SokoDay – easy to pick up, hard to put down.

Description

SokoDay downloads three new Sokoban puzzles every day from a server. One short, one medium and one long (referring to number of moves to solve the level). You can build your own device with a 3D printer, soldering iron and a handful of components. 

Sokoban is an addictive game where you push boxes to designated areas. It has been praised for being “pure and simple, very playable and mentally challenging”.

a 40 sec video showing how a medium level is loaded and solved
  • Fun and challenging to play
  • Easy to build
  • No PCB needed – 3D print two parts and connect the components using 3DPCB
  • Only a handful of components needed
  • Open source and easy to hack (write new games, change hardware etc.)
  • Use of modules simplifies design and removes the need for soldering tiny components
  • Portable – built in Li-ion battery and USB-C charging circuit
  • Wi-Fi connection to server with over 10 000 levels
  • Emulator avaliable

Idea

One day I realized that a Sokoban game could be played using a low resolution RGB matrix as display. Different colors could represent the player, boxes, walls and goals. It was such a simple idea that I expected to find several projects when I searched for it online. When I couldn’t find any, I was excited to start the development. Little would I know that I would spend several months completing and tuning the project until I was satisfied with the result. It was however a very fun project, covering electronics, embedded programming with Wi-Fi connections, industrial design, web servers and lots of scripts to gather, filter, sort and solve the different Sokoban levels.

Instead of letting the players choose from ten thousand challenges, I thought ‘less is more’ and got the idea to present a new challenge every day instead. This was in line with my ambition to make the game simplistic and clean, while hiding the backend from the user.

Sokoban game manual

  Red pulsating LEDPlayer
  Pink LEDWall
  Green LEDTarget
  Blue LEDBox (on floor)
  Cyan LED (blue + green)Box on target
  Yellow pulsating LED (red+ green)Player on target
  Black (LED off)Empty floor
Display color explanation
  • Use the four switches to move the player
  • A box can only be pushed (not pulled)
  • Push the boxes to their designated areas (targets)
  • When all boxes are on top of the goals the game is won

Note that there is no undo, you must reload the level to start over – “You don´t play Sokoban because it is easy, you play it because it is hard ;)”. This forces you to think several steps ahead instead of trial and error. It can be frustrating, but it becomes more rewarding.

You select one of three daily levels by holding down the navigation keys during power on.

  • Pressing up during power on loads the short level
  • Pressing right during power on loads medium level and
  • Pressing down during power on loads the long level

By pressing left and then one of the other keys (short, medium, long) during power up you can change the default level to be loaded. This setting is stored in nonvolatile memory and will be used during a normal power up (without navigation key). This is useful to match your preferred level or if you e.g. print several games, each with one level length.


Emulator

http://vonkonow.com/sokoplay/

If you are unsure if this game is for you, I built an html-based emulator, allowing anyone to test the game in a web browser. The daily levels are identical with the ones on the handheld device and it also shows map credits and solution. Test the game and decide if it is for you, if you like it, I highly recommend building the handheld version which provides a better experience ;)

BOM

  • 1x D1-mini board (ESP8266 based, Wemos Lolin or compatible) – This module provides both the CPU/ brain of the game and the wireless access
  • 1x WS2812 8×8 RGB LED Matrix (PCB, not FPC)
  • 1x charging module (with USB-C connection)
  • 1x Li-ion battery 503450 1000mAh (50x34x5mm) a smaller battery works, but gives a shorter battery lifetime…
  • 4x 6x6mm through hole tact switches
  • 1x miniature sliding switch (3 pin 2 position SPDT SS12D00 4mm handle)
  • 1x 20cm power or ethernet cable (providing the strands that connects the components)
  • 1x 3D printed base (PLA)
  • 1x 3D printed diffuser (preferably white PLA)

Building instructions

The device is quite easy to assemble – if you have access to a 3D printer and can identify the hot side of a soldering iron.

  • 3D print the base and the display diffuser (I used a Prusa i3 MK3S with 0.4mm nozzle and 0.2mm layer height).
  • Download the Arduino code and update the name and password of the Wi-Fi networks. (By adding home, work and mobile tethering credentials, you can use the device anywhere). It is also recommended to change the OTA password (see details below). Then compile and upload the code to the D1 mini board. (add esp8266 support if it is not present in the board manager, select 8266 lolin(wemos)D1mini board and the appropriate serial port).
  • Place the modules (D1 mini board, charging board) and battery in place (ensure that the holes align with the base) and secure them with a drop of hot glue (to avoid rattling noises).
Schematic drawing showing how the modules are connected (from top side)
  • Solder two wires to the power switch. Thread the wires through the hole in the base unit and secure the switch with a generous amount of hot glue (but ensure there is no glue preventing the sliding action). Route one wire to the D1 (but do NOT solder it in place) and the other to the charging module. Flip it over and solder the wire to the charging module (positive terminal).
  • Place the four 6x6mm tact switches and solder the common ground wire to their terminals, start where the channel ends and then route the wire through the channel and solder it to the switches one by one. Thread the wire through the base and the charging module (but do NOT solder it in place). Repeat the procedure for the other 4 wires connecting the switches to the D1 module (they can be soldered to the D1 module).
  • Connect the charging module to the ground of D1. (It is now ok to solder both wires to the charging module, but do NOT solder the other end to the D1). Solder the battery wires to the charging module (verify the polarity + is red, – is black).
  • Verify all connections carefully, check for cold solder joints and short circuits.
  • Add two wires to D1 power and ground and solder them in place with the existing wires from the switch and charging module. Also solder the third wire for the RGB data to D1 and route all three wires through their channels and through the base unit. Route the wires through the RGB matrix (ensure it is oriented so the top wire goes to data in) and place it on top of the other components. Once in place solder the wires and trim them, but spare a cm until you have verified that everything works.
All components but the display matrix in place.
Topside view with LED module mounted (covering battery, D1 & charging modules)
  • Power on the device and verify its operation (it should display random blue dots when it is searching for Wi-Fi and internet access. Once it works, trim the display wires (when the device is powered off) and add the diffuser.
  • Done, you now have three daily Sokoban levels for the coming eight years :)

Development

Once I knew what I wanted to do, I planned the project and selected suitable components, I needed to verify that both the D1 board and the display module could be operated on ~3.7V from the battery. I did some prototyping and verified that everything worked as planned. I used the built-in weak pull ups in the esp8266 for the switches, removing the need for external resistors. Since the display matrix uses serial communication, the schematics becomes dead simple and assembly too.

No D1 mini board? – Print one ;)

I only had one D1 module at home, so when I wanted to build more devices, I created a 3DPCB adaptor board that converts an ESP12F, a 3.3V LDO and two capacitors to a D1-ish board. It doesn’t have all connections of the original board, but it works for this purpose. Please note that you need to take special care programming the esp8266E module with a serial interface since it doesn’t have the USB interface of the D1-mini…

Software

By using existing Arduino libraries for Wi-Fi connection and WS2812 I could focus on the game.

Game and communication

Since Sokoban is such an easy game to understand (but difficult to master), programming the game rules is a walk in the park. Once that was done (with a hard coded map), I started to implement the server communication. Instead of using bare sockets I relied on a http connection over port 80, where the client downloads a text description of the map from a web server. Unfortunately, the ESP8266 doesn’t support TLS1.3, so it must rely on unencrypted communication, which however is fine since the game data isn’t confidential nor sensitive.

Winner animation

When the game is won the device uploads number of moves for completion and displays a rainbow plasma animation. Since the ESP8266 is so powerful I didn’t even need to create look up tables for the sinus, cos and square root functions and got good performance doing floating point calculation in real time. To be honest, I think I spent more time tweakig the plasma feature than on the actual game ;)

Over the Air programming

The device supports OTA updates, enabling you to reprogram them wirelessly. For this to work, you first need to program them with Wi-Fi credentials to the same network. It is recommended to change the password for OTA to prevent them from being hacked (I don’t want to see the devices being used as a bot-net ;) Once the device is powered on and connected to the same network, the unit can be selected in the port menu in the Arduino environment.

Industrial design

After I had verified that the components worked together, I started to sketch on how I wanted the device to be designed. I stacked the modules in different arrangements and modelled shapes in 3D where I experimented with portrait and landscape orientation, gamepad type and parting lines. I spent quite a lot of time on a tabletop version with a slanted top surface that was designed to be easy and ergonomic to use and I even made wooden block models where I experimented with the size and feel of the product. The wedge shape gave it some attitude, but it also became a bit bulky.  I then experimented with a square model with a switch on each side. I liked that the design had no orientation and since a Sokoban level can be played upside down this was pretty cool. But after printing a block model I realized that the ergonomics weren’t good when you had to hold it and press the spread-out buttons with two hands.

I realized that a thinner and symmetrical design was more pleasant to handle. My first attempt exposed the modules, which looks both cool and unfinished at the same time, when I tried to hide the modules behind the matrix, the box shape became quite boring. I therefore sketched on parting lines and contrast between the base and the diffuser, and once I split the front surface in an output and input area, I knew I was getting close. A few iterations with different radiuses and chamfers and I was happy with the design.

The diffuser is quite smart, by adjusting the thickness of the ribs and material it both diffuses and blocks the light, creating a pleasant rectangle that blends the individual LEDs. Test the game with and without it to see the notable difference.

Server side

A Sokoban game needs good levels to be fun. There are lots of different level collections online, and after analyzing different options I used the levels found on https://www.sourcecode.se/sokoban/levels .

Filter size of playfield

Since most levels are bigger than 8×8 (number of LEDs) I wrote a script that filtered out all levels above a certain size. I thought about having a dynamic view of a larger playfield, but playing Sokoban is difficult enough when you see the whole level, and seeing just a bit would probably be more painful than pleasant. Since the outer edge of a level doesn’t need to be visible, I realized that I could display levels up to 10×10 in size. I also wrote a script that cleaned the levels by removing tiles outside the playing field and removed corner bricks in the outer wall (because it looks nicer). To do this I virtually flooded the level with water, and once flooded I removed everything outside the water and then rebuilt the containing edge.

Providing solutions

I wanted to provide a solution to all the maps in the game, I therefore wrote a script that converted the levels to a format accepted by jsoko. I then ran all the maps with a timeout of 6 seconds. This resulted in a list with lots of solutions. Another script merged the solutions with corresponding level and separated the solved maps from the ones that were not solved before the timeout. I repeated the process with 60s and 600s timeouts. After more than a month of continuous crunching using a quite powerful computer, I had solutions to over 10 000 levels with a size of 10×10.

Level format

I decided to create a simple custom text format for the levels instead of using a database or XML/ JSON. This makes the infrastructure easier to maintain, and since the files are quite small it scales decent. If I one day want to switch to a database, I just need a script to convert the text files. The levels are separated with a semicolon, a comma separates the level from the metadata and colons are used to separate the field names from the data in the metadata area. Here is an example of a level:

     ###
 ####  
# $    
# $###*#
#  .#..#
 ## $  #
  #   @#
   ####
,
Name:erim239-fifth collection
Columns:9
Rows:8
Authour:Erim Sever
Collection:Erim Sever Collection
Description: These levels are copyright Erim Sever  
Moves:54
Pushes:14
Solution:llluulluuRRRRurrdLDDlddlluRdrUdrUlllulluurDldRuurrRurD
Device Retries:2
Solved:1
;

Analysis and groups

I wrote another script that compiled the number of moves for the solution of each level, I imported the data in Excel and plotted a histogram to understand the distribution.

This allowed me to build a custom graph that divided the solution space in three rather equal parts. Another script then filtered out levels within an interval of moves and I then had three groups:

  • Group one can be solved in 10 to 70 moves. This is group short, or S
  • Group two can be solved in 71 to 129 moves. This is group Medium or M
  • Group three can be solved in 130 moves and more. This group is called Large, or L

The server provides 3 new levels every day, one from each group. The server uses three php scripts that isolates todays three levels. It also updates the number of times the level has been downloaded and completed, which provides rough statistics about how difficult the levels are.

Please note that the number of moves does not correlate 1:1 to the difficulty of the level. I’m sure there are large levels that are easier for a human to solve than a short level, but in most of the cases I find an L level to be more difficult than an M, which in turn usually is more challenging than the S level.

I considered to use number of pushes instead of moves, but the scatter chart above indicated that the difference would be quite small. (On average 3.8 times more moves than pushes).

It was quite fun to parse, filter, clean, solve and combine all the levels. I wrote the scripts in JavaScript, which is not ideal, but that runs in any browser. If I had known how much time I were to spend on this, I would have used Python to automate the whole chain, but “don’t mess with a working system” ;)

Troubleshooting

The device needs two things to work:

  1. power (a charged battery) and
  2. Wi-Fi connection to internet

If the device doesn’t load a level, it is likely due to one of these errors. Connect it to a 5V USB charger and verify that the Wi-Fi name and password are correct and that the access point is connected to internet.

All levels should be possible to complete (the solution has been simulated). The solution can be found on the emulator. I have solved all the bugs that I have found, but with lots of different scripts handling lots of different levels from different sources in different formats, I wouldn’t be surprised if there are a couple of bugs left. If you find one, let me know!

Improvements

I think the units works quite well the way they are, but there is always room for improvements. Here is a list of things that would make SokoDay even better – feel free to contribute :)

  • Battery meter (display when battery is too low to operate)
  • Ambient light sensor (automatically dim display in low light conditions)
  • Ability to rate levels
  • Best of the best levels, (based on level rating) 
  • Wi-Fi host (to manage Wi-Fi credentials)
  • More games and a game select menu? (note the absence of A and B buttons)

Credits

Sokoban was originally created in 1981 by Hiroyuki Imabayashi. Levels were imported from https://www.sourcecode.se/sokoban/levels together with metadata and credits (readable in the emulator).

Download project files

All files required for this project are available at this GitHub repository:
https://github.com/vonkonow/SokoDay

License

This project is open source under MIT License
(Attribution is optional, but appreciated /Johan von Konow ;)

8 Replies to “SokoDay – easy to pick up, hard to put down.”

  1. Johan! This is astonishing tremendous work! I’m shocked!!!! GREAT MAN!!!!

  2. BTW, let me know if anyone would appreciate an assembly video. It is quite straightforward to build, but as they say- a video is worth a 10^6 words ;)

  3. I just tried the HTML based emulator and found a bug.
    Basically, I can push box out of the level and can never complete a level.

    I was a playing a long level.

      1. I just tried it on the emulator, same issue.

        The level I played is: (according to the level details)
        Name:35
        Columns:9
        Rows:8
        Authour:Marcus Hof
        Collection:Revenge 33

        I can push all of the boxes out to the right hand side.
        I took a screenshot as well, but I cannot post that here.

        If you give me an email, I can email it to you.

        1. lol, there were actually two problems: The first was a standard off by one, the second was that some levels ended prematurely, causing undefined states in the map.
          Now I solved both. (let me know if you find a third ;)
          The hardware has a different code base (written in c instead of javascript) missing both bugs.

          1. Pushing boxes to outside bug is fixed.
            I found one thing that is probably better if the behavior is changed: when I try to solve a level, doing the last push to end it.
            It pops up: “vonkonow.com says Well done, level solved!” with the OK button.
            but the display didn’t show the level as solved. The last push didn’t happen.
            After I clicked the “OK” button, then it shows the last push performed.
            It would be nicer that after the last push was done — meaning the level is has all boxes on all goals, then display the “well done” message.
            It would be nice to have a undo button (or u or ctrl+z).

Leave a Reply

%d bloggers like this: