hex-game
Version:
Hex Game utilities
520 lines • 26.2 kB
JavaScript
"use strict";
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
to[j] = from[i];
return to;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Board = void 0;
var core_1 = require("@my-devkit/core");
var stone_1 = require("./stone");
var template_1 = require("./template");
var types_1 = require("./types");
var Board = /** @class */ (function () {
function Board(size, moves) {
this.size = size;
this._moves = [];
this._occupiedPositions = new Map();
this.positions = [];
this.moves = moves;
this.positions = [];
for (var i = 0; i < this.size; i++) {
for (var j = 0; j < this.size; j++) {
this.positions.push(new stone_1.Stone([i + 1, j + 1], this));
}
}
this._templates = [];
var _loop_1 = function (template) {
this_1._templates.push(template);
// add symetrical template if different
var stone = new stone_1.Stone('h9', this_1);
var transform = function (c) { return stone.getRelativeStone(stone.getRelativeCoordinates(stone.reflect(c))).position; };
var stonesPositions = template.stones.map(function (c) { return stone.getRelativeStone(c).position; });
var freePositions = template.free.map(function (c) { return stone.getRelativeStone(c).position; });
if (template.stones.some(function (s) { return !stonesPositions.includes(transform(s)); })
|| template.free.some(function (s) { return !freePositions.includes(transform(s)); })) {
this_1._templates.push({
name: template.name,
stones: template.stones.map(function (c) { return stone.getRelativeCoordinates(stone.reflect(c)); }),
edges: template.edges.map(function (c) { return stone.getRelativeCoordinates(stone.reflect(c)); }),
free: template.free.map(function (c) { return stone.getRelativeCoordinates(stone.reflect(c)); }),
});
}
};
var this_1 = this;
for (var _i = 0, TEMPLATES_1 = types_1.TEMPLATES; _i < TEMPLATES_1.length; _i++) {
var template = TEMPLATES_1[_i];
_loop_1(template);
}
this._bridgeTemplate = this._templates.find(function (t) { return t.name === 'bridge'; });
}
Object.defineProperty(Board.prototype, "moves", {
set: function (moves) {
this._moves = core_1._cloneDeep(moves);
this._occupiedPositions = new Map();
for (var _i = 0, _a = this._moves; _i < _a.length; _i++) {
var move = _a[_i];
this._occupiedPositions.set(new stone_1.Stone(move.position, this).position, move.player);
}
},
enumerable: false,
configurable: true
});
Object.defineProperty(Board.prototype, "playerToMove", {
get: function () {
if (!this._moves || this._moves.length === 0) {
return types_1.HexGamePlayer.Black;
}
var lastMove = this._moves[this._moves.length - 1];
return types_1.HexGamePlayer.helper.getOpponent(lastMove.player);
},
enumerable: false,
configurable: true
});
Object.defineProperty(Board.prototype, "freePositions", {
get: function () {
var _this = this;
return this.positions.filter(function (s) { return _this.isFreePosition(s); });
},
enumerable: false,
configurable: true
});
Board.prototype.addMove = function (move) {
this.moves = __spreadArray(__spreadArray([], this._moves), [move]);
};
Board.prototype.revertLastMove = function () {
this.moves = this._moves.slice(0, -1);
};
Board.prototype.isFreePosition = function (stone) {
return stone.isValid && !this._occupiedPositions.has(stone.position);
};
Board.prototype.isOccupiedBy = function (stone, player) {
return this._occupiedPositions.get(stone.position) === player;
};
Board.prototype.isOccupiedByOpponent = function (stone, player) {
return this._occupiedPositions.get(stone.position) === types_1.HexGamePlayer.helper.getOpponent(player);
};
Board.prototype.isWinningPosition = function (stoneAdded) {
var _this = this;
var checkedPositions = new Set();
var stonesToBeChecked = [stoneAdded];
var finished = false;
var reachedEdges = new Set();
while (!finished) {
var stone = stonesToBeChecked.pop();
if (stone.isAdjacentToAnEdge(this.playerToMove)) {
reachedEdges.add(stone.adjacentEdge(this.playerToMove));
}
checkedPositions.add(stone.position);
stone.getNeighbors().forEach(function (s) {
if (_this._occupiedPositions.get(s.position) === _this.playerToMove && !checkedPositions.has(s.position)) {
stonesToBeChecked.push(s);
}
});
finished = reachedEdges.size === 2 || stonesToBeChecked.length === 0;
}
return reachedEdges.size === 2;
};
Board.prototype.getChains = function () {
var chainedPositions = new Set();
var chains = [];
for (var _i = 0, _a = this._moves; _i < _a.length; _i++) {
var move = _a[_i];
var stone = new stone_1.Stone(move.position, this);
if (chainedPositions.has(stone.position)) {
continue;
}
var chain = this.getChain(stone);
for (var _b = 0, chain_1 = chain; _b < chain_1.length; _b++) {
var chainStone = chain_1[_b];
chainedPositions.add(chainStone.position);
}
chains.push({ player: move.player, stones: chain });
}
return chains;
};
Board.prototype.getChain = function (start) {
var player = this._occupiedPositions.get(start.position);
if (!player) {
return [];
}
var chain = new Map();
var stonesToBeChecked = [start];
while (stonesToBeChecked.length > 0) {
var stone = stonesToBeChecked.pop();
if (chain.has(stone.position)) {
continue;
}
if (this._occupiedPositions.get(stone.position) !== player) {
continue;
}
chain.set(stone.position, stone);
stonesToBeChecked.push.apply(stonesToBeChecked, stone.getNeighbors());
}
return Array.from(chain.values());
};
Board.prototype.getGroups = function (templates) {
var groupedPositions = new Set();
var groups = [];
for (var _i = 0, _a = this._moves; _i < _a.length; _i++) {
var move = _a[_i];
var stone = new stone_1.Stone(move.position, this);
if (groupedPositions.has(stone.position)) {
continue;
}
var group = this.getGroup(stone, templates);
for (var _b = 0, group_1 = group; _b < group_1.length; _b++) {
var groupStone = group_1[_b];
groupedPositions.add(groupStone.position);
}
var groupId = move.player + "-" + group.map(function (s) { return s.position; }).sort().join('-');
groups.push({ id: groupId, player: move.player, stones: group, movesToConnectNorth: [], movesToConnectSouth: [], moveCountToConnect: 0 });
}
var _loop_2 = function (group) {
var otherGroups = groups.filter(function (g) { return g.player === group.player; }).map(function (g) { return types_1.iStoneMap.fromArray(g.stones); });
group.movesToConnectNorth = this_2.getMovesToConnectEdge('North', group, templates, otherGroups);
group.movesToConnectSouth = this_2.getMovesToConnectEdge('South', group, templates, otherGroups);
group.moveCountToConnect += group.movesToConnectNorth ? group.movesToConnectNorth.length : Infinity;
group.moveCountToConnect += group.movesToConnectSouth ? group.movesToConnectSouth.length : Infinity;
};
var this_2 = this;
for (var _c = 0, groups_1 = groups; _c < groups_1.length; _c++) {
var group = groups_1[_c];
_loop_2(group);
}
return groups;
};
Board.prototype.getGroup = function (start, templates) {
if (templates === void 0) { templates = []; }
var player = this._occupiedPositions.get(start.position);
if (!player) {
return [];
}
var group = new Map();
var stonesToBeChecked = [start];
while (stonesToBeChecked.length > 0) {
var stone = stonesToBeChecked.pop();
if (group.has(stone.position)) {
continue;
}
if (this._occupiedPositions.get(stone.position) !== player) {
continue;
}
group.set(stone.position, stone);
stonesToBeChecked.push.apply(stonesToBeChecked, stone.getNeighbors());
for (var _i = 0, templates_1 = templates; _i < templates_1.length; _i++) {
var template = templates_1[_i];
if (template.player === player && template.startingStone.position === stone.position) {
stonesToBeChecked.push.apply(stonesToBeChecked, template.connectableStones);
}
}
}
return Array.from(group.values());
};
Board.prototype.getTemplates = function () {
var templates = [];
for (var _i = 0, _a = this._moves; _i < _a.length; _i++) {
var move = _a[_i];
templates.push.apply(templates, this.getStoneTemplates(move.player, move.position));
}
return templates;
};
Board.prototype.rate = function () {
var _this = this;
var _a;
var chainedPositions = new Set();
var templatePositions = new Map();
var positionRates = new Map();
var templates = this.getTemplates();
for (var _i = 0, templates_2 = templates; _i < templates_2.length; _i++) {
var template = templates_2[_i];
for (var _b = 0, _c = template.freeStones; _b < _c.length; _b++) {
var stone = _c[_b];
templatePositions.set(stone.position, template.player);
}
}
var _loop_3 = function (chain) {
var chainNeighbors = new Map();
var blockedPositions = new Map();
blockedPositions.set(0, new Set());
var _loop_5 = function (distance) {
blockedPositions.set(distance, new Set());
for (var _l = 0, _m = chain.stones; _l < _m.length; _l++) {
var chainStone = _m[_l];
chainedPositions.add(chainStone.position);
for (var _o = 0, _p = chainStone.getNeighbors(distance); _o < _p.length; _o++) {
var neighbor = _p[_o];
var blocked = neighbor.getNeighbors().some(function (n) { return blockedPositions.get(distance - 1).has(n.position); });
var isFree = this_3.isFreePosition(neighbor);
if (isFree && !blocked && !templatePositions.has(neighbor.position) && !chainNeighbors.has(neighbor.position)) {
chainNeighbors.set(neighbor.position, { distance: distance, stone: neighbor });
}
if (this_3.isOccupiedByOpponent(neighbor, chain.player) || blocked) {
blockedPositions.get(distance).add(neighbor.position);
}
}
}
};
for (var distance = 1; distance <= 6; distance++) {
_loop_5(distance);
}
for (var _h = 0, _j = Array.from(chainNeighbors.entries()); _h < _j.length; _h++) {
var _k = _j[_h], position = _k[0], neighbor = _k[1];
if (!positionRates.has(position)) {
positionRates.set(position, { player: chain.player, position: position, rate: 0 });
}
var positionRate = positionRates.get(position);
var increment = this_3.ratePlayerSign(chain.player) * 0.25 / Math.pow(2, neighbor.distance - 1);
var signedRate = this_3.ratePlayerSign(positionRate.player) * positionRate.rate;
var newSignedRate = signedRate + increment;
positionRate.player = this_3.signedRatePlayer(newSignedRate);
positionRate.rate = Math.abs(newSignedRate);
}
};
var this_3 = this;
for (var _d = 0, _e = this.getChains(); _d < _e.length; _d++) {
var chain = _e[_d];
_loop_3(chain);
}
var playerRates = types_1.HexGamePlayer.helper.members.map(function (player) {
return {
player: player,
rate: core_1._sum(Array.from(positionRates.values()).filter(function (position) { return position.player === player; }).map(function (position) { return position.rate; })) / Math.pow(3, _this._moves.length / 2),
bestGroup: null
};
});
var _loop_4 = function (group) {
if (group.stones.length > 1) {
var playerRate = playerRates.find(function (pr) { return pr.player === group.player; });
var groupRate = Math.pow(Math.max(0, this_4.size - 1 - group.moveCountToConnect), 3);
playerRate.rate += groupRate;
if (groupRate >= ((_a = playerRate.bestGroup) === null || _a === void 0 ? void 0 : _a.rate)) {
playerRate.bestGroup = { group: group, rate: groupRate };
}
}
};
var this_4 = this;
for (var _f = 0, _g = this.getGroups(templates); _f < _g.length; _f++) {
var group = _g[_f];
_loop_4(group);
}
var forecastWinner = core_1._maxBy(playerRates, function (pr) { return pr.rate; }).player;
var forecastReliability = core_1._maxBy(playerRates, function (pr) { return pr.rate; }).rate - core_1._minBy(playerRates, function (pr) { return pr.rate; }).rate;
return {
forecast: {
winner: forecastWinner,
reliability: forecastReliability
},
players: new Map(playerRates.map(function (pr) { return [pr.player, pr]; })),
positions: positionRates,
};
};
Board.prototype.getStoneTemplates = function (player, position) {
var _this = this;
var stone = new stone_1.Stone(position, this);
var result = [];
var _loop_6 = function (rotation) {
var transform = function (c) { return stone.rotate(c, rotation); };
for (var _b = 0, _c = this_5._templates; _b < _c.length; _b++) {
var template = _c[_b];
var isEdgeTemplate = template.edges.length > 0;
if ((!isEdgeTemplate || result.filter(function (r) { return !!r.edge; }).length === 0)
&& template.edges.every(function (p) { return transform(p).isEdge(player); })
&& template.stones.every(function (p) { return _this.isOccupiedBy(transform(p), player); })
&& template.free.every(function (p) { return _this.isFreePosition(transform(p)); })) {
var freeStones = template.free.map(function (p) { return transform(p); });
var connectableStones = template.stones.map(function (p) { return transform(p); });
result.push(new template_1.Template(this_5, template.name, player, stone, freeStones, connectableStones, isEdgeTemplate ? transform(template.edges[0]).edge(player) : null));
}
}
};
var this_5 = this;
for (var _i = 0, _a = [0, 1, 2, 3, 4, 5]; _i < _a.length; _i++) {
var rotation = _a[_i];
_loop_6(rotation);
}
return result;
};
Board.prototype.getMovesToConnectEdge = function (edge, group, templates, otherGroups) {
var stones = types_1.iStoneMap.fromArray(group.stones);
var opponentTemplateFreeStones = new Set();
core_1._flatten(templates
.filter(function (t) { return t.player === types_1.HexGamePlayer.helper.getOpponent(group.player); })
.map(function (t) { return t.freeStones; })).forEach(function (s) { return opponentTemplateFreeStones.add(s.position); });
var bestTry;
for (var _i = 0, _a = types_1.iStoneMap.stones(stones); _i < _a.length; _i++) {
var stone = _a[_i];
bestTry = this.recursive(stone, edge, group.player, types_1.iStoneMap.clone(stones), __spreadArray([], templates), otherGroups, [], opponentTemplateFreeStones, bestTry);
}
return bestTry;
};
Board.prototype.recursive = function (stone, edge, player, stones, templates, otherGroups, moves, opponentTemplateFreeStones, bestTry) {
if (this.edgesReached(player, types_1.iStoneMap.stones(stones), templates).has(edge)) {
return moves;
}
if (moves.length > 7 || (bestTry && moves.length >= bestTry.length)) {
return null;
}
var _loop_7 = function (escape_1) {
var tryStones = types_1.iStoneMap.clone(stones);
var tryTemplates = __spreadArray([], templates);
var tryMoves = __spreadArray([], moves);
if (!opponentTemplateFreeStones.has(escape_1.stone.position) && !tryStones.has(escape_1.stone.position)) {
tryStones.set(escape_1.stone.position, escape_1.stone);
tryTemplates.push.apply(tryTemplates, escape_1.templates);
var otherGroupReached = otherGroups.find(function (groupStones) { return groupStones.has(escape_1.stone.position); });
if (otherGroupReached) {
var result = this_6.recursive(escape_1.stone, edge, player, types_1.iStoneMap.clone(tryStones), __spreadArray([], tryTemplates), otherGroups, tryMoves, opponentTemplateFreeStones, bestTry);
if (result && result.length < (bestTry ? bestTry.length : Infinity)) {
bestTry = result;
}
var stonesToAdd = types_1.iStoneMap.stones(otherGroupReached).filter(function (ogs) { return !tryStones.has(ogs.position); });
stonesToAdd.forEach(function (s) { return tryStones.set(s.position, s); });
for (var _b = 0, stonesToAdd_1 = stonesToAdd; _b < stonesToAdd_1.length; _b++) {
var addedStone = stonesToAdd_1[_b];
var result_1 = this_6.recursive(addedStone, edge, player, types_1.iStoneMap.clone(tryStones), __spreadArray([], tryTemplates), otherGroups, tryMoves, opponentTemplateFreeStones, bestTry);
if (result_1 && result_1.length < (bestTry ? bestTry.length : Infinity)) {
bestTry = result_1;
}
}
}
else {
tryMoves.push(escape_1.stone);
var result = this_6.recursive(escape_1.stone, edge, player, types_1.iStoneMap.clone(tryStones), __spreadArray([], tryTemplates), otherGroups, tryMoves, opponentTemplateFreeStones, bestTry);
if (result && result.length < (bestTry ? bestTry.length : Infinity)) {
bestTry = result;
}
}
}
};
var this_6 = this;
for (var _i = 0, _a = this.getBestEscapes(stone, player); _i < _a.length; _i++) {
var escape_1 = _a[_i];
_loop_7(escape_1);
}
return bestTry;
};
Board.prototype.getBestEscapes = function (stone, player) {
var escapes = [];
for (var _i = 0, _a = stone.getNeighbors(2); _i < _a.length; _i++) {
var potentialBridgeStone = _a[_i];
if (!this.isFreePosition(potentialBridgeStone) && !this.isOccupiedBy(potentialBridgeStone, player)) {
continue;
}
var bridgeTemplate = this.getBridge([stone, potentialBridgeStone], player);
if (bridgeTemplate) {
var escape_2 = { stone: potentialBridgeStone, templates: [bridgeTemplate] };
var edgeTemplate = this.getEdgeTemplate(potentialBridgeStone, player);
if (edgeTemplate) {
escape_2.templates.push(edgeTemplate);
}
escapes.push(escape_2);
}
else {
var bridgeFreeStones = this.getBridgeFreeStones([stone, potentialBridgeStone]);
for (var _b = 0, bridgeFreeStones_1 = bridgeFreeStones; _b < bridgeFreeStones_1.length; _b++) {
var stone_2 = bridgeFreeStones_1[_b];
if (this.isFreePosition(stone_2) || this.isOccupiedBy(stone_2, player)) {
escapes.push({ stone: stone_2, templates: [] });
}
}
}
}
return escapes;
};
Board.prototype.getBridge = function (stones, player) {
var _this = this;
var template = this._bridgeTemplate;
if (stones.length !== (template.stones.length + 1)) {
return null;
}
var stone = stones[0];
var _loop_8 = function (rotation) {
var transform = function (c) { return stone.rotate(c, rotation); };
var freeStatus = template.free.every(function (p) { return _this.isFreePosition(transform(p)); });
var stoneStatus = transform(template.stones[0]).position === stones[1].position;
if (freeStatus && stoneStatus) {
return { value: new template_1.Template(this_7, 'bridge', player, stone, template.stones.map(function (c) { return transform(c); }), template.free.map(function (c) { return transform(c); }), null) };
}
};
var this_7 = this;
for (var _i = 0, _a = [0, 1, 2, 3, 4, 5]; _i < _a.length; _i++) {
var rotation = _a[_i];
var state_1 = _loop_8(rotation);
if (typeof state_1 === "object")
return state_1.value;
}
};
Board.prototype.getBridgeFreeStones = function (stones) {
var template = this._bridgeTemplate;
if (stones.length !== (template.stones.length + 1)) {
return [];
}
var stone = stones[0];
var _loop_9 = function (rotation) {
var transform = function (c) { return stone.rotate(c, rotation); };
var stoneStatus = transform(template.stones[0]).position === stones[1].position;
if (stoneStatus) {
return { value: template.free.map(function (c) { return transform(c); }) };
}
};
for (var _i = 0, _a = [0, 1, 2, 3, 4, 5]; _i < _a.length; _i++) {
var rotation = _a[_i];
var state_2 = _loop_9(rotation);
if (typeof state_2 === "object")
return state_2.value;
}
return [];
};
Board.prototype.getEdgeTemplate = function (stone, player) {
var _this = this;
if (stone.isAdjacentToAnEdge(player)) {
return new template_1.Template(this, 'a1', player, stone, [], [], stone.adjacentEdge(player));
}
var eligibleTemplates = this._templates.filter(function (t) { return t.edges.length > 0 && t.stones.length === 0; });
for (var _i = 0, eligibleTemplates_1 = eligibleTemplates; _i < eligibleTemplates_1.length; _i++) {
var template = eligibleTemplates_1[_i];
var _loop_10 = function (rotation) {
var transform = function (c) { return stone.rotate(c, rotation); };
if (template.edges.every(function (p) { return transform(p).isEdge(player); })
&& template.free.every(function (p) { return _this.isFreePosition(transform(p)); })) {
return { value: new template_1.Template(this_8, template.name, player, stone, template.free.map(function (p) { return transform(p); }), [], transform(template.edges[0]).edge(player)) };
}
};
var this_8 = this;
for (var _a = 0, _b = [0, 1, 2, 3, 4, 5]; _a < _b.length; _a++) {
var rotation = _b[_a];
var state_3 = _loop_10(rotation);
if (typeof state_3 === "object")
return state_3.value;
}
}
return null;
};
Board.prototype.edgesReached = function (player, stones, templates) {
if (templates === void 0) { templates = []; }
var reachedEdges = new Set();
for (var _i = 0, stones_1 = stones; _i < stones_1.length; _i++) {
var stone = stones_1[_i];
if (stone.isAdjacentToAnEdge(player)) {
reachedEdges.add(stone.adjacentEdge(player));
}
for (var _a = 0, templates_3 = templates; _a < templates_3.length; _a++) {
var template = templates_3[_a];
if (template.player === player && template.startingStone.position === stone.position && template.edge) {
reachedEdges.add(template.edge);
}
}
}
return reachedEdges;
};
Board.prototype.ratePlayerSign = function (player) {
return player === types_1.HexGamePlayer.Black ? 1 : -1;
};
Board.prototype.signedRatePlayer = function (rate) {
return rate > 0 ? types_1.HexGamePlayer.Black : types_1.HexGamePlayer.White;
};
return Board;
}());
exports.Board = Board;
//# sourceMappingURL=board.js.map