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