They say that some people just like chaos.
Hi there 👋🏼, I am Graham "loves chaos" TheDev, and this time I am back with another silly internet experiment (and you can skip straight to the game if you want).
It all started so innocently, "can I write a snake game?".
But, as always, the little devil on my shoulder whispered "make it harder"...so I thought "no JavaScript, do it in pure CSS".
Yet again, he chirped up "pffft, still too easy and you have done too much CSS stuff lately, do it in raw, unstyled HTML".
I turned to my other shoulder to hear what the angel thought, hoping for something more sensible.
Then I remembered that the angel is never there for me...
So here it is, snake, in pure HTML (with a little PHP trickery to power it).
That's right!
- No JavaScript
- No Images
- No CSS
- No Cookies
However, I want to be clear (as I do not want to be accused of clickbait), I am rendering this HTML using PHP.
Although it is possible to do it in pure HTML using just files, with no backend language, it would require 640,345,228,142,352,307,046,244,325,015,114,448,670,890,662,773,914,918,117,331,955,996,440,709,549,671,345,290,477,020,322,434,911,210,797,593,280,795,101,545,372,667,251,627,877,890,009,349,763,765,710,326,350,331,533,965,349,868,386,831,339,352,024,373,788,157,786,791,506,311,858,702,618,270,169,819,740,062,983,025,308,591,298,346,162,272,304,558,339,520,759,611,505,302,236,086,810,433,297,255,194,852,674,432,232,438,669,948,422,404,232,599,805,551,610,635,942,376,961,399,231,917,134,063,858,996,537,970,147,827,206,606,320,217,379,472,010,321,356,624,613,809,077,942,304,597,360,699,567,595,836,096,158,715,129,913,822,286,578,579,549,361,617,654,480,453,222,007,825,818,400,848,436,415,591,229,454,275,384,803,558,374,518,022,675,900,061,399,560,145,595,206,127,211,192,918,105,032,491,008,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000 files.
So please excuse the "shortcut" of using PHP generate the next page (and for those of you who spend too much time on tech Twitter, please excuse my use of a "dead" language too! 😱🤣).
Anyway, that is way too much preamble, I know why you are here, you want to see it in action!
Play HTML snake (with caveats!)
Works on Chrome on Desktop PC...is too fast to play on Firefox and any browser on iOS for me...you will see why later in this article.
So basically, play it in Chrome on desktop!
The keys are:
- ALT + I for up,
- ALT + J for left,
- ALT + K for down,
- ALT + L for right,
- ALT + O to start a new game (once you lose).
On Mac it is Control + Option instead of Alt I believe!
If you want to know why the weird keys and not WASD, unfortunately ALT + D is already used in Chrome on Windows, so I had to pick "safe" keys.
One last Warning: One of the tricks we use to make this work will flood your browser history with a lot of URLs...you have been warned!
The game
Sadly this won't run in a codepen, so you will have to play HTML snake on my site.
When you have finished playing, pop back to see what tricks I used to make this work (and share your top score in the comments too!).
Problem 1: Getting a game tick
For a game you generally need to have a "game tick". Each tick is when an action occurs or you compute a new game state and then render the new game state.
But that raises our first issue, how can we possibly have a page automatically update without JavaScript?
Well, to do this in HTML is actually quite simple, we just use <meta http-equiv="refresh"
set to a low value.
So we start out at 0.35 seconds refresh time and then speed that up to 0.2 seconds refresh time as your score climbs.
What "meta refresh" allows us to do is to instruct a browser that once it has loaded the HTML for the page, wait for X seconds and then load another URL.
By setting a low refresh time and then changing the URL we redirect to in each refresh (more on that in a second), we have a way of having the page change all on its own, even if you don't press any buttons!
Here is a quick example of what the format looks like:
<meta http-equiv="refresh" content="[time];url=[url-to-redirect-to]">
Side note: This is where I mentioned earlier that it doesn't work on other browsers. They do not except partial second refresh times and so the refresh is instant, making the game too fast to play.
But meta refresh alone isn't enough to make the game work, we need some way of saving the game state and communicating changes in the snakes direction to the server.
For that we use another straight-forward trick: URL encoded GET parameters.
Problem 2: Managing game state
Because we can't use POST requests or anything like that, we need another mechanism for managing game state between the browser and the server.
At first I was managing the state with several GET parameters, so the URL looked like this:
url?playing=1&x=2&y=6&foodx=3&foody=6&dir=left.
This worked fine right up until the point I needed to store multiple points for the snake (x,y coordinates for each square it occupies).
While I did get it working with some hacky x,y coordinates list and parsing (like snake=1,1,2,1
with the snake being at x=1,y=1 and x=2,y=1) that was messy.
So instead we turn to our good friends: urlencode()
and json_encode()
.
Used together I can take an array (or in this case a multi-dimensional array), convert it to JSON and then convert it to valid characters for a URL.
Let me explain:
Storing complex data in the URL
Here is a sample of the data I use for game state:
$state = array(
'width' => $width,
'height' => $height,
'snake' => array(array('x' => 5, 'y' => 5)),
'food' => array('x' => 7, 'y' => 7),
'direction' => 'right',
'score' => 0,
'state' => true
);
To store that data in the URL we can use the following:
$url = urlencode(json_encode($state));
By JSON encoding our array and then replacing invalid characters with URL friendly ones, that gives us a our state in a URL friendly (although not human friendly!) way:
%7B%22width%22%3A20%2C%22height%22%3A20%2C%22snake%22%3A%5B%7B%22x%22%3A19%2C%22y%22%3A5%7D%5D%2C%22food%22%3A%7B%22x%22%3A4%2C%22y%22%3A11%7D%2C%22direction%22%3A%22right%22%2C%22score%22%3A0%2C%22state%22%3Afalse%7D
And now we have a mechanism to pass the game state down to the browser and back up to the server.
max URL lengths
Those of you who know your stuff will know a gotchya here. There is a maximum URL length!
In Chrome that is 2083 characters.
If you play the game long enough you will actually hit that character limit, as to store our x,y position pairs we use over 10 characters each time.
But this is a silly demo and so I will just say: let me know what error happens if you make your snake long enough!
Oh and in the real world, you shouldn't JSON encode parameters in the URL, let's leave it at that!
We have state and game ticks, now what?
That is it!
Well, almost.
We need to communicate key presses to the server.
Problem 3: Changing the snake direction
This was the final problem (and why we ended up with the game state in the URL), we need to communicate a key press to the server to change the snake direction.
Problem 3a: button presses
Before we can communicate key presses to the server we need some way to actually capture them.
Remember, we have no JS to capture key presses.
We also can't use <button>
elements as those require JS to work.
So all we have left is the humble anchor element (<a>
).
But getting someone to click on anchors would make the game hard to play.
Luckily there is something called accesskey
s built into HTML.
They allow us to assign a character to an anchor and then these can be accessed via a shortcut (ALT + the character in Chrome on Windows).
This gave us our mechanism for allowing keyboard controls, we just needed 4 links (anchors) with different directions as the URLs and then assign an accesskey
to each of them.
Important Note: accesskey
s should be used sparingly, if you pick keys that are used by assistive technology (AT) users then it may interfere.
Problem 3b: direction
Now that we had a way to press keys, and a way to communicate that key press to the server, we need a way to manage presses so they update the snake direction.
Luckily we already had a direction
property in our state object we pass via the URL.
So all we had to do was create 4 different URLs, one for each direction. Then we add these to links and we are done.
$encodedState = urlencode(json_encode($state));
<a href="index.php?state=<?php echo $encodedState; ?>&direction=up" accesskey="i">up (ALT + I)</a><br/>
<a href="index.php?state=<?php echo $encodedState; ?>&direction=left" accesskey="j">left (ALT + J)</a>
<a href="index.php?state=<?php echo $encodedState; ?>&direction=right" accesskey="l">right (ALT + L)</a>
<a href="index.php?state=<?php echo $encodedState; ?>&direction=down" accesskey="k">down (ALT + K)</a>
Now when you press ALT + K for example, the 4th link is clicked and we send the current state + new direction to the server!
Now all is left it is to take that information and compute the next game state.
Game logic
Finally, the last part of the puzzle is a bit of game logic.
For example when generating the food position we need to check it isn't on a tile the snake already occupies, so we have this function:
function generateFoodPosition($width, $height, $snake) {
do {
$food = array(
'x' => rand(0, $width - 1),
'y' => rand(0, $height - 1));
} while (
in_array($food, $snake)
);
return $food;
}
And another function to move the snake
function moveSnake($state) {
$newHead = array('x' => $state['snake'][0]['x'], 'y' => $state['snake'][0]['y']);
// Update snake's head position based on direction
switch ($state['direction']) {
case 'up':
$newHead['y']--;
break;
case 'down':
$newHead['y']++;
break;
case 'left':
$newHead['x']--;
break;
case 'right':
$newHead['x']++;
break;
}
// Check if snake has collided with the wall or itself
if ($newHead['x'] < 0 || $newHead['x'] >= $state['width'] || $newHead['y'] < 0 || $newHead['y'] >= $state['height'] || in_array($newHead, array_slice($state['snake'], 1))) {
$state['state'] = false;
return $state; // Game over
}
// Check if snake has eaten the food
if ($newHead['x'] == $state['food']['x'] && $newHead['y'] == $state['food']['y']) {
$state['score'] += 10;
// Generate new food position
$state['food'] = generateFoodPosition($state['width'], $state['height'], $state['snake']);
} else {
// Remove tail segment
array_pop($state['snake']);
}
// Move snake
array_unshift($state['snake'], $newHead);
return $state;
}
and a loop to build the game board.
for ($y = 0; $y < 20; $y++) {
echo "\r\n";
for ($x = 0; $x < 20; $x++) {
if ($x == $state['food']['x'] && $y == $state['food']['y']) {
echo '🍎';
} elseif (in_array(array('x' => $x, 'y' => $y), $state['snake'])) {
echo '🟩';
}else{
echo '⬜';
}
}
}
But I am not going to cover those in any detail as those are things that you can easily look up (and find much cleaner ways to do), find (better) code other people have written and adapt to your needs.
That's a wrap
So there you have it, we built a game using meta refresh, access keys and a hack to encode complex data in a URL.
Will these things be useful in your day to day? No, probably not.
Will they perhaps save your ass in a weird edge case, got to get this finished, can afford to use a hack to ship a product situation? Possibly.
What? You weren't expecting a useful tutorial from me were you? You should know better by now.
But, with that being said, if you did enjoy this article or by some miracle you learned something new, do drop me a comment below, it really means a lot!
Have a wonderful weekend everyone! 💗
Top comments (56)
Am I missing something? This is more than a little click-baity, since it does not accomplish the title. They do acknowledge the PHP in the beginning of the article, but that still invalidates the promise made in the title
if you believe it is clickbait, then you are right (and thanks for the click and comment), it is in the eye of the beholder.
however, I think you are over-thinking the PHP part of this. All it is doing is simulating the equivalent of all the files that could have done the same job. If there were infinite storage and instant processing time, this could certainly have been done without a backend and just having ever game state stored as a page in a huge folder and the links directing to those pages. 💗
yep, you got me Graham :)
I did like it, overall
As long as you took some enjoyment from it then I am happy. If you had left feeling completely cheated I would have been a little disappointed. 🙏🏼💗
absolutely... made me think a little, which is always a good thing. and I did take some enjoyment from it in the end
I think there was also some PTSD there... we all remember doing this game in various languages
hahaha, sorry to bring up past troubles! 🤣💗
If I'm over-thinking the PHP part then what about the following:
There is a difference between writing the game logic in PHP and actually extracting the game logic as a Finite State Machine in pure HTML.
With the latter, there are challenges that you could skip simply because you wrote the logic in PHP instead of writing a script that extracts the logic as a flat FSM in pure HTML (using whatever language).
Here are the ones I can think of:
If you can do it then you'd have in written a snake in pure HTML (in theory).
There is nothing wrong with naming files with the query part (you need a file system that supports all url characters as part of a file name). There is also a nothing wrong with pre-determined placements of the apple.
For the file name part, your suggestion would require a custom web server so that's just working around the issue. We want to write a snake in pure HTML that works anywhere. So ideally we'd have a state to ID mapping that can be also used as a filename to fix both the filename and the maximum URL length issue.
For the determination issue, there's nothing wrong with with pre-determined placements from a gameplay point of view. However, this is not what the program in the article is doing, even though this is not possible in pure HTML.
Correct on how the URL was going to be handled (if physics hadn't got in the way), I was going to go very simple and just do s010101020103a0206dirl with "s010101020103" being snake positions as "x1,y1,x2,y2,x3,y3" and "a0206" being the apple position x,y and "dirl" being left for a naming convention (although anything will do that is a valid URL obviously). All the other info could have been stored in the HTML such as score.
As for the randomisation of the apples, it is obviously impossible, but one idea I had was to make a "start screen" as a 300 x 80 table that used the following technique to "pseudo-random" the start position.
By changing the
bgcolor
of each<td>
to spell words ("click here to start") and having a different snake starting position and apple start position for each anchor it would have offered 2400 possible (and about 1500 realistic "people might actually click here") random starting positions and apple positions. To a human it would appear random (even if you could technically click the exact same spot and perform the exact same moves to get a repeated game).You could even have a 0 refresh time meta refresh on the start screen with other starting snake and apple positions for each page's table, to multiply the possible starts by 100 or so and increase the pseudo-randomness further.
in the scheme of this project, 2400 times to 240000 times more permutations is nothing 🤷🏼♂️🤣💗
No JS, no CSS, no images... haha, tricked ya, I didn't specifically mention no PHP. Although I did say "pure HTML"... oh, dang... that doesn't work at all
Since its function is not php based (ie it works w/out php), then it does exactly what is said. But if u actually want to use this versus learn from it, then the time it would take to build w/out php is outlandish. His point was to show what can be done with pure html, to that there were no lies told. Even if this was done w/out php, you'd still want to see a demo, thus click bait is inevitable if that's what you want to call it.😆
Thanks, I am glad someone realised why there is PHP and that it doesn't invalidate the fact this can be done entirely with HTML and PHP is there purely for making a working demo vs just theory. 🙏🏼💗
I would call this SSSnake... Server Side Snake.
Haha, Server Side Slithering is my new favourite pastime! 🤣💗
You're a mad, jury is still out on evil, genius and I love it 😅
hahaha, thanks...think? 🤣💗
Only 6e686 files? You only need like 9 extra dimensions1 in the universe and to assign a file to each of its atoms. Just use Hyper Tesseract Markup Language (abbreviated HTML, with the T bolded) and you'll be good to go ⚛️
haha I love the fact that I did the math, and then you did the math on me doing the math! 🤣💗
I really, really thought I'd escaped Snake as a game and played it for the last time ever in a car park in Bristol on a Blackberry in 2009. Apparently not.
hehe, well at least this is not the best experience, so hopefully it will put you off and you never play again! 🤣💗
I'm afraid this is too late as I already clicked on the article because it claims in the title to have written a snake game in "Pure HTML" instead of "PHP/HTML" and we all know here why it makes all the difference.
clickbait.
Then you didn't read the part of why we used PHP, i didnt have enough atoms in the universe to create all the pages required, otherwise it would have been pure HTML! 🤣💗
I didn't, I stopped at that paragraph I quoted, and I already know that writing a snake in pure HTML is challenging, that's precisely the reason why I visited on your article.
Why not updating the title to match what you actually did?
Then if you have no context I will just say thank you for the click and the comments (they really help boost the article) and I can't wait to get you with some more clickbait in the future 🤷🏼♂️🤣
I got the context right when I read the word PHP. You were initially planning to generate all the states as HTML pages and use links/refresh to step the game. But you quickly realised you couldn't do it because the number of states to generate is way too large. So you wrote the game in PHP instead.
There is a much better simpler and highly compressible way to store the state for snake and food. Which will prevent from hitting url limit by massive margin. Css selector inpection, maybe u can inject content into a form, using css, then submit form to same page. Url#anchor, ccs selector inspection.
No js then.
Then it is using CSS...and this is pure HTML? A little confused what you are suggesting as it would break the idea of HTML only?? 💗
html + backend, not just html frontend.
When read HTML only, mean no backend my mind. U can still improve the state. Attemot add 4 snakes, that eat ewah other. U will probly simplify the implementation of state then.
Built 3 iteration of snake as a child in java.
My top score is only 450, who can beat that? 👇🏼
Not the snake game we deserve....but the snake game we need 🫡🫡🫡
This is so, so, so incredible Graham! I especially appreciated right-clicking to view the page source for it and realized I could play the game and cheat by just clicking on each anchor tag to move the snake around without it moving by itself. Absolutely incredible and that reinforces the fact this truly is HTML.
Hehe, nice way to make sure you get the high score, funnily enough I hadn't thought of that! 🤣💗
This is quite impressive. I'm really surprised you didn't use cookies and keep the session management in PHP - This would have ensured users can't cheat (lol) and it would also fix your URL length issue.
It works reasonable well, although the latency is a bit of a pain (game works quickly sometimes, other times not so quickly). This is probably as I'm in Australia.
Because you've kept the PHP stateless, it would probably work quite well in a FaaS environment.
Latency to Australia will certainly make the game slower and less consistent lol!
The reason state is in the URL is because originally I was going to just do this as a series of static files, but then did the calculation on the number of files required.
You are right, we could certainly do it in cookies now that it has a backend, I was trying to keep it as close as possible to HTML only as possible! 💗
Some comments may only be visible to logged-in visitors. Sign in to view all comments.