first commit
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"Lua.diagnostics.globals": [
|
||||
"love"
|
||||
]
|
||||
}
|
||||
11
conf.lua
Normal file
@@ -0,0 +1,11 @@
|
||||
function love.conf(t)
|
||||
t.identity = "tromi_ver3"
|
||||
t.externalstorage=true
|
||||
|
||||
-- t.console = true
|
||||
|
||||
t.window.title = "Tromi"
|
||||
t.window.width = 1280
|
||||
t.window.height = 960
|
||||
t.window.vsync = false
|
||||
end
|
||||
162
funcs.lua
Normal file
@@ -0,0 +1,162 @@
|
||||
function copy(t)
|
||||
-- returns deep copy of t (as opposed to the shallow copy you get from var = t)
|
||||
if type(t) ~= "table" then return t end
|
||||
local meta = getmetatable(t)
|
||||
local target = {}
|
||||
for k, v in pairs(t) do target[k] = v end
|
||||
setmetatable(target, meta)
|
||||
return target
|
||||
end
|
||||
|
||||
function strTrueValues(tbl)
|
||||
-- returns a concatenation of all the keys in tbl with value true, separated with spaces
|
||||
str = ""
|
||||
for k, v in pairs(tbl) do
|
||||
if v == true then
|
||||
str = str .. k .. " "
|
||||
end
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
function frameTime(min, sec, hth)
|
||||
-- returns a time in frames from a time in minutes-seconds-hundredths format
|
||||
if min == nil then min = 0 end
|
||||
if sec == nil then sec = 0 end
|
||||
if hth == nil then hth = 0 end
|
||||
return min*3600 + sec*60 + math.ceil(hth * 0.6)
|
||||
end
|
||||
|
||||
function vAdd(v1, v2)
|
||||
-- returns the sum of vectors v1 and v2
|
||||
return {
|
||||
x = v1.x + v2.x,
|
||||
y = v1.y + v2.y
|
||||
}
|
||||
end
|
||||
|
||||
function vNeg(v)
|
||||
-- returns the opposite of vector v
|
||||
return {
|
||||
x = -v.x,
|
||||
y = -v.y
|
||||
}
|
||||
end
|
||||
|
||||
function formatTime(frames)
|
||||
-- returns a mm:ss:hh (h=hundredths) representation of the time in frames given
|
||||
if frames < 0 then return formatTime(0) end
|
||||
local min, sec, hund
|
||||
min = math.floor(frames/3600)
|
||||
sec = math.floor(frames/60) % 60
|
||||
hund = math.floor(frames/.6) % 100
|
||||
str = string.format("%02d:%02d.%02d", min, sec, hund)
|
||||
return str
|
||||
end
|
||||
|
||||
function formatBigNum(number)
|
||||
-- returns a string representing a number with commas as thousands separator (e.g. 12,345,678)
|
||||
local s
|
||||
if type(number) == "number" then
|
||||
s = string.format("%d", number)
|
||||
elseif type(number) == "string" then
|
||||
if not tonumber(number) then
|
||||
return
|
||||
else
|
||||
s = number
|
||||
end
|
||||
else
|
||||
return
|
||||
end
|
||||
local pos = Mod1(string.len(s), 3)
|
||||
return string.sub(s, 1, pos)
|
||||
.. string.gsub(string.sub(s, pos+1), "(...)", ",%1")
|
||||
end
|
||||
|
||||
function Mod1(n, m)
|
||||
-- returns a number congruent to n modulo m in the range [1;m] (as opposed to [0;m-1])
|
||||
return ((n-1) % m) + 1
|
||||
end
|
||||
|
||||
function table.contains(table, element)
|
||||
for _, value in pairs(table) do
|
||||
if value == element then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function table.highest(table)
|
||||
local i = 1
|
||||
local highest = nil
|
||||
for key, value in pairs(table) do
|
||||
if highest == nil then highest = key end
|
||||
if table[key] > table[highest] then highest = key end
|
||||
end
|
||||
return highest
|
||||
end
|
||||
|
||||
function table.lowest(table)
|
||||
local i = 1
|
||||
local lowest = nil
|
||||
for key, value in pairs(table) do
|
||||
if lowest == nil then lowest = key end
|
||||
if table[key] < table[lowest] then lowest = key end
|
||||
end
|
||||
return lowest
|
||||
end
|
||||
|
||||
function clamp(x, min, max)
|
||||
if max < min then
|
||||
min, max = max, min
|
||||
end
|
||||
return x < min and min or (x > max and max or x)
|
||||
end
|
||||
|
||||
function drawText(text, x, y, size, orientation, color)
|
||||
if color == nil then color = {1, 1, 1, 1} end
|
||||
love.graphics.setFont(tromi_font)
|
||||
love.graphics.setColor(0, 0, 0, 0.8)
|
||||
love.graphics.printf(text, x+1, y+1, size*2, orientation, nil, 0.50)
|
||||
love.graphics.setColor(color)
|
||||
love.graphics.printf(text, x, y, size*2, orientation, nil, 0.5)
|
||||
end
|
||||
|
||||
function drawBigText(text, x, y, size, orientation, color)
|
||||
if color == nil then color = {1, 1, 1, 1} end
|
||||
love.graphics.setFont(big_font)
|
||||
love.graphics.setColor(0, 0, 0, 0.8)
|
||||
love.graphics.printf(text, x+1, y+1, size*2, orientation, nil, 0.50)
|
||||
love.graphics.setColor(color)
|
||||
love.graphics.printf(text, x, y, size*2, orientation, nil, 0.5)
|
||||
end
|
||||
|
||||
function boolToInt(value)
|
||||
if value then return 1 else return 0 end
|
||||
end
|
||||
|
||||
function pairsByKeysReverse (t, f)
|
||||
local a = {}
|
||||
for n in pairs(t) do table.insert(a, n) end
|
||||
table.sort(a, f)
|
||||
local i = #t+1 -- iterator variable
|
||||
local iter = function () -- iterator function
|
||||
i = i - 1
|
||||
if a[i] == nil then return nil
|
||||
else return a[i], t[a[i]]
|
||||
end
|
||||
end
|
||||
return iter
|
||||
end
|
||||
|
||||
function split_string(inputstr, sep)
|
||||
if sep == nil then
|
||||
sep = "%s"
|
||||
end
|
||||
local t={}
|
||||
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
|
||||
table.insert(t, str)
|
||||
end
|
||||
return t
|
||||
end
|
||||
1154
game/gamemode.lua
Normal file
212
game/grid.lua
Normal file
@@ -0,0 +1,212 @@
|
||||
local Object = require 'libs.classic'
|
||||
|
||||
local Grid = Object:extend()
|
||||
|
||||
local empty = { skin = "", colour = "", oob=false }
|
||||
local oob = { skin = "", colour = "", oob=true }
|
||||
local block = { skin = "2tie", colour = "A", oob=false }
|
||||
|
||||
function Grid:new(width, height)
|
||||
self.grid = {}
|
||||
self.grid_age = {}
|
||||
self.width = width
|
||||
self.height = height
|
||||
for y = 1, self.height do
|
||||
self.grid[y] = {}
|
||||
self.grid_age[y] = {}
|
||||
for x = 1, self.width do
|
||||
self.grid[y][x] = empty
|
||||
self.grid_age[y][x] = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:clear()
|
||||
for y = 1, self.height do
|
||||
for x = 1, self.width do
|
||||
self.grid[y][x] = empty
|
||||
self.grid_age[y][x] = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:getCell(x, y)
|
||||
if x < 1 or x > self.width or y > self.height then return oob
|
||||
elseif y < 1 then return oob
|
||||
else return self.grid[y][x]
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:isOccupied(x, y)
|
||||
return self:getCell(x+1, y+1) ~= empty
|
||||
end
|
||||
|
||||
function Grid:isRowFull(row)
|
||||
for index, square in pairs(self.grid[row]) do
|
||||
if square == empty then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Grid:canPlacePiece(piece)
|
||||
local offsets = piece:getBlockOffsets()
|
||||
for index, offset in pairs(offsets) do
|
||||
local x = piece.position.x + offset.x
|
||||
local y = piece.position.y + offset.y
|
||||
if self:isOccupied(x, y) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Grid:canPlacePieceInVisibleGrid(piece)
|
||||
local offsets = piece:getBlockOffsets()
|
||||
for index, offset in pairs(offsets) do
|
||||
local x = piece.position.x + offset.x
|
||||
local y = piece.position.y + offset.y
|
||||
if y < 1 or self:isOccupied(x, y) ~= empty then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Grid:getClearedRowCount()
|
||||
local count = 0
|
||||
local cleared_row_table = {}
|
||||
for row = 1, self.height do
|
||||
if self:isRowFull(row) then
|
||||
count = count + 1
|
||||
table.insert(cleared_row_table, row)
|
||||
end
|
||||
end
|
||||
return count, cleared_row_table
|
||||
end
|
||||
|
||||
function Grid:markClearedRows()
|
||||
local block_table = {}
|
||||
for row = 1, self.height do
|
||||
if self:isRowFull(row) then
|
||||
block_table[row] = {}
|
||||
for x = 1, self.width do
|
||||
block_table[row][x] = {
|
||||
skin = self.grid[row][x].skin,
|
||||
colour = self.grid[row][x].colour,
|
||||
}
|
||||
self.grid[row][x] = {
|
||||
skin = self.grid[row][x].skin,
|
||||
colour = "X"
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
return block_table
|
||||
end
|
||||
|
||||
function Grid:clearClearedRows()
|
||||
for row = 1, self.height do
|
||||
if self:isRowFull(row) then
|
||||
for above_row = row, 2, -1 do
|
||||
self.grid[above_row] = self.grid[above_row - 1]
|
||||
self.grid_age[above_row] = self.grid_age[above_row - 1]
|
||||
end
|
||||
self.grid[1] = {}
|
||||
self.grid_age[1] = {}
|
||||
for i = 1, self.width do
|
||||
self.grid[1][i] = empty
|
||||
self.grid_age[1][i] = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Grid:clearSpecificRow(row)
|
||||
for col = 1, self.width do
|
||||
self.grid[row][col] = empty
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:applyPiece(piece)
|
||||
if piece.big then
|
||||
self:applyBigPiece(piece)
|
||||
return
|
||||
end
|
||||
offsets = piece:getBlockOffsets()
|
||||
for index, offset in pairs(offsets) do
|
||||
x = piece.position.x + offset.x
|
||||
y = piece.position.y + offset.y
|
||||
if y + 1 > 0 and y < self.height then
|
||||
self.grid[y+1][x+1] = {
|
||||
skin = piece.skin,
|
||||
colour = piece.colour,
|
||||
flash = 5
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:checkStackHeight()
|
||||
for i = 0, self.height - 1 do
|
||||
for j = 0, self.width - 1 do
|
||||
if self:isOccupied(j, i) then return self.height - i end
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function Grid:applyMap(map)
|
||||
for y, row in pairs(map) do
|
||||
for x, block in pairs(row) do
|
||||
self.grid_age[y][x] = 0
|
||||
self.grid[y][x] = block
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:update()
|
||||
for y = 1, self.height do
|
||||
for x = 1, self.width do
|
||||
if self.grid[y][x] ~= empty then
|
||||
self.grid_age[y][x] = self.grid_age[y][x] + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:draw(greyscale, timer)
|
||||
love.graphics.setColor(0,0,0,1)
|
||||
love.graphics.rectangle("fill", 256, 31, 80, 45, 0, 0)
|
||||
love.graphics.setColor(0.3, 0.3, 0.3, 1)
|
||||
love.graphics.line(256,31,256,31+45)
|
||||
love.graphics.line(256,31,256+80,31)
|
||||
love.graphics.line(256+80,31,256+80,31+45)
|
||||
love.graphics.line(256,31+45,256+80,31+45)
|
||||
for y = 1, self.height do
|
||||
for x = 1, self.width do
|
||||
if blocks[self.grid[y][x].skin] and
|
||||
blocks[self.grid[y][x].skin][self.grid[y][x].colour] then
|
||||
if self.grid[y][x].flash ~= nil then
|
||||
if self.grid[y][x].flash > 0 then
|
||||
love.graphics.setColor(0.4+(self.grid[y][x].flash*0.1), 0.4+(self.grid[y][x].flash*0.1), 0.4+(self.grid[y][x].flash*0.1), 1)
|
||||
love.graphics.draw(blocks[self.grid[y][x].skin]['W'], 200+x*16, 64+y*16)
|
||||
self.grid[y][x].flash = self.grid[y][x].flash - 1
|
||||
else
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour..'_d'], 200+x*16, 64+y*16)
|
||||
end
|
||||
end
|
||||
if greyscale then
|
||||
if timer > 1 then timer = 1 end
|
||||
love.graphics.setColor(0.7, 0.7, 0.7, 0+timer)
|
||||
if self.grid[y][x] ~= empty and self.grid[y][x].colour ~= 'X' then
|
||||
love.graphics.draw(blocks[self.grid[y][x].skin]["A"], 200+x*16, 64+y*16)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Grid
|
||||
174
game/piece.lua
Normal file
@@ -0,0 +1,174 @@
|
||||
local Object = require 'libs.classic'
|
||||
|
||||
local Piece = Object:extend()
|
||||
|
||||
function Piece:new(shape, rotation, position, block_offsets, gravity, lock_delay, skin, colour, big)
|
||||
self.shape = shape
|
||||
self.rotation = rotation
|
||||
self.position = position
|
||||
self.block_offsets = block_offsets
|
||||
self.gravity = gravity
|
||||
self.lock_delay = lock_delay
|
||||
self.skin = skin
|
||||
self.colour = colour
|
||||
self.lowest_point = -1
|
||||
-- self.ghost = false
|
||||
self.locked = false
|
||||
-- self.big = big
|
||||
end
|
||||
|
||||
-- Functions that return a new piece to test in rotation systems.
|
||||
|
||||
function Piece:withOffset(offset)
|
||||
return Piece(
|
||||
self.shape, self.rotation,
|
||||
{x = self.position.x + offset.x, y = self.position.y + offset.y},
|
||||
self.block_offsets, self.gravity, self.lock_delay, self.skin, self.colour
|
||||
)
|
||||
end
|
||||
|
||||
function Piece:withRelativeRotation(rot)
|
||||
local new_rot = self.rotation + rot
|
||||
while new_rot < 0 do new_rot = new_rot + 4 end
|
||||
while new_rot >= 4 do new_rot = new_rot - 4 end
|
||||
return Piece(
|
||||
self.shape, new_rot, self.position,
|
||||
self.block_offsets, self.gravity, self.lock_delay, self.skin, self.colour
|
||||
)
|
||||
end
|
||||
|
||||
-- Functions that return predicates relative to a grid.
|
||||
|
||||
function Piece:getBlockOffsets()
|
||||
return self.block_offsets[self.shape][self.rotation + 1]
|
||||
end
|
||||
|
||||
function Piece:occupiesSquare(x, y)
|
||||
local offsets = self:getBlockOffsets()
|
||||
for index, offset in pairs(offsets) do
|
||||
local new_offset = {x = self.position.x + offset.x, y = self.position.y + offset.y}
|
||||
if new_offset.x == x and new_offset.y == y then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function Piece:isMoveBlocked(grid, offset)
|
||||
local moved_piece = self:withOffset(offset)
|
||||
return not grid:canPlacePiece(moved_piece)
|
||||
end
|
||||
|
||||
function Piece:isDropBlocked(grid)
|
||||
return self:isMoveBlocked(grid, { x=0, y=1 })
|
||||
end
|
||||
|
||||
-- Procedures to actually do stuff to pieces.
|
||||
|
||||
function Piece:setOffset(offset)
|
||||
self.position.x = self.position.x + offset.x
|
||||
self.position.y = self.position.y + offset.y
|
||||
return self
|
||||
end
|
||||
|
||||
function Piece:setRelativeRotation(rot)
|
||||
new_rot = self.rotation + rot
|
||||
while new_rot < 0 do new_rot = new_rot + 4 end
|
||||
while new_rot >= 4 do new_rot = new_rot - 4 end
|
||||
self.rotation = new_rot
|
||||
return self
|
||||
end
|
||||
|
||||
function Piece:moveInGrid(step, squares, grid, instant)
|
||||
local moved = false
|
||||
for x = 1, squares do
|
||||
if grid:canPlacePiece(self:withOffset(step)) then
|
||||
moved = true
|
||||
self:setOffset(step)
|
||||
if instant then
|
||||
self:dropToBottom(grid)
|
||||
end
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Piece:dropSquares(dropped_squares, grid)
|
||||
self:moveInGrid({ x = 0, y = 1 }, dropped_squares, grid)
|
||||
end
|
||||
|
||||
function Piece:dropToBottom(grid)
|
||||
local piece_y = self.position.y
|
||||
self:dropSquares(math.huge, grid)
|
||||
self.gravity = 0
|
||||
if self.position.y > piece_y then
|
||||
--self.lock_delay = 0
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Piece:lockIfBottomed(grid)
|
||||
if self:isDropBlocked(grid) then
|
||||
self.locked = true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Piece:addGravity(gravity, grid, classic_lock)
|
||||
gravity = gravity / (self.big and 2 or 1)
|
||||
local new_gravity = self.gravity + gravity
|
||||
if self:isDropBlocked(grid) then
|
||||
if classic_lock then
|
||||
self.gravity = new_gravity
|
||||
else
|
||||
self.gravity = 0
|
||||
self.lock_delay = self.lock_delay + 1
|
||||
end
|
||||
elseif not (
|
||||
self:isMoveBlocked(grid, { x=0, y=-1 }) and gravity < 0
|
||||
) then
|
||||
local dropped_squares = math.floor(math.abs(new_gravity))
|
||||
if gravity >= 0 then
|
||||
local new_frac_gravity = new_gravity - dropped_squares
|
||||
self.gravity = new_frac_gravity
|
||||
self:dropSquares(dropped_squares, grid)
|
||||
if self:isDropBlocked(grid) then
|
||||
playSE("bottom")
|
||||
end
|
||||
else
|
||||
local new_frac_gravity = new_gravity + dropped_squares
|
||||
self.gravity = new_frac_gravity
|
||||
self:moveInGrid({ x=0, y=-1 }, dropped_squares, grid)
|
||||
if self:isMoveBlocked(grid, { x=0, y=-1 }) then
|
||||
playSE("bottom")
|
||||
end
|
||||
end
|
||||
else
|
||||
self.gravity = 0
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-- Procedures for drawing.
|
||||
|
||||
function Piece:draw(opacity, brightness, grid, partial_das)
|
||||
if opacity == nil then opacity = 1 end
|
||||
if brightness == nil then brightness = 1 end
|
||||
love.graphics.setColor(brightness, brightness, brightness, opacity)
|
||||
local offsets = self:getBlockOffsets()
|
||||
local gravity_offset = 0
|
||||
if partial_das == nil then partial_das = 0 end
|
||||
for index, offset in pairs(offsets) do
|
||||
local x = self.position.x + offset.x
|
||||
local y = self.position.y + offset.y
|
||||
love.graphics.draw(
|
||||
blocks[self.skin][self.colour],
|
||||
216+x*16+partial_das, 80+y*16+gravity_offset
|
||||
)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return Piece
|
||||
148
game/randomizer.lua
Normal file
@@ -0,0 +1,148 @@
|
||||
local Object = require 'libs.classic'
|
||||
|
||||
local Randomizer = Object:extend()
|
||||
|
||||
local highest_count = 0
|
||||
|
||||
function Randomizer:new(always, seed)
|
||||
self.always = always
|
||||
if seed ~= nil then
|
||||
self.seed = seed
|
||||
else
|
||||
self.seed = love.math.random(1, 9007199254740991)
|
||||
end
|
||||
self.drought = {
|
||||
I = 0,
|
||||
J = 0,
|
||||
L = 0,
|
||||
T = 0,
|
||||
S = 0,
|
||||
Z = 0,
|
||||
O = 0
|
||||
}
|
||||
self.droughted_deals = {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}
|
||||
self.piece_counts = {
|
||||
I = 0,
|
||||
J = 0,
|
||||
L = 0,
|
||||
T = 0,
|
||||
S = 0,
|
||||
Z = 0,
|
||||
O = 0
|
||||
}
|
||||
self.piece_round = 0
|
||||
self:initialize()
|
||||
end
|
||||
|
||||
function Randomizer:nextPiece()
|
||||
return self:generatePiece()
|
||||
end
|
||||
|
||||
function Randomizer:runTest()
|
||||
local i_distance = 0
|
||||
local highest_distance = 0
|
||||
local distance_total = 0
|
||||
local i_appearances = 0
|
||||
local dupe = 0
|
||||
local trip = 0
|
||||
local quad = 0
|
||||
local quin = 0
|
||||
local lastpiece = 0
|
||||
local lastpiece2 = 0
|
||||
local lastpiece3 = 0
|
||||
local lastpiece4 = 0
|
||||
local pieceseq_rep = 0
|
||||
local pieceseq = {}
|
||||
local last_pieceseq = {}
|
||||
local highest_discrepancy = 0
|
||||
local discrepancy = self.piece_counts[table.highest(self.piece_counts)] - self.piece_counts[table.lowest(self.piece_counts)]
|
||||
local total_chance = 0
|
||||
for i=0, 750000 do
|
||||
piece = self:generatePiece()
|
||||
if #pieceseq < 7 then
|
||||
table.insert(pieceseq, piece)
|
||||
else
|
||||
--print(table.concat(pieceseq))
|
||||
if table.concat(pieceseq) == table.concat(last_pieceseq) then
|
||||
pieceseq_rep = pieceseq_rep + 1
|
||||
end
|
||||
last_pieceseq = copy(pieceseq)
|
||||
pieceseq = {}
|
||||
end
|
||||
if piece == lastpiece then dupe = dupe + 1 end
|
||||
if piece == lastpiece and piece == lastpiece2 then trip = trip + 1 end
|
||||
if piece == lastpiece and piece == lastpiece2 and piece == lastpiece3 then quad = quad + 1 end
|
||||
if piece == lastpiece and piece == lastpiece2 and piece == lastpiece3 and piece == lastpiece4 then quin = quin + 1 end
|
||||
if piece == 'I' then
|
||||
distance_total = distance_total + i_distance
|
||||
i_appearances = i_appearances + 1
|
||||
i_distance = 0
|
||||
else i_distance = i_distance + 1 end
|
||||
if highest_distance < i_distance then highest_distance = i_distance end
|
||||
lastpiece4 = lastpiece3
|
||||
lastpiece3 = lastpiece2
|
||||
lastpiece2 = lastpiece
|
||||
lastpiece = piece
|
||||
--if self.piece_round <= 750 then print(discrepancy) end
|
||||
local new_discrepancy = self.piece_counts[table.highest(self.piece_counts)] - self.piece_counts[table.lowest(self.piece_counts)]
|
||||
if new_discrepancy > discrepancy then highest_discrepancy = new_discrepancy end
|
||||
discrepancy = new_discrepancy
|
||||
local chance = 0
|
||||
for piece,drought in pairs(self.drought) do
|
||||
local piece_chance = 0.14/( ((self.drought[table.highest(self.drought)]+1)-drought) / (self.drought[table.highest(self.drought)]) )
|
||||
chance = chance + piece_chance
|
||||
end
|
||||
chance = chance / 7
|
||||
total_chance = total_chance + chance
|
||||
end
|
||||
--something = something / 750000
|
||||
print(string.format('dupes: %d, trips: %s, quads: %s, quins: %s, highest i distance: %d, average i distance: %f, pieceseq reps: %d, highest discrepancy:%d, average chance:%f\ndrought lengths dealt: %s', dupe, trip, quad, quin, highest_distance, distance_total/i_appearances, pieceseq_rep, highest_discrepancy, total_chance/750000, table.concat(self.droughted_deals, '-')))
|
||||
for piece,count in pairs(self.piece_counts) do
|
||||
print(piece..' '..count)
|
||||
end
|
||||
end
|
||||
|
||||
function Randomizer:initialize()
|
||||
local start_pieces = {"I", "J", "L", "T"}
|
||||
local shapes = {"I", "J", "L", "T", "S", "Z", "O"}
|
||||
love.math.setRandomSeed(self.seed)
|
||||
self.drought[table.remove(shapes, love.math.random(1, 4))] = 10
|
||||
for i=4,9 do
|
||||
self.drought[table.remove(shapes, love.math.random(1, #shapes))] = i
|
||||
end
|
||||
|
||||
--self:runTest()
|
||||
end
|
||||
|
||||
function Randomizer:generatePiece(always)
|
||||
if self.always then
|
||||
return "I"
|
||||
end
|
||||
|
||||
local shapes = {"I", "J", "L", "T", "S", "Z", "O"}
|
||||
local new_piece = shapes[love.math.random(1,7)]
|
||||
local chance = love.math.random(0,self.drought[table.highest(self.drought)])
|
||||
while self.drought[new_piece] < chance do
|
||||
new_piece = shapes[love.math.random(1,7)]
|
||||
end
|
||||
for piece,drought in pairs(self.drought) do
|
||||
if drought >= 10 then
|
||||
new_piece = piece
|
||||
end
|
||||
end
|
||||
generated_piece = new_piece
|
||||
|
||||
for piece,drought in pairs(self.drought) do
|
||||
if new_piece ~= piece then self.drought[piece] = self.drought[piece] + 1
|
||||
else
|
||||
if drought < #self.droughted_deals then self.droughted_deals[drought+1] = self.droughted_deals[drought+1] + 1 end
|
||||
self.piece_counts[piece] = self.piece_counts[piece] + 1
|
||||
self.drought[piece] = 0
|
||||
end
|
||||
end
|
||||
self.piece_round = self.piece_round + 1
|
||||
|
||||
return generated_piece
|
||||
end
|
||||
|
||||
return Randomizer
|
||||
299
game/rotation.lua
Normal file
@@ -0,0 +1,299 @@
|
||||
local Object = require 'libs.classic'
|
||||
local Piece = require 'game.piece'
|
||||
|
||||
local Rotation = Object:extend()
|
||||
|
||||
Rotation.spawn_positions = {
|
||||
I = { x=3, y= -1 },
|
||||
J = { x=3, y= -1 },
|
||||
L = { x=3, y= -1 },
|
||||
O = { x=3, y= -1 },
|
||||
S = { x=3, y= -1 },
|
||||
T = { x=3, y= -1 },
|
||||
Z = { x=3, y= -1 },
|
||||
}
|
||||
|
||||
Rotation.colourscheme = {
|
||||
I = "R",
|
||||
L = "O",
|
||||
J = "B",
|
||||
S = "M",
|
||||
Z = "G",
|
||||
O = "Y",
|
||||
T = "C",
|
||||
}
|
||||
|
||||
Rotation.block_offsets = {
|
||||
I={
|
||||
{ {x=0, y=1}, {x=1, y=1}, {x=2, y=1}, {x=3, y=1} },
|
||||
{ {x=2, y=0}, {x=2, y=1}, {x=2, y=2}, {x=2, y=3} },
|
||||
{ {x=0, y=1}, {x=1, y=1}, {x=2, y=1}, {x=3, y=1} },
|
||||
{ {x=2, y=0}, {x=2, y=1}, {x=2, y=2}, {x=2, y=3} },
|
||||
},
|
||||
J={
|
||||
{ {x=0, y=1}, {x=1, y=1}, {x=2, y=1}, {x=2, y=2} },
|
||||
{ {x=1, y=0}, {x=1, y=1}, {x=1, y=2}, {x=0, y=2} },
|
||||
{ {x=0, y=1}, {x=0, y=2}, {x=1, y=2}, {x=2, y=2} },
|
||||
{ {x=1, y=0}, {x=2, y=0}, {x=1, y=1}, {x=1, y=2} },
|
||||
},
|
||||
L={
|
||||
{ {x=0, y=1}, {x=0, y=2}, {x=1, y=1}, {x=2, y=1} },
|
||||
{ {x=0, y=0}, {x=1, y=0}, {x=1, y=1}, {x=1, y=2} },
|
||||
{ {x=0, y=2}, {x=1, y=2}, {x=2, y=1}, {x=2, y=2} },
|
||||
{ {x=1, y=0}, {x=1, y=1}, {x=1, y=2}, {x=2, y=2} },
|
||||
},
|
||||
O={
|
||||
{ {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=2, y=2} },
|
||||
{ {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=2, y=2} },
|
||||
{ {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=2, y=2} },
|
||||
{ {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=2, y=2} },
|
||||
},
|
||||
S={
|
||||
{ {x=1, y=1}, {x=2, y=1}, {x=0, y=2}, {x=1, y=2} },
|
||||
{ {x=0, y=0}, {x=0, y=1}, {x=1, y=1}, {x=1, y=2} },
|
||||
{ {x=1, y=1}, {x=2, y=1}, {x=0, y=2}, {x=1, y=2} },
|
||||
{ {x=0, y=0}, {x=0, y=1}, {x=1, y=1}, {x=1, y=2} },
|
||||
},
|
||||
T={
|
||||
{ {x=0, y=1}, {x=1, y=1}, {x=1, y=2}, {x=2, y=1} },
|
||||
{ {x=0, y=1}, {x=1, y=0}, {x=1, y=1}, {x=1, y=2} },
|
||||
{ {x=0, y=2}, {x=1, y=1}, {x=1, y=2}, {x=2, y=2} },
|
||||
{ {x=1, y=0}, {x=1, y=1}, {x=2, y=1}, {x=1, y=2} },
|
||||
},
|
||||
Z={
|
||||
{ {x=0, y=1}, {x=1, y=1}, {x=1, y=2}, {x=2, y=2} },
|
||||
{ {x=2, y=0}, {x=1, y=1}, {x=2, y=1}, {x=1, y=2} },
|
||||
{ {x=0, y=1}, {x=1, y=1}, {x=1, y=2}, {x=2, y=2} },
|
||||
{ {x=2, y=0}, {x=1, y=1}, {x=2, y=1}, {x=1, y=2} },
|
||||
}
|
||||
}
|
||||
|
||||
Rotation.pieces = 7
|
||||
|
||||
-- Component functions.
|
||||
|
||||
function Rotation:new(game_mode)
|
||||
self.game = require 'game.gamemode'
|
||||
end
|
||||
|
||||
function Rotation:rotatePiece(inputs, piece, grid, prev_inputs, initial, lastdir)
|
||||
local new_inputs = {}
|
||||
|
||||
for input, value in pairs(inputs) do
|
||||
if value and not prev_inputs[input] then
|
||||
new_inputs[input] = true
|
||||
end
|
||||
end
|
||||
local was_drop_blocked = piece:isDropBlocked(grid)
|
||||
|
||||
if self:canPieceRotate(piece, grid) then
|
||||
-- if not self.held_rotate then
|
||||
-- self:attemptRotate(inputs, piece, grid, initial)
|
||||
-- self.held_rotate = true
|
||||
-- else
|
||||
self:attemptRotate(new_inputs, piece, grid, initial, lastdir)
|
||||
-- end
|
||||
end
|
||||
|
||||
if not initial and not was_drop_blocked and piece:isDropBlocked(grid) then
|
||||
playSE("bottom")
|
||||
end
|
||||
|
||||
-- prev_inputs becomes the previous inputs
|
||||
for input, value in pairs(inputs) do
|
||||
prev_inputs[input] = inputs[input]
|
||||
end
|
||||
end
|
||||
|
||||
function Rotation:attemptRotate(new_inputs, piece, grid, initial, lastdir)
|
||||
local rot_dir = 0
|
||||
|
||||
if (new_inputs["rotate_left"] or new_inputs["rotate_left2"]) then
|
||||
rot_dir = 3
|
||||
if lastdir == 0 then lastdir = -1 end
|
||||
elseif (new_inputs["rotate_right"] or new_inputs["rotate_right2"]) then
|
||||
rot_dir = 1
|
||||
if lastdir == 0 then lastdir = 1 end
|
||||
end
|
||||
if rot_dir == 0 then return end
|
||||
|
||||
local new_piece = piece:withRelativeRotation(rot_dir)
|
||||
self:attemptWallkicks(piece, new_piece, rot_dir, grid, lastdir)
|
||||
end
|
||||
|
||||
function Rotation:attemptWallkicks(piece, new_piece, rot_dir, grid, lastdir)
|
||||
-- wallkick routine designed to maximize flexibility while minimizing teleports
|
||||
|
||||
-- O doesn't kick
|
||||
if (piece.shape == "O") then return end
|
||||
|
||||
-- assess precisely what rows/columns would be blocked given the desired rotation
|
||||
local sides = {top=false,uright=false,lright=false,uleft=false,lleft=false,center=false,bottom=false}
|
||||
local left_exists = false
|
||||
local right_exists = false
|
||||
local kick = {x=0,y=0}
|
||||
for _,offset in pairs(new_piece:getBlockOffsets()) do
|
||||
local x = piece.position.x + offset.x
|
||||
local y = piece.position.y + offset.y
|
||||
if offset.x == 0 then left_exists = true end
|
||||
if offset.x == 2 or offset.x == 3 then right_exists = true end
|
||||
|
||||
if grid:isOccupied(x,y) then
|
||||
if offset.y == 0 then sides.top = true end
|
||||
if offset.y == 3 then sides.bottom = true end
|
||||
if offset.y == 1 and offset.x == 0 then sides.uleft = true end
|
||||
if offset.y == 2 and offset.x == 0 then sides.lleft = true end
|
||||
if offset.y == 1 and (offset.x == 2 or offset.x == 3) then sides.uright = true end
|
||||
if offset.y == 2 and (offset.x == 2 or offset.x == 3) then sides.lright = true end
|
||||
if offset.x == 1 then sides.center = true end
|
||||
end
|
||||
end
|
||||
|
||||
if sides.top then kick = {x=0,y=1}
|
||||
elseif (sides.uleft and sides.lright) or (sides.uright and sides.lleft) or (sides.uright and sides.uleft) then kick = {x=0,y=1}
|
||||
elseif (sides.lleft and sides.lright) then kick = {x=0,y=-1}
|
||||
elseif (sides.lleft or sides.uleft) then kick = {x=1,y=0}
|
||||
elseif (sides.lright or sides.uright) then kick = {x=-1,y=0}
|
||||
elseif sides.center and left_exists then kick = {x=1,y=0}
|
||||
elseif sides.center and right_exists then kick = {x=-1,y=0}
|
||||
elseif sides.bottom then kick = {x=0,y=-1}
|
||||
end
|
||||
|
||||
|
||||
if grid:canPlacePiece(new_piece:withOffset({x=0,y=0})) then
|
||||
self:onPieceRotate(piece, grid)
|
||||
piece:setRelativeRotation(rot_dir):setOffset({x=0,y=0})
|
||||
elseif grid:canPlacePiece(new_piece:withOffset(kick)) then
|
||||
self:onPieceRotate(piece, grid)
|
||||
piece:setRelativeRotation(rot_dir):setOffset(kick)
|
||||
end
|
||||
end
|
||||
|
||||
function Rotation:movePiece(piece, grid, move, instant)
|
||||
local was_drop_blocked = piece:isDropBlocked(grid)
|
||||
local offset = ({x=0, y=0})
|
||||
local moves = 0
|
||||
local y = piece.position.y
|
||||
if move == "left" then
|
||||
offset.x = -1
|
||||
moves = 1
|
||||
elseif move == "right" then
|
||||
offset.x = 1
|
||||
moves = 1
|
||||
elseif move == "speedleft" then
|
||||
offset.x = -1
|
||||
moves = grid.width
|
||||
elseif move == "speedright" then
|
||||
offset.x = 1
|
||||
moves = grid.width
|
||||
end
|
||||
if not self:canPieceMove(piece, grid) then return end
|
||||
for i = 1, moves do
|
||||
local x = piece.position.x
|
||||
if moves ~= 1 then
|
||||
piece:moveInGrid(offset, 1, grid, instant)
|
||||
else
|
||||
piece:moveInGrid(offset, 1, grid, false)
|
||||
end
|
||||
if piece.position.x ~= x then
|
||||
self:onPieceMove(piece, grid)
|
||||
if piece.locked then break end
|
||||
end
|
||||
end
|
||||
if not was_drop_blocked and piece:isDropBlocked(grid) then
|
||||
playSE("bottom")
|
||||
end
|
||||
end
|
||||
|
||||
function Rotation:dropPiece(
|
||||
inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
|
||||
hard_drop_enabled, additive_gravity, classic_lock
|
||||
)
|
||||
local y = piece.position.y
|
||||
if inputs["down"] == true and drop_locked == false then
|
||||
piece:addGravity(math.max(gravity, drop_speed), grid, classic_lock)
|
||||
else
|
||||
piece:addGravity(gravity, grid, classic_lock)
|
||||
end
|
||||
if piece.position.y ~= y then
|
||||
self:onPieceDrop(piece, grid)
|
||||
end
|
||||
end
|
||||
|
||||
function Rotation:lockPiece(piece, grid, lock_delay, classic_lock)
|
||||
if piece:isDropBlocked(grid) and piece.lock_delay >= lock_delay then
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
|
||||
function Rotation:get180RotationValue() return 3 end
|
||||
function Rotation:getDefaultOrientation() return 1 end
|
||||
function Rotation:getDrawOffset(shape, orientation) return { x=0, y=0 } end
|
||||
function Rotation:getAboveFieldOffset(shape, orientation)
|
||||
if shape == "I" then
|
||||
return 1
|
||||
else
|
||||
return 2
|
||||
end
|
||||
end
|
||||
|
||||
function Rotation:initializePiece(
|
||||
inputs, data, grid, gravity, prev_inputs,
|
||||
move, lock_delay, drop_speed,
|
||||
drop_locked, hard_drop_locked, big, irs, lastdir
|
||||
)
|
||||
local spawn_positions
|
||||
spawn_positions = self.spawn_positions
|
||||
|
||||
local colours
|
||||
colours = self.colourscheme
|
||||
self.last_dir = 0
|
||||
self.held_rotate = false
|
||||
if inputs['left'] then self.last_dir = -1
|
||||
elseif inputs['right'] then self.last_dir = 1 end
|
||||
local spawn_x = math.floor(spawn_positions[data.shape].x / 10 * grid.width)
|
||||
|
||||
local spawn_dy
|
||||
spawn_dy = 0
|
||||
|
||||
local piece = Piece(data.shape, data.orientation - 1, {
|
||||
x = spawn_x or spawn_positions[data.shape].x,
|
||||
y = spawn_positions[data.shape].y - spawn_dy
|
||||
}, self.block_offsets, 0, 0, data.skin, colours[data.shape], big)
|
||||
|
||||
self:onPieceCreate(piece)
|
||||
if irs then
|
||||
self:rotatePiece(inputs, piece, grid, {}, true, lastdir)
|
||||
end
|
||||
return piece
|
||||
end
|
||||
|
||||
function Rotation:onPieceCreate(piece) end
|
||||
|
||||
function Rotation:processPiece(
|
||||
inputs, piece, grid, gravity, prev_inputs,
|
||||
move, lock_delay, drop_speed,
|
||||
drop_locked, hard_drop_locked,
|
||||
hard_drop_enabled, additive_gravity, classic_lock, lastdir
|
||||
)
|
||||
self:rotatePiece(inputs, piece, grid, prev_inputs, false, lastdir)
|
||||
self:movePiece(piece, grid, move, gravity >= grid.height)
|
||||
self:dropPiece(
|
||||
inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
|
||||
hard_drop_enabled, additive_gravity, classic_lock
|
||||
)
|
||||
self:lockPiece(piece, grid, lock_delay, classic_lock)
|
||||
end
|
||||
|
||||
function Rotation:canPieceMove(piece, grid) return true end
|
||||
function Rotation:canPieceRotate(piece, grid) return true end
|
||||
function Rotation:onPieceMove(piece) end
|
||||
function Rotation:onPieceRotate(piece) end
|
||||
function Rotation:onPieceDrop(piece)
|
||||
if piece.position.y > piece.lowest_point then
|
||||
piece.lock_delay = 0
|
||||
piece.lowest_point = piece.position.y
|
||||
end
|
||||
end
|
||||
|
||||
return Rotation
|
||||
287
game/rotation_pent.lua
Normal file
@@ -0,0 +1,287 @@
|
||||
local Object = require 'libs.classic'
|
||||
local Piece = require 'game.piece'
|
||||
|
||||
local Rotation = Object:extend()
|
||||
|
||||
Rotation.spawn_positions = {
|
||||
I = { x=3, y= -2 },
|
||||
J = { x=3, y= -2 },
|
||||
L = { x=3, y= -2 },
|
||||
O = { x=3, y= -2 },
|
||||
S = { x=3, y= -2 },
|
||||
T = { x=3, y= -2 },
|
||||
Z = { x=3, y= -2 },
|
||||
}
|
||||
|
||||
Rotation.colourscheme = {
|
||||
I = "R",
|
||||
L = "O",
|
||||
J = "B",
|
||||
S = "M",
|
||||
Z = "G",
|
||||
O = "Y",
|
||||
T = "C",
|
||||
}
|
||||
|
||||
Rotation.block_offsets = {
|
||||
I={
|
||||
{ {x=0, y=2}, {x=1, y=2}, {x=2, y=2}, {x=3, y=2}, {x=4, y=2} },
|
||||
{ {x=2, y=0}, {x=2, y=1}, {x=2, y=2}, {x=2, y=3}, {x=2, y=4} },
|
||||
{ {x=0, y=2}, {x=1, y=2}, {x=2, y=2}, {x=3, y=2}, {x=4, y=2} },
|
||||
{ {x=2, y=0}, {x=2, y=1}, {x=2, y=2}, {x=2, y=3}, {x=2, y=4} },
|
||||
},
|
||||
J={
|
||||
{ {x=0, y=2}, {x=1, y=2}, {x=2, y=2}, {x=2, y=3}, {x=3, y=2} },
|
||||
{ {x=1, y=0}, {x=1, y=1}, {x=1, y=2}, {x=0, y=2}, {x=1, y=3} },
|
||||
{ {x=1, y=2}, {x=1, y=3}, {x=2, y=3}, {x=3, y=3}, {x=0, y=3} },
|
||||
{ {x=1, y=1}, {x=2, y=1}, {x=1, y=2}, {x=1, y=3}, {x=1, y=0} },
|
||||
},
|
||||
L={
|
||||
{ {x=0, y=2}, {x=0, y=3}, {x=1, y=2}, {x=2, y=2}, {x=3, y=2} },
|
||||
{ {x=0, y=0}, {x=1, y=0}, {x=1, y=1}, {x=1, y=2}, {x=1, y=3} },
|
||||
{ {x=1, y=3}, {x=2, y=3}, {x=3, y=2}, {x=3, y=3}, {x=0, y=3} },
|
||||
{ {x=1, y=1}, {x=1, y=2}, {x=1, y=3}, {x=2, y=3}, {x=1, y=0} },
|
||||
},
|
||||
O={
|
||||
{ {x=0, y=2}, {x=0, y=3}, {x=1, y=2}, {x=2, y=2}, {x=1, y=3} },
|
||||
{ {x=0, y=1}, {x=1, y=1}, {x=1, y=2}, {x=1, y=3}, {x=0, y=2} },
|
||||
{ {x=0, y=3}, {x=1, y=3}, {x=2, y=2}, {x=2, y=3}, {x=1, y=2} },
|
||||
{ {x=1, y=1}, {x=1, y=2}, {x=1, y=3}, {x=2, y=3}, {x=2, y=2} },
|
||||
},
|
||||
S={
|
||||
{ {x=1, y=2}, {x=2, y=2}, {x=0, y=3}, {x=1, y=3}, {x=3, y=2} },
|
||||
{ {x=0, y=0}, {x=0, y=1}, {x=1, y=1}, {x=1, y=2}, {x=1, y=3} },
|
||||
{ {x=2, y=2}, {x=3, y=2}, {x=1, y=3}, {x=2, y=3}, {x=0, y=3} },
|
||||
{ {x=1, y=1}, {x=1, y=2}, {x=2, y=2}, {x=2, y=3}, {x=1, y=0} },
|
||||
},
|
||||
T={
|
||||
{ {x=0, y=2}, {x=1, y=2}, {x=1, y=3}, {x=2, y=2}, {x=3, y=2} },
|
||||
{ {x=0, y=1}, {x=1, y=0}, {x=1, y=1}, {x=1, y=2}, {x=1, y=3} },
|
||||
{ {x=1, y=3}, {x=2, y=2}, {x=2, y=3}, {x=3, y=3}, {x=0, y=3} },
|
||||
{ {x=2, y=1}, {x=2, y=2}, {x=3, y=2}, {x=2, y=3}, {x=2, y=0} },
|
||||
},
|
||||
Z={
|
||||
{ {x=0, y=2}, {x=1, y=2}, {x=1, y=3}, {x=2, y=3}, {x=2, y=2} },
|
||||
{ {x=1, y=1}, {x=0, y=2}, {x=1, y=2}, {x=0, y=3}, {x=1, y=3} },
|
||||
{ {x=0, y=2}, {x=1, y=2}, {x=1, y=3}, {x=2, y=3}, {x=0, y=3} },
|
||||
{ {x=2, y=1}, {x=1, y=2}, {x=2, y=2}, {x=1, y=3}, {x=1, y=1} },
|
||||
}
|
||||
}
|
||||
|
||||
Rotation.pieces = 7
|
||||
|
||||
-- Component functions.
|
||||
|
||||
function Rotation:new(game_mode)
|
||||
self.game = require 'game.gamemode'
|
||||
end
|
||||
|
||||
function Rotation:rotatePiece(inputs, piece, grid, prev_inputs, initial, lastdir)
|
||||
local new_inputs = {}
|
||||
|
||||
for input, value in pairs(inputs) do
|
||||
if value and not prev_inputs[input] then
|
||||
new_inputs[input] = true
|
||||
end
|
||||
end
|
||||
local was_drop_blocked = piece:isDropBlocked(grid)
|
||||
|
||||
if self:canPieceRotate(piece, grid) then
|
||||
-- if not self.held_rotate then
|
||||
-- self:attemptRotate(inputs, piece, grid, initial)
|
||||
-- self.held_rotate = true
|
||||
-- else
|
||||
self:attemptRotate(new_inputs, piece, grid, initial, lastdir)
|
||||
-- end
|
||||
end
|
||||
|
||||
if not initial and not was_drop_blocked and piece:isDropBlocked(grid) then
|
||||
playSE("bottom")
|
||||
end
|
||||
|
||||
-- prev_inputs becomes the previous inputs
|
||||
for input, value in pairs(inputs) do
|
||||
prev_inputs[input] = inputs[input]
|
||||
end
|
||||
end
|
||||
|
||||
function Rotation:attemptRotate(new_inputs, piece, grid, initial, lastdir)
|
||||
local rot_dir = 0
|
||||
|
||||
if (new_inputs["rotate_left"] or new_inputs["rotate_left2"]) then
|
||||
rot_dir = 3
|
||||
if lastdir == 0 then lastdir = -1 end
|
||||
elseif (new_inputs["rotate_right"] or new_inputs["rotate_right2"]) then
|
||||
rot_dir = 1
|
||||
if lastdir == 0 then lastdir = 1 end
|
||||
end
|
||||
if rot_dir == 0 then return end
|
||||
|
||||
local new_piece = piece:withRelativeRotation(rot_dir)
|
||||
self:attemptWallkicks(piece, new_piece, rot_dir, grid, lastdir)
|
||||
end
|
||||
|
||||
function Rotation:attemptWallkicks(piece, new_piece, rot_dir, grid, lastdir)
|
||||
-- assess precisely what rows/columns would be blocked given the desired rotation
|
||||
local sides = {top=false,uright=false,lright=false,uleft=false,lleft=false,center=false,bottom=false}
|
||||
local left_exists = false
|
||||
local right_exists = false
|
||||
local kick = {x=0,y=0}
|
||||
for _,offset in pairs(new_piece:getBlockOffsets()) do
|
||||
local x = piece.position.x + offset.x
|
||||
local y = piece.position.y + offset.y
|
||||
|
||||
if grid:isOccupied(x,y) then
|
||||
if offset.y <= 1 then sides.top = true end
|
||||
if offset.y == 4 then sides.bottom = true end
|
||||
if offset.x <= 1 then sides.lleft = true end
|
||||
if offset.x >= 2 then sides.lright = true end
|
||||
end
|
||||
end
|
||||
|
||||
if sides.top then kick = {x=0,y=1}
|
||||
elseif sides.bottom then kick = {x=0,y=-1}
|
||||
elseif (sides.lleft or sides.uleft) then kick = {x=1,y=0}
|
||||
elseif (sides.lright or sides.uright) then kick = {x=-1,y=0}
|
||||
end
|
||||
|
||||
if grid:canPlacePiece(new_piece:withOffset({x=0,y=0})) then
|
||||
self:onPieceRotate(piece, grid)
|
||||
piece:setRelativeRotation(rot_dir):setOffset({x=0,y=0})
|
||||
elseif grid:canPlacePiece(new_piece:withOffset(kick)) then
|
||||
self:onPieceRotate(piece, grid)
|
||||
piece:setRelativeRotation(rot_dir):setOffset(kick)
|
||||
elseif grid:canPlacePiece(new_piece:withOffset({x=kick.x*2,y=kick.y*2})) then
|
||||
self:onPieceRotate(piece, grid)
|
||||
piece:setRelativeRotation(rot_dir):setOffset({x=kick.x*2,y=kick.y*2})
|
||||
end
|
||||
end
|
||||
|
||||
function Rotation:movePiece(piece, grid, move, instant)
|
||||
local was_drop_blocked = piece:isDropBlocked(grid)
|
||||
local offset = ({x=0, y=0})
|
||||
local moves = 0
|
||||
local y = piece.position.y
|
||||
if move == "left" then
|
||||
offset.x = -1
|
||||
moves = 1
|
||||
elseif move == "right" then
|
||||
offset.x = 1
|
||||
moves = 1
|
||||
elseif move == "speedleft" then
|
||||
offset.x = -1
|
||||
moves = grid.width
|
||||
elseif move == "speedright" then
|
||||
offset.x = 1
|
||||
moves = grid.width
|
||||
end
|
||||
if not self:canPieceMove(piece, grid) then return end
|
||||
for i = 1, moves do
|
||||
local x = piece.position.x
|
||||
if moves ~= 1 then
|
||||
piece:moveInGrid(offset, 1, grid, instant)
|
||||
else
|
||||
piece:moveInGrid(offset, 1, grid, false)
|
||||
end
|
||||
if piece.position.x ~= x then
|
||||
self:onPieceMove(piece, grid)
|
||||
if piece.locked then break end
|
||||
end
|
||||
end
|
||||
if not was_drop_blocked and piece:isDropBlocked(grid) then
|
||||
playSE("bottom")
|
||||
end
|
||||
end
|
||||
|
||||
function Rotation:dropPiece(
|
||||
inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
|
||||
hard_drop_enabled, additive_gravity, classic_lock
|
||||
)
|
||||
local y = piece.position.y
|
||||
if inputs["down"] == true and drop_locked == false then
|
||||
piece:addGravity(math.max(gravity, drop_speed), grid, classic_lock)
|
||||
else
|
||||
piece:addGravity(gravity, grid, classic_lock)
|
||||
end
|
||||
if piece.position.y ~= y then
|
||||
self:onPieceDrop(piece, grid)
|
||||
end
|
||||
end
|
||||
|
||||
function Rotation:lockPiece(piece, grid, lock_delay, classic_lock)
|
||||
if piece:isDropBlocked(grid) and piece.lock_delay >= lock_delay then
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
|
||||
function Rotation:get180RotationValue() return 3 end
|
||||
function Rotation:getDefaultOrientation() return 1 end
|
||||
function Rotation:getDrawOffset(shape, orientation) return { x=0, y=0 } end
|
||||
function Rotation:getAboveFieldOffset(shape, orientation)
|
||||
if shape == "I" then
|
||||
return 1
|
||||
else
|
||||
return 2
|
||||
end
|
||||
end
|
||||
|
||||
function Rotation:initializePiece(
|
||||
inputs, data, grid, gravity, prev_inputs,
|
||||
move, lock_delay, drop_speed,
|
||||
drop_locked, hard_drop_locked, big, irs, lastdir
|
||||
)
|
||||
local spawn_positions
|
||||
spawn_positions = self.spawn_positions
|
||||
|
||||
local colours
|
||||
colours = self.colourscheme
|
||||
self.last_dir = 0
|
||||
self.held_rotate = false
|
||||
if inputs['left'] then self.last_dir = -1
|
||||
elseif inputs['right'] then self.last_dir = 1 end
|
||||
local spawn_x = math.floor(spawn_positions[data.shape].x / 10 * grid.width)
|
||||
|
||||
local spawn_dy
|
||||
spawn_dy = 0
|
||||
|
||||
local piece = Piece(data.shape, data.orientation - 1, {
|
||||
x = spawn_x or spawn_positions[data.shape].x,
|
||||
y = spawn_positions[data.shape].y - spawn_dy
|
||||
}, self.block_offsets, 0, 0, data.skin, colours[data.shape], big)
|
||||
|
||||
self:onPieceCreate(piece)
|
||||
if irs then
|
||||
self:rotatePiece(inputs, piece, grid, {}, true, lastdir)
|
||||
end
|
||||
return piece
|
||||
end
|
||||
|
||||
function Rotation:onPieceCreate(piece) end
|
||||
|
||||
function Rotation:processPiece(
|
||||
inputs, piece, grid, gravity, prev_inputs,
|
||||
move, lock_delay, drop_speed,
|
||||
drop_locked, hard_drop_locked,
|
||||
hard_drop_enabled, additive_gravity, classic_lock, lastdir
|
||||
)
|
||||
self:rotatePiece(inputs, piece, grid, prev_inputs, false, lastdir)
|
||||
self:movePiece(piece, grid, move, gravity >= grid.height)
|
||||
self:dropPiece(
|
||||
inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
|
||||
hard_drop_enabled, additive_gravity, classic_lock
|
||||
)
|
||||
self:lockPiece(piece, grid, lock_delay, classic_lock)
|
||||
end
|
||||
|
||||
function Rotation:canPieceMove(piece, grid) return true end
|
||||
function Rotation:canPieceRotate(piece, grid) return true end
|
||||
function Rotation:onPieceMove(piece) end
|
||||
function Rotation:onPieceRotate(piece) end
|
||||
function Rotation:onPieceDrop(piece)
|
||||
if piece.position.y > piece.lowest_point then
|
||||
piece.lock_delay = 0
|
||||
piece.lowest_point = piece.position.y
|
||||
end
|
||||
end
|
||||
|
||||
return Rotation
|
||||
566
libs/bigint/bigint.lua
Normal file
@@ -0,0 +1,566 @@
|
||||
#!/usr/bin/env lua
|
||||
-- If this variable is true, then strict type checking is performed for all
|
||||
-- operations. This may result in slower code, but it will allow you to catch
|
||||
-- errors and bugs earlier.
|
||||
local strict = false
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local bigint = {}
|
||||
|
||||
local mt = {
|
||||
__add = function(lhs, rhs)
|
||||
return bigint.add(lhs, rhs)
|
||||
end,
|
||||
__unm = function(arg)
|
||||
return bigint.negate(arg)
|
||||
end,
|
||||
__sub = function(lhs, rhs)
|
||||
return bigint.subtract(lhs, rhs)
|
||||
end,
|
||||
__mul = function(lhs, rhs)
|
||||
return bigint.multiply(lhs, rhs)
|
||||
end,
|
||||
__div = function(lhs, rhs)
|
||||
return bigint.divide(lhs, rhs)
|
||||
end,
|
||||
__mod = function(lhs, rhs)
|
||||
return bigint.modulus(lhs, rhs)
|
||||
end,
|
||||
__pow = function(lhs, rhs)
|
||||
return bigint.exponentiate(lhs, rhs)
|
||||
end,
|
||||
__tostring = function(arg)
|
||||
return bigint.unserialize(arg, "s")
|
||||
end,
|
||||
__eq = function(lhs, rhs)
|
||||
return bigint.compare(lhs, rhs, "==")
|
||||
end,
|
||||
__lt = function(lhs, rhs)
|
||||
return bigint.compare(lhs, rhs, "<")
|
||||
end,
|
||||
__le = function(lhs, rhs)
|
||||
return bigint.compare(lhs, rhs, "<=")
|
||||
end
|
||||
}
|
||||
|
||||
local named_powers = require("libs.bigint.named-powers-of-ten")
|
||||
|
||||
-- Create a new bigint or convert a number or string into a big
|
||||
-- Returns an empty, positive bigint if no number or string is given
|
||||
function bigint.new(num)
|
||||
local self = {
|
||||
sign = "+",
|
||||
digits = {}
|
||||
}
|
||||
|
||||
-- Return a new bigint with the same sign and digits
|
||||
function self:clone()
|
||||
local newint = bigint.new()
|
||||
newint.sign = self.sign
|
||||
for _, digit in pairs(self.digits) do
|
||||
newint.digits[#newint.digits + 1] = digit
|
||||
end
|
||||
return newint
|
||||
end
|
||||
|
||||
setmetatable(self, mt)
|
||||
|
||||
if (num) then
|
||||
local num_string = tostring(num)
|
||||
for digit in string.gmatch(num_string, "[0-9]") do
|
||||
table.insert(self.digits, tonumber(digit))
|
||||
end
|
||||
if string.sub(num_string, 1, 1) == "-" then
|
||||
self.sign = "-"
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- Check the type of a big
|
||||
-- Normally only runs when global variable "strict" == true, but checking can be
|
||||
-- forced by supplying "true" as the second argument.
|
||||
function bigint.check(big, force)
|
||||
if (strict or force) then
|
||||
assert(getmetatable(big) == mt, "at least one arg is not a bigint")
|
||||
assert(#big.digits > 0, "bigint is empty")
|
||||
assert(big.sign == "+" or big.sign == "-", "bigint is unsigned")
|
||||
for _, digit in pairs(big.digits) do
|
||||
assert(type(digit) == "number", "at least one digit is invalid")
|
||||
assert(digit <= 9 and digit >= 0, digit .. " is not between 0 and 9")
|
||||
assert(math.floor(digit) == digit, digit .. " is not an integer")
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Return a new big with the same digits but with a positive sign (absolute
|
||||
-- value)
|
||||
function bigint.abs(big)
|
||||
bigint.check(big)
|
||||
local result = big:clone()
|
||||
result.sign = "+"
|
||||
return result
|
||||
end
|
||||
|
||||
-- Return a new big with the same digits but the opposite sign (negation)
|
||||
function bigint.negate(big)
|
||||
bigint.check(big)
|
||||
local result = big:clone()
|
||||
if (result.sign == "+") then
|
||||
result.sign = "-"
|
||||
else
|
||||
result.sign = "+"
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- Return the number of digits in the big
|
||||
function bigint.digits(big)
|
||||
bigint.check(big)
|
||||
return #big.digits
|
||||
end
|
||||
|
||||
-- Convert a big to a number or string
|
||||
function bigint.unserialize(big, output_type, precision)
|
||||
bigint.check(big)
|
||||
|
||||
local num = ""
|
||||
if big.sign == "-" then
|
||||
num = "-"
|
||||
end
|
||||
|
||||
|
||||
if ((output_type == nil)
|
||||
or (output_type == "number")
|
||||
or (output_type == "n")
|
||||
or (output_type == "string")
|
||||
or (output_type == "s")) then
|
||||
-- Unserialization to a string or number requires reconstructing the
|
||||
-- entire number
|
||||
|
||||
for _, digit in pairs(big.digits) do
|
||||
num = num .. math.floor(digit) -- lazy way of getting rid of .0$
|
||||
end
|
||||
|
||||
if ((output_type == nil)
|
||||
or (output_type == "number")
|
||||
or (output_type == "n")) then
|
||||
return tonumber(num)
|
||||
else
|
||||
return num
|
||||
end
|
||||
|
||||
else
|
||||
-- Unserialization to human-readable form or scientific notation only
|
||||
-- requires reading the first few digits
|
||||
if (precision == nil) then
|
||||
precision = math.min(#big.digits, 3)
|
||||
else
|
||||
assert(precision > 0, "Precision cannot be less than 1")
|
||||
assert(math.floor(precision) == precision,
|
||||
"Precision must be a positive integer")
|
||||
end
|
||||
|
||||
-- num is the first (precision + 1) digits, the first being separated by
|
||||
-- a decimal point from the others
|
||||
num = num .. math.floor(big.digits[1])
|
||||
if (precision > 1) then
|
||||
num = num .. "."
|
||||
for i = 1, (precision - 1) do
|
||||
num = num .. math.floor(big.digits[i + 1])
|
||||
end
|
||||
end
|
||||
|
||||
if ((output_type == "human-readable")
|
||||
or (output_type == "human")
|
||||
or (output_type == "h"))
|
||||
and (#big.digits >= 3 and #big.digits <= 10002) then
|
||||
-- Human-readable output contributed by 123eee555
|
||||
|
||||
local name
|
||||
local walkback = 0 -- Used to enumerate "ten", "hundred", etc
|
||||
|
||||
-- Walk backwards in the index of named_powers starting at the
|
||||
-- number of digits of the input until the first value is found
|
||||
for i = (#big.digits - 1), (#big.digits - 4), -1 do
|
||||
name = named_powers[i]
|
||||
if (name) then
|
||||
if (walkback == 1) then
|
||||
name = "ten " .. name
|
||||
elseif (walkback == 2) then
|
||||
name = "hundred " .. name
|
||||
end
|
||||
break
|
||||
else
|
||||
walkback = walkback + 1
|
||||
end
|
||||
end
|
||||
|
||||
return num .. " " .. name
|
||||
|
||||
else
|
||||
return num .. "*10^" .. (#big.digits - 1)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- Basic comparisons
|
||||
-- Accepts symbols (<, >=, ~=) and Unix shell-like options (lt, ge, ne)
|
||||
function bigint.compare(big1, big2, comparison)
|
||||
bigint.check(big1)
|
||||
bigint.check(big2)
|
||||
|
||||
local greater = false -- If big1.digits > big2.digits
|
||||
local equal = false
|
||||
|
||||
if (big1.sign == "-") and (big2.sign == "+") then
|
||||
greater = false
|
||||
elseif (#big1.digits > #big2.digits)
|
||||
or ((big1.sign == "+") and (big2.sign == "-")) then
|
||||
greater = true
|
||||
elseif (#big1.digits == #big2.digits) then
|
||||
-- Walk left to right, comparing digits
|
||||
for digit = 1, #big1.digits do
|
||||
if (big1.digits[digit] > big2.digits[digit]) then
|
||||
greater = true
|
||||
break
|
||||
elseif (big2.digits[digit] > big1.digits[digit]) then
|
||||
break
|
||||
elseif (digit == #big1.digits)
|
||||
and (big1.digits[digit] == big2.digits[digit]) then
|
||||
equal = true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- If both numbers are negative, then the requirements for greater are
|
||||
-- reversed
|
||||
if (not equal) and (big1.sign == "-") and (big2.sign == "-") then
|
||||
greater = not greater
|
||||
end
|
||||
|
||||
return (((comparison == "<") or (comparison == "lt"))
|
||||
and ((not greater) and (not equal)) and true)
|
||||
or (((comparison == ">") or (comparison == "gt"))
|
||||
and ((greater) and (not equal)) and true)
|
||||
or (((comparison == "==") or (comparison == "eq"))
|
||||
and (equal) and true)
|
||||
or (((comparison == ">=") or (comparison == "ge"))
|
||||
and (equal or greater) and true)
|
||||
or (((comparison == "<=") or (comparison == "le"))
|
||||
and (equal or not greater) and true)
|
||||
or (((comparison == "~=") or (comparison == "!=") or (comparison == "ne"))
|
||||
and (not equal) and true)
|
||||
or false
|
||||
end
|
||||
|
||||
-- BACKEND: Add big1 and big2, ignoring signs
|
||||
function bigint.add_raw(big1, big2)
|
||||
bigint.check(big1)
|
||||
bigint.check(big2)
|
||||
|
||||
local result = bigint.new()
|
||||
local max_digits = 0
|
||||
local carry = 0
|
||||
|
||||
if (#big1.digits >= #big2.digits) then
|
||||
max_digits = #big1.digits
|
||||
else
|
||||
max_digits = #big2.digits
|
||||
end
|
||||
|
||||
-- Walk backwards right to left, like in long addition
|
||||
for digit = 0, max_digits - 1 do
|
||||
local sum = (big1.digits[#big1.digits - digit] or 0)
|
||||
+ (big2.digits[#big2.digits - digit] or 0)
|
||||
+ carry
|
||||
|
||||
if (sum >= 10) then
|
||||
carry = 1
|
||||
sum = sum - 10
|
||||
else
|
||||
carry = 0
|
||||
end
|
||||
|
||||
result.digits[max_digits - digit] = sum
|
||||
end
|
||||
|
||||
-- Leftover carry in cases when #big1.digits == #big2.digits and sum > 10, ex. 7 + 9
|
||||
if (carry == 1) then
|
||||
table.insert(result.digits, 1, 1)
|
||||
end
|
||||
|
||||
return result
|
||||
|
||||
end
|
||||
|
||||
-- BACKEND: Subtract big2 from big1, ignoring signs
|
||||
function bigint.subtract_raw(big1, big2)
|
||||
-- Type checking is done by bigint.compare
|
||||
assert(bigint.compare(bigint.abs(big1), bigint.abs(big2), ">="),
|
||||
"Size of " .. bigint.unserialize(big1, "string") .. " is less than "
|
||||
.. bigint.unserialize(big2, "string"))
|
||||
|
||||
local result = big1:clone()
|
||||
local max_digits = #big1.digits
|
||||
local borrow = 0
|
||||
|
||||
-- Logic mostly copied from bigint.add_raw ---------------------------------
|
||||
-- Walk backwards right to left, like in long subtraction
|
||||
for digit = 0, max_digits - 1 do
|
||||
local diff = (big1.digits[#big1.digits - digit] or 0)
|
||||
- (big2.digits[#big2.digits - digit] or 0)
|
||||
- borrow
|
||||
|
||||
if (diff < 0) then
|
||||
borrow = 1
|
||||
diff = diff + 10
|
||||
else
|
||||
borrow = 0
|
||||
end
|
||||
|
||||
result.digits[max_digits - digit] = diff
|
||||
end
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
|
||||
-- Strip leading zeroes if any, but not if 0 is the only digit
|
||||
while (#result.digits > 1) and (result.digits[1] == 0) do
|
||||
table.remove(result.digits, 1)
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-- FRONTEND: Addition and subtraction operations, accounting for signs
|
||||
function bigint.add(big1, big2)
|
||||
-- Type checking is done by bigint.compare
|
||||
|
||||
local result
|
||||
|
||||
-- If adding numbers of different sign, subtract the smaller sized one from
|
||||
-- the bigger sized one and take the sign of the bigger sized one
|
||||
if (big1.sign ~= big2.sign) then
|
||||
if (bigint.compare(bigint.abs(big1), bigint.abs(big2), ">")) then
|
||||
result = bigint.subtract_raw(big1, big2)
|
||||
result.sign = big1.sign
|
||||
else
|
||||
result = bigint.subtract_raw(big2, big1)
|
||||
result.sign = big2.sign
|
||||
end
|
||||
|
||||
elseif (big1.sign == "+") and (big2.sign == "+") then
|
||||
result = bigint.add_raw(big1, big2)
|
||||
|
||||
elseif (big1.sign == "-") and (big2.sign == "-") then
|
||||
result = bigint.add_raw(big1, big2)
|
||||
result.sign = "-"
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
function bigint.subtract(big1, big2)
|
||||
-- Type checking is done by bigint.compare in bigint.add
|
||||
-- Subtracting is like adding a negative
|
||||
local big2_local = big2:clone()
|
||||
if (big2.sign == "+") then
|
||||
big2_local.sign = "-"
|
||||
else
|
||||
big2_local.sign = "+"
|
||||
end
|
||||
return bigint.add(big1, big2_local)
|
||||
end
|
||||
|
||||
-- BACKEND: Multiply a big by a single digit big, ignoring signs
|
||||
function bigint.multiply_single(big1, big2)
|
||||
bigint.check(big1)
|
||||
bigint.check(big2)
|
||||
assert(#big2.digits == 1, bigint.unserialize(big2, "string")
|
||||
.. " has more than one digit")
|
||||
|
||||
local result = bigint.new()
|
||||
local carry = 0
|
||||
|
||||
-- Logic mostly copied from bigint.add_raw ---------------------------------
|
||||
-- Walk backwards right to left, like in long multiplication
|
||||
for digit = 0, #big1.digits - 1 do
|
||||
local this_digit = big1.digits[#big1.digits - digit]
|
||||
* big2.digits[1]
|
||||
+ carry
|
||||
|
||||
if (this_digit >= 10) then
|
||||
carry = math.floor(this_digit / 10)
|
||||
this_digit = this_digit - (carry * 10)
|
||||
else
|
||||
carry = 0
|
||||
end
|
||||
|
||||
result.digits[#big1.digits - digit] = this_digit
|
||||
end
|
||||
|
||||
-- Leftover carry in cases when big1.digits[1] * big2.digits[1] > 0
|
||||
if (carry > 0) then
|
||||
table.insert(result.digits, 1, carry)
|
||||
end
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-- FRONTEND: Multiply two bigs, accounting for signs
|
||||
function bigint.multiply(big1, big2)
|
||||
-- Type checking done by bigint.multiply_single
|
||||
|
||||
local result = bigint.new(0)
|
||||
local larger, smaller -- Larger and smaller in terms of digits, not size
|
||||
|
||||
if (bigint.unserialize(big1) == 0) or (bigint.unserialize(big2) == 0) then
|
||||
return result
|
||||
end
|
||||
|
||||
if (#big1.digits >= #big2.digits) then
|
||||
larger = big1
|
||||
smaller = big2
|
||||
else
|
||||
larger = big2
|
||||
smaller = big1
|
||||
end
|
||||
|
||||
-- Walk backwards right to left, like in long multiplication
|
||||
for digit = 0, #smaller.digits - 1 do
|
||||
-- Sorry for going over column 80! There's lots of big names here
|
||||
local this_digit_product = bigint.multiply_single(larger,
|
||||
bigint.new(smaller.digits[#smaller.digits - digit]))
|
||||
|
||||
-- "Placeholding zeroes"
|
||||
if (digit > 0) then
|
||||
for placeholder = 1, digit do
|
||||
table.insert(this_digit_product.digits, 0)
|
||||
end
|
||||
end
|
||||
|
||||
result = bigint.add(result, this_digit_product)
|
||||
end
|
||||
|
||||
if (larger.sign == smaller.sign) then
|
||||
result.sign = "+"
|
||||
else
|
||||
result.sign = "-"
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-- Raise a big to a positive integer or big power (TODO: negative integer power)
|
||||
function bigint.exponentiate(big, power)
|
||||
-- Type checking for big done by bigint.multiply
|
||||
assert(bigint.compare(power, bigint.new(0), ">="),
|
||||
" negative powers are not supported")
|
||||
local exp = power:clone()
|
||||
|
||||
if (bigint.compare(exp, bigint.new(0), "==")) then
|
||||
return bigint.new(1)
|
||||
elseif (bigint.compare(exp, bigint.new(1), "==")) then
|
||||
return big:clone()
|
||||
else
|
||||
local result = bigint.new(1)
|
||||
local base = big:clone()
|
||||
|
||||
while (true) do
|
||||
if (bigint.compare(
|
||||
bigint.modulus(exp, bigint.new(2)), bigint.new(1), "=="
|
||||
)) then
|
||||
result = bigint.multiply(result, base)
|
||||
end
|
||||
if (bigint.compare(exp, bigint.new(1), "==")) then
|
||||
break
|
||||
else
|
||||
exp = bigint.divide(exp, bigint.new(2))
|
||||
base = bigint.multiply(base, base)
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- BACKEND: Divide two bigs (decimals not supported), returning big result and
|
||||
-- big remainder
|
||||
-- WARNING: Only supports positive integers
|
||||
function bigint.divide_raw(big1, big2)
|
||||
-- Type checking done by bigint.compare
|
||||
if (bigint.compare(big1, big2, "==")) then
|
||||
return bigint.new(1), bigint.new(0)
|
||||
elseif (bigint.compare(big1, big2, "<")) then
|
||||
return bigint.new(0), big1:clone()
|
||||
else
|
||||
assert(bigint.compare(big2, bigint.new(0), "!="), "error: divide by zero")
|
||||
assert(big1.sign == "+", "error: big1 is not positive")
|
||||
assert(big2.sign == "+", "error: big2 is not positive")
|
||||
|
||||
local result = bigint.new()
|
||||
|
||||
local dividend = bigint.new() -- Dividend of a single operation
|
||||
|
||||
local neg_zero = bigint.new(0)
|
||||
neg_zero.sign = "-"
|
||||
|
||||
for i = 1, #big1.digits do
|
||||
-- Fixes a negative zero bug
|
||||
if (#dividend.digits ~= 0) and (bigint.compare(dividend, neg_zero, "==")) then
|
||||
dividend = bigint.new()
|
||||
end
|
||||
|
||||
table.insert(dividend.digits, big1.digits[i])
|
||||
|
||||
local factor = bigint.new(0)
|
||||
while bigint.compare(dividend, big2, ">=") do
|
||||
dividend = bigint.subtract(dividend, big2)
|
||||
factor = bigint.add(factor, bigint.new(1))
|
||||
end
|
||||
|
||||
for i = 0, #factor.digits - 1 do
|
||||
result.digits[#result.digits + 1 - i] = factor.digits[i + 1]
|
||||
end
|
||||
end
|
||||
|
||||
-- Remove leading zeros from result
|
||||
while (result.digits[1] == 0) do
|
||||
table.remove(result.digits, 1)
|
||||
end
|
||||
|
||||
return result, dividend
|
||||
end
|
||||
end
|
||||
|
||||
-- FRONTEND: Divide two bigs (decimals not supported), returning big result and
|
||||
-- big remainder, accounting for signs
|
||||
function bigint.divide(big1, big2)
|
||||
local result, remainder = bigint.divide_raw(bigint.abs(big1),
|
||||
bigint.abs(big2))
|
||||
if (big1.sign == big2.sign) then
|
||||
result.sign = "+"
|
||||
else
|
||||
result.sign = "-"
|
||||
end
|
||||
|
||||
return result, remainder
|
||||
end
|
||||
|
||||
-- FRONTEND: Return only the remainder from bigint.divide
|
||||
function bigint.modulus(big1, big2)
|
||||
local result, remainder = bigint.divide(big1, big2)
|
||||
|
||||
-- Remainder will always have the same sign as the dividend per C standard
|
||||
-- https://en.wikipedia.org/wiki/Modulo_operation#Remainder_calculation_for_the_modulo_operation
|
||||
remainder.sign = big1.sign
|
||||
return remainder
|
||||
end
|
||||
|
||||
return bigint
|
||||
3340
libs/bigint/named-powers-of-ten.lua
Normal file
689
libs/binser.lua
Normal file
@@ -0,0 +1,689 @@
|
||||
-- binser.lua
|
||||
|
||||
--[[
|
||||
Copyright (c) 2016 Calvin Rose
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
]]
|
||||
|
||||
local assert = assert
|
||||
local error = error
|
||||
local select = select
|
||||
local pairs = pairs
|
||||
local getmetatable = getmetatable
|
||||
local setmetatable = setmetatable
|
||||
local tonumber = tonumber
|
||||
local type = type
|
||||
local loadstring = loadstring or load
|
||||
local concat = table.concat
|
||||
local char = string.char
|
||||
local byte = string.byte
|
||||
local format = string.format
|
||||
local sub = string.sub
|
||||
local dump = string.dump
|
||||
local floor = math.floor
|
||||
local frexp = math.frexp
|
||||
local unpack = unpack or table.unpack
|
||||
|
||||
-- Lua 5.3 frexp polyfill
|
||||
-- From https://github.com/excessive/cpml/blob/master/modules/utils.lua
|
||||
if not frexp then
|
||||
local log, abs, floor = math.log, math.abs, math.floor
|
||||
local log2 = log(2)
|
||||
frexp = function(x)
|
||||
if x == 0 then return 0, 0 end
|
||||
local e = floor(log(abs(x)) / log2 + 1)
|
||||
return x / 2 ^ e, e
|
||||
end
|
||||
end
|
||||
|
||||
-- NIL = 202
|
||||
-- FLOAT = 203
|
||||
-- TRUE = 204
|
||||
-- FALSE = 205
|
||||
-- STRING = 206
|
||||
-- TABLE = 207
|
||||
-- REFERENCE = 208
|
||||
-- CONSTRUCTOR = 209
|
||||
-- FUNCTION = 210
|
||||
-- RESOURCE = 211
|
||||
-- INT64 = 212
|
||||
|
||||
local mts = {}
|
||||
local ids = {}
|
||||
local serializers = {}
|
||||
local deserializers = {}
|
||||
local resources = {}
|
||||
local resources_by_name = {}
|
||||
|
||||
local function pack(...)
|
||||
return {...}, select("#", ...)
|
||||
end
|
||||
|
||||
local function not_array_index(x, len)
|
||||
return type(x) ~= "number" or x < 1 or x > len or x ~= floor(x)
|
||||
end
|
||||
|
||||
local function type_check(x, tp, name)
|
||||
assert(type(x) == tp,
|
||||
format("Expected parameter %q to be of type %q.", name, tp))
|
||||
end
|
||||
|
||||
local bigIntSupport = false
|
||||
local isInteger
|
||||
if math.type then -- Detect Lua 5.3
|
||||
local mtype = math.type
|
||||
bigIntSupport = loadstring[[
|
||||
local char = string.char
|
||||
return function(n)
|
||||
local nn = n < 0 and -(n + 1) or n
|
||||
local b1 = nn // 0x100000000000000
|
||||
local b2 = nn // 0x1000000000000 % 0x100
|
||||
local b3 = nn // 0x10000000000 % 0x100
|
||||
local b4 = nn // 0x100000000 % 0x100
|
||||
local b5 = nn // 0x1000000 % 0x100
|
||||
local b6 = nn // 0x10000 % 0x100
|
||||
local b7 = nn // 0x100 % 0x100
|
||||
local b8 = nn % 0x100
|
||||
if n < 0 then
|
||||
b1, b2, b3, b4 = 0xFF - b1, 0xFF - b2, 0xFF - b3, 0xFF - b4
|
||||
b5, b6, b7, b8 = 0xFF - b5, 0xFF - b6, 0xFF - b7, 0xFF - b8
|
||||
end
|
||||
return char(212, b1, b2, b3, b4, b5, b6, b7, b8)
|
||||
end]]()
|
||||
isInteger = function(x)
|
||||
return mtype(x) == 'integer'
|
||||
end
|
||||
else
|
||||
isInteger = function(x)
|
||||
return floor(x) == x
|
||||
end
|
||||
end
|
||||
|
||||
-- Copyright (C) 2012-2015 Francois Perrad.
|
||||
-- number serialization code modified from https://github.com/fperrad/lua-MessagePack
|
||||
-- Encode a number as a big-endian ieee-754 double, big-endian signed 64 bit integer, or a small integer
|
||||
local function number_to_str(n)
|
||||
if isInteger(n) then -- int
|
||||
if n <= 100 and n >= -27 then -- 1 byte, 7 bits of data
|
||||
return char(n + 27)
|
||||
elseif n <= 8191 and n >= -8192 then -- 2 bytes, 14 bits of data
|
||||
n = n + 8192
|
||||
return char(128 + (floor(n / 0x100) % 0x100), n % 0x100)
|
||||
elseif bigIntSupport then
|
||||
return bigIntSupport(n)
|
||||
end
|
||||
end
|
||||
local sign = 0
|
||||
if n < 0.0 then
|
||||
sign = 0x80
|
||||
n = -n
|
||||
end
|
||||
local m, e = frexp(n) -- mantissa, exponent
|
||||
if m ~= m then
|
||||
return char(203, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
|
||||
elseif m == 1/0 then
|
||||
if sign == 0 then
|
||||
return char(203, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
|
||||
else
|
||||
return char(203, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
|
||||
end
|
||||
end
|
||||
e = e + 0x3FE
|
||||
if e < 1 then -- denormalized numbers
|
||||
m = m * 2 ^ (52 + e)
|
||||
e = 0
|
||||
else
|
||||
m = (m * 2 - 1) * 2 ^ 52
|
||||
end
|
||||
return char(203,
|
||||
sign + floor(e / 0x10),
|
||||
(e % 0x10) * 0x10 + floor(m / 0x1000000000000),
|
||||
floor(m / 0x10000000000) % 0x100,
|
||||
floor(m / 0x100000000) % 0x100,
|
||||
floor(m / 0x1000000) % 0x100,
|
||||
floor(m / 0x10000) % 0x100,
|
||||
floor(m / 0x100) % 0x100,
|
||||
m % 0x100)
|
||||
end
|
||||
|
||||
-- Copyright (C) 2012-2015 Francois Perrad.
|
||||
-- number deserialization code also modified from https://github.com/fperrad/lua-MessagePack
|
||||
local function number_from_str(str, index)
|
||||
local b = byte(str, index)
|
||||
if b < 128 then
|
||||
return b - 27, index + 1
|
||||
elseif b < 192 then
|
||||
return byte(str, index + 1) + 0x100 * (b - 128) - 8192, index + 2
|
||||
end
|
||||
local b1, b2, b3, b4, b5, b6, b7, b8 = byte(str, index + 1, index + 8)
|
||||
if b == 212 then
|
||||
local flip = b1 >= 128
|
||||
if flip then -- negative
|
||||
b1, b2, b3, b4 = 0xFF - b1, 0xFF - b2, 0xFF - b3, 0xFF - b4
|
||||
b5, b6, b7, b8 = 0xFF - b5, 0xFF - b6, 0xFF - b7, 0xFF - b8
|
||||
end
|
||||
local n = ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
|
||||
if flip then
|
||||
return (-n) - 1, index + 9
|
||||
else
|
||||
return n, index + 9
|
||||
end
|
||||
end
|
||||
local sign = b1 > 0x7F and -1 or 1
|
||||
local e = (b1 % 0x80) * 0x10 + floor(b2 / 0x10)
|
||||
local m = ((((((b2 % 0x10) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
|
||||
local n
|
||||
if e == 0 then
|
||||
if m == 0 then
|
||||
n = sign * 0.0
|
||||
else
|
||||
n = sign * (m / 2 ^ 52) * 2 ^ -1022
|
||||
end
|
||||
elseif e == 0x7FF then
|
||||
if m == 0 then
|
||||
n = sign * (1/0)
|
||||
else
|
||||
n = 0.0/0.0
|
||||
end
|
||||
else
|
||||
n = sign * (1.0 + m / 2 ^ 52) * 2 ^ (e - 0x3FF)
|
||||
end
|
||||
return n, index + 9
|
||||
end
|
||||
|
||||
local types = {}
|
||||
|
||||
types["nil"] = function(x, visited, accum)
|
||||
accum[#accum + 1] = "\202"
|
||||
end
|
||||
|
||||
function types.number(x, visited, accum)
|
||||
accum[#accum + 1] = number_to_str(x)
|
||||
end
|
||||
|
||||
function types.boolean(x, visited, accum)
|
||||
accum[#accum + 1] = x and "\204" or "\205"
|
||||
end
|
||||
|
||||
function types.string(x, visited, accum)
|
||||
local alen = #accum
|
||||
if visited[x] then
|
||||
accum[alen + 1] = "\208"
|
||||
accum[alen + 2] = number_to_str(visited[x])
|
||||
else
|
||||
visited[x] = visited.next
|
||||
visited.next = visited.next + 1
|
||||
accum[alen + 1] = "\206"
|
||||
accum[alen + 2] = number_to_str(#x)
|
||||
accum[alen + 3] = x
|
||||
end
|
||||
end
|
||||
|
||||
local function check_custom_type(x, visited, accum)
|
||||
local res = resources[x]
|
||||
if res then
|
||||
accum[#accum + 1] = "\211"
|
||||
types[type(res)](res, visited, accum)
|
||||
return true
|
||||
end
|
||||
local mt = getmetatable(x)
|
||||
local id = mt and ids[mt]
|
||||
if id then
|
||||
if x == visited.temp then
|
||||
error("Infinite loop in constructor.")
|
||||
end
|
||||
visited.temp = x
|
||||
accum[#accum + 1] = "\209"
|
||||
types[type(id)](id, visited, accum)
|
||||
local args, len = pack(serializers[id](x))
|
||||
accum[#accum + 1] = number_to_str(len)
|
||||
for i = 1, len do
|
||||
local arg = args[i]
|
||||
types[type(arg)](arg, visited, accum)
|
||||
end
|
||||
visited[x] = visited.next
|
||||
visited.next = visited.next + 1
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function types.userdata(x, visited, accum)
|
||||
if visited[x] then
|
||||
accum[#accum + 1] = "\208"
|
||||
accum[#accum + 1] = number_to_str(visited[x])
|
||||
else
|
||||
if check_custom_type(x, visited, accum) then return end
|
||||
error("Cannot serialize this userdata.")
|
||||
end
|
||||
end
|
||||
|
||||
function types.table(x, visited, accum)
|
||||
if visited[x] then
|
||||
accum[#accum + 1] = "\208"
|
||||
accum[#accum + 1] = number_to_str(visited[x])
|
||||
else
|
||||
if check_custom_type(x, visited, accum) then return end
|
||||
visited[x] = visited.next
|
||||
visited.next = visited.next + 1
|
||||
local xlen = #x
|
||||
accum[#accum + 1] = "\207"
|
||||
accum[#accum + 1] = number_to_str(xlen)
|
||||
for i = 1, xlen do
|
||||
local v = x[i]
|
||||
types[type(v)](v, visited, accum)
|
||||
end
|
||||
local key_count = 0
|
||||
for k in pairs(x) do
|
||||
if not_array_index(k, xlen) then
|
||||
key_count = key_count + 1
|
||||
end
|
||||
end
|
||||
accum[#accum + 1] = number_to_str(key_count)
|
||||
for k, v in pairs(x) do
|
||||
if not_array_index(k, xlen) then
|
||||
types[type(k)](k, visited, accum)
|
||||
types[type(v)](v, visited, accum)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
types["function"] = function(x, visited, accum)
|
||||
if visited[x] then
|
||||
accum[#accum + 1] = "\208"
|
||||
accum[#accum + 1] = number_to_str(visited[x])
|
||||
else
|
||||
if check_custom_type(x, visited, accum) then return end
|
||||
visited[x] = visited.next
|
||||
visited.next = visited.next + 1
|
||||
local str = dump(x)
|
||||
accum[#accum + 1] = "\210"
|
||||
accum[#accum + 1] = number_to_str(#str)
|
||||
accum[#accum + 1] = str
|
||||
end
|
||||
end
|
||||
|
||||
types.cdata = function(x, visited, accum)
|
||||
if visited[x] then
|
||||
accum[#accum + 1] = "\208"
|
||||
accum[#accum + 1] = number_to_str(visited[x])
|
||||
else
|
||||
if check_custom_type(x, visited, #accum) then return end
|
||||
error("Cannot serialize this cdata.")
|
||||
end
|
||||
end
|
||||
|
||||
types.thread = function() error("Cannot serialize threads.") end
|
||||
|
||||
local function deserialize_value(str, index, visited)
|
||||
local t = byte(str, index)
|
||||
if not t then return end
|
||||
if t < 128 then
|
||||
return t - 27, index + 1
|
||||
elseif t < 192 then
|
||||
return byte(str, index + 1) + 0x100 * (t - 128) - 8192, index + 2
|
||||
elseif t == 202 then
|
||||
return nil, index + 1
|
||||
elseif t == 203 then
|
||||
return number_from_str(str, index)
|
||||
elseif t == 204 then
|
||||
return true, index + 1
|
||||
elseif t == 205 then
|
||||
return false, index + 1
|
||||
elseif t == 206 then
|
||||
local length, dataindex = deserialize_value(str, index + 1, visited)
|
||||
local nextindex = dataindex + length
|
||||
local substr = sub(str, dataindex, nextindex - 1)
|
||||
visited[#visited + 1] = substr
|
||||
return substr, nextindex
|
||||
elseif t == 207 then
|
||||
local count, nextindex = number_from_str(str, index + 1)
|
||||
local ret = {}
|
||||
visited[#visited + 1] = ret
|
||||
for i = 1, count do
|
||||
ret[i], nextindex = deserialize_value(str, nextindex, visited)
|
||||
end
|
||||
count, nextindex = number_from_str(str, nextindex)
|
||||
for i = 1, count do
|
||||
local k, v
|
||||
k, nextindex = deserialize_value(str, nextindex, visited)
|
||||
v, nextindex = deserialize_value(str, nextindex, visited)
|
||||
ret[k] = v
|
||||
end
|
||||
return ret, nextindex
|
||||
elseif t == 208 then
|
||||
local ref, nextindex = number_from_str(str, index + 1)
|
||||
return visited[ref], nextindex
|
||||
elseif t == 209 then
|
||||
local count
|
||||
local name, nextindex = deserialize_value(str, index + 1, visited)
|
||||
count, nextindex = number_from_str(str, nextindex)
|
||||
local args = {}
|
||||
for i = 1, count do
|
||||
args[i], nextindex = deserialize_value(str, nextindex, visited)
|
||||
end
|
||||
local ret = deserializers[name](unpack(args))
|
||||
visited[#visited + 1] = ret
|
||||
return ret, nextindex
|
||||
elseif t == 210 then
|
||||
local length, dataindex = deserialize_value(str, index + 1, visited)
|
||||
local nextindex = dataindex + length
|
||||
local ret = loadstring(sub(str, dataindex, nextindex - 1))
|
||||
visited[#visited + 1] = ret
|
||||
return ret, nextindex
|
||||
elseif t == 211 then
|
||||
local res, nextindex = deserialize_value(str, index + 1, visited)
|
||||
return resources_by_name[res], nextindex
|
||||
elseif t == 212 then
|
||||
return number_from_str(str, index)
|
||||
else
|
||||
error("Could not deserialize type byte " .. t .. ".")
|
||||
end
|
||||
end
|
||||
|
||||
local function serialize(...)
|
||||
local visited = {next = 1}
|
||||
local accum = {}
|
||||
for i = 1, select("#", ...) do
|
||||
local x = select(i, ...)
|
||||
types[type(x)](x, visited, accum)
|
||||
end
|
||||
return concat(accum)
|
||||
end
|
||||
|
||||
local function make_file_writer(file)
|
||||
return setmetatable({}, {
|
||||
__newindex = function(_, _, v)
|
||||
file:write(v)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
local function serialize_to_file(path, mode, ...)
|
||||
local file, err = io.open(path, mode)
|
||||
assert(file, err)
|
||||
local visited = {next = 1}
|
||||
local accum = make_file_writer(file)
|
||||
for i = 1, select("#", ...) do
|
||||
local x = select(i, ...)
|
||||
types[type(x)](x, visited, accum)
|
||||
end
|
||||
-- flush the writer
|
||||
file:flush()
|
||||
file:close()
|
||||
end
|
||||
|
||||
local function writeFile(path, ...)
|
||||
return serialize_to_file(path, "wb", ...)
|
||||
end
|
||||
|
||||
local function appendFile(path, ...)
|
||||
return serialize_to_file(path, "ab", ...)
|
||||
end
|
||||
|
||||
local function deserialize(str, index)
|
||||
assert(type(str) == "string", "Expected string to deserialize.")
|
||||
local vals = {}
|
||||
index = index or 1
|
||||
local visited = {}
|
||||
local len = 0
|
||||
local val
|
||||
while index do
|
||||
val, index = deserialize_value(str, index, visited)
|
||||
if index then
|
||||
len = len + 1
|
||||
vals[len] = val
|
||||
end
|
||||
end
|
||||
return vals, len
|
||||
end
|
||||
|
||||
local function deserializeN(str, n, index)
|
||||
assert(type(str) == "string", "Expected string to deserialize.")
|
||||
n = n or 1
|
||||
assert(type(n) == "number", "Expected a number for parameter n.")
|
||||
assert(n > 0 and floor(n) == n, "N must be a poitive integer.")
|
||||
local vals = {}
|
||||
index = index or 1
|
||||
local visited = {}
|
||||
local len = 0
|
||||
local val
|
||||
while index and len < n do
|
||||
val, index = deserialize_value(str, index, visited)
|
||||
if index then
|
||||
len = len + 1
|
||||
vals[len] = val
|
||||
end
|
||||
end
|
||||
vals[len + 1] = index
|
||||
return unpack(vals, 1, n + 1)
|
||||
end
|
||||
|
||||
local function readFile(path)
|
||||
local file, err = io.open(path, "rb")
|
||||
if file == nil then
|
||||
return nil, 0
|
||||
end
|
||||
local str = file:read("*all")
|
||||
file:close()
|
||||
return deserialize(str)
|
||||
end
|
||||
|
||||
local function default_deserialize(metatable)
|
||||
return function(...)
|
||||
local ret = {}
|
||||
for i = 1, select("#", ...), 2 do
|
||||
ret[select(i, ...)] = select(i + 1, ...)
|
||||
end
|
||||
return setmetatable(ret, metatable)
|
||||
end
|
||||
end
|
||||
|
||||
local function default_serialize(x)
|
||||
assert(type(x) == "table",
|
||||
"Default serialization for custom types only works for tables.")
|
||||
local args = {}
|
||||
local len = 0
|
||||
for k, v in pairs(x) do
|
||||
args[len + 1], args[len + 2] = k, v
|
||||
len = len + 2
|
||||
end
|
||||
return unpack(args, 1, len)
|
||||
end
|
||||
|
||||
-- Templating
|
||||
|
||||
local function normalize_template(template)
|
||||
local ret = {}
|
||||
for i = 1, #template do
|
||||
ret[i] = template[i]
|
||||
end
|
||||
local non_array_part = {}
|
||||
-- The non-array part of the template (nested templates) have to be deterministic, so they are sorted.
|
||||
-- This means that inherently non deterministicly sortable keys (tables, functions) should NOT be used
|
||||
-- in templates. Looking for way around this.
|
||||
for k in pairs(template) do
|
||||
if not_array_index(k, #template) then
|
||||
non_array_part[#non_array_part + 1] = k
|
||||
end
|
||||
end
|
||||
table.sort(non_array_part)
|
||||
for i = 1, #non_array_part do
|
||||
local name = non_array_part[i]
|
||||
ret[#ret + 1] = {name, normalize_template(template[name])}
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
local function templatepart_serialize(part, argaccum, x, len)
|
||||
local extras = {}
|
||||
local extracount = 0
|
||||
for k, v in pairs(x) do
|
||||
extras[k] = v
|
||||
extracount = extracount + 1
|
||||
end
|
||||
for i = 1, #part do
|
||||
extracount = extracount - 1
|
||||
if type(part[i]) == "table" then
|
||||
extras[part[i][1]] = nil
|
||||
len = templatepart_serialize(part[i][2], argaccum, x[part[i][1]], len)
|
||||
else
|
||||
extras[part[i]] = nil
|
||||
len = len + 1
|
||||
argaccum[len] = x[part[i]]
|
||||
end
|
||||
end
|
||||
if extracount > 0 then
|
||||
argaccum[len + 1] = extras
|
||||
else
|
||||
argaccum[len + 1] = nil
|
||||
end
|
||||
return len + 1
|
||||
end
|
||||
|
||||
local function templatepart_deserialize(ret, part, values, vindex)
|
||||
for i = 1, #part do
|
||||
local name = part[i]
|
||||
if type(name) == "table" then
|
||||
local newret = {}
|
||||
ret[name[1]] = newret
|
||||
vindex = templatepart_deserialize(newret, name[2], values, vindex)
|
||||
else
|
||||
ret[name] = values[vindex]
|
||||
vindex = vindex + 1
|
||||
end
|
||||
end
|
||||
local extras = values[vindex]
|
||||
if extras then
|
||||
for k, v in pairs(extras) do
|
||||
ret[k] = v
|
||||
end
|
||||
end
|
||||
return vindex + 1
|
||||
end
|
||||
|
||||
local function template_serializer_and_deserializer(metatable, template)
|
||||
return function(x)
|
||||
argaccum = {}
|
||||
local len = templatepart_serialize(template, argaccum, x, 0)
|
||||
return unpack(argaccum, 1, len)
|
||||
end, function(...)
|
||||
local ret = {}
|
||||
local len = select("#", ...)
|
||||
local args = {...}
|
||||
templatepart_deserialize(ret, template, args, 1)
|
||||
return setmetatable(ret, metatable)
|
||||
end
|
||||
end
|
||||
|
||||
local function register(metatable, name, serialize, deserialize)
|
||||
name = name or metatable.name
|
||||
serialize = serialize or metatable._serialize
|
||||
deserialize = deserialize or metatable._deserialize
|
||||
if not serialize then
|
||||
if metatable._template then
|
||||
local t = normalize_template(metatable._template)
|
||||
serialize, deserialize = template_serializer_and_deserializer(metatable, t)
|
||||
elseif not deserialize then
|
||||
serialize = default_serialize
|
||||
deserialize = default_deserialize(metatable)
|
||||
else
|
||||
serialize = metatable
|
||||
end
|
||||
end
|
||||
type_check(metatable, "table", "metatable")
|
||||
type_check(name, "string", "name")
|
||||
type_check(serialize, "function", "serialize")
|
||||
type_check(deserialize, "function", "deserialize")
|
||||
assert(not ids[metatable], "Metatable already registered.")
|
||||
assert(not mts[name], ("Name %q already registered."):format(name))
|
||||
mts[name] = metatable
|
||||
ids[metatable] = name
|
||||
serializers[name] = serialize
|
||||
deserializers[name] = deserialize
|
||||
return metatable
|
||||
end
|
||||
|
||||
local function unregister(item)
|
||||
local name, metatable
|
||||
if type(item) == "string" then -- assume name
|
||||
name, metatable = item, mts[item]
|
||||
else -- assume metatable
|
||||
name, metatable = ids[item], item
|
||||
end
|
||||
type_check(name, "string", "name")
|
||||
type_check(metatable, "table", "metatable")
|
||||
mts[name] = nil
|
||||
ids[metatable] = nil
|
||||
serializers[name] = nil
|
||||
deserializers[name] = nil
|
||||
return metatable
|
||||
end
|
||||
|
||||
local function registerClass(class, name)
|
||||
name = name or class.name
|
||||
if class.__instanceDict then -- middleclass
|
||||
register(class.__instanceDict, name)
|
||||
else -- assume 30log or similar library
|
||||
register(class, name)
|
||||
end
|
||||
return class
|
||||
end
|
||||
|
||||
local function registerResource(resource, name)
|
||||
type_check(name, "string", "name")
|
||||
assert(not resources[resource],
|
||||
"Resource already registered.")
|
||||
assert(not resources_by_name[name],
|
||||
format("Resource %q already exists.", name))
|
||||
resources_by_name[name] = resource
|
||||
resources[resource] = name
|
||||
return resource
|
||||
end
|
||||
|
||||
local function unregisterResource(name)
|
||||
type_check(name, "string", "name")
|
||||
assert(resources_by_name[name], format("Resource %q does not exist.", name))
|
||||
local resource = resources_by_name[name]
|
||||
resources_by_name[name] = nil
|
||||
resources[resource] = nil
|
||||
return resource
|
||||
end
|
||||
|
||||
return {
|
||||
-- aliases
|
||||
s = serialize,
|
||||
d = deserialize,
|
||||
dn = deserializeN,
|
||||
r = readFile,
|
||||
w = writeFile,
|
||||
a = appendFile,
|
||||
|
||||
serialize = serialize,
|
||||
deserialize = deserialize,
|
||||
deserializeN = deserializeN,
|
||||
readFile = readFile,
|
||||
writeFile = writeFile,
|
||||
appendFile = appendFile,
|
||||
register = register,
|
||||
unregister = unregister,
|
||||
registerResource = registerResource,
|
||||
unregisterResource = unregisterResource,
|
||||
registerClass = registerClass
|
||||
}
|
||||
68
libs/classic.lua
Normal file
@@ -0,0 +1,68 @@
|
||||
--
|
||||
-- classic
|
||||
--
|
||||
-- Copyright (c) 2014, rxi
|
||||
--
|
||||
-- This module is free software; you can redistribute it and/or modify it under
|
||||
-- the terms of the MIT license. See LICENSE for details.
|
||||
--
|
||||
|
||||
|
||||
local Object = {}
|
||||
Object.__index = Object
|
||||
|
||||
|
||||
function Object:new()
|
||||
end
|
||||
|
||||
|
||||
function Object:extend()
|
||||
local cls = {}
|
||||
for k, v in pairs(self) do
|
||||
if k:find("__") == 1 then
|
||||
cls[k] = v
|
||||
end
|
||||
end
|
||||
cls.__index = cls
|
||||
cls.super = self
|
||||
setmetatable(cls, self)
|
||||
return cls
|
||||
end
|
||||
|
||||
|
||||
function Object:implement(...)
|
||||
for _, cls in pairs({...}) do
|
||||
for k, v in pairs(cls) do
|
||||
if self[k] == nil and type(v) == "function" then
|
||||
self[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Object:is(T)
|
||||
local mt = getmetatable(self)
|
||||
while mt do
|
||||
if mt == T then
|
||||
return true
|
||||
end
|
||||
mt = getmetatable(mt)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function Object:__tostring()
|
||||
return "Object"
|
||||
end
|
||||
|
||||
|
||||
function Object:__call(...)
|
||||
local obj = setmetatable({}, self)
|
||||
obj:new(...)
|
||||
return obj
|
||||
end
|
||||
|
||||
|
||||
return Object
|
||||
165
libs/lualzw.lua
Normal file
@@ -0,0 +1,165 @@
|
||||
--[[
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Rochet2
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
]]
|
||||
|
||||
local char = string.char
|
||||
local type = type
|
||||
local select = select
|
||||
local sub = string.sub
|
||||
local tconcat = table.concat
|
||||
|
||||
local basedictcompress = {}
|
||||
local basedictdecompress = {}
|
||||
for i = 0, 255 do
|
||||
local ic, iic = char(i), char(i, 0)
|
||||
basedictcompress[ic] = iic
|
||||
basedictdecompress[iic] = ic
|
||||
end
|
||||
|
||||
local function dictAddA(str, dict, a, b)
|
||||
if a >= 256 then
|
||||
a, b = 0, b+1
|
||||
if b >= 256 then
|
||||
dict = {}
|
||||
b = 1
|
||||
end
|
||||
end
|
||||
dict[str] = char(a,b)
|
||||
a = a+1
|
||||
return dict, a, b
|
||||
end
|
||||
|
||||
local function compress(input)
|
||||
if type(input) ~= "string" then
|
||||
return nil, "string expected, got "..type(input)
|
||||
end
|
||||
local len = #input
|
||||
if len <= 1 then
|
||||
return "u"..input
|
||||
end
|
||||
|
||||
local dict = {}
|
||||
local a, b = 0, 1
|
||||
|
||||
local result = {"c"}
|
||||
local resultlen = 1
|
||||
local n = 2
|
||||
local word = ""
|
||||
for i = 1, len do
|
||||
local c = sub(input, i, i)
|
||||
local wc = word..c
|
||||
if not (basedictcompress[wc] or dict[wc]) then
|
||||
local write = basedictcompress[word] or dict[word]
|
||||
if not write then
|
||||
return nil, "algorithm error, could not fetch word"
|
||||
end
|
||||
result[n] = write
|
||||
resultlen = resultlen + #write
|
||||
n = n+1
|
||||
if len <= resultlen then
|
||||
return "u"..input
|
||||
end
|
||||
dict, a, b = dictAddA(wc, dict, a, b)
|
||||
word = c
|
||||
else
|
||||
word = wc
|
||||
end
|
||||
end
|
||||
result[n] = basedictcompress[word] or dict[word]
|
||||
resultlen = resultlen+#result[n]
|
||||
n = n+1
|
||||
if len <= resultlen then
|
||||
return "u"..input
|
||||
end
|
||||
return tconcat(result)
|
||||
end
|
||||
|
||||
local function dictAddB(str, dict, a, b)
|
||||
if a >= 256 then
|
||||
a, b = 0, b+1
|
||||
if b >= 256 then
|
||||
dict = {}
|
||||
b = 1
|
||||
end
|
||||
end
|
||||
dict[char(a,b)] = str
|
||||
a = a+1
|
||||
return dict, a, b
|
||||
end
|
||||
|
||||
local function decompress(input)
|
||||
if type(input) ~= "string" then
|
||||
return nil, "string expected, got "..type(input)
|
||||
end
|
||||
|
||||
if #input < 1 then
|
||||
return nil, "invalid input - not a compressed string"
|
||||
end
|
||||
|
||||
local control = sub(input, 1, 1)
|
||||
if control == "u" then
|
||||
return sub(input, 2)
|
||||
elseif control ~= "c" then
|
||||
return nil, "invalid input - not a compressed string"
|
||||
end
|
||||
input = sub(input, 2)
|
||||
local len = #input
|
||||
|
||||
if len < 2 then
|
||||
return nil, "invalid input - not a compressed string"
|
||||
end
|
||||
|
||||
local dict = {}
|
||||
local a, b = 0, 1
|
||||
|
||||
local result = {}
|
||||
local n = 1
|
||||
local last = sub(input, 1, 2)
|
||||
result[n] = basedictdecompress[last] or dict[last]
|
||||
n = n+1
|
||||
for i = 3, len, 2 do
|
||||
local code = sub(input, i, i+1)
|
||||
local lastStr = basedictdecompress[last] or dict[last]
|
||||
if not lastStr then
|
||||
return nil, "could not find last from dict. Invalid input?"
|
||||
end
|
||||
local toAdd = basedictdecompress[code] or dict[code]
|
||||
if toAdd then
|
||||
result[n] = toAdd
|
||||
n = n+1
|
||||
dict, a, b = dictAddB(lastStr..sub(toAdd, 1, 1), dict, a, b)
|
||||
else
|
||||
local tmp = lastStr..sub(lastStr, 1, 1)
|
||||
result[n] = tmp
|
||||
n = n+1
|
||||
dict, a, b = dictAddB(tmp, dict, a, b)
|
||||
end
|
||||
last = code
|
||||
end
|
||||
return tconcat(result)
|
||||
end
|
||||
|
||||
return {
|
||||
compress = compress,
|
||||
decompress = decompress,
|
||||
}
|
||||
138
libs/simple-slider.lua
Normal file
@@ -0,0 +1,138 @@
|
||||
--[[
|
||||
Copyright (c) 2016 George Prosser
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
]]
|
||||
|
||||
local slider = {}
|
||||
slider.__index = slider
|
||||
|
||||
function newSlider(x, y, length, value, min, max, setter, style)
|
||||
local s = {}
|
||||
s.value = (value - min) / (max - min)
|
||||
s.min = min
|
||||
s.max = max
|
||||
s.setter = setter
|
||||
s.x = x
|
||||
s.y = y
|
||||
s.length = length
|
||||
|
||||
local p = style or {}
|
||||
s.width = p.width or length * 0.1
|
||||
s.orientation = p.orientation or 'horizontal'
|
||||
s.track = p.track or 'rectangle'
|
||||
s.knob = p.knob or 'rectangle'
|
||||
|
||||
s.grabbed = false
|
||||
s.wasDown = true
|
||||
s.ox = 0
|
||||
s.oy = 0
|
||||
|
||||
return setmetatable(s, slider)
|
||||
end
|
||||
|
||||
function slider:update(mouseX, mouseY, mouseDown)
|
||||
local x = mouseX or love.mouse.getX()
|
||||
local y = mouseY or love.mouse.getY()
|
||||
local down = love.mouse.isDown(1)
|
||||
if mouseDown ~= nil then
|
||||
down = mouseDown
|
||||
end
|
||||
|
||||
local knobX = self.x
|
||||
local knobY = self.y
|
||||
if self.orientation == 'horizontal' then
|
||||
knobX = self.x - self.length/2 + self.length * self.value
|
||||
elseif self.orientation == 'vertical' then
|
||||
knobY = self.y + self.length/2 - self.length * self.value
|
||||
end
|
||||
|
||||
local ox = x - knobX
|
||||
local oy = y - knobY
|
||||
|
||||
local dx = ox - self.ox
|
||||
local dy = oy - self.oy
|
||||
|
||||
if down then
|
||||
if self.grabbed then
|
||||
if self.orientation == 'horizontal' then
|
||||
self.value = self.value + dx / self.length
|
||||
elseif self.orientation == 'vertical' then
|
||||
self.value = self.value - dy / self.length
|
||||
end
|
||||
elseif (x > knobX - self.width/2 and x < knobX + self.width/2 and y > knobY - self.width/2 and y < knobY + self.width/2) and not self.wasDown then
|
||||
self.ox = ox
|
||||
self.oy = oy
|
||||
self.grabbed = true
|
||||
end
|
||||
else
|
||||
self.grabbed = false
|
||||
end
|
||||
|
||||
self.value = math.max(0, math.min(1, self.value))
|
||||
|
||||
if self.setter ~= nil then
|
||||
self.setter(self.min + self.value * (self.max - self.min))
|
||||
end
|
||||
|
||||
self.wasDown = down
|
||||
end
|
||||
|
||||
function slider:draw()
|
||||
if self.track == 'rectangle' then
|
||||
if self.orientation == 'horizontal' then
|
||||
love.graphics.rectangle('line', self.x - self.length/2 - self.width/2, self.y - self.width/2, self.length + self.width, self.width)
|
||||
elseif self.orientation == 'vertical' then
|
||||
love.graphics.rectangle('line', self.x - self.width/2, self.y - self.length/2 - self.width/2, self.width, self.length + self.width)
|
||||
end
|
||||
elseif self.track == 'line' then
|
||||
if self.orientation == 'horizontal' then
|
||||
love.graphics.line(self.x - self.length/2, self.y, self.x + self.length/2, self.y)
|
||||
elseif self.orientation == 'vertical' then
|
||||
love.graphics.line(self.x, self.y - self.length/2, self.x, self.y + self.length/2)
|
||||
end
|
||||
elseif self.track == 'roundrect' then
|
||||
if self.orientation == 'horizontal' then
|
||||
love.graphics.rectangle('line', self.x - self.length/2 - self.width/2, self.y - self.width/2, self.length + self.width, self.width, self.width/2, self.width)
|
||||
elseif self.orientation == 'vertical' then
|
||||
love.graphics.rectangle('line', self.x - self.width/2, self.y - self.length/2 - self.width/2, self.width, self.length + self.width, self.width, self.width/2)
|
||||
end
|
||||
end
|
||||
|
||||
local knobX = self.x
|
||||
local knobY = self.y
|
||||
if self.orientation == 'horizontal' then
|
||||
knobX = self.x - self.length/2 + self.length * self.value
|
||||
elseif self.orientation == 'vertical' then
|
||||
knobY = self.y + self.length/2 - self.length * self.value
|
||||
end
|
||||
|
||||
if self.knob == 'rectangle' then
|
||||
love.graphics.rectangle('fill', knobX - self.width/2, knobY - self.width/2, self.width, self.width)
|
||||
elseif self.knob == 'circle' then
|
||||
love.graphics.circle('fill', knobX, knobY, self.width/2)
|
||||
end
|
||||
end
|
||||
|
||||
function slider:getValue()
|
||||
return self.min + self.value * (self.max - self.min)
|
||||
end
|
||||
2
load/bigint.lua
Normal file
@@ -0,0 +1,2 @@
|
||||
bigint = require "libs.bigint.bigint"
|
||||
number_names = require "libs.bigint.named-powers-of-ten"
|
||||
2
load/fonts.lua
Normal file
@@ -0,0 +1,2 @@
|
||||
tromi_font = love.graphics.newFont('res/fonts/monofonto rg.otf', 28)
|
||||
big_font = love.graphics.newFont('res/fonts/monofonto rg.otf', 56)
|
||||
48
load/graphics.lua
Normal file
@@ -0,0 +1,48 @@
|
||||
backgrounds = {
|
||||
[0] = love.graphics.newVideo("res/backgrounds/green_waterfall.ogv", {audio=false}),
|
||||
love.graphics.newVideo("res/backgrounds/water.ogv", {audio=false}),
|
||||
love.graphics.newVideo("res/backgrounds/green_streams.ogv", {audio=false}),
|
||||
love.graphics.newVideo("res/backgrounds/streams.ogv", {audio=false}),
|
||||
love.graphics.newVideo("res/backgrounds/red_forest_waterfall.ogv", {audio=false}),
|
||||
love.graphics.newVideo("res/backgrounds/flowers_rain.ogv", {audio=false}),
|
||||
love.graphics.newVideo("res/backgrounds/moonlight_tree.ogv", {audio=false}),
|
||||
love.graphics.newVideo("res/backgrounds/lisa_frank.ogv", {audio=false}),
|
||||
love.graphics.newVideo("res/backgrounds/snowy_trees.ogv", {audio=false}),
|
||||
love.graphics.newVideo("res/backgrounds/snowy_cabin.ogv", {audio=false}),
|
||||
}
|
||||
|
||||
|
||||
blocks = {
|
||||
["2tie"] = {
|
||||
R = love.graphics.newImage("res/img/r.png"),
|
||||
O = love.graphics.newImage("res/img/o.png"),
|
||||
Y = love.graphics.newImage("res/img/y.png"),
|
||||
G = love.graphics.newImage("res/img/g.png"),
|
||||
C = love.graphics.newImage("res/img/b.png"),
|
||||
B = love.graphics.newImage("res/img/i.png"),
|
||||
M = love.graphics.newImage("res/img/v.png"),
|
||||
F = love.graphics.newImage("res/img/bl.png"),
|
||||
A = love.graphics.newImage("res/img/bl.png"),
|
||||
X = love.graphics.newImage("res/img/t.png"),
|
||||
W = love.graphics.newImage("res/img/w.png"),
|
||||
R_d = love.graphics.newImage("res/img/r_d.png"),
|
||||
O_d = love.graphics.newImage("res/img/o_d.png"),
|
||||
Y_d = love.graphics.newImage("res/img/y_d.png"),
|
||||
G_d = love.graphics.newImage("res/img/g_d.png"),
|
||||
C_d = love.graphics.newImage("res/img/b_d.png"),
|
||||
B_d = love.graphics.newImage("res/img/i_d.png"),
|
||||
M_d = love.graphics.newImage("res/img/v_d.png"),
|
||||
}
|
||||
}
|
||||
|
||||
ColourSchemes = {
|
||||
Arika = {
|
||||
I = "R",
|
||||
L = "O",
|
||||
J = "B",
|
||||
S = "M",
|
||||
Z = "G",
|
||||
O = "Y",
|
||||
T = "C",
|
||||
}
|
||||
}
|
||||
29
load/save.lua
Normal file
@@ -0,0 +1,29 @@
|
||||
local binser = require 'libs.binser'
|
||||
local fs = love.filesystem
|
||||
|
||||
function loadSave()
|
||||
config = loadFromFile(CONFIG_FILE)
|
||||
end
|
||||
|
||||
function loadFromFile(filename)
|
||||
local save_data = fs.read(filename)
|
||||
if save_data == nil then
|
||||
return {} -- new object
|
||||
end
|
||||
return binser.deserialize(save_data)[1]
|
||||
end
|
||||
|
||||
function initConfig()
|
||||
if config.fullscreen == nil then config.fullscreen = false end
|
||||
if config.music == nil then config.music = true end
|
||||
if not config.input then
|
||||
scene = InputConfigScene(true)
|
||||
else
|
||||
scene = TitleScene()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function saveConfig()
|
||||
fs.write(CONFIG_FILE,binser.serialize(config))
|
||||
end
|
||||
49
load/sounds.lua
Normal file
@@ -0,0 +1,49 @@
|
||||
sounds = {
|
||||
bottom = love.audio.newSource("res/se/bottom.wav", "static"),
|
||||
lock = love.audio.newSource("res/se/lock.wav", "static"),
|
||||
erase = love.audio.newSource("res/se/erase.wav", "static"),
|
||||
fall = love.audio.newSource("res/se/fall.wav", "static"),
|
||||
ready = love.audio.newSource("res/se/ready.wav", "static"),
|
||||
promote = love.audio.newSource("res/se/promote.wav", "static"),
|
||||
demote = love.audio.newSource("res/se/demote.wav", "static"),
|
||||
autopromote = love.audio.newSource("res/se/autopromote.wav", "static"),
|
||||
bgm_firsthalf = love.audio.newSource("res/bgm/firsthalf.flac", "static"),
|
||||
bgm_secondhalf = love.audio.newSource("res/bgm/secondhalf.flac", "static"),
|
||||
bgm_title = love.audio.newSource("res/bgm/title.flac", "static")
|
||||
}
|
||||
|
||||
function playSE(sound, subsound)
|
||||
if sound ~= nil then
|
||||
if subsound ~= nil then
|
||||
sounds[sound][subsound]:setVolume(0.4)
|
||||
if sounds[sound][subsound]:isPlaying() then
|
||||
sounds[sound][subsound]:stop()
|
||||
end
|
||||
sounds[sound][subsound]:play()
|
||||
else
|
||||
sounds[sound]:setVolume(0.4)
|
||||
if sounds[sound]:isPlaying() then
|
||||
sounds[sound]:stop()
|
||||
end
|
||||
sounds[sound]:play()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function playSEOnce(sound, subsound)
|
||||
if sound ~= nil then
|
||||
if subsound ~= nil then
|
||||
sounds[sound][subsound]:setVolume(0.4)
|
||||
if sounds[sound][subsound]:isPlaying() then
|
||||
return
|
||||
end
|
||||
sounds[sound][subsound]:play()
|
||||
else
|
||||
sounds[sound]:setVolume(0.4)
|
||||
if sounds[sound]:isPlaying() then
|
||||
return
|
||||
end
|
||||
sounds[sound]:play()
|
||||
end
|
||||
end
|
||||
end
|
||||
389
main.lua
Normal file
@@ -0,0 +1,389 @@
|
||||
require 'funcs'
|
||||
-- savedir = love.filesystem.getSaveDirectory()
|
||||
-- if love.system.getOS() == 'Windows' then
|
||||
-- local test_for_separator = string.find(savedir, '/')
|
||||
-- if test_for_separator ~= nil then savedir = table.concat(split_string(savedir, '/'), '\\') end
|
||||
-- savedir = savedir..'\\saves\\'
|
||||
-- configfile = savedir..'config.sav'
|
||||
-- replaydir = savedir..'replays\\'
|
||||
-- hiscorefile = savedir..'hiscores.sav'
|
||||
-- else
|
||||
-- savedir = savedir..'/saves/'
|
||||
-- configfile = savedir..'config.sav'
|
||||
-- replaydir = savedir..'replays/'
|
||||
-- hiscorefile = savedir..'hiscores.sav'
|
||||
-- end
|
||||
SAVE_DIR = '/saves/'
|
||||
CONFIG_FILE = 'config.sav'
|
||||
REPLAY_DIR = 'replays/'
|
||||
HIscoreFILE = 'hiscores.sav'
|
||||
|
||||
PENTO_MODE = false
|
||||
|
||||
|
||||
function love.load()
|
||||
math.randomseed(os.time())
|
||||
highscores = {}
|
||||
require "load.graphics"
|
||||
require "load.fonts"
|
||||
require "load.sounds"
|
||||
require "load.save"
|
||||
require "load.bigint"
|
||||
loadSave()
|
||||
require "scene"
|
||||
|
||||
love.window.setMode(love.graphics.getWidth(), love.graphics.getHeight(), {resizable = true});
|
||||
|
||||
love.mouse.setVisible(false)
|
||||
love.keyboard.setKeyRepeat(true)
|
||||
love.keyboard.setTextInput(true)
|
||||
|
||||
-- used for screenshots
|
||||
GLOBAL_CANVAS = love.graphics.newCanvas()
|
||||
|
||||
-- init config
|
||||
initConfig()
|
||||
|
||||
love.window.setFullscreen(config["fullscreen"])
|
||||
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
love.graphics.setCanvas(GLOBAL_CANVAS)
|
||||
love.graphics.clear()
|
||||
love.graphics.push()
|
||||
|
||||
-- get offset matrix
|
||||
local width = love.graphics.getWidth()
|
||||
local height = love.graphics.getHeight()
|
||||
local scale_factor = math.min(width / 640, height / 480)
|
||||
love.graphics.translate(
|
||||
(width - scale_factor * 640) / 2,
|
||||
(height - scale_factor * 480) / 2
|
||||
)
|
||||
love.graphics.scale(scale_factor)
|
||||
|
||||
scene:render()
|
||||
love.graphics.pop()
|
||||
|
||||
drawText("Pressed: "..(lastPressedKey or '[NONE]').." | Released key: "..(lastReleasedKey or '[NONE]'),0,0,1000,"left")
|
||||
|
||||
love.graphics.setCanvas()
|
||||
love.graphics.setColor(1,1,1,1)
|
||||
love.graphics.draw(GLOBAL_CANVAS)
|
||||
end
|
||||
|
||||
function love.keypressed(key, scancode)
|
||||
local input_pressed=nil
|
||||
|
||||
-- global hotkeys
|
||||
if scancode == "f4" then
|
||||
config["fullscreen"] = not config["fullscreen"]
|
||||
saveConfig()
|
||||
love.window.setFullscreen(config["fullscreen"])
|
||||
elseif scancode == "f2" and scene.title ~= "Input Config" and scene.title ~= "Game" then
|
||||
scene = InputConfigScene()
|
||||
-- function keys are reserved
|
||||
elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f[1-9][0-9]+$") then
|
||||
return
|
||||
-- escape is reserved for menu_back
|
||||
elseif scancode == "escape" then
|
||||
scene:onInputPress({input="menu_back", type="key", key=key, scancode=scancode})
|
||||
-- pass any other key to the scene, with its configured mapping
|
||||
else
|
||||
if config.input and config.input.keys then
|
||||
input_pressed = config.input.keys[scancode]
|
||||
end
|
||||
scene:onInputPress({input=input_pressed, type="key", key=key, scancode=scancode})
|
||||
end
|
||||
|
||||
lastPressedKey = input_pressed or scancode
|
||||
end
|
||||
|
||||
function love.keyreleased(key, scancode)
|
||||
local input_released = nil
|
||||
|
||||
-- escape is reserved for menu_back
|
||||
if scancode == "escape" then
|
||||
scene:onInputRelease({input="menu_back", type="key", key=key, scancode=scancode})
|
||||
-- function keys are reserved
|
||||
elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f[1-9][0-9]+$") then
|
||||
return
|
||||
-- handle all other keys; tab is reserved, but the input config scene keeps it from getting configured as a game input, so pass tab to the scene here
|
||||
else
|
||||
if config.input and config.input.keys then
|
||||
input_released = config.input.keys[scancode]
|
||||
end
|
||||
scene:onInputRelease({input=input_released, type="key", key=key, scancode=scancode})
|
||||
end
|
||||
|
||||
lastReleasedKey = input_released or scancode
|
||||
end
|
||||
|
||||
function love.joystickpressed(joystick, button)
|
||||
local input_pressed = nil
|
||||
if
|
||||
config.input and
|
||||
config.input.joysticks and
|
||||
config.input.joysticks[joystick:getName()] and
|
||||
config.input.joysticks[joystick:getName()].buttons
|
||||
then
|
||||
input_pressed = config.input.joysticks[joystick:getName()].buttons[button]
|
||||
end
|
||||
scene:onInputPress({input=input_pressed, type="joybutton", name=joystick:getName(), button=button})
|
||||
end
|
||||
|
||||
function love.joystickreleased(joystick, button)
|
||||
local input_released = nil
|
||||
if
|
||||
config.input and
|
||||
config.input.joysticks and
|
||||
config.input.joysticks[joystick:getName()] and
|
||||
config.input.joysticks[joystick:getName()].buttons
|
||||
then
|
||||
input_released = config.input.joysticks[joystick:getName()].buttons[button]
|
||||
end
|
||||
scene:onInputRelease({input=input_released, type="joybutton", name=joystick:getName(), button=button})
|
||||
end
|
||||
|
||||
function love.joystickaxis(joystick, axis, value)
|
||||
local input_pressed = nil
|
||||
local positive_released = nil
|
||||
local negative_released = nil
|
||||
if
|
||||
config.input and
|
||||
config.input.joysticks and
|
||||
config.input.joysticks[joystick:getName()] and
|
||||
config.input.joysticks[joystick:getName()].axes and
|
||||
config.input.joysticks[joystick:getName()].axes[axis]
|
||||
then
|
||||
if math.abs(value) >= 1 then
|
||||
input_pressed = config.input.joysticks[joystick:getName()].axes[axis][value >= 1 and "positive" or "negative"]
|
||||
end
|
||||
positive_released = config.input.joysticks[joystick:getName()].axes[axis].positive
|
||||
negative_released = config.input.joysticks[joystick:getName()].axes[axis].negative
|
||||
end
|
||||
if math.abs(value) >= 1 then
|
||||
scene:onInputPress({input=input_pressed, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
|
||||
else
|
||||
scene:onInputRelease({input=positive_released, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
|
||||
scene:onInputRelease({input=negative_released, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
|
||||
end
|
||||
end
|
||||
|
||||
function love.touchPressed()
|
||||
love.keyboard.setKeyRepeat(true)
|
||||
love.keyboard.setTextInput(false)
|
||||
love.keyboard.setTextInput(true)
|
||||
end
|
||||
|
||||
local last_hat_direction = ""
|
||||
local directions = {
|
||||
["u"] = "up",
|
||||
["d"] = "down",
|
||||
["l"] = "left",
|
||||
["r"] = "right",
|
||||
}
|
||||
|
||||
function love.joystickhat(joystick, hat, direction)
|
||||
local input_pressed = nil
|
||||
local has_hat = false
|
||||
if
|
||||
config.input and
|
||||
config.input.joysticks and
|
||||
config.input.joysticks[joystick:getName()] and
|
||||
config.input.joysticks[joystick:getName()].hats and
|
||||
config.input.joysticks[joystick:getName()].hats[hat]
|
||||
then
|
||||
if direction ~= "c" then
|
||||
input_pressed = config.input.joysticks[joystick:getName()].hats[hat][direction]
|
||||
end
|
||||
has_hat = true
|
||||
end
|
||||
if input_pressed then
|
||||
for i = 1, #direction do
|
||||
local char = direction:sub(i, i)
|
||||
local _, count = last_hat_direction:gsub(char, char)
|
||||
if count == 0 then
|
||||
scene:onInputPress({input=config.input.joysticks[joystick:getName()].hats[hat][char], type="joyhat", name=joystick:getName(), hat=hat, direction=char})
|
||||
end
|
||||
end
|
||||
for i = 1, #last_hat_direction do
|
||||
local char = last_hat_direction:sub(i, i)
|
||||
local _, count = direction:gsub(char, char)
|
||||
if count == 0 then
|
||||
scene:onInputRelease({input=config.input.joysticks[joystick:getName()].hats[hat][char], type="joyhat", name=joystick:getName(), hat=hat, direction=char})
|
||||
end
|
||||
end
|
||||
last_hat_direction = direction
|
||||
elseif has_hat then
|
||||
for i, direction in ipairs{"d", "l", "ld", "lu", "r", "rd", "ru", "u"} do
|
||||
scene:onInputRelease({input=config.input.joysticks[joystick:getName()].hats[hat][direction], type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
|
||||
end
|
||||
last_hat_direction = ""
|
||||
elseif direction ~= "c" then
|
||||
for i = 1, #direction do
|
||||
local char = direction:sub(i, i)
|
||||
local _, count = last_hat_direction:gsub(char, char)
|
||||
if count == 0 then
|
||||
scene:onInputPress({input=directions[char], type="joyhat", name=joystick:getName(), hat=hat, direction=char})
|
||||
end
|
||||
end
|
||||
for i = 1, #last_hat_direction do
|
||||
local char = last_hat_direction:sub(i, i)
|
||||
local _, count = direction:gsub(char, char)
|
||||
if count == 0 then
|
||||
scene:onInputRelease({input=directions[char], type="joyhat", name=joystick:getName(), hat=hat, direction=char})
|
||||
end
|
||||
end
|
||||
last_hat_direction = direction
|
||||
else
|
||||
for i, direction in ipairs{"d", "l", "ld", "lu", "r", "rd", "ru", "u"} do
|
||||
scene:onInputRelease({input=nil, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
|
||||
end
|
||||
last_hat_direction = ""
|
||||
end
|
||||
end
|
||||
|
||||
function love.focus(f)
|
||||
return
|
||||
end
|
||||
|
||||
function love.resize(w, h)
|
||||
GLOBAL_CANVAS:release()
|
||||
GLOBAL_CANVAS = love.graphics.newCanvas(w, h)
|
||||
end
|
||||
|
||||
local TARGET_FPS = 60
|
||||
|
||||
function love.run()
|
||||
if love.load then love.load(love.arg.parseGameArguments(arg), arg) end
|
||||
|
||||
if love.timer then love.timer.step() end
|
||||
|
||||
local dt = 0
|
||||
|
||||
local last_time = love.timer.getTime()
|
||||
local time_accumulator = 0
|
||||
return function()
|
||||
if love.event then
|
||||
love.event.pump()
|
||||
for name, a,b,c,d,e,f in love.event.poll() do
|
||||
if name == "quit" then
|
||||
if not love.quit or not love.quit() then
|
||||
return a or 0
|
||||
end
|
||||
end
|
||||
love.handlers[name](a,b,c,d,e,f)
|
||||
end
|
||||
end
|
||||
|
||||
if scene and scene.update and love.timer then
|
||||
scene:update()
|
||||
|
||||
local frame_duration = 1.0 / TARGET_FPS
|
||||
if time_accumulator < frame_duration then
|
||||
if love.graphics and love.graphics.isActive() and love.draw then
|
||||
love.graphics.origin()
|
||||
love.graphics.clear(love.graphics.getBackgroundColor())
|
||||
love.draw()
|
||||
love.graphics.present()
|
||||
end
|
||||
local end_time = last_time + frame_duration
|
||||
local time = love.timer.getTime()
|
||||
while time < end_time do
|
||||
love.timer.sleep(0.001)
|
||||
time = love.timer.getTime()
|
||||
end
|
||||
time_accumulator = time_accumulator + time - last_time
|
||||
end
|
||||
time_accumulator = time_accumulator - frame_duration
|
||||
end
|
||||
last_time = love.timer.getTime()
|
||||
end
|
||||
end
|
||||
|
||||
minos = {'R_d', 'O_d', 'Y_d', 'G_d', 'C_d', 'B_d', 'M_d'}
|
||||
main_bg_grid = {}
|
||||
for x=1, 40 do
|
||||
main_bg_grid[x] = {}
|
||||
for y=1, 30 do
|
||||
main_bg_grid[x][y] = 0
|
||||
end
|
||||
end
|
||||
main_bg_cur_pos = {20,6}
|
||||
main_bg_cur_color = minos[love.math.random(1,7)]
|
||||
main_bg_cur_mino = 1
|
||||
main_bg_draw_frame = 0
|
||||
main_bg_last_color = nil
|
||||
|
||||
function mainBackground()
|
||||
if config["music"] and not sounds["bgm_title"]:isPlaying() then
|
||||
sounds["bgm_title"]:setVolume(0.3)
|
||||
sounds["bgm_title"]:play()
|
||||
end
|
||||
local y = 40
|
||||
if main_bg_draw_frame >= 16 then
|
||||
while y > 1 do
|
||||
for x = 1, 40 do
|
||||
main_bg_grid[x][y] = main_bg_grid[x][y-1]
|
||||
end
|
||||
y = y - 1
|
||||
end
|
||||
for x=1, 40 do
|
||||
main_bg_grid[x][1] = 0
|
||||
end
|
||||
main_bg_draw_frame = 0
|
||||
main_bg_cur_pos[2] = main_bg_cur_pos[2] + 1
|
||||
end
|
||||
local directions = { {0,-1},{1,0}, {-1,0}}
|
||||
local test_dir = directions[love.math.random(1,3)]
|
||||
main_bg_cur_pos[1] = main_bg_cur_pos[1] + test_dir[1]
|
||||
main_bg_cur_pos[2] = main_bg_cur_pos[2] + test_dir[2]
|
||||
if main_bg_cur_pos[1] > 40 then main_bg_cur_pos[1] = 40 end
|
||||
if main_bg_cur_pos[1] < 1 then main_bg_cur_pos[1] = 1 end
|
||||
if main_bg_cur_pos[2] > 30 then main_bg_cur_pos[2] = 30 end
|
||||
if main_bg_cur_pos[2] < 1 then main_bg_cur_pos[2] = 1 end
|
||||
if main_bg_grid[main_bg_cur_pos[1]][main_bg_cur_pos[2]] == 0 then
|
||||
main_bg_grid[main_bg_cur_pos[1]][main_bg_cur_pos[2]] = main_bg_cur_color
|
||||
main_bg_cur_mino = main_bg_cur_mino + 1
|
||||
end
|
||||
for x=1,40 do
|
||||
for y=1,30 do
|
||||
if main_bg_grid[x][y] ~= 0 then
|
||||
love.graphics.setColor(1, 1, 1, 0.4)
|
||||
if ((x-1)*48)-560 > 0 and ((x-1)*48)-560 < 640 then love.graphics.draw(blocks["2tie"][main_bg_grid[x][y]], ((x-1)*48)-570, (((y+2)*48)+main_bg_draw_frame*3)-480,0, 3) end
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
for x=1,40 do
|
||||
for y=1,30 do
|
||||
if main_bg_grid[x][y] ~= 0 then
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.draw(blocks["2tie"][main_bg_grid[x][y]], (x-1)*16, ((y-1)*16)+main_bg_draw_frame)
|
||||
end
|
||||
end
|
||||
end
|
||||
for x=1,40 do
|
||||
for y=1,30 do
|
||||
if main_bg_grid[x][y] ~= 0 then
|
||||
love.graphics.setColor(1, 1, 1, 0.6)
|
||||
if ((x-1)*32)-320 > 0 and ((x-1)*32)-320 < 640 then love.graphics.draw(blocks["2tie"][main_bg_grid[x][y]], ((x-1)*32)-320, (((y+1)*32)+main_bg_draw_frame*2)-320,0, 2) end
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
if main_bg_cur_mino == 5 then
|
||||
--if main_bg_cur_pos[2] < 4 then
|
||||
-- main_bg_cur_pos[2] = 4
|
||||
-- main_bg_cur_pos[1] = love.math.random(4, 36)
|
||||
--end
|
||||
main_bg_cur_pos = {love.math.random(16,24),6}
|
||||
main_bg_last_color = main_bg_cur_color
|
||||
while main_bg_cur_color == main_bg_last_color do main_bg_cur_color = minos[love.math.random(1,7)] end
|
||||
main_bg_cur_mino = 1
|
||||
end
|
||||
main_bg_placed = false
|
||||
main_bg_draw_frame = main_bg_draw_frame + 1
|
||||
end
|
||||
BIN
res/backgrounds/flowers_rain.ogv
Normal file
BIN
res/backgrounds/green_streams.ogv
Normal file
BIN
res/backgrounds/green_waterfall.ogv
Normal file
BIN
res/backgrounds/lisa_frank.ogv
Normal file
BIN
res/backgrounds/moonlight_tree.ogv
Normal file
BIN
res/backgrounds/red_forest_waterfall.ogv
Normal file
BIN
res/backgrounds/snowy_cabin.ogv
Normal file
BIN
res/backgrounds/snowy_trees.ogv
Normal file
BIN
res/backgrounds/streams.ogv
Normal file
BIN
res/backgrounds/water.ogv
Normal file
BIN
res/bgm/firsthalf.flac
Normal file
BIN
res/bgm/secondhalf.flac
Normal file
BIN
res/bgm/title.flac
Normal file
BIN
res/fonts/monofonto rg.otf
Normal file
BIN
res/img/b.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
res/img/b_d.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
res/img/bl.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
res/img/g.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
res/img/g_d.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
res/img/i.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
res/img/i_d.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
res/img/o.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
res/img/o_d.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
res/img/r.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
res/img/r_d.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
res/img/t.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
res/img/v.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
res/img/v_d.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
res/img/w.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
res/img/y.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
res/img/y_d.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
res/se/autopromote.wav
Normal file
BIN
res/se/bottom.wav
Normal file
BIN
res/se/demote.wav
Normal file
BIN
res/se/erase.wav
Normal file
BIN
res/se/fall.wav
Normal file
BIN
res/se/lock.wav
Normal file
BIN
res/se/promote.wav
Normal file
BIN
res/se/ready.wav
Normal file
21
scene.lua
Normal file
@@ -0,0 +1,21 @@
|
||||
local Object = require "libs.classic"
|
||||
|
||||
Scene = Object:extend()
|
||||
|
||||
function Scene:new() end
|
||||
function Scene:update() end
|
||||
function Scene:render() end
|
||||
function Scene:onInputPress() end
|
||||
function Scene:onInputRelease() end
|
||||
|
||||
ExitScene = require "scene.exit"
|
||||
GameScene = require "scene.game"
|
||||
NameEntryScene = require "scene.name_entry"
|
||||
KeyConfigScene = require "scene.key_config"
|
||||
StickConfigScene = require "scene.stick_config"
|
||||
InputConfigScene = require "scene.input_config"
|
||||
ReplaySelectScene = require "scene.replay"
|
||||
FullscreenScene = require "scene.fullscreen"
|
||||
MusicToggleScene = require "scene.music_toggle"
|
||||
TrainingScene = require "scene.training"
|
||||
TitleScene = require "scene.title"
|
||||
23
scene/exit.lua
Normal file
@@ -0,0 +1,23 @@
|
||||
local ExitScene = Scene:extend()
|
||||
require 'load.save'
|
||||
|
||||
ExitScene.title = "Exit Game"
|
||||
|
||||
function ExitScene:new()
|
||||
end
|
||||
|
||||
function ExitScene:update()
|
||||
love.event.quit()
|
||||
end
|
||||
|
||||
function ExitScene:render()
|
||||
end
|
||||
|
||||
function ExitScene:changeOption(rel)
|
||||
end
|
||||
|
||||
function ExitScene:onInputPress(e)
|
||||
end
|
||||
|
||||
return ExitScene
|
||||
|
||||
25
scene/fullscreen.lua
Normal file
@@ -0,0 +1,25 @@
|
||||
local FullscreenScene = Scene:extend()
|
||||
require 'load.save'
|
||||
|
||||
FullscreenScene.title = "Fullscreen"
|
||||
|
||||
function FullscreenScene:new()
|
||||
end
|
||||
|
||||
function FullscreenScene:update()
|
||||
config["fullscreen"] = not config["fullscreen"]
|
||||
saveConfig()
|
||||
love.window.setFullscreen(config["fullscreen"])
|
||||
scene = TitleScene()
|
||||
end
|
||||
|
||||
function FullscreenScene:render()
|
||||
end
|
||||
|
||||
function FullscreenScene:changeOption(rel)
|
||||
end
|
||||
|
||||
function FullscreenScene:onInputPress(e)
|
||||
end
|
||||
|
||||
return FullscreenScene
|
||||
115
scene/game.lua
Normal file
@@ -0,0 +1,115 @@
|
||||
local GameScene = Scene:extend()
|
||||
local binser = require 'libs.binser'
|
||||
|
||||
GameScene.title = "Game"
|
||||
local tas = false
|
||||
require 'load.save'
|
||||
|
||||
function GameScene:new(player_name, replay_file, replay_grade)
|
||||
game_mode = require 'game.gamemode'
|
||||
if PENTO_MODE then
|
||||
ruleset = require 'game.rotation_pent'
|
||||
else
|
||||
ruleset = require 'game.rotation'
|
||||
end
|
||||
self.retry_mode = game_mode
|
||||
self.retry_ruleset = ruleset
|
||||
self.secret_inputs = inputs
|
||||
self.reset_stuff = {player_name, replay_file, replay_grade}
|
||||
self.game = game_mode(player_name, replay_file, replay_grade)
|
||||
self.ruleset = ruleset(self.game)
|
||||
self.grace_frames = 0
|
||||
self.normal_volume = love.audio.getVolume()
|
||||
self.game:initialize(self.ruleset)
|
||||
self.inputs = {
|
||||
left=false,
|
||||
right=false,
|
||||
up=false,
|
||||
down=false,
|
||||
rotate_left=false,
|
||||
rotate_left2=false,
|
||||
rotate_right=false,
|
||||
rotate_right2=false,
|
||||
rotate_180=false,
|
||||
hold=false,
|
||||
}
|
||||
self.paused = false
|
||||
end
|
||||
|
||||
function GameScene:update(nosound, tas_update)
|
||||
local inputs = {}
|
||||
if tas then
|
||||
while self.game.are > 2 do
|
||||
self.game:update(inputs, self.ruleset)
|
||||
end
|
||||
end
|
||||
for input, value in pairs(self.inputs) do
|
||||
inputs[input] = value
|
||||
end
|
||||
if tas and tas_update then
|
||||
self.paused = false
|
||||
self.game:update(inputs, self.ruleset)
|
||||
self.paused = true
|
||||
return
|
||||
end
|
||||
if nosound then
|
||||
love.audio.setVolume(0)
|
||||
end
|
||||
if not nosound and self.grace_frames > 0 then
|
||||
self.grace_frames = self.grace_frames - 1
|
||||
if self.grace_frames == 1 then love.audio.setVolume(self.normal_volume) end
|
||||
end
|
||||
if not self.paused then
|
||||
self.game:update(inputs, self.ruleset)
|
||||
end
|
||||
end
|
||||
|
||||
function GameScene:render()
|
||||
self.game:draw(self.paused)
|
||||
end
|
||||
|
||||
function GameScene:onInputPress(e)
|
||||
if (self.game.game_over or self.game.completed) and (e.input == "menu_decide" or e.input == "menu_back" or e.input == "rotate_right") and self.game.game_over_frames > 50 then
|
||||
scene = TitleScene()
|
||||
elseif tas and e.input == "menu_decide" then
|
||||
self:update(false, true)
|
||||
elseif self.game.input_playback and (e.input == "menu_back") then
|
||||
scene = TitleScene()
|
||||
elseif self.game.input_playback and e.input == "rotate_left" then
|
||||
self.paused = false
|
||||
self:update()
|
||||
self.paused = true
|
||||
elseif self.game.input_playback and e.input == "rotate_right" then
|
||||
self.paused = false
|
||||
elseif self.game.input_playback and not self.paused and e.input == 'left' then
|
||||
local target = self.game.frames - 300
|
||||
if target < 1 then target = 1 end
|
||||
self.game = game_mode(self.reset_stuff[1], self.reset_stuff[2], self.reset_stuff[3])
|
||||
self.ruleset = ruleset(self.game)
|
||||
self.game:initialize(self.ruleset)
|
||||
while self.game.frames < (target) do
|
||||
self:update(true)
|
||||
end
|
||||
self.grace_frames = 90
|
||||
elseif self.game.input_playback and not self.paused and e.input == 'right' then
|
||||
local target = self.game.frames + 600
|
||||
if target > #self.game.replay_inputs then target = #self.game.replay_inputs-10 end
|
||||
self.game = game_mode(self.reset_stuff[1], self.reset_stuff[2], self.reset_stuff[3])
|
||||
self.ruleset = ruleset(self.game)
|
||||
self.game:initialize(self.ruleset)
|
||||
while self.game.frames < (target) do
|
||||
self:update(true)
|
||||
end
|
||||
self.grace_frames = 90
|
||||
elseif e.input and string.sub(e.input, 1, 5) ~= "menu_" then
|
||||
self.inputs[e.input] = true
|
||||
end
|
||||
end
|
||||
|
||||
function GameScene:onInputRelease(e)
|
||||
if e.input and string.sub(e.input, 1, 5) ~= "menu_" then
|
||||
self.inputs[e.input] = false
|
||||
end
|
||||
end
|
||||
|
||||
return GameScene
|
||||
60
scene/input_config.lua
Normal file
@@ -0,0 +1,60 @@
|
||||
local ConfigScene = Scene:extend()
|
||||
|
||||
ConfigScene.title = "Input Config"
|
||||
|
||||
local minos = {'R_d', 'O_d', 'Y_d', 'G_d', 'C_d', 'B_d', 'M_d'}
|
||||
|
||||
local menu_screens = {
|
||||
KeyConfigScene,
|
||||
StickConfigScene
|
||||
}
|
||||
|
||||
function ConfigScene:new(first_time)
|
||||
self.menu_state = 1
|
||||
if first_time then
|
||||
self.first_time = true
|
||||
else
|
||||
self.first_time = false
|
||||
end
|
||||
end
|
||||
|
||||
function ConfigScene:update() end
|
||||
|
||||
function ConfigScene:render()
|
||||
mainBackground()
|
||||
if not self.first_time then
|
||||
drawText("Which controls do you want to configure?", 80, 70, 1000)
|
||||
else
|
||||
drawText("Thanks for playing Tromi!", 80, 40, 1000)
|
||||
drawText("Please begin by configuring your controls:", 80, 70, 1000)
|
||||
end
|
||||
|
||||
love.graphics.setColor(1, 1, 1, 0.5)
|
||||
love.graphics.rectangle("fill", 75, 118 + 50 * self.menu_state, 200, 33)
|
||||
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
for i, screen in pairs(menu_screens) do
|
||||
drawText(screen.title, 80, 120 + 50 * i, 200, "left")
|
||||
end
|
||||
end
|
||||
|
||||
function ConfigScene:changeOption(rel)
|
||||
local len = table.getn(menu_screens)
|
||||
self.menu_state = (self.menu_state + len + rel - 1) % len + 1
|
||||
end
|
||||
|
||||
function ConfigScene:onInputPress(e)
|
||||
if e.input == "menu_decide" or e.input == "rotate_left" or e.scancode == "return" then
|
||||
scene = menu_screens[self.menu_state]()
|
||||
elseif e.input == "up" or e.scancode == "up" then
|
||||
self:changeOption(-1)
|
||||
elseif e.input == "down" or e.scancode == "down" then
|
||||
self:changeOption(1)
|
||||
elseif config.input and (
|
||||
e.input == "menu_back" or e.input == "rotate_right" or e.scancode == "backspace" or e.scancode == "delete"
|
||||
) then
|
||||
scene = TitleScene()
|
||||
end
|
||||
end
|
||||
|
||||
return ConfigScene
|
||||
100
scene/key_config.lua
Normal file
@@ -0,0 +1,100 @@
|
||||
local KeyConfigScene = Scene:extend()
|
||||
|
||||
KeyConfigScene.title = "Key Config"
|
||||
|
||||
require 'load.save'
|
||||
|
||||
local minos = {'R_d', 'O_d', 'Y_d', 'G_d', 'C_d', 'B_d', 'M_d'}
|
||||
|
||||
local configurable_inputs = {
|
||||
"menu_decide",
|
||||
"menu_back",
|
||||
"left",
|
||||
"right",
|
||||
"up",
|
||||
"down",
|
||||
"rotate_left",
|
||||
"rotate_left2",
|
||||
"rotate_right",
|
||||
"rotate_right2",
|
||||
}
|
||||
|
||||
local input_names = {
|
||||
menu_decide='Confirm Selection',
|
||||
menu_back = 'Go Back',
|
||||
left='Left',
|
||||
right='Right',
|
||||
up='Up',
|
||||
down='Down',
|
||||
rotate_left='Rotate Counter-clockwise',
|
||||
rotate_left2='Rotate Counter-clockwise (2)',
|
||||
rotate_right='Rotate Clockwise',
|
||||
rotate_right2='Rotate Clockwise (2)'
|
||||
}
|
||||
|
||||
|
||||
local function newSetInputs()
|
||||
local set_inputs = {}
|
||||
for i, input in ipairs(configurable_inputs) do
|
||||
set_inputs[input] = false
|
||||
end
|
||||
return set_inputs
|
||||
end
|
||||
|
||||
function KeyConfigScene:new()
|
||||
self.input_state = 1
|
||||
self.set_inputs = newSetInputs()
|
||||
self.new_input = {}
|
||||
end
|
||||
|
||||
function KeyConfigScene:update()
|
||||
end
|
||||
|
||||
function KeyConfigScene:render()
|
||||
mainBackground()
|
||||
for i, input in ipairs(configurable_inputs) do
|
||||
drawText(input_names[input], 40, 50 + i * 20, 200, "left")
|
||||
if self.set_inputs[input] then
|
||||
drawText(self.set_inputs[input], 240, 50 + i * 20, 300, "left")
|
||||
end
|
||||
end
|
||||
if self.input_state > table.getn(configurable_inputs) then
|
||||
drawText("Press enter to confirm, delete/backspace to retry" .. (config.input and ", escape to cancel" or ""),0,0,1000)
|
||||
else
|
||||
drawText("Press key input for " .. input_names[configurable_inputs[self.input_state]] .. ", tab to skip, escape to cancel",0,0,1000)
|
||||
drawText("Function keys (F1, F2, etc.), escape, and tab can't be changed", 0, 20,1000)
|
||||
end
|
||||
end
|
||||
|
||||
function KeyConfigScene:onInputPress(e)
|
||||
if e.type == "key" then
|
||||
-- function keys, escape, and tab are reserved and can't be remapped
|
||||
if e.scancode == "escape" then
|
||||
scene = InputConfigScene()
|
||||
elseif self.input_state > table.getn(configurable_inputs) then
|
||||
if e.scancode == "return" then
|
||||
-- save new input, then load next scene
|
||||
local had_config = config.input ~= nil
|
||||
if not config.input then config.input = {} end
|
||||
config.input.keys = self.new_input
|
||||
saveConfig()
|
||||
scene = had_config and InputConfigScene() or TitleScene()
|
||||
elseif e.scancode == "delete" or e.scancode == "backspace" then
|
||||
-- retry
|
||||
self.input_state = 1
|
||||
self.set_inputs = newSetInputs()
|
||||
self.new_input = {}
|
||||
end
|
||||
elseif e.scancode == "tab" then
|
||||
self.set_inputs[configurable_inputs[self.input_state]] = "skipped"
|
||||
self.input_state = self.input_state + 1
|
||||
elseif e.scancode ~= "escape" and not self.new_input[e.scancode] then
|
||||
-- all other keys can be configured
|
||||
self.set_inputs[configurable_inputs[self.input_state]] = "key " .. love.keyboard.getKeyFromScancode(e.scancode) .. " (" .. e.scancode .. ")"
|
||||
self.new_input[e.scancode] = configurable_inputs[self.input_state]
|
||||
self.input_state = self.input_state + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return KeyConfigScene
|
||||
24
scene/music_toggle.lua
Normal file
@@ -0,0 +1,24 @@
|
||||
local MusicToggleScene = Scene:extend()
|
||||
require 'load.save'
|
||||
|
||||
MusicToggleScene.title = "Play music during game:"
|
||||
|
||||
function MusicToggleScene:new()
|
||||
end
|
||||
|
||||
function MusicToggleScene:update()
|
||||
config["music"] = not config["music"]
|
||||
saveConfig()
|
||||
scene = TitleScene()
|
||||
end
|
||||
|
||||
function MusicToggleScene:render()
|
||||
end
|
||||
|
||||
function MusicToggleScene:changeOption(rel)
|
||||
end
|
||||
|
||||
function MusicToggleScene:onInputPress(e)
|
||||
end
|
||||
|
||||
return MusicToggleScene
|
||||
191
scene/name_entry.lua
Normal file
@@ -0,0 +1,191 @@
|
||||
local NameEntryScene = Scene:extend()
|
||||
local Grid = require 'game.grid'
|
||||
local binser = require 'libs.binser'
|
||||
require 'load.save'
|
||||
|
||||
NameEntryScene.title = "Game Start"
|
||||
|
||||
|
||||
function NameEntryScene:new()
|
||||
self.chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890."
|
||||
self.char_pos = 1
|
||||
self.name_entry = {'A','A','A'}
|
||||
self.entry_pos = 1
|
||||
self.entry_chars = self.name_entry[1]..self.name_entry[2]..self.name_entry[3]
|
||||
self.grid = Grid(10, 20)
|
||||
self.repeat_limit = 10
|
||||
self.repeat_counter = self.repeat_limit-1
|
||||
self.direction = nil
|
||||
self.grade = 0
|
||||
self.wins = 0
|
||||
self.plays = 0
|
||||
self.delete_confirm = false
|
||||
self.delete_input_count = 0
|
||||
self.gradeNames = {
|
||||
"19k", "18k", "17k", "16k", "15k", "14k", "13k", "12k", "11k",
|
||||
"10k", "9k", "8k", "7k", "6k", "5k", "4k", "3k", "2k", "1k",
|
||||
"1D", "2D", "3D", "4D", "5D", "6D", "7D", "8D", "9D"
|
||||
}
|
||||
if config['last_entry'] ~= nil then
|
||||
self.name_entry = {config['last_entry']:sub(1,1),config['last_entry']:sub(2,2),config['last_entry']:sub(3,3)}
|
||||
self.entry_pos = 3
|
||||
end
|
||||
score_file = io.open(HIscoreFILE, 'rb')
|
||||
if score_file ~= nil then
|
||||
self.hi_scores = binser.deserialize(score_file:read('a'))[1]
|
||||
else
|
||||
self.hi_scores = {"TRO",0,"MIT",0,"ROM",0,"ITR",0,"OMI",0}
|
||||
end
|
||||
end
|
||||
|
||||
function NameEntryScene:drawGradeList(left, top)
|
||||
love.graphics.setColor(0,0,0,0.5)
|
||||
love.graphics.rectangle("fill", left+3, top+3, 200, 240, 10, 10)
|
||||
love.graphics.setColor(0.05,0.05,0.05,1)
|
||||
love.graphics.rectangle("fill", left, top, 200, 240, 10, 10)
|
||||
drawText("Grade list:", left+15, top+10, 1000, "left")
|
||||
drawText("Beginner\n19 kyu\n18 kyu\n17 kyu\n16 kyu\n15 kyu\n14 kyu\n13 kyu\n12 kyu\n11 kyu\n10 kyu", left+15, top+25, 1000, "left")
|
||||
drawText("Intermed.\n9 kyu\n8 kyu\n7 kyu\n6 kyu\n5 kyu\n4 kyu\n3 kyu\n2 kyu\n1 kyu", left+80, top+25, 1000, "left")
|
||||
drawText("Expert\n1 Dan\n2 Dan\n3 Dan\n4 Dan\n5 Dan\n6 Dan\n7 Dan\n8 Dan\n9 Dan", left+145, top+25, 1000, "left")
|
||||
end
|
||||
|
||||
function NameEntryScene:render()
|
||||
mainBackground()
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.line(216,80,216,80+(16*self.grid.height))
|
||||
love.graphics.line(216+(16*self.grid.width),80,216+(16*self.grid.width),80+(16*self.grid.height))
|
||||
love.graphics.line(216,80+(16*self.grid.height),216+(16*self.grid.width),80+(16*self.grid.height))
|
||||
love.graphics.line(216,80,216+(16*self.grid.width),80)
|
||||
love.graphics.setColor(0, 0, 0, 1)
|
||||
love.graphics.rectangle(
|
||||
"fill", 216, 80,
|
||||
16 * self.grid.width, 16 * self.grid.height
|
||||
)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
drawText('Enter your initials:', 227, 180, 200, "left")
|
||||
drawBigText(self.entry_chars, 272, 200, 200, "left")
|
||||
drawText('o', 262+(self.entry_pos*14), 225, 200, "left")
|
||||
self:drawGradeList(397, 40)
|
||||
love.graphics.setColor(0,0,0,0.5)
|
||||
love.graphics.rectangle("fill", 400, 295, 130, 130, 10, 10)
|
||||
love.graphics.setColor(0.05,0.05,0.05,1)
|
||||
love.graphics.rectangle("fill", 397, 292, 130, 130, 10, 10)
|
||||
drawText("Best scores:", 410, 297, 1000, "left")
|
||||
i = 2
|
||||
while i <= 10 do
|
||||
drawText(self.hi_scores[i-1]..' - '..self.hi_scores[i], 410, 297+(i*10), 1000, "left")
|
||||
i = i + 2
|
||||
end
|
||||
if self.entry_pos == 4 then
|
||||
drawText('Press confirm\nto play', 255, 290, 1000)
|
||||
end
|
||||
if self.grade > 0 then
|
||||
drawText(string.format('Games: %s', self.plays), 255, 250, 1000)
|
||||
drawText(string.format('Grade: %s', self.gradeNames[self.grade]), 255, 270, 1000)
|
||||
--if not self.delete_confirm then
|
||||
-- drawText('Press up\nthree times\nto delete', 255, 330, 1000)
|
||||
--else
|
||||
-- drawText('Are you sure?\nPress down\nthree times\nto confirm', 255, 330, 1000)
|
||||
--end
|
||||
end
|
||||
end
|
||||
|
||||
function NameEntryScene:update()
|
||||
if self.direction == "left" then
|
||||
if self.repeat_counter >= self.repeat_limit then
|
||||
self.char_pos = self.char_pos - 1
|
||||
if self.char_pos < 1 then self.char_pos = 37 end
|
||||
self.name_entry[self.entry_pos] = self.chars:sub(self.char_pos, self.char_pos)
|
||||
self.repeat_counter = 0
|
||||
end
|
||||
self.repeat_counter = self.repeat_counter + 1
|
||||
elseif self.direction == "right" then
|
||||
if self.repeat_counter >= self.repeat_limit then
|
||||
self.char_pos = self.char_pos + 1
|
||||
if self.char_pos > 37 then self.char_pos = 1 end
|
||||
self.name_entry[self.entry_pos] = self.chars:sub(self.char_pos, self.char_pos)
|
||||
self.repeat_counter = 0
|
||||
end
|
||||
self.repeat_counter = self.repeat_counter + 1
|
||||
end
|
||||
self.entry_chars = self.name_entry[1]..self.name_entry[2]..self.name_entry[3]
|
||||
end
|
||||
|
||||
function NameEntryScene:onInputPress(e)
|
||||
if e.input == "menu_decide" or e.input == "rotate_left" or e.scancode == "return" then
|
||||
self.delete_confirm = false
|
||||
self.delete_input_count = 0
|
||||
if self.entry_pos == 4 then
|
||||
config['last_entry'] = name:upper()
|
||||
saveConfig()
|
||||
scene = GameScene(name:lower())
|
||||
else
|
||||
if self.entry_pos == 3 then
|
||||
name = string.lower(self.name_entry[1]..self.name_entry[2]..self.name_entry[3])
|
||||
grade_history = io.open(SAVE_DIR..name.."_grade_history.sav", 'rb')
|
||||
if grade_history ~= nil then
|
||||
grade_history = binser.deserialize(grade_history:read())[1]
|
||||
self.grade = grade_history[1]
|
||||
self.wins = grade_history[2]
|
||||
self.plays = grade_history[4]
|
||||
end
|
||||
end
|
||||
if self.entry_pos < 3 then
|
||||
self.name_entry[self.entry_pos] = self.chars:sub(self.char_pos, self.char_pos)
|
||||
self.name_entry[self.entry_pos+1] = self.chars:sub(self.char_pos, self.char_pos)
|
||||
end
|
||||
self.entry_pos = self.entry_pos + 1
|
||||
end
|
||||
elseif e.input == "left" or e.scancode == "left" then
|
||||
self.delete_confirm = false
|
||||
self.delete_input_count = 0
|
||||
self.direction = "left"
|
||||
elseif e.input == "right" or e.scancode == "right" then
|
||||
self.delete_confirm = false
|
||||
self.delete_input_count = 0
|
||||
self.direction = "right"
|
||||
elseif e.input == "menu_back" or e.input == "rotate_right" or e.scancode == "delete" or e.scancode == "backspace" then
|
||||
self.delete_confirm = false
|
||||
self.delete_input_count = 0
|
||||
if self.entry_pos == 1 then
|
||||
scene = TitleScene()
|
||||
else
|
||||
self.name_entry[self.entry_pos] = 'A'
|
||||
self.name_entry[self.entry_pos-1] = 'A'
|
||||
self.char_pos = 1
|
||||
self.entry_pos = self.entry_pos - 1
|
||||
self.grade = 0
|
||||
end
|
||||
end
|
||||
--elseif e.input == "up" or e.scancode == "up" then
|
||||
-- if self.delete_confirm then
|
||||
-- self.delete_confirm = false
|
||||
-- self.delete_input_count = 0
|
||||
-- end
|
||||
-- if self.entry_pos == 4 and self.grade > 0 and not self.delete_confirm then
|
||||
-- self.delete_input_count = self.delete_input_count + 1
|
||||
-- if self.delete_input_count >= 3 then
|
||||
-- self.delete_input_count = 0
|
||||
-- self.delete_confirm = true
|
||||
-- end
|
||||
-- end
|
||||
--elseif e.input == "down" or e.scancode == "down" then
|
||||
-- if not self.delete_confirm then self.delete_input_count = 0 end
|
||||
-- if self.entry_pos == 4 and self.delete_confirm then
|
||||
-- self.delete_input_count = self.delete_input_count + 1
|
||||
-- if self.delete_input_count >= 3 then
|
||||
-- love.filesystem.remove(string.lower(self.name_entry[1]..self.name_entry[2]..self.name_entry[3]).."_grade_history.sav")
|
||||
-- scene = TitleScene()
|
||||
-- end
|
||||
-- end
|
||||
--end
|
||||
end
|
||||
|
||||
function NameEntryScene:onInputRelease(e)
|
||||
if e.input == "left" or e.scancode == "left" or e.input == "right" or e.scancode == "right" then
|
||||
self.direction = nil
|
||||
self.repeat_counter = self.repeat_limit-1
|
||||
end
|
||||
end
|
||||
|
||||
return NameEntryScene
|
||||
150
scene/replay.lua
Normal file
@@ -0,0 +1,150 @@
|
||||
require 'funcs'
|
||||
local ReplaySelectScene = Scene:extend()
|
||||
|
||||
ReplaySelectScene.title = "Replay"
|
||||
|
||||
local minos = {'R_d', 'O_d', 'Y_d', 'G_d', 'C_d', 'B_d', 'M_d'}
|
||||
|
||||
function ReplaySelectScene:new()
|
||||
self:initList()
|
||||
PENTO_MODE = false
|
||||
end
|
||||
|
||||
function ReplaySelectScene:initList()
|
||||
self.replays = {}
|
||||
replay_list = love.filesystem.getDirectoryItems('saves/replays/')
|
||||
table.sort(replay_list, function(a,b) return a > b end)
|
||||
self.replay_text = {}
|
||||
self.page_flip = 16
|
||||
self.page = 1
|
||||
gradeNames = {
|
||||
"19k", "18k", "17k", "16k", "15k", "14k", "13k", "12k", "11k",
|
||||
"10k", "9k", "8k", "7k", "6k", "5k", "4k", "3k", "2k", "1k",
|
||||
"1D", "2D", "3D", "4D", "5D", "6D", "7D", "8D", "9D"
|
||||
}
|
||||
for i=1, #replay_list do
|
||||
replay_found = string.find(replay_list[i], "_replay.sav")
|
||||
if replay_found ~= nil then
|
||||
table.insert(self.replays, replay_list[i])
|
||||
line_components = {}
|
||||
for str in string.gmatch(replay_list[i], "([^".."_".."]+)") do
|
||||
table.insert(line_components, str)
|
||||
end
|
||||
player_name = line_components[2]
|
||||
player_grade = gradeNames[tonumber(line_components[3])]
|
||||
player_score = line_components[4]
|
||||
if #player_grade == 2 then player_grade = ' '..player_grade end
|
||||
table.insert(self.replay_text, player_name..' - '..player_grade..' - '..string.format('%6d',player_score))
|
||||
end
|
||||
end
|
||||
self.dialog = false
|
||||
self.dialog_select = 1
|
||||
self.replay_select = 1
|
||||
end
|
||||
|
||||
function ReplaySelectScene:render()
|
||||
mainBackground()
|
||||
love.graphics.setColor(0.4, 1, 1, 0.5)
|
||||
love.graphics.rectangle("fill", 0, 20 + 20 * self.replay_select, 640, 22)
|
||||
|
||||
drawText('Name - Grade - Score', 40, 20, 1000, "left")
|
||||
drawText(string.format('Page %s', self.page), 20, 440, 1000, "left")
|
||||
j = 1
|
||||
i = 1
|
||||
while i <= #self.replay_text do
|
||||
if j > self.page_flip then j = 1
|
||||
elseif j < 1 then j = self.page_flip
|
||||
end
|
||||
if i > (self.page-1) * self.page_flip and i <= self.page * self.page_flip then
|
||||
drawText(self.replay_text[i], 40, 20 + 20 * j, 1000, "left")
|
||||
end
|
||||
j = j + 1
|
||||
i = i + 1
|
||||
end
|
||||
if self.dialog then
|
||||
love.graphics.setColor(0, 0, 0, 0.5)
|
||||
love.graphics.rectangle("fill", 23, 43 + 20 * self.replay_select, 65, 40, 5, 5)
|
||||
love.graphics.setColor(0.3, 0.8, 0.8, 1)
|
||||
love.graphics.rectangle("fill", 20, 40 + 20 * self.replay_select, 65, 40, 5, 5)
|
||||
drawText("Play", 30, 40 + 20 * self.replay_select, 1000, "left")
|
||||
drawText("Delete", 30, 60 + 20 * self.replay_select, 1000, "left")
|
||||
drawText("o", 20, 40+((self.dialog_select-1) * 20) + (20 * self.replay_select), 1000, "left")
|
||||
|
||||
end
|
||||
if self.replays[1] == nil then
|
||||
drawText('No replays yet!', 40, 40, 1000, "left")
|
||||
end
|
||||
end
|
||||
|
||||
function ReplaySelectScene:changeOption(rel)
|
||||
local len = table.getn(self.replays)
|
||||
self.replay_select = self.replay_select + rel
|
||||
if self.replay_select + ((self.page-1) * self.page_flip) > len then
|
||||
self.page = 1
|
||||
self.replay_select = 1
|
||||
elseif self.replay_select < 1 and self.page == 1 then
|
||||
self.page = 1+(math.floor(len / self.page_flip))
|
||||
self.replay_select = len - ((self.page-1) * self.page_flip)
|
||||
end
|
||||
if self.replay_select > self.page_flip then
|
||||
self.page = self.page + 1
|
||||
self.replay_select = 1
|
||||
elseif self.replay_select < 1 then
|
||||
self.page = self.page - 1
|
||||
self.replay_select = self.page_flip
|
||||
end
|
||||
end
|
||||
|
||||
function ReplaySelectScene:changeDialog(rel)
|
||||
self.dialog_select = self.dialog_select + rel
|
||||
if self.dialog_select > 2 then self.dialog_select = 1
|
||||
elseif self.dialog_select < 1 then self.dialog_select = 2
|
||||
end
|
||||
end
|
||||
|
||||
function ReplaySelectScene:onInputPress(e)
|
||||
selected_replay = self.replays[self.replay_select + ((self.page-1) * self.page_flip)]
|
||||
selected_replay_text = self.replay_text[self.replay_select + ((self.page-1) * self.page_flip)]
|
||||
if e.input == "menu_decide" or e.input == "rotate_left" or e.scancode == "return" then
|
||||
if self.replays[1] == nil then
|
||||
scene = TitleScene()
|
||||
--if not self.dialog then
|
||||
-- self.dialog_select = 1
|
||||
-- self.dialog = true
|
||||
--elseif self.dialog_select == 2 then
|
||||
-- love.filesystem.remove(selected_replay)
|
||||
-- self:initList()
|
||||
--elseif self.dialog_select == 1 then
|
||||
-- line_components = {}
|
||||
-- for str in string.gmatch(selected_replay_text, "([^".."-".."]+)") do
|
||||
-- table.insert(line_components, str)
|
||||
-- end
|
||||
-- player_name = line_components[1]
|
||||
-- player_grade = string.gsub(line_components[2], " ", "")
|
||||
-- scene = GameScene(player_name, selected_replay, player_grade)
|
||||
--end
|
||||
else
|
||||
line_components = {}
|
||||
for str in string.gmatch(selected_replay_text, "([^".."-".."]+)") do
|
||||
table.insert(line_components, str)
|
||||
end
|
||||
player_name = line_components[1]
|
||||
player_grade = string.gsub(line_components[2], " ", "")
|
||||
scene = GameScene(player_name, selected_replay, player_grade)
|
||||
end
|
||||
elseif e.input == "up" or e.scancode == "up" then
|
||||
if self.replays[1] == nil then scene = TitleScene() end
|
||||
if not self.dialog then self:changeOption(-1)
|
||||
else self:changeDialog(-1) end
|
||||
elseif e.input == "down" or e.scancode == "down" then
|
||||
if self.replays[1] == nil then scene = TitleScene() end
|
||||
if not self.dialog then self:changeOption(1)
|
||||
else self:changeDialog(1) end
|
||||
elseif e.input == "menu_back" or e.input == "rotate_right" or e.scancode == "backspace" or e.scancode == "delete" then
|
||||
if self.replays[1] == nil then scene = TitleScene() end
|
||||
if not self.dialog then scene = TitleScene()
|
||||
else self.dialog = false end
|
||||
end
|
||||
end
|
||||
|
||||
return ReplaySelectScene
|
||||
158
scene/stick_config.lua
Normal file
@@ -0,0 +1,158 @@
|
||||
local StickConfigScene = Scene:extend()
|
||||
|
||||
StickConfigScene.title = "Controller Config"
|
||||
|
||||
require 'load.save'
|
||||
|
||||
local minos = {'R_d', 'O_d', 'Y_d', 'G_d', 'C_d', 'B_d', 'M_d'}
|
||||
|
||||
local configurable_inputs = {
|
||||
"menu_decide",
|
||||
"menu_back",
|
||||
"left",
|
||||
"right",
|
||||
"up",
|
||||
"down",
|
||||
"rotate_left",
|
||||
"rotate_left2",
|
||||
"rotate_right",
|
||||
"rotate_right2",
|
||||
}
|
||||
|
||||
local input_names = {
|
||||
menu_decide='Confirm Selection',
|
||||
menu_back = 'Go Back',
|
||||
left='Left',
|
||||
right='Right',
|
||||
up='Up',
|
||||
down='Down',
|
||||
rotate_left='Rotate Counter-clockwise',
|
||||
rotate_left2='Rotate Counter-clockwise (2)',
|
||||
rotate_right='Rotate Clockwise',
|
||||
rotate_right2='Rotate Clockwise (2)'
|
||||
}
|
||||
|
||||
local function newSetInputs()
|
||||
local set_inputs = {}
|
||||
for i, input in ipairs(configurable_inputs) do
|
||||
set_inputs[input] = false
|
||||
end
|
||||
return set_inputs
|
||||
end
|
||||
|
||||
function StickConfigScene:new()
|
||||
self.input_state = 1
|
||||
self.set_inputs = newSetInputs()
|
||||
self.new_input = {}
|
||||
self.axis_timer = 0
|
||||
end
|
||||
|
||||
function StickConfigScene:update()
|
||||
end
|
||||
|
||||
function StickConfigScene:render()
|
||||
mainBackground()
|
||||
for i, input in ipairs(configurable_inputs) do
|
||||
drawText(input_names[input], 40, 50 + i * 20, 200, "left")
|
||||
if self.set_inputs[input] then
|
||||
drawText(self.set_inputs[input], 240, 50 + i * 20, 300, "left")
|
||||
end
|
||||
end
|
||||
if self.input_state > table.getn(configurable_inputs) then
|
||||
drawText("Press enter to confirm, delete/backspace to retry" .. (config.input and ", escape to cancel" or ""), 0, 0, 1000)
|
||||
else
|
||||
drawText("Press joystick input for " .. input_names[configurable_inputs[self.input_state]] .. ", tab to skip, escape to cancel", 0, 0, 1000)
|
||||
end
|
||||
|
||||
self.axis_timer = self.axis_timer + 1
|
||||
end
|
||||
|
||||
local function addJoystick(input, name)
|
||||
if not input[name] then
|
||||
input[name] = {}
|
||||
end
|
||||
end
|
||||
|
||||
function StickConfigScene:onInputPress(e)
|
||||
if e.type == "key" then
|
||||
-- function keys, escape, and tab are reserved and can't be remapped
|
||||
if e.scancode == "escape" then
|
||||
scene = InputConfigScene()
|
||||
elseif self.input_state > table.getn(configurable_inputs) then
|
||||
if e.scancode == "return" then
|
||||
-- save new input, then load next scene
|
||||
local had_config = config.input ~= nil
|
||||
if not config.input then config.input = {} end
|
||||
config.input.joysticks = self.new_input
|
||||
saveConfig()
|
||||
scene = had_config and InputConfigScene() or TitleScene()
|
||||
elseif e.scancode == "delete" or e.scancode == "backspace" then
|
||||
-- retry
|
||||
self.input_state = 1
|
||||
self.set_inputs = newSetInputs()
|
||||
self.new_input = {}
|
||||
end
|
||||
elseif e.scancode == "tab" then
|
||||
self.set_inputs[configurable_inputs[self.input_state]] = "skipped"
|
||||
self.input_state = self.input_state + 1
|
||||
end
|
||||
elseif string.sub(e.type, 1, 3) == "joy" then
|
||||
if self.input_state <= table.getn(configurable_inputs) then
|
||||
if e.type == "joybutton" then
|
||||
addJoystick(self.new_input, e.name)
|
||||
if not self.new_input[e.name].buttons then
|
||||
self.new_input[e.name].buttons = {}
|
||||
end
|
||||
if self.new_input[e.name].buttons[e.button] then return end
|
||||
self.set_inputs[configurable_inputs[self.input_state]] =
|
||||
"jbtn " ..
|
||||
e.button ..
|
||||
" " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
|
||||
self.new_input[e.name].buttons[e.button] = configurable_inputs[self.input_state]
|
||||
self.input_state = self.input_state + 1
|
||||
elseif e.type == "joyaxis" then
|
||||
if (e.axis ~= self.last_axis or self.axis_timer > 30) and math.abs(e.value) >= 1 then
|
||||
addJoystick(self.new_input, e.name)
|
||||
if not self.new_input[e.name].axes then
|
||||
self.new_input[e.name].axes = {}
|
||||
end
|
||||
if not self.new_input[e.name].axes[e.axis] then
|
||||
self.new_input[e.name].axes[e.axis] = {}
|
||||
end
|
||||
if (
|
||||
self.new_input[e.name].axes[e.axis][e.value >= 1 and "positive" or "negative"]
|
||||
) then return end
|
||||
self.set_inputs[configurable_inputs[self.input_state]] =
|
||||
"jaxis " ..
|
||||
(e.value >= 1 and "+" or "-") .. e.axis ..
|
||||
" " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
|
||||
self.new_input[e.name].axes[e.axis][e.value >= 1 and "positive" or "negative"] = configurable_inputs[self.input_state]
|
||||
self.input_state = self.input_state + 1
|
||||
self.last_axis = e.axis
|
||||
self.axis_timer = 0
|
||||
end
|
||||
elseif e.type == "joyhat" then
|
||||
if e.direction ~= "c" then
|
||||
addJoystick(self.new_input, e.name)
|
||||
if not self.new_input[e.name].hats then
|
||||
self.new_input[e.name].hats = {}
|
||||
end
|
||||
if not self.new_input[e.name].hats[e.hat] then
|
||||
self.new_input[e.name].hats[e.hat] = {}
|
||||
end
|
||||
if self.new_input[e.name].hats[e.hat][e.direction] then
|
||||
return
|
||||
end
|
||||
self.set_inputs[configurable_inputs[self.input_state]] =
|
||||
"jhat " ..
|
||||
e.hat .. " " .. e.direction ..
|
||||
" " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
|
||||
self.new_input[e.name].hats[e.hat][e.direction] = configurable_inputs[self.input_state]
|
||||
self.input_state = self.input_state + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return StickConfigScene
|
||||
106
scene/title.lua
Normal file
@@ -0,0 +1,106 @@
|
||||
local TitleScene = Scene:extend()
|
||||
require 'load.save'
|
||||
require 'funcs'
|
||||
|
||||
TitleScene.title = "Title"
|
||||
TitleScene.restart_message = false
|
||||
|
||||
local main_menu_screens = {
|
||||
NameEntryScene,
|
||||
ReplaySelectScene,
|
||||
TrainingScene,
|
||||
InputConfigScene,
|
||||
FullscreenScene,
|
||||
MusicToggleScene,
|
||||
ExitScene,
|
||||
}
|
||||
|
||||
function TitleScene:new()
|
||||
if sounds['bgm_firsthalf']:isPlaying() or sounds['bgm_secondhalf']:isPlaying() or not config["music"] then
|
||||
love.audio.stop()
|
||||
end
|
||||
self.main_menu_state = 1
|
||||
PENTO_MODE = false
|
||||
self.code = {0,0,0,0,0,0,0,0}
|
||||
end
|
||||
|
||||
function TitleScene:update()
|
||||
end
|
||||
|
||||
function TitleScene:drawCredits(top, left)
|
||||
love.graphics.setColor(0,0,0,0.7)
|
||||
love.graphics.rectangle("fill", top, left, 335, 345, 10, 10)
|
||||
drawText("Design & Programming mycophobia ", 6+top, 6+left, 1000, "left")
|
||||
drawText("Programming Cambridge contributors", 6+top, 21+left, 1000, "left")
|
||||
drawText("Music Jerry Martin", 6+top, 36+left, 1000, "left")
|
||||
drawText(" Juraj Stanik", 6+top, 51+left, 1000, "left")
|
||||
drawText("RNG Consultant colour_thief", 6+top, 66+left, 1000, "left")
|
||||
drawText("Mac Launcher nightmareci", 6+top, 81+left, 1000, "left")
|
||||
drawText("People Who Tested switchpalacecorner", 6+top, 106+left, 1000, "left")
|
||||
drawText("and/or Offered esquatre", 6+top, 121+left, 1000, "left")
|
||||
drawText("Cool Suggestions Kirby703", 6+top, 136+left, 1000, "left")
|
||||
drawText(" netdoll", 6+top, 151+left, 1000, "left")
|
||||
drawText(" lindtobias", 6+top, 166+left, 1000, "left")
|
||||
drawText(" zaphod77", 6+top, 181+left, 1000, "left")
|
||||
drawText(" Arch Nemesis", 6+top, 196+left, 1000, "left")
|
||||
drawText(" dtet_enjoyer", 6+top, 211+left, 1000, "left")
|
||||
drawText(" woozy", 6+top, 226+left, 1000, "left")
|
||||
drawText(" AgentBasey", 6+top, 241+left, 1000, "left")
|
||||
drawText(" Zircean", 6+top, 256+left, 1000, "left")
|
||||
drawText(" Eden GT", 6+top, 271+left, 1000, "left")
|
||||
drawText("Special Thanks theabsolute.plus", 6+top, 291+left, 1000, "left")
|
||||
drawText(" FYAD/Imp Zone Collective", 6+top, 306+left, 1000, "left")
|
||||
drawText(" All Version 1 Players", 6+top, 321+left, 1000, "left")
|
||||
end
|
||||
|
||||
function TitleScene:render()
|
||||
mainBackground()
|
||||
love.graphics.setColor(0,0,0,0.7)
|
||||
love.graphics.rectangle("fill", 14, 174, 260, 200, 10, 10)
|
||||
love.graphics.setColor(0,0,0,0.7)
|
||||
love.graphics.rectangle("fill", 14, 400, 605, 75, 10, 10)
|
||||
love.graphics.setColor(0.4, 1, 1, 0.5)
|
||||
love.graphics.rectangle("fill", 20, 198 + 20 * self.main_menu_state, 240, 22)
|
||||
drawBigText('Tromi', 30, 180, 120, "left")
|
||||
drawText('version 2', 110, 193, 120, "left")
|
||||
self:drawCredits(300, 40)
|
||||
drawText("mycophobia.org", 20, 405, 1000)
|
||||
drawText("Based on Cambridge - t-sp.in/cambridge", 20, 420, 1000)
|
||||
drawText("Music for Tromi by Jerry Martin, all rights reserved - jerrymartinmusic.com", 20, 435, 1000)
|
||||
drawText("Game backgrounds by Pixabay users Joe_hackney, yokim, Favorisxp, Any_Ann, VisualSkyFX ", 20, 450, 1000)
|
||||
if config["music"] == true then
|
||||
drawText("On", 230, 320, 1000)
|
||||
else
|
||||
drawText("Off", 230, 320, 1000)
|
||||
end
|
||||
for i, screen in pairs(main_menu_screens) do
|
||||
drawText(screen.title, 40, 200 + 20 * i, 1200, "left")
|
||||
end
|
||||
if table.concat(self.code, ',') == '1,1,1,1,-1,-1,-1,-1' then PENTO_MODE = true end
|
||||
if PENTO_MODE then
|
||||
drawBigText('PENT MODE', 30, 100, 120, "left")
|
||||
end
|
||||
end
|
||||
|
||||
function TitleScene:changeOption(rel)
|
||||
local len = table.getn(main_menu_screens)
|
||||
self.main_menu_state = (self.main_menu_state + len + rel - 1) % len + 1
|
||||
end
|
||||
|
||||
function TitleScene:onInputPress(e)
|
||||
if e.input == "menu_decide" or e.input == "rotate_left" or e.scancode == "return" then
|
||||
scene = main_menu_screens[self.main_menu_state]()
|
||||
elseif e.input == "up" or e.scancode == "up" then
|
||||
self:changeOption(-1)
|
||||
elseif e.input == "down" or e.scancode == "down" then
|
||||
self:changeOption(1)
|
||||
elseif e.input == "left" or e.scancode == "left" then
|
||||
table.remove(self.code, 8)
|
||||
table.insert(self.code, 1, -1)
|
||||
elseif e.input == "right" or e.scancode == "right" then
|
||||
table.remove(self.code, 8)
|
||||
table.insert(self.code, 1, 1)
|
||||
end
|
||||
end
|
||||
|
||||
return TitleScene
|
||||
64
scene/training.lua
Normal file
@@ -0,0 +1,64 @@
|
||||
local TrainingScene = Scene:extend()
|
||||
|
||||
TrainingScene.title = "Max Gravity Training"
|
||||
|
||||
require 'load.save'
|
||||
|
||||
function TrainingScene:new()
|
||||
game_mode = require 'game.gamemode'
|
||||
if PENTO_MODE then
|
||||
ruleset = require 'game.rotation_pent'
|
||||
else
|
||||
ruleset = require 'game.rotation'
|
||||
end
|
||||
self.retry_mode = game_mode
|
||||
self.retry_ruleset = ruleset
|
||||
self.secret_inputs = inputs
|
||||
self.game = game_mode()
|
||||
self.ruleset = ruleset(self.game)
|
||||
self.game:initialize(self.ruleset)
|
||||
self.inputs = {
|
||||
left=false,
|
||||
right=false,
|
||||
up=false,
|
||||
down=false,
|
||||
rotate_left=false,
|
||||
rotate_left2=false,
|
||||
rotate_right=false,
|
||||
rotate_right2=false,
|
||||
rotate_180=false,
|
||||
hold=false,
|
||||
}
|
||||
self.paused = false
|
||||
end
|
||||
|
||||
function TrainingScene:update()
|
||||
local inputs = {}
|
||||
for input, value in pairs(self.inputs) do
|
||||
inputs[input] = value
|
||||
end
|
||||
self.game:update(inputs, self.ruleset)
|
||||
self.game.grid:update()
|
||||
end
|
||||
|
||||
function TrainingScene:render()
|
||||
self.game:draw(self.paused)
|
||||
end
|
||||
|
||||
function TrainingScene:onInputPress(e)
|
||||
if (self.game.game_over or self.game.completed) and (e.input == "menu_decide" or e.input == "menu_back" or e.input == "rotate_right") and self.game.game_over_frames > 50 then
|
||||
scene = TitleScene()
|
||||
elseif (e.input == "menu_back") then
|
||||
scene = TitleScene()
|
||||
elseif e.input and string.sub(e.input, 1, 5) ~= "menu_" then
|
||||
self.inputs[e.input] = true
|
||||
end
|
||||
end
|
||||
|
||||
function TrainingScene:onInputRelease(e)
|
||||
if e.input and string.sub(e.input, 1, 5) ~= "menu_" then
|
||||
self.inputs[e.input] = false
|
||||
end
|
||||
end
|
||||
|
||||
return TrainingScene
|
||||