morris
Version:
Nine Men's Morris implementation in Node.js
238 lines (189 loc) • 7.61 kB
JavaScript
(function() {
class MorrisAI {
constructor(game, gameClass) {
this.game = game;
this.gameConstructor = gameClass;
}
calcMoves() {
var self = this;
var team = this.game.nextTeam;
var possibleMoves = this.game.getMoves();
for (var move of possibleMoves) {
let millPotential = self.millPotential(move) * 1.9;
//console.log(millPotential);
let preventMillPotential = self.preventMillPotential(move) * 1.8;
//console.log(preventMillPotential, move.targetPoint);
let prepareMillPotential = self.prepareMillPotential(move) * 1.7;
//console.log(prepareMillPotential);
let tacticsPotential = self.tacticsPotential(move) * 1;
move.potential = millPotential + preventMillPotential + prepareMillPotential + tacticsPotential;
}
var moves = possibleMoves.sort(function(a, b) {
return a.potential < b.potential ? 1 : a.potential > b.potential ? -1 : 0;
})
return moves;
}
millPotential(move) {
var self = this;
var game = MorrisAI.cloneGame(this.game, this.gameConstructor);
var changeset;
var actions = {
set() {
var setOptions = Object.assign({
team: game.nextTeam
}, move.targetPoint.position);
changeset = game.set(setOptions);
},
move() {
changeset = game.move(move.startPoint.position, move.targetPoint.position);
},
remove() {
changeset = game.remove(move.targetPoint.position);
}
};
actions[move.action]();
return changeset.targetPoint.mills.length;
}
preventMillPotential(move) {
var self = this;
var changeset;
var game = MorrisAI.cloneGame(this.game, this.gameConstructor);
var contraryTeams = game.teams.filter(team => team.name != game.nextTeam);
var targetPoint = game.board.getPoint(move.targetPoint.position.row, move.targetPoint.position.position);
var potential = 0;
var actions = {
set() {
contraryTeams.forEach(function(team) {
potential += checkForMill(team);
game = MorrisAI.cloneGame(self.game, self.gameConstructor);
});
},
move() {
contraryTeams.forEach(function(team) {
potential += checkForMill(team);
game = MorrisAI.cloneGame(self.game, self.gameConstructor);
});
},
remove() {
potential += targetPoint.mills.length;
}
};
function checkForMill(team) {
var setOptions = Object.assign({
team: team.name
}, move.targetPoint.position);
game.board.set(setOptions);
return targetPoint.mills.length;
}
actions[game.nextAction]();
return potential;
}
prepareMillPotential(move) {
var self = this;
var game = MorrisAI.cloneGame(this.game, this.gameConstructor);
var targetPoint = game.board.getPoint(move.targetPoint.position.row, move.targetPoint.position.position);
var contraryTeams = game.teams.filter(team => team.name != game.nextTeam);
var potential = 0;
var actions = {
set: checkForPreparedMill,
move: checkForPreparedMill,
remove() {
contraryTeams.forEach(function(team) {
var setOptions = Object.assign({
team: team.name
}, move.targetPoint.position);
game.board.set(setOptions);
potential += targetPoint.mills.length;
});
}
};
function checkForPreparedMill() {
for (var direction in targetPoint.surroundings) {
if (targetPoint.surroundings.hasOwnProperty(direction)) {
let point = targetPoint.surroundings[direction];
if (point) {
let setOptions = Object.assign({
team: game.nextTeam
}, point.position);
game.board.set(setOptions);
potential += point.mills.length;
setOptions.team = false;
game.board.set(setOptions);
}
}
}
}
return potential;
}
tacticsPotential(move) {
var potential = 0;
for (var direction in move.targetPoint.surroundings) {
if (move.targetPoint.surroundings.hasOwnProperty(direction)) {
let point = move.targetPoint.surroundings[direction];
if (point) {
potential += point.team === game.nextTeam ? 1 : 0.5;
}
}
}
return potential;
}
// Static method to clone a game instance
static cloneGame(gameInstance, gameConstructor) {
var options = {
board: {
rows: gameInstance.board.rows.length,
points: gameInstance.board.points,
default: gameInstance.board.map.map(point => point.team)
},
pieces: gameInstance.teams[0].pieces.length,
teams: gameInstance.teams.map(function(team) {
return {
name: team.name,
pieces: team.pieces.map(piece => piece)
}
}),
rules: false,
lastChangeset: Object.assign({}, gameInstance.__lastChangeset)
};
var game = new gameConstructor(options);
// Set the 'targetPoint' to the new instance
var lastChangesetTargetIndex = gameInstance.board.map.indexOf(gameInstance.__lastChangeset.targetPoint);
game.__lastChangeset.targetPoint = game.board.map[lastChangesetTargetIndex];
// Get the team object from the original game instance
var lastChangesetTeam = gameInstance.teams.objectFromKey(gameInstance.getTeam(gameInstance.__lastChangeset.piece), "name");
// Get the index of the piece within this original team object
var lastChangesetPieceIndex = lastChangesetTeam.pieces.indexOf(gameInstance.__lastChangeset.piece);
// Set the new instance's lastChangeset.piece to a piece object from its own team but usually with the same index as the original
game.__lastChangeset.piece = game.teams.objectFromKey(lastChangesetTeam.name, "name").pieces[lastChangesetPieceIndex];
return game;
}
}
if ("process" in this) {
module.exports = MorrisAI;
}
else {
window.MorrisAI = MorrisAI;
}
})();
Object.defineProperty(Array.prototype, "max",{
get() {
return Math.max.apply(Math, this);
}
});
/*
# AI
The AI used here is a fast and simple AI program that tries to get the best moves by validating their potential. It is important to know, that the AI **is not** perfect by calculating the whole game. That would have been a solution but I did not liked such a AI. I wanted an AI that is not absolutely perfect but just simple and fast.
In detail, the AI works with a lot of methods that return a potential for inner game actions and moves. They are not too complex but very general. You can win against the AI ;-)
The AI module can also be used within browser's API or within Node.js. *You do not have to browserify the module!* Within a browser's API it will create the class `MorrisAI` to the window object you can use instead. If you are using Node.js, you can *require* it simply.
```javascript
const MorrisAI = require('./morrisAI');
```
```javascript
var ai = new MorrisAI(myGame, MorrisGame);
// Get the best moves
var bestMoves = ai.calcMoves();
// Log them to console
console.log(bestMoves);
```
You need to put your game instance into and your constructor class. That is important because the AI would do some stuff with the original class.
*/