party.gno
5.03 Kb · 277 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. The fish realm pays a kill.
3// Not deployed.
4package party
5
6import (
7 "chain"
8 "chain/runtime/unsafe"
9 "strconv"
10)
11
12const (
13 partyCap = 8
14 nameMax = 16
15)
16
17type crew struct {
18 id int
19 name string
20 leader address
21 members []address
22 closed bool
23}
24
25var (
26 crews []*crew
27 seat map[address]int
28 invited map[address]int
29 nextPID int
30)
31
32func Create(cur realm, name string) int {
33 caller := requireUser(cur)
34 takeExact(0)
35 if seatOf(caller) != 0 {
36 panic("party: seat")
37 }
38 clean := cleanName(name)
39 if clean == "" || taken(clean) {
40 panic("party: name")
41 }
42 ensure()
43 nextPID++
44 id := nextPID
45 body := &crew{
46 id: id,
47 name: clean,
48 leader: caller,
49 members: []address{caller},
50 }
51 for len(crews) <= id {
52 crews = append(crews, nil)
53 }
54 crews[id] = body
55 seat[caller] = id
56 chain.Emit("Create", "id", strconv.Itoa(id), "leader", caller.String(), "name", clean)
57 return id
58}
59
60func Invite(cur realm, who address) {
61 caller := requireUser(cur)
62 takeExact(0)
63 if !who.IsValid() {
64 panic("party: addr")
65 }
66 if who == caller {
67 panic("party: self")
68 }
69 id := seatOf(caller)
70 body := openCrew(id)
71 if seatOf(who) != 0 {
72 panic("party: seat")
73 }
74 if len(body.members) >= partyCap {
75 panic("party: full")
76 }
77 ensure()
78 invited[who] = id
79 chain.Emit("Invite", "id", strconv.Itoa(id), "who", who.String())
80}
81
82func Accept(cur realm) int {
83 caller := requireUser(cur)
84 takeExact(0)
85 if seatOf(caller) != 0 {
86 panic("party: seat")
87 }
88 id := 0
89 if invited != nil {
90 id = invited[caller]
91 }
92 body := openCrew(id)
93 if len(body.members) >= partyCap {
94 panic("party: full")
95 }
96 delete(invited, caller)
97 body.members = append(body.members, caller)
98 seat[caller] = id
99 chain.Emit("Join", "id", strconv.Itoa(id), "who", caller.String())
100 return id
101}
102
103func Leave(cur realm) {
104 caller := requireUser(cur)
105 takeExact(0)
106 id := seatOf(caller)
107 body := openCrew(id)
108 dropMember(body, caller)
109 chain.Emit("Leave", "id", strconv.Itoa(id), "who", caller.String())
110}
111
112func Kick(cur realm, who address) {
113 caller := requireUser(cur)
114 takeExact(0)
115 id := seatOf(caller)
116 body := openCrew(id)
117 if body.leader != caller {
118 panic("party: leader")
119 }
120 if who == caller || seatOf(who) != id {
121 panic("party: member")
122 }
123 dropMember(body, who)
124 chain.Emit("Kick", "id", strconv.Itoa(id), "who", who.String())
125}
126
127func Disband(cur realm) {
128 caller := requireUser(cur)
129 takeExact(0)
130 id := seatOf(caller)
131 body := openCrew(id)
132 if body.leader != caller {
133 panic("party: leader")
134 }
135 for _, member := range body.members {
136 delete(seat, member)
137 }
138 body.members = nil
139 body.closed = true
140 body.name = ""
141 dropInvites(id)
142 chain.Emit("Disband", "id", strconv.Itoa(id))
143}
144
145func PartyOf(cur realm, who address) int {
146 takeExact(0)
147 return seatOf(who)
148}
149
150func SameParty(cur realm, a, b address) bool {
151 takeExact(0)
152 id := seatOf(a)
153 return id != 0 && id == seatOf(b)
154}
155
156func NameOf(cur realm, id int) string {
157 takeExact(0)
158 return openCrew(id).name
159}
160
161func LeaderOf(cur realm, id int) address {
162 takeExact(0)
163 return openCrew(id).leader
164}
165
166func MemberCount(cur realm, id int) int {
167 takeExact(0)
168 return len(openCrew(id).members)
169}
170
171func MemberAt(cur realm, id, index int) address {
172 takeExact(0)
173 body := openCrew(id)
174 if index < 0 || index >= len(body.members) {
175 panic("party: index")
176 }
177 return body.members[index]
178}
179
180func requireUser(cur realm) address {
181 if !cur.Previous().IsUserCall() {
182 panic("party: eoa")
183 }
184 return cur.Previous().Address()
185}
186
187func takeExact(n int64) {
188 sent := unsafe.OriginSend()
189 if n == 0 {
190 if len(sent) != 0 {
191 panic("party: payment")
192 }
193 return
194 }
195 panic("party: payment")
196}
197
198func ensure() {
199 if seat == nil {
200 seat = make(map[address]int)
201 }
202 if invited == nil {
203 invited = make(map[address]int)
204 }
205}
206
207func seatOf(who address) int {
208 if seat == nil || !who.IsValid() {
209 return 0
210 }
211 return seat[who]
212}
213
214func openCrew(id int) *crew {
215 if id < 1 || id >= len(crews) || crews[id] == nil || crews[id].closed {
216 panic("party: none")
217 }
218 return crews[id]
219}
220
221func taken(name string) bool {
222 for _, body := range crews {
223 if body != nil && !body.closed && body.name == name {
224 return true
225 }
226 }
227 return false
228}
229
230func cleanName(name string) string {
231 if len(name) < 1 || len(name) > nameMax {
232 return ""
233 }
234 for _, c := range name {
235 if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && c != ' ' {
236 return ""
237 }
238 }
239 return name
240}
241
242func dropMember(body *crew, who address) {
243 next := make([]address, 0, len(body.members))
244 for _, member := range body.members {
245 if member != who {
246 next = append(next, member)
247 }
248 }
249 delete(seat, who)
250 if invited != nil {
251 delete(invited, who)
252 }
253 if len(next) == 0 {
254 body.members = nil
255 body.closed = true
256 body.name = ""
257 dropInvites(body.id)
258 return
259 }
260 body.members = next
261 body.leader = next[0]
262}
263
264func dropInvites(id int) {
265 if invited == nil {
266 return
267 }
268 var drop []address
269 for who, pid := range invited {
270 if pid == id {
271 drop = append(drop, who)
272 }
273 }
274 for _, who := range drop {
275 delete(invited, who)
276 }
277}