Created
September 8, 2018 21:05
-
-
Save nico-abram/fe8a1b9bf49b491023c7340347933080 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
GridActor handles the update and input callback registering. | |
It also contains all the grid actors (Quads, or "Coloured rectangles") | |
The config table has all the basic configurable game parameters | |
The grid table contains a matrix of cells, used by gridActor quads to draw themselves | |
A cell can either be nil (Empty) or have a block | |
A block is a lua table which has a color | |
the currentPiece variable is a table with a 1 character string (type), a | |
a rotation (Number 0-3) and an offset (How many cells it has fallen) number | |
inputCallback is the inputCallback function | |
]] | |
--config | |
local config = { | |
grid = { | |
height = 20, | |
width = 10, | |
blockWidth = 20, | |
blockHeight = 20, | |
}, | |
pieces = { | |
colors = { | |
I = color("#8888ffCC"), | |
J = color("#bbbbbbCC"), | |
L = color("#0000ffCC"), | |
T = color("#FF3333CC"), | |
Z = color("#BB7777CC"), | |
S = color("#00ff00CC"), | |
O = color("#FFAAAACC"), | |
} | |
}, | |
buttons = { | |
speedUp = "u", | |
rotateLeft = "z", | |
rotateRight = "x", | |
rotate180 = ",", | |
drop = " ", | |
down = "down", | |
up = "up", | |
left = "left", | |
right = "right", | |
}, | |
bgColor = color("#33FF33CC"), | |
normalSpeed = 2, | |
highSpeed = 0.01, | |
inputPollingSeconds = 0.05, | |
} | |
-- General utility functions | |
function tableKeys(t) | |
local keys={} | |
local n=0 | |
for k,v in pairs(t) do | |
n=n+1 | |
keys[n]=k | |
end | |
return keys | |
end | |
function shuffle(tbl) | |
size = #tbl | |
for i = size, 1, -1 do | |
local rand = math.random(size) | |
tbl[i], tbl[rand] = tbl[rand], tbl[i] | |
end | |
return tbl | |
end | |
function serializeTable(val, name, skipnewlines, depth) | |
skipnewlines = skipnewlines or false | |
depth = depth or 0 | |
local tmp = string.rep(" ", depth) | |
if name then tmp = tmp .. name .. " = " end | |
if type(val) == "table" then | |
tmp = tmp .. "{" .. (not skipnewlines and "\n" or "") | |
for k, v in pairs(val) do | |
tmp = tmp .. serializeTable(v, k, skipnewlines, depth + 1) .. "," .. (not skipnewlines and "\n" or "") | |
end | |
tmp = tmp .. string.rep(" ", depth) .. "}" | |
elseif type(val) == "number" then | |
tmp = tmp .. tostring(val) | |
elseif type(val) == "string" then | |
tmp = tmp .. string.format("%q", val) | |
elseif type(val) == "boolean" then | |
tmp = tmp .. (val and "true" or "false") | |
else | |
tmp = tmp .. "\"[inserializeable datatype:" .. type(val) .. "]\"" | |
end | |
return tmp | |
end | |
function invertTable(t) | |
local s={} | |
for k,v in pairs(t) do | |
s[v]=k | |
end | |
return s | |
end | |
--List | |
List = {} | |
function List.new () | |
return {first = 0, last = -1} | |
end | |
function List.pushleft (list, value) | |
local first = list.first - 1 | |
list.first = first | |
list[first] = value | |
end | |
function List.pushright (list, value) | |
local last = list.last + 1 | |
list.last = last | |
list[last] = value | |
end | |
function List.popleft (list) | |
local first = list.first | |
if first > list.last then error("list is empty") end | |
local value = list[first] | |
list[first] = nil -- to allow garbage collection | |
list.first = first + 1 | |
return value | |
end | |
function List.popright (list) | |
local last = list.last | |
if list.first > last then error("list is empty") end | |
local value = list[last] | |
list[last] = nil -- to allow garbage collection | |
list.last = last - 1 | |
return value | |
end | |
function List.fromTable(t) | |
local l = List.new() | |
for i,v in ipairs(t) do | |
List.pushright(l, v) | |
end | |
return l | |
end | |
function copyTable(obj, seen) | |
if type(obj) ~= 'table' then return obj end | |
if seen and seen[obj] then return seen[obj] end | |
local s = seen or {} | |
local res = setmetatable({}, getmetatable(obj)) | |
s[obj] = res | |
for k, v in pairs(obj) do res[copyTable(k, s)] = copyTable(v, s) end | |
return res | |
end | |
-- Game globals | |
local currentSpeed = config.normalSpeed | |
local currentPiece = { name="L", rotation=1, offset={x=0,y=0} } | |
local secondsSinceLastMovement = 0 | |
local lastUpdateExecutionSeconds = nil | |
local grid = {} | |
local drawGrid = {} | |
setmetatable(drawGrid,{__index=grid} ) | |
local gridActor | |
function pos(x,y) return {x=x,y=y} end | |
local blocksByPiece = { | |
I = {pos(0,0),pos(0,1),pos(0,2),pos(0,3)}, | |
J = {pos(0,0),pos(1,0),pos(1,1),pos(1,2)}, | |
L = {pos(0,0),pos(1,0),pos(0,1),pos(0,2)}, | |
T = {pos(0,0),pos(0,1),pos(0,2),pos(1,1)}, | |
Z = {pos(0,0),pos(0,1),pos(1,1),pos(1,2)}, | |
S = {pos(1,0),pos(0,1),pos(1,1),pos(0,2)}, | |
O = {pos(0,0),pos(0,1),pos(1,0),pos(1,1)}, | |
} | |
local pieceNames = tableKeys(blocksByPiece) | |
local pieceQueue = List.fromTable(shuffle(pieceNames)) | |
--Tetris utility functions/tables, and game globals | |
function rotatePos(coord, numberino) | |
if numberino == 0 then | |
return {x=coord.x, y=coord.y} | |
elseif numberino == 1 then | |
return {x=coord.y, y=coord.x} | |
elseif numberino == 2 then | |
return {x=-1*coord.x+1, y=coord.y} | |
end | |
return {x=-1*coord.y+1, y=coord.x} | |
end | |
function setSpeed(newSpeed) | |
secondsSinceLastMovement= secondsSinceLastMovement*newSpeed/currentSpeed | |
currentSpeed = newSpeed | |
end | |
function rotatePiece(piece, numberino) | |
newPiece = {} | |
for i, v in ipairs(piece) do | |
newPiece[i] = rotatePos(v, numberino) | |
end | |
return newPiece | |
end | |
function translatePiece(piece, offset) | |
newPiece = {} | |
for i, v in ipairs(piece) do | |
newPiece[i] = v | |
newPiece[i].y = offset.y + newPiece[i].y | |
newPiece[i].x = offset.x + newPiece[i].x | |
end | |
return newPiece | |
end | |
function dropCurrentPiece() | |
--???? | |
end | |
local inputMappings = invertTable(config.buttons) | |
function getPieceBlocks(pieceData) | |
local pieceBlocks = blocksByPiece[pieceData.name] | |
pieceBlocks = rotatePiece(pieceBlocks, pieceData.rotation) | |
return translatePiece(pieceBlocks, {y=pieceData.offset.y, x=pieceData.offset.x+math.floor(config.grid.width/2)}) | |
end | |
function collidesWithBlocks(piece) | |
local pieceBlocks = getPieceBlocks(piece) | |
for i,v in ipairs(pieceBlocks) do | |
--collision | |
if grid[v.x] and grid[v.x][v.y] then return true end | |
--out of bounds | |
if v.x > config.grid.width-1 or v.x < 0 or v.y > config.grid.height-1 or v.y < 0 then | |
return true | |
end | |
end | |
return false | |
end | |
function rotateCurrentPiece(rotation) | |
local newPiece = copyTable(currentPiece) | |
newPiece.rotation=(currentPiece.rotation+rotation) % 4 | |
if not collidesWithBlocks(newPiece) then | |
currentPiece = newPiece | |
return newPiece | |
end | |
return nil | |
end | |
function moveCurrentPiece(offset) | |
local newPiece = copyTable(currentPiece) | |
newPiece.offset.x=newPiece.offset.x+offset.x | |
newPiece.offset.y=newPiece.offset.y+offset.y | |
if not collidesWithBlocks(newPiece) then | |
currentPiece = newPiece | |
return newPiece | |
end | |
return nil | |
end | |
local buttonMappings = { | |
speedUp = function() setSpeed(config.highSpeed) end, | |
rotateLeft = function() rotateCurrentPiece(1) end, | |
rotateRight = function() rotateCurrentPiece(-1) end, | |
rotate180 = function() rotateCurrentPiece(2) end, | |
drop = dropCurrentPiece, | |
down = function() setSpeed(config.highSpeed) end, | |
up = function() setSpeed(config.normalSpeed) end, | |
left = function() moveCurrentPiece({x=-1,y=0}) end, | |
right = function() moveCurrentPiece({x=1,y=0}) end, | |
leftRepeat = function() local b=true while b do b=moveCurrentPiece({x=-1,y=0}) end end, | |
rightRepeat = function() local b=true while b do b=moveCurrentPiece({x=1,y=0}) end end, | |
} | |
-- input callback | |
function buttonForEvent(event) | |
local button = inputMappings[string.gsub(event.DeviceInput.button, "DeviceButton_", "")] | |
if not button then | |
button = inputMappings[event.char] | |
end | |
return button | |
end | |
local lastInputs = {} | |
function buttonPress(button) | |
lastInputs[button] = os.clock() | |
buttonMappings[button]() | |
updateColors() | |
end | |
function inputCallback(event) | |
if event.type == "InputEventType_Release" then | |
local button = buttonForEvent(event) | |
if not button then return end | |
if button == "down" then | |
buttonPress("up") | |
end | |
return | |
end | |
if event.type ~= "InputEventType_FirstPress" then | |
if event.type == "InputEventType_Repeat" then | |
local button = buttonForEvent(event) | |
if not button then return end | |
if button == "left" or button == "right" then | |
buttonMappings[button.."Repeat"]() | |
end | |
end | |
local button = buttonForEvent(event) | |
if button and (button == "left" or button == "right") then | |
if lastInputs[button] and lastInputs[button] > config.inputPollingSeconds then | |
buttonPress(button.."Repeat") | |
end | |
end | |
return | |
end | |
local button = buttonForEvent(event) | |
if not button then return end | |
buttonPress(button) | |
end | |
-- Update all grid actor quad colors | |
function updateColors() | |
drawGrid = {} | |
if currentPiece.name then | |
local pieceBlocks = getPieceBlocks(currentPiece) | |
local pieceColor = config.pieces.colors[currentPiece.name] | |
for i,v in ipairs(pieceBlocks) do | |
v.x = v.x+1 | |
v.y=v.y+1 | |
if not drawGrid[v.x] then | |
if not grid[v.x] then grid[v.x] = {} end | |
drawGrid[v.x] = {} | |
setmetatable(drawGrid[v.x], {__index=grid[v.x]}) | |
end | |
drawGrid[v.x][v.y] = { color = pieceColor} | |
end | |
end | |
setmetatable(drawGrid,{__index=grid} ) | |
MESSAGEMAN:Broadcast("RedrawQuads") | |
end | |
-- Tick function (Called every 'currentSpeed' interval, in seconds) | |
function makeGameTick() | |
--make the currentPiece fall 1 cell | |
--check if fall ends (If so set the new piece with -1 offset) | |
--if the current piece has -1 offset | |
currentPiece.offset.y = currentPiece.offset.y+1 | |
local newPiece = copyTable(currentPiece) | |
newPiece.offset.y = currentPiece.offset.y+1 | |
if not collidesWithBlocks(newPiece) then | |
currentPiece = newPiece | |
else | |
local pieceBlocks = getPieceBlocks(currentPiece) | |
local pieceColor = config.pieces.colors[currentPiece.name] | |
for i,v in ipairs(pieceBlocks) do | |
if not grid[v.x] then grid[v.x] = {} end | |
grid[v.x][v.y] = {color=pieceColor} | |
end | |
currentPiece = { name=List.popright(pieceQueue), rotation=1, offset={x=0,y=0} } | |
List.pushright(pieceQueue, pieceNames[math.random(#pieceNames)]) | |
end | |
updateColors() | |
end | |
-- Update function (Called all the time) | |
local function everyFrame() | |
if not lastUpdateExecutionSeconds then lastUpdateExecutionSeconds=os.clock() end | |
secondsSinceLastMovement = secondsSinceLastMovement + os.clock() - lastUpdateExecutionSeconds | |
lastUpdateExecutionSeconds = os.clock() | |
if secondsSinceLastMovement > currentSpeed then | |
secondsSinceLastMovement = secondsSinceLastMovement - currentSpeed | |
makeGameTick() | |
end | |
end | |
-- grid initialization code | |
gridActor = Def.ActorFrame{ | |
OnCommand=function(self) | |
SCREENMAN:GetTopScreen():AddInputCallback(inputCallback) | |
end, | |
InitCommand=function(self) | |
self:SetUpdateFunction(everyFrame) | |
end | |
} | |
for i=1,config.grid.width do | |
local index = #gridActor+1 | |
gridActor[index] = Def.ActorFrame{ } | |
grid[i] = {} | |
for j=1,config.grid.height do | |
gridActor[index][#(gridActor[index])+1] = Def.Quad { | |
InitCommand=function(self) | |
self:xy(SCREEN_WIDTH/2+(i-1-math.floor(config.grid.width/2))*config.grid.blockWidth,(j-1-math.floor(config.grid.height/2))*config.grid.blockHeight+SCREEN_HEIGHT/2):zoomto(config.grid.blockWidth-1,config.grid.blockHeight-1):halign(0):valign(0):diffuse(config.bgColor) | |
end, | |
RedrawQuadsMessageCommand = function(self) | |
self:diffuse(drawGrid[i][j] and drawGrid[i][j].color or config.bgColor) | |
end | |
} | |
end | |
end | |
return Def.ActorFrame {gridActor} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment