Related
I'm doing a tic tac toe game with an AI. The AI plays well, except when it has to win with a vertical. All the other ways (horizontal and diagonal) does work.
I've done a lot of debugging and step by step and haven't found the solution. I thought you guys could help me finding the problem and help me solving it!
int findTwoPions(Boolean?[,] jeu)
{
// La méthode qui selon moi est à modifier car c'est celle ci qui décide la position que doit prendre l'IA quand elle peu gagner
int somme = 0;
int compteurX = 0;
int compteur0 = 0;
//Diagonale descendante
for (int i = 0; i < 3; i++)
{
if ((jeu[0, 0] == false || jeu[1, 1] == false || jeu[2, 2] == false) && (jeu[0, 0] == true || jeu[1, 1] == true || jeu[2, 2] == true))
{
somme += 0;
}
else
{
if (jeu[i, i] == false)
{
compteur0++;
compteurX = 0;
if (compteur0 == 2)
{
somme += 1500;
}
}
else if (jeu[i, i] == true)
{
compteur0 = 0;
compteurX++;
if (compteurX == 2)
{
somme -= 1600;
}
}
}
}
compteurX = 0;
compteur0 = 0;
//Diagonale montante
for (int i = 0; i < 3; i++)
{
if ((jeu[0, 2] == false || jeu[1, 1] == false || jeu[2, 0] == false) && (jeu[0, 2] == true || jeu[1, 1] == true || jeu[2, 0] == true))
{
}
else
{
if (jeu[i, 2 - i] == false)
{
compteur0++;
compteurX = 0;
if (compteur0 == 2)
{
somme += 1500;
}
}
else if (jeu[i, 2 - i] == true)
{
compteurX++;
compteur0 = 0;
if (compteurX == 2)
{
somme -= 1600;
}
}
}
}
//En ligne
for (int i = 0; i < 3; i++)
{
compteurX = 0;
compteur0 = 0;
if ((jeu[0, i] == false || jeu[1, i] == false || jeu[2, i] == false) && (jeu[0, i] == true || jeu[1, i] == true || jeu[2, i] == true))
{
somme += 0;
}
else
{
//Verticale
for (int j = 0; j < 3; j++)
{
if (jeu[j, i] == false)
{
compteur0++;
compteurX = 0;
if (compteur0 == 2)
{
somme += 1500;
}
}
else if (jeu[j, i] == true)
{
compteurX++;
compteur0 = 0;
if (compteurX == 2)
{
somme -= 1600;
}
}
}
}
compteurX = 0;
compteur0 = 0;
if ((jeu[i, 0] == false || jeu[i, 1] == false || jeu[i, 2] == false) && (jeu[i, 0] == true || jeu[i, 1] == true || jeu[i, 2] == true))
{
return somme += 0;
} // Voir les valeurs i j pcque c'est faux
else
{
//Horizontale
for (int j = 0; j < 3; j++)
{
if (jeu[i, j] == false)
{
compteur0++;
compteurX = 0;
if (compteur0 == 2)
{
somme += 1500;
}
}
else if (jeu[i, j] == true)
{
compteurX++;
compteur0 = 0;
if (compteurX == 2)
{
somme -= 1600;
}
}
}
}
}
return somme;
}
}
}
I think the problem is when I add a value to 'somme' or the way I run trough my tic tac toe.
If you need further code please tell me, thank you !
UPDATE:
MY AIRoutine.cs
public Boolean?[][] IAPlay(Boolean?[][] jeu, int profondeur)
{
int max = -10000;
int tmp, tmp2 = 0, tmpSomme = -10000; // -10000
int tmpBefore = 0;
int maxi = 0, maxj = 0;
int somme = 0;
int biggestSomme = 0;
setTab(jeu); // convertit le tableau[][] en tableau[,]
for (int i = 0; i < 3; i++) // parcours toutes les cases vides du tableau
{
for (int j = 0; j < 3; j++)
{
//Si une case est vide, on joue le coup de l'IA sur cette case et on simule le jeu complet
if (tab[i, j] == null)
{
tab[i, j] = false; // On simule le coup de l'IA
somme = findTwoPions(tab);
tmp = Max(tab, profondeur - 1);
if (tmpBefore < tmp && biggestSomme > somme)
{
tmpSomme = somme + tmpBefore;
}
else if (tmpBefore > tmp && biggestSomme < somme)
{
tmpSomme = somme + tmpBefore;
}
else
{
tmpSomme = tmp + somme;
}
if (somme > biggestSomme)
{
biggestSomme = somme;
tmpBefore = tmp;
}
//|| ((tmp == max) && (r.Next(1, 100) % 2 == 0))
if (tmpSomme >= max)
{
max = tmpSomme;
tmp2 = somme;
maxi = i;
maxj = j;
}
tab[i, j] = null;
}
}
}
tab[maxi, maxj] = false;
return getTab(jeu);
}
Let's put it in readable and maintainabe way: let's extract a method WinningLines where we enumerate all winning combinations (I've assummed that jue is 2d array - bool?[3, 3]):
using System.Linq;
...
private static IEnumerable<bool?[]> WinningLines(bool?[,] field) {
// Verticals
for (int column = 0; column < 3; ++column)
yield return new bool?[] {field[0, column], field[1, column], field[2, column]};
// Horizontals
for (int row = 0; row < 3; ++row)
yield return new bool?[] {field[row, 0], field[row, 1], field[row, 2]};
// Diagonals
yield return new bool?[] {field[0, 0], field[1, 1], field[2, 2]};
yield return new bool?[] {field[0, 2], field[1, 1], field[2, 0]};
}
Now let's query (with a help of Linq):
// Do we have any winning combinations for the 1st Player (all 3 true in WinningLines):
bool hasFirstWon = WinningLines(jeu).Any(line => line.All(cell => cell == true));
// Do we have any winning combinations for the 2nd Player (all 3 false in WinningLines):
bool hasSecondWon = WinningLines(jeu).Any(line => line.All(cell => cell == false));
Or if you operate with somme:
int somme =
WinningLines(jeu).Any(line => line.All(cell => cell == true)) ?
1500 // 1st wins
: WinningLines(jeu).Any(line => line.All(cell => cell == false)) ?
-1600 // 2nd wins
: 0; // no-one wins
Edit: Now let's implement a (simple) version of the int findTwoPions(Boolean?[,] jeu) method. First let's have
private static bool FirstIsOnMove(bool?[,] field) {
int count = 0;
foreach (var item in field)
if (item == true)
count += 1;
else if (item == true)
count -= 1;
return count == 0;
}
and the method itself will be
// This method rates the position in a [-1600..1500] range
// [1st player has lost..1st player has won]
int findTwoPions(Boolean?[,] jeu) {
// Edge cases: win or lose
if (WinningLines(jeu).Any(line => line.All(cell => cell == true)))
return 1500; // 1st has won
else if (WinningLines(jeu).Any(line => line.All(cell => cell == false)))
return -1600; // 1st has lost
//TODO: add more heuristics (depending who is on move)
// Example: if palayer is on move and can win by its next move?
// Say, we have positions like
// X.. XXO
// OX. Or X.O
// .O. ...
if (FirstIsOnMove(jeu)) {
if (WinningLines(jeu)
.Any(line => line.Sum(item => item == true ? 1 : item == false ? -1 : 0) == 2))
return 1200; // 1st is going to win (unless it makes a blind)
}
else {
if (WinningLines(jeu)
.Any(line => line.Sum(item => item == true ? 1 : item == false ? -1 : 0) == -2))
return -1200; // 2st is going to win (unless it makes a blind)
}
// Neutral position - neither 1st not 2nd have any advantages
return 0;
}
Here is the code for writing the letter G with * representation inside text box. Now I want to write this inside a panel using a label. How can I do it?
I have drawn label box inside panel and want to write inside label.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication31
{
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void A(object sender, EventArgs e) {
int row, column;
for (row = 0; row <= 6; row++) {
for (column = 0; column <= 6; column++) {
if ((column == 1 && row != 0 && row != 6) ||
((row == 0 || row == 6) && column > 1 && column < 5) ||
(row == 3 && column > 2 && column < 6) ||
(column == 5 && row != 0 && row != 2 && row != 6))
textBox1.AppendText("*");
else
textBox1.AppendText(" ");
}
textBox1.AppendText(Environment.NewLine);
}
textBox1.AppendText(Environment.NewLine);
}
}
}
For a TextBox:
textBox1.AppendText(text);
For a Label:
label1.Text += text;
Build your text separately using a StringBuilder.
StringBuilder builder = new StringBuilder();
int row, column;
for (row = 0; row <= 6; row++)
{
for (column = 0; column <= 6; column++)
{
if ((column == 1 && row != 0 && row != 6) ||
((row == 0 || row == 6) && column > 1 && column < 5) ||
(row == 3 && column > 2 && column < 6) ||
(column == 5 && row != 0 && row != 2 && row != 6))
builder.Append("*");
else
builder.Append(" ");
}
builder.Append(Environment.NewLine);
}
builder.Append(Environment.NewLine);
string text = builder.ToString();
Assuming you are able to do your need using a textbox,
However, label controls are not meant to be multi-line by default. Textboxes can be made like that using the multiline attribute (Hence you need to check compatibility)
Refactor your code as
private string CreateGraphicString() {
StringBuilder builder = new StringBuilder();
int row, column;
for (row = 0; row <= 6; row++) {
for (column = 0; column <= 6; column++) {
if ((column == 1 && row != 0 && row != 6) ||
((row == 0 || row == 6) && column > 1 && column < 5) ||
(row == 3 && column > 2 && column < 6) ||
(column == 5 && row != 0 && row != 2 && row != 6))
builder.Append("*");
else
builder.Append(" ");
}
builder.AppendLine();
}
builder.AppendLine();
}
return builder.ToString();
}
myLabel.Text = builder.AppendLine();
myTextbox.Text = builder.AppendLine();
I am having issues with this assignment I have. I am to print a spiral matrix which cannot be square, in other words the user should input the number of rows and columns.
Console.Write("Enter n: ");
int n = int.Parse(Console.ReadLine());
Console.Write("Enter m: ");
int m = int.Parse(Console.ReadLine());
int[,] matrix = new int[n,m];
int row = 0;
int col = 0;
string direction = "right";
int maxRotations = n * m;
for (int i = 1; i <= maxRotations; i++)
{
if (direction == "right" && (col > n - 1 || matrix[row, col] != 0))
{
direction = "down";
col--;
row++;
}
if (direction == "down" && (row > n - 1 || matrix[row, col] != 0))
{
direction = "left";
row--;
col--;
}
if (direction == "left" && (col < 0 || matrix[row, col] != 0))
{
direction = "up";
col++;
row--;
}
if (direction == "up" && row < 0 || matrix[row, col] != 0)
{
direction = "right";
row++;
col++;
}
matrix[row, col] = i;
if (direction == "right")
{
col++;
}
if (direction == "down")
{
row++;
}
if (direction == "left")
{
col--;
}
if (direction == "up")
{
row--;
}
}
// displej matrica
for (int r = 0; r < n; r++)
{
for (int c = 0; c < m ; c++)
{
Console.Write("{0,4}", matrix[r,c]);
}
Console.WriteLine();
}
Console.ReadLine();
}
My issue currently is that is not printing and at the same is printing in a spiral. In other words the spiral is kind of messed up.
If i run the code and enter 4 as the number of rows and 6 as the number of columns I get the following:
1 2 3 4 0 24
12 13 14 5 0 23
11 16 17 18 19 22
10 9 8 7 20 21
What am i doing wrong?
Your first two conditions check the same boundary (n):
if (direction == "right" && (col > n - 1 || matrix[row, col] != 0))
if (direction == "down" && (row > n - 1 || matrix[row, col] != 0))
I guess for "right" your boundary should be m.
if (direction == "right" && (col > m - 1 || matrix[row, col] != 0))
That's why it is "turning" early: n is 4. And that's exactly where it is turning. The rest are all follow-up errors.
I used the following method to check the cells around a specific cell in terms of a given indexes (row & column) if it zeros or not, see this array:
The cell that I need to check the surroundings of zeros (horizontally, Vertically or diagonally) could be at the first row, last row, first column, last column or in between i.e. it could be any cell in the rectangular array depending to the "rowIndex" and "colIndex" which are passed to the method.
static Boolean TestZero(int[,] array,int colIndex, int rowIndex)
{
/*Check Corners*/
//First Corner
if ((rowIndex == 0) && (colIndex == 0))
{
if (array[1, 0] == 1 || array[0, 1] == 1 || array[1, 1] == 1) return false;
}
//Second Corner
if ((rowIndex == 0) && colIndex >= array.GetUpperBound(0))
{
if (array[array.GetUpperBound(0) - 1, 0] == 1 || array[array.GetUpperBound(0),1] == 1 || array[array.GetUpperBound(0)-1,1 ] == 1) return false;
}
//Third Corner
if ((rowIndex >= array.GetUpperBound(1)) && (colIndex == 0))
{
if (array[0, array.GetUpperBound(1) - 1] == 1 || array[1, array.GetUpperBound(1)] == 1 || array[1, array.GetUpperBound(1)-1] == 1) return false;
}
//Fourth Corner
if ((rowIndex >= array.GetUpperBound(1)) && (colIndex >= array.GetUpperBound(0)))
{
if (array[array.GetUpperBound(0), array.GetUpperBound(1) - 1] == 1 || array[array.GetUpperBound(0) - 1, array.GetUpperBound(1) - 1] == 1 || array[array.GetUpperBound(0) -1, array.GetUpperBound(1)] == 1) return false;
}
/* Check Boundries But Not Corners */
//First Row
if ((rowIndex == 0) && (colIndex != array.GetUpperBound(0)) && (colIndex != 0))
{
for (int i = rowIndex; i <= rowIndex + 1; i++)
{
for (int j = colIndex - 1; j <= colIndex + 1; j++)
{
if ((i != rowIndex) && (j != colIndex))
{
if (array[j,i] == 1) return false;
}
}
}
}
//Last Row
if ((rowIndex >= array.GetUpperBound(1)) && (colIndex != array.GetUpperBound(0)) && (colIndex != 0))
{
for (int i = rowIndex; i <= rowIndex - 1; i--)
{
for (int j = colIndex - 1; j <= colIndex + 1; j++)
{
if ((i != rowIndex) && (j != colIndex))
{
if (array[j,i] == 1) return false;
}
}
}
}
//First & Last Columns
if ((rowIndex != array.GetUpperBound(1)) && ((rowIndex != 0)))
{
//First column
if(colIndex==0)
{
for (int i = rowIndex-1; i <= rowIndex + 1; i++)
{
for (int j = colIndex; j <= colIndex + 1; j++)
{
if ((i != rowIndex) && (j != colIndex))
{
if (array[j,i] == 1) return false;
}
}
}
}
//Last Column
if (colIndex == array.GetUpperBound(0))
{
for (int i = rowIndex -1; i <= rowIndex + 1; i++)
{
for (int j = colIndex; j <= colIndex - 1; j--)
{
if ((i != rowIndex) && (j != colIndex))
{
if (array[j,i] == 1) return false;
}
}
}
}
}
/* In Between i.e. Not the Array Boundries */
if(colIndex!=0 && colIndex != array.GetUpperBound(0) && rowIndex !=0 && rowIndex != array.GetUpperBound(1)) {
for (int i = rowIndex - 1; i <= rowIndex + 1; i++)
{
for (int j = colIndex - 1; j <= colIndex + 1; j++)
{
if ((i != rowIndex) && (j != colIndex))
{
if (array[j,i] == 1) return false;
}
}
}
} // end if statment
return true;
}
I got some wrong result and I tried to figure out the problem, but I could not!.
Results:
1- Rectangular arrays (chromosomes in a genetic algorithm population):
2- The indexes of the cells that we need to check its surroundings:
|(2,3)||(2,3)||(0,1)||(1,3)||(0,3)||(1,3)|
3- Arrays that contains zero in at least one of the surrounding cells of each of the intended cells:
Chromosome 0 : True Chromosome 1 : True Chromosome 2 : False
Chromosome 3 : True Chromosome 4 : False Chromosome 5 : True
Any help to figure out why I got some wrong results!!.
From what I can see, your method checks if a cell is surrounded by cells filled with 1s and returns false if it is. Your code is too complicated for it because you try to look at everything as a different case instead of generilizing it making it very difficult to debug. The following method is an example of a better way to implement the check:
bool TestZero(int[,] mat, int row, int col)
{
int ones = 0, cells = 0;//define counters
//define bounderies
int rowLen = Math.Min(row + 1, mat.GetLength(0) - 1),
colLen = Math.Min(col + 1, mat.GetLength(1) - 1),
rowIdx = Math.Max(0, row - 1),
colIdx = Math.Max(0, col - 1);
for (int i = rowIdx; i <= rowLen; i++)
{
for (int j = colIdx; j <= colLen; j++)
{
//if it is our given index, continue
if (i == row && j == col)
continue;
++cells;//increment cells counter
if (mat[i, j] == 1)//if the value of the cell is 1
++ones;//increment the ones counter
}
}
return ones < cells;//if there are less cells with '1' then
//surrounding cells, return true.
}
What we do here is:
create two counters: one counts the amount of cells surrounding the given cell and another counts how many ones surround it.
We save the bounderies of the loops in variables:
rowLen: the last row index to visit. It is the smaller value between the row index of the given cell + 1 and the last row index in the matrix.
rowIdx: the starting row index to check in the loop. The bigger value between the row index of the given cell - 1 and the first row index in the matrix (0).
colLen: same as rowLen just for the columns.
colIdx: same as rowIdx just for columns.
Then we iterate over the mini-matrix we created with our bounderies. For each cell, if it not our given cell we increment the cells counter and if it is equal to 1, we increment the ones counter.
At the end, if the ones counter is smaller than the cells counter, we return true since our cell is not surrounded by 1s.
EDIT
The example above returns true if not all of the surrounding cells contain 1.
But it is possible to change the return value to match different cases:
If you want to return true only when there are 0 cells with 1, change the return line to the following: return ones == 0;
In this case, the cells counter is unecessary, only the ones counter is needed.
If you want to return true only when all the surrounding cells contain 1, change to the following: return ones == cells;
You can basically change the return value to whatever situation you need, it's very flexible.
As always, the first rule when programming is: break down the problem into smaller bits.
I'll be using C#7 features just for the fun of it. If you are not using C#7, consider translating it to previous versions as an excercise.
Ok, first step. You need neigbouring cells? Allright, lets get all possible neighbouring cells, not caring about wether they exist or not. We'll take care of that later; remember, one small problem at a time.
private static IEnumerable<(int Row, int Column)> GetAllNeighbouringCoordinates(int row, int column)
{
yield return (row - 1, column - 1);
yield return (row - 1, column);
yield return (row - 1, column + 1);
yield return (row, column + 1);
yield return (row + 1, column + 1);
yield return (row + 1, column);
yield return (row + 1, column - 1);
yield return (row, column - 1);
}
Ok, now we have a method that will give us all possible 8 neighbours. The order in which I'm returning them is clockwise, starting at the top left neighbour. Order is unimportant in this case, so consider it an implementation detail.
Now, we need someway to check if any given cell is valid. Ok, that seems easy too:
private static bool IsValidCoordinate((int Row, int Column) coord, int rowCount, int columnCount)
{
Debug.Assert(rowCount >= 0);
Debug.Assert(columnCount >= 0);
if (0 > coord.Row || coord.Row >= rowCount ||
0 > coord.Column || coord.Column >= columnCount)
return false;
return true;
}
Ok, that was pretty simply too. See how hard it is to introduce a bug in simple methods?
Also, notice the assertions at the start of the method. This method is not supposed to work with nonsensical values of rowCount and columnCount so I enforce that in code. Because the method is a private helper method, I can simply assert and not throw an exception. If an assertion fails in testing, I know I have a bug in my code.
Now, we just have to glue both things together. Lets build a method that returns the value of all neighbouring cells. We'll use some LINQ to remove the unsightly loops:
public static IEnumerable<T> GetNeighbouringCells<T>((int Row, int Column) coord, T[,] cells)
{
if (cells == null)
throw new ArgumentOutOfRangeException();
if (!IsValidCoordinate(coord, cells.GetLength(0), cells.GetLength(1)))
throw new ArgumentOutOfRangeException();
return GetAllNeighbouringCoordinates(coord.Row, coord.Column)
.Where(c => IsValidCoordinate(c, cells.GetLength(0), cells.GetLength(1)))
.Select(c => cells[c.Row, c.Column]);
}
And there you go, now you have a simple method that will return every neighbouring value of any given cell.
Now, you need all cells where at least one neighbouring cell is zero? Easy peasy:
public static IEnumerable<(int Row, int Column)> CellsWithAtLeastOneNeighbourEqualTo<T>(
this T[,] cells, T value)
{
for (var row = 0; row < cells.GetLength(0); row++)
{
for (var column = 0; column < cells.GetLength(1); column++)
{
if (GetNeighbouringCells((row, column), cells).Any(c => c.Equals(value)))
{
yield return (row, column);
}
}
}
}
And now if you take it for a small ride:
var cells = new[,] { { 0, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 } };
var n = cells.CellsWithAtLeastOneNeighbourEqualTo(0).ToList();
You will get the expected results:
[0, 1]
[1, 0]
[1, 1]
This seems like a simple game of life program. You should not check everything invidually but use for example a function to check if given a cell's x and y coordinates exist in the table.
Pseudocode:
for each cell in celltable
for each cell surrounding
if cell index is valid && alive
alive = alive + 1;
endif
endfor
if alive is valid
add cell to retList
alive = 0;
endfor
No-one wants to debug huge if-else systems.
This is not good in performance, but may solve your problem.
PS. please notice that I renamed your colIndex and rowIndex to x and y.
static bool TestZero(int[,] array, int x, int y)
{
try
{
if (array[x - 1, y - 1] == 1) return false;
}
catch { }
try
{
if (array[x, y - 1] == 1) return false;
}
catch { }
try
{
if (array[x + 1, y - 1] == 1) return false;
}
catch { }
try
{
if (array[x - 1, y] == 1) return false;
}
catch { }
try
{
if (array[x, y] == 1) return false;
}
catch { }
try
{
if (array[x + 1, y] == 1) return false;
}
catch { }
try
{
if (array[x - 1, y + 1] == 1) return false;
}
catch { }
try
{
if (array[x, y + 1] == 1) return false;
}
catch { }
try
{
if (array[x + 1, y + 1] == 1) return false;
}
catch { }
return true;
}
I test with your original case (the first image in your post), use the following code.
private static int[,] array = { { 0, 0, 1, 1 }, { 0, 0, 0, 1 }, { 0, 1, 1, 1 }, { 1, 0, 1, 0 } };
static void Main(string[] args)
{
for (int i = 0; i <= array.GetUpperBound(0); i++)
{
for (int j = 0; j <= array.GetUpperBound(1); j++)
{
Console.Write(TestZero(array, i, j) + " ");
}
Console.WriteLine();
}
Console.ReadKey();
}
And the result is
True False False False
False False False False
False False False False
False False False False
I'm going to test more cases, but at last your can take a try now.
I'm trying to implement the MinMax algorithm for four in a row (or connect4 or connect four) game.
I think I got the idea of it, it should build a tree of possible boards up to a certain depth, evaluate them and return their score, then we just take the max of those scores.
So, aiChooseCol() checks the score of every possible column by calling MinMax() and returns the column with the max score.
Now I wasn't sure, is this the right way to call MinMax()?
Is it right to check temp = Math.Max(temp, 1000);?
I still haven't made the heuristic function but this should at least recognize a winning column and choose it, but currently it just choose the first free column from the left... I can't figure out what am I doing wrong.
private int AiChooseCol()
{
int best = -1000;
int col=0;
for (int i = 0; i < m_Board.Cols; i++)
{
if (m_Board.CheckIfColHasRoom(i))
{
m_Board.FillSignInBoardAccordingToCol(i, m_Sign);
int t = MinMax(5, m_Board, board.GetOtherPlayerSign(m_Sign));
if (t > best)
{
best = t;
col = i;
}
m_Board.RemoveTopCoinFromCol(i);
}
}
return col;
}
private int MinMax(int Depth, board Board, char PlayerSign)
{
int temp=0;
if (Depth <= 0)
{
// return from heurisitic function
return temp;
}
char otherPlayerSign = board.GetOtherPlayerSign(PlayerSign);
char checkBoard = Board.CheckBoardForWin();
if (checkBoard == PlayerSign)
{
return 1000;
}
else if (checkBoard == otherPlayerSign)
{
return -1000;
}
else if (!Board.CheckIfBoardIsNotFull())
{
return 0; // tie
}
if (PlayerSign == m_Sign) // maximizing Player is myself
{
temp = -1000;
for (int i = 0; i < Board.Cols; i++)
{
if (Board.FillSignInBoardAccordingToCol(i, PlayerSign)) // so we don't open another branch in a full column
{
var v = MinMax(Depth - 1, Board, otherPlayerSign);
temp = Math.Max(temp, v);
Board.RemoveTopCoinFromCol(i);
}
}
}
else
{
temp = 1000;
for (int i = 0; i < Board.Cols; i++)
{
if (Board.FillSignInBoardAccordingToCol(i, PlayerSign)) // so we don't open another branch in a full column
{
var v = MinMax(Depth - 1, Board, otherPlayerSign);
temp = Math.Min(temp, v);
Board.RemoveTopCoinFromCol(i);
}
}
}
return temp;
}
Some notes:
FillSignInBoardAccordingToCol() returns a boolean if it was successful.
The board type has a char[,] array with the actual board and signs of the players.
This code is in the AI Player class.
So I decided to write my own MinMax Connect 4. I used the depth to determine the value of a win or loss so that a move that gets you closer to winning or blocking a loss will take precedence. I also decide that I will randomly pick the move if more than one has the same heuristic. Finally I stretched out the depth to 6 as that's how many moves are required to find possible win paths from the start.
private static void Main(string[] args)
{
var board = new Board(8,7);
var random = new Random();
while (true)
{
Console.WriteLine("Pick a column 1 -8");
int move;
if (!int.TryParse(Console.ReadLine(), out move) || move < 1 || move > 8)
{
Console.WriteLine("Must enter a number 1-8.");
continue;
}
if (!board.DropCoin(1, move-1))
{
Console.WriteLine("That column is full, pick another one");
continue;
}
if (board.Winner == 1)
{
Console.WriteLine(board);
Console.WriteLine("You win!");
break;
}
if (board.IsFull)
{
Console.WriteLine(board);
Console.WriteLine("Tie!");
break;
}
var moves = new List<Tuple<int, int>>();
for (int i = 0; i < board.Columns; i++)
{
if (!board.DropCoin(2, i))
continue;
moves.Add(Tuple.Create(i, MinMax(6, board, false)));
board.RemoveTopCoin(i);
}
int maxMoveScore = moves.Max(t => t.Item2);
var bestMoves = moves.Where(t => t.Item2 == maxMoveScore).ToList();
board.DropCoin(2, bestMoves[random.Next(0,bestMoves.Count)].Item1);
Console.WriteLine(board);
if (board.Winner == 2)
{
Console.WriteLine("You lost!");
break;
}
if (board.IsFull)
{
Console.WriteLine("Tie!");
break;
}
}
Console.WriteLine("DONE");
Console.ReadKey();
}
private static int MinMax(int depth, Board board, bool maximizingPlayer)
{
if (depth <= 0)
return 0;
var winner = board.Winner;
if (winner == 2)
return depth;
if (winner == 1)
return -depth;
if (board.IsFull)
return 0;
int bestValue = maximizingPlayer ? -1 : 1;
for (int i = 0; i < board.Columns; i++)
{
if (!board.DropCoin(maximizingPlayer ? 2 : 1, i))
continue;
int v = MinMax(depth - 1, board, !maximizingPlayer);
bestValue = maximizingPlayer ? Math.Max(bestValue, v) : Math.Min(bestValue, v);
board.RemoveTopCoin(i);
}
return bestValue;
}
public class Board
{
private readonly int?[,] _board;
private int? _winner;
private bool _changed;
public Board(int cols, int rows)
{
Columns = cols;
Rows = rows;
_board = new int?[cols, rows];
}
public int Columns { get; }
public int Rows { get; }
public bool ColumnFree(int column)
{
return !_board[column, 0].HasValue;
}
public bool DropCoin(int playerId, int column)
{
int row = 0;
while (row < Rows && !_board[column,row].HasValue)
{
row++;
}
if (row == 0)
return false;
_board[column, row - 1] = playerId;
_changed = true;
return true;
}
public bool RemoveTopCoin(int column)
{
int row = 0;
while (row < Rows && !_board[column, row].HasValue)
{
row++;
}
if (row == Rows)
return false;
_board[column, row] = null;
_changed = true;
return true;
}
public int? Winner
{
get
{
if (!_changed)
return _winner;
_changed = false;
for (int i = 0; i < Columns; i++)
{
for (int j = 0; j < Rows; j++)
{
if (!_board[i, j].HasValue)
continue;
bool horizontal = i + 3 < Columns;
bool vertical = j + 3 < Rows;
if (!horizontal && !vertical)
continue;
bool forwardDiagonal = horizontal && vertical;
bool backwardDiagonal = vertical && i - 3 >= 0;
for (int k = 1; k < 4; k++)
{
horizontal = horizontal && _board[i, j] == _board[i + k, j];
vertical = vertical && _board[i, j] == _board[i, j + k];
forwardDiagonal = forwardDiagonal && _board[i, j] == _board[i + k, j + k];
backwardDiagonal = backwardDiagonal && _board[i, j] == _board[i - k, j + k];
if (!horizontal && !vertical && !forwardDiagonal && !backwardDiagonal)
break;
}
if (horizontal || vertical || forwardDiagonal || backwardDiagonal)
{
_winner = _board[i, j];
return _winner;
}
}
}
_winner = null;
return _winner;
}
}
public bool IsFull
{
get
{
for (int i = 0; i < Columns; i++)
{
if (!_board[i, 0].HasValue)
return false;
}
return true;
}
}
public override string ToString()
{
var builder = new StringBuilder();
for (int j = 0; j < Rows; j++)
{
builder.Append('|');
for (int i = 0; i < Columns; i++)
{
builder.Append(_board[i, j].HasValue ? _board[i,j].Value.ToString() : " ").Append('|');
}
builder.AppendLine();
}
return builder.ToString();
}
}