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