ZPuzzle
A slide puzzle made with Flutter for the Flutter Puzzle Hack.
Try it on the Web: https://play-zpuzzle.web.app/ Or on the Play Store: https://play.google.com/store/apps/details?id=com.apalala.zpuzzle
Its core features are:
- 3D effect: 3D like animations using the plugin made for this hackathon,
zwidget
- Widget as background: Use of almost any Widget as background of the puzzle. You can also pick your own images, including Gif.
- Correct position indicators: They point towards the correct position of each tile.
- Auto-solve: An easy to learn approach is used so the player can reproduce it.
- Responsive design: Looks well on almost any screen size.
- Multi-Platform: Works on Mobile, Desktop and Web (with limitations on the Web)
- Inputs: Play the puzzle with your mouse or keyboard on Desktop and Web or by tapping on the tiles on Mobile
- Gyroscope: Use the gyroscope to see the 3D effect in action on Mobile
How we built it
Thanks to the work done by Very Good Ventures, the main logic of the game was already done and I could focus on the UX. I took their base Puzzle class and added a few features to make my own version of the game. Rest of the app is done by myself. I took inspiration from Liquid Studio to make the app's background.
Below is a detail of the ZPuzzle core features.
3D effect
Overlapping widgets with some matrix transformations can lead to a 3D like effect. That's what the package ZWidget
uses to make this effect.
It was done for this hackathon and more improvements are coming. Find more on the Github repo of ZWidget or on pub.dev.
Widget as background
The use of the OverflowBox
widget with an alignment calculated using a FractionalOffset
is the key for this feature. This is what it looks like:
Stack(children: [
Positioned.fill(
child: ClipRRect(
borderRadius: widget.borderRadius,
child: OverflowBox(
maxWidth: double.infinity,
maxHeight: double.infinity,
alignment: fracOff,
child: SizedBox(
child: widget.child,
height: nbTiles * widget.tileSize,
width: nbTiles * widget.tileSize,
),
),
),
),
Center(child: indicator),
])
Once this was done, I just had to place the tiles in a Stack
with Positioned
widget.
Correct position indicators
MaybeShowIndicator
determines if it should show and eventually animate our indicator or not.
It should show the indicator if it's on a tile that is not in its correct position.
It should appear/disappear with an animation if:
- it moved from its correct position to an incorrect position or the reverse,
- the user asked to show/hide it.
I use the didUpdateWidget()
to check if the user changed the
showIndicator setting and I animate the appearance or disappearance of the indicator accordingly.
It should also animate the rotation of the indicator if the angle between its current position and its correct position has changed.
Auto-solve
Instead of taking an approach where the AI would find the best or one of the best set of moves to solve the puzzle by trying a lot of moves, I decided to use a more human approach that I found on wikiHow.
Thanks to this, the player might learn the AI technique by looking at how it solves the puzzle. This method is not the best in terms of moves or time it takes to complete the puzzle so the player might still beat the AI score which is good for the ego.
To make this feature, I used unit tests to iterate quickly on the solve feature and make sure that everything kept working after changes.
Responsive design
I used the LayoutBuilder
a lot to determine my Widgets maxWidth and maxHeight and made their children depend on it. Font sizes are based on these measures.
If the screen size reaches a minimal width and height I scale the whole interface instead. This way, the UI keeps looking well on almost any screen size. I made a specific widget for the scale feature: FitOrScaleWidget
that again also uses OverflowBox
and LayoutBuilder
.
Multi-Platform
ZPuzzle works well on Mobile, Desktop and Web.
Mobile platform has one feature that others don't have: it can use the device's gyroscope to move the tiles and see the 3D effect in motion.
Desktop support has been well tested on MacOS since I own a Mac. However, I could not test it on Windows and Linux since I didn't have devices set with a development environment yet. These platforms should work out of the box but I didn't test them so I didn't include them in the project.
Working with the Web platform was the most painful point for me in this project. I expected it to work similarly to the Desktop platform, but instead I ran into several performance problems which cost me a lot of time.
The use of a lot of animations and/or a lot of widgets made the app become unresponsive. Even displaying a simple Gif could lead to unresponsiveness without much logs. It seemed to be (at least partially) related to an issue on the Skia engine.
As a workaround, I finally disabled the use of my package ZWidget
on the Web since it draws a lot of widgets and uses shadows instead where applicable. My tests ran well after that. A later fix to the Skia engine might allow me to enable ZWidget
again on the Web.
In the meantime, ZPuzzle looks better on Desktop and Mobile than on Web.
The Web platform has other problems: for instance, we can't use Isolates
. I also had to use the CanvasKit renderer to prevent issues with the HTML renderer and general performances are lower than on other platforms.
Despite the issues on the Web, I was quite happy and proud to be able to make one app for every platform with only occasional changes for each. I will definitively consider building more apps for Desktop and Web.
Inputs
Flutter handles the use of the mouse in addition to touch controls with the InkWell
widget. It just worked out of the box which is really nice!
I added the use of the arrow keys to move the tiles on platforms that support it (mostly Desktop and Web). The process was quite easy and it ran well quickly.
Gyroscope
I added a gyroscope feature to be able to rotate the board and see the 3D tiles. However, I am not fully satisfied with this feature, especially with getting the current x, y and z values of the gyroscope. More work needs to be done to improve it.