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 sp_last = "" # 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 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_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 = sp_pile[winner] " took " sp_piles
351 sp_next(sp_pile[winner])
356 if (sp_tricks[0] + sp_tricks[1] + \
357 sp_tricks[2] + sp_tricks[3] == 13) {
358 sp_say("Round over!")
360 if (sp_scores[0] >= sp_playto || sp_scores[1] >= sp_playto &&
361 sp_scores[0] != sp_scores[1]) {
363 winner = sp_scores[0] > sp_scores[1] ? 0 : 1
365 say(CHANNEL, sp_team(winner) " wins the game " \
366 sp_scores[winner] " to " sp_scores[looser])
367 say(CHANNEL, sp_order[winner+0] "++")
368 say(CHANNEL, sp_order[winner+2] "++")
372 if (sp_scores[0] == sp_scores[1] &&
373 sp_scores[0] >= sp_playto)
374 sp_say("It's tie! Playing an extra round!");
383 cmd = "od -An -N4 -td4 /dev/random"
389 sp_load("var/sp_cur.json");
391 # sp_say("Game restored.")
395 sp_from = AUTH in sp_auths ? sp_auths[AUTH] : \
396 AUTH in sp_share ? sp_share[AUTH] : FROM
397 sp_valid = sp_from && sp_from == sp_player
403 say("Spades! " sp_pretty("As,Ah,Ad,Ac", FROM))
408 sp_save("var/sp_save.json");
414 sp_load("var/sp_save.json");
420 say(".help spades -- play a game of spades")
423 /^\.help [Ss]pades$/ {
424 say("Spades -- play a game of spades")
425 say(".help game -- setup and administer the game")
426 say(".help play -- commands for playing spades")
427 say(".help auth -- control player authorization")
432 say(".newgame [score] -- start a game to <score> points, default 500")
433 say(".endgame -- abort the current game")
434 say(".savegame -- save the current game to disk")
435 say(".loadgame -- load the previously saved game")
440 say(".join -- join the current game")
441 say(".look -- look at your cards")
442 say(".bid [n] -- bid for <n> tricks")
443 say(".pass [card] -- pass a card to your partner")
444 say(".play [card] -- play a card")
445 say(".last -- show who took the previous trick")
446 say(".turn -- check whose turn it is")
447 say(".bids -- check what everyone bid")
448 say(".tricks -- check how many trick have been taken")
449 say(".score -- check the score")
454 say(".auth [who] -- display authentication info for a user")
455 say(".allow [who] -- allow another person to play on your behalf")
456 say(".deny [who] -- prevent a previously allowed user from playing")
457 say(".show -- display which users can play for which players")
458 say(".notify [addr] -- email user when it is their turn")
464 /^\.deal (\w+) (.*)/ {
465 sp_say(FROM " is cheating for " $2)
467 for (i=3; i<=NF; i++)
473 /^\.order (\w+) ([0-4])/ {
474 sp_say(FROM " is cheating for " $2)
477 sp_player = sp_order[sp_turn]
481 sp_state == "play" &&
482 /^\.force (\w+) (\S+)$/ {
483 sp_say(FROM " is cheating for " $2)
491 match($0, /^\.newgame ?([1-9][0-9]*) *- *([1-9][0-9]*)$/, _arr) {
492 if (_arr[2] > _arr[1])
493 $0 = $1 " " int(rand() * (_arr[2]-_arr[1])+_arr[1])
496 /^\.newgame ?([1-9][0-9]*)?$/ {
497 if (sp_state != "new") {
498 reply("There is already a game in progress.")
502 sp_playto = $2 ? $2 : 200
503 sp_limit = sp_playto > 200 ? 10 : 5;
506 sp_log = strftime("%Y%m%d_%H%M%S.log")
507 sp_say(sp_owner " starts a game of Spades to " sp_playto " with " sp_limit " bags!")
511 (sp_from == sp_owner || AUTH == OWNER) &&
513 if (sp_state == "new") {
514 reply("There is no game in progress.")
516 sp_say(FROM " ends the game")
522 if (sp_state == "new") {
523 reply("There is no game in progress")
525 else if (sp_state == "play") {
526 reply("The game has already started")
528 else if (sp_state == "join" && sp_from in sp_players) {
529 reply("You are already playing")
531 else if (sp_state == "join") {
535 sp_auths[AUTH] = FROM
537 sp_say(FROM " joins the game!")
539 if (sp_state == "join" && sp_turn == 0) {
546 _who = $2 in USERS ? USERS[$2]["auth"] : ""
547 _str = _who && _who != $2 ? $2 " (" _who ")" : $2
548 if (sp_state ~ "new|join") {
549 reply("The game has not yet started")
551 else if (!(sp_from in sp_players)) {
552 reply("You are not playing")
555 reply(_str " is not logged in")
557 else if (_who in sp_players || _who in sp_auths) {
558 reply(_str " is a primary player")
560 else if (_who in sp_share) {
561 reply(_str " is already playing for " sp_share[_who])
564 sp_say(_str " can now play for " sp_from)
565 sp_share[_who] = sp_from
570 _who = $2 in USERS ? USERS[$2]["auth"] : $2
571 _str = _who && _who != $2 ? $2 " (" _who ")" : $2
572 if (sp_state ~ "new|join") {
573 reply("The game has not yet started")
575 else if (!(sp_from in sp_players)) {
576 reply("You are not playing")
578 else if (_who in sp_players || _who in sp_auths) {
579 reply(_str " is a primary player")
581 else if (!(_who in sp_share) || sp_share[_who] != sp_from) {
582 reply(_str " is not playing for " sp_from)
585 sp_say(_str " can no longer play for " sp_from)
586 delete sp_share[_who]
591 if (sp_from in sp_notify)
592 reply("Your address is " sp_notify[sp_from])
594 reply("Your address is not set")
598 if (sp_from in sp_notify) {
599 reply("Removing address " sp_notify[sp_from])
600 delete sp_notify[sp_from]
602 reply("Your address is not set")
606 /^\.notify \S+@\S+.\S+$/ {
608 gsub(/[^a-zA-Z0-9_+@.-]/, "", _addr)
609 sp_notify[sp_from] = _addr
610 reply("Notifying you at " _addr)
613 sp_state ~ "(bid|pass|play)" &&
617 _lines[sp_share[_i]] = _lines[sp_share[_i]] " " _i
619 sp_say(_i " allowed:" _lines[_i])
623 (sp_state == "bid" || sp_state == "play") &&
625 if (sp_from in sp_players)
626 reply("It is not your turn.")
628 reply("You are not playing.")
633 /^\.bid (0|[1-9][0-9]*)$/ {
634 if ($2 < 0 || $2 > 13) {
635 reply("You can only bid from 0 to 13")
639 if ($2 == 0 && !sp_looked[i]) {
640 sp_say(FROM " goes blind nil!")
642 } else if ($2 == 0) {
643 sp_say(FROM " goes nil!")
648 if (sp_turn != sp_dealer) {
649 sp_say(sp_player ": it is your bid! (" sp_bidders() ")")
651 for (p in sp_players)
652 say(p, "You have: " sp_hand(p, p))
654 for (i=0; i<2; i++) {
656 sp_say(sp_team(i) ": select a card to pass " \
657 "(/msg " NICK " .pass <card>)")
661 if (sp_state == "play")
662 sp_say(sp_player ": you have the opening lead!")
667 sp_state == "pass" &&
670 _team = sp_from in sp_players ? sp_players[sp_from] % 2 : 0
672 # check validity and pass
673 if (!(sp_from in sp_players)) {
674 reply("You are not playing.")
676 else if (!sp_passer(_team)) {
677 reply("Your team did not go blind")
679 else if (sp_pass[sp_players[sp_from]]) {
680 reply("You have already passed a card")
682 else if (!(_card in sp_deck)) {
683 reply("Invalid card")
685 else if (!(_card in sp_hands[sp_from])) {
686 reply("You do not have that card")
689 sp_pass[sp_players[sp_from]] = $2
690 sp_say(FROM " passes a card")
693 # check for end of passing
694 if ((!sp_passer(0) || (sp_pass[0] && sp_pass[2])) &&
695 (!sp_passer(1) || (sp_pass[1] && sp_pass[3]))) {
699 delete sp_hands[sp_order[i]][_card]
700 sp_hands[sp_order[_partner]][_card] = 1
702 sp_say("Cards have been passed!")
703 sp_say(sp_player ": you have the opening lead!")
704 for (p in sp_players)
705 say(p, "You have: " sp_hand(p, p))
710 sp_state ~ "(bid|pass|play)" &&
712 if (!(sp_from in sp_players)) {
713 reply("You are not playing.")
715 sp_looked[sp_players[sp_from]] = 1
716 say(FROM, "You have: " sp_hand(FROM, sp_from))
721 sp_state == "play" &&
724 gsub(/[^A-Za-z0-9]/, "", _card);
725 if (!(_card in sp_deck)) {
726 reply("Invalid card")
728 else if (sp_suit && _card !~ sp_suit && sp_hasa(sp_from, sp_suit)) {
729 reply("You must follow suit (" sp_suit ")")
731 else if (_card ~ /s/ && length(sp_hands[sp_from]) == 13 && sp_hasa(sp_from, "[^s]$")) {
732 reply("You cannot trump on the first hand")
734 else if (_card ~ /s/ && length(sp_pile) == 0 && sp_hasa(sp_from, "[^s]$") && !sp_broken) {
735 reply("Spades have not been broken")
737 else if (!(_card in sp_hands[sp_from])) {
738 reply("You do not have that card")
742 if (sp_state == "play") {
743 if (length(sp_hands[sp_from]))
744 say(FROM, "You have: " sp_hand(FROM, sp_from))
746 sp_say(sp_player ": it is your turn! " \
747 "(" sp_pretty(sp_piles, sp_player) ")")
749 sp_say(sp_player ": it is your turn!")
754 /^\.last/ && sp_state == "play" {
756 say("No tricks have been taken!");
758 say(sp_pretty(sp_last, FROM));
761 /^\.bids/ && sp_state == "bid" ||
762 /^\.turn/ && sp_state ~ "(bid|pass|play)" {
764 _pile = sp_pretty(sp_piles, FROM)
768 if (/!/ && sp_share[_i] == sp_player)
769 _extra = _extra " " _i "!"
771 if (sp_state == "bid" && !_bids)
772 say("It is " sp_player "'s bid!" _extra)
773 if (sp_state == "bid" && _bids)
774 say("It is " sp_player "'s bid!" _extra " (" _bids ")")
775 if (sp_state == "play" && !_pile)
776 say("It is " sp_player "'s turn!" _extra)
777 if (sp_state == "play" && _pile)
778 say("It is " sp_player "'s turn!" _extra " (" _pile ")")
780 for (_i=0; sp_state == "pass" && _i<4; _i++)
781 if (sp_passer(_i) && !sp_pass[_i])
782 say("Waiting for " sp_order[_i] " to pass a card!")
784 if (/!!/ && (sp_state == "bid" || sp_state == "play")) {
785 if (sp_player in sp_notify) {
786 _bids = _bids ? _bids : "none"
787 _pile = _pile ? sp_piles : "none"
788 mail_send(sp_notify[sp_player], \
789 "It is your " sp_state "!", \
790 "Bids so far: " _bids "\n" \
791 "Cards played: " _pile)
792 say("Notified " sp_player " at " sp_notify[sp_player])
794 say("No email address for " sp_player)
799 /^\.bids$/ && sp_state ~ "(pass|play)" {
800 say(sp_order[0] " bid " sp_bid(0) ", " \
801 sp_order[2] " bid " sp_bid(2) ", " \
802 "total: " sp_bids[0] + sp_bids[2])
803 say(sp_order[1] " bid " sp_bid(1) ", " \
804 sp_order[3] " bid " sp_bid(3) ", " \
805 "total: " sp_bids[1] + sp_bids[3])
808 /^\.tricks$/ && sp_state == "play" {
809 say(sp_order[0] " took " int(sp_tricks[0]) "/" sp_bid(0) ", " \
810 sp_order[2] " took " int(sp_tricks[2]) "/" sp_bid(2))
811 say(sp_order[1] " took " int(sp_tricks[1]) "/" sp_bid(1) ", " \
812 sp_order[3] " took " int(sp_tricks[3]) "/" sp_bid(3))
815 (TO == NICK || DST == sp_channel) &&
816 /^\.(score|status)$/ {
817 if (sp_state == "new") {
818 say("There is no game in progress")
820 if (sp_state ~ "join|bid|pass|play") {
822 sp_playto " points, " \
825 if (sp_state == "join") {
826 say("Waiting for players: " \
827 sp_order[0] " " sp_order[1] " " \
828 sp_order[2] " " sp_order[3])
830 if (sp_state ~ "bid|pass|play") {
831 say(sp_team(0) ": " \
832 int(sp_scores[0]) " points, " \
833 int(sp_bags(0)) " bags")
834 say(sp_team(1) ": " \
835 int(sp_scores[1]) " points, " \
836 int(sp_bags(1)) " bags")
840 (DST == sp_channel) &&
842 say("http://pileus.org/andy/spades/" sp_log)
845 /^\.((new|end|load)game|join|look|bid|pass|play|notify)/ {
846 sp_save("var/sp_cur.json");