Using drawing tricks

The last class we finished out our snake game, so it now properly adds apples in random places, and runs until the game is over. If you haven't yet, you can copy the code I posted in discord and play around with it. I encourage you to change some parameters to the code, to see how nice it is when you build your game based on variables and not constants. For instance, you can change the board size, or the square size, or the number of apples, or any other pieces that you want. You can even build a menu system to set these values!

But one thing that was pointed out as the snake grew is how hard it is to see the path of the snake when it is occuping many adjacent squares. The connections of the snake are not visible to the person, you have to kind of remember where you have gone. This is not as good as other snake games which show the path more clearly.

So how do we fix this? Sometimes, instead of thinking about drawing the whole thing, we can think about drawing it in pieces. Let's look at a typical snake game being played with our current drawing code:

We can see where the head is, but where is the tail? How will it move from here on out? What we need is a border around the snake. Well, we can put a border around each of the tail pieces and the head piece by just making the pieces a tiny bit smaller. Here is the same state drawn with squares that have squares that are 8 pixels smaller (4 pixels gap on each side):

Well, this isn't much better, because now it just looks like a snake with gaps! But now, all we need to do is fill in the gaps! Given 2 tail pieces, we can check the difference between them, then we can tell if the new pieces is one to the right, up, down, or left. And fill in that 8 pixel rectangle. Here is the same state with the gaps filled in with blue rectangles:

Hey, now we can see the snake's form! Though it still looks awfully segmented because of the different coloring. If we change the color of the gaps to green instead of blue, then we get a great picture:

Immediately, we can now see how the snake has moved. And we didn't have to change much!

Fixing the drawing code

In this homework, we are going to implement the final version of the snake drawing modes above. To do this, we start by drawing a square that is 8 pixels smaller than the square size. But how do we know how to draw gaps? We just need for the drawing code to see where the last square was drawn (whether it was a head or a tail piece isn't important). So we need to accept an additional 2 parameters to our tail piece draw function, the x and y location of the previous piece. The piece being drawn can now see where the gap is that needs drawing.

Here is a new TailPiece.draw function that now properly draws the tail piece along with the gap between it and the previous piece:

    void draw(int squaresize, int lastx, int lasty)
    {
        // start out drawing a square slightly smaller than the square size
        int drawx = x * squaresize + 4;
        int drawy = y * squaresize + 4;
        int width = squaresize - 8;
        int height = squaresize - 8;
        if(lastx != x) // tail moved left or right
        {
            width = width + 8; // rectangle is 8 pixels wider
            if(lastx < x) // this tailpiece is to the right of the previous
                drawx = drawx - 8;
        }
        if(lasty != y) // tail moved up or down
        {
            height = height + 8; // rectangle is 8 pixels taller
            if(lasty < y) // this tailpiece is below the previous
                drawy = drawy - 8;
        }
        // this draws the square plus any gap between it and the previous
        DrawRectangle(drawx, drawy, width, height, Colors.GREEN);
    }

How does this work? First we start out expecting to draw the smaller square. But if we notice that the tail piece is to the right or left, we expand the width of the rectangle to draw. If it's to the left of the previous piece, the part we need to fill in is to the right of this piece. So just expanding the width is fine. However, if it's to the right of the previous piece, we need to start drawing 8 pixels to the left (hence the additional check).

I will note that I didn't write this code at first, I played with many different things to come up with it. But the nice trick of just starting with a smaller square and expanding as necessary for connecting makes it very easy code, and efficient code. Most times in code, when you break a problem into smaller and simpler problems, then solving the smaller problems also solves the bigger problem.

One thing to note is if two tail pieces are on top of each other (which can happen if you eat a big apple), you shouldn't worry about drawing any gaps. This handles that situation fine, because the lastx and lasty parameters will be the same as x and y, and no expansion of the small square will be needed!

Your mission

For homework, use the above draw code inside your TailPiece struct. The compiler will immediately complain that you aren't passing in lastx and lasty into the draw function. I want you to change the drawing loop inside the main function to pass that information. The two values should be the x and y of the previous piece. You need to figure out how to do this in your code. I'm not giving any hints or full code this time, you should be able to figure it out from the compiler errors and your previous experience making variables and handling loops. Look at other parts of the code that might do something very similar.

If you need help, please ping me on discord! Note that drawing the snake head might look weird now that we are using smaller squares for the tail. You can change the head drawing code too if you can figure it out! The snake head does not need to draw any gaps because there aren't any previous pieces. It just needs to be drawn smaller.

©2021 Steven Schveighoffer