]> Pileus Git - ~andy/rhawk/blob - spades.awk
Abbreviate usernames in stats
[~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         say(sp_channel, msg)
163         print msg |& sp_sock
164         print strftime("%Y-%m-%d %H:%M:%S | ") msg >> "logs/" sp_log
165         fflush("logs/" sp_log)
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, short, 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                 short = length(user) <= 4 ? user : substr(user, 0, 4)
465                 extra = (user != turn) ? "" : \
466                         ", " sp_delay(sp_cur(delay[user])) " (cur)";
467                 say("latency for " short \
468                         ": " sp_delay(sp_avg(delay[user])) " (avg)" \
469                         ", " sp_delay(sp_max(delay[user])) " (max)" extra)
470         }
471 }
472
473 # Misc
474 BEGIN {
475         cmd = "od -An -N4 -td4 /dev/random"
476         cmd | getline seed
477         close(cmd)
478         srand(seed)
479         sp_init()
480         sp_reset(2)
481         sp_load("var/sp_cur.json")
482         sp_sock = "/inet/udp/0/localhost/6173"
483         print "starting rhawk" |& sp_sock
484         #if (sp_channel)
485         #       sp_say("Game restored.")
486 }
487
488 // {
489         sp_from  = AUTH in sp_auths ? sp_auths[AUTH] : \
490                    AUTH in sp_share ? sp_share[AUTH] : FROM
491         sp_valid = sp_from && sp_from == sp_player
492 }
493
494 CMD == "PRIVMSG" &&
495 ! /help/ &&
496 /[Ss]pades/ {
497         say("Spades! " sp_pretty("As,Ah,Ad,Ac", FROM))
498 }
499
500 AUTH == OWNER &&
501 /^\.savegame/ {
502         sp_save("var/sp_save.json");
503         say("Game saved.")
504 }
505
506 AUTH == OWNER &&
507 /^\.loadgame/ {
508         sp_load("var/sp_save.json");
509         say("Game loaded.")
510 }
511
512 # Help
513 /^\.help$/ {
514         say(".help spades -- play a game of spades")
515 }
516
517 /^\.help [Ss]pades$/ {
518         say("Spades -- play a game of spades")
519         say(".help game -- setup and administer the game")
520         say(".help play -- commands for playing spades")
521         say(".help auth -- control player authorization")
522         next
523 }
524
525 /^\.help game$/ {
526         say(".newgame [score] -- start a game to <score> points, default 500")
527         say(".endgame -- abort the current game")
528         say(".savegame -- save the current game to disk")
529         say(".loadgame -- load the previously saved game")
530         next
531 }
532
533 /^\.help play$/ {
534         say(".join -- join the current game")
535         say(".look -- look at your cards")
536         say(".bid [n] -- bid for <n> tricks")
537         say(".pass [card] -- pass a card to your partner")
538         say(".play [card] -- play a card")
539         say(".last -- show who took the previous trick")
540         say(".turn -- check whose turn it is")
541         say(".bids -- check what everyone bid")
542         say(".tricks -- check how many trick have been taken")
543         say(".score -- check the score")
544         next
545 }
546
547 /^\.help auth$/ {
548         say(".auth [who] -- display authentication info for a user")
549         say(".allow [who] -- allow another person to play on your behalf")
550         say(".deny [who] -- prevent a previously allowed user from playing")
551         say(".show -- display which users can play for which players")
552         say(".notify [addr] -- email user when it is their turn")
553         next
554 }
555
556 # Debugging
557 AUTH == OWNER &&
558 /^\.deal (\w+) (.*)/ {
559         sp_say(FROM " is cheating for " $2)
560         delete sp_hands[$2]
561         for (i=3; i<=NF; i++)
562                 sp_hands[$2][$i] = 1
563         next
564 }
565
566 AUTH == OWNER &&
567 /^\.order (\w+) ([0-4])/ {
568         sp_say(FROM " is cheating for " $2)
569         sp_order[$3] = $2
570         sp_players[$2] = $3
571         sp_player = sp_order[sp_turn]
572 }
573
574 AUTH == OWNER &&
575 sp_state == "play" &&
576 /^\.force (\w+) (\S+)$/ {
577         sp_say(FROM " is cheating for " $2)
578         sp_from = $2
579         sp_play($3)
580         next
581 }
582
583
584 # Setup
585 match($0, /^\.newgame ?([1-9][0-9]*) *- *([1-9][0-9]*)$/, _arr) {
586         if (_arr[2] > _arr[1])
587                 $0 = $1 " " int(rand() * (_arr[2]-_arr[1])+_arr[1])
588 }
589
590 /^\.newgame ?([1-9][0-9]*)?$/ {
591         if (sp_state != "new") {
592                 reply("There is already a game in progress.")
593         } else {
594                 $1         = ".join"
595                 sp_owner   = FROM
596                 sp_playto  = $2 ? $2 : 200
597                 sp_limit   = sp_playto > 200 ? 10 : 5;
598                 sp_state   = "join"
599                 sp_channel = DST
600                 sp_log     = strftime("%Y%m%d_%H%M%S.log")
601                 sp_say(sp_owner " starts a game of Spades to " sp_playto " with " sp_limit " bags!")
602         }
603 }
604
605 (sp_from == sp_owner || AUTH == OWNER) &&
606 /^\.endgame$/ {
607         if (sp_state == "new") {
608                 reply("There is no game in progress.")
609         } else {
610                 sp_say(FROM " ends the game")
611                 sp_reset(2)
612         }
613 }
614
615 /^\.join/ {
616         if (sp_state == "new") {
617                 reply("There is no game in progress")
618         }
619         else if (sp_state == "play") {
620                 reply("The game has already started")
621         }
622         else if (sp_state == "join" && sp_from in sp_players) {
623                 reply("You are already playing")
624         }
625         else if (sp_state == "join") {
626                 i = sp_next()
627                 sp_players[FROM] = i
628                 if (AUTH)
629                         sp_auths[AUTH] = FROM
630                 sp_order[i] = FROM
631                 sp_say(FROM " joins the game!")
632         }
633         if (sp_state == "join" && sp_turn == 0) {
634                 sp_shuf()
635                 sp_deal()
636         }
637 }
638
639 /^\.allow \S+$/ {
640         _who = $2 in USERS ? USERS[$2]["auth"] : ""
641         _str = _who && _who != $2 ? $2 " (" _who ")" : $2
642         if (sp_state ~ "new|join") {
643                 reply("The game has not yet started")
644         }
645         else if (!(sp_from in sp_players)) {
646                 reply("You are not playing")
647         }
648         else if (!_who) {
649                 reply(_str " is not logged in")
650         }
651         else if (_who in sp_players || _who in sp_auths) {
652                 reply(_str " is a primary player")
653         }
654         else if (_who in sp_share) {
655                 reply(_str " is already playing for " sp_share[_who])
656         }
657         else {
658                 sp_say(_str " can now play for " sp_from)
659                 sp_share[_who] = sp_from
660         }
661 }
662
663 /^\.deny \S+$/ {
664         _who = $2 in USERS ? USERS[$2]["auth"] : $2
665         _str = _who && _who != $2 ? $2 " (" _who ")" : $2
666         if (sp_state ~ "new|join") {
667                 reply("The game has not yet started")
668         }
669         else if (!(sp_from in sp_players)) {
670                 reply("You are not playing")
671         }
672         else if (_who in sp_players || _who in sp_auths) {
673                 reply(_str " is a primary player")
674         }
675         else if (!(_who in sp_share) || sp_share[_who] != sp_from) {
676                 reply(_str " is not playing for " sp_from)
677         }
678         else {
679                 sp_say(_str " can no longer play for " sp_from)
680                 delete sp_share[_who]
681         }
682 }
683
684 /^\.whoami/ {
685         if (!(sp_from in sp_players))
686                 reply("You are not playing")
687         else if (sp_from == FROM)
688                 say(FROM " has an existential crisis")
689         else
690                 reply("You are playing for " sp_from);
691 }
692
693 /^\.notify$/ {
694         if (sp_from in sp_notify)
695                 reply("Your address is " sp_notify[sp_from])
696         else
697                 reply("Your address is not set")
698 }
699
700 /^\.notify clear$/ {
701         if (sp_from in sp_notify) {
702                 reply("Removing address " sp_notify[sp_from])
703                 delete sp_notify[sp_from]
704         } else {
705                 reply("Your address is not set")
706         }
707 }
708
709 /^\.notify \S+@\S+.\S+$/ {
710         _addr = $2
711         gsub(/[^a-zA-Z0-9_+@.-]/, "", _addr)
712         sp_notify[sp_from] = _addr
713         reply("Notifying you at " _addr)
714 }
715
716 sp_state ~ "(bid|pass|play)" &&
717 /^\.show/ {
718         delete _lines
719         for (_i in sp_share)
720                 _lines[sp_share[_i]] = _lines[sp_share[_i]] " " _i
721         for (_i in _lines)
722                 say(_i " allowed:" _lines[_i])
723 }
724
725 !sp_valid &&
726 (sp_state == "bid" || sp_state == "play") &&
727 /^\.(bid|play)\>/ {
728         if (sp_from in sp_players)
729                 reply("It is not your turn.")
730         else
731                 reply("You are not playing.")
732 }
733
734 sp_valid &&
735 sp_state == "bid" &&
736 /^\.bid (0|[1-9][0-9]*)$/ {
737         if ($2 < 0 || $2 > 13) {
738                 reply("You can only bid from 0 to 13")
739         } else {
740                 i = sp_next()
741                 sp_bids[i] = $2
742                 if ($2 == 0 && !sp_looked[i]) {
743                         sp_say(FROM " goes blind nil!")
744                         sp_nil[i] = 2
745                 } else if ($2 == 0) {
746                         sp_say(FROM " goes nil!")
747                         sp_nil[i] = 1
748                 } else {
749                         sp_nil[i] = 0
750                 }
751                 if (sp_turn != sp_dealer) {
752                         sp_say(sp_player ": it is your bid! (" sp_bidders() ")")
753                 } else {
754                         sp_say(sp_extra() " (" sp_bidders() ")")
755                         for (p in sp_players)
756                                 say(p, "You have: " sp_hand(p, p))
757                         sp_state = "play"
758                         for (i=0; i<2; i++) {
759                                 if (sp_passer(i)) {
760                                         sp_say(sp_team(i) ": select a card to pass " \
761                                             "(/msg " NICK " .pass <card>)")
762                                         sp_state = "pass"
763                                 }
764                         }
765                         if (sp_state == "play")
766                                 sp_say(sp_player ": you have the opening lead!")
767                 }
768         }
769 }
770
771 sp_state == "pass" &&
772 /^\.pass (\S+)$/ {
773         _card = $2
774         _team = sp_from in sp_players ? sp_players[sp_from] % 2 : 0
775
776         # check validity and pass
777         if (!(sp_from in sp_players)) {
778                 reply("You are not playing.")
779         }
780         else if (!sp_passer(_team)) {
781                 reply("Your team did not go blind")
782         }
783         else if (sp_pass[sp_players[sp_from]]) {
784                 reply("You have already passed a card")
785         }
786         else if (!(_card in sp_deck)) {
787                 reply("Invalid card")
788         }
789         else if (!(_card in sp_hands[sp_from])) {
790                 reply("You do not have that card")
791         }
792         else {
793                 sp_pass[sp_players[sp_from]] = $2
794                 sp_say(FROM " passes a card")
795         }
796
797         # check for end of passing
798         if ((!sp_passer(0) || (sp_pass[0] && sp_pass[2])) &&
799             (!sp_passer(1) || (sp_pass[1] && sp_pass[3]))) {
800                 for (i in sp_pass) {
801                         _partner = (i+2)%4
802                         _card    = sp_pass[i]
803                         delete sp_hands[sp_order[i]][_card]
804                         sp_hands[sp_order[_partner]][_card] = 1
805                 }
806                 sp_say("Cards have been passed!")
807                 sp_say(sp_player ": you have the opening lead!")
808                 for (p in sp_players)
809                         say(p, "You have: " sp_hand(p, p))
810                 sp_state = "play"
811         }
812 }
813
814 sp_state ~ "(bid|pass|play)" &&
815 /^\.look$/ {
816         if (!(sp_from in sp_players)) {
817                 reply("You are not playing.")
818         } else {
819                 sp_looked[sp_players[sp_from]] = 1
820                 say(FROM, "You have: " sp_hand(FROM, sp_from))
821         }
822 }
823
824 sp_valid &&
825 sp_state == "play" &&
826 /^\.play (\S+)/ {
827         _card = $2
828         gsub(/[^A-Za-z0-9]/, "", _card);
829         if (!(_card in sp_deck)) {
830                 reply("Invalid card")
831         }
832         else if (sp_suit && _card !~ sp_suit && sp_hasa(sp_from, sp_suit)) {
833                 reply("You must follow suit (" sp_suit ")")
834         }
835         else if (_card ~ /s/ && length(sp_hands[sp_from]) == 13 && sp_hasa(sp_from, "[^s]$")) {
836                 reply("You cannot trump on the first hand")
837         }
838         else if (_card ~ /s/ && length(sp_pile) == 0 && sp_hasa(sp_from, "[^s]$") && !sp_broken) {
839                 reply("Spades have not been broken")
840         }
841         else if (!(_card in sp_hands[sp_from])) {
842                 reply("You do not have that card")
843         }
844         else {
845                 sp_play(_card)
846                 if (sp_state == "play") {
847                         if (length(sp_hands[sp_from]))
848                                 say(FROM, "You have: " sp_hand(FROM, sp_from))
849                         if (sp_piles)
850                                 sp_say(sp_player ": it is your turn! " \
851                                     "(" sp_pretty(sp_piles, sp_player) ")")
852                         else
853                                 sp_say(sp_player ": it is your turn!")
854                 }
855         }
856 }
857
858 /^\.last/ && sp_state == "play" {
859         if (!isarray(sp_last))
860                 say("No tricks have been taken!");
861         else
862                 say(sp_last["player"] " took " \
863                     sp_pretty(sp_last["pile"], FROM));
864 }
865
866 /^\.bids/ && sp_state == "bid" ||
867 /^\.turn/ && sp_state ~ "(bid|pass|play)" {
868         _bids   = sp_bidders()
869         _pile   = sp_pretty(sp_piles, FROM)
870         _extra  = ""
871         delete _notify
872
873         if (/!!/)
874                 _notify[0] = sp_player
875         for (_i in sp_share) {
876                 if (sp_share[_i] != sp_player)
877                         continue
878                 if (/!/)
879                         _extra = _extra " " _i "!"
880                 if (/!!!/)
881                         _notify[length(_notify)] = _i
882         }
883
884         if (sp_state == "bid" && !_bids)
885                 say("It is " sp_player "'s bid!" _extra)
886         if (sp_state == "bid" && _bids)
887                 say("It is " sp_player "'s bid!" _extra " (" _bids ")")
888         if (sp_state == "play" && !_pile)
889                 say("It is " sp_player "'s turn!" _extra)
890         if (sp_state == "play" && _pile)
891                 say("It is " sp_player "'s turn!" _extra " (" _pile ")")
892
893         if (sp_state == "bid" || sp_state == "play") {
894                 for (_i in _notify) {
895                         if (_notify[_i] in sp_notify) {
896                                 _bids = _bids ? _bids    : "none"
897                                 _pile = _pile ? sp_piles : "none"
898                                 mail_send(sp_notify[_notify[_i]],     \
899                                         "It is your " sp_state "!", \
900                                         "Bids so far:  " _bids "\n" \
901                                         "Cards played: " _pile)
902                                 say("Notified " _notify[_i] " at " sp_notify[_notify[_i]])
903                         } else {
904                                 say("No email address for " _notify[_i])
905                         }
906                 }
907         }
908
909         for (_i=0; sp_state == "pass" && _i<4; _i++)
910                 if (sp_passer(_i) && !sp_pass[_i])
911                         say("Waiting for " sp_order[_i] " to pass a card!")
912 }
913
914 /^\.bids$/ && sp_state ~ "(pass|play)" {
915         say(sp_order[0] " bid " sp_bid(0) ", " \
916             sp_order[2] " bid " sp_bid(2) ", " \
917             "total: " sp_bids[0] + sp_bids[2])
918         say(sp_order[1] " bid " sp_bid(1) ", " \
919             sp_order[3] " bid " sp_bid(3) ", " \
920             "total: " sp_bids[1] + sp_bids[3])
921 }
922
923 /^\.tricks$/ && sp_state == "play" {
924         say(sp_order[0] " took " int(sp_tricks[0]) "/" sp_bid(0) ", " \
925             sp_order[2] " took " int(sp_tricks[2]) "/" sp_bid(2))
926         say(sp_order[1] " took " int(sp_tricks[1]) "/" sp_bid(1) ", " \
927             sp_order[3] " took " int(sp_tricks[3]) "/" sp_bid(3))
928 }
929
930 (TO == NICK || DST == sp_channel) &&
931 /^\.(score|status)$/ {
932         if (sp_state == "new") {
933                 say("There is no game in progress")
934         }
935         if (sp_state ~ "join|bid|pass|play") {
936                 say("Playing to: " \
937                     sp_playto " points, " \
938                     sp_limit  " bags")
939         }
940         if (sp_state == "join") {
941                 say("Waiting for players: " \
942                     sp_order[0] " " sp_order[1] " " \
943                     sp_order[2] " " sp_order[3])
944         }
945         if (sp_state ~ "bid|pass|play") {
946                 say(sp_team(0) ": " \
947                     int(sp_scores[0]) " points, " \
948                     int(sp_bags(0))   " bags")
949                 say(sp_team(1) ": " \
950                     int(sp_scores[1]) " points, " \
951                     int(sp_bags(1))   " bags")
952         }
953 }
954
955 (TO == NICK || DST == sp_channel) &&
956 /^\.log/ {
957         say("http://pileus.org/andy/spades/" sp_log)
958 }
959
960 (TO == NICK || DST == sp_channel) &&
961 /^\.stats$/ {
962         sp_stats("logs/" sp_log);
963 }
964
965 (TO == NICK || DST == sp_channel) &&
966 /^\.stats ([0-9]+_[0-9]+)(\.log)$/ {
967         gsub(/\.log$/, "", $2);
968         sp_stats("logs/" $2 ".log");
969 }
970
971 /^\.((new|end|load)game|join|look|bid|pass|play|notify)/ {
972         sp_save("var/sp_cur.json");
973 }