In the last chapter we picked up while, for, do-while, 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 — actually write a few lines and watch a screen fill up with stuff.
This project is small, even by the standards of Act 1. A single for loop draws a diagonal cascade of colored squares across the window — as many as the loop can fit before it runs out of screen. Tweak one number and the cascade changes color. Tweak another and it changes direction. Add a single line and it animates. The whole thing is well under a hundred lines and 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 another empty SDL project
- Use a
forloop to draw many squares from a few lines of code - Cycle through a fixed list of colors using array indexing and the modulo operator
- Use
breakto exit a loop the moment we've drawn enough - Add one tiny variable to make the whole thing animate
- Experiment with the values to see how the cascade bends
- Try an optional AI exercise
Let's build it.
Setting Up the Project
By now the setup steps are a routine. The condensed version:
- File > New > Project, Empty Project (C++), named
Cascade. - Add
main.cppunder Source Files. - Project Properties with Configuration: All Configurations, Platform: All Platforms:
- C/C++ > General > Additional Include Directories → your SDL
includefolder. - Linker > General > Additional Library Directories → your SDL
lib\x64folder. - Linker > Input > Additional Dependencies → add
SDL3.lib. - Linker > System > SubSystem → Windows.
- C/C++ > General > Additional Include Directories → your SDL
- Copy
SDL3.dllintox64\Debug.
Coding the Game
Open main.cpp. We'll build it up piece by piece.
Includes, Constants, and the Color List
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
const int SCREEN_W = 800;
const int SCREEN_H = 600;
const int SQUARE_SZ = 30;
const int PADDING = 2;
const int STEP = SQUARE_SZ + PADDING; // distance between square corners
In the preceding code, our usual includes, plus five constants describing the window and the cascade. STEP is the distance from one square's top-left corner to the next — the square's size plus a small gap.
Now we need a list of colors to cycle through. C++ lets us declare a fixed list using square-bracket syntax — a C-style array:
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 = sizeof(COLORS) / sizeof(COLORS[0]);
In the preceding code, SDL_Color is one of SDL's built-in structs — four members for red, green, blue, and alpha (opacity). The empty brackets let the compiler count the items for us. The expression COLORS[0] means "the first item", COLORS[1] means "the second", and so on. The slightly mysterious sizeof(COLORS) / sizeof(COLORS[0]) is the classic C++ trick for asking the compiler how many items are in this array — total size divided by the size of one item. Arrays are properly covered in Chapter 12; for now, just know that COLORS[i] reaches into the list at position i.
main and SDL Setup
int main(int argc, char* argv[])
{
if (!SDL_Init(SDL_INIT_VIDEO))
{
SDL_Log("SDL_Init failed: %s", SDL_GetError());
return 1;
}
SDL_Window* window = SDL_CreateWindow("Cascade", SCREEN_W, SCREEN_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;
}
The usual SDL boilerplate. By now the shape of it is familiar — start SDL, create a window, create a renderer, complain and exit if anything fails.
The Game Loop
bool running = true;
SDL_Event event;
while (running)
{
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;
}
The outer while is the main game loop and the inner while drains the event queue. The inner loop is a particularly nice example of what while is good at — we don't know how many events SDL has queued up. while (SDL_PollEvent(&event)) reads as plain English: "while we successfully poll an event, do this."
Drawing the Cascade with a for Loop
This is the part the chapter is really about. We want to draw a row of squares marching diagonally from the top-left corner, each one a different color, until the next square wouldn't fit on the screen anymore.
// Clear to a dark background
SDL_SetRenderDrawColor(renderer, 30, 30, 30, 255);
SDL_RenderClear(renderer);
int x = PADDING;
int y = PADDING;
for (int i = 0; ; ++i)
{
// Stop when the next square would be off both edges
if (x + SQUARE_SZ > SCREEN_W && y + SQUARE_SZ > SCREEN_H)
break;
// Pick a color from the list, looping back to the start as needed
SDL_Color c = COLORS[i % COLOR_COUNT];
SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
SDL_FRect rect = {
(float)x, (float)y,
(float)SQUARE_SZ, (float)SQUARE_SZ
};
SDL_RenderFillRect(renderer, &rect);
x += STEP;
y += STEP;
}
SDL_RenderPresent(renderer);
}
Let's walk through the loop carefully.
We create two int variables, x and y, set to PADDING. These are the position of the next square we're about to draw. Now the for loop itself — notice something unusual:
for (int i = 0; ; ++i)
The middle slot — the condition — is empty. That's perfectly legal C++. An empty condition means "keep going forever." You'd normally never write this without a plan to escape from inside the loop, which is exactly what we do with the break check:
if (x + SQUARE_SZ > SCREEN_W && y + SQUARE_SZ > SCREEN_H)
break;
We check whether the next square would extend past the right edge and past the bottom edge. Once both are true, no more squares will fit. We use break to leave the loop immediately. Why not a hard upper bound like i < 1000? Because the right number of squares depends on the window size, the square size, and the padding. Letting the loop run until the geometry says "stop" is cleaner.
Next, we pick the color:
SDL_Color c = COLORS[i % COLOR_COUNT];
The % is the modulo operator from Chapter 2 — it gives you the remainder of a division. As i climbs from zero, the result of i % COLOR_COUNT cycles 0, 1, 2, ..., 8, 0, 1, 2, ... forever, wrapping back to the start of the palette automatically. No if, no reset.
Finally, x += STEP; y += STEP; advances both coordinates by the same amount each iteration — equal horizontal and vertical movement gives a 45-degree diagonal.
Cleanup
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
The Complete Program
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
const int SCREEN_W = 800;
const int SCREEN_H = 600;
const int SQUARE_SZ = 30;
const int PADDING = 2;
const int STEP = SQUARE_SZ + PADDING;
const SDL_Color COLORS[] = {
{220, 50, 50, 255},
{230, 140, 30, 255},
{220, 210, 40, 255},
{ 50, 180, 50, 255},
{ 40, 140, 220, 255},
{100, 60, 200, 255},
{170, 60, 200, 255},
{ 50, 200, 180, 255},
{200, 80, 130, 255},
};
const int COLOR_COUNT = sizeof(COLORS) / sizeof(COLORS[0]);
int main(int argc, char* argv[])
{
if (!SDL_Init(SDL_INIT_VIDEO))
{
SDL_Log("SDL_Init failed: %s", SDL_GetError());
return 1;
}
SDL_Window* window = SDL_CreateWindow("Cascade", SCREEN_W, SCREEN_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;
}
bool running = true;
SDL_Event event;
while (running)
{
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;
}
SDL_SetRenderDrawColor(renderer, 30, 30, 30, 255);
SDL_RenderClear(renderer);
int x = PADDING;
int y = PADDING;
for (int i = 0; ; ++i)
{
if (x + SQUARE_SZ > SCREEN_W && y + SQUARE_SZ > SCREEN_H)
break;
SDL_Color c = COLORS[i % COLOR_COUNT];
SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
SDL_FRect rect = {
(float)x, (float)y,
(float)SQUARE_SZ, (float)SQUARE_SZ
};
SDL_RenderFillRect(renderer, &rect);
x += STEP;
y += STEP;
}
SDL_RenderPresent(renderer);
}
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Playing the Game
Hit F5. The window opens. A diagonal cascade of around 20 colored squares marches from the top-left toward the bottom-right corner, rainbow-cycling through the palette as it goes. The cascade is rendered every frame, but because nothing changes between frames, it looks completely still.
Adding One Line of Animation
Right now the cascade is a still image. Let's make it move. Add one new variable at the top of main (above the while loop):
Uint64 startTime = SDL_GetTicks();
And change the color-picking line inside the for loop to this:
int offset = (int)((SDL_GetTicks() - startTime) / 100);
SDL_Color c = COLORS[(i + offset) % COLOR_COUNT];
In the preceding code, we ask SDL how many milliseconds have passed since the program started, divide by 100 to get a slowly-rising integer, and add that to i before doing the modulo. The result: the color each square picks shifts one step along the palette every 100 milliseconds. The whole cascade ripples through the colors smoothly.
Two extra lines of code. The loop and everything around it didn't change. That's the kind of leverage variables and loops give you — small changes, dramatic effect.
Understanding the Code
Step back from the screen and look at the program as a whole. There is exactly one game-relevant loop in the entire file, and it does all the visible work. Twelve or so lines inside that loop end up producing a screen full of squares.
What's worth noticing is which concepts each piece pulls from. The for loop with an empty middle slot is plain Chapter 6 syntax. The break is Chapter 6 too. The modulo for wrapping the color index is Chapter 2's math operators. Everything else — the rectangle, the renderer call — is plumbing we've seen in every previous project.
Loops are an organizational tool more than they are a feature. They take repetition and turn it into structure. Without the loop in this chapter, the equivalent program would be a rigid 60-line stretch of "draw a square at (2,2), then draw one at (34,34)..." The loop version adapts automatically to a different window size, different square size, different padding. The structure stays put; the result flexes.
Experimenting
SQUARE_SZ = 12— many more, smaller squares.SQUARE_SZ = 80,PADDING = 6— chunky squares, a much shorter cascade.- Change
y += STEP;toy += STEP / 2;— a shallower diagonal. - Change
y += STEP;toy -= STEP;(startingynear the bottom) — a diagonal climbing up. - Wrap the entire
forloop in another loop that runs three times with different starting positions. You'll get three parallel cascades.
Common Errors and Fixes
The program builds but draws nothing. Either the loop never enters — check that your termination condition uses &&, not || — or you're forgetting to call SDL_RenderPresent at the bottom of the frame.
The program freezes the moment it opens. You probably wrote the loop without a way out. If the empty-condition for has no break, it runs forever and never returns to the event loop. End the program from the IDE and add the break check.
Squares overlap or there's no padding. Make sure STEP = SQUARE_SZ + PADDING, not just SQUARE_SZ. Otherwise each square is drawn flush against the next.
The colors don't cycle. You probably forgot the % COLOR_COUNT. Without it, COLORS[i] reads past the end of the array — most often a crash, sometimes garbled colors.
AI Exercise (Optional)
Open your AI chatbot of choice and try a prompt like this:
"I have a small C++ SDL 3 program that uses one
forloop to draw a diagonal cascade of colored squares from the top-left to the bottom-right of the window. I have only learned C++ variables, flow control (if,else,switch), and loops (while,for,do-while,break,continue). I have not learned functions, vectors, classes, or anything else. Show me how to make the cascade animate so the squares appear to slide diagonally across the screen over time. Use only what I have learned. Paste the complete updated program."
Notice what the preceding prompt is doing. It names the language and library exactly. It states the constraint — what features you have available — so the AI works inside your bubble of understanding. And it asks for the whole program back, not just a snippet. When the AI replies, read it carefully. Did the AI stick to the constraint, or did it try to sneak in a function or a vector? If it did, push back: "That uses a feature I haven't learned yet."
Summary
You've used a for loop to turn a dozen lines of code into a screen full of squares. The shape of the cascade — its direction, its color cycle, its density — is controlled by a handful of variables, and the loop adapts to whatever you give it. With one extra line, the whole thing animates.
Functions, which we meet in the next chapter, are the same kind of superpower for structure — a way of giving names to chunks of work so we can organize and reuse them. Once we have functions and loops together, we'll be ready for the Act 1 capstone in Chapter 9.