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