Merge pull request 'perf(ui): reuse hint result as move preview, skip redundant evaluate' (#85) from feature/hint-preview-reuse into development
This commit was merged in pull request #85.
This commit is contained in:
@@ -29,6 +29,7 @@
|
|||||||
newPlacement,
|
newPlacement,
|
||||||
place,
|
place,
|
||||||
placementFromHint,
|
placementFromHint,
|
||||||
|
previewFromHint,
|
||||||
rackView,
|
rackView,
|
||||||
recallAt,
|
recallAt,
|
||||||
recallToSlot,
|
recallToSlot,
|
||||||
@@ -751,7 +752,14 @@
|
|||||||
if (isCoarse()) zoomed = true;
|
if (isCoarse()) zoomed = true;
|
||||||
view = { ...view, hintsRemaining: h.hintsRemaining, walletBalance: h.walletBalance };
|
view = { ...view, hintsRemaining: h.hintsRemaining, walletBalance: h.walletBalance };
|
||||||
syncWallet(h.walletBalance);
|
syncWallet(h.walletBalance);
|
||||||
recompute();
|
// The hint is the engine's own top-ranked, fully scored legal move: reuse it as the
|
||||||
|
// preview instead of a redundant evaluate (same engine call, same placement). Cancel any
|
||||||
|
// pending evaluate so a stale one cannot clobber it; a later manual edit re-arms recompute().
|
||||||
|
if (previewTimer) {
|
||||||
|
clearTimeout(previewTimer);
|
||||||
|
previewTimer = null;
|
||||||
|
}
|
||||||
|
preview = previewFromHint(h.move);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// The backend does not spend a hint when there is no move.
|
// The backend does not spend a hint when there is no move.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
newPlacement,
|
newPlacement,
|
||||||
place,
|
place,
|
||||||
placementFromHint,
|
placementFromHint,
|
||||||
|
previewFromHint,
|
||||||
rackView,
|
rackView,
|
||||||
recallAt,
|
recallAt,
|
||||||
recallIndex,
|
recallIndex,
|
||||||
@@ -105,6 +106,24 @@ describe('placementFromHint', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('previewFromHint', () => {
|
||||||
|
it('reuses the hint move as a legal preview, lower-casing words to match evaluate', () => {
|
||||||
|
const preview = previewFromHint({
|
||||||
|
player: 0,
|
||||||
|
action: 'play',
|
||||||
|
dir: 'V',
|
||||||
|
mainRow: 7,
|
||||||
|
mainCol: 7,
|
||||||
|
tiles: [{ row: 7, col: 7, letter: 'C', blank: false }],
|
||||||
|
words: ['CAT', 'AN'],
|
||||||
|
count: 0,
|
||||||
|
score: 18,
|
||||||
|
total: 42,
|
||||||
|
});
|
||||||
|
expect(preview).toEqual({ legal: true, score: 18, words: ['cat', 'an'], dir: 'V' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('reorderIndices', () => {
|
describe('reorderIndices', () => {
|
||||||
it('lifts an element and drops it at the given slot among the others', () => {
|
it('lifts an element and drops it at the given slot among the others', () => {
|
||||||
expect(reorderIndices(4, 0, 2)).toEqual([1, 2, 0, 3]);
|
expect(reorderIndices(4, 0, 2)).toEqual([1, 2, 0, 3]);
|
||||||
|
|||||||
+14
-1
@@ -4,7 +4,7 @@
|
|||||||
// payload. It is board-agnostic (the gateway/engine does full legality validation at
|
// payload. It is board-agnostic (the gateway/engine does full legality validation at
|
||||||
// submit), which keeps it trivially unit-testable.
|
// submit), which keeps it trivially unit-testable.
|
||||||
|
|
||||||
import type { Tile } from './model';
|
import type { EvalResult, MoveRecord, Tile } from './model';
|
||||||
import type { PlacedTile } from './client';
|
import type { PlacedTile } from './client';
|
||||||
|
|
||||||
export interface PendingTile {
|
export interface PendingTile {
|
||||||
@@ -55,6 +55,19 @@ export function placementFromHint(tiles: Tile[], rack: string[]): Placement {
|
|||||||
return { rack: [...rack], pending };
|
return { rack: [...rack], pending };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* previewFromHint turns a hint's move into a move preview. A hint is the engine's own
|
||||||
|
* top-ranked, fully scored legal play, so its move already carries the score, words and
|
||||||
|
* direction an evaluate would compute for the same placement; reusing it lets the client
|
||||||
|
* skip a redundant evaluate round-trip after auto-placing the hint. The words are
|
||||||
|
* lower-cased to match the evaluate result the caption renders: a hint's MoveRecord words
|
||||||
|
* are upper-cased on decode (for the move-history view), whereas an EvalResult keeps the
|
||||||
|
* backend's lower case, so reusing them verbatim would otherwise change the caption's case.
|
||||||
|
*/
|
||||||
|
export function previewFromHint(move: MoveRecord): EvalResult {
|
||||||
|
return { legal: true, score: move.score, words: move.words.map((w) => w.toLowerCase()), dir: move.dir };
|
||||||
|
}
|
||||||
|
|
||||||
function usedIndexes(p: Placement): Set<number> {
|
function usedIndexes(p: Placement): Set<number> {
|
||||||
return new Set(p.pending.map((t) => t.rackIndex));
|
return new Set(p.pending.map((t) => t.rackIndex));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user