Compare commits

..

1 Commits

Author SHA1 Message Date
Ishaan Bhardwaj
9f570306f5 Init 2021-01-26 13:36:50 -05:00
52 changed files with 1930 additions and 1488 deletions

View File

@@ -1,15 +1,13 @@
![Cambridge Banner](https://t-sp.in/public/img/cambridge.png)
![Cambridge Banner](https://cdn.discordapp.com/attachments/764432435802013709/767724895076614154/cambridge_logo_lt.png)
Cambridge
=========
Welcome to Cambridge, the next open-source falling-block game engine!
The project is written and maintained exclusively by [SashLilac](https://github.com/SashLilac), [joezeng](https://github.com/joezeng) and [Oshisaure](https://github.com/oshisaure)!
This fork is written and maintained exclusively by [SashLilac](https://github.com/SashLilac), [joezeng](https://github.com/joezeng) and [Oshisaure](https://github.com/oshisaure)!
The Discord server has been reopened! https://discord.gg/AADZUmgsph
The game also has a website now with more detail than seen on this README: https://t-sp.in/cambridge
Join our Discord server for help and a welcoming community! https://discord.gg/mteMJw4
Credits
-------
@@ -19,7 +17,19 @@ Credits
- [The Tetra Legends Discord](http://discord.com/invite/7hMx5r2) for supporting me and playtesting!
- [The Absolute Plus](https://discord.gg/6Gf2awJ) for being another source of motivation!
More special thanks can be found in-game, under the "Credits" menu.
The following people in no particular order also helped with the project:
- [Hailey](https://github.com/haileylgbt)
- CylinderKnot
- MarkGamed7794
- [Mizu](https://github.com/rexxt)
- MattMayuga
- Kitaru
- switchpalacecorner
- [sinefuse](https://github.com/sinefuse)
- [2Tie](https://github.com/2Tie)
- [nightmareci](https://github.com/nightmareci)
- [MyPasswordIsWeak](https://github.com/MyPasswordIsWeak)
- [Dr Ocelot](https://github.com/Dr-Ocelot)
![Cambridge Logo](https://cdn.discordapp.com/attachments/625496179433668635/763363717730664458/Icon_2.png)
@@ -30,13 +40,7 @@ Playing the game
You do not need LÖVE on Windows, as it comes bundled with the program.
#### Stable release
To get the stable release, simply download either `cambridge-win32.zip` (32-bit) or `cambridge-windows.zip` (64-bit) in the [latest release](https://github.com/sashlilac/cambridge/releases/latest).
All assets needed are bundled with the executable.
#### Bleeding edge
To get the stable release, simply download the ZIP in the latest release. All assets needed are bundled with the executable.
If you want the bleeding edge version, download [this](https://github.com/SashLilac/cambridge/archive/master.zip).
@@ -78,7 +82,13 @@ It should run automatically!
## Installing modpacks
For instructions on how to install modpacks, go to [this](https://github.com/SashLilac/cambridge-modpack) mod pack to get a taste of the mod potential.
Simply drag your mode, ruleset, and randomizer Lua files into their respective [directory](https://love2d.org/wiki/love.filesystem), and they should appear automatically.
You can also load custom assets through this way, assuming you preserve the directory structure.
**WARNING:** The .exe / .love files and the bleeding edge releases have different save directories. Read the above link carefully!
For more detailed instructions, install [this](https://github.com/SashLilac/cambridge-modpack) mod pack to get a taste of the mod potential.
License
-------

View File

@@ -6,6 +6,5 @@ function love.conf(t)
t.window.title = "Cambridge"
t.window.width = 640
t.window.height = 480
t.window.icon = "res/img/cambridge_icon.png"
t.window.vsync = false
end

View File

@@ -3,11 +3,19 @@ Game modes
There are several classes of game modes. The modes that originate from other games are organized by suffix:
* The "C" series stand for "Classic" games, games that were produced before around 1992-1993 and generally have no wallkicks or lock delay.
* C84 - The original version from the Electronika 60.
* C88 - Sega Tetris.
* C89 - Nintendo / NES Tetris.
* The "A" series stand for "Arika" games, or games in the Tetris the Grand Master series.
* A1 - Tetris The Grand Master (the original from 1998).
* A2 - Tetris The Absolute The Grand Master 2 PLUS.
* A3 - Tetris The Grand Master 3 Terror-Instinct.
* AX - Tetris The Grand Master ACE (X for Xbox).
* The "G" series stand for "Guideline" games, or games that follow the Tetris Guideline.
* GF - Tetris Friends (2007-2019)
* GJ - Tetris Online Japan (2005-2011)
* N stands for Nullpomino, only used for Phantom Mania N.
MARATHON
--------
@@ -20,6 +28,8 @@ From other games:
* **MARATHON A1**: Tetris the Grand Master 1.
* **MARATHON A2**: Tetris the Grand Master 2 (TAP Master).
* **MARATHON A3**: Tetris the Grand Master 3 (no exams).
* **MARATHON AX4**: Another mode from TGM Ace.
* **MARATHON C89**: Nintendo NES Tetris. Can you transition and make it to the killscreen?
SURVIVAL
@@ -33,7 +43,14 @@ From other games:
* **SURVIVAL A1**: 20G mode from Tetris the Grand Master.
* **SURVIVAL A2**: T.A. Death.
* **SURVIVAL A3**: Ti Shirase.
* **SURVIVAL AX**: Another mode from TGM Ace.
RACE
----
Modes with no levels, just a single timed goal.
* **Race 40**: How fast can you clear 40 lines? No limits, no holds barred.
PHANTOM MANIA
@@ -52,4 +69,8 @@ OTHER MODES
* **Strategy**: How well can you plan ahead your movements? Can you handle only having a short time to place each piece?
* **Big A2**: Marathon A2 but all the pieces are BIG!
* **TetrisGram™ Pacer Test**: is a multi-stage piece-placing ability test that progressively gets more difficult as it continues.
* **Interval Training**: 30 seconds per section. 20G. 15 frames of lock delay. How long can you last?
* **Demon Mode**: An original mode from Oshisaure! Can you push through the ever faster levels and not get denied?

View File

@@ -80,16 +80,9 @@ end
function table.contains(table, element)
for _, value in pairs(table) do
if value == element then
return true
end
if value == element then
return true
end
end
return false
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
end

View File

@@ -2,48 +2,12 @@
-- 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 strict = true
--------------------------------------------------------------------------------
local bigint = {}
local mt = {
__add = function(lhs, rhs)
return bigint.add(lhs, rhs)
end,
__unm = function()
return bigint.negate(self)
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()
return bigint.unserialize(self, "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
@@ -64,7 +28,46 @@ function bigint.new(num)
return newint
end
setmetatable(self, mt)
setmetatable(self, {
__add = function(lhs, rhs)
return bigint.add(lhs, rhs)
end,
__unm = function()
if (self.sign == "+") then
self.sign = "-"
else
self.sign = "+"
end
return self
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,
__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,
__tostring = function()
return bigint.unserialize(self, "s")
end
})
if (num) then
local num_string = tostring(num)
@@ -84,13 +87,11 @@ end
-- 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")
assert(type(big.sign) == "string", "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")
assert(type(digit) == "number", digit .. " is not a number")
assert(digit < 10, digit .. " is greater than or equal to 10")
end
end
return true
@@ -105,24 +106,6 @@ function bigint.abs(big)
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)
@@ -157,7 +140,7 @@ function bigint.unserialize(big, output_type, precision)
-- 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)
precision = 3
else
assert(precision > 0, "Precision cannot be less than 1")
assert(math.floor(precision) == precision,
@@ -166,18 +149,17 @@ function bigint.unserialize(big, output_type, precision)
-- 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])
num = num .. big.digits[1]
if (precision > 1) then
num = num .. "."
for i = 1, (precision - 1) do
num = num .. math.floor(big.digits[i + 1])
num = num .. 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
or (output_type == "h")) then
-- Human-readable output contributed by 123eee555
local name
@@ -456,6 +438,7 @@ function bigint.multiply(big1, big2)
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
@@ -466,23 +449,13 @@ function bigint.exponentiate(big, power)
if (bigint.compare(exp, bigint.new(0), "==")) then
return bigint.new(1)
elseif (bigint.compare(exp, bigint.new(1), "==")) then
return big:clone()
return big
else
local result = bigint.new(1)
local base = big:clone()
local result = 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
while (bigint.compare(exp, bigint.new(1), ">")) do
result = bigint.multiply(result, big)
exp = bigint.subtract(exp, bigint.new(1))
end
return result
@@ -498,7 +471,7 @@ function bigint.divide_raw(big1, big2)
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()
return bigint.new(0), bigint.new(0)
else
assert(bigint.compare(big2, bigint.new(0), "!="), "error: divide by zero")
assert(big1.sign == "+", "error: big1 is not positive")
@@ -506,35 +479,54 @@ function bigint.divide_raw(big1, big2)
local result = bigint.new()
local dividend = bigint.new() -- Dividend of a single operation
local dividend = bigint.new() -- Dividend of a single operation, not the
-- dividend of the overall function
local divisor = big2:clone()
local factor = 1
local neg_zero = bigint.new(0)
neg_zero.sign = "-"
-- Walk left to right among digits in the dividend, like in long
-- division
for _, digit in pairs(big1.digits) do
dividend.digits[#dividend.digits + 1] = digit
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))
-- The dividend is smaller than the divisor, so a zero is appended
-- to the result and the loop ends
if (bigint.compare(dividend, divisor, "<")) then
if (#result.digits > 0) then -- Don't add leading zeroes
result.digits[#result.digits + 1] = 0
end
else
-- Find the maximum number of divisors that fit into the
-- dividend
factor = 0
while (bigint.compare(divisor, dividend, "<=")) do
divisor = bigint.add(divisor, big2)
factor = factor + 1
end
-- Append the factor to the result
if (factor == 10) then
-- Fixes a weird bug that introduces a new bug if fixed by
-- changing the comparison in the while loop to "<="
result.digits[#result.digits] = 1
result.digits[#result.digits + 1] = 0
else
result.digits[#result.digits + 1] = factor
end
-- Subtract the divisor from the dividend to obtain the
-- remainder, which is the new dividend for the next loop
dividend = bigint.subtract(dividend,
bigint.subtract(divisor, big2))
-- Reset the divisor
divisor = big2:clone()
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
-- The remainder of the final loop is returned as the function's
-- overall remainder
return result, dividend
end
end

View File

@@ -1,17 +1,8 @@
local binser = require 'libs.binser'
function loadSave()
local info = love.filesystem.getInfo(love.filesystem.getSaveDirectory())
if not info or info.type ~= "directory" then
love.filesystem.remove(love.filesystem.getSaveDirectory())
love.filesystem.createDirectory(love.filesystem.getSaveDirectory())
end
config = loadFromFile(
love.filesystem.getSaveDirectory() .. '/config.sav'
)
highscores = loadFromFile(
love.filesystem.getSaveDirectory() .. '/highscores.sav'
)
config = loadFromFile('config.sav')
highscores = loadFromFile('highscores.sav')
end
function loadFromFile(filename)
@@ -22,40 +13,12 @@ function loadFromFile(filename)
return save_data[1]
end
function initConfig()
if not config.das then config.das = 10 end
if not config.arr then config.arr = 2 end
if not config.dcd then config.dcd = 0 end
if not config.sfx_volume then config.sfx_volume = 0.5 end
if not config.bgm_volume then config.bgm_volume = 0.5 end
if config.fullscreen == nil then config.fullscreen = false end
if config.secret == nil then config.secret = false end
if not config.gamesettings then config.gamesettings = {} end
for _, option in ipairs(GameConfigScene.options) do
if not config.gamesettings[option[1]] then
config.gamesettings[option[1]] = 1
end
end
if not config.input then
scene = InputConfigScene()
else
if config.current_mode then current_mode = config.current_mode end
if config.current_ruleset then current_ruleset = config.current_ruleset end
scene = TitleScene()
end
end
function saveConfig()
binser.writeFile(
love.filesystem.getSaveDirectory() .. '/config.sav', config
)
binser.writeFile('config.sav', config)
end
function saveHighscores()
binser.writeFile(
love.filesystem.getSaveDirectory() .. '/highscores.sav', highscores
)
binser.writeFile('highscores.sav', highscores)
end

View File

@@ -1 +0,0 @@
version = "v0.3-beta5.2"

119
main.lua
View File

@@ -8,31 +8,44 @@ function love.load()
require "load.bgm"
require "load.save"
require "load.bigint"
require "load.version"
loadSave()
require "scene"
--config["side_next"] = false
--config["reverse_rotate"] = true
--config["das_last_key"] = false
--config["fullscreen"] = false
config["fullscreen"] = false
love.window.setMode(love.graphics.getWidth(), love.graphics.getHeight(), {resizable = true});
-- used for screenshots
GLOBAL_CANVAS = love.graphics.newCanvas()
-- init config
initConfig()
if not config.das then config.das = 10 end
if not config.arr then config.arr = 2 end
if not config.dcd then config.dcd = 0 end
if not config.sfx_volume then config.sfx_volume = 0.5 end
if not config.bgm_volume then config.bgm_volume = 0.5 end
if config.secret == nil then config.secret = false
elseif config.secret == true then playSE("welcome") end
love.window.setFullscreen(config["fullscreen"])
if config.secret then playSE("welcome") end
if not config.gamesettings then
config.gamesettings = {}
config["das_last_key"] = false
else
config["das_last_key"] = config.gamesettings.das_last_key == 2
end
for _, option in ipairs(GameConfigScene.options) do
if not config.gamesettings[option[1]] then
config.gamesettings[option[1]] = 1
end
end
if not config.input then
scene = InputConfigScene()
else
if config.current_mode then current_mode = config.current_mode end
if config.current_ruleset then current_ruleset = config.current_ruleset end
scene = TitleScene()
end
-- import custom modules
initModules()
end
function initModules()
game_modes = {}
mode_list = love.filesystem.getDirectoryItems("tetris/modes")
for i=1,#mode_list do
@@ -53,6 +66,7 @@ function initModules()
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end)
table.sort(rulesets, function(a,b)
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end)
end
local TARGET_FPS = 60
@@ -94,10 +108,7 @@ function love.update(dt)
end
function love.draw()
love.graphics.setCanvas(GLOBAL_CANVAS)
love.graphics.clear()
love.graphics.push()
love.graphics.push()
-- get offset matrix
love.graphics.setDefaultFilter("linear", "nearest")
@@ -109,20 +120,15 @@ function love.draw()
(height - scale_factor * 480) / 2
)
love.graphics.scale(scale_factor)
scene:render()
love.graphics.pop()
love.graphics.setCanvas()
love.graphics.setColor(1,1,1,1)
love.graphics.draw(GLOBAL_CANVAS)
end
function love.keypressed(key, scancode)
-- 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()
@@ -134,16 +140,6 @@ function love.keypressed(key, scancode)
scene.restart_message = true
if config.secret then playSE("mode_decide")
else playSE("erase") end
-- f12 is reserved for saving screenshots
elseif scancode == "f12" then
local ss_name = os.date("ss/%Y-%m-%d_%H-%M-%S.png")
local info = love.filesystem.getInfo("ss")
if not info or info.type ~= "directory" then
love.filesystem.remove("ss")
love.filesystem.createDirectory("ss")
end
print("Saving screenshot as "..ss_name)
GLOBAL_CANVAS:newImageData():encode("png", ss_name)
-- function keys are reserved
elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f[1-9][0-9]+$") then
return
@@ -214,13 +210,13 @@ function love.joystickaxis(joystick, axis, value)
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"]
if math.abs(value) >= 0.5 then
input_pressed = config.input.joysticks[joystick:getName()].axes[axis][value >= 0.5 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
if math.abs(value) >= 0.5 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})
@@ -228,14 +224,6 @@ function love.joystickaxis(joystick, axis, value)
end
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
@@ -252,47 +240,17 @@ function love.joystickhat(joystick, hat, direction)
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
scene:onInputPress({input=input_pressed, type="joyhat", name=joystick:getName(), hat=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
scene:onInputPress({input=nil, type="joyhat", name=joystick:getName(), hat=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
@@ -303,8 +261,3 @@ function love.focus(f)
pauseBGM()
end
end
function love.resize(w, h)
GLOBAL_CANVAS:release()
GLOBAL_CANVAS = love.graphics.newCanvas(w, h)
end

View File

@@ -1,2 +1,2 @@
tar -a -c -f cambridge.zip libs load res scene tetris conf.lua main.lua scene.lua funcs.lua
tar -a -c -f cambridge.zip libs/binser.lua libs/classic.lua libs/simple-slider.lua libs/discordRPC.lua load res scene tetris conf.lua main.lua scene.lua funcs.lua
rename cambridge.zip cambridge.love

BIN
res/bgm/highscores.wav Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 B

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -11,8 +11,6 @@ function Scene:onInputRelease() end
ExitScene = require "scene.exit"
GameScene = require "scene.game"
ModeSelectScene = require "scene.mode_select"
KeyConfigScene = require "scene.key_config"
StickConfigScene = require "scene.stick_config"
InputConfigScene = require "scene.input_config"
GameConfigScene = require "scene.game_config"
TuningScene = require "scene.tuning"

View File

@@ -30,35 +30,26 @@ function CreditsScene:render()
love.graphics.setFont(font_3x5_4)
love.graphics.print("Cambridge Credits", 320, 500 - self.frames / 2)
love.graphics.print("THANK YOU\nFOR PLAYING!", 320, math.max(1770 - self.frames / 2, 240))
love.graphics.print("THANK YOU\nFOR PLAYING!", 320, math.max(1500 - self.frames / 2, 240))
love.graphics.setFont(font_3x5_3)
love.graphics.print("Game Developers", 320, 550 - self.frames / 2)
love.graphics.print("Project Heads", 320, 640 - self.frames / 2)
love.graphics.print("Other Game Developers", 320, 730 - self.frames / 2)
love.graphics.print("Special Thanks", 320, 950 - self.frames / 2)
love.graphics.print("- SashLilac / TS3 / Milla", 320, math.max(1850 - self.frames / 2, 320))
love.graphics.print("Special Thanks", 320, 900 - self.frames / 2)
love.graphics.print("- SashLilac / SpinTriple", 320, math.max(2000 - self.frames / 2, 320))
love.graphics.setFont(font_3x5_2)
love.graphics.print("Oshisaure\nJoe Zeng", 320, 590 - self.frames / 2)
love.graphics.print("Mizu\nHailey", 320, 680 - self.frames / 2)
love.graphics.print(
"Axel Fox - Multimino\nMine - Tetra Online\nDr Ocelot - Tetra Legends\n" ..
"Felicity / nightmareci - Shiromino\n2Tie - TGMsim\nPhoenix Flare - Master of Blocks\n" ..
"RayRay26 - Spirit Drop\nosk - TETR.IO\nMarkGamed7794 - Picoris 2",
320, 770 - self.frames / 2
)
love.graphics.print("Axel Fox - Multimino\nMine - Tetra Online\nDr Ocelot - Tetra Legends\nFelicity / nightmareci - Shiromino\n2Tie - TGMsim\nPhoenix Flare - Master of Blocks", 320, 770 - self.frames / 2)
love.graphics.print(
"RocketLanterns\nCylinderKnot\nHammrTime\nKirby703\nMattMayuga\nMyPasswordIsWeak\n" ..
"Nikki Karissa\noffwo\nsinefuse\nTetro48\nTimmSkiller\nuser74003\nAgentBasey\n" ..
"CheeZed_Fish\neightsixfivezero\nEricICX\ngizmo4487\nM1ssing0\n" ..
"pokemonfan1937\nSimon\nstratus\nZaptorZap\nArchina\nOliver\ncolour_thief\n" ..
"Caithness\nkdex\nzid\nsaphie\nSuper302\nAurora\nswitchpalacecorner\nKitaru\n" ..
"JBroms\nMany more I definitely missed!\n" ..
"The Absolute PLUS Discord\nTetra Legends Discord\nTetra Online Discord\n" ..
"Multimino Discord\nHard Drop Discord\nCambridge Discord\n" ..
"And to you, the player!",
320, 990 - self.frames / 2
"CheeZed_Fish\neightsixfivezero\nEricICX\ngizmo4487\nM1ssing0\nMarkGamed7794\n" ..
"pokemonfan1937\nSimon\nstratus\nZaptorZap\nThe Absolute PLUS Discord\nTetra Legends Discord\n" ..
"Tetra Online Discord\nMultimino Discord\nCambridge Discord\nAnd to you, the player!",
320, 940 - self.frames / 2
)
end

View File

@@ -7,10 +7,10 @@ require 'load.save'
function GameScene:new(game_mode, ruleset, inputs)
self.retry_mode = game_mode
self.retry_ruleset = ruleset
self.secret_inputs = inputs
self.secret_inputs = copy(inputs)
self.game = game_mode(self.secret_inputs)
self.ruleset = ruleset(self.game)
self.game:initialize(self.ruleset)
self.ruleset = ruleset()
self.game:initialize(self.ruleset, self.secret_inputs)
self.inputs = {
left=false,
right=false,
@@ -83,7 +83,6 @@ function GameScene:render()
end
self.game:drawGrid()
if self.game.lcd > 0 then self.game:drawLineClearAnimation() end
self.game:drawPiece()
self.game:drawNextQueue(self.ruleset)
self.game:drawScoringInfo()
@@ -118,18 +117,15 @@ function GameScene:onInputPress(e)
highscore_entry = self.game:getHighscoreData()
highscore_hash = self.game.hash .. "-" .. self.ruleset.hash
submitHighscore(highscore_hash, highscore_entry)
self.game:onExit()
scene = e.input == "retry" and GameScene(self.retry_mode, self.retry_ruleset, self.secret_inputs) or ModeSelectScene()
elseif e.input == "retry" then
switchBGM(nil)
self.game:onExit()
scene = GameScene(self.retry_mode, self.retry_ruleset, self.secret_inputs)
elseif e.input == "pause" and not (self.game.game_over or self.game.completed) then
self.paused = not self.paused
if self.paused then pauseBGM()
else resumeBGM() end
elseif e.input == "menu_back" then
self.game:onExit()
scene = ModeSelectScene()
elseif e.input and string.sub(e.input, 1, 5) ~= "menu_" then
self.inputs[e.input] = true
@@ -143,8 +139,19 @@ function GameScene:onInputRelease(e)
end
function submitHighscore(hash, data)
function isHighscore(score, high)
for k, _ in pairs(score) do
if not high[k] or score[k] > high[k] then
return true
end
end
return false
end
if not highscores[hash] then highscores[hash] = {} end
table.insert(highscores[hash], data)
if isHighscore(data, highscores[hash]) then
highscores[hash] = data
end
saveHighscores()
end

View File

@@ -11,9 +11,9 @@ ConfigScene.options = {
{"manlock", "Manual Locking", false, {"Per ruleset", "Per gamemode", "Harddrop", "Softdrop"}},
{"piece_colour", "Piece Colours", false, {"Per ruleset", "Arika", "TTC"}},
{"world_reverse", "A Button Rotation", false, {"Left", "Auto", "Right"}},
{"spawn_positions", "Spawn Positions", false, {"Per ruleset", "In field", "Out of field"}},
{"spawn_positions", "Spawn Positions", false, {"In field", "Out of field"}},
{"display_gamemode", "Display Gamemode", false, {"On", "Off"}},
{"das_last_key", "DAS Last Key", false, {"Off", "On"}},
{"das_last_key", "DAS Switch", false, {"Default", "Instant"}},
{"smooth_movement", "Smooth Piece Drop", false, {"On", "Off"}},
{"synchroes_allowed", "Synchroes", false, {"Per ruleset", "On", "Off"}},
{"diagonal_input", "Diagonal Input", false, {"On", "Off"}},
@@ -38,6 +38,7 @@ function ConfigScene:new()
end
function ConfigScene:update()
config["das_last_key"] = config.gamesettings.das_last_key == 2
self.sfxSlider:update()
self.bgmSlider:update()
end
@@ -102,7 +103,7 @@ function ConfigScene:onInputPress(e)
else
playSE("cursor")
sld = self[self.options[self.highlight][4]]
sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() - 5) / (sld.max - sld.min)))
sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() - 3) / (sld.max - sld.min)))
end
elseif e.input == "right" or e.scancode == "right" then
if not self.options[self.highlight][3] then
@@ -112,7 +113,7 @@ function ConfigScene:onInputPress(e)
else
playSE("cursor")
sld = self[self.options[self.highlight][4]]
sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() + 5) / (sld.max - sld.min)))--math.max(0, (math.floor(sld:getValue())+2)/(sld.max-sld.min))
sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() + 3) / (sld.max - sld.min)))--math.max(0, (math.floor(sld:getValue())+2)/(sld.max-sld.min))
end
elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
loadSave()

View File

@@ -2,65 +2,162 @@ local ConfigScene = Scene:extend()
ConfigScene.title = "Input Config"
local menu_screens = {
KeyConfigScene,
StickConfigScene
require 'load.save'
local configurable_inputs = {
"menu_decide",
"menu_back",
"left",
"right",
"up",
"down",
"rotate_left",
"rotate_left2",
"rotate_right",
"rotate_right2",
"rotate_180",
"hold",
"retry",
"pause",
}
function ConfigScene:new()
self.menu_state = 1
DiscordRPC:update({
details = "In menus",
state = "Changing input config",
})
local function newSetInputs()
local set_inputs = {}
for i, input in ipairs(configurable_inputs) do
set_inputs[input] = false
end
return set_inputs
end
function ConfigScene:update() end
function ConfigScene:new()
self.input_state = 1
self.set_inputs = newSetInputs()
self.new_input = {}
self.axis_timer = 0
DiscordRPC:update({
details = "In menus",
state = "Changing input config",
})
end
function ConfigScene:update()
end
function ConfigScene:render()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(
backgrounds["input_config"],
0, 0, 0,
0.5, 0.5
)
)
love.graphics.setFont(font_3x5_4)
love.graphics.print("INPUT CONFIG", 80, 40)
love.graphics.setFont(font_3x5_2)
for i, input in ipairs(configurable_inputs) do
love.graphics.printf(input, 40, 50 + i * 20, 200, "left")
if self.set_inputs[input] then
love.graphics.printf(self.set_inputs[input], 240, 50 + i * 20, 300, "left")
end
end
if self.input_state > table.getn(configurable_inputs) then
love.graphics.print("press enter to confirm, delete/backspace to retry" .. (config.input and ", escape to cancel" or ""))
else
love.graphics.print("press key or joystick input for " .. configurable_inputs[self.input_state] .. ", tab to skip" .. (config.input and ", escape to cancel" or ""), 0, 0)
love.graphics.print("function keys (F1, F2, etc.), escape, and tab can't be changed", 0, 20)
end
love.graphics.setFont(font_3x5_2)
love.graphics.print("Which controls do you want to configure?", 80, 90)
love.graphics.setColor(1, 1, 1, 0.5)
love.graphics.rectangle("fill", 75, 118 + 50 * self.menu_state, 200, 33)
love.graphics.setFont(font_3x5_3)
love.graphics.setColor(1, 1, 1, 1)
for i, screen in pairs(menu_screens) do
love.graphics.printf(screen.title, 80, 120 + 50 * i, 200, "left")
end
self.axis_timer = self.axis_timer + 1
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.scancode == "return" then
playSE("main_decide")
scene = menu_screens[self.menu_state]()
elseif e.input == "up" or e.scancode == "up" then
self:changeOption(-1)
playSE("cursor")
elseif e.input == "down" or e.scancode == "down" then
self:changeOption(1)
playSE("cursor")
elseif config.input and (
e.input == "menu_back" or e.scancode == "backspace" or e.scancode == "delete"
) then
scene = SettingsScene()
local function addJoystick(input, name)
if not input.joysticks then
input.joysticks = {}
end
if not input.joysticks[name] then
input.joysticks[name] = {}
end
end
return ConfigScene
function ConfigScene:onInputPress(e)
if e.type == "key" then
-- function keys, escape, and tab are reserved and can't be remapped
if e.scancode == "escape" and config.input then
-- cancel only if there was an input config already
scene = SettingsScene()
elseif self.input_state > table.getn(configurable_inputs) then
if e.scancode == "return" then
-- save new input, then load next scene
config.input = self.new_input
saveConfig()
scene = 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" then
-- all other keys can be configured
if not self.new_input.keys then
self.new_input.keys = {}
end
self.set_inputs[configurable_inputs[self.input_state]] = "key " .. love.keyboard.getKeyFromScancode(e.scancode) .. " (" .. e.scancode .. ")"
self.new_input.keys[e.scancode] = configurable_inputs[self.input_state]
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.joysticks[e.name].buttons then
self.new_input.joysticks[e.name].buttons = {}
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.joysticks[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.joysticks[e.name].axes then
self.new_input.joysticks[e.name].axes = {}
end
if not self.new_input.joysticks[e.name].axes[e.axis] then
self.new_input.joysticks[e.name].axes[e.axis] = {}
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.joysticks[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.joysticks[e.name].hats then
self.new_input.joysticks[e.name].hats = {}
end
if not self.new_input.joysticks[e.name].hats[e.hat] then
self.new_input.joysticks[e.name].hats[e.hat] = {}
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.joysticks[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 ConfigScene

View File

@@ -1,100 +0,0 @@
local KeyConfigScene = Scene:extend()
KeyConfigScene.title = "Key Config"
require 'load.save'
local configurable_inputs = {
"menu_decide",
"menu_back",
"left",
"right",
"up",
"down",
"rotate_left",
"rotate_left2",
"rotate_right",
"rotate_right2",
"rotate_180",
"hold",
"retry",
"pause",
}
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 = {}
DiscordRPC:update({
details = "In menus",
state = "Changing key config",
})
end
function KeyConfigScene:update()
end
function KeyConfigScene:render()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(
backgrounds["input_config"],
0, 0, 0,
0.5, 0.5
)
love.graphics.setFont(font_3x5_2)
for i, input in ipairs(configurable_inputs) do
love.graphics.printf(input, 40, 50 + i * 20, 200, "left")
if self.set_inputs[input] then
love.graphics.printf(self.set_inputs[input], 240, 50 + i * 20, 300, "left")
end
end
if self.input_state > table.getn(configurable_inputs) then
love.graphics.print("press enter to confirm, delete/backspace to retry" .. (config.input and ", escape to cancel" or ""))
else
love.graphics.print("press key input for " .. configurable_inputs[self.input_state] .. ", tab to skip, escape to cancel", 0, 0)
love.graphics.print("function keys (F1, F2, etc.), escape, and tab can't be changed", 0, 20)
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" 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

View File

@@ -41,14 +41,14 @@ function ModeSelectScene:render()
elseif self.menu_state.select == "ruleset" then
love.graphics.setColor(1, 1, 1, 0.25)
end
love.graphics.rectangle("fill", 20, 258, 240, 22)
love.graphics.rectangle("fill", 20, 198, 240, 22)
if self.menu_state.select == "mode" then
love.graphics.setColor(1, 1, 1, 0.25)
elseif self.menu_state.select == "ruleset" then
love.graphics.setColor(1, 1, 1, 0.5)
end
love.graphics.rectangle("fill", 340, 258, 200, 22)
love.graphics.rectangle("fill", 340, 198, 200, 22)
love.graphics.setColor(1, 1, 1, 1)
@@ -56,13 +56,36 @@ function ModeSelectScene:render()
love.graphics.setFont(font_3x5_2)
for idx, mode in pairs(game_modes) do
if(idx >= self.menu_state.mode-9 and idx <= self.menu_state.mode+9) then
love.graphics.printf(mode.name, 40, (260 - 20*(self.menu_state.mode)) + 20 * idx, 200, "left")
if(idx >= self.menu_state.mode-6 and idx <= self.menu_state.mode+6) then
love.graphics.printf(mode.name, 40, (200 - 20*(self.menu_state.mode)) + 20 * idx, 200, "left")
end
end
for idx, ruleset in pairs(rulesets) do
if(idx >= self.menu_state.ruleset-9 and idx <= self.menu_state.ruleset+9) then
love.graphics.printf(ruleset.name, 360, (260 - 20*(self.menu_state.ruleset)) + 20 * idx, 160, "left")
if(idx >= self.menu_state.ruleset-6 and idx <= self.menu_state.ruleset+6) then
love.graphics.printf(ruleset.name, 360, (200 - 20*(self.menu_state.ruleset)) + 20 * idx, 160, "left")
end
end
-- mode description and highscore
for midx, mode in pairs(game_modes) do
for ridx, ruleset in pairs(rulesets) do
if (midx == self.menu_state.mode) and (ridx == self.menu_state.ruleset) then
love.graphics.printf(
"Mode Description:\n\n" .. mode.tagline, 20, 350, 200, "left"
)
love.graphics.printf(
ruleset.name .. " Highscore:", 240, 350, 200, "right"
)
local highscore_string = ""
if highscores[mode.hash .. "-" .. ruleset.hash] then
for k, v in ipairs(highscores[mode.hash .. "-" .. ruleset.hash]) do
highscore_string = highscore_string .. k .. ": " .. v .. "\n"
end
else
highscore_string = "You don't have any highscores yet!"
end
love.graphics.printf(highscore_string, 450, 350, 200, "left")
end
end
end
end

View File

@@ -1,152 +0,0 @@
local StickConfigScene = Scene:extend()
StickConfigScene.title = "Joystick Config"
require 'load.save'
local configurable_inputs = {
"menu_decide",
"menu_back",
"left",
"right",
"up",
"down",
"rotate_left",
"rotate_left2",
"rotate_right",
"rotate_right2",
"rotate_180",
"hold",
"retry",
"pause",
}
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
DiscordRPC:update({
details = "In menus",
state = "Changing joystick config",
})
end
function StickConfigScene:update()
end
function StickConfigScene:render()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(
backgrounds["input_config"],
0, 0, 0,
0.5, 0.5
)
love.graphics.setFont(font_3x5_2)
for i, input in ipairs(configurable_inputs) do
love.graphics.printf(input, 40, 50 + i * 20, 200, "left")
if self.set_inputs[input] then
love.graphics.printf(self.set_inputs[input], 240, 50 + i * 20, 300, "left")
end
end
if self.input_state > table.getn(configurable_inputs) then
love.graphics.print("press enter to confirm, delete/backspace to retry" .. (config.input and ", escape to cancel" or ""))
else
love.graphics.print("press joystick input for " .. configurable_inputs[self.input_state] .. ", tab to skip, escape to cancel", 0, 0)
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
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
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
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

View File

@@ -83,7 +83,6 @@ function TitleScene:render()
love.graphics.printf(screen.title, 40, 280 + 20 * i, 120, "left")
end
love.graphics.printf(version, 0, 460, love.graphics.getWidth() - 5, "right")
end
function TitleScene:changeOption(rel)

View File

@@ -111,24 +111,18 @@ function Grid:getClearedRowCount()
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"
}
--self.grid_age[row][x] = 0
self.grid_age[row][x] = 0
end
end
end
return block_table
return true
end
function Grid:clearClearedRows()
@@ -394,16 +388,15 @@ end
function Grid:draw()
for y = 5, 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] ~= empty then
if self.grid_age[y][x] < 2 then
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(blocks[self.grid[y][x].skin]["F"], 48+x*16, y*16)
else
if self.grid[y][x].colour == "X" then
love.graphics.setColor(0, 0, 0, 0)
elseif self.grid[y][x].skin == "bone" then
if self.grid[y][x].skin == "bone" then
love.graphics.setColor(1, 1, 1, 1)
elseif self.grid[y][x].colour == "X" then
love.graphics.setColor(0.5, 0.5, 0.5, 1 - self.grid_age[y][x] / 15)
else
love.graphics.setColor(0.5, 0.5, 0.5, 1)
end
@@ -412,11 +405,11 @@ function Grid:draw()
if self.grid[y][x].skin ~= "bone" and self.grid[y][x].colour ~= "X" then
love.graphics.setColor(0.8, 0.8, 0.8, 1)
love.graphics.setLineWidth(1)
if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then
if y > 1 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
end
if y < self.height and self.grid[y+1][x] == empty or
(y + 1 <= self.height and self.grid[y+1][x].colour == "X") then
(y + 1 > self.height or self.grid[y+1][x].colour == "X") then
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
end
if x > 1 and self.grid[y][x-1] == empty then
@@ -434,14 +427,18 @@ end
function Grid:drawOutline()
for y = 5, self.height do
for x = 1, self.width do
if self.grid[y][x].colour == "X" then
love.graphics.setColor(0.5, 0.5, 0.5, 1 - self.grid_age[y][x] / 15)
love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16)
end
if self.grid[y][x] ~= empty and self.grid[y][x].colour ~= "X" then
love.graphics.setColor(0.8, 0.8, 0.8, 1)
love.graphics.setLineWidth(1)
if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then
if y > 1 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
end
if y < self.height and self.grid[y+1][x] == empty or
(y + 1 <= self.height and self.grid[y+1][x].colour == "X") then
(y + 1 > self.height or self.grid[y+1][x].colour == "X") then
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
end
if x > 1 and self.grid[y][x-1] == empty then
@@ -462,7 +459,7 @@ function Grid:drawInvisible(opacity_function, garbage_opacity_function, lock_fla
for x = 1, self.width do
if self.grid[y][x] ~= empty then
if self.grid[y][x].colour == "X" then
opacity = 0
opacity = 1 - self.grid_age[y][x] / 15
elseif garbage_opacity_function and self.grid[y][x].colour == "A" then
opacity = garbage_opacity_function(self.grid_age[y][x])
else
@@ -474,11 +471,11 @@ function Grid:drawInvisible(opacity_function, garbage_opacity_function, lock_fla
if opacity > 0 and self.grid[y][x].colour ~= "X" then
love.graphics.setColor(0.64, 0.64, 0.64)
love.graphics.setLineWidth(1)
if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then
if y > 1 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
end
if y < self.height and self.grid[y+1][x] == empty or
(y + 1 <= self.height and self.grid[y+1][x].colour == "X") then
(y + 1 > self.height or self.grid[y+1][x].colour == "X") then
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
end
if x > 1 and self.grid[y][x-1] == empty then
@@ -509,18 +506,18 @@ function Grid:drawCustom(colour_function, gamestate)
if block ~= empty then
local R, G, B, A, outline = colour_function(gamestate, block, x, y, self.grid_age[y][x])
if self.grid[y][x].colour == "X" then
A = 0
A = 1 - self.grid_age[y][x] / 15
end
love.graphics.setColor(R, G, B, A)
love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16)
if outline > 0 and self.grid[y][x].colour ~= "X" then
love.graphics.setColor(0.64, 0.64, 0.64, outline)
love.graphics.setLineWidth(1)
if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then
if y > 1 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
end
if y < self.height and self.grid[y+1][x] == empty or
(y + 1 <= self.height and self.grid[y+1][x].colour == "X") then
(y + 1 > self.height or self.grid[y+1][x].colour == "X") then
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
end
if x > 1 and self.grid[y][x-1] == empty then

View File

@@ -104,8 +104,9 @@ function Piece:dropToBottom(grid)
self:dropSquares(math.huge, grid)
self.gravity = 0
if self.position.y > piece_y then
-- if it got dropped any, also reset lock delay
if self.ghost == false then playSE("bottom") end
-- self.lock_delay = 0
self.lock_delay = 0
end
return self
end
@@ -117,36 +118,19 @@ function Piece:lockIfBottomed(grid)
return self
end
function Piece:addGravity(gravity, grid, classic_lock)
function Piece:addGravity(gravity, grid)
local new_gravity = self.gravity + gravity
if self:isDropBlocked(grid) then
if classic_lock then
self.gravity = math.min(1, 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
self.gravity = math.min(1, new_gravity)
self.lock_delay = self.lock_delay + 1
else
self.gravity = 0
local dropped_squares = math.floor(new_gravity)
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
end
return self
end

View File

@@ -1,24 +1,138 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local MarathonA2Game = require 'tetris.modes.marathon_a2'
local Piece = require 'tetris.components.piece'
local BigA2Game = MarathonA2Game:extend()
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
BigA2Game.name = "Big A2"
BigA2Game.hash = "BigA2"
BigA2Game.tagline = "Big blocks in the most celebrated TGM mode!"
local MarathonA2Game = GameMode:extend()
function BigA2Game:new()
BigA2Game.super:new()
MarathonA2Game.name = "Big A2"
MarathonA2Game.hash = "BigA2"
MarathonA2Game.tagline = "The points don't matter! Can you reach the invisible roll? Big mode too!"
function MarathonA2Game:new()
self.super:new()
self.big_mode = true
self.roll_frames = 0
self.combo = 1
self.grade = 0
self.grade_points = 0
self.grade_point_decay_counter = 0
self.randomizer = History6RollsRandomizer()
self.lock_drop = false
self.lock_hard_drop = false
self.enable_hold = false
self.next_queue_length = 1
end
function BigA2Game:updateScore(level, drop_bonus, cleared_lines)
cleared_lines = cleared_lines / 2
if not self.clear then
self:updateGrade(cleared_lines)
if cleared_lines >= 4 then
self.tetris_count = self.tetris_count + 1
function MarathonA2Game:getARE()
if self.level < 700 then return 27
elseif self.level < 800 then return 18
else return 14 end
end
function MarathonA2Game:getLineARE()
if self.level < 600 then return 27
elseif self.level < 700 then return 18
elseif self.level < 800 then return 14
else return 8 end
end
function MarathonA2Game:getDasLimit()
if self.level < 500 then return 15
elseif self.level < 900 then return 9
else return 7 end
end
function MarathonA2Game:getLineClearDelay()
if self.level < 500 then return 40
elseif self.level < 600 then return 25
elseif self.level < 700 then return 16
elseif self.level < 800 then return 12
else return 6 end
end
function MarathonA2Game:getLockDelay()
if self.level < 900 then return 30
else return 17 end
end
function MarathonA2Game:getGravity()
if (self.level < 30) then return 4/256
elseif (self.level < 35) then return 6/256
elseif (self.level < 40) then return 8/256
elseif (self.level < 50) then return 10/256
elseif (self.level < 60) then return 12/256
elseif (self.level < 70) then return 16/256
elseif (self.level < 80) then return 32/256
elseif (self.level < 90) then return 48/256
elseif (self.level < 100) then return 64/256
elseif (self.level < 120) then return 80/256
elseif (self.level < 140) then return 96/256
elseif (self.level < 160) then return 112/256
elseif (self.level < 170) then return 128/256
elseif (self.level < 200) then return 144/256
elseif (self.level < 220) then return 4/256
elseif (self.level < 230) then return 32/256
elseif (self.level < 233) then return 64/256
elseif (self.level < 236) then return 96/256
elseif (self.level < 239) then return 128/256
elseif (self.level < 243) then return 160/256
elseif (self.level < 247) then return 192/256
elseif (self.level < 251) then return 224/256
elseif (self.level < 300) then return 1
elseif (self.level < 330) then return 2
elseif (self.level < 360) then return 3
elseif (self.level < 400) then return 4
elseif (self.level < 420) then return 5
elseif (self.level < 450) then return 4
elseif (self.level < 500) then return 3
else return 20
end
end
function MarathonA2Game:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames < 0 then return false end
if self.roll_frames > 3694 then
self.completed = true
end
elseif self.ready_frames == 0 then
self.frames = self.frames + 1
end
return true
end
function MarathonA2Game:onPieceEnter()
if (self.level % 100 ~= 99 and self.level ~= 998) and not self.clear and self.frames ~= 0 then
self.level = self.level + 1
end
end
function MarathonA2Game:onLineClear(cleared_row_count)
cleared_row_count = cleared_row_count / 2
self.level = math.min(self.level + cleared_row_count, 999)
if self.level == 999 and not self.clear then
self.clear = true
self.grid:clear()
self.roll_frames = -150
end
self.lock_drop = self.level >= 900
self.lock_hard_drop = self.level >= 900
end
function MarathonA2Game:updateScore(level, drop_bonus, cleared_lines)
if not self.clear then
cleared_lines = cleared_lines / 2
self:updateGrade(cleared_lines)
if self.grid:checkForBravo(cleared_lines) then self.bravo = 4 else self.bravo = 1 end
if cleared_lines > 0 then
self.combo = self.combo + (cleared_lines - 1) * 2
@@ -30,21 +144,164 @@ function BigA2Game:updateScore(level, drop_bonus, cleared_lines)
self.combo = 1
end
self.drop_bonus = 0
else self.lines = self.lines + cleared_lines end
end
function BigA2Game:onLineClear(cleared_row_count)
cleared_row_count = cleared_row_count / 2
self:updateSectionTimes(self.level, self.level + cleared_row_count)
self.level = math.min(self.level + cleared_row_count, 999)
if self.level == 999 and not self.clear then
self.clear = true
self.grid:clear()
if self:qualifiesForMRoll() then self.grade = 32 end
self.roll_frames = -150
end
self.lock_drop = self.level >= 900
self.lock_hard_drop = self.level >= 900
end
return BigA2Game
local grade_point_bonuses = {
{10, 20, 40, 50},
{10, 20, 30, 40},
{10, 20, 30, 40},
{10, 15, 30, 40},
{10, 15, 20, 40},
{5, 15, 20, 30},
{5, 10, 20, 30},
{5, 10, 15, 30},
{5, 10, 15, 30},
{5, 10, 15, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
}
local grade_point_decays = {
125, 80, 80, 50, 45, 45, 45,
40, 40, 40, 40, 40, 30, 30, 30,
20, 20, 20, 20, 20,
15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
10, 10
}
local combo_multipliers = {
{1.0, 1.0, 1.0, 1.0},
{1.2, 1.4, 1.5, 1.0},
{1.2, 1.5, 1.8, 1.0},
{1.4, 1.6, 2.0, 1.0},
{1.4, 1.7, 2.2, 1.0},
{1.4, 1.8, 2.3, 1.0},
{1.4, 1.9, 2.4, 1.0},
{1.5, 2.0, 2.5, 1.0},
{1.5, 2.1, 2.6, 1.0},
{2.0, 2.5, 3.0, 1.0},
}
local grade_conversion = {
[0] = 0,
1, 2, 3, 4, 5, 5, 6, 6, 7, 7,
7, 8, 8, 8, 9, 9, 9, 10, 11, 12,
12, 12, 13, 13, 14, 14, 15, 15, 16, 16,
17
}
function MarathonA2Game:updateGrade(cleared_lines)
if self.clear then return end
if cleared_lines == 0 then
self.grade_point_decay_counter = self.grade_point_decay_counter + 1
if self.grade_point_decay_counter >= grade_point_decays[self.grade + 1] then
self.grade_point_decay_counter = 0
self.grade_points = math.max(0, self.grade_points - 1)
end
else
self.grade_points = self.grade_points + (
math.ceil(
grade_point_bonuses[self.grade + 1][cleared_lines] *
combo_multipliers[math.min(self.combo, 10)][cleared_lines]
) * (1 + math.floor(self.level / 250))
)
if self.grade_points >= 100 and self.grade < 31 then
self.grade_points = 0
self.grade = self.grade + 1
end
end
end
function MarathonA2Game:getLetterGrade()
local grade = grade_conversion[self.grade]
if grade < 9 then
return tostring(9 - grade)
elseif grade < 18 then
return "S" .. tostring(grade - 8)
end
end
MarathonA2Game.rollOpacityFunction = function(age)
if age < 240 then return 1
elseif age > 300 then return 0
else return 1 - (age - 240) / 60 end
end
function MarathonA2Game:drawGrid(ruleset)
if self.clear and not (self.completed or self.game_over) then
self.grid:drawInvisible(self.rollOpacityFunction, nil, false)
else
self.grid:draw()
if self.piece ~= nil and self.level < 100 then
self:drawGhostPiece(ruleset)
end
end
end
function MarathonA2Game:drawScoringInfo()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_2)
love.graphics.print(
self.das.direction .. " " ..
self.das.frames .. " " ..
strTrueValues(self.prev_inputs)
)
love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("GRADE", 240, 120, 40, "left")
love.graphics.printf("SCORE", 240, 200, 40, "left")
love.graphics.printf("LEVEL", 240, 320, 40, "left")
love.graphics.setFont(font_3x5_3)
if self.roll_frames > 3694 then love.graphics.setColor(1, 0.5, 0, 1)
elseif self.clear then love.graphics.setColor(0, 1, 0, 1) end
love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left")
love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(self.score, 240, 220, 90, "left")
love.graphics.printf(self.level, 240, 340, 40, "right")
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")
love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")
end
function MarathonA2Game:getHighscoreData()
return {
grade = grade_conversion[self.grade],
score = self.score,
level = self.level,
frames = self.frames,
}
end
function MarathonA2Game:getSectionEndLevel()
if self.level >= 900 then return 999
else return math.floor(self.level / 100 + 1) * 100 end
end
function MarathonA2Game:getBackground()
return math.floor(self.level / 100)
end
return MarathonA2Game

View File

@@ -10,9 +10,6 @@ local BagRandomizer = require 'tetris.randomizers.bag'
local GameMode = Object:extend()
GameMode.name = ""
GameMode.hash = ""
GameMode.tagline = ""
GameMode.rollOpacityFunction = function(age) return 0 end
function GameMode:new(secret_inputs)
@@ -45,7 +42,6 @@ function GameMode:new(secret_inputs)
self.enable_hard_drop = true
self.next_queue_length = 1
self.additive_gravity = true
self.classic_lock = false
self.draw_section_times = false
self.draw_secondary_section_times = false
self.big_mode = false
@@ -64,8 +60,6 @@ function GameMode:new(secret_inputs)
self.hard_drop_locked = false
self.lock_on_soft_drop = false
self.lock_on_hard_drop = false
self.cleared_block_table = {}
self.last_lcd = 0
self.used_randomizer = nil
self.hold_queue = nil
self.held = false
@@ -82,7 +76,6 @@ function GameMode:getLockDelay() return 30 end
function GameMode:getLineClearDelay() return 40 end
function GameMode:getDasLimit() return 15 end
function GameMode:getDasCutDelay() return 0 end
function GameMode:getGravity() return 1/64 end
function GameMode:getNextPiece(ruleset)
return {
@@ -96,8 +89,9 @@ function GameMode:getSkin()
return "2tie"
end
function GameMode:initialize(ruleset)
function GameMode:initialize(ruleset, secret_inputs)
-- generate next queue
self:new(secret_inputs)
self.used_randomizer = (
ruleset.pieces == self.randomizer.possible_pieces and
self.randomizer or
@@ -107,8 +101,7 @@ function GameMode:initialize(ruleset)
BagRandomizer(ruleset.pieces)
)
)
self.ruleset = ruleset
for i = 1, math.max(self.next_queue_length, 1) do
for i = 1, self.next_queue_length do
table.insert(self.next_queue, self:getNextPiece(ruleset))
end
self.lock_on_soft_drop = ({ruleset.softdrop_lock, self.instant_soft_drop, false, true })[config.gamesettings.manlock]
@@ -137,13 +130,31 @@ function GameMode:update(inputs, ruleset)
self:chargeDAS(inputs, self:getDasLimit(), self:getARR())
-- set attempt flags
if inputs["left"] or inputs["right"] then self:onAttemptPieceMove(self.piece, self.grid) end
if (
if inputs["left"] or inputs["right"] then
self:onAttemptPieceMove(self.piece)
if self.immobile_spin_bonus and self.piece ~= nil then
if not self.piece:isMoveBlocked(self.grid, { x=-1, y=0 }) and
not self.piece:isMoveBlocked(self.grid, { x=1, y=0 }) then
self.piece.spin = false
end
end
end
if
inputs["rotate_left"] or inputs["rotate_right"] or
inputs["rotate_left2"] or inputs["rotate_right2"] or
inputs["rotate_180"]
) then
self:onAttemptPieceRotate(self.piece, self.grid)
then
self:onAttemptPieceRotate(self.piece)
if self.immobile_spin_bonus and self.piece ~= nil then
if self.piece:isDropBlocked(self.grid) and
self.piece:isMoveBlocked(self.grid, { x=-1, y=0 }) and
self.piece:isMoveBlocked(self.grid, { x=1, y=0 }) and
self.piece:isMoveBlocked(self.grid, { x=0, y=-1 }) then
self.piece.spin = true
else
self.piece.spin = false
end
end
end
if self.piece == nil then
@@ -156,38 +167,29 @@ function GameMode:update(inputs, ruleset)
if self.enable_hold and inputs["hold"] == true and self.held == false and self.prev_inputs["hold"] == false then
self:hold(inputs, ruleset)
self.prev_inputs = inputs
if not self.grid:canPlacePiece(self.piece) then
self.game_over = true
end
return
end
if (self.lock_drop or (
not ruleset.are or self:getARE() == 0
)) and inputs["down"] ~= true then
if self.lock_drop and inputs["down"] ~= true then
self.drop_locked = false
end
if (self.lock_hard_drop or (
not ruleset.are or self:getARE() == 0
)) and inputs["up"] ~= true then
if self.lock_hard_drop and inputs["up"] ~= true then
self.hard_drop_locked = false
end
-- diff vars to use in checks
local piece_y = self.piece.position.y
local piece_x = self.piece.position.x
local piece_rot = self.piece.rotation
ruleset:processPiece(
inputs, self.piece, self.grid, self:getGravity(), self.prev_inputs,
self.move, self:getLockDelay(), self:getDropSpeed(),
self.drop_locked, self.hard_drop_locked,
self.enable_hard_drop, self.additive_gravity, self.classic_lock
self.enable_hard_drop, self.additive_gravity
)
local piece_dy = self.piece.position.y - piece_y
local piece_dx = self.piece.position.x - piece_x
local piece_drot = self.piece.rotation - piece_rot
-- das cut
@@ -199,20 +201,10 @@ function GameMode:update(inputs, ruleset)
inputs.rotate_180
))
) then
self:dasCut()
end
if (piece_dx ~= 0) then
self.piece.last_rotated = false
self:onPieceMove(self.piece, self.grid, piece_dx)
end
if (piece_dy ~= 0) then
self.piece.last_rotated = false
self:onPieceDrop(self.piece, self.grid, piece_dy)
end
if (piece_drot ~= 0) then
self.piece.last_rotated = true
self:onPieceRotate(self.piece, self.grid, piece_drot)
self.das.frames = math.max(
self.das.frames - self:getDasCutDelay(),
-self:getDasCutDelay()
)
end
if inputs["up"] == true and
@@ -226,12 +218,7 @@ function GameMode:update(inputs, ruleset)
end
if inputs["down"] == true then
if not (
self.piece:isDropBlocked(self.grid) and
piece_drot ~= 0
) then
self:onSoftDrop(piece_dy)
end
self:onSoftDrop(piece_dy)
if self.piece:isDropBlocked(self.grid) and
not self.drop_locked and
self.lock_on_soft_drop
@@ -241,20 +228,7 @@ function GameMode:update(inputs, ruleset)
end
if self.piece.locked == true then
-- spin detection, immobile only for now
if self.immobile_spin_bonus and
self.piece.last_rotated and (
self.piece:isDropBlocked(self.grid) and
self.piece:isMoveBlocked(self.grid, { x=-1, y=0 }) and
self.piece:isMoveBlocked(self.grid, { x=1, y=0 }) and
self.piece:isMoveBlocked(self.grid, { x=0, y=-1 })
) then
self.piece.spin = true
end
self.grid:applyPiece(self.piece)
-- mark squares (can be overridden)
if self.square_mode then
self.squares = self.squares + self.grid:markSquares()
end
@@ -263,7 +237,7 @@ function GameMode:update(inputs, ruleset)
self:onPieceLock(self.piece, cleared_row_count)
self:updateScore(self.level, self.drop_bonus, cleared_row_count)
self.cleared_block_table = self.grid:markClearedRows()
self.grid:markClearedRows()
self.piece = nil
if self.enable_hold then
self.held = false
@@ -272,13 +246,11 @@ function GameMode:update(inputs, ruleset)
if cleared_row_count > 0 then
playSE("erase")
self.lcd = self:getLineClearDelay()
self.last_lcd = self.lcd
self.are = (
ruleset.are and self:getLineARE() or 0
)
if self.lcd == 0 then
self.grid:clearClearedRows()
self:afterLineClear(cleared_row_count)
if self.are == 0 then
self:initializeOrHold(inputs, ruleset)
end
@@ -308,17 +280,13 @@ end
-- event functions
function GameMode:whilePieceActive() end
function GameMode:onAttemptPieceMove(piece, grid) end
function GameMode:onAttemptPieceRotate(piece, grid) end
function GameMode:onPieceMove(piece, grid, dx) end
function GameMode:onPieceRotate(piece, grid, drot) end
function GameMode:onPieceDrop(piece, grid, dy) end
function GameMode:onAttemptPieceMove(piece) end
function GameMode:onAttemptPieceRotate(piece) end
function GameMode:onPieceLock(piece, cleared_row_count)
playSE("lock")
end
function GameMode:onLineClear(cleared_row_count) end
function GameMode:afterLineClear(cleared_row_count) end
function GameMode:onPieceEnter() end
function GameMode:onHold() end
@@ -344,8 +312,6 @@ function GameMode:onGameComplete()
self:onGameOver()
end
function GameMode:onExit() end
-- DAS functions
function GameMode:startRightDAS()
@@ -386,7 +352,7 @@ function GameMode:stopDAS()
end
function GameMode:chargeDAS(inputs)
if config.gamesettings.das_last_key == 2 then
if config["das_last_key"] then
if inputs["right"] == true and self.das.direction ~= "right" and not self.prev_inputs["right"] then
self:startRightDAS()
elseif inputs["left"] == true and self.das.direction ~= "left" and not self.prev_inputs["left"] then
@@ -409,13 +375,6 @@ function GameMode:chargeDAS(inputs)
end
end
function GameMode:dasCut()
self.das.frames = math.max(
self.das.frames - self:getDasCutDelay(),
-(self:getDasCutDelay() + 1)
)
end
function GameMode:areCancel(inputs, ruleset)
if ruleset.are_cancel and self.piece_hard_dropped and
not self.prev_inputs.up and
@@ -459,9 +418,7 @@ function GameMode:processDelays(inputs, ruleset, drop_speed)
self.lcd = self.lcd - 1
self:areCancel(inputs, ruleset)
if self.lcd == 0 then
local cleared_row_count = self.grid:getClearedRowCount()
self.grid:clearClearedRows()
self:afterLineClear(cleared_row_count)
playSE("fall")
if self.are == 0 then
self:initializeOrHold(inputs, ruleset)
@@ -484,7 +441,7 @@ end
function GameMode:initializeOrHold(inputs, ruleset)
if (
(self.frames == 0 or (ruleset.are and self:getARE() ~= 0)) and self.ihs or false
self.frames == 0 or (ruleset.are and self:getARE() ~= 0) and self.ihs or false
) and self.enable_hold and inputs["hold"] == true then
self:hold(inputs, ruleset, true)
else
@@ -530,50 +487,29 @@ function GameMode:initializeNextPiece(inputs, ruleset, piece_data, generate_next
self.lock_drop, self.lock_hard_drop, self.big_mode,
(
self.frames == 0 or (ruleset.are and self:getARE() ~= 0)
) and self.irs or false
) and self.irs or false,
self.buffer_hard_drop, self.buffer_soft_drop,
self.lock_on_hard_drop, self.lock_on_soft_drop
)
if self.buffer_hard_drop then
if config.gamesettings.buffer_lock == 1 then
self.piece:dropToBottom(self.grid)
if self.lock_on_hard_drop then self.piece.locked = true end
end
local above_field = (
(config.gamesettings.spawn_positions == 1 and
ruleset.spawn_above_field) or
config.gamesettings.spawn_positions == 3
)
self:onHardDrop(self.piece.position.y - (
self.piece.big and
ruleset.big_spawn_positions[self.piece.shape].y or
ruleset.spawn_positions[self.piece.shape].y) +
(above_field and ruleset:getAboveFieldOffset(
piece_data.shape, piece_data.orientation
) or 0)
)
self.buffer_hard_drop = false
end
if self.buffer_soft_drop then
if (
self.lock_on_soft_drop and
self.piece:isDropBlocked(self.grid) and
config.gamesettings.buffer_lock == 1
) then
self.piece.locked = true
end
self.buffer_soft_drop = false
end
if self.piece:isDropBlocked(self.grid) and
self.grid:canPlacePiece(self.piece) then
playSE("bottom")
end
if self.lock_drop or (
not ruleset.are or self:getARE() == 0
) then
if self.buffer_hard_drop then
self.buffer_hard_drop = false
self:onHardDrop(self.piece.position.y - (
self.big_mode and
ruleset.big_spawn_positions[self.piece.shape].y or
ruleset.spawn_positions[self.piece.shape].y)
)
end
if self.buffer_soft_drop then
self.buffer_soft_drop = false
end
if self.lock_drop then
self.drop_locked = true
end
if self.lock_hard_drop or (
not ruleset.are or self:getARE() == 0
) then
if self.lock_hard_drop then
self.hard_drop_locked = true
end
if generate_next_piece == nil then
@@ -593,87 +529,14 @@ function GameMode:getHighScoreData()
}
end
function GameMode:animation(x, y, skin, colour)
return {
1, 1, 1,
-0.25 + 1.25 * (self.lcd / self.last_lcd),
skin, colour,
48 + x * 16, y * 16
}
end
function GameMode:drawLineClearAnimation()
-- animation function
-- params: block x, y, skin, colour
-- returns: table with RGBA, skin, colour, x, y
-- Fadeout (default)
--[[
function animation(x, y, skin, colour)
return {
1, 1, 1,
-0.25 + 1.25 * (self.lcd / self.last_lcd),
skin, colour,
48 + x * 16, y * 16
}
end
--]]
-- Flash
--[[
function animation(x, y, skin, colour)
return {
1, 1, 1,
self.lcd % 6 < 3 and 1 or 0.25,
skin, colour,
48 + x * 16, y * 16
}
end
--]]
-- TGM1 pop-out
--[[
function animation(x, y, skin, colour)
local p = 0.5
local l = (
(self.last_lcd - self.lcd) / self.last_lcd
)
local dx = l * (x - (1 + self.grid.width) / 2)
local dy = l * (y - (1 + self.grid.height) / 2)
return {
1, 1, 1, 1, skin, colour,
48 + (x + dx) * 16,
(y + dy) * 16 + (464 / (p - 1)) * l * (p - l)
}
end
--]]
for y, row in pairs(self.cleared_block_table) do
for x, block in pairs(row) do
local animation_table = self:animation(x, y, block.skin, block.colour)
love.graphics.setColor(
animation_table[1], animation_table[2],
animation_table[3], animation_table[4]
)
love.graphics.draw(
blocks[animation_table[5]][animation_table[6]],
animation_table[7], animation_table[8]
)
end
end
end
function GameMode:drawPiece()
if self.piece ~= nil then
local b = (
self.classic_lock and
(
self.piece:isDropBlocked(self.grid) and
1 - self.piece.gravity or 1
) or
1 - (self.piece.lock_delay / self:getLockDelay())
self.piece:draw(
1,
self:getLockDelay() == 0 and 1 or
(0.25 + 0.75 * math.max(1 - self.piece.gravity, 1 - (self.piece.lock_delay / self:getLockDelay()))),
self.grid
)
self.piece:draw(1, 0.25 + 0.75 * b, self.grid)
end
end
@@ -686,16 +549,11 @@ function GameMode:drawGhostPiece(ruleset)
end
function GameMode:drawNextQueue(ruleset)
local colourscheme
if ruleset.pieces == 7 then
colourscheme = ({ruleset.colourscheme, ColourSchemes.Arika, ColourSchemes.TTC})[config.gamesettings.piece_colour]
else
colourscheme = ruleset.colourscheme
end
local colourscheme = ({ruleset.colourscheme, ColourSchemes.Arika, ColourSchemes.TTC})[config.gamesettings.piece_colour]
function drawPiece(piece, skin, offsets, pos_x, pos_y)
for index, offset in pairs(offsets) do
local x = offset.x + ruleset:getDrawOffset(piece, rotation).x + ruleset.spawn_positions[piece].x
local y = offset.y + ruleset:getDrawOffset(piece, rotation).y + 4.7
local x = offset.x + ruleset.draw_offsets[piece].x + ruleset.spawn_positions[piece].x
local y = offset.y + ruleset.draw_offsets[piece].y + 4.7
love.graphics.draw(blocks[skin][colourscheme[piece]], pos_x+x*16, pos_y+y*16)
end
end
@@ -731,18 +589,6 @@ function GameMode:setHoldOpacity()
love.graphics.setColor(colour, colour, colour, 1)
end
function GameMode:getBackground()
return 0
end
function GameMode:getHighscoreData()
return {}
end
function GameMode:drawGrid()
self.grid:draw()
end
function GameMode:drawScoringInfo()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_2)
@@ -811,9 +657,7 @@ function GameMode:drawSectionTimesWithSecondary(current_section, section_limit)
end
end
function GameMode:drawSectionTimesWithSplits(current_section, section_limit)
section_limit = section_limit or math.huge
function GameMode:drawSectionTimesWithSplits(current_section)
local section_x = 440
local split_x = 530
@@ -821,18 +665,14 @@ function GameMode:drawSectionTimesWithSplits(current_section, section_limit)
for section, time in pairs(self.section_times) do
if section > 0 then
love.graphics.setColor(self:sectionColourFunction(section))
love.graphics.printf(formatTime(time), section_x, 40 + 20 * section, 90, "left")
love.graphics.setColor(1, 1, 1, 1)
split_time = split_time + time
love.graphics.printf(formatTime(split_time), split_x, 40 + 20 * section, 90, "left")
end
end
if (current_section <= section_limit) then
love.graphics.printf(formatTime(self.frames - self.section_start_time), section_x, 40 + 20 * current_section, 90, "left")
love.graphics.printf(formatTime(self.frames), split_x, 40 + 20 * current_section, 90, "left")
end
love.graphics.printf(formatTime(self.frames - self.section_start_time), section_x, 40 + 20 * current_section, 90, "left")
love.graphics.printf(formatTime(self.frames), split_x, 40 + 20 * current_section, 90, "left")
end
function GameMode:drawCustom() end

View File

@@ -33,7 +33,6 @@ function MarathonA1Game:new()
self.randomizer = History4RollsRandomizer()
self.additive_gravity = false
self.lock_drop = false
self.enable_hard_drop = false
self.enable_hold = false

View File

@@ -34,7 +34,6 @@ function MarathonA2Game:new()
"GM"
}
self.additive_gravity = false
self.lock_drop = false
self.lock_hard_drop = false
self.enable_hold = false
@@ -172,6 +171,7 @@ function MarathonA2Game:updateSectionTimes(old_level, new_level)
self.section_start_time = self.frames
self.section_tetrises[math.floor(old_level / 100)] = self.tetris_count
self.tetris_count = 0
print(self.section_tetrises[math.floor(old_level / 100)])
end
end

View File

@@ -39,7 +39,6 @@ self.SGnames = {
"GM"
}
self.additive_gravity = false
self.lock_drop = true
self.lock_hard_drop = true
self.enable_hold = true

View File

@@ -5,16 +5,17 @@ local Piece = require 'tetris.components.piece'
local Bag7NoSZOStartRandomizer = require 'tetris.randomizers.bag7noSZOstart'
local SurvivalAXGame = GameMode:extend()
local MarathonAX4Game = GameMode:extend()
SurvivalAXGame.name = "Survival AX"
SurvivalAXGame.hash = "SurvivalAX"
SurvivalAXGame.tagline = "Can you clear the time hurdles when the game goes this fast?"
MarathonAX4Game.name = "Marathon AX4"
MarathonAX4Game.hash = "MarathonAX4"
MarathonAX4Game.tagline = "Can you clear the time hurdles when the game goes this fast?"
function SurvivalAXGame:new()
SurvivalAXGame.super:new()
function MarathonAX4Game:new()
MarathonAX4Game.super:new()
self.roll_frames = 0
self.randomizer = Bag7NoSZOStartRandomizer()
self.section_time_limit = 3600
@@ -28,7 +29,7 @@ function SurvivalAXGame:new()
self.next_queue_length = 3
end
function SurvivalAXGame:getARE()
function MarathonAX4Game:getARE()
if self.lines < 10 then return 18
elseif self.lines < 40 then return 14
elseif self.lines < 60 then return 12
@@ -38,24 +39,24 @@ function SurvivalAXGame:getARE()
else return 6 end
end
function SurvivalAXGame:getLineARE()
function MarathonAX4Game:getLineARE()
return self:getARE()
end
function SurvivalAXGame:getDasLimit()
function MarathonAX4Game:getDasLimit()
if self.lines < 20 then return 10
elseif self.lines < 50 then return 9
elseif self.lines < 70 then return 8
else return 7 end
end
function SurvivalAXGame:getLineClearDelay()
function MarathonAX4Game:getLineClearDelay()
if self.lines < 10 then return 14
elseif self.lines < 30 then return 9
else return 5 end
end
function SurvivalAXGame:getLockDelay()
function MarathonAX4Game:getLockDelay()
if self.lines < 10 then return 28
elseif self.lines < 20 then return 24
elseif self.lines < 30 then return 22
@@ -65,16 +66,23 @@ function SurvivalAXGame:getLockDelay()
else return 13 end
end
function SurvivalAXGame:getGravity()
function MarathonAX4Game:getGravity()
return 20
end
function SurvivalAXGame:getSection()
function MarathonAX4Game:getSection()
return math.floor(level / 100) + 1
end
function SurvivalAXGame:advanceOneFrame()
if self.ready_frames == 0 then
function MarathonAX4Game:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames < 0 then
return false
elseif self.roll_frames > 2968 then
self.completed = true
end
elseif self.ready_frames == 0 then
if not self.section_clear then
self.frames = self.frames + 1
end
@@ -85,7 +93,7 @@ function SurvivalAXGame:advanceOneFrame()
return true
end
function SurvivalAXGame:onLineClear(cleared_row_count)
function MarathonAX4Game:onLineClear(cleared_row_count)
if not self.clear then
local new_lines = self.lines + cleared_row_count
self:updateSectionTimes(self.lines, new_lines)
@@ -93,16 +101,16 @@ function SurvivalAXGame:onLineClear(cleared_row_count)
if self.lines == 150 then
self.grid:clear()
self.clear = true
self.completed = true
self.roll_frames = -150
end
end
end
function SurvivalAXGame:getSectionTime()
function MarathonAX4Game:getSectionTime()
return self.frames - self.section_start_time
end
function SurvivalAXGame:updateSectionTimes(old_lines, new_lines)
function MarathonAX4Game:updateSectionTimes(old_lines, new_lines)
if math.floor(old_lines / 10) < math.floor(new_lines / 10) then
-- record new section
table.insert(self.section_times, self:getSectionTime())
@@ -111,23 +119,23 @@ function SurvivalAXGame:updateSectionTimes(old_lines, new_lines)
end
end
function SurvivalAXGame:onPieceEnter()
function MarathonAX4Game:onPieceEnter()
self.section_clear = false
end
function SurvivalAXGame:drawGrid(ruleset)
function MarathonAX4Game:drawGrid(ruleset)
self.grid:draw()
end
function SurvivalAXGame:getHighscoreData()
function MarathonAX4Game:getHighscoreData()
return {
lines = self.lines,
frames = self.frames,
}
end
function SurvivalAXGame:drawScoringInfo()
SurvivalAXGame.super.drawScoringInfo(self)
function MarathonAX4Game:drawScoringInfo()
MarathonAX4Game.super.drawScoringInfo(self)
love.graphics.setColor(1, 1, 1, 1)
@@ -157,12 +165,12 @@ function SurvivalAXGame:drawScoringInfo()
love.graphics.setColor(1, 1, 1, 1)
end
function SurvivalAXGame:getSectionEndLines()
function MarathonAX4Game:getSectionEndLines()
return math.floor(self.lines / 10 + 1) * 10
end
function SurvivalAXGame:getBackground()
function MarathonAX4Game:getBackground()
return math.floor(self.lines / 10)
end
return SurvivalAXGame
return MarathonAX4Game

View File

@@ -1,155 +0,0 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local Bag7Randomiser = require 'tetris.randomizers.bag7noSZOstart'
local Race40Game = GameMode:extend()
Race40Game.name = "Race 40"
Race40Game.hash = "Race40"
Race40Game.tagline = "How fast can you clear 40 lines?"
function Race40Game:new()
Race40Game.super:new()
self.lines = 0
self.line_goal = 40
self.pieces = 0
self.randomizer = Bag7Randomiser()
self.roll_frames = 0
self.SGnames = {
[0] = "",
"9", "8", "7", "6", "5", "4", "3", "2", "1",
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"GM"
}
self.upstacked = false
self.lock_drop = true
self.lock_hard_drop = true
self.instant_hard_drop = true
self.instant_soft_drop = false
self.enable_hold = true
self.next_queue_length = 6
end
function Race40Game:getDropSpeed()
return 20
end
function Race40Game:getARR()
return config.arr
end
function Race40Game:getARE()
return 0
end
function Race40Game:getLineARE()
return self:getARE()
end
function Race40Game:getDasLimit()
return config.das
end
function Race40Game:getLineClearDelay()
return 0
end
function Race40Game:getLockDelay()
return 30
end
function Race40Game:getGravity()
return 1/64
end
function Race40Game:getDasCutDelay()
return config.dcd
end
function Race40Game:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames > 150 then
self.completed = true
end
return false
elseif self.ready_frames == 0 then
self.frames = self.frames + 1
end
return true
end
function Race40Game:onPieceLock()
self.super:onPieceLock()
self.pieces = self.pieces + 1
end
function Race40Game:onLineClear(cleared_row_count)
if not self.clear then
self.lines = self.lines + cleared_row_count
if self.lines >= self.line_goal then
self.clear = true
end
end
end
function Race40Game:drawGrid(ruleset)
self.grid:draw()
if self.piece ~= nil then
self:drawGhostPiece(ruleset)
end
end
function Race40Game:getHighscoreData()
return {
level = self.level,
frames = self.frames,
}
end
function Race40Game:getSecretGrade(sg)
if sg == 19 then self.upstacked = true end
if self.upstacked then return self.SGnames[14 + math.floor((20 - sg) / 4)]
else return self.SGnames[math.floor((sg / 19) * 14)] end
end
function Race40Game:drawScoringInfo()
Race40Game.super.drawScoringInfo(self)
love.graphics.setColor(1, 1, 1, 1)
local text_x = config["side_next"] and 320 or 240
love.graphics.setFont(font_3x5_2)
love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("LINES", text_x, 320, 40, "left")
love.graphics.printf("line/min", text_x, 160, 80, "left")
love.graphics.printf("piece/sec", text_x, 220, 80, "left")
local sg = self.grid:checkSecretGrade()
if sg >= 7 or self.upstacked then
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
end
love.graphics.setFont(font_3x5_3)
love.graphics.printf(string.format("%.02f", self.lines / math.max(1, self.frames) * 3600), text_x, 180, 80, "left")
love.graphics.printf(string.format("%.04f", self.pieces / math.max(1, self.frames) * 60), text_x, 240, 80, "left")
if sg >= 7 or self.upstacked then
love.graphics.printf(self:getSecretGrade(sg), 240, 450, 180, "left")
end
love.graphics.setFont(font_3x5_4)
love.graphics.printf(math.max(0, self.line_goal - self.lines), text_x, 340, 40, "left")
end
function Race40Game:getBackground()
return 2
end
return Race40Game

583
tetris/modes/sakura.lua Normal file
View File

@@ -0,0 +1,583 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local SakuraRandomizer = require 'tetris.randomizers.sakura'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls_35bag'
local SakuraGame = GameMode:extend()
SakuraGame.name = "Sakura A3"
SakuraGame.hash = "SakuraA3"
SakuraGame.tagline = "Clear away the Gem Blocks as fast as possible!"
local b = {
["r"] = { skin = "2tie", colour = "R" },
["o"] = { skin = "2tie", colour = "O" },
["y"] = { skin = "2tie", colour = "Y" },
["g"] = { skin = "2tie", colour = "G" },
["c"] = { skin = "2tie", colour = "C" },
["b"] = { skin = "2tie", colour = "B" },
["m"] = { skin = "2tie", colour = "M" },
["R"] = { skin = "gem", colour = "R" },
["O"] = { skin = "gem", colour = "O" },
["Y"] = { skin = "gem", colour = "Y" },
["G"] = { skin = "gem", colour = "G" },
["C"] = { skin = "gem", colour = "C" },
["B"] = { skin = "gem", colour = "B" },
["M"] = { skin = "gem", colour = "M" },
}
local effects = {
[4] = "mirror",
[8] = "xray",
[12] = "color",
[13] = "mirror",
[16] = "roll",
[23] = "big"
}
local maps = {
[1] = {
[22] = {nil, nil, b.O, b.R, nil, nil, b.M, b.m, nil, nil},
[23] = {nil, b.G, b.c, b.c, b.c, b.c, b.c, b.c, b.Y, nil},
[24] = {nil, b.C, b.y, b.y, b.y, b.y, b.y, b.y, b.B, nil},
},
[2] = {
[20] = {nil, nil, nil, nil, b.G, b.b, b.b, b.M, nil, nil},
[21] = {nil, nil, nil, nil, b.c, b.c, b.c, b.c, nil, nil},
[22] = {nil, nil, nil, nil, nil, b.R, b.Y, b.O, nil, nil},
[23] = {nil, b.B, b.c, b.c, b.c, b.c, b.c, b.c, b.c, nil},
[24] = {nil, b.b, b.b, b.b, b.b, b.b, b.b, b.b, b.C, nil},
},
[3] = {
[20] = {nil, nil, nil, b.R, b.m, b.o, b.M, nil, nil, nil},
[21] = {nil, nil, nil, nil, b.o, b.O, nil, nil, nil, nil},
[22] = {nil, nil, nil, nil, b.G, b.Y, nil, nil, nil, nil},
[23] = {nil, b.m, b.o, b.m, b.o, b.m, b.o, b.m, b.o, nil},
[24] = {nil, b.B, b.m, b.o, b.m, b.o, b.m, b.o, b.C, nil},
},
[4] = {
[21] = {nil, nil, b.O, b.g, b.g, b.g, b.g, nil, nil, nil},
[22] = {nil, nil, nil, b.R, b.M, b.b, b.b, nil, nil, nil},
[23] = {b.G, nil, b.Y, b.g, b.g, b.g, b.g, b.g, nil, nil},
[24] = {b.b, b.C, b.b, b.b, b.b, b.b, b.b, b.b, b.B, nil},
},
[5] = {
[16] = {nil, b.B, b.c, b.y, b.c, b.G, b.c, b.y, b.C, nil},
[22] = {nil, nil, b.c, b.y, b.c, b.y, b.c, b.y, nil, nil},
[23] = {nil, b.O, b.y, b.c, b.y, b.c, b.y, b.c, b.Y, nil},
[24] = {nil, b.R, b.c, b.y, b.c, b.y, b.c, b.y, b.M, nil},
},
[6] = {
[21] = {nil, nil, nil, nil, b.O, b.Y, nil, nil, nil, nil},
[22] = {nil, nil, b.R, nil, b.b, b.y, nil, b.M, nil, nil},
[23] = {nil, nil, nil, nil, b.y, b.b, nil, nil, nil, nil},
[24] = {nil, b.G, b.y, b.b, b.C, b.y, b.b, b.y, b.B, nil},
},
[7] = {
[20] = {nil, b.C, b.G, nil, b.r, b.g, b.r, b.g, nil, nil},
[21] = {nil, nil, nil, nil, b.R, b.M, b.g, b.r, nil, nil},
[22] = {b.r, nil, nil, nil, b.r, b.g, b.O, b.Y, nil, nil},
[23] = {b.g, b.r, b.g, b.r, b.g, b.r, b.g, b.r, nil, nil},
[24] = {b.r, b.g, b.r, b.g, b.r, b.g, b.r, b.g, b.B, nil},
},
[8] = {
[15] = {nil, nil, nil, b.B, b.m, b.m, b.m, b.m, b.m, b.C},
[16] = {nil, nil, nil, nil, nil, nil, nil, nil, nil, b.m},
[17] = {nil, nil, nil, nil, nil, nil, nil, nil, nil, b.m},
[18] = {nil, b.Y, b.y, b.y, b.y, b.y, b.y, b.y, b.y, b.G},
[21] = {b.b, b.b, b.b, b.b, b.b, b.b, b.O, nil, nil, nil},
[22] = {b.b, nil, nil, nil, nil, nil, nil, nil, nil, nil},
[23] = {b.M, nil, nil, nil, nil, nil, nil, nil, nil, nil},
[24] = {b.o, b.o, b.o, b.o, b.o, b.o, b.o, b.o, b.R, nil},
},
[9] = {
[18] = {nil, nil, nil, b.Y, b.m, b.m, b.m, b.m, nil, nil},
[19] = {nil, nil, nil, b.c, b.c, b.c, b.c, b.G, nil, nil},
[20] = {b.m, b.m, b.m, b.O, b.M, b.R, nil, nil, nil, nil},
[21] = {b.c, b.c, b.c, b.c, b.c, b.c, b.c, b.c, nil, nil},
[22] = {b.m, b.m, b.m, b.m, b.m, b.m, b.m, b.m, nil, nil},
[23] = {b.c, b.c, b.c, b.c, b.c, b.c, b.c, b.c, b.C, nil},
[24] = {b.m, b.m, b.m, b.m, b.m, b.m, b.m, b.m, b.B, nil},
},
[10] = {
[18] = {nil, nil, nil, b.C, b.g, b.g, b.B, nil, nil, nil},
[19] = {nil, nil, b.G, b.g, b.g, b.g, b.g, b.Y, nil, nil},
[20] = {nil, b.M, b.g, b.g, b.g, b.g, b.g, b.g, b.O, nil},
[21] = {nil, nil, nil, nil, b.c, nil, nil, nil, nil, nil},
[22] = {nil, nil, nil, nil, b.c, nil, nil, nil, nil, nil},
[23] = {nil, nil, nil, nil, b.c, nil, b.o, nil, nil, nil},
[24] = {nil, nil, nil, nil, b.R, b.o, b.o, nil, nil, nil},
},
[11] = {
[18] = {nil, nil, nil, nil, b.o, b.o, nil, nil, nil, nil},
[19] = {nil, nil, nil, nil, b.R, nil, nil, nil, nil, nil},
[20] = {nil, nil, nil, nil, b.r, b.O, nil, nil, nil, nil},
[21] = {nil, nil, nil, nil, nil, b.M, nil, nil, nil, nil},
[22] = {nil, nil, nil, nil, b.o, b.o, nil, nil, nil, nil},
[23] = {nil, nil, nil, nil, b.G, b.Y, nil, nil, nil, nil},
[24] = {b.C, b.g, b.g, nil, b.o, b.o, nil, b.g, b.g, b.B},
},
[12] = {
[21] = {nil, nil, nil, nil, nil, nil, nil, b.g, b.g, b.Y},
[22] = {nil, nil, b.r, b.G, b.r, nil, nil, nil, b.O, b.g},
[23] = {nil, b.r, b.C, b.r, b.B, b.r, nil, nil, nil, b.M},
[24] = {b.r, b.r, b.r, b.R, b.r, b.r, b.r, nil, nil, nil},
},
[13] = {
[20] = {b.c, nil, nil, nil, nil, nil, nil, nil, nil, b.B},
[21] = {b.c, b.c, nil, nil, nil, nil, nil, nil, b.C, b.c},
[22] = {b.c, b.c, b.c, nil, nil, nil, nil, b.G, b.c, b.c},
[23] = {b.b, b.b, b.b, b.b, nil, nil, b.Y, b.b, b.b, b.b},
[24] = {nil, b.M, b.b, b.b, b.b, b.O, b.b, b.b, b.R, nil},
},
[14] = {
[20] = {nil, nil, nil, b.y, b.r, b.y, nil, nil, nil, nil},
[21] = {b.R, nil, nil, b.Y, b.y, b.r, b.G, nil, nil, nil},
[22] = {nil, nil, nil, b.y, b.r, b.y, b.r, b.y, b.r, b.B},
[23] = {nil, nil, nil, nil, nil, nil, nil, b.O, b.y, b.r},
[24] = {nil, nil, nil, nil, nil, nil, b.M, b.y, b.r, b.C},
},
[15] = {
[17] = {nil, nil, b.b, nil, nil, nil, nil, b.b, nil, nil},
[18] = {nil, nil, b.b, b.y, b.b, b.b, b.y, b.b, nil, nil},
[19] = {nil, nil, nil, b.y, b.b, b.b, b.y, nil, nil, nil},
[20] = {nil, nil, nil, nil, b.O, b.Y, nil, nil, nil, nil},
[22] = {nil, nil, nil, nil, b.M, b.R, nil, nil, nil, nil},
[23] = {nil, nil, nil, b.G, b.y, b.y, b.C, nil, nil, nil},
[24] = {nil, nil, b.B, b.y, b.y, b.y, b.y, b.y, nil, nil},
},
[16] = {
[18] = {nil, nil, b.O, nil, nil, nil, nil, b.B, nil, nil},
[19] = {nil, nil, b.c, nil, nil, b.G, nil, b.c, nil, nil},
[20] = {nil, nil, b.c, nil, b.C, b.R, nil, b.c, nil, nil},
[21] = {nil, nil, b.c, nil, nil, nil, nil, b.c, nil, nil},
[22] = {nil, nil, b.Y, b.c, b.c, b.c, b.c, b.M, nil, nil},
},
[17] = {
[15] = {b.O, nil, nil, b.g, nil, nil, b.m, nil, nil, b.Y},
[16] = {nil, nil, nil, b.g, nil, nil, b.m, nil, nil, nil},
[17] = {nil, nil, nil, b.g, nil, nil, b.R, nil, nil, nil},
[18] = {nil, nil, nil, b.g, nil, nil, b.m, nil, nil, nil},
[19] = {nil, nil, nil, b.M, nil, nil, b.m, nil, nil, nil},
[20] = {nil, nil, nil, b.g, nil, nil, b.m, nil, nil, nil},
[21] = {nil, nil, nil, b.g, nil, nil, b.G, nil, nil, nil},
[22] = {nil, nil, nil, b.g, nil, nil, b.m, nil, nil, nil},
[23] = {nil, nil, nil, b.g, nil, nil, b.m, nil, nil, nil},
[24] = {nil, b.m, b.m, b.m, b.B, b.C, b.g, b.g, b.g, nil},
},
[18] = {
[19] = {nil, nil, nil, b.y, b.B, b.y, b.y, nil, nil, nil},
[20] = {nil, nil, b.y, nil, nil, nil, nil, b.y, nil, nil},
[21] = {nil, b.o, nil, nil, b.C, b.R, nil, nil, b.O, nil},
[22] = {nil, b.M, nil, nil, b.Y, b.G, nil, nil, b.o, nil},
[23] = {nil, b.r, b.o, nil, nil, nil, nil, b.o, b.r, nil},
[24] = {nil, b.r, b.r, b.o, b.o, b.o, b.o, b.r, b.r, nil},
},
[19] = {
[15] = {nil, nil, nil, nil, nil, nil, b.O, nil, nil, nil},
[16] = {nil, nil, nil, nil, nil, b.o, nil, nil, nil, nil},
[17] = {nil, nil, nil, nil, b.o, b.r, b.o, nil, nil, nil},
[18] = {nil, b.o, nil, nil, b.o, b.r, b.r, b.o, nil, nil},
[19] = {nil, b.o, b.o, nil, nil, b.R, b.r, b.r, nil, nil},
[20] = {nil, nil, b.M, b.r, nil, b.r, b.r, b.r, b.r, nil},
[21] = {nil, nil, b.r, b.r, b.r, b.r, b.y, b.G, b.o, b.o},
[22] = {nil, b.r, b.o, b.y, b.Y, b.y, b.y, b.y, b.y, b.o},
[23] = {nil, b.o, b.o, b.y, b.y, b.c, b.c, b.c, b.c, b.C},
[24] = {nil, nil, b.o, b.y, b.b, b.b, b.B, b.b, b.b, b.b},
},
[20] = {
[20] = {nil, nil, b.B, b.b, b.b, b.b, b.b, b.b, nil, nil},
[21] = {b.c, nil, nil, b.C, b.c, b.c, b.c, nil, nil, b.c},
[22] = {b.g, b.g, nil, nil, b.G, b.g, nil, nil, b.g, b.g},
[23] = {b.y, b.y, b.o, nil, nil, nil, nil, b.Y, b.y, b.y},
[24] = {b.r, b.r, b.r, b.R, nil, nil, b.M, b.r, b.r, b.r},
},
[21] = {
[16] = {nil, nil, b.g, b.g, b.g, b.g, b.g, nil, nil, nil},
[17] = {nil, b.g, b.g, b.g, b.g, b.g, b.g, b.B, b.g, nil},
[18] = {b.g, b.g, b.g, b.g, b.g, b.g, b.g, b.g, b.C, nil},
[19] = {b.g, nil, nil, b.g, b.o, b.o, b.g, nil, nil, b.g},
[20] = {nil, b.R, nil, b.g, b.o, b.o, nil, b.M, nil, b.G},
[21] = {nil, nil, nil, nil, b.o, b.o, nil, nil, nil, nil},
[22] = {nil, nil, nil, nil, b.o, b.o, nil, nil, nil, nil},
[23] = {nil, nil, nil, nil, b.o, b.o, nil, nil, nil, nil},
[24] = {nil, nil, nil, nil, b.Y, b.O, nil, nil, nil, nil},
},
[22] = {
[15] = {nil, nil, b.b, nil, nil, nil, nil, b.b, nil, nil},
[16] = {nil, b.b, b.O, b.b, nil, nil, b.b, b.Y, b.b, nil},
[17] = {nil, nil, b.b, nil, nil, nil, nil, b.b, nil, nil},
[20] = {nil, nil, nil, nil, b.R, b.M, nil, nil, nil, nil},
[23] = {nil, nil, nil, b.b, b.C, b.G, b.b, nil, nil, nil},
[24] = {nil, nil, nil, b.b, b.b, b.B, b.b, nil, nil, nil},
},
[23] = {
[13] = {nil, nil, nil, nil, nil, nil, nil, nil, b.c, b.m},
[14] = {nil, nil, nil, nil, nil, nil, nil, nil, b.y, b.g},
[15] = {b.G, b.B, nil, nil, nil, nil, nil, nil, nil, nil},
[16] = {b.r, b.O, nil, nil, nil, nil, nil, nil, nil, nil},
[23] = {nil, nil, nil, nil, b.C, b.M, nil, nil, nil, nil},
[24] = {nil, nil, nil, nil, b.R, b.Y, nil, nil, nil, nil},
},
[24] = {
[20] = {b.g, b.g, b.g, b.g, b.G, nil, nil, nil, nil, nil},
[21] = {nil, nil, nil, nil, nil, b.O, b.y, b.y, b.y, b.Y},
[23] = {b.M, b.r, b.r, b.r, b.R, nil, nil, nil, nil, nil},
[24] = {nil, nil, nil, nil, nil, b.M, b.r, b.r, b.r, b.R},
},
[25] = {
[18] = {nil, nil, nil, nil, nil, b.B, nil, nil, nil, nil},
[19] = {nil, nil, nil, b.G, nil, nil, nil, b.C, nil, nil},
[20] = {nil, nil, nil, nil, nil, b.Y, nil, nil, nil, nil},
[21] = {nil, nil, nil, b.M, nil, nil, nil, b.O, nil, nil},
[22] = {nil, nil, nil, nil, nil, b.R, nil, nil, nil, nil},
},
[26] = {
[13] = {nil, nil, nil, nil, b.r, b.r, nil, nil, nil, nil},
[14] = {nil, nil, nil, nil, b.o, b.o, nil, nil, nil, nil},
[15] = {nil, nil, nil, nil, b.o, b.o, nil, nil, nil, nil},
[16] = {nil, nil, nil, nil, b.o, b.o, nil, nil, nil, nil},
[17] = {nil, nil, nil, b.O, b.o, b.o, b.Y, nil, nil, nil},
[18] = {nil, nil, b.o, b.c, b.c, b.c, b.c, b.o, nil, nil},
[19] = {nil, nil, b.o, b.c, b.c, b.c, b.c, b.o, nil, nil},
[20] = {nil, nil, b.o, nil, nil, b.G, nil, b.o, nil, nil},
[21] = {nil, nil, b.o, nil, b.M, b.R, nil, b.o, nil, nil},
[22] = {nil, nil, b.o, b.b, b.b, b.b, b.B, b.o, nil, nil},
[23] = {nil, nil, b.o, b.C, b.b, b.b, b.b, b.o, nil, nil},
[24] = {nil, nil, b.o, b.o, b.o, b.o, b.o, b.o, nil, nil},
},
[27] = {
[15] = {nil, b.C, b.o, b.g, b.g, b.g, b.g, b.g, b.B, nil},
[16] = {b.g, nil, b.y, b.o, b.g, b.g, b.g, b.g, nil, b.y},
[17] = {b.g, b.g, nil, b.y, b.o, b.g, b.g, nil, b.y, b.o},
[18] = {b.g, b.g, b.g, nil, b.y, b.o, nil, b.y, b.o, b.g},
[19] = {b.g, b.g, b.g, b.o, nil, b.G, b.y, b.o, b.g, b.g},
[20] = {b.g, b.g, b.o, b.o, b.Y, nil, b.o, b.g, b.g, b.g},
[21] = {b.g, b.o, b.y, nil, b.o, b.O, nil, b.g, b.g, b.g},
[22] = {b.o, b.y, nil, b.g, b.g, b.o, b.y, nil, b.g, b.g},
[23] = {b.y, nil, b.g, b.g, b.g, b.g, b.o, b.y, nil, b.g},
[24] = {nil, b.M, b.g, b.g, b.g, b.g, b.g, b.o, b.R, nil},
},
}
local STAGE_TRANSITION_TIME = 300
function SakuraGame:new(secret_inputs)
self.super:new()
self.randomizer = (
(
secret_inputs.rotate_left and secret_inputs.rotate_right
) and History6RollsRandomizer() or SakuraRandomizer()
)
self.current_map = 1
self.time_limit = 10800
self.cleared_frames = STAGE_TRANSITION_TIME
self.stage_frames = 0
self.time_extend = 0
self.maps_cleared = 0
self.map_20_time = 0
self.stage_pieces = 0
self.grid:applyMap(maps[self.current_map])
self.lock_drop = true
self.lock_hard_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
function SakuraGame:checkRequirements()
if self.maps_cleared >= 14 + 2 * (self.current_map - 20) and
self.map_20_time <= frameTime(8,00) - frameTime(0,30) * (self.current_map - 20)
then
return false
end
return true
end
function SakuraGame:getGravity()
if self.level < 8 then return 4/256
elseif self.level < 19 then return 5/256
elseif self.level < 35 then return 6/256
elseif self.level < 40 then return 8/256
elseif self.level < 50 then return 10/256
elseif self.level < 60 then return 12/256
elseif self.level < 70 then return 16/256
elseif self.level < 80 then return 32/256
elseif self.level < 90 then return 48/256
elseif self.level < 100 then return 64/256
elseif self.level < 108 then return 4/256
elseif self.level < 119 then return 5/256
elseif self.level < 125 then return 6/256
elseif self.level < 131 then return 8/256
elseif self.level < 139 then return 12/256
elseif self.level < 149 then return 32/256
elseif self.level < 156 then return 48/256
elseif self.level < 164 then return 80/256
elseif self.level < 174 then return 112/256
elseif self.level < 180 then return 128/256
elseif self.level < 200 then return 144/256
elseif self.level < 212 then return 16/256
elseif self.level < 221 then return 48/256
elseif self.level < 232 then return 80/256
elseif self.level < 244 then return 112/256
elseif self.level < 256 then return 144/256
elseif self.level < 267 then return 176/256
elseif self.level < 277 then return 192/256
elseif self.level < 287 then return 208/256
elseif self.level < 295 then return 224/256
elseif self.level < 300 then return 240/256
else return 20 end
end
function SakuraGame:onLineClear(cleared_row_count)
self.level = self.level + cleared_row_count
for i = 13, 24 do
for j = 1, 10 do
local block = self.grid.grid[i][j]
if block and block.skin == "gem" and block.colour == "X" then
self.time_limit = self.time_limit + 60
end
end
end
end
function SakuraGame:onPieceEnter()
if self.level % 100 ~= 99 and not self.clear and self.stage_frames ~= 0 then
self.level = self.level + 1
end
if effects[self.current_map] == "mirror" and
self.stage_pieces % 3 == 0 and self.stage_pieces ~= 0
then
self.grid:mirror()
end
self.stage_pieces = self.stage_pieces + 1
end
function SakuraGame:advanceOneFrame(inputs, ruleset)
if self.ready_frames == 0 then
if self.lcd > 0 then
if self.stage_frames <= frameTime(0,10) then self.time_extend = 600
elseif self.stage_frames <= frameTime(0,30) then self.time_extend = 300
else self.time_extend = 0 end
end
if not self.grid:hasGemBlocks() or
(self.stage_frames >= 3600 and self.current_map <= 20) then
self.lcd = 0
self.are = 0
if self.stage_frames >= 3600 then self.time_extend = 0 end
self.piece = nil
-- transition to next map
if self.cleared_frames > 0 then
self.cleared_frames = self.cleared_frames - 1
if self.time_extend > 0 then
self.time_limit = self.time_limit + 3
self.time_extend = self.time_extend - 3
end
return false
end
self.hold_queue = nil
if self.current_map > 20 or (self.stage_frames < 3600 and self.current_map <= 20) then self.maps_cleared = self.maps_cleared + 1 end
self.stage_frames = -1
self.level = 0
self.grid:clear()
if (self.current_map == 20) then self.map_20_time = self.frames end
if self.current_map >= 20 and self:checkRequirements() then
self.clear = true
self.completed = true
return false
else
self.current_map = self.current_map + 1
self.big_mode = effects[self.current_map] == "big"
self.ready_frames = 100
self.stage_pieces = 0
self.grid:applyMap(maps[self.current_map])
end
-- this is necessary to fix timer
self.frames = self.frames - 1
self.time_limit = self.time_limit + 1
end
self.frames = self.frames + 1
self.stage_frames = self.stage_frames + 1
self.time_limit = math.max(self.time_limit - 1, 0)
if self.time_limit <= 0 and self.piece == nil then
self.game_over = true
end
if self.piece ~= nil and
effects[self.current_map] == "roll" and
self.stage_pieces % 4 == 0
then
self.piece.colour = "F"
if self.stage_frames % 30 == 0 then
ruleset:attemptRotate(
{[config.gamesettings.world_reverse == 3 or
(ruleset.world and config.gamesettings.world_reverse == 2)
and "rotate_left" or "rotate_right"] = true},
self.piece, self.grid, false
)
end
end
else
self.cleared_frames = STAGE_TRANSITION_TIME
if not self.prev_inputs.hold and inputs.hold then
self.hold_queue = table.remove(self.next_queue, 1)
table.insert(self.next_queue, self:getNextPiece(ruleset))
end
end
return true
end
local function colourXRay(game, block, x, y, age)
local r, g, b, a = .5,.5,.5
if ((game.stage_frames/2 - x) % 30 < 1)
or game.stage_frames == 0
or game.cleared_frames ~= STAGE_TRANSITION_TIME
or game.stage_pieces % 2 == 0
then
a = 1
else
a = 1 - age / 4
end
return r, g, b, a, a
end
local function colourColor(game, block, x, y, age)
local r, g, b, a = .5,.5,.5
if game.stage_frames == 0 or game.cleared_frames ~= STAGE_TRANSITION_TIME then
a = 1
else
a = (game.stage_frames/30 + (y + math.abs(x-5.5))/5) % 1
end
return r, g, b, a, 0
end
function SakuraGame:drawGrid()
if effects[self.current_map] == "xray" then
self.grid:drawCustom(colourXRay, self)
elseif effects[self.current_map] == "color" then
self.grid:drawCustom(colourColor, self)
else
self.grid:draw()
-- if self.piece ~= nil and self.level < 100 then
self:drawGhostPiece(ruleset)
-- end
end
end
function SakuraGame:drawScoringInfo()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_2)
love.graphics.print(
self.das.direction .. " " ..
self.das.frames .. " " ..
strTrueValues(self.prev_inputs)
)
love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("STAGE", 240, 120, 80, "left")
love.graphics.printf("TIME LIMIT", 240, 180, 80, "left")
love.graphics.printf("LEVEL", 240, 320, 40, "left")
if self.current_map <= 20 then
love.graphics.printf("STAGE LIMIT", 240, 240, 100, "left")
end
if effects[self.current_map] then
love.graphics.printf("EFFECT: " .. effects[self.current_map], 240, 300, 160, "left")
end
if self.used_randomizer.history then
love.graphics.printf("RANDOM PIECES ACTIVE!", 240, 150, 200, "left")
end
love.graphics.setFont(font_3x5_3)
love.graphics.setColor(
(self.time_limit % 4 < 2 and
self.time_limit <= frameTime(0,10) and
self.grid:hasGemBlocks() and
self.time_limit ~= 0 and
self.ready_frames == 0) and
{ 1, 0.3, 0.3, 1 } or
{ 1, 1, 1, 1 }
)
love.graphics.printf(formatTime(self.time_limit), 240, 200, 120, "left")
love.graphics.setColor(1, 1, 1, 1)
if self.current_map <= 20 then
love.graphics.printf(formatTime(3600 - self.stage_frames), 240, 260, 120, "left")
end
love.graphics.printf(self.level, 240, 340, 40, "right")
love.graphics.printf(math.floor((self.level + 100) / 100) * 100, 240, 370, 40, "right")
love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")
love.graphics.printf(self.current_map, 290, 110, 80, "left")
end
function SakuraGame:drawCustom()
love.graphics.setColor(1, 1, 1, 1)
if self.ready_frames ~= 0 and not self.clear then
love.graphics.setFont(font_3x5_4)
love.graphics.printf("STAGE " .. self.current_map, 64, 170, 160, "center")
love.graphics.setFont(font_3x5_3)
if effects[self.current_map] then
love.graphics.printf("EFFECT: " .. effects[self.current_map], 64, 270, 160, "center")
end
end
if self.cleared_frames > 0 and
(not self.grid:hasGemBlocks() or
(self.stage_frames >= 3600 and self.current_map <= 20)) then
love.graphics.setFont(font_3x5_2)
love.graphics.printf("TIME LIMIT", 64, 180, 160, "center")
love.graphics.printf("TIME EXTEND", 64, 240, 160, "center")
love.graphics.printf("STAGE TIME", 64, 300, 160, "center")
love.graphics.setFont(font_3x5_3)
love.graphics.printf("STAGE " .. self.current_map, 64, 100, 160, "center")
love.graphics.setColor(
self.cleared_frames % 4 < 2 and
{ 1, 1, 0.3, 1 } or
{ 1, 1, 1, 1 }
)
love.graphics.printf(formatTime(self.time_limit), 64, 200, 160, "center")
love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(formatTime(self.time_extend), 64, 260, 160, "center")
love.graphics.printf(formatTime(self.stage_frames), 64, 320, 160, "center")
love.graphics.setFont(font_3x5_4)
love.graphics.printf((self.stage_frames >= 3600 and self.current_map <= 20) and "" or "CLEAR!", 64, 130, 160, "center")
end
if self.clear then
love.graphics.setFont(font_3x5_3)
love.graphics.printf("EXCELLENT!", 64, 180, 160, "center")
love.graphics.setFont(font_3x5_2)
if self.current_map ~= 27 then
love.graphics.printf("...but let's go\nbetter next time", 64, 220, 160, "center")
end
end
end
function SakuraGame:getBackground()
return (self.current_map - 1) % 20
end
function SakuraGame:getHighscoreData()
return {
maps = self.maps_cleared,
current_map = self.current_map,
frames = self.frames,
}
end
return SakuraGame

View File

@@ -52,14 +52,23 @@ function StrategyGame:getLineClearDelay()
end
function StrategyGame:getLockDelay()
if self.level < 700 then return 8
else return 6 end
if self.level < 500 then return 8
elseif self.level < 700 then return 6
else return 4 end
end
function StrategyGame:getGravity()
return 20
end
function StrategyGame:getNextPiece(ruleset)
return {
skin = "2tie",
shape = self.randomizer:nextPiece(),
orientation = ruleset:getDefaultOrientation(),
}
end
function StrategyGame:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1

View File

@@ -94,8 +94,12 @@ function Survival2020Game:getGravity()
return 20
end
function Survival2020Game:getSkin()
return self.level >= 1000 and "bone" or "2tie"
function Survival2020Game:getNextPiece(ruleset)
return {
skin = self.level >= 1000 and "bone" or "2tie",
shape = self.randomizer:nextPiece(),
orientation = ruleset:getDefaultOrientation(),
}
end
function Survival2020Game:hitTorikan(old_level, new_level)

View File

@@ -66,24 +66,24 @@ function SurvivalA1Game:getGravity()
end
local function getRankForScore(score)
if score < 400 then return {rank = "9", next = 400}
elseif score < 800 then return {rank = "8", next = 800}
elseif score < 1400 then return {rank = "7", next = 1400}
elseif score < 2000 then return {rank = "6", next = 2000}
elseif score < 3500 then return {rank = "5", next = 3500}
elseif score < 5500 then return {rank = "4", next = 5500}
elseif score < 8000 then return {rank = "3", next = 8000}
elseif score < 12000 then return {rank = "2", next = 12000}
elseif score < 16000 then return {rank = "1", next = 16000}
elseif score < 22000 then return {rank = "S1", next = 22000}
elseif score < 30000 then return {rank = "S2", next = 30000}
elseif score < 40000 then return {rank = "S3", next = 40000}
elseif score < 52000 then return {rank = "S4", next = 52000}
elseif score < 66000 then return {rank = "S5", next = 66000}
elseif score < 82000 then return {rank = "S6", next = 82000}
elseif score < 100000 then return {rank = "S7", next = 100000}
elseif score < 120000 then return {rank = "S8", next = 120000}
else return {rank = "S9", next = "???"}
if score < 400 then return {rank = "9", next = 400, grade = 0}
elseif score < 800 then return {rank = "8", next = 800, grade = 1}
elseif score < 1400 then return {rank = "7", next = 1400, grade = 2}
elseif score < 2000 then return {rank = "6", next = 2000, grade = 3}
elseif score < 3500 then return {rank = "5", next = 3500, grade = 4}
elseif score < 5500 then return {rank = "4", next = 5500, grade = 5}
elseif score < 8000 then return {rank = "3", next = 8000, grade = 6}
elseif score < 12000 then return {rank = "2", next = 12000, grade = 7}
elseif score < 16000 then return {rank = "1", next = 16000, grade = 8}
elseif score < 22000 then return {rank = "S1", next = 22000, grade = 9}
elseif score < 30000 then return {rank = "S2", next = 30000, grade = 10}
elseif score < 40000 then return {rank = "S3", next = 40000, grade = 11}
elseif score < 52000 then return {rank = "S4", next = 52000, grade = 12}
elseif score < 66000 then return {rank = "S5", next = 66000, grade = 13}
elseif score < 82000 then return {rank = "S6", next = 82000, grade = 14}
elseif score < 100000 then return {rank = "S7", next = 100000, grade = 15}
elseif score < 120000 then return {rank = "S8", next = 120000, grade = 16}
else return {rank = "S9", next = "???", grade = 17}
end
end
@@ -111,8 +111,9 @@ function SurvivalA1Game:onLineClear(cleared_row_count)
local new_level = math.min(self.level + cleared_row_count, 999)
if new_level == 999 then
self.clear = true
end
else
self.level = new_level
end
end
end
@@ -207,10 +208,15 @@ end
function SurvivalA1Game:getHighscoreData()
return {
grade = self.grade,
grade = (
(self.gm_conditions["level300"] and
self.gm_conditions["level500"] and
self.gm_conditions["level999"]) and
18 or getRankForScore(self.score).grade
),
frames = self.frames,
score = self.score,
level = self.level,
frames = self.frames,
}
end

View File

@@ -4,7 +4,6 @@ local BagRandomizer = Randomizer:extend()
function BagRandomizer:new(pieces)
self.bag = {}
self.possible_pieces = pieces
self.pieces = pieces
for i = 1, self.pieces do
table.insert(self.bag, i)

View File

@@ -28,8 +28,11 @@ end
function History6Rolls35PoolRandomizer:generatePiece()
local index, x
if self.first then
index = math.random(20)
x = self.pool[index]
local prevent = {"S", "Z", "O"}
repeat
index = math.random(#self.pool)
x = self.pool[index]
until not inHistory(x, prevent)
self.first = false
else
for i = 1, 6 do

View File

@@ -110,7 +110,13 @@ function ARS:onPieceDrop(piece, grid)
piece.lock_delay = 0 -- step reset
end
function ARS:get180RotationValue() return 3 end
function ARS:get180RotationValue()
if config.gamesettings.world_reverse == 3 then
return 1
else
return 3
end
end
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default

View File

@@ -1,5 +1,5 @@
local Piece = require 'tetris.components.piece'
local Ruleset = require 'tetris.rulesets.arika_ace2'
local Ruleset = require 'tetris.rulesets.arika_ti'
local ARS = Ruleset:extend()
@@ -19,4 +19,34 @@ ARS.colourscheme = {
ARS.softdrop_lock = false
ARS.harddrop_lock = true
function ARS:onPieceCreate(piece, grid)
piece.floorkick = 0
piece.manipulations = 0
end
function ARS:onPieceMove(piece, grid)
piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= 128 then
piece:dropToBottom(grid)
piece.locked = true
end
end
end
function ARS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- rotate reset
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= 128 then
piece:dropToBottom(grid)
piece.locked = true
end
end
if piece.floorkick >= 1 then
piece.floorkick = piece.floorkick + 1
end
end
return ARS

View File

@@ -5,86 +5,12 @@ local ARS = Ruleset:extend()
ARS.name = "ACE-ARS2"
ARS.hash = "ArikaACE2"
ARS.spawn_above_field = true
function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
-- O doesn't kick
if (piece.shape == "O") then return end
-- center column rule
if (
piece.shape == "J" or piece.shape == "T" or piece.shape == "L"
) and (
piece.rotation == 0 or piece.rotation == 2
) then
local offsets = new_piece:getBlockOffsets()
table.sort(offsets, function(A, B) return A.y < B.y or A.y == B.y and A.x < B.y end)
for index, offset in pairs(offsets) do
if grid:isOccupied(piece.position.x + offset.x, piece.position.y + offset.y) then
if offset.x == 0 then
return
else
break
end
end
end
end
if piece.shape == "I" then
-- special kick rules for I
if (new_piece.rotation == 0 or new_piece.rotation == 2) and
(piece:isMoveBlocked(grid, {x=-1, y=0}) or piece:isMoveBlocked(grid, {x=1, y=0})) then
-- kick right, right2, left
if grid:canPlacePiece(new_piece:withOffset({x=1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
self:onPieceRotate(piece, grid)
elseif grid:canPlacePiece(new_piece:withOffset({x=2, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=2, y=0})
self:onPieceRotate(piece, grid)
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
self:onPieceRotate(piece, grid)
end
elseif piece:isDropBlocked(grid) and (new_piece.rotation == 1 or new_piece.rotation == 3) then
-- kick up, up2
if grid:canPlacePiece(new_piece:withOffset({x=0, y=-1})) then
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
self:onPieceRotate(piece, grid)
elseif grid:canPlacePiece(new_piece:withOffset({x=0, y=-2})) then
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-2})
self:onPieceRotate(piece, grid)
end
end
else
-- kick right, kick left
if grid:canPlacePiece(new_piece:withOffset({x=1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
self:onPieceRotate(piece, grid)
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
self:onPieceRotate(piece, grid)
elseif piece.shape == "T"
and new_piece.rotation == 0
and piece:isDropBlocked(grid)
and grid:canPlacePiece(new_piece:withOffset({x=0, y=-1}))
then
-- T floorkick
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
self:onPieceRotate(piece, grid)
end
end
end
function ARS:onPieceCreate(piece, grid)
piece.floorkick = 0
piece.manipulations = 0
end
function ARS:onPieceDrop(piece, grid)
piece.lock_delay = 0
end
function ARS:onPieceMove(piece, grid)
piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then
@@ -105,10 +31,9 @@ function ARS:onPieceRotate(piece, grid)
piece.locked = true
end
end
if piece.floorkick >= 1 then
piece.floorkick = piece.floorkick + 1
end
end
function ARS:get180RotationValue() return 3 end
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
return ARS

View File

@@ -4,35 +4,18 @@ local Ruleset = require 'tetris.rulesets.ti_srs'
local SRS = Ruleset:extend()
SRS.name = "ACE-SRS"
SRS.hash = "StandardACE"
SRS.world = true
SRS.colourscheme = {
I = "C",
L = "O",
J = "B",
S = "G",
Z = "R",
O = "Y",
T = "M",
}
SRS.softdrop_lock = false
SRS.harddrop_lock = true
SRS.spawn_above_field = true
SRS.hash = "ACE Standard"
SRS.MANIPULATIONS_MAX = 128
function SRS:onPieceRotate(piece, grid, upward)
function SRS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- rotate reset
if upward or piece:isDropBlocked(grid) then
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= self.MANIPULATIONS_MAX and piece:isDropBlocked(grid) then
if piece.manipulations >= self.MANIPULATIONS_MAX then
piece.locked = true
end
end
end
function SRS:canPieceRotate(piece)
return piece.manipulations < self.MANIPULATIONS_MAX
end
return SRS

View File

@@ -38,35 +38,35 @@ function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
(piece:isMoveBlocked(grid, {x=-1, y=0}) or piece:isMoveBlocked(grid, {x=1, y=0})) then
-- kick right, right2, left
if grid:canPlacePiece(new_piece:withOffset({x=1, y=0})) then
self:onPieceRotate(piece, grid)
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
self:onPieceRotate(piece, grid)
elseif grid:canPlacePiece(new_piece:withOffset({x=2, y=0})) then
self:onPieceRotate(piece, grid)
piece:setRelativeRotation(rot_dir):setOffset({x=2, y=0})
self:onPieceRotate(piece, grid)
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
self:onPieceRotate(piece, grid)
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
end
elseif piece:isDropBlocked(grid) and (new_piece.rotation == 1 or new_piece.rotation == 3) and piece.floorkick == 0 then
-- kick up, up2
if grid:canPlacePiece(new_piece:withOffset({x=0, y=-1})) then
self:onPieceRotate(piece, grid)
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
piece.floorkick = 1
self:onPieceRotate(piece, grid, true)
elseif grid:canPlacePiece(new_piece:withOffset({x=0, y=-2})) then
self:onPieceRotate(piece, grid)
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-2})
piece.floorkick = 1
self:onPieceRotate(piece, grid, true)
end
end
else
-- kick right, kick left
if grid:canPlacePiece(new_piece:withOffset({x=1, y=0})) then
self:onPieceRotate(piece, grid)
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
self:onPieceRotate(piece, grid)
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
self:onPieceRotate(piece, grid)
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
elseif piece.shape == "T"
and new_piece.rotation == 0
and piece.floorkick == 0
@@ -75,8 +75,8 @@ function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
then
-- T floorkick
piece.floorkick = piece.floorkick + 1
self:onPieceRotate(piece, grid)
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
self:onPieceRotate(piece, grid, true)
end
end
@@ -93,16 +93,10 @@ function ARS:onPieceDrop(piece, grid)
end
end
function ARS:onPieceRotate(piece, grid, floorkick)
if piece.floorkick >= 2 and piece:isDropBlocked(grid) then
piece.locked = true
elseif piece.floorkick >= 1 and not floorkick then
function ARS:onPieceRotate(piece, grid)
if piece.floorkick >= 1 then
piece.floorkick = piece.floorkick + 1
end
end
function ARS:get180RotationValue() return 3 end
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
return ARS

View File

@@ -364,9 +364,9 @@ function CRS:attemptRotate(new_inputs, piece, grid, initial)
if rot_dir == 0 then return end
if config.gamesettings.world_reverse == 3 or (self.world and config.gamesettings.world_reverse == 2) then
rot_dir = 4 - rot_dir
end
if self.world and config.gamesettings.world_reverse == 2 then
rot_dir = 4 - rot_dir
end
local new_piece = piece:withRelativeRotation(rot_dir)
self:attemptWallkicks(piece, new_piece, rot_dir, grid)
@@ -384,9 +384,9 @@ function CRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
for idx, offset in pairs(kicks) do
kicked_piece = new_piece:withOffset(offset)
if grid:canPlacePiece(kicked_piece) then
self:onPieceRotate(piece, grid)
piece:setRelativeRotation(rot_dir)
piece:setOffset(offset)
self:onPieceRotate(piece, grid)
return
end
end

275
tetris/rulesets/pairs.lua Normal file
View File

@@ -0,0 +1,275 @@
local Ruleset = require 'tetris.rulesets.ruleset'
local PAIRS = Ruleset:extend()
PAIRS.name = "PAIRS"
PAIRS.hash = "PAIRS"
PAIRS.world = true
PAIRS.spawn_positions = {
[1] = { x=4, y=4 },
[2] = { x=4, y=5 },
[3] = { x=4, y=5 },
[4] = { x=4, y=5 },
[5] = { x=5, y=5 },
[6] = { x=5, y=5 },
[7] = { x=5, y=5 },
[8] = { x=5, y=5 },
[9] = { x=5, y=5 },
[10] = { x=5, y=5 },
[11] = { x=4, y=5 },
[12] = { x=4, y=5 },
[13] = { x=4, y=5 },
[14] = { x=4, y=5 },
[15] = { x=4, y=5 },
[16] = { x=4, y=5 },
[17] = { x=4, y=5 },
[18] = { x=4, y=5 },
}
PAIRS.big_spawn_positions = {
[1] = { x=2, y=2 },
[2] = { x=2, y=3 },
[3] = { x=2, y=3 },
[4] = { x=2, y=3 },
[5] = { x=3, y=3 },
[6] = { x=3, y=3 },
[7] = { x=3, y=3 },
[8] = { x=3, y=3 },
[9] = { x=3, y=3 },
[10] = { x=3, y=3 },
[11] = { x=2, y=3 },
[12] = { x=2, y=3 },
[13] = { x=2, y=3 },
[14] = { x=2, y=3 },
[15] = { x=2, y=3 },
[16] = { x=2, y=3 },
[17] = { x=2, y=3 },
[18] = { x=2, y=3 },
}
PAIRS.draw_offsets = {
[1] = { x=0, y=0 },
[2] = { x=0, y=0 },
[3] = { x=0, y=0 },
[4] = { x=0, y=0 },
[5] = { x=0, y=0 },
[6] = { x=0, y=0 },
[7] = { x=0, y=0 },
[8] = { x=0, y=0 },
[9] = { x=0, y=0 },
[10] = { x=0, y=0 },
[11] = { x=0, y=0 },
[12] = { x=0, y=0 },
[13] = { x=0, y=0 },
[14] = { x=0, y=0 },
[15] = { x=0, y=0 },
[16] = { x=0, y=0 },
[17] = { x=0, y=0 },
[18] = { x=0, y=0 },
}
PAIRS.next_sounds = {
[1] = "I",
[2] = "O",
[3] = "S",
[4] = "Z",
[5] = "L",
[6] = "J",
[7] = "Z",
[8] = "S",
[9] = "J",
[10] = "L",
[11] = "O",
[12] = "O",
[13] = "T",
[14] = "L",
[15] = "J",
[16] = "T",
[17] = "J",
[18] = "I"
}
PAIRS.colourscheme = {
[1] = "R",
[2] = "C",
[3] = "G",
[4] = "M",
[5] = "O",
[6] = "C",
[7] = "G",
[8] = "M",
[9] = "G",
[10] = "M",
[11] = "Y",
[12] = "B",
[13] = "M",
[14] = "O",
[15] = "B",
[16] = "G",
[17] = "C",
[18] = "R"
}
PAIRS.pieces = 18
PAIRS.block_offsets = {
[1]={
{ {x=-2, y=0}, {x=-1, y=0}, {x=0, y=0}, {x=1, y=0}, {x=2, y=0} },
{ {x=0, y=-2}, {x=0, y=-1}, {x=0, y=0}, {x=0, y=1}, {x=0, y=2} },
{ {x=-2, y=0}, {x=-1, y=0}, {x=0, y=0}, {x=1, y=0}, {x=2, y=0} },
{ {x=0, y=-2}, {x=0, y=-1}, {x=0, y=0}, {x=0, y=1}, {x=0, y=2} },
},
[2]={
{ {x=0, y=-1}, {x=0, y=-2}, {x=0, y=0}, {x=1, y=-1}, {x=-1, y=-1} },
{ {x=0, y=-1}, {x=0, y=-2}, {x=0, y=0}, {x=1, y=-1}, {x=-1, y=-1} },
{ {x=0, y=-1}, {x=0, y=-2}, {x=0, y=0}, {x=1, y=-1}, {x=-1, y=-1} },
{ {x=0, y=-1}, {x=0, y=-2}, {x=0, y=0}, {x=1, y=-1}, {x=-1, y=-1} },
},
[3]={
{ {x=0, y=-1}, {x=0, y=-2}, {x=0, y=0}, {x=1, y=-2}, {x=-1, y=0} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=1, y=0}, {x=-1, y=-2} },
{ {x=0, y=-1}, {x=0, y=-2}, {x=0, y=0}, {x=1, y=-2}, {x=-1, y=0} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=1, y=0}, {x=-1, y=-2} },
},
[4]={
{ {x=0, y=-1}, {x=0, y=-2}, {x=0, y=0}, {x=1, y=0}, {x=-1, y=-2} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=-1, y=0}, {x=1, y=-2} },
{ {x=0, y=-1}, {x=0, y=-2}, {x=0, y=0}, {x=1, y=0}, {x=-1, y=-2} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=-1, y=0}, {x=1, y=-2} },
},
[5]={
{ {x=1, y=-1}, {x=-2, y=0}, {x=-1, y=0}, {x=0, y=0}, {x=1, y=0} },
{ {x=0, y=0}, {x=-1, y=-3}, {x=-1, y=-2}, {x=-1, y=-1}, {x=-1, y=0} },
{ {x=-2, y=0}, {x=-2, y=-1}, {x=-1, y=-1}, {x=0, y=-1}, {x=1, y=-1} },
{ {x=-1, y=-3}, {x=0, y=-3}, {x=0, y=-2}, {x=0, y=-1}, {x=0, y=0} },
},
[6]={
{ {x=-2, y=-1}, {x=-2, y=0}, {x=-1, y=0}, {x=0, y=0}, {x=1, y=0} },
{ {x=0, y=-3}, {x=-1, y=-3}, {x=-1, y=-2}, {x=-1, y=-1}, {x=-1, y=0} },
{ {x=1, y=0}, {x=-2, y=-1}, {x=-1, y=-1}, {x=0, y=-1}, {x=1, y=-1} },
{ {x=-1, y=0}, {x=0, y=-3}, {x=0, y=-2}, {x=0, y=-1}, {x=0, y=0} },
},
[7]={
{ {x=-2, y=-1}, {x=-1, y=-1}, {x=-1, y=0}, {x=0, y=0}, {x=1, y=0} },
{ {x=0, y=-3}, {x=0, y=-2}, {x=-1, y=-2}, {x=-1, y=-1}, {x=-1, y=0} },
{ {x=-2, y=-1}, {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=1, y=0} },
{ {x=-1, y=0}, {x=0, y=-3}, {x=0, y=-2}, {x=0, y=-1}, {x=-1, y=-1} },
},
[8]={
{ {x=1, y=-1}, {x=-2, y=0}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=-3}, {x=-1, y=-2}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=-2, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1}, {x=1, y=-1} },
{ {x=-1, y=-3}, {x=-1, y=-2}, {x=0, y=-2}, {x=0, y=-1}, {x=0, y=0} },
},
[9]={
{ {x=-1, y=-1}, {x=-2, y=0}, {x=-1, y=0}, {x=0, y=0}, {x=1, y=0} },
{ {x=0, y=-2}, {x=-1, y=-3}, {x=-1, y=-2}, {x=-1, y=-1}, {x=-1, y=0} },
{ {x=0, y=0}, {x=-2, y=-1}, {x=-1, y=-1}, {x=0, y=-1}, {x=1, y=-1} },
{ {x=-1, y=-1}, {x=0, y=-3}, {x=0, y=-2}, {x=0, y=-1}, {x=0, y=0} },
},
[10]={
{ {x=0, y=-1}, {x=-2, y=0}, {x=-1, y=0}, {x=0, y=0}, {x=1, y=0} },
{ {x=0, y=-1}, {x=-1, y=-3}, {x=-1, y=-2}, {x=-1, y=-1}, {x=-1, y=0} },
{ {x=-1, y=0}, {x=-2, y=-1}, {x=-1, y=-1}, {x=0, y=-1}, {x=1, y=-1} },
{ {x=-1, y=-2}, {x=0, y=-3}, {x=0, y=-2}, {x=0, y=-1}, {x=0, y=0} },
},
[11]={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1}, {x=-1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=-2}, {x=1, y=-1}, {x=1, y=-2} },
{ {x=0, y=0}, {x=1, y=-1}, {x=1, y=0}, {x=0, y=-1}, {x=-1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=-2}, {x=-1, y=-1}, {x=-1, y=0} },
},
[12]={
{ {x=0, y=0}, {x=1, y=-1}, {x=1, y=0}, {x=0, y=-1}, {x=-1, y=0} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=-2}, {x=1, y=-1}, {x=1, y=0} },
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=-1}, {x=0, y=-1}, {x=-1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=-2}, {x=-1, y=-1}, {x=-1, y=-2} },
},
[13]={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=-1, y=-1}, {x=1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=-2}, {x=1, y=0}, {x=1, y=-2} },
{ {x=0, y=-1}, {x=-1, y=0}, {x=1, y=0}, {x=-1, y=-1}, {x=1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=-2}, {x=-1, y=0}, {x=-1, y=-2} },
},
[14]={
{ {x=0, y=-1}, {x=0, y=0}, {x=0, y=-2}, {x=-1, y=-1}, {x=1, y=0} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=0, y=-2}, {x=-1, y=0} },
{ {x=0, y=-1}, {x=0, y=-2}, {x=0, y=0}, {x=1, y=-1}, {x=-1, y=-2} },
{ {x=0, y=-1}, {x=1, y=-1}, {x=-1, y=-1}, {x=0, y=0}, {x=1, y=-2} },
},
[15]={
{ {x=0, y=-1}, {x=0, y=0}, {x=0, y=-2}, {x=-1, y=0}, {x=1, y=-1} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=-1, y=-2}, {x=0, y=0} },
{ {x=0, y=-1}, {x=0, y=-2}, {x=0, y=0}, {x=1, y=-2}, {x=-1, y=-1} },
{ {x=0, y=-1}, {x=1, y=-1}, {x=-1, y=-1}, {x=1, y=0}, {x=0, y=-2} },
},
[16]={
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=-2}, {x=-1, y=0}, {x=1, y=0} },
{ {x=-1, y=0}, {x=0, y=-1}, {x=-1, y=-2}, {x=-1, y=-1}, {x=1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=-2}, {x=-1, y=-2}, {x=1, y=-2} },
{ {x=1, y=0}, {x=0, y=-1}, {x=1, y=-2}, {x=-1, y=-1}, {x=1, y=-1} },
},
[17]={
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=-1, y=-2} },
{ {x=0, y=-2}, {x=1, y=-2}, {x=-1, y=0}, {x=-1, y=-1}, {x=-1, y=-2} },
{ {x=0, y=-2}, {x=1, y=0}, {x=-1, y=-2}, {x=1, y=-1}, {x=1, y=-2} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=1, y=-1}, {x=1, y=-2} },
},
[18]={
{ {x=1, y=0}, {x=0, y=0}, {x=0, y=-1}, {x=-1, y=-1}, {x=-1, y=-2} },
{ {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=-2}, {x=1, y=-2} },
{ {x=-1, y=-2}, {x=0, y=-2}, {x=0, y=-1}, {x=1, y=-1}, {x=1, y=0} },
{ {x=1, y=-2}, {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0} },
},
}
PAIRS.wallkicks = {
{x=1, y=0}, {x=-1, y=0}, {x=2, y=0}, {x=-2, y=0}, {x=0, y=-1}
}
function PAIRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
if (piece.shape == 2) then return end
local kicks = PAIRS.wallkicks
assert(piece.rotation ~= new_piece.rotation)
for idx, offset in pairs(kicks) do
kicked_piece = new_piece:withOffset(offset)
if grid:canPlacePiece(kicked_piece) then
self:onPieceRotate(piece, grid)
piece:setRelativeRotation(rot_dir)
piece:setOffset(offset)
return
end
end
end
function PAIRS:checkNewLow(piece)
for _, block in pairs(piece:getBlockOffsets()) do
local y = piece.position.y + block.y
if y > piece.lowest_y then
piece.lock_delay = 0
piece.lowest_y = y
end
end
end
function PAIRS:onPieceCreate(piece, grid)
piece.lowest_y = -math.huge
end
function PAIRS:onPieceDrop(piece, grid)
self:checkNewLow(piece)
end
function PAIRS:get180RotationValue()
if config.gamesettings.world_reverse == 1 then
return 1
else
return 3
end
end
return PAIRS

View File

@@ -23,7 +23,6 @@ Ruleset.harddrop_lock = false
Ruleset.enable_IRS_wallkicks = false
Ruleset.are_cancel = false
Ruleset.are = true
Ruleset.spawn_above_field = false
Ruleset.next_sounds = {
I = "I",
@@ -35,30 +34,73 @@ Ruleset.next_sounds = {
T = "T"
}
Ruleset.draw_offsets = {
I = { x=0, y=0 },
J = { x=0, y=0 },
L = { x=0, y=0 },
O = { x=0, y=0 },
S = { x=0, y=0 },
T = { x=0, y=0 },
Z = { x=0, y=0 },
}
Ruleset.pieces = 7
-- Component functions.
function Ruleset:new(game_mode)
self.game = game_mode
local bones
function Ruleset:new()
if config.gamesettings.piece_colour == 1 then
bones = self.world and "w" or ""
blocks["bone"] = (not self.world) and
{
R = love.graphics.newImage("res/img/bone.png"),
O = love.graphics.newImage("res/img/bone.png"),
Y = love.graphics.newImage("res/img/bone.png"),
G = love.graphics.newImage("res/img/bone.png"),
C = love.graphics.newImage("res/img/bone.png"),
B = love.graphics.newImage("res/img/bone.png"),
M = love.graphics.newImage("res/img/bone.png"),
F = love.graphics.newImage("res/img/bone.png"),
A = love.graphics.newImage("res/img/bone.png"),
X = love.graphics.newImage("res/img/bone.png"),
} or {
R = love.graphics.newImage("res/img/bonew.png"),
O = love.graphics.newImage("res/img/bonew.png"),
Y = love.graphics.newImage("res/img/bonew.png"),
G = love.graphics.newImage("res/img/bonew.png"),
C = love.graphics.newImage("res/img/bonew.png"),
B = love.graphics.newImage("res/img/bonew.png"),
M = love.graphics.newImage("res/img/bonew.png"),
F = love.graphics.newImage("res/img/bonew.png"),
A = love.graphics.newImage("res/img/bonew.png"),
X = love.graphics.newImage("res/img/bonew.png"),
}
else
bones = config.gamesettings.piece_colour == 3 and "w" or ""
blocks["bone"] = (config.gamesettings.piece_colour == 2) and
{
R = love.graphics.newImage("res/img/bone.png"),
O = love.graphics.newImage("res/img/bone.png"),
Y = love.graphics.newImage("res/img/bone.png"),
G = love.graphics.newImage("res/img/bone.png"),
C = love.graphics.newImage("res/img/bone.png"),
B = love.graphics.newImage("res/img/bone.png"),
M = love.graphics.newImage("res/img/bone.png"),
F = love.graphics.newImage("res/img/bone.png"),
A = love.graphics.newImage("res/img/bone.png"),
X = love.graphics.newImage("res/img/bone.png"),
} or {
R = love.graphics.newImage("res/img/bonew.png"),
O = love.graphics.newImage("res/img/bonew.png"),
Y = love.graphics.newImage("res/img/bonew.png"),
G = love.graphics.newImage("res/img/bonew.png"),
C = love.graphics.newImage("res/img/bonew.png"),
B = love.graphics.newImage("res/img/bonew.png"),
M = love.graphics.newImage("res/img/bonew.png"),
F = love.graphics.newImage("res/img/bonew.png"),
A = love.graphics.newImage("res/img/bonew.png"),
X = love.graphics.newImage("res/img/bonew.png"),
}
end
blocks.bone = {
R = love.graphics.newImage("res/img/bone" .. bones .. ".png"),
O = love.graphics.newImage("res/img/bone" .. bones .. ".png"),
Y = love.graphics.newImage("res/img/bone" .. bones .. ".png"),
G = love.graphics.newImage("res/img/bone" .. bones .. ".png"),
C = love.graphics.newImage("res/img/bone" .. bones .. ".png"),
B = love.graphics.newImage("res/img/bone" .. bones .. ".png"),
M = love.graphics.newImage("res/img/bone" .. bones .. ".png"),
F = love.graphics.newImage("res/img/bone" .. bones .. ".png"),
A = love.graphics.newImage("res/img/bone" .. bones .. ".png"),
X = love.graphics.newImage("res/img/bone" .. bones .. ".png"),
}
end
function Ruleset:rotatePiece(inputs, piece, grid, prev_inputs, initial)
@@ -72,9 +114,7 @@ function Ruleset:rotatePiece(inputs, piece, grid, prev_inputs, initial)
local was_drop_blocked = piece:isDropBlocked(grid)
if self:canPieceRotate(piece, grid) then
self:attemptRotate(new_inputs, piece, grid, initial)
end
self:attemptRotate(new_inputs, piece, grid, initial)
if not was_drop_blocked and piece:isDropBlocked(grid) then
playSE("bottom")
@@ -119,47 +159,28 @@ function Ruleset:attemptWallkicks(piece, new_piece, rot_dir, grid)
end
function Ruleset:movePiece(piece, grid, move, instant)
if not self:canPieceMove(piece, grid) then return end
local x = piece.position.x
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
piece:moveInGrid({x=-1, y=0}, 1, grid, false)
elseif move == "right" then
offset.x = 1
moves = 1
piece:moveInGrid({x=1, y=0}, 1, grid, false)
elseif move == "speedleft" then
offset.x = -1
moves = grid.width
piece:moveInGrid({x=-1, y=0}, grid.width, grid, instant)
elseif move == "speedright" then
offset.x = 1
moves = grid.width
piece:moveInGrid({x=1, y=0}, grid.width, grid, instant)
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)
if piece.position.x ~= x then
self:onPieceMove(piece, grid)
if not was_drop_blocked and piece:isDropBlocked(grid) then
playSE("bottom")
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
if instant and piece.position.y ~= y then
self:onPieceDrop(piece, grid)
end
end
function Ruleset:dropPiece(
inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
hard_drop_enabled, additive_gravity, classic_lock
hard_drop_enabled, additive_gravity
)
if piece.big then
gravity = gravity / 2
@@ -169,48 +190,39 @@ function Ruleset:dropPiece(
local y = piece.position.y
if inputs["down"] == true and drop_locked == false then
if additive_gravity then
piece:addGravity(gravity + drop_speed, grid, classic_lock)
piece:addGravity(gravity + drop_speed, grid)
else
piece:addGravity(math.max(gravity, drop_speed), grid, classic_lock)
piece:addGravity(math.max(gravity, drop_speed), grid)
end
elseif inputs["up"] == true and hard_drop_enabled == true then
if hard_drop_locked == true or piece:isDropBlocked(grid) then
piece:addGravity(gravity, grid, classic_lock)
piece:addGravity(gravity, grid)
else
piece:dropToBottom(grid)
end
else
piece:addGravity(gravity, grid, classic_lock)
piece:addGravity(gravity, grid)
end
if piece.position.y ~= y then
self:onPieceDrop(piece, grid)
end
end
function Ruleset:lockPiece(piece, grid, lock_delay, classic_lock)
if piece:isDropBlocked(grid) and (
(classic_lock and piece.gravity >= 1) or
(not classic_lock and piece.lock_delay >= lock_delay)
) then
function Ruleset:lockPiece(piece, grid, lock_delay)
if piece:isDropBlocked(grid) and piece.gravity >= 1 and piece.lock_delay >= lock_delay then
piece.locked = true
end
end
function Ruleset:get180RotationValue() return 2 end
function Ruleset:getDefaultOrientation() return 1 end
function Ruleset:getDrawOffset(shape, orientation) return { x=0, y=0 } end
function Ruleset:getAboveFieldOffset(shape, orientation)
if shape == "I" then
return 1
else
return 2
end
end
function Ruleset:initializePiece(
inputs, data, grid, gravity, prev_inputs,
move, lock_delay, drop_speed,
drop_locked, hard_drop_locked, big, irs
drop_locked, hard_drop_locked, big, irs,
buffer_hard_drop, buffer_soft_drop,
lock_on_hard_drop, lock_on_soft_drop
)
local spawn_positions
if big then
@@ -218,40 +230,26 @@ function Ruleset:initializePiece(
else
spawn_positions = self.spawn_positions
end
local colours
if self.pieces == 7 then
colours = ({self.colourscheme, ColourSchemes.Arika, ColourSchemes.TTC})[config.gamesettings.piece_colour]
else
colours = self.colourscheme
end
local colours = ({self.colourscheme, ColourSchemes.Arika, ColourSchemes.TTC})[config.gamesettings.piece_colour]
local spawn_x
if (grid.width ~= 10) then
local percent = spawn_positions[data.shape].x / 10
for i = grid.width - 1, 0, -1 do
if i / grid.width <= percent then
for i = 0, grid.width - 1 do
if i / grid.width >= percent then
spawn_x = i
break
end
end
end
local spawn_dy
if (config.gamesettings.spawn_positions == 1) then
spawn_dy = (
self.spawn_above_field and
self:getAboveFieldOffset(data.shape, data.orientation) or 0
)
else
spawn_dy = (
config.gamesettings.spawn_positions == 3 and
self:getAboveFieldOffset(data.shape, data.orientation) or 0
)
end
local spawn_dy = (
config.gamesettings.spawn_positions == 2 and
2 or 0
)
local piece = Piece(data.shape, data.orientation - 1, {
x = spawn_x or spawn_positions[data.shape].x,
x = spawn_x and 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)
@@ -263,6 +261,13 @@ function Ruleset:initializePiece(
end
end
self:dropPiece(inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked)
if (buffer_hard_drop and config.gamesettings.buffer_lock == 1) then
piece:dropToBottom(grid)
if lock_on_hard_drop then piece.locked = true end
end
if (buffer_soft_drop and lock_on_soft_drop and piece:isDropBlocked(grid) and config.gamesettings.buffer_lock == 1) then
piece.locked = true
end
return piece
end
@@ -273,9 +278,8 @@ function Ruleset:processPiece(
inputs, piece, grid, gravity, prev_inputs,
move, lock_delay, drop_speed,
drop_locked, hard_drop_locked,
hard_drop_enabled, additive_gravity, classic_lock
hard_drop_enabled, additive_gravity
)
if piece.locked then return end
local synchroes_allowed = ({not self.world, true, false})[config.gamesettings.synchroes_allowed]
@@ -288,13 +292,11 @@ function Ruleset:processPiece(
end
self:dropPiece(
inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
hard_drop_enabled, additive_gravity, classic_lock
hard_drop_enabled, additive_gravity
)
self:lockPiece(piece, grid, lock_delay, classic_lock)
self:lockPiece(piece, grid, lock_delay)
end
function Ruleset:canPieceMove(piece, grid) return true end
function Ruleset:canPieceRotate(piece, grid) return true end
function Ruleset:onPieceMove(piece) end
function Ruleset:onPieceRotate(piece) end
function Ruleset:onPieceDrop(piece) end

View File

@@ -1,105 +0,0 @@
local Piece = require 'tetris.components.piece'
local Ruleset = require 'tetris.rulesets.standard_exp'
local SRS = Ruleset:extend()
SRS.name = "Guideline SRS"
SRS.hash = "Standard"
SRS.softdrop_lock = false
SRS.harddrop_lock = true
SRS.MANIPULATIONS_MAX = 15
SRS.wallkicks_line = {
[0]={
[1]={{x=-2, y=0}, {x=1, y=0}, {x=-2, y=1}, {x=1, y=-2}},
[2]={{x=-1,y=0},{x=-2,y=0},{x=1,y=0},{x=2,y=0},{x=0,y=1}},
[3]={{x=-1, y=0}, {x=2, y=0}, {x=-1, y=-2}, {x=2, y=1}},
},
[1]={
[0]={{x=2, y=0}, {x=-1, y=0}, {x=2, y=-1}, {x=-1, y=2}},
[2]={{x=-1, y=0}, {x=2, y=0}, {x=-1, y=-2}, {x=2, y=1}},
[3]={{x=0,y=1},{x=0,y=2},{x=0,y=-1},{x=0,y=-2},{x=-1,y=0}},
},
[2]={
[0]={{x=1,y=0},{x=2,y=0},{x=-1,y=0},{x=-2,y=0},{x=0,y=-1}},
[1]={{x=1, y=0}, {x=-2, y=0}, {x=1, y=2}, {x=-2, y=-1}},
[3]={{x=2, y=0}, {x=-1, y=0}, {x=2, y=-1}, {x=-1, y=2}},
},
[3]={
[0]={{x=1, y=0}, {x=-2, y=0}, {x=1, y=2}, {x=-2, y=-1}},
[1]={{x=0,y=1},{x=0,y=2},{x=0,y=-1},{x=0,y=-2},{x=1,y=0}},
[2]={{x=-2, y=0}, {x=1, y=0}, {x=-2, y=1}, {x=1, y=-2}},
},
};
function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
local kicks
if piece.shape == "O" then
return
elseif piece.shape == "I" then
kicks = SRS.wallkicks_line[piece.rotation][new_piece.rotation]
else
kicks = SRS.wallkicks_3x3[piece.rotation][new_piece.rotation]
end
assert(piece.rotation ~= new_piece.rotation)
for idx, offset in pairs(kicks) do
kicked_piece = new_piece:withOffset(offset)
if grid:canPlacePiece(kicked_piece) then
piece:setRelativeRotation(rot_dir)
piece:setOffset(offset)
self:onPieceRotate(piece, grid)
return
end
end
end
function SRS:checkNewLow(piece)
for _, block in pairs(piece:getBlockOffsets()) do
local y = piece.position.y + block.y
if y > piece.lowest_y then
piece.manipulations = 0
piece.rotations = 0
piece.lowest_y = y
end
end
end
function SRS:onPieceDrop(piece, grid)
self:checkNewLow(piece)
if piece.manipulations >= self.MANIPULATIONS_MAX and piece:isDropBlocked(grid) then
piece.locked = true
else
piece.lock_delay = 0 -- step reset
end
end
function SRS:onPieceMove(piece, grid)
piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= SRS.MANIPULATIONS_MAX then
piece.locked = true
end
end
end
function SRS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- rotate reset
self:checkNewLow(piece)
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= self.MANIPULATIONS_MAX then
piece:moveInGrid({ x = 0, y = 1 }, 1, grid)
if piece:isDropBlocked(grid) then
piece.locked = true
end
end
end
function SRS:canPieceRotate() return true end
return SRS

View File

@@ -3,37 +3,46 @@ local Ruleset = require 'tetris.rulesets.arika_srs'
local SRS = Ruleset:extend()
SRS.name = "SRS-X"
SRS.hash = "StandardEXP"
SRS.world = true
SRS.colourscheme = {
I = "C",
L = "O",
J = "B",
S = "G",
Z = "R",
O = "Y",
T = "M",
}
SRS.softdrop_lock = true
SRS.harddrop_lock = false
SRS.name = "Guideline SRS"
SRS.hash = "Standard"
SRS.enable_IRS_wallkicks = true
SRS.MANIPULATIONS_MAX = 24
SRS.ROTATIONS_MAX = 12
SRS.MANIPULATIONS_MAX = 15
function SRS:checkNewLow(piece)
for _, block in pairs(piece:getBlockOffsets()) do
local y = piece.position.y + block.y
if y > piece.lowest_y then
--piece.manipulations = 0
--piece.rotations = 0
piece.manipulations = 0
piece.lowest_y = y
end
end
end
SRS.wallkicks_line = {
[0]={
[1]={{x=-2, y=0}, {x=1, y=0}, {x=-2, y=1}, {x=1, y=-2}},
[2]={},
[3]={{x=-1, y=0}, {x=2, y=0}, {x=-1, y=-2}, {x=2, y=1}},
},
[1]={
[0]={{x=2, y=0}, {x=-1, y=0}, {x=2, y=-1}, {x=-1, y=2}},
[2]={{x=-1, y=0}, {x=2, y=0}, {x=-1, y=-2}, {x=2, y=1}},
[3]={{x=0, y=1}, {x=0, y=-1}, {x=0, y=2}, {x=0, y=-2}},
},
[2]={
[0]={},
[1]={{x=1, y=0}, {x=-2, y=0}, {x=1, y=2}, {x=-2, y=-1}},
[3]={{x=2, y=0}, {x=-1, y=0}, {x=2, y=-1}, {x=-1, y=2}},
},
[3]={
[0]={{x=1, y=0}, {x=-2, y=0}, {x=1, y=2}, {x=-2, y=-1}},
[1]={{x=0, y=1}, {x=0, y=-1}, {x=0, y=2}, {x=0, y=-2}},
[2]={{x=-2, y=0}, {x=1, y=0}, {x=-2, y=1}, {x=1, y=-2}},
},
};
-- Component functions.
function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
@@ -54,7 +63,7 @@ function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
if grid:canPlacePiece(kicked_piece) then
piece:setRelativeRotation(rot_dir)
piece:setOffset(offset)
self:onPieceRotate(piece, grid, offset.y < 0)
self:onPieceRotate(piece, grid)
return
end
end
@@ -63,16 +72,12 @@ end
function SRS:onPieceCreate(piece, grid)
piece.manipulations = 0
piece.rotations = 0
piece.lowest_y = -math.huge
end
function SRS:onPieceDrop(piece, grid)
self:checkNewLow(piece)
if (
piece.manipulations >= self.MANIPULATIONS_MAX or
piece.rotations >= self.ROTATIONS_MAX
) and piece:isDropBlocked(grid) then
if piece.manipulations >= self.MANIPULATIONS_MAX and piece:isDropBlocked(grid) then
piece.locked = true
else
piece.lock_delay = 0 -- step reset
@@ -83,24 +88,22 @@ function SRS:onPieceMove(piece, grid)
piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= SRS.MANIPULATIONS_MAX then
if piece.manipulations >= self.MANIPULATIONS_MAX then
piece.locked = true
end
end
end
function SRS:onPieceRotate(piece, grid, upward)
function SRS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- rotate reset
if upward or piece:isDropBlocked(grid) then
piece.rotations = piece.rotations + 1
if piece.rotations >= self.ROTATIONS_MAX and piece:isDropBlocked(grid) then
piece.locked = true
end
end
end
function SRS:canPieceRotate(piece)
return piece.rotations < self.ROTATIONS_MAX
self:checkNewLow(piece)
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= self.MANIPULATIONS_MAX then
piece:moveInGrid({ x = 0, y = 1 }, 1, grid)
if piece:isDropBlocked(grid) then
piece.locked = true
end
end
end
function SRS:get180RotationValue() return 2 end

View File

@@ -4,7 +4,7 @@ local Ruleset = require 'tetris.rulesets.ruleset'
local SRS = Ruleset:extend()
SRS.name = "Ti-World"
SRS.hash = "StandardTI"
SRS.hash = "Bad I-kicks"
SRS.world = true
SRS.colourscheme = {
I = "C",
@@ -89,22 +89,22 @@ SRS.block_offsets = {
SRS.wallkicks_3x3 = {
[0]={
[1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}},
[2]={{x=1,y=0},{x=2,y=0},{x=1,y=1},{x=2,y=1},{x=-1,y=0},{x=-2,y=0},{x=-1,y=1},{x=-2,y=1},{x=0,y=-1},{x=3,y=0},{x=-3,y=0}},
[2]={{x=0, y=1}, {x=0, y=-1}},
[3]={{x=1, y=0}, {x=1, y=-1}, {x=0, y=2}, {x=1, y=2}},
},
[1]={
[0]={{x=1, y=0}, {x=1, y=1}, {x=0, y=-2}, {x=1, y=-2}},
[2]={{x=1, y=0}, {x=1, y=1}, {x=0, y=-2}, {x=1, y=-2}},
[3]={{x=0,y=1},{x=0,y=2},{x=-1,y=1},{x=-1,y=2},{x=0,y=-1},{x=0,y=-2},{x=-1,y=-1},{x=-1,y=-2},{x=1,y=0},{x=0,y=3},{x=0,y=-3}},
[3]={{x=0, y=1}, {x=0, y=-1}},
},
[2]={
[0]={{x=-1,y=0},{x=-2,y=0},{x=-1,y=-1},{x=-2,y=-1},{x=1,y=0},{x=2,y=0},{x=1,y=-1},{x=2,y=-1},{x=0,y=1},{x=-3,y=0},{x=3,y=0}},
[0]={{x=0, y=1}, {x=0, y=-1}},
[1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}},
[3]={{x=1, y=0}, {x=1, y=-1}, {x=0, y=2}, {x=1, y=2}},
},
[3]={
[0]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
[1]={{x=0,y=1},{x=0,y=2},{x=1,y=1},{x=1,y=2},{x=0,y=-1},{x=0,y=-2},{x=1,y=-1},{x=1,y=-2},{x=-1,y=0},{x=0,y=3},{x=0,y=-3}},
[1]={{x=0, y=1}, {x=0, y=-1}},
[2]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
},
};
@@ -112,22 +112,22 @@ SRS.wallkicks_3x3 = {
SRS.wallkicks_line = {
[0]={
[1]={{x=-2, y= 0}, {x= 1, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
[2]={{x=-1,y=0},{x=-2,y=0},{x=1,y=0},{x=2,y=0},{x=0,y=1}},
[2]={},
[3]={{x= 2, y= 0}, {x=-1, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
},
[1]={
[0]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 2}},
[2]={{x=-1, y= 0}, {x= 2, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
[3]={{x=0,y=1},{x=0,y=2},{x=0,y=-1},{x=0,y=-2},{x=-1,y=0}},
[3]={},
},
[2]={
[0]={{x=1,y=0},{x=2,y=0},{x=-1,y=0},{x=-2,y=0},{x=0,y=-1}},
[0]={},
[1]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 1}},
[3]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 1}},
},
[3]={
[0]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 2}},
[1]={{x=0,y=1},{x=0,y=2},{x=0,y=-1},{x=0,y=-2},{x=1,y=0}},
[1]={},
[2]={{x= 1, y= 0}, {x=-2, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
},
};
@@ -150,9 +150,9 @@ function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
for idx, offset in pairs(kicks) do
kicked_piece = new_piece:withOffset(offset)
if grid:canPlacePiece(kicked_piece) then
self:onPieceRotate(piece, grid)
piece:setRelativeRotation(rot_dir)
piece:setOffset(offset)
self:onPieceRotate(piece, grid, offset.y < 0)
return
end
end
@@ -182,20 +182,22 @@ function SRS:onPieceMove(piece, grid)
end
end
function SRS:onPieceRotate(piece, grid, upward)
function SRS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- rotate reset
if upward or piece:isDropBlocked(grid) then
if piece:isDropBlocked(grid) then
piece.rotations = piece.rotations + 1
if piece.rotations >= self.ROTATIONS_MAX and piece:isDropBlocked(grid) then
if piece.rotations >= self.ROTATIONS_MAX then
piece.locked = true
end
end
end
function SRS:canPieceRotate(piece)
return piece.rotations < self.ROTATIONS_MAX
function SRS:get180RotationValue()
if config.gamesettings.world_reverse == 1 then
return 1
else
return 3
end
end
function SRS:get180RotationValue() return 3 end
return SRS