UNPKG

hex-game

Version:
520 lines 26.2 kB
"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