Compare commits

..

12 Commits

Author SHA1 Message Date
Joe Z
2b9740768b How did that get away? 2019-07-07 23:14:17 -04:00
Joe Z
0e65b39395 BGM kept looping after exiting credits_a3, which is not what we want. 2019-07-07 19:38:46 -04:00
Joe Z
ca04333bb7 Lowered the volume of Credits A3 by a factor of 10. 2019-07-07 19:34:07 -04:00
Joe Zeng
80c7c99bd7 Added Credits A3 mode. (#26)
Also updated the documentation.
2019-07-07 18:55:03 -04:00
Joe Z
717afbebf6 Added Marathon WCB. 2019-07-07 18:06:13 -04:00
Joe Z
7227085f84 Made instant DAS respect instant gravity. 2019-07-07 17:25:35 -04:00
Joe Zeng
c40392f00f DAS priority reversal (#25)
* Reversed the priority of key presses when charging DAS.
* Made it an actual config option.
* Config should be false by default.
2019-07-07 17:23:17 -04:00
Joe Z
001c8f0ea8 Fixed some issues with Marathon A1. 2019-07-07 12:11:28 -04:00
Joe Z
7fa0e60145 Made the menu scroll up and down when it overflows. 2019-06-29 15:09:48 -04:00
Joe Z
901f7f2d12 Fixed a bug in the "actual cleared row count" for Big Mode. 2019-06-22 00:07:10 -04:00
Joe Z
35f4aea67d Added Ti-SRS and modified delay curve behaviour on Marathon 2020.
I realized that playing at 4/8 for 800 levels straight is probably too much,
so I made it that only the first 10 sections count for advancing the delay
curve faster than it would normally go. Now only the last 500 levels can be
at delay level 20.
2019-06-21 23:44:58 -04:00
Joe Z
7deaa5ab92 Added all the Marathon AX modes and Ace-ARS. 2019-06-19 22:56:33 -04:00
77 changed files with 1132 additions and 2865 deletions

View File

@@ -1,26 +1,8 @@
![Cambridge Banner](https://cdn.discordapp.com/attachments/764432435802013709/767724895076614154/cambridge_logo_lt.png)
Cambridge Cambridge
========= =========
Welcome to Cambridge, the next open-source falling-block game engine! Welcome to Cambridge, the next open-source falling-block game engine!
This fork is written and maintained exclusively by [SashLilac](https://github.com/SashLilac) and [Oshisaure](https://github.com/oshisaure)!
Join our Discord server for help and a welcoming community! https://discord.gg/mteMJw4
Credits
-------
- [Lilla Oshisaure](https://www.youtube.com/user/LeSpyroshisaure) for their amazing contributions to my life in general!
- [The Tetra Legends Discord](http://discord.com/invite/7hMx5r2) for supporting me and playtesting!
- [joezeng](https://github.com/joezeng) for the original project.
- [Hailey](https://github.com/haileylgbt) for some miscellaneous assets.
- MarkGamed7794 for some miscellaneous contributions.
- Mizu for the Cambridge logo and the [Cambridge launcher](https://github.com/rexxt/cambridge-launcher).
- MattMayuga for the Cambridge banner.
![Cambridge Logo](https://cdn.discordapp.com/attachments/625496179433668635/763363717730664458/Icon_2.png)
Installation instructions Installation instructions
------------------------- -------------------------
@@ -33,7 +15,7 @@ Unzip the exe file and run it directly. All assets are currently bundled inside
### macOS ### macOS
For the time being, the file `cambridge.love` only works on the command line. Install `love` with [Homebrew](https://brew.sh), and run: For the time being, the file `cambridge.love` only works on the command line. Install `love` with [https://brew.sh/](Homebrew), and run:
$ love cambridge.love $ love cambridge.love
@@ -53,7 +35,7 @@ If you haven't already, install `love` with your favourite package manager (Home
Clone the repository in git: Clone the repository in git:
git clone https://github.com/SashLilac/cambridge git clone https://github.com/joezeng/cambridge
Then, navigate to the root directory that you just cloned, and type: Then, navigate to the root directory that you just cloned, and type:

View File

@@ -15,7 +15,9 @@ There are several classes of game modes. The modes that originate from other gam
* The "G" series stand for "Guideline" games, or games that follow the Tetris Guideline. * The "G" series stand for "Guideline" games, or games that follow the Tetris Guideline.
* GF - Tetris Friends (2007-2019) * GF - Tetris Friends (2007-2019)
* GJ - Tetris Online Japan (2005-2011) * GJ - Tetris Online Japan (2005-2011)
* N stands for Nullpomino, only used for Phantom Mania N. * The "N" series stands for Nullpomino, only used for Phantom Mania N.
* The "W" series stands for "Web" games, which are fanmade games released on the web.
* WCB - RainComplex.net's Cat Boi Quatro.
MARATHON MARATHON
-------- --------
@@ -23,12 +25,15 @@ MARATHON
Modes in which the goal is to play as well as possible over a limited game interval. Modes in which the goal is to play as well as possible over a limited game interval.
* **MARATHON 2020**: 2020 levels of pure pain. Can you make it all the way? * **MARATHON 2020**: 2020 levels of pure pain. Can you make it all the way?
* **MARATHON WCB**: CatBoiQuatro! Can you control the pieces?
From other games: From other games:
* **MARATHON A1**: Tetris the Grand Master 1. * **MARATHON A1**: Tetris the Grand Master 1.
* **MARATHON A2**: Tetris the Grand Master 2 (TAP Master). * **MARATHON A2**: Tetris the Grand Master 2 (TAP Master).
* **MARATHON A3**: Tetris the Grand Master 3 (no exams). * **MARATHON A3**: Tetris the Grand Master 3 (no exams).
* **MARATHON AX4**: Another mode from TGM Ace. * **MARATHON AX**: Normal mode from TGM Ace.
* **MARATHON AX2**: Hi-Speed1 mode from TGM Ace.
* **MARATHON AX3**: Hi-Speed2 mode from TGM Ace.
* **MARATHON C89**: Nintendo NES Tetris. Can you transition and make it to the killscreen? * **MARATHON C89**: Nintendo NES Tetris. Can you transition and make it to the killscreen?
@@ -43,6 +48,8 @@ From other games:
* **SURVIVAL A1**: 20G mode from Tetris the Grand Master. * **SURVIVAL A1**: 20G mode from Tetris the Grand Master.
* **SURVIVAL A2**: T.A. Death. * **SURVIVAL A2**: T.A. Death.
* **SURVIVAL A3**: Ti Shirase. * **SURVIVAL A3**: Ti Shirase.
* **SURVIVAL AX**: Another mode from TGM Ace.
* **SURVIVAL AX2**: Another2 mode from TGM Ace.
RACE RACE
@@ -53,6 +60,14 @@ Modes with no levels, just a single timed goal.
* **Race 40**: How fast can you clear 40 lines? No limits, no holds barred. * **Race 40**: How fast can you clear 40 lines? No limits, no holds barred.
CREDITS
-------
Modes that are just the credit rolls of specific games.
* **CREDITS A3**: Tetris the Grand Master 3's famous M-roll.
PHANTOM MANIA PHANTOM MANIA
------------- -------------
@@ -63,7 +78,6 @@ Modes where pieces turn invisible as soon as you lock them. One of Cambridge's s
* **Phantom Mania 2**: Phantom Mania but way faster! Can you face a mode where even the garbage and the next preview turn invisible? * **Phantom Mania 2**: Phantom Mania but way faster! Can you face a mode where even the garbage and the next preview turn invisible?
OTHER MODES OTHER MODES
----------- -----------

View File

@@ -10,16 +10,13 @@ A ruleset consists of the following things:
If you're used to Nullpomino, you may notice a few things missing from that definition. For example, piece previews, hold queues, and randomizers have been moved to being game-specific rules, rather than rules that are changeable with the ruleset you use. Soft and hard drop behaviour is also game-specific now, so that times can be more plausibly compared across rulesets. If you're used to Nullpomino, you may notice a few things missing from that definition. For example, piece previews, hold queues, and randomizers have been moved to being game-specific rules, rather than rules that are changeable with the ruleset you use. Soft and hard drop behaviour is also game-specific now, so that times can be more plausibly compared across rulesets.
There are six rulesets currently supported:
Rotation system * Cambridge - a ruleset original to Cambridge, used for all custom modes. Supports 180-degree rotations!
---------------
A rotation system defines the following things:
* The block offsets of each piece orientation.
* The wall or floor kicks that will be attempted for each type of rotation.
There are four rotation systems currently supported: * SRS - the rotation system used in the Tetris Guideline games. Supports 180-degree rotations!
* Ti-SRS - SRS but with no 180-degree rotations.
* Cambridge * ARS - the rotation system from the original Tetris the Grand Master.
* Classic ARS * Ti-ARS - ARS with floorkicks! From TGM3: Terror Instinct.
* Ti-ARS * Ace-ARS - ARS with floorkicks and move reset! From TGM ACE.
* SRS

View File

@@ -1,5 +1,4 @@
function copy(t) function copy(t)
-- returns deep copy of t (as opposed to the shallow copy you get from var = t)
if type(t) ~= "table" then return t end if type(t) ~= "table" then return t end
local meta = getmetatable(t) local meta = getmetatable(t)
local target = {} local target = {}
@@ -8,8 +7,7 @@ function copy(t)
return target return target
end end
function strTrueValues(tbl) function st(tbl)
-- returns a concatenation of all the keys in tbl with value true, separated with spaces
str = "" str = ""
for k, v in pairs(tbl) do for k, v in pairs(tbl) do
if v == true then if v == true then
@@ -19,16 +17,14 @@ function strTrueValues(tbl)
return str return str
end end
function frameTime(min, sec, hth) function sp(m, s, f)
-- returns a time in frames from a time in minutes-seconds-hundredths format if m == nil then m = 0 end
if min == nil then min = 0 end if s == nil then s = 0 end
if sec == nil then sec = 0 end if f == nil then f = 0 end
if hth == nil then hth = 0 end return m*3600 + s*60 + math.ceil(f * 0.6)
return min*3600 + sec*60 + math.ceil(hth * 0.6)
end end
function vAdd(v1, v2) function vAdd(v1, v2)
-- returns the sum of vectors v1 and v2
return { return {
x = v1.x + v2.x, x = v1.x + v2.x,
y = v1.y + v2.y y = v1.y + v2.y
@@ -36,7 +32,6 @@ function vAdd(v1, v2)
end end
function vNeg(v) function vNeg(v)
-- returns the opposite of vector v
return { return {
x = -v.x, x = -v.x,
y = -v.y y = -v.y
@@ -44,26 +39,17 @@ function vNeg(v)
end end
function formatTime(frames) function formatTime(frames)
-- returns a mm:ss:hh (h=hundredths) representation of the time in frames given
if frames < 0 then return formatTime(0) end if frames < 0 then return formatTime(0) end
local min, sec, hund str = string.format("%02d", math.floor(frames / 3600)) .. ":"
min = math.floor(frames/3600) .. string.format("%02d", math.floor(frames / 60) % 60) .. "."
sec = math.floor(frames/60) % 60 .. string.format("%02d", math.floor(frames / 0.6) % 100)
hund = math.floor(frames/.6) % 100
str = string.format("%02d:%02d.%02d", min, sec, hund)
return str return str
end end
function formatBigNum(number) 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 s = string.format("%d", number)
local pos = string.len(s) % 3 local pos = string.len(s) % 3
if pos == 0 then pos = 3 end if pos == 0 then pos = 3 end
return string.sub(s, 1, pos) return string.sub(s, 1, pos)
.. string.gsub(string.sub(s, pos+1), "(...)", ",%1") .. string.gsub(string.sub(s, pos+1), "(...)", ",%1")
end 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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,268 +0,0 @@
local ffi = require "ffi"
-- Get the host os to load correct lib
local osname = love.system.getOS()
local discordRPClib = nil
if osname == "Linux" then
discordRPClib = ffi.load(love.filesystem.getSource().."/libs/discord-rpc.so")
elseif osname == "OS X" then
discordRPClib = ffi.load(love.filesystem.getSource().."/libs/discord-rpc.dylib")
elseif osname == "Windows" then
discordRPClib = ffi.load(love.filesystem.getSource().."/libs/discord-rpc.dll")
else
-- Else it crashes later on
error(string.format("Discord rpc not supported on platform (%s)", osname))
end
ffi.cdef[[
typedef struct DiscordRichPresence {
const char* state; /* max 128 bytes */
const char* details; /* max 128 bytes */
int64_t startTimestamp;
int64_t endTimestamp;
const char* largeImageKey; /* max 32 bytes */
const char* largeImageText; /* max 128 bytes */
const char* smallImageKey; /* max 32 bytes */
const char* smallImageText; /* max 128 bytes */
const char* partyId; /* max 128 bytes */
int partySize;
int partyMax;
const char* matchSecret; /* max 128 bytes */
const char* joinSecret; /* max 128 bytes */
const char* spectateSecret; /* max 128 bytes */
int8_t instance;
} DiscordRichPresence;
typedef struct DiscordUser {
const char* userId;
const char* username;
const char* discriminator;
const char* avatar;
} DiscordUser;
typedef void (*readyPtr)(const DiscordUser* request);
typedef void (*disconnectedPtr)(int errorCode, const char* message);
typedef void (*erroredPtr)(int errorCode, const char* message);
typedef void (*joinGamePtr)(const char* joinSecret);
typedef void (*spectateGamePtr)(const char* spectateSecret);
typedef void (*joinRequestPtr)(const DiscordUser* request);
typedef struct DiscordEventHandlers {
readyPtr ready;
disconnectedPtr disconnected;
erroredPtr errored;
joinGamePtr joinGame;
spectateGamePtr spectateGame;
joinRequestPtr joinRequest;
} DiscordEventHandlers;
void Discord_Initialize(const char* applicationId,
DiscordEventHandlers* handlers,
int autoRegister,
const char* optionalSteamId);
void Discord_Shutdown(void);
void Discord_RunCallbacks(void);
void Discord_UpdatePresence(const DiscordRichPresence* presence);
void Discord_ClearPresence(void);
void Discord_Respond(const char* userid, int reply);
void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
]]
local discordRPC = {} -- module table
-- proxy to detect garbage collection of the module
discordRPC.gcDummy = newproxy(true)
local function unpackDiscordUser(request)
return ffi.string(request.userId), ffi.string(request.username),
ffi.string(request.discriminator), ffi.string(request.avatar)
end
-- callback proxies
-- note: callbacks are not JIT compiled (= SLOW), try to avoid doing performance critical tasks in them
-- luajit.org/ext_ffi_semantics.html
local ready_proxy = ffi.cast("readyPtr", function(request)
if discordRPC.ready then
discordRPC.ready(unpackDiscordUser(request))
end
end)
local disconnected_proxy = ffi.cast("disconnectedPtr", function(errorCode, message)
if discordRPC.disconnected then
discordRPC.disconnected(errorCode, ffi.string(message))
end
end)
local errored_proxy = ffi.cast("erroredPtr", function(errorCode, message)
if discordRPC.errored then
discordRPC.errored(errorCode, ffi.string(message))
end
end)
local joinGame_proxy = ffi.cast("joinGamePtr", function(joinSecret)
if discordRPC.joinGame then
discordRPC.joinGame(ffi.string(joinSecret))
end
end)
local spectateGame_proxy = ffi.cast("spectateGamePtr", function(spectateSecret)
if discordRPC.spectateGame then
discordRPC.spectateGame(ffi.string(spectateSecret))
end
end)
local joinRequest_proxy = ffi.cast("joinRequestPtr", function(request)
if discordRPC.joinRequest then
discordRPC.joinRequest(unpackDiscordUser(request))
end
end)
-- helpers
local function checkArg(arg, argType, argName, func, maybeNil)
assert(type(arg) == argType or (maybeNil and arg == nil),
string.format("Argument \"%s\" to function \"%s\" has to be of type \"%s\"",
argName, func, argType))
end
local function checkStrArg(arg, maxLen, argName, func, maybeNil)
if maxLen then
assert(type(arg) == "string" and arg:len() <= maxLen or (maybeNil and arg == nil),
string.format("Argument \"%s\" of function \"%s\" has to be of type string with maximum length %d",
argName, func, maxLen))
else
checkArg(arg, "string", argName, func, true)
end
end
local function checkIntArg(arg, maxBits, argName, func, maybeNil)
maxBits = math.min(maxBits or 32, 52) -- lua number (double) can only store integers < 2^53
local maxVal = 2^(maxBits-1) -- assuming signed integers, which, for now, are the only ones in use
assert(type(arg) == "number" and math.floor(arg) == arg
and arg < maxVal and arg >= -maxVal
or (maybeNil and arg == nil),
string.format("Argument \"%s\" of function \"%s\" has to be a whole number <= %d",
argName, func, maxVal))
end
-- function wrappers
function discordRPC.initialize(applicationId, autoRegister, optionalSteamId)
local func = "discordRPC.Initialize"
checkStrArg(applicationId, nil, "applicationId", func)
checkArg(autoRegister, "boolean", "autoRegister", func)
if optionalSteamId ~= nil then
checkStrArg(optionalSteamId, nil, "optionalSteamId", func)
end
local eventHandlers = ffi.new("struct DiscordEventHandlers")
eventHandlers.ready = ready_proxy
eventHandlers.disconnected = disconnected_proxy
eventHandlers.errored = errored_proxy
eventHandlers.joinGame = joinGame_proxy
eventHandlers.spectateGame = spectateGame_proxy
eventHandlers.joinRequest = joinRequest_proxy
discordRPClib.Discord_Initialize(applicationId, eventHandlers,
autoRegister and 1 or 0, optionalSteamId)
end
function discordRPC.shutdown()
discordRPClib.Discord_Shutdown()
end
function discordRPC.runCallbacks()
discordRPClib.Discord_RunCallbacks()
end
-- http://luajit.org/ext_ffi_semantics.html#callback :
-- It is not allowed, to let an FFI call into a C function (runCallbacks)
-- get JIT-compiled, which in turn calls a callback, calling into Lua again (e.g. discordRPC.ready).
-- Usually this attempt is caught by the interpreter first and the C function
-- is blacklisted for compilation.
-- solution:
-- "Then you'll need to manually turn off JIT-compilation with jit.off() for
-- the surrounding Lua function that invokes such a message polling function."
jit.off(discordRPC.runCallbacks)
function discordRPC.updatePresence(presence)
local func = "discordRPC.updatePresence"
checkArg(presence, "table", "presence", func)
-- -1 for string length because of 0-termination
checkStrArg(presence.state, 127, "presence.state", func, true)
checkStrArg(presence.details, 127, "presence.details", func, true)
checkIntArg(presence.startTimestamp, 64, "presence.startTimestamp", func, true)
checkIntArg(presence.endTimestamp, 64, "presence.endTimestamp", func, true)
checkStrArg(presence.largeImageKey, 31, "presence.largeImageKey", func, true)
checkStrArg(presence.largeImageText, 127, "presence.largeImageText", func, true)
checkStrArg(presence.smallImageKey, 31, "presence.smallImageKey", func, true)
checkStrArg(presence.smallImageText, 127, "presence.smallImageText", func, true)
checkStrArg(presence.partyId, 127, "presence.partyId", func, true)
checkIntArg(presence.partySize, 32, "presence.partySize", func, true)
checkIntArg(presence.partyMax, 32, "presence.partyMax", func, true)
checkStrArg(presence.matchSecret, 127, "presence.matchSecret", func, true)
checkStrArg(presence.joinSecret, 127, "presence.joinSecret", func, true)
checkStrArg(presence.spectateSecret, 127, "presence.spectateSecret", func, true)
checkIntArg(presence.instance, 8, "presence.instance", func, true)
local cpresence = ffi.new("struct DiscordRichPresence")
cpresence.state = presence.state
cpresence.details = presence.details
cpresence.startTimestamp = presence.startTimestamp or 0
cpresence.endTimestamp = presence.endTimestamp or 0
cpresence.largeImageKey = presence.largeImageKey
cpresence.largeImageText = presence.largeImageText
cpresence.smallImageKey = presence.smallImageKey
cpresence.smallImageText = presence.smallImageText
cpresence.partyId = presence.partyId
cpresence.partySize = presence.partySize or 0
cpresence.partyMax = presence.partyMax or 0
cpresence.matchSecret = presence.matchSecret
cpresence.joinSecret = presence.joinSecret
cpresence.spectateSecret = presence.spectateSecret
cpresence.instance = presence.instance or 0
discordRPClib.Discord_UpdatePresence(cpresence)
end
function discordRPC.clearPresence()
discordRPClib.Discord_ClearPresence()
end
local replyMap = {
no = 0,
yes = 1,
ignore = 2
}
-- maybe let reply take ints too (0, 1, 2) and add constants to the module
function discordRPC.respond(userId, reply)
checkStrArg(userId, nil, "userId", "discordRPC.respond")
assert(replyMap[reply], "Argument 'reply' to discordRPC.respond has to be one of \"yes\", \"no\" or \"ignore\"")
discordRPClib.Discord_Respond(userId, replyMap[reply])
end
-- garbage collection callback
getmetatable(discordRPC.gcDummy).__gc = function()
discordRPC.shutdown()
ready_proxy:free()
disconnected_proxy:free()
errored_proxy:free()
joinGame_proxy:free()
spectateGame_proxy:free()
joinRequest_proxy:free()
end
return discordRPC

View File

@@ -6,7 +6,7 @@ bgm = {
} }
local current_bgm = nil local current_bgm = nil
local bgm_locked = true local bgm_locked = false
function switchBGM(sound, subsound) function switchBGM(sound, subsound)
if bgm_locked then return end if bgm_locked then return end

View File

@@ -20,54 +20,35 @@ backgrounds = {
love.graphics.newImage("res/backgrounds/1800-railways.png"), love.graphics.newImage("res/backgrounds/1800-railways.png"),
love.graphics.newImage("res/backgrounds/1900-world-wide-web.png"), love.graphics.newImage("res/backgrounds/1900-world-wide-web.png"),
title = love.graphics.newImage("res/backgrounds/title_v0.1.png"), title = love.graphics.newImage("res/backgrounds/title_v0.1.png"),
input_config = love.graphics.newImage("res/backgrounds/options-pcb.png"),
game_config = love.graphics.newImage("res/backgrounds/options-gears.png"),
} }
blocks = { blocks = {
["2tie"] = { ["2tie"] = {
R = love.graphics.newImage("res/img/s1.png"), I = love.graphics.newImage("res/img/s1.png"),
O = love.graphics.newImage("res/img/s3.png"), J = love.graphics.newImage("res/img/s4.png"),
Y = love.graphics.newImage("res/img/s7.png"), L = love.graphics.newImage("res/img/s3.png"),
G = love.graphics.newImage("res/img/s6.png"), O = love.graphics.newImage("res/img/s7.png"),
C = love.graphics.newImage("res/img/s2.png"), S = love.graphics.newImage("res/img/s5.png"),
B = love.graphics.newImage("res/img/s4.png"), T = love.graphics.newImage("res/img/s2.png"),
M = love.graphics.newImage("res/img/s5.png"), Z = love.graphics.newImage("res/img/s6.png"),
F = love.graphics.newImage("res/img/s9.png"),
G = love.graphics.newImage("res/img/s9.png"),
X = love.graphics.newImage("res/img/s9.png"), X = love.graphics.newImage("res/img/s9.png"),
}, },
["bone"] = { ["bone"] = {
R = love.graphics.newImage("res/img/bone.png"), I = love.graphics.newImage("res/img/bone.png"),
J = love.graphics.newImage("res/img/bone.png"),
L = love.graphics.newImage("res/img/bone.png"),
O = love.graphics.newImage("res/img/bone.png"), O = love.graphics.newImage("res/img/bone.png"),
Y = love.graphics.newImage("res/img/bone.png"), S = love.graphics.newImage("res/img/bone.png"),
T = love.graphics.newImage("res/img/bone.png"),
Z = love.graphics.newImage("res/img/bone.png"),
F = love.graphics.newImage("res/img/bone.png"),
G = love.graphics.newImage("res/img/bone.png"), G = love.graphics.newImage("res/img/bone.png"),
C = love.graphics.newImage("res/img/bone.png"),
B = love.graphics.newImage("res/img/bone.png"),
M = love.graphics.newImage("res/img/bone.png"),
X = love.graphics.newImage("res/img/bone.png"), X = love.graphics.newImage("res/img/bone.png"),
} }
} }
ColourSchemes = {
Arika = {
I = "R",
L = "O",
J = "B",
S = "M",
Z = "G",
O = "Y",
T = "C",
},
TTC = {
I = "C",
L = "O",
J = "B",
S = "G",
Z = "R",
O = "Y",
T = "M",
},
}
for name, blockset in pairs(blocks) do for name, blockset in pairs(blocks) do
for shape, image in pairs(blockset) do for shape, image in pairs(blockset) do
image:setFilter("nearest") image:setFilter("nearest")

View File

@@ -1,58 +0,0 @@
print("Loading discord RPC...")
DiscordRPC = {
loaded = false
}
local success, RPC = pcall(require, "libs.discordRPC")
if success then
DiscordRPC.loaded = true
DiscordRPC.appId = "599778517789573120"
function RPC.ready(userId, username, discriminator, avatar)
print(string.format("Discord: ready (%s, %s, %s, %s)", userId, username, discriminator, avatar))
end
function RPC.disconnected(errorCode, message)
print(string.format("Discord: disconnected (%d: %s)", errorCode, message))
end
function RPC.errored(errorCode, message)
print(string.format("Discord: error (%d: %s)", errorCode, message))
end
function RPC.joinGame(joinSecret)
print(string.format("Discord: join (%s)", joinSecret))
end
function RPC.spectateGame(spectateSecret)
print(string.format("Discord: spectate (%s)", spectateSecret))
end
function RPC.joinRequest(userId, username, discriminator, avatar)
print(string.format("Discord: join request (%s, %s, %s, %s)", userId, username, discriminator, avatar))
RPC.respond(userId, "yes")
end
RPC.initialize(DiscordRPC.appId, true)
local now = os.time(os.date("*t"))
DiscordRPC.RPC = RPC
print("DiscordRPC successfully loaded.")
else
print("DiscordRPC failed to load!")
print(RPC)
end
DiscordRPC.presence = {
startTimestamp = now,
details = "Loading game...",
state = "",
largeImageKey = "icon2",
largeImageText = "Arcade Stacker",
smallImageKey = "",
smallImageText = ""
}
function DiscordRPC:update(newstuff)
for k, v in pairs(newstuff) do self.presence[k] = v end
if self.loaded then self.RPC.updatePresence(self.presence) end
end

View File

@@ -10,10 +10,6 @@ sounds = {
}, },
move = love.audio.newSource("res/se/move.wav", "static"), move = love.audio.newSource("res/se/move.wav", "static"),
bottom = love.audio.newSource("res/se/bottom.wav", "static"), bottom = love.audio.newSource("res/se/bottom.wav", "static"),
cursor = love.audio.newSource("res/se/cursor.wav", "static"),
cursor_lr = love.audio.newSource("res/se/cursor_lr.wav", "static"),
main_decide = love.audio.newSource("res/se/main_decide.wav", "static"),
mode_decide = love.audio.newSource("res/se/mode_decide.wav", "static"),
} }
function playSE(sound, subsound) function playSE(sound, subsound)

View File

@@ -1,7 +1,6 @@
function love.load() function love.load()
math.randomseed(os.time()) math.randomseed(os.time())
highscores = {} highscores = {}
require "load.rpc"
require "load.graphics" require "load.graphics"
require "load.fonts" require "load.fonts"
require "load.sounds" require "load.sounds"
@@ -12,16 +11,10 @@ function love.load()
config["side_next"] = false config["side_next"] = false
config["reverse_rotate"] = true config["reverse_rotate"] = true
config["fullscreen"] = false config["fullscreen"] = false
config["das_last_key"] = false
love.window.setMode(love.graphics.getWidth(), love.graphics.getHeight(), {resizable = true}); love.window.setMode(love.graphics.getWidth(), love.graphics.getHeight(), {resizable = true});
if not config.gamesettings then config.gamesettings = {} end
for _, option in ipairs(GameConfigScene.options) do
if not config.gamesettings[option[1]] then
config.gamesettings[option[1]] = 1
end
end
if not config.input then if not config.input then
config.input = {} config.input = {}
scene = InputConfigScene() scene = InputConfigScene()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -7,9 +7,8 @@ function Scene:update() end
function Scene:render() end function Scene:render() end
function Scene:onKeyPress() end function Scene:onKeyPress() end
ExitScene = require "scene.exit"
GameScene = require "scene.game" GameScene = require "scene.game"
ModeSelectScene = require "scene.mode_select" ModeSelectScene = require "scene.mode_select"
InputConfigScene = require "scene.input_config" InputConfigScene = require "scene.input_config"
GameConfigScene = require "scene.game_config" ConfigScene = require "scene.config"
TitleScene = require "scene.title" TitleScene = require "scene.title"

View File

@@ -1,23 +0,0 @@
local ExitScene = Scene:extend()
require 'load.save'
ExitScene.title = "Exit Game"
function ExitScene:new()
end
function ExitScene:update()
love.event.quit()
end
function ExitScene:render()
end
function ExitScene:changeOption(rel)
end
function ExitScene:onKeyPress(e)
end
return ExitScene

View File

@@ -5,10 +5,6 @@ function GameScene:new(game_mode, ruleset)
self.game = game_mode() self.game = game_mode()
self.ruleset = ruleset() self.ruleset = ruleset()
self.game:initialize(self.ruleset) self.game:initialize(self.ruleset)
DiscordRPC:update({
details = self.game.rpc_details,
state = self.game.name,
})
end end
function GameScene:update() function GameScene:update()
@@ -61,21 +57,11 @@ end
function GameScene:onKeyPress(e) function GameScene:onKeyPress(e)
if (self.game.completed) and if (self.game.completed) and
(e.scancode == "return" or e.scancode == "escape") and e.isRepeat == false then e.scancode == "return" and e.isRepeat == false then
highscore_entry = self.game:getHighscoreData() highscore_entry = self.game:getHighscoreData()
highscore_hash = self.game.hash .. "-" .. self.ruleset.hash highscore_hash = self.game.hash .. "-" .. self.ruleset.hash
submitHighscore(highscore_hash, highscore_entry) submitHighscore(highscore_hash, highscore_entry)
scene = ModeSelectScene() scene = ModeSelectScene()
elseif (e.scancode == config.input.retry) then
-- fuck this, this is hacky but the way this codebase is setup prevents anything else
-- it seems like all the values that get touched in the child gamemode class
-- stop being linked to the values of the GameMode superclass because of how `mt.__index` works
-- not even sure this is the actual problem, but I don't want to have to rebuild everything about
-- the core organisation of everything. this hacky way will have to do until someone figures out something.
love.keypressed("escape", "escape", false)
love.keypressed("return", "return", false)
elseif e.scancode == "escape" then
scene = ModeSelectScene()
end end
end end

View File

@@ -1,79 +0,0 @@
local ConfigScene = Scene:extend()
ConfigScene.title = "Game Settings"
require 'load.save'
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", "World Reverse", {"No", "Yes"}},
}
local optioncount = #ConfigScene.options
function ConfigScene:new()
-- load current config
self.config = config.input
self.highlight = 1
DiscordRPC:update({
details = "In menus",
state = "Changing game settings",
})
end
function ConfigScene:update()
end
function ConfigScene: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("GAME SETTINGS", 80, 40)
love.graphics.setColor(1, 1, 1, 0.5)
love.graphics.rectangle("fill", 20, 98 + self.highlight * 20, 170, 22)
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")
end
end
end
function ConfigScene:onKeyPress(e)
if e.scancode == "return" and e.isRepeat == false then
playSE("mode_decide")
saveConfig()
scene = TitleScene()
elseif (e.scancode == config.input["up"] or e.scancode == "up") and e.isRepeat == false then
playSE("cursor")
self.highlight = Mod1(self.highlight-1, optioncount)
elseif (e.scancode == config.input["down"] or e.scancode == "down") and e.isRepeat == false then
playSE("cursor")
self.highlight = Mod1(self.highlight+1, optioncount)
elseif (e.scancode == config.input["left"] or e.scancode == "left") and e.isRepeat == false then
playSE("cursor_lr")
local option = ConfigScene.options[self.highlight]
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]-1, #option[3])
elseif (e.scancode == config.input["right"] or e.scancode == "right") and e.isRepeat == false then
playSE("cursor_lr")
local option = ConfigScene.options[self.highlight]
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]+1, #option[3])
elseif e.scancode == "escape" then
loadSave()
scene = TitleScene()
end
end
return ConfigScene

View File

@@ -15,35 +15,22 @@ local configurable_inputs = {
"rotate_right2", "rotate_right2",
"rotate_180", "rotate_180",
"hold", "hold",
"retry",
} }
function ConfigScene:new() function ConfigScene:new()
-- load current config -- load current config
self.config = config.input self.config = config.input
self.input_state = 1 self.input_state = 1
DiscordRPC:update({
details = "In menus",
state = "Changing input config",
})
end end
function ConfigScene:update() function ConfigScene:update()
end end
function ConfigScene:render() function ConfigScene:render()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(
backgrounds["input_config"],
0, 0, 0,
0.5, 0.5
)
love.graphics.setFont(font_3x5_2) love.graphics.setFont(font_3x5_2)
for i, input in pairs(configurable_inputs) do for i, input in pairs(configurable_inputs) do
love.graphics.printf(input, 40, 50 + i * 20, 200, "left")
if config.input[input] then if config.input[input] then
love.graphics.printf(input, 40, 50 + i * 20, 200, "left")
love.graphics.printf( love.graphics.printf(
love.keyboard.getKeyFromScancode(config.input[input]) .. " (" .. config.input[input] .. ")", love.keyboard.getKeyFromScancode(config.input[input]) .. " (" .. config.input[input] .. ")",
240, 50 + i * 20, 200, "left" 240, 50 + i * 20, 200, "left"
@@ -66,15 +53,10 @@ function ConfigScene:onKeyPress(e)
elseif e.scancode == "delete" or e.scancode == "backspace" then elseif e.scancode == "delete" or e.scancode == "backspace" then
self.input_state = 1 self.input_state = 1
end end
else
if e.scancode == "escape" then
loadSave()
scene = TitleScene()
else else
config.input[configurable_inputs[self.input_state]] = e.scancode config.input[configurable_inputs[self.input_state]] = e.scancode
self.input_state = self.input_state + 1 self.input_state = self.input_state + 1
end end
end end
end
return ConfigScene return ConfigScene

102
scene/mode_select.lua Executable file → Normal file
View File

@@ -5,12 +5,15 @@ ModeSelectScene.title = "Game Start"
current_mode = 1 current_mode = 1
current_ruleset = 1 current_ruleset = 1
MAX_MODES = 19
game_modes = { game_modes = {
require 'tetris.modes.marathon_2020', require 'tetris.modes.marathon_2020',
require 'tetris.modes.survival_2020', require 'tetris.modes.survival_2020',
--require 'tetris.modes.strategy', require 'tetris.modes.strategy',
--require 'tetris.modes.interval_training', require 'tetris.modes.interval_training',
--require 'tetris.modes.pacer_test', require 'tetris.modes.pacer_test',
require 'tetris.modes.marathon_wcb',
require 'tetris.modes.demon_mode', require 'tetris.modes.demon_mode',
require 'tetris.modes.phantom_mania', require 'tetris.modes.phantom_mania',
require 'tetris.modes.phantom_mania2', require 'tetris.modes.phantom_mania2',
@@ -19,24 +22,25 @@ game_modes = {
require 'tetris.modes.marathon_a1', require 'tetris.modes.marathon_a1',
require 'tetris.modes.marathon_a2', require 'tetris.modes.marathon_a2',
require 'tetris.modes.marathon_a3', require 'tetris.modes.marathon_a3',
require 'tetris.modes.marathon_ax4', require 'tetris.modes.marathon_ax',
require 'tetris.modes.marathon_ax2',
require 'tetris.modes.marathon_ax3',
require 'tetris.modes.marathon_c89', require 'tetris.modes.marathon_c89',
require 'tetris.modes.survival_a1', require 'tetris.modes.survival_a1',
require 'tetris.modes.survival_a2', require 'tetris.modes.survival_a2',
require 'tetris.modes.survival_a3', require 'tetris.modes.survival_a3',
require 'tetris.modes.big_a2', require 'tetris.modes.survival_ax',
require 'tetris.modes.konoha', require 'tetris.modes.survival_ax2',
require 'tetris.modes.credits_a3',
} }
rulesets = { rulesets = {
require 'tetris.rulesets.cambridge', require 'tetris.rulesets.cambridge',
require 'tetris.rulesets.standard',
require 'tetris.rulesets.standard_ti',
require 'tetris.rulesets.arika', require 'tetris.rulesets.arika',
require 'tetris.rulesets.arika_ti', require 'tetris.rulesets.arika_ti',
require 'tetris.rulesets.ti_srs',
require 'tetris.rulesets.arika_ace', require 'tetris.rulesets.arika_ace',
require 'tetris.rulesets.arika_ace2',
require 'tetris.rulesets.arika_srs',
require 'tetris.rulesets.standard_exp',
--require 'tetris.rulesets.bonkers', --require 'tetris.rulesets.bonkers',
--require 'tetris.rulesets.shirase', --require 'tetris.rulesets.shirase',
--require 'tetris.rulesets.super302', --require 'tetris.rulesets.super302',
@@ -48,47 +52,63 @@ function ModeSelectScene:new()
ruleset = current_ruleset, ruleset = current_ruleset,
select = "mode", select = "mode",
} }
DiscordRPC:update({
details = "In menus",
state = "Choosing a mode",
})
end end
function ModeSelectScene:update() function ModeSelectScene:update()
end end
function getCursorHeight(x)
return 78 + 20 * x
end
function ModeSelectScene:drawList(name, items, cursor, x)
local start, finish
if table.getn(items) < MAX_MODES then
start = 1
finish = table.getn(items)
else
if cursor < 10 then
start = 1
finish = 19
elseif cursor > table.getn(items) - 9 then
start = table.getn(items) - 18
finish = table.getn(items)
else
start = cursor - 9
finish = cursor + 9
end
end
if self.menu_state.select == name then
love.graphics.setColor(1, 1, 1, 0.5)
else
love.graphics.setColor(1, 1, 1, 0.25)
end
love.graphics.rectangle("fill", x, getCursorHeight(cursor - start), 240, 22)
love.graphics.setColor(1, 1, 1, 1)
for idx = start, finish do
local item = items[idx]
love.graphics.printf(item.name, x + 20, 80 + 20 * (idx - start), 200, "left")
end
end
function ModeSelectScene:render() function ModeSelectScene:render()
love.graphics.setFont(font_3x5_2)
love.graphics.draw( love.graphics.draw(
backgrounds[0], backgrounds[0],
0, 0, 0, 0, 0, 0,
0.5, 0.5 0.5, 0.5
) )
if self.menu_state.select == "mode" then love.graphics.draw(misc_graphics["select_mode"], 20, 30)
love.graphics.setColor(1, 1, 1, 0.5)
elseif self.menu_state.select == "ruleset" then
love.graphics.setColor(1, 1, 1, 0.25)
end
love.graphics.rectangle("fill", 20, 78 + 20 * self.menu_state.mode, 240, 22)
if self.menu_state.select == "mode" then self:drawList("mode", game_modes, self.menu_state.mode, 20)
love.graphics.setColor(1, 1, 1, 0.25) self:drawList("ruleset", rulesets, self.menu_state.ruleset, 320)
elseif self.menu_state.select == "ruleset" then
love.graphics.setColor(1, 1, 1, 0.5)
end
love.graphics.rectangle("fill", 340, 78 + 20 * self.menu_state.ruleset, 200, 22)
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(misc_graphics["select_mode"], 20, 40)
love.graphics.setFont(font_3x5_2)
for idx, mode in pairs(game_modes) do
love.graphics.printf(mode.name, 40, 80 + 20 * idx, 200, "left")
end
for idx, ruleset in pairs(rulesets) do
love.graphics.printf(ruleset.name, 360, 80 + 20 * idx, 160, "left")
end
end end
function ModeSelectScene:onKeyPress(e) function ModeSelectScene:onKeyPress(e)
@@ -97,21 +117,15 @@ function ModeSelectScene:onKeyPress(e)
current_ruleset = self.menu_state.ruleset current_ruleset = self.menu_state.ruleset
config.current_mode = current_mode config.current_mode = current_mode
config.current_ruleset = current_ruleset config.current_ruleset = current_ruleset
playSE("mode_decide")
saveConfig() 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])
elseif (e.scancode == config.input["up"] or e.scancode == "up") and e.isRepeat == false then elseif (e.scancode == config.input["up"] or e.scancode == "up") and e.isRepeat == false then
self:changeOption(-1) self:changeOption(-1)
playSE("cursor")
elseif (e.scancode == config.input["down"] or e.scancode == "down") and e.isRepeat == false then elseif (e.scancode == config.input["down"] or e.scancode == "down") and e.isRepeat == false then
self:changeOption(1) self:changeOption(1)
playSE("cursor")
elseif (e.scancode == config.input["left"] or e.scancode == "left") or elseif (e.scancode == config.input["left"] or e.scancode == "left") or
(e.scancode == config.input["right"] or e.scancode == "right") then (e.scancode == config.input["right"] or e.scancode == "right") then
self:switchSelect() self:switchSelect()
playSE("cursor_lr")
elseif e.scancode == "escape" then
scene = TitleScene()
end end
end end

View File

@@ -3,31 +3,10 @@ local TitleScene = Scene:extend()
local main_menu_screens = { local main_menu_screens = {
ModeSelectScene, ModeSelectScene,
InputConfigScene, InputConfigScene,
GameConfigScene,
ExitScene,
}
local mainmenuidle = {
"Idle",
"On title screen",
"On main menu screen",
"Twiddling their thumbs",
"Admiring the main menu's BG",
"Waiting for spring to come",
"Actually not playing",
"Contemplating collecting stars",
"Preparing to put the block!!",
"Having a nap",
"In menus",
"Bottom text",
} }
function TitleScene:new() function TitleScene:new()
self.main_menu_state = 1 self.main_menu_state = 1
DiscordRPC:update({
details = "In menus",
state = mainmenuidle[math.random(#mainmenuidle)],
})
end end
function TitleScene:update() function TitleScene:update()
@@ -59,16 +38,11 @@ end
function TitleScene:onKeyPress(e) function TitleScene:onKeyPress(e)
if e.scancode == "return" and e.isRepeat == false then if e.scancode == "return" and e.isRepeat == false then
playSE("main_decide")
scene = main_menu_screens[self.main_menu_state]() scene = main_menu_screens[self.main_menu_state]()
elseif (e.scancode == config.input["up"] or e.scancode == "up") and e.isRepeat == false then elseif (e.scancode == config.input["up"] or e.scancode == "up") and e.isRepeat == false then
self:changeOption(-1) self:changeOption(-1)
playSE("cursor")
elseif (e.scancode == config.input["down"] or e.scancode == "down") and e.isRepeat == false then elseif (e.scancode == config.input["down"] or e.scancode == "down") and e.isRepeat == false then
self:changeOption(1) self:changeOption(1)
playSE("cursor")
elseif e.scancode == "escape" and e.isRepeat == false then
love.event.quit()
end end
end end

View File

@@ -3,7 +3,6 @@ local Object = require 'libs.classic'
local Grid = Object:extend() local Grid = Object:extend()
local empty = { skin = "", colour = "" } local empty = { skin = "", colour = "" }
local oob = { skin = "", colour = "" }
function Grid:new() function Grid:new()
self.grid = {} self.grid = {}
@@ -27,15 +26,8 @@ function Grid:clear()
end end
end end
function Grid:getCell(x, y)
if x < 1 or x > 10 or y > 24 then return oob
elseif y < 1 then return empty
else return self.grid[y][x]
end
end
function Grid:isOccupied(x, y) function Grid:isOccupied(x, y)
return self:getCell(x+1, y+1) ~= empty return self.grid[y+1][x+1] ~= empty
end end
function Grid:isRowFull(row) function Grid:isRowFull(row)
@@ -54,7 +46,7 @@ function Grid:canPlacePiece(piece)
for index, offset in pairs(offsets) do for index, offset in pairs(offsets) do
local x = piece.position.x + offset.x local x = piece.position.x + offset.x
local y = piece.position.y + offset.y local y = piece.position.y + offset.y
if self:isOccupied(x, y) then if x >= 10 or x < 0 or y >= 24 or y < 0 or self.grid[y+1][x+1] ~= empty then
return false return false
end end
end end
@@ -66,12 +58,12 @@ function Grid:canPlaceBigPiece(piece)
for index, offset in pairs(offsets) do for index, offset in pairs(offsets) do
local x = piece.position.x + offset.x local x = piece.position.x + offset.x
local y = piece.position.y + offset.y local y = piece.position.y + offset.y
if ( if x >= 5 or x < 0 or y >= 12 or y < 0 or
self:isOccupied(x * 2 + 0, y * 2 + 0) self.grid[y * 2 + 1][x * 2 + 1] ~= empty or
or self:isOccupied(x * 2 + 1, y * 2 + 0) self.grid[y * 2 + 1][x * 2 + 2] ~= empty or
or self:isOccupied(x * 2 + 0, y * 2 + 1) self.grid[y * 2 + 2][x * 2 + 1] ~= empty or
or self:isOccupied(x * 2 + 1, y * 2 + 1) self.grid[y * 2 + 2][x * 2 + 2] ~= empty
) then then
return false return false
end end
end end
@@ -88,7 +80,7 @@ function Grid:canPlacePieceInVisibleGrid(piece)
for index, offset in pairs(offsets) do for index, offset in pairs(offsets) do
local x = piece.position.x + offset.x local x = piece.position.x + offset.x
local y = piece.position.y + offset.y local y = piece.position.y + offset.y
if y < 4 or self:isOccupied(x, y) ~= empty then if x >= 10 or x < 0 or y >= 24 or y < 4 or self.grid[y+1][x+1] ~= empty then
return false return false
end end
end end
@@ -143,7 +135,7 @@ function Grid:copyBottomRow()
for col = 1, 10 do for col = 1, 10 do
self.grid[24][col] = (self.grid[23][col] == empty) and empty or { self.grid[24][col] = (self.grid[23][col] == empty) and empty or {
skin = self.grid[23][col].skin, skin = self.grid[23][col].skin,
colour = "X" colour = "G"
} }
end end
return true return true
@@ -158,14 +150,12 @@ function Grid:applyPiece(piece)
for index, offset in pairs(offsets) do for index, offset in pairs(offsets) do
x = piece.position.x + offset.x x = piece.position.x + offset.x
y = piece.position.y + offset.y y = piece.position.y + offset.y
if y + 1 > 0 then
self.grid[y+1][x+1] = { self.grid[y+1][x+1] = {
skin = piece.skin, skin = piece.skin,
colour = piece.colour colour = piece.shape
} }
end end
end end
end
function Grid:applyBigPiece(piece) function Grid:applyBigPiece(piece)
offsets = piece:getBlockOffsets() offsets = piece:getBlockOffsets()
@@ -174,59 +164,14 @@ function Grid:applyBigPiece(piece)
y = piece.position.y + offset.y y = piece.position.y + offset.y
for a = 1, 2 do for a = 1, 2 do
for b = 1, 2 do for b = 1, 2 do
if y*2+a > 0 then
self.grid[y*2+a][x*2+b] = { self.grid[y*2+a][x*2+b] = {
skin = piece.skin, skin = piece.skin,
colour = piece.colour colour = piece.shape
} }
end end
end end
end end
end end
end
function Grid:checkForBravo(cleared_row_count)
for i = 0, 23 - cleared_row_count do
for j = 0, 9 do
if self:isOccupied(j, i) then return false end
end
end
return true
end
function Grid:checkSecretGrade()
local sgrade = 0
for i=23,5,-1 do
local validLine = true
local emptyCell = 0
if i > 13 then
emptyCell = 23-i
end
if i <= 13 then
emptyCell = i-5
end
for j=0,9 do
if (not self:isOccupied(j,i) and j ~= emptyCell) or (j == emptyCell and self:isOccupied(j,i)) then
validLine = false
end
end
if not self:isOccupied(emptyCell,i-1) then
validLine = false
end
if(validLine) then
sgrade = sgrade + 1
else
-- return sgrade
end
end
--[[
if(sgrade == 0) then return ""
elseif(sgrade < 10) then return 10-sgrade
elseif(sgrade < 19) then return "S"..(sgrade-9) end
return "GM"
--]]
return sgrade
end
function Grid:update() function Grid:update()
for y = 1, 24 do for y = 1, 24 do

View File

@@ -2,7 +2,7 @@ local Object = require 'libs.classic'
local Piece = Object:extend() local Piece = Object:extend()
function Piece:new(shape, rotation, position, block_offsets, gravity, lock_delay, skin, colour, big) function Piece:new(shape, rotation, position, block_offsets, gravity, lock_delay, skin, big)
self.shape = shape self.shape = shape
self.rotation = rotation self.rotation = rotation
self.position = position self.position = position
@@ -10,7 +10,6 @@ function Piece:new(shape, rotation, position, block_offsets, gravity, lock_delay
self.gravity = gravity self.gravity = gravity
self.lock_delay = lock_delay self.lock_delay = lock_delay
self.skin = skin self.skin = skin
self.colour = colour
self.ghost = false self.ghost = false
self.locked = false self.locked = false
self.big = big self.big = big
@@ -22,7 +21,7 @@ function Piece:withOffset(offset)
return Piece( return Piece(
self.shape, self.rotation, self.shape, self.rotation,
{x = self.position.x + offset.x, y = self.position.y + offset.y}, {x = self.position.x + offset.x, y = self.position.y + offset.y},
self.block_offsets, self.gravity, self.lock_delay, self.skin, self.colour, self.big self.block_offsets, self.gravity, self.lock_delay, self.skin, self.big
) )
end end
@@ -32,7 +31,7 @@ function Piece:withRelativeRotation(rot)
while new_rot >= 4 do new_rot = new_rot - 4 end while new_rot >= 4 do new_rot = new_rot - 4 end
return Piece( return Piece(
self.shape, new_rot, self.position, self.shape, new_rot, self.position,
self.block_offsets, self.gravity, self.lock_delay, self.skin, self.colour, self.big self.block_offsets, self.gravity, self.lock_delay, self.skin, self.big
) )
end end
@@ -78,12 +77,15 @@ function Piece:setRelativeRotation(rot)
return self return self
end end
function Piece:moveInGrid(step, squares, grid) function Piece:moveInGrid(step, squares, grid, instant)
local moved = false local moved = false
for x = 1, squares do for x = 1, squares do
if grid:canPlacePiece(self:withOffset(step)) then if grid:canPlacePiece(self:withOffset(step)) then
moved = true moved = true
self:setOffset(step) self:setOffset(step)
if instant then
self:dropToBottom(grid)
end
else else
break break
end end
@@ -98,7 +100,7 @@ end
function Piece:dropToBottom(grid) function Piece:dropToBottom(grid)
local piece_y = self.position.y local piece_y = self.position.y
self:dropSquares(24, grid) self:dropSquares(20, grid)
self.gravity = 0 self.gravity = 0
if self.position.y > piece_y then if self.position.y > piece_y then
-- if it got dropped any, also reset lock delay -- if it got dropped any, also reset lock delay
@@ -149,13 +151,13 @@ function Piece:draw(opacity, brightness, grid, partial_das)
local y = self.position.y + offset.y local y = self.position.y + offset.y
if self.big then if self.big then
love.graphics.draw( love.graphics.draw(
blocks[self.skin][self.colour], blocks[self.skin][self.shape],
64+x*32+partial_das*2, 16+y*32+gravity_offset*2, 64+x*32+partial_das*2, 16+y*32+gravity_offset*2,
0, 2, 2 0, 2, 2
) )
else else
love.graphics.draw( love.graphics.draw(
blocks[self.skin][self.colour], blocks[self.skin][self.shape],
64+x*16+partial_das, 16+y*16+gravity_offset 64+x*16+partial_das, 16+y*16+gravity_offset
) )
end end

View File

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

119
tetris/modes/credits_a3.lua Normal file
View File

@@ -0,0 +1,119 @@
require 'funcs'
local IntervalTrainingMode = require 'tetris.modes.interval_training'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local CreditsA3Game = IntervalTrainingMode:extend()
CreditsA3Game.name = "Credits A3"
CreditsA3Game.hash = "CreditsA3"
CreditsA3Game.tagline = "How consistently can you clear the Ti M-roll?"
function CreditsA3Game:new()
CreditsA3Game.super:new()
self.section_time_limit = 3238
self.norm = 0
self.section = 0
end
function CreditsA3Game:advanceOneFrame(inputs, ruleset)
if self.frames == 0 then
switchBGM("credit_roll", "gm3")
end
if self.roll_frames > 0 then
self.roll_frames = self.roll_frames - 1
if self.roll_frames == 0 then
-- reset
self.norm = 0
self.frames = 0
self.grid:clear()
switchBGM("credit_roll", "gm3")
self:initializeOrHold(inputs, ruleset)
else
return false
end
elseif self.ready_frames == 0 then
self.frames = self.frames + 1
if self:getSectionTime() >= self.section_time_limit then
self.norm = self.norm + 16
self.piece = nil
if self.norm >= 60 then
self.section = self.section + 1
self.roll_frames = 150
else
self.game_over = true
switchBGM(nil)
end
end
end
return true
end
function CreditsA3Game:onPieceEnter()
-- do nothing
end
function CreditsA3Game:onLineClear(cleared_row_count)
if not self.clear then
self.norm = self.norm + (cleared_row_count == 4 and 10 or cleared_row_count)
end
end
CreditsA3Game.rollOpacityFunction = function(age)
if age > 4 then return 0
else return 1 - age / 4 end
end
function CreditsA3Game:drawGrid(ruleset)
if not self.game_over and self.roll_frames < 30 then
self.grid:drawInvisible(self.rollOpacityFunction)
else
self.grid:draw()
end
end
function CreditsA3Game:drawScoringInfo()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_2)
love.graphics.print(
self.das.direction .. " " ..
self.das.frames .. " " ..
st(self.prev_inputs)
)
love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("TIME LEFT", 240, 250, 80, "left")
love.graphics.printf("NORM", 240, 320, 40, "left")
self:drawSectionTimesWithSplits(self.section)
love.graphics.setFont(font_3x5_3)
-- draw time left, flash red if necessary
local time_left = self.section_time_limit - math.max(self:getSectionTime(), 0)
if not self.game_over and not self.clear and time_left < sp(0,10) and time_left % 4 < 2 then
if self.norm >= 44 then
love.graphics.setColor(0.3, 1, 0.3, 1) -- flash green if goal has been cleared
else
love.graphics.setColor(1, 0.3, 0.3, 1) -- otherwise flash red
end
end
love.graphics.printf(formatTime(time_left), 240, 270, 160, "left")
love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(self.norm, 240, 340, 40, "right")
if self.game_over or self.roll_frames > 0 then
love.graphics.printf("60", 240, 370, 40, "right")
else
love.graphics.printf("44", 240, 370, 40, "right")
end
end
function CreditsA3Game:getBackground()
return self.section
end
return CreditsA3Game

View File

@@ -11,6 +11,9 @@ DemonModeGame.name = "Demon Mode"
DemonModeGame.hash = "DemonMode" DemonModeGame.hash = "DemonMode"
DemonModeGame.tagline = "Can you handle the ludicrous speed past level 20?" DemonModeGame.tagline = "Can you handle the ludicrous speed past level 20?"
function DemonModeGame:new() function DemonModeGame:new()
DemonModeGame.super:new() DemonModeGame.super:new()
self.roll_frames = 0 self.roll_frames = 0
@@ -26,9 +29,6 @@ function DemonModeGame:new()
self.enable_hold = true self.enable_hold = true
self.lock_drop = true self.lock_drop = true
self.next_queue_length = 3 self.next_queue_length = 3
if math.random() < 1/6.66 then
self.rpc_details = "Suffering"
end
end end
function DemonModeGame:getARE() function DemonModeGame:getARE()
@@ -159,25 +159,18 @@ function DemonModeGame:updateSectionTimes(old_level, new_level)
end end
elseif old_level < 100 then elseif old_level < 100 then
-- If section time is under cutoff, skip to level 500. -- If section time is under cutoff, skip to level 500.
if self.frames < frameTime(1,00) then if self.frames < sp(1,00) then
self.level = 500 self.level = 500
self.grade = 5 self.grade = 5
self.section_tries = 0 self.section_tries = 0
self.section_tetris_count = 0 self.section_tetris_count = 0
else else
self.level = math.min(new_level, 2500) self.level = math.min(new_level, 2500)
self.skip_failed = true
end end
-- record new section -- record new section
section_time = self.frames - self.section_start_time section_time = self.frames - self.section_start_time
table.insert(self.section_times, section_time) table.insert(self.section_times, section_time)
self.section_start_time = self.frames self.section_start_time = self.frames
else
self.level = math.min(new_level, 2500)
if self.skip_failed and new_level >= 500 then
self.level = 500
self.game_over = true
end
end end
else else
self.level = math.min(new_level, 2500) self.level = math.min(new_level, 2500)
@@ -233,10 +226,10 @@ function DemonModeGame:drawScoringInfo()
love.graphics.print( love.graphics.print(
self.das.direction .. " " .. self.das.direction .. " " ..
self.das.frames .. " " .. self.das.frames .. " " ..
strTrueValues(self.prev_inputs) st(self.prev_inputs)
) )
love.graphics.printf("NEXT", 64, 40, 40, "left") love.graphics.printf("NEXT", 64, 40, 40, "left")
if self.grade ~= 0 then love.graphics.printf("GRADE", 240, 120, 40, "left") end love.graphics.printf("GRADE", 240, 120, 40, "left")
love.graphics.printf("SCORE", 240, 200, 40, "left") love.graphics.printf("SCORE", 240, 200, 40, "left")
love.graphics.printf("LEVEL", 240, 320, 40, "left") love.graphics.printf("LEVEL", 240, 320, 40, "left")

View File

@@ -40,12 +40,9 @@ function GameMode:new()
self.draw_section_times = false self.draw_section_times = false
self.draw_secondary_section_times = false self.draw_secondary_section_times = false
self.big_mode = false self.big_mode = false
self.rpc_details = "In game"
-- variables related to configurable parameters -- variables related to configurable parameters
self.drop_locked = false self.drop_locked = false
self.hard_drop_locked = false self.hard_drop_locked = false
self.lock_on_soft_drop = false
self.lock_on_hard_drop = false
self.hold_queue = nil self.hold_queue = nil
self.held = false self.held = false
self.section_start_time = 0 self.section_start_time = 0
@@ -53,6 +50,13 @@ function GameMode:new()
self.secondary_section_times = { [0] = 0 } self.secondary_section_times = { [0] = 0 }
end end
function GameMode:initialize()
-- after all the variables are initialized, run initialization procedures
for i = 1, 30 do
table.insert(self.next_queue, self:getNextPiece(ruleset))
end
end
function GameMode:getARR() return 1 end function GameMode:getARR() return 1 end
function GameMode:getDropSpeed() return 1 end function GameMode:getDropSpeed() return 1 end
function GameMode:getARE() return 25 end function GameMode:getARE() return 25 end
@@ -62,7 +66,6 @@ function GameMode:getLineClearDelay() return 40 end
function GameMode:getDasLimit() return 15 end function GameMode:getDasLimit() return 15 end
function GameMode:getNextPiece(ruleset) function GameMode:getNextPiece(ruleset)
return { return {
skin = "2tie", skin = "2tie",
shape = self.randomizer:nextPiece(), shape = self.randomizer:nextPiece(),
@@ -72,12 +75,9 @@ end
function GameMode:initialize(ruleset) function GameMode:initialize(ruleset)
-- generate next queue -- generate next queue
self:new()
for i = 1, self.next_queue_length do for i = 1, self.next_queue_length do
table.insert(self.next_queue, self:getNextPiece(ruleset)) table.insert(self.next_queue, self:getNextPiece(ruleset))
end end
self.lock_on_soft_drop = ({ruleset.softdrop_lock, self.instant_soft_drop, false, true })[config.gamesettings.manlock]
self.lock_on_hard_drop = ({ruleset.harddrop_lock, self.instant_hard_drop, true, false})[config.gamesettings.manlock]
end end
function GameMode:update(inputs, ruleset) function GameMode:update(inputs, ruleset)
@@ -91,10 +91,20 @@ function GameMode:update(inputs, ruleset)
if self.completed then return end if self.completed then return end
-- advance one frame -- advance one frame
if self:advanceOneFrame(inputs) == false then return end 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["rotate_left"] or inputs["rotate_right"] or
inputs["rotate_left2"] or inputs["rotate_right2"] or
inputs["rotate_180"]
then
self:onAttemptPieceRotate(self.piece)
end
if self.piece == nil then if self.piece == nil then
self:processDelays(inputs, ruleset) self:processDelays(inputs, ruleset)
else else
@@ -131,7 +141,7 @@ function GameMode:update(inputs, ruleset)
self.piece:isDropBlocked(self.grid) and self.piece:isDropBlocked(self.grid) and
not self.hard_drop_locked then not self.hard_drop_locked then
self:onHardDrop(piece_dy) self:onHardDrop(piece_dy)
if self.lock_on_hard_drop then if self.instant_hard_drop then
self.piece.locked = true self.piece.locked = true
end end
end end
@@ -140,7 +150,7 @@ function GameMode:update(inputs, ruleset)
self:onSoftDrop(piece_dy) self:onSoftDrop(piece_dy)
if self.piece:isDropBlocked(self.grid) and if self.piece:isDropBlocked(self.grid) and
not self.drop_locked and not self.drop_locked and
self.lock_on_soft_drop self.instant_soft_drop
then then
self.piece.locked = true self.piece.locked = true
end end
@@ -152,6 +162,10 @@ function GameMode:update(inputs, ruleset)
local cleared_row_count = self.grid:getClearedRowCount() local cleared_row_count = self.grid:getClearedRowCount()
if self.big_mode then
cleared_row_count = cleared_row_count / 2
end
self:onPieceLock(self.piece, cleared_row_count) self:onPieceLock(self.piece, cleared_row_count)
self:updateScore(self.level, self.drop_bonus, cleared_row_count) self:updateScore(self.level, self.drop_bonus, cleared_row_count)
@@ -194,6 +208,8 @@ end
-- event functions -- event functions
function GameMode:whilePieceActive() end function GameMode:whilePieceActive() end
function GameMode:onAttemptPieceMove(piece) end
function GameMode:onAttemptPieceRotate(piece) end
function GameMode:onPieceLock(piece, cleared_row_count) end function GameMode:onPieceLock(piece, cleared_row_count) end
function GameMode:onLineClear(cleared_row_count) end function GameMode:onLineClear(cleared_row_count) end
function GameMode:onPieceEnter() end function GameMode:onPieceEnter() end
@@ -211,8 +227,25 @@ function GameMode:onGameOver()
switchBGM(nil) switchBGM(nil)
end end
function GameMode:chargeDAS(inputs) -- DAS functions
if inputs[self.das.direction] == true then
function GameMode:startRightDAS()
self.move = "right"
self.das = { direction = "right", frames = 0 }
if self:getDasLimit() == 0 then
self:continueDAS()
end
end
function GameMode:startLeftDAS()
self.move = "left"
self.das = { direction = "left", frames = 0 }
if self:getDasLimit() == 0 then
self:continueDAS()
end
end
function GameMode:continueDAS()
local das_frames = self.das.frames + 1 local das_frames = self.das.frames + 1
if das_frames >= self:getDasLimit() then if das_frames >= self:getDasLimit() then
if self.das.direction == "left" then if self.das.direction == "left" then
@@ -226,16 +259,35 @@ function GameMode:chargeDAS(inputs)
self.move = "none" self.move = "none"
self.das.frames = das_frames self.das.frames = das_frames
end end
elseif inputs["right"] == true then end
self.move = "right"
self.das = { direction = "right", frames = 0 } function GameMode:stopDAS()
elseif inputs["left"] == true then
self.move = "left"
self.das = { direction = "left", frames = 0 }
else
self.move = "none" self.move = "none"
self.das = { direction = "none", frames = -1 } self.das = { direction = "none", frames = -1 }
end end
function GameMode:chargeDAS(inputs)
if config["das_last_key"] then
if inputs["right"] == true and self.das.direction ~= "right" and not self.prev_inputs["right"] then
self:startRightDAS()
elseif inputs["left"] == true and self.das.direction ~= "left" and not self.prev_inputs["left"] then
self:startLeftDAS()
elseif inputs[self.das.direction] == true then
self:continueDAS()
else
self:stopDAS()
end
else -- default behaviour, das first key pressed
if inputs[self.das.direction] == true then
self:continueDAS()
elseif inputs["right"] == true then
self:startRightDAS()
elseif inputs["left"] == true then
self:startLeftDAS()
else
self:stopDAS()
end
end
end end
function GameMode:processDelays(inputs, ruleset, drop_speed) function GameMode:processDelays(inputs, ruleset, drop_speed)
@@ -346,12 +398,11 @@ function GameMode:drawGhostPiece(ruleset)
end end
function GameMode:drawNextQueue(ruleset) 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) function drawPiece(piece, skin, offsets, pos_x, pos_y)
for index, offset in pairs(offsets) do for index, offset in pairs(offsets) do
local x = offset.x + ruleset.spawn_positions[piece].x local x = ruleset.spawn_positions[piece].x + offset.x
local y = offset.y + 4.7 local y = ruleset.spawn_positions[piece].y + offset.y
love.graphics.draw(blocks[skin][colourscheme[piece]], pos_x+x*16, pos_y+y*16) love.graphics.draw(blocks[skin][piece], pos_x+x*16, pos_y+y*16)
end end
end end
for i = 1, self.next_queue_length do for i = 1, self.next_queue_length do
@@ -393,8 +444,7 @@ function GameMode:drawScoringInfo()
love.graphics.print( love.graphics.print(
self.das.direction .. " " .. self.das.direction .. " " ..
self.das.frames .. " " .. self.das.frames .. " " ..
strTrueValues(self.prev_inputs) .. st(self.prev_inputs)
self.drop_bonus
) )
love.graphics.setFont(font_8x11) love.graphics.setFont(font_8x11)
@@ -404,7 +454,7 @@ end
function GameMode:drawSectionTimes(current_section) function GameMode:drawSectionTimes(current_section)
local section_x = 530 local section_x = 530
for section, time in pairs(self.section_times) do for section, time in ipairs(self.section_times) do
if section > 0 then if section > 0 then
love.graphics.printf(formatTime(time), section_x, 40 + 20 * section, 90, "left") love.graphics.printf(formatTime(time), section_x, 40 + 20 * section, 90, "left")
end end
@@ -413,7 +463,7 @@ function GameMode:drawSectionTimes(current_section)
love.graphics.printf(formatTime(self.frames - self.section_start_time), section_x, 40 + 20 * current_section, 90, "left") love.graphics.printf(formatTime(self.frames - self.section_start_time), section_x, 40 + 20 * current_section, 90, "left")
end end
function GameMode:drawSectionTimesWithSecondary(current_section) function GameMode:drawSectionTimesWithSecondary(current_section, section_colour_function)
local section_x = 530 local section_x = 530
local section_secondary_x = 440 local section_secondary_x = 440
@@ -424,6 +474,9 @@ function GameMode:drawSectionTimesWithSecondary(current_section)
end end
for section, time in pairs(self.secondary_section_times) do for section, time in pairs(self.secondary_section_times) do
if self.section_colour_function then
love.graphics.setColor(self:section_colour_function(section))
end
if section > 0 then if section > 0 then
love.graphics.printf(formatTime(time), section_secondary_x, 40 + 20 * section, 90, "left") love.graphics.printf(formatTime(time), section_secondary_x, 40 + 20 * section, 90, "left")
end end

View File

@@ -28,19 +28,19 @@ function IntervalTrainingGame:new()
end end
function IntervalTrainingGame:getARE() function IntervalTrainingGame:getARE()
return 4 return 6
end end
function IntervalTrainingGame:getLineARE() function IntervalTrainingGame:getLineARE()
return 4 return 6
end end
function IntervalTrainingGame:getDasLimit() function IntervalTrainingGame:getDasLimit()
return 6 return 7
end end
function IntervalTrainingGame:getLineClearDelay() function IntervalTrainingGame:getLineClearDelay()
return 6 return 4
end end
function IntervalTrainingGame:getLockDelay() function IntervalTrainingGame:getLockDelay()
@@ -120,7 +120,7 @@ function IntervalTrainingGame:drawScoringInfo()
love.graphics.print( love.graphics.print(
self.das.direction .. " " .. self.das.direction .. " " ..
self.das.frames .. " " .. self.das.frames .. " " ..
strTrueValues(self.prev_inputs) st(self.prev_inputs)
) )
love.graphics.printf("NEXT", 64, 40, 40, "left") love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("TIME LEFT", 240, 250, 80, "left") love.graphics.printf("TIME LEFT", 240, 250, 80, "left")
@@ -130,16 +130,15 @@ function IntervalTrainingGame:drawScoringInfo()
self:drawSectionTimesWithSplits(current_section) self:drawSectionTimesWithSplits(current_section)
love.graphics.setFont(font_3x5_3) love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.level, 240, 340, 40, "right")
-- draw time left, flash red if necessary -- draw time left, flash red if necessary
local time_left = self.section_time_limit - math.max(self:getSectionTime(), 0) local time_left = self.section_time_limit - math.max(self:getSectionTime(), 0)
if not self.game_over and not self.clear and time_left < frameTime(0,10) and time_left % 4 < 2 then if not self.game_over and not self.clear and time_left < sp(0,10) and time_left % 4 < 2 then
love.graphics.setColor(1, 0.3, 0.3, 1) love.graphics.setColor(1, 0.3, 0.3, 1)
end end
love.graphics.printf(formatTime(time_left), 240, 270, 160, "left") love.graphics.printf(formatTime(time_left), 240, 270, 160, "left")
love.graphics.setColor(1, 1, 1, 1) love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(self.level, 240, 340, 40, "right")
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right") love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")
end end

View File

@@ -1,190 +0,0 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local KonohaRandomizer = require 'tetris.randomizers.bag_konoha'
local KonohaGame = GameMode:extend()
KonohaGame.name = "All Clear A4"
KonohaGame.hash = "AllClearA4"
KonohaGame.tagline = "Get as many bravos as you can under the time limit!"
function KonohaGame:new()
KonohaGame.super:new()
self.randomizer = KonohaRandomizer()
self.bravos = 0
self.last_bonus_amount = 0
self.last_bonus_display_time = 0
self.time_limit = 10800
self.big_mode = true
self.enable_hold = true
self.next_queue_length = 3
end
function KonohaGame:getARE()
if self.level < 300 then return 30
elseif self.level < 400 then return 25
elseif self.level < 500 then return 20
elseif self.level < 600 then return 17
elseif self.level < 800 then return 15
elseif self.level < 900 then return 13
elseif self.level < 1000 then return 10
elseif self.level < 1300 then return 8
else return 6 end
end
function KonohaGame:getLineARE()
return self:getARE()
end
function KonohaGame:getDasLimit()
if self.level < 500 then return 10
elseif self.level < 800 then return 9
elseif self.level < 1000 then return 8
else return 7 end
end
function KonohaGame:getLineClearDelay()
if self.level < 200 then return 14
elseif self.level < 500 then return 9
elseif self.level < 800 then return 8
elseif self.level < 1000 then return 7
else return 6 end
end
function KonohaGame:getLockDelay()
if self.level < 500 then return 30
elseif self.level < 600 then return 25
elseif self.level < 700 then return 23
elseif self.level < 800 then return 20
elseif self.level < 900 then return 17
elseif self.level < 1000 then return 15
elseif self.level < 1200 then return 13
elseif self.level < 1300 then return 10
else return 8 end
end
function KonohaGame:getGravity()
if (self.level < 30) then return 4/256
elseif (self.level < 35) then return 8/256
elseif (self.level < 40) then return 12/256
elseif (self.level < 50) then return 16/256
elseif (self.level < 60) then return 32/256
elseif (self.level < 70) then return 48/256
elseif (self.level < 80) then return 64/256
elseif (self.level < 90) then return 128/256
elseif (self.level < 100) then return 192/256
elseif (self.level < 120) then return 1
elseif (self.level < 140) then return 2
elseif (self.level < 160) then return 3
elseif (self.level < 170) then return 4
elseif (self.level < 200) then return 5
else return 20 end
end
function KonohaGame:getSection()
return math.floor(level / 100) + 1
end
function KonohaGame:getSectionEndLevel()
return math.floor(self.level / 100 + 1) * 100
end
function KonohaGame:advanceOneFrame()
if self.ready_frames == 0 then
self.time_limit = self.time_limit - 1
self.frames = self.frames + 1
end
if self.time_limit <= 0 then
self.game_over = true
end
self.last_bonus_display_time = self.last_bonus_display_time - 1
end
function KonohaGame:onPieceEnter()
if (self.level % 100 ~= 99) and self.frames ~= 0 then
self.level = self.level + 1
end
end
function KonohaGame:drawGrid(ruleset)
self.grid:draw()
if self.piece ~= nil and self.level < 100 then
self:drawGhostPiece(ruleset)
end
end
local cleared_row_levels = {2, 4, 6, 12}
local bravo_bonus = {300, 480, 660, 900}
local non_bravo_bonus = {0, 0, 20, 40}
local bravo_ot_bonus = {0, 60, 120, 180}
function KonohaGame:onLineClear(cleared_row_count)
local oldtime = self.time_limit
self.level = self.level + cleared_row_levels[cleared_row_count / 2]
if self.grid:checkForBravo(cleared_row_count) then
self.bravos = self.bravos + 1
if self.level < 1000 then self.time_limit = self.time_limit + bravo_bonus[cleared_row_count / 2]
else self.time_limit = self.time_limit + bravo_ot_bonus[cleared_row_count / 2]
end
if self.bravos == 11 then self.randomizer.allowrepeat = true end
elseif self.level < 1000 then
self.time_limit = self.time_limit + non_bravo_bonus[cleared_row_count / 2]
end
local bonus = self.time_limit - oldtime
if bonus > 0 then
self.last_bonus_amount = bonus
self.last_bonus_display_time = 120
end
end
function KonohaGame:getBackground()
return math.floor(self.level / 100)
end
function KonohaGame: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("TIME LIMIT", 240, 120, 120, "left")
love.graphics.printf("BRAVOS", 240, 200, 50, "left")
love.graphics.printf("LEVEL", 240, 320, 40, "left")
love.graphics.setFont(font_3x5_3)
if not self.game_over and self.time_limit < frameTime(0,10) and self.time_limit % 4 < 2 then
love.graphics.setColor(1, 0.3, 0.3, 1)
end
love.graphics.printf(formatTime(self.time_limit), 240, 140, 120, "right")
love.graphics.setColor(1, 1, 1, 1)
if self.last_bonus_display_time > 0 then
love.graphics.printf("+"..formatTime(self.last_bonus_amount), 240, 160, 120, "right")
end
love.graphics.printf(self.bravos, 240, 220, 90, "left")
love.graphics.printf(self.level, 240, 340, 50, "right")
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 50, "right")
love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")
end
function KonohaGame:getHighscoreData()
return {
bravos = self.bravos,
level = self.level,
frames = self.frames,
}
end
return KonohaGame

View File

@@ -151,11 +151,11 @@ function Marathon2020Game:advanceOneFrame()
end end
local cool_cutoffs = { local cool_cutoffs = {
frameTime(0,45,00), frameTime(0,41,50), frameTime(0,38,50), frameTime(0,35,00), frameTime(0,32,50), sp(0,45,00), sp(0,41,50), sp(0,38,50), sp(0,35,00), sp(0,32,50),
frameTime(0,29,20), frameTime(0,27,20), frameTime(0,24,80), frameTime(0,22,80), frameTime(0,20,60), sp(0,29,20), sp(0,27,20), sp(0,24,80), sp(0,22,80), sp(0,20,60),
frameTime(0,19,60), frameTime(0,19,40), frameTime(0,19,40), frameTime(0,18,40), frameTime(0,18,20), sp(0,19,60), sp(0,19,40), sp(0,19,40), sp(0,18,40), sp(0,18,20),
frameTime(0,16,20), frameTime(0,16,20), frameTime(0,16,20), frameTime(0,16,20), frameTime(0,16,20), sp(0,16,20), sp(0,16,20), sp(0,16,20), sp(0,16,20), sp(0,16,20),
frameTime(0,15,20) sp(0,15,20)
} }
local levels_for_cleared_rows = { 1, 2, 4, 6 } local levels_for_cleared_rows = { 1, 2, 4, 6 }
@@ -284,11 +284,11 @@ function Marathon2020Game:sectionPassed(old_level, new_level)
end end
function Marathon2020Game:checkTorikan(section) function Marathon2020Game:checkTorikan(section)
if section == 5 and self.frames < frameTime(6,00,00) then self.torikan_passed[500] = true end if section == 5 and self.frames < sp(6,00,00) then self.torikan_passed[500] = true end
if section == 9 and self.frames < frameTime(8,30,00) then self.torikan_passed[900] = true end if section == 9 and self.frames < sp(8,30,00) then self.torikan_passed[900] = true end
if section == 10 and self.frames < frameTime(8,45,00) then self.torikan_passed[1000] = true end if section == 10 and self.frames < sp(8,45,00) then self.torikan_passed[1000] = true end
if section == 15 and self.frames < frameTime(11,30,00) then self.torikan_passed[1500] = true end if section == 15 and self.frames < sp(11,30,00) then self.torikan_passed[1500] = true end
if section == 19 and self.frames < frameTime(13,15,00) then self.torikan_passed[1900] = true end if section == 19 and self.frames < sp(13,15,00) then self.torikan_passed[1900] = true end
end end
function Marathon2020Game:checkClear(level) function Marathon2020Game:checkClear(level)
@@ -323,9 +323,9 @@ function Marathon2020Game:checkClear(level)
end end
function Marathon2020Game:updateSectionTimes(old_level, new_level) function Marathon2020Game:updateSectionTimes(old_level, new_level)
function sectionCool() function sectionCool(section)
self.section_cool_count = self.section_cool_count + 1 self.section_cool_count = self.section_cool_count + 1
self.delay_level = math.min(20, self.delay_level + 1) if section < 10 then self.delay_level = math.min(20, self.delay_level + 1) end
table.insert(self.section_status, "cool") table.insert(self.section_status, "cool")
end end
@@ -343,7 +343,7 @@ function Marathon2020Game:updateSectionTimes(old_level, new_level)
table.insert(self.section_times, section_time) table.insert(self.section_times, section_time)
self.section_start_time = self.frames self.section_start_time = self.frames
if section > 4 then self.delay_level = math.min(20, self.delay_level + 1) end if section > 5 then self.delay_level = math.min(20, self.delay_level + 1) end
self:checkTorikan(section) self:checkTorikan(section)
self:checkClear(new_level) self:checkClear(new_level)
@@ -352,11 +352,11 @@ function Marathon2020Game:updateSectionTimes(old_level, new_level)
self.secondary_section_times[section] < self.secondary_section_times[section - 1] + 120 and self.secondary_section_times[section] < self.secondary_section_times[section - 1] + 120 and
self.secondary_section_times[section] < cool_cutoffs[section] self.secondary_section_times[section] < cool_cutoffs[section]
) then ) then
sectionCool() sectionCool(section)
elseif self.section_status[section - 1] == "cool" then elseif self.section_status[section - 1] == "cool" then
table.insert(self.section_status, "none") table.insert(self.section_status, "none")
elseif section <= 19 and self.secondary_section_times[section] < cool_cutoffs[section] then elseif section <= 19 and self.secondary_section_times[section] < cool_cutoffs[section] then
sectionCool() sectionCool(section)
else else
table.insert(self.section_status, "none") table.insert(self.section_status, "none")
end end
@@ -417,6 +417,14 @@ function Marathon2020Game:drawGrid()
end end
end end
function Marathon2020Game:sectionColourFunction(section)
if self.section_status[section] == "cool" then
return { 0, 1, 0, 1 }
else
return { 1, 1, 1, 1 }
end
end
function Marathon2020Game:drawScoringInfo() function Marathon2020Game:drawScoringInfo()
Marathon2020Game.super.drawScoringInfo(self) Marathon2020Game.super.drawScoringInfo(self)
@@ -428,7 +436,7 @@ function Marathon2020Game:drawScoringInfo()
love.graphics.printf("GRADE PTS.", text_x, 200, 90, "left") love.graphics.printf("GRADE PTS.", text_x, 200, 90, "left")
love.graphics.printf("LEVEL", text_x, 320, 40, "left") love.graphics.printf("LEVEL", text_x, 320, 40, "left")
self:drawSectionTimesWithSecondary(current_section) self:drawSectionTimesWithSecondary(current_section, self.sectionColourFunction)
love.graphics.setFont(font_3x5_3) love.graphics.setFont(font_3x5_3)
love.graphics.printf(self:getTotalGrade(), text_x, 120, 90, "left") love.graphics.printf(self:getTotalGrade(), text_x, 120, 90, "left")

View File

@@ -12,23 +12,16 @@ MarathonA1Game.hash = "MarathonA1"
MarathonA1Game.tagline = "Can you score enough points to reach the title of Grand Master?" MarathonA1Game.tagline = "Can you score enough points to reach the title of Grand Master?"
function MarathonA1Game:new() function MarathonA1Game:new()
MarathonA1Game.super:new() MarathonA1Game.super:new()
self.roll_frames = 0 self.roll_frames = 0
self.combo = 1 self.combo = 1
self.bravos = 0
self.gm_conditions = { self.gm_conditions = {
level300 = false, level300 = false,
level500 = false, level500 = false,
level999 = false level999 = false
} }
self.SGnames = {
"9", "8", "7", "6", "5", "4", "3", "2", "1",
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"GM"
}
self.randomizer = History4RollsRandomizer() self.randomizer = History4RollsRandomizer()
@@ -137,7 +130,8 @@ function MarathonA1Game:onLineClear(cleared_row_count)
self:checkGMRequirements(self.level, self.level + cleared_row_count) self:checkGMRequirements(self.level, self.level + cleared_row_count)
if not self.clear then if not self.clear then
local new_level = math.min(self.level + cleared_row_count, 999) local new_level = math.min(self.level + cleared_row_count, 999)
if self.level == 999 then if new_level == 999 then
self.level = 999
self.clear = true self.clear = true
else else
self.level = new_level self.level = new_level
@@ -146,15 +140,12 @@ function MarathonA1Game:onLineClear(cleared_row_count)
end end
function MarathonA1Game:updateScore(level, drop_bonus, cleared_lines) function MarathonA1Game:updateScore(level, drop_bonus, cleared_lines)
if self.grid:checkForBravo(cleared_lines) then if self.clear then return end
self.bravo = 4
self.bravos = self.bravos + 1
else self.bravo = 1 end
if cleared_lines > 0 then if cleared_lines > 0 then
self.combo = self.combo + (cleared_lines - 1) * 2 self.combo = self.combo + (cleared_lines - 1) * 2
self.score = self.score + ( self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) * (math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * self.combo * self.bravo cleared_lines * self.combo
) )
self.lines = self.lines + cleared_lines self.lines = self.lines + cleared_lines
else else
@@ -165,15 +156,15 @@ end
function MarathonA1Game:checkGMRequirements(old_level, new_level) function MarathonA1Game:checkGMRequirements(old_level, new_level)
if old_level < 300 and new_level >= 300 then if old_level < 300 and new_level >= 300 then
if self.score > 12000 and self.frames <= frameTime(4,15) then if self.score > 12000 and self.frames <= sp(4,15) then
self.gm_conditions["level300"] = true self.gm_conditions["level300"] = true
end end
elseif old_level < 500 and new_level >= 500 then elseif old_level < 500 and new_level >= 500 then
if self.score > 40000 and self.frames <= frameTime(7,30) then if self.score > 40000 and self.frames <= sp(7,30) then
self.gm_conditions["level500"] = true self.gm_conditions["level500"] = true
end end
elseif old_level < 999 and new_level >= 999 then elseif old_level < 999 and new_level >= 999 then
if self.score > 126000 and self.frames <= frameTime(13,30) then if self.score > 126000 and self.frames <= sp(13,30) then
self.gm_conditions["level900"] = true self.gm_conditions["level900"] = true
end end
end end
@@ -194,19 +185,13 @@ function MarathonA1Game:drawScoringInfo()
love.graphics.print( love.graphics.print(
self.das.direction .. " " .. self.das.direction .. " " ..
self.das.frames .. " " .. self.das.frames .. " " ..
strTrueValues(self.prev_inputs) st(self.prev_inputs)
) )
love.graphics.printf("NEXT", 64, 40, 40, "left") love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("GRADE", 240, 120, 40, "left") love.graphics.printf("GRADE", 240, 120, 40, "left")
love.graphics.printf("SCORE", 240, 200, 40, "left") love.graphics.printf("SCORE", 240, 200, 40, "left")
love.graphics.printf("NEXT RANK", 240, 260, 90, "left") love.graphics.printf("NEXT RANK", 240, 260, 90, "left")
love.graphics.printf("LEVEL", 240, 320, 40, "left") love.graphics.printf("LEVEL", 240, 320, 40, "left")
local sg = self.grid:checkSecretGrade()
if sg >= 5 then
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
end
if self.bravos > 0 then love.graphics.printf("BRAVO", 300, 120, 40, "left") end
love.graphics.setFont(font_3x5_3) love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.score, 240, 220, 90, "left") love.graphics.printf(self.score, 240, 220, 90, "left")
@@ -218,10 +203,6 @@ function MarathonA1Game:drawScoringInfo()
love.graphics.printf(getRankForScore(self.score).next, 240, 280, 90, "left") love.graphics.printf(getRankForScore(self.score).next, 240, 280, 90, "left")
love.graphics.printf(self.level, 240, 340, 40, "right") love.graphics.printf(self.level, 240, 340, 40, "right")
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right") love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")
if sg >= 5 then
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
end
if self.bravos > 0 then love.graphics.printf(self.bravos, 300, 140, 40, "left") end
love.graphics.setFont(font_8x11) love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center") love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")

View File

@@ -27,12 +27,6 @@ function MarathonA2Game:new()
self.section_times = { [0] = 0 } self.section_times = { [0] = 0 }
self.section_tetrises = { [0] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } self.section_tetrises = { [0] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
self.SGnames = {
"9", "8", "7", "6", "5", "4", "3", "2", "1",
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"GM"
}
self.randomizer = History6RollsRandomizer() self.randomizer = History6RollsRandomizer()
self.lock_drop = false self.lock_drop = false
@@ -142,14 +136,13 @@ end
function MarathonA2Game:updateScore(level, drop_bonus, cleared_lines) function MarathonA2Game:updateScore(level, drop_bonus, cleared_lines)
self:updateGrade(cleared_lines) self:updateGrade(cleared_lines)
if self.grid:checkForBravo(cleared_lines) then self.bravo = 4 else self.bravo = 1 end
if cleared_lines > 0 then if cleared_lines > 0 then
self.score = self.score + ( self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + 2 * drop_bonus) * (math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * self.combo * self.bravo cleared_lines * (cleared_lines * 2 - 1) * (self.combo * 2 - 1)
) )
self.lines = self.lines + cleared_lines self.lines = self.lines + cleared_lines
self.combo = self.combo + (cleared_lines - 1) * 2 self.combo = self.combo + cleared_lines - 1
else else
self.drop_bonus = 0 self.drop_bonus = 0
self.combo = 1 self.combo = 1
@@ -267,7 +260,7 @@ function MarathonA2Game:qualifiesForMRoll()
local section_average = 0 local section_average = 0
for section = 0, 4 do for section = 0, 4 do
section_average = section_average + self.section_times[section] section_average = section_average + self.section_times[section]
if self.section_times[section] > frameTime(1,05) then if self.section_times[section] > sp(1,05) then
return false return false
end end
end end
@@ -280,7 +273,7 @@ function MarathonA2Game:qualifiesForMRoll()
return false return false
end end
end end
if self.grade < 17 or self.frames > frameTime(9,30) then if self.grade < 17 or self.frames > sp(8,45) then
return false return false
end end
return true return true
@@ -332,25 +325,18 @@ function MarathonA2Game:drawScoringInfo()
love.graphics.print( love.graphics.print(
self.das.direction .. " " .. self.das.direction .. " " ..
self.das.frames .. " " .. self.das.frames .. " " ..
strTrueValues(self.prev_inputs) st(self.prev_inputs)
) )
love.graphics.printf("NEXT", 64, 40, 40, "left") love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("GRADE", 240, 120, 40, "left") love.graphics.printf("GRADE", 240, 120, 40, "left")
love.graphics.printf("SCORE", 240, 200, 40, "left") love.graphics.printf("SCORE", 240, 200, 40, "left")
love.graphics.printf("LEVEL", 240, 320, 40, "left") love.graphics.printf("LEVEL", 240, 320, 40, "left")
local sg = self.grid:checkSecretGrade()
if sg >= 5 then
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
end
love.graphics.setFont(font_3x5_3) love.graphics.setFont(font_3x5_3)
love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left") love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left")
love.graphics.printf(self.score, 240, 220, 90, "left") love.graphics.printf(self.score, 240, 220, 90, "left")
love.graphics.printf(self.level, 240, 340, 40, "right") love.graphics.printf(self.level, 240, 340, 40, "right")
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right") love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")
if sg >= 5 then
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
end
love.graphics.setFont(font_8x11) love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center") love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")

View File

@@ -32,19 +32,10 @@ function MarathonA3Game:new()
self.randomizer = History6RollsRandomizer() self.randomizer = History6RollsRandomizer()
self.SGnames = {
"9", "8", "7", "6", "5", "4", "3", "2", "1",
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"GM"
}
self.lock_drop = true self.lock_drop = true
self.lock_hard_drop = true self.lock_hard_drop = true
self.enable_hold = true self.enable_hold = true
self.next_queue_length = 3 self.next_queue_length = 3
self.coolregret_message = "COOL!!"
self.coolregret_timer = 0
end end
function MarathonA3Game:getARE() function MarathonA3Game:getARE()
@@ -169,13 +160,13 @@ function MarathonA3Game:onLineClear(cleared_row_count)
end end
local cool_cutoffs = { local cool_cutoffs = {
frameTime(0,52), frameTime(0,52), frameTime(0,49), frameTime(0,45), frameTime(0,45), sp(0,52), sp(0,52), sp(0,49), sp(0,45), sp(0,45),
frameTime(0,42), frameTime(0,42), frameTime(0,38), frameTime(0,38), sp(0,42), sp(0,42), sp(0,38), sp(0,38),
} }
local regret_cutoffs = { local regret_cutoffs = {
frameTime(0,90), frameTime(0,75), frameTime(0,75), frameTime(0,68), frameTime(0,60), sp(0,90), sp(0,75), sp(0,75), sp(0,68), sp(0,60),
frameTime(0,60), frameTime(0,50), frameTime(0,50), frameTime(0,50), frameTime(0,50), sp(0,60), sp(0,50), sp(0,50), sp(0,50), sp(0,50),
} }
function MarathonA3Game:updateSectionTimes(old_level, new_level) function MarathonA3Game:updateSectionTimes(old_level, new_level)
@@ -191,23 +182,17 @@ function MarathonA3Game:updateSectionTimes(old_level, new_level)
if section_time > regret_cutoffs[section] then if section_time > regret_cutoffs[section] then
self.section_cool_grade = self.section_cool_grade - 1 self.section_cool_grade = self.section_cool_grade - 1
table.insert(self.section_status, "regret") table.insert(self.section_status, "regret")
self.coolregret_message = "REGRET!!"
self.coolregret_timer = 300
elseif section <= 9 and self.section_status[section - 1] == "cool" and elseif section <= 9 and self.section_status[section - 1] == "cool" and
self.section_70_times[section] < self.section_70_times[section - 1] + 120 then self.section_70_times[section] < self.section_70_times[section - 1] + 120 then
self.section_cool_grade = self.section_cool_grade + 1 self.section_cool_grade = self.section_cool_grade + 1
self.speed_level = self.speed_level + 100 self.speed_level = self.speed_level + 100
table.insert(self.section_status, "cool") table.insert(self.section_status, "cool")
self.coolregret_message = "COOL!!"
self.coolregret_timer = 300
elseif self.section_status[section - 1] == "cool" then elseif self.section_status[section - 1] == "cool" then
table.insert(self.section_status, "none") table.insert(self.section_status, "none")
elseif section <= 9 and self.section_70_times[section] < cool_cutoffs[section] then elseif section <= 9 and self.section_70_times[section] < cool_cutoffs[section] then
self.section_cool_grade = self.section_cool_grade + 1 self.section_cool_grade = self.section_cool_grade + 1
self.speed_level = self.speed_level + 100 self.speed_level = self.speed_level + 100
table.insert(self.section_status, "cool") table.insert(self.section_status, "cool")
self.coolregret_message = "COOL!!"
self.coolregret_timer = 300
else else
table.insert(self.section_status, "none") table.insert(self.section_status, "none")
end end
@@ -387,16 +372,12 @@ function MarathonA3Game:drawScoringInfo()
love.graphics.print( love.graphics.print(
self.das.direction .. " " .. self.das.direction .. " " ..
self.das.frames .. " " .. self.das.frames .. " " ..
strTrueValues(self.prev_inputs) st(self.prev_inputs)
) )
love.graphics.printf("NEXT", 64, 40, 40, "left") love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("GRADE", 240, 120, 40, "left") love.graphics.printf("GRADE", 240, 120, 40, "left")
love.graphics.printf("SCORE", 240, 200, 40, "left") love.graphics.printf("SCORE", 240, 200, 40, "left")
love.graphics.printf("LEVEL", 240, 320, 40, "left") love.graphics.printf("LEVEL", 240, 320, 40, "left")
local sg = self.grid:checkSecretGrade()
if sg >= 5 then
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
end
-- draw section time data -- draw section time data
current_section = math.floor(self.level / 100) + 1 current_section = math.floor(self.level / 100) + 1
@@ -425,19 +406,11 @@ function MarathonA3Game:drawScoringInfo()
love.graphics.printf(formatTime(self.frames - self.section_start_time), current_x, 40 + 20 * current_section, 90, "left") love.graphics.printf(formatTime(self.frames - self.section_start_time), current_x, 40 + 20 * current_section, 90, "left")
if(self.coolregret_timer > 0) then
love.graphics.printf(self.coolregret_message, 64, 400, 160, "center")
self.coolregret_timer = self.coolregret_timer - 1
end
love.graphics.setFont(font_3x5_3) love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.score, 240, 220, 90, "left") love.graphics.printf(self.score, 240, 220, 90, "left")
love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left") love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left")
love.graphics.printf(self.level, 240, 340, 40, "right") love.graphics.printf(self.level, 240, 340, 40, "right")
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right") love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")
if sg >= 5 then
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
end
love.graphics.setFont(font_8x11) love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center") love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")

View File

@@ -0,0 +1,169 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local MarathonAXGame = GameMode:extend()
MarathonAXGame.name = "Marathon AX"
MarathonAXGame.hash = "MarathonAX"
MarathonAXGame.tagline = "Can you clear the time hurdles when the game goes this fast?"
function MarathonAXGame:new()
MarathonAXGame.super:new()
self.roll_frames = 0
self.randomizer = History6RollsRandomizer()
self.section_start_time = 0
self.section_times = { [0] = 0 }
self.section_clear = false
self.lock_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
function MarathonAXGame:getSectionTimeLimit()
if self.lines < 20 then return 7200
else return 5400 end
end
function MarathonAXGame:getARE()
return 27
end
function MarathonAXGame:getLineARE()
return self:getARE()
end
function MarathonAXGame:getDasLimit()
return 15
end
function MarathonAXGame:getLineClearDelay()
return 40
end
function MarathonAXGame:getLockDelay()
return 30
end
function MarathonAXGame:getGravity()
if self.lines < 10 then return 4/256
elseif self.lines < 20 then return 12/256
elseif self.lines < 30 then return 48/256
elseif self.lines < 40 then return 72/256
elseif self.lines < 50 then return 96/256
elseif self.lines < 60 then return 1/2
elseif self.lines < 70 then return 1
elseif self.lines < 80 then return 3/2
elseif self.lines < 90 then return 2
elseif self.lines < 100 then return 3
elseif self.lines < 110 then return 4
elseif self.lines < 120 then return 5
else return 20 end
end
function MarathonAXGame:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames < 0 then
return false
elseif self.roll_frames > 2968 then
self.completed = true
end
elseif self.ready_frames == 0 then
if not self.section_clear then
self.frames = self.frames + 1
end
if self:getSectionTime() >= self:getSectionTimeLimit() then
self.game_over = true
end
end
return true
end
function MarathonAXGame:onLineClear(cleared_row_count)
if not self.clear then
local new_lines = self.lines + cleared_row_count
self:updateSectionTimes(self.lines, new_lines)
self.lines = math.min(new_lines, 150)
if self.lines == 150 then
self.clear = true
self.roll_frames = -150
end
end
end
function MarathonAXGame:getSectionTime()
return self.frames - self.section_start_time
end
function MarathonAXGame: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())
self.section_start_time = self.frames
self.section_clear = true
end
end
function MarathonAXGame:onPieceEnter()
self.section_clear = false
end
function MarathonAXGame:drawGrid(ruleset)
self.grid:draw()
end
function MarathonAXGame:getHighscoreData()
return {
lines = self.lines,
frames = self.frames,
}
end
function MarathonAXGame:drawScoringInfo()
MarathonAXGame.super.drawScoringInfo(self)
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_2)
love.graphics.print(
self.das.direction .. " " ..
self.das.frames .. " " ..
st(self.prev_inputs)
)
love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("TIME LEFT", 240, 250, 80, "left")
love.graphics.printf("LINES", 240, 320, 40, "left")
local current_section = math.floor(self.lines / 10) + 1
self:drawSectionTimesWithSplits(current_section)
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.lines, 240, 340, 40, "right")
love.graphics.printf(self.clear and self.lines or self:getSectionEndLines(), 240, 370, 40, "right")
-- draw time left, flash red if necessary
local time_left = self:getSectionTimeLimit() - math.max(self:getSectionTime(), 0)
if not self.game_over and not self.clear and time_left < sp(0,10) and time_left % 4 < 2 then
love.graphics.setColor(1, 0.3, 0.3, 1)
end
love.graphics.printf(formatTime(time_left), 240, 270, 160, "left")
love.graphics.setColor(1, 1, 1, 1)
end
function MarathonAXGame:getSectionEndLines()
return math.floor(self.lines / 10 + 1) * 10
end
function MarathonAXGame:getBackground()
return math.floor(self.lines / 10)
end
return MarathonAXGame

View File

@@ -0,0 +1,42 @@
require 'funcs'
local MarathonAX = require 'tetris.modes.marathon_ax'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local MarathonAX2Game = MarathonAX:extend()
MarathonAX2Game.name = "Marathon AX2"
MarathonAX2Game.hash = "MarathonAX2"
MarathonAX2Game.tagline = "Can you clear the time hurdles when the game goes this fast?"
function MarathonAX2Game:new()
MarathonAX2Game.super:new()
self.roll_frames = 0
self.randomizer = History6RollsRandomizer()
self.section_time_limit = 3600
self.section_start_time = 0
self.section_times = { [0] = 0 }
self.section_clear = false
self.lock_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
function MarathonAX2Game:getGravity()
if self.lines < 10 then return 84/256
elseif self.lines < 20 then return 1/2
elseif self.lines < 30 then return 1
elseif self.lines < 40 then return 2
elseif self.lines < 50 then return 3
elseif self.lines < 60 then return 4
elseif self.lines < 70 then return 5
else return 20 end
end
return MarathonAX2Game

View File

@@ -0,0 +1,35 @@
require 'funcs'
local MarathonAX = require 'tetris.modes.marathon_ax'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local MarathonAX3Game = MarathonAX:extend()
MarathonAX3Game.name = "Marathon AX3"
MarathonAX3Game.hash = "MarathonAX3"
MarathonAX3Game.tagline = "Can you clear the time hurdles when the game goes this fast?"
function MarathonAX3Game:new()
MarathonAX3Game.super:new()
self.roll_frames = 0
self.randomizer = History6RollsRandomizer()
self.section_time_limit = 3600
self.section_start_time = 0
self.section_times = { [0] = 0 }
self.section_clear = false
self.lock_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
function MarathonAX3Game:getGravity()
return 20
end
return MarathonAX3Game

View File

@@ -1,174 +0,0 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local Bag7NoSZOStartRandomizer = require 'tetris.randomizers.bag7noSZOstart'
local MarathonAX4Game = GameMode:extend()
MarathonAX4Game.name = "Marathon AX4"
MarathonAX4Game.hash = "MarathonAX4"
MarathonAX4Game.tagline = "Can you clear the time hurdles when the game goes this fast?"
function MarathonAX4Game:new()
MarathonAX4Game.super:new()
self.roll_frames = 0
self.randomizer = Bag7NoSZOStartRandomizer()
self.section_time_limit = 3600
self.section_start_time = 0
self.section_times = { [0] = 0 }
self.section_clear = false
self.lock_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
function MarathonAX4Game:getARE()
if self.lines < 10 then return 18
elseif self.lines < 40 then return 14
elseif self.lines < 60 then return 12
elseif self.lines < 70 then return 10
elseif self.lines < 80 then return 8
elseif self.lines < 90 then return 7
else return 6 end
end
function MarathonAX4Game:getLineARE()
return self:getARE()
end
function MarathonAX4Game:getDasLimit()
if self.lines < 20 then return 10
elseif self.lines < 50 then return 9
elseif self.lines < 70 then return 8
else return 7 end
end
function MarathonAX4Game:getLineClearDelay()
if self.lines < 10 then return 14
elseif self.lines < 30 then return 9
else return 5 end
end
function MarathonAX4Game:getLockDelay()
if self.lines < 10 then return 28
elseif self.lines < 20 then return 24
elseif self.lines < 30 then return 22
elseif self.lines < 40 then return 20
elseif self.lines < 50 then return 18
elseif self.lines < 70 then return 14
else return 13 end
end
function MarathonAX4Game:getGravity()
return 20
end
function MarathonAX4Game:getSection()
return math.floor(level / 100) + 1
end
function MarathonAX4Game:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames < 0 then
return false
elseif self.roll_frames > 2968 then
self.completed = true
end
elseif self.ready_frames == 0 then
if not self.section_clear then
self.frames = self.frames + 1
end
if self:getSectionTime() >= self.section_time_limit then
self.game_over = true
end
end
return true
end
function MarathonAX4Game:onLineClear(cleared_row_count)
if not self.clear then
local new_lines = self.lines + cleared_row_count
self:updateSectionTimes(self.lines, new_lines)
self.lines = math.min(new_lines, 150)
if self.lines == 150 then
self.clear = true
self.roll_frames = -150
end
end
end
function MarathonAX4Game:getSectionTime()
return self.frames - self.section_start_time
end
function MarathonAX4Game:updateSectionTimes(old_lines, new_lines)
if math.floor(old_lines / 10) < math.floor(new_lines / 10) then
-- record new section
table.insert(self.section_times, self:getSectionTime())
self.section_start_time = self.frames
self.section_clear = true
end
end
function MarathonAX4Game:onPieceEnter()
self.section_clear = false
end
function MarathonAX4Game:drawGrid(ruleset)
self.grid:draw()
end
function MarathonAX4Game:getHighscoreData()
return {
lines = self.lines,
frames = self.frames,
}
end
function MarathonAX4Game:drawScoringInfo()
MarathonAX4Game.super.drawScoringInfo(self)
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("TIME LEFT", 240, 250, 80, "left")
love.graphics.printf("LINES", 240, 320, 40, "left")
local current_section = math.floor(self.lines / 10) + 1
self:drawSectionTimesWithSplits(current_section)
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.lines, 240, 340, 40, "right")
love.graphics.printf(self.clear and self.lines or self:getSectionEndLines(), 240, 370, 40, "right")
-- draw time left, flash red if necessary
local time_left = self.section_time_limit - math.max(self:getSectionTime(), 0)
if not self.game_over and not self.clear and time_left < frameTime(0,10) and time_left % 4 < 2 then
love.graphics.setColor(1, 0.3, 0.3, 1)
end
love.graphics.printf(formatTime(time_left), 240, 270, 160, "left")
love.graphics.setColor(1, 1, 1, 1)
end
function MarathonAX4Game:getSectionEndLines()
return math.floor(self.lines / 10 + 1) * 10
end
function MarathonAX4Game:getBackground()
return math.floor(self.lines / 10)
end
return MarathonAX4Game

View File

@@ -156,7 +156,7 @@ function MarathonC89Game:drawScoringInfo()
love.graphics.print( love.graphics.print(
self.das.direction .. " " .. self.das.direction .. " " ..
self.das.frames .. " " .. self.das.frames .. " " ..
strTrueValues(self.prev_inputs) st(self.prev_inputs)
) )
love.graphics.printf("NEXT", 64, 40, 40, "left") love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("LINES", 240, 120, 40, "left") love.graphics.printf("LINES", 240, 120, 40, "left")

View File

@@ -0,0 +1,133 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local MarathonWCBGame = GameMode:extend()
MarathonWCBGame.name = "Marathon WCB"
MarathonWCBGame.hash = "MarathonWCB"
MarathonWCBGame.tagline = "When all the pieces slip right to their destinations... can you keep up?"
function MarathonWCBGame:new()
MarathonWCBGame.super:new()
self.pieces = 0
self.randomizer = History6RollsRandomizer()
self.lock_drop = true
self.lock_hard_drop = true
self.instant_hard_drop = true
self.instant_soft_drop = true
self.enable_hold = false
self.next_queue_length = 3
self.piece_is_active = false
end
function MarathonWCBGame:getDropSpeed()
return 20
end
function MarathonWCBGame:getARR()
return 0
end
function MarathonWCBGame:getARE()
return 0
end
function MarathonWCBGame:getLineARE()
return 0
end
function MarathonWCBGame:getDasLimit()
return 0
end
function MarathonWCBGame:getLineClearDelay()
return 0
end
function MarathonWCBGame:getLockDelay()
return math.huge
end
function MarathonWCBGame:getGravity()
return self.piece_is_active and 20 or 0
end
function MarathonWCBGame:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames > 150 then
self.completed = true
end
return false
elseif self.ready_frames == 0 then
self.frames = self.frames + 1
end
return true
end
function MarathonWCBGame:onAttemptPieceMove()
if self.piece ~= nil then
-- don't let the piece move before it's finished dropping
self.piece:dropToBottom(self.grid)
end
self.piece_is_active = true
end
function MarathonWCBGame:onAttemptPieceRotate()
self.piece_is_active = true
end
function MarathonWCBGame:onPieceLock()
self.piece_is_active = false
self.pieces = self.pieces + 1
end
function MarathonWCBGame:onLineClear(cleared_row_count)
if not self.clear then
self.lines = self.lines + cleared_row_count
end
end
function MarathonWCBGame:drawGrid(ruleset)
self.grid:draw()
if self.piece ~= nil then
self:drawGhostPiece(ruleset)
end
end
function MarathonWCBGame:getHighscoreData()
return {
pieces = self.pieces,
frames = self.frames,
}
end
function MarathonWCBGame:drawScoringInfo()
MarathonWCBGame.super.drawScoringInfo(self)
love.graphics.setColor(1, 1, 1, 1)
local text_x = config["side_next"] and 320 or 240
love.graphics.setFont(font_3x5_2)
love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("lines", text_x, 160, 80, "left")
love.graphics.printf("pieces", text_x, 220, 80, "left")
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.lines, text_x, 180, 80, "left")
love.graphics.printf(self.pieces, text_x, 240, 80, "left")
end
function MarathonWCBGame:getBackground()
return (math.floor(self.pieces / 50) % 20)
end
return MarathonWCBGame

View File

@@ -17,12 +17,6 @@ function PhantomManiaGame:new()
self.lock_drop = true self.lock_drop = true
self.next_queue_length = 1 self.next_queue_length = 1
self.SGnames = {
"9", "8", "7", "6", "5", "4", "3", "2", "1",
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"GM"
}
self.roll_frames = 0 self.roll_frames = 0
self.combo = 1 self.combo = 1
self.randomizer = History6RollsRandomizer() self.randomizer = History6RollsRandomizer()
@@ -67,15 +61,15 @@ function PhantomManiaGame:getGravity()
end end
function PhantomManiaGame:hitTorikan(old_level, new_level) function PhantomManiaGame:hitTorikan(old_level, new_level)
if old_level < 300 and new_level >= 300 and self.frames > frameTime(2,28) then if old_level < 300 and new_level >= 300 and self.frames > sp(2,28) then
self.level = 300 self.level = 300
return true return true
end end
if old_level < 500 and new_level >= 500 and self.frames > frameTime(3,38) then if old_level < 500 and new_level >= 500 and self.frames > sp(3,38) then
self.level = 500 self.level = 500
return true return true
end end
if old_level < 800 and new_level >= 800 and self.frames > frameTime(5,23) then if old_level < 800 and new_level >= 800 and self.frames > sp(5,23) then
self.level = 800 self.level = 800
return true return true
end end
@@ -167,16 +161,12 @@ function PhantomManiaGame:drawScoringInfo()
local text_x = config["side_next"] and 320 or 240 local text_x = config["side_next"] and 320 or 240
love.graphics.setFont(font_3x5_2) love.graphics.setFont(font_3x5_2)
if getLetterGrade(self.level, self.clear) ~= "" then love.graphics.printf("GRADE", text_x, 120, 40, "left") end love.graphics.printf("GRADE", text_x, 120, 40, "left")
love.graphics.printf("SCORE", text_x, 200, 40, "left") love.graphics.printf("SCORE", text_x, 200, 40, "left")
love.graphics.printf("LEVEL", text_x, 320, 40, "left") love.graphics.printf("LEVEL", text_x, 320, 40, "left")
local sg = self.grid:checkSecretGrade()
if sg >= 5 then
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
end
love.graphics.setFont(font_3x5_3) love.graphics.setFont(font_3x5_3)
if getLetterGrade(self.level, self.clear) ~= "" then love.graphics.printf(getLetterGrade(self.level, self.clear), text_x, 140, 90, "left") end love.graphics.printf(getLetterGrade(self.level, self.clear), text_x, 140, 90, "left")
love.graphics.printf(self.score, text_x, 220, 90, "left") love.graphics.printf(self.score, text_x, 220, 90, "left")
love.graphics.printf(self.level, text_x, 340, 40, "right") love.graphics.printf(self.level, text_x, 340, 40, "right")
if self.clear then if self.clear then
@@ -185,9 +175,6 @@ function PhantomManiaGame:drawScoringInfo()
love.graphics.printf(self:getSectionEndLevel(), text_x, 370, 40, "right") love.graphics.printf(self:getSectionEndLevel(), text_x, 370, 40, "right")
end end
if sg >= 5 then
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
end
end end
function PhantomManiaGame:getSectionEndLevel() function PhantomManiaGame:getSectionEndLevel()

View File

@@ -27,12 +27,6 @@ function PhantomMania2Game:new()
self.queue_age = 0 self.queue_age = 0
self.roll_points = 0 self.roll_points = 0
self.SGnames = {
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m9",
"GM"
}
self.randomizer = History6RollsRandomizer() self.randomizer = History6RollsRandomizer()
self.lock_drop = true self.lock_drop = true
@@ -92,19 +86,19 @@ function PhantomMania2Game:getNextPiece(ruleset)
end end
function PhantomMania2Game:hitTorikan(old_level, new_level) function PhantomMania2Game:hitTorikan(old_level, new_level)
if old_level < 300 and new_level >= 300 and self.frames > frameTime(2,02) then if old_level < 300 and new_level >= 300 and self.frames > sp(2,02) then
self.level = 300 self.level = 300
return true return true
end end
if old_level < 500 and new_level >= 500 and self.frames > frameTime(3,03) then if old_level < 500 and new_level >= 500 and self.frames > sp(3,03) then
self.level = 500 self.level = 500
return true return true
end end
if old_level < 800 and new_level >= 800 and self.frames > frameTime(4,45) then if old_level < 800 and new_level >= 800 and self.frames > sp(4,45) then
self.level = 800 self.level = 800
return true return true
end end
if old_level < 1000 and new_level >= 1000 and self.frames > frameTime(5,38) then if old_level < 1000 and new_level >= 1000 and self.frames > sp(5,38) then
self.level = 1000 self.level = 1000
return true return true
end end
@@ -162,7 +156,7 @@ function PhantomMania2Game:onLineClear(cleared_row_count)
end end
self:advanceBottomRow(-cleared_row_count) self:advanceBottomRow(-cleared_row_count)
else else
self.roll_points = self.roll_points + cleared_row_points[cleared_row_count / 2] self.roll_points = self.roll_points + cleared_row_points[cleared_row_count]
if self.roll_points >= 100 then if self.roll_points >= 100 then
self.roll_points = self.roll_points - 100 self.roll_points = self.roll_points - 100
self.grade = self.grade + 1 self.grade = self.grade + 1
@@ -194,15 +188,15 @@ end
local cool_cutoffs = { local cool_cutoffs = {
frameTime(0,36), frameTime(0,36), frameTime(0,36), frameTime(0,36), frameTime(0,36), sp(0,36), sp(0,36), sp(0,36), sp(0,36), sp(0,36),
frameTime(0,30), frameTime(0,30), frameTime(0,30), frameTime(0,30), frameTime(0,30), sp(0,30), sp(0,30), sp(0,30), sp(0,30), sp(0,30),
frameTime(0,27), frameTime(0,27), frameTime(0,27), sp(0,27), sp(0,27), sp(0,27),
} }
local regret_cutoffs = { local regret_cutoffs = {
frameTime(0,50), frameTime(0,50), frameTime(0,50), frameTime(0,50), frameTime(0,50), sp(0,50), sp(0,50), sp(0,50), sp(0,50), sp(0,50),
frameTime(0,40), frameTime(0,40), frameTime(0,40), frameTime(0,40), frameTime(0,40), sp(0,40), sp(0,40), sp(0,40), sp(0,40), sp(0,40),
frameTime(0,35), frameTime(0,35), frameTime(0,35), sp(0,35), sp(0,35), sp(0,35),
} }
function PhantomMania2Game:updateSectionTimes(old_level, new_level) function PhantomMania2Game:updateSectionTimes(old_level, new_level)
@@ -240,7 +234,7 @@ PhantomMania2Game.garbageOpacityFunction = function(age)
end end
function PhantomMania2Game:drawGrid() function PhantomMania2Game:drawGrid()
if not (self.game_over or (self.clear and self.level < 1300)) then if not (self.game_over) then
self.grid:drawInvisible(self.rollOpacityFunction, self.garbageOpacityFunction) self.grid:drawInvisible(self.rollOpacityFunction, self.garbageOpacityFunction)
else else
self.grid:draw() self.grid:draw()
@@ -291,10 +285,6 @@ function PhantomMania2Game:drawScoringInfo()
love.graphics.printf("GRADE", text_x, 120, 40, "left") love.graphics.printf("GRADE", text_x, 120, 40, "left")
love.graphics.printf("SCORE", text_x, 200, 40, "left") love.graphics.printf("SCORE", text_x, 200, 40, "left")
love.graphics.printf("LEVEL", text_x, 320, 40, "left") love.graphics.printf("LEVEL", text_x, 320, 40, "left")
local sg = self.grid:checkSecretGrade()
if sg >= 5 then
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
end
love.graphics.setFont(font_3x5_3) love.graphics.setFont(font_3x5_3)
love.graphics.printf(getLetterGrade(math.floor(self.grade)), text_x, 140, 90, "left") love.graphics.printf(getLetterGrade(math.floor(self.grade)), text_x, 140, 90, "left")
@@ -305,10 +295,6 @@ function PhantomMania2Game:drawScoringInfo()
else else
love.graphics.printf(math.floor(self.level / 100 + 1) * 100, text_x, 370, 50, "right") love.graphics.printf(math.floor(self.level / 100 + 1) * 100, text_x, 370, 50, "right")
end end
if sg >= 5 then
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
end
end end
function PhantomMania2Game:getBackground() function PhantomMania2Game:getBackground()

View File

@@ -9,12 +9,6 @@ PhantomManiaNGame.tagline = "The old mode from Nullpomino, for Ti-ARS and SRS su
function PhantomManiaNGame:new() function PhantomManiaNGame:new()
PhantomManiaNGame.super:new() PhantomManiaNGame.super:new()
self.SGnames = {
"M1", "M2", "M3", "M4", "M5", "M6", "M7", "M8", "M9",
"M10", "M11", "M12", "M13", "M14", "M15", "M16", "M17", "M18",
"GM"
}
self.next_queue_length = 3 self.next_queue_length = 3
self.enable_hold = true self.enable_hold = true
end end

View File

@@ -3,7 +3,7 @@ require 'funcs'
local GameMode = require 'tetris.modes.gamemode' local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece' local Piece = require 'tetris.components.piece'
local Bag7Randomiser = require 'tetris.randomizers.bag7noSZOstart' local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local Race40Game = GameMode:extend() local Race40Game = GameMode:extend()
@@ -18,14 +18,14 @@ function Race40Game:new()
self.lines = 0 self.lines = 0
self.line_goal = 40 self.line_goal = 40
self.pieces = 0 self.pieces = 0
self.randomizer = Bag7Randomiser() self.randomizer = History6RollsRandomizer()
self.roll_frames = 0 self.roll_frames = 0
self.lock_drop = true self.lock_drop = true
self.lock_hard_drop = true self.lock_hard_drop = true
self.instant_hard_drop = true self.instant_hard_drop = true
self.instant_soft_drop = false self.instant_soft_drop = true
self.enable_hold = true self.enable_hold = true
self.next_queue_length = 3 self.next_queue_length = 3
end end
@@ -39,11 +39,11 @@ function Race40Game:getARR()
end end
function Race40Game:getARE() function Race40Game:getARE()
return 0 return 4
end end
function Race40Game:getLineARE() function Race40Game:getLineARE()
return self:getARE() return 2
end end
function Race40Game:getDasLimit() function Race40Game:getDasLimit()
@@ -51,15 +51,15 @@ function Race40Game:getDasLimit()
end end
function Race40Game:getLineClearDelay() function Race40Game:getLineClearDelay()
return 0 return 2
end end
function Race40Game:getLockDelay() function Race40Game:getLockDelay()
return 15 return 30
end end
function Race40Game:getGravity() function Race40Game:getGravity()
return 1/64 return 20
end end
function Race40Game:advanceOneFrame() function Race40Game:advanceOneFrame()

View File

@@ -102,15 +102,15 @@ function Survival2020Game:getNextPiece(ruleset)
end end
function Survival2020Game:hitTorikan(old_level, new_level) function Survival2020Game:hitTorikan(old_level, new_level)
if old_level < 500 and new_level >= 500 and self.frames > frameTime(3,00) then if old_level < 500 and new_level >= 500 and self.frames > sp(3,00) then
self.level = 500 self.level = 500
return true return true
end end
if old_level < 1000 and new_level >= 1000 and self.frames > frameTime(5,00) then if old_level < 1000 and new_level >= 1000 and self.frames > sp(5,00) then
self.level = 1000 self.level = 1000
return true return true
end end
if old_level < 1500 and new_level >= 1500 and self.frames > frameTime(7,00) then if old_level < 1500 and new_level >= 1500 and self.frames > sp(7,00) then
self.level = 1500 self.level = 1500
return true return true
end end
@@ -193,7 +193,7 @@ function Survival2020Game:updateSectionTimes(old_level, new_level)
section_time = self.frames - self.section_start_time section_time = self.frames - self.section_start_time
table.insert(self.section_times, section_time) table.insert(self.section_times, section_time)
self.section_start_time = self.frames self.section_start_time = self.frames
if section_time <= frameTime(0,30) then if section_time <= sp(0,30) then
self.grade = self.grade + 2 self.grade = self.grade + 2
else else
self.grade = self.grade + 1 self.grade = self.grade + 1

View File

@@ -19,20 +19,12 @@ function SurvivalA1Game:new()
self.roll_frames = 0 self.roll_frames = 0
self.combo = 1 self.combo = 1
self.bravos = 0
self.gm_conditions = { self.gm_conditions = {
level300 = false, level300 = false,
level500 = false, level500 = false,
level999 = false level999 = false
} }
self.SGnames = {
"9", "8", "7", "6", "5", "4", "3", "2", "1",
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"GM"
}
self.randomizer = History4RollsRandomizer() self.randomizer = History4RollsRandomizer()
self.lock_drop = false self.lock_drop = false
@@ -118,14 +110,10 @@ function SurvivalA1Game:onLineClear(cleared_row_count)
end end
function SurvivalA1Game:updateScore(level, drop_bonus, cleared_lines) function SurvivalA1Game:updateScore(level, drop_bonus, cleared_lines)
if self.grid:checkForBravo(cleared_lines) then
self.bravo = 4
self.bravos = self.bravos + 1
else self.bravo = 1 end
if cleared_lines > 0 then if cleared_lines > 0 then
self.score = self.score + ( self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) * (math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * self.bravo * self.combo cleared_lines * (cleared_lines * 2 - 1) * self.combo
) )
self.lines = self.lines + cleared_lines self.lines = self.lines + cleared_lines
self.combo = self.combo + (cleared_lines - 1) * 2 self.combo = self.combo + (cleared_lines - 1) * 2
@@ -137,15 +125,15 @@ end
function SurvivalA1Game:checkGMRequirements(old_level, new_level) function SurvivalA1Game:checkGMRequirements(old_level, new_level)
if old_level < 300 and new_level >= 300 then if old_level < 300 and new_level >= 300 then
if self.score > 12000 and self.frames <= frameTime(4,15) then if self.score > 12000 and self.frames <= sp(4,15) then
self.gm_conditions["level300"] = true self.gm_conditions["level300"] = true
end end
elseif old_level < 500 and new_level >= 500 then elseif old_level < 500 and new_level >= 500 then
if self.score > 40000 and self.frames <= frameTime(7,30) then if self.score > 40000 and self.frames <= sp(7,30) then
self.gm_conditions["level500"] = true self.gm_conditions["level500"] = true
end end
elseif old_level < 999 and new_level >= 999 then elseif old_level < 999 and new_level >= 999 then
if self.score > 126000 and self.frames <= frameTime(13,30) then if self.score > 126000 and self.frames <= sp(13,30) then
self.gm_conditions["level900"] = true self.gm_conditions["level900"] = true
end end
end end
@@ -163,19 +151,13 @@ function SurvivalA1Game:drawScoringInfo()
love.graphics.print( love.graphics.print(
self.das.direction .. " " .. self.das.direction .. " " ..
self.das.frames .. " " .. self.das.frames .. " " ..
strTrueValues(self.prev_inputs) st(self.prev_inputs)
) )
love.graphics.printf("NEXT", 64, 40, 40, "left") love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("GRADE", 240, 120, 40, "left") love.graphics.printf("GRADE", 240, 120, 40, "left")
love.graphics.printf("SCORE", 240, 200, 40, "left") love.graphics.printf("SCORE", 240, 200, 40, "left")
love.graphics.printf("NEXT RANK", 240, 260, 90, "left") love.graphics.printf("NEXT RANK", 240, 260, 90, "left")
love.graphics.printf("LEVEL", 240, 320, 40, "left") love.graphics.printf("LEVEL", 240, 320, 40, "left")
local sg = self.grid:checkSecretGrade()
if sg >= 5 then
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
end
if self.bravos > 0 then love.graphics.printf("BRAVO", 300, 120, 40, "left") end
love.graphics.setFont(font_3x5_3) love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.score, 240, 220, 90, "left") love.graphics.printf(self.score, 240, 220, 90, "left")
@@ -187,10 +169,6 @@ function SurvivalA1Game:drawScoringInfo()
love.graphics.printf(getRankForScore(self.score).next, 240, 280, 90, "left") love.graphics.printf(getRankForScore(self.score).next, 240, 280, 90, "left")
love.graphics.printf(self.level, 240, 340, 40, "right") love.graphics.printf(self.level, 240, 340, 40, "right")
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right") love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")
if sg >= 5 then
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
end
if self.bravos > 0 then love.graphics.printf(self.bravos, 300, 140, 40, "left") end
love.graphics.setFont(font_8x11) love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center") love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")

View File

@@ -20,12 +20,6 @@ function SurvivalA2Game:new()
self.combo = 1 self.combo = 1
self.randomizer = History6RollsRandomizer() self.randomizer = History6RollsRandomizer()
self.SGnames = {
"9", "8", "7", "6", "5", "4", "3", "2", "1",
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"GM"
}
self.lock_drop = true self.lock_drop = true
end end
@@ -68,7 +62,7 @@ function SurvivalA2Game:getGravity()
end end
function SurvivalA2Game:hitTorikan(old_level, new_level) function SurvivalA2Game:hitTorikan(old_level, new_level)
if old_level < 500 and new_level >= 500 and self.frames > frameTime(3,25) then if old_level < 500 and new_level >= 500 and self.frames > sp(3,25) then
self.level = 500 self.level = 500
return true return true
end end
@@ -105,11 +99,10 @@ function SurvivalA2Game:onLineClear(cleared_row_count)
end end
function SurvivalA2Game:updateScore(level, drop_bonus, cleared_lines) function SurvivalA2Game:updateScore(level, drop_bonus, cleared_lines)
if self.grid:checkForBravo(cleared_lines) then self.bravo = 4 else self.bravo = 1 end
if cleared_lines > 0 then if cleared_lines > 0 then
self.score = self.score + ( self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) * (math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * self.bravo * self.combo cleared_lines * (cleared_lines * 2 - 1) * self.combo
) )
self.lines = self.lines + cleared_lines self.lines = self.lines + cleared_lines
self.combo = self.combo + (cleared_lines - 1) * 2 self.combo = self.combo + (cleared_lines - 1) * 2
@@ -139,25 +132,18 @@ function SurvivalA2Game:drawScoringInfo()
love.graphics.print( love.graphics.print(
self.das.direction .. " " .. self.das.direction .. " " ..
self.das.frames .. " " .. self.das.frames .. " " ..
strTrueValues(self.prev_inputs) st(self.prev_inputs)
) )
love.graphics.printf("NEXT", 64, 40, 40, "left") love.graphics.printf("NEXT", 64, 40, 40, "left")
if self:getLetterGrade() ~= "" then love.graphics.printf("GRADE", text_x, 120, 40, "left") end love.graphics.printf("GRADE", text_x, 120, 40, "left")
love.graphics.printf("SCORE", text_x, 200, 40, "left") love.graphics.printf("SCORE", text_x, 200, 40, "left")
love.graphics.printf("LEVEL", text_x, 320, 40, "left") love.graphics.printf("LEVEL", text_x, 320, 40, "left")
local sg = self.grid:checkSecretGrade()
if sg >= 5 then
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
end
love.graphics.setFont(font_3x5_3) love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.score, text_x, 220, 90, "left") love.graphics.printf(self.score, text_x, 220, 90, "left")
if self:getLetterGrade() ~= "" then love.graphics.printf(self:getLetterGrade(), text_x, 140, 90, "left") end love.graphics.printf(self:getLetterGrade(), text_x, 140, 90, "left")
love.graphics.printf(self.level, text_x, 340, 40, "right") love.graphics.printf(self.level, text_x, 340, 40, "right")
love.graphics.printf(self:getSectionEndLevel(), text_x, 370, 40, "right") love.graphics.printf(self:getSectionEndLevel(), text_x, 370, 40, "right")
if sg >= 5 then
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
end
end end
function SurvivalA2Game:getSectionEndLevel() function SurvivalA2Game:getSectionEndLevel()

View File

@@ -25,26 +25,9 @@ function SurvivalA3Game:new()
self.combo = 1 self.combo = 1
self.randomizer = History6RollsRandomizer() self.randomizer = History6RollsRandomizer()
self.SGnames = {
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m9",
"GM"
}
self.lock_drop = true self.lock_drop = true
self.enable_hold = true self.enable_hold = true
self.next_queue_length = 3 self.next_queue_length = 3
self.coolregret_message = "COOL!!"
self.coolregret_timer = 0
end
function SurvivalA3Game:initialize(ruleset)
self.torikan_time = frameTime(2,28)
if ruleset.world then self.torikan_time = frameTime(3,03) end
self.super.initialize(self, ruleset)
-- ^ notice the . here instead of the :
end end
function SurvivalA3Game:getARE() function SurvivalA3Game:getARE()
@@ -103,11 +86,11 @@ function SurvivalA3Game:getNextPiece(ruleset)
end end
function SurvivalA3Game:hitTorikan(old_level, new_level) function SurvivalA3Game:hitTorikan(old_level, new_level)
if old_level < 500 and new_level >= 500 and self.frames > self.torikan_time then if old_level < 500 and new_level >= 500 and self.frames > sp(2,28) then
self.level = 500 self.level = 500
return true return true
end end
if old_level < 1000 and new_level >= 1000 and self.frames > self.torikan_time*2 then if old_level < 1000 and new_level >= 1000 and self.frames > sp(4,56) then
self.level = 1000 self.level = 1000
return true return true
end end
@@ -149,10 +132,10 @@ function SurvivalA3Game:onLineClear(cleared_row_count)
if new_level >= 1300 or self:hitTorikan(self.level, new_level) then if new_level >= 1300 or self:hitTorikan(self.level, new_level) then
if new_level >= 1300 then if new_level >= 1300 then
self.level = 1300 self.level = 1300
self.big_mode = true
end end
self.clear = true self.clear = true
self.grid:clear() self.grid:clear()
self.big_mode = true
self.roll_frames = -150 self.roll_frames = -150
else else
self.level = math.min(new_level, 1300) self.level = math.min(new_level, 1300)
@@ -185,11 +168,8 @@ function SurvivalA3Game:updateSectionTimes(old_level, new_level)
section_time = self.frames - self.section_start_time section_time = self.frames - self.section_start_time
table.insert(self.section_times, section_time) table.insert(self.section_times, section_time)
self.section_start_time = self.frames self.section_start_time = self.frames
if section_time <= frameTime(1,00) then if section_time <= sp(1,00) then
self.grade = self.grade + 1 self.grade = self.grade + 1
else
self.coolregret_message = "REGRET!!"
self.coolregret_timer = 300
end end
end end
end end
@@ -227,15 +207,6 @@ function SurvivalA3Game:drawScoringInfo()
love.graphics.printf("GRADE", text_x, 120, 40, "left") love.graphics.printf("GRADE", text_x, 120, 40, "left")
love.graphics.printf("SCORE", text_x, 200, 40, "left") love.graphics.printf("SCORE", text_x, 200, 40, "left")
love.graphics.printf("LEVEL", text_x, 320, 40, "left") love.graphics.printf("LEVEL", text_x, 320, 40, "left")
local sg = self.grid:checkSecretGrade()
if sg >= 5 then
love.graphics.printf("SECRET GRADE", 240, 430, 180, "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
end
local current_section = math.floor(self.level / 100) + 1 local current_section = math.floor(self.level / 100) + 1
self:drawSectionTimesWithSplits(current_section) self:drawSectionTimesWithSplits(current_section)
@@ -249,9 +220,6 @@ function SurvivalA3Game:drawScoringInfo()
else else
love.graphics.printf(math.floor(self.level / 100 + 1) * 100, text_x, 370, 50, "right") love.graphics.printf(math.floor(self.level / 100 + 1) * 100, text_x, 370, 50, "right")
end end
if sg >= 5 then
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
end
end end
function SurvivalA3Game:getBackground() function SurvivalA3Game:getBackground()

View File

@@ -0,0 +1,76 @@
require 'funcs'
local MarathonAX = require 'tetris.modes.marathon_ax'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local SurvivalAXGame = MarathonAX:extend()
SurvivalAXGame.name = "Survival AX"
SurvivalAXGame.hash = "SurvivalAX"
SurvivalAXGame.tagline = "Can you clear the time hurdles when the game goes this fast?"
function SurvivalAXGame:new()
SurvivalAXGame.super:new()
self.roll_frames = 0
self.randomizer = History6RollsRandomizer()
self.section_time_limit = 3600
self.section_start_time = 0
self.section_times = { [0] = 0 }
self.section_clear = false
self.lock_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
function SurvivalAXGame:getSectionTimeLimit()
return 3600
end
function SurvivalAXGame:getARE()
if self.lines < 10 then return 18
elseif self.lines < 40 then return 14
elseif self.lines < 60 then return 12
elseif self.lines < 70 then return 10
elseif self.lines < 80 then return 8
elseif self.lines < 90 then return 7
else return 6 end
end
function SurvivalAXGame:getLineARE()
return self:getARE()
end
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 SurvivalAXGame:getLineClearDelay()
if self.lines < 10 then return 14
elseif self.lines < 30 then return 8
else return 5 end
end
function SurvivalAXGame:getLockDelay()
if self.lines < 10 then return 30
elseif self.lines < 20 then return 26
elseif self.lines < 30 then return 24
elseif self.lines < 40 then return 22
elseif self.lines < 50 then return 20
elseif self.lines < 70 then return 16
else return 15 end
end
function SurvivalAXGame:getGravity()
return 20
end
return SurvivalAXGame

View File

@@ -0,0 +1,59 @@
require 'funcs'
local MarathonAX2 = require 'tetris.modes.marathon_ax2'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local SurvivalAX2Game = MarathonAX2:extend()
SurvivalAX2Game.name = "Survival AX2"
SurvivalAX2Game.hash = "SurvivalAX2"
SurvivalAX2Game.tagline = "Can you clear the time hurdles when the game goes this fast?"
function SurvivalAX2Game:new()
SurvivalAX2Game.super:new()
self.roll_frames = 0
self.randomizer = History6RollsRandomizer()
self.section_time_limit = 3600
self.section_start_time = 0
self.section_times = { [0] = 0 }
self.section_clear = false
self.lock_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
function SurvivalAX2Game:getSectionTimeLimit()
return 3600
end
function SurvivalAX2Game:getARE()
return 6
end
function SurvivalAX2Game:getLineARE()
return self:getARE()
end
function SurvivalAX2Game:getDasLimit()
return 7
end
function SurvivalAX2Game:getLineClearDelay()
return 5
end
function SurvivalAX2Game:getLockDelay()
return 15
end
function SurvivalAX2Game:getGravity()
return 20
end
return SurvivalAX2Game

View File

@@ -2,8 +2,17 @@ local Randomizer = require 'tetris.randomizers.randomizer'
local AlwaysRandomizer = Randomizer:extend() local AlwaysRandomizer = Randomizer:extend()
function AlwaysRandomizer:new(piece)
self.piece = piece
self:initialize()
self.next_queue = {}
for i = 1, 30 do
table.insert(self.next_queue, self:generatePiece())
end
end
function AlwaysRandomizer:generatePiece() function AlwaysRandomizer:generatePiece()
return "I" return self.piece
end end
return AlwaysRandomizer return AlwaysRandomizer

View File

@@ -1,17 +0,0 @@
local Randomizer = require 'tetris.randomizers.randomizer'
local Bag5Randomizer = Randomizer:extend()
function Bag5Randomizer:initialize()
self.bag = {"I", "J", "L", "O", "T"}
end
function Bag5Randomizer:generatePiece()
if next(self.bag) == nil then
self.bag = {"I", "J", "L", "O", "T"}
end
local x = math.random(table.getn(self.bag))
return table.remove(self.bag, x)
end
return Bag5Randomizer

View File

@@ -1,24 +0,0 @@
local Randomizer = require 'tetris.randomizers.randomizer'
local Bag5AltRandomizer = Randomizer:extend()
function Bag5AltRandomizer:initialize()
self.bag = {"I", "J", "L", "O", "T"}
self.prev = nil
end
function Bag5AltRandomizer:generatePiece()
if next(self.bag) == nil then
self.bag = {"I", "J", "L", "O", "T"}
end
local x = math.random(table.getn(self.bag))
local temp = table.remove(self.bag, x)
if temp == self.prev then
local y = math.random(table.getn(self.bag))
temp = table.remove(self.bag, y)
end
self.prev = temp
return temp
end
return Bag5AltRandomizer

View File

@@ -1,31 +0,0 @@
local Randomizer = require 'tetris.randomizers.randomizer'
local Bag7NoSZOStartRandomizer = Randomizer:extend()
function Bag7NoSZOStartRandomizer:shuffleBag()
local b = self.bag
local ln = #b
for i = 1, ln do
local j = math.random(i, ln)
b[i], b[j] = b[j], b[i]
end
end
local function isnotSZO(x) return not(x == "S" or x == "Z" or x == "O") end
function Bag7NoSZOStartRandomizer:initialize()
self.bag = {"I", "J", "L", "O", "S", "T", "Z"}
repeat
self:shuffleBag()
until isnotSZO(self.bag[7])
end
function Bag7NoSZOStartRandomizer:generatePiece()
if #self.bag == 0 then
self.bag = {"I", "J", "L", "O", "S", "T", "Z"}
self:shuffleBag()
end
return table.remove(self.bag)
end
return Bag7NoSZOStartRandomizer

View File

@@ -1,24 +0,0 @@
local Randomizer = require 'tetris.randomizers.randomizer'
local Bag7Randomizer = Randomizer:extend()
function Bag7Randomizer:initialize()
self.bag = {"I", "J", "L", "O", "S", "T", "Z"}
self.extra = {"I", "J", "L", "O", "S", "T", "Z"}
table.insert(self.bag, table.remove(self.extra, math.random(table.getn(self.extra))))
end
function Bag7Randomizer:generatePiece()
if next(self.extra) == nil then
self.extra = {"I", "J", "L", "O", "S", "T", "Z"}
end
if next(self.bag) == nil then
self.bag = {"I", "J", "L", "O", "S", "T", "Z"}
table.insert(self.bag, table.remove(self.extra, math.random(table.getn(self.extra))))
end
local x = math.random(table.getn(self.bag))
--print("Bag: "..table.concat(self.bag, ", ").." | Extra: "..table.concat(self.extra, ", "))
return table.remove(self.bag, x)
end
return Bag7Randomizer

View File

@@ -1,28 +0,0 @@
local Randomizer = require 'tetris.randomizers.randomizer'
local BagKonoha = Randomizer:extend()
function BagKonoha:initialize()
self.bag = {"I", "J", "L", "O", "T"}
self.prev = nil
self.allowrepeat = false
self.generated = 0
end
function BagKonoha:generatePiece()
self.generated = self.generated + 1
if #self.bag == 0 then
self.bag = {"I", "J", "L", "O", "T"}
end
local x = math.random(#self.bag)
local temp = table.remove(self.bag, x)
if temp == self.prev and not self.allowrepeat then
local y = math.random(#self.bag)
table.insert(self.bag, temp) -- should insert at the end of the bag, bag[y] doesnt change
temp = table.remove(self.bag, y)
end
self.prev = temp
return temp
end
return BagKonoha

View File

@@ -3,15 +3,10 @@ local Randomizer = require 'tetris.randomizers.randomizer'
local History4RollsRandomizer = Randomizer:extend() local History4RollsRandomizer = Randomizer:extend()
function History4RollsRandomizer:initialize() function History4RollsRandomizer:initialize()
self.history = {"Z", "Z", "Z", "Z"} self.history = {"Z", "S", "Z", "S"}
self.first = true
end end
function History4RollsRandomizer:generatePiece() function History4RollsRandomizer:generatePiece()
if self.first then
self.first = false
return self:updateHistory(({"L", "J", "I", "T"})[math.random(4)])
else
local shapes = {"I", "J", "L", "O", "S", "T", "Z"} local shapes = {"I", "J", "L", "O", "S", "T", "Z"}
for i = 1, 4 do for i = 1, 4 do
local x = math.random(7) local x = math.random(7)
@@ -20,7 +15,6 @@ function History4RollsRandomizer:generatePiece()
end end
end end
end end
end
function History4RollsRandomizer:updateHistory(shape) function History4RollsRandomizer:updateHistory(shape)
table.remove(self.history, 1) table.remove(self.history, 1)

View File

@@ -4,14 +4,9 @@ local History6RollsRandomizer = Randomizer:extend()
function History6RollsRandomizer:initialize() function History6RollsRandomizer:initialize()
self.history = {"Z", "S", "Z", "S"} self.history = {"Z", "S", "Z", "S"}
self.first = true
end end
function History6RollsRandomizer:generatePiece() function History6RollsRandomizer:generatePiece()
if self.first then
self.first = false
return self:updateHistory(({"L", "J", "I", "T"})[math.random(4)])
else
local shapes = {"I", "J", "L", "O", "S", "T", "Z"} local shapes = {"I", "J", "L", "O", "S", "T", "Z"}
for i = 1, 6 do for i = 1, 6 do
local x = math.random(7) local x = math.random(7)
@@ -20,7 +15,6 @@ function History6RollsRandomizer:generatePiece()
end end
end end
end end
end
function History6RollsRandomizer:updateHistory(shape) function History6RollsRandomizer:updateHistory(shape)
table.remove(self.history, 1) table.remove(self.history, 1)

View File

@@ -1,70 +1,38 @@
local Randomizer = require 'tetris.randomizers.randomizer' local Randomizer = require 'tetris.randomizers.randomizer'
local History6Rolls35PoolRandomizer = Randomizer:extend() local History6RollsRandomizer = Randomizer:extend()
function History6Rolls35PoolRandomizer:initialize() function History6RollsRandomizer:initialize()
self.first = true
self.history = {"Z", "S", "Z", "S"} self.history = {"Z", "S", "Z", "S"}
self.pool = { self.bag_counts = {
"I", "I", "I", "I", "I", I = 5, J = 5, L = 5, O = 5, S = 3, T = 5, Z = 3
"T", "T", "T", "T", "T",
"L", "L", "L", "L", "L",
"J", "J", "J", "J", "J",
"S", "S", "S", "S", "S",
"Z", "Z", "Z", "Z", "Z",
"O", "O", "O", "O", "O",
}
self.droughts = {
I = 0,
T = 0,
L = 0,
J = 0,
S = 0,
Z = 0,
O = 0,
} }
end end
function History6Rolls35PoolRandomizer:generatePiece() function History6RollsRandomizer:getBagPiece(n)
local index, x for shape, count in pairs(self.bag_counts) do
if self.first then n = n - count
local prevent = {"S", "Z", "O"} if n <= 0 then
repeat return shape
index = math.random(#self.pool) end
x = self.pool[index] end
until not inHistory(x, prevent) end
self.first = false
else function History6RollsRandomizer:generatePiece()
for i = 1, 6 do for i = 1, 6 do
index = math.random(#self.pool) local x = self:getBagPiece(math.random(31))
x = self.pool[index]
if not inHistory(x, self.history) or i == 6 then if not inHistory(x, self.history) or i == 6 then
break return self:updateHistory(x)
end end
end end
end end
self.pool[index] = self:updateHistory(x)
return x
end
function History6Rolls35PoolRandomizer:updateHistory(shape) function History6RollsRandomizer:updateHistory(shape)
table.remove(self.history, 1) self.bag_counts[shape] = self.bag_counts[shape] - 1
local replaced_piece = table.remove(self.history, 1)
table.insert(self.history, shape) table.insert(self.history, shape)
self.bag_counts[replaced_piece] = self.bag_counts[replaced_piece] + 1
local highdrought return shape
local highdroughtcount = 0
for k, v in pairs(self.droughts) do
if k == shape then
self.droughts[k] = 0
else
self.droughts[k] = v + 1
if v >= highdroughtcount then
highdrought = k
highdroughtcount = v
end
end
end
return highdrought
end end
function inHistory(piece, history) function inHistory(piece, history)
@@ -76,4 +44,4 @@ function inHistory(piece, history)
return false return false
end end
return History6Rolls35PoolRandomizer return History6RollsRandomizer

View File

@@ -4,10 +4,15 @@ local Randomizer = Object:extend()
function Randomizer:new() function Randomizer:new()
self:initialize() self:initialize()
self.next_queue = {}
for i = 1, 30 do
table.insert(self.next_queue, self:generatePiece())
end
end end
function Randomizer:nextPiece() function Randomizer:nextPiece()
return self:generatePiece() table.insert(self.next_queue, self:generatePiece())
return table.remove(self.next_queue, 1)
end end
function Randomizer:initialize() function Randomizer:initialize()

View File

@@ -1,30 +0,0 @@
local Randomizer = require 'tetris.randomizers.randomizer'
local RecursiveRandomizer = Randomizer:extend()
function RecursiveRandomizer:initialize()
self.bag = {"I", "J", "L", "O", "S", "T", "Z"}
end
function RecursiveRandomizer:generatePiece()
--if next(self.bag) == nil then
-- self.bag = {"I", "J", "L", "O", "S", "T", "Z"}
--end
local x = math.random(table.getn(self.bag) + 1)
while x == table.getn(self.bag) + 1 do
--print("Refill piece pulled")
table.insert(self.bag, "I")
table.insert(self.bag, "J")
table.insert(self.bag, "L")
table.insert(self.bag, "O")
table.insert(self.bag, "S")
table.insert(self.bag, "T")
table.insert(self.bag, "Z")
x = math.random(table.getn(self.bag) + 1)
end
--print("Number of pieces in bag: "..table.getn(self.bag))
--print("Bag: "..table.concat(self.bag, ", "))
return table.remove(self.bag, x)
end
return RecursiveRandomizer

View File

@@ -1,19 +0,0 @@
local Randomizer = require 'tetris.randomizers.randomizer'
local SegaRandomizer = Randomizer:extend()
function SegaRandomizer:initialize()
self.bag = {"I", "J", "L", "O", "S", "T", "Z"}
self.sequence = {}
for i = 1, 1000 do
self.sequence[i] = self.bag[math.random(table.getn(self.bag))]
end
self.counter = 0
end
function SegaRandomizer:generatePiece()
self.counter = self.counter + 1
return self.sequence[self.counter % 1000 + 1]
end
return SegaRandomizer

View File

@@ -17,10 +17,10 @@ ARS.spawn_positions = {
} }
ARS.big_spawn_positions = { ARS.big_spawn_positions = {
I = { x=3, y=2 }, I = { x=2, y=2 },
J = { x=2, y=3 }, J = { x=2, y=3 },
L = { x=2, y=3 }, L = { x=2, y=3 },
O = { x=3, y=3 }, O = { x=2, y=3 },
S = { x=2, y=3 }, S = { x=2, y=3 },
T = { x=2, y=3 }, T = { x=2, y=3 },
Z = { x=2, y=3 }, Z = { x=2, y=3 },
@@ -81,19 +81,11 @@ function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
piece.shape == "J" or piece.shape == "T" or piece.shape == "L" piece.shape == "J" or piece.shape == "T" or piece.shape == "L"
) and ( ) and (
piece.rotation == 0 or piece.rotation == 2 piece.rotation == 0 or piece.rotation == 2
) then ) and (
local offsets = new_piece:getBlockOffsets() grid:isOccupied(piece.position.x, piece.position.y) or
table.sort(offsets, function(A, B) return A.y < B.y or A.y == B.y and A.x < B.y end) grid:isOccupied(piece.position.x, piece.position.y - 1) or
for index, offset in pairs(offsets) do grid:isOccupied(piece.position.x, piece.position.y - 2)
if grid:isOccupied(piece.position.x + offset.x, piece.position.y + offset.y) then ) then return end
if offset.x == 0 then
return
else
break
end
end
end
end
-- kick right, kick left -- kick right, kick left
if (grid:canPlacePiece(new_piece:withOffset({x=1, y=0}))) then if (grid:canPlacePiece(new_piece:withOffset({x=1, y=0}))) then
@@ -110,7 +102,7 @@ function ARS:onPieceDrop(piece, grid)
piece.lock_delay = 0 -- step reset piece.lock_delay = 0 -- step reset
end end
function ARS:get180RotationValue() return 3 end function ARS:get180RotationValue() return config["reverse_rotate"] and 1 or 3 end
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
return ARS return ARS

177
tetris/rulesets/arika_ace.lua Executable file → Normal file
View File

@@ -1,164 +1,14 @@
local Piece = require 'tetris.components.piece' local ArikaTI = require 'tetris.rulesets.arika_ti'
local Ruleset = require 'tetris.rulesets.ruleset'
local ARS = Ruleset:extend() local ARS = ArikaTI:extend()
ARS.name = "ACE-ARS" ARS.name = "Ace-ARS"
ARS.hash = "ArikaACE" ARS.hash = "ArikaAce"
ARS.colourscheme = {
I = "C",
L = "O",
J = "B",
S = "G",
Z = "R",
O = "Y",
T = "M",
}
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 },
}
ARS.block_offsets = {
I={
{ {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=0, y=2} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=0, y=2} },
},
J={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=-1, y=-1} },
{ {x=0, y=-1}, {x=1, y=-2}, {x=0, y=-2}, {x=0, y=0} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=1, y=0} },
{ {x=0, y=-1}, {x=0, y=-2}, {x=0, y=0}, {x=-1, y=0} },
},
L={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=1, y=-1} },
{ {x=0, y=-2}, {x=0, y=-1}, {x=1, y=0}, {x=0, y=0} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=-1, y=0} },
{ {x=0, y=-1}, {x=-1, y=-2}, {x=0, y=-2}, {x=0, y=0} },
},
O={
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
},
S={
{ {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=-1, y=-2}, {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0} },
{ {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=-1, y=-2}, {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0} },
},
T={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} },
{ {x=0, y=-1}, {x=0, y=0}, {x=1, y=-1}, {x=0, y=-2} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=0, y=0} },
{ {x=0, y=-1}, {x=0, y=0}, {x=-1, y=-1}, {x=0, y=-2} },
},
Z={
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=0}, {x=0, y=0} },
{ {x=0, y=-1}, {x=0, y=0}, {x=1, y=-2}, {x=1, y=-1} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=0}, {x=0, y=0} },
{ {x=0, y=-1}, {x=0, y=0}, {x=1, y=-2}, {x=1, y=-1} },
}
}
-- Component functions.
function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
-- O doesn't kick
if (piece.shape == "O") then return end
-- center column rule
if (
piece.shape == "J" or piece.shape == "T" or piece.shape == "L"
) and (
piece.rotation == 0 or piece.rotation == 2
) then
local offsets = new_piece:getBlockOffsets()
table.sort(offsets, function(A, B) return A.y < B.y or A.y == B.y and A.x < B.y end)
for index, offset in pairs(offsets) do
if grid:isOccupied(piece.position.x + offset.x, piece.position.y + offset.y) then
if offset.x == 0 then
return
else
break
end
end
end
end
if piece.shape == "I" then
-- special kick rules for I
if new_piece.rotation == 0 or new_piece.rotation == 2 then
-- kick right, right2, left
if grid:canPlacePiece(new_piece:withOffset({x=1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
self:onPieceRotate(piece, grid)
elseif grid:canPlacePiece(new_piece:withOffset({x=2, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=2, y=0})
self:onPieceRotate(piece, grid)
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
self:onPieceRotate(piece, grid)
end
elseif piece:isDropBlocked(grid) and (new_piece.rotation == 1 or new_piece.rotation == 3) and piece.floorkick == 0 then
-- kick up, up2
if grid:canPlacePiece(new_piece:withOffset({x=0, y=-1})) then
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
self:onPieceRotate(piece, grid)
piece.floorkick = 1
elseif grid:canPlacePiece(new_piece:withOffset({x=0, y=-2})) then
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-2})
self:onPieceRotate(piece, grid)
piece.floorkick = 1
end
end
else
-- kick right, kick left
if grid:canPlacePiece(new_piece:withOffset({x=1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
elseif piece.shape == "T"
and new_piece.rotation == 0
and piece.floorkick == 0
and grid:canPlacePiece(new_piece:withOffset({x=0, y=-1}))
then
-- T floorkick
piece.floorkick = piece.floorkick + 1
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
end
end
end
function ARS:onPieceCreate(piece, grid) function ARS:onPieceCreate(piece, grid)
piece.floorkick = 0 piece.floorkick = 0
piece.manipulations = 0 piece.rotate_counter = 0
piece.move_counter = 0
end end
function ARS:onPieceDrop(piece, grid) function ARS:onPieceDrop(piece, grid)
@@ -168,24 +18,15 @@ end
function ARS:onPieceMove(piece, grid) function ARS:onPieceMove(piece, grid)
piece.lock_delay = 0 -- move reset piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1 piece.move_counter = piece.move_counter + 1
if piece.manipulations >= 127 then if piece.move_counter >= 128 then
piece.locked = true piece.locked = true
end end
end end
end end
function ARS:onPieceRotate(piece, grid) function ARS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- rotate reset self:onPieceMove(piece, grid)
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= 127 then
piece.locked = true
end end
end
end
function ARS:get180RotationValue() return 3 end
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
return ARS return ARS

View File

@@ -1,178 +0,0 @@
local Piece = require 'tetris.components.piece'
local Ruleset = require 'tetris.rulesets.ruleset'
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 },
}
ARS.block_offsets = {
I={
{ {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=0, y=2} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=0, y=2} },
},
J={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=-1, y=-1} },
{ {x=0, y=-1}, {x=1, y=-2}, {x=0, y=-2}, {x=0, y=0} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=1, y=0} },
{ {x=0, y=-1}, {x=0, y=-2}, {x=0, y=0}, {x=-1, y=0} },
},
L={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=1, y=-1} },
{ {x=0, y=-2}, {x=0, y=-1}, {x=1, y=0}, {x=0, y=0} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=-1, y=0} },
{ {x=0, y=-1}, {x=-1, y=-2}, {x=0, y=-2}, {x=0, y=0} },
},
O={
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
},
S={
{ {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=-1, y=-2}, {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0} },
{ {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=-1, y=-2}, {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0} },
},
T={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} },
{ {x=0, y=-1}, {x=0, y=0}, {x=1, y=-1}, {x=0, y=-2} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=-1}, {x=0, y=0} },
{ {x=0, y=-1}, {x=0, y=0}, {x=-1, y=-1}, {x=0, y=-2} },
},
Z={
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=0}, {x=0, y=0} },
{ {x=0, y=-1}, {x=0, y=0}, {x=1, y=-2}, {x=1, y=-1} },
{ {x=0, y=-1}, {x=-1, y=-1}, {x=1, y=0}, {x=0, y=0} },
{ {x=0, y=-1}, {x=0, y=0}, {x=1, y=-2}, {x=1, y=-1} },
}
}
-- Component functions.
function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
-- O doesn't kick
if (piece.shape == "O") then return end
-- center column rule
if (
piece.shape == "J" or piece.shape == "T" or piece.shape == "L"
) and (
piece.rotation == 0 or piece.rotation == 2
) then
local offsets = new_piece:getBlockOffsets()
table.sort(offsets, function(A, B) return A.y < B.y or A.y == B.y and A.x < B.y end)
for index, offset in pairs(offsets) do
if grid:isOccupied(piece.position.x + offset.x, piece.position.y + offset.y) then
if offset.x == 0 then
return
else
break
end
end
end
end
if piece.shape == "I" then
-- special kick rules for I
if new_piece.rotation == 0 or new_piece.rotation == 2 then
-- kick right, right2, left
if grid:canPlacePiece(new_piece:withOffset({x=1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
self:onPieceRotate(piece, grid)
elseif grid:canPlacePiece(new_piece:withOffset({x=2, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=2, y=0})
self:onPieceRotate(piece, grid)
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
self:onPieceRotate(piece, grid)
end
elseif piece:isDropBlocked(grid) and (new_piece.rotation == 1 or new_piece.rotation == 3) and piece.floorkick == 0 then
-- kick up, up2
if grid:canPlacePiece(new_piece:withOffset({x=0, y=-1})) then
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
self:onPieceRotate(piece, grid)
piece.floorkick = 1
elseif grid:canPlacePiece(new_piece:withOffset({x=0, y=-2})) then
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-2})
self:onPieceRotate(piece, grid)
piece.floorkick = 1
end
end
else
-- kick right, kick left
if grid:canPlacePiece(new_piece:withOffset({x=1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
elseif piece.shape == "T"
and new_piece.rotation == 0
and piece.floorkick == 0
and grid:canPlacePiece(new_piece:withOffset({x=0, y=-1}))
then
-- T floorkick
piece.floorkick = piece.floorkick + 1
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
end
end
end
function ARS:onPieceCreate(piece, grid)
piece.floorkick = 0
piece.manipulations = 0
end
function ARS:onPieceDrop(piece, grid)
piece.lock_delay = 0 -- step reset
end
function ARS:onPieceMove(piece, grid)
piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= 127 then
piece.locked = true
end
end
end
function ARS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- rotate reset
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= 127 then
piece.locked = true
end
end
end
function ARS:get180RotationValue() return 3 end
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
return ARS

View File

@@ -1,190 +0,0 @@
local Piece = require 'tetris.components.piece'
local Ruleset = require 'tetris.rulesets.ruleset'
local SRS = Ruleset:extend()
SRS.name = "ACE-SRS"
SRS.hash = "ACE Standard"
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.enable_IRS_wallkicks = true
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.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 },
}
SRS.block_offsets = {
I={
{ {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=0, y=2} },
{ {x=0, y=1}, {x=-1, y=1}, {x=-2, y=1}, {x=1, y=1} },
{ {x=-1, y=0}, {x=-1, y=-1}, {x=-1, y=1}, {x=-1, y=2} },
},
J={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=-1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1} , {x=1, y=-1} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=1, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=1} },
},
L={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=1} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=-1, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=-1} },
},
O={
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
},
S={
{ {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=1, y=1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=-1} },
{ {x=-1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=1, y=0} },
{ {x=-1, y=-1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=1} },
},
T={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=0} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=0, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=0} },
},
Z={
{ {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=1, y=0} },
{ {x=1, y=-1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=1} },
{ {x=1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=-1, y=1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=-1} },
}
}
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}},
[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}},
},
[2]={
[0]={{x=0, y=1}, {x=0, y=-1}},
[1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}},
[3]={{x=1, y=0}, {x=1, y=-1}, {x=0, y=2}, {x=1, y=2}},
},
[3]={
[0]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
[1]={{x=0, y=1}, {x=0, y=-1}},
[2]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
},
};
SRS.wallkicks_line = {
[0]={
[1]={{x=-2, y= 0}, {x= 1, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
[2]={},
[3]={{x= 2, y= 0}, {x=-1, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
},
[1]={
[0]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 2}},
[2]={{x=-1, y= 0}, {x= 2, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
[3]={},
},
[2]={
[0]={},
[1]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 1}},
[3]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 1}},
},
[3]={
[0]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 2}},
[1]={},
[2]={{x= 1, y= 0}, {x=-2, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
},
};
-- Component functions.
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:onPieceCreate(piece, grid)
piece.manipulations = 0
end
function SRS:onPieceDrop(piece, grid)
piece.lock_delay = 0 -- step reset
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 >= 127 then
piece.locked = true
end
end
end
function SRS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- rotate reset
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= 127 then
piece.locked = true
end
end
end
function SRS:get180RotationValue() return 3 end
return SRS

View File

@@ -17,10 +17,10 @@ ARS.spawn_positions = {
} }
ARS.big_spawn_positions = { ARS.big_spawn_positions = {
I = { x=3, y=2 }, I = { x=2, y=2 },
J = { x=2, y=3 }, J = { x=2, y=3 },
L = { x=2, y=3 }, L = { x=2, y=3 },
O = { x=3, y=3 }, O = { x=2, y=3 },
S = { x=2, y=3 }, S = { x=2, y=3 },
T = { x=2, y=3 }, T = { x=2, y=3 },
Z = { x=2, y=3 }, Z = { x=2, y=3 },
@@ -84,19 +84,11 @@ function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
piece.shape == "J" or piece.shape == "T" or piece.shape == "L" piece.shape == "J" or piece.shape == "T" or piece.shape == "L"
) and ( ) and (
piece.rotation == 0 or piece.rotation == 2 piece.rotation == 0 or piece.rotation == 2
) then ) and (
local offsets = new_piece:getBlockOffsets() grid:isOccupied(piece.position.x, piece.position.y) or
table.sort(offsets, function(A, B) return A.y < B.y or A.y == B.y and A.x < B.y end) grid:isOccupied(piece.position.x, piece.position.y - 1) or
for index, offset in pairs(offsets) do grid:isOccupied(piece.position.x, piece.position.y - 2)
if grid:isOccupied(piece.position.x + offset.x, piece.position.y + offset.y) then ) then return end
if offset.x == 0 then
return
else
break
end
end
end
end
if piece.shape == "I" then if piece.shape == "I" then
-- special kick rules for I -- special kick rules for I
@@ -124,20 +116,12 @@ function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
piece.floorkick = 1 piece.floorkick = 1
end end
end end
else elseif piece.shape ~= "I" then
-- kick right, kick left -- kick right, kick left
if grid:canPlacePiece(new_piece:withOffset({x=1, y=0})) then if (grid:canPlacePiece(new_piece:withOffset({x=1, y=0}))) then
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0}) piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then elseif (grid:canPlacePiece(new_piece:withOffset({x=-1, y=0}))) then
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0}) piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
elseif piece.shape == "T"
and new_piece.rotation == 0
and piece.floorkick == 0
and grid:canPlacePiece(new_piece:withOffset({x=0, y=-1}))
then
-- T floorkick
piece.floorkick = piece.floorkick + 1
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
end end
end end
@@ -151,7 +135,7 @@ function ARS:onPieceDrop(piece, grid)
piece.lock_delay = 0 -- step reset piece.lock_delay = 0 -- step reset
end end
function ARS:get180RotationValue() return 3 end function ARS:get180RotationValue() return config["reverse_rotate"] and 1 or 3 end
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
return ARS return ARS

View File

@@ -5,26 +5,25 @@ local CRS = Ruleset:extend()
CRS.name = "Cambridge" CRS.name = "Cambridge"
CRS.hash = "Cambridge" CRS.hash = "Cambridge"
CRS.world = true
CRS.spawn_positions = { CRS.spawn_positions = {
I = { x=5, y=4 }, I = { x=5, y=4 },
J = { x=4, y=5 }, J = { x=4, y=5 },
L = { x=4, y=5 }, L = { x=4, y=5 },
O = { x=5, y=5 }, O = { x=5, y=5 },
S = { x=4, y=5 }, S = { x=4, y=4 },
T = { x=4, y=5 }, T = { x=4, y=5 },
Z = { x=4, y=5 }, Z = { x=4, y=4 },
} }
CRS.big_spawn_positions = { CRS.big_spawn_positions = {
I = { x=3, y=2 }, I = { x=2, y=2 },
J = { x=2, y=3 }, J = { x=2, y=3 },
L = { x=2, y=3 }, L = { x=2, y=3 },
O = { x=3, y=3 }, O = { x=2, y=3 },
S = { x=2, y=3 }, S = { x=2, y=2 },
T = { x=2, y=3 }, T = { x=2, y=3 },
Z = { x=2, y=3 }, Z = { x=2, y=2 },
} }
CRS.block_offsets = { CRS.block_offsets = {
@@ -53,10 +52,10 @@ CRS.block_offsets = {
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} }, { {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
}, },
S={ S={
{ {x=-1, y=0}, {x=0, y=0}, {x=0, y=-1}, {x=1, y=-1} }, { {x=-1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=1, y=0} },
{ {x=0, y=0}, {x=0, y=-1}, {x=-1, y=-1}, {x=-1, y=-2} }, { {x=0, y=1}, {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1} },
{ {x=-1, y=0}, {x=0, y=0}, {x=0, y=-1}, {x=1, y=-1} }, { {x=-1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=1, y=0} },
{ {x=0, y=0}, {x=0, y=-1}, {x=-1, y=-1}, {x=-1, y=-2} }, { {x=0, y=1}, {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1} },
}, },
T={ T={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} }, { {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} },
@@ -65,10 +64,10 @@ CRS.block_offsets = {
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=0} }, { {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=0} },
}, },
Z={ Z={
{ {x=1, y=0}, {x=0, y=0}, {x=0, y=-1}, {x=-1, y=-1} }, { {x=1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=1, y=-2}, {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0} }, { {x=1, y=-1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=1} },
{ {x=1, y=0}, {x=0, y=0}, {x=0, y=-1}, {x=-1, y=-1} }, { {x=1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=1, y=-2}, {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0} }, { {x=1, y=-1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=1} },
} }
} }
@@ -364,10 +363,6 @@ function CRS:attemptRotate(new_inputs, piece, grid, initial)
if rot_dir == 0 then return end if rot_dir == 0 then return end
if self.world and config.gamesettings.world_reverse == 2 then
rot_dir = 4 - rot_dir
end
local new_piece = piece:withRelativeRotation(rot_dir) local new_piece = piece:withRelativeRotation(rot_dir)
self:attemptWallkicks(piece, new_piece, rot_dir, grid) self:attemptWallkicks(piece, new_piece, rot_dir, grid)
end end
@@ -403,6 +398,7 @@ function CRS:onPieceDrop(piece, grid)
end end
function CRS:onPieceMove(piece, grid) function CRS:onPieceMove(piece, grid)
piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then if piece:isDropBlocked(grid) then
piece.move_counter = piece.move_counter + 1 piece.move_counter = piece.move_counter + 1
if piece.move_counter >= 24 then if piece.move_counter >= 24 then
@@ -412,6 +408,7 @@ function CRS:onPieceMove(piece, grid)
end end
function CRS:onPieceRotate(piece, grid) function CRS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then if piece:isDropBlocked(grid) then
piece.rotate_counter = piece.rotate_counter + 1 piece.rotate_counter = piece.rotate_counter + 1
if piece.rotate_counter >= 12 then if piece.rotate_counter >= 12 then
@@ -420,6 +417,4 @@ function CRS:onPieceRotate(piece, grid)
end end
end end
function CRS:getDefaultOrientation() return 1 end -- downward facing pieces by default
return CRS return CRS

View File

@@ -6,20 +6,6 @@ local Ruleset = Object:extend()
Ruleset.name = "" Ruleset.name = ""
Ruleset.hash = "" Ruleset.hash = ""
-- Arika-type ruleset defaults
Ruleset.world = false
Ruleset.colourscheme = {
I = "R",
L = "O",
J = "B",
S = "M",
Z = "G",
O = "Y",
T = "C",
}
Ruleset.softdrop_lock = true
Ruleset.harddrop_lock = false
Ruleset.enable_IRS_wallkicks = false Ruleset.enable_IRS_wallkicks = false
-- Component functions. -- Component functions.
@@ -53,9 +39,6 @@ function Ruleset:attemptRotate(new_inputs, piece, grid, initial)
end end
if rot_dir == 0 then return end if rot_dir == 0 then return end
if self.world and config.gamesettings.world_reverse == 2 then
rot_dir = 4 - rot_dir
end
local new_piece = piece:withRelativeRotation(rot_dir) local new_piece = piece:withRelativeRotation(rot_dir)
@@ -73,16 +56,16 @@ function Ruleset:attemptWallkicks(piece, new_piece, rot_dir, grid)
-- do nothing in default ruleset -- do nothing in default ruleset
end end
function Ruleset:movePiece(piece, grid, move) function Ruleset:movePiece(piece, grid, move, instant)
local x = piece.position.x local x = piece.position.x
if move == "left" then if move == "left" then
piece:moveInGrid({x=-1, y=0}, 1, grid) piece:moveInGrid({x=-1, y=0}, 1, grid, false)
elseif move == "speedleft" then
piece:moveInGrid({x=-1, y=0}, 10, grid)
elseif move == "right" then elseif move == "right" then
piece:moveInGrid({x=1, y=0}, 1, grid) piece:moveInGrid({x=1, y=0}, 1, grid, false)
elseif move == "speedleft" then
piece:moveInGrid({x=-1, y=0}, 10, grid, instant)
elseif move == "speedright" then elseif move == "speedright" then
piece:moveInGrid({x=1, y=0}, 10, grid) piece:moveInGrid({x=1, y=0}, 10, grid, instant)
end end
if piece.position.x ~= x then if piece.position.x ~= x then
self:onPieceMove(piece, grid) self:onPieceMove(piece, grid)
@@ -134,12 +117,10 @@ function Ruleset:initializePiece(
else else
spawn_positions = self.spawn_positions spawn_positions = self.spawn_positions
end end
local colours = ({self.colourscheme, ColourSchemes.Arika, ColourSchemes.TTC})[config.gamesettings.piece_colour]
local piece = Piece(data.shape, data.orientation - 1, { local piece = Piece(data.shape, data.orientation - 1, {
x = spawn_positions[data.shape].x, x = spawn_positions[data.shape].x,
y = spawn_positions[data.shape].y y = spawn_positions[data.shape].y
}, self.block_offsets, 0, 0, data.skin, colours[data.shape], big) }, self.block_offsets, 0, 0, data.skin, big)
self:onPieceCreate(piece) self:onPieceCreate(piece)
self:rotatePiece(inputs, piece, grid, {}, true) self:rotatePiece(inputs, piece, grid, {}, true)
@@ -157,7 +138,7 @@ function Ruleset:processPiece(
hard_drop_enabled, additive_gravity hard_drop_enabled, additive_gravity
) )
self:rotatePiece(inputs, piece, grid, prev_inputs, false) self:rotatePiece(inputs, piece, grid, prev_inputs, false)
self:movePiece(piece, grid, move) self:movePiece(piece, grid, move, gravity >= 20)
self:dropPiece( self:dropPiece(
inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked, inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
hard_drop_enabled, additive_gravity hard_drop_enabled, additive_gravity

View File

@@ -3,41 +3,29 @@ local Ruleset = require 'tetris.rulesets.ruleset'
local SRS = Ruleset:extend() local SRS = Ruleset:extend()
SRS.name = "Guideline SRS" SRS.name = "SRS"
SRS.hash = "Standard" SRS.hash = "Standard"
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.enable_IRS_wallkicks = true SRS.enable_IRS_wallkicks = true
SRS.spawn_positions = { SRS.spawn_positions = {
I = { x=5, y=2 }, I = { x=5, y=4 },
J = { x=4, y=3 }, J = { x=4, y=5 },
L = { x=4, y=3 }, L = { x=4, y=5 },
O = { x=5, y=3 }, O = { x=5, y=5 },
S = { x=4, y=3 }, S = { x=4, y=5 },
T = { x=4, y=3 }, T = { x=4, y=5 },
Z = { x=4, y=3 }, Z = { x=4, y=5 },
} }
SRS.big_spawn_positions = { SRS.big_spawn_positions = {
I = { x=3, y=0 }, I = { x=2, y=2 },
J = { x=2, y=1 }, J = { x=2, y=3 },
L = { x=2, y=1 }, L = { x=2, y=3 },
O = { x=3, y=1 }, O = { x=2, y=3 },
S = { x=2, y=1 }, S = { x=2, y=3 },
T = { x=2, y=1 }, T = { x=2, y=3 },
Z = { x=2, y=1 }, Z = { x=2, y=3 },
} }
SRS.block_offsets = { SRS.block_offsets = {

View File

@@ -0,0 +1,30 @@
local Standard = require 'tetris.rulesets.standard'
local SRS = Standard:extend()
SRS.name = "Ti-SRS"
SRS.hash = "StandardTI"
function SRS:onPieceMove(piece, grid)
piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then
piece.move_counter = piece.move_counter + 1
if piece.move_counter >= 10 then
piece.locked = true
end
end
end
function SRS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- rotate reset
if piece:isDropBlocked(grid) then
piece.rotate_counter = piece.rotate_counter + 1
if piece.rotate_counter >= 8 then
piece.locked = true
end
end
end
function SRS:get180RotationValue() return config["reverse_rotate"] and 1 or 3 end
return SRS

View File

@@ -1,191 +0,0 @@
local Piece = require 'tetris.components.piece'
local Ruleset = require 'tetris.rulesets.ruleset'
local SRS = Ruleset:extend()
SRS.name = "Ti-World"
SRS.hash = "Bad I-kicks"
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.enable_IRS_wallkicks = true
SRS.spawn_positions = {
I = { x=5, y=4 },
J = { x=4, y=5 },
L = { x=4, y=5 },
O = { x=5, y=5 },
S = { x=4, y=5 },
T = { x=4, y=5 },
Z = { x=4, y=5 },
}
SRS.big_spawn_positions = {
I = { x=3, y=2 },
J = { x=2, y=3 },
L = { x=2, y=3 },
O = { x=3, y=3 },
S = { x=2, y=3 },
T = { x=2, y=3 },
Z = { x=2, y=3 },
}
SRS.block_offsets = {
I={
{ {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=0, y=2} },
{ {x=0, y=1}, {x=-1, y=1}, {x=-2, y=1}, {x=1, y=1} },
{ {x=-1, y=0}, {x=-1, y=-1}, {x=-1, y=1}, {x=-1, y=2} },
},
J={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=-1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1} , {x=1, y=-1} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=1, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=1} },
},
L={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=1} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=-1, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=-1} },
},
O={
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
},
S={
{ {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=1, y=1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=-1} },
{ {x=-1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=1, y=0} },
{ {x=-1, y=-1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=1} },
},
T={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=0} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=0, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=0} },
},
Z={
{ {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=1, y=0} },
{ {x=1, y=-1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=1} },
{ {x=1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=-1, y=1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=-1} },
}
}
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}},
[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}},
},
[2]={
[0]={{x=0, y=1}, {x=0, y=-1}},
[1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}},
[3]={{x=1, y=0}, {x=1, y=-1}, {x=0, y=2}, {x=1, y=2}},
},
[3]={
[0]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
[1]={{x=0, y=1}, {x=0, y=-1}},
[2]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
},
};
SRS.wallkicks_line = {
[0]={
[1]={{x=-2, y= 0}, {x= 1, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
[2]={},
[3]={{x= 2, y= 0}, {x=-1, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
},
[1]={
[0]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 2}},
[2]={{x=-1, y= 0}, {x= 2, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
[3]={},
},
[2]={
[0]={},
[1]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 1}},
[3]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 1}},
},
[3]={
[0]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 2}},
[1]={},
[2]={{x= 1, y= 0}, {x=-2, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
},
};
-- Component functions.
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:onPieceCreate(piece, grid)
piece.manipulations = 0
piece.rotations = 0
end
function SRS:onPieceDrop(piece, grid)
piece.lock_delay = 0 -- step reset
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
piece.locked = true
end
end
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.locked = true
end
end
end
function SRS:get180RotationValue() return 3 end
return SRS