Commit 62d54dbf authored by Mahmoud Aglan's avatar Mahmoud Aglan

copy: the main dictionary in Egyptian, short; fixes the suites turned up

All 690 stiff or wordy Arabic lines of i18n.js now read the way an Egyptian
game talks: ماتش، كسبت، البوت، ليفل، أونلاين، لايف، الباسورد، إنت;
بنـ… for "loading", اتـ… for "done", مفيش for "none". Chess speaks like
Egyptian players (العسكري، كش مات، بولت/بليتز); domino too (دقّ، الدُش).
Meaning fixes: checkmate read "check"; a domino tip gave the opposite
advice; the master rank shared the legendary rarity's word; a bye read
like a promotion.

Found by the suites:
- Eight sign-in errors had no strings (auth.err_*): a failed login showed
  the raw key. Added in both languages.
- Domino: when the other player reported a too-short match first, the
  winner saw 0 coins with no reason. The server now says too_short to the
  later reporter as well.
- Ludo: the knocked-over pawn on a last place sank under the finishing
  order; it rests inside its box now, at every phone height.
- Tests: the ludo sticker check accepts the fallen pawn; the chess puzzle
  failure step copes with a position that has no wrong, non-mating move.
Co-Authored-By: 's avatarClaude Opus 5.5 <noreply@anthropic.com>
parent 632a4891
...@@ -592,7 +592,7 @@ function handleComplete(string $userId, array $input): void { ...@@ -592,7 +592,7 @@ function handleComplete(string $userId, array $input): void {
if (ghostTooShort($row['created_at'] ?? null)) { if (ghostTooShort($row['created_at'] ?? null)) {
$done = $sdb->update('domino_matches', ['status' => 'completed', 'completed_at' => date('c'), 'result' => 'too_short', 'winner_id' => $first, 'updated_at' => date('c')], $done = $sdb->update('domino_matches', ['status' => 'completed', 'completed_at' => date('c'), 'result' => 'too_short', 'winner_id' => $first, 'updated_at' => date('c')],
['id' => 'eq.' . $matchId, 'completed_at' => 'is.null']); ['id' => 'eq.' . $matchId, 'completed_at' => 'is.null']);
jsonResponse(['success' => true, (empty($done) ? 'already_completed' : 'too_short') => true, 'rewards' => []]); jsonResponse(['success' => true, 'too_short' => true, 'already_completed' => empty($done), 'rewards' => []]);
} }
} }
...@@ -652,10 +652,13 @@ function handleComplete(string $userId, array $input): void { ...@@ -652,10 +652,13 @@ function handleComplete(string $userId, array $input): void {
], ['id' => 'eq.' . $matchId, 'completed_at' => 'is.null']); ], ['id' => 'eq.' . $matchId, 'completed_at' => 'is.null']);
if (empty($claimed) || isset($claimed['error'])) { if (empty($claimed) || isset($claimed['error'])) {
// Already completed by another player: hand back what was paid. // Already completed by another player: hand back what was paid, and
$rows = $sdb->get('domino_matches', ['id' => 'eq.' . $matchId, 'select' => 'game_state', 'limit' => 1]); // say so when it paid nothing because the match was too short (the
// later reporter would otherwise see 0 with no reason).
$rows = $sdb->get('domino_matches', ['id' => 'eq.' . $matchId, 'select' => 'game_state,result', 'limit' => 1]);
$gs = dominoJson($rows[0]['game_state'] ?? null); $gs = dominoJson($rows[0]['game_state'] ?? null);
jsonResponse(['success' => true, 'already_completed' => true, 'rewards' => $gs['rewards'] ?? [], 'me' => dominoProgress($sdb, $userId)]); $short = ($rows[0]['result'] ?? '') === 'too_short';
jsonResponse(['success' => true, 'already_completed' => true, 'too_short' => $short, 'rewards' => $gs['rewards'] ?? [], 'me' => dominoProgress($sdb, $userId)]);
} }
if ($tooShort) jsonResponse(['success' => true, 'too_short' => true, 'rewards' => [], 'me' => dominoProgress($sdb, $userId)]); if ($tooShort) jsonResponse(['success' => true, 'too_short' => true, 'rewards' => [], 'me' => dominoProgress($sdb, $userId)]);
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -265,9 +265,11 @@ const STYLE = ` ...@@ -265,9 +265,11 @@ const STYLE = `
.lr-sticker .gi { position: relative; z-index: 1; } .lr-sticker .gi { position: relative; z-index: 1; }
.lr-sticker.is-win .gi { animation: lrFloat 2.6s ease-in-out 1.4s infinite; } .lr-sticker.is-win .gi { animation: lrFloat 2.6s ease-in-out 1.4s infinite; }
.lr-sticker.is-loss .gi { filter: grayscale(0.4); rotate: -10deg; } .lr-sticker.is-loss .gi { filter: grayscale(0.4); rotate: -10deg; }
.lr-fallen { position: relative; z-index: 1; display: flex; transform-origin: 50% 92%; rotate: 90deg; translate: 0 14px; animation: lrTopple 900ms var(--ease-pop) 700ms both; } /* Knocked over around its base; --fall-y keeps the lying pawn inside the
sticker box instead of sinking under the finishing order. */
.lr-fallen { --fall-y: -8px; position: relative; z-index: 1; display: flex; transform-origin: 50% 92%; rotate: 90deg; translate: 0 var(--fall-y); animation: lrTopple 900ms var(--ease-pop) 700ms both; }
.lr-fallen svg { width: 58px; height: 72px; } .lr-fallen svg { width: 58px; height: 72px; }
@keyframes lrTopple { 0% { rotate: 0deg; translate: 0 0; } 55% { rotate: 104deg; translate: 0 14px; } 75% { rotate: 84deg; } 100% { rotate: 90deg; translate: 0 14px; } } @keyframes lrTopple { 0% { rotate: 0deg; translate: 0 0; } 55% { rotate: 104deg; translate: 0 var(--fall-y); } 75% { rotate: 84deg; } 100% { rotate: 90deg; translate: 0 var(--fall-y); } }
@keyframes lrSticker { 0% { opacity: 0; scale: 0.2; rotate: -25deg; } 70% { opacity: 1; scale: 1.15; rotate: 6deg; } 100% { scale: 1; rotate: 0deg; } } @keyframes lrSticker { 0% { opacity: 0; scale: 0.2; rotate: -25deg; } 70% { opacity: 1; scale: 1.15; rotate: 6deg; } 100% { scale: 1; rotate: 0deg; } }
@keyframes lrFloat { 50% { translate: 0 -6px; } } @keyframes lrFloat { 50% { translate: 0 -6px; } }
.lr-rays { .lr-rays {
...@@ -358,6 +360,8 @@ const STYLE = ` ...@@ -358,6 +360,8 @@ const STYLE = `
.lr-ribbon { font-size: var(--fs-xl); padding: 7px 26px 9px; } .lr-ribbon { font-size: var(--fs-xl); padding: 7px 26px 9px; }
.lr-sticker { height: 62px; margin-bottom: 0; } .lr-sticker { height: 62px; margin-bottom: 0; }
.lr-sticker .gi { width: 58px; height: 58px; } .lr-sticker .gi { width: 58px; height: 58px; }
.lr-fallen { --fall-y: -14px; }
.lr-fallen svg { width: 44px; height: 54px; }
.lr-rays { width: 130px; height: 130px; } .lr-rays { width: 130px; height: 130px; }
.lr-sub { margin-bottom: 4px; } .lr-sub { margin-bottom: 4px; }
.lr-row { min-height: 36px; padding-block: 1px; } .lr-row { min-height: 36px; padding-block: 1px; }
......
...@@ -971,8 +971,17 @@ async function testPuzzles(acc) { ...@@ -971,8 +971,17 @@ async function testPuzzles(acc) {
const fen = await fenOf(page, '__el3abPuzzle'); const fen = await fenOf(page, '__el3abPuzzle');
const g = new Chess(fen); const g = new Chess(fen);
const answer = String(p2.moves).split(' ')[String(p2.moves).split(' ').length % 2 === 0 ? 1 : 0]; const answer = String(p2.moves).split(' ')[String(p2.moves).split(' ').length % 2 === 0 ? 1 : 0];
const wrong = g.moves({ verbose: true }).find(m => (m.from + m.to) !== answer.slice(0, 4) && !m.promotion && (() => { g.move(m); const mate = g.in_checkmate(); g.undo(); return !mate; })()); // A legal move that is not the answer and does not mate (any mate counts
await play(page, wrong.from + wrong.to, '__el3abPuzzle'); // as solved). Promotions only when nothing else is left.
const notAnswer = g.moves({ verbose: true }).filter(m => (m.from + m.to) !== answer.slice(0, 4) && (() => { g.move(m); const mate = g.in_checkmate(); g.undo(); return !mate; })());
const wrong = notAnswer.find(m => !m.promotion) || notAnswer[0];
if (!wrong) {
console.log(` note puzzle ${next}: every legal move is the answer or mates, so the failure path is not tried`);
await click(page, '#back-btn');
page.where = 'home';
return page;
}
await play(page, wrong.from + wrong.to + (wrong.promotion || ''), '__el3abPuzzle');
const failed = await page.waitForSelector('.pz-status.pz-bad', { visible: true, timeout: 8000 }).catch(() => null); const failed = await page.waitForSelector('.pz-status.pz-bad', { visible: true, timeout: 8000 }).catch(() => null);
check('a wrong move fails the puzzle', !!failed); check('a wrong move fails the puzzle', !!failed);
await sleep(900); await sleep(900);
......
...@@ -433,7 +433,7 @@ async function partLocal4() { ...@@ -433,7 +433,7 @@ async function partLocal4() {
ribbon: document.querySelector('.lr-ribbon')?.textContent.trim(), ribbon: document.querySelector('.lr-ribbon')?.textContent.trim(),
tone: document.querySelector('.lr-ribbon')?.className, tone: document.querySelector('.lr-ribbon')?.className,
rows: [...document.querySelectorAll('.lr-row .lr-name')].map(n => n.textContent.trim()), rows: [...document.querySelectorAll('.lr-row .lr-name')].map(n => n.textContent.trim()),
sticker: !!document.querySelector('.lr-sticker .gi'), sticker: !!document.querySelector('.lr-sticker .gi, .lr-sticker .lr-fallen svg'), // last place: your own pawn, knocked over
again: !!document.querySelector('#btn-again.btn-primary.btn-xl'), again: !!document.querySelector('#btn-again.btn-primary.btn-xl'),
back: !!document.querySelector('#btn-back.btn-secondary'), back: !!document.querySelector('#btn-back.btn-secondary'),
shot: !!document.querySelector('.lr-shot'), shot: !!document.querySelector('.lr-shot'),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment