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