]> Pileus Git - ~andy/rhawk/blob - spades.awk
Add player shuffling
[~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 /^\.newgame ?([0-9]+)?$/ {
441         if (sp_state != "new") {
442                 reply("There is already a game in progress.")
443         } else {
444                 $1         = ".join"
445                 sp_owner   = FROM
446                 sp_playto  = $2 ? $2 : 200
447                 sp_limit   = sp_playto > 200 ? 10 : 5;
448                 sp_state   = "join"
449                 sp_channel = DST
450                 say(sp_owner " starts a game of Spades to " sp_playto " with " sp_limit " bags!")
451         }
452 }
453
454 (sp_from == sp_owner || AUTH == OWNER) &&
455 /^\.endgame$/ {
456         if (sp_state == "new") {
457                 reply("There is no game in progress.")
458         } else {
459                 say(FROM " ends the game")
460                 sp_reset(2)
461         }
462 }
463
464 /^\.join$/ {
465         if (sp_state == "new") {
466                 reply("There is no game in progress")
467         }
468         else if (sp_state == "play") {
469                 reply("The game has already started")
470         }
471         else if (sp_state == "join" && sp_from in sp_players) {
472                 reply("You are already playing")
473         }
474         else if (sp_state == "join") {
475                 i = sp_next()
476                 sp_players[FROM] = i
477                 if (AUTH)
478                         sp_auths[AUTH] = FROM
479                 sp_order[i] = FROM
480                 say(FROM " joins the game!")
481         }
482         if (sp_state == "join" && sp_turn == 0) {
483                 sp_shuf()
484                 sp_deal()
485         }
486 }
487
488 /^\.allow \S+$/ {
489         _who = $2 in USERS ? USERS[$2]["auth"] : ""
490         _str = _who && _who != $2 ? $2 " (" _who ")" : $2
491         if (sp_state ~ "new|join") {
492                 reply("The game has not yet started")
493         }
494         else if (!(sp_from in sp_players)) {
495                 reply("You are not playing")
496         }
497         else if (!_who) {
498                 reply(_str " is not logged in")
499         }
500         else if (_who in sp_players || _who in sp_auths) {
501                 reply(_str " is a primary player")
502         }
503         else if (_who in sp_share) {
504                 reply(_str " is already playing for " sp_share[_who])
505         }
506         else {
507                 reply(_str " can now play for " sp_from)
508                 sp_share[_who] = sp_from
509         }
510 }
511
512 /^\.deny \S+$/ {
513         _who = $2 in USERS ? USERS[$2]["auth"] : $2
514         _str = _who && _who != $2 ? $2 " (" _who ")" : $2
515         if (sp_state ~ "new|join") {
516                 reply("The game has not yet started")
517         }
518         else if (!(sp_from in sp_players)) {
519                 reply("You are not playing")
520         }
521         else if (_who in sp_players || _who in sp_auths) {
522                 reply(_str " is a primary player")
523         }
524         else if (!(_who in sp_share) || sp_share[_who] != sp_from) {
525                 reply(_str " is not playing for " sp_from)
526         }
527         else {
528                 reply(_str " can no longer play for " sp_from)
529                 delete sp_share[_who]
530         }
531 }
532
533 sp_state ~ "(bid|pass|play)" &&
534 /^\.show/ {
535         for (_i in sp_share)
536                 say(_i " can play for " sp_share[_i]);
537 }
538
539 !sp_valid &&
540 (sp_state == "bid" || sp_state == "play") &&
541 /^\.(bid|play)\>/ {
542         if (sp_from in sp_players)
543                 say(".slap " FROM ", it is not your turn.")
544         else
545                 say(".slap " FROM ", you are not playing.")
546 }
547
548 sp_valid &&
549 sp_state == "bid" &&
550 /^\.bid [0-9]+$/ {
551         if ($2 < 0 || $2 > 13) {
552                 say("You can only bid from 0 to 13")
553         } else {
554                 i = sp_next()
555                 sp_bids[i] = $2
556                 if ($2 == 0 && !sp_looked[i]) {
557                         say(FROM " goes blind nil!")
558                         sp_nil[i] = 2
559                 } else if ($2 == 0) {
560                         say(FROM " goes nil!")
561                         sp_nil[i] = 1
562                 } else {
563                         sp_nil[i] = 0
564                 }
565                 if (sp_turn != sp_dealer) {
566                         say("Bidding goes to " sp_player "!")
567                 } else {
568                         for (p in sp_players)
569                                 say(p, "You have: " sp_hand(p, p))
570                         sp_state = "play"
571                         for (i=0; i<2; i++) {
572                                 if (sp_nil[i] == 2 || sp_nil[i+2] == 2) {
573                                         say(sp_team(i) ": select a card to pass " \
574                                             "(/msg " NICK " .pass <card>)")
575                                         sp_state = "pass"
576                                 }
577                         }
578                         if (sp_state == "play")
579                                 say("Play starts with " sp_player "!")
580                 }
581         }
582 }
583
584 sp_state == "pass" &&
585 /^\.pass (\S+)$/ {
586         _card = $2
587         _team = sp_from in sp_players ? sp_players[sp_from] % 2 : 0
588
589         # check validity and pass
590         if (!(sp_from in sp_players)) {
591                 say(".slap " FROM ", you are not playing.")
592         }
593         else if (sp_nil[_team] != 2 && sp_nil[_team+2] != 2) {
594                 reply("Your team did not go blind")
595         }
596         else if (sp_pass[sp_players[sp_from]]) {
597                 reply("You have already passed a card")
598         }
599         else if (!(_card in sp_deck)) {
600                 reply("Invalid card")
601         }
602         else if (!(_card in sp_hands[sp_from])) {
603                 reply("You do not have that card")
604         }
605         else {
606                 sp_pass[sp_players[sp_from]] = $2
607                 say(sp_channel, FROM " passes a card")
608         }
609
610         # check for end of passing
611         if (((sp_nil[0] != 2 && sp_nil[2] != 2) || (sp_pass[0] && sp_pass[2])) &&
612             ((sp_nil[1] != 2 && sp_nil[3] != 2) || (sp_pass[1] && sp_pass[3]))) {
613                 for (i in sp_pass) {
614                         _partner = (i+2)%4
615                         _card    = sp_pass[i]
616                         delete sp_hands[sp_order[i]][_card]
617                         sp_hands[sp_order[_partner]][_card] = 1
618                 }
619                 say(sp_channel, "Cards have been passed, play starts with " sp_player "!")
620                 for (p in sp_players)
621                         say(p, "You have: " sp_hand(p, p))
622                 sp_state = "play"
623         }
624 }
625
626 sp_state ~ "(bid|pass|play)" &&
627 /^\.look$/ {
628         if (!(sp_from in sp_players)) {
629                 say(".slap " FROM ", you are not playing.")
630         } else {
631                 sp_looked[sp_players[sp_from]] = 1
632                 say(FROM, "You have: " sp_hand(FROM, sp_from))
633         }
634 }
635
636 sp_valid &&
637 sp_state == "play" &&
638 /^\.play (\S+)/ {
639         _card = $2
640         gsub(/[^A-Za-z0-9]/, "", _card);
641         if (!(_card in sp_deck)) {
642                 reply("Invalid card")
643         }
644         else if (!(_card in sp_hands[sp_from])) {
645                 reply("You do not have that card")
646         }
647         else if (sp_suit && _card !~ sp_suit && sp_hasa(sp_from, sp_suit)) {
648                 reply("You must follow suit (" sp_suit ")")
649         }
650         else if (_card ~ /s/ && length(sp_hands[sp_from]) == 13 && sp_hasa(sp_from, "[^s]$")) {
651                 reply("You cannot trump on the first hand")
652         }
653         else if (_card ~ /s/ && length(sp_pile) == 0 && sp_hasa(sp_from, "[^s]$") && !sp_broken) {
654                 reply("Spades have not been broken")
655         }
656         else {
657                 sp_play(_card)
658                 if (sp_state == "play") {
659                         if (length(sp_hands[sp_from]))
660                                 say(FROM, "You have: " sp_hand(FROM, sp_from))
661                         if (sp_piles)
662                                 say(sp_player ": it is your turn! " \
663                                     "(" sp_pretty(sp_piles, sp_player) ")")
664                         else
665                                 say(sp_player ": it is your turn!")
666                 }
667         }
668 }
669
670 /^\.turn/ && sp_state ~ "(bid|pass|play)" {
671         _bids = sp_bidders()
672         _pile = sp_pretty(sp_piles, FROM)
673         if (sp_state == "bid" && !_bids)
674                 say("It is " sp_player "'s bid!")
675         if (sp_state == "bid" && _bids)
676                 say("It is " sp_player "'s bid! (" _bids ")")
677         if (sp_state == "play" && !_pile)
678                 say("It is " sp_player "'s turn!")
679         if (sp_state == "play" && _pile)
680                 say("It is " sp_player "'s turn! (" _pile ")")
681         for (_i=0; sp_state == "pass" && _i<4; _i++)
682                 if ((sp_nil[_i%2+0]==2 || sp_nil[_i%2+2]==2) && !sp_pass[_i])
683                         say("Waiting for " sp_order[_i] " to pass a card!")
684 }
685
686 /^\.bids$/ && sp_state ~ "(pass|play)" {
687         say(sp_order[0] " bid " sp_bid(0) ", " \
688             sp_order[2] " bid " sp_bid(2) ", " \
689             "total: " sp_bids[0] + sp_bids[2])
690         say(sp_order[1] " bid " sp_bid(1) ", " \
691             sp_order[3] " bid " sp_bid(3) ", " \
692             "total: " sp_bids[1] + sp_bids[3])
693 }
694
695 /^\.tricks$/ && sp_state == "play" {
696         say(sp_order[0] " took " int(sp_tricks[0]) "/" sp_bid(0) ", " \
697             sp_order[2] " took " int(sp_tricks[2]) "/" sp_bid(2))
698         say(sp_order[1] " took " int(sp_tricks[1]) "/" sp_bid(1) ", " \
699             sp_order[3] " took " int(sp_tricks[3]) "/" sp_bid(3))
700 }
701
702 (TO == NICK || DST == sp_channel) &&
703 /^\.(score|status)$/ {
704         if (sp_state == "new") {
705                 say("There is no game in progress")
706         }
707         if (sp_state ~ "join|bid|pass|play") {
708                 say("Playing to: " \
709                     sp_playto " points, " \
710                     sp_limit  " bags")
711         }
712         if (sp_state == "join") {
713                 say("Waiting for players: " \
714                     sp_order[0] " " sp_order[1] " " \
715                     sp_order[2] " " sp_order[3])
716         }
717         if (sp_state ~ "bid|pass|play") {
718                 say(sp_team(0) ": " \
719                     int(sp_scores[0]) " points, " \
720                     int(sp_bags(0))   " bags")
721                 say(sp_team(1) ": " \
722                     int(sp_scores[1]) " points, " \
723                     int(sp_bags(1))   " bags")
724         }
725 }
726
727 /^\.((new|end|load)game|join|look|bid|pass|play)/ {
728         sp_save("var/sp_cur.json");
729 }