Compare commits

...

139 Commits

Author SHA1 Message Date
Ishaan Bhardwaj
3b0fdba27d Updated README.md to have the updated mod pack 2020-11-12 09:45:48 -05:00
Ishaan Bhardwaj
d8fad3dc37 Added some more developer functions, to aid in building modes. 2020-11-11 22:30:30 -05:00
Ishaan Bhardwaj
f9368fa806 Forgot to add a contributor, whoops... 2020-11-11 11:11:55 -05:00
Ishaan Bhardwaj
189feb1802 Fixed the last of the hard drop safelocks... 2020-11-10 23:13:25 -05:00
Ishaan Bhardwaj
dc09dabacb Fixed Phantom Mania safelock behaviors 2020-11-10 23:10:39 -05:00
Ishaan Bhardwaj
e13278c6a8 Fixed safelock behavior for hard drop modes 2020-11-10 22:34:48 -05:00
Ishaan Bhardwaj
f7f11b0e22 Updated README.md with new Windows instructions 2020-11-10 21:41:34 -05:00
Ishaan Bhardwaj
869a0f7ec5 Added more input failsafes 2020-11-10 21:26:19 -05:00
Ishaan Bhardwaj
10a9d97848 Removed modes from core game to create modpack. Check README 2020-11-10 21:16:17 -05:00
Ishaan Bhardwaj
a470b40def Refactored input handling, so that arrow keys will always work on menus. 2020-11-10 20:08:34 -05:00
nightmareci
fd739dcfdf Changed reserved keys (arrows are no longer reserved) and now F2 always gets to the input config except when in-game. 2020-11-10 14:37:59 -08:00
Ishaan Bhardwaj
e1dc01d0d0 Changed the rotation game setting to be less ambiguous 2020-11-10 16:10:10 -05:00
Ishaan Bhardwaj
7228707241 Changed the way color override works on bone blocks 2020-11-10 16:03:30 -05:00
Ishaan Bhardwaj
af86ce3a98 Updated README.md 2020-11-10 13:41:23 -05:00
Ishaan Bhardwaj
78ae0ae671 Fixed the piece lock SFX where applicable, in modes that had their own lock functions. 2020-11-09 23:22:28 -05:00
Ishaan Bhardwaj
c614e9c4cd Fixed safelock behavior on modes that needed it. 2020-11-09 23:16:28 -05:00
Ishaan Bhardwaj
b27ef0e9f4 Made it so that in TGM3, you can only get GM by clearing the roll, and fixed hold sound in Phantom Mania 2 2020-11-09 23:04:50 -05:00
Joe Zeng
843b1e108a Added safe-lock back to Survival CK. 2020-11-09 21:13:07 -05:00
Brandon McGriff
a8d697064c Merge branch 'master' of https://github.com/SashLilac/cambridge 2020-11-09 16:09:39 -08:00
Brandon McGriff
cf32474898 Fixed libs/discordRPC.lua so Discord RPC loads on Linux.
I changed how the library was loaded before, but turns out that way only
worked on Windows. Changed it back to how it was, so it works on Linux
for me, and presumably macOS.
2020-11-09 16:06:09 -08:00
Brandon McGriff
6a295cad59 Added Windows batch files for automated packaging of fused Windows packages.
They require the tar utility, but that's included in the latest versions of Windows 10. The utility is available for installation on other versions of Windows.

I also found out how to get the Discord RPC library to load with all ways of running the game, but that required changing libs/discordRPC.lua. Binary libraries can only be loaded from the filesystem, outside of a .love archive or fused executable.
2020-11-09 15:51:00 -08:00
Ishaan Bhardwaj
2d80e20c82 Updated README.md with more new contributors 2020-11-09 11:47:21 -05:00
Ishaan Bhardwaj
2279c24d11 Closed an if in the previous fix 2020-11-08 23:23:26 -05:00
Ishaan Bhardwaj
8510ad9bea Fixed Phantom Mania 2 roll grade points. 2020-11-08 23:19:21 -05:00
Joe Zeng
6b77ad8547 Merge pull request #8 from nightmareci/master
Implemented joystick input.
2020-11-08 17:17:30 -05:00
nightmareci
6834e92674 Changed indentation to hard tabs. 2020-11-08 13:19:01 -08:00
nightmareci
3479374686 Forgot that the code used "enter" instead of "return" in the input config scene before, so changed it back. 2020-11-08 13:06:29 -08:00
nightmareci
863c614a4c Implemented joystick input.
I had to redo how input is done entirely, so more than one source of input can be used for game inputs.

I added new inputs, menu_decide and menu_back. Return and escape still have their reserved status, sending menu_decide and menu_back, respectively. Other keys are reserved too, like arrows, to ensure users can always reconfigure input.
2020-11-08 12:55:06 -08:00
Joe Zeng
49e52c6a39 Merge pull request #4 from Rexxt/master
Small updates to visuals and sound
2020-11-07 14:31:17 -05:00
Joe Zeng
a105086ca6 Fixed the garbage pausing problem in Phantom Mania 2. 2020-11-07 01:31:09 -05:00
Joe Zeng
1b381c4bf3 Might as well add the dark square while I'm at it. 2020-11-07 01:29:44 -05:00
Joe Zeng
91a87fea73 Fixed the garbage pausing problem in Phantom Mania 2. 2020-11-07 01:12:13 -05:00
Ishaan Bhardwaj
28b455fcc0 Updated README.md with new contributors 2020-11-06 20:57:01 -05:00
Joe Zeng
2e3eff025f Replaced spaces with tabs.
Check CONTRIBUTING.md, guys!
2020-11-06 20:54:14 -05:00
Joe Zeng
4670cb7c15 Added the ability to _always_ reverse rotation. 2020-11-06 20:46:36 -05:00
Joe Zeng
9b04e14388 Added "Always reverse" option for rotation reversal. 2020-11-06 19:17:32 -05:00
Ishaan Bhardwaj
f52da36bf7 TGM3 cool system change 2020-11-06 14:01:28 -05:00
Ishaan Bhardwaj
76142c1dff More Green Orange line 2020-11-05 16:21:58 -05:00
Ishaan Bhardwaj
a3458e2413 Fix Arika SRS 2020-11-04 22:46:45 -05:00
Ishaan Bhardwaj
7eba9c012f Merge branch 'master' of https://github.com/sashlilac/cambridge 2020-11-04 16:58:11 -05:00
Ishaan Bhardwaj
4d2868b7b6 Small changes. 2020-11-04 16:57:44 -05:00
Ishaan Bhardwaj
2e6fcd232b Update README.md 2020-11-04 08:42:42 -05:00
Ishaan Bhardwaj
10833f2ec1 Score drain changes 2020-11-03 23:20:41 -05:00
Ishaan Bhardwaj
abb2b9491e Preparing for v0.2.1 2020-11-03 23:04:47 -05:00
Ishaan Bhardwaj
062ab2005e v0.2 release commit - hold piece darken 2020-11-03 16:56:08 -05:00
Ishaan Bhardwaj
468025fc80 last commit to core modes before release 2020-11-03 12:17:36 -05:00
Ishaan Bhardwaj
c8544975d6 Fix interval training 2020-11-03 11:55:30 -05:00
Ishaan Bhardwaj
6776229bfb Small push to Cambridge modes 2020-11-03 11:52:52 -05:00
Ishaan Bhardwaj
84b4dc5073 World Bone Blocks 2020-11-03 10:58:21 -05:00
Ishaan Bhardwaj
35dafb6615 keep leaving debug code in new commits 2020-11-02 22:51:16 -05:00
Ishaan Bhardwaj
3641d85fcb Major changes, including modpack support 2020-11-02 22:47:58 -05:00
Ishaan Bhardwaj
9b89c4d1de G/O line fix 2020-11-02 21:17:13 -05:00
Ishaan Bhardwaj
2dba120919 Green line and orange line for TAP Master 2020-11-02 20:43:10 -05:00
Ishaan Bhardwaj
9224f271b1 Hotfix for last 2020-11-02 16:20:22 -05:00
Ishaan Bhardwaj
febb5d546c Score overhauls 2020-11-02 16:12:05 -05:00
Ishaan Bhardwaj
c6482c423e 4w optimization and green/orange line adding for applicable modes 2020-11-02 13:46:16 -05:00
Ishaan Bhardwaj
6beb313c6b Debug fixes 2020-11-02 12:44:15 -05:00
Ishaan Bhardwaj
eb70f55b6e TGM2 fixes and cool fixes 2020-11-02 12:21:12 -05:00
Ishaan Bhardwaj
0badcde9ad Basset: the only person to leave debug code in a repo 2020-11-01 13:44:35 -05:00
Ishaan Bhardwaj
6f39b591d3 Hotfix for TGM+ 2020-11-01 13:28:13 -05:00
Ishaan Bhardwaj
129237f0b0 TGM+ 2020-11-01 13:24:52 -05:00
Ishaan Bhardwaj
741c246244 second lol 2020-11-01 12:04:07 -05:00
Ishaan Bhardwaj
b5937af8b2 lol 2020-11-01 12:01:26 -05:00
Ishaan Bhardwaj
33b8533d8e Fixes to TAP M-roll requirements 2020-11-01 11:06:43 -05:00
Ishaan Bhardwaj
69959ff687 TA Death level advance formula is very bugged 2020-10-30 21:36:05 -04:00
Ishaan Bhardwaj
f91cd99dfd Minor fixes to TGM modes 2020-10-30 21:28:39 -04:00
Ishaan Bhardwaj
be59727ca5 Some demon mode fixes 2020-10-30 13:09:49 -04:00
Ishaan Bhardwaj
cca295066c Fix. 2020-10-29 23:05:49 -04:00
Ishaan Bhardwaj
f2862b4d93 Some score fixes for core TGM modes 2020-10-29 23:03:54 -04:00
Ishaan Bhardwaj
2aafd30253 Fixed secret grade detection 2020-10-29 22:14:34 -04:00
Ishaan Bhardwaj
b27ba335ba Improved secret grade 2020-10-29 21:40:50 -04:00
Ishaan Bhardwaj
33244736b8 Secret grade for sprint? Also ARR and DAS change so remember to change it in the lua file 2020-10-29 20:56:18 -04:00
Mizu
a324e0015a Replace SFX and add hold 2020-10-27 12:17:00 +01:00
Ishaan Bhardwaj
285108ca08 ACTUALLY fixed TI Master torikan 2020-10-26 14:07:09 -04:00
Ishaan Bhardwaj
4b1fed727c Update marathon_a3.lua 2020-10-26 14:03:09 -04:00
Mizu
d38168ca00 Updated title screen background 2020-10-26 16:57:20 +01:00
Mizu
b0ce0f17f5 Add new sound effects to the game 2020-10-26 14:21:49 +01:00
Ishaan Bhardwaj
9fca272e8d Update ck.lua 2020-10-24 09:12:12 -04:00
Ishaan Bhardwaj
5a21c8244b Update README.md 2020-10-23 21:28:32 -04:00
Ishaan Bhardwaj
4923b2e2d4 Removed debug code 2020-10-23 21:23:45 -04:00
Ishaan Bhardwaj
8810f24e7a v0.1.8, SHIRASE-CK ADDEDgit add scene/mode_select.lua tetris/modes/ck.lua ! 2020-10-23 21:02:27 -04:00
Ishaan Bhardwaj
57a9f6ef55 Fixing Demon Mode backgrounds 2020-10-23 14:52:59 -04:00
Oshisaure
342036bc28 Fixed guideline SRS anti-stalling to work closer to guideline games
Currenly behaves similarly to Tetris Friends, lock as soon as it can after exceeding the manipulation limit.
That still allows to have a piece in the air forever in low G though, might be worth looking into it?
2020-10-21 05:30:28 +01:00
Oshisaure
78dcfe43c4 Made Survival A2/A3 torikans stop your game instead of giving an end roll 2020-10-21 05:24:11 +01:00
Ishaan Bhardwaj
cdf6b5cf33 TI Master torikan 2020-10-20 23:14:25 -04:00
Oshisaure
e6a60b0021 Merge branch 'master' of https://github.com/SashLilac/cambridge into master 2020-10-19 16:54:27 +01:00
Oshisaure
5f29c987f2 Tweaked Cambridge RS to fix how S and Z show in the next queue 2020-10-19 16:50:49 +01:00
Ishaan Bhardwaj
608d75b1ac Update README.md 2020-10-19 08:45:22 -04:00
Oshisaure
1427c0d19e Updated Ti-World and ACE-SRS to use the Arika kick table
This possibly needs further testing, although the example
given [on the wiki](tetris.wiki/SRS#Arika_SRS) works
2020-10-19 05:07:31 +01:00
Ishaan Bhardwaj
e221a91d73 Swapped some settings around 2020-10-18 23:32:57 -04:00
Oshisaure
bdcd25b82c Fixed T floorkick in ARS Ti/ACE/ACE2 2020-10-19 04:19:36 +01:00
Ishaan Bhardwaj
a5158e0994 Fixed Demon Mode torikan madness 2020-10-18 21:54:24 -04:00
Oshisaure
d946b17e13 Minor change on PR #3 to use the error handling that was already implemented 2020-10-18 00:40:14 +01:00
Oshisaure
69a5c0a21a Merge pull request #3 from MyPasswordIsWeak/master
Fixed discord rpc not working on linux
2020-10-18 00:12:50 +01:00
MyPasswordIsWeak
b6423c3335 Change tabs to spaces for consistency 2020-10-17 21:17:49 +02:00
MyPasswordIsWeak
5b960d7291 Change single quotes to double quotes 2020-10-17 21:15:06 +02:00
MyPasswordIsWeak
54f4b0b890 Fixed rpc not working on linux 2020-10-17 21:11:38 +02:00
Oshisaure
8c62f321a0 Added secret grade for the Phantom Mania/-2/-N modes 2020-10-17 04:22:41 +01:00
Oshisaure
fdffd2cd9a Actually made ACE-ARS2 selectable 2020-10-17 04:17:25 +01:00
Oshisaure
8ddf468121 Removed print statements leftover from when i was making sure the torikans were working as intended 2020-10-17 03:42:31 +01:00
Oshisaure
8e77407ff2 Merge branch 'master' of https://github.com/SashLilac/cambridge 2020-10-17 03:38:07 +01:00
Oshisaure
92c852d178 Renamed ACE-ARS to ACE-ARS2 and added ACE-ARS that guideline piece lock and colours 2020-10-17 03:37:44 +01:00
Ishaan Bhardwaj
f658ed63f2 apparently I made ti-srs too strict 2020-10-16 22:20:18 -04:00
Oshisaure
c2d1c1183c Survival A3 torikan now switches to 03:03:00 when playing with World-type rulesets 2020-10-17 02:51:29 +01:00
Ishaan Bhardwaj
36c568feaf demon mode grade display fixed 2020-10-16 21:35:30 -04:00
Ishaan Bhardwaj
bf30fcefbd Add Sega randomizer 2020-10-14 15:00:17 -04:00
Oshisaure
d9f5bd16d7 Disabled BGM for now since the only music is the pacer test (not selectable in game) and the credit roll 2020-10-14 19:30:37 +01:00
Ishaan Bhardwaj
d978ff8d87 fixed a thing 2020-10-14 13:45:56 -04:00
Ishaan Bhardwaj
b47d0f36b9 oshi forced me to add bags 2020-10-14 13:43:28 -04:00
Ishaan Bhardwaj
abc210c69c made our debug randomizer not bad 2020-10-13 22:10:27 -04:00
Ishaan Bhardwaj
436e4ac861 Update README.md 2020-10-13 15:56:11 -04:00
Oshisaure
a48d7c67b5 Merge branch 'HEAD' of https://github.com/SashLilac/cambridge.git 2020-10-13 20:26:33 +01:00
Oshisaure
f6ca79ff91 Fixed big mode spawn positions 2020-10-13 20:26:07 +01:00
Ishaan Bhardwaj
4eb3901610 Some big mode fixes 2020-10-13 14:16:23 -04:00
Ishaan Bhardwaj
3f7fc4b622 Rename Mac RPC lib 2020-10-13 11:20:46 -04:00
Ishaan Bhardwaj
ac7ae91c39 Small RPC change 2020-10-12 22:44:47 -04:00
Oshisaure
0c8e910245 Linux RPC maybe? 2020-10-13 01:23:24 +01:00
Oshisaure
6233ffb12d Fixed RPC icon and included RPC lib for mac 2020-10-13 00:39:13 +01:00
Oshisaure
1f78bb9e99 Merge branch 'HEAD' of https://github.com/SashLilac/cambridge.git 2020-10-12 21:22:15 +01:00
Oshisaure
a125c09106 Fixed crash on loading the game with no save 2020-10-12 21:21:10 +01:00
Ishaan Bhardwaj
090ffa5126 Update README.md 2020-10-12 14:48:00 -04:00
Ishaan Bhardwaj
12a6f42198 Revert "Fixing step reset" - didn't realize infinite floorkicks
This reverts commit 0c317d9ce1.
2020-10-11 15:46:34 -04:00
Ishaan Bhardwaj
0c317d9ce1 Fixing step reset 2020-10-11 15:41:56 -04:00
Ishaan Bhardwaj
eddfee566d Grade display changed for TA Death modes 2020-10-11 15:34:10 -04:00
Ishaan Bhardwaj
7fe366a8de Bravo score update 2020-10-11 14:17:18 -04:00
Ishaan Bhardwaj
55be30c99f experimental bravo formula for tgm1 2020-10-11 13:21:03 -04:00
Ishaan Bhardwaj
36ceef8488 experimental bravo formula for tgm1 2020-10-11 12:57:57 -04:00
Oshisaure
b59edb5e8e Accidentally swapped blue and orange in the colour scheme update, changing it back with this commit 2020-10-11 03:12:22 +01:00
Oshisaure
5d32b6a3e7 Discord RPC cleanup
- Loading Discord RPC is now handled by `load/rpc.lua`
- Removed `presence` global, call `DiscordRPC:update()` directly with what needs updating
- Game doesn't crash anymore if the Discord RPC fails to load
- Added RPC variables in the gamemode superclass to let each gamemode handle its special case
2020-10-11 02:17:48 +01:00
Oshisaure
05230ac046 Game settings screen, and minor fix on discordRPC
- Uses BG previously from the input config screen, which has gotten a new BG
- Minor tweak on the input config screen to display all inputs names regardless of if they are bound or not
- Added Mod1 function to `funcs.lua`, may be useful again sometime
- Added game settings
  * Manual locking (per gamemode, per ruleset, on harddrop or on softdrop)
  * Piece colours (per ruleset, TTC or Arika)
  * World Reverse toggle
- Moved the discordRPC `libs/` directory, as it's a third party library
- Edited the `discordRPC.lua` file to look for the dll at the right place regardless of how you run the game (until we fuse it that is)

This should have probably been done in several commits, sorry about that
2020-10-11 00:42:56 +01:00
Oshisaure
f28dc08ae2 Updated Race 40 randomiser to use 7-bag no SZO start 2020-10-10 23:07:12 +01:00
Ishaan Bhardwaj
ecd958bdc5 Update README.md 2020-10-10 10:30:31 -04:00
Ishaan Bhardwaj
43f59cfde8 Merge pull request #1 from haileylgbt/master
Added fitting menu sfx + RPC
2020-10-09 21:37:45 -04:00
Ishaan Bhardwaj
00c46961f9 Bug. 2020-10-09 21:31:49 -04:00
Hailey
d0f1d869a8 RP Icon 2020-10-10 09:47:33 +10:00
Hailey
29ee000998 Minor Rich Presence adjustments 2020-10-10 09:05:59 +10:00
Hailey
995fd7fee9 rich presence!! 2020-10-10 08:43:22 +10:00
Hailey
67abf35a28 Merge pull request #1 from SashLilac/master
Removed the BG limit, because someone is a madman
2020-10-10 08:02:00 +10:00
Hailey
629beb7240 Added fitting menu sfx 2020-10-10 07:50:05 +10:00
83 changed files with 2415 additions and 2945 deletions

2
.gitignore vendored
View File

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

View File

@@ -1,41 +1,59 @@
![Cambridge Banner](https://cdn.discordapp.com/attachments/764432435802013709/767724895076614154/cambridge_logo_lt.png)
Cambridge
=========
Welcome to Cambridge, the next open-source falling-block game engine!
This fork is written and maintained exclusively by [SashLilac](https://github.com/SashLilac) and [Oshisaure](https://github.com/oshisaure)!
This fork is written and maintained exclusively by [SashLilac](https://github.com/SashLilac), [joezeng](https://github.com/joezeng) and [Oshisaure](https://github.com/oshisaure)!
Join our Discord server for help and a welcoming community! https://discord.gg/mteMJw4
Credits
-------
- [Lilla Oshisaure](https://www.youtube.com/user/LeSpyroshisaure) for their amazing contributions to my life in general!
- [Lilla Oshisaure](https://www.youtube.com/user/LeSpyroshisaure) for being my co-dev!
- [joezeng](https://github.com/joezeng) for the original project, and for offering to help with the expansion!
- [The Tetra Legends Discord](http://discord.com/invite/7hMx5r2) for supporting me and playtesting!
- [joezeng](https://github.com/joezeng) for the original project.
- [The Absolute Plus](https://discord.gg/6Gf2awJ) for being another source of motivation!
Installation instructions
-------------------------
The following people in no particular order also helped with the project:
- [Hailey](https://github.com/haileylgbt)
- CylinderKnot
- MarkGamed7794
- [Mizu](https://github.com/rexxt)
- MattMayuga
- Kitaru
- switchpalacecorner
- [sinefuse](https://github.com/sinefuse)
- [2Tie](https://github.com/2Tie)
- [nightmareci](https://github.com/nightmareci)
- [MyPasswordIsWeak](https://github.com/MyPasswordIsWeak)
Pre-built releases are available on the releases page.
![Cambridge Logo](https://cdn.discordapp.com/attachments/625496179433668635/763363717730664458/Icon_2.png)
Playing the game
----------------
### Windows
Unzip the exe file and run it directly. All assets are currently bundled inside the executable.
You do not need LÖVE on Windows, as it comes bundled with the program.
### macOS
To get the stable release, simply download the ZIP in the latest release. All assets needed are bundled with the executable.
For the time being, the file `cambridge.love` only works on the command line. Install `love` with [Homebrew](https://brew.sh), and run:
If you want the bleeding edge version, or want mod pack support, download [this](https://github.com/SashLilac/cambridge/archive/master.zip).
$ love cambridge.love
Extract the ZIP, open a Command Prompt at the folder you extracted Cambridge to, then run this command:
### Linux
dist\windows\love.exe .
Same as macOS, except install `love` with your favourite package manager.
Alternatively, if you're on a 32-bit system, run this instead:
dist\win32\love.exe .
Running from source
-------------------
32-bit systems do not support rich presence integration.
If you want the bleeding-edge release, you can also clone the code straight from this repository.
Then, check the mod pack section at the bottom of this page.
### macOS, Linux
@@ -45,12 +63,19 @@ Clone the repository in git:
git clone https://github.com/SashLilac/cambridge
Alternatively, download the source code ZIP in the latest release.
Then, navigate to the root directory that you just cloned, and type:
love .
It should run automatically!
## Installing modpacks
Simply drag your mode, ruleset, and randomizer Lua files into their respective directory, and they should appear automatically.
Alternatively, install [this](https://github.com/SashLilac/cambridge-modpack) mod pack to get a taste of the mod potential.
License
-------

11
clean.bat Normal file
View File

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

View File

@@ -62,3 +62,8 @@ function formatBigNum(number)
return string.sub(s, 1, pos)
.. string.gsub(string.sub(s, pos+1), "(...)", ",%1")
end
function Mod1(n, m)
-- returns a number congruent to n modulo m in the range [1;m] (as opposed to [0;m-1])
return ((n-1) % m) + 1
end

BIN
libs/discord-rpc.dll Normal file

Binary file not shown.

BIN
libs/discord-rpc.dylib Normal file

Binary file not shown.

BIN
libs/discord-rpc.so Normal file

Binary file not shown.

282
libs/discordRPC.lua Normal file
View File

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

View File

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

View File

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

58
load/rpc.lua Normal file
View File

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

View File

@@ -10,11 +10,21 @@ sounds = {
},
move = love.audio.newSource("res/se/move.wav", "static"),
bottom = love.audio.newSource("res/se/bottom.wav", "static"),
cursor = love.audio.newSource("res/se/cursor.wav", "static"),
cursor_lr = love.audio.newSource("res/se/cursor_lr.wav", "static"),
main_decide = love.audio.newSource("res/se/main_decide.wav", "static"),
mode_decide = love.audio.newSource("res/se/mode_decide.wav", "static"),
lock = love.audio.newSource("res/se/lock.wav", "static"),
hold = love.audio.newSource("res/se/hold.wav", "static"),
erase = love.audio.newSource("res/se/erase.wav", "static"),
fall = love.audio.newSource("res/se/fall.wav", "static"),
ready = love.audio.newSource("res/se/ready.wav", "static"),
go = love.audio.newSource("res/se/go.wav", "static"),
}
function playSE(sound, subsound)
if subsound == nil then
sounds[sound]:setVolume(0.1)
sounds[sound]:setVolume(0.5)
if sounds[sound]:isPlaying() then
sounds[sound]:stop()
end
@@ -27,3 +37,19 @@ function playSE(sound, subsound)
sounds[sound][subsound]:play()
end
end
function playSEOnce(sound, subsound)
if subsound == nil then
sounds[sound]:setVolume(0.5)
if sounds[sound]:isPlaying() then
return
end
sounds[sound]:play()
else
sounds[sound][subsound]:setVolume(0.5)
if sounds[sound][subsound]:isPlaying() then
return
end
sounds[sound][subsound]:play()
end
end

146
main.lua
View File

@@ -1,6 +1,7 @@
function love.load()
math.randomseed(os.time())
highscores = {}
require "load.rpc"
require "load.graphics"
require "load.fonts"
require "load.sounds"
@@ -14,14 +15,42 @@ function love.load()
love.window.setMode(love.graphics.getWidth(), love.graphics.getHeight(), {resizable = true});
if not config.gamesettings then config.gamesettings = {} end
for _, option in ipairs(GameConfigScene.options) do
if not config.gamesettings[option[1]] then
config.gamesettings[option[1]] = 1
end
end
if not config.input then
config.input = {}
scene = InputConfigScene()
else
if config.current_mode then current_mode = config.current_mode end
if config.current_ruleset then current_ruleset = config.current_ruleset end
scene = TitleScene()
end
game_modes = {}
mode_list = love.filesystem.getDirectoryItems("tetris/modes")
for i=1,#mode_list do
if(mode_list[i] ~= "gamemode.lua" and mode_list[i] ~= "unrefactored_modes") then
game_modes[#game_modes+1] = require ("tetris.modes."..string.sub(mode_list[i],1,-5))
end
end
rulesets = {}
rule_list = love.filesystem.getDirectoryItems("tetris/rulesets")
for i=1,#rule_list do
if(rule_list[i] ~= "ruleset.lua" and rule_list[i] ~= "unrefactored_rulesets") then
rulesets[#rulesets+1] = require ("tetris.rulesets."..string.sub(rule_list[i],1,-5))
end
end
--sort mode/rule lists
local function padnum(d) return ("%03d%s"):format(#d, d) end
table.sort(game_modes, function(a,b)
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end)
table.sort(rulesets, function(a,b)
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end)
end
local TARGET_FPS = 60
@@ -80,13 +109,124 @@ function love.draw()
love.graphics.pop()
end
function love.keypressed(key, scancode, isrepeat)
function love.keypressed(key, scancode)
-- global hotkeys
if scancode == "f4" then
config["fullscreen"] = not config["fullscreen"]
love.window.setFullscreen(config["fullscreen"])
elseif scancode == "f2" and scene.title ~= "Input Config" and scene.title ~= "Game" then
scene = InputConfigScene()
-- function keys are reserved
elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f[1-9][0-9]+$") then
return
-- escape is reserved for menu_back
elseif scancode == "escape" then
scene:onInputPress({input="menu_back", type="key", key=key, scancode=scancode})
-- pass any other key to the scene, with its configured mapping
else
scene:onKeyPress({key=key, scancode=scancode, isRepeat=isrepeat})
local input_pressed = nil
if config.input and config.input.keys then
input_pressed = config.input.keys[scancode]
end
scene:onInputPress({input=input_pressed, type="key", key=key, scancode=scancode})
end
end
function love.keyreleased(key, scancode)
-- escape is reserved for menu_back
if scancode == "escape" then
scene:onInputRelease({input="menu_back", type="key", key=key, scancode=scancode})
-- function keys are reserved
elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f[1-9][0-9]+$") then
return
-- handle all other keys; tab is reserved, but the input config scene keeps it from getting configured as a game input, so pass tab to the scene here
else
local input_released = nil
if config.input and config.input.keys then
input_released = config.input.keys[scancode]
end
scene:onInputRelease({input=input_released, type="key", key=key, scancode=scancode})
end
end
function love.joystickpressed(joystick, button)
local input_pressed = nil
if
config.input and
config.input.joysticks and
config.input.joysticks[joystick:getName()] and
config.input.joysticks[joystick:getName()].buttons
then
input_pressed = config.input.joysticks[joystick:getName()].buttons[button]
end
scene:onInputPress({input=input_pressed, type="joybutton", name=joystick:getName(), button=button})
end
function love.joystickreleased(joystick, button)
local input_released = nil
if
config.input and
config.input.joysticks and
config.input.joysticks[joystick:getName()] and
config.input.joysticks[joystick:getName()].buttons
then
input_released = config.input.joysticks[joystick:getName()].buttons[button]
end
scene:onInputRelease({input=input_released, type="joybutton", name=joystick:getName(), button=button})
end
function love.joystickaxis(joystick, axis, value)
local input_pressed = nil
local positive_released = nil
local negative_released = nil
if
config.input and
config.input.joysticks and
config.input.joysticks[joystick:getName()] and
config.input.joysticks[joystick:getName()].axes and
config.input.joysticks[joystick:getName()].axes[axis]
then
if math.abs(value) >= 0.5 then
input_pressed = config.input.joysticks[joystick:getName()].axes[axis][value >= 0.5 and "positive" or "negative"]
end
positive_released = config.input.joysticks[joystick:getName()].axes[axis].positive
negative_released = config.input.joysticks[joystick:getName()].axes[axis].negative
end
if math.abs(value) >= 0.5 then
scene:onInputPress({input=input_pressed, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
else
scene:onInputRelease({input=positive_released, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
scene:onInputRelease({input=negative_released, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
end
end
function love.joystickhat(joystick, hat, direction)
local input_pressed = nil
local has_hat = false
if
config.input and
config.input.joysticks and
config.input.joysticks[joystick:getName()] and
config.input.joysticks[joystick:getName()].hats and
config.input.joysticks[joystick:getName()].hats[hat]
then
if direction ~= "c" then
input_pressed = config.input.joysticks[joystick:getName()].hats[hat][direction]
end
has_hat = true
end
if input_pressed then
scene:onInputPress({input=input_pressed, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
elseif has_hat then
for i, direction in ipairs{"d", "l", "ld", "lu", "r", "rd", "ru", "u"} do
scene:onInputRelease({input=config.input.joysticks[joystick:getName()].hats[hat][direction], type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
end
elseif direction ~= "c" then
scene:onInputPress({input=nil, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
else
for i, direction in ipairs{"d", "l", "ld", "lu", "r", "rd", "ru", "u"} do
scene:onInputRelease({input=nil, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
end
end
end

2
package.bat Normal file
View File

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

26
release.bat Normal file
View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
res/img/bonew.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

BIN
res/img/s8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

BIN
res/se/cursor.wav Normal file

Binary file not shown.

BIN
res/se/cursor_lr.wav Normal file

Binary file not shown.

BIN
res/se/erase.wav Normal file

Binary file not shown.

BIN
res/se/fall.wav Normal file

Binary file not shown.

BIN
res/se/go.wav Normal file

Binary file not shown.

BIN
res/se/hold.wav Normal file

Binary file not shown.

BIN
res/se/lock.wav Normal file

Binary file not shown.

BIN
res/se/main_decide.wav Normal file

Binary file not shown.

BIN
res/se/mode_decide.wav Normal file

Binary file not shown.

BIN
res/se/ready.wav Normal file

Binary file not shown.

View File

@@ -5,11 +5,12 @@ Scene = Object:extend()
function Scene:new() end
function Scene:update() end
function Scene:render() end
function Scene:onKeyPress() end
function Scene:onInputPress() end
function Scene:onInputRelease() end
ExitScene = require "scene.exit"
GameScene = require "scene.game"
ModeSelectScene = require "scene.mode_select"
InputConfigScene = require "scene.input_config"
ConfigScene = require "scene.config"
GameConfigScene = require "scene.game_config"
TitleScene = require "scene.title"

View File

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

View File

@@ -16,7 +16,7 @@ end
function ExitScene:changeOption(rel)
end
function ExitScene:onKeyPress(e)
function ExitScene:onInputPress(e)
end
return ExitScene

View File

@@ -1,26 +1,40 @@
local GameScene = Scene:extend()
GameScene.title = "Game"
require 'load.save'
function GameScene:new(game_mode, ruleset)
self.retry_mode = game_mode
self.retry_ruleset = ruleset
self.game = game_mode()
self.ruleset = ruleset()
self.game:initialize(self.ruleset)
self.inputs = {
left=false,
right=false,
up=false,
down=false,
rotate_left=false,
rotate_left2=false,
rotate_right=false,
rotate_right2=false,
rotate_180=false,
hold=false,
}
DiscordRPC:update({
details = self.game.rpc_details,
state = self.game.name,
})
end
function GameScene:update()
if love.window.hasFocus() then
self.game:update({
left = love.keyboard.isScancodeDown(config.input.left),
right = love.keyboard.isScancodeDown(config.input.right),
up = love.keyboard.isScancodeDown(config.input.up),
down = love.keyboard.isScancodeDown(config.input.down),
rotate_left = love.keyboard.isScancodeDown(config.input.rotate_left),
rotate_left2 = love.keyboard.isScancodeDown(config.input.rotate_left2),
rotate_right = love.keyboard.isScancodeDown(config.input.rotate_right),
rotate_right2 = love.keyboard.isScancodeDown(config.input.rotate_right2),
rotate_180 = love.keyboard.isScancodeDown(config.input.rotate_180),
hold = love.keyboard.isScancodeDown(config.input.hold),
}, self.ruleset)
local inputs = {}
for input, value in pairs(self.inputs) do
inputs[input] = value
end
self.game:update(inputs, self.ruleset)
end
self.game.grid:update()
@@ -45,6 +59,7 @@ function GameScene:render()
self.game:drawScoringInfo()
-- ready/go graphics
if self.game.ready_frames <= 100 and self.game.ready_frames > 52 then
love.graphics.draw(misc_graphics["ready"], 144 - 50, 240 - 14)
elseif self.game.ready_frames <= 50 and self.game.ready_frames > 2 then
@@ -55,23 +70,24 @@ function GameScene:render()
end
function GameScene:onKeyPress(e)
if (self.game.completed) and
(e.scancode == "return" or e.scancode == "escape") and e.isRepeat == false then
function GameScene:onInputPress(e)
if self.game.completed and (e.input == "menu_decide" or e.input == "menu_back" or e.input == "retry") then
highscore_entry = self.game:getHighscoreData()
highscore_hash = self.game.hash .. "-" .. self.ruleset.hash
submitHighscore(highscore_hash, highscore_entry)
scene = e.input == "retry" and GameScene(self.retry_mode, self.retry_ruleset) or ModeSelectScene()
elseif e.input == "retry" then
scene = GameScene(self.retry_mode, self.retry_ruleset)
elseif e.input == "menu_back" then
scene = ModeSelectScene()
elseif (e.scancode == config.input.retry) then
-- fuck this, this is hacky but the way this codebase is setup prevents anything else
-- it seems like all the values that get touched in the child gamemode class
-- stop being linked to the values of the GameMode superclass because of how `mt.__index` works
-- not even sure this is the actual problem, but I don't want to have to rebuild everything about
-- the core organisation of everything. this hacky way will have to do until someone figures out something.
love.keypressed("escape", "escape", false)
love.keypressed("return", "return", false)
elseif e.scancode == "escape" then
scene = ModeSelectScene()
elseif e.input and string.sub(e.input, 1, 5) ~= "menu_" then
self.inputs[e.input] = true
end
end
function GameScene:onInputRelease(e)
if e.input and string.sub(e.input, 1, 5) ~= "menu_" then
self.inputs[e.input] = false
end
end

79
scene/game_config.lua Normal file
View File

@@ -0,0 +1,79 @@
local ConfigScene = Scene:extend()
ConfigScene.title = "Game Settings"
require 'load.save'
ConfigScene.options = {
-- this serves as reference to what the options' values mean i guess?
{"manlock", "Manual locking",{"Per ruleset","Per gamemode","Harddrop", "Softdrop"}},
{"piece_colour", "Piece Colours", {"Per ruleset","Arika" ,"TTC"}},
{"world_reverse","A Button Rotation", {"Left" ,"Auto" ,"Right"}},
}
local optioncount = #ConfigScene.options
function ConfigScene:new()
-- load current config
self.config = config.input
self.highlight = 1
DiscordRPC:update({
details = "In menus",
state = "Changing game settings",
})
end
function ConfigScene:update()
end
function ConfigScene:render()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(
backgrounds["game_config"],
0, 0, 0,
0.5, 0.5
)
love.graphics.setFont(font_3x5_4)
love.graphics.print("GAME SETTINGS", 80, 40)
love.graphics.setColor(1, 1, 1, 0.5)
love.graphics.rectangle("fill", 20, 98 + self.highlight * 20, 170, 22)
love.graphics.setFont(font_3x5_2)
for i, option in ipairs(ConfigScene.options) do
love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(option[2], 40, 100 + i * 20, 150, "left")
for j, setting in ipairs(option[3]) do
love.graphics.setColor(1, 1, 1, config.gamesettings[option[1]] == j and 1 or 0.5)
love.graphics.printf(setting, 100 + 110 * j, 100 + i * 20, 100, "center")
end
end
end
function ConfigScene:onInputPress(e)
if e.input == "menu_decide" or e.scancode == "return" then
playSE("mode_decide")
saveConfig()
scene = TitleScene()
elseif e.input == "up" or e.scancode == "up" then
playSE("cursor")
self.highlight = Mod1(self.highlight-1, optioncount)
elseif e.input == "down" or e.scancode == "down" then
playSE("cursor")
self.highlight = Mod1(self.highlight+1, optioncount)
elseif e.input == "left" or e.scancode == "left" then
playSE("cursor_lr")
local option = ConfigScene.options[self.highlight]
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]-1, #option[3])
elseif e.input == "right" or e.scancode == "right" then
playSE("cursor_lr")
local option = ConfigScene.options[self.highlight]
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]+1, #option[3])
elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
loadSave()
scene = TitleScene()
end
end
return ConfigScene

View File

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

View File

@@ -5,48 +5,16 @@ ModeSelectScene.title = "Game Start"
current_mode = 1
current_ruleset = 1
game_modes = {
require 'tetris.modes.marathon_2020',
require 'tetris.modes.survival_2020',
--require 'tetris.modes.strategy',
--require 'tetris.modes.interval_training',
--require 'tetris.modes.pacer_test',
--require 'tetris.modes.demon_mode',
require 'tetris.modes.phantom_mania',
require 'tetris.modes.phantom_mania2',
require 'tetris.modes.phantom_mania_n',
require 'tetris.modes.race_40',
require 'tetris.modes.marathon_a1',
require 'tetris.modes.marathon_a2',
require 'tetris.modes.marathon_a3',
require 'tetris.modes.marathon_ax4',
require 'tetris.modes.marathon_c89',
require 'tetris.modes.survival_a1',
require 'tetris.modes.survival_a2',
require 'tetris.modes.survival_a3',
require 'tetris.modes.big_a2',
require 'tetris.modes.konoha',
}
rulesets = {
require 'tetris.rulesets.cambridge',
require 'tetris.rulesets.arika',
require 'tetris.rulesets.arika_ti',
require 'tetris.rulesets.ti_srs',
require 'tetris.rulesets.arika_ace',
require 'tetris.rulesets.arika_srs',
require 'tetris.rulesets.standard_exp',
--require 'tetris.rulesets.bonkers',
--require 'tetris.rulesets.shirase',
--require 'tetris.rulesets.super302',
}
function ModeSelectScene:new()
self.menu_state = {
mode = current_mode,
ruleset = current_ruleset,
select = "mode",
}
DiscordRPC:update({
details = "In menus",
state = "Choosing a mode",
})
end
function ModeSelectScene:update()
@@ -64,14 +32,14 @@ function ModeSelectScene:render()
elseif self.menu_state.select == "ruleset" then
love.graphics.setColor(1, 1, 1, 0.25)
end
love.graphics.rectangle("fill", 20, 78 + 20 * self.menu_state.mode, 240, 22)
love.graphics.rectangle("fill", 20, 258, 240, 22)
if self.menu_state.select == "mode" then
love.graphics.setColor(1, 1, 1, 0.25)
elseif self.menu_state.select == "ruleset" then
love.graphics.setColor(1, 1, 1, 0.5)
end
love.graphics.rectangle("fill", 340, 78 + 20 * self.menu_state.ruleset, 200, 22)
love.graphics.rectangle("fill", 340, 258, 200, 22)
love.graphics.setColor(1, 1, 1, 1)
@@ -79,29 +47,36 @@ function ModeSelectScene:render()
love.graphics.setFont(font_3x5_2)
for idx, mode in pairs(game_modes) do
love.graphics.printf(mode.name, 40, 80 + 20 * idx, 200, "left")
if(idx >= self.menu_state.mode-9 and idx <= self.menu_state.mode+9) then
love.graphics.printf(mode.name, 40, (260 - 20*(self.menu_state.mode)) + 20 * idx, 200, "left")
end
end
for idx, ruleset in pairs(rulesets) do
love.graphics.printf(ruleset.name, 360, 80 + 20 * idx, 160, "left")
if(idx >= self.menu_state.ruleset-9 and idx <= self.menu_state.ruleset+9) then
love.graphics.printf(ruleset.name, 360, (260 - 20*(self.menu_state.ruleset)) + 20 * idx, 160, "left")
end
end
end
function ModeSelectScene:onKeyPress(e)
if e.scancode == "return" and e.isRepeat == false then
function ModeSelectScene:onInputPress(e)
if e.input == "menu_decide" or e.scancode == "return" then
current_mode = self.menu_state.mode
current_ruleset = self.menu_state.ruleset
config.current_mode = current_mode
config.current_ruleset = current_ruleset
playSE("mode_decide")
saveConfig()
scene = GameScene(game_modes[self.menu_state.mode], rulesets[self.menu_state.ruleset])
elseif (e.scancode == config.input["up"] or e.scancode == "up") and e.isRepeat == false then
elseif e.input == "up" or e.scancode == "up" then
self:changeOption(-1)
elseif (e.scancode == config.input["down"] or e.scancode == "down") and e.isRepeat == false then
playSE("cursor")
elseif e.input == "down" or e.scancode == "down" then
self:changeOption(1)
elseif (e.scancode == config.input["left"] or e.scancode == "left") or
(e.scancode == config.input["right"] or e.scancode == "right") then
playSE("cursor")
elseif e.input == "left" or e.input == "right" or e.scancode == "left" or e.scancode == "right" then
self:switchSelect()
elseif e.scancode == "escape" then
playSE("cursor_lr")
elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
scene = TitleScene()
end
end

View File

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

View File

@@ -4,6 +4,7 @@ local Grid = Object:extend()
local empty = { skin = "", colour = "" }
local oob = { skin = "", colour = "" }
local block = { skin = "2tie", colour = "A" }
function Grid:new()
self.grid = {}
@@ -141,14 +142,49 @@ function Grid:copyBottomRow()
self.grid[24] = {empty, empty, empty, empty, empty, empty, empty, empty, empty, empty}
self.grid_age[24] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
for col = 1, 10 do
self.grid[24][col] = (self.grid[23][col] == empty) and empty or {
skin = self.grid[23][col].skin,
colour = "G"
}
self.grid[24][col] = (self.grid[23][col] == empty) and empty or block
end
return true
end
function Grid:garbageRise(row_vals)
for row = 1, 23 do
self.grid[row] = self.grid[row+1]
self.grid_age[row] = self.grid_age[row+1]
end
self.grid[24] = {empty, empty, empty, empty, empty, empty, empty, empty, empty, empty}
self.grid_age[24] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
for col = 1, 10 do
self.grid[24][col] = (row_vals[col] == "e") and empty or block
end
end
function Grid:applyFourWide()
for row = 1, 24 do
local x = self.grid[row]
x[1] = x[1]~=block and block or x[1]
x[2] = x[2]~=block and block or x[2]
x[3] = x[3]~=block and block or x[3]
x[8] = x[8]~=block and block or x[8]
x[9] = x[9]~=block and block or x[9]
x[10] = x[10]~=block and block or x[10]
end
end
function Grid:applyCeiling(lines)
for row = 1, lines do
for col = 1, 9 do
self.grid[row][col] = block
end
end
end
function Grid:clearSpecificRow(row)
for col = 1, 10 do
self.grid[row][col] = empty
end
end
function Grid:applyPiece(piece)
if piece.big then
self:applyBigPiece(piece)
@@ -161,7 +197,7 @@ function Grid:applyPiece(piece)
if y + 1 > 0 then
self.grid[y+1][x+1] = {
skin = piece.skin,
colour = piece.shape
colour = piece.colour
}
end
end
@@ -177,7 +213,7 @@ function Grid:applyBigPiece(piece)
if y*2+a > 0 then
self.grid[y*2+a][x*2+b] = {
skin = piece.skin,
colour = piece.shape
colour = piece.colour
}
end
end
@@ -216,7 +252,7 @@ function Grid:checkSecretGrade()
if(validLine) then
sgrade = sgrade + 1
else
-- return sgrade
return sgrade
end
end
--[[
@@ -239,14 +275,18 @@ function Grid:update()
end
function Grid:draw()
for y = 1, 24 do
for y = 5, 24 do
for x = 1, 10 do
if self.grid[y][x] ~= empty then
if self.grid_age[y][x] < 1 then
if self.grid_age[y][x] < 2 then
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(blocks[self.grid[y][x].skin]["F"], 48+x*16, y*16)
else
if self.grid[y][x].skin == "bone" then
love.graphics.setColor(1, 1, 1, 1)
else
love.graphics.setColor(0.5, 0.5, 0.5, 1)
end
love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16)
end
if self.grid[y][x].skin ~= "bone" then
@@ -271,12 +311,12 @@ function Grid:draw()
end
function Grid:drawInvisible(opacity_function, garbage_opacity_function)
for y = 1, 24 do
for y = 5, 24 do
for x = 1, 10 do
if self.grid[y][x] ~= empty then
if self.grid[y][x].colour == "X" then
opacity = 1
elseif garbage_opacity_function and self.grid[y][x].colour == "G" then
elseif garbage_opacity_function and self.grid[y][x].colour == "A" then
opacity = garbage_opacity_function(self.grid_age[y][x])
else
opacity = opacity_function(self.grid_age[y][x])

View File

@@ -2,7 +2,7 @@ local Object = require 'libs.classic'
local Piece = Object:extend()
function Piece:new(shape, rotation, position, block_offsets, gravity, lock_delay, skin, big)
function Piece:new(shape, rotation, position, block_offsets, gravity, lock_delay, skin, colour, big)
self.shape = shape
self.rotation = rotation
self.position = position
@@ -10,6 +10,7 @@ function Piece:new(shape, rotation, position, block_offsets, gravity, lock_delay
self.gravity = gravity
self.lock_delay = lock_delay
self.skin = skin
self.colour = colour
self.ghost = false
self.locked = false
self.big = big
@@ -21,7 +22,7 @@ function Piece:withOffset(offset)
return Piece(
self.shape, self.rotation,
{x = self.position.x + offset.x, y = self.position.y + offset.y},
self.block_offsets, self.gravity, self.lock_delay, self.skin, self.big
self.block_offsets, self.gravity, self.lock_delay, self.skin, self.colour, self.big
)
end
@@ -31,7 +32,7 @@ function Piece:withRelativeRotation(rot)
while new_rot >= 4 do new_rot = new_rot - 4 end
return Piece(
self.shape, new_rot, self.position,
self.block_offsets, self.gravity, self.lock_delay, self.skin, self.big
self.block_offsets, self.gravity, self.lock_delay, self.skin, self.colour, self.big
)
end
@@ -97,7 +98,7 @@ end
function Piece:dropToBottom(grid)
local piece_y = self.position.y
self:dropSquares(24, grid)
self:dropSquares(math.huge, grid)
self.gravity = 0
if self.position.y > piece_y then
-- if it got dropped any, also reset lock delay
@@ -148,13 +149,13 @@ function Piece:draw(opacity, brightness, grid, partial_das)
local y = self.position.y + offset.y
if self.big then
love.graphics.draw(
blocks[self.skin][self.shape],
blocks[self.skin][self.colour],
64+x*32+partial_das*2, 16+y*32+gravity_offset*2,
0, 2, 2
)
else
love.graphics.draw(
blocks[self.skin][self.shape],
blocks[self.skin][self.colour],
64+x*16+partial_das, 16+y*16+gravity_offset
)
end

View File

@@ -19,17 +19,15 @@ function MarathonA2Game:new()
self.big_mode = true
self.roll_frames = 0
self.combo = 1
self.randomizer = History6RollsRandomizer()
self.grade = 0
self.grade_points = 0
self.grade_point_decay_counter = 0
self.section_start_time = 0
self.section_times = { [0] = 0 }
self.section_tetrises = { [0] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
self.randomizer = History6RollsRandomizer()
self.lock_drop = false
self.lock_hard_drop = false
self.enable_hold = false
self.next_queue_length = 1
end
@@ -103,11 +101,9 @@ end
function MarathonA2Game:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames < 0 then return false end
if self.roll_frames > 3694 then
self.completed = true
if self.grade == 32 then
self.grade = 33
end
end
elseif self.ready_frames == 0 then
self.frames = self.frames + 1
@@ -123,42 +119,31 @@ end
function MarathonA2Game:onLineClear(cleared_row_count)
cleared_row_count = cleared_row_count / 2
self:updateSectionTimes(self.level, self.level + cleared_row_count)
self.level = math.min(self.level + cleared_row_count, 999)
if self.level == 999 and not self.clear then
self.clear = true
if self:qualifiesForMRoll() then
self.grade = 32
end
self.grid:clear()
self.roll_frames = -150
end
self.lock_drop = self.level >= 900
self.lock_hard_drop = self.level >= 900
end
function MarathonA2Game:updateScore(level, drop_bonus, cleared_lines)
if not self.clear then
cleared_lines = cleared_lines / 2
self:updateGrade(cleared_lines)
if self.grid:checkForBravo(cleared_lines) then self.bravo = 4 else self.bravo = 1 end
if cleared_lines > 0 then
self.combo = self.combo + (cleared_lines - 1) * 2
self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * (cleared_lines * 2 - 1) * (self.combo * 2 - 1)
cleared_lines * self.combo * self.bravo
)
self.lines = self.lines + cleared_lines
self.combo = self.combo + cleared_lines - 1
else
self.drop_bonus = 0
self.combo = 1
end
end
function MarathonA2Game:updateSectionTimes(old_level, new_level)
if self.clear then return end
if math.floor(old_level / 100) < math.floor(new_level / 100) or
new_level >= 999 then
-- record new section
section_time = self.frames - self.section_start_time
self.section_times[math.floor(old_level / 100)] = section_time
self.section_start_time = self.frames
self.drop_bonus = 0
end
end
@@ -223,7 +208,7 @@ local grade_conversion = {
1, 2, 3, 4, 5, 5, 6, 6, 7, 7,
7, 8, 8, 8, 9, 9, 9, 10, 11, 12,
12, 12, 13, 13, 14, 14, 15, 15, 16, 16,
17, 18, 19
17
}
function MarathonA2Game:updateGrade(cleared_lines)
@@ -248,49 +233,12 @@ function MarathonA2Game:updateGrade(cleared_lines)
end
end
local tetris_requirements = { [0] = 2, 2, 2, 2, 2, 1, 1, 1, 1, 1 }
function MarathonA2Game:qualifiesForMRoll()
if not self.clear then return false end
-- tetris requirements
for section = 0, 9 do
if self.section_tetrises[section] < tetris_requirements[section] then
return false
end
end
-- section time requirements
local section_average = 0
for section = 0, 4 do
section_average = section_average + self.section_times[section]
if self.section_times[section] > frameTime(1,05) then
return false
end
end
-- section time average requirements
if self.section_times[5] > section_average / 5 then
return false
end
for section = 6, 9 do
if self.section_times[section] > self.section_times[section - 1] + 120 then
return false
end
end
if self.grade < 17 or self.frames > frameTime(8,45) then
return false
end
return true
end
function MarathonA2Game:getLetterGrade()
local grade = grade_conversion[self.grade]
if grade < 9 then
return tostring(9 - grade)
elseif grade < 18 then
return "S" .. tostring(grade - 8)
elseif grade == 18 then
return "M"
else
return "GM"
end
end
@@ -300,18 +248,9 @@ MarathonA2Game.rollOpacityFunction = function(age)
else return 1 - (age - 240) / 60 end
end
MarathonA2Game.mRollOpacityFunction = function(age)
if age > 4 then return 0
else return 1 - age / 4 end
end
function MarathonA2Game:drawGrid(ruleset)
if self.clear and not (self.completed or self.game_over) then
if self:qualifiesForMRoll() then
self.grid:drawInvisible(self.mRollOpacityFunction)
else
self.grid:drawInvisible(self.rollOpacityFunction)
end
else
self.grid:draw()
if self.piece ~= nil and self.level < 100 then
@@ -335,7 +274,10 @@ function MarathonA2Game:drawScoringInfo()
love.graphics.printf("LEVEL", 240, 320, 40, "left")
love.graphics.setFont(font_3x5_3)
if self.roll_frames > 3694 then love.graphics.setColor(1, 0.5, 0, 1)
elseif self.clear then love.graphics.setColor(0, 1, 0, 1) end
love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left")
love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(self.score, 240, 220, 90, "left")
love.graphics.printf(self.level, 240, 340, 40, "right")
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")

View File

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

View File

@@ -1,6 +1,9 @@
local Object = require 'libs.classic'
require 'funcs'
local playedReadySE = false
local playedGoSE = false
local Grid = require 'tetris.components.grid'
local Randomizer = require 'tetris.randomizers.randomizer'
@@ -40,9 +43,12 @@ function GameMode:new()
self.draw_section_times = false
self.draw_secondary_section_times = false
self.big_mode = false
self.rpc_details = "In game"
-- variables related to configurable parameters
self.drop_locked = false
self.hard_drop_locked = false
self.lock_on_soft_drop = false
self.lock_on_hard_drop = false
self.hold_queue = nil
self.held = false
self.section_start_time = 0
@@ -59,6 +65,7 @@ function GameMode:getLineClearDelay() return 40 end
function GameMode:getDasLimit() return 15 end
function GameMode:getNextPiece(ruleset)
return {
skin = "2tie",
shape = self.randomizer:nextPiece(),
@@ -72,6 +79,8 @@ function GameMode:initialize(ruleset)
for i = 1, self.next_queue_length do
table.insert(self.next_queue, self:getNextPiece(ruleset))
end
self.lock_on_soft_drop = ({ruleset.softdrop_lock, self.instant_soft_drop, false, true })[config.gamesettings.manlock]
self.lock_on_hard_drop = ({ruleset.harddrop_lock, self.instant_hard_drop, true, false})[config.gamesettings.manlock]
end
function GameMode:update(inputs, ruleset)
@@ -125,7 +134,7 @@ function GameMode:update(inputs, ruleset)
self.piece:isDropBlocked(self.grid) and
not self.hard_drop_locked then
self:onHardDrop(piece_dy)
if self.instant_hard_drop then
if self.lock_on_hard_drop then
self.piece.locked = true
end
end
@@ -134,7 +143,7 @@ function GameMode:update(inputs, ruleset)
self:onSoftDrop(piece_dy)
if self.piece:isDropBlocked(self.grid) and
not self.drop_locked and
self.instant_soft_drop
self.lock_on_soft_drop
then
self.piece.locked = true
end
@@ -155,6 +164,7 @@ function GameMode:update(inputs, ruleset)
end
if cleared_row_count > 0 then
playSE("erase")
self.lcd = self:getLineClearDelay()
self.are = self:getLineARE()
if self.lcd == 0 then
@@ -188,10 +198,16 @@ end
-- event functions
function GameMode:whilePieceActive() end
function GameMode:onPieceLock(piece, cleared_row_count) end
function GameMode:onPieceLock(piece, cleared_row_count)
playSE("lock")
end
function GameMode:onLineClear(cleared_row_count) end
function GameMode:onPieceEnter() end
function GameMode:onHold() end
function GameMode:onHold()
playSE("hold")
end
function GameMode:onSoftDrop(dropped_row_count)
self.drop_bonus = self.drop_bonus + 1 * dropped_row_count
@@ -233,8 +249,20 @@ function GameMode:chargeDAS(inputs)
end
function GameMode:processDelays(inputs, ruleset, drop_speed)
if self.ready_frames == 100 then
playedReadySE = false
playedGoSE = false
end
if self.ready_frames > 0 then
if not playedReadySE then
playedReadySE = true
playSEOnce("ready")
end
self.ready_frames = self.ready_frames - 1
if self.ready_frames == 50 and not playedGoSE then
playedGoSE = true
playSEOnce("go")
end
if self.ready_frames == 0 then
self:initializeOrHold(inputs, ruleset)
end
@@ -242,6 +270,7 @@ function GameMode:processDelays(inputs, ruleset, drop_speed)
self.lcd = self.lcd - 1
if self.lcd == 0 then
self.grid:clearClearedRows()
playSE("fall")
if self.are == 0 then
self:initializeOrHold(inputs, ruleset)
end
@@ -340,11 +369,12 @@ function GameMode:drawGhostPiece(ruleset)
end
function GameMode:drawNextQueue(ruleset)
local colourscheme = ({ruleset.colourscheme, ColourSchemes.Arika, ColourSchemes.TTC})[config.gamesettings.piece_colour]
function drawPiece(piece, skin, offsets, pos_x, pos_y)
for index, offset in pairs(offsets) do
local x = offset.x + ruleset.spawn_positions[piece].x
local y = offset.y + 4.7
love.graphics.draw(blocks[skin][piece], pos_x+x*16, pos_y+y*16)
love.graphics.draw(blocks[skin][colourscheme[piece]], pos_x+x*16, pos_y+y*16)
end
end
for i = 1, self.next_queue_length do
@@ -359,7 +389,8 @@ function GameMode:drawNextQueue(ruleset)
end
end
if self.hold_queue ~= nil then
self:setHoldOpacity()
local hold_color = self.held and 0.6 or 1
self:setHoldOpacity(1, hold_color)
drawPiece(
self.hold_queue.shape,
self.hold_queue.skin,
@@ -370,8 +401,16 @@ function GameMode:drawNextQueue(ruleset)
return false
end
function GameMode:setNextOpacity(i) love.graphics.setColor(1, 1, 1, 1) end
function GameMode:setHoldOpacity() love.graphics.setColor(1, 1, 1, 1) end
function GameMode:setNextOpacity(i, j)
i = i ~= nil and i or 1
j = j ~= nil and j or 1
love.graphics.setColor(j, j, j, i)
end
function GameMode:setHoldOpacity(i, j)
i = i ~= nil and i or 1
j = j ~= nil and j or 1
love.graphics.setColor(j, j, j, i)
end
function GameMode:drawScoringInfo()
love.graphics.setColor(1, 1, 1, 1)

View File

@@ -1,155 +0,0 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local IntervalTrainingGame = GameMode:extend()
IntervalTrainingGame.name = "Interval Training"
IntervalTrainingGame.hash = "IntervalTraining"
IntervalTrainingGame.tagline = "Can you clear the time hurdles when the game goes this fast?"
function IntervalTrainingGame:new()
IntervalTrainingGame.super:new()
self.roll_frames = 0
self.combo = 1
self.randomizer = History6RollsRandomizer()
self.section_time_limit = 1800
self.section_start_time = 0
self.section_times = { [0] = 0 }
self.lock_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
function IntervalTrainingGame:getARE()
return 4
end
function IntervalTrainingGame:getLineARE()
return 4
end
function IntervalTrainingGame:getDasLimit()
return 6
end
function IntervalTrainingGame:getLineClearDelay()
return 6
end
function IntervalTrainingGame:getLockDelay()
return 15
end
function IntervalTrainingGame:getGravity()
return 20
end
function IntervalTrainingGame:getSection()
return math.floor(level / 100) + 1
end
function IntervalTrainingGame:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames > 2968 then
self.completed = true
end
return false
elseif self.ready_frames == 0 then
self.frames = self.frames + 1
if self:getSectionTime() >= self.section_time_limit then
self.game_over = true
end
end
return true
end
function IntervalTrainingGame:onPieceEnter()
if (self.level % 100 ~= 99 or self.level == 998) and not self.clear and self.frames ~= 0 then
self.level = self.level + 1
end
end
function IntervalTrainingGame:onLineClear(cleared_row_count)
if not self.clear then
local new_level = self.level + cleared_row_count
self:updateSectionTimes(self.level, new_level)
self.level = math.min(new_level, 999)
if self.level == 999 then
self.clear = true
end
end
end
function IntervalTrainingGame:getSectionTime()
return self.frames - self.section_start_time
end
function IntervalTrainingGame:updateSectionTimes(old_level, new_level)
if math.floor(old_level / 100) < math.floor(new_level / 100) then
-- record new section
table.insert(self.section_times, self:getSectionTime())
self.section_start_time = self.frames
else
self.level = math.min(new_level, 999)
end
end
function IntervalTrainingGame:drawGrid(ruleset)
self.grid:draw()
end
function IntervalTrainingGame:getHighscoreData()
return {
level = self.level,
frames = self.frames,
}
end
function IntervalTrainingGame:drawScoringInfo()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_2)
love.graphics.print(
self.das.direction .. " " ..
self.das.frames .. " " ..
strTrueValues(self.prev_inputs)
)
love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("TIME LEFT", 240, 250, 80, "left")
love.graphics.printf("LEVEL", 240, 320, 40, "left")
local current_section = math.floor(self.level / 100) + 1
self:drawSectionTimesWithSplits(current_section)
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.level, 240, 340, 40, "right")
-- draw time left, flash red if necessary
local time_left = self.section_time_limit - math.max(self:getSectionTime(), 0)
if not self.game_over and not self.clear and time_left < frameTime(0,10) and time_left % 4 < 2 then
love.graphics.setColor(1, 0.3, 0.3, 1)
end
love.graphics.printf(formatTime(time_left), 240, 270, 160, "left")
love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")
end
function IntervalTrainingGame:getSectionEndLevel()
if self.level >= 900 then return 999
else return math.floor(self.level / 100 + 1) * 100 end
end
function IntervalTrainingGame:getBackground()
return math.floor(self.level / 100)
end
return IntervalTrainingGame

View File

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

View File

@@ -36,6 +36,8 @@ function Marathon2020Game:new()
self.grade_points = 0
self.grade_point_decay_counter = 0
self.max_grade_points = 0
self.cool_timer = 0
end
function Marathon2020Game:getARE()
@@ -327,6 +329,7 @@ function Marathon2020Game:updateSectionTimes(old_level, new_level)
self.section_cool_count = self.section_cool_count + 1
self.delay_level = math.min(20, self.delay_level + 1)
table.insert(self.section_status, "cool")
self.cool_timer = 300
end
local section = getSectionForLevel(old_level)
@@ -430,6 +433,11 @@ function Marathon2020Game:drawScoringInfo()
self:drawSectionTimesWithSecondary(current_section)
if (self.cool_timer > 0) then
love.graphics.printf("COOL!!", 64, 400, 160, "center")
self.cool_timer = self.cool_timer - 1
end
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self:getTotalGrade(), text_x, 120, 90, "left")
love.graphics.printf(self.grade_points, text_x, 220, 90, "left")

View File

@@ -18,6 +18,7 @@ function MarathonA1Game:new()
self.roll_frames = 0
self.combo = 1
self.bravos = 0
self.gm_conditions = {
level300 = false,
level500 = false,
@@ -136,40 +137,44 @@ function MarathonA1Game:onLineClear(cleared_row_count)
self:checkGMRequirements(self.level, self.level + cleared_row_count)
if not self.clear then
local new_level = math.min(self.level + cleared_row_count, 999)
if self.level == 999 then
if new_level == 999 then
self.clear = true
else
self.level = new_level
end
self.level = new_level
end
end
function MarathonA1Game:updateScore(level, drop_bonus, cleared_lines)
if not self.clear then
if self.grid:checkForBravo(cleared_lines) then
self.bravo = 4
self.bravos = self.bravos + 1
else self.bravo = 1 end
if cleared_lines > 0 then
self.combo = self.combo + (cleared_lines - 1) * 2
self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * self.combo
cleared_lines * self.combo * self.bravo
)
self.lines = self.lines + cleared_lines
else
self.drop_bonus = 0
self.combo = 1
end
self.drop_bonus = 0
end
end
function MarathonA1Game:checkGMRequirements(old_level, new_level)
if old_level < 300 and new_level >= 300 then
if self.score > 12000 and self.frames <= frameTime(4,15) then
if self.score >= 12000 and self.frames <= frameTime(4,15) then
self.gm_conditions["level300"] = true
end
elseif old_level < 500 and new_level >= 500 then
if self.score > 40000 and self.frames <= frameTime(7,30) then
if self.score >= 40000 and self.frames <= frameTime(7,30) then
self.gm_conditions["level500"] = true
end
elseif old_level < 999 and new_level >= 999 then
if self.score > 126000 and self.frames <= frameTime(13,30) then
self.gm_conditions["level900"] = true
if self.score >= 126000 and self.frames <= frameTime(13,30) then
self.gm_conditions["level999"] = true
end
end
end
@@ -201,9 +206,11 @@ function MarathonA1Game:drawScoringInfo()
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
end
if self.bravos > 0 then love.graphics.printf("BRAVO", 300, 120, 40, "left") end
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.score, 240, 220, 90, "left")
if self.gm_conditions["level300"] and self.gm_conditions["level500"] and self.gm_conditions["level900"] then
if self.gm_conditions["level300"] and self.gm_conditions["level500"] and self.gm_conditions["level999"] then
love.graphics.printf("GM", 240, 140, 90, "left")
else
love.graphics.printf(getRankForScore(self.score).rank, 240, 140, 90, "left")
@@ -214,6 +221,7 @@ function MarathonA1Game:drawScoringInfo()
if sg >= 5 then
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
end
if self.bravos > 0 then love.graphics.printf(self.bravos, 300, 140, 40, "left") end
love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")

View File

@@ -33,9 +33,8 @@ function MarathonA2Game:new()
"GM"
}
self.randomizer = History6RollsRandomizer()
self.lock_drop = false
self.lock_hard_drop = false
self.enable_hold = false
self.next_queue_length = 1
end
@@ -109,6 +108,7 @@ end
function MarathonA2Game:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames < 0 then return false end
if self.roll_frames > 3694 then
self.completed = true
if self.grade == 32 then
@@ -127,32 +127,33 @@ function MarathonA2Game:onPieceEnter()
end
end
function MarathonA2Game:updateScore(level, drop_bonus, cleared_lines)
if not self.clear then
self:updateGrade(cleared_lines)
if self.grid:checkForBravo(cleared_lines) then self.bravo = 4 else self.bravo = 1 end
if cleared_lines > 0 then
self.combo = self.combo + (cleared_lines - 1) * 2
self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * self.combo * self.bravo
)
else
self.combo = 1
end
self.drop_bonus = 0
else self.lines = self.lines + cleared_lines end
end
function MarathonA2Game:onLineClear(cleared_row_count)
self:updateSectionTimes(self.level, self.level + cleared_row_count)
self.level = math.min(self.level + cleared_row_count, 999)
if self.level == 999 and not self.clear then
self.clear = true
if self:qualifiesForMRoll() then
self.grade = 32
end
self.grid:clear()
if self:qualifiesForMRoll() then self.grade = 32 end
self.roll_frames = -150
end
end
function MarathonA2Game:updateScore(level, drop_bonus, cleared_lines)
self:updateGrade(cleared_lines)
if cleared_lines > 0 then
self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * (cleared_lines * 2 - 1) * (self.combo * 2 - 1)
)
self.lines = self.lines + cleared_lines
self.combo = self.combo + cleared_lines - 1
else
self.drop_bonus = 0
self.combo = 1
end
self.lock_drop = self.level >= 900
self.lock_hard_drop = self.level >= 900
end
function MarathonA2Game:updateSectionTimes(old_level, new_level)
@@ -252,7 +253,7 @@ function MarathonA2Game:updateGrade(cleared_lines)
end
end
local tetris_requirements = { [0] = 2, 2, 2, 2, 2, 1, 1, 1, 1, 1 }
local tetris_requirements = { [0] = 2, 2, 2, 2, 2, 1, 1, 1, 1, 0 }
function MarathonA2Game:qualifiesForMRoll()
if not self.clear then return false end
@@ -279,7 +280,7 @@ function MarathonA2Game:qualifiesForMRoll()
return false
end
end
if self.grade < 17 or self.frames > frameTime(9,30) then
if self.grade < 31 or self.frames > frameTime(8,45) then
return false
end
return true
@@ -343,7 +344,17 @@ function MarathonA2Game:drawScoringInfo()
end
love.graphics.setFont(font_3x5_3)
if self.clear then
if self:qualifiesForMRoll() then
if self.lines >= 32 and self.roll_frames > 3694 then love.graphics.setColor(1, 0.5, 0, 1)
else love.graphics.setColor(0, 1, 0, 1) end
else
if self.roll_frames > 3694 then love.graphics.setColor(1, 0.5, 0, 1)
else love.graphics.setColor(0, 1, 0, 1) end
end
end
love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left")
love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(self.score, 240, 220, 90, "left")
love.graphics.printf(self.level, 240, 340, 40, "right")
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")

View File

@@ -29,6 +29,7 @@ function MarathonA3Game:new()
self.section_start_time = 0
self.section_70_times = { [0] = 0 }
self.section_times = { [0] = 0 }
self.section_cool = false
self.randomizer = History6RollsRandomizer()
@@ -45,6 +46,8 @@ self.SGnames = {
self.coolregret_message = "COOL!!"
self.coolregret_timer = 0
self.torikan_passed = false
end
function MarathonA3Game:getARE()
@@ -149,6 +152,7 @@ function MarathonA3Game:onPieceEnter()
self:updateSectionTimes(self.level, self.level + 1)
self.level = self.level + 1
self.speed_level = self.speed_level + 1
self.torikan_passed = self.level >= 500 and true or false
end
end
@@ -166,6 +170,10 @@ function MarathonA3Game:onLineClear(cleared_row_count)
self.grid:clear()
self.roll_frames = -150
end
if not self.torikan_passed and self.level >= 500 and self.frames >= 25200 then
self.level = 500
self.game_over = true
end
end
local cool_cutoffs = {
@@ -188,49 +196,54 @@ function MarathonA3Game:updateSectionTimes(old_level, new_level)
table.insert(self.section_times, section_time)
self.section_start_time = self.frames
self.speed_level = self.section_cool and self.speed_level + 100 or self.speed_level
if section_time > regret_cutoffs[section] then
self.section_cool_grade = self.section_cool_grade - 1
table.insert(self.section_status, "regret")
self.coolregret_message = "REGRET!!"
self.coolregret_timer = 300
elseif section <= 9 and self.section_status[section - 1] == "cool" and
self.section_70_times[section] < self.section_70_times[section - 1] + 120 then
elseif self.section_cool then
self.section_cool_grade = self.section_cool_grade + 1
self.speed_level = self.speed_level + 100
table.insert(self.section_status, "cool")
self.coolregret_message = "COOL!!"
self.coolregret_timer = 300
elseif self.section_status[section - 1] == "cool" then
table.insert(self.section_status, "none")
elseif section <= 9 and self.section_70_times[section] < cool_cutoffs[section] then
self.section_cool_grade = self.section_cool_grade + 1
self.speed_level = self.speed_level + 100
table.insert(self.section_status, "cool")
self.coolregret_message = "COOL!!"
self.coolregret_timer = 300
else
table.insert(self.section_status, "none")
end
elseif section <= 9 and old_level % 100 < 70 and new_level % 100 >= 70 then
self.section_cool = false
elseif old_level % 100 < 70 and new_level % 100 >= 70 then
-- record section 70 time
section_70_time = self.frames - self.section_start_time
table.insert(self.section_70_times, section_70_time)
if section <= 9 and self.section_status[section - 1] == "cool" and
self.section_70_times[section] < self.section_70_times[section - 1] + 120 then
self.section_cool = true
self.coolregret_message = "COOL!!"
self.coolregret_timer = 300
elseif self.section_status[section - 1] == "cool" then self.section_cool = false
elseif section <= 9 and self.section_70_times[section] < cool_cutoffs[section] then
self.section_cool = true
self.coolregret_message = "COOL!!"
self.coolregret_timer = 300
end
end
end
function MarathonA3Game:updateScore(level, drop_bonus, cleared_lines)
if not self.clear then
self:updateGrade(cleared_lines)
if cleared_lines > 0 then
self.combo = self.combo + (cleared_lines - 1) * 2
self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * (cleared_lines * 2 - 1) * (self.combo * 2 - 1)
cleared_lines * self.combo
)
self.lines = self.lines + cleared_lines
self.combo = self.combo + cleared_lines - 1
else
self.drop_bonus = 0
self.combo = 1
end
self.drop_bonus = 0
end
end
local grade_point_bonuses = {
@@ -349,6 +362,8 @@ function MarathonA3Game:getLetterGrade()
return "M" .. tostring(grade - 17)
elseif grade < 32 then
return master_grades[grade - 26]
elseif grade >= 32 and self.roll_frames < 3238 then
return "MM"
else
return "GM"
end
@@ -423,7 +438,7 @@ function MarathonA3Game:drawScoringInfo()
current_x = section_70_x
end
love.graphics.printf(formatTime(self.frames - self.section_start_time), current_x, 40 + 20 * current_section, 90, "left")
if not self.clear then love.graphics.printf(formatTime(self.frames - self.section_start_time), current_x, 40 + 20 * current_section, 90, "left") end
if(self.coolregret_timer > 0) then
love.graphics.printf(self.coolregret_message, 64, 400, 160, "center")
@@ -432,7 +447,10 @@ function MarathonA3Game:drawScoringInfo()
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.score, 240, 220, 90, "left")
if self.roll_frames > 3238 then love.graphics.setColor(1, 0.5, 0, 1)
elseif self.clear then love.graphics.setColor(0, 1, 0, 1) end
love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left")
love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(self.level, 240, 340, 40, "right")
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")
if sg >= 5 then

View File

@@ -24,6 +24,7 @@ function MarathonAX4Game:new()
self.section_clear = false
self.lock_drop = true
self.lock_hard_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
@@ -98,6 +99,7 @@ function MarathonAX4Game:onLineClear(cleared_row_count)
self:updateSectionTimes(self.lines, new_lines)
self.lines = math.min(new_lines, 150)
if self.lines == 150 then
self.grid:clear()
self.clear = true
self.roll_frames = -150
end

View File

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

View File

@@ -1,170 +0,0 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local PacerTest = GameMode:extend()
PacerTest.name = "TetrisGram™ Pacer Test"
PacerTest.hash = "PacerTest"
PacerTest.tagline = ""
local function getLevelFrames(level)
if level == 1 then return 72 * 60 / 8.0
else return 72 * 60 / (8 + level * 0.5)
end
end
local level_end_sections = {
7, 15, 23, 32, 41, 51, 61, 72, 83, 94,
106, 118, 131, 144, 157, 171, 185, 200,
215, 231, 247
}
function PacerTest:new()
PacerTest.super:new()
self.ready_frames = 2430
self.clear_frames = 0
self.randomizer = History6RollsRandomizer()
self.level = 1
self.section = 0
self.level_frames = 0
self.section_lines = 0
self.section_clear = false
self.strikes = 0
self.lock_drop = true
self.lock_hard_drop = true
self.enable_hold = true
self.instant_hard_drop = true
self.instant_soft_drop = false
self.next_queue_length = 3
end
function PacerTest:initialize(ruleset)
for i = 1, 30 do
table.insert(self.next_queue, self:getNextPiece(ruleset))
end
self.level_frames = getLevelFrames(1)
switchBGM("pacer_test")
end
function PacerTest:getARE()
return 0
end
function PacerTest:getLineARE()
return 0
end
function PacerTest:getDasLimit()
return 8
end
function PacerTest:getLineClearDelay()
return 6
end
function PacerTest:getLockDelay()
return 30
end
function PacerTest:getGravity()
return 1/64
end
function PacerTest:getSection()
return math.floor(level / 100) + 1
end
function PacerTest:advanceOneFrame()
if self.clear then
self.clear_frames = self.clear_frames + 1
if self.clear_frames > 600 then
self.completed = true
end
return false
elseif self.ready_frames == 0 then
self.frames = self.frames + 1
self.level_frames = self.level_frames - 1
if self.level_frames <= 0 then
self:checkSectionStatus()
self.section = self.section + 1
if self.section >= level_end_sections[self.level] then
self.level = self.level + 1
end
self.level_frames = self.level_frames + getLevelFrames(self.level)
end
end
return true
end
function PacerTest:checkSectionStatus()
if self.section_clear then
self.strikes = 0
self.section_clear = false
else
self.strikes = self.strikes + 1
if self.strikes >= 2 then
self.game_over = true
fadeoutBGM(2.5)
end
end
self.section_lines = 0
end
function PacerTest:onLineClear(cleared_row_count)
self.section_lines = self.section_lines + cleared_row_count
if self.section_lines >= 3 then
self.section_clear = true
end
end
function PacerTest:drawGrid(ruleset)
self.grid:draw()
if self.piece ~= nil then
self:drawGhostPiece(ruleset)
end
end
function PacerTest:getHighscoreData()
return {
level = self.level,
frames = self.frames,
}
end
function PacerTest:drawScoringInfo()
PacerTest.super.drawScoringInfo(self)
love.graphics.setColor(1, 1, 1, 1)
local text_x = config["side_next"] and 320 or 240
love.graphics.setFont(font_3x5_2)
love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("LINES", text_x, 224, 70, "left")
love.graphics.printf("LEVEL", text_x, 320, 40, "left")
for i = 1, math.min(self.strikes, 3) do
love.graphics.draw(misc_graphics["strike"], text_x + (i - 1) * 30, 280)
end
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.section_lines .. "/3", text_x, 244, 40, "left")
love.graphics.printf(self.level, text_x, 340, 40, "right")
love.graphics.printf(self.section, text_x, 370, 40, "right")
end
function PacerTest:getBackground()
return math.min(self.level - 1, 19)
end
return PacerTest

View File

@@ -15,8 +15,15 @@ function PhantomManiaGame:new()
PhantomManiaGame.super:new()
self.lock_drop = true
self.lock_hard_drop = true
self.next_queue_length = 1
self.SGnames = {
"9", "8", "7", "6", "5", "4", "3", "2", "1",
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"GM"
}
self.roll_frames = 0
self.combo = 1
self.randomizer = History6RollsRandomizer()
@@ -111,17 +118,18 @@ function PhantomManiaGame:onLineClear(cleared_row_count)
end
function PhantomManiaGame:updateScore(level, drop_bonus, cleared_lines)
if not self.clear then
if cleared_lines > 0 then
self.combo = self.combo + (cleared_lines - 1) * 2
self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * (cleared_lines * 2 - 1) * (self.combo * 2 - 1)
cleared_lines * self.combo
)
self.lines = self.lines + cleared_lines
self.combo = self.combo + cleared_lines - 1
else
self.drop_bonus = 0
self.combo = 1
end
self.drop_bonus = 0
end
end
PhantomManiaGame.rollOpacityFunction = function(age)
@@ -161,12 +169,16 @@ function PhantomManiaGame:drawScoringInfo()
local text_x = config["side_next"] and 320 or 240
love.graphics.setFont(font_3x5_2)
love.graphics.printf("GRADE", text_x, 120, 40, "left")
if getLetterGrade(self.level, self.clear) ~= "" then love.graphics.printf("GRADE", text_x, 120, 40, "left") end
love.graphics.printf("SCORE", text_x, 200, 40, "left")
love.graphics.printf("LEVEL", text_x, 320, 40, "left")
local sg = self.grid:checkSecretGrade()
if sg >= 5 then
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
end
love.graphics.setFont(font_3x5_3)
love.graphics.printf(getLetterGrade(self.level, self.clear), text_x, 140, 90, "left")
if getLetterGrade(self.level, self.clear) ~= "" then love.graphics.printf(getLetterGrade(self.level, self.clear), text_x, 140, 90, "left") end
love.graphics.printf(self.score, text_x, 220, 90, "left")
love.graphics.printf(self.level, text_x, 340, 40, "right")
if self.clear then
@@ -175,6 +187,9 @@ function PhantomManiaGame:drawScoringInfo()
love.graphics.printf(self:getSectionEndLevel(), text_x, 370, 40, "right")
end
if sg >= 5 then
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
end
end
function PhantomManiaGame:getSectionEndLevel()

View File

@@ -16,7 +16,6 @@ PhantomMania2Game.tagline = "The blocks disappear even faster now! Can you make
function PhantomMania2Game:new()
PhantomMania2Game.super:new()
self.level = 0
self.grade = 0
self.garbage = 0
self.clear = false
@@ -27,11 +26,21 @@ function PhantomMania2Game:new()
self.queue_age = 0
self.roll_points = 0
self.SGnames = {
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m9",
"GM"
}
self.randomizer = History6RollsRandomizer()
self.lock_drop = true
self.lock_hard_drop = true
self.enable_hold = true
self.next_queue_length = 3
self.coolregret_message = ""
self.coolregret_timer = 0
end
function PhantomMania2Game:getARE()
@@ -116,6 +125,8 @@ function PhantomMania2Game:advanceOneFrame()
return false
elseif self.roll_frames > 3238 then
switchBGM(nil)
self.roll_points = self.level >= 1300 and self.roll_points + 150 or self.roll_points
self.grade = self.grade + math.floor(self.roll_points / 100)
self.completed = true
end
elseif self.ready_frames == 0 then
@@ -137,7 +148,8 @@ function PhantomMania2Game:onPieceEnter()
end
local cleared_row_levels = {1, 2, 4, 6}
local cleared_row_points = {2, 6, 15, 40}
local torikan_roll_points = {10, 20, 30, 100}
local big_roll_points = {10, 20, 100, 200}
function PhantomMania2Game:onLineClear(cleared_row_count)
if not self.clear then
@@ -156,7 +168,8 @@ function PhantomMania2Game:onLineClear(cleared_row_count)
end
self:advanceBottomRow(-cleared_row_count)
else
self.roll_points = self.roll_points + cleared_row_points[cleared_row_count / 2]
if self.big_mode then self.roll_points = self.roll_points + big_roll_points[cleared_row_count / 2]
else self.roll_points = self.roll_points + torikan_roll_points[cleared_row_count] end
if self.roll_points >= 100 then
self.roll_points = self.roll_points - 100
self.grade = self.grade + 1
@@ -165,25 +178,28 @@ function PhantomMania2Game:onLineClear(cleared_row_count)
end
function PhantomMania2Game:onPieceLock(piece, cleared_row_count)
self.super:onPieceLock()
if cleared_row_count == 0 then self:advanceBottomRow(1) end
end
function PhantomMania2Game:onHold()
self.super.onHold()
self.hold_age = 0
end
function PhantomMania2Game:updateScore(level, drop_bonus, cleared_lines)
if not self.clear then
if cleared_lines > 0 then
self.combo = self.combo + (cleared_lines - 1) * 2
self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * (cleared_lines * 2 - 1) * (self.combo * 2 - 1)
cleared_lines * self.combo
)
self.lines = self.lines + cleared_lines
self.combo = self.combo + cleared_lines - 1
else
self.drop_bonus = 0
self.combo = 1
end
self.drop_bonus = 0
end
end
@@ -207,8 +223,13 @@ 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
self.coolregret_message = "COOL!!"
self.coolregret_timer = 300
elseif section_time <= regret_cutoffs[section] then
self.grade = self.grade + 1
else
self.coolregret_message = "REGRET!!"
self.coolregret_timer = 300
end
end
end
@@ -285,6 +306,17 @@ function PhantomMania2Game:drawScoringInfo()
love.graphics.printf("GRADE", text_x, 120, 40, "left")
love.graphics.printf("SCORE", text_x, 200, 40, "left")
love.graphics.printf("LEVEL", text_x, 320, 40, "left")
local sg = self.grid:checkSecretGrade()
if sg >= 5 then
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
end
self:drawSectionTimesWithSplits(math.floor(self.level / 100) + 1)
if(self.coolregret_timer > 0) then
love.graphics.printf(self.coolregret_message, 64, 400, 160, "center")
self.coolregret_timer = self.coolregret_timer - 1
end
love.graphics.setFont(font_3x5_3)
love.graphics.printf(getLetterGrade(math.floor(self.grade)), text_x, 140, 90, "left")
@@ -295,6 +327,10 @@ function PhantomMania2Game:drawScoringInfo()
else
love.graphics.printf(math.floor(self.level / 100 + 1) * 100, text_x, 370, 50, "right")
end
if sg >= 5 then
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
end
end
function PhantomMania2Game:getBackground()

View File

@@ -1,16 +0,0 @@
local PhantomManiaGame = require 'tetris.modes.phantom_mania'
local PhantomManiaNGame = PhantomManiaGame:extend()
PhantomManiaNGame.name = "Phantom Mania N"
PhantomManiaNGame.hash = "PhantomManiaN"
PhantomManiaNGame.tagline = "The old mode from Nullpomino, for Ti-ARS and SRS support."
function PhantomManiaNGame:new()
PhantomManiaNGame.super:new()
self.next_queue_length = 3
self.enable_hold = true
end
return PhantomManiaNGame

View File

@@ -1,129 +0,0 @@
require 'funcs'
local GameMode = require 'tetris.modes.gamemode'
local Piece = require 'tetris.components.piece'
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
local Race40Game = GameMode:extend()
Race40Game.name = "Race 40"
Race40Game.hash = "Race40"
Race40Game.tagline = "How fast can you clear 40 lines?"
function Race40Game:new()
Race40Game.super:new()
self.lines = 0
self.line_goal = 40
self.pieces = 0
self.randomizer = History6RollsRandomizer()
self.roll_frames = 0
self.lock_drop = true
self.lock_hard_drop = true
self.instant_hard_drop = true
self.instant_soft_drop = false
self.enable_hold = true
self.next_queue_length = 3
end
function Race40Game:getDropSpeed()
return 20
end
function Race40Game:getARR()
return 0
end
function Race40Game:getARE()
return 0
end
function Race40Game:getLineARE()
return self:getARE()
end
function Race40Game:getDasLimit()
return 6
end
function Race40Game:getLineClearDelay()
return 0
end
function Race40Game:getLockDelay()
return 15
end
function Race40Game:getGravity()
return 1/64
end
function Race40Game:advanceOneFrame()
if self.clear then
self.roll_frames = self.roll_frames + 1
if self.roll_frames > 150 then
self.completed = true
end
return false
elseif self.ready_frames == 0 then
self.frames = self.frames + 1
end
return true
end
function Race40Game:onPieceLock()
self.pieces = self.pieces + 1
end
function Race40Game:onLineClear(cleared_row_count)
if not self.clear then
self.lines = self.lines + cleared_row_count
if self.lines >= self.line_goal then
self.clear = true
end
end
end
function Race40Game:drawGrid(ruleset)
self.grid:draw()
if self.piece ~= nil then
self:drawGhostPiece(ruleset)
end
end
function Race40Game:getHighscoreData()
return {
level = self.level,
frames = self.frames,
}
end
function Race40Game:drawScoringInfo()
Race40Game.super.drawScoringInfo(self)
love.graphics.setColor(1, 1, 1, 1)
local text_x = config["side_next"] and 320 or 240
love.graphics.setFont(font_3x5_2)
love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("LINES", text_x, 320, 40, "left")
love.graphics.printf("line/min", text_x, 160, 80, "left")
love.graphics.printf("piece/sec", text_x, 220, 80, "left")
love.graphics.setFont(font_3x5_3)
love.graphics.printf(string.format("%.02f", self.lines / math.max(1, self.frames) * 3600), text_x, 180, 80, "left")
love.graphics.printf(string.format("%.04f", self.pieces / math.max(1, self.frames) * 60), text_x, 240, 80, "left")
love.graphics.setFont(font_3x5_4)
love.graphics.printf(math.max(0, self.line_goal - self.lines), text_x, 340, 40, "left")
end
function Race40Game:getBackground()
return 2
end
return Race40Game

View File

@@ -16,7 +16,6 @@ StrategyGame.tagline = "You have lots of time to think! Can you use it to place
function StrategyGame:new()
StrategyGame.super:new()
self.level = 0
self.clear = false
self.completed = false
self.roll_frames = 0
@@ -84,7 +83,7 @@ function StrategyGame:advanceOneFrame()
end
function StrategyGame:onPieceEnter()
if (self.level % 100 ~= 99) and not self.clear and self.frames ~= 0 then
if (self.level % 100 ~= 99 and self.level ~= 998) and not self.clear and self.frames ~= 0 then
self.level = self.level + 1
end
end
@@ -99,17 +98,18 @@ function StrategyGame:onLineClear(cleared_row_count)
end
function StrategyGame:updateScore(level, drop_bonus, cleared_lines)
if not self.clear then
if cleared_lines > 0 then
self.combo = self.combo + (cleared_lines - 1) * 2
self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * (cleared_lines * 2 - 1) * (self.combo * 2 - 1)
cleared_lines * self.combo
)
self.lines = self.lines + cleared_lines
self.combo = self.combo + cleared_lines - 1
else
self.drop_bonus = 0
self.combo = 1
end
self.drop_bonus = 0
end
end
function StrategyGame:setNextOpacity(i)
@@ -135,11 +135,11 @@ function StrategyGame:drawScoringInfo()
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.score, text_x, 220, 90, "left")
love.graphics.printf(self.level, text_x, 340, 50, "right")
love.graphics.printf(self.level, text_x, 340, 40, "right")
if self.clear then
love.graphics.printf(self.level, text_x, 370, 50, "right")
love.graphics.printf(self.level, text_x, 370, 40, "right")
else
love.graphics.printf(math.floor(self.level / 100 + 1) * 100, text_x, 370, 50, "right")
love.graphics.printf(self.level < 900 and math.floor(self.level / 100 + 1) * 100 or 999, text_x, 370, 40, "right")
end
end

View File

@@ -27,6 +27,7 @@ function Survival2020Game:new()
self.randomizer = History6RollsRandomizer()
self.lock_drop = true
self.lock_hard_drop = true
self.enable_hold = true
self.next_queue_length = 3
end
@@ -174,17 +175,18 @@ function Survival2020Game:onLineClear(cleared_row_count)
end
function Survival2020Game:updateScore(level, drop_bonus, cleared_lines)
if not self.clear then
if cleared_lines > 0 then
self.combo = self.combo + (cleared_lines - 1) * 2
self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * (cleared_lines * 2 - 1) * (self.combo * 2 - 1)
cleared_lines * self.combo
)
self.lines = self.lines + cleared_lines
self.combo = self.combo + cleared_lines - 1
else
self.drop_bonus = 0
self.combo = 1
end
self.drop_bonus = 0
end
end
function Survival2020Game:updateSectionTimes(old_level, new_level)

View File

@@ -19,6 +19,8 @@ function SurvivalA1Game:new()
self.roll_frames = 0
self.combo = 1
self.bravos = 0
self.gm_conditions = {
level300 = false,
level500 = false,
@@ -107,7 +109,7 @@ function SurvivalA1Game:onLineClear(cleared_row_count)
self:checkGMRequirements(self.level, self.level + cleared_row_count)
if not self.clear then
local new_level = math.min(self.level + cleared_row_count, 999)
if self.level == 999 then
if new_level == 999 then
self.clear = true
else
self.level = new_level
@@ -116,31 +118,36 @@ function SurvivalA1Game:onLineClear(cleared_row_count)
end
function SurvivalA1Game:updateScore(level, drop_bonus, cleared_lines)
if not self.clear then
if self.grid:checkForBravo(cleared_lines) then
self.bravo = 4
self.bravos = self.bravos + 1
else self.bravo = 1 end
if cleared_lines > 0 then
self.combo = self.combo + (cleared_lines - 1) * 2
self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * (cleared_lines * 2 - 1) * self.combo
cleared_lines * self.combo * self.bravo
)
self.lines = self.lines + cleared_lines
self.combo = self.combo + (cleared_lines - 1) * 2
else
self.drop_bonus = 0
self.combo = 1
end
self.drop_bonus = 0
end
end
function SurvivalA1Game:checkGMRequirements(old_level, new_level)
if old_level < 300 and new_level >= 300 then
if self.score > 12000 and self.frames <= frameTime(4,15) then
if self.score >= 12000 and self.frames <= frameTime(4,15) then
self.gm_conditions["level300"] = true
end
elseif old_level < 500 and new_level >= 500 then
if self.score > 40000 and self.frames <= frameTime(7,30) then
if self.score >= 40000 and self.frames <= frameTime(7,30) then
self.gm_conditions["level500"] = true
end
elseif old_level < 999 and new_level >= 999 then
if self.score > 126000 and self.frames <= frameTime(13,30) then
self.gm_conditions["level900"] = true
if self.score >= 126000 and self.frames <= frameTime(13,30) then
self.gm_conditions["level999"] = true
end
end
end
@@ -169,9 +176,11 @@ function SurvivalA1Game:drawScoringInfo()
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
end
if self.bravos > 0 then love.graphics.printf("BRAVO", 300, 120, 40, "left") end
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.score, 240, 220, 90, "left")
if self.gm_conditions["level300"] and self.gm_conditions["level500"] and self.gm_conditions["level900"] then
if self.gm_conditions["level300"] and self.gm_conditions["level500"] and self.gm_conditions["level999"] then
love.graphics.printf("GM", 240, 140, 90, "left")
else
love.graphics.printf(getRankForScore(self.score).rank, 240, 140, 90, "left")
@@ -182,6 +191,7 @@ function SurvivalA1Game:drawScoringInfo()
if sg >= 5 then
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
end
if self.bravos > 0 then love.graphics.printf(self.bravos, 300, 140, 40, "left") end
love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")

View File

@@ -27,6 +27,7 @@ function SurvivalA2Game:new()
}
self.lock_drop = true
self.lock_hard_drop = true
end
function SurvivalA2Game:getARE()
@@ -88,7 +89,7 @@ function SurvivalA2Game:advanceOneFrame()
end
function SurvivalA2Game:onPieceEnter()
if (self.level % 100 ~= 99 or self.level == 998) and not self.clear and self.frames ~= 0 then
if (self.level % 100 ~= 99 and self.level ~= 998) and not self.clear and self.frames ~= 0 then
self.level = self.level + 1
end
end
@@ -98,6 +99,9 @@ function SurvivalA2Game:onLineClear(cleared_row_count)
local new_level = math.min(self.level + cleared_row_count, 999)
if self.level == 999 or self:hitTorikan(self.level, new_level) then
self.clear = true
if self.level < 999 then
self.game_over = true
end
else
self.level = new_level
end
@@ -105,22 +109,25 @@ function SurvivalA2Game:onLineClear(cleared_row_count)
end
function SurvivalA2Game:updateScore(level, drop_bonus, cleared_lines)
if not self.clear then
if self.grid:checkForBravo(cleared_lines) then self.bravo = 4 else self.bravo = 1 end
if cleared_lines > 0 then
self.combo = self.combo + (cleared_lines - 1) * 2
self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * (cleared_lines * 2 - 1) * self.combo
cleared_lines * self.combo * self.bravo
)
self.lines = self.lines + cleared_lines
self.combo = self.combo + (cleared_lines - 1) * 2
else
self.drop_bonus = 0
self.combo = 1
end
self.drop_bonus = 0
end
end
function SurvivalA2Game:getLetterGrade()
if self.level >= 999 then return "GM"
elseif self.level >= 500 then return "M"
elseif self.level > 500 then return "M"
elseif self.level == 500 and not self.clear then return "M"
else return "" end
end
@@ -141,7 +148,7 @@ function SurvivalA2Game:drawScoringInfo()
strTrueValues(self.prev_inputs)
)
love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("GRADE", text_x, 120, 40, "left")
if self:getLetterGrade() ~= "" then love.graphics.printf("GRADE", text_x, 120, 40, "left") end
love.graphics.printf("SCORE", text_x, 200, 40, "left")
love.graphics.printf("LEVEL", text_x, 320, 40, "left")
local sg = self.grid:checkSecretGrade()
@@ -151,7 +158,9 @@ function SurvivalA2Game:drawScoringInfo()
love.graphics.setFont(font_3x5_3)
love.graphics.printf(self.score, text_x, 220, 90, "left")
love.graphics.printf(self:getLetterGrade(), text_x, 140, 90, "left")
if self.roll_frames > 2968 then love.graphics.setColor(1, 0.5, 0, 1)
elseif self.clear then love.graphics.setColor(0, 1, 0, 1) end
if self:getLetterGrade() ~= "" then love.graphics.printf(self:getLetterGrade(), text_x, 140, 90, "left") end
love.graphics.printf(self.level, text_x, 340, 40, "right")
love.graphics.printf(self:getSectionEndLevel(), text_x, 370, 40, "right")
if sg >= 5 then

View File

@@ -12,11 +12,8 @@ SurvivalA3Game.hash = "SurvivalA3"
SurvivalA3Game.tagline = "The blocks turn black and white! Can you make it to level 1300?"
function SurvivalA3Game:new()
SurvivalA3Game.super:new()
self.level = 0
self.grade = 0
self.garbage = 0
self.clear = false
@@ -32,6 +29,7 @@ function SurvivalA3Game:new()
}
self.lock_drop = true
self.lock_hard_drop = true
self.enable_hold = true
self.next_queue_length = 3
@@ -39,6 +37,14 @@ function SurvivalA3Game:new()
self.coolregret_timer = 0
end
function SurvivalA3Game:initialize(ruleset)
self.torikan_time = frameTime(2,28)
if ruleset.world then self.torikan_time = frameTime(3,03) end
self.super.initialize(self, ruleset)
-- ^ notice the . here instead of the :
end
function SurvivalA3Game:getARE()
if self.level < 300 then return 12
else return 6 end
@@ -95,11 +101,11 @@ function SurvivalA3Game:getNextPiece(ruleset)
end
function SurvivalA3Game:hitTorikan(old_level, new_level)
if old_level < 500 and new_level >= 500 and self.frames > frameTime(2,28) then
if old_level < 500 and new_level >= 500 and self.frames > self.torikan_time then
self.level = 500
return true
end
if old_level < 1000 and new_level >= 1000 and self.frames > frameTime(4,56) then
if old_level < 1000 and new_level >= 1000 and self.frames > self.torikan_time*2 then
self.level = 1000
return true
end
@@ -132,20 +138,21 @@ function SurvivalA3Game:onPieceEnter()
end
local cleared_row_levels = {1, 2, 4, 6}
local cleared_row_points = {0.02, 0.05, 0.15, 0.6}
function SurvivalA3Game:onLineClear(cleared_row_count)
if not self.clear then
local new_level = self.level + cleared_row_levels[cleared_row_count]
self:updateSectionTimes(self.level, new_level)
if new_level >= 1300 or self:hitTorikan(self.level, new_level) then
self.clear = true
if new_level >= 1300 then
self.level = 1300
end
self.clear = true
self.grid:clear()
self.big_mode = true
self.roll_frames = -150
else
self.game_over = true
end
else
self.level = math.min(new_level, 1300)
end
@@ -154,21 +161,23 @@ function SurvivalA3Game:onLineClear(cleared_row_count)
end
function SurvivalA3Game:onPieceLock(piece, cleared_row_count)
self.super:onPieceLock()
if cleared_row_count == 0 then self:advanceBottomRow(1) end
end
function SurvivalA3Game:updateScore(level, drop_bonus, cleared_lines)
if not self.clear then
if cleared_lines > 0 then
self.combo = self.combo + (cleared_lines - 1) * 2
self.score = self.score + (
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
cleared_lines * (cleared_lines * 2 - 1) * (self.combo * 2 - 1)
cleared_lines * self.combo
)
self.lines = self.lines + cleared_lines
self.combo = self.combo + cleared_lines - 1
else
self.drop_bonus = 0
self.combo = 1
end
self.drop_bonus = 0
end
end
function SurvivalA3Game:updateSectionTimes(old_level, new_level)
@@ -233,7 +242,10 @@ function SurvivalA3Game:drawScoringInfo()
self:drawSectionTimesWithSplits(current_section)
love.graphics.setFont(font_3x5_3)
if self.roll_frames > 3238 then love.graphics.setColor(1, 0.5, 0, 1)
elseif self.clear then love.graphics.setColor(0, 1, 0, 1) end
love.graphics.printf(getLetterGrade(math.floor(self.grade)), text_x, 140, 90, "left")
love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(self.score, text_x, 220, 90, "left")
love.graphics.printf(self.level, text_x, 340, 50, "right")
if self.clear then

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,10 +17,10 @@ ARS.spawn_positions = {
}
ARS.big_spawn_positions = {
I = { x=2, y=2 },
I = { x=3, y=2 },
J = { x=2, y=3 },
L = { x=2, y=3 },
O = { x=2, y=3 },
O = { x=3, y=3 },
S = { x=2, y=3 },
T = { x=2, y=3 },
Z = { x=2, y=3 },
@@ -110,7 +110,14 @@ function ARS:onPieceDrop(piece, grid)
piece.lock_delay = 0 -- step reset
end
function ARS:get180RotationValue() return config["reverse_rotate"] and 1 or 3 end
function ARS:get180RotationValue()
if config.gamesettings.world_reverse == 3 then
return 3
else
return 1
end
end
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
return ARS

View File

@@ -6,6 +6,19 @@ local ARS = Ruleset:extend()
ARS.name = "ACE-ARS"
ARS.hash = "ArikaACE"
ARS.colourscheme = {
I = "C",
L = "O",
J = "B",
S = "G",
Z = "R",
O = "Y",
T = "M",
}
ARS.softdrop_lock = false
ARS.harddrop_lock = true
ARS.spawn_positions = {
I = { x=5, y=2 },
J = { x=4, y=3 },
@@ -17,10 +30,10 @@ ARS.spawn_positions = {
}
ARS.big_spawn_positions = {
I = { x=2, y=0 },
I = { x=3, y=0 },
J = { x=2, y=1 },
L = { x=2, y=1 },
O = { x=2, y=1 },
O = { x=3, y=1 },
S = { x=2, y=1 },
T = { x=2, y=1 },
Z = { x=2, y=1 },
@@ -131,7 +144,7 @@ function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
elseif piece.shape == "T"
and new_piece.rotation == 1
and new_piece.rotation == 0
and piece.floorkick == 0
and grid:canPlacePiece(new_piece:withOffset({x=0, y=-1}))
then
@@ -172,7 +185,14 @@ function ARS:onPieceRotate(piece, grid)
end
end
function ARS:get180RotationValue() return config["reverse_rotate"] and 1 or 3 end
function ARS:get180RotationValue()
if config.gamesettings.world_reverse == 3 then
return 3
else
return 1
end
end
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
return ARS

View File

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

View File

@@ -5,8 +5,18 @@ local SRS = Ruleset:extend()
SRS.name = "ACE-SRS"
SRS.hash = "ACE Standard"
SRS.enable_IRS_wallkicks = true
SRS.world = true
SRS.colourscheme = {
I = "C",
L = "O",
J = "B",
S = "G",
Z = "R",
O = "Y",
T = "M",
}
SRS.softdrop_lock = false
SRS.harddrop_lock = true
SRS.spawn_positions = {
I = { x=5, y=2 },
@@ -19,10 +29,10 @@ SRS.spawn_positions = {
}
SRS.big_spawn_positions = {
I = { x=2, y=0 },
I = { x=3, y=0 },
J = { x=2, y=1 },
L = { x=2, y=1 },
O = { x=2, y=1 },
O = { x=3, y=1 },
S = { x=2, y=1 },
T = { x=2, y=1 },
Z = { x=2, y=1 },
@@ -98,24 +108,24 @@ SRS.wallkicks_3x3 = {
SRS.wallkicks_line = {
[0]={
[1]={{x=-2, y=0}, {x=1, y=0}, {x=-2, y=1}, {x=1, y=-2}},
[1]={{x=-2, y= 0}, {x= 1, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
[2]={},
[3]={{x=-1, y=0}, {x=2, y=0}, {x=-1, y=-2}, {x=2, y=1}},
[3]={{x= 2, y= 0}, {x=-1, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
},
[1]={
[0]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 2}},
[2]={{x=-1, y= 0}, {x= 2, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
[3]={{x=0, y=1}, {x=0, y=-1}, {x=0, y=2}, {x=0, y=-2}},
[3]={},
},
[2]={
[0]={},
[1]={{x=1, y=0}, {x=-2, y=0}, {x=1, y=2}, {x=-2, y=-1}},
[3]={{x=2, y=0}, {x=-1, y=0}, {x=2, y=-1}, {x=-1, y=2}},
[1]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 1}},
[3]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 1}},
},
[3]={
[0]={{x=1, y=0}, {x=-2, y=0}, {x=1, y=2}, {x=-2, y=-1}},
[1]={{x=0, y=1}, {x=0, y=-1}, {x=0, y=2}, {x=0, y=-2}},
[2]={{x=-2, y=0}, {x=1, y=0}, {x=-2, y=1}, {x=1, y=-2}},
[0]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 2}},
[1]={},
[2]={{x= 1, y= 0}, {x=-2, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
},
};
@@ -174,4 +184,12 @@ function SRS:onPieceRotate(piece, grid)
end
end
function SRS:get180RotationValue()
if config.gamesettings.world_reverse == 1 then
return 1
else
return 3
end
end
return SRS

View File

@@ -17,10 +17,10 @@ ARS.spawn_positions = {
}
ARS.big_spawn_positions = {
I = { x=2, y=2 },
I = { x=3, y=2 },
J = { x=2, y=3 },
L = { x=2, y=3 },
O = { x=2, y=3 },
O = { x=3, y=3 },
S = { x=2, y=3 },
T = { x=2, y=3 },
Z = { x=2, y=3 },
@@ -131,7 +131,7 @@ function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
elseif piece.shape == "T"
and new_piece.rotation == 1
and new_piece.rotation == 0
and piece.floorkick == 0
and grid:canPlacePiece(new_piece:withOffset({x=0, y=-1}))
then
@@ -151,7 +151,14 @@ function ARS:onPieceDrop(piece, grid)
piece.lock_delay = 0 -- step reset
end
function ARS:get180RotationValue() return config["reverse_rotate"] and 1 or 3 end
function ARS:get180RotationValue()
if config.gamesettings.world_reverse == 3 then
return 3
else
return 1
end
end
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
return ARS

View File

@@ -5,25 +5,26 @@ local CRS = Ruleset:extend()
CRS.name = "Cambridge"
CRS.hash = "Cambridge"
CRS.world = true
CRS.spawn_positions = {
I = { x=5, y=4 },
J = { x=4, y=5 },
L = { x=4, y=5 },
O = { x=5, y=5 },
S = { x=4, y=4 },
S = { x=4, y=5 },
T = { x=4, y=5 },
Z = { x=4, y=4 },
Z = { x=4, y=5 },
}
CRS.big_spawn_positions = {
I = { x=2, y=2 },
I = { x=3, y=2 },
J = { x=2, y=3 },
L = { x=2, y=3 },
O = { x=2, y=3 },
S = { x=2, y=2 },
O = { x=3, y=3 },
S = { x=2, y=3 },
T = { x=2, y=3 },
Z = { x=2, y=2 },
Z = { x=2, y=3 },
}
CRS.block_offsets = {
@@ -52,10 +53,10 @@ CRS.block_offsets = {
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
},
S={
{ {x=-1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=1, y=0} },
{ {x=0, y=1}, {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1} },
{ {x=-1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=1, y=0} },
{ {x=0, y=1}, {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1} },
{ {x=-1, y=0}, {x=0, y=0}, {x=0, y=-1}, {x=1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=-1, y=-1}, {x=-1, y=-2} },
{ {x=-1, y=0}, {x=0, y=0}, {x=0, y=-1}, {x=1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=-1, y=-1}, {x=-1, y=-2} },
},
T={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} },
@@ -64,10 +65,10 @@ CRS.block_offsets = {
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=0} },
},
Z={
{ {x=1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=1, y=-1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=1} },
{ {x=1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=1, y=-1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=1} },
{ {x=1, y=0}, {x=0, y=0}, {x=0, y=-1}, {x=-1, y=-1} },
{ {x=1, y=-2}, {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0} },
{ {x=1, y=0}, {x=0, y=0}, {x=0, y=-1}, {x=-1, y=-1} },
{ {x=1, y=-2}, {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0} },
}
}
@@ -363,6 +364,10 @@ function CRS:attemptRotate(new_inputs, piece, grid, initial)
if rot_dir == 0 then return end
if self.world and config.gamesettings.world_reverse == 2 then
rot_dir = 4 - rot_dir
end
local new_piece = piece:withRelativeRotation(rot_dir)
self:attemptWallkicks(piece, new_piece, rot_dir, grid)
end

View File

@@ -6,10 +6,78 @@ local Ruleset = Object:extend()
Ruleset.name = ""
Ruleset.hash = ""
-- Arika-type ruleset defaults
Ruleset.world = false
Ruleset.colourscheme = {
I = "R",
L = "O",
J = "B",
S = "M",
Z = "G",
O = "Y",
T = "C",
}
Ruleset.softdrop_lock = true
Ruleset.harddrop_lock = false
Ruleset.enable_IRS_wallkicks = false
-- Component functions.
function Ruleset:new()
if config.gamesettings.piece_colour == 1 then
blocks["bone"] = (not self.world) and
{
R = love.graphics.newImage("res/img/bone.png"),
O = love.graphics.newImage("res/img/bone.png"),
Y = love.graphics.newImage("res/img/bone.png"),
G = love.graphics.newImage("res/img/bone.png"),
C = love.graphics.newImage("res/img/bone.png"),
B = love.graphics.newImage("res/img/bone.png"),
M = love.graphics.newImage("res/img/bone.png"),
F = love.graphics.newImage("res/img/bone.png"),
A = love.graphics.newImage("res/img/bone.png"),
X = love.graphics.newImage("res/img/bone.png"),
} or {
R = love.graphics.newImage("res/img/bonew.png"),
O = love.graphics.newImage("res/img/bonew.png"),
Y = love.graphics.newImage("res/img/bonew.png"),
G = love.graphics.newImage("res/img/bonew.png"),
C = love.graphics.newImage("res/img/bonew.png"),
B = love.graphics.newImage("res/img/bonew.png"),
M = love.graphics.newImage("res/img/bonew.png"),
F = love.graphics.newImage("res/img/bonew.png"),
A = love.graphics.newImage("res/img/bonew.png"),
X = love.graphics.newImage("res/img/bonew.png"),
}
else
blocks["bone"] = (config.gamesettings.piece_colour == 2) and
{
R = love.graphics.newImage("res/img/bone.png"),
O = love.graphics.newImage("res/img/bone.png"),
Y = love.graphics.newImage("res/img/bone.png"),
G = love.graphics.newImage("res/img/bone.png"),
C = love.graphics.newImage("res/img/bone.png"),
B = love.graphics.newImage("res/img/bone.png"),
M = love.graphics.newImage("res/img/bone.png"),
F = love.graphics.newImage("res/img/bone.png"),
A = love.graphics.newImage("res/img/bone.png"),
X = love.graphics.newImage("res/img/bone.png"),
} or {
R = love.graphics.newImage("res/img/bonew.png"),
O = love.graphics.newImage("res/img/bonew.png"),
Y = love.graphics.newImage("res/img/bonew.png"),
G = love.graphics.newImage("res/img/bonew.png"),
C = love.graphics.newImage("res/img/bonew.png"),
B = love.graphics.newImage("res/img/bonew.png"),
M = love.graphics.newImage("res/img/bonew.png"),
F = love.graphics.newImage("res/img/bonew.png"),
A = love.graphics.newImage("res/img/bonew.png"),
X = love.graphics.newImage("res/img/bonew.png"),
}
end
end
function Ruleset:rotatePiece(inputs, piece, grid, prev_inputs, initial)
local new_inputs = {}
@@ -39,6 +107,9 @@ function Ruleset:attemptRotate(new_inputs, piece, grid, initial)
end
if rot_dir == 0 then return end
if config.gamesettings.world_reverse == 3 or (self.world and config.gamesettings.world_reverse == 2) then
rot_dir = 4 - rot_dir
end
local new_piece = piece:withRelativeRotation(rot_dir)
@@ -117,10 +188,12 @@ function Ruleset:initializePiece(
else
spawn_positions = self.spawn_positions
end
local colours = ({self.colourscheme, ColourSchemes.Arika, ColourSchemes.TTC})[config.gamesettings.piece_colour]
local piece = Piece(data.shape, data.orientation - 1, {
x = spawn_positions[data.shape].x,
y = spawn_positions[data.shape].y
}, self.block_offsets, 0, 0, data.skin, big)
}, self.block_offsets, 0, 0, data.skin, colours[data.shape], big)
self:onPieceCreate(piece)
self:rotatePiece(inputs, piece, grid, {}, true)

View File

@@ -5,6 +5,18 @@ local SRS = Ruleset:extend()
SRS.name = "Guideline SRS"
SRS.hash = "Standard"
SRS.world = true
SRS.colourscheme = {
I = "C",
L = "O",
J = "B",
S = "G",
Z = "R",
O = "Y",
T = "M",
}
SRS.softdrop_lock = false
SRS.harddrop_lock = true
SRS.enable_IRS_wallkicks = true
@@ -19,10 +31,10 @@ SRS.spawn_positions = {
}
SRS.big_spawn_positions = {
I = { x=2, y=0 },
I = { x=3, y=0 },
J = { x=2, y=1 },
L = { x=2, y=1 },
O = { x=2, y=1 },
O = { x=3, y=1 },
S = { x=2, y=1 },
T = { x=2, y=1 },
Z = { x=2, y=1 },
@@ -119,6 +131,16 @@ SRS.wallkicks_line = {
},
};
function SRS:check_new_low(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.lowest_y = y
end
end
end
-- Component functions.
function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
@@ -147,19 +169,24 @@ function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
end
function SRS:onPieceCreate(piece, grid)
piece.rotate_counter = 0
piece.move_counter = 0
piece.manipulations = 0
piece.lowest_y = -math.huge
end
function SRS:onPieceDrop(piece, grid)
self:check_new_low(piece)
if piece.manipulations >= 15 and piece:isDropBlocked(grid) then
piece.locked = true
else
piece.lock_delay = 0 -- step reset
end
end
function SRS:onPieceMove(piece, grid)
piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then
piece.move_counter = piece.move_counter + 1
if piece.move_counter >= 24 then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= 15 then
piece.locked = true
end
end
@@ -167,9 +194,10 @@ end
function SRS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- rotate reset
self:check_new_low(piece)
piece.manipulations = piece.manipulations + 1
if piece:isDropBlocked(grid) then
piece.rotate_counter = piece.rotate_counter + 1
if piece.rotate_counter >= 12 then
if piece.manipulations >= 15 then
piece.locked = true
end
end

View File

@@ -5,27 +5,37 @@ local SRS = Ruleset:extend()
SRS.name = "Ti-World"
SRS.hash = "Bad I-kicks"
SRS.enable_IRS_wallkicks = true
SRS.world = true
SRS.colourscheme = {
I = "C",
L = "O",
J = "B",
S = "G",
Z = "R",
O = "Y",
T = "M",
}
SRS.softdrop_lock = false
SRS.harddrop_lock = true
SRS.spawn_positions = {
I = { x=5, y=2 },
J = { x=4, y=3 },
L = { x=4, y=3 },
O = { x=5, y=3 },
S = { x=4, y=3 },
T = { x=4, y=3 },
Z = { x=4, y=3 },
I = { x=5, y=4 },
J = { x=4, y=5 },
L = { x=4, y=5 },
O = { x=5, y=5 },
S = { x=4, y=5 },
T = { x=4, y=5 },
Z = { x=4, y=5 },
}
SRS.big_spawn_positions = {
I = { x=2, y=0 },
J = { x=2, y=1 },
L = { x=2, y=1 },
O = { x=2, y=1 },
S = { x=2, y=1 },
T = { x=2, y=1 },
Z = { x=2, y=1 },
I = { x=3, y=2 },
J = { x=2, y=3 },
L = { x=2, y=3 },
O = { x=3, y=3 },
S = { x=2, y=3 },
T = { x=2, y=3 },
Z = { x=2, y=3 },
}
SRS.block_offsets = {
@@ -98,24 +108,24 @@ SRS.wallkicks_3x3 = {
SRS.wallkicks_line = {
[0]={
[1]={{x=-2, y=0}, {x=1, y=0}, {x=-2, y=1}, {x=1, y=-2}},
[1]={{x=-2, y= 0}, {x= 1, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
[2]={},
[3]={{x=-1, y=0}, {x=2, y=0}, {x=-1, y=-2}, {x=2, y=1}},
[3]={{x= 2, y= 0}, {x=-1, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
},
[1]={
[0]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 2}},
[2]={{x=-1, y= 0}, {x= 2, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
[3]={{x=0, y=1}, {x=0, y=-1}, {x=0, y=2}, {x=0, y=-2}},
[3]={},
},
[2]={
[0]={},
[1]={{x=1, y=0}, {x=-2, y=0}, {x=1, y=2}, {x=-2, y=-1}},
[3]={{x=2, y=0}, {x=-1, y=0}, {x=2, y=-1}, {x=-1, y=2}},
[1]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 1}},
[3]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 1}},
},
[3]={
[0]={{x=1, y=0}, {x=-2, y=0}, {x=1, y=2}, {x=-2, y=-1}},
[1]={{x=0, y=1}, {x=0, y=-1}, {x=0, y=2}, {x=0, y=-2}},
[2]={{x=-2, y=0}, {x=1, y=0}, {x=-2, y=1}, {x=1, y=-2}},
[0]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 2}},
[1]={},
[2]={{x= 1, y= 0}, {x=-2, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
},
};
@@ -148,6 +158,7 @@ end
function SRS:onPieceCreate(piece, grid)
piece.manipulations = 0
piece.rotations = 0
end
function SRS:onPieceDrop(piece, grid)
@@ -158,7 +169,7 @@ function SRS:onPieceMove(piece, grid)
piece.lock_delay = 0 -- move reset
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= 8 then
if piece.manipulations >= 10 then
piece.locked = true
end
end
@@ -167,11 +178,19 @@ end
function SRS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- rotate reset
if piece:isDropBlocked(grid) then
piece.manipulations = piece.manipulations + 1
if piece.manipulations >= 8 then
piece.rotations = piece.rotations + 1
if piece.rotations >= 8 then
piece.locked = true
end
end
end
function SRS:get180RotationValue()
if config.gamesettings.world_reverse == 1 then
return 1
else
return 3
end
end
return SRS

View File

@@ -1,102 +0,0 @@
Piece = require("tetris.components.piece")
local BONKERS = {}
BONKERS.name = "B.O.N.K.E.R.S."
BONKERS.hash = "Bonkers"
BONKERS.spawn_positions = {
I = { x=5, y=4 },
J = { x=4, y=5 },
L = { x=4, y=5 },
O = { x=5, y=5 },
S = { x=4, y=5 },
T = { x=4, y=5 },
Z = { x=4, y=5 },
}
BONKERS.block_offsets = {
I={
{ {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=0, y=2} },
{ {x=0, y=1}, {x=-1, y=1}, {x=-2, y=1}, {x=1, y=1} },
{ {x=-1, y=0}, {x=-1, y=-1}, {x=-1, y=1}, {x=-1, y=2} },
},
J={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=-1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1} , {x=1, y=-1} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=1, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=1} },
},
L={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=1} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=-1, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=-1} },
},
O={
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
},
S={
{ {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=1, y=1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=-1} },
{ {x=-1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=1, y=0} },
{ {x=-1, y=-1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=1} },
},
T={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=0} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=0, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=0} },
},
Z={
{ {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=1, y=0} },
{ {x=1, y=-1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=1} },
{ {x=1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=-1, y=1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=-1} },
}
}
-- Component functions.
function BONKERS:attemptWallkicks(piece, new_piece, rot_dir, grid)
if piece.shape == "O" then
break
elseif piece.shape == "I" then
horizontal_kicks = {0, 1, -1, 2, -2}
else
horizontal_kicks = {0, 1, -1}
end
for y_offset = 20, new_piece.position.y - 24, -1 do
for idx, x_offset in pairs(horizontal_kicks) do
local offset = {x=x_offset, y=y_offset}
kicked_piece = new_piece:withOffset(offset)
if grid:canPlacePiece(kicked_piece) then
piece:setRelativeRotation(rot_dir)
piece:setOffset(offset)
piece.lock_delay = 0 -- rotate reset
return
end
end
end
end
function BONKERS:onPieceDrop(piece, grid)
piece.lock_delay = 0 -- step reset
end
function BONKERS:onPieceMove(piece, grid)
piece.lock_delay = 0 -- move reset
end
function BONKERS:onPieceRotate(piece, grid)
piece.lock_delay = 0 -- rotate reset
end
return BONKERS

View File

@@ -1,235 +0,0 @@
Piece = require("tetris.components.piece")
require("funcs")
local SRS = {}
SRS.name = "SHIRASE"
SRS.hash = "Shirase"
SRS.spawn_positions = {
I = { x=5, y=4 },
J = { x=4, y=5 },
L = { x=4, y=5 },
O = { x=5, y=5 },
S = { x=4, y=5 },
T = { x=4, y=5 },
Z = { x=4, y=5 },
}
SRS.block_offsets = {
I={
{ {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=0, y=2} },
{ {x=0, y=1}, {x=-1, y=1}, {x=-2, y=1}, {x=1, y=1} },
{ {x=-1, y=0}, {x=-1, y=-1}, {x=-1, y=1}, {x=-1, y=2} },
},
J={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=-1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1} , {x=1, y=-1} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=1, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=1} },
},
L={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=1} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=-1, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=-1} },
},
O={
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
},
S={
{ {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=1, y=1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=-1} },
{ {x=-1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=1, y=0} },
{ {x=-1, y=-1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=1} },
},
T={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=0} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=0, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=0} },
},
Z={
{ {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=1, y=0} },
{ {x=1, y=-1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=1} },
{ {x=1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=-1, y=1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=-1} },
}
}
SRS.wallkicks_3x3 = {
[0]={
[1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}},
[2]={{x=0, y=1}, {x=0, y=-1}},
[3]={{x=1, y=0}, {x=1, y=-1}, {x=0, y=2}, {x=1, y=2}},
},
[1]={
[0]={{x=1, y=0}, {x=1, y=1}, {x=0, y=-2}, {x=1, y=-2}},
[2]={{x=1, y=0}, {x=1, y=1}, {x=0, y=-2}, {x=1, y=-2}},
[3]={{x=0, y=1}, {x=0, y=-1}},
},
[2]={
[0]={{x=0, y=1}, {x=0, y=-1}},
[1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}},
[3]={{x=1, y=0}, {x=1, y=-1}, {x=0, y=2}, {x=1, y=2}},
},
[3]={
[0]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
[1]={{x=0, y=1}, {x=0, y=-1}},
[2]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
},
};
SRS.wallkicks_line = {
[0]={
[1]={{x=-2, y=0}, {x=1, y=0}, {x=-2, y=1}, {x=1, y=-2}},
[2]={},
[3]={{x=-1, y=0}, {x=2, y=0}, {x=-1, y=-2}, {x=2, y=1}},
},
[1]={
[0]={{x=2, y=0}, {x=-1, y=0}, {x=2, y=-1}, {x=-1, y=2}},
[2]={{x=-1, y=0}, {x=2, y=0}, {x=-1, y=-2}, {x=2, y=1}},
[3]={{x=0, y=1}, {x=0, y=-1}, {x=0, y=2}, {x=0, y=-2}},
},
[2]={
[0]={},
[1]={{x=1, y=0}, {x=-2, y=0}, {x=1, y=2}, {x=-2, y=-1}},
[3]={{x=2, y=0}, {x=-1, y=0}, {x=2, y=-1}, {x=-1, y=2}},
},
[3]={
[0]={{x=1, y=0}, {x=-2, y=0}, {x=1, y=2}, {x=-2, y=-1}},
[1]={{x=0, y=1}, {x=0, y=-1}, {x=0, y=2}, {x=0, y=-2}},
[2]={{x=-2, y=0}, {x=1, y=0}, {x=-2, y=1}, {x=1, y=-2}},
},
};
local basicOffsets = {
[0] = { x = 1, y = 0 },
[1] = { x = 0, y = 1 },
[2] = { x = -1, y = 0 },
[3] = { x = 0, y = -1 }
}
-- Component functions.
local function rotatePiece(inputs, piece, grid, prev_inputs)
local new_inputs = {}
for input, value in pairs(inputs) do
if value and not prev_inputs[input] then
new_inputs[input] = true
end
end
local rot_dir = 0
if (new_inputs["rotate_left"] or new_inputs["rotate_left2"]) then
rot_dir = 3
elseif (new_inputs["rotate_right"] or new_inputs["rotate_right2"]) then
rot_dir = 1
elseif (new_inputs["rotate_180"]) then
rot_dir = 2
end
while rot_dir ~= 0 do
rotated_piece = piece:withRelativeRotation(rot_dir)
rotation_offset = vAdd(
basicOffsets[piece.rotation],
vNeg(basicOffsets[rotated_piece.rotation])
)
new_piece = rotated_piece:withOffset(rotation_offset)
if (grid:canPlacePiece(new_piece)) then
piece:setRelativeRotation(rot_dir)
piece:setOffset(rotation_offset)
piece.lock_delay = 0 -- rotate reset
break
end
if piece.shape == "I" then
kicks = SRS.wallkicks_line[piece.rotation][new_piece.rotation]
else
kicks = SRS.wallkicks_3x3[piece.rotation][new_piece.rotation]
end
for idx, offset in pairs(kicks) do
kicked_piece = new_piece:withOffset(offset)
if grid:canPlacePiece(kicked_piece) then
piece:setRelativeRotation(rot_dir)
piece:setOffset(vAdd(offset, rotation_offset))
piece.lock_delay = 0 -- rotate reset
rot_dir = 0
end
if rot_dir == 0 then
break
end
end
rot_dir = 0
end
-- prev_inputs becomes the previous inputs
for input, value in pairs(inputs) do
prev_inputs[input] = inputs[input]
end
end
local function movePiece(piece, grid, move)
if move == "left" then
if not piece:isMoveBlocked(grid, {x=-1, y=0}) then
piece.lock_delay = 0 -- move reset
end
piece:moveInGrid({x=-1, y=0}, 1, grid)
elseif move == "right" then
if not piece:isMoveBlocked(grid, {x=1, y=0}) then
piece.lock_delay = 0 -- move reset
end
piece:moveInGrid({x=1, y=0}, 1, grid)
end
end
local function dropPiece(inputs, piece, grid, gravity, drop_speed, drop_locked)
local y = piece.position.y
if inputs["down"] == true and drop_locked == false then
piece:addGravity(gravity + 1, grid):lockIfBottomed(grid)
elseif inputs["up"] == true then
if piece:isDropBlocked(grid) then
return
end
piece:dropToBottom(grid)
else
piece:addGravity(gravity, grid)
end
if piece.position.y ~= y then -- step reset
piece.lock_delay = 0
end
end
local function lockPiece(piece, grid, lock_delay)
if piece:isDropBlocked(grid) and piece.lock_delay >= lock_delay then
piece.locked = true
end
end
function SRS.initializePiece(inputs, data, grid, gravity, prev_inputs, move, lock_delay, drop_speed, drop_locked)
local piece = Piece(shape, 0, {
x = SRS.spawn_positions[shape].x,
y = SRS.spawn_positions[shape].y
}, SRS.block_offsets, 0, 0)
-- have to copy that object otherwise it gets referenced
rotatePiece(inputs, piece, grid, {})
dropPiece(inputs, piece, grid, gravity, drop_speed, drop_locked)
return piece
end
function SRS.processPiece(inputs, piece, grid, gravity, prev_inputs, move, lock_delay, drop_speed, drop_locked)
rotatePiece(inputs, piece, grid, prev_inputs)
movePiece(piece, grid, move)
dropPiece(inputs, piece, grid, gravity, drop_speed, drop_locked)
lockPiece(piece, grid, lock_delay)
end
return SRS

View File

@@ -1,174 +0,0 @@
Piece = require("tetris.components.piece")
local BONKERS = {}
BONKERS.name = "SUPER302"
BONKERS.hash = "Super302"
BONKERS.spawn_positions = {
I = { x=5, y=4 },
J = { x=4, y=5 },
L = { x=4, y=5 },
O = { x=5, y=5 },
S = { x=4, y=5 },
T = { x=4, y=5 },
Z = { x=4, y=5 },
}
BONKERS.block_offsets = {
I={
{ {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=0, y=2} },
{ {x=0, y=1}, {x=-1, y=1}, {x=-2, y=1}, {x=1, y=1} },
{ {x=-1, y=0}, {x=-1, y=-1}, {x=-1, y=1}, {x=-1, y=2} },
},
J={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=-1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1} , {x=1, y=-1} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=1, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=1} },
},
L={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=1, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=1} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=-1, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=-1} },
},
O={
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
},
S={
{ {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=1, y=1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=-1} },
{ {x=-1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=1, y=0} },
{ {x=-1, y=-1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=1} },
},
T={
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} },
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=0} },
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=0, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=0} },
},
Z={
{ {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=1, y=0} },
{ {x=1, y=-1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=1} },
{ {x=1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=-1, y=0} },
{ {x=-1, y=1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=-1} },
}
}
-- Component functions.
local function rotatePiece(inputs, piece, grid, prev_inputs)
local new_inputs = {}
for input, value in pairs(inputs) do
if value and not prev_inputs[input] then
new_inputs[input] = true
end
end
local rot_dir = 0
if (new_inputs["rotate_left"] or new_inputs["rotate_left2"]) then
rot_dir = 3
elseif (new_inputs["rotate_right"] or new_inputs["rotate_right2"]) then
rot_dir = 1
elseif (new_inputs["rotate_180"]) then
rot_dir = 2
end
while rot_dir ~= 0 do
if piece.filled then break end
new_piece = piece:withRelativeRotation(rot_dir)
if (grid:canPlacePiece(new_piece)) and piece.shape ~= "O" then
piece:setRelativeRotation(rot_dir)
piece.lock_delay = 0 -- rotate reset
else
-- set the piece to occupy the whole grid
piece.filled = true
unfilled_block_offsets = {}
for y = 4, 23 do
for x = 0, 9 do
if not grid:isOccupied(x, y) then
table.insert(unfilled_block_offsets, {x=x, y=y})
end
end
end
piece.position = {x=0, y=0}
piece.getBlockOffsets = function(piece)
return unfilled_block_offsets
end
piece.isDropBlocked = function(piece)
return true
end
end
rot_dir = 0
end
-- prev_inputs becomes the previous inputs
for input, value in pairs(inputs) do
prev_inputs[input] = inputs[input]
end
end
local function movePiece(piece, grid, move)
if move == "left" then
if not piece:isMoveBlocked(grid, {x=-1, y=0}) then
piece.lock_delay = 0 -- move reset
end
piece:moveInGrid({x=-1, y=0}, 1, grid)
elseif move == "right" then
if not piece:isMoveBlocked(grid, {x=1, y=0}) then
piece.lock_delay = 0 -- move reset
end
piece:moveInGrid({x=1, y=0}, 1, grid)
end
end
local function dropPiece(inputs, piece, grid, gravity, drop_speed, drop_locked)
local y = piece.position.y
if inputs["down"] == true and drop_locked == false then
piece:addGravity(gravity + 1, grid):lockIfBottomed(grid)
elseif inputs["up"] == true then
if piece:isDropBlocked(grid) then
return
end
piece:dropToBottom(grid)
else
piece:addGravity(gravity, grid)
end
if piece.position.y ~= y then -- step reset
piece.lock_delay = 0
end
end
local function lockPiece(piece, grid, lock_delay)
if piece:isDropBlocked(grid) and piece.lock_delay >= lock_delay then
piece.locked = true
end
end
function BONKERS.initializePiece(inputs, data, grid, gravity, prev_inputs, move, lock_delay, drop_speed, drop_locked)
local piece = Piece(shape, 0, {
x = BONKERS.spawn_positions[shape].x,
y = BONKERS.spawn_positions[shape].y
}, BONKERS.block_offsets, 0, 0)
-- have to copy that object otherwise it gets referenced
rotatePiece(inputs, piece, grid, {})
dropPiece(inputs, piece, grid, gravity, drop_speed, drop_locked)
return piece
end
function BONKERS.processPiece(inputs, piece, grid, gravity, prev_inputs, move, lock_delay, drop_speed, drop_locked)
rotatePiece(inputs, piece, grid, prev_inputs)
movePiece(piece, grid, move)
dropPiece(inputs, piece, grid, gravity, drop_speed, drop_locked)
lockPiece(piece, grid, lock_delay)
end
return BONKERS

View File

@@ -1,133 +0,0 @@
local Piece = require 'tetris.components.piece'
local Ruleset = require 'tetris.rulesets.ruleset'
local Tengen = Ruleset:extend()
Tengen.name = "Tengen"
Tengen.hash = "Tengen"
Tengen.spawn_positions = {
I = { x=3, y=4 },
J = { x=4, y=4 },
L = { x=4, y=4 },
O = { x=5, y=4 },
S = { x=4, y=4 },
T = { x=4, y=4 },
Z = { x=4, y=4 },
}
Tengen.block_offsets = {
I={
{ {x=0, y=0}, {x=1, y=0}, {x=2, y=0}, {x=3, y=0} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=2}, {x=0, y=3} },
{ {x=0, y=0}, {x=1, y=0}, {x=2, y=0}, {x=3, y=0} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=2}, {x=0, y=3} },
},
J={
{ {x=0, y=0}, {x=1, y=0}, {x=2, y=0}, {x=2, y=1} },
{ {x=1, y=0}, {x=1, y=1}, {x=1, y=2}, {x=0, y=2} },
{ {x=0, y=0}, {x=0, y=1}, {x=1, y=1}, {x=2, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=2}, {x=1, y=0} },
},
L={
{ {x=0, y=0}, {x=1, y=0}, {x=2, y=0}, {x=0, y=1} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=2}, {x=1, y=2} },
{ {x=2, y=0}, {x=0, y=1}, {x=1, y=1}, {x=2, y=1} },
{ {x=0, y=0}, {x=1, y=0}, {x=1, y=1}, {x=1, y=2} },
},
O={
{ {x=0, y=0}, {x=1, y=0}, {x=1, y=1}, {x=0, y=1} },
{ {x=0, y=0}, {x=1, y=0}, {x=1, y=1}, {x=0, y=1} },
{ {x=0, y=0}, {x=1, y=0}, {x=1, y=1}, {x=0, y=1} },
{ {x=0, y=0}, {x=1, y=0}, {x=1, y=1}, {x=0, y=1} },
},
-- up to here
S={
{ {x=0, y=0}, {x=1, y=0}, {x=2, y=0}, {x=3, y=0} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=2}, {x=0, y=3} },
{ {x=0, y=0}, {x=1, y=0}, {x=2, y=0}, {x=3, y=0} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=2}, {x=0, y=3} },
},
T={
{ {x=0, y=0}, {x=1, y=0}, {x=2, y=0}, {x=3, y=0} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=2}, {x=0, y=3} },
{ {x=0, y=0}, {x=1, y=0}, {x=2, y=0}, {x=3, y=0} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=2}, {x=0, y=3} },
},
Z={
{ {x=0, y=0}, {x=1, y=0}, {x=2, y=0}, {x=3, y=0} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=2}, {x=0, y=3} },
{ {x=0, y=0}, {x=1, y=0}, {x=2, y=0}, {x=3, y=0} },
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=2}, {x=0, y=3} },
}
}
-- Component functions.
function Tengen:attemptWallkicks(piece, new_piece, rot_dir, grid)
-- O doesn't kick
if (piece.shape == "O") then return end
-- center column rule
if (
piece.shape == "J" or piece.shape == "T" or piece.shape == "L"
) and (
piece.rotation == 0 or piece.rotation == 2
) and (
grid:isOccupied(piece.position.x, piece.position.y) or
grid:isOccupied(piece.position.x, piece.position.y - 1) or
grid:isOccupied(piece.position.x, piece.position.y - 2)
) then return end
if piece.shape == "I" then
-- special kick rules for I
if new_piece.rotation == 0 or new_piece.rotation == 2 then
-- kick right, right2, left
if grid:canPlacePiece(new_piece:withOffset({x=1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
self:onPieceRotate(piece, grid)
elseif grid:canPlacePiece(new_piece:withOffset({x=2, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=2, y=0})
self:onPieceRotate(piece, grid)
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
self:onPieceRotate(piece, grid)
end
elseif piece:isDropBlocked(grid) and (new_piece.rotation == 1 or new_piece.rotation == 3) and piece.floorkick == 0 then
-- kick up, up2
if grid:canPlacePiece(new_piece:withOffset({x=0, y=-1})) then
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
self:onPieceRotate(piece, grid)
piece.floorkick = 1
elseif grid:canPlacePiece(new_piece:withOffset({x=0, y=-2})) then
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-2})
self:onPieceRotate(piece, grid)
piece.floorkick = 1
end
end
elseif piece.shape ~= "I" then
-- kick right, kick left
if (grid:canPlacePiece(new_piece:withOffset({x=1, y=0}))) then
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
elseif (grid:canPlacePiece(new_piece:withOffset({x=-1, y=0}))) then
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
end
else
end
end
function Tengen:onPieceCreate(piece, grid)
piece.floorkick = 0
end
function Tengen:onPieceDrop(piece, grid)
piece.lock_delay = 0 -- step reset
end
function Tengen:get180RotationValue() return config["reverse_rotate"] and 1 or 3 end
function Tengen:getDefaultOrientation() return 3 end -- downward facing pieces by default
return Tengen