It's for a game of checkers. See revision history for older versions of code.
private static Move GetBestMove(Color color, Board board, int depth)
{
var bestMoves = new List<Move>();
var validMoves = board.GetValidMoves(color);
int highestScore = int.MinValue;
Board boardAfterMove;
int tmpScore;
var rand = new Random();
Debug.WriteLine("{0}'s Moves:", color);
foreach (var move in validMoves)
{
boardAfterMove = board.Clone().ApplyMove(move);
if(move.IsJump && !move.IsCrowned && boardAfterMove.GetJumps(color).Any())
tmpScore = NegaMax(color, boardAfterMove, depth);
else
tmpScore = -NegaMax(Board.Opposite(color), boardAfterMove, depth);
Debug.WriteLine("{0}: {1}", move, tmpScore);
if (tmpScore > highestScore)
{
bestMoves.Clear();
bestMoves.Add(move);
highestScore = tmpScore;
}
else if (tmpScore == highestScore)
{
bestMoves.Add(move);
}
}
return bestMoves[rand.Next(bestMoves.Count)];
}
private static int NegaMax(Color color, Board board, int depth)
{
var validMoves = board.GetValidMoves(color);
int highestScore = int.MinValue;
Board boardAfterMove;
if (depth <= 0 || !validMoves.Any())
return BoardScore(color, board);
foreach (var move in validMoves)
{
boardAfterMove = board.Clone().ApplyMove(move);
if(move.IsJump && !move.IsCrowned && boardAfterMove.GetJumps(color).Any())
highestScore = Math.Max(highestScore, NegaMax(color, boardAfterMove, depth));
else
highestScore = Math.Max(highestScore, -NegaMax(Board.Opposite(color), boardAfterMove, depth - 1));
}
return highestScore;
}
private static int BoardScore(Color color, Board board)
{
if (!board.GetValidMoves(color).Any()) return -1000;
return board.OfType<Checker>().Sum(c => (c.Color == color ? 1 : -1) * (c.Class == Class.Man ? 2 : 3));
}
I'm trying it with depth 0, and the scores are correct for about half the game, and then all of a sudden it starts screwing up. One of the players will start proclaiming his score is higher than it really is. Why would it only work for half a game?!
Interesting approach, the first time I see MaxiMax. But I see a problem here:
var minMove = GetBestMove(... board.Clone().ApplyMove(move), ...);
float score = ... BoardScore(color, board.Clone().ApplyMove(minMove));
In this code, move and minMove are moves for different sides and yet you apply them equally at the same level here. The second line should be something like:
float score = ... BoardScore(... board.Clone().ApplyMove(move).ApplyMove(minMove));
You can of course store and re-use the board.Clone().ApplyMove(move) part.
But then you still loose information: At Depth 100 you filter out the best boardScore at depth 99 but you don't have/use anything from levels 98..0 except when there was no move (null), but as you noticed yourself that part goes wrong.
Tried looking at some pseudo
algorithms, but all the seem to return
a score. That confuses me, because I
don't really want to get a score back,
I want to get a Move back.
Still, that is the way to go. The main result from a tree-search is the value of the best branch. The move itself is only essential at the root level. Leave it until you start implementing alpha/beta, then you will be able to store the best branch in a single table.
I would advice switching to a regular NegaMax,
also see this SO question.
Found the bug: What could cause this to start miscalculating after awhile?
New code:
private static Move GetBestMove(Color color, Board board, int depth)
{
var bestMoves = new List<Move>();
IEnumerable<Move> validMoves = board.GetValidMoves(color);
int highestScore = int.MinValue;
Board boardAfterMove;
int tmpScore;
var rand = new Random();
Debug.WriteLine("{0}'s Moves:", color);
foreach (Move move in validMoves)
{
boardAfterMove = board.Clone().ApplyMove(move);
if (move.IsJump && !move.IsCrowned && boardAfterMove.GetJumps(color).Any())
tmpScore = NegaMax(color, boardAfterMove, depth);
else
tmpScore = -NegaMax(Board.Opposite(color), boardAfterMove, depth);
Debug.WriteLine("{0}: {1}", move, tmpScore);
if (tmpScore > highestScore)
{
bestMoves.Clear();
bestMoves.Add(move);
highestScore = tmpScore;
}
else if (tmpScore == highestScore)
{
bestMoves.Add(move);
}
}
return bestMoves[rand.Next(bestMoves.Count)];
}
private static int NegaMax(Color color, Board board, int depth)
{
IEnumerable<Move> validMoves = board.GetValidMoves(color);
int highestScore = int.MinValue;
Board boardAfterMove;
if (depth <= 0 || !validMoves.Any())
return BoardScore(color, board);
foreach (Move move in validMoves)
{
boardAfterMove = board.Clone().ApplyMove(move);
if (move.IsJump && !move.IsCrowned && boardAfterMove.GetJumps(color).Any())
highestScore = Math.Max(highestScore, NegaMax(color, boardAfterMove, depth));
else
highestScore = Math.Max(highestScore, -NegaMax(Board.Opposite(color), boardAfterMove, depth - 1));
}
return highestScore;
}
private static int BoardScore(Color color, Board board)
{
if (!board.GetValidMoves(color).Any()) return -1000;
return board.OfType<Checker>().Sum(c => (c.Color == color ? 1 : -1) * (c.Class == Class.Man ? 2 : 3));
}
I'm not 100% convinced this works perfectly. It seems to work for depth 0, and usually for depth 1... beyond that, I have no idea what the computer is thinking. Still doesn't appear to play super intelligently.
Edit: Running this and max speed... NegaMax agent vs Random. NegaMax always wins. Watching the scores for occurrences of "1000". He always wins within a few turns after that, so it does appear to be working, finally!
Related
I'm trying to find a faster implementation of a flood fill algorithm for a program I'm making using C# in Unity 2020.
This is my current method, which in my program takes about 400ms to run on a 1000 x 1000 map. Instead of a target colour to replace, I am using a height map (called noiseMap in this code snippet) and all values above a threshold should be considered inside the flooded area.
public void Flood()
{
landMasses.Clear();
globalSet.Clear();
HashSet<Vector2Int> samples = new HashSet<Vector2Int>();
for (int x = 0; x < mapGen.mapSize; x += mapGen.scanStride)
{
for (int y = 0; y < mapGen.mapSize; y += mapGen.scanStride)
{
samples.Add(new Vector2Int(x, y));
}
}
float[,] noiseMap = mapGen.noiseMap;
int mapSize = mapGen.mapSize;
float threshold = mapGen.threshold;
foreach (var sample in samples)
{
CalculateSets(sample, noiseMap, mapSize, threshold);
}
}
public bool Inside(Vector2Int point)
{
return Inside(point.x, point.y);
}
public bool Inside(int x, int y)
{
if (x < mapGen.mapSize && x >= 0 && y < mapGen.mapSize && y >= 0)
{
return mapGen.noiseMap[x, y] > mapGen.threshold;
}
return false;
}
public void CalculateSets(Vector2Int sample, float[,] noiseMap, int mapSize, float threshold)
{
if (globalSet.Contains(sample) || noiseMap[sample.x, sample.y] < threshold)
{
return;
}
HashSet<Vector2Int> set = new HashSet<Vector2Int>();
Queue<Vector2Int> queue = new Queue<Vector2Int>();
queue.Enqueue(sample);
while (queue.Count > 0)
{
Vector2Int n = queue.Dequeue();
if (set.Contains(n))
{
continue;
}
if(Inside(n))
{
set.Add(n);
globalSet.Add(n);
queue.Enqueue(new Vector2Int(n.x, n.y - 1));
queue.Enqueue(new Vector2Int(n.x, n.y + 1));
queue.Enqueue(new Vector2Int(n.x - 1, n.y));
queue.Enqueue(new Vector2Int(n.x + 1, n.y));
}
}
landMasses.Add(landMasses.Count.ToString(), set);
}
I've looked around at places like Wikipedia and other online forums for an implementation of the scan line flood fill, but every implementation I find has very little documentation to go along with it, or has no definitions of what their variable names represent. Regardless of this, I have tried to decipher these other implementations and have had 0 luck.
For example, on the Floodfill Wikipedia Page, there are a few different methods along with pseudocode to go along with it - but I cannot find definitions for what most of the variables mean in the later methods which are supposedly faster. Perhaps it's simple, but as someone overall new to computing algorithms I am struggling to figure it out.
So at the end of all this, I am essentially just looking for a faster way to implement something like a floodfill algorithm than what I currently have. It doesn't need to exactly fit into my program of course, even just a general C# implementation or more clarified pseudocode example with comments will be a great help.
Thank you for reading!!
So how can I update the position every time I call the StartRandomizingRightSpikePosition
private bool CheckOverlap(GameObject o1, GameObject o2)
{
return spikeRight.Select(t => t.GetComponent<Collider>().bounds.Intersects(t.GetComponent<Collider>().bounds)).FirstOrDefault();
}
public void StartRandomizingRightSpikesPosition()
{
foreach (var t in spikeRight)
{
foreach (var t1 in spikeRight)
{
if (t == t1) continue;
if (!CheckOverlap(t, t1)) continue;
yPosition = Random.Range(-7, 7);
var position = t1.transform.position;
desiredPosition = new Vector3(position.x, yPosition, position.z);
t1.transform.position = desiredPosition;
Debug.Log(t.gameObject + " intersects " + t1.gameObject);
}
}
}
The short answer is yes but I'm not sure you would want too. I'm not sure you're going to find a way to do this efficiently and you might be better off finding a way to generate the objects such that this step is not necessary.
I can't tell from your question how the objects are actually stored so I'm going to provide some sample code that just deals with a simple array of Rectangles. You should be able to adapt it to your specifics.
I tried to make it slightly more efficient by not checking both t1 == t and t == t1.
const int Bounds = 1000;
static bool RemovedOverlappingRects(Rectangle[] rects)
{
for (int pos = 0; pos < rects.Length; ++pos)
{
for (int check = pos +1; check < rects.Length; ++check)
{
var r1 = rects[pos];
var r2 = rects[check];
if (r1.IntersectsWith(r2))
{
r2.Y = Rng.Next(1, Bounds);
rects[check] = r2;
Console.WriteLine($"{pos} overlaps with {check}");
return true;
}
}
}
return false;
}
Once we've randomly generated a new rectangle we have to start over. Which means invoking the above method in a loop.
var rects = GetRandomeRects(20).ToArray();
while (RemovedOverlappingRects(rects))
;
Because of the random movement I'm not certain you can guarantee this will always end. If you can deal with the non-random look of the results changing it to stack the overlaps would I believe always finish. That would be this:
r2.Y = r1.Y + r1.Height + 1;
in place of
r2.Y = Rng.Next(1, Bounds);
But even then you're still dealing with a very unpredictable run time due to the random input.
Maybe someone else can show us a more efficient way...
been trying to implement a Q deep learning algorithm, having an issue though, its not working, after 100 000 game plays and using 1000 iterations to train each step (although i have tried lower numbers for both) it's still not learning. Network and game are in the linked image, http://imgur.com/a/hATfB here is what happens in each training step:
double maxQval;
double[] inputvec;
int MaxQ = GetRandDir(state, out maxQval, out inputvec);//input vec is board
double[] QtarVec = new double[] { 0, 0, 0, 0 };
double r = GetR((int)state[0], (int)state[1]); // GetR is reward
QtarVec[MaxQ] = Qtar(r, maxQval); // backprop vector of 0's except Qtar replaces a value
associator.Train(50, new double[][] { inputvec }, new double[][] { QtarVec });
Training data pair for backprop is (input i linked in image,QTarget = r + gamma * MaxQ) , MaxQ is max network output layer activation or a random one (epsilon greedy). r is reward obtained from each move, -10 for obstacle and 10 for goal. (althogh I have tried just 10 for goal and 0 for everything else. Here is training code.
public void Train(int nTrails)
{
double[] state = new double[] { 1, 1 }; // inital position
int its = 0;
for (int i = 0; i < nTrails; i++)
{
while (((state[0] < 4) && (state[1] < 4))&&((state[0] * 100 >0) && (state[1] * 100 >0)) && (state[0] != 3 && state[1] != 3))//while on board and not at goal postion
{
double temp = r.NextDouble();
int next = -1;
lines.Add(new Vector2((float)(state[0] * 100), (float)(state[1] * 100)));
if (temp < epsilon)
{
next = TrainRandIt(state); // move random direction, backprop
}
else
{
next = TrainMaxIt(state); // move in max activation direction, backprop
}
if (next == 0) .//updating postion
{
state[0]++;
}
else if (next == 1)
{
state[0]--;
}
else if (next == 2)
{
state[1]++;
}
else if (next == 3)
{
state[1]--;
}
}
}
state[0] = 1;
state[1] = 1; // resetting game
}
Any Help appreciated.
Judging from the linked image you provided, it is just like a maze game where you have inputs for the player's position and the output as the direction the player should move to (up, down, left or right).
Here is a machine learning engine which is able to solve exactly that and more - the Ryskamp Learning Machine (RLM). The RLM has a different approach compared to the typical machine learning engines that you may have tried so far so I suggest you go to the link I've provided to learn more about it and what makes it different.
It is written in C# and we have an example of the Maze game just like the one you are trying out which you can browse through our Github page or even try it yourself by cloning/downloading the source code together with the examples apps provided.
For documentation, you may refer to the Documentations files provided or even through the github wiki.
The RLM is also available via Nuget.
Small bit of background first. I am developing a system that generates a "route" between locations. Locations have a pre-defined list of neighbours not limited to those adjacent to it. The search can safely assume that by picking the closest neighbour (numerically) to the target destination, it is making the optimal move towards it.
I have working code as shown below:
public Route GetRoute(int StartPoint, int Destination)
{
Route returnRoute = new Route();
returnRoute.steps = new List<int>();
bool locationReached = false;
int selectedNeighbour;
int distanceFromTarget;
int currentPoint = StartPoint; // set the current point to the start point
while (!locationReached)
{
selectedNeighbour = 0;
distanceFromTarget = 5000; // nominal amount guaranteed to be overwritten
var neighbours = locations.FirstOrDefault(l => l.LocationID == currentPoint).Neighbours;
for (int i = 0; i < neighbours.Length; i++)
{
// get the current neighbours, then check proximity
int currentNeighbour = neighbours[i];
int tempDistance = Math.Abs( currentNeighbour - Destination );
// if nearer than previous neighbour, set it as the chosen location
if ( tempDistance < distanceFromTarget )
{
distanceFromTarget = tempDistance;
selectedNeighbour = currentNeighbour;
// if the selected neighbour is the destination, we're done
if ( selectedNeighbour == Destination )
locationReached = true;
}
} // for
// add the selected neighbour if we found one
if ( selectedNeighbour != 0 )
{
currentPoint = selectedNeighbour;
returnRoute.steps.Add(selectedNeighbour);
}
else
{
Debug.Log ("No Route Found");
return returnRoute;
}
} // while
return returnRoute;
}
My question is regarding the loop of the neighbours (int[]) variable. How can this best be optimised? I've seen some use of linq and ordering, but also comments that this approach might be inefficient. I need efficiency over neatness here.
Many thanks.
So here is the problem. I'm working on a combat-simulator for an AI based game. The AI should calculate the best move for him when the enemy makes his best move against every move.
My team has X units with 5 possible moves and my opponent has Y units with 5 possible moves. X and Y > 0
using alpha-beta pruning we want to generate each possible outcome and take the best one out in the end. The problem is the fact that we save each outcome into a situation, this situation stores lists but the lists contain references to the objects stored, this makes them save their moves into the same situation (all 5 moves of 1 unit)
Imagine 2 of our units and one of theirs. We create a situation and add one unit with 1 of the 5 directions to it. Then for our second unit we add one direction, then for the enemy unit. Now we got the end situation we want to save this. Then from the situation we had in OUR second unit (so without the enemy unit) we want to add a different move for the enemy to the situation and save that new situation if it was better. But since C# uses references for lists this situation is the situation with the other enemy move included aswell.
Code is a bit large but I'm really stuck here so I'd hope if anyone has some spare time to help me out with ideas to fix this.
public Situation RecursiveMaxTree(List<SimAnt> undone, int index, bool[,] positions, Situation situation)
{
SimAnt front = undone[index];
int max = situation.Max;
List<SimAnt> bestmoves = new List<SimAnt>();
int frontY = front.position.Y;
int frontX = front.position.X;
if (!front.isEnemy() && undone[index + 1].isEnemy())//cutoff activated----------------------------------------------------------------------------
{
foreach (Direction direction in directions)
{
int newX = GameState.WrapHorizonal(frontX + direction.toVector().X);
int newY = GameState.WrapVertical(frontY + direction.toVector().Y);
if (!positions[newX, newY] && map.isNotWater(newX, newY))
{
//add the updated ant to the new allAnts list
positions[newX, newY] = true;
List<SimAnt> newallAnts = situation.GetList;
Friend newant = new Friend(newX, newY);
newant.direction = direction;
newallAnts.Add(newant);
Situation current = RecursiveMinTree(undone, index + 1, positions, new Situation(newallAnts, max));//geeft min van alle enemy options
positions[newX, newY] = false;
if (current.Max > max)
{
max = current.Max;
bestmoves = current.GetList;
}
}
}
}
else //max-------------------------------------------------------------------------------------------------------------------------------------
{
foreach (Direction direction in directions)
{
int newX = GameState.WrapHorizonal(frontX + direction.toVector().X);
int newY = GameState.WrapVertical(frontY + direction.toVector().Y);
if (!positions[newX, newY] && map.isNotWater(newX, newY))
{
//add the updated ant to the new allAnts list
positions[newX, newY] = true;
List<SimAnt> newallAnts = situation.GetList.Clone();
Friend newant = new Friend(newX, newY);
newant.direction = direction;
newallAnts.Add(newant);
Situation current = RecursiveMaxTree(undone, index + 1, positions, new Situation(newallAnts, max));//geeft min van alle enemy options
positions[newX, newY] = false;
if (current.Max > max)
{
max = current.Max;
bestmoves = current.GetList;
}
}
}
}
return new Situation(bestmoves, max);
}
Situation RecursiveMinTree(List<SimAnt> undone, int index, bool[,] positions, Situation situation)
{
SimAnt front = undone[index];
int max = situation.Max;
int frontY = front.position.Y;
int frontX = front.position.X;
int currentmin = 100;
foreach (Direction direction in directions)
{
int newX = GameState.WrapHorizonal(frontX + direction.toVector().X);
int newY = GameState.WrapVertical(frontY + direction.toVector().Y);
if (!positions[newX, newY] && map.isNotWater(newX, newY))
{
//add the updated ant to the new allAnts list
List<SimAnt> newallAnts = situation.GetList;
Foe newant = new Foe(newX, newY);
newallAnts.Add(newant);
if (index >= undone.Count - 1)
{
return new Situation(situation.GetList, CalculateBattleValue(situation.GetList));
}
else
{
positions[newX, newY] = true;
Situation current = RecursiveMinTree(undone, index + 1, positions, new Situation(newallAnts, max));//geeft min van alle enemy options
positions[newX, newY] = false;
if (current.Max < max)
return current;
else if (current.Max < currentmin)
{
currentmin = current.Max;
}
}
}
}
return new Situation(situation.GetList, currentmin);
}
class Situation
{
public Situation(List<SimAnt> ants, int max)
{
this.max = max;
this.ants = ants;
}
List<SimAnt> ants;
int max;
public List<SimAnt> GetList
{ get { return ants; } }
public int Max
{ get { return max; } }
}
Kind regards, me