Snake Game JS: Update the Body Before Trusting the Collision Result

The snake turns Down, then Left before the next tick. It has reversed through its own neck even though each key looked legal when it arrived.

Another version loses when the head enters the tail's old square, although that tail moves away in the same nongrowing step.

A Snake game JS design needs an ordered body, a bounded turn queue, and a collision check that knows whether the tail will move. Food, score, pause, and Reset must follow the same update transaction rather than separate visual guesses.

Use this original ten-by-ten design to specify a native HTML/JavaScript game. Use its fixtures and test matrix to inspect the local HTML example, then verify controls and timing in the intended host.

Snake / one turn per tick
Snake / one turn per tick
Open the full example ↗

Keep the head-to-tail order explicit

Brown University's Snake project discusses body growth, collision, the just-vacated-tail case, reversal restrictions, and terminal updates. Use those concepts without copying its C implementation, assignment tests, artwork, or player-name collection. Brown CS: Snake Project

Coordinates here are zero-based row/column values from 0 through 9. Start with head-to-tail body [(4,4), (4,3), (4,2)], direction Right, and food at (4,5). Distinguish head, body, and food visually and in text.

Set the initial game paused in this design, with an explicit Resume/Start action. Reset restores that same paused state. The example's tick interval is 150 milliseconds, a local design choice rather than a standard speed or tested performance figure.

The first advance Right reaches food at (4,5). Add that new head, retain the old tail, and produce [(4,5), (4,4), (4,3), (4,2)]. Length becomes four and score becomes one. A nongrowing step adds the new head but removes the old tail, preserving length.

Rendering reads the resulting body array. It must not retain a tail graphic after the authoritative array has removed that segment or show a food item still occupying the new head.

Accept only one valid queued turn per tick

While running, compare a requested direction with the committed movement direction. Ignore its immediate opposite. For this starting Right direction, Left is rejected. A repeated request for Right does not occupy the queue.

Accept the first valid perpendicular change for the next tick, then ignore further direction changes until that tick consumes it. If Down is queued and Left arrives before the tick, retain Down. Applying both changes immediately would turn Right → Down → Left before the body moved.

At the tick, apply the queued direction once, clear the queue, and calculate the next head. Keyboard arrows and native direction buttons use this same queue rule. Paused or terminal games do not collect hidden turns to execute later.

Keep direction, queued direction, body, score, and result as distinct state. A button's selected appearance cannot replace the direction the next movement transaction actually uses.

Check collision with the tail's movement in mind

Reject a next head outside rows or columns 0–9 as a wall loss; there is no wraparound. Check body collision before committing movement.

For a nongrowing step, the tail will vacate its old cell. Exclude that one departing cell from the occupied-body collision set. For a growing step, retain the tail and check the whole existing body. Food must never be placed on that body, so a valid food layout cannot manufacture a growing step into an occupied tail cell.

Use a separate tail fixture:

Fixture component Value
Ordered body [(2,2), (2,1), (3,1), (3,2)]
Current direction Right
Queued direction Down
Next head (3,2), the old tail
Growth No; no food at the next head

The move is allowed because the old tail leaves. The resulting ordered body is [(3,2), (2,2), (2,1), (3,1)], still four distinct cells. Entering an occupied non-tail segment would instead lose.

This is an independent test fixture, not a state claimed to follow immediately from the starting-food example. Keep its own body, direction, and nongrowth condition together.

Choose food from the actual free cells

After a food-consuming move, enumerate the board cells not occupied by the updated body and sample a position from that free-cell list. Do not guess random coordinates repeatedly until one happens to work.

If all 100 cells are occupied after growth, the free list is empty. Record a full-board win and stop rather than trying forever to place food. The food and body views must agree about which cells are occupied.

Keep score as the number of food items consumed in this design. An ignored turn, nongrowing move, paused tick, or repeated event cannot increase it. There is no saved score, leaderboard, or player profile.

Pause without a burst of catch-up movement

Provide Pause/Resume and Reset buttons, plus Space to toggle pause only when the game board has focus. Arrow input should likewise stay within the game, leaving typing and normal page keys elsewhere alone.

When the board loses focus or the tab becomes hidden, pause and clear queued direction input. Returning to the tab does not resume automatically. Require explicit Resume, reset the elapsed-time accumulator, and avoid processing a backlog of missed ticks.

Once a wall/body loss or full-board win occurs, future ticks and keys do not advance the body. Reset cancels the old timer/frame transaction or invalidates its generation, restores the original body/food/score-zero/Right direction/empty queue, and leaves the game paused. A callback from before Reset must not move the restored snake.

Test loss, full-board state, pause/blur/visibility, explicit resume, and Reset while an update is pending. Those are actual checks to perform; the fixture arithmetic alone does not prove the browser loop works.

Copy this prompt to specify the native game

Create an original self-contained HTML/CSS/JavaScript 10×10 Snake file with complete native source and a manual test matrix. Cite https://cs.brown.edu/courses/csci0300/2023/assign/projects/project1.html for ordered growth, collision, tail, reversal, and terminal concepts only. Do not copy its C code, tests, art, map compression, or name collection. Include no network calls, uploads, tracking, storage, saved scores, or leaderboard.

Use zero-based row/column coordinates 0–9. Initial head-to-tail body: [(4,4), (4,3), (4,2)]; direction: Right; food: (4,5); score: 0; queued direction: empty; state: paused. Provide explicit Resume/Start. A 150 ms tick is illustrative. The first Right advance eats food: body becomes [(4,5), (4,4), (4,3), (4,2)], length 4, score 1. Nongrowth adds the head and removes the old tail. One authoritative ordered body and state drive rendering.

Only a running, nonterminal game accepts direction requests. Ignore the immediate opposite of the committed direction. Ignore the same direction without consuming the queue. Queue the first valid perpendicular turn for the next tick; ignore further changes until consumed. If Right → Down is queued and Left arrives before the tick, retain Down. Apply the queue once at the tick and clear it. Keyboard and direction buttons share this rule.

A next head outside 0–9 loses without wrapping. Nongrowth collision checks exclude only the departing tail; growth checks retain the entire body. Separate tail fixture: body [(2,2), (2,1), (3,1), (3,2)], moving Right, queued Down, no food at (3,2). Moving into the old tail is allowed; result: [(3,2), (2,2), (2,1), (3,1)]. A non-tail body hit loses. Keep independent fixtures separate.

After growth, sample from enumerated free cells outside the updated body. Food never occupies the snake. An empty free list with all 100 cells occupied means a full-board win, not an infinite random retry. Increment score once per food only. Ignored input, nongrowing movement, and pause do not score. Ignore terminal updates.

Provide native labeled direction, Pause/Resume, and Reset buttons, plus board-focused arrows and Space. Show head position, length, score, and status with non-color cues. Board blur or a hidden tab pauses and clears queued input. Return requires explicit Resume; reset elapsed time to prevent catch-up. Reset cancels or invalidates old timer/frame callbacks and restores the original body, food, Right direction, score 0, empty queue, and paused state. A stale tick cannot advance the reset game.

Test growth/nongrowth; opposite and multiple-input guards; vacating tail versus non-tail collision; walls; food occupancy and full-board win; pause, blur, hidden tab and explicit resume; stale callbacks, Reset and terminal state; keyboard/touch parity. Check the actual article width and 360 px with no page-level overflow. Report performed tests separately from proposed tests. Do not claim a built game, tested timing/accessibility, or hosted-script behavior before verification. Native source remains authoritative. An optional NOS explanation promises no export, automatic updates, or saved progress.

Keep the update fixtures beside the source

Maintain the native source, ordered-body fixtures, and actual test results together. A reviewer should be able to see why the next head can occupy a vacating tail cell and why a second queued turn is ignored before movement.

NOS can save and render HTML. A prepared HTML explanation can present those original body/turn transactions beside the maintained source. Verify hosted ticks, focus, keyboard/touch, and Reset in the actual environment before claiming a working product example.

Explore NOS with the update review. The useful game result keeps the body, food, score, and next movement in agreement.

Keep reading