Related
I've been trying to implement minimax algorithm in my C# chess engine for a week now and it makes legal moves but not meaningful moves. I cannot find the error, hopefully someone can spot it. I have tried to isolate the problem by testing each method and they all seem to work correctly except minimax.
public enum Piece
{
Empty, Pawn_W, Pawn_B, Knight_W, Knight_B,
Bishop_W, Bishop_B, Rook_W, Rook_B,
Queen_W, Queen_B, King_W, King_B
}
public bool IsPieceWhite(Piece piece)
{
if (piece == Piece.Pawn_W || piece == Piece.Knight_W ||
piece == Piece.Bishop_W || piece == Piece.Rook_W ||
piece == Piece.Queen_W || piece == Piece.King_W)
return true;
else return false;
}
public bool IsPieceBlack(Piece piece)
{
if (piece == Piece.Pawn_B || piece == Piece.Knight_B ||
piece == Piece.Bishop_B || piece == Piece.Rook_B ||
piece == Piece.Queen_B || piece == Piece.King_B)
return true;
else return false;
}
public int GetPieceWorth(Piece piece)
{
if (piece == Piece.Pawn_W || piece == Piece.Pawn_B)
return 1;
if (piece == Piece.Knight_W || piece == Piece.Knight_B)
return 3;
if (piece == Piece.Bishop_W || piece == Piece.Bishop_B)
return 3;
if (piece == Piece.Rook_W || piece == Piece.Rook_B)
return 5;
if (piece == Piece.Queen_W || piece == Piece.Queen_B)
return 9;
if (piece == Piece.King_W || piece == Piece.King_B)
return 9999999;
return 0;
}
Piece[,] CurrentBoard = GetStartingBoard();
Piece[,] bestMove;
public int depthB = 3;
public double minimax(Piece[,] board, int depth, bool maximizingPlayer)
{
if (depth == 0)
{
double result = EvaluatePosition(board, maximizingPlayer);
return result;
}
if (maximizingPlayer)
{
double best = Double.MinValue;
double value = Double.MinValue;
foreach (var move in GenerateMoves(board, maximizingPlayer))
{
Piece[,] clonedMove = CloneBoard(move);
value = Math.Max(value, minimax(clonedMove, depth - 1, false));
if (depth == depthB && value >= best)
{
best = value;
bestMove = clonedMove;
}
}
return value;
}
else
{
double best = Double.MaxValue;
double value = Double.MaxValue;
foreach (var move in GenerateMoves(board, maximizingPlayer))
{
Piece[,] clonedMove = CloneBoard(move);
value = Math.Min(value, minimax(clonedMove, depth - 1, true));
if (depth == depthB && value <= best)
{
best = value;
bestMove = clonedMove;
}
}
return value;
}
}
public Piece[,] CloneBoard(Piece[,] boardPos)
{
Piece[,] copy = boardPos.Clone() as Piece[,];
return copy;
}
public double EvaluatePosition(Piece[,] boardPos, bool ForWhite)
{
double eval = 0;
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
if (boardPos[i, j] != Piece.Empty)
{
if (IsPieceWhite(boardPos[i, j]))
{
eval += GetPieceWorth(boardPos[i, j]);
}
else if (IsPieceBlack(boardPos[i, j]))
{
eval -= GetPieceWorth(boardPos[i, j]);
}
}
}
}
if (ForWhite)
return eval;
else
return eval * -1;
}
//a-h,0-7
//Piece[,] board = new Piece[8, 8];
public static Piece[,] GetStartingBoard()
{
Piece[,] board = new Piece[8, 8];
for (int i = 0; i < 8; i++)
{
//initiate pawns
board[1, i] = Piece.Pawn_W;
board[6, i] = Piece.Pawn_B;
}
//white pieces
board[0, 0] = Piece.Rook_W;
board[0, 1] = Piece.Knight_W;
board[0, 2] = Piece.Bishop_W;
board[0, 3] = Piece.Queen_W;
board[0, 4] = Piece.King_W;
board[0, 5] = Piece.Bishop_W;
board[0, 6] = Piece.Knight_W;
board[0, 7] = Piece.Rook_W;
//black pieces
board[7, 0] = Piece.Rook_B;
board[7, 1] = Piece.Knight_B;
board[7, 2] = Piece.Bishop_B;
board[7, 3] = Piece.Queen_B;
board[7, 4] = Piece.King_B;
board[7, 5] = Piece.Bishop_B;
board[7, 6] = Piece.Knight_B;
board[7, 7] = Piece.Rook_B;
//test
//board[1, 4] = Piece.Pawn_B;
//board[6, 2] = Piece.Pawn_W;
return board;
}
I have uploaded a short clip of the engine playing against itself, to show the wierd moves: https://www.youtube.com/watch?v=A0HVgXYSciY
Finally was able to make the minimax function work. Thanks for all the help!
Working method:
public int minimax(Piece[,] board, int depth, bool maximizingPlayer, bool WhiteToPlay)
{
if (depth == 0)
{
int result = EvaluatePosition(board, WhiteToPlay);
return result;
}
var moves = GenerateMoves(board, WhiteToPlay);
if (maximizingPlayer)
{
int value = int.MinValue;
foreach (var move in moves)
{
int minmaxResult = minimax(move, depth - 1, false, !WhiteToPlay);
value = Math.Max(value, minmaxResult);
if (depth == depthB)
{
moveScores.Add(move, minmaxResult);
}
}
return value;
}
else
{
int value = int.MaxValue;
foreach (var move in moves)
{
int minmaxResult = minimax(move, depth - 1, true, !WhiteToPlay);
value = Math.Min(value, minmaxResult);
if (depth == depthB)
{
moveScores.Add(move, minmaxResult);
}
}
return value;
}
}
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");
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;
}
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)