UNO-pseudo-AI
One of the IRC networks I hang out on has an IRC bot on it using the script. I wrote a script to play the game on my behalf in a highly efficient manner. Esentially, it`ll avoid playing certain cards if it knows it can map and link them to other cards.
; UNO pseudo "AI" (Extended) v2 - For playing against: http://hawkee.com/snippet/5301/
#UNO on
; Settings
alias -l UNO.bot { return UNO }
alias -l UNO.chan { return #UNO }
alias -l UNO.network { return UNO }
; Definitions
alias -l UNO.deal.time { return 20 }
; `-> Seconds before (re)dealing.
alias -l UNO.enum { return B,G,R,Y }
alias -l UNO.hand { return UNO.Hand }
; `-> Hash table only.
alias -l UNO.limit.join { return 0 }
; `-> Only join games that'll have a certain number of players.
alias -l UNO.limit.to { return $iif($hget(UNO,JoinMin),$v1,3) }
; `-> Basically, only join if there are already X players.
alias -l UNO.limit.rand { return 1 }
; `-> Set a random JoinMin number. 0: no | 1: yes
alias -l UNO.random { return 0 }
; `-> Random attack pattern. 0: no | 1: yes
alias -l UNO.skip.enum { return DT,S }
; `-> Technically R (currently broken) should be here if there's only two players since it functions as another S.
alias -l UNO.sort { return 1 }
; `-> Sort the cards. E.g. DT 9 3 S R 4 -> DT S R 3 4 9 - 0: no | 1: yes
; Commands
alias -l UNO.deal { return !deal }
alias -l UNO.draw { return !draw }
alias -l UNO.join { return !join }
alias -l UNO.pass { return !pass }
alias -l UNO.play { return !play }
on *:disconnect:{ uno_stop }
on me:*:kick:#:{
if (($chan == $UNO.chan) && ($network == $UNO.network)) { uno_stop }
}
on *:notice:*:?:{
if (($nick == $UNO.bot) && ($network == $UNO.network)) {
hadd -m UNO str $remove($1-,,)
tokenize 32 $strip($1-)
if (Type $UNO.deal to start the game* iswm $1-) {
if ($hget(UNO,Count) >= 2) { .timeruno.deal 1 $UNO.deal.time msg $UNO.chan $UNO.deal }
else { uno_attempt_redeal }
; `-> Wait until another player joins before dealing.
}
if (You drew:* iswm $1-) {
uno_add_card $gettok($hget(UNO,str),3,32)
.timeruno.uno_play_hand_b 1 3 uno_play_hand b $hget(UNO,Top)
}
if (Your cards:* iswm $1-) { uno_set_hand $gettok($hget(UNO,str),3-,32) }
; `-> Destroy and set the hash table each time. It's a hacky way of doing it; but it works.
}
}
on me:*:part:#:{
if (($chan == $UNO.chan) && ($network == $UNO.network)) { uno_stop }
}
on *:text:*:#:{
if (($nick == $UNO.bot) && ($network == $UNO.network)) {
hadd -m UNO str $remove($1-,,)
tokenize 32 $strip($1-)
if ((Congratulations*you win!!! iswm $1-) || (Game ended* iswm $1-) || ($me has been kicked from the game* iswm $1-)) { uno_stop }
if (*has started UNO* iswm $1-) {
if (!$istok($1-,$me,32)) {
if ($UNO.limit.join == 0) { uno_join_game }
else {
if (($UNO.limit.rand == 1) && (!$hget(UNO,JoinMin))) { hadd -m UNO JoinMin $rand(3,5) }
}
}
; `-> Just to stop joining games I've started.
}
if (* $+ $+($me,'s) turn* iswm $1-) {
if ($1 != Its) { .timeruno.uno_play_hand_a 1 3 uno_play_hand_a }
; `-> Just ignore @count...
}
if (Top card:* iswm $1-) { hadd -m UNO Top $gettok($hget(UNO,str),3,32) }
if (*will be player* iswm $1-) {
hadd -m UNO Count $left($4,-1)
if (!$istok($1-,$me,32)) {
if ((!$hget(UNO,Joined)) && ($UNO.limit.join == 1) && ($hget(UNO,Count) >= $UNO.limit.to)) { uno_join_game }
}
else { hadd -m UNO Joined 1 }
}
}
}
on *:unload:{ uno_stop }
alias -l color_as_long { return blue B,green G,red R,yellow Y }
alias -l color_to_card { return $gettok($matchtok(01 W¦12 B¦09 G¦04 R¦08 Y,$1,1,166),2,32) }
alias -l comma { return $chr(44) }
alias -l count_no_skips {
if ($istok($UNO.skip.enum,R,44)) {
if ($hget(UNO,Count) > 2) { var %R = 0 }
; `-> R is useless for skipping with more than two players.
else { var %R = $count($hget($UNO.Hand,$1),R) }
}
else { var %R = 0 }
return $calc($numtok($hget($UNO.Hand,$1),32) - ($count($hget($UNO.Hand,$1),DT) + %R + $count($hget($UNO.Hand,$1),S)))
}
alias -l deck_check {
; $deck_check(<card>,<excluded color>)
var %thisCard = $1, %thisColor = $2
return $calc($left($regsubex($str(.,3),/./g,$iif($istok($hget($UNO.Hand,$gettok($remtok($UNO.enum,%thisColor,44),\n,44)),%thisCard,32),1+,0+)),-1))
}
alias -l have_card {
; $have_card(<card>,<excluded color>)
var %thisCard = $1, %thisColor = $2
return $left($regsubex($str(.,3),/./g,$iif($istok($hget($UNO.Hand,$gettok($remtok($UNO.enum,%thisColor,44),\n,44)),%thisCard,32),$+($gettok($remtok($UNO.enum,%thisColor,44),\n,44),$comma))),-1)
}
alias -l least_cards_by_color {
var %thisEnum = $iif($1,$v1,$UNO.enum), %thisResult = $regsubex($str(.,$numtok(%thisEnum,44)),/./g,$+($numtok($hget($UNO.Hand,$gettok(%thisEnum,\n,44)),32),¦))
return $iif($gettok(%thisEnum,$findtok(%thisResult,$gettok($sorttok(%thisResult,166,n),1,166),1,166),44),$v1,$randtok($UNO.enum,44))
}
alias -l most_cards_by_color {
var %thisEnum = $iif($1,$v1,$UNO.enum), %thisResult = $regsubex($str(.,$numtok(%thisEnum,44)),/./g,$+($numtok($hget($UNO.Hand,$gettok(%thisEnum,\n,44)),32),¦))
return $iif($gettok(%thisEnum,$findtok(%thisResult,$gettok($sorttok(%thisResult,166,nr),1,166),1,166),44),$v1,$randtok($UNO.enum,44))
}
; `-> Pick a random color on the off chance something goes wrong. (E.g. A hand full of wilds.)
alias -l randtok { return $gettok($1,$rand(1,$numtok($1,$iif($2,$v1,32))),$iif($2,$v1,32)) }
alias -l unotok { return $str($+($chr(32),R),$count($1,R)) $sorttok($remtok($1,R,0,$2),$2,$3) }
; `-> Since R is broken, place it in a way which won't fuck up mapping. (Either at the start or at the end.)
alias -l uno_add_card {
; /uno_add_card <card>
tokenize 32 $base($gettok($remove($1,,),1,91),10,10,2) $left($gettok($remove($1,,),2,91),-1)
hadd -m $UNO.Hand $color_to_card($1) $iif($UNO.sort == 1,$iif($hget(UNO,Count) > 2,$sorttok($2 $hget($UNO.Hand,$color_to_card($1)),32,n),$unotok($2 $hget($UNO.Hand,$color_to_card($1)),32,n)),$2 $hget($UNO.Hand,$color_to_card($1)))
}
alias -l uno_attempt_redeal {
if ($hget(UNO,Count) >= 2) { msg $UNO.chan $UNO.deal }
else { .timeruno.deal 1 $UNO.deal.time uno_attempt_redeal }
}
alias -l uno_join_game { .timeruno.join 1 $rand(2,8) msg $UNO.chan $UNO.join }
alias -l uno_play_hand {
; /uno_play_hand <a|b> <top card>
; `-> a: 1st try (pre draw) | b: 2nd try (post draw)
var %thisTurn = $1
tokenize 32 $base($gettok($remove($2,,),1,91),10,10,2) $left($gettok($remove($2,,),2,91),-1)
var %thisColor = $color_to_card($1)
if ($hget($UNO.Hand,%thisColor)) {
; `-> Color match.
var %thisList = $v1
var %thisResult = $regsubex($str(.,$numtok(%thisList,32)),/./g,$+($deck_check($gettok(%thisList,\n,32),%thisColor),¦))
; `-> Now check if the card is elsewhere in my hand - regardless of color - then play a card depending on how little I have.
if ($findtok(%thisResult,0,$iif($UNO.random == 1,$rand(1,$iif($count(%thisResult,0) > 1,$v1,1)),1),166)) { var %thisToken = $v1 | goto uno_play_card }
if ($findtok(%thisResult,1,$iif($UNO.random == 1,$rand(1,$iif($count(%thisResult,1) > 1,$v1,1)),1),166)) { var %thisToken = $v1 | goto uno_play_card }
if ($findtok(%thisResult,2,$iif($UNO.random == 1,$rand(1,$iif($count(%thisResult,2) > 1,$v1,1)),1),166)) { var %thisToken = $v1 | goto uno_play_card }
if ($findtok(%thisResult,3,$iif($UNO.random == 1,$rand(1,$iif($count(%thisResult,3) > 1,$v1,1)),1),166)) { var %thisToken = $v1 | goto uno_play_card }
; |- Try and condense this code down into less lines if possible.
; `-> 0 = zero matches elsewhere. 1 = one match elsewhere. 2 = etc. 3 = etc.
:uno_play_card
msg $UNO.chan $UNO.play %thisColor $gettok($hget($UNO.Hand,%thisColor),%thisToken,32)
goto uno_end_turn
}
else {
; `-> We don't have any cards that match this color. Do we have a matching type instead? E.g. R S->B S
if ($have_card($2,%thisColor)) {
var %thisHave = $v1
if (($istok($UNO.skip.enum,$2,44)) && ($numtok($have_card($2,%thisColor),44) > 1)) {
; |- Note: Untested.
; `-> Make sure it plays the color of the least amount of cards first assuming that COLOR->(NUMBER_OF_CARDS - (NUMBER_OF_DT + NUMBER_OF_R + NUMBER_OF_S)) = 0; otherwise, play the most.
var %thisResult = $regsubex($str(.,$numtok(%thisHave,44)),/./g,$+($gettok(%thisHave,\n,44),:,$numtok($hget($UNO.Hand,$gettok(%thisHave,\n,44)),32),=,$count_no_skips($gettok(%thisHave,\n,44)),¦))
var %thisMatch = $gettok($matchtok(%thisResult,=0,$rand(1,$matchtok(%thisResult,=0,0,166)),166),1,58)
; |-> Not random. E.g. G:4=3¦R:2=0¦Y:1=0 means it'll pick R first rather than yellow. (Or R rather than R or Y.)
; `---^-> Ignore this; it should now be random.
msg $UNO.chan $UNO.play $iif(%thisMatch,$v1,$least_cards_by_color($have_card($2,%thisColor))) $2
goto uno_end_turn
}
msg $UNO.chan $UNO.play $most_cards_by_color($have_card($2,%thisColor)) $2
goto uno_end_turn
}
else {
; `-> No matching type either. Oh well, play a W(D4) instead.
if ($hget($UNO.Hand,W)) {
; var %UNO.wild.hand = $v1
; msg $UNO.chan $UNO.play $iif($UNO.random == 1,$randtok(%UNO.wild.hand,32),$gettok($sorttok(%UNO.wild.hand,32,ar),1,32)) $most_cards_by_color
msg $UNO.chan $UNO.play $gettok($sorttok($v1,32,ar),1,32) $most_cards_by_color
; `-> WD4 always trumps W.
goto uno_end_turn
}
}
}
msg $UNO.chan $iif(%thisTurn == a,$UNO.draw,$UNO.pass)
:uno_end_turn
}
alias -l uno_play_hand_a { uno_play_hand a $hget(UNO,Top) }
; `-> I have to call this separately due to the fact the top card will be incorrectly registered otherwise.
alias -l uno_set_hand {
; /uno_set_hand <hand ...>
if ($hget($UNO.Hand)) { hfree $v1 }
tokenize 32 $remove($1-,,)
uno_add_card $*
}
alias uno_stop {
; /uno_stop
.timeruno.* off
if ($hget(UNO)) { hfree $v1 }
if ($hget($UNO.Hand)) { hfree $v1 }
}
#UNO end
; EOF