Skip to content

Instantly share code, notes, and snippets.

@courthead
Created September 4, 2019 18:48
Show Gist options
  • Save courthead/3d41cc03edda1e596051129b445fca17 to your computer and use it in GitHub Desktop.
Save courthead/3d41cc03edda1e596051129b445fca17 to your computer and use it in GitHub Desktop.
class GameTreeNode {
constructor(obj = {}) {
this.parentNode = obj.parentNode || null;
this.upNode = null;
this.rightNode = null;
this.downNode = null;
this.leftNode = null;
this.minScore = obj.parentNode ? obj.parentNode.minScore + 1 : 0;
this.score = null;
this.dir = obj.dir || null;
}
getNextNode() {
if (!this.upNode || !this.upNode.score) {
if (!this.upNode) {
this.upNode = new GameTreeNode({ parentNode: this, dir: 'u' });
}
return this.upNode;
}
if (!this.rightNode || !this.rightNode.score) {
if (!this.rightNode) {
this.rightNode = new GameTreeNode({ parentNode: this, dir: 'r' });
}
return this.rightNode;
}
if (!this.downNode || !this.downNode.score) {
if (!this.downNode) {
this.downNode = new GameTreeNode({ parentNode: this, dir: 'd' });
}
return this.downNode;
}
if (!this.leftNode || !this.leftNode.score) {
if (!this.leftNode) {
this.leftNode = new GameTreeNode({ parentNode: this, dir: 'l' });
}
return this.leftNode;
}
return this.parentNode;
}
getMoveList() {
let str = this.dir;
let node = this;
while (node.parentNode) {
if (node.parentNode.dir) {
str = `${node.parentNode.dir}${str}`;
}
node = node.parentNode;
}
return str;
}
}
class Solver {
constructor() {
this.nodeExamining = null;
}
searchNextNode() {
this.playNodeMoveOnGame(this.nodeExamining);
if (!window.dead) {
this.nodeExamining = this.nodeExamining.getNextNode();
this.searchNextNode();
}
}
playNodeMoveOnGame(node) {
if (node.dir) {
this.dispatchMove(node.dir);
if (window.dead) {
node.score = node.minScore;
console.log(node.score + ' - ' + node.getMoveList()); // eslint-disable-line
if (!this.bestNode || node.score > this.bestNode.score) {
this.bestNode = node;
console.log(` *** BEST: ${node.score} ***\n ${node.getMoveList()}\n`); // eslint-disable-line
}
setTimeout(() => this.restartPlaying(), 0)
}
}
}
dispatchMove(dir) {
switch (dir) {
case 'u':
window.dispatchEvent(new KeyboardEvent('keyup', { keyCode: 38 }));
break;
case 'r':
window.dispatchEvent(new KeyboardEvent('keyup', { keyCode: 39 }));
break;
case 'd':
window.dispatchEvent(new KeyboardEvent('keyup', { keyCode: 40 }));
break;
case 'l':
window.dispatchEvent(new KeyboardEvent('keyup', { keyCode: 37 }));
break;
}
}
restartPlaying() {
this.resetNodeExamining();
if (window.ogMap) {
window.MAP = JSON.parse(JSON.stringify(window.ogMap));
window.dead = false;
window.myRPG = new RPG(document.getElementById('game').getContext('2d'), window.MAP, document.getElementById('score')); // eslint-disable-line
this.searchNextNode();
} else {
window.ogMap = JSON.parse(JSON.stringify(window.MAP));
this.createCustomRpg();
document.querySelector('body').innerHTML = `<canvas id="game" width="32" height="32" style="height: 89vh;image-rendering: pixelated;image-rendering: crisp-edges;">
Sorry, this game doesn't work on your browser. Please update it.
</canvas><span id="score" style="font-size: 10em;position: relative;top: -1.5em;">0</span>`;
this.restartPlaying();
}
}
resetNodeExamining() {
if (this.nodeExamining) {
while (this.nodeExamining.parentNode) {
this.nodeExamining = this.nodeExamining.parentNode;
}
} else {
this.nodeExamining = new GameTreeNode();
}
}
// I edited the `RPG` function a bit to remove unnecessary rendering and other
// stuff that makes it take longer to play and reset the game.
createCustomRpg() {
window.RPG = function (ctx, map, score) {
var _mapMeanings = {
" ": {
color: "#000000"
},
"E": {
color: "#af0000"
},
"S": {
color: "#ff9800"
},
"1": {
color: "green"
},
"2": {
color: "darkgreen"
}
};
var _enemyLocations = [];
var _health = 1;
var over = false;
var _enemyTick = function (perferAxis) {
switch (_turnCount % 12) {
case 0:
_enemyLocations.push({x: 0, y: 0});
break;
case 3:
_enemyLocations.push({x: 0, y: 31});
break;
case 6:
_enemyLocations.push({x: 31, y: 0});
break;
case 9:
_enemyLocations.push({x: 31, y: 31});
break;
}
if (!perferAxis) perferAxis = "y";
_enemyLocations.forEach(function (enemy, index) {
var xDistance = enemy.x - _player[_currentPlayer].X;
var yDistance = enemy.y - _player[_currentPlayer].Y;
if (xDistance > 0.5) xDistance = -1;
else if (xDistance < -0.5) xDistance = 1;
else xDistance = 0;
if (yDistance > 0.5) yDistance = -1;
else if (yDistance < -0.5) yDistance = 1;
else yDistance = 0;
if ((xDistance !== 0) && (yDistance !== 0)) {
if ((_turnCount % 2) === 0) {
xDistance = 0;
} else {
yDistance = 0;
}
}
var newCoords = _putInBounds(enemy.x + xDistance, enemy.y + yDistance);
xDistance = newCoords.x;
yDistance = newCoords.y;
var conflict = false;
enemy.current = true;
for (var enemyName in _enemyLocations) {
if (_enemyLocations[enemyName].current) continue;
if ((_enemyLocations[enemyName].x === xDistance) && (_enemyLocations[enemyName].y === yDistance)) {
conflict = true;
break;
}
}
enemy.current = false;
if (!conflict) {
enemy.x = xDistance;
enemy.y = yDistance;
}
if ((enemy.x === _player[1].X) && (enemy.y === _player[1].Y)) {
_health--;
dead = true;
if (onDeath === noop) {
over = true;
} else {
onDeath();
}
}
_enemyLocations[index] = enemy;
});
};
var _putInBounds = function (x, y) {
if (x > 31) {
x = 31;
}
if (y > 31) {
y = 31;
}
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
return {x: x, y: y};
};
var _player = {};
_player[1] = {
X: 16,
Y: 16
};
_player[2] = {
X: 24,
Y: 16
};
this.render = function () {
window.scroll(0, 0);
score.innerText = _turnCount;
map.forEach(function (mapRow, y) {
for (var x = 0; x < mapRow.length; x++) {
let mapTile = mapRow[x];
if (mapTile === "E") {
mapTile = " ";
}
ctx.fillStyle = _mapMeanings[mapTile].color;
ctx.fillRect(x, y, 1, 1);
}
});
ctx.fillStyle = _mapMeanings["E"].color;
_enemyLocations.forEach(function (enemy) {
ctx.fillRect(enemy.x, enemy.y, 1, 1);
});
ctx.fillStyle = _mapMeanings["1"].color;
ctx.fillRect(_player[1].X, _player[1].Y, 1, 1);
};
var _turnCount = 0;
var _currentPlayer = 1;
this.parseKeyPress = function (e) {
if (over) return;
var perferAxis = "y";
switch (e.keyCode) {
case 40: // Down arrow
case 83: // S
_player[_currentPlayer].Y++;
break;
case 38: // Up arrow
case 87: // W
_player[_currentPlayer].Y--;
break;
case 39: // Right arrow
case 68: // D
perferAxis = "x";
_player[_currentPlayer].X++;
break;
case 37: // Left arrow
case 65:
perferAxis = "x";
_player[_currentPlayer].X--;
break;
default:
return;
break;
}
_turnCount++;
var newCoords = _putInBounds(_player[_currentPlayer].X, _player[_currentPlayer].Y);
_player[_currentPlayer].X = newCoords.x;
_player[_currentPlayer].Y = newCoords.y;
_enemyTick(perferAxis);
this.render();
};
this.render();
};
}
}
s = new Solver()
s.restartPlaying();
// Run this any time you want to see the best score so far:
// console.log(s.bestNode.score)
// Run this any time you want to see the move list for the best score so far:
// console.log(s.bestNode.getMoveList)
// You can do the same to look at the current node being examined:
// console.log(s.nodeExamining)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment