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