How to make tic tac toe check for vertical and diagonal?

Hello I am making tic tac toe and I figured out how to check the horizontal (rows) for a match and it works as it should but now I also want it to check for the vertical and diagonal. Thanks.

local board = {
	{"x", "o", "x"},
	{"o", "x", "o"},
	{"x", "o", "x"}
}

local function checkHorizontal(plrname, other)
	for row = 1, #board do
		local first = board[row][1]
		for column = 2, #board[row] do
			if board[row][column] == 0 or board[row][column] ~= first then
				break
			elseif column == #board[row] then
				local winner = board[row][column]
				if winner == player.Value or winner == plrname and debounce == false then
                --code stuff
				end
				return winner
			end
		end
	end
end

You can only win when you place a piece down, so you really only need to check 3 things each move rather than the entire board.

That is, check horizontal, diagonal, and vertical only if they contain the most recently placed piece.

Perhaps this simplifies the problem for you and you’ll find it easier to write!

The logic for a vertical check is exactly the same as a horizontal check, except you’re swapping the indexes of the row and column. For example, a win condition for this board:

x, x, x
_, _, _
_, _, _

The indexes are

[1][1], [1][2], [1][3]

Note that if the board looked like:

x, _, _
x, _, _
x, _, _

Then you just flip the indexes:

[1][1], [2][1], [3][1]
2 Likes

Okay I understand that. But I don’t really understand how I would implement it in my code. I tried…

	for row = 1, #board do
		local first = board[row][1]
		for column = 2, #board[row] do
			if board[column][row] == 0 or board[column][row] ~= first then
				break
			elseif row == #board[column] then
				local winner = board[column][row]
				if winner == player.Value or winner == plrname and debounce == false then
                --stuff
				end
				return winner
			end
		end
	end

Here’s a similar vertical function and diagonal functions. You can quickly see that there is a lot of code reduction an optimization to do, but at least it should give you a place to start:

local BOARD_SIZE = 3

local function checkHorizontal(plrname, other)
	for y = 1, BOARD_SIZE do
		local row = board[y]
		local first = row[1]

		if first == 0 then
			break
		end
		
		for x = 2, BOARD_SIZE do
			local cell = row[x]
			
			if cell ~= first then
				break
			elseif x == BOARD_SIZE then
				local winner = cell
				if winner == player.Value or winner == plrname and debounce == false then
					-- code stuff
				end

				return winner
			end
		end
	end
end


local function checkVertical(plrname, other)
	for x = 1, BOARD_SIZE do
		local first = board[1][x]
		
		if first == 0 then
			break
		end
		
		for y = 2, BOARD_SIZE do
			local row = board[y]
			local cell = row[x]
			
			if cell ~= first then
				break
			elseif y == BOARD_SIZE then
				local winner = cell
				if winner == player.Value or winner == plrname and debounce == false then
					-- code stuff
				end

				return winner
			end
		end
	end
end

local function checkBackDiagonal(plrname, other)
	-- Top left to bottom right
	local first = board[1][1]
	if first == 0 then
		return
	end
	for i = 2, BOARD_SIZE do
		local row = board[i]
		local cell = row[i]
			
		if cell ~= first then
			break
		elseif i == BOARD_SIZE then
			local winner = cell
			if winner == player.Value or winner == plrname and debounce == false then
				-- code stuff
			end

			return winner
		end
	end
end

local function checkForwardDiagonal(plrname, other)
	-- Top right to bottom left
	local first = board[1][BOARD_SIZE]
	if first == 0 then
		return
	end
	for i = 2, BOARD_SIZE do
		local row = board[i]
		local cell = row[BOARD_SIZE - i + 1]
			
		if cell ~= first then
			break
		elseif i == BOARD_SIZE then
			local winner = cell
			if winner == player.Value or winner == plrname and debounce == false then
				-- code stuff
			end

			return winner
		end
	end
end
1 Like

Okay thanks. I have it so when you click a field to choose it does not put in “X” or “O” in the table it just adds the player’s name so I can print it out at the end when there is a win.
I can see that it does not work right now how it is so I need to get it to find matching names and not “X” or “O”. How do I do that?

Can’t you just check if the strings on each row match to see if there’s a winner? For the horizontal one i mean. Something like:

for row = 1, #board do
    if board[row][1] == board[row][2] and board[row][2] == board[row][3] then
        local winner = board[row][1] -- for example, it could be any column. We just need to know if its x or o.
        return winner
    end
end

and for vertical ones, it could be the same but flipped:

for column= 1, #board do
    if board[1][column] == board[2][column] and board[2][column] == board[3][column] then
        local winner = board[1][column] -- for example, it could be any column. We just need to know if its x or o.
        return winner
    end
end

i haven’t tested this, but i think it could be a more simple way of doing it

This is definitely a simpler way of doing it, but it gets very long to do it this way if you want to check the win condition for a board of any size. OP didn’t specify that it had to be a board of any size, however, so if it’s always only 3, then this may be a nice solution.

The code I provided is agnostic of the string value, the only hardcoded value there is 0 for an “unfilled” space. Otherwise, all it does is check that the value is the same as the “first” cell (which is different for each method), which should work fine whether your string is an “x”, an “o”, or someone’s username-- as long as it’s consistent for all 3 winning cells.

The horizontal works perfectly but the vertical doesn’t seem to work and I don’t know why to be honest…?

Okay thank you. I went with @jXD20072’s code because of the simplicity and I don’t need a board bigger than 3.

Here’s a more complete version of his solution:


local function getWinner()
	for i = 1, 3 do
		-- Check horizontal
		if board[i][1] ~= 0 and board[i][1] == board[i][2] and board[i][2] == board[i][3] then
			return board[i][1]
		end

		-- Check vertical
		if board[1][i] ~= 0 and board[1][i] == board[2][i] and board[2][i] == board[3][i] then
			return board[1][i]
		end

		-- Check top left to bottom right
		if board[2][2] ~= 0 and board[1][1] == board[2][2] and board[2][2] == board[3][3] then
			return board[2][2]
		end

		-- Check top right to bottom left
		if board[2][2] ~= 0 and board[1][3] == board[2][2] and board[2][2] == board[3][1] then
			return board[2][2]
		end
	end

	return nil
end

If this helped, please mark it as solved!

The vertical and diagonal stil seems to not work for me… This is how I have set it up with your version:

local function getWinner(plrname, other)
	for i = 1, 3 do
		-- Check horizontal
		if board[i][1] ~= 0 and board[i][1] == board[i][2] and board[i][2] == board[i][3] then
			local winner = board[i][1]
			return board[i][1]
		end

		-- Check vertical
		if board[1][i] ~= 0 and board[1][i] == board[2][i] and board[2][i] == board[3][i] then
			local winner = board[1][i]
			return board[i][1]
		end

		-- Check top left to bottom right
		if board[2][2] ~= 0 and board[1][1] == board[2][2] and board[2][2] == board[3][3] then
			local winner = board[1][1]
			return board[2][2]
		end

		-- Check top right to bottom left
		if board[2][2] ~= 0 and board[1][3] == board[2][2] and board[2][2] == board[3][1] then
			local winner = board[1][3]
			return board[2][2]
		end
	end

	return nil
end

I had a mistake in my original return statement for vertical, I was returning the wrong value. I edited my previous post to correct the error.

In your version, you’re saving the winner in a local variable, which isn’t used anywhere. The proper way to use this function is to call it and use the return value, like so:

local winner = getWinner()
if winner then
    -- Do winner code here, where winner is the value saved in the cells of the winner
end

Shouldn’t need to pass plrname or other into the getWinner function.

I can only get it to work for the horizontal…

Can you post a more complete sample of your code so we can troubleshoot it? The function I posted works, it’s probably your usage of it that isn’t set up right.

The getWinner() is as default I haven’t changed anything there. Then I have made a function to call everything I want and display the win:

local function afterWin()
	local winner = getWinner()
	if winner and winner == player.Value or winner == plr.Name and debounce == false then
		debounce = true

		print("Winner:", winner)
		game.Players[plr.Name].PlayerGui.TicTacToe.winValue.Value = 1
		game.Players[plr.Name].PlayerGui.TicTacToe.Frame.GameFrame.Visible = false
		game.Players[player.Value].PlayerGui.TicTacToe.Frame.GameFrame.Visible = false
		game.Players[plr.Name].PlayerGui.TicTacToe.Frame.Status.Text = winner.." won the game!"
		game.Players[player.Value].PlayerGui.TicTacToe.Frame.Status.Text = winner.." won the game!"
		wait(3)
		game.Players[plr.Name].PlayerGui.TicTacToe.Frame.Visible = false
		game.Players[player.Value].PlayerGui.TicTacToe.Frame.Visible = false
		game.Players[plr.Name].PlayerGui.TicTacToe.Clicked.Value = false
		game.Players[player.Value].PlayerGui.TicTacToe.Clicked.Value = false
		game.Players[plr.Name].PlayerGui.TicTacToe.winValue.Value = 0

		script.Parent.Count.Value = nil
		script.Parent.Player.Value = ""
		script.Parent.secondPlayer.Value = ""

		for i,v in pairs(game.Players[plr.Name].PlayerGui.TicTacToe.Frame.GameFrame:GetChildren()) do
			if v:IsA("TextButton") then
				v.Text = ""
			end
		end
		for i,v in pairs(game.Players[player.Value].PlayerGui.TicTacToe.Frame.GameFrame:GetChildren()) do
			if v:IsA("TextButton") then
				v.Text = ""
			end
		end

		local new = game.ServerScriptService.mainTTT:Clone()
		new.Parent = script.Parent
		new.Disabled = false

		script:Destroy()
	end
end

Then where it gets called:

				one2.MouseButton1Click:Connect(function()
					bruh.Value = 2

					local fn = 1
					local sn = 1

					one1.Text = "X"
					one2.Text = "X"

					board[fn][sn] = plr.Name

					afterWin()
				end)

I suspect the issue may be here, perhaps your values are getting set incorrectly? I’m just taking a guess at your naming scheme here, but does the variable name one2 mean it’s a button in row 1, column 2? If so, note that your fn and sn (not sure what they stand for, but they’re used as row and column indexes) are both 1 and 1, which doesn’t match your variable name one2. If you have a connection like this for each of your board cells, make sure all the indexes are getting set correctly.

Let me know if you’ve double checked this all to be correct and I’ll look elsewhere.

one2 is just the second players button one. So the one1 is player one’s button one. That is row 1, column 1.

Gotcha! Makes sense. Have you verified all your fn and sn is set correctly according to its position?

Yes everything should be as it should.