Skip to content

Commit

Permalink
some progress
Browse files Browse the repository at this point in the history
  • Loading branch information
allanjoseph98 committed Jul 31, 2024
1 parent c3a7af2 commit 26d1c29
Show file tree
Hide file tree
Showing 11 changed files with 99 additions and 95 deletions.
19 changes: 11 additions & 8 deletions ui/learn/src/assert.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { SquareName as Key, Piece, Role } from 'chessops';
import { charToRole, SquareName as Key, Piece } from 'chessops';
import { AssertData } from './levelCtrl';
import { readKeys } from './util';

type Assert = (level: AssertData) => boolean;

type FenPiece = 'p' | 'n' | 'b' | 'r' | 'q' | 'k' | 'P' | 'N' | 'B' | 'R' | 'Q' | 'K';

const pieceMatch = (piece: Piece | undefined, matcher: Piece): boolean =>
piece?.role === matcher.role && piece.color === matcher.color;

Expand All @@ -12,18 +14,18 @@ const pieceOnAnyOf =
(level: AssertData) =>
keys.some(key => pieceMatch(level.chess.get(key), matcher));

const fenToMatcher = (fenPiece: string): Piece => ({
role: fenPiece.toLowerCase() as Role,
const fenToMatcher = (fenPiece: FenPiece): Piece => ({
role: charToRole(fenPiece),
color: fenPiece.toLowerCase() === fenPiece ? 'black' : 'white',
});

export const pieceOn =
(fenPiece: string, key: Key): Assert =>
(fenPiece: FenPiece, key: Key): Assert =>
(level: AssertData) =>
pieceMatch(level.chess.get(key), fenToMatcher(fenPiece));

export const pieceNotOn =
(fenPiece: string, key: Key): Assert =>
(fenPiece: FenPiece, key: Key): Assert =>
(level: AssertData) =>
!pieceMatch(level.chess.get(key), fenToMatcher(fenPiece));

Expand Down Expand Up @@ -65,9 +67,10 @@ export function not(assert: Assert): Assert {
return (level: AssertData) => !assert(level);
}

export function and(...asserts: Assert[]): Assert {
return (level: AssertData) => asserts.every(a => a(level));
}
export const and =
(...asserts: Assert[]): Assert =>
(level: AssertData) =>
asserts.every(a => a(level));

export function or(...asserts: Assert[]): Assert {
return (level: AssertData) => asserts.some(a => a(level));
Expand Down
70 changes: 46 additions & 24 deletions ui/learn/src/chess.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import * as cg from 'chessground/types';
import { parseFen, makeFen } from 'chessops/fen';
import { parseFen, makeBoardFen } from 'chessops/fen';
import {
parseSquare,
makeSquare,
Piece,
SquareSet,
Chess,
NormalMove as Move,
SquareName as Key,
charToRole,
} from 'chessops';
import { Antichess, Context } from 'chessops/variant';
import { chessgroundDests } from 'chessops/compat';
import { CgMove } from './chessground';
import { makeSan } from 'chessops/san';
import { oppColor, PromotionChar, promotionCharToRole } from './util';
import { isRole, oppColor, PromotionChar, PromotionRole } from './util';

type LearnVariant = Chess | Antichess;

Expand All @@ -22,7 +22,7 @@ export interface ChessCtrl {
setColor(c: Color): void;
getColor(): Color;
fen(): string;
move(orig: Key, dest: Key, prom?: PromotionChar | ''): Move | null;
move(orig: Key, dest: Key, prom?: PromotionRole | PromotionChar | ''): Move | null;
moves(pos: LearnVariant): Move[];
occupiedKeys(): Key[];
kingKey(color: Color): Key | undefined;
Expand All @@ -36,7 +36,7 @@ export interface ChessCtrl {
}

export default function (fen: string, appleKeys: Key[]): ChessCtrl {
let setup = parseFen(fen).unwrap();
const setup = parseFen(fen).unwrap();
const chess = Chess.fromSetup(setup);
// Use antichess when there are less than 2 kings
const pos = chess.isOk ? chess.unwrap() : Antichess.fromSetup(setup).unwrap();
Expand All @@ -49,20 +49,40 @@ export default function (fen: string, appleKeys: Key[]): ChessCtrl {
});
}

const context = (): Context => ({
blockers: setup.board.occupied,
checkers: SquareSet.empty(), //Revisit
king: undefined,
mustCapture: false,
variantEnd: false,
});
if (pos instanceof Antichess) pos.ctx = context;
const defaultAntichess = Antichess.default();
const defaultChess = Chess.default();
const context = (): Context => {
const king = pos.board.kingOf(pos.turn);
const occupied = pos.board.occupied;
return king
? {
blockers: occupied,
checkers: pos.kingAttackers(king, oppColor(pos.turn), occupied),
king: king,
mustCapture: false,
variantEnd: false,
}
: defaultAntichess.ctx();
};
if (pos instanceof Antichess) {
pos.ctx = context;
pos.kingAttackers = defaultChess.kingAttackers;
}

const cloneWithCtx = (pos: LearnVariant): LearnVariant => {
const clone = pos.clone();
clone.ctx = pos.ctx;
clone.kingAttackers = pos.kingAttackers;
return clone;
};

const history: string[] = [];

const moves = (pos: LearnVariant): Move[] =>
Array.from(chessgroundDests(pos)).flatMap(([orig, dests]) =>
dests.map((dest): Move => ({ from: parseSquare(orig), to: parseSquare(dest) })),
Array.from(chessgroundDests(pos)).reduce<Move[]>(
(prev, [orig, dests]) =>
prev.concat(dests.map((dest): Move => ({ from: parseSquare(orig), to: parseSquare(dest) }))),
[],
);

const findCaptures = (pos: LearnVariant): Move[] => moves(pos).filter(move => pos.board.get(move.to));
Expand All @@ -77,28 +97,30 @@ export default function (fen: string, appleKeys: Key[]): ChessCtrl {
dests: () => chessgroundDests(pos),
getColor: () => pos.turn,
setColor: setColor,
fen: () => makeFen(setup),
fen: () => makeBoardFen(pos.board),
moves: moves,
move: (orig: Key, dest: Key, prom?: PromotionChar) => {
move: (orig: Key, dest: Key, prom?: PromotionChar | PromotionRole | '') => {
const move: Move = {
from: parseSquare(orig),
to: parseSquare(dest),
promotion: prom ? promotionCharToRole[prom] : undefined,
promotion: prom ? (isRole(prom) ? prom : charToRole(prom)) : undefined,
};
pos.play(move);
if (pos.isCheck()) return null;
const clone = cloneWithCtx(pos);
clone.play(move);
clone.turn = oppColor(clone.turn);
history.push(makeSan(pos, move));
return move;
pos.play(move);
return !clone.isCheck() ? move : null;
},
occupiedKeys: () => Array.from(pos.board.occupied).map(s => makeSquare(s)),
kingKey: (color: Color) => {
const kingSq = pos.board.kingOf(color);
return kingSq ? makeSquare(kingSq) : undefined;
return kingSq !== undefined ? makeSquare(kingSq) : undefined;
},
findCapture: () => moveToCgMove(findCaptures(pos)[0]),
findUnprotectedCapture: () => {
const maybeCapture = findCaptures(pos).find(capture => {
const clone = pos.clone();
const clone = cloneWithCtx(pos);
clone.play({ from: capture.from, to: capture.to });
return !findCaptures(clone).length;
});
Expand All @@ -123,7 +145,7 @@ export default function (fen: string, appleKeys: Key[]): ChessCtrl {
}
return undefined;
},
get: (key: Key) => setup.board.get(parseSquare(key)),
get: (key: Key) => pos.board.get(parseSquare(key)),
instance: pos,
sanHistory: () => history,
};
Expand Down
2 changes: 1 addition & 1 deletion ui/learn/src/chessground.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default function (ctrl: RunCtrl): VNode {
el.addEventListener('contextmenu', e => e.preventDefault());
ctrl.setChessground(site.makeChessground(el, makeConfig(ctrl)));
},
destroy: () => ctrl.chessground!.destroy(),
destroy: () => ctrl.chessground?.destroy(),
},
});
}
Expand Down
4 changes: 1 addition & 3 deletions ui/learn/src/congrats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,4 @@ shuffle(list);

let it = 0;

export default function () {
return list[it++ % list.length];
}
export default (): string => list[it++ % list.length];
17 changes: 9 additions & 8 deletions ui/learn/src/levelCtrl.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { WithGround, arrow } from './util';
import { PromotionRole, WithGround, arrow, oppColor } from './util';
import { Items, ctrl as makeItems } from './item';
import { Level } from './stage/list';
import * as scoring from './score';
import * as timeouts from './timeouts';
import * as sound from './sound';
import makeChess, { ChessCtrl } from './chess';
import makeScenario, { Scenario } from './scenario';
import { SquareName as Key, makeSquare } from 'chessops';
import { SquareName as Key, makeSquare, makeUci } from 'chessops';
import { CgMove } from './chessground';
import * as cg from 'chessground/types';
import { PromotionCtrl } from './promotionCtrl';
Expand Down Expand Up @@ -91,7 +91,8 @@ export class LevelCtrl {
movable: {
free: false,
color: chess.getColor(),
dests: chess.dests({ illegal: blueprint.offerIllegalMove }),
dests: chess.dests(),
rookCastle: false,
},
events: {
move: (orig: Key, dest: Key) => {
Expand Down Expand Up @@ -135,9 +136,9 @@ export class LevelCtrl {
return true;
};

return (orig: Key, dest: Key) => {
return (orig: Key, dest: Key, prom?: PromotionRole) => {
vm.nbMoves++;
const move = chess.move(orig, dest);
const move = chess.move(orig, dest, prom);
if (move) this.setFen(chess.fen(), blueprint.color, new Map());
else {
// moving into check
Expand All @@ -161,7 +162,7 @@ export class LevelCtrl {
took = true;
}
this.setCheck();
if (scenario.player(move.from + move.to + (move.promotion || ''))) {
if (scenario.player(makeUci(move))) {
vm.score += scoring.scenario;
inScenario = true;
} else {
Expand Down Expand Up @@ -224,11 +225,11 @@ export class LevelCtrl {
showKingAttackers = () =>
this.withGround(ground => {
const turn = this.chess.getColor();
const kingKey = this.chess.kingKey(turn);
const kingKey = this.chess.kingKey(oppColor(turn));
const shapes = this.chess
.moves(this.chess.instance)
.filter(m => makeSquare(m.to) === kingKey)
.map(m => arrow(makeSquare(m.from) + makeSquare(m.to), 'red'));
.map(m => arrow(makeUci(m), 'red'));
ground.set({ check: turn });
this.setShapes(shapes);
});
Expand Down
8 changes: 2 additions & 6 deletions ui/learn/src/run/runCtrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,9 @@ export class RunCtrl {

withGround: WithGround = f => (this.chessground ? f(this.chessground) : undefined);

stageScore = () => {
const res = this.data.stages[this.stage.key];
return res?.scores.reduce((a, b) => a + b) ?? 0;
};
stageScore = () => this.data.stages[this.stage.key]?.scores.reduce((a, b) => a + b) ?? 0;

score = (level: stages.Level) =>
this.data.stages[this.stage.key] ? this.data.stages[this.stage.key].scores[level.id - 1] : 0;
score = (level: stages.Level) => this.data.stages[this.stage.key]?.scores[level.id - 1] ?? 0;

getNext = () => stages.byId[this.stage.id + 1];

Expand Down
12 changes: 5 additions & 7 deletions ui/learn/src/run/runView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,19 @@ import { LevelCtrl } from '../levelCtrl';
import { RunCtrl } from './runCtrl';
import { mapSideView } from '../mapSideView';
import { LearnCtrl } from '../ctrl';
import { h } from 'snabbdom';
import { h, VNode } from 'snabbdom';
import { bind } from 'common/snabbdom';
import { makeStars, progressView } from '../progressView';
import { promotionView } from '../promotionView';

function renderFailed(ctrl: RunCtrl) {
return h('div.result.failed', { hook: bind('click', ctrl.restart) }, [
const renderFailed = (ctrl: RunCtrl): VNode =>
h('div.result.failed', { hook: bind('click', ctrl.restart) }, [
h('h2', ctrl.trans.noarg('puzzleFailed')),
h('button', ctrl.trans.noarg('retry')),
]);
}

function renderCompleted(ctrl: RunCtrl, level: LevelCtrl) {
return h(
const renderCompleted = (ctrl: RunCtrl, level: LevelCtrl): VNode =>
h(
'div.result.completed',
{
class: { next: !!level.blueprint.nextButton },
Expand All @@ -33,7 +32,6 @@ function renderCompleted(ctrl: RunCtrl, level: LevelCtrl) {
: makeStars(level.blueprint, level.vm.score),
],
);
}

export const runView = (ctrl: LearnCtrl) => {
const runCtrl = ctrl.runCtrl;
Expand Down
5 changes: 1 addition & 4 deletions ui/learn/src/stage/king.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ const stage: StageNoID = {
apples: 'b5 c5 d6 e3 f3 g4',
nbMoves: 8,
},
].map((l: LevelPartial, i) => {
l.emptyApples = true;
return toLevel(l, i);
}),
].map((l: LevelPartial, i) => toLevel({ ...l, emptyApples: true }, i)),
complete: 'kingComplete',
};
export default stage;
22 changes: 8 additions & 14 deletions ui/learn/src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,23 @@ export interface Storage {
reset(): void;
}

const key = 'learn.progress';

const defaultValue: LearnProgress = {
stages: {},
};

function xhrSaveScore(stageKey: string, levelId: number, score: number) {
return xhr.jsonAnyResponse('/learn/score', {
const xhrSaveScore = (stageKey: string, levelId: number, score: number) =>
xhr.jsonAnyResponse('/learn/score', {
method: 'POST',
body: xhr.form({
stage: stageKey,
level: levelId,
score: score,
}),
});
}

function xhrReset() {
return xhr.jsonAnyResponse('/learn/reset', { method: 'POST' });
}
const xhrReset = () => xhr.jsonAnyResponse('/learn/reset', { method: 'POST' });

export default function (d?: LearnProgress): Storage {
const key = 'learn.progress';
const defaultValue: LearnProgress = {
stages: {},
};
const data: LearnProgress = d || JSON.parse(site.storage.get(key)!) || defaultValue;

return {
Expand All @@ -41,8 +36,7 @@ export default function (d?: LearnProgress): Storage {
};
if (data.stages[stage.key].scores[level.id - 1] > score) return;
data.stages[stage.key].scores[level.id - 1] = score;
if (data._id) xhrSaveScore(stage.key, level.id, score);
else site.storage.set(key, JSON.stringify(data));
data._id ? xhrSaveScore(stage.key, level.id, score) : site.storage.set(key, JSON.stringify(data));
},
reset: () => {
data.stages = {};
Expand Down
10 changes: 2 additions & 8 deletions ui/learn/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,11 @@ export function toLevel(l: LevelPartial, it: number): Level {

export const assetUrl = document.body.dataset.assetUrl + '/assets/';

export const promotionCharToRole: {
[R in PromotionChar]: PromotionRole;
} = {
n: 'knight',
b: 'bishop',
r: 'rook',
q: 'queen',
};
export type PromotionRole = 'knight' | 'bishop' | 'rook' | 'queen';
export type PromotionChar = 'n' | 'b' | 'r' | 'q';

export const isRole = (str: PromotionChar | PromotionRole): str is PromotionRole => str.length > 1;

export const arrow = (vector: Uci, brush?: cg.BrushColor): DrawShape => ({
brush: brush || 'paleGreen',
orig: vector.slice(0, 2) as Key,
Expand Down
Loading

0 comments on commit 26d1c29

Please sign in to comment.