⚠️ Warning: This is a draft ⚠️
This means it might contain formatting issues, incorrect code, conceptual problems, or other severe issues.
If you want to help to improve and eventually enable this page, please fork RosettaGit's repository and open a merge request on GitHub.
{{collection|Go Fish}} If 'computer' is entered for the player's name, it will play itself.
#MaxCards = 52 ;Max #of cards possible in a card collection
#ShortCardRanks$ = "2;3;4;5;6;7;8;9;10;J;Q;K;A"
#LongCardRanks$ = "deuce;three;four;five;six;seven;eight;nine;ten;jack;queen;king;ace"
#CardRankArticles$ = "a;a;a;a;a;a;an;a;a;a;a;a;an"
#CardRankPlurals$ = "s;s;s;s;es;s;s;s;s;s;s;s;s"
#NumCardRanks = 13
#MaxHistorySize = 4 ;must be 0 < #MaxHistorySize <= #NumCardRanks
#Indent$ = " "
Structure _membersCardCollectionClass
*vtable.i
size.i ;# of cards present
card.i[#MaxCards] ;collection content, stores rank# for each card, suits aren't used
EndStructure
Interface CardCollectionObj
init(isDeck = #False)
count()
countMatchingCards(rank)
drawFrom(src.CardCollectionObj)
pushCard(rank)
popCard()
removeCards(rank)
transferCards(src.CardCollectionObj, rank)
shuffle()
show.s()
sort()
EndInterface
Structure _membersPlayerClass
*vtable.i
isHuman.i
name.s
score.i
hand.CardCollectionObj
ranksOpponentHas.i[#NumCardRanks] ;priority 1 for requests, index = (rank# - 1)
newRanksDrawn.i[#NumCardRanks] ;priority 2 for requests, index = (rank# - 1)
history.i[#MaxHistorySize] ;priority 3 requests are selected with the help of this history
EndStructure
Interface PlayerObj
init(deck.CardCollectionObj)
getScore()
getName.s()
isHuman() ;boolean
countCardsInHand()
takeTurn(otherPlayer.PlayerObj,deck.CardCollectionObj)
draw(deck.CardCollectionObj)
shiftHistory()
setHistory(rank)
createDiffHistory(*hand.CardCollectionObj)
request(dest.CardCollectionObj, rank) ;boolean
updateScore()
EndInterface
Enumeration
#HumanGamePlayer
#ComputerGamePlayer
EndEnumeration
Structure _membersGameClass
*vtable.i
turn.i ;player# whose turn it is
deck.CardCollectionObj
player.PlayerObj[2]
EndStructure
Interface GameObj
play()
displayWinner()
EndInterface
Procedure handleError(condition,Msg$)
If Not condition
MessageRequester("Error",Msg$)
End
EndIf
EndProcedure
Procedure.s verboseCardInfo(rank)
If rank > 0 And rank <= #NumCardRanks
ProcedureReturn StringField(#LongCardRanks$, rank, ";")
EndIf
EndProcedure
Procedure CC_init(*this._membersCardCollectionClass, isDeck)
Protected i
If isDeck
*this\size = #MaxCards - 1
For i = 0 To #MaxCards - 1
*this\card[i] = (i % #NumCardRanks) + 1
Next
Else
*this\size = -1
EndIf
EndProcedure
Procedure CC_countCards(*this._membersCardCollectionClass)
ProcedureReturn *this\size + 1
EndProcedure
Procedure CC_countMatchingCards (*this._membersCardCollectionClass, rank)
Protected i, count
For i = 0 To *this\size
If *this\card[i] = rank
count + 1
EndIf
Next
ProcedureReturn count
EndProcedure
Procedure CC_drawFrom(*this._membersCardCollectionClass, *source.CardCollectionObj)
Protected cardDrawn, *dest.CardCollectionObj = *this
cardDrawn = *source\popCard()
*dest\pushCard(cardDrawn)
ProcedureReturn cardDrawn
EndProcedure
Procedure CC_pushCard(*this._membersCardCollectionClass, rank)
If *this\size < #MaxCards And (rank > 0 And rank <= #NumCardRanks)
*this\size + 1
*this\card[*this\size] = rank
EndIf
EndProcedure
Procedure CC_popCard(*this._membersCardCollectionClass)
Protected rank
If *this\size >= 0
rank = *this\card[*this\size]
*this\size - 1
EndIf
ProcedureReturn rank ;returns #Null if no cards are in collection
EndProcedure
Procedure CC_removeCards(*this._membersCardCollectionClass, rank)
;remove all cards matching rank
Protected i
For i = *this\size To 0 Step -1
If *this\card[i] = rank
If *this\size < (#MaxCards - 1) And i <> *this\size
MoveMemory(@*this\card[i + 1], @*this\card[i], SizeOf(Integer) * *this\size - i)
EndIf
*this\size - 1
EndIf
Next
EndProcedure
Procedure CC_transferCards(*this._membersCardCollectionClass, *source.CardCollectionObj, rank)
;move all cards matching rank from source and return count
Protected i, cardsTransfered, *src._membersCardCollectionClass = *source, blankcard
If *source <> #Null
For i = *src\size To 0 Step -1
If *src\card[i] = rank
*this\size + 1
*this\card[*this\size] = *src\card[i]
If *src\size < (#MaxCards - 1) And i <> *src\size
MoveMemory(@*src\card[i + 1], @*src\card[i], SizeOf(Integer) * (*src\size - i))
EndIf
*src\size - 1
cardsTransfered + 1
EndIf
Next
EndIf
ProcedureReturn cardsTransfered
EndProcedure
Procedure CC_shuffle(*this._membersCardCollectionClass)
Protected w, i
If *this\size >= 0
Dim shuffled(*this\size)
For i = *this\size To 0 Step -1
w = Random(i)
shuffled(i) = *this\card[w]
If w <> i
*this\card[w] = *this\card[i]
EndIf
Next
For i = 0 To *this\size
*this\card[i] = shuffled(i)
Next
EndIf
EndProcedure
Procedure.s CC_showCards(*this._membersCardCollectionClass)
Protected i, output$
For i = 0 To *this\size
output$ + StringField(#ShortCardRanks$, *this\card[i],";")
If i <> *this\size: output$ + ", ": EndIf
Next
ProcedureReturn output$
EndProcedure
Procedure CC_sortCards(*this._membersCardCollectionClass)
Protected low, high
Protected firstIndex, lastIndex = *this\size
If lastIndex > firstIndex + 1
low = firstIndex + 1
While low <= lastIndex
high = low
While high > firstIndex
If *this\card[high] < *this\card[high - 1]
Swap *this\card[high - 1], *this\card[high]
Else
Break
EndIf
high - 1
Wend
low + 1
Wend
EndIf
EndProcedure
Procedure newCardCollection(isDeck = #False)
Protected *newCardCollection._membersCardCollectionClass = AllocateMemory(SizeOf(_membersCardCollectionClass))
If *newCardCollection
*newCardCollection\vtable = ?vTable_CardCollectionClass
CC_init(*newCardCollection, isDeck)
EndIf
ProcedureReturn *newCardCollection
EndProcedure
Procedure _player_validateRank(inputRank.s)
;check if inputRank is valid, allows verbose and abbrieviated entries
Protected rankSize, result, validatedRank, i
inputRank = Trim(inputRank)
rankSize = Len(inputRank)
Select rankSize
Case 0
Case 1
inputRank = UCase(inputRank)
If inputRank = "T": inputRank = "10": EndIf ;handle an alias
For i = 1 To #NumCardRanks
If inputRank = StringField(#ShortCardRanks$, i, ";")
validatedRank = i
Break
EndIf
Next
Default
inputRank = LCase(inputRank)
result = FindString(inputRank, " ", 1)
If result
inputRank = Left(inputRank, result - 1)
EndIf
result = FindString(inputRank, "s", 2)
If result
inputRank = Left(inputRank, result - 1)
EndIf
;handle some aliases
Select inputRank
Case "10"
inputRank = "ten"
Case "two"
inputRank = "deuce"
EndSelect
For i = 1 To #NumCardRanks
If inputRank = StringField(#LongCardRanks$, i, ";")
validatedRank = i
Break
EndIf
Next
EndSelect
ProcedureReturn validatedRank ;returns #Null if rank not valid
EndProcedure
Procedure player_init(*this._membersPlayerClass, *deck.CardCollectionObj)
Protected i, rankDrawn
;draw 9 cards from *deck and add them to player's hand
If *deck <> #Null
For i = 1 To 9
rankDrawn = *this\hand\drawFrom(*deck)
*this\newRanksDrawn[rankDrawn - 1] = #True
Next
EndIf
EndProcedure
Procedure player_getScore(*this._membersPlayerClass)
ProcedureReturn *this\score
EndProcedure
Procedure.s player_getName(*this._membersPlayerClass)
ProcedureReturn *this\name
EndProcedure
Procedure player_isHuman(*this._membersPlayerClass)
ProcedureReturn *this\isHuman
EndProcedure
Procedure player_countCardsInHand(*this._membersPlayerClass)
ProcedureReturn *this\hand\count()
EndProcedure
Procedure player_takeTurn(*this._membersPlayerClass, otherPlayer.PlayerObj, *deck.CardCollectionObj)
Static tempHand.CardCollectionObj
Protected player.PlayerObj = *this, *otherPlayer._membersPlayerClass = otherPlayer
Protected request.s, rank, cardsReceived, turnOver = #False, AI_stage, i
If Not tempHand
tempHand = newCardCollection()
handleError(tempHand,"Unable to allocate enough memory.")
EndIf
While Not turnOver
;prepare request
If player\isHuman()
*this\hand\sort()
Repeat
PrintN(#CRLF$ + "Your hand: " + *this\hand\show())
rank = *this\hand\popCard() ;peek at last card
*this\hand\pushCard(rank)
If *this\hand\countMatchingCards(rank) = *this\hand\count()
Print(#CRLF$ + "You are obligated to ask for " + verboseCardInfo(rank) + StringField(#CardRankPlurals$, rank, ";") + ".")
Break
Else
Print(#CRLF$ + player\getName() + ", which rank do you want?")
rank = _player_validateRank(Input())
Select rank
Case #Null
PrintN("** That's not a valid rank, try one from your hand.")
Case 1 To #NumCardRanks
If *this\hand\countMatchingCards(rank)
Break
Else
PrintN("** You don't have any of those cards in your hand, choose another.")
EndIf
EndSelect
EndIf
ForEver
Else
Repeat
Select AI_stage
Case 0
;request all known cards that are in both opponent's and player's hand
rank = #Null
For i = 1 To #NumCardRanks
If *this\ranksOpponentHas[i - 1] And *this\hand\countMatchingCards(i)
*this\ranksOpponentHas[i - 1] = 0
rank = i
EndIf
Next
If rank
player\shiftHistory()
player\setHistory(rank)
Else
AI_stage = 1
tempHand\init()
For i = 1 To #NumCardRanks
If *this\newRanksDrawn[i - 1]
If *this\hand\countMatchingCards(i)
tempHand\pushCard(i)
Else
*this\newRanksDrawn[i - 1] = #False ;card is no longer in hand
EndIf
EndIf
Next
EndIf
Case 1
;request a random card from the drawn list
tempHand\shuffle()
rank = tempHand\popCard()
If rank
*this\newRanksDrawn[rank - 1] = #False
player\shiftHistory()
player\setHistory(rank)
Else
AI_stage = 2
EndIf
Case 2
;request a random card from remaining cards
player\shiftHistory()
player\createDiffHistory(tempHand)
tempHand\shuffle()
rank = tempHand\popCard()
player\setHistory(rank)
EndSelect
Until rank <> #Null
Print(#CRLF$ + player\getName() + " asks for all of ")
If otherPlayer\isHuman()
Print("your ")
Else
Print(otherPlayer\getName() + "'s ")
EndIf
PrintN(verboseCardInfo(rank) + StringField(#CardRankPlurals$, rank, ";") + ".")
EndIf
cardsReceived = otherPlayer\request(*this\hand, rank)
If cardsReceived
If player\isHuman()
Print(#CRLF$ + #Indent$ + "You are given ")
ElseIf Not otherPlayer\isHuman()
Print(#CRLF$ + #Indent$ + player\getName() + " is given ")
Else
Print(#CRLF$ + #Indent$ + "You give " + player\getName() + " ")
EndIf
Print(Str(cardsReceived) + " " + verboseCardInfo(rank))
If cardsReceived > 1
PrintN(StringField(#CardRankPlurals$, rank, ";") + ".")
Else
PrintN(".")
EndIf
player\updateScore()
If otherPlayer\countCardsInHand() = 0
If Not otherPlayer\draw(*deck)
turnOver = #True ;game over
EndIf
EndIf
If player\countCardsInHand() = 0
If Not player\draw(*deck)
turnOver = #True ;game over
Continue
Else
AI_stage = 0
EndIf
EndIf
Else
turnOver = #True
EndIf
Wend
;go fish
If *deck\count()
If otherPlayer\isHuman()
Print(#CRLF$ + "You tell ")
Else
Print(#CRLF$ + otherPlayer\getName() + " tells ")
EndIf
If player\isHuman()
Print("you to 'Go Fish!'")
Else
Print(player\getName() + " to 'Go Fish!'")
EndIf
EndIf
Repeat
If Not player\draw(*deck)
Break ;game over, exit loop
EndIf
Until player\countCardsInHand() > 0
EndProcedure
Procedure player_draw(*this._membersPlayerClass, *deck.CardCollectionObj)
;draw a card from *deck and display card if player is human
;return #True if a card was drawn, return #False if no cards drawn
Protected isGoFish = #False, player.PlayerObj = *this
If *deck And *deck\count()
If *this\hand\count() = 0
If player\isHuman()
Print(#Indent$ + "You are")
Else
Print(#Indent$ + *this\name + " is")
EndIf
Print(" out of cards, so ")
Else
isGoFish = #True
Print(#Indent$)
EndIf
Protected cardDrawn = *this\hand\drawFrom(*deck)
If Not *this\hand\countMatchingCards(cardDrawn)
*this\newRanksDrawn[cardDrawn - 1] = #True
EndIf
If player\isHuman()
If isGoFish
Print("Y")
Else
Print("y")
EndIf
PrintN("ou draw a card and get " + StringField(#CardRankArticles$, cardDrawn, ";") + " " + StringField(#LongCardRanks$, cardDrawn, ";") + ".")
Else
PrintN(*this\name + " draws a card.")
EndIf
If *this\hand\count() > 3
player\updateScore()
EndIf
ProcedureReturn #True
Else
;No more cards in deck.
ProcedureReturn #False
EndIf
EndProcedure
Procedure player_shiftHistory(*this._membersPlayerClass)
Protected i
For i =#MaxHistorySize - 1 To 1 Step - 1
*this\history[i] = *this\history[i - 1]
Next
EndProcedure
Procedure player_setHistory(*this._membersPlayerClass, rank)
*this\history[0] = rank
EndProcedure
Procedure player_createDiffHistory(*this._membersPlayerClass, *hand.CardCollectionObj)
;update *hand to contain only ranks in hand but not in history
Protected i, activeHistorySize
;determine history size (= min(#MaxHistorySize, uniqueRankCount)
If *hand
For i = 1 To #NumCardRanks
If activeHistorySize = #MaxHistorySize: Break: EndIf
If *this\hand\countMatchingCards(i)
activeHistorySize + 1
EndIf
Next
;add all card-ranks from player's hand
*hand\init()
For i = 1 To #NumCardRanks
If *this\hand\countMatchingCards(i)
*hand\pushCard(i)
EndIf
Next
;remove cards that are also in history
For i = 0 To activeHistorySize - 1
*hand\removeCards(*this\history[i])
Next
EndIf
EndProcedure
Procedure player_request(*this._membersPlayerClass, *dest.CardCollectionObj, rank)
;report how many cards of rank are present in hand
;also mark the requested card as being in the opponent's hand
Protected cardsPresent
If *dest <> #Null
cardsPresent = *dest\transferCards(*this\hand, rank)
*this\ranksOpponentHas[rank - 1] = 1
*this\newRanksDrawn[rank - 1] = #False ;no need to keep in two places
ProcedureReturn cardsPresent
Else
ProcedureReturn #Null
EndIf
EndProcedure
Procedure player_updateScore(*this._membersPlayerClass)
;check for complete books and remove them from hand while increasing score
Protected bookCount, bookFound, rank, player.PlayerObj = *this
Repeat
bookFound = #False
For rank = #NumCardRanks To 1 Step -1
If *this\hand\countMatchingCards(rank) = 4
*this\hand\removeCards(rank)
If player\isHuman()
Print(#CRLF$ + #Indent$ + "You complete")
Else
Print(#CRLF$ + #Indent$ + *this\name + " completes")
EndIf
PrintN(" a book of " + verboseCardInfo(rank) + StringField(#CardRankPlurals$, rank, ";") + ".")
*this\ranksOpponentHas[rank - 1] = #False
bookFound = #True
*this\score + 1
If player\ishuman()
Print("** You now have ")
Else
Print("** " + player\getName() + " now has ")
EndIf
Print(Str(player\getScore()) + " book"): If player\getScore() > 1: Print("s"): EndIf
PrintN(".")
Break
EndIf
Next
Until bookFound = #False
EndProcedure
Procedure newPlayer(isHuman = #False)
Protected *newPlayer._membersPlayerClass = AllocateMemory(SizeOf(_membersPlayerClass))
If *newPlayer
*newPlayer\vtable = ?vTable_PlayerClass
*newPlayer\score = 0
If Not isHuman
*newPlayer\name = "Computer"
Else
*newPlayer\isHuman = #True
Protected name.s
Print("What is your name?")
name = Trim(Input())
Select name
Case ""
name = "Human"
Case "computer", "Computer"
name = "*Computer*"
*newPlayer\isHuman = #False
Default
If Left(name,1) <> UCase(Left(name,1))
name = UCase(Left(name,1)) + LCase(Mid(name,2))
EndIf
EndSelect
*newPlayer\name = name
EndIf
*newPlayer\hand = newCardCollection()
handleError(*newPlayer\hand,"Unable to initialize hand.")
EndIf
ProcedureReturn *newPlayer
EndProcedure
Procedure game_Play(*this._membersGameClass)
*this\deck\shuffle()
Print(#CRLF$ + "Both players are dealt 9 cards.")
*this\player[#HumanGamePlayer]\init(*this\deck)
*this\player[#ComputerGamePlayer]\init(*this\deck)
*this\player[#HumanGamePlayer]\updateScore()
*this\player[#ComputerGamePlayer]\updateScore()
PrintN(#Indent$ + *this\player[*this\turn ! 1]\getName() + " will go first.")
PrintN(#CRLF$ + "-----------------------------------------------------------------------------")
;take turns until all books scored (i.e. no cards are in deck or hands)
Repeat
*this\turn ! 1
*this\player[*this\turn]\takeTurn(*this\player[*this\turn ! 1], *this\deck)
PrintN(#CRLF$ + "-----------------------------------------------------------------------------")
Until *this\deck\count() = 0 And *this\player[*this\turn]\countCardsInHand() = 0
EndProcedure
Procedure game_DisplayWinner(*this._membersGameClass)
Protected winningPlayer = #HumanGamePlayer
If *this\player[#ComputerGamePlayer]\getScore() > *this\player[#HumanGamePlayer]\getScore()
winningPlayer = #ComputerGamePlayer
EndIf
PrintN(*this\player[winningPlayer]\getName() + " won with a score of " + Str(*this\player[winningPlayer]\getScore()) + " books.")
EndProcedure
Procedure newGame()
Protected *newGame._membersGameClass = AllocateMemory(SizeOf(_membersGameClass))
If *newGame
*newGame\vtable = ?vTable_GameClass
*newGame\player[#HumanGamePlayer] = newPlayer(#True) ;'human'
*newGame\player[#ComputerGamePlayer] = newPlayer() ;computer
*newGame\deck = newCardCollection(#True)
If *newGame\player[#HumanGamePlayer] = #Null Or *newGame\player[#ComputerGamePlayer] = #Null Or *newGame\deck = #Null
PrintN("Unable to initialize.")
End ;all allocated memory is automatically freed at program's end
EndIf
*newGame\turn = Random(1)
EndIf
ProcedureReturn *newGame
EndProcedure
DataSection
vTable_GameClass:
Data.i @game_Play()
Data.i @game_DisplayWinner()
vTable_PlayerClass:
Data.i @player_init()
Data.i @player_getScore()
Data.i @player_getName()
Data.i @player_isHuman()
Data.i @player_countCardsInHand()
Data.i @player_takeTurn()
Data.i @player_draw()
Data.i @player_shiftHistory()
Data.i @player_setHistory()
Data.i @player_createDiffHistory()
Data.i @player_request()
Data.i @player_updateScore()
vTable_CardCollectionClass:
Data.i @CC_init()
Data.i @CC_countCards()
Data.i @CC_countMatchingCards()
Data.i @CC_drawFrom()
Data.i @CC_pushCard()
Data.i @CC_popCard()
Data.i @CC_removeCards()
Data.i @CC_transferCards()
Data.i @CC_shuffle()
Data.i @CC_showCards()
Data.i @CC_sortCards()
EndDataSection
handleError(OpenConsole(), "Couldn't open console.")
PrintN("
### =============
")
PrintN("Let's play GO FISH.")
PrintN("
### =============
")
Define game.GameObj = newGame()
handleError(game,"Couldn't initialize game.")
game\play()
PrintN(#CRLF$ + "Game over.")
game\displayWinner()
PrintN(#CRLF$ + "Press Enter to exit.")
Input()