PC Games

Orb
Lasagne Monsters
Three Guys Apocalypse
Water Closet
Blob Wars : Attrition
The Legend of Edgar
TBFTSS: The Pandoran War
Three Guys
Blob Wars : Blob and Conquer
Blob Wars : Metal Blob Solid
Project: Starfighter
TANX Squadron

Tutorials

2D shoot 'em up
2D top-down shooter
2D platform game
Sprite atlas tutorial
Working with TTF fonts
2D adventure game
Widget tutorial
2D shoot 'em up sequel
2D run and gun
Roguelike
Medals (Achievements)
2D turn-based strategy game
2D isometric game
2D map editor
2D mission-based shoot 'em up
2D Santa game
2D split screen game
SDL 1 tutorials (outdated)

Latest Updates

SDL2 Versus game tutorial
Wed, 20th March 2024

Download keys for SDL2 tutorials on itch.io
Sat, 16th March 2024

The Legend of Edgar 1.37
Mon, 1st January 2024

SDL2 Santa game tutorial 🎅
Thu, 23rd November 2023

SDL2 Shooter 3 tutorial
Wed, 15th February 2023

All Updates »

Tags

android (3)
battle-for-the-solar-system (10)
blob-wars (10)
brexit (1)
code (6)
edgar (9)
games (43)
lasagne-monsters (1)
making-of (5)
match3 (1)
numberblocksonline (1)
orb (2)
site (1)
tanx (4)
three-guys (3)
three-guys-apocalypse (3)
tutorials (17)
water-closet (4)

Books


Project Starfighter

In his fight back against the ruthless Wade-Ellen Asset Protection Corporation, pilot Chris Bainfield finds himself teaming up with the most unlikely of allies - a sentient starfighter known as Athena.

Click here to learn more and read an extract!

« Back to tutorial listing

— Creating a simple roguelike —
Part 19: The Mouse King

Note: this tutorial assumes knowledge of C, as well as prior tutorials.

Introduction

The time has come to add in the primary baddie of our game - The Mouse King. The naughty mouse can be found hiding on the top floor of the dungeon, where he waits for the player to confront him. Since this part only involves adding in the King, it is fairly short.

Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./rogue19 to run the code. You will see a window open displaying the player character in a small room, with just a stair case (leading up). Play the game as normal and reach floor 13, to battle the Mouse King (or just hack the code, to cheat your way there, since it can prove difficult otherwise!). Once the King is dead, the game will end and display the highscore table, with your entry at the top. Once you're finished, close the window to exit.

Inspecting the code

We've made a number of misc. changes to the code to support the Mouse King. Nothing huge, thankfully.

Starting with structs.h:


typedef struct {
	int entityId;
	Entity entityHead, *entityTail;
	Entity deadHead, *deadTail;
	Entity *player, *currentEntity, *attackingEntity, *mouseKing;
	MapTile map[MAP_WIDTH][MAP_HEIGHT];
	SDL_Point camera;
	SDL_Point attackDir;
	SDL_Point selectedTile;
	double animationTimer;
	int floor, newFloor;
} Dungeon;

We've added in an Entity pointer called `mouseKing`, to track the Mouse King when he's added to the dungeon, in much the same way as the player.

Heading over to monsters.c, we've added in a new function named initMouseKing:


void initMouseKing(Entity *e)
{
	Monster *m;

	m = createMonster(e);
	m->hp = m->maxHP = 1;
	m->defence = 1;
	m->minAttack = 1;
	m->maxAttack = 2;
	m->visRange = MAP_WIDTH;
	m->xp = 1;

	STRCPY(e->name, "Mouse King");
	STRCPY(e->description, "King of Mice. He has a crown and wears it well.");
	e->texture = getAtlasImage("gfx/entities/mouseKing.png", 1);

	dungeon.mouseKing = e;
}

This is a standard entity init function, for adding in the Mouse King. The Mouse King himself is very, very weak..! He is actually weaker than a Micro Mouse. Who knew? The King has 1 `hp`, 1 `defence`, 1 `minAttack`, 2 `maxAttack`, a very, very high visRange (the entire map width), and is worth 1 `xp`. We're setting his `name`, `description`, and `texture`, and then assigning `e` to dungeon's mouseKing variable. This allows us to track the Mouse King and test his state.

We've also added in a function called addMouseKing:


void addMouseKing(void)
{
	addEntityToDungeon(initEntity("Mouse King"), 0);
}

This function simply adds the Mouse King to the dungeon.

Turning next to dungeon.c, we've made a number of changes to support the Mouse King.

To begin with, we've updated initDungeon:


void initDungeon(void)
{
	memset(&dungeon, 0, sizeof(Dungeon));

	floorChangeTimer = FPS / 2;

	playerDeathAlpha = -FPS / 2;

	mouseKingDeathAlpha = -FPS / 2;

	initMap();

	initHud();

	initInventory();

	initEntities();

	if (!loadGame())
	{
		createDungeon();
	}

	app.delegate.logic = logic;

	app.delegate.draw = draw;
}

We're setting a new variable called mouseKingDeathAlpha (static within dungeon.c) to half a second. This will work in the same way as playerDeathAlpha, as we'll see shortly.

Next, we've tweaked createDungeon:


static void createDungeon(void)
{
	int oldFloor;
	char text[MAX_DESCRIPTION_LENGTH];

	oldFloor = dungeon.floor;

	dungeon.floor = dungeon.newFloor;

	dungeon.mouseKing = NULL;

	initEntities();

	if (dungeon.player == NULL)
	{
		initEntity("Player");
	}
	else
	{
		dungeon.player->next = NULL;

		dungeon.entityTail->next = dungeon.player;
		dungeon.entityTail = dungeon.player;
	}

	generateMap();

	if (dungeon.floor > 0 && dungeon.floor < MAX_FLOORS)
	{
		addMonsters();

		addItems();
	}
	else if (dungeon.floor == 0)
	{
		addHelperItems();
	}
	else if (dungeon.floor == MAX_FLOORS)
	{
		addMouseKing();
	}

	// snipped
}

We're setting dungeon's mouseKing pointer to NULL before setting up the dungeon proper, to make sure he's not being tracked by default (this prevents dangling pointer references, that could lead to crashes). We're also testing whether we're on the top floor of the dungeon, by testing whether dungeon's `floor` equals MAX_FLOORS. If so, we'll be calling addMouseKing, to add the Mouse King to the floor.

Turning to `logic` next, we've reworked the function a bit:


static void logic(void)
{
	floorChangeTimer = MAX(floorChangeTimer - app.deltaTime, 0);

	if (dungeon.player->dead)
	{
		playerDeathAlpha += 0.5 * app.deltaTime;

		if (playerDeathAlpha >= 64)
		{
			initGameOver();
		}
	}
	else if (dungeon.mouseKing != NULL && dungeon.mouseKing->dead)
	{
		mouseKingDeathAlpha += app.deltaTime;

		if (mouseKingDeathAlpha >= 128)
		{
			game.highscore.defeatedMouseKing = 1;
			STRCPY(game.highscore.killedBy, "A ripe old age");

			initGameOver();
		}
	}
	else if (floorChangeTimer == 0)
	{
		doEntities();

		doHud();

		dungeon.animationTimer = MAX(dungeon.animationTimer - app.deltaTime, 0);

		if (dungeon.animationTimer <= FPS / 5)
		{
			dungeon.attackingEntity = NULL;

			if (dungeon.animationTimer == 0)
			{
				if (dungeon.currentEntity == dungeon.player)
				{
					doPlayer();
				}
				else
				{
					doMonsters();
				}
			}
		}

		doCamera();

		doSelectTile();

		if (dungeon.floor != dungeon.newFloor)
		{
			changeDungeonFloor();
		}
	}
}

After decreasing our floorChangeTimer, we're now testing if the player is dead, and handling that condition if so. Otherwise, we're now testing to see if dungeon's mouseKing pointer is not NULL and whether the mouseKing is dead. If this is true, we're increasing the value of mouseKingDeathAlpha, much the same way as we do with playerDeathAlpha when the player is killed. If mouseKingDeathAlpha reaches 128 or higher, we're setting game's highscore's defeatedMouseKing flag to 1, setting the killedBy string to "A ripe old age", and then calling initGameOver. Yes, defeating the Mouse King will immediately end the game. Ultimately, this will mean that we will earn one of the top places on the highscore table, since it is sorted first by those who have defeated the King. The rest of the `logic` function continues as before.

The final tweak is to the `draw` function:


static void draw(void)
{
	if (floorChangeTimer == 0)
	{
		drawMap();

		drawEntities();

		drawHud();
	}

	if (dungeon.player->dead && playerDeathAlpha > 0)
	{
		drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 255, 0, 0, playerDeathAlpha);
	}

	if (dungeon.mouseKing != NULL && dungeon.mouseKing->dead && mouseKingDeathAlpha > 0)
	{
		drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 255, 255, 255, mouseKingDeathAlpha);
	}
}

We're now testing at the end of the function if the Mouse King has been defeated and whether mouseKingDeathAlpha is greater than 0. If so, we'll begin to turn the screen white, in the same way as we turn it red when the player is killed.

Finally, we need to ensure that The Mouse King can be created. To do this, we just add a line to entityFactory.c, in initEntityFactory:


void initEntityFactory(void)
{
	memset(&head, 0, sizeof(InitFunc));
	tail = &head;

	addInitFunc("Player", initPlayer);
	addInitFunc("Micro Mouse", initMicroMouse);
	addInitFunc("Neo Mouse", initNeoMouse);
	addInitFunc("Tough Mouse", initToughMouse);
	addInitFunc("Rabid Mouse", initRabidMouse);
	addInitFunc("Meta Mouse", initMetaMouse);
	addInitFunc("Key", initKey);
	addInitFunc("Health Pack", initHealthPack);
	addInitFunc("Crowbar", initCrowbar);
	addInitFunc("Stun Baton", initStunBaton);
	addInitFunc("Biker Jacket", initBikerJacket);
	addInitFunc("Bulletproof Vest", initBulletproofVest);
	addInitFunc("Microchip", initMicrochip);
	addInitFunc("Stairs (Up)", initStairsUp);
	addInitFunc("Stairs (Down)", initStairsDown);
	addInitFunc("Door", initDoorNormal);
	addInitFunc("Door (Locked)", initDoorLocked);
	addInitFunc("Antidote", initAntidote);
	addInitFunc("Mouse King", initMouseKing);
}

We'll be calling initMouseKing when asked to create "Mouse King".

And that's our game pretty much finished. Our final part, the finishing touches, will involve adding in some sound effects, music, widgets, and a title screen, all of which are simple tasks.

Purchase

The source code for all parts of this tutorial (including assets) is available for purchase:

From itch.io

It is also available as part of the SDL2 tutorial bundle:

Mobile site