C# Tic-Tac-Toe Minimax - c#

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;
}
}
}

Related

Shift selection c# optimization

Basically I have done a code which works as a shift selection, I have already programmed the file selection working with Ctrl-Key selection, but it is required to optimize it. Nevertheless I have not found any way to make it easier has anyone any idea of how could I achieve that? The highlightedIndex is a list of int.
private int lastClickedPointIdx = -1;
int firstvaluepoint = 0;
private int movingPointIdx = -1; //-1: no point moving
private void KeySelection()
{
bool shiftclick = false;
int firstselectedpoint = 0;
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) //Ctrl
{
if (highlightedIndex.Contains(movingPointIdx)) //remove point if already is in the highlightedIndex list
{
highlightedIndex.Remove(movingPointIdx);
firstselectedpoint = movingPointIdx;
shiftclick = false;
return;
}
else //otherwise add it to the highlightedIndex list
{
highlightedIndex.Add(movingPointIdx);
highlightedIndex.Sort();
firstselectedpoint = movingPointIdx;
shiftclick = false;
}
}
else if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) //Shift
{
if (!shiftclick)
{
highlightedIndex.Clear();
shiftclick = true;
}
if (lastClickedPointIdx > -1) //highlights all points
{
if (firstvaluepoint > firstselectedpoint)
{
for (int i = firstselectedpoint; i <= firstvaluepoint; i++)
{
highlightedIndex.Add(i);
}
}
else
{
for (int i = firstvaluepoint; i <= firstselectedpoint; i++)
{
highlightedIndex.Add(i);
}
}
}
}
lastClickedPointIdx = movingPointIdx;
if (!shiftclick)
firstvaluepoint = firstselectedpoint;
}

Trying implement min max algorithm to tic tac toe. How should I implement the recursive function?

I am trying to implement a minmax algorithm that I found in javascript to my c# tic Tac toe game. I have done my best, but in my opinion the problem seems to be in the recursive function that I have been debugging for ages. This is my first time using recursive function so it is confusing for me.
So I implement the algorithm this way and it is giving me move that is not with
the best score so the problem could be in returning score for the move.
Link to the javascript code:
https://medium.freecodecamp.org/how-to-make-your-tic-tac-toe-game-unbeatable-by-using-the-minimax-algorithm-9d690bad4b37
public int Minimax(string[] reboard, bool player) {
// board looks like this [] {"0","1","2","3","4","5","6","7","8"}
//player X == true and player O == false
//X is human O is an AI
//array of possible moves
var array = Avail(reboard);
//check if current position of the board, true/false is the current player
if (WinningMinMax(reboard, true)) {
return -10;
}
else if (WinningMinMax(reboard, false)) {
return 10;
}
// or it is a draw
else if (DrawMinMax(reboard)) {
return 0;
}
//MoveS is an object with two parameters: index and score
List<MoveS> moves = new List<MoveS>();
for (var i = 0; i < array.Length; i++)
{
var move = new MoveS();
move.index = Convert.ToInt32(reboard[Convert.ToInt32(array[i])]);
if (player)
{
reboard[Convert.ToInt32(array[i])] = "X";
}
else
{
reboard[Convert.ToInt32(array[i])] = "O";
}
if (!player) {
var g = new MoveS();
//recursive call for building the tree of possible moves
g.score = Minimax(reboard, true);
move.score = g.score;
} else {
var g = new MoveS();
g.score = Minimax(reboard, false);
move.score = g.score;
}
//resets the board value
reboard[Convert.ToInt32(array[i])] = move.index.ToString();
// adding the final object move to a List of moves with score for every move
moves.Add(move);
}
//finding the best move of possible moves
int bestMove = 0;
if (player == false) {
var bestScore = -10000;
for (var i = 0; i < moves.Count; i++) {
if (moves[i].score > bestScore) {
bestScore = moves[i].score;
bestMove = i;
}
}
} else {
var bestScore = 10000;
for (var i = 0; i < moves.Count; i++) {
if (moves[i].score < bestScore) {
bestScore = moves[i].score;
bestMove = i;
}
}
}
//returning the best move's index for an Ai to play
return moves[bestMove].index;
}

Implementing and using MinMax with four in row (connect4) game

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();
}
}

Ray/AABB Intersection Incorrect

I've tried to reimplement the Fast Graphics Gems Ray/AABB Intersection Method in C#:
// Based on "Fast Ray-Box Intersection" algorithm by Andrew Woo, "Graphics Gems", Academic Press, 1990
public unsafe Vector? IntersectionWith(Cuboid other) {
const int NUM_DIMENSIONS = 3;
Assure.Equal(NUM_DIMENSIONS, 3); // If that value is ever changed, this algorithm will need some maintenance
const byte QUADRANT_MIN = 0;
const byte QUADRANT_MAX = 1;
const byte QUADRANT_BETWEEN = 2;
// Step 1: Work out which direction from the start point to test for intersection for all 3 dimensions, and the distance
byte* quadrants = stackalloc byte[NUM_DIMENSIONS];
float* candidatePlanes = stackalloc float[NUM_DIMENSIONS];
float* cuboidMinPoints = stackalloc float[NUM_DIMENSIONS];
float* cuboidMaxPoints = stackalloc float[NUM_DIMENSIONS];
float maxDistance = Single.NegativeInfinity;
byte maxDistanceDimension = 0;
bool startPointIsInsideCuboid = true;
cuboidMinPoints[0] = other.X;
cuboidMinPoints[1] = other.Y;
cuboidMinPoints[2] = other.Z;
cuboidMaxPoints[0] = other.X + other.Width;
cuboidMaxPoints[1] = other.Y + other.Height;
cuboidMaxPoints[2] = other.Z + other.Depth;
for (byte i = 0; i < NUM_DIMENSIONS; ++i) {
if (StartPoint[i] < cuboidMinPoints[i]) {
quadrants[i] = QUADRANT_MIN;
candidatePlanes[i] = cuboidMinPoints[i];
startPointIsInsideCuboid = false;
}
else if (StartPoint[i] > cuboidMaxPoints[i]) {
quadrants[i] = QUADRANT_MAX;
candidatePlanes[i] = cuboidMaxPoints[i];
startPointIsInsideCuboid = false;
}
else {
quadrants[i] = QUADRANT_BETWEEN;
}
}
if (startPointIsInsideCuboid) return StartPoint;
// Step 2: Find farthest dimension from cuboid
for (byte i = 0; i < NUM_DIMENSIONS; ++i) {
// ReSharper disable once CompareOfFloatsByEqualityOperator Exact check is desired here: Anything other than 0f is usable
if (quadrants[i] != QUADRANT_BETWEEN && Orientation[i] != 0f) {
float thisDimensionDist = (candidatePlanes[i] - StartPoint[i]) / Orientation[i];
if (thisDimensionDist > maxDistance) {
maxDistance = thisDimensionDist;
maxDistanceDimension = i;
}
}
}
if (maxDistance < 0f) return null;
if (maxDistance - Length > MathUtils.FlopsErrorMargin) return null;
float* intersectionPoint = stackalloc float[NUM_DIMENSIONS];
for (byte i = 0; i < NUM_DIMENSIONS; ++i) {
if (maxDistanceDimension == i) {
intersectionPoint[i] = StartPoint[i] + maxDistance * Orientation[i];
if (cuboidMinPoints[i] - intersectionPoint[i] > MathUtils.FlopsErrorMargin || intersectionPoint[i] - cuboidMaxPoints[i] > MathUtils.FlopsErrorMargin) return null;
}
else intersectionPoint[i] = candidatePlanes[i];
}
Vector result = new Vector(intersectionPoint[0], intersectionPoint[1], intersectionPoint[2]);
if (!IsInfiniteLength && Vector.DistanceSquared(StartPoint, result) > Length * Length) return null;
else return result;
}
However, although it sort of works, I'm getting incorrect results on the following part of a unit test:
Cuboid cuboid = new Cuboid(frontBottomLeft: new Vector(0f, 7.1f, 0f), width: 0f, height: 5f, depth: 0f);
Ray testRayC = new Ray(startPoint: new Vector(30f, 30f, 30f), orientation: new Vector(-1f, -1f, -1f));
Assert.AreEqual(
null,
testRayC.IntersectionWith(cuboid)
);
I am expecting null from the call to testRayC.IntersectionWith(cuboid), but instead it returns a Vector(0, 12.1, 0), which is not a point on the ray at all.
So is it just a case of adding a final check that the calculated point is on the ray? Or (and this is what I suspect), have I made an error in transcribing the code? I have double and triple checked but didn't see anything obvious...
The problem in your code is when you do if (maxDistanceDimension == i) {. The original code checks if (whichPlane != i) {. I don't have your data structures, but a fix should look like:
for (byte i = 0; i < NUM_DIMENSIONS; ++i)
{
if (maxDistanceDimension != i)
{
intersectionPoint[i] = StartPoint[i] + maxDistance * Orientation[i];
if (intersectionPoint[i] < cuboidMinPoints[i] - MathUtils.FlopsErrorMargin || intersectionPoint[i] > cuboidMaxPoints[i] + MathUtils.FlopsErrorMargin)
return null;
}
else
{
intersectionPoint[i] = candidatePlanes[i];
}
}
Next, the following isn't in the original code. What is this for?
if (maxDistance - Length > MathUtils.FlopsErrorMargin)
return null;
If you are trying to check if the hit is within the extent of the ray, this may be a bug. Given that your Orientation does not appear to be normalized, maxDistance is not necessarily in units of length. This may not matter in the original algorithm, but if you are going to check maxDistance against some other length you need to normalize Orientation (make it dimensionless) so that
thisDimensionDist = (candidatePlanes[i] - StartPoint[i]) / Orientation[i];
will have units of length.
Incidentally, in the original I think the following is wrong:
if(inside) {
coord = origin;
return (TRUE);
}
Assuming this code is c and not c++, this simply sets the the coord pointer to have the same reference as the origin pointer, which will have no effect on the caller. This issue doesn't apply to your version, however.
Also, in the course of looking at this, I made a more literal c# transcription of the algorithm here:
public static class RayXCuboid
{
enum HitQuadrant
{
Right = 0,
Left = 1,
Middle = 2,
}
const int Dimension = 3;
[Conditional("DEBUG")]
static void AssertValidArguments<TDoubleList>(params TDoubleList[] args) where TDoubleList : IList<double>
{
Debug.Assert(Dimension == 3);
foreach (var list in args)
Debug.Assert(list != null && list.Count == Dimension);
}
public static bool HitBoundingBox<TDoubleList>(TDoubleList minB, TDoubleList maxB, TDoubleList origin, TDoubleList dir, TDoubleList coord) where TDoubleList : IList<double>
{
AssertValidArguments(minB, maxB, origin, dir, coord);
HitQuadrant[] quadrant = new HitQuadrant[Dimension];
double[] maxT = new double[Dimension];
double[] candidatePlane = new double[Dimension];
/* Find candidate planes; this loop can be avoided if
rays cast all from the eye(assume perpsective view) */
bool inside = true;
for (int i = 0; i < Dimension; i++)
if (origin[i] < minB[i])
{
quadrant[i] = HitQuadrant.Left;
candidatePlane[i] = minB[i];
inside = false;
}
else if (origin[i] > maxB[i])
{
quadrant[i] = HitQuadrant.Right;
candidatePlane[i] = maxB[i];
inside = false;
}
else
{
quadrant[i] = HitQuadrant.Middle;
}
/* Ray origin inside bounding box */
if (inside)
{
CopyTo(origin, coord);
return true;
}
/* Calculate T distances to candidate planes */
for (int i = 0; i < Dimension; i++)
if (quadrant[i] != HitQuadrant.Middle && dir[i] != 0.0)
maxT[i] = (candidatePlane[i] - origin[i]) / dir[i];
else
maxT[i] = -1.0;
/* Get largest of the maxT's for final choice of intersection */
int whichPlane = 0;
for (int i = 1; i < Dimension; i++)
if (maxT[whichPlane] < maxT[i])
whichPlane = i;
/* Check final candidate actually inside box */
if (maxT[whichPlane] < 0.0)
{
FillWithDefault(coord);
return false;
}
for (int i = 0; i < Dimension; i++)
if (whichPlane != i)
{
coord[i] = origin[i] + maxT[whichPlane] * dir[i];
if (coord[i] < minB[i] || coord[i] > maxB[i])
{
FillWithDefault(coord);
return false;
}
}
else
{
coord[i] = candidatePlane[i];
}
return true; /* ray hits box */
}
static void FillWithDefault<T>(IList<T> list)
{
for (int i = 0; i < list.Count; i++)
list[i] = default(T);
}
static void CopyTo<T>(IList<T> from, IList<T> to)
{
int arrayIndex = 0;
foreach (var item in from)
to[arrayIndex++] = item;
}
}

Issues with MinHeaps

I'm trying to develop custom path-finding for the game I'm making. It's using the new 2D colliders so I can't use unity's built in navmesh. So instead of using an asset someone else created I figured I'd have a go at making my own implementation.
I've looked up a bunch of stuff about path-finding and A*, which I have no problem at all understanding. Where I'm having troubles though is the MinHeap. I've never looked into bit shifting and the like so I modified some code I found in a tutorial to suit my needs.
The problem that I'm having is that, from what I can tell in my debugging, it isn't properly sorting the nodes by cost value. From what I can see, and I'll post a picture below so you know what I'm talking about, every node that's being added to the open list is staying where it's added, so it's more like a Dijkstra's search then an A* one.
Could someone look at the code and tell me if I'm doing something very wrong.. because this is driving me mad.
This is the code:
using System;
public class NodeList<T> where T : IComparable<T>
{
private int count;
private int capacity;
private T temp;
private T mheap;
private T[] array;
private T[] tempArray;
public int Count
{
get { return this.count; }
}
public NodeList() : this(16) { }
public NodeList(int capacity)
{
this.count = 0;
this.capacity = capacity;
array = new T[capacity];
}
public void BuildHead()
{
int position;
for (position = (this.count - 1) >> 1; position >= 0; position--)
{
this.MinHeapify(position);
}
}
public void Add(T item)
{
this.count++;
if (this.count > this.capacity)
{
DoubleArray();
}
this.array[this.count - 1] = item;
int position = this.count - 1;
int parentPosition = ((position - 1) >> 1);
while (position > 0 && array[parentPosition].CompareTo(array[position]) > 0)
{
temp = this.array[position];
this.array[position] = this.array[parentPosition];
this.array[parentPosition] = temp;
position = parentPosition;
parentPosition = ((position - 1) >> 1);
}
}
private void DoubleArray()
{
this.capacity <<= 1;
tempArray = new T[this.capacity];
CopyArray(this.array, tempArray);
this.array = tempArray;
}
private static void CopyArray(T[] source, T[] destination)
{
int index;
for (index = 0; index < source.Length; index++)
{
destination[index] = source[index];
}
}
public T Peek()
{
if (this.count == 0)
{
throw new InvalidOperationException("Heap is empty");
}
return this.array[0];
}
public T ExtractFirst()
{
if (this.count == 0)
{
throw new InvalidOperationException("Heap is empty");
}
temp = this.array[0];
this.array[0] = this.array[this.count - 1];
this.count--;
this.MinHeapify(0);
return temp;
}
private void MinHeapify(int position)
{
do
{
int left = ((position << 1) + 1);
int right = left + 1;
int minPosition;
if (left < count && array[left].CompareTo(array[position]) < 0)
{
minPosition = left;
}
else
{
minPosition = position;
}
if (right < count && array[right].CompareTo(array[minPosition]) < 0)
{
minPosition = right;
}
if (minPosition != position)
{
mheap = this.array[position];
this.array[position] = this.array[minPosition];
this.array[minPosition] = mheap;
position = minPosition;
}
else
{
return;
}
} while (true);
}
}
My path searches from the end point to the start (From where the player clicks, to the player), so the "Starting point" is the grey node in the middle of the screen:
Pathfinding issue
This is why it's a problem:
Issue 2

Categories