Skip to content

Ruins of Alph Sliding Puzzles

judicornadamsfoster edited this page Jul 10, 2026 · 1 revision

Full credit goes to Spherical Ice, all I did was create the guide and update a tiny bit of code

Original Source: Spherical Ice's pokecommunity post

Spherical Ice's branch

Just a simple port of the Ruins of Alph sliding puzzles from HG/SS. All four puzzles (Kabuto, Omanyte, Aerodactyl and Ho-Oh) are included with initial layouts as close as possible to how they appear in HG/SS (with some tiles moved due to the loss of the top and bottom rows of tiles for the smaller GBA screen size).

VQzqqxR

How To Use

pull from Spherical Ice's branch or follow the below guide:

git remote add sphericalice https://github.com/sphericalice/pokeemerald
git pull sphericalice sliding_puzzles

if pulling from Spherical Ice's branch, use the below "Manual Changes" sections only to manually change/update the code to compile in up-to-date pokeemerald


Pulling From Branch

After pulling from Spherical Ice's branch, make the following manual changes


spritesheet_rules.mk

-- IMPORTANT: only make this change if you have an older version of pokeemerald and still use spriteskeet_rules.mk
-- if using an upto date version that uses INCGFX for sprites, make the alternate change for file src/data/sliding_puzzles.h

add to the top of the file after the first few entries:

SLIDINGPUZZLESDIR := graphics/sliding_puzzle/puzzles
$(SLIDINGPUZZLESDIR)/kabuto/tiles.4bpp: %.4bpp: %.png
	$(GFX) $< $@ -mwidth 4 -mheight 4

$(SLIDINGPUZZLESDIR)/omanyte/tiles.4bpp: %.4bpp: %.png
	$(GFX) $< $@ -mwidth 4 -mheight 4

$(SLIDINGPUZZLESDIR)/aerodactyl/tiles.4bpp: %.4bpp: %.png
	$(GFX) $< $@ -mwidth 4 -mheight 4

$(SLIDINGPUZZLESDIR)/ho_oh/tiles.4bpp: %.4bpp: %.png
	$(GFX) $< $@ -mwidth 4 -mheight 4

[root] ld_script.ld

If pulling from Spherical Ice's branch, you may get an error saying to "commit or stash changes" to this file (This seems to be normal with changes to this file, so don't worry). Make the two changes:

add at line 344, before } > ROM =0

        src/sliding_puzzle.o(.text);

add at now line 708, before } > ROM =0

        src/sliding_puzzle.o(.rodata);

include/constants/flags.h

define 4 new flags

FLAG_SLIDING_PUZZLE_KABUTO_SOLVED
FLAG_SLIDING_PUZZLE_OMANYTE_SOLVED
FLAG_SLIDING_PUZZLE_AERODACTYL_SOLVED
FLAG_SLIDING_PUZZLE_HO_OH_SOLVED

src/data/sliding_puzzles.h

--IMPORTANT: only make this change if you use an up-to-date version of pokeemerald that uses INCGFX for sprites and no longer uses spritesheet_rules.mk

change the first 4 lines from:

static const u32 sKabutoPuzzle_Gfx[] = INCBIN_U32("graphics/sliding_puzzle/puzzles/kabuto/tiles.4bpp.lz");
static const u32 sOmanytePuzzle_Gfx[] = INCBIN_U32("graphics/sliding_puzzle/puzzles/omanyte/tiles.4bpp.lz");
static const u32 sAerodactylPuzzle_Gfx[] = INCBIN_U32("graphics/sliding_puzzle/puzzles/aerodactyl/tiles.4bpp.lz");
static const u32 sHoOhPuzzle_Gfx[] = INCBIN_U32("graphics/sliding_puzzle/puzzles/ho_oh/tiles.4bpp.lz");

to

static const u32 sKabutoPuzzle_Gfx[]     = INCGFX_U32("graphics/sliding_puzzle/puzzles/kabuto/tiles.png", ".4bpp.lz", "-mwidth 4 -mheight 4");
static const u32 sOmanytePuzzle_Gfx[]    = INCGFX_U32("graphics/sliding_puzzle/puzzles/omanyte/tiles.png", ".4bpp.lz", "-mwidth 4 -mheight 4");
static const u32 sAerodactylPuzzle_Gfx[] = INCGFX_U32("graphics/sliding_puzzle/puzzles/aerodactyl/tiles.png", ".4bpp.lz", "-mwidth 4 -mheight 4");
static const u32 sHoOhPuzzle_Gfx[]       = INCGFX_U32("graphics/sliding_puzzle/puzzles/ho_oh/tiles.png", ".4bpp.lz", "-mwidth 4 -mheight 4");

[map]/scripts.inc

add the below code to your map scripts.inc or wherever you keep your added code

in porymap, however you want to access the puzzle, set these as the script:
SlidingPuzzle_EventScript_Kabuto_Start
SlidingPuzzle_EventScript_Omanyte_Start
SlidingPuzzle_EventScript_Aerodactyl_Start
SlidingPuzzle_EventScript_Ho_oh_Start

//Sliding Puzzles Start

SlidingPuzzle_EventScript_Kabuto_Start::
	goto_if_set FLAG_SLIDING_PUZZLE_KABUTO_SOLVED, SlidingPuzzle_EventScript_Kabuto_Solved
	fadescreen FADE_TO_BLACK
	setvar VAR_0x8004, SLIDING_PUZZLE_KABUTO
	setvar VAR_0x8005, FALSE
	special DoSlidingPuzzle
	waitstate
	goto_if_eq VAR_RESULT, TRUE, SlidingPuzzle_EventScript_Kabuto_SetFlag
	release
	end

SlidingPuzzle_EventScript_Kabuto_SetFlag:
	setflag FLAG_SLIDING_PUZZLE_KABUTO_SOLVED
	release
	end

SlidingPuzzle_EventScript_Kabuto_Solved:
	fadescreen FADE_TO_BLACK
	setvar VAR_0x8004, SLIDING_PUZZLE_KABUTO
	setvar VAR_0x8005, TRUE
	special DoSlidingPuzzle
	waitstate
	release
	end

SlidingPuzzle_EventScript_Omanyte_Start::
	goto_if_set FLAG_SLIDING_PUZZLE_OMANYTE_SOLVED, SlidingPuzzle_EventScript_Omanyte_Solved
	fadescreen FADE_TO_BLACK
	setvar VAR_0x8004, SLIDING_PUZZLE_OMANYTE
	setvar VAR_0x8005, FALSE
	special DoSlidingPuzzle
	waitstate
	goto_if_eq VAR_RESULT, TRUE, SlidingPuzzle_EventScript_Omanyte_SetFlag
	release
	end

SlidingPuzzle_EventScript_Omanyte_SetFlag:
	setflag FLAG_SLIDING_PUZZLE_OMANYTE_SOLVED
	release
	end

SlidingPuzzle_EventScript_Omanyte_Solved:
	fadescreen FADE_TO_BLACK
	setvar VAR_0x8004, SLIDING_PUZZLE_OMANYTE
	setvar VAR_0x8005, TRUE
	special DoSlidingPuzzle
	waitstate
	release
	end

SlidingPuzzle_EventScript_Aerodactyl_Start::
	goto_if_set FLAG_SLIDING_PUZZLE_AERODACTYL_SOLVED, SlidingPuzzle_EventScript_Aerodactyl_Solved
	fadescreen FADE_TO_BLACK
	setvar VAR_0x8004, SLIDING_PUZZLE_AERODACTYL
	setvar VAR_0x8005, FALSE
	special DoSlidingPuzzle
	waitstate
	goto_if_eq VAR_RESULT, TRUE, SlidingPuzzle_EventScript_Aerodactyl_SetFlag
	release
	end

SlidingPuzzle_EventScript_Aerodactyl_SetFlag:
	setflag FLAG_SLIDING_PUZZLE_AERODACTYL_SOLVED
	release
	end

SlidingPuzzle_EventScript_Aerodactyl_Solved:
	fadescreen FADE_TO_BLACK
	setvar VAR_0x8004, SLIDING_PUZZLE_AERODACTYL
	setvar VAR_0x8005, TRUE
	special DoSlidingPuzzle
	waitstate
	release
	end

SlidingPuzzle_EventScript_Ho_oh_Start::
	goto_if_set FLAG_SLIDING_PUZZLE_HO_OH_SOLVED, SlidingPuzzle_EventScript_Ho_oh_Solved
	fadescreen FADE_TO_BLACK
	setvar VAR_0x8004, SLIDING_PUZZLE_HO_OH
	setvar VAR_0x8005, FALSE
	special DoSlidingPuzzle
	waitstate
	goto_if_eq VAR_RESULT, TRUE, SlidingPuzzle_EventScript_Ho_oh_SetFlag
	release
	end

SlidingPuzzle_EventScript_Ho_oh_SetFlag:
	setflag FLAG_SLIDING_PUZZLE_HO_OH_SOLVED
	release
	end

SlidingPuzzle_EventScript_Ho_oh_Solved:
	fadescreen FADE_TO_BLACK
	setvar VAR_0x8004, SLIDING_PUZZLE_HO_OH
	setvar VAR_0x8005, TRUE
	special DoSlidingPuzzle
	waitstate
	release
	end


Adding The Code To Your Hack Manually

-- if adding this code manually, first download this zip sliding_puzzle.zip, extract and place folder sliding_puzzle in "[root]/graphics" folder (so the paths should end up [root]/graphics/sliding_puzzle/)

Make the changes using the above sections first, then add the below


data/event_scripts.s

add this line somewhere in the file (I added this near the top of the file in alphabetical order), this makes the new code be added to your hack when compiling.

#include "constants/sliding_puzzles.h"

data/specials.inc

add this line at the end of the file

def_special DoSlidingPuzzle

include/constants/sliding_puzzles.h

create a new file called "sliding_puzzles.h" at this location and add the following

#ifndef GUARD_SLIDING_PUZZLES_CONSTANTS_H
#define GUARD_SLIDING_PUZZLES_CONSTANTS_H

#define SLIDING_PUZZLE_KABUTO      0
#define SLIDING_PUZZLE_OMANYTE     1
#define SLIDING_PUZZLE_AERODACTYL  2
#define SLIDING_PUZZLE_HO_OH       3
#define SLIDING_PUZZLE_SOLVED      4

#define SLIDING_PUZZLE_COUNT       5

#endif // GUARD_SLIDING_PUZZLES_CONSTANTS_H

include/sliding_puzzle.h

create a new file called "sliding_puzzle.h" at this location and add the following

#ifndef GUARD_SLIDING_PUZZLE_H
#define GUARD_SLIDING_PUZZLE_H

#include "constants/sliding_puzzles.h"

enum
{
    ROTATE_NONE,
    ROTATE_ANTICLOCKWISE,
    ROTATE_CLOCKWISE,
};

enum
{
    ORIENTATION_0,
    ORIENTATION_90,
    ORIENTATION_180,
    ORIENTATION_270,
    ORIENTATION_MAX,
};

#define IMMOVABLE_TILE ORIENTATION_MAX

#define NUM_SLIDING_PUZZLE_COLS 6
#define NUM_SLIDING_PUZZLE_ROWS 4

#define FIRST_SLIDING_PUZZLE_COL  0
#define FINAL_SLIDING_PUZZLE_COL  (NUM_SLIDING_PUZZLE_COLS - 1)
#define FIRST_SLIDING_PUZZLE_ROW  0
#define FINAL_SLIDING_PUZZLE_ROW  (NUM_SLIDING_PUZZLE_ROWS - 1)

struct SlidingPuzzle
{
    u8 tiles[NUM_SLIDING_PUZZLE_ROWS][NUM_SLIDING_PUZZLE_COLS];
    u8 puzzleId;
    u8 cursorSpriteId;
    u8 heldTile;
    bool8 solved;
};

#endif // GUARD_SLIDING_PUZZLE_H

src/data/sliding_puzzles.h

create a new file called "sliding_puzzles.h" at this location and add the following

--IMPORTANT: depending if you have an older or an up-to-date version of pokeemerald, only make the change to INCGFX if you no longer use spritesheet_rules.mk

static const u32 sKabutoPuzzle_Gfx[]     = INCBIN_U32("graphics/sliding_puzzle/puzzles/kabuto/tiles.4bpp.lz");
static const u32 sOmanytePuzzle_Gfx[]    = INCBIN_U32("graphics/sliding_puzzle/puzzles/omanyte/tiles.4bpp.lz");
static const u32 sAerodactylPuzzle_Gfx[] = INCBIN_U32("graphics/sliding_puzzle/puzzles/aerodactyl/tiles.4bpp.lz");
static const u32 sHoOhPuzzle_Gfx[]       = INCBIN_U32("graphics/sliding_puzzle/puzzles/ho_oh/tiles.4bpp.lz");

static const struct CompressedSpriteSheet sSpriteSheet_Tiles[SLIDING_PUZZLE_COUNT] =
{
    [SLIDING_PUZZLE_KABUTO]     = {sKabutoPuzzle_Gfx,     0x400 * 16, GFXTAG_TILES},
    [SLIDING_PUZZLE_OMANYTE]    = {sOmanytePuzzle_Gfx,    0x400 * 16, GFXTAG_TILES},
    [SLIDING_PUZZLE_AERODACTYL] = {sAerodactylPuzzle_Gfx, 0x400 * 16, GFXTAG_TILES},
    [SLIDING_PUZZLE_HO_OH]      = {sHoOhPuzzle_Gfx,       0x400 * 16, GFXTAG_TILES},
    [SLIDING_PUZZLE_SOLVED]     = {},
};

static const u8 sPuzzleLayouts[SLIDING_PUZZLE_COUNT][NUM_SLIDING_PUZZLE_ROWS][NUM_SLIDING_PUZZLE_COLS] =
{
    [SLIDING_PUZZLE_KABUTO] =
    {
        { 6, 1,__, 3, 4,__},
        {__, 5,__, 7, 8,11},
        {__,__,10,__,12,__},
        { 2,13,14,15,16, 9},
    },
    [SLIDING_PUZZLE_OMANYTE] =
    {
        { 9,__, 2, 3, 4,11},
        {__, 5, 6,12,__,14},
        { 8,__,__,__,__,13},
        { 1, 7,__,15,16,10},
    },
    [SLIDING_PUZZLE_AERODACTYL] =
    {
        {14,__,__, 3, 4,__},
        {__,__,10, 7, 8,16},
        { 2, 9,__,11,12, 5},
        { 1,13,__,15,__, 6},
    },
    [SLIDING_PUZZLE_HO_OH] =
    {
        {15,__,14, 3, 4, 1},
        {13,__, 6, 7,__,10},
        { 2,__,__,__,12, 9},
        { 8,__, 5,__,16,11},
    },
    [SLIDING_PUZZLE_SOLVED] =
    {
        {__, 1, 2, 3, 4,__},
        {__, 5, 6, 7, 8,__},
        {__, 9,10,11,12,__},
        {__,13,14,15,16,__},
    },
};

static const u8 sTileOrientations[SLIDING_PUZZLE_COUNT][NUM_SLIDING_PUZZLE_ROWS][NUM_SLIDING_PUZZLE_COLS] =
{
    [SLIDING_PUZZLE_KABUTO] =
    {
        {ORIENTATION_90,  IMMOVABLE_TILE,  ORIENTATION_0,   IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_0},
        {ORIENTATION_0,   IMMOVABLE_TILE,  ORIENTATION_0,   IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_180},
        {ORIENTATION_0,   ORIENTATION_0,   IMMOVABLE_TILE,  ORIENTATION_0,  IMMOVABLE_TILE, ORIENTATION_0},
        {ORIENTATION_270, IMMOVABLE_TILE,  IMMOVABLE_TILE,  IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_90},
    },
    [SLIDING_PUZZLE_OMANYTE] =
    {
        {ORIENTATION_0,   ORIENTATION_0,   IMMOVABLE_TILE,  IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_90},
        {ORIENTATION_0,   IMMOVABLE_TILE,  IMMOVABLE_TILE,  ORIENTATION_90, ORIENTATION_0,  ORIENTATION_180},
        {ORIENTATION_0,   ORIENTATION_0,   ORIENTATION_0,   ORIENTATION_0,  ORIENTATION_0,  ORIENTATION_270},
        {ORIENTATION_270, ORIENTATION_180, ORIENTATION_0,   IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_0},
    },
    [SLIDING_PUZZLE_AERODACTYL] =
    {
        {ORIENTATION_180, ORIENTATION_0,   ORIENTATION_0,   IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_0},
        {ORIENTATION_0,   ORIENTATION_0,   ORIENTATION_270, IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_270},
        {ORIENTATION_270, IMMOVABLE_TILE,  ORIENTATION_0,   IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_90},
        {ORIENTATION_180, IMMOVABLE_TILE,  ORIENTATION_0,   IMMOVABLE_TILE, ORIENTATION_0,  ORIENTATION_180},
    },
    [SLIDING_PUZZLE_HO_OH] =
    {
        {ORIENTATION_180, ORIENTATION_180, ORIENTATION_90,  IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_270},
        {ORIENTATION_180, ORIENTATION_0,   IMMOVABLE_TILE,  IMMOVABLE_TILE, ORIENTATION_0,  ORIENTATION_270},
        {ORIENTATION_270, ORIENTATION_0,   ORIENTATION_0,   ORIENTATION_0,  IMMOVABLE_TILE, ORIENTATION_0},
        {ORIENTATION_90,  ORIENTATION_0,   ORIENTATION_180, ORIENTATION_0,  IMMOVABLE_TILE, ORIENTATION_270},
    },
    [SLIDING_PUZZLE_SOLVED] =
    {
        {ORIENTATION_0,   ORIENTATION_0,   ORIENTATION_0,   ORIENTATION_0,  ORIENTATION_0,  ORIENTATION_0},
        {ORIENTATION_0,   ORIENTATION_0,   ORIENTATION_0,   ORIENTATION_0,  ORIENTATION_0,  ORIENTATION_0},
        {ORIENTATION_0,   ORIENTATION_0,   ORIENTATION_0,   ORIENTATION_0,  ORIENTATION_0,  ORIENTATION_0},
        {ORIENTATION_0,   ORIENTATION_0,   ORIENTATION_0,   ORIENTATION_0,  ORIENTATION_0,  ORIENTATION_0},
    },
};

[root] spritesheet_rules.mk

-- IMPORTANT: only keep this change if you have an older version of pokeemerald and still use spriteskeet_rules.mk
-- if using an upto date version that uses INCGFX for sprites, make the alternate change for file src/data/sliding_puzzles.h


[root] sym_ewram.txt

add to the end of the file

.include "src/sliding_puzzle.o"

src/sliding_puzzle.c

create a new file called "sliding_puzzle.c" at this location and add the following

#include "global.h"
#include "sliding_puzzle.h"
#include "bg.h"
#include "decompress.h"
#include "event_data.h"
#include "gpu_regs.h"
#include "graphics.h"
#include "main.h"
#include "malloc.h"
#include "menu.h"
#include "overworld.h"
#include "palette.h"
#include "scanline_effect.h"
#include "sound.h"
#include "strings.h"
#include "task.h"
#include "text.h"
#include "text_window.h"
#include "window.h"
#include "constants/rgb.h"
#include "constants/songs.h"

static void CB2_LoadSlidingPuzzle(void);
static void CB2_SlidingPuzzle(void);
static void VBlankCB_SlidingPuzzle(void);
static void Task_SlidingPuzzle_WaitFadeIn(u8);
static void Task_SlidingPuzzle_HandleInput(u8);
static void Task_SlidingPuzzle_Glow(u8);
static void Task_SlidingPuzzle_Solved(u8);
static void Task_SlidingPuzzle_Exit(u8);
static void DrawInstructionsBar(u8);
static void CreateCursorSprite(void);
static void CreateTileSprites(void);
static void MoveCursor(s8, s8);
static void MoveCursorSprite(s8, s8, u8);
static bool32 CursorIsOnTile(void);
static bool32 CursorIsOnImmovableTile(void);
static void SelectTile(void);
static void PlaceTile(void);
static void RotateTile(u8);
static void CheckForSolution(void);
static void ExitSlidingPuzzle(u8);
static void SpriteCB_Cursor(struct Sprite *);

static EWRAM_DATA struct SlidingPuzzle *sSlidingPuzzle = NULL;

static const u32 sSlidingPuzzle_Gfx[]     = INCBIN_U32("graphics/sliding_puzzle/bg.4bpp.lz");
static const u32 sSlidingPuzzle_Tilemap[] = INCBIN_U32("graphics/sliding_puzzle/map.bin.lz");
static const u16 sSlidingPuzzle_Pal[]     = INCBIN_U16("graphics/sliding_puzzle/bg.gbapal");

enum
{
    WIN_INSTRUCTIONS_BAR,
    WIN_DUMMY,
};

static const struct WindowTemplate sWindowTemplates[] =
{
    [WIN_INSTRUCTIONS_BAR] =
    {
        .bg = 0,
        .tilemapLeft = 0,
        .tilemapTop = 0,
        .width = 30,
        .height = 2,
        .paletteNum = 13,
        .baseBlock = 1,
    },
    [WIN_DUMMY] = DUMMY_WIN_TEMPLATE,
};

static const struct BgTemplate sBgTemplates[] =
{
    {
        .bg = 0,
        .charBaseIndex = 1,
        .mapBaseIndex = 29,
        .priority = 3,
    },
    {
        .bg = 1,
        .charBaseIndex = 0,
        .mapBaseIndex = 7,
        .priority = 3,
    },
};

static const u16 sColumnXCoords[NUM_SLIDING_PUZZLE_COLS] =
{
    32, 72, 104, 136, 168, 208,
};

static const u16 sRowYCoords[NUM_SLIDING_PUZZLE_ROWS] =
{
    40,
    72,
    104,
    136,
};

static const u32 sCursor_Gfx[]      = INCBIN_U32("graphics/sliding_puzzle/cursor.4bpp.lz");
static const u16 sCursorTiles_Pal[] = INCBIN_U16("graphics/sliding_puzzle/cursor_tiles.gbapal");

#define TAG_CURSOR    244
#define GFXTAG_TILES  245

static const struct SpritePalette sSpritePalettes_CursorTiles[] =
{
    {
        .data = sCursorTiles_Pal,
        .tag = TAG_CURSOR
    },
    {},
};

static const struct CompressedSpriteSheet sSpriteSheet_Cursor =
{
    .data = sCursor_Gfx,
    .size = 0x400,
    .tag = TAG_CURSOR,
};

static const struct OamData sOamData_Cursor =
{
    .y = 0,
    .affineMode = ST_OAM_AFFINE_OFF,
    .objMode = ST_OAM_OBJ_NORMAL,
    .mosaic = 0,
    .bpp = ST_OAM_4BPP,
    .shape = SPRITE_SHAPE(32x32),
    .x = 0,
    .matrixNum = 0,
    .size = SPRITE_SIZE(32x32),
    .tileNum = 0,
    .priority = 2,
    .paletteNum = 0,
    .affineParam = 0,
};

static const struct SpriteTemplate sSpriteTemplate_Cursor =
{
    .tileTag = TAG_CURSOR,
    .paletteTag = TAG_CURSOR,
    .oam = &sOamData_Cursor,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCB_Cursor,
};

static const struct OamData sOamData_Tiles =
{
    .y = 0,
    .affineMode = ST_OAM_AFFINE_NORMAL,
    .objMode = ST_OAM_OBJ_NORMAL,
    .mosaic = 0,
    .bpp = ST_OAM_4BPP,
    .shape = SPRITE_SHAPE(32x32),
    .x = 0,
    .matrixNum = 0,
    .size = SPRITE_SIZE(32x32),
    .tileNum = 0,
    .priority = 2,
    .paletteNum = 0,
    .affineParam = 0,
};

static const union AnimCmd sAnim_Tile0[] =
{
    ANIMCMD_FRAME(0 * 16, 30),
    ANIMCMD_END,
};

static const union AnimCmd sAnim_Tile1[] =
{
    ANIMCMD_FRAME(1 * 16, 30),
    ANIMCMD_END,
};

static const union AnimCmd sAnim_Tile2[] =
{
    ANIMCMD_FRAME(2 * 16, 30),
    ANIMCMD_END,
};

static const union AnimCmd sAnim_Tile3[] =
{
    ANIMCMD_FRAME(3 * 16, 30),
    ANIMCMD_END,
};

static const union AnimCmd sAnim_Tile4[] =
{
    ANIMCMD_FRAME(4 * 16, 30),
    ANIMCMD_END,
};

static const union AnimCmd sAnim_Tile5[] =
{
    ANIMCMD_FRAME(5 * 16, 30),
    ANIMCMD_END,
};

static const union AnimCmd sAnim_Tile6[] =
{
    ANIMCMD_FRAME(6 * 16, 30),
    ANIMCMD_END,
};

static const union AnimCmd sAnim_Tile7[] =
{
    ANIMCMD_FRAME(7 * 16, 30),
    ANIMCMD_END,
};

static const union AnimCmd sAnim_Tile8[] =
{
    ANIMCMD_FRAME(8 * 16, 30),
    ANIMCMD_END,
};

static const union AnimCmd sAnim_Tile9[] =
{
    ANIMCMD_FRAME(9 * 16, 30),
    ANIMCMD_END,
};

static const union AnimCmd sAnim_Tile10[] =
{
    ANIMCMD_FRAME(10 * 16, 30),
    ANIMCMD_END,
};

static const union AnimCmd sAnim_Tile11[] =
{
    ANIMCMD_FRAME(11 * 16, 30),
    ANIMCMD_END,
};

static const union AnimCmd sAnim_Tile12[] =
{
    ANIMCMD_FRAME(12 * 16, 30),
    ANIMCMD_END,
};

static const union AnimCmd sAnim_Tile13[] =
{
    ANIMCMD_FRAME(13 * 16, 30),
    ANIMCMD_END,
};

static const union AnimCmd sAnim_Tile14[] =
{
    ANIMCMD_FRAME(14 * 16, 30),
    ANIMCMD_END,
};

static const union AnimCmd sAnim_Tile15[] =
{
    ANIMCMD_FRAME(15 * 16, 30),
    ANIMCMD_END,
};

static const union AnimCmd *const sAnims_Tiles[] =
{
    sAnim_Tile0,
    sAnim_Tile1,
    sAnim_Tile2,
    sAnim_Tile3,
    sAnim_Tile4,
    sAnim_Tile5,
    sAnim_Tile6,
    sAnim_Tile7,
    sAnim_Tile8,
    sAnim_Tile9,
    sAnim_Tile10,
    sAnim_Tile11,
    sAnim_Tile12,
    sAnim_Tile13,
    sAnim_Tile14,
    sAnim_Tile15,
};

static const union AffineAnimCmd sSpriteAffineAnim_Rotated0[] =
{
    AFFINEANIMCMD_FRAME(0x100, 0x100, 0, 0),
    AFFINEANIMCMD_JUMP(0),
};

static const union AffineAnimCmd sSpriteAffineAnim_Rotated90[] =
{
    AFFINEANIMCMD_FRAME(0x100, 0x100, -64, 0),
    AFFINEANIMCMD_JUMP(0),
};

static const union AffineAnimCmd sSpriteAffineAnim_Rotated180[] =
{
    AFFINEANIMCMD_FRAME(0x100, 0x100, -128, 0),
    AFFINEANIMCMD_JUMP(0),
};

static const union AffineAnimCmd sSpriteAffineAnim_Rotated270[] =
{
    AFFINEANIMCMD_FRAME(0x100, 0x100, 64, 0),
    AFFINEANIMCMD_JUMP(0),
};

static const union AffineAnimCmd sSpriteAffineAnim_RotatingClockwise0to90[] =
{
    AFFINEANIMCMD_FRAME(0x100, 0x100, 0, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -8, 8),
    AFFINEANIMCMD_END,
};

static const union AffineAnimCmd sSpriteAffineAnim_RotatingClockwise90to180[] =
{
    AFFINEANIMCMD_FRAME(0x100, 0x100, -64, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -8, 8),
    AFFINEANIMCMD_END,
};

static const union AffineAnimCmd sSpriteAffineAnim_RotatingClockwise180to270[] =
{
    AFFINEANIMCMD_FRAME(0x100, 0x100, -128, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -8, 8),
    AFFINEANIMCMD_END,
};

static const union AffineAnimCmd sSpriteAffineAnim_RotatingClockwise270to360[] =
{
    AFFINEANIMCMD_FRAME(0x100, 0x100, 64, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -8, 8),
    AFFINEANIMCMD_END,
};

static const union AffineAnimCmd sSpriteAffineAnim_RotatingAnticlockwise360to270[] =
{
    AFFINEANIMCMD_FRAME(0x100, 0x100, 0, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 8, 8),
    AFFINEANIMCMD_END,
};

static const union AffineAnimCmd sSpriteAffineAnim_RotatingAnticlockwise270to180[] =
{
    AFFINEANIMCMD_FRAME(0x100, 0x100, 64, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 8, 8),
    AFFINEANIMCMD_END,
};

static const union AffineAnimCmd sSpriteAffineAnim_RotatingAnticlockwise180to90[] =
{
    AFFINEANIMCMD_FRAME(0x100, 0x100, -128, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 8, 8),
    AFFINEANIMCMD_END,
};

static const union AffineAnimCmd sSpriteAffineAnim_RotatingAnticlockwise90to0[] =
{
    AFFINEANIMCMD_FRAME(0x100, 0x100, -64, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 8, 8),
    AFFINEANIMCMD_END,
};

static const union AffineAnimCmd *const sSpriteAffineAnimTable_RotateTile[] =
{
    sSpriteAffineAnim_Rotated0,
    sSpriteAffineAnim_Rotated90,
    sSpriteAffineAnim_Rotated180,
    sSpriteAffineAnim_Rotated270,
    sSpriteAffineAnim_RotatingAnticlockwise360to270,
    sSpriteAffineAnim_RotatingAnticlockwise90to0,
    sSpriteAffineAnim_RotatingAnticlockwise180to90,
    sSpriteAffineAnim_RotatingAnticlockwise270to180,
    sSpriteAffineAnim_RotatingClockwise0to90,
    sSpriteAffineAnim_RotatingClockwise90to180,
    sSpriteAffineAnim_RotatingClockwise180to270,
    sSpriteAffineAnim_RotatingClockwise270to360,
};

static const struct SpriteTemplate sSpriteTemplate_Tiles =
{
    .tileTag = GFXTAG_TILES,
    .paletteTag = TAG_CURSOR,
    .oam = &sOamData_Tiles,
    .anims = sAnims_Tiles,
    .images = NULL,
    .affineAnims = sSpriteAffineAnimTable_RotateTile,
    .callback = SpriteCallbackDummy,
};

#define __ 0

#include "data/sliding_puzzles.h"

enum
{
    INSTRUCTION_NO_SELECTION,
    INSTRUCTION_PICK_UP,
    INSTRUCTION_PLACE,
    INSTRUCTION_SWAP,
    INSTRUCTION_ROTATE,
    INSTRUCTION_CONTINUE,
};

static const u8 sText_MoveQuit[]            = _("{DPAD_NONE} Move {B_BUTTON} Quit");
static const u8 sText_MovePickUpQuit[]      = _("{DPAD_NONE} Move {A_BUTTON} Pick Up {B_BUTTON} Quit");
static const u8 sText_MovePlaceRotateQuit[] = _("{DPAD_NONE} Move {A_BUTTON} Place {L_BUTTON}{R_BUTTON} Rotate {B_BUTTON} Quit");
static const u8 sText_MoveSwapRotateQuit[]  = _("{DPAD_NONE} Move {A_BUTTON} Swap {L_BUTTON}{R_BUTTON} Rotate {B_BUTTON} Quit");
static const u8 sText_MoveRotateQuit[]      = _("{DPAD_NONE} Move {L_BUTTON}{R_BUTTON} Rotate {B_BUTTON} Quit");
static const u8 sText_Continue[]            = _("{A_BUTTON}{B_BUTTON} Continue");

static const u8 *const sInstructions[] =
{
    [INSTRUCTION_NO_SELECTION] = sText_MoveQuit,
    [INSTRUCTION_PICK_UP]      = sText_MovePickUpQuit,
    [INSTRUCTION_PLACE]        = sText_MovePlaceRotateQuit,
    [INSTRUCTION_SWAP]         = sText_MoveSwapRotateQuit,
    [INSTRUCTION_ROTATE]       = sText_MoveRotateQuit,
    [INSTRUCTION_CONTINUE]     = sText_Continue,
};

void DoSlidingPuzzle(void)
{
    SetMainCallback2(CB2_LoadSlidingPuzzle);
    gMain.savedCallback = CB2_ReturnToFieldContinueScriptPlayMapMusic;
}

static void CB2_LoadSlidingPuzzle(void)
{
    switch (gMain.state)
    {
    case 0:
        SetVBlankCallback(NULL);
        SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_OBJ_ON | DISPCNT_BG_ALL_ON | DISPCNT_OBJ_1D_MAP);
        SetGpuReg(REG_OFFSET_BLDCNT, 0);
        SetGpuReg(REG_OFFSET_BLDALPHA, 0);
        SetGpuReg(REG_OFFSET_BLDY, 0);
        SetGpuReg(REG_OFFSET_BG3CNT, 0);
        SetGpuReg(REG_OFFSET_BG2CNT, 0);
        SetGpuReg(REG_OFFSET_BG1CNT, 0);
        SetGpuReg(REG_OFFSET_BG0CNT, 0);
        SetGpuReg(REG_OFFSET_BG3HOFS, 0);
        SetGpuReg(REG_OFFSET_BG3VOFS, 0);
        SetGpuReg(REG_OFFSET_BG2HOFS, 0);
        SetGpuReg(REG_OFFSET_BG2VOFS, 0);
        SetGpuReg(REG_OFFSET_BG1HOFS, 0);
        SetGpuReg(REG_OFFSET_BG1VOFS, 0);
        SetGpuReg(REG_OFFSET_BG0HOFS, 0);
        SetGpuReg(REG_OFFSET_BG0VOFS, 0);
        break;
    case 1:
        ScanlineEffect_Stop();
        ResetTasks();
        ResetSpriteData();
        FreeAllSpritePalettes();
        ResetPaletteFade();
        CpuFill32(0, (void *)VRAM, VRAM_SIZE);
        ResetBgsAndClearDma3BusyFlags(0);
        break;
    case 2:
        InitBgsFromTemplates(0, sBgTemplates, ARRAY_COUNT(sBgTemplates));
        InitWindows(sWindowTemplates);
        break;
    case 3:
        sSlidingPuzzle = AllocZeroed(sizeof(*sSlidingPuzzle));
        sSlidingPuzzle->heldTile = __;
        sSlidingPuzzle->puzzleId = gSpecialVar_0x8004;
        sSlidingPuzzle->solved = gSpecialVar_0x8005;
        break;
    case 4:
        LoadPalette(GetTextWindowPalette(2), 0xD0, 32);
        if (sSlidingPuzzle->solved)
            DrawInstructionsBar(INSTRUCTION_CONTINUE);
        else
            DrawInstructionsBar(INSTRUCTION_NO_SELECTION);
        ShowBg(0);
        break;
    case 5:
        LoadPalette(sSlidingPuzzle_Pal, 0, 32);
        LZ77UnCompVram(sSlidingPuzzle_Gfx, (void *)(BG_CHAR_ADDR(0)));
        LZ77UnCompVram(sSlidingPuzzle_Tilemap, (void *)(BG_SCREEN_ADDR(7)));
        ShowBg(1);
        break;
    case 6:
        LoadSpritePalettes(sSpritePalettes_CursorTiles);
        BlendPalettes(PALETTES_ALL, 16, RGB_BLACK);
        break;
    case 7:
        LoadCompressedSpriteSheet(&sSpriteSheet_Cursor);
        CreateCursorSprite();
        break;
    case 8:
        LoadCompressedSpriteSheet(&sSpriteSheet_Tiles[sSlidingPuzzle->puzzleId]);
        CreateTileSprites();
        break;
    default:
        BeginNormalPaletteFade(PALETTES_ALL, 0, 16, 0, RGB_BLACK);
        CreateTask(Task_SlidingPuzzle_WaitFadeIn, 0);
        SetVBlankCallback(VBlankCB_SlidingPuzzle);
        SetMainCallback2(CB2_SlidingPuzzle);
        return;
    }
    gMain.state++;
}

static void CB2_SlidingPuzzle(void)
{
    RunTasks();
    AnimateSprites();
    BuildOamBuffer();
    DoScheduledBgTilemapCopiesToVram();
    UpdatePaletteFade();
}

static void VBlankCB_SlidingPuzzle(void)
{
    LoadOam();
    ProcessSpriteCopyRequests();
    TransferPlttBuffer();
}

static void Task_SlidingPuzzle_WaitFadeIn(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        if (!sSlidingPuzzle->solved)
            gTasks[taskId].func = Task_SlidingPuzzle_HandleInput;
        else
            gTasks[taskId].func = Task_SlidingPuzzle_Solved;
    }
}

#define sTimer        data[0]
#define sAnimating    data[1]
#define sRow          data[2]
#define sCol          data[3]
#define sTileId       data[4]
#define sOrientation  data[5]

static void Task_SlidingPuzzle_HandleInput(u8 taskId)
{
    // Prevent input if the puzzle has been solved
    if (sSlidingPuzzle->solved)
    {
        gTasks[taskId].func = Task_SlidingPuzzle_Glow;
        return;
    }

    // Prevent input while the tile is rotating
    if (sSlidingPuzzle->heldTile != __)
    {
        struct Sprite *sprite = &gSprites[sSlidingPuzzle->heldTile];

        if (sprite->sAnimating && !sprite->affineAnimEnded)
            return;
    }

    if (JOY_NEW(B_BUTTON))
        ExitSlidingPuzzle(taskId);
    else if (JOY_NEW(DPAD_LEFT))
        MoveCursor(-1,  0);
    else if (JOY_NEW(DPAD_RIGHT))
        MoveCursor( 1,  0);
    else if (JOY_NEW(DPAD_UP))
        MoveCursor( 0, -1);
    else if (JOY_NEW(DPAD_DOWN))
        MoveCursor( 0,  1);

    if (sSlidingPuzzle->heldTile == __)
    {
        // Not holding a tile
        if (CursorIsOnTile() && !CursorIsOnImmovableTile())
        {
            DrawInstructionsBar(INSTRUCTION_PICK_UP);
            if (JOY_NEW(A_BUTTON))
                SelectTile();
        }
        else
        {
            DrawInstructionsBar(INSTRUCTION_NO_SELECTION);
        }
    }
    else
    {
        // Currently holding a tile
        if (CursorIsOnTile() && !CursorIsOnImmovableTile())
        {
            DrawInstructionsBar(INSTRUCTION_SWAP);
            if (JOY_NEW(A_BUTTON))
                SelectTile();
        }
        else if (CursorIsOnImmovableTile())
        {
            DrawInstructionsBar(INSTRUCTION_ROTATE);
        }
        else
        {
            DrawInstructionsBar(INSTRUCTION_PLACE);
            if (JOY_NEW(A_BUTTON))
                PlaceTile();
        }

        if (JOY_NEW(L_BUTTON))
            RotateTile(ROTATE_ANTICLOCKWISE);
        else if (JOY_NEW(R_BUTTON))
            RotateTile(ROTATE_CLOCKWISE);
    }
}

#define tState  data[0]
#define tTimer  data[1]
#define tGlow   data[2]

static void Task_SlidingPuzzle_Glow(u8 taskId)
{
    u16 color;
    s16* data = gTasks[taskId].data;
    switch (tState)
    {
    case 0:
        DrawInstructionsBar(INSTRUCTION_CONTINUE);
        tState++;
        break;
    case 1:
        DestroySprite(&gSprites[sSlidingPuzzle->cursorSpriteId]);
        PlayFanfare(MUS_LEVEL_UP);
        tGlow = 22;
        tState++;
        break;
    case 2:
        tTimer++;
        if ((tTimer % 4) == 0)
        {
            if (++tGlow == 31)
                tState++;
            color = RGB(tGlow, tGlow, tGlow);
            LoadPalette(&color, 0x103, sizeof(color));
        }
        break;
    case 3:
        tTimer++;
        if ((tTimer % 4) == 0)
        {
            tGlow--;
            if (tGlow > 22)
            {
                color = RGB(tGlow, tGlow, tGlow);
            }
            else
            {
                color = RGB(28, 22, 13);
                tState++;
            }
            LoadPalette(&color, 0x103, sizeof(color));
        }
        break;
    default:
        if (IsFanfareTaskInactive())
            gTasks[taskId].func = Task_SlidingPuzzle_Solved;
        break;
    }
}

#undef tState
#undef tTimer
#undef tGlow

static void Task_SlidingPuzzle_Solved(u8 taskId)
{
    if (JOY_NEW(A_BUTTON) || JOY_NEW(B_BUTTON))
        ExitSlidingPuzzle(taskId);
}

static void Task_SlidingPuzzle_Exit(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        gSpecialVar_Result = sSlidingPuzzle->solved;
        Free(sSlidingPuzzle);
        FreeAllWindowBuffers();
        SetMainCallback2(gMain.savedCallback);
    }
}

static void DrawInstructionsBar(u8 stringId)
{
    const u8 color[3] = { TEXT_DYNAMIC_COLOR_6, TEXT_COLOR_WHITE, TEXT_COLOR_DARK_GRAY };

    FillWindowPixelBuffer(WIN_INSTRUCTIONS_BAR, PIXEL_FILL(15));
    AddTextPrinterParameterized3(WIN_INSTRUCTIONS_BAR, FONT_SMALL, 2, 1, color, 0, sInstructions[stringId]);
    PutWindowTilemap(WIN_INSTRUCTIONS_BAR);
    CopyWindowToVram(WIN_INSTRUCTIONS_BAR, COPYWIN_FULL);
    ScheduleBgCopyTilemapToVram(0);
}

static void CreateCursorSprite(void)
{
    u8 spriteId = SPRITE_NONE;

    if (!sSlidingPuzzle->solved)
    {
        spriteId = CreateSprite(&sSpriteTemplate_Cursor,
                                sColumnXCoords[0],
                                sRowYCoords[0],
                                1);
        gSprites[spriteId].sTimer = 0;
        gSprites[spriteId].sAnimating = TRUE;
        gSprites[spriteId].sRow = 0;
        gSprites[spriteId].sCol = 0;
    }

    sSlidingPuzzle->cursorSpriteId = spriteId;
}

static void CreateTileSprites(void)
{
    u8 row, col, puzzleId, tile;
    for (row = 0; row < NUM_SLIDING_PUZZLE_ROWS; ++row)
    {
        for (col = 0; col < NUM_SLIDING_PUZZLE_COLS; ++col)
        {
            if (sSlidingPuzzle->solved)
                puzzleId = SLIDING_PUZZLE_SOLVED;
            else
                puzzleId = sSlidingPuzzle->puzzleId;

            tile = sPuzzleLayouts[puzzleId][row][col];
            if (tile != __)
            {
                u8 spriteId = CreateSprite(&sSpriteTemplate_Tiles,
                                           sColumnXCoords[col],
                                           sRowYCoords[row],
                                           2);
                struct Sprite *sprite = &gSprites[spriteId];
                sprite->sAnimating = FALSE;
                sprite->sRow = row;
                sprite->sCol = col;
                sprite->sTileId = tile;
                sprite->sOrientation = sTileOrientations[puzzleId][row][col];
                if (sprite->sOrientation >= ORIENTATION_MAX)
                    sprite->sOrientation = ORIENTATION_0;
                StartSpriteAnim(sprite, tile - 1);
                StartSpriteAffineAnim(sprite, sprite->sOrientation);
                tile = spriteId;
            }

            sSlidingPuzzle->tiles[row][col] = tile;
        }
    }
}

static void MoveCursor(s8 deltaX, s8 deltaY)
{
    MoveCursorSprite(deltaX, deltaY, sSlidingPuzzle->cursorSpriteId);

    if (sSlidingPuzzle->heldTile != __)
    {
        MoveCursorSprite(deltaX, deltaY, sSlidingPuzzle->heldTile);
        PlaySE(SE_BALL_TRAY_ENTER);
    }
}

static void MoveCursorSprite(s8 deltaX, s8 deltaY, u8 spriteId)
{
    struct Sprite *sprite = &gSprites[spriteId];
    s8 row = sprite->sRow + deltaY;
    s8 col = sprite->sCol + deltaX;

    if (row < FIRST_SLIDING_PUZZLE_ROW)
        row = FINAL_SLIDING_PUZZLE_ROW;
    else if (row > FINAL_SLIDING_PUZZLE_ROW)
        row = FIRST_SLIDING_PUZZLE_ROW;

    if (col < FIRST_SLIDING_PUZZLE_COL)
        col = FINAL_SLIDING_PUZZLE_COL;
    else if (col > FINAL_SLIDING_PUZZLE_COL)
        col = FIRST_SLIDING_PUZZLE_COL;

    sprite->sCol = col;
    sprite->sRow = row;
    sprite->x = sColumnXCoords[sprite->sCol];
    sprite->y = sRowYCoords[sprite->sRow];

    sprite->invisible = FALSE;
    sprite->sTimer = 0;
}

static bool32 CursorIsOnTile(void)
{
    struct Sprite *cursor = &gSprites[sSlidingPuzzle->cursorSpriteId];

    if (sSlidingPuzzle->tiles[cursor->sRow][cursor->sCol] != __)
        return TRUE;

    return FALSE;
}

static bool32 CursorIsOnImmovableTile(void)
{
    struct Sprite *cursor = &gSprites[sSlidingPuzzle->cursorSpriteId];

    if (sTileOrientations[sSlidingPuzzle->puzzleId][cursor->sRow][cursor->sCol] == IMMOVABLE_TILE)
        return TRUE;

    return FALSE;
}

static void SelectTile(void)
{
    struct Sprite *cursor = &gSprites[sSlidingPuzzle->cursorSpriteId];

    u8 tile = sSlidingPuzzle->heldTile;
    if (tile != __)
        gSprites[tile].subpriority = 2;

    sSlidingPuzzle->heldTile = sSlidingPuzzle->tiles[cursor->sRow][cursor->sCol];
    gSprites[sSlidingPuzzle->heldTile].subpriority = 0;

    sSlidingPuzzle->tiles[cursor->sRow][cursor->sCol] = tile;

    cursor->sAnimating = FALSE;
    PlaySE(SE_SELECT);
    CheckForSolution();
}

static void PlaceTile(void)
{
    struct Sprite *cursor = &gSprites[sSlidingPuzzle->cursorSpriteId];

    sSlidingPuzzle->tiles[cursor->sRow][cursor->sCol] = sSlidingPuzzle->heldTile;
    gSprites[sSlidingPuzzle->heldTile].subpriority = 2;

    sSlidingPuzzle->heldTile = __;

    cursor->sAnimating = TRUE;
    PlaySE(SE_SELECT);
    CheckForSolution();
}

static void RotateTile(u8 rotDir)
{
    struct Sprite *sprite = &gSprites[sSlidingPuzzle->heldTile];
    u8 affineAnimation;

    if (rotDir == ROTATE_ANTICLOCKWISE)
    {
        affineAnimation = sprite->sOrientation + 4;
        if (sprite->sOrientation)
            sprite->sOrientation--;
        else
            sprite->sOrientation = ORIENTATION_270;
    }
    else if (rotDir == ROTATE_CLOCKWISE)
    {
        affineAnimation = sprite->sOrientation + 8;
        sprite->sOrientation++;
        sprite->sOrientation = sprite->sOrientation % ORIENTATION_MAX;
    }

    StartSpriteAffineAnim(sprite, affineAnimation);
    sprite->sAnimating = TRUE;
}

static void CheckForSolution(void)
{
    u8 row, col, tile;
    for (row = 0; row < NUM_SLIDING_PUZZLE_ROWS; row++)
    {
        for (col = 0; col < NUM_SLIDING_PUZZLE_COLS; col++)
        {
            struct Sprite *tile = &gSprites[sSlidingPuzzle->tiles[row][col]];
            u8 tileId = sPuzzleLayouts[SLIDING_PUZZLE_SOLVED][row][col];

            if (tile->sTileId != tileId || tile->sOrientation != ORIENTATION_0)
            {
                sSlidingPuzzle->solved = FALSE;
                return;
            }
        }
    }

    sSlidingPuzzle->solved = TRUE;
}

static void ExitSlidingPuzzle(u8 taskId)
{
    BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, RGB_BLACK);
    gTasks[taskId].func = Task_SlidingPuzzle_Exit;
}

static void SpriteCB_Cursor(struct Sprite *sprite)
{
    if (++sprite->sTimer % 16 == 0)
    {
        sprite->sTimer = 0;
        if (sprite->sAnimating)
            sprite->invisible ^= TRUE;
        else
            sprite->invisible = FALSE;
    }
}

#undef sTimer
#undef sAnimating
#undef sRow
#undef sCol
#undef sTileId
#undef sOrientation

#undef __

End of guide


From Spherical Ice's pokecommunity post:

fadescreen FADE_TO_BLACK
setvar VAR_0x8004, PUZZLE_KABUTO
setvar VAR_0x8005, FALSE // or TRUE if you want to display the solved puzzle
special DoSlidingPuzzle
waitstate
goto_if_eq VAR_RESULT, TRUE, EventScript_PuzzleSolved

The layouts for puzzles are in src/data/sliding_puzzles.h. To add new puzzles, you'll need to define the new puzzle's ID in include/constants/sliding_puzzles.h, add relevant entries to sPuzzleLayouts and sTileOrientations in src/data/sliding_puzzles.h, add your new puzzle's graphics in graphics/sliding_puzzle/puzzles/ and define the graphics at the top of src/data/sliding_puzzles.h as pointed to in sSpriteSheet_Tiles. I don't really have time to write up a full tutorial on adding new puzzles but hopefully the code is self-documenting enough for someone with C experience to understand.

pokefirered branch: https://github.com/Jaizu/pokefirered/tree/sliding_puzzles
by Jaizu

The special sets VAR_RESULT to TRUE if the player solves the puzzle, but this is obviously volatile so I recommend whatever happens in your script checks that VAR_RESULT is TRUE and sets a flag to remember if so. Then, if the player interacts with the script again, check if that flag is set and set VAR_0x8005 to TRUE if it is to display the solved puzzle. I didnt incorporate flag checking directly into the special as some people may want to reuse individual puzzles in several places and it's pretty simple to just use normal script flag checking to control this logic flow.

Clone this wiki locally