Here's the bootstrap prompt that is always provided to the LLM for context:
We are writing a 2048 game in pure HTML and JS.
Our game board HTML will look like this:
```html
<div id=game>
<div class="tile tile-empty"></div>
<div class="tile tile-empty"></div>
<div class="tile tile-1">2</div>
<div class="tile tile-empty"></div>
<div class="tile tile-empty"></div>
<div class="tile tile-2">4</div>
<div class="tile tile-empty"></div>
<div class="tile tile-empty"></div>
<div class="tile tile-empty"></div>
<div class="tile tile-empty"></div>
<div class="tile tile-empty"></div>
<div class="tile tile-empty"></div>
<div class="tile tile-empty"></div>
<div class="tile tile-empty"></div>
<div class="tile tile-empty"></div>
<div class="tile tile-empty"></div>
</div>
```
Our game state representation uses a simple array of 16 integers.
We'll use the log of the tile value, for example here is our sample board above:
```js
[0,0,1,0,0,2,0,0,0,0,0,0,0,0,0,0]
```
We can illustrate the relationship between array indices and positions by the following grid:
```
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
```
Thus in the example board above the 2 and 4 tiles are at index 2 and 5.
We also use this log representations for things like CSS class names.
So "tile-1" means the tile with value 2^1, and "tile-17" is the highest-valued tile possible in the game.
This keeps our CSS and HTML a bit shorter and we only print the big numbers where necessary.
To communicate between the input layer and game implementation, we use "u", "d", "l", "r" to identify the moves that the player can make in the game.
Code we already have:
- setup_game - returns initial random game representation
- board - takes game representation into HTML string
- game_update - takes game representation and move into new representation
- handle_move_4cell - helper for game_update, handles a row/col vector of 4
- add_new_tile - helper for game_update, handles adding a new tile
Reply with "OK".
The CSS prompt:
Here we will be writing the CSS.
Write all CSS on single lines in the form "selector { rule ; rule }" with all rules for a selector on a single line, separated by semicolon, and with no semicolon after the last rule before the closing curly brace.
Notes:
- We can use flexbox on the body (with column direction) for the basic layout and centering.
- Use #bbada0 for the page background, #776e65 for the h1 color.
- Center the h1 and give it some vertical space; a 60px font looks nice.
- We use grid for the 4-by-4 #game layout, with repeat (4, 1fr) / repeat (4, 1fr).
- The div#game should be 500px square.
- Give us a nice vertical spacing around the #game itself and center it horizontally.
- The text should be #81a8b5 for the 2 and 4 tiles, and #f9f6f2 for all others. (set the default color on the .tile class and then only special case -1 and -2).
- background colors for the tiles:
```
empty: #cdc1b4
tiles 1 through 11:
#eee4da
#ede0c8
#f2b179
#f59563
#f67c5f
#f65e3b
#edcf72
#edcc61
#edc850
#edc53f
#edc22e
12--17: #3c3a32
```
- font-sizes:
- 1--9: 55px
- 10--13: 45px
- 14--16: 36px
- 17: 30px
Everything else.
Note that you can read this code and understand everything there is to know about how to write a playable version of 2048. Unlike the comments in the typical codebase, you know that everything here is relevant, current, and sufficient.
/*
In setup_game we initialize and return the game state for a new game.
There are two random tiles in the initial game state.
First we select two random distinct indices for the tiles, and then for each we pick a 2 with probability .9 or a 4 with probability .1.
*/
/* board()
In board() we get our game representation and return an HTML string containing div#game.
We only return the contents of the div (i.e. the inner HTML), which is just the 16 div tiles.
*/
/* #game_update
In game_update() we take a board and a direction, and perform the update according to the rules of 2048, specifically:
1. Tiles slide in the direction of the move as far as they can until stopped by another tile or the edge of the board.
2. Tiles which are adjacent in the direction of the move and have the same value are then merged.
3. When multiple merges are possible, the rules are:
- A merge which is furthest in the direction of motion is prioritized.
For example, in a row [2 2 2 0] moving to the left, the result would be [4 2 0 0], not [2 4 0 0].
- When both pairs in a row can merge, both merges are made, e.g. [2 2 2 2] -> [4 4 0 0] and [2 2 4 4] -> [4 8 0 0].
In game_update we extract each row or column in a particular way from the board state which is passed in.
Then we call handle_move_4cell() with a length-4 array of those values, and that function always handles the transformation as if it was a row moving to the left.
When we merge, since our board stores exponents (i.e. an 8 tile is stored as 3) and not tile values directly, we just add one to the exponent, rather than doubling it as we would do if we stored the actual power of two.
We can see that, for example, if the move is "d", we will call handle_move_4cell() with the array values from the board at indices [12 8 4 0], [13 9 5 1], and so on for all four columns.
Instead of doing arithmetic with rows and cols, we just directly include a nested structure of ints between 0 and 15 that maps each of the four moves onto the correct indices, as this is the easiest and cleanest way to write the code.
We handle all four directions analogously.
Below we just write handle_move_4cell and game_update.
At the end of game_update we call add_new_tile, which we write in the next block, which takes the old and new array and returns the next game state.
This function takes the board position and adds empty tiles, and it also handles detecting when the game is over.
*/
/*
In add_new_tile() we get two boards in the usual format.
First we compare the two boards.
These are the board before and after a move.
If the board didn't change we just return the second board.
We find all the empty tiles, pick one uniformly at random, and add a new tile there (that is, actually a 1 in the board representation).
(Note that if the move did something, there will always be at least one empty spot.)
The new tile will be 2 or 4 with odds 9 : 1, just as in the initial game setup.
*/
/* game state
Here we call setup_game and put the game state in a global variable.
We call board() and update the inner HTML of div#game.
(We are brought in by a script tag at the end of the body, so the div is already there.)
We handle keyboard input (arrow keys) and update the game state and the HTML.
*/