]> Pileus Git - ~andy/rhawk/commitdiff
save
authorAndy Spencer <andy753421@gmail.com>
Fri, 20 Jan 2012 07:50:53 +0000 (07:50 +0000)
committerAndy Spencer <andy753421@gmail.com>
Fri, 20 Jan 2012 07:50:53 +0000 (07:50 +0000)
.gitignore [new file with mode: 0644]
irc.awk [new file with mode: 0644]
mkfile [new file with mode: 0644]
rhawk [new file with mode: 0755]
spades.awk [new file with mode: 0644]
test.awk [new file with mode: 0644]
test.txt [new file with mode: 0644]
testirc.txt [new file with mode: 0644]
testirc2.txt [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..b25c15b
--- /dev/null
@@ -0,0 +1 @@
+*~
diff --git a/irc.awk b/irc.awk
new file mode 100644 (file)
index 0000000..6fc0c46
--- /dev/null
+++ b/irc.awk
@@ -0,0 +1,133 @@
+# Connect with:
+#   socat 'TCP:localhost:12345' 'EXEC:awk -f $0'
+#
+# Pre-defined variables:
+#
+# When connected:
+#   SERVER:  IRC Server that is connected to
+#   NICK:    Nickname given by the bot
+#   CHANNEL: Channel the bot is currently in
+#
+# When a messages is recieved:
+#   CMD:     Message, e.g. PRIVMSG
+#   SRC:     Source of the message
+#   DST:     Destination of the message, e.g. the channel
+#   TO:      Nickname the message was addressed to
+#   FROM:    Nickname of the user who sent the message
+#   MSG:     Message sent
+#   $0:      Message sans TO
+
+# Debugging
+function send(msg) {
+       #print "  > " msg > "/dev/stderr"
+       print msg
+       #system("sleep 1")
+       fflush()
+}
+
+// {
+       #print ""         > "/dev/stderr"
+       #print "  < " $0  > "/dev/stderr"
+}
+
+function debug(msg) {
+       print "  # " msg > "/dev/stderr"
+       fflush()
+}
+
+function set() {
+       debug("CMD:  " CMD)
+       debug("SRC:  " SRC)
+       debug("DST:  " DST)
+       debug("FROM: " FROM)
+       debug("TO:   " TO)
+       debug("MSG:  " MSG)
+}
+
+# Functions
+function connect(server, nick, channel) {
+       SERVER  = server
+       NICK    = nick
+       CHANNEL = channel
+       if (FIRST) {
+               "whoami"   | getline _name
+               "hostname" | getline _host
+               send("USER " _name " " _host " " server " :" nick)
+               send("NICK " nick)
+       }
+}
+function privmsg(to, msg) {
+       send("PRIVMSG " to " :" msg)
+}
+function say(msg) {
+       if (DST ~ "^#")
+               privmsg(DST, msg)
+       else if (DST == NICK && FROM)
+               privmsg(FROM, msg)
+       else
+               privmsg(CHANNEL, msg)
+}
+
+function reply(msg) {
+       say(FROM ": " msg)
+}
+
+function join(chan) {
+       send("JOIN " chan)
+}
+
+function part(chan) {
+       send("PART " chan)
+}
+
+# Reloading
+BEGIN {
+       if (CHILD == "") {
+               debug("Starting server");
+               status = system("awk -f rhawk -v CHILD=1 -v FIRST=1");
+               while (status)
+                       status = system("awk -f rhawk -v CHILD=1");
+               exit(0);
+       } else {
+               debug("Starting child: CHILD=" CHILD " FIRST=" FIRST);
+       }
+}
+
+function quit() {
+       exit(0)
+}
+
+function reload() {
+       exit(1)
+}
+
+# Input parsing
+// {
+       match($0, /(:([^ ]+) +)?(([A-Z0-9]+) +)(([^ ]+) +)?([^:]*:(.*))/, arr);
+       gsub(/\s+/,     " ", arr[8])
+       gsub(/^ | $/,    "", arr[8])
+       gsub(/\3[0-9]*/, "", arr[8])
+       SRC = arr[2]
+       CMD = arr[4]
+       DST = arr[6]
+       MSG = arr[8]
+
+       match(SRC, /([^! ]+)!/, arr);
+       FROM = arr[1]
+
+       match(MSG, /(([^ :,]*)[:,] *)?(.*)/, arr);
+       TO  = arr[2]
+       $0  = TO == NICK ? arr[3] : MSG
+
+       if (CMD == "PRIVMSG" && DST == NICK && FROM)
+               TO = DST
+}
+
+# IRC client
+CMD == "001" && MSG ~ /Welcome/ {
+       join(CHANNEL)
+}
+
+CMD == "PING" {
+       send("PING " MSG)
+}
diff --git a/mkfile b/mkfile
new file mode 100644 (file)
index 0000000..4f52cb2
--- /dev/null
+++ b/mkfile
@@ -0,0 +1,4 @@
+test:Q:
+       awk -f rhawk < testirc.txt
+       #awk -f rhawk < testirc.txt
+       #awk -f test.awk test.txt | awk -f rhawk #| grep 'points\|bid\|took'
diff --git a/rhawk b/rhawk
new file mode 100755 (executable)
index 0000000..a077499
--- /dev/null
+++ b/rhawk
@@ -0,0 +1,109 @@
+#!awk -f
+
+@include "irc.awk"
+@include "spades.awk"
+
+# Initialization
+BEGIN {
+       OWNER = "andy753421"
+       connect("localhost", "rhawk", "#rhtest");
+}
+
+# Admin
+FROM == OWNER && TO == NICK && /^die in a fire/ {
+       say("Ack, argh, barasdjf..")
+       quit()
+}
+
+FROM == OWNER && TO == NICK && /^reload/ {
+       say("Reloading..")
+       reload()
+}
+
+FROM == OWNER && TO == NICK && /^rejoin/ {
+       reply("joining..")
+       join("#rhnoise")
+       next
+}
+
+FROM == OWNER && TO == NICK && /^(join|part)/ {
+       match(MSG, /(join|part) +(#+\w+)/, arr)
+       if (arr[1] && arr[2]) {
+               send(toupper(arr[1]) " " arr[2]);
+               next
+       }
+}
+
+FROM == OWNER && TO == NICK && /^\.msg/ {
+       match(MSG, /.*\.msg +(#*\w+) +(.*)/, arr)
+       send("PRIVMSG " arr[1] " :" arr[2])
+}
+
+# Kick handling
+CMD == "KICK" {
+       kick_delay = (kick_delay + 2) * 2
+       system("sleep " kick_delay)
+       join(DST)
+       reply("I feel happy!")
+}
+
+# Identify bots
+FROM ~ /bo+t$|rhnoise/ {
+       bots[FROM] = 1
+}
+
+CMD == "NICK" && FROM in bots {
+       bots[MSG] = FROM
+}
+
+
+# Unicode
+/[Uu]nicode :-?\(/ {
+       plain[FROM] = 1
+}
+
+/[Uu]nicode :-?\)/ {
+       plain[FROM] = 0
+}
+
+# Fortune
+TO == NICK && /^/             { extra = ""   }
+TO == NICK && /^.fortune.*-o/ { extra = "-o" }
+TO == NICK && /^.fortune/     {
+       gsub(/.*\.fortune *|-[a-z]* *|[^a-zA-Z0-9 ]/, "", MSG)
+       cmd = "fortune " extra " " (MSG ? "-m '" MSG "'" : "-s")
+       while (cmd | getline _fortune && lines < 5) {
+               say(_fortune)
+               lines++
+       }
+       close(cmd)
+       next
+}
+
+# Noise
+FROM ~ OWNER && /^go go gadget woop/ {
+       for (i=20; i>0; i--)
+               say(".delay " i " seconds; .woop " i)
+}
+
+TO == NICK && DST ~ /^#/ {
+       #say("Hello, " FROM)
+}
+
+!(FROM in bots) &&
+MSG !~ /^\./ &&
+/\<awk\>/ {
+       say("Awk, awk, awk! I'm a bird!")
+}
+
+/^\.help/ {
+       reply("Nothing can help you now..")
+}
+
+/Ho.*Ho.*Ho/ {
+       say("\00309Merry \00304Christmas!")
+}
+
+{ fflush("") }
+
+# vim: ft=awk
diff --git a/spades.awk b/spades.awk
new file mode 100644 (file)
index 0000000..844326a
--- /dev/null
@@ -0,0 +1,491 @@
+# Todo:
+#   - highest bidder leads
+#   - show card after play
+
+# Functions
+function sp_init(cards, tmp0, tmp1)
+{
+       sp_valid    = 0     #     Message sent from sp_player
+       sp_player   = ""    #     Who's turn it is
+       sp_turn     = 0     #     Index of who's turn it is
+       delete sp_hands     # [p] Each players cards
+
+       # Init deck
+       cards ="As Ks Qs Js 10s 9s 8s 7s 6s 5s 4s 3s 2s "\
+              "Ah Kh Qh Jh 10h 9h 8h 7h 6h 5h 4h 3h 2h "\
+              "Ac Kc Qc Jc 10c 9c 8c 7c 6c 5c 4c 3c 2c "\
+              "Ad Kd Qd Jd 10d 9d 8d 7d 6d 5d 4d 3d 2d"
+       split(cards, tmp0)
+       for (i=1; i<=length(tmp0); i++)
+               sp_deck[tmp0[i]] = i
+}
+
+function sp_reset(type)
+{
+       # Per hand
+       if (type >= 0) {
+               sp_suit     = ""    #     The lead suit {s,h,d,c}
+               sp_piles    = ""    # [x] Played cards this turn
+               delete sp_pile      # [x] Played cards this turn
+       }
+
+       # Per round
+       if (type >= 1) {
+               sp_state    = "bid" #     {new,join,bid,pass,play}
+               sp_broken   = 0     #     Whether spades are broken
+               delete sp_looked    # [i] Whether a player has looked a their cards
+               delete sp_bids      # [i] Each players bid
+               delete sp_nil       # [i] Nil multiplier 0=regular, 1=nil, 2=blind
+               delete sp_pass      # [i] Cards to pass
+               delete sp_tricks    # [i] Tricks this round
+       }
+
+       # Per game
+       if (type >= 2) {
+               sp_channel  = ""    #     channel to play in
+               sp_state    = "new" #     {new,join,bid,play}
+               sp_owner    = ""    #     Who started the game
+               sp_playto   = 0     #     Score the game will go to
+               sp_dealer   =-1     #     Who is dealing this round
+               delete sp_players   # [p] Player names players["name"] -> i
+               delete sp_order     # [i] Player order order[i] -> "name"
+               delete sp_scores    # [i] Teams score
+       }
+}
+
+function sp_pretty(cards, who)
+{
+       if (!plain[who]) {
+               gsub(/[0-9JQKA]*[sc]/, "\0031,00\002&\017", cards) # black
+               gsub(/[0-9JQKA]*[hd]/, "\0034,00\002&\017", cards) # red
+               gsub(/s/, "\002♠", cards)
+               gsub(/h/, "\002♥", cards)
+               gsub(/d/, "\002♦", cards)
+               gsub(/c/, "\002♣", cards)
+       }
+       return cards
+}
+
+function sp_next(who, prev)
+{
+       prev      = sp_turn
+       sp_turn   = who ? sp_players[who] : (sp_turn + 1) % 4
+       sp_player = sp_order[sp_turn]
+       return prev
+}
+
+function sp_deal(      shuf)
+{
+       say("/me deals the cards")
+       asorti(sp_deck, shuf, "sp_usort")
+       for (i=1; i<=52; i++)
+               sp_hands[sp_order[i%4]][shuf[i]] = 1
+       sp_state  = "bid"
+       sp_dealer = (sp_dealer+1)%4
+       sp_turn   =  sp_dealer
+       sp_player =  sp_order[sp_turn]
+       say("Bidding starts with " sp_player "!")
+}
+
+function sp_hand(who,  sort, str)
+{
+       asorti(sp_hands[who], sort, "sp_csort")
+       for (i=0; i<length(sort); i++)
+               str = str "" sprintf("%4s", sort[i])
+       gsub(/^ +| +$/, "", str)
+       return sp_pretty(str, who)
+}
+
+function sp_hasa(who, expr)
+{
+       for (c in sp_hands[who]) {
+               if (c ~ expr)
+                       return 1
+       }
+       return 0
+}
+
+function sp_type(card)
+{
+       return substr(card, length(card))
+}
+
+function sp_usort(a,b,c,d) {
+       return rand() - 0.5
+}
+
+function sp_csort(i1,v1,i2,v2) {
+       return sp_deck[i1] > sp_deck[i2] ? +1 :
+              sp_deck[i1] < sp_deck[i2] ? -1 : 0;
+}
+
+function sp_winner(    card, tmp)
+{
+       for (card in sp_pile)
+               if (card !~ sp_suit && card !~ /s/)
+                       delete sp_pile[card]
+       asorti(sp_pile, tmp, "sp_csort")
+       #print "pile: " tmp[1] ">" tmp[2] ">" tmp[3] ">" tmp[4]
+       return tmp[1]
+}
+
+function sp_team(i)
+{
+       #return "{" sp_order[i+0] "," sp_order[i+2] "}"
+       return sp_order[i+0] "/" sp_order[i+2]
+}
+
+function sp_bags(i,    bags)
+{
+       bags = sp_scores[i] % 10
+       if (bags < 0)
+               bags += 10
+       return bags
+}
+
+function sp_score(     bids, tricks)
+{
+       for (i=0; i<2; i++) {
+               bids   = sp_bids[i]   + sp_bids[i+2]
+               tricks = sp_tricks[i] + sp_tricks[i+2]
+               bags   = tricks - bids
+               if (sp_bags(i) + bags > 10) {
+                       say(sp_team(i) " bag out")
+                       sp_scores[i] -= 100
+               }
+               if (tricks >= bids) {
+                       say(sp_team(i) " make their bid")
+                       sp_scores[i] += bids*10 + bags;
+               } else {
+                       say(sp_team(i) " go bust")
+                       sp_scores[i] -= bids*10;
+               }
+       }
+       for (i=0; i<4; i++) {
+               if (!sp_nil[i])
+                       continue
+               say(sp_order[i] " " \
+                   (sp_nil[i] == 1 && !sp_tricks[i] ? "makes nil!"       :
+                    sp_nil[i] == 1 &&  sp_tricks[i] ? "fails at nil!"    :
+                    sp_nil[i] == 2 && !sp_tricks[i] ? "makes blind nil!" :
+                    sp_nil[i] == 2 &&  sp_tricks[i] ? "fails miserably at blind nil!" :
+                                                      "unknown"))
+               sp_scores[i%2] += 100 * sp_nil[i] * \
+                       (sp_tricks[i] == 0 ? 1 : -1)
+       }
+}
+
+function sp_play(card, winner, pi)
+{
+       delete sp_hands[FROM][card]
+       sp_pile[card] = sp_player
+       sp_piles      = sp_piles (sp_piles?",":"") card
+       sp_next()
+
+       if (card ~ /s/)
+               sp_broken = 1
+
+       # Start hand
+       if (length(sp_pile) == 1)
+               sp_suit = sp_type(card)
+
+       # Finish hand
+       if (length(sp_pile) == 4) {
+               winner = sp_winner()
+               pi     = sp_players[sp_pile[winner]]
+               sp_tricks[pi]++
+               say(sp_pile[winner] " wins with " sp_pretty(winner, FROM) \
+                   " (" sp_pretty(sp_piles, FROM) ")")
+               sp_next(sp_pile[winner])
+               sp_reset(0)
+       }
+
+       # Finish round
+       if (sp_tricks[0] + sp_tricks[1] + \
+           sp_tricks[2] + sp_tricks[3] == 13) {
+               say("Round over!")
+               sp_score()
+               if (sp_scores[0] >= sp_playto || sp_scores[1] >= sp_playto &&
+                   sp_scores[0]              != sp_scores[1]) {
+                       say("Game over!")
+                       winner = sp_scores[0] > sp_scores[1] ? 0 : 1
+                       looser = !winner
+                       say(sp_team(winner) " wins the game " \
+                           sp_scores[winner] " to " sp_scores[looser])
+                       say(sp_order[winner+0] "++")
+                       say(sp_order[winner+2] "++")
+                       say(sp_order[looser+0] "--")
+                       say(sp_order[looser+2] "--")
+                       sp_reset(2)
+
+               } else {
+                       if (sp_scores[0] == sp_scores[1] && 
+                           sp_scores[0] >= sp_playto)
+                               say("It's tie! Playing an extra round!");
+                       sp_reset(1)
+                       sp_deal()
+               }
+       }
+}
+
+# Misc
+BEGIN {
+       cmd = "od -An -N4 -td4 /dev/random"
+       cmd | getline seed
+       close(cmd)
+       srand(seed)
+       sp_init()
+       sp_reset(2)
+}
+
+// {
+       sp_valid = (FROM && FROM == sp_player)
+}
+
+! /help/ &&
+/[Ss]pades/ {
+       say("Spades! " sp_pretty("As,Ah,Ad,Ac", FROM))
+}
+
+# Help
+/^\.help [Ss]pades$/ {
+       say("Spades -- play a game of spades")
+       say("Examples:")
+       say(".newgame [score] -- start a game to <score> points, default 500")
+       say(".endgame -- abort the current game")
+       say(".join -- join the current game")
+       say(".look -- look at your cards")
+       say(".bid n -- bid for <n> tricks")
+       say(".play [card] -- play a card")
+       say(".score -- check the score")
+       say(".tricks -- check how many trick have been taken")
+       say(".bids -- check what everyone bid")
+       next
+}
+
+# Debugging
+FROM == OWNER &&
+/^\.deal (\w+) (.*)/ {
+       delete sp_hands[$2]
+       for (i=3; i<=NF; i++)
+               sp_hands[$2][$i] = 1
+       privmsg(sp_channel, FROM " is cheating for " $2)
+}
+
+
+# Setup
+/^\.newgame ?([0-9]+)?$/ {
+       if (sp_state != "new") {
+               reply("There is already a game in progress.")
+       } else {
+               sp_owner   = FROM
+               sp_playto  = $2 ? $2 : 200
+               sp_state   = "join"
+               sp_channel = DST
+               say(sp_owner " starts a game of Spades to " sp_playto "!")
+               #privmsg("#rhnoise", sp_owner " starts a game of Spades in " DST "!")
+       }
+}
+
+(FROM == sp_owner || FROM == OWNER) &&
+/^\.endgame$/ {
+       if (sp_state == "new") {
+               reply("There is no game in progress.")
+       } else {
+               say(FROM " ends the game")
+               sp_reset(5)
+       }
+}
+
+/^\.join$/ {
+       if (sp_state == "new") {
+               reply("There is no game in progress")
+       }
+       else if (sp_state == "play") {
+               reply("The game has already started")
+       }
+       else if (sp_state == "join" && FROM in sp_players) {
+               reply("You are already playing")
+       }
+       else if (sp_state == "join") {
+               i = sp_next()
+               sp_order[i] = FROM
+               sp_players[FROM] = i
+               say(FROM " joins the game!")
+       }
+       if (sp_state == "join" && sp_turn == 0)
+               sp_deal()
+}
+
+!sp_valid &&
+(sp_state "bid" || sp_state == "play") &&
+/^\.(bid|play)\>$/ {
+       if (FROM in sp_players)
+               say(".slap " FROM ", it is not your turn.")
+       else
+               say(".slap " FROM ", you are not playing.")
+}
+
+sp_valid &&
+sp_state == "bid" &&
+/^\.bid [0-9]+$/ {
+       if ($2 < 0 || $2 > 13) {
+               say("You can only bid from 0 to 13")
+       } else {
+               i = sp_next()
+               sp_bids[i] = $2
+               if ($2 == 0 && !sp_looked[i]) {
+                       say(FROM " goes blind nil!")
+                       sp_nil[i] = 2
+               } else if ($2 == 0) {
+                       say(FROM " goes nil!")
+                       sp_nil[i] = 1
+               }
+               if (sp_turn != sp_dealer) {
+                       say("Bidding goes to " sp_player "!")
+               } else {
+                       for (p in sp_players)
+                               privmsg(p, "You have: " sp_hand(p))
+                       sp_state = "play"
+                       for (i=0; i<2; i++) {
+                               if (sp_nil[i] == 2 || sp_nil[i+2] == 2) {
+                                       say(sp_team(i) ": select a card to pass " \
+                                           "(/msg " NICK " .pass <card>)")
+                                       sp_state = "pass"
+                               }
+                       }
+                       if (sp_state == "play")
+                               say("Play starts with " sp_player "!")
+               }
+       }
+}
+
+sp_state == "pass" &&
+/^\.pass (\S+)$/ {
+       card = $2
+       team = sp_players[FROM] % 2
+       if (!(FROM in sp_players)) {
+               say(".slap " FROM ", you are not playing.")
+       }
+       else if (sp_nil[team] != 2 && sp_nil[team+2] != 2) {
+               reply("Your team did not go blind")
+       }
+       else if (sp_pass[sp_players[FROM]]) {
+               reply("You have already passed a card")
+       }
+       else if (!(card in sp_deck)) {
+               reply("Invalid card")
+       }
+       else if (!(card in sp_hands[FROM])) {
+               reply("You do not have that card")
+       }
+       else {
+               sp_pass[sp_players[FROM]] = $2
+               privmsg(sp_channel, FROM " passes a card")
+       }
+       if (((sp_nil[0] != 2 && sp_nil[2] != 2) || (sp_pass[0] && sp_pass[2])) &&
+           ((sp_nil[1] != 2 && sp_nil[3] != 2) || (sp_pass[1] && sp_pass[3]))) {
+               for (i in sp_pass) {
+                       partner = (i+2)%4
+                       card    = sp_pass[i]
+                       delete sp_hands[sp_order[i]][card]
+                       sp_hands[sp_order[partner]][card] = 1
+               }
+               privmsg(sp_channel, "Cards have been passed, play starts with " sp_player "!")
+               for (p in sp_players)
+                       privmsg(p, "You have: " sp_hand(p))
+               sp_state = "play"
+       }
+}
+
+sp_state ~ "(play|bid)" &&
+/^\.look$/ {
+       if (!(FROM in sp_players)) {
+               say(".slap " FROM ", you are not playing.")
+       } else {
+               sp_looked[sp_players[FROM]] = 1
+               privmsg(FROM, "You have: " sp_hand(FROM))
+       }
+}
+
+sp_valid &&
+sp_state == "play" &&
+/^\.play (\S+)$/ {
+       card = $2
+       if (!(card in sp_deck)) {
+               reply("Invalid card")
+       }
+       else if (!(card in sp_hands[FROM])) {
+               reply("You do not have that card")
+       }
+       else if (sp_suit && card !~ sp_suit && sp_hasa(FROM, sp_suit)) {
+               reply("You must follow suit (" sp_suit ")")
+       }
+       else if (card ~ /s/ && length(sp_hands[FROM]) == 13 && sp_hasa(FROM, "[^s]$")) {
+               reply("You cannot trump on the first hand")
+       }
+       else if (card ~ /s/ && length(sp_pile) == 0 && sp_hasa(FROM, "[^s]$") && !sp_broken) {
+               reply("Spades have not been broken")
+       }
+       else {
+               sp_play(card)
+               privmsg(FROM, "You have: " sp_hand(FROM))
+               if (sp_state == "play") {
+                       if (sp_piles)
+                               say(sp_player ": it is your turn! " \
+                                   "(" sp_pretty(sp_piles, sp_player) ")")
+                       else
+                               say(sp_player ": it is your turn!")
+               }
+       }
+}
+
+/^\.bids$/ && sp_state == "play" {
+       say(sp_order[0] " bid " sp_bids[0] ", " \
+           sp_order[2] " bid " sp_bids[2] ", " \
+           "total: " sp_bids[0] + sp_bids[2])
+       say(sp_order[1] " bid " sp_bids[1] ", " \
+           sp_order[3] " bid " sp_bids[3] ", " \
+           "total: " sp_bids[1] + sp_bids[3])
+}
+
+/^\.tricks$/ && sp_state == "play" {
+       say(sp_order[0] " took " int(sp_tricks[0]) "/" int(sp_bids[0]) ", " \
+           sp_order[2] " took " int(sp_tricks[2]) "/" int(sp_bids[2]))
+       say(sp_order[1] " took " int(sp_tricks[1]) "/" int(sp_bids[1]) ", " \
+           sp_order[3] " took " int(sp_tricks[3]) "/" int(sp_bids[3]))
+}
+
+/^\.(score|status)$/ {
+       if (sp_state == "new") {
+               say("There is no game in progress")
+       }
+       if (sp_state == "join") {
+               say("Waiting for palyers: " \
+                   sp_order[0] " " sp_order[1] " " \
+                   sp_order[2] " " sp_order[3])
+       }
+       if (sp_state == "bid" || sp_state == "play") {
+               say(sp_team(0) ": " \
+                   int(sp_scores[0]) " points, " \
+                   int(sp_bags(0))   " bags")
+               say(sp_team(1) ": " \
+                   int(sp_scores[1]) " points, " \
+                   int(sp_bags(1))   " bags")
+       }
+}
+
+# Standin
+#/^\.playfor [^ ]*$/ {
+#}
+#
+#/^\.standin [^ ]*$/ {
+#      if (p in sp_players) {
+#      }
+#      for (p in sp_standin) {
+#              if ($2 in sp_standin) 
+#              say(here " is already playing for " sp_standin[p]);
+#      }
+#      sp_standin[away] = here
+#}
+#
diff --git a/test.awk b/test.awk
new file mode 100644 (file)
index 0000000..8210626
--- /dev/null
+++ b/test.awk
@@ -0,0 +1,68 @@
+# Functions
+function say(who, msg)
+{
+       print ":" who "! PRIVMSG #rhtest :" msg
+}
+
+function error(msg)
+{
+       print "error: " msg > "/dev/stderr"
+}
+
+function debug(msg)
+{
+       print msg > "/dev/stderr"
+}
+
+function command(who, cmd)
+{
+       arg=cmd
+       gsub(/\<[nbp]|[+-]/, "", arg)
+       if      (cmd ~ /^\./)     0 # nop
+       else if (cmd ~ /^n/)      say(who, ".newgame " arg)
+       else if (cmd ~ /^e/)      say(who, ".endgame ")
+       else if (cmd ~ /^j/)      say(who, ".join")
+       else if (cmd ~ /^d/)      say("andy753421", ".deal " who " " hand[who])
+       else if (cmd ~ /^l/)      say(who, ".look")
+       else if (cmd ~ /^b/)      say(who, ".bid "  arg)
+       else if (cmd ~ /^s/)      say(who, ".score")
+       else if (cmd ~ /^B/)      say(who, ".bids")
+       else if (cmd ~ /^t/)      say(who, ".tricks")
+       else if (cmd ~ /^p/)      say(who, ".pass " arg)
+       else if (arg ~ /[shdc]$/) say(who, ".play " arg)
+       else                      error("unknown cmd '" cmd "'")
+}
+
+function reset()
+{
+       nturns = 0
+       delete players # players[i]   -> "name"
+       delete turns   # turns[pi][i] -> "cmd"
+}
+
+# Rules
+BEGIN { reset() }
+
+//  { gsub(/#.*/, "") }
+
+/^[^ ]+:/ {
+       gsub(/:/, "")
+       pi = length(players)
+       if (NF-2 > nturns)
+               nturns = NF-1
+       for (i=2; i<=NF; i++)
+               turns[pi][i-2] = $i
+       players[pi] = $1
+       hand[$1]    = $0
+       gsub(/^\w*|[nbp-]\w+|\<[nejlbsBtpd]\>|[.+]/, "", hand[$1])
+       gsub(/^ */, "", hand[$1])
+       print $1 ": " hand[$1] > "/dev/stderr"
+       say($1, "unicode :(")
+}
+
+/^\s*$/ {
+       for (ti=0; ti<nturns; ti++)
+               for (pi=0; pi<length(players); pi++)
+                       command(players[pi], turns[pi][ti])
+       reset()
+}
diff --git a/test.txt b/test.txt
new file mode 100644 (file)
index 0000000..2b70101
--- /dev/null
+++ b/test.txt
@@ -0,0 +1,46 @@
+# Deck:
+#   As Ks Qs Js 10s 9s 8s 7s 6s 5s 4s 3s 2s
+#   Ah Kh Qh Jh 10h 9h 8h 7h 6h 5h 4h 3h 2h
+#   Ad Kd Qd Jd 10d 9d 8d 7d 6d 5d 4d 3d 2d
+#   Ac Kc Qc Jc 10c 9c 8c 7c 6c 5c 4c 3c 2c
+#
+# Format:
+#   # Comments
+#   player1: commands ..
+#   player2: commands ..
+#   playern: commands ..
+#
+# Commands:
+#   .    - nop
+#   nn   - newgame to n
+#   e    - endgame
+#   j    - join
+#   l    - look
+#   bn   - bid n
+#   s    - score
+#   card - play card
+#  -card - play but don't add to deck
+#  +card - add to deck but don't play
+
+# Test scoring
+#x: s s s s s s s  s  s  s  s  s
+#x: B B B B B B B  B  B  B  B  B
+#x: t t t t t t t  t  t  t  t  t
+#A: n j . . . d b3 .  .  .  As .
+#b: . . j . . d .  b3 .  .  Ah .
+#c: . . . j . d .  .  b3 .  Ad .
+#d: . . . . j d .  .  .  b3 Ac .
+
+# Nil/blind
+a: n j d l b0  .  .    As Ks Qs Js 10s 9s 8s 7s 6s 5s 4s 3s 2s t e
+b: . j d . b0 +Ah pAh -Ac Kh Qh Jh 10h 9h 8h 7h 6h 5h 4h 3h 2h s
+c: . j d . b7  .  .    Ad Kd Qd Jd 10d 9d 8d 7d 6d 5d 4d 3d 2d .
+d: . j d . b7 +Ac pAc -Ah Kc Qc Jc 10c 9c 8c 7c 6c 5c 4c 3c 2c .
+
+# Test full game
+#a: n j d . b1  +Ah As Ks Qs Js 10s 9s 8s 7s 6s 5s 4s 3s 2s t
+#b: . j d . b3   .  Ah Kh Qh Jh 10h 9h 8h 7h 6h 5h 4h 3h 2h s
+#c: . j d . b1   .  Ad Kd Qd Jd 10d 9d 8d 7d 6d 5d 4d 3d 2d .
+#d: . j d . b0   .  Ac Kc Qc Jc 10c 9c 8c 7c 6c 5c 4c 3c 2c .
+
+# vim: ft=conf
diff --git a/testirc.txt b/testirc.txt
new file mode 100644 (file)
index 0000000..88662c5
--- /dev/null
@@ -0,0 +1,5 @@
+:andy753421!~a@c.d PRIVMSG #rhnoise :spades
+:andy753421!~a@c.d PRIVMSG #rhnoise :unicode :)
+:andy753421!~a@c.d PRIVMSG #rhnoise :spades
+:andy753421!~a@c.d PRIVMSG #rhnoise :unicode :(
+:andy753421!~a@c.d PRIVMSG #rhnoise :spades
diff --git a/testirc2.txt b/testirc2.txt
new file mode 100644 (file)
index 0000000..97351e5
--- /dev/null
@@ -0,0 +1,40 @@
+:andy!a@c.d PRIVMSG #rhnoise :test
+:andy!a@c.d PRIVMSG #rhnoise :unicode :)
+:andy!a@c.d PRIVMSG #rhnoise :test
+:andy!a@c.d PRIVMSG #rhnoise :unicode :(
+:andy!a@c.d PRIVMSG #rhnoise :test
+
+:andy!a@c.d PRIVMSG #rhnoise :#.help
+:andy!a@c.d PRIVMSG #rhnoise :#.help spades
+
+:andy!a@c.d PRIVMSG #rhnoise :#.join
+:andy!a@c.d PRIVMSG #rhnoise :#.newgame 123
+:andy!a@c.d PRIVMSG #rhnoise :#.newgame
+:andy!a@c.d PRIVMSG #rhnoise :# .newgame   123  
+
+:a!a@c.d PRIVMSG #rhnoise :#.join
+:a!a@c.d PRIVMSG #rhnoise :#.join
+:b!a@c.d PRIVMSG #rhnoise :#.join
+:c!a@c.d PRIVMSG #rhnoise :#.join
+:d!a@c.d PRIVMSG #rhnoise :#.join
+
+:a!a@c.d PRIVMSG #rhnoise :#.look
+
+:a!a@c.d PRIVMSG #rhnoise :#.bid 1
+:b!a@c.d PRIVMSG #rhnoise :#.bid 2
+:c!a@c.d PRIVMSG #rhnoise :#.bid 3
+:d!a@c.d PRIVMSG #rhnoise :#.bid 4
+
+:a!a@c.d PRIVMSG #rhnoise :#.play Ah
+:b!a@c.d PRIVMSG #rhnoise :#.play Kh
+:c!a@c.d PRIVMSG #rhnoise :#.play Qh
+:d!a@c.d PRIVMSG #rhnoise :#.play 5s
+:d!a@c.d PRIVMSG #rhnoise :#.play Ad
+
+:a!a@c.d PRIVMSG #rhnoise :#.play 9d
+:b!a@c.d PRIVMSG #rhnoise :##.play 4c
+:b!a@c.d PRIVMSG #rhnoise :#.play 9s
+:c!a@c.d PRIVMSG #rhnoise :#.play 6d
+:d!a@c.d PRIVMSG #rhnoise :#.play 7d
+
+:a!a@c.d PRIVMSG #rhnoise :#.play 5s