Skip to content

Game Samples

1. Block breaking game

Code
# include <Siv3D.hpp>

void Main()
{
	// Size of a single block | Size of a single block
	constexpr Size BrickSize{ 40, 20 };

	// Ball speed (pixels / second) | Ball speed (pixels / second)
	constexpr double BallSpeedPerSec = 480.0;

	// Ball velocity | Ball velocity
	Vec2 ballVelocity{ 0, -BallSpeedPerSec };

	// Ball | Ball
	Circle ball{ 400, 400, 8 };

	// Array of bricks | Array of bricks
	Array<Rect> bricks;

	for (int32 y = 0; y < 5; ++y)
	{
		for (int32 x = 0; x < (Scene::Width() / BrickSize.x); ++x)
		{
			bricks << Rect{ (x * BrickSize.x), (60 + y * BrickSize.y), BrickSize };
		}
	}

	while (System::Update())
	{
		// Paddle | Paddle
		const Rect paddle{ Arg::center(Cursor::Pos().x, 500), 60, 10 };

		// Move the ball | Move the ball
		ball.moveBy(ballVelocity * Scene::DeltaTime());

		// Check bricks in sequence | Check bricks in sequence
		for (auto it = bricks.begin(); it != bricks.end(); ++it)
		{
			// If block and ball intersect | If block and ball intersect
			if (it->intersects(ball))
			{
				// If ball intersects with top or bottom of the block | If ball intersects with top or bottom of the block
				if (it->bottom().intersects(ball) || it->top().intersects(ball))
				{
					// Reverse the sign of the Y component of the ball's velocity | Reverse the sign of the Y component of the ball's velocity
					ballVelocity.y *= -1;
				}
				else // If intersecting with left or right side of the block
				{
					// Reverse the sign of the X component of the ball's velocity | Reverse the sign of the X component of the ball's velocity
					ballVelocity.x *= -1;
				}

				// Remove the block from the array (the iterator becomes invalid) | Remove the block from the array (the iterator becomes invalid)
				bricks.erase(it);

				// Do not check any more | Do not check any more
				break;
			}
		}

		// If the ball hits the ceiling | If the ball hits the ceiling
		if ((ball.y < 0) && (ballVelocity.y < 0))
		{
			// Reverse the sign of the Y component of the ball's velocity | Reverse the sign of the Y component of the ball's velocity
			ballVelocity.y *= -1;
		}

		// If the ball hits the left or right wall | If the ball hits the left or right wall
		if (((ball.x < 0) && (ballVelocity.x < 0))
			|| ((Scene::Width() < ball.x) && (0 < ballVelocity.x)))
		{
			// Reverse the sign of the X component of the ball's velocity | Reverse the sign of the X component of the ball's velocity
			ballVelocity.x *= -1;
		}

		// If the ball hits the left or right wall | If the ball hits the left or right wall
		if ((0 < ballVelocity.y) && paddle.intersects(ball))
		{
			// Change the direction (velocity vector) of the ball depending on the distance from the center of the paddle | Change the direction (velocity vector) of the ball depending on the distance from the center of the paddle
			ballVelocity = Vec2{ (ball.x - paddle.center().x) * 10, -ballVelocity.y }.setLength(BallSpeedPerSec);
		}

		// Draw all the bricks | Draw all the bricks
		for (const auto& brick : bricks)
		{
			// Change the color of the brick depending on the Y coordinate | Change the color of the brick depending on the Y coordinate
			brick.stretched(-1).draw(HSV{ brick.y - 40 });
		}

		// Hide the mouse cursor | Hide the mouse cursor
		Cursor::RequestStyle(CursorStyle::Hidden);

		// Draw the ball | Draw the ball
		ball.draw();

		// Draw the paddle | Draw the paddle
		paddle.rounded(3).draw();
	}
}

2. Collecting falling items game

Code
# include <Siv3D.hpp>

// Player class
struct Player
{
	Circle circle{ 400, 530, 30 };

	Texture texture{ U"😃"_emoji };

	// Function to update player state
	void update(double deltaTime)
	{
		const double speed = (deltaTime * 400.0);

		// Move left when [←] key is pressed
		if (KeyLeft.pressed())
		{
			circle.x -= speed;
		}

		// Move right when [→] key is pressed
		if (KeyRight.pressed())
		{
			circle.x += speed;
		}

		// Keep player within screen bounds
		circle.x = Clamp(circle.x, 30.0, 770.0);
	}

	// Function to draw player
	void draw() const
	{
		texture.scaled(0.5).drawAt(circle.center);
	}
};

// Item class
struct Item
{
	Circle circle;

	// Item type (0: candy, 1: cake)
	int32 type;

	void update(double deltaTime)
	{
		// Move item downward
		circle.y += (deltaTime * 200.0);
	}

	// Function to draw item
	void draw(const Array<Texture>& itemTextures) const
	{
		// Draw texture based on item type
		itemTextures[type].scaled(0.5).rotated(circle.y * 0.3_deg).drawAt(circle.center);
	}
};

void UpdateItems(Array<Item>& items, double deltaTime, const Player& player, int32& score)
{
	// Update all item states
	for (auto& item : items)
	{
		item.update(deltaTime);
	}

	// For each item
	for (auto it = items.begin(); it != items.end();)
	{
		// If player and item intersect
		if (player.circle.intersects(it->circle))
		{
			// Add score (candy: 10 points, cake: 50 points)
			score += ((it->type == 0) ? 10 : 50);

			// Remove item
			it = items.erase(it);
		}
		else
		{
			++it;
		}
	}

	// Remove items that fell to the ground
	items.remove_if([](const Item& item) { return (580 < item.circle.y); });
}

// Function to draw background
void DrawBackground()
{
	// Draw sky
	Rect{ 0, 0, 800, 550 }.draw(Arg::top(0.3, 0.6, 1.0), Arg::bottom(0.6, 0.9, 1.0));

	// Draw ground
	Rect{ 0, 550, 800, 50 }.draw(ColorF{ 0.3, 0.6, 0.3 });
}

// Function to draw items
void DrawItems(const Array<Item>& items, const Array<Texture>& itemTextures)
{
	for (const auto& item : items)
	{
		item.draw(itemTextures);
	}
}

// Function to draw UI
void DrawUI(int32 score, double remainingTime, const Font& font)
{
	// Draw score
	font(U"SCORE: {}"_fmt(score)).draw(30, Vec2{ 20, 20 });

	// Draw remaining time
	font(U"TIME: {:.0f}"_fmt(remainingTime)).draw(30, Arg::topRight(780, 20));

	if (remainingTime <= 0.0)
	{
		font(U"TIME'S UP!").drawAt(80, Vec2{ 400, 270 }, ColorF{ 0.3 });
	}
}

void Main()
{
	const Font font{ FontMethod::MSDF, 48, Typeface::Bold };

	// Item texture array
	const Array<Texture> itemTextures =
	{
		Texture{ U"🍬"_emoji },
		Texture{ U"🍰"_emoji },
	};

	Player player;

	// Item array
	Array<Item> items;
	items << Item{ Circle{ 200, 200, 30 }, 0 };
	items << Item{ Circle{ 600, 100, 30 }, 1 };

	// Item spawn interval (seconds)
	const double spawnInterval = 0.8;

	// Accumulated time (seconds)
	double accumulatedTime = 0.0;

	// Score
	int32 score = 0;

	// Remaining time (seconds)
	double remainingTime = 20.0;

	while (System::Update())
	{
		/////////////////////////////////
		//
		//	Update
		//
		/////////////////////////////////

		const double deltaTime = Scene::DeltaTime();

		// Decrease remaining time
		remainingTime = Max((remainingTime - deltaTime), 0.0);

		// If game is still running
		if (0.0 < remainingTime)
		{
			// Increase accumulated time
			accumulatedTime += deltaTime;

			// If accumulated time exceeds interval
			if (spawnInterval < accumulatedTime)
			{
				// Add new item
				items << Item{ Circle{ Random(30.0, 770.0), -30, 30 }, Random(0, 1) };

				// Reduce accumulated time by interval
				accumulatedTime -= spawnInterval;
			}

			// Update player state
			player.update(deltaTime);

			// Update all item states
			UpdateItems(items, deltaTime, player, score);
		}
		else
		{
			items.clear();
		}

		/////////////////////////////////
		//
		//	Drawing
		//
		/////////////////////////////////

		// Draw background
		DrawBackground();

		// Draw player
		player.draw();

		// Draw all items
		DrawItems(items, itemTextures);

		// Draw UI
		DrawUI(score, remainingTime, font);
	}
}

3. 15 puzzle

Code
# include <Siv3D.hpp>

// Check if two pieces are adjacent
bool Swappable(int32 a, int32 b)
{
	return ((a / 4 == b / 4) && (AbsDiff(a, b) == 1))
		|| ((a % 4 == b % 4) && (AbsDiff(a, b) == 4));
}

void Main()
{
	Scene::SetBackground(ColorF{ 0.8, 0.9, 1.0 });

	// Piece size
	constexpr int32 CellSize = 100;

	// Position
	constexpr Point Offset{ 60, 40 };

	// Select image from dialog
	const Image image = Dialog::OpenImage();

	// Crop to square
	const Texture texture{ image.squareClipped(), TextureDesc::Mipped };

	// Shuffle puzzle with random operations
	Array<int32> pieces = Range(0, 15);
	{
		// Empty space position
		int32 blankPos = 15;

		for (int32 i = 0; i < 1000; ++i)
		{
			const int32 to = (blankPos + Sample({ -4, -1, 1, 4 }));

			if (InRange(to, 0, 15) && Swappable(blankPos, to))
			{
				std::swap(pieces[blankPos], pieces[to]);
				blankPos = to;
			}
		}
	}

	// Currently grabbed piece number
	Optional<int32> grabbed;

	while (System::Update())
	{
		Rect{ Offset, (CellSize * 4) }
			.drawShadow(Vec2{ 0, 2 }, 12, 8)
			.draw(ColorF{ 0.25 })
			.drawFrame(0, 8, ColorF{ 0.3, 0.5, 0.7 });

		if (not MouseL.pressed())
		{
			grabbed.reset();
		}

		for (int32 i = 0; i < 16; ++i)
		{
			const int32 pieceID = pieces[i];
			const Rect rect = Rect{ (CellSize * (i % 4)), (CellSize * (i / 4)), CellSize }.movedBy(Offset);

			if (pieceID == 15)
			{
				if (grabbed && rect.mouseOver() && Swappable(i, grabbed.value()))
				{
					std::swap(pieces[i], pieces[grabbed.value()]);
					grabbed = i;
				}

				continue;
			}

			if (rect.leftClicked())
			{
				grabbed = i;
			}

			rect(texture.uv((pieceID % 4 * 0.25), (pieceID / 4 * 0.25), 0.25, 0.25))
				.draw()
				.drawFrame(1, 0, ColorF{ 1.0, 0.75 });

			if (grabbed == i)
			{
				rect.draw(ColorF{ 1.0, 0.5, 0.0, 0.3 });
			}

			if (rect.mouseOver())
			{
				Cursor::RequestStyle(CursorStyle::Hand);
			}
		}

		// Draw reference image
		texture.resized(180)
			.draw((Offset.x + CellSize * 4 + 40), Offset.y)
			.drawFrame(0, 4, ColorF{ 0.3, 0.5, 0.7 });
	}
}

4. Number chain

Code
# include <Siv3D.hpp>

struct Bubble
{
	// Bubble circle radius
	static constexpr int32 Radius = 30;

	// Bubble circle
	Circle circle;

	// Bubble index
	int32 index;

	// True if connected
	bool connected = false;

	void draw(const Font& font) const
	{
		if (connected)
		{
			circle.drawShadow(Vec2{ 1, 2 }, 10, 3).draw()
				.drawFrame(2, 0, ColorF{ 0.3, 0.6, 1.0 });
		}
		else
		{
			circle.draw();
		}

		font(index + 1).drawAt(36, circle.center, ColorF{ 0.25 });
	}
};

// Check if bubbles overlap each other
bool CheckBubbles(const Array<Bubble>& bubbles)
{
	for (size_t i = 0; i < bubbles.size(); ++i)
	{
		for (size_t k = (i + 1); k < bubbles.size(); ++k)
		{
			// Overlapping
			if (bubbles[i].circle.stretched(5)
				.intersects(bubbles[k].circle.stretched(5)))
			{
				return false;
			}
		}
	}

	return true;
}

// Generate specified number of bubbles without overlap
Array<Bubble> MakeBubbles(int32 count)
{
	Array<Bubble> bubbles(count);

	do
	{
		for (int32 i = 0; i < count; ++i)
		{
			// Bubble index
			bubbles[i].index = i;

			// Bubble circle
			bubbles[i].circle.set(RandomVec2(Circle{ Scene::Center(), (Scene::Height() / 2 - Bubble::Radius) }), Bubble::Radius);
		}
	} while (not CheckBubbles(bubbles));

	return bubbles;
}

// Number of bubbles at specified level
constexpr int32 GetBubbleCount(int32 level)
{
	return Min(level, 15);
}

// Time limit at specified level (seconds)
constexpr Duration GetTime(int32 level)
{
	return Duration{ (level <= 15) ? 8.0 : 8.0 - Min((level - 15) * 0.05, 2.0) };
}

void Main()
{
	Scene::SetBackground(Palette::White);

	const Font font{ FontMethod::MSDF, 48, Typeface::Medium };

	Effect effect;

	// Create sound effects
	const Array<PianoKey> keys = { PianoKey::C5,  PianoKey::D5, PianoKey::E5, PianoKey::F5, PianoKey::G5,
		PianoKey::A5, PianoKey::B5, PianoKey::C6, PianoKey::D6, PianoKey::E6,
		PianoKey::F6, PianoKey::G6, PianoKey::A6, PianoKey::B6, PianoKey::C7 };
	const Array<Audio> sounds = keys.map([](auto k) { return Audio{ GMInstrument::Glockenspiel, k, 0.3s }; });

	// High score
	int32 highScore = 0;

	// Current level
	int32 level = 1;

	// Connection count
	int32 connected = 0;

	// Remaining time timer
	Timer timer{ GetTime(level), StartImmediately::Yes };

	// Bubbles
	Array<Bubble> bubbles = MakeBubbles(GetBubbleCount(level));

	while (System::Update())
	{
		const double delta = Scene::DeltaTime();

		for (auto& bubble : bubbles)
		{
			if ((bubble.index == connected)
				&& (not bubble.connected)
				&& bubble.circle.stretched(10).mouseOver())
			{
				// Mark as connected
				bubble.connected = true;

				// Increase connection count
				++connected;

				// Add effect
				effect.add([pos = Cursor::Pos()](double t)
				{
					Circle{ pos, (Bubble::Radius + t * 200) }.drawFrame(2, 0, ColorF{ 0.2, 0.5, 1.0, (1.0 - t * 2.5) });
					return (t < 0.4);
				});

				// Play sound based on bubble number
				sounds[bubble.index].playOneShot(0.8);
			}

			// Move bubbles around circumference
			bubble.circle.center = OffsetCircular{ Scene::Center(), bubble.circle.center }
				.rotate((IsEven(bubble.index) ? 20_deg : -20_deg) * delta);
		}

		// When all bubbles are connected or time runs out
		if (const bool failed = timer.reachedZero();
			(connected == GetBubbleCount(level)) || failed)
		{
			// Update level
			level = (failed ? 1 : ++level);

			// Reset connection count
			connected = 0;

			// Reset time limit
			timer = Timer{ GetTime(level), StartImmediately::Yes };

			// Regenerate bubbles
			bubbles = MakeBubbles(GetBubbleCount(level));

			// Update high score
			highScore = Max(highScore, level);

			// Update title
			Window::SetTitle(U"Level {} (High score: {})"_fmt(level, highScore));
		}

		// Draw background representing time limit
		RectF{ Scene::Width(), (Scene::Height() * timer.progress0_1()) }.draw(HSV{ (level * 30), 0.3, 0.9 });

		// Draw lines connecting bubbles
		for (int32 i = 0; i < (connected - 1); ++i)
		{
			Line{ bubbles[i].circle.center, bubbles[i + 1].circle.center }.draw(3, Palette::Orange);
		}

		// Draw bubbles
		for (const auto& bubble : bubbles)
		{
			bubble.draw(font);
		}

		// Draw effects
		effect.update();
	}
}

5. Typing game

Code
# include <Siv3D.hpp>

void Main()
{
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// List of problem texts
	const Array<String> texts =
	{
		U"Practice makes perfect.",
		U"Don't cry over spilt milk.",
		U"Faith will move mountains.",
		U"Nothing ventured, nothing gained.",
		U"Bad news travels fast.",
	};

	// Randomly select problem text
	String target = texts.choice();

	// Input string
	String input;

	const Font font{ FontMethod::MSDF, 48, Typeface::Bold };

	while (System::Update())
	{
		// Text input (TextInputMode::DenyControl: don't accept enter, tab, backspace)
		TextInput::UpdateText(input, TextInputMode::DenyControl);

		// Delete incorrect input
		while (not target.starts_with(input))
		{
			input.pop_back();
		}

		// Move to next problem if matched
		if (input == target)
		{
			// Randomly select problem text
			target = texts.choice();

			// Clear input string	
			input.clear();
		}

		// Draw problem text
		font(target).draw(40, Vec2{ 40, 80 }, ColorF{ 0.98 });

		// Draw input text
		font(input).draw(40, Vec2{ 40, 80 }, ColorF{ 0.12 });
	}
}

6. Emoji tower

Code
# include <Siv3D.hpp>

void Main()
{
	// Resize window to 1280x720
	Window::Resize(1280, 720);

	// Set background color
	Scene::SetBackground(ColorF{ 0.2, 0.7, 1.0 });

	// Appearing emojis
	const Array<String> emojis = { U"🐘", U"🐧", U"🐐", U"🐤" };

	Array<MultiPolygon> polygons;

	Array<Texture> textures;

	for (const auto& emoji : emojis)
	{
		// Create shape information from emoji image
		polygons << Emoji::CreateImage(emoji).alphaToPolygonsCentered().simplified(2.0);

		// Create texture from emoji image
		textures << Texture{ Emoji{ emoji } };
	}

	// 2D physics simulation step (seconds)
	constexpr double StepTime = (1.0 / 200.0);

	// 2D physics simulation accumulated time (seconds)
	double accumulatedTime = 0.0;

	// 2D physics world
	P2World world;

	// [_] Ground
	const P2Body ground = world.createLine(P2Static, Vec2{ 0, 0 }, Line{ -300, 0, 300, 0 });

	// Animal bodies
	Array<P2Body> bodies;

	// Correspondence table between body ID and emoji index
	HashTable<P2BodyID, size_t> table;

	// Emoji index
	size_t index = Random(polygons.size() - 1);

	// 2D camera
	Camera2D camera{ Vec2{ 0, -200 } };

	while (System::Update())
	{
		accumulatedTime += Scene::DeltaTime();

		while (StepTime <= accumulatedTime)
		{
			// Update 2D physics world
			world.update(StepTime);

			accumulatedTime -= StepTime;
		}

		// Remove bodies that fell below ground
		for (auto it = bodies.begin(); it != bodies.end();)
		{
			if (100 < it->getPos().y)
			{
				// Also remove from correspondence table
				table.erase(it->id());

				it = bodies.erase(it);
			}
			else
			{
				++it;
			}
		}

		// Update 2D camera
		camera.update();
		{
			// Create Transformer2D from 2D camera
			const auto t = camera.createTransformer();

			// If left clicked
			if (MouseL.down())
			{
				// Add body
				bodies << world.createPolygons(P2Dynamic, Cursor::PosF(), polygons[index], P2Material{ 0.1, 0.0, 1.0 });

				// Add body ID and emoji index pair to correspondence table
				table.emplace(bodies.back().id(), std::exchange(index, Random(polygons.size() - 1)));
			}

			// Draw all bodies
			for (const auto& body : bodies)
			{
				textures[table[body.id()]].rotated(body.getAngle()).drawAt(body.getPos());
			}

			// Draw ground
			ground.draw(Palette::Green);

			// Draw currently controllable emoji
			textures[index].drawAt(Cursor::PosF(), ColorF{ 1.0, (0.5 + Periodic::Sine0_1(1s) * 0.5) });
		}

		// Draw 2D camera controls
		camera.draw(Palette::Orange);
	}
}

7. Shooting game

Code
# include <Siv3D.hpp>

// Function to create random enemy position
Vec2 GenerateEnemy()
{
	return RandomVec2({ 50, 750 }, -20);
}

void Main()
{
	Scene::SetBackground(ColorF{ 0.1, 0.2, 0.7 });

	const Font font{ FontMethod::MSDF, 48 };

	// Player texture
	const Texture playerTexture{ U"🤖"_emoji };
	// Enemy texture
	const Texture enemyTexture{ U"👾"_emoji };

	// Player
	Vec2 playerPos{ 400, 500 };
	// Enemy
	Array<Vec2> enemies = { GenerateEnemy() };

	// Player shots
	Array<Vec2> playerBullets;
	// Enemy shots
	Array<Vec2> enemyBullets;

	// Player speed
	constexpr double PlayerSpeed = 550.0;
	// Player shot speed
	constexpr double PlayerBulletSpeed = 500.0;
	// Enemy speed
	constexpr double EnemySpeed = 100.0;
	// Enemy shot speed
	constexpr double EnemyBulletSpeed = 300.0;

	// Initial enemy spawn interval (seconds)
	constexpr double InitialEnemySpawnInterval = 2.0;
	// Enemy spawn interval (seconds)
	double enemySpawnTime = InitialEnemySpawnInterval;
	// Enemy spawn accumulated time (seconds)
	double enemyAccumulatedTime = 0.0;

	// Player shot cooltime (seconds)
	constexpr double PlayerShotCoolTime = 0.1;
	// Player shot cooltime timer (seconds)
	double playerShotTimer = 0.0;

	// Enemy shot cooltime (seconds)
	constexpr double EnemyShotCoolTime = 0.9;
	// Enemy shot cooltime timer (seconds)
	double enemyShotTimer = 0.0;

	Effect effect;

	// High score
	int32 highScore = 0;
	// Current score
	int32 score = 0;

	while (System::Update())
	{
		// Game over check
		bool gameover = false;

		const double deltaTime = Scene::DeltaTime();
		enemyAccumulatedTime += deltaTime;
		playerShotTimer = Min((playerShotTimer + deltaTime), PlayerShotCoolTime);
		enemyShotTimer += deltaTime;

		// Generate enemies
		while (enemySpawnTime <= enemyAccumulatedTime)
		{
			enemyAccumulatedTime -= enemySpawnTime;
			enemySpawnTime = Max(enemySpawnTime * 0.95, 0.3);
			enemies << GenerateEnemy();
		}

		// Player movement
		const Vec2 move = Vec2{ (KeyRight.pressed() - KeyLeft.pressed()), (KeyDown.pressed() - KeyUp.pressed()) }
			.setLength(deltaTime * PlayerSpeed * (KeyShift.pressed() ? 0.5 : 1.0));
		playerPos.moveBy(move).clamp(Scene::Rect());

		// Player shot firing
		if (PlayerShotCoolTime <= playerShotTimer)
		{
			playerShotTimer -= PlayerShotCoolTime;
			playerBullets << playerPos.movedBy(0, -50);
		}

		// Move player shots
		for (auto& playerBullet : playerBullets)
		{
			playerBullet.y += (deltaTime * -PlayerBulletSpeed);
		}
		// Remove player shots that went off screen
		playerBullets.remove_if([](const Vec2& b) { return (b.y < -40); });

		// Move enemies
		for (auto& enemy : enemies)
		{
			enemy.y += (deltaTime * EnemySpeed);
		}
		// Remove enemies that went off screen
		enemies.remove_if([&](const Vec2& e)
		{
			if (700 < e.y)
			{
				// Game over if enemy goes off screen
				gameover = true;
				return true;
			}
			else
			{
				return false;
			}
		});

		// Fire enemy shots
		if (EnemyShotCoolTime <= enemyShotTimer)
		{
			enemyShotTimer -= EnemyShotCoolTime;

			for (const auto& enemy : enemies)
			{
				enemyBullets << enemy;
			}
		}

		// Move enemy shots
		for (auto& enemyBullet : enemyBullets)
		{
			enemyBullet.y += (deltaTime * EnemyBulletSpeed);
		}
		// Remove player shots that went off screen
		enemyBullets.remove_if([](const Vec2& b) {return (700 < b.y); });

		////////////////////////////////
		//
		//	Hit detection
		//
		////////////////////////////////

		// Enemy vs player shot
		for (auto itEnemy = enemies.begin(); itEnemy != enemies.end();)
		{
			const Circle enemyCircle{ *itEnemy, 40 };
			bool skip = false;

			for (auto itBullet = playerBullets.begin(); itBullet != playerBullets.end();)
			{
				if (enemyCircle.intersects(*itBullet))
				{
					// Add explosion effect
					effect.add([pos = *itEnemy](double t)
					{
						const double t2 = ((0.5 - t) * 2.0);
						Circle{ pos, (10 + t * 280) }.drawFrame((20 * t2), ColorF{ 1.0, (t2 * 0.5) });
						return (t < 0.5);
					});

					itEnemy = enemies.erase(itEnemy);
					playerBullets.erase(itBullet);
					++score;
					skip = true;
					break;
				}

				++itBullet;
			}

			if (skip)
			{
				continue;
			}

			++itEnemy;
		}

		// Enemy shot vs player
		for (const auto& enemyBullet : enemyBullets)
		{
			// If enemy shot approaches within 20 pixels of playerPos
			if (enemyBullet.distanceFrom(playerPos) <= 20)
			{
				// Game over
				gameover = true;
				break;
			}
		}

		// Reset if game over
		if (gameover)
		{
			playerPos = Vec2{ 400, 500 };
			enemies.clear();
			playerBullets.clear();
			enemyBullets.clear();
			enemySpawnTime = InitialEnemySpawnInterval;
			highScore = Max(highScore, score);
			score = 0;
		}

		////////////////////////////////
		//
		//	Drawing
		//
		////////////////////////////////

		// Draw background animation
		for (int32 i = 0; i < 12; ++i)
		{
			const double a = Periodic::Sine0_1(2s, Scene::Time() - (2.0 / 12 * i));
			Rect{ 0, (i * 50), 800, 50 }.draw(ColorF(1.0, a * 0.2));
		}

		// Draw player
		playerTexture.resized(80).flipped().drawAt(playerPos);

		// Draw player shots
		for (const auto& playerBullet : playerBullets)
		{
			Circle{ playerBullet, 8 }.draw(Palette::Orange);
		}

		// Draw enemies
		for (const auto& enemy : enemies)
		{
			enemyTexture.resized(60).drawAt(enemy);
		}

		// Draw enemy shots
		for (const auto& enemyBullet : enemyBullets)
		{
			Circle{ enemyBullet, 4 }.draw(Palette::White);
		}

		// Draw explosion effects
		effect.update();

		// Draw score
		font(U"{} [{}]"_fmt(score, highScore)).draw(30, Arg::bottomRight(780, 580));
	}
}

8. Pinball

Code
# include <Siv3D.hpp>

// Function to create frame vertex list
LineString CreateFrame(const Vec2& leftAnchor, const Vec2& rightAnchor)
{
	Array<Vec2> points = { leftAnchor, Vec2{ -70, -20 } };
	for (int32 i = -30; i <= 30; ++i)
	{
		points << OffsetCircular(Vec2{ 0.0, -120 }, 70, (i * 3_deg));
	}
	points << Vec2{ 70, -20 } << rightAnchor;
	return LineString{ points };
}

// Function to determine color based on contact
ColorF GetColor(const P2Body& body, const HashSet<P2BodyID>& list)
{
	return list.contains(body.id()) ? Palette::White : Palette::Orange;
}

void Main()
{
	// Set background color
	Scene::SetBackground(ColorF(0.2, 0.3, 0.4));

	// 2D physics simulation step (seconds)
	constexpr double StepTime = (1.0 / 200.0);

	// 2D physics simulation accumulated time (seconds)
	double accumulatedTime = 0.0;

	// Physics world
	P2World world{ 60.0 };

	// Left and right flipper axis coordinates
	constexpr Vec2 LeftFlipperAnchor{ -25, 10 }, RightFlipperAnchor{ 25, 10 };

	// Fixed frames
	Array<P2Body> frames;
	{
		// Perimeter
		frames << world.createLineString(P2Static, Vec2{ 0, 0 }, CreateFrame(LeftFlipperAnchor, RightFlipperAnchor));
		// Top left (
		frames << world.createLineString(P2Static, Vec2{ 0, 0 }, LineString{ Range(-25, -10).map([=](int32 i) { return OffsetCircular(Vec2{ 0.0, -120 }, 55, (i * 3_deg)).toVec2(); }) });
		// Top right )
		frames << world.createLineString(P2Static, Vec2{ 0, 0 }, LineString{ Range(10, 25).map([=](int32 i) { return OffsetCircular(Vec2{ 0.0, -120 }, 55, (i * 3_deg)).toVec2(); }) });
	}

	// Bumpers
	Array<P2Body> bumpers;
	{
		// ● x3
		{
			const P2Material material{ .restitution = 1.0, .restitutionThreshold = 0.1 };
			bumpers << world.createCircle(P2Static, Vec2{ 0, -170 }, 5, material);
			bumpers << world.createCircle(P2Static, Vec2{ -20, -150 }, 5, material);
			bumpers << world.createCircle(P2Static, Vec2{ 20, -150 }, 5, material);
		}
		// ▲ x2
		{
			const P2Material material{ .restitution = 0.8, .restitutionThreshold = 0.1 };
			bumpers << world.createTriangle(P2Static, Vec2{ 0, 0 }, Triangle{ -60, -50, -40, -15, -60, -30 }, material);
			bumpers << world.createTriangle(P2Static, Vec2{ 0, 0 }, Triangle{ 60, -50, 60, -30, 40, -15 }, material);
		}
	}

	const P2Material softMaterial{ .density = 0.1, .restitution = 0.0 };

	// Left flipper
	P2Body leftFlipper = world.createRect(P2Dynamic, LeftFlipperAnchor, RectF{ 0, 0.4, 21, 4.5 }, softMaterial);
	// Left flipper joint
	const P2PivotJoint leftJoint = world.createPivotJoint(frames[0], leftFlipper, LeftFlipperAnchor).setLimits(-20_deg, 25_deg).setLimitsEnabled(true);

	// Right flipper
	P2Body rightFlipper = world.createRect(P2Dynamic, RightFlipperAnchor, RectF{ -21, 0.4, 21, 4.5 }, softMaterial);
	// Right flipper joint
	const P2PivotJoint rightJoint = world.createPivotJoint(frames[0], rightFlipper, RightFlipperAnchor).setLimits(-25_deg, 20_deg).setLimitsEnabled(true);

	// Spinner +
	const P2Body spinner = world.createRect(P2Dynamic, Vec2{ -58, -120 }, SizeF{ 20, 1 }, softMaterial).addRect(RectF{ Arg::center(0, 0), 1, 20 }, P2Material{ 0.01, 0.0 });
	// Spinner joint
	P2PivotJoint spinnerJoint = world.createPivotJoint(frames[0], spinner, Vec2{ -58, -120 }).setMaxMotorTorque(0.05).setMotorSpeed(0).setMotorEnabled(true);

	// Windmill |
	frames << world.createLine(P2Static, Vec2{ 0, 0 }, Line{ -40, -60, -40, -40 });
	// Windmill wing /
	const P2Body windmillWing = world.createRect(P2Dynamic, Vec2{ -40, -60 }, SizeF{ 30, 2 }, P2Material{ 0.1, 0.8 });
	// Windmill joint
	const P2PivotJoint windmillJoint = world.createPivotJoint(frames.back(), windmillWing, Vec2{ -40, -60 }).setMotorSpeed(240_deg).setMaxMotorTorque(10000.0).setMotorEnabled(true);

	// Pendulum axis
	const P2Body pendulumBase = world.createPlaceholder(P2Static, Vec2{ 0, -190 });
	// Pendulum ●
	P2Body pendulum = world.createCircle(P2Dynamic, Vec2{ 0, -120 }, 4, P2Material{ 0.1, 1.0 });
	// Pendulum joint
	const P2DistanceJoint pendulumJoint = world.createDistanceJoint(pendulumBase, Vec2{ 0, -190 }, pendulum, Vec2{ 0, -120 }, 70);

	// Elevator top ●
	const P2Body elevatorA = world.createCircle(P2Static, Vec2{ 40, -100 }, 3);
	// Elevator floor -
	const P2Body elevatorB = world.createRect(P2Dynamic, Vec2{ 40, -100 }, SizeF{ 20, 2 });
	// Elevator joint
	P2SliderJoint elevatorSliderJoint = world.createSliderJoint(elevatorA, elevatorB, Vec2{ 40, -100 }, Vec2::Down()).setLimits(5, 50).setLimitEnabled(true).setMaxMotorForce(10000).setMotorSpeed(-100);

	// Ball 〇
	const P2Body ball = world.createCircle(P2Dynamic, Vec2{ -40, -120 }, 4, P2Material{ 0.05, 0.0 });
	const P2BodyID ballID = ball.id();

	// Elevator animation stopwatch
	Stopwatch sliderStopwatch{ StartImmediately::Yes };

	// 2D camera
	const Camera2D camera{ Vec2{ 0, -80 }, 2.4 };

	while (System::Update())
	{
		////////////////////////////////
		//
		//	Update
		//
		////////////////////////////////

		if (4s < sliderStopwatch)
		{
			// Stop elevator lifting
			elevatorSliderJoint.setMotorEnabled(false);
			sliderStopwatch.restart();
		}
		else if (2s < sliderStopwatch)
		{
			// Elevator lifting
			elevatorSliderJoint.setMotorEnabled(true);
		}

		// IDs of bodies in contact with ball
		HashSet<P2BodyID> collidedIDs;

		// Update physics world
		for (accumulatedTime += Scene::DeltaTime(); StepTime <= accumulatedTime; accumulatedTime -= StepTime)
		{
			// Resistance to suppress pendulum oscillation
			pendulum.applyForce(Vec2{ (pendulum.getVelocity().x < 0.0) ? 0.0001 : -0.0001, 0.0 });

			// Left flipper control
			leftFlipper.applyTorque(KeyLeft.pressed() ? -80 : 40);

			// Right flipper control
			rightFlipper.applyTorque(KeyRight.pressed() ? 80 : -40);

			world.update(StepTime);

			// Store IDs of bodies in contact with ball
			for (auto&& [pair, collision] : world.getCollisions())
			{
				if (pair.a == ballID)
				{
					collidedIDs.emplace(pair.b);
				}
				else if (pair.b == ballID)
				{
					collidedIDs.emplace(pair.a);
				}
			}
		}

		////////////////////////////////
		//
		//	Drawing
		//
		////////////////////////////////

		// Drawing Transformer2D
		const auto transformer = camera.createTransformer();

		// Draw frames
		for (const auto& frame : frames)
		{
			frame.draw(Palette::Skyblue);
		}

		// Draw spinner
		spinner.draw(GetColor(spinner, collidedIDs));

		// Draw bumpers
		for (const auto& bumper : bumpers)
		{
			bumper.draw(GetColor(bumper, collidedIDs));
		}

		// Draw windmill
		windmillWing.draw(GetColor(windmillWing, collidedIDs));

		// Draw pendulum
		pendulum.draw(GetColor(pendulum, collidedIDs));

		// Draw elevator
		elevatorA.draw(GetColor(elevatorA, collidedIDs));
		elevatorB.draw(GetColor(elevatorB, collidedIDs));

		// Draw ball
		ball.draw(Palette::White);

		// Draw flippers
		leftFlipper.draw(Palette::Orange);
		rightFlipper.draw(Palette::Orange);

		// Visualize joints
		leftJoint.draw(Palette::Red);
		rightJoint.draw(Palette::Red);
		spinnerJoint.draw(Palette::Red);
		windmillJoint.draw(Palette::Red);
		pendulumJoint.draw(Palette::Red);
		elevatorSliderJoint.draw(Palette::Red);
	}
}

Code
# include <Siv3D.hpp>

// Game save data
struct SaveData
{
	double cookies;

	Array<int32> itemCounts;

	// Define member function for serialization support
	template <class Archive>
	void SIV3D_SERIALIZE(Archive& archive)
	{
		archive(cookies, itemCounts);
	}
};

/// @brief Item button
/// @param rect Button area
/// @param texture Button emoji
/// @param font Font for text drawing
/// @param name Item name
/// @param desc Item description
/// @param count Item possession count
/// @param enabled Whether button can be pressed
/// @return true if button was pressed, false otherwise
bool Button(const Rect& rect, const Texture& texture, const Font& font, const String& name, const String& desc, int32 count, bool enabled)
{
	if (enabled)
	{
		rect.draw(ColorF{ 0.3, 0.5, 0.9, 0.8 });

		rect.drawFrame(2, 2, ColorF{ 0.5, 0.7, 1.0 });

		if (rect.mouseOver())
		{
			Cursor::RequestStyle(CursorStyle::Hand);
		}
	}
	else
	{
		rect.draw(ColorF{ 0.0, 0.4 });

		rect.drawFrame(2, 2, ColorF{ 0.5 });
	}

	texture.scaled(0.5).drawAt(rect.x + 50, rect.y + 50);

	font(name).draw(30, rect.x + 100, rect.y + 15, Palette::White);

	font(desc).draw(18, rect.x + 102, rect.y + 60, Palette::White);

	font(count).draw(50, Arg::rightCenter((rect.x + rect.w - 20), (rect.y + 50)), Palette::White);

	return (enabled && rect.leftClicked());
}

// Cookie falling effect
struct CookieBackgroundEffect : IEffect
{
	// Initial position
	Vec2 m_start;

	// Rotation angle
	double m_angle;

	// Texture
	Texture m_texture;

	CookieBackgroundEffect(const Vec2& start, const Texture& texture)
		: m_start{ start }
		, m_angle{ Random(2_pi) }
		, m_texture{ texture } {}

	bool update(double t) override
	{
		const Vec2 pos = m_start + 0.5 * t * t * Vec2{ 0, 120 };

		m_texture.scaled(0.3).rotated(m_angle).drawAt(pos, ColorF{ 1.0, (1.0 - t / 3.0) });

		return (t < 3.0);
	}
};

// Cookie dancing effect
struct CookieEffect : IEffect
{
	// Initial position
	Vec2 m_start;

	// Initial velocity
	Vec2 m_velocity;

	// Scale factor
	double m_scale;

	// Rotation angle
	double m_angle;

	// Texture
	Texture m_texture;

	CookieEffect(const Vec2& start, const Texture& texture)
		: m_start{ start }
		, m_velocity{ Circular{ 80, Random(-40_deg, 40_deg) } }
		, m_scale{ Random(0.2, 0.3) }
		, m_angle{ Random(2_pi) }
		, m_texture{ texture } {}

	bool update(double t) override
	{
		const Vec2 pos = m_start
			+ m_velocity * t + 0.5 * t * t * Vec2{ 0, 120 };

		m_texture.scaled(m_scale).rotated(m_angle).drawAt(pos, ColorF{ 1.0, (1.0 - t) });

		return (t < 1.0);
	}
};

// "+1" rising effect
struct PlusOneEffect : IEffect
{
	// Initial position
	Vec2 m_start;

	// Font
	Font m_font;

	PlusOneEffect(const Vec2& start, const Font& font)
		: m_start{ start }
		, m_font{ font } {}

	bool update(double t) override
	{
		m_font(U"+1").drawAt(24, m_start.movedBy(0, t * -120), ColorF{ 1.0, (1.0 - t) });

		return (t < 1.0);
	}
};

// Item data
struct Item
{
	// Item emoji
	Texture emoji;

	// Item name
	String name;

	// Cost when purchasing item for the first time
	int32 initialCost;

	// Item CPS
	int32 cps;

	// Returns purchase cost when owning count items
	int32 getCost(int32 count) const
	{
		return initialCost * (count + 1);
	}
};

// Cookie spring
class CookieSpring
{
public:

	void update(double deltaTime, bool pressed)
	{
		// Add to spring accumulated time
		m_accumulatedTime += deltaTime;

		while (0.005 <= m_accumulatedTime)
		{
			// Spring force (direction to cancel change)
			double force = (-0.02 * m_x);

			// Force when screen is pressed
			if (pressed)
			{
				force += 0.004;
			}

			// Apply force to velocity (also dampen)
			m_velocity = (m_velocity + force) * 0.92;

			// Reflect in position
			m_x += m_velocity;

			m_accumulatedTime -= 0.005;
		}
	}

	double get() const
	{
		return m_x;
	}

private:

	// Spring extension
	double m_x = 0.0;

	// Spring velocity
	double m_velocity = 0.0;

	// Spring accumulated time
	double m_accumulatedTime = 0.0;
};

// Function to draw cookie halo
void DrawHalo(const Vec2& center)
{
	for (int32 i = 0; i < 4; ++i)
	{
		double startAngle = Scene::Time() * 15_deg + i * 90_deg;
		Circle{ center, 180 }.drawPie(startAngle, 60_deg, ColorF{ 1.0, 0.3 }, ColorF{ 1.0, 0.0 });
	}

	for (int32 i = 0; i < 6; ++i)
	{
		double startAngle = Scene::Time() * -15_deg + i * 60_deg;
		Circle{ center, 180 }.drawPie(startAngle, 40_deg, ColorF{ 1.0, 0.3 }, ColorF{ 1.0, 0.0 });
	}
}

// Function to calculate CPS based on item ownership counts
int32 CalculateCPS(const Array<Item>& ItemTable, const Array<int32>& itemCounts)
{
	int32 cps = 0;

	for (size_t i = 0; i < ItemTable.size(); ++i)
	{
		cps += ItemTable[i].cps * itemCounts[i];
	}

	return cps;
}

void Main()
{
	// Cookie emoji
	const Texture texture{ U"🍪"_emoji };

	// Item data
	const Array<Item> ItemTable = {
		{ Texture{ U"🌾"_emoji }, U"Cookie Farm", 10, 1 },
		{ Texture{ U"🏭"_emoji }, U"Cookie Factory", 100, 10 },
		{ Texture{ U"⚓"_emoji }, U"Cookie Port", 1000, 100 },
	};

	// Number of each item owned
	Array<int32> itemCounts(ItemTable.size()); // = { 0, 0, 0 }

	// Font
	const Font font{ FontMethod::MSDF, 48, Typeface::Bold };

	// Cookie click circle
	constexpr Circle CookieCircle{ 170, 300, 100 };

	// Effects
	Effect effectBackground, effect;

	// Cookie spring
	CookieSpring cookieSpring;

	// Number of cookies
	double cookies = 0;

	// Game elapsed time accumulation
	double accumulatedTime = 0.0;

	// Background cookie accumulated time
	double cookieBackgroundAccumulatedTime = 0.0;

	// Load save data if found
	{
		// Open binary file
		Deserializer<BinaryReader> reader{ U"game.save" };

		if (reader) // If successfully opened
		{
			SaveData saveData;

			reader(saveData);

			cookies = saveData.cookies;

			itemCounts = saveData.itemCounts;
		}
	}

	while (System::Update())
	{
		// Calculate cookies per second production
		const int32 cps = CalculateCPS(ItemTable, itemCounts);

		// Add game elapsed time
		accumulatedTime += Scene::DeltaTime();

		// If accumulated 0.1 seconds or more
		if (0.1 <= accumulatedTime)
		{
			accumulatedTime -= 0.1;

			// Add 0.1 seconds worth of cookie production
			cookies += (cps * 0.1);
		}

		// Background cookies
		{
			// Calculate appropriate interval for background cookie generation from cps (gradually decrease to prevent too many, with lower limit)
			const double cookieBackgroundSpawnTime = cps ? Max(1.0 / Math::Log2(cps * 2), 0.03) : Math::Inf;

			if (cps)
			{
				cookieBackgroundAccumulatedTime += Scene::DeltaTime();
			}

			while (cookieBackgroundSpawnTime <= cookieBackgroundAccumulatedTime)
			{
				effectBackground.add<CookieBackgroundEffect>(RandomVec2(Rect{ 0, -150, 800, 100 }), texture);

				cookieBackgroundAccumulatedTime -= cookieBackgroundSpawnTime;
			}
		}

		// Update cookie spring
		cookieSpring.update(Scene::DeltaTime(), CookieCircle.leftPressed());

		// If mouse cursor is over cookie circle
		if (CookieCircle.mouseOver())
		{
			Cursor::RequestStyle(CursorStyle::Hand);
		}

		// If cookie circle is left clicked
		if (CookieCircle.leftClicked())
		{
			++cookies;

			// Add cookie dancing effect
			effect.add<CookieEffect>(Cursor::Pos().movedBy(Random(-5, 5), Random(-5, 5)), texture);

			// Add "+1" rising effect
			effect.add<PlusOneEffect>(Cursor::Pos().movedBy(Random(-5, 5), Random(-15, -5)), font);

			// Add background cookie
			effectBackground.add<CookieBackgroundEffect>(RandomVec2(Rect{ 0, -150, 800, 100 }), texture);
		}

		// Draw background
		Rect{ 0, 0, 800, 600 }.draw(Arg::top = ColorF{ 0.6, 0.5, 0.3 }, Arg::bottom = ColorF{ 0.2, 0.5, 0.3 });

		// Draw background falling cookies
		effectBackground.update();

		// Draw cookie halo
		DrawHalo(CookieCircle.center);

		// Display cookie count as integer
		font(ThousandsSeparate((int32)cookies)).drawAt(60, 170, 100);

		// Display cookie production rate
		font(U"Per second: {}"_fmt(cps)).drawAt(24, 170, 160);

		// Draw cookie
		texture.scaled(1.5 - cookieSpring.get()).drawAt(CookieCircle.center);

		// Draw effects
		effect.update();

		for (size_t i = 0; i < ItemTable.size(); ++i)
		{
			// Item ownership count
			const int32 itemCount = itemCounts[i];

			// Current item price
			const int32 itemCost = ItemTable[i].getCost(itemCount);

			// CPS per item
			const int32 itemCps = ItemTable[i].cps;

			// Button
			if (Button(Rect{ 340, (40 + 120 * i), 420, 100 }, ItemTable[i].emoji,
				font, ItemTable[i].name, U"C{} / {} CPS"_fmt(itemCost, itemCps), itemCount, (itemCost <= cookies)))
			{
				cookies -= itemCost;
				++itemCounts[i];
			}
		}
	}

	// Save game at exit after main loop
	{
		// Open binary file
		Serializer<BinaryWriter> writer{ U"game.save" };

		// Write serializable data
		writer(SaveData{ cookies, itemCounts });
	}
}

10. Drawing playing cards

Code
# include <Siv3D.hpp>

void Main()
{
	Window::Resize(1280, 720);

	Scene::SetBackground(Palette::Darkgreen);

	// Create card pack with 75 pixel card width and red back
	const PlayingCard::Pack pack{ 75, Palette::Red };

	// Number of jokers
	constexpr int32 NumJokers = 2;

	// Create deck including 52 cards + jokers
	Array<PlayingCard::Card> cards = PlayingCard::CreateDeck(NumJokers);

	while (System::Update())
	{
		for (size_t i = 0; i < cards.size(); ++i)
		{
			const Vec2 center{ (100 + i % 13 * 90), (100 + (i / 13) * 130) };

			if (pack.regionAt(center).mouseOver())
			{
				Cursor::RequestStyle(CursorStyle::Hand);

				if (MouseL.down())
				{
					// Flip card
					cards[i].flip();
				}
			}

			// Draw card
			pack(cards[i]).drawAt(center);
		}
	}
}

11. Tic-tac-toe

Code
# include <Siv3D.hpp>

// Function to check if 3 marks are connected
bool CheckLine(const Grid<int32>& grid, const Point& cellA, const Point& cellB, const Point& cellC)
{
	const int32 a = grid[cellA];
	const int32 b = grid[cellB];
	const int32 c = grid[cellC];
	return ((a != 0) && (a == b) && (b == c));
}

// Function to return list of connected lines
Array<std::pair<Point, Point>> CheckLines(const Grid<int32>& grid)
{
	Array<std::pair<Point, Point>> results;

	// Check 3 vertical columns
	for (int32 x = 0; x < 3; ++x)
	{
		if (CheckLine(grid, Point{ x, 0 }, Point{ x, 1 }, Point{ x, 2 }))
		{
			results.emplace_back(Point{ x, 0 }, Point{ x, 2 });
		}
	}

	// Check 3 horizontal rows
	for (int32 y = 0; y < 3; ++y)
	{
		if (CheckLine(grid, Point{ 0, y }, Point{ 1, y }, Point{ 2, y }))
		{
			results.emplace_back(Point{ 0, y }, Point{ 2, y });
		}
	}

	// Check diagonal (top-left -> bottom-right)
	if (CheckLine(grid, Point{ 0, 0 }, Point{ 1, 1 }, Point{ 2, 2 }))
	{
		results.emplace_back(Point{ 0, 0 }, Point{ 2, 2 });
	}

	// Check diagonal (top-right -> bottom-left)
	if (CheckLine(grid, Point{ 2, 0 }, Point{ 1, 1 }, Point{ 0, 2 }))
	{
		results.emplace_back(Point{ 2, 0 }, Point{ 0, 2 });
	}

	return results;
}

class GameBoard
{
public:

	// Cell size
	static constexpr int32 CellSize = 150;

	// O mark value
	static constexpr int32 O_Mark = 1;

	// X mark value
	static constexpr int32 X_Mark = 2;

	void update()
	{
		if (m_gameOver)
		{
			return;
		}

		// 3x3 cells
		for (auto p : step(Size{ 3, 3 }))
		{
			// Cell
			const Rect cell{ (p * CellSize), CellSize };

			// Cell mark
			const int32 mark = m_grid[p];

			// If cell is empty and clicked
			if ((mark == 0) && cell.leftClicked())
			{
				// Write current mark to cell
				m_grid[p] = m_currentMark;

				// Switch current mark
				m_currentMark = ((m_currentMark == O_Mark) ? X_Mark : O_Mark);

				// Look for connected lines
				m_lines = CheckLines(m_grid);

				// If empty cells become 0 or connected lines are found
				if (m_grid.count(0) == 0 || m_lines)
				{
					// Game over
					m_gameOver = true;
				}
			}
		}
	}

	// Reset game
	void reset()
	{
		m_currentMark = O_Mark;

		m_grid.fill(0);

		m_lines.clear();

		m_gameOver = false;
	}

	// Drawing
	void draw() const
	{
		drawGridLines();

		drawCells();

		drawResults();
	}

	// Returns whether game is over
	bool isGameOver() const
	{
		return m_gameOver;
	}

private:

	// 3x3 2D array (initial value is 0 for all elements)
	Grid<int32> m_grid = Grid<int32>(3, 3);

	// Mark to be placed next
	int32 m_currentMark = O_Mark;

	// Game over flag
	bool m_gameOver = false;

	// List of 3 consecutive lines
	Array<std::pair<Point, Point>> m_lines;

	// Draw grid
	void drawGridLines() const
	{
		// Draw lines
		for (auto i : { 1, 2 })
		{
			Line{ (i * CellSize), 0, (i * CellSize), (3 * CellSize) }
				.draw(4, ColorF{ 0.25 });

			Line{ 0, (i * CellSize), (3 * CellSize), (i * CellSize) }
				.draw(4, ColorF{ 0.25 });
		}
	}

	// Draw cells
	void drawCells() const
	{
		// 3x3 cells
		for (auto p : step(Size{ 3, 3 }))
		{
			// Cell
			const Rect cell{ (p * CellSize), CellSize };

			// Cell mark
			const int32 mark = m_grid[p];

			// If X mark
			if (mark == X_Mark)
			{
				// Draw X mark
				Shape2D::Cross(CellSize * 0.4, 10, cell.center())
					.draw(ColorF{ 0.2 });

				// Don't process this cell further
				continue;
			}
			else if (mark == O_Mark) // If O mark
			{
				// Draw O mark
				Circle{ cell.center(), (CellSize * 0.4 - 10) }
				.drawFrame(10, 0, ColorF{ 0.2 });

				// Don't process this cell further
				continue;
			}

			// If cell is moused over
			if (!m_gameOver && cell.mouseOver())
			{
				// Change cursor to hand icon
				Cursor::RequestStyle(CursorStyle::Hand);

				// Draw semi-transparent white over cell
				cell.stretched(-2).draw(ColorF{ 1.0, 0.6 });
			}
		}
	}

	// Draw connected lines
	void drawResults() const
	{
		for (const auto& line : m_lines)
		{
			// Get start and end cells of connected line
			const Rect cellBegin{ line.first * CellSize, CellSize };
			const Rect cellEnd{ line.second * CellSize, CellSize };

			// Draw line
			Line{ cellBegin.center(), cellEnd.center() }
				.stretched(CellSize * 0.45)
				.draw(LineStyle::RoundCap, 5, ColorF{ 0.6 });
		}
	}
};

void Main()
{
	// Background color
	Scene::SetBackground(ColorF{ 0.8, 1.0, 0.9 });

	constexpr Point Offset{ 175, 30 };

	GameBoard gameBoard;

	while (System::Update())
	{
		{
			// Move 2D drawing and mouse cursor coordinates
			const Transformer2D transform{ Mat3x2::Translate(Offset), TransformCursor::Yes };

			gameBoard.update();

			gameBoard.draw();
		}

		// If game is over
		if (gameBoard.isGameOver())
		{
			// Reset if Reset button is pressed
			if (SimpleGUI::ButtonAt(U"Reset", Vec2{ 400, 520 }))
			{
				gameBoard.reset();
			}
		}
	}
}

12. Rhythm game basics

Code

First, place a chart file notes.txt written as follows in the App/ folder of your project.

notes.txt
2000 0
2500 1
3000 2
3500 3
4000 3
4500 2
5000 1
5500 0
6000 0
6500 1
7000 2
7500 3
8000 3
8500 2
9000 1
9500 0

In actual games, elapsed time should be calculated from .posSec() or .posSample() of Audio, but this sample uses Stopwatch to determine elapsed time without using audio files.

# include <Siv3D.hpp>

// Note
struct Note
{
	// Note time
	int32 time;

	// Key index to press (0, 1, 2, 3)
	int32 key;

	// false when disappeared
	bool active = true;
};

// Function to load note information from chart file
Array<Note> LoadNotes(const FilePath& path)
{
	TextReader reader{ path };

	if (not reader)
	{
		throw Error{ U"Chart {} not found."_fmt(path) };
	}

	Array<Note> notes;

	String line;

	// Read line by line
	while (reader.readLine(line))
	{
		// Skip empty lines
		if (line.isEmpty())
		{
			continue;
		}

		// Split read line by half-width space
		const Array<String> params = line.split(U' ');

		// If split result is not 2 elements, it's an invalid chart
		if (params.size() != 2)
		{
			throw Error{ U"Invalid chart." };
		}

		// Convert split results to int32 type
		notes.emplace_back(Parse<int32>(params[0]), Parse<int32>(params[1]));
	}

	return notes;
}

// Function to calculate note position
Vec2 GetNotePos(const Note& note, int32 time)
{
	const double x = (250 + note.key * 100);
	const double y = (500 - (note.time - time) * 0.25);
	return{ x, y };
}

// Effect when note is hit
struct NoteEffect : IEffect
{
	Vec2 m_start;

	int32 m_score;

	Font m_font;

	NoteEffect(const Vec2& start, int32 score, const Font& font)
		: m_start{ start }
		, m_score{ score }
		, m_font{ font } {}

	bool update(double t) override
	{
		Circle{ m_start, (30 + t * 80) }.drawFrame(15 * (0.5 - t));

		if (m_score == 2)
		{
			m_font(U"Excellent").drawAt(32, m_start.movedBy(0, (-20 - t * 160)), Palette::Orange);
		}
		else if (m_score == 1)
		{
			m_font(U"Good").drawAt(32, m_start.movedBy(0, (-20 - t * 160)), Palette::Skyblue);
		}

		return (t < 0.5);
	}
};

void Main()
{
	// Note array
	Array<Note> notes = LoadNotes(U"notes.txt");

	// Judgment keys
	const Array<Input> Keys = { KeyA, KeyS, KeyD, KeyF };

	// Key input effect transitions
	Array<Transition> keyTransitions(Keys.size(), Transition{ 0.0s, 0.2s });

	// Stopwatch for time measurement
	Stopwatch stopwatch{ StartImmediately::Yes };

	// Font
	const Font font{ FontMethod::MSDF, 48, Typeface::Heavy };

	// Effect management
	Effect effect;

	while (System::Update())
	{
		// Elapsed time (milliseconds)
		const int32 time = stopwatch.ms();

		ClearPrint();

		Print << time;

		////////////////////////////////
		//
		//	State update
		//
		////////////////////////////////

		for (size_t i = 0; i < Keys.size(); ++i)
		{
			keyTransitions[i].update(Keys[i].down());
		}

		for (auto& note : notes)
		{
			// Skip disappeared notes
			if (not note.active)
			{
				continue;
			}

			// Difference between current time and note time (milliseconds)
			const int32 diffMillisec = (time - note.time);

			// If absolute difference is less than 250 milliseconds
			if (Abs(diffMillisec) < 250)
			{
				// If key corresponding to note is pressed
				if (Keys[note.key].down())
				{
					// Remove note
					note.active = false;

					// Note position
					const Vec2 notePos = GetNotePos(note, time);

					// Add effect
					effect.add<NoteEffect>(Vec2{ notePos.x, 500 }, (Abs(diffMillisec) < 80 ? 2 : 1), font);
				}
			}

			// Delay of 250 milliseconds or more is a miss
			if (note.active && (250 <= diffMillisec))
			{
				// Remove note
				note.active = false;
			}
		}

		////////////////////////////////
		//
		//	Drawing
		//
		////////////////////////////////

		// Draw input
		for (int32 i = 0; i < 4; ++i)
		{
			const double x = (250 + i * 100);
			RectF{ Arg::bottomCenter(x, 600), 80, 600 }
				.draw(Arg::top = ColorF{ 1.0, 0.0 }, Arg::bottom = ColorF{ 1.0, keyTransitions[i].easeOut() * 0.5 });
		}

		// Draw rectangle
		Rect{ 0, 480, 800, 40 }.draw(ColorF{ 0.5 });

		// Draw key names
		for (int32 i = 0; i < 4; ++i)
		{
			const double x = (250 + i * 100);
			font(Keys[i].name()).drawAt(20, Vec2{ x, 500 }, ColorF{ 0.7 });
		}

		// Draw notes
		for (const auto& note : notes)
		{
			// Skip disappeared notes
			if (not note.active)
			{
				continue;
			}

			// Note position
			const Vec2 notePos = GetNotePos(note, time);

			// Only draw notes that are on screen
			if (-100.0 < notePos.y)
			{
				Circle{ notePos, 30 }.draw();
			}
		}

		// Draw effects
		effect.update();
	}
}

13. Minesweeper

Siv3D-Sample | Minesweeper

14. AI Othello

Siv3D-Sample | AI Othello

15. Klondike

Siv3D-Sample | Klondike

16. Memory game

Game Patterns | Memory game

17. Tower of Hanoi

Game Patterns | Tower of Hanoi

18. Wheel of Fortune (Roulette)

Game Patterns | Wheel of Fortune (Roulette)

19. 2D RPG map and movement basics

Game Patterns | 2D RPG map and movement basics

20. Auto tiles

Siv3D-Sample | Auto tiles

21. Board game basics

Code
# include <Siv3D.hpp>

/// @brief Board game square types
enum class SquareType
{
	/// @brief Start
	Start,

	/// @brief Normal
	Normal,

	/// @brief Goal
	Goal,
};

/// @brief Board game square information
struct SquareInfo
{
	/// @brief Square position
	Point pos;

	/// @brief Square type
	SquareType type;
};

/// @brief Draws board game squares.
/// @param squares Board game squares
/// @param font Font
void DrawSquares(const Array<SquareInfo>& squares, const Font& font)
{
	// Draw lines between squares
	for (size_t i = 0; i < (squares.size() - 1); ++i)
	{
		Line{ squares[i].pos, squares[i + 1].pos }
			.draw(32, ColorF{ 1.0, 0.95, 0.9 });
	}

	// For each square
	for (const auto& square : squares)
	{
		if (square.type == SquareType::Start)
		{
			// Draw start square
			RoundRect{ Arg::center = square.pos, 144, 144, 24 }
				.draw(ColorF{ 0.5, 0.5, 0.8 }).drawFrame(4, ColorF{ 0.3 });

			// Draw start text
			font(U"START")
				.drawAt(36, square.pos);
		}
		else if (square.type == SquareType::Normal)
		{
			// Draw normal square
			RoundRect{ Arg::center = square.pos, 100, 100, 24 }
				.draw().drawFrame(4, ColorF{ 0.3 });
		}
		else if (square.type == SquareType::Goal)
		{
			// Draw goal square
			RoundRect{ Arg::center = square.pos, 144, 144, 24 }
				.draw(ColorF{ 0.8, 0.5, 0.5 }).drawFrame(4, ColorF{ 0.3 });

			// Draw goal text
			font(U"GOAL")
				.drawAt(36, square.pos);
		}
	}
}

void Main()
{
	// Set background color
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// Font
	const Font font{ FontMethod::MSDF, 30, Typeface::Bold };

	// Player emoji
	const Texture playerEmoji{ U"🐥"_emoji };

	// Board game square information
	const Array<SquareInfo> squares = {
		{ {100, 500}, SquareType::Start },
		{ {300, 500}, SquareType::Normal },
		{ {500, 500}, SquareType::Normal },
		{ {700, 500}, SquareType::Normal },
		{ {700, 350}, SquareType::Normal },
		{ {500, 350}, SquareType::Normal },
		{ {300, 350}, SquareType::Normal },
		{ {100, 350}, SquareType::Normal },
		{ {100, 200}, SquareType::Normal },
		{ {300, 200}, SquareType::Normal },
		{ {500, 200}, SquareType::Normal },
		{ {700, 200}, SquareType::Goal },
	};

	// Dice rotation timer
	Timer diceTimer{ 1s };

	// Player movement timer
	Timer walkTimer{ 0.5s };

	// Can roll dice
	bool canRollDice = true;

	// Player position
	size_t playerPos = 0;

	// Dice result
	int32 diceResult = 0;

	// Step count
	int32 walkCount = 0;

	while (System::Update())
	{
		// Roll dice button
		if (SimpleGUI::Button(U"Roll dice",
			Vec2{ 40, 40 }, 200, canRollDice))
		{
			// Start dice rotation
			diceTimer.start();

			// Disable dice rolling
			canRollDice = false;
		}

		// Dice is rotating
		if (diceTimer.isRunning())
		{
			// Draw dice face
			Circle{ 300, 60, 40 }.draw();
			font(U"0/{}"_fmt(Random(1, 6)))
				.drawAt(30, Vec2{ 300, 60 }, ColorF{ 0.11 });
		}

		// Finalize dice result
		if (diceTimer.reachedZero())
		{
			// Determine dice result
			diceResult = Random(1, 6);

			// Stop dice rotation
			diceTimer.reset();

			// Start player movement
			walkTimer.restart();
		}

		// Display dice result
		if (diceResult)
		{
			// Draw dice face and step count
			Circle{ 300, 60, 40 }.draw();
			font(U"{}/{}"_fmt(walkCount, diceResult))
				.drawAt(30, Vec2{ 300, 60 }, ColorF{ 0.11 });
		}

		// Player movement
		if ((walkCount != diceResult) && walkTimer.reachedZero())
		{
			// Advance step count
			++walkCount;

			// Don't advance beyond goal
			playerPos = Min((playerPos + 1), (squares.size() - 1));

			// Restart movement timer
			walkTimer.restart();
		}

		// When movement is complete
		if ((diceResult == walkCount) && walkTimer.reachedZero())
		{
			// Reset dice result
			diceResult = 0;

			// Reset step count
			walkCount = 0;

			// Reset movement timer
			walkTimer.reset();

			// Enable dice rolling
			canRollDice = true;
		}

		// Draw board game squares
		DrawSquares(squares, font);

		// Draw player shadow
		Ellipse{ squares[playerPos].pos.movedBy(0, 30), 40, 10 }
			.draw(ColorF{ 0.0, 0.2 });

		// Draw player
		playerEmoji.drawAt(squares[playerPos].pos.movedBy(0, -24));
	}
}

22. Slot machine

Control with Space.

Code
# include <Siv3D.hpp>

/// @brief Slot game symbol
struct Symbol
{
	/// @brief Symbol
	Texture symbol;

	/// @brief Prize money
	int32 score;
};

void Main()
{
	// Set background color
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// Font
	const Font font{ FontMethod::MSDF, 48,
		U"example/font/RocknRoll/RocknRollOne-Regular.ttf" };

	// Game start sound effect
	const Audio soundStart{ Wave{ GMInstrument::Agogo,
		PianoKey::A3, 0.3s, 0.2s } };

	// Reel stop sound effect
	const Audio soundStop{ Wave{ GMInstrument::SteelDrums,
		PianoKey::A3, 0.3s, 0.2s } };

	// Prize winning sound effect (loop playback)
	const Audio soundGet{ Wave{ GMInstrument::TinkleBell,
		PianoKey::A6, 0.1s, 0.0s }, Loop::Yes };

	// Symbol list
	const Array<Symbol> symbols
	{
		{ Texture{ U"💎"_emoji }, 1000 },
		{ Texture{ U"7️⃣"_emoji }, 777 },
		{ Texture{ U"💰"_emoji }, 300 },
		{ Texture{ U"🃏"_emoji }, 100 },
		{ Texture{ U"🍇"_emoji }, 30 },
		{ Texture{ U"🍒"_emoji }, 10 },
	};

	// Basic symbol list for one reel
	const Array<int32> symbolListBase =
		{ 0, 1, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5 };

	// Symbol lists for 3 reels (shuffled basic list)
	const std::array<Array<int32>, 3> symbolLists =
	{
		symbolListBase.shuffled(),
		symbolListBase.shuffled(),
		symbolListBase.shuffled()
	};

	// Drawing positions for 3 reels
	const std::array<Rect, 3> reels
	{
		Rect{ 80, 100, 130, 300 },
		Rect{ 230, 100, 130, 300 },
		Rect{ 380, 100, 130, 300 },
	};

	// Money display position
	const RoundRect moneyRect{ 560, 440, 190, 60, 20 };

	// Rotation amounts for 3 reels
	std::array<double, 3> rolls = { 0.0, 0.0, 0.0 };

	// Reel stop count for current game (result determination at 3)
	int32 stopCount = 3;

	// Money
	int32 money = 1000;

	while (System::Update())
	{
		// If space key is pressed
		if (KeySpace.down())
		{
			// If all 3 reels are stopped
			if (stopCount == 3)
			{
				// If money is 3 or more
				if (3 <= money)
				{
					// Subtract 3 from money
					money -= 3;

					// Reset reel stop count to 0
					stopCount = 0;

					// Play game start sound effect
					soundStart.playOneShot();
				}
			}
			else
			{
				// Stop reel at integer position
				rolls[stopCount] = Math::Ceil(rolls[stopCount]);

				// Increase reel stop count
				++stopCount;

				// Play reel stop sound effect
				soundStop.playOneShot();

				// If all 3 reels are stopped
				if (stopCount == 3)
				{
					// Symbols on each reel
					const int32 r0 = symbolLists[0][(
						static_cast<int32>(rolls[0] + 1) % symbolLists[0].size())];
					const int32 r1 = symbolLists[1][(
						static_cast<int32>(rolls[1] + 1) % symbolLists[1].size())];
					const int32 r2 = symbolLists[2][(
						static_cast<int32>(rolls[2] + 1) % symbolLists[2].size())];

					// If all 3 reel symbols are the same
					if ((r0 == r1) && (r1 == r2))
					{
						// Add prize money to money
						money += symbols[r0].score;

						// Play prize winning sound effect
						soundGet.play();

						// Stop prize winning sound effect after 1.5 seconds
						soundGet.stop(1.5s);
					}
				}
			}
		}

		// Reel rotation
		for (int32 i = 0; i < 3; ++i)
		{
			// Skip stopped reels
			if (i < stopCount)
			{
				continue;
			}

			// Increase reel rotation amount according to elapsed time from previous frame
			rolls[i] += (Scene::DeltaTime() * 12);
		}

		// Draw reels
		for (int32 k = 0; k < 3; ++k)
		{
			// Reel background
			reels[k].draw();

			// Draw reel symbols
			for (int32 i = 0; i < 4; ++i)
			{
				// Which element of the reel to point to (integer part of rotation amount)
				const int32 index = (static_cast<int32>(rolls[k] + i)
					% symbolLists[k].size());

				// Symbol index
				const int32 symbolIndex = symbolLists[k][index];

				// Symbol position correction (fractional part of rotation amount)
				const double t = Math::Fraction(rolls[k]);

				// Draw symbol
				symbols[symbolIndex].symbol.resized(90)
					.drawAt(reels[k].center().movedBy(0, 140 * (1 - i + t)));
			}
		}

		// Draw background color above and below reels to hide overflowing symbols
		Rect{ 80, 0, 430, 100 }.draw(Scene::GetBackground());
		Rect{ 80, 400, 430, 200 }.draw(Scene::GetBackground());

		// Draw reel shadows and frames
		for (const auto& reel : reels)
		{
			// Top shadow
			Rect{ reel.tl(), reel.w, 40 }.draw(Arg::top(0.0, 0.3), Arg::bottom(0.0, 0.0));

			// Bottom shadow
			Rect{ (reel.bl() - Point{ 0, 40 }), reel.w, 40 }.draw(Arg::top(0.0, 0.0), Arg::bottom(0.0, 0.3));

			// Frame
			reel.drawFrame(4, ColorF{ 0.5 });
		}

		// Draw 2 triangles pointing to center
		Triangle{ 60, 250, 36, 90_deg }.draw(ColorF{ 1.0, 0.2, 0.2 });
		Triangle{ 530, 250, 36, -90_deg }.draw(ColorF{ 1.0, 0.2, 0.2 });

		// Draw symbol list
		RoundRect{ 560, 100, 190, 300, 20 }.draw(ColorF{ 0.9, 0.95, 1.0 });

		for (size_t i = 0; i < symbols.size(); ++i)
		{
			// Draw symbol
			symbols[i].symbol.resized(32).draw(Vec2{ 586, (114 + i * 48) });

			// Draw prize money
			font(symbols[i].score).draw(TextStyle::OutlineShadow(0.2, ColorF{ 0.5, 0.3, 0.2 },
				Vec2{ 1.5, 1.5 }, ColorF{ 0.5, 0.3, 0.2 }),
				25, Arg::topRight(720, (109 + i * 48)), ColorF{ 1.0, 0.9, 0.1 });

			if (i != 0)
			{
				// Draw separator line between symbols
				Rect{ 570, (105 + i * 48), 170, 1 }.draw(ColorF{ 0.7 });
			}
		}

		// Draw money background
		if (soundGet.isPlaying())
		{
			// Flash during prize winning
			const ColorF color = Periodic::Sine0_1(0.3s) * ColorF { 0.5, 0.6, 0.7 };
			moneyRect.draw(color).drawFrame(1);
		}
		else
		{
			moneyRect.draw(ColorF{ 0.1, 0.2, 0.3 }).drawFrame(1);
		}

		// Draw money
		font(money).draw(30, Arg::rightCenter(moneyRect.rightCenter().movedBy(-30, 0)));
	}
}