Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

party.gno

6.24 Kb · 318 lines
  1// Package party is the Shoal crew book. Members share a fight and do not
  2// bite each other. This realm holds no ugnot and no FISH. The fish realm pays a kill.
  3// Deployed on onyx-1 at gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/shoal/party.
  4// The older gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/party package stays as it was.
  5package party
  6
  7import (
  8	"chain"
  9	"chain/runtime/unsafe"
 10	"strconv"
 11)
 12
 13const (
 14	partyCap = 8
 15	nameMax  = 16
 16	capFloor = 2
 17	capCeil  = 32
 18)
 19
 20type crew struct {
 21	id      int
 22	name    string
 23	leader  address
 24	members []address
 25	closed  bool
 26}
 27
 28var (
 29	crews    []*crew
 30	seat     map[address]int
 31	invited  map[address]int
 32	nextPID  int
 33	crewCap  = partyCap
 34	admin    address
 35	adminSet bool
 36)
 37
 38func Create(cur realm, name string) int {
 39	caller := requireUser(cur)
 40	takeExact(0)
 41	if seatOf(caller) != 0 {
 42		panic("party: seat")
 43	}
 44	clean := cleanName(name)
 45	if clean == "" || taken(clean) {
 46		panic("party: name")
 47	}
 48	ensure()
 49	nextPID++
 50	id := nextPID
 51	body := &crew{
 52		id:      id,
 53		name:    clean,
 54		leader:  caller,
 55		members: []address{caller},
 56	}
 57	for len(crews) <= id {
 58		crews = append(crews, nil)
 59	}
 60	crews[id] = body
 61	seat[caller] = id
 62	chain.Emit("Create", "id", strconv.Itoa(id), "leader", caller.String(), "name", clean)
 63	return id
 64}
 65
 66func Invite(cur realm, who address) {
 67	caller := requireUser(cur)
 68	takeExact(0)
 69	if !who.IsValid() {
 70		panic("party: addr")
 71	}
 72	if who == caller {
 73		panic("party: self")
 74	}
 75	id := seatOf(caller)
 76	body := openCrew(id)
 77	if seatOf(who) != 0 {
 78		panic("party: seat")
 79	}
 80	if len(body.members) >= crewCap {
 81		panic("party: full")
 82	}
 83	ensure()
 84	invited[who] = id
 85	chain.Emit("Invite", "id", strconv.Itoa(id), "who", who.String())
 86}
 87
 88func Accept(cur realm) int {
 89	caller := requireUser(cur)
 90	takeExact(0)
 91	if seatOf(caller) != 0 {
 92		panic("party: seat")
 93	}
 94	id := 0
 95	if invited != nil {
 96		id = invited[caller]
 97	}
 98	body := openCrew(id)
 99	if len(body.members) >= crewCap {
100		panic("party: full")
101	}
102	delete(invited, caller)
103	body.members = append(body.members, caller)
104	seat[caller] = id
105	chain.Emit("Join", "id", strconv.Itoa(id), "who", caller.String())
106	return id
107}
108
109func Leave(cur realm) {
110	caller := requireUser(cur)
111	takeExact(0)
112	id := seatOf(caller)
113	body := openCrew(id)
114	dropMember(body, caller)
115	chain.Emit("Leave", "id", strconv.Itoa(id), "who", caller.String())
116}
117
118func Kick(cur realm, who address) {
119	caller := requireUser(cur)
120	takeExact(0)
121	id := seatOf(caller)
122	body := openCrew(id)
123	if body.leader != caller {
124		panic("party: leader")
125	}
126	if who == caller || seatOf(who) != id {
127		panic("party: member")
128	}
129	dropMember(body, who)
130	chain.Emit("Kick", "id", strconv.Itoa(id), "who", who.String())
131}
132
133func Disband(cur realm) {
134	caller := requireUser(cur)
135	takeExact(0)
136	id := seatOf(caller)
137	body := openCrew(id)
138	if body.leader != caller {
139		panic("party: leader")
140	}
141	for _, member := range body.members {
142		delete(seat, member)
143	}
144	body.members = nil
145	body.closed = true
146	body.name = ""
147	dropInvites(id)
148	chain.Emit("Disband", "id", strconv.Itoa(id))
149}
150
151// Cap is the seat limit for a new invite. The default is 8.
152func Cap() int { return crewCap }
153
154// Version names this local package shape. The live party realm is unchanged.
155func Version() string { return "party-1" }
156
157// Admin is the address that claimed the cap hook. It is empty until ClaimAdmin.
158func Admin() address { return admin }
159
160// ClaimAdmin records the first caller. A later caller cannot replace them here.
161func ClaimAdmin(cur realm) {
162	caller := requireUser(cur)
163	takeExact(0)
164	if adminSet {
165		panic("party: admin")
166	}
167	admin = caller
168	adminSet = true
169	chain.Emit("ClaimAdmin", "who", caller.String())
170}
171
172// SetCap changes how many seats a crew may fill. Crews that are already larger stay as they are.
173func SetCap(cur realm, n int) {
174	caller := requireUser(cur)
175	takeExact(0)
176	if !adminSet || caller != admin {
177		panic("party: admin")
178	}
179	if n < capFloor || n > capCeil {
180		panic("party: cap")
181	}
182	crewCap = n
183	chain.Emit("SetCap", "n", strconv.Itoa(n))
184}
185
186func PartyOf(cur realm, who address) int {
187	takeExact(0)
188	return seatOf(who)
189}
190
191func SameParty(cur realm, a, b address) bool {
192	takeExact(0)
193	id := seatOf(a)
194	return id != 0 && id == seatOf(b)
195}
196
197func NameOf(cur realm, id int) string {
198	takeExact(0)
199	return openCrew(id).name
200}
201
202func LeaderOf(cur realm, id int) address {
203	takeExact(0)
204	return openCrew(id).leader
205}
206
207func MemberCount(cur realm, id int) int {
208	takeExact(0)
209	return len(openCrew(id).members)
210}
211
212func MemberAt(cur realm, id, index int) address {
213	takeExact(0)
214	body := openCrew(id)
215	if index < 0 || index >= len(body.members) {
216		panic("party: index")
217	}
218	return body.members[index]
219}
220
221func requireUser(cur realm) address {
222	if !cur.Previous().IsUserCall() {
223		panic("party: eoa")
224	}
225	return cur.Previous().Address()
226}
227
228func takeExact(n int64) {
229	sent := unsafe.OriginSend()
230	if n == 0 {
231		if len(sent) != 0 {
232			panic("party: payment")
233		}
234		return
235	}
236	panic("party: payment")
237}
238
239func ensure() {
240	if seat == nil {
241		seat = make(map[address]int)
242	}
243	if invited == nil {
244		invited = make(map[address]int)
245	}
246}
247
248func seatOf(who address) int {
249	if seat == nil || !who.IsValid() {
250		return 0
251	}
252	return seat[who]
253}
254
255func openCrew(id int) *crew {
256	if id < 1 || id >= len(crews) || crews[id] == nil || crews[id].closed {
257		panic("party: none")
258	}
259	return crews[id]
260}
261
262func taken(name string) bool {
263	for _, body := range crews {
264		if body != nil && !body.closed && body.name == name {
265			return true
266		}
267	}
268	return false
269}
270
271func cleanName(name string) string {
272	if len(name) < 1 || len(name) > nameMax {
273		return ""
274	}
275	for _, c := range name {
276		if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && c != ' ' {
277			return ""
278		}
279	}
280	return name
281}
282
283func dropMember(body *crew, who address) {
284	next := make([]address, 0, len(body.members))
285	for _, member := range body.members {
286		if member != who {
287			next = append(next, member)
288		}
289	}
290	delete(seat, who)
291	if invited != nil {
292		delete(invited, who)
293	}
294	if len(next) == 0 {
295		body.members = nil
296		body.closed = true
297		body.name = ""
298		dropInvites(body.id)
299		return
300	}
301	body.members = next
302	body.leader = next[0]
303}
304
305func dropInvites(id int) {
306	if invited == nil {
307		return
308	}
309	var drop []address
310	for who, pid := range invited {
311		if pid == id {
312			drop = append(drop, who)
313		}
314	}
315	for _, who := range drop {
316		delete(invited, who)
317	}
318}