AutoHotkey

Go Fish/AutoHotkey is part of Go Fish. You may find other members of Go Fish at Category:Go Fish.

Works with: AutoHotkey L

Unicode

Easily expanded to multiple players, any combination of human and AI.

#NoEnv
SetBatchLines -1
#SingleInstance Force

Global Suits:={1:"♣",2:"♦",3:"♠",4:"♥"}
Global Pips:={13:"A",1:"2",2:"3",3:"4",4:"5",5:"6",6:"7",7:"8",8:"9",9:"T",10:"J",11:"Q",12:"K"}
Deck:=new Deck
Deck.Shuffle()
Player1:=new Human
Player0:=new AI
i:=!i ; current player
o:=!i ; current opponent
While (Player%i%.Archive() || Player1.Hand.HasCards() || Player0.Hand.HasCards())
{
	If (!Player%i%.Hand.HasCards()) ; if hand is empty,
		Player%i%.Hand.Draw() ; draw 1
	pip := Player%i%.Choose(o) ; player's request
	If pip not in % Player%i%.Pips() ; if invalid,
		Continue ; try again
	If (Player%i%.Take(pip, o)) ; if received what requested from opponent,
		Continue ; go again
	If (Player%i%.Hand.Draw().Pip = pip) ; if obtained what requested from pond,
		Continue ; go again
	i:=!i  ; current player
	o:=!i ; current opponent
}
Msgbox % "Human:  " Player1.Book.Show() 
	 . "`nAI:     " Player0.Book.Show() 
	 . "`nWinner: " (Player1.Book.HasCards() > Player0.Book.HasCards() ? "Human" : "AI")
Return ;--------------------------------------------------------------------------------

class Card
{
	__New(Pip, Suit) {
		this.Pip:=Pip, this.Suit:=Suit
	}
	Show() {
		Return this.Pip . this.Suit
	}
}
class Cards
{
	Cards:=[]
	
	HasCards() {
		Return this.Cards.MaxIndex()
	}
	Shuffle() {	; Knuth Shuffle from http://rosettacode.org/wiki/Knuth_Shuffle#AutoHotkey
		Loop % this.HasCards()-1 {
			Random, i, A_Index, this.HasCards()	; swap item 1,2... with a random item to the right of it
			temp := this.Cards[i], this.Cards[i] := this.Cards[A_Index], this.Cards[A_Index] := temp
		}
	}
	Show(Sort=0) {
		For i, Card in this.Cards
			s .= Card.Show() ","
		s:=SubStr(s,1,-1)
		If Sort
			Sort, s, D,
		Return s
	}
}
class Deck extends Cards
{
	__New() {
		For i, Pip in Pips
			For j, Suit in Suits
				this.Cards.Insert(new Card(Pip,Suit))
	}
}
class Hand extends Cards
{
	lastDraw := ""
	__New() {
		this.Draw(9)
	}
	Draw(n=1) {
		Loop, %n%
			If (Deck.HasCards())
				this.Cards.Insert(temp:=Deck.Cards.Remove(1)) ; to deal from bottom, use Remove()
		lastDraw := temp.Pip ","
		Return temp.Pip
	}
}
class Player
{
	Hand := new Hand
	Book := new Cards
	oRequests := "" ; log pips requested by opponent here
	
	Pip() {
		For i, Card in this.Hand.Cards
			s .= Card.Pip ","
		s:=SubStr(s,1,-1)
		Sort, s, D,
		Return s
	}
	Pips() {
		s := this.Pip()
		Sort, s, UD,
		Return s
	}
	Take(Pip, o) {
		Player%o%.oRequests := Pip "," Player%o%.oRequests
		For i, Card in Player%o%.Hand.Cards
			If (Card.Pip = Pip)
				this.Hand.Cards.Insert(Card)
				, toremove .= i ","
		toremove:=SubStr(toremove,1,-1)
		Sort, toremove, RND, ; must remove in reverse order
		Loop, Parse, toremove, `, ; so as not to mess up loop
			Player%o%.Hand.Cards.Remove(A_LoopField)
		Return toremove ? 1 : 0
	}
	Archive() {
		s := this.Pip()
		Loop, Parse, s, `,
		{
			If (A_LoopField = previous)
				count++
			Else
				count := 1
			If (count = Suits.MaxIndex()) ; normally 4
			{
				toarchive := A_LoopField
				Break
			}
			previous := A_LoopField
		}
		If (toarchive)
		{
			For i, Card in this.Hand.Cards
				If (Card.Pip = toarchive)
					this.Book.Cards.Insert(Card)
					, toremove .= i ","
			toremove:=SubStr(toremove,1,-1)
			Sort, toremove, RND, ; must remove in reverse order
			Loop, Parse, toremove, `, ; so as not to mess up loop
				this.Hand.Cards.Remove(A_LoopField)
		}
		Return 0
	}
}
class Human extends Player
{
	Choose(o) {
		InputBox, Pip, Enter a rank to request from opponent.
			, % "Your cards: " this.Hand.Show(1) 
			. "`nYour books: " this.Book.Show(1) 
			. "`nAI's books: " Player%o%.Book.Show(1) 
			. "`nEnter rank: " this.Pips()
			, , 900
		Return Pip
	}
}
class AI extends Player
{
	Choose(o) {
		Static Pip ; last asked
		s := this.Pip()
		Loop, Parse, s, `,
		{
			If (A_LoopField = previous)
				count++
			Else
				count := 1
			Random, rand, 0, 2
			If (count > oldcount - rand//2 && count < Suits.MaxIndex())
				oldcount := count
				, IHaveMostOf := A_LoopField
			previous := A_LoopField
		}
		ChoicePrefs := this.oRequests . this.Hand.lastDraw . IHaveMostOf . "," . s
		this.Hand.lastDraw := ""
		Loop, Parse, ChoicePrefs, `,
			If A_LoopField in % this.Pips()
				If (A_LoopField != Pip) ; don't repeat last asked
				{
					Pip := A_LoopField
					Break
				}
		Msgbox, , Opponent requests a rank from you.
			, % "Your cards: " Player%o%.Hand.Show(1) 
			. "`nYour books: " Player%o%.Book.Show(1) 
			. "`nAI's books: " this.Book.Show(1) 
			. "`n`n`nRequested rank: " Pip
		Return Pip
	}
}