Skip to content

Commit

Permalink
Alice (#1536)
Browse files Browse the repository at this point in the history
* AliceBoard.py

* More Alice work

* Update dependencies

* Typing

* getLegalAliceMoves()

* Fix get_san()

* Fix current board

* Fix game_result()

* First unit tests

* Implement castling moves

* Format with black

* Cleanup

* En passant excluded

* Remove unneeded code

* Typing fixes

* Implement client side AliceBoard

* Fix Alice startFen

* Remove debug log lines

* Use one board only to play Alice

* Fix typings

* Remove deprecated comment

* Simplify the code

* Skip LOOKING_GLASS_ALICE_FEN validation

* Black

* Fix check mark handling

* Fix Alice PGN

* Fix no dests after goPly bug

* Alice boards on profile page

* Disable Editor for Alice chess

* Use boardMark: 'alice' instead of showPromoted: true

* Basic analysis board support

* Typing fixes
  • Loading branch information
gbtami committed Jul 27, 2024
1 parent 52ca0a8 commit 02f4167
Show file tree
Hide file tree
Showing 24 changed files with 930 additions and 32 deletions.
184 changes: 184 additions & 0 deletions client/alice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import * as cg from 'chessgroundx/types';

import { Board } from 'ffish-es6';

import { Position, castlingSide } from 'chessops/chess';
import { makeBoardFen, makeFen, parseFen } from 'chessops/fen';
import { makeSquare, opposite, parseUci, rookCastlesTo } from 'chessops/util';
import { Setup } from 'chessops/setup';
import { NormalMove, SquareName } from 'chessops/types';

export type Fens = [string, string];
export type BoardId = 0 | 1;
type Boards = [AlicePosition, AlicePosition];

class AlicePosition extends Position {
private constructor() {
super('chess');
}

static fromSetup(setup: Setup): AlicePosition {
const pos = new this();
pos.setupUnchecked(setup);
return pos;
}
}

export class AliceBoard {
fens: Fens;
boards: Boards;
turnColor: cg.Color;
check: boolean;
ffishBoard: Board;

constructor(fullfen: string, ffishBoard: Board) {
this.ffishBoard = ffishBoard;
this.fens = fullfen.split(' | ') as Fens;
this.turnColor = this.fens[0].split(' ')[1] === "w" ? "white" : "black";

const setup0 = parseFen(this.fens[0]).unwrap();
const setup1 = parseFen(this.fens[1]).unwrap();

const pos0 = AlicePosition.fromSetup(setup0);
const pos1 = AlicePosition.fromSetup(setup1);

this.boards = [pos0, pos1];
// console.log("new AliceBoard", this.boards);

this.ffishBoard.setFen(this.fens[0]);
const check0 = this.ffishBoard.isCheck();

this.ffishBoard.setFen(this.fens[1]);
const check1 = this.ffishBoard.isCheck();

this.check = check0 || check1;
}

getFen(uci: string): string {
const move = parseUci(uci) as NormalMove;
const boardId = (this.boards[0].board.get((parseUci(uci)! as NormalMove).from) === undefined) ? 1 : 0;
const afterBoards: Boards = [this.boards[0].clone(), this.boards[1].clone()];
this.playMove(afterBoards, boardId, move!);
afterBoards[1 - boardId].turn = opposite(afterBoards[1 - boardId].turn);

return makeFen(afterBoards[0].toSetup()) + ' | ' + makeFen(afterBoards[1].toSetup());
}

playMove(afterBoards: Boards, boardId: BoardId, move: NormalMove): void {
const castlSide = castlingSide(afterBoards[boardId], move);

afterBoards[boardId].play(move);

// Remove piece from the target square and put it to the other board
const piece = afterBoards[boardId].board.take(move.to);
afterBoards[1 - boardId].board.set(move.to, piece!);

// Remove the castled rook and put it to the another board
if (castlSide !== undefined) {
const rook_to_square = rookCastlesTo(this.turnColor, castlSide);
const rook = afterBoards[boardId].board.take(rook_to_square);
afterBoards[1 - boardId].board.set(rook_to_square, rook!);
}
}

getLegalAliceMoves(): string[] {
let pseudo_legal_moves_0: string[], pseudo_legal_moves_1: string[];

this.ffishBoard.setFen(this.fens[0]);
const moves_0 = this.ffishBoard.legalMoves();
if (moves_0.length > 0) {
pseudo_legal_moves_0 = moves_0.split(' ').filter(uci => this.boards[1].board.get(parseUci(uci)!.to) === undefined);
} else {
pseudo_legal_moves_0 = [];
}

this.ffishBoard.setFen(this.fens[1]);
const moves_1 = this.ffishBoard.legalMoves();
if (moves_1.length > 0) {
pseudo_legal_moves_1 = moves_1.split(' ').filter(uci => this.boards[0].board.get(parseUci(uci)!.to) === undefined);
} else {
pseudo_legal_moves_1 = [];
}

return this.filterOutIllegalMoves(0, pseudo_legal_moves_0).concat(this.filterOutIllegalMoves(1, pseudo_legal_moves_1));
}

filterOutIllegalMoves(boardId: BoardId, uciMoves: string[]): string[] {
const legals: string[] = [];
for (const uci of uciMoves) {

const move = parseUci(uci) as NormalMove;
const castlSide = castlingSide(this.boards[boardId], move!);

const afterBoards: Boards = [this.boards[0].clone(), this.boards[1].clone()];
this.playMove(afterBoards, boardId, move!);

this.ffishBoard.setFen(makeBoardFen(afterBoards[0].board) + ' ' + this.turnColor[0]);
if (this.ffishBoard.isCheck()) continue;

this.ffishBoard.setFen(makeBoardFen(afterBoards[1].board) + ' ' + this.turnColor[0]);
if (this.ffishBoard.isCheck()) continue;

// We have to check that rook_to_square was vacant as well
if (castlSide !== undefined) {
const rook_to_square = rookCastlesTo(this.turnColor, castlSide);
if (this.boards[1 - boardId].board.get(rook_to_square) !== undefined) continue;
}

legals.push(uci);
}
return legals;
}

getOccupiedSquares(boardId: BoardId): SquareName[] {
const squareNames: SquareName[] = [];
//console.log("occ AliceBoard", boardId, this.boards);
const occ = this.boards[boardId].board.occupied;
for (const square of occ) {
squareNames.push(makeSquare(square));
}
return squareNames;
}

getUnionFen(boardId: BoardId): string {
// Make all the other board pieces promoted to let us use variant ui.showPromoted
// to present the other board piece images differently
const newBoard = this.boards[boardId].board.clone();
newBoard.occupied = newBoard.occupied.union(this.boards[1 - boardId].board.occupied);
newBoard.promoted = this.boards[1 - boardId].board.occupied;
newBoard.white = newBoard.white.union(this.boards[1 - boardId].board.white);
newBoard.black = newBoard.black.union(this.boards[1 - boardId].board.black);
newBoard.pawn = newBoard.pawn.union(this.boards[1 - boardId].board.pawn);
newBoard.knight = newBoard.knight.union(this.boards[1 - boardId].board.knight);
newBoard.bishop = newBoard.bishop.union(this.boards[1 - boardId].board.bishop);
newBoard.rook = newBoard.rook.union(this.boards[1 - boardId].board.rook);
newBoard.queen = newBoard.queen.union(this.boards[1 - boardId].board.queen);
newBoard.king = newBoard.king.union(this.boards[1 - boardId].board.king);
return makeBoardFen(newBoard);
}
}

export function getUnionFenFromFullFen(fullfen: string, boardId: BoardId): string {
const fens = fullfen.split(' | ') as Fens;

const setup0 = parseFen(fens[0]).unwrap();
const setup1 = parseFen(fens[1]).unwrap();

const pos0 = AlicePosition.fromSetup(setup0);
const pos1 = AlicePosition.fromSetup(setup1);
const boards = [pos0, pos1];
// Make all the other board pieces promoted to let us use variant ui.showPromoted
// to present the other board piece images differently
const newBoard = boards[boardId].board.clone();
newBoard.occupied = newBoard.occupied.union(boards[1 - boardId].board.occupied);
newBoard.promoted = boards[1 - boardId].board.occupied;
newBoard.white = newBoard.white.union(boards[1 - boardId].board.white);
newBoard.black = newBoard.black.union(boards[1 - boardId].board.black);
newBoard.pawn = newBoard.pawn.union(boards[1 - boardId].board.pawn);
newBoard.knight = newBoard.knight.union(boards[1 - boardId].board.knight);
newBoard.bishop = newBoard.bishop.union(boards[1 - boardId].board.bishop);
newBoard.rook = newBoard.rook.union(boards[1 - boardId].board.rook);
newBoard.queen = newBoard.queen.union(boards[1 - boardId].board.queen);
newBoard.king = newBoard.king.union(boards[1 - boardId].board.king);
return makeBoardFen(newBoard);
}
8 changes: 7 additions & 1 deletion client/analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { h, VNode } from "snabbdom";

import { _ } from './i18n';
import { AnalysisController } from './analysisCtrl';
import { AnalysisControllerAlice } from './analysisCtrlAlice';
import { gameInfo } from './gameInfo';
import { selectVariant, VARIANTS } from './variants';
import { renderTimeago } from './datetime';
Expand All @@ -11,7 +12,12 @@ import { analysisSettings } from './analysisSettings';

function runGround(vnode: VNode, model: PyChessModel) {
const el = vnode.elm as HTMLElement;
const ctrl = new AnalysisController(el, model);
let ctrl;
if (model.variant === 'alice') {
ctrl = new AnalysisControllerAlice(el, model);
} else {
ctrl = new AnalysisController(el, model);
}
window['onFSFline'] = ctrl.onFSFline;
}

Expand Down
12 changes: 7 additions & 5 deletions client/analysisCtrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ export class AnalysisController extends GameController {
analysisChart(this);
}

private checkStatus = (msg: MsgBoard | MsgAnalysisBoard) => {
checkStatus(msg: MsgBoard | MsgAnalysisBoard) {
if ((msg.gameId !== this.gameId && !this.isAnalysisBoard) || this.embed) return;
if (("status" in msg && msg.status >= 0) || this.isAnalysisBoard) {

Expand Down Expand Up @@ -351,7 +351,7 @@ export class AnalysisController extends GameController {
}
}

private onMsgBoard = (msg: MsgBoard) => {
onMsgBoard(msg: MsgBoard) {
if (msg.gameId !== this.gameId) return;

this.importedBy = msg.by;
Expand Down Expand Up @@ -440,6 +440,8 @@ export class AnalysisController extends GameController {
onFSFline = (line: string) => {
if (this.fsfDebug) console.debug('--->', line);

if (this.variant.name === 'alice') return;

if (line.startsWith('info')) {
const error = 'info string ERROR: ';
if (line.startsWith(error)) {
Expand Down Expand Up @@ -733,7 +735,7 @@ export class AnalysisController extends GameController {

// When we are moving inside a variation move list
// then plyVari > 0 and ply is the index inside vari movelist
goPly = (ply: number, plyVari = 0) => {
goPly(ply: number, plyVari = 0) {
super.goPly(ply, plyVari);

if (this.plyVari > 0) {
Expand Down Expand Up @@ -821,7 +823,7 @@ export class AnalysisController extends GameController {
}
}

private getPgn = (idxInVari = 0) => {
getPgn(idxInVari = 0) {
const moves : string[] = [];
let moveCounter: string = '';
let whiteMove: boolean = true;
Expand Down Expand Up @@ -987,7 +989,7 @@ export class AnalysisController extends GameController {
// this.doSend({ type: "analysis_move", gameId: this.gameId, move: move, fen: this.fullfen, ply: this.ply + 1 });
}

private onMsgAnalysisBoard = (msg: MsgAnalysisBoard) => {
onMsgAnalysisBoard(msg: MsgAnalysisBoard) {
// console.log("got analysis_board msg:", msg);
if (msg.gameId !== this.gameId) return;
if (this.localAnalysis) this.engineStop();
Expand Down
Loading

0 comments on commit 02f4167

Please sign in to comment.