From 18a6ef7d729171c23844291ef48e1ae49681ad4f Mon Sep 17 00:00:00 2001 From: codex-bot Date: Sat, 12 Sep 2026 13:18:15 +0100 Subject: [PATCH] [verified] Snake 0.8: circular arena and larger 32px tiles --- README.md | 42 +++++++++++++---------- package.json | 2 +- src/common/snake.js | 48 ++++++++++++++++++++++---- src/common/tex/apple.png | Bin 902 -> 1103 bytes src/common/tex/board.png | Bin 4416 -> 20723 bytes src/common/tex/body.png | Bin 672 -> 791 bytes src/common/tex/body_h.png | Bin 694 -> 839 bytes src/common/tex/body_v.png | Bin 727 -> 873 bytes src/common/tex/corner_en.png | Bin 306 -> 372 bytes src/common/tex/corner_es.png | Bin 321 -> 383 bytes src/common/tex/corner_nw.png | Bin 237 -> 276 bytes src/common/tex/corner_sw.png | Bin 346 -> 397 bytes src/common/tex/head_e.png | Bin 841 -> 955 bytes src/common/tex/head_n.png | Bin 918 -> 1129 bytes src/common/tex/head_s.png | Bin 913 -> 1120 bytes src/common/tex/head_w.png | Bin 731 -> 878 bytes src/common/tex/tail_e.png | Bin 644 -> 768 bytes src/common/tex/tail_n.png | Bin 848 -> 1085 bytes src/common/tex/tail_s.png | Bin 794 -> 1047 bytes src/common/tex/tail_w.png | Bin 673 -> 790 bytes src/manifest.json | 4 +-- src/pages/index/index.ux | 34 +++++++++---------- tests/art.cjs | 8 +++++ tests/layout.cjs | 15 +++++++- tests/make_round_art.py | 64 +++++++++++++++++++++++++++++++++++ tests/page.cjs | 18 +++++----- tests/preview-0.8-static.png | Bin 0 -> 43205 bytes tests/properties.cjs | 10 +++--- tests/round-history.cjs | 30 ++++++++++++++++ tests/round.cjs | 38 +++++++++++++++++++++ tests/upgrade.cjs | 29 +++++++++------- 31 files changed, 272 insertions(+), 70 deletions(-) create mode 100644 tests/art.cjs create mode 100644 tests/make_round_art.py create mode 100644 tests/preview-0.8-static.png create mode 100644 tests/round-history.cjs create mode 100644 tests/round.cjs diff --git a/README.md b/README.md index 0575701..bbe5c8c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ # SNAKE — Xiaomi Watch S4 -Textured Snake on a 16×16 grid: 20px tiles, 320px board, centered on a round -466px screen. Existing PNG board, directional snake pieces, apple and icon are -preserved. Package identity: `com.ilias.snake`. +Textured Snake on a **368px circular arena**, centered on the round 466px +screen. **32px tiles** (60% larger than 0.7) occupy 89 legal cells of an 11×11 +bounding grid; every tile corner stays inside the actual circle. The square +board frame is gone. Directional sprite artwork and user icon are preserved. Package identity: `com.ilias.snake`. -**Version 0.7 (versionCode 7): locally tested and release-built; the new features +**Version 0.8 (versionCode 8): locally tested and release-built; the new features still need an on-watch check.** Earlier versions rendered and played on the user's Watch S4 41mm. Node tests and numeric layout checks are not device tests. @@ -15,14 +16,15 @@ user's Watch S4 41mm. Node tests and numeric layout checks are not device tests. queued direction. Excess inputs are ignored, not allowed to replace a turn. - **Tap the board, snake or apple** to pause. Tap **RESUME** (or an exposed part of the board) to resume. Pausing clears queued turns. -- In the pause or game-over panel, select **CLASSIC** (walls end the run) or - **WRAP** (cross an edge to appear on the opposite edge). Changing mode starts +- In the pause or game-over panel, select **CLASSIC** (the circular boundary ends the run) or + **WRAP** (cross the circular boundary to appear at the opposite legal end + of the **same row or column**). Changing mode starts a fresh, paused game; tap RESUME to begin. Selecting the current mode does not reset the run. The current mode is shown below the board. - Both modes end on self-collision. Moving into the vacating tail is legal. - **PLAY AGAIN** is a large 216×56px retry target. Retry keeps the selected mode, resets score/speed/queued turns, and starts playing. Filling the board displays - **YOU WIN** and hides the apple safely. + **YOU WIN** at 89 occupied cells (86 points) and hides the apple safely. - Leaving the page pauses it and stops the timer. Returning requires an explicit resume; game-over never resumes automatically. The system back action remains untouched. No crown, rotary, side-button or back interception APIs are used. @@ -41,18 +43,24 @@ paused, finished or destroyed. Old timer callbacks are invalidated. Storage reads merge with the in-memory maximum; writes wait for the initial read and are serialized/coalesced so slow callbacks cannot overwrite a newer record. The coordinator survives page recreation within the module lifetime. -Invalid saved values are ignored. Errors/exceptions do not interrupt gameplay: +Old 0.7 records through 253 remain valid and are never reduced to the new +86-point maximum. Invalid saved values are ignored. Errors/exceptions do not interrupt gameplay: failed reads/writes retry on later apples or page-show activity, without a retry loop. A failed/uncompleted write or immediate app termination may lose the unsaved record. Storage persistence still needs verification on the watch. ## Lightweight rendering -Directional PNG tiles use only `left`/`top` positioning, no transforms or new -art assets. Wrapped neighbors are normalized before classifying seam textures. -Food placement takes at most 256 cell checks, including a nearly full board. -The HUD, footer, board and overlay fit the circular chord numerically with -at least an 8px total horizontal safety allowance. +Directional PNG tiles use only `left`/`top` positioning, no transforms. Existing +20px sprite art is nearest-neighbor enlarged to native 32px assets (crisp pixel +art, not new high-resolution detail). The board is a newly generated circular +PNG with transparent corners and a subtle legal-cell checker pattern. Wrapped +texture connections handle each varying row/column length. All rows/columns +have at least three cells, avoiding ambiguous one-/two-cell seams. +Food scans only 89 legal cells, including a nearly full board. HUD/footer +rectangles never overlap legal play tiles; text/overlay fit the watch chord. +`tests/preview-0.8-static.png` is a static composition of actual geometry/art, +with a legal-cell overlay—not a runtime screenshot. ## Build and test @@ -62,13 +70,13 @@ npm test npm run release ``` -`npm test` runs the original core, piece and page tests, 13 incremental upgrade +`npm test` runs the original core, piece and page tests, 14 upgrade regressions, the numeric layout suite, and deterministic randomized property tests. Upgrade coverage includes rapid swipes, lifecycle/timer races, storage failures and async ordering, mode changes, all four Wrap edges, seam textures, full-board victory and bounded food spawning. New behaviors were exercised through failing-then-passing test cycles. -Release output: `dist/com.ilias.snake.release.0.7.rpk`. The locked +Release output: `dist/com.ilias.snake.release.0.8.rpk`. The locked `aiot-toolkit` 2.0.5 uses the existing `sign/private.pem` and `sign/certificate.pem`; do not regenerate or commit those keys. Release uses plain JS (not JSC/protobuf), matching the previously working toolchain. The dependency lock is unchanged; @@ -80,8 +88,8 @@ matching the previously working toolchain. The dependency lock is unchanged; - `src/pages/index/index.ux`: round-screen UI, swipe queue, game/lifecycle state. - `src/common/snake.js`: movement, collision, Wrap piece classification, food. - `src/common/best.js`: asynchronous storage coordinator. -- `src/common/tex/`: preserved textured artwork. -- `tests/{snake,pieces,page,upgrade,layout,properties}.cjs`: Node regression suites. +- `src/common/tex/`: native 32px sprite artwork and circular board. +- `tests/{snake,pieces,page,upgrade,layout,properties,round,art,round-history}.cjs`: Node regression suites, including history-based circular seam orientation checks. Install the signed RPK through AstroBox. Source/releases live at https://gitea.oimcloud.myaddr.tools/codex-bot/vela-snake — publication is a diff --git a/package.json b/package.json index 2badb6b..f598d6d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "description": "Textured Snake for the round Xiaomi Watch S4 screen", "scripts": { - "test": "node tests/snake.cjs && node tests/pieces.cjs && node tests/page.cjs && node --test tests/upgrade.cjs && node tests/layout.cjs && node tests/properties.cjs", + "test": "node tests/snake.cjs && node tests/pieces.cjs && node tests/page.cjs && node --test tests/upgrade.cjs && node tests/layout.cjs && node tests/properties.cjs && node tests/round.cjs && node tests/art.cjs && node tests/round-history.cjs", "build": "aiot build", "release": "aiot release", "build:source-map": "aiot build --devtool=source-map", diff --git a/src/common/snake.js b/src/common/snake.js index e1d8144..06476c4 100644 --- a/src/common/snake.js +++ b/src/common/snake.js @@ -1,4 +1,18 @@ -// Snake core — pure functions, no Vela APIs. Exports: initialSnake, step, turn, spawnFood. +// Shared geometry: 32px tiles wholly inside the 368px circular arena. +const ARENA = { cell: 32, size: 11, offset: 57, radius: 184, cells: [], rows: [], columns: [] } +for (let y = 0; y < ARENA.size; y++) { + ARENA.rows[y] = [] + ARENA.columns[y] = [] +} +for (let y = 0; y < ARENA.size; y++) for (let x = 0; x < ARENA.size; x++) { + const dx = Math.abs((x + 0.5) * ARENA.cell - 176) + ARENA.cell / 2 + const dy = Math.abs((y + 0.5) * ARENA.cell - 176) + ARENA.cell / 2 + if (dx * dx + dy * dy <= ARENA.radius * ARENA.radius) { + ARENA.cells.push({ x, y }) + ARENA.rows[y].push(x) + ARENA.columns[x].push(y) + } +} function initialSnake() { return [ { x: 6, y: 7 }, @@ -12,13 +26,22 @@ function step(snake, dir, food, N, mode) { const head = snake[0] let nx = head.x + dir.x let ny = head.y + dir.y - if (mode === 'wrap') { + if (typeof N === 'object') { + const row = N.rows[ny] + if (!row || row.indexOf(nx) === -1) { + if (mode !== 'wrap') return { snake, ate: false, alive: false } + const span = dir.x ? N.rows[head.y] : N.columns[head.x] + const opposite = dir.x + dir.y > 0 ? span[0] : span[span.length - 1] + if (dir.x) nx = opposite + else ny = opposite + } + } else if (mode === 'wrap') { nx = (nx + N) % N ny = (ny + N) % N } else if (nx < 0 || ny < 0 || nx >= N || ny >= N) { return { snake, ate: false, alive: false } } - const ate = (nx === food.x && ny === food.y) + const ate = !!food && (nx === food.x && ny === food.y) // tail cell vacates unless we grew, so exclude it from self-collision check const body = ate ? snake : snake.slice(0, snake.length - 1) for (let i = 0; i < body.length; i++) { @@ -44,13 +67,22 @@ function renderPieces(snakeCells, dir, N) { const D = { '1,0': 'e', '-1,0': 'w', '0,-1': 'n', '0,1': 's' } const opp = { e: 'w', w: 'e', n: 's', s: 'n' } const delta = d => N && d === N - 1 ? -1 : N && d === 1 - N ? 1 : d + // Circular seams join the endpoints of this row/column, not the bounding grid. + const side = (s, neighbor) => { + let dx = neighbor.x - s.x, dy = neighbor.y - s.y + if (N && typeof N === 'object') { + if (Math.abs(dx) > 1) dx = dx > 0 ? -1 : 1 + if (Math.abs(dy) > 1) dy = dy > 0 ? -1 : 1 + } else { dx = delta(dx); dy = delta(dy) } + return D[dx + ',' + dy] + } return snakeCells.map((s, i) => { if (i === 0) return 'head_' + D[dir.x + ',' + dir.y] const prev = snakeCells[i - 1] const next = snakeCells[i + 1] - const from = D[delta(prev.x - s.x) + ',' + delta(prev.y - s.y)] // side toward head + const from = side(s, prev) // side toward head if (!next) return 'tail_' + opp[from] // tip points away from body - const to = D[delta(next.x - s.x) + ',' + delta(next.y - s.y)] // side toward tail tip + const to = side(s, next) // side toward tail tip if ((from === 'e' || from === 'w') && (to === 'e' || to === 'w')) return 'body_h' if ((from === 'n' || from === 's') && (to === 'n' || to === 's')) return 'body_v' // corner: name = the two sides it connects; pairs are alphabetically sorted @@ -75,6 +107,10 @@ function rotateDir(dir, clockwise) { // Random empty cell, avoiding the snake. function spawnFood(snake, N) { const occupied = new Set(snake.map(s => s.x + ',' + s.y)) + if (typeof N === 'object') { + const empty = N.cells.filter(c => !occupied.has(c.x + ',' + c.y)) + return empty.length ? empty[Math.floor(Math.random() * empty.length)] : null + } if (occupied.size >= N * N) return null // Select an empty-cell rank uniformly; at most N*N checks, even almost full. let rank = Math.floor(Math.random() * (N * N - occupied.size)) @@ -89,4 +125,4 @@ function spawnFood(snake, N) { return null } -module.exports = { initialSnake, step, turn, renderPieces, rotateDir, spawnFood } +module.exports = { ARENA, initialSnake, step, turn, renderPieces, rotateDir, spawnFood } diff --git a/src/common/tex/apple.png b/src/common/tex/apple.png index 556ee0e69969ef7573e1554d805fa3b69e3c689e..3d5413f6f198b0038855bdd059a623ca664689a6 100644 GIT binary patch literal 1103 zcmV-V1hD&wP)I2kBHX|W5l z5GuA^2qjqvF3h5cL@b&>yD5z+S{EfIYMYs4lF7`=`?xOV-sI)eX%Hr7@p$jvd;aHt z&bjBjdj-3cS9&vXfL1GE1AIO4ducZ~a<>HjFR|}uajvh=2FQ<%d8t@*06>YABYL_& zm#aCH&lA*(S$70-wY4^2D-c4cq!20zq7y`?0>LCUli0cYz}@>P7zPXARqz=bs{-VK zqe?{qlvDwL0L2I^Tk7o~qAtV0Ku5=;4<{0D0JID1`M=xblK@K*6#;nUA9Z~`aDN1t zSzi9~)ZE;ydWbYU1zSd77%ap1bT<1sKvz8eSx+L-3XrrcDq7XJB?Oc`4}htqrP6Pe z%6kAG%*=eX5i=}%D*{Rr5)houWZnemNu|yMJf2KKHv27rACDK^+qaJbh`>((_z2$? zLIFr2?5V{?0IruxZvza>&YlnBZz7;T3Qz#KOy;q}iNsBSBdx6fxZT}D`!8Jh!Ysdc z_G~}E^5x6t09>VFY7H-liQ4PL!h!&}Q7#|K&(7Y`wuB+k@D!}}fsq>f03dByC!RPG->t=0jk zn%dvtzhW>A{yGAt_7zC6u&@B|TwB|euT(pLrRP16zTV}!j-eLfEu$!PMfpkzfJNV* zx}@=S&)FM&V3mOG0}4qI0e_eXz)iu z=~}mI2le+P*e@P9Fb42KI{gelvu(Rh5ZD{_Hi)9Ok(QYp(?2UPNXW*w&l@K?2Z z>DO}kV}Q>Kg%UstWS-6Ug>9QhxJ8?>57X)W01q@bKOGaIJG|ae>PE3r83*{ZQ228v zoUk-J1v|D+LLLx0#nhoAO-XL&xsm_8n1ldS=*lIty}j$m&s@8v%z>s}qU?nf{0j=t VhWDVVk*oj!002ovPDHLkV1i7!13~}* literal 902 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 literal 4416 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 aeiWPa+1tSax%p=V06P(Qy#82J_J05ZZ?PNz diff --git a/src/common/tex/body.png b/src/common/tex/body.png index 186c62715d45528b6050801ebc7db117713fc1a5..0729f224f2419975ce9fd8a0d16330161e9d9b02 100644 GIT binary patch literal 791 zcmV+y1L*vTP)PJPthB%QngP~)Jn9fL@EUkp#@R037K^d zGrI{1frKIvpH@4bvHkfQk1YhLzuA60U$1-sC@)4^WMM&&Tt`*8gD8L)p?lu@3eZ0I z`~Kk5m(Kv^E+^inNd4{BD}a?}$;ZdfYZky*QI|PkAZkId*8Yzz&72Cr$!^R2{&xHh zAbON6?f}%bYgVi{0M~UkUWx%&T37-g?WVL&TjWlO$XH9)n@3FK&#!IQZ2P#q!z`pA znv2Vuc#>$Z+p_>0>l>^kYa`0Zast5K{vIxK0Fxsjpgpw!VnyfTxlMqG%qQ7pPF9vy z0H`N*daegU%wMi9`}JC##$m%p5Je+;H#w1isq-5iJ_R|3s!Fv|g~0fN{Q+V{an8eD zU92KU;s4!gLs4I50D&Nakap4$1@(mtRmE8EZ`w(()Q3U6sU641$pz3c+eJZ>Up$~rJcW+w#?2`9TJ0G$3f9dUf2vB2ufRj*s>dosHz zxK%>rrsQL|IJ_t#H)S(&{VtCB>$UA#vH#8v6gMFl$aBunYg+t^q|ni<_2`658=ZjAhzf#6%D>mf<@5DD_mv0!uhXr2!u3|k1c=BOY~@T=w!BY z=IuUs3dW9R(~=VHhhwZ6ZE7=BcbSchW`&XA>3HC-PTzYCx~~J%@kN-9n=>>W{{xFB VKj^#c_GAD6002ovPDHLkV1lZldjtRg literal 672 zcmV;R0$=@!P)wYkx9mO3`{9%X2mzQGUon#tvybh|`tskWmw){A zr^;VXe>!|T`u&?HL+`vSrYJK*2=D#ZfB=@F>H0+#-~WB}TX-<)KOdeAy}V+aMPKX} z?C=gNloUEO-{?YLx9*!Qa{v92RIDd0aOmQ<* zV6Az1*-o)@;z(vOMIG)RQjSX6*fMYD%-cB!g9FN8NlNK{mE9*HYGX}i@LZ6N( zh%qwg50JD?fk4PDo;1^DEiHzb;hdwHHY|&@jX5*Jdr$SDV)Ai9p5=Jw$wSU;K4W|} zMs7P8Kv-9G{^p!=R5I8b(99afS7Vx4gOkJ9;>hbjNkofWTwbhlCMUf7U6^$Og!nQO zVf{sj5W=c3X7<0zlnLh?BuH)A3}kKGHdR$#)NgB=sv+xV^p1O7NR9&ww2LL+N zP1Rt@77DlM2O%)X`q|O}>N16uKlAS0hT5Epegc-<*qaTjA`R9hZs>ym8#digHk;gbqetI)~b?1WRPOE+n z#ewtgQ?DkoI8Un!R(!Dn7RkMTc$Z;qu>x+}m-V3<_wEL&uN4smFp)u> zCIHD_fB!K1>5n4->3;Re=Kx##>vy+4-r4h;pePCe)Vm50tg+$HiXSx41wJM+$ZHd> zaA@HNMlRvUmcOBp3E0mxl}FG;$Gv?G7cZkwoO9KhOoe=nb>ceoH1Xmpx40gM&R=XnF*>W5bubO!)v zsiG0Xc6%Fu2TvYQWd4qncN8@+`gH_Aa+n~d1=cWxH7oH!#^Ja(1;rVOdn549wlC@II08>0?uE|8UH>8p!Zo% zH1`Wu?*e2I3*u%BHM3LG02WWP)ofM#&}Or2X>E9Z)pL!6Y?dwNO5DlEt+y_mQcFY#CJf=WAMc{P0n zz~S(a{lUKHTOa*U7`_++FgzMoe3$vKd#QhkolkcF82vUv^AI3>)4A5+^7YI9s9Y=l z>Mc+o1g(xnM@(iDs9nr7PdT0+15{BE78<(TvGLJ{-_woBrvAd&*q_0&giwy|}7!mP_9aKte%rMp63yzhJ4qf?u=u^bg4HLZ1Xl RAyfbW002ovPDHLkV1gmkhI0S_ literal 694 zcmV;n0!jUeP)0v@6o#L3W}4)hwy|v*TdCTGE)xJagcLOQz%IrrW&zZz5YyfUT_v7(75v%-#c30A_Dp zTyCv2?}!+Hc=~2n+IOyAU$`*)c;U*@nh3-gF$@r# zIz$A+km@}Es8?jXkYd83(R*qT4hn<^e)?8hS6q9!5muWVd6lK9){*{m- zs)`wvhzN(D4;l4F_@<}1+Qc_B&CGVz!mxPgYpwc*I>th84!GgOqt<|41( zzb2Nka;mWRVvl?~M^vz6WbKU3jSjBitTtcYP$Cv$koIDm)#X*nP!eKb6h^e>TAW)s zM@$i|DF+`9$lvDp%%e5MXP)7gA-z{U#EDoi^GJnGR?jfAN!UP@XQSRgL=X|mK{?&S zsIv9d`Z#vciNO9~zj~_bRMy z!#Rf&NB2vY?rs<79H~s4>YQTf+ESHYFf1Cai!DxHI*kPb0Y72OImhnaF8hOhvU)}l z3J!;dsE)Z8NFgzIevYi2QT9t*=BUlruxLnyG=2&l;M7r+1xLjZBJxA*r?O!9%rn!R z!OW)4#<`joKStD$3g8N8>?e|s|CE>@R5k>eoHOwvzQ{+WiL&j0`b07*qoM6N<$f>GF4H2?qr diff --git a/src/common/tex/body_v.png b/src/common/tex/body_v.png index f43235bfceda41f0e45a89b40e96c123ae04624c..88c9379d598e15cc25a76b0f3076099832a0ff6e 100644 GIT binary patch literal 873 zcmV-v1D5=WP)}g1kbXrf-lUz2wjqXwV2~ z)5fln*ph2Gj>z5VVV5);(`c+qIDnw#>~g;6&mEyk4>15d!i~S){}Q0PyYU@BtJgZf zssJEJ@rtTmzON{N#747a_5|Sk#o4z2N8kVaV^x0fis2)Z)UA78eX=#VH+l*%J{V5` zBo=3LYY2`S@wOXNhfinsmXD{eDyNic1nxh)9|fikCL0en_tVMd zemXI2lbN>3?8=&Sn34`t_O|!vcl-3aeex_P&vMjh`Ewa2?nGJKiLye@V6yR`kRNnG z6H*Y@F-6Q!#D~aQCjfVM?lRe$Kq$PObO(UxUsC{PCo^Ib#EIf^U$BK75i`7j0p!+M z0ocAh0$`kuS!Jv8Op*IL<2wMHE>FvSL8{eN7?RiNYHA8ZivI2zYO)%~*nP2_z@ z+6fn{3jiV$U6HA(a)sI02vof-@@kC1Itu{@N-zvSxXM>ocxxK~5Ji6LcySECW^a?h z?EwJRTB68@`SA520OuFym!Nb51DcrvD!80E-#yR%c*gE{7ec|!&*lKUeDx9lv!*ZC z8c=N5!&r`cmQ?2(p=_B-a6F$2uR_X5l(+U1t2@i z=%gJ0x}$)Uxw}@zS_YJ@91M8#(;F7kg=ZOo-#kD4JO!Y?*I&zsW~RV7hl#yy=SOb= zSWFj0iElYUaMpW!{EJ7*Bu$7DFHY@;L*ey66hi-Q{?0E9Ncf)UR^Os$9T83Vo z62(#|7XR<&b-F%!3jTLA+YY0dX^Au8%A=}C=+j#CyTND{|A!H#*X5(>>}-&H;}vL_ zj^hK(imNbETk)6EG1)Tvfy#8ed_0{saytGOLTgaAUaboU00000NkvXXu0mjfCF!G` literal 727 zcmV;|0x127P)NklDF!Bm{n!G`Z^pZncpg|*` zO_R6^VoR>1Bug$Q2U&__$2Kw-a=Dz3L;i+CVQxn#4@teB4t{9uu05)?Yll=O2m)Yc zvw4PzR|}Kyboz4q;P~n8S{mp~C(1wPptTgZD?A_X<)9MgI#FAmjNCEXt z7d8*_Vxw5Q57;>a&ytB~R|Ko7B)4~NQ*0HO89Vt7{a1Ykrvtot5FH^Qz2axO-r(E#B*_g2OD zDxe6U!r9+vgmIvmH)(CRNHKjq(3lW>f=_zUtoIi0=EjnGSJqh6UFzh~4R z;WCG7IK&Ajf1D7e0dd#w`;u%T@NxJN6vPBehB(2ZQT|^dDxWLQOmX{i?3 ztnZtC(ZA^P#Rx_4DVnu++GKe~d>^rBR39qV_t&X+>ZF*KZ;P3&2DS2G#?Y!HzM;1L z{Whn6o)X4^Y%8PtT^CEXNF(RP%n-9QTnTEqofp_!hrL2;l2ldA3=vsX_nHDbkI*QG5UZ diff --git a/src/common/tex/corner_en.png b/src/common/tex/corner_en.png index 3bb5503319d18c22e5e7b66a7ed533159b259cbc..9a03cd2194292f4d51718ffd5e78a1fad3b65ac4 100644 GIT binary patch literal 372 zcmV-)0gL{LP)xW*+Y`FtUBKuQ(jz@^TO00X%>Q@Bkjb1B?I^aQGu|ZlENg6aYHZ zzzM+X!&?AQT`60g7@1=d_X%G5&$j@@dvREA)`tL^+tS3C08G#tVB{2FVWXDY z+=lQahs*}ReFD-n0C>!@Gu5pCK!lUpT6Od~bPQ>cSy*sxKtAhs5iYR+@j>gNvQ+5; zMD54|h!Me#_fFA-d|RjK$ViqI@m{L~P+qNE12mGz8e;@N3!Peyf05wFB%LJe_xd?N zyt{b}P+qR~qILv;2@U9U9Q=An(n&&q3CQA3^SriRzXE8qBhlg5-XAfXC-?wX_DitV Sx@(I70000>7h^n literal 306 zcmV-20nPr2P)ZuTM51Bo!nq08_(Q76D=;k^+V;wB1HQAaG3}1Xe*Zn4tm$KY~mcC}lcT z4DVeG2;?+RAbJ<@<;%I}aN~GW5%44=Yyn&V{|_+eP5_~$3ksaxmxhD~0|E2oBFUliLqVfrCKjeHH-_T@Zcv zm92KKx!UZu)?1$PlWa{>0iftZ5yvJwOMB&Gxtm|VimECNYn%QSTQMBEs1w=jA|1>g zik_ z)lEW`0fZD&p zj}riqU+zmzm0y8q5!eAm7Oup3Tq-IFg=egbKz6u{}z*}$sVJ%DH$CF`Di39x;% zb2VQ|GoFTv=k;IA*@z0Z_8LuqG*TG_J+FTP$W*2Q$}ndmDnRD{epxC#0RvzF41fVJ d00zJSd;!<-UH<0W$}Io@002ovPDHLkV1g|qo2&o; literal 321 zcmV-H0lxl;P)_JtVH8KT$4)jtEQOw6f=yhwAYXBu5P=;v4=4Q4v9^yOv@DEAi7jFZ!b@5DI$tr zWW4DewtW$uc=NgvHV&@czhQ&ssb8-h+UKmOf&u8)Vq^J9OzS2O`%zEV{r&7A%|%52 zJpKjf5QRgAGPGeo>gm8g$R4v?gvuCz7DtZYw$7VpI`BJ_U;w&EtlhcZ?Rs;%laA7f zFaW){xpWND$VN%|mz{l#T38JlMQTebUh`Y9w55aTU;qk1riJn8Q)b`{{3qZGea2nu T`7RdD00000NkvXXu0mjffVz%Q diff --git a/src/common/tex/corner_nw.png b/src/common/tex/corner_nw.png index 9382c872d3ab7280e8bfa24228e4a0cfa5731e81..1e0422df4b3bd659ed77f6c16892583b1cdcf4db 100644 GIT binary patch literal 276 zcmV+v0qg#WP)a9G43s)fVMkYEqu({w>grF{tx)e%qRdfGxRq`j&3}zkaVTt&EqG z8$FilGu#0<00-az9DoCG006qT+b=iU8?eM_843XM5_ycO|8xn6aExH zw7S?`p#kjH%uefg${8WN5x$ zbZ~gWCH4pZ;BSJRV~}RlvKt8?z}_#lbOI8K#-xVJMEjE`6N|>2DmIz+ietQ-R7flu zbFTOn>s=nk8z?157rwDXZ}Tu(K`DtKu%ilzsr{5i>mCEp4F?6R$tqc<+ACf&dkljf ny90>H*4Kw96YUp=ZwvsQccNPNU`U9t00000NkvXXu0mjf6?bh zm+j8(41zXn2w=7}4c+6v`OnNZGh*XNWtDb^x@}F(AIi zja0)YfR%cM(VkSSbbcvk0+e=2t0!J{8=xwx@vrgJ6(rC~D*(Ib8c|<90d$`STL9&9 z;idJ^G60&L6*bQMg9;J|Ll^+^Z5Y`L&Kf{rIe!n(>I^n3mz4mZdFq3Ynz%qh#FD*G z0ysr?9U!Olxi9=f0I%kmbzK+CLIo4Ogx{ex5r9*0EZGaM02&8(M_$b{nTrz`qcxGU z=vn}>8(uW_Z;z)cz{js00oZxll7sMuS*YMop5af3(LC7?mF3!dQy2IY5k?fqn7OMU ri4bBo{((#&6UYQIflMG1$OPU2*RpT%E32Gh00000NkvXXu0mjfY@49! literal 346 zcmV-g0j2(lP)8K7yytU zq27nNoRb^=vUeYMQnz{0)L4xeQKTz_pQu#5Ov!nkog10IB+!k&eAT6|g`_ sz zPiRzE6vlt|{hjH=jDKj%A`(km2W`|!5UQ?(QV|Ta*iue-psq##l1I9HO@elOu)04#hb(Z&iTG` z?l~8%b$7eWmsMdo^9K3|6ZD!O`jE@EW-@}iIOJkN3d_Rl}a)hOAAfyHe+14_;ISIiZ0gy_i0GP;4P?(+upgCBA zGSa1s`gZL2t4w4hhysTz=F(lb4A}PZU9I8m!Z?FBhX8n`t&?4E?*iaR?-2lYyxzsj zof`o7bnrCcOa*{%*Xq{It`8Y+(ujrcKb`_$O-qKpLwx{ry|x2@ z&JCUXHgO$*Geh5y_O&$QcKK3bRu16^3Z~u{gakFOVmo4!UV@=JR{?mp|3h}K-3dVE zr56DB{A!j{KV<;U`W@Zx&&`S;=MK!?ilG~la1&6~Edqf;%N0vCx1Nr6P)ZR^MSrj$ z7%2WzJPa^7GF5jet^T4O_-cl3)$fD$-=);zdbj$2ISt^2?#d`vnzwg>#cj9*4S$;5 dx2N@Wd=DG=U+oZ!!ejsd002ovPDHLkV1j9=#8dzP literal 841 zcmV-P1GfB$P)svrpBrUsLMAc#vr zaHn>WViqL<|1LzSSPW8%4K+ziGm>buCT1p+%;e2`_qdoS4VguyJ#aXSd-?A7eVlVH z0QC1;t~E9=gh33$`0bDM_GY$a9~VQ>dw*3J#u(I7t}oTTT>NHgib0G3bU$~`K)$Da zu&}e}#2Qe&I#wcR>Z#0@lWJ*Ycy9FU+tT%P-wU1lx<2W+qliy^0`fn#D#jZ3_e_~T zCl+3G*_*OMg`T`m;}kp&wz4x~5D`=ruUqX%)6^Gw@~q5ehg_HscZmqPHc;LCtYX9nVLsgDR8?uAQ$&c<27z%r+xsLv+qyY3|1+oNCdjx5X_Cm7So8G#-XOL@92(B2utl<>1UBH0wMEYUHc7s{ox{%` zrc^5N;KL7b@80|Pdgc_-dcx;-XF2rpjl6NNgOU4l%w}2u{u|C%KKSWlDsh$Jw}zSc z;YY?sPV&q01x`$kQ4De{W}qGj#hRy3_k`L6+|tQjJz1Aw@~>%L8GoIz>91(NYdarJ zk8yV+Yb20F0`p&*z$Q3J z)Nw^MiMisA1d`a$no6l^YL{z6Zeje~aTnR0ak+lY>uX{B-0|zaV4Ek`@eB9|{ T)6L1300000NkvXXu0mjfM2nk= diff --git a/src/common/tex/head_n.png b/src/common/tex/head_n.png index 89772e89e4a81ef9c67da175abf569dd7b2c436e..a2f3349ca50181c5b0f3e0e62f3064504acba853 100644 GIT binary patch literal 1129 zcmV-v1eW`WP)X)S)xh2mt;h0+#` zQK(d1h+3Et6a;BY)ubzHL|RExXvKiK7>!BW=_E6m_i91r{YHXiL?vQeY}4MfKg|?={a5(0GB1sWRI3-Uj%tXXqe6 zB9TzZWKx%A2T;`<_&>2AiiA!mfTSe7hffUb28h&KM*+}Uym?m#tE8H-FfK6!vmpwA zoy#8q80i^)w%mTD13IAy2D(r7-*3dCV*uLmKkB)j7LnC4rqQ5X0IR*x1Srnr21a^^ z-(!))Yc7EqHsahR=~B1sS5be+DcCN6C^4&As0Kl__JebBg^=zSx>AXw0IB$(EkW!i zS_?p0#C_0p*>LlVh`-MOtu@OcJQ+$1L#d|4!?neL=~%wM5xm6{l_-jy4M5ScF_phHVupX- zJzcS+0$Rjf)`9CYujdCPesk8z(-Cb0;P#ddri)nsF6I6Jpuwyw$+DyauC7R6wIxA0 zN|Cz-0Pb$RgToID0MIP;rFjQ090TC<-$!XQ>tK~~tmr^!#0v7y8~5~){cRe6=U&^x zuBUeb@N&P%25h*)vo?WsLFsKIR0Hiv*FX)5xaqpPJIEuryqZU z5%c>W>FovJruud+1{*SFL|4ua0cw*0jV2OkKV#FirngjehMXqQ%Lm1|$%_kc_9|M*6X#s{ot7**B(Q#uk8xV`zZOI&b{) z((?dxoSml790Q=Swbi$z)WXPvbEqPI%68|SDYIan4Tsb5lt;xL5Py+-iuX-JZ4J7L z0A9pW7dqPnVAFV(Qg!A!RU^|{+W=Nm%mSF7nf+|^@!>0_U;`=ET!JO5;_j1sj{wAO zY}^A-xIBjr3e7|BOvw7b{8CE-+F#$@DL-~L3qV8O6|=oT0Gw=o3}Ck4^={wZmIFwV z)FIoo#X$H!p%W6;?wh{dp8zn07jw;46TsFpQ@nC)68t+JfCGIS_~FhL0P1o@*AvnJ z`TVthfbX|`I8(ajrM${o3X3Lpf4z6TYOyu|wEkT%4h@}u5TJG5jR3s+=&k2YY`NtM zK=U>4&*K{pPb{>jB|LHEs5uEL5SGv3OJ>zb_#2wRWcZw`dONM?K)J#A;DkRv4F}%( vbpd-{?P$~!gZ&zyi}Skt{kI&}T!Mc9FU^)L_Yn)M00000NkvXXu0mjfoxm9O literal 918 zcmV;H18Mw;P)KZtFNorccrxIFQVYf? zSoB5l#*CmKN-se(eba!b*c3!6=u4y;lXQ}~oU`}xA(@MlM*p|7_g>%m*IIiop-WLI z^2&S5cI3#|9xofd>{2(3QxF6IMa2^p-^^>`Ui*62tR)5U1b<5(NwxOY%&w`&Td^IW#s(?6kz->!yiDJ$rdFMGDk6q+ zNl{Q64)y!Zh3fds=+v97>UzUE@x2R99ijNV zbi)CbfBTDl`yb+~>1m$3_Cc=7ZKrMyV96ogbQ174O3Gv{$^`ZyoF_df5IOCZ7p zxgippZr-HclocfqpM|8#cMCtWf8WDcO}TNV!qV82GXBHOF{)z z5Iixw@Z&rDwE8OtMt5`Z{5=BVsziL>NqQ)AV%3V{BH5O|VfswP#e8&6Y?n}-2 zzV8>p&<`_#=jTFW`aGAe)IY8@tGmCwXKJ}fQIVc`*i~df(2*~nxWsG?ug?Ej_g;5>h^U&OS?t+MJ;B+f0MgKc(h-1@ws@>ZBhQ_gpCgy}P sJpamR1MvBtew~{b(_v0(YeM(xA6Z3s{C|aZ9RL6T07*qoM6N<$f_ZqyjsO4v diff --git a/src/common/tex/head_s.png b/src/common/tex/head_s.png index 6b97cccf93f12d09e4279e12890c7e18532d1671..33ac95e0bff631ef52414627f934143193121c6a 100644 GIT binary patch literal 1120 zcmV-m1fTnfP)%Q>AtPSwsKP9duT?-&tsu~9ZA`!y^mE83TK-jWDu)(8phFFCha`CT&=>$+SKGL_=XHbm z-#N3RQil5_1SS|27<;EZwy7MHw?wOF)7gwg%cUT6D-kWO5FOP}o0)Rpi9I}?cmRO$ zFDKa2)dfKJBaa#5^#aveK{jZx0D+Jm&>vU28JlWaJ#HSMg?AtrQ?tOa! zIDYga0NTzSMO{8`9K@2K>xZG(R_nK705lrM6hHZD34q!1JkM_50YLxmS9mR*iIh!H zmy2__QCrr!;oxg1t;qm@B?enbF9PuH&*L2K83N$*u*FW092i*5ox!M8Ko7j81Pa^5M7nideY8HCJ)eedgzQ3Z|?`FR-y?ba?>N*NVGP^ zVYeAGRSEvcUETCkv?d}}$nq3qnO?@LKB5+@{Qwe`y9$*Ok+m_V(E~h-I%rTR z9RoPK?c+C>`)>%KCh5tvU3&}mof_N;V8>(ctE$=zfYS9KG`iMk#IY{vKpg=%vu*4k mfaiIZ@B4a5JCfU;g8u+f{H4oL_}K~o000057m6Q?H;A7I(I6$ZAm~PTdD4wo#o7c4 zMWNb-@mS=X!y;u^Td>&Z4{<-t6{@*0eQxMSl@O1$Q2m~n~o4!>I zr$$Q`LcMSQ(vDKwI4WJ~b|z*SO09+V z@#BB+g(OL8QItesY?Efh+_uW(GtXvIg@ql*ri>>e^S`p84eS*oiluNtLowakUY( zQd%ZXRrQ^JwN#ifMu7|nnurt;D5_nKoZ;;Dtb=!b^4Ses)u#{w(!i8hW&vpxM|96` zHN;5_5e9VsoQTMY>md`V=gLQSmx4-QAwmWs6cE|+(cL8(;_Vz-6^oS{D2KcD?Z?Q) zG*%{Gf&5zqm;s{hIEiB`CX?D4#^D117TuttdJ3%*(Uok06~^BCCvjiy4jekMfO8`w z$dt!`Vi8D^tI*D#kVsFy>azhtED5%l0v_Blio-`f!)vcjVK$$~@Z85p-#rR$wE)#u z2GUA41PTO(H0rj6^WiFXj!)qEmtMfjZUqp*Kg}u}Z6G2T^wf0$QivqMJ3k*naodeJ za`0Kan?Cuxx(9 z{lLz)vk3Agi&}gMf7hxoN~7T92Es^haC(V}$cWQx0Sm(+gOuYyAe$=NaLOu_7(nyd zL+^qH4uJq+v6i`h6-Xqm%1)cH(=b;^LF`1jhytv`tf1(h$Y z0)PNZ9SQOgw-b<2T4WQC0wI7+A)^3%xLon0N>EXNbS#t4o_Oly&bd=@E1D!nL#K_} za6befP2?1_CR%MATpT|z=ZU9+N?`kX=#$UxysN)|Z1xj-x&0&rOHhP9s6a>)lTj;4 nqy5(6qX%C1JWu)L_j3LN+CiE=gN-CN00000NkvXXu0mjf8EvTH diff --git a/src/common/tex/head_w.png b/src/common/tex/head_w.png index 2878d2970e9730da49d0353f0f3b30a724affb10..896661ff0ae8fa2d3055568caf011b528788bbf9 100644 GIT binary patch literal 878 zcmV-!1CjiRP)z z&udgy6vsdJzB}(_CNmQ?F^xYUN+R(Woo;M#QBf#F3W}ia%(`3ufIAC1E;8dUCS%O}diQj3-y0{0kTGp$z_XZn@4m}D_k6$KbMCni z+u5@o8)YrN^oBWoizz0fO-y0?kByB1^lV9vD@`AOwRT+&w06sjrBr#oavk8=uL}#Y zUlmW>c_#N|dN?x)klod1lox>`vaSuFoC^spOZid*;N_$8)Z@#4d`(wSFm&SJIe@`K zL(`l0^Z{T)+h_sjKQcjk+5o(`U!r(xaRR_3wJDx#+DHVwmGx<$)xt2e zkuS=5=eOe&m>?63#4k&gG611i5)LhT{gnGC!YVn&FYPcTk@B^|Px~Wu1p?K2)y1Mv zr}usi!1l}#bH#Z8X8*YfK#vJfakv6_(X)NGG?hqXW^-zj0{CwH8*+pD-|qAMtvJb4~~+~9udwgG@{QP!!k;{ePp&U5wAbpX8E zRf?A$0FXXB2tfa-VWbwNY#YMVN0S2_O%7n|HHwW6Iq2^Q}NM%Pa;5tZp79tDsV@-tvK4G zYTv1PcOHQ`T?62dA2dbiDNSfyxCzgt-i^QSAZZt3_T#NQxt0f@H`fn9&+ZJi8bPGi z4u$#Zm+t|X{xC_mUV*1K{s)rCKEv@nP0h7OZ{h z|6^+B7av)G!{eW*i36tr*p~T#KMVH&n7MlefLHaEW+fEFrCTIO076-F=W4_yfU1>$}_abxy1*Wo8c>plr4eSoQ z006O~RoqNQ9Z_gk*NHcjzLw&ru|x+~?PcvDU4g*Lla&hqrF^;Blu9U#*8%C4-}Tt) zrZo0z7O&l%0Wg8Lt5&LP*u5b3+i?myel>e-@9XXOUnLDfSxaQX4FCWD07*qoM6N<$ Eg3Se(eE7j`}bRG?X>~meG#b%_u>^eRrxv z@X}acY!@5XI%k(I{Blml-+c1a*fZnj_Z@3ur4=Cm)2pLFWUGsF2puBe9?N z9YIb!_BxN$$GNvF5{g_k} zNQJVK6RHt+hv8h&>9@|%8aqyI1*MggR`T}bYrJvz6*lBH-5=LjI`=a-=dL4BcLJg> zRYtLVK-5Uk00QR5t4CkN|1oZTv4j^+=i&;z#SWT})yx@&igMOY9RVl2bnpB7yusd(seADJIWh$)x zuuAN$_x>0SqldDP;pQqLxErP2ZPZrHwrj3p?#^Ii@TYcfbZ6R&9hF3sNa2BksD&n4 z$gYkQAq~2IZ9}^^x-)TkZhkgq;ozX%yXW<_JU2gk-xus==01J_{{psdMa@NE@ksyx N002ovPDHLkV1fZQYtjG! diff --git a/src/common/tex/tail_e.png b/src/common/tex/tail_e.png index 1cbeb5241f16cad763ba2486f0534b73b688c1c9..d9e49d5ba9693c7f17e2a8c2542e0547a8f9d299 100644 GIT binary patch literal 768 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 Rj`08h002ovPDHLkV1h(z?;QXD literal 794 zcmV+#1LgdQP)6~_6vuz(+$+0kWkN!2h`Tf?AyYm<3w9wxw@%eqC>^@CpTOu7v}=db58$a=2a!n7 zjwXpi1;*f#5JIsBCrFOu_wKv@4o|Wr*+b6sc=w(EIrslN=h@cdt=RvvpFaQn>mOHI zt3MaR^UB-;?-9zXR@PQSF*^V0@9+NjW$W=)^o^4Z0Z8msWtB1PeZ&ATtSYN4u?Mhm zvLQnlhQ9dit99ZBhh}y|m4so2k(DvKriPFOGf8hrlY4)C|K|z997B>Uyd0mBbz}_( zBuS*4h%q9Gth}rzYdB;bg_q+~NroW@Xz#YY-{~h{-(w&{4j#Ds$z2|N_JI4J-lw_R zB4Bw3uLnP-+@IX-%oK4PZ zQC$z@QV?h}1Ec3523{S%%AJugWTA!v1`2eRMgghM|YQKD;qr{pir6ud9^I%;rZ0A#0fBluw%HXZN~4bl=W$ z7dphyj&{8Jx;r#+;4vVS7sP74>@i^Cp!>Qz+|iCRHxWMnaj(~NI%ME!y#=&<}ZS|HbYIACB?Fnbo3!I1gx5QV>)T^q;5C`KWI(6~S#i1-ai6bLBjKom$u;vskj!(&iV(2$NNFb_deB18kB zh#(2^dUt0i?yl`%gCP7kFx~FX-Z^K^%-nm0ziEyDbRKo2OIHE(uL780@<-O|bfmqs zDgf(8)&*eU!-B8eU&((D07u#@Z3S3=RRma?9AMOzpWLYf%+zOEdVmw`UkSd=&;}k8 zto^(l0lcf|B3s#M0Jr=~@p&1M%i(WdjB1q#Ksud{0(5V7^=0>F2=Mr^O$t!jjb{($ zElth@3i%}fqUgGrodOW26&;A{02+rRd+t~o6tc{4_ndtz!PV#O!|_gfHaPf209QQ^ z5?Gs`)QZjlNWswZoQNnakUL3|ge-~}KqMka8Be5)J^c(^dwTB{fL7(Ur*;9rb33QC z_%I_H;^288WvOAg8Gx!9!10CS(rh#XD^c0n*%E-a@81H@>-SI@=Nr4*U4Y7=gAdb> zy#f|kk{Ljr$gm*Y1wcg9+!Ua7w50&Y=8w_O`UBVbxp@F;)fxb+uUBy&Z=R`35WDym zK)Tt_@`AM>6(rMT9KN0QlVd9Dp2Z4nYXSQ4Bzur31gR27F|WO<#h&{3HlQsLeO;-azOE zW@lzg2awA(WL>i5)HT?`29{SnIprGaG_v*m$fu1|P9RSu|uQ6K>8BkHSd)qKl z1C!VT#NsjwP-#?Stcaf*UJM#%!W=*b09A)c+}z$A0xaH_#W-mOurDMK(H8*er(W*a z#8F)K&aP4H_Em=5MO91L2Aj>dm^0!yWiBR zTn9NtNJcBFMs)y*$_zQy|5r{#zkDdob&>1`52bs8{r}$!es#S+el^>x?Z?~k7cX{m Udi$O9mor1L#@@9QYzR}Hb7sD|eBT`Le^{XDPgBj9nf`;jm`*jhyQBSg_OT{LNr(ur{`9yr)Z?9f;*8lP8n3SJ@Va-3Q_2 z0dNoLJ`r1~UnNbaQ=2`S*>AH)&f~{^5&g?wf7tN0zJtnVr1a3;Zz~w5Yk6SVh!S{P z@Xm>dvXbHFPbN3=N&BIQPs8kE&>cM9x5}<78(s>D1SP2J+L1g-LI@;4`r?=GzOtUa zj@Cbk;vZQ*->_^D5XJG<@#1b~erxlV)<^3ynGR15_2Q2g%$qqTJ4-4@__x*DZ~fT5 znuEaW&P%OD=@F7-9&Y1z$98k$CQa7>uyK8Zey`8Y^Bp42+iamYY!8~Q)Lj!@1jqzg zkR_#vP_OE-K3b<~o5hF%nzmukA21q>C|(v-17^r1GLX(rmq{nlbsfj^W2#&c5d=X_ znYL>=o*!c};*<>eG8ziuIJI*hAqeWGW_NFwx~)l;&U@&DThIYcU>CCt0+Vqv zKjGQ)XAB2JOvcgth_-82f&pZOhSjK-aVLrs#XVRW3NnPu7Lz
PAUSED @@ -40,14 +40,14 @@ import snake from '../../common/snake.js' import storage from '@system.storage' import best from '../../common/best.js' -const CELL = 20 -const GRID = 16 -const OFFSET = 73 +const CELL = snake.ARENA.cell +const GRID = snake.ARENA +const OFFSET = snake.ARENA.offset export default { private: { snake: [], - food: { left: '73px', top: '73px' }, + food: { left: '57px', top: '57px' }, hasFood: true, endText: 'GAME OVER', scoreText: 'SCORE 0', @@ -195,41 +195,41 @@ export default { width: 466px; height: 466px; } .board { - position: absolute; left: 73px; top: 73px; - width: 320px; height: 320px; + position: absolute; left: 49px; top: 49px; + width: 368px; height: 368px; } .seg { position: absolute; - width: 20px; height: 20px; + width: 32px; height: 32px; } .food { position: absolute; - width: 20px; height: 20px; + width: 32px; height: 32px; } .score { - width: 188px; height: 22px; + width: 152px; height: 20px; color: #d8edce; font-size: 18px; text-align: center; } .best { - width: 188px; height: 22px; + width: 152px; height: 20px; color: #f3bd60; font-size: 17px; text-align: center; } .hud { - position: absolute; left: 139px; top: 24px; - width: 188px; height: 44px; + position: absolute; left: 157px; top: 16px; + width: 152px; height: 40px; flex-direction: column; align-items: center; } .footer { - position: absolute; left: 135px; top: 400px; - width: 196px; height: 40px; + position: absolute; left: 143px; top: 410px; + width: 180px; height: 36px; flex-direction: column; align-items: center; } .modeLabel { - width: 196px; height: 20px; + width: 180px; height: 18px; color: #cee4bd; font-size: 17px; text-align: center; } .hint { - width: 196px; height: 20px; + width: 180px; height: 18px; color: #adb7c6; font-size: 16px; text-align: center; } .overlay { diff --git a/tests/art.cjs b/tests/art.cjs new file mode 100644 index 0000000..704a98d --- /dev/null +++ b/tests/art.cjs @@ -0,0 +1,8 @@ +const assert=require('node:assert/strict'), fs=require('node:fs'), zlib=require('node:zlib') +const a=require('../src/common/snake.js').ARENA +for(const name of fs.readdirSync('src/common/tex').filter(n=>n.endsWith('.png'))) { + const b=fs.readFileSync('src/common/tex/'+name) + const size=name==='board.png'?a.radius*2:a.cell + assert.equal(b.readUInt32BE(16),size,name+' width');assert.equal(b.readUInt32BE(20),size,name+' height') +} +console.log('NATIVE ART DIMENSIONS PASS') diff --git a/tests/layout.cjs b/tests/layout.cjs index e3070f9..38427c6 100644 --- a/tests/layout.cjs +++ b/tests/layout.cjs @@ -8,7 +8,20 @@ function rule(name) { return Object.fromEntries(m[1].split(';').filter(s=>s.includes(':')).map(s=>s.trim().split(/:\s*/))) } function px(r,k) { assert.match(r[k] || '', /^\d+px$/, k); return Number(r[k].slice(0,-2)) } -for (const name of ['board','hud','footer','overlay']) { +const arena = require('../src/common/snake.js').ARENA +assert.equal(px(rule('seg'),'width'),arena.cell) +assert.equal(px(rule('seg'),'height'),arena.cell) +assert.equal(px(rule('food'),'width'),arena.cell) +const board=rule('board') +assert.equal(px(board,'width'),arena.radius*2) +assert.equal(px(board,'height'),arena.radius*2) +assert.equal(px(board,'left')+arena.radius,233) +assert.equal(px(board,'top')+arena.radius,233) +for(const c of arena.cells) for(const name of ['hud','footer']) { + const r=rule(name), x=arena.offset+c.x*arena.cell,y=arena.offset+c.y*arena.cell + assert.ok(x+arena.cell<=px(r,'left') || x>=px(r,'left')+px(r,'width') || y+arena.cell<=px(r,'top') || y>=px(r,'top')+px(r,'height'), 'legal cell never overlaps '+name) +} +for (const name of ['hud','footer','overlay']) { const r = rule(name), x=px(r,'left'), y=px(r,'top'), w=px(r,'width'), h=px(r,'height') const dy = Math.max(Math.abs(y-233), Math.abs(y+h-233)) const chord = 2*Math.sqrt(233*233-dy*dy) diff --git a/tests/make_round_art.py b/tests/make_round_art.py new file mode 100644 index 0000000..9a6752f --- /dev/null +++ b/tests/make_round_art.py @@ -0,0 +1,64 @@ +"""Create circular board and static preview from the real JS geometry and PNG art. +Preview is a composited illustration, NOT a Vela runtime screenshot. +Existing sprite art is nearest-neighbor enlarged to native 32px, not blurred. +""" +import json +from pathlib import Path +import subprocess +from PIL import Image, ImageDraw, ImageFont +ROOT = Path(__file__).resolve().parents[1] +a = json.loads(subprocess.check_output(['node', '-p', "JSON.stringify(require('./src/common/snake.js').ARENA)"], cwd=ROOT)) +tex = ROOT / 'src/common/tex' +for p in tex.glob('*.png'): + if p.name != 'board.png': + img = Image.open(p).convert('RGBA') + if img.size != (a['cell'], a['cell']): + img.resize((a['cell'], a['cell']), Image.Resampling.NEAREST).save(p) +S=4 +size=a['radius']*2 +board=Image.new('RGBA',(size*S,size*S),(0,0,0,0)) +d=ImageDraw.Draw(board) +d.ellipse((0,0,size*S-1,size*S-1),fill='#14251e',outline='#36513c',width=2*S) +local=a['offset']-(233-a['radius']) +for c in a['cells']: + x=(local+c['x']*a['cell'])*S;y=(local+c['y']*a['cell'])*S + d.rectangle((x,y,x+a['cell']*S-1,y+a['cell']*S-1),fill=('#1d3227' if (c['x']+c['y'])%2 else '#20362a')) +board=board.resize((size,size),Image.Resampling.LANCZOS) +board.save(tex/'board.png') +# Read actual CSS text placement/colors instead of inventing a separate layout. +import re +ux=(ROOT/'src/pages/index/index.ux').read_text() +def rule(name): + body=re.search(r'\.'+name+r'\s*\{([^}]+)\}',ux)[1] + return dict(re.findall(r'([\w-]+):\s*([^;]+)',body)) +def px(r,key): return int(r[key].removesuffix('px')) +font=subprocess.check_output(['fc-match','-f','%{file}','sans'],text=True) +canvas=Image.new('RGB',(980,526),'#10131a') +for panel,mask in enumerate([False,True]): + watch=Image.new('RGBA',(466,466),'black');watch.alpha_composite(board,(233-a['radius'],233-a['radius'])) + wd=ImageDraw.Draw(watch) + if mask: + for c in a['cells']: + x=a['offset']+c['x']*a['cell'];y=a['offset']+c['y']*a['cell'] + wd.rectangle((x,y,x+a['cell']-1,y+a['cell']-1),outline='#71967a') + cells=[{'x':7,'y':4},{'x':6,'y':4},{'x':5,'y':4},{'x':5,'y':5},{'x':4,'y':5},{'x':3,'y':5}] + names=json.loads(subprocess.check_output(['node','-p',f"JSON.stringify(require('./src/common/snake.js').renderPieces({json.dumps(cells)},{{x:1,y:0}}))"],cwd=ROOT)) + for c,name in zip(cells,names): + watch.alpha_composite(Image.open(tex/(name+'.png')),(a['offset']+c['x']*a['cell'],a['offset']+c['y']*a['cell'])) + watch.alpha_composite(Image.open(tex/'apple.png'),(a['offset']+8*a['cell'],a['offset']+6*a['cell'])) + for parent,entries in [('hud',[('score','SCORE 3'),('best','BEST 253')]),('footer',[('modeLabel','CLASSIC'),('hint','swipe / tap pause')])]: + pr=rule(parent); y=px(pr,'top') + for cls,text in entries: + r=rule(cls); f=ImageFont.truetype(font,px(r,'font-size')) + wd.text((233,y+px(r,'height')/2),text,font=f,fill=r['color'],anchor='mm') + y+=px(r,'height') + circle=Image.new('L',(466,466));ImageDraw.Draw(circle).ellipse((0,0,465,465),fill=255) + canvas.paste(watch.convert('RGB'),(12+panel*490,12),circle) +cd=ImageDraw.Draw(canvas) +cd.text((20,490),'STATIC ART / GEOMETRY COMPOSITE — NOT WATCH RUNTIME',fill='white',font=ImageFont.truetype(font,16)) +cd.text((510,490),'Legal-cell overlay: 89 cells, 32px tiles',fill='white',font=ImageFont.truetype(font,16)) +canvas.save(ROOT/'tests/preview-0.8-static.png') +print(ROOT/'tests/preview-0.8-static.png') +# Alpha corners prove the replacement board is circular, not a square frame. +assert all(board.getpixel(p)[3]==0 for p in [(0,0),(size-1,0),(0,size-1),(size-1,size-1)]) +print('Circular PNG alpha corners verified; native 32px sprites generated.') diff --git a/tests/page.cjs b/tests/page.cjs index e04c1c3..d546dc3 100644 --- a/tests/page.cjs +++ b/tests/page.cjs @@ -7,13 +7,13 @@ const snake = require('../src/common/snake.js'); const ux = fs.readFileSync('src/pages/index/index.ux', 'utf8'); const body = ux.match(/