Skip to content

Commit

Permalink
golf puzzle nvui
Browse files Browse the repository at this point in the history
  • Loading branch information
allanjoseph98 committed Dec 17, 2024
1 parent f82f82c commit 894dac0
Showing 1 changed file with 54 additions and 82 deletions.
136 changes: 54 additions & 82 deletions ui/puzzle/src/plugins/puzzle.nvui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { bind, onInsert } from 'common/snabbdom';
import { throttle } from 'common/timing';
import type PuzzleCtrl from '../ctrl';
import { Chessground as makeChessground } from 'chessground';
import { defined } from 'common';
import { opposite } from 'chessops';

const throttled = (sound: string) => throttle(100, () => site.sound.play(sound));
const selectSound = throttled('select');
Expand Down Expand Up @@ -66,7 +68,7 @@ export function initModule() {
h('h2', 'Puzzle info'),
puzzleBox(ctrl),
theme(ctrl),
!ctrl.streak ? userBox(ctrl) : null,
!ctrl.streak && userBox(ctrl),
h('h2', 'Moves'),
h(
'p.moves',
Expand Down Expand Up @@ -96,7 +98,7 @@ export function initModule() {
hook: onInsert(el => {
const $form = $(el),
$input = $form.find('.move').val('');
$input[0]!.focus();
$input[0]?.focus();
$form.on('submit', onSubmit(ctrl, notify.set, moveStyle.get, $input, ground));
}),
},
Expand All @@ -119,10 +121,10 @@ export function initModule() {
hook: onInsert(el => {
const $board = $(el);
const $buttons = $board.find('button');
const steps = () => ctrl.tree.getNodeList(ctrl.path);
const uciSteps = () => steps().filter(hasUci);
const fenSteps = () => steps().map(step => step.fen);
const opponentColor = ctrl.pov === 'white' ? 'black' : 'white';
const steps = ctrl.tree.getNodeList(ctrl.path);
const uciSteps = () => steps.filter((s): s is StepWithUci => defined(s.uci));
const fenSteps = () => steps.map(step => step.fen);
const opponentColor = opposite(ctrl.pov);
$board.on(
'click',
selectionHandler(() => opponentColor, selectSound),
Expand Down Expand Up @@ -229,15 +231,12 @@ interface StepWithUci extends Tree.Node {
uci: Uci;
}

function hasUci(step: Tree.Node): step is StepWithUci {
return step.uci !== undefined;
}

function lastMove(ctrl: PuzzleCtrl, style: MoveStyle): string {
const node = ctrl.node;
if (node.ply === 0) return 'Initial position';
// make sure consecutive moves are different so that they get re-read
return renderSan(node.san || '', node.uci, style) + (node.ply % 2 === 0 ? '' : ' ');
return node.ply === 0
? 'Initial position'
: // make sure consecutive moves are different so that they get re-read
renderSan(node.san || '', node.uci, style) + (node.ply % 2 === 0 ? '' : ' ');
}

function onSubmit(
Expand All @@ -246,48 +245,35 @@ function onSubmit(
style: () => MoveStyle,
$input: Cash,
ground: CgApi,
): () => false {
return () => {
): (ev: SubmitEvent) => void {
return (ev: SubmitEvent) => {
ev.preventDefault();
let input = castlingFlavours(($input.val() as string).trim());
if (isShortCommand(input)) input = '/' + input;
if (input[0] === '/') onCommand(ctrl, notify, input.slice(1), style());
else {
const uci = inputToLegalUci(input, ctrl.node.fen, ground);
if (uci) {
ctrl.playUci(uci);
switch (ctrl.lastFeedback) {
case 'fail':
notify(i18n.puzzle.notTheMove);
break;
case 'good':
notify(i18n.puzzle.bestMove);
break;
case 'win':
notify(i18n.puzzle.puzzleSuccess);
}
} else {
notify([`Invalid move: ${input}`, ...browseHint(ctrl)].join('. '));
}
const fback = ctrl.lastFeedback;
if (fback === 'fail') notify(i18n.puzzle.notTheMove);
else if (fback === 'good') notify(i18n.puzzle.bestMove);
else if (fback === 'win') notify(i18n.puzzle.puzzleSuccess);
} else notify([`Invalid move: ${input}`, ...browseHint(ctrl)].join('. '));
}
$input.val('');
return false;
};
}

function isYourMove(ctrl: PuzzleCtrl) {
return ctrl.node.children.length === 0 || ctrl.node.children[0].puzzle === 'fail';
}
const isYourMove = (ctrl: PuzzleCtrl): boolean =>
ctrl.node.children.length === 0 || ctrl.node.children[0].puzzle === 'fail';

function browseHint(ctrl: PuzzleCtrl): string[] {
if (ctrl.mode !== 'view' && !isYourMove(ctrl)) return ['You browsed away from the latest position.'];
else return [];
}
const browseHint = (ctrl: PuzzleCtrl): string[] =>
ctrl.mode !== 'view' && !isYourMove(ctrl) ? ['You browsed away from the latest position.'] : [];

const shortCommands = ['l', 'last', 'p', 's', 'v'];

function isShortCommand(input: string): boolean {
return shortCommands.includes(input.split(' ')[0].toLowerCase());
}
const isShortCommand = (input: string): boolean => shortCommands.includes(input.split(' ')[0].toLowerCase());

function onCommand(ctrl: PuzzleCtrl, notify: (txt: string) => void, c: string, style: MoveStyle): void {
const lowered = c.toLowerCase();
Expand All @@ -310,29 +296,21 @@ function viewOrAdvanceSolution(ctrl: PuzzleCtrl, notify: (txt: string) => void):
if (isInSolution(next) || (isInSolution(node) && isInSolution(nextNext))) {
controlNext(ctrl);
ctrl.redraw();
} else if (isInSolution(node)) {
notify('Puzzle complete!');
} else {
ctrl.viewSolution();
}
} else {
ctrl.viewSolution();
}
} else if (isInSolution(node)) notify('Puzzle complete!');
else ctrl.viewSolution();
} else ctrl.viewSolution();
}

function isInSolution(node?: Tree.Node): boolean {
return !!node && (node.puzzle === 'good' || node.puzzle === 'win');
}
const isInSolution = (node?: Tree.Node): boolean =>
!!node && (node.puzzle === 'good' || node.puzzle === 'win');

function nextNode(node?: Tree.Node): Tree.Node | undefined {
if (node?.children?.length) return node.children[0];
else return;
}
const nextNode = (node?: Tree.Node): Tree.Node | undefined =>
node?.children?.length ? node.children[0] : undefined;

function renderStreak(ctrl: PuzzleCtrl): VNode[] {
if (!ctrl.streak) return [];
return [h('h2', 'Puzzle streak'), h('p', ctrl.streak.data.index || i18n.puzzle.streakDescription)];
}
const renderStreak = (ctrl: PuzzleCtrl): VNode[] =>
!ctrl.streak
? []
: [h('h2', 'Puzzle streak'), h('p', ctrl.streak.data.index || i18n.puzzle.streakDescription)];

function renderStatus(ctrl: PuzzleCtrl): string {
if (ctrl.mode !== 'view') return 'Solving';
Expand All @@ -349,44 +327,38 @@ function renderReplay(ctrl: PuzzleCtrl): string {
return `Replaying ${text} puzzles: ${i} of ${replay.of}`;
}

function playActions(ctrl: PuzzleCtrl): VNode {
if (ctrl.streak)
return button(i18n.storm.skip, ctrl.skip, i18n.puzzle.streakSkipExplanation, !ctrl.streak.data.skip);
else return h('div.actions_play', button('View the solution', ctrl.viewSolution));
}
const playActions = (ctrl: PuzzleCtrl): VNode =>
ctrl.streak
? button(i18n.storm.skip, ctrl.skip, i18n.puzzle.streakSkipExplanation, !ctrl.streak.data.skip)
: h('div.actions_play', button('View the solution', ctrl.viewSolution));

function afterActions(ctrl: PuzzleCtrl): VNode {
const win = ctrl.lastFeedback === 'win';
return h(
const afterActions = (ctrl: PuzzleCtrl): VNode =>
h(
'div.actions_after',
ctrl.streak && !win
ctrl.streak && ctrl.lastFeedback === 'win'
? anchor(i18n.puzzle.newStreak, '/streak')
: [...renderVote(ctrl), button('Continue training', ctrl.nextPuzzle)],
);
}

const renderVoteTutorial = (ctrl: PuzzleCtrl): VNode[] =>
ctrl.session.isNew() && ctrl.data.user?.provisional
? [h('p', i18n.puzzle.didYouLikeThisPuzzle), h('p', i18n.puzzle.voteToLoadNextOne)]
: [];

function renderVote(ctrl: PuzzleCtrl): VNode[] {
if (!ctrl.data.user || ctrl.autoNexting()) return [];
return [
...renderVoteTutorial(ctrl),
button('Thumbs up', () => ctrl.vote(true), undefined, ctrl.voteDisabled),
button('Thumbs down', () => ctrl.vote(false), undefined, ctrl.voteDisabled),
];
}
const renderVote = (ctrl: PuzzleCtrl): VNode[] =>
!ctrl.data.user || ctrl.autoNexting()
? []
: [
...renderVoteTutorial(ctrl),
button('Thumbs up', () => ctrl.vote(true), undefined, ctrl.voteDisabled),
button('Thumbs down', () => ctrl.vote(false), undefined, ctrl.voteDisabled),
];

function anchor(text: string, href: string): VNode {
return h('a', { attrs: { href } }, text);
}
const anchor = (text: string, href: string): VNode => h('a', { attrs: { href } }, text);

function button(text: string, action: (e: Event) => void, title?: string, disabled?: boolean): VNode {
return h(
const button = (text: string, action: (e: Event) => void, title?: string, disabled?: boolean): VNode =>
h(
'button',
{ hook: bind('click', action), attrs: { ...(title ? { title } : {}), disabled: !!disabled } },
text,
);
}

0 comments on commit 894dac0

Please sign in to comment.