Dec. 24, 2015 - cheapie
This article assumes you have the mesecons, digilines, rgblightstone, and digibutton mods installed. It may still be possible to build this without some or all of these mods, but replacing them with fancy wiring is an exercise left to the reader.
That aside, this project is actually not too complex, and it is quite possible to complete it with only minimal prior experience. The reason for the high difficulty rating is more because the code on the Luacontroller (there's only one!) is rather complex, and it is strongly recommended to understand what is going on in there in order to get the most from this article.
So, what is this thing? Well, if you're here, odds are you've already seen some variant of Uberi's Tic-Tac-Toe machine. It's rather impressive, but I found that it's not only quite large, but the 31 Luacontrollers required made me think that there must be a better way to do it. The two main obstacles, however, were that every pixel on the display took a port on a Luacontroller (81 in total), and so did every button (10 of those).
To overcome these limitations, I figured that the best course of action would be to make digilines-connectable buttons and lightstone. That's exactly what digibutton and rgblightstone do. After making those, I took the opportunity to rewrite the Luacontroller program from scratch, adding many new features in the process such as crossing out the winning symbols and making the colors configurable.
Start with the backplane for the display. Don't let the name scare you - it's just an 11x11 square of digimese.
Now place a block of RGB Lightstone in the top left corner.
This one should be on channel "ttt", X address 1, and Y address 1.
Next, extend this all the way to the right, incrementing the X address each time. The far right one should be at X address 11.
Now place a row below it, and punch each one in turn. They should auto-fill with the same values as the one above them, but with the next Y address. Continue doing this one row at a time until the front of the digimese is covered. The bottom-right corner will have X and Y addresses of 11 if you did it right.
There's not really much to this step. Simply place 4 RGB Lightstones, and set them to the channels "xwin" (X wins), "owin" (O wins), "xturn" (X's turn), and "oturn" (O's turn). They should all be connected via digilines or similar to the display's digimese. While optional, a sign is recommended to remind you what they each do.
Moves will be entered on the keypad. Place 9 digilines buttons in a 3x3 grid on some digimese. Set the channel on all of them to "keypad", and the message to the number in the chart below corresponding to where they are on the grid.
11 21 31 12 22 32 13 23 33
Off to the side (or right next to them) place another digilines button on the channel "reset". Any message works for this one. This button will be used to start a new game.
Connect the digimese behind the keypad, as well as the reset button, to the display with digilines.
This may very well be the simplest step of them all. First, place a Luacontroller anywhere along the digilines or digimese.
Then, program it with the following program:
--Tic-Tac-Toe Machine APU
--By Advanced Mesecons Devices, a division of Cheapie Systems
--WTFPL
if event.type == "program" then
--Settings
mem.ledoffcolor = "darkgray"
mem.xcolor = "darkgreen"
mem.ocolor = "darkred"
mem.winlinecolor = "darkgray"
mem.darkxcolor = "darkblue"
mem.darkocolor = "brown"
mem.bgcolor = "white"
mem.gridcolor = "gray"
mem.darkgridcolor = "black"
end
function checkwin(b)
if b[1][1] == "X" and b[2][1] == "X" and b[3][1] == "X" then return({win="X",dir=2,offset=2}) end
if b[1][2] == "X" and b[2][2] == "X" and b[3][2] == "X" then return({win="X",dir=2,offset=6}) end
if b[1][3] == "X" and b[2][3] == "X" and b[3][3] == "X" then return({win="X",dir=2,offset=10}) end
if b[1][1] == "X" and b[1][2] == "X" and b[1][3] == "X" then return({win="X",dir=1,offset=2}) end
if b[2][1] == "X" and b[2][2] == "X" and b[2][3] == "X" then return({win="X",dir=1,offset=6}) end
if b[3][1] == "X" and b[3][2] == "X" and b[3][3] == "X" then return({win="X",dir=1,offset=10}) end
if b[1][1] == "X" and b[2][2] == "X" and b[3][3] == "X" then return({win="X",dir=3,offset=nil}) end
if b[3][1] == "X" and b[2][2] == "X" and b[1][3] == "X" then return({win="X",dir=4,offset=nil}) end
if b[1][1] == "O" and b[2][1] == "O" and b[3][1] == "O" then return({win="O",dir=2,offset=2}) end
if b[1][2] == "O" and b[2][2] == "O" and b[3][2] == "O" then return({win="O",dir=2,offset=6}) end
if b[1][3] == "O" and b[2][3] == "O" and b[3][3] == "O" then return({win="O",dir=2,offset=10}) end
if b[1][1] == "O" and b[1][2] == "O" and b[1][3] == "O" then return({win="O",dir=1,offset=2}) end
if b[2][1] == "O" and b[2][2] == "O" and b[2][3] == "O" then return({win="O",dir=1,offset=6}) end
if b[3][1] == "O" and b[3][2] == "O" and b[3][3] == "O" then return({win="O",dir=1,offset=10}) end
if b[1][1] == "O" and b[2][2] == "O" and b[3][3] == "O" then return({win="O",dir=3,offset=nil}) end
if b[3][1] == "O" and b[2][2] == "O" and b[1][3] == "O" then return({win="O",dir=4,offset=nil}) end
return({win="none",dir=nil,offset=nil})
end
function coverpixel(y,x)
if mem.buffer[y][x] == mem.bgcolor then
mem.buffer[y][x] = mem.winlinecolor
elseif mem.buffer[y][x] == mem.xcolor then
mem.buffer[y][x] = mem.darkxcolor
elseif mem.buffer[y][x] == mem.ocolor then
mem.buffer[y][x] = mem.darkocolor
elseif mem.buffer[y][x] == mem.gridcolor then
mem.buffer[y][x] = mem.darkgridcolor
end
end
function drawline(win)
if win.dir==1 then
coverpixel(win.offset,1)
coverpixel(win.offset,2)
coverpixel(win.offset,3)
coverpixel(win.offset,4)
coverpixel(win.offset,5)
coverpixel(win.offset,6)
coverpixel(win.offset,7)
coverpixel(win.offset,8)
coverpixel(win.offset,9)
coverpixel(win.offset,10)
coverpixel(win.offset,11)
elseif win.dir==2 then
coverpixel(1,win.offset)
coverpixel(2,win.offset)
coverpixel(3,win.offset)
coverpixel(4,win.offset)
coverpixel(5,win.offset)
coverpixel(6,win.offset)
coverpixel(7,win.offset)
coverpixel(8,win.offset)
coverpixel(9,win.offset)
coverpixel(10,win.offset)
coverpixel(11,win.offset)
elseif win.dir==3 then
coverpixel(1,1)
coverpixel(2,2)
coverpixel(3,3)
coverpixel(4,4)
coverpixel(5,5)
coverpixel(6,6)
coverpixel(7,7)
coverpixel(8,8)
coverpixel(9,9)
coverpixel(10,10)
coverpixel(11,11)
elseif win.dir==4 then
coverpixel(1,11)
coverpixel(2,10)
coverpixel(3,9)
coverpixel(4,8)
coverpixel(5,7)
coverpixel(6,6)
coverpixel(7,5)
coverpixel(8,4)
coverpixel(9,3)
coverpixel(10,2)
coverpixel(11,1)
end
digiline_send("ttt",mem.buffer)
end
function rendersymbol(sym)
if sym.val == "X" then
mem.buffer[sym.yp][sym.xp] = mem.xcolor
mem.buffer[sym.yp][sym.xp+1] = mem.bgcolor
mem.buffer[sym.yp][sym.xp+2] = mem.xcolor
mem.buffer[sym.yp+1][sym.xp] = mem.bgcolor
mem.buffer[sym.yp+1][sym.xp+1] = mem.xcolor
mem.buffer[sym.yp+1][sym.xp+2] = mem.bgcolor
mem.buffer[sym.yp+2][sym.xp] = mem.xcolor
mem.buffer[sym.yp+2][sym.xp+1] = mem.bgcolor
mem.buffer[sym.yp+2][sym.xp+2] = mem.xcolor
elseif sym.val == "O" then
mem.buffer[sym.yp][sym.xp] = mem.bgcolor
mem.buffer[sym.yp][sym.xp+1] = mem.ocolor
mem.buffer[sym.yp][sym.xp+2] = mem.bgcolor
mem.buffer[sym.yp+1][sym.xp] = mem.ocolor
mem.buffer[sym.yp+1][sym.xp+1] = mem.bgcolor
mem.buffer[sym.yp+1][sym.xp+2] = mem.ocolor
mem.buffer[sym.yp+2][sym.xp] = mem.bgcolor
mem.buffer[sym.yp+2][sym.xp+1] = mem.ocolor
mem.buffer[sym.yp+2][sym.xp+2] = mem.bgcolor
else
mem.buffer[sym.yp][sym.xp] = mem.bgcolor
mem.buffer[sym.yp][sym.xp+1] = mem.bgcolor
mem.buffer[sym.yp][sym.xp+2] = mem.bgcolor
mem.buffer[sym.yp+1][sym.xp] = mem.bgcolor
mem.buffer[sym.yp+1][sym.xp+1] = mem.bgcolor
mem.buffer[sym.yp+1][sym.xp+2] = mem.bgcolor
mem.buffer[sym.yp+2][sym.xp] = mem.bgcolor
mem.buffer[sym.yp+2][sym.xp+1] = mem.bgcolor
mem.buffer[sym.yp+2][sym.xp+2] = mem.bgcolor
end
digiline_send("ttt",mem.buffer)
end
if event.type=="program" or (event.type=="digiline" and event.channel=="reset") then
mem.board = {{"blank","blank","blank"},{"blank","blank","blank"},{"blank","blank","blank"}}
mem.win={win="none",dir=nil,offset=nil}
mem.turn=false
port.a=mem.turn
digiline_send("xwin",mem.ledoffcolor)
digiline_send("owin",mem.ledoffcolor)
digiline_send("xturn",mem.xcolor)
digiline_send("oturn",mem.ledoffcolor)
mem.buffer = {
{mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor},
{mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor},
{mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor},
{mem.gridcolor,mem.gridcolor,mem.gridcolor,mem.gridcolor,mem.gridcolor,mem.gridcolor,mem.gridcolor,mem.gridcolor,mem.gridcolor,mem.gridcolor,mem.gridcolor},
{mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor},
{mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor},
{mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor},
{mem.gridcolor,mem.gridcolor,mem.gridcolor,mem.gridcolor,mem.gridcolor,mem.gridcolor,mem.gridcolor,mem.gridcolor,mem.gridcolor,mem.gridcolor,mem.gridcolor},
{mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor},
{mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor},
{mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor,mem.gridcolor,mem.bgcolor,mem.bgcolor,mem.bgcolor}
}
digiline_send("ttt",mem.buffer)
end
if event.type=="digiline" and event.channel=="keypad" and mem.win.win == "none" then
x = tonumber(string.sub(event.msg,1,1))
y = tonumber(string.sub(event.msg,2,2))
if mem.board[y][x] == "blank" then
mem.board[y][x] = mem.turn and "O" or "X"
temptable = {}
temptable.xp = 1+((x-1)*4)
temptable.yp = 1+((y-1)*4)
temptable.val = mem.board[y][x]
rendersymbol(temptable)
mem.win = checkwin(mem.board)
if mem.win.win == "X" then
digiline_send("xwin",mem.xcolor)
drawline(mem.win)
elseif mem.win.win == "O" then
digiline_send("owin",mem.ocolor)
drawline(mem.win)
else
mem.turn = not mem.turn
if mem.turn then
digiline_send("oturn",mem.ocolor)
digiline_send("xturn",mem.ledoffcolor)
else
digiline_send("xturn",mem.xcolor)
digiline_send("oturn",mem.ledoffcolor)
end
end
end
end
Assuming you did everything right (let's hope so!) the machine should spring to life and be ready to play as soon as you press "Execute".
Now all that remains to be done is for the circuitry to be moved closer together (to make it more compact) and for a case to be made for it, and you're all done!