It’s hard to start coding. Let’s the computer help us.
Today we will vibe code.
Chatbots can code quite well if supervised correctly.
Idea
Let’s create a solo paintball game where player automatically shoots projectiles which bounce off the wall and another projectiles. If player is hit game restarts.
Plan
We can break the idea to several steps:
10x10 white tile grid. Black lines separate the tiles. 1.
Right bottom tile is occupied by a player which is blue. 1.
Player can move with WASD or Arrows. 1.
Player shoots red balls every 5 seconds to the direction he is looking to. Direction is determined by the mouse. 1.
When the ball hits the wall it bounces off. 1.
When the ball hits the another ball it bounces off. 1.
When the player is hit game is reset. …
It’s hard to start coding. Let’s the computer help us.
Today we will vibe code.
Chatbots can code quite well if supervised correctly.
Idea
Let’s create a solo paintball game where player automatically shoots projectiles which bounce off the wall and another projectiles. If player is hit game restarts.
Plan
We can break the idea to several steps:
10x10 white tile grid. Black lines separate the tiles. 1.
Right bottom tile is occupied by a player which is blue. 1.
Player can move with WASD or Arrows. 1.
Player shoots red balls every 5 seconds to the direction he is looking to. Direction is determined by the mouse. 1.
When the ball hits the wall it bounces off. 1.
When the ball hits the another ball it bounces off. 1.
When the player is hit game is reset.
We will be using a JavaScript library/2d physics engine Matter.js.
Setup
Open a folder you want to code in and create file: game.html.
Chat with the bot
Now that we have the plan let’s prompt ChatGPT to code for us.
Prompt no 1:
With Matter.js create a HTML page containing 10x10 white tile grid. Black lines separate the tiles.
Answer no 1:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>10x10 Tile Grid with Matter.js</title>
<style>
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #eee;
}
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
<script>
const { Engine, Render, World, Bodies } = Matter;
// Create engine
const engine = Engine.create();
// Grid configuration
const rows = 10;
const cols = 10;
const tileSize = 50; // width and height of each tile
// Canvas size
const width = cols * tileSize;
const height = rows * tileSize;
// Create renderer
const render = Render.create({
element: document.body,
engine: engine,
options: {
width,
height,
wireframes: false,
background: '#fff',
}
});
// Create tile grid
const tiles = [];
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const x = col * tileSize + tileSize / 2;
const y = row * tileSize + tileSize / 2;
const tile = Bodies.rectangle(x, y, tileSize, tileSize, {
isStatic: true,
render: {
fillStyle: '#fff',
strokeStyle: '#000',
lineWidth: 1
}
});
tiles.push(tile);
}
}
// Add tiles to the world
World.add(engine.world, tiles);
// Run engine and renderer
Engine.run(engine);
Render.run(render);
</script>
</body>
</html>
Result no 1:

Prompt no 2:
Make whole right bottom tile occupied by a player which is blue square.
Answer no 2:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>10x10 Tile Grid with Player</title>
<style>
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #eee;
}
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
<script>
const { Engine, Render, World, Bodies } = Matter;
// Create engine
const engine = Engine.create();
// Grid configuration
const rows = 10;
const cols = 10;
const tileSize = 50; // width and height of each tile
// Canvas size
const width = cols * tileSize;
const height = rows * tileSize;
// Create renderer
const render = Render.create({
element: document.body,
engine: engine,
options: {
width,
height,
wireframes: false,
background: '#fff',
}
});
// Create tile grid
const tiles = [];
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const x = col * tileSize + tileSize / 2;
const y = row * tileSize + tileSize / 2;
const tile = Bodies.rectangle(x, y, tileSize, tileSize, {
isStatic: true,
render: {
fillStyle: '#fff',
strokeStyle: '#000',
lineWidth: 1
}
});
tiles.push(tile);
}
}
// Add tiles to the world
World.add(engine.world, tiles);
// Add player at bottom-right tile
const playerX = (cols - 1) * tileSize + tileSize / 2;
const playerY = (rows - 1) * tileSize + tileSize / 2;
const player = Bodies.rectangle(playerX, playerY, tileSize, tileSize, {
isStatic: true,
render: {
fillStyle: 'blue',
strokeStyle: '#000',
lineWidth: 1
}
});
World.add(engine.world, player);
// Run engine and renderer
Engine.run(engine);
Render.run(render);
</script>
</body>
</html>
Result no 2:

Prompt no 3:
Make player move with WASD or Arrows.
Answer no 3:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>10x10 Tile Grid with Movable Player</title>
<style>
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #eee;
}
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
<script>
const { Engine, Render, World, Bodies, Body } = Matter;
// Engine
const engine = Engine.create();
// Grid configuration
const rows = 10;
const cols = 10;
const tileSize = 50;
const width = cols * tileSize;
const height = rows * tileSize;
// Renderer
const render = Render.create({
element: document.body,
engine: engine,
options: {
width,
height,
wireframes: false,
background: '#fff',
}
});
// Create grid tiles
const tiles = [];
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const x = col * tileSize + tileSize / 2;
const y = row * tileSize + tileSize / 2;
const tile = Bodies.rectangle(x, y, tileSize, tileSize, {
isStatic: true,
render: {
fillStyle: '#fff',
strokeStyle: '#000',
lineWidth: 1
}
});
tiles.push(tile);
}
}
World.add(engine.world, tiles);
// Player
let playerRow = rows - 1;
let playerCol = cols - 1;
const player = Bodies.rectangle(
playerCol * tileSize + tileSize / 2,
playerRow * tileSize + tileSize / 2,
tileSize,
tileSize,
{
isStatic: true,
render: {
fillStyle: 'blue',
strokeStyle: '#000',
lineWidth: 1
}
}
);
World.add(engine.world, player);
// Handle keyboard input
document.addEventListener('keydown', (event) => {
let moved = false;
if (event.key === 'ArrowUp' || event.key.toLowerCase() === 'w') {
if (playerRow > 0) { playerRow--; moved = true; }
}
if (event.key === 'ArrowDown' || event.key.toLowerCase() === 's') {
if (playerRow < rows - 1) { playerRow++; moved = true; }
}
if (event.key === 'ArrowLeft' || event.key.toLowerCase() === 'a') {
if (playerCol > 0) { playerCol--; moved = true; }
}
if (event.key === 'ArrowRight' || event.key.toLowerCase() === 'd') {
if (playerCol < cols - 1) { playerCol++; moved = true; }
}
if (moved) {
Body.setPosition(player, {
x: playerCol * tileSize + tileSize / 2,
y: playerRow * tileSize + tileSize / 2
});
}
});
// Run engine and renderer
Engine.run(engine);
Render.run(render);
</script>
</body>
</html>
Result no 3:
Press the WASD or Arrows and you will see the results.
Prompt no 4:
Make player shoot red balls every 5 seconds to the direction he is looking to. Direction is determined by the mouse.
Question:
If you raise your mouse higher than the player balls will get stuck. Think a bit, why?
Answer:
When the ball spawns it seems to collide with the player and therefore immediately stop.
Prompt no 4 retry 1:
It seems then the ball is spawned it collides with the player and gets stuck. They do not move: [also uploaded the image of balls stuck]
Result:
Good news that we guessed correctly why balls get stuck and now that problem is fixed. However it seems gravity is enabled we need to disable it.
Prompt no 4 retry 2:
Gravity must be disabled. The ball must have a constant travel speed instead.
Problem:
Now gravity is disabled but the balls seem to hit empty tiles.
Prompt no 4 retry 3:
I ran out of ChatGPT 5 so I copied the code and asked question in new chat:
Fix code so that white tiles and black lines do not have collision.
Answer:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>10x10 Grid with Constant-Speed Shooting</title>
<style>
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #eee;
user-select: none;
}
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
<script>
const { Engine, Render, World, Bodies, Body, Vector, Events } = Matter;
// Engine
const engine = Engine.create();
engine.gravity.y = 0; // disable gravity
// Grid configuration
const rows = 10;
const cols = 10;
const tileSize = 50;
const width = cols * tileSize;
const height = rows * tileSize;
// Renderer
const render = Render.create({
element: document.body,
engine: engine,
options: {
width,
height,
wireframes: false,
background: '#fff'
}
});
// Create grid tiles (visual only, no collisions)
const tiles = [];
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const x = col * tileSize + tileSize / 2;
const y = row * tileSize + tileSize / 2;
const tile = Bodies.rectangle(x, y, tileSize, tileSize, {
isStatic: true,
isSensor: true, // <-- makes tile non-collidable
render: { fillStyle: '#fff', strokeStyle: '#000', lineWidth: 1 }
});
tiles.push(tile);
}
}
World.add(engine.world, tiles);
// Player
let playerRow = rows - 1;
let playerCol = cols - 1;
const player = Bodies.rectangle(
playerCol * tileSize + tileSize / 2,
playerRow * tileSize + tileSize / 2,
tileSize,
tileSize,
{
isStatic: true,
render: { fillStyle: 'blue', strokeStyle: '#000', lineWidth: 1 }
}
);
World.add(engine.world, player);
// Track mouse for direction
let mousePos = { x: width/2, y: height/2 };
render.canvas.addEventListener('mousemove', (e) => {
const rect = render.canvas.getBoundingClientRect();
mousePos = {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
});
// Player movement
document.addEventListener('keydown', (event) => {
let moved = false;
if (event.key === 'ArrowUp' || event.key.toLowerCase() === 'w') {
if (playerRow > 0) { playerRow--; moved = true; }
}
if (event.key === 'ArrowDown' || event.key.toLowerCase() === 's') {
if (playerRow < rows - 1) { playerRow++; moved = true; }
}
if (event.key === 'ArrowLeft' || event.key.toLowerCase() === 'a') {
if (playerCol > 0) { playerCol--; moved = true; }
}
if (event.key === 'ArrowRight' || event.key.toLowerCase() === 'd') {
if (playerCol < cols - 1) { playerCol++; moved = true; }
}
if (moved) {
Body.setPosition(player, {
x: playerCol * tileSize + tileSize / 2,
y: playerRow * tileSize + tileSize / 2
});
}
});
// Array to hold active balls
const balls = [];
// Shoot red ball every 5 seconds
setInterval(() => {
const playerPos = player.position;
let dir = Vector.sub(mousePos, playerPos);
dir = Vector.normalise(dir);
const speed = 5;
const spawnDistance = tileSize / 2 + tileSize / 6;
const spawnPos = {
x: playerPos.x + dir.x * spawnDistance,
y: playerPos.y + dir.y * spawnDistance
};
const ball = Bodies.circle(spawnPos.x, spawnPos.y, tileSize/6, {
isStatic: false,
frictionAir: 0,
collisionFilter: { group: -1 }, // optional: ignore collisions with player
render: { fillStyle: 'red' }
});
ball.customVelocity = { x: dir.x * speed, y: dir.y * speed }; // constant velocity
balls.push(ball);
World.add(engine.world, ball);
}, 5000);
// Update loop for constant speed balls
Events.on(engine, 'beforeUpdate', () => {
balls.forEach(ball => {
Body.setVelocity(ball, ball.customVelocity);
});
});
// Run engine and renderer
Engine.run(engine);
Render.run(render);
</script>
</body>
</html>
Result:
Open browser make sure your mouse is above the player and see the red balls shooting every 5 seconds.
Prompt no 5:
Make it so when the ball hits end of map it bounces off.
Answer no 5:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>10x10 Grid with Constant-Speed Shooting</title>
<style>
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #eee;
user-select: none;
}
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
<script>
const { Engine, Render, World, Bodies, Body, Vector, Events } = Matter;
// Engine
const engine = Engine.create();
engine.gravity.y = 0; // disable gravity
// Grid configuration
const rows = 10;
const cols = 10;
const tileSize = 50;
const width = cols * tileSize;
const height = rows * tileSize;
// Renderer
const render = Render.create({
element: document.body,
engine: engine,
options: {
width,
height,
wireframes: false,
background: '#fff'
}
});
// Create grid tiles (visual only, no collisions)
const tiles = [];
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const x = col * tileSize + tileSize / 2;
const y = row * tileSize + tileSize / 2;
const tile = Bodies.rectangle(x, y, tileSize, tileSize, {
isStatic: true,
isSensor: true, // <-- makes tile non-collidable
render: { fillStyle: '#fff', strokeStyle: '#000', lineWidth: 1 }
});
tiles.push(tile);
}
}
World.add(engine.world, tiles);
// Create boundary walls for bouncing
const thickness = 50; // thicker than a tile so balls never escape
const walls = [
// top
Bodies.rectangle(width/2, -thickness/2, width, thickness, { isStatic: true }),
// bottom
Bodies.rectangle(width/2, height + thickness/2, width, thickness, { isStatic: true }),
// left
Bodies.rectangle(-thickness/2, height/2, thickness, height, { isStatic: true }),
// right
Bodies.rectangle(width + thickness/2, height/2, thickness, height, { isStatic: true })
];
World.add(engine.world, walls);
// Player
let playerRow = rows - 1;
let playerCol = cols - 1;
const player = Bodies.rectangle(
playerCol * tileSize + tileSize / 2,
playerRow * tileSize + tileSize / 2,
tileSize,
tileSize,
{
isStatic: true,
render: { fillStyle: 'blue', strokeStyle: '#000', lineWidth: 1 }
}
);
World.add(engine.world, player);
// Track mouse for direction
let mousePos = { x: width/2, y: height/2 };
render.canvas.addEventListener('mousemove', (e) => {
const rect = render.canvas.getBoundingClientRect();
mousePos = {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
});
// Player movement
document.addEventListener('keydown', (event) => {
let moved = false;
if (event.key === 'ArrowUp' || event.key.toLowerCase() === 'w') {
if (playerRow > 0) { playerRow--; moved = true; }
}
if (event.key === 'ArrowDown' || event.key.toLowerCase() === 's') {
if (playerRow < rows - 1) { playerRow++; moved = true; }
}
if (event.key === 'ArrowLeft' || event.key.toLowerCase() === 'a') {
if (playerCol > 0) { playerCol--; moved = true; }
}
if (event.key === 'ArrowRight' || event.key.toLowerCase() === 'd') {
if (playerCol < cols - 1) { playerCol++; moved = true; }
}
if (moved) {
Body.setPosition(player, {
x: playerCol * tileSize + tileSize / 2,
y: playerRow * tileSize + tileSize / 2
});
}
});
// Array to hold active balls
const balls = [];
// Shoot red ball every 5 seconds
setInterval(() => {
const playerPos = player.position;
let dir = Vector.sub(mousePos, playerPos);
dir = Vector.normalise(dir);
const speed = 5;
const spawnDistance = tileSize / 2 + tileSize / 6;
const spawnPos = {
x: playerPos.x + dir.x * spawnDistance,
y: playerPos.y + dir.y * spawnDistance
};
const ball = Bodies.circle(spawnPos.x, spawnPos.y, tileSize/6, {
restitution: 1, // <-- makes it bouncy
frictionAir: 0,
collisionFilter: { group: -1 }, // optional: ignore collisions with player
render: { fillStyle: 'red' }
});
Body.setVelocity(ball, { x: dir.x * speed, y: dir.y * speed }); // initial velocity
balls.push(ball);
World.add(engine.world, ball);
}, 5000);
// Run engine and renderer
Engine.run(engine);
Render.run(render);
</script>
</body>
</html>
Result no 5:
Open the game and you will see the red balls shoot every 5 seconds and bounce off the wall.
Prompt no 6:
Make it so when the ball hits the another ball they both bounce off each other.
Answer no 6:
To make balls bounce each other remove collisionFilter: { group: -1 } inside const ball.
Result no 6:
Open the game and see the balls bounce off each other. Or skip this since it can take some time :D.
Prompt no 7:
Make it so when the player is hit by a ball the game is reset: no balls and player at starting position.
Answer no 7:
Before lines:
Engine.run(engine);
Render.run(render);
add this:
// Listen for collisions
Events.on(engine, 'collisionStart', (event) => {
const pairs = event.pairs;
for (const pair of pairs) {
// Check if player is involved
if (pair.bodyA === player || pair.bodyB === player) {
// Check if the other body is a ball
const ball = pair.bodyA === player ? pair.bodyB : pair.bodyA;
if (balls.includes(ball)) {
// Reset the game
// 1. Remove all balls from world
balls.forEach(b => World.remove(engine.world, b));
balls.length = 0;
// 2. Reset player position
playerRow = rows - 1;
playerCol = cols - 1;
Body.setPosition(player, {
x: playerCol * tileSize + tileSize / 2,
y: playerRow * tileSize + tileSize / 2
});
}
}
}
});
and side note from me: modify const spawnDistance to be equal to tileSize otherwise balls with spawn inside the player and game will immediatelly reset.
Result no 7:
Open the browser and play the game. Avoid the balls as long as you can.
Summary
So we coded the game and saw that we need to be very specific with AI (on Prompt 4). Otherwise it will ignore the features you really need. We must also split the problem to smaller steps as we did here. Current AI is not so powerful to take larger problems and do no mistakes. Well, they can sometimes but I advise splitting the problem. This will make sure you will understand the problem yourself.
Challenges:
Play around and modify balls speed, player controls, ball shoot cooldown and so on. 1.
Ask AI to count and display seconds you lasted as a highscore. 1.
Think of other things you can ask AI to improve this game.
It will be enough for this day.