Movement on the console in c# - c#

I'm working on a snake game right now, and am having difficulty with making sure the tail follows my head. I'm currently trying to get it to work, and I've tried about a dozen different ideas, all of which either make it stall out completely (e.g. snake appears to be frozen in one place), or the vertical motion happens at all points of the tail at once, instead of one movement following another. I'm also having some trouble with the Console.Clear() method that seems inescapable. Either I do it too many times and it deletes everything but the first point of my snake, or I don't and the old positions don't get erased. Here's the code (it's a test code, split from the actual game as I need to make sure the code works):
class Program
{
const int size = 10;
struct Sprite
{
public char[] ch;
public int[,] posXY;
public int directionX;
public int directionY;
}
static void Main(string[] args)
{
int startX = 10;
int startY;
Sprite player = new Sprite();
player.ch = new char[7];
player.posXY = new int[7,2];
for (int i = 0; i < player.ch.Length; i++)
{
player.ch[i] = '*';
}
for (int i = 0; i < 7; i++)
{
startY = 10;
player.posXY[i, 0] = startX;
player.posXY[i, 1] = startY;
startX--;
}
ConsoleKeyInfo cki = new ConsoleKeyInfo();
while (true)
{
update(cki, ref player);
draw(player);
Thread.Sleep(200);
}//end while
}//end main
static void update(ConsoleKeyInfo cki, ref Sprite player)
{
if (Console.KeyAvailable)
{
cki = Console.ReadKey(true);
if (cki.Key == ConsoleKey.LeftArrow || cki.Key == ConsoleKey.A)
{
player.directionX = -1;
player.directionY = 0;
}
if (cki.Key == ConsoleKey.RightArrow || cki.Key == ConsoleKey.D)
{
player.directionX = 1;
player.directionY = 0;
}
if (cki.Key == ConsoleKey.UpArrow || cki.Key == ConsoleKey.W)
{
player.directionX = 0;
player.directionY = -1;
}
if (cki.Key == ConsoleKey.DownArrow || cki.Key == ConsoleKey.S)
{
player.directionX = 0;
player.directionY = 1;
}
}//endif
for (int i = 0; i < 7; i++)
{
player.posXY[i, 0] = player.posXY[i, 0] + player.directionX;
player.posXY[i, 1] = player.posXY[i, 1] + player.directionY;
}
}//end update
static void draw(Sprite player)
{
Console.Clear();
for (int i = 0; i < 7; i++)
{
Console.SetCursorPosition(player.posXY[i, 0], player.posXY[i, 1]);
Console.Write(player.ch[i]);
}
}//end draw
}
P.S. I need to use a struct for my snake, using a Class isn't an option.

IMHO the best datastructure to describe the "snake" game is a queue. So that you can dequeue ("undraw") 1 "item" from the tail and enqueue ("draw") a new one with the new coordinates as the head.
If it happens to be on the "apple" you just skip one dequeue operation or enqueue twice.
If you are not familiar with the Queue data structure take a look at this fist: http://en.wikipedia.org/wiki/Queue_(abstract_data_type)
Look at the default Queue of .net framework here: http://msdn.microsoft.com/en-us/library/7977ey2c.aspx

Related

How can I make an "item" disappear when the player once stands on it's position? C#

I make this little Escape Room game where the player needs to collect a key for getting out of the room. How can I make the Key disappear whenever the player steps on it? This is my code so far. I know I haven't used any methods because I haven't learned it in university so far.. So it would be great if anyone has a simple solution or help for me. Thanks in advance! <3
static void Main(string[] args)
{
ConsoleKeyInfo consoleKey;
int XPositionCursor = 5;
int YPositionCursor = 5;
int MapWidth = 20;
int MapHeight = 20;
char Wall = '█';
bool GameOver = true;
char Key = '#';
char Character = 'H';
int[,] MapGenerationArray = new int[MapWidth, MapHeight];
Random RandomKeyCoordinate = new Random();
Random RandomDoorCoordinate = new Random();
#region Instructions
Console.WriteLine("Wähle eine Breite für dein Spielfeld:");
string MapWidthString = Console.ReadLine();
MapWidth = int.Parse(MapWidthString);
Console.Clear();
Console.WriteLine("Wähle eine Höhe für dein Spielfeld:");
string MapHeightString = Console.ReadLine();
MapHeight = int.Parse(MapHeightString);
Console.Clear();
Console.WriteLine($"Dein Spielfeld wird {MapWidth} x {MapHeight} groß sein!");
Console.ReadLine();
#endregion
Vector2 KeyCoordinate = new Vector2();
KeyCoordinate.X = RandomKeyCoordinate.Next(1, MapWidth - 1);
KeyCoordinate.Y = RandomKeyCoordinate.Next(1, MapHeight - 1);
Vector2 DoorCoordinate1 = new Vector2();
DoorCoordinate1.X = RandomDoorCoordinate.Next(0, MapWidth);
DoorCoordinate1.Y = RandomDoorCoordinate.Next(0, 0);
bool PlayerIsOnKeyPosition = XPositionCursor == KeyCoordinate.X && YPositionCursor == KeyCoordinate.Y;
bool PlayerCarryingKey = false;
do
{
#region Map
Console.Clear();
for (int i = 0; i < MapWidth; i++)
{
Console.SetCursorPosition(i, 0);
Console.Write(Wall);
}
for (int i = 0; i < MapWidth; i++)
{
Console.SetCursorPosition(i, MapHeight);
Console.Write(Wall);
}
for (int i = 0; i < MapHeight; i++)
{
Console.SetCursorPosition(0, i);
Console.Write(Wall);
}
for (int i = 0; i < MapHeight; i++)
{
Console.SetCursorPosition(MapWidth, i);
Console.Write(Wall);
}
#endregion
Console.SetCursorPosition(XPositionCursor, YPositionCursor);
Console.CursorVisible = false;
Console.Beep(200, 100);
Console.Write(Character);
if (PlayerIsOnKeyPosition)
{
PlayerCarryingKey = true;
}
if (PlayerCarryingKey == true)
{
Console.SetCursorPosition((int)DoorCoordinate1.X, (int)DoorCoordinate1.Y);
Console.Write(' ');
}
else
{
Console.SetCursorPosition((int)KeyCoordinate.X, (int)KeyCoordinate.Y);
Console.Write(Key);
Console.SetCursorPosition((int)DoorCoordinate1.X, (int)DoorCoordinate1.Y);
Console.ForegroundColor = ConsoleColor.Red;
Console.Write(Wall);
Console.ResetColor();
}
#region CharacterMovement
consoleKey = Console.ReadKey(true);
Console.Clear();
switch (consoleKey.Key)
{
case ConsoleKey.UpArrow:
YPositionCursor--;
break;
case ConsoleKey.DownArrow:
YPositionCursor++;
break;
case ConsoleKey.LeftArrow:
XPositionCursor--;
break;
case ConsoleKey.RightArrow:
XPositionCursor++;
break;
}
if (YPositionCursor < 1) { YPositionCursor = 1; }
if (XPositionCursor < 1) { XPositionCursor = 1; }
if (YPositionCursor >= MapHeight - 1) { YPositionCursor = MapHeight - 1; };
if (XPositionCursor >= MapWidth - 1) { XPositionCursor = MapWidth - 1; };
#endregion
} while (GameOver == true);
}
}
As #doctorlove wrote, you are calculating whether player has a key before your main loop (do/while).
But you should do this inside main loop, every time player changes his position.
Replace line if (PlayerIsOnKeyPosition) with this if (XPositionCursor == KeyCoordinate.X && YPositionCursor == KeyCoordinate.Y).
P.S. Good idea is to use methods (functions) to split code into smaller chunks :)

C# how to restart the console application

I am a begginer, and Im trying to make a Tic-Tac-Toe console game.But when O player has won, it shows, only when X player has made a decision.So that, there can be a situation when 2 players have won.I tried to use if statement to stop aplication but it doesn't help. I think I don't need to restart the application, just start Main again. But how??
class Program
{
static void Main()
{
string Player1;
string Player2;
int turnPlayer1;
int turnPlayer2;
while (CheckTheWinner() == false)
{
BoardPlay();
Console.Clear();
BoardPlay();
for ( turnPlayer1 = 0; turnPlayer1 < 1; turnPlayer1++)
{
Console.Write("Player O: Choose your field:");
Player1 = Console.ReadLine();
Player1Choice(Player1);
}
CheckTheWinner();
Console.Clear();
BoardPlay();
for (int i = 0; i < 1; i++)
{
Console.Write("Player X: Choose your field:");
Player2 = Console.ReadLine();
Player2choice(Player2);
}
CheckTheWinner();
Console.Clear();
BoardPlay();
}
}
public static void Player1Choice(string P1)
{
string o = "O";
for (int i = 0; i < position.GetLength(0); i++)
{
Console.ForegroundColor = ConsoleColor.Red;
for (int j = 0; j <= 2; j++)
{
if (position[i, j] == P1)
{
position[i, j] = o;
}
}
};
}
public static void Player2choice(string P2)
{
string x = "X";
for (int i = 0; i < position.GetLength(0); i++)
{
Console.ForegroundColor = ConsoleColor.Red;
for (int j = 0; j <= 2; j++)
{
if (position[i, j] == P2)
{
position[i, j] = x;
}
}
};
}
static bool CheckTheWinner()
{
for (int i = 0; i < 3; i++)
{
if (position[i, 0] == position[i, 1] && position[i, 1] == position[i, 2])
{
return true;
}
if (position[0, i] == position[1, i] && position[1, i] == position[2, i])
{
return true;
}
}
if (position[0, 0] == position[1, 1] && position[1, 1] == position[2, 2])
{
return true;
}
if (position[0, 2] == position[1, 1] && position[1, 1] == position[2, 0])
{
return true;
}
return false;
}
"Starting a new game" is basically "repeating the operation of having a game". Use loops to repeat operations.
First, define the loop condition. For example, a boolean flag indicating to keep playing:
var keepPlaying = true;
Then loop as long as that flag is true:
var keepPlaying = true;
while (keepPlaying)
{
// all of the logic currently in your Main method
}
Then all you need to do is prompt the player if they want another game and update the variable accordingly. For example:
var keepPlaying = true;
while (keepPlaying)
{
// all of the logic currently in your Main method
Console.Write("Would you like to play again? (Y/N): ");
var playerReply = Console.ReadLine();
if (playerReply == "N")
keepPlaying = false;
}
You could perhaps simplify the logic a bit, this is mainly for demonstration. You can also add some logic to check the input, you can add a closing message if the player chooses to quit, add other options, etc.
But the point is that if you want to repeat the game, it's not a matter of restarting the application or re-invoking the Main method, it's just a matter of encapsulating what you want to repeat in a loop.

ArgumentException & process memory keeps increasing in 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.

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