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