ArgumentException & process memory keeps increasing in C# - c#

I have wrote and modified the source code of the classic snake game. As the level increases, more apples are being generated (e.g. level 1 will generate 1 apple, level 2 will generate 2 apple, and so on.) As i get to level 5, 5 apple were being generated, along with tremendous increases of my process memory, from 400MB to 2GB. That is where "ArgumentException" error pop up and the game crashed. Sorry for my bad coding as i am still learning.
The error showing to my draw() method where i called every 500ms for refreshing the board.
Error occurs at draw() method in Board.cs
public void draw(Position p, Image pic)
{
squares[p.getRowNo(), p.getColNo()].Image = pic;
}
refresh method in form1.cs
private void refresh(Object myObject, EventArgs myEventArgs)
{
mySnake.move(mode); //Move the snake based on mode
mainBoard.draw();
apples.draw(); //<----- draw apples
mySnake.draw();
//increment the duration by amount of time that has passed
duration += speed;
timerLBL.Text = Convert.ToString(duration / 1000); //Show time passed
//Check if snake is biting itself. If so, call GameOver.
if (mySnake.checkEatItself() == true)
{
GameOver();
}
else if (apples.checkIFSnakeHeadEatApple( mySnake.getHeadPosition()) == true)
{
score += apples.eatAppleAtPostion(mySnake.getHeadPosition());
scoreLBL.Text = Convert.ToString(score);
if (apples.noMoreApples() == true)
{
EatenAllApple();
clock.Stop();
level++;
gotoNextLevel(level);
MessageBox.Show("Press the start button to go to Level " + level, "Congrats");
}
else
{
//Length the snake and continue with the Game
mySnake.extendBody();
}
}
}
Overal Board.cs
class Board
{
int maxRow = 10, maxCol = 20; //Max 10 rows, and 20 columns in the board
int squareSize = 30; //Each square is 30px by 30px
PictureBox[,] squares;
public Board(Form mainForm)
{
squares = new PictureBox[maxRow, maxCol];
for (int row = 0; row < maxRow; row++)
{
for (int col = 0; col < maxCol; col++)
{
squares[row, col] = new PictureBox();
squares[row, col].Location = new Point(col * squareSize, row * squareSize);
squares[row, col].Height = squareSize;
squares[row, col].Width = squareSize;
squares[row, col].SizeMode = PictureBoxSizeMode.StretchImage;
squares[row, col].BackColor = Color.Black;
squares[row, col].BorderStyle = BorderStyle.FixedSingle;
mainForm.Controls["boardPanel"].Controls.Add(squares[row, col]);
}
}
mainForm.Controls["controlPanel"].Location = new Point(mainForm.Controls["boardPanel"].Location.X, mainForm.Controls["boardPanel"].Location.Y + mainForm.Controls["boardPanel"].Height + 20);
}
//setter
public void setMaxColNo(int x)
{
maxCol = x;
}
public void setMaxRowNo(int x)
{
maxRow = x;
}
//getter
public int getMaxColNo()
{
return maxCol-1; //Last Column No is 19, not 20
}
public int getMaxRowNo()
{
return maxRow-1; //Last Row No is 9, not 10
}
public int getMinColNo()
{
return 0; // 0 is the smallest Col number of the board
}
public int getMinRowNo()
{
return 0; // 0 is the smallest Row number of the board
}
public void draw()
{
for (int row = 0; row < maxRow; row++)
{
for (int col = 0; col < maxCol; col++)
{
squares[row, col].Image = null ;
}
}
}
public void draw(Position p, Image pic)
{
squares[p.getRowNo(), p.getColNo()].Image = pic;
}
}
Rewards.cs ( as requested by #AxelWass )
class Rewards
{
List<Position> appleList;
Board mainBoard;
public Rewards(int size, Board mainBoard)
{
this.mainBoard = mainBoard;
appleList = new List<Position>();
for (int i=0;i< size;i++)
{
int rowNo, colNo;
//Generate an apple at random position but not duplicated
do
{
//Generate a random number between 1 and MaxRowNo
rowNo = (new Random()).Next(1, mainBoard.getMaxRowNo()+1);
//Generate a random number between 1 and MaxColNo
colNo = (new Random()).Next(1, mainBoard.getMaxColNo()+1);
} while (isDuplicate(rowNo, colNo) == true);
appleList.Add(new Position(rowNo, colNo));
}
}
private Boolean isDuplicate(int row, int col)
{
Boolean result = false;
for (int i=0;i< appleList.Count;i++)
{
if (appleList[i].getRowNo() == row && appleList[i].getColNo() == col)
result = true;
}
return result;
}
public void draw()
{
for (int i = 0; i < appleList.Count; i++)
{
mainBoard.draw(appleList[i], Properties.Resources.apple);
}
}
public Boolean checkIFSnakeHeadEatApple(Position snakeHead)
{
Boolean result = false;
for (int i = 0; i < appleList.Count; i++)
{
if (snakeHead.getRowNo() == appleList[i].getRowNo() && snakeHead.getColNo() == appleList[i].getColNo())
result = true;
}
return result;
}
public Boolean checkIFSnakeEatApple(Position snakeHead)
{
Boolean result = false;
for (int i = 0; i < appleList.Count; i++)
{
if (snakeHead.getRowNo() == appleList[i].getRowNo() && snakeHead.getColNo() == appleList[i].getColNo())
result = true;
}
return result;
}
public int eatAppleAtPostion(Position p)
{
for (int i = 0; i < appleList.Count; i++)
{
if (p.getRowNo() == appleList[i].getRowNo() && p.getColNo() == appleList[i].getColNo())
appleList.RemoveAt(i);
//snakeEatApple();
}
return 50; //50 points per apple
}
public Boolean noMoreApples()
{
if (appleList.Count > 0)
return false;
else
return true;
}
/*public void snakeEatApple()
{
System.Media.SoundPlayer EatenApple = new System.Media.SoundPlayer(Properties.Resources.Eating_Apple);
EatenApple.Play();
}*/
}

mainBoard.draw(appleList[i], Properties.Resources.apple);
This is the problem statement. The resource designer in VS was not designed very well and violates Microsoft's own coding guidelines. What is not obvious at all is that the apple property creates a new Bitmap object every single time you use it. Since it is inside a loop, in a method that itself will be called very often, the code generates a lot of bitmap objects.
Bitmap is a disposable class. Not disposing it is in general pretty troublesome, it is a very small wrapper class that can use a lot of unmanaged memory. If the garbage collector doesn't run often enough so that the finalizer can run, the memory usage of your program can run up very quickly.
The workaround is to use the property only once. Store it in field of your class (outside of a method):
Bitmap apple = Properties.Resources.apple;
And fix the statement:
mainBoard.draw(appleList[i], apple);
And if you cross your T's and dot your I's then you dispose it when the form closes:
private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
apple.Dispose();
}
Which is a good habit to get into, although it is probably unnecessary since your program probably ends when the user closes the window.

Related

Optimizing grid based inventory

I am trying to create a grid based inventory and so far everything is going well except for one thing.
The inventory consists of Grids that are built of Cells, Grids can have custom size (like 5x3, 3x3). Every Cell is always 1x1 and can hold Items which can be of different sizes (like Grids).
I am handling the interactions in inventory using HandleCursor function which I run every frame to check if user is highlighting items/cells or moving stuff around.
As you can see in the code below I'm using a lot of fors to do that and I'am wondering if I could optimize it somehow so it isn't this hard on the CPU (right now highlighting a grid/item makes my inventory script create an overhead of 0.27-0.31ms.
Is there any better alternative than checking if Rect of each Cell contains my cursor?
Sorry if this is a wrong place to ask this kind of questions.
My code:
void HandleHighlighting()
{
HandleHighlightingCells();
HandleHighlightingItems();
HandleHighlightingPickedItem();
}
Highlighting all cells in all grids:
void HandleHighlightingCells()
{
for (int i = 0; i < Grids.Count; i++)
{
InventoryGrid grid = Grids[i];
if (grid.GetRect().Contains(Input.mousePosition))
{
for (int x = 0; x < grid.width; x++)
{
for (int y = 0; y < grid.height; y++)
{
InventoryCell cell = grid.cells[x, y];
if (cell.GetRect().Contains(Input.mousePosition))
{
if (highlightedCell && highlightedCell != cell)
{
highlightedCell.Highlight(EItemHighlightMode.NONE);
}
highlightedCell = cell;
highlightedCell.Highlight(EItemHighlightMode.BASE);
}
else
{
if (cell == highlightedCell)
{
highlightedCell = null;
}
cell.Highlight(EItemHighlightMode.NONE);
}
}
}
}
else
{
for (int x = 0; x < grid.width; x++)
{
for (int y = 0; y < grid.height; y++)
{
InventoryCell cell = grid.cells[x, y];
if (highlightedCell && highlightedCell != cell)
{
if (highlightedCell.grid == grid)
{
highlightedCell = null;
}
}
cell.Highlight(EItemHighlightMode.NONE);
}
}
}
}
}
Highlighting items in inventory:
void HandleHighlightingItems()
{
if (highlightedCell && highlightedCell.heldItem)
{
InventoryItem item = highlightedCell.heldItem;
if (highlightedItem && highlightedItem != item)
{
for (int i = 0; i < highlightedItem.occupiedCells.Length; i++)
{
highlightedItem.occupiedCells[i].Highlight(EItemHighlightMode.NONE);
}
}
highlightedItem = item;
for (int i = 0; i < item.occupiedCells.Length; i++)
{
item.occupiedCells[i].Highlight(EItemHighlightMode.BASE);
}
}
else
{
if (highlightedItem)
{
for (int i = 0; i < highlightedItem.occupiedCells.Length; i++)
{
highlightedItem.occupiedCells[i].Highlight(EItemHighlightMode.NONE);
}
highlightedItem = null;
}
}
}
Highlighting picked items:
void HandleHighlightingPickedItem()
{
if (pickedItem)
{
if (highlightedCell)
{
InventoryGrid grid = highlightedCell.grid;
InventoryCell[] cellsToHighlight = new InventoryCell[pickedItem.width * pickedItem.height];
InventoryItem firstItem = null;
bool valid = true;
int index = 0;
for (int x = 0; x < pickedItem.width; x++)
{
for (int y = 0; y < pickedItem.height; y++)
{
if (highlightedCell.x + x < grid.width && highlightedCell.y + y < grid.height)
{
InventoryCell cell = grid.cells[highlightedCell.x + x, highlightedCell.y + y];
cellsToHighlight[index] = cell;
if (highlightedItem)
{
if (cell.heldItem != highlightedItem)
{
if (cell.heldItem)
{
valid = false;
}
}
}
else
{
if (cell.heldItem)
{
if (!firstItem)
{
firstItem = cell.heldItem;
}
else
{
if (cell.heldItem != firstItem)
{
valid = false;
}
}
}
}
}
else
{
valid = false;
}
index++;
}
}
for (int i = 0; i < cellsToHighlight.Length; i++)
{
if (cellsToHighlight[i])
{
if (valid)
{
cellsToHighlight[i].Highlight(EItemHighlightMode.VALID);
}
else
{
cellsToHighlight[i].Highlight(EItemHighlightMode.INVALID);
}
}
}
}
}
}
With help of t3chb0t and juvian from codereview.stackexchange.com I managed to reduce the overhead from ~0.31ms to <0.08ms.
I actually ended up only changing the function that highlights all cells.
I'm posting this as an answer so it's visible in case someone else has similar problem.
void HandleHighlightingCells()
{
Vector2 mousePos = Input.mousePosition;
bool anyHighlighted = false;
for (int i = 0; i < Grids.Count; i++)
{
InventoryGrid grid = Grids[i];
if (grid.GetRect().Contains(mousePos))
{
Vector2 uiPos = Vector2.zero;
RectTransformUtility.ScreenPointToLocalPointInRectangle(grid.cellsRoot.GetComponent<RectTransform>(), Input.mousePosition, GameManager.singleton.inventoryCanvas.worldCamera, out uiPos);
//64 is the constant size of a cell
int cellX = Mathf.FloorToInt(uiPos.x / 64);
int cellY = Mathf.FloorToInt(-uiPos.y / 64);
if(cellX < grid.width && cellY < grid.height)
{
cellX = Mathf.Clamp(cellX, 0, grid.width - 1);
cellY = Mathf.Clamp(cellY, 0, grid.height - 1);
InventoryCell cell = grid.cells[cellX, cellY];
if (highlightedCell && highlightedCell != cell)
{
highlightedCell.Highlight(EItemHighlightMode.NONE);
}
highlightedCell = cell;
highlightedCell.Highlight(EItemHighlightMode.BASE);
anyHighlighted = true;
}
}
}
if(!anyHighlighted)
{
if(highlightedCell)
{
highlightedCell.Highlight(EItemHighlightMode.NONE);
highlightedCell = null;
}
}
}

C#, Having an issue with two buttons starting one method

If you're from the USA, and you've ever been to a Cracker Barrel, then you've probably played the board game where you have to jump pegs until you only have one left. It's similar to Chinese checkers, with just a pyramid, or a triangle.
I have a form that makes the buttons and adds them to the form, and I have a "TheBoard" class that has all of the rules for how jumping works on the form. In my form, I also have a button clicker method that needs to run all of this.
I seem to have hit a brick wall. I can't figure out the logic behind getting it to accept a second click, in order to move through the whole if statements in the board class. My parameter for the move method in the board class takes an int x, which is the button you click on as a parameter. I feel like I'm missing the second half of the move. How do I get my move method to register two button clicks (the starting location of the peg and the end location of the peg)?
Code for form:
public partial class Form1 : Form
{
private Button[] btn = new Button[15];
private TheBoard myboard = new TheBoard();
public Form1()
{
InitializeComponent();
int buttonsPerRow = 1;
int index = 0;
while (index < btn.Length)
{
int increment = this.Width / (buttonsPerRow + 1);
for (int j = 1; j <= buttonsPerRow; j++)
{
btn[index] = new Button
{
//other style elements of the button
Name = "btn" + index
}
btn[index].Click += new EventHandler(this.My_Click);
Controls.Add(btn[index]);
index++;
}
buttonsPerRow++;
}
}
private void My_Click(object sender, EventArgs e) {
myboard.getValues();
Button b = (Button)sender;
string bName = b.Name;
// Now pull off the btn
string num = bName.Substring(3, bName.Length - 3);
// Parsing the number to an int
int x = Int32.Parse(num);
myboard.move(x);
int[] color = myboard.getValues();
for (int i = 0; i < 15; i++)
{
color = myboard.getValues();
if (color[i] == TheBoard.hasPeg)
{
btn[i].BackColor = System.Drawing.Color.Yellow;
}
else
btn[i].BackColor = System.Drawing.Color.Black;
}//for
}
}
Code for TheBoard class:
class TheBoard
{
static public int hasPeg = 100;
static public int noPeg = 50;
private int[] board;
private int firstMove; //1st click
public TheBoard()
{
board = new int[15];
board[0] = noPeg;
for(int i = 1; i < 15; i++)
{
board[i] = hasPeg;
}
firstMove = -1; //giving last move a location, starting it at the beginning
}
public int move(int x)
{
if(firstMove == -1)
{
firstMove = x;
return 0;
}
// blank at 0
// if you click a blank your 1st move
if (firstMove == noPeg)
{
Console.WriteLine("You cant move if there isn't a peg.");
return 666;
}
// first---------------------------------------middle-----------------------end
if (firstMove == 1 && board[0] == hasPeg && board[3] == hasPeg && board[6] == noPeg)
{
RemovePeg(board[0], board[3], board[6]);
return 0;
}
if (firstMove == 1 && board[0] == hasPeg && board[2] == hasPeg && board[4] == noPeg)
{
RemovePeg(board[0], board[2], board[4]);
return 0;
}
//etc for remaining firstMove possibilities
firstMove = -1;
return 5;
}
private int RemovePeg(int first, int second, int goal) {
board[goal] = hasPeg;
board[first] = noPeg;
board[second] = noPeg;
return 0;
}
public int[] getValues()
{
return board;
}
}
I looked over the code and I think I understand your problem; you can select the starting peg but you don't have a way to select where it should go. With minimal edit to your code, I would store the first button click in a global variable and then the second button click knows that it is the second and initiates the board move with the two pieces of information (and resets the global variable).

I am trying to make a bunch of ascii characters move, but they only move together as one group. Why is that? (C#)

I've been trying to figure out as to why my ascii people won't move independently as one figure, as they've been moving as one group.
My goal is to make a bunch of guys move in four random directions every frame. Every person is a class with it's own set of data, like the X and Y position of them. Here is a part of the code:
What the code does by the way, is it spawns 10 people in a rectangle of 10x2. This is when the problem arises.
public class Soldier
{
public int Health;
public string Symbol;
public int x, y;
public int ID;
public void SetHealth(int hp)
{
Health = hp;
}
public void SetSymbol(string sym)
{
Symbol = sym;
}
public void SetID(int id)
{
ID = id;
}
public void RandomStep()
{
Random rnd = new Random();
int randNum = rnd.Next(1, 5);
if (randNum == 1)
{
x += 1;
}
else if (randNum == 2)
{
x -= 1;
}
else if (randNum == 3)
{
y += 1;
}
else if (randNum == 4)
{
y -= 1;
}
}
}
else if (battleMode == true)
{
bool battleLoad = true;
int team1Size = 0;
int team1Max = 10;
int rowXstart = 40;
int rowYstart = 10;
int rowX = rowXstart;
int rowXmax = rowXstart + 5;
int rowY = rowYstart;
int rowYmax = 5;
int pickIDsol = 1;
int IDsoldier = 1;
int battleID = 1;
List<Soldier> soldierunlist = new List<Soldier>();
List<Soldier> soldierlist = new List<Soldier>();
for (; ; )
{
if (battleLoad == true)
{
for (; ; )
{
if (team1Size < team1Max)
{
soldierunlist.Add(new Soldier());
Soldier unpickSoldier = soldierunlist.First(x => x.ID == 0);
unpickSoldier.ID = IDsoldier;
IDsoldier += 1;
team1Size += 1;
Console.WriteLine("Loading1");
}
else
{
break;
}
}
foreach (Soldier soldier in soldierunlist) {
soldier.Symbol = "☻";
Console.WriteLine("Loading Graphics");
}
for (; ; )
{
if (soldierunlist.Count != 0)
{
if (rowX < rowXmax)
{
Soldier pickSoldier = soldierunlist.First(x => x.ID == pickIDsol);
pickSoldier.x = rowX;
pickSoldier.y = rowY;
rowX += 1;
pickIDsol += 1;
soldierunlist.Remove(pickSoldier);
soldierlist.Add(pickSoldier);
Console.WriteLine("Creating Position values for soldiers");
}
else if (rowX >= rowXmax)
{
rowX = rowXstart;
rowY += 1;
}
}
else
{
battleLoad = false;
battleMode = true;
break;
}
}
}
else
{
for (; ; ){
Console.Clear();
int RefreshRate = 100; // 1000 = 1 second
/// [ START ]
//foreach (Soldier soldier in soldierlist)
//{
// WriteAt(soldier.Symbol, soldier.x, soldier.y);
// soldier.RandomStep();
//}
for (; ; )
{
if (battleID != team1Max)
{
Soldier aSoldier = soldierlist.First(x => x.ID == battleID);
aSoldier.RandomStep();
battleID += 1;
}
else
{
break;
}
}
foreach (Soldier soldier in soldierlist)
{
WriteAt(soldier.Symbol, soldier.x, soldier.y);
}
battleID = 1;
/// [ END ]
System.Threading.Thread.Sleep(RefreshRate); // Waits for (number) of time
}
}
}
}
Random rnd = new Random();
creates a new random every time you want a random number. As all the calls will be about instant, you will end up with the same seed every time, so all move in the same direction.
From documentation (https://msdn.microsoft.com/en-us/library/system.random(v=vs.110).aspx):
However, because the clock has finite resolution, using the parameterless constructor to create different Random objects in close succession creates random number generators that produce identical sequences of random numbers. ... On most Windows systems, Random objects created within 15 milliseconds of one another are likely to have identical seed values. ... To avoid this problem, create a single Random object instead of multiple objects.
So, you need to create a single random object. Maybe at the loading of the game. Then use that single random to create directions to move in, which will be then different.

Can a thread run a recursion that is called by the method assigned to it?

This may be a silly question and I couldn't find an answer around for a while.
Basically: I got a method that creates a thread that calls a method that calls a recursion, ....does this recursion run? because what I get from it is garbage...
I code in c# for unity3d. the script I want to run on a separate thread does not contain unity api methods.
Elaboration:
This is what I have:
The method GetPCNextTurn creates the thread like this:
Thread myThread = new Thread(() => CompPlayTurn(MinMaxBoard, weights));
myThread.Start();
Then CompPlayTurn should start right?
CompPlayTurn calls ScoreBoard which returns a value.
then after some condition CompPlayTurn calls a recursion which calls ScoreBoard recursivly
I would assume at this point it returns to the first method after the thread start lines right?
Something does not seem to happen the way I wish it would it seems. Can someone please enlighten me with behavior of threads and recursions?
I need 1 thread for this recursion all I need is to separate it from the main thread.
This is the code:
this is the main method in the script:
public int GetPCNextTurn(int[][] board, int height, int width, int sequence)
{
this.done = false;
this.height = height;
this.width = width;
this.sequence = sequence;
int[][] MinMaxBoard = CopyBoard(board);
weights = GetWeights(sequence);
Thread myThread = new Thread(() => CompPlayTurn(MinMaxBoard, weights));
myThread.Start();
return ans;
}
public void CompPlayTurn(int[][] MinMaxBoard, int[] weights)
{
int scoreOrig = ScoreBoard(MinMaxBoard);
if (scoreOrig == orangeWins) winner = (int)Winner.pc;
// Debug.Log("I win\n");
else if (scoreOrig == yellowWins) winner = (int)Winner.player;
// Debug.Log("You win\n");
else
{
int move, score;
Minimax(true, (int)Mycell.Orange, maxDepth, MinMaxBoard, out move, out score);
ans = move;
if (move != -1)
{
ans = move;
// dropDisk(board, move, (int)Mycell.Orange);
scoreOrig = ScoreBoard(MinMaxBoard);
if (scoreOrig == orangeWins) { winner = (int)Winner.pc; }//Debug.Log("I win\n"); }
else if (scoreOrig == yellowWins) { winner = (int)Winner.player; }//Debug.Log("You win\n"); }
}
else winner = (int)Winner.draw;
}
}
public int ScoreBoard(int[][] scores)
{
int[] counters;
int x, y, count = 0, size = (2 * sequence + 1);
counters = new int[size];
Array.Clear(counters, 0, counters.Length); //needed?
// Horizontal spans
for (y = 0; y < height; y++)
{
int score = 0;
for (int i = 0; i <= sequence - 2; i++)
score += scores[y][i];
for (x = (sequence - 1); x < width; x++)
{
score += scores[y][x];
counters[score + sequence]++;
score -= scores[y][x - (sequence - 1)];
}
}
// Vertical spans
for (x = 0; x < width; x++)
{
int score = 0;
for (int i = 0; i <= sequence - 2; i++)
score += scores[i][x];
for (y = (sequence - 1); y < height; y++)
{
score += scores[y][x];
counters[score + sequence]++;
score -= scores[y - (sequence - 1)][x];
}
}
// Down-right (and up-left) diagonals
for (y = 0; y < height - (sequence - 1); y++)
{
for (x = 0; x < width - (sequence - 1); x++)
{
int score = 0, idx = 0;
for (idx = 0; idx < sequence; idx++)
{
score += scores[y + idx][x + idx];
}
counters[(score + sequence)]++;
}
}
// up-right (and down-left) diagonals
for (y = (sequence - 1); y < height; y++)
{
for (x = 0; x < width - (sequence - 1); x++)
{
int score = 0, idx = 0;
for (idx = 0; idx < sequence; idx++)
{
score += scores[y - idx][x + idx];
}
counters[(score + sequence)]++;
}
}
if (counters[0] != 0)
return yellowWins;
else if (counters[(sequence * 2)] != 0)
return orangeWins;
else
{
for (int i = 1; i < size - 1; i++)
{ count += weights[i] * counters[i]; }
return count;
}
}
public void Minimax(bool maximizeOrMinimize, int color, int depth, int[][] MinMaxBoard, out int move, out int score)
{
if (0 == depth)
{
move = -1;
score = ScoreBoard(MinMaxBoard);
}
else
{
int bestScore = maximizeOrMinimize ? -10000000 : 10000000;
int bestMove = -1;
for (int column = 0; column < width; column++)
{
if (MinMaxBoard[0][column] != (int)Mycell.Barren)
continue;
int rowFilled = dropDisk(MinMaxBoard, column, color); // damage the state
if (rowFilled == -1)
continue;
int s = ScoreBoard(MinMaxBoard);
if (s == (maximizeOrMinimize ? orangeWins : yellowWins))
{
bestMove = column;
bestScore = s;
MinMaxBoard[rowFilled][column] = (int)Mycell.Barren;
break;
}
int moveInner, scoreInner;
Minimax(!maximizeOrMinimize, color == (int)Mycell.Orange ? (int)Mycell.Yellow : (int)Mycell.Orange,
depth - 1, MinMaxBoard, out moveInner, out scoreInner);
MinMaxBoard[rowFilled][column] = (int)Mycell.Barren; // Undo the damage
// No need for lists and sorting - just keep the best value you meet.
if (maximizeOrMinimize)
{
if (scoreInner >= bestScore)
{
bestScore = scoreInner;
bestMove = column;
}
}
else
{
if (scoreInner <= bestScore)
{
bestScore = scoreInner;
bestMove = column;
}
}
}
move = bestMove;
score = bestScore;
}
}
public int dropDisk(int[][] MinMaxBoard, int column, int color)
{
int y;
for (y = height - 1; y >= 0; y--)
if (MinMaxBoard[y][column] == (int)Mycell.Barren)
{
MinMaxBoard[y][column] = color;
return y;
}
return -1;
}
edit:
I tried adding a method to tell whether the thread finished running:
public bool TryGetValue(out int val)
{
val = ans;
this.done = false;
if (done==true)
return true;
return false;
}
The thread has a public bool variable that gets initialized in CompPlayTurn so the thread initializes it with false for the first time
And just for testing it out, I set its value to true inside CompPlayTurn, the first thing it does (before the recursion and everything, right after the method signature).
And for the main thread I added:
while (!(minimaxscript.TryGetValue(out column)))
{ StartCoroutine(wait(count)); }
and
public IEnumerator wait(int count)
{
Debug.Log("not done yet");
count++;
if (count == 7)
{
Application.Quit();
yield break;
}
yield return new WaitForSeconds(3f);
}
I started with yield return new WaitForEndOfFrame(); then yield return new waitforseconds and finally I added Application.Quit()
It freezes....I don;t think it is the thread, since he does't call the recourse...can;t be the main thread logic can it? it runs without the thread recursion ai just fine.
I even tried updating the bool variable to: done=true right after thread was created (after the thread.start) so it should be the main thread updating the variable before leaving the script, but it still freezes. as if the variable is never set....but without this protection I do get values, where are they coming from then?.
Nvm just lack of sleep -.- I am stupid...
this is the method I wanted to write and no idea what crossed my mind when I wrote what I did:
public bool TryGetValue(out int val)
{
val = ans;
return done;
}
edit:
I changed the done=false to be the first thing the thead does (first line in invoked method) and done=false, at the end of it.
Then main thread invokes waitforseconds(1f) and a debug msg to make sure it waits.
using this:
bool ok = (minimaxscript.TryGetValue(out column));
while (ok == false)
{
ok = (minimaxscript.TryGetValue(out column));
StartCoroutine( wait());
}
And it freezes...
final solution:
Adding the polling and protecting the method from invoking while it hasn;t finished yet seems to solve the problem.
The problem was eventually in unity, I kept calling the method again and again.
I was checking if it finished or not and coroutine it, but kept calling it regardless via a method that is called in update method so I didn;t notice that.
To answer your question, yes you can recursively call a function and it will stay in the thread you just created. Once you start that thread it no matter what function you call it will stay on that thread until it no longer has any code to execute.
However it looks like you are assigning a value to "ans" inside your CompPlayTurn function and then returning that value from GetPCNextTurn
The problem is that you can't guarantee that ans will be assigned before you return from that function (and most likely wont be). When you create a new thread you have no guarantees of when each thread will finish doing work. Which means that ans could be set before or after your return function, therefore making your return value no longer valid.
If you want to be able to return a value from a separate thread you will need to either pass a delegate to the function, or create a seperate function to poll when the value is set. However be warned, anything that is unity specific can only be used from the main thread, so i would recommend using the polling function to return the value
public void GetPCNextTurn(int[][] board, int height, int width, int sequence)
{
this.done = false;
this.height = height;
this.width = width;
this.sequence = sequence;
int[][] MinMaxBoard = CopyBoard(board);
weights = GetWeights(sequence);
Thread myThread = new Thread(() => CompPlayTurn(MinMaxBoard, weights));
myThread.Start();
}
public bool TryGetValue(out int val)
{
//return true if you have a value and set that to val
}
This is a very rough implementation, as with anything multithreaded you will want to make this a lot more robust, I mainly wrote this to show you the concept

C# Tic-Tac-Toe Minimax

I am currently trying my hand at making a minimax AI for tictactoe. My goal was that it should suffice for larger boards as well. However, I am having quite a hard time wrapping my head around how to implement the algorithm. I have read countless different descriptions of the algorithm but I still don't seem to be able to make it work. My result as of yet is an incredibly stupid AI. Here is my code for it.
Edit: The main point I am wondering about is how I make the AI value me not winning over forwarding itself towards the win. As of now it doesn't really care that I will win the next turn.
namespace TicTacToe_AI
{
public class Move //A class for moves
{
public int x, y, value, MoveNumber;
void SetMove(int a, int b)
{
x = a;
y = b;
}
public Move(int a, int b)
{
SetMove(a, b);
}
public Move()
{ }
}
class AI //AIClass
{
//The minimax algorithm
public Move CalculateMoves(int[,] Board, int BoardSize, int Depth, Move BestMoveAI, Move BestMovePlayer, int OriginalDepth, int CurrentTurn)
{
Depth--; //Decrease the depth for each iteration
bool Alpha = false; //Alpha-beta pruning - needs improvement
bool Beta = false;
bool WinningMove = false;
if (CurrentTurn == 1) CurrentTurn = 2;
if (CurrentTurn == 2) CurrentTurn = 1;
List<Move> DifferentMoves = new List<Move>();
List<Move> PossibleMoves = new List<Move>();
for (int i = 0; i < BoardSize; i++) //Add all possible moves to a list
{
for (int j = 0; j < BoardSize; j++)
{
if (Board[i, j] == 0)
{
Move Possible = new Move(i, j);
PossibleMoves.Add(Possible);
}
}
}
if (CurrentTurn == 2 && Depth >= 0 && Depth < BestMoveAI.MoveNumber) Alpha = true; //Alpha-beta pruning
if (CurrentTurn == 1 && Depth >= 0 && Depth < BestMovePlayer.MoveNumber) Beta = true;
if(Alpha || Beta)
{
foreach (Move TryMove in PossibleMoves) //Try every possible move to see if they are a winning move
{
int[,] Trying = new int[BoardSize, BoardSize];
Trying = (int[,])Board.Clone();
Trying[TryMove.x, TryMove.y] = CurrentTurn;
TryMove.MoveNumber = OriginalDepth - Depth;
if (Form1.Win(Trying) == 2)
{
TryMove.value = -1;
DifferentMoves.Add(TryMove);
if (Depth + 1 == OriginalDepth)
{
if (TryMove.MoveNumber < BestMoveAI.MoveNumber) BestMoveAI = TryMove;
WinningMove = true;
break;
}
else
{
WinningMove = true;
if (TryMove.MoveNumber < BestMoveAI.MoveNumber) BestMoveAI = TryMove;
return TryMove;
}
}
else if (Form1.Win(Trying) == 1)
{
WinningMove = true;
TryMove.value = 1;
BestMovePlayer = TryMove;
DifferentMoves.Add(TryMove);
return TryMove;
}
}
if (!WinningMove) // If no winning move was found, try recursively searching for a winning move
{
if (Alpha || Beta)
{
foreach (Move TryMove2 in PossibleMoves)
{
int[,] TestMove = new int[BoardSize, BoardSize];
TestMove = (int[,])Board.Clone();
TestMove[TryMove2.x, TryMove2.y] = CurrentTurn;
TryMove2.value = CalculateMoves(TestMove, BoardSize, Depth, BestMoveAI, BestMovePlayer, OriginalDepth, CurrentTurn).value;
DifferentMoves.Add(TryMove2);
}
}
}
}
//Find the best possible move and return it
BestMoveAI.value = 0;
BestMoveAI.MoveNumber = OriginalDepth;
BestMovePlayer.value = 0;
BestMovePlayer.MoveNumber = OriginalDepth;
if (CurrentTurn == 2)
{
foreach (Move AllMoves in DifferentMoves)
{
if (AllMoves.value <= BestMoveAI.value && AllMoves.MoveNumber <= BestMoveAI.MoveNumber)
{
BestMoveAI = AllMoves;
}
}
return BestMoveAI;
}
else if(CurrentTurn == 1)
{
foreach (Move AllMoves in DifferentMoves)
{
if (AllMoves.value >= BestMovePlayer.value && AllMoves.MoveNumber <= BestMovePlayer.MoveNumber)
{
BestMovePlayer = AllMoves;
}
}
return BestMovePlayer;
}
Move BadMove = new Move();
BadMove.value = 0;
BadMove.MoveNumber = Depth;
return BadMove;
}
}
}

Categories