Nvui for puzzles: add streak & replay info, polish

This commit is contained in:
Albert Ford 2021-06-15 02:09:06 -07:00
parent 33278568e7
commit 18ed6ba6b8

View file

@ -50,195 +50,193 @@ lichess.PuzzleNVUI = function (redraw: Redraw) {
return {
render(ctrl: Controller): VNode {
const ground = ctrl.ground() || createGround(ctrl),
pieces = ground.state.pieces;
const ground = ctrl.ground() || createGround(ctrl);
return h(
`main.puzzle.puzzle-${ctrl.getData().replay ? 'replay' : 'play'}${ctrl.streak ? '.puzzle--streak' : ''}`,
[
h('div.nvui', [
h('h1', `You play the ${ctrl.vm.pov} pieces. ${ctrl.difficulty || ''} puzzle`),
h('h2', 'Puzzle info'),
puzzleBox(ctrl),
theme(ctrl),
!ctrl.streak ? userBox(ctrl) : null,
h('h2', 'Moves'),
h(
'p.moves',
{
attrs: {
role: 'log',
'aria-live': 'off',
},
h('div.nvui', [
h('h1', `Puzzle: ${ctrl.vm.pov} to play.`),
h('h2', 'Puzzle info'),
puzzleBox(ctrl),
theme(ctrl),
!ctrl.streak ? userBox(ctrl) : null,
h('h2', 'Moves'),
h(
'p.moves',
{
attrs: {
role: 'log',
'aria-live': 'off',
},
renderMainline(ctrl.vm.mainline, ctrl.vm.path, moveStyle.get())
),
h('h2', 'Pieces'),
h('div.pieces', renderPieces(pieces, moveStyle.get())),
h('h2', 'Puzzle status'),
h(
'div.status',
{
attrs: {
role: 'status',
'aria-live': 'polite',
'aria-atomic': 'true',
},
},
renderMainline(ctrl.vm.mainline, ctrl.vm.path, moveStyle.get())
),
h('h2', 'Pieces'),
h('div.pieces', renderPieces(ground.state.pieces, moveStyle.get())),
h('h2', 'Puzzle status'),
h(
'div.status',
{
attrs: {
role: 'status',
'aria-live': 'polite',
'aria-atomic': 'true',
},
renderStatus(ctrl)
),
ctrl.streak ? renderStreak(ctrl) : null,
h('h2', 'Last move'),
h(
'p.lastMove',
{
attrs: {
'aria-live': 'assertive',
'aria-atomic': 'true',
},
},
renderStatus(ctrl)
),
h('div.replay', renderReplay(ctrl)),
...(ctrl.streak ? renderStreak(ctrl) : []),
h('h2', 'Last move'),
h(
'p.lastMove',
{
attrs: {
'aria-live': 'assertive',
'aria-atomic': 'true',
},
lastMove(ctrl, moveStyle.get())
),
h('h2', 'Move form'),
h(
'form',
{
hook: onInsert(el => {
const $form = $(el),
$input = $form.find('.move').val('');
$input[0]!.focus();
$form.on('submit', onSubmit(ctrl, notify.set, moveStyle.get, $input, ground));
},
lastMove(ctrl, moveStyle.get())
),
h('h2', 'Move form'),
h(
'form',
{
hook: onInsert(el => {
const $form = $(el),
$input = $form.find('.move').val('');
$input[0]!.focus();
$form.on('submit', onSubmit(ctrl, notify.set, moveStyle.get, $input, ground));
}),
},
[
h('label', [
ctrl.vm.mode === 'view' ? 'Command input' : `Find the best move for ${ctrl.vm.pov}.`,
h('input.move.mousetrap', {
attrs: {
name: 'move',
type: 'text',
autocomplete: 'off',
autofocus: true,
},
}),
]),
]
),
notify.render(),
h('h2', 'Actions'),
ctrl.vm.mode === 'view' ? afterActions(ctrl) : playActions(ctrl),
h('h2', 'Board'),
h(
'div.board',
{
hook: onInsert(el => {
const $board = $(el);
const $buttons = $board.find('button');
const steps = () => ctrl.getTree().getNodeList(ctrl.vm.path);
const uciSteps = () => steps().filter(hasUci);
const fenSteps = () => steps().map(step => step.fen);
const opponentColor = ctrl.vm.pov === 'white' ? 'black' : 'white';
$board.on('click', selectionHandler(opponentColor, selectSound));
$board.on('keypress', arrowKeyHandler(ctrl.vm.pov, borderSound));
$board.on('keypress', boardCommandsHandler());
$buttons.on('keypress', lastCapturedCommandHandler(fenSteps, pieceStyle.get(), prefixStyle.get()));
$buttons.on(
'keypress',
possibleMovesHandler(
ctrl.vm.pov,
() => ground.state.turnColor,
ground.getFen,
() => ground.state.pieces,
'standard',
() => ground.state.movable.dests,
uciSteps
)
);
$buttons.on('keypress', positionJumpHandler());
$buttons.on('keypress', pieceJumpingHandler(wrapSound, errorSound));
}),
},
renderBoard(
ground.state.pieces,
ctrl.vm.pov,
pieceStyle.get(),
prefixStyle.get(),
positionStyle.get(),
boardStyle.get()
)
),
h(
'div.boardstatus',
{
attrs: {
'aria-live': 'polite',
'aria-atomic': 'true',
},
[
h('label', [
ctrl.vm.pov === 'white' ? 'Find the best move for white.' : 'Find the best move for black.',
h('input.move.mousetrap', {
attrs: {
name: 'move',
type: 'text',
autocomplete: 'off',
autofocus: true,
},
}),
]),
]
),
notify.render(),
h('h2', 'Actions'),
ctrl.vm.mode === 'view' ? afterActions(ctrl) : playActions(ctrl),
h('h2', 'Board'),
h(
'div.board',
{
hook: onInsert(el => {
const $board = $(el);
const $buttons = $board.find('button');
const steps = () => ctrl.getTree().getNodeList(ctrl.vm.path);
const uciSteps = () => steps().filter(hasUci);
const fenSteps = () => steps().map(step => step.fen);
const opponentColor = ctrl.vm.pov === 'white' ? 'black' : 'white';
$board.on('click', selectionHandler(opponentColor, selectSound));
$board.on('keypress', arrowKeyHandler(ctrl.vm.pov, borderSound));
$board.on('keypress', boardCommandsHandler());
$buttons.on('keypress', lastCapturedCommandHandler(fenSteps, pieceStyle.get(), prefixStyle.get()));
$buttons.on(
'keypress',
possibleMovesHandler(
ctrl.vm.pov,
() => ground.state.turnColor,
ground.getFen,
() => ground.state.pieces,
'standard',
() => ground.state.movable.dests,
uciSteps
)
);
$buttons.on('keypress', positionJumpHandler());
$buttons.on('keypress', pieceJumpingHandler(wrapSound, errorSound));
}),
},
renderBoard(
ground.state.pieces,
ctrl.vm.pov,
pieceStyle.get(),
prefixStyle.get(),
positionStyle.get(),
boardStyle.get()
)
),
h(
'div.boardstatus',
{
attrs: {
'aria-live': 'polite',
'aria-atomic': 'true',
},
},
''
),
h('h2', 'Settings'),
h('label', ['Move notation', renderSetting(moveStyle, ctrl.redraw)]),
h('h3', 'Board Settings'),
h('label', ['Piece style', renderSetting(pieceStyle, ctrl.redraw)]),
h('label', ['Piece prefix style', renderSetting(prefixStyle, ctrl.redraw)]),
h('label', ['Show position', renderSetting(positionStyle, ctrl.redraw)]),
h('label', ['Board layout', renderSetting(boardStyle, ctrl.redraw)]),
...(!ctrl.getData().replay && !ctrl.streak && ctrl.difficulty
? [h('h3', 'Puzzle Settings'), renderDifficultyForm(ctrl)]
: []),
h('h2', 'Keyboard shortcuts'),
h('p', [
'Left and right arrow keys or k and j: Navigate to the previous or next move.',
h('br'),
'Up and down arrow keys or 0 and $: Jump to the first or last move.',
]),
h('h2', 'Commands'),
h('p', [
'Type these commands in the move input.',
h('br'),
'v: View the solution.',
h('br'),
'l: Read last move.',
h('br'),
commands.piece.help,
h('br'),
commands.scan.help,
h('br'),
]),
h('h2', 'Board Mode commands'),
h('p', [
'Use these commands when focused on the board itself.',
h('br'),
'o: announce current position.',
h('br'),
"c: announce last move's captured piece.",
h('br'),
'l: announce last move.',
h('br'),
't: announce clocks.',
h('br'),
'm: announce possible moves for the selected piece.',
h('br'),
'shift+m: announce possible moves for the selected pieces which capture..',
h('br'),
'arrow keys: move left, right, up or down.',
h('br'),
'kqrbnp/KQRBNP: move forward/backward to a piece.',
h('br'),
'1-8: move to rank 1-8.',
h('br'),
'Shift+1-8: move to file a-h.',
h('br'),
]),
h('h2', 'Promotion'),
h('p', [
'Standard PGN notation selects the piece to promote to. Example: a8=n promotes to a knight.',
h('br'),
'Omission results in promotion to queen',
]),
},
''
),
h('h2', 'Settings'),
h('label', ['Move notation', renderSetting(moveStyle, ctrl.redraw)]),
h('h3', 'Board Settings'),
h('label', ['Piece style', renderSetting(pieceStyle, ctrl.redraw)]),
h('label', ['Piece prefix style', renderSetting(prefixStyle, ctrl.redraw)]),
h('label', ['Show position', renderSetting(positionStyle, ctrl.redraw)]),
h('label', ['Board layout', renderSetting(boardStyle, ctrl.redraw)]),
...(!ctrl.getData().replay && !ctrl.streak && ctrl.difficulty
? [h('h3', 'Puzzle Settings'), renderDifficultyForm(ctrl)]
: []),
h('h2', 'Keyboard shortcuts'),
h('p', [
'Left and right arrow keys or k and j: Navigate to the previous or next move.',
h('br'),
'Up and down arrow keys or 0 and $: Jump to the first or last move.',
]),
]
h('h2', 'Commands'),
h('p', [
'Type these commands in the move input.',
h('br'),
'v: View the solution.',
h('br'),
'l: Read last move.',
h('br'),
commands.piece.help,
h('br'),
commands.scan.help,
h('br'),
]),
h('h2', 'Board Mode commands'),
h('p', [
'Use these commands when focused on the board itself.',
h('br'),
'o: announce current position.',
h('br'),
"c: announce last move's captured piece.",
h('br'),
'l: announce last move.',
h('br'),
't: announce clocks.',
h('br'),
'm: announce possible moves for the selected piece.',
h('br'),
'shift+m: announce possible moves for the selected pieces which capture..',
h('br'),
'arrow keys: move left, right, up or down.',
h('br'),
'kqrbnp/KQRBNP: move forward/backward to a piece.',
h('br'),
'1-8: move to rank 1-8.',
h('br'),
'Shift+1-8: move to file a-h.',
h('br'),
]),
h('h2', 'Promotion'),
h('p', [
'Standard PGN notation selects the piece to promote to. Example: a8=n promotes to a knight.',
h('br'),
'Omission results in promotion to queen',
]),
])
);
},
};
@ -351,15 +349,28 @@ function renderStreak(ctrl: Controller): VNode[] {
return [h('h2', 'Puzzle streak'), h('p', ctrl.streak.data.index || ctrl.trans.noarg('streakDescription'))];
}
function renderStatus(ctrl: Controller) {
if (ctrl.vm.mode !== 'view') return 'Your move.';
function renderStatus(ctrl: Controller): string {
if (ctrl.vm.mode !== 'view') return 'Solving';
else if (ctrl.streak) return `GAME OVER. Your streak: ${ctrl.streak.data.index}`;
else if (ctrl.vm.lastFeedback === 'win') return 'Success!';
else return 'Puzzle complete.';
}
function renderReplay(ctrl: Controller): string {
const replay = ctrl.getData().replay;
if (!replay) return '';
const i = replay.i + (ctrl.vm.mode === 'play' ? 0 : 1);
return `Replaying ${ctrl.trans.noarg(ctrl.getData().theme.key)} puzzles: ${i} of ${replay.of}`;
}
function playActions(ctrl: Controller): VNode {
if (ctrl.streak) return button(ctrl.trans.noarg('skip'), ctrl.skip, ctrl.trans.noarg('streakSkipExplanation'));
if (ctrl.streak)
return button(
ctrl.trans.noarg('skip'),
ctrl.skip,
ctrl.trans.noarg('streakSkipExplanation'),
!ctrl.streak.data.skip
);
else return h('div.actions_play', button('View the solution', ctrl.viewSolution));
}
@ -382,8 +393,8 @@ function renderVote(ctrl: Controller): VNode[] {
if (!ctrl.getData().user || ctrl.autoNexting()) return [];
return [
...renderVoteTutorial(ctrl),
button('Vote up', () => ctrl.vote(true), undefined, ctrl.vm.voteDisabled),
button('Vote down', () => ctrl.vote(false), undefined, ctrl.vm.voteDisabled),
button('Thumbs up', () => ctrl.vote(true), undefined, ctrl.vm.voteDisabled),
button('Thumbs down', () => ctrl.vote(false), undefined, ctrl.vm.voteDisabled),
];
}