Chapter 26

Vibe Coding Project 1 — Vibe Runner

Time to put the last chapter into practice. We are going to take the endless runner from Chapter 23 and, working with an AI assistant, turbo-charge it — adding a parallax background and a satisfying particle explosion when the player crashes. We will follow the vibe coding loop from Chapter 25: describe, generate, review, run, refine.

This chapter works differently from every project chapter before it. I am not going to hand you a finished program and walk you through every line. Instead, I am going to show you the conversation — the prompts I gave the AI, the kind of thing it gave back, what I kept, what I changed, and where it went wrong. You will follow along by having the same kind of conversation with your own AI. And here is the important part: your result will be different from mine, and that is completely fine. There is no single right answer. The author's version — one possible outcome — is in this chapter's code folder for you to compare against, but it is not gospel. The goal is the process, not matching my output.

Code for this chapter: one possible finished version of the Vibe Runner lives in the Chapter 26 folder at github.com/EliteIntegrity/Learning-Kotlin-by-Building-Android-Games. Use it as a reference, not a target.

In this chapter, we will:

Let's vibe.

The Starting Point

We begin with the Chapter 23 endless runner, exactly as we left it: the Atlas, Player, Obstacle, and GameView classes, with the player running, jumping, and crashing into walls. Open that project (or a copy of it — always work on a copy when experimenting, so your working game stays safe). This is our foundation, and we understand every line of it. That understanding is what makes the next part safe.

Setting the Ground Rules

The very first thing I do in any AI session is give it the context it needs, so it doesn't hand me code that doesn't fit. This is the "give context and constraints" advice from Chapter 25, applied. My opening message is not a request at all — it is a briefing:

"I'm building a 2D Android game in Kotlin. Important constraints: it uses a custom SurfaceView with a threaded game loop and Canvas drawing — NO game engine, NO Jetpack Compose, NO external libraries. It's organized into classes: Atlas (draws sprites from a texture atlas), Player, Obstacle, and GameView (the game loop). All movement uses delta time (speed in pixels per second). I'm comfortable with Kotlin classes, lists, lambdas, and the game loop. Over the next few messages I'll ask you to add features. Keep everything within these constraints and explain your changes. Acknowledge and wait for my first feature request."

That last line is a small trick worth knowing: telling the AI to acknowledge and wait stops it from charging off and generating a wall of code before it even knows what I want. It replies with a short "got it," and now every answer that follows is shaped by my constraints. No Compose. No mystery libraries. Code I can read.

Round 1 — A Parallax Background

Our runner has a flat blue sky. Real runners have depth — distant clouds drifting slowly, hills rolling by faster, the ground rushing past fastest of all. That trick is called parallax: things farther away scroll slower. Here is my first feature prompt:

"My GameView draws a plain blue sky, then a scrolling row of grass tiles along the bottom (using a groundScroll value that increases by gameSpeed * deltaTime and wraps at the tile width). Add a parallax background behind the grass: a layer of clouds that scrolls slowly, and a layer of hills that scrolls a bit faster, but both slower than the grass. Draw them as plain shapes (circles), no new sprites needed. Show me the new draw code and the variables it needs, and keep it to my existing style."

Notice how specific that is. I told it how my existing scroll works, exactly what I wanted, and — crucially — "draw them as plain shapes, no new sprites," because I did not want to be sent off to create more art. The AI came back with something close to this:

// New scroll variables, alongside groundScroll
private var hillScroll = 0f
private var cloudScroll = 0f

// In update(), scrolling slower than the grass:
hillScroll = wrap(hillScroll + gameSpeed * 0.4f * deltaTime, hillSpacing)
cloudScroll = wrap(cloudScroll + gameSpeed * 0.15f * deltaTime, cloudSpacing)

and a chunk of draw code that tiled clouds and hills across the screen with the same while-loop tiling we use for grass. I read it line by line. It used the delta-time pattern correctly. It scrolled the clouds at 0.15 and hills at 0.4 of the game speed — slower than the foreground, exactly the parallax idea. It fit my style. I accepted it, with one tweak: I pulled the repeated "wrap a scroll value" logic into a small wrap helper function, because the AI had written the wrapping inline three times and I prefer it named once. That is the refine step — the AI's code was correct, and I made it a little more like mine.

I ran it. Clouds drifted lazily, hills rolled by, grass rushed past underneath. Instant depth, and the game suddenly felt fast. One prompt, one read-through, one small tweak.

Round 2 — A Particle Explosion on Crash

Right now, crashing just stops the game. It should feel like an event. We already know how to build a particle system — we did it by hand back in Chapter 11, and again as a class in Chapter 17 — so this is a great chance to let the AI do the legwork while we check it against what we already understand. My prompt:

"When the player crashes into a wall, I want a burst of particles to explode from the player's position. Write me a Particle class in the same style as my other classes: it should take a position and a velocity in pixels per second, fall under gravity, fade out over about 0.8 seconds using the paint's alpha, and have update(deltaSeconds), isDead(), and draw(canvas, paint) methods. Then show me how to spawn a burst of about 28 of them with random velocities when the crash happens, and how to update and draw them from GameView — remembering they should keep animating even after the game is over so the explosion plays out."

That prompt is detailed because I know what I want — I have built this before. The AI produced a Particle class almost identical to the one in this chapter's code folder, plus a spawnExplosion function and the loop to update and draw the particles. I read it carefully, and because I have written a particle system myself, I could judge it instantly: the gravity was applied with delta time, the fade mapped life to alpha, the dead ones were removed with a backwards loop. All correct. This is the ideal use of AI — it wrote, quickly and competently, a thing I fully understand and could have written myself but would rather not type out again.

There was one genuinely nice catch in my own prompt, worth pointing out: "they should keep animating even after the game is over." If I had not said that, the explosion would have frozen the instant the game stopped updating — a common bug. Because I understood my own game loop (the if (!isGameOver) update() line from Chapter 23), I knew to ask for it. The AI is only as good as the understanding behind the prompt.

When the AI Gets It Wrong

Now the honest part, because Chapter 25 promised it. Vibe coding is not magic, and the AI is not always right. While tuning the explosion, I asked:

"Make the particles spawn from the exact center of the player."

and the AI confidently handed back code using player.x and player.y as the center. I almost accepted it — it looked perfectly reasonable. But I read it, and I knew from my own Player class that x and y are the top-left corner of the sprite, not its center. The AI had assumed otherwise, with total confidence and no warning. Its code would have spawned the explosion from the player's corner, slightly off. A small bug, but a real one — and a perfect illustration of the Chapter 25 point that an AI will be fluently, cheerfully wrong. Because I understood my own code, I caught it in two seconds and replied:

"player.x/player.y are the top-left of the sprite, not the center. Use the center of the player's hit box instead."

It apologized (they always do) and produced the corrected version using hitBox().centerX() and centerY(). This is the whole reason we spent twenty-three chapters learning to read code. A reader who could not read it would have shipped the bug and never known why the explosion looked slightly wrong. You caught it because you are the senior partner.

The Result

A few rounds of describe–generate–review–run–refine, and the humble Chapter 23 runner now has a parallax world and explodes with sparks on every crash. My version is in this chapter's code folder — com.example.viberunner — if you would like to run it and compare. But I want to be clear one more time: it is one outcome. Yours might have three parallax layers instead of two, or stars instead of clouds, or a screen shake on impact, or particles in a different color. If your runner does something mine doesn't, you have not done it "wrong" — you have done it yours. That is exactly the point of this chapter.

What stayed constant, for both of us, is the discipline. We briefed the AI with our constraints. We asked for specific, scoped features. We read every line before accepting it. We caught the mistakes. And we kept the architecture — our classes, our game loop — firmly in our own hands. The AI made us faster; it did not take the wheel.

Over to You

This is your playground now. Open your own copy of the Chapter 23 runner, brief your AI as I did, and add whatever you like. Some prompts to get you going — each scoped the way Chapter 25 taught:

Try them. Read what comes back. Run it. Refine it. Break it and fix it. And remember the two unbreakable rules: read every line before it goes in, and keep the shape of the game in your own head.

Summary

You ran your first real vibe-coding session. You briefed an AI with your project's constraints, then expanded the Chapter 23 runner with a parallax background and a particle explosion, one scoped feature at a time, following the describe–generate–review–run–refine loop. You saw the AI write competent code fast — and saw it confidently produce a real bug that only your own understanding could catch. The acceleration was huge; the control stayed with you.

In the next chapter we go further: instead of expanding a game we already have, we will build a brand-new one — a vertical space shooter — with AI assistance from a blank screen. It will lean on new sprites and sounds, and it will push the vibe-coding loop harder, because designing something from scratch is where an AI partner is both most helpful and most in need of your direction.