Cinquillo, or Spanish Sevens (card game)

Post your working scripts, libraries and tools
User avatar
fincs
Posts: 504
Joined: 30 Sep 2013, 14:17
GitHub: fincs
Location: Seville, Spain
Contact:

Cinquillo, or Spanish Sevens (card game)

16 Feb 2014, 18:11

I was feeling really bored the other day and decided to implement my favourite playing card game in AutoHotkey. For those who don't know:
Wikipedia wrote:Sevens, also known as Laying Out Sevens, Fan Tan, Crazy Sevens or Parliament, is a card game for 3–7 players using a standard deck of 52 cards. Cards are played out to form a layout of sequences going up and down in suit from the sevens (as in many solitaire games). The game is won by emptying one's hand before the other players.
This script is also mainly a showoff of heavy object/OOP usage (including a QuickSort implementation in AHK, really crappy i18n that I quickly hacked in so that some friends who don't speak English could try the script, a functor class that implements comparison with state, some class inheritance with "virtual" methods as well as some really basic AI), and has a terrible user interface consisting of MsgBoxes and InputBoxes (I couldn't be arsed to make a cool GUI, I leave that as an exercise for the reader). So here's the unedited rawness of the script:

Code: Select all

#NoEnv
#NoTrayIcon
#SingleInstance Ignore

global g_allStrings := { _:_
, en: { _:_
	, nameEnter: "Please enter your name:"
	, cpuEnter: "Please enter the number of CPUs:"
	, finish: " -- FINISHES as #"
	, output: " outputted the "
	, pass: " passed"
	, ply_5o: "You have 5 de Oros, therefore you go first"
	, ply_pass: "You cannot play any card, therefore you pass"
	, ply_sel1: "Select a card..."
	, ply_sel2: "Illegal option, please select a valid card..."
	, cancel: "The game is canceled"
	, ranking: "Player ranking" }
, sp: { _:_
	, nameEnter: "Introduzca su nombre:"
	, cpuEnter: "Introduzca el número de CPUs:"
	, finish: " -- TERMINA en el lugar nº"
	, output: " echó el "
	, pass: " pasa"
	, ply_5o: "Tienes el 5 de Oros así que vas primero"
	, ply_pass: "No puedes echar ninguna carta así que pasas"
	, ply_sel1: "Elige una carta..."
	, ply_sel2: "Opción ilegal, elige una carta válida..."
	, cancel: "Juego cancelado"
	, ranking: "Clasificación" } }

if (A_Language & 0xFF) = 0x0a
	lang := "sp"
else
	lang := "en"
global g_strings := g_allStrings[lang]

InputBox, theName,, % g_strings.nameEnter,,,,,,,, %A_UserName%
if ErrorLevel
	ExitApp

InputBox, nPlayers,, % g_strings.cpuEnter,,,,,,,, 1
if ErrorLevel
	ExitApp
nPlayers++

players := []
game := new Cinquillo
winList := []

Random, humanId, 1, %nPlayers%

cpup := 0
Loop, % nPlayers
{
	ply := A_Index = humanId ? new PlayerHuman(game,theName) : new PlayerCPU(game,"CPU #" (++cpup))
	players.Insert(ply)
}

; Deal cards
Loop
{
	curP := Mod(A_Index-1, nPlayers)+1
	card := game.DealCard()
	if !card
		break
	players[curP].ReceiveCard(card)
}

; Play the game
while ObjCount(winList) != nPlayers
{
	curP := players[Mod(A_Index-1, nPlayers)+1]
	if curP.Finished()
		continue
	card := curP.Play()
	if card
	{
		if curP.Finished()
		{
			winList.Insert(curP.name)
			winText := g_strings.finish ObjCount(winList)
		} else
			winText := ""
		MsgBox % curP.name g_strings.output card.ToString() winText "`n`n" game.State()
	} else if !game.Beginning()
		MsgBox % curP.name g_strings.pass "`n`n" game.State()
}

winText := ""
for k,v in winList
	winText .= "#" k " - " v "`n"
MsgBox % g_strings.ranking "`n" winText

class Card
{
	__New(id,suit)
	{
		this.i := id, this.s := suit
	}
	ToString()
	{
		return this.i " de " this.s
	}
	ToString2()
	{
		return this.i SubStr(this.s, 1, 1)
	}
}

class Cinquillo
{
	desk := {}
	d := Deck()
	
	Beginning()
	{
		return !this.desk.Oros
	}
	
	DealCard()
	{
		return ObjRemove(this.d)
	}
	
	PlayCard(card, upd := true)
	{
		if !this.desk.Oros && !(card.i = 5 && card.s = "Oros")
			return false
		if card.i = 5
		{
			if upd
				this.desk[card.s] := { min: 5, max: 5 }
			return true
		} else
		{
			if not x := this.desk[card.s]
				return false
			d := card.i
			if (d < 5) && x.min == (d+1)
			{
				if upd
					x.min := d
				return true
			} else if x.max == (d-1)
			{
				if upd
					x.max := d
				return true
			}
			return false
		}
	}
	
	State()
	{
		for k,x in this.desk
			out .= k ": <" x.min "," x.max ">`n"
		StringTrimRight, out, out, 1
		return out
	}
}

class Player
{
	cards := []
	__New(cinq, name)
	{
		this.g := cinq
		this.name := name
	}
	ReceiveCard(card)
	{
		this.cards.Insert(card)
	}
	Play()
	{
		throw Exception("Pure virtual method called")
	}
	State()
	{
		for _,c in this.cards
			x .= c.ToString2() " "
		StringTrimRight, x, x, 1
		return x
	}
	Finished()
	{
		return !ObjCount(this.cards)
	}
}

class PlayerHuman extends Player
{
	Play()
	{
		if this.Finished()
			return
		if this.g.Beginning()
		{
			for k,c in this.cards
			{
				if (c.i = 5 && c.s = "Oros")
				{
					MsgBox % "(" this.name "): " g_strings.ply_5o
					ObjRemove(this.cards, k)
					this.g.PlayCard(c)
					return c
				}
			}
			return
		}
		
		vv := false
		for k,c in this.cards
			if this.g.PlayCard(c,false)
			{
				vv := true
				break
			}
		if !vv
		{
			MsgBox % "(" this.name "): " g_strings.ply_pass
			return
		}
		
		QuickSort(this.cards, Func("BasicCardComparator"))
		Loop
		{
			ill := A_Index > 1 ? g_strings.ply_sel2 : g_strings.ply_sel1
			prompt := "(" this.name "): " ill "`n" this.State() "`n`n" this.g.State() 
			InputBox, p,, %prompt%,,, 266
			if ErrorLevel
				throw Exception(g_strings.cancel)
			if !RegExMatch(p, "i)^(\d+)([OCBE])$", o)
				continue
			for k,c in this.cards
			{
				if (c.i = o1 && SubStr(c.s,1,1) = o2) 
				{
					if this.g.PlayCard(c)
					{
						ObjRemove(this.cards, k)
						return c
					}
					continue 2
				}
			}
		}
	}
}

BasicCardComparator(c1, c2)
{
	static _ := { Oros: 0, Copas: 1, Bastos: 2, Espadas: 3 }
	c1i := c1.i, c2i := c2.i, c1s := c1.s, c2s := c2.s
	if (c1s != c2s)
		return _[c1s] - _[c2s]
	return c1i-c2i
}

class PlayerCPU extends Player
{
	Play()
	{
		if this.Finished()
			return
		comp := new CardComp(CardPrioInfo(this.cards))
		QuickSort(this.cards, comp)
		for k,c in this.cards
			if this.g.PlayCard(c)
			{
				ObjRemove(this.cards, k)
				return c
			}
		return
	}
}

QuickSort(arr, comp, i := -1, j := -1)
{
	if (i = -1) || (j = -1)
		i := 1, j := ObjCount(arr)+1
	if (j-i) < 2
		return
	; Select pivot
	Random, id, % i, % j-1
	; Dutch flag
	r := DutchFlag(arr, comp, i, j, id)
	; Recursive
	QuickSort(arr, comp, i, r[1])
	QuickSort(arr, comp, r[2], j)
}

DutchFlag(arr, comp, i, j, pivId)
{
	a := i, b := i+1, c := j
	ObjSwap(arr, a, pivId)
	while ((c-b) > 0)
	{
		res := %comp%(arr[b], arr[a])
		if (res < 0)
			ObjSwap(arr, a, b), a++, b++
		else if (res = 0)
			b++
		else
			ObjSwap(arr, b, c-1), c--
	}
	return [a,b]
}

ObjSwap(x,i1,i2)
{
	r := x[i1], x[i1] := x[i2], x[i2] := r
}

ObjCount(x)
{
	r := ObjMaxIndex(x)
	return r ? r : 0
}

class CardComp
{
	__New(relat)
	{
		this.r := relat
	}
	__Call(m, c1, c2)
	{
		r := this.r
		return CardPrio(r,c1) - CardPrio(r,c2)
	}
}

CardPrioInfo(list)
{
	out := { Oros: { max: -1, has5: false, cardC: 0 }
		, Copas: { max: -1, has5: false, cardC: 0 }
		, Bastos: { max: -1, has5: false, cardC: 0 }
		, Espadas: { max: -1, has5: false, cardC: 0 } }
	for _,c in list
	{
		d := out[this.s]
		d.cardC++
		if c.i = 5
			d.has5 := true
		curD := Abs(c.i-5)
		if (curD > d.maxD)
			d.maxD := curD
	}
	return out
}

CardPrio(info, card)
{
	dist := Abs(card.i-5), d := info[card.s]
	return d.has5 ? (dist+d.maxD+d.cardC) : (dist*2+d.cardC)
}

Deck()
{
	ret := {}
	for _,d in ["Oros","Copas","Bastos","Espadas"]
		Loop 12
			ret.Insert(new Card(A_Index,d))
	return ObjShuffle(ret)
}

ObjToString(obj)
{
	for k,v in obj
		x .= "[" k "]: " v.ToString() "`n"
	StringTrimRight, x, x, 1
	return x
}

ObjShuffle(obj)
{
	Loop, % len := ObjMaxIndex(obj)
	{
		i := len-A_Index+1
		Random, j, 1, % i
		x := obj[j], obj[j] := obj[i], obj[i] := x
	}
	return obj
}
fincs
Windows 10 x64 Build 18362 | AMD Ryzen 7 3700X with 32 GB of RAM | AutoHotkey v1.1.31.01
Get SciTE4AutoHotkey v3.0.06.01 - [My project list]

Return to “Scripts and Functions”

Who is online

Users browsing this forum: AHK_user, Antoine, burque505, guest3456 and 47 guests