use chessops to validate positions in editor
parent
2d2529b5e5
commit
f4ea296b48
|
@ -17,6 +17,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"chessground": "^7.6",
|
||||
"chessops": "^0.1.0",
|
||||
"snabbdom": "ornicar/snabbdom#0.7.1-lichess",
|
||||
"common": "2.0.0"
|
||||
}
|
||||
|
|
|
@ -1,23 +1,34 @@
|
|||
import { EditorConfig, EditorData, EditorOptions, Selected, Redraw, OpeningPosition, CastlingSide } from './interfaces';
|
||||
import * as editor from './editor';
|
||||
import { read as fenRead } from 'chessground/fen';
|
||||
import { EditorConfig, EditorOptions, EditorState, Selected, Redraw, OpeningPosition, CastlingToggle, CastlingToggles, CASTLING_TOGGLES } from './interfaces';
|
||||
import { Api as CgApi } from 'chessground/api';
|
||||
import { prop, Prop } from 'common';
|
||||
import { Rules, Square } from 'chessops/types';
|
||||
import { SquareSet } from 'chessops/squareSet';
|
||||
import { Board } from 'chessops/board';
|
||||
import { Setup, Material, RemainingChecks } from 'chessops/setup';
|
||||
import { Castles, setupPosition } from 'chessops/variant';
|
||||
import { makeFen, parseFen, parseCastlingFen, INITIAL_FEN, EMPTY_FEN } from 'chessops/fen';
|
||||
import { defined, prop, Prop } from 'common';
|
||||
|
||||
export default class EditorCtrl {
|
||||
cfg: EditorConfig;
|
||||
data: EditorData;
|
||||
options: EditorOptions;
|
||||
trans: Trans;
|
||||
selected: Prop<Selected>;
|
||||
extraPositions: OpeningPosition[];
|
||||
chessground: CgApi | undefined;
|
||||
positionIndex: { [boardFen: string]: number };
|
||||
redraw: Redraw;
|
||||
|
||||
selected: Prop<Selected>;
|
||||
|
||||
pockets: Material | undefined;
|
||||
turn: Color;
|
||||
unmovedRooks: SquareSet | undefined;
|
||||
castlingToggles: CastlingToggles<boolean>;
|
||||
epSquare: Square | undefined;
|
||||
remainingChecks: RemainingChecks | undefined;
|
||||
rules: Rules;
|
||||
|
||||
constructor(cfg: EditorConfig, redraw: Redraw) {
|
||||
this.cfg = cfg;
|
||||
this.data = editor.init(cfg);
|
||||
this.options = cfg.options || {};
|
||||
|
||||
this.trans = window.lichess.trans(this.cfg.i18n);
|
||||
|
@ -46,18 +57,71 @@ export default class EditorCtrl {
|
|||
redraw();
|
||||
});
|
||||
|
||||
|
||||
this.castlingToggles = { K: false, Q: false, k: false, q: false };
|
||||
this.rules = 'chess';
|
||||
|
||||
this.redraw = () => {};
|
||||
this.setFen(cfg.fen);
|
||||
this.redraw = redraw;
|
||||
}
|
||||
|
||||
onChange(): void {
|
||||
this.options.onChange && this.options.onChange(this.computeFen());
|
||||
this.options.onChange && this.options.onChange(this.getLegalFen());
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
computeFen(): string {
|
||||
return this.chessground ?
|
||||
editor.computeFen(this.data, this.chessground.getFen()) :
|
||||
this.cfg.fen;
|
||||
private castlingToggleFen(): string {
|
||||
let fen = '';
|
||||
for (const toggle of CASTLING_TOGGLES) {
|
||||
if (this.castlingToggles[toggle]) fen += toggle;
|
||||
}
|
||||
return fen;
|
||||
}
|
||||
|
||||
private getSetup(): Setup {
|
||||
const boardFen = this.chessground ? this.chessground.getFen() : this.cfg.fen;
|
||||
const board = parseFen(boardFen).unwrap(setup => setup.board, _ => Board.empty());
|
||||
return {
|
||||
board,
|
||||
pockets: this.pockets,
|
||||
turn: this.turn,
|
||||
unmovedRooks: this.unmovedRooks || parseCastlingFen(board, this.castlingToggleFen()).unwrap(),
|
||||
epSquare: this.epSquare,
|
||||
remainingChecks: this.remainingChecks,
|
||||
halfmoves: 0,
|
||||
fullmoves: 1,
|
||||
};
|
||||
}
|
||||
|
||||
getFen(): string {
|
||||
return makeFen(this.getSetup(), {promoted: this.rules == 'crazyhouse'});
|
||||
}
|
||||
|
||||
getLegalFen(): string | undefined {
|
||||
return setupPosition(this.rules, this.getSetup()).unwrap(pos => {
|
||||
return makeFen(pos.toSetup(), {promoted: pos.rules == 'crazyhouse'});
|
||||
}, _ => undefined);
|
||||
}
|
||||
|
||||
isPlayable(): boolean {
|
||||
return setupPosition(this.rules, this.getSetup()).unwrap(pos => !pos.isEnd(), _ => false);
|
||||
}
|
||||
|
||||
getState(): EditorState {
|
||||
return {
|
||||
fen: this.getFen(),
|
||||
legalFen: this.getLegalFen(),
|
||||
playable: this.isPlayable(),
|
||||
};
|
||||
}
|
||||
|
||||
makeAnalysisUrl(legalFen: string): string {
|
||||
return this.makeUrl('', legalFen); // TODO
|
||||
}
|
||||
|
||||
makeUrl(baseUrl: string, fen: string): string {
|
||||
return baseUrl + encodeURIComponent(fen).replace(/%20/g, '_').replace(/%2F/g, '/');
|
||||
}
|
||||
|
||||
bottomColor(): Color {
|
||||
|
@ -66,31 +130,23 @@ export default class EditorCtrl {
|
|||
this.options.orientation || 'white';
|
||||
}
|
||||
|
||||
setColor(letter: 'w' | 'b'): void {
|
||||
this.data.color(letter);
|
||||
setCastlingToggle(id: CastlingToggle, value: boolean): void {
|
||||
if (this.castlingToggles[id] != value) this.unmovedRooks = undefined;
|
||||
this.castlingToggles[id] = value;
|
||||
this.onChange();
|
||||
}
|
||||
|
||||
setCastle(id: CastlingSide, value: boolean): void {
|
||||
this.data.castles[id](value);
|
||||
setTurn(turn: Color): void {
|
||||
this.turn = turn;
|
||||
this.onChange();
|
||||
}
|
||||
|
||||
startPosition(): void {
|
||||
this.chessground!.set({
|
||||
fen: 'start'
|
||||
});
|
||||
this.data.castles = editor.castlesAt(true);
|
||||
this.data.color('w');
|
||||
this.onChange();
|
||||
this.setFen(INITIAL_FEN);
|
||||
}
|
||||
|
||||
clearBoard(): void {
|
||||
this.chessground!.set({
|
||||
fen: '8/8/8/8/8/8/8/8'
|
||||
});
|
||||
this.data.castles = editor.castlesAt(false);
|
||||
this.onChange();
|
||||
this.setFen(EMPTY_FEN);
|
||||
}
|
||||
|
||||
loadNewFen(fen: string | 'prompt'): void {
|
||||
|
@ -98,31 +154,36 @@ export default class EditorCtrl {
|
|||
fen = (prompt('Paste FEN position') || '').trim();
|
||||
if (!fen) return;
|
||||
}
|
||||
this.changeFen(fen);
|
||||
this.setFen(fen);
|
||||
}
|
||||
|
||||
changeFen(fen: string) {
|
||||
window.location.href = editor.makeUrl(this.data.baseUrl, fen);
|
||||
setFen(fen: string): boolean {
|
||||
return parseFen(fen).unwrap(setup => {
|
||||
if (this.chessground) this.chessground.set({fen});
|
||||
this.pockets = setup.pockets;
|
||||
this.turn = setup.turn;
|
||||
this.unmovedRooks = setup.unmovedRooks;
|
||||
this.epSquare = setup.epSquare;
|
||||
this.remainingChecks = setup.remainingChecks;
|
||||
|
||||
const castles = Castles.fromSetup(setup);
|
||||
this.castlingToggles['K'] = defined(castles.rook.white.h);
|
||||
this.castlingToggles['Q'] = defined(castles.rook.white.a);
|
||||
this.castlingToggles['k'] = defined(castles.rook.white.h);
|
||||
this.castlingToggles['q'] = defined(castles.rook.white.a);
|
||||
|
||||
this.onChange();
|
||||
return true;
|
||||
}, _ => false);
|
||||
}
|
||||
|
||||
changeVariant(variant: VariantKey): void {
|
||||
this.data.variant = variant;
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
positionLooksLegit(): boolean {
|
||||
const variant = this.data.variant;
|
||||
if (variant === 'antichess') return true;
|
||||
const pieces = this.chessground ? this.chessground.state.pieces : fenRead(this.cfg.fen);
|
||||
const kings = {
|
||||
white: 0,
|
||||
black: 0
|
||||
};
|
||||
for (const pos in pieces) {
|
||||
const piece = pieces[pos];
|
||||
if (piece && piece.role === 'king') kings[piece.color]++;
|
||||
}
|
||||
return kings.white === (variant !== 'horde' ? 1 : 0) && kings.black === 1;
|
||||
setRules(rules: Rules): void {
|
||||
this.rules = rules;
|
||||
if (rules != 'crazyhouse') this.pockets = undefined;
|
||||
else if (!this.pockets) this.pockets = Material.empty();
|
||||
if (rules != '3check') this.remainingChecks = undefined;
|
||||
else if (!this.remainingChecks) this.remainingChecks = RemainingChecks.default();
|
||||
this.onChange();
|
||||
}
|
||||
|
||||
setOrientation(o: Color): void {
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
import { prop, Prop } from 'common';
|
||||
import { EditorConfig, EditorData, Castles, CASTLING_SIDES } from './interfaces';
|
||||
|
||||
export function init(cfg: EditorConfig): EditorData {
|
||||
return {
|
||||
color: prop(cfg.color),
|
||||
castles: {
|
||||
K: prop(cfg.castles.K),
|
||||
Q: prop(cfg.castles.Q),
|
||||
k: prop(cfg.castles.k),
|
||||
q: prop(cfg.castles.q),
|
||||
},
|
||||
baseUrl: cfg.baseUrl,
|
||||
variant: 'standard'
|
||||
};
|
||||
}
|
||||
|
||||
export function castlesAt(v: boolean): Castles<Prop<boolean>> {
|
||||
return {
|
||||
K: prop(v),
|
||||
Q: prop(v),
|
||||
k: prop(v),
|
||||
q: prop(v),
|
||||
};
|
||||
}
|
||||
|
||||
function fenMetadatas(data: EditorData): string {
|
||||
let castles = '';
|
||||
for (const side of CASTLING_SIDES) {
|
||||
if (data.castles[side]()) castles += side;
|
||||
}
|
||||
return `${data.color()} ${castles.length ? castles : '-'} -`;
|
||||
}
|
||||
|
||||
export function computeFen(data: EditorData, cgFen: string): string {
|
||||
return `${cgFen} ${fenMetadatas(data)}`;
|
||||
}
|
||||
|
||||
export function makeUrl(url: string, fen: string): string {
|
||||
return url + encodeURIComponent(fen).replace(/%20/g, '_').replace(/%2F/g, '/');
|
||||
}
|
|
@ -1,12 +1,11 @@
|
|||
import { Prop } from 'common';
|
||||
import { Role } from 'chessground/types';
|
||||
|
||||
export type CastlingSide = 'K' | 'Q' | 'k' | 'q';
|
||||
export type CastlingToggle = 'K' | 'Q' | 'k' | 'q';
|
||||
|
||||
export const CASTLING_SIDES: CastlingSide[] = ['K', 'Q', 'k', 'q'];
|
||||
export const CASTLING_TOGGLES: CastlingToggle[] = ['K', 'Q', 'k', 'q'];
|
||||
|
||||
export type Castles<T> = {
|
||||
[side in CastlingSide]: T;
|
||||
export type CastlingToggles<T> = {
|
||||
[side in CastlingToggle]: T;
|
||||
};
|
||||
|
||||
export interface OpeningPosition {
|
||||
|
@ -25,22 +24,19 @@ export interface EditorConfig {
|
|||
};
|
||||
embed: boolean;
|
||||
positions?: OpeningPosition[];
|
||||
color: 'w' | 'b';
|
||||
i18n: any;
|
||||
castles: Castles<boolean>;
|
||||
}
|
||||
|
||||
export interface EditorOptions {
|
||||
orientation?: Color;
|
||||
onChange?: (fen: string) => void;
|
||||
onChange?: (fen: string | undefined) => void;
|
||||
inlineCastling?: boolean;
|
||||
}
|
||||
|
||||
export interface EditorData {
|
||||
baseUrl: string;
|
||||
color: Prop<'w' | 'b'>;
|
||||
castles: Castles<Prop<boolean>>;
|
||||
variant: VariantKey;
|
||||
export interface EditorState {
|
||||
fen: string;
|
||||
legalFen: string | undefined;
|
||||
playable: boolean;
|
||||
}
|
||||
|
||||
export type Redraw = () => void;
|
||||
|
|
|
@ -29,7 +29,8 @@ window.LichessEditor = (element: HTMLElement, config: EditorConfig) => {
|
|||
vnode = patch(inner, view(ctrl));
|
||||
|
||||
return {
|
||||
getFen: ctrl.computeFen.bind(ctrl),
|
||||
getFen: ctrl.getFen.bind(ctrl),
|
||||
getLegalFen: ctrl.getLegalFen.bind(ctrl),
|
||||
setOrientation: ctrl.setOrientation.bind(ctrl)
|
||||
};
|
||||
};
|
||||
|
|
|
@ -3,20 +3,20 @@ import { VNode } from 'snabbdom/vnode';
|
|||
import { MouchEvent, NumberPair } from 'chessground/types';
|
||||
import { dragNewPiece } from 'chessground/drag';
|
||||
import { eventPosition, opposite } from 'chessground/util';
|
||||
import { Rules } from 'chessops/types';
|
||||
import EditorCtrl from './ctrl';
|
||||
import chessground from './chessground';
|
||||
import * as editor from './editor';
|
||||
import { OpeningPosition, Selected } from './interfaces';
|
||||
import { OpeningPosition, Selected, CastlingToggle, EditorState } from './interfaces';
|
||||
|
||||
function castleCheckBox(ctrl: EditorCtrl, id: 'K' | 'Q' | 'k' | 'q', label: string, reversed: boolean): VNode {
|
||||
function castleCheckBox(ctrl: EditorCtrl, id: CastlingToggle, label: string, reversed: boolean): VNode {
|
||||
const input = h('input', {
|
||||
attrs: {
|
||||
type: 'checkbox',
|
||||
checked: ctrl.data.castles[id](),
|
||||
checked: ctrl.castlingToggles[id],
|
||||
},
|
||||
on: {
|
||||
change(e) {
|
||||
ctrl.setCastle(id, (e.target as HTMLInputElement).checked);
|
||||
ctrl.setCastlingToggle(id, (e.target as HTMLInputElement).checked);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -27,7 +27,7 @@ function optgroup(name: string, opts: VNode[]): VNode {
|
|||
return h('optgroup', { attrs: { label: name } }, opts);
|
||||
}
|
||||
|
||||
function studyButton(ctrl: EditorCtrl, fen: string): VNode {
|
||||
function studyButton(ctrl: EditorCtrl, state: EditorState): VNode {
|
||||
return h('form', {
|
||||
attrs: {
|
||||
method: 'post',
|
||||
|
@ -35,46 +35,46 @@ function studyButton(ctrl: EditorCtrl, fen: string): VNode {
|
|||
}
|
||||
}, [
|
||||
h('input', { attrs: { type: 'hidden', name: 'orientation', value: ctrl.bottomColor() } }),
|
||||
h('input', { attrs: { type: 'hidden', name: 'variant', value: ctrl.data.variant } }),
|
||||
h('input', { attrs: { type: 'hidden', name: 'fen', value: fen } }),
|
||||
h('input', { attrs: { type: 'hidden', name: 'variant', value: ctrl.rules } }),
|
||||
h('input', { attrs: { type: 'hidden', name: 'fen', value: state.legalFen || '' } }),
|
||||
h('button', {
|
||||
attrs: {
|
||||
type: 'submit',
|
||||
'data-icon': '4',
|
||||
disabled: !ctrl.positionLooksLegit(),
|
||||
disabled: !state.legalFen,
|
||||
},
|
||||
class: {
|
||||
button: true,
|
||||
'button-empty': true,
|
||||
text: true,
|
||||
disabled: !ctrl.positionLooksLegit()
|
||||
disabled: !state.legalFen,
|
||||
}
|
||||
}, 'Study')
|
||||
]);
|
||||
}
|
||||
|
||||
function variant2option(key: VariantKey, name: string, ctrl: EditorCtrl): VNode {
|
||||
function variant2option(key: Rules, name: string, ctrl: EditorCtrl): VNode {
|
||||
return h('option', {
|
||||
attrs: {
|
||||
value: key,
|
||||
selected: key == ctrl.data.variant
|
||||
selected: key == ctrl.rules
|
||||
},
|
||||
}, `${ctrl.trans.noarg('variant')} | ${name}`);
|
||||
}
|
||||
|
||||
const allVariants: Array<[VariantKey, string]> = [
|
||||
['standard', 'Standard'],
|
||||
const allVariants: Array<[Rules, string]> = [
|
||||
['chess', 'Standard'],
|
||||
['antichess', 'Antichess'],
|
||||
['atomic', 'Atomic'],
|
||||
['crazyhouse', 'Crazyhouse'],
|
||||
['horde', 'Horde'],
|
||||
['kingOfTheHill', 'King of the Hill'],
|
||||
['racingKings', 'Racing Kings'],
|
||||
['threeCheck', 'Three-check'],
|
||||
['kingofthehill', 'King of the Hill'],
|
||||
['racingkings', 'Racing Kings'],
|
||||
['3check', 'Three-check'],
|
||||
];
|
||||
|
||||
function controls(ctrl: EditorCtrl, fen: string): VNode {
|
||||
const positionIndex = ctrl.positionIndex[fen.split(' ')[0]];
|
||||
function controls(ctrl: EditorCtrl, state: EditorState): VNode {
|
||||
const positionIndex = ctrl.positionIndex[state.fen.split(' ')[0]];
|
||||
const currentPosition = ctrl.cfg.positions && positionIndex !== -1 ? ctrl.cfg.positions[positionIndex] : null;
|
||||
const position2option = function(pos: OpeningPosition): VNode {
|
||||
return h('option', {
|
||||
|
@ -84,8 +84,6 @@ function controls(ctrl: EditorCtrl, fen: string): VNode {
|
|||
}
|
||||
}, pos.eco ? `${pos.eco} ${pos.name}` : pos.name);
|
||||
};
|
||||
const selectedVariant = ctrl.data.variant;
|
||||
const looksLegit = ctrl.positionLooksLegit();
|
||||
return h('div.board-editor__tools', [
|
||||
...(ctrl.cfg.embed || !ctrl.cfg.positions ? [] : [h('div', [
|
||||
h('select.positions', {
|
||||
|
@ -98,7 +96,7 @@ function controls(ctrl: EditorCtrl, fen: string): VNode {
|
|||
optgroup(ctrl.trans.noarg('setTheBoard'), [
|
||||
...(currentPosition ? [] : [h('option', {
|
||||
attrs: {
|
||||
value: fen,
|
||||
value: state.fen,
|
||||
selected: true
|
||||
}
|
||||
}, `- ${ctrl.trans.noarg('boardEditor')} -`)]),
|
||||
|
@ -112,14 +110,14 @@ function controls(ctrl: EditorCtrl, fen: string): VNode {
|
|||
h('select', {
|
||||
on: {
|
||||
change(e) {
|
||||
ctrl.setColor((e.target as HTMLSelectElement).value as 'w' | 'b');
|
||||
ctrl.setTurn((e.target as HTMLSelectElement).value as Color);
|
||||
}
|
||||
}
|
||||
}, ['whitePlays', 'blackPlays'].map(function(key) {
|
||||
return h('option', {
|
||||
attrs: {
|
||||
value: key[0],
|
||||
selected: ctrl.data.color() === key[0]
|
||||
value: key[0] == 'w' ? 'white' : 'black',
|
||||
selected: ctrl.turn[0] === key[0]
|
||||
}
|
||||
}, ctrl.trans(key));
|
||||
}))
|
||||
|
@ -157,7 +155,7 @@ function controls(ctrl: EditorCtrl, fen: string): VNode {
|
|||
attrs: { id: 'variants' },
|
||||
on: {
|
||||
change(e) {
|
||||
ctrl.changeVariant((e.target as HTMLSelectElement).value as VariantKey);
|
||||
ctrl.setRules((e.target as HTMLSelectElement).value as Rules);
|
||||
}
|
||||
}
|
||||
}, allVariants.map(x => variant2option(x[0], x[1], ctrl)))
|
||||
|
@ -175,39 +173,39 @@ function controls(ctrl: EditorCtrl, fen: string): VNode {
|
|||
attrs: {
|
||||
'data-icon': 'A',
|
||||
rel: 'nofollow',
|
||||
...(looksLegit ? { href: editor.makeUrl('/analysis/' + selectedVariant + '/', fen) } : {})
|
||||
...(state.legalFen ? { href: ctrl.makeAnalysisUrl(state.legalFen) } : {})
|
||||
},
|
||||
class: {
|
||||
button: true,
|
||||
'button-empty': true,
|
||||
text: true,
|
||||
disabled: !looksLegit
|
||||
disabled: !state.legalFen
|
||||
}
|
||||
}, ctrl.trans.noarg('analysis')),
|
||||
h('a', {
|
||||
class: {
|
||||
button: true,
|
||||
'button-empty': true,
|
||||
disabled: !looksLegit || selectedVariant !== 'standard'
|
||||
disabled: !state.playable || ctrl.rules !== 'chess'
|
||||
},
|
||||
on: {
|
||||
click: () => {
|
||||
if (ctrl.positionLooksLegit() && selectedVariant === 'standard') $.modal($('.continue-with'));
|
||||
if (state.playable && ctrl.rules === 'chess') $.modal($('.continue-with'));
|
||||
}
|
||||
}
|
||||
}, [h('span.text', { attrs: { 'data-icon' : 'U' } }, ctrl.trans.noarg('continueFromHere'))]),
|
||||
studyButton(ctrl, fen)
|
||||
studyButton(ctrl, state)
|
||||
]),
|
||||
h('div.continue-with.none', [
|
||||
h('a.button', {
|
||||
attrs: {
|
||||
href: '/?fen=' + fen + '#ai',
|
||||
href: '/?fen=' + state.legalFen + '#ai',
|
||||
rel: 'nofollow'
|
||||
}
|
||||
}, ctrl.trans.noarg('playWithTheMachine')),
|
||||
h('a.button', {
|
||||
attrs: {
|
||||
href: '/?fen=' + fen + '#friend',
|
||||
href: '/?fen=' + state.legalFen + '#friend',
|
||||
rel: 'nofollow'
|
||||
}
|
||||
}, ctrl.trans.noarg('playWithAFriend'))
|
||||
|
@ -229,7 +227,7 @@ function inputs(ctrl: EditorCtrl, fen: string): VNode | undefined {
|
|||
on: {
|
||||
change(e) {
|
||||
const value = (e.target as HTMLInputElement).value;
|
||||
if (value !== fen) ctrl.changeFen(value);
|
||||
if (value !== fen) ctrl.setFen(value);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -240,7 +238,7 @@ function inputs(ctrl: EditorCtrl, fen: string): VNode | undefined {
|
|||
attrs: {
|
||||
readonly: true,
|
||||
spellcheck: false,
|
||||
value: editor.makeUrl(ctrl.data.baseUrl, fen)
|
||||
value: ctrl.makeUrl(ctrl.cfg.baseUrl, fen)
|
||||
}
|
||||
})
|
||||
])
|
||||
|
@ -328,7 +326,7 @@ function makeCursor(selected: Selected): string {
|
|||
}
|
||||
|
||||
export default function(ctrl: EditorCtrl): VNode {
|
||||
const fen = ctrl.computeFen();
|
||||
const state = ctrl.getState();
|
||||
const color = ctrl.bottomColor();
|
||||
|
||||
return h('div.board-editor', {
|
||||
|
@ -339,7 +337,7 @@ export default function(ctrl: EditorCtrl): VNode {
|
|||
sparePieces(ctrl, opposite(color), color, 'top'),
|
||||
h('div.main-board', [chessground(ctrl)]),
|
||||
sparePieces(ctrl, color, color, 'bottom'),
|
||||
controls(ctrl, fen),
|
||||
inputs(ctrl, fen)
|
||||
controls(ctrl, state),
|
||||
inputs(ctrl, state.fen)
|
||||
]);
|
||||
}
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -2,6 +2,11 @@
|
|||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@badrap/result@^0.2.5":
|
||||
version "0.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@badrap/result/-/result-0.2.5.tgz#d2594ab32ad4a9150d489ecddbf08fb9f6d71d11"
|
||||
integrity sha512-gPu1w+LiYkcfPuRK2de68g14VFRGNSQ+CXF6GXIa+0NfUeacuHplJlXyyN57ihZXJ1kbbLYcWdeRUARX8htocA==
|
||||
|
||||
"@gulp-sourcemaps/identity-map@1.X":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz#1e6fe5d8027b1f285dc0d31762f566bccd73d5a9"
|
||||
|
@ -791,6 +796,13 @@ chessground@^7.6:
|
|||
resolved "https://registry.yarnpkg.com/chessground/-/chessground-7.6.10.tgz#b837c11e27a8504c6f3ed06c659e8e2d8ea11f3e"
|
||||
integrity sha512-kAnS6hy7NNdGLu6H1b2IOodwEHu9cDfaG/GVhh96KuBm2D9llZ2Ccf/j0sLwmMQctn3cIxOXV4yeRsT4/8szzg==
|
||||
|
||||
chessops@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/chessops/-/chessops-0.1.0.tgz#4079c3997026f32bf5055431b2babf9a6814ec2d"
|
||||
integrity sha512-9EicBwEJrz3zQWh0os1e7gpBwHMGB5mzx23xhKWCEfFknN3ub3t+V+qpKQ7WUXok+wGWkP8lQ6wzy3ffs14sdA==
|
||||
dependencies:
|
||||
"@badrap/result" "^0.2.5"
|
||||
|
||||
chokidar@^2.0.0, chokidar@^2.1.1:
|
||||
version "2.1.8"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
|
||||
|
|
Loading…
Reference in New Issue