Scite Tic Tac Toe


An improved tic tac toe game. This script acts like a self-contained Lua "application" in SciTE; it opens a new buffer to play the game and does not affect other buffers. It hooks to handlers in a well-behaved manner, and is compatible with extman (SciteExtMan). Finally, it demonstrates a simple application that interacts by having the user double-click on "buttons" or press certain keys. The buffer window is marked read-only so that display refreshes can be better controlled. Coded and tested on SciTE 1.71. Now obsolete old version can be found [here].

Note: If you use proportional fonts, grab SciteMakeMonospace and then add MakeMonospace() at the end of the initialization function, TicTacToe().


Sample output:

SciTE Tic Tac Toe
-----------------
Status: No more moves to make, draw
+---+---+---+
| O | O | X | SciTE: O
+---+---+---+ Human: X
| X | X | O |
+---+---+---+
| O | X | X |
+---+---+---+
+----------+----------+
| New Game | Autoplay |
+----------+----------+
For best results, please use a monospace font (press Ctrl+F11 for
monospace font mode.) Double-click boxes to play, or press keys
1 through 9 to make a move. Key positions correspond to the usual
keypad arrangement. For a new game, you can press the N key or
double-click the "NewGame" box. To autoplay, you can press [Space]
or double-click the "Autoplay" box.


-----------------------------------------------------------------------
-- Tic Tac Toe for SciTE Version 2.2
-- Kein-Hong Man <khman@users.sf.net> 20060905
-- This program is hereby placed into PUBLIC DOMAIN
-----------------------------------------------------------------------
-- This script can be installed to a shortcut using properties:
-- command.name.8.*=Tic Tac Toe
-- command.subsystem.8.*=3
-- command.8.*=TicTacToe
-- command.save.before.8.*=2
-- If you use extman, you can do it in Lua like this:
-- scite_Command('Tic Tac Toe|TicTacToe|Ctrl+8')
-----------------------------------------------------------------------
-- * This is a demonstration of a (hopefully) well-behaved Lua-based
-- "application" in SciTE that hooks to handlers, is compatible
-- with extman, and uses mouse doubleclicks as the user interface.
-- * TicTacToe is the main function. It opens a new buffer and the
-- game is played by double-clicking on boxed areas, or by pressing
-- the number keys 1 through 9.
-- * Note that the computer player and the player human are fixed
-- at 'O' and 'X', respectively.
-- * If you play using digit keys, do not change buffer from read-only.
-----------------------------------------------------------------------
------------------------------------------------------------------------
-- constants and primitives
------------------------------------------------------------------------
local string = string
local O, X = 1, 10
local STR = { -- various strings
 Sig = "SciTE_TicTacToe2",
 Prompt = ">SciTE_TicTacToe2: ",
 Horiz = "+---+---+---+",
 HorizRegex = "^%+%-%-%-%+%-%-%-%+%-%-%-%+",
 TrioRegex = "^|%s*(%w*)%s*|%s*(%w*)%s*|%s*(%w*)%s*|",
 BoardPat = "12121",
 ToolBar = [[
+----------+----------+
| New Game | Autoplay |
+----------+----------+
]],
}
local MSG = { -- game messages
 Title = "SciTE Tic Tac Toe",
 Conflict = "There is an OnDoubleClick conflict, please use extman",
 BadBoard = "Board not recognized, computer cannot continue",
 BadPieces = "Something strange on the board, cannot continue",
 IllegalMove = "Illegal move",
 Borked = "Evaluator borked",
 Key1 = "SciTE: O",
 Key2 = "Human: X",
 Start1 = "Human starts",
 Start2 = "Computer starts",
 AlreadyEnd = "Game has already ended",
 NoMoves = "No more moves to make, draw",
 HumanWin = "Human wins this round",
 ComputerWin = "Computer wins this round",
 Help = [[
For best results, please use a monospace font (press Ctrl+F11 for
monospace font mode.) Double-click boxes to play, or press keys
1 through 9 to make a move. Key positions correspond to the usual
keypad arrangement. For a new game, you can press the N key or
double-click the "NewGame" box. To autoplay, you can press [Space]
or double-click the "Autoplay" box.
]]
}
local BUT = { -- fixed button set
 [5] = {{2,4,"7"},{6,8,"8"},{10,12,"9"},},
 [7] = {{2,4,"4"},{6,8,"5"},{10,12,"6"},},
 [9] = {{2,4,"1"},{6,8,"2"},{10,12,"3"},},
 [13] = {{2,11,"NewGame"},{13,22,"Autoplay"},},
}
local function Error(msg) _ALERT(STR.Prompt..msg) end -- error msg
------------------------------------------------------------------------
-- simple check for extman, partially emulate if okay to do so
------------------------------------------------------------------------
if (OnDoubleClick or OnChar) and not scite_Command then
 Error(MSG.Conflict)
else
 -- simple way to add a handler only, can't remove like extman does
 if not scite_OnDoubleClick then
 local _OnDC
 scite_OnDoubleClick = function(f) _OnDC = f end
 OnDoubleClick = function(c) if _OnDC then return _OnDC(c) end end
 end
 if not scite_OnChar then
 local _OnCh
 scite_OnChar = function(f) _OnCh = f end
 OnChar = function(c) if _OnCh then return _OnCh(c) end end
 end
end
------------------------------------------------------------------------
-- tic tac toe functions (implicitly uses O as computer, X as human)
------------------------------------------------------------------------
local function CheckForWin(t, player) -- see who wins
 local wins = player * 3
 if t[1]+t[5]+t[9] == wins or
 t[3]+t[5]+t[7] == wins then return true end
 for i = 1,3 do
 local j = i * 3
 if t[i]+t[i+3]+t[i+6] == wins or
 t[j-2]+t[j-1]+t[j] == wins then return true end
 end
 return false
end
local function AnyWin(t) -- see if somebody won
 return CheckForWin(t, X) or CheckForWin(t, O)
end
local function MoveCount(t) -- counts the number of moves
 local n = 0
 for i = 1, 9 do if t[i] == O or t[i] == X then n = n + 1 end end
 return n
end
-- not-bad movement evaluator (minimax can be easily made perfect)
-- (1) picks the obvious
-- (2) blocks the obvious
-- (3) otherwise pick randomly
local function MoveSimple(t, player)
 local mv, opponent
 if player == X then opponent = O else opponent = X end
 for i = 1, 9 do -- (1)
 if t[i] == 0 then
 t[i] = player
 if CheckForWin(t, player) then t[i] = player return end
 t[i] = 0
 end
 end
 for i = 1, 9 do -- (2)
 if t[i] == 0 then
 t[i] = opponent
 if CheckForWin(t, opponent) then t[i] = player return end
 t[i] = 0
 end
 end
 if MoveCount(t) == 9 then Error(MSG.Borked) return end
 repeat mv = math.random(1, 9) until t[mv] == 0 -- (3)
 t[mv] = player
end
local Evaluate = MoveSimple -- select evaluator
local function ComputerStart(t) -- computer may start
 if math.random(1, 10) > 5 then
 t[math.random(1, 9)] = O
 return MSG.Start2
 end
 return MSG.Start1
end
------------------------------------------------------------------------
-- redraws the screen (complete redraw for simplicity)
------------------------------------------------------------------------
local function DrawBoard(t)
 if not t then t = {} end
 local p = function(i)
 if not t[i] then return " "
 elseif t[i] == O then return " O "
 elseif t[i] == X then return " X "
 else return " "
 end
 end
 editor:AddText(
 STR.Horiz.."\n"..
 "|"..p(7).."|"..p(8).."|"..p(9).."| "..MSG.Key1.."\n"..
 STR.Horiz.." "..MSG.Key2.."\n"..
 "|"..p(4).."|"..p(5).."|"..p(6).."|\n"..
 STR.Horiz.."\n"..
 "|"..p(1).."|"..p(2).."|"..p(3).."|\n"..
 STR.Horiz.."\n"
 )
end
local function Refresh(t, msg)
 local function Underline(s) return string.rep("-", string.len(s)) end
 msg = msg or ""
 editor.ReadOnly = false
 editor:ClearAll()
 editor:AddText(MSG.Title.."\n"..Underline(MSG.Title).."\n")
 editor:AddText("Status: "..msg.."\n\n")
 DrawBoard(t)
 editor:AddText("\n"..STR.ToolBar.."\n"..MSG.Help)
 editor.ReadOnly = true
end
------------------------------------------------------------------------
-- main routine, processes double-clicks
------------------------------------------------------------------------
local function TicTacClick(ch)
 local BEG = 4 -- first line of board
 if not buffer[STR.Sig] then return end-- verify buffer signature
 --------------------------------------------------------------------
 -- check appearance of board
 --------------------------------------------------------------------
 local tln = editor:GetLine(0) or "" -- verify title signature
 if string.sub(tln, 1, string.len(MSG.Title)) ~= MSG.Title then
 Error(MSG.BadBoard) return true
 end
 local LineType = function(ln) -- classify TTT line
 local text = editor:GetLine(ln)
 if text == nil then return 0
 elseif string.find(text, STR.HorizRegex) then return 1
 elseif string.find(text, STR.TrioRegex) then return 2
 else return 0 end
 end
 local id = "" -- verify board pattern
 for i = BEG, BEG+4 do id = id..tostring(LineType(i)) end
 if id ~= STR.BoardPat then Error(MSG.BadBoard) return true end
 --------------------------------------------------------------------
 -- extract board information
 --------------------------------------------------------------------
 local IsXOrO = function(c) -- classify pieces
 if c == nil or c == "" then return 0
 elseif string.upper(c) == "O" then return O
 elseif string.upper(c) == "X" then return X
 else return -1
 end
 end
 local GetData = function(ln) -- extract data from a line
 local text = editor:GetLine(ln)
 local _, _, c1, c2, c3 = string.find(text, STR.TrioRegex)
 return IsXOrO(c1), IsXOrO(c2), IsXOrO(c3)
 end
 local t = {} -- convert pieces to data
 t[7], t[8], t[9] = GetData(BEG+1)
 t[4], t[5], t[6] = GetData(BEG+3)
 t[1], t[2], t[3] = GetData(BEG+5)
 local delta = 0
 for i = 1,9 do -- verify board contents
 if t[i] == -1 then Error(MSG.BadPieces) return true
 elseif t[i] == O then delta = delta - 1
 elseif t[i] == X then delta = delta + 1
 end
 end
 if math.abs(delta) > 1 then Error(MSG.BadPieces) return true end
 --------------------------------------------------------------------
 -- decode user-clicked position or keypresses
 --------------------------------------------------------------------
 if ch == "click" then -- mouse double-click event
 local pos = editor.CurrentPos
 local ln = editor:LineFromPosition(pos)
 local col = editor.Column[pos]
 local bln = editor:GetLine(ln) or ""
 tln, id = BUT[ln], nil -- check for button click
 if not tln then return end
 for i,b in ipairs(tln) do
 if col >= b[1] and col <= b[2] then id = b[3] end
 end
 if not id then return true end -- nothing happen if no button
 else -- keypress event
 id = string.find("123456789 nN", ch, 1, 1)
 if not id then return true end
 if id == 10 then id = "Autoplay"
 elseif id >= 11 then id = "NewGame"
 end
 end
 --------------------------------------------------------------------
 -- interactive game logic, takes id and t as state inputs
 --------------------------------------------------------------------
 local msg
 if id == "NewGame" then -- new game
 t = {}
 msg = ComputerStart(t)
 elseif AnyWin(t) then msg = MSG.AlreadyEnd -- already won
 elseif MoveCount(t) == 9 then msg = MSG.NoMoves -- draw
 else
 if id == "Autoplay" then -- auto play
 Evaluate(t, X)
 else -- human play
 id = id+0
 if t[id] ~= 0 then Refresh(t, MSG.IllegalMove) return true end
 t[id] = X
 end
 if CheckForWin(t, X) then msg = MSG.HumanWin -- human moves
 elseif MoveCount(t) == 9 then msg = MSG.NoMoves
 else
 Evaluate(t, O) -- computer moves
 if CheckForWin(t, O) then msg = MSG.ComputerWin
 elseif MoveCount(t) == 9 then msg = MSG.NoMoves
 end
 end
 end
 Refresh(t, msg) -- redraw screen
 return true
end
-- handle incoming events
local function HandleClick() return TicTacClick("click") end
local function HandleChar(c) return TicTacClick(c) end
------------------------------------------------------------------------
-- game initialization (opens a new file and set up handlers)
------------------------------------------------------------------------
function TicTacToe()
 scite_OnDoubleClick(HandleClick)
 scite_OnChar(HandleChar)
 scite.Open("")
 buffer[STR.Sig] = true;
 local t = {}
 Refresh(t, ComputerStart(t))
end
-- end of script

RecentChanges · preferences
edit · history
Last edited November 15, 2012 1:50 am GMT (diff)

AltStyle によって変換されたページ (->オリジナル) /