Page Created: 5/28/2015   Last Modified: 3/28/2019   Last Generated: 5/5/2021
- There are two ways to play
- Morse Code User Interface
- Port to Raspberry Pi and Packet Radio
- Working Commands
- Additional Notes on the Design
On May 30th, 2015, on the Georgia Tech campus in Atlanta, I gave a presentation at the first US IRDC↗ (International Roguelike Developers Conference) of a Solar, microcontroller-based RogueLike game. I've been working on ideas for an electronic boardgame since the early 1990's and began building a microcontroller-based roguelike in 2013.
I noticed after watching it that I made an error on one of the earlier slides. The ATTiny 1634 doesn't have 16 MB of Flash, it has 16 KB (kilobytes). I mentioned the correct value later in the presentation, not realizing my earlier mistake.
The slides that I used in the presentation in PDF form can be found here. They have been edited to account for the corrected Flash information.
On May 31st, there was also a recording for one of my favorite podcast radio shows, Roguelike Radio↗ which can be found here↗. At the 34:10 mark, I made a comment about the importance of design minimalism in response to the previous comment about roguelike functionalism. The roguelike genre encompasses a wide range of concepts, and I didn't want people to forget that the roguelike "core" is the epitome of RPG minimalism (which is one of the primary things that drew me to it and why I built this game). It is true that many human tools and machines are designed such that "form follows function", but roguelike games are unique types of creations in that they are random, procedurally-generated virtual worlds, not unlike a natural fractal, which contains both chaos and order. And in these types of creations, function can also follow form, the "form" being the unique instance of that world (or even the generator itself).
In other words, one could create the rules (the structure) of a game, and then fill in the details later, or one could "grow" the details first and then allow the rules, the structure, to emerge↗ during the process of exploring that world. Roguelikes are fascinating in that they incorporate both methods, a hand-crafted experience and a mathematical simulation.
I've written about many of these ideas here previously, including RogueLike, DungeonCrawl, PeaLanguage, TheBoardGame and in various places on my blog, but have never gone public with the main concept until the IRDC. The "box in a box", the recursive pattern, is something I write about on this site frequently, something I think is tied to a fractal universe, which is one of the reasons I study the roguelike structure. Computation and communication are very closely linked, and a computer and radio tuner are fundamentally the same device, but most of us don't think of it that way. Many of the computer systems that I've built are communication systems, and this is no different, which is one reason I employed Morse code in the design.
The conference was a wonderful experience, and all of the people I met were talented and amazing. Everyone treated each other as equals and with great respect--it was the most non-judgmental gathering I have attended to date. It was more than that, however--there was an actual camaraderie. I don't travel much and have never been to Georgia until then (my first trip through the Smoky Mountains), so the whole thing was an adventure.
Someone at the conference suggested that the map could indeed be randomly, procedurally generated in-chip even with extremely low levels of RAM. I do not dispute this, but it cannot easily be done or match the power of an external generator. Generators take up space, so whatever generator one decides to incorporate in-chip will limited by its space. Even if one could generate a new map, one could not generate a new subset of monsters/items from a large pool of English words. Just the spelling of those words themselves would consume all space, let alone any attributes.
So what is happening is that algorithmic complexity increases at the cost of numerical complexity, and vice-versa. Just like the real world, it is important to have both in balance with one another to try to make a rewarding experience. The interesting thing about a lot of our numerical complexity, however, it that it was already pre-generated by a much, much larger "generator" (our world, our society, etc) and has attributes that are already stored in our minds, eliminating the need to represent this information inside a tiny device.
For the few things that had to be randomly generated in-chip, I used the LCG (Linear Congruential Generator) algorithm for random numbers (since the chip has no hardware RNG and I didn't want to use an external library).
For the demo electronics, I created the simplest circuit possible with both light and sound, using the least amount of AVR lines. I wanted to demonstrate just how simple the circuit can be. I connected a 2 mA yellow LED between one of the timer pins and ground. I used a 1/2 watt surface-mount current limiting resistor for the LED (1/2 watt is not needed electrically, but the smaller resistors were too small for me to solder conventionally). I also overlapped a piezo transducer in parallel with the LED (bypassing the series resistor).
What this means is that I can turn on the LED by simply sending a logic high. This will flex the piezo transducer slightly and the transducer will use negligible power and emit only a very tiny click. But if you pulse the LED fast enough so that it appears on (due to our persistence of vision), the piezo device will emit sounds. Conveniently, the frequency of sounds that we can hear is faster than images we can see, allowing this sort of simple arrangement.
I directed all of the 6 ISP programming lines (VCC, GND, RESET, SCK, MISO, MOSI) to some jumper pins, forming a tiny port. I cut off any unused pins on the microcontroller to make soldering easier, and ran one of the ground wires underneath the chip. I then mounted this simple circuit into a wooden box, with the port on back, and the yellow LED on front. By keeping the circuit flat at the bottom of the box, the box still has space to store the game token, tiles, and keyer. I made the keyer out of a microswitch and some epoxy. The keyer was also connected to the SCK line, surrounded by a shielded return ground to reduce RF emission.
There are two ways to play:
A solar cell can be connected to the VCC and GND lines along with a keyer to play in a purely Solar, standalone mode, using International Morse Code. The commands that are crossed out have not yet been implemented.
My first design was to simply use a physical grid for the game board:
... but then I decided that physical tiles were more efficient and also more fun. The tiles allow the map to appear to "grow" like a traditional roguelike.
Or the port can be connected to a RaspberryPi and played over a UART serial connection with the game board being represented in ASCII, like a traditional roguelike. If you don't want to play on the Pi directly, you can simply access the Pi remotely via SSH and run Minicom at 9600 baud. In this mode, it accepts keyboard commands. The picture above is connected from a networked computer running Ubuntu Linux.
The ASCII symbols represent:
- empty space - floor
- # - wall
- ~ - room floor
- + - closed door
- > - downstairs
- @ - the player
- misc. symbols - items
- capital letters - monsters
I created a "growing, digging" algorithm on an external computer to procedural generate the map, creating little digging bots to excavate the map (and then annihilating themselves). It made good use of small space.
Note that the solar panel should not be connected in this mode. The ATTiny 1634 requires such low power that it can actually run off of power siphoned from some of the IO lines when connected to the Raspberry Pi. So even if you connected the solar panel in this arrangement, it would not be considered solar powered.
Having two separate play modes consumes precious space on the ATTiny 1634, but the UART server is also the primary method of developing and testing the game. So I decided to integrate the development environment into the game itself and allow it to act as a separate mode of play, if desired. If I stripped out the UART for the sake of space, it would be very hard to debug and test the game as I used up the remaining space.
Morse Code User Interface
In 2017, I published an improved Morse code decoding algorithm, called SIMTHEO, and added this algorithm to the game, which improved recognition compared to my original demo in 2015. In 2018, I then separated the decoder from the game to create its own module, added a new SIMTHEO SHH inverted mode with haptic support for my TrillSat project, and ported the decoder to the Raspberry Pi ARMv6, ARMv7, and ARMv8 architectures, but the decoder is primarily developed for the ATtiny 1634 and kept as small as possible.
You simply key (in Morse) the command and the computer blinks a yellow LED and emits dits and dahs and a few other sound effects (Success/Fail, Hits/Misses). Or, alternatively, commands can be entered via keyboard if playing via UART server.
Key in the ROW:COLUMN letters to enter word from the lookup table.
Up to 3 words can be entered in sequence. This allows combinations of actions that go beyond the simple lookup table.
Computer repeats each letter back for confirmation, and will also repeat each ROW:COLUMN pair.
If a letter or pair is incorrect, the keyer can be held down to clear the input buffer and start from the beginning.
Holding down the keyer also repeats the last message.
To enter words as command string, pause for several seconds and computer will auto-accept.
If the computer references an empty cell in Column T, you can use the Spell command to spell out the hidden word (then write it in). You can also draw a picture of the item on a blank tile. You cannot spell hidden words if you have not yet encountered the item or monster the word represents. The microcontroller can thus hold secrets that may not be unlocked for a long time.
Port to Raspberry Pi and Packet Radio
In December 2018, I ported the game to the Raspberry Pi ARMv6, ARMv7, and ARMv8 architectures, creating any constructs necessary to mimic all of the ATtiny's functions, primarily to speed up testing, as it was too slow to run around the game world to test features and bugs. But the Linux OS also aids in debugging by providing actual error messages after a crash.
The game can be played on the same Raspberry Pi used to program the game, and it can even play in tandem with the ATtiny, with the Pi dungeon in one terminal and the ATtiny dungeon via UART in another terminal, with the former being much faster. I also created a special passthrough mode which allows the Pi to put the ATtiny game into stasis and assume control over the LED and piezo speaker used by the game, so it can be played exactly like the ATtiny version, but hundreds of times faster for testing. The Pi also intercepts the ATtiny UART which allows both engines to interpret commands at the same time. Even though they are deterministic, the game worlds eventually diverge due to timing differences in user input, which is intentional (and fascinating).
Interestingly, the ARM version can also be in a "finite state machine" mode on the Pi Linux OS which makes it well-suited to some of my other servers for testing (XMPP, FCGI, or even Packet Radio). So, in my test simulations (as I haven't actually put it on the air), it can run from my packet radio BBS menu on my TrillSat experimental radio platform, or even via a special AX.25 port that listens for commands and responds back with the results of the turn. The Pi doesn't have an EEPROM like the ATtiny, so a 256-byte save file was used to mimic the EEPROM function and enforce permadeath while saving the game world between turns. Since the game resource usage is so tiny, and since the game engine only runs between turns, the packet BBS can, in theory, host a large number of users and games simultaneously.
I aligned some commands (like moving and hitting) with Morse patterns that mimicked the action, analogous to an onomatopoeia in phonetics. Walking North and South (N and A) are like footsteps (dah, dit and dit, dah, like a walking stride left-right, right-left, etc). Hitting (H) dit,dit,dit,dit is like pounding your fist.
Diagonal 8-way movement has not been implemented yet. This could be achieved by simply stringing two commands together (N, E) for example, for northeast. However, diagonal movement uses a surprisingly large amount of code to add, so this is on hold until other game mechanics are added.
Pass(P) - Pass 1 game turn.
Hit(H) - Hit monster (direction is auto-facing from last movement)
Open Close(O) - Open or close doors
Toggles between 5 display modes, primarily for testing:
0 NORMAL - Morse game and Map, Saves, Symbols are from generator
1 BLINDMORSE - No map, played via Morse, Saves, Symbols are from generator
2 HIDDENMORSE - Morse game and Map, Saves, but Symbols are hidden
3 NOMORSE - Map and Saves, but no Morse, Symbols are from generator
4 FASTTEST - No Morse, no Save, Symbols are actual letters in command table for quick input
The STATEMACHINE modes aren't accessible via the Gamemode command, but are set at compile time.
Get(G) - Get all items on your cell. Individual items can also be picked up if specified.
Drop(W) - Drop an item in your inventory. Item must be indicated using ROW:COLUMN position in table (Morse).
Inventory(I) - Inventory of items you carry.
Climb© - Climb up or down stairs to another level.
Spell(S) - Spell a word. Spell out the letters as selected via its table ROW:COLUMN position (meant to identify unknown object).
Position(V) - Get present position on game board. Provides X,Y,Z coordinates starting from lower left corner 0,0,0 (A,A,A). This is important if player gets lost, if power loss causes the game to reset, or if player is resuming play in the future.
Escape(ESC) - Escape out of the command
Quit(-) - ARM Architecture only - Exit the game engine
Passthrough(*) - ARM Architecture only - Passthrough mode toggle
Currently, I have used 11K of the 16K of flash memory for a basic roguelike core, and I had to make some compromises to get a "playable" demo. It is just a basic proof-of-concept, hack-and-slash engine at this stage.
- There are only 3 player/monster stats: STRENGTH, DEXTERITY, HIT POINTS.
- Player HP is restored by 1 point every successful spatial move (non-combat). There is no other healing mechanism at this point, but certain items can increase HP.
- There are no wear, eat, wield options so items affect stats immediately when picked up or dropped.
- No magic system has been implemented. No ranged weapons.
- There is no Field of View (since there is no virtual game board in Standalone Solar mode). It could be done, but it would not be very playable (since the monster's location would have to be sent over Morse). However, I will implement sounds and smells in the future to indicate if a monster is nearby.
- No monster AI has been implemented yet. They simply seek and attack. This works surprisingly well in a "blind" environment. Most of the time they are stuck behind walls and corners until you get near them. When they do attack, the direction of attack is noted (otherwise you would not know where they are).
- Monsters may drop loot when killed. Some loot is "higher level" loot that you might find on the lower dungeon levels.
Additional Notes on the Design
Lastly, I'd like to mention that this proof-of-concept engine only. I have built different prototypes that I have not revealed publicly at this time. Most of my time on this project has not been spent programming the ATtiny 1634, but has been spent in PCB design, game world/lore design, and creating interfaces and testing environments. A roguelike engine is just an engine, it is not a game. What makes games fun are the non-technical elements! If I can't get that right, then this is just a technical exercise. But my goal from the very start was to create a game world.
Improving the Circuit
Piezo diaphrams are great for 1-bit creature sound effects. They can also be used as input devices and can sense vibration. For the demo, I sent a half-wave signal to the piezo device, for simplicity, as it was loud enough for the demo. But you can easily increase the volume by connecting the piezo diaphram to two IO lines and alternate their states, driving it using full-wave and doubling the voltage. However, you have to watch out for high voltages generated by the piezo itself, if it is knocked or hit, for example. The ATTiny 1634 has built-in protection diodes which help somewhat, but they won't protect the rest of your circuit, so you have to account for this.
- The ATTiny 1634 has hardware PWM and timers that can be used for output modulation. I also made use of the hardware timer for my timing routines (Morse, etc).
- I personally chose not to use capacitive touch since the chip cannot be awoken from its deep sleep mode using it. A simple contact switch, however, can trigger the chip to wake up and process a turn, then power back down. The capacitive touch library is closed source, requires capacitors, is difficult to get working with the GCC compiler, and uses precious space that could be used for the game.
- The ISP lines can also be used for IO, but this is a little tricky if you already have it connected to a programmer (such as Avrdude linuxgpio on the Raspberry Pi). The clock line (SCK) is easy to use, and I used it for the Morse keyer, but the RESET line requires that you set a fuse that prevents you from programming it from the Raspberry Pi (as it then requires 12 volts to program). It is easy to build a simple circuit to do this if you really need to use the RESET line.
- Most of the ATTiny 1634 lines go into a floating mode during programming. This is ideal if you have circuitry attached that you don't want to activate during programming. However, if you reuse the ISP lines for IO (such as the MISO, MOSI lines), you have to make sure that parasitic effects don't interfere with its programming. Long wires can act like resistors, flat wires can act like capacitors, curled wires can act like inductors, etc.
- In the United States, microcontroller circuits are subject to FCC Part 15b and are not allowed to be sold unless they are certified that they don't emit unintentional RF emissions. The ATTiny 1634 typically runs at frequencies and power levels that make it subject to these regulations, even though those power levels can be extremely small.