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