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!");
383 function sp_delay(sec)
385 return (sec > 60*60*24 ? int(sec/60/60/24) "d " : "") \
386 (sec > 60*60 ? int(sec/60/60)%24 "h " : "") \
390 function sp_max(list, i, max)
392 for (i=0; i<length(list); i++)
393 if (max == "" || list[i] > max)
398 function sp_avg(list, i, sum)
400 for (i=0; i<length(list); i++)
402 return sum / length(list)
405 function sp_stats(file, line, arr, time, user, turn, start, delay)
408 while (getline line < file) {
410 if (!match(line, /^([0-9\- \:]*) \| (.*)$/, arr))
412 gsub(/[:-]/, " ", arr[1])
413 time = mktime(arr[1])
416 if (!match(arr[2], /^([^:]*): (.*)$/, arr))
420 # Record user latency
422 delay[turn][length(delay[turn])] = time - start
425 if (match(arr[2], /^it is your.*$/, arr)) {
432 for (user in delay) {
433 say("latency for " user \
434 ": " sp_delay(sp_avg(delay[user])) " (avg)" \
435 ", " sp_delay(sp_max(delay[user])) " (max)")
441 cmd = "od -An -N4 -td4 /dev/random"
447 sp_load("var/sp_cur.json");
449 # sp_say("Game restored.")
453 sp_from = AUTH in sp_auths ? sp_auths[AUTH] : \
454 AUTH in sp_share ? sp_share[AUTH] : FROM
455 sp_valid = sp_from && sp_from == sp_player
461 say("Spades! " sp_pretty("As,Ah,Ad,Ac", FROM))
466 sp_save("var/sp_save.json");
472 sp_load("var/sp_save.json");
478 say(".help spades -- play a game of spades")
481 /^\.help [Ss]pades$/ {
482 say("Spades -- play a game of spades")
483 say(".help game -- setup and administer the game")
484 say(".help play -- commands for playing spades")
485 say(".help auth -- control player authorization")
490 say(".newgame [score] -- start a game to <score> points, default 500")
491 say(".endgame -- abort the current game")
492 say(".savegame -- save the current game to disk")
493 say(".loadgame -- load the previously saved game")
498 say(".join -- join the current game")
499 say(".look -- look at your cards")
500 say(".bid [n] -- bid for <n> tricks")
501 say(".pass [card] -- pass a card to your partner")
502 say(".play [card] -- play a card")
503 say(".last -- show who took the previous trick")
504 say(".turn -- check whose turn it is")
505 say(".bids -- check what everyone bid")
506 say(".tricks -- check how many trick have been taken")
507 say(".score -- check the score")
512 say(".auth [who] -- display authentication info for a user")
513 say(".allow [who] -- allow another person to play on your behalf")
514 say(".deny [who] -- prevent a previously allowed user from playing")
515 say(".show -- display which users can play for which players")
516 say(".notify [addr] -- email user when it is their turn")
522 /^\.deal (\w+) (.*)/ {
523 sp_say(FROM " is cheating for " $2)
525 for (i=3; i<=NF; i++)
531 /^\.order (\w+) ([0-4])/ {
532 sp_say(FROM " is cheating for " $2)
535 sp_player = sp_order[sp_turn]
539 sp_state == "play" &&
540 /^\.force (\w+) (\S+)$/ {
541 sp_say(FROM " is cheating for " $2)
549 match($0, /^\.newgame ?([1-9][0-9]*) *- *([1-9][0-9]*)$/, _arr) {
550 if (_arr[2] > _arr[1])
551 $0 = $1 " " int(rand() * (_arr[2]-_arr[1])+_arr[1])
554 /^\.newgame ?([1-9][0-9]*)?$/ {
555 if (sp_state != "new") {
556 reply("There is already a game in progress.")
560 sp_playto = $2 ? $2 : 200
561 sp_limit = sp_playto > 200 ? 10 : 5;
564 sp_log = strftime("%Y%m%d_%H%M%S.log")
565 sp_say(sp_owner " starts a game of Spades to " sp_playto " with " sp_limit " bags!")
569 (sp_from == sp_owner || AUTH == OWNER) &&
571 if (sp_state == "new") {
572 reply("There is no game in progress.")
574 sp_say(FROM " ends the game")
580 if (sp_state == "new") {
581 reply("There is no game in progress")
583 else if (sp_state == "play") {
584 reply("The game has already started")
586 else if (sp_state == "join" && sp_from in sp_players) {
587 reply("You are already playing")
589 else if (sp_state == "join") {
593 sp_auths[AUTH] = FROM
595 sp_say(FROM " joins the game!")
597 if (sp_state == "join" && sp_turn == 0) {
604 _who = $2 in USERS ? USERS[$2]["auth"] : ""
605 _str = _who && _who != $2 ? $2 " (" _who ")" : $2
606 if (sp_state ~ "new|join") {
607 reply("The game has not yet started")
609 else if (!(sp_from in sp_players)) {
610 reply("You are not playing")
613 reply(_str " is not logged in")
615 else if (_who in sp_players || _who in sp_auths) {
616 reply(_str " is a primary player")
618 else if (_who in sp_share) {
619 reply(_str " is already playing for " sp_share[_who])
622 sp_say(_str " can now play for " sp_from)
623 sp_share[_who] = sp_from
628 _who = $2 in USERS ? USERS[$2]["auth"] : $2
629 _str = _who && _who != $2 ? $2 " (" _who ")" : $2
630 if (sp_state ~ "new|join") {
631 reply("The game has not yet started")
633 else if (!(sp_from in sp_players)) {
634 reply("You are not playing")
636 else if (_who in sp_players || _who in sp_auths) {
637 reply(_str " is a primary player")
639 else if (!(_who in sp_share) || sp_share[_who] != sp_from) {
640 reply(_str " is not playing for " sp_from)
643 sp_say(_str " can no longer play for " sp_from)
644 delete sp_share[_who]
649 if (sp_from in sp_notify)
650 reply("Your address is " sp_notify[sp_from])
652 reply("Your address is not set")
656 if (sp_from in sp_notify) {
657 reply("Removing address " sp_notify[sp_from])
658 delete sp_notify[sp_from]
660 reply("Your address is not set")
664 /^\.notify \S+@\S+.\S+$/ {
666 gsub(/[^a-zA-Z0-9_+@.-]/, "", _addr)
667 sp_notify[sp_from] = _addr
668 reply("Notifying you at " _addr)
671 sp_state ~ "(bid|pass|play)" &&
675 _lines[sp_share[_i]] = _lines[sp_share[_i]] " " _i
677 sp_say(_i " allowed:" _lines[_i])
681 (sp_state == "bid" || sp_state == "play") &&
683 if (sp_from in sp_players)
684 reply("It is not your turn.")
686 reply("You are not playing.")
691 /^\.bid (0|[1-9][0-9]*)$/ {
692 if ($2 < 0 || $2 > 13) {
693 reply("You can only bid from 0 to 13")
697 if ($2 == 0 && !sp_looked[i]) {
698 sp_say(FROM " goes blind nil!")
700 } else if ($2 == 0) {
701 sp_say(FROM " goes nil!")
706 if (sp_turn != sp_dealer) {
707 sp_say(sp_player ": it is your bid! (" sp_bidders() ")")
709 for (p in sp_players)
710 say(p, "You have: " sp_hand(p, p))
712 for (i=0; i<2; i++) {
714 sp_say(sp_team(i) ": select a card to pass " \
715 "(/msg " NICK " .pass <card>)")
719 if (sp_state == "play")
720 sp_say(sp_player ": you have the opening lead!")
725 sp_state == "pass" &&
728 _team = sp_from in sp_players ? sp_players[sp_from] % 2 : 0
730 # check validity and pass
731 if (!(sp_from in sp_players)) {
732 reply("You are not playing.")
734 else if (!sp_passer(_team)) {
735 reply("Your team did not go blind")
737 else if (sp_pass[sp_players[sp_from]]) {
738 reply("You have already passed a card")
740 else if (!(_card in sp_deck)) {
741 reply("Invalid card")
743 else if (!(_card in sp_hands[sp_from])) {
744 reply("You do not have that card")
747 sp_pass[sp_players[sp_from]] = $2
748 sp_say(FROM " passes a card")
751 # check for end of passing
752 if ((!sp_passer(0) || (sp_pass[0] && sp_pass[2])) &&
753 (!sp_passer(1) || (sp_pass[1] && sp_pass[3]))) {
757 delete sp_hands[sp_order[i]][_card]
758 sp_hands[sp_order[_partner]][_card] = 1
760 sp_say("Cards have been passed!")
761 sp_say(sp_player ": you have the opening lead!")
762 for (p in sp_players)
763 say(p, "You have: " sp_hand(p, p))
768 sp_state ~ "(bid|pass|play)" &&
770 if (!(sp_from in sp_players)) {
771 reply("You are not playing.")
773 sp_looked[sp_players[sp_from]] = 1
774 say(FROM, "You have: " sp_hand(FROM, sp_from))
779 sp_state == "play" &&
782 gsub(/[^A-Za-z0-9]/, "", _card);
783 if (!(_card in sp_deck)) {
784 reply("Invalid card")
786 else if (sp_suit && _card !~ sp_suit && sp_hasa(sp_from, sp_suit)) {
787 reply("You must follow suit (" sp_suit ")")
789 else if (_card ~ /s/ && length(sp_hands[sp_from]) == 13 && sp_hasa(sp_from, "[^s]$")) {
790 reply("You cannot trump on the first hand")
792 else if (_card ~ /s/ && length(sp_pile) == 0 && sp_hasa(sp_from, "[^s]$") && !sp_broken) {
793 reply("Spades have not been broken")
795 else if (!(_card in sp_hands[sp_from])) {
796 reply("You do not have that card")
800 if (sp_state == "play") {
801 if (length(sp_hands[sp_from]))
802 say(FROM, "You have: " sp_hand(FROM, sp_from))
804 sp_say(sp_player ": it is your turn! " \
805 "(" sp_pretty(sp_piles, sp_player) ")")
807 sp_say(sp_player ": it is your turn!")
812 /^\.last/ && sp_state == "play" {
813 if (!isarray(sp_last))
814 say("No tricks have been taken!");
816 say(sp_last["player"] " took " \
817 sp_pretty(sp_last["pile"], FROM));
820 /^\.bids/ && sp_state == "bid" ||
821 /^\.turn/ && sp_state ~ "(bid|pass|play)" {
823 _pile = sp_pretty(sp_piles, FROM)
827 if (/!/ && sp_share[_i] == sp_player)
828 _extra = _extra " " _i "!"
830 if (sp_state == "bid" && !_bids)
831 say("It is " sp_player "'s bid!" _extra)
832 if (sp_state == "bid" && _bids)
833 say("It is " sp_player "'s bid!" _extra " (" _bids ")")
834 if (sp_state == "play" && !_pile)
835 say("It is " sp_player "'s turn!" _extra)
836 if (sp_state == "play" && _pile)
837 say("It is " sp_player "'s turn!" _extra " (" _pile ")")
839 for (_i=0; sp_state == "pass" && _i<4; _i++)
840 if (sp_passer(_i) && !sp_pass[_i])
841 say("Waiting for " sp_order[_i] " to pass a card!")
843 if (/!!/ && (sp_state == "bid" || sp_state == "play")) {
844 if (sp_player in sp_notify) {
845 _bids = _bids ? _bids : "none"
846 _pile = _pile ? sp_piles : "none"
847 mail_send(sp_notify[sp_player], \
848 "It is your " sp_state "!", \
849 "Bids so far: " _bids "\n" \
850 "Cards played: " _pile)
851 say("Notified " sp_player " at " sp_notify[sp_player])
853 say("No email address for " sp_player)
858 /^\.bids$/ && sp_state ~ "(pass|play)" {
859 say(sp_order[0] " bid " sp_bid(0) ", " \
860 sp_order[2] " bid " sp_bid(2) ", " \
861 "total: " sp_bids[0] + sp_bids[2])
862 say(sp_order[1] " bid " sp_bid(1) ", " \
863 sp_order[3] " bid " sp_bid(3) ", " \
864 "total: " sp_bids[1] + sp_bids[3])
867 /^\.tricks$/ && sp_state == "play" {
868 say(sp_order[0] " took " int(sp_tricks[0]) "/" sp_bid(0) ", " \
869 sp_order[2] " took " int(sp_tricks[2]) "/" sp_bid(2))
870 say(sp_order[1] " took " int(sp_tricks[1]) "/" sp_bid(1) ", " \
871 sp_order[3] " took " int(sp_tricks[3]) "/" sp_bid(3))
874 (TO == NICK || DST == sp_channel) &&
875 /^\.(score|status)$/ {
876 if (sp_state == "new") {
877 say("There is no game in progress")
879 if (sp_state ~ "join|bid|pass|play") {
881 sp_playto " points, " \
884 if (sp_state == "join") {
885 say("Waiting for players: " \
886 sp_order[0] " " sp_order[1] " " \
887 sp_order[2] " " sp_order[3])
889 if (sp_state ~ "bid|pass|play") {
890 say(sp_team(0) ": " \
891 int(sp_scores[0]) " points, " \
892 int(sp_bags(0)) " bags")
893 say(sp_team(1) ": " \
894 int(sp_scores[1]) " points, " \
895 int(sp_bags(1)) " bags")
899 (DST == sp_channel) &&
901 say("http://pileus.org/andy/spades/" sp_log)
904 (DST == sp_channel) &&
906 sp_stats("logs/" sp_log);
909 /^\.((new|end|load)game|join|look|bid|pass|play|notify)/ {
910 sp_save("var/sp_cur.json");