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 delete sp_notify # [p] E-mail notification address
68 function sp_acopy(dst, src, key)
73 json_copy(dst, key, src[key])
77 function sp_save(file, game)
80 game["suit"] = sp_suit;
81 game["piles"] = sp_piles;
82 json_copy(game, "pile", sp_pile);
85 game["state"] = sp_state;
86 game["broken"] = sp_broken;
87 json_copy(game, "last", sp_last);
88 json_copy(game, "looked", sp_looked);
89 json_copy(game, "bids", sp_bids);
90 json_copy(game, "nil", sp_nil);
91 json_copy(game, "pass", sp_pass);
92 json_copy(game, "tricks", sp_tricks);
95 game["channel"] = sp_channel;
96 game["owner"] = sp_owner;
97 game["playto"] = sp_playto;
98 game["dealer"] = sp_dealer;
99 game["turn"] = sp_turn;
100 game["player"] = sp_player;
101 game["limit"] = sp_limit;
102 json_copy(game, "hands", sp_hands);
103 json_copy(game, "players", sp_players);
104 json_copy(game, "auths", sp_auths);
105 json_copy(game, "share", sp_share);
106 json_copy(game, "order", sp_order);
107 json_copy(game, "scores", sp_scores);
108 json_copy(game, "notify", sp_notify);
111 json_save(file, game);
114 function sp_load(file, game)
117 if (!json_load(file, game))
121 sp_suit = game["suit"];
122 sp_piles = game["piles"];
123 sp_acopy(sp_pile, game["pile"]);
126 sp_state = game["state"];
127 sp_broken = game["broken"];
128 sp_acopy(sp_last, game["last"]);
129 sp_acopy(sp_looked, game["looked"]);
130 sp_acopy(sp_bids, game["bids"]);
131 sp_acopy(sp_nil, game["nil"]);
132 sp_acopy(sp_pass, game["pass"]);
133 sp_acopy(sp_tricks, game["tricks"]);
136 sp_channel = game["channel"];
137 sp_owner = game["owner"];
138 sp_playto = game["playto"];
139 sp_dealer = game["dealer"];
140 sp_turn = game["turn"];
141 sp_player = game["player"];
142 sp_limit = game["limit"];
143 sp_acopy(sp_hands, game["hands"]);
144 sp_acopy(sp_players, game["players"]);
145 sp_acopy(sp_auths, game["auths"]);
146 sp_acopy(sp_share, game["share"]);
147 sp_acopy(sp_order, game["order"]);
148 sp_acopy(sp_scores, game["scores"]);
149 sp_acopy(sp_notify, game["notify"]);
152 function sp_pretty(cards, who)
155 gsub(/[0-9JQKA]*[sc]/, "\0031,00\002&\017", cards) # black
156 gsub(/[0-9JQKA]*[hd]/, "\0034,00\002&\017", cards) # red
158 if (!nounicode[who]) {
159 gsub(/s/, "\002♠", cards)
160 gsub(/h/, "\002♥", cards)
161 gsub(/d/, "\002♦", cards)
162 gsub(/c/, "\002♣", cards)
167 function sp_next(who, prev)
170 sp_turn = who ? sp_players[who] : (sp_turn + 1) % 4
171 if (length(sp_order) == 4)
172 sp_player = sp_order[sp_turn]
176 function sp_shuf(i, mixed)
178 sp_usort(sp_players, mixed)
180 sp_order[i-1] = mixed[i]
181 sp_players[mixed[i]] = i-1
185 function sp_deal( shuf)
187 say("/me deals the cards")
188 sp_usort(sp_deck, shuf)
189 for (i=1; i<=52; i++)
190 sp_hands[sp_order[i%4]][shuf[i]] = 1
192 sp_dealer = (sp_dealer+1)%4
194 sp_player = sp_order[sp_turn]
195 say(sp_player ": you bid first!")
198 function sp_hand(to, who, sort, str)
200 asorti(sp_hands[who], sort, "sp_csort")
201 for (i=0; i<length(sort); i++)
202 str = str "" sprintf("%4s", sort[i])
203 gsub(/^ +| +$/, "", str)
204 return sp_pretty(str, to)
207 function sp_hasa(who, expr)
209 for (c in sp_hands[who]) {
216 function sp_type(card)
218 return substr(card, length(card))
221 function sp_usort(list, out) {
224 asorti(out, out, "@val_num_asc")
227 function sp_csort(i1,v1,i2,v2) {
228 return sp_deck[i1] > sp_deck[i2] ? +1 :
229 sp_deck[i1] < sp_deck[i2] ? -1 : 0;
232 function sp_winner( card, tmp)
234 for (card in sp_pile)
235 if (card !~ sp_suit && card !~ /s/)
237 asorti(sp_pile, tmp, "sp_csort")
238 #print "pile: " tmp[1] ">" tmp[2] ">" tmp[3] ">" tmp[4]
244 #return "{" sp_order[i+0] "," sp_order[i+2] "}"
245 return sp_order[i+0] "/" sp_order[i+2]
248 function sp_bags(i, bags)
250 bags = sp_scores[i] % sp_limit
258 return sp_nil[who] == 0 ? sp_bids[who] :
259 sp_nil[who] == 1 ? "nil" :
260 sp_nil[who] == 2 ? "blind" : "n/a"
263 function sp_passer(who)
265 return sp_nil[(who+0)%4] == 2 || sp_nil[(who+1)%4] != 0 ||
266 sp_nil[(who+2)%4] == 2 || sp_nil[(who+3)%4] != 0
269 function sp_bidders( i, turn, bid, bids)
271 for (i = 0; i < 4; i++) {
272 turn = (sp_dealer + i) % 4
273 if (bid = sp_bid(turn))
274 bids = bids " " sp_order[turn] ":" bid
276 gsub(/^ +| +$/, "", bids)
280 function sp_score( bids, times, tricks)
282 for (i=0; i<2; i++) {
283 bids = sp_bids[i] + sp_bids[i+2]
284 tricks = sp_tricks[i] + sp_tricks[i+2]
286 times = int((sp_bags(i) + bags) / sp_limit)
288 say(sp_team(i) " bag" (times>1?" way ":" ") "out")
289 sp_scores[i] -= sp_limit * 10 * times;
291 if (tricks >= bids) {
292 say(sp_team(i) " make their bid: " tricks "/" bids)
293 sp_scores[i] += bids*10 + bags;
295 say(sp_team(i) " go bust: " tricks "/" bids)
296 sp_scores[i] -= bids*10;
299 for (i=0; i<4; i++) {
302 say(sp_order[i] " " \
303 (sp_nil[i] == 1 && !sp_tricks[i] ? "makes nil!" :
304 sp_nil[i] == 1 && sp_tricks[i] ? "fails at nil!" :
305 sp_nil[i] == 2 && !sp_tricks[i] ? "makes blind nil!" :
306 sp_nil[i] == 2 && sp_tricks[i] ? "fails miserably at blind nil!" :
308 sp_scores[i%2] += sp_limit * 10 * sp_nil[i] * \
309 (sp_tricks[i] == 0 ? 1 : -1)
311 if (sp_scores[0] > sp_scores[1])
312 say(sp_team(0) " lead " sp_scores[0] " to " sp_scores[1] " of " sp_playto)
313 else if (sp_scores[1] > sp_scores[0])
314 say(sp_team(1) " lead " sp_scores[1] " to " sp_scores[0] " of " sp_playto)
316 say("tied at " sp_scores[0])
319 function sp_play(card, winner, pi)
321 delete sp_hands[sp_from][card]
322 sp_pile[card] = sp_player
323 sp_piles = sp_piles (sp_piles?",":"") card
330 if (length(sp_pile) == 1)
331 sp_suit = sp_type(card)
334 if (length(sp_pile) == 4) {
336 pi = sp_players[sp_pile[winner]]
338 say(sp_pile[winner] " wins with " sp_pretty(winner, FROM) \
339 " (" sp_pretty(sp_piles, FROM) ")")
340 sp_last["player"] = sp_pile[winner];
341 sp_last["pile"] = sp_piles;
342 sp_next(sp_pile[winner])
347 if (sp_tricks[0] + sp_tricks[1] + \
348 sp_tricks[2] + sp_tricks[3] == 13) {
351 if (sp_scores[0] >= sp_playto || sp_scores[1] >= sp_playto &&
352 sp_scores[0] != sp_scores[1]) {
354 winner = sp_scores[0] > sp_scores[1] ? 0 : 1
356 say(CHANNEL, sp_team(winner) " wins the game " \
357 sp_scores[winner] " to " sp_scores[looser])
358 say(CHANNEL, sp_order[winner+0] "++")
359 say(CHANNEL, sp_order[winner+2] "++")
363 if (sp_scores[0] == sp_scores[1] &&
364 sp_scores[0] >= sp_playto)
365 say("It's tie! Playing an extra round!");
374 cmd = "od -An -N4 -td4 /dev/random"
380 sp_load("var/sp_cur.json");
382 # say(sp_channel, "Game restored.")
386 sp_from = AUTH in sp_auths ? sp_auths[AUTH] : \
387 AUTH in sp_share ? sp_share[AUTH] : FROM
388 sp_valid = sp_from && sp_from == sp_player
394 say("Spades! " sp_pretty("As,Ah,Ad,Ac", FROM))
399 sp_save("var/sp_save.json");
405 sp_load("var/sp_save.json");
411 say(".help spades -- play a game of spades")
414 /^\.help [Ss]pades$/ {
415 say("Spades -- play a game of spades")
416 say(".help game -- setup and administer the game")
417 say(".help play -- commands for playing spades")
418 say(".help auth -- control player authorization")
423 say(".newgame [score] -- start a game to <score> points, default 500")
424 say(".endgame -- abort the current game")
425 say(".savegame -- save the current game to disk")
426 say(".loadgame -- load the previously saved game")
431 say(".join -- join the current game")
432 say(".look -- look at your cards")
433 say(".bid [n] -- bid for <n> tricks")
434 say(".pass [card] -- pass a card to your partner")
435 say(".play [card] -- play a card")
436 say(".last -- show who took the previous trick")
437 say(".turn -- check whose turn it is")
438 say(".bids -- check what everyone bid")
439 say(".tricks -- check how many trick have been taken")
440 say(".score -- check the score")
445 say(".auth [who] -- display authentication info for a user")
446 say(".allow [who] -- allow another person to play on your behalf")
447 say(".deny [who] -- prevent a previously allowed user from playing")
448 say(".show -- display which users can play for which players")
449 say(".notify [addr] -- email user when it is their turn")
455 /^\.deal (\w+) (.*)/ {
456 say(sp_channel, FROM " is cheating for " $2)
458 for (i=3; i<=NF; i++)
464 /^\.order (\w+) ([0-4])/ {
465 say(sp_channel, FROM " is cheating for " $2)
468 sp_player = sp_order[sp_turn]
472 sp_state == "play" &&
473 /^\.force (\w+) (\S+)$/ {
474 say(sp_channel, FROM " is cheating for " $2)
482 match($0, /^\.newgame ?([1-9][0-9]*) *- *([1-9][0-9]*)$/, _arr) {
483 if (_arr[2] > _arr[1])
484 $0 = $1 " " int(rand() * (_arr[2]-_arr[1])+_arr[1])
487 /^\.newgame ?([1-9][0-9]*)?$/ {
488 if (sp_state != "new") {
489 reply("There is already a game in progress.")
493 sp_playto = $2 ? $2 : 200
494 sp_limit = sp_playto > 200 ? 10 : 5;
497 say(sp_owner " starts a game of Spades to " sp_playto " with " sp_limit " bags!")
501 (sp_from == sp_owner || AUTH == OWNER) &&
503 if (sp_state == "new") {
504 reply("There is no game in progress.")
506 say(FROM " ends the game")
512 if (sp_state == "new") {
513 reply("There is no game in progress")
515 else if (sp_state == "play") {
516 reply("The game has already started")
518 else if (sp_state == "join" && sp_from in sp_players) {
519 reply("You are already playing")
521 else if (sp_state == "join") {
525 sp_auths[AUTH] = FROM
527 say(FROM " joins the game!")
529 if (sp_state == "join" && sp_turn == 0) {
536 _who = $2 in USERS ? USERS[$2]["auth"] : ""
537 _str = _who && _who != $2 ? $2 " (" _who ")" : $2
538 if (sp_state ~ "new|join") {
539 reply("The game has not yet started")
541 else if (!(sp_from in sp_players)) {
542 reply("You are not playing")
545 reply(_str " is not logged in")
547 else if (_who in sp_players || _who in sp_auths) {
548 reply(_str " is a primary player")
550 else if (_who in sp_share) {
551 reply(_str " is already playing for " sp_share[_who])
554 reply(_str " can now play for " sp_from)
555 sp_share[_who] = sp_from
560 _who = $2 in USERS ? USERS[$2]["auth"] : $2
561 _str = _who && _who != $2 ? $2 " (" _who ")" : $2
562 if (sp_state ~ "new|join") {
563 reply("The game has not yet started")
565 else if (!(sp_from in sp_players)) {
566 reply("You are not playing")
568 else if (_who in sp_players || _who in sp_auths) {
569 reply(_str " is a primary player")
571 else if (!(_who in sp_share) || sp_share[_who] != sp_from) {
572 reply(_str " is not playing for " sp_from)
575 reply(_str " can no longer play for " sp_from)
576 delete sp_share[_who]
581 if (sp_from in sp_notify)
582 reply("Your address is " sp_notify[sp_from])
584 reply("Your address is not set")
588 if (sp_from in sp_notify) {
589 reply("Removing address " sp_notify[sp_from])
590 delete sp_notify[sp_from]
592 reply("Your address is not set")
596 /^\.notify \S+@\S+.\S+$/ {
598 gsub(/[^a-zA-Z0-9_+@.-]/, "", _addr)
599 sp_notify[sp_from] = _addr
600 reply("Notifying you at " _addr)
603 sp_state ~ "(bid|pass|play)" &&
607 _lines[sp_share[_i]] = _lines[sp_share[_i]] " " _i
609 say(_i " allowed:" _lines[_i])
613 (sp_state == "bid" || sp_state == "play") &&
615 if (sp_from in sp_players)
616 say(".slap " FROM ", it is not your turn.")
618 say(".slap " FROM ", you are not playing.")
623 /^\.bid (0|[1-9][0-9]*)$/ {
624 if ($2 < 0 || $2 > 13) {
625 reply("You can only bid from 0 to 13")
629 if ($2 == 0 && !sp_looked[i]) {
630 say(FROM " goes blind nil!")
632 } else if ($2 == 0) {
633 say(FROM " goes nil!")
638 if (sp_turn != sp_dealer) {
639 say(sp_player ": it is your bid! (" sp_bidders() ")")
641 for (p in sp_players)
642 say(p, "You have: " sp_hand(p, p))
644 for (i=0; i<2; i++) {
646 say(sp_team(i) ": select a card to pass " \
647 "(/msg " NICK " .pass <card>)")
651 if (sp_state == "play")
652 say(sp_player ": you have the opening lead!")
657 sp_state == "pass" &&
660 _team = sp_from in sp_players ? sp_players[sp_from] % 2 : 0
662 # check validity and pass
663 if (!(sp_from in sp_players)) {
664 say(".slap " FROM ", you are not playing.")
666 else if (!sp_passer(_team)) {
667 reply("Your team did not go blind")
669 else if (sp_pass[sp_players[sp_from]]) {
670 reply("You have already passed a card")
672 else if (!(_card in sp_deck)) {
673 reply("Invalid card")
675 else if (!(_card in sp_hands[sp_from])) {
676 reply("You do not have that card")
679 sp_pass[sp_players[sp_from]] = $2
680 say(sp_channel, FROM " passes a card")
683 # check for end of passing
684 if ((!sp_passer(0) || (sp_pass[0] && sp_pass[2])) &&
685 (!sp_passer(1) || (sp_pass[1] && sp_pass[3]))) {
689 delete sp_hands[sp_order[i]][_card]
690 sp_hands[sp_order[_partner]][_card] = 1
692 say(sp_channel, "Cards have been passed!")
693 say(sp_channel, sp_player ": you have the opening lead!")
694 for (p in sp_players)
695 say(p, "You have: " sp_hand(p, p))
700 sp_state ~ "(bid|pass|play)" &&
702 if (!(sp_from in sp_players)) {
703 say(".slap " FROM ", you are not playing.")
705 sp_looked[sp_players[sp_from]] = 1
706 say(FROM, "You have: " sp_hand(FROM, sp_from))
711 sp_state == "play" &&
714 gsub(/[^A-Za-z0-9]/, "", _card);
715 if (!(_card in sp_deck)) {
716 reply("Invalid card")
718 else if (sp_suit && _card !~ sp_suit && sp_hasa(sp_from, sp_suit)) {
719 reply("You must follow suit (" sp_suit ")")
721 else if (_card ~ /s/ && length(sp_hands[sp_from]) == 13 && sp_hasa(sp_from, "[^s]$")) {
722 reply("You cannot trump on the first hand")
724 else if (_card ~ /s/ && length(sp_pile) == 0 && sp_hasa(sp_from, "[^s]$") && !sp_broken) {
725 reply("Spades have not been broken")
727 else if (!(_card in sp_hands[sp_from])) {
728 reply("You do not have that card")
732 if (sp_state == "play") {
733 if (length(sp_hands[sp_from]))
734 say(FROM, "You have: " sp_hand(FROM, sp_from))
736 say(sp_player ": it is your turn! " \
737 "(" sp_pretty(sp_piles, sp_player) ")")
739 say(sp_player ": it is your turn!")
744 /^\.last/ && sp_state == "play" {
745 if (!isarray(sp_last))
746 say("No tricks have been taken!");
748 say(sp_last["player"] " took " \
749 sp_pretty(sp_last["hand"], FROM));
752 /^\.bids/ && sp_state == "bid" ||
753 /^\.turn/ && sp_state ~ "(bid|pass|play)" {
755 _pile = sp_pretty(sp_piles, FROM)
759 if (/!/ && sp_share[_i] == sp_player)
760 _extra = _extra " " _i "!"
762 if (sp_state == "bid" && !_bids)
763 say("It is " sp_player "'s bid!" _extra)
764 if (sp_state == "bid" && _bids)
765 say("It is " sp_player "'s bid!" _extra " (" _bids ")")
766 if (sp_state == "play" && !_pile)
767 say("It is " sp_player "'s turn!" _extra)
768 if (sp_state == "play" && _pile)
769 say("It is " sp_player "'s turn!" _extra " (" _pile ")")
771 for (_i=0; sp_state == "pass" && _i<4; _i++)
772 if (sp_passer(_i) && !sp_pass[_i])
773 say("Waiting for " sp_order[_i] " to pass a card!")
775 if (/!!/ && (sp_state == "bid" || sp_state == "play")) {
776 if (sp_player in sp_notify) {
777 _bids = _bids ? _bids : "none"
778 _pile = _pile ? sp_piles : "none"
779 mail_send(sp_notify[sp_player], \
780 "It is your " sp_state "!", \
781 "Bids so far: " _bids "\n" \
782 "Cards played: " _pile)
783 say("Notified " sp_player " at " sp_notify[sp_player])
785 say("No email address for " sp_player)
790 /^\.bids$/ && sp_state ~ "(pass|play)" {
791 say(sp_order[0] " bid " sp_bid(0) ", " \
792 sp_order[2] " bid " sp_bid(2) ", " \
793 "total: " sp_bids[0] + sp_bids[2])
794 say(sp_order[1] " bid " sp_bid(1) ", " \
795 sp_order[3] " bid " sp_bid(3) ", " \
796 "total: " sp_bids[1] + sp_bids[3])
799 /^\.tricks$/ && sp_state == "play" {
800 say(sp_order[0] " took " int(sp_tricks[0]) "/" sp_bid(0) ", " \
801 sp_order[2] " took " int(sp_tricks[2]) "/" sp_bid(2))
802 say(sp_order[1] " took " int(sp_tricks[1]) "/" sp_bid(1) ", " \
803 sp_order[3] " took " int(sp_tricks[3]) "/" sp_bid(3))
806 (TO == NICK || DST == sp_channel) &&
807 /^\.(score|status)$/ {
808 if (sp_state == "new") {
809 say("There is no game in progress")
811 if (sp_state ~ "join|bid|pass|play") {
813 sp_playto " points, " \
816 if (sp_state == "join") {
817 say("Waiting for players: " \
818 sp_order[0] " " sp_order[1] " " \
819 sp_order[2] " " sp_order[3])
821 if (sp_state ~ "bid|pass|play") {
822 say(sp_team(0) ": " \
823 int(sp_scores[0]) " points, " \
824 int(sp_bags(0)) " bags")
825 say(sp_team(1) ": " \
826 int(sp_scores[1]) " points, " \
827 int(sp_bags(1)) " bags")
831 /^\.((new|end|load)game|join|look|bid|pass|play|notify)/ {
832 sp_save("var/sp_cur.json");