Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Completely move fog FEN creation to server side #1742

Merged
merged 7 commits into from
Jan 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions client/cgCtrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { FairyStockfish, Board, Notation } from 'ffish-es6';
import { boardSettings, BoardController } from '@/boardSettings';
import { CGMove, uci2cg } from '@/chess';
import { BoardName, PyChessModel } from '@/types';
import { Variant, VARIANTS, moddedVariant } from '@/variants';
import { fogFen, Variant, VARIANTS, moddedVariant } from '@/variants';

export abstract class ChessgroundController implements BoardController {
boardName: BoardName;
Expand All @@ -27,6 +27,7 @@ export abstract class ChessgroundController implements BoardController {

fullfen: string;
notation: cg.Notation;
fog: boolean;

constructor(el: HTMLElement, model: PyChessModel, fullfen: string, pocket0: HTMLElement, pocket1: HTMLElement, boardName: BoardName = '') {
this.boardName = boardName;
Expand All @@ -40,9 +41,10 @@ export abstract class ChessgroundController implements BoardController {
this.oppcolor = 'black';
this.fullfen = fullfen;
this.notation = this.variant.notation;
this.fog = model.variant === 'fogofwar';

const parts = this.fullfen.split(" ");
const fen_placement: cg.FEN = parts[0];
const fen_placement: cg.FEN = (this.fog) ? fogFen(parts[0]) : parts[0];

this.chessground = Chessground(el, {
fen: fen_placement as cg.FEN,
Expand Down
1 change: 0 additions & 1 deletion client/chess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { Variant, variantGroups } from './variants';

export const WHITE = 0;
export const BLACK = 1;
export const DARK_FEN = "*~*~*~*~*~*~*~*~/*~*~*~*~*~*~*~*~/*~*~*~*~*~*~*~*~/*~*~*~*~*~*~*~*~/*~*~*~*~*~*~*~*~/*~*~*~*~*~*~*~*~/*~*~*~*~*~*~*~*~/*~*~*~*~*~*~*~*~ w KQkq - 0 1"

export const ranksUCI = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'] as const;
export type UCIRank = typeof ranksUCI[number];
Expand Down
43 changes: 3 additions & 40 deletions client/gameCtrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import { WebsocketHeartbeatJs } from './socket/socket';

import { h, VNode } from 'snabbdom';
import * as Mousetrap from 'mousetrap';
import * as fen from 'chessgroundx/fen';
import * as cg from 'chessgroundx/types';
import * as util from 'chessgroundx/util';

import { _ } from './i18n';
import { patch } from './document';
import { Step, MsgChat, MsgFullChat, MsgSpectators, MsgShutdown,MsgGameNotFound } from './messages';
import { adjacent, DARK_FEN, uci2LastMove, moveDests, cg2uci, uci2cg, unpromotedRole, UCIMove } from './chess';
import { adjacent, uci2LastMove, moveDests, cg2uci, unpromotedRole, UCIMove } from './chess';
import { InputType } from '@/input/input';
import { GatingInput } from './input/gating';
import { PromotionInput } from './input/promotion';
Expand All @@ -21,7 +20,7 @@ import { sound } from './sound';
import { chatMessage, ChatController } from './chat';
import { selectMove } from './movelist';
import { Api } from "chessgroundx/api";
import { Variant } from "@/variants";
import { fogFen, Variant } from "./variants";
import { CheckCounterSvg, Counter } from './glyphs';

export abstract class GameController extends ChessgroundController implements ChatController {
Expand All @@ -37,7 +36,6 @@ export abstract class GameController extends ChessgroundController implements Ch
aiLevel: number;
rated: string;
corr : boolean;
fog: boolean;

base: number;
inc: number;
Expand Down Expand Up @@ -116,7 +114,6 @@ export abstract class GameController extends ChessgroundController implements Ch
this.brating = model["brating"];
this.rated = model["rated"];
this.corr = model["corr"] === 'True';
this.fog = this.variant.name === 'fogofwar';
this.mirrorBoard = false;

this.spectator = this.username !== this.wplayer && this.username !== this.bplayer;
Expand Down Expand Up @@ -221,40 +218,6 @@ export abstract class GameController extends ChessgroundController implements Ch
}
}

fogFen(currentFen: string): string {
// No king, no fog (game is over)
if (!currentFen.includes('k') || !currentFen.includes('K') || this.result !== '*') return currentFen;

if (this.spectator) return DARK_FEN;

// Squares visibility is always calculated from my color turn perspective
const parts = currentFen.split(' ');
this.ffishBoard.setFen([parts[0], this.mycolor[0], parts[2], parts[3]].join(' '));
const legalMoves = this.ffishBoard.legalMoves().split(" ");

const pieces = fen.read(currentFen, this.variant.board.dimensions).pieces;
const myPieceKeys = Array.from(pieces.keys()).filter((key) => pieces.get(key)!.color === this.mycolor);
const visibleKeys = new Set(myPieceKeys);

// Add dest squares to visibleKeys
legalMoves.map(uci2cg).forEach(move => {
visibleKeys.add(move.slice(2, 4) as cg.Key);
});

// We use promoted block pieces as fog to let them style differently in extension.css
const fog = {
color: this.oppcolor,
role: '_-piece' as cg.Role,
promoted: true
}
const darks: cg.Key[] = util.allKeys(this.variant.board.dimensions).filter((key) => !(visibleKeys.has(key)));
const darkPieces: [cg.Key, cg.Piece][] = darks.map((key) => [key, fog]);
const visiblePieces: [cg.Key, cg.Piece][] = Array.from(visibleKeys).filter((key) => pieces.get(key)).map((key) => [key, pieces.get(key)!]);
const newPieces: cg.Pieces = new Map(darkPieces.concat(visiblePieces));

return fen.writeBoard(newPieces, this.variant.board.dimensions);
}

abstract toggleSettings(): void;

abstract doSendMove(move: string): void;
Expand Down Expand Up @@ -355,7 +318,7 @@ export abstract class GameController extends ChessgroundController implements Ch

const fen = (this.mirrorBoard) ? this.getAliceFen(step.fen) : step.fen;
this.chessground.set({
fen: (this.fog) ? this.fogFen(fen) : fen,
fen: (this.fog) ? fogFen(fen) : fen,
turnColor: step.turnColor,
movable: {
color: step.turnColor,
Expand Down
4 changes: 2 additions & 2 deletions client/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type Games = Map<string, GameData>;
function gameView(games: Games, game: Game) {
const variant = VARIANTS[game.variant];
let lastMove, fen;
[lastMove, fen] = getLastMoveFen(variant.name, game.lastMove, game.fen, '*')
[lastMove, fen] = getLastMoveFen(variant.name, game.lastMove, game.fen)
return h(`minigame#${game.gameId}.${variant.boardFamily}.${variant.pieceFamily}.${variant.ui.boardMark}`, {
class: {
"with-pockets": !!variant.pocket,
Expand Down Expand Up @@ -97,7 +97,7 @@ export function renderGames(model: PyChessModel): VNode[] {
let cg, variantName;
[cg, variantName] = gameData;
let lastMove, fen;
[lastMove, fen] = getLastMoveFen(variantName, message.lastMove, message.fen, '*')
[lastMove, fen] = getLastMoveFen(variantName, message.lastMove, message.fen)
cg.set({
fen: fen,
lastMove: lastMove,
Expand Down
4 changes: 2 additions & 2 deletions client/nowPlaying.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function handleOngoingGameEvents(username: string, cgMap: {[gameId: strin
[cg, variantName] = cgMap[message.gameId];

let lastMove, fen;
[lastMove, fen] = getLastMoveFen(variantName, message.lastMove, message.fen, '*')
[lastMove, fen] = getLastMoveFen(variantName, message.lastMove, message.fen)

cg.set({
fen: fen,
Expand Down Expand Up @@ -95,7 +95,7 @@ export function gameViewPlaying(cgMap: {[gameId: string]: [Api, string]}, game:
const mycolor = (username === game.w) ? 'white' : 'black';

let lastMove, fen;
[lastMove, fen] = getLastMoveFen(variant.name, game.lastMove, game.fen, '*')
[lastMove, fen] = getLastMoveFen(variant.name, game.lastMove, game.fen)

return h(`a.${variant.boardFamily}.${variant.pieceFamily}.${variant.ui.boardMark}`, { attrs: { href: game.gameId } }, [
h(`div.cg-wrap.${variant.board.cg}`, {
Expand Down
2 changes: 1 addition & 1 deletion client/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ function renderGames(model: PyChessModel, games: Game[]) {
teamSecond = game["us"][2] + "+" + game["us"][1];
}
let lastMove, fen;
[lastMove, fen] = getLastMoveFen(variant.name, game.lm, game.f, game.r)
[lastMove, fen] = getLastMoveFen(variant.name, game.lm, game.f)
return h('tr', [h('a', { attrs: { href : '/' + game["_id"] } }, [
h('td.board', { class: { "with-pockets": !!variant.pocket, "bug": isBug} },
isBug? renderGameBoardsBug(game, model["profileid"]): [
Expand Down
11 changes: 6 additions & 5 deletions client/roundCtrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { patch } from './document';
import { boardSettings } from './boardSettings';
import { Clock } from './clock';
import { sound } from './sound';
import { DARK_FEN, WHITE, BLACK, uci2LastMove, getCounting, isHandicap } from './chess';
import { fogFen } from "./variants";
import { WHITE, BLACK, uci2LastMove, getCounting, isHandicap } from './chess';
import { crosstableView } from './crosstable';
import { chatMessage, chatView } from './chat';
import { createMovelistButtons, updateMovelist, updateResult, selectMove } from './movelist';
Expand Down Expand Up @@ -884,7 +885,7 @@ export class RoundController extends GameController {
if (this.spectator) {
if (latestPly) {
this.chessground.set({
fen: (this.fog) ? DARK_FEN : this.fullfen,
fen: (this.fog) ? fogFen(this.fullfen) : this.fullfen,
turnColor: this.turnColor,
check: msg.check,
lastMove: (this.fog) ? undefined : lastMove,
Expand All @@ -906,7 +907,7 @@ export class RoundController extends GameController {
if (this.turnColor === this.mycolor) {
if (latestPly) {
this.chessground.set({
fen: (this.fog) ? this.fogFen(this.fullfen) : this.fullfen,
fen: (this.fog) ? fogFen(this.fullfen) : this.fullfen,
turnColor: this.turnColor,
movable: {
free: false,
Expand Down Expand Up @@ -941,10 +942,10 @@ export class RoundController extends GameController {
} else {
this.chessground.set({
// giving fen here will place castling rooks to their destination in chess960 variants
fen: (this.fog) ? this.fogFen(this.fullfen) : parts[0],
fen: (this.fog) ? fogFen(this.fullfen) : parts[0],
turnColor: this.turnColor,
check: msg.check,
lastMove: (this.fog) ? undefined : lastMove,
lastMove: lastMove,
});

// This have to be here, because in case of takeback
Expand Down
4 changes: 2 additions & 2 deletions client/tournament.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ export class TournamentController implements ChatController {
hook: {
insert: vnode => {
let lastMove, fen;
[lastMove, fen] = getLastMoveFen(this.variant.name, game.lastMove, game.fen, '*')
[lastMove, fen] = getLastMoveFen(this.variant.name, game.lastMove, game.fen)
const cg = Chessground(vnode.elm as HTMLElement, {
fen: fen,
lastMove: lastMove,
Expand Down Expand Up @@ -620,7 +620,7 @@ export class TournamentController implements ChatController {
return;
};
let lastMove, fen;
[lastMove, fen] = getLastMoveFen(this.variant.name, msg.lastMove, msg.fen, msg.result)
[lastMove, fen] = getLastMoveFen(this.variant.name, msg.lastMove, msg.fen)

this.topGameChessground.set({
fen: fen,
Expand Down
17 changes: 8 additions & 9 deletions client/variants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { h, InsertHook, VNode } from 'snabbdom';
import * as cg from 'chessgroundx/types';
import * as util from 'chessgroundx/util';

import { DARK_FEN, BoardMarkType, ColorName, CountingType, MaterialPointType, PieceSoundType, PromotionSuffix, PromotionType, TimeControlType, uci2LastMove } from './chess';
import { BoardMarkType, ColorName, CountingType, MaterialPointType, PieceSoundType, PromotionSuffix, PromotionType, TimeControlType, uci2LastMove } from './chess';
import { _ } from './i18n';
import { calculateDiff, Equivalence, MaterialDiff } from './material';

Expand Down Expand Up @@ -1165,12 +1165,11 @@ export function moddedVariant(variantName: string, chess960: boolean, pieces: cg
return variantName;
}

export function getLastMoveFen(variantName: string, lastMove: string, fen: string, result: string): [cg.Orig[] | undefined, string] {
switch (variantName) {
case 'fogofwar':
// Prevent leaking ongoing game info
return [undefined, (result === "*") ? DARK_FEN : fen];
default:
return [uci2LastMove(lastMove), fen];
}
export function getLastMoveFen(variantName: string, lastMove: string, fen: string): [cg.Orig[] | undefined, string] {
return [uci2LastMove(lastMove), variantName === 'fogofwar' ? fogFen(fen) : fen];
}

// Replace all brick ("*") pieces to be promoted ("*~") to let them CSS style as fog instead of duck
export function fogFen(currentFen: string): string {
return currentFen.replace(/\*/g, '*~');
}
5 changes: 3 additions & 2 deletions client/variantsIni.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,5 +286,6 @@ immobilityIllegal = true
king = -
commoner = k
castlingKingPiece = k
extinctionValue = loss
extinctionPieceTypes = k`
# extinction rules prevents to get valid moves for fog FENs ceated on server side
#extinctionValue = loss
#extinctionPieceTypes = k`
2 changes: 1 addition & 1 deletion server/bug/game_bug.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ async def game_ended(self, user, reason):
),
}

def get_board(self, full=False):
def get_board(self, full=False, persp_color=None):
[clocks_a, clocks_b] = self.gameClocks.get_clocks_for_board_msg(full)
if full:
steps = self.steps
Expand Down
1 change: 1 addition & 0 deletions server/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
LOOKING_GLASS_ALICE_FEN = "|r|n|b|q|k|b|n|r/|p|p|p|p|p|p|p|p/8/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1"
MANCHU_FEN = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/9/9/M1BAKAB2 w - - 0 1"
MANCHU_R_FEN = "m1bakab1r/9/9/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1"
DARK_FEN = "********/********/********/********/********/********/********/******** w - - 0 1"

VARIANTS = (
"chess",
Expand Down
29 changes: 29 additions & 0 deletions server/fairy.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import logging
import re
import random
from functools import cache

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -412,6 +413,34 @@ def shuffle_start(variant):
return fen


@cache
def get_fog_fen(fen, persp_color):
parts = fen.split(" ")

fen_color = "w" if parts[1] == "w" else "b"
opp_color = "w" if persp_color == WHITE else "b"

# set the perspective color to sf.get_fog_fen()
parts[1] = parts[1].replace(fen_color, opp_color)

# remove castling rights of the player in fog
# because the resulting fog FEN may have no king
if persp_color == WHITE:
parts[2] = "".join((letter for letter in parts[2] if letter.isupper()))
else:
parts[2] = "".join((letter for letter in parts[2] if letter.islower()))
fen = " ".join(parts)

fen = sf.get_fog_fen(fen, "fogofwar")

# restore original FEN color
parts = fen.split(" ")
parts[1] = parts[1].replace(opp_color, fen_color)
fen = " ".join(parts)

return fen


def get_san_moves(variant, fen, mlist, chess960, notation):
if variant == "alice":
return sf_alice.get_san_moves(variant, fen, mlist, chess960, notation)
Expand Down
Loading
Loading