Chapter 7 · Project · ~22 min read

Cascade

In the last chapter, we picked up while, do-while, for, break, and continue. Loops are the tool that lets a few lines of code do thousands of things, and there's only one good way to feel that for yourself: write a few lines, and watch the screen fill up.

This project is small, even by the standards of Act 1. A single for loop draws a diagonal cascade of colored squares from the top-left corner of the window, as many as will fit, going around a rainbow palette as it goes. Tweak one number, and the cascade changes shape. Add two lines and change a third, and the rainbow starts to flow. The whole program is about 130 lines, and most of them are the setup you already know by heart. The loop is the star.

Project folder: SDL3 Projects/Draw squares — the complete source for this chapter lives here.

In this chapter, we will:

  • Set up a new SDL project, using the checklist from Chapter 3
  • Store a palette of colors in a list, and count them with SDL_arraysize
  • Draw a whole cascade of squares with a single for loop
  • Stop the loop with break as soon as the next square won't fit
  • Go round and round the palette with the modulo operator
  • Make the colors flow, using the time
  • Experiment with the values, and fix the most common mistakes
  • Try an optional AI exercise to extend the project

Let's build it.

Setting Up the Project

The setup is the same as in the last two projects, so here's the checklist again:

  1. Choose File > New > Project, pick Empty Project (the one tagged C++, Windows, and Console), name it Cascade, and click Create.
  2. In Solution Explorer, right-click Source Files, choose Add > New Item, and add a file called main.cpp.
  3. Right-click the project, choose Properties, set the two dropdowns to All Configurations and All Platforms, and then make the four changes:
    • C/C++ > General > Additional Include Directories: C:\SDL3\include
    • C/C++ > Language > C++ Language Standard: ISO C++20 Standard (/std:c++20)
    • Linker > General > Additional Library Directories: C:\SDL3\lib\x64
    • Linker > Input > Additional Dependencies: SDL3.lib
  4. Right-click the project, choose Open Folder in File Explorer, and copy SDL3.dll from C:\SDL3\lib\x64 into that folder, beside main.cpp.

If you made a project template with the tip in Chapter 3, pick it in step 1 instead, and then you only need step 4. Either way, Chapter 1 walks through every step slowly, and its Common Errors section covers anything that goes wrong.

With the project ready, it's time to write the program.

Coding the Game

We'll build this one the same way as the last three. First comes an empty window with a working game loop, and then the cascade, a piece at a time, with a checkpoint whenever there's something new to see. As before, the blocks are shown without the indentation they'll have in your file, and the complete program at the end shows every line where it sits.

The Header Comment and Includes

Type this at the very top of main.cpp:

/*
    Cascade
    The Chapter 7 project from Learning C++ by Building Games

    A single for loop draws a diagonal cascade of colored squares
    from the top-left of the window, going around a palette of nine
    colors, and stops as soon as the next square wouldn't fit. The
    colors shift one place every tenth of a second, so the rainbow
    flows along the diagonal. Escape, or the window's X, quits.
*/

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>

In the preceding code, the multi-line comment describes the program, and the two #include lines bring in SDL and its helper for main, as in every project so far.

The Constants

Now for the numbers that shape the cascade. Add these below the includes, with a blank line in between:

const int   WINDOW_W  = 800;                   // window width in pixels
const int   WINDOW_H  = 600;                   // window height in pixels
const float SQUARE_SZ = 30.0f;                 // each square's width and height
const float PADDING   = 2.0f;                  // the gap between squares
const float STEP      = SQUARE_SZ + PADDING;   // from one corner to the next

const SDL_Color BACKGROUND = { 30, 30, 30, 255 };   // dark gray

In the preceding code, the first two constants are the window's size, as always. The SQUARE_SZ constant is the width and height of each square, a float like Chapter 1's, so it can go straight into an SDL_FRect. Then PADDING is the small gap we'll leave between the squares, and around the first one.

Look at how STEP gets its value. Like Chapter 3's BALL_SIZE, it's worked out from other constants: a square's size plus the gap, 32 pixels in all. That's the distance from one square's top-left corner to the next square's, and because it's worked out rather than typed in, changing SQUARE_SZ or PADDING keeps it right automatically. The last line names the background color, a dark gray, as an SDL_Color, just as in the last two projects.

The Palette

The cascade needs a palette of colors to go around. We could make nine separate color constants, but it's much handier to keep them together in a single list. Add this below the background color, with a blank line in between:

// The palette the cascade goes around, one color per square
const SDL_Color COLORS[] = {
    { 220,  50,  50, 255 },   // red
    { 230, 140,  30, 255 },   // orange
    { 220, 210,  40, 255 },   // yellow
    {  50, 180,  50, 255 },   // green
    {  40, 140, 220, 255 },   // blue
    { 100,  60, 200, 255 },   // indigo
    { 170,  60, 200, 255 },   // violet
    {  50, 200, 180, 255 },   // teal
    { 200,  80, 130, 255 }    // pink
};
const int COLOR_COUNT = SDL_arraysize(COLORS);   // how many colors: 9

In the preceding code, the square brackets after COLORS make it an array: a numbered list of values, all of the same type, kept together under one name. Each item in this list is an SDL_Color in its own curly braces, with commas between the items, and the whole list sits inside one more pair of braces, with a semicolon at the end. We left the square brackets empty, so the compiler counts the items for us.

To reach an item in an array, you put its position in square brackets after the name. As in Chapter 6, positions are counted from 0, so COLORS[0] is the red at the top of the list, COLORS[1] is the orange, and COLORS[8] is the pink at the bottom. That's all we need for now. Arrays get a whole chapter to themselves in Chapter 13.

The last line counts the palette with SDL_arraysize, SDL's helper for counting the items in an array, so COLOR_COUNT is 9. If you add a tenth color to the list later, COLOR_COUNT becomes 10 by itself, just as STEP follows SQUARE_SZ.

Note

As an interesting aside, SDL_arraysize is a shortcut for a classic trick from C. The sizeof operator says how many bytes something takes up in memory. Each SDL_Color is four one-byte parts, so the whole palette is 36 bytes, and sizeof(COLORS) / sizeof(COLORS[0]) works out 36 divided by 4, which is 9. You'll see that line, written out in full, in plenty of older code.

With the palette ready, we can start on main.

main and the SDL Setup

Add main below the palette, leaving a blank line after it, with just return 0; inside for now:

int main(int argc, char* argv[])
{
    return 0;
}

In the preceding code, main has its usual two parameters, and everything else will go inside it, above return 0;.

The setup comes first. Click at the end of the line with main’s opening brace, press Enter, and add this:

// Start SDL, then make the window and the renderer
if (!SDL_Init(SDL_INIT_VIDEO))
{
    SDL_Log("SDL_Init failed: %s", SDL_GetError());
    return 1;
}

SDL_Window* window = SDL_CreateWindow("Cascade", WINDOW_W, WINDOW_H, 0);
if (!window)
{
    SDL_Log("SDL_CreateWindow failed: %s", SDL_GetError());
    SDL_Quit();
    return 1;
}

In the preceding code, we start SDL's video system and create an 800 by 600 window titled Cascade, checking each step and bailing out with a message if it fails. The title is short, so the call to SDL_CreateWindow fits on one line this time.

The renderer and vsync finish the setup. Add this below the window check, with a blank line in between:

SDL_Renderer* renderer = SDL_CreateRenderer(window, nullptr);
if (!renderer)
{
    SDL_Log("SDL_CreateRenderer failed: %s", SDL_GetError());
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 1;
}

// Show each frame in step with the monitor's refresh
SDL_SetRenderVSync(renderer, 1);

In the preceding code, we create the renderer, tidying up the window if that fails, and then turn on vsync, so the program runs in step with the monitor.

The Game Loop

Next comes the game loop, with its event handling already inside. Add it below the vsync line:

bool running = true;
SDL_Event event;

while (running)
{
    // Events: quit on the window's X, or on Escape
    while (SDL_PollEvent(&event))
    {
        if (event.type == SDL_EVENT_QUIT)
        {
            running = false;
        }
        if (event.type == SDL_EVENT_KEY_DOWN &&
            event.key.key == SDLK_ESCAPE)
        {
            running = false;
        }
    }
}

In the preceding code, the running flag keeps the game loop going, and the event loop inside it reads every waiting event, exactly as Chapter 6 explained. Clicking the window's X or pressing Escape sets running to false, and the game loop ends.

Now the drawing. Add this inside the game loop, below the event loop's closing brace, with a blank line in between:

// Draw the frame
SDL_SetRenderDrawColor(renderer, BACKGROUND.r, BACKGROUND.g,
                       BACKGROUND.b, BACKGROUND.a);
SDL_RenderClear(renderer);

SDL_RenderPresent(renderer);

In the preceding code, we set the draw color from the BACKGROUND constant, clear the window with it, and present the frame, leaving a gap between the two for the cascade.

Finally, add the cleanup below the game loop's closing brace, just above return 0;, with a blank line on each side:

// Clean up, in the reverse order we created things
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();

In the preceding code, we destroy the renderer and the window and shut SDL down, in the reverse order we made them.

Checkpoint: Press F5. You should see an empty, dark gray window titled Cascade, and pressing Escape or clicking its X should close it. If it doesn't build, compare your file with the complete program at the end of this section.

The Cascade

From here on, each block goes into a gap in the code you've already typed, as Figure 7.1 shows. This time there are only three slots.

The map of main.cpp after the first checkpoint. The cascade's loop goes in slot A, between clearing the window and presenting it, and the two lines that make it flow go in slots B and C.
Figure 7.1 — The map of main.cpp after the first checkpoint. The cascade's loop goes in slot A, between clearing the window and presenting it, and the two lines that make it flow go in slots B and C.

The cascade is a single for loop, and we'll build it in two parts. First comes the loop itself, which works out where each square goes and stops at the edge of the window. It goes in slot A, between SDL_RenderClear(renderer); and SDL_RenderPresent(renderer);:

// The cascade: squares marching diagonally from the top-left
float x = PADDING;
float y = PADDING;

for (int i = 0; ; i++)
{
    // Stop as soon as the next square would cross either edge
    if (x + SQUARE_SZ > WINDOW_W || y + SQUARE_SZ > WINDOW_H)
        break;

    // Move along the diagonal to the next square's spot
    x += STEP;
    y += STEP;
}

In the preceding code, x and y are the top-left corner of the next square to draw, and they start at PADDING, so the first square sits two pixels in from each edge of the window.

Then comes the loop, and there's something unusual about its first line: the middle part, the condition, is empty. That's perfectly legal. A for loop with no condition has nothing to check before each pass, so it keeps going until something inside it says stop. The counter i counts the squares, 0, 1, 2, and so on, and we'll use it to choose each square's color in a moment.

The first thing the loop does on each pass is ask whether the square it's about to draw would fit. Its right edge would be at x + SQUARE_SZ and its bottom edge at y + SQUARE_SZ, so the if asks whether either one would stick out past the window. If it would, break, from Chapter 6, leaves the loop at once. If it wouldn't, the pass carries on, and at the end of the body, x and y both move along by STEP. So each square is one step to the right of the last one, and one step down, which makes a diagonal line.

The || in the stop test matters. As Figure 7.2 shows, the window is wider than it is tall, so the squares reach the bottom edge long before the right one. The square numbered 18, the 19th square, would cross the bottom edge, so the loop stops after 18 squares. The squares wouldn't cross the right edge until square 25. With &&, the loop would only stop when both edges were crossed, so it would go on to draw seven more squares: the first hanging off the bottom edge, and the other six entirely below the window, where nobody can see them.

Each square is one step right and one step down from the last. The 19th square, numbered 18, would cross the bottom edge, so the loop breaks there, long before the right edge.
Figure 7.2 — Each square is one step right and one step down from the last. The 19th square, numbered 18, would cross the bottom edge, so the loop breaks there, long before the right edge.

Why leave the condition empty, instead of counting to a fixed number of squares? Because the right number depends on the window's size, the squares' size, and the padding, and working it out would mean more arithmetic that could go wrong. Instead, the loop simply asks, square by square, whether the next one fits. Chapter 6 advised using break sparingly, and this is the kind of place where it earns its keep: the loop's natural stopping point is a question about the next square, and the top of the body is the easiest place to ask it.

Warning

A for loop with an empty condition must have its break. Without one, the loop never ends, so the program never gets as far as SDL_RenderPresent, and it never goes back to check for events. The window opens but never draws anything, and after a few seconds, Windows reports that it isn't responding. If that happens, press Shift+F5 to stop it, and check the stop test.

Run it now if you like. You won't see anything new, because the loop works out where each square goes but doesn't draw it yet. The drawing goes inside the loop, between the break; line and the // Move along the diagonal comment, with a blank line on each side:

// Pick this square's color, going around the palette
SDL_Color color = COLORS[i % COLOR_COUNT];
SDL_SetRenderDrawColor(renderer, color.r, color.g,
                       color.b, color.a);

SDL_FRect square = { x, y, SQUARE_SZ, SQUARE_SZ };
SDL_RenderFillRect(renderer, &square);

In the preceding code, the first line picks a color from the palette, using Chapter 2's modulo operator, %, which gives the remainder after dividing. For squares 0 to 8, dividing by 9 leaves the square's own number as the remainder, so they take the colors in order. For square 9, the remainder is 0, so it's red again, square 10 gets orange, and so on. As Figure 7.3 shows, the modulo wraps any count, however big, back around onto the palette, so the index can never run past the end of the list.

The modulo operator wraps the square numbers around the nine-color palette: after 8 comes 0 again. Adding an offset, as we'll do shortly, moves every square one color along.
Figure 7.3 — The modulo operator wraps the square numbers around the nine-color palette: after 8 comes 0 again. Adding an offset, as we'll do shortly, moves every square one color along.

The chosen color is an SDL_Color, so we set the draw color from its four parts, just as we did with the background. Then we build an SDL_FRect at x and y and fill it, exactly as Chapter 1 drew its square.

Checkpoint: Press F5. A diagonal line of 18 squares runs from the top-left corner toward the bottom of the window, going red, orange, yellow, and on around the palette twice, and stopping just short of the bottom edge.

Try it

Check the count for yourself. Put a breakpoint on the break; line and press F5. When Visual Studio pauses, look at i in the Locals window: it's 18, because squares 0 to 17 fit and square 18 wouldn't. Then remove the breakpoint and press F5 to carry on.

That's the cascade drawn. Now let's make it move.

Bringing It to Life

Right now, the cascade is a still picture: the same 18 squares, in the same colors, every frame. Let's make the colors flow along the diagonal, which takes two new lines and one small change. First, the program needs to know when it started. Add this in slot B, between the vsync line and bool running = true;, with a blank line on each side:

// The time the program started, in milliseconds
Uint64 startTime = SDL_GetTicks();

In the preceding code, SDL_GetTicks gives the number of milliseconds since SDL started, as in Chapter 1, and we keep a copy of it in startTime, once, before the game loop begins.

Next, the program works out how far the colors have shifted. This goes in slot C, below SDL_RenderClear(renderer); and just above the // The cascade comment, with a blank line on each side:

// Shift the colors along one place every 100 milliseconds
int offset = static_cast<int>((SDL_GetTicks() - startTime) / 100);

In the preceding code, SDL_GetTicks() - startTime is the number of milliseconds since the program started, and dividing it by 100 turns that into tenths of a second. Both sides of the division are whole numbers, so, as Chapter 2 warned, the fraction is thrown away: offset is 0 for the first tenth of a second, then 1, then 2, and so on, going up by one every 100 milliseconds. The subtraction gives a Uint64, and static_cast, from Chapter 2, turns it into an int, to match i.

Finally, change the line that picks each square's color, just below the // Pick this square's color comment, so that it adds offset to i before the %:

SDL_Color color = COLORS[(i + offset) % COLOR_COUNT];

In the preceding code, square i now takes color number (i + offset) % COLOR_COUNT. While offset is 0, nothing changes. When it goes up to 1, every square takes the color that the square after it had a moment ago, as the second row of Figure 7.3 shows, so the whole rainbow moves one square up the diagonal, toward the top-left corner. The modulo keeps the sum wrapped onto the palette however big offset grows.

Note

As an interesting aside, the integer division is what gives the animation its character. Because offset only changes every 100 milliseconds, the colors hop along one square at a time, like the lights around a theater sign, instead of blending smoothly from one to the next.

Checkpoint: Press F5. The same cascade appears, but now the rainbow flows up the diagonal, one step every tenth of a second, for as long as you care to watch.

That's the last change, and the program is finished.

The Complete Program

Here's the whole file in one piece, with every line at its real indentation. If you've typed every block in the place described, this is exactly what you have:

/*
    Cascade
    The Chapter 7 project from Learning C++ by Building Games

    A single for loop draws a diagonal cascade of colored squares
    from the top-left of the window, going around a palette of nine
    colors, and stops as soon as the next square wouldn't fit. The
    colors shift one place every tenth of a second, so the rainbow
    flows along the diagonal. Escape, or the window's X, quits.
*/

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>

const int   WINDOW_W  = 800;                   // window width in pixels
const int   WINDOW_H  = 600;                   // window height in pixels
const float SQUARE_SZ = 30.0f;                 // each square's width and height
const float PADDING   = 2.0f;                  // the gap between squares
const float STEP      = SQUARE_SZ + PADDING;   // from one corner to the next

const SDL_Color BACKGROUND = { 30, 30, 30, 255 };   // dark gray

// The palette the cascade goes around, one color per square
const SDL_Color COLORS[] = {
    { 220,  50,  50, 255 },   // red
    { 230, 140,  30, 255 },   // orange
    { 220, 210,  40, 255 },   // yellow
    {  50, 180,  50, 255 },   // green
    {  40, 140, 220, 255 },   // blue
    { 100,  60, 200, 255 },   // indigo
    { 170,  60, 200, 255 },   // violet
    {  50, 200, 180, 255 },   // teal
    { 200,  80, 130, 255 }    // pink
};
const int COLOR_COUNT = SDL_arraysize(COLORS);   // how many colors: 9

int main(int argc, char* argv[])
{
    // Start SDL, then make the window and the renderer
    if (!SDL_Init(SDL_INIT_VIDEO))
    {
        SDL_Log("SDL_Init failed: %s", SDL_GetError());
        return 1;
    }

    SDL_Window* window = SDL_CreateWindow("Cascade", WINDOW_W, WINDOW_H, 0);
    if (!window)
    {
        SDL_Log("SDL_CreateWindow failed: %s", SDL_GetError());
        SDL_Quit();
        return 1;
    }

    SDL_Renderer* renderer = SDL_CreateRenderer(window, nullptr);
    if (!renderer)
    {
        SDL_Log("SDL_CreateRenderer failed: %s", SDL_GetError());
        SDL_DestroyWindow(window);
        SDL_Quit();
        return 1;
    }

    // Show each frame in step with the monitor's refresh
    SDL_SetRenderVSync(renderer, 1);

    // The time the program started, in milliseconds
    Uint64 startTime = SDL_GetTicks();

    bool running = true;
    SDL_Event event;

    while (running)
    {
        // Events: quit on the window's X, or on Escape
        while (SDL_PollEvent(&event))
        {
            if (event.type == SDL_EVENT_QUIT)
            {
                running = false;
            }
            if (event.type == SDL_EVENT_KEY_DOWN &&
                event.key.key == SDLK_ESCAPE)
            {
                running = false;
            }
        }

        // Draw the frame
        SDL_SetRenderDrawColor(renderer, BACKGROUND.r, BACKGROUND.g,
                               BACKGROUND.b, BACKGROUND.a);
        SDL_RenderClear(renderer);

        // Shift the colors along one place every 100 milliseconds
        int offset = static_cast<int>((SDL_GetTicks() - startTime) / 100);

        // The cascade: squares marching diagonally from the top-left
        float x = PADDING;
        float y = PADDING;

        for (int i = 0; ; i++)
        {
            // Stop as soon as the next square would cross either edge
            if (x + SQUARE_SZ > WINDOW_W || y + SQUARE_SZ > WINDOW_H)
                break;

            // Pick this square's color, going around the palette
            SDL_Color color = COLORS[(i + offset) % COLOR_COUNT];
            SDL_SetRenderDrawColor(renderer, color.r, color.g,
                                   color.b, color.a);

            SDL_FRect square = { x, y, SQUARE_SZ, SQUARE_SZ };
            SDL_RenderFillRect(renderer, &square);

            // Move along the diagonal to the next square's spot
            x += STEP;
            y += STEP;
        }

        SDL_RenderPresent(renderer);
    }

    // Clean up, in the reverse order we created things
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

In the preceding code, the shape is the same as in every project so far: the constants and the palette at the top, then the setup, the game loop, and the cleanup inside main. The cascade's for loop sits in the middle of the game loop, between clearing the window and presenting it, so it runs once every frame.

Playing the Game

Press F5. A dark gray window opens, as in Figure 7.4, with a diagonal line of 18 colored squares running from the top-left corner toward the bottom, and the rainbow flowing steadily up the line, toward the corner. It isn't really a game, as there's nothing to do and nothing to win, but it's strangely hypnotic.

The finished cascade. The loop draws 18 squares, going around the palette twice, and stops just short of the bottom edge. In the running program, the colors flow up the diagonal.
Figure 7.4 — The finished cascade. The loop draws 18 squares, going around the palette twice, and stops just short of the bottom edge. In the running program, the colors flow up the diagonal.

Every one of those squares, in every frame, comes from the same dozen lines inside one loop. Press Escape, or close the window, when you've seen enough.

Understanding the Code

Step back from the screen, and look at the program as a whole. There's exactly one loop in it that does any visible work, and a dozen lines inside it produce the whole cascade, 60 times a second.

What's worth noticing is where each piece of that loop comes from. The for loop and the break that ends it are straight from Chapter 6, and the empty condition is the for loop's version of Chapter 6's while (true). The modulo that wraps the colors is Chapter 2's math, and the || in the stop test is Chapter 4's. The palette is an array, which we'll meet properly in Chapter 13. Everything else, from the SDL_Color members to the rectangle, is plumbing you've used in every project so far.

Loops are an organizational tool as much as anything. They take repetition and turn it into structure. Without the loop, this program would need 18 nearly identical blocks: draw a red square at 2, 2, draw an orange one at 34, 34, draw a yellow one at 66, 66, and so on. It would work, but it would be rigid. Change the window's size, the squares' size, or the padding, and all 18 blocks would need rewriting. The loop version adapts by itself: the structure stays put, and the result flexes.

The same idea drives every game you'll build. A hundred bullets are a loop over a list of bullets. A map made of tiles is two loops, one nested inside the other. An enemy deciding where to go is a loop over the things it can see. We're only at the beginning of that, but the shape is the thing to remember.

Experimenting

Try these one at a time, and see how the loop copes:

  • Change SQUARE_SZ to 12.0f for a longer cascade of 42 small squares.
  • Change SQUARE_SZ to 80.0f and PADDING to 6.0f for just six chunky squares.
  • Change y += STEP; to y += STEP / 2; for a shallower diagonal. Now it's the right edge that stops the loop, after 25 squares.
  • Make the cascade climb instead. Start y at the bottom, with float y = WINDOW_H - SQUARE_SZ - PADDING;, change y += STEP; to y -= STEP;, and add || y < 0.0f to the stop test, so that the loop also stops at the top edge.
  • Add a tenth color to the palette, with a comma after the pink. The count, COLOR_COUNT, becomes 10 by itself, and the pattern shifts.
  • Change the 100 in the offset line to 30 for a racing rainbow, or to 1000 for one step a second.

For a bigger challenge, draw three cascades side by side. Wrap the cascade, from float x = PADDING; down to the for loop's closing brace, in another for loop that counts from 0 to 2, and start each cascade's x a little further to the right. That's a loop inside a loop, just like the grid in Chapter 6.

Each of these teaches you something about how a loop's structure decides what you see.

Common Errors and Fixes

If the build fails with errors about SDL3/SDL.h, SDL3.lib, or unresolved external symbols, the problem is the project setup, and Chapter 1's Common Errors section covers each one. Here are the problems that are particular to this chapter.

A flood of errors starting with C2059: syntax error: '{'. There's a comma missing between two colors in the palette. The first error points at the line after the missing comma, and the rest follow on from it, so fix the first one, and the flood goes away.

C2143: syntax error: missing ';' before 'const'. The semicolon after the palette's closing brace is missing. The error points at the next line, the one that defines COLOR_COUNT, so look at the end of the line above.

The window opens but stays empty, and Windows says it isn't responding. The for loop's break is missing, so the loop never ends. Press Shift+F5 to stop the program, and check the stop test at the top of the loop.

The first nine squares are fine, but the rest are black, gray, or murky red. The color line is missing its % COLOR_COUNT, so COLORS[i] reads past the end of the palette as soon as i reaches 9, as Figure 7.5 shows. C++ doesn't check array positions, so it reads whatever happens to be in the memory after the list. That's called undefined behavior: C++ makes no promises about what happens, and it can be odd colors on one computer and a crash on another. Put the % COLOR_COUNT back.

Without the modulo, squares 9 to 17 read past the end of the palette and come out in whatever colors the memory there happens to make.
Figure 7.5 — Without the modulo, squares 9 to 17 read past the end of the palette and come out in whatever colors the memory there happens to make.

The squares touch, with no gaps between them. Check that STEP is SQUARE_SZ + PADDING, and not just SQUARE_SZ.

The colors never move. Check that the color line uses (i + offset), and that startTime is created above the game loop, not inside it. Inside the loop, startTime is set afresh every frame, so the time since it was set is always about 0, and so is offset.

A message box says "The code execution cannot proceed because SDL3.dll was not found." Copy SDL3.dll from C:\SDL3\lib\x64 into the project folder, beside main.cpp.

AI Exercise (Optional)

If you'd like to push this further with AI help, here's a small vibe coding challenge. As always, skip it if you'd rather not; nothing later in the book depends on it.

Open your AI chatbot of choice and try a prompt like this:

"I have a small C++ SDL 3 program that uses one for loop to draw a diagonal cascade of colored squares from the top-left of the window, choosing each color from a nine-color array with the modulo operator, and stopping with break when the next square wouldn't fit. The colors already flow along the line over time. I have only learned variables, structs, flow control (if, else, and switch), and loops (while, for, do-while, break, and continue). I haven't learned functions, vectors, or classes. Show me how to make the squares themselves slide diagonally across the window over time, reappearing at the top-left when they reach the edge. Use only what I've learned, put each curly brace on its own line, and show me the complete program."

Notice what the preceding prompt does. It names the language and the library exactly, so the AI doesn't suggest SDL 2 code. The description of what the program already does gives the AI something to build on, and the list of what you've learned keeps the answer inside your bubble of understanding. Asking for the whole program, rather than a snippet, means you can see exactly where every change goes.

When the answer comes back, read it carefully before you run it. Did the AI stick to the tools you listed, or did it sneak in a function or a vector? If it did, push back: "That uses a feature I haven't learned yet. Show me a version that only uses variables, flow control, and loops." That's how to make an AI work at your level, rather than dragging you up to its own.

How does its sliding work? There's more than one good answer, and a version that uses modulo to wrap the squares around would be a fine echo of this chapter.

Your version will look different from anyone else's, and that's the point.

Summary

You've used a single for loop to turn a dozen lines of code into a whole cascade of squares. The shape of the cascade, its direction, its density, and its colors, is controlled by a handful of constants, and the loop adapts to whatever you give it. An empty condition and a break stop it at exactly the right square, the modulo operator sends it round and round the palette, and two new lines and one small change make the whole rainbow flow.

Loops are an honest superpower: they're the difference between a program that handles three things and one that handles three thousand. In the next chapter, we'll meet functions, which are the same kind of superpower for structure: a way of giving a name to a chunk of work, so we can organize it and reuse it. With functions and loops together, we'll be ready for Chapter 9, the Act 1 capstone, a complete little game with real gameplay, built from well-named pieces. That's where all of this is heading.