Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
CBGamesdev authored Sep 16, 2021
1 parent eb28f41 commit 43ffcdd
Show file tree
Hide file tree
Showing 8 changed files with 1,112 additions and 0 deletions.
150 changes: 150 additions & 0 deletions 2048/js/ai.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
function AI(grid) {
this.grid = grid;
}

// static evaluation function
AI.prototype.eval = function() {
var emptyCells = this.grid.availableCells().length;

var smoothWeight = 0.1,
//monoWeight = 0.0,
//islandWeight = 0.0,
mono2Weight = 1.0,
emptyWeight = 2.7,
maxWeight = 1.0;

return this.grid.smoothness() * smoothWeight
//+ this.grid.monotonicity() * monoWeight
//- this.grid.islands() * islandWeight
+ this.grid.monotonicity2() * mono2Weight
+ Math.log(emptyCells) * emptyWeight
+ this.grid.maxValue() * maxWeight;
};

// alpha-beta depth first search
AI.prototype.search = function(depth, alpha, beta, positions, cutoffs) {
var bestScore;
var bestMove = -1;
var result;

// the maxing player
if (this.grid.playerTurn) {
bestScore = alpha;
for (var direction in [0, 1, 2, 3]) {
var newGrid = this.grid.clone();
if (newGrid.move(direction).moved) {
positions++;
if (newGrid.isWin()) {
return { move: direction, score: 10000, positions: positions, cutoffs: cutoffs };
}
var newAI = new AI(newGrid);

if (depth == 0) {
result = { move: direction, score: newAI.eval() };
} else {
result = newAI.search(depth-1, bestScore, beta, positions, cutoffs);
if (result.score > 9900) { // win
result.score--; // to slightly penalize higher depth from win
}
positions = result.positions;
cutoffs = result.cutoffs;
}

if (result.score > bestScore) {
bestScore = result.score;
bestMove = direction;
}
if (bestScore > beta) {
cutoffs++
return { move: bestMove, score: beta, positions: positions, cutoffs: cutoffs };
}
}
}
}

else { // computer's turn, we'll do heavy pruning to keep the branching factor low
bestScore = beta;

// try a 2 and 4 in each cell and measure how annoying it is
// with metrics from eval
var candidates = [];
var cells = this.grid.availableCells();
var scores = { 2: [], 4: [] };
for (var value in scores) {
for (var i in cells) {
scores[value].push(null);
var cell = cells[i];
var tile = new Tile(cell, parseInt(value, 10));
this.grid.insertTile(tile);
scores[value][i] = -this.grid.smoothness() + this.grid.islands();
this.grid.removeTile(cell);
}
}

// now just pick out the most annoying moves
var maxScore = Math.max(Math.max.apply(null, scores[2]), Math.max.apply(null, scores[4]));
for (var value in scores) { // 2 and 4
for (var i=0; i<scores[value].length; i++) {
if (scores[value][i] == maxScore) {
candidates.push( { position: cells[i], value: parseInt(value, 10) } );
}
}
}

// search on each candidate
for (var i=0; i<candidates.length; i++) {
var position = candidates[i].position;
var value = candidates[i].value;
var newGrid = this.grid.clone();
var tile = new Tile(position, value);
newGrid.insertTile(tile);
newGrid.playerTurn = true;
positions++;
newAI = new AI(newGrid);
result = newAI.search(depth, alpha, bestScore, positions, cutoffs);
positions = result.positions;
cutoffs = result.cutoffs;

if (result.score < bestScore) {
bestScore = result.score;
}
if (bestScore < alpha) {
cutoffs++;
return { move: null, score: alpha, positions: positions, cutoffs: cutoffs };
}
}
}

return { move: bestMove, score: bestScore, positions: positions, cutoffs: cutoffs };
}

// performs a search and returns the best move
AI.prototype.getBest = function() {
return this.iterativeDeep();
}

// performs iterative deepening over the alpha-beta search
AI.prototype.iterativeDeep = function() {
var start = (new Date()).getTime();
var depth = 0;
var best;
do {
var newBest = this.search(depth, -10000, 10000, 0 ,0);
if (newBest.move == -1) {
break;
} else {
best = newBest;
}
depth++;
} while ( (new Date()).getTime() - start < minSearchTime);
return best
}

AI.prototype.translate = function(move) {
return {
0: 'up',
1: 'right',
2: 'down',
3: 'left'
}[move];
}
26 changes: 26 additions & 0 deletions 2048/js/animframe_polyfill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
(function() {
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}

if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}

if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}
}());
7 changes: 7 additions & 0 deletions 2048/js/application.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
animationDelay = 100;
minSearchTime = 100;

// Wait till the browser is ready to render the game (avoids glitches)
window.requestAnimationFrame(function () {
var manager = new GameManager(4, KeyboardInputManager, HTMLActuator);
});
97 changes: 97 additions & 0 deletions 2048/js/game_manager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
function GameManager(size, InputManager, Actuator) {
this.size = size; // Size of the grid
this.inputManager = new InputManager;
this.actuator = new Actuator;

this.running = false;

this.inputManager.on("move", this.move.bind(this));
this.inputManager.on("restart", this.restart.bind(this));

this.inputManager.on('think', function() {
var best = this.ai.getBest();
this.actuator.showHint(best.move);
}.bind(this));


this.inputManager.on('run', function() {
if (this.running) {
this.running = false;
this.actuator.setRunButton('Auto-run');
} else {
this.running = true;
this.run()
this.actuator.setRunButton('Stop');
}
}.bind(this));

this.setup();
}

// Restart the game
GameManager.prototype.restart = function () {
this.actuator.restart();
this.running = false;
this.actuator.setRunButton('Auto-run');
this.setup();
};

// Set up the game
GameManager.prototype.setup = function () {
this.grid = new Grid(this.size);
this.grid.addStartTiles();

this.ai = new AI(this.grid);

this.score = 0;
this.over = false;
this.won = false;

// Update the actuator
this.actuate();
};


// Sends the updated grid to the actuator
GameManager.prototype.actuate = function () {
this.actuator.actuate(this.grid, {
score: this.score,
over: this.over,
won: this.won
});
};

// makes a given move and updates state
GameManager.prototype.move = function(direction) {
var result = this.grid.move(direction);
this.score += result.score;

if (!result.won) {
if (result.moved) {
this.grid.computerMove();
}
} else {
this.won = true;
}

//console.log(this.grid.valueSum());

if (!this.grid.movesAvailable()) {
this.over = true; // Game over!
}

this.actuate();
}

// moves continuously until game is over
GameManager.prototype.run = function() {
var best = this.ai.getBest();
this.move(best.move);
var timeout = animationDelay;
if (this.running && !this.over && !this.won) {
var self = this;
setTimeout(function(){
self.run();
}, timeout);
}
}
Loading

0 comments on commit 43ffcdd

Please sign in to comment.