Tuesday, January 1, 2019

Thinking Differently with SHENZHEN I/O

SHENZHEN I/O is a pretty compelling game. And it's also quite challenging. So what's a game review doing on a programming blog? Because it's helping me look at programming a bit differently.

In short, it's made me come up with new ways (for me) to solve problems due to limitations in the instruction set, lines of code, and memory. In addition, I've had to consider timing issues and hardware capabilities. I know that this will help me in my future programming, even if it's not applied directly.

In the game, you get a job working for an electronics company building and programming components. Why would you play a game about being at work? I'm not 100% sure (particularly since it includes things like undocumented instructions -- something that seems a little too real). But you should give it a try.

I'll start by saying that I'm not a hardware person. I haven't fiddled with IoT devices or Arduino, Raspberry Pi, or other electronics. I've always been interested in hardware, but I haven't taken the time to pursue it seriously. (It might be time to get the Snap Circuits back out.) This game let me get a taste of what it's like in that world.

You're given various tasks that consist of inputs and outputs. It's your job to put the stuff in the middle. Here's one of the early scenarios (that I already completed):

This is a simple score keeper. When the "point" input is fired, then you add 1 to the score. When the "foul" input is fired, you subtract 2 from the score (but it can't go below zero).

A Simple Program
You connect things using programmable modules, like this one:

The programming is through a limited set of instructions that is similar to assembly language. I've done a little hobby programming with assembly using the x86 instruction set, so these ideas weren't completely new to me.

To walk through this program...
  • teq 100 p1
    "teq" is "test equal", so this compares the value 100 with the input on p1 (which is connected to the "point" input). This value will be either 100 (button pushed) or 0 (button not pushed).
  • + add 1
    The "+" denotes that this line is only run if the test on the previous line is "true". (You can also have a "-" to run if "false"; this creates if/else structures.) The "add 1" will add one to the accumulator register (the "acc" on the screen). The accumulator will keep track of the current score.
  • teq 100 p0
    Similar to the first line, but this checks to see if the "foul" button is pressed.
  • + sub 2
    If there is a foul, then subtract two from the accumulator
  • tlt acc 0
    "tlt" is "test less than", so this checks to see if the accumulator value is less than 0.
  • + mov 0 acc
    "mov" will "move" the value of 0 into the accumulator. So if the accumulator is less than 0, it gets reset to 0.
  • mov acc x3
    Moves the value in the accumulator to the x3 output. This is hooked up to the LCD and shows the running score.
  • slp 1
    "slp" is "sleep". So this sleeps for 1 cycle. The program starts running over again at the next cycle.
This program is pretty simple. But it is only the beginning of the challenge.

Does It Work?
There is a verification section to check if the program is working:

This shows the inputs (point and foul) and the outputs (display). The inputs show the values changing between 0 and 100, and the display shows the expected output. If the output does not match, then the verification fails.

To help with debugging, there is the "Step" button which will pause after each line of code and the "Advance" button which will pause after the cycle is complete. You can also set breakpoints. This way you can inspect the values of the registers and otherwise see how the program behaves.

More Challenge
There is a certain amount of accomplishment in getting something to work, but there is also a challenge of being efficient. For example, the module above costs ¥5. But there is a simpler module that only costs ¥3 that we can use:

This module has a smaller number of I/O ports (4 instead of 6), but it has the 3 we need for this implementation. In addition, it allows for fewer lines of code (9 instead of 15). But since we have 8 lines of code, it fits fine here.

This is all important because you're also graded on how much things cost and how efficient they are:

This shows the "Production Cost" based on the modules that are used, and it also has "Lines of Code" which is self-explanatory. The "Power Usage" stat is a bit harder to gauge, but the power usage at each step is shown on the module. The white line on the graph shows the stats for this implementation. The outlines on the graph show what other people have done with this scenario. So this shows that my implementation is in line with what most people have achieved. Some people have lower power usage, but potentially at more cost or lines of code.

It's fun to come up with a solution, and then try to optimize it.

Challenges Ramp Up
Things get more complicated very quickly. Here's an example of something that I fought with for several hours just to get something working:

This is an alarm system. There is a "sensor" that gives values between 0 and 100. If it is higher than 20, we potentially want to sound the alarm. But in addition, there are also "on" and "off" times that specify when the alarm should be armed. So you need to get values from the real-time clock ("rtc"), compare it to the set times to see if the alarm should be armed, and also check the value of the sensor.

This was most frustrating for me because I was having trouble getting all of the modules I needed (or thought I needed) to fit along with the connecting circuits. This works (yay!), but the stats do not look good:

The stats show that my cost is ¥13. Most people had a cost of ¥8, and some people got the cost down to ¥6. My lines of code is equally high at 29, where most people could get it down to 11.

Since I had so much trouble getting this to work to start with, I didn't spend much time trying to optimize it. I'm pretty sure that I would need to take a completely different approach to the problem. I might take another shot at this in the future.

Thinking Differently
The thing that I've found most compelling about this game is that it has made me think about problems differently. I'm used to effectively unlimited resources when I write programs. I rarely think about memory usage and never think about clock cycles. The constraints create a completely new challenge.

Limited Instruction Set
The limited instruction set makes me think about the most basic operations. There are ways to do if/else and simple loops, but I often found myself wanting a nested "if" or the ability to compare 2 conditions at the same time. Here I have to break problems down in a different way than I usually do.

Restricted Lines of Code
Each module only allows for a handful of instructions. It's a challenge to write things in 15 lines of code or less, particularly when each line is single simple instruction.

Limited Storage
While there are memory modules that can be used (I haven't gotten to those scenarios yet), temporary values are stored in registers. The simplest module has a single accumulator register, and even the more complex modules only have 2 registers. I'm used to local variables where I can store temporary values. There's none of that here. So again, I have to think about the problem a bit differently.

Register Math
There's no way to simply add two values together. Instead you have to use the registers. For example, to add 2 and 3, first you "move" the 2 into the accumulator. then you "add 3". The "add" and "sub" operators always operate on the value in the accumulator. This wasn't too bad for me since I've encountered this type of math before, but it does force you to approach things a bit differently.

Hardware Constraints
The thing that has been most of a challenge is the hardware part. Each module has I/O ports that operate in different ways. Some of them are state-based (meaning, if you change the value on the port, it stays there). Others are blocking (meaning, a write operation blocks the program until the value has been read at the other end).

Other Things I Will Try
The game also has some "experiment" tasks where you're given an empty space to do whatever you'd like. I'm sure I'll play with this a lot more. Several years ago, I read Code: The Hidden Language of Computer Hardware and Software by Charles Petzold (Amazon link). I found this book very interesting because it shows the basic circuits that make up a CPU and how to build things using simple gates (AND, OR, NOT). This was particularly enlightening for me since I did not take CS courses in college. After reading the book, I wanted to go to the electronics store and get some switches and such to build an 8-bit adder. I never got around to that, but I'll take a shot at it here. This can be an interesting playground where I don't have to worry about ruining real hardware.

Wrap Up
SHENZHEN I/O is a pretty fun game. I've played for 6 hours, so I haven't invested a whole lot of time into it. But I see it as something that I'll keep going back to when I'm looking for a challenge.

It's very easy to get too comfortable in whatever environment you happen to be working in. It's good to shake things up now and again. I've found that even if I don't directly transfer those skills, they still color how I do things. And new solutions to problems come out of that. So try something new, and keep exploring.

Happy Coding!

No comments:

Post a Comment