Compare commits

..

81 Commits

Author SHA1 Message Date
Ishaan Bhardwaj
788aa11470 Bump version to v0.3.3.2 2023-07-07 18:16:34 -04:00
Ishaan Bhardwaj
a303e82b90 Merge branch 'master' of https://github.com/millabasset/cambridge 2023-07-07 18:14:20 -04:00
Ishaan Bhardwaj
b06d03c4e6 Fix bravo detection 2023-07-07 18:14:17 -04:00
Ishaan Bhardwaj
a6b8abff6d Merge pull request #69 from Kirby703/patch-11
hotfix cool logic
2023-07-07 17:19:24 -04:00
Kirby703
bdc317c3c5 hotfix cool logic 2023-07-07 17:10:24 -04:00
Ishaan Bhardwaj
71c9147a2c Merge pull request #68 from Kirby703/patch-10
re-add 180s and IRS wallkicks after inheritance change
2023-07-04 02:38:52 -04:00
Kirby703
79d706a415 re-add 180s and IRS wallkicks after inheritance change
fixes results of 323c457809
2023-07-04 02:27:04 -04:00
Ishaan Bhardwaj
5fa144f146 Fix Marathon 2020 section cool highlighting 2023-07-02 19:47:39 -04:00
Ishaan Bhardwaj
244e67074d Bump version to v0.3.3.1 2023-07-02 19:13:13 -04:00
Ishaan Bhardwaj
4b456cf49c Removed xcf fonts 2023-07-02 18:20:56 -04:00
Ishaan Bhardwaj
9a67a6ce03 8x11 font by MattMayuga with more characters 2023-07-02 18:07:40 -04:00
Ishaan Bhardwaj
df19129228 Update main.lua 2023-07-01 23:30:36 -04:00
--global
80de771d2a Elaborate on TARGET_FPS 2023-07-01 23:27:10 -04:00
--global
7c32273971 Alias Lua random functions to Love2D's 2023-07-01 23:15:14 -04:00
--global
9d5dbb4674 Bump version to v0.3.3 2023-07-01 22:34:34 -04:00
Ishaan Bhardwaj
8d7ccae2bc README: fix Cambridge logo link 2023-07-01 22:25:50 -04:00
Ishaan Bhardwaj
1e06a1ce8a Merge pull request #67 from MillaBasset/features/backgrounds
Slight revamp on BG image handling
2023-07-01 22:22:50 -04:00
Ishaan Bhardwaj
d24fff5bdc Merge pull request #65 from nightmareci/master
Create unified batch file for running the game from the source directory on Windows and update README.md
2023-07-01 22:22:38 -04:00
Ishaan Bhardwaj
e34005093c Merge pull request #64 from hebo-MAI/master
fix the bug hanging up when starting M-roll
2023-07-01 22:22:04 -04:00
Ishaan Bhardwaj
3dc8b1214b Merge pull request #63 from Kirby703/patch-9
fix line clear delay
2023-07-01 22:21:45 -04:00
Ishaan Bhardwaj
5d2da1b4fb Merge pull request #62 from Tetro48/replay-qol
New replay file naming and fast replay saving.
2023-07-01 22:21:30 -04:00
Ishaan Bhardwaj
f786bda9dd Merge pull request #59 from Tetro48/fixes
Replay tunings fix
2023-07-01 22:21:10 -04:00
Ishaan Bhardwaj
3f789210a6 Merge pull request #56 from Kirby703/patch-8
added cool+regret colors to splits
2023-07-01 22:20:52 -04:00
Ishaan Bhardwaj
a7e7ac43a6 Merge pull request #55 from Kirby703/patch-7
fixes a3 regret grading
2023-07-01 22:20:43 -04:00
Ishaan Bhardwaj
5dc72037ec Merge pull request #54 from Kirby703/patch-6
fixed 2s rule for cools
2023-07-01 22:20:30 -04:00
Oshisaure
e5cb69df43 Slight revamp on BG image handling
- The game can now load more than 20 backgrounds by putting them in /res/backgrounds in the save directory
- If a gamemode tries to set its background to an ID higher than the max it will be clamped down to the last background loaded
2023-07-01 01:56:51 +01:00
Oshisaure
3e68af6a5b Merge pull request #66 from MillaBasset/features/pausing
Added counter for amount of pauses used
2023-06-24 20:35:17 +01:00
Oshisaure
8e6a760fe7 Added counter for amount of pauses used
* Pause count is saved in replays and shown when viewing replay
* Old replays display ?? pauses
* Removed suspend on lose focus
2023-06-24 15:46:38 +01:00
Joe Zeng
a4b7a41a15 Changed the Phantom Mania non-N requirement back to automatic GM at 999.
(Only N should have the 31-tetris rule.)
2023-04-11 12:19:38 -04:00
nightmareci
aa9e03506b Create unified batch file for running the game from the source directory on Windows and update README.md 2023-02-13 09:35:28 -08:00
hebo-MAI
40ac08c7e5 fix the bug hanging up when starting M-roll 2023-01-09 00:12:00 +09:00
Joe Zeng
323c457809 Reorganized the ruleset names and added ARS-X.
Also, the ruleset inheritance was a little wonky, so I changed that too.
In particular, I made SRS-X always spawn in-frame since that was always
supposed to be how it worked.
2022-10-24 20:09:08 -04:00
Joe Zeng
decc1f563f Changed cool/regret cutoffs.
They're all the same past 500 now - I've been meaning to tweak that for quite some time now.
2022-09-28 00:18:44 -04:00
Kirby703
63823ed4b1 fix line clear delay 2022-06-14 00:28:27 -04:00
Tetro48
d7c83b0bc7 New replay file naming and *fast replay saving.
*fast because lower CPU and IO use. No longer O(n²).
2022-05-16 19:53:31 +07:00
nightmareci
e5892c0fae Rename shell scripts and implement better frame timing 2022-04-28 11:47:31 -07:00
--global
23a8c400ba Revert "made the experience feel closer to arcade stackers"
Happy April Fools!
This reverts commit bfbba75f17.
2022-04-01 18:43:35 -04:00
--global
bfbba75f17 made the experience feel closer to arcade stackers 2022-03-31 23:27:22 -04:00
--global
27e699841e Fixed a graphical issue in Survival A2 when getting torikanned 2022-03-31 23:00:39 -04:00
--global
fac8c6584e Added batch scripts to start the game on Windows 2022-03-10 22:36:21 -05:00
Tetro48
d868e8b803 Replay tunings fix 2022-03-09 10:05:06 +07:00
Oshisaure
9e447d51a7 Merge pull request #57 from jjdelvalle/master
Include complete path when printing screenshot info
2022-03-03 19:29:48 +00:00
JDV
4dfa234bc3 Include complete path when printing screenshot info 2022-03-03 20:27:41 +01:00
Kirby703
47863175a3 added cool+regret colors to splits 2022-02-10 01:09:21 -05:00
Kirby703
8730261a78 fixes a3 regret grading
getting a cool and a regret in the same split displays as yellow
2022-02-10 00:40:25 -05:00
Kirby703
703ce66c42 fix 2s rule for cools 2022-02-09 23:48:09 -05:00
Ishaan Bhardwaj
92d67968f5 Tiny UI feature added to the title screen.
If you load a custom block skin, the title screen will use your skin to draw the logo.
2022-02-07 20:39:26 -05:00
Ishaan Bhardwaj
d68bd13d2a LOVE 11.3 notice
Reminder that Windows is unaffected by this, because Windows releases come bundled with the correct version, and the bleeding edge source includes the correct version of LOVE for Windows
2022-01-28 22:22:05 -05:00
Ishaan Bhardwaj
a84335646d Fix two bugs with Marathon A3 grading
S4 now correctly has 3 internal grades instead of 4
You can get a green-line GM
2022-01-28 20:51:25 -05:00
Ishaan Bhardwaj
d46973f12d Fixed color scheme setting not applying to active piece 2021-12-18 21:30:19 -05:00
Ishaan Bhardwaj
d4360b3662 Fixed the replay system's interaction with secret inputs 2021-12-09 23:00:20 -05:00
Ishaan Bhardwaj
06225bd35a Fixed an issue where replays played in the menu could save a duplicate copy 2021-12-09 22:21:48 -05:00
Ishaan Bhardwaj
e68238cbce Fixed disabling saving replays 2021-12-09 22:00:13 -05:00
Ishaan Bhardwaj
83e197b5d6 Slight RPC change to the selection menus 2021-12-09 21:51:41 -05:00
Ishaan Bhardwaj
1c0b73987d Rearrange sliders on the game settings menu 2021-12-09 21:48:39 -05:00
Ishaan Bhardwaj
afe6a43dab Rearrange game settings, add toggle for replay saving 2021-12-09 21:44:18 -05:00
Ishaan Bhardwaj
47a5a53e23 Fixed sorting replays in the replay select
In addition, minor change to the default text that shows up when the game is paused
2021-12-09 18:18:23 -05:00
Ishaan Bhardwaj
b9ae08051a Fixed BGM not resetting after a replay 2021-12-08 21:56:31 -05:00
Ishaan Bhardwaj
d7f4aa2007 Reverted a mode select change 2021-12-08 21:56:17 -05:00
Ishaan Bhardwaj
ca85107063 Replace replay select title graphic with text 2021-12-08 21:37:34 -05:00
Ishaan Bhardwaj
fdcec19d56 Bump version to v0.3.1 + re-add snow easter egg 2021-12-08 21:30:09 -05:00
Ishaan Bhardwaj
89c7205347 Replay system v3 + love.math.random migration 2021-12-08 21:23:00 -05:00
Ishaan Bhardwaj
9b41e56135 Replay system v2 2021-12-08 20:19:46 -05:00
710f658540 Merge pull request #48 from BoatsandJoes/replays
Added replays
2021-12-07 22:39:08 -05:00
BoatsandJoes
332e3869de Replay menu no longer crashes if level or timer is nil. 2021-12-06 22:38:07 -06:00
BoatsandJoes
febd1de0ef Replays are now fully functional. 2021-12-05 22:17:44 -06:00
BoatsandJoes
81ab7cd4de Replays now replay inputs properly, and replay list has fast scroll. 2021-12-05 21:16:13 -06:00
BoatsandJoes
a5750e4959 Replays list is now sorted, and replays are smaller. 2021-12-05 15:41:51 -06:00
BoatsandJoes
59c7834c9a Fixed replay deserialization. 2021-12-05 00:18:19 -06:00
BoatsandJoes
71ada76a00 Started work on replay select menu. 2021-12-04 23:37:51 -06:00
BoatsandJoes
6c4551ebef Added replay saving. 2021-12-04 20:35:15 -06:00
9fc7e4b1eb Merge pull request #47 from BoatsandJoes/game-over-animation
Game over fadeout easing function now accelerates
2021-12-02 19:27:32 -05:00
BoatsandJoes
9e59c158b2 Line clear easing is now quadratic for all line clear delays. 2021-12-02 16:22:29 -06:00
BoatsandJoes
e464307625 Game over animation is longer, and reveals stack once it's over. 2021-12-02 15:49:27 -06:00
BoatsandJoes
888312c578 Game over fadeout easing function now accelerates. 2021-12-02 13:42:51 -06:00
a838294435 Merge pull request #46 from BoatsandJoes/line-clear-animation
Added new default line clear animations using easing functions.
2021-12-02 13:57:19 -05:00
BoatsandJoes
049806d9e2 Added a new default line clear animation using easing functions. 2021-12-02 10:59:45 -06:00
Ishaan Bhardwaj
c1693524d7 Credits change 2021-11-30 20:08:52 -05:00
Ishaan Bhardwaj
a063f10d33 Merge branch 'master' of https://github.com/MillaBasset/cambridge 2021-11-23 22:56:19 -05:00
Ishaan Bhardwaj
6e0b5e27c1 Rearranged the spawn SE actions 2021-11-23 22:56:11 -05:00
Joe Zeng
18e0e02c76 Removed a stupid exception to the mixing-tabs-and-spaces rule.
I apologize that it took me 2 years to think of this workaround.
2021-11-09 14:48:05 -05:00
54 changed files with 828 additions and 626 deletions

View File

@@ -57,13 +57,13 @@ Coding conventions
Use tabs to indent, spaces to align.
* Specifically, spaces should not appear at the beginning of a line, and tabs should not appear _except_ at the beginning of a line.
* The sole exception is in a multiline `if` statement; the initial `if` should have four spaces before it to align it with an `elseif` on the next line. For example:
* If you're aligning multiline if-statements, the initial "if", "elseif" or "else" should be flush left with the indentation level, with spaces padding the gap to the next word as necessary. For example:
```lua
---- 4 spaces
if self.level < 900 then return 12
elseif self.level < 1200 then return 8
else return 6 end
if self.level < 900 then return 12
elseif self.level < 1200 then return 8
else return 6
end
```
Comments at the end of lines of code must be one line long. Multi-line comments must appear in their own block.

View File

@@ -5,7 +5,7 @@ Cambridge
Welcome to Cambridge, the next open-source falling-block game engine!
The project is written and maintained exclusively by [Milla](https://github.com/MillaBasset), [joezeng](https://github.com/joezeng) and [Oshisaure](https://github.com/oshisaure)!
The project is written and maintained exclusively by [Milla](https://github.com/MillaBasset), [joezeng](https://github.com/joezeng) and [Oshisaure](https://github.com/oshisaure)!
The Discord server has been reopened! https://discord.gg/AADZUmgsph
@@ -16,23 +16,23 @@ Playing the game
### Windows
You do not need LÖVE on Windows, as it comes bundled with the program.
You do not need LÖVE on Windows, as it comes bundled with the program.
#### Stable release
To get the stable release, simply download either `cambridge-win32.zip` (32-bit) or `cambridge-windows.zip` (64-bit) in the [latest release](https://github.com/MillaBasset/cambridge/releases/latest).
To get the stable release, simply download either `cambridge-win32.zip` (32-bit) or `cambridge-windows.zip` (64-bit) in the [latest release](https://github.com/MillaBasset/cambridge/releases/latest).
All assets needed are bundled with the executable.
#### Bleeding edge
If you want the bleeding edge version, download [this](https://github.com/MillaBasset/cambridge/archive/master.zip).
If you want the bleeding edge version, download [this](https://github.com/MillaBasset/cambridge/archive/master.zip). Extract the ZIP to a folder of your choosing.
Extract the ZIP, open a Command Prompt at the folder you extracted Cambridge to, then run this command:
If you're on Windows, you can double-click `start.bat` to run the game. If that doesn't work, open a Command Prompt where you extracted Cambridge and run:
dist\windows\love.exe .
Alternatively, if you're on a 32-bit system, run this instead:
If that doesn't work, run this instead, still using Command Prompt where you extracted Cambridge:
dist\win32\love.exe .
@@ -40,17 +40,9 @@ Alternatively, if you're on a 32-bit system, run this instead:
Then, check the mod pack section at the bottom of this page.
#### Other branches/haileyjunk
The Cambridge repository has multiple public and private branches for working on and testing out major new features over the course of many commits.
If you're looking to test even newer features that aren't even in the master branch, the closest you're going to get is probably the haileyjunk branch, which you can download [here](https://github.com/MillaBasset/cambridge/archive/master.zip).
> WARNING: This branch can be very buggy and broken at times. Use with care.
### macOS, Linux
If you haven't already, install `love` with your favourite package manager (Homebrew on macOS, your system's default on Linux). **Make sure you're using LÖVE 11, because it won't work with earlier versions!**
If you haven't already, install `love` with your favourite package manager (Homebrew on macOS, your system's default on Linux). **Make sure you're using LÖVE 11.3, because it won't work with earlier or later versions!**
#### Downloading a release
@@ -110,10 +102,9 @@ Other Notable Games
- [Tetra Online](https://github.com/Juan-Cartes/Tetra-Offline) by Mine
- [Techmino](https://discord.gg/6Yuww44tq8) by MrZ
- [Example Block Game](https://github.com/oshisaure/example-block-game) by Oshisaure
- [TETR.IO](https://tetr.io) by osk
- [Master of Blocks](https://discord.gg/72FZ49mjWh) by Phoenix Flare
- [Spirit Drop](https://rayblastgames.com/spiritdrop.php) by RayRay26
- [Puzzle Trial](https://kagamine-rin.itch.io/puzzle-trial) by Rin
- [stackfuse](https://github.com/sinefuse/stackfuse) by sinefuse
![Cambridge Logo](https://cdn.discordapp.com/attachments/625496179433668635/763363717730664458/Icon_2.png)
![Cambridge Logo](https://cdn.discordapp.com/attachments/827186653772644452/1077674343544393820/Icon_2.png)

View File

@@ -5,8 +5,6 @@ bgm = {
pacer_test = love.audio.newSource("res/bgm/pacer_test.mp3", "stream"),
}
local frames = 0
local current_bgm = nil
local bgm_locked = false
local unfocused = false

View File

@@ -26,8 +26,18 @@ font_3x5_4 = love.graphics.newImageFont(
-4
)
font_8x11 = love.graphics.newImageFont(
"res/fonts/8x11_medium.png",
"0123456789:.",
-- this would be font_8x11 with the other one as 8x11_2
-- but that would break compatibility :(
font_8x11_small = love.graphics.newImageFont(
"res/fonts/8x11.png",
" 0123456789:;.,ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ..
"?!/\\^@$%<=>()*-+[]_&",
1
)
font_8x11 = love.graphics.newImageFont(
"res/fonts/8x11_medium.png",
" 0123456789:;.,ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ..
"?!/\\^@$%<=>()*-+[]_&",
1
)

View File

@@ -1,31 +1,19 @@
backgrounds = {
[0] = love.graphics.newImage("res/backgrounds/0.png"),
love.graphics.newImage("res/backgrounds/100.png"),
love.graphics.newImage("res/backgrounds/200.png"),
love.graphics.newImage("res/backgrounds/300.png"),
love.graphics.newImage("res/backgrounds/400.png"),
love.graphics.newImage("res/backgrounds/500.png"),
love.graphics.newImage("res/backgrounds/600.png"),
love.graphics.newImage("res/backgrounds/700.png"),
love.graphics.newImage("res/backgrounds/800.png"),
love.graphics.newImage("res/backgrounds/900.png"),
love.graphics.newImage("res/backgrounds/1000.png"),
love.graphics.newImage("res/backgrounds/1100.png"),
love.graphics.newImage("res/backgrounds/1200.png"),
love.graphics.newImage("res/backgrounds/1300.png"),
love.graphics.newImage("res/backgrounds/1400.png"),
love.graphics.newImage("res/backgrounds/1500.png"),
love.graphics.newImage("res/backgrounds/1600.png"),
love.graphics.newImage("res/backgrounds/1700.png"),
love.graphics.newImage("res/backgrounds/1800.png"),
love.graphics.newImage("res/backgrounds/1900.png"),
title = love.graphics.newImage("res/backgrounds/title.png"),
title_no_icon = love.graphics.newImage("res/backgrounds/title-no-icon.jpg"),
title_night = love.graphics.newImage("res/backgrounds/title-night.jpg"),
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"),
}
local i = 0
local bgpath = "res/backgrounds/%d.png"
while love.filesystem.getInfo(bgpath:format(i*100)) do
backgrounds[i] = love.graphics.newImage(bgpath:format(i*100))
i = i + 1
end
-- in order, the colors are:
-- red, orange, yellow, green, cyan, blue
-- magenta (or purple), white, black
@@ -118,8 +106,7 @@ misc_graphics = {
ready = love.graphics.newImage("res/img/ready.png"),
go = love.graphics.newImage("res/img/go.png"),
select_mode = love.graphics.newImage("res/img/select_mode.png"),
select_challenge = love.graphics.newImage("res/img/select_challenge_placeholder.png"),
strike = love.graphics.newImage("res/img/strike.png"),
santa = love.graphics.newImage("res/img/santa.png"),
icon = love.graphics.newImage("res/img/cambridge_transparent.png")
}
}

View File

@@ -1 +1 @@
version = "v0.3.1"
version = "v0.3.3.2"

View File

@@ -1,5 +1,4 @@
function love.load()
math.randomseed(os.time())
highscores = {}
love.graphics.setDefaultFilter("linear", "nearest")
require "load.rpc"
@@ -13,17 +12,22 @@ function love.load()
loadSave()
require "funcs"
require "scene"
--config["side_next"] = false
--config["reverse_rotate"] = true
--config["das_last_key"] = false
--config["fullscreen"] = false
love.window.setMode(love.graphics.getWidth(), love.graphics.getHeight(), {resizable = true});
-- used for screenshots
GLOBAL_CANVAS = love.graphics.newCanvas()
-- aliasing to prevent people using math.random by accident
math.random = love.math.random
math.randomseed = love.math.setRandomSeed
math.randomseed(os.time())
-- init config
initConfig()
@@ -35,6 +39,8 @@ function love.load()
end
function initModules()
-- replays are not loaded here, but they are cleared
replays = {}
game_modes = {}
mode_list = love.filesystem.getDirectoryItems("tetris/modes")
for i=1,#mode_list do
@@ -42,13 +48,6 @@ function initModules()
game_modes[#game_modes+1] = require ("tetris.modes."..string.sub(mode_list[i],1,-5))
end
end
challenges = {}
challenge_list = love.filesystem.getDirectoryItems("tetris/challenges")
for i=1,#challenge_list do
if(challenge_list[i] ~= "challenge.lua" and string.sub(challenge_list[i], -4) == ".lua") then
challenges[#challenges+1] = require ("tetris.challenges."..string.sub(challenge_list[i],1,-5))
end
end
rulesets = {}
rule_list = love.filesystem.getDirectoryItems("tetris/rulesets")
for i=1,#rule_list do
@@ -79,20 +78,20 @@ function love.draw()
(height - scale_factor * 480) / 2
)
love.graphics.scale(scale_factor)
scene:render()
if config.gamesettings.display_gamemode == 1 or scene.title == "Title" then
love.graphics.setFont(font_3x5_2)
love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(
string.format("%.2f", 1 / love.timer.getAverageDelta()) ..
string.format("%.2f", 1.0 / love.timer.getAverageDelta()) ..
"fps - " .. version, 0, 460, 635, "right"
)
end
love.graphics.pop()
love.graphics.setCanvas()
love.graphics.setColor(1,1,1,1)
love.graphics.draw(GLOBAL_CANVAS)
@@ -123,11 +122,11 @@ function love.keypressed(key, scancode)
love.filesystem.remove("ss")
love.filesystem.createDirectory("ss")
end
print("Saving screenshot as "..ss_name)
print("Saving screenshot as "..love.filesystem.getSaveDirectory().."/"..ss_name)
GLOBAL_CANVAS:newImageData():encode("png", ss_name)
-- function keys are reserved
elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f[1-9][0-9]+$") then
return
return
-- escape is reserved for menu_back
elseif scancode == "escape" then
scene:onInputPress({input="menu_back", type="key", key=key, scancode=scancode})
@@ -147,7 +146,7 @@ function love.keyreleased(key, scancode)
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
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
@@ -193,7 +192,7 @@ function love.joystickaxis(joystick, axis, value)
config.input.joysticks and
config.input.joysticks[joystick:getName()] and
config.input.joysticks[joystick:getName()].axes and
config.input.joysticks[joystick:getName()].axes[axis]
config.input.joysticks[joystick:getName()].axes[axis]
then
if math.abs(value) >= 1 then
input_pressed = config.input.joysticks[joystick:getName()].axes[axis][value >= 1 and "positive" or "negative"]
@@ -290,12 +289,16 @@ function love.focus(f)
end
function love.resize(w, h)
GLOBAL_CANVAS:release()
GLOBAL_CANVAS = love.graphics.newCanvas(w, h)
GLOBAL_CANVAS:release()
GLOBAL_CANVAS = love.graphics.newCanvas(w, h)
end
-- higher values of TARGET_FPS will make the game run "faster"
-- since the game is mostly designed for 60 FPS
local TARGET_FPS = 60
local FRAME_DURATION = 1.0 / TARGET_FPS
-- custom run function; optimizes game by syncing draw/update calls
function love.run()
if love.load then love.load(love.arg.parseGameArguments(arg), arg) end
@@ -304,7 +307,7 @@ function love.run()
local dt = 0
local last_time = love.timer.getTime()
local time_accumulator = 0
local time_accumulator = 0.0
return function()
if love.event then
love.event.pump()
@@ -321,28 +324,43 @@ function love.run()
if love.timer then
processBGMFadeout(love.timer.step())
end
if scene and scene.update and love.timer then
scene:update()
local frame_duration = 1.0 / TARGET_FPS
if time_accumulator < frame_duration then
if time_accumulator < FRAME_DURATION then
if love.graphics and love.graphics.isActive() and love.draw then
love.graphics.origin()
love.graphics.clear(love.graphics.getBackgroundColor())
love.draw()
love.graphics.present()
end
local end_time = last_time + frame_duration
local time = love.timer.getTime()
while time < end_time do
love.timer.sleep(0.001)
time = love.timer.getTime()
-- request 1ms delays first but stop short of overshooting, then do "0ms" delays without overshooting (0ms requests generally do a delay of some nonzero amount of time, but maybe less than 1ms)
for milliseconds=0.001,0.000,-0.001 do
local max_delay = 0.0
while max_delay < FRAME_DURATION do
local delay_start_time = love.timer.getTime()
if delay_start_time - last_time < FRAME_DURATION - max_delay then
love.timer.sleep(milliseconds)
local last_delay = love.timer.getTime() - delay_start_time
if last_delay > max_delay then
max_delay = last_delay
end
else
break
end
end
end
while love.timer.getTime() - last_time < FRAME_DURATION do
-- busy loop, do nothing here until delay is finished; delays above stop short of finishing, so this part can finish it off precisely
end
time_accumulator = time_accumulator + time - last_time
end
time_accumulator = time_accumulator - frame_duration
local finish_delay_time = love.timer.getTime()
local real_frame_duration = finish_delay_time - last_time
time_accumulator = time_accumulator + real_frame_duration - FRAME_DURATION
last_time = finish_delay_time
end
last_time = love.timer.getTime()
end
end

View File

@@ -1 +1,3 @@
#!/bin/sh
zip -r cambridge.love libs load res scene tetris conf.lua main.lua scene.lua funcs.lua

View File

@@ -1,4 +1,6 @@
./package
#!/bin/sh
./package-love.sh
mkdir dist
mkdir dist/windows
mkdir dist/win32
@@ -8,4 +10,4 @@ zip dist/cambridge-windows.zip dist/windows/* SOURCES.md LICENSE.md
cat dist/win32/love.exe cambridge.love > dist/win32/cambridge.exe
zip dist/cambridge-win32.zip dist/win32/* SOURCES.md LICENSE.md
cp cambridge.love dist/other/
zip dist/cambridge-other.zip cambridge.love libs/discord-rpc.* SOURCES.md LICENSE.md
zip dist/cambridge-other.zip cambridge.love libs/discord-rpc.* SOURCES.md LICENSE.md

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 734 B

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -10,9 +10,9 @@ function Scene:onInputRelease() end
ExitScene = require "scene.exit"
GameScene = require "scene.game"
ChallengeScene = require "scene.challenge"
ReplayScene = require "scene.replay"
ModeSelectScene = require "scene.mode_select"
ChallengeSelectScene = require "scene.challenge_select"
ReplaySelectScene = require "scene.replay_select"
KeyConfigScene = require "scene.key_config"
StickConfigScene = require "scene.stick_config"
InputConfigScene = require "scene.input_config"

View File

@@ -1,93 +0,0 @@
local ChallengeScene = Scene:extend()
ChallengeScene.title = "Challenge"
require 'load.save'
function ChallengeScene:new(game_mode, ruleset, inputs)
self.retry_mode = game_mode
self.retry_ruleset = ruleset
self.secret_inputs = inputs
self.game = game_mode(self.secret_inputs)
self.ruleset = ruleset(self.game)
self.game:initialize(self.ruleset)
self.inputs = {
left=false,
right=false,
up=false,
down=false,
rotate_left=false,
rotate_left2=false,
rotate_right=false,
rotate_right2=false,
rotate_180=false,
hold=false,
}
self.paused = false
DiscordRPC:update({
details = "In challenge",
state = self.game.name,
largeImageKey = "ingame-"..self.game:getBackground().."00"
})
end
function ChallengeScene:update()
if love.window.hasFocus() and not self.paused then
local inputs = {}
for input, value in pairs(self.inputs) do
inputs[input] = value
end
self.game:update(inputs, self.ruleset)
self.game.grid:update()
DiscordRPC:update({
largeImageKey = "ingame-"..self.game:getBackground().."00"
})
end
end
function ChallengeScene:render()
self.game:draw(self.paused)
end
function ChallengeScene:onInputPress(e)
if (
self.game.game_over or self.game.completed
) and (
e.input == "menu_decide" or
e.input == "menu_back" or
e.input == "retry"
) then
highscore_entry = self.game:getHighscoreData()
highscore_hash = self.game.hash .. "-" .. self.ruleset.hash
submitHighscore(highscore_hash, highscore_entry)
self.game:onExit()
scene = e.input == "retry" and ChallengeScene(self.retry_mode, self.retry_ruleset, self.secret_inputs) or ModeSelectScene()
elseif e.input == "retry" then
switchBGM(nil)
self.game:onExit()
scene = ChallengeScene(self.retry_mode, self.retry_ruleset, self.secret_inputs)
elseif e.input == "pause" and not (self.game.game_over or self.game.completed) then
self.paused = not self.paused
if self.paused then pauseBGM()
else resumeBGM() end
elseif e.input == "menu_back" then
self.game:onExit()
scene = ChallengeSelectScene()
elseif e.input and string.sub(e.input, 1, 5) ~= "menu_" then
self.inputs[e.input] = true
end
end
function ChallengeScene:onInputRelease(e)
if e.input and string.sub(e.input, 1, 5) ~= "menu_" then
self.inputs[e.input] = false
end
end
function submitHighscore(hash, data)
if not highscores[hash] then highscores[hash] = {} end
table.insert(highscores[hash], data)
saveHighscores()
end
return ChallengeScene

View File

@@ -1,186 +0,0 @@
local ChallengeSelectScene = Scene:extend()
ChallengeSelectScene.title = "Challenges"
current_challenge = 1
function indexOf(array, value)
for i, v in ipairs(array) do
if v == value then
return i
end
end
return nil
end
function ChallengeSelectScene:new()
-- reload custom modules
initModules()
if table.getn(challenges) == 0 then
self.display_warning = true
current_challenge = 1
else
self.display_warning = false
if current_challenge > table.getn(challenges) then
current_challenge = 1
end
end
self.menu_state = {
challenge = current_challenge,
select = "challenge",
}
self.secret_inputs = {}
self.das = 0
DiscordRPC:update({
details = "In menus",
state = "Choosing a challenge",
largeImageKey = "ingame-000"
})
end
function ChallengeSelectScene:update()
switchBGM(nil) -- experimental
if self.das_up or self.das_down then
self.das = self.das + 1
else
self.das = 0
end
if self.das >= 15 then
self:changeOption(self.das_up and -1 or 1)
self.das = self.das - 4
end
DiscordRPC:update({
details = "In menus",
state = "Choosing a challenge",
largeImageKey = "ingame-000"
})
end
function ChallengeSelectScene:render()
love.graphics.draw(
backgrounds[0],
0, 0, 0,
0.5, 0.5
)
love.graphics.draw(misc_graphics["select_challenge"], 20, 40)
if self.display_warning then
love.graphics.setFont(font_3x5_3)
love.graphics.printf(
"You have no challenges",
80, 200, 480, "center"
)
love.graphics.setFont(font_3x5_2)
love.graphics.printf(
"Come back to this menu after getting more challenges. " ..
"Press any button to return to the main menu.",
80, 250, 480, "center"
)
return
end
if self.menu_state.select == "challenge" then
love.graphics.setColor(1, 1, 1, 0.5)
end
love.graphics.rectangle("fill", 20, 258, 240, 22)
if self.menu_state.select == "challenge" then
love.graphics.setColor(1, 1, 1, 0.25)
end
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_2)
for idx, challenge in pairs(challenges) do
if(idx >= self.menu_state.challenge-9 and idx <= self.menu_state.challenge+9) then
love.graphics.printf(challenge.name, 40, (260 - 20*(self.menu_state.challenge)) + 20 * idx, 200, "left")
end
end
-- challenge details
love.graphics.printf(challenges[self.menu_state.challenge].tagline, 340, 150, 200, "left")
love.graphics.printf(challenges[self.menu_state.challenge].description, 340, 250, 200, "left")
love.graphics.printf("Mode: "..challenges[self.menu_state.challenge].mode, 340, 350, 200, "left")
love.graphics.printf("Ruleset: "..challenges[self.menu_state.challenge].ruleset, 340, 380, 200, "left")
end
function ChallengeSelectScene:onInputPress(e)
if self.display_warning and e.input then
scene = TitleScene()
elseif e.type == "wheel" then
if e.x % 2 == 1 then
self:switchSelect()
end
if e.y ~= 0 then
self:changeOption(-e.y)
end
elseif e.input == "menu_decide" or e.scancode == "return" then
for idx, mode in pairs(game_modes) do
if mode.hash == challenges[self.menu_state.challenge].mode then
cur_mode = idx
break
end
end
for idx, ruleset in pairs(rulesets) do
if ruleset.hash == challenges[self.menu_state.challenge].ruleset then
cur_ruleset = idx
break
end
end
playSE("mode_decide")
saveConfig()
scene = ChallengeScene(
challenges[current_challenge],
rulesets[cur_ruleset]
)
elseif e.input == "up" or e.scancode == "up" then
self:changeOption(-1)
self.das_up = true
self.das_down = nil
elseif e.input == "down" or e.scancode == "down" then
self:changeOption(1)
self.das_down = true
self.das_up = nil
elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
scene = TitleScene()
elseif e.input then
self.secret_inputs[e.input] = true
end
end
function ChallengeSelectScene:onInputRelease(e)
if e.input == "up" or e.scancode == "up" then
self.das_up = nil
elseif e.input == "down" or e.scancode == "down" then
self.das_down = nil
elseif e.input then
self.secret_inputs[e.input] = false
end
end
function ChallengeSelectScene:changeOption(rel)
self:changechallenge(rel)
playSE("cursor")
end
function ChallengeSelectScene:changechallenge(rel)
local len = table.getn(challenges)
self.menu_state.challenge = Mod1(self.menu_state.challenge + rel, len)
end
return ChallengeSelectScene

View File

@@ -5,7 +5,7 @@ CreditsScene.title = "Credits"
function CreditsScene:new()
self.frames = 0
-- higher = slower
self.scroll_speed = 1.85
self.scroll_speed = 1.8
switchBGM("credit_roll", "gm3")
DiscordRPC:update({
@@ -46,7 +46,7 @@ function CreditsScene:render()
love.graphics.print("Game Developers", 320, 550 - offset)
love.graphics.print("Project Heads", 320, 640 - offset)
love.graphics.print("Notable Game Developers", 320, 750 - offset)
love.graphics.print("Special Thanks", 320, 1020 - offset)
love.graphics.print("Special Thanks", 320, 1000 - offset)
love.graphics.print("- Milla", 320, math.max(2130 - offset, 320))
love.graphics.setFont(font_3x5_2)
@@ -55,7 +55,7 @@ function CreditsScene:render()
love.graphics.print(
"2Tie - TGMsim\nAxel Fox - Multimino\nDr Ocelot - Tetra Legends\n" ..
"Electra - ZTrix\nFelicity/nightmareci/kdex - Shiromino\n" ..
"Mine - Tetra Online\nMrZ - Techmino\nosk - TETR.IO\n" ..
"Mine - Tetra Online\nMrZ - Techmino\n" ..
"Phoenix Flare - Master of Blocks\nRayRay26 - Spirit Drop\n" ..
"Rin - Puzzle Trial\nsinefuse - stackfuse",
320, 790 - offset
@@ -66,7 +66,7 @@ function CreditsScene:render()
"CylinderKnot\neightsixfivezero\nEricICX\nGesomaru\n" ..
"gizmo4487\nJBroms\nKirby703\nKitaru\n" ..
"M1ssing0\nMattMayuga\nMyPasswordIsWeak\n" ..
"Nikki Karissa\noffwo\nOliver\nPineapple\npokemonfan1937\n" ..
"Nikki Karissa\nnim\noffwo\nOliver\nPineapple\npokemonfan1937\n" ..
"Pyra Neoxi\nRDST64\nRocketLanterns\nRustyFoxxo\n" ..
"saphie\nShelleloch\nSimon\nstratus\nSuper302\n" ..
"switchpalacecorner\nterpyderp\nTetrian22\nTetro48\nThatCookie\n" ..
@@ -75,7 +75,7 @@ function CreditsScene:render()
"Tetra Legends Discord\nTetra Online Discord\nMultimino Discord\n" ..
"Hard Drop Discord\nRusty's Systemspace\nCambridge Discord\n" ..
"And to you, the player!",
320, 1060 - offset
320, 1040 - offset
)
end

View File

@@ -9,6 +9,7 @@ function GameScene:new(game_mode, ruleset, inputs)
self.retry_ruleset = ruleset
self.secret_inputs = inputs
self.game = game_mode(self.secret_inputs)
self.game.secret_inputs = inputs
self.ruleset = ruleset(self.game)
self.game:initialize(self.ruleset)
self.inputs = {
@@ -24,6 +25,8 @@ function GameScene:new(game_mode, ruleset, inputs)
hold=false,
}
self.paused = false
self.game.pause_count = 0
self.game.pause_time = 0
DiscordRPC:update({
details = self.game.rpc_details,
state = self.game.name,
@@ -32,7 +35,9 @@ function GameScene:new(game_mode, ruleset, inputs)
end
function GameScene:update()
if love.window.hasFocus() and not self.paused then
if self.paused then
self.game.pause_time = self.game.pause_time + 1
else
local inputs = {}
for input, value in pairs(self.inputs) do
inputs[input] = value
@@ -40,6 +45,8 @@ function GameScene:update()
self.game:update(inputs, self.ruleset)
self.game.grid:update()
DiscordRPC:update({
details = self.game.rpc_details,
state = self.game.name,
largeImageKey = "ingame-"..self.game:getBackground().."00"
})
end
@@ -47,6 +54,16 @@ end
function GameScene:render()
self.game:draw(self.paused)
if self.game.pause_time > 0 or self.game.pause_count > 0 then
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_2)
love.graphics.printf(string.format(
"%d PAUSE%s (%s)",
self.game.pause_count,
self.game.pause_count == 1 and "" or "S",
formatTime(self.game.pause_time)
), 0, 0, 635, "right")
end
end
function GameScene:onInputPress(e)
@@ -68,8 +85,12 @@ function GameScene:onInputPress(e)
scene = GameScene(self.retry_mode, self.retry_ruleset, self.secret_inputs)
elseif e.input == "pause" and not (self.game.game_over or self.game.completed) then
self.paused = not self.paused
if self.paused then pauseBGM()
else resumeBGM() end
if self.paused then
pauseBGM()
self.game.pause_count = self.game.pause_count + 1
else
resumeBGM()
end
elseif e.input == "menu_back" then
self.game:onExit()
scene = ModeSelectScene()

View File

@@ -13,11 +13,12 @@ ConfigScene.options = {
{"world_reverse", "A Button Rotation", false, {"Left", "Auto", "Right"}},
{"spawn_positions", "Spawn Positions", false, {"Per ruleset", "In field", "Out of field"}},
{"display_gamemode", "Debug Info", false, {"On", "Off"}},
{"das_last_key", "DAS Last Key", false, {"Off", "On"}},
{"save_replay", "Save Replays", false, {"On", "Off"}},
{"smooth_movement", "Smooth Piece Drop", false, {"On", "Off"}},
{"synchroes_allowed", "Synchroes", false, {"Per ruleset", "On", "Off"}},
{"diagonal_input", "Diagonal Input", false, {"On", "Off"}},
{"das_last_key", "DAS Last Key", false, {"Off", "On"}},
{"buffer_lock", "Buffer Drop Type", false, {"Off", "Hold", "Tap"}},
{"synchroes_allowed", "Synchroes", false, {"Per ruleset", "On", "Off"}},
{"sfx_volume", "SFX", true, "sfxSlider"},
{"bgm_volume", "BGM", true, "bgmSlider"},
}
@@ -33,8 +34,8 @@ function ConfigScene:new()
state = "Changing game settings",
})
self.sfxSlider = newSlider(165, 400, 225, config.sfx_volume * 100, 0, 100, function(v) config.sfx_volume = v / 100 end, {width=20, knob="circle", track="roundrect"})
self.bgmSlider = newSlider(465, 400, 225, config.bgm_volume * 100, 0, 100, function(v) config.bgm_volume = v / 100 end, {width=20, knob="circle", track="roundrect"})
self.sfxSlider = newSlider(165, 420, 225, config.sfx_volume * 100, 0, 100, function(v) config.sfx_volume = v / 100 end, {width=20, knob="circle", track="roundrect"})
self.bgmSlider = newSlider(465, 420, 225, config.bgm_volume * 100, 0, 100, function(v) config.bgm_volume = v / 100 end, {width=20, knob="circle", track="roundrect"})
end
function ConfigScene:update()
@@ -58,7 +59,7 @@ function ConfigScene:render()
if not ConfigScene.options[self.highlight][3] then
love.graphics.rectangle("fill", 25, 98 + self.highlight * 20, 170, 22)
else
love.graphics.rectangle("fill", 65 + (1+self.highlight-#self.options) * 300, 342, 215, 33)
love.graphics.rectangle("fill", 65 + (1+self.highlight-#self.options) * 300, 362, 215, 33)
end
love.graphics.setFont(font_3x5_2)
@@ -75,8 +76,8 @@ function ConfigScene:render()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_3)
love.graphics.print("SFX Volume: " .. math.floor(self.sfxSlider:getValue()) .. "%", 75, 345)
love.graphics.print("BGM Volume: " .. math.floor(self.bgmSlider:getValue()) .. "%", 375, 345)
love.graphics.print("SFX Volume: " .. math.floor(self.sfxSlider:getValue()) .. "%", 75, 365)
love.graphics.print("BGM Volume: " .. math.floor(self.bgmSlider:getValue()) .. "%", 375, 365)
love.graphics.setColor(1, 1, 1, 0.75)
self.sfxSlider:draw()

View File

@@ -1,6 +1,6 @@
local ModeSelectScene = Scene:extend()
ModeSelectScene.title = "Freeplay"
ModeSelectScene.title = "Game Start"
current_mode = 1
current_ruleset = 1
@@ -108,7 +108,6 @@ function ModeSelectScene:render()
love.graphics.printf(ruleset.name, 360, (260 - 20*(self.menu_state.ruleset)) + 20 * idx, 160, "left")
end
end
love.graphics.printf(game_modes[self.menu_state.mode].tagline, 5, 5, 600, "left")
end
function ModeSelectScene:onInputPress(e)
@@ -188,4 +187,4 @@ function ModeSelectScene:changeRuleset(rel)
self.menu_state.ruleset = Mod1(self.menu_state.ruleset + rel, len)
end
return ModeSelectScene
return ModeSelectScene

110
scene/replay.lua Normal file
View File

@@ -0,0 +1,110 @@
local Sequence = require 'tetris.randomizers.fixed_sequence'
local ReplayScene = Scene:extend()
ReplayScene.title = "Replay"
function ReplayScene:new(replay, game_mode, ruleset)
config.gamesettings = replay["gamesettings"]
if replay["delayed_auto_shift"] then config.das = replay["delayed_auto_shift"] end
if replay["auto_repeat_rate"] then config.arr = replay["auto_repeat_rate"] end
if replay["das_cut_delay"] then config.dcd = replay["das_cut_delay"] end
love.math.setRandomSeed(replay["random_low"], replay["random_high"])
love.math.setRandomState(replay["random_state"])
self.retry_replay = replay
self.retry_mode = game_mode
self.retry_ruleset = ruleset
self.secret_inputs = replay["secret_inputs"]
self.game = game_mode(self.secret_inputs)
self.game.save_replay = false
self.ruleset = ruleset(self.game)
self.game:initialize(self.ruleset)
self.inputs = {
left=false,
right=false,
up=false,
down=false,
rotate_left=false,
rotate_left2=false,
rotate_right=false,
rotate_right2=false,
rotate_180=false,
hold=false,
}
self.paused = false
self.game.pause_count = replay["pause_count"]
self.game.pause_time = replay["pause_time"]
self.replay = deepcopy(replay)
self.replay_index = 1
DiscordRPC:update({
details = "Viewing a replay",
state = self.game.name,
largeImageKey = "ingame-"..self.game:getBackground().."00"
})
end
function ReplayScene:update()
if not self.paused then
self.inputs = self.replay["inputs"][self.replay_index]["inputs"]
self.replay["inputs"][self.replay_index]["frames"] = self.replay["inputs"][self.replay_index]["frames"] - 1
if self.replay["inputs"][self.replay_index]["frames"] == 0 and self.replay_index < table.getn(self.replay["inputs"]) then
self.replay_index = self.replay_index + 1
end
local input_copy = {}
for input, value in pairs(self.inputs) do
input_copy[input] = value
end
self.game:update(input_copy, self.ruleset)
self.game.grid:update()
DiscordRPC:update({
details = "Viewing a replay",
state = self.game.name,
largeImageKey = "ingame-"..self.game:getBackground().."00"
})
end
end
function ReplayScene:render()
self.game:draw(self.paused)
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_3)
love.graphics.printf("REPLAY", 0, 0, 635, "right")
love.graphics.setFont(font_3x5_2)
if self.game.pause_time and self.game.pause_count then
if self.game.pause_time > 0 or self.game.pause_count > 0 then
love.graphics.printf(string.format(
"%d PAUSE%s (%s)",
self.game.pause_count,
self.game.pause_count == 1 and "" or "S",
formatTime(self.game.pause_time)
), 0, 23, 635, "right")
end
else
love.graphics.printf("?? PAUSES (--:--.--)", 0, 23, 635, "right")
end
end
function ReplayScene:onInputPress(e)
if (
e.input == "menu_back" or
e.input == "menu_decide" or
e.input == "retry"
) then
self.game:onExit()
loadSave()
love.math.setRandomSeed(os.time())
scene = (
(e.input == "retry") and
ReplayScene(
self.retry_replay, self.retry_mode,
self.retry_ruleset, self.secret_inputs
) or ReplaySelectScene()
)
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
end
end
return ReplayScene

222
scene/replay_select.lua Normal file
View File

@@ -0,0 +1,222 @@
local ReplaySelectScene = Scene:extend()
ReplaySelectScene.title = "Replays"
local binser = require 'libs.binser'
current_replay = 1
function ReplaySelectScene:new()
-- reload custom modules
initModules()
-- load replays
replays = {}
replay_file_list = love.filesystem.getDirectoryItems("replays")
for i=1,#replay_file_list do
local data = love.filesystem.read("replays/"..replay_file_list[i])
local new_replay = binser.deserialize(data)[1]
replays[#replays + 1] = new_replay
end
table.sort(replays, function(a, b)
return a["timestamp"] > b["timestamp"]
end)
self.display_error = false
if table.getn(replays) == 0 then
self.display_warning = true
current_replay = 1
else
self.display_warning = false
if current_replay > table.getn(replays) then
current_replay = 1
end
end
self.menu_state = {
replay = current_replay,
}
self.das = 0
DiscordRPC:update({
details = "In menus",
state = "Choosing a replay",
largeImageKey = "ingame-000"
})
end
function ReplaySelectScene:update()
switchBGM(nil) -- experimental
if self.das_up or self.das_down or self.das_left or self.das_right then
self.das = self.das + 1
else
self.das = 0
end
if self.das >= 15 then
local change = 0
if self.das_up then
change = -1
elseif self.das_down then
change = 1
elseif self.das_left then
change = -9
elseif self.das_right then
change = 9
end
self:changeOption(change)
self.das = self.das - 4
end
DiscordRPC:update({
details = "In menus",
state = "Choosing a replay",
largeImageKey = "ingame-000"
})
end
function ReplaySelectScene:render()
love.graphics.draw(
backgrounds[0],
0, 0, 0,
0.5, 0.5
)
-- Same graphic as mode select
--love.graphics.draw(misc_graphics["select_mode"], 20, 40)
love.graphics.setFont(font_8x11)
love.graphics.print("SELECT REPLAY", 20, 35)
if self.display_warning then
love.graphics.setFont(font_3x5_3)
love.graphics.printf(
"You have no replays.",
80, 200, 480, "center"
)
love.graphics.setFont(font_3x5_2)
love.graphics.printf(
"Come back to this menu after playing some games. " ..
"Press any button to return to the main menu.",
80, 250, 480, "center"
)
return
elseif self.display_error then
love.graphics.setFont(font_3x5_3)
love.graphics.printf(
"You are missing this mode or ruleset.",
80, 200, 480, "center"
)
love.graphics.setFont(font_3x5_2)
love.graphics.printf(
"Come back after getting the proper mode or ruleset. " ..
"Press any button to return to the main menu.",
80, 250, 480, "center"
)
return
end
love.graphics.setColor(1, 1, 1, 0.5)
love.graphics.rectangle("fill", 3, 258, 634, 22)
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_2)
for idx, replay in ipairs(replays) do
if(idx >= self.menu_state.replay-9 and idx <= self.menu_state.replay+9) then
local display_string = os.date("%c", replay["timestamp"]).." - "..replay["mode"].." - "..replay["ruleset"]
if replay["level"] ~= nil then
display_string = display_string.." - Level: "..replay["level"]
end
if replay["timer"] ~= nil then
display_string = display_string.." - Time: "..formatTime(replay["timer"])
end
if #display_string > 75 then
display_string = display_string:sub(1, 75) .. "..."
end
love.graphics.printf(display_string, 6, (260 - 20*(self.menu_state.replay)) + 20 * idx, 640, "left")
end
end
end
function ReplaySelectScene:onInputPress(e)
if (self.display_warning or self.display_error) and e.input then
scene = TitleScene()
elseif e.type == "wheel" then
if e.y ~= 0 then
self:changeOption(-e.y)
end
elseif e.input == "menu_decide" or e.scancode == "return" then
current_replay = self.menu_state.replay
-- Same as mode decide
playSE("mode_decide")
-- Get game mode and ruleset
local mode
local rules
for key, value in pairs(game_modes) do
if value.name == replays[self.menu_state.replay]["mode"] then
mode = value
break
end
end
for key, value in pairs(rulesets) do
if value.name == replays[self.menu_state.replay]["ruleset"] then
rules = value
break
end
end
if mode == nil or rules == nil then
self.display_error = true
return
end
-- TODO compare replay versions to current versions for Cambridge, ruleset, and mode
scene = ReplayScene(
replays[self.menu_state.replay],
mode,
rules
)
elseif e.input == "up" or e.scancode == "up" then
self:changeOption(-1)
self.das_up = true
self.das_down = nil
self.das_left = nil
self.das_right = nil
elseif e.input == "down" or e.scancode == "down" then
self:changeOption(1)
self.das_down = true
self.das_up = nil
self.das_left = nil
self.das_right = nil
elseif e.input == "left" or e.scancode == "left" then
self:changeOption(-9)
self.das_left = true
self.das_right = nil
self.das_up = nil
self.das_down = nil
elseif e.input == "right" or e.scancode == "right" then
self:changeOption(9)
self.das_right = true
self.das_left = nil
self.das_up = nil
self.das_down = nil
elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
scene = TitleScene()
end
end
function ReplaySelectScene:onInputRelease(e)
if e.input == "up" or e.scancode == "up" then
self.das_up = nil
elseif e.input == "down" or e.scancode == "down" then
self.das_down = nil
elseif e.input == "right" or e.scancode == "right" then
self.das_right = nil
elseif e.input == "left" or e.scancode == "left" then
self.das_left = nil
end
end
function ReplaySelectScene:changeOption(rel)
local len = table.getn(replays)
self.menu_state.replay = Mod1(self.menu_state.replay + rel, len)
playSE("cursor")
end
return ReplaySelectScene

View File

@@ -20,7 +20,7 @@ function SettingsScene:new()
self.menu_state = 1
DiscordRPC:update({
details = "In settings",
state = settingsidle[math.random(#settingsidle)],
state = settingsidle[love.math.random(#settingsidle)],
largeImageKey = "settings",
})
end

View File

@@ -5,7 +5,7 @@ TitleScene.restart_message = false
local main_menu_screens = {
ModeSelectScene,
ChallengeSelectScene,
ReplaySelectScene,
SettingsScene,
CreditsScene,
ExitScene,
@@ -35,21 +35,6 @@ local mainmenuidle = {
"This is probably the longest RPC string out of every possible RPC string that can be displayed."
}
local menusplash = {
"Welcome to Cambridge!",
"Get ready to put the block!",
"Also try Master of Blocks!",
"Also try Shiromino!",
"1 year in the making!",
"haileyjunk!",
"WOOOOAAAAAHHH!!!!!"
}
local currentSplash = menusplash[math.random(#menusplash)]
local now = os.date("t")
showDebugKeys = false
function TitleScene:new()
self.main_menu_state = 1
self.frames = 0
@@ -59,16 +44,10 @@ function TitleScene:new()
self.text_flag = false
DiscordRPC:update({
details = "In menus",
state = mainmenuidle[math.random(#mainmenuidle)],
largeImageKey = "1year",
largeImageText = version.." | Thanks for 1 year!"
state = mainmenuidle[love.math.random(#mainmenuidle)],
largeImageKey = "icon2",
largeImageText = version
})
if now.month == 12 then
DiscordRPC:update({
largeImageKey = "snow"
})
end
end
function TitleScene:update()
@@ -81,27 +60,42 @@ function TitleScene:update()
else self.y_offset = 310 - self.frames end
end
local block_offsets = {
{color = "M", x = 0, y = 0},
{color = "G", x = 32, y = 0},
{color = "Y", x = 64, y = 0},
{color = "B", x = 0, y = 32},
{color = "O", x = 0, y = 64},
{color = "C", x = 32, y = 64},
{color = "R", x = 64, y = 64}
}
function TitleScene:render()
love.graphics.setFont(font_3x5_4)
love.graphics.setColor(1, 1, 1, 1 - self.snow_bg_opacity)
love.graphics.draw(
backgrounds["title_no_icon"], -- title, title_night
0, 0, 0,
0.5, 0.5
)
-- 490, 192
for _, b in ipairs(block_offsets) do
love.graphics.draw(
blocks["2tie"][b.color],
490 + b.x, 192 + b.y, 0,
2, 2
)
end
--[[
love.graphics.draw(
backgrounds["title"],
0, 0, 0,
0.5, 0.5
)
]]
love.graphics.draw(
backgrounds["title_night"],
0, 0, 0,
0.5, 0.5
)
love.graphics.draw(
misc_graphics["icon"],
460, 170, 0,
490, 192, 0,
2, 2
)
love.graphics.printf(currentSplash, 390, 280, 320, "center", 0, 0.75, 0.75)
]]
--love.graphics.printf("Thanks for 1 year!", 430, 280, 160, "center")
love.graphics.setFont(font_3x5_2)
love.graphics.setColor(1, 1, 1, self.snow_bg_opacity)
@@ -128,11 +122,6 @@ function TitleScene:render()
for i, screen in pairs(main_menu_screens) do
love.graphics.printf(screen.title, 40, 280 + 20 * i, 120, "left")
end
if showDebugKeys then
love.graphics.print("DEBUG KEYS\n\nF3+S: Get new splash message\nF3+R: Restart\nF3+I: Toggle this")
end
end
function TitleScene:changeOption(rel)
@@ -140,10 +129,7 @@ function TitleScene:changeOption(rel)
self.main_menu_state = (self.main_menu_state + len + rel - 1) % len + 1
end
function TitleScene:onInputPress(e)
local debugkey = love.keyboard.isDown("f3")
if e.input == "menu_decide" or e.scancode == "return" then
playSE("main_decide")
scene = main_menu_screens[self.main_menu_state]()
@@ -155,27 +141,6 @@ function TitleScene:onInputPress(e)
playSE("cursor")
elseif e.input == "menu_back" or e.scancode == "backspace" or e.scancode == "delete" then
love.event.quit()
-- small debug feature, press f3+s to get a new splash message
elseif e.scancode == "s" then
if debugkey then
currentSplash = menusplash[math.random(#menusplash)]
playSE("main_decide")
end
elseif e.scancode == "r" then
if debugkey then
love.event.quit("restart")
end
elseif e.scancode == "i" then
if debugkey then
if showDebugKeys then
showDebugKeys = false
else
showDebugKeys = true
end
end
-- no winter easter egg for now
--[[
else
self.text = self.text .. (e.scancode or "")
if self.text == "ffffff" then
@@ -184,7 +149,6 @@ function TitleScene:onInputPress(e)
largeImageKey = "snow"
})
end
]]
end
end

7
start.bat Normal file
View File

@@ -0,0 +1,7 @@
@echo OFF
rem This solution of detecting the current CPU taken from here: https://stackoverflow.com/a/24590583
reg Query "HKLM\Hardware\Description\System\CentralProcessor\0" | find /i "x86" > NUL && set CURRENT_CPU=32BIT || set CURRENT_CPU=64BIT
if %CURRENT_CPU%==32BIT .\dist\win32\love.exe .
if %CURRENT_CPU%==64BIT .\dist\windows\love.exe .

View File

@@ -1,26 +0,0 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local Grid = require 'tetris.components.grid'
local Randomizer = require 'tetris.randomizers.randomizer'
local Bag7Randomizer = require 'tetris.randomizers.bag7noI'
local MarathonGF = require 'tetris.modes.marathon_gf'
local TetrsChallenge = MarathonGF:extend()
TetrsChallenge.name = "Tetrs"
TetrsChallenge.hash = "Tetrs"
TetrsChallenge.mode = "MarathonGF"
TetrsChallenge.ruleset = "Standard"
TetrsChallenge.tagline = "Where's the long bar? Seriously, I can't find it."
TetrsChallenge.description = "Complete a 150-line Marathon...without the I piece!"
function TetrsChallenge:new()
TetrsChallenge.super:new()
self.randomizer = Bag7Randomizer()
self.next_queue_length = 6
end
return TetrsChallenge

View File

@@ -1,23 +0,0 @@
-- currently you need to require and extend the gamemode you're making a challenge out of
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local Grid = require 'tetris.components.grid'
local Randomizer = require 'tetris.randomizers.randomizer'
local BagRandomizer = require 'tetris.randomizers.bag'
local MarathonGF = require 'tetris.modes.marathon_gf'
local Challenge = GameMode:extend()
Challenge.name = "A really cool challenge name"
Challenge.hash = ""
Challenge.mode = ""
Challenge.ruleset = ""
Challenge.tagline = "Are you up for this challenge?"
Challenge.description = "Complete a mode with a specific ruleset and idk they did some other stupid things too lol"
return Challenge

View File

@@ -1,26 +0,0 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local Grid = require 'tetris.components.grid'
local Randomizer = require 'tetris.randomizers.randomizer'
local Bag7Randomizer = require 'tetris.randomizers.bag7noI'
local MarathonGF = require 'tetris.modes.marathon_gf'
local TetrsChallenge = MarathonGF:extend()
TetrsChallenge.name = "Another mode"
TetrsChallenge.hash = "Tetrs2"
TetrsChallenge.mode = "MarathonGF"
TetrsChallenge.ruleset = "Standard"
TetrsChallenge.tagline = "Hey look! The feature works!"
TetrsChallenge.description = "This is just a clone of Tetrs to test out the challenge details feature."
function TetrsChallenge:new()
TetrsChallenge.super:new()
self.randomizer = Bag7Randomizer()
self.next_queue_length = 6
end
return TetrsChallenge

View File

@@ -182,6 +182,19 @@ function Grid:clearSpecificRow(row)
end
end
function Grid:clearBlock(x, y)
self.grid[x+1][y+1] = empty
end
function Grid:clearBottomRows(num)
local old_isRowFull = self.isRowFull
self.isRowFull = function(self, row)
return row >= self.height + 1 - num
end
self:clearClearedRows()
self.isRowFull = old_isRowFull
end
function Grid:applyPiece(piece)
if piece.big then
self:applyBigPiece(piece)
@@ -218,12 +231,15 @@ function Grid:applyBigPiece(piece)
end
end
function Grid:checkForBravo(cleared_row_count)
for i = 0, self.height - 1 - cleared_row_count do
for j = 0, self.width - 1 do
if self:isOccupied(j, i) then return false end
end
-- places where you see this take an argument used the old, buggy method
function Grid:checkForBravo()
for i = 0, self.height - 1 do
if not self:isRowFull(i+1) then
for j = 0, self.width - 1 do
if self:isOccupied(j, i) then return false end
end
end
end
return true
end

View File

@@ -7,6 +7,7 @@ local playedGoSE = false
local Grid = require 'tetris.components.grid'
local Randomizer = require 'tetris.randomizers.randomizer'
local BagRandomizer = require 'tetris.randomizers.bag'
local binser = require 'libs.binser'
local GameMode = Object:extend()
@@ -15,7 +16,12 @@ GameMode.hash = ""
GameMode.tagline = ""
GameMode.rollOpacityFunction = function(age) return 0 end
function GameMode:new(secret_inputs)
function GameMode:new()
self.replay_inputs = {}
self.random_low, self.random_high = love.math.getRandomSeed()
self.random_state = love.math.getRandomState()
self.save_replay = config.gamesettings.save_replay == 1
self.grid = Grid(10, 24)
self.randomizer = Randomizer()
self.piece = nil
@@ -114,8 +120,66 @@ function GameMode:initialize(ruleset)
self.lock_on_hard_drop = ({ruleset.harddrop_lock, self.instant_hard_drop, true, false})[config.gamesettings.manlock]
end
function GameMode:saveReplay()
-- Save replay.
local replay = {}
replay["inputs"] = self.replay_inputs
replay["random_low"] = self.random_low
replay["random_high"] = self.random_high
replay["random_state"] = self.random_state
replay["mode"] = self.name
replay["ruleset"] = self.ruleset.name
replay["timer"] = self.frames
replay["score"] = self.score
replay["level"] = self.level
replay["lines"] = self.lines
replay["gamesettings"] = config.gamesettings
replay["secret_inputs"] = self.secret_inputs
replay["delayed_auto_shift"] = config.das
replay["auto_repeat_rate"] = config.arr
replay["das_cut_delay"] = config.dcd
replay["timestamp"] = os.time()
replay["pause_count"] = self.pause_count
replay["pause_time"] = self.pause_time
if love.filesystem.getInfo("replays") == nil then
love.filesystem.createDirectory("replays")
end
local init_name = string.format("replays/%s.crp", os.date("%Y-%m-%d_%H-%M-%S"))
local replay_name = init_name
local replay_number = 0
while true do
if love.filesystem.getInfo(replay_name, "file") then
replay_number = replay_number + 1
replay_name = string.format("%s (%d)", init_name, replay_number)
else
break
end
end
love.filesystem.write(replay_name, binser.serialize(replay))
end
function GameMode:addReplayInput(inputs)
-- check if inputs have changed since last frame
if not equals(self.prev_inputs, inputs) then
-- insert new inputs into replay inputs table
local new_inputs = {}
new_inputs["inputs"] = {}
new_inputs["frames"] = 1
for key, value in pairs(inputs) do
new_inputs["inputs"][key] = value
end
self.replay_inputs[#self.replay_inputs + 1] = new_inputs
else
-- add 1 to input frame counter
self.replay_inputs[#self.replay_inputs]["frames"] = self.replay_inputs[#self.replay_inputs]["frames"] + 1
end
end
function GameMode:update(inputs, ruleset)
if self.game_over or self.completed then
if self.save_replay and self.game_over_frames == 0 then
self:saveReplay()
end
self.game_over_frames = self.game_over_frames + 1
return
end
@@ -129,6 +193,8 @@ function GameMode:update(inputs, ruleset)
end
end
if self.save_replay then self:addReplayInput(inputs) end
-- advance one frame
if self:advanceOneFrame(inputs, ruleset) == false then return end
@@ -143,7 +209,7 @@ function GameMode:update(inputs, ruleset)
) then
self:onAttemptPieceRotate(self.piece, self.grid)
end
if self.piece == nil then
self:processDelays(inputs, ruleset)
else
@@ -243,7 +309,7 @@ function GameMode:update(inputs, ruleset)
if self.immobile_spin_bonus and
self.piece.last_rotated and (
self.piece:isDropBlocked(self.grid) and
self.piece:isMoveBlocked(self.grid, { x=-1, y=0 }) and
self.piece:isMoveBlocked(self.grid, { x=-1, y=0 }) and
self.piece:isMoveBlocked(self.grid, { x=1, y=0 }) and
self.piece:isMoveBlocked(self.grid, { x=0, y=-1 })
) then
@@ -251,7 +317,7 @@ function GameMode:update(inputs, ruleset)
end
self.grid:applyPiece(self.piece)
-- mark squares (can be overridden)
if self.square_mode then
self.squares = self.squares + self.grid:markSquares()
@@ -312,7 +378,7 @@ function GameMode:onAttemptPieceRotate(piece, grid) end
function GameMode:onPieceMove(piece, grid, dx) end
function GameMode:onPieceRotate(piece, grid, drot) end
function GameMode:onPieceDrop(piece, grid, dy) end
function GameMode:onPieceLock(piece, cleared_row_count)
function GameMode:onPieceLock(piece, cleared_row_count)
playSE("lock")
end
@@ -334,7 +400,16 @@ end
function GameMode:onGameOver()
switchBGM(nil)
love.graphics.setColor(0, 0, 0, 1 - 2 ^ (-self.game_over_frames / 30))
local alpha = 0
local animation_length = 120
if self.game_over_frames < animation_length then
-- Show field for a bit, then fade out.
alpha = math.pow(2048, self.game_over_frames/animation_length - 1)
elseif self.game_over_frames < 2 * animation_length then
-- Keep field hidden for a short time, then pop it back in (for screenshots).
alpha = 1
end
love.graphics.setColor(0, 0, 0, alpha)
love.graphics.rectangle(
"fill", 64, 80,
16 * self.grid.width, 16 * (self.grid.height - 4)
@@ -530,6 +605,8 @@ function GameMode:onEnterOrHold(inputs, ruleset)
if not self.grid:canPlacePiece(self.piece) then
self.game_over = true
return
elseif self.piece:isDropBlocked(self.grid) then
playSE("bottom")
end
ruleset:dropPiece(
inputs, self.piece, self.grid, self:getGravity(),
@@ -578,10 +655,6 @@ function GameMode:initializeNextPiece(
self.piece_soft_locked = false
self.buffer_hard_drop = false
self.buffer_soft_drop = false
if self.piece:isDropBlocked(self.grid) and
self.grid:canPlacePiece(self.piece) then
playSE("bottom")
end
if generate_next_piece == nil then
table.remove(self.next_queue, 1)
table.insert(self.next_queue, self:getNextPiece(ruleset))
@@ -600,11 +673,18 @@ function GameMode:getHighScoreData()
end
function GameMode:animation(x, y, skin, colour)
-- Animation progress where 0 = start and 1 = end
local progress = 1
if self.last_lcd ~= 0 then
progress = (self.last_lcd - self.lcd) / self.last_lcd
end
-- Convert progress through the animation into an alpha value, with easing
local alpha = 1 - progress ^ 2
return {
1, 1, 1,
-0.25 + 1.25 * (self.lcd / self.last_lcd),
skin, colour,
48 + x * 16, y * 16
1, 1, 1,
alpha,
skin, colour,
48 + x * 16, y * 16
}
end
@@ -617,7 +697,55 @@ function GameMode:drawLineClearAnimation()
-- params: block x, y, skin, colour
-- returns: table with RGBA, skin, colour, x, y
-- Fadeout (default)
-- Quadratic Fadeout (default)
--[[
function animation(x, y, skin, colour)
-- Animation progress where 0 = start and 1 = end
local progress = 1
if self.last_lcd ~= 0 then
progress = (self.last_lcd - self.lcd) / self.last_lcd
end
-- Convert progress through the animation into an alpha value, with easing
local alpha = 1 - progress ^ 2
return {
1, 1, 1,
alpha,
skin, colour,
48 + x * 16, y * 16
}
end
--]]
-- Flashy Fadeout
--[[
function animation(x, y, skin, colour)
-- Animation progress where 0 = start and 1 = end
local progress = 1
if self.last_lcd ~= 0 then
progress = (self.last_lcd - self.lcd) / self.last_lcd
end
-- Change this number to change "bounciness"
local bounce = 13
-- Convert progress through the animation into an alpha value
local alpha = 0
-- Cutoff is arbitrary: corresponds to level 500 in Marathon A2
if self.last_lcd > 25 then
-- Goes up and down: looks better when animation is long
alpha = 1 - (bounce * progress^3 - 1.5 * bounce * progress^2 + (0.5 * bounce + 1) * progress)
else
-- Always decreasing: looks better when animation is short
alpha = 1 - progress * progress
end
return {
1, 1, 1,
alpha,
skin, colour,
48 + x * 16, y * 16
}
end
--]]
-- Fadeout
--[[
function animation(x, y, skin, colour)
return {
@@ -728,8 +856,8 @@ function GameMode:drawNextQueue(ruleset)
if self.hold_queue ~= nil and self.enable_hold then
self:setHoldOpacity()
drawPiece(
self.hold_queue.shape,
self.hold_queue.skin,
self.hold_queue.shape,
self.hold_queue.skin,
ruleset.block_offsets[self.hold_queue.shape][self.hold_queue.orientation],
-16, -32
)
@@ -828,7 +956,7 @@ end
function GameMode:drawSectionTimesWithSplits(current_section, section_limit)
section_limit = section_limit or math.huge
local section_x = 440
local split_x = 530
@@ -843,7 +971,7 @@ function GameMode:drawSectionTimesWithSplits(current_section, section_limit)
love.graphics.printf(formatTime(split_time), split_x, 40 + 20 * section, 90, "left")
end
end
if (current_section <= section_limit) then
love.graphics.printf(formatTime(self.frames - self.section_start_time), section_x, 40 + 20 * current_section, 90, "left")
love.graphics.printf(formatTime(self.frames), split_x, 40 + 20 * current_section, 90, "left")
@@ -851,9 +979,11 @@ function GameMode:drawSectionTimesWithSplits(current_section, section_limit)
end
function GameMode:drawBackground()
local id = self:getBackground()
if type(id) == "number" then id = clamp(id, 0, #backgrounds) end
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(
backgrounds[self:getBackground()],
backgrounds[id],
0, 0, 0,
0.5, 0.5
)
@@ -906,7 +1036,7 @@ function GameMode:drawCustom() end
function GameMode:drawIfPaused()
love.graphics.setFont(font_3x5_3)
love.graphics.printf("GAME PAUSED!", 64, 160, 160, "center")
love.graphics.printf("PAUSED!", 64, 160, 160, "center")
end
-- transforms specified in here will transform the whole screen

View File

@@ -353,15 +353,10 @@ function Marathon2020Game:updateSectionTimes(old_level, new_level)
self.section_start_time = self.frames
if (
self.section_status[section - 1] == "cool" and
self.secondary_section_times[section] <= self.secondary_section_times[section - 1] + 120 and
self.secondary_section_times[section] < cool_cutoffs[self.delay_level]
(self.secondary_section_times[section] < cool_cutoffs[self.delay_level]) and
(section == 1 or self.secondary_section_times[section] <= self.secondary_section_times[section - 1] + 120)
) then
sectionCool(section)
elseif self.section_status[section - 1] == "cool" then
table.insert(self.section_status, "none")
elseif self.secondary_section_times[section] < cool_cutoffs[self.delay_level] then
sectionCool(section)
else
table.insert(self.section_status, "none")
end

View File

@@ -249,10 +249,13 @@ local grade_conversion = {
}
function MarathonA2Game:whilePieceActive()
self.grade_point_decay_counter = self.grade_point_decay_counter + 1
if self.grade_point_decay_counter >= grade_point_decays[self.grade + 1] then
self.grade_point_decay_counter = 0
self.grade_points = math.max(0, self.grade_points - 1)
if self.clear then return
else
self.grade_point_decay_counter = self.grade_point_decay_counter + 1
if self.grade_point_decay_counter >= grade_point_decays[self.grade + 1] then
self.grade_point_decay_counter = 0
self.grade_points = math.max(0, self.grade_points - 1)
end
end
end

View File

@@ -26,7 +26,9 @@ function MarathonA3Game:new()
self.roll_points = 0
self.grade_point_decay_counter = 0
self.section_cool_grade = 0
self.section_status = { [0] = "none" }
--self.section_status = { [0] = "none" }
self.section_cools = { [0] = 0 }
self.section_regrets = { [0] = 0 }
self.section_start_time = 0
self.secondary_section_times = { [0] = 0 }
self.section_times = { [0] = 0 }
@@ -201,15 +203,23 @@ function MarathonA3Game:updateSectionTimes(old_level, new_level)
self.speed_level = self.section_cool and self.speed_level + 100 or self.speed_level
if section_time > regret_cutoffs[section] then
self.section_cool_grade = self.section_cool_grade - 1
table.insert(self.section_status, "regret")
if self.grade > 0 then
--this happens after the points are added, intentionally
local currentgrade = self:getAggregateGrade()
while self:getAggregateGrade() >= currentgrade do
self.grade = self.grade - 1
end
self.grade_points = 0
end
table.insert(self.section_regrets, 1)
self.coolregret_message = "REGRET!!"
self.coolregret_timer = 300
elseif self.section_cool then
self.section_cool_grade = self.section_cool_grade + 1
table.insert(self.section_status, "cool")
else
table.insert(self.section_status, "none")
table.insert(self.section_regrets, 0)
end
if self.section_cool then
self.section_cool_grade = self.section_cool_grade + 1
end
self.section_cool = false
@@ -223,6 +233,9 @@ function MarathonA3Game:updateSectionTimes(old_level, new_level)
self.section_cool = true
self.coolregret_message = "COOL!!"
self.coolregret_timer = 300
table.insert(self.section_cools, 1)
else
table.insert(self.section_cools, 0)
end
end
end
@@ -309,7 +322,7 @@ local mroll_points = {10, 20, 30, 100}
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,
7, 8, 8, 8, 9, 9, 9, 10, 11, 12,
12, 12, 13, 13, 14, 14, 15, 15, 16, 16,
17
}
@@ -354,8 +367,7 @@ function MarathonA3Game:getAggregateGrade()
return math.min(
self.section_cool_grade +
math.floor(self.roll_points / 100) +
grade_conversion[self.grade],
self.roll_frames > 3238 and 32 or 31
grade_conversion[self.grade]
)
end
@@ -403,9 +415,11 @@ MarathonA3Game.mRollOpacityFunction = function(age)
end
function MarathonA3Game:sectionColourFunction(section)
if self.section_status[section] == "cool" then
if self.section_cools[section] == 1 and self.section_regrets[section] == 1 then
return { 1, 1, 0, 1 }
elseif self.section_cools[section] == 1 then
return { 0, 1, 0, 1 }
elseif self.section_status[section] == "regret" then
elseif self.section_regrets[section] == 1 then
return { 1, 0, 0, 1 }
else
return { 1, 1, 1, 1 }

View File

@@ -28,7 +28,6 @@ function PhantomManiaGame:new()
self.combo = 1
self.tetrises = 0
self.section_tetrises = {[0] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
self.section_req = true
self.randomizer = History6RollsRandomizer()
end
@@ -55,7 +54,7 @@ function PhantomManiaGame:getDasLimit()
end
function PhantomManiaGame:getLineClearDelay()
return self:getLineARE()
return self:getLineARE() - 2
end
function PhantomManiaGame:getLockDelay()
@@ -120,11 +119,6 @@ function PhantomManiaGame:onLineClear(cleared_row_count)
self.level = 999
end
self.clear = true
for i = 0, 9 do
if self.section_tetrises[i] < (i == 9 and 1 or 2) then
self.section_req = false
end
end
else
self.level = new_level
end
@@ -175,6 +169,10 @@ local function getLetterGrade(level, clear)
end
end
function PhantomManiaGame:qualifiesForGM()
return true
end
function PhantomManiaGame:drawScoringInfo()
PhantomManiaGame.super.drawScoringInfo(self)
@@ -195,7 +193,7 @@ function PhantomManiaGame:drawScoringInfo()
if getLetterGrade(self.level, self.clear) ~= "" then
if self.roll_frames > 1982 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
if self.level == 999 and self.section_req and self.tetrises >= 31 then
if self.level == 999 and self:qualifiesForGM() then
love.graphics.printf("GM", text_x, 140, 90, "left")
else
love.graphics.printf(getLetterGrade(self.level, self.clear), text_x, 140, 90, "left")

View File

@@ -41,6 +41,7 @@ function PhantomMania2Game:new()
self.coolregret_message = ""
self.coolregret_timer = 0
self.coolregrets = { [0] = 0 }
end
function PhantomMania2Game:getARE()
@@ -202,13 +203,13 @@ end
local cool_cutoffs = {
frameTime(0,36), frameTime(0,36), frameTime(0,36), frameTime(0,36), frameTime(0,36),
frameTime(0,30), frameTime(0,30), frameTime(0,30), frameTime(0,30), frameTime(0,30),
frameTime(0,27), frameTime(0,27), frameTime(0,27),
frameTime(0,30), frameTime(0,30), frameTime(0,30),
}
local regret_cutoffs = {
frameTime(0,50), frameTime(0,50), frameTime(0,50), frameTime(0,50), frameTime(0,50),
frameTime(0,40), frameTime(0,40), frameTime(0,40), frameTime(0,40), frameTime(0,40),
frameTime(0,35), frameTime(0,35), frameTime(0,35),
frameTime(0,42), frameTime(0,42), frameTime(0,42), frameTime(0,42), frameTime(0,42),
frameTime(0,42), frameTime(0,42), frameTime(0,42),
}
function PhantomMania2Game:updateSectionTimes(old_level, new_level)
@@ -219,11 +220,14 @@ function PhantomMania2Game:updateSectionTimes(old_level, new_level)
self.section_start_time = self.frames
if section_time <= cool_cutoffs[section] then
self.grade = self.grade + 2
table.insert(self.coolregrets, 2)
self.coolregret_message = "COOL!!"
self.coolregret_timer = 300
elseif section_time <= regret_cutoffs[section] then
self.grade = self.grade + 1
table.insert(self.coolregrets, 1)
else
table.insert(self.coolregrets, 0)
self.coolregret_message = "REGRET!!"
self.coolregret_timer = 300
end
@@ -292,6 +296,16 @@ function PhantomMania2Game:setHoldOpacity()
end
end
function PhantomMania2Game:sectionColourFunction(section)
if self.coolregrets[section] == 2 then
return { 0, 1, 0, 1 }
elseif self.coolregrets[section] == 0 then
return { 1, 0, 0, 1 }
else
return { 1, 1, 1, 1 }
end
end
function PhantomMania2Game:drawScoringInfo()
PhantomMania2Game.super.drawScoringInfo(self)

View File

@@ -13,4 +13,14 @@ function PhantomManiaNGame:new()
self.enable_hold = true
end
function PhantomManiaNGame:qualifiesForGM()
if self.tetrises < 31 then return false end
for i = 0, 9 do
if self.section_tetrises[i] < (i == 9 and 1 or 2) then
return false
end
end
return true
end
return PhantomManiaNGame

View File

@@ -19,7 +19,7 @@ function SurvivalA2Game:new()
self.roll_frames = 0
self.combo = 1
self.randomizer = History6RollsRandomizer()
self.SGnames = {
"9", "8", "7", "6", "5", "4", "3", "2", "1",
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
@@ -101,6 +101,7 @@ function SurvivalA2Game:onLineClear(cleared_row_count)
self.clear = true
if new_level < 999 then
self.game_over = true
return
end
end
self.level = new_level

View File

@@ -13,7 +13,7 @@ function BagRandomizer:generatePiece()
table.insert(self.bag, v)
end
end
local x = math.random(table.getn(self.bag))
local x = love.math.random(table.getn(self.bag))
return table.remove(self.bag, x)
end

View File

@@ -10,7 +10,7 @@ function Bag7Randomizer:generatePiece()
if next(self.bag) == nil then
self.bag = {"I", "J", "L", "O", "S", "T", "Z"}
end
local x = math.random(table.getn(self.bag))
local x = love.math.random(table.getn(self.bag))
return table.remove(self.bag, x)
end

View File

@@ -1,19 +0,0 @@
-- for the pre-packaged/example challenge tetrs
local Randomizer = require 'tetris.randomizers.randomizer'
local Bag7NoIRandomizer = Randomizer:extend()
function Bag7NoIRandomizer:initialize()
self.bag = {"J", "L", "O", "S", "T", "Z"}
end
function Bag7NoIRandomizer:generatePiece()
if next(self.bag) == nil then
self.bag = {"J", "L", "O", "S", "T", "Z"}
end
local x = math.random(table.getn(self.bag))
return table.remove(self.bag, x)
end
return Bag7NoIRandomizer

View File

@@ -6,7 +6,7 @@ function Bag7NoSZOStartRandomizer:shuffleBag()
local b = self.bag
local ln = #b
for i = 1, ln do
local j = math.random(i, ln)
local j = love.math.random(i, ln)
b[i], b[j] = b[j], b[i]
end
end

View File

@@ -8,8 +8,13 @@ function Sequence:initialize()
end
function Sequence:generatePiece()
local piece = string.sub(self.sequence, self.counter + 1, self.counter + 1)
self.counter = (self.counter + 1) % string.len(self.sequence)
local piece
if type(self.sequence) == "string" then
piece = string.sub(self.sequence, self.counter + 1, self.counter + 1)
else
piece = self.sequence[self.counter + 1]
end
self.counter = (self.counter + 1) % (#self.sequence)
return piece
end

View File

@@ -10,11 +10,11 @@ end
function History4RollsRandomizer:generatePiece()
if self.first then
self.first = false
return self:updateHistory(({"L", "J", "I", "T"})[math.random(4)])
return self:updateHistory(({"L", "J", "I", "T"})[love.math.random(4)])
else
local shapes = {"I", "J", "L", "O", "S", "T", "Z"}
for i = 1, 4 do
local x = math.random(7)
local x = love.math.random(7)
if not inHistory(shapes[x], self.history) or i == 4 then
return self:updateHistory(shapes[x])
end

View File

@@ -10,11 +10,11 @@ end
function History6RollsRandomizer:generatePiece()
if self.first then
self.first = false
return self:updateHistory(({"L", "J", "I", "T"})[math.random(4)])
return self:updateHistory(({"L", "J", "I", "T"})[love.math.random(4)])
else
local shapes = {"I", "J", "L", "O", "S", "T", "Z"}
for i = 1, 6 do
local x = math.random(7)
local x = love.math.random(7)
if not inHistory(shapes[x], self.history) or i == 6 then
return self:updateHistory(shapes[x])
end

View File

@@ -28,12 +28,12 @@ end
function History6Rolls35PoolRandomizer:generatePiece()
local index, x
if self.first then
index = math.random(20)
index = love.math.random(20)
x = self.pool[index]
self.first = false
else
for i = 1, 6 do
index = math.random(#self.pool)
index = love.math.random(#self.pool)
x = self.pool[index]
if not inHistory(x, self.history) or i == 6 then
break

View File

@@ -16,7 +16,7 @@ function Randomizer:initialize()
end
function Randomizer:generatePiece()
return self.possible_pieces[math.random(7)]
return self.possible_pieces[love.math.random(7)]
end
return Randomizer

View File

@@ -0,0 +1,49 @@
local Piece = require 'tetris.components.piece'
local Ruleset = require 'tetris.rulesets.arika_ace2'
local ARS = Ruleset:extend()
ARS.name = "ARS-X"
ARS.hash = "ArikaEXP"
ARS.MANIPULATIONS_MAX = 24
ARS.ROTATIONS_MAX = 12
function ARS:onPieceCreate(piece, grid)
piece.manipulations = 0
piece.rotations = 0
piece.lowest_y = -math.huge
end
function ARS:checkNewLow(piece)
for _, block in pairs(piece:getBlockOffsets()) do
local y = piece.position.y + block.y
if y > piece.lowest_y then
piece.manipulations = 0
piece.rotations = 0
piece.lowest_y = y
end
end
end
function ARS:onPieceMove(piece, grid)
piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= ARS.MANIPULATIONS_MAX then
piece.locked = true
end
end
end
function ARS:onPieceRotate(piece, grid, upward)
piece.lock_delay = 0 -- rotate reset
if upward or piece:isDropBlocked(grid) then
piece.rotations = piece.rotations + 1
if piece.rotations >= ARS.ROTATIONS_MAX and piece:isDropBlocked(grid) then
piece.locked = true
end
end
end
return ARS

View File

@@ -209,7 +209,7 @@ function Ruleset:initializePiece(
local colours
if table.equalvalues(
self.colourscheme, {"I", "J", "L", "O", "S", "T", "Z"}
table.keys(self.colourscheme), {"I", "J", "L", "O", "S", "T", "Z"}
) then
colours = ({self.colourscheme, ColourSchemes.Arika, ColourSchemes.TTC})[config.gamesettings.piece_colour]
else

View File

@@ -1,5 +1,5 @@
local Piece = require 'tetris.components.piece'
local Ruleset = require 'tetris.rulesets.standard_exp'
local Ruleset = require 'tetris.rulesets.standard_ace'
local SRS = Ruleset:extend()
@@ -8,6 +8,8 @@ SRS.hash = "Standard"
SRS.softdrop_lock = false
SRS.harddrop_lock = true
SRS.enable_IRS_wallkicks = true
SRS.MANIPULATIONS_MAX = 15
SRS.wallkicks_line = {
@@ -33,8 +35,8 @@ SRS.wallkicks_line = {
},
};
function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
local kicks
if piece.shape == "O" then
return
@@ -69,6 +71,12 @@ function SRS:checkNewLow(piece)
end
end
function SRS:onPieceCreate(piece, grid)
piece.manipulations = 0
piece.rotations = 0
piece.lowest_y = -math.huge
end
function SRS:onPieceDrop(piece, grid)
self:checkNewLow(piece)
if piece.manipulations >= self.MANIPULATIONS_MAX and piece:isDropBlocked(grid) then
@@ -102,4 +110,6 @@ end
function SRS:canPieceRotate() return true end
function SRS:get180RotationValue() return 2 end
return SRS

View File

@@ -1,5 +1,5 @@
local Piece = require 'tetris.components.piece'
local Ruleset = require 'tetris.rulesets.ti_srs'
local Ruleset = require 'tetris.rulesets.standard_ti'
local SRS = Ruleset:extend()

View File

@@ -1,5 +1,5 @@
local Piece = require 'tetris.components.piece'
local Ruleset = require 'tetris.rulesets.arika_srs'
local Ruleset = require 'tetris.rulesets.standard_ti'
local SRS = Ruleset:extend()
@@ -27,8 +27,6 @@ function SRS:checkNewLow(piece)
for _, block in pairs(piece:getBlockOffsets()) do
local y = piece.position.y + block.y
if y > piece.lowest_y then
--piece.manipulations = 0
--piece.rotations = 0
piece.lowest_y = y
end
end