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