Skip to content

Instantly share code, notes, and snippets.

@AntonioCiolino
Created December 12, 2012 22:06
Show Gist options
  • Save AntonioCiolino/4272076 to your computer and use it in GitHub Desktop.
Save AntonioCiolino/4272076 to your computer and use it in GitHub Desktop.
TicTacToe - simple TicTacToe game
TicTacToe for Codea
--
-- Explode.lua
--
-- subset of the old school explode code form forums
NUMSTARS = 50
COLOURS = {
color(255, 0, 0, 125),
color(255, 227, 0, 125),
color(99, 255, 0, 125),
color(0, 140, 255, 125),
color(238, 0, 255, 125),
color(255, 156, 0, 125),
color(0, 255, 189, 125),
color(255, 0, 146, 125)
}
-- iparameter("Stars",0,1,1)
-- number of particles of each firework
iparameter("Firework_Parts",0,500,100)
iparameter("Life",0,100,30)
iparameter("Life_Variation",0,100,100)
parameter("Air_Resistance",0,1,0.1)
iparameter("PartSize",0,100,35)
iparameter("PartSize_Variation",0,100,50)
parameter("Velocity",0,50,50)
iparameter("Velocity_Variation",0,100,100)
iparameter("Color_Variation",0,100,50)
--watch("DeltaTime")
Explode = class()
function Explode:init()
self.fireworks={}
end
function Explode:draw()
-- draw fireworks
for k,fw in pairs(self.fireworks) do
if fw:isDead() then
self.fireworks[k] = nil
else
pushStyle()
fw:draw()
popStyle()
end
end
end
function Explode:createFirework(x,y,color)
--local pos = self:freePos(self.fireworks)
self.fireworks[#self.fireworks+1] = Firework(x,y,color)
end
function Explode:freePos(list)
return self:freePosRecursive(list,1)
end
function Explode:freePosRecursive(list,n)
if list[n] == nil then
return n
else
return self:freePosRecursive(list,n+1)
end
end
--
-- Firework.lua
--
Firework = class()
function Firework:init(x,y,colour)
-- you can accept and set parameters here
self.p = {}
self.numParticles = Firework_Parts
for i=1,self.numParticles do
local psize = genNumber(PartSize,PartSize_Variation)
local v = vec2(math.random(-100,100),math.random(-100,100))
v = v:normalize()
v = v * genNumber(Velocity,Velocity_Variation)
local c = color(genNumber(colour.r,Color_Variation),
genNumber(colour.g,Color_Variation),
genNumber(colour.b,Color_Variation),
colour.a
)
self.p[i] = Particle(x,
y,
psize,
genNumber(Life,Life_Variation),
c,
v)
--self.p[i].sprite = Sprite:GetImage()
end
end
function Firework:draw()
local resistance = 1/(Air_Resistance + 1)
local g = vec2(Gravity.x,Gravity.y)
for i=1,self.numParticles do
local p = self.p[i]
p.x = p.x + p.v.x
p.y = p.y + p.v.y
p.v = p.v + g
p.v = p.v * resistance
local size = math.random(PartSize) * (p.lifeLeft/p.life)
p.width = size
p.height = size
p:draw()
end
end
function Firework:isDead()
for i=1,self.numParticles do
p = self.p[i]
if p.lifeLeft ~= 0 and
(p.x>0 and p.x<WIDTH and p.y>0 and p.y<HEIGHT)then
return false
end
end
return true
end
--
-- FlashText.lua
--
FlashText = class()
-- used to display a score on the screen. builds ina delay for x secs.
function FlashText:init(params)
-- you can accept and set parameters here
self.time = params.time or 5 --seconds
self.text = params.text or "..." -- display text
self.pos = params.pos -- where in the world to show it.
self.fontsize = params.fontsize or 48
self.startTime = nil
self:start() -- auto start
end
function FlashText:start()
self.startTime = ElapsedTime
end
-- false means its done and should be purged.
function FlashText:active()
if self.startTime == nil then return false end
return ElapsedTime - self.startTime < self.time
end
function FlashText:draw()
if self.startTime == nil then return end
if ElapsedTime - self.startTime < self.time then
pushStyle()
font("MyriadPro-Bold")
fontSize(self.fontsize)
r=math.random(255)
g=math.random(255)
b=math.random(255)
fill(r,g,b,255)
text(self.text, self.pos.x, self.pos.y)
popStyle()
end
end
--
-- GameFunctions.lua
--
--this file contains all of the needed functions to drive the game
function SetDisplayMode()
debug = true --can be used to do logging, etc. set to false to supress msgs
production = false --for the "real deal"
-- release as this - don't do in Codea or else you can't get back to code!
if production == true then
displayMode(FULLSCREEN_NO_BUTTONS)
else
displayMode(STANDARD)
displayMode(FULLSCREEN)
end
end
function GameSetup()
--Helper- used to archive code - we need to improve saveCode.
if (archive==true) then
displayMode(STANDARD)
if saveCode ~= nil then
saveCode("TicTacToe")
end
end
--highscores = {} --pull these from a param or a network service
LoadAssets() --if there are assets to load, do it here.
explode = Explode() --set up for fireworks
message = FlashText({time=0, text="",pos = vec2(0,0)}) --if we have text to render/flash
end
--The game has started - gameplay begins.
function StartNewGame()
--any vars that require initialization ot start a new game (hearbeat)
--scores per player - HACK!
playerX = 0
playerO = 0
blank_board = {
{"","",""},
{"","",""},
{"","",""} }
offset = WIDTH - HEIGHT
end
--The game has ended - gameplay ends
function GameEnded()
--anything we need to do to clean up an existing game
end
--Load any assets needed. Currently, simple img loading, but this can be used to load
--audio files or 3D objects.
function LoadAssets()
--imgCircle = readImage("Documents:ExampleCircle")
--imgCross = readImage()
end
--if we decide to do http loading, we'll need a callback function...
--function LoadAssetCallback()
--end
--logs a message from the developer.
function log(msg)
if debug == false then
return
else
--handle yor messages appropirately.
--print(msg)
end
end
--this is in my generic utils library, added here so that this code can operate standalone
function clamp(x, min, max)
return math.max(min, math.min(max, x))
end
--
-- main.lua
--
-- 0_GameTemplate
function setup()
archive = true --false --we want to archive the code on each run - backup often!
SetDisplayMode()
--GAME STATE DRIVER -- CAN ABSTRACT IN THE FUTURE
-- define consts - these drive the game state
--Note that I could have abstracted these into the GameFunctions file, but I want
--to "see" the states.
GAMESTATE_TITLE = 1
GAMESTATE_PLAYING = 2
-- put any screen states you want in here.
GAMESTATE_SCREEN1 = 3
GAMESTATE_SCREEN2 = 4
GAMESTATE_ENDED = 5
--these are the screens (classes) that are called when the game performs it's heartbeat
--there should be one of these for each of the items above.
GAMESTATES = { ScreenTitle, ScreenGame, nil, nil, ScreenEnd }
--start at the default Title / Menu screen
--note that this will occur fast, so if you are defer loading, you'll have to
--be aware that all assets might not be loaded before the Title screen renders.
GAMESTATE = GAMESTATE_TITLE
--END OF GAMESTATE DRIVER
GameSetup() --sets up game parameters. Not the same as the game state driver.
ScreenTitle:init()
end
-- This function gets called once every frame
--this is the game's heartbeat.
function draw()
background(0, 0, 0, 255)
--state machine
GAMESTATES[GAMESTATE]:draw()
end
--any touches we get, forward on to the screen / class that needs them.
--note that this assumes that the default touch() is working, in the case of the
--Controllers library, touch() gets overridden and this becomes unused.
function touched(touch)
--state machine
GAMESTATES[GAMESTATE]:touched(touch)
end
--
-- Particle.lua
--
Particle = class()
Particle.DEFAULT_OPACITY = 125
Particle.DEFAULT_ANGLE = 0
Particle.DEFAULT_MASS = 1
function Particle:init(posx,posy,size,life,colour,
velocity,mass,angle,sprite)
-- position
self.x = posx
self.y = posy
self.ox = -5
self.oy = 0
-- size
self.width = size
self.height = size
-- color
if colour == nil then
self.color = color(255, 255, 255, 255)
else
self.color = colour
end
-- velocity
self.v = velocity
-- life
self.life = life
self.lifeLeft= life
-- sprite
self.sprite = sprite
-- mass
if mass == nil then
self.mass = DEFAULT_MASS
else
self.mass = mass
end
-- angle
if angle == nil then
self.angle = self.DEFAULT_ANGLE
else
self.angle = angle
end
end
function Particle:draw()
if self.lifeLeft > 0 then
self.lifeLeft = self.lifeLeft - 1
end
if self.lifeLeft ~= 0 then
if self.sprite == nil then
fill(self.color)
ellipse(self.x,self.y,self.width,self.height)
else
pushMatrix()
translate(self.x,self.y)
rotate(self.angle)
tint(self.color)
sprite(self.sprite,0,0,self.width,self.height)
popMatrix()
end
end
end
function genNumber(number,variation)
ret = variation*0.01*number
ret = number + math.random(-ret,ret)
return ret
end
--
-- ScreenEnd.lua
--
ScreenEnd = class()
--Call init() to set up ScreenEnd parameters. This is effectively a static class
function ScreenEnd:init()
end
function ScreenEnd:draw()
text("unused. remove?", WIDTH/2, HEIGHT/2)
end
function ScreenEnd:touched(touch)
if touch.state == ENDED then
GAMESTATE = GAMESTATE_TITLE
end
end
--
-- ScreenGame.lua
--
ScreenGame = class()
--Call init() to set up ScreenGame parameters. This is effectively a static class
function ScreenGame:init(params)
-- you can accept and set parameters here
self.x = params.x or 1
self.y = params.y or 1
self.drawSpeed = params.drawSpeed or 2 --secs
self.drawing = true --drawing the board.
self.player = "X"
self.board = params.board --
ScreenGame:SetupNewBoard()
end
function ScreenGame:draw()
self:DrawInitialBoard()
explode:draw()
message:draw()
if (message:active() == false and self.winner ~= nil) then
ScreenGame:SetupNewBoard()
end
textMode(CORNER)
text("Player: " .. self.player, 20, HEIGHT - 100)
text ("X: " .. playerX, 20, HEIGHT - 200)
text ("O: " .. playerO, 20, HEIGHT - 250)
--draw marks
for k,v in pairs(self.board) do
for key, item in pairs(v) do
text(item , 75+ key*225, 800-(k*225))
end
end
--AC: HACK!!! Check for end of moves in the game
for k,v in pairs(self.board) do
for key, item in pairs(v) do
if item == "" then return end
end
end
if self.winner == nil then
message = FlashText({ time = 2, text = "Cat's Game, no winner!",
pos = vec2(WIDTH/2-140, HEIGHT/2)} )
sound(DATA, "ZgFAGwBDVRljX0pZpyABv1aNGz+iVWM/QABPXz5NfT5AZSxr")
ScreenGame:SetupNewBoard()
end
end
function ScreenGame:touched(touch)
if self.winner ~= nil then return end --allow code to do stuff w/o touch interfering
if touch.state == ENDED then
local x = touch.x - offset
x = math.floor(x/225)+1
local y = touch.y
y = 3-math.floor(y/225)
x=clamp(x,1,3)
y=clamp(y,1,3)
--now we have a bucket
if self.board[y][x] ~= "" then
--illegal touch
sound(SOUND_EXPLODE, 967)
else
self.board[y][x] = self.player
sound(SOUND_PICKUP, 6768)
if self.player == "X" then self.player = "O" else self.player = "X" end
end
self.winner = Analyze(self.board)
end
end
--fun way of drawing the background initially
function ScreenGame:DrawInitialBoard()
local linesize = 30 --line size
local padding = 50 --edge padding
pushStyle()
--AC we're drawing only 4 lines - make this easy!
strokeWidth(linesize)
stroke(186, 142, 142, 255)
line(400,padding, 400, HEIGHT-padding)
line(700,padding, 700, HEIGHT-padding)
--note that HEIGHT is correct for the WIDTH param, we are maknig a square
line(padding+ offset/2, 250, HEIGHT+ offset/2, 250)
line(padding+ offset/2, 550, HEIGHT+ offset/2, 550)
popStyle()
end
function Analyze(board)
local winner = nil
--check all horizontals
for x=1,3 do
if board[1][x] ~= "" then
if board[1][x] == board[2][x] and board[2][x] == board[3][x] then
--winner! draw line?
--text("WINNER "..board[1][x], 100,100)
winner = board[1][x]
end
end
end
--check second horizontal
for y=1,3 do
if board[y][1] ~= "" then
if board[y][1] == board[y][2] and board[y][2] == board[y][3] then
--winner! draw line?
--text("WINNER " .. board[y][1], 100,100)
winner = board[y][1]
end
end
end
--check diagonals
if board[1][1] ~= "" then
if board[1][1] == board[2][2] and board[2][2] == board[3][3] then
--winner! draw line?
--text("WINNER "..board[1][1], 100,100)
winner = board[1][1]
end
end
if board[1][3] ~= "" then
if board[1][3] == board[2][2] and board[2][2] == board[3][1] then
--winner! draw line?
--text("WINNER "..board[1][3], 100,100)
winner = board[1][3]
end
end
if (winner ~= nil ) then
--draw pretty explosions
explode:createFirework(100,500,color(255,255,255,255))
sound(SOUND_EXPLODE, 968)
explode:createFirework(300,200,color(255,255,255,255))
sound(SOUND_EXPLODE, 966)
explode:createFirework(500,400,color(255,255,255,255))
sound(SOUND_EXPLODE, 967)
--increment player's win count
if winner == "X" then
playerX = playerX + 1
else
playerO = playerO + 1
end
message = FlashText({ time = 2, text = "Player " .. winner .. " wins!",
pos = vec2(WIDTH/2-80, HEIGHT/2)} )
end
return winner --value or nil
end
function ScreenGame:SetupNewBoard()
self.board = {
{"","",""},
{"","",""},
{"","",""} }
-- self.player = "X" --we should enforce X alwys first, but let's not...
self.winner= nil --this will allow tuch() to operate - this is the gatekeeper :)
end
--
-- ScreenTitle.lua
--
ScreenTitle = class()
function ScreenTitle:init()
--cascade items down the screen
self.letter = "X"
items = {}
for x=1,25 do
i = vec3(math.random(WIDTH), math.random(HEIGHT)+HEIGHT,
math.random(3)) --20==item width , z= item falling speed
table.insert(items, i)
end
tmr = Timer(5)
tmr:start()
end
function ScreenTitle:draw()
fontSize(36)
font("Baskerville-SemiBold")
fill(114, 88, 88, 255)
if tmr:check() == true then
sound(DATA, "ZgNACwBAeQwzIHxmAAAAAA0PxD6kYgc+WQBbeXNiXDgKBghw")
if self.letter == "X" then self.letter = "O" else self.letter = "X" end
tmr = Timer(5)
tmr:start()
end
for k,v in pairs(items) do
text(self.letter, v.x, v.y)
v.y = v.y - v.z
if v.y < 0 then v.y = math.random(HEIGHT)+HEIGHT end
end
local w,h = textSize("TicTacToe")
fill(200, 188, 135, 255)
stroke(174, 174, 174, 255)
rectMode(CORNER)
local pad = 5
rect(WIDTH/2-w/2-pad, HEIGHT/2-h/2-pad, w+pad*2,h+pad*2)
fill(255, 255, 255, 255)
text("TicTacToe", WIDTH/2, HEIGHT/2)
text("Touch to play", WIDTH/2, 100)
if (message:active() == false) then
message = FlashText({time=2, text="Play TicTacToe!",
pos=vec2(math.random(200)+ 400, math.random(200)+400),
fontsize = 36 })
else
message:draw()
end
end
function ScreenTitle:touched(touch)
if touch.state == ENDED then
StartNewGame()
ScreenGame:init({x=WIDTH/2, y=HEIGHT/2, board=blank_board})
GAMESTATE = GAMESTATE_PLAYING
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment