Step 3¶
Drawing the grid, the fruit, and the snake¶
We will assign some values to define what entity each cell of our grid can represent. For now, we will map these numeric values to draw a unique single character each as the visual representation.
value | entity | character |
---|---|---|
0 | empty | "." |
1 | fruit | "F" |
2 | head | "H" |
3 | body | "B" |
4 | tail | "T" |
A snake has a head and a tail, and a body. We can represent these entities as a sequence of coordinates. The first element is the tail, the last one is the head, and everything in between is the body.
1 |
|
The fruit, however, occupies a single cell - so we just need one set of coordinates.
Every other cell is empty (or unoccupied) and is represented with a ‘.’.
1 |
|
To position the cells, we will use a (UI.Area)
. By default, it is anchored at the top left corner, which means the position at that corner is (float2 0 0)
.
To calculate the position of our cell, we take into account the number of columns, the size in pixels we want our cell to have (cell-size
) and additional x-offset
and y-offset
so that the overall grid is not stuck at the top left corner but slightly moved right and down.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
- The input of this function will be our grid. The parameter
n
is the cell index we want to render. - We want to apply mathematical functions to integral numbers (so-called integers) so we explicitly use the
(int2)
type. %
is the modulo function. We use it to get thex
coordinate, i.e. the index of the column in our grid./
is the division function. We use it to get they
coordinate, i.e. the index of the row in our grid.(UI.Area)
requires a(float2)
type for its:Position
parameter, so we do a conversion using(ToFloat2)
- We defined the size for a single cell to be
cell-size
. Without it, it would have been a 1x1 pixel which we could hardly see. - Finally, we add the offsets so that our grid is more centered. The final value is saved into the
.position
variable. - Now, we get the value of
n
-indexed cell from the.grid
variable that was given as input to therender-area
function. - In that action, we
(Match)
the value of the cell to the corresponding character we have chosen. - Then the matched character is displayed in place of that grid element using
(UI.Label)
.
The above function only deals with a single cell. We want to render all cells. To do so we will slightly modify it to recursively apply to each cell index (from 0
to (- (* grid-cols grid-rows) 1)
, i.e. the number of columns times the number of rows, minus 1 because we start our index at 0
).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
- We pluralized the function since it now renders several cells.
- We test whether
n
equals-1
. Since we want to render cells from0
to(- (* grid-cols grid-rows) 1)
, this is our stopping condition. - If we are in that case, we do nothing (
nil
). - Otherwise, we perform the same code as we did above.
- Finally we do the recursion, which is just calling the same
render-cells
function but with a decremented value forn
.
Populating the grid¶
Before we can draw anything we need to update the grid with the fruit and the snake. To update a sequence at a given index, we can use the (Assoc)
shard. And since the snake is saved as a sequence itself, we need to iterate through all its elements. However, the head, tail, and body are represented by different values, so we will handle them separately.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
- We have already seen
(Take)
andget-index
in step 2. - Assoc lets us update the sequence.
(Slice)
gives a part of a sequence in a range.1
means we start at the second element of the sequence (in other words, we skip1
element), and-1
means we stop at one element before the last (in other words, we skip1
element from the end).(RTake)
is similar to(Take)
, except it starts from the end of the sequence instead of the beginning (i.e. "reverse take").
This new function populate-grid
will take our empty grid as input and return a populated grid. That is why we need a temporary variable inside the function (.tmp-grid
).
Note
Another alternative would have been, to have a single grid and erase the previous positions of the fruit and the snake before updating to their new positions. We find it easier to just update the whole grid at once for this tutorial.
Let's try it out!¶
Let's put into practice all that we have seen so far.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
|