private static bool CheckDirection(GridModel grid, Direction direction) { var vector = Vector.FromDirection(direction); foreach (int x in grid.TraversalsX(direction)) { foreach (int y in grid.TraversalsY(direction)) { var cell1 = grid[x, y]; var cell2 = grid[x + vector.x, y + vector.y]; if (cell1 != null && cell2 != null) { if (cell1.IsOccupied && cell2.IsAvailable) { return true; } if (cell1.IsOccupied && cell2.IsOccupied) { if (cell1.Tile.Value == cell2.Tile.Value) return true; } } } } return false; }
private double Maximizer(GridModel grid, int depth, double alpha, double beta) { double bestScore = alpha; foreach (var direction in Vector.Directions) { var newGrid = new GridModel(grid); int deltaScore; if (PlayerTurn.Move(newGrid, direction, out deltaScore)) { if (depth == 0) { // leaf node, static evaluation return StaticEvaluation(newGrid); } else { alpha = Math.Max(alpha, AlphaBetaSearch(newGrid, depth-1, false, alpha, beta)); } if (alpha > beta) // pruning { return beta; } } } return alpha; }
public override Direction ComputeNextMove(GridModel grid) { var input = GridToInput(grid); var output = _network.Compute(input); // if output is impossible teach->0 // if output has many merges teach good // learning mode from human Direction result = OutputToDirection(output); var validMoves = ValidDirections(grid); if (validMoves.Contains(result)) return result; foreach (Direction direction in validMoves) { TrainNextMove(grid, direction); } /* int validMoveCount = validMoves.Count(); if (validMoveCount > 0) TrainNextMove(grid, validMoves.Last()); */ return Direction.None; }
protected Direction[] ValidDirections(GridModel grid) { Dictionary<Direction, bool> d = new Dictionary<Direction, bool>(); foreach (Direction direction in Vector.Directions) { d[direction] = CheckDirection(grid, direction); } return d.Where(x => x.Value).Select(x => x.Key).ToArray(); }
/// <summary> /// Adds a tile in a random position /// </summary> public void Move(GridModel grid) { var cells = grid.AvailableCells(); if (cells.Any()) { int value = _rand.NextDouble() < _percentageOfValue1 ? 1 : 2; var tile = new TileModel(value); var position = cells[_rand.Next(cells.Length)]; tile.Position = position; } }
public GameModel() { _playerTurn = new PlayerTurn(); _computerTurn = new ComputerTurn(); Score = 0; HighScore = 0; StartTiles = 2; KeepPlaying = true; Grid = new GridModel(4); }
public bool Move(GridModel grid, Direction direction, out int score) { score = 0; if (direction == Direction.None) return false; var vector = Vector.FromDirection(direction); var moved = false; var alreadyMerged = new List<TileModel>(); // Traverse the grid in the right direction and move tiles foreach (int x in grid.TraversalsX(direction)) { foreach (int y in grid.TraversalsY(direction)) { CellModel cell = grid[x, y]; if (cell.IsOccupied) { TileModel tile = cell.Tile; CellModel nextCell; CellModel farthestCell = grid.FindFarthestPosition(cell, vector, out nextCell); TileModel nextTile = (nextCell == null) ? null : nextCell.Tile; // Only one merger per row traversal? if (nextTile != null && nextTile.Value == tile.Value && !alreadyMerged.Contains(nextTile)) { var merged = new TileModel(tile, nextTile); alreadyMerged.Add(merged); tile.Position = null; nextTile.Position = null; merged.Position = nextCell; moved = true; // Update the score score += (int)Math.Pow(2, merged.Value); } else { var oldPosition = tile.Position; tile.Position = farthestCell; if ((oldPosition.PosX != tile.Position.PosX) || (oldPosition.PosY != tile.Position.PosY)) moved = true; } } } } return moved; }
private static double[] GridToInput(GridModel grid) { var input = new double[16]; for (int i = 0; i < 16; i++) input[i] = grid[i].Value; double max = input.Max(); if (max > 0) { // scale all inputs for (int i = 0; i < 16; i++) input[i] = input[i] / max; } return input; }
// use a neural network to compute a score out of different simulated moves private double StaticEvaluation(GridModel grid, Direction direction) { var newGrid = new GridModel(grid); int score; if (PlayerTurn.Move(newGrid, direction, out score)) { var input = new double[16]; for (int i = 0; i < 16; i++) input[i] = newGrid[i].Value; var output = _network.Compute(input); return output[0]; } return double.NegativeInfinity; }
public override Direction ComputeNextMove(GridModel grid) { Direction bestMove = Direction.None; double bestScore = double.NegativeInfinity; foreach (var direction in Vector.Directions) { var score = StaticEvaluation(grid, direction); if (score > bestScore) { bestScore = score; bestMove = direction; } } return bestMove; }
public override Direction ComputeNextMove(GridModel grid) { int maxMerges = 0; var dirs = ValidDirections(grid); Direction move = dirs[_rand.Next(dirs.Length)]; foreach (Direction direction in Vector.Directions) { int merges = CountMerges(direction, grid); if (merges > maxMerges) { maxMerges = merges; move = direction; } } return move; }
/// <summary> /// Create a deep copy of a grid, history of moves and merges removed. /// </summary> /// <param name="grid"></param> public GridModel(GridModel grid) { SizeX = grid.SizeX; SizeY = grid.SizeY; _cells = new List<CellModel>(SizeX * SizeY); for (int i = 0; i < SizeX * SizeY; i++) { int x = i % SizeY; int y = i / SizeX; var cell = new CellModel(x, y); _cells.Add(cell); var sourceCell = grid[x,y]; if (sourceCell.IsOccupied) { var tileCopy = new TileModel(sourceCell.Tile); tileCopy.Position = cell; } } }
private static double StaticEvaluation(GridModel grid) { double smoothWeight = 0.1, monoWeight = 0.1, //islandWeight = 0.0, mono2Weight = 1.0, emptyWeight = 2.7, maxWeight = 1.0; return // grid.Smoothness() * smoothWeight //grid.Monotonicity() * monoWeight //- grid.islands() * islandWeight + grid.Monotonicity2() * mono2Weight //+ Math.Log(grid.EmptyCells()) * emptyWeight //+ grid.MaxValue() * maxWeight ; }
public override Direction ComputeNextMove(GridModel grid) { Direction bestMove = Direction.None; double bestScore = double.NegativeInfinity; var timer = new Stopwatch(); timer.Start(); int depth = 2; do { foreach (var direction in Vector.Directions) { var score = AlphaBetaSearch(grid, depth, true, Double.NegativeInfinity, Double.PositiveInfinity); if (score > bestScore) { bestScore = score; bestMove = direction; } } depth++; } while (depth < 3);//timer.ElapsedMilliseconds < _minSearchTime); return bestMove; }
private int CountMerges(Direction direction, GridModel grid) { var vector = Vector.FromDirection(direction); int merges = 0; foreach (int x in grid.TraversalsX(direction)) { foreach (int y in grid.TraversalsY(direction)) { var cell1 = grid[x, y]; var cell2 = grid[x + vector.x, y + vector.y]; if (cell1 != null && cell2 != null) { if (cell1.IsOccupied && cell2.IsOccupied) { if (cell1.Tile.Value == cell2.Tile.Value) merges++; } } } } return merges; }
public void SetGridSize(int value) { Grid = new GridModel(value); Restart(); }
private double Minimizer(GridModel grid, int depth, double alpha, double beta) { // computer's turn, we'll do heavy pruning to keep the branching factor low var availableCells = grid.AvailableCells().ToList(); // try out all combinations possible foreach (var value in new int[] { 1, 2 }) { foreach (CellModel cell in availableCells) { var tile = new TileModel(value); tile.Position = cell; beta = Math.Min(beta, AlphaBetaSearch(grid, depth, true, alpha, beta)); tile.Position = null; if (beta < alpha) // pruning { return alpha; } } } /* // try a 2 and 4 in each cell and measure how annoying it is // with metrics from eval //var scores = { 2: [], 4: [] }; foreach (var value in new int[] { 1, 2 }) { foreach (CellModel cell in availableCells) { scores[value].push(null); var tile = new TileModel(value); tile.Position = cell; scores[value][i] = -grid.Smoothness() + grid.Islands(); tile.Position = null; } } // now just pick out the most annoying moves var candidates = new List<CellModel>(); var maxScore = Math.Max(Math.Max(null, scores[2]), Math.Max(null, scores[4])); foreach (var value in new int[] { 1, 2 }) { for (var i = 0; i < scores[value].length; i++) { if (scores[value][i] >= maxScore) { candidates.Add(cells[i]); } } } // search on each candidate foreach (CellModel position in candidates) { } */ return beta; }
public abstract Direction ComputeNextMove(GridModel grid);
private double AlphaBetaSearch(GridModel grid, int depth, bool playerTurn, double alpha, double beta) { if (playerTurn) { return Maximizer(grid, depth, alpha, beta); } else { return Minimizer(grid, depth, alpha, beta); } }
public override Direction ComputeNextMove(GridModel grid) { return Direction.Up; }
/* * Grid.prototype.monotonicity = function() { * var self = this; * var marked = []; * var queued = []; * var highestValue = 0; * var highestCell = {x:0, y:0}; * for (var x=0; x<4; x++) { * marked.push([]); * queued.push([]); * for (var y=0; y<4; y++) { * marked[x].push(false); * queued[x].push(false); * if (this.cells[x][y] && * this.cells[x][y].value > highestValue) { * highestValue = this.cells[x][y].value; * highestCell.x = x; * highestCell.y = y; * } * } * } * * increases = 0; * cellQueue = [highestCell]; * queued[highestCell.x][highestCell.y] = true; * markList = [highestCell]; * markAfter = 1; // only mark after all queued moves are done, as if searching in parallel * * var markAndScore = function(cell) { * markList.push(cell); * var value; * if (self.cellOccupied(cell)) { * value = Math.log(self.cellContent(cell).value) / Math.log(2); * } else { * value = 0; * } * for (direction in [0,1,2,3]) { * var vector = self.getVector(direction); * var target = { x: cell.x + vector.x, y: cell.y+vector.y } * if (self.withinBounds(target) && !marked[target.x][target.y]) { * if ( self.cellOccupied(target) ) { * targetValue = Math.log(self.cellContent(target).value ) / Math.log(2); * if ( targetValue > value ) { * //console.log(cell, value, target, targetValue); * increases += targetValue - value; * } * } * if (!queued[target.x][target.y]) { * cellQueue.push(target); * queued[target.x][target.y] = true; * } * } * } * if (markAfter == 0) { * while (markList.length > 0) { * var cel = markList.pop(); * marked[cel.x][cel.y] = true; * } * markAfter = cellQueue.length; * } * } * * while (cellQueue.length > 0) { * markAfter--; * markAndScore(cellQueue.shift()) * } * * return -increases; * } */ // measures how monotonic the grid is. This means the values of the tiles are strictly increasing // or decreasing in both the left/right and up/down directions public static double Monotonicity2(this GridModel grid) { // scores for all four directions var totals = new Dictionary <Direction, double>(); totals[Direction.Up] = 0; totals[Direction.Down] = 0; totals[Direction.Left] = 0; totals[Direction.Right] = 0; // up/down direction for (var x = 0; x < grid.SizeX; x++) { var next = 1; var currentValue = grid[x, 0].Value; while (next < grid.SizeY) { var nextValue = grid[x, next].Value; if (nextValue > 0) { if (currentValue > nextValue) { totals[Direction.Up] += nextValue - currentValue; } else if (nextValue > currentValue) { totals[Direction.Down] += currentValue - nextValue; } currentValue = nextValue; } next++; } } // left/right direction for (var y = 0; y < grid.SizeY; y++) { var next = 1; var currentValue = grid[0, y].Value; while (next < grid.SizeX) { var nextValue = grid[next, y].Value; if (nextValue > 0) { if (currentValue > nextValue) { totals[Direction.Left] += nextValue - currentValue; } else if (nextValue > currentValue) { totals[Direction.Right] += currentValue - nextValue; } currentValue = nextValue; } next++; } } return(Math.Max(totals[Direction.Left], totals[Direction.Right]) + Math.Max(totals[Direction.Up], totals[Direction.Down])); }
public static CellModel FindFarthestPosition(this GridModel grid, CellModel cell, Direction direction, out CellModel next) { return(FindFarthestPosition(grid, cell, Vector.FromDirection(direction), out next)); }
public virtual void TrainNextMove(GridModel grid, Direction direction) { }
public override void TrainNextMove(GridModel grid, Direction direction) { var input = GridToInput(grid); var output = DirectionToOutput(direction); double error = _teacher.Run(input, output); // also train rotated boards at the same time! input = RotateLeft(input); output = DirectionToOutput(RotateLeft(direction)); error = Math.Max(_teacher.Run(input, output), error); input = RotateLeft(input); output = DirectionToOutput(RotateLeft(direction)); error = Math.Max(_teacher.Run(input, output), error); input = RotateLeft(input); output = DirectionToOutput(RotateLeft(direction)); error = Math.Max(_teacher.Run(input, output), error); Console.WriteLine("Error: {0}", error); }
public GridViewModel(GridModel model) { Model = model; }
public static double EmptyCells(this GridModel grid) { return(grid.Cells .Count(x => x.IsAvailable)); }