Flipper Mon: A Pokémon-Style Adventure for Your Flipper Zero. Part 1

Pokémon-Style Adventure for Your Flipper Zero
As developers and tinkerers, we often look for projects that combine our passions. For me, that's the world of retro gaming and the incredible versatility of the Flipper Zero. It's this combination that led to the start of a new, ongoing project I'm calling Flipper Mon—a Pokémon-inspired RPG that runs right on the Flipper's monochrome screen. This is a journey of iteration, bug squashing, and building a tiny world one line of code at a time.
View the full project on GitHub.
The Core Engine: Scenes and Game Loops
At its heart, Flipper Mon is built around a classic game loop and a scene manager. The Flipper Zero's application structure, which processes events from a message queue, is perfect for this. The main while
loop in app_main
waits for user input and then updates the game state accordingly.
A simple scene manager, defined by the GameScene
enum, dictates what the player is currently doing:
SceneExploration
: The player is walking around the world map.SceneBattle
: The player is in a turn-based battle.
The main draw callback checks the current scene and calls the appropriate function to render the visuals, either draw_exploration_scene
for the overworld or draw_battle_scene
for combat.
// from flipper_mon.c
static void game_draw_callback(Canvas* canvas, void* ctx) {
(void)ctx;
canvas_clear(canvas);
if (scene_manager.current_scene == SceneExploration) {
draw_exploration_scene(canvas);
} else {
draw_battle_scene(canvas);
}
}
int32_t app_main(void* p) {
// ... setup code ...
while(running) {
// Wait for an event (100ms timeout).
if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) {
if(event.type == EventTypeKey && event.input.type == InputTypePress) {
handle_movement(&event);
}
}
// Update game state
update_game_state();
view_port_update(view_port);
}
// ... cleanup code ...
return 0;
}
Exploring the World
Movement and exploration are handled by tracking the trainer's X/Y coordinates on a tile-based map. When the player presses a direction on the D-Pad, the handle_movement
function calculates the new coordinates. It then checks the destination tile to see if it's an obstacle. If not, the player moves.
A camera system follows the player, ensuring that the trainer stays centered on the Flipper's 128x64 pixel screen. This gives the illusion of a much larger world.
// from flipper_mon.c
static void draw_exploration_scene(Canvas* canvas) {
canvas_clear(canvas);
// Center the camera on the player, clamping at the map edges
int camera_x = trainer.x - SCREEN_WIDTH / 2;
int camera_y = trainer.y - SCREEN_HEIGHT / 2;
// ... camera clamping logic ...
// Draw only the tiles visible to the camera
for(int ty = start_tile_y; ty <= end_tile_y; ty++) {
for(int tx = start_tile_x; tx <= end_tile_x; tx++) {
// ... tile drawing logic ...
}
}
// Draw the player's sprite
canvas_draw_xbm(canvas, draw_x, draw_y, TILE_SIZE, TILE_SIZE, sprite);
}
Random encounters are a staple of the genre. As the player walks through special "grass" tiles, there's a chance to trigger a battle. Each tile has data defining the encounter rate and which Pokémon can appear there.
The Thrill of Battle
The battle system is the most complex part of the game. It’s managed by a state machine using the BattleState
enum, which tracks every phase of combat:
BattleStateIntro
: "A wild Pidgey appeared!"BattleStateChooseAction
: The main menu (FIGHT, PKMN, ITEM, RUN).BattleStateChooseMove
: The player selects an attack.BattleStateExecuteMove
: An animation plays and damage is calculated.BattleStateResult
: Shows the outcome ("It did 10 damage!").BattleStateEnemyTurn
: The opponent attacks.BattleStateEnd
: The battle concludes with a win or loss. Theprocess_battle_input
function is a largeswitch
statement that manages these state transitions based on the player's D-pad and OK button presses.
Data-Driven Pokémon Design
All Pokémon stats and moves are defined in a data-driven way, making the game easy to expand. The pokemon.h
header defines the structures for Pokémon and their moves.
// from pokemon.h
typedef struct {
const char* name;
PokemonSpecies species;
int level;
int max_hp;
int current_hp;
int attack;
int defense;
// ... other stats ...
Move moves[4];
} Pokemon;
Actually creating a new Pokémon is handled by the create_pokemon
function in pokemon.c
. This function takes a species (like POKEMON_BULBASAUR
) and a level, then calculates its stats and assigns its default moveset, pulling all the base data from arrays. This makes adding a new creature as simple as adding its base stats and moves to the global arrays.
Damage is calculated using a simplified version of the official formula: damage = (2 * attacker.level * move.power * attacker.attack) / (defender.defense * 50) + 2;
.
The Iterative Process: Fixing Bugs
This project is a perfect example of iterative development. Early versions had bugs that were only found through playtesting.
One of the first major issues was a "MissingImports" error related to the strtok
function, which was used to split dialog text into multiple lines. It turns out this function is disabled in some Flipper Zero firmware versions. The fix was to remove the dependency entirely and write a manual string parser to handle newlines.
Another bug was in the battle loop. After an attack, the game would get stuck on the enemy's turn, never returning control to the player. The problem was a faulty state transition. The fix involved adding more robust logic to the BattleStateResult
case to check whose turn had just ended using the player_turn
boolean and then handing control back to the correct entity.
// The corrected battle loop logic from flipper_mon.c
case BattleStateResult:
if(key == InputKeyOk) {
if(wild_pokemon.current_hp <= 0 || player_pokemon.current_hp <= 0) {
battle_state = BattleStateEnd;
} else {
if(player_turn) { // If it was the player's turn...
battle_state = BattleStateEnemyTurn; // ...it's now the enemy's.
player_turn = false;
} else { // If it was the enemy's turn...
battle_state = BattleStateChooseAction; // ...it's now the player's.
player_turn = true;
}
}
update_battle_ui();
}
break;
The Journey Continues
Flipper Mon is still very much a work in progress. There is a long road ahead with many features yet to implement: leveling up, evolution, status effects, a larger world with multiple maps, and more Pokémon. But with each coding session, this small world expands, demonstrating the power and fun of developing for the Flipper Zero. It’s a rewarding journey, and I look forward to seeing where it leads.
Source Code & Future Development
You can view the full source code, track progress, and even contribute to the project on GitHub. As this is an ongoing project, I welcome feedback and ideas!