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