Compare commits

...

89 Commits

Author SHA1 Message Date
Ishaan Bhardwaj
52d4aeb3d0 Merge branch 'Tetro48-replay-qol' 2023-07-10 20:51:02 -04:00
Ishaan Bhardwaj
91279c9f38 Merge branch 'replay-qol' of https://github.com/Tetro48/cambridge into Tetro48-replay-qol 2023-07-10 20:50:44 -04:00
Ishaan Bhardwaj
0572803627 Fixed a slight indentation error 2023-07-09 22:35:07 -04:00
Tetro48
1fef7b4880 Added replay fast-forwarding 2023-07-10 08:58:09 +07:00
Ishaan Bhardwaj
e09b044de4 Merge pull request #71 from Kirby703/patch-13
fix lategame levelling bug
2023-07-09 20:45:35 -04:00
Kirby703
7d6f783c40 fix lategame levelling bug 2023-07-09 20:06:27 -04:00
Ishaan Bhardwaj
9d365f61a7 Merge pull request #70 from Kirby703/patch-12
fix duplicate cool at 2000
2023-07-09 15:44:04 -04:00
Kirby703
082697c3cd fix duplicate cool at 2000
now you have to survive the roll for gm! terrifying
2023-07-09 15:41:19 -04:00
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
47 changed files with 835 additions and 170 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
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

@@ -26,13 +26,13 @@ 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 .
@@ -42,7 +42,7 @@ Then, check the mod pack section at the bottom of this page.
### 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
@@ -102,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

@@ -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

View File

@@ -1 +1 @@
version = "v0.3"
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"
@@ -24,6 +23,11 @@ function love.load()
-- 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
@@ -79,7 +85,7 @@ function love.draw()
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
@@ -116,7 +122,7 @@ 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
@@ -287,8 +293,12 @@ function love.resize(w, h)
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
@@ -297,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()
@@ -318,24 +328,39 @@ function love.run()
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
end
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
time_accumulator = time_accumulator + time - last_time
end
time_accumulator = time_accumulator - frame_duration
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

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.

View File

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

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()

130
scene/replay.lua Normal file
View File

@@ -0,0 +1,130 @@
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
self.replay_speed = 1
DiscordRPC:update({
details = "Viewing a replay",
state = self.game.name,
largeImageKey = "ingame-"..self.game:getBackground().."00"
})
end
function ReplayScene:update()
local frames_left = self.replay_speed
if not self.paused then
while frames_left > 0 do
frames_left = frames_left - 1
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()
end
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")
local pauses_y_coordinate = 23
if self.replay_speed > 1 then
pauses_y_coordinate = pauses_y_coordinate + 20
love.graphics.printf(self.replay_speed.."X", 0, 20, 635, "right")
end
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, pauses_y_coordinate, 635, "right")
end
else
love.graphics.printf("?? PAUSES (--:--.--)", 0, pauses_y_coordinate, 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
elseif e.input == "left" then
self.replay_speed = self.replay_speed - 1
if self.replay_speed < 1 then
self.replay_speed = 1
end
elseif e.input == "right" then
self.replay_speed = self.replay_speed + 1
if self.replay_speed > 99 then
self.replay_speed = 99
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,6 +5,7 @@ TitleScene.restart_message = false
local main_menu_screens = {
ModeSelectScene,
ReplaySelectScene,
SettingsScene,
CreditsScene,
ExitScene,
@@ -43,9 +44,9 @@ 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
})
end
@@ -59,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"],
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(
backgrounds["title_night"],
0, 0, 0,
0.5, 0.5
)
love.graphics.draw(
misc_graphics["icon"],
460, 170, 0,
blocks["2tie"][b.color],
490 + b.x, 192 + b.y, 0,
2, 2
)
love.graphics.printf("Thanks for 1 year!", 430, 280, 160, "center")
end
--[[
love.graphics.draw(
misc_graphics["icon"],
490, 192, 0,
2, 2
)
]]
--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)
@@ -125,8 +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()
-- no winter easter egg for now
--[[
else
self.text = self.text .. (e.scancode or "")
if self.text == "ffffff" then
@@ -135,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

@@ -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
-- 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
@@ -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,9 +673,16 @@ 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),
alpha,
skin, colour,
48 + x * 16, y * 16
}
@@ -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 {
@@ -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

@@ -283,7 +283,7 @@ function Marathon2020Game:sectionPassed(old_level, new_level)
)
else
return (
(new_level < 2001 and math.floor(old_level / 100) < math.floor(new_level / 100)) or
(new_level < 2000 and math.floor(old_level / 100) < math.floor(new_level / 100)) or
(new_level >= 2020)
)
end
@@ -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,12 +249,15 @@ local grade_conversion = {
}
function MarathonA2Game:whilePieceActive()
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
function MarathonA2Game:updateGrade(cleared_lines)
if self.clear or cleared_lines == 0 then return

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

@@ -143,7 +143,7 @@ end
function Survival2020Game:onPieceEnter()
if not self.clear and (
(self.level < 1900 and self.level % 100 ~= 99) or
self.level == 2019
(1900 <= self.level and self.level < 2019)
) then
self.level = self.level + 1
end
@@ -249,7 +249,7 @@ function Survival2020Game:drawScoringInfo()
end
function Survival2020Game:getBackground()
return math.floor(self.level / 100)
return math.min(19, math.floor(self.level / 100))
end
function Survival2020Game:getHighscoreData()

View File

@@ -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

@@ -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