mirror of
https://github.com/SashLilac/cambridge.git
synced 2025-05-13 20:21:25 -05:00
Compare commits
196 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
351fb4cfe9 | ||
|
|
103f04ceaa | ||
|
|
88d2f0d8d1 | ||
|
|
e100289c82 | ||
|
|
e38da49180 | ||
|
|
b03473d2fe | ||
|
|
cf6e0be4e7 | ||
|
|
2bc9dc179c | ||
|
|
d626926d5a | ||
|
|
721acefea0 | ||
|
|
b9b71e90bb | ||
|
|
9f61b139fd | ||
|
|
3b0fdba27d | ||
|
|
d8fad3dc37 | ||
|
|
f9368fa806 | ||
|
|
189feb1802 | ||
|
|
dc09dabacb | ||
|
|
e13278c6a8 | ||
|
|
f7f11b0e22 | ||
|
|
869a0f7ec5 | ||
|
|
10a9d97848 | ||
|
|
a470b40def | ||
|
|
fd739dcfdf | ||
|
|
e1dc01d0d0 | ||
|
|
7228707241 | ||
|
|
af86ce3a98 | ||
|
|
78ae0ae671 | ||
|
|
c614e9c4cd | ||
|
|
b27ef0e9f4 | ||
|
|
843b1e108a | ||
|
|
a8d697064c | ||
|
|
cf32474898 | ||
|
|
6a295cad59 | ||
|
|
2d80e20c82 | ||
|
|
2279c24d11 | ||
|
|
8510ad9bea | ||
|
|
6b77ad8547 | ||
|
|
6834e92674 | ||
|
|
3479374686 | ||
|
|
863c614a4c | ||
|
|
49e52c6a39 | ||
|
|
a105086ca6 | ||
|
|
1b381c4bf3 | ||
|
|
91a87fea73 | ||
|
|
28b455fcc0 | ||
|
|
2e3eff025f | ||
|
|
4670cb7c15 | ||
|
|
9b04e14388 | ||
|
|
f52da36bf7 | ||
|
|
76142c1dff | ||
|
|
a3458e2413 | ||
|
|
7eba9c012f | ||
|
|
4d2868b7b6 | ||
|
|
2e6fcd232b | ||
|
|
10833f2ec1 | ||
|
|
abb2b9491e | ||
|
|
062ab2005e | ||
|
|
468025fc80 | ||
|
|
c8544975d6 | ||
|
|
6776229bfb | ||
|
|
84b4dc5073 | ||
|
|
35dafb6615 | ||
|
|
3641d85fcb | ||
|
|
9b89c4d1de | ||
|
|
2dba120919 | ||
|
|
9224f271b1 | ||
|
|
febb5d546c | ||
|
|
c6482c423e | ||
|
|
6beb313c6b | ||
|
|
eb70f55b6e | ||
|
|
0badcde9ad | ||
|
|
6f39b591d3 | ||
|
|
129237f0b0 | ||
|
|
741c246244 | ||
|
|
b5937af8b2 | ||
|
|
33b8533d8e | ||
|
|
69959ff687 | ||
|
|
f91cd99dfd | ||
|
|
be59727ca5 | ||
|
|
cca295066c | ||
|
|
f2862b4d93 | ||
|
|
2aafd30253 | ||
|
|
b27ba335ba | ||
|
|
33244736b8 | ||
|
|
a324e0015a | ||
|
|
285108ca08 | ||
|
|
4b1fed727c | ||
|
|
d38168ca00 | ||
|
|
b0ce0f17f5 | ||
|
|
9fca272e8d | ||
|
|
5a21c8244b | ||
|
|
4923b2e2d4 | ||
|
|
8810f24e7a | ||
|
|
57a9f6ef55 | ||
|
|
342036bc28 | ||
|
|
78dcfe43c4 | ||
|
|
cdf6b5cf33 | ||
|
|
e6a60b0021 | ||
|
|
5f29c987f2 | ||
|
|
608d75b1ac | ||
|
|
1427c0d19e | ||
|
|
e221a91d73 | ||
|
|
bdcd25b82c | ||
|
|
a5158e0994 | ||
|
|
d946b17e13 | ||
|
|
69a5c0a21a | ||
|
|
b6423c3335 | ||
|
|
5b960d7291 | ||
|
|
54f4b0b890 | ||
|
|
8c62f321a0 | ||
|
|
fdffd2cd9a | ||
|
|
8ddf468121 | ||
|
|
8e77407ff2 | ||
|
|
92c852d178 | ||
|
|
f658ed63f2 | ||
|
|
c2d1c1183c | ||
|
|
36c568feaf | ||
|
|
bf30fcefbd | ||
|
|
d9f5bd16d7 | ||
|
|
d978ff8d87 | ||
|
|
b47d0f36b9 | ||
|
|
abc210c69c | ||
|
|
436e4ac861 | ||
|
|
a48d7c67b5 | ||
|
|
f6ca79ff91 | ||
|
|
4eb3901610 | ||
|
|
3f7fc4b622 | ||
|
|
ac7ae91c39 | ||
|
|
0c8e910245 | ||
|
|
6233ffb12d | ||
|
|
1f78bb9e99 | ||
|
|
a125c09106 | ||
|
|
090ffa5126 | ||
|
|
12a6f42198 | ||
|
|
0c317d9ce1 | ||
|
|
eddfee566d | ||
|
|
7fe366a8de | ||
|
|
55be30c99f | ||
|
|
36ceef8488 | ||
|
|
b59edb5e8e | ||
|
|
5d32b6a3e7 | ||
|
|
05230ac046 | ||
|
|
f28dc08ae2 | ||
|
|
ecd958bdc5 | ||
|
|
43f59cfde8 | ||
|
|
00c46961f9 | ||
|
|
0a0053276b | ||
|
|
d0f1d869a8 | ||
|
|
29ee000998 | ||
|
|
995fd7fee9 | ||
|
|
67abf35a28 | ||
|
|
9982613e26 | ||
|
|
629beb7240 | ||
|
|
4cb20101b0 | ||
|
|
63c0721978 | ||
|
|
1366451a3d | ||
|
|
6b624b9853 | ||
|
|
b0bda25466 | ||
|
|
2bde9d1378 | ||
|
|
6178b2cee9 | ||
|
|
4e8a237de3 | ||
|
|
5606251ea7 | ||
|
|
a4984fd687 | ||
|
|
b7ef7d1976 | ||
|
|
293b7398a2 | ||
|
|
8a2237a77c | ||
|
|
bdad32ac79 | ||
|
|
3cc918841f | ||
|
|
5f7ea0648e | ||
|
|
5d34218b97 | ||
|
|
fcd8b0f360 | ||
|
|
b983e1c108 | ||
|
|
36ab451b70 | ||
|
|
8fef7faa6a | ||
|
|
f13e2096b2 | ||
|
|
6b7f18d58a | ||
|
|
d5ce2ee9ba | ||
|
|
f04b57e7eb | ||
|
|
be644bf57b | ||
|
|
9d15feef33 | ||
|
|
634a5bc03b | ||
|
|
f1ad1f0ea4 | ||
|
|
a534331b11 | ||
|
|
d602fdfc7e | ||
|
|
971151e210 | ||
|
|
593cad0e71 | ||
|
|
1254de15d5 | ||
|
|
5c5ffc6887 | ||
|
|
5131061e42 | ||
|
|
209e60e82e | ||
|
|
c4ba80b60d | ||
|
|
2ba957f65a | ||
|
|
321de8564c | ||
|
|
96ac054cf6 | ||
|
|
b7fc51f4bd | ||
|
|
04ccd628be |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,3 +2,5 @@
|
||||
*.love
|
||||
dist/*.zip
|
||||
dist/**/cambridge.exe
|
||||
dist/**/libs
|
||||
dist/**/*.md
|
||||
120
CONTRIBUTING.md
120
CONTRIBUTING.md
@@ -1,33 +1,3 @@
|
||||
Coding conventions
|
||||
------------------
|
||||
|
||||
* Use tabs to indent, spaces to align.
|
||||
* Specifically, spaces should not appear at the beginning of a line, and tabs should not appear _except_ at the beginning of a line.
|
||||
* The sole exception is in a multiline `if` statement; the initial `if` should have four spaces before it to align it with an `elseif` on the next line. For example:
|
||||
|
||||
```lua
|
||||
if self.level < 900 then return 12
|
||||
elseif self.level < 1200 then return 8
|
||||
else return 6 end
|
||||
```
|
||||
|
||||
* Comments at the end of lines of code must be one line long. Multi-line comments must appear in their own block.
|
||||
|
||||
```lua
|
||||
if self.piece:isDropBlocked(self.grid) then
|
||||
-- for bottomed out pieces, decrease the drop bonus if they stall on dropping it
|
||||
self.drop_bonus = math.min(self.drop_bonus - 1, 0) -- by 1 point per frame
|
||||
else
|
||||
if piece_dy >= 1 then -- basically
|
||||
self.drop_bonus = self.drop_bonus + piece_dy * 20 -- this sort of
|
||||
end -- multiline comment
|
||||
self.drop_bonus = self.drop_bonus + 1 -- is completely
|
||||
end -- unacceptable
|
||||
```
|
||||
|
||||
* Use `snake_case` for variables, `camelCase` for functions.
|
||||
|
||||
|
||||
Contributor's License Agreement
|
||||
-------------------------------
|
||||
|
||||
@@ -36,3 +6,93 @@ By contributing source code or other assets (e.g. music, artwork, graphics) to C
|
||||
You also waive all moral rights to your contributions insofar as they are used in the Cambridge repository or in any code or works deriving therefrom.
|
||||
|
||||
(Notwithstanding the above clause, I will still make my best effort to provide sufficient attribution to all contributions. At the very least you'll get documentation of your contributions under SOURCES, and probably a special place in the credit roll as well.)
|
||||
|
||||
|
||||
Git / Repo conventions
|
||||
----------------------
|
||||
|
||||
In general, use `kebab-case` for branch names. Also, make sure they're concise and descriptive - like 2 or 3 words is usually good.
|
||||
|
||||
```
|
||||
* badbeef (badBranchName) This branch name is bad.
|
||||
| * defaced (another_bad_branch_name) This branch name is also bad because it uses snake case.
|
||||
|/
|
||||
| * deadcab (generic) This branch name isn't very descriptive.
|
||||
|/
|
||||
| * bac0040 (this-long-winded-branch-name-that-could-be-its-own-commit-message) Self-explanatory.
|
||||
|/
|
||||
| * 600db01 (good-branch-name) This branch name is good.
|
||||
|/
|
||||
* 0000420 (HEAD -> master, tag: v0.6.9) This is a sexy root commit.
|
||||
```
|
||||
|
||||
The top line of a commit message should generally be one full sentence long, without too many subordinate clauses. Don't sweat 50/72, but try not go over about 100 characters either.
|
||||
* If the message starts with a verb, it should be written in the past tense, as a description of what the commit _did_ to the commit tree. (e.g. _Made_ a change, _Fixed_ a bug, _Added_ a feature)
|
||||
* Alternatively, include a description (in the present tense) of what is now true thanks to this commit. (e.g. "The Puyo Puyo mode can now support up to 50 players.")
|
||||
|
||||
```
|
||||
* 800000d (message-too-long) Made multiplayer stuff play well with the new v0.2.5 server by fixing a problem the client was having with sending multiple 4-KB packets within 2 milliseconds of each other.
|
||||
| * defaced (not-descriptive-enough) Fixed stuff.
|
||||
|/
|
||||
| * bad0003 (present-tense) Lengthens the retry period of the server connection to 15 seconds.
|
||||
|/
|
||||
| * bad0004 (imperative-mood) Force the credit roll to end after 67 seconds if no input is detected.
|
||||
|/
|
||||
| * 600d001 (good-commit-summary) Made the Jenny Marathon mode not top out randomly at level 600.
|
||||
| * 600d002 (also-good) Backgrounds don't suck anymore.
|
||||
|/
|
||||
* 1234567 (HEAD -> master, tag: v0.4.2) Updated docs in preparation for a new release.
|
||||
```
|
||||
|
||||
When making pull requests, always include:
|
||||
|
||||
* A title that works well as a commit title, since that's what's going to appear when it's merged.
|
||||
* A full description of the problem that the pull request solves or the feature that it implements.
|
||||
* If the whole purpose of the pull request is to resolve a particular issue and nothing else, "Fixes #[issue number]" counts as a full description. Otherwise if there's anything else in the pull request, make a short note of "also [did this other thing]".
|
||||
|
||||
|
||||
Coding conventions
|
||||
------------------
|
||||
|
||||
Use tabs to indent, spaces to align.
|
||||
|
||||
* Specifically, spaces should not appear at the beginning of a line, and tabs should not appear _except_ at the beginning of a line.
|
||||
* The sole exception is in a multiline `if` statement; the initial `if` should have four spaces before it to align it with an `elseif` on the next line. For example:
|
||||
|
||||
```lua
|
||||
---- 4 spaces
|
||||
if self.level < 900 then return 12
|
||||
elseif self.level < 1200 then return 8
|
||||
else return 6 end
|
||||
```
|
||||
|
||||
Comments at the end of lines of code must be one line long. Multi-line comments must appear in their own block.
|
||||
|
||||
```lua
|
||||
if not self.piece:isDropBlocked(self.grid) then
|
||||
-- this is a comment that appears in a block of its own, separate from any code
|
||||
-- consecutive multiline comments must have the same indentation level and
|
||||
-- not appear next on the same line as actual code
|
||||
self.drop_bonus = 0 -- comments at the end of a line must stay on that line
|
||||
else
|
||||
if piece_dy >= 1 then -- basically
|
||||
self.drop_bonus = self.drop_bonus + piece_dy * 20 -- this sort of
|
||||
end -- multiline comment
|
||||
self.drop_bonus = self.drop_bonus + 1 -- is completely
|
||||
end -- unacceptable
|
||||
```
|
||||
|
||||
Use `snake_case` for variables, `camelCase` for functions.
|
||||
|
||||
```lua
|
||||
function MyGameMode:on_activate_bleep_bloop()
|
||||
-- no, bad, use "onActivateBleepBloop"
|
||||
|
||||
local bleepBloopFrames = 240
|
||||
-- this is also bad, use "bleep_bloop_frames"
|
||||
|
||||
local bleep_bloop_bonus = self.lock_delay * 150
|
||||
self.bleepBloopSubscore = self.bleepBloopSubscore + bleep_bloop_bonus
|
||||
-- this should be self."bleep_bloop_subscore", member variables are also variables
|
||||
end
|
||||
```
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
The code in Cambridge is licensed under the MIT license.
|
||||
|
||||
Copyright (c) 2018-2019 Joe Zeng
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
@@ -18,12 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
-------------------------
|
||||
|
||||
Some code and assets in this repository are contributed by members of the
|
||||
community, as well as borrowed from other places, either with licensing
|
||||
or as placeholders until suitable material can be found that is properly
|
||||
licensed. Their original sources, and copyright notices if applicable, are
|
||||
listed in the file SOURCES.
|
||||
SOFTWARE.
|
||||
90
README.md
Normal file
90
README.md
Normal file
@@ -0,0 +1,90 @@
|
||||

|
||||
|
||||
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), [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 being my co-dev!
|
||||
- [joezeng](https://github.com/joezeng) for the original project, and for offering to help with the expansion!
|
||||
- [The Tetra Legends Discord](http://discord.com/invite/7hMx5r2) for supporting me and playtesting!
|
||||
- [The Absolute Plus](https://discord.gg/6Gf2awJ) for being another source of motivation!
|
||||
|
||||
The following people in no particular order also helped with the project:
|
||||
- [Hailey](https://github.com/haileylgbt)
|
||||
- CylinderKnot
|
||||
- MarkGamed7794
|
||||
- [Mizu](https://github.com/rexxt)
|
||||
- MattMayuga
|
||||
- Kitaru
|
||||
- switchpalacecorner
|
||||
- [sinefuse](https://github.com/sinefuse)
|
||||
- [2Tie](https://github.com/2Tie)
|
||||
- [nightmareci](https://github.com/nightmareci)
|
||||
- [MyPasswordIsWeak](https://github.com/MyPasswordIsWeak)
|
||||
- [Dr Ocelot](https://github.com/Dr-Ocelot)
|
||||
|
||||

|
||||
|
||||
Playing the game
|
||||
----------------
|
||||
|
||||
### Windows
|
||||
|
||||
You do not need LÖVE on Windows, as it comes bundled with the program.
|
||||
|
||||
To get the stable release, simply download the ZIP in the latest release. All assets needed are bundled with the executable.
|
||||
|
||||
If you want the bleeding edge version, or want mod pack support, download [this](https://github.com/SashLilac/cambridge/archive/master.zip).
|
||||
|
||||
Extract the ZIP, open a Command Prompt at the folder you extracted Cambridge to, then run this command:
|
||||
|
||||
dist\windows\love.exe .
|
||||
|
||||
Alternatively, if you're on a 32-bit system, run this instead:
|
||||
|
||||
dist\win32\love.exe .
|
||||
|
||||
32-bit systems do not support rich presence integration.
|
||||
|
||||
Then, check the mod pack section at the bottom of this page.
|
||||
|
||||
### macOS, Linux
|
||||
|
||||
If you haven't already, install `love` with your favourite package manager (Homebrew on macOS, your system's default on Linux). **Make sure you're using LÖVE 11, because it won't work with earlier versions!**
|
||||
|
||||
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
|
||||
-------
|
||||
|
||||
The Cambridge project is licensed under the MIT license (included in LICENSE.md).
|
||||
|
||||
Some code and assets in this repository are contributed by members of the
|
||||
community, as well as borrowed from other places, either with licensing
|
||||
or as placeholders until suitable material can be found that is properly
|
||||
licensed. Their original sources, and copyright notices if applicable, are
|
||||
listed in the file SOURCES.
|
||||
11
clean.bat
Normal file
11
clean.bat
Normal 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
|
||||
2
conf.lua
2
conf.lua
@@ -1,6 +1,8 @@
|
||||
function love.conf(t)
|
||||
t.identity = "cambridge"
|
||||
|
||||
t.console = true
|
||||
|
||||
t.window.title = "Cambridge"
|
||||
t.window.width = 640
|
||||
t.window.height = 480
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
Game modes
|
||||
==========
|
||||
|
||||
There are several classes of game modes.
|
||||
There are several classes of game modes. The modes that originate from other games are organized by suffix:
|
||||
|
||||
* The "C" series stand for "Classic" games, games that were produced before around 1992-1993 and generally have no wallkicks or lock delay.
|
||||
* C84 - The original version from the Electronika 60.
|
||||
* C88 - Sega Tetris.
|
||||
* C89 - Nintendo / NES Tetris.
|
||||
* The "A" series stand for "Arika" games, or games in the Tetris the Grand Master series.
|
||||
* A1 - Tetris The Grand Master (the original from 1998).
|
||||
* A2 - Tetris The Absolute The Grand Master 2 PLUS.
|
||||
* A3 - Tetris The Grand Master 3 Terror-Instinct.
|
||||
* AX - Tetris The Grand Master ACE (X for Xbox).
|
||||
* The "G" series stand for "Guideline" games, or games that follow the Tetris Guideline.
|
||||
* GF - Tetris Friends (2007-2019)
|
||||
* GJ - Tetris Online Japan (2005-2011)
|
||||
* N stands for Nullpomino, only used for Phantom Mania N.
|
||||
|
||||
MARATHON
|
||||
--------
|
||||
|
||||
Modes in which the goal is to play as well as possible over a limited game interval, to ultimately achieve the title of Grand Master.
|
||||
Modes in which the goal is to play as well as possible over a limited game interval.
|
||||
|
||||
* **MARATHON 2020**: 2020 levels of pure pain. Can you make it all the way?
|
||||
|
||||
@@ -15,6 +28,8 @@ From other games:
|
||||
* **MARATHON A1**: Tetris the Grand Master 1.
|
||||
* **MARATHON A2**: Tetris the Grand Master 2 (TAP Master).
|
||||
* **MARATHON A3**: Tetris the Grand Master 3 (no exams).
|
||||
* **MARATHON AX4**: Another mode from TGM Ace.
|
||||
* **MARATHON C89**: Nintendo NES Tetris. Can you transition and make it to the killscreen?
|
||||
|
||||
|
||||
SURVIVAL
|
||||
@@ -22,9 +37,40 @@ SURVIVAL
|
||||
|
||||
Modes that concentrate on how long you can survive an increasingly fast and difficult game.
|
||||
|
||||
* **SURVIVAL 2020**: It only gets worse. Beware of bone blocks!
|
||||
* **SURVIVAL 2020**: It only gets worse from Marathon 2020. Beware of the total delay!
|
||||
|
||||
From other games:
|
||||
* **SURVIVAL A1**: 20G mode from Tetris the Grand Master.
|
||||
* **SURVIVAL A2**: T.A. Death.
|
||||
* **SURVIVAL A3**: Ti Shirase.
|
||||
|
||||
|
||||
RACE
|
||||
----
|
||||
|
||||
Modes with no levels, just a single timed goal.
|
||||
|
||||
* **Race 40**: How fast can you clear 40 lines? No limits, no holds barred.
|
||||
|
||||
|
||||
PHANTOM MANIA
|
||||
-------------
|
||||
|
||||
Modes where pieces turn invisible as soon as you lock them. One of Cambridge's signature features.
|
||||
|
||||
* **Phantom Mania**: The classic invisible mode from Nullpomino. Can you handle 999 levels of "the M roll"?
|
||||
|
||||
* **Phantom Mania 2**: Phantom Mania but way faster! Can you face a mode where even the garbage and the next preview turn invisible?
|
||||
|
||||
|
||||
|
||||
OTHER MODES
|
||||
-----------
|
||||
|
||||
* **Strategy**: How well can you plan ahead your movements? Can you handle only having a short time to place each piece?
|
||||
|
||||
* **TetrisGram™ Pacer Test**: is a multi-stage piece-placing ability test that progressively gets more difficult as it continues.
|
||||
|
||||
* **Interval Training**: 30 seconds per section. 20G. 15 frames of lock delay. How long can you last?
|
||||
|
||||
* **Demon Mode**: An original mode from Oshisaure! Can you push through the ever faster levels and not get denied?
|
||||
@@ -1,207 +0,0 @@
|
||||
Marathon 2020
|
||||
=============
|
||||
|
||||
To celebrate the coming of the year 2020, I've created a new "extended Tetris the Grand Master" mode where the level counter goes up twice as far as normal, all the way up to 2020.
|
||||
|
||||
|
||||
Gameplay
|
||||
--------
|
||||
|
||||
The goal of this game is to reach the end at level 2020.
|
||||
|
||||
Every piece placed increases the level by 1, and every line cleared also increases the level by 1, with bonuses for large numbers of lines:
|
||||
|
||||
| Lines cleared | Levels advanced |
|
||||
|---------------|-----------------|
|
||||
| 1 | 1 |
|
||||
| 2 | 2 |
|
||||
| 3 | 4 |
|
||||
| 4 | 6 |
|
||||
|
||||
When the current level reaches one less than the level at the bottom of the display (usually a multiple of 100), the level will not advance until a line is cleared.
|
||||
|
||||
|
||||
Levels
|
||||
------
|
||||
|
||||
Each section is 100 levels long, except for the last section, whose levels go from 1900 all the way to 2020.
|
||||
|
||||
However, it is possible to be stopped early on if you do not play fast enough.
|
||||
|
||||
|
||||
### Torikans
|
||||
|
||||
There are certain checkpoints at which your current time will be checked and you will be stopped if your time is over a set objective time.
|
||||
|
||||
| Level | Time limit |
|
||||
|-------|------------|
|
||||
| 500 | 6:00.00 |
|
||||
| 900 | 8:30.00 |
|
||||
| 1000 | 8:45.00 |
|
||||
| 1500 | 11:30.00 |
|
||||
| 1900 | 13:15.00 |
|
||||
|
||||
At levels 500, 1000, and 1500, you will be stopped immediately if your time is not under the objective.
|
||||
|
||||
At levels 900 and 1900, the next section will be capped at 999 or 1999 respectively, and you will get a short credit roll when the section is over.
|
||||
|
||||
|
||||
Speed
|
||||
-----
|
||||
|
||||
Marathon 2020 gets faster in two different, independent ways, the gravity curve and the delay curve.
|
||||
|
||||
The gravity curve is always the same at a particular level, while the delay curve can vary based on your previous section time.
|
||||
|
||||
### Gravity Curve
|
||||
|
||||
The gravity curve is the same as it is in the original TGM and TAP.
|
||||
|
||||
### Delay Curve
|
||||
|
||||
The delay curve is shown as in the following table. Line ARE is always equal to ARE.
|
||||
|
||||
If your time in a particular section from 0 to 70 is smaller than the "cool" requirement at that level, your delay curve will be bumped up an extra level at the end of the section.
|
||||
|
||||
The delay curve always advances at least 1 level past level 500, and if you get a section cool when the level is past 500, it will advance 2 levels instead.
|
||||
|
||||
| Level | ARE | Lock | DAS | Line | Cool |
|
||||
|-------|------|------|------|------|------|
|
||||
|0|27|30|15|40|45.00|
|
||||
|100|24|30|12|25|41.50|
|
||||
|200|21|30|12|25|38.50|
|
||||
|300|18|30|9|20|35.00|
|
||||
|400|16|30|9|15|32.50|
|
||||
|500|14|30|8|12|29.20|
|
||||
|600|12|26|8|12|27.20|
|
||||
|700|10|22|8|8|24.80|
|
||||
|800|8|19|7|8|22.80|
|
||||
|900|6|17|7|6|20.60|
|
||||
|1000|6|15|6|6|19.60|
|
||||
|1100|6|15|6|4|19.40|
|
||||
|1200|6|15|6|4|19.40|
|
||||
|1300|5|15|5|4|18.40|
|
||||
|1400|5|15|5|2|18.20|
|
||||
|1500|4|15|4|2|16.20|
|
||||
|1600|4|13|4|2|16.20|
|
||||
|1700|4|11|4|2|16.20|
|
||||
|1800|4|10|4|2|16.20|
|
||||
|1900|4|9|4|2|16.20|
|
||||
|2000|4|8|3|2|15.20|
|
||||
|
||||
In order to get a section cool, your 0-70 section time must be below the cutoff *and* no more than 2 seconds slower than your previous 0-70 time.
|
||||
|
||||
|
||||
Grading
|
||||
-------
|
||||
|
||||
|
||||
|
||||
|
||||
### Basic grades
|
||||
|
||||
Internally, the grade counter is a number that can range from 0 to 30.
|
||||
|
||||
At the beginning of the game, it starts at 0. To increase it, you must bring an internal grade point counter past a certain threshold.
|
||||
|
||||
The threshold is set at 50 points for the first grade, then 100 more points for the next grade, then 150 points for the grade after that, and so on. You reach the maximum level of 30 at a total of 23,250 points.
|
||||
|
||||
A table of the thresholds in grade points required to reach each level is provided below:
|
||||
|
||||
Grade|Threshold
|
||||
-|-
|
||||
0|0
|
||||
1|50
|
||||
2|150
|
||||
3|300
|
||||
4|500
|
||||
5|750
|
||||
6|1050
|
||||
7|1400
|
||||
8|1800
|
||||
9|2250
|
||||
10|2750
|
||||
11|3300
|
||||
12|3900
|
||||
13|4550
|
||||
14|5250
|
||||
15|6000
|
||||
16|6800
|
||||
17|7650
|
||||
18|8550
|
||||
19|9500
|
||||
20|10500
|
||||
21|11550
|
||||
22|12650
|
||||
23|13800
|
||||
24|15000
|
||||
25|16250
|
||||
26|17550
|
||||
27|18900
|
||||
28|20300
|
||||
29|21750
|
||||
**30**|**23250**
|
||||
|
||||
Points are given according to a different scale, the point level. The point level is calculated by taking your current actual level, and adding (100 * the delay level) to it.
|
||||
|
||||
The points given for clearing certain amounts of lines is given as follows:
|
||||
|
||||
Level| x1 | x2 | x3 | x4
|
||||
-|-|-|-|-
|
||||
0|10|20|30|40
|
||||
100|10|20|30|40
|
||||
200|10|20|30|48
|
||||
300|10|20|30|60
|
||||
400|10|20|36|72
|
||||
500|10|21|42|84
|
||||
600|10|24|48|96
|
||||
700|10|27|54|108
|
||||
800|10|30|60|120
|
||||
900|11|33|66|140
|
||||
1000|12|36|72|160
|
||||
1100|13|39|81|180
|
||||
1200|14|42|90|200
|
||||
1300|15|45|99|220
|
||||
1400|16|48|108|240
|
||||
1500|17|52|117|260
|
||||
1600|18|56|126|280
|
||||
1700|19|60|135|300
|
||||
1800|20|64|144|320
|
||||
1900|21|68|153|340
|
||||
2000|22|72|162|360
|
||||
2100|23|76|171|380
|
||||
2200|24|80|180|400
|
||||
2300|25|84|189|420
|
||||
2400|26|88|198|440
|
||||
2500|27|92|207|460
|
||||
2600|28|96|216|480
|
||||
2700|29|100|225|500
|
||||
2800|30|104|234|520
|
||||
2900|31|108|243|540
|
||||
3000|32|112|252|560
|
||||
3100|33|116|261|580
|
||||
3200|34|120|270|600
|
||||
3300|35|124|279|620
|
||||
3400|36|128|288|640
|
||||
3500|37|132|297|660
|
||||
3600|38|136|306|680
|
||||
3700|39|140|315|700
|
||||
3800|40|144|324|720
|
||||
3900|41|148|333|740
|
||||
|
||||
Past level 1000, a 4-line clear will always give (30 * current grade), regardless of point level.
|
||||
|
||||
The remaining 20 grades come from section cools. Every section cool you get boosts your score by one grade. There are no regrets.
|
||||
|
||||
Points are also taken away with the time it takes to lock down a piece. The delay counter starts at 0, and increases by (current grade + 2) every frame. When the counter reaches or exceeds 240, it resets to 0, and 1 grade point is taken away.
|
||||
|
||||
Grades are based on the maximum grade points achieved. Once a grade has been attained, it cannot be lost even if grade points drop below the threshold for that grade.
|
||||
|
||||
|
||||
|
||||
Stats
|
||||
-----
|
||||
|
||||
* Fewest number of lines/pieces to reach 2020: 1263 pieces / 505 lines [all Tetrises]
|
||||
|
||||
* Most number of lines/pieces to reach 2020: 1448 pieces / 572 lines [all singles / doubles, full board]
|
||||
@@ -1,104 +0,0 @@
|
||||
Phantom Mania 2
|
||||
===============
|
||||
|
||||
The Phantom Mania mode in Nullpomino is based on T.A. Death (Speed Mania), but where everything is invisible. The obvious sequel to such a mode is Phantom Mania 2, which is based on Shirase (Speed Mania 2), but again where everything is invisible.
|
||||
|
||||
|
||||
|
||||
Gameplay
|
||||
--------
|
||||
|
||||
The goal of this game is to reach the end at level 1300, and then score as many grades as possible in the ending credit roll.
|
||||
|
||||
Each piece disappears as soon as it is placed down. Only the active piece is visible at any given time.
|
||||
|
||||
Every piece placed increases the level by 1, and every line cleared also increases the level by 1, with bonuses for large numbers of lines:
|
||||
|
||||
| Lines cleared | Levels advanced |
|
||||
|---------------|-----------------|
|
||||
| 1 | 1 |
|
||||
| 2 | 2 |
|
||||
| 3 | 4 |
|
||||
| 4 | 6 |
|
||||
|
||||
When the current level reaches one less than the level at the bottom of the display (usually a multiple of 100), the level will not advance until a line is cleared.
|
||||
|
||||
|
||||
Levels
|
||||
------
|
||||
|
||||
Each section is 100 levels long. At certain levels, the speed may get faster, or the difficulty may increase some other way.
|
||||
|
||||
| Level | What happens |
|
||||
|-------|------------|
|
||||
| 500 | Rows of garbage start to advance on the board. The bottom row is copied, appearing for a brief flash before disappearing again. |
|
||||
| 1000 | The garbage stops, but the hold queue turns invisible. |
|
||||
| 1100 | The first next preview turns invisible. |
|
||||
| 1200 | The second next preview turns invisible. |
|
||||
|
||||
Unlike in Shirase mode, the speed of the game does not get faster past level 1000.
|
||||
|
||||
### Torikans
|
||||
|
||||
There are certain checkpoints at which your current time will be checked and you will be stopped if your time is over a set objective time.
|
||||
|
||||
| Level | Time limit |
|
||||
|-------|------------|
|
||||
| 300 | 2:02.00 |
|
||||
| 500 | 3:03.00 |
|
||||
| 800 | 4:45.00 |
|
||||
| 1000 | 5:38.00 |
|
||||
|
||||
At each torikan, you will get a 54-second invisible roll which is worth grade points.
|
||||
|
||||
Only the 1300 roll is in Big Mode.
|
||||
|
||||
|
||||
Grading
|
||||
-------
|
||||
|
||||
Your grade starts at 1, and goes on from S1 to S9, and then M1 upwards indefinitely.
|
||||
|
||||
Your grade advances each section based on your section time. If you achieve a section COOL, you get 2 grade points. If you instead incur a section REGRET, you get no grade points. The normal section time (in between COOL and REGRET) is worth one grade point.
|
||||
|
||||
| Level | Section COOL | Section REGRET |
|
||||
|-------|--------------|----------------|
|
||||
| 0-4 | 36.00 | 48.00 |
|
||||
| 5-9 | 30.00 | 39.00 |
|
||||
| 10-12 | 27.00 | 35.00 |
|
||||
|
||||
|
||||
Then, the invisible roll is also worth grade points.
|
||||
|
||||
| Lines cleared | Grade points |
|
||||
|---------------|--------------|
|
||||
| 1 | 0.02 |
|
||||
| 2 | 0.06 |
|
||||
| 3 | 0.15 |
|
||||
| 4 | 0.40 |
|
||||
| Clear | 1.00 |
|
||||
|
||||
|
||||
The number of grade points you earn correspond to your grade as follows:
|
||||
|
||||
| Points | Grade |
|
||||
|--------|--------|
|
||||
| 0 | 1 |
|
||||
| 1 | S1 |
|
||||
| 2 | S2 |
|
||||
| 3 | S3 |
|
||||
| 4 | S4 |
|
||||
| 5 | S5 |
|
||||
| 6 | S6 |
|
||||
| 7 | S7 |
|
||||
| 8 | S8 |
|
||||
| 9 | S9 |
|
||||
| 10 | m1 |
|
||||
| 11 | m2 |
|
||||
| 12 | m3 |
|
||||
| 13 | m4 |
|
||||
| 14 | m5 |
|
||||
|
||||
And so on. For *x* > 9 grade points, your grade is m(*x*-9).
|
||||
|
||||
If you achieve a section COOL on each section, your rank will be m17 entering the credit roll.
|
||||
@@ -1,32 +0,0 @@
|
||||
Phantom Mania comparison
|
||||
========================
|
||||
|
||||
## Features
|
||||
|
||||
| | PM | PM2 |
|
||||
|------------|-----|------|
|
||||
| Max level | 999 | 1300 |
|
||||
| Piece preview | 1 | 3 |
|
||||
| Hold | No | Yes |
|
||||
| Graded roll | No | Yes |
|
||||
|
||||
## Torikans
|
||||
|
||||
| Level | PM | PM2 |
|
||||
|------------|-------|-------|
|
||||
| 300 | 2'28" | 2'02" |
|
||||
| 500 | 3'38" | 3'03" |
|
||||
| 800 | 5'23" | 4'40" |
|
||||
| 1000 | --- | 5'38" |
|
||||
|
||||
## Speed Curve
|
||||
|
||||
| Level | PM | PM2 |
|
||||
|------------|-------|-------|
|
||||
| 0 | 16/30 | 10/18 |
|
||||
| 100 | 12/26 | 10/18 |
|
||||
| 200 | 6/22 | 10/17 |
|
||||
| 300 | 6/18 | 4/15 |
|
||||
| 400 | 5/15 | 4/15 |
|
||||
| 500 | 4/15 | 4/13 |
|
||||
| 600+ | 4/15 | 4/12 |
|
||||
42
funcs.lua
42
funcs.lua
@@ -1,13 +1,15 @@
|
||||
function copy(t)
|
||||
-- returns deep copy of t (as opposed to the shallow copy you get from var = t)
|
||||
if type(t) ~= "table" then return t end
|
||||
local meta = getmetatable(t)
|
||||
local target = {}
|
||||
for k, v in pairs(t) do target[k] = v end
|
||||
setmetatable(target, meta)
|
||||
return target
|
||||
local meta = getmetatable(t)
|
||||
local target = {}
|
||||
for k, v in pairs(t) do target[k] = v end
|
||||
setmetatable(target, meta)
|
||||
return target
|
||||
end
|
||||
|
||||
function st(tbl)
|
||||
function strTrueValues(tbl)
|
||||
-- returns a concatenation of all the keys in tbl with value true, separated with spaces
|
||||
str = ""
|
||||
for k, v in pairs(tbl) do
|
||||
if v == true then
|
||||
@@ -17,14 +19,16 @@ function st(tbl)
|
||||
return str
|
||||
end
|
||||
|
||||
function sp(m, s, f)
|
||||
if m == nil then m = 0 end
|
||||
if s == nil then s = 0 end
|
||||
if f == nil then f = 0 end
|
||||
return m*3600 + s*60 + math.ceil(f * 0.6)
|
||||
function frameTime(min, sec, hth)
|
||||
-- returns a time in frames from a time in minutes-seconds-hundredths format
|
||||
if min == nil then min = 0 end
|
||||
if sec == nil then sec = 0 end
|
||||
if hth == nil then hth = 0 end
|
||||
return min*3600 + sec*60 + math.ceil(hth * 0.6)
|
||||
end
|
||||
|
||||
function vAdd(v1, v2)
|
||||
-- returns the sum of vectors v1 and v2
|
||||
return {
|
||||
x = v1.x + v2.x,
|
||||
y = v1.y + v2.y
|
||||
@@ -32,6 +36,7 @@ function vAdd(v1, v2)
|
||||
end
|
||||
|
||||
function vNeg(v)
|
||||
-- returns the opposite of vector v
|
||||
return {
|
||||
x = -v.x,
|
||||
y = -v.y
|
||||
@@ -39,17 +44,26 @@ function vNeg(v)
|
||||
end
|
||||
|
||||
function formatTime(frames)
|
||||
-- returns a mm:ss:hh (h=hundredths) representation of the time in frames given
|
||||
if frames < 0 then return formatTime(0) end
|
||||
str = string.format("%02d", math.floor(frames / 3600)) .. ":"
|
||||
.. string.format("%02d", math.floor(frames / 60) % 60) .. "."
|
||||
.. string.format("%02d", math.floor(frames / 0.6) % 100)
|
||||
local min, sec, hund
|
||||
min = math.floor(frames/3600)
|
||||
sec = math.floor(frames/60) % 60
|
||||
hund = math.floor(frames/.6) % 100
|
||||
str = string.format("%02d:%02d.%02d", min, sec, hund)
|
||||
return str
|
||||
end
|
||||
|
||||
function formatBigNum(number)
|
||||
-- returns a string representing a number with commas as thousands separator (e.g. 12,345,678)
|
||||
local s = string.format("%d", number)
|
||||
local pos = string.len(s) % 3
|
||||
if pos == 0 then pos = 3 end
|
||||
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
BIN
libs/discord-rpc.dll
Normal file
Binary file not shown.
BIN
libs/discord-rpc.dylib
Normal file
BIN
libs/discord-rpc.dylib
Normal file
Binary file not shown.
BIN
libs/discord-rpc.so
Normal file
BIN
libs/discord-rpc.so
Normal file
Binary file not shown.
282
libs/discordRPC.lua
Normal file
282
libs/discordRPC.lua
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -20,39 +20,68 @@ 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-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")
|
||||
end
|
||||
end
|
||||
|
||||
misc_graphics = {
|
||||
frame = love.graphics.newImage("res/img/frame.png"),
|
||||
ready = love.graphics.newImage("res/img/ready.png"),
|
||||
go = love.graphics.newImage("res/img/go.png"),
|
||||
select_mode = love.graphics.newImage("res/img/select_mode.png"),
|
||||
strike = love.graphics.newImage("res/img/strike.png"),
|
||||
}
|
||||
}
|
||||
58
load/rpc.lua
Normal file
58
load/rpc.lua
Normal 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
|
||||
@@ -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
|
||||
148
main.lua
148
main.lua
@@ -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
|
||||
@@ -64,7 +93,7 @@ end
|
||||
|
||||
function love.draw()
|
||||
love.graphics.push()
|
||||
|
||||
|
||||
-- get offset matrix
|
||||
love.graphics.setDefaultFilter("linear", "nearest")
|
||||
local width = love.graphics.getWidth()
|
||||
@@ -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
2
package.bat
Normal 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
26
release.bat
Normal 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 ..\..
|
||||
BIN
res/backgrounds/options-gears.png
Normal file
BIN
res/backgrounds/options-gears.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 MiB |
BIN
res/backgrounds/options-pcb.png
Normal file
BIN
res/backgrounds/options-pcb.png
Normal file
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
BIN
res/img/bonew.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 229 B |
BIN
res/img/s8.png
Normal file
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
BIN
res/se/cursor.wav
Normal file
Binary file not shown.
BIN
res/se/cursor_lr.wav
Normal file
BIN
res/se/cursor_lr.wav
Normal file
Binary file not shown.
BIN
res/se/erase.wav
Normal file
BIN
res/se/erase.wav
Normal file
Binary file not shown.
BIN
res/se/fall.wav
Normal file
BIN
res/se/fall.wav
Normal file
Binary file not shown.
BIN
res/se/go.wav
Normal file
BIN
res/se/go.wav
Normal file
Binary file not shown.
BIN
res/se/hold.wav
Normal file
BIN
res/se/hold.wav
Normal file
Binary file not shown.
BIN
res/se/lock.wav
Normal file
BIN
res/se/lock.wav
Normal file
Binary file not shown.
BIN
res/se/main_decide.wav
Normal file
BIN
res/se/main_decide.wav
Normal file
Binary file not shown.
BIN
res/se/mode_decide.wav
Normal file
BIN
res/se/mode_decide.wav
Normal file
Binary file not shown.
BIN
res/se/ready.wav
Normal file
BIN
res/se/ready.wav
Normal file
Binary file not shown.
@@ -5,10 +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"
|
||||
|
||||
@@ -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
|
||||
|
||||
23
scene/exit.lua
Normal file
23
scene/exit.lua
Normal file
@@ -0,0 +1,23 @@
|
||||
local ExitScene = Scene:extend()
|
||||
require 'load.save'
|
||||
|
||||
ExitScene.title = "Exit Game"
|
||||
|
||||
function ExitScene:new()
|
||||
end
|
||||
|
||||
function ExitScene:update()
|
||||
love.event.quit()
|
||||
end
|
||||
|
||||
function ExitScene:render()
|
||||
end
|
||||
|
||||
function ExitScene:changeOption(rel)
|
||||
end
|
||||
|
||||
function ExitScene:onInputPress(e)
|
||||
end
|
||||
|
||||
return ExitScene
|
||||
|
||||
@@ -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,13 +70,24 @@ function GameScene:render()
|
||||
|
||||
end
|
||||
|
||||
function GameScene:onKeyPress(e)
|
||||
if (self.game.completed) and
|
||||
e.scancode == "return" 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.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
79
scene/game_config.lua
Normal 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
|
||||
@@ -5,57 +5,152 @@ ConfigScene.title = "Input Config"
|
||||
require 'load.save'
|
||||
|
||||
local configurable_inputs = {
|
||||
"menu_decide",
|
||||
"menu_back",
|
||||
"left",
|
||||
"right",
|
||||
"up",
|
||||
"down",
|
||||
"right",
|
||||
"up",
|
||||
"down",
|
||||
"rotate_left",
|
||||
"rotate_left2",
|
||||
"rotate_right",
|
||||
"rotate_right2",
|
||||
"rotate_180",
|
||||
"hold",
|
||||
"rotate_right",
|
||||
"rotate_right2",
|
||||
"rotate_180",
|
||||
"hold",
|
||||
"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()
|
||||
end
|
||||
|
||||
function ConfigScene:render()
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.draw(
|
||||
backgrounds["input_config"],
|
||||
0, 0, 0,
|
||||
0.5, 0.5
|
||||
)
|
||||
|
||||
love.graphics.setFont(font_3x5_2)
|
||||
for i, input in pairs(configurable_inputs) do
|
||||
if config.input[input] then
|
||||
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"
|
||||
)
|
||||
for i, input in ipairs(configurable_inputs) do
|
||||
love.graphics.printf(input, 40, 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
|
||||
if e.scancode == "return" then
|
||||
-- save, then load next scene
|
||||
saveConfig()
|
||||
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 e.scancode == "delete" or e.scancode == "backspace" then
|
||||
self.input_state = 1
|
||||
elseif self.input_state > table.getn(configurable_inputs) then
|
||||
if e.scancode == "return" then
|
||||
-- save new input, then load next scene
|
||||
config.input = self.new_input
|
||||
saveConfig()
|
||||
scene = TitleScene()
|
||||
elseif e.scancode == "delete" or e.scancode == "backspace" then
|
||||
-- retry
|
||||
self.input_state = 1
|
||||
self.set_inputs = newSetInputs()
|
||||
self.new_input = {}
|
||||
end
|
||||
elseif e.scancode == "tab" then
|
||||
self.set_inputs[configurable_inputs[self.input_state]] = "skipped"
|
||||
self.input_state = self.input_state + 1
|
||||
elseif e.scancode ~= "escape" then
|
||||
-- all other keys can be configured
|
||||
if not self.new_input.keys then
|
||||
self.new_input.keys = {}
|
||||
end
|
||||
self.set_inputs[configurable_inputs[self.input_state]] = "key " .. love.keyboard.getKeyFromScancode(e.scancode) .. " (" .. e.scancode .. ")"
|
||||
self.new_input.keys[e.scancode] = configurable_inputs[self.input_state]
|
||||
self.input_state = self.input_state + 1
|
||||
end
|
||||
elseif string.sub(e.type, 1, 3) == "joy" then
|
||||
if self.input_state <= table.getn(configurable_inputs) then
|
||||
if e.type == "joybutton" then
|
||||
addJoystick(self.new_input, e.name)
|
||||
if not self.new_input.joysticks[e.name].buttons then
|
||||
self.new_input.joysticks[e.name].buttons = {}
|
||||
end
|
||||
self.set_inputs[configurable_inputs[self.input_state]] =
|
||||
"jbtn " ..
|
||||
e.button ..
|
||||
" " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
|
||||
self.new_input.joysticks[e.name].buttons[e.button] = configurable_inputs[self.input_state]
|
||||
self.input_state = self.input_state + 1
|
||||
elseif e.type == "joyaxis" then
|
||||
if 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
|
||||
else
|
||||
config.input[configurable_inputs[self.input_state]] = e.scancode
|
||||
self.input_state = self.input_state + 1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
67
scene/mode_select.lua
Normal file → Executable file
67
scene/mode_select.lua
Normal file → Executable file
@@ -5,42 +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.ligne',
|
||||
require 'tetris.modes.marathon_a1',
|
||||
require 'tetris.modes.marathon_a2',
|
||||
require 'tetris.modes.marathon_a3',
|
||||
require 'tetris.modes.survival_a1',
|
||||
require 'tetris.modes.survival_a2',
|
||||
require 'tetris.modes.survival_a3',
|
||||
require 'tetris.modes.marathon_l1',
|
||||
}
|
||||
|
||||
rulesets = {
|
||||
require 'tetris.rulesets.cambridge',
|
||||
require 'tetris.rulesets.arika',
|
||||
require 'tetris.rulesets.arika_ti',
|
||||
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()
|
||||
@@ -52,49 +26,58 @@ function ModeSelectScene:render()
|
||||
0, 0, 0,
|
||||
0.5, 0.5
|
||||
)
|
||||
|
||||
|
||||
if self.menu_state.select == "mode" then
|
||||
love.graphics.setColor(1, 1, 1, 0.5)
|
||||
elseif self.menu_state.select == "ruleset" then
|
||||
love.graphics.setColor(1, 1, 1, 0.25)
|
||||
end
|
||||
love.graphics.rectangle("fill", 20, 78 + 20 * self.menu_state.mode, 240, 22)
|
||||
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)
|
||||
|
||||
love.graphics.draw(misc_graphics["select_mode"], 20, 40)
|
||||
|
||||
love.graphics.setFont(font_3x5_2)
|
||||
for idx, mode in pairs(game_modes) do
|
||||
love.graphics.printf(mode.name, 40, 80 + 20 * idx, 200, "left")
|
||||
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()
|
||||
playSE("cursor_lr")
|
||||
elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
|
||||
scene = TitleScene()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -3,10 +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()
|
||||
@@ -36,13 +57,18 @@ 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)
|
||||
playSE("cursor")
|
||||
elseif e.input == "menu_back" or e.scancode == "backspace" or e.scancode == "delete" then
|
||||
love.event.quit()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ local Object = require 'libs.classic'
|
||||
local Grid = Object:extend()
|
||||
|
||||
local empty = { skin = "", colour = "" }
|
||||
local oob = { skin = "", colour = "" }
|
||||
local block = { skin = "2tie", colour = "A" }
|
||||
|
||||
function Grid:new()
|
||||
self.grid = {}
|
||||
@@ -26,8 +28,15 @@ function Grid:clear()
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:getCell(x, y)
|
||||
if x < 1 or x > 10 or y > 24 then return oob
|
||||
elseif y < 1 then return empty
|
||||
else return self.grid[y][x]
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:isOccupied(x, y)
|
||||
return self.grid[y+1][x+1] ~= empty
|
||||
return self:getCell(x+1, y+1) ~= empty
|
||||
end
|
||||
|
||||
function Grid:isRowFull(row)
|
||||
@@ -38,11 +47,32 @@ function Grid:isRowFull(row)
|
||||
end
|
||||
|
||||
function Grid:canPlacePiece(piece)
|
||||
if piece.big then
|
||||
return self:canPlaceBigPiece(piece)
|
||||
end
|
||||
|
||||
local offsets = piece:getBlockOffsets()
|
||||
for index, offset in pairs(offsets) do
|
||||
local x = piece.position.x + offset.x
|
||||
local y = piece.position.y + offset.y
|
||||
if x >= 10 or x < 0 or y >= 24 or y < 0 or self.grid[y+1][x+1] ~= empty then
|
||||
if self:isOccupied(x, y) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Grid:canPlaceBigPiece(piece)
|
||||
local offsets = piece:getBlockOffsets()
|
||||
for index, offset in pairs(offsets) do
|
||||
local x = piece.position.x + offset.x
|
||||
local y = piece.position.y + offset.y
|
||||
if (
|
||||
self:isOccupied(x * 2 + 0, y * 2 + 0)
|
||||
or self:isOccupied(x * 2 + 1, y * 2 + 0)
|
||||
or self:isOccupied(x * 2 + 0, y * 2 + 1)
|
||||
or self:isOccupied(x * 2 + 1, y * 2 + 1)
|
||||
) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
@@ -50,11 +80,16 @@ function Grid:canPlacePiece(piece)
|
||||
end
|
||||
|
||||
function Grid:canPlacePieceInVisibleGrid(piece)
|
||||
if piece.big then
|
||||
return self:canPlaceBigPiece(piece)
|
||||
-- forget canPlaceBigPieceInVisibleGrid for now
|
||||
end
|
||||
|
||||
local offsets = piece:getBlockOffsets()
|
||||
for index, offset in pairs(offsets) do
|
||||
local x = piece.position.x + offset.x
|
||||
local y = piece.position.y + offset.y
|
||||
if x >= 10 or x < 0 or y >= 24 or y < 4 or self.grid[y+1][x+1] ~= empty then
|
||||
if y < 4 or self:isOccupied(x, y) ~= empty then
|
||||
return false
|
||||
end
|
||||
end
|
||||
@@ -107,26 +142,137 @@ 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)
|
||||
return
|
||||
end
|
||||
offsets = piece:getBlockOffsets()
|
||||
for index, offset in pairs(offsets) do
|
||||
x = piece.position.x + offset.x
|
||||
y = piece.position.y + offset.y
|
||||
self.grid[y+1][x+1] = {
|
||||
skin = piece.skin,
|
||||
colour = piece.shape
|
||||
}
|
||||
if y + 1 > 0 then
|
||||
self.grid[y+1][x+1] = {
|
||||
skin = piece.skin,
|
||||
colour = piece.colour
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:applyBigPiece(piece)
|
||||
offsets = piece:getBlockOffsets()
|
||||
for index, offset in pairs(offsets) do
|
||||
x = piece.position.x + offset.x
|
||||
y = piece.position.y + offset.y
|
||||
for a = 1, 2 do
|
||||
for b = 1, 2 do
|
||||
if y*2+a > 0 then
|
||||
self.grid[y*2+a][x*2+b] = {
|
||||
skin = piece.skin,
|
||||
colour = piece.colour
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:checkForBravo(cleared_row_count)
|
||||
for i = 0, 23 - cleared_row_count do
|
||||
for j = 0, 9 do
|
||||
if self:isOccupied(j, i) then return false end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Grid:checkStackHeight()
|
||||
for i = 0, 23 do
|
||||
for j = 0, 9 do
|
||||
if self:isOccupied(j, i) then return 24 - i end
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function Grid:checkSecretGrade()
|
||||
local sgrade = 0
|
||||
for i=23,5,-1 do
|
||||
local validLine = true
|
||||
local emptyCell = 0
|
||||
if i > 13 then
|
||||
emptyCell = 23-i
|
||||
end
|
||||
if i <= 13 then
|
||||
emptyCell = i-5
|
||||
end
|
||||
for j=0,9 do
|
||||
if (not self:isOccupied(j,i) and j ~= emptyCell) or (j == emptyCell and self:isOccupied(j,i)) then
|
||||
validLine = false
|
||||
end
|
||||
end
|
||||
if not self:isOccupied(emptyCell,i-1) then
|
||||
validLine = false
|
||||
end
|
||||
if(validLine) then
|
||||
sgrade = sgrade + 1
|
||||
else
|
||||
return sgrade
|
||||
end
|
||||
end
|
||||
--[[
|
||||
if(sgrade == 0) then return ""
|
||||
elseif(sgrade < 10) then return 10-sgrade
|
||||
elseif(sgrade < 19) then return "S"..(sgrade-9) end
|
||||
return "GM"
|
||||
--]]
|
||||
return sgrade
|
||||
end
|
||||
|
||||
function Grid:update()
|
||||
for y = 1, 24 do
|
||||
for x = 1, 10 do
|
||||
@@ -138,50 +284,22 @@ 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
|
||||
love.graphics.setColor(0.5, 0.5, 0.5, 1)
|
||||
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
|
||||
love.graphics.setColor(0.8, 0.8, 0.8, 1)
|
||||
love.graphics.setLineWidth(1)
|
||||
if y > 1 and self.grid[y-1][x] == empty then
|
||||
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
|
||||
end
|
||||
if y < 24 and self.grid[y+1][x] == empty then
|
||||
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
|
||||
end
|
||||
if x > 1 and self.grid[y][x-1] == empty then
|
||||
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
|
||||
end
|
||||
if x < 10 and self.grid[y][x+1] == empty then
|
||||
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:drawInvisible(opacity_function, garbage_opacity_function)
|
||||
for y = 1, 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
|
||||
opacity = garbage_opacity_function(self.grid_age[y][x])
|
||||
else
|
||||
opacity = opacity_function(self.grid_age[y][x])
|
||||
end
|
||||
love.graphics.setColor(0.5, 0.5, 0.5, opacity)
|
||||
love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16)
|
||||
if opacity > 0 and self.grid[y][x].colour ~= "X" then
|
||||
love.graphics.setColor(0.64, 0.64, 0.64)
|
||||
if self.grid[y][x].skin ~= "bone" then
|
||||
love.graphics.setColor(0.8, 0.8, 0.8, 1)
|
||||
love.graphics.setLineWidth(1)
|
||||
if y > 1 and self.grid[y-1][x] == empty then
|
||||
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
|
||||
@@ -201,4 +319,65 @@ function Grid:drawInvisible(opacity_function, garbage_opacity_function)
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:drawOutline()
|
||||
for y = 5, 24 do
|
||||
for x = 1, 10 do
|
||||
if self.grid[y][x] ~= empty then
|
||||
love.graphics.setColor(0.8, 0.8, 0.8, 1)
|
||||
love.graphics.setLineWidth(1)
|
||||
if y > 1 and self.grid[y-1][x] == empty then
|
||||
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
|
||||
end
|
||||
if y < 24 and self.grid[y+1][x] == empty then
|
||||
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
|
||||
end
|
||||
if x > 1 and self.grid[y][x-1] == empty then
|
||||
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
|
||||
end
|
||||
if x < 10 and self.grid[y][x+1] == empty then
|
||||
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Grid:drawInvisible(opacity_function, garbage_opacity_function, lock_flash, brightness)
|
||||
lock_flash = lock_flash == nil and true or lock_flash
|
||||
brightness = brightness == nil and 0.5 or brightness
|
||||
for y = 5, 24 do
|
||||
for x = 1, 10 do
|
||||
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 == "A" then
|
||||
opacity = garbage_opacity_function(self.grid_age[y][x])
|
||||
else
|
||||
opacity = opacity_function(self.grid_age[y][x])
|
||||
end
|
||||
love.graphics.setColor(brightness, brightness, brightness, opacity)
|
||||
love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16)
|
||||
if lock_flash then
|
||||
if opacity > 0 and self.grid[y][x].colour ~= "X" then
|
||||
love.graphics.setColor(0.64, 0.64, 0.64)
|
||||
love.graphics.setLineWidth(1)
|
||||
if y > 1 and self.grid[y-1][x] == empty then
|
||||
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
|
||||
end
|
||||
if y < 24 and self.grid[y+1][x] == empty then
|
||||
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
|
||||
end
|
||||
if x > 1 and self.grid[y][x-1] == empty then
|
||||
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
|
||||
end
|
||||
if x < 10 and self.grid[y][x+1] == empty then
|
||||
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Grid
|
||||
|
||||
@@ -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)
|
||||
function Piece:new(shape, rotation, position, block_offsets, gravity, lock_delay, skin, colour, big)
|
||||
self.shape = shape
|
||||
self.rotation = rotation
|
||||
self.position = position
|
||||
@@ -10,8 +10,10 @@ 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
|
||||
end
|
||||
|
||||
-- Functions that return a new piece to test in rotation systems.
|
||||
@@ -20,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.block_offsets, self.gravity, self.lock_delay, self.skin, self.colour, self.big
|
||||
)
|
||||
end
|
||||
|
||||
@@ -30,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.block_offsets, self.gravity, self.lock_delay, self.skin, self.colour, self.big
|
||||
)
|
||||
end
|
||||
|
||||
@@ -96,7 +98,7 @@ end
|
||||
|
||||
function Piece:dropToBottom(grid)
|
||||
local piece_y = self.position.y
|
||||
self:dropSquares(20, 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
|
||||
@@ -138,14 +140,25 @@ function Piece:draw(opacity, brightness, grid, partial_das)
|
||||
love.graphics.setColor(brightness, brightness, brightness, opacity)
|
||||
local offsets = self:getBlockOffsets()
|
||||
local gravity_offset = 0
|
||||
if grid ~= nil and not self:isDropBlocked(grid) then
|
||||
gravity_offset = self.gravity * 16
|
||||
end
|
||||
--if grid ~= nil and not self:isDropBlocked(grid) then
|
||||
-- gravity_offset = self.gravity * 16
|
||||
--end
|
||||
if partial_das == nil then partial_das = 0 end
|
||||
for index, offset in pairs(offsets) do
|
||||
local x = self.position.x + offset.x
|
||||
local y = self.position.y + offset.y
|
||||
love.graphics.draw(blocks[self.skin][self.shape], 64+x*16+partial_das, 16+y*16+gravity_offset)
|
||||
if self.big then
|
||||
love.graphics.draw(
|
||||
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.colour],
|
||||
64+x*16+partial_das, 16+y*16+gravity_offset
|
||||
)
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
307
tetris/modes/big_a2.lua
Executable file
307
tetris/modes/big_a2.lua
Executable file
@@ -0,0 +1,307 @@
|
||||
require 'funcs'
|
||||
|
||||
local GameMode = require 'tetris.modes.gamemode'
|
||||
local Piece = require 'tetris.components.piece'
|
||||
|
||||
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
|
||||
|
||||
local MarathonA2Game = GameMode:extend()
|
||||
|
||||
MarathonA2Game.name = "Big A2"
|
||||
MarathonA2Game.hash = "BigA2"
|
||||
MarathonA2Game.tagline = "The points don't matter! Can you reach the invisible roll? Big mode too!"
|
||||
|
||||
|
||||
|
||||
|
||||
function MarathonA2Game:new()
|
||||
self.super:new()
|
||||
self.big_mode = true
|
||||
self.roll_frames = 0
|
||||
self.combo = 1
|
||||
|
||||
self.grade = 0
|
||||
self.grade_points = 0
|
||||
self.grade_point_decay_counter = 0
|
||||
|
||||
self.randomizer = History6RollsRandomizer()
|
||||
|
||||
self.lock_drop = false
|
||||
self.lock_hard_drop = false
|
||||
self.enable_hold = false
|
||||
self.next_queue_length = 1
|
||||
end
|
||||
|
||||
function MarathonA2Game:getARE()
|
||||
if self.level < 700 then return 27
|
||||
elseif self.level < 800 then return 18
|
||||
else return 14 end
|
||||
end
|
||||
|
||||
function MarathonA2Game:getLineARE()
|
||||
if self.level < 600 then return 27
|
||||
elseif self.level < 700 then return 18
|
||||
elseif self.level < 800 then return 14
|
||||
else return 8 end
|
||||
end
|
||||
|
||||
function MarathonA2Game:getDasLimit()
|
||||
if self.level < 500 then return 15
|
||||
elseif self.level < 900 then return 9
|
||||
else return 7 end
|
||||
end
|
||||
|
||||
function MarathonA2Game:getLineClearDelay()
|
||||
if self.level < 500 then return 40
|
||||
elseif self.level < 600 then return 25
|
||||
elseif self.level < 700 then return 16
|
||||
elseif self.level < 800 then return 12
|
||||
else return 6 end
|
||||
end
|
||||
|
||||
function MarathonA2Game:getLockDelay()
|
||||
if self.level < 900 then return 30
|
||||
else return 17 end
|
||||
end
|
||||
|
||||
function MarathonA2Game:getGravity()
|
||||
if (self.level < 30) then return 4/256
|
||||
elseif (self.level < 35) then return 6/256
|
||||
elseif (self.level < 40) then return 8/256
|
||||
elseif (self.level < 50) then return 10/256
|
||||
elseif (self.level < 60) then return 12/256
|
||||
elseif (self.level < 70) then return 16/256
|
||||
elseif (self.level < 80) then return 32/256
|
||||
elseif (self.level < 90) then return 48/256
|
||||
elseif (self.level < 100) then return 64/256
|
||||
elseif (self.level < 120) then return 80/256
|
||||
elseif (self.level < 140) then return 96/256
|
||||
elseif (self.level < 160) then return 112/256
|
||||
elseif (self.level < 170) then return 128/256
|
||||
elseif (self.level < 200) then return 144/256
|
||||
elseif (self.level < 220) then return 4/256
|
||||
elseif (self.level < 230) then return 32/256
|
||||
elseif (self.level < 233) then return 64/256
|
||||
elseif (self.level < 236) then return 96/256
|
||||
elseif (self.level < 239) then return 128/256
|
||||
elseif (self.level < 243) then return 160/256
|
||||
elseif (self.level < 247) then return 192/256
|
||||
elseif (self.level < 251) then return 224/256
|
||||
elseif (self.level < 300) then return 1
|
||||
elseif (self.level < 330) then return 2
|
||||
elseif (self.level < 360) then return 3
|
||||
elseif (self.level < 400) then return 4
|
||||
elseif (self.level < 420) then return 5
|
||||
elseif (self.level < 450) then return 4
|
||||
elseif (self.level < 500) then return 3
|
||||
else return 20
|
||||
end
|
||||
end
|
||||
|
||||
function MarathonA2Game:advanceOneFrame()
|
||||
if self.clear then
|
||||
self.roll_frames = self.roll_frames + 1
|
||||
if self.roll_frames < 0 then return false end
|
||||
if self.roll_frames > 3694 then
|
||||
self.completed = true
|
||||
end
|
||||
elseif self.ready_frames == 0 then
|
||||
self.frames = self.frames + 1
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function MarathonA2Game:onPieceEnter()
|
||||
if (self.level % 100 ~= 99 and self.level ~= 998) and not self.clear and self.frames ~= 0 then
|
||||
self.level = self.level + 1
|
||||
end
|
||||
end
|
||||
|
||||
function MarathonA2Game:onLineClear(cleared_row_count)
|
||||
cleared_row_count = cleared_row_count / 2
|
||||
self.level = math.min(self.level + cleared_row_count, 999)
|
||||
if self.level == 999 and not self.clear then
|
||||
self.clear = true
|
||||
self.grid:clear()
|
||||
self.roll_frames = -150
|
||||
end
|
||||
self.lock_drop = self.level >= 900
|
||||
self.lock_hard_drop = self.level >= 900
|
||||
end
|
||||
|
||||
function MarathonA2Game:updateScore(level, drop_bonus, cleared_lines)
|
||||
if not self.clear then
|
||||
cleared_lines = cleared_lines / 2
|
||||
self:updateGrade(cleared_lines)
|
||||
if self.grid:checkForBravo(cleared_lines) then self.bravo = 4 else self.bravo = 1 end
|
||||
if cleared_lines > 0 then
|
||||
self.combo = self.combo + (cleared_lines - 1) * 2
|
||||
self.score = self.score + (
|
||||
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
|
||||
cleared_lines * self.combo * self.bravo
|
||||
)
|
||||
else
|
||||
self.combo = 1
|
||||
end
|
||||
self.drop_bonus = 0
|
||||
end
|
||||
end
|
||||
|
||||
local grade_point_bonuses = {
|
||||
{10, 20, 40, 50},
|
||||
{10, 20, 30, 40},
|
||||
{10, 20, 30, 40},
|
||||
{10, 15, 30, 40},
|
||||
{10, 15, 20, 40},
|
||||
{5, 15, 20, 30},
|
||||
{5, 10, 20, 30},
|
||||
{5, 10, 15, 30},
|
||||
{5, 10, 15, 30},
|
||||
{5, 10, 15, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
{2, 12, 13, 30},
|
||||
}
|
||||
|
||||
local grade_point_decays = {
|
||||
125, 80, 80, 50, 45, 45, 45,
|
||||
40, 40, 40, 40, 40, 30, 30, 30,
|
||||
20, 20, 20, 20, 20,
|
||||
15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||
10, 10
|
||||
}
|
||||
|
||||
local combo_multipliers = {
|
||||
{1.0, 1.0, 1.0, 1.0},
|
||||
{1.2, 1.4, 1.5, 1.0},
|
||||
{1.2, 1.5, 1.8, 1.0},
|
||||
{1.4, 1.6, 2.0, 1.0},
|
||||
{1.4, 1.7, 2.2, 1.0},
|
||||
{1.4, 1.8, 2.3, 1.0},
|
||||
{1.4, 1.9, 2.4, 1.0},
|
||||
{1.5, 2.0, 2.5, 1.0},
|
||||
{1.5, 2.1, 2.6, 1.0},
|
||||
{2.0, 2.5, 3.0, 1.0},
|
||||
}
|
||||
|
||||
local grade_conversion = {
|
||||
[0] = 0,
|
||||
1, 2, 3, 4, 5, 5, 6, 6, 7, 7,
|
||||
7, 8, 8, 8, 9, 9, 9, 10, 11, 12,
|
||||
12, 12, 13, 13, 14, 14, 15, 15, 16, 16,
|
||||
17
|
||||
}
|
||||
|
||||
function MarathonA2Game:updateGrade(cleared_lines)
|
||||
if self.clear then return end
|
||||
if cleared_lines == 0 then
|
||||
self.grade_point_decay_counter = self.grade_point_decay_counter + 1
|
||||
if self.grade_point_decay_counter >= grade_point_decays[self.grade + 1] then
|
||||
self.grade_point_decay_counter = 0
|
||||
self.grade_points = math.max(0, self.grade_points - 1)
|
||||
end
|
||||
else
|
||||
self.grade_points = self.grade_points + (
|
||||
math.ceil(
|
||||
grade_point_bonuses[self.grade + 1][cleared_lines] *
|
||||
combo_multipliers[math.min(self.combo, 10)][cleared_lines]
|
||||
) * (1 + math.floor(self.level / 250))
|
||||
)
|
||||
if self.grade_points >= 100 and self.grade < 31 then
|
||||
self.grade_points = 0
|
||||
self.grade = self.grade + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MarathonA2Game:getLetterGrade()
|
||||
local grade = grade_conversion[self.grade]
|
||||
if grade < 9 then
|
||||
return tostring(9 - grade)
|
||||
elseif grade < 18 then
|
||||
return "S" .. tostring(grade - 8)
|
||||
end
|
||||
end
|
||||
|
||||
MarathonA2Game.rollOpacityFunction = function(age)
|
||||
if age < 240 then return 1
|
||||
elseif age > 300 then return 0
|
||||
else return 1 - (age - 240) / 60 end
|
||||
end
|
||||
|
||||
function MarathonA2Game:drawGrid(ruleset)
|
||||
if self.clear and not (self.completed or self.game_over) then
|
||||
self.grid:drawInvisible(self.rollOpacityFunction, nil, false)
|
||||
else
|
||||
self.grid:draw()
|
||||
if self.piece ~= nil and self.level < 100 then
|
||||
self:drawGhostPiece(ruleset)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MarathonA2Game:drawScoringInfo()
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
|
||||
love.graphics.setFont(font_3x5_2)
|
||||
love.graphics.print(
|
||||
self.das.direction .. " " ..
|
||||
self.das.frames .. " " ..
|
||||
strTrueValues(self.prev_inputs)
|
||||
)
|
||||
love.graphics.printf("NEXT", 64, 40, 40, "left")
|
||||
love.graphics.printf("GRADE", 240, 120, 40, "left")
|
||||
love.graphics.printf("SCORE", 240, 200, 40, "left")
|
||||
love.graphics.printf("LEVEL", 240, 320, 40, "left")
|
||||
|
||||
love.graphics.setFont(font_3x5_3)
|
||||
if self.roll_frames > 3694 then love.graphics.setColor(1, 0.5, 0, 1)
|
||||
elseif self.clear then love.graphics.setColor(0, 1, 0, 1) end
|
||||
love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left")
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.printf(self.score, 240, 220, 90, "left")
|
||||
love.graphics.printf(self.level, 240, 340, 40, "right")
|
||||
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")
|
||||
|
||||
love.graphics.setFont(font_8x11)
|
||||
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")
|
||||
end
|
||||
|
||||
function MarathonA2Game:getHighscoreData()
|
||||
return {
|
||||
grade = grade_conversion[self.grade],
|
||||
score = self.score,
|
||||
level = self.level,
|
||||
frames = self.frames,
|
||||
}
|
||||
end
|
||||
|
||||
function MarathonA2Game:getSectionEndLevel()
|
||||
if self.level >= 900 then return 999
|
||||
else return math.floor(self.level / 100 + 1) * 100 end
|
||||
end
|
||||
|
||||
function MarathonA2Game:getBackground()
|
||||
return math.floor(self.level / 100)
|
||||
end
|
||||
|
||||
return MarathonA2Game
|
||||
@@ -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
|
||||
elseif self.roll_frames >= 1337 then
|
||||
self.completed = true
|
||||
end
|
||||
elseif self.ready_frames == 0 then
|
||||
self.frames = self.frames + 1
|
||||
end
|
||||
end
|
||||
|
||||
function DemonModeGame:onPieceEnter()
|
||||
if (self.level % 100 ~= 99) and self.frames ~= 0 then
|
||||
self.level = self.level + 1
|
||||
end
|
||||
end
|
||||
|
||||
function DemonModeGame:onLineClear(cleared_row_count)
|
||||
if cleared_row_count == 4 then
|
||||
self.section_tetris_count = self.section_tetris_count + 1
|
||||
end
|
||||
local advanced_levels = cleared_row_levels[cleared_row_count]
|
||||
if not self.clear then
|
||||
self:updateSectionTimes(self.level, self.level + advanced_levels)
|
||||
end
|
||||
end
|
||||
|
||||
function DemonModeGame:updateSectionTimes(old_level, new_level)
|
||||
local section = math.floor(old_level / 100) + 1
|
||||
if math.floor(old_level / 100) < math.floor(new_level / 100) then
|
||||
-- If at least one Tetris in this section hasn't been made,
|
||||
-- deny section passage.
|
||||
if old_level > 500 then
|
||||
if self.section_tetris_count == 0 then
|
||||
self.level = 100 * math.floor(old_level / 100)
|
||||
self.section_tries = self.section_tries + 1
|
||||
else
|
||||
self.level = math.min(new_level, 2500)
|
||||
-- if this is first try (no denials, add a grade)
|
||||
if self.section_tries == 0 then
|
||||
self.grade = self.grade + 1
|
||||
end
|
||||
self.section_tries = 0
|
||||
self.section_tetris_count = 0
|
||||
-- record new section
|
||||
section_time = self.frames - self.section_start_time
|
||||
table.insert(self.section_times, section_time)
|
||||
self.section_start_time = self.frames
|
||||
-- maybe clear
|
||||
if self.level == 2500 and not self.clear then
|
||||
self.clear = true
|
||||
self.grid:clear()
|
||||
self.roll_frames = -150
|
||||
end
|
||||
end
|
||||
elseif old_level < 100 then
|
||||
-- If section time is under cutoff, skip to level 500.
|
||||
if self.frames < sp(1,00) then
|
||||
self.level = 500
|
||||
self.grade = 5
|
||||
self.section_tries = 0
|
||||
self.section_tetris_count = 0
|
||||
else
|
||||
self.level = math.min(new_level, 2500)
|
||||
end
|
||||
-- record new section
|
||||
section_time = self.frames - self.section_start_time
|
||||
table.insert(self.section_times, section_time)
|
||||
self.section_start_time = self.frames
|
||||
end
|
||||
else
|
||||
self.level = math.min(new_level, 2500)
|
||||
end
|
||||
end
|
||||
|
||||
function DemonModeGame:updateScore(level, drop_bonus, cleared_lines)
|
||||
if cleared_lines > 0 then
|
||||
self.score = self.score + (
|
||||
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
|
||||
cleared_lines * (cleared_lines * 2 - 1) * (self.combo * 2 - 1)
|
||||
)
|
||||
self.lines = self.lines + cleared_lines
|
||||
self.combo = self.combo + cleared_lines - 1
|
||||
else
|
||||
self.drop_bonus = 0
|
||||
self.combo = 1
|
||||
end
|
||||
end
|
||||
|
||||
local letter_grades = {
|
||||
[0] = "", "D", "C", "B", "A",
|
||||
"S", "S-A", "S-B", "S-C", "S-D",
|
||||
"X", "X-A", "X-B", "X-C", "X-D",
|
||||
"W", "W-A", "W-B", "W-C", "W-D",
|
||||
"Master", "MasterS", "MasterX", "MasterW", "Grand Master",
|
||||
"Demon Master"
|
||||
}
|
||||
|
||||
function DemonModeGame:getLetterGrade()
|
||||
return letter_grades[self.grade]
|
||||
end
|
||||
|
||||
function DemonModeGame:drawGrid()
|
||||
if self.clear and not (self.completed or self.game_over) then
|
||||
self.grid:drawInvisible(self.rollOpacityFunction)
|
||||
else
|
||||
self.grid:draw()
|
||||
end
|
||||
end
|
||||
|
||||
DemonModeGame.rollOpacityFunction = function(age)
|
||||
if age > 4 then return 0
|
||||
else return 1 - age / 4 end
|
||||
end
|
||||
|
||||
function DemonModeGame:drawScoringInfo()
|
||||
DemonModeGame.super.drawScoringInfo(self)
|
||||
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
|
||||
love.graphics.setFont(font_3x5_2)
|
||||
love.graphics.print(
|
||||
self.das.direction .. " " ..
|
||||
self.das.frames .. " " ..
|
||||
st(self.prev_inputs)
|
||||
)
|
||||
love.graphics.printf("NEXT", 64, 40, 40, "left")
|
||||
love.graphics.printf("GRADE", 240, 120, 40, "left")
|
||||
love.graphics.printf("SCORE", 240, 200, 40, "left")
|
||||
love.graphics.printf("LEVEL", 240, 320, 40, "left")
|
||||
|
||||
-- draw section time data
|
||||
local current_section = getSectionForLevel(self.level)
|
||||
self:drawSectionTimesWithSecondary(current_section)
|
||||
|
||||
love.graphics.setFont(font_3x5_3)
|
||||
love.graphics.printf(self.score, 240, 220, 90, "left")
|
||||
love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left")
|
||||
love.graphics.printf(string.format("%.2f", self.level / 100), 240, 340, 70, "right")
|
||||
end
|
||||
|
||||
function DemonModeGame:getHighscoreData()
|
||||
return {
|
||||
grade = self.grade,
|
||||
level = self.level,
|
||||
frames = self.frames,
|
||||
}
|
||||
end
|
||||
|
||||
function DemonModeGame:getBackground()
|
||||
return math.floor(self.level / 100)
|
||||
end
|
||||
|
||||
return DemonModeGame
|
||||
@@ -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'
|
||||
|
||||
@@ -36,11 +39,18 @@ function GameMode:new()
|
||||
self.enable_hold = false
|
||||
self.enable_hard_drop = true
|
||||
self.next_queue_length = 1
|
||||
self.additive_gravity = true
|
||||
self.draw_section_times = false
|
||||
self.draw_secondary_section_times = false
|
||||
self.big_mode = false
|
||||
self.irs = true
|
||||
self.ihs = true
|
||||
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
|
||||
@@ -48,13 +58,6 @@ function GameMode:new()
|
||||
self.secondary_section_times = { [0] = 0 }
|
||||
end
|
||||
|
||||
function GameMode:initialize()
|
||||
-- after all the variables are initialized, run initialization procedures
|
||||
for i = 1, 30 do
|
||||
table.insert(self.next_queue, self:getNextPiece(ruleset))
|
||||
end
|
||||
end
|
||||
|
||||
function GameMode:getARR() return 1 end
|
||||
function GameMode:getDropSpeed() return 1 end
|
||||
function GameMode:getARE() return 25 end
|
||||
@@ -64,6 +67,7 @@ function GameMode:getLineClearDelay() return 40 end
|
||||
function GameMode:getDasLimit() return 15 end
|
||||
|
||||
function GameMode:getNextPiece(ruleset)
|
||||
|
||||
return {
|
||||
skin = "2tie",
|
||||
shape = self.randomizer:nextPiece(),
|
||||
@@ -73,9 +77,12 @@ end
|
||||
|
||||
function GameMode:initialize(ruleset)
|
||||
-- generate next queue
|
||||
self:new()
|
||||
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)
|
||||
@@ -100,7 +107,7 @@ function GameMode:update(inputs, ruleset)
|
||||
self:whilePieceActive()
|
||||
local gravity = self:getGravity()
|
||||
|
||||
if self.enable_hold and inputs["hold"] == true and self.held == false then
|
||||
if self.enable_hold and inputs["hold"] == true and self.held == false and self.prev_inputs["hold"] == false then
|
||||
self:hold(inputs, ruleset)
|
||||
self.prev_inputs = inputs
|
||||
return
|
||||
@@ -119,7 +126,8 @@ function GameMode:update(inputs, ruleset)
|
||||
ruleset:processPiece(
|
||||
inputs, self.piece, self.grid, self:getGravity(), self.prev_inputs,
|
||||
self.move, self:getLockDelay(), self:getDropSpeed(),
|
||||
self.drop_locked, self.hard_drop_locked, self.enable_hard_drop
|
||||
self.drop_locked, self.hard_drop_locked,
|
||||
self.enable_hard_drop, self.additive_gravity
|
||||
)
|
||||
|
||||
local piece_dy = self.piece.position.y - piece_y
|
||||
@@ -128,7 +136,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
|
||||
@@ -137,7 +145,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
|
||||
@@ -145,19 +153,20 @@ function GameMode:update(inputs, ruleset)
|
||||
|
||||
if self.piece.locked == true then
|
||||
self.grid:applyPiece(self.piece)
|
||||
self:onPieceLock(self.piece)
|
||||
self.piece = nil
|
||||
if self.enable_hold then
|
||||
self.held = false
|
||||
end
|
||||
|
||||
self.grid:markClearedRows()
|
||||
|
||||
local cleared_row_count = self.grid:getClearedRowCount()
|
||||
|
||||
self:onPieceLock(self.piece, cleared_row_count)
|
||||
self:updateScore(self.level, self.drop_bonus, cleared_row_count)
|
||||
|
||||
self.piece = nil
|
||||
if self.enable_hold then
|
||||
self.held = false
|
||||
end
|
||||
|
||||
if cleared_row_count > 0 then
|
||||
playSE("erase")
|
||||
self.lcd = self:getLineClearDelay()
|
||||
self.are = self:getLineARE()
|
||||
if self.lcd == 0 then
|
||||
@@ -191,10 +200,16 @@ end
|
||||
|
||||
-- event functions
|
||||
function GameMode:whilePieceActive() end
|
||||
function GameMode:onPieceLock(piece) 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
|
||||
@@ -236,8 +251,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
|
||||
@@ -245,12 +272,20 @@ 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
|
||||
end
|
||||
elseif self.are > 0 then
|
||||
self.are = self.are - 1
|
||||
if ruleset.are_cancel and
|
||||
(inputs["left"] or inputs["right"] or
|
||||
inputs["rotate_left"] or inputs["rotate_left2"] or
|
||||
inputs["rotate_right"] or inputs["rotate_right2"] or
|
||||
inputs["rotate_180"]) then
|
||||
self.are = 0
|
||||
end
|
||||
if self.are == 0 then
|
||||
self:initializeOrHold(inputs, ruleset)
|
||||
end
|
||||
@@ -258,7 +293,7 @@ function GameMode:processDelays(inputs, ruleset, drop_speed)
|
||||
end
|
||||
|
||||
function GameMode:initializeOrHold(inputs, ruleset)
|
||||
if self.enable_hold and inputs["hold"] == true then
|
||||
if self.ihs and self.enable_hold and inputs["hold"] == true then
|
||||
self:hold(inputs, ruleset)
|
||||
else
|
||||
self:initializeNextPiece(inputs, ruleset, self.next_queue[1])
|
||||
@@ -298,7 +333,8 @@ function GameMode:initializeNextPiece(inputs, ruleset, piece_data, generate_next
|
||||
inputs, piece_data, self.grid, gravity,
|
||||
self.prev_inputs, self.move,
|
||||
self:getLockDelay(), self:getDropSpeed(),
|
||||
self.lock_drop, self.lock_hard_drop
|
||||
self.lock_drop, self.lock_hard_drop, self.big_mode,
|
||||
self.irs
|
||||
)
|
||||
if self.lock_drop then
|
||||
self.drop_locked = true
|
||||
@@ -343,11 +379,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 = ruleset.spawn_positions[piece].x + offset.x
|
||||
local y = ruleset.spawn_positions[piece].y + offset.y
|
||||
love.graphics.draw(blocks[skin][piece], pos_x+x*16, pos_y+y*16)
|
||||
local x = offset.x + ruleset.spawn_positions[piece].x
|
||||
local y = offset.y + 4.7
|
||||
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
|
||||
@@ -362,7 +399,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,
|
||||
@@ -373,8 +411,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)
|
||||
@@ -389,7 +435,7 @@ function GameMode:drawScoringInfo()
|
||||
love.graphics.print(
|
||||
self.das.direction .. " " ..
|
||||
self.das.frames .. " " ..
|
||||
st(self.prev_inputs) ..
|
||||
strTrueValues(self.prev_inputs) ..
|
||||
self.drop_bonus
|
||||
)
|
||||
|
||||
|
||||
@@ -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 .. " " ..
|
||||
st(self.prev_inputs)
|
||||
)
|
||||
love.graphics.printf("NEXT", 64, 40, 40, "left")
|
||||
love.graphics.printf("TIME LEFT", 240, 250, 80, "left")
|
||||
love.graphics.printf("LEVEL", 240, 320, 40, "left")
|
||||
|
||||
local current_section = math.floor(self.level / 100) + 1
|
||||
self:drawSectionTimesWithSplits(current_section)
|
||||
|
||||
love.graphics.setFont(font_3x5_3)
|
||||
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 < sp(0,10) and time_left % 4 < 2 then
|
||||
love.graphics.setColor(1, 0.3, 0.3, 1)
|
||||
end
|
||||
love.graphics.printf(formatTime(time_left), 240, 270, 160, "left")
|
||||
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.printf(self: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
|
||||
@@ -1,131 +0,0 @@
|
||||
require 'funcs'
|
||||
|
||||
local GameMode = require 'tetris.modes.gamemode'
|
||||
local Piece = require 'tetris.components.piece'
|
||||
|
||||
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
|
||||
|
||||
local LigneGame = GameMode:extend()
|
||||
|
||||
LigneGame.name = "Ligne"
|
||||
LigneGame.hash = "Ligne"
|
||||
LigneGame.tagline = "How fast can you clear 40 lines?"
|
||||
|
||||
|
||||
|
||||
|
||||
function LigneGame:new()
|
||||
LigneGame.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 LigneGame:getDropSpeed()
|
||||
return 20
|
||||
end
|
||||
|
||||
function LigneGame:getARR()
|
||||
return 0
|
||||
end
|
||||
|
||||
function LigneGame:getARE()
|
||||
return 0
|
||||
end
|
||||
|
||||
function LigneGame:getLineARE()
|
||||
return self:getARE()
|
||||
end
|
||||
|
||||
function LigneGame:getDasLimit()
|
||||
return 6
|
||||
end
|
||||
|
||||
function LigneGame:getLineClearDelay()
|
||||
return 0
|
||||
end
|
||||
|
||||
function LigneGame:getLockDelay()
|
||||
return 15
|
||||
end
|
||||
|
||||
function LigneGame:getGravity()
|
||||
return 1/64
|
||||
end
|
||||
|
||||
function LigneGame: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 LigneGame:onPieceLock()
|
||||
self.pieces = self.pieces + 1
|
||||
end
|
||||
|
||||
function LigneGame: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 LigneGame:drawGrid(ruleset)
|
||||
self.grid:draw()
|
||||
if self.piece ~= nil then
|
||||
self:drawGhostPiece(ruleset)
|
||||
end
|
||||
end
|
||||
|
||||
function LigneGame:getHighscoreData()
|
||||
return {
|
||||
level = self.level,
|
||||
frames = self.frames,
|
||||
}
|
||||
end
|
||||
|
||||
function LigneGame:drawScoringInfo()
|
||||
LigneGame.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 LigneGame:getBackground()
|
||||
return 2
|
||||
end
|
||||
|
||||
return LigneGame
|
||||
@@ -36,10 +36,12 @@ 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()
|
||||
if self.delay_level < 1 then return 27
|
||||
if self.delay_level < 1 then return 27
|
||||
elseif self.delay_level < 2 then return 24
|
||||
elseif self.delay_level < 3 then return 21
|
||||
elseif self.delay_level < 4 then return 18
|
||||
@@ -58,7 +60,7 @@ function Marathon2020Game:getLineARE()
|
||||
end
|
||||
|
||||
function Marathon2020Game:getDasLimit()
|
||||
if self.delay_level < 1 then return 15
|
||||
if self.delay_level < 1 then return 15
|
||||
elseif self.delay_level < 3 then return 12
|
||||
elseif self.delay_level < 5 then return 9
|
||||
elseif self.delay_level < 8 then return 8
|
||||
@@ -70,7 +72,7 @@ function Marathon2020Game:getDasLimit()
|
||||
end
|
||||
|
||||
function Marathon2020Game:getLineClearDelay()
|
||||
if self.delay_level < 1 then return 40
|
||||
if self.delay_level < 1 then return 40
|
||||
elseif self.delay_level < 3 then return 25
|
||||
elseif self.delay_level < 4 then return 20
|
||||
elseif self.delay_level < 5 then return 15
|
||||
@@ -82,7 +84,7 @@ function Marathon2020Game:getLineClearDelay()
|
||||
end
|
||||
|
||||
function Marathon2020Game:getLockDelay()
|
||||
if self.delay_level < 6 then return 30
|
||||
if self.delay_level < 6 then return 30
|
||||
elseif self.delay_level < 7 then return 26
|
||||
elseif self.delay_level < 8 then return 22
|
||||
elseif self.delay_level < 9 then return 19
|
||||
@@ -96,35 +98,35 @@ function Marathon2020Game:getLockDelay()
|
||||
end
|
||||
|
||||
function Marathon2020Game:getGravity()
|
||||
if self.level < 30 then return 4/256
|
||||
if self.level < 30 then return 4/256
|
||||
elseif self.level < 35 then return 6/256
|
||||
elseif self.level < 40 then return 8/256
|
||||
elseif self.level < 50 then return 10/256
|
||||
elseif self.level < 60 then return 12/256
|
||||
elseif self.level < 70 then return 16/256
|
||||
elseif self.level < 80 then return 32/256
|
||||
elseif self.level < 90 then return 48/256
|
||||
elseif self.level < 100 then return 64/256
|
||||
elseif self.level < 120 then return 80/256
|
||||
elseif self.level < 140 then return 96/256
|
||||
elseif self.level < 160 then return 112/256
|
||||
elseif self.level < 170 then return 128/256
|
||||
elseif self.level < 200 then return 144/256
|
||||
elseif self.level < 220 then return 4/256
|
||||
elseif self.level < 230 then return 32/256
|
||||
elseif self.level < 233 then return 64/256
|
||||
elseif self.level < 236 then return 96/256
|
||||
elseif self.level < 239 then return 128/256
|
||||
elseif self.level < 243 then return 160/256
|
||||
elseif self.level < 247 then return 192/256
|
||||
elseif self.level < 251 then return 224/256
|
||||
elseif self.level < 40 then return 8/256
|
||||
elseif self.level < 50 then return 10/256
|
||||
elseif self.level < 60 then return 12/256
|
||||
elseif self.level < 70 then return 16/256
|
||||
elseif self.level < 80 then return 32/256
|
||||
elseif self.level < 90 then return 48/256
|
||||
elseif self.level < 100 then return 64/256
|
||||
elseif self.level < 120 then return 80/256
|
||||
elseif self.level < 140 then return 96/256
|
||||
elseif self.level < 160 then return 112/256
|
||||
elseif self.level < 170 then return 128/256
|
||||
elseif self.level < 200 then return 144/256
|
||||
elseif self.level < 220 then return 4/256
|
||||
elseif self.level < 230 then return 32/256
|
||||
elseif self.level < 233 then return 64/256
|
||||
elseif self.level < 236 then return 96/256
|
||||
elseif self.level < 239 then return 128/256
|
||||
elseif self.level < 243 then return 160/256
|
||||
elseif self.level < 247 then return 192/256
|
||||
elseif self.level < 251 then return 224/256
|
||||
elseif self.level < 300 then return 1
|
||||
elseif self.level < 330 then return 2
|
||||
elseif self.level < 360 then return 3
|
||||
elseif self.level < 400 then return 4
|
||||
elseif self.level < 420 then return 5
|
||||
elseif self.level < 450 then return 4
|
||||
elseif self.level < 500 then return 3
|
||||
elseif self.level < 330 then return 2
|
||||
elseif self.level < 360 then return 3
|
||||
elseif self.level < 400 then return 4
|
||||
elseif self.level < 420 then return 5
|
||||
elseif self.level < 450 then return 4
|
||||
elseif self.level < 500 then return 3
|
||||
else return 20 end
|
||||
end
|
||||
|
||||
@@ -142,6 +144,7 @@ function Marathon2020Game:advanceOneFrame()
|
||||
if self.roll_frames < 0 then
|
||||
return false
|
||||
elseif self.roll_frames > 4000 then
|
||||
if self.grade >= 30 and self.section_cool_count >= 20 then self.grade = 31 end
|
||||
self.completed = true
|
||||
end
|
||||
elseif self.ready_frames == 0 then
|
||||
@@ -151,11 +154,11 @@ function Marathon2020Game:advanceOneFrame()
|
||||
end
|
||||
|
||||
local cool_cutoffs = {
|
||||
sp(0,45,00), sp(0,41,50), sp(0,38,50), sp(0,35,00), sp(0,32,50),
|
||||
sp(0,29,20), sp(0,27,20), sp(0,24,80), sp(0,22,80), sp(0,20,60),
|
||||
sp(0,19,60), sp(0,19,40), sp(0,19,40), sp(0,18,40), sp(0,18,20),
|
||||
sp(0,16,20), sp(0,16,20), sp(0,16,20), sp(0,16,20), sp(0,16,20),
|
||||
sp(0,15,20)
|
||||
frameTime(0,45,00), frameTime(0,41,50), frameTime(0,38,50), frameTime(0,35,00), frameTime(0,32,50),
|
||||
frameTime(0,29,20), frameTime(0,27,20), frameTime(0,24,80), frameTime(0,22,80), frameTime(0,20,60),
|
||||
frameTime(0,19,60), frameTime(0,19,40), frameTime(0,19,40), frameTime(0,18,40), frameTime(0,18,20),
|
||||
frameTime(0,16,20), frameTime(0,16,20), frameTime(0,16,20), frameTime(0,16,20), frameTime(0,16,20),
|
||||
frameTime(0,15,20)
|
||||
}
|
||||
|
||||
local levels_for_cleared_rows = { 1, 2, 4, 6 }
|
||||
@@ -246,6 +249,7 @@ function Marathon2020Game:updateGrade(cleared_lines)
|
||||
end
|
||||
|
||||
function Marathon2020Game:getTotalGrade()
|
||||
if self.grade + self.section_cool_count > 50 then return "GM" end
|
||||
return self.grade + self.section_cool_count
|
||||
end
|
||||
|
||||
@@ -258,7 +262,7 @@ local function getSectionForLevel(level)
|
||||
end
|
||||
|
||||
function Marathon2020Game:getEndOfSectionForSection(section)
|
||||
if self.torikan_passed[900] == false and section == 10 then return 999
|
||||
if self.torikan_passed[900] == false and section == 10 then return 999
|
||||
elseif self.torikan_passed[1900] == false and section == 20 then return 2000
|
||||
elseif section == 20 then return 2020
|
||||
else return section * 100 end
|
||||
@@ -284,11 +288,11 @@ function Marathon2020Game:sectionPassed(old_level, new_level)
|
||||
end
|
||||
|
||||
function Marathon2020Game:checkTorikan(section)
|
||||
if section == 5 and self.frames < sp(6,00,00) then self.torikan_passed[500] = true end
|
||||
if section == 9 and self.frames < sp(8,30,00) then self.torikan_passed[900] = true end
|
||||
if section == 10 and self.frames < sp(8,45,00) then self.torikan_passed[1000] = true end
|
||||
if section == 15 and self.frames < sp(11,30,00) then self.torikan_passed[1500] = true end
|
||||
if section == 19 and self.frames < sp(13,15,00) then self.torikan_passed[1900] = true end
|
||||
if section == 5 and self.frames < frameTime(6,00,00) then self.torikan_passed[500] = true end
|
||||
if section == 9 and self.frames < frameTime(8,30,00) then self.torikan_passed[900] = true end
|
||||
if section == 10 and self.frames < frameTime(8,45,00) then self.torikan_passed[1000] = true end
|
||||
if section == 15 and self.frames < frameTime(11,30,00) then self.torikan_passed[1500] = true end
|
||||
if section == 19 and self.frames < frameTime(13,15,00) then self.torikan_passed[1900] = true end
|
||||
end
|
||||
|
||||
function Marathon2020Game:checkClear(level)
|
||||
@@ -301,7 +305,7 @@ function Marathon2020Game:checkClear(level)
|
||||
level >= 2020
|
||||
) then
|
||||
|
||||
if self.torikan_passed[500] == false then self.level = 500
|
||||
if self.torikan_passed[500] == false then self.level = 500
|
||||
elseif self.torikan_passed[900] == false then self.level = 999
|
||||
elseif self.torikan_passed[1000] == false then self.level = 1000
|
||||
elseif self.torikan_passed[1500] == false then self.level = 1500
|
||||
@@ -327,6 +331,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)
|
||||
@@ -389,7 +394,6 @@ Marathon2020Game.mRollOpacityFunction = function(age)
|
||||
end
|
||||
|
||||
function Marathon2020Game:qualifiesForMRoll()
|
||||
return false -- until I actually have grading working
|
||||
--[[
|
||||
|
||||
GM-roll requirements
|
||||
@@ -400,6 +404,8 @@ You qualify for the GM roll if you:
|
||||
- in less than 13:30.00 total.
|
||||
|
||||
]]--
|
||||
|
||||
return self.level >= 2020 and self:getTotalGrade() == 50 and self.frames <= frameTime(13,30)
|
||||
end
|
||||
|
||||
function Marathon2020Game:drawGrid()
|
||||
@@ -430,6 +436,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")
|
||||
|
||||
@@ -14,20 +14,26 @@ MarathonA1Game.tagline = "Can you score enough points to reach the title of Gran
|
||||
|
||||
|
||||
function MarathonA1Game:new()
|
||||
MarathonA1Game.super:new()
|
||||
|
||||
MarathonA1Game.super:new()
|
||||
|
||||
self.roll_frames = 0
|
||||
self.combo = 1
|
||||
self.combo = 1
|
||||
self.bravos = 0
|
||||
self.gm_conditions = {
|
||||
level300 = false,
|
||||
level500 = false,
|
||||
level999 = false
|
||||
}
|
||||
|
||||
}
|
||||
self.SGnames = {
|
||||
"9", "8", "7", "6", "5", "4", "3", "2", "1",
|
||||
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
|
||||
"GM"
|
||||
}
|
||||
|
||||
self.randomizer = History4RollsRandomizer()
|
||||
|
||||
self.lock_drop = false
|
||||
self.enable_hard_drop = false
|
||||
self.lock_drop = false
|
||||
self.enable_hard_drop = false
|
||||
self.enable_hold = false
|
||||
self.next_queue_length = 1
|
||||
end
|
||||
@@ -53,60 +59,60 @@ function MarathonA1Game:getLockDelay()
|
||||
end
|
||||
|
||||
local function getRankForScore(score)
|
||||
if score < 400 then return {rank = "9", next = 400}
|
||||
elseif score < 800 then return {rank = "8", next = 800}
|
||||
elseif score < 1400 then return {rank = "7", next = 1400}
|
||||
elseif score < 2000 then return {rank = "6", next = 2000}
|
||||
elseif score < 3500 then return {rank = "5", next = 3500}
|
||||
elseif score < 5500 then return {rank = "4", next = 5500}
|
||||
elseif score < 8000 then return {rank = "3", next = 8000}
|
||||
elseif score < 12000 then return {rank = "2", next = 12000}
|
||||
elseif score < 16000 then return {rank = "1", next = 16000}
|
||||
elseif score < 22000 then return {rank = "S1", next = 22000}
|
||||
elseif score < 30000 then return {rank = "S2", next = 30000}
|
||||
elseif score < 40000 then return {rank = "S3", next = 40000}
|
||||
elseif score < 52000 then return {rank = "S4", next = 52000}
|
||||
elseif score < 66000 then return {rank = "S5", next = 66000}
|
||||
elseif score < 82000 then return {rank = "S6", next = 82000}
|
||||
elseif score < 100000 then return {rank = "S7", next = 100000}
|
||||
elseif score < 120000 then return {rank = "S8", next = 120000}
|
||||
else return {rank = "S9", next = "???"}
|
||||
end
|
||||
if score < 400 then return {rank = "9", next = 400}
|
||||
elseif score < 800 then return {rank = "8", next = 800}
|
||||
elseif score < 1400 then return {rank = "7", next = 1400}
|
||||
elseif score < 2000 then return {rank = "6", next = 2000}
|
||||
elseif score < 3500 then return {rank = "5", next = 3500}
|
||||
elseif score < 5500 then return {rank = "4", next = 5500}
|
||||
elseif score < 8000 then return {rank = "3", next = 8000}
|
||||
elseif score < 12000 then return {rank = "2", next = 12000}
|
||||
elseif score < 16000 then return {rank = "1", next = 16000}
|
||||
elseif score < 22000 then return {rank = "S1", next = 22000}
|
||||
elseif score < 30000 then return {rank = "S2", next = 30000}
|
||||
elseif score < 40000 then return {rank = "S3", next = 40000}
|
||||
elseif score < 52000 then return {rank = "S4", next = 52000}
|
||||
elseif score < 66000 then return {rank = "S5", next = 66000}
|
||||
elseif score < 82000 then return {rank = "S6", next = 82000}
|
||||
elseif score < 100000 then return {rank = "S7", next = 100000}
|
||||
elseif score < 120000 then return {rank = "S8", next = 120000}
|
||||
else return {rank = "S9", next = "???"}
|
||||
end
|
||||
end
|
||||
|
||||
function MarathonA1Game:getGravity()
|
||||
local level = self.level
|
||||
if (level < 30) then return 4/256
|
||||
elseif (level < 35) then return 6/256
|
||||
elseif (level < 40) then return 8/256
|
||||
elseif (level < 50) then return 10/256
|
||||
elseif (level < 60) then return 12/256
|
||||
elseif (level < 70) then return 16/256
|
||||
elseif (level < 80) then return 32/256
|
||||
elseif (level < 90) then return 48/256
|
||||
elseif (level < 100) then return 64/256
|
||||
elseif (level < 120) then return 80/256
|
||||
elseif (level < 140) then return 96/256
|
||||
elseif (level < 160) then return 112/256
|
||||
elseif (level < 170) then return 128/256
|
||||
elseif (level < 200) then return 144/256
|
||||
elseif (level < 220) then return 4/256
|
||||
elseif (level < 230) then return 32/256
|
||||
elseif (level < 233) then return 64/256
|
||||
elseif (level < 236) then return 96/256
|
||||
elseif (level < 239) then return 128/256
|
||||
elseif (level < 243) then return 160/256
|
||||
elseif (level < 247) then return 192/256
|
||||
elseif (level < 251) then return 224/256
|
||||
elseif (level < 300) then return 1
|
||||
elseif (level < 330) then return 2
|
||||
elseif (level < 360) then return 3
|
||||
elseif (level < 400) then return 4
|
||||
elseif (level < 420) then return 5
|
||||
elseif (level < 450) then return 4
|
||||
elseif (level < 500) then return 3
|
||||
else return 20
|
||||
end
|
||||
local level = self.level
|
||||
if (level < 30) then return 4/256
|
||||
elseif (level < 35) then return 6/256
|
||||
elseif (level < 40) then return 8/256
|
||||
elseif (level < 50) then return 10/256
|
||||
elseif (level < 60) then return 12/256
|
||||
elseif (level < 70) then return 16/256
|
||||
elseif (level < 80) then return 32/256
|
||||
elseif (level < 90) then return 48/256
|
||||
elseif (level < 100) then return 64/256
|
||||
elseif (level < 120) then return 80/256
|
||||
elseif (level < 140) then return 96/256
|
||||
elseif (level < 160) then return 112/256
|
||||
elseif (level < 170) then return 128/256
|
||||
elseif (level < 200) then return 144/256
|
||||
elseif (level < 220) then return 4/256
|
||||
elseif (level < 230) then return 32/256
|
||||
elseif (level < 233) then return 64/256
|
||||
elseif (level < 236) then return 96/256
|
||||
elseif (level < 239) then return 128/256
|
||||
elseif (level < 243) then return 160/256
|
||||
elseif (level < 247) then return 192/256
|
||||
elseif (level < 251) then return 224/256
|
||||
elseif (level < 300) then return 1
|
||||
elseif (level < 330) then return 2
|
||||
elseif (level < 360) then return 3
|
||||
elseif (level < 400) then return 4
|
||||
elseif (level < 420) then return 5
|
||||
elseif (level < 450) then return 4
|
||||
elseif (level < 500) then return 3
|
||||
else return 20
|
||||
end
|
||||
end
|
||||
|
||||
function MarathonA1Game:advanceOneFrame()
|
||||
@@ -128,43 +134,47 @@ function MarathonA1Game:onPieceEnter()
|
||||
end
|
||||
|
||||
function MarathonA1Game:onLineClear(cleared_row_count)
|
||||
self:checkGMRequirements(self.level, self.level + cleared_row_count)
|
||||
self:checkGMRequirements(self.level, self.level + cleared_row_count)
|
||||
if not self.clear then
|
||||
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 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.lines = self.lines + cleared_lines
|
||||
else
|
||||
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 * self.bravo
|
||||
)
|
||||
else
|
||||
self.combo = 1
|
||||
end
|
||||
self.drop_bonus = 0
|
||||
self.combo = 1
|
||||
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 <= sp(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 <= sp(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 <= sp(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
|
||||
@@ -184,17 +194,23 @@ function MarathonA1Game:drawScoringInfo()
|
||||
love.graphics.print(
|
||||
self.das.direction .. " " ..
|
||||
self.das.frames .. " " ..
|
||||
st(self.prev_inputs)
|
||||
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("NEXT RANK", 240, 260, 90, "left")
|
||||
love.graphics.printf("LEVEL", 240, 320, 40, "left")
|
||||
local sg = self.grid:checkSecretGrade()
|
||||
if sg >= 5 then
|
||||
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
|
||||
end
|
||||
|
||||
if self.bravos > 0 then love.graphics.printf("BRAVO", 300, 120, 40, "left") end
|
||||
|
||||
love.graphics.setFont(font_3x5_3)
|
||||
love.graphics.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")
|
||||
@@ -202,7 +218,11 @@ function MarathonA1Game:drawScoringInfo()
|
||||
love.graphics.printf(getRankForScore(self.score).next, 240, 280, 90, "left")
|
||||
love.graphics.printf(self.level, 240, 340, 40, "right")
|
||||
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")
|
||||
|
||||
if sg >= 5 then
|
||||
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
|
||||
end
|
||||
if self.bravos > 0 then love.graphics.printf(self.bravos, 300, 140, 40, "left") end
|
||||
|
||||
love.graphics.setFont(font_8x11)
|
||||
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")
|
||||
end
|
||||
|
||||
@@ -15,10 +15,10 @@ MarathonA2Game.tagline = "The points don't matter! Can you reach the invisible r
|
||||
|
||||
|
||||
function MarathonA2Game:new()
|
||||
MarathonA2Game.super:new()
|
||||
|
||||
MarathonA2Game.super:new()
|
||||
|
||||
self.roll_frames = 0
|
||||
self.combo = 1
|
||||
self.combo = 1
|
||||
self.randomizer = History6RollsRandomizer()
|
||||
self.grade = 0
|
||||
self.grade_points = 0
|
||||
@@ -26,83 +26,89 @@ function MarathonA2Game:new()
|
||||
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.SGnames = {
|
||||
"9", "8", "7", "6", "5", "4", "3", "2", "1",
|
||||
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
|
||||
"GM"
|
||||
}
|
||||
|
||||
self.lock_drop = false
|
||||
self.lock_drop = false
|
||||
self.lock_hard_drop = false
|
||||
self.enable_hold = false
|
||||
self.next_queue_length = 1
|
||||
end
|
||||
|
||||
function MarathonA2Game:getARE()
|
||||
if self.level < 700 then return 27
|
||||
elseif self.level < 800 then return 18
|
||||
else return 14 end
|
||||
if self.level < 700 then return 27
|
||||
elseif self.level < 800 then return 18
|
||||
else return 14 end
|
||||
end
|
||||
|
||||
function MarathonA2Game:getLineARE()
|
||||
if self.level < 600 then return 27
|
||||
elseif self.level < 700 then return 18
|
||||
elseif self.level < 800 then return 14
|
||||
else return 8 end
|
||||
if self.level < 600 then return 27
|
||||
elseif self.level < 700 then return 18
|
||||
elseif self.level < 800 then return 14
|
||||
else return 8 end
|
||||
end
|
||||
|
||||
function MarathonA2Game:getDasLimit()
|
||||
if self.level < 500 then return 15
|
||||
elseif self.level < 900 then return 9
|
||||
else return 7 end
|
||||
if self.level < 500 then return 15
|
||||
elseif self.level < 900 then return 9
|
||||
else return 7 end
|
||||
end
|
||||
|
||||
function MarathonA2Game:getLineClearDelay()
|
||||
if self.level < 500 then return 40
|
||||
elseif self.level < 600 then return 25
|
||||
elseif self.level < 700 then return 16
|
||||
elseif self.level < 800 then return 12
|
||||
else return 6 end
|
||||
if self.level < 500 then return 40
|
||||
elseif self.level < 600 then return 25
|
||||
elseif self.level < 700 then return 16
|
||||
elseif self.level < 800 then return 12
|
||||
else return 6 end
|
||||
end
|
||||
|
||||
function MarathonA2Game:getLockDelay()
|
||||
if self.level < 900 then return 30
|
||||
else return 17 end
|
||||
if self.level < 900 then return 30
|
||||
else return 17 end
|
||||
end
|
||||
|
||||
function MarathonA2Game:getGravity()
|
||||
if (self.level < 30) then return 4/256
|
||||
elseif (self.level < 35) then return 6/256
|
||||
elseif (self.level < 40) then return 8/256
|
||||
elseif (self.level < 50) then return 10/256
|
||||
elseif (self.level < 60) then return 12/256
|
||||
elseif (self.level < 70) then return 16/256
|
||||
elseif (self.level < 80) then return 32/256
|
||||
elseif (self.level < 90) then return 48/256
|
||||
elseif (self.level < 100) then return 64/256
|
||||
elseif (self.level < 120) then return 80/256
|
||||
elseif (self.level < 140) then return 96/256
|
||||
elseif (self.level < 160) then return 112/256
|
||||
elseif (self.level < 170) then return 128/256
|
||||
elseif (self.level < 200) then return 144/256
|
||||
elseif (self.level < 220) then return 4/256
|
||||
elseif (self.level < 230) then return 32/256
|
||||
elseif (self.level < 233) then return 64/256
|
||||
elseif (self.level < 236) then return 96/256
|
||||
elseif (self.level < 239) then return 128/256
|
||||
elseif (self.level < 243) then return 160/256
|
||||
elseif (self.level < 247) then return 192/256
|
||||
elseif (self.level < 251) then return 224/256
|
||||
elseif (self.level < 300) then return 1
|
||||
elseif (self.level < 330) then return 2
|
||||
elseif (self.level < 360) then return 3
|
||||
elseif (self.level < 400) then return 4
|
||||
elseif (self.level < 420) then return 5
|
||||
elseif (self.level < 450) then return 4
|
||||
elseif (self.level < 500) then return 3
|
||||
else return 20
|
||||
end
|
||||
if (self.level < 30) then return 4/256
|
||||
elseif (self.level < 35) then return 6/256
|
||||
elseif (self.level < 40) then return 8/256
|
||||
elseif (self.level < 50) then return 10/256
|
||||
elseif (self.level < 60) then return 12/256
|
||||
elseif (self.level < 70) then return 16/256
|
||||
elseif (self.level < 80) then return 32/256
|
||||
elseif (self.level < 90) then return 48/256
|
||||
elseif (self.level < 100) then return 64/256
|
||||
elseif (self.level < 120) then return 80/256
|
||||
elseif (self.level < 140) then return 96/256
|
||||
elseif (self.level < 160) then return 112/256
|
||||
elseif (self.level < 170) then return 128/256
|
||||
elseif (self.level < 200) then return 144/256
|
||||
elseif (self.level < 220) then return 4/256
|
||||
elseif (self.level < 230) then return 32/256
|
||||
elseif (self.level < 233) then return 64/256
|
||||
elseif (self.level < 236) then return 96/256
|
||||
elseif (self.level < 239) then return 128/256
|
||||
elseif (self.level < 243) then return 160/256
|
||||
elseif (self.level < 247) then return 192/256
|
||||
elseif (self.level < 251) then return 224/256
|
||||
elseif (self.level < 300) then return 1
|
||||
elseif (self.level < 330) then return 2
|
||||
elseif (self.level < 360) then return 3
|
||||
elseif (self.level < 400) then return 4
|
||||
elseif (self.level < 420) then return 5
|
||||
elseif (self.level < 450) then return 4
|
||||
elseif (self.level < 500) then return 3
|
||||
else return 20
|
||||
end
|
||||
end
|
||||
|
||||
function MarathonA2Game:advanceOneFrame()
|
||||
if self.clear then
|
||||
self.roll_frames = self.roll_frames + 1
|
||||
if self.roll_frames < 0 then return false end
|
||||
if self.roll_frames > 3694 then
|
||||
self.completed = true
|
||||
if self.grade == 32 then
|
||||
@@ -121,32 +127,33 @@ function MarathonA2Game:onPieceEnter()
|
||||
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()
|
||||
self.roll_frames = -150
|
||||
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: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
|
||||
function MarathonA2Game:onLineClear(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
|
||||
self.grid:clear()
|
||||
if self:qualifiesForMRoll() then self.grade = 32 end
|
||||
self.roll_frames = -150
|
||||
end
|
||||
self.lock_drop = self.level >= 900
|
||||
self.lock_hard_drop = self.level >= 900
|
||||
end
|
||||
|
||||
function MarathonA2Game:updateSectionTimes(old_level, new_level)
|
||||
@@ -246,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
|
||||
@@ -260,7 +267,7 @@ function MarathonA2Game:qualifiesForMRoll()
|
||||
local section_average = 0
|
||||
for section = 0, 4 do
|
||||
section_average = section_average + self.section_times[section]
|
||||
if self.section_times[section] > sp(1,05) then
|
||||
if self.section_times[section] > frameTime(1,05) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
@@ -273,7 +280,7 @@ function MarathonA2Game:qualifiesForMRoll()
|
||||
return false
|
||||
end
|
||||
end
|
||||
if self.grade < 17 or self.frames > sp(8,45) then
|
||||
if self.grade < 31 or self.frames > frameTime(8,45) then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
@@ -303,12 +310,12 @@ MarathonA2Game.mRollOpacityFunction = function(age)
|
||||
else return 1 - age / 4 end
|
||||
end
|
||||
|
||||
function MarathonA2Game:drawGrid(ruleset)
|
||||
function MarathonA2Game:drawGrid()
|
||||
if self.clear and not (self.completed or self.game_over) then
|
||||
if self:qualifiesForMRoll() then
|
||||
self.grid:drawInvisible(self.mRollOpacityFunction)
|
||||
self.grid:drawInvisible(self.mRollOpacityFunction, nil, false)
|
||||
else
|
||||
self.grid:drawInvisible(self.rollOpacityFunction)
|
||||
self.grid:drawInvisible(self.rollOpacityFunction, nil, false)
|
||||
end
|
||||
else
|
||||
self.grid:draw()
|
||||
@@ -325,18 +332,35 @@ function MarathonA2Game:drawScoringInfo()
|
||||
love.graphics.print(
|
||||
self.das.direction .. " " ..
|
||||
self.das.frames .. " " ..
|
||||
st(self.prev_inputs)
|
||||
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")
|
||||
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)
|
||||
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")
|
||||
if sg >= 5 then
|
||||
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
|
||||
end
|
||||
|
||||
love.graphics.setFont(font_8x11)
|
||||
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")
|
||||
|
||||
@@ -15,11 +15,11 @@ MarathonA3Game.tagline = "The game gets faster way more quickly! Can you get all
|
||||
|
||||
|
||||
function MarathonA3Game:new()
|
||||
MarathonA3Game.super:new()
|
||||
|
||||
self.speed_level = 0
|
||||
MarathonA3Game.super:new()
|
||||
|
||||
self.speed_level = 0
|
||||
self.roll_frames = 0
|
||||
self.combo = 1
|
||||
self.combo = 1
|
||||
self.grade = 0
|
||||
self.grade_points = 0
|
||||
self.roll_points = 0
|
||||
@@ -29,85 +29,99 @@ 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()
|
||||
|
||||
self.lock_drop = true
|
||||
self.lock_hard_drop = true
|
||||
self.SGnames = {
|
||||
"9", "8", "7", "6", "5", "4", "3", "2", "1",
|
||||
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
|
||||
"GM"
|
||||
}
|
||||
|
||||
self.lock_drop = true
|
||||
self.lock_hard_drop = true
|
||||
self.enable_hold = true
|
||||
self.next_queue_length = 3
|
||||
|
||||
self.coolregret_message = "COOL!!"
|
||||
self.coolregret_timer = 0
|
||||
|
||||
self.torikan_passed = false
|
||||
end
|
||||
|
||||
function MarathonA3Game:getARE()
|
||||
if self.speed_level < 700 then return 27
|
||||
elseif self.speed_level < 800 then return 18
|
||||
elseif self.speed_level < 1000 then return 14
|
||||
elseif self.speed_level < 1100 then return 8
|
||||
elseif self.speed_level < 1200 then return 7
|
||||
else return 6 end
|
||||
if self.speed_level < 700 then return 27
|
||||
elseif self.speed_level < 800 then return 18
|
||||
elseif self.speed_level < 1000 then return 14
|
||||
elseif self.speed_level < 1100 then return 8
|
||||
elseif self.speed_level < 1200 then return 7
|
||||
else return 6 end
|
||||
end
|
||||
|
||||
function MarathonA3Game:getLineARE()
|
||||
if self.speed_level < 600 then return 27
|
||||
elseif self.speed_level < 700 then return 18
|
||||
elseif self.speed_level < 800 then return 14
|
||||
elseif self.speed_level < 1100 then return 8
|
||||
elseif self.speed_level < 1200 then return 7
|
||||
else return 6 end
|
||||
if self.speed_level < 600 then return 27
|
||||
elseif self.speed_level < 700 then return 18
|
||||
elseif self.speed_level < 800 then return 14
|
||||
elseif self.speed_level < 1100 then return 8
|
||||
elseif self.speed_level < 1200 then return 7
|
||||
else return 6 end
|
||||
end
|
||||
|
||||
function MarathonA3Game:getDasLimit()
|
||||
if self.speed_level < 500 then return 15
|
||||
elseif self.speed_level < 900 then return 9
|
||||
else return 7 end
|
||||
if self.speed_level < 500 then return 15
|
||||
elseif self.speed_level < 900 then return 9
|
||||
else return 7 end
|
||||
end
|
||||
|
||||
function MarathonA3Game:getLineClearDelay()
|
||||
if self.speed_level < 500 then return 40
|
||||
elseif self.speed_level < 600 then return 25
|
||||
elseif self.speed_level < 700 then return 16
|
||||
elseif self.speed_level < 800 then return 12
|
||||
else return 6 end
|
||||
if self.speed_level < 500 then return 40
|
||||
elseif self.speed_level < 600 then return 25
|
||||
elseif self.speed_level < 700 then return 16
|
||||
elseif self.speed_level < 800 then return 12
|
||||
elseif self.speed_level < 1100 then return 6
|
||||
elseif self.speed_level < 1200 then return 5
|
||||
else return 4 end
|
||||
end
|
||||
|
||||
function MarathonA3Game:getLockDelay()
|
||||
if self.speed_level < 900 then return 30
|
||||
elseif self.speed_level < 1100 then return 17
|
||||
else return 15 end
|
||||
if self.speed_level < 900 then return 30
|
||||
elseif self.speed_level < 1100 then return 17
|
||||
else return 15 end
|
||||
end
|
||||
|
||||
function MarathonA3Game:getGravity()
|
||||
if (self.speed_level < 30) then return 4/256
|
||||
elseif (self.speed_level < 35) then return 6/256
|
||||
elseif (self.speed_level < 40) then return 8/256
|
||||
elseif (self.speed_level < 50) then return 10/256
|
||||
elseif (self.speed_level < 60) then return 12/256
|
||||
elseif (self.speed_level < 70) then return 16/256
|
||||
elseif (self.speed_level < 80) then return 32/256
|
||||
elseif (self.speed_level < 90) then return 48/256
|
||||
elseif (self.speed_level < 100) then return 64/256
|
||||
elseif (self.speed_level < 120) then return 80/256
|
||||
elseif (self.speed_level < 140) then return 96/256
|
||||
elseif (self.speed_level < 160) then return 112/256
|
||||
elseif (self.speed_level < 170) then return 128/256
|
||||
elseif (self.speed_level < 200) then return 144/256
|
||||
elseif (self.speed_level < 220) then return 4/256
|
||||
elseif (self.speed_level < 230) then return 32/256
|
||||
elseif (self.speed_level < 233) then return 64/256
|
||||
elseif (self.speed_level < 236) then return 96/256
|
||||
elseif (self.speed_level < 239) then return 128/256
|
||||
elseif (self.speed_level < 243) then return 160/256
|
||||
elseif (self.speed_level < 247) then return 192/256
|
||||
elseif (self.speed_level < 251) then return 224/256
|
||||
elseif (self.speed_level < 300) then return 1
|
||||
elseif (self.speed_level < 330) then return 2
|
||||
elseif (self.speed_level < 360) then return 3
|
||||
elseif (self.speed_level < 400) then return 4
|
||||
elseif (self.speed_level < 420) then return 5
|
||||
elseif (self.speed_level < 450) then return 4
|
||||
elseif (self.speed_level < 500) then return 3
|
||||
else return 20
|
||||
end
|
||||
if (self.speed_level < 30) then return 4/256
|
||||
elseif (self.speed_level < 35) then return 6/256
|
||||
elseif (self.speed_level < 40) then return 8/256
|
||||
elseif (self.speed_level < 50) then return 10/256
|
||||
elseif (self.speed_level < 60) then return 12/256
|
||||
elseif (self.speed_level < 70) then return 16/256
|
||||
elseif (self.speed_level < 80) then return 32/256
|
||||
elseif (self.speed_level < 90) then return 48/256
|
||||
elseif (self.speed_level < 100) then return 64/256
|
||||
elseif (self.speed_level < 120) then return 80/256
|
||||
elseif (self.speed_level < 140) then return 96/256
|
||||
elseif (self.speed_level < 160) then return 112/256
|
||||
elseif (self.speed_level < 170) then return 128/256
|
||||
elseif (self.speed_level < 200) then return 144/256
|
||||
elseif (self.speed_level < 220) then return 4/256
|
||||
elseif (self.speed_level < 230) then return 32/256
|
||||
elseif (self.speed_level < 233) then return 64/256
|
||||
elseif (self.speed_level < 236) then return 96/256
|
||||
elseif (self.speed_level < 239) then return 128/256
|
||||
elseif (self.speed_level < 243) then return 160/256
|
||||
elseif (self.speed_level < 247) then return 192/256
|
||||
elseif (self.speed_level < 251) then return 224/256
|
||||
elseif (self.speed_level < 300) then return 1
|
||||
elseif (self.speed_level < 330) then return 2
|
||||
elseif (self.speed_level < 360) then return 3
|
||||
elseif (self.speed_level < 400) then return 4
|
||||
elseif (self.speed_level < 420) then return 5
|
||||
elseif (self.speed_level < 450) then return 4
|
||||
elseif (self.speed_level < 500) then return 3
|
||||
else return 20
|
||||
end
|
||||
end
|
||||
|
||||
function MarathonA3Game:advanceOneFrame()
|
||||
@@ -117,7 +131,7 @@ function MarathonA3Game:advanceOneFrame()
|
||||
if self.roll_frames + 1 == 0 then
|
||||
switchBGM("credit_roll", "gm3")
|
||||
end
|
||||
return
|
||||
return false
|
||||
elseif self.roll_frames > 3238 then
|
||||
if self:qualifiesForMRoll() then
|
||||
self.roll_points = self.roll_points + 160
|
||||
@@ -135,36 +149,41 @@ end
|
||||
|
||||
function MarathonA3Game:onPieceEnter()
|
||||
if (self.level % 100 ~= 99) and self.level ~= 998 and self.frames ~= 0 then
|
||||
self:updateSectionTimes(self.level, self.level + 1)
|
||||
self.level = self.level + 1
|
||||
self.speed_level = self.speed_level + 1
|
||||
end
|
||||
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
|
||||
|
||||
local cleared_row_levels = {1, 2, 4, 6}
|
||||
|
||||
function MarathonA3Game:onLineClear(cleared_row_count)
|
||||
local advanced_levels = cleared_row_levels[cleared_row_count]
|
||||
self:updateSectionTimes(self.level, self.level + advanced_levels)
|
||||
if not self.clear then
|
||||
self.level = math.min(self.level + advanced_levels, 999)
|
||||
end
|
||||
self.speed_level = self.speed_level + advanced_levels
|
||||
if self.level == 999 and not self.clear then
|
||||
self.clear = true
|
||||
self.grid:clear()
|
||||
self.roll_frames = -150
|
||||
end
|
||||
local advanced_levels = cleared_row_levels[cleared_row_count]
|
||||
self:updateSectionTimes(self.level, self.level + advanced_levels)
|
||||
if not self.clear then
|
||||
self.level = math.min(self.level + advanced_levels, 999)
|
||||
end
|
||||
self.speed_level = self.speed_level + advanced_levels
|
||||
if self.level == 999 and not self.clear then
|
||||
self.clear = true
|
||||
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 = {
|
||||
sp(0,52), sp(0,52), sp(0,49), sp(0,45), sp(0,45),
|
||||
sp(0,42), sp(0,42), sp(0,38), sp(0,38),
|
||||
frameTime(0,52), frameTime(0,52), frameTime(0,49), frameTime(0,45), frameTime(0,45),
|
||||
frameTime(0,42), frameTime(0,42), frameTime(0,38), frameTime(0,38),
|
||||
}
|
||||
|
||||
local regret_cutoffs = {
|
||||
sp(0,90), sp(0,75), sp(0,75), sp(0,68), sp(0,60),
|
||||
sp(0,60), sp(0,50), sp(0,50), sp(0,50), sp(0,50),
|
||||
frameTime(0,90), frameTime(0,75), frameTime(0,75), frameTime(0,68), frameTime(0,60),
|
||||
frameTime(0,60), frameTime(0,50), frameTime(0,50), frameTime(0,50), frameTime(0,50),
|
||||
}
|
||||
|
||||
function MarathonA3Game:updateSectionTimes(old_level, new_level)
|
||||
@@ -177,42 +196,53 @@ 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")
|
||||
elseif section <= 9 and self.section_status[section - 1] == "cool" and
|
||||
self.section_70_times[section] < self.section_70_times[section - 1] + 1000 then
|
||||
self.coolregret_message = "REGRET!!"
|
||||
self.coolregret_timer = 300
|
||||
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")
|
||||
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")
|
||||
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)
|
||||
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:updateGrade(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 * self.combo
|
||||
)
|
||||
else
|
||||
self.combo = 1
|
||||
end
|
||||
self.drop_bonus = 0
|
||||
self.combo = 1
|
||||
end
|
||||
end
|
||||
|
||||
@@ -332,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
|
||||
@@ -370,12 +402,16 @@ function MarathonA3Game:drawScoringInfo()
|
||||
love.graphics.print(
|
||||
self.das.direction .. " " ..
|
||||
self.das.frames .. " " ..
|
||||
st(self.prev_inputs)
|
||||
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")
|
||||
local sg = self.grid:checkSecretGrade()
|
||||
if sg >= 5 then
|
||||
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
|
||||
end
|
||||
|
||||
-- draw section time data
|
||||
current_section = math.floor(self.level / 100) + 1
|
||||
@@ -402,14 +438,25 @@ 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")
|
||||
self.coolregret_timer = self.coolregret_timer - 1
|
||||
end
|
||||
|
||||
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
|
||||
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
|
||||
end
|
||||
|
||||
love.graphics.setFont(font_8x11)
|
||||
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")
|
||||
end
|
||||
|
||||
@@ -3,22 +3,20 @@ require 'funcs'
|
||||
local GameMode = require 'tetris.modes.gamemode'
|
||||
local Piece = require 'tetris.components.piece'
|
||||
|
||||
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
|
||||
local Bag7NoSZOStartRandomizer = require 'tetris.randomizers.bag7noSZOstart'
|
||||
|
||||
local MarathonL1Game = GameMode:extend()
|
||||
local MarathonAX4Game = GameMode:extend()
|
||||
|
||||
MarathonL1Game.name = "Line Attack"
|
||||
MarathonL1Game.hash = "MarathonL1"
|
||||
MarathonL1Game.tagline = "Can you clear the time hurdles when the game goes this fast?"
|
||||
MarathonAX4Game.name = "Marathon AX4"
|
||||
MarathonAX4Game.hash = "MarathonAX4"
|
||||
MarathonAX4Game.tagline = "Can you clear the time hurdles when the game goes this fast?"
|
||||
|
||||
|
||||
|
||||
|
||||
function MarathonL1Game:new()
|
||||
MarathonL1Game.super:new()
|
||||
function MarathonAX4Game:new()
|
||||
MarathonAX4Game.super:new()
|
||||
|
||||
self.roll_frames = 0
|
||||
self.randomizer = History6RollsRandomizer()
|
||||
self.randomizer = Bag7NoSZOStartRandomizer()
|
||||
|
||||
self.section_time_limit = 3600
|
||||
self.section_start_time = 0
|
||||
@@ -26,12 +24,13 @@ function MarathonL1Game:new()
|
||||
self.section_clear = false
|
||||
|
||||
self.lock_drop = true
|
||||
self.lock_hard_drop = true
|
||||
self.enable_hold = true
|
||||
self.next_queue_length = 3
|
||||
end
|
||||
|
||||
function MarathonL1Game:getARE()
|
||||
if self.lines < 10 then return 18
|
||||
function MarathonAX4Game:getARE()
|
||||
if self.lines < 10 then return 18
|
||||
elseif self.lines < 40 then return 14
|
||||
elseif self.lines < 60 then return 12
|
||||
elseif self.lines < 70 then return 10
|
||||
@@ -40,24 +39,24 @@ function MarathonL1Game:getARE()
|
||||
else return 6 end
|
||||
end
|
||||
|
||||
function MarathonL1Game:getLineARE()
|
||||
function MarathonAX4Game:getLineARE()
|
||||
return self:getARE()
|
||||
end
|
||||
|
||||
function MarathonL1Game:getDasLimit()
|
||||
if self.lines < 20 then return 10
|
||||
function MarathonAX4Game:getDasLimit()
|
||||
if self.lines < 20 then return 10
|
||||
elseif self.lines < 50 then return 9
|
||||
elseif self.lines < 70 then return 8
|
||||
else return 7 end
|
||||
end
|
||||
|
||||
function MarathonL1Game:getLineClearDelay()
|
||||
if self.lines < 10 then return 14
|
||||
function MarathonAX4Game:getLineClearDelay()
|
||||
if self.lines < 10 then return 14
|
||||
elseif self.lines < 30 then return 9
|
||||
else return 5 end
|
||||
end
|
||||
|
||||
function MarathonL1Game:getLockDelay()
|
||||
function MarathonAX4Game:getLockDelay()
|
||||
if self.lines < 10 then return 28
|
||||
elseif self.lines < 20 then return 24
|
||||
elseif self.lines < 30 then return 22
|
||||
@@ -67,15 +66,15 @@ function MarathonL1Game:getLockDelay()
|
||||
else return 13 end
|
||||
end
|
||||
|
||||
function MarathonL1Game:getGravity()
|
||||
function MarathonAX4Game:getGravity()
|
||||
return 20
|
||||
end
|
||||
|
||||
function MarathonL1Game:getSection()
|
||||
function MarathonAX4Game:getSection()
|
||||
return math.floor(level / 100) + 1
|
||||
end
|
||||
|
||||
function MarathonL1Game:advanceOneFrame()
|
||||
function MarathonAX4Game:advanceOneFrame()
|
||||
if self.clear then
|
||||
self.roll_frames = self.roll_frames + 1
|
||||
if self.roll_frames < 0 then
|
||||
@@ -94,23 +93,24 @@ function MarathonL1Game:advanceOneFrame()
|
||||
return true
|
||||
end
|
||||
|
||||
function MarathonL1Game:onLineClear(cleared_row_count)
|
||||
function MarathonAX4Game:onLineClear(cleared_row_count)
|
||||
if not self.clear then
|
||||
local new_lines = self.lines + cleared_row_count
|
||||
self:updateSectionTimes(self.lines, new_lines)
|
||||
self.lines = math.min(new_lines, 150)
|
||||
if self.lines == 150 then
|
||||
self.grid:clear()
|
||||
self.clear = true
|
||||
self.roll_frames = -150
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MarathonL1Game:getSectionTime()
|
||||
function MarathonAX4Game:getSectionTime()
|
||||
return self.frames - self.section_start_time
|
||||
end
|
||||
|
||||
function MarathonL1Game:updateSectionTimes(old_lines, new_lines)
|
||||
function MarathonAX4Game:updateSectionTimes(old_lines, new_lines)
|
||||
if math.floor(old_lines / 10) < math.floor(new_lines / 10) then
|
||||
-- record new section
|
||||
table.insert(self.section_times, self:getSectionTime())
|
||||
@@ -119,23 +119,23 @@ function MarathonL1Game:updateSectionTimes(old_lines, new_lines)
|
||||
end
|
||||
end
|
||||
|
||||
function MarathonL1Game:onPieceEnter()
|
||||
function MarathonAX4Game:onPieceEnter()
|
||||
self.section_clear = false
|
||||
end
|
||||
|
||||
function MarathonL1Game:drawGrid(ruleset)
|
||||
function MarathonAX4Game:drawGrid(ruleset)
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
function MarathonL1Game:getHighscoreData()
|
||||
function MarathonAX4Game:getHighscoreData()
|
||||
return {
|
||||
lines = self.lines,
|
||||
frames = self.frames,
|
||||
}
|
||||
end
|
||||
|
||||
function MarathonL1Game:drawScoringInfo()
|
||||
MarathonL1Game.super.drawScoringInfo(self)
|
||||
function MarathonAX4Game:drawScoringInfo()
|
||||
MarathonAX4Game.super.drawScoringInfo(self)
|
||||
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
|
||||
@@ -143,7 +143,7 @@ function MarathonL1Game:drawScoringInfo()
|
||||
love.graphics.print(
|
||||
self.das.direction .. " " ..
|
||||
self.das.frames .. " " ..
|
||||
st(self.prev_inputs)
|
||||
strTrueValues(self.prev_inputs)
|
||||
)
|
||||
love.graphics.printf("NEXT", 64, 40, 40, "left")
|
||||
love.graphics.printf("TIME LEFT", 240, 250, 80, "left")
|
||||
@@ -158,19 +158,19 @@ function MarathonL1Game:drawScoringInfo()
|
||||
|
||||
-- draw time left, flash red if necessary
|
||||
local time_left = self.section_time_limit - math.max(self:getSectionTime(), 0)
|
||||
if not self.game_over and not self.clear and time_left < sp(0,10) and time_left % 4 < 2 then
|
||||
if not self.game_over and not self.clear and time_left < frameTime(0,10) and time_left % 4 < 2 then
|
||||
love.graphics.setColor(1, 0.3, 0.3, 1)
|
||||
end
|
||||
love.graphics.printf(formatTime(time_left), 240, 270, 160, "left")
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
end
|
||||
|
||||
function MarathonL1Game:getSectionEndLines()
|
||||
function MarathonAX4Game:getSectionEndLines()
|
||||
return math.floor(self.lines / 10 + 1) * 10
|
||||
end
|
||||
|
||||
function MarathonL1Game:getBackground()
|
||||
function MarathonAX4Game:getBackground()
|
||||
return math.floor(self.lines / 10)
|
||||
end
|
||||
|
||||
return MarathonL1Game
|
||||
return MarathonAX4Game
|
||||
@@ -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, 20)
|
||||
end
|
||||
|
||||
return PacerTest
|
||||
@@ -11,14 +11,18 @@ PhantomManiaGame.name = "Phantom Mania"
|
||||
PhantomManiaGame.hash = "PhantomMania"
|
||||
PhantomManiaGame.tagline = "The blocks disappear as soon as they're locked! Can you remember where everything is?"
|
||||
|
||||
|
||||
|
||||
|
||||
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
|
||||
@@ -26,7 +30,7 @@ function PhantomManiaGame:new()
|
||||
end
|
||||
|
||||
function PhantomManiaGame:getARE()
|
||||
if self.level < 100 then return 18
|
||||
if self.level < 100 then return 18
|
||||
elseif self.level < 200 then return 14
|
||||
elseif self.level < 400 then return 8
|
||||
elseif self.level < 500 then return 7
|
||||
@@ -34,14 +38,14 @@ function PhantomManiaGame:getARE()
|
||||
end
|
||||
|
||||
function PhantomManiaGame:getLineARE()
|
||||
if self.level < 100 then return 18
|
||||
if self.level < 100 then return 18
|
||||
elseif self.level < 400 then return 8
|
||||
elseif self.level < 500 then return 7
|
||||
else return 6 end
|
||||
end
|
||||
|
||||
function PhantomManiaGame:getDasLimit()
|
||||
if self.level < 200 then return 11
|
||||
if self.level < 200 then return 11
|
||||
elseif self.level < 300 then return 10
|
||||
elseif self.level < 400 then return 9
|
||||
else return 7 end
|
||||
@@ -52,7 +56,7 @@ function PhantomManiaGame:getLineClearDelay()
|
||||
end
|
||||
|
||||
function PhantomManiaGame:getLockDelay()
|
||||
if self.level < 100 then return 30
|
||||
if self.level < 100 then return 30
|
||||
elseif self.level < 200 then return 26
|
||||
elseif self.level < 300 then return 22
|
||||
elseif self.level < 400 then return 18
|
||||
@@ -64,15 +68,15 @@ function PhantomManiaGame:getGravity()
|
||||
end
|
||||
|
||||
function PhantomManiaGame:hitTorikan(old_level, new_level)
|
||||
if old_level < 300 and new_level >= 300 and self.frames > sp(2,28) then
|
||||
if old_level < 300 and new_level >= 300 and self.frames > frameTime(2,28) then
|
||||
self.level = 300
|
||||
return true
|
||||
end
|
||||
if old_level < 500 and new_level >= 500 and self.frames > sp(3,38) then
|
||||
if old_level < 500 and new_level >= 500 and self.frames > frameTime(3,38) then
|
||||
self.level = 500
|
||||
return true
|
||||
end
|
||||
if old_level < 800 and new_level >= 800 and self.frames > sp(5,23) then
|
||||
if old_level < 800 and new_level >= 800 and self.frames > frameTime(5,23) then
|
||||
self.level = 800
|
||||
return true
|
||||
end
|
||||
@@ -102,7 +106,7 @@ end
|
||||
function PhantomManiaGame:onLineClear(cleared_row_count)
|
||||
if not self.clear then
|
||||
local new_level = self.level + cleared_row_count
|
||||
if self:hitTorikan(self.level, new_level) then
|
||||
if new_level >= 999 or self:hitTorikan(self.level, new_level) then
|
||||
if new_level >= 999 then
|
||||
self.level = 999
|
||||
end
|
||||
@@ -114,16 +118,17 @@ function PhantomManiaGame:onLineClear(cleared_row_count)
|
||||
end
|
||||
|
||||
function PhantomManiaGame: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
|
||||
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 * self.combo
|
||||
)
|
||||
else
|
||||
self.combo = 1
|
||||
end
|
||||
self.drop_bonus = 0
|
||||
self.combo = 1
|
||||
end
|
||||
end
|
||||
|
||||
@@ -134,7 +139,7 @@ end
|
||||
|
||||
function PhantomManiaGame:drawGrid()
|
||||
if not (self.game_over or self.clear) then
|
||||
self.grid:drawInvisible(self.rollOpacityFunction)
|
||||
self.grid:drawInvisible(self.rollOpacityFunction, nil, false)
|
||||
else
|
||||
self.grid:draw()
|
||||
end
|
||||
@@ -164,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
|
||||
@@ -178,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()
|
||||
|
||||
@@ -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
|
||||
@@ -25,20 +24,32 @@ function PhantomMania2Game:new()
|
||||
self.combo = 1
|
||||
self.hold_age = 0
|
||||
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()
|
||||
if self.level < 300 then return 12
|
||||
if self.level < 300 then return 12
|
||||
else return 6 end
|
||||
end
|
||||
|
||||
function PhantomMania2Game:getLineARE()
|
||||
if self.level < 100 then return 8
|
||||
if self.level < 100 then return 8
|
||||
elseif self.level < 200 then return 7
|
||||
elseif self.level < 500 then return 6
|
||||
elseif self.level < 1300 then return 5
|
||||
@@ -46,7 +57,7 @@ function PhantomMania2Game:getLineARE()
|
||||
end
|
||||
|
||||
function PhantomMania2Game:getDasLimit()
|
||||
if self.level < 200 then return 9
|
||||
if self.level < 200 then return 9
|
||||
elseif self.level < 500 then return 7
|
||||
else return 5 end
|
||||
end
|
||||
@@ -56,7 +67,7 @@ function PhantomMania2Game:getLineClearDelay()
|
||||
end
|
||||
|
||||
function PhantomMania2Game:getLockDelay()
|
||||
if self.level < 200 then return 18
|
||||
if self.level < 200 then return 18
|
||||
elseif self.level < 300 then return 17
|
||||
elseif self.level < 500 then return 15
|
||||
elseif self.level < 600 then return 13
|
||||
@@ -84,19 +95,19 @@ function PhantomMania2Game:getNextPiece(ruleset)
|
||||
end
|
||||
|
||||
function PhantomMania2Game:hitTorikan(old_level, new_level)
|
||||
if old_level < 300 and new_level >= 300 and self.frames > sp(2,02) then
|
||||
if old_level < 300 and new_level >= 300 and self.frames > frameTime(2,02) then
|
||||
self.level = 300
|
||||
return true
|
||||
end
|
||||
if old_level < 500 and new_level >= 500 and self.frames > sp(3,03) then
|
||||
if old_level < 500 and new_level >= 500 and self.frames > frameTime(3,03) then
|
||||
self.level = 500
|
||||
return true
|
||||
end
|
||||
if old_level < 800 and new_level >= 800 and self.frames > sp(4,40) then
|
||||
if old_level < 800 and new_level >= 800 and self.frames > frameTime(4,45) then
|
||||
self.level = 800
|
||||
return true
|
||||
end
|
||||
if old_level < 1000 and new_level >= 1000 and self.frames > sp(5,38) then
|
||||
if old_level < 1000 and new_level >= 1000 and self.frames > frameTime(5,38) then
|
||||
self.level = 1000
|
||||
return true
|
||||
end
|
||||
@@ -114,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
|
||||
@@ -135,7 +148,8 @@ function PhantomMania2Game:onPieceEnter()
|
||||
end
|
||||
|
||||
local cleared_row_levels = {1, 2, 4, 6}
|
||||
local cleared_row_points = {0.02, 0.05, 0.15, 0.6}
|
||||
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
|
||||
@@ -144,6 +158,7 @@ function PhantomMania2Game:onLineClear(cleared_row_count)
|
||||
if new_level >= 1300 or self:hitTorikan(self.level, new_level) then
|
||||
if new_level >= 1300 then
|
||||
self.level = 1300
|
||||
self.big_mode = true
|
||||
end
|
||||
self.clear = true
|
||||
self.grid:clear()
|
||||
@@ -152,42 +167,52 @@ function PhantomMania2Game:onLineClear(cleared_row_count)
|
||||
self.level = math.min(new_level, 1300)
|
||||
end
|
||||
self:advanceBottomRow(-cleared_row_count)
|
||||
else
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PhantomMania2Game:onPieceLock()
|
||||
self:advanceBottomRow(1)
|
||||
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 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
|
||||
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 * self.combo
|
||||
)
|
||||
else
|
||||
self.combo = 1
|
||||
end
|
||||
self.drop_bonus = 0
|
||||
self.combo = 1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local cool_cutoffs = {
|
||||
sp(0,36), sp(0,36), sp(0,36), sp(0,36), sp(0,36),
|
||||
sp(0,30), sp(0,30), sp(0,30), sp(0,30), sp(0,30),
|
||||
sp(0,27), sp(0,27), sp(0,27),
|
||||
frameTime(0,36), frameTime(0,36), frameTime(0,36), frameTime(0,36), frameTime(0,36),
|
||||
frameTime(0,30), frameTime(0,30), frameTime(0,30), frameTime(0,30), frameTime(0,30),
|
||||
frameTime(0,27), frameTime(0,27), frameTime(0,27),
|
||||
}
|
||||
|
||||
local regret_cutoffs = {
|
||||
sp(0,50), sp(0,50), sp(0,50), sp(0,50), sp(0,50),
|
||||
sp(0,40), sp(0,40), sp(0,40), sp(0,40), sp(0,40),
|
||||
sp(0,35), sp(0,35), sp(0,35),
|
||||
frameTime(0,50), frameTime(0,50), frameTime(0,50), frameTime(0,50), frameTime(0,50),
|
||||
frameTime(0,40), frameTime(0,40), frameTime(0,40), frameTime(0,40), frameTime(0,40),
|
||||
frameTime(0,35), frameTime(0,35), frameTime(0,35),
|
||||
}
|
||||
|
||||
function PhantomMania2Game:updateSectionTimes(old_level, new_level)
|
||||
@@ -198,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
|
||||
@@ -225,7 +255,7 @@ PhantomMania2Game.garbageOpacityFunction = function(age)
|
||||
end
|
||||
|
||||
function PhantomMania2Game:drawGrid()
|
||||
if not (self.game_over or self.clear) then
|
||||
if not (self.game_over or (self.clear and self.level < 1300)) then
|
||||
self.grid:drawInvisible(self.rollOpacityFunction, self.garbageOpacityFunction)
|
||||
else
|
||||
self.grid:draw()
|
||||
@@ -243,7 +273,7 @@ local function getLetterGrade(grade)
|
||||
end
|
||||
|
||||
function PhantomMania2Game:setNextOpacity(i)
|
||||
if self.level > 1000 then
|
||||
if self.level > 1000 and self.level < 1300 then
|
||||
local hidden_next_pieces = math.floor(self.level / 100) - 10
|
||||
if i < hidden_next_pieces then
|
||||
love.graphics.setColor(1, 1, 1, 0)
|
||||
@@ -258,7 +288,7 @@ function PhantomMania2Game:setNextOpacity(i)
|
||||
end
|
||||
|
||||
function PhantomMania2Game:setHoldOpacity()
|
||||
if self.level > 1000 then
|
||||
if self.level > 1000 and self.level < 1300 then
|
||||
love.graphics.setColor(1, 1, 1, 1 - math.min(1, self.hold_age / 15))
|
||||
else
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
@@ -276,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")
|
||||
@@ -286,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()
|
||||
|
||||
@@ -1,200 +1,22 @@
|
||||
require 'funcs'
|
||||
local PhantomManiaGame = require 'tetris.modes.phantom_mania'
|
||||
|
||||
local GameMode = require 'tetris.modes.gamemode'
|
||||
local Piece = require 'tetris.components.piece'
|
||||
local PhantomManiaNGame = PhantomManiaGame:extend()
|
||||
|
||||
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
|
||||
PhantomManiaNGame.name = "Phantom Mania N"
|
||||
PhantomManiaNGame.hash = "PhantomManiaN"
|
||||
PhantomManiaNGame.tagline = "The old mode from Nullpomino, for Ti-ARS and SRS support."
|
||||
|
||||
local PhantomManiaGame = GameMode:extend()
|
||||
function PhantomManiaNGame:new()
|
||||
PhantomManiaNGame.super:new()
|
||||
|
||||
self.SGnames = {
|
||||
"M1", "M2", "M3", "M4", "M5", "M6", "M7", "M8", "M9",
|
||||
"M10", "M11", "M12", "M13", "M14", "M15", "M16", "M17", "M18",
|
||||
"GM"
|
||||
}
|
||||
|
||||
PhantomManiaGame.name = "Phantom Mania N"
|
||||
PhantomManiaGame.hash = "PhantomManiaN"
|
||||
PhantomManiaGame.tagline = "The old mode from Nullpomino."
|
||||
|
||||
|
||||
|
||||
|
||||
function PhantomManiaGame:new()
|
||||
PhantomManiaGame.super:new()
|
||||
|
||||
self.lock_drop = true
|
||||
self.next_queue_length = 3
|
||||
self.enable_hold = true
|
||||
|
||||
self.roll_frames = 0
|
||||
self.combo = 1
|
||||
self.randomizer = History6RollsRandomizer()
|
||||
end
|
||||
|
||||
function PhantomManiaGame:getARE()
|
||||
if self.level < 100 then return 18
|
||||
elseif self.level < 200 then return 14
|
||||
elseif self.level < 400 then return 8
|
||||
elseif self.level < 500 then return 7
|
||||
else return 6 end
|
||||
end
|
||||
|
||||
function PhantomManiaGame:getLineARE()
|
||||
if self.level < 100 then return 18
|
||||
elseif self.level < 400 then return 8
|
||||
elseif self.level < 500 then return 7
|
||||
else return 6 end
|
||||
end
|
||||
|
||||
function PhantomManiaGame:getDasLimit()
|
||||
if self.level < 200 then return 11
|
||||
elseif self.level < 300 then return 10
|
||||
elseif self.level < 400 then return 9
|
||||
else return 7 end
|
||||
end
|
||||
|
||||
function PhantomManiaGame:getLineClearDelay()
|
||||
return self:getLineARE()
|
||||
end
|
||||
|
||||
function PhantomManiaGame:getLockDelay()
|
||||
if self.level < 100 then return 30
|
||||
elseif self.level < 200 then return 26
|
||||
elseif self.level < 300 then return 22
|
||||
elseif self.level < 400 then return 18
|
||||
else return 15 end
|
||||
end
|
||||
|
||||
function PhantomManiaGame:getGravity()
|
||||
return 20
|
||||
end
|
||||
|
||||
function PhantomManiaGame:hitTorikan(old_level, new_level)
|
||||
if old_level < 300 and new_level >= 300 and self.frames > sp(2,28) then
|
||||
self.level = 300
|
||||
return true
|
||||
end
|
||||
if old_level < 500 and new_level >= 500 and self.frames > sp(3,38) then
|
||||
self.level = 500
|
||||
return true
|
||||
end
|
||||
if old_level < 800 and new_level >= 800 and self.frames > sp(5,23) then
|
||||
self.level = 800
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function PhantomManiaGame:advanceOneFrame()
|
||||
if self.clear then
|
||||
self.roll_frames = self.roll_frames + 1
|
||||
if self.roll_frames < 0 then
|
||||
return false
|
||||
elseif self.roll_frames > 1982 then
|
||||
self.completed = true
|
||||
end
|
||||
elseif self.ready_frames == 0 then
|
||||
self.frames = self.frames + 1
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function PhantomManiaGame:onPieceEnter()
|
||||
if (self.level % 100 ~= 99 and self.level ~= 998) and not self.clear and self.frames ~= 0 then
|
||||
self.level = self.level + 1
|
||||
end
|
||||
end
|
||||
|
||||
function PhantomManiaGame:onLineClear(cleared_row_count)
|
||||
if not self.clear then
|
||||
local new_level = self.level + cleared_row_count
|
||||
if self:hitTorikan(self.level, new_level) then
|
||||
if new_level >= 999 then
|
||||
self.level = 999
|
||||
end
|
||||
self.clear = true
|
||||
else
|
||||
self.level = new_level
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PhantomManiaGame: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
|
||||
|
||||
PhantomManiaGame.rollOpacityFunction = function(age)
|
||||
if age > 4 then return 0
|
||||
else return 1 - age / 4 end
|
||||
end
|
||||
|
||||
function PhantomManiaGame:drawGrid()
|
||||
if not (self.game_over or self.clear) then
|
||||
self.grid:drawInvisible(self.rollOpacityFunction)
|
||||
else
|
||||
self.grid:draw()
|
||||
end
|
||||
end
|
||||
|
||||
local function getLetterGrade(level, clear)
|
||||
if level < 300 or level == 300 and clear then
|
||||
return ""
|
||||
elseif level < 500 or level == 500 and clear then
|
||||
return "M"
|
||||
elseif level < 700 then
|
||||
return "MK"
|
||||
elseif level < 800 or level == 800 and clear then
|
||||
return "MV"
|
||||
elseif level < 900 then
|
||||
return "MO"
|
||||
elseif level < 999 then
|
||||
return "MM"
|
||||
elseif level == 999 then
|
||||
return "GM"
|
||||
end
|
||||
end
|
||||
|
||||
function PhantomManiaGame:drawScoringInfo()
|
||||
PhantomManiaGame.super.drawScoringInfo(self)
|
||||
|
||||
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")
|
||||
love.graphics.printf("SCORE", text_x, 200, 40, "left")
|
||||
love.graphics.printf("LEVEL", text_x, 320, 40, "left")
|
||||
|
||||
love.graphics.setFont(font_3x5_3)
|
||||
love.graphics.printf(getLetterGrade(self.level, self.clear), text_x, 140, 90, "left")
|
||||
love.graphics.printf(self.score, text_x, 220, 90, "left")
|
||||
love.graphics.printf(self.level, text_x, 340, 40, "right")
|
||||
if self.clear then
|
||||
love.graphics.printf(self.level, text_x, 370, 40, "right")
|
||||
else
|
||||
love.graphics.printf(self:getSectionEndLevel(), text_x, 370, 40, "right")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function PhantomManiaGame:getSectionEndLevel()
|
||||
if self.level >= 900 then return 999
|
||||
else return math.floor(self.level / 100 + 1) * 100 end
|
||||
end
|
||||
|
||||
function PhantomManiaGame:getBackground()
|
||||
return math.floor(self.level / 100)
|
||||
end
|
||||
|
||||
function PhantomManiaGame:getHighscoreData()
|
||||
return {
|
||||
level = self.level,
|
||||
frames = self.frames,
|
||||
}
|
||||
end
|
||||
|
||||
return PhantomManiaGame
|
||||
return PhantomManiaNGame
|
||||
|
||||
@@ -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
|
||||
@@ -28,7 +27,7 @@ function StrategyGame:new()
|
||||
end
|
||||
|
||||
function StrategyGame:getARE()
|
||||
if self.level < 100 then return 60
|
||||
if self.level < 100 then return 60
|
||||
elseif self.level < 200 then return 54
|
||||
elseif self.level < 300 then return 48
|
||||
elseif self.level < 400 then return 42
|
||||
@@ -53,7 +52,7 @@ function StrategyGame:getLineClearDelay()
|
||||
end
|
||||
|
||||
function StrategyGame:getLockDelay()
|
||||
if self.level < 500 then return 8
|
||||
if self.level < 500 then return 8
|
||||
elseif self.level < 700 then return 6
|
||||
else return 4 end
|
||||
end
|
||||
@@ -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,16 +98,17 @@ function StrategyGame:onLineClear(cleared_row_count)
|
||||
end
|
||||
|
||||
function StrategyGame: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
|
||||
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 * self.combo
|
||||
)
|
||||
else
|
||||
self.combo = 1
|
||||
end
|
||||
self.drop_bonus = 0
|
||||
self.combo = 1
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -45,7 +46,7 @@ function Survival2020Game:getLineARE()
|
||||
end
|
||||
|
||||
function Survival2020Game:getDasLimit()
|
||||
if self.level < 200 then return 9
|
||||
if self.level < 200 then return 9
|
||||
elseif self.level < 500 then return 7
|
||||
elseif self.level < 1000 then return 5
|
||||
elseif self.level < 1500 then return 4
|
||||
@@ -102,15 +103,15 @@ function Survival2020Game:getNextPiece(ruleset)
|
||||
end
|
||||
|
||||
function Survival2020Game:hitTorikan(old_level, new_level)
|
||||
if old_level < 500 and new_level >= 500 and self.frames > sp(3,00) then
|
||||
if old_level < 500 and new_level >= 500 and self.frames > frameTime(3,00) then
|
||||
self.level = 500
|
||||
return true
|
||||
end
|
||||
if old_level < 1000 and new_level >= 1000 and self.frames > sp(5,00) then
|
||||
if old_level < 1000 and new_level >= 1000 and self.frames > frameTime(5,00) then
|
||||
self.level = 1000
|
||||
return true
|
||||
end
|
||||
if old_level < 1500 and new_level >= 1500 and self.frames > sp(7,00) then
|
||||
if old_level < 1500 and new_level >= 1500 and self.frames > frameTime(7,00) then
|
||||
self.level = 1500
|
||||
return true
|
||||
end
|
||||
@@ -174,16 +175,17 @@ function Survival2020Game:onLineClear(cleared_row_count)
|
||||
end
|
||||
|
||||
function Survival2020Game: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
|
||||
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 * self.combo
|
||||
)
|
||||
else
|
||||
self.combo = 1
|
||||
end
|
||||
self.drop_bonus = 0
|
||||
self.combo = 1
|
||||
end
|
||||
end
|
||||
|
||||
@@ -193,7 +195,7 @@ function Survival2020Game:updateSectionTimes(old_level, new_level)
|
||||
section_time = self.frames - self.section_start_time
|
||||
table.insert(self.section_times, section_time)
|
||||
self.section_start_time = self.frames
|
||||
if section_time <= sp(0,30) then
|
||||
if section_time <= frameTime(0,30) then
|
||||
self.grade = self.grade + 2
|
||||
else
|
||||
self.grade = self.grade + 1
|
||||
|
||||
@@ -15,20 +15,28 @@ SurvivalA1Game.tagline = "The game starts fast and only gets faster!"
|
||||
|
||||
|
||||
function SurvivalA1Game:new()
|
||||
SurvivalA1Game.super:new()
|
||||
|
||||
SurvivalA1Game.super:new()
|
||||
|
||||
self.roll_frames = 0
|
||||
self.combo = 1
|
||||
self.combo = 1
|
||||
self.bravos = 0
|
||||
|
||||
self.gm_conditions = {
|
||||
level300 = false,
|
||||
level500 = false,
|
||||
level999 = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
self.SGnames = {
|
||||
"9", "8", "7", "6", "5", "4", "3", "2", "1",
|
||||
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
|
||||
"GM"
|
||||
}
|
||||
|
||||
self.randomizer = History4RollsRandomizer()
|
||||
|
||||
self.lock_drop = false
|
||||
self.enable_hard_drop = false
|
||||
self.lock_drop = false
|
||||
self.enable_hard_drop = false
|
||||
self.enable_hold = false
|
||||
self.next_queue_length = 1
|
||||
end
|
||||
@@ -54,29 +62,29 @@ function SurvivalA1Game:getLockDelay()
|
||||
end
|
||||
|
||||
function SurvivalA1Game:getGravity()
|
||||
return 20
|
||||
return 20
|
||||
end
|
||||
|
||||
local function getRankForScore(score)
|
||||
if score < 400 then return {rank = "9", next = 400}
|
||||
elseif score < 800 then return {rank = "8", next = 800}
|
||||
elseif score < 1400 then return {rank = "7", next = 1400}
|
||||
elseif score < 2000 then return {rank = "6", next = 2000}
|
||||
elseif score < 3500 then return {rank = "5", next = 3500}
|
||||
elseif score < 5500 then return {rank = "4", next = 5500}
|
||||
elseif score < 8000 then return {rank = "3", next = 8000}
|
||||
elseif score < 12000 then return {rank = "2", next = 12000}
|
||||
elseif score < 16000 then return {rank = "1", next = 16000}
|
||||
elseif score < 22000 then return {rank = "S1", next = 22000}
|
||||
elseif score < 30000 then return {rank = "S2", next = 30000}
|
||||
elseif score < 40000 then return {rank = "S3", next = 40000}
|
||||
elseif score < 52000 then return {rank = "S4", next = 52000}
|
||||
elseif score < 66000 then return {rank = "S5", next = 66000}
|
||||
elseif score < 82000 then return {rank = "S6", next = 82000}
|
||||
elseif score < 100000 then return {rank = "S7", next = 100000}
|
||||
elseif score < 120000 then return {rank = "S8", next = 120000}
|
||||
else return {rank = "S9", next = "???"}
|
||||
end
|
||||
if score < 400 then return {rank = "9", next = 400}
|
||||
elseif score < 800 then return {rank = "8", next = 800}
|
||||
elseif score < 1400 then return {rank = "7", next = 1400}
|
||||
elseif score < 2000 then return {rank = "6", next = 2000}
|
||||
elseif score < 3500 then return {rank = "5", next = 3500}
|
||||
elseif score < 5500 then return {rank = "4", next = 5500}
|
||||
elseif score < 8000 then return {rank = "3", next = 8000}
|
||||
elseif score < 12000 then return {rank = "2", next = 12000}
|
||||
elseif score < 16000 then return {rank = "1", next = 16000}
|
||||
elseif score < 22000 then return {rank = "S1", next = 22000}
|
||||
elseif score < 30000 then return {rank = "S2", next = 30000}
|
||||
elseif score < 40000 then return {rank = "S3", next = 40000}
|
||||
elseif score < 52000 then return {rank = "S4", next = 52000}
|
||||
elseif score < 66000 then return {rank = "S5", next = 66000}
|
||||
elseif score < 82000 then return {rank = "S6", next = 82000}
|
||||
elseif score < 100000 then return {rank = "S7", next = 100000}
|
||||
elseif score < 120000 then return {rank = "S8", next = 120000}
|
||||
else return {rank = "S9", next = "???"}
|
||||
end
|
||||
end
|
||||
|
||||
function SurvivalA1Game:advanceOneFrame()
|
||||
@@ -98,43 +106,48 @@ function SurvivalA1Game:onPieceEnter()
|
||||
end
|
||||
|
||||
function SurvivalA1Game:onLineClear(cleared_row_count)
|
||||
self:checkGMRequirements(self.level, self.level + cleared_row_count)
|
||||
self:checkGMRequirements(self.level, self.level + cleared_row_count)
|
||||
if not self.clear then
|
||||
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
|
||||
self.level = new_level
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SurvivalA1Game: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
|
||||
)
|
||||
self.lines = self.lines + cleared_lines
|
||||
self.combo = self.combo + (cleared_lines - 1) * 2
|
||||
else
|
||||
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 * self.bravo
|
||||
)
|
||||
else
|
||||
self.combo = 1
|
||||
end
|
||||
self.drop_bonus = 0
|
||||
self.combo = 1
|
||||
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 <= sp(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 <= sp(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 <= sp(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
|
||||
@@ -151,17 +164,23 @@ function SurvivalA1Game:drawScoringInfo()
|
||||
love.graphics.print(
|
||||
self.das.direction .. " " ..
|
||||
self.das.frames .. " " ..
|
||||
st(self.prev_inputs)
|
||||
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("NEXT RANK", 240, 260, 90, "left")
|
||||
love.graphics.printf("LEVEL", 240, 320, 40, "left")
|
||||
local sg = self.grid:checkSecretGrade()
|
||||
if sg >= 5 then
|
||||
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
|
||||
end
|
||||
|
||||
if self.bravos > 0 then love.graphics.printf("BRAVO", 300, 120, 40, "left") end
|
||||
|
||||
love.graphics.setFont(font_3x5_3)
|
||||
love.graphics.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")
|
||||
@@ -169,6 +188,10 @@ function SurvivalA1Game:drawScoringInfo()
|
||||
love.graphics.printf(getRankForScore(self.score).next, 240, 280, 90, "left")
|
||||
love.graphics.printf(self.level, 240, 340, 40, "right")
|
||||
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")
|
||||
if sg >= 5 then
|
||||
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
|
||||
end
|
||||
if self.bravos > 0 then love.graphics.printf(self.bravos, 300, 140, 40, "left") end
|
||||
|
||||
love.graphics.setFont(font_8x11)
|
||||
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")
|
||||
|
||||
@@ -19,8 +19,15 @@ function SurvivalA2Game:new()
|
||||
self.roll_frames = 0
|
||||
self.combo = 1
|
||||
self.randomizer = History6RollsRandomizer()
|
||||
|
||||
self.SGnames = {
|
||||
"9", "8", "7", "6", "5", "4", "3", "2", "1",
|
||||
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
|
||||
"GM"
|
||||
}
|
||||
|
||||
self.lock_drop = true
|
||||
self.lock_hard_drop = true
|
||||
end
|
||||
|
||||
function SurvivalA2Game:getARE()
|
||||
@@ -62,7 +69,7 @@ function SurvivalA2Game:getGravity()
|
||||
end
|
||||
|
||||
function SurvivalA2Game:hitTorikan(old_level, new_level)
|
||||
if old_level < 500 and new_level >= 500 and self.frames > sp(3,25) then
|
||||
if old_level < 500 and new_level >= 500 and self.frames > frameTime(3,25) then
|
||||
self.level = 500
|
||||
return true
|
||||
end
|
||||
@@ -82,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
|
||||
@@ -92,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
|
||||
@@ -99,22 +109,25 @@ function SurvivalA2Game:onLineClear(cleared_row_count)
|
||||
end
|
||||
|
||||
function SurvivalA2Game: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
|
||||
)
|
||||
self.lines = self.lines + cleared_lines
|
||||
self.combo = self.combo + (cleared_lines - 1) * 2
|
||||
else
|
||||
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 * self.combo * self.bravo
|
||||
)
|
||||
else
|
||||
self.combo = 1
|
||||
end
|
||||
self.drop_bonus = 0
|
||||
self.combo = 1
|
||||
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
|
||||
|
||||
@@ -132,18 +145,27 @@ function SurvivalA2Game:drawScoringInfo()
|
||||
love.graphics.print(
|
||||
self.das.direction .. " " ..
|
||||
self.das.frames .. " " ..
|
||||
st(self.prev_inputs)
|
||||
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()
|
||||
if sg >= 5 then
|
||||
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
|
||||
end
|
||||
|
||||
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
|
||||
love.graphics.printf(self.SGnames[sg], 240, 450, 180, "left")
|
||||
end
|
||||
end
|
||||
|
||||
function SurvivalA2Game:getSectionEndLevel()
|
||||
|
||||
@@ -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
|
||||
@@ -24,19 +21,37 @@ function SurvivalA3Game:new()
|
||||
self.roll_frames = 0
|
||||
self.combo = 1
|
||||
self.randomizer = History6RollsRandomizer()
|
||||
|
||||
|
||||
self.SGnames = {
|
||||
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
|
||||
"m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m9",
|
||||
"GM"
|
||||
}
|
||||
|
||||
self.lock_drop = true
|
||||
self.lock_hard_drop = true
|
||||
self.enable_hold = true
|
||||
self.next_queue_length = 3
|
||||
|
||||
self.coolregret_message = "COOL!!"
|
||||
self.coolregret_timer = 0
|
||||
end
|
||||
|
||||
function SurvivalA3Game:initialize(ruleset)
|
||||
|
||||
self.torikan_time = frameTime(2,28)
|
||||
if ruleset.world then self.torikan_time = frameTime(3,03) end
|
||||
self.super.initialize(self, ruleset)
|
||||
-- ^ notice the . here instead of the :
|
||||
end
|
||||
|
||||
function SurvivalA3Game:getARE()
|
||||
if self.level < 300 then return 12
|
||||
if self.level < 300 then return 12
|
||||
else return 6 end
|
||||
end
|
||||
|
||||
function SurvivalA3Game:getLineARE()
|
||||
if self.level < 100 then return 8
|
||||
if self.level < 100 then return 8
|
||||
elseif self.level < 200 then return 7
|
||||
elseif self.level < 500 then return 6
|
||||
elseif self.level < 1300 then return 5
|
||||
@@ -44,23 +59,25 @@ function SurvivalA3Game:getLineARE()
|
||||
end
|
||||
|
||||
function SurvivalA3Game:getDasLimit()
|
||||
if self.level < 200 then return 9
|
||||
if self.level < 100 then return 9
|
||||
elseif self.level < 500 then return 7
|
||||
else return 5 end
|
||||
end
|
||||
|
||||
function SurvivalA3Game:getLineClearDelay()
|
||||
return self:getLineARE() - 2
|
||||
if self.level < 1300 then return self:getLineARE() - 2
|
||||
else return 6 end
|
||||
end
|
||||
|
||||
function SurvivalA3Game:getLockDelay()
|
||||
if self.level < 200 then return 18
|
||||
if self.level < 200 then return 18
|
||||
elseif self.level < 300 then return 17
|
||||
elseif self.level < 500 then return 15
|
||||
elseif self.level < 600 then return 13
|
||||
elseif self.level < 1100 then return 12
|
||||
elseif self.level < 1200 then return 10
|
||||
else return 8 end
|
||||
elseif self.level < 1300 then return 8
|
||||
else return 15 end
|
||||
end
|
||||
|
||||
function SurvivalA3Game:getGravity()
|
||||
@@ -84,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 > sp(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 > sp(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
|
||||
@@ -121,19 +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
|
||||
self.grid:clear()
|
||||
self.big_mode = true
|
||||
self.roll_frames = -150
|
||||
else
|
||||
self.game_over = true
|
||||
end
|
||||
self.clear = true
|
||||
self.grid:clear()
|
||||
self.roll_frames = -150
|
||||
else
|
||||
self.level = math.min(new_level, 1300)
|
||||
end
|
||||
@@ -141,21 +160,23 @@ function SurvivalA3Game:onLineClear(cleared_row_count)
|
||||
end
|
||||
end
|
||||
|
||||
function SurvivalA3Game:onPieceLock()
|
||||
self:advanceBottomRow(1)
|
||||
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 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
|
||||
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 * self.combo
|
||||
)
|
||||
else
|
||||
self.combo = 1
|
||||
end
|
||||
self.drop_bonus = 0
|
||||
self.combo = 1
|
||||
end
|
||||
end
|
||||
|
||||
@@ -165,8 +186,11 @@ function SurvivalA3Game:updateSectionTimes(old_level, new_level)
|
||||
section_time = self.frames - self.section_start_time
|
||||
table.insert(self.section_times, section_time)
|
||||
self.section_start_time = self.frames
|
||||
if section_time <= sp(1,00) then
|
||||
if section_time <= frameTime(1,00) then
|
||||
self.grade = self.grade + 1
|
||||
else
|
||||
self.coolregret_message = "REGRET!!"
|
||||
self.coolregret_timer = 300
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -188,10 +212,8 @@ end
|
||||
local function getLetterGrade(grade)
|
||||
if grade == 0 then
|
||||
return "1"
|
||||
elseif grade <= 9 then
|
||||
return "S" .. tostring(grade)
|
||||
else
|
||||
return "M" .. tostring(grade - 9)
|
||||
return "S" .. tostring(grade)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -206,12 +228,24 @@ function SurvivalA3Game: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
|
||||
|
||||
if(self.coolregret_timer > 0) then
|
||||
love.graphics.printf(self.coolregret_message, 64, 400, 160, "center")
|
||||
self.coolregret_timer = self.coolregret_timer - 1
|
||||
end
|
||||
|
||||
local current_section = math.floor(self.level / 100) + 1
|
||||
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
|
||||
@@ -219,6 +253,9 @@ function SurvivalA3Game: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 SurvivalA3Game:getBackground()
|
||||
|
||||
9
tetris/randomizers/always.lua
Normal file
9
tetris/randomizers/always.lua
Normal file
@@ -0,0 +1,9 @@
|
||||
local Randomizer = require 'tetris.randomizers.randomizer'
|
||||
|
||||
local AlwaysRandomizer = Randomizer:extend()
|
||||
|
||||
function AlwaysRandomizer:generatePiece()
|
||||
return "I"
|
||||
end
|
||||
|
||||
return AlwaysRandomizer
|
||||
@@ -1,9 +0,0 @@
|
||||
local Randomizer = require 'tetris.randomizers.randomizer'
|
||||
|
||||
local AlwaysORandomizer = Randomizer:extend()
|
||||
|
||||
function AlwaysORandomizer:generatePiece()
|
||||
return "O"
|
||||
end
|
||||
|
||||
return AlwaysORandomizer
|
||||
31
tetris/randomizers/bag7noSZOstart.lua
Normal file
31
tetris/randomizers/bag7noSZOstart.lua
Normal file
@@ -0,0 +1,31 @@
|
||||
local Randomizer = require 'tetris.randomizers.randomizer'
|
||||
|
||||
local Bag7NoSZOStartRandomizer = Randomizer:extend()
|
||||
|
||||
function Bag7NoSZOStartRandomizer:shuffleBag()
|
||||
local b = self.bag
|
||||
local ln = #b
|
||||
for i = 1, ln do
|
||||
local j = math.random(i, ln)
|
||||
b[i], b[j] = b[j], b[i]
|
||||
end
|
||||
end
|
||||
|
||||
local function isnotSZO(x) return not(x == "S" or x == "Z" or x == "O") end
|
||||
|
||||
function Bag7NoSZOStartRandomizer:initialize()
|
||||
self.bag = {"I", "J", "L", "O", "S", "T", "Z"}
|
||||
repeat
|
||||
self:shuffleBag()
|
||||
until isnotSZO(self.bag[7])
|
||||
end
|
||||
|
||||
function Bag7NoSZOStartRandomizer:generatePiece()
|
||||
if #self.bag == 0 then
|
||||
self.bag = {"I", "J", "L", "O", "S", "T", "Z"}
|
||||
self:shuffleBag()
|
||||
end
|
||||
return table.remove(self.bag)
|
||||
end
|
||||
|
||||
return Bag7NoSZOStartRandomizer
|
||||
@@ -3,15 +3,21 @@ local Randomizer = require 'tetris.randomizers.randomizer'
|
||||
local History4RollsRandomizer = Randomizer:extend()
|
||||
|
||||
function History4RollsRandomizer:initialize()
|
||||
self.history = {"Z", "S", "Z", "S"}
|
||||
self.history = {"Z", "Z", "Z", "Z"}
|
||||
self.first = true
|
||||
end
|
||||
|
||||
function History4RollsRandomizer:generatePiece()
|
||||
local shapes = {"I", "J", "L", "O", "S", "T", "Z"}
|
||||
for i = 1, 4 do
|
||||
local x = math.random(7)
|
||||
if not inHistory(shapes[x], self.history) or i == 4 then
|
||||
return self:updateHistory(shapes[x])
|
||||
if self.first then
|
||||
self.first = false
|
||||
return self:updateHistory(({"L", "J", "I", "T"})[math.random(4)])
|
||||
else
|
||||
local shapes = {"I", "J", "L", "O", "S", "T", "Z"}
|
||||
for i = 1, 4 do
|
||||
local x = math.random(7)
|
||||
if not inHistory(shapes[x], self.history) or i == 4 then
|
||||
return self:updateHistory(shapes[x])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,14 +4,20 @@ local History6RollsRandomizer = Randomizer:extend()
|
||||
|
||||
function History6RollsRandomizer:initialize()
|
||||
self.history = {"Z", "S", "Z", "S"}
|
||||
self.first = true
|
||||
end
|
||||
|
||||
function History6RollsRandomizer:generatePiece()
|
||||
local shapes = {"I", "J", "L", "O", "S", "T", "Z"}
|
||||
for i = 1, 6 do
|
||||
local x = math.random(7)
|
||||
if not inHistory(shapes[x], self.history) or i == 6 then
|
||||
return self:updateHistory(shapes[x])
|
||||
if self.first then
|
||||
self.first = false
|
||||
return self:updateHistory(({"L", "J", "I", "T"})[math.random(4)])
|
||||
else
|
||||
local shapes = {"I", "J", "L", "O", "S", "T", "Z"}
|
||||
for i = 1, 6 do
|
||||
local x = math.random(7)
|
||||
if not inHistory(shapes[x], self.history) or i == 6 then
|
||||
return self:updateHistory(shapes[x])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,38 +1,70 @@
|
||||
local Randomizer = require 'tetris.randomizers.randomizer'
|
||||
|
||||
local History6RollsRandomizer = Randomizer:extend()
|
||||
local History6Rolls35PoolRandomizer = Randomizer:extend()
|
||||
|
||||
function History6RollsRandomizer:initialize()
|
||||
function History6Rolls35PoolRandomizer:initialize()
|
||||
self.first = true
|
||||
self.history = {"Z", "S", "Z", "S"}
|
||||
self.bag_counts = {
|
||||
I = 5, J = 5, L = 5, O = 5, S = 3, T = 5, Z = 3
|
||||
self.pool = {
|
||||
"I", "I", "I", "I", "I",
|
||||
"T", "T", "T", "T", "T",
|
||||
"L", "L", "L", "L", "L",
|
||||
"J", "J", "J", "J", "J",
|
||||
"S", "S", "S", "S", "S",
|
||||
"Z", "Z", "Z", "Z", "Z",
|
||||
"O", "O", "O", "O", "O",
|
||||
}
|
||||
self.droughts = {
|
||||
I = 0,
|
||||
T = 0,
|
||||
L = 0,
|
||||
J = 0,
|
||||
S = 0,
|
||||
Z = 0,
|
||||
O = 0,
|
||||
}
|
||||
end
|
||||
|
||||
function History6RollsRandomizer:getBagPiece(n)
|
||||
for shape, count in pairs(self.bag_counts) do
|
||||
n = n - count
|
||||
if n <= 0 then
|
||||
return shape
|
||||
function History6Rolls35PoolRandomizer:generatePiece()
|
||||
local index, x
|
||||
if self.first then
|
||||
local prevent = {"S", "Z", "O"}
|
||||
repeat
|
||||
index = math.random(#self.pool)
|
||||
x = self.pool[index]
|
||||
until not inHistory(x, prevent)
|
||||
self.first = false
|
||||
else
|
||||
for i = 1, 6 do
|
||||
index = math.random(#self.pool)
|
||||
x = self.pool[index]
|
||||
if not inHistory(x, self.history) or i == 6 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
self.pool[index] = self:updateHistory(x)
|
||||
return x
|
||||
end
|
||||
|
||||
function History6RollsRandomizer:generatePiece()
|
||||
for i = 1, 6 do
|
||||
local x = self:getBagPiece(math.random(31))
|
||||
if not inHistory(x, self.history) or i == 6 then
|
||||
return self:updateHistory(x)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function History6RollsRandomizer:updateHistory(shape)
|
||||
self.bag_counts[shape] = self.bag_counts[shape] - 1
|
||||
local replaced_piece = table.remove(self.history, 1)
|
||||
function History6Rolls35PoolRandomizer:updateHistory(shape)
|
||||
table.remove(self.history, 1)
|
||||
table.insert(self.history, shape)
|
||||
self.bag_counts[replaced_piece] = self.bag_counts[replaced_piece] + 1
|
||||
return shape
|
||||
|
||||
local highdrought
|
||||
local highdroughtcount = 0
|
||||
for k, v in pairs(self.droughts) do
|
||||
if k == shape then
|
||||
self.droughts[k] = 0
|
||||
else
|
||||
self.droughts[k] = v + 1
|
||||
if v >= highdroughtcount then
|
||||
highdrought = k
|
||||
highdroughtcount = v
|
||||
end
|
||||
end
|
||||
end
|
||||
return highdrought
|
||||
end
|
||||
|
||||
function inHistory(piece, history)
|
||||
@@ -44,4 +76,4 @@ function inHistory(piece, history)
|
||||
return false
|
||||
end
|
||||
|
||||
return History6RollsRandomizer
|
||||
return History6Rolls35PoolRandomizer
|
||||
|
||||
@@ -4,15 +4,10 @@ local Randomizer = Object:extend()
|
||||
|
||||
function Randomizer:new()
|
||||
self:initialize()
|
||||
self.next_queue = {}
|
||||
for i = 1, 30 do
|
||||
table.insert(self.next_queue, self:generatePiece())
|
||||
end
|
||||
end
|
||||
|
||||
function Randomizer:nextPiece()
|
||||
table.insert(self.next_queue, self:generatePiece())
|
||||
return table.remove(self.next_queue, 1)
|
||||
return self:generatePiece()
|
||||
end
|
||||
|
||||
function Randomizer:initialize()
|
||||
|
||||
@@ -16,6 +16,16 @@ ARS.spawn_positions = {
|
||||
Z = { x=4, y=5 },
|
||||
}
|
||||
|
||||
ARS.big_spawn_positions = {
|
||||
I = { x=3, y=2 },
|
||||
J = { x=2, y=3 },
|
||||
L = { x=2, y=3 },
|
||||
O = { x=3, y=3 },
|
||||
S = { x=2, y=3 },
|
||||
T = { x=2, y=3 },
|
||||
Z = { x=2, y=3 },
|
||||
}
|
||||
|
||||
ARS.block_offsets = {
|
||||
I={
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} },
|
||||
@@ -71,11 +81,19 @@ function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
||||
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
|
||||
) 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
|
||||
|
||||
-- kick right, kick left
|
||||
if (grid:canPlacePiece(new_piece:withOffset({x=1, y=0}))) then
|
||||
@@ -92,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 1
|
||||
else
|
||||
return 3
|
||||
end
|
||||
end
|
||||
|
||||
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
|
||||
|
||||
return ARS
|
||||
|
||||
198
tetris/rulesets/arika_ace.lua
Executable file
198
tetris/rulesets/arika_ace.lua
Executable file
@@ -0,0 +1,198 @@
|
||||
local Piece = require 'tetris.components.piece'
|
||||
local Ruleset = require 'tetris.rulesets.ruleset'
|
||||
|
||||
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 },
|
||||
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 1
|
||||
else
|
||||
return 3
|
||||
end
|
||||
end
|
||||
|
||||
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
|
||||
|
||||
return ARS
|
||||
185
tetris/rulesets/arika_ace2.lua
Normal file
185
tetris/rulesets/arika_ace2.lua
Normal 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 1
|
||||
else
|
||||
return 3
|
||||
end
|
||||
end
|
||||
|
||||
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
|
||||
|
||||
return ARS
|
||||
195
tetris/rulesets/arika_srs.lua
Executable file
195
tetris/rulesets/arika_srs.lua
Executable file
@@ -0,0 +1,195 @@
|
||||
local Piece = require 'tetris.components.piece'
|
||||
local Ruleset = require 'tetris.rulesets.ruleset'
|
||||
|
||||
local SRS = Ruleset:extend()
|
||||
|
||||
SRS.name = "ACE-SRS"
|
||||
SRS.hash = "ACE Standard"
|
||||
SRS.world = true
|
||||
SRS.colourscheme = {
|
||||
I = "C",
|
||||
L = "O",
|
||||
J = "B",
|
||||
S = "G",
|
||||
Z = "R",
|
||||
O = "Y",
|
||||
T = "M",
|
||||
}
|
||||
SRS.softdrop_lock = false
|
||||
SRS.harddrop_lock = true
|
||||
|
||||
SRS.spawn_positions = {
|
||||
I = { x=5, y=2 },
|
||||
J = { x=4, y=3 },
|
||||
L = { x=4, y=3 },
|
||||
O = { x=5, y=3 },
|
||||
S = { x=4, y=3 },
|
||||
T = { x=4, y=3 },
|
||||
Z = { x=4, y=3 },
|
||||
}
|
||||
|
||||
SRS.big_spawn_positions = {
|
||||
I = { x=3, y=0 },
|
||||
J = { x=2, y=1 },
|
||||
L = { x=2, y=1 },
|
||||
O = { x=3, y=1 },
|
||||
S = { x=2, y=1 },
|
||||
T = { x=2, y=1 },
|
||||
Z = { x=2, y=1 },
|
||||
}
|
||||
|
||||
SRS.block_offsets = {
|
||||
I={
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} },
|
||||
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=0, y=2} },
|
||||
{ {x=0, y=1}, {x=-1, y=1}, {x=-2, y=1}, {x=1, y=1} },
|
||||
{ {x=-1, y=0}, {x=-1, y=-1}, {x=-1, y=1}, {x=-1, y=2} },
|
||||
},
|
||||
J={
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=-1, y=-1} },
|
||||
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1} , {x=1, y=-1} },
|
||||
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=1, y=1} },
|
||||
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=1} },
|
||||
},
|
||||
L={
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=1, y=-1} },
|
||||
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=1} },
|
||||
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=-1, y=1} },
|
||||
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=-1} },
|
||||
},
|
||||
O={
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
|
||||
},
|
||||
S={
|
||||
{ {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0} },
|
||||
{ {x=1, y=1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=-1} },
|
||||
{ {x=-1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=1, y=0} },
|
||||
{ {x=-1, y=-1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=1} },
|
||||
},
|
||||
T={
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} },
|
||||
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=0} },
|
||||
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=0, y=1} },
|
||||
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=0} },
|
||||
},
|
||||
Z={
|
||||
{ {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=1, y=0} },
|
||||
{ {x=1, y=-1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=1} },
|
||||
{ {x=1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=-1, y=0} },
|
||||
{ {x=-1, y=1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=-1} },
|
||||
}
|
||||
}
|
||||
|
||||
SRS.wallkicks_3x3 = {
|
||||
[0]={
|
||||
[1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}},
|
||||
[2]={{x=0, y=1}, {x=0, y=-1}},
|
||||
[3]={{x=1, y=0}, {x=1, y=-1}, {x=0, y=2}, {x=1, y=2}},
|
||||
},
|
||||
[1]={
|
||||
[0]={{x=1, y=0}, {x=1, y=1}, {x=0, y=-2}, {x=1, y=-2}},
|
||||
[2]={{x=1, y=0}, {x=1, y=1}, {x=0, y=-2}, {x=1, y=-2}},
|
||||
[3]={{x=0, y=1}, {x=0, y=-1}},
|
||||
},
|
||||
[2]={
|
||||
[0]={{x=0, y=1}, {x=0, y=-1}},
|
||||
[1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}},
|
||||
[3]={{x=1, y=0}, {x=1, y=-1}, {x=0, y=2}, {x=1, y=2}},
|
||||
},
|
||||
[3]={
|
||||
[0]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
|
||||
[1]={{x=0, y=1}, {x=0, y=-1}},
|
||||
[2]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
|
||||
},
|
||||
};
|
||||
|
||||
SRS.wallkicks_line = {
|
||||
[0]={
|
||||
[1]={{x=-2, y= 0}, {x= 1, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
|
||||
[2]={},
|
||||
[3]={{x= 2, y= 0}, {x=-1, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
|
||||
},
|
||||
[1]={
|
||||
[0]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 2}},
|
||||
[2]={{x=-1, y= 0}, {x= 2, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
|
||||
[3]={},
|
||||
},
|
||||
[2]={
|
||||
[0]={},
|
||||
[1]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 1}},
|
||||
[3]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 1}},
|
||||
},
|
||||
[3]={
|
||||
[0]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 2}},
|
||||
[1]={},
|
||||
[2]={{x= 1, y= 0}, {x=-2, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
|
||||
},
|
||||
};
|
||||
|
||||
-- Component functions.
|
||||
|
||||
function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
||||
|
||||
local kicks
|
||||
if piece.shape == "O" then
|
||||
return
|
||||
elseif piece.shape == "I" then
|
||||
kicks = SRS.wallkicks_line[piece.rotation][new_piece.rotation]
|
||||
else
|
||||
kicks = SRS.wallkicks_3x3[piece.rotation][new_piece.rotation]
|
||||
end
|
||||
|
||||
assert(piece.rotation ~= new_piece.rotation)
|
||||
|
||||
for idx, offset in pairs(kicks) do
|
||||
kicked_piece = new_piece:withOffset(offset)
|
||||
if grid:canPlacePiece(kicked_piece) then
|
||||
piece:setRelativeRotation(rot_dir)
|
||||
piece:setOffset(offset)
|
||||
self:onPieceRotate(piece, grid)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function SRS:onPieceCreate(piece, grid)
|
||||
piece.manipulations = 0
|
||||
end
|
||||
|
||||
function SRS:onPieceDrop(piece, grid)
|
||||
piece.lock_delay = 0 -- step reset
|
||||
end
|
||||
|
||||
function SRS:onPieceMove(piece, grid)
|
||||
piece.lock_delay = 0 -- move reset
|
||||
if piece:isDropBlocked(grid) then
|
||||
piece.manipulations = piece.manipulations + 1
|
||||
if piece.manipulations >= 127 then
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SRS:onPieceRotate(piece, grid)
|
||||
piece.lock_delay = 0 -- rotate reset
|
||||
if piece:isDropBlocked(grid) then
|
||||
piece.manipulations = piece.manipulations + 1
|
||||
if piece.manipulations >= 127 then
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SRS:get180RotationValue()
|
||||
if config.gamesettings.world_reverse == 1 then
|
||||
return 1
|
||||
else
|
||||
return 3
|
||||
end
|
||||
end
|
||||
|
||||
return SRS
|
||||
@@ -16,6 +16,16 @@ ARS.spawn_positions = {
|
||||
Z = { x=4, y=5 },
|
||||
}
|
||||
|
||||
ARS.big_spawn_positions = {
|
||||
I = { x=3, y=2 },
|
||||
J = { x=2, y=3 },
|
||||
L = { x=2, y=3 },
|
||||
O = { x=3, y=3 },
|
||||
S = { x=2, y=3 },
|
||||
T = { x=2, y=3 },
|
||||
Z = { x=2, y=3 },
|
||||
}
|
||||
|
||||
ARS.block_offsets = {
|
||||
I={
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} },
|
||||
@@ -74,11 +84,19 @@ function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
||||
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
|
||||
) 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
|
||||
@@ -106,14 +124,21 @@ function ARS:attemptWallkicks(piece, new_piece, rot_dir, 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
|
||||
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
|
||||
@@ -126,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 1
|
||||
else
|
||||
return 3
|
||||
end
|
||||
end
|
||||
|
||||
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
|
||||
|
||||
return ARS
|
||||
|
||||
@@ -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
|
||||
@@ -5,15 +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=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 },
|
||||
}
|
||||
|
||||
CRS.block_offsets = {
|
||||
@@ -42,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} },
|
||||
@@ -54,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} },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,6 +363,10 @@ function CRS:attemptRotate(new_inputs, piece, grid, initial)
|
||||
end
|
||||
|
||||
if rot_dir == 0 then return end
|
||||
|
||||
if self.world and config.gamesettings.world_reverse == 2 then
|
||||
rot_dir = 4 - rot_dir
|
||||
end
|
||||
|
||||
local new_piece = piece:withRelativeRotation(rot_dir)
|
||||
self:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
||||
|
||||
@@ -6,10 +6,79 @@ 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
|
||||
Ruleset.are_cancel = 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 +108,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)
|
||||
|
||||
@@ -72,10 +144,17 @@ function Ruleset:movePiece(piece, grid, move)
|
||||
end
|
||||
end
|
||||
|
||||
function Ruleset:dropPiece(inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked, hard_drop_enabled)
|
||||
function Ruleset:dropPiece(
|
||||
inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
|
||||
hard_drop_enabled, additive_gravity
|
||||
)
|
||||
local y = piece.position.y
|
||||
if inputs["down"] == true and drop_locked == false then
|
||||
piece:addGravity(gravity + drop_speed, grid)
|
||||
if additive_gravity then
|
||||
piece:addGravity(gravity + drop_speed, grid)
|
||||
else
|
||||
piece:addGravity(math.max(gravity, drop_speed), grid)
|
||||
end
|
||||
elseif inputs["up"] == true and hard_drop_enabled == true then
|
||||
if hard_drop_locked == true or piece:isDropBlocked(grid) then
|
||||
piece:addGravity(gravity, grid)
|
||||
@@ -102,14 +181,23 @@ function Ruleset:getDefaultOrientation() return 1 end
|
||||
function Ruleset:initializePiece(
|
||||
inputs, data, grid, gravity, prev_inputs,
|
||||
move, lock_delay, drop_speed,
|
||||
drop_locked, hard_drop_locked
|
||||
drop_locked, hard_drop_locked, big, irs
|
||||
)
|
||||
local spawn_positions
|
||||
if big then
|
||||
spawn_positions = self.big_spawn_positions
|
||||
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 = self.spawn_positions[data.shape].x,
|
||||
y = self.spawn_positions[data.shape].y
|
||||
}, self.block_offsets, 0, 0, data.skin)
|
||||
x = spawn_positions[data.shape].x,
|
||||
y = spawn_positions[data.shape].y
|
||||
}, self.block_offsets, 0, 0, data.skin, colours[data.shape], big)
|
||||
|
||||
self:onPieceCreate(piece)
|
||||
self:rotatePiece(inputs, piece, grid, {}, true)
|
||||
if irs then self:rotatePiece(inputs, piece, grid, {}, true) end
|
||||
self:dropPiece(inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked)
|
||||
return piece
|
||||
end
|
||||
@@ -120,11 +208,15 @@ function Ruleset:onPieceCreate(piece) end
|
||||
function Ruleset:processPiece(
|
||||
inputs, piece, grid, gravity, prev_inputs,
|
||||
move, lock_delay, drop_speed,
|
||||
drop_locked, hard_drop_locked, hard_drop_enabled
|
||||
drop_locked, hard_drop_locked,
|
||||
hard_drop_enabled, additive_gravity
|
||||
)
|
||||
self:rotatePiece(inputs, piece, grid, prev_inputs, false)
|
||||
self:movePiece(piece, grid, move)
|
||||
self:dropPiece(inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked, hard_drop_enabled)
|
||||
self:dropPiece(
|
||||
inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
|
||||
hard_drop_enabled, additive_gravity
|
||||
)
|
||||
self:lockPiece(piece, grid, lock_delay)
|
||||
end
|
||||
|
||||
|
||||
68
tetris/rulesets/standard_exp.lua
Normal file → Executable file
68
tetris/rulesets/standard_exp.lua
Normal file → Executable file
@@ -3,19 +3,41 @@ local Ruleset = require 'tetris.rulesets.ruleset'
|
||||
|
||||
local SRS = Ruleset:extend()
|
||||
|
||||
SRS.name = "SRS"
|
||||
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
|
||||
|
||||
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 },
|
||||
I = { x=5, y=2 },
|
||||
J = { x=4, y=3 },
|
||||
L = { x=4, y=3 },
|
||||
O = { x=5, y=3 },
|
||||
S = { x=4, y=3 },
|
||||
T = { x=4, y=3 },
|
||||
Z = { x=4, y=3 },
|
||||
}
|
||||
|
||||
SRS.big_spawn_positions = {
|
||||
I = { x=3, y=0 },
|
||||
J = { x=2, y=1 },
|
||||
L = { x=2, y=1 },
|
||||
O = { x=3, y=1 },
|
||||
S = { x=2, y=1 },
|
||||
T = { x=2, y=1 },
|
||||
Z = { x=2, y=1 },
|
||||
}
|
||||
|
||||
SRS.block_offsets = {
|
||||
@@ -109,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)
|
||||
@@ -137,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)
|
||||
piece.lock_delay = 0 -- step reset
|
||||
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
|
||||
@@ -157,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
|
||||
|
||||
@@ -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
|
||||
196
tetris/rulesets/ti_srs.lua
Normal file
196
tetris/rulesets/ti_srs.lua
Normal file
@@ -0,0 +1,196 @@
|
||||
local Piece = require 'tetris.components.piece'
|
||||
local Ruleset = require 'tetris.rulesets.ruleset'
|
||||
|
||||
local SRS = Ruleset:extend()
|
||||
|
||||
SRS.name = "Ti-World"
|
||||
SRS.hash = "Bad I-kicks"
|
||||
SRS.world = true
|
||||
SRS.colourscheme = {
|
||||
I = "C",
|
||||
L = "O",
|
||||
J = "B",
|
||||
S = "G",
|
||||
Z = "R",
|
||||
O = "Y",
|
||||
T = "M",
|
||||
}
|
||||
SRS.softdrop_lock = false
|
||||
SRS.harddrop_lock = true
|
||||
|
||||
SRS.spawn_positions = {
|
||||
I = { x=5, y=4 },
|
||||
J = { x=4, y=5 },
|
||||
L = { x=4, y=5 },
|
||||
O = { x=5, y=5 },
|
||||
S = { x=4, y=5 },
|
||||
T = { x=4, y=5 },
|
||||
Z = { x=4, y=5 },
|
||||
}
|
||||
|
||||
SRS.big_spawn_positions = {
|
||||
I = { x=3, y=2 },
|
||||
J = { x=2, y=3 },
|
||||
L = { x=2, y=3 },
|
||||
O = { x=3, y=3 },
|
||||
S = { x=2, y=3 },
|
||||
T = { x=2, y=3 },
|
||||
Z = { x=2, y=3 },
|
||||
}
|
||||
|
||||
SRS.block_offsets = {
|
||||
I={
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=-2, y=0}, {x=1, y=0} },
|
||||
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=0, y=2} },
|
||||
{ {x=0, y=1}, {x=-1, y=1}, {x=-2, y=1}, {x=1, y=1} },
|
||||
{ {x=-1, y=0}, {x=-1, y=-1}, {x=-1, y=1}, {x=-1, y=2} },
|
||||
},
|
||||
J={
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=-1, y=-1} },
|
||||
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1} , {x=1, y=-1} },
|
||||
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=1, y=1} },
|
||||
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=1} },
|
||||
},
|
||||
L={
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=1, y=-1} },
|
||||
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=1} },
|
||||
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=-1, y=1} },
|
||||
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=-1} },
|
||||
},
|
||||
O={
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=-1, y=-1}, {x=0, y=-1} },
|
||||
},
|
||||
S={
|
||||
{ {x=1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=-1, y=0} },
|
||||
{ {x=1, y=1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=-1} },
|
||||
{ {x=-1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=1, y=0} },
|
||||
{ {x=-1, y=-1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=1} },
|
||||
},
|
||||
T={
|
||||
{ {x=0, y=0}, {x=-1, y=0}, {x=1, y=0}, {x=0, y=-1} },
|
||||
{ {x=0, y=0}, {x=0, y=-1}, {x=0, y=1}, {x=1, y=0} },
|
||||
{ {x=0, y=0}, {x=1, y=0}, {x=-1, y=0}, {x=0, y=1} },
|
||||
{ {x=0, y=0}, {x=0, y=1}, {x=0, y=-1}, {x=-1, y=0} },
|
||||
},
|
||||
Z={
|
||||
{ {x=-1, y=-1}, {x=0, y=-1}, {x=0, y=0}, {x=1, y=0} },
|
||||
{ {x=1, y=-1}, {x=1, y=0}, {x=0, y=0}, {x=0, y=1} },
|
||||
{ {x=1, y=1}, {x=0, y=1}, {x=0, y=0}, {x=-1, y=0} },
|
||||
{ {x=-1, y=1}, {x=-1, y=0}, {x=0, y=0}, {x=0, y=-1} },
|
||||
}
|
||||
}
|
||||
|
||||
SRS.wallkicks_3x3 = {
|
||||
[0]={
|
||||
[1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}},
|
||||
[2]={{x=0, y=1}, {x=0, y=-1}},
|
||||
[3]={{x=1, y=0}, {x=1, y=-1}, {x=0, y=2}, {x=1, y=2}},
|
||||
},
|
||||
[1]={
|
||||
[0]={{x=1, y=0}, {x=1, y=1}, {x=0, y=-2}, {x=1, y=-2}},
|
||||
[2]={{x=1, y=0}, {x=1, y=1}, {x=0, y=-2}, {x=1, y=-2}},
|
||||
[3]={{x=0, y=1}, {x=0, y=-1}},
|
||||
},
|
||||
[2]={
|
||||
[0]={{x=0, y=1}, {x=0, y=-1}},
|
||||
[1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}},
|
||||
[3]={{x=1, y=0}, {x=1, y=-1}, {x=0, y=2}, {x=1, y=2}},
|
||||
},
|
||||
[3]={
|
||||
[0]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
|
||||
[1]={{x=0, y=1}, {x=0, y=-1}},
|
||||
[2]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
|
||||
},
|
||||
};
|
||||
|
||||
SRS.wallkicks_line = {
|
||||
[0]={
|
||||
[1]={{x=-2, y= 0}, {x= 1, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
|
||||
[2]={},
|
||||
[3]={{x= 2, y= 0}, {x=-1, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
|
||||
},
|
||||
[1]={
|
||||
[0]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 2}},
|
||||
[2]={{x=-1, y= 0}, {x= 2, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
|
||||
[3]={},
|
||||
},
|
||||
[2]={
|
||||
[0]={},
|
||||
[1]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 1}},
|
||||
[3]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 1}},
|
||||
},
|
||||
[3]={
|
||||
[0]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 2}},
|
||||
[1]={},
|
||||
[2]={{x= 1, y= 0}, {x=-2, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
|
||||
},
|
||||
};
|
||||
|
||||
-- Component functions.
|
||||
|
||||
function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
||||
|
||||
local kicks
|
||||
if piece.shape == "O" then
|
||||
return
|
||||
elseif piece.shape == "I" then
|
||||
kicks = SRS.wallkicks_line[piece.rotation][new_piece.rotation]
|
||||
else
|
||||
kicks = SRS.wallkicks_3x3[piece.rotation][new_piece.rotation]
|
||||
end
|
||||
|
||||
assert(piece.rotation ~= new_piece.rotation)
|
||||
|
||||
for idx, offset in pairs(kicks) do
|
||||
kicked_piece = new_piece:withOffset(offset)
|
||||
if grid:canPlacePiece(kicked_piece) then
|
||||
piece:setRelativeRotation(rot_dir)
|
||||
piece:setOffset(offset)
|
||||
self:onPieceRotate(piece, grid)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function SRS:onPieceCreate(piece, grid)
|
||||
piece.manipulations = 0
|
||||
piece.rotations = 0
|
||||
end
|
||||
|
||||
function SRS:onPieceDrop(piece, grid)
|
||||
piece.lock_delay = 0 -- step reset
|
||||
end
|
||||
|
||||
function SRS:onPieceMove(piece, grid)
|
||||
piece.lock_delay = 0 -- move reset
|
||||
if piece:isDropBlocked(grid) then
|
||||
piece.manipulations = piece.manipulations + 1
|
||||
if piece.manipulations >= 10 then
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SRS:onPieceRotate(piece, grid)
|
||||
piece.lock_delay = 0 -- rotate reset
|
||||
if piece:isDropBlocked(grid) then
|
||||
piece.rotations = piece.rotations + 1
|
||||
if piece.rotations >= 8 then
|
||||
piece.locked = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SRS:get180RotationValue()
|
||||
if config.gamesettings.world_reverse == 1 then
|
||||
return 1
|
||||
else
|
||||
return 3
|
||||
end
|
||||
end
|
||||
|
||||
return SRS
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user