]> Pileus Git - ~andy/rhawk/blob - spades.awk
Fix .last command formatting
[~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_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         json_copy(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_acopy(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])
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["player"] = sp_pile[winner];
341                 sp_last["pile"]   = sp_piles;
342                 sp_next(sp_pile[winner])
343                 sp_reset(0)
344         }
345
346         # Finish round
347         if (sp_tricks[0] + sp_tricks[1] + \
348             sp_tricks[2] + sp_tricks[3] == 13) {
349                 say("Round over!")
350                 sp_score()
351                 if (sp_scores[0] >= sp_playto || sp_scores[1] >= sp_playto &&
352                     sp_scores[0]              != sp_scores[1]) {
353                         say("Game over!")
354                         winner = sp_scores[0] > sp_scores[1] ? 0 : 1
355                         looser = !winner
356                         say(CHANNEL, sp_team(winner) " wins the game " \
357                             sp_scores[winner] " to " sp_scores[looser])
358                         say(CHANNEL, sp_order[winner+0] "++")
359                         say(CHANNEL, sp_order[winner+2] "++")
360                         sp_reset(2)
361
362                 } else {
363                         if (sp_scores[0] == sp_scores[1] && 
364                             sp_scores[0] >= sp_playto)
365                                 say("It's tie! Playing an extra round!");
366                         sp_reset(1)
367                         sp_deal()
368                 }
369         }
370 }
371
372 # Misc
373 BEGIN {
374         cmd = "od -An -N4 -td4 /dev/random"
375         cmd | getline seed
376         close(cmd)
377         srand(seed)
378         sp_init()
379         sp_reset(2)
380         sp_load("var/sp_cur.json");
381         #if (sp_channel)
382         #       say(sp_channel, "Game restored.")
383 }
384
385 // {
386         sp_from  = AUTH in sp_auths ? sp_auths[AUTH] : \
387                    AUTH in sp_share ? sp_share[AUTH] : FROM
388         sp_valid = sp_from && sp_from == sp_player
389 }
390
391 CMD == "PRIVMSG" &&
392 ! /help/ &&
393 /[Ss]pades/ {
394         say("Spades! " sp_pretty("As,Ah,Ad,Ac", FROM))
395 }
396
397 AUTH == OWNER &&
398 /^\.savegame/ {
399         sp_save("var/sp_save.json");
400         say("Game saved.")
401 }
402
403 AUTH == OWNER &&
404 /^\.loadgame/ {
405         sp_load("var/sp_save.json");
406         say("Game loaded.")
407 }
408
409 # Help
410 /^\.help$/ {
411         say(".help spades -- play a game of spades")
412 }
413
414 /^\.help [Ss]pades$/ {
415         say("Spades -- play a game of spades")
416         say(".help game -- setup and administer the game")
417         say(".help play -- commands for playing spades")
418         say(".help auth -- control player authorization")
419         next
420 }
421
422 /^\.help game$/ {
423         say(".newgame [score] -- start a game to <score> points, default 500")
424         say(".endgame -- abort the current game")
425         say(".savegame -- save the current game to disk")
426         say(".loadgame -- load the previously saved game")
427         next
428 }
429
430 /^\.help play$/ {
431         say(".join -- join the current game")
432         say(".look -- look at your cards")
433         say(".bid [n] -- bid for <n> tricks")
434         say(".pass [card] -- pass a card to your partner")
435         say(".play [card] -- play a card")
436         say(".last -- show who took the previous trick")
437         say(".turn -- check whose turn it is")
438         say(".bids -- check what everyone bid")
439         say(".tricks -- check how many trick have been taken")
440         say(".score -- check the score")
441         next
442 }
443
444 /^\.help auth$/ {
445         say(".auth [who] -- display authentication info for a user")
446         say(".allow [who] -- allow another person to play on your behalf")
447         say(".deny [who] -- prevent a previously allowed user from playing")
448         say(".show -- display which users can play for which players")
449         say(".notify [addr] -- email user when it is their turn")
450         next
451 }
452
453 # Debugging
454 AUTH == OWNER &&
455 /^\.deal (\w+) (.*)/ {
456         say(sp_channel, FROM " is cheating for " $2)
457         delete sp_hands[$2]
458         for (i=3; i<=NF; i++)
459                 sp_hands[$2][$i] = 1
460         next
461 }
462
463 AUTH == OWNER &&
464 /^\.order (\w+) ([0-4])/ {
465         say(sp_channel, FROM " is cheating for " $2)
466         sp_order[$3] = $2
467         sp_players[$2] = $3
468         sp_player = sp_order[sp_turn]
469 }
470
471 AUTH == OWNER &&
472 sp_state == "play" &&
473 /^\.force (\w+) (\S+)$/ {
474         say(sp_channel, FROM " is cheating for " $2)
475         sp_from = $2
476         sp_play($3)
477         next
478 }
479
480
481 # Setup
482 match($0, /^\.newgame ?([1-9][0-9]*) *- *([1-9][0-9]*)$/, _arr) {
483         if (_arr[2] > _arr[1])
484                 $0 = $1 " " int(rand() * (_arr[2]-_arr[1])+_arr[1])
485 }
486
487 /^\.newgame ?([1-9][0-9]*)?$/ {
488         if (sp_state != "new") {
489                 reply("There is already a game in progress.")
490         } else {
491                 $1         = ".join"
492                 sp_owner   = FROM
493                 sp_playto  = $2 ? $2 : 200
494                 sp_limit   = sp_playto > 200 ? 10 : 5;
495                 sp_state   = "join"
496                 sp_channel = DST
497                 say(sp_owner " starts a game of Spades to " sp_playto " with " sp_limit " bags!")
498         }
499 }
500
501 (sp_from == sp_owner || AUTH == OWNER) &&
502 /^\.endgame$/ {
503         if (sp_state == "new") {
504                 reply("There is no game in progress.")
505         } else {
506                 say(FROM " ends the game")
507                 sp_reset(2)
508         }
509 }
510
511 /^\.join$/ {
512         if (sp_state == "new") {
513                 reply("There is no game in progress")
514         }
515         else if (sp_state == "play") {
516                 reply("The game has already started")
517         }
518         else if (sp_state == "join" && sp_from in sp_players) {
519                 reply("You are already playing")
520         }
521         else if (sp_state == "join") {
522                 i = sp_next()
523                 sp_players[FROM] = i
524                 if (AUTH)
525                         sp_auths[AUTH] = FROM
526                 sp_order[i] = FROM
527                 say(FROM " joins the game!")
528         }
529         if (sp_state == "join" && sp_turn == 0) {
530                 sp_shuf()
531                 sp_deal()
532         }
533 }
534
535 /^\.allow \S+$/ {
536         _who = $2 in USERS ? USERS[$2]["auth"] : ""
537         _str = _who && _who != $2 ? $2 " (" _who ")" : $2
538         if (sp_state ~ "new|join") {
539                 reply("The game has not yet started")
540         }
541         else if (!(sp_from in sp_players)) {
542                 reply("You are not playing")
543         }
544         else if (!_who) {
545                 reply(_str " is not logged in")
546         }
547         else if (_who in sp_players || _who in sp_auths) {
548                 reply(_str " is a primary player")
549         }
550         else if (_who in sp_share) {
551                 reply(_str " is already playing for " sp_share[_who])
552         }
553         else {
554                 reply(_str " can now play for " sp_from)
555                 sp_share[_who] = sp_from
556         }
557 }
558
559 /^\.deny \S+$/ {
560         _who = $2 in USERS ? USERS[$2]["auth"] : $2
561         _str = _who && _who != $2 ? $2 " (" _who ")" : $2
562         if (sp_state ~ "new|join") {
563                 reply("The game has not yet started")
564         }
565         else if (!(sp_from in sp_players)) {
566                 reply("You are not playing")
567         }
568         else if (_who in sp_players || _who in sp_auths) {
569                 reply(_str " is a primary player")
570         }
571         else if (!(_who in sp_share) || sp_share[_who] != sp_from) {
572                 reply(_str " is not playing for " sp_from)
573         }
574         else {
575                 reply(_str " can no longer play for " sp_from)
576                 delete sp_share[_who]
577         }
578 }
579
580 /^\.notify$/ {
581         if (sp_from in sp_notify)
582                 reply("Your address is " sp_notify[sp_from])
583         else
584                 reply("Your address is not set")
585 }
586
587 /^\.notify clear$/ {
588         if (sp_from in sp_notify) {
589                 reply("Removing address " sp_notify[sp_from])
590                 delete sp_notify[sp_from]
591         } else {
592                 reply("Your address is not set")
593         }
594 }
595
596 /^\.notify \S+@\S+.\S+$/ {
597         _addr = $2
598         gsub(/[^a-zA-Z0-9_+@.-]/, "", _addr)
599         sp_notify[sp_from] = _addr
600         reply("Notifying you at " _addr)
601 }
602
603 sp_state ~ "(bid|pass|play)" &&
604 /^\.show/ {
605         delete _lines
606         for (_i in sp_share)
607                 _lines[sp_share[_i]] = _lines[sp_share[_i]] " " _i
608         for (_i in _lines)
609                 say(_i " allowed:" _lines[_i])
610 }
611
612 !sp_valid &&
613 (sp_state == "bid" || sp_state == "play") &&
614 /^\.(bid|play)\>/ {
615         if (sp_from in sp_players)
616                 say(".slap " FROM ", it is not your turn.")
617         else
618                 say(".slap " FROM ", you are not playing.")
619 }
620
621 sp_valid &&
622 sp_state == "bid" &&
623 /^\.bid (0|[1-9][0-9]*)$/ {
624         if ($2 < 0 || $2 > 13) {
625                 reply("You can only bid from 0 to 13")
626         } else {
627                 i = sp_next()
628                 sp_bids[i] = $2
629                 if ($2 == 0 && !sp_looked[i]) {
630                         say(FROM " goes blind nil!")
631                         sp_nil[i] = 2
632                 } else if ($2 == 0) {
633                         say(FROM " goes nil!")
634                         sp_nil[i] = 1
635                 } else {
636                         sp_nil[i] = 0
637                 }
638                 if (sp_turn != sp_dealer) {
639                         say(sp_player ": it is your bid! (" sp_bidders() ")")
640                 } else {
641                         for (p in sp_players)
642                                 say(p, "You have: " sp_hand(p, p))
643                         sp_state = "play"
644                         for (i=0; i<2; i++) {
645                                 if (sp_passer(i)) {
646                                         say(sp_team(i) ": select a card to pass " \
647                                             "(/msg " NICK " .pass <card>)")
648                                         sp_state = "pass"
649                                 }
650                         }
651                         if (sp_state == "play")
652                                 say(sp_player ": you have the opening lead!")
653                 }
654         }
655 }
656
657 sp_state == "pass" &&
658 /^\.pass (\S+)$/ {
659         _card = $2
660         _team = sp_from in sp_players ? sp_players[sp_from] % 2 : 0
661
662         # check validity and pass
663         if (!(sp_from in sp_players)) {
664                 say(".slap " FROM ", you are not playing.")
665         }
666         else if (!sp_passer(_team)) {
667                 reply("Your team did not go blind")
668         }
669         else if (sp_pass[sp_players[sp_from]]) {
670                 reply("You have already passed a card")
671         }
672         else if (!(_card in sp_deck)) {
673                 reply("Invalid card")
674         }
675         else if (!(_card in sp_hands[sp_from])) {
676                 reply("You do not have that card")
677         }
678         else {
679                 sp_pass[sp_players[sp_from]] = $2
680                 say(sp_channel, FROM " passes a card")
681         }
682
683         # check for end of passing
684         if ((!sp_passer(0) || (sp_pass[0] && sp_pass[2])) &&
685             (!sp_passer(1) || (sp_pass[1] && sp_pass[3]))) {
686                 for (i in sp_pass) {
687                         _partner = (i+2)%4
688                         _card    = sp_pass[i]
689                         delete sp_hands[sp_order[i]][_card]
690                         sp_hands[sp_order[_partner]][_card] = 1
691                 }
692                 say(sp_channel, "Cards have been passed!")
693                 say(sp_channel, sp_player ": you have the opening lead!")
694                 for (p in sp_players)
695                         say(p, "You have: " sp_hand(p, p))
696                 sp_state = "play"
697         }
698 }
699
700 sp_state ~ "(bid|pass|play)" &&
701 /^\.look$/ {
702         if (!(sp_from in sp_players)) {
703                 say(".slap " FROM ", you are not playing.")
704         } else {
705                 sp_looked[sp_players[sp_from]] = 1
706                 say(FROM, "You have: " sp_hand(FROM, sp_from))
707         }
708 }
709
710 sp_valid &&
711 sp_state == "play" &&
712 /^\.play (\S+)/ {
713         _card = $2
714         gsub(/[^A-Za-z0-9]/, "", _card);
715         if (!(_card in sp_deck)) {
716                 reply("Invalid card")
717         }
718         else if (sp_suit && _card !~ sp_suit && sp_hasa(sp_from, sp_suit)) {
719                 reply("You must follow suit (" sp_suit ")")
720         }
721         else if (_card ~ /s/ && length(sp_hands[sp_from]) == 13 && sp_hasa(sp_from, "[^s]$")) {
722                 reply("You cannot trump on the first hand")
723         }
724         else if (_card ~ /s/ && length(sp_pile) == 0 && sp_hasa(sp_from, "[^s]$") && !sp_broken) {
725                 reply("Spades have not been broken")
726         }
727         else if (!(_card in sp_hands[sp_from])) {
728                 reply("You do not have that card")
729         }
730         else {
731                 sp_play(_card)
732                 if (sp_state == "play") {
733                         if (length(sp_hands[sp_from]))
734                                 say(FROM, "You have: " sp_hand(FROM, sp_from))
735                         if (sp_piles)
736                                 say(sp_player ": it is your turn! " \
737                                     "(" sp_pretty(sp_piles, sp_player) ")")
738                         else
739                                 say(sp_player ": it is your turn!")
740                 }
741         }
742 }
743
744 /^\.last/ && sp_state == "play" {
745         if (!isarray(sp_last))
746                 say("No tricks have been taken!");
747         else
748                 say(sp_last["player"] " took " \
749                     sp_pretty(sp_last["hand"], FROM));
750 }
751
752 /^\.bids/ && sp_state == "bid" ||
753 /^\.turn/ && sp_state ~ "(bid|pass|play)" {
754         _bids  = sp_bidders()
755         _pile  = sp_pretty(sp_piles, FROM)
756         _extra = ""
757
758         for (_i in sp_share)
759                 if (/!/ && sp_share[_i] == sp_player)
760                         _extra = _extra " " _i "!"
761
762         if (sp_state == "bid" && !_bids)
763                 say("It is " sp_player "'s bid!" _extra)
764         if (sp_state == "bid" && _bids)
765                 say("It is " sp_player "'s bid!" _extra " (" _bids ")")
766         if (sp_state == "play" && !_pile)
767                 say("It is " sp_player "'s turn!" _extra)
768         if (sp_state == "play" && _pile)
769                 say("It is " sp_player "'s turn!" _extra " (" _pile ")")
770
771         for (_i=0; sp_state == "pass" && _i<4; _i++)
772                 if (sp_passer(_i) && !sp_pass[_i])
773                         say("Waiting for " sp_order[_i] " to pass a card!")
774
775         if (/!!/ && (sp_state == "bid" || sp_state == "play")) {
776                 if (sp_player in sp_notify) {
777                         _bids = _bids ? _bids    : "none"
778                         _pile = _pile ? sp_piles : "none"
779                         mail_send(sp_notify[sp_player],     \
780                                 "It is your " sp_state "!", \
781                                 "Bids so far:  " _bids "\n" \
782                                 "Cards played: " _pile)
783                         say("Notified " sp_player " at " sp_notify[sp_player])
784                 } else {
785                         say("No email address for " sp_player)
786                 }
787         }
788 }
789
790 /^\.bids$/ && sp_state ~ "(pass|play)" {
791         say(sp_order[0] " bid " sp_bid(0) ", " \
792             sp_order[2] " bid " sp_bid(2) ", " \
793             "total: " sp_bids[0] + sp_bids[2])
794         say(sp_order[1] " bid " sp_bid(1) ", " \
795             sp_order[3] " bid " sp_bid(3) ", " \
796             "total: " sp_bids[1] + sp_bids[3])
797 }
798
799 /^\.tricks$/ && sp_state == "play" {
800         say(sp_order[0] " took " int(sp_tricks[0]) "/" sp_bid(0) ", " \
801             sp_order[2] " took " int(sp_tricks[2]) "/" sp_bid(2))
802         say(sp_order[1] " took " int(sp_tricks[1]) "/" sp_bid(1) ", " \
803             sp_order[3] " took " int(sp_tricks[3]) "/" sp_bid(3))
804 }
805
806 (TO == NICK || DST == sp_channel) &&
807 /^\.(score|status)$/ {
808         if (sp_state == "new") {
809                 say("There is no game in progress")
810         }
811         if (sp_state ~ "join|bid|pass|play") {
812                 say("Playing to: " \
813                     sp_playto " points, " \
814                     sp_limit  " bags")
815         }
816         if (sp_state == "join") {
817                 say("Waiting for players: " \
818                     sp_order[0] " " sp_order[1] " " \
819                     sp_order[2] " " sp_order[3])
820         }
821         if (sp_state ~ "bid|pass|play") {
822                 say(sp_team(0) ": " \
823                     int(sp_scores[0]) " points, " \
824                     int(sp_bags(0))   " bags")
825                 say(sp_team(1) ": " \
826                     int(sp_scores[1]) " points, " \
827                     int(sp_bags(1))   " bags")
828         }
829 }
830
831 /^\.((new|end|load)game|join|look|bid|pass|play|notify)/ {
832         sp_save("var/sp_cur.json");
833 }