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