Skip to content
5 changes: 1 addition & 4 deletions ui/dasher/src/piece.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,7 @@ export function ctrl(
redraw: Redraw,
close: Close,
): PieceCtrl {
function dimensionData() {
return data[dimension()];
}

const dimensionData = () => data[dimension()];
return {
dimension,
trans,
Expand Down
11 changes: 11 additions & 0 deletions ui/editor/css/_tools.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@
vertical-align: middle;
}
}

.enpassant {
@extend %flex-between;
margin-top: 1em;
label {
font-weight: bold;
}
select {
width: 9ch;
}
}
}

.actions {
Expand Down
64 changes: 62 additions & 2 deletions ui/editor/src/ctrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,30 @@ export default class EditorCtrl {
this.redraw = redraw;
}

private nthIndexOf = (haystack: string, needle: string, n: number): number => {
let index = haystack.indexOf(needle);
while (n-- > 0) {
if (index === -1) break;
index = haystack.indexOf(needle, index + needle.length);
}
return index;
};

// Ideally to be replaced when something like parseCastlingFen exists in chessops but for epSquare (@getSetup)
private fenFixedEp(fen: string) {
let enPassant = fen.split(' ')[3];
if (enPassant !== '-' && !this.getEnPassantOptions(fen).includes(enPassant)) {
this.epSquare = undefined;
enPassant = '-';
}

const epIndex = this.nthIndexOf(fen, ' ', 2) + 1;
const epEndIndex = fen.indexOf(' ', epIndex);
return `${fen.substring(0, epIndex)}${enPassant}${fen.substring(epEndIndex)}`;
}

onChange(): void {
const fen = this.getFen();
const fen = this.fenFixedEp(this.getFen());
if (!this.cfg.embed) {
window.history.replaceState(null, '', this.makeEditorUrl(fen, this.bottomColor()));
}
Expand Down Expand Up @@ -123,11 +145,43 @@ export default class EditorCtrl {
);
}

// hopefully moved to chessops soon
// https://github.com/niklasf/chessops/issues/154
private getEnPassantOptions(fen: string): string[] {
const unpackRank = (packedRank: string) =>
[...packedRank].reduce((accumulator, current) => {
const parsedInt = parseInt(current);
return accumulator + (parsedInt >= 1 ? 'x'.repeat(parsedInt) : current);
}, '');
const checkRank = (rank: string, regex: RegExp, offset: number, filesEnPassant: Set<number>) => {
let match: RegExpExecArray | null;
while ((match = regex.exec(rank)) != null) {
filesEnPassant.add(match.index + offset);
}
};
const filesEnPassant: Set<number> = new Set();
const [positions, turn] = fen.split(' ');
const ranks = positions.split('/');
const unpackedRank = unpackRank(ranks[turn === 'w' ? 3 : 4]);
checkRank(unpackedRank, /pP/g, turn === 'w' ? 0 : 1, filesEnPassant);
checkRank(unpackedRank, /Pp/g, turn === 'w' ? 1 : 0, filesEnPassant);
const [rank1, rank2] =
filesEnPassant.size >= 1
? [unpackRank(ranks[turn === 'w' ? 1 : 6]), unpackRank(ranks[turn === 'w' ? 2 : 5])]
: [null, null];
return Array.from(filesEnPassant)
.filter(e => rank1![e] === 'x' && rank2![e] === 'x')
.map(e => String.fromCharCode('a'.charCodeAt(0) + e) + (turn === 'w' ? '6' : '3'))
.sort();
}

getState(): EditorState {
const legalFen = this.getLegalFen();
return {
fen: this.getFen(),
legalFen: this.getLegalFen(),
legalFen: legalFen,
playable: this.rules == 'chess' && this.isPlayable(),
enPassantOptions: legalFen ? this.getEnPassantOptions(legalFen) : [],
};
}

Expand Down Expand Up @@ -155,6 +209,12 @@ export default class EditorCtrl {

setTurn(turn: Color): void {
this.turn = turn;
this.epSquare = undefined;
this.onChange();
}

setEnPassant(epSquare: Square | undefined): void {
this.epSquare = epSquare;
this.onChange();
}

Expand Down
1 change: 1 addition & 0 deletions ui/editor/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface EditorState {
fen: string;
legalFen: string | undefined;
playable: boolean;
enPassantOptions: string[];
}

export type Redraw = () => void;
Expand Down
33 changes: 32 additions & 1 deletion ui/editor/src/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { dragNewPiece } from 'chessground/drag';
import { eventPosition, opposite } from 'chessground/util';
import { Rules } from 'chessops/types';
import { parseFen } from 'chessops/fen';
import { parseSquare, makeSquare } from 'chessops/util';
import { domDialog } from 'common/dialog';
import EditorCtrl from './ctrl';
import chessground from './chessground';
Expand Down Expand Up @@ -164,6 +165,36 @@ function controls(ctrl: EditorCtrl, state: EditorState): VNode {
castleCheckBox(ctrl, 'q', 'O-O-O', true),
]),
]),
h('div.enpassant', [
h('label', { attrs: { for: 'enpassant-select' } }, 'En passant'),
h(
'select#enpassant-select',
{
on: {
change(e) {
ctrl.setEnPassant(parseSquare((e.target as HTMLSelectElement).value));
},
},
props: {
value: ctrl.epSquare ? makeSquare(ctrl.epSquare) : '',
},
},
['', ...[3, 6].flatMap(r => 'abcdefgh'.split('').map(f => f + r))].map(key =>
h(
'option',
{
attrs: {
value: key,
selected: (key ? parseSquare(key) : undefined) === ctrl.epSquare,
hidden: Boolean(key && !state.enPassantOptions.includes(key)),
disabled: Boolean(key && !state.enPassantOptions.includes(key)) /*Safari*/,
},
},
key,
),
),
),
]),
]),
...(ctrl.cfg.embed || !ctrl.cfg.positions || !ctrl.cfg.endgamePositions
? []
Expand Down Expand Up @@ -483,7 +514,7 @@ export default function (ctrl: EditorCtrl): VNode {
h('div.main-board', [chessground(ctrl)]),
sparePieces(ctrl, color, color, 'bottom'),
controls(ctrl, state),
inputs(ctrl, state.fen),
inputs(ctrl, state.legalFen || state.fen),
],
);
}