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