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!