UNPKG

@algorithm.ts/gomoku

Version:

A algorithm based on minimax search and alpha-beta prune to solve gomoku game.

561 lines (541 loc) 19.8 kB
import { IPriorityQueue } from '@algorithm.ts/queue'; declare enum GomokuDirectionType { LEFT = 0, TOP_LEFT = 2, TOP = 4, TOP_RIGHT = 6, RIGHT = 1, BOTTOM_RIGHT = 3, BOTTOM = 5, BOTTOM_LEFT = 7 } declare const GomokuDirections: ReadonlyArray<[dr: number, dc: number]>; declare const GomokuDirectionTypes: { full: ReadonlyArray<GomokuDirectionType>; leftHalf: ReadonlyArray<GomokuDirectionType>; rightHalf: ReadonlyArray<GomokuDirectionType>; }; declare const GomokuDirectionTypeBitset: { full: number; leftHalf: number; rightHalf: number; }; interface IGomokuPiece { r: number; c: number; p: number; } interface IGomokuCandidateState { posId: number; score: number; } type IShapeCount = [ noSideAvailable: number, oneSideAvailable: number, twoSideAvailable: number ]; type IShapeCountScore = IShapeCount[]; interface IShapeScoreMap { con: IShapeCountScore; gap: IShapeCountScore; } type IGomokuBoard = number[]; interface IDirCounter { playerId: number; count: number; } interface IGomokuMoverStep { /** * Initialize context with given pieces. * @param pieces */ init(pieces: Iterable<IGomokuPiece>): void; /** * Place a piece on the given position. * @param posId * @param playerId */ forward(posId: number, playerId: number): void; /** * Remove the piece from the given position. * @param posId */ revert(posId: number): void; } interface IGomokuMover extends IGomokuMoverStep { /** * The own player. */ readonly rootPlayerId: number; /** * Update rootPlayerId * @param rootPlayerId */ resetRootPlayerId(rootPlayerId: number): void; /** * Get candidates. * @param nextPlayerId * @param candidates * @param candidateGrowthFactor * @param MAX_SIZE */ expand(nextPlayerId: number, candidates: IGomokuCandidateState[], candidateGrowthFactor: number, MAX_SIZE?: number): number; /** * Get top candidate * @param nextPlayerId */ topCandidate(nextPlayerId: number): IGomokuCandidateState | undefined; /** * Evaluate a score for current state. * @param nextPlayerId */ score(nextPlayerId: number): number; /** * Check if the game is already end. */ isFinal(): boolean; /** * Check if next move could reach the final state. * @param nextPlayerId */ couldReachFinal(nextPlayerId: number): boolean; } interface IGomokuMoverContext extends IGomokuMoverStep { /** * Number of rows. */ readonly MAX_ROW: number; /** * Number of columns. */ readonly MAX_COL: number; /** * The maximum number of pieces with the same color allowed to be adjacent in the same direction. */ readonly MAX_ADJACENT: number; /** * The distance to the farthest neighbor to the current position. */ readonly MAX_DISTANCE_OF_NEIGHBOR: number; /** * Total player. */ readonly TOTAL_PLAYER: number; /** * The total number of valid positions on the board. */ readonly TOTAL_POS: number; /** * Gomoku board. */ readonly board: Readonly<IGomokuBoard>; /** * The total number of positions that haven been placed. */ readonly placedCount: number; /** * The coordinate id of the position in the middle of the chessboard. */ readonly MIDDLE_POS: number; /** * Get position id. * @param r Row number of the target position. * @param c Column number of the target position. */ idx(r: number, c: number): number; /** * Get the coordinates (r, c) of the target position. * @param posId */ revIdx(posId: number): Readonly<[r: number, c: number]>; /** * Check if a given position is a legal position * @param r * @param c */ isValidPos(r: number, c: number): boolean; /** * Check if a given position id is represent a legal position. * @param posId */ isValidIdx(posId: number): boolean; /** * Move the specified number of steps in the given direction. * * If the position after the move is out of bounds, return -1. * * @param posId Started position. !!!Should be a valid pos id in bounds. * @param dirType Moving direction. * @param step Number of steps to move. !!! Should be a non-negative integer. */ safeMove(posId: number, dirType: GomokuDirectionType, step: number): number | -1; /** * Move exactly one step in the given direction. * * If the position after the move is out of bounds, return -1. * * @param posId Started position. !!!Should be a valid pos id in bounds. * @param dirType Moving direction. */ safeMoveOneStep(posId: number, dirType: GomokuDirectionType): number | -1; /** * Move the specified number of steps in the given direction. * * !!! The position after the move should be not out of bounds. * * @param posId Started position. !!!Should be a valid pos id in bounds. * @param dirType Moving direction. * @param step Number of steps to move. !!! Should be a non-negative integer. */ fastMove(posId: number, dirType: GomokuDirectionType, step: number): number; /** * Move exactly one step in the given direction. * * !!! The position after the move should be not out of bounds. * * @param posId Started position. !!!Should be a valid pos id in bounds. * @param dirType Moving direction. */ fastMoveOneStep(posId: number, dirType: GomokuDirectionType): number | -1; /** * The maximum number of steps in the given direction can be moved. * @param posId * @param dirType */ maxMovableSteps(posId: number, dirType: GomokuDirectionType): number; /** * All accessible neighbor positions. */ accessibleNeighbors(posId: number): Iterable<number>; /** * * @param posId */ hasPlacedNeighbors(posId: number): boolean; /** * Checks whether it is reached the final situation in the given direction * or its opposite direction if put the piece (by playerId) on the given position. * @param playerId * @param posId * @param dirType */ couldReachFinalInDirection(playerId: number, posId: number, dirType: GomokuDirectionType): boolean; /** * Get the id of first position in the given direction. * @param posId * @param dirType */ getStartPosId(posId: number, dirType: GomokuDirectionType): number; /** * * @param dirType */ getStartPosSet(dirType: GomokuDirectionType): Iterable<number>; /** * */ getDirCounters(startPosId: number, dirType: GomokuDirectionType): ReadonlyArray<IDirCounter>; /** * * @param handle */ traverseAllDirections(handle: (dirType: GomokuDirectionType) => (posId: number) => void): void; } type IGomokuDirections = number[]; type IIdxMap = Array<Readonly<[r: number, c: number]>>; interface IGomokuMoverContextProps { MAX_ROW: number; MAX_COL: number; MAX_ADJACENT: number; MAX_DISTANCE_OF_NEIGHBOR: number; } declare class GomokuMoverContext implements IGomokuMoverContext { readonly MAX_ROW: number; readonly MAX_COL: number; readonly MAX_ADJACENT: number; readonly MAX_DISTANCE_OF_NEIGHBOR: number; readonly TOTAL_PLAYER: number; readonly TOTAL_POS: number; readonly MIDDLE_POS: number; readonly board: Readonly<IGomokuBoard>; protected readonly _idxMap: Readonly<IIdxMap>; protected readonly _gomokuDirections: Readonly<IGomokuDirections>; protected readonly _maxMovableMap: number[][]; protected readonly _dirStartPosMap: number[][]; protected readonly _dirStartPosSet: number[][]; protected readonly _dirNeighborSet: number[][]; protected readonly _neighborPlacedCount: number[]; protected readonly _rightHalfDirCountMap: IDirCounter[][][]; protected _placedCount: number; constructor(props: IGomokuMoverContextProps); get placedCount(): number; init(pieces: Iterable<IGomokuPiece>): void; forward(posId: number, playerId: number): void; revert(posId: number): void; idx(r: number, c: number): number; revIdx(posId: number): Readonly<[r: number, c: number]>; isValidPos(r: number, c: number): boolean; isValidIdx(posId: number): boolean; safeMove(posId: number, dirType: GomokuDirectionType, step: number): number; safeMoveOneStep(posId: number, dirType: GomokuDirectionType): number; fastMove(posId: number, dirType: GomokuDirectionType, step: number): number; fastMoveOneStep(posId: number, dirType: GomokuDirectionType): number; maxMovableSteps(posId: number, dirType: GomokuDirectionType): number; accessibleNeighbors(posId: number): Iterable<number>; hasPlacedNeighbors(posId: number): boolean; couldReachFinalInDirection(playerId: number, posId: number, dirType: GomokuDirectionType): boolean; getStartPosId(posId: number, dirType: GomokuDirectionType): number; getStartPosSet(dirType: GomokuDirectionType): Iterable<number>; getDirCounters(startPosId: number, dirType: GomokuDirectionType): ReadonlyArray<IDirCounter>; traverseAllDirections(handle: (dirType: GomokuDirectionType) => (posId: number) => void): void; protected _updateHalfDirCounter(playerId: number, posId: number, dirType: GomokuDirectionType): void; } interface IGomokuMoverCounter extends IGomokuMoverStep { /** * Win position set. * @param playerId */ mustWinPosSet(playerId: number): Iterable<number> & { size: number; }; /** * * @param playerId * @param posId */ candidateCouldReachFinal(playerId: number, posId: number): boolean; } declare class GomokuMoverCounter implements IGomokuMoverCounter { readonly context: Readonly<IGomokuMoverContext>; protected readonly _mustWinPosSet: Array<Set<number>>; protected readonly _candidateCouldReachFinal: number[][]; constructor(context: Readonly<IGomokuMoverContext>); init(): void; forward(posId: number): void; revert(posId: number): void; mustWinPosSet(playerId: number): Iterable<number> & { size: number; }; candidateCouldReachFinal(playerId: number, posId: number): boolean; protected _updateRelatedCouldReachFinal(centerPosId: number): void; protected _updateCouldReachFinal(posId: number, expiredBitset: number): void; } interface IGomokuMoverState extends IGomokuMoverStep { /** * @param nextPlayerId * @param candidates * @param candidateGrowthFactor * @param MAX_SIZE */ expand(nextPlayerId: number, candidates: IGomokuCandidateState[], candidateGrowthFactor: number, MAX_SIZE?: number): number; /** * Get top candidate * @param nextPlayerId */ topCandidate(nextPlayerId: number): IGomokuCandidateState | undefined; /** * * @param currentPlayer * @param scoreForPlayer */ score(currentPlayer: number, scoreForPlayer: number): number; /** * * @param currentPlayer */ isWin(currentPlayer: number): boolean; /** * */ isDraw(): boolean; /** * */ isFinal(): boolean; } interface IGomokuMoverProps { context: Readonly<IGomokuMoverContext>; counter: Readonly<IGomokuMoverCounter>; state: Readonly<IGomokuMoverState>; } declare class GomokuMover implements IGomokuMover { readonly rootPlayerId: 0 | 1; protected readonly context: Readonly<IGomokuMoverContext>; protected readonly counter: Readonly<IGomokuMoverCounter>; protected readonly state: Readonly<IGomokuMoverState>; constructor(props: IGomokuMoverProps); resetRootPlayerId(rootPlayerId: number): void; init(pieces: Iterable<IGomokuPiece>): void; forward(posId: number, playerId: number): void; revert(posId: number): void; expand(nextPlayerId: number, candidates: IGomokuCandidateState[], candidateGrowthFactor: number, MAX_SIZE?: number): number; topCandidate(nextPlayerId: number): IGomokuCandidateState | undefined; score(nextPlayerId: number): number; isFinal(): boolean; couldReachFinal(nextPlayerId: number): boolean; } interface IGomokuStateProps { context: Readonly<IGomokuMoverContext>; counter: Readonly<IGomokuMoverCounter>; scoreMap: Readonly<IShapeScoreMap>; } declare class GomokuMoverState implements IGomokuMoverState { readonly NEXT_MOVER_BUFFER = 4; readonly context: Readonly<IGomokuMoverContext>; readonly counter: Readonly<IGomokuMoverCounter>; readonly scoreMap: Readonly<IShapeScoreMap>; protected readonly _candidateQueues: Array<IPriorityQueue<IGomokuCandidateState>>; protected readonly _candidateInqSets: Array<Array<Set<number>>>; protected readonly _candidateSet: Set<number>; protected readonly _candidateScores: number[][]; protected readonly _candidateScoreDirMap: number[][][]; protected readonly _candidateScoreExpired: number[]; protected readonly _stateScores: number[]; protected readonly _stateScoreDirMap: number[][][]; protected readonly _countOfReachFinal: number[]; protected readonly _countOfReachFinalDirMap: number[][][]; constructor(props: IGomokuStateProps); init(pieces: Iterable<IGomokuPiece>): void; forward(posId: number): void; revert(posId: number): void; expand(nextPlayerId: number, candidates: IGomokuCandidateState[], candidateGrowthFactor: number, MAX_SIZE?: number): number; topCandidate(nextPlayerId: number): IGomokuCandidateState | undefined; score(currentPlayer: number, scoreForPlayer: number): number; isWin(currentPlayer: number): boolean; isDraw(): boolean; isFinal(): boolean; protected _updateStateScore(posId: number): void; protected _expireCandidates(centerPosId: number): void; protected _reEvaluateAndEnqueueCandidate(posId: number): void; protected _reEvaluateCandidate(posId: number): void; protected _evaluateScoreInDirection(playerId: number, startPosId: number, dirType: GomokuDirectionType): { score: number; countOfReachFinal: number; }; protected _temporaryForward(posId: number, playerId: number): void; protected _temporaryRevert(posId: number): void; } interface IGomokuSearcher { /** * * @param curPlayerId Identifier of next mover. * @param alpha Minimum expected score * @param beta Maximum expected score * @param cur Current search depth */ search(curPlayerId: number, alpha: number, beta: number, cur: number): number | -1; } interface IGomokuSolutionProps { MAX_ROW: number; MAX_COL: number; MAX_ADJACENT?: number; MAX_DISTANCE_OF_NEIGHBOR?: number; CANDIDATE_GROWTH_FACTOR?: number; scoreMap?: IShapeScoreMap; deeperSearcher?(mover: IGomokuMover): IGomokuSearcher; } declare class GomokuSolution { readonly CANDIDATE_GROWTH_FACTOR: number; readonly scoreMap: Readonly<IShapeScoreMap>; readonly mover: Readonly<IGomokuMover>; protected readonly _moverContext: Readonly<IGomokuMoverContext>; protected readonly _searcher: IGomokuSearcher; constructor(props: IGomokuSolutionProps); init(pieces: Iterable<IGomokuPiece>, searcher?: IGomokuSearcher): void; forward(r: number, c: number, playerId: number): void; revert(r: number, c: number): void; minimaxSearch(nextPlayerId: number): [r: number, c: number]; } interface IAlphaBetaSearcherProps { MAX_CANDIDATE_COUNT: number; MIN_PROMOTION_SCORE: number; CANDIDATE_GROWTH_FACTOR: number; mover: IGomokuMover; deeperSearcher: IGomokuSearcher; } declare class AlphaBetaSearcher implements IGomokuSearcher { readonly MAX_CANDIDATE_COUNT: number; readonly MIN_PROMOTION_SCORE: number; readonly CANDIDATE_GROWTH_FACTOR: number; readonly searchContext: Readonly<IGomokuMover>; readonly deeperSearcher: IGomokuSearcher; protected readonly _candidateCache: IGomokuCandidateState[]; constructor(props: IAlphaBetaSearcherProps); search(rootPlayerId: number, alpha: number, beta: number): number | -1; } interface IDeepSearcherProps { MAX_SEARCH_DEPTH: number; MIN_PROMOTION_SCORE: number; mover: IGomokuMover; } declare class DeepSearcher implements IGomokuSearcher { readonly MAX_SEARCH_DEPTH: number; readonly MIN_PROMOTION_SCORE: number; readonly mover: Readonly<IGomokuMover>; constructor(props: IDeepSearcherProps); search(curPlayerId: number, alpha: number, beta: number, cur: number): number; } interface INarrowSearcherProps { MAX_SEARCH_DEPTH: number; MAX_CANDIDATE_COUNT: number; MIN_PROMOTION_SCORE: number; CANDIDATE_GROWTH_FACTOR: number; mover: IGomokuMover; deeperSearcher: IGomokuSearcher; } declare class NarrowSearcher implements IGomokuSearcher { readonly MAX_SEARCH_DEPTH: number; readonly MAX_CANDIDATE_COUNT: number; readonly MIN_PROMOTION_SCORE: number; readonly CANDIDATE_GROWTH_FACTOR: number; readonly mover: Readonly<IGomokuMover>; readonly deeperSearcher: IGomokuSearcher; protected readonly _candidatesListCache: Record<number, IGomokuCandidateState[]>; constructor(props: INarrowSearcherProps); search(curPlayerId: number, alpha: number, beta: number, cur: number): number; protected _getCandidates(cur: number): IGomokuCandidateState[]; } type INarrowSearchOption = Omit<INarrowSearcherProps, 'mover' | 'deeperSearcher'>; type IDeepSearcherOption = Omit<IDeepSearcherProps, 'mover'>; interface IProps { narrowSearcherOptions: INarrowSearchOption[]; deepSearcherOption: IDeepSearcherOption; searchContext: IGomokuMover; } declare const createGomokuSearcher: (props: IProps) => IGomokuSearcher; declare const createDefaultGomokuSearcher: (scoreMap: Readonly<IShapeScoreMap>, searchContext: IGomokuMover, options: { MAX_ADJACENT: number; CANDIDATE_GROWTH_FACTOR: number; }) => IGomokuSearcher; /** * Create high dimension array, eg: createHighDimensionArray(-1, 3, 4, 2) => * * [ * [ * [-1, -1], * [-1, -1], * [-1, -1], * [-1, -1], * ], * [ * [-1, -1], * [-1, -1], * [-1, -1], * [-1, -1], * ], * [ * [-1, -1], * [-1, -1], * [-1, -1], * [-1, -1], * ] * ] * * @param elementProvider * @param firstDimension * @param dimensions * @returns */ declare const createHighDimensionArray: <T>(elementProvider: (index: number) => T, firstDimension: number, ...dimensions: number[]) => any[]; declare const createScoreMap: (MAX_ADJACENT: number) => IShapeScoreMap; export { AlphaBetaSearcher, DeepSearcher, GomokuDirectionType, GomokuDirectionTypeBitset, GomokuDirectionTypes, GomokuDirections, GomokuMover, GomokuMoverContext, GomokuMoverCounter, GomokuMoverState, GomokuSolution, type IAlphaBetaSearcherProps, type IDeepSearcherOption, type IDeepSearcherProps, type IDirCounter, type IGomokuBoard, type IGomokuCandidateState, type IGomokuMover, type IGomokuMoverContext, type IGomokuMoverContextProps, type IGomokuMoverCounter, type IGomokuMoverProps, type IGomokuMoverState, type IGomokuMoverStep, type IGomokuPiece, type IGomokuSearcher, type IGomokuSolutionProps, type IGomokuStateProps, type INarrowSearchOption, type INarrowSearcherProps, type IShapeCount, type IShapeCountScore, type IShapeScoreMap, NarrowSearcher, createDefaultGomokuSearcher, createGomokuSearcher, createHighDimensionArray, createScoreMap };