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