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