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