Player Controls in a Zero-Player Game
The part that interested me right away was one of the trivial details. Obviously you pretty much have to build some kind of programmable computer, and attach to a display that might look more or less like Brice Due’s metacells/movie pixels. But what about the game controls?
Life simulations aren't really very compatible with playing Tetris. Seems like it will be necessary to set up some kind of "control panel" area, where a user can make edits to the Life universe while the Tetris computer is running its program, and the effect of those edits will be to send messages to the computer saying "left", "right", "turn", or "drop".
A Little Problem of Scale with a Nice Solution
The problem is that if we’re zoomed out far enough to see the metapixel Tetris display, we’re out way too far to make precision edits to individual cells.
Luckily that’s easily solvable by a seldom-used feature of Golly, which should work reasonably well with a Tetris-computer pattern. Set up two tiled layers. One is zoomed in to the control panel, and the other displays the whole pattern.
This gets us into a series of fascinating/silly [delete whichever is inapplicable] design questions.
Let's assume that Golly is used to run the Tetris computer and play the game, and that Golly can run the computer at maybe a few frames per second (which may or may not turn out to be true in practice). If that all works, what will be the most user-friendly way to do on-the-fly editing in Golly to send messages to the computer?
It needs to be the simplest possible interaction with the Life universe, preferably something that can be done easily while the pattern is running at a suitably high step rate -- and preferably something where being one cell off on a click won't crash the computer.
A First Attempt
The challenge specifies that "Your program will receive input by manually changing the state of the automaton at a specific generation to represent an interrupt"... It seems clear that you wouldn’t want to have to try to draw a glider in the exact right place to tell the computer "Drop" or whatever.
Luckily there are various possible sensor mechanisms that require either adding just one cell in a specific location, or subtracting one cell from a stable object
Here's an example using a single-cell addition: to trigger the interrupt, you have to fill in the cell in the center of a ship to launch a Cordership.
To try it out, just run the script and then hit Enter to start the pattern running. Turn on the ship's central cell in the control panel (right half of the screen) and the Cordership seed in the upper left corner will be triggered. (Fill in the ship again and you can shoot down the Cordership, and maybe cause various explosions, but you won't be able to see that very well, and that's not the point here anyway.)
CordershipLauncher.py:
(Lua version available on request)
Code: Select all
import golly as g
# if there's more than one existing layer, the layout won't work correctly
if g.numlayers() > 1:
answer = g.getstring("All existing layers will be removed. OK?")
if answer[:1].lower() == "n": g.exit()
oldswitch = g.setoption("switchlayers", 0) # don't allow user to switch layers
oldtile = g.setoption("tilelayers", 1)
while g.numlayers() > 1: g.dellayer()
def init():
g.setrule("B3/S23")
g.new("Sample One-Pixel Control Panel")
g.putcells(g.parse("""121bo$121b3o$124bo$123b2o6bo$130bobo$131bo2$125b2o$118b2o5b2o$118b2o3$
135b2o$135b2o2$120b2o$119bobo$119bo$118b2o15$130b2o$129bobo$123b2o4bo$
121bo2bo2b2ob4o$121b2obobobobo2bo$124bobobobo$124bobob2o$125bo2$138b2o
$129b2o7bo$129b2o5bobo$136b2o2$108bo$81bo26b3o$81b3o27bo$84bo25b2o$83b
2o$126b2o$102b2o23bo$75b2o25bo21b3o$75bo23b2obo21bo$72b2obo23bo2b3o4b
2o$72bo2b3o4b2o16b2o3bo3b2o$73b2o3bo3b2o18b4o$75b4o23bo15b2o$75bo15b2o
10b3o12bobo$76b3o12bobo12bo13bo$79bo13bo7b5o14bobo$74b5o14b2o6bo17b2ob
3o$74bo28bo21bo$77b2o23b2o20bobo$77b2o45bobo$125bo$101b2o$101b2o3$110b
o29b2o$109bobo28b2o$110bo3$120b2o$106b2o11bobo77bo8b2o$106b2o11bo79b3o
6b2o$111b2o5b2o7b2o73bo$111bobo13b2o72b2o13b2o$113bo102bo$98b2o13b2o
20b2obo75bobo$99bo35b2ob3o37b2o34b2o$96b3o6b2o34bo36bo$96bo8b2o28b2ob
3o39bo26b2o$134bo2b2o21b2o14b5o26b2o$133bobo25bo13bo$132bobob2obo21bob
o12b3o$115b2o16bo2bob2o22b2o15bo33bo$116bo19bo39b4o32bobo$116bobo16b2o
34b2o3bo3b2o31bo$117b2o13bobo2b2o32b2o4b3o2bo$132b2o2bo2bo39bob2o$137b
2o40bo24b2o$178b2o24b2o2$111b2o$110bo2bo56b2o$111b2obo55bo$114bo56b3o$
114b2o49bob2o4bo$165b2obo2$118b2o15b2o26b5o$52bo65b2o15bobo3b2o20bo4bo
2b2o$52b3o82bo4bo23bo2bo2bo$55bo81b2o3bobo21b2obobo$54b2o87b2o18bo5bob
2o$162bobo4bo$162bo2bo2b2o$46b2o115b2o$46bo85b2o$43b2obo84bobo$43bo2b
3o4b2o74b3obobo$44b2o3bo3b2o73bo5b2o$46b4o78b2o$46bo15b2o88b2o$47b3o
12bobo87b2o$50bo13bo$45b5o14b2o$45bo$47bo$46b2o7$143bo$142bobo$54bo87b
obo$52b3o88bo$51bo$44bo6b2o67b2o$43bobo74bobo$44bo76b2o2$49b2o$49b2o5b
2o$56b2o3$39b2o$39b2o2$54b2o$54bobo$56bo$56b2o3$98b2o$98b2o3$107b2o$
106bobo$106b2o$110b2o$110bobo13b3o$112bo15bo$112b2o13bo2$105b2o$105b2o
2$8b2o8bo$8b2o6b3o$15bo20b2o$2o13b2o12b2o5b2o$bo27b2o$bobo$2b2o98b2o$
31b2o17b2o8b2o40bo$9b2o20b2o17bo9b2o41b3o$9b2o14b2o21bobo54bo$25b2o21b
2o38b2o$66b2o20bo$4bo61b2o18bobo$3bobo56b2o22b2o$4bo57b2o$71b2o$40b3o
28b2o$12b2o28bo$12b2o27bo25b2o11b2o$67b2o11b2o6b2o$88b2o3$76bo$75bobo$
76bo6b2o$26b2o55bo$25bo2bo55b3o$25bo2bo57bo$25bo2bo$25bo2bo$24bo$21bob
2o$20bo16bo$20bo3bo7bo3bo$21b4o6bo4bo3b2o$31bo2bo2bo3bo$28bo3b4o3bobo$
26b2obo4b2o3b2o29bo$26b2obo38bobo$21b2o2bo2bo40b2o$21b2o3b2o$26b2o11b
2o21b2o$27b2o9bobo21b2o21b2o$27b2o9bo17b2o27bo$27b2o8b2o17b2o25bobo$
83b2o2$58b2o8b2o$51b2o5b2o8b2o$51b2o2$85b2o$78b2o5b2o$78b2o2$73bo$72bo
bo$73bo6b2o$80bo$81b3o$83bo!"""))
g.fit()
g.setname("Sample Display")
g.putcells(g.parse("""47b2o$47b2o$56b2o$56b2o3$59b2o$55b2o2b2o$55b2o4$47b2o$47b2o$58b2o$41bo
16bobo$40bobo11b2o3bobo$41b2o10bobo4bo$54bo$67b2o$41bo25bobo$40bobo25b
o$41b2o12$59b2o$59b2o3$34bo11bo$33bobo9bobo$34b2o9b2o3$34bo4b2o$33bobo
3bobo$34b2o4bo5$14bo4bo$13bobo2bobo$14b2o3b2o5$7b2o$2o5bobo$2o6bobo$9b
o9$22b2o8b2o$22bobo7b2o$24bo$24b2o!""",-113, -115))
g.fit()
g.clone()
g.setname("Control Panel")
g.setpos("121","137")
g.setmag(5)
init()
g.setbase(8)
g.setstep(4)
Probably Remove Instead of Adding
There might be an argument for having the interrupt trigger be a removal rather than an addition, so that it’s not possible to accidentally toggle an ON cell OFF when you're trying to turn a cell ON.
That could certainly happen with this pattern. As it happens, you can click anywhere in the northwest half of the ship, and nothing will happen, the deleted cell just gets magically restored. But a click on the southeast half of the ship blows up the whole control panel.
Seems like a blinker or beehive would be a good control-panel object, since any subtraction kills the whole object cleanly. Set Golly’s drawing color to 0, and there would be no possibility of accidentally toggling a cell ON. So --
Challenge #1
Design a control-panel setup along the above lines, with as little likelihood as possible of accidental clicks blowing everything up. Ideally there will be four objects in view in the control-panel half of the display, and nothing else visible except maybe "L", "R", "T"=turn, "D"=drop labels, or something along those lines. An easy change to an object sends out a single glider, on a different lane for each control.
The step size can perfectly well be much slower than the above example. Given the ridiculously large number of ticks that are going to be needed for each Tetris-computer cycle, it seems like pretty much any 2^N-tick period could be used to query the control panel area and cleanly rebuild the four objects. But in the spirit of the original challenge, if multiple solutions are constructed, a smaller bounding box will beat a bigger bounding box.
Smaller Hashlife-friendly Metacells
-- I have a Quest for Tetris Challenge #2 in mind. Will post something in a few days or weeks, depending on if Challenge #1 catches anyone's interest.