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