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