]> Pileus Git - ~andy/rhawk/blob - spades.awk
save
[~andy/rhawk] / spades.awk
1 # Todo:
2 #   - highest bidder leads
3 #   - show card after play
4
5 # Functions
6 function sp_init(cards, tmp0, tmp1)
7 {
8         sp_valid    = 0     #     Message sent from sp_player
9         sp_player   = ""    #     Who's turn it is
10         sp_turn     = 0     #     Index of who's turn it is
11         delete sp_hands     # [p] Each players cards
12
13         # Init deck
14         cards ="As Ks Qs Js 10s 9s 8s 7s 6s 5s 4s 3s 2s "\
15                "Ah Kh Qh Jh 10h 9h 8h 7h 6h 5h 4h 3h 2h "\
16                "Ac Kc Qc Jc 10c 9c 8c 7c 6c 5c 4c 3c 2c "\
17                "Ad Kd Qd Jd 10d 9d 8d 7d 6d 5d 4d 3d 2d"
18         split(cards, tmp0)
19         for (i=1; i<=length(tmp0); i++)
20                 sp_deck[tmp0[i]] = i
21 }
22
23 function sp_reset(type)
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_looked    # [i] Whether a player has looked a their cards
37                 delete sp_bids      # [i] Each players bid
38                 delete sp_nil       # [i] Nil multiplier 0=regular, 1=nil, 2=blind
39                 delete sp_pass      # [i] Cards to pass
40                 delete sp_tricks    # [i] Tricks this round
41         }
42
43         # Per game
44         if (type >= 2) {
45                 sp_channel  = ""    #     channel to play in
46                 sp_state    = "new" #     {new,join,bid,play}
47                 sp_owner    = ""    #     Who started the game
48                 sp_playto   = 0     #     Score the game will go to
49                 sp_dealer   =-1     #     Who is dealing this round
50                 delete sp_players   # [p] Player names players["name"] -> i
51                 delete sp_order     # [i] Player order order[i] -> "name"
52                 delete sp_scores    # [i] Teams score
53         }
54 }
55
56 function sp_pretty(cards, who)
57 {
58         if (!plain[who]) {
59                 gsub(/[0-9JQKA]*[sc]/, "\0031,00\002&\017", cards) # black
60                 gsub(/[0-9JQKA]*[hd]/, "\0034,00\002&\017", cards) # red
61                 gsub(/s/, "\002♠", cards)
62                 gsub(/h/, "\002♥", cards)
63                 gsub(/d/, "\002♦", cards)
64                 gsub(/c/, "\002♣", cards)
65         }
66         return cards
67 }
68
69 function sp_next(who, prev)
70 {
71         prev      = sp_turn
72         sp_turn   = who ? sp_players[who] : (sp_turn + 1) % 4
73         sp_player = sp_order[sp_turn]
74         return prev
75 }
76
77 function sp_deal(       shuf)
78 {
79         say("/me deals the cards")
80         asorti(sp_deck, shuf, "sp_usort")
81         for (i=1; i<=52; i++)
82                 sp_hands[sp_order[i%4]][shuf[i]] = 1
83         sp_state  = "bid"
84         sp_dealer = (sp_dealer+1)%4
85         sp_turn   =  sp_dealer
86         sp_player =  sp_order[sp_turn]
87         say("Bidding starts with " sp_player "!")
88 }
89
90 function sp_hand(who,   sort, str)
91 {
92         asorti(sp_hands[who], sort, "sp_csort")
93         for (i=0; i<length(sort); i++)
94                 str = str "" sprintf("%4s", sort[i])
95         gsub(/^ +| +$/, "", str)
96         return sp_pretty(str, who)
97 }
98
99 function sp_hasa(who, expr)
100 {
101         for (c in sp_hands[who]) {
102                 if (c ~ expr)
103                         return 1
104         }
105         return 0
106 }
107
108 function sp_type(card)
109 {
110         return substr(card, length(card))
111 }
112
113 function sp_usort(a,b,c,d) {
114         return rand() - 0.5
115 }
116
117 function sp_csort(i1,v1,i2,v2) {
118         return sp_deck[i1] > sp_deck[i2] ? +1 :
119                sp_deck[i1] < sp_deck[i2] ? -1 : 0;
120 }
121
122 function sp_winner(     card, tmp)
123 {
124         for (card in sp_pile)
125                 if (card !~ sp_suit && card !~ /s/)
126                         delete sp_pile[card]
127         asorti(sp_pile, tmp, "sp_csort")
128         #print "pile: " tmp[1] ">" tmp[2] ">" tmp[3] ">" tmp[4]
129         return tmp[1]
130 }
131
132 function sp_team(i)
133 {
134         #return "{" sp_order[i+0] "," sp_order[i+2] "}"
135         return sp_order[i+0] "/" sp_order[i+2]
136 }
137
138 function sp_bags(i,     bags)
139 {
140         bags = sp_scores[i] % 10
141         if (bags < 0)
142                 bags += 10
143         return bags
144 }
145
146 function sp_score(      bids, tricks)
147 {
148         for (i=0; i<2; i++) {
149                 bids   = sp_bids[i]   + sp_bids[i+2]
150                 tricks = sp_tricks[i] + sp_tricks[i+2]
151                 bags   = tricks - bids
152                 if (sp_bags(i) + bags > 10) {
153                         say(sp_team(i) " bag out")
154                         sp_scores[i] -= 100
155                 }
156                 if (tricks >= bids) {
157                         say(sp_team(i) " make their bid")
158                         sp_scores[i] += bids*10 + bags;
159                 } else {
160                         say(sp_team(i) " go bust")
161                         sp_scores[i] -= bids*10;
162                 }
163         }
164         for (i=0; i<4; i++) {
165                 if (!sp_nil[i])
166                         continue
167                 say(sp_order[i] " " \
168                     (sp_nil[i] == 1 && !sp_tricks[i] ? "makes nil!"       :
169                      sp_nil[i] == 1 &&  sp_tricks[i] ? "fails at nil!"    :
170                      sp_nil[i] == 2 && !sp_tricks[i] ? "makes blind nil!" :
171                      sp_nil[i] == 2 &&  sp_tricks[i] ? "fails miserably at blind nil!" :
172                                                        "unknown"))
173                 sp_scores[i%2] += 100 * sp_nil[i] * \
174                         (sp_tricks[i] == 0 ? 1 : -1)
175         }
176 }
177
178 function sp_play(card,  winner, pi)
179 {
180         delete sp_hands[FROM][card]
181         sp_pile[card] = sp_player
182         sp_piles      = sp_piles (sp_piles?",":"") card
183         sp_next()
184
185         if (card ~ /s/)
186                 sp_broken = 1
187
188         # Start hand
189         if (length(sp_pile) == 1)
190                 sp_suit = sp_type(card)
191
192         # Finish hand
193         if (length(sp_pile) == 4) {
194                 winner = sp_winner()
195                 pi     = sp_players[sp_pile[winner]]
196                 sp_tricks[pi]++
197                 say(sp_pile[winner] " wins with " sp_pretty(winner, FROM) \
198                     " (" sp_pretty(sp_piles, FROM) ")")
199                 sp_next(sp_pile[winner])
200                 sp_reset(0)
201         }
202
203         # Finish round
204         if (sp_tricks[0] + sp_tricks[1] + \
205             sp_tricks[2] + sp_tricks[3] == 13) {
206                 say("Round over!")
207                 sp_score()
208                 if (sp_scores[0] >= sp_playto || sp_scores[1] >= sp_playto &&
209                     sp_scores[0]              != sp_scores[1]) {
210                         say("Game over!")
211                         winner = sp_scores[0] > sp_scores[1] ? 0 : 1
212                         looser = !winner
213                         say(sp_team(winner) " wins the game " \
214                             sp_scores[winner] " to " sp_scores[looser])
215                         say(sp_order[winner+0] "++")
216                         say(sp_order[winner+2] "++")
217                         say(sp_order[looser+0] "--")
218                         say(sp_order[looser+2] "--")
219                         sp_reset(2)
220
221                 } else {
222                         if (sp_scores[0] == sp_scores[1] && 
223                             sp_scores[0] >= sp_playto)
224                                 say("It's tie! Playing an extra round!");
225                         sp_reset(1)
226                         sp_deal()
227                 }
228         }
229 }
230
231 # Misc
232 BEGIN {
233         cmd = "od -An -N4 -td4 /dev/random"
234         cmd | getline seed
235         close(cmd)
236         srand(seed)
237         sp_init()
238         sp_reset(2)
239 }
240
241 // {
242         sp_valid = (FROM && FROM == sp_player)
243 }
244
245 ! /help/ &&
246 /[Ss]pades/ {
247         say("Spades! " sp_pretty("As,Ah,Ad,Ac", FROM))
248 }
249
250 # Help
251 /^\.help [Ss]pades$/ {
252         say("Spades -- play a game of spades")
253         say("Examples:")
254         say(".newgame [score] -- start a game to <score> points, default 500")
255         say(".endgame -- abort the current game")
256         say(".join -- join the current game")
257         say(".look -- look at your cards")
258         say(".bid n -- bid for <n> tricks")
259         say(".play [card] -- play a card")
260         say(".score -- check the score")
261         say(".tricks -- check how many trick have been taken")
262         say(".bids -- check what everyone bid")
263         next
264 }
265
266 # Debugging
267 FROM == OWNER &&
268 /^\.deal (\w+) (.*)/ {
269         delete sp_hands[$2]
270         for (i=3; i<=NF; i++)
271                 sp_hands[$2][$i] = 1
272         privmsg(sp_channel, FROM " is cheating for " $2)
273 }
274
275
276 # Setup
277 /^\.newgame ?([0-9]+)?$/ {
278         if (sp_state != "new") {
279                 reply("There is already a game in progress.")
280         } else {
281                 sp_owner   = FROM
282                 sp_playto  = $2 ? $2 : 200
283                 sp_state   = "join"
284                 sp_channel = DST
285                 say(sp_owner " starts a game of Spades to " sp_playto "!")
286                 #privmsg("#rhnoise", sp_owner " starts a game of Spades in " DST "!")
287         }
288 }
289
290 (FROM == sp_owner || FROM == OWNER) &&
291 /^\.endgame$/ {
292         if (sp_state == "new") {
293                 reply("There is no game in progress.")
294         } else {
295                 say(FROM " ends the game")
296                 sp_reset(5)
297         }
298 }
299
300 /^\.join$/ {
301         if (sp_state == "new") {
302                 reply("There is no game in progress")
303         }
304         else if (sp_state == "play") {
305                 reply("The game has already started")
306         }
307         else if (sp_state == "join" && FROM in sp_players) {
308                 reply("You are already playing")
309         }
310         else if (sp_state == "join") {
311                 i = sp_next()
312                 sp_order[i] = FROM
313                 sp_players[FROM] = i
314                 say(FROM " joins the game!")
315         }
316         if (sp_state == "join" && sp_turn == 0)
317                 sp_deal()
318 }
319
320 !sp_valid &&
321 (sp_state "bid" || sp_state == "play") &&
322 /^\.(bid|play)\>$/ {
323         if (FROM in sp_players)
324                 say(".slap " FROM ", it is not your turn.")
325         else
326                 say(".slap " FROM ", you are not playing.")
327 }
328
329 sp_valid &&
330 sp_state == "bid" &&
331 /^\.bid [0-9]+$/ {
332         if ($2 < 0 || $2 > 13) {
333                 say("You can only bid from 0 to 13")
334         } else {
335                 i = sp_next()
336                 sp_bids[i] = $2
337                 if ($2 == 0 && !sp_looked[i]) {
338                         say(FROM " goes blind nil!")
339                         sp_nil[i] = 2
340                 } else if ($2 == 0) {
341                         say(FROM " goes nil!")
342                         sp_nil[i] = 1
343                 }
344                 if (sp_turn != sp_dealer) {
345                         say("Bidding goes to " sp_player "!")
346                 } else {
347                         for (p in sp_players)
348                                 privmsg(p, "You have: " sp_hand(p))
349                         sp_state = "play"
350                         for (i=0; i<2; i++) {
351                                 if (sp_nil[i] == 2 || sp_nil[i+2] == 2) {
352                                         say(sp_team(i) ": select a card to pass " \
353                                             "(/msg " NICK " .pass <card>)")
354                                         sp_state = "pass"
355                                 }
356                         }
357                         if (sp_state == "play")
358                                 say("Play starts with " sp_player "!")
359                 }
360         }
361 }
362
363 sp_state == "pass" &&
364 /^\.pass (\S+)$/ {
365         card = $2
366         team = sp_players[FROM] % 2
367         if (!(FROM in sp_players)) {
368                 say(".slap " FROM ", you are not playing.")
369         }
370         else if (sp_nil[team] != 2 && sp_nil[team+2] != 2) {
371                 reply("Your team did not go blind")
372         }
373         else if (sp_pass[sp_players[FROM]]) {
374                 reply("You have already passed a card")
375         }
376         else if (!(card in sp_deck)) {
377                 reply("Invalid card")
378         }
379         else if (!(card in sp_hands[FROM])) {
380                 reply("You do not have that card")
381         }
382         else {
383                 sp_pass[sp_players[FROM]] = $2
384                 privmsg(sp_channel, FROM " passes a card")
385         }
386         if (((sp_nil[0] != 2 && sp_nil[2] != 2) || (sp_pass[0] && sp_pass[2])) &&
387             ((sp_nil[1] != 2 && sp_nil[3] != 2) || (sp_pass[1] && sp_pass[3]))) {
388                 for (i in sp_pass) {
389                         partner = (i+2)%4
390                         card    = sp_pass[i]
391                         delete sp_hands[sp_order[i]][card]
392                         sp_hands[sp_order[partner]][card] = 1
393                 }
394                 privmsg(sp_channel, "Cards have been passed, play starts with " sp_player "!")
395                 for (p in sp_players)
396                         privmsg(p, "You have: " sp_hand(p))
397                 sp_state = "play"
398         }
399 }
400
401 sp_state ~ "(play|bid)" &&
402 /^\.look$/ {
403         if (!(FROM in sp_players)) {
404                 say(".slap " FROM ", you are not playing.")
405         } else {
406                 sp_looked[sp_players[FROM]] = 1
407                 privmsg(FROM, "You have: " sp_hand(FROM))
408         }
409 }
410
411 sp_valid &&
412 sp_state == "play" &&
413 /^\.play (\S+)$/ {
414         card = $2
415         if (!(card in sp_deck)) {
416                 reply("Invalid card")
417         }
418         else if (!(card in sp_hands[FROM])) {
419                 reply("You do not have that card")
420         }
421         else if (sp_suit && card !~ sp_suit && sp_hasa(FROM, sp_suit)) {
422                 reply("You must follow suit (" sp_suit ")")
423         }
424         else if (card ~ /s/ && length(sp_hands[FROM]) == 13 && sp_hasa(FROM, "[^s]$")) {
425                 reply("You cannot trump on the first hand")
426         }
427         else if (card ~ /s/ && length(sp_pile) == 0 && sp_hasa(FROM, "[^s]$") && !sp_broken) {
428                 reply("Spades have not been broken")
429         }
430         else {
431                 sp_play(card)
432                 privmsg(FROM, "You have: " sp_hand(FROM))
433                 if (sp_state == "play") {
434                         if (sp_piles)
435                                 say(sp_player ": it is your turn! " \
436                                     "(" sp_pretty(sp_piles, sp_player) ")")
437                         else
438                                 say(sp_player ": it is your turn!")
439                 }
440         }
441 }
442
443 /^\.bids$/ && sp_state == "play" {
444         say(sp_order[0] " bid " sp_bids[0] ", " \
445             sp_order[2] " bid " sp_bids[2] ", " \
446             "total: " sp_bids[0] + sp_bids[2])
447         say(sp_order[1] " bid " sp_bids[1] ", " \
448             sp_order[3] " bid " sp_bids[3] ", " \
449             "total: " sp_bids[1] + sp_bids[3])
450 }
451
452 /^\.tricks$/ && sp_state == "play" {
453         say(sp_order[0] " took " int(sp_tricks[0]) "/" int(sp_bids[0]) ", " \
454             sp_order[2] " took " int(sp_tricks[2]) "/" int(sp_bids[2]))
455         say(sp_order[1] " took " int(sp_tricks[1]) "/" int(sp_bids[1]) ", " \
456             sp_order[3] " took " int(sp_tricks[3]) "/" int(sp_bids[3]))
457 }
458
459 /^\.(score|status)$/ {
460         if (sp_state == "new") {
461                 say("There is no game in progress")
462         }
463         if (sp_state == "join") {
464                 say("Waiting for palyers: " \
465                     sp_order[0] " " sp_order[1] " " \
466                     sp_order[2] " " sp_order[3])
467         }
468         if (sp_state == "bid" || sp_state == "play") {
469                 say(sp_team(0) ": " \
470                     int(sp_scores[0]) " points, " \
471                     int(sp_bags(0))   " bags")
472                 say(sp_team(1) ": " \
473                     int(sp_scores[1]) " points, " \
474                     int(sp_bags(1))   " bags")
475         }
476 }
477
478 # Standin
479 #/^\.playfor [^ ]*$/ {
480 #}
481 #
482 #/^\.standin [^ ]*$/ {
483 #       if (p in sp_players) {
484 #       }
485 #       for (p in sp_standin) {
486 #               if ($2 in sp_standin) 
487 #               say(here " is already playing for " sp_standin[p]);
488 #       }
489 #       sp_standin[away] = here
490 #}
491 #