]> Pileus Git - ~andy/rhawk/blob - spades.awk
Remove special characters from the logs
[~andy/rhawk] / spades.awk
1 # For saving
2 @include "json.awk"
3
4 # Functions
5 function sp_init(cards, tmp0, tmp1)
6 {
7         # Init deck
8         cards ="As Ks Qs Js 10s 9s 8s 7s 6s 5s 4s 3s 2s "\
9                "Ah Kh Qh Jh 10h 9h 8h 7h 6h 5h 4h 3h 2h "\
10                "Ac Kc Qc Jc 10c 9c 8c 7c 6c 5c 4c 3c 2c "\
11                "Ad Kd Qd Jd 10d 9d 8d 7d 6d 5d 4d 3d 2d"
12         split(cards, tmp0)
13         for (i=1; i<=length(tmp0); i++)
14                 sp_deck[tmp0[i]] = i
15 }
16
17 function sp_reset(type)
18 {
19         # Per message
20         if (type <  0) {
21                 sp_from     = ""    #    The speakers player name
22                 sp_valid    = ""    #    It is the speaker turn
23         }
24
25         # Per hand
26         if (type >= 0) {
27                 sp_suit     = ""    #     The lead suit {s,h,d,c}
28                 sp_piles    = ""    # [x] Played cards this turn
29                 delete sp_pile      # [x] Played cards this turn
30         }
31
32         # Per round
33         if (type >= 1) {
34                 sp_state    = "bid" #     {new,join,bid,pass,play}
35                 sp_broken   = 0     #     Whether spades are broken
36                 delete sp_last      # [x] The result of the last hand
37                 delete sp_hands     # [p] Each players cards
38                 delete sp_looked    # [i] Whether a player has looked a their cards
39                 delete sp_bids      # [i] Each players bid
40                 delete sp_nil       # [i] Nil multiplier 0=regular, 1=nil, 2=blind
41                 delete sp_pass      # [i] Cards to pass
42                 delete sp_tricks    # [i] Tricks this round
43         }
44
45         # Per game
46         if (type >= 2) {
47                 sp_state    = "new" #     {new,join,bid,pass,play}
48                 sp_owner    = ""    #     Who started the game
49                 sp_playto   = 0     #     Score the game will go to
50                 sp_dealer   =-1     #     Who is dealing this round
51                 sp_turn     = 0     #     Index of who's turn it is
52                 sp_player   = ""    #     Who's turn it is
53                 sp_limit    = 10    #     Bag out limit / nil bonus
54                 delete sp_players   # [p] Player names players["name"] -> i
55                 delete sp_auths     # [c] Player auth names auths["auth"] -> "name"
56                 delete sp_share     # [c] Player teammates share["friend"] -> "name"
57                 delete sp_order     # [i] Player order order[i] -> "name"
58                 delete sp_scores    # [i] Teams score
59                 delete sp_teams     # [i] Teams names
60         }
61
62         # Persistent
63         if (type >= 3) {
64                 sp_channel  = ""    #     channel to play in
65                 sp_log      = ""    #     Log file name
66                 sp_sock     = ""    #     UDP log socket
67                 delete sp_notify    # [p] E-mail notification address
68         }
69 }
70
71 function sp_acopy(dst, src,     key)
72 {
73         if (isarray(src)) {
74                 delete(dst)
75                 for (key in src)
76                         json_copy(dst, key, src[key])
77         }
78 }
79
80 function sp_save(file,  game)
81 {
82         # Per hand
83         game["suit"]    = sp_suit;
84         game["piles"]   = sp_piles;
85         json_copy(game, "pile",    sp_pile);
86
87         # Per round
88         game["state"]   = sp_state;
89         game["broken"]  = sp_broken;
90         json_copy(game, "last",    sp_last);
91         json_copy(game, "looked",  sp_looked);
92         json_copy(game, "bids",    sp_bids);
93         json_copy(game, "nil",     sp_nil);
94         json_copy(game, "pass",    sp_pass);
95         json_copy(game, "tricks",  sp_tricks);
96
97         # Per game
98         game["owner"]   = sp_owner;
99         game["playto"]  = sp_playto;
100         game["dealer"]  = sp_dealer;
101         game["turn"]    = sp_turn;
102         game["player"]  = sp_player;
103         game["limit"]   = sp_limit;
104         json_copy(game, "hands",   sp_hands);
105         json_copy(game, "players", sp_players);
106         json_copy(game, "auths",   sp_auths);
107         json_copy(game, "share",   sp_share);
108         json_copy(game, "order",   sp_order);
109         json_copy(game, "scores",  sp_scores);
110         json_copy(game, "teams",   sp_teams);
111
112         # Persistent
113         game["channel"] = sp_channel;
114         game["log"]     = sp_log;
115         json_copy(game, "notify",  sp_notify);
116
117         # Save
118         json_save(file, game);
119 }
120
121 function sp_load(file,  game)
122 {
123         # Load
124         if (!json_load(file, game))
125                 return
126
127         # Per hand
128         sp_suit    = game["suit"];
129         sp_piles   = game["piles"];
130         sp_acopy(sp_pile,    game["pile"]);
131
132         # Per round
133         sp_state   = game["state"];
134         sp_broken  = game["broken"];
135         sp_acopy(sp_last,    game["last"]);
136         sp_acopy(sp_looked,  game["looked"]);
137         sp_acopy(sp_bids,    game["bids"]);
138         sp_acopy(sp_nil,     game["nil"]);
139         sp_acopy(sp_pass,    game["pass"]);
140         sp_acopy(sp_tricks,  game["tricks"]);
141
142         # Per game
143         sp_owner   = game["owner"];
144         sp_playto  = game["playto"];
145         sp_dealer  = game["dealer"];
146         sp_turn    = game["turn"];
147         sp_player  = game["player"];
148         sp_limit   = game["limit"];
149         sp_acopy(sp_hands,   game["hands"]);
150         sp_acopy(sp_players, game["players"]);
151         sp_acopy(sp_auths,   game["auths"]);
152         sp_acopy(sp_share,   game["share"]);
153         sp_acopy(sp_order,   game["order"]);
154         sp_acopy(sp_scores,  game["scores"]);
155         sp_acopy(sp_teams,   game["teams"]);
156
157         # Persistent
158         sp_channel = game["channel"];
159         sp_log     = game["log"];
160         sp_acopy(sp_notify,  game["notify"]);
161 }
162
163 function sp_say(msg)
164 {
165         say(sp_channel, msg)
166         print msg |& sp_sock
167         print strftime("%Y-%m-%d %H:%M:%S | ") sp_ugly(msg) >> "logs/" sp_log
168         fflush("logs/" sp_log)
169 }
170
171 function sp_pretty(cards, who)
172 {
173         if (!nocolor[who]) {
174                 gsub(/[0-9JQKA]*[sc]/, "\0031,00\002&\017", cards) # black
175                 gsub(/[0-9JQKA]*[hd]/, "\0034,00\002&\017", cards) # red
176         }
177         if (!nounicode[who]) {
178                 gsub(/s/, "\002♠", cards)
179                 gsub(/h/, "\002♥", cards)
180                 gsub(/d/, "\002♦", cards)
181                 gsub(/c/, "\002♣", cards)
182         }
183         return cards
184 }
185
186 function sp_ugly(cards, who)
187 {
188         gsub(/[\2\17]|\3[14],00|/, "", cards)
189         gsub(/♠/, "s", cards)
190         gsub(/♥/, "h", cards)
191         gsub(/♦/, "d", cards)
192         gsub(/♣/, "c", cards)
193         return cards
194 }
195
196 function sp_next(who, prev)
197 {
198         prev      = sp_turn
199         sp_turn   = who ? sp_players[who] : (sp_turn + 1) % 4
200         if (length(sp_order) == 4)
201                 sp_player = sp_order[sp_turn]
202         return prev
203 }
204
205 function sp_shuf(i, mixed)
206 {
207         sp_usort(sp_players, mixed)
208         for (i in mixed) {
209                 sp_order[i-1] = mixed[i]
210                 sp_players[mixed[i]] = i-1
211         }
212 }
213
214 function sp_deal(       shuf)
215 {
216         sp_say("/me deals the cards")
217         sp_usort(sp_deck, shuf)
218         for (i=1; i<=52; i++)
219                 sp_hands[sp_order[i%4]][shuf[i]] = 1
220         sp_state  = "bid"
221         sp_dealer = (sp_dealer+1)%4
222         sp_turn   =  sp_dealer
223         sp_player =  sp_order[sp_turn]
224         sp_say(sp_player ": you bid first!")
225 }
226
227 function sp_hand(to, who,       sort, str)
228 {
229         asorti(sp_hands[who], sort, "sp_csort")
230         for (i=0; i<length(sort); i++)
231                 str = str "" sprintf("%4s", sort[i])
232         gsub(/^ +| +$/, "", str)
233         return sp_pretty(str, to)
234 }
235
236 function sp_hasa(who, expr)
237 {
238         for (c in sp_hands[who]) {
239                 if (c ~ expr)
240                         return 1
241         }
242         return 0
243 }
244
245 function sp_type(card)
246 {
247         return substr(card, length(card))
248 }
249
250 function sp_usort(list, out) {
251         for (i in list)
252                 out[i] = rand()
253         asorti(out, out, "@val_num_asc")
254 }
255
256 function sp_csort(i1,v1,i2,v2) {
257         return sp_deck[i1] > sp_deck[i2] ? +1 :
258                sp_deck[i1] < sp_deck[i2] ? -1 : 0;
259 }
260
261 function sp_winner(     card, tmp)
262 {
263         for (card in sp_pile)
264                 if (card !~ sp_suit && card !~ /s/)
265                         delete sp_pile[card]
266         asorti(sp_pile, tmp, "sp_csort")
267         #print "pile: " tmp[1] ">" tmp[2] ">" tmp[3] ">" tmp[4]
268         return tmp[1]
269 }
270
271 function sp_team(i, players)
272 {
273         #return "{" sp_order[i+0] "," sp_order[i+2] "}"
274         if ((i in sp_teams) && !players)
275                 return sp_teams[i]
276         else
277                 return sp_order[i+0] "/" sp_order[i+2]
278 }
279
280 function sp_bags(i,     bags)
281 {
282         bags = sp_scores[i] % sp_limit
283         if (bags < 0)
284                 bags += sp_limit
285         return bags
286 }
287
288 function sp_bid(who)
289 {
290         return sp_nil[who] == 0 ? sp_bids[who] :
291                sp_nil[who] == 1 ? "nil"        :
292                sp_nil[who] == 2 ? "blind"      : "n/a"
293 }
294
295 function sp_passer(who)
296 {
297         return sp_nil[(who+0)%4] == 2 || sp_nil[(who+1)%4] != 0 ||
298                sp_nil[(who+2)%4] == 2 || sp_nil[(who+3)%4] != 0
299 }
300
301 function sp_bidders(    i, turn, bid, bids)
302 {
303         for (i = 0; i < 4; i++) {
304                 turn = (sp_dealer + i) % 4
305                 if (bid = sp_bid(turn))
306                         bids = bids " " sp_order[turn] ":" bid
307         }
308         gsub(/^ +| +$/, "", bids)
309         return bids
310 }
311
312 function sp_extra(      n, s)
313 {
314         n = sp_bids[0] + sp_bids[1] + sp_bids[2] + sp_bids[3];
315         s = n == 12 || n == 14 ? "" : "s";
316
317         return n<13 ? "Playing with " 13-n " bag"   s "!" :
318                n>13 ? "Fighting for " n-13 " trick" s "!" : "No bags!";
319 }
320
321 function sp_score(      bids, times, tricks)
322 {
323         for (i=0; i<2; i++) {
324                 bids   = sp_bids[i]   + sp_bids[i+2]
325                 tricks = sp_tricks[i] + sp_tricks[i+2]
326                 bags   = tricks - bids
327                 times  = int((sp_bags(i) + bags) / sp_limit)
328                 if (times > 0) {
329                         sp_say(sp_team(i) " bag" (times>1?" way ":" ") "out")
330                         sp_scores[i] -= sp_limit * 10 * times;
331                 }
332                 if (tricks >= bids) {
333                         sp_say(sp_team(i) " make their bid: " tricks "/" bids)
334                         sp_scores[i] += bids*10 + bags;
335                 } else {
336                         sp_say(sp_team(i) " go bust: " tricks "/" bids)
337                         sp_scores[i] -= bids*10;
338                 }
339         }
340         for (i=0; i<4; i++) {
341                 if (!sp_nil[i])
342                         continue
343                 sp_say(sp_order[i] " " \
344                     (sp_nil[i] == 1 && !sp_tricks[i] ? "makes nil!"       :
345                      sp_nil[i] == 1 &&  sp_tricks[i] ? "fails at nil!"    :
346                      sp_nil[i] == 2 && !sp_tricks[i] ? "makes blind nil!" :
347                      sp_nil[i] == 2 &&  sp_tricks[i] ? "fails miserably at blind nil!" :
348                                                        "unknown"))
349                 sp_scores[i%2] += sp_limit * 10 * sp_nil[i] * \
350                         (sp_tricks[i] == 0 ? 1 : -1)
351         }
352         if (sp_scores[0] > sp_scores[1])
353                 sp_say(sp_team(0) " lead " sp_scores[0] " to " sp_scores[1] " of " sp_playto)
354         else if (sp_scores[1] > sp_scores[0])
355                 sp_say(sp_team(1) " lead " sp_scores[1] " to " sp_scores[0] " of " sp_playto)
356         else
357                 sp_say("tied at " sp_scores[0] " of " sp_playto)
358 }
359
360 function sp_play(card,  winner, pi)
361 {
362         delete sp_hands[sp_from][card]
363         sp_pile[card] = sp_player
364         sp_piles      = sp_piles (sp_piles?",":"") card
365         sp_next()
366
367         if (card ~ /s/)
368                 sp_broken = 1
369
370         # Start hand
371         if (length(sp_pile) == 1)
372                 sp_suit = sp_type(card)
373
374         # Finish hand
375         if (length(sp_pile) == 4) {
376                 winner = sp_winner()
377                 pi     = sp_players[sp_pile[winner]]
378                 sp_tricks[pi]++
379                 sp_say(sp_pile[winner] " wins with " sp_pretty(winner, FROM) \
380                        " (" sp_pretty(sp_piles, FROM) ")")
381                 sp_last["player"] = sp_pile[winner];
382                 sp_last["pile"]   = sp_piles;
383                 sp_next(sp_pile[winner])
384                 sp_reset(0)
385         }
386
387         # Finish round
388         if (sp_tricks[0] + sp_tricks[1] + \
389             sp_tricks[2] + sp_tricks[3] == 13) {
390                 sp_say("Round over!")
391                 sp_score()
392                 if ((sp_scores[0] >= sp_playto || sp_scores[1] >= sp_playto) &&
393                     (sp_scores[0]              != sp_scores[1])) {
394                         sp_say("Game over!")
395                         winner = sp_scores[0] > sp_scores[1] ? 0 : 1
396                         looser = !winner
397                         say(CHANNEL, sp_team(winner) " wins the game " \
398                             sp_scores[winner] " to " sp_scores[looser])
399                         say(CHANNEL, sp_order[winner+0] "++")
400                         say(CHANNEL, sp_order[winner+2] "++")
401                         sp_reset(2)
402
403                 } else {
404                         if (sp_scores[0] == sp_scores[1] &&
405                             sp_scores[0] >= sp_playto)
406                                 sp_say("It's a tie! Playing an extra round!");
407                         sp_reset(1)
408                         sp_deal()
409                 }
410         }
411 }
412
413 # Statistics
414 function sp_delay(sec)
415 {
416         return (sec > 60*60*24 ? int(sec/60/60/24) "d " : "") \
417                (sec > 60*60    ? int(sec/60/60)%24 "h " : "") \
418                                  int(sec/60)%60    "m"
419 }
420
421 function sp_max(list,    i, max)
422 {
423         for (i=0; i<length(list); i++)
424                 if (max == "" || list[i] > max)
425                         max = list[i]
426         return max
427 }
428
429 function sp_avg(list,    i, sum)
430 {
431         for (i=0; i<length(list); i++)
432                 sum += list[i]
433         return sum / length(list)
434 }
435
436 function sp_cur(list)
437 {
438         return list[length(list)-1]
439 }
440
441 function sp_stats(file,   line, arr, time, user, turn, start, delay, short, extra)
442 {
443         # Process log file
444         while ((stat = getline line < file) > 0) {
445                 # Parse date
446                 if (!match(line, /^([0-9\- \:]*) \| (.*)$/, arr))
447                         continue
448                 gsub(/[:-]/, " ", arr[1])
449                 time = mktime(arr[1])
450
451                 # Parse user
452                 if (!match(arr[2], /^([^:]*): (.*)$/, arr))
453                         continue
454                 user = arr[1]
455
456                 # Record user latency
457                 if (turn) {
458                         delay[turn][length(delay[turn])] = time - start
459                         turn  = 0
460                 }
461                 if (match(arr[2], /^(it is your|you .*(first|lead)!$)/, arr)) {
462                         turn  = user
463                         start = time
464                 }
465         }
466         close(file)
467
468         # Add current latency
469         if (turn) {
470                 delay[turn][length(delay[turn])] = systime() - start
471                 debug("time: " (systime() - start))
472         }
473
474         # Check for error
475         if (stat < 0)
476                 reply("File does not exist: " file);
477
478         # Output statistics
479         for (user in delay) {
480                 short = length(user) <= 4 ? user : substr(user, 0, 4)
481                 extra = (user != turn) ? "" : \
482                         ", " sp_delay(sp_cur(delay[user])) " (cur)";
483                 say("latency for " short \
484                         ": " sp_delay(sp_avg(delay[user])) " (avg)" \
485                         ", " sp_delay(sp_max(delay[user])) " (max)" extra)
486         }
487 }
488
489 # Misc
490 BEGIN {
491         cmd = "od -An -N4 -td4 /dev/random"
492         cmd | getline seed
493         close(cmd)
494         srand(seed)
495         sp_init()
496         sp_reset(2)
497         sp_load("var/sp_cur.json")
498         sp_sock = "/inet/udp/0/localhost/6173"
499         print "starting rhawk" |& sp_sock
500         #if (sp_channel)
501         #       sp_say("Game restored.")
502 }
503
504 // {
505         sp_from  = AUTH in sp_auths ? sp_auths[AUTH] : \
506                    AUTH in sp_share ? sp_share[AUTH] : FROM
507         sp_valid = sp_from && sp_from == sp_player
508 }
509
510 CMD == "PRIVMSG" &&
511 ! /help/ &&
512 /[Ss]pades/ {
513         say("Spades! " sp_pretty("As,Ah,Ad,Ac", FROM))
514 }
515
516 AUTH == OWNER &&
517 /^\.savegame/ {
518         sp_save("var/sp_save.json");
519         say("Game saved.")
520 }
521
522 AUTH == OWNER &&
523 /^\.loadgame/ {
524         sp_load("var/sp_save.json");
525         say("Game loaded.")
526 }
527
528 # Help
529 /^\.help$/ {
530         say(".help spades -- play a game of spades")
531 }
532
533 /^\.help [Ss]pades$/ {
534         say("Spades -- play a game of spades")
535         say(".help game -- setup and administer the game")
536         say(".help play -- commands for playing spades")
537         say(".help auth -- control player authorization")
538         next
539 }
540
541 /^\.help game$/ {
542         say(".newgame [score] -- start a game to <score> points, default 300")
543         say(".endgame -- abort the current game")
544         say(".savegame -- save the current game to disk")
545         say(".loadgame -- load the previously saved game")
546         next
547 }
548
549 /^\.help play$/ {
550         say(".join -- join the current game")
551         say(".look -- look at your cards")
552         say(".bid [n] -- bid for <n> tricks")
553         say(".pass [card] -- pass a card to your partner")
554         say(".play [card] -- play a card")
555         say(".team [name] -- set your team name")
556         say(".last -- show who took the previous trick")
557         say(".turn -- check whose turn it is")
558         say(".bids -- check what everyone bid")
559         say(".tricks -- check how many trick have been taken")
560         say(".score -- check the score")
561         next
562 }
563
564 /^\.help auth$/ {
565         say(".auth [who] -- display authentication info for a user")
566         say(".allow [who] -- allow another person to play on your behalf")
567         say(".deny [who] -- prevent a previously allowed user from playing")
568         say(".show -- display which users can play for which players")
569         say(".notify [addr] -- email user when it is their turn")
570         next
571 }
572
573 # Debugging
574 AUTH == OWNER &&
575 /^\.deal (\w+) (.*)/ {
576         sp_say(FROM " is cheating for " $2)
577         delete sp_hands[$2]
578         for (i=3; i<=NF; i++)
579                 sp_hands[$2][$i] = 1
580         next
581 }
582
583 AUTH == OWNER &&
584 /^\.order (\w+) ([0-4])/ {
585         sp_say(FROM " is cheating for " $2)
586         sp_order[$3] = $2
587         sp_players[$2] = $3
588         sp_player = sp_order[sp_turn]
589 }
590
591 AUTH == OWNER &&
592 sp_state == "play" &&
593 /^\.force (\w+) (\S+)$/ {
594         sp_say(FROM " is cheating for " $2)
595         sp_from = $2
596         sp_play($3)
597         next
598 }
599
600
601 # Setup
602 match($0, /^\.newgame ?([1-9][0-9]*) *- *([1-9][0-9]*)$/, _arr) {
603         if (_arr[2] > _arr[1])
604                 $0 = $1 " " int(rand() * (_arr[2]-_arr[1])+_arr[1])
605 }
606
607 /^\.newgame ?([1-9][0-9]*)?$/ {
608         if (sp_state != "new") {
609                 reply("There is already a game in progress.")
610         } else {
611                 $1         = ".join"
612                 sp_owner   = FROM
613                 sp_playto  = $2 ? $2 : 300
614                 sp_limit   = sp_playto > 200 ? 10 : 5;
615                 sp_state   = "join"
616                 sp_channel = DST
617                 sp_log     = strftime("%Y%m%d_%H%M%S.log")
618                 sp_say(sp_owner " starts a game of Spades to " sp_playto " with " sp_limit " bags!")
619         }
620 }
621
622 (sp_from == sp_owner || AUTH == OWNER) &&
623 /^\.endgame$/ {
624         if (sp_state == "new") {
625                 reply("There is no game in progress.")
626         } else {
627                 sp_say(FROM " ends the game")
628                 sp_reset(2)
629         }
630 }
631
632 /^\.join/ {
633         if (sp_state == "new") {
634                 reply("There is no game in progress")
635         }
636         else if (sp_state == "play") {
637                 reply("The game has already started")
638         }
639         else if (sp_state == "join" && sp_from in sp_players) {
640                 reply("You are already playing")
641         }
642         else if (sp_state == "join") {
643                 i = sp_next()
644                 sp_players[FROM] = i
645                 if (AUTH)
646                         sp_auths[AUTH] = FROM
647                 sp_order[i] = FROM
648                 sp_say(FROM " joins the game!")
649         }
650         if (sp_state == "join" && sp_turn == 0) {
651                 sp_shuf()
652                 sp_deal()
653         }
654 }
655
656 /^\.allow \S+$/ {
657         _who = $2 in USERS ? USERS[$2]["auth"] : ""
658         _str = _who && _who != $2 ? $2 " (" _who ")" : $2
659         if (sp_state ~ "new|join") {
660                 reply("The game has not yet started")
661         }
662         else if (!(sp_from in sp_players)) {
663                 reply("You are not playing")
664         }
665         else if (!_who) {
666                 reply(_str " is not logged in")
667         }
668         else if (_who in sp_players || _who in sp_auths) {
669                 reply(_str " is a primary player")
670         }
671         else if (_who in sp_share) {
672                 reply(_str " is already playing for " sp_share[_who])
673         }
674         else {
675                 sp_say(_str " can now play for " sp_from)
676                 sp_share[_who] = sp_from
677         }
678 }
679
680 /^\.deny \S+$/ {
681         _who = $2 in USERS ? USERS[$2]["auth"] : $2
682         _str = _who && _who != $2 ? $2 " (" _who ")" : $2
683         if (sp_state ~ "new|join") {
684                 reply("The game has not yet started")
685         }
686         else if (!(sp_from in sp_players)) {
687                 reply("You are not playing")
688         }
689         else if (_who in sp_players || _who in sp_auths) {
690                 reply(_str " is a primary player")
691         }
692         else if (!(_who in sp_share) || sp_share[_who] != sp_from) {
693                 reply(_str " is not playing for " sp_from)
694         }
695         else {
696                 sp_say(_str " can no longer play for " sp_from)
697                 delete sp_share[_who]
698         }
699 }
700
701 /^\.team/ {
702         gsub(/^\.team */, "")
703         _team = sp_from in sp_players ? sp_players[sp_from] % 2 : 0
704         if (sp_state ~ "new|join") {
705                 reply("The game has not yet started")
706         }
707         else if (!(sp_from in sp_players)) {
708                 reply("You are not playing")
709         }
710         else if ($0 ~ /^[^a-zA-Z0-9]/) {
711                 reply("Invalid team name")
712         }
713         else if ($0 ~ /^./) {
714                 sp_teams[_team] = substr($0, 0, 32)
715                 sp_say(sp_team(_team,1) " are now known as " sp_team(_team))
716         }
717         else {
718                 delete sp_teams[_team]
719                 sp_say(sp_team(_team,1) " are boring")
720         }
721 }
722
723 /^\.whoami/ {
724         if (!(sp_from in sp_players))
725                 reply("You are not playing")
726         else if (sp_from == FROM)
727                 say(FROM " has an existential crisis")
728         else
729                 reply("You are playing for " sp_from);
730 }
731
732 /^\.notify$/ {
733         if (sp_from in sp_notify)
734                 reply("Your address is " sp_notify[sp_from])
735         else
736                 reply("Your address is not set")
737 }
738
739 /^\.notify clear$/ {
740         if (sp_from in sp_notify) {
741                 reply("Removing address " sp_notify[sp_from])
742                 delete sp_notify[sp_from]
743         } else {
744                 reply("Your address is not set")
745         }
746 }
747
748 /^\.notify \S+@\S+.\S+$/ {
749         _addr = $2
750         gsub(/[^a-zA-Z0-9_+@.-]/, "", _addr)
751         sp_notify[sp_from] = _addr
752         reply("Notifying you at " _addr)
753 }
754
755 sp_state ~ "(bid|pass|play)" &&
756 /^\.show/ {
757         delete _lines
758         for (_i in sp_share)
759                 _lines[sp_share[_i]] = _lines[sp_share[_i]] " " _i
760         for (_i in _lines)
761                 say(_i " allowed:" _lines[_i])
762 }
763
764 !sp_valid &&
765 (sp_state == "bid" || sp_state == "play") &&
766 /^\.(bid|play)\>/ {
767         if (sp_from in sp_players)
768                 reply("It is not your turn.")
769         else
770                 reply("You are not playing.")
771 }
772
773 sp_valid &&
774 sp_state == "bid" &&
775 /^\.bid (0|[1-9][0-9]*)$/ {
776         if ($2 < 0 || $2 > 13) {
777                 reply("You can only bid from 0 to 13")
778         } else {
779                 i = sp_next()
780                 sp_bids[i] = $2
781                 if ($2 == 0 && !sp_looked[i]) {
782                         sp_say(FROM " goes blind nil!")
783                         sp_nil[i] = 2
784                 } else if ($2 == 0) {
785                         sp_say(FROM " goes nil!")
786                         sp_nil[i] = 1
787                 } else {
788                         sp_nil[i] = 0
789                 }
790                 if (sp_turn != sp_dealer) {
791                         sp_say(sp_player ": it is your bid! (" sp_bidders() ")")
792                 } else {
793                         sp_say(sp_extra() " (" sp_bidders() ")")
794                         for (p in sp_players)
795                                 say(p, "You have: " sp_hand(p, p))
796                         sp_state = "play"
797                         for (i=0; i<2; i++) {
798                                 if (sp_passer(i)) {
799                                         sp_say(sp_team(i,1) ": select a card to pass " \
800                                             "(/msg " NICK " .pass <card>)")
801                                         sp_state = "pass"
802                                 }
803                         }
804                         if (sp_state == "play")
805                                 sp_say(sp_player ": you have the opening lead!")
806                 }
807         }
808 }
809
810 sp_state == "pass" &&
811 /^\.pass (\S+)$/ {
812         _card = $2
813         _team = sp_from in sp_players ? sp_players[sp_from] % 2 : 0
814
815         # check validity and pass
816         if (!(sp_from in sp_players)) {
817                 reply("You are not playing.")
818         }
819         else if (!sp_passer(_team)) {
820                 reply("Your team did not go blind")
821         }
822         else if (sp_pass[sp_players[sp_from]]) {
823                 reply("You have already passed a card")
824         }
825         else if (!(_card in sp_deck)) {
826                 reply("Invalid card")
827         }
828         else if (!(_card in sp_hands[sp_from])) {
829                 reply("You do not have that card")
830         }
831         else {
832                 sp_pass[sp_players[sp_from]] = $2
833                 sp_say(FROM " passes a card")
834         }
835
836         # check for end of passing
837         if ((!sp_passer(0) || (sp_pass[0] && sp_pass[2])) &&
838             (!sp_passer(1) || (sp_pass[1] && sp_pass[3]))) {
839                 for (i in sp_pass) {
840                         _partner = (i+2)%4
841                         _card    = sp_pass[i]
842                         delete sp_hands[sp_order[i]][_card]
843                         sp_hands[sp_order[_partner]][_card] = 1
844                 }
845                 sp_say("Cards have been passed!")
846                 sp_say(sp_player ": you have the opening lead!")
847                 for (p in sp_players)
848                         say(p, "You have: " sp_hand(p, p))
849                 sp_state = "play"
850         }
851 }
852
853 sp_state ~ "(bid|pass|play)" &&
854 /^\.look$/ {
855         if (!(sp_from in sp_players)) {
856                 reply("You are not playing.")
857         } else {
858                 sp_looked[sp_players[sp_from]] = 1
859                 say(FROM, "You have: " sp_hand(FROM, sp_from))
860         }
861 }
862
863 sp_valid &&
864 sp_state == "play" &&
865 /^\.play (\S+)/ {
866         _card = $2
867         gsub(/[^A-Za-z0-9]/, "", _card);
868         if (!(_card in sp_deck)) {
869                 reply("Invalid card")
870         }
871         else if (sp_suit && _card !~ sp_suit && sp_hasa(sp_from, sp_suit)) {
872                 reply("You must follow suit (" sp_suit ")")
873         }
874         else if (_card ~ /s/ && length(sp_hands[sp_from]) == 13 && sp_hasa(sp_from, "[^s]$")) {
875                 reply("You cannot trump on the first hand")
876         }
877         else if (_card ~ /s/ && length(sp_pile) == 0 && sp_hasa(sp_from, "[^s]$") && !sp_broken) {
878                 reply("Spades have not been broken")
879         }
880         else if (!(_card in sp_hands[sp_from])) {
881                 reply("You do not have that card")
882         }
883         else {
884                 sp_play(_card)
885                 if (sp_state == "play") {
886                         if (length(sp_hands[sp_from]))
887                                 say(FROM, "You have: " sp_hand(FROM, sp_from))
888                         if (sp_piles)
889                                 sp_say(sp_player ": it is your turn! " \
890                                     "(" sp_pretty(sp_piles, sp_player) ")")
891                         else
892                                 sp_say(sp_player ": it is your turn!")
893                 }
894         }
895 }
896
897 /^\.last/ && sp_state == "play" {
898         if (!isarray(sp_last))
899                 say("No tricks have been taken!");
900         else
901                 say(sp_last["player"] " took " \
902                     sp_pretty(sp_last["pile"], FROM));
903 }
904
905 /^\.bids/ && sp_state == "bid" ||
906 /^\.turn/ && sp_state ~ "(bid|pass|play)" {
907         _bids   = sp_bidders()
908         _pile   = sp_pretty(sp_piles, FROM)
909         _extra  = ""
910         delete _notify
911
912         if (/!!/)
913                 _notify[0] = sp_player
914         for (_i in sp_share) {
915                 if (sp_share[_i] != sp_player)
916                         continue
917                 if (/!/)
918                         _extra = _extra " " _i "!"
919                 if (/!!!/)
920                         _notify[length(_notify)] = _i
921         }
922
923         if (sp_state == "bid" && !_bids)
924                 say("It is " sp_player "'s bid!" _extra)
925         if (sp_state == "bid" && _bids)
926                 say("It is " sp_player "'s bid!" _extra " (" _bids ")")
927         if (sp_state == "play" && !_pile)
928                 say("It is " sp_player "'s turn!" _extra)
929         if (sp_state == "play" && _pile)
930                 say("It is " sp_player "'s turn!" _extra " (" _pile ")")
931
932         if (sp_state == "bid" || sp_state == "play") {
933                 for (_i in _notify) {
934                         if (_notify[_i] in sp_notify) {
935                                 _bids = _bids ? _bids    : "none"
936                                 _pile = _pile ? sp_piles : "none"
937                                 mail_send(sp_notify[_notify[_i]],     \
938                                         "It is your " sp_state "!", \
939                                         "Bids so far:  " _bids "\n" \
940                                         "Cards played: " _pile)
941                                 say("Notified " _notify[_i] " at " sp_notify[_notify[_i]])
942                         } else {
943                                 say("No email address for " _notify[_i])
944                         }
945                 }
946         }
947
948         for (_i=0; sp_state == "pass" && _i<4; _i++)
949                 if (sp_passer(_i) && !sp_pass[_i])
950                         say("Waiting for " sp_order[_i] " to pass a card!")
951 }
952
953 /^\.bids$/ && sp_state ~ "(pass|play)" {
954         say(sp_order[0] " bid " sp_bid(0) ", " \
955             sp_order[2] " bid " sp_bid(2) ", " \
956             "total: " sp_bids[0] + sp_bids[2])
957         say(sp_order[1] " bid " sp_bid(1) ", " \
958             sp_order[3] " bid " sp_bid(3) ", " \
959             "total: " sp_bids[1] + sp_bids[3])
960 }
961
962 /^\.tricks$/ && sp_state == "play" {
963         say(sp_order[0] " took " int(sp_tricks[0]) "/" sp_bid(0) ", " \
964             sp_order[2] " took " int(sp_tricks[2]) "/" sp_bid(2))
965         say(sp_order[1] " took " int(sp_tricks[1]) "/" sp_bid(1) ", " \
966             sp_order[3] " took " int(sp_tricks[3]) "/" sp_bid(3))
967 }
968
969 (TO == NICK || DST == sp_channel) &&
970 /^\.(score|status)$/ {
971         if (sp_state == "new") {
972                 say("There is no game in progress")
973         }
974         if (sp_state ~ "join|bid|pass|play") {
975                 say("Playing to: " \
976                     sp_playto " points, " \
977                     sp_limit  " bags")
978         }
979         if (sp_state == "join") {
980                 say("Waiting for players: " \
981                     sp_order[0] " " sp_order[1] " " \
982                     sp_order[2] " " sp_order[3])
983         }
984         if (sp_state ~ "bid|pass|play") {
985                 say(sp_team(0) ": " \
986                     int(sp_scores[0]) " points, " \
987                     int(sp_bags(0))   " bags")
988                 say(sp_team(1) ": " \
989                     int(sp_scores[1]) " points, " \
990                     int(sp_bags(1))   " bags")
991         }
992 }
993
994 (TO == NICK || DST == sp_channel) &&
995 /^\.log/ {
996         say("http://pileus.org/andy/spades/" sp_log)
997 }
998
999 (TO == NICK || DST == sp_channel) &&
1000 /^\.stats$/ {
1001         sp_stats("logs/" sp_log);
1002 }
1003
1004 (TO == NICK || DST == sp_channel) &&
1005 /^\.stats ([0-9]+_[0-9]+)(\.log)$/ {
1006         gsub(/\.log$/, "", $2);
1007         sp_stats("logs/" $2 ".log");
1008 }
1009
1010 /^\.((new|end|load)game|join|look|bid|pass|play|allow|deny|team|notify)/ {
1011         sp_save("var/sp_cur.json");
1012 }