goban-engine
Version:
This contains the built Go engine that is used by the Goban package. There are no display components in this package, only the logic for playing the game of Go, making it suitable for usage in node.js or other server-side environments.
428 lines (427 loc) • 15.7 kB
TypeScript
import { BoardState, BoardConfig } from "./BoardState";
import { MoveTree, MoveTreeJson } from "./MoveTree";
import { ScoreEstimator } from "./ScoreEstimator";
import { GobanBase, GobanEvents } from "../GobanBase";
import { JGOFTimeControl, JGOFNumericPlayerColor, JGOFMove, JGOFPlayerSummary, JGOFIntersection, JGOFSealingIntersection } from "./formats/JGOF";
import { AdHocPackedMove } from "./formats/AdHocFormat";
import { EventEmitter } from "eventemitter3";
import { GameClock, StallingScoreEstimate } from "./protocol";
export declare const AUTOSCORE_TRIALS = 1000;
export declare const AUTOSCORE_TOLERANCE = 0.1;
export type GobanEnginePhase = "play" | "stone removal" | "finished";
export type GobanEngineRules = "chinese" | "aga" | "japanese" | "korean" | "ing" | "nz";
export type GobanEngineSuperKoAlgorithm = "psk" | "csk" | "ssk" | "noresult" | "ing";
export interface PlayerScore {
total: number;
stones: number;
territory: number;
prisoners: number;
scoring_positions: string;
handicap: number;
komi: number;
}
export interface Score {
white: PlayerScore;
black: PlayerScore;
}
export interface GobanEnginePlayerEntry {
id: number;
username: string;
country?: string;
rank?: number;
/** The accepted stones for the stone removal phase that the player has accepted */
accepted_stones?: string;
/** Whether or not the player has accepted scoring with strict seki mode on or not */
accepted_strict_seki_mode?: boolean;
/** XXX: The server is using these, the client may or may not be, we need to normalize this */
name?: string;
pro?: boolean;
}
export type GobanMovesArray = Array<AdHocPackedMove> | Array<JGOFMove>;
export interface GobanEngineConfig extends BoardConfig {
game_id?: number | string;
review_id?: number;
game_name?: string;
player_id?: number;
tournament_id?: number;
ladder_id?: number;
group_ids?: Array<number>;
initial_player?: PlayerColor;
width?: number;
height?: number;
disable_analysis?: boolean;
handicap_rank_difference?: number;
handicap?: number;
komi?: number;
rules?: GobanEngineRules;
phase?: GobanEnginePhase;
initial_state?: GobanEngineInitialState;
marks?: {
[mark: string]: string;
};
latencies?: {
[player_id: string]: number;
};
player_pool?: {
[id: number]: GobanEnginePlayerEntry;
};
players?: {
black: GobanEnginePlayerEntry;
white: GobanEnginePlayerEntry;
};
rengo?: boolean;
rengo_teams?: {
black: Array<GobanEnginePlayerEntry>;
white: Array<GobanEnginePlayerEntry>;
};
rengo_casual_mode?: boolean;
reviews?: {
[review_id: number]: GobanEnginePlayerEntry;
};
is_game_record?: boolean;
time_control?: JGOFTimeControl;
moves?: GobanMovesArray;
move_tree?: MoveTreeJson;
ranked?: boolean;
original_disable_analysis?: boolean;
original_sgf?: string;
free_handicap_placement?: boolean;
score?: Score;
outcome?: string;
winner?: number | "black" | "white" | "tie";
start_time?: number;
end_time?: number;
game_date?: string;
allow_self_capture?: boolean;
automatic_stone_removal?: boolean;
allow_ko?: boolean;
allow_superko?: boolean;
score_territory?: boolean;
score_territory_in_seki?: boolean;
strict_seki_mode?: boolean;
score_stones?: boolean;
score_passes?: boolean;
score_prisoners?: boolean;
score_handicap?: boolean;
white_must_pass_last?: boolean;
aga_handicap_scoring?: boolean;
opponent_plays_first_after_resume?: boolean;
superko_algorithm?: GobanEngineSuperKoAlgorithm;
stalling_score_estimate?: StallingScoreEstimate;
clock?: GameClock;
/** When loading initial state or moves, by default GobanEngine will try and
* handle bad data by just resorting to 'edit placing' moves. If this is
* true, then those errors are thrown instead.
*/
throw_all_errors?: boolean;
/** Removed stones in stone removal phase
* Passing an array of JGOFMove objects is preferred, the string
* format exists for historical backwards compatibility. It is an
* encoded move string, e.g. "aa" for A19
*/
removed?: string | JGOFMove[];
/** Intersections that need to be sealed before scoring should happen */
needs_sealing?: JGOFSealingIntersection[];
ogs?: {
black_stones: string;
black_territory: string;
black_seki_eyes: string;
black_dead_stones: string;
white_stones: string;
white_territory: string;
white_seki_eyes: string;
white_dead_stones: string;
};
time_per_move?: number;
errors?: Array<{
error: string;
stack: any;
}>;
/** Deprecated, I don't think we need this anymore, but need to be sure */
ogs_import?: boolean;
ladder?: number;
black_player_id?: number;
white_player_id?: number;
}
export interface GobanEngineInitialState {
black?: string;
white?: string;
}
/** Reviews are constructed by a stream of modifications messages,
* this interface describes the format of those modification messages.
* A message can contain any number of the fields listed. */
export interface ReviewMessage {
/** The review ID. This is used when sending from the client to the server,
* but is not sent by the server back to the client (as the id is encoded
* in the message event name) */
"review_id"?: number;
/** timestamp (ms) */
"ts"?: number;
/** from (move number) */
"f"?: number;
/** Moves made */
"m"?: string;
/** official move [reviewing live game] */
"om"?: [number, number, number];
/** official undo [reviewing live game] */
"undo"?: boolean;
/** text note for the current node */
"t"?: string;
/** text append to the current node */
"t+"?: string;
/** Marks made */
"k"?: {
[mark: string]: string;
};
/** pen point */
"pp"?: [number, number];
/** pen color / pen start */
"pen"?: string;
/** Chat message */
"chat"?: {
chat_id: string;
player_id: number;
channel: string;
date: number;
/** Turn number */
from: number;
/** this might just be "string", i'm not entirely sure */
moves: AdHocPackedMove | string;
};
/** Remove's the given chat by id */
"remove-chat"?: string;
/** Clears the pen drawings on the node */
"clearpen"?: boolean;
/** Delete */
"delete"?: number;
/** Sets the owner of the review */
"owner"?: number | {
id: number;
username: string;
};
/** Initial gamedata to review */
"gamedata"?: GobanEngineConfig;
/** Sets the controller of the review */
"controller"?: number | {
id: number;
username: string;
};
/** Updated information about the players, such as name etc. */
"player_update"?: JGOFPlayerSummary;
}
export interface PuzzleConfig extends BoardConfig {
mode?: string;
name?: string;
puzzle_type?: string;
initial_state?: GobanEngineInitialState;
marks?: {
[mark: string]: string;
};
puzzle_autoplace_delay?: number;
puzzle_opponent_move_mode?: PuzzleOpponentMoveMode;
puzzle_player_move_mode?: PuzzlePlayerMoveMode;
puzzle_rank?: number;
puzzle_description?: string;
puzzle_collection?: number;
initial_player?: PlayerColor;
move_tree?: MoveTreeJson;
}
export type PuzzlePlayerMoveMode = "free" | "fixed";
export type PuzzleOpponentMoveMode = "manual" | "automatic";
export type PuzzlePlacementSetting = {
mode: "play";
} | {
mode: "setup";
color: JGOFNumericPlayerColor;
} | {
mode: "place";
color: 0;
};
export type PlayerColor = "black" | "white";
export declare class GobanEngine extends BoardState {
throw_all_errors?: boolean;
handicap_rank_difference?: number;
handicap: number;
initial_state: GobanEngineInitialState;
komi: number;
move_tree: MoveTree;
move_tree_layout_vector: Array<number>;
move_tree_layout_hash: {
[coords: string]: MoveTree;
};
move_tree_layout_dirty: boolean;
readonly name: string;
player_pool: {
[id: number]: GobanEnginePlayerEntry;
};
latencies?: {
[player_id: string]: number;
};
players: {
black: GobanEnginePlayerEntry;
white: GobanEnginePlayerEntry;
};
puzzle_collection: number;
puzzle_description: string;
puzzle_opponent_move_mode: PuzzleOpponentMoveMode;
puzzle_player_move_mode: PuzzlePlayerMoveMode;
puzzle_rank: number;
puzzle_type: string;
readonly config: GobanEngineConfig;
readonly disable_analysis: boolean;
time_control: JGOFTimeControl;
game_id: number;
review_id?: number;
decoded_moves: Array<JGOFMove>;
automatic_stone_removal: boolean;
group_ids?: Array<number>;
rengo?: boolean;
rengo_teams?: {
[colour: string]: Array<GobanEnginePlayerEntry>;
};
rengo_casual_mode: boolean;
stalling_score_estimate?: StallingScoreEstimate;
readonly is_game_record: boolean;
private _phase;
get phase(): GobanEnginePhase;
set phase(phase: GobanEnginePhase);
private _cur_move;
get cur_move(): MoveTree;
set cur_move(cur_move: MoveTree);
private _cur_review_move;
get cur_review_move(): MoveTree | undefined;
set cur_review_move(cur_review_move: MoveTree | undefined);
private _last_official_move;
get last_official_move(): MoveTree;
set last_official_move(last_official_move: MoveTree);
private _strict_seki_mode;
get strict_seki_mode(): boolean;
set strict_seki_mode(strict_seki_mode: boolean);
private _rules;
get rules(): GobanEngineRules;
set rules(rules: GobanEngineRules);
private _winner?;
get winner(): number | "black" | "white" | undefined;
set winner(winner: number | "black" | "white" | undefined);
private _undo_requested?;
get undo_requested(): number | undefined;
set undo_requested(undo_requested: number | undefined);
private _outcome;
get outcome(): string;
set outcome(outcome: string);
private aga_handicap_scoring;
private allow_ko;
private allow_self_capture;
private allow_superko;
private superko_algorithm;
private dontStoreBoardHistory;
free_handicap_placement: boolean;
private loading_sgf;
private move_before_jump?;
needs_sealing?: Array<JGOFSealingIntersection>;
score_prisoners: boolean;
score_stones: boolean;
score_handicap: boolean;
score_territory: boolean;
score_territory_in_seki: boolean;
territory_included_in_sgf: boolean;
constructor(config: GobanEngineConfig, goban_callback?: GobanBase, dontStoreBoardHistory?: boolean);
/**
* Decodes any of the various ways we express moves that we've accumulated over the years into
* a unified `JGOFMove[]`.
*/
decodeMoves(move_obj: string | AdHocPackedMove | AdHocPackedMove[] | JGOFMove | JGOFMove[] | [object] | undefined): JGOFMove[];
encodeMoves(lst: JGOFMove[]): string;
encodeMove(lst: JGOFMove): string;
/**
* Decodes a move string like `"A11"` into a move object like `{x: 0, y: 10}`. Also
* handles the special cases like `".."` and "pass" which map to `{x: -1, y: -1}`.
*/
decodePrettyCoordinates(coordinates: string): JGOFMove;
/** Encodes an x,y pair or a move object like {x: 0, y: 0} into a move string like "A1" */
prettyCoordinates(x: JGOFMove): string;
prettyCoordinates(x: number, y: number): string;
private getState;
private setState;
currentPositionId(): string;
followPath(from_turn: number, moves: AdHocPackedMove | string, cb?: (x: number, y: number, edited: boolean, color: number) => void): Array<MoveTree>;
updatePlayers(player_update: JGOFPlayerSummary): void;
/** Returns true if there was a previous to show */
showPrevious(): boolean;
/** Returns true if there was a next to show */
showNext(): boolean;
/** Returns true if there was a next to show */
showNextTrunk(): boolean;
jumpTo(node?: MoveTree | null): void;
jumpToLastOfficialMove(): void;
/** Saves our current move as our last official move */
setLastOfficialMove(): void;
/** returns true if our current move is our last official move */
isLastOfficialMove(): boolean;
/** Returns a move string from the given official move number (aka branch point) */
getMoveDiff(): {
from: number;
moves: string;
};
setAsCurrentReviewMove(): void;
deleteCurMove(): void;
gameCanBeCancelled(): boolean;
jumpToOfficialMoveNumber(move_number: number): void;
private opponent;
private captureGroup;
isParticipant(player_id: number): boolean;
isActivePlayer(player_id: number): boolean;
playerToMoveOnOfficialBranch(): number;
playerToMove(): number;
playerNotToMove(): number;
otherPlayer(): JGOFNumericPlayerColor;
playerColor(player_id?: number): "black" | "white" | "invalid";
colorToMove(): "black" | "white";
colorNotToMove(): "black" | "white";
playerByColor(color: PlayerColor | JGOFNumericPlayerColor): JGOFNumericPlayerColor;
/** Returns the number of stones removed. If you want the coordinates of
* the stones removed, pass in a removed_stones array to append the moves
* to. */
place(x: number, y: number, checkForKo?: boolean, errorOnSuperKo?: boolean, dontCheckForSuperKo?: boolean, dontCheckForSelfCapture?: boolean, isTrunkMove?: boolean, removed_stones?: Array<JGOFIntersection>): number;
isBoardRepeating(superko_rule: GobanEngineSuperKoAlgorithm): boolean;
editPlace(x: number, y: number, color: JGOFNumericPlayerColor, isTrunkMove?: boolean): void;
initialStatePlace(x: number, y: number, color: JGOFNumericPlayerColor, dont_record_placement?: boolean): void;
resetMoveTree(): void;
computeInitialStateForForkedGame(): {
black: string;
white: string;
};
setNeedsSealing(x: number, y: number, needs_sealing?: boolean): void;
getStoneRemovalString(): string;
getMoveNumber(): number;
getCurrentMoveNumber(): number;
/**
* Computes the score of the current board state.
*
* If only_prisoners is true, we return the same data structure for convenience, but only
* the prisoners will be counted, other sources of points will be zero.
*/
computeScore(only_prisoners?: boolean): Score;
handicapMovesLeft(): number;
/**
* This function migrates old config's to whatever our current standard is
* for configs.
*/
private static migrateConfig;
/**
* This function fills in default values for any missing fields in the
* config.
*/
static fillDefaults(game_obj: GobanEngineConfig): GobanEngineConfig;
static clearRuleSettings(game_obj: GobanEngineConfig): GobanEngineConfig;
private parseSGF;
estimateScore(trials: number, tolerance: number, prefer_remote?: boolean, should_autoscore?: boolean): ScoreEstimator;
getMoveByLocation(x: number, y: number, include_forward_search: boolean): MoveTree | null;
exportAsPuzzle(): PuzzleConfig;
getBlackPrisoners(): number;
getWhitePrisoners(): number;
getHandicapPointAdjustmentForWhite(): number;
parentEventEmitter?: EventEmitter<GobanEvents>;
emit<K extends keyof GobanEvents>(event: K, ...args: EventEmitter.EventArgs<GobanEvents, K>): boolean;
setRemoved(x: number, y: number, removed: boolean, emit_stone_removal_updated?: boolean): void;
}