-- This is a tic-tac-toe thing I made in one day, on 2009-09-08 to try as a
-- part of Reddit's developer hiring round 3. I was stupid to try to do this in
-- one day, but at least it hopefully excuses me from the awful looks and
-- unoptimized code. I am proud to say that I think the AI is quite impressive,
-- though. I've never coded tic-tac-toe before.
--
-- I'll let you guys know if I start improving things so you don't think I
-- actually made some awesome code in one day.
--
-- Released into the public domain by Teran McKinney (sega01).
-- PS: If you're crazy enough to actually want to run this, you need WebLua 0.3
-- from http://go-beyond.org/weblua/
-- PPS: If you wish to find me, I'm sega01 on Reddit, or you can see my
-- website: http://go-beyond.org/
-- 2010-09-09 14:41 Update:
-- So the 486 survived the night quite well, but it wasn't well tested. Only
-- 1500 hits or so :-(. I did do one tiny optimization, just removing some
-- duplicity, and one small bug fix. I could barely find a spot where it would
-- run differently, but sure enough, ?s=4&t=2211012000000000 was it.
--
-- The nature of this code makes bugs really hard to spot, at least via using
-- it, rather than looking at the code. You could probably take out one
-- direction and it'd still run quite well, with how recursive the tests are.
--
-- And it looks like there's another: ?s=10&t=2000000001000000000000000000000000000000000000000000000000000000000000000000000000000000001000000002
--
-- Search for BUGFIX, or OPTIMIZATION, if you're curious.
-- 2010-09-09 16:11 Update:
-- So it looks like it is beatable, believe it or not. You have to start
-- yourself, which requires some URL tweaking. Start with:
-- http://foureightysix.go-beyond.org/?s=3&t=000000100
-- and do top right, bottom right, and then middle right, and you win.
--
-- Seems to only work on a 3x3 board. I might have to add an opposite corner
-- factor for that.
-- This is not fully commented, so beware.
-- Old ideas and concepts. You can ignore this bit.
-- i=slot on the board
-- s=square root of board size
-- Obviously only works with non-edge pieces, would need work. Not using anyways
--[[
function whichtoucheswhich(i,s)
print(i+1)
print(i-1)
print(i+s)
print(i-s)
print(i+s+1)
print(i+s-1)
print(i-s+1)
print(i-s-1)
end
--]]
-- To help with debugging.
--[[if not wl_send_headers then
wl_send_headers=function() print "You\'re on the console and I need to get working!" end
write=function(i) print(i) end
wl={get={}}
wl.get["s"]="3"
wl.get["t"]="210210210"
else--]]
wl_send_headers()
wl.get={}
wl_decode_get()
--end
--Board: 0 is blank, 1 is them, 2 is us.
--[[board={ 0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0}
--]]
--s=10
--ss=s^2
-- To the real stuff, and yes, I know this is ugly.
-- This is the ?s= bit. It's the board size in one dimension.
if wl.get["s"] then
-- Ugly, rushed coding.
if (string.len(wl.get["s"])) <= 2 and (wl.get["s"]:match("^%d+$")) then
s=tonumber(wl.get["s"])
if (s > 10) then
return
end
ss=s^2
-- t for tic-tac-toe?
if wl.get["t"] then
board={}
if (string.len(wl.get["t"])) == ss and (wl.get["t"]:match("^%d+$")) then
--Thanks to someone on Stackoverflow, via google. I can't believe I haven't asked on IRC for help tonight. --MODIFY if changes <- Yeah, it changed
for c in wl.get["t"]:gmatch"." do
local n=tonumber(c)
if (n >= 0 and n <= 2) then
table.insert(board,n)
else
return --MODIFY is return the right thing to use?
end
end
else
return
end
end
end
end
--for k,v in pairs(board) do write(k.." "..v) end --MODIFY
function resetTheScores()
freedom=true
friendlyScore=0
enemyScore=0
end
function checkTheSpot()
-- These may need tweaking.
if (board[spotInQuestion] == 1) then
enemyScore=enemyScore+2
freedom=false
elseif (board[spotInQuestion] == 2) then
friendlyScore=friendlyScore+1
end
end
function totalTheScores()
if freedom then
--Freedom modifier
score=score+1+friendlyScore
else
if (friendlyScore == 0) then
--Enemy modifier, if not blocked.
score=score+enemyScore
end
end
end
-- Main AI loop
function doTheAI()
weightedPossibilities={}
for i=1,ss do
local spot=i
score=0
-- Lua seriously needs a continue statement :-/. Ugly hack:
if (board[i] == 0) then
-- Order of directions doesn't really matter, so let's make it easy on
-- ourselves and start horizontally.
--
-- Sadly, we have to scan in *all* directions, else we get issues, or so
-- I think.
resetTheScores()
local startScan=spot-((spot-1)%s)
for i=startScan,startScan+s-1 do
spotInQuestion=i
checkTheSpot()
end
totalTheScores()
-- Now that wasn't so bad, was it?
-- Vertically
resetTheScores()
local startScan=spot%s
if (startScan == 0) then
startScan=s
end
-- I think these are symptoms of this being coded in one day.
spotInQuestion=startScan
checkTheSpot()
for i=1,s-1 do
spotInQuestion=startScan+(i*s)
checkTheSpot()
end
totalTheScores()
-- Diagonally
-- I don't know of a good way to do this, so I'd prefer to avoid it :-/.
-- Left to right (top to bottom)
-- s+2 is the minimum second multiple for a 3x3 or larger board.
leftDiagonal=false
local leftDiagonalMultiples={}
--OPTIMIZATION: We don't need local leftDiagonalMultiples={1 or s} in
-- these, with the test below. Still not good, but it's a slight
-- improvement, nonetheless.
for i=2,s do
local multiple=1+(s+1)*(i-1)
if (multiple == spot) then
leftDiagonal=true
else
leftDiagonalMultiples[i]=multiple
end
end
-- Right to left (top to bottom)
-- s+2 is the minimum second multiple for a 3x3 or larger board.
rightDiagonal=false
local rightDiagonalMultiples={}
for i=2,s do
local multiple=s+(s-1)*(i-1)
if (multiple == spot) then
rightDiagonal=true
else
rightDiagonalMultiples[i]=multiple
--BUGFIX: was rightDiagonalMultiples[i-1]=multiple
end
end
-- Covering all bases. I'm sure we could combine this with ^ if my
-- brain was working right now.
if (spot == 1) then
leftDiagonal=true
elseif (spot == s) then
rightDiagonal=true
end
if leftDiagonal then
resetTheScores()
for i=1,s do
if not (leftDiagonalMultiples[i] == spot) then
spotInQuestion=leftDiagonalMultiples[i]
checkTheSpot()
end
end
totalTheScores()
end
if rightDiagonal then
resetTheScores()
for i=1,s do
if not (rightDiagonalMultiples[i] == spot) then
spotInQuestion=rightDiagonalMultiples[i]
checkTheSpot()
end
end
totalTheScores()
end
table.insert(weightedPossibilities,{spot,score})
end -- If statement
end
table.sort(weightedPossibilities, function(lhs, rhs) return lhs[2] > rhs[2] end)
-- Might want to random up the top ones if they are equal, eventually.
end
function printTheBoard()
local textBoard=""
for k,v in pairs(board) do
-- Basically the w.get["t"], but with my AI's improvements :-).
-- This must be slow.
textBoard=textBoard..v
end
-- Yes, we have to do this twice :-/.
for k,v in pairs(board) do
if (v == 0) then
-- Huge thanks to Dylan on #lua for this.
local chosenBoard=textBoard:sub(1,k-1).."1"..textBoard:sub(k+1)
write ("0")
elseif (v == 2) then
write "2"
else
write "1"
end
if (k%s == 0) then
write"
"
end
end
end
-- Ugly, ugly, ugly
write [[
So it looks like I've made the world's ugliest tic-tac-toe. You'll have to pretend that 2 is the computer and 1 is you. 0 is a free spot that you can click on, if you haven't noticed. If you make the final move and it clears up the board, you'll get a blank page; I may fix that later. And here's the source code, if you're curious.
3x3Total hits since 2009-09-09T06:32Z: ]]..wl.hits..[[
]])