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