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