]> Pileus Git - ~andy/rhawk/blob - spades.awk
Fall though to .join after .newgame
[~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(sp_team(winner) " wins the game " \
317                             sp_scores[winner] " to " sp_scores[looser])
318                         say(sp_order[winner+0] "++")
319                         say(sp_order[winner+2] "++")
320                         say(sp_order[looser+0] "--")
321                         say(sp_order[looser+2] "--")
322                         sp_reset(2)
323
324                 } else {
325                         if (sp_scores[0] == sp_scores[1] && 
326                             sp_scores[0] >= sp_playto)
327                                 say("It's tie! Playing an extra round!");
328                         sp_reset(1)
329                         sp_deal()
330                 }
331         }
332 }
333
334 # Misc
335 BEGIN {
336         cmd = "od -An -N4 -td4 /dev/random"
337         cmd | getline seed
338         close(cmd)
339         srand(seed)
340         sp_init()
341         sp_reset(2)
342         sp_load("var/sp_cur.json");
343         #if (sp_channel)
344         #       say(sp_channel, "Game restored.")
345 }
346
347 // {
348         sp_from  = AUTH in sp_auths ? sp_auths[AUTH] : FROM
349         sp_valid = sp_from && sp_from == sp_player
350 }
351
352 CMD == "PRIVMSG" &&
353 ! /help/ &&
354 /[Ss]pades/ {
355         say("Spades! " sp_pretty("As,Ah,Ad,Ac", FROM))
356 }
357
358 AUTH == OWNER &&
359 /^\.savegame/ {
360         sp_save("var/sp_save.json");
361         say("Game saved.")
362 }
363
364 AUTH == OWNER &&
365 /^\.loadgame/ {
366         sp_load("var/sp_save.json");
367         say("Game loaded.")
368 }
369
370 # Help
371 /^\.help [Ss]pades$/ {
372         say("Spades -- play a game of spades")
373         say("Examples:")
374         say(".newgame [score] -- start a game to <score> points, default 500")
375         say(".endgame -- abort the current game")
376         say(".savegame -- save the current game to disk")
377         say(".loadgame -- load the previously saved game")
378         say(".join -- join the current game")
379         say(".look -- look at your cards")
380         say(".bid n -- bid for <n> tricks")
381         say(".play [card] -- play a card")
382         say(".score -- check the score")
383         say(".tricks -- check how many trick have been taken")
384         say(".bids -- check what everyone bid")
385         next
386 }
387
388 # Debugging
389 AUTH == OWNER &&
390 /^\.deal (\w+) (.*)/ {
391         say(sp_channel, FROM " is cheating for " $2)
392         delete sp_hands[$2]
393         for (i=3; i<=NF; i++)
394                 sp_hands[$2][$i] = 1
395         next
396 }
397
398 AUTH == OWNER &&
399 sp_state == "play" &&
400 /^\.play (\w+) (\S+)$/ {
401         say(sp_channel, FROM " is cheating for " $2)
402         sp_from = $2
403         sp_play($3)
404         next
405 }
406
407
408 # Setup
409 /^\.newgame ?([0-9]+)?/ {
410         if (sp_state != "new") {
411                 reply("There is already a game in progress.")
412         } else {
413                 $1         = ".join"
414                 sp_owner   = FROM
415                 sp_playto  = $2 ? $2 : 200
416                 sp_limit   = sp_playto > 200 ? 10 : 5;
417                 sp_state   = "join"
418                 sp_channel = DST
419                 say(sp_owner " starts a game of Spades to " sp_playto " with " sp_limit " bags!")
420         }
421 }
422
423 (sp_from == sp_owner || AUTH == OWNER) &&
424 /^\.endgame$/ {
425         if (sp_state == "new") {
426                 reply("There is no game in progress.")
427         } else {
428                 say(FROM " ends the game")
429                 sp_reset(2)
430         }
431 }
432
433 /^\.join$/ {
434         if (sp_state == "new") {
435                 reply("There is no game in progress")
436         }
437         else if (sp_state == "play") {
438                 reply("The game has already started")
439         }
440         else if (sp_state == "join" && sp_from in sp_players) {
441                 reply("You are already playing")
442         }
443         else if (sp_state == "join") {
444                 i = sp_next()
445                 sp_players[FROM] = i
446                 if (AUTH)
447                         sp_auths[AUTH] = FROM
448                 sp_order[i] = FROM
449                 say(FROM " joins the game!")
450         }
451         if (sp_state == "join" && sp_turn == 0)
452                 sp_deal()
453 }
454
455 !sp_valid &&
456 (sp_state "bid" || sp_state == "play") &&
457 /^\.(bid|play)\>/ {
458         if (sp_from in sp_players)
459                 say(".slap " FROM ", it is not your turn.")
460         else
461                 say(".slap " FROM ", you are not playing.")
462 }
463
464 sp_valid &&
465 sp_state == "bid" &&
466 /^\.bid [0-9]+$/ {
467         if ($2 < 0 || $2 > 13) {
468                 say("You can only bid from 0 to 13")
469         } else {
470                 i = sp_next()
471                 sp_bids[i] = $2
472                 if ($2 == 0 && !sp_looked[i]) {
473                         say(FROM " goes blind nil!")
474                         sp_nil[i] = 2
475                 } else if ($2 == 0) {
476                         say(FROM " goes nil!")
477                         sp_nil[i] = 1
478                 } else {
479                         sp_nil[i] = 0
480                 }
481                 if (sp_turn != sp_dealer) {
482                         say("Bidding goes to " sp_player "!")
483                 } else {
484                         for (p in sp_players)
485                                 say(p, "You have: " sp_hand(p, p))
486                         sp_state = "play"
487                         for (i=0; i<2; i++) {
488                                 if (sp_nil[i] == 2 || sp_nil[i+2] == 2) {
489                                         say(sp_team(i) ": select a card to pass " \
490                                             "(/msg " NICK " .pass <card>)")
491                                         sp_state = "pass"
492                                 }
493                         }
494                         if (sp_state == "play")
495                                 say("Play starts with " sp_player "!")
496                 }
497         }
498 }
499
500 sp_state == "pass" &&
501 /^\.pass (\S+)$/ {
502         _card = $2
503         _team = sp_from in sp_players ? sp_players[sp_from] % 2 : 0
504
505         # check validity and pass
506         if (!(sp_from in sp_players)) {
507                 say(".slap " FROM ", you are not playing.")
508         }
509         else if (sp_nil[_team] != 2 && sp_nil[_team+2] != 2) {
510                 reply("Your team did not go blind")
511         }
512         else if (sp_pass[sp_players[sp_from]]) {
513                 reply("You have already passed a card")
514         }
515         else if (!(_card in sp_deck)) {
516                 reply("Invalid card")
517         }
518         else if (!(_card in sp_hands[sp_from])) {
519                 reply("You do not have that card")
520         }
521         else {
522                 sp_pass[sp_players[sp_from]] = $2
523                 say(sp_channel, FROM " passes a card")
524         }
525
526         # check for end of passing
527         if (((sp_nil[0] != 2 && sp_nil[2] != 2) || (sp_pass[0] && sp_pass[2])) &&
528             ((sp_nil[1] != 2 && sp_nil[3] != 2) || (sp_pass[1] && sp_pass[3]))) {
529                 for (i in sp_pass) {
530                         _partner = (i+2)%4
531                         _card    = sp_pass[i]
532                         delete sp_hands[sp_order[i]][_card]
533                         sp_hands[sp_order[_partner]][_card] = 1
534                 }
535                 say(sp_channel, "Cards have been passed, play starts with " sp_player "!")
536                 for (p in sp_players)
537                         say(p, "You have: " sp_hand(p, p))
538                 sp_state = "play"
539         }
540 }
541
542 sp_state ~ "(bid|pass|play)" &&
543 /^\.look$/ {
544         if (!(sp_from in sp_players)) {
545                 say(".slap " FROM ", you are not playing.")
546         } else {
547                 sp_looked[sp_players[sp_from]] = 1
548                 say(FROM, "You have: " sp_hand(FROM, sp_from))
549         }
550 }
551
552 sp_valid &&
553 sp_state == "play" &&
554 /^\.play (\S+)$/ {
555         _card = $2
556         if (!(_card in sp_deck)) {
557                 reply("Invalid card")
558         }
559         else if (!(_card in sp_hands[sp_from])) {
560                 reply("You do not have that card")
561         }
562         else if (sp_suit && _card !~ sp_suit && sp_hasa(sp_from, sp_suit)) {
563                 reply("You must follow suit (" sp_suit ")")
564         }
565         else if (_card ~ /s/ && length(sp_hands[sp_from]) == 13 && sp_hasa(sp_from, "[^s]$")) {
566                 reply("You cannot trump on the first hand")
567         }
568         else if (_card ~ /s/ && length(sp_pile) == 0 && sp_hasa(sp_from, "[^s]$") && !sp_broken) {
569                 reply("Spades have not been broken")
570         }
571         else {
572                 sp_play(_card)
573                 if (sp_state == "play") {
574                         if (length(sp_hands[sp_from]))
575                                 say(FROM, "You have: " sp_hand(FROM, sp_from))
576                         if (sp_piles)
577                                 say(sp_player ": it is your turn! " \
578                                     "(" sp_pretty(sp_piles, sp_player) ")")
579                         else
580                                 say(sp_player ": it is your turn!")
581                 }
582         }
583 }
584
585 /^\.bids$/ && sp_state ~ "(pass|play)" {
586         say(sp_order[0] " bid " sp_bids[0] ", " \
587             sp_order[2] " bid " sp_bids[2] ", " \
588             "total: " sp_bids[0] + sp_bids[2])
589         say(sp_order[1] " bid " sp_bids[1] ", " \
590             sp_order[3] " bid " sp_bids[3] ", " \
591             "total: " sp_bids[1] + sp_bids[3])
592 }
593
594 /^\.tricks$/ && sp_state == "play" {
595         say(sp_order[0] " took " int(sp_tricks[0]) "/" int(sp_bids[0]) ", " \
596             sp_order[2] " took " int(sp_tricks[2]) "/" int(sp_bids[2]))
597         say(sp_order[1] " took " int(sp_tricks[1]) "/" int(sp_bids[1]) ", " \
598             sp_order[3] " took " int(sp_tricks[3]) "/" int(sp_bids[3]))
599 }
600
601 /^\.turn/ && sp_state ~ "(bid|pass|play)" {
602         _bids = sp_bidders()
603         _pile = sp_pretty(sp_piles, FROM)
604         if (sp_state == "bid" && !_bids)
605                 say("It is " sp_player "'s bid!")
606         if (sp_state == "bid" && _bids)
607                 say("It is " sp_player "'s bid! (" _bids ")")
608         if (sp_state == "play" && !_pile)
609                 say("It is " sp_player "'s turn!")
610         if (sp_state == "play" && _pile)
611                 say("It is " sp_player "'s turn! (" _pile ")")
612         for (_i=0; sp_state == "pass" && _i<4; _i++)
613                 if ((sp_nil[_i%2+0]==2 || sp_nil[_i%2+2]==2) && !sp_pass[_i])
614                         say("Waiting for " sp_order[_i] " to pass a card!")
615 }
616
617 (TO == NICK || DST == sp_channel) &&
618 /^\.(score|status)$/ {
619         if (sp_state == "new") {
620                 say("There is no game in progress")
621         }
622         if (sp_state == "join") {
623                 say("Waiting for players: " \
624                     sp_order[0] " " sp_order[1] " " \
625                     sp_order[2] " " sp_order[3])
626         }
627         if (sp_state ~ "bid|pass|play") {
628                 say("Playing to: " \
629                     sp_playto " points, " \
630                     sp_limit  " bags")
631                 say(sp_team(0) ": " \
632                     int(sp_scores[0]) " points, " \
633                     int(sp_bags(0))   " bags")
634                 say(sp_team(1) ": " \
635                     int(sp_scores[1]) " points, " \
636                     int(sp_bags(1))   " bags")
637         }
638 }
639
640 /^\.((new|end|load)game|join|look|bid|play)/ {
641         sp_save("var/sp_cur.json");
642 }
643
644 # Standin
645 #/^\.playfor [^ ]*$/ {
646 #}
647 #
648 #/^\.standin [^ ]*$/ {
649 #       if (p in sp_players) {
650 #       }
651 #       for (p in sp_standin) {
652 #               if ($2 in sp_standin) 
653 #               say(here " is already playing for " sp_standin[p]);
654 #       }
655 #       sp_standin[away] = here
656 #}
657 #