Compare commits
130 Commits
v0.2.4
...
v0.3-beta2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb6962825f | ||
|
|
b5e7ce5be6 | ||
|
|
1ccd6a09d3 | ||
|
|
5a074f77cf | ||
|
|
81677221f1 | ||
|
|
a998be6f7b | ||
|
|
9c1c8eea21 | ||
|
|
f022c6c4b7 | ||
|
|
38f3d23b95 | ||
|
|
816d27db39 | ||
|
|
ce08ffd3da | ||
|
|
f0e84a8874 | ||
|
|
5e02471fb4 | ||
|
|
fa2fe77081 | ||
|
|
682c4a485a | ||
|
|
68760105cc | ||
|
|
e19da98ea1 | ||
|
|
e8904b92ed | ||
|
|
4f574e7716 | ||
|
|
f1528e8d71 | ||
|
|
79a25c3954 | ||
|
|
0f3883e18d | ||
|
|
1acd0ec65a | ||
|
|
b22f671409 | ||
|
|
0b6f62d50e | ||
|
|
086f327371 | ||
|
|
3c83ae0bf4 | ||
|
|
450833b246 | ||
|
|
8e7a5418dc | ||
|
|
6609b642dc | ||
|
|
452879ebab | ||
|
|
70a827b477 | ||
|
|
d281a732db | ||
|
|
01e91fbd93 | ||
|
|
ece853c9d3 | ||
|
|
ea8d008370 | ||
|
|
e20eb048c8 | ||
|
|
a33ca1af24 | ||
|
|
664bca2282 | ||
|
|
fc8fb8b66f | ||
|
|
fc58e6e908 | ||
|
|
061f6f5164 | ||
|
|
4e9cea7dda | ||
|
|
fa97216167 | ||
|
|
3f8d68cc9d | ||
|
|
6639d73c1c | ||
|
|
668f061077 | ||
|
|
cb70967b82 | ||
|
|
0c2ba5f0cc | ||
|
|
6d07a3b820 | ||
|
|
2de13a97f0 | ||
|
|
512c2149f0 | ||
|
|
6fb19220b7 | ||
|
|
08da67c434 | ||
|
|
2d63ca8ee1 | ||
|
|
0f09d47e60 | ||
|
|
9d44d1e771 | ||
|
|
5d022f9037 | ||
|
|
818743fe77 | ||
|
|
f22424d671 | ||
|
|
dd6baf1fe6 | ||
|
|
11cf5a9d55 | ||
|
|
5642ed1326 | ||
|
|
c0888c484f | ||
|
|
3ef3b193fd | ||
|
|
0c2e3efd1a | ||
|
|
5076adf022 | ||
|
|
1a75d983dc | ||
|
|
5b8e9586bd | ||
|
|
7d7dd8c3c2 | ||
|
|
29afdcecfc | ||
|
|
8b09833ae6 | ||
|
|
64047eaf9c | ||
|
|
125488b4d9 | ||
|
|
1fdd091456 | ||
|
|
ced40297cc | ||
|
|
32f2a0b3e7 | ||
|
|
dd5347ad8d | ||
|
|
b732ebb213 | ||
|
|
84634d6933 | ||
|
|
0d13a9f236 | ||
|
|
45120bc9f7 | ||
|
|
57c7d9c4c3 | ||
|
|
9f52d8bf10 | ||
|
|
57bd6a8286 | ||
|
|
1a68cd8fce | ||
|
|
56baf46839 | ||
|
|
305d07e10a | ||
|
|
8d954cabc2 | ||
|
|
0281220ea0 | ||
|
|
aef5d88d3f | ||
|
|
3676f7697c | ||
|
|
acb0eb1a71 | ||
|
|
a89bf05cab | ||
|
|
8008315994 | ||
|
|
90f62cb7dd | ||
|
|
eaee5fc7f0 | ||
|
|
e3b038b5a7 | ||
|
|
083693496e | ||
|
|
ba576dfc77 | ||
|
|
e195ccd721 | ||
|
|
70f703eb2f | ||
|
|
dc4d4a8259 | ||
|
|
565510c7b2 | ||
|
|
c26a3f37de | ||
|
|
0c1ce2f717 | ||
|
|
f14ab2a328 | ||
|
|
042dbd220b | ||
|
|
548612123a | ||
|
|
f4675da0b0 | ||
|
|
511e9592bc | ||
|
|
5f3990ff58 | ||
|
|
50ff4adf27 | ||
|
|
87b88f4b42 | ||
|
|
130c2ea403 | ||
|
|
1ea304916e | ||
|
|
e26b094830 | ||
|
|
bcb44725bf | ||
|
|
2990844c52 | ||
|
|
c343014d6f | ||
|
|
605add7e94 | ||
|
|
d3b647ca71 | ||
|
|
1101aa467d | ||
|
|
ce27a7ed18 | ||
|
|
f31beffab8 | ||
|
|
2ff8fb5edc | ||
|
|
1bf8f91ef2 | ||
|
|
ba5f78d5f1 | ||
|
|
f7c4908062 | ||
|
|
3aa5bae7be |
18
README.md
@@ -42,7 +42,7 @@ You do not need LÖVE on Windows, as it comes bundled with the program.
|
||||
|
||||
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, or want mod pack support, download [this](https://github.com/SashLilac/cambridge/archive/master.zip).
|
||||
If you want the bleeding edge version, download [this](https://github.com/SashLilac/cambridge/archive/master.zip).
|
||||
|
||||
Extract the ZIP, open a Command Prompt at the folder you extracted Cambridge to, then run this command:
|
||||
|
||||
@@ -60,6 +60,14 @@ Then, check the mod pack section at the bottom of this page.
|
||||
|
||||
If you haven't already, install `love` with your favourite package manager (Homebrew on macOS, your system's default on Linux). **Make sure you're using LÖVE 11, because it won't work with earlier versions!**
|
||||
|
||||
#### Downloading a release
|
||||
|
||||
You can download the .love file in the latest release, and run it with:
|
||||
|
||||
love cambridge.love
|
||||
|
||||
#### Installing from source
|
||||
|
||||
Clone the repository in git:
|
||||
|
||||
git clone https://github.com/SashLilac/cambridge
|
||||
@@ -74,9 +82,13 @@ It should run automatically!
|
||||
|
||||
## Installing modpacks
|
||||
|
||||
Simply drag your mode, ruleset, and randomizer Lua files into their respective directory, and they should appear automatically.
|
||||
Simply drag your mode, ruleset, and randomizer Lua files into their respective [directory](https://love2d.org/wiki/love.filesystem), and they should appear automatically.
|
||||
|
||||
Alternatively, install [this](https://github.com/SashLilac/cambridge-modpack) mod pack to get a taste of the mod potential.
|
||||
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
|
||||
-------
|
||||
|
||||
58
SOURCES.md
@@ -45,7 +45,7 @@ Other sounds from:
|
||||
Music
|
||||
-----
|
||||
|
||||
1. TGM3 credit roll music.
|
||||
1. Second Reality opening scene music (1993).
|
||||
2. The FitnessGram™ Pacer Test.
|
||||
|
||||
All background music is (currently) only unofficially included. In later releases they may be replaced with specifically licensed music as applicable.
|
||||
@@ -114,3 +114,59 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
simple-slider (https://love2d.org/forums/viewtopic.php?t=80711)
|
||||
--------------------
|
||||
|
||||
Copyright (c) 2016 George Prosser
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
bigint.lua (https://github.com/empyreuma/bigint.lua)
|
||||
--------------------
|
||||
|
||||
3-Clause BSD License
|
||||
|
||||
Copyright (c) Emily "empyreuma" 2016
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the <organization> nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -3,19 +3,11 @@ 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
|
||||
--------
|
||||
@@ -28,8 +20,6 @@ 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
|
||||
@@ -43,14 +33,7 @@ From other games:
|
||||
* **SURVIVAL A1**: 20G mode from Tetris the Grand Master.
|
||||
* **SURVIVAL A2**: T.A. Death.
|
||||
* **SURVIVAL A3**: Ti Shirase.
|
||||
|
||||
|
||||
RACE
|
||||
----
|
||||
|
||||
Modes with no levels, just a single timed goal.
|
||||
|
||||
* **Race 40**: How fast can you clear 40 lines? No limits, no holds barred.
|
||||
* **SURVIVAL AX**: Another mode from TGM Ace.
|
||||
|
||||
|
||||
PHANTOM MANIA
|
||||
@@ -69,8 +52,4 @@ OTHER MODES
|
||||
|
||||
* **Strategy**: How well can you plan ahead your movements? Can you handle only having a short time to place each piece?
|
||||
|
||||
* **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?
|
||||
* **Big A2**: Marathon A2 but all the pieces are BIG!
|
||||
29
funcs.lua
@@ -56,9 +56,19 @@ end
|
||||
|
||||
function formatBigNum(number)
|
||||
-- returns a string representing a number with commas as thousands separator (e.g. 12,345,678)
|
||||
local s = string.format("%d", number)
|
||||
local pos = string.len(s) % 3
|
||||
if pos == 0 then pos = 3 end
|
||||
local s
|
||||
if type(number) == "number" then
|
||||
s = string.format("%d", number)
|
||||
elseif type(number) == "string" then
|
||||
if not tonumber(number) then
|
||||
return
|
||||
else
|
||||
s = number
|
||||
end
|
||||
else
|
||||
return
|
||||
end
|
||||
local pos = Mod1(string.len(s), 3)
|
||||
return string.sub(s, 1, pos)
|
||||
.. string.gsub(string.sub(s, pos+1), "(...)", ",%1")
|
||||
end
|
||||
@@ -66,4 +76,17 @@ end
|
||||
function Mod1(n, m)
|
||||
-- returns a number congruent to n modulo m in the range [1;m] (as opposed to [0;m-1])
|
||||
return ((n-1) % m) + 1
|
||||
end
|
||||
|
||||
function table.contains(table, element)
|
||||
for _, value in pairs(table) do
|
||||
if value == element then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function clamp(a, b, c)
|
||||
return math.min(a, math.max(b, c))
|
||||
end
|
||||
555
libs/bigint/bigint.lua
Normal file
@@ -0,0 +1,555 @@
|
||||
#!/usr/bin/env lua
|
||||
-- If this variable is true, then strict type checking is performed for all
|
||||
-- operations. This may result in slower code, but it will allow you to catch
|
||||
-- errors and bugs earlier.
|
||||
local strict = true
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local bigint = {}
|
||||
|
||||
local named_powers = require("libs.bigint.named-powers-of-ten")
|
||||
|
||||
-- Create a new bigint or convert a number or string into a big
|
||||
-- Returns an empty, positive bigint if no number or string is given
|
||||
function bigint.new(num)
|
||||
local self = {
|
||||
sign = "+",
|
||||
digits = {}
|
||||
}
|
||||
|
||||
-- Return a new bigint with the same sign and digits
|
||||
function self:clone()
|
||||
local newint = bigint.new()
|
||||
newint.sign = self.sign
|
||||
for _, digit in pairs(self.digits) do
|
||||
newint.digits[#newint.digits + 1] = digit
|
||||
end
|
||||
return newint
|
||||
end
|
||||
|
||||
setmetatable(self, {
|
||||
__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
|
||||
})
|
||||
|
||||
if (num) then
|
||||
local num_string = tostring(num)
|
||||
for digit in string.gmatch(num_string, "[0-9]") do
|
||||
table.insert(self.digits, tonumber(digit))
|
||||
end
|
||||
if string.sub(num_string, 1, 1) == "-" then
|
||||
self.sign = "-"
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- Check the type of a big
|
||||
-- Normally only runs when global variable "strict" == true, but checking can be
|
||||
-- forced by supplying "true" as the second argument.
|
||||
function bigint.check(big, force)
|
||||
if (strict or force) then
|
||||
assert(#big.digits > 0, "bigint is empty")
|
||||
assert(type(big.sign) == "string", "bigint is unsigned")
|
||||
for _, digit in pairs(big.digits) do
|
||||
assert(type(digit) == "number", digit .. " is not a number")
|
||||
assert(digit < 10, digit .. " is greater than or equal to 10")
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Return a new big with the same digits but with a positive sign (absolute
|
||||
-- value)
|
||||
function bigint.abs(big)
|
||||
bigint.check(big)
|
||||
local result = big:clone()
|
||||
result.sign = "+"
|
||||
return result
|
||||
end
|
||||
|
||||
-- Convert a big to a number or string
|
||||
function bigint.unserialize(big, output_type, precision)
|
||||
bigint.check(big)
|
||||
|
||||
local num = ""
|
||||
if big.sign == "-" then
|
||||
num = "-"
|
||||
end
|
||||
|
||||
|
||||
if ((output_type == nil)
|
||||
or (output_type == "number")
|
||||
or (output_type == "n")
|
||||
or (output_type == "string")
|
||||
or (output_type == "s")) then
|
||||
-- Unserialization to a string or number requires reconstructing the
|
||||
-- entire number
|
||||
|
||||
for _, digit in pairs(big.digits) do
|
||||
num = num .. math.floor(digit) -- lazy way of getting rid of .0$
|
||||
end
|
||||
|
||||
if ((output_type == nil)
|
||||
or (output_type == "number")
|
||||
or (output_type == "n")) then
|
||||
return tonumber(num)
|
||||
else
|
||||
return num
|
||||
end
|
||||
|
||||
else
|
||||
-- Unserialization to human-readable form or scientific notation only
|
||||
-- requires reading the first few digits
|
||||
if (precision == nil) then
|
||||
precision = 3
|
||||
else
|
||||
assert(precision > 0, "Precision cannot be less than 1")
|
||||
assert(math.floor(precision) == precision,
|
||||
"Precision must be a positive integer")
|
||||
end
|
||||
|
||||
-- num is the first (precision + 1) digits, the first being separated by
|
||||
-- a decimal point from the others
|
||||
num = num .. big.digits[1]
|
||||
if (precision > 1) then
|
||||
num = num .. "."
|
||||
for i = 1, (precision - 1) do
|
||||
num = num .. big.digits[i + 1]
|
||||
end
|
||||
end
|
||||
|
||||
if ((output_type == "human-readable")
|
||||
or (output_type == "human")
|
||||
or (output_type == "h")) then
|
||||
-- Human-readable output contributed by 123eee555
|
||||
|
||||
local name
|
||||
local walkback = 0 -- Used to enumerate "ten", "hundred", etc
|
||||
|
||||
-- Walk backwards in the index of named_powers starting at the
|
||||
-- number of digits of the input until the first value is found
|
||||
for i = (#big.digits - 1), (#big.digits - 4), -1 do
|
||||
name = named_powers[i]
|
||||
if (name) then
|
||||
if (walkback == 1) then
|
||||
name = "ten " .. name
|
||||
elseif (walkback == 2) then
|
||||
name = "hundred " .. name
|
||||
end
|
||||
break
|
||||
else
|
||||
walkback = walkback + 1
|
||||
end
|
||||
end
|
||||
|
||||
return num .. " " .. name
|
||||
|
||||
else
|
||||
return num .. "*10^" .. (#big.digits - 1)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- Basic comparisons
|
||||
-- Accepts symbols (<, >=, ~=) and Unix shell-like options (lt, ge, ne)
|
||||
function bigint.compare(big1, big2, comparison)
|
||||
bigint.check(big1)
|
||||
bigint.check(big2)
|
||||
|
||||
local greater = false -- If big1.digits > big2.digits
|
||||
local equal = false
|
||||
|
||||
if (big1.sign == "-") and (big2.sign == "+") then
|
||||
greater = false
|
||||
elseif (#big1.digits > #big2.digits)
|
||||
or ((big1.sign == "+") and (big2.sign == "-")) then
|
||||
greater = true
|
||||
elseif (#big1.digits == #big2.digits) then
|
||||
-- Walk left to right, comparing digits
|
||||
for digit = 1, #big1.digits do
|
||||
if (big1.digits[digit] > big2.digits[digit]) then
|
||||
greater = true
|
||||
break
|
||||
elseif (big2.digits[digit] > big1.digits[digit]) then
|
||||
break
|
||||
elseif (digit == #big1.digits)
|
||||
and (big1.digits[digit] == big2.digits[digit]) then
|
||||
equal = true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- If both numbers are negative, then the requirements for greater are
|
||||
-- reversed
|
||||
if (not equal) and (big1.sign == "-") and (big2.sign == "-") then
|
||||
greater = not greater
|
||||
end
|
||||
|
||||
return (((comparison == "<") or (comparison == "lt"))
|
||||
and ((not greater) and (not equal)) and true)
|
||||
or (((comparison == ">") or (comparison == "gt"))
|
||||
and ((greater) and (not equal)) and true)
|
||||
or (((comparison == "==") or (comparison == "eq"))
|
||||
and (equal) and true)
|
||||
or (((comparison == ">=") or (comparison == "ge"))
|
||||
and (equal or greater) and true)
|
||||
or (((comparison == "<=") or (comparison == "le"))
|
||||
and (equal or not greater) and true)
|
||||
or (((comparison == "~=") or (comparison == "!=") or (comparison == "ne"))
|
||||
and (not equal) and true)
|
||||
or false
|
||||
end
|
||||
|
||||
-- BACKEND: Add big1 and big2, ignoring signs
|
||||
function bigint.add_raw(big1, big2)
|
||||
bigint.check(big1)
|
||||
bigint.check(big2)
|
||||
|
||||
local result = bigint.new()
|
||||
local max_digits = 0
|
||||
local carry = 0
|
||||
|
||||
if (#big1.digits >= #big2.digits) then
|
||||
max_digits = #big1.digits
|
||||
else
|
||||
max_digits = #big2.digits
|
||||
end
|
||||
|
||||
-- Walk backwards right to left, like in long addition
|
||||
for digit = 0, max_digits - 1 do
|
||||
local sum = (big1.digits[#big1.digits - digit] or 0)
|
||||
+ (big2.digits[#big2.digits - digit] or 0)
|
||||
+ carry
|
||||
|
||||
if (sum >= 10) then
|
||||
carry = 1
|
||||
sum = sum - 10
|
||||
else
|
||||
carry = 0
|
||||
end
|
||||
|
||||
result.digits[max_digits - digit] = sum
|
||||
end
|
||||
|
||||
-- Leftover carry in cases when #big1.digits == #big2.digits and sum > 10, ex. 7 + 9
|
||||
if (carry == 1) then
|
||||
table.insert(result.digits, 1, 1)
|
||||
end
|
||||
|
||||
return result
|
||||
|
||||
end
|
||||
|
||||
-- BACKEND: Subtract big2 from big1, ignoring signs
|
||||
function bigint.subtract_raw(big1, big2)
|
||||
-- Type checking is done by bigint.compare
|
||||
assert(bigint.compare(bigint.abs(big1), bigint.abs(big2), ">="),
|
||||
"Size of " .. bigint.unserialize(big1, "string") .. " is less than "
|
||||
.. bigint.unserialize(big2, "string"))
|
||||
|
||||
local result = big1:clone()
|
||||
local max_digits = #big1.digits
|
||||
local borrow = 0
|
||||
|
||||
-- Logic mostly copied from bigint.add_raw ---------------------------------
|
||||
-- Walk backwards right to left, like in long subtraction
|
||||
for digit = 0, max_digits - 1 do
|
||||
local diff = (big1.digits[#big1.digits - digit] or 0)
|
||||
- (big2.digits[#big2.digits - digit] or 0)
|
||||
- borrow
|
||||
|
||||
if (diff < 0) then
|
||||
borrow = 1
|
||||
diff = diff + 10
|
||||
else
|
||||
borrow = 0
|
||||
end
|
||||
|
||||
result.digits[max_digits - digit] = diff
|
||||
end
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
|
||||
-- Strip leading zeroes if any, but not if 0 is the only digit
|
||||
while (#result.digits > 1) and (result.digits[1] == 0) do
|
||||
table.remove(result.digits, 1)
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-- FRONTEND: Addition and subtraction operations, accounting for signs
|
||||
function bigint.add(big1, big2)
|
||||
-- Type checking is done by bigint.compare
|
||||
|
||||
local result
|
||||
|
||||
-- If adding numbers of different sign, subtract the smaller sized one from
|
||||
-- the bigger sized one and take the sign of the bigger sized one
|
||||
if (big1.sign ~= big2.sign) then
|
||||
if (bigint.compare(bigint.abs(big1), bigint.abs(big2), ">")) then
|
||||
result = bigint.subtract_raw(big1, big2)
|
||||
result.sign = big1.sign
|
||||
else
|
||||
result = bigint.subtract_raw(big2, big1)
|
||||
result.sign = big2.sign
|
||||
end
|
||||
|
||||
elseif (big1.sign == "+") and (big2.sign == "+") then
|
||||
result = bigint.add_raw(big1, big2)
|
||||
|
||||
elseif (big1.sign == "-") and (big2.sign == "-") then
|
||||
result = bigint.add_raw(big1, big2)
|
||||
result.sign = "-"
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
function bigint.subtract(big1, big2)
|
||||
-- Type checking is done by bigint.compare in bigint.add
|
||||
-- Subtracting is like adding a negative
|
||||
local big2_local = big2:clone()
|
||||
if (big2.sign == "+") then
|
||||
big2_local.sign = "-"
|
||||
else
|
||||
big2_local.sign = "+"
|
||||
end
|
||||
return bigint.add(big1, big2_local)
|
||||
end
|
||||
|
||||
-- BACKEND: Multiply a big by a single digit big, ignoring signs
|
||||
function bigint.multiply_single(big1, big2)
|
||||
bigint.check(big1)
|
||||
bigint.check(big2)
|
||||
assert(#big2.digits == 1, bigint.unserialize(big2, "string")
|
||||
.. " has more than one digit")
|
||||
|
||||
local result = bigint.new()
|
||||
local carry = 0
|
||||
|
||||
-- Logic mostly copied from bigint.add_raw ---------------------------------
|
||||
-- Walk backwards right to left, like in long multiplication
|
||||
for digit = 0, #big1.digits - 1 do
|
||||
local this_digit = big1.digits[#big1.digits - digit]
|
||||
* big2.digits[1]
|
||||
+ carry
|
||||
|
||||
if (this_digit >= 10) then
|
||||
carry = math.floor(this_digit / 10)
|
||||
this_digit = this_digit - (carry * 10)
|
||||
else
|
||||
carry = 0
|
||||
end
|
||||
|
||||
result.digits[#big1.digits - digit] = this_digit
|
||||
end
|
||||
|
||||
-- Leftover carry in cases when big1.digits[1] * big2.digits[1] > 0
|
||||
if (carry > 0) then
|
||||
table.insert(result.digits, 1, carry)
|
||||
end
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-- FRONTEND: Multiply two bigs, accounting for signs
|
||||
function bigint.multiply(big1, big2)
|
||||
-- Type checking done by bigint.multiply_single
|
||||
|
||||
local result = bigint.new(0)
|
||||
local larger, smaller -- Larger and smaller in terms of digits, not size
|
||||
|
||||
if (bigint.unserialize(big1) == 0) or (bigint.unserialize(big2) == 0) then
|
||||
return result
|
||||
end
|
||||
|
||||
if (#big1.digits >= #big2.digits) then
|
||||
larger = big1
|
||||
smaller = big2
|
||||
else
|
||||
larger = big2
|
||||
smaller = big1
|
||||
end
|
||||
|
||||
-- Walk backwards right to left, like in long multiplication
|
||||
for digit = 0, #smaller.digits - 1 do
|
||||
-- Sorry for going over column 80! There's lots of big names here
|
||||
local this_digit_product = bigint.multiply_single(larger,
|
||||
bigint.new(smaller.digits[#smaller.digits - digit]))
|
||||
|
||||
-- "Placeholding zeroes"
|
||||
if (digit > 0) then
|
||||
for placeholder = 1, digit do
|
||||
table.insert(this_digit_product.digits, 0)
|
||||
end
|
||||
end
|
||||
|
||||
result = bigint.add(result, this_digit_product)
|
||||
end
|
||||
|
||||
if (larger.sign == smaller.sign) then
|
||||
result.sign = "+"
|
||||
else
|
||||
result.sign = "-"
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
-- Raise a big to a positive integer or big power (TODO: negative integer power)
|
||||
function bigint.exponentiate(big, power)
|
||||
-- Type checking for big done by bigint.multiply
|
||||
assert(bigint.compare(power, bigint.new(0), ">="),
|
||||
" negative powers are not supported")
|
||||
local exp = power:clone()
|
||||
|
||||
if (bigint.compare(exp, bigint.new(0), "==")) then
|
||||
return bigint.new(1)
|
||||
elseif (bigint.compare(exp, bigint.new(1), "==")) then
|
||||
return big
|
||||
else
|
||||
local result = big:clone()
|
||||
|
||||
while (bigint.compare(exp, bigint.new(1), ">")) do
|
||||
result = bigint.multiply(result, big)
|
||||
exp = bigint.subtract(exp, bigint.new(1))
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- BACKEND: Divide two bigs (decimals not supported), returning big result and
|
||||
-- big remainder
|
||||
-- WARNING: Only supports positive integers
|
||||
function bigint.divide_raw(big1, big2)
|
||||
-- Type checking done by bigint.compare
|
||||
if (bigint.compare(big1, big2, "==")) then
|
||||
return bigint.new(1), bigint.new(0)
|
||||
elseif (bigint.compare(big1, big2, "<")) then
|
||||
return bigint.new(0), bigint.new(0)
|
||||
else
|
||||
assert(bigint.compare(big2, bigint.new(0), "!="), "error: divide by zero")
|
||||
assert(big1.sign == "+", "error: big1 is not positive")
|
||||
assert(big2.sign == "+", "error: big2 is not positive")
|
||||
|
||||
local result = bigint.new()
|
||||
|
||||
local dividend = bigint.new() -- Dividend of a single operation, not the
|
||||
-- dividend of the overall function
|
||||
local divisor = big2:clone()
|
||||
local factor = 1
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
|
||||
end
|
||||
|
||||
-- The remainder of the final loop is returned as the function's
|
||||
-- overall remainder
|
||||
return result, dividend
|
||||
end
|
||||
end
|
||||
|
||||
-- FRONTEND: Divide two bigs (decimals not supported), returning big result and
|
||||
-- big remainder, accounting for signs
|
||||
function bigint.divide(big1, big2)
|
||||
local result, remainder = bigint.divide_raw(bigint.abs(big1),
|
||||
bigint.abs(big2))
|
||||
if (big1.sign == big2.sign) then
|
||||
result.sign = "+"
|
||||
else
|
||||
result.sign = "-"
|
||||
end
|
||||
|
||||
return result, remainder
|
||||
end
|
||||
|
||||
-- FRONTEND: Return only the remainder from bigint.divide
|
||||
function bigint.modulus(big1, big2)
|
||||
local result, remainder = bigint.divide(big1, big2)
|
||||
|
||||
-- Remainder will always have the same sign as the dividend per C standard
|
||||
-- https://en.wikipedia.org/wiki/Modulo_operation#Remainder_calculation_for_the_modulo_operation
|
||||
remainder.sign = big1.sign
|
||||
return remainder
|
||||
end
|
||||
|
||||
return bigint
|
||||
3340
libs/bigint/named-powers-of-ten.lua
Normal file
138
libs/simple-slider.lua
Normal file
@@ -0,0 +1,138 @@
|
||||
--[[
|
||||
Copyright (c) 2016 George Prosser
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
]]
|
||||
|
||||
local slider = {}
|
||||
slider.__index = slider
|
||||
|
||||
function newSlider(x, y, length, value, min, max, setter, style)
|
||||
local s = {}
|
||||
s.value = (value - min) / (max - min)
|
||||
s.min = min
|
||||
s.max = max
|
||||
s.setter = setter
|
||||
s.x = x
|
||||
s.y = y
|
||||
s.length = length
|
||||
|
||||
local p = style or {}
|
||||
s.width = p.width or length * 0.1
|
||||
s.orientation = p.orientation or 'horizontal'
|
||||
s.track = p.track or 'rectangle'
|
||||
s.knob = p.knob or 'rectangle'
|
||||
|
||||
s.grabbed = false
|
||||
s.wasDown = true
|
||||
s.ox = 0
|
||||
s.oy = 0
|
||||
|
||||
return setmetatable(s, slider)
|
||||
end
|
||||
|
||||
function slider:update(mouseX, mouseY, mouseDown)
|
||||
local x = mouseX or love.mouse.getX()
|
||||
local y = mouseY or love.mouse.getY()
|
||||
local down = love.mouse.isDown(1)
|
||||
if mouseDown ~= nil then
|
||||
down = mouseDown
|
||||
end
|
||||
|
||||
local knobX = self.x
|
||||
local knobY = self.y
|
||||
if self.orientation == 'horizontal' then
|
||||
knobX = self.x - self.length/2 + self.length * self.value
|
||||
elseif self.orientation == 'vertical' then
|
||||
knobY = self.y + self.length/2 - self.length * self.value
|
||||
end
|
||||
|
||||
local ox = x - knobX
|
||||
local oy = y - knobY
|
||||
|
||||
local dx = ox - self.ox
|
||||
local dy = oy - self.oy
|
||||
|
||||
if down then
|
||||
if self.grabbed then
|
||||
if self.orientation == 'horizontal' then
|
||||
self.value = self.value + dx / self.length
|
||||
elseif self.orientation == 'vertical' then
|
||||
self.value = self.value - dy / self.length
|
||||
end
|
||||
elseif (x > knobX - self.width/2 and x < knobX + self.width/2 and y > knobY - self.width/2 and y < knobY + self.width/2) and not self.wasDown then
|
||||
self.ox = ox
|
||||
self.oy = oy
|
||||
self.grabbed = true
|
||||
end
|
||||
else
|
||||
self.grabbed = false
|
||||
end
|
||||
|
||||
self.value = math.max(0, math.min(1, self.value))
|
||||
|
||||
if self.setter ~= nil then
|
||||
self.setter(self.min + self.value * (self.max - self.min))
|
||||
end
|
||||
|
||||
self.wasDown = down
|
||||
end
|
||||
|
||||
function slider:draw()
|
||||
if self.track == 'rectangle' then
|
||||
if self.orientation == 'horizontal' then
|
||||
love.graphics.rectangle('line', self.x - self.length/2 - self.width/2, self.y - self.width/2, self.length + self.width, self.width)
|
||||
elseif self.orientation == 'vertical' then
|
||||
love.graphics.rectangle('line', self.x - self.width/2, self.y - self.length/2 - self.width/2, self.width, self.length + self.width)
|
||||
end
|
||||
elseif self.track == 'line' then
|
||||
if self.orientation == 'horizontal' then
|
||||
love.graphics.line(self.x - self.length/2, self.y, self.x + self.length/2, self.y)
|
||||
elseif self.orientation == 'vertical' then
|
||||
love.graphics.line(self.x, self.y - self.length/2, self.x, self.y + self.length/2)
|
||||
end
|
||||
elseif self.track == 'roundrect' then
|
||||
if self.orientation == 'horizontal' then
|
||||
love.graphics.rectangle('line', self.x - self.length/2 - self.width/2, self.y - self.width/2, self.length + self.width, self.width, self.width/2, self.width)
|
||||
elseif self.orientation == 'vertical' then
|
||||
love.graphics.rectangle('line', self.x - self.width/2, self.y - self.length/2 - self.width/2, self.width, self.length + self.width, self.width, self.width/2)
|
||||
end
|
||||
end
|
||||
|
||||
local knobX = self.x
|
||||
local knobY = self.y
|
||||
if self.orientation == 'horizontal' then
|
||||
knobX = self.x - self.length/2 + self.length * self.value
|
||||
elseif self.orientation == 'vertical' then
|
||||
knobY = self.y + self.length/2 - self.length * self.value
|
||||
end
|
||||
|
||||
if self.knob == 'rectangle' then
|
||||
love.graphics.rectangle('fill', knobX - self.width/2, knobY - self.width/2, self.width, self.width)
|
||||
elseif self.knob == 'circle' then
|
||||
love.graphics.circle('fill', knobX, knobY, self.width/2)
|
||||
end
|
||||
end
|
||||
|
||||
function slider:getValue()
|
||||
return self.min + self.value * (self.max - self.min)
|
||||
end
|
||||
@@ -6,7 +6,7 @@ bgm = {
|
||||
}
|
||||
|
||||
local current_bgm = nil
|
||||
local bgm_locked = true
|
||||
local bgm_locked = false
|
||||
|
||||
function switchBGM(sound, subsound)
|
||||
if bgm_locked then return end
|
||||
@@ -47,7 +47,7 @@ function fadeoutBGM(time)
|
||||
end
|
||||
|
||||
function resetBGMFadeout(time)
|
||||
current_bgm:setVolume(1)
|
||||
current_bgm:setVolume(config.bgm_volume)
|
||||
fading_bgm = false
|
||||
current_bgm:play()
|
||||
end
|
||||
@@ -59,7 +59,7 @@ function processBGMFadeout(dt)
|
||||
fadeout_time = 0
|
||||
fading_bgm = false
|
||||
end
|
||||
current_bgm:setVolume(fadeout_time / total_fadeout_time)
|
||||
current_bgm:setVolume(fadeout_time * config.bgm_volume / total_fadeout_time)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
2
load/bigint.lua
Normal file
@@ -0,0 +1,2 @@
|
||||
bigint = require "libs.bigint.bigint"
|
||||
number_names = require "libs.bigint.named-powers-of-ten"
|
||||
@@ -20,6 +20,7 @@ backgrounds = {
|
||||
love.graphics.newImage("res/backgrounds/1800.png"),
|
||||
love.graphics.newImage("res/backgrounds/1900.png"),
|
||||
title = love.graphics.newImage("res/backgrounds/title.png"),
|
||||
snow = love.graphics.newImage("res/backgrounds/snow.png"),
|
||||
input_config = love.graphics.newImage("res/backgrounds/options-input.png"),
|
||||
game_config = love.graphics.newImage("res/backgrounds/options-game.png"),
|
||||
}
|
||||
@@ -48,6 +49,23 @@ blocks = {
|
||||
F = love.graphics.newImage("res/img/bone.png"),
|
||||
A = love.graphics.newImage("res/img/bone.png"),
|
||||
X = love.graphics.newImage("res/img/bone.png"),
|
||||
},
|
||||
["gem"] = {
|
||||
R = love.graphics.newImage("res/img/gem1.png"),
|
||||
O = love.graphics.newImage("res/img/gem3.png"),
|
||||
Y = love.graphics.newImage("res/img/gem7.png"),
|
||||
G = love.graphics.newImage("res/img/gem6.png"),
|
||||
C = love.graphics.newImage("res/img/gem2.png"),
|
||||
B = love.graphics.newImage("res/img/gem4.png"),
|
||||
M = love.graphics.newImage("res/img/gem5.png"),
|
||||
F = love.graphics.newImage("res/img/gem9.png"),
|
||||
A = love.graphics.newImage("res/img/gem9.png"),
|
||||
X = love.graphics.newImage("res/img/gem9.png"),
|
||||
},
|
||||
["square"] = {
|
||||
F = love.graphics.newImage("res/img/squares.png"),
|
||||
Y = love.graphics.newImage("res/img/squareg.png"),
|
||||
X = love.graphics.newImage("res/img/squares.png"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,4 +102,5 @@ misc_graphics = {
|
||||
go = love.graphics.newImage("res/img/go.png"),
|
||||
select_mode = love.graphics.newImage("res/img/select_mode.png"),
|
||||
strike = love.graphics.newImage("res/img/strike.png"),
|
||||
santa = love.graphics.newImage("res/img/santa.png")
|
||||
}
|
||||
@@ -22,17 +22,19 @@ sounds = {
|
||||
go = love.audio.newSource("res/se/go.wav", "static"),
|
||||
irs = love.audio.newSource("res/se/irs.wav", "static"),
|
||||
ihs = love.audio.newSource("res/se/ihs.wav", "static"),
|
||||
-- a secret sound!
|
||||
welcome = love.audio.newSource("res/se/welcomeToCambridge.wav", "static"),
|
||||
}
|
||||
|
||||
function playSE(sound, subsound)
|
||||
if subsound == nil then
|
||||
sounds[sound]:setVolume(0.5)
|
||||
sounds[sound]:setVolume(config.sfx_volume)
|
||||
if sounds[sound]:isPlaying() then
|
||||
sounds[sound]:stop()
|
||||
end
|
||||
sounds[sound]:play()
|
||||
else
|
||||
sounds[sound][subsound]:setVolume(0.6)
|
||||
sounds[sound][subsound]:setVolume(config.sfx_volume)
|
||||
if sounds[sound][subsound]:isPlaying() then
|
||||
sounds[sound][subsound]:stop()
|
||||
end
|
||||
@@ -42,13 +44,13 @@ end
|
||||
|
||||
function playSEOnce(sound, subsound)
|
||||
if subsound == nil then
|
||||
sounds[sound]:setVolume(0.5)
|
||||
sounds[sound]:setVolume(config.sfx_volume)
|
||||
if sounds[sound]:isPlaying() then
|
||||
return
|
||||
end
|
||||
sounds[sound]:play()
|
||||
else
|
||||
sounds[sound][subsound]:setVolume(0.5)
|
||||
sounds[sound][subsound]:setVolume(config.sfx_volume)
|
||||
if sounds[sound][subsound]:isPlaying() then
|
||||
return
|
||||
end
|
||||
|
||||
52
main.lua
@@ -7,13 +7,27 @@ function love.load()
|
||||
require "load.sounds"
|
||||
require "load.bgm"
|
||||
require "load.save"
|
||||
require "load.bigint"
|
||||
loadSave()
|
||||
require "scene"
|
||||
config["side_next"] = false
|
||||
config["reverse_rotate"] = true
|
||||
--config["side_next"] = false
|
||||
--config["reverse_rotate"] = true
|
||||
config["fullscreen"] = false
|
||||
|
||||
love.window.setMode(love.graphics.getWidth(), love.graphics.getHeight(), {resizable = true});
|
||||
|
||||
-- used for screenshots
|
||||
GLOBAL_CANVAS = love.graphics.newCanvas()
|
||||
|
||||
-- init config
|
||||
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
|
||||
|
||||
if not config.gamesettings then
|
||||
config.gamesettings = {}
|
||||
@@ -97,7 +111,10 @@ function love.update(dt)
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
love.graphics.push()
|
||||
love.graphics.setCanvas(GLOBAL_CANVAS)
|
||||
love.graphics.clear()
|
||||
|
||||
love.graphics.push()
|
||||
|
||||
-- get offset matrix
|
||||
love.graphics.setDefaultFilter("linear", "nearest")
|
||||
@@ -109,9 +126,13 @@ 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)
|
||||
@@ -121,6 +142,22 @@ function love.keypressed(key, scancode)
|
||||
love.window.setFullscreen(config["fullscreen"])
|
||||
elseif scancode == "f2" and scene.title ~= "Input Config" and scene.title ~= "Game" then
|
||||
scene = InputConfigScene()
|
||||
switchBGM(nil)
|
||||
-- secret sound playing :eyes:
|
||||
elseif scancode == "f8" and scene.title == "Title" then
|
||||
config.secret = not config.secret
|
||||
saveConfig()
|
||||
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")
|
||||
if not love.filesystem.getInfo("ss") then
|
||||
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
|
||||
@@ -236,9 +273,14 @@ function love.joystickhat(joystick, hat, direction)
|
||||
end
|
||||
|
||||
function love.focus(f)
|
||||
if f then
|
||||
if f and (scene.title ~= "Game" or not scene.paused) then
|
||||
resumeBGM()
|
||||
else
|
||||
pauseBGM()
|
||||
end
|
||||
end
|
||||
|
||||
function love.resize(w, h)
|
||||
GLOBAL_CANVAS:release()
|
||||
GLOBAL_CANVAS = love.graphics.newCanvas(w, h)
|
||||
end
|
||||
@@ -1,2 +1,2 @@
|
||||
tar -a -c -f cambridge.zip libs/binser.lua libs/classic.lua libs/discordRPC.lua load res scene tetris conf.lua main.lua scene.lua funcs.lua
|
||||
tar -a -c -f cambridge.zip libs load res scene tetris conf.lua main.lua scene.lua funcs.lua
|
||||
rename cambridge.zip cambridge.love
|
||||
4
release
@@ -4,6 +4,6 @@ mkdir dist/windows
|
||||
mkdir dist/win32
|
||||
cp cambridge.love dist/
|
||||
cat dist/windows/love.exe cambridge.love > dist/windows/cambridge.exe
|
||||
zip dist/cambridge-windows.zip dist/windows/* SOURCES.md LICENSE
|
||||
zip dist/cambridge-windows.zip dist/windows/* SOURCES.md LICENSE.md
|
||||
cat dist/win32/love.exe cambridge.love > dist/win32/cambridge.exe
|
||||
zip dist/cambridge-win32.zip dist/win32/* SOURCES.md LICENSE
|
||||
zip dist/cambridge-win32.zip dist/win32/* SOURCES.md LICENSE.md
|
||||
|
||||
BIN
res/backgrounds/snow.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.4 KiB |
BIN
res/img/gem1.png
Normal file
|
After Width: | Height: | Size: 462 B |
BIN
res/img/gem2.png
Normal file
|
After Width: | Height: | Size: 388 B |
BIN
res/img/gem3.png
Normal file
|
After Width: | Height: | Size: 445 B |
BIN
res/img/gem4.png
Normal file
|
After Width: | Height: | Size: 426 B |
BIN
res/img/gem5.png
Normal file
|
After Width: | Height: | Size: 376 B |
BIN
res/img/gem6.png
Normal file
|
After Width: | Height: | Size: 377 B |
BIN
res/img/gem7.png
Normal file
|
After Width: | Height: | Size: 399 B |
BIN
res/img/gem9.png
Normal file
|
After Width: | Height: | Size: 354 B |
BIN
res/img/santa.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
res/img/squareg.png
Normal file
|
After Width: | Height: | Size: 708 B |
BIN
res/img/squares.png
Normal file
|
After Width: | Height: | Size: 639 B |
BIN
res/se/welcomeToCambridge.wav
Normal file
@@ -13,4 +13,7 @@ GameScene = require "scene.game"
|
||||
ModeSelectScene = require "scene.mode_select"
|
||||
InputConfigScene = require "scene.input_config"
|
||||
GameConfigScene = require "scene.game_config"
|
||||
TuningScene = require "scene.tuning"
|
||||
SettingsScene = require "scene.settings"
|
||||
CreditsScene = require "scene.credits"
|
||||
TitleScene = require "scene.title"
|
||||
|
||||
64
scene/credits.lua
Normal file
@@ -0,0 +1,64 @@
|
||||
local CreditsScene = Scene:extend()
|
||||
|
||||
CreditsScene.title = "Credits"
|
||||
|
||||
function CreditsScene:new()
|
||||
self.frames = 0
|
||||
switchBGM("credit_roll", "gm3")
|
||||
end
|
||||
|
||||
function CreditsScene:update()
|
||||
if love.window.hasFocus() then
|
||||
self.frames = self.frames + 1
|
||||
end
|
||||
if self.frames >= 4200 then
|
||||
playSE("mode_decide")
|
||||
scene = TitleScene()
|
||||
switchBGM(nil)
|
||||
elseif self.frames == 3600 then
|
||||
fadeoutBGM(2)
|
||||
end
|
||||
end
|
||||
|
||||
function CreditsScene:render()
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.draw(
|
||||
backgrounds[19],
|
||||
0, 0, 0,
|
||||
0.5, 0.5
|
||||
)
|
||||
|
||||
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(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, 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\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\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
|
||||
|
||||
function CreditsScene:onInputPress(e)
|
||||
if e.input == "menu_decide" or e.scancode == "return" or
|
||||
e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
|
||||
scene = TitleScene()
|
||||
switchBGM(nil)
|
||||
end
|
||||
end
|
||||
|
||||
return CreditsScene
|
||||
@@ -4,12 +4,13 @@ GameScene.title = "Game"
|
||||
|
||||
require 'load.save'
|
||||
|
||||
function GameScene:new(game_mode, ruleset)
|
||||
function GameScene:new(game_mode, ruleset, inputs)
|
||||
self.retry_mode = game_mode
|
||||
self.retry_ruleset = ruleset
|
||||
self.game = game_mode()
|
||||
self.secret_inputs = copy(inputs)
|
||||
self.game = game_mode(self.secret_inputs)
|
||||
self.ruleset = ruleset()
|
||||
self.game:initialize(self.ruleset)
|
||||
self.game:initialize(self.ruleset, self.secret_inputs)
|
||||
self.inputs = {
|
||||
left=false,
|
||||
right=false,
|
||||
@@ -22,6 +23,7 @@ function GameScene:new(game_mode, ruleset)
|
||||
rotate_180=false,
|
||||
hold=false,
|
||||
}
|
||||
self.paused = false
|
||||
DiscordRPC:update({
|
||||
details = self.game.rpc_details,
|
||||
state = self.game.name,
|
||||
@@ -29,15 +31,14 @@ function GameScene:new(game_mode, ruleset)
|
||||
end
|
||||
|
||||
function GameScene:update()
|
||||
if love.window.hasFocus() then
|
||||
if love.window.hasFocus() and not self.paused then
|
||||
local inputs = {}
|
||||
for input, value in pairs(self.inputs) do
|
||||
inputs[input] = value
|
||||
end
|
||||
self.game:update(inputs, self.ruleset)
|
||||
self.game.grid:update()
|
||||
end
|
||||
|
||||
self.game.grid:update()
|
||||
end
|
||||
|
||||
function GameScene:render()
|
||||
@@ -49,9 +50,37 @@ function GameScene:render()
|
||||
)
|
||||
|
||||
-- game frame
|
||||
love.graphics.draw(misc_graphics["frame"], 48, 64)
|
||||
if self.game.grid.width == 10 and self.game.grid.height == 24 then
|
||||
love.graphics.draw(misc_graphics["frame"], 48, 64)
|
||||
end
|
||||
|
||||
love.graphics.setColor(0, 0, 0, 200)
|
||||
love.graphics.rectangle("fill", 64, 80, 160, 320)
|
||||
love.graphics.rectangle(
|
||||
"fill", 64, 80,
|
||||
16 * self.game.grid.width, 16 * (self.game.grid.height - 4)
|
||||
)
|
||||
|
||||
if self.game.grid.width ~= 10 or self.game.grid.height ~= 24 then
|
||||
love.graphics.setColor(174/255, 83/255, 76/255, 1)
|
||||
love.graphics.setLineWidth(8)
|
||||
love.graphics.line(
|
||||
60,76,
|
||||
68+16*self.game.grid.width,76,
|
||||
68+16*self.game.grid.width,84+16*(self.game.grid.height-4),
|
||||
60,84+16*(self.game.grid.height-4),
|
||||
60,76
|
||||
)
|
||||
love.graphics.setColor(203/255, 137/255, 111/255, 1)
|
||||
love.graphics.setLineWidth(4)
|
||||
love.graphics.line(
|
||||
60,76,
|
||||
68+16*self.game.grid.width,76,
|
||||
68+16*self.game.grid.width,84+16*(self.game.grid.height-4),
|
||||
60,84+16*(self.game.grid.height-4),
|
||||
60,76
|
||||
)
|
||||
love.graphics.setLineWidth(1)
|
||||
end
|
||||
|
||||
self.game:drawGrid()
|
||||
self.game:drawPiece()
|
||||
@@ -68,16 +97,34 @@ function GameScene:render()
|
||||
|
||||
self.game:drawCustom()
|
||||
|
||||
love.graphics.setFont(font_3x5_2)
|
||||
if config.gamesettings.display_gamemode == 1 then
|
||||
love.graphics.printf(self.game.name .. " - " .. self.ruleset.name, 0, 460, 640, "left")
|
||||
end
|
||||
|
||||
love.graphics.setFont(font_3x5_3)
|
||||
if self.paused then love.graphics.print("PAUSED!", 80, 100) end
|
||||
|
||||
if self.game.completed then
|
||||
self.game:onGameComplete()
|
||||
elseif self.game.game_over then
|
||||
self.game:onGameOver()
|
||||
end
|
||||
end
|
||||
|
||||
function GameScene:onInputPress(e)
|
||||
if self.game.completed and (e.input == "menu_decide" or e.input == "menu_back" or e.input == "retry") then
|
||||
if (self.game.game_over or self.game.completed) and (e.input == "menu_decide" or e.input == "menu_back" or e.input == "retry") then
|
||||
highscore_entry = self.game:getHighscoreData()
|
||||
highscore_hash = self.game.hash .. "-" .. self.ruleset.hash
|
||||
submitHighscore(highscore_hash, highscore_entry)
|
||||
scene = e.input == "retry" and GameScene(self.retry_mode, self.retry_ruleset) or ModeSelectScene()
|
||||
scene = e.input == "retry" and GameScene(self.retry_mode, self.retry_ruleset, self.secret_inputs) or ModeSelectScene()
|
||||
elseif e.input == "retry" then
|
||||
scene = GameScene(self.retry_mode, self.retry_ruleset)
|
||||
switchBGM(nil)
|
||||
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
|
||||
scene = ModeSelectScene()
|
||||
elseif e.input and string.sub(e.input, 1, 5) ~= "menu_" then
|
||||
|
||||
@@ -3,15 +3,23 @@ local ConfigScene = Scene:extend()
|
||||
ConfigScene.title = "Game Settings"
|
||||
|
||||
require 'load.save'
|
||||
require 'libs.simple-slider'
|
||||
|
||||
ConfigScene.options = {
|
||||
-- this serves as reference to what the options' values mean i guess?
|
||||
{"manlock", "Manual locking",{"Per ruleset","Per gamemode","Harddrop", "Softdrop"}},
|
||||
{"piece_colour", "Piece Colours", {"Per ruleset","Arika" ,"TTC"}},
|
||||
{"world_reverse","A Button Rotation", {"Left" ,"Auto" ,"Right"}},
|
||||
{"next_se", "Next Piece SFX", {"On", "Off"}},
|
||||
{"das_last_key", "DAS Switch", {"Default", "Instant"}},
|
||||
{"synchroes_allowed", "Synchroes", {"Per ruleset", "On", "Off"}}
|
||||
-- Format: {name in config, displayed name, uses slider?, options OR slider name}
|
||||
{"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, {"In field", "Out of field"}},
|
||||
{"display_gamemode", "Display Gamemode", false, {"On", "Off"}},
|
||||
{"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"}},
|
||||
{"buffer_lock", "Buffer Lock Inputs", false, {"On", "Off"}},
|
||||
{"sfx_volume", "SFX", true, "sfxSlider"},
|
||||
{"bgm_volume", "BGM", true, "bgmSlider"},
|
||||
}
|
||||
local optioncount = #ConfigScene.options
|
||||
|
||||
@@ -24,10 +32,15 @@ function ConfigScene:new()
|
||||
details = "In menus",
|
||||
state = "Changing game settings",
|
||||
})
|
||||
|
||||
self.sfxSlider = newSlider(165, 400, 225, config.sfx_volume * 100, 0, 100, function(v) config.sfx_volume = v / 100 end, {width=20, knob="circle", track="roundrect"})
|
||||
self.bgmSlider = newSlider(465, 400, 225, config.bgm_volume * 100, 0, 100, function(v) config.bgm_volume = v / 100 end, {width=20, knob="circle", track="roundrect"})
|
||||
end
|
||||
|
||||
function ConfigScene:update()
|
||||
config["das_last_key"] = config.gamesettings.das_last_key == 2
|
||||
self.sfxSlider:update()
|
||||
self.bgmSlider:update()
|
||||
end
|
||||
|
||||
function ConfigScene:render()
|
||||
@@ -37,29 +50,45 @@ function ConfigScene:render()
|
||||
0, 0, 0,
|
||||
0.5, 0.5
|
||||
)
|
||||
|
||||
|
||||
love.graphics.setFont(font_3x5_4)
|
||||
love.graphics.print("GAME SETTINGS", 80, 40)
|
||||
|
||||
--Lazy check to see if we're on the SFX or BGM slider. Probably will need to be rewritten if more options get added.
|
||||
love.graphics.setColor(1, 1, 1, 0.5)
|
||||
love.graphics.rectangle("fill", 20, 98 + self.highlight * 20, 170, 22)
|
||||
if not ConfigScene.options[self.highlight][3] then
|
||||
love.graphics.rectangle("fill", 25, 98 + self.highlight * 20, 170, 22)
|
||||
else
|
||||
love.graphics.rectangle("fill", 65 + (1+self.highlight-#self.options) * 300, 342, 215, 33)
|
||||
end
|
||||
|
||||
love.graphics.setFont(font_3x5_2)
|
||||
for i, option in ipairs(ConfigScene.options) do
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.printf(option[2], 40, 100 + i * 20, 150, "left")
|
||||
for j, setting in ipairs(option[3]) do
|
||||
love.graphics.setColor(1, 1, 1, config.gamesettings[option[1]] == j and 1 or 0.5)
|
||||
love.graphics.printf(setting, 100 + 110 * j, 100 + i * 20, 100, "center")
|
||||
if not option[3] then
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.printf(option[2], 40, 100 + i * 20, 150, "left")
|
||||
for j, setting in ipairs(option[4]) do
|
||||
love.graphics.setColor(1, 1, 1, config.gamesettings[option[1]] == j and 1 or 0.5)
|
||||
love.graphics.printf(setting, 100 + 110 * j, 100 + i * 20, 100, "center")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.setFont(font_3x5_3)
|
||||
love.graphics.print("SFX Volume: " .. math.floor(self.sfxSlider:getValue()) .. "%", 75, 345)
|
||||
love.graphics.print("BGM Volume: " .. math.floor(self.bgmSlider:getValue()) .. "%", 375, 345)
|
||||
|
||||
love.graphics.setColor(1, 1, 1, 0.75)
|
||||
self.sfxSlider:draw()
|
||||
self.bgmSlider:draw()
|
||||
end
|
||||
|
||||
function ConfigScene:onInputPress(e)
|
||||
if e.input == "menu_decide" or e.scancode == "return" then
|
||||
playSE("mode_decide")
|
||||
saveConfig()
|
||||
scene = TitleScene()
|
||||
scene = SettingsScene()
|
||||
elseif e.input == "up" or e.scancode == "up" then
|
||||
playSE("cursor")
|
||||
self.highlight = Mod1(self.highlight-1, optioncount)
|
||||
@@ -67,16 +96,28 @@ function ConfigScene:onInputPress(e)
|
||||
playSE("cursor")
|
||||
self.highlight = Mod1(self.highlight+1, optioncount)
|
||||
elseif e.input == "left" or e.scancode == "left" then
|
||||
playSE("cursor_lr")
|
||||
local option = ConfigScene.options[self.highlight]
|
||||
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]-1, #option[3])
|
||||
if not self.options[self.highlight][3] then
|
||||
playSE("cursor_lr")
|
||||
local option = ConfigScene.options[self.highlight]
|
||||
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]-1, #option[4])
|
||||
else
|
||||
playSE("cursor")
|
||||
sld = self[self.options[self.highlight][4]]
|
||||
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
|
||||
playSE("cursor_lr")
|
||||
local option = ConfigScene.options[self.highlight]
|
||||
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]+1, #option[3])
|
||||
if not self.options[self.highlight][3] then
|
||||
playSE("cursor_lr")
|
||||
local option = ConfigScene.options[self.highlight]
|
||||
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]+1, #option[4])
|
||||
else
|
||||
playSE("cursor")
|
||||
sld = self[self.options[self.highlight][4]]
|
||||
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()
|
||||
scene = TitleScene()
|
||||
scene = SettingsScene()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ local configurable_inputs = {
|
||||
"rotate_180",
|
||||
"hold",
|
||||
"retry",
|
||||
"pause",
|
||||
}
|
||||
|
||||
local function newSetInputs()
|
||||
@@ -82,7 +83,7 @@ function ConfigScene:onInputPress(e)
|
||||
-- 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 = TitleScene()
|
||||
scene = SettingsScene()
|
||||
elseif self.input_state > table.getn(configurable_inputs) then
|
||||
if e.scancode == "return" then
|
||||
-- save new input, then load next scene
|
||||
|
||||
@@ -11,6 +11,14 @@ function ModeSelectScene:new()
|
||||
ruleset = current_ruleset,
|
||||
select = "mode",
|
||||
}
|
||||
self.secret_inputs = {
|
||||
rotate_left = false,
|
||||
rotate_left2 = false,
|
||||
rotate_right = false,
|
||||
rotate_right2 = false,
|
||||
rotate_180 = false,
|
||||
hold = false,
|
||||
}
|
||||
DiscordRPC:update({
|
||||
details = "In menus",
|
||||
state = "Choosing a mode",
|
||||
@@ -18,6 +26,7 @@ function ModeSelectScene:new()
|
||||
end
|
||||
|
||||
function ModeSelectScene:update()
|
||||
switchBGM(nil) -- experimental
|
||||
end
|
||||
|
||||
function ModeSelectScene:render()
|
||||
@@ -66,7 +75,7 @@ function ModeSelectScene:onInputPress(e)
|
||||
config.current_ruleset = current_ruleset
|
||||
playSE("mode_decide")
|
||||
saveConfig()
|
||||
scene = GameScene(game_modes[self.menu_state.mode], rulesets[self.menu_state.ruleset])
|
||||
scene = GameScene(game_modes[self.menu_state.mode], rulesets[self.menu_state.ruleset], self.secret_inputs)
|
||||
elseif e.input == "up" or e.scancode == "up" then
|
||||
self:changeOption(-1)
|
||||
playSE("cursor")
|
||||
@@ -78,6 +87,14 @@ function ModeSelectScene:onInputPress(e)
|
||||
playSE("cursor_lr")
|
||||
elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
|
||||
scene = TitleScene()
|
||||
elseif e.input then
|
||||
self.secret_inputs[e.input] = true
|
||||
end
|
||||
end
|
||||
|
||||
function ModeSelectScene:onInputRelease(e)
|
||||
if e.input == "hold" or (e.input and string.sub(e.input, 1, 7) == "rotate_") then
|
||||
self.secret_inputs[e.input] = false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
65
scene/settings.lua
Normal file
@@ -0,0 +1,65 @@
|
||||
local SettingsScene = Scene:extend()
|
||||
|
||||
SettingsScene.title = "Settings"
|
||||
|
||||
local menu_screens = {
|
||||
InputConfigScene,
|
||||
GameConfigScene,
|
||||
TuningScene
|
||||
}
|
||||
|
||||
function SettingsScene:new()
|
||||
self.menu_state = 1
|
||||
DiscordRPC:update({
|
||||
details = "In menus",
|
||||
state = "Changing settings",
|
||||
})
|
||||
end
|
||||
|
||||
function SettingsScene:update() end
|
||||
|
||||
function SettingsScene:render()
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.draw(
|
||||
backgrounds["game_config"],
|
||||
0, 0, 0,
|
||||
0.5, 0.5
|
||||
)
|
||||
|
||||
love.graphics.setFont(font_3x5_4)
|
||||
love.graphics.print("SETTINGS", 80, 40)
|
||||
|
||||
love.graphics.setFont(font_3x5_2)
|
||||
love.graphics.print("Here, you can change some settings that change\nthe look and feel of the game.", 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
|
||||
end
|
||||
|
||||
function SettingsScene:changeOption(rel)
|
||||
local len = table.getn(menu_screens)
|
||||
self.menu_state = (self.menu_state + len + rel - 1) % len + 1
|
||||
end
|
||||
|
||||
function SettingsScene: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 e.input == "menu_back" or e.scancode == "backspace" or e.scancode == "delete" then
|
||||
scene = TitleScene()
|
||||
end
|
||||
end
|
||||
|
||||
return SettingsScene
|
||||
@@ -1,9 +1,12 @@
|
||||
local TitleScene = Scene:extend()
|
||||
|
||||
TitleScene.title = "Title"
|
||||
TitleScene.restart_message = false
|
||||
|
||||
local main_menu_screens = {
|
||||
ModeSelectScene,
|
||||
InputConfigScene,
|
||||
GameConfigScene,
|
||||
SettingsScene,
|
||||
CreditsScene,
|
||||
ExitScene,
|
||||
}
|
||||
|
||||
@@ -24,6 +27,11 @@ local mainmenuidle = {
|
||||
|
||||
function TitleScene:new()
|
||||
self.main_menu_state = 1
|
||||
self.frames = 0
|
||||
self.snow_bg_opacity = 0
|
||||
self.y_offset = 0
|
||||
self.text = ""
|
||||
self.text_flag = false
|
||||
DiscordRPC:update({
|
||||
details = "In menus",
|
||||
state = mainmenuidle[math.random(#mainmenuidle)],
|
||||
@@ -31,17 +39,42 @@ function TitleScene:new()
|
||||
end
|
||||
|
||||
function TitleScene:update()
|
||||
if self.text_flag then
|
||||
self.frames = self.frames + 1
|
||||
self.snow_bg_opacity = self.snow_bg_opacity + 0.01
|
||||
end
|
||||
if self.frames < 125 then self.y_offset = self.frames
|
||||
elseif self.frames < 185 then self.y_offset = 125
|
||||
else self.y_offset = 310 - self.frames end
|
||||
end
|
||||
|
||||
function TitleScene:render()
|
||||
love.graphics.setFont(font_3x5_2)
|
||||
|
||||
love.graphics.setColor(1, 1, 1, 1 - self.snow_bg_opacity)
|
||||
love.graphics.draw(
|
||||
backgrounds["title"],
|
||||
0, 0, 0,
|
||||
0.5, 0.5
|
||||
)
|
||||
|
||||
love.graphics.setColor(1, 1, 1, self.snow_bg_opacity)
|
||||
love.graphics.draw(
|
||||
backgrounds["snow"],
|
||||
0, 0, 0,
|
||||
0.5, 0.5
|
||||
)
|
||||
|
||||
love.graphics.draw(
|
||||
misc_graphics["santa"],
|
||||
400, -205 + self.y_offset,
|
||||
0, 0.5, 0.5
|
||||
)
|
||||
love.graphics.print("Happy Holidays!", 320, -100 + self.y_offset)
|
||||
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.print(self.restart_message and "Restart Cambridge..." or "", 0, 0)
|
||||
|
||||
love.graphics.setColor(1, 1, 1, 0.5)
|
||||
love.graphics.rectangle("fill", 20, 278 + 20 * self.main_menu_state, 160, 22)
|
||||
|
||||
@@ -69,6 +102,11 @@ function TitleScene:onInputPress(e)
|
||||
playSE("cursor")
|
||||
elseif e.input == "menu_back" or e.scancode == "backspace" or e.scancode == "delete" then
|
||||
love.event.quit()
|
||||
else
|
||||
self.text = self.text .. (e.scancode ~= nil and e.scancode or "")
|
||||
if self.text == "ffffff" then
|
||||
self.text_flag = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
90
scene/tuning.lua
Normal file
@@ -0,0 +1,90 @@
|
||||
local TuningScene = Scene:extend()
|
||||
|
||||
TuningScene.title = "Tuning Settings"
|
||||
|
||||
require 'load.save'
|
||||
require 'libs.simple-slider'
|
||||
|
||||
TuningScene.options = {
|
||||
-- Serves as a reference for the options available in the menu. Format: {name in config, name as displayed if applicable, slider name}
|
||||
{"das", "DAS", "dasSlider"},
|
||||
{"arr", "ARR", "arrSlider"},
|
||||
{"dcd", "DCD", "dcdSlider"},
|
||||
}
|
||||
|
||||
local optioncount = #TuningScene.options
|
||||
|
||||
function TuningScene:new()
|
||||
DiscordRPC:update({
|
||||
details = "In menus",
|
||||
state = "Changing tuning settings",
|
||||
})
|
||||
self.highlight = 1
|
||||
|
||||
self.dasSlider = newSlider(290, 225, 400, config.das, 0, 20, function(v) config.das = math.floor(v) end, {width=20, knob="circle", track="roundrect"})
|
||||
self.arrSlider = newSlider(290, 300, 400, config.arr, 0, 6, function(v) config.arr = math.floor(v) end, {width=20, knob="circle", track="roundrect"})
|
||||
self.dcdSlider = newSlider(290, 375, 400, config.dcd, 0, 6, function(v) config.dcd = math.floor(v) end, {width=20, knob="circle", track="roundrect"})
|
||||
end
|
||||
|
||||
function TuningScene:update()
|
||||
self.dasSlider:update()
|
||||
self.arrSlider:update()
|
||||
self.dcdSlider:update()
|
||||
end
|
||||
|
||||
function TuningScene:render()
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.draw(
|
||||
backgrounds["game_config"],
|
||||
0, 0, 0,
|
||||
0.5, 0.5
|
||||
)
|
||||
|
||||
love.graphics.setColor(1, 1, 1, 0.5)
|
||||
love.graphics.rectangle("fill", 75, 98 + self.highlight * 75, 400, 33)
|
||||
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
|
||||
love.graphics.setFont(font_3x5_4)
|
||||
love.graphics.print("TUNING SETTINGS", 80, 40)
|
||||
|
||||
love.graphics.setFont(font_3x5_2)
|
||||
love.graphics.print("These settings will only apply to modes\nthat do not use their own tunings.", 80, 90)
|
||||
|
||||
love.graphics.setFont(font_3x5_3)
|
||||
love.graphics.print("Delayed Auto-Shift (DAS): " .. math.floor(self.dasSlider:getValue()) .. "F", 80, 175)
|
||||
love.graphics.print("Auto-Repeat Rate (ARR): " .. math.floor(self.arrSlider:getValue()) .. "F", 80, 250)
|
||||
love.graphics.print("DAS Cut Delay (DCD): " .. math.floor(self.dcdSlider:getValue()) .. "F", 80, 325)
|
||||
|
||||
love.graphics.setColor(1, 1, 1, 0.75)
|
||||
self.dasSlider:draw()
|
||||
self.arrSlider:draw()
|
||||
self.dcdSlider:draw()
|
||||
end
|
||||
|
||||
function TuningScene:onInputPress(e)
|
||||
if e.input == "menu_decide" or e.scancode == "return" then
|
||||
playSE("mode_decide")
|
||||
saveConfig()
|
||||
scene = SettingsScene()
|
||||
elseif e.input == "up" or e.scancode == "up" then
|
||||
playSE("cursor")
|
||||
self.highlight = Mod1(self.highlight-1, optioncount)
|
||||
elseif e.input == "down" or e.scancode == "down" then
|
||||
playSE("cursor")
|
||||
self.highlight = Mod1(self.highlight+1, optioncount)
|
||||
elseif e.input == "left" or e.scancode == "left" then
|
||||
playSE("cursor")
|
||||
sld = self[self.options[self.highlight][3]]
|
||||
sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() - 1) / (sld.max - sld.min)))
|
||||
elseif e.input == "right" or e.scancode == "right" then
|
||||
playSE("cursor")
|
||||
sld = self[self.options[self.highlight][3]]
|
||||
sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() + 1) / (sld.max - sld.min)))
|
||||
elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
|
||||
loadSave()
|
||||
scene = SettingsScene()
|
||||
end
|
||||
end
|
||||
|
||||
return TuningScene
|
||||
@@ -6,13 +6,15 @@ local empty = { skin = "", colour = "" }
|
||||
local oob = { skin = "", colour = "" }
|
||||
local block = { skin = "2tie", colour = "A" }
|
||||
|
||||
function Grid:new()
|
||||
function Grid:new(width, height)
|
||||
self.grid = {}
|
||||
self.grid_age = {}
|
||||
for y = 1, 24 do
|
||||
self.width = width
|
||||
self.height = height
|
||||
for y = 1, self.height do
|
||||
self.grid[y] = {}
|
||||
self.grid_age[y] = {}
|
||||
for x = 1, 10 do
|
||||
for x = 1, self.width do
|
||||
self.grid[y][x] = empty
|
||||
self.grid_age[y][x] = 0
|
||||
end
|
||||
@@ -20,8 +22,8 @@ function Grid:new()
|
||||
end
|
||||
|
||||
function Grid:clear()
|
||||
for y = 1, 24 do
|
||||
for x = 1, 10 do
|
||||
for y = 1, self.height do
|
||||
for x = 1, self.width do
|
||||
self.grid[y][x] = empty
|
||||
self.grid_age[y][x] = 0
|
||||
end
|
||||
@@ -29,7 +31,7 @@ function Grid:clear()
|
||||
end
|
||||
|
||||
function Grid:getCell(x, y)
|
||||
if x < 1 or x > 10 or y > 24 then return oob
|
||||
if x < 1 or x > self.width or y > self.height then return oob
|
||||
elseif y < 1 then return empty
|
||||
else return self.grid[y][x]
|
||||
end
|
||||
@@ -98,22 +100,25 @@ end
|
||||
|
||||
function Grid:getClearedRowCount()
|
||||
local count = 0
|
||||
for row = 1, 24 do
|
||||
local cleared_row_table = {}
|
||||
for row = 1, self.height do
|
||||
if self:isRowFull(row) then
|
||||
count = count + 1
|
||||
table.insert(cleared_row_table, row)
|
||||
end
|
||||
end
|
||||
return count
|
||||
return count, cleared_row_table
|
||||
end
|
||||
|
||||
function Grid:markClearedRows()
|
||||
for row = 1, 24 do
|
||||
for row = 1, self.height do
|
||||
if self:isRowFull(row) then
|
||||
for x = 1, 10 do
|
||||
for x = 1, self.width do
|
||||
self.grid[row][x] = {
|
||||
skin = self.grid[row][x].skin,
|
||||
colour = "X"
|
||||
}
|
||||
self.grid_age[row][x] = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -121,66 +126,52 @@ function Grid:markClearedRows()
|
||||
end
|
||||
|
||||
function Grid:clearClearedRows()
|
||||
for row = 1, 24 do
|
||||
for row = 1, self.height do
|
||||
if self:isRowFull(row) then
|
||||
for above_row = row, 2, -1 do
|
||||
self.grid[above_row] = self.grid[above_row - 1]
|
||||
self.grid_age[above_row] = self.grid_age[above_row - 1]
|
||||
end
|
||||
self.grid[1] = {empty, empty, empty, empty, empty, empty, empty, empty, empty, empty}
|
||||
self.grid_age[1] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
self.grid[1] = {}
|
||||
self.grid_age[1] = {}
|
||||
for i = 1, self.width do
|
||||
self.grid[1][i] = empty
|
||||
self.grid_age[1][i] = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Grid:copyBottomRow()
|
||||
for row = 1, 23 do
|
||||
for row = 1, self.height - 1 do
|
||||
self.grid[row] = self.grid[row+1]
|
||||
self.grid_age[row] = self.grid_age[row+1]
|
||||
end
|
||||
self.grid[24] = {empty, empty, empty, empty, empty, empty, empty, empty, empty, empty}
|
||||
self.grid_age[24] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
for col = 1, 10 do
|
||||
self.grid[24][col] = (self.grid[23][col] == empty) and empty or block
|
||||
self.grid[self.height] = {}
|
||||
self.grid_age[self.height] = {}
|
||||
for i = 1, self.width do
|
||||
self.grid[self.height][i] = (self.grid[self.height - 1][i] == empty) and empty or block
|
||||
self.grid_age[self.height][i] = 0
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Grid:garbageRise(row_vals)
|
||||
for row = 1, 23 do
|
||||
self.grid[row] = self.grid[row+1]
|
||||
self.grid_age[row] = self.grid_age[row+1]
|
||||
end
|
||||
self.grid[24] = {empty, empty, empty, empty, empty, empty, empty, empty, empty, empty}
|
||||
self.grid_age[24] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
for col = 1, 10 do
|
||||
self.grid[24][col] = (row_vals[col] == "e") and empty or block
|
||||
for row = 1, self.height - 1 do
|
||||
self.grid[row] = self.grid[row+1]
|
||||
self.grid_age[row] = self.grid_age[row+1]
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:applyFourWide()
|
||||
for row = 1, 24 do
|
||||
local x = self.grid[row]
|
||||
x[1] = x[1]~=block and block or x[1]
|
||||
x[2] = x[2]~=block and block or x[2]
|
||||
x[3] = x[3]~=block and block or x[3]
|
||||
x[8] = x[8]~=block and block or x[8]
|
||||
x[9] = x[9]~=block and block or x[9]
|
||||
x[10] = x[10]~=block and block or x[10]
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:applyCeiling(lines)
|
||||
for row = 1, lines do
|
||||
for col = 1, 9 do
|
||||
self.grid[row][col] = block
|
||||
end
|
||||
self.grid[self.height] = {}
|
||||
self.grid_age[self.height] = {}
|
||||
for i = 1, self.width do
|
||||
self.grid[self.height][i] = (row_vals[i] == "e") and empty or block
|
||||
self.grid_age[self.height][i] = 0
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:clearSpecificRow(row)
|
||||
for col = 1, 10 do
|
||||
for col = 1, self.width do
|
||||
self.grid[row][col] = empty
|
||||
end
|
||||
end
|
||||
@@ -194,7 +185,7 @@ function Grid:applyPiece(piece)
|
||||
for index, offset in pairs(offsets) do
|
||||
x = piece.position.x + offset.x
|
||||
y = piece.position.y + offset.y
|
||||
if y + 1 > 0 then
|
||||
if y + 1 > 0 and y < self.height then
|
||||
self.grid[y+1][x+1] = {
|
||||
skin = piece.skin,
|
||||
colour = piece.colour
|
||||
@@ -210,7 +201,7 @@ function Grid:applyBigPiece(piece)
|
||||
y = piece.position.y + offset.y
|
||||
for a = 1, 2 do
|
||||
for b = 1, 2 do
|
||||
if y*2+a > 0 then
|
||||
if y*2+a > 0 and y*2 < self.height then
|
||||
self.grid[y*2+a][x*2+b] = {
|
||||
skin = piece.skin,
|
||||
colour = piece.colour
|
||||
@@ -222,8 +213,8 @@ function Grid:applyBigPiece(piece)
|
||||
end
|
||||
|
||||
function Grid:checkForBravo(cleared_row_count)
|
||||
for i = 0, 23 - cleared_row_count do
|
||||
for j = 0, 9 do
|
||||
for i = 0, self.height - 1 - cleared_row_count do
|
||||
for j = 0, self.width - 1 do
|
||||
if self:isOccupied(j, i) then return false end
|
||||
end
|
||||
end
|
||||
@@ -231,9 +222,9 @@ function Grid:checkForBravo(cleared_row_count)
|
||||
end
|
||||
|
||||
function Grid:checkStackHeight()
|
||||
for i = 0, 23 do
|
||||
for j = 0, 9 do
|
||||
if self:isOccupied(j, i) then return 24 - i end
|
||||
for i = 0, self.height - 1 do
|
||||
for j = 0, self.width - 1 do
|
||||
if self:isOccupied(j, i) then return self.height - i end
|
||||
end
|
||||
end
|
||||
return 0
|
||||
@@ -273,9 +264,120 @@ function Grid:checkSecretGrade()
|
||||
return sgrade
|
||||
end
|
||||
|
||||
function Grid:hasGemBlocks()
|
||||
for y = 1, self.height do
|
||||
for x = 1, self.width do
|
||||
if self.grid[y][x].skin == "gem" then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function Grid:mirror()
|
||||
local new_grid = {}
|
||||
for y = 1, self.height do
|
||||
new_grid[y] = {}
|
||||
for x = 1, self.width do
|
||||
new_grid[y][x] = empty
|
||||
end
|
||||
end
|
||||
|
||||
for y = 1, self.height do
|
||||
for x = 1, self.width do
|
||||
new_grid[y][x] = self.grid[y][self.width + 1 - x]
|
||||
end
|
||||
end
|
||||
self.grid = new_grid
|
||||
end
|
||||
|
||||
function Grid:applyMap(map)
|
||||
for y, row in pairs(map) do
|
||||
for x, block in pairs(row) do
|
||||
self.grid_age[y][x] = 0
|
||||
self.grid[y][x] = block
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- inefficient algorithm for squares
|
||||
function Grid:markSquares()
|
||||
-- goes up by 1 for silver, 2 for gold
|
||||
local square_count = 0
|
||||
for i = 1, 2 do
|
||||
for y = 5, self.height - 3 do
|
||||
for x = 1, self.width - 3 do
|
||||
local age_table = {}
|
||||
local age_count = 0
|
||||
local colour_table = {}
|
||||
local is_square = true
|
||||
for j = 0, 3 do
|
||||
for k = 0, 3 do
|
||||
if self.grid[y+j][x+k].skin == "" or self.grid[y+j][x+k].skin == "square" then
|
||||
is_square = false
|
||||
end
|
||||
if age_table[self.grid_age[y+j][x+k]] == nil then
|
||||
age_table[self.grid_age[y+j][x+k]] = 1
|
||||
age_count = age_count + 1
|
||||
else
|
||||
age_table[self.grid_age[y+j][x+k]] = age_table[self.grid_age[y+j][x+k]] + 1
|
||||
end
|
||||
if age_count > 4 or age_table[self.grid_age[y+j][x+k]] > 4 then
|
||||
is_square = false
|
||||
end
|
||||
if not table.contains(colour_table, self.grid[y+j][x+k].colour) then
|
||||
table.insert(colour_table, self.grid[y+j][x+k].colour)
|
||||
end
|
||||
end
|
||||
end
|
||||
if is_square then
|
||||
if i == 1 and #colour_table == 1 then
|
||||
for j = 0, 3 do
|
||||
for k = 0, 3 do
|
||||
self.grid[y+j][x+k].colour = "Y"
|
||||
self.grid[y+j][x+k].skin = "square"
|
||||
end
|
||||
end
|
||||
square_count = square_count + 2
|
||||
elseif i == 2 then
|
||||
for j = 0, 3 do
|
||||
for k = 0, 3 do
|
||||
self.grid[y+j][x+k].colour = "F"
|
||||
self.grid[y+j][x+k].skin = "square"
|
||||
end
|
||||
|
||||
end
|
||||
square_count = square_count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return square_count
|
||||
end
|
||||
|
||||
-- square scan
|
||||
function Grid:scanForSquares()
|
||||
local table = {}
|
||||
for row = 1, self.height do
|
||||
local silver = 0
|
||||
local gold = 0
|
||||
for col = 1, self.width do
|
||||
local colour = self.grid[row][col].colour
|
||||
if self.grid[row][col].skin == "square" then
|
||||
if colour == "Y" then gold = gold + 1
|
||||
else silver = silver + 1 end
|
||||
end
|
||||
end
|
||||
table[row] = gold * 2.5 + silver * 1.25
|
||||
end
|
||||
return table
|
||||
end
|
||||
|
||||
function Grid:update()
|
||||
for y = 1, 24 do
|
||||
for x = 1, 10 do
|
||||
for y = 1, self.height do
|
||||
for x = 1, self.width do
|
||||
if self.grid[y][x] ~= empty then
|
||||
self.grid_age[y][x] = self.grid_age[y][x] + 1
|
||||
end
|
||||
@@ -284,8 +386,8 @@ function Grid:update()
|
||||
end
|
||||
|
||||
function Grid:draw()
|
||||
for y = 5, 24 do
|
||||
for x = 1, 10 do
|
||||
for y = 5, self.height do
|
||||
for x = 1, self.width do
|
||||
if self.grid[y][x] ~= empty then
|
||||
if self.grid_age[y][x] < 2 then
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
@@ -293,24 +395,27 @@ function Grid:draw()
|
||||
else
|
||||
if self.grid[y][x].skin == "bone" then
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
else
|
||||
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
|
||||
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].skin ~= "bone" then
|
||||
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 > 1 and self.grid[y-1][x] == empty then
|
||||
if y > 5 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 < 24 and self.grid[y+1][x] == empty then
|
||||
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
|
||||
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
|
||||
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
|
||||
end
|
||||
if x < 10 and self.grid[y][x+1] == empty then
|
||||
if x < self.width and self.grid[y][x+1] == empty then
|
||||
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
|
||||
end
|
||||
end
|
||||
@@ -320,21 +425,26 @@ function Grid:draw()
|
||||
end
|
||||
|
||||
function Grid:drawOutline()
|
||||
for y = 5, 24 do
|
||||
for x = 1, 10 do
|
||||
if self.grid[y][x] ~= empty then
|
||||
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 > 1 and self.grid[y-1][x] == empty then
|
||||
if y > 5 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 < 24 and self.grid[y+1][x] == empty then
|
||||
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
|
||||
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
|
||||
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
|
||||
end
|
||||
if x < 10 and self.grid[y][x+1] == empty then
|
||||
if x < self.width and self.grid[y][x+1] == empty then
|
||||
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
|
||||
end
|
||||
end
|
||||
@@ -345,11 +455,11 @@ end
|
||||
function Grid:drawInvisible(opacity_function, garbage_opacity_function, lock_flash, brightness)
|
||||
lock_flash = lock_flash == nil and true or lock_flash
|
||||
brightness = brightness == nil and 0.5 or brightness
|
||||
for y = 5, 24 do
|
||||
for x = 1, 10 do
|
||||
for y = 5, self.height do
|
||||
for x = 1, self.width do
|
||||
if self.grid[y][x] ~= empty then
|
||||
if self.grid[y][x].colour == "X" then
|
||||
opacity = 1
|
||||
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
|
||||
@@ -361,16 +471,17 @@ 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 > 1 and self.grid[y-1][x] == empty then
|
||||
if y > 5 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 < 24 and self.grid[y+1][x] == empty then
|
||||
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
|
||||
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
|
||||
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
|
||||
end
|
||||
if x < 10 and self.grid[y][x+1] == empty then
|
||||
if x < self.width and self.grid[y][x+1] == empty then
|
||||
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
|
||||
end
|
||||
end
|
||||
@@ -380,4 +491,45 @@ function Grid:drawInvisible(opacity_function, garbage_opacity_function, lock_fla
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:drawCustom(colour_function, gamestate)
|
||||
--[[
|
||||
colour_function: (game, block, x, y, age) -> (R, G, B, A, outlineA)
|
||||
When called, calls the supplied function on every block passing the block itself as argument
|
||||
as well as coordinates and the grid_age value of the same cell.
|
||||
Should return a RGBA colour for the block, as well as the opacity of the stack outline (0 for no outline).
|
||||
|
||||
gamestate: the gamemode instance itself to pass in colour_function
|
||||
]]
|
||||
for y = 5, self.height do
|
||||
for x = 1, self.width do
|
||||
local block = self.grid[y][x]
|
||||
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 = 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
|
||||
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
|
||||
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
|
||||
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
|
||||
end
|
||||
if x < self.width and self.grid[y][x+1] == empty then
|
||||
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Grid
|
||||
|
||||
@@ -143,9 +143,10 @@ function Piece:draw(opacity, brightness, grid, partial_das)
|
||||
love.graphics.setColor(brightness, brightness, brightness, opacity)
|
||||
local offsets = self:getBlockOffsets()
|
||||
local gravity_offset = 0
|
||||
--if grid ~= nil and not self:isDropBlocked(grid) then
|
||||
-- gravity_offset = self.gravity * 16
|
||||
--end
|
||||
if config.gamesettings.smooth_movement == 1 and
|
||||
grid ~= nil and not self:isDropBlocked(grid) then
|
||||
gravity_offset = self.gravity * 16
|
||||
end
|
||||
if partial_das == nil then partial_das = 0 end
|
||||
for index, offset in pairs(offsets) do
|
||||
local x = self.position.x + offset.x
|
||||
|
||||
@@ -5,14 +5,18 @@ local playedReadySE = false
|
||||
local playedGoSE = false
|
||||
|
||||
local Grid = require 'tetris.components.grid'
|
||||
local Randomizer = require 'tetris.randomizers.randomizer'
|
||||
local Randomizer = require 'tetris.randomizers.bag7'
|
||||
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()
|
||||
self.grid = Grid()
|
||||
function GameMode:new(secret_inputs)
|
||||
self.grid = Grid(10, 24)
|
||||
self.randomizer = Randomizer()
|
||||
self.piece = nil
|
||||
self.ready_frames = 100
|
||||
@@ -21,6 +25,7 @@ function GameMode:new()
|
||||
self.score = 0
|
||||
self.level = 0
|
||||
self.lines = 0
|
||||
self.squares = 0
|
||||
self.drop_bonus = 0
|
||||
self.are = 0
|
||||
self.lcd = 0
|
||||
@@ -45,6 +50,8 @@ function GameMode:new()
|
||||
self.big_mode = false
|
||||
self.irs = true
|
||||
self.ihs = true
|
||||
self.square_mode = false
|
||||
self.immobile_spin_bonus = false
|
||||
self.rpc_details = "In game"
|
||||
self.SGnames = {
|
||||
"9", "8", "7", "6", "5", "4", "3", "2", "1",
|
||||
@@ -56,6 +63,7 @@ function GameMode:new()
|
||||
self.hard_drop_locked = false
|
||||
self.lock_on_soft_drop = false
|
||||
self.lock_on_hard_drop = false
|
||||
self.used_randomizer = nil
|
||||
self.hold_queue = nil
|
||||
self.held = false
|
||||
self.section_start_time = 0
|
||||
@@ -70,20 +78,34 @@ function GameMode:getLineARE() return 25 end
|
||||
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 {
|
||||
skin = "2tie",
|
||||
shape = self.randomizer:nextPiece(),
|
||||
skin = self:getSkin(),
|
||||
shape = self.used_randomizer:nextPiece(),
|
||||
orientation = ruleset:getDefaultOrientation(),
|
||||
}
|
||||
end
|
||||
|
||||
function GameMode:initialize(ruleset)
|
||||
function GameMode:getSkin()
|
||||
return "2tie"
|
||||
end
|
||||
|
||||
function GameMode:initialize(ruleset, secret_inputs)
|
||||
-- generate next queue
|
||||
self:new()
|
||||
for i = 1, self.next_queue_length do
|
||||
self:new(secret_inputs)
|
||||
self.used_randomizer = (
|
||||
ruleset.pieces == self.randomizer.possible_pieces and
|
||||
self.randomizer or
|
||||
(
|
||||
ruleset.pieces == 7 and
|
||||
Randomizer() or
|
||||
BagRandomizer(ruleset.pieces)
|
||||
)
|
||||
)
|
||||
for i = 1, math.max(self.next_queue_length, 1) 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]
|
||||
@@ -91,28 +113,52 @@ function GameMode:initialize(ruleset)
|
||||
end
|
||||
|
||||
function GameMode:update(inputs, ruleset)
|
||||
if self.game_over then
|
||||
if self.game_over or self.completed then
|
||||
self.game_over_frames = self.game_over_frames + 1
|
||||
if self.game_over_frames >= 60 then
|
||||
self.completed = true
|
||||
end
|
||||
return
|
||||
end
|
||||
if self.completed then return end
|
||||
|
||||
if config.gamesettings.diagonal_input == 2 then
|
||||
if inputs["left"] or inputs["right"] then
|
||||
inputs["up"] = false
|
||||
inputs["down"] = false
|
||||
elseif inputs["up"] or inputs["down"] then
|
||||
inputs["left"] = false
|
||||
inputs["right"] = false
|
||||
end
|
||||
end
|
||||
|
||||
-- advance one frame
|
||||
if self:advanceOneFrame(inputs, ruleset) == false then return end
|
||||
|
||||
self:chargeDAS(inputs, self:getDasLimit(), self.getARR())
|
||||
self:chargeDAS(inputs, self:getDasLimit(), self:getARR())
|
||||
|
||||
-- set attempt flags
|
||||
if inputs["left"] or inputs["right"] then self:onAttemptPieceMove(self.piece) end
|
||||
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)
|
||||
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
|
||||
@@ -125,6 +171,9 @@ 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
|
||||
|
||||
@@ -136,7 +185,9 @@ function GameMode:update(inputs, ruleset)
|
||||
self.hard_drop_locked = false
|
||||
end
|
||||
|
||||
-- diff vars to use in checks
|
||||
local piece_y = self.piece.position.y
|
||||
local piece_rot = self.piece.rotation
|
||||
|
||||
ruleset:processPiece(
|
||||
inputs, self.piece, self.grid, self:getGravity(), self.prev_inputs,
|
||||
@@ -146,12 +197,29 @@ function GameMode:update(inputs, ruleset)
|
||||
)
|
||||
|
||||
local piece_dy = self.piece.position.y - piece_y
|
||||
local piece_drot = self.piece.rotation - piece_rot
|
||||
|
||||
-- das cut
|
||||
if (
|
||||
(piece_dy ~= 0 and (inputs.up or inputs.down)) or
|
||||
(piece_drot ~= 0 and (
|
||||
inputs.rotate_left or inputs.rotate_right or
|
||||
inputs.rotate_left2 or inputs.rotate_right2 or
|
||||
inputs.rotate_180
|
||||
))
|
||||
) then
|
||||
self.das.frames = math.max(
|
||||
self.das.frames - self:getDasCutDelay(),
|
||||
-(self:getDasCutDelay() + 1)
|
||||
)
|
||||
end
|
||||
|
||||
if inputs["up"] == true and
|
||||
self.piece:isDropBlocked(self.grid) and
|
||||
not self.hard_drop_locked then
|
||||
self:onHardDrop(piece_dy)
|
||||
if self.lock_on_hard_drop then
|
||||
self.piece_hard_dropped = true
|
||||
self.piece.locked = true
|
||||
end
|
||||
end
|
||||
@@ -168,13 +236,15 @@ function GameMode:update(inputs, ruleset)
|
||||
|
||||
if self.piece.locked == true then
|
||||
self.grid:applyPiece(self.piece)
|
||||
self.grid:markClearedRows()
|
||||
if self.square_mode then
|
||||
self.squares = self.squares + self.grid:markSquares()
|
||||
end
|
||||
|
||||
local cleared_row_count = self.grid:getClearedRowCount()
|
||||
|
||||
self:onPieceLock(self.piece, cleared_row_count)
|
||||
self:updateScore(self.level, self.drop_bonus, cleared_row_count)
|
||||
|
||||
self.grid:markClearedRows()
|
||||
self.piece = nil
|
||||
if self.enable_hold then
|
||||
self.held = false
|
||||
@@ -183,7 +253,9 @@ function GameMode:update(inputs, ruleset)
|
||||
if cleared_row_count > 0 then
|
||||
playSE("erase")
|
||||
self.lcd = self:getLineClearDelay()
|
||||
self.are = self:getLineARE()
|
||||
self.are = (
|
||||
ruleset.are and self:getLineARE() or 0
|
||||
)
|
||||
if self.lcd == 0 then
|
||||
self.grid:clearClearedRows()
|
||||
if self.are == 0 then
|
||||
@@ -192,7 +264,7 @@ function GameMode:update(inputs, ruleset)
|
||||
end
|
||||
self:onLineClear(cleared_row_count)
|
||||
else
|
||||
if self:getARE() == 0 then
|
||||
if self:getARE() == 0 or not ruleset.are then
|
||||
self:initializeOrHold(inputs, ruleset)
|
||||
else
|
||||
self.are = self:getARE()
|
||||
@@ -236,6 +308,15 @@ end
|
||||
|
||||
function GameMode:onGameOver()
|
||||
switchBGM(nil)
|
||||
love.graphics.setColor(0, 0, 0, 1 - 2 ^ (-self.game_over_frames / 30))
|
||||
love.graphics.rectangle(
|
||||
"fill", 64, 80,
|
||||
16 * self.grid.width, 16 * (self.grid.height - 4)
|
||||
)
|
||||
end
|
||||
|
||||
function GameMode:onGameComplete()
|
||||
self:onGameOver()
|
||||
end
|
||||
|
||||
-- DAS functions
|
||||
@@ -301,12 +382,27 @@ function GameMode:chargeDAS(inputs)
|
||||
end
|
||||
end
|
||||
|
||||
function GameMode:areCancel(inputs, ruleset)
|
||||
if ruleset.are_cancel and self.piece_hard_dropped and
|
||||
not self.prev_inputs.up and
|
||||
strTrueValues(inputs) ~= "" then
|
||||
self.lcd = 0
|
||||
self.are = 0
|
||||
end
|
||||
end
|
||||
|
||||
function GameMode:processDelays(inputs, ruleset, drop_speed)
|
||||
if self.ready_frames == 100 then
|
||||
playedReadySE = false
|
||||
playedGoSE = false
|
||||
end
|
||||
if self.ready_frames > 0 then
|
||||
if not self.prev_inputs["up"] and inputs["up"] and self.enable_hard_drop then
|
||||
self.buffer_hard_drop = true
|
||||
end
|
||||
if not self.prev_inputs["down"] and inputs["down"] then
|
||||
self.buffer_soft_drop = true
|
||||
end
|
||||
if not playedReadySE then
|
||||
playedReadySE = true
|
||||
playSEOnce("ready")
|
||||
@@ -320,19 +416,14 @@ function GameMode:processDelays(inputs, ruleset, drop_speed)
|
||||
self:initializeOrHold(inputs, ruleset)
|
||||
end
|
||||
elseif self.lcd > 0 then
|
||||
self.lcd = self.lcd - 1
|
||||
if ruleset.are_cancel and
|
||||
(self.move == "none" and not self.prev_inputs["up"] and
|
||||
not self.prev_inputs["rotate_left"] and not self.prev_inputs["rotate_left2"] and
|
||||
not self.prev_inputs["rotate_right"] and not self.prev_inputs["rotate_right2"] and
|
||||
not self.prev_inputs["rotate_180"]) and
|
||||
(inputs["left"] or inputs["right"] or inputs["up"] or
|
||||
inputs["rotate_left"] or inputs["rotate_left2"] or
|
||||
inputs["rotate_right"] or inputs["rotate_right2"] or
|
||||
inputs["rotate_180"]) then
|
||||
self.lcd = 0
|
||||
self.are = 0
|
||||
if not self.prev_inputs["up"] and inputs["up"] and self.enable_hard_drop then
|
||||
self.buffer_hard_drop = true
|
||||
end
|
||||
if not self.prev_inputs["down"] and inputs["down"] then
|
||||
self.buffer_soft_drop = true
|
||||
end
|
||||
self.lcd = self.lcd - 1
|
||||
self:areCancel(inputs, ruleset)
|
||||
if self.lcd == 0 then
|
||||
self.grid:clearClearedRows()
|
||||
playSE("fall")
|
||||
@@ -341,18 +432,14 @@ function GameMode:processDelays(inputs, ruleset, drop_speed)
|
||||
end
|
||||
end
|
||||
elseif self.are > 0 then
|
||||
self.are = self.are - 1
|
||||
if ruleset.are_cancel and
|
||||
(self.move == "none" and not self.prev_inputs["up"] and
|
||||
not self.prev_inputs["rotate_left"] and not self.prev_inputs["rotate_left2"] and
|
||||
not self.prev_inputs["rotate_right"] and not self.prev_inputs["rotate_right2"] and
|
||||
not self.prev_inputs["rotate_180"]) and
|
||||
(inputs["left"] or inputs["right"] or inputs["up"] or
|
||||
inputs["rotate_left"] or inputs["rotate_left2"] or
|
||||
inputs["rotate_right"] or inputs["rotate_right2"] or
|
||||
inputs["rotate_180"]) then
|
||||
self.are = 0
|
||||
if not self.prev_inputs["up"] and inputs["up"] and self.enable_hard_drop then
|
||||
self.buffer_hard_drop = true
|
||||
end
|
||||
if not self.prev_inputs["down"] and inputs["down"] then
|
||||
self.buffer_soft_drop = true
|
||||
end
|
||||
self.are = self.are - 1
|
||||
self:areCancel(inputs, ruleset)
|
||||
if self.are == 0 then
|
||||
self:initializeOrHold(inputs, ruleset)
|
||||
end
|
||||
@@ -360,14 +447,15 @@ function GameMode:processDelays(inputs, ruleset, drop_speed)
|
||||
end
|
||||
|
||||
function GameMode:initializeOrHold(inputs, ruleset)
|
||||
if self.ihs and self.enable_hold and inputs["hold"] == true then
|
||||
if (
|
||||
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
|
||||
self:initializeNextPiece(inputs, ruleset, self.next_queue[1])
|
||||
end
|
||||
self:onPieceEnter()
|
||||
if not self.grid:canPlacePiece(self.piece) then
|
||||
self:onGameOver()
|
||||
self.game_over = true
|
||||
end
|
||||
end
|
||||
@@ -397,14 +485,34 @@ function GameMode:hold(inputs, ruleset, ihs)
|
||||
end
|
||||
|
||||
function GameMode:initializeNextPiece(inputs, ruleset, piece_data, generate_next_piece)
|
||||
self.piece_hard_dropped = false
|
||||
local gravity = self:getGravity()
|
||||
self.piece = ruleset:initializePiece(
|
||||
inputs, piece_data, self.grid, gravity,
|
||||
self.prev_inputs, self.move,
|
||||
self:getLockDelay(), self:getDropSpeed(),
|
||||
self.lock_drop, self.lock_hard_drop, self.big_mode,
|
||||
self.irs
|
||||
(
|
||||
self.frames == 0 or (ruleset.are and self:getARE() ~= 0)
|
||||
) 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.piece:isDropBlocked(self.grid) and
|
||||
self.grid:canPlacePiece(self.piece) then
|
||||
playSE("bottom")
|
||||
end
|
||||
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
|
||||
@@ -415,11 +523,11 @@ function GameMode:initializeNextPiece(inputs, ruleset, piece_data, generate_next
|
||||
table.remove(self.next_queue, 1)
|
||||
table.insert(self.next_queue, self:getNextPiece(ruleset))
|
||||
end
|
||||
if config.gamesettings.next_se == 1 then self:playNextSound() end
|
||||
self:playNextSound(ruleset)
|
||||
end
|
||||
|
||||
function GameMode:playNextSound()
|
||||
playSE("blocks", self.next_queue[1].shape)
|
||||
function GameMode:playNextSound(ruleset)
|
||||
playSE("blocks", ruleset.next_sounds[self.next_queue[1].shape])
|
||||
end
|
||||
|
||||
function GameMode:getHighScoreData()
|
||||
@@ -451,8 +559,8 @@ function GameMode:drawNextQueue(ruleset)
|
||||
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.spawn_positions[piece].x
|
||||
local y = offset.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
|
||||
@@ -468,8 +576,7 @@ function GameMode:drawNextQueue(ruleset)
|
||||
end
|
||||
end
|
||||
if self.hold_queue ~= nil and self.enable_hold then
|
||||
local hold_color = self.held and 0.6 or 1
|
||||
self:setHoldOpacity(1, hold_color)
|
||||
self:setHoldOpacity()
|
||||
drawPiece(
|
||||
self.hold_queue.shape,
|
||||
self.hold_queue.skin,
|
||||
@@ -480,15 +587,25 @@ function GameMode:drawNextQueue(ruleset)
|
||||
return false
|
||||
end
|
||||
|
||||
function GameMode:setNextOpacity(i, j)
|
||||
i = i ~= nil and i or 1
|
||||
j = j ~= nil and j or 1
|
||||
love.graphics.setColor(j, j, j, i)
|
||||
function GameMode:setNextOpacity(i)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
end
|
||||
function GameMode:setHoldOpacity(i, j)
|
||||
i = i ~= nil and i or 1
|
||||
j = j ~= nil and j or 1
|
||||
love.graphics.setColor(j, j, j, i)
|
||||
|
||||
function GameMode:setHoldOpacity()
|
||||
local colour = self.held and 0.6 or 1
|
||||
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()
|
||||
@@ -528,7 +645,8 @@ function GameMode:sectionColourFunction(section)
|
||||
return { 1, 1, 1, 1 }
|
||||
end
|
||||
|
||||
function GameMode:drawSectionTimesWithSecondary(current_section)
|
||||
function GameMode:drawSectionTimesWithSecondary(current_section, section_limit)
|
||||
section_limit = section_limit or math.huge
|
||||
local section_x = 530
|
||||
local section_secondary_x = 440
|
||||
|
||||
@@ -553,7 +671,9 @@ function GameMode:drawSectionTimesWithSecondary(current_section)
|
||||
current_x = section_secondary_x
|
||||
end
|
||||
|
||||
love.graphics.printf(formatTime(self.frames - self.section_start_time), current_x, 40 + 20 * current_section, 90, "left")
|
||||
if current_section <= section_limit then
|
||||
love.graphics.printf(formatTime(self.frames - self.section_start_time), current_x, 40 + 20 * current_section, 90, "left")
|
||||
end
|
||||
end
|
||||
|
||||
function GameMode:drawSectionTimesWithSplits(current_section)
|
||||
|
||||
@@ -256,8 +256,10 @@ end
|
||||
local function getSectionForLevel(level)
|
||||
if level < 2001 then
|
||||
return math.floor(level / 100) + 1
|
||||
else
|
||||
elseif level < 2020 then
|
||||
return 20
|
||||
else
|
||||
return 21
|
||||
end
|
||||
end
|
||||
|
||||
@@ -442,7 +444,7 @@ function Marathon2020Game:drawScoringInfo()
|
||||
love.graphics.printf("GRADE PTS.", text_x, 200, 90, "left")
|
||||
love.graphics.printf("LEVEL", text_x, 320, 40, "left")
|
||||
|
||||
self:drawSectionTimesWithSecondary(current_section)
|
||||
self:drawSectionTimesWithSecondary(current_section, 20)
|
||||
|
||||
if (self.cool_timer > 0) then
|
||||
love.graphics.printf("COOL!!", 64, 400, 160, "center")
|
||||
|
||||
@@ -2,6 +2,7 @@ require 'funcs'
|
||||
|
||||
local GameMode = require 'tetris.modes.gamemode'
|
||||
local Piece = require 'tetris.components.piece'
|
||||
local Grid = require 'tetris.components.grid'
|
||||
|
||||
local History4RollsRandomizer = require 'tetris.randomizers.history_4rolls'
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ function MarathonA2Game:new()
|
||||
self.section_start_time = 0
|
||||
self.section_times = { [0] = 0 }
|
||||
self.section_tetrises = { [0] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
self.tetris_count = 0
|
||||
|
||||
self.SGnames = {
|
||||
"9", "8", "7", "6", "5", "4", "3", "2", "1",
|
||||
@@ -130,6 +131,9 @@ end
|
||||
function MarathonA2Game:updateScore(level, drop_bonus, cleared_lines)
|
||||
if not self.clear then
|
||||
self:updateGrade(cleared_lines)
|
||||
if cleared_lines >= 4 then
|
||||
self.tetris_count = self.tetris_count + 1
|
||||
end
|
||||
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
|
||||
@@ -145,6 +149,7 @@ function MarathonA2Game:updateScore(level, drop_bonus, cleared_lines)
|
||||
end
|
||||
|
||||
function MarathonA2Game:onLineClear(cleared_row_count)
|
||||
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
|
||||
@@ -164,6 +169,9 @@ function MarathonA2Game:updateSectionTimes(old_level, new_level)
|
||||
section_time = self.frames - self.section_start_time
|
||||
self.section_times[math.floor(old_level / 100)] = section_time
|
||||
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
|
||||
|
||||
@@ -231,14 +239,16 @@ local grade_conversion = {
|
||||
17, 18, 19
|
||||
}
|
||||
|
||||
function MarathonA2Game:whilePieceActive()
|
||||
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
|
||||
end
|
||||
|
||||
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
|
||||
if self.clear or cleared_lines == 0 then return
|
||||
else
|
||||
self.grade_points = self.grade_points + (
|
||||
math.ceil(
|
||||
|
||||
@@ -27,7 +27,7 @@ function MarathonA3Game:new()
|
||||
self.section_cool_grade = 0
|
||||
self.section_status = { [0] = "none" }
|
||||
self.section_start_time = 0
|
||||
self.section_70_times = { [0] = 0 }
|
||||
self.secondary_section_times = { [0] = 0 }
|
||||
self.section_times = { [0] = 0 }
|
||||
self.section_cool = false
|
||||
|
||||
@@ -163,8 +163,8 @@ function MarathonA3Game:onLineClear(cleared_row_count)
|
||||
self:updateSectionTimes(self.level, self.level + advanced_levels)
|
||||
if not self.clear then
|
||||
self.level = math.min(self.level + advanced_levels, 999)
|
||||
self.speed_level = self.speed_level + advanced_levels
|
||||
end
|
||||
self.speed_level = self.speed_level + advanced_levels
|
||||
if self.level == 999 and not self.clear then
|
||||
self.clear = true
|
||||
self.grid:clear()
|
||||
@@ -194,7 +194,7 @@ function MarathonA3Game:updateSectionTimes(old_level, new_level)
|
||||
-- record new section
|
||||
section_time = self.frames - self.section_start_time
|
||||
table.insert(self.section_times, section_time)
|
||||
self.section_start_time = self.frames
|
||||
if new_level < 999 then self.section_start_time = self.frames end
|
||||
|
||||
self.speed_level = self.section_cool and self.speed_level + 100 or self.speed_level
|
||||
|
||||
@@ -214,15 +214,15 @@ function MarathonA3Game:updateSectionTimes(old_level, new_level)
|
||||
elseif old_level % 100 < 70 and new_level % 100 >= 70 then
|
||||
-- record section 70 time
|
||||
section_70_time = self.frames - self.section_start_time
|
||||
table.insert(self.section_70_times, section_70_time)
|
||||
table.insert(self.secondary_section_times, section_70_time)
|
||||
|
||||
if section <= 9 and self.section_status[section - 1] == "cool" and
|
||||
self.section_70_times[section] < self.section_70_times[section - 1] + 120 then
|
||||
self.secondary_section_times[section] < self.secondary_section_times[section - 1] + 120 then
|
||||
self.section_cool = true
|
||||
self.coolregret_message = "COOL!!"
|
||||
self.coolregret_timer = 300
|
||||
elseif self.section_status[section - 1] == "cool" then self.section_cool = false
|
||||
elseif section <= 9 and self.section_70_times[section] < cool_cutoffs[section] then
|
||||
elseif section <= 9 and self.secondary_section_times[section] < cool_cutoffs[section] then
|
||||
self.section_cool = true
|
||||
self.coolregret_message = "COOL!!"
|
||||
self.coolregret_timer = 300
|
||||
@@ -313,13 +313,16 @@ local grade_conversion = {
|
||||
17
|
||||
}
|
||||
|
||||
function MarathonA3Game:whilePieceActive()
|
||||
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
|
||||
end
|
||||
|
||||
function MarathonA3Game:updateGrade(cleared_lines)
|
||||
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
|
||||
if cleared_lines == 0 then return
|
||||
else
|
||||
if self.clear then
|
||||
if self:qualifiesForMRoll() then
|
||||
@@ -395,6 +398,16 @@ MarathonA3Game.mRollOpacityFunction = function(age)
|
||||
else return 1 - age / 4 end
|
||||
end
|
||||
|
||||
function MarathonA3Game:sectionColourFunction(section)
|
||||
if self.section_status[section] == "cool" then
|
||||
return { 0, 1, 0, 1 }
|
||||
elseif self.section_status[section] == "regret" then
|
||||
return { 1, 0, 0, 1 }
|
||||
else
|
||||
return { 1, 1, 1, 1 }
|
||||
end
|
||||
end
|
||||
|
||||
function MarathonA3Game:drawScoringInfo()
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
|
||||
@@ -414,7 +427,9 @@ function MarathonA3Game:drawScoringInfo()
|
||||
end
|
||||
|
||||
-- draw section time data
|
||||
current_section = math.floor(self.level / 100) + 1
|
||||
current_section = self.level >= 999 and 11 or math.floor(self.level / 100) + 1
|
||||
self:drawSectionTimesWithSecondary(current_section, 10)
|
||||
--[[
|
||||
|
||||
section_x = 530
|
||||
section_70_x = 440
|
||||
@@ -425,21 +440,22 @@ function MarathonA3Game:drawScoringInfo()
|
||||
end
|
||||
end
|
||||
|
||||
for section, time in pairs(self.section_70_times) do
|
||||
for section, time in pairs(self.secondary_section_times) do
|
||||
if section > 0 then
|
||||
love.graphics.printf(formatTime(time), section_70_x, 40 + 20 * section, 90, "left")
|
||||
end
|
||||
end
|
||||
|
||||
local current_x
|
||||
if table.getn(self.section_times) < table.getn(self.section_70_times) then
|
||||
if table.getn(self.section_times) < table.getn(self.secondary_section_times) then
|
||||
current_x = section_x
|
||||
else
|
||||
current_x = section_70_x
|
||||
end
|
||||
|
||||
if not self.clear then love.graphics.printf(formatTime(self.frames - self.section_start_time), current_x, 40 + 20 * current_section, 90, "left") end
|
||||
|
||||
]]--
|
||||
|
||||
if(self.coolregret_timer > 0) then
|
||||
love.graphics.printf(self.coolregret_message, 64, 400, 160, "center")
|
||||
self.coolregret_timer = self.coolregret_timer - 1
|
||||
@@ -475,7 +491,7 @@ function MarathonA3Game:getSectionEndLevel()
|
||||
end
|
||||
|
||||
function MarathonA3Game:getBackground()
|
||||
return math.floor(self.level / 100)
|
||||
return math.floor(self.speed_level / 100)
|
||||
end
|
||||
|
||||
return MarathonA3Game
|
||||
|
||||
@@ -86,12 +86,8 @@ function PhantomMania2Game:getGarbageLimit()
|
||||
else return 8 end
|
||||
end
|
||||
|
||||
function PhantomMania2Game:getNextPiece(ruleset)
|
||||
return {
|
||||
skin = self.level >= 1000 and "bone" or "2tie",
|
||||
shape = self.randomizer:nextPiece(),
|
||||
orientation = ruleset:getDefaultOrientation(),
|
||||
}
|
||||
function PhantomMania2Game:getSkin()
|
||||
return self.level >= 1000 and "bone" or "2tie"
|
||||
end
|
||||
|
||||
function PhantomMania2Game:hitTorikan(old_level, new_level)
|
||||
|
||||
585
tetris/modes/sakura.lua
Normal file
@@ -0,0 +1,585 @@
|
||||
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
|
||||
|
||||
function SakuraGame:onGameComplete() 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()
|
||||
end
|
||||
if self.piece ~= nil and self.level < 100 then
|
||||
self:drawGhostPiece()
|
||||
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
|
||||
@@ -92,12 +92,8 @@ function SurvivalA3Game:getGarbageLimit()
|
||||
else return 8 end
|
||||
end
|
||||
|
||||
function SurvivalA3Game:getNextPiece(ruleset)
|
||||
return {
|
||||
skin = self.level >= 1000 and "bone" or "2tie",
|
||||
shape = self.randomizer:nextPiece(),
|
||||
orientation = ruleset:getDefaultOrientation(),
|
||||
}
|
||||
function SurvivalA3Game:getSkin()
|
||||
return self.level >= 1000 and "bone" or "2tie"
|
||||
end
|
||||
|
||||
function SurvivalA3Game:hitTorikan(old_level, new_level)
|
||||
|
||||
@@ -5,15 +5,15 @@ local Piece = require 'tetris.components.piece'
|
||||
|
||||
local Bag7NoSZOStartRandomizer = require 'tetris.randomizers.bag7noSZOstart'
|
||||
|
||||
local MarathonAX4Game = GameMode:extend()
|
||||
local SurvivalAXGame = GameMode:extend()
|
||||
|
||||
MarathonAX4Game.name = "Marathon AX4"
|
||||
MarathonAX4Game.hash = "MarathonAX4"
|
||||
MarathonAX4Game.tagline = "Can you clear the time hurdles when the game goes this fast?"
|
||||
SurvivalAXGame.name = "Survival AX"
|
||||
SurvivalAXGame.hash = "SurvivalAX"
|
||||
SurvivalAXGame.tagline = "Can you clear the time hurdles when the game goes this fast?"
|
||||
|
||||
|
||||
function MarathonAX4Game:new()
|
||||
MarathonAX4Game.super:new()
|
||||
function SurvivalAXGame:new()
|
||||
SurvivalAXGame.super:new()
|
||||
|
||||
self.roll_frames = 0
|
||||
self.randomizer = Bag7NoSZOStartRandomizer()
|
||||
@@ -29,7 +29,7 @@ function MarathonAX4Game:new()
|
||||
self.next_queue_length = 3
|
||||
end
|
||||
|
||||
function MarathonAX4Game:getARE()
|
||||
function SurvivalAXGame:getARE()
|
||||
if self.lines < 10 then return 18
|
||||
elseif self.lines < 40 then return 14
|
||||
elseif self.lines < 60 then return 12
|
||||
@@ -39,24 +39,24 @@ function MarathonAX4Game:getARE()
|
||||
else return 6 end
|
||||
end
|
||||
|
||||
function MarathonAX4Game:getLineARE()
|
||||
function SurvivalAXGame:getLineARE()
|
||||
return self:getARE()
|
||||
end
|
||||
|
||||
function MarathonAX4Game:getDasLimit()
|
||||
function SurvivalAXGame: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 MarathonAX4Game:getLineClearDelay()
|
||||
function SurvivalAXGame:getLineClearDelay()
|
||||
if self.lines < 10 then return 14
|
||||
elseif self.lines < 30 then return 9
|
||||
else return 5 end
|
||||
end
|
||||
|
||||
function MarathonAX4Game:getLockDelay()
|
||||
function SurvivalAXGame:getLockDelay()
|
||||
if self.lines < 10 then return 28
|
||||
elseif self.lines < 20 then return 24
|
||||
elseif self.lines < 30 then return 22
|
||||
@@ -66,15 +66,15 @@ function MarathonAX4Game:getLockDelay()
|
||||
else return 13 end
|
||||
end
|
||||
|
||||
function MarathonAX4Game:getGravity()
|
||||
function SurvivalAXGame:getGravity()
|
||||
return 20
|
||||
end
|
||||
|
||||
function MarathonAX4Game:getSection()
|
||||
function SurvivalAXGame:getSection()
|
||||
return math.floor(level / 100) + 1
|
||||
end
|
||||
|
||||
function MarathonAX4Game:advanceOneFrame()
|
||||
function SurvivalAXGame:advanceOneFrame()
|
||||
if self.clear then
|
||||
self.roll_frames = self.roll_frames + 1
|
||||
if self.roll_frames < 0 then
|
||||
@@ -93,7 +93,7 @@ function MarathonAX4Game:advanceOneFrame()
|
||||
return true
|
||||
end
|
||||
|
||||
function MarathonAX4Game:onLineClear(cleared_row_count)
|
||||
function SurvivalAXGame:onLineClear(cleared_row_count)
|
||||
if not self.clear then
|
||||
local new_lines = self.lines + cleared_row_count
|
||||
self:updateSectionTimes(self.lines, new_lines)
|
||||
@@ -106,11 +106,11 @@ function MarathonAX4Game:onLineClear(cleared_row_count)
|
||||
end
|
||||
end
|
||||
|
||||
function MarathonAX4Game:getSectionTime()
|
||||
function SurvivalAXGame:getSectionTime()
|
||||
return self.frames - self.section_start_time
|
||||
end
|
||||
|
||||
function MarathonAX4Game:updateSectionTimes(old_lines, new_lines)
|
||||
function SurvivalAXGame: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())
|
||||
@@ -119,23 +119,23 @@ function MarathonAX4Game:updateSectionTimes(old_lines, new_lines)
|
||||
end
|
||||
end
|
||||
|
||||
function MarathonAX4Game:onPieceEnter()
|
||||
function SurvivalAXGame:onPieceEnter()
|
||||
self.section_clear = false
|
||||
end
|
||||
|
||||
function MarathonAX4Game:drawGrid(ruleset)
|
||||
function SurvivalAXGame:drawGrid(ruleset)
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
function MarathonAX4Game:getHighscoreData()
|
||||
function SurvivalAXGame:getHighscoreData()
|
||||
return {
|
||||
lines = self.lines,
|
||||
frames = self.frames,
|
||||
}
|
||||
end
|
||||
|
||||
function MarathonAX4Game:drawScoringInfo()
|
||||
MarathonAX4Game.super.drawScoringInfo(self)
|
||||
function SurvivalAXGame:drawScoringInfo()
|
||||
SurvivalAXGame.super.drawScoringInfo(self)
|
||||
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
|
||||
@@ -165,12 +165,12 @@ function MarathonAX4Game:drawScoringInfo()
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
end
|
||||
|
||||
function MarathonAX4Game:getSectionEndLines()
|
||||
function SurvivalAXGame:getSectionEndLines()
|
||||
return math.floor(self.lines / 10 + 1) * 10
|
||||
end
|
||||
|
||||
function MarathonAX4Game:getBackground()
|
||||
function SurvivalAXGame:getBackground()
|
||||
return math.floor(self.lines / 10)
|
||||
end
|
||||
|
||||
return MarathonAX4Game
|
||||
return SurvivalAXGame
|
||||
23
tetris/randomizers/bag.lua
Normal file
@@ -0,0 +1,23 @@
|
||||
local Randomizer = require 'tetris.randomizers.randomizer'
|
||||
|
||||
local BagRandomizer = Randomizer:extend()
|
||||
|
||||
function BagRandomizer:new(pieces)
|
||||
self.bag = {}
|
||||
self.pieces = pieces
|
||||
for i = 1, self.pieces do
|
||||
table.insert(self.bag, i)
|
||||
end
|
||||
end
|
||||
|
||||
function BagRandomizer:generatePiece()
|
||||
if next(self.bag) == nil then
|
||||
for i = 1, self.pieces do
|
||||
table.insert(self.bag, i)
|
||||
end
|
||||
end
|
||||
local x = math.random(table.getn(self.bag))
|
||||
return table.remove(self.bag, x)
|
||||
end
|
||||
|
||||
return BagRandomizer
|
||||
16
tetris/randomizers/fixed_sequence.lua
Normal file
@@ -0,0 +1,16 @@
|
||||
local Randomizer = require 'tetris.randomizers.randomizer'
|
||||
|
||||
local Sequence = Randomizer:extend()
|
||||
|
||||
function Sequence:initialize()
|
||||
self.sequence = "IJLOT"
|
||||
self.counter = 0
|
||||
end
|
||||
|
||||
function Sequence:generatePiece()
|
||||
local piece = string.sub(self.sequence, self.counter + 1, self.counter + 1)
|
||||
self.counter = (self.counter + 1) % string.len(self.sequence)
|
||||
return piece
|
||||
end
|
||||
|
||||
return Sequence
|
||||
@@ -3,6 +3,7 @@ local Object = require 'libs.classic'
|
||||
local Randomizer = Object:extend()
|
||||
|
||||
function Randomizer:new()
|
||||
self.possible_pieces = 7
|
||||
self:initialize()
|
||||
end
|
||||
|
||||
|
||||
10
tetris/randomizers/sakura.lua
Normal file
@@ -0,0 +1,10 @@
|
||||
local Sequence = require 'tetris.randomizers.fixed_sequence'
|
||||
|
||||
local Sakura = Sequence:extend()
|
||||
|
||||
function Sakura:initialize()
|
||||
self.super:initialize()
|
||||
self.sequence = "LIJOTSZILJOTISJZLOIJSZTIOJZTLSOZTISOLTJSIZTOJLIZSTOIZLTJOSILTZSOITJLZSTJJISOLJITSLZOIZSJOITSZLJTSZLISTJLZOTIOZSJILTZSOITZJSOLTJSZIOJLZIOJTZIZLOSIZTJOILZSOJIOSZTJILOSSILZOTJIZTSOLZTSOIJTZSILTZOSIJZTOLJISOLJTZSOLTZJSOTILZJTOLZIJSOZTJLOZSTLOZITSOLZTJIOSLZJTO"
|
||||
end
|
||||
|
||||
return Sakura
|
||||
@@ -110,13 +110,7 @@ function ARS:onPieceDrop(piece, grid)
|
||||
piece.lock_delay = 0 -- step reset
|
||||
end
|
||||
|
||||
function ARS:get180RotationValue()
|
||||
if config.gamesettings.world_reverse == 3 then
|
||||
return 1
|
||||
else
|
||||
return 3
|
||||
end
|
||||
end
|
||||
function ARS:get180RotationValue() return 3 end
|
||||
|
||||
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
|
||||
|
||||
|
||||
@@ -19,26 +19,6 @@ ARS.colourscheme = {
|
||||
ARS.softdrop_lock = false
|
||||
ARS.harddrop_lock = true
|
||||
|
||||
ARS.spawn_positions = {
|
||||
I = { x=5, y=2 },
|
||||
J = { x=4, y=3 },
|
||||
L = { x=4, y=3 },
|
||||
O = { x=5, y=3 },
|
||||
S = { x=4, y=3 },
|
||||
T = { x=4, y=3 },
|
||||
Z = { x=4, y=3 },
|
||||
}
|
||||
|
||||
ARS.big_spawn_positions = {
|
||||
I = { x=3, y=0 },
|
||||
J = { x=2, y=1 },
|
||||
L = { x=2, y=1 },
|
||||
O = { x=3, y=1 },
|
||||
S = { x=2, y=1 },
|
||||
T = { x=2, y=1 },
|
||||
Z = { x=2, y=1 },
|
||||
}
|
||||
|
||||
function ARS:onPieceCreate(piece, grid)
|
||||
piece.floorkick = 0
|
||||
piece.manipulations = 0
|
||||
@@ -49,6 +29,7 @@ function ARS:onPieceMove(piece, grid)
|
||||
if piece:isDropBlocked(grid) then
|
||||
piece.manipulations = piece.manipulations + 1
|
||||
if piece.manipulations >= 128 then
|
||||
piece:dropToBottom(grid)
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
@@ -59,6 +40,7 @@ function ARS:onPieceRotate(piece, grid)
|
||||
if piece:isDropBlocked(grid) then
|
||||
piece.manipulations = piece.manipulations + 1
|
||||
if piece.manipulations >= 128 then
|
||||
piece:dropToBottom(grid)
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
@@ -67,4 +49,8 @@ function ARS:onPieceRotate(piece, grid)
|
||||
end
|
||||
end
|
||||
|
||||
function ARS:get180RotationValue() return 3 end
|
||||
|
||||
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
|
||||
|
||||
return ARS
|
||||
|
||||
@@ -6,26 +6,6 @@ local ARS = Ruleset:extend()
|
||||
ARS.name = "ACE-ARS2"
|
||||
ARS.hash = "ArikaACE2"
|
||||
|
||||
ARS.spawn_positions = {
|
||||
I = { x=5, y=2 },
|
||||
J = { x=4, y=3 },
|
||||
L = { x=4, y=3 },
|
||||
O = { x=5, y=3 },
|
||||
S = { x=4, y=3 },
|
||||
T = { x=4, y=3 },
|
||||
Z = { x=4, y=3 },
|
||||
}
|
||||
|
||||
ARS.big_spawn_positions = {
|
||||
I = { x=3, y=0 },
|
||||
J = { x=2, y=1 },
|
||||
L = { x=2, y=1 },
|
||||
O = { x=3, y=1 },
|
||||
S = { x=2, y=1 },
|
||||
T = { x=2, y=1 },
|
||||
Z = { x=2, y=1 },
|
||||
}
|
||||
|
||||
function ARS:onPieceCreate(piece, grid)
|
||||
piece.floorkick = 0
|
||||
piece.manipulations = 0
|
||||
@@ -36,6 +16,7 @@ function ARS:onPieceMove(piece, grid)
|
||||
if piece:isDropBlocked(grid) then
|
||||
piece.manipulations = piece.manipulations + 1
|
||||
if piece.manipulations >= 128 then
|
||||
piece:dropToBottom(grid)
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
@@ -46,6 +27,7 @@ function ARS:onPieceRotate(piece, grid)
|
||||
if piece:isDropBlocked(grid) then
|
||||
piece.manipulations = piece.manipulations + 1
|
||||
if piece.manipulations >= 128 then
|
||||
piece:dropToBottom(grid)
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
@@ -54,4 +36,8 @@ function ARS:onPieceRotate(piece, grid)
|
||||
end
|
||||
end
|
||||
|
||||
function ARS:get180RotationValue() return 3 end
|
||||
|
||||
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
|
||||
|
||||
return ARS
|
||||
|
||||
@@ -4,43 +4,27 @@ local Ruleset = require 'tetris.rulesets.ti_srs'
|
||||
local SRS = Ruleset:extend()
|
||||
|
||||
SRS.name = "ACE-SRS"
|
||||
SRS.hash = "ACE Standard"
|
||||
|
||||
SRS.spawn_positions = {
|
||||
I = { x=5, y=2 },
|
||||
J = { x=4, y=3 },
|
||||
L = { x=4, y=3 },
|
||||
O = { x=5, y=3 },
|
||||
S = { x=4, y=3 },
|
||||
T = { x=4, y=3 },
|
||||
Z = { x=4, y=3 },
|
||||
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.big_spawn_positions = {
|
||||
I = { x=3, y=0 },
|
||||
J = { x=2, y=1 },
|
||||
L = { x=2, y=1 },
|
||||
O = { x=3, y=1 },
|
||||
S = { x=2, y=1 },
|
||||
T = { x=2, y=1 },
|
||||
Z = { x=2, y=1 },
|
||||
}
|
||||
|
||||
function SRS: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.locked = true
|
||||
end
|
||||
end
|
||||
end
|
||||
SRS.MANIPULATIONS_MAX = 128
|
||||
|
||||
function SRS: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.manipulations = piece.manipulations + 1
|
||||
if piece.manipulations >= self.MANIPULATIONS_MAX then
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -99,4 +99,8 @@ function ARS:onPieceRotate(piece, grid)
|
||||
end
|
||||
end
|
||||
|
||||
function ARS:get180RotationValue() return 3 end
|
||||
|
||||
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
|
||||
|
||||
return ARS
|
||||
|
||||
@@ -364,9 +364,9 @@ function CRS:attemptRotate(new_inputs, piece, grid, initial)
|
||||
|
||||
if rot_dir == 0 then return end
|
||||
|
||||
if self.world and config.gamesettings.world_reverse == 2 then
|
||||
rot_dir = 4 - rot_dir
|
||||
end
|
||||
if config.gamesettings.world_reverse == 3 or (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)
|
||||
@@ -406,6 +406,7 @@ function CRS:onPieceMove(piece, grid)
|
||||
if piece:isDropBlocked(grid) then
|
||||
piece.move_counter = piece.move_counter + 1
|
||||
if piece.move_counter >= 24 then
|
||||
piece:dropToBottom(grid)
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
@@ -415,6 +416,7 @@ function CRS:onPieceRotate(piece, grid)
|
||||
if piece:isDropBlocked(grid) then
|
||||
piece.rotate_counter = piece.rotate_counter + 1
|
||||
if piece.rotate_counter >= 12 then
|
||||
piece:dropToBottom(grid)
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
|
||||
275
tetris/rulesets/pairs.lua
Normal 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
|
||||
@@ -22,10 +22,34 @@ Ruleset.harddrop_lock = false
|
||||
|
||||
Ruleset.enable_IRS_wallkicks = false
|
||||
Ruleset.are_cancel = false
|
||||
Ruleset.are = true
|
||||
|
||||
Ruleset.next_sounds = {
|
||||
I = "I",
|
||||
L = "L",
|
||||
J = "J",
|
||||
S = "S",
|
||||
Z = "Z",
|
||||
O = "O",
|
||||
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()
|
||||
|
||||
if config.gamesettings.piece_colour == 1 then
|
||||
blocks["bone"] = (not self.world) and
|
||||
{
|
||||
@@ -88,8 +112,14 @@ function Ruleset:rotatePiece(inputs, piece, grid, prev_inputs, initial)
|
||||
end
|
||||
end
|
||||
|
||||
local was_drop_blocked = piece:isDropBlocked(grid)
|
||||
|
||||
self:attemptRotate(new_inputs, piece, grid, initial)
|
||||
|
||||
if not was_drop_blocked and piece:isDropBlocked(grid) then
|
||||
playSE("bottom")
|
||||
end
|
||||
|
||||
-- prev_inputs becomes the previous inputs
|
||||
for input, value in pairs(inputs) do
|
||||
prev_inputs[input] = inputs[input]
|
||||
@@ -115,8 +145,8 @@ function Ruleset:attemptRotate(new_inputs, piece, grid, initial)
|
||||
local new_piece = piece:withRelativeRotation(rot_dir)
|
||||
|
||||
if (grid:canPlacePiece(new_piece)) then
|
||||
self:onPieceRotate(piece, grid)
|
||||
piece:setRelativeRotation(rot_dir)
|
||||
self:onPieceRotate(piece, grid)
|
||||
else
|
||||
if not(initial and self.enable_IRS_wallkicks == false) then
|
||||
self:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
||||
@@ -130,17 +160,21 @@ end
|
||||
|
||||
function Ruleset:movePiece(piece, grid, move, instant)
|
||||
local x = piece.position.x
|
||||
local was_drop_blocked = piece:isDropBlocked(grid)
|
||||
if move == "left" then
|
||||
piece:moveInGrid({x=-1, y=0}, 1, grid, false)
|
||||
elseif move == "right" then
|
||||
piece:moveInGrid({x=1, y=0}, 1, grid, false)
|
||||
elseif move == "speedleft" then
|
||||
piece:moveInGrid({x=-1, y=0}, 10, grid, instant)
|
||||
piece:moveInGrid({x=-1, y=0}, grid.width, grid, instant)
|
||||
elseif move == "speedright" then
|
||||
piece:moveInGrid({x=1, y=0}, 10, grid, instant)
|
||||
piece:moveInGrid({x=1, y=0}, grid.width, grid, instant)
|
||||
end
|
||||
if piece.position.x ~= x then
|
||||
self:onPieceMove(piece, grid)
|
||||
if not was_drop_blocked and piece:isDropBlocked(grid) then
|
||||
playSE("bottom")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -148,6 +182,11 @@ function Ruleset:dropPiece(
|
||||
inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
|
||||
hard_drop_enabled, additive_gravity
|
||||
)
|
||||
if piece.big then
|
||||
gravity = gravity / 2
|
||||
drop_speed = drop_speed / 2
|
||||
end
|
||||
|
||||
local y = piece.position.y
|
||||
if inputs["down"] == true and drop_locked == false then
|
||||
if additive_gravity then
|
||||
@@ -181,7 +220,9 @@ function Ruleset:getDefaultOrientation() return 1 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
|
||||
@@ -191,21 +232,42 @@ function Ruleset:initializePiece(
|
||||
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 = 0, grid.width - 1 do
|
||||
if i / grid.width >= percent then
|
||||
spawn_x = i
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local spawn_dy = (
|
||||
config.gamesettings.spawn_positions == 2 and
|
||||
2 or 0
|
||||
)
|
||||
|
||||
local piece = Piece(data.shape, data.orientation - 1, {
|
||||
x = spawn_positions[data.shape].x,
|
||||
y = spawn_positions[data.shape].y
|
||||
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)
|
||||
|
||||
self:onPieceCreate(piece)
|
||||
if irs then
|
||||
if inputs.rotate_left or inputs.rotate_left2 or
|
||||
inputs.rotate_right or inputs.rotate_right2 or
|
||||
inputs.rotate_180 then
|
||||
self:rotatePiece(inputs, piece, grid, {}, true)
|
||||
if (data.orientation - 1) ~= piece.rotation then
|
||||
playSE("irs")
|
||||
end
|
||||
self:rotatePiece(inputs, piece, grid, {}, true)
|
||||
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
|
||||
|
||||
|
||||
102
tetris/rulesets/standard.lua
Normal file
@@ -0,0 +1,102 @@
|
||||
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:isDropBlocked(grid) then
|
||||
if piece.manipulations >= SRS.MANIPULATIONS_MAX then
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return SRS
|
||||
@@ -3,44 +3,37 @@ local Ruleset = require 'tetris.rulesets.arika_srs'
|
||||
|
||||
local SRS = Ruleset:extend()
|
||||
|
||||
SRS.name = "Guideline SRS"
|
||||
SRS.hash = "Standard"
|
||||
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.enable_IRS_wallkicks = true
|
||||
|
||||
function SRS:check_new_low(piece)
|
||||
SRS.MANIPULATIONS_MAX = 24
|
||||
SRS.ROTATIONS_MAX = 12
|
||||
|
||||
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.manipulations = 0
|
||||
--piece.rotations = 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)
|
||||
@@ -70,12 +63,13 @@ end
|
||||
|
||||
function SRS:onPieceCreate(piece, grid)
|
||||
piece.manipulations = 0
|
||||
piece.rotations = 0
|
||||
piece.lowest_y = -math.huge
|
||||
end
|
||||
|
||||
function SRS:onPieceDrop(piece, grid)
|
||||
self:check_new_low(piece)
|
||||
if piece.manipulations >= 15 and piece:isDropBlocked(grid) then
|
||||
self:checkNewLow(piece)
|
||||
if piece.manipulations >= self.MANIPULATIONS_MAX and piece:isDropBlocked(grid) then
|
||||
piece.locked = true
|
||||
else
|
||||
piece.lock_delay = 0 -- step reset
|
||||
@@ -86,7 +80,7 @@ function SRS:onPieceMove(piece, grid)
|
||||
piece.lock_delay = 0 -- move reset
|
||||
if piece:isDropBlocked(grid) then
|
||||
piece.manipulations = piece.manipulations + 1
|
||||
if piece.manipulations >= 15 then
|
||||
if piece.manipulations >= SRS.MANIPULATIONS_MAX then
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
@@ -94,13 +88,14 @@ end
|
||||
|
||||
function SRS:onPieceRotate(piece, grid)
|
||||
piece.lock_delay = 0 -- rotate reset
|
||||
self:check_new_low(piece)
|
||||
piece.manipulations = piece.manipulations + 1
|
||||
if piece:isDropBlocked(grid) then
|
||||
if piece.manipulations >= 15 then
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
self:checkNewLow(piece)
|
||||
if piece.rotations >= self.ROTATIONS_MAX then
|
||||
piece.rotations = piece.rotations + 1
|
||||
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
|
||||
|
||||
@@ -4,7 +4,7 @@ local Ruleset = require 'tetris.rulesets.ruleset'
|
||||
local SRS = Ruleset:extend()
|
||||
|
||||
SRS.name = "Ti-World"
|
||||
SRS.hash = "Bad I-kicks"
|
||||
SRS.hash = "StandardTI"
|
||||
SRS.world = true
|
||||
SRS.colourscheme = {
|
||||
I = "C",
|
||||
@@ -18,6 +18,9 @@ SRS.colourscheme = {
|
||||
SRS.softdrop_lock = false
|
||||
SRS.harddrop_lock = true
|
||||
|
||||
SRS.MANIPULATIONS_MAX = 10
|
||||
SRS.ROTATIONS_MAX = 8
|
||||
|
||||
SRS.spawn_positions = {
|
||||
I = { x=5, y=4 },
|
||||
J = { x=4, y=5 },
|
||||
@@ -86,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=0, y=1}, {x=0, y=-1}},
|
||||
[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}},
|
||||
[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=-1}},
|
||||
[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}},
|
||||
},
|
||||
[2]={
|
||||
[0]={{x=0, y=1}, {x=0, y=-1}},
|
||||
[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}},
|
||||
[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=-1}},
|
||||
[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}},
|
||||
[2]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
|
||||
},
|
||||
};
|
||||
@@ -109,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]={},
|
||||
[2]={{x=-1,y=0},{x=-2,y=0},{x=1,y=0},{x=2,y=0},{x=0,y=1}},
|
||||
[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]={},
|
||||
[3]={{x=0,y=1},{x=0,y=2},{x=0,y=-1},{x=0,y=-2},{x=-1,y=0}},
|
||||
},
|
||||
[2]={
|
||||
[0]={},
|
||||
[0]={{x=1,y=0},{x=2,y=0},{x=-1,y=0},{x=-2,y=0},{x=0,y=-1}},
|
||||
[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]={},
|
||||
[1]={{x=0,y=1},{x=0,y=2},{x=0,y=-1},{x=0,y=-2},{x=1,y=0}},
|
||||
[2]={{x= 1, y= 0}, {x=-2, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
|
||||
},
|
||||
};
|
||||
@@ -162,14 +165,18 @@ function SRS:onPieceCreate(piece, grid)
|
||||
end
|
||||
|
||||
function SRS:onPieceDrop(piece, grid)
|
||||
piece.lock_delay = 0 -- step reset
|
||||
if (piece.manipulations >= self.MANIPULATIONS_MAX or piece.rotations >= self.ROTATIONS_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 >= 10 then
|
||||
if piece.manipulations >= self.MANIPULATIONS_MAX then
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
@@ -178,19 +185,13 @@ end
|
||||
function SRS:onPieceRotate(piece, grid)
|
||||
piece.lock_delay = 0 -- rotate reset
|
||||
if piece:isDropBlocked(grid) then
|
||||
piece.rotations = piece.rotations + 1
|
||||
if piece.rotations >= 8 then
|
||||
piece.rotations = piece.rotations + 1
|
||||
if piece.rotations >= self.ROTATIONS_MAX then
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||