I'm trying to implement the game Snake in Javascript and getting the computer to play it by itself, and want to check whenever the snake loops (repeats the same set of moves twice) so I can kill it.
To do this, the first thing that came to mind is saving the state the entire game at every frame (the state being the position of the snake's head and body segments, as well as the position of the 'pallet' it's looking to eat) in some sort of dictionary, and checking every frame if the new state of the game exists in this dictionary.
I don't quite know what's the best/most efficient/most readable way to implement this is. Here's what I tried so far:
const Snake = {
direction: new Vector2(0, 1), // Up
pallet: Vector2.random(0, 100, 0, 100).floor(),
head: Vector2.random(0, 100, 0, 100).floor(),
body: [], // Array of Vector2's
loopHistory: {}, // Records every state
checkForLoop: function () {
// Current state of the system:
const arr = [this.direction.hash(), this.pallet.hash(), this.head.hash(), ...this.body.map(x => x.hash())];
//True if a loop is found, false if the state is completely new:
let found = true;
let obj = this.loopHistory;
for (let i = 0; i < arr.length; i++) {
if (!obj.hasOwnProperty(arr[i])) {
//This is a part of the state that has never been seen before (new pallet position, new body segment etc.)
found = false;
// Record it for the next time:
obj[arr[i]] = {};
}
obj = obj[arr[i]];
}
return found;
}
}
loopHistory
and checkForLoop
are the relevant bits. Here's the Vector2 class for reference:
const Vector2 = function (x, y) {
this.x = x;
this.y = y;
};
Vector2.random = function (xmin, xmax, ymin, ymax) {
return new Vector2(
xmin + Math.random() * (xmax - xmin),
ymin + Math.random() * (ymax - ymin)
);
};
Vector2.prototype.hash = function () {
return this.x + 73856093 * this.y;
};
Vector2.prototype.floor = function () {
return new Vector2(Math.floor(this.x), Math.floor(this.y));
};
The issue with this implementation is it generates a massive ever growing arbitrarily deep object (loopHistory) for every game being run. With each game there could be thousands of states before the snake actually loops to its death, and there could be up to one hundred games running at once. The solution feels inefficient and clumsy and difficult to read/maintain, plus it takes a lot of memory, but that seems inevitable. So I'm looking for the go-to solution for situation like this, if one exists, or just any better implementation.
Please help me kill the snake! Thanks in advance for answers.
Edit: I forgot to mention that the behavior of the snake is only dependent on the state of the system that we captured earlier (head position, body positions, pallet position) so if it finds itself in the exact same circumstance it will inevitably react the same way and repeat the same moves.