Go Fish/Lua

From Rosetta Code
Go Fish/Lua is part of Go_Fish. You may find other members of Go_Fish at Category:Go_Fish.

Library: LÖVE

LÖVE (Love2D) v.11.3 solution. There are no card suit characters in the standard font, so suits are A, B, C and D, but ranks are 1 to 13.

function createDeck ()
	Deck = {}
	for iSuit = 1, #CardSuits do
		for iRank = 1, #CardRanks do
			local card = {
				iSuit = iSuit,
				iRank = iRank,
				suit = CardSuits[iSuit],
				rank = CardRanks[iRank],
				name = CardSuits[iSuit]..'-'..CardRanks[iRank]
			}
			local i = math.random (#Deck + 1)
			table.insert (Deck, i, card)
		end
	end
end

function createPlayers (nPlayers)
	Players = {}
	for index = 1, nPlayers do
		local player = {
			cards = {},
			moves = {},
			score = 0,
			index = index,
		}
		table.insert (Players, player)
	end
end

function dealPlayers ()
	local nCards = 7
	if #Players > 2 then nCards = 5 end
		
	for i = 1, nCards do
		for iPlayer = 1, #Players do
			-- player takes a card from deck
			takeCardFromDeck (Players[iPlayer])
		end
	end
end

function isKeyValueInList (key, value, list)
	for index, element in ipairs (list) do
		if element[key] == value then
			return element, index
		end
	end
	return false
end

function updateMoves ()
	for iPlayer = 1, #Players do
		local player = Players[iPlayer]
		
		local moves = {}
		for iCard = 1, #player.cards do
			local rank = player.cards[iCard].rank
			local move = isKeyValueInList ("rank", rank, moves)
			if not move then
				move = {rank = rank, amount = 1}
				table.insert (moves, move)
			else
				move.amount = move.amount + 1
			end
		end
		player.moves = moves
	end
end

function takeCardFromDeck (player)
	if #Deck > 0 then
		-- player takes last card from deck
		table.insert (player.cards, table.remove(Deck, #Deck))
		return true
	end
	return false
end

function updateGUI ()
	local player = Players[1]
	local nMoves = #player.moves
	local x1, y1 = 100, 450
	local cardW = 50
	local cardH = 30
	
	GUI = {}
	local totalCards = 0
	totalCards = totalCards + #Deck
	for iPlayer = 1, #Players do
		local player = Players [iPlayer]
		totalCards = totalCards + #player.cards
	end
	if totalCards == 0 then
		GameOver = true
		table.insert (Statistics, 1, "Game Over")
		local winners = {}
		local max = 0
		for iPlayer = 1, #Players do
			local player = Players [iPlayer]
			if player.score > max then
				winners = {player.index}
				max = player.score
			elseif player.score == max then
				table.insert (winners, player.index)
			end
		end
		if #winners == 1 then
			table.insert (Statistics, 1, "Player " .. winners[1] .. ' wins the game')
		elseif #winners > 1 then
			table.insert (Statistics, 1, "Players " .. table.concat(winners, ', ') .. ' win the game')
		end
	end
	
	if #Players > 2 then
		GUI.selectedPlayer = nil
		local amount, last = 0, nil
		for iPlayer = 2, #Players do
			local player = Players [iPlayer]
			if #player.cards > 0 then
				local x = x1 + cardW*(iPlayer-2)
				local y = y1
				local button = {
					x=x, 
					y=y, 
					w=cardW, 
					h=cardH, 
					text = 'Player' .. iPlayer,
					iPlayer = iPlayer,
					playerButton = true,
				}
				table.insert (GUI, button)
				amount = amount + 1
				last = player
			end
		end
		if amount == 1 then
			-- only one player
			GUI.selectedPlayer = last
		end
	else
		GUI.selectedPlayer = 2
	end
	for iMove, move in ipairs (player.moves) do
		local x = x1 + cardW*(iMove-1)
		local y = y1 + cardH
		local button = {
			x=x, 
			y=y, 
			w=cardW, 
			h=cardH, 
			text = move.rank .. ' (' .. move.amount..')',
			rank = move.rank,
			moveButton = true,
		}
		table.insert (GUI, button)
	end
end

function love.load()
	love.window.setTitle( 'Go Fish' )
	Statistics = {}
	CardSuits = {"A", "B", "C", "D"}
	CardRanks = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13"}
	NPlayers = 6
	Hidden = true
	
	-- new game
	createDeck ()
	createPlayers (NPlayers)
	dealPlayers ()
	
	updateMoves ()
	updateGUI ()
end

function love.draw()
	if not Hidden then
		for i, t in ipairs (Deck) do
			love.graphics.print (t.name, 0, i*11)
		end
	end
	for iPlayer, player in ipairs (Players) do
		local x = 60 * iPlayer
		local y = 0
		love.graphics.print ('player '..iPlayer, x, y)
		y = y + 14
		love.graphics.print ('score: '.. player.score, x, y)
		if not Hidden or (iPlayer == 1) then
			for iCard, card in ipairs (player.cards) do
				y = y + 14
				love.graphics.print (card.name, x, y)
			end
		end
		if not Hidden then
			y = y + 14
			for iMove, move in ipairs (player.moves) do
				y = y + 14
				love.graphics.print (move.rank .. ' ' .. move.amount, x, y)
			end
		end
	end

--	draw first player moves
	for iGUI, button in ipairs (GUI) do
--		print (GUI.selectedPlayer or '', button.iPlayer or '')
		if  GUI.hoveredMove and (GUI.hoveredMove == iGUI) or
			GUI.selectedPlayer and button.iPlayer and (GUI.selectedPlayer.index == button.iPlayer) then
			love.graphics.setColor (0.3,0.3,0.3)
			love.graphics.rectangle ('fill', button.x, button.y, button.w, button.h)
		end
		love.graphics.setColor (1,1,1)
		love.graphics.rectangle ('line', button.x, button.y, button.w, button.h)
		love.graphics.printf(button.text, button.x, button.y, button.w, 'center', 0, 1, 1, 0, -math.floor(button.h/4))
	end
	for i = 1, math.min(30, #Statistics) do
		local str = Statistics[i]
		love.graphics.print (str, 430, 20+(i-1)*14)
	end
end

function love.keypressed(key, scancode, isrepeat)
	if false then
	elseif key == "h" then
		Hidden = not Hidden
	elseif key == "escape" then
		love.event.quit()
	end
end

function love.mousemoved( x, y, dx, dy, istouch )
	GUI.hoveredMove = nil
	for iGUI, button in ipairs (GUI) do
		if x > button.x and x < button.x + button.w and y > button.y and y < button.y + button.h then
			GUI.hoveredMove = iGUI
		end
	end
end

function checkHandCards (player)
	local sets = {}
	for iCard = 1, #player.cards do
		local rank = player.cards[iCard].rank
		local set = isKeyValueInList ("rank", rank, sets)
		if not set then
			set = {rank = rank, amount = 1}
			table.insert (sets, set)
		else
			set.amount = set.amount + 1
		end
	end
	for iSet = 1, #sets do
		local set = sets[iSet]
		if set.amount == 4 then -- full set
			local rank = set.rank
			
			table.insert (Statistics, 1, "Player "..player.index .. ' has set of "' .. rank .. '"')
			-- remove cards
			for iCard = #player.cards, 1, -1 do -- backwards
				local card = player.cards[iCard]
				if card.rank == rank then
					table.remove (player.cards, iCard)
				end
			end
			player.score = player.score + 1
		end
	end
end

function fillHandCards (player)
	
	if #player.cards == 0 and #Deck > 0 then
		local nCards = math.min(5, #Deck)
		if nCards == 1 then
			table.insert (Statistics, 1, "Player ".. player.index .. ' takes one card from deck')
		else
			table.insert (Statistics, 1, "Player ".. player.index .. ' takes '.. nCards ..' cards from deck')
		end
		for i = 1, nCards do
			takeCardFromDeck (player)
		end
	end
end

function doMove (activePlayer, selectedPlayer, rank)
	local takenCards = 0
	for iCard = #selectedPlayer.cards, 1, -1 do -- iterate backwards
		local card = selectedPlayer.cards[iCard]
		if card.rank == rank then
			table.insert (activePlayer.cards, table.remove (selectedPlayer.cards, iCard))
			takenCards = takenCards + 1
		end
	end
	if takenCards > 0 then 
		if takenCards == 1 then
			table.insert (Statistics, 1, 
								"Player "..activePlayer.index .. ' asks the Player '.. selectedPlayer.index .. ' for card "'.. rank .. 
				'" and get one card.')
		else
			table.insert (Statistics, 1, 
				"Player "..activePlayer.index .. ' asks the Player '.. selectedPlayer.index .. ' for card "'.. rank .. 
				'" and get ' .. takenCards .. ' cards.')
		end
		checkHandCards (activePlayer)
		fillHandCards (activePlayer)
		fillHandCards (selectedPlayer)
		updateMoves ()
		if #activePlayer.cards > 0 then
--		repeat the turn
			return true
		else
			return false
		end
	else
		table.insert (Statistics, 1, 
							"Player "..activePlayer.index .. ' asks the Player '.. selectedPlayer.index .. ' for card "'.. rank .. 
			'", but he has no one.')
		if takeCardFromDeck (activePlayer) then
			table.insert (Statistics, 1, "Player ".. activePlayer.index .. ' takes one card from deck')
		end
		checkHandCards (activePlayer)
--		next player turn
		return false
	end

end


function AI_moves ()
	for iPlayer = 2, #Players do
		local activePlayer = Players[iPlayer]
		if #activePlayer.moves > 0 then
			local iMove = math.random (#activePlayer.moves)
			local rank = activePlayer.moves[iMove].rank
			local activePlayers = {}
			for iPlayer, player in ipairs (Players) do
				if #player.cards > 0 and not (activePlayer == player) then
					table.insert (activePlayers, player)
				end
			end
			local selectedPlayer = activePlayers[math.random (#activePlayers)]
			while doMove (activePlayer, selectedPlayer, rank) do
				activePlayers = {}
				for iPlayer, player in ipairs (Players) do
					if #player.cards > 0 and not (activePlayer == player) then
						table.insert (activePlayers, player)
					end
				end
				iMove = math.random (#activePlayer.moves)
				rank = activePlayer.moves[iMove].rank
			end
		else
			table.insert (Statistics, 1, "Player ".. activePlayer.index .. ' has no cards')
		end
	end
end

function love.mousereleased( x, y, button, istouch, presses )
	if GameOver then
		GameOver = false
		
		createDeck ()
		createPlayers (NPlayers)
		dealPlayers ()
		
		updateMoves ()
		updateGUI ()
	elseif GUI.hoveredMove then
		local button = GUI[GUI.hoveredMove]
		
		if button.playerButton then
			GUI.selectedPlayer = Players[button.iPlayer]
			table.insert (Statistics, 1, 'Selected Player: ' .. button.iPlayer)
		elseif button.moveButton then
			-- check if the player has these cards
			GUI.hoveredMove = nil
			if GUI.selectedPlayer then -- no SelectedPlayer means no move
				if doMove (Players[1], GUI.selectedPlayer, button.rank) then
					-- player has another move
				else
					-- other players
					AI_moves ()
				end
				updateGUI ()
			end
		
		end
	elseif #Players[1].cards == 0 then
		-- player has no cards
		table.insert (Statistics, 1, 'Player 1 has no cards;')
		AI_moves ()
		updateGUI ()
	end
end