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 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 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_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 = sp_pile[winner] " took " sp_piles
341 sp_next(sp_pile[winner])
346 if (sp_tricks[0] + sp_tricks[1] + \
347 sp_tricks[2] + sp_tricks[3] == 13) {
350 if (sp_scores[0] >= sp_playto || sp_scores[1] >= sp_playto &&
351 sp_scores[0] != sp_scores[1]) {
353 winner = sp_scores[0] > sp_scores[1] ? 0 : 1
355 say(CHANNEL, sp_team(winner) " wins the game " \
356 sp_scores[winner] " to " sp_scores[looser])
357 say(CHANNEL, sp_order[winner+0] "++")
358 say(CHANNEL, sp_order[winner+2] "++")
362 if (sp_scores[0] == sp_scores[1] &&
363 sp_scores[0] >= sp_playto)
364 say("It's tie! Playing an extra round!");
373 cmd = "od -An -N4 -td4 /dev/random"
379 sp_load("var/sp_cur.json");
381 # say(sp_channel, "Game restored.")
385 sp_from = AUTH in sp_auths ? sp_auths[AUTH] : \
386 AUTH in sp_share ? sp_share[AUTH] : FROM
387 sp_valid = sp_from && sp_from == sp_player
393 say("Spades! " sp_pretty("As,Ah,Ad,Ac", FROM))
398 sp_save("var/sp_save.json");
404 sp_load("var/sp_save.json");
410 say(".help spades -- play a game of spades")
413 /^\.help [Ss]pades$/ {
414 say("Spades -- play a game of spades")
415 say(".help game -- setup and administer the game")
416 say(".help play -- commands for playing spades")
417 say(".help auth -- control player authorization")
422 say(".newgame [score] -- start a game to <score> points, default 500")
423 say(".endgame -- abort the current game")
424 say(".savegame -- save the current game to disk")
425 say(".loadgame -- load the previously saved game")
430 say(".join -- join the current game")
431 say(".look -- look at your cards")
432 say(".bid [n] -- bid for <n> tricks")
433 say(".pass [card] -- pass a card to your partner")
434 say(".play [card] -- play a card")
435 say(".last -- show who took the previous trick")
436 say(".turn -- check whose turn it is")
437 say(".bids -- check what everyone bid")
438 say(".tricks -- check how many trick have been taken")
439 say(".score -- check the score")
444 say(".auth [who] -- display authentication info for a user")
445 say(".allow [who] -- allow another person to play on your behalf")
446 say(".deny [who] -- prevent a previously allowed user from playing")
447 say(".show -- display which users can play for which players")
448 say(".notify [addr] -- email user when it is their turn")
454 /^\.deal (\w+) (.*)/ {
455 say(sp_channel, FROM " is cheating for " $2)
457 for (i=3; i<=NF; i++)
463 /^\.order (\w+) ([0-4])/ {
464 say(sp_channel, FROM " is cheating for " $2)
467 sp_player = sp_order[sp_turn]
471 sp_state == "play" &&
472 /^\.force (\w+) (\S+)$/ {
473 say(sp_channel, FROM " is cheating for " $2)
481 match($0, /^\.newgame ?([1-9][0-9]*) *- *([1-9][0-9]*)$/, _arr) {
482 if (_arr[2] > _arr[1])
483 $0 = $1 " " int(rand() * (_arr[2]-_arr[1])+_arr[1])
486 /^\.newgame ?([1-9][0-9]*)?$/ {
487 if (sp_state != "new") {
488 reply("There is already a game in progress.")
492 sp_playto = $2 ? $2 : 200
493 sp_limit = sp_playto > 200 ? 10 : 5;
496 say(sp_owner " starts a game of Spades to " sp_playto " with " sp_limit " bags!")
500 (sp_from == sp_owner || AUTH == OWNER) &&
502 if (sp_state == "new") {
503 reply("There is no game in progress.")
505 say(FROM " ends the game")
511 if (sp_state == "new") {
512 reply("There is no game in progress")
514 else if (sp_state == "play") {
515 reply("The game has already started")
517 else if (sp_state == "join" && sp_from in sp_players) {
518 reply("You are already playing")
520 else if (sp_state == "join") {
524 sp_auths[AUTH] = FROM
526 say(FROM " joins the game!")
528 if (sp_state == "join" && sp_turn == 0) {
535 _who = $2 in USERS ? USERS[$2]["auth"] : ""
536 _str = _who && _who != $2 ? $2 " (" _who ")" : $2
537 if (sp_state ~ "new|join") {
538 reply("The game has not yet started")
540 else if (!(sp_from in sp_players)) {
541 reply("You are not playing")
544 reply(_str " is not logged in")
546 else if (_who in sp_players || _who in sp_auths) {
547 reply(_str " is a primary player")
549 else if (_who in sp_share) {
550 reply(_str " is already playing for " sp_share[_who])
553 reply(_str " can now play for " sp_from)
554 sp_share[_who] = sp_from
559 _who = $2 in USERS ? USERS[$2]["auth"] : $2
560 _str = _who && _who != $2 ? $2 " (" _who ")" : $2
561 if (sp_state ~ "new|join") {
562 reply("The game has not yet started")
564 else if (!(sp_from in sp_players)) {
565 reply("You are not playing")
567 else if (_who in sp_players || _who in sp_auths) {
568 reply(_str " is a primary player")
570 else if (!(_who in sp_share) || sp_share[_who] != sp_from) {
571 reply(_str " is not playing for " sp_from)
574 reply(_str " can no longer play for " sp_from)
575 delete sp_share[_who]
580 if (sp_from in sp_notify)
581 reply("Your address is " sp_notify[sp_from])
583 reply("Your address is not set")
587 if (sp_from in sp_notify) {
588 reply("Removing address " sp_notify[sp_from])
589 delete sp_notify[sp_from]
591 reply("Your address is not set")
595 /^\.notify \S+@\S+.\S+$/ {
597 gsub(/[^a-zA-Z0-9_+@.-]/, "", _addr)
598 sp_notify[sp_from] = _addr
599 reply("Notifying you at " _addr)
602 sp_state ~ "(bid|pass|play)" &&
606 _lines[sp_share[_i]] = _lines[sp_share[_i]] " " _i
608 say(_i " allowed:" _lines[_i])
612 (sp_state == "bid" || sp_state == "play") &&
614 if (sp_from in sp_players)
615 say(".slap " FROM ", it is not your turn.")
617 say(".slap " FROM ", you are not playing.")
622 /^\.bid (0|[1-9][0-9]*)$/ {
623 if ($2 < 0 || $2 > 13) {
624 reply("You can only bid from 0 to 13")
628 if ($2 == 0 && !sp_looked[i]) {
629 say(FROM " goes blind nil!")
631 } else if ($2 == 0) {
632 say(FROM " goes nil!")
637 if (sp_turn != sp_dealer) {
638 say(sp_player ": it is your bid! (" sp_bidders() ")")
640 for (p in sp_players)
641 say(p, "You have: " sp_hand(p, p))
643 for (i=0; i<2; i++) {
645 say(sp_team(i) ": select a card to pass " \
646 "(/msg " NICK " .pass <card>)")
650 if (sp_state == "play")
651 say(sp_player ": you have the opening lead!")
656 sp_state == "pass" &&
659 _team = sp_from in sp_players ? sp_players[sp_from] % 2 : 0
661 # check validity and pass
662 if (!(sp_from in sp_players)) {
663 say(".slap " FROM ", you are not playing.")
665 else if (!sp_passer(_team)) {
666 reply("Your team did not go blind")
668 else if (sp_pass[sp_players[sp_from]]) {
669 reply("You have already passed a card")
671 else if (!(_card in sp_deck)) {
672 reply("Invalid card")
674 else if (!(_card in sp_hands[sp_from])) {
675 reply("You do not have that card")
678 sp_pass[sp_players[sp_from]] = $2
679 say(sp_channel, FROM " passes a card")
682 # check for end of passing
683 if ((!sp_passer(0) || (sp_pass[0] && sp_pass[2])) &&
684 (!sp_passer(1) || (sp_pass[1] && sp_pass[3]))) {
688 delete sp_hands[sp_order[i]][_card]
689 sp_hands[sp_order[_partner]][_card] = 1
691 say(sp_channel, "Cards have been passed!")
692 say(sp_channel, sp_player ": you have the opening lead!")
693 for (p in sp_players)
694 say(p, "You have: " sp_hand(p, p))
699 sp_state ~ "(bid|pass|play)" &&
701 if (!(sp_from in sp_players)) {
702 say(".slap " FROM ", you are not playing.")
704 sp_looked[sp_players[sp_from]] = 1
705 say(FROM, "You have: " sp_hand(FROM, sp_from))
710 sp_state == "play" &&
713 gsub(/[^A-Za-z0-9]/, "", _card);
714 if (!(_card in sp_deck)) {
715 reply("Invalid card")
717 else if (sp_suit && _card !~ sp_suit && sp_hasa(sp_from, sp_suit)) {
718 reply("You must follow suit (" sp_suit ")")
720 else if (_card ~ /s/ && length(sp_hands[sp_from]) == 13 && sp_hasa(sp_from, "[^s]$")) {
721 reply("You cannot trump on the first hand")
723 else if (_card ~ /s/ && length(sp_pile) == 0 && sp_hasa(sp_from, "[^s]$") && !sp_broken) {
724 reply("Spades have not been broken")
726 else if (!(_card in sp_hands[sp_from])) {
727 reply("You do not have that card")
731 if (sp_state == "play") {
732 if (length(sp_hands[sp_from]))
733 say(FROM, "You have: " sp_hand(FROM, sp_from))
735 say(sp_player ": it is your turn! " \
736 "(" sp_pretty(sp_piles, sp_player) ")")
738 say(sp_player ": it is your turn!")
743 /^\.last/ && sp_state == "play" {
745 say("No tricks have been taken!");
747 say(sp_pretty(sp_last, FROM));
750 /^\.bids/ && sp_state == "bid" ||
751 /^\.turn/ && sp_state ~ "(bid|pass|play)" {
753 _pile = sp_pretty(sp_piles, FROM)
757 if (/!/ && sp_share[_i] == sp_player)
758 _extra = _extra " " _i "!"
760 if (sp_state == "bid" && !_bids)
761 say("It is " sp_player "'s bid!" _extra)
762 if (sp_state == "bid" && _bids)
763 say("It is " sp_player "'s bid!" _extra " (" _bids ")")
764 if (sp_state == "play" && !_pile)
765 say("It is " sp_player "'s turn!" _extra)
766 if (sp_state == "play" && _pile)
767 say("It is " sp_player "'s turn!" _extra " (" _pile ")")
769 for (_i=0; sp_state == "pass" && _i<4; _i++)
770 if (sp_passer(_i) && !sp_pass[_i])
771 say("Waiting for " sp_order[_i] " to pass a card!")
773 if (/!!/ && (sp_state == "bid" || sp_state == "play")) {
774 if (sp_player in sp_notify) {
775 _bids = _bids ? _bids : "none"
776 _pile = _pile ? sp_piles : "none"
777 mail_send(sp_notify[sp_player], \
778 "It is your " sp_state "!", \
779 "Bids so far: " _bids "\n" \
780 "Cards played: " _pile)
781 say("Notified " sp_player " at " sp_notify[sp_player])
783 say("No email address for " sp_player)
788 /^\.bids$/ && sp_state ~ "(pass|play)" {
789 say(sp_order[0] " bid " sp_bid(0) ", " \
790 sp_order[2] " bid " sp_bid(2) ", " \
791 "total: " sp_bids[0] + sp_bids[2])
792 say(sp_order[1] " bid " sp_bid(1) ", " \
793 sp_order[3] " bid " sp_bid(3) ", " \
794 "total: " sp_bids[1] + sp_bids[3])
797 /^\.tricks$/ && sp_state == "play" {
798 say(sp_order[0] " took " int(sp_tricks[0]) "/" sp_bid(0) ", " \
799 sp_order[2] " took " int(sp_tricks[2]) "/" sp_bid(2))
800 say(sp_order[1] " took " int(sp_tricks[1]) "/" sp_bid(1) ", " \
801 sp_order[3] " took " int(sp_tricks[3]) "/" sp_bid(3))
804 (TO == NICK || DST == sp_channel) &&
805 /^\.(score|status)$/ {
806 if (sp_state == "new") {
807 say("There is no game in progress")
809 if (sp_state ~ "join|bid|pass|play") {
811 sp_playto " points, " \
814 if (sp_state == "join") {
815 say("Waiting for players: " \
816 sp_order[0] " " sp_order[1] " " \
817 sp_order[2] " " sp_order[3])
819 if (sp_state ~ "bid|pass|play") {
820 say(sp_team(0) ": " \
821 int(sp_scores[0]) " points, " \
822 int(sp_bags(0)) " bags")
823 say(sp_team(1) ": " \
824 int(sp_scores[1]) " points, " \
825 int(sp_bags(1)) " bags")
829 /^\.((new|end|load)game|join|look|bid|pass|play|notify)/ {
830 sp_save("var/sp_cur.json");