5 function sp_init(cards, tmp0, tmp1)
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"
13 for (i=1; i<=length(tmp0); i++)
17 function sp_reset(type)
21 sp_from = "" # The speakers player name
22 sp_valid = "" # It is the speaker turn
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
34 sp_state = "bid" # {new,join,bid,pass,play}
35 sp_broken = 0 # Whether spades are broken
36 delete sp_last # [x] The result of the last hand
37 delete sp_hands # [p] Each players cards
38 delete sp_looked # [i] Whether a player has looked a their cards
39 delete sp_bids # [i] Each players bid
40 delete sp_nil # [i] Nil multiplier 0=regular, 1=nil, 2=blind
41 delete sp_pass # [i] Cards to pass
42 delete sp_tricks # [i] Tricks this round
47 sp_channel = "" # channel to play in
48 sp_state = "new" # {new,join,bid,pass,play}
49 sp_owner = "" # Who started the game
50 sp_playto = 0 # Score the game will go to
51 sp_dealer =-1 # Who is dealing this round
52 sp_turn = 0 # Index of who's turn it is
53 sp_player = "" # Who's turn it is
54 sp_limit = 10 # Bag out limit / nil bonus
55 delete sp_players # [p] Player names players["name"] -> i
56 delete sp_auths # [c] Player auth names auths["auth"] -> "name"
57 delete sp_share # [c] Player teammates share["friend"] -> "name"
58 delete sp_order # [i] Player order order[i] -> "name"
59 delete sp_scores # [i] Teams score
64 sp_log = "" # Log file name
65 delete sp_notify # [p] E-mail notification address
69 function sp_acopy(dst, src, key)
74 json_copy(dst, key, src[key])
78 function sp_save(file, game)
81 game["suit"] = sp_suit;
82 game["piles"] = sp_piles;
83 json_copy(game, "pile", sp_pile);
86 game["state"] = sp_state;
87 game["broken"] = sp_broken;
88 json_copy(game, "last", sp_last);
89 json_copy(game, "looked", sp_looked);
90 json_copy(game, "bids", sp_bids);
91 json_copy(game, "nil", sp_nil);
92 json_copy(game, "pass", sp_pass);
93 json_copy(game, "tricks", sp_tricks);
96 game["channel"] = sp_channel;
97 game["owner"] = sp_owner;
98 game["playto"] = sp_playto;
99 game["dealer"] = sp_dealer;
100 game["turn"] = sp_turn;
101 game["player"] = sp_player;
102 game["limit"] = sp_limit;
103 game["log"] = sp_log;
104 json_copy(game, "hands", sp_hands);
105 json_copy(game, "players", sp_players);
106 json_copy(game, "auths", sp_auths);
107 json_copy(game, "share", sp_share);
108 json_copy(game, "order", sp_order);
109 json_copy(game, "scores", sp_scores);
110 json_copy(game, "notify", sp_notify);
113 json_save(file, game);
116 function sp_load(file, game)
119 if (!json_load(file, game))
123 sp_suit = game["suit"];
124 sp_piles = game["piles"];
125 sp_acopy(sp_pile, game["pile"]);
128 sp_state = game["state"];
129 sp_broken = game["broken"];
130 sp_acopy(sp_last, game["last"]);
131 sp_acopy(sp_looked, game["looked"]);
132 sp_acopy(sp_bids, game["bids"]);
133 sp_acopy(sp_nil, game["nil"]);
134 sp_acopy(sp_pass, game["pass"]);
135 sp_acopy(sp_tricks, game["tricks"]);
138 sp_channel = game["channel"];
139 sp_owner = game["owner"];
140 sp_playto = game["playto"];
141 sp_dealer = game["dealer"];
142 sp_turn = game["turn"];
143 sp_player = game["player"];
144 sp_limit = game["limit"];
145 sp_log = game["log"];
146 sp_acopy(sp_hands, game["hands"]);
147 sp_acopy(sp_players, game["players"]);
148 sp_acopy(sp_auths, game["auths"]);
149 sp_acopy(sp_share, game["share"]);
150 sp_acopy(sp_order, game["order"]);
151 sp_acopy(sp_scores, game["scores"]);
152 sp_acopy(sp_notify, game["notify"]);
157 print strftime("%Y-%m-%d %H:%M:%S | ") msg >> "logs/" sp_log
158 fflush("logs/" sp_log)
159 say(sp_channel, msg);
162 function sp_pretty(cards, who)
165 gsub(/[0-9JQKA]*[sc]/, "\0031,00\002&\017", cards) # black
166 gsub(/[0-9JQKA]*[hd]/, "\0034,00\002&\017", cards) # red
168 if (!nounicode[who]) {
169 gsub(/s/, "\002♠", cards)
170 gsub(/h/, "\002♥", cards)
171 gsub(/d/, "\002♦", cards)
172 gsub(/c/, "\002♣", cards)
177 function sp_next(who, prev)
180 sp_turn = who ? sp_players[who] : (sp_turn + 1) % 4
181 if (length(sp_order) == 4)
182 sp_player = sp_order[sp_turn]
186 function sp_shuf(i, mixed)
188 sp_usort(sp_players, mixed)
190 sp_order[i-1] = mixed[i]
191 sp_players[mixed[i]] = i-1
195 function sp_deal( shuf)
197 sp_say("/me deals the cards")
198 sp_usort(sp_deck, shuf)
199 for (i=1; i<=52; i++)
200 sp_hands[sp_order[i%4]][shuf[i]] = 1
202 sp_dealer = (sp_dealer+1)%4
204 sp_player = sp_order[sp_turn]
205 sp_say(sp_player ": you bid first!")
208 function sp_hand(to, who, sort, str)
210 asorti(sp_hands[who], sort, "sp_csort")
211 for (i=0; i<length(sort); i++)
212 str = str "" sprintf("%4s", sort[i])
213 gsub(/^ +| +$/, "", str)
214 return sp_pretty(str, to)
217 function sp_hasa(who, expr)
219 for (c in sp_hands[who]) {
226 function sp_type(card)
228 return substr(card, length(card))
231 function sp_usort(list, out) {
234 asorti(out, out, "@val_num_asc")
237 function sp_csort(i1,v1,i2,v2) {
238 return sp_deck[i1] > sp_deck[i2] ? +1 :
239 sp_deck[i1] < sp_deck[i2] ? -1 : 0;
242 function sp_winner( card, tmp)
244 for (card in sp_pile)
245 if (card !~ sp_suit && card !~ /s/)
247 asorti(sp_pile, tmp, "sp_csort")
248 #print "pile: " tmp[1] ">" tmp[2] ">" tmp[3] ">" tmp[4]
254 #return "{" sp_order[i+0] "," sp_order[i+2] "}"
255 return sp_order[i+0] "/" sp_order[i+2]
258 function sp_bags(i, bags)
260 bags = sp_scores[i] % sp_limit
268 return sp_nil[who] == 0 ? sp_bids[who] :
269 sp_nil[who] == 1 ? "nil" :
270 sp_nil[who] == 2 ? "blind" : "n/a"
273 function sp_passer(who)
275 return sp_nil[(who+0)%4] == 2 || sp_nil[(who+1)%4] != 0 ||
276 sp_nil[(who+2)%4] == 2 || sp_nil[(who+3)%4] != 0
279 function sp_bidders( i, turn, bid, bids)
281 for (i = 0; i < 4; i++) {
282 turn = (sp_dealer + i) % 4
283 if (bid = sp_bid(turn))
284 bids = bids " " sp_order[turn] ":" bid
286 gsub(/^ +| +$/, "", bids)
290 function sp_score( bids, times, tricks)
292 for (i=0; i<2; i++) {
293 bids = sp_bids[i] + sp_bids[i+2]
294 tricks = sp_tricks[i] + sp_tricks[i+2]
296 times = int((sp_bags(i) + bags) / sp_limit)
298 sp_say(sp_team(i) " bag" (times>1?" way ":" ") "out")
299 sp_scores[i] -= sp_limit * 10 * times;
301 if (tricks >= bids) {
302 sp_say(sp_team(i) " make their bid: " tricks "/" bids)
303 sp_scores[i] += bids*10 + bags;
305 sp_say(sp_team(i) " go bust: " tricks "/" bids)
306 sp_scores[i] -= bids*10;
309 for (i=0; i<4; i++) {
312 sp_say(sp_order[i] " " \
313 (sp_nil[i] == 1 && !sp_tricks[i] ? "makes nil!" :
314 sp_nil[i] == 1 && sp_tricks[i] ? "fails at nil!" :
315 sp_nil[i] == 2 && !sp_tricks[i] ? "makes blind nil!" :
316 sp_nil[i] == 2 && sp_tricks[i] ? "fails miserably at blind nil!" :
318 sp_scores[i%2] += sp_limit * 10 * sp_nil[i] * \
319 (sp_tricks[i] == 0 ? 1 : -1)
321 if (sp_scores[0] > sp_scores[1])
322 sp_say(sp_team(0) " lead " sp_scores[0] " to " sp_scores[1] " of " sp_playto)
323 else if (sp_scores[1] > sp_scores[0])
324 sp_say(sp_team(1) " lead " sp_scores[1] " to " sp_scores[0] " of " sp_playto)
326 sp_say("tied at " sp_scores[0] " of " sp_playto)
329 function sp_play(card, winner, pi)
331 delete sp_hands[sp_from][card]
332 sp_pile[card] = sp_player
333 sp_piles = sp_piles (sp_piles?",":"") card
340 if (length(sp_pile) == 1)
341 sp_suit = sp_type(card)
344 if (length(sp_pile) == 4) {
346 pi = sp_players[sp_pile[winner]]
348 sp_say(sp_pile[winner] " wins with " sp_pretty(winner, FROM) \
349 " (" sp_pretty(sp_piles, FROM) ")")
350 sp_last["player"] = sp_pile[winner];
351 sp_last["pile"] = sp_piles;
352 sp_next(sp_pile[winner])
357 if (sp_tricks[0] + sp_tricks[1] + \
358 sp_tricks[2] + sp_tricks[3] == 13) {
359 sp_say("Round over!")
361 if (sp_scores[0] >= sp_playto || sp_scores[1] >= sp_playto &&
362 sp_scores[0] != sp_scores[1]) {
364 winner = sp_scores[0] > sp_scores[1] ? 0 : 1
366 say(CHANNEL, sp_team(winner) " wins the game " \
367 sp_scores[winner] " to " sp_scores[looser])
368 say(CHANNEL, sp_order[winner+0] "++")
369 say(CHANNEL, sp_order[winner+2] "++")
373 if (sp_scores[0] == sp_scores[1] &&
374 sp_scores[0] >= sp_playto)
375 sp_say("It's tie! Playing an extra round!");
384 cmd = "od -An -N4 -td4 /dev/random"
390 sp_load("var/sp_cur.json");
392 # sp_say("Game restored.")
396 sp_from = AUTH in sp_auths ? sp_auths[AUTH] : \
397 AUTH in sp_share ? sp_share[AUTH] : FROM
398 sp_valid = sp_from && sp_from == sp_player
404 say("Spades! " sp_pretty("As,Ah,Ad,Ac", FROM))
409 sp_save("var/sp_save.json");
415 sp_load("var/sp_save.json");
421 say(".help spades -- play a game of spades")
424 /^\.help [Ss]pades$/ {
425 say("Spades -- play a game of spades")
426 say(".help game -- setup and administer the game")
427 say(".help play -- commands for playing spades")
428 say(".help auth -- control player authorization")
433 say(".newgame [score] -- start a game to <score> points, default 500")
434 say(".endgame -- abort the current game")
435 say(".savegame -- save the current game to disk")
436 say(".loadgame -- load the previously saved game")
441 say(".join -- join the current game")
442 say(".look -- look at your cards")
443 say(".bid [n] -- bid for <n> tricks")
444 say(".pass [card] -- pass a card to your partner")
445 say(".play [card] -- play a card")
446 say(".last -- show who took the previous trick")
447 say(".turn -- check whose turn it is")
448 say(".bids -- check what everyone bid")
449 say(".tricks -- check how many trick have been taken")
450 say(".score -- check the score")
455 say(".auth [who] -- display authentication info for a user")
456 say(".allow [who] -- allow another person to play on your behalf")
457 say(".deny [who] -- prevent a previously allowed user from playing")
458 say(".show -- display which users can play for which players")
459 say(".notify [addr] -- email user when it is their turn")
465 /^\.deal (\w+) (.*)/ {
466 sp_say(FROM " is cheating for " $2)
468 for (i=3; i<=NF; i++)
474 /^\.order (\w+) ([0-4])/ {
475 sp_say(FROM " is cheating for " $2)
478 sp_player = sp_order[sp_turn]
482 sp_state == "play" &&
483 /^\.force (\w+) (\S+)$/ {
484 sp_say(FROM " is cheating for " $2)
492 match($0, /^\.newgame ?([1-9][0-9]*) *- *([1-9][0-9]*)$/, _arr) {
493 if (_arr[2] > _arr[1])
494 $0 = $1 " " int(rand() * (_arr[2]-_arr[1])+_arr[1])
497 /^\.newgame ?([1-9][0-9]*)?$/ {
498 if (sp_state != "new") {
499 reply("There is already a game in progress.")
503 sp_playto = $2 ? $2 : 200
504 sp_limit = sp_playto > 200 ? 10 : 5;
507 sp_log = strftime("%Y%m%d_%H%M%S.log")
508 sp_say(sp_owner " starts a game of Spades to " sp_playto " with " sp_limit " bags!")
512 (sp_from == sp_owner || AUTH == OWNER) &&
514 if (sp_state == "new") {
515 reply("There is no game in progress.")
517 sp_say(FROM " ends the game")
523 if (sp_state == "new") {
524 reply("There is no game in progress")
526 else if (sp_state == "play") {
527 reply("The game has already started")
529 else if (sp_state == "join" && sp_from in sp_players) {
530 reply("You are already playing")
532 else if (sp_state == "join") {
536 sp_auths[AUTH] = FROM
538 sp_say(FROM " joins the game!")
540 if (sp_state == "join" && sp_turn == 0) {
547 _who = $2 in USERS ? USERS[$2]["auth"] : ""
548 _str = _who && _who != $2 ? $2 " (" _who ")" : $2
549 if (sp_state ~ "new|join") {
550 reply("The game has not yet started")
552 else if (!(sp_from in sp_players)) {
553 reply("You are not playing")
556 reply(_str " is not logged in")
558 else if (_who in sp_players || _who in sp_auths) {
559 reply(_str " is a primary player")
561 else if (_who in sp_share) {
562 reply(_str " is already playing for " sp_share[_who])
565 sp_say(_str " can now play for " sp_from)
566 sp_share[_who] = sp_from
571 _who = $2 in USERS ? USERS[$2]["auth"] : $2
572 _str = _who && _who != $2 ? $2 " (" _who ")" : $2
573 if (sp_state ~ "new|join") {
574 reply("The game has not yet started")
576 else if (!(sp_from in sp_players)) {
577 reply("You are not playing")
579 else if (_who in sp_players || _who in sp_auths) {
580 reply(_str " is a primary player")
582 else if (!(_who in sp_share) || sp_share[_who] != sp_from) {
583 reply(_str " is not playing for " sp_from)
586 sp_say(_str " can no longer play for " sp_from)
587 delete sp_share[_who]
592 if (sp_from in sp_notify)
593 reply("Your address is " sp_notify[sp_from])
595 reply("Your address is not set")
599 if (sp_from in sp_notify) {
600 reply("Removing address " sp_notify[sp_from])
601 delete sp_notify[sp_from]
603 reply("Your address is not set")
607 /^\.notify \S+@\S+.\S+$/ {
609 gsub(/[^a-zA-Z0-9_+@.-]/, "", _addr)
610 sp_notify[sp_from] = _addr
611 reply("Notifying you at " _addr)
614 sp_state ~ "(bid|pass|play)" &&
618 _lines[sp_share[_i]] = _lines[sp_share[_i]] " " _i
620 sp_say(_i " allowed:" _lines[_i])
624 (sp_state == "bid" || sp_state == "play") &&
626 if (sp_from in sp_players)
627 reply("It is not your turn.")
629 reply("You are not playing.")
634 /^\.bid (0|[1-9][0-9]*)$/ {
635 if ($2 < 0 || $2 > 13) {
636 reply("You can only bid from 0 to 13")
640 if ($2 == 0 && !sp_looked[i]) {
641 sp_say(FROM " goes blind nil!")
643 } else if ($2 == 0) {
644 sp_say(FROM " goes nil!")
649 if (sp_turn != sp_dealer) {
650 sp_say(sp_player ": it is your bid! (" sp_bidders() ")")
652 for (p in sp_players)
653 say(p, "You have: " sp_hand(p, p))
655 for (i=0; i<2; i++) {
657 sp_say(sp_team(i) ": select a card to pass " \
658 "(/msg " NICK " .pass <card>)")
662 if (sp_state == "play")
663 sp_say(sp_player ": you have the opening lead!")
668 sp_state == "pass" &&
671 _team = sp_from in sp_players ? sp_players[sp_from] % 2 : 0
673 # check validity and pass
674 if (!(sp_from in sp_players)) {
675 reply("You are not playing.")
677 else if (!sp_passer(_team)) {
678 reply("Your team did not go blind")
680 else if (sp_pass[sp_players[sp_from]]) {
681 reply("You have already passed a card")
683 else if (!(_card in sp_deck)) {
684 reply("Invalid card")
686 else if (!(_card in sp_hands[sp_from])) {
687 reply("You do not have that card")
690 sp_pass[sp_players[sp_from]] = $2
691 sp_say(FROM " passes a card")
694 # check for end of passing
695 if ((!sp_passer(0) || (sp_pass[0] && sp_pass[2])) &&
696 (!sp_passer(1) || (sp_pass[1] && sp_pass[3]))) {
700 delete sp_hands[sp_order[i]][_card]
701 sp_hands[sp_order[_partner]][_card] = 1
703 sp_say("Cards have been passed!")
704 sp_say(sp_player ": you have the opening lead!")
705 for (p in sp_players)
706 say(p, "You have: " sp_hand(p, p))
711 sp_state ~ "(bid|pass|play)" &&
713 if (!(sp_from in sp_players)) {
714 reply("You are not playing.")
716 sp_looked[sp_players[sp_from]] = 1
717 say(FROM, "You have: " sp_hand(FROM, sp_from))
722 sp_state == "play" &&
725 gsub(/[^A-Za-z0-9]/, "", _card);
726 if (!(_card in sp_deck)) {
727 reply("Invalid card")
729 else if (sp_suit && _card !~ sp_suit && sp_hasa(sp_from, sp_suit)) {
730 reply("You must follow suit (" sp_suit ")")
732 else if (_card ~ /s/ && length(sp_hands[sp_from]) == 13 && sp_hasa(sp_from, "[^s]$")) {
733 reply("You cannot trump on the first hand")
735 else if (_card ~ /s/ && length(sp_pile) == 0 && sp_hasa(sp_from, "[^s]$") && !sp_broken) {
736 reply("Spades have not been broken")
738 else if (!(_card in sp_hands[sp_from])) {
739 reply("You do not have that card")
743 if (sp_state == "play") {
744 if (length(sp_hands[sp_from]))
745 say(FROM, "You have: " sp_hand(FROM, sp_from))
747 sp_say(sp_player ": it is your turn! " \
748 "(" sp_pretty(sp_piles, sp_player) ")")
750 sp_say(sp_player ": it is your turn!")
755 /^\.last/ && sp_state == "play" {
756 if (!isarray(sp_last))
757 say("No tricks have been taken!");
759 say(sp_last["player"] " took " \
760 sp_pretty(sp_last["pile"], FROM));
763 /^\.bids/ && sp_state == "bid" ||
764 /^\.turn/ && sp_state ~ "(bid|pass|play)" {
766 _pile = sp_pretty(sp_piles, FROM)
770 if (/!/ && sp_share[_i] == sp_player)
771 _extra = _extra " " _i "!"
773 if (sp_state == "bid" && !_bids)
774 say("It is " sp_player "'s bid!" _extra)
775 if (sp_state == "bid" && _bids)
776 say("It is " sp_player "'s bid!" _extra " (" _bids ")")
777 if (sp_state == "play" && !_pile)
778 say("It is " sp_player "'s turn!" _extra)
779 if (sp_state == "play" && _pile)
780 say("It is " sp_player "'s turn!" _extra " (" _pile ")")
782 for (_i=0; sp_state == "pass" && _i<4; _i++)
783 if (sp_passer(_i) && !sp_pass[_i])
784 say("Waiting for " sp_order[_i] " to pass a card!")
786 if (/!!/ && (sp_state == "bid" || sp_state == "play")) {
787 if (sp_player in sp_notify) {
788 _bids = _bids ? _bids : "none"
789 _pile = _pile ? sp_piles : "none"
790 mail_send(sp_notify[sp_player], \
791 "It is your " sp_state "!", \
792 "Bids so far: " _bids "\n" \
793 "Cards played: " _pile)
794 say("Notified " sp_player " at " sp_notify[sp_player])
796 say("No email address for " sp_player)
801 /^\.bids$/ && sp_state ~ "(pass|play)" {
802 say(sp_order[0] " bid " sp_bid(0) ", " \
803 sp_order[2] " bid " sp_bid(2) ", " \
804 "total: " sp_bids[0] + sp_bids[2])
805 say(sp_order[1] " bid " sp_bid(1) ", " \
806 sp_order[3] " bid " sp_bid(3) ", " \
807 "total: " sp_bids[1] + sp_bids[3])
810 /^\.tricks$/ && sp_state == "play" {
811 say(sp_order[0] " took " int(sp_tricks[0]) "/" sp_bid(0) ", " \
812 sp_order[2] " took " int(sp_tricks[2]) "/" sp_bid(2))
813 say(sp_order[1] " took " int(sp_tricks[1]) "/" sp_bid(1) ", " \
814 sp_order[3] " took " int(sp_tricks[3]) "/" sp_bid(3))
817 (TO == NICK || DST == sp_channel) &&
818 /^\.(score|status)$/ {
819 if (sp_state == "new") {
820 say("There is no game in progress")
822 if (sp_state ~ "join|bid|pass|play") {
824 sp_playto " points, " \
827 if (sp_state == "join") {
828 say("Waiting for players: " \
829 sp_order[0] " " sp_order[1] " " \
830 sp_order[2] " " sp_order[3])
832 if (sp_state ~ "bid|pass|play") {
833 say(sp_team(0) ": " \
834 int(sp_scores[0]) " points, " \
835 int(sp_bags(0)) " bags")
836 say(sp_team(1) ": " \
837 int(sp_scores[1]) " points, " \
838 int(sp_bags(1)) " bags")
842 (DST == sp_channel) &&
844 say("http://pileus.org/andy/spades/" sp_log)
847 /^\.((new|end|load)game|join|look|bid|pass|play|notify)/ {
848 sp_save("var/sp_cur.json");