Inspiration

For this project, I took inspiration from Rush Hour and other escape-to-win sliding puzzles. I wanted to make something more than just a carbon copy of existing puzzles, so I brainstormed new mechanics.

At first, I had the idea of having movement in the form of power-ups. Blocks would be allocated power-ups for vertical/horizontal movement at the start of the game. Then they could be moved accordingly once the level began. So the player would have to think of how to allocate these movements, and subsequently escape the puzzle.

However, this seemed too complicated, both in terms of UX design and game design. Hence I went back to the drawing board and came up with Blocked.

What it does

An innovative spin on the classic sliding puzzle game. Sliding puzzle games usually feature free-moving blocks that have to be slid to achieve a set position or to let a block free (Rush Hour). Any block may be moved, as long as there is space ahead of it.

Core game mechanic - Control

Blocked introduces a new concept: control.

image

At any point in the game, only the controlled block may be moved. Control may be transferred to another block by sliding into it. However, control may only be transferred if the currently controlled block impacts exactly one other block.

Having a single controllable block at a time, combined with the control-transfer mechanic, introduces several interesting mechanisms that are explored in the several levels in the game. Blocks have to be coordinated in order to prevent deadlock later in the game. Walls also add a new dimension to the game by implicitly introducing single-axis movement and one-way mechanisms.

Level editor

image

The game also features an in-built level editor that allows for convenient level prototyping and sharing.

Sharing levels

User-created levels can be shared via a button that is on the generated level page. This copies a shareable URL into the user's clipboard.

Example link: https://slide.jeffsieu.com/#/editor/generated/eAHT0oKC1FQurQoQ0KvQiwCy9cCgQk9PC8SuQGZD5KDiIB1wNVp6WNRDAZCdm5sLEq%2BoALIRAACupRzb

Generating solutions

Solutions can be generated then autoplayed. The generator uses the A* algorithm to search for the optimal solution.

image

How I built it

Most of the views and logic were built on top of Flutter native widgets and capability. FocusableActionDetector was used for hotkey detection on keyboard-enabled devices. GestureDetector and MouseRegion were used extensively for the level editor which required gesture information for the drag-and-drop UX.

Animations

Almost all animations in Blocked are done implicitly via animation widgets.

AnimatedPositioned - Block movement

The movement of blocks are implicitly animated using AnimatedPositioned. This automatically animates the following:

  • Block movement within the puzzle.

CPT2203141359-546x430

  • Playing the solution to a puzzle.

CPT2203141403-350x543

  • Resizing the resizable blocks/walls in the editor.

AnimationController - Main menu background

The main menu background animation is done using an AnimationController, and a Bloc that provides the same controller to subsequent app screens, so as to persist the current state of the animation. CPT2203141358-726x648

Hero transitions

Lots of Hero transitions were used throughout the app to smoothly between screens containing the same puzzle.

Theming

The app is fully themed using Material You color palettes. The colors are generated by the flex_color_scheme package, and are adapted to both light and dark themes. adaptive_theme was to store user theme brightness (Light/Dark/System) preferences.

Editor

The level editor features drag-to-resize and snappy drag-to-move gestures. MouseRegion was used to change cursor appearance, and GestureDetector was used to detect drag/pan gestures.

Each resizable object is handled by a Resizable widget, which keeps track of its internal size and offset whenever the user shifts or resizes it. It then snaps the "raw" size and offset to pre-defined intervals, so that the block/wall snaps to its appropriate size and position. Upon gesture completion, the internal size and offset are set to match the snapped size and offset.

Packages used

Blocked was mostly built from the ground up. Some packages were used for utility functions. These include:

Challenges I ran into

  • Creating levels by hand was difficult, and thus a more robust method of level creation was needed. This gave rise to the level editor.
  • FocusableActionDetector kept unintentionally losing focus, which causes keyboard shortcuts to break in the level editor.
  • Testing levels was very time-consuming, and it was hard to tell whether a created level was solvable or not. This was solved with the introduction of a puzzle solver.
  • Navigator 2.0 was hard to figure out, especially when dealing with the bidirectional changes between app-induced navigation and URL-based navigation. This was prevalent when working on loading level information from the URL for the editor and generated level pages.

Accomplishments that I'm proud of

  • Creating a built-in level editor
  • Creating a puzzle solver that uses A* Search with a simple heuristic
  • Being able to make a fun game

What I learned

  • How to chain various native Flutter widgets to create complex widgets like resizable containers.
  • Getting a computer to solve a problem for you is almost always better than solving it yourself

What's next for blocked

Addition of complex mechanics, for example:

  • Floor spaces that require blocks on top of them to open the exit.
  • Blocks with durability (fixed number of moves or control transfers before disappearing)

Built With

+ 16 more
Share this project:

Updates