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