Plateau soigné (1i) : glisser-déposer, double toucher, clavier, filet des pions #69
+55
-32
@@ -1719,45 +1719,44 @@ export function startApp(options: AppOptions): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Double toucher : joue le coup évident (le domaine le cherche, le
|
||||
* moteur le juge) commencé au premier toucher, si le joueur n'a rien
|
||||
* fait d'autre entre-temps. Le premier toucher a pu faire partir le
|
||||
* Double toucher (charte, 1i) : joue le coup évident (le domaine le
|
||||
* cherche, le moteur le juge) qui commence par le pion touché, depuis
|
||||
* l'état du premier toucher. Ce premier toucher a pu faire partir le
|
||||
* pion tout de suite (une seule case d'arrivée) : il ne reste alors que
|
||||
* la suite du coup. Sinon, rien : le double toucher vaut un toucher.
|
||||
* la suite du coup. Sans coup évident, le second toucher n'est qu'un
|
||||
* toucher : touchers rapides sur une même pile (un double, par exemple)
|
||||
* et pion repris (touché deux fois : plus rien de choisi).
|
||||
* L'appelant a pris l'écran (busy) : le moteur est seul à travailler.
|
||||
*/
|
||||
async function playObvious(tap: ObviousTap): Promise<void> {
|
||||
await whenIdle();
|
||||
const g = game;
|
||||
if (g !== tap.game || busy || g.state.phase !== "moving") {
|
||||
return;
|
||||
}
|
||||
const now = g.pendingHalfMoves;
|
||||
const now = g?.pendingHalfMoves ?? [];
|
||||
const same = (a: HalfMove | undefined, b: HalfMove | undefined) =>
|
||||
a !== undefined && b !== undefined && a.from === b.from && a.to === b.to;
|
||||
if (now.length < tap.pending.length || !tap.pending.every((h, i) => same(h, now[i]))) {
|
||||
return;
|
||||
}
|
||||
// Demandé seulement maintenant : un toucher simple ne coûte rien au
|
||||
// moteur.
|
||||
busy = true;
|
||||
render();
|
||||
let obvious: HalfMove[] | null = null;
|
||||
try {
|
||||
obvious = await g.obviousMove(pointOf(tap.place), tap.pending);
|
||||
} catch {
|
||||
obvious = null;
|
||||
let rest: HalfMove[] = [];
|
||||
if (
|
||||
g === tap.game &&
|
||||
g.state.phase === "moving" &&
|
||||
now.length >= tap.pending.length &&
|
||||
tap.pending.every((h, i) => same(h, now[i]))
|
||||
) {
|
||||
// Demandé seulement maintenant : un toucher simple ne coûte rien
|
||||
// au moteur.
|
||||
const obvious = await g.obviousMove(pointOf(tap.place), tap.pending).catch(() => null);
|
||||
const done = now.slice(tap.pending.length);
|
||||
if (obvious !== null && done.every((h, i) => same(h, obvious[i]))) {
|
||||
rest = obvious.slice(done.length);
|
||||
}
|
||||
}
|
||||
busy = false;
|
||||
const done = now.slice(tap.pending.length);
|
||||
const rest = obvious?.slice(done.length) ?? [];
|
||||
const unchanged =
|
||||
game === g &&
|
||||
g.pendingHalfMoves.length === now.length &&
|
||||
done.every((h, i) => same(h, obvious?.[i]));
|
||||
if (!unchanged || rest.length === 0) {
|
||||
if (game !== g || g === null) {
|
||||
render();
|
||||
return;
|
||||
}
|
||||
if (rest.length === 0) {
|
||||
tapPlace(tap.place);
|
||||
return;
|
||||
}
|
||||
void act(async (g) => {
|
||||
info = "";
|
||||
const before = (await g.displayedBoard()).computer.bar;
|
||||
@@ -2673,12 +2672,18 @@ export function startApp(options: AppOptions): void {
|
||||
: { game: current, place, pending: [...current.pendingHalfMoves] },
|
||||
);
|
||||
if (second !== null) {
|
||||
void playObvious(second);
|
||||
doubleTapped(second);
|
||||
return;
|
||||
}
|
||||
if (busy) {
|
||||
return;
|
||||
if (!busy) {
|
||||
tapPlace(place);
|
||||
}
|
||||
});
|
||||
|
||||
/** Un toucher simple sur un endroit du plateau : le pion choisi va sur
|
||||
* cette case d'arrivée ; ou ce pion part tout de suite, s'il n'a qu'une
|
||||
* case d'arrivée ; ou il est choisi (touché de nouveau : plus rien). */
|
||||
function tapPlace(place: Place): void {
|
||||
keyTarget = null;
|
||||
const halfMove = selected === null ? undefined : targets.get(selected)?.get(place);
|
||||
if (halfMove !== undefined) {
|
||||
@@ -2695,7 +2700,25 @@ export function startApp(options: AppOptions): void {
|
||||
shownMove = null; // toucher un pion : le coup de l'ordinateur s'efface
|
||||
}
|
||||
render();
|
||||
});
|
||||
}
|
||||
|
||||
/** Le second toucher d'un double toucher : l'écran le prend tout de
|
||||
* suite (réponse immédiate), ou dès qu'il a fini l'effet du premier. */
|
||||
function doubleTapped(tap: ObviousTap): void {
|
||||
const claim = () => {
|
||||
if (busy) {
|
||||
return;
|
||||
}
|
||||
busy = true;
|
||||
render();
|
||||
void playObvious(tap);
|
||||
};
|
||||
if (busy) {
|
||||
void whenIdle().then(claim);
|
||||
} else {
|
||||
claim();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Le plateau au clavier (interaction.md) : les flèches gauche et droite
|
||||
|
||||
+11
-2
@@ -353,7 +353,8 @@ async function checkStable(when) {
|
||||
}
|
||||
|
||||
/** Les boutons visibles d'une page dont le texte passe à la ligne : un
|
||||
* bouton tient sur une ligne et fait 48 px de haut (charte). Un bouton plus
|
||||
* bouton tient sur une ligne et fait 48 px de haut (charte), touche de son
|
||||
* raccourci comprise. Un bouton plus
|
||||
* haut que 48 px n'est refusé que si son texte occupe plusieurs lignes ;
|
||||
* un texte coupé par le bord (white-space: nowrap) est refusé aussi. */
|
||||
async function wrappedButtons(target) {
|
||||
@@ -368,7 +369,9 @@ async function wrappedButtons(target) {
|
||||
const tops = new Set();
|
||||
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
||||
for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
|
||||
if (node.textContent.trim() === "") {
|
||||
// La touche du raccourci (kbd, quand un clavier est détecté)
|
||||
// est un badge en coin, pas une ligne du libellé.
|
||||
if (node.textContent.trim() === "" || node.parentElement?.closest("kbd.shortcut")) {
|
||||
continue;
|
||||
}
|
||||
const range = document.createRange();
|
||||
@@ -1193,6 +1196,7 @@ let resumed = false;
|
||||
let landscapeTurn = false;
|
||||
let rotatedChecked = false;
|
||||
let vibrationsChecked = false;
|
||||
let lastTouch = 0;
|
||||
let tracesSeen = 0;
|
||||
let replays = 0;
|
||||
let skips = 0;
|
||||
@@ -1496,9 +1500,14 @@ for (let actions = 0; ; actions++) {
|
||||
check(!(await page.isVisible("#menu")), "Échap ne ferme pas le menu");
|
||||
}
|
||||
const vibrationsBefore = (await vibrations()).length;
|
||||
// Deux touchers rapprochés sur la même pile seraient un double
|
||||
// toucher (coup évident, tests/browser/plateau.mjs) : ici, un
|
||||
// toucher à la fois, un demi-coup à la fois.
|
||||
await sleep(Math.max(0, lastTouch + 500 - Date.now()));
|
||||
// Un pion à destination unique avance dès qu'on le touche ; sinon,
|
||||
// ses cases d'arrivée s'allument. Le premier toucher est mesuré :
|
||||
// l'écran répond dans le gestionnaire du toucher.
|
||||
lastTouch = Date.now();
|
||||
const ms = await page.evaluate(() => {
|
||||
const hit = document.querySelector(".hit.can-move");
|
||||
const t0 = performance.now();
|
||||
|
||||
Reference in New Issue
Block a user