I am trying to implement the minimax algorithm into a tic-tac-toe game, where the computer will play the optimal move based on the minimax algorithm. I have written the whole algorithm but get weird and seemingly random moves by the AI. For example, when the tic-tac-toe board
is laid out like shown:
' ' 'o' 'x'
' ' 'o' ' '
'x' ' ' ' '
The Minimax algorithm then chooses the first spot, which is obviously not the optimal choice:
'o' 'o' 'x'
' ' 'o' ' '
'x' ' ' ' '
Here is my code:
namespace MinimaxAlg
{
class Program
{
static void Main(string[] args)
{
char[,] board = { { '-', 'o', 'x'},
{ '-', 'o', '-'},
{ 'x', '-', '-'} };
int bestScore = int.MinValue;
int moveI = -1;
int moveJ = -1;
for (var i = 0; i < 3; i++)
{
for (var j = 0; j < 3; j++)
{
if (board[i, j] == '-')
{
board[i, j] = 'o';
var score = Minimax(board, 'o');
board[i, j] = '-';
Console.WriteLine("Score: " + score);
Console.WriteLine("BestScore: " + bestScore);
if (score > bestScore)
{
bestScore = score;
moveI = i;
moveJ = j;
}
}
}
}
board[moveI, moveJ] = 'p';
foreach (var i in board)
{
Console.WriteLine(i);
}
}
static int Minimax(char[,] board, char forWho)
{
if (CheckWhoWins(board, forWho))
{
return 1;
}
if (forWho == 'o')
{
var bestScore = int.MinValue;
for (var i = 0; i < 3; i++)
{
for (var j = 0; j < 3; j++)
{
if (board[i, j] == '-')
{
board[i, j] = forWho;
var score = Minimax(board, 'x');
board[i, j] = '-';
bestScore = Math.Max(bestScore, score);
}
}
}
return bestScore;
}
else
{
var bestScore = int.MaxValue;
for (var i = 0; i < 3; i++)
{
for (var j = 0; j < 3; j++)
{
if (board[i, j] == '-')
{
board[i, j] = forWho;
var score = Minimax(board, 'o');
board[i, j] = '-';
bestScore = Math.Min(bestScore, score);
}
}
}
return bestScore;
}
}
static bool CheckWhoWins(char[,] board, char forWho)
{
if ((board[0, 0] == forWho && board[0, 1] == forWho && board[0, 2] == forWho) || (board[1, 0] == forWho && board[1, 1] == forWho && board[1, 2] == forWho) ||
(board[2, 0] == forWho && board[2, 1] == forWho && board[2, 2] == forWho) || (board[0, 0] == forWho && board[1, 0] == forWho && board[2, 0] == forWho) ||
(board[0, 1] == forWho && board[1, 1] == forWho && board[2, 1] == forWho) || (board[0, 2] == forWho && board[1, 2] == forWho && board[2, 2] == forWho) ||
(board[0, 0] == forWho && board[1, 1] == forWho && board[0, 0] == forWho) || (board[0, 2] == forWho && board[1, 1] == forWho && board[2, 0] == forWho))
return true;
else
return false;
}
}
}
If anyone has any idea of what might be wrong, the help would be greatly appreciated.
You have three issues in your code.
First up, a simple typo. One of your conditions for the win is board[0, 0] == forWho && board[1, 1] == forWho && board[0, 0] == forWho. Clearly the last one should be board[2, 2].
Secondly, you're giving o two goes straight away. These lines show it:
board[i, j] = 'o';
var score = Minimax(board, 'o');
The second line should be var score = Minimax(board, 'x');.
Finally, user3386109 nailed it. You're giving a long game the same score as a short game.
If we change the scoring system to give bigger scores for the least moves then it works just fine.
static int CheckWhoWins(char[,] board, char forWho)
{
if ((board[0, 0] == forWho && board[0, 1] == forWho && board[0, 2] == forWho)
|| (board[1, 0] == forWho && board[1, 1] == forWho && board[1, 2] == forWho)
|| (board[2, 0] == forWho && board[2, 1] == forWho && board[2, 2] == forWho)
|| (board[0, 0] == forWho && board[1, 0] == forWho && board[2, 0] == forWho)
|| (board[0, 1] == forWho && board[1, 1] == forWho && board[2, 1] == forWho)
|| (board[0, 2] == forWho && board[1, 2] == forWho && board[2, 2] == forWho)
|| (board[0, 0] == forWho && board[1, 1] == forWho && board[2, 2] == forWho)
|| (board[0, 2] == forWho && board[1, 1] == forWho && board[2, 0] == forWho))
{
var score = 1;
for (var i = 0; i < 3; i++)
{
for (var j = 0; j < 3; j++)
{
if (board[i, j] == '-')
{
score++;
}
}
}
return score;
}
else
return 0;
}
Minimax (which should be just MinMax) is now:
static int Minimax(char[,] board, char forWho)
{
var score = CheckWhoWins(board, forWho);
if (score != 0)
{
return score;
}
if (forWho == 'o')
{
var bestScore = int.MinValue;
for (var i = 0; i < 3; i++)
{
for (var j = 0; j < 3; j++)
{
if (board[i, j] == '-')
{
board[i, j] = forWho;
var currentScore = Minimax(board, 'x');
board[i, j] = '-';
bestScore = Math.Max(bestScore, currentScore);
}
}
}
return bestScore;
}
else
{
var bestScore = int.MaxValue;
for (var i = 0; i < 3; i++)
{
for (var j = 0; j < 3; j++)
{
if (board[i, j] == '-')
{
board[i, j] = forWho;
var currentScore = Minimax(board, 'o');
board[i, j] = '-';
bestScore = Math.Min(bestScore, currentScore);
}
}
}
return bestScore;
}
}
When you run this you get the following result:
And for bonus points, in my opinion, here's a slightly better version of Minimax:
static int Minimax(char[,] board, char forWho)
{
var score = CheckWhoWins(board, forWho);
if (score != 0)
{
return score;
}
var bestScore = forWho == 'o' ? int.MinValue : int.MaxValue;
int CalcBest(int x, int y) => (forWho == 'o' ? x > y : y > x) ? x : y;
for (var i = 0; i < 3; i++)
{
for (var j = 0; j < 3; j++)
{
if (board[i, j] == '-')
{
board[i, j] = forWho;
var currentScore = Minimax(board, forWho == 'o' ? 'x' : 'o');
board[i, j] = '-';
bestScore = CalcBest(bestScore, currentScore);
}
}
}
return bestScore;
}
Related
Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 1 year ago.
Improve this question
I'm going to post the whole code so you can test yourself.
I don't get why the victory condition for player 2 is not triggered in the if statement.
I know that is cause the if statement, maybe I wrote it wrong but it is like 2 hours that I'm on it and I don't get what's wrong with the statement.
Works fine for player 1.
private static void ticTacToe()
{
string[,] board = new string[3, 3];
for (int i = 0; i < board.GetLength(0); i++)
for (int j = 0; j < board.GetLength(1); j++)
board[i, j] = "-";
string player1 = "X";
string player2 = "O";
string currentPlayer;
int round = 1;
int aX = 0;
int aY = 0;
bool flag = false;
bool victory = false;
while (flag == false || victory == false)
{
if (round == 1)
{
currentPlayer = player1;
round++;
}
else
{
currentPlayer = player2;
round--;
}
bool valid = false;
while (valid == false)
{
bool validCord = false;
while (validCord == false)
{
Console.Write("Where to put(X Co)?: ");
aX = int.Parse(Console.ReadLine());
Console.Write("Where to put(Y Co)?: ");
aY = int.Parse(Console.ReadLine());
if(0 <= aX && aX <= 2 && 0 <= aY && aY <= 2)
{
validCord = true;
}
}
if (board[aY, aX] == "-")
{
valid = true;
board[aY, aX] = currentPlayer;
}
}
for (int i = 0; i < board.GetLength(0); i++)
{
for (int j = 0; j < board.GetLength(1); j++)
{
Console.Write(board[i, j]);
if (board[i, j] != "-")
{
flag = true;
}
else
{
flag = false;
}
}
Console.WriteLine();
}
//|||
if ((board[0, 0] == "X" && board[1, 0] == "X" && board[2, 0] == "X") || (board[0, 0] == "Y" && board[1, 0] == "Y" && board[2, 0] == "Y"))
{
victory = true;
Console.Write(victory + " Victory for player " + currentPlayer);
}
else if ((board[0, 1] == "X" && board[1, 1] == "X" && board[2, 1] == "X") || (board[0, 1] == "Y" && board[1, 1] == "Y" && board[2, 1] == "Y"))
{
victory = true;
Console.Write(victory + " Victory for player " + currentPlayer);
}
else if ((board[0, 2] == "X" && board[1, 2] == "X" && board[2, 2] == "X") || (board[0, 2] == "Y" && board[1, 2] == "Y" && board[2, 2] == "Y"))
{
victory = true;
Console.Write(victory + " Victory for player " + currentPlayer);
}
//---
else if ((board[0, 0] == "X" && board[0, 1] == "X" && board[0, 2] == "X") || (board[0, 0] == "Y" && board[0, 1] == "Y" && board[0, 2] == "Y"))
{
victory = true;
Console.Write(victory + " Victory for player " + currentPlayer);
}
else if ((board[1, 0] == "X" && board[1, 1] == "X" && board[1, 2] == "X") || (board[1, 0] == "Y" && board[1, 1] == "Y" && board[1, 2] == "Y"))
{
victory = true;
Console.Write(victory + " Victory for player " + currentPlayer);
}
else if ((board[2, 0] == "X" && board[2, 1] == "X" && board[2, 2] == "X") || (board[2, 0] == "Y" && board[2, 1] == "Y" && board[2, 2] == "Y"))
{
victory = true;
Console.Write(victory + " Victory for player " + currentPlayer);
}
//X
else if ((board[0, 0] == "X" && board[1, 1] == "X" && board[2, 2] == "X") || (board[0, 0] == "Y" && board[1, 1] == "Y" && board[2, 2] == "Y"))
{
victory = true;
Console.Write(victory + " Victory for player " + currentPlayer);
}
else if ((board[0, 2] == "X" && board[1, 1] == "X" && board[2, 0] == "X") || (board[0, 2] == "Y" && board[1, 1] == "Y" && board[2, 0] == "Y"))
{
victory = true;
Console.Write(victory + " Victory for player " + currentPlayer);
}
}
Console.ReadLine();
}
At the top of your code, you have these definitions for player1 and player2:
string player1 = "X";
string player2 = "O";
However, all of your if statements look like this:
if ((board[0, 0] == "X" && board[1, 0] == "X" && board[2, 0] == "X") || (board[0, 0] == "Y" && board[1, 0] == "Y" && board[2, 0] == "Y"))
You're checking for X and Y instead of X and O, hence why the win condition is never triggered for player2.
In Tic Tac Toe, Program works for horizontal, and vertical, but it does not work for diagonal.
I think that private bool checkWin part is wrong.
private bool checkWin()
{
for (int row=0; row<3; row++)
{
if (values[row,0] != ' ' && values[row,0]==values[row,1]&&values[row,0]==values[row, 2])
{
lockButton(false);
return true;
}
}
for (int col = 0; col < 3; col++)
{
if (values[0, col] != ' ' && values[0, col] == values[1, col] && values[0, col] == values[2, col])
{
lockButton(false);//asdfasdfsdafadsfasdfasdfasdf
return true;
}
}
return false;
}
Maybe you can try to add something like this betwen the second for and the return false:
if (values[0, 0] != ' ' && values[0, 0] == values[1, 1] && values[0, 0] == values[2, 2])
{
lockButton(false);
return true;
}else if(values[0, 2] != ' ' && values[0, 2] == values[1, 1] && values[0, 2] == values[2, 0])
{
lockButton(false);
return true;
}
The fist one is for diagonal for 0,0 to 2,2 and the second one for 0,2 to 2,0
Console App C#
I made a 2D array to represent a grid for the game. I cant find a way to check if the player has won.
I tried many things and searched online and I cant find it
I tried to loop thru the grid and check each one of the elements inside that row/column
I would love to get the explanation of why its not working and how to do it instead of the code answer.
public static void Win_check()
{
for (int i = 0; i < 3; i++)
{
if (grid[1, i] == 'x' && grid[1, i] == 'x' && grid[1, i] == 'x')
{
gameStatus = 'x';
}
if (grid[2, i] == 'o' && grid[2, i] == 'o' && grid[2, i] == 'o')
{
gameStatus = 'o';
}
if (grid[3, i] == 'o' && grid[3, i] == 'o' && grid[3, i] == 'o')
{
gameStatus = 'o';
}
if (grid[i, 1] == 'x' && grid[i, 1] == 'x' && grid[i, 1] == 'x')
{
gameStatus = 'x';
}
if (grid[i, 2] == 'o' && grid[i, 2] == 'o' && grid[i, 2] == 'o')
{
gameStatus = 'o';
}
if (grid[i, 3] == 'o' && grid[i, 3] == 'o' && grid[i, 3] == 'o')
{
gameStatus = 'o';
}
}
}
I suggest enumerating all the lines which bring win: 3 horizontal, 3 vertical and finally 2 diagonals.
private static IEnumerable<char[]> Lines(char[,] field) {
char[] result;
int size = Math.Min(field.GetLength(0), field.GetLength(1));
for (int r = 0; r < size; ++r) {
result = new char[size];
for (int c = 0; c < size; ++c)
result[c] = field[r, c];
yield return result;
}
for (int c = 0; c < size; ++c) {
result = new char[size];
for (int r = 0; r < size; ++r)
result[r] = field[r, c];
yield return result;
}
result = new char[size];
for (int d = 0; d < size; ++d)
result[d] = field[d, d];
yield return result;
result = new char[size];
for (int d = 0; d < size; ++d)
result[d] = field[d, size - d - 1];
yield return result;
}
Then you can easily query these lines with a help of Linq:
using System.Linq;
...
private static bool IsWon(char[,] field, char who) {
return Lines(field).Any(line => line.All(item => item == who));
}
For instance:
char[,] field = new char[,] {
{ 'x', 'o', 'o'},
{ 'x', 'o', ' '},
{ 'o', 'x', 'x'},
};
// Is field won by x?
Console.WriteLine(IsWon(field, 'x') ? "Y" : "N");
// Is field won by o?
Console.WriteLine(IsWon(field, 'o') ? "Y" : "N");
This question already has answers here:
C# Copy Array by Value
(8 answers)
Closed 4 years ago.
So, I'm trying to make simple Console Tic Tac Toe Game, with AI.
Current board state is stored inside multi-dimensional array.
To check what the best move for AI is, im using recursion.
The function "CheckMove" changes board position, and then calls itself to see where it goes.
But the problem is that if i change board state inside called function, it will also change inside a caller.
How to avoid that?
Simplified code:
static void Main()
{
int[] board = { 1 };
CheckMove(board);
//board = 2
}
static void CheckMove(int[] board)
{
board[0] = 2;
}
full code (WIP):
enum Sym
{
E, X, O
}
class Program
{
static void Main(string[] args)
{
Sym[,] board = new Sym[3,3];
int x, y;
while (End(board) == 2)
{
Display(board);
Console.WriteLine("Make your move - column: ");
x = Convert.ToInt32(Console.ReadLine())-1;
Console.WriteLine("Make your move - row: ");
y = Convert.ToInt32(Console.ReadLine())-1;
board[y, x] = Sym.X;
int[,] chances = new int[3, 3];
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
{
chances[i, j] = CheckMove(board, i, j, true);
}
}
Display(board);
Console.WriteLine("GG");
Console.WriteLine("____________________________________________________________");
Console.ReadLine();
}
static void Display(Sym[,] board) //Displays whole board
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine($"{board[i,0]} {board[i,1]} {board[i,2]}");
}
}
static int End(Sym[,] board) //Chcecks if the game shall end (-1 player won) (0 tie) (1 cpu won) (2 game in progress)
{
bool Full = true;
for (int i = 0; i < 3; i++)
{ //This part is currently broken
if (board[i, 0] == Sym.E || board[i, 1] == Sym.E || board[i, 2] == Sym.E) Full = false;
if (board[i, 0] == board[i, 1] && board[i, 1] == board[i, 2] && board[i, 0] == Sym.X) return -1;
if (board[0, i] == board[1, i] && board[1, i] == board[2, i] && board[0, i] == Sym.X) return -1;
if (board[i, 0] == board[i, 1] && board[i, 1] == board[i, 2] && board[i, 0] == Sym.O) return 1;
if (board[0, i] == board[1, i] && board[1, i] == board[2, i] && board[0, i] == Sym.O) return 1;
}
if (board[0, 0] == board[1, 1] && board[1, 1] == board[2, 2] && board[0, 0] == Sym.X) return -1;
if (board[0, 0] == board[1, 1] && board[1, 1] == board[2, 2] && board[0, 0] == Sym.O) return 1;
if (board[2, 0] == board[1, 1] && board[1, 1] == board[0, 2] && board[2, 0] == Sym.X) return -1;
if (board[2, 0] == board[1, 1] && board[1, 1] == board[0, 2] && board[2, 0] == Sym.O) return 1;
if (Full == true) return 0;
return 2;
}
static int CheckMove(Sym[,] board, int a, int b, bool cpuTurn) //Check how good subjected move is
{
if (board[a, b] == Sym.E)
if (cpuTurn == true) board[a, b] = Sym.O;
else board[a, b] = Sym.X;
else return 0;
if (End(board) != 2) return End(board);
int Value = 0;
for (int m = 0; m < 3; m++)
for(int n = 0; n < 3; n++)
{
Value += CheckMove(board, m, n, !cpuTurn);
}
return Value;
}
}
As far as I can see, you have problems with 2D array Sym[,] in the
static int CheckMove(Sym[,] board, int a, int b, bool cpuTurn)
method; since board is 2D array, typical solutions like board.ToArray() don't work (they don't even compile). Try Clone() the board instance:
// Let's rename board into value...
static int CheckMove(Sym[,] value, int a, int b, bool cpuTurn) {
// ... in order to preserve all the other code:
// we are now working with the copy of the passed board
Sym[,] board = value.Clone() as Sym[,];
// Your code from CheckMove here
...
}
Since Sym is a enum (i.e. a value type), shallow copy is enough
/*this is my ontroller and array object has to be converted to string type so that i can perform my action
namespace taskmvc.Controllers
{
public class dogController : Controller
{
public ActionResult dog()
{
string[,] array = new string[7, 14]
{
{"D","G","O","O","D","D","O","D","G","O","O","D","D","O"},
{"O","D","O","O","G","G","G","D","O","D","G","O","G","G"},
{"O","G","O","G","D","O","O","D","G","O","O","D","D","D"},
{"D","G","D","O","O","O","G","G","O","O","G","D","G","O"},
{"O","G","D","G","O","G","D","G","O","G","G","O","G","D"},
{"D","D","D","G","D","D","O","D","O","O","G","D","O","O"},
{"O","D","G","O","G","G","D","O","O","G","G","O","O","D"}
};
ViewData["f"] = array;
return View(array);
}
}
}
/* this is my view here you can see that my array is a string
#{
ViewBag.Title = "dog";
}
<h2>dog</h2>
#{
string [,] array = ViewData["f"];
int i, j;
var n = 7;
var m = 14;
}
#for (i = 0; i <n;i++)
{
for(j=0;j<m;j++)
{
if (array[i, j] == "D")
{
if (j + 1 < m && array[i, j + 1] == "O")//
{
if(j + 1 < m && array[i,j+1]=="G")
{
}
}
}
else if (i+1<n && j + 1 < m && array[i + 1, j + 1] == "O")
{
if (i + 1 < n && j + 1 < m && array[i + 1, j + 1] == "G")
{
}
}
else if (i - 1 >0 && array[i - 1, j] == "O")//
{
if (i - 1 > 0 && array[i - 1, j] == "G")
{
}
}
else if (j-1>0 && array[i, j - 1] == "O")
{
if (j - 1 > 0 && array[i, j - 1] == "G")
{
}
}
else if(i + 1< n && array[i +1, j ] == "O")
{
if (i + 1 < n && array[i + 1, j] == "G")
{
}
}
else if(i+1<n && j-1>0 && array[i+1,j-1]=="O")
{
if (i + 1 < n && j - 1 > 0 && array[i + 1, j - 1] == "G")
{
}
}
else if (i-1>0&&j-1>0&&array[i-1,j-1]=="O")
{
if (i - 1 > 0 && j - 1 > 0 && array[i - 1, j - 1] == "G")
{
}
}
else if(i-1>0&&j+1<m&&array[i-1,j+1]=="O")
{
if (i - 1 > 0 && j + 1 < m && array[i - 1, j + 1] == "G")
{
}
}
}
}
j < 14, so max(j) = 13, j+1 = 14, array[i,j+1] is illegal.
j+1 is out of range.
Check before:
if(j+1 < 14 && array[i,j+1]=="G")
...and in many other places.
Also, replace 7 and 14 with variables. For example:
var n = 7;
var m = 14;
if(j+1 < m && array[i,j+1]=="G")
Since you are checking array[i+1, j+1] , array[i - 1, j],array[i+1,j-1], then you should use the following loop :
for (i = 1; i <6i++)
{
for(j=1;j<13;j++)
{
...
You are trying to access array[i,j+1]
for last index j=13, and j+1 will be "14"
So you are accessing array[i,14]
As this memory location not allocated to to your array, You will get index out of range exception.
Try looping from i=0; j=0; and avoid situation like array[i-1,j-1]. (Boundary Conditions)