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
132 changed files with 3232 additions and 3713 deletions

2
.gitignore vendored
View File

@@ -2,5 +2,3 @@
*.love *.love
dist/*.zip dist/*.zip
dist/**/cambridge.exe dist/**/cambridge.exe
dist/**/libs
dist/**/*.md

View File

@@ -1,65 +1,33 @@
![Cambridge Banner](https://cdn.discordapp.com/attachments/764432435802013709/767724895076614154/cambridge_logo_lt.png)
Important notice
================
![Tetra Online Notice](https://pbs.twimg.com/media/Eo3CkIHW8AEoK_U?format=png&name=small)
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), [joezeng](https://github.com/joezeng) and [Oshisaure](https://github.com/oshisaure)!
Join our Discord server for help and a welcoming community! https://discord.gg/mteMJw4 Installation instructions
-------------------------
Credits Pre-built releases are available on the releases page.
-------
- [Lilla Oshisaure](https://www.youtube.com/user/LeSpyroshisaure) for being my co-dev!
- [joezeng](https://github.com/joezeng) for the original project, and for offering to help with the expansion!
- [The Tetra Legends Discord](http://discord.com/invite/7hMx5r2) for supporting me and playtesting!
- [The Absolute Plus](https://discord.gg/6Gf2awJ) for being another source of motivation!
The following people in no particular order also helped with the project:
- [Hailey](https://github.com/haileylgbt)
- CylinderKnot
- MarkGamed7794
- [Mizu](https://github.com/rexxt)
- MattMayuga
- Kitaru
- switchpalacecorner
- [sinefuse](https://github.com/sinefuse)
- [2Tie](https://github.com/2Tie)
- [nightmareci](https://github.com/nightmareci)
- [MyPasswordIsWeak](https://github.com/MyPasswordIsWeak)
- [Dr Ocelot](https://github.com/Dr-Ocelot)
![Cambridge Logo](https://cdn.discordapp.com/attachments/625496179433668635/763363717730664458/Icon_2.png)
Playing the game
----------------
### Windows ### Windows
You do not need LÖVE on Windows, as it comes bundled with the program. Unzip the exe file and run it directly. All assets are currently bundled inside the executable.
To get the stable release, simply download the ZIP in the latest release. All assets needed are bundled with the executable. ### macOS
If you want the bleeding edge version, download [this](https://github.com/SashLilac/cambridge/archive/master.zip). For the time being, the file `cambridge.love` only works on the command line. Install `love` with [https://brew.sh/](Homebrew), and run:
Extract the ZIP, open a Command Prompt at the folder you extracted Cambridge to, then run this command: $ love cambridge.love
dist\windows\love.exe . ### Linux
Alternatively, if you're on a 32-bit system, run this instead: Same as macOS, except install `love` with your favourite package manager.
dist\win32\love.exe .
32-bit systems do not support rich presence integration. Running from source
-------------------
Then, check the mod pack section at the bottom of this page. If you want the bleeding-edge release, you can also clone the code straight from this repository.
### macOS, Linux ### macOS, Linux
@@ -67,23 +35,14 @@ 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
Alternatively, download the source code ZIP in the latest release.
Then, navigate to the root directory that you just cloned, and type: Then, navigate to the root directory that you just cloned, and type:
love . love .
It should run automatically! It should run automatically!
## Installing modpacks
Simply drag your mode, ruleset, and randomizer Lua files into their respective [directory](https://love2d.org/wiki/love.filesystem), and they should appear automatically.
**WARNING:** The .exe / .love files and the bleeding edge releases have different save directories. Read the above link carefully!
For more detailed instructions, install [this](https://github.com/SashLilac/cambridge-modpack) mod pack to get a taste of the mod potential.
License License
------- -------

View File

@@ -8,7 +8,7 @@ Some of the assets are used without proper licenses. We aim to have fully licens
Backgrounds Backgrounds
----------- -----------
1. Title: Original picrute found on the Wikipedia article for Cambridge 1. Title: "Motus Glacies." Contributed by Daniel "Explo" McCarthy.
1. *Gameplay level 0: "Quantum foam." Alex Sukontsev. https://www.flickr.com/photos/control9/14957509814/ 1. *Gameplay level 0: "Quantum foam." Alex Sukontsev. https://www.flickr.com/photos/control9/14957509814/
2. *Gameplay level 1: No name. http://www.onekind.tv/univision-mqb/q5mqh5brlvuuj2nhdx7ch7eum183uu 2. *Gameplay level 1: No name. http://www.onekind.tv/univision-mqb/q5mqh5brlvuuj2nhdx7ch7eum183uu
@@ -34,18 +34,10 @@ Backgrounds
Backgrounds marked with a * are placeholders that will be replaced in later versions due to incompatible licenses. We are generally aiming for public domain background images, but will also accept backgrounds given proper licenses to be included within Cambridge. Backgrounds marked with a * are placeholders that will be replaced in later versions due to incompatible licenses. We are generally aiming for public domain background images, but will also accept backgrounds given proper licenses to be included within Cambridge.
Sounds
------
All piece sounds are (c) 2020 Damian Yerrick.
Other sounds from:
- NullpoMino
- DTET, (c) 2003 Mihys.
Music Music
----- -----
1. Second Reality opening scene music (1993). 1. TGM3 credit roll music.
2. The FitnessGram™ Pacer Test. 2. The FitnessGram™ Pacer Test.
All background music is (currently) only unofficially included. In later releases they may be replaced with specifically licensed music as applicable. All background music is (currently) only unofficially included. In later releases they may be replaced with specifically licensed music as applicable.
@@ -114,29 +106,3 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
simple-slider (https://love2d.org/forums/viewtopic.php?t=80711)
--------------------
Copyright (c) 2016 George Prosser
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,11 +0,0 @@
@del cambridge.love
@del dist\windows\cambridge.exe
@del dist\windows\SOURCES.md
@del dist\windows\LICENSE.md
@rmdir /Q /S dist\windows\libs
@del dist\win32\cambridge.exe
@del dist\win32\SOURCES.md
@del dist\win32\LICENSE.md
@rmdir /Q /S dist\win32\libs
@del dist\cambridge-windows.zip
@del dist\cambridge-win32.zip

View File

@@ -4,18 +4,20 @@ Game modes
There are several classes of game modes. The modes that originate from other games are organized by suffix: There are several classes of game modes. The modes that originate from other games are organized by suffix:
* The "C" series stand for "Classic" games, games that were produced before around 1992-1993 and generally have no wallkicks or lock delay. * The "C" series stand for "Classic" games, games that were produced before around 1992-1993 and generally have no wallkicks or lock delay.
* C84 - The original version from the Electronika 60. * C84 - The original version from the Electronika 60.
* C88 - Sega Tetris. * C88 - Sega Tetris.
* C89 - Nintendo / NES Tetris. * C89 - Nintendo / NES Tetris.
* The "A" series stand for "Arika" games, or games in the Tetris the Grand Master series. * The "A" series stand for "Arika" games, or games in the Tetris the Grand Master series.
* A1 - Tetris The Grand Master (the original from 1998). * A1 - Tetris The Grand Master (the original from 1998).
* A2 - Tetris The Absolute The Grand Master 2 PLUS. * A2 - Tetris The Absolute The Grand Master 2 PLUS.
* A3 - Tetris The Grand Master 3 Terror-Instinct. * A3 - Tetris The Grand Master 3 Terror-Instinct.
* AX - Tetris The Grand Master ACE (X for Xbox). * AX - Tetris The Grand Master ACE (X for Xbox).
* 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,15 +1,13 @@
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 = {}
for k, v in pairs(t) do target[k] = v end for k, v in pairs(t) do target[k] = v end
setmetatable(target, meta) setmetatable(target, meta)
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,282 +0,0 @@
local ffi = require "ffi"
-- Get the host os to load correct lib
local osname = love.system.getOS()
local discordRPClib = nil
-- FFI requires the libraries really be files just sitting in the filesystem. It
-- can't load libraries from a .love archive, nor a fused executable on Windows.
-- Merely using love.filesystem.getSource() only works when running LOVE with
-- the game unarchived from command line, like "love .".
--
-- The code here setting "source" will set the directory where the game was run
-- from, so FFI can load discordRPC. We assume that the discordRPC library's
-- libs directory is in the same directory as the .love archive; if it's
-- missing, it just won't load.
local source = love.filesystem.getSource()
if string.sub(source, -5) == ".love" or love.filesystem.isFused() then
source = love.filesystem.getSourceBaseDirectory()
end
if osname == "Linux" then
discordRPClib = ffi.load(source.."/libs/discord-rpc.so")
elseif osname == "OS X" then
discordRPClib = ffi.load(source.."/libs/discord-rpc.dylib")
elseif osname == "Windows" then
discordRPClib = ffi.load(source.."/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

@@ -1,138 +0,0 @@
--[[
Copyright (c) 2016 George Prosser
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
]]
local slider = {}
slider.__index = slider
function newSlider(x, y, length, value, min, max, setter, style)
local s = {}
s.value = (value - min) / (max - min)
s.min = min
s.max = max
s.setter = setter
s.x = x
s.y = y
s.length = length
local p = style or {}
s.width = p.width or length * 0.1
s.orientation = p.orientation or 'horizontal'
s.track = p.track or 'rectangle'
s.knob = p.knob or 'rectangle'
s.grabbed = false
s.wasDown = true
s.ox = 0
s.oy = 0
return setmetatable(s, slider)
end
function slider:update(mouseX, mouseY, mouseDown)
local x = mouseX or love.mouse.getX()
local y = mouseY or love.mouse.getY()
local down = love.mouse.isDown(1)
if mouseDown ~= nil then
down = mouseDown
end
local knobX = self.x
local knobY = self.y
if self.orientation == 'horizontal' then
knobX = self.x - self.length/2 + self.length * self.value
elseif self.orientation == 'vertical' then
knobY = self.y + self.length/2 - self.length * self.value
end
local ox = x - knobX
local oy = y - knobY
local dx = ox - self.ox
local dy = oy - self.oy
if down then
if self.grabbed then
if self.orientation == 'horizontal' then
self.value = self.value + dx / self.length
elseif self.orientation == 'vertical' then
self.value = self.value - dy / self.length
end
elseif (x > knobX - self.width/2 and x < knobX + self.width/2 and y > knobY - self.width/2 and y < knobY + self.width/2) and not self.wasDown then
self.ox = ox
self.oy = oy
self.grabbed = true
end
else
self.grabbed = false
end
self.value = math.max(0, math.min(1, self.value))
if self.setter ~= nil then
self.setter(self.min + self.value * (self.max - self.min))
end
self.wasDown = down
end
function slider:draw()
if self.track == 'rectangle' then
if self.orientation == 'horizontal' then
love.graphics.rectangle('line', self.x - self.length/2 - self.width/2, self.y - self.width/2, self.length + self.width, self.width)
elseif self.orientation == 'vertical' then
love.graphics.rectangle('line', self.x - self.width/2, self.y - self.length/2 - self.width/2, self.width, self.length + self.width)
end
elseif self.track == 'line' then
if self.orientation == 'horizontal' then
love.graphics.line(self.x - self.length/2, self.y, self.x + self.length/2, self.y)
elseif self.orientation == 'vertical' then
love.graphics.line(self.x, self.y - self.length/2, self.x, self.y + self.length/2)
end
elseif self.track == 'roundrect' then
if self.orientation == 'horizontal' then
love.graphics.rectangle('line', self.x - self.length/2 - self.width/2, self.y - self.width/2, self.length + self.width, self.width, self.width/2, self.width)
elseif self.orientation == 'vertical' then
love.graphics.rectangle('line', self.x - self.width/2, self.y - self.length/2 - self.width/2, self.width, self.length + self.width, self.width, self.width/2)
end
end
local knobX = self.x
local knobY = self.y
if self.orientation == 'horizontal' then
knobX = self.x - self.length/2 + self.length * self.value
elseif self.orientation == 'vertical' then
knobY = self.y + self.length/2 - self.length * self.value
end
if self.knob == 'rectangle' then
love.graphics.rectangle('fill', knobX - self.width/2, knobY - self.width/2, self.width, self.width)
elseif self.knob == 'circle' then
love.graphics.circle('fill', knobX, knobY, self.width/2)
end
end
function slider:getValue()
return self.min + self.value * (self.max - self.min)
end

View File

@@ -47,7 +47,7 @@ function fadeoutBGM(time)
end end
function resetBGMFadeout(time) function resetBGMFadeout(time)
current_bgm:setVolume(config.bgm_volume) current_bgm:setVolume(1)
fading_bgm = false fading_bgm = false
current_bgm:play() current_bgm:play()
end end
@@ -59,7 +59,7 @@ function processBGMFadeout(dt)
fadeout_time = 0 fadeout_time = 0
fading_bgm = false fading_bgm = false
end end
current_bgm:setVolume(fadeout_time * config.bgm_volume / total_fadeout_time) current_bgm:setVolume(fadeout_time / total_fadeout_time)
end end
end end

View File

@@ -1,78 +1,54 @@
backgrounds = { backgrounds = {
[0] = love.graphics.newImage("res/backgrounds/0.png"), [0] = love.graphics.newImage("res/backgrounds/0-quantum-foam.png"),
love.graphics.newImage("res/backgrounds/100.png"), love.graphics.newImage("res/backgrounds/100-big-bang.png"),
love.graphics.newImage("res/backgrounds/200.png"), love.graphics.newImage("res/backgrounds/200-spiral-galaxy.png"),
love.graphics.newImage("res/backgrounds/300.png"), love.graphics.newImage("res/backgrounds/300-sun-and-dust.png"),
love.graphics.newImage("res/backgrounds/400.png"), love.graphics.newImage("res/backgrounds/400-earth-and-moon.png"),
love.graphics.newImage("res/backgrounds/500.png"), love.graphics.newImage("res/backgrounds/500-cambrian-explosion.png"),
love.graphics.newImage("res/backgrounds/600.png"), love.graphics.newImage("res/backgrounds/600-dinosaurs.png"),
love.graphics.newImage("res/backgrounds/700.png"), love.graphics.newImage("res/backgrounds/700-asteroid.png"),
love.graphics.newImage("res/backgrounds/800.png"), love.graphics.newImage("res/backgrounds/800-human-fire.png"),
love.graphics.newImage("res/backgrounds/900.png"), love.graphics.newImage("res/backgrounds/900-early-civilization.png"),
love.graphics.newImage("res/backgrounds/1000.png"), love.graphics.newImage("res/backgrounds/1000-vikings.png"),
love.graphics.newImage("res/backgrounds/1100.png"), love.graphics.newImage("res/backgrounds/1100-crusades.png"),
love.graphics.newImage("res/backgrounds/1200.png"), love.graphics.newImage("res/backgrounds/1200-genghis-khan.png"),
love.graphics.newImage("res/backgrounds/1300.png"), love.graphics.newImage("res/backgrounds/1300-black-death.png"),
love.graphics.newImage("res/backgrounds/1400.png"), love.graphics.newImage("res/backgrounds/1400-columbus-discovery.png"),
love.graphics.newImage("res/backgrounds/1500.png"), love.graphics.newImage("res/backgrounds/1500-aztecas.png"),
love.graphics.newImage("res/backgrounds/1600.png"), love.graphics.newImage("res/backgrounds/1600-telescope.png"),
love.graphics.newImage("res/backgrounds/1700.png"), love.graphics.newImage("res/backgrounds/1700-american-revolution.png"),
love.graphics.newImage("res/backgrounds/1800.png"), love.graphics.newImage("res/backgrounds/1800-railways.png"),
love.graphics.newImage("res/backgrounds/1900.png"), love.graphics.newImage("res/backgrounds/1900-world-wide-web.png"),
title = love.graphics.newImage("res/backgrounds/title.png"), title = love.graphics.newImage("res/backgrounds/title_v0.1.png"),
snow = love.graphics.newImage("res/backgrounds/snow.png"),
input_config = love.graphics.newImage("res/backgrounds/options-input.png"),
game_config = love.graphics.newImage("res/backgrounds/options-game.png"),
} }
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"), F = love.graphics.newImage("res/img/s9.png"),
A = love.graphics.newImage("res/img/s8.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"),
G = love.graphics.newImage("res/img/bone.png"), T = love.graphics.newImage("res/img/bone.png"),
C = love.graphics.newImage("res/img/bone.png"), Z = love.graphics.newImage("res/img/bone.png"),
B = love.graphics.newImage("res/img/bone.png"),
M = love.graphics.newImage("res/img/bone.png"),
F = love.graphics.newImage("res/img/bone.png"), F = love.graphics.newImage("res/img/bone.png"),
A = love.graphics.newImage("res/img/bone.png"), G = 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")
@@ -85,5 +61,4 @@ misc_graphics = {
go = love.graphics.newImage("res/img/go.png"), go = love.graphics.newImage("res/img/go.png"),
select_mode = love.graphics.newImage("res/img/select_mode.png"), select_mode = love.graphics.newImage("res/img/select_mode.png"),
strike = love.graphics.newImage("res/img/strike.png"), strike = love.graphics.newImage("res/img/strike.png"),
santa = love.graphics.newImage("res/img/santa.png")
} }

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,50 +10,20 @@ 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"),
lock = love.audio.newSource("res/se/lock.wav", "static"),
hold = love.audio.newSource("res/se/hold.wav", "static"),
erase = love.audio.newSource("res/se/erase.wav", "static"),
fall = love.audio.newSource("res/se/fall.wav", "static"),
ready = love.audio.newSource("res/se/ready.wav", "static"),
go = love.audio.newSource("res/se/go.wav", "static"),
irs = love.audio.newSource("res/se/irs.wav", "static"),
ihs = love.audio.newSource("res/se/ihs.wav", "static"),
-- a secret sound!
welcome = love.audio.newSource("res/se/welcomeToCambridge.wav", "static"),
} }
function playSE(sound, subsound) function playSE(sound, subsound)
if subsound == nil then if subsound == nil then
sounds[sound]:setVolume(config.sfx_volume) sounds[sound]:setVolume(0.1)
if sounds[sound]:isPlaying() then if sounds[sound]:isPlaying() then
sounds[sound]:stop() sounds[sound]:stop()
end end
sounds[sound]:play() sounds[sound]:play()
else else
sounds[sound][subsound]:setVolume(config.sfx_volume) sounds[sound][subsound]:setVolume(0.1)
if sounds[sound][subsound]:isPlaying() then if sounds[sound][subsound]:isPlaying() then
sounds[sound][subsound]:stop() sounds[sound][subsound]:stop()
end end
sounds[sound][subsound]:play() sounds[sound][subsound]:play()
end end
end end
function playSEOnce(sound, subsound)
if subsound == nil then
sounds[sound]:setVolume(config.sfx_volume)
if sounds[sound]:isPlaying() then
return
end
sounds[sound]:play()
else
sounds[sound][subsound]:setVolume(config.sfx_volume)
if sounds[sound][subsound]:isPlaying() then
return
end
sounds[sound][subsound]:play()
end
end

174
main.lua
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"
@@ -9,61 +8,21 @@ function love.load()
require "load.save" require "load.save"
loadSave() loadSave()
require "scene" require "scene"
--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.das then config.das = 10 end
if not config.arr then config.arr = 2 end
if not config.sfx_volume then config.sfx_volume = 0.5 end
if not config.bgm_volume then config.bgm_volume = 0.5 end
if config.secret == nil then config.secret = false
elseif config.secret == true then playSE("welcome") end
if not config.gamesettings then
config.gamesettings = {}
config["das_last_key"] = false
else
config["das_last_key"] = config.gamesettings.das_last_key == 2
end
for _, option in ipairs(GameConfigScene.options) do
if not config.gamesettings[option[1]] then
config.gamesettings[option[1]] = 1
end
end
if not config.input then if not config.input then
config.input = {}
scene = InputConfigScene() scene = InputConfigScene()
else else
if config.current_mode then current_mode = config.current_mode end if config.current_mode then current_mode = config.current_mode end
if config.current_ruleset then current_ruleset = config.current_ruleset end if config.current_ruleset then current_ruleset = config.current_ruleset end
scene = TitleScene() scene = TitleScene()
end end
game_modes = {}
mode_list = love.filesystem.getDirectoryItems("tetris/modes")
for i=1,#mode_list do
if(mode_list[i] ~= "gamemode.lua" and mode_list[i] ~= "unrefactored_modes") then
game_modes[#game_modes+1] = require ("tetris.modes."..string.sub(mode_list[i],1,-5))
end
end
rulesets = {}
rule_list = love.filesystem.getDirectoryItems("tetris/rulesets")
for i=1,#rule_list do
if(rule_list[i] ~= "ruleset.lua" and rule_list[i] ~= "unrefactored_rulesets") then
rulesets[#rulesets+1] = require ("tetris.rulesets."..string.sub(rule_list[i],1,-5))
end
end
--sort mode/rule lists
local function padnum(d) return ("%03d%s"):format(#d, d) end
table.sort(game_modes, function(a,b)
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end)
table.sort(rulesets, function(a,b)
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end)
end end
local TARGET_FPS = 60 local TARGET_FPS = 60
@@ -122,137 +81,18 @@ function love.draw()
love.graphics.pop() love.graphics.pop()
end end
function love.keypressed(key, scancode) function love.keypressed(key, scancode, isrepeat)
-- global hotkeys -- global hotkeys
if scancode == "f4" then if scancode == "f4" then
config["fullscreen"] = not config["fullscreen"] config["fullscreen"] = not config["fullscreen"]
love.window.setFullscreen(config["fullscreen"]) love.window.setFullscreen(config["fullscreen"])
elseif scancode == "f2" and scene.title ~= "Input Config" and scene.title ~= "Game" then
scene = InputConfigScene()
switchBGM(nil)
-- secret sound playing :eyes:
elseif scancode == "f8" and scene.title == "Title" then
config.secret = not config.secret
saveConfig()
scene.restart_message = true
if config.secret then playSE("mode_decide")
else playSE("erase") end
-- function keys are reserved
elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f[1-9][0-9]+$") then
return
-- escape is reserved for menu_back
elseif scancode == "escape" then
scene:onInputPress({input="menu_back", type="key", key=key, scancode=scancode})
-- pass any other key to the scene, with its configured mapping
else else
local input_pressed = nil scene:onKeyPress({key=key, scancode=scancode, isRepeat=isrepeat})
if config.input and config.input.keys then
input_pressed = config.input.keys[scancode]
end
scene:onInputPress({input=input_pressed, type="key", key=key, scancode=scancode})
end
end
function love.keyreleased(key, scancode)
-- escape is reserved for menu_back
if scancode == "escape" then
scene:onInputRelease({input="menu_back", type="key", key=key, scancode=scancode})
-- function keys are reserved
elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f[1-9][0-9]+$") then
return
-- handle all other keys; tab is reserved, but the input config scene keeps it from getting configured as a game input, so pass tab to the scene here
else
local input_released = nil
if config.input and config.input.keys then
input_released = config.input.keys[scancode]
end
scene:onInputRelease({input=input_released, type="key", key=key, scancode=scancode})
end
end
function love.joystickpressed(joystick, button)
local input_pressed = nil
if
config.input and
config.input.joysticks and
config.input.joysticks[joystick:getName()] and
config.input.joysticks[joystick:getName()].buttons
then
input_pressed = config.input.joysticks[joystick:getName()].buttons[button]
end
scene:onInputPress({input=input_pressed, type="joybutton", name=joystick:getName(), button=button})
end
function love.joystickreleased(joystick, button)
local input_released = nil
if
config.input and
config.input.joysticks and
config.input.joysticks[joystick:getName()] and
config.input.joysticks[joystick:getName()].buttons
then
input_released = config.input.joysticks[joystick:getName()].buttons[button]
end
scene:onInputRelease({input=input_released, type="joybutton", name=joystick:getName(), button=button})
end
function love.joystickaxis(joystick, axis, value)
local input_pressed = nil
local positive_released = nil
local negative_released = nil
if
config.input and
config.input.joysticks and
config.input.joysticks[joystick:getName()] and
config.input.joysticks[joystick:getName()].axes and
config.input.joysticks[joystick:getName()].axes[axis]
then
if math.abs(value) >= 0.5 then
input_pressed = config.input.joysticks[joystick:getName()].axes[axis][value >= 0.5 and "positive" or "negative"]
end
positive_released = config.input.joysticks[joystick:getName()].axes[axis].positive
negative_released = config.input.joysticks[joystick:getName()].axes[axis].negative
end
if math.abs(value) >= 0.5 then
scene:onInputPress({input=input_pressed, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
else
scene:onInputRelease({input=positive_released, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
scene:onInputRelease({input=negative_released, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
end
end
function love.joystickhat(joystick, hat, direction)
local input_pressed = nil
local has_hat = false
if
config.input and
config.input.joysticks and
config.input.joysticks[joystick:getName()] and
config.input.joysticks[joystick:getName()].hats and
config.input.joysticks[joystick:getName()].hats[hat]
then
if direction ~= "c" then
input_pressed = config.input.joysticks[joystick:getName()].hats[hat][direction]
end
has_hat = true
end
if input_pressed then
scene:onInputPress({input=input_pressed, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
elseif has_hat then
for i, direction in ipairs{"d", "l", "ld", "lu", "r", "rd", "ru", "u"} do
scene:onInputRelease({input=config.input.joysticks[joystick:getName()].hats[hat][direction], type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
end
elseif direction ~= "c" then
scene:onInputPress({input=nil, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
else
for i, direction in ipairs{"d", "l", "ld", "lu", "r", "rd", "ru", "u"} do
scene:onInputRelease({input=nil, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
end
end end
end end
function love.focus(f) function love.focus(f)
if f and (scene.title ~= "Game" or not scene.paused) then if f then
resumeBGM() resumeBGM()
else else
pauseBGM() pauseBGM()

View File

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

View File

@@ -4,6 +4,6 @@ mkdir dist/windows
mkdir dist/win32 mkdir dist/win32
cp cambridge.love dist/ cp cambridge.love dist/
cat dist/windows/love.exe cambridge.love > dist/windows/cambridge.exe cat dist/windows/love.exe cambridge.love > dist/windows/cambridge.exe
zip dist/cambridge-windows.zip dist/windows/* SOURCES.md LICENSE.md zip dist/cambridge-windows.zip dist/windows/* SOURCES.md LICENSE
cat dist/win32/love.exe cambridge.love > dist/win32/cambridge.exe cat dist/win32/love.exe cambridge.love > dist/win32/cambridge.exe
zip dist/cambridge-win32.zip dist/win32/* SOURCES.md LICENSE.md zip dist/cambridge-win32.zip dist/win32/* SOURCES.md LICENSE

View File

@@ -1,26 +0,0 @@
call package.bat
mkdir dist
mkdir dist\windows
mkdir dist\windows\libs
mkdir dist\win32
mkdir dist\win32\libs
copy /b dist\windows\love.exe+cambridge.love dist\windows\cambridge.exe
copy /b dist\win32\love.exe+cambridge.love dist\win32\cambridge.exe
copy libs\discord-rpc.dll dist\windows\libs
copy libs\discord-rpc.dll dist\win32\libs
copy SOURCES.md dist\windows
copy LICENSE.md dist\windows
copy SOURCES.md dist\win32
copy LICENSE.md dist\win32
cd dist\windows
tar -a -c -f ..\cambridge-windows.zip cambridge.exe *.dll libs *.md
cd ..\..
cd dist\win32
tar -a -c -f ..\cambridge-win32.zip cambridge.exe *.dll libs *.md
cd ..\..

View File

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

View File

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

Before

Width:  |  Height:  |  Size: 2.9 MiB

After

Width:  |  Height:  |  Size: 2.9 MiB

View File

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 2.6 MiB

View File

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

View File

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

Before

Width:  |  Height:  |  Size: 3.1 MiB

After

Width:  |  Height:  |  Size: 3.1 MiB

View File

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 2.7 MiB

View File

Before

Width:  |  Height:  |  Size: 3.6 MiB

After

Width:  |  Height:  |  Size: 3.6 MiB

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.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -5,15 +5,10 @@ Scene = Object:extend()
function Scene:new() end function Scene:new() end
function Scene:update() end function Scene:update() end
function Scene:render() end function Scene:render() end
function Scene:onInputPress() end function Scene:onKeyPress() end
function Scene:onInputRelease() 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"
TuningScene = require "scene.tuning"
SettingsScene = require "scene.settings"
CreditsScene = require "scene.credits"
TitleScene = require "scene.title" TitleScene = require "scene.title"

View File

@@ -17,7 +17,7 @@ function ConfigScene:changeOption(rel)
self.main_menu_state = (self.main_menu_state + len + rel - 1) % len + 1 self.main_menu_state = (self.main_menu_state + len + rel - 1) % len + 1
end end
function ConfigScene:onInputPress(e) function ConfigScene:onKeyPress(e)
end end
return ConfigScene return ConfigScene

View File

@@ -1,62 +0,0 @@
local CreditsScene = Scene:extend()
CreditsScene.title = "Credits"
function CreditsScene:new()
self.frames = 0
switchBGM("credit_roll", "gm3")
end
function CreditsScene:update()
self.frames = self.frames + 1
if self.frames >= 4200 then
playSE("mode_decide")
scene = TitleScene()
switchBGM(nil)
elseif self.frames == 3600 then
fadeoutBGM(2)
end
end
function CreditsScene:render()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(
backgrounds[19],
0, 0, 0,
0.5, 0.5
)
love.graphics.setFont(font_3x5_4)
love.graphics.print("Cambridge Credits", 320, 500 - self.frames / 2)
love.graphics.print("THANK YOU\nFOR PLAYING!", 320, math.max(1500 - self.frames / 2, 240))
love.graphics.setFont(font_3x5_3)
love.graphics.print("Game Developers", 320, 550 - self.frames / 2)
love.graphics.print("Project Heads", 320, 640 - self.frames / 2)
love.graphics.print("Other Game Developers", 320, 730 - self.frames / 2)
love.graphics.print("Special Thanks", 320, 900 - self.frames / 2)
love.graphics.print("- SashLilac / SpinTriple", 320, math.max(2000 - self.frames / 2, 320))
love.graphics.setFont(font_3x5_2)
love.graphics.print("Oshisaure\nJoe Zeng", 320, 590 - self.frames / 2)
love.graphics.print("Mizu\nHailey", 320, 680 - self.frames / 2)
love.graphics.print("Axel Fox - Multimino\nMine - Tetra Online\nDr Ocelot - Tetra Legends\nFelicity / nightmareci - Shiromino\n2Tie - TGMsim\nPhoenix Flare - Master of Blocks", 320, 770 - self.frames / 2)
love.graphics.print(
"RocketLanterns\nCylinderKnot\nHammrTime\nKirby703\nMattMayuga\nMyPasswordIsWeak\n" ..
"Nikki Karissa\noffwo\nsinefuse\nTetro48\nTimmSkiller\nuser74003\nAgentBasey\n" ..
"CheeZed_Fish\neightsixfivezero\nEricICX\ngizmo4487\nM1ssing0\nMarkGamed7794\n" ..
"pokemonfan1937\nSimon\nstratus\nZaptorZap\nThe Absolute PLUS Discord\nTetra Legends Discord\n" ..
"Tetra Online Discord\nMultimino Discord\nCambridge Discord\nAnd to you, the player!",
320, 940 - self.frames / 2
)
end
function CreditsScene:onInputPress(e)
if e.input == "menu_decide" or e.scancode == "return" or
e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
scene = TitleScene()
switchBGM(nil)
end
end
return CreditsScene

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:onInputPress(e)
end
return ExitScene

View File

@@ -1,43 +1,29 @@
local GameScene = Scene:extend() local GameScene = Scene:extend()
GameScene.title = "Game"
require 'load.save' require 'load.save'
function GameScene:new(game_mode, ruleset) function GameScene:new(game_mode, ruleset)
self.retry_mode = game_mode
self.retry_ruleset = 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)
self.inputs = {
left=false,
right=false,
up=false,
down=false,
rotate_left=false,
rotate_left2=false,
rotate_right=false,
rotate_right2=false,
rotate_180=false,
hold=false,
}
self.paused = false
DiscordRPC:update({
details = self.game.rpc_details,
state = self.game.name,
})
end end
function GameScene:update() function GameScene:update()
if love.window.hasFocus() and not self.paused then if love.window.hasFocus() then
local inputs = {} self.game:update({
for input, value in pairs(self.inputs) do left = love.keyboard.isScancodeDown(config.input.left),
inputs[input] = value right = love.keyboard.isScancodeDown(config.input.right),
end up = love.keyboard.isScancodeDown(config.input.up),
self.game:update(inputs, self.ruleset) down = love.keyboard.isScancodeDown(config.input.down),
self.game.grid:update() rotate_left = love.keyboard.isScancodeDown(config.input.rotate_left),
rotate_left2 = love.keyboard.isScancodeDown(config.input.rotate_left2),
rotate_right = love.keyboard.isScancodeDown(config.input.rotate_right),
rotate_right2 = love.keyboard.isScancodeDown(config.input.rotate_right2),
rotate_180 = love.keyboard.isScancodeDown(config.input.rotate_180),
hold = love.keyboard.isScancodeDown(config.input.hold),
}, self.ruleset)
end end
self.game.grid:update()
end end
function GameScene:render() function GameScene:render()
@@ -59,7 +45,6 @@ function GameScene:render()
self.game:drawScoringInfo() self.game:drawScoringInfo()
-- ready/go graphics -- ready/go graphics
if self.game.ready_frames <= 100 and self.game.ready_frames > 52 then if self.game.ready_frames <= 100 and self.game.ready_frames > 52 then
love.graphics.draw(misc_graphics["ready"], 144 - 50, 240 - 14) love.graphics.draw(misc_graphics["ready"], 144 - 50, 240 - 14)
elseif self.game.ready_frames <= 50 and self.game.ready_frames > 2 then elseif self.game.ready_frames <= 50 and self.game.ready_frames > 2 then
@@ -68,37 +53,15 @@ function GameScene:render()
self.game:drawCustom() self.game:drawCustom()
love.graphics.setFont(font_3x5_2)
if config.gamesettings.display_gamemode == 1 then
love.graphics.printf(self.game.name .. " - " .. self.ruleset.name, 0, 460, 640, "left")
end
love.graphics.setFont(font_3x5_3)
if self.paused then love.graphics.print("PAUSED!", 80, 100) end
end end
function GameScene:onInputPress(e) function GameScene:onKeyPress(e)
if self.game.completed and (e.input == "menu_decide" or e.input == "menu_back" or e.input == "retry") then if (self.game.completed) and
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 = e.input == "retry" and GameScene(self.retry_mode, self.retry_ruleset) or ModeSelectScene()
elseif e.input == "retry" then
scene = GameScene(self.retry_mode, self.retry_ruleset)
elseif e.input == "pause" and not (self.game.game_over or self.game.completed) then
self.paused = not self.paused
if self.paused then pauseBGM()
else resumeBGM() end
elseif e.input == "menu_back" then
scene = ModeSelectScene() scene = ModeSelectScene()
elseif e.input and string.sub(e.input, 1, 5) ~= "menu_" then
self.inputs[e.input] = true
end
end
function GameScene:onInputRelease(e)
if e.input and string.sub(e.input, 1, 5) ~= "menu_" then
self.inputs[e.input] = false
end end
end end

View File

@@ -1,122 +0,0 @@
local ConfigScene = Scene:extend()
ConfigScene.title = "Game Settings"
require 'load.save'
require 'libs.simple-slider'
ConfigScene.options = {
-- this serves as reference to what the options' values mean i guess?
-- Format: {name in config, displayed name, uses slider?, options OR slider name}
{"manlock", "Manual Locking", false, {"Per ruleset", "Per gamemode", "Harddrop", "Softdrop"}},
{"piece_colour", "Piece Colours", false, {"Per ruleset", "Arika", "TTC"}},
{"world_reverse", "A Button Rotation", false, {"Left", "Auto", "Right"}},
{"display_gamemode", "Display Gamemode", false, {"On", "Off"}},
{"das_last_key", "DAS Switch", false, {"Default", "Instant"}},
{"smooth_movement", "Smooth Piece Drop", false, {"On", "Off"}},
{"synchroes_allowed", "Synchroes", false, {"Per ruleset", "On", "Off"}},
{"diagonal_input", "Diagonal Input", false, {"On", "Off"}},
{"sfx_volume", "SFX", true, "sfxSlider"},
{"bgm_volume", "BGM", true, "bgmSlider"},
}
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",
})
self.sfxSlider = newSlider(165, 375, 225, config.sfx_volume * 100, 0, 100, function(v) config.sfx_volume = v / 100 end, {width=20})
self.bgmSlider = newSlider(465, 375, 225, config.bgm_volume * 100, 0, 100, function(v) config.bgm_volume = v / 100 end, {width=20})
end
function ConfigScene:update()
config["das_last_key"] = config.gamesettings.das_last_key == 2
self.sfxSlider:update()
self.bgmSlider: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)
--Lazy check to see if we're on the SFX or BGM slider. Probably will need to be rewritten if more options get added.
love.graphics.setColor(1, 1, 1, 0.5)
if not ConfigScene.options[self.highlight][3] then
love.graphics.rectangle("fill", 20, 98 + self.highlight * 20, 170, 22)
else
love.graphics.rectangle("fill", 65 + (1+self.highlight-#self.options) * 300, 322, 215, 33)
end
love.graphics.setFont(font_3x5_2)
for i, option in ipairs(ConfigScene.options) do
if not option[3] then
love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(option[2], 40, 100 + i * 20, 150, "left")
for j, setting in ipairs(option[4]) do
love.graphics.setColor(1, 1, 1, config.gamesettings[option[1]] == j and 1 or 0.5)
love.graphics.printf(setting, 100 + 110 * j, 100 + i * 20, 100, "center")
end
end
end
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_3)
love.graphics.print("SFX Volume: " .. math.floor(self.sfxSlider:getValue()) .. "%", 75, 325)
love.graphics.print("BGM Volume: " .. math.floor(self.bgmSlider:getValue()) .. "%", 375, 325)
love.graphics.setColor(1, 1, 1, 0.75)
self.sfxSlider:draw()
self.bgmSlider:draw()
end
function ConfigScene:onInputPress(e)
if e.input == "menu_decide" or e.scancode == "return" then
playSE("mode_decide")
saveConfig()
scene = SettingsScene()
elseif e.input == "up" or e.scancode == "up" then
playSE("cursor")
self.highlight = Mod1(self.highlight-1, optioncount)
elseif e.input == "down" or e.scancode == "down" then
playSE("cursor")
self.highlight = Mod1(self.highlight+1, optioncount)
elseif e.input == "left" or e.scancode == "left" then
if not self.options[self.highlight][3] then
playSE("cursor_lr")
local option = ConfigScene.options[self.highlight]
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]-1, #option[4])
else
playSE("cursor")
sld = self[self.options[self.highlight][4]]
sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() - 3) / (sld.max - sld.min)))
end
elseif e.input == "right" or e.scancode == "right" then
if not self.options[self.highlight][3] then
playSE("cursor_lr")
local option = ConfigScene.options[self.highlight]
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]+1, #option[4])
else
playSE("cursor")
sld = self[self.options[self.highlight][4]]
sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() + 3) / (sld.max - sld.min)))--math.max(0, (math.floor(sld:getValue())+2)/(sld.max-sld.min))
end
elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
loadSave()
scene = SettingsScene()
end
end
return ConfigScene

View File

@@ -5,158 +5,57 @@ ConfigScene.title = "Input Config"
require 'load.save' require 'load.save'
local configurable_inputs = { local configurable_inputs = {
"menu_decide",
"menu_back",
"left", "left",
"right", "right",
"up", "up",
"down", "down",
"rotate_left", "rotate_left",
"rotate_left2", "rotate_left2",
"rotate_right", "rotate_right",
"rotate_right2", "rotate_right2",
"rotate_180", "rotate_180",
"hold", "hold",
"retry",
"pause",
} }
local function newSetInputs()
local set_inputs = {}
for i, input in ipairs(configurable_inputs) do
set_inputs[input] = false
end
return set_inputs
end
function ConfigScene:new() function ConfigScene:new()
-- load current config
self.config = config.input
self.input_state = 1 self.input_state = 1
self.set_inputs = newSetInputs()
self.new_input = {}
self.axis_timer = 0
DiscordRPC:update({
details = "In menus",
state = "Changing input config",
})
end 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 ipairs(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 self.set_inputs[input] then love.graphics.printf(input, 40, 50 + i * 20, 200, "left")
love.graphics.printf(self.set_inputs[input], 240, 50 + i * 20, 300, "left") love.graphics.printf(
love.keyboard.getKeyFromScancode(config.input[input]) .. " (" .. config.input[input] .. ")",
240, 50 + i * 20, 200, "left"
)
end end
end end
if self.input_state > table.getn(configurable_inputs) then if self.input_state > table.getn(configurable_inputs) then
love.graphics.print("press enter to confirm, delete/backspace to retry" .. (config.input and ", escape to cancel" or "")) love.graphics.print("press enter to confirm, delete to retry")
else else
love.graphics.print("press key or joystick input for " .. configurable_inputs[self.input_state] .. ", tab to skip" .. (config.input and ", escape to cancel" or ""), 0, 0) love.graphics.print("press key for " .. configurable_inputs[self.input_state])
love.graphics.print("function keys (F1, F2, etc.), escape, and tab can't be changed", 0, 20)
end
self.axis_timer = self.axis_timer + 1
end
local function addJoystick(input, name)
if not input.joysticks then
input.joysticks = {}
end
if not input.joysticks[name] then
input.joysticks[name] = {}
end end
end end
function ConfigScene:onInputPress(e) function ConfigScene:onKeyPress(e)
if e.type == "key" then if self.input_state > table.getn(configurable_inputs) then
-- function keys, escape, and tab are reserved and can't be remapped if e.scancode == "return" then
if e.scancode == "escape" and config.input then -- save, then load next scene
-- cancel only if there was an input config already saveConfig()
scene = SettingsScene() scene = TitleScene()
elseif self.input_state > table.getn(configurable_inputs) then elseif e.scancode == "delete" or e.scancode == "backspace" then
if e.scancode == "return" then self.input_state = 1
-- save new input, then load next scene
config.input = self.new_input
saveConfig()
scene = TitleScene()
elseif e.scancode == "delete" or e.scancode == "backspace" then
-- retry
self.input_state = 1
self.set_inputs = newSetInputs()
self.new_input = {}
end
elseif e.scancode == "tab" then
self.set_inputs[configurable_inputs[self.input_state]] = "skipped"
self.input_state = self.input_state + 1
elseif e.scancode ~= "escape" then
-- all other keys can be configured
if not self.new_input.keys then
self.new_input.keys = {}
end
self.set_inputs[configurable_inputs[self.input_state]] = "key " .. love.keyboard.getKeyFromScancode(e.scancode) .. " (" .. e.scancode .. ")"
self.new_input.keys[e.scancode] = configurable_inputs[self.input_state]
self.input_state = self.input_state + 1
end
elseif string.sub(e.type, 1, 3) == "joy" then
if self.input_state <= table.getn(configurable_inputs) then
if e.type == "joybutton" then
addJoystick(self.new_input, e.name)
if not self.new_input.joysticks[e.name].buttons then
self.new_input.joysticks[e.name].buttons = {}
end
self.set_inputs[configurable_inputs[self.input_state]] =
"jbtn " ..
e.button ..
" " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
self.new_input.joysticks[e.name].buttons[e.button] = configurable_inputs[self.input_state]
self.input_state = self.input_state + 1
elseif e.type == "joyaxis" then
if (e.axis ~= self.last_axis or self.axis_timer > 30) and math.abs(e.value) >= 1 then
addJoystick(self.new_input, e.name)
if not self.new_input.joysticks[e.name].axes then
self.new_input.joysticks[e.name].axes = {}
end
if not self.new_input.joysticks[e.name].axes[e.axis] then
self.new_input.joysticks[e.name].axes[e.axis] = {}
end
self.set_inputs[configurable_inputs[self.input_state]] =
"jaxis " ..
(e.value >= 1 and "+" or "-") .. e.axis ..
" " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
self.new_input.joysticks[e.name].axes[e.axis][e.value >= 1 and "positive" or "negative"] = configurable_inputs[self.input_state]
self.input_state = self.input_state + 1
self.last_axis = e.axis
self.axis_timer = 0
end
elseif e.type == "joyhat" then
if e.direction ~= "c" then
addJoystick(self.new_input, e.name)
if not self.new_input.joysticks[e.name].hats then
self.new_input.joysticks[e.name].hats = {}
end
if not self.new_input.joysticks[e.name].hats[e.hat] then
self.new_input.joysticks[e.name].hats[e.hat] = {}
end
self.set_inputs[configurable_inputs[self.input_state]] =
"jhat " ..
e.hat .. " " .. e.direction ..
" " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
self.new_input.joysticks[e.name].hats[e.hat][e.direction] = configurable_inputs[self.input_state]
self.input_state = self.input_state + 1
end
end
end end
else
config.input[configurable_inputs[self.input_state]] = e.scancode
self.input_state = self.input_state + 1
end end
end end

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

@@ -5,80 +5,127 @@ ModeSelectScene.title = "Game Start"
current_mode = 1 current_mode = 1
current_ruleset = 1 current_ruleset = 1
MAX_MODES = 19
game_modes = {
require 'tetris.modes.marathon_2020',
require 'tetris.modes.survival_2020',
require 'tetris.modes.strategy',
require 'tetris.modes.interval_training',
require 'tetris.modes.pacer_test',
require 'tetris.modes.marathon_wcb',
require 'tetris.modes.demon_mode',
require 'tetris.modes.phantom_mania',
require 'tetris.modes.phantom_mania2',
require 'tetris.modes.phantom_mania_n',
require 'tetris.modes.race_40',
require 'tetris.modes.marathon_a1',
require 'tetris.modes.marathon_a2',
require 'tetris.modes.marathon_a3',
require 'tetris.modes.marathon_ax',
require 'tetris.modes.marathon_ax2',
require 'tetris.modes.marathon_ax3',
require 'tetris.modes.marathon_c89',
require 'tetris.modes.survival_a1',
require 'tetris.modes.survival_a2',
require 'tetris.modes.survival_a3',
require 'tetris.modes.survival_ax',
require 'tetris.modes.survival_ax2',
require 'tetris.modes.credits_a3',
}
rulesets = {
require 'tetris.rulesets.cambridge',
require 'tetris.rulesets.standard',
require 'tetris.rulesets.standard_ti',
require 'tetris.rulesets.arika',
require 'tetris.rulesets.arika_ti',
require 'tetris.rulesets.arika_ace',
--require 'tetris.rulesets.bonkers',
--require 'tetris.rulesets.shirase',
--require 'tetris.rulesets.super302',
}
function ModeSelectScene:new() function ModeSelectScene:new()
self.menu_state = { self.menu_state = {
mode = current_mode, mode = current_mode,
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()
switchBGM(nil) -- experimental
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, 258, 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, 258, 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
if(idx >= self.menu_state.mode-9 and idx <= self.menu_state.mode+9) then
love.graphics.printf(mode.name, 40, (260 - 20*(self.menu_state.mode)) + 20 * idx, 200, "left")
end
end
for idx, ruleset in pairs(rulesets) do
if(idx >= self.menu_state.ruleset-9 and idx <= self.menu_state.ruleset+9) then
love.graphics.printf(ruleset.name, 360, (260 - 20*(self.menu_state.ruleset)) + 20 * idx, 160, "left")
end
end
end end
function ModeSelectScene:onInputPress(e) function ModeSelectScene:onKeyPress(e)
if e.input == "menu_decide" or e.scancode == "return" then if e.scancode == "return" and e.isRepeat == false then
current_mode = self.menu_state.mode current_mode = self.menu_state.mode
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.input == "up" or e.scancode == "up" 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.input == "down" or e.scancode == "down" then
self:changeOption(1) self:changeOption(1)
playSE("cursor") elseif (e.scancode == config.input["left"] or e.scancode == "left") or
elseif e.input == "left" or e.input == "right" or e.scancode == "left" or e.scancode == "right" then (e.scancode == config.input["right"] or e.scancode == "right") then
self:switchSelect() self:switchSelect()
playSE("cursor_lr")
elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
scene = TitleScene()
end end
end end

View File

@@ -1,65 +0,0 @@
local SettingsScene = Scene:extend()
SettingsScene.title = "Settings"
local menu_screens = {
InputConfigScene,
GameConfigScene,
TuningScene
}
function SettingsScene:new()
self.menu_state = 1
DiscordRPC:update({
details = "In menus",
state = "Changing settings",
})
end
function SettingsScene:update() end
function SettingsScene:render()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(
backgrounds["game_config"],
0, 0, 0,
0.5, 0.5
)
love.graphics.setFont(font_3x5_4)
love.graphics.print("SETTINGS", 80, 40)
love.graphics.setFont(font_3x5_2)
love.graphics.print("Here, you can change some settings that change\nthe look and feel of the game.", 80, 90)
love.graphics.setColor(1, 1, 1, 0.5)
love.graphics.rectangle("fill", 75, 118 + 50 * self.menu_state, 200, 33)
love.graphics.setFont(font_3x5_3)
love.graphics.setColor(1, 1, 1, 1)
for i, screen in pairs(menu_screens) do
love.graphics.printf(screen.title, 80, 120 + 50 * i, 200, "left")
end
end
function SettingsScene:changeOption(rel)
local len = table.getn(menu_screens)
self.menu_state = (self.menu_state + len + rel - 1) % len + 1
end
function SettingsScene:onInputPress(e)
if e.input == "menu_decide" or e.scancode == "return" then
playSE("main_decide")
scene = menu_screens[self.menu_state]()
elseif e.input == "up" or e.scancode == "up" then
self:changeOption(-1)
playSE("cursor")
elseif e.input == "down" or e.scancode == "down" then
self:changeOption(1)
playSE("cursor")
elseif e.input == "menu_back" or e.scancode == "backspace" or e.scancode == "delete" then
scene = TitleScene()
end
end
return SettingsScene

View File

@@ -1,79 +1,26 @@
local TitleScene = Scene:extend() local TitleScene = Scene:extend()
TitleScene.title = "Title"
TitleScene.restart_message = false
local main_menu_screens = { local main_menu_screens = {
ModeSelectScene, ModeSelectScene,
SettingsScene, InputConfigScene,
CreditsScene,
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
self.frames = 0
self.snow_bg_opacity = 0
self.y_offset = 0
self.text = ""
self.text_flag = false
DiscordRPC:update({
details = "In menus",
state = mainmenuidle[math.random(#mainmenuidle)],
})
end end
function TitleScene:update() function TitleScene:update()
if self.text_flag then
self.frames = self.frames + 1
self.snow_bg_opacity = self.snow_bg_opacity + 0.01
end
if self.frames < 125 then self.y_offset = self.frames
elseif self.frames < 185 then self.y_offset = 125
else self.y_offset = 310 - self.frames end
end end
function TitleScene:render() function TitleScene:render()
love.graphics.setFont(font_3x5_2) love.graphics.setFont(font_3x5_2)
love.graphics.setColor(1, 1, 1, 1 - self.snow_bg_opacity)
love.graphics.draw( love.graphics.draw(
backgrounds["title"], backgrounds["title"],
0, 0, 0, 0, 0, 0,
0.5, 0.5 0.5, 0.5
) )
love.graphics.setColor(1, 1, 1, self.snow_bg_opacity)
love.graphics.draw(
backgrounds["snow"],
0, 0, 0,
0.5, 0.5
)
love.graphics.draw(
misc_graphics["santa"],
400, -205 + self.y_offset,
0, 0.5, 0.5
)
love.graphics.print("Happy Holidays!", 320, -100 + self.y_offset)
love.graphics.print(self.restart_message and "Restart Cambridge..." or "", 0, 0)
love.graphics.setColor(1, 1, 1, 0.5) love.graphics.setColor(1, 1, 1, 0.5)
love.graphics.rectangle("fill", 20, 278 + 20 * self.main_menu_state, 160, 22) love.graphics.rectangle("fill", 20, 278 + 20 * self.main_menu_state, 160, 22)
@@ -89,23 +36,13 @@ function TitleScene:changeOption(rel)
self.main_menu_state = (self.main_menu_state + len + rel - 1) % len + 1 self.main_menu_state = (self.main_menu_state + len + rel - 1) % len + 1
end end
function TitleScene:onInputPress(e) function TitleScene:onKeyPress(e)
if e.input == "menu_decide" or e.scancode == "return" 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.input == "up" or e.scancode == "up" 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.input == "down" or e.scancode == "down" then
self:changeOption(1) self:changeOption(1)
playSE("cursor")
elseif e.input == "menu_back" or e.scancode == "backspace" or e.scancode == "delete" then
love.event.quit()
else
self.text = self.text .. e.scancode
if self.text == "ffffff" then
self.text_flag = true
end
end end
end end

View File

@@ -1,85 +0,0 @@
local TuningScene = Scene:extend()
TuningScene.title = "Tuning Settings"
require 'load.save'
require 'libs.simple-slider'
TuningScene.options = {
-- Serves as a reference for the options available in the menu. Format: {name in config, name as displayed if applicable, slider name}
{"das", "DAS", "dasSlider"},
{"arr", "ARR", "arrSlider"},
}
local optioncount = #TuningScene.options
function TuningScene:new()
DiscordRPC:update({
details = "In menus",
state = "Changing tuning settings",
})
self.highlight = 1
self.dasSlider = newSlider(290, 225, 400, config.das, 0, 20, function(v) config.das = math.floor(v) end, {width=20})
self.arrSlider = newSlider(290, 325, 400, config.arr, 0, 6, function(v) config.arr = math.floor(v) end, {width=20})
end
function TuningScene:update()
self.dasSlider:update()
self.arrSlider:update()
end
function TuningScene:render()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(
backgrounds["game_config"],
0, 0, 0,
0.5, 0.5
)
love.graphics.setColor(1, 1, 1, 0.5)
love.graphics.rectangle("fill", 75, 73 + self.highlight * 100, 400, 33)
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_4)
love.graphics.print("TUNING SETTINGS", 80, 40)
love.graphics.setFont(font_3x5_2)
love.graphics.print("These settings will only apply to modes\nthat do not use their own tunings.", 80, 90)
love.graphics.setFont(font_3x5_3)
love.graphics.print("Delayed Auto-Shift (DAS): " .. math.floor(self.dasSlider:getValue()) .. "F", 80, 175)
love.graphics.print("Auto-Repeat Rate (ARR): " .. math.floor(self.arrSlider:getValue()) .. "F", 80, 275)
love.graphics.setColor(1, 1, 1, 0.75)
self.dasSlider:draw()
self.arrSlider:draw()
end
function TuningScene:onInputPress(e)
if e.input == "menu_decide" or e.scancode == "return" then
playSE("mode_decide")
saveConfig()
scene = SettingsScene()
elseif e.input == "up" or e.scancode == "up" then
playSE("cursor")
self.highlight = Mod1(self.highlight-1, optioncount)
elseif e.input == "down" or e.scancode == "down" then
playSE("cursor")
self.highlight = Mod1(self.highlight+1, optioncount)
elseif e.input == "left" or e.scancode == "left" then
playSE("cursor")
sld = self[self.options[self.highlight][3]]
sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() - 1) / (sld.max - sld.min)))
elseif e.input == "right" or e.scancode == "right" then
playSE("cursor")
sld = self[self.options[self.highlight][3]]
sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() + 1) / (sld.max - sld.min)))
elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
loadSave()
scene = SettingsScene()
end
end
return TuningScene

View File

@@ -3,8 +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 = "" }
local block = { skin = "2tie", colour = "A" }
function Grid:new() function Grid:new()
self.grid = {} self.grid = {}
@@ -28,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)
@@ -55,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
@@ -67,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
@@ -89,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
@@ -142,49 +133,14 @@ function Grid:copyBottomRow()
self.grid[24] = {empty, empty, empty, empty, empty, empty, empty, empty, empty, empty} self.grid[24] = {empty, empty, empty, empty, empty, empty, empty, empty, empty, empty}
self.grid_age[24] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} self.grid_age[24] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
for col = 1, 10 do for col = 1, 10 do
self.grid[24][col] = (self.grid[23][col] == empty) and empty or block self.grid[24][col] = (self.grid[23][col] == empty) and empty or {
skin = self.grid[23][col].skin,
colour = "G"
}
end end
return true return true
end end
function Grid:garbageRise(row_vals)
for row = 1, 23 do
self.grid[row] = self.grid[row+1]
self.grid_age[row] = self.grid_age[row+1]
end
self.grid[24] = {empty, empty, empty, empty, empty, empty, empty, empty, empty, empty}
self.grid_age[24] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
for col = 1, 10 do
self.grid[24][col] = (row_vals[col] == "e") and empty or block
end
end
function Grid:applyFourWide()
for row = 1, 24 do
local x = self.grid[row]
x[1] = x[1]~=block and block or x[1]
x[2] = x[2]~=block and block or x[2]
x[3] = x[3]~=block and block or x[3]
x[8] = x[8]~=block and block or x[8]
x[9] = x[9]~=block and block or x[9]
x[10] = x[10]~=block and block or x[10]
end
end
function Grid:applyCeiling(lines)
for row = 1, lines do
for col = 1, 9 do
self.grid[row][col] = block
end
end
end
function Grid:clearSpecificRow(row)
for col = 1, 10 do
self.grid[row][col] = empty
end
end
function Grid:applyPiece(piece) function Grid:applyPiece(piece)
if piece.big then if piece.big then
self:applyBigPiece(piece) self:applyBigPiece(piece)
@@ -194,12 +150,10 @@ 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 and y < 24 then self.grid[y+1][x+1] = {
self.grid[y+1][x+1] = { skin = piece.skin,
skin = piece.skin, colour = piece.shape
colour = piece.colour }
}
end
end end
end end
@@ -210,69 +164,15 @@ 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.shape
colour = piece.colour }
}
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:checkStackHeight()
for i = 0, 23 do
for j = 0, 9 do
if self:isOccupied(j, i) then return 24 - i end
end
end
return 0
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
for x = 1, 10 do for x = 1, 10 do
@@ -284,18 +184,14 @@ function Grid:update()
end end
function Grid:draw() function Grid:draw()
for y = 5, 24 do for y = 1, 24 do
for x = 1, 10 do for x = 1, 10 do
if self.grid[y][x] ~= empty then if self.grid[y][x] ~= empty then
if self.grid_age[y][x] < 2 then if self.grid_age[y][x] < 1 then
love.graphics.setColor(1, 1, 1, 1) love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(blocks[self.grid[y][x].skin]["F"], 48+x*16, y*16) love.graphics.draw(blocks[self.grid[y][x].skin]["F"], 48+x*16, y*16)
else else
if self.grid[y][x].skin == "bone" then love.graphics.setColor(0.5, 0.5, 0.5, 1)
love.graphics.setColor(1, 1, 1, 1)
else
love.graphics.setColor(0.5, 0.5, 0.5, 1)
end
love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16) love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16)
end end
if self.grid[y][x].skin ~= "bone" then if self.grid[y][x].skin ~= "bone" then
@@ -319,60 +215,33 @@ function Grid:draw()
end end
end end
function Grid:drawOutline() function Grid:drawInvisible(opacity_function, garbage_opacity_function)
for y = 5, 24 do for y = 1, 24 do
for x = 1, 10 do
if self.grid[y][x] ~= empty then
love.graphics.setColor(0.8, 0.8, 0.8, 1)
love.graphics.setLineWidth(1)
if y > 1 and self.grid[y-1][x] == empty then
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
end
if y < 24 and self.grid[y+1][x] == empty then
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
end
if x > 1 and self.grid[y][x-1] == empty then
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
end
if x < 10 and self.grid[y][x+1] == empty then
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
end
end
end
end
end
function Grid:drawInvisible(opacity_function, garbage_opacity_function, lock_flash, brightness)
lock_flash = lock_flash == nil and true or lock_flash
brightness = brightness == nil and 0.5 or brightness
for y = 5, 24 do
for x = 1, 10 do for x = 1, 10 do
if self.grid[y][x] ~= empty then if self.grid[y][x] ~= empty then
if self.grid[y][x].colour == "X" then if self.grid[y][x].colour == "X" then
opacity = 1 opacity = 1
elseif garbage_opacity_function and self.grid[y][x].colour == "A" then elseif garbage_opacity_function and self.grid[y][x].colour == "G" then
opacity = garbage_opacity_function(self.grid_age[y][x]) opacity = garbage_opacity_function(self.grid_age[y][x])
else else
opacity = opacity_function(self.grid_age[y][x]) opacity = opacity_function(self.grid_age[y][x])
end end
love.graphics.setColor(brightness, brightness, brightness, opacity) love.graphics.setColor(0.5, 0.5, 0.5, opacity)
love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16) love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16)
if lock_flash then if opacity > 0 and self.grid[y][x].colour ~= "X" then
if opacity > 0 and self.grid[y][x].colour ~= "X" then love.graphics.setColor(0.64, 0.64, 0.64)
love.graphics.setColor(0.64, 0.64, 0.64) love.graphics.setLineWidth(1)
love.graphics.setLineWidth(1) if y > 1 and self.grid[y-1][x] == empty then
if y > 1 and self.grid[y-1][x] == empty then love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16) end
end if y < 24 and self.grid[y+1][x] == empty then
if y < 24 and self.grid[y+1][x] == empty then love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16) end
end if x > 1 and self.grid[y][x-1] == empty then
if x > 1 and self.grid[y][x-1] == empty then love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16) end
end if x < 10 and self.grid[y][x+1] == empty then
if x < 10 and self.grid[y][x+1] == empty then love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
end
end end
end end
end end

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
@@ -101,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(math.huge, 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
@@ -143,23 +142,22 @@ function Piece:draw(opacity, brightness, grid, partial_das)
love.graphics.setColor(brightness, brightness, brightness, opacity) love.graphics.setColor(brightness, brightness, brightness, opacity)
local offsets = self:getBlockOffsets() local offsets = self:getBlockOffsets()
local gravity_offset = 0 local gravity_offset = 0
if config.gamesettings.smooth_movement == 1 and --if grid ~= nil and not self:isDropBlocked(grid) then
grid ~= nil and not self:isDropBlocked(grid) then -- gravity_offset = self.gravity * 16
gravity_offset = self.gravity * 16 --end
end
if partial_das == nil then partial_das = 0 end if partial_das == nil then partial_das = 0 end
for index, offset in pairs(offsets) do for index, offset in pairs(offsets) do
local x = self.position.x + offset.x local x = self.position.x + offset.x
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,307 +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.grade = 0
self.grade_points = 0
self.grade_point_decay_counter = 0
self.randomizer = History6RollsRandomizer()
self.lock_drop = false
self.lock_hard_drop = false
self.enable_hold = false
self.next_queue_length = 1
end
function MarathonA2Game:getARE()
if self.level < 700 then return 27
elseif self.level < 800 then return 18
else return 14 end
end
function MarathonA2Game:getLineARE()
if self.level < 600 then return 27
elseif self.level < 700 then return 18
elseif self.level < 800 then return 14
else return 8 end
end
function MarathonA2Game:getDasLimit()
if self.level < 500 then return 15
elseif self.level < 900 then return 9
else return 7 end
end
function MarathonA2Game:getLineClearDelay()
if self.level < 500 then return 40
elseif self.level < 600 then return 25
elseif self.level < 700 then return 16
elseif self.level < 800 then return 12
else return 6 end
end
function MarathonA2Game:getLockDelay()
if self.level < 900 then return 30
else return 17 end
end
function MarathonA2Game:getGravity()
if (self.level < 30) then return 4/256
elseif (self.level < 35) then return 6/256
elseif (self.level < 40) then return 8/256
elseif (self.level < 50) then return 10/256
elseif (self.level < 60) then return 12/256
elseif (self.level < 70) then return 16/256
elseif (self.level < 80) then return 32/256
elseif (self.level < 90) then return 48/256
elseif (self.level < 100) then return 64/256
elseif (self.level < 120) then return 80/256
elseif (self.level < 140) then return 96/256
elseif (self.level < 160) then return 112/256
elseif (self.level < 170) then return 128/256
elseif (self.level < 200) then return 144/256
elseif (self.level < 220) then return 4/256
elseif (self.level < 230) then return 32/256
elseif (self.level < 233) then return 64/256
elseif (self.level < 236) then return 96/256
elseif (self.level < 239) then return 128/256
elseif (self.level < 243) then return 160/256
elseif (self.level < 247) then return 192/256
elseif (self.level < 251) then return 224/256
elseif (self.level < 300) then return 1
elseif (self.level < 330) then return 2
elseif (self.level < 360) then return 3
elseif (self.level < 400) then return 4
elseif (self.level < 420) then return 5
elseif (self.level < 450) then return 4
elseif (self.level < 500) then return 3
else return 20
end
end
function MarathonA2Game:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames < 0 then return false end
if self.roll_frames > 3694 then
self.completed = true
end
elseif self.ready_frames == 0 then
self.frames = self.frames + 1
end
return true
end
function MarathonA2Game:onPieceEnter()
if (self.level % 100 ~= 99 and self.level ~= 998) and not self.clear and self.frames ~= 0 then
self.level = self.level + 1
end
end
function MarathonA2Game:onLineClear(cleared_row_count)
cleared_row_count = cleared_row_count / 2
self.level = math.min(self.level + cleared_row_count, 999)
if self.level == 999 and not self.clear then
self.clear = true
self.grid:clear()
self.roll_frames = -150
end
self.lock_drop = self.level >= 900
self.lock_hard_drop = self.level >= 900
end
function MarathonA2Game:updateScore(level, drop_bonus, cleared_lines)
if not self.clear then
cleared_lines = cleared_lines / 2
self:updateGrade(cleared_lines)
if self.grid:checkForBravo(cleared_lines) then self.bravo = 4 else self.bravo = 1 end
if cleared_lines > 0 then
self.combo = self.combo + (cleared_lines - 1) * 2
self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * self.combo * self.bravo
)
else
self.combo = 1
end
self.drop_bonus = 0
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
}
function MarathonA2Game:updateGrade(cleared_lines)
if self.clear then return end
if cleared_lines == 0 then
self.grade_point_decay_counter = self.grade_point_decay_counter + 1
if self.grade_point_decay_counter >= grade_point_decays[self.grade + 1] then
self.grade_point_decay_counter = 0
self.grade_points = math.max(0, self.grade_points - 1)
end
else
self.grade_points = self.grade_points + (
math.ceil(
grade_point_bonuses[self.grade + 1][cleared_lines] *
combo_multipliers[math.min(self.combo, 10)][cleared_lines]
) * (1 + math.floor(self.level / 250))
)
if self.grade_points >= 100 and self.grade < 31 then
self.grade_points = 0
self.grade = self.grade + 1
end
end
end
function MarathonA2Game:getLetterGrade()
local grade = grade_conversion[self.grade]
if grade < 9 then
return tostring(9 - grade)
elseif grade < 18 then
return "S" .. tostring(grade - 8)
end
end
MarathonA2Game.rollOpacityFunction = function(age)
if age < 240 then return 1
elseif age > 300 then return 0
else return 1 - (age - 240) / 60 end
end
function MarathonA2Game:drawGrid(ruleset)
if self.clear and not (self.completed or self.game_over) then
self.grid:drawInvisible(self.rollOpacityFunction, nil, false)
else
self.grid:draw()
if self.piece ~= nil and self.level < 100 then
self:drawGhostPiece(ruleset)
end
end
end
function MarathonA2Game:drawScoringInfo()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_2)
love.graphics.print(
self.das.direction .. " " ..
self.das.frames .. " " ..
strTrueValues(self.prev_inputs)
)
love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("GRADE", 240, 120, 40, "left")
love.graphics.printf("SCORE", 240, 200, 40, "left")
love.graphics.printf("LEVEL", 240, 320, 40, "left")
love.graphics.setFont(font_3x5_3)
if self.roll_frames > 3694 then love.graphics.setColor(1, 0.5, 0, 1)
elseif self.clear then love.graphics.setColor(0, 1, 0, 1) end
love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left")
love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(self.score, 240, 220, 90, "left")
love.graphics.printf(self.level, 240, 340, 40, "right")
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")
love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")
end
function MarathonA2Game:getHighscoreData()
return {
grade = grade_conversion[self.grade],
score = self.score,
level = self.level,
frames = self.frames,
}
end
function MarathonA2Game:getSectionEndLevel()
if self.level >= 900 then return 999
else return math.floor(self.level / 100 + 1) * 100 end
end
function MarathonA2Game:getBackground()
return math.floor(self.level / 100)
end
return MarathonA2Game

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

258
tetris/modes/demon_mode.lua Normal file
View File

@@ -0,0 +1,258 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local DemonModeGame = GameMode:extend()
DemonModeGame.name = "Demon Mode"
DemonModeGame.hash = "DemonMode"
DemonModeGame.tagline = "Can you handle the ludicrous speed past level 20?"
function DemonModeGame:new()
DemonModeGame.super:new()
self.roll_frames = 0
self.combo = 1
self.randomizer = History6RollsRandomizer()
self.grade = 0
self.section_start_time = 0
self.section_times = { [0] = 0 }
self.section_tetris_count = 0
self.section_tries = 0
self.enable_hold = true
self.lock_drop = true
self.next_queue_length = 3
end
function DemonModeGame:getARE()
if self.level < 500 then return 30
elseif self.level < 600 then return 25
elseif self.level < 700 then return 15
elseif self.level < 800 then return 14
elseif self.level < 900 then return 12
elseif self.level < 1000 then return 11
elseif self.level < 1100 then return 10
elseif self.level < 1300 then return 8
elseif self.level < 1400 then return 6
elseif self.level < 1700 then return 4
elseif self.level < 1800 then return 3
elseif self.level < 1900 then return 2
elseif self.level < 2000 then return 1
else return 0 end
end
function DemonModeGame:getLineARE()
return self:getARE()
end
function DemonModeGame:getDasLimit()
if self.level < 500 then return 15
elseif self.level < 1000 then return 10
elseif self.level < 1500 then return 5
elseif self.level < 1700 then return 4
elseif self.level < 1900 then return 3
elseif self.level < 2000 then return 2
else return 1 end
end
function DemonModeGame:getLineClearDelay()
if self.level < 600 then return 15
elseif self.level < 800 then return 10
elseif self.level < 1000 then return 8
elseif self.level < 1500 then return 5
elseif self.level < 1700 then return 3
elseif self.level < 1900 then return 2
elseif self.level < 2000 then return 1
else return 0 end
end
function DemonModeGame:getLockDelay()
if self.level < 100 then return 30
elseif self.level < 200 then return 25
elseif self.level < 300 then return 22
elseif self.level < 400 then return 20
elseif self.level < 1000 then return 15
elseif self.level < 1200 then return 10
elseif self.level < 1400 then return 9
elseif self.level < 1500 then return 8
elseif self.level < 1600 then return 7
elseif self.level < 1700 then return 6
elseif self.level < 1800 then return 5
elseif self.level < 1900 then return 4
elseif self.level < 2000 then return 3
else return 2 end
end
function DemonModeGame:getGravity()
return 20
end
local function getSectionForLevel(level)
return math.floor(level / 100) + 1
end
local cleared_row_levels = {1, 3, 6, 10}
function DemonModeGame:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames < 0 then
return false
elseif self.roll_frames >= 1337 then
self.completed = true
end
elseif self.ready_frames == 0 then
self.frames = self.frames + 1
end
end
function DemonModeGame:onPieceEnter()
if (self.level % 100 ~= 99) and self.frames ~= 0 then
self.level = self.level + 1
end
end
function DemonModeGame:onLineClear(cleared_row_count)
if cleared_row_count == 4 then
self.section_tetris_count = self.section_tetris_count + 1
end
local advanced_levels = cleared_row_levels[cleared_row_count]
if not self.clear then
self:updateSectionTimes(self.level, self.level + advanced_levels)
end
end
function DemonModeGame:updateSectionTimes(old_level, new_level)
local section = math.floor(old_level / 100) + 1
if math.floor(old_level / 100) < math.floor(new_level / 100) then
-- If at least one Tetris in this section hasn't been made,
-- deny section passage.
if old_level > 500 then
if self.section_tetris_count == 0 then
self.level = 100 * math.floor(old_level / 100)
self.section_tries = self.section_tries + 1
else
self.level = math.min(new_level, 2500)
-- if this is first try (no denials, add a grade)
if self.section_tries == 0 then
self.grade = self.grade + 1
end
self.section_tries = 0
self.section_tetris_count = 0
-- record new section
section_time = self.frames - self.section_start_time
table.insert(self.section_times, section_time)
self.section_start_time = self.frames
-- maybe clear
if self.level == 2500 and not self.clear then
self.clear = true
self.grid:clear()
self.roll_frames = -150
end
end
elseif old_level < 100 then
-- If section time is under cutoff, skip to level 500.
if self.frames < sp(1,00) then
self.level = 500
self.grade = 5
self.section_tries = 0
self.section_tetris_count = 0
else
self.level = math.min(new_level, 2500)
end
-- record new section
section_time = self.frames - self.section_start_time
table.insert(self.section_times, section_time)
self.section_start_time = self.frames
end
else
self.level = math.min(new_level, 2500)
end
end
function DemonModeGame:updateScore(level, drop_bonus, cleared_lines)
if cleared_lines > 0 then
self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * (cleared_lines * 2 - 1) * (self.combo * 2 - 1)
)
self.lines = self.lines + cleared_lines
self.combo = self.combo + cleared_lines - 1
else
self.drop_bonus = 0
self.combo = 1
end
end
local letter_grades = {
[0] = "", "D", "C", "B", "A",
"S", "S-A", "S-B", "S-C", "S-D",
"X", "X-A", "X-B", "X-C", "X-D",
"W", "W-A", "W-B", "W-C", "W-D",
"Master", "MasterS", "MasterX", "MasterW", "Grand Master",
"Demon Master"
}
function DemonModeGame:getLetterGrade()
return letter_grades[self.grade]
end
function DemonModeGame:drawGrid()
if self.clear and not (self.completed or self.game_over) then
self.grid:drawInvisible(self.rollOpacityFunction)
else
self.grid:draw()
end
end
DemonModeGame.rollOpacityFunction = function(age)
if age > 4 then return 0
else return 1 - age / 4 end
end
function DemonModeGame:drawScoringInfo()
DemonModeGame.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("GRADE", 240, 120, 40, "left")
love.graphics.printf("SCORE", 240, 200, 40, "left")
love.graphics.printf("LEVEL", 240, 320, 40, "left")
-- draw section time data
local current_section = getSectionForLevel(self.level)
self:drawSectionTimesWithSecondary(current_section)
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.score, 240, 220, 90, "left")
love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left")
love.graphics.printf(string.format("%.2f", self.level / 100), 240, 340, 70, "right")
end
function DemonModeGame:getHighscoreData()
return {
grade = self.grade,
level = self.level,
frames = self.frames,
}
end
function DemonModeGame:getBackground()
return math.floor(self.level / 100)
end
return DemonModeGame

View File

@@ -1,9 +1,6 @@
local Object = require 'libs.classic' local Object = require 'libs.classic'
require 'funcs' require 'funcs'
local playedReadySE = false
local playedGoSE = false
local Grid = require 'tetris.components.grid' local Grid = require 'tetris.components.grid'
local Randomizer = require 'tetris.randomizers.randomizer' local Randomizer = require 'tetris.randomizers.randomizer'
@@ -43,19 +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.irs = true
self.ihs = true
self.rpc_details = "In game"
self.SGnames = {
"9", "8", "7", "6", "5", "4", "3", "2", "1",
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"GM"
}
-- 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
@@ -63,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
@@ -72,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(),
@@ -82,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)
@@ -100,16 +90,6 @@ function GameMode:update(inputs, ruleset)
end end
if self.completed then return end if self.completed then return end
if config.gamesettings.diagonal_input == 2 then
if inputs["left"] or inputs["right"] then
inputs["up"] = false
inputs["down"] = false
elseif inputs["up"] or inputs["down"] then
inputs["left"] = false
inputs["right"] = false
end
end
-- advance one frame -- advance one frame
if self:advanceOneFrame(inputs, ruleset) == false then return end if self:advanceOneFrame(inputs, ruleset) == false then return end
@@ -132,7 +112,7 @@ function GameMode:update(inputs, ruleset)
self:whilePieceActive() self:whilePieceActive()
local gravity = self:getGravity() local gravity = self:getGravity()
if self.enable_hold and inputs["hold"] == true and self.held == false and self.prev_inputs["hold"] == false then if self.enable_hold and inputs["hold"] == true and self.held == false then
self:hold(inputs, ruleset) self:hold(inputs, ruleset)
self.prev_inputs = inputs self.prev_inputs = inputs
return return
@@ -161,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
@@ -170,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
@@ -182,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)
@@ -191,7 +175,6 @@ function GameMode:update(inputs, ruleset)
end end
if cleared_row_count > 0 then if cleared_row_count > 0 then
playSE("erase")
self.lcd = self:getLineClearDelay() self.lcd = self:getLineClearDelay()
self.are = self:getLineARE() self.are = self:getLineARE()
if self.lcd == 0 then if self.lcd == 0 then
@@ -227,12 +210,8 @@ end
function GameMode:whilePieceActive() end function GameMode:whilePieceActive() end
function GameMode:onAttemptPieceMove(piece) end function GameMode:onAttemptPieceMove(piece) end
function GameMode:onAttemptPieceRotate(piece) end function GameMode:onAttemptPieceRotate(piece) end
function GameMode:onPieceLock(piece, cleared_row_count) function GameMode:onPieceLock(piece, cleared_row_count) end
playSE("lock")
end
function GameMode:onLineClear(cleared_row_count) end function GameMode:onLineClear(cleared_row_count) end
function GameMode:onPieceEnter() end function GameMode:onPieceEnter() end
function GameMode:onHold() end function GameMode:onHold() end
@@ -312,57 +291,21 @@ function GameMode:chargeDAS(inputs)
end end
function GameMode:processDelays(inputs, ruleset, drop_speed) function GameMode:processDelays(inputs, ruleset, drop_speed)
if self.ready_frames == 100 then
playedReadySE = false
playedGoSE = false
end
if self.ready_frames > 0 then if self.ready_frames > 0 then
if not playedReadySE then
playedReadySE = true
playSEOnce("ready")
end
self.ready_frames = self.ready_frames - 1 self.ready_frames = self.ready_frames - 1
if self.ready_frames == 50 and not playedGoSE then
playedGoSE = true
playSEOnce("go")
end
if self.ready_frames == 0 then if self.ready_frames == 0 then
self:initializeOrHold(inputs, ruleset) self:initializeOrHold(inputs, ruleset)
end end
elseif self.lcd > 0 then elseif self.lcd > 0 then
self.lcd = self.lcd - 1 self.lcd = self.lcd - 1
if ruleset.are_cancel and
(self.move == "none" and not self.prev_inputs["up"] and
not self.prev_inputs["rotate_left"] and not self.prev_inputs["rotate_left2"] and
not self.prev_inputs["rotate_right"] and not self.prev_inputs["rotate_right2"] and
not self.prev_inputs["rotate_180"]) and
(inputs["left"] or inputs["right"] or inputs["up"] or
inputs["rotate_left"] or inputs["rotate_left2"] or
inputs["rotate_right"] or inputs["rotate_right2"] or
inputs["rotate_180"]) then
self.lcd = 0
self.are = 0
end
if self.lcd == 0 then if self.lcd == 0 then
self.grid:clearClearedRows() self.grid:clearClearedRows()
playSE("fall")
if self.are == 0 then if self.are == 0 then
self:initializeOrHold(inputs, ruleset) self:initializeOrHold(inputs, ruleset)
end end
end end
elseif self.are > 0 then elseif self.are > 0 then
self.are = self.are - 1 self.are = self.are - 1
if ruleset.are_cancel and
(self.move == "none" and not self.prev_inputs["up"] and
not self.prev_inputs["rotate_left"] and not self.prev_inputs["rotate_left2"] and
not self.prev_inputs["rotate_right"] and not self.prev_inputs["rotate_right2"] and
not self.prev_inputs["rotate_180"]) and
(inputs["left"] or inputs["right"] or inputs["up"] or
inputs["rotate_left"] or inputs["rotate_left2"] or
inputs["rotate_right"] or inputs["rotate_right2"] or
inputs["rotate_180"]) then
self.are = 0
end
if self.are == 0 then if self.are == 0 then
self:initializeOrHold(inputs, ruleset) self:initializeOrHold(inputs, ruleset)
end end
@@ -370,8 +313,8 @@ function GameMode:processDelays(inputs, ruleset, drop_speed)
end end
function GameMode:initializeOrHold(inputs, ruleset) function GameMode:initializeOrHold(inputs, ruleset)
if self.ihs and self.enable_hold and inputs["hold"] == true then if self.enable_hold and inputs["hold"] == true then
self:hold(inputs, ruleset, true) self:hold(inputs, ruleset)
else else
self:initializeNextPiece(inputs, ruleset, self.next_queue[1]) self:initializeNextPiece(inputs, ruleset, self.next_queue[1])
end end
@@ -382,7 +325,7 @@ function GameMode:initializeOrHold(inputs, ruleset)
end end
end end
function GameMode:hold(inputs, ruleset, ihs) function GameMode:hold(inputs, ruleset)
local data = copy(self.hold_queue) local data = copy(self.hold_queue)
if self.piece == nil then if self.piece == nil then
self.hold_queue = self.next_queue[1] self.hold_queue = self.next_queue[1]
@@ -401,8 +344,6 @@ function GameMode:hold(inputs, ruleset, ihs)
self:initializeNextPiece(inputs, ruleset, data, false) self:initializeNextPiece(inputs, ruleset, data, false)
end end
self.held = true self.held = true
if ihs then playSE("ihs")
else playSE("hold") end
self:onHold() self:onHold()
end end
@@ -412,8 +353,7 @@ function GameMode:initializeNextPiece(inputs, ruleset, piece_data, generate_next
inputs, piece_data, self.grid, gravity, inputs, piece_data, self.grid, gravity,
self.prev_inputs, self.move, self.prev_inputs, self.move,
self:getLockDelay(), self:getDropSpeed(), self:getLockDelay(), self:getDropSpeed(),
self.lock_drop, self.lock_hard_drop, self.big_mode, self.lock_drop, self.lock_hard_drop, self.big_mode
self.irs
) )
if self.lock_drop then if self.lock_drop then
self.drop_locked = true self.drop_locked = true
@@ -458,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
@@ -477,9 +416,8 @@ function GameMode:drawNextQueue(ruleset)
drawPiece(next_piece, skin, ruleset.block_offsets[next_piece][rotation], -16+i*80, -32) drawPiece(next_piece, skin, ruleset.block_offsets[next_piece][rotation], -16+i*80, -32)
end end
end end
if self.hold_queue ~= nil and self.enable_hold then if self.hold_queue ~= nil then
local hold_color = self.held and 0.6 or 1 self:setHoldOpacity()
self:setHoldOpacity(1, hold_color)
drawPiece( drawPiece(
self.hold_queue.shape, self.hold_queue.shape,
self.hold_queue.skin, self.hold_queue.skin,
@@ -490,16 +428,8 @@ function GameMode:drawNextQueue(ruleset)
return false return false
end end
function GameMode:setNextOpacity(i, j) function GameMode:setNextOpacity(i) love.graphics.setColor(1, 1, 1, 1) end
i = i ~= nil and i or 1 function GameMode:setHoldOpacity() love.graphics.setColor(1, 1, 1, 1) end
j = j ~= nil and j or 1
love.graphics.setColor(j, j, j, i)
end
function GameMode:setHoldOpacity(i, j)
i = i ~= nil and i or 1
j = j ~= nil and j or 1
love.graphics.setColor(j, j, j, i)
end
function GameMode:drawScoringInfo() function GameMode:drawScoringInfo()
love.graphics.setColor(1, 1, 1, 1) love.graphics.setColor(1, 1, 1, 1)
@@ -514,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)
@@ -525,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
@@ -534,11 +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:sectionColourFunction(section) function GameMode:drawSectionTimesWithSecondary(current_section, section_colour_function)
return { 1, 1, 1, 1 }
end
function GameMode:drawSectionTimesWithSecondary(current_section)
local section_x = 530 local section_x = 530
local section_secondary_x = 440 local section_secondary_x = 440
@@ -549,11 +474,12 @@ 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
love.graphics.setColor(self:sectionColourFunction(section)) 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
love.graphics.setColor(1, 1, 1, 1)
end end
local current_x local current_x

View File

@@ -0,0 +1,154 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local IntervalTrainingGame = GameMode:extend()
IntervalTrainingGame.name = "Interval Training"
IntervalTrainingGame.hash = "IntervalTraining"
IntervalTrainingGame.tagline = "Can you clear the time hurdles when the game goes this fast?"
function IntervalTrainingGame:new()
IntervalTrainingGame.super:new()
self.roll_frames = 0
self.combo = 1
self.randomizer = History6RollsRandomizer()
self.section_time_limit = 1800
self.section_start_time = 0
self.section_times = { [0] = 0 }
self.lock_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
function IntervalTrainingGame:getARE()
return 6
end
function IntervalTrainingGame:getLineARE()
return 6
end
function IntervalTrainingGame:getDasLimit()
return 7
end
function IntervalTrainingGame:getLineClearDelay()
return 4
end
function IntervalTrainingGame:getLockDelay()
return 15
end
function IntervalTrainingGame:getGravity()
return 20
end
function IntervalTrainingGame:getSection()
return math.floor(level / 100) + 1
end
function IntervalTrainingGame:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames > 2968 then
self.completed = true
end
return false
elseif self.ready_frames == 0 then
self.frames = self.frames + 1
if self:getSectionTime() >= self.section_time_limit then
self.game_over = true
end
end
return true
end
function IntervalTrainingGame:onPieceEnter()
if (self.level % 100 ~= 99 or self.level == 998) and not self.clear and self.frames ~= 0 then
self.level = self.level + 1
end
end
function IntervalTrainingGame:onLineClear(cleared_row_count)
if not self.clear then
local new_level = self.level + cleared_row_count
self:updateSectionTimes(self.level, new_level)
self.level = math.min(new_level, 999)
if self.level == 999 then
self.clear = true
end
end
end
function IntervalTrainingGame:getSectionTime()
return self.frames - self.section_start_time
end
function IntervalTrainingGame:updateSectionTimes(old_level, new_level)
if math.floor(old_level / 100) < math.floor(new_level / 100) then
-- record new section
table.insert(self.section_times, self:getSectionTime())
self.section_start_time = self.frames
else
self.level = math.min(new_level, 999)
end
end
function IntervalTrainingGame:drawGrid(ruleset)
self.grid:draw()
end
function IntervalTrainingGame:getHighscoreData()
return {
level = self.level,
frames = self.frames,
}
end
function IntervalTrainingGame: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("LEVEL", 240, 320, 40, "left")
local current_section = math.floor(self.level / 100) + 1
self:drawSectionTimesWithSplits(current_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
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)
love.graphics.printf(self.level, 240, 340, 40, "right")
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")
end
function IntervalTrainingGame:getSectionEndLevel()
if self.level >= 900 then return 999
else return math.floor(self.level / 100 + 1) * 100 end
end
function IntervalTrainingGame:getBackground()
return math.floor(self.level / 100)
end
return IntervalTrainingGame

View File

@@ -36,12 +36,10 @@ function Marathon2020Game:new()
self.grade_points = 0 self.grade_points = 0
self.grade_point_decay_counter = 0 self.grade_point_decay_counter = 0
self.max_grade_points = 0 self.max_grade_points = 0
self.cool_timer = 0
end end
function Marathon2020Game:getARE() function Marathon2020Game:getARE()
if self.delay_level < 1 then return 27 if self.delay_level < 1 then return 27
elseif self.delay_level < 2 then return 24 elseif self.delay_level < 2 then return 24
elseif self.delay_level < 3 then return 21 elseif self.delay_level < 3 then return 21
elseif self.delay_level < 4 then return 18 elseif self.delay_level < 4 then return 18
@@ -60,7 +58,7 @@ function Marathon2020Game:getLineARE()
end end
function Marathon2020Game:getDasLimit() function Marathon2020Game:getDasLimit()
if self.delay_level < 1 then return 15 if self.delay_level < 1 then return 15
elseif self.delay_level < 3 then return 12 elseif self.delay_level < 3 then return 12
elseif self.delay_level < 5 then return 9 elseif self.delay_level < 5 then return 9
elseif self.delay_level < 8 then return 8 elseif self.delay_level < 8 then return 8
@@ -72,7 +70,7 @@ function Marathon2020Game:getDasLimit()
end end
function Marathon2020Game:getLineClearDelay() function Marathon2020Game:getLineClearDelay()
if self.delay_level < 1 then return 40 if self.delay_level < 1 then return 40
elseif self.delay_level < 3 then return 25 elseif self.delay_level < 3 then return 25
elseif self.delay_level < 4 then return 20 elseif self.delay_level < 4 then return 20
elseif self.delay_level < 5 then return 15 elseif self.delay_level < 5 then return 15
@@ -84,7 +82,7 @@ function Marathon2020Game:getLineClearDelay()
end end
function Marathon2020Game:getLockDelay() function Marathon2020Game:getLockDelay()
if self.delay_level < 6 then return 30 if self.delay_level < 6 then return 30
elseif self.delay_level < 7 then return 26 elseif self.delay_level < 7 then return 26
elseif self.delay_level < 8 then return 22 elseif self.delay_level < 8 then return 22
elseif self.delay_level < 9 then return 19 elseif self.delay_level < 9 then return 19
@@ -98,35 +96,35 @@ function Marathon2020Game:getLockDelay()
end end
function Marathon2020Game:getGravity() function Marathon2020Game:getGravity()
if self.level < 30 then return 4/256 if self.level < 30 then return 4/256
elseif self.level < 35 then return 6/256 elseif self.level < 35 then return 6/256
elseif self.level < 40 then return 8/256 elseif self.level < 40 then return 8/256
elseif self.level < 50 then return 10/256 elseif self.level < 50 then return 10/256
elseif self.level < 60 then return 12/256 elseif self.level < 60 then return 12/256
elseif self.level < 70 then return 16/256 elseif self.level < 70 then return 16/256
elseif self.level < 80 then return 32/256 elseif self.level < 80 then return 32/256
elseif self.level < 90 then return 48/256 elseif self.level < 90 then return 48/256
elseif self.level < 100 then return 64/256 elseif self.level < 100 then return 64/256
elseif self.level < 120 then return 80/256 elseif self.level < 120 then return 80/256
elseif self.level < 140 then return 96/256 elseif self.level < 140 then return 96/256
elseif self.level < 160 then return 112/256 elseif self.level < 160 then return 112/256
elseif self.level < 170 then return 128/256 elseif self.level < 170 then return 128/256
elseif self.level < 200 then return 144/256 elseif self.level < 200 then return 144/256
elseif self.level < 220 then return 4/256 elseif self.level < 220 then return 4/256
elseif self.level < 230 then return 32/256 elseif self.level < 230 then return 32/256
elseif self.level < 233 then return 64/256 elseif self.level < 233 then return 64/256
elseif self.level < 236 then return 96/256 elseif self.level < 236 then return 96/256
elseif self.level < 239 then return 128/256 elseif self.level < 239 then return 128/256
elseif self.level < 243 then return 160/256 elseif self.level < 243 then return 160/256
elseif self.level < 247 then return 192/256 elseif self.level < 247 then return 192/256
elseif self.level < 251 then return 224/256 elseif self.level < 251 then return 224/256
elseif self.level < 300 then return 1 elseif self.level < 300 then return 1
elseif self.level < 330 then return 2 elseif self.level < 330 then return 2
elseif self.level < 360 then return 3 elseif self.level < 360 then return 3
elseif self.level < 400 then return 4 elseif self.level < 400 then return 4
elseif self.level < 420 then return 5 elseif self.level < 420 then return 5
elseif self.level < 450 then return 4 elseif self.level < 450 then return 4
elseif self.level < 500 then return 3 elseif self.level < 500 then return 3
else return 20 end else return 20 end
end end
@@ -144,7 +142,6 @@ function Marathon2020Game:advanceOneFrame()
if self.roll_frames < 0 then if self.roll_frames < 0 then
return false return false
elseif self.roll_frames > 4000 then elseif self.roll_frames > 4000 then
if self.grade >= 30 and self.section_cool_count >= 20 then self.grade = 31 end
self.completed = true self.completed = true
end end
elseif self.ready_frames == 0 then elseif self.ready_frames == 0 then
@@ -154,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 }
@@ -249,7 +246,6 @@ function Marathon2020Game:updateGrade(cleared_lines)
end end
function Marathon2020Game:getTotalGrade() function Marathon2020Game:getTotalGrade()
if self.grade + self.section_cool_count > 50 then return "GM" end
return self.grade + self.section_cool_count return self.grade + self.section_cool_count
end end
@@ -262,7 +258,7 @@ local function getSectionForLevel(level)
end end
function Marathon2020Game:getEndOfSectionForSection(section) function Marathon2020Game:getEndOfSectionForSection(section)
if self.torikan_passed[900] == false and section == 10 then return 999 if self.torikan_passed[900] == false and section == 10 then return 999
elseif self.torikan_passed[1900] == false and section == 20 then return 2000 elseif self.torikan_passed[1900] == false and section == 20 then return 2000
elseif section == 20 then return 2020 elseif section == 20 then return 2020
else return section * 100 end else return section * 100 end
@@ -288,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)
@@ -305,7 +301,7 @@ function Marathon2020Game:checkClear(level)
level >= 2020 level >= 2020
) then ) then
if self.torikan_passed[500] == false then self.level = 500 if self.torikan_passed[500] == false then self.level = 500
elseif self.torikan_passed[900] == false then self.level = 999 elseif self.torikan_passed[900] == false then self.level = 999
elseif self.torikan_passed[1000] == false then self.level = 1000 elseif self.torikan_passed[1000] == false then self.level = 1000
elseif self.torikan_passed[1500] == false then self.level = 1500 elseif self.torikan_passed[1500] == false then self.level = 1500
@@ -329,9 +325,8 @@ end
function Marathon2020Game:updateSectionTimes(old_level, new_level) function Marathon2020Game:updateSectionTimes(old_level, new_level)
function sectionCool(section) 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
if section < 10 then table.insert(self.section_status, "cool") end table.insert(self.section_status, "cool")
self.cool_timer = 300
end end
local section = getSectionForLevel(old_level) local section = getSectionForLevel(old_level)
@@ -394,6 +389,7 @@ Marathon2020Game.mRollOpacityFunction = function(age)
end end
function Marathon2020Game:qualifiesForMRoll() function Marathon2020Game:qualifiesForMRoll()
return false -- until I actually have grading working
--[[ --[[
GM-roll requirements GM-roll requirements
@@ -404,8 +400,6 @@ You qualify for the GM roll if you:
- in less than 13:30.00 total. - in less than 13:30.00 total.
]]-- ]]--
return self.level >= 2020 and self:getTotalGrade() == 50 and self.frames <= frameTime(13,30)
end end
function Marathon2020Game:drawGrid() function Marathon2020Game:drawGrid()
@@ -442,12 +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)
if (self.cool_timer > 0) then
love.graphics.printf("COOL!!", 64, 400, 160, "center")
self.cool_timer = self.cool_timer - 1
end
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,28 +12,21 @@ 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()
self.lock_drop = false self.lock_drop = false
self.enable_hard_drop = false self.enable_hard_drop = false
self.enable_hold = false self.enable_hold = false
self.next_queue_length = 1 self.next_queue_length = 1
end end
@@ -59,60 +52,60 @@ function MarathonA1Game:getLockDelay()
end end
local function getRankForScore(score) local function getRankForScore(score)
if score < 400 then return {rank = "9", next = 400} if score < 400 then return {rank = "9", next = 400}
elseif score < 800 then return {rank = "8", next = 800} elseif score < 800 then return {rank = "8", next = 800}
elseif score < 1400 then return {rank = "7", next = 1400} elseif score < 1400 then return {rank = "7", next = 1400}
elseif score < 2000 then return {rank = "6", next = 2000} elseif score < 2000 then return {rank = "6", next = 2000}
elseif score < 3500 then return {rank = "5", next = 3500} elseif score < 3500 then return {rank = "5", next = 3500}
elseif score < 5500 then return {rank = "4", next = 5500} elseif score < 5500 then return {rank = "4", next = 5500}
elseif score < 8000 then return {rank = "3", next = 8000} elseif score < 8000 then return {rank = "3", next = 8000}
elseif score < 12000 then return {rank = "2", next = 12000} elseif score < 12000 then return {rank = "2", next = 12000}
elseif score < 16000 then return {rank = "1", next = 16000} elseif score < 16000 then return {rank = "1", next = 16000}
elseif score < 22000 then return {rank = "S1", next = 22000} elseif score < 22000 then return {rank = "S1", next = 22000}
elseif score < 30000 then return {rank = "S2", next = 30000} elseif score < 30000 then return {rank = "S2", next = 30000}
elseif score < 40000 then return {rank = "S3", next = 40000} elseif score < 40000 then return {rank = "S3", next = 40000}
elseif score < 52000 then return {rank = "S4", next = 52000} elseif score < 52000 then return {rank = "S4", next = 52000}
elseif score < 66000 then return {rank = "S5", next = 66000} elseif score < 66000 then return {rank = "S5", next = 66000}
elseif score < 82000 then return {rank = "S6", next = 82000} elseif score < 82000 then return {rank = "S6", next = 82000}
elseif score < 100000 then return {rank = "S7", next = 100000} elseif score < 100000 then return {rank = "S7", next = 100000}
elseif score < 120000 then return {rank = "S8", next = 120000} elseif score < 120000 then return {rank = "S8", next = 120000}
else return {rank = "S9", next = "???"} else return {rank = "S9", next = "???"}
end end
end end
function MarathonA1Game:getGravity() function MarathonA1Game:getGravity()
local level = self.level local level = self.level
if (level < 30) then return 4/256 if (level < 30) then return 4/256
elseif (level < 35) then return 6/256 elseif (level < 35) then return 6/256
elseif (level < 40) then return 8/256 elseif (level < 40) then return 8/256
elseif (level < 50) then return 10/256 elseif (level < 50) then return 10/256
elseif (level < 60) then return 12/256 elseif (level < 60) then return 12/256
elseif (level < 70) then return 16/256 elseif (level < 70) then return 16/256
elseif (level < 80) then return 32/256 elseif (level < 80) then return 32/256
elseif (level < 90) then return 48/256 elseif (level < 90) then return 48/256
elseif (level < 100) then return 64/256 elseif (level < 100) then return 64/256
elseif (level < 120) then return 80/256 elseif (level < 120) then return 80/256
elseif (level < 140) then return 96/256 elseif (level < 140) then return 96/256
elseif (level < 160) then return 112/256 elseif (level < 160) then return 112/256
elseif (level < 170) then return 128/256 elseif (level < 170) then return 128/256
elseif (level < 200) then return 144/256 elseif (level < 200) then return 144/256
elseif (level < 220) then return 4/256 elseif (level < 220) then return 4/256
elseif (level < 230) then return 32/256 elseif (level < 230) then return 32/256
elseif (level < 233) then return 64/256 elseif (level < 233) then return 64/256
elseif (level < 236) then return 96/256 elseif (level < 236) then return 96/256
elseif (level < 239) then return 128/256 elseif (level < 239) then return 128/256
elseif (level < 243) then return 160/256 elseif (level < 243) then return 160/256
elseif (level < 247) then return 192/256 elseif (level < 247) then return 192/256
elseif (level < 251) then return 224/256 elseif (level < 251) then return 224/256
elseif (level < 300) then return 1 elseif (level < 300) then return 1
elseif (level < 330) then return 2 elseif (level < 330) then return 2
elseif (level < 360) then return 3 elseif (level < 360) then return 3
elseif (level < 400) then return 4 elseif (level < 400) then return 4
elseif (level < 420) then return 5 elseif (level < 420) then return 5
elseif (level < 450) then return 4 elseif (level < 450) then return 4
elseif (level < 500) then return 3 elseif (level < 500) then return 3
else return 20 else return 20
end end
end end
function MarathonA1Game:advanceOneFrame() function MarathonA1Game:advanceOneFrame()
@@ -134,47 +127,45 @@ function MarathonA1Game:onPieceEnter()
end end
function MarathonA1Game:onLineClear(cleared_row_count) 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 new_level == 999 then if new_level == 999 then
self.level = 999
self.clear = true self.clear = true
else
self.level = new_level
end end
self.level = new_level
end end
end end
function MarathonA1Game:updateScore(level, drop_bonus, cleared_lines) function MarathonA1Game:updateScore(level, drop_bonus, cleared_lines)
if not self.clear then if self.clear then return end
if self.grid:checkForBravo(cleared_lines) then if cleared_lines > 0 then
self.bravo = 4 self.combo = self.combo + (cleared_lines - 1) * 2
self.bravos = self.bravos + 1 self.score = self.score + (
else self.bravo = 1 end (math.ceil((level + cleared_lines) / 4) + drop_bonus) *
if cleared_lines > 0 then cleared_lines * self.combo
self.combo = self.combo + (cleared_lines - 1) * 2 )
self.score = self.score + ( self.lines = self.lines + cleared_lines
(math.ceil((level + cleared_lines) / 4) + drop_bonus) * else
cleared_lines * self.combo * self.bravo
)
else
self.combo = 1
end
self.drop_bonus = 0 self.drop_bonus = 0
self.combo = 1
end end
end 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["level999"] = true self.gm_conditions["level900"] = true
end end
end end
end end
@@ -194,23 +185,17 @@ 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")
if self.gm_conditions["level300"] and self.gm_conditions["level500"] and self.gm_conditions["level999"] then if self.gm_conditions["level300"] and self.gm_conditions["level500"] and self.gm_conditions["level900"] then
love.graphics.printf("GM", 240, 140, 90, "left") love.graphics.printf("GM", 240, 140, 90, "left")
else else
love.graphics.printf(getRankForScore(self.score).rank, 240, 140, 90, "left") love.graphics.printf(getRankForScore(self.score).rank, 240, 140, 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

@@ -15,10 +15,10 @@ MarathonA2Game.tagline = "The points don't matter! Can you reach the invisible r
function MarathonA2Game:new() function MarathonA2Game:new()
MarathonA2Game.super:new() MarathonA2Game.super:new()
self.roll_frames = 0 self.roll_frames = 0
self.combo = 1 self.combo = 1
self.randomizer = History6RollsRandomizer() self.randomizer = History6RollsRandomizer()
self.grade = 0 self.grade = 0
self.grade_points = 0 self.grade_points = 0
@@ -27,88 +27,82 @@ 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 = { self.randomizer = History6RollsRandomizer()
"9", "8", "7", "6", "5", "4", "3", "2", "1",
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"GM"
}
self.lock_drop = false self.lock_drop = false
self.lock_hard_drop = false
self.enable_hold = false self.enable_hold = false
self.next_queue_length = 1 self.next_queue_length = 1
end end
function MarathonA2Game:getARE() function MarathonA2Game:getARE()
if self.level < 700 then return 27 if self.level < 700 then return 27
elseif self.level < 800 then return 18 elseif self.level < 800 then return 18
else return 14 end else return 14 end
end end
function MarathonA2Game:getLineARE() function MarathonA2Game:getLineARE()
if self.level < 600 then return 27 if self.level < 600 then return 27
elseif self.level < 700 then return 18 elseif self.level < 700 then return 18
elseif self.level < 800 then return 14 elseif self.level < 800 then return 14
else return 8 end else return 8 end
end end
function MarathonA2Game:getDasLimit() function MarathonA2Game:getDasLimit()
if self.level < 500 then return 15 if self.level < 500 then return 15
elseif self.level < 900 then return 9 elseif self.level < 900 then return 9
else return 7 end else return 7 end
end end
function MarathonA2Game:getLineClearDelay() function MarathonA2Game:getLineClearDelay()
if self.level < 500 then return 40 if self.level < 500 then return 40
elseif self.level < 600 then return 25 elseif self.level < 600 then return 25
elseif self.level < 700 then return 16 elseif self.level < 700 then return 16
elseif self.level < 800 then return 12 elseif self.level < 800 then return 12
else return 6 end else return 6 end
end end
function MarathonA2Game:getLockDelay() function MarathonA2Game:getLockDelay()
if self.level < 900 then return 30 if self.level < 900 then return 30
else return 17 end else return 17 end
end end
function MarathonA2Game:getGravity() function MarathonA2Game:getGravity()
if (self.level < 30) then return 4/256 if (self.level < 30) then return 4/256
elseif (self.level < 35) then return 6/256 elseif (self.level < 35) then return 6/256
elseif (self.level < 40) then return 8/256 elseif (self.level < 40) then return 8/256
elseif (self.level < 50) then return 10/256 elseif (self.level < 50) then return 10/256
elseif (self.level < 60) then return 12/256 elseif (self.level < 60) then return 12/256
elseif (self.level < 70) then return 16/256 elseif (self.level < 70) then return 16/256
elseif (self.level < 80) then return 32/256 elseif (self.level < 80) then return 32/256
elseif (self.level < 90) then return 48/256 elseif (self.level < 90) then return 48/256
elseif (self.level < 100) then return 64/256 elseif (self.level < 100) then return 64/256
elseif (self.level < 120) then return 80/256 elseif (self.level < 120) then return 80/256
elseif (self.level < 140) then return 96/256 elseif (self.level < 140) then return 96/256
elseif (self.level < 160) then return 112/256 elseif (self.level < 160) then return 112/256
elseif (self.level < 170) then return 128/256 elseif (self.level < 170) then return 128/256
elseif (self.level < 200) then return 144/256 elseif (self.level < 200) then return 144/256
elseif (self.level < 220) then return 4/256 elseif (self.level < 220) then return 4/256
elseif (self.level < 230) then return 32/256 elseif (self.level < 230) then return 32/256
elseif (self.level < 233) then return 64/256 elseif (self.level < 233) then return 64/256
elseif (self.level < 236) then return 96/256 elseif (self.level < 236) then return 96/256
elseif (self.level < 239) then return 128/256 elseif (self.level < 239) then return 128/256
elseif (self.level < 243) then return 160/256 elseif (self.level < 243) then return 160/256
elseif (self.level < 247) then return 192/256 elseif (self.level < 247) then return 192/256
elseif (self.level < 251) then return 224/256 elseif (self.level < 251) then return 224/256
elseif (self.level < 300) then return 1 elseif (self.level < 300) then return 1
elseif (self.level < 330) then return 2 elseif (self.level < 330) then return 2
elseif (self.level < 360) then return 3 elseif (self.level < 360) then return 3
elseif (self.level < 400) then return 4 elseif (self.level < 400) then return 4
elseif (self.level < 420) then return 5 elseif (self.level < 420) then return 5
elseif (self.level < 450) then return 4 elseif (self.level < 450) then return 4
elseif (self.level < 500) then return 3 elseif (self.level < 500) then return 3
else return 20 else return 20
end end
end end
function MarathonA2Game:advanceOneFrame() function MarathonA2Game:advanceOneFrame()
if self.clear then if self.clear then
self.roll_frames = self.roll_frames + 1 self.roll_frames = self.roll_frames + 1
if self.roll_frames < 0 then return false end
if self.roll_frames > 3694 then if self.roll_frames > 3694 then
self.completed = true self.completed = true
if self.grade == 32 then if self.grade == 32 then
@@ -127,33 +121,32 @@ function MarathonA2Game:onPieceEnter()
end end
end end
function MarathonA2Game:updateScore(level, drop_bonus, cleared_lines) function MarathonA2Game:onLineClear(cleared_row_count)
if not self.clear then self:updateSectionTimes(self.level, self.level + cleared_row_count)
self:updateGrade(cleared_lines) self.level = math.min(self.level + cleared_row_count, 999)
if self.grid:checkForBravo(cleared_lines) then self.bravo = 4 else self.bravo = 1 end if self.level == 999 and not self.clear then
if cleared_lines > 0 then self.clear = true
self.combo = self.combo + (cleared_lines - 1) * 2 if self:qualifiesForMRoll() then
self.score = self.score + ( self.grade = 32
(math.ceil((level + cleared_lines) / 4) + drop_bonus) * end
cleared_lines * self.combo * self.bravo self.grid:clear()
) self.roll_frames = -150
else end
self.combo = 1
end
self.drop_bonus = 0
else self.lines = self.lines + cleared_lines end
end end
function MarathonA2Game:onLineClear(cleared_row_count) function MarathonA2Game:updateScore(level, drop_bonus, cleared_lines)
self.level = math.min(self.level + cleared_row_count, 999) self:updateGrade(cleared_lines)
if self.level == 999 and not self.clear then if cleared_lines > 0 then
self.clear = true self.score = self.score + (
self.grid:clear() (math.ceil((level + cleared_lines) / 4) + drop_bonus) *
if self:qualifiesForMRoll() then self.grade = 32 end cleared_lines * (cleared_lines * 2 - 1) * (self.combo * 2 - 1)
self.roll_frames = -150 )
self.lines = self.lines + cleared_lines
self.combo = self.combo + cleared_lines - 1
else
self.drop_bonus = 0
self.combo = 1
end end
self.lock_drop = self.level >= 900
self.lock_hard_drop = self.level >= 900
end end
function MarathonA2Game:updateSectionTimes(old_level, new_level) function MarathonA2Game:updateSectionTimes(old_level, new_level)
@@ -253,7 +246,7 @@ function MarathonA2Game:updateGrade(cleared_lines)
end end
end end
local tetris_requirements = { [0] = 2, 2, 2, 2, 2, 1, 1, 1, 1, 0 } local tetris_requirements = { [0] = 2, 2, 2, 2, 2, 1, 1, 1, 1, 1 }
function MarathonA2Game:qualifiesForMRoll() function MarathonA2Game:qualifiesForMRoll()
if not self.clear then return false end if not self.clear then return false end
@@ -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 < 31 or self.frames > frameTime(8,45) then if self.grade < 17 or self.frames > sp(8,45) then
return false return false
end end
return true return true
@@ -310,12 +303,12 @@ MarathonA2Game.mRollOpacityFunction = function(age)
else return 1 - age / 4 end else return 1 - age / 4 end
end end
function MarathonA2Game:drawGrid() function MarathonA2Game:drawGrid(ruleset)
if self.clear and not (self.completed or self.game_over) then if self.clear and not (self.completed or self.game_over) then
if self:qualifiesForMRoll() then if self:qualifiesForMRoll() then
self.grid:drawInvisible(self.mRollOpacityFunction, nil, false) self.grid:drawInvisible(self.mRollOpacityFunction)
else else
self.grid:drawInvisible(self.rollOpacityFunction, nil, false) self.grid:drawInvisible(self.rollOpacityFunction)
end end
else else
self.grid:draw() self.grid:draw()
@@ -332,35 +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)
if self.clear then
if self:qualifiesForMRoll() then
if self.lines >= 32 and self.roll_frames > 3694 then love.graphics.setColor(1, 0.5, 0, 1)
else love.graphics.setColor(0, 1, 0, 1) end
else
if self.roll_frames > 3694 then love.graphics.setColor(1, 0.5, 0, 1)
else love.graphics.setColor(0, 1, 0, 1) end
end
end
love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left") love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left")
love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(self.score, 240, 220, 90, "left") love.graphics.printf(self.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

@@ -15,11 +15,11 @@ MarathonA3Game.tagline = "The game gets faster way more quickly! Can you get all
function MarathonA3Game:new() function MarathonA3Game:new()
MarathonA3Game.super:new() MarathonA3Game.super:new()
self.speed_level = 0 self.speed_level = 0
self.roll_frames = 0 self.roll_frames = 0
self.combo = 1 self.combo = 1
self.grade = 0 self.grade = 0
self.grade_points = 0 self.grade_points = 0
self.roll_points = 0 self.roll_points = 0
@@ -27,101 +27,89 @@ function MarathonA3Game:new()
self.section_cool_grade = 0 self.section_cool_grade = 0
self.section_status = { [0] = "none" } self.section_status = { [0] = "none" }
self.section_start_time = 0 self.section_start_time = 0
self.secondary_section_times = { [0] = 0 } self.section_70_times = { [0] = 0 }
self.section_times = { [0] = 0 } self.section_times = { [0] = 0 }
self.section_cool = false
self.randomizer = History6RollsRandomizer() self.randomizer = History6RollsRandomizer()
self.SGnames = { self.lock_drop = true
"9", "8", "7", "6", "5", "4", "3", "2", "1", self.lock_hard_drop = true
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"GM"
}
self.lock_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
self.torikan_passed = false
end end
function MarathonA3Game:getARE() function MarathonA3Game:getARE()
if self.speed_level < 700 then return 27 if self.speed_level < 700 then return 27
elseif self.speed_level < 800 then return 18 elseif self.speed_level < 800 then return 18
elseif self.speed_level < 1000 then return 14 elseif self.speed_level < 1000 then return 14
elseif self.speed_level < 1100 then return 8 elseif self.speed_level < 1100 then return 8
elseif self.speed_level < 1200 then return 7 elseif self.speed_level < 1200 then return 7
else return 6 end else return 6 end
end end
function MarathonA3Game:getLineARE() function MarathonA3Game:getLineARE()
if self.speed_level < 600 then return 27 if self.speed_level < 600 then return 27
elseif self.speed_level < 700 then return 18 elseif self.speed_level < 700 then return 18
elseif self.speed_level < 800 then return 14 elseif self.speed_level < 800 then return 14
elseif self.speed_level < 1100 then return 8 elseif self.speed_level < 1100 then return 8
elseif self.speed_level < 1200 then return 7 elseif self.speed_level < 1200 then return 7
else return 6 end else return 6 end
end end
function MarathonA3Game:getDasLimit() function MarathonA3Game:getDasLimit()
if self.speed_level < 500 then return 15 if self.speed_level < 500 then return 15
elseif self.speed_level < 900 then return 9 elseif self.speed_level < 900 then return 9
else return 7 end else return 7 end
end end
function MarathonA3Game:getLineClearDelay() function MarathonA3Game:getLineClearDelay()
if self.speed_level < 500 then return 40 if self.speed_level < 500 then return 40
elseif self.speed_level < 600 then return 25 elseif self.speed_level < 600 then return 25
elseif self.speed_level < 700 then return 16 elseif self.speed_level < 700 then return 16
elseif self.speed_level < 800 then return 12 elseif self.speed_level < 800 then return 12
elseif self.speed_level < 1100 then return 6 elseif self.speed_level < 1100 then return 6
elseif self.speed_level < 1200 then return 5 elseif self.speed_level < 1200 then return 5
else return 4 end else return 4 end
end end
function MarathonA3Game:getLockDelay() function MarathonA3Game:getLockDelay()
if self.speed_level < 900 then return 30 if self.speed_level < 900 then return 30
elseif self.speed_level < 1100 then return 17 elseif self.speed_level < 1100 then return 17
else return 15 end else return 15 end
end end
function MarathonA3Game:getGravity() function MarathonA3Game:getGravity()
if (self.speed_level < 30) then return 4/256 if (self.speed_level < 30) then return 4/256
elseif (self.speed_level < 35) then return 6/256 elseif (self.speed_level < 35) then return 6/256
elseif (self.speed_level < 40) then return 8/256 elseif (self.speed_level < 40) then return 8/256
elseif (self.speed_level < 50) then return 10/256 elseif (self.speed_level < 50) then return 10/256
elseif (self.speed_level < 60) then return 12/256 elseif (self.speed_level < 60) then return 12/256
elseif (self.speed_level < 70) then return 16/256 elseif (self.speed_level < 70) then return 16/256
elseif (self.speed_level < 80) then return 32/256 elseif (self.speed_level < 80) then return 32/256
elseif (self.speed_level < 90) then return 48/256 elseif (self.speed_level < 90) then return 48/256
elseif (self.speed_level < 100) then return 64/256 elseif (self.speed_level < 100) then return 64/256
elseif (self.speed_level < 120) then return 80/256 elseif (self.speed_level < 120) then return 80/256
elseif (self.speed_level < 140) then return 96/256 elseif (self.speed_level < 140) then return 96/256
elseif (self.speed_level < 160) then return 112/256 elseif (self.speed_level < 160) then return 112/256
elseif (self.speed_level < 170) then return 128/256 elseif (self.speed_level < 170) then return 128/256
elseif (self.speed_level < 200) then return 144/256 elseif (self.speed_level < 200) then return 144/256
elseif (self.speed_level < 220) then return 4/256 elseif (self.speed_level < 220) then return 4/256
elseif (self.speed_level < 230) then return 32/256 elseif (self.speed_level < 230) then return 32/256
elseif (self.speed_level < 233) then return 64/256 elseif (self.speed_level < 233) then return 64/256
elseif (self.speed_level < 236) then return 96/256 elseif (self.speed_level < 236) then return 96/256
elseif (self.speed_level < 239) then return 128/256 elseif (self.speed_level < 239) then return 128/256
elseif (self.speed_level < 243) then return 160/256 elseif (self.speed_level < 243) then return 160/256
elseif (self.speed_level < 247) then return 192/256 elseif (self.speed_level < 247) then return 192/256
elseif (self.speed_level < 251) then return 224/256 elseif (self.speed_level < 251) then return 224/256
elseif (self.speed_level < 300) then return 1 elseif (self.speed_level < 300) then return 1
elseif (self.speed_level < 330) then return 2 elseif (self.speed_level < 330) then return 2
elseif (self.speed_level < 360) then return 3 elseif (self.speed_level < 360) then return 3
elseif (self.speed_level < 400) then return 4 elseif (self.speed_level < 400) then return 4
elseif (self.speed_level < 420) then return 5 elseif (self.speed_level < 420) then return 5
elseif (self.speed_level < 450) then return 4 elseif (self.speed_level < 450) then return 4
elseif (self.speed_level < 500) then return 3 elseif (self.speed_level < 500) then return 3
else return 20 else return 20
end end
end end
function MarathonA3Game:advanceOneFrame() function MarathonA3Game:advanceOneFrame()
@@ -149,41 +137,36 @@ end
function MarathonA3Game:onPieceEnter() function MarathonA3Game:onPieceEnter()
if (self.level % 100 ~= 99) and self.level ~= 998 and self.frames ~= 0 then if (self.level % 100 ~= 99) and self.level ~= 998 and self.frames ~= 0 then
self:updateSectionTimes(self.level, self.level + 1) self:updateSectionTimes(self.level, self.level + 1)
self.level = self.level + 1 self.level = self.level + 1
self.speed_level = self.speed_level + 1 self.speed_level = self.speed_level + 1
self.torikan_passed = self.level >= 500 and true or false end
end
end end
local cleared_row_levels = {1, 2, 4, 6} local cleared_row_levels = {1, 2, 4, 6}
function MarathonA3Game:onLineClear(cleared_row_count) function MarathonA3Game:onLineClear(cleared_row_count)
local advanced_levels = cleared_row_levels[cleared_row_count] local advanced_levels = cleared_row_levels[cleared_row_count]
self:updateSectionTimes(self.level, self.level + advanced_levels) self:updateSectionTimes(self.level, self.level + advanced_levels)
if not self.clear then if not self.clear then
self.level = math.min(self.level + advanced_levels, 999) self.level = math.min(self.level + advanced_levels, 999)
self.speed_level = self.speed_level + advanced_levels end
end self.speed_level = self.speed_level + advanced_levels
if self.level == 999 and not self.clear then if self.level == 999 and not self.clear then
self.clear = true self.clear = true
self.grid:clear() self.grid:clear()
self.roll_frames = -150 self.roll_frames = -150
end end
if not self.torikan_passed and self.level >= 500 and self.frames >= 25200 then
self.level = 500
self.game_over = true
end
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)
@@ -194,55 +177,44 @@ function MarathonA3Game:updateSectionTimes(old_level, new_level)
-- 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)
if new_level < 999 then self.section_start_time = self.frames end self.section_start_time = self.frames
self.speed_level = self.section_cool and self.speed_level + 100 or self.speed_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!!" elseif section <= 9 and self.section_status[section - 1] == "cool" and
self.coolregret_timer = 300 self.section_70_times[section] < self.section_70_times[section - 1] + 120 then
elseif self.section_cool 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
table.insert(self.section_status, "cool")
elseif self.section_status[section - 1] == "cool" then
table.insert(self.section_status, "none")
elseif section <= 9 and self.section_70_times[section] < cool_cutoffs[section] then
self.section_cool_grade = self.section_cool_grade + 1
self.speed_level = self.speed_level + 100
table.insert(self.section_status, "cool") table.insert(self.section_status, "cool")
else else
table.insert(self.section_status, "none") table.insert(self.section_status, "none")
end end
elseif section <= 9 and old_level % 100 < 70 and new_level % 100 >= 70 then
self.section_cool = false
elseif old_level % 100 < 70 and new_level % 100 >= 70 then
-- record section 70 time -- record section 70 time
section_70_time = self.frames - self.section_start_time section_70_time = self.frames - self.section_start_time
table.insert(self.secondary_section_times, section_70_time) table.insert(self.section_70_times, section_70_time)
if section <= 9 and self.section_status[section - 1] == "cool" and
self.secondary_section_times[section] < self.secondary_section_times[section - 1] + 120 then
self.section_cool = true
self.coolregret_message = "COOL!!"
self.coolregret_timer = 300
elseif self.section_status[section - 1] == "cool" then self.section_cool = false
elseif section <= 9 and self.secondary_section_times[section] < cool_cutoffs[section] then
self.section_cool = true
self.coolregret_message = "COOL!!"
self.coolregret_timer = 300
end
end end
end end
function MarathonA3Game:updateScore(level, drop_bonus, cleared_lines) function MarathonA3Game:updateScore(level, drop_bonus, cleared_lines)
self:updateGrade(cleared_lines) self:updateGrade(cleared_lines)
if not self.clear then if cleared_lines > 0 then
if cleared_lines > 0 then self.score = self.score + (
self.combo = self.combo + (cleared_lines - 1) * 2 (math.ceil((level + cleared_lines) / 4) + drop_bonus) *
self.score = self.score + ( cleared_lines * (cleared_lines * 2 - 1) * (self.combo * 2 - 1)
(math.ceil((level + cleared_lines) / 4) + drop_bonus) * )
cleared_lines * self.combo self.lines = self.lines + cleared_lines
) self.combo = self.combo + cleared_lines - 1
else else
self.combo = 1
end
self.drop_bonus = 0 self.drop_bonus = 0
self.combo = 1
end end
end end
@@ -362,8 +334,6 @@ function MarathonA3Game:getLetterGrade()
return "M" .. tostring(grade - 17) return "M" .. tostring(grade - 17)
elseif grade < 32 then elseif grade < 32 then
return master_grades[grade - 26] return master_grades[grade - 26]
elseif grade >= 32 and self.roll_frames < 3238 then
return "MM"
else else
return "GM" return "GM"
end end
@@ -395,16 +365,6 @@ MarathonA3Game.mRollOpacityFunction = function(age)
else return 1 - age / 4 end else return 1 - age / 4 end
end end
function MarathonA3Game:sectionColourFunction(section)
if self.section_status[section] == "cool" then
return { 0, 1, 0, 1 }
elseif self.section_status[section] == "regret" then
return { 1, 0, 0, 1 }
else
return { 1, 1, 1, 1 }
end
end
function MarathonA3Game:drawScoringInfo() function MarathonA3Game:drawScoringInfo()
love.graphics.setColor(1, 1, 1, 1) love.graphics.setColor(1, 1, 1, 1)
@@ -412,21 +372,15 @@ 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
self:drawSectionTimesWithSecondary(current_section)
--[[
section_x = 530 section_x = 530
section_70_x = 440 section_70_x = 440
@@ -437,38 +391,26 @@ function MarathonA3Game:drawScoringInfo()
end end
end end
for section, time in pairs(self.secondary_section_times) do for section, time in pairs(self.section_70_times) do
if section > 0 then if section > 0 then
love.graphics.printf(formatTime(time), section_70_x, 40 + 20 * section, 90, "left") love.graphics.printf(formatTime(time), section_70_x, 40 + 20 * section, 90, "left")
end end
end end
local current_x local current_x
if table.getn(self.section_times) < table.getn(self.secondary_section_times) then if table.getn(self.section_times) < table.getn(self.section_70_times) then
current_x = section_x current_x = section_x
else else
current_x = section_70_x current_x = section_70_x
end end
if not self.clear then love.graphics.printf(formatTime(self.frames - self.section_start_time), current_x, 40 + 20 * current_section, 90, "left") end 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")
if self.roll_frames > 3238 then love.graphics.setColor(1, 0.5, 0, 1)
elseif self.level >= 999 and self.clear then love.graphics.setColor(0, 1, 0, 1) end
love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left") love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left")
love.graphics.setColor(1, 1, 1, 1)
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")
@@ -488,7 +430,7 @@ function MarathonA3Game:getSectionEndLevel()
end end
function MarathonA3Game:getBackground() function MarathonA3Game:getBackground()
return math.floor(self.speed_level / 100) return math.floor(self.level / 100)
end end
return MarathonA3Game return MarathonA3Game

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,176 +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.lock_hard_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.grid:clear()
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")
if self.lines < 150 then love.graphics.printf("TIME LEFT", 240, 250, 80, "left") end
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
if self.lines < 150 then love.graphics.printf(formatTime(time_left), 240, 270, 160, "left") end
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

@@ -0,0 +1,185 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local Randomizer = require 'tetris.randomizers.randomizer'
local MarathonC89Game = GameMode:extend()
MarathonC89Game.name = "Marathon C89"
MarathonC89Game.hash = "MarathonC89"
MarathonC89Game.tagline = "Can you play fast enough to reach the killscreen?"
function MarathonC89Game:new()
MarathonC89Game.super:new()
self.randomizer = Randomizer()
self.ready_frames = 1
self.waiting_frames = 72
self.start_level = 12
self.level = 12
self.lock_drop = true
self.enable_hard_drop = false
self.enable_hold = false
self.next_queue_length = 1
self.additive_gravity = false
end
function MarathonC89Game:getDropSpeed() return 1/2 end
function MarathonC89Game:getDasLimit() return 16 end
function MarathonC89Game:getARR() return 6 end
function MarathonC89Game:getARE() return 6 end
function MarathonC89Game:getLineARE() return 6 end
function MarathonC89Game:getLineClearDelay() return 30 end
function MarathonC89Game:getLockDelay() return 0 end
function MarathonC89Game:chargeDAS(inputs)
if inputs[self.das.direction] == true and
self.prev_inputs[self.das.direction] == true and
not inputs["down"] and
self.piece ~= nil
then
local das_frames = self.das.frames + 1
if das_frames >= self:getDasLimit() then
if self.das.direction == "left" then
self.move = (self:getARR() == 0 and "speed" or "") .. "left"
self.das.frames = self:getDasLimit() - self:getARR()
elseif self.das.direction == "right" then
self.move = (self:getARR() == 0 and "speed" or "") .. "right"
self.das.frames = self:getDasLimit() - self:getARR()
end
else
self.move = "none"
self.das.frames = das_frames
end
elseif inputs["right"] == true then
self.das.direction = "right"
if not inputs["down"] and self.piece ~= nil then
self.move = "right"
self.das.frames = 0
else
self.move = "none"
end
elseif inputs["left"] == true then
self.das.direction = "left"
if not inputs["down"] and self.piece ~= nil then
self.move = "left"
self.das.frames = 0
else
self.move = "none"
end
else
self.move = "none"
end
if self.das.direction == "left" and self.piece ~= nil and self.piece:isMoveBlocked(self.grid, {x=-1, y=0}) or
self.das.direction == "right" and self.piece ~= nil and self.piece:isMoveBlocked(self.grid, {x=1, y=0})
then
self.das.frames = self:getDasLimit()
end
if inputs["down"] == false and self.prev_inputs["down"] == true then
self.drop_bonus = 0
end
end
local gravity_table = {
[0] =
1366/65536, 1525/65536, 1725/65536, 1986/65536, 2341/65536,
2850/65536, 3641/65536, 5042/65536, 8192/65536, 10923/65536,
13108/65536, 13108/65536, 13108/65536, 16384/65536, 16384/65536,
16384/65536, 21846/65536, 21846/65536, 21846/65536
}
function MarathonC89Game:getGravity()
if self.waiting_frames > 0 then return 0 end
if self.level >= 29 then return 1
elseif self.level >= 19 then return 1/2
else return gravity_table[self.level] end
end
function MarathonC89Game:advanceOneFrame()
if self.waiting_frames > 0 then
self.waiting_frames = self.waiting_frames - 1
else
self.frames = self.frames + 1
end
return true
end
function MarathonC89Game:onPieceLock()
self.score = self.score + self.drop_bonus
self.drop_bonus = 0
end
local cleared_line_scores = { 40, 100, 300, 1200 }
function MarathonC89Game:getLevelForLines()
if self.start_level < 10 then
return math.max(self.start_level, math.floor(self.lines / 10))
elseif self.start_level < 16 then
return math.max(self.start_level, self.start_level + math.floor((self.lines - 100) / 10))
else
return math.max(self.start_level, math.floor((self.lines - 60) / 10))
end
end
function MarathonC89Game:updateScore(level, drop_bonus, cleared_lines)
if cleared_lines > 0 then
self.score = self.score + cleared_line_scores[cleared_lines] * (self.level + 1)
self.lines = self.lines + cleared_lines
self.level = self:getLevelForLines()
else
self.drop_bonus = 0
self.combo = 1
end
end
function MarathonC89Game:drawGrid()
self.grid:draw()
if self.piece ~= nil and self.level < 100 then
self:drawGhostPiece(ruleset)
end
end
function MarathonC89Game:drawScoringInfo()
MarathonC89Game.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("LINES", 240, 120, 40, "left")
love.graphics.printf("SCORE", 240, 200, 40, "left")
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.lines, 240, 140, 90, "left")
love.graphics.printf(self.score, 240, 220, 90, "left")
love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")
end
function MarathonC89Game:getBackground()
return math.min(self.level, 19)
end
function MarathonC89Game:getHighscoreData()
return {
score = self.score,
level = self.level,
}
end
return MarathonC89Game

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

Some files were not shown because too many files have changed in this diff Show More