]> Pileus Git - ~andy/rhawk/blob - spades.awk
a377ed5c415b6e6a6ac9a1e5c351e580e8f8b249
[~andy/rhawk] / spades.awk
1 # For saving
2 @include "json.awk"
3
4 # Functions
5 function sp_init(cards, tmp0, tmp1)
6 {
7         # Init deck
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"
12         split(cards, tmp0)
13         for (i=1; i<=length(tmp0); i++)
14                 sp_deck[tmp0[i]] = i
15 }
16
17 function sp_reset(type)
18 {
19         # Per message
20         if (type <  0) {
21                 sp_from     = ""    #    The speakers player name
22                 sp_valid    = ""    #    It is the speaker turn
23         }
24
25         # Per hand
26         if (type >= 0) {
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
30         }
31
32         # Per round
33         if (type >= 1) {
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
43         }
44
45         # Per game
46         if (type >= 2) {
47                 sp_state    = "new" #     {new,join,bid,pass,play}
48                 sp_owner    = ""    #     Who started the game
49                 sp_playto   = 0     #     Score the game will go to
50                 sp_dealer   =-1     #     Who is dealing this round
51                 sp_turn     = 0     #     Index of who's turn it is
52                 sp_player   = ""    #     Who's turn it is
53                 sp_limit    = 10    #     Bag out limit / nil bonus
54                 delete sp_players   # [p] Player names players["name"] -> i
55                 delete sp_auths     # [c] Player auth names auths["auth"] -> "name"
56                 delete sp_share     # [c] Player teammates share["friend"] -> "name"
57                 delete sp_order     # [i] Player order order[i] -> "name"
58                 delete sp_scores    # [i] Teams score
59                 delete sp_teams     # [i] Teams names
60         }
61
62         # Persistent
63         if (type >= 3) {
64                 sp_channel  = ""    #     channel to play in
65                 sp_log      = ""    #     Log file name
66                 sp_sock     = ""    #     UDP log socket
67                 delete sp_notify    # [p] E-mail notification address
68         }
69 }
70
71 function sp_acopy(dst, src,     key)
72 {
73         if (isarray(src)) {
74                 delete(dst)
75                 for (key in src)
76                         json_copy(dst, key, src[key])
77         }
78 }
79
80 function sp_save(file,  game)
81 {
82         # Per hand
83         game["suit"]    = sp_suit;
84         game["piles"]   = sp_piles;
85         json_copy(game, "pile",    sp_pile);
86
87         # Per round
88         game["state"]   = sp_state;
89         game["broken"]  = sp_broken;
90         json_copy(game, "last",    sp_last);
91         json_copy(game, "looked",  sp_looked);
92         json_copy(game, "bids",    sp_bids);
93         json_copy(game, "nil",     sp_nil);
94         json_copy(game, "pass",    sp_pass);
95         json_copy(game, "tricks",  sp_tricks);
96
97         # Per game
98         game["owner"]   = sp_owner;
99         game["playto"]  = sp_playto;
100         game["dealer"]  = sp_dealer;
101         game["turn"]    = sp_turn;
102         game["player"]  = sp_player;
103         game["limit"]   = sp_limit;
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, "teams",   sp_teams);
111
112         # Persistent
113         game["channel"] = sp_channel;
114         game["log"]     = sp_log;
115         json_copy(game, "notify",  sp_notify);
116
117         # Save
118         json_save(file, game);
119 }
120
121 function sp_load(file,  game)
122 {
123         # Load
124         if (!json_load(file, game))
125                 return
126
127         # Per hand
128         sp_suit    = game["suit"];
129         sp_piles   = game["piles"];
130         sp_acopy(sp_pile,    game["pile"]);
131
132         # Per round
133         sp_state   = game["state"];
134         sp_broken  = game["broken"];
135         sp_acopy(sp_last,    game["last"]);
136         sp_acopy(sp_looked,  game["looked"]);
137         sp_acopy(sp_bids,    game["bids"]);
138         sp_acopy(sp_nil,     game["nil"]);
139         sp_acopy(sp_pass,    game["pass"]);
140         sp_acopy(sp_tricks,  game["tricks"]);
141
142         # Per game
143         sp_owner   = game["owner"];
144         sp_playto  = game["playto"];
145         sp_dealer  = game["dealer"];
146         sp_turn    = game["turn"];
147         sp_player  = game["player"];
148         sp_limit   = game["limit"];
149         sp_acopy(sp_hands,   game["hands"]);
150         sp_acopy(sp_players, game["players"]);
151         sp_acopy(sp_auths,   game["auths"]);
152         sp_acopy(sp_share,   game["share"]);
153         sp_acopy(sp_order,   game["order"]);
154         sp_acopy(sp_scores,  game["scores"]);
155         sp_acopy(sp_teams,   game["teams"]);
156
157         # Persistent
158         sp_channel = game["channel"];
159         sp_log     = game["log"];
160         sp_acopy(sp_notify,  game["notify"]);
161 }
162
163 function sp_say(msg)
164 {
165         say(sp_channel, msg)
166         print msg |& sp_sock
167         print strftime("%Y-%m-%d %H:%M:%S | ") msg >> "logs/" sp_log
168         fflush("logs/" sp_log)
169 }
170
171 function sp_pretty(cards, who)
172 {
173         if (!nocolor[who]) {
174                 gsub(/[0-9JQKA]*[sc]/, "\0031,00\002&\017", cards) # black
175                 gsub(/[0-9JQKA]*[hd]/, "\0034,00\002&\017", cards) # red
176         }
177         if (!nounicode[who]) {
178                 gsub(/s/, "\002♠", cards)
179                 gsub(/h/, "\002♥", cards)
180                 gsub(/d/, "\002♦", cards)
181                 gsub(/c/, "\002♣", cards)
182         }
183         return cards
184 }
185
186 function sp_next(who, prev)
187 {
188         prev      = sp_turn
189         sp_turn   = who ? sp_players[who] : (sp_turn + 1) % 4
190         if (length(sp_order) == 4)
191                 sp_player = sp_order[sp_turn]
192         return prev
193 }
194
195 function sp_shuf(i, mixed)
196 {
197         sp_usort(sp_players, mixed)
198         for (i in mixed) {
199                 sp_order[i-1] = mixed[i]
200                 sp_players[mixed[i]] = i-1
201         }
202 }
203
204 function sp_deal(       shuf)
205 {
206         sp_say("/me deals the cards")
207         sp_usort(sp_deck, shuf)
208         for (i=1; i<=52; i++)
209                 sp_hands[sp_order[i%4]][shuf[i]] = 1
210         sp_state  = "bid"
211         sp_dealer = (sp_dealer+1)%4
212         sp_turn   =  sp_dealer
213         sp_player =  sp_order[sp_turn]
214         sp_say(sp_player ": you bid first!")
215 }
216
217 function sp_hand(to, who,       sort, str)
218 {
219         asorti(sp_hands[who], sort, "sp_csort")
220         for (i=0; i<length(sort); i++)
221                 str = str "" sprintf("%4s", sort[i])
222         gsub(/^ +| +$/, "", str)
223         return sp_pretty(str, to)
224 }
225
226 function sp_hasa(who, expr)
227 {
228         for (c in sp_hands[who]) {
229                 if (c ~ expr)
230                         return 1
231         }
232         return 0
233 }
234
235 function sp_type(card)
236 {
237         return substr(card, length(card))
238 }
239
240 function sp_usort(list, out) {
241         for (i in list)
242                 out[i] = rand()
243         asorti(out, out, "@val_num_asc")
244 }
245
246 function sp_csort(i1,v1,i2,v2) {
247         return sp_deck[i1] > sp_deck[i2] ? +1 :
248                sp_deck[i1] < sp_deck[i2] ? -1 : 0;
249 }
250
251 function sp_winner(     card, tmp)
252 {
253         for (card in sp_pile)
254                 if (card !~ sp_suit && card !~ /s/)
255                         delete sp_pile[card]
256         asorti(sp_pile, tmp, "sp_csort")
257         #print "pile: " tmp[1] ">" tmp[2] ">" tmp[3] ">" tmp[4]
258         return tmp[1]
259 }
260
261 function sp_team(i, players)
262 {
263         #return "{" sp_order[i+0] "," sp_order[i+2] "}"
264         if ((i in sp_teams) && !players)
265                 return sp_teams[i]
266         else
267                 return sp_order[i+0] "/" sp_order[i+2]
268 }
269
270 function sp_bags(i,     bags)
271 {
272         bags = sp_scores[i] % sp_limit
273         if (bags < 0)
274                 bags += sp_limit
275         return bags
276 }
277
278 function sp_bid(who)
279 {
280         return sp_nil[who] == 0 ? sp_bids[who] :
281                sp_nil[who] == 1 ? "nil"        :
282                sp_nil[who] == 2 ? "blind"      : "n/a"
283 }
284
285 function sp_passer(who)
286 {
287         return sp_nil[(who+0)%4] == 2 || sp_nil[(who+1)%4] != 0 ||
288                sp_nil[(who+2)%4] == 2 || sp_nil[(who+3)%4] != 0
289 }
290
291 function sp_bidders(    i, turn, bid, bids)
292 {
293         for (i = 0; i < 4; i++) {
294                 turn = (sp_dealer + i) % 4
295                 if (bid = sp_bid(turn))
296                         bids = bids " " sp_order[turn] ":" bid
297         }
298         gsub(/^ +| +$/, "", bids)
299         return bids
300 }
301
302 function sp_extra(      n, s)
303 {
304         n = sp_bids[0] + sp_bids[1] + sp_bids[2] + sp_bids[3];
305         s = n == 12 || n == 14 ? "" : "s";
306
307         return n<13 ? "Playing with " 13-n " bag"   s "!" :
308                n>13 ? "Fighting for " n-13 " trick" s "!" : "No bags!";
309 }
310
311 function sp_score(      bids, times, tricks)
312 {
313         for (i=0; i<2; i++) {
314                 bids   = sp_bids[i]   + sp_bids[i+2]
315                 tricks = sp_tricks[i] + sp_tricks[i+2]
316                 bags   = tricks - bids
317                 times  = int((sp_bags(i) + bags) / sp_limit)
318                 if (times > 0) {
319                         sp_say(sp_team(i) " bag" (times>1?" way ":" ") "out")
320                         sp_scores[i] -= sp_limit * 10 * times;
321                 }
322                 if (tricks >= bids) {
323                         sp_say(sp_team(i) " make their bid: " tricks "/" bids)
324                         sp_scores[i] += bids*10 + bags;
325                 } else {
326                         sp_say(sp_team(i) " go bust: " tricks "/" bids)
327                         sp_scores[i] -= bids*10;
328                 }
329         }
330         for (i=0; i<4; i++) {
331                 if (!sp_nil[i])
332                         continue
333                 sp_say(sp_order[i] " " \
334                     (sp_nil[i] == 1 && !sp_tricks[i] ? "makes nil!"       :
335                      sp_nil[i] == 1 &&  sp_tricks[i] ? "fails at nil!"    :
336                      sp_nil[i] == 2 && !sp_tricks[i] ? "makes blind nil!" :
337                      sp_nil[i] == 2 &&  sp_tricks[i] ? "fails miserably at blind nil!" :
338                                                        "unknown"))
339                 sp_scores[i%2] += sp_limit * 10 * sp_nil[i] * \
340                         (sp_tricks[i] == 0 ? 1 : -1)
341         }
342         if (sp_scores[0] > sp_scores[1])
343                 sp_say(sp_team(0) " lead " sp_scores[0] " to " sp_scores[1] " of " sp_playto)
344         else if (sp_scores[1] > sp_scores[0])
345                 sp_say(sp_team(1) " lead " sp_scores[1] " to " sp_scores[0] " of " sp_playto)
346         else
347                 sp_say("tied at " sp_scores[0] " of " sp_playto)
348 }
349
350 function sp_play(card,  winner, pi)
351 {
352         delete sp_hands[sp_from][card]
353         sp_pile[card] = sp_player
354         sp_piles      = sp_piles (sp_piles?",":"") card
355         sp_next()
356
357         if (card ~ /s/)
358                 sp_broken = 1
359
360         # Start hand
361         if (length(sp_pile) == 1)
362                 sp_suit = sp_type(card)
363
364         # Finish hand
365         if (length(sp_pile) == 4) {
366                 winner = sp_winner()
367                 pi     = sp_players[sp_pile[winner]]
368                 sp_tricks[pi]++
369                 sp_say(sp_pile[winner] " wins with " sp_pretty(winner, FROM) \
370                        " (" sp_pretty(sp_piles, FROM) ")")
371                 sp_last["player"] = sp_pile[winner];
372                 sp_last["pile"]   = sp_piles;
373                 sp_next(sp_pile[winner])
374                 sp_reset(0)
375         }
376
377         # Finish round
378         if (sp_tricks[0] + sp_tricks[1] + \
379             sp_tricks[2] + sp_tricks[3] == 13) {
380                 sp_say("Round over!")
381                 sp_score()
382                 if (sp_scores[0] >= sp_playto || sp_scores[1] >= sp_playto &&
383                     sp_scores[0]              != sp_scores[1]) {
384                         sp_say("Game over!")
385                         winner = sp_scores[0] > sp_scores[1] ? 0 : 1
386                         looser = !winner
387                         say(CHANNEL, sp_team(winner) " wins the game " \
388                             sp_scores[winner] " to " sp_scores[looser])
389                         say(CHANNEL, sp_order[winner+0] "++")
390                         say(CHANNEL, sp_order[winner+2] "++")
391                         sp_reset(2)
392
393                 } else {
394                         if (sp_scores[0] == sp_scores[1] &&
395                             sp_scores[0] >= sp_playto)
396                                 sp_say("It's tie! Playing an extra round!");
397                         sp_reset(1)
398                         sp_deal()
399                 }
400         }
401 }
402
403 # Statistics
404 function sp_delay(sec)
405 {
406         return (sec > 60*60*24 ? int(sec/60/60/24) "d " : "") \
407                (sec > 60*60    ? int(sec/60/60)%24 "h " : "") \
408                                  int(sec/60)%60    "m"
409 }
410
411 function sp_max(list,    i, max)
412 {
413         for (i=0; i<length(list); i++)
414                 if (max == "" || list[i] > max)
415                         max = list[i]
416         return max
417 }
418
419 function sp_avg(list,    i, sum)
420 {
421         for (i=0; i<length(list); i++)
422                 sum += list[i]
423         return sum / length(list)
424 }
425
426 function sp_cur(list)
427 {
428         return list[length(list)-1]
429 }
430
431 function sp_stats(file,   line, arr, time, user, turn, start, delay, short, extra)
432 {
433         # Process log file
434         while ((stat = getline line < file) > 0) {
435                 # Parse date
436                 if (!match(line, /^([0-9\- \:]*) \| (.*)$/, arr))
437                         continue
438                 gsub(/[:-]/, " ", arr[1])
439                 time = mktime(arr[1])
440
441                 # Parse user
442                 if (!match(arr[2], /^([^:]*): (.*)$/, arr))
443                         continue
444                 user = arr[1]
445
446                 # Record user latency
447                 if (turn) {
448                         delay[turn][length(delay[turn])] = time - start
449                         turn  = 0
450                 }
451                 if (match(arr[2], /^(it is your|you .*(first|lead)!$)/, arr)) {
452                         turn  = user
453                         start = time
454                 }
455         }
456         close(file)
457
458         # Add current latency
459         if (turn) {
460                 delay[turn][length(delay[turn])] = systime() - start
461                 debug("time: " (systime() - start))
462         }
463
464         # Check for error
465         if (stat < 0)
466                 reply("File does not exist: " file);
467
468         # Output statistics
469         for (user in delay) {
470                 short = length(user) <= 4 ? user : substr(user, 0, 4)
471                 extra = (user != turn) ? "" : \
472                         ", " sp_delay(sp_cur(delay[user])) " (cur)";
473                 say("latency for " short \
474                         ": " sp_delay(sp_avg(delay[user])) " (avg)" \
475                         ", " sp_delay(sp_max(delay[user])) " (max)" extra)
476         }
477 }
478
479 # Misc
480 BEGIN {
481         cmd = "od -An -N4 -td4 /dev/random"
482         cmd | getline seed
483         close(cmd)
484         srand(seed)
485         sp_init()
486         sp_reset(2)
487         sp_load("var/sp_cur.json")
488         sp_sock = "/inet/udp/0/localhost/6173"
489         print "starting rhawk" |& sp_sock
490         #if (sp_channel)
491         #       sp_say("Game restored.")
492 }
493
494 // {
495         sp_from  = AUTH in sp_auths ? sp_auths[AUTH] : \
496                    AUTH in sp_share ? sp_share[AUTH] : FROM
497         sp_valid = sp_from && sp_from == sp_player
498 }
499
500 CMD == "PRIVMSG" &&
501 ! /help/ &&
502 /[Ss]pades/ {
503         say("Spades! " sp_pretty("As,Ah,Ad,Ac", FROM))
504 }
505
506 AUTH == OWNER &&
507 /^\.savegame/ {
508         sp_save("var/sp_save.json");
509         say("Game saved.")
510 }
511
512 AUTH == OWNER &&
513 /^\.loadgame/ {
514         sp_load("var/sp_save.json");
515         say("Game loaded.")
516 }
517
518 # Help
519 /^\.help$/ {
520         say(".help spades -- play a game of spades")
521 }
522
523 /^\.help [Ss]pades$/ {
524         say("Spades -- play a game of spades")
525         say(".help game -- setup and administer the game")
526         say(".help play -- commands for playing spades")
527         say(".help auth -- control player authorization")
528         next
529 }
530
531 /^\.help game$/ {
532         say(".newgame [score] -- start a game to <score> points, default 500")
533         say(".endgame -- abort the current game")
534         say(".savegame -- save the current game to disk")
535         say(".loadgame -- load the previously saved game")
536         next
537 }
538
539 /^\.help play$/ {
540         say(".join -- join the current game")
541         say(".look -- look at your cards")
542         say(".bid [n] -- bid for <n> tricks")
543         say(".pass [card] -- pass a card to your partner")
544         say(".play [card] -- play a card")
545         say(".team [name] -- set your team name")
546         say(".last -- show who took the previous trick")
547         say(".turn -- check whose turn it is")
548         say(".bids -- check what everyone bid")
549         say(".tricks -- check how many trick have been taken")
550         say(".score -- check the score")
551         next
552 }
553
554 /^\.help auth$/ {
555         say(".auth [who] -- display authentication info for a user")
556         say(".allow [who] -- allow another person to play on your behalf")
557         say(".deny [who] -- prevent a previously allowed user from playing")
558         say(".show -- display which users can play for which players")
559         say(".notify [addr] -- email user when it is their turn")
560         next
561 }
562
563 # Debugging
564 AUTH == OWNER &&
565 /^\.deal (\w+) (.*)/ {
566         sp_say(FROM " is cheating for " $2)
567         delete sp_hands[$2]
568         for (i=3; i<=NF; i++)
569                 sp_hands[$2][$i] = 1
570         next
571 }
572
573 AUTH == OWNER &&
574 /^\.order (\w+) ([0-4])/ {
575         sp_say(FROM " is cheating for " $2)
576         sp_order[$3] = $2
577         sp_players[$2] = $3
578         sp_player = sp_order[sp_turn]
579 }
580
581 AUTH == OWNER &&
582 sp_state == "play" &&
583 /^\.force (\w+) (\S+)$/ {
584         sp_say(FROM " is cheating for " $2)
585         sp_from = $2
586         sp_play($3)
587         next
588 }
589
590
591 # Setup
592 match($0, /^\.newgame ?([1-9][0-9]*) *- *([1-9][0-9]*)$/, _arr) {
593         if (_arr[2] > _arr[1])
594                 $0 = $1 " " int(rand() * (_arr[2]-_arr[1])+_arr[1])
595 }
596
597 /^\.newgame ?([1-9][0-9]*)?$/ {
598         if (sp_state != "new") {
599                 reply("There is already a game in progress.")
600         } else {
601                 $1         = ".join"
602                 sp_owner   = FROM
603                 sp_playto  = $2 ? $2 : 200
604                 sp_limit   = sp_playto > 200 ? 10 : 5;
605                 sp_state   = "join"
606                 sp_channel = DST
607                 sp_log     = strftime("%Y%m%d_%H%M%S.log")
608                 sp_say(sp_owner " starts a game of Spades to " sp_playto " with " sp_limit " bags!")
609         }
610 }
611
612 (sp_from == sp_owner || AUTH == OWNER) &&
613 /^\.endgame$/ {
614         if (sp_state == "new") {
615                 reply("There is no game in progress.")
616         } else {
617                 sp_say(FROM " ends the game")
618                 sp_reset(2)
619         }
620 }
621
622 /^\.join/ {
623         if (sp_state == "new") {
624                 reply("There is no game in progress")
625         }
626         else if (sp_state == "play") {
627                 reply("The game has already started")
628         }
629         else if (sp_state == "join" && sp_from in sp_players) {
630                 reply("You are already playing")
631         }
632         else if (sp_state == "join") {
633                 i = sp_next()
634                 sp_players[FROM] = i
635                 if (AUTH)
636                         sp_auths[AUTH] = FROM
637                 sp_order[i] = FROM
638                 sp_say(FROM " joins the game!")
639         }
640         if (sp_state == "join" && sp_turn == 0) {
641                 sp_shuf()
642                 sp_deal()
643         }
644 }
645
646 /^\.allow \S+$/ {
647         _who = $2 in USERS ? USERS[$2]["auth"] : ""
648         _str = _who && _who != $2 ? $2 " (" _who ")" : $2
649         if (sp_state ~ "new|join") {
650                 reply("The game has not yet started")
651         }
652         else if (!(sp_from in sp_players)) {
653                 reply("You are not playing")
654         }
655         else if (!_who) {
656                 reply(_str " is not logged in")
657         }
658         else if (_who in sp_players || _who in sp_auths) {
659                 reply(_str " is a primary player")
660         }
661         else if (_who in sp_share) {
662                 reply(_str " is already playing for " sp_share[_who])
663         }
664         else {
665                 sp_say(_str " can now play for " sp_from)
666                 sp_share[_who] = sp_from
667         }
668 }
669
670 /^\.deny \S+$/ {
671         _who = $2 in USERS ? USERS[$2]["auth"] : $2
672         _str = _who && _who != $2 ? $2 " (" _who ")" : $2
673         if (sp_state ~ "new|join") {
674                 reply("The game has not yet started")
675         }
676         else if (!(sp_from in sp_players)) {
677                 reply("You are not playing")
678         }
679         else if (_who in sp_players || _who in sp_auths) {
680                 reply(_str " is a primary player")
681         }
682         else if (!(_who in sp_share) || sp_share[_who] != sp_from) {
683                 reply(_str " is not playing for " sp_from)
684         }
685         else {
686                 sp_say(_str " can no longer play for " sp_from)
687                 delete sp_share[_who]
688         }
689 }
690
691 /^\.team/ {
692         gsub(/^\.team */, "")
693         _team = sp_from in sp_players ? sp_players[sp_from] % 2 : 0
694         if (sp_state ~ "new|join") {
695                 reply("The game has not yet started")
696         }
697         else if (!(sp_from in sp_players)) {
698                 reply("You are not playing")
699         }
700         else if ($0 ~ /^[^a-zA-Z0-9]/) {
701                 reply("Invalid team name")
702         }
703         else if ($0 ~ /^./) {
704                 sp_teams[_team] = substr($0, 0, 32)
705                 sp_say(sp_team(_team,1) " are now known as " sp_team(_team))
706         }
707         else {
708                 delete sp_teams[_team]
709                 sp_say(sp_team(_team,1) " are boring")
710         }
711 }
712
713 /^\.whoami/ {
714         if (!(sp_from in sp_players))
715                 reply("You are not playing")
716         else if (sp_from == FROM)
717                 say(FROM " has an existential crisis")
718         else
719                 reply("You are playing for " sp_from);
720 }
721
722 /^\.notify$/ {
723         if (sp_from in sp_notify)
724                 reply("Your address is " sp_notify[sp_from])
725         else
726                 reply("Your address is not set")
727 }
728
729 /^\.notify clear$/ {
730         if (sp_from in sp_notify) {
731                 reply("Removing address " sp_notify[sp_from])
732                 delete sp_notify[sp_from]
733         } else {
734                 reply("Your address is not set")
735         }
736 }
737
738 /^\.notify \S+@\S+.\S+$/ {
739         _addr = $2
740         gsub(/[^a-zA-Z0-9_+@.-]/, "", _addr)
741         sp_notify[sp_from] = _addr
742         reply("Notifying you at " _addr)
743 }
744
745 sp_state ~ "(bid|pass|play)" &&
746 /^\.show/ {
747         delete _lines
748         for (_i in sp_share)
749                 _lines[sp_share[_i]] = _lines[sp_share[_i]] " " _i
750         for (_i in _lines)
751                 say(_i " allowed:" _lines[_i])
752 }
753
754 !sp_valid &&
755 (sp_state == "bid" || sp_state == "play") &&
756 /^\.(bid|play)\>/ {
757         if (sp_from in sp_players)
758                 reply("It is not your turn.")
759         else
760                 reply("You are not playing.")
761 }
762
763 sp_valid &&
764 sp_state == "bid" &&
765 /^\.bid (0|[1-9][0-9]*)$/ {
766         if ($2 < 0 || $2 > 13) {
767                 reply("You can only bid from 0 to 13")
768         } else {
769                 i = sp_next()
770                 sp_bids[i] = $2
771                 if ($2 == 0 && !sp_looked[i]) {
772                         sp_say(FROM " goes blind nil!")
773                         sp_nil[i] = 2
774                 } else if ($2 == 0) {
775                         sp_say(FROM " goes nil!")
776                         sp_nil[i] = 1
777                 } else {
778                         sp_nil[i] = 0
779                 }
780                 if (sp_turn != sp_dealer) {
781                         sp_say(sp_player ": it is your bid! (" sp_bidders() ")")
782                 } else {
783                         sp_say(sp_extra() " (" sp_bidders() ")")
784                         for (p in sp_players)
785                                 say(p, "You have: " sp_hand(p, p))
786                         sp_state = "play"
787                         for (i=0; i<2; i++) {
788                                 if (sp_passer(i)) {
789                                         sp_say(sp_team(i,1) ": select a card to pass " \
790                                             "(/msg " NICK " .pass <card>)")
791                                         sp_state = "pass"
792                                 }
793                         }
794                         if (sp_state == "play")
795                                 sp_say(sp_player ": you have the opening lead!")
796                 }
797         }
798 }
799
800 sp_state == "pass" &&
801 /^\.pass (\S+)$/ {
802         _card = $2
803         _team = sp_from in sp_players ? sp_players[sp_from] % 2 : 0
804
805         # check validity and pass
806         if (!(sp_from in sp_players)) {
807                 reply("You are not playing.")
808         }
809         else if (!sp_passer(_team)) {
810                 reply("Your team did not go blind")
811         }
812         else if (sp_pass[sp_players[sp_from]]) {
813                 reply("You have already passed a card")
814         }
815         else if (!(_card in sp_deck)) {
816                 reply("Invalid card")
817         }
818         else if (!(_card in sp_hands[sp_from])) {
819                 reply("You do not have that card")
820         }
821         else {
822                 sp_pass[sp_players[sp_from]] = $2
823                 sp_say(FROM " passes a card")
824         }
825
826         # check for end of passing
827         if ((!sp_passer(0) || (sp_pass[0] && sp_pass[2])) &&
828             (!sp_passer(1) || (sp_pass[1] && sp_pass[3]))) {
829                 for (i in sp_pass) {
830                         _partner = (i+2)%4
831                         _card    = sp_pass[i]
832                         delete sp_hands[sp_order[i]][_card]
833                         sp_hands[sp_order[_partner]][_card] = 1
834                 }
835                 sp_say("Cards have been passed!")
836                 sp_say(sp_player ": you have the opening lead!")
837                 for (p in sp_players)
838                         say(p, "You have: " sp_hand(p, p))
839                 sp_state = "play"
840         }
841 }
842
843 sp_state ~ "(bid|pass|play)" &&
844 /^\.look$/ {
845         if (!(sp_from in sp_players)) {
846                 reply("You are not playing.")
847         } else {
848                 sp_looked[sp_players[sp_from]] = 1
849                 say(FROM, "You have: " sp_hand(FROM, sp_from))
850         }
851 }
852
853 sp_valid &&
854 sp_state == "play" &&
855 /^\.play (\S+)/ {
856         _card = $2
857         gsub(/[^A-Za-z0-9]/, "", _card);
858         if (!(_card in sp_deck)) {
859                 reply("Invalid card")
860         }
861         else if (sp_suit && _card !~ sp_suit && sp_hasa(sp_from, sp_suit)) {
862                 reply("You must follow suit (" sp_suit ")")
863         }
864         else if (_card ~ /s/ && length(sp_hands[sp_from]) == 13 && sp_hasa(sp_from, "[^s]$")) {
865                 reply("You cannot trump on the first hand")
866         }
867         else if (_card ~ /s/ && length(sp_pile) == 0 && sp_hasa(sp_from, "[^s]$") && !sp_broken) {
868                 reply("Spades have not been broken")
869         }
870         else if (!(_card in sp_hands[sp_from])) {
871                 reply("You do not have that card")
872         }
873         else {
874                 sp_play(_card)
875                 if (sp_state == "play") {
876                         if (length(sp_hands[sp_from]))
877                                 say(FROM, "You have: " sp_hand(FROM, sp_from))
878                         if (sp_piles)
879                                 sp_say(sp_player ": it is your turn! " \
880                                     "(" sp_pretty(sp_piles, sp_player) ")")
881                         else
882                                 sp_say(sp_player ": it is your turn!")
883                 }
884         }
885 }
886
887 /^\.last/ && sp_state == "play" {
888         if (!isarray(sp_last))
889                 say("No tricks have been taken!");
890         else
891                 say(sp_last["player"] " took " \
892                     sp_pretty(sp_last["pile"], FROM));
893 }
894
895 /^\.bids/ && sp_state == "bid" ||
896 /^\.turn/ && sp_state ~ "(bid|pass|play)" {
897         _bids   = sp_bidders()
898         _pile   = sp_pretty(sp_piles, FROM)
899         _extra  = ""
900         delete _notify
901
902         if (/!!/)
903                 _notify[0] = sp_player
904         for (_i in sp_share) {
905                 if (sp_share[_i] != sp_player)
906                         continue
907                 if (/!/)
908                         _extra = _extra " " _i "!"
909                 if (/!!!/)
910                         _notify[length(_notify)] = _i
911         }
912
913         if (sp_state == "bid" && !_bids)
914                 say("It is " sp_player "'s bid!" _extra)
915         if (sp_state == "bid" && _bids)
916                 say("It is " sp_player "'s bid!" _extra " (" _bids ")")
917         if (sp_state == "play" && !_pile)
918                 say("It is " sp_player "'s turn!" _extra)
919         if (sp_state == "play" && _pile)
920                 say("It is " sp_player "'s turn!" _extra " (" _pile ")")
921
922         if (sp_state == "bid" || sp_state == "play") {
923                 for (_i in _notify) {
924                         if (_notify[_i] in sp_notify) {
925                                 _bids = _bids ? _bids    : "none"
926                                 _pile = _pile ? sp_piles : "none"
927                                 mail_send(sp_notify[_notify[_i]],     \
928                                         "It is your " sp_state "!", \
929                                         "Bids so far:  " _bids "\n" \
930                                         "Cards played: " _pile)
931                                 say("Notified " _notify[_i] " at " sp_notify[_notify[_i]])
932                         } else {
933                                 say("No email address for " _notify[_i])
934                         }
935                 }
936         }
937
938         for (_i=0; sp_state == "pass" && _i<4; _i++)
939                 if (sp_passer(_i) && !sp_pass[_i])
940                         say("Waiting for " sp_order[_i] " to pass a card!")
941 }
942
943 /^\.bids$/ && sp_state ~ "(pass|play)" {
944         say(sp_order[0] " bid " sp_bid(0) ", " \
945             sp_order[2] " bid " sp_bid(2) ", " \
946             "total: " sp_bids[0] + sp_bids[2])
947         say(sp_order[1] " bid " sp_bid(1) ", " \
948             sp_order[3] " bid " sp_bid(3) ", " \
949             "total: " sp_bids[1] + sp_bids[3])
950 }
951
952 /^\.tricks$/ && sp_state == "play" {
953         say(sp_order[0] " took " int(sp_tricks[0]) "/" sp_bid(0) ", " \
954             sp_order[2] " took " int(sp_tricks[2]) "/" sp_bid(2))
955         say(sp_order[1] " took " int(sp_tricks[1]) "/" sp_bid(1) ", " \
956             sp_order[3] " took " int(sp_tricks[3]) "/" sp_bid(3))
957 }
958
959 (TO == NICK || DST == sp_channel) &&
960 /^\.(score|status)$/ {
961         if (sp_state == "new") {
962                 say("There is no game in progress")
963         }
964         if (sp_state ~ "join|bid|pass|play") {
965                 say("Playing to: " \
966                     sp_playto " points, " \
967                     sp_limit  " bags")
968         }
969         if (sp_state == "join") {
970                 say("Waiting for players: " \
971                     sp_order[0] " " sp_order[1] " " \
972                     sp_order[2] " " sp_order[3])
973         }
974         if (sp_state ~ "bid|pass|play") {
975                 say(sp_team(0) ": " \
976                     int(sp_scores[0]) " points, " \
977                     int(sp_bags(0))   " bags")
978                 say(sp_team(1) ": " \
979                     int(sp_scores[1]) " points, " \
980                     int(sp_bags(1))   " bags")
981         }
982 }
983
984 (TO == NICK || DST == sp_channel) &&
985 /^\.log/ {
986         say("http://pileus.org/andy/spades/" sp_log)
987 }
988
989 (TO == NICK || DST == sp_channel) &&
990 /^\.stats$/ {
991         sp_stats("logs/" sp_log);
992 }
993
994 (TO == NICK || DST == sp_channel) &&
995 /^\.stats ([0-9]+_[0-9]+)(\.log)$/ {
996         gsub(/\.log$/, "", $2);
997         sp_stats("logs/" $2 ".log");
998 }
999
1000 /^\.((new|end|load)game|join|look|bid|pass|play|allow|deny|team|notify)/ {
1001         sp_save("var/sp_cur.json");
1002 }