Compare commits
319 Commits
v0.2.5
...
v0.3-beta7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffd808e6a0 | ||
|
|
dd96db170e | ||
|
|
7fa547c307 | ||
| b2d0838f90 | |||
|
|
42375cb2b8 | ||
|
|
fe162ed215 | ||
|
|
dda116f00f | ||
|
|
2d3aeeb47d | ||
|
|
784c768c57 | ||
|
|
c18e7ed244 | ||
|
|
9df6bb9989 | ||
|
|
f5873c97bc | ||
|
|
fabdad056e | ||
|
|
0e82a8758c | ||
|
|
e78df19112 | ||
|
|
49775b9578 | ||
|
|
6a3c6ecac0 | ||
|
|
90cf2ebef5 | ||
|
|
799a905a9c | ||
|
|
985f73c39d | ||
|
|
b5db5bbdc3 | ||
|
|
438acde2e2 | ||
|
|
0e1f40ad30 | ||
|
|
6cf6568a57 | ||
|
|
dafc113038 | ||
|
|
923f3d3696 | ||
|
|
db4132bf31 | ||
|
|
c58018dd51 | ||
|
|
c7d0034f9b | ||
|
|
ed5ea72e66 | ||
|
|
dc3ad825dc | ||
|
|
40cba83003 | ||
| a1b3f73787 | |||
|
|
4243d6b2ba | ||
| 33b3ad2889 | |||
|
|
adab1df480 | ||
|
|
711fa830a3 | ||
|
|
c434a3406b | ||
|
|
769b5043e3 | ||
|
|
713c62d807 | ||
|
|
c3f6e34518 | ||
|
|
4d0f6ab9fc | ||
|
|
594aa2620f | ||
|
|
199b535f70 | ||
|
|
9fbfbd5cda | ||
|
|
c5c4c4d95c | ||
|
|
53c51c2062 | ||
|
|
e4eb9972e6 | ||
|
|
7dbfe23059 | ||
|
|
61d5410f22 | ||
|
|
2cb0416548 | ||
| 83f3e297ce | |||
|
|
8fb01dc9a8 | ||
|
|
61de3c6dbf | ||
|
|
3c718c38e4 | ||
|
|
d18c3e298d | ||
|
|
33934bfb53 | ||
|
|
3e2d107687 | ||
|
|
312b95728d | ||
|
|
5013443302 | ||
|
|
a8ac8f5966 | ||
|
|
a5032386e6 | ||
|
|
264255290d | ||
|
|
a5839bede2 | ||
|
|
4ebf24316a | ||
|
|
f2acab4496 | ||
| 929069c1b6 | |||
|
|
3f2b38f7b3 | ||
|
|
56fb5aebea | ||
|
|
6c201596b0 | ||
|
|
50466c5902 | ||
| ae1231c47a | |||
|
|
366ac1d552 | ||
| 302353f716 | |||
|
|
7f550b629f | ||
|
|
6b2252e6d9 | ||
|
|
e1741440f2 | ||
|
|
c56f290921 | ||
|
|
86e975f929 | ||
|
|
9f8e9a9778 | ||
|
|
62f9475fa9 | ||
|
|
99d3732d00 | ||
|
|
f5121b62e5 | ||
|
|
cbdbfa6633 | ||
|
|
1b1abc9792 | ||
|
|
894e99e677 | ||
|
|
d4b619da89 | ||
|
|
3766149cb7 | ||
|
|
449ca16bc4 | ||
|
|
71d76e8a6b | ||
|
|
3bdc6e1b2d | ||
|
|
d9b6c85704 | ||
|
|
5ce0686e1a | ||
|
|
3cf496ba98 | ||
|
|
5ddc6ec561 | ||
|
|
b91ffc913b | ||
|
|
ab445ff699 | ||
|
|
7b7a255bf8 | ||
|
|
57721ed35d | ||
|
|
8383d3f445 | ||
|
|
116284f31c | ||
|
|
2189e3a7b8 | ||
|
|
b1d325b714 | ||
|
|
4ab5e3747a | ||
|
|
9761ead48f | ||
|
|
8dedc8a70e | ||
|
|
21769f21c8 | ||
|
|
5cf26b4500 | ||
|
|
4b4a968632 | ||
|
|
e0d98de50d | ||
|
|
4992ea733c | ||
|
|
30ca434027 | ||
|
|
502a50d004 | ||
|
|
36f5287a39 | ||
|
|
a9bbe4a08d | ||
|
|
ee431f5fd8 | ||
|
|
36f2672e06 | ||
|
|
6ecea7edb1 | ||
|
|
dc764b9177 | ||
|
|
5a1494cb5a | ||
|
|
684c4f5b78 | ||
|
|
b568c0fe69 | ||
|
|
2ea75cdfaf | ||
|
|
1f0b43f1b7 | ||
|
|
40bdc5ed99 | ||
|
|
33f2a96ae8 | ||
|
|
846013ce7a | ||
|
|
37c85adc75 | ||
|
|
e7bb44deb4 | ||
|
|
57518dc299 | ||
|
|
0453a3db97 | ||
|
|
b85de17e51 | ||
|
|
163b8f6cc5 | ||
|
|
7250bee619 | ||
|
|
83de216408 | ||
|
|
ba235c8a41 | ||
|
|
ca18d090c9 | ||
|
|
a3a27d2566 | ||
|
|
b15cd9802f | ||
|
|
4c4a818c5c | ||
|
|
716de2814b | ||
|
|
bf19f49323 | ||
|
|
1234e78354 | ||
|
|
9129503d54 | ||
|
|
eae58f11e9 | ||
|
|
3cf5daeb2e | ||
|
|
1dfe68ccff | ||
|
|
8a459b68ba | ||
|
|
cb2b693bcb | ||
|
|
ef6d156d38 | ||
|
|
83e498534c | ||
|
|
8f19c73e2a | ||
|
|
e36b855ff7 | ||
|
|
23b58951cb | ||
|
|
1d73916b7c | ||
|
|
3947e9f02f | ||
|
|
99b15803ee | ||
|
|
d350b25726 | ||
|
|
44e4d00172 | ||
|
|
31e2529265 | ||
|
|
ea7c75f0b3 | ||
|
|
714c6b5e99 | ||
|
|
6a5d5a9c88 | ||
|
|
03491ba151 | ||
|
|
6e22e3d15b | ||
|
|
66ab5992ad | ||
|
|
2c07c2a58c | ||
|
|
a4d3f3bffc | ||
|
|
9ac60cbb5e | ||
|
|
cdd846c3e6 | ||
|
|
33d260b753 | ||
|
|
1644fcdf8e | ||
|
|
f3c1cf6e1f | ||
|
|
06a8a2ebf7 | ||
|
|
15354ce004 | ||
|
|
af02cd3467 | ||
|
|
acb05918c1 | ||
|
|
b644c8e457 | ||
|
|
288961e12a | ||
|
|
a047e51681 | ||
|
|
77f24f5ee5 | ||
|
|
32c2274bef | ||
|
|
4920e5de1c | ||
|
|
8418fc8ab7 | ||
|
|
711a5120f1 | ||
|
|
e7c3c9446a | ||
|
|
3ac39acd7a | ||
|
|
d0505251b3 | ||
|
|
bb0fe2ac20 | ||
|
|
986ebac47f | ||
|
|
9799147f96 | ||
|
|
1dda12e4be | ||
|
|
38947e00c0 | ||
|
|
035f6dd7b4 | ||
|
|
aa3eadc93d | ||
|
|
cb6962825f | ||
|
|
b5e7ce5be6 | ||
|
|
1ccd6a09d3 | ||
|
|
5a074f77cf | ||
|
|
81677221f1 | ||
|
|
a998be6f7b | ||
|
|
9c1c8eea21 | ||
|
|
f022c6c4b7 | ||
|
|
38f3d23b95 | ||
|
|
816d27db39 | ||
|
|
ce08ffd3da | ||
|
|
f0e84a8874 | ||
|
|
5e02471fb4 | ||
|
|
fa2fe77081 | ||
|
|
682c4a485a | ||
|
|
68760105cc | ||
|
|
e19da98ea1 | ||
|
|
e8904b92ed | ||
|
|
4f574e7716 | ||
|
|
f1528e8d71 | ||
|
|
79a25c3954 | ||
|
|
0f3883e18d | ||
|
|
1acd0ec65a | ||
|
|
b22f671409 | ||
|
|
0b6f62d50e | ||
|
|
086f327371 | ||
|
|
3c83ae0bf4 | ||
|
|
450833b246 | ||
|
|
8e7a5418dc | ||
|
|
6609b642dc | ||
|
|
452879ebab | ||
|
|
70a827b477 | ||
|
|
d281a732db | ||
|
|
01e91fbd93 | ||
|
|
ece853c9d3 | ||
|
|
ea8d008370 | ||
|
|
e20eb048c8 | ||
|
|
a33ca1af24 | ||
|
|
664bca2282 | ||
|
|
fc8fb8b66f | ||
|
|
fc58e6e908 | ||
|
|
061f6f5164 | ||
|
|
4e9cea7dda | ||
|
|
fa97216167 | ||
|
|
3f8d68cc9d | ||
|
|
6639d73c1c | ||
|
|
668f061077 | ||
|
|
cb70967b82 | ||
|
|
0c2ba5f0cc | ||
|
|
6d07a3b820 | ||
|
|
2de13a97f0 | ||
|
|
512c2149f0 | ||
|
|
6fb19220b7 | ||
|
|
08da67c434 | ||
|
|
2d63ca8ee1 | ||
|
|
0f09d47e60 | ||
|
|
9d44d1e771 | ||
|
|
5d022f9037 | ||
|
|
818743fe77 | ||
|
|
f22424d671 | ||
|
|
dd6baf1fe6 | ||
|
|
11cf5a9d55 | ||
|
|
5642ed1326 | ||
|
|
c0888c484f | ||
|
|
3ef3b193fd | ||
|
|
0c2e3efd1a | ||
|
|
5076adf022 | ||
|
|
1a75d983dc | ||
|
|
5b8e9586bd | ||
|
|
7d7dd8c3c2 | ||
|
|
29afdcecfc | ||
|
|
8b09833ae6 | ||
|
|
64047eaf9c | ||
|
|
125488b4d9 | ||
|
|
1fdd091456 | ||
|
|
ced40297cc | ||
|
|
32f2a0b3e7 | ||
|
|
dd5347ad8d | ||
|
|
b732ebb213 | ||
|
|
84634d6933 | ||
|
|
0d13a9f236 | ||
|
|
45120bc9f7 | ||
|
|
57c7d9c4c3 | ||
|
|
9f52d8bf10 | ||
|
|
57bd6a8286 | ||
|
|
1a68cd8fce | ||
|
|
56baf46839 | ||
|
|
305d07e10a | ||
|
|
8d954cabc2 | ||
|
|
0281220ea0 | ||
|
|
aef5d88d3f | ||
|
|
3676f7697c | ||
|
|
acb0eb1a71 | ||
|
|
a89bf05cab | ||
|
|
8008315994 | ||
|
|
90f62cb7dd | ||
|
|
eaee5fc7f0 | ||
|
|
e3b038b5a7 | ||
|
|
083693496e | ||
|
|
ba576dfc77 | ||
|
|
e195ccd721 | ||
|
|
70f703eb2f | ||
|
|
dc4d4a8259 | ||
|
|
565510c7b2 | ||
|
|
c26a3f37de | ||
|
|
0c1ce2f717 | ||
|
|
f14ab2a328 | ||
|
|
042dbd220b | ||
|
|
548612123a | ||
|
|
f4675da0b0 | ||
|
|
511e9592bc | ||
|
|
5f3990ff58 | ||
|
|
50ff4adf27 | ||
|
|
87b88f4b42 | ||
|
|
130c2ea403 | ||
|
|
1ea304916e | ||
|
|
e26b094830 | ||
|
|
bcb44725bf | ||
|
|
2990844c52 | ||
|
|
c343014d6f | ||
|
|
605add7e94 | ||
|
|
d3b647ca71 | ||
|
|
1101aa467d | ||
|
|
ce27a7ed18 |
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
*.sav
|
*.sav
|
||||||
*.love
|
*.love
|
||||||
|
*.zip
|
||||||
dist/*.zip
|
dist/*.zip
|
||||||
dist/**/cambridge.exe
|
dist/**/cambridge.exe
|
||||||
dist/**/libs
|
dist/**/libs
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2018-2019 Joe Zeng
|
Copyright (c) 2018-2021 Joe Zeng, Ishaan Bhardwaj
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
91
README.md
@@ -1,43 +1,15 @@
|
|||||||

|

|
||||||
|
|
||||||
Important notice
|
|
||||||
================
|
|
||||||
|
|
||||||
Tetra Online was recently struck by an illegal DMCA claim from the Tetris Company.
|
|
||||||
Please, spread awareness of this, and use the hashtag #DMCAgaming in your posts.
|
|
||||||
|
|
||||||
Cambridge
|
Cambridge
|
||||||
=========
|
=========
|
||||||
|
|
||||||
Welcome to Cambridge, the next open-source falling-block game engine!
|
Welcome to Cambridge, the next open-source falling-block game engine!
|
||||||
|
|
||||||
This fork is written and maintained exclusively by [SashLilac](https://github.com/SashLilac), [joezeng](https://github.com/joezeng) and [Oshisaure](https://github.com/oshisaure)!
|
The project is written and maintained exclusively by [Milla](https://github.com/MillaBasset), [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
|
The Discord server has been reopened! https://discord.gg/AADZUmgsph
|
||||||
|
|
||||||
Credits
|
The game also has a website now with more detail than seen on this README: https://t-sp.in/cambridge
|
||||||
-------
|
|
||||||
|
|
||||||
- [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
|
Playing the game
|
||||||
----------------
|
----------------
|
||||||
@@ -46,9 +18,15 @@ Playing the game
|
|||||||
|
|
||||||
You do not need LÖVE on Windows, as it comes bundled with the program.
|
You do not need LÖVE on Windows, as it comes bundled with the program.
|
||||||
|
|
||||||
To get the stable release, simply download the ZIP in the latest release. All assets needed are bundled with the executable.
|
#### Stable release
|
||||||
|
|
||||||
If you want the bleeding edge version, download [this](https://github.com/SashLilac/cambridge/archive/master.zip).
|
To get the stable release, simply download either `cambridge-win32.zip` (32-bit) or `cambridge-windows.zip` (64-bit) in the [latest release](https://github.com/MillaBasset/cambridge/releases/latest).
|
||||||
|
|
||||||
|
All assets needed are bundled with the executable.
|
||||||
|
|
||||||
|
#### Bleeding edge
|
||||||
|
|
||||||
|
If you want the bleeding edge version, download [this](https://github.com/MillaBasset/cambridge/archive/master.zip).
|
||||||
|
|
||||||
Extract the ZIP, open a Command Prompt at the folder you extracted Cambridge to, then run this command:
|
Extract the ZIP, open a Command Prompt at the folder you extracted Cambridge to, then run this command:
|
||||||
|
|
||||||
@@ -66,9 +44,17 @@ Then, check the mod pack section at the bottom of this page.
|
|||||||
|
|
||||||
If you haven't already, install `love` with your favourite package manager (Homebrew on macOS, your system's default on Linux). **Make sure you're using LÖVE 11, because it won't work with earlier versions!**
|
If you haven't already, install `love` with your favourite package manager (Homebrew on macOS, your system's default on Linux). **Make sure you're using LÖVE 11, because it won't work with earlier versions!**
|
||||||
|
|
||||||
|
#### Downloading a release
|
||||||
|
|
||||||
|
You can download the .love file in the latest release, and run it with:
|
||||||
|
|
||||||
|
love cambridge.love
|
||||||
|
|
||||||
|
#### Installing from source
|
||||||
|
|
||||||
Clone the repository in git:
|
Clone the repository in git:
|
||||||
|
|
||||||
git clone https://github.com/SashLilac/cambridge
|
git clone https://github.com/MillaBasset/cambridge
|
||||||
|
|
||||||
Alternatively, download the source code ZIP in the latest release.
|
Alternatively, download the source code ZIP in the latest release.
|
||||||
|
|
||||||
@@ -80,9 +66,7 @@ It should run automatically!
|
|||||||
|
|
||||||
## Installing modpacks
|
## Installing modpacks
|
||||||
|
|
||||||
Simply drag your mode, ruleset, and randomizer Lua files into their respective [directory](https://love2d.org/wiki/love.filesystem), and they should appear automatically.
|
For instructions on how to install modpacks, go to [this](https://github.com/MillaBasset/cambridge-modpack) mod pack to get a taste of the mod potential.
|
||||||
|
|
||||||
For more detailed instructions, install [this](https://github.com/SashLilac/cambridge-modpack) mod pack to get a taste of the mod potential.
|
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
@@ -94,3 +78,34 @@ community, as well as borrowed from other places, either with licensing
|
|||||||
or as placeholders until suitable material can be found that is properly
|
or as placeholders until suitable material can be found that is properly
|
||||||
licensed. Their original sources, and copyright notices if applicable, are
|
licensed. Their original sources, and copyright notices if applicable, are
|
||||||
listed in the file SOURCES.
|
listed in the file SOURCES.
|
||||||
|
|
||||||
|
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!
|
||||||
|
|
||||||
|
More special thanks can be found in-game, under the "Credits" menu.
|
||||||
|
|
||||||
|
Other Notable Games
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- [TGMsim](https://github.com/2Tie/TGMsim) by 2Tie
|
||||||
|
- [Multimino](https://gamejolt.com/games/multimino/556683) by Axel Fox
|
||||||
|
- [Tetra Legends](https://tetralegends.app) by Dr Ocelot
|
||||||
|
- [ZTrix](https://discord.gg/MGhqCBDGNH) by Electra
|
||||||
|
- [Shiromino](https://github.com/shiromino/shiromino) by Felicity/nightmareci/kdex
|
||||||
|
- [Cursed Blocks](https://github.com/Manabender/Cursed-Blocks) by Manabender
|
||||||
|
- [Picoris 2](https://www.lexaloffle.com/bbs/?tid=41733) by MarkGamed
|
||||||
|
- [Tetra Online](https://github.com/Juan-Cartes/Tetra-Offline) by Mine
|
||||||
|
- [Techmino](https://discord.gg/6Yuww44tq8) by MrZ
|
||||||
|
- [Example Block Game](https://github.com/oshisaure/example-block-game) by Oshisaure
|
||||||
|
- [TETR.IO](https://tetr.io) by osk
|
||||||
|
- [Master of Blocks](https://discord.gg/72FZ49mjWh) by Phoenix Flare
|
||||||
|
- [Spirit Drop](https://rayblastgames.com/spiritdrop.php) by RayRay26
|
||||||
|
- [Puzzle Trial](https://kagamine-rin.itch.io/puzzle-trial) by Rin
|
||||||
|
- [stackfuse](https://github.com/sinefuse/stackfuse) by sinefuse
|
||||||
|
|
||||||
|

|
||||||
|
|||||||
106
SOURCES.md
@@ -45,7 +45,7 @@ Other sounds from:
|
|||||||
Music
|
Music
|
||||||
-----
|
-----
|
||||||
|
|
||||||
1. TGM3 credit roll music.
|
1. Second Reality opening scene music (1993).
|
||||||
2. The FitnessGram™ Pacer Test.
|
2. The FitnessGram™ Pacer Test.
|
||||||
|
|
||||||
All background music is (currently) only unofficially included. In later releases they may be replaced with specifically licensed music as applicable.
|
All background music is (currently) only unofficially included. In later releases they may be replaced with specifically licensed music as applicable.
|
||||||
@@ -114,3 +114,107 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
simple-slider (https://love2d.org/forums/viewtopic.php?t=80711)
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Copyright (c) 2016 George Prosser
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
bigint.lua (https://github.com/empyreuma/bigint.lua)
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
3-Clause BSD License
|
||||||
|
|
||||||
|
Copyright (c) Emily "empyreuma" 2016
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the <organization> nor the
|
||||||
|
names of its contributors may be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
discord-rpc (https://github.com/discord/discord-rpc)
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Copyright 2017 Discord, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
lua-discordRPC (https://github.com/pfirsich/lua-discordRPC)
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Joel Schumacher
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -7,5 +7,11 @@
|
|||||||
@del dist\win32\SOURCES.md
|
@del dist\win32\SOURCES.md
|
||||||
@del dist\win32\LICENSE.md
|
@del dist\win32\LICENSE.md
|
||||||
@rmdir /Q /S dist\win32\libs
|
@rmdir /Q /S dist\win32\libs
|
||||||
|
@del dist\other\cambridge.love
|
||||||
|
@del dist\other\SOURCES.md
|
||||||
|
@del dist\other\LICENSE.md
|
||||||
|
@rmdir /Q /S dist\other\libs
|
||||||
|
@rmdir /Q /S dist\other
|
||||||
@del dist\cambridge-windows.zip
|
@del dist\cambridge-windows.zip
|
||||||
@del dist\cambridge-win32.zip
|
@del dist\cambridge-win32.zip
|
||||||
|
@del dist\cambridge-other.zip
|
||||||
1
conf.lua
@@ -6,5 +6,6 @@ function love.conf(t)
|
|||||||
t.window.title = "Cambridge"
|
t.window.title = "Cambridge"
|
||||||
t.window.width = 640
|
t.window.width = 640
|
||||||
t.window.height = 480
|
t.window.height = 480
|
||||||
|
t.window.icon = "res/img/cambridge_icon.png"
|
||||||
t.window.vsync = false
|
t.window.vsync = false
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,19 +3,11 @@ Game modes
|
|||||||
|
|
||||||
There are several classes of game modes. The modes that originate from other games are organized by suffix:
|
There are several classes of game modes. The modes that originate from other games are organized by suffix:
|
||||||
|
|
||||||
* The "C" series stand for "Classic" games, games that were produced before around 1992-1993 and generally have no wallkicks or lock delay.
|
|
||||||
* 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.
|
* The "A" series stand for "Arika" games, or games in the Tetris the Grand Master series.
|
||||||
* A1 - Tetris The Grand Master (the original from 1998).
|
* A1 - Tetris The Grand Master (the original from 1998).
|
||||||
* A2 - Tetris The Absolute The Grand Master 2 PLUS.
|
* A2 - Tetris The Absolute The Grand Master 2 PLUS.
|
||||||
* A3 - Tetris The Grand Master 3 Terror-Instinct.
|
* A3 - Tetris The Grand Master 3 Terror-Instinct.
|
||||||
* AX - Tetris The Grand Master ACE (X for Xbox).
|
* AX - Tetris The Grand Master ACE (X for Xbox).
|
||||||
* The "G" series stand for "Guideline" games, or games that follow the Tetris Guideline.
|
|
||||||
* GF - Tetris Friends (2007-2019)
|
|
||||||
* GJ - Tetris Online Japan (2005-2011)
|
|
||||||
* N stands for Nullpomino, only used for Phantom Mania N.
|
|
||||||
|
|
||||||
MARATHON
|
MARATHON
|
||||||
--------
|
--------
|
||||||
@@ -28,8 +20,6 @@ From other games:
|
|||||||
* **MARATHON A1**: Tetris the Grand Master 1.
|
* **MARATHON A1**: Tetris the Grand Master 1.
|
||||||
* **MARATHON A2**: Tetris the Grand Master 2 (TAP Master).
|
* **MARATHON A2**: Tetris the Grand Master 2 (TAP Master).
|
||||||
* **MARATHON A3**: Tetris the Grand Master 3 (no exams).
|
* **MARATHON A3**: Tetris the Grand Master 3 (no exams).
|
||||||
* **MARATHON AX4**: Another mode from TGM Ace.
|
|
||||||
* **MARATHON C89**: Nintendo NES Tetris. Can you transition and make it to the killscreen?
|
|
||||||
|
|
||||||
|
|
||||||
SURVIVAL
|
SURVIVAL
|
||||||
@@ -43,14 +33,7 @@ From other games:
|
|||||||
* **SURVIVAL A1**: 20G mode from Tetris the Grand Master.
|
* **SURVIVAL A1**: 20G mode from Tetris the Grand Master.
|
||||||
* **SURVIVAL A2**: T.A. Death.
|
* **SURVIVAL A2**: T.A. Death.
|
||||||
* **SURVIVAL A3**: Ti Shirase.
|
* **SURVIVAL A3**: Ti Shirase.
|
||||||
|
* **SURVIVAL AX**: Another mode from TGM Ace.
|
||||||
|
|
||||||
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
|
PHANTOM MANIA
|
||||||
@@ -69,8 +52,4 @@ OTHER MODES
|
|||||||
|
|
||||||
* **Strategy**: How well can you plan ahead your movements? Can you handle only having a short time to place each piece?
|
* **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.
|
* **Big A2**: Marathon A2 but all the pieces are BIG!
|
||||||
|
|
||||||
* **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?
|
|
||||||
50
funcs.lua
@@ -1,10 +1,20 @@
|
|||||||
function copy(t)
|
function copy(t)
|
||||||
-- returns deep copy of t (as opposed to the shallow copy you get from var = t)
|
-- returns top-layer shallow copy of t
|
||||||
if type(t) ~= "table" then return t end
|
if type(t) ~= "table" then return t end
|
||||||
local meta = getmetatable(t)
|
|
||||||
local target = {}
|
local target = {}
|
||||||
for k, v in pairs(t) do target[k] = v end
|
for k, v in next, t do target[k] = v end
|
||||||
setmetatable(target, meta)
|
setmetatable(target, getmetatable(t))
|
||||||
|
return target
|
||||||
|
end
|
||||||
|
|
||||||
|
function deepcopy(t)
|
||||||
|
-- returns infinite-layer deep copy of t
|
||||||
|
if type(t) ~= "table" then return t end
|
||||||
|
local target = {}
|
||||||
|
for k, v in next, t do
|
||||||
|
target[deepcopy(k)] = deepcopy(v)
|
||||||
|
end
|
||||||
|
setmetatable(target, deepcopy(getmetatable(t)))
|
||||||
return target
|
return target
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -56,9 +66,19 @@ end
|
|||||||
|
|
||||||
function formatBigNum(number)
|
function formatBigNum(number)
|
||||||
-- returns a string representing a number with commas as thousands separator (e.g. 12,345,678)
|
-- returns a string representing a number with commas as thousands separator (e.g. 12,345,678)
|
||||||
local s = string.format("%d", number)
|
local s
|
||||||
local pos = string.len(s) % 3
|
if type(number) == "number" then
|
||||||
if pos == 0 then pos = 3 end
|
s = string.format("%d", number)
|
||||||
|
elseif type(number) == "string" then
|
||||||
|
if not tonumber(number) then
|
||||||
|
return
|
||||||
|
else
|
||||||
|
s = number
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local pos = Mod1(string.len(s), 3)
|
||||||
return string.sub(s, 1, pos)
|
return string.sub(s, 1, pos)
|
||||||
.. string.gsub(string.sub(s, pos+1), "(...)", ",%1")
|
.. string.gsub(string.sub(s, pos+1), "(...)", ",%1")
|
||||||
end
|
end
|
||||||
@@ -67,3 +87,19 @@ function Mod1(n, m)
|
|||||||
-- returns a number congruent to n modulo m in the range [1;m] (as opposed to [0;m-1])
|
-- returns a number congruent to n modulo m in the range [1;m] (as opposed to [0;m-1])
|
||||||
return ((n-1) % m) + 1
|
return ((n-1) % m) + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function table.contains(table, element)
|
||||||
|
for _, value in pairs(table) do
|
||||||
|
if value == element then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function clamp(x, min, max)
|
||||||
|
if max < min then
|
||||||
|
min, max = max, min
|
||||||
|
end
|
||||||
|
return x < min and min or (x > max and max or x)
|
||||||
|
end
|
||||||
|
|||||||
566
libs/bigint/bigint.lua
Normal file
@@ -0,0 +1,566 @@
|
|||||||
|
#!/usr/bin/env lua
|
||||||
|
-- If this variable is true, then strict type checking is performed for all
|
||||||
|
-- operations. This may result in slower code, but it will allow you to catch
|
||||||
|
-- errors and bugs earlier.
|
||||||
|
local strict = true
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local bigint = {}
|
||||||
|
setmetatable(bigint, {__call = function(_, arg) return bigint.new(arg) end})
|
||||||
|
|
||||||
|
local mt = {
|
||||||
|
__add = function(lhs, rhs)
|
||||||
|
return bigint.add(lhs, rhs)
|
||||||
|
end,
|
||||||
|
__unm = function(arg)
|
||||||
|
return bigint.negate(arg)
|
||||||
|
end,
|
||||||
|
__sub = function(lhs, rhs)
|
||||||
|
return bigint.subtract(lhs, rhs)
|
||||||
|
end,
|
||||||
|
__mul = function(lhs, rhs)
|
||||||
|
return bigint.multiply(lhs, rhs)
|
||||||
|
end,
|
||||||
|
__div = function(lhs, rhs)
|
||||||
|
return bigint.divide(lhs, rhs)
|
||||||
|
end,
|
||||||
|
__mod = function(lhs, rhs)
|
||||||
|
return bigint.modulus(lhs, rhs)
|
||||||
|
end,
|
||||||
|
__pow = function(lhs, rhs)
|
||||||
|
return bigint.exponentiate(lhs, rhs)
|
||||||
|
end,
|
||||||
|
__tostring = function(arg)
|
||||||
|
return bigint.unserialize(arg, "s")
|
||||||
|
end,
|
||||||
|
__eq = function(lhs, rhs)
|
||||||
|
return bigint.compare(lhs, rhs, "==")
|
||||||
|
end,
|
||||||
|
__lt = function(lhs, rhs)
|
||||||
|
return bigint.compare(lhs, rhs, "<")
|
||||||
|
end,
|
||||||
|
__le = function(lhs, rhs)
|
||||||
|
return bigint.compare(lhs, rhs, "<=")
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
local named_powers = require("libs.bigint.named-powers-of-ten")
|
||||||
|
|
||||||
|
-- Create a new bigint or convert a number or string into a big
|
||||||
|
-- Returns an empty, positive bigint if no number or string is given
|
||||||
|
function bigint.new(num)
|
||||||
|
local self = {
|
||||||
|
sign = "+",
|
||||||
|
digits = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Return a new bigint with the same sign and digits
|
||||||
|
function self:clone()
|
||||||
|
local newint = bigint.new()
|
||||||
|
newint.sign = self.sign
|
||||||
|
for _, digit in pairs(self.digits) do
|
||||||
|
newint.digits[#newint.digits + 1] = digit
|
||||||
|
end
|
||||||
|
return newint
|
||||||
|
end
|
||||||
|
|
||||||
|
setmetatable(self, mt)
|
||||||
|
|
||||||
|
if (num) then
|
||||||
|
local num_string = tostring(num)
|
||||||
|
for digit in string.gmatch(num_string, "[0-9]") do
|
||||||
|
table.insert(self.digits, tonumber(digit))
|
||||||
|
end
|
||||||
|
if string.sub(num_string, 1, 1) == "-" then
|
||||||
|
self.sign = "-"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return bigint.strip(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check the type of a big
|
||||||
|
-- Normally only runs when global variable "strict" == true, but checking can be
|
||||||
|
-- forced by supplying "true" as the second argument.
|
||||||
|
function bigint.check(big, force)
|
||||||
|
if (strict or force) then
|
||||||
|
assert(getmetatable(big) == mt, "at least one arg is not a bigint")
|
||||||
|
assert(#big.digits > 0, "bigint is empty")
|
||||||
|
assert(big.sign == "+" or big.sign == "-", "bigint is unsigned")
|
||||||
|
for _, digit in pairs(big.digits) do
|
||||||
|
assert(type(digit) == "number", "at least one digit is invalid")
|
||||||
|
assert(digit <= 9 and digit >= 0, digit .. " is not between 0 and 9")
|
||||||
|
assert(math.floor(digit) == digit, digit .. " is not an integer")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Strip leading zeroes from a big, but don't remove the last zero
|
||||||
|
function bigint.strip(big)
|
||||||
|
while (#big.digits > 1) and (big.digits[1] == 0) do
|
||||||
|
table.remove(big.digits, 1)
|
||||||
|
end
|
||||||
|
return big
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Return a new big with the same digits but with a positive sign (absolute
|
||||||
|
-- value)
|
||||||
|
function bigint.abs(big)
|
||||||
|
bigint.check(big)
|
||||||
|
local result = big:clone()
|
||||||
|
result.sign = "+"
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Return a new big with the same digits but the opposite sign (negation)
|
||||||
|
function bigint.negate(big)
|
||||||
|
bigint.check(big)
|
||||||
|
local result = big:clone()
|
||||||
|
if (result.sign == "+") then
|
||||||
|
result.sign = "-"
|
||||||
|
else
|
||||||
|
result.sign = "+"
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Return the number of digits in the big
|
||||||
|
function bigint.digits(big)
|
||||||
|
bigint.check(big)
|
||||||
|
return #big.digits
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Convert a big to a number or string
|
||||||
|
function bigint.unserialize(big, output_type, precision)
|
||||||
|
bigint.check(big)
|
||||||
|
|
||||||
|
local num = ""
|
||||||
|
if big.sign == "-" then
|
||||||
|
num = "-"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
if ((output_type == nil)
|
||||||
|
or (output_type == "number")
|
||||||
|
or (output_type == "n")
|
||||||
|
or (output_type == "string")
|
||||||
|
or (output_type == "s")) then
|
||||||
|
-- Unserialization to a string or number requires reconstructing the
|
||||||
|
-- entire number
|
||||||
|
|
||||||
|
for _, digit in pairs(big.digits) do
|
||||||
|
num = num .. math.floor(digit) -- lazy way of getting rid of .0$
|
||||||
|
end
|
||||||
|
|
||||||
|
if ((output_type == nil)
|
||||||
|
or (output_type == "number")
|
||||||
|
or (output_type == "n")) then
|
||||||
|
return tonumber(num)
|
||||||
|
else
|
||||||
|
return num
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
-- Unserialization to human-readable form or scientific notation only
|
||||||
|
-- requires reading the first few digits
|
||||||
|
if (precision == nil) then
|
||||||
|
precision = math.min(#big.digits, 3)
|
||||||
|
else
|
||||||
|
assert(precision > 0, "Precision cannot be less than 1")
|
||||||
|
assert(math.floor(precision) == precision,
|
||||||
|
"Precision must be a positive integer")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- num is the first (precision + 1) digits, the first being separated by
|
||||||
|
-- a decimal point from the others
|
||||||
|
num = num .. math.floor(big.digits[1])
|
||||||
|
if (precision > 1) then
|
||||||
|
num = num .. "."
|
||||||
|
for i = 1, (precision - 1) do
|
||||||
|
num = num .. math.floor(big.digits[i + 1])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if ((output_type == "human-readable")
|
||||||
|
or (output_type == "human")
|
||||||
|
or (output_type == "h"))
|
||||||
|
and (#big.digits >= 3 and #big.digits <= 10002) then
|
||||||
|
-- Human-readable output contributed by 123eee555
|
||||||
|
|
||||||
|
local name
|
||||||
|
local walkback = 0 -- Used to enumerate "ten", "hundred", etc
|
||||||
|
|
||||||
|
-- Walk backwards in the index of named_powers starting at the
|
||||||
|
-- number of digits of the input until the first value is found
|
||||||
|
for i = (#big.digits - 1), (#big.digits - 4), -1 do
|
||||||
|
name = named_powers[i]
|
||||||
|
if (name) then
|
||||||
|
if (walkback == 1) then
|
||||||
|
name = "ten " .. name
|
||||||
|
elseif (walkback == 2) then
|
||||||
|
name = "hundred " .. name
|
||||||
|
end
|
||||||
|
break
|
||||||
|
else
|
||||||
|
walkback = walkback + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return num .. " " .. name
|
||||||
|
|
||||||
|
else
|
||||||
|
return num .. "*10^" .. (#big.digits - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Basic comparisons
|
||||||
|
-- Accepts symbols (<, >=, ~=) and Unix shell-like options (lt, ge, ne)
|
||||||
|
function bigint.compare(big1, big2, comparison)
|
||||||
|
bigint.check(big1)
|
||||||
|
bigint.check(big2)
|
||||||
|
|
||||||
|
local greater = false -- If big1.digits > big2.digits
|
||||||
|
local equal = false
|
||||||
|
|
||||||
|
if (big1.sign == "-") and (big2.sign == "+") then
|
||||||
|
greater = false
|
||||||
|
elseif (#big1.digits > #big2.digits)
|
||||||
|
or ((big1.sign == "+") and (big2.sign == "-")) then
|
||||||
|
greater = true
|
||||||
|
elseif (#big1.digits == #big2.digits) then
|
||||||
|
-- Walk left to right, comparing digits
|
||||||
|
for digit = 1, #big1.digits do
|
||||||
|
if (big1.digits[digit] > big2.digits[digit]) then
|
||||||
|
greater = true
|
||||||
|
break
|
||||||
|
elseif (big2.digits[digit] > big1.digits[digit]) then
|
||||||
|
break
|
||||||
|
elseif (digit == #big1.digits)
|
||||||
|
and (big1.digits[digit] == big2.digits[digit]) then
|
||||||
|
equal = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If both numbers are negative, then the requirements for greater are
|
||||||
|
-- reversed
|
||||||
|
if (not equal) and (big1.sign == "-") and (big2.sign == "-") then
|
||||||
|
greater = not greater
|
||||||
|
end
|
||||||
|
|
||||||
|
return (((comparison == "<") or (comparison == "lt"))
|
||||||
|
and ((not greater) and (not equal)) and true)
|
||||||
|
or (((comparison == ">") or (comparison == "gt"))
|
||||||
|
and ((greater) and (not equal)) and true)
|
||||||
|
or (((comparison == "==") or (comparison == "eq"))
|
||||||
|
and (equal) and true)
|
||||||
|
or (((comparison == ">=") or (comparison == "ge"))
|
||||||
|
and (equal or greater) and true)
|
||||||
|
or (((comparison == "<=") or (comparison == "le"))
|
||||||
|
and (equal or not greater) and true)
|
||||||
|
or (((comparison == "~=") or (comparison == "!=") or (comparison == "ne"))
|
||||||
|
and (not equal) and true)
|
||||||
|
or false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- BACKEND: Add big1 and big2, ignoring signs
|
||||||
|
function bigint.add_raw(big1, big2)
|
||||||
|
bigint.check(big1)
|
||||||
|
bigint.check(big2)
|
||||||
|
|
||||||
|
local result = bigint.new()
|
||||||
|
local max_digits = 0
|
||||||
|
local carry = 0
|
||||||
|
|
||||||
|
if (#big1.digits >= #big2.digits) then
|
||||||
|
max_digits = #big1.digits
|
||||||
|
else
|
||||||
|
max_digits = #big2.digits
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Walk backwards right to left, like in long addition
|
||||||
|
for digit = 0, max_digits - 1 do
|
||||||
|
local sum = (big1.digits[#big1.digits - digit] or 0)
|
||||||
|
+ (big2.digits[#big2.digits - digit] or 0)
|
||||||
|
+ carry
|
||||||
|
|
||||||
|
if (sum >= 10) then
|
||||||
|
carry = 1
|
||||||
|
sum = sum - 10
|
||||||
|
else
|
||||||
|
carry = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
result.digits[max_digits - digit] = sum
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Leftover carry in cases when #big1.digits == #big2.digits and sum > 10, ex. 7 + 9
|
||||||
|
if (carry == 1) then
|
||||||
|
table.insert(result.digits, 1, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
-- BACKEND: Subtract big2 from big1, ignoring signs
|
||||||
|
function bigint.subtract_raw(big1, big2)
|
||||||
|
-- Type checking is done by bigint.compare
|
||||||
|
assert(bigint.compare(bigint.abs(big1), bigint.abs(big2), ">="),
|
||||||
|
"Size of " .. bigint.unserialize(big1, "string") .. " is less than "
|
||||||
|
.. bigint.unserialize(big2, "string"))
|
||||||
|
|
||||||
|
local result = big1:clone()
|
||||||
|
local max_digits = #big1.digits
|
||||||
|
local borrow = 0
|
||||||
|
|
||||||
|
-- Logic mostly copied from bigint.add_raw ---------------------------------
|
||||||
|
-- Walk backwards right to left, like in long subtraction
|
||||||
|
for digit = 0, max_digits - 1 do
|
||||||
|
local diff = (big1.digits[#big1.digits - digit] or 0)
|
||||||
|
- (big2.digits[#big2.digits - digit] or 0)
|
||||||
|
- borrow
|
||||||
|
|
||||||
|
if (diff < 0) then
|
||||||
|
borrow = 1
|
||||||
|
diff = diff + 10
|
||||||
|
else
|
||||||
|
borrow = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
result.digits[max_digits - digit] = diff
|
||||||
|
end
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
return bigint.strip(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- FRONTEND: Addition and subtraction operations, accounting for signs
|
||||||
|
function bigint.add(big1, big2)
|
||||||
|
-- Type checking is done by bigint.compare
|
||||||
|
|
||||||
|
local result
|
||||||
|
|
||||||
|
-- If adding numbers of different sign, subtract the smaller sized one from
|
||||||
|
-- the bigger sized one and take the sign of the bigger sized one
|
||||||
|
if (big1.sign ~= big2.sign) then
|
||||||
|
if (bigint.compare(bigint.abs(big1), bigint.abs(big2), ">")) then
|
||||||
|
result = bigint.subtract_raw(big1, big2)
|
||||||
|
result.sign = big1.sign
|
||||||
|
else
|
||||||
|
result = bigint.subtract_raw(big2, big1)
|
||||||
|
result.sign = big2.sign
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif (big1.sign == "+") and (big2.sign == "+") then
|
||||||
|
result = bigint.add_raw(big1, big2)
|
||||||
|
|
||||||
|
elseif (big1.sign == "-") and (big2.sign == "-") then
|
||||||
|
result = bigint.add_raw(big1, big2)
|
||||||
|
result.sign = "-"
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function bigint.subtract(big1, big2)
|
||||||
|
-- Type checking is done by bigint.compare in bigint.add
|
||||||
|
-- Subtracting is like adding a negative
|
||||||
|
local big2_local = big2:clone()
|
||||||
|
if (big2.sign == "+") then
|
||||||
|
big2_local.sign = "-"
|
||||||
|
else
|
||||||
|
big2_local.sign = "+"
|
||||||
|
end
|
||||||
|
return bigint.add(big1, big2_local)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- BACKEND: Multiply a big by a single digit big, ignoring signs
|
||||||
|
function bigint.multiply_single(big1, big2)
|
||||||
|
bigint.check(big1)
|
||||||
|
bigint.check(big2)
|
||||||
|
assert(#big2.digits == 1, bigint.unserialize(big2, "string")
|
||||||
|
.. " has more than one digit")
|
||||||
|
|
||||||
|
local result = bigint.new()
|
||||||
|
local carry = 0
|
||||||
|
|
||||||
|
-- Logic mostly copied from bigint.add_raw ---------------------------------
|
||||||
|
-- Walk backwards right to left, like in long multiplication
|
||||||
|
for digit = 0, #big1.digits - 1 do
|
||||||
|
local this_digit = big1.digits[#big1.digits - digit]
|
||||||
|
* big2.digits[1]
|
||||||
|
+ carry
|
||||||
|
|
||||||
|
if (this_digit >= 10) then
|
||||||
|
carry = math.floor(this_digit / 10)
|
||||||
|
this_digit = this_digit - (carry * 10)
|
||||||
|
else
|
||||||
|
carry = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
result.digits[#big1.digits - digit] = this_digit
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Leftover carry in cases when big1.digits[1] * big2.digits[1] > 0
|
||||||
|
if (carry > 0) then
|
||||||
|
table.insert(result.digits, 1, carry)
|
||||||
|
end
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- FRONTEND: Multiply two bigs, accounting for signs
|
||||||
|
function bigint.multiply(big1, big2)
|
||||||
|
-- Type checking done by bigint.multiply_single
|
||||||
|
|
||||||
|
local result = bigint.new(0)
|
||||||
|
local larger, smaller -- Larger and smaller in terms of digits, not size
|
||||||
|
|
||||||
|
if (bigint.unserialize(big1) == 0) or (bigint.unserialize(big2) == 0) then
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
if (#big1.digits >= #big2.digits) then
|
||||||
|
larger = big1
|
||||||
|
smaller = big2
|
||||||
|
else
|
||||||
|
larger = big2
|
||||||
|
smaller = big1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Walk backwards right to left, like in long multiplication
|
||||||
|
for digit = 0, #smaller.digits - 1 do
|
||||||
|
-- Sorry for going over column 80! There's lots of big names here
|
||||||
|
local this_digit_product = bigint.multiply_single(larger,
|
||||||
|
bigint.new(smaller.digits[#smaller.digits - digit]))
|
||||||
|
|
||||||
|
-- "Placeholding zeroes"
|
||||||
|
if (digit > 0) then
|
||||||
|
for placeholder = 1, digit do
|
||||||
|
table.insert(this_digit_product.digits, 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
result = bigint.add(result, this_digit_product)
|
||||||
|
end
|
||||||
|
|
||||||
|
if (larger.sign == smaller.sign) then
|
||||||
|
result.sign = "+"
|
||||||
|
else
|
||||||
|
result.sign = "-"
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Raise a big to a positive integer or big power (TODO: negative integer power)
|
||||||
|
function bigint.exponentiate(big, power)
|
||||||
|
-- Type checking for big done by bigint.multiply
|
||||||
|
assert(bigint.compare(power, bigint.new(0), ">="),
|
||||||
|
"negative powers are not supported")
|
||||||
|
local exp = power:clone()
|
||||||
|
|
||||||
|
if (bigint.compare(exp, bigint.new(0), "==")) then
|
||||||
|
return bigint.new(1)
|
||||||
|
elseif (bigint.compare(exp, bigint.new(1), "==")) then
|
||||||
|
return big:clone()
|
||||||
|
else
|
||||||
|
local result = bigint.new(1)
|
||||||
|
local base = big:clone()
|
||||||
|
|
||||||
|
while (true) do
|
||||||
|
if (bigint.compare(
|
||||||
|
bigint.modulus(exp, bigint.new(2)), bigint.new(1), "=="
|
||||||
|
)) then
|
||||||
|
result = bigint.multiply(result, base)
|
||||||
|
end
|
||||||
|
if (bigint.compare(exp, bigint.new(1), "==")) then
|
||||||
|
break
|
||||||
|
else
|
||||||
|
exp = bigint.divide(exp, bigint.new(2))
|
||||||
|
base = bigint.multiply(base, base)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
-- BACKEND: Divide two bigs (decimals not supported), returning big result and
|
||||||
|
-- big remainder
|
||||||
|
-- WARNING: Only supports positive integers
|
||||||
|
function bigint.divide_raw(big1, big2)
|
||||||
|
-- Type checking done by bigint.compare
|
||||||
|
if (bigint.compare(big1, big2, "==")) then
|
||||||
|
return bigint.new(1), bigint.new(0)
|
||||||
|
elseif (bigint.compare(big1, big2, "<")) then
|
||||||
|
return bigint.new(0), big1:clone()
|
||||||
|
else
|
||||||
|
assert(bigint.compare(big2, bigint.new(0), "!="), "error: divide by zero")
|
||||||
|
assert(big1.sign == "+", "error: big1 is not positive")
|
||||||
|
assert(big2.sign == "+", "error: big2 is not positive")
|
||||||
|
|
||||||
|
local result = bigint.new()
|
||||||
|
|
||||||
|
local dividend = bigint.new() -- Dividend of a single operation
|
||||||
|
|
||||||
|
local neg_zero = bigint.new(0)
|
||||||
|
neg_zero.sign = "-"
|
||||||
|
|
||||||
|
for i = 1, #big1.digits do
|
||||||
|
-- Fixes a negative zero bug
|
||||||
|
if (#dividend.digits ~= 0) and (bigint.compare(dividend, neg_zero, "==")) then
|
||||||
|
dividend = bigint.new()
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(dividend.digits, big1.digits[i])
|
||||||
|
|
||||||
|
local factor = bigint.new(0)
|
||||||
|
while bigint.compare(dividend, big2, ">=") do
|
||||||
|
dividend = bigint.subtract(dividend, big2)
|
||||||
|
factor = bigint.add(factor, bigint.new(1))
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 0, #factor.digits - 1 do
|
||||||
|
result.digits[#result.digits + 1 - i] = factor.digits[i + 1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return bigint.strip(result), dividend
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- FRONTEND: Divide two bigs (decimals not supported), returning big result and
|
||||||
|
-- big remainder, accounting for signs
|
||||||
|
function bigint.divide(big1, big2)
|
||||||
|
local result, remainder = bigint.divide_raw(bigint.abs(big1),
|
||||||
|
bigint.abs(big2))
|
||||||
|
if (big1.sign == big2.sign) then
|
||||||
|
result.sign = "+"
|
||||||
|
else
|
||||||
|
result.sign = "-"
|
||||||
|
end
|
||||||
|
|
||||||
|
return result, remainder
|
||||||
|
end
|
||||||
|
|
||||||
|
-- FRONTEND: Return only the remainder from bigint.divide
|
||||||
|
function bigint.modulus(big1, big2)
|
||||||
|
local result, remainder = bigint.divide(big1, big2)
|
||||||
|
|
||||||
|
-- Remainder will always have the same sign as the dividend per C standard
|
||||||
|
-- https://en.wikipedia.org/wiki/Modulo_operation#Remainder_calculation_for_the_modulo_operation
|
||||||
|
remainder.sign = big1.sign
|
||||||
|
return remainder
|
||||||
|
end
|
||||||
|
|
||||||
|
return bigint
|
||||||
3340
libs/bigint/named-powers-of-ten.lua
Normal file
@@ -24,6 +24,59 @@ if osname == "Linux" then
|
|||||||
elseif osname == "OS X" then
|
elseif osname == "OS X" then
|
||||||
discordRPClib = ffi.load(source.."/libs/discord-rpc.dylib")
|
discordRPClib = ffi.load(source.."/libs/discord-rpc.dylib")
|
||||||
elseif osname == "Windows" then
|
elseif osname == "Windows" then
|
||||||
|
-- I would strongly advise never touching this. It was not trivial to get correct. -nightmareci
|
||||||
|
|
||||||
|
ffi.cdef[[
|
||||||
|
typedef uint32_t DWORD;
|
||||||
|
typedef char CHAR;
|
||||||
|
typedef CHAR *LPSTR;
|
||||||
|
typedef const CHAR *LPCSTR;
|
||||||
|
typedef wchar_t WCHAR;
|
||||||
|
typedef WCHAR *LPWSTR;
|
||||||
|
typedef LPWSTR PWSTR;
|
||||||
|
typedef const WCHAR *LPCWSTR;
|
||||||
|
|
||||||
|
static const DWORD CP_UTF8 = 65001;
|
||||||
|
int32_t MultiByteToWideChar(
|
||||||
|
DWORD CodePage,
|
||||||
|
DWORD dwFlags,
|
||||||
|
LPCSTR lpMultiByteStr,
|
||||||
|
int32_t cbMultiByte,
|
||||||
|
LPWSTR lpWideCharStr,
|
||||||
|
int32_t cchWideChar
|
||||||
|
);
|
||||||
|
|
||||||
|
int32_t WideCharToMultiByte(
|
||||||
|
DWORD CodePage,
|
||||||
|
DWORD dwFlags,
|
||||||
|
LPCWSTR lpWideCharStr,
|
||||||
|
int32_t cchWideChar,
|
||||||
|
LPSTR lpMultiByteStr,
|
||||||
|
int32_t cbMultiByte,
|
||||||
|
void* lpDefaultChar,
|
||||||
|
void* lpUsedDefaultChar
|
||||||
|
);
|
||||||
|
|
||||||
|
DWORD GetShortPathNameW(
|
||||||
|
LPCWSTR lpszLongPath,
|
||||||
|
LPWSTR lpszShortPath,
|
||||||
|
DWORD cchBuffer
|
||||||
|
);
|
||||||
|
]]
|
||||||
|
|
||||||
|
local originalWideSize = ffi.C.MultiByteToWideChar(ffi.C.CP_UTF8, 0, source, -1, nil, 0)
|
||||||
|
local originalWide = ffi.new('WCHAR[?]', originalWideSize)
|
||||||
|
ffi.C.MultiByteToWideChar(ffi.C.CP_UTF8, 0, source, -1, originalWide, originalWideSize)
|
||||||
|
|
||||||
|
local sourceSize = ffi.C.GetShortPathNameW(originalWide, nil, 0)
|
||||||
|
local sourceWide = ffi.new('WCHAR[?]', sourceSize)
|
||||||
|
ffi.C.GetShortPathNameW(originalWide, sourceWide, sourceSize)
|
||||||
|
|
||||||
|
local sourceChar = ffi.new('char[?]', sourceSize)
|
||||||
|
ffi.C.WideCharToMultiByte(ffi.C.CP_UTF8, 0, sourceWide, sourceSize, sourceChar, sourceSize, nil, nil)
|
||||||
|
|
||||||
|
source = ffi.string(sourceChar)
|
||||||
|
|
||||||
discordRPClib = ffi.load(source.."/libs/discord-rpc.dll")
|
discordRPClib = ffi.load(source.."/libs/discord-rpc.dll")
|
||||||
else
|
else
|
||||||
-- Else it crashes later on
|
-- Else it crashes later on
|
||||||
|
|||||||
138
libs/simple-slider.lua
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
--[[
|
||||||
|
Copyright (c) 2016 George Prosser
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local slider = {}
|
||||||
|
slider.__index = slider
|
||||||
|
|
||||||
|
function newSlider(x, y, length, value, min, max, setter, style)
|
||||||
|
local s = {}
|
||||||
|
s.value = (value - min) / (max - min)
|
||||||
|
s.min = min
|
||||||
|
s.max = max
|
||||||
|
s.setter = setter
|
||||||
|
s.x = x
|
||||||
|
s.y = y
|
||||||
|
s.length = length
|
||||||
|
|
||||||
|
local p = style or {}
|
||||||
|
s.width = p.width or length * 0.1
|
||||||
|
s.orientation = p.orientation or 'horizontal'
|
||||||
|
s.track = p.track or 'rectangle'
|
||||||
|
s.knob = p.knob or 'rectangle'
|
||||||
|
|
||||||
|
s.grabbed = false
|
||||||
|
s.wasDown = true
|
||||||
|
s.ox = 0
|
||||||
|
s.oy = 0
|
||||||
|
|
||||||
|
return setmetatable(s, slider)
|
||||||
|
end
|
||||||
|
|
||||||
|
function slider:update(mouseX, mouseY, mouseDown)
|
||||||
|
local x = mouseX or love.mouse.getX()
|
||||||
|
local y = mouseY or love.mouse.getY()
|
||||||
|
local down = love.mouse.isDown(1)
|
||||||
|
if mouseDown ~= nil then
|
||||||
|
down = mouseDown
|
||||||
|
end
|
||||||
|
|
||||||
|
local knobX = self.x
|
||||||
|
local knobY = self.y
|
||||||
|
if self.orientation == 'horizontal' then
|
||||||
|
knobX = self.x - self.length/2 + self.length * self.value
|
||||||
|
elseif self.orientation == 'vertical' then
|
||||||
|
knobY = self.y + self.length/2 - self.length * self.value
|
||||||
|
end
|
||||||
|
|
||||||
|
local ox = x - knobX
|
||||||
|
local oy = y - knobY
|
||||||
|
|
||||||
|
local dx = ox - self.ox
|
||||||
|
local dy = oy - self.oy
|
||||||
|
|
||||||
|
if down then
|
||||||
|
if self.grabbed then
|
||||||
|
if self.orientation == 'horizontal' then
|
||||||
|
self.value = self.value + dx / self.length
|
||||||
|
elseif self.orientation == 'vertical' then
|
||||||
|
self.value = self.value - dy / self.length
|
||||||
|
end
|
||||||
|
elseif (x > knobX - self.width/2 and x < knobX + self.width/2 and y > knobY - self.width/2 and y < knobY + self.width/2) and not self.wasDown then
|
||||||
|
self.ox = ox
|
||||||
|
self.oy = oy
|
||||||
|
self.grabbed = true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.grabbed = false
|
||||||
|
end
|
||||||
|
|
||||||
|
self.value = math.max(0, math.min(1, self.value))
|
||||||
|
|
||||||
|
if self.setter ~= nil then
|
||||||
|
self.setter(self.min + self.value * (self.max - self.min))
|
||||||
|
end
|
||||||
|
|
||||||
|
self.wasDown = down
|
||||||
|
end
|
||||||
|
|
||||||
|
function slider:draw()
|
||||||
|
if self.track == 'rectangle' then
|
||||||
|
if self.orientation == 'horizontal' then
|
||||||
|
love.graphics.rectangle('line', self.x - self.length/2 - self.width/2, self.y - self.width/2, self.length + self.width, self.width)
|
||||||
|
elseif self.orientation == 'vertical' then
|
||||||
|
love.graphics.rectangle('line', self.x - self.width/2, self.y - self.length/2 - self.width/2, self.width, self.length + self.width)
|
||||||
|
end
|
||||||
|
elseif self.track == 'line' then
|
||||||
|
if self.orientation == 'horizontal' then
|
||||||
|
love.graphics.line(self.x - self.length/2, self.y, self.x + self.length/2, self.y)
|
||||||
|
elseif self.orientation == 'vertical' then
|
||||||
|
love.graphics.line(self.x, self.y - self.length/2, self.x, self.y + self.length/2)
|
||||||
|
end
|
||||||
|
elseif self.track == 'roundrect' then
|
||||||
|
if self.orientation == 'horizontal' then
|
||||||
|
love.graphics.rectangle('line', self.x - self.length/2 - self.width/2, self.y - self.width/2, self.length + self.width, self.width, self.width/2, self.width)
|
||||||
|
elseif self.orientation == 'vertical' then
|
||||||
|
love.graphics.rectangle('line', self.x - self.width/2, self.y - self.length/2 - self.width/2, self.width, self.length + self.width, self.width, self.width/2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local knobX = self.x
|
||||||
|
local knobY = self.y
|
||||||
|
if self.orientation == 'horizontal' then
|
||||||
|
knobX = self.x - self.length/2 + self.length * self.value
|
||||||
|
elseif self.orientation == 'vertical' then
|
||||||
|
knobY = self.y + self.length/2 - self.length * self.value
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.knob == 'rectangle' then
|
||||||
|
love.graphics.rectangle('fill', knobX - self.width/2, knobY - self.width/2, self.width, self.width)
|
||||||
|
elseif self.knob == 'circle' then
|
||||||
|
love.graphics.circle('fill', knobX, knobY, self.width/2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function slider:getValue()
|
||||||
|
return self.min + self.value * (self.max - self.min)
|
||||||
|
end
|
||||||
41
load/bgm.lua
@@ -6,34 +6,42 @@ bgm = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
local current_bgm = nil
|
local current_bgm = nil
|
||||||
local bgm_locked = true
|
local bgm_locked = false
|
||||||
|
local unfocused = false
|
||||||
|
|
||||||
function switchBGM(sound, subsound)
|
function switchBGM(sound, subsound)
|
||||||
if bgm_locked then return end
|
|
||||||
if current_bgm ~= nil then
|
if current_bgm ~= nil then
|
||||||
current_bgm:stop()
|
current_bgm:stop()
|
||||||
end
|
end
|
||||||
|
if bgm_locked or config.bgm_volume <= 0 then
|
||||||
|
current_bgm = nil
|
||||||
|
elseif sound ~= nil then
|
||||||
if subsound ~= nil then
|
if subsound ~= nil then
|
||||||
current_bgm = bgm[sound][subsound]
|
current_bgm = bgm[sound][subsound]
|
||||||
resetBGMFadeout()
|
else
|
||||||
elseif sound ~= nil then
|
|
||||||
current_bgm = bgm[sound]
|
current_bgm = bgm[sound]
|
||||||
resetBGMFadeout()
|
end
|
||||||
else
|
else
|
||||||
current_bgm = nil
|
current_bgm = nil
|
||||||
end
|
end
|
||||||
|
if current_bgm ~= nil then
|
||||||
|
resetBGMFadeout()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function switchBGMLoop(sound, subsound)
|
function switchBGMLoop(sound, subsound)
|
||||||
if bgm_locked then return end
|
|
||||||
switchBGM(sound, subsound)
|
switchBGM(sound, subsound)
|
||||||
current_bgm:setLooping(true)
|
if current_bgm then current_bgm:setLooping(true) end
|
||||||
end
|
end
|
||||||
|
|
||||||
function lockBGM()
|
function lockBGM()
|
||||||
bgm_locked = true
|
bgm_locked = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function unlockBGM()
|
||||||
|
bgm_locked = false
|
||||||
|
end
|
||||||
|
|
||||||
local fading_bgm = false
|
local fading_bgm = false
|
||||||
local fadeout_time = 0
|
local fadeout_time = 0
|
||||||
local total_fadeout_time = 0
|
local total_fadeout_time = 0
|
||||||
@@ -47,29 +55,36 @@ function fadeoutBGM(time)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function resetBGMFadeout(time)
|
function resetBGMFadeout(time)
|
||||||
current_bgm:setVolume(1)
|
current_bgm:setVolume(config.bgm_volume)
|
||||||
fading_bgm = false
|
fading_bgm = false
|
||||||
current_bgm:play()
|
resumeBGM()
|
||||||
end
|
end
|
||||||
|
|
||||||
function processBGMFadeout(dt)
|
function processBGMFadeout(dt)
|
||||||
if fading_bgm then
|
if current_bgm and fading_bgm then
|
||||||
fadeout_time = fadeout_time - dt
|
fadeout_time = fadeout_time - dt
|
||||||
if fadeout_time < 0 then
|
if fadeout_time < 0 then
|
||||||
fadeout_time = 0
|
fadeout_time = 0
|
||||||
fading_bgm = false
|
fading_bgm = false
|
||||||
end
|
end
|
||||||
current_bgm:setVolume(fadeout_time / total_fadeout_time)
|
current_bgm:setVolume(fadeout_time * config.bgm_volume / total_fadeout_time)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function pauseBGM()
|
function pauseBGM(f)
|
||||||
|
if f then
|
||||||
|
unfocused = true
|
||||||
|
end
|
||||||
if current_bgm ~= nil then
|
if current_bgm ~= nil then
|
||||||
current_bgm:pause()
|
current_bgm:pause()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function resumeBGM()
|
function resumeBGM(f)
|
||||||
|
if f and scene.paused and unfocused then
|
||||||
|
unfocused = false
|
||||||
|
return
|
||||||
|
end
|
||||||
if current_bgm ~= nil then
|
if current_bgm ~= nil then
|
||||||
current_bgm:play()
|
current_bgm:play()
|
||||||
end
|
end
|
||||||
|
|||||||
2
load/bigint.lua
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
bigint = require "libs.bigint.bigint"
|
||||||
|
number_names = require "libs.bigint.named-powers-of-ten"
|
||||||
@@ -20,10 +20,20 @@ backgrounds = {
|
|||||||
love.graphics.newImage("res/backgrounds/1800.png"),
|
love.graphics.newImage("res/backgrounds/1800.png"),
|
||||||
love.graphics.newImage("res/backgrounds/1900.png"),
|
love.graphics.newImage("res/backgrounds/1900.png"),
|
||||||
title = love.graphics.newImage("res/backgrounds/title.png"),
|
title = love.graphics.newImage("res/backgrounds/title.png"),
|
||||||
|
snow = love.graphics.newImage("res/backgrounds/snow.png"),
|
||||||
input_config = love.graphics.newImage("res/backgrounds/options-input.png"),
|
input_config = love.graphics.newImage("res/backgrounds/options-input.png"),
|
||||||
game_config = love.graphics.newImage("res/backgrounds/options-game.png"),
|
game_config = love.graphics.newImage("res/backgrounds/options-game.png"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- in order, the colors are:
|
||||||
|
-- red, orange, yellow, green, cyan, blue
|
||||||
|
-- magenta (or purple), white, black
|
||||||
|
-- the next three don't have colors tied to them
|
||||||
|
-- F is used for lock flash
|
||||||
|
-- A is a garbage block
|
||||||
|
-- X is an invisible "block"
|
||||||
|
-- don't use these for piece colors when making a ruleset
|
||||||
|
-- all the others are fine to use
|
||||||
blocks = {
|
blocks = {
|
||||||
["2tie"] = {
|
["2tie"] = {
|
||||||
R = love.graphics.newImage("res/img/s1.png"),
|
R = love.graphics.newImage("res/img/s1.png"),
|
||||||
@@ -33,6 +43,8 @@ blocks = {
|
|||||||
C = love.graphics.newImage("res/img/s2.png"),
|
C = love.graphics.newImage("res/img/s2.png"),
|
||||||
B = love.graphics.newImage("res/img/s4.png"),
|
B = love.graphics.newImage("res/img/s4.png"),
|
||||||
M = love.graphics.newImage("res/img/s5.png"),
|
M = love.graphics.newImage("res/img/s5.png"),
|
||||||
|
W = love.graphics.newImage("res/img/s9.png"),
|
||||||
|
D = love.graphics.newImage("res/img/s8.png"),
|
||||||
F = love.graphics.newImage("res/img/s9.png"),
|
F = love.graphics.newImage("res/img/s9.png"),
|
||||||
A = love.graphics.newImage("res/img/s8.png"),
|
A = love.graphics.newImage("res/img/s8.png"),
|
||||||
X = love.graphics.newImage("res/img/s9.png"),
|
X = love.graphics.newImage("res/img/s9.png"),
|
||||||
@@ -45,9 +57,31 @@ blocks = {
|
|||||||
C = love.graphics.newImage("res/img/bone.png"),
|
C = love.graphics.newImage("res/img/bone.png"),
|
||||||
B = love.graphics.newImage("res/img/bone.png"),
|
B = love.graphics.newImage("res/img/bone.png"),
|
||||||
M = love.graphics.newImage("res/img/bone.png"),
|
M = love.graphics.newImage("res/img/bone.png"),
|
||||||
|
W = love.graphics.newImage("res/img/bone.png"),
|
||||||
|
D = love.graphics.newImage("res/img/bone.png"),
|
||||||
F = love.graphics.newImage("res/img/bone.png"),
|
F = love.graphics.newImage("res/img/bone.png"),
|
||||||
A = love.graphics.newImage("res/img/bone.png"),
|
A = love.graphics.newImage("res/img/bone.png"),
|
||||||
X = love.graphics.newImage("res/img/bone.png"),
|
X = love.graphics.newImage("res/img/bone.png"),
|
||||||
|
},
|
||||||
|
["gem"] = {
|
||||||
|
R = love.graphics.newImage("res/img/gem1.png"),
|
||||||
|
O = love.graphics.newImage("res/img/gem3.png"),
|
||||||
|
Y = love.graphics.newImage("res/img/gem7.png"),
|
||||||
|
G = love.graphics.newImage("res/img/gem6.png"),
|
||||||
|
C = love.graphics.newImage("res/img/gem2.png"),
|
||||||
|
B = love.graphics.newImage("res/img/gem4.png"),
|
||||||
|
M = love.graphics.newImage("res/img/gem5.png"),
|
||||||
|
W = love.graphics.newImage("res/img/gem9.png"),
|
||||||
|
D = love.graphics.newImage("res/img/gem9.png"),
|
||||||
|
F = love.graphics.newImage("res/img/gem9.png"),
|
||||||
|
A = love.graphics.newImage("res/img/gem9.png"),
|
||||||
|
X = love.graphics.newImage("res/img/gem9.png"),
|
||||||
|
},
|
||||||
|
["square"] = {
|
||||||
|
W = love.graphics.newImage("res/img/squares.png"),
|
||||||
|
Y = love.graphics.newImage("res/img/squareg.png"),
|
||||||
|
F = love.graphics.newImage("res/img/squares.png"),
|
||||||
|
X = love.graphics.newImage("res/img/squares.png"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +103,7 @@ ColourSchemes = {
|
|||||||
Z = "R",
|
Z = "R",
|
||||||
O = "Y",
|
O = "Y",
|
||||||
T = "M",
|
T = "M",
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, blockset in pairs(blocks) do
|
for name, blockset in pairs(blocks) do
|
||||||
@@ -84,4 +118,5 @@ misc_graphics = {
|
|||||||
go = love.graphics.newImage("res/img/go.png"),
|
go = love.graphics.newImage("res/img/go.png"),
|
||||||
select_mode = love.graphics.newImage("res/img/select_mode.png"),
|
select_mode = love.graphics.newImage("res/img/select_mode.png"),
|
||||||
strike = love.graphics.newImage("res/img/strike.png"),
|
strike = love.graphics.newImage("res/img/strike.png"),
|
||||||
|
santa = love.graphics.newImage("res/img/santa.png")
|
||||||
}
|
}
|
||||||
@@ -6,19 +6,51 @@ function loadSave()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function loadFromFile(filename)
|
function loadFromFile(filename)
|
||||||
local save_data, len = binser.readFile(filename)
|
local file_data = love.filesystem.read(filename)
|
||||||
|
if file_data == nil then
|
||||||
|
return {} -- new object
|
||||||
|
end
|
||||||
|
local save_data = binser.deserialize(file_data)
|
||||||
if save_data == nil then
|
if save_data == nil then
|
||||||
return {} -- new object
|
return {} -- new object
|
||||||
end
|
end
|
||||||
return save_data[1]
|
return save_data[1]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function initConfig()
|
||||||
|
if not config.das then config.das = 10 end
|
||||||
|
if not config.arr then config.arr = 2 end
|
||||||
|
if not config.dcd then config.dcd = 0 end
|
||||||
|
if not config.sfx_volume then config.sfx_volume = 0.5 end
|
||||||
|
if not config.bgm_volume then config.bgm_volume = 0.5 end
|
||||||
|
|
||||||
|
if config.fullscreen == nil then config.fullscreen = false end
|
||||||
|
if config.secret == nil then config.secret = false end
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
function saveConfig()
|
function saveConfig()
|
||||||
binser.writeFile('config.sav', config)
|
love.filesystem.write(
|
||||||
|
'config.sav', binser.serialize(config)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
function saveHighscores()
|
function saveHighscores()
|
||||||
binser.writeFile('highscores.sav', highscores)
|
love.filesystem.write(
|
||||||
|
'highscores.sav', binser.serialize(highscores)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -22,36 +22,42 @@ sounds = {
|
|||||||
go = love.audio.newSource("res/se/go.wav", "static"),
|
go = love.audio.newSource("res/se/go.wav", "static"),
|
||||||
irs = love.audio.newSource("res/se/irs.wav", "static"),
|
irs = love.audio.newSource("res/se/irs.wav", "static"),
|
||||||
ihs = love.audio.newSource("res/se/ihs.wav", "static"),
|
ihs = love.audio.newSource("res/se/ihs.wav", "static"),
|
||||||
|
-- a secret sound!
|
||||||
|
welcome = love.audio.newSource("res/se/welcomeToCambridge.wav", "static"),
|
||||||
}
|
}
|
||||||
|
|
||||||
function playSE(sound, subsound)
|
function playSE(sound, subsound)
|
||||||
if subsound == nil then
|
if sound ~= nil then
|
||||||
sounds[sound]:setVolume(0.5)
|
if subsound ~= nil then
|
||||||
if sounds[sound]:isPlaying() then
|
sounds[sound][subsound]:setVolume(config.sfx_volume)
|
||||||
sounds[sound]:stop()
|
|
||||||
end
|
|
||||||
sounds[sound]:play()
|
|
||||||
else
|
|
||||||
sounds[sound][subsound]:setVolume(0.6)
|
|
||||||
if sounds[sound][subsound]:isPlaying() then
|
if sounds[sound][subsound]:isPlaying() then
|
||||||
sounds[sound][subsound]:stop()
|
sounds[sound][subsound]:stop()
|
||||||
end
|
end
|
||||||
sounds[sound][subsound]:play()
|
sounds[sound][subsound]:play()
|
||||||
|
else
|
||||||
|
sounds[sound]:setVolume(config.sfx_volume)
|
||||||
|
if sounds[sound]:isPlaying() then
|
||||||
|
sounds[sound]:stop()
|
||||||
|
end
|
||||||
|
sounds[sound]:play()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function playSEOnce(sound, subsound)
|
function playSEOnce(sound, subsound)
|
||||||
if subsound == nil then
|
if sound ~= nil then
|
||||||
sounds[sound]:setVolume(0.5)
|
if subsound ~= nil then
|
||||||
if sounds[sound]:isPlaying() then
|
sounds[sound][subsound]:setVolume(config.sfx_volume)
|
||||||
return
|
|
||||||
end
|
|
||||||
sounds[sound]:play()
|
|
||||||
else
|
|
||||||
sounds[sound][subsound]:setVolume(0.5)
|
|
||||||
if sounds[sound][subsound]:isPlaying() then
|
if sounds[sound][subsound]:isPlaying() then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
sounds[sound][subsound]:play()
|
sounds[sound][subsound]:play()
|
||||||
|
else
|
||||||
|
sounds[sound]:setVolume(config.sfx_volume)
|
||||||
|
if sounds[sound]:isPlaying() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
sounds[sound]:play()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
1
load/version.lua
Normal file
@@ -0,0 +1 @@
|
|||||||
|
version = "v0.3-beta6"
|
||||||
226
main.lua
@@ -7,45 +7,44 @@ function love.load()
|
|||||||
require "load.sounds"
|
require "load.sounds"
|
||||||
require "load.bgm"
|
require "load.bgm"
|
||||||
require "load.save"
|
require "load.save"
|
||||||
|
require "load.bigint"
|
||||||
|
require "load.version"
|
||||||
loadSave()
|
loadSave()
|
||||||
|
require "funcs"
|
||||||
require "scene"
|
require "scene"
|
||||||
config["side_next"] = false
|
|
||||||
config["reverse_rotate"] = true
|
--config["side_next"] = false
|
||||||
config["fullscreen"] = false
|
--config["reverse_rotate"] = true
|
||||||
|
--config["das_last_key"] = false
|
||||||
|
--config["fullscreen"] = false
|
||||||
|
|
||||||
love.window.setMode(love.graphics.getWidth(), love.graphics.getHeight(), {resizable = true});
|
love.window.setMode(love.graphics.getWidth(), love.graphics.getHeight(), {resizable = true});
|
||||||
|
|
||||||
if not config.gamesettings then
|
-- used for screenshots
|
||||||
config.gamesettings = {}
|
GLOBAL_CANVAS = love.graphics.newCanvas()
|
||||||
config["das_last_key"] = false
|
|
||||||
else
|
-- init config
|
||||||
config["das_last_key"] = config.gamesettings.das_last_key == 2
|
initConfig()
|
||||||
end
|
|
||||||
for _, option in ipairs(GameConfigScene.options) do
|
love.window.setFullscreen(config["fullscreen"])
|
||||||
if not config.gamesettings[option[1]] then
|
if config.secret then playSE("welcome") end
|
||||||
config.gamesettings[option[1]] = 1
|
|
||||||
end
|
-- import custom modules
|
||||||
end
|
initModules()
|
||||||
|
|
||||||
if not config.input then
|
|
||||||
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
|
end
|
||||||
|
|
||||||
|
function initModules()
|
||||||
game_modes = {}
|
game_modes = {}
|
||||||
mode_list = love.filesystem.getDirectoryItems("tetris/modes")
|
mode_list = love.filesystem.getDirectoryItems("tetris/modes")
|
||||||
for i=1,#mode_list do
|
for i=1,#mode_list do
|
||||||
if(mode_list[i] ~= "gamemode.lua" and mode_list[i] ~= "unrefactored_modes") then
|
if(mode_list[i] ~= "gamemode.lua" and string.sub(mode_list[i], -4) == ".lua") then
|
||||||
game_modes[#game_modes+1] = require ("tetris.modes."..string.sub(mode_list[i],1,-5))
|
game_modes[#game_modes+1] = require ("tetris.modes."..string.sub(mode_list[i],1,-5))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
rulesets = {}
|
rulesets = {}
|
||||||
rule_list = love.filesystem.getDirectoryItems("tetris/rulesets")
|
rule_list = love.filesystem.getDirectoryItems("tetris/rulesets")
|
||||||
for i=1,#rule_list do
|
for i=1,#rule_list do
|
||||||
if(rule_list[i] ~= "ruleset.lua" and rule_list[i] ~= "unrefactored_rulesets") then
|
if(rule_list[i] ~= "ruleset.lua" and string.sub(rule_list[i], -4) == ".lua") then
|
||||||
rulesets[#rulesets+1] = require ("tetris.rulesets."..string.sub(rule_list[i],1,-5))
|
rulesets[#rulesets+1] = require ("tetris.rulesets."..string.sub(rule_list[i],1,-5))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -55,48 +54,12 @@ function love.load()
|
|||||||
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end)
|
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end)
|
||||||
table.sort(rulesets, function(a,b)
|
table.sort(rulesets, function(a,b)
|
||||||
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end)
|
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end)
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
local TARGET_FPS = 60
|
|
||||||
local SAMPLE_SIZE = 60
|
|
||||||
|
|
||||||
local rolling_samples = {}
|
|
||||||
local rolling_total = 0
|
|
||||||
local average_n = 0
|
|
||||||
local frame = 0
|
|
||||||
|
|
||||||
function getSmoothedDt(dt)
|
|
||||||
rolling_total = rolling_total + dt
|
|
||||||
frame = frame + 1
|
|
||||||
if frame > SAMPLE_SIZE then frame = frame - SAMPLE_SIZE end
|
|
||||||
if average_n == SAMPLE_SIZE then
|
|
||||||
rolling_total = rolling_total - rolling_samples[frame]
|
|
||||||
else
|
|
||||||
average_n = average_n + 1
|
|
||||||
end
|
|
||||||
rolling_samples[frame] = dt
|
|
||||||
return rolling_total / average_n
|
|
||||||
end
|
|
||||||
|
|
||||||
local update_time = 0.52
|
|
||||||
|
|
||||||
function love.update(dt)
|
|
||||||
processBGMFadeout(dt)
|
|
||||||
local old_update_time = update_time
|
|
||||||
update_time = update_time + getSmoothedDt(dt) * TARGET_FPS
|
|
||||||
updates = 0
|
|
||||||
while (update_time >= 1.02) do
|
|
||||||
scene:update()
|
|
||||||
updates = updates + 1
|
|
||||||
update_time = update_time - 1
|
|
||||||
end
|
|
||||||
if math.abs(update_time - old_update_time) < 0.02 then
|
|
||||||
update_time = old_update_time
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.draw()
|
function love.draw()
|
||||||
|
love.graphics.setCanvas(GLOBAL_CANVAS)
|
||||||
|
love.graphics.clear()
|
||||||
|
|
||||||
love.graphics.push()
|
love.graphics.push()
|
||||||
|
|
||||||
-- get offset matrix
|
-- get offset matrix
|
||||||
@@ -112,15 +75,38 @@ function love.draw()
|
|||||||
|
|
||||||
scene:render()
|
scene:render()
|
||||||
love.graphics.pop()
|
love.graphics.pop()
|
||||||
|
|
||||||
|
love.graphics.setCanvas()
|
||||||
|
love.graphics.setColor(1,1,1,1)
|
||||||
|
love.graphics.draw(GLOBAL_CANVAS)
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.keypressed(key, scancode)
|
function love.keypressed(key, scancode)
|
||||||
-- global hotkeys
|
-- global hotkeys
|
||||||
if scancode == "f4" then
|
if scancode == "f11" then
|
||||||
config["fullscreen"] = not config["fullscreen"]
|
config["fullscreen"] = not config["fullscreen"]
|
||||||
|
saveConfig()
|
||||||
love.window.setFullscreen(config["fullscreen"])
|
love.window.setFullscreen(config["fullscreen"])
|
||||||
elseif scancode == "f2" and scene.title ~= "Input Config" and scene.title ~= "Game" then
|
elseif scancode == "f2" and scene.title ~= "Input Config" and scene.title ~= "Game" then
|
||||||
scene = InputConfigScene()
|
scene = InputConfigScene()
|
||||||
|
switchBGM(nil)
|
||||||
|
-- secret sound playing :eyes:
|
||||||
|
elseif scancode == "f8" and scene.title == "Title" then
|
||||||
|
config.secret = not config.secret
|
||||||
|
saveConfig()
|
||||||
|
scene.restart_message = true
|
||||||
|
if config.secret then playSE("mode_decide")
|
||||||
|
else playSE("erase") end
|
||||||
|
-- f12 is reserved for saving screenshots
|
||||||
|
elseif scancode == "f12" then
|
||||||
|
local ss_name = os.date("ss/%Y-%m-%d_%H-%M-%S.png")
|
||||||
|
local info = love.filesystem.getInfo("ss", "directory")
|
||||||
|
if not info then
|
||||||
|
love.filesystem.remove("ss")
|
||||||
|
love.filesystem.createDirectory("ss")
|
||||||
|
end
|
||||||
|
print("Saving screenshot as "..ss_name)
|
||||||
|
GLOBAL_CANVAS:newImageData():encode("png", ss_name)
|
||||||
-- function keys are reserved
|
-- function keys are reserved
|
||||||
elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f[1-9][0-9]+$") then
|
elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f[1-9][0-9]+$") then
|
||||||
return
|
return
|
||||||
@@ -191,13 +177,13 @@ function love.joystickaxis(joystick, axis, value)
|
|||||||
config.input.joysticks[joystick:getName()].axes and
|
config.input.joysticks[joystick:getName()].axes and
|
||||||
config.input.joysticks[joystick:getName()].axes[axis]
|
config.input.joysticks[joystick:getName()].axes[axis]
|
||||||
then
|
then
|
||||||
if math.abs(value) >= 0.5 then
|
if math.abs(value) >= 1 then
|
||||||
input_pressed = config.input.joysticks[joystick:getName()].axes[axis][value >= 0.5 and "positive" or "negative"]
|
input_pressed = config.input.joysticks[joystick:getName()].axes[axis][value >= 1 and "positive" or "negative"]
|
||||||
end
|
end
|
||||||
positive_released = config.input.joysticks[joystick:getName()].axes[axis].positive
|
positive_released = config.input.joysticks[joystick:getName()].axes[axis].positive
|
||||||
negative_released = config.input.joysticks[joystick:getName()].axes[axis].negative
|
negative_released = config.input.joysticks[joystick:getName()].axes[axis].negative
|
||||||
end
|
end
|
||||||
if math.abs(value) >= 0.5 then
|
if math.abs(value) >= 1 then
|
||||||
scene:onInputPress({input=input_pressed, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
|
scene:onInputPress({input=input_pressed, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
|
||||||
else
|
else
|
||||||
scene:onInputRelease({input=positive_released, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
|
scene:onInputRelease({input=positive_released, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
|
||||||
@@ -205,6 +191,14 @@ function love.joystickaxis(joystick, axis, value)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local last_hat_direction = ""
|
||||||
|
local directions = {
|
||||||
|
["u"] = "up",
|
||||||
|
["d"] = "down",
|
||||||
|
["l"] = "left",
|
||||||
|
["r"] = "right",
|
||||||
|
}
|
||||||
|
|
||||||
function love.joystickhat(joystick, hat, direction)
|
function love.joystickhat(joystick, hat, direction)
|
||||||
local input_pressed = nil
|
local input_pressed = nil
|
||||||
local has_hat = false
|
local has_hat = false
|
||||||
@@ -221,24 +215,116 @@ function love.joystickhat(joystick, hat, direction)
|
|||||||
has_hat = true
|
has_hat = true
|
||||||
end
|
end
|
||||||
if input_pressed then
|
if input_pressed then
|
||||||
scene:onInputPress({input=input_pressed, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
|
for i = 1, #direction do
|
||||||
|
local char = direction:sub(i, i)
|
||||||
|
local _, count = last_hat_direction:gsub(char, char)
|
||||||
|
if count == 0 then
|
||||||
|
scene:onInputPress({input=config.input.joysticks[joystick:getName()].hats[hat][char], type="joyhat", name=joystick:getName(), hat=hat, direction=char})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for i = 1, #last_hat_direction do
|
||||||
|
local char = last_hat_direction:sub(i, i)
|
||||||
|
local _, count = direction:gsub(char, char)
|
||||||
|
if count == 0 then
|
||||||
|
scene:onInputRelease({input=config.input.joysticks[joystick:getName()].hats[hat][char], type="joyhat", name=joystick:getName(), hat=hat, direction=char})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
last_hat_direction = direction
|
||||||
elseif has_hat then
|
elseif has_hat then
|
||||||
for i, direction in ipairs{"d", "l", "ld", "lu", "r", "rd", "ru", "u"} do
|
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})
|
scene:onInputRelease({input=config.input.joysticks[joystick:getName()].hats[hat][direction], type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
|
||||||
end
|
end
|
||||||
|
last_hat_direction = ""
|
||||||
elseif direction ~= "c" then
|
elseif direction ~= "c" then
|
||||||
scene:onInputPress({input=nil, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
|
for i = 1, #direction do
|
||||||
|
local char = direction:sub(i, i)
|
||||||
|
local _, count = last_hat_direction:gsub(char, char)
|
||||||
|
if count == 0 then
|
||||||
|
scene:onInputPress({input=directions[char], type="joyhat", name=joystick:getName(), hat=hat, direction=char})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for i = 1, #last_hat_direction do
|
||||||
|
local char = last_hat_direction:sub(i, i)
|
||||||
|
local _, count = direction:gsub(char, char)
|
||||||
|
if count == 0 then
|
||||||
|
scene:onInputRelease({input=directions[char], type="joyhat", name=joystick:getName(), hat=hat, direction=char})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
last_hat_direction = direction
|
||||||
else
|
else
|
||||||
for i, direction in ipairs{"d", "l", "ld", "lu", "r", "rd", "ru", "u"} do
|
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})
|
scene:onInputRelease({input=nil, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
|
||||||
end
|
end
|
||||||
|
last_hat_direction = ""
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function love.wheelmoved(x, y)
|
||||||
|
scene:onInputPress({input=nil, type="wheel", x=x, y=y})
|
||||||
|
end
|
||||||
|
|
||||||
function love.focus(f)
|
function love.focus(f)
|
||||||
if f then
|
if f then
|
||||||
resumeBGM()
|
resumeBGM(true)
|
||||||
else
|
else
|
||||||
pauseBGM()
|
pauseBGM(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function love.resize(w, h)
|
||||||
|
GLOBAL_CANVAS:release()
|
||||||
|
GLOBAL_CANVAS = love.graphics.newCanvas(w, h)
|
||||||
|
end
|
||||||
|
|
||||||
|
local TARGET_FPS = 60
|
||||||
|
|
||||||
|
function love.run()
|
||||||
|
if love.load then love.load(love.arg.parseGameArguments(arg), arg) end
|
||||||
|
|
||||||
|
if love.timer then love.timer.step() end
|
||||||
|
|
||||||
|
local dt = 0
|
||||||
|
|
||||||
|
local last_time = love.timer.getTime()
|
||||||
|
local time_accumulator = 0
|
||||||
|
return function()
|
||||||
|
if love.event then
|
||||||
|
love.event.pump()
|
||||||
|
for name, a,b,c,d,e,f in love.event.poll() do
|
||||||
|
if name == "quit" then
|
||||||
|
if not love.quit or not love.quit() then
|
||||||
|
return a or 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
love.handlers[name](a,b,c,d,e,f)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if love.timer then
|
||||||
|
processBGMFadeout(love.timer.step())
|
||||||
|
end
|
||||||
|
|
||||||
|
if scene and scene.update and love.timer then
|
||||||
|
scene:update()
|
||||||
|
|
||||||
|
local frame_duration = 1.0 / TARGET_FPS
|
||||||
|
if time_accumulator < frame_duration then
|
||||||
|
if love.graphics and love.graphics.isActive() and love.draw then
|
||||||
|
love.graphics.origin()
|
||||||
|
love.graphics.clear(love.graphics.getBackgroundColor())
|
||||||
|
love.draw()
|
||||||
|
love.graphics.present()
|
||||||
|
end
|
||||||
|
local end_time = last_time + frame_duration
|
||||||
|
local time = love.timer.getTime()
|
||||||
|
while time < end_time do
|
||||||
|
love.timer.sleep(0.001)
|
||||||
|
time = love.timer.getTime()
|
||||||
|
end
|
||||||
|
time_accumulator = time_accumulator + time - last_time
|
||||||
|
end
|
||||||
|
time_accumulator = time_accumulator - frame_duration
|
||||||
|
end
|
||||||
|
last_time = love.timer.getTime()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,2 +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
|
tar -a -c -f cambridge.zip libs load res scene tetris conf.lua main.lua scene.lua funcs.lua
|
||||||
rename cambridge.zip cambridge.love
|
rename cambridge.zip cambridge.love
|
||||||
4
release
@@ -2,8 +2,10 @@
|
|||||||
mkdir dist
|
mkdir dist
|
||||||
mkdir dist/windows
|
mkdir dist/windows
|
||||||
mkdir dist/win32
|
mkdir dist/win32
|
||||||
cp cambridge.love dist/
|
mkdir dist/other
|
||||||
cat dist/windows/love.exe cambridge.love > dist/windows/cambridge.exe
|
cat dist/windows/love.exe cambridge.love > dist/windows/cambridge.exe
|
||||||
zip dist/cambridge-windows.zip dist/windows/* SOURCES.md LICENSE.md
|
zip dist/cambridge-windows.zip dist/windows/* SOURCES.md LICENSE.md
|
||||||
cat dist/win32/love.exe cambridge.love > dist/win32/cambridge.exe
|
cat dist/win32/love.exe cambridge.love > dist/win32/cambridge.exe
|
||||||
zip dist/cambridge-win32.zip dist/win32/* SOURCES.md LICENSE.md
|
zip dist/cambridge-win32.zip dist/win32/* SOURCES.md LICENSE.md
|
||||||
|
cp cambridge.love dist/other/
|
||||||
|
zip dist/cambridge-other.zip cambridge.love libs/discord-rpc.* SOURCES.md LICENSE.md
|
||||||
10
release.bat
@@ -5,17 +5,23 @@ mkdir dist\windows
|
|||||||
mkdir dist\windows\libs
|
mkdir dist\windows\libs
|
||||||
mkdir dist\win32
|
mkdir dist\win32
|
||||||
mkdir dist\win32\libs
|
mkdir dist\win32\libs
|
||||||
|
mkdir dist\other
|
||||||
|
mkdir dist\other\libs
|
||||||
|
|
||||||
copy /b dist\windows\love.exe+cambridge.love dist\windows\cambridge.exe
|
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 /b dist\win32\love.exe+cambridge.love dist\win32\cambridge.exe
|
||||||
|
copy /b cambridge.love dist\other\cambridge.love
|
||||||
|
|
||||||
copy libs\discord-rpc.dll dist\windows\libs
|
copy libs\discord-rpc.dll dist\windows\libs
|
||||||
copy libs\discord-rpc.dll dist\win32\libs
|
copy libs\discord-rpc.dll dist\win32\libs
|
||||||
|
copy libs\discord-rpc.* dist\other\libs
|
||||||
|
|
||||||
copy SOURCES.md dist\windows
|
copy SOURCES.md dist\windows
|
||||||
copy LICENSE.md dist\windows
|
copy LICENSE.md dist\windows
|
||||||
copy SOURCES.md dist\win32
|
copy SOURCES.md dist\win32
|
||||||
copy LICENSE.md dist\win32
|
copy LICENSE.md dist\win32
|
||||||
|
copy SOURCES.md dist\other
|
||||||
|
copy LICENSE.md dist\other
|
||||||
|
|
||||||
cd dist\windows
|
cd dist\windows
|
||||||
tar -a -c -f ..\cambridge-windows.zip cambridge.exe *.dll libs *.md
|
tar -a -c -f ..\cambridge-windows.zip cambridge.exe *.dll libs *.md
|
||||||
@@ -24,3 +30,7 @@ cd ..\..
|
|||||||
cd dist\win32
|
cd dist\win32
|
||||||
tar -a -c -f ..\cambridge-win32.zip cambridge.exe *.dll libs *.md
|
tar -a -c -f ..\cambridge-win32.zip cambridge.exe *.dll libs *.md
|
||||||
cd ..\..
|
cd ..\..
|
||||||
|
|
||||||
|
cd dist\other
|
||||||
|
tar -a -c -f ..\cambridge-other.zip cambridge.love libs *.md
|
||||||
|
cd ..\..
|
||||||
BIN
res/backgrounds/snow.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.4 KiB |
BIN
res/img/bone.png
|
Before Width: | Height: | Size: 188 B After Width: | Height: | Size: 151 B |
|
Before Width: | Height: | Size: 229 B After Width: | Height: | Size: 151 B |
BIN
res/img/cambridge_icon.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 1.1 KiB |
BIN
res/img/gem1.png
Normal file
|
After Width: | Height: | Size: 462 B |
BIN
res/img/gem2.png
Normal file
|
After Width: | Height: | Size: 388 B |
BIN
res/img/gem3.png
Normal file
|
After Width: | Height: | Size: 445 B |
BIN
res/img/gem4.png
Normal file
|
After Width: | Height: | Size: 426 B |
BIN
res/img/gem5.png
Normal file
|
After Width: | Height: | Size: 376 B |
BIN
res/img/gem6.png
Normal file
|
After Width: | Height: | Size: 377 B |
BIN
res/img/gem7.png
Normal file
|
After Width: | Height: | Size: 399 B |
BIN
res/img/gem9.png
Normal file
|
After Width: | Height: | Size: 354 B |
BIN
res/img/santa.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
res/img/squareg.png
Normal file
|
After Width: | Height: | Size: 708 B |
BIN
res/img/squares.png
Normal file
|
After Width: | Height: | Size: 639 B |
BIN
res/se/welcomeToCambridge.wav
Normal file
@@ -11,6 +11,11 @@ function Scene:onInputRelease() end
|
|||||||
ExitScene = require "scene.exit"
|
ExitScene = require "scene.exit"
|
||||||
GameScene = require "scene.game"
|
GameScene = require "scene.game"
|
||||||
ModeSelectScene = require "scene.mode_select"
|
ModeSelectScene = require "scene.mode_select"
|
||||||
|
KeyConfigScene = require "scene.key_config"
|
||||||
|
StickConfigScene = require "scene.stick_config"
|
||||||
InputConfigScene = require "scene.input_config"
|
InputConfigScene = require "scene.input_config"
|
||||||
GameConfigScene = require "scene.game_config"
|
GameConfigScene = require "scene.game_config"
|
||||||
|
TuningScene = require "scene.tuning"
|
||||||
|
SettingsScene = require "scene.settings"
|
||||||
|
CreditsScene = require "scene.credits"
|
||||||
TitleScene = require "scene.title"
|
TitleScene = require "scene.title"
|
||||||
|
|||||||
84
scene/credits.lua
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
local CreditsScene = Scene:extend()
|
||||||
|
|
||||||
|
CreditsScene.title = "Credits"
|
||||||
|
|
||||||
|
function CreditsScene:new()
|
||||||
|
self.frames = 0
|
||||||
|
-- higher = slower
|
||||||
|
self.scroll_speed = 1.85
|
||||||
|
switchBGM("credit_roll", "gm3")
|
||||||
|
end
|
||||||
|
|
||||||
|
function CreditsScene:update()
|
||||||
|
if love.window.hasFocus() then
|
||||||
|
self.frames = self.frames + 1
|
||||||
|
end
|
||||||
|
if self.frames >= 2100 * self.scroll_speed then
|
||||||
|
playSE("mode_decide")
|
||||||
|
scene = TitleScene()
|
||||||
|
switchBGM(nil)
|
||||||
|
elseif self.frames == math.floor(1950 * self.scroll_speed) then
|
||||||
|
fadeoutBGM(2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function CreditsScene:render()
|
||||||
|
local offset = self.frames / self.scroll_speed
|
||||||
|
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
love.graphics.draw(
|
||||||
|
backgrounds[19],
|
||||||
|
0, 0, 0,
|
||||||
|
0.5, 0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
love.graphics.setFont(font_3x5_4)
|
||||||
|
love.graphics.print("Cambridge Credits", 320, 500 - offset)
|
||||||
|
love.graphics.print("THANK YOU\nFOR PLAYING!", 320, math.max(2030 - offset, 240))
|
||||||
|
|
||||||
|
love.graphics.setFont(font_3x5_3)
|
||||||
|
love.graphics.print("Game Developers", 320, 550 - offset)
|
||||||
|
love.graphics.print("Project Heads", 320, 640 - offset)
|
||||||
|
love.graphics.print("Notable Game Developers", 320, 730 - offset)
|
||||||
|
love.graphics.print("Special Thanks", 320, 1000 - offset)
|
||||||
|
love.graphics.print("- Milla", 320, math.max(2110 - offset, 320))
|
||||||
|
|
||||||
|
love.graphics.setFont(font_3x5_2)
|
||||||
|
love.graphics.print("Oshisaure\nJoe Zeng", 320, 590 - offset)
|
||||||
|
love.graphics.print("Mizu\nMarkGamed", 320, 680 - offset)
|
||||||
|
love.graphics.print(
|
||||||
|
"2Tie - TGMsim\nAxel Fox - Multimino\nDr Ocelot - Tetra Legends\n" ..
|
||||||
|
"Electra - ZTrix\nFelicity/nightmareci/kdex - Shiromino\n" ..
|
||||||
|
"Mine - Tetra Online\nMrZ - Techmino\nosk - TETR.IO\n" ..
|
||||||
|
"Phoenix Flare - Master of Blocks\nRayRay26 - Spirit Drop\n" ..
|
||||||
|
"Rin - Puzzle Trial\nsinefuse - stackfuse",
|
||||||
|
320, 770 - offset
|
||||||
|
)
|
||||||
|
love.graphics.print(
|
||||||
|
"321MrHaatz\nAdventium\nAgentBasey\nArchina\nAurora\n" ..
|
||||||
|
"Caithness\nCheez\ncolour_thief\nCommando\nCublex\n" ..
|
||||||
|
"CylinderKnot\neightsixfivezero\nEricICX\nGesomaru\n" ..
|
||||||
|
"gizmo4487\nJBroms\nKirby703\nKitaru\n" ..
|
||||||
|
"M1ssing0\nMattMayuga\nMyPasswordIsWeak\n" ..
|
||||||
|
"Nikki Karissa\noffwo\nOliver\nPineapple\npokemonfan1937\n" ..
|
||||||
|
"Pyra Neoxi\nRDST64\nRocketLanterns\nRustyFoxxo\n" ..
|
||||||
|
"saphie\nShelleloch\nSimon\nstratus\nSuper302\n" ..
|
||||||
|
"switchpalacecorner\nterpyderp\nTetrian22\nTetro48\nThatCookie\n" ..
|
||||||
|
"TimmSkiller\nTrixciel\nuser74003\nZaptorZap\nZircean\n" ..
|
||||||
|
"All other contributors and friends!\nThe Absolute PLUS Discord\n" ..
|
||||||
|
"Tetra Legends Discord\nTetra Online Discord\nMultimino Discord\n" ..
|
||||||
|
"Hard Drop Discord\nRusty's Systemspace\nCambridge Discord\n" ..
|
||||||
|
"And to you, the player!",
|
||||||
|
320, 1040 - offset
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function CreditsScene:onInputPress(e)
|
||||||
|
if e.input == "menu_decide" or e.scancode == "return" or
|
||||||
|
e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
|
||||||
|
scene = TitleScene()
|
||||||
|
switchBGM(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return CreditsScene
|
||||||
@@ -4,11 +4,12 @@ GameScene.title = "Game"
|
|||||||
|
|
||||||
require 'load.save'
|
require 'load.save'
|
||||||
|
|
||||||
function GameScene:new(game_mode, ruleset)
|
function GameScene:new(game_mode, ruleset, inputs)
|
||||||
self.retry_mode = game_mode
|
self.retry_mode = game_mode
|
||||||
self.retry_ruleset = ruleset
|
self.retry_ruleset = ruleset
|
||||||
self.game = game_mode()
|
self.secret_inputs = inputs
|
||||||
self.ruleset = ruleset()
|
self.game = game_mode(self.secret_inputs)
|
||||||
|
self.ruleset = ruleset(self.game)
|
||||||
self.game:initialize(self.ruleset)
|
self.game:initialize(self.ruleset)
|
||||||
self.inputs = {
|
self.inputs = {
|
||||||
left=false,
|
left=false,
|
||||||
@@ -22,6 +23,7 @@ function GameScene:new(game_mode, ruleset)
|
|||||||
rotate_180=false,
|
rotate_180=false,
|
||||||
hold=false,
|
hold=false,
|
||||||
}
|
}
|
||||||
|
self.paused = false
|
||||||
DiscordRPC:update({
|
DiscordRPC:update({
|
||||||
details = self.game.rpc_details,
|
details = self.game.rpc_details,
|
||||||
state = self.game.name,
|
state = self.game.name,
|
||||||
@@ -29,60 +31,43 @@ function GameScene:new(game_mode, ruleset)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function GameScene:update()
|
function GameScene:update()
|
||||||
if love.window.hasFocus() then
|
if love.window.hasFocus() and not self.paused then
|
||||||
local inputs = {}
|
local inputs = {}
|
||||||
for input, value in pairs(self.inputs) do
|
for input, value in pairs(self.inputs) do
|
||||||
inputs[input] = value
|
inputs[input] = value
|
||||||
end
|
end
|
||||||
self.game:update(inputs, self.ruleset)
|
self.game:update(inputs, self.ruleset)
|
||||||
end
|
|
||||||
|
|
||||||
self.game.grid:update()
|
self.game.grid:update()
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function GameScene:render()
|
function GameScene:render()
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
self.game:draw(self.paused)
|
||||||
love.graphics.draw(
|
|
||||||
backgrounds[self.game:getBackground()],
|
|
||||||
0, 0, 0,
|
|
||||||
0.5, 0.5
|
|
||||||
)
|
|
||||||
|
|
||||||
-- game frame
|
|
||||||
love.graphics.draw(misc_graphics["frame"], 48, 64)
|
|
||||||
love.graphics.setColor(0, 0, 0, 200)
|
|
||||||
love.graphics.rectangle("fill", 64, 80, 160, 320)
|
|
||||||
|
|
||||||
self.game:drawGrid()
|
|
||||||
self.game:drawPiece()
|
|
||||||
self.game:drawNextQueue(self.ruleset)
|
|
||||||
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
|
|
||||||
love.graphics.draw(misc_graphics["go"], 144 - 27, 240 - 14)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.game:drawCustom()
|
|
||||||
|
|
||||||
love.graphics.setFont(font_3x5_2)
|
|
||||||
if config.gamesettings.display_gamemode == 1 then
|
|
||||||
love.graphics.printf(self.game.name .. " - " .. self.ruleset.name, 0, 460, 640, "left")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameScene:onInputPress(e)
|
function GameScene:onInputPress(e)
|
||||||
if self.game.completed and (e.input == "menu_decide" or e.input == "menu_back" or e.input == "retry") then
|
if (
|
||||||
|
self.game.game_over or self.game.completed
|
||||||
|
) and (
|
||||||
|
e.input == "menu_decide" or
|
||||||
|
e.input == "menu_back" or
|
||||||
|
e.input == "retry"
|
||||||
|
) then
|
||||||
highscore_entry = self.game:getHighscoreData()
|
highscore_entry = self.game:getHighscoreData()
|
||||||
highscore_hash = self.game.hash .. "-" .. self.ruleset.hash
|
highscore_hash = self.game.hash .. "-" .. self.ruleset.hash
|
||||||
submitHighscore(highscore_hash, highscore_entry)
|
submitHighscore(highscore_hash, highscore_entry)
|
||||||
scene = e.input == "retry" and GameScene(self.retry_mode, self.retry_ruleset) or ModeSelectScene()
|
self.game:onExit()
|
||||||
|
scene = e.input == "retry" and GameScene(self.retry_mode, self.retry_ruleset, self.secret_inputs) or ModeSelectScene()
|
||||||
elseif e.input == "retry" then
|
elseif e.input == "retry" then
|
||||||
scene = GameScene(self.retry_mode, self.retry_ruleset)
|
switchBGM(nil)
|
||||||
|
self.game:onExit()
|
||||||
|
scene = GameScene(self.retry_mode, self.retry_ruleset, self.secret_inputs)
|
||||||
|
elseif e.input == "pause" and not (self.game.game_over or self.game.completed) then
|
||||||
|
self.paused = not self.paused
|
||||||
|
if self.paused then pauseBGM()
|
||||||
|
else resumeBGM() end
|
||||||
elseif e.input == "menu_back" then
|
elseif e.input == "menu_back" then
|
||||||
|
self.game:onExit()
|
||||||
scene = ModeSelectScene()
|
scene = ModeSelectScene()
|
||||||
elseif e.input and string.sub(e.input, 1, 5) ~= "menu_" then
|
elseif e.input and string.sub(e.input, 1, 5) ~= "menu_" then
|
||||||
self.inputs[e.input] = true
|
self.inputs[e.input] = true
|
||||||
|
|||||||
@@ -3,17 +3,23 @@ local ConfigScene = Scene:extend()
|
|||||||
ConfigScene.title = "Game Settings"
|
ConfigScene.title = "Game Settings"
|
||||||
|
|
||||||
require 'load.save'
|
require 'load.save'
|
||||||
|
require 'libs.simple-slider'
|
||||||
|
|
||||||
ConfigScene.options = {
|
ConfigScene.options = {
|
||||||
-- this serves as reference to what the options' values mean i guess?
|
-- this serves as reference to what the options' values mean i guess?
|
||||||
{"manlock", "Manual locking",{"Per ruleset","Per gamemode","Harddrop", "Softdrop"}},
|
-- Format: {name in config, displayed name, uses slider?, options OR slider name}
|
||||||
{"piece_colour", "Piece Colours", {"Per ruleset","Arika" ,"TTC"}},
|
{"manlock", "Manual Locking", false, {"Per ruleset", "Per gamemode", "Harddrop", "Softdrop"}},
|
||||||
{"world_reverse","A Button Rotation", {"Left" ,"Auto" ,"Right"}},
|
{"piece_colour", "Piece Colours", false, {"Per ruleset", "Arika", "TTC"}},
|
||||||
{"display_gamemode", "Display Gamemode", {"On", "Off"}},
|
{"world_reverse", "A Button Rotation", false, {"Left", "Auto", "Right"}},
|
||||||
{"next_se", "Next Piece SFX", {"On", "Off"}},
|
{"spawn_positions", "Spawn Positions", false, {"Per ruleset", "In field", "Out of field"}},
|
||||||
{"das_last_key", "DAS Switch", {"Default", "Instant"}},
|
{"display_gamemode", "Display Gamemode", false, {"On", "Off"}},
|
||||||
{"synchroes_allowed", "Synchroes", {"Per ruleset", "On", "Off"}},
|
{"das_last_key", "DAS Last Key", false, {"Off", "On"}},
|
||||||
{"diagonal_input", "Diagonal Input", {"On", "Off"}}
|
{"smooth_movement", "Smooth Piece Drop", false, {"On", "Off"}},
|
||||||
|
{"synchroes_allowed", "Synchroes", false, {"Per ruleset", "On", "Off"}},
|
||||||
|
{"diagonal_input", "Diagonal Input", false, {"On", "Off"}},
|
||||||
|
{"buffer_lock", "Buffer Drop Type", false, {"Off", "Hold", "Tap"}},
|
||||||
|
{"sfx_volume", "SFX", true, "sfxSlider"},
|
||||||
|
{"bgm_volume", "BGM", true, "bgmSlider"},
|
||||||
}
|
}
|
||||||
local optioncount = #ConfigScene.options
|
local optioncount = #ConfigScene.options
|
||||||
|
|
||||||
@@ -26,10 +32,14 @@ function ConfigScene:new()
|
|||||||
details = "In menus",
|
details = "In menus",
|
||||||
state = "Changing game settings",
|
state = "Changing game settings",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
self.sfxSlider = newSlider(165, 400, 225, config.sfx_volume * 100, 0, 100, function(v) config.sfx_volume = v / 100 end, {width=20, knob="circle", track="roundrect"})
|
||||||
|
self.bgmSlider = newSlider(465, 400, 225, config.bgm_volume * 100, 0, 100, function(v) config.bgm_volume = v / 100 end, {width=20, knob="circle", track="roundrect"})
|
||||||
end
|
end
|
||||||
|
|
||||||
function ConfigScene:update()
|
function ConfigScene:update()
|
||||||
config["das_last_key"] = config.gamesettings.das_last_key == 2
|
self.sfxSlider:update()
|
||||||
|
self.bgmSlider:update()
|
||||||
end
|
end
|
||||||
|
|
||||||
function ConfigScene:render()
|
function ConfigScene:render()
|
||||||
@@ -43,25 +53,41 @@ function ConfigScene:render()
|
|||||||
love.graphics.setFont(font_3x5_4)
|
love.graphics.setFont(font_3x5_4)
|
||||||
love.graphics.print("GAME SETTINGS", 80, 40)
|
love.graphics.print("GAME SETTINGS", 80, 40)
|
||||||
|
|
||||||
|
--Lazy check to see if we're on the SFX or BGM slider. Probably will need to be rewritten if more options get added.
|
||||||
love.graphics.setColor(1, 1, 1, 0.5)
|
love.graphics.setColor(1, 1, 1, 0.5)
|
||||||
love.graphics.rectangle("fill", 20, 98 + self.highlight * 20, 170, 22)
|
if not ConfigScene.options[self.highlight][3] then
|
||||||
|
love.graphics.rectangle("fill", 25, 98 + self.highlight * 20, 170, 22)
|
||||||
|
else
|
||||||
|
love.graphics.rectangle("fill", 65 + (1+self.highlight-#self.options) * 300, 342, 215, 33)
|
||||||
|
end
|
||||||
|
|
||||||
love.graphics.setFont(font_3x5_2)
|
love.graphics.setFont(font_3x5_2)
|
||||||
for i, option in ipairs(ConfigScene.options) do
|
for i, option in ipairs(ConfigScene.options) do
|
||||||
|
if not option[3] then
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
love.graphics.printf(option[2], 40, 100 + i * 20, 150, "left")
|
love.graphics.printf(option[2], 40, 100 + i * 20, 150, "left")
|
||||||
for j, setting in ipairs(option[3]) do
|
for j, setting in ipairs(option[4]) do
|
||||||
love.graphics.setColor(1, 1, 1, config.gamesettings[option[1]] == j and 1 or 0.5)
|
love.graphics.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")
|
love.graphics.printf(setting, 100 + 110 * j, 100 + i * 20, 100, "center")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
love.graphics.setFont(font_3x5_3)
|
||||||
|
love.graphics.print("SFX Volume: " .. math.floor(self.sfxSlider:getValue()) .. "%", 75, 345)
|
||||||
|
love.graphics.print("BGM Volume: " .. math.floor(self.bgmSlider:getValue()) .. "%", 375, 345)
|
||||||
|
|
||||||
|
love.graphics.setColor(1, 1, 1, 0.75)
|
||||||
|
self.sfxSlider:draw()
|
||||||
|
self.bgmSlider:draw()
|
||||||
|
end
|
||||||
|
|
||||||
function ConfigScene:onInputPress(e)
|
function ConfigScene:onInputPress(e)
|
||||||
if e.input == "menu_decide" or e.scancode == "return" then
|
if e.input == "menu_decide" or e.scancode == "return" then
|
||||||
playSE("mode_decide")
|
playSE("mode_decide")
|
||||||
saveConfig()
|
saveConfig()
|
||||||
scene = TitleScene()
|
scene = SettingsScene()
|
||||||
elseif e.input == "up" or e.scancode == "up" then
|
elseif e.input == "up" or e.scancode == "up" then
|
||||||
playSE("cursor")
|
playSE("cursor")
|
||||||
self.highlight = Mod1(self.highlight-1, optioncount)
|
self.highlight = Mod1(self.highlight-1, optioncount)
|
||||||
@@ -69,16 +95,30 @@ function ConfigScene:onInputPress(e)
|
|||||||
playSE("cursor")
|
playSE("cursor")
|
||||||
self.highlight = Mod1(self.highlight+1, optioncount)
|
self.highlight = Mod1(self.highlight+1, optioncount)
|
||||||
elseif e.input == "left" or e.scancode == "left" then
|
elseif e.input == "left" or e.scancode == "left" then
|
||||||
|
if not self.options[self.highlight][3] then
|
||||||
playSE("cursor_lr")
|
playSE("cursor_lr")
|
||||||
local option = ConfigScene.options[self.highlight]
|
local option = ConfigScene.options[self.highlight]
|
||||||
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]-1, #option[3])
|
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]-1, #option[4])
|
||||||
|
else
|
||||||
|
local sld = self[self.options[self.highlight][4]]
|
||||||
|
sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() - 5) / (sld.max - sld.min)))
|
||||||
|
sld:update()
|
||||||
|
playSE("cursor")
|
||||||
|
end
|
||||||
elseif e.input == "right" or e.scancode == "right" then
|
elseif e.input == "right" or e.scancode == "right" then
|
||||||
|
if not self.options[self.highlight][3] then
|
||||||
playSE("cursor_lr")
|
playSE("cursor_lr")
|
||||||
local option = ConfigScene.options[self.highlight]
|
local option = ConfigScene.options[self.highlight]
|
||||||
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]+1, #option[3])
|
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]+1, #option[4])
|
||||||
|
else
|
||||||
|
sld = self[self.options[self.highlight][4]]
|
||||||
|
sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() + 5) / (sld.max - sld.min)))
|
||||||
|
sld:update()
|
||||||
|
playSE("cursor")
|
||||||
|
end
|
||||||
elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
|
elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
|
||||||
loadSave()
|
loadSave()
|
||||||
scene = TitleScene()
|
scene = SettingsScene()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -2,46 +2,20 @@ local ConfigScene = Scene:extend()
|
|||||||
|
|
||||||
ConfigScene.title = "Input Config"
|
ConfigScene.title = "Input Config"
|
||||||
|
|
||||||
require 'load.save'
|
local menu_screens = {
|
||||||
|
KeyConfigScene,
|
||||||
local configurable_inputs = {
|
StickConfigScene
|
||||||
"menu_decide",
|
|
||||||
"menu_back",
|
|
||||||
"left",
|
|
||||||
"right",
|
|
||||||
"up",
|
|
||||||
"down",
|
|
||||||
"rotate_left",
|
|
||||||
"rotate_left2",
|
|
||||||
"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()
|
function ConfigScene:new()
|
||||||
self.input_state = 1
|
self.menu_state = 1
|
||||||
self.set_inputs = newSetInputs()
|
|
||||||
self.new_input = {}
|
|
||||||
self.axis_timer = 0
|
|
||||||
|
|
||||||
DiscordRPC:update({
|
DiscordRPC:update({
|
||||||
details = "In menus",
|
details = "In menus",
|
||||||
state = "Changing input config",
|
state = "Changing input config",
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
function ConfigScene:update()
|
function ConfigScene:update() end
|
||||||
end
|
|
||||||
|
|
||||||
function ConfigScene:render()
|
function ConfigScene:render()
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
@@ -51,111 +25,41 @@ function ConfigScene:render()
|
|||||||
0.5, 0.5
|
0.5, 0.5
|
||||||
)
|
)
|
||||||
|
|
||||||
|
love.graphics.setFont(font_3x5_4)
|
||||||
|
love.graphics.print("INPUT CONFIG", 80, 40)
|
||||||
|
|
||||||
love.graphics.setFont(font_3x5_2)
|
love.graphics.setFont(font_3x5_2)
|
||||||
for i, input in ipairs(configurable_inputs) do
|
love.graphics.print("Which controls do you want to configure?", 80, 90)
|
||||||
love.graphics.printf(input, 40, 50 + i * 20, 200, "left")
|
|
||||||
if self.set_inputs[input] then
|
love.graphics.setColor(1, 1, 1, 0.5)
|
||||||
love.graphics.printf(self.set_inputs[input], 240, 50 + i * 20, 300, "left")
|
love.graphics.rectangle("fill", 75, 118 + 50 * self.menu_state, 200, 33)
|
||||||
|
|
||||||
|
love.graphics.setFont(font_3x5_3)
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
for i, screen in pairs(menu_screens) do
|
||||||
|
love.graphics.printf(screen.title, 80, 120 + 50 * i, 200, "left")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if self.input_state > table.getn(configurable_inputs) then
|
|
||||||
love.graphics.print("press enter to confirm, delete/backspace to retry" .. (config.input and ", escape to cancel" or ""))
|
|
||||||
else
|
|
||||||
love.graphics.print("press key or joystick input for " .. configurable_inputs[self.input_state] .. ", tab to skip" .. (config.input and ", escape to cancel" or ""), 0, 0)
|
|
||||||
love.graphics.print("function keys (F1, F2, etc.), escape, and tab can't be changed", 0, 20)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.axis_timer = self.axis_timer + 1
|
function ConfigScene:changeOption(rel)
|
||||||
end
|
local len = table.getn(menu_screens)
|
||||||
|
self.menu_state = (self.menu_state + len + rel - 1) % len + 1
|
||||||
local function addJoystick(input, name)
|
|
||||||
if not input.joysticks then
|
|
||||||
input.joysticks = {}
|
|
||||||
end
|
|
||||||
if not input.joysticks[name] then
|
|
||||||
input.joysticks[name] = {}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function ConfigScene:onInputPress(e)
|
function ConfigScene:onInputPress(e)
|
||||||
if e.type == "key" then
|
if e.input == "menu_decide" or e.scancode == "return" then
|
||||||
-- function keys, escape, and tab are reserved and can't be remapped
|
playSE("main_decide")
|
||||||
if e.scancode == "escape" and config.input then
|
scene = menu_screens[self.menu_state]()
|
||||||
-- cancel only if there was an input config already
|
elseif e.input == "up" or e.scancode == "up" then
|
||||||
scene = TitleScene()
|
self:changeOption(-1)
|
||||||
elseif self.input_state > table.getn(configurable_inputs) then
|
playSE("cursor")
|
||||||
if e.scancode == "return" then
|
elseif e.input == "down" or e.scancode == "down" then
|
||||||
-- save new input, then load next scene
|
self:changeOption(1)
|
||||||
config.input = self.new_input
|
playSE("cursor")
|
||||||
saveConfig()
|
elseif config.input and (
|
||||||
scene = TitleScene()
|
e.input == "menu_back" or e.scancode == "backspace" or e.scancode == "delete"
|
||||||
elseif e.scancode == "delete" or e.scancode == "backspace" then
|
) then
|
||||||
-- retry
|
scene = SettingsScene()
|
||||||
self.input_state = 1
|
|
||||||
self.set_inputs = newSetInputs()
|
|
||||||
self.new_input = {}
|
|
||||||
end
|
|
||||||
elseif e.scancode == "tab" then
|
|
||||||
self.set_inputs[configurable_inputs[self.input_state]] = "skipped"
|
|
||||||
self.input_state = self.input_state + 1
|
|
||||||
elseif e.scancode ~= "escape" then
|
|
||||||
-- all other keys can be configured
|
|
||||||
if not self.new_input.keys then
|
|
||||||
self.new_input.keys = {}
|
|
||||||
end
|
|
||||||
self.set_inputs[configurable_inputs[self.input_state]] = "key " .. love.keyboard.getKeyFromScancode(e.scancode) .. " (" .. e.scancode .. ")"
|
|
||||||
self.new_input.keys[e.scancode] = configurable_inputs[self.input_state]
|
|
||||||
self.input_state = self.input_state + 1
|
|
||||||
end
|
|
||||||
elseif string.sub(e.type, 1, 3) == "joy" then
|
|
||||||
if self.input_state <= table.getn(configurable_inputs) then
|
|
||||||
if e.type == "joybutton" then
|
|
||||||
addJoystick(self.new_input, e.name)
|
|
||||||
if not self.new_input.joysticks[e.name].buttons then
|
|
||||||
self.new_input.joysticks[e.name].buttons = {}
|
|
||||||
end
|
|
||||||
self.set_inputs[configurable_inputs[self.input_state]] =
|
|
||||||
"jbtn " ..
|
|
||||||
e.button ..
|
|
||||||
" " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
|
|
||||||
self.new_input.joysticks[e.name].buttons[e.button] = configurable_inputs[self.input_state]
|
|
||||||
self.input_state = self.input_state + 1
|
|
||||||
elseif e.type == "joyaxis" then
|
|
||||||
if (e.axis ~= self.last_axis or self.axis_timer > 30) and math.abs(e.value) >= 1 then
|
|
||||||
addJoystick(self.new_input, e.name)
|
|
||||||
if not self.new_input.joysticks[e.name].axes then
|
|
||||||
self.new_input.joysticks[e.name].axes = {}
|
|
||||||
end
|
|
||||||
if not self.new_input.joysticks[e.name].axes[e.axis] then
|
|
||||||
self.new_input.joysticks[e.name].axes[e.axis] = {}
|
|
||||||
end
|
|
||||||
self.set_inputs[configurable_inputs[self.input_state]] =
|
|
||||||
"jaxis " ..
|
|
||||||
(e.value >= 1 and "+" or "-") .. e.axis ..
|
|
||||||
" " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
|
|
||||||
self.new_input.joysticks[e.name].axes[e.axis][e.value >= 1 and "positive" or "negative"] = configurable_inputs[self.input_state]
|
|
||||||
self.input_state = self.input_state + 1
|
|
||||||
self.last_axis = e.axis
|
|
||||||
self.axis_timer = 0
|
|
||||||
end
|
|
||||||
elseif e.type == "joyhat" then
|
|
||||||
if e.direction ~= "c" then
|
|
||||||
addJoystick(self.new_input, e.name)
|
|
||||||
if not self.new_input.joysticks[e.name].hats then
|
|
||||||
self.new_input.joysticks[e.name].hats = {}
|
|
||||||
end
|
|
||||||
if not self.new_input.joysticks[e.name].hats[e.hat] then
|
|
||||||
self.new_input.joysticks[e.name].hats[e.hat] = {}
|
|
||||||
end
|
|
||||||
self.set_inputs[configurable_inputs[self.input_state]] =
|
|
||||||
"jhat " ..
|
|
||||||
e.hat .. " " .. e.direction ..
|
|
||||||
" " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
|
|
||||||
self.new_input.joysticks[e.name].hats[e.hat][e.direction] = configurable_inputs[self.input_state]
|
|
||||||
self.input_state = self.input_state + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
100
scene/key_config.lua
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
local KeyConfigScene = Scene:extend()
|
||||||
|
|
||||||
|
KeyConfigScene.title = "Key Config"
|
||||||
|
|
||||||
|
require 'load.save'
|
||||||
|
|
||||||
|
local configurable_inputs = {
|
||||||
|
"menu_decide",
|
||||||
|
"menu_back",
|
||||||
|
"left",
|
||||||
|
"right",
|
||||||
|
"up",
|
||||||
|
"down",
|
||||||
|
"rotate_left",
|
||||||
|
"rotate_left2",
|
||||||
|
"rotate_right",
|
||||||
|
"rotate_right2",
|
||||||
|
"rotate_180",
|
||||||
|
"hold",
|
||||||
|
"retry",
|
||||||
|
"pause",
|
||||||
|
}
|
||||||
|
|
||||||
|
local function newSetInputs()
|
||||||
|
local set_inputs = {}
|
||||||
|
for i, input in ipairs(configurable_inputs) do
|
||||||
|
set_inputs[input] = false
|
||||||
|
end
|
||||||
|
return set_inputs
|
||||||
|
end
|
||||||
|
|
||||||
|
function KeyConfigScene:new()
|
||||||
|
self.input_state = 1
|
||||||
|
self.set_inputs = newSetInputs()
|
||||||
|
self.new_input = {}
|
||||||
|
|
||||||
|
DiscordRPC:update({
|
||||||
|
details = "In menus",
|
||||||
|
state = "Changing key config",
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function KeyConfigScene:update()
|
||||||
|
end
|
||||||
|
|
||||||
|
function KeyConfigScene: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 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/backspace to retry" .. (config.input and ", escape to cancel" or ""))
|
||||||
|
else
|
||||||
|
love.graphics.print("press key input for " .. configurable_inputs[self.input_state] .. ", tab to skip, escape to cancel", 0, 0)
|
||||||
|
love.graphics.print("function keys (F1, F2, etc.), escape, and tab can't be changed", 0, 20)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function KeyConfigScene:onInputPress(e)
|
||||||
|
if e.type == "key" then
|
||||||
|
-- function keys, escape, and tab are reserved and can't be remapped
|
||||||
|
if e.scancode == "escape" then
|
||||||
|
scene = InputConfigScene()
|
||||||
|
elseif self.input_state > table.getn(configurable_inputs) then
|
||||||
|
if e.scancode == "return" then
|
||||||
|
-- save new input, then load next scene
|
||||||
|
local had_config = config.input ~= nil
|
||||||
|
if not config.input then config.input = {} end
|
||||||
|
config.input.keys = self.new_input
|
||||||
|
saveConfig()
|
||||||
|
scene = had_config and InputConfigScene() or 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" and not self.new_input[e.scancode] then
|
||||||
|
-- all other keys can be configured
|
||||||
|
self.set_inputs[configurable_inputs[self.input_state]] = "key " .. love.keyboard.getKeyFromScancode(e.scancode) .. " (" .. e.scancode .. ")"
|
||||||
|
self.new_input[e.scancode] = configurable_inputs[self.input_state]
|
||||||
|
self.input_state = self.input_state + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return KeyConfigScene
|
||||||
@@ -6,11 +6,36 @@ current_mode = 1
|
|||||||
current_ruleset = 1
|
current_ruleset = 1
|
||||||
|
|
||||||
function ModeSelectScene:new()
|
function ModeSelectScene:new()
|
||||||
|
-- reload custom modules
|
||||||
|
initModules()
|
||||||
|
if table.getn(game_modes) == 0 or table.getn(rulesets) == 0 then
|
||||||
|
self.display_warning = true
|
||||||
|
current_mode = 1
|
||||||
|
current_ruleset = 1
|
||||||
|
else
|
||||||
|
self.display_warning = false
|
||||||
|
if current_mode > table.getn(game_modes) then
|
||||||
|
current_mode = 1
|
||||||
|
end
|
||||||
|
if current_ruleset > table.getn(rulesets) then
|
||||||
|
current_ruleset = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
self.menu_state = {
|
self.menu_state = {
|
||||||
mode = current_mode,
|
mode = current_mode,
|
||||||
ruleset = current_ruleset,
|
ruleset = current_ruleset,
|
||||||
select = "mode",
|
select = "mode",
|
||||||
}
|
}
|
||||||
|
self.secret_inputs = {
|
||||||
|
rotate_left = false,
|
||||||
|
rotate_left2 = false,
|
||||||
|
rotate_right = false,
|
||||||
|
rotate_right2 = false,
|
||||||
|
rotate_180 = false,
|
||||||
|
hold = false,
|
||||||
|
}
|
||||||
|
self.das = 0
|
||||||
DiscordRPC:update({
|
DiscordRPC:update({
|
||||||
details = "In menus",
|
details = "In menus",
|
||||||
state = "Choosing a mode",
|
state = "Choosing a mode",
|
||||||
@@ -18,6 +43,18 @@ function ModeSelectScene:new()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ModeSelectScene:update()
|
function ModeSelectScene:update()
|
||||||
|
switchBGM(nil) -- experimental
|
||||||
|
|
||||||
|
if self.das_up or self.das_down then
|
||||||
|
self.das = self.das + 1
|
||||||
|
else
|
||||||
|
self.das = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.das >= 15 then
|
||||||
|
self:changeOption(self.das_up and -1 or 1)
|
||||||
|
self.das = self.das - 4
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function ModeSelectScene:render()
|
function ModeSelectScene:render()
|
||||||
@@ -27,6 +64,23 @@ function ModeSelectScene:render()
|
|||||||
0.5, 0.5
|
0.5, 0.5
|
||||||
)
|
)
|
||||||
|
|
||||||
|
love.graphics.draw(misc_graphics["select_mode"], 20, 40)
|
||||||
|
|
||||||
|
if self.display_warning then
|
||||||
|
love.graphics.setFont(font_3x5_3)
|
||||||
|
love.graphics.printf(
|
||||||
|
"You have no modes or rulesets.",
|
||||||
|
80, 200, 480, "center"
|
||||||
|
)
|
||||||
|
love.graphics.setFont(font_3x5_2)
|
||||||
|
love.graphics.printf(
|
||||||
|
"Come back to this menu after getting more modes or rulesets. " ..
|
||||||
|
"Press any button to return to the main menu.",
|
||||||
|
80, 250, 480, "center"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
if self.menu_state.select == "mode" then
|
if self.menu_state.select == "mode" then
|
||||||
love.graphics.setColor(1, 1, 1, 0.5)
|
love.graphics.setColor(1, 1, 1, 0.5)
|
||||||
elseif self.menu_state.select == "ruleset" then
|
elseif self.menu_state.select == "ruleset" then
|
||||||
@@ -43,8 +97,6 @@ function ModeSelectScene:render()
|
|||||||
|
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
|
||||||
love.graphics.draw(misc_graphics["select_mode"], 20, 40)
|
|
||||||
|
|
||||||
love.graphics.setFont(font_3x5_2)
|
love.graphics.setFont(font_3x5_2)
|
||||||
for idx, mode in pairs(game_modes) do
|
for idx, mode in pairs(game_modes) do
|
||||||
if(idx >= self.menu_state.mode-9 and idx <= self.menu_state.mode+9) then
|
if(idx >= self.menu_state.mode-9 and idx <= self.menu_state.mode+9) then
|
||||||
@@ -59,25 +111,51 @@ function ModeSelectScene:render()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ModeSelectScene:onInputPress(e)
|
function ModeSelectScene:onInputPress(e)
|
||||||
if e.input == "menu_decide" or e.scancode == "return" then
|
if self.display_warning and e.input then
|
||||||
|
scene = TitleScene()
|
||||||
|
elseif e.type == "wheel" then
|
||||||
|
if e.x % 2 == 1 then
|
||||||
|
self:switchSelect()
|
||||||
|
end
|
||||||
|
if e.y ~= 0 then
|
||||||
|
self:changeOption(-e.y)
|
||||||
|
end
|
||||||
|
elseif e.input == "menu_decide" or e.scancode == "return" then
|
||||||
current_mode = self.menu_state.mode
|
current_mode = self.menu_state.mode
|
||||||
current_ruleset = self.menu_state.ruleset
|
current_ruleset = self.menu_state.ruleset
|
||||||
config.current_mode = current_mode
|
config.current_mode = current_mode
|
||||||
config.current_ruleset = current_ruleset
|
config.current_ruleset = current_ruleset
|
||||||
playSE("mode_decide")
|
playSE("mode_decide")
|
||||||
saveConfig()
|
saveConfig()
|
||||||
scene = GameScene(game_modes[self.menu_state.mode], rulesets[self.menu_state.ruleset])
|
scene = GameScene(
|
||||||
|
game_modes[self.menu_state.mode],
|
||||||
|
rulesets[self.menu_state.ruleset],
|
||||||
|
self.secret_inputs
|
||||||
|
)
|
||||||
elseif e.input == "up" or e.scancode == "up" then
|
elseif e.input == "up" or e.scancode == "up" then
|
||||||
self:changeOption(-1)
|
self:changeOption(-1)
|
||||||
playSE("cursor")
|
self.das_up = true
|
||||||
|
self.das_down = nil
|
||||||
elseif e.input == "down" or e.scancode == "down" then
|
elseif e.input == "down" or e.scancode == "down" then
|
||||||
self:changeOption(1)
|
self:changeOption(1)
|
||||||
playSE("cursor")
|
self.das_down = true
|
||||||
|
self.das_up = nil
|
||||||
elseif e.input == "left" or e.input == "right" or e.scancode == "left" or e.scancode == "right" then
|
elseif e.input == "left" or e.input == "right" or e.scancode == "left" or e.scancode == "right" then
|
||||||
self:switchSelect()
|
self:switchSelect()
|
||||||
playSE("cursor_lr")
|
|
||||||
elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
|
elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
|
||||||
scene = TitleScene()
|
scene = TitleScene()
|
||||||
|
elseif e.input then
|
||||||
|
self.secret_inputs[e.input] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ModeSelectScene:onInputRelease(e)
|
||||||
|
if e.input == "hold" or (e.input and string.sub(e.input, 1, 7) == "rotate_") then
|
||||||
|
self.secret_inputs[e.input] = false
|
||||||
|
elseif e.input == "up" or e.scancode == "up" then
|
||||||
|
self.das_up = nil
|
||||||
|
elseif e.input == "down" or e.scancode == "down" then
|
||||||
|
self.das_down = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -87,24 +165,26 @@ function ModeSelectScene:changeOption(rel)
|
|||||||
elseif self.menu_state.select == "ruleset" then
|
elseif self.menu_state.select == "ruleset" then
|
||||||
self:changeRuleset(rel)
|
self:changeRuleset(rel)
|
||||||
end
|
end
|
||||||
|
playSE("cursor")
|
||||||
end
|
end
|
||||||
|
|
||||||
function ModeSelectScene:switchSelect(rel)
|
function ModeSelectScene:switchSelect()
|
||||||
if self.menu_state.select == "mode" then
|
if self.menu_state.select == "mode" then
|
||||||
self.menu_state.select = "ruleset"
|
self.menu_state.select = "ruleset"
|
||||||
elseif self.menu_state.select == "ruleset" then
|
elseif self.menu_state.select == "ruleset" then
|
||||||
self.menu_state.select = "mode"
|
self.menu_state.select = "mode"
|
||||||
end
|
end
|
||||||
|
playSE("cursor_lr")
|
||||||
end
|
end
|
||||||
|
|
||||||
function ModeSelectScene:changeMode(rel)
|
function ModeSelectScene:changeMode(rel)
|
||||||
local len = table.getn(game_modes)
|
local len = table.getn(game_modes)
|
||||||
self.menu_state.mode = (self.menu_state.mode + len + rel - 1) % len + 1
|
self.menu_state.mode = Mod1(self.menu_state.mode + rel, len)
|
||||||
end
|
end
|
||||||
|
|
||||||
function ModeSelectScene:changeRuleset(rel)
|
function ModeSelectScene:changeRuleset(rel)
|
||||||
local len = table.getn(rulesets)
|
local len = table.getn(rulesets)
|
||||||
self.menu_state.ruleset = (self.menu_state.ruleset + len + rel - 1) % len + 1
|
self.menu_state.ruleset = Mod1(self.menu_state.ruleset + rel, len)
|
||||||
end
|
end
|
||||||
|
|
||||||
return ModeSelectScene
|
return ModeSelectScene
|
||||||
|
|||||||
65
scene/settings.lua
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
local SettingsScene = Scene:extend()
|
||||||
|
|
||||||
|
SettingsScene.title = "Settings"
|
||||||
|
|
||||||
|
local menu_screens = {
|
||||||
|
InputConfigScene,
|
||||||
|
GameConfigScene,
|
||||||
|
TuningScene
|
||||||
|
}
|
||||||
|
|
||||||
|
function SettingsScene:new()
|
||||||
|
self.menu_state = 1
|
||||||
|
DiscordRPC:update({
|
||||||
|
details = "In menus",
|
||||||
|
state = "Changing settings",
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function SettingsScene:update() end
|
||||||
|
|
||||||
|
function SettingsScene:render()
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
love.graphics.draw(
|
||||||
|
backgrounds["game_config"],
|
||||||
|
0, 0, 0,
|
||||||
|
0.5, 0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
love.graphics.setFont(font_3x5_4)
|
||||||
|
love.graphics.print("SETTINGS", 80, 40)
|
||||||
|
|
||||||
|
love.graphics.setFont(font_3x5_2)
|
||||||
|
love.graphics.print("Here, you can change some settings that change\nthe look and feel of the game.", 80, 90)
|
||||||
|
|
||||||
|
love.graphics.setColor(1, 1, 1, 0.5)
|
||||||
|
love.graphics.rectangle("fill", 75, 118 + 50 * self.menu_state, 200, 33)
|
||||||
|
|
||||||
|
love.graphics.setFont(font_3x5_3)
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
for i, screen in pairs(menu_screens) do
|
||||||
|
love.graphics.printf(screen.title, 80, 120 + 50 * i, 200, "left")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function SettingsScene:changeOption(rel)
|
||||||
|
local len = table.getn(menu_screens)
|
||||||
|
self.menu_state = (self.menu_state + len + rel - 1) % len + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
function SettingsScene:onInputPress(e)
|
||||||
|
if e.input == "menu_decide" or e.scancode == "return" then
|
||||||
|
playSE("main_decide")
|
||||||
|
scene = menu_screens[self.menu_state]()
|
||||||
|
elseif e.input == "up" or e.scancode == "up" then
|
||||||
|
self:changeOption(-1)
|
||||||
|
playSE("cursor")
|
||||||
|
elseif e.input == "down" or e.scancode == "down" then
|
||||||
|
self:changeOption(1)
|
||||||
|
playSE("cursor")
|
||||||
|
elseif e.input == "menu_back" or e.scancode == "backspace" or e.scancode == "delete" then
|
||||||
|
scene = TitleScene()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return SettingsScene
|
||||||
159
scene/stick_config.lua
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
local StickConfigScene = Scene:extend()
|
||||||
|
|
||||||
|
StickConfigScene.title = "Joystick Config"
|
||||||
|
|
||||||
|
require 'load.save'
|
||||||
|
|
||||||
|
local configurable_inputs = {
|
||||||
|
"menu_decide",
|
||||||
|
"menu_back",
|
||||||
|
"left",
|
||||||
|
"right",
|
||||||
|
"up",
|
||||||
|
"down",
|
||||||
|
"rotate_left",
|
||||||
|
"rotate_left2",
|
||||||
|
"rotate_right",
|
||||||
|
"rotate_right2",
|
||||||
|
"rotate_180",
|
||||||
|
"hold",
|
||||||
|
"retry",
|
||||||
|
"pause",
|
||||||
|
}
|
||||||
|
|
||||||
|
local function newSetInputs()
|
||||||
|
local set_inputs = {}
|
||||||
|
for i, input in ipairs(configurable_inputs) do
|
||||||
|
set_inputs[input] = false
|
||||||
|
end
|
||||||
|
return set_inputs
|
||||||
|
end
|
||||||
|
|
||||||
|
function StickConfigScene:new()
|
||||||
|
self.input_state = 1
|
||||||
|
self.set_inputs = newSetInputs()
|
||||||
|
self.new_input = {}
|
||||||
|
self.axis_timer = 0
|
||||||
|
|
||||||
|
DiscordRPC:update({
|
||||||
|
details = "In menus",
|
||||||
|
state = "Changing joystick config",
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function StickConfigScene:update()
|
||||||
|
end
|
||||||
|
|
||||||
|
function StickConfigScene: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 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/backspace to retry" .. (config.input and ", escape to cancel" or ""))
|
||||||
|
else
|
||||||
|
love.graphics.print("press joystick input for " .. configurable_inputs[self.input_state] .. ", tab to skip, escape to cancel", 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.axis_timer = self.axis_timer + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local function addJoystick(input, name)
|
||||||
|
if not input[name] then
|
||||||
|
input[name] = {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function StickConfigScene:onInputPress(e)
|
||||||
|
if e.type == "key" then
|
||||||
|
-- function keys, escape, and tab are reserved and can't be remapped
|
||||||
|
if e.scancode == "escape" then
|
||||||
|
scene = InputConfigScene()
|
||||||
|
elseif self.input_state > table.getn(configurable_inputs) then
|
||||||
|
if e.scancode == "return" then
|
||||||
|
-- save new input, then load next scene
|
||||||
|
local had_config = config.input ~= nil
|
||||||
|
if not config.input then config.input = {} end
|
||||||
|
config.input.joysticks = self.new_input
|
||||||
|
saveConfig()
|
||||||
|
scene = had_config and InputConfigScene() or 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
|
||||||
|
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[e.name].buttons then
|
||||||
|
self.new_input[e.name].buttons = {}
|
||||||
|
end
|
||||||
|
if self.new_input[e.name].buttons[e.button] then return 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[e.name].buttons[e.button] = configurable_inputs[self.input_state]
|
||||||
|
self.input_state = self.input_state + 1
|
||||||
|
elseif e.type == "joyaxis" then
|
||||||
|
if (e.axis ~= self.last_axis or self.axis_timer > 30) and math.abs(e.value) >= 1 then
|
||||||
|
addJoystick(self.new_input, e.name)
|
||||||
|
if not self.new_input[e.name].axes then
|
||||||
|
self.new_input[e.name].axes = {}
|
||||||
|
end
|
||||||
|
if not self.new_input[e.name].axes[e.axis] then
|
||||||
|
self.new_input[e.name].axes[e.axis] = {}
|
||||||
|
end
|
||||||
|
if (
|
||||||
|
self.new_input[e.name].axes[e.axis][e.value >= 1 and "positive" or "negative"]
|
||||||
|
) then return end
|
||||||
|
self.set_inputs[configurable_inputs[self.input_state]] =
|
||||||
|
"jaxis " ..
|
||||||
|
(e.value >= 1 and "+" or "-") .. e.axis ..
|
||||||
|
" " .. string.sub(e.name, 1, 10) .. (string.len(e.name) > 10 and "..." or "")
|
||||||
|
self.new_input[e.name].axes[e.axis][e.value >= 1 and "positive" or "negative"] = configurable_inputs[self.input_state]
|
||||||
|
self.input_state = self.input_state + 1
|
||||||
|
self.last_axis = e.axis
|
||||||
|
self.axis_timer = 0
|
||||||
|
end
|
||||||
|
elseif e.type == "joyhat" then
|
||||||
|
if e.direction ~= "c" then
|
||||||
|
addJoystick(self.new_input, e.name)
|
||||||
|
if not self.new_input[e.name].hats then
|
||||||
|
self.new_input[e.name].hats = {}
|
||||||
|
end
|
||||||
|
if not self.new_input[e.name].hats[e.hat] then
|
||||||
|
self.new_input[e.name].hats[e.hat] = {}
|
||||||
|
end
|
||||||
|
if self.new_input[e.name].hats[e.hat][e.direction] then
|
||||||
|
return
|
||||||
|
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[e.name].hats[e.hat][e.direction] = configurable_inputs[self.input_state]
|
||||||
|
self.input_state = self.input_state + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return StickConfigScene
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
local TitleScene = Scene:extend()
|
local TitleScene = Scene:extend()
|
||||||
|
|
||||||
|
TitleScene.title = "Title"
|
||||||
|
TitleScene.restart_message = false
|
||||||
|
|
||||||
local main_menu_screens = {
|
local main_menu_screens = {
|
||||||
ModeSelectScene,
|
ModeSelectScene,
|
||||||
InputConfigScene,
|
SettingsScene,
|
||||||
GameConfigScene,
|
CreditsScene,
|
||||||
ExitScene,
|
ExitScene,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,6 +27,11 @@ local mainmenuidle = {
|
|||||||
|
|
||||||
function TitleScene:new()
|
function TitleScene:new()
|
||||||
self.main_menu_state = 1
|
self.main_menu_state = 1
|
||||||
|
self.frames = 0
|
||||||
|
self.snow_bg_opacity = 0
|
||||||
|
self.y_offset = 0
|
||||||
|
self.text = ""
|
||||||
|
self.text_flag = false
|
||||||
DiscordRPC:update({
|
DiscordRPC:update({
|
||||||
details = "In menus",
|
details = "In menus",
|
||||||
state = mainmenuidle[math.random(#mainmenuidle)],
|
state = mainmenuidle[math.random(#mainmenuidle)],
|
||||||
@@ -31,17 +39,42 @@ function TitleScene:new()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function TitleScene:update()
|
function TitleScene:update()
|
||||||
|
if self.text_flag then
|
||||||
|
self.frames = self.frames + 1
|
||||||
|
self.snow_bg_opacity = self.snow_bg_opacity + 0.01
|
||||||
|
end
|
||||||
|
if self.frames < 125 then self.y_offset = self.frames
|
||||||
|
elseif self.frames < 185 then self.y_offset = 125
|
||||||
|
else self.y_offset = 310 - self.frames end
|
||||||
end
|
end
|
||||||
|
|
||||||
function TitleScene:render()
|
function TitleScene:render()
|
||||||
love.graphics.setFont(font_3x5_2)
|
love.graphics.setFont(font_3x5_2)
|
||||||
|
|
||||||
|
love.graphics.setColor(1, 1, 1, 1 - self.snow_bg_opacity)
|
||||||
love.graphics.draw(
|
love.graphics.draw(
|
||||||
backgrounds["title"],
|
backgrounds["title"],
|
||||||
0, 0, 0,
|
0, 0, 0,
|
||||||
0.5, 0.5
|
0.5, 0.5
|
||||||
)
|
)
|
||||||
|
|
||||||
|
love.graphics.setColor(1, 1, 1, self.snow_bg_opacity)
|
||||||
|
love.graphics.draw(
|
||||||
|
backgrounds["snow"],
|
||||||
|
0, 0, 0,
|
||||||
|
0.5, 0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
love.graphics.draw(
|
||||||
|
misc_graphics["santa"],
|
||||||
|
400, -205 + self.y_offset,
|
||||||
|
0, 0.5, 0.5
|
||||||
|
)
|
||||||
|
love.graphics.print("Happy Holidays!", 320, -100 + self.y_offset)
|
||||||
|
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
love.graphics.print(self.restart_message and "Restart Cambridge..." or "", 0, 0)
|
||||||
|
|
||||||
love.graphics.setColor(1, 1, 1, 0.5)
|
love.graphics.setColor(1, 1, 1, 0.5)
|
||||||
love.graphics.rectangle("fill", 20, 278 + 20 * self.main_menu_state, 160, 22)
|
love.graphics.rectangle("fill", 20, 278 + 20 * self.main_menu_state, 160, 22)
|
||||||
|
|
||||||
@@ -50,6 +83,7 @@ function TitleScene:render()
|
|||||||
love.graphics.printf(screen.title, 40, 280 + 20 * i, 120, "left")
|
love.graphics.printf(screen.title, 40, 280 + 20 * i, 120, "left")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
love.graphics.printf(version, 0, 460, love.graphics.getWidth() - 5, "right")
|
||||||
end
|
end
|
||||||
|
|
||||||
function TitleScene:changeOption(rel)
|
function TitleScene:changeOption(rel)
|
||||||
@@ -69,6 +103,11 @@ function TitleScene:onInputPress(e)
|
|||||||
playSE("cursor")
|
playSE("cursor")
|
||||||
elseif e.input == "menu_back" or e.scancode == "backspace" or e.scancode == "delete" then
|
elseif e.input == "menu_back" or e.scancode == "backspace" or e.scancode == "delete" then
|
||||||
love.event.quit()
|
love.event.quit()
|
||||||
|
else
|
||||||
|
self.text = self.text .. (e.scancode ~= nil and e.scancode or "")
|
||||||
|
if self.text == "ffffff" then
|
||||||
|
self.text_flag = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
90
scene/tuning.lua
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
local TuningScene = Scene:extend()
|
||||||
|
|
||||||
|
TuningScene.title = "Tuning Settings"
|
||||||
|
|
||||||
|
require 'load.save'
|
||||||
|
require 'libs.simple-slider'
|
||||||
|
|
||||||
|
TuningScene.options = {
|
||||||
|
-- Serves as a reference for the options available in the menu. Format: {name in config, name as displayed if applicable, slider name}
|
||||||
|
{"das", "DAS", "dasSlider"},
|
||||||
|
{"arr", "ARR", "arrSlider"},
|
||||||
|
{"dcd", "DCD", "dcdSlider"},
|
||||||
|
}
|
||||||
|
|
||||||
|
local optioncount = #TuningScene.options
|
||||||
|
|
||||||
|
function TuningScene:new()
|
||||||
|
DiscordRPC:update({
|
||||||
|
details = "In menus",
|
||||||
|
state = "Changing tuning settings",
|
||||||
|
})
|
||||||
|
self.highlight = 1
|
||||||
|
|
||||||
|
self.dasSlider = newSlider(290, 225, 400, config.das, 0, 20, function(v) config.das = math.floor(v) end, {width=20, knob="circle", track="roundrect"})
|
||||||
|
self.arrSlider = newSlider(290, 300, 400, config.arr, 0, 6, function(v) config.arr = math.floor(v) end, {width=20, knob="circle", track="roundrect"})
|
||||||
|
self.dcdSlider = newSlider(290, 375, 400, config.dcd, 0, 6, function(v) config.dcd = math.floor(v) end, {width=20, knob="circle", track="roundrect"})
|
||||||
|
end
|
||||||
|
|
||||||
|
function TuningScene:update()
|
||||||
|
self.dasSlider:update()
|
||||||
|
self.arrSlider:update()
|
||||||
|
self.dcdSlider:update()
|
||||||
|
end
|
||||||
|
|
||||||
|
function TuningScene:render()
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
love.graphics.draw(
|
||||||
|
backgrounds["game_config"],
|
||||||
|
0, 0, 0,
|
||||||
|
0.5, 0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
love.graphics.setColor(1, 1, 1, 0.5)
|
||||||
|
love.graphics.rectangle("fill", 75, 98 + self.highlight * 75, 400, 33)
|
||||||
|
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
|
||||||
|
love.graphics.setFont(font_3x5_4)
|
||||||
|
love.graphics.print("TUNING SETTINGS", 80, 40)
|
||||||
|
|
||||||
|
love.graphics.setFont(font_3x5_2)
|
||||||
|
love.graphics.print("These settings will only apply to modes\nthat do not use their own tunings.", 80, 90)
|
||||||
|
|
||||||
|
love.graphics.setFont(font_3x5_3)
|
||||||
|
love.graphics.print("Delayed Auto-Shift (DAS): " .. math.floor(self.dasSlider:getValue()) .. "F", 80, 175)
|
||||||
|
love.graphics.print("Auto-Repeat Rate (ARR): " .. math.floor(self.arrSlider:getValue()) .. "F", 80, 250)
|
||||||
|
love.graphics.print("DAS Cut Delay (DCD): " .. math.floor(self.dcdSlider:getValue()) .. "F", 80, 325)
|
||||||
|
|
||||||
|
love.graphics.setColor(1, 1, 1, 0.75)
|
||||||
|
self.dasSlider:draw()
|
||||||
|
self.arrSlider:draw()
|
||||||
|
self.dcdSlider:draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
function TuningScene:onInputPress(e)
|
||||||
|
if e.input == "menu_decide" or e.scancode == "return" then
|
||||||
|
playSE("mode_decide")
|
||||||
|
saveConfig()
|
||||||
|
scene = SettingsScene()
|
||||||
|
elseif e.input == "up" or e.scancode == "up" then
|
||||||
|
playSE("cursor")
|
||||||
|
self.highlight = Mod1(self.highlight-1, optioncount)
|
||||||
|
elseif e.input == "down" or e.scancode == "down" then
|
||||||
|
playSE("cursor")
|
||||||
|
self.highlight = Mod1(self.highlight+1, optioncount)
|
||||||
|
elseif e.input == "left" or e.scancode == "left" then
|
||||||
|
playSE("cursor")
|
||||||
|
sld = self[self.options[self.highlight][3]]
|
||||||
|
sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() - 1) / (sld.max - sld.min)))
|
||||||
|
elseif e.input == "right" or e.scancode == "right" then
|
||||||
|
playSE("cursor")
|
||||||
|
sld = self[self.options[self.highlight][3]]
|
||||||
|
sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() + 1) / (sld.max - sld.min)))
|
||||||
|
elseif e.input == "menu_back" or e.scancode == "delete" or e.scancode == "backspace" then
|
||||||
|
loadSave()
|
||||||
|
scene = SettingsScene()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return TuningScene
|
||||||
@@ -6,13 +6,15 @@ local empty = { skin = "", colour = "" }
|
|||||||
local oob = { skin = "", colour = "" }
|
local oob = { skin = "", colour = "" }
|
||||||
local block = { skin = "2tie", colour = "A" }
|
local block = { skin = "2tie", colour = "A" }
|
||||||
|
|
||||||
function Grid:new()
|
function Grid:new(width, height)
|
||||||
self.grid = {}
|
self.grid = {}
|
||||||
self.grid_age = {}
|
self.grid_age = {}
|
||||||
for y = 1, 24 do
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
for y = 1, self.height do
|
||||||
self.grid[y] = {}
|
self.grid[y] = {}
|
||||||
self.grid_age[y] = {}
|
self.grid_age[y] = {}
|
||||||
for x = 1, 10 do
|
for x = 1, self.width do
|
||||||
self.grid[y][x] = empty
|
self.grid[y][x] = empty
|
||||||
self.grid_age[y][x] = 0
|
self.grid_age[y][x] = 0
|
||||||
end
|
end
|
||||||
@@ -20,8 +22,8 @@ function Grid:new()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Grid:clear()
|
function Grid:clear()
|
||||||
for y = 1, 24 do
|
for y = 1, self.height do
|
||||||
for x = 1, 10 do
|
for x = 1, self.width do
|
||||||
self.grid[y][x] = empty
|
self.grid[y][x] = empty
|
||||||
self.grid_age[y][x] = 0
|
self.grid_age[y][x] = 0
|
||||||
end
|
end
|
||||||
@@ -29,7 +31,7 @@ function Grid:clear()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Grid:getCell(x, y)
|
function Grid:getCell(x, y)
|
||||||
if x < 1 or x > 10 or y > 24 then return oob
|
if x < 1 or x > self.width or y > self.height then return oob
|
||||||
elseif y < 1 then return empty
|
elseif y < 1 then return empty
|
||||||
else return self.grid[y][x]
|
else return self.grid[y][x]
|
||||||
end
|
end
|
||||||
@@ -98,89 +100,84 @@ end
|
|||||||
|
|
||||||
function Grid:getClearedRowCount()
|
function Grid:getClearedRowCount()
|
||||||
local count = 0
|
local count = 0
|
||||||
for row = 1, 24 do
|
local cleared_row_table = {}
|
||||||
|
for row = 1, self.height do
|
||||||
if self:isRowFull(row) then
|
if self:isRowFull(row) then
|
||||||
count = count + 1
|
count = count + 1
|
||||||
|
table.insert(cleared_row_table, row)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return count
|
return count, cleared_row_table
|
||||||
end
|
end
|
||||||
|
|
||||||
function Grid:markClearedRows()
|
function Grid:markClearedRows()
|
||||||
for row = 1, 24 do
|
local block_table = {}
|
||||||
|
for row = 1, self.height do
|
||||||
if self:isRowFull(row) then
|
if self:isRowFull(row) then
|
||||||
for x = 1, 10 do
|
block_table[row] = {}
|
||||||
|
for x = 1, self.width do
|
||||||
|
block_table[row][x] = {
|
||||||
|
skin = self.grid[row][x].skin,
|
||||||
|
colour = self.grid[row][x].colour,
|
||||||
|
}
|
||||||
self.grid[row][x] = {
|
self.grid[row][x] = {
|
||||||
skin = self.grid[row][x].skin,
|
skin = self.grid[row][x].skin,
|
||||||
colour = "X"
|
colour = "X"
|
||||||
}
|
}
|
||||||
|
--self.grid_age[row][x] = 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return true
|
return block_table
|
||||||
end
|
end
|
||||||
|
|
||||||
function Grid:clearClearedRows()
|
function Grid:clearClearedRows()
|
||||||
for row = 1, 24 do
|
for row = 1, self.height do
|
||||||
if self:isRowFull(row) then
|
if self:isRowFull(row) then
|
||||||
for above_row = row, 2, -1 do
|
for above_row = row, 2, -1 do
|
||||||
self.grid[above_row] = self.grid[above_row - 1]
|
self.grid[above_row] = self.grid[above_row - 1]
|
||||||
self.grid_age[above_row] = self.grid_age[above_row - 1]
|
self.grid_age[above_row] = self.grid_age[above_row - 1]
|
||||||
end
|
end
|
||||||
self.grid[1] = {empty, empty, empty, empty, empty, empty, empty, empty, empty, empty}
|
self.grid[1] = {}
|
||||||
self.grid_age[1] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
self.grid_age[1] = {}
|
||||||
|
for i = 1, self.width do
|
||||||
|
self.grid[1][i] = empty
|
||||||
|
self.grid_age[1][i] = 0
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
function Grid:copyBottomRow()
|
function Grid:copyBottomRow()
|
||||||
for row = 1, 23 do
|
for row = 1, self.height - 1 do
|
||||||
self.grid[row] = self.grid[row+1]
|
self.grid[row] = self.grid[row+1]
|
||||||
self.grid_age[row] = self.grid_age[row+1]
|
self.grid_age[row] = self.grid_age[row+1]
|
||||||
end
|
end
|
||||||
self.grid[24] = {empty, empty, empty, empty, empty, empty, empty, empty, empty, empty}
|
self.grid[self.height] = {}
|
||||||
self.grid_age[24] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
self.grid_age[self.height] = {}
|
||||||
for col = 1, 10 do
|
for i = 1, self.width do
|
||||||
self.grid[24][col] = (self.grid[23][col] == empty) and empty or block
|
self.grid[self.height][i] = (self.grid[self.height - 1][i] == empty) and empty or block
|
||||||
|
self.grid_age[self.height][i] = 0
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
function Grid:garbageRise(row_vals)
|
function Grid:garbageRise(row_vals)
|
||||||
for row = 1, 23 do
|
for row = 1, self.height - 1 do
|
||||||
self.grid[row] = self.grid[row+1]
|
self.grid[row] = self.grid[row+1]
|
||||||
self.grid_age[row] = self.grid_age[row+1]
|
self.grid_age[row] = self.grid_age[row+1]
|
||||||
end
|
end
|
||||||
self.grid[24] = {empty, empty, empty, empty, empty, empty, empty, empty, empty, empty}
|
self.grid[self.height] = {}
|
||||||
self.grid_age[24] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
self.grid_age[self.height] = {}
|
||||||
for col = 1, 10 do
|
for i = 1, self.width do
|
||||||
self.grid[24][col] = (row_vals[col] == "e") and empty or block
|
self.grid[self.height][i] = (row_vals[i] == "e") and empty or block
|
||||||
end
|
self.grid_age[self.height][i] = 0
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
function Grid:clearSpecificRow(row)
|
function Grid:clearSpecificRow(row)
|
||||||
for col = 1, 10 do
|
for col = 1, self.width do
|
||||||
self.grid[row][col] = empty
|
self.grid[row][col] = empty
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -194,7 +191,7 @@ function Grid:applyPiece(piece)
|
|||||||
for index, offset in pairs(offsets) do
|
for index, offset in pairs(offsets) do
|
||||||
x = piece.position.x + offset.x
|
x = piece.position.x + offset.x
|
||||||
y = piece.position.y + offset.y
|
y = piece.position.y + offset.y
|
||||||
if y + 1 > 0 then
|
if y + 1 > 0 and y < self.height then
|
||||||
self.grid[y+1][x+1] = {
|
self.grid[y+1][x+1] = {
|
||||||
skin = piece.skin,
|
skin = piece.skin,
|
||||||
colour = piece.colour
|
colour = piece.colour
|
||||||
@@ -210,7 +207,7 @@ function Grid:applyBigPiece(piece)
|
|||||||
y = piece.position.y + offset.y
|
y = piece.position.y + offset.y
|
||||||
for a = 1, 2 do
|
for a = 1, 2 do
|
||||||
for b = 1, 2 do
|
for b = 1, 2 do
|
||||||
if y*2+a > 0 then
|
if y*2+a > 0 and y*2 < self.height then
|
||||||
self.grid[y*2+a][x*2+b] = {
|
self.grid[y*2+a][x*2+b] = {
|
||||||
skin = piece.skin,
|
skin = piece.skin,
|
||||||
colour = piece.colour
|
colour = piece.colour
|
||||||
@@ -222,8 +219,8 @@ function Grid:applyBigPiece(piece)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Grid:checkForBravo(cleared_row_count)
|
function Grid:checkForBravo(cleared_row_count)
|
||||||
for i = 0, 23 - cleared_row_count do
|
for i = 0, self.height - 1 - cleared_row_count do
|
||||||
for j = 0, 9 do
|
for j = 0, self.width - 1 do
|
||||||
if self:isOccupied(j, i) then return false end
|
if self:isOccupied(j, i) then return false end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -231,9 +228,9 @@ function Grid:checkForBravo(cleared_row_count)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Grid:checkStackHeight()
|
function Grid:checkStackHeight()
|
||||||
for i = 0, 23 do
|
for i = 0, self.height - 1 do
|
||||||
for j = 0, 9 do
|
for j = 0, self.width - 1 do
|
||||||
if self:isOccupied(j, i) then return 24 - i end
|
if self:isOccupied(j, i) then return self.height - i end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return 0
|
return 0
|
||||||
@@ -273,9 +270,120 @@ function Grid:checkSecretGrade()
|
|||||||
return sgrade
|
return sgrade
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Grid:hasGemBlocks()
|
||||||
|
for y = 1, self.height do
|
||||||
|
for x = 1, self.width do
|
||||||
|
if self.grid[y][x].skin == "gem" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function Grid:mirror()
|
||||||
|
local new_grid = {}
|
||||||
|
for y = 1, self.height do
|
||||||
|
new_grid[y] = {}
|
||||||
|
for x = 1, self.width do
|
||||||
|
new_grid[y][x] = empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for y = 1, self.height do
|
||||||
|
for x = 1, self.width do
|
||||||
|
new_grid[y][x] = self.grid[y][self.width + 1 - x]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.grid = new_grid
|
||||||
|
end
|
||||||
|
|
||||||
|
function Grid:applyMap(map)
|
||||||
|
for y, row in pairs(map) do
|
||||||
|
for x, block in pairs(row) do
|
||||||
|
self.grid_age[y][x] = 0
|
||||||
|
self.grid[y][x] = block
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- inefficient algorithm for squares
|
||||||
|
function Grid:markSquares()
|
||||||
|
-- goes up by 1 for silver, 2 for gold
|
||||||
|
local square_count = 0
|
||||||
|
for i = 1, 2 do
|
||||||
|
for y = 5, self.height - 3 do
|
||||||
|
for x = 1, self.width - 3 do
|
||||||
|
local age_table = {}
|
||||||
|
local age_count = 0
|
||||||
|
local colour_table = {}
|
||||||
|
local is_square = true
|
||||||
|
for j = 0, 3 do
|
||||||
|
for k = 0, 3 do
|
||||||
|
if self.grid[y+j][x+k].skin == "" or self.grid[y+j][x+k].skin == "square" then
|
||||||
|
is_square = false
|
||||||
|
end
|
||||||
|
if age_table[self.grid_age[y+j][x+k]] == nil then
|
||||||
|
age_table[self.grid_age[y+j][x+k]] = 1
|
||||||
|
age_count = age_count + 1
|
||||||
|
else
|
||||||
|
age_table[self.grid_age[y+j][x+k]] = age_table[self.grid_age[y+j][x+k]] + 1
|
||||||
|
end
|
||||||
|
if age_count > 4 or age_table[self.grid_age[y+j][x+k]] > 4 then
|
||||||
|
is_square = false
|
||||||
|
end
|
||||||
|
if not table.contains(colour_table, self.grid[y+j][x+k].colour) then
|
||||||
|
table.insert(colour_table, self.grid[y+j][x+k].colour)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if is_square then
|
||||||
|
if i == 1 and #colour_table == 1 then
|
||||||
|
for j = 0, 3 do
|
||||||
|
for k = 0, 3 do
|
||||||
|
self.grid[y+j][x+k].colour = "Y"
|
||||||
|
self.grid[y+j][x+k].skin = "square"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
square_count = square_count + 2
|
||||||
|
elseif i == 2 then
|
||||||
|
for j = 0, 3 do
|
||||||
|
for k = 0, 3 do
|
||||||
|
self.grid[y+j][x+k].colour = "W"
|
||||||
|
self.grid[y+j][x+k].skin = "square"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
square_count = square_count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return square_count
|
||||||
|
end
|
||||||
|
|
||||||
|
-- square scan
|
||||||
|
function Grid:scanForSquares()
|
||||||
|
local table = {}
|
||||||
|
for row = 1, self.height do
|
||||||
|
local silver = 0
|
||||||
|
local gold = 0
|
||||||
|
for col = 1, self.width do
|
||||||
|
local colour = self.grid[row][col].colour
|
||||||
|
if self.grid[row][col].skin == "square" then
|
||||||
|
if colour == "Y" then gold = gold + 1
|
||||||
|
else silver = silver + 1 end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table[row] = gold * 2.5 + silver * 1.25
|
||||||
|
end
|
||||||
|
return table
|
||||||
|
end
|
||||||
|
|
||||||
function Grid:update()
|
function Grid:update()
|
||||||
for y = 1, 24 do
|
for y = 1, self.height do
|
||||||
for x = 1, 10 do
|
for x = 1, self.width do
|
||||||
if self.grid[y][x] ~= empty then
|
if self.grid[y][x] ~= empty then
|
||||||
self.grid_age[y][x] = self.grid_age[y][x] + 1
|
self.grid_age[y][x] = self.grid_age[y][x] + 1
|
||||||
end
|
end
|
||||||
@@ -284,33 +392,37 @@ function Grid:update()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Grid:draw()
|
function Grid:draw()
|
||||||
for y = 5, 24 do
|
for y = 5, self.height do
|
||||||
for x = 1, 10 do
|
for x = 1, self.width do
|
||||||
if self.grid[y][x] ~= empty then
|
if blocks[self.grid[y][x].skin] and
|
||||||
|
blocks[self.grid[y][x].skin][self.grid[y][x].colour] then
|
||||||
if self.grid_age[y][x] < 2 then
|
if self.grid_age[y][x] < 2 then
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
love.graphics.draw(blocks[self.grid[y][x].skin]["F"], 48+x*16, y*16)
|
love.graphics.draw(blocks[self.grid[y][x].skin]["F"], 48+x*16, y*16)
|
||||||
else
|
else
|
||||||
if self.grid[y][x].skin == "bone" then
|
if self.grid[y][x].colour == "X" then
|
||||||
|
love.graphics.setColor(0, 0, 0, 0)
|
||||||
|
elseif self.grid[y][x].skin == "bone" then
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
else
|
else
|
||||||
love.graphics.setColor(0.5, 0.5, 0.5, 1)
|
love.graphics.setColor(0.5, 0.5, 0.5, 1)
|
||||||
end
|
end
|
||||||
love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16)
|
love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16)
|
||||||
end
|
end
|
||||||
if self.grid[y][x].skin ~= "bone" then
|
if self.grid[y][x].skin ~= "bone" and self.grid[y][x].colour ~= "X" then
|
||||||
love.graphics.setColor(0.8, 0.8, 0.8, 1)
|
love.graphics.setColor(0.8, 0.8, 0.8, 1)
|
||||||
love.graphics.setLineWidth(1)
|
love.graphics.setLineWidth(1)
|
||||||
if y > 1 and self.grid[y-1][x] == empty then
|
if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then
|
||||||
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
|
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
|
||||||
end
|
end
|
||||||
if y < 24 and self.grid[y+1][x] == empty then
|
if y < self.height and self.grid[y+1][x] == empty or
|
||||||
|
(y + 1 <= self.height and self.grid[y+1][x].colour == "X") then
|
||||||
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
|
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
|
||||||
end
|
end
|
||||||
if x > 1 and self.grid[y][x-1] == empty then
|
if x > 1 and self.grid[y][x-1] == empty then
|
||||||
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
|
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
|
||||||
end
|
end
|
||||||
if x < 10 and self.grid[y][x+1] == empty then
|
if x < self.width and self.grid[y][x+1] == empty then
|
||||||
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
|
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -320,21 +432,22 @@ function Grid:draw()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Grid:drawOutline()
|
function Grid:drawOutline()
|
||||||
for y = 5, 24 do
|
for y = 5, self.height do
|
||||||
for x = 1, 10 do
|
for x = 1, self.width do
|
||||||
if self.grid[y][x] ~= empty then
|
if self.grid[y][x] ~= empty and self.grid[y][x].colour ~= "X" then
|
||||||
love.graphics.setColor(0.8, 0.8, 0.8, 1)
|
love.graphics.setColor(0.8, 0.8, 0.8, 1)
|
||||||
love.graphics.setLineWidth(1)
|
love.graphics.setLineWidth(1)
|
||||||
if y > 1 and self.grid[y-1][x] == empty then
|
if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then
|
||||||
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
|
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
|
||||||
end
|
end
|
||||||
if y < 24 and self.grid[y+1][x] == empty then
|
if y < self.height and self.grid[y+1][x] == empty or
|
||||||
|
(y + 1 <= self.height and self.grid[y+1][x].colour == "X") then
|
||||||
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
|
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
|
||||||
end
|
end
|
||||||
if x > 1 and self.grid[y][x-1] == empty then
|
if x > 1 and self.grid[y][x-1] == empty then
|
||||||
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
|
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
|
||||||
end
|
end
|
||||||
if x < 10 and self.grid[y][x+1] == empty then
|
if x < self.width and self.grid[y][x+1] == empty then
|
||||||
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
|
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -345,11 +458,11 @@ end
|
|||||||
function Grid:drawInvisible(opacity_function, garbage_opacity_function, lock_flash, brightness)
|
function Grid:drawInvisible(opacity_function, garbage_opacity_function, lock_flash, brightness)
|
||||||
lock_flash = lock_flash == nil and true or lock_flash
|
lock_flash = lock_flash == nil and true or lock_flash
|
||||||
brightness = brightness == nil and 0.5 or brightness
|
brightness = brightness == nil and 0.5 or brightness
|
||||||
for y = 5, 24 do
|
for y = 5, self.height do
|
||||||
for x = 1, 10 do
|
for x = 1, self.width do
|
||||||
if self.grid[y][x] ~= empty then
|
if self.grid[y][x] ~= empty then
|
||||||
if self.grid[y][x].colour == "X" then
|
if self.grid[y][x].colour == "X" then
|
||||||
opacity = 1
|
opacity = 0
|
||||||
elseif garbage_opacity_function and self.grid[y][x].colour == "A" then
|
elseif garbage_opacity_function and self.grid[y][x].colour == "A" then
|
||||||
opacity = garbage_opacity_function(self.grid_age[y][x])
|
opacity = garbage_opacity_function(self.grid_age[y][x])
|
||||||
else
|
else
|
||||||
@@ -361,16 +474,17 @@ function Grid:drawInvisible(opacity_function, garbage_opacity_function, lock_fla
|
|||||||
if opacity > 0 and self.grid[y][x].colour ~= "X" then
|
if opacity > 0 and self.grid[y][x].colour ~= "X" then
|
||||||
love.graphics.setColor(0.64, 0.64, 0.64)
|
love.graphics.setColor(0.64, 0.64, 0.64)
|
||||||
love.graphics.setLineWidth(1)
|
love.graphics.setLineWidth(1)
|
||||||
if y > 1 and self.grid[y-1][x] == empty then
|
if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then
|
||||||
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
|
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
|
||||||
end
|
end
|
||||||
if y < 24 and self.grid[y+1][x] == empty then
|
if y < self.height and self.grid[y+1][x] == empty or
|
||||||
|
(y + 1 <= self.height and self.grid[y+1][x].colour == "X") then
|
||||||
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
|
love.graphics.line(48.0+x*16, 16.5+y*16, 64.0+x*16, 16.5+y*16)
|
||||||
end
|
end
|
||||||
if x > 1 and self.grid[y][x-1] == empty then
|
if x > 1 and self.grid[y][x-1] == empty then
|
||||||
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
|
love.graphics.line(47.5+x*16, -0.0+y*16, 47.5+x*16, 16.0+y*16)
|
||||||
end
|
end
|
||||||
if x < 10 and self.grid[y][x+1] == empty then
|
if x < self.width and self.grid[y][x+1] == empty then
|
||||||
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
|
love.graphics.line(64.5+x*16, -0.0+y*16, 64.5+x*16, 16.0+y*16)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -380,4 +494,45 @@ function Grid:drawInvisible(opacity_function, garbage_opacity_function, lock_fla
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Grid:drawCustom(colour_function, gamestate)
|
||||||
|
--[[
|
||||||
|
colour_function: (game, block, x, y, age) -> (R, G, B, A, outlineA)
|
||||||
|
When called, calls the supplied function on every block passing the block itself as argument
|
||||||
|
as well as coordinates and the grid_age value of the same cell.
|
||||||
|
Should return a RGBA colour for the block, as well as the opacity of the stack outline (0 for no outline).
|
||||||
|
|
||||||
|
gamestate: the gamemode instance itself to pass in colour_function
|
||||||
|
]]
|
||||||
|
for y = 5, self.height do
|
||||||
|
for x = 1, self.width do
|
||||||
|
local block = self.grid[y][x]
|
||||||
|
if block ~= empty then
|
||||||
|
local R, G, B, A, outline = colour_function(gamestate, block, x, y, self.grid_age[y][x])
|
||||||
|
if self.grid[y][x].colour == "X" then
|
||||||
|
A = 0
|
||||||
|
end
|
||||||
|
love.graphics.setColor(R, G, B, A)
|
||||||
|
love.graphics.draw(blocks[self.grid[y][x].skin][self.grid[y][x].colour], 48+x*16, y*16)
|
||||||
|
if outline > 0 and self.grid[y][x].colour ~= "X" then
|
||||||
|
love.graphics.setColor(0.64, 0.64, 0.64, outline)
|
||||||
|
love.graphics.setLineWidth(1)
|
||||||
|
if y > 5 and self.grid[y-1][x] == empty or self.grid[y-1][x].colour == "X" then
|
||||||
|
love.graphics.line(48.0+x*16, -0.5+y*16, 64.0+x*16, -0.5+y*16)
|
||||||
|
end
|
||||||
|
if y < self.height and self.grid[y+1][x] == empty or
|
||||||
|
(y + 1 <= self.height and self.grid[y+1][x].colour == "X") 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 < self.width 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
|
||||||
|
|
||||||
return Grid
|
return Grid
|
||||||
|
|||||||
@@ -104,9 +104,8 @@ function Piece:dropToBottom(grid)
|
|||||||
self:dropSquares(math.huge, grid)
|
self:dropSquares(math.huge, grid)
|
||||||
self.gravity = 0
|
self.gravity = 0
|
||||||
if self.position.y > piece_y then
|
if self.position.y > piece_y then
|
||||||
-- if it got dropped any, also reset lock delay
|
|
||||||
if self.ghost == false then playSE("bottom") end
|
if self.ghost == false then playSE("bottom") end
|
||||||
self.lock_delay = 0
|
-- self.lock_delay = 0
|
||||||
end
|
end
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
@@ -118,19 +117,37 @@ function Piece:lockIfBottomed(grid)
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
function Piece:addGravity(gravity, grid)
|
function Piece:addGravity(gravity, grid, classic_lock)
|
||||||
|
gravity = gravity / (self.big and 2 or 1)
|
||||||
local new_gravity = self.gravity + gravity
|
local new_gravity = self.gravity + gravity
|
||||||
if self:isDropBlocked(grid) then
|
if self:isDropBlocked(grid) then
|
||||||
self.gravity = math.min(1, new_gravity)
|
if classic_lock then
|
||||||
self.lock_delay = self.lock_delay + 1
|
self.gravity = new_gravity
|
||||||
else
|
else
|
||||||
local dropped_squares = math.floor(new_gravity)
|
self.gravity = 0
|
||||||
|
self.lock_delay = self.lock_delay + 1
|
||||||
|
end
|
||||||
|
elseif not (
|
||||||
|
self:isMoveBlocked(grid, { x=0, y=-1 }) and gravity < 0
|
||||||
|
) then
|
||||||
|
local dropped_squares = math.floor(math.abs(new_gravity))
|
||||||
|
if gravity >= 0 then
|
||||||
local new_frac_gravity = new_gravity - dropped_squares
|
local new_frac_gravity = new_gravity - dropped_squares
|
||||||
self.gravity = new_frac_gravity
|
self.gravity = new_frac_gravity
|
||||||
self:dropSquares(dropped_squares, grid)
|
self:dropSquares(dropped_squares, grid)
|
||||||
if self:isDropBlocked(grid) then
|
if self:isDropBlocked(grid) then
|
||||||
playSE("bottom")
|
playSE("bottom")
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
local new_frac_gravity = new_gravity + dropped_squares
|
||||||
|
self.gravity = new_frac_gravity
|
||||||
|
self:moveInGrid({ x=0, y=-1 }, dropped_squares, grid)
|
||||||
|
if self:isMoveBlocked(grid, { x=0, y=-1 }) then
|
||||||
|
playSE("bottom")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.gravity = 0
|
||||||
end
|
end
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
@@ -143,9 +160,10 @@ function Piece:draw(opacity, brightness, grid, partial_das)
|
|||||||
love.graphics.setColor(brightness, brightness, brightness, opacity)
|
love.graphics.setColor(brightness, brightness, brightness, opacity)
|
||||||
local offsets = self:getBlockOffsets()
|
local offsets = self:getBlockOffsets()
|
||||||
local gravity_offset = 0
|
local gravity_offset = 0
|
||||||
--if grid ~= nil and not self:isDropBlocked(grid) then
|
if config.gamesettings.smooth_movement == 1 and
|
||||||
-- gravity_offset = self.gravity * 16
|
grid ~= nil and not self:isDropBlocked(grid) then
|
||||||
--end
|
gravity_offset = self.gravity * 16
|
||||||
|
end
|
||||||
if partial_das == nil then partial_das = 0 end
|
if partial_das == nil then partial_das = 0 end
|
||||||
for index, offset in pairs(offsets) do
|
for index, offset in pairs(offsets) do
|
||||||
local x = self.position.x + offset.x
|
local x = self.position.x + offset.x
|
||||||
|
|||||||
@@ -1,138 +1,24 @@
|
|||||||
require 'funcs'
|
|
||||||
|
|
||||||
local GameMode = require 'tetris.modes.gamemode'
|
local GameMode = require 'tetris.modes.gamemode'
|
||||||
local Piece = require 'tetris.components.piece'
|
local MarathonA2Game = require 'tetris.modes.marathon_a2'
|
||||||
|
|
||||||
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
|
local BigA2Game = MarathonA2Game:extend()
|
||||||
|
|
||||||
local MarathonA2Game = GameMode:extend()
|
BigA2Game.name = "Big A2"
|
||||||
|
BigA2Game.hash = "BigA2"
|
||||||
|
BigA2Game.tagline = "Big blocks in the most celebrated TGM mode!"
|
||||||
|
|
||||||
MarathonA2Game.name = "Big A2"
|
function BigA2Game:new()
|
||||||
MarathonA2Game.hash = "BigA2"
|
BigA2Game.super:new()
|
||||||
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.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
|
end
|
||||||
|
|
||||||
function MarathonA2Game:getARE()
|
function BigA2Game:updateScore(level, drop_bonus, cleared_lines)
|
||||||
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
|
cleared_lines = cleared_lines / 2
|
||||||
|
if not self.clear then
|
||||||
self:updateGrade(cleared_lines)
|
self:updateGrade(cleared_lines)
|
||||||
|
if cleared_lines >= 4 then
|
||||||
|
self.tetris_count = self.tetris_count + 1
|
||||||
|
end
|
||||||
if self.grid:checkForBravo(cleared_lines) then self.bravo = 4 else self.bravo = 1 end
|
if self.grid:checkForBravo(cleared_lines) then self.bravo = 4 else self.bravo = 1 end
|
||||||
if cleared_lines > 0 then
|
if cleared_lines > 0 then
|
||||||
self.combo = self.combo + (cleared_lines - 1) * 2
|
self.combo = self.combo + (cleared_lines - 1) * 2
|
||||||
@@ -144,164 +30,21 @@ function MarathonA2Game:updateScore(level, drop_bonus, cleared_lines)
|
|||||||
self.combo = 1
|
self.combo = 1
|
||||||
end
|
end
|
||||||
self.drop_bonus = 0
|
self.drop_bonus = 0
|
||||||
end
|
else self.lines = self.lines + cleared_lines end
|
||||||
end
|
end
|
||||||
|
|
||||||
local grade_point_bonuses = {
|
function BigA2Game:onLineClear(cleared_row_count)
|
||||||
{10, 20, 40, 50},
|
cleared_row_count = cleared_row_count / 2
|
||||||
{10, 20, 30, 40},
|
self:updateSectionTimes(self.level, self.level + cleared_row_count)
|
||||||
{10, 20, 30, 40},
|
self.level = math.min(self.level + cleared_row_count, 999)
|
||||||
{10, 15, 30, 40},
|
if self.level == 999 and not self.clear then
|
||||||
{10, 15, 20, 40},
|
self.clear = true
|
||||||
{5, 15, 20, 30},
|
self.grid:clear()
|
||||||
{5, 10, 20, 30},
|
if self:qualifiesForMRoll() then self.grade = 32 end
|
||||||
{5, 10, 15, 30},
|
self.roll_frames = -150
|
||||||
{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
|
||||||
|
self.lock_drop = self.level >= 900
|
||||||
|
self.lock_hard_drop = self.level >= 900
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonA2Game:getLetterGrade()
|
return BigA2Game
|
||||||
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
|
|
||||||
@@ -5,14 +5,18 @@ local playedReadySE = false
|
|||||||
local playedGoSE = false
|
local playedGoSE = false
|
||||||
|
|
||||||
local Grid = require 'tetris.components.grid'
|
local Grid = require 'tetris.components.grid'
|
||||||
local Randomizer = require 'tetris.randomizers.randomizer'
|
local Randomizer = require 'tetris.randomizers.bag7'
|
||||||
|
local BagRandomizer = require 'tetris.randomizers.bag'
|
||||||
|
|
||||||
local GameMode = Object:extend()
|
local GameMode = Object:extend()
|
||||||
|
|
||||||
|
GameMode.name = ""
|
||||||
|
GameMode.hash = ""
|
||||||
|
GameMode.tagline = ""
|
||||||
GameMode.rollOpacityFunction = function(age) return 0 end
|
GameMode.rollOpacityFunction = function(age) return 0 end
|
||||||
|
|
||||||
function GameMode:new()
|
function GameMode:new(secret_inputs)
|
||||||
self.grid = Grid()
|
self.grid = Grid(10, 24)
|
||||||
self.randomizer = Randomizer()
|
self.randomizer = Randomizer()
|
||||||
self.piece = nil
|
self.piece = nil
|
||||||
self.ready_frames = 100
|
self.ready_frames = 100
|
||||||
@@ -21,6 +25,7 @@ function GameMode:new()
|
|||||||
self.score = 0
|
self.score = 0
|
||||||
self.level = 0
|
self.level = 0
|
||||||
self.lines = 0
|
self.lines = 0
|
||||||
|
self.squares = 0
|
||||||
self.drop_bonus = 0
|
self.drop_bonus = 0
|
||||||
self.are = 0
|
self.are = 0
|
||||||
self.lcd = 0
|
self.lcd = 0
|
||||||
@@ -40,11 +45,14 @@ function GameMode:new()
|
|||||||
self.enable_hard_drop = true
|
self.enable_hard_drop = true
|
||||||
self.next_queue_length = 1
|
self.next_queue_length = 1
|
||||||
self.additive_gravity = true
|
self.additive_gravity = true
|
||||||
|
self.classic_lock = false
|
||||||
self.draw_section_times = false
|
self.draw_section_times = false
|
||||||
self.draw_secondary_section_times = false
|
self.draw_secondary_section_times = false
|
||||||
self.big_mode = false
|
self.big_mode = false
|
||||||
self.irs = true
|
self.irs = true
|
||||||
self.ihs = true
|
self.ihs = true
|
||||||
|
self.square_mode = false
|
||||||
|
self.immobile_spin_bonus = false
|
||||||
self.rpc_details = "In game"
|
self.rpc_details = "In game"
|
||||||
self.SGnames = {
|
self.SGnames = {
|
||||||
"9", "8", "7", "6", "5", "4", "3", "2", "1",
|
"9", "8", "7", "6", "5", "4", "3", "2", "1",
|
||||||
@@ -56,6 +64,9 @@ function GameMode:new()
|
|||||||
self.hard_drop_locked = false
|
self.hard_drop_locked = false
|
||||||
self.lock_on_soft_drop = false
|
self.lock_on_soft_drop = false
|
||||||
self.lock_on_hard_drop = false
|
self.lock_on_hard_drop = false
|
||||||
|
self.cleared_block_table = {}
|
||||||
|
self.last_lcd = 0
|
||||||
|
self.used_randomizer = nil
|
||||||
self.hold_queue = nil
|
self.hold_queue = nil
|
||||||
self.held = false
|
self.held = false
|
||||||
self.section_start_time = 0
|
self.section_start_time = 0
|
||||||
@@ -70,20 +81,35 @@ function GameMode:getLineARE() return 25 end
|
|||||||
function GameMode:getLockDelay() return 30 end
|
function GameMode:getLockDelay() return 30 end
|
||||||
function GameMode:getLineClearDelay() return 40 end
|
function GameMode:getLineClearDelay() return 40 end
|
||||||
function GameMode:getDasLimit() return 15 end
|
function GameMode:getDasLimit() return 15 end
|
||||||
|
function GameMode:getDasCutDelay() return 0 end
|
||||||
|
function GameMode:getGravity() return 1/64 end
|
||||||
|
|
||||||
function GameMode:getNextPiece(ruleset)
|
function GameMode:getNextPiece(ruleset)
|
||||||
|
local shape = self.used_randomizer:nextPiece()
|
||||||
return {
|
return {
|
||||||
skin = "2tie",
|
skin = self:getSkin(),
|
||||||
shape = self.randomizer:nextPiece(),
|
shape = shape,
|
||||||
orientation = ruleset:getDefaultOrientation(),
|
orientation = ruleset:getDefaultOrientation(shape),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function GameMode:getSkin()
|
||||||
|
return "2tie"
|
||||||
|
end
|
||||||
|
|
||||||
function GameMode:initialize(ruleset)
|
function GameMode:initialize(ruleset)
|
||||||
-- generate next queue
|
-- generate next queue
|
||||||
self:new()
|
self.used_randomizer = (
|
||||||
for i = 1, self.next_queue_length do
|
ruleset.pieces == self.randomizer.possible_pieces and
|
||||||
|
self.randomizer or
|
||||||
|
(
|
||||||
|
ruleset.pieces == 7 and
|
||||||
|
Randomizer() or
|
||||||
|
BagRandomizer(ruleset.pieces)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.ruleset = ruleset
|
||||||
|
for i = 1, math.max(self.next_queue_length, 1) do
|
||||||
table.insert(self.next_queue, self:getNextPiece(ruleset))
|
table.insert(self.next_queue, self:getNextPiece(ruleset))
|
||||||
end
|
end
|
||||||
self.lock_on_soft_drop = ({ruleset.softdrop_lock, self.instant_soft_drop, false, true })[config.gamesettings.manlock]
|
self.lock_on_soft_drop = ({ruleset.softdrop_lock, self.instant_soft_drop, false, true })[config.gamesettings.manlock]
|
||||||
@@ -91,38 +117,31 @@ function GameMode:initialize(ruleset)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function GameMode:update(inputs, ruleset)
|
function GameMode:update(inputs, ruleset)
|
||||||
if self.game_over then
|
if self.game_over or self.completed then
|
||||||
self.game_over_frames = self.game_over_frames + 1
|
self.game_over_frames = self.game_over_frames + 1
|
||||||
if self.game_over_frames >= 60 then
|
|
||||||
self.completed = true
|
|
||||||
end
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if self.completed then return end
|
|
||||||
|
|
||||||
if config.gamesettings.diagonal_input == 2 then
|
if config.gamesettings.diagonal_input == 2 then
|
||||||
if inputs["left"] or inputs["right"] then
|
if inputs["left"] or inputs["right"] then
|
||||||
inputs["up"] = false
|
inputs["up"] = false
|
||||||
inputs["down"] = false
|
inputs["down"] = false
|
||||||
elseif inputs["up"] or inputs["down"] then
|
|
||||||
inputs["left"] = false
|
|
||||||
inputs["right"] = false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- advance one frame
|
-- advance one frame
|
||||||
if self:advanceOneFrame(inputs, ruleset) == false then return end
|
if self:advanceOneFrame(inputs, ruleset) == false then return end
|
||||||
|
|
||||||
self:chargeDAS(inputs, self:getDasLimit(), self.getARR())
|
self:chargeDAS(inputs, self:getDasLimit(), self:getARR())
|
||||||
|
|
||||||
-- set attempt flags
|
-- set attempt flags
|
||||||
if inputs["left"] or inputs["right"] then self:onAttemptPieceMove(self.piece) end
|
if inputs["left"] or inputs["right"] then self:onAttemptPieceMove(self.piece, self.grid) end
|
||||||
if
|
if (
|
||||||
inputs["rotate_left"] or inputs["rotate_right"] or
|
inputs["rotate_left"] or inputs["rotate_right"] or
|
||||||
inputs["rotate_left2"] or inputs["rotate_right2"] or
|
inputs["rotate_left2"] or inputs["rotate_right2"] or
|
||||||
inputs["rotate_180"]
|
inputs["rotate_180"]
|
||||||
then
|
) then
|
||||||
self:onAttemptPieceRotate(self.piece)
|
self:onAttemptPieceRotate(self.piece, self.grid)
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.piece == nil then
|
if self.piece == nil then
|
||||||
@@ -138,53 +157,112 @@ function GameMode:update(inputs, ruleset)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.lock_drop and inputs["down"] ~= true then
|
if (self.lock_drop or (
|
||||||
|
not ruleset.are or self:getARE() == 0
|
||||||
|
)) and inputs["down"] ~= true then
|
||||||
self.drop_locked = false
|
self.drop_locked = false
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.lock_hard_drop and inputs["up"] ~= true then
|
if (self.lock_hard_drop or (
|
||||||
|
not ruleset.are or self:getARE() == 0
|
||||||
|
)) and inputs["up"] ~= true then
|
||||||
self.hard_drop_locked = false
|
self.hard_drop_locked = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- diff vars to use in checks
|
||||||
local piece_y = self.piece.position.y
|
local piece_y = self.piece.position.y
|
||||||
|
local piece_x = self.piece.position.x
|
||||||
|
local piece_rot = self.piece.rotation
|
||||||
|
|
||||||
ruleset:processPiece(
|
ruleset:processPiece(
|
||||||
inputs, self.piece, self.grid, self:getGravity(), self.prev_inputs,
|
inputs, self.piece, self.grid, self:getGravity(), self.prev_inputs,
|
||||||
self.move, self:getLockDelay(), self:getDropSpeed(),
|
(
|
||||||
|
inputs.up and self.lock_on_hard_drop and not self.hard_drop_locked
|
||||||
|
) and "none" or self.move,
|
||||||
|
self:getLockDelay(), self:getDropSpeed(),
|
||||||
self.drop_locked, self.hard_drop_locked,
|
self.drop_locked, self.hard_drop_locked,
|
||||||
self.enable_hard_drop, self.additive_gravity
|
self.enable_hard_drop, self.additive_gravity, self.classic_lock
|
||||||
)
|
)
|
||||||
|
|
||||||
local piece_dy = self.piece.position.y - piece_y
|
local piece_dy = self.piece.position.y - piece_y
|
||||||
|
local piece_dx = self.piece.position.x - piece_x
|
||||||
|
local piece_drot = self.piece.rotation - piece_rot
|
||||||
|
|
||||||
|
-- das cut
|
||||||
|
if (
|
||||||
|
(piece_dy ~= 0 and (inputs.up or inputs.down)) or
|
||||||
|
(piece_drot ~= 0 and (
|
||||||
|
inputs.rotate_left or inputs.rotate_right or
|
||||||
|
inputs.rotate_left2 or inputs.rotate_right2 or
|
||||||
|
inputs.rotate_180
|
||||||
|
))
|
||||||
|
) then
|
||||||
|
self:dasCut()
|
||||||
|
end
|
||||||
|
|
||||||
|
if (piece_dx ~= 0) then
|
||||||
|
self.piece.last_rotated = false
|
||||||
|
self:onPieceMove(self.piece, self.grid, piece_dx)
|
||||||
|
end
|
||||||
|
if (piece_dy ~= 0) then
|
||||||
|
self.piece.last_rotated = false
|
||||||
|
self:onPieceDrop(self.piece, self.grid, piece_dy)
|
||||||
|
end
|
||||||
|
if (piece_drot ~= 0) then
|
||||||
|
self.piece.last_rotated = true
|
||||||
|
self:onPieceRotate(self.piece, self.grid, piece_drot)
|
||||||
|
end
|
||||||
|
|
||||||
if inputs["up"] == true and
|
if inputs["up"] == true and
|
||||||
self.piece:isDropBlocked(self.grid) and
|
self.piece:isDropBlocked(self.grid) and
|
||||||
not self.hard_drop_locked then
|
not self.hard_drop_locked then
|
||||||
self:onHardDrop(piece_dy)
|
self:onHardDrop(piece_dy)
|
||||||
if self.lock_on_hard_drop then
|
if self.lock_on_hard_drop then
|
||||||
|
self.piece_hard_dropped = true
|
||||||
self.piece.locked = true
|
self.piece.locked = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if inputs["down"] == true then
|
if inputs["down"] == true then
|
||||||
|
if not (
|
||||||
|
self.piece:isDropBlocked(self.grid) and
|
||||||
|
piece_drot ~= 0
|
||||||
|
) then
|
||||||
self:onSoftDrop(piece_dy)
|
self:onSoftDrop(piece_dy)
|
||||||
|
end
|
||||||
if self.piece:isDropBlocked(self.grid) and
|
if self.piece:isDropBlocked(self.grid) and
|
||||||
not self.drop_locked and
|
not self.drop_locked and
|
||||||
self.lock_on_soft_drop
|
self.lock_on_soft_drop
|
||||||
then
|
then
|
||||||
self.piece.locked = true
|
self.piece.locked = true
|
||||||
|
self.piece_soft_locked = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.piece.locked == true then
|
if self.piece.locked == true then
|
||||||
|
-- spin detection, immobile only for now
|
||||||
|
if self.immobile_spin_bonus and
|
||||||
|
self.piece.last_rotated and (
|
||||||
|
self.piece:isDropBlocked(self.grid) and
|
||||||
|
self.piece:isMoveBlocked(self.grid, { x=-1, y=0 }) and
|
||||||
|
self.piece:isMoveBlocked(self.grid, { x=1, y=0 }) and
|
||||||
|
self.piece:isMoveBlocked(self.grid, { x=0, y=-1 })
|
||||||
|
) then
|
||||||
|
self.piece.spin = true
|
||||||
|
end
|
||||||
|
|
||||||
self.grid:applyPiece(self.piece)
|
self.grid:applyPiece(self.piece)
|
||||||
self.grid:markClearedRows()
|
|
||||||
|
-- mark squares (can be overridden)
|
||||||
|
if self.square_mode then
|
||||||
|
self.squares = self.squares + self.grid:markSquares()
|
||||||
|
end
|
||||||
|
|
||||||
local cleared_row_count = self.grid:getClearedRowCount()
|
local cleared_row_count = self.grid:getClearedRowCount()
|
||||||
|
|
||||||
self:onPieceLock(self.piece, cleared_row_count)
|
self:onPieceLock(self.piece, cleared_row_count)
|
||||||
self:updateScore(self.level, self.drop_bonus, cleared_row_count)
|
self:updateScore(self.level, self.drop_bonus, cleared_row_count)
|
||||||
|
|
||||||
|
self.cleared_block_table = self.grid:markClearedRows()
|
||||||
self.piece = nil
|
self.piece = nil
|
||||||
if self.enable_hold then
|
if self.enable_hold then
|
||||||
self.held = false
|
self.held = false
|
||||||
@@ -193,16 +271,20 @@ function GameMode:update(inputs, ruleset)
|
|||||||
if cleared_row_count > 0 then
|
if cleared_row_count > 0 then
|
||||||
playSE("erase")
|
playSE("erase")
|
||||||
self.lcd = self:getLineClearDelay()
|
self.lcd = self:getLineClearDelay()
|
||||||
self.are = self:getLineARE()
|
self.last_lcd = self.lcd
|
||||||
|
self.are = (
|
||||||
|
ruleset.are and self:getLineARE() or 0
|
||||||
|
)
|
||||||
if self.lcd == 0 then
|
if self.lcd == 0 then
|
||||||
self.grid:clearClearedRows()
|
self.grid:clearClearedRows()
|
||||||
|
self:afterLineClear(cleared_row_count)
|
||||||
if self.are == 0 then
|
if self.are == 0 then
|
||||||
self:initializeOrHold(inputs, ruleset)
|
self:initializeOrHold(inputs, ruleset)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self:onLineClear(cleared_row_count)
|
self:onLineClear(cleared_row_count)
|
||||||
else
|
else
|
||||||
if self:getARE() == 0 then
|
if self:getARE() == 0 or not ruleset.are then
|
||||||
self:initializeOrHold(inputs, ruleset)
|
self:initializeOrHold(inputs, ruleset)
|
||||||
else
|
else
|
||||||
self.are = self:getARE()
|
self.are = self:getARE()
|
||||||
@@ -225,29 +307,46 @@ end
|
|||||||
|
|
||||||
-- event functions
|
-- event functions
|
||||||
function GameMode:whilePieceActive() end
|
function GameMode:whilePieceActive() end
|
||||||
function GameMode:onAttemptPieceMove(piece) end
|
function GameMode:onAttemptPieceMove(piece, grid) end
|
||||||
function GameMode:onAttemptPieceRotate(piece) end
|
function GameMode:onAttemptPieceRotate(piece, grid) end
|
||||||
|
function GameMode:onPieceMove(piece, grid, dx) end
|
||||||
|
function GameMode:onPieceRotate(piece, grid, drot) end
|
||||||
|
function GameMode:onPieceDrop(piece, grid, dy) end
|
||||||
function GameMode:onPieceLock(piece, cleared_row_count)
|
function GameMode:onPieceLock(piece, cleared_row_count)
|
||||||
playSE("lock")
|
playSE("lock")
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameMode:onLineClear(cleared_row_count) end
|
function GameMode:onLineClear(cleared_row_count) end
|
||||||
|
function GameMode:afterLineClear(cleared_row_count) end
|
||||||
|
|
||||||
function GameMode:onPieceEnter() end
|
function GameMode:onPieceEnter() end
|
||||||
function GameMode:onHold() end
|
function GameMode:onHold() end
|
||||||
|
|
||||||
function GameMode:onSoftDrop(dropped_row_count)
|
function GameMode:onSoftDrop(dropped_row_count)
|
||||||
self.drop_bonus = self.drop_bonus + 1 * dropped_row_count
|
self.drop_bonus = self.drop_bonus + (
|
||||||
|
(self.piece.big and 2 or 1) * dropped_row_count
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameMode:onHardDrop(dropped_row_count)
|
function GameMode:onHardDrop(dropped_row_count)
|
||||||
self.drop_bonus = self.drop_bonus + 2 * dropped_row_count
|
self:onSoftDrop(dropped_row_count * 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameMode:onGameOver()
|
function GameMode:onGameOver()
|
||||||
switchBGM(nil)
|
switchBGM(nil)
|
||||||
|
love.graphics.setColor(0, 0, 0, 1 - 2 ^ (-self.game_over_frames / 30))
|
||||||
|
love.graphics.rectangle(
|
||||||
|
"fill", 64, 80,
|
||||||
|
16 * self.grid.width, 16 * (self.grid.height - 4)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function GameMode:onGameComplete()
|
||||||
|
self:onGameOver()
|
||||||
|
end
|
||||||
|
|
||||||
|
function GameMode:onExit() end
|
||||||
|
|
||||||
-- DAS functions
|
-- DAS functions
|
||||||
|
|
||||||
function GameMode:startRightDAS()
|
function GameMode:startRightDAS()
|
||||||
@@ -288,7 +387,7 @@ function GameMode:stopDAS()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function GameMode:chargeDAS(inputs)
|
function GameMode:chargeDAS(inputs)
|
||||||
if config["das_last_key"] then
|
if config.gamesettings.das_last_key == 2 then
|
||||||
if inputs["right"] == true and self.das.direction ~= "right" and not self.prev_inputs["right"] then
|
if inputs["right"] == true and self.das.direction ~= "right" and not self.prev_inputs["right"] then
|
||||||
self:startRightDAS()
|
self:startRightDAS()
|
||||||
elseif inputs["left"] == true and self.das.direction ~= "left" and not self.prev_inputs["left"] then
|
elseif inputs["left"] == true and self.das.direction ~= "left" and not self.prev_inputs["left"] then
|
||||||
@@ -311,12 +410,46 @@ function GameMode:chargeDAS(inputs)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function GameMode:dasCut()
|
||||||
|
self.das.frames = math.max(
|
||||||
|
self.das.frames - self:getDasCutDelay(),
|
||||||
|
-(self:getDasCutDelay() + 1)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function GameMode:areCancel(inputs, ruleset)
|
||||||
|
if ruleset.are_cancel and strTrueValues(inputs) ~= "" and
|
||||||
|
not self.prev_inputs.up and
|
||||||
|
(self.piece_hard_dropped or
|
||||||
|
(self.piece_soft_locked and not self.prev_inputs.down)) then
|
||||||
|
self.lcd = 0
|
||||||
|
self.are = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function GameMode:checkBufferedInputs(inputs)
|
||||||
|
if (
|
||||||
|
config.gamesettings.buffer_lock ~= 1 and
|
||||||
|
not self.prev_inputs["up"] and inputs["up"] and
|
||||||
|
self.enable_hard_drop
|
||||||
|
) then
|
||||||
|
self.buffer_hard_drop = true
|
||||||
|
end
|
||||||
|
if (
|
||||||
|
config.gamesettings.buffer_lock ~= 1 and
|
||||||
|
not self.prev_inputs["down"] and inputs["down"]
|
||||||
|
) then
|
||||||
|
self.buffer_soft_drop = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function GameMode:processDelays(inputs, ruleset, drop_speed)
|
function GameMode:processDelays(inputs, ruleset, drop_speed)
|
||||||
if self.ready_frames == 100 then
|
if self.ready_frames == 100 then
|
||||||
playedReadySE = false
|
playedReadySE = false
|
||||||
playedGoSE = false
|
playedGoSE = false
|
||||||
end
|
end
|
||||||
if self.ready_frames > 0 then
|
if self.ready_frames > 0 then
|
||||||
|
self:checkBufferedInputs(inputs)
|
||||||
if not playedReadySE then
|
if not playedReadySE then
|
||||||
playedReadySE = true
|
playedReadySE = true
|
||||||
playSEOnce("ready")
|
playSEOnce("ready")
|
||||||
@@ -330,39 +463,22 @@ function GameMode:processDelays(inputs, ruleset, drop_speed)
|
|||||||
self:initializeOrHold(inputs, ruleset)
|
self:initializeOrHold(inputs, ruleset)
|
||||||
end
|
end
|
||||||
elseif self.lcd > 0 then
|
elseif self.lcd > 0 then
|
||||||
|
self:checkBufferedInputs(inputs)
|
||||||
self.lcd = self.lcd - 1
|
self.lcd = self.lcd - 1
|
||||||
if ruleset.are_cancel and
|
self:areCancel(inputs, ruleset)
|
||||||
(self.move == "none" and not self.prev_inputs["up"] and
|
|
||||||
not self.prev_inputs["rotate_left"] and not self.prev_inputs["rotate_left2"] and
|
|
||||||
not self.prev_inputs["rotate_right"] and not self.prev_inputs["rotate_right2"] and
|
|
||||||
not self.prev_inputs["rotate_180"]) and
|
|
||||||
(inputs["left"] or inputs["right"] or inputs["up"] or
|
|
||||||
inputs["rotate_left"] or inputs["rotate_left2"] or
|
|
||||||
inputs["rotate_right"] or inputs["rotate_right2"] or
|
|
||||||
inputs["rotate_180"]) then
|
|
||||||
self.lcd = 0
|
|
||||||
self.are = 0
|
|
||||||
end
|
|
||||||
if self.lcd == 0 then
|
if self.lcd == 0 then
|
||||||
|
local cleared_row_count = self.grid:getClearedRowCount()
|
||||||
self.grid:clearClearedRows()
|
self.grid:clearClearedRows()
|
||||||
|
self:afterLineClear(cleared_row_count)
|
||||||
playSE("fall")
|
playSE("fall")
|
||||||
if self.are == 0 then
|
if self.are == 0 then
|
||||||
self:initializeOrHold(inputs, ruleset)
|
self:initializeOrHold(inputs, ruleset)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif self.are > 0 then
|
elseif self.are > 0 then
|
||||||
|
self:checkBufferedInputs(inputs)
|
||||||
self.are = self.are - 1
|
self.are = self.are - 1
|
||||||
if ruleset.are_cancel and
|
self:areCancel(inputs, ruleset)
|
||||||
(self.move == "none" and not self.prev_inputs["up"] and
|
|
||||||
not self.prev_inputs["rotate_left"] and not self.prev_inputs["rotate_left2"] and
|
|
||||||
not self.prev_inputs["rotate_right"] and not self.prev_inputs["rotate_right2"] and
|
|
||||||
not self.prev_inputs["rotate_180"]) and
|
|
||||||
(inputs["left"] or inputs["right"] or inputs["up"] or
|
|
||||||
inputs["rotate_left"] or inputs["rotate_left2"] or
|
|
||||||
inputs["rotate_right"] or inputs["rotate_right2"] or
|
|
||||||
inputs["rotate_180"]) then
|
|
||||||
self.are = 0
|
|
||||||
end
|
|
||||||
if self.are == 0 then
|
if self.are == 0 then
|
||||||
self:initializeOrHold(inputs, ruleset)
|
self:initializeOrHold(inputs, ruleset)
|
||||||
end
|
end
|
||||||
@@ -370,16 +486,16 @@ function GameMode:processDelays(inputs, ruleset, drop_speed)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function GameMode:initializeOrHold(inputs, ruleset)
|
function GameMode:initializeOrHold(inputs, ruleset)
|
||||||
if self.ihs and self.enable_hold and inputs["hold"] == true then
|
if (
|
||||||
|
(self.frames == 0 or (ruleset.are and self:getARE() ~= 0))
|
||||||
|
and self.ihs or false
|
||||||
|
) and self.enable_hold and inputs["hold"] == true then
|
||||||
self:hold(inputs, ruleset, true)
|
self:hold(inputs, ruleset, true)
|
||||||
else
|
else
|
||||||
self:initializeNextPiece(inputs, ruleset, self.next_queue[1])
|
self:initializeNextPiece(inputs, ruleset, self.next_queue[1])
|
||||||
end
|
end
|
||||||
self:onPieceEnter()
|
self:onPieceEnter()
|
||||||
if not self.grid:canPlacePiece(self.piece) then
|
self:onEnterOrHold(inputs, ruleset)
|
||||||
self:onGameOver()
|
|
||||||
self.game_over = true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameMode:hold(inputs, ruleset, ihs)
|
function GameMode:hold(inputs, ruleset, ihs)
|
||||||
@@ -392,7 +508,7 @@ function GameMode:hold(inputs, ruleset, ihs)
|
|||||||
self.hold_queue = {
|
self.hold_queue = {
|
||||||
skin = self.piece.skin,
|
skin = self.piece.skin,
|
||||||
shape = self.piece.shape,
|
shape = self.piece.shape,
|
||||||
orientation = ruleset:getDefaultOrientation(),
|
orientation = ruleset:getDefaultOrientation(self.piece.shape),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
if data == nil then
|
if data == nil then
|
||||||
@@ -401,35 +517,81 @@ function GameMode:hold(inputs, ruleset, ihs)
|
|||||||
self:initializeNextPiece(inputs, ruleset, data, false)
|
self:initializeNextPiece(inputs, ruleset, data, false)
|
||||||
end
|
end
|
||||||
self.held = true
|
self.held = true
|
||||||
if ihs then playSE("ihs")
|
|
||||||
else playSE("hold") end
|
|
||||||
self:onHold()
|
self:onHold()
|
||||||
|
if ihs then
|
||||||
|
playSE("ihs")
|
||||||
|
else
|
||||||
|
playSE("hold")
|
||||||
|
self:onEnterOrHold(inputs, ruleset)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameMode:initializeNextPiece(inputs, ruleset, piece_data, generate_next_piece)
|
function GameMode:onEnterOrHold(inputs, ruleset)
|
||||||
local gravity = self:getGravity()
|
if not self.grid:canPlacePiece(self.piece) then
|
||||||
self.piece = ruleset:initializePiece(
|
self.game_over = true
|
||||||
inputs, piece_data, self.grid, gravity,
|
return
|
||||||
self.prev_inputs, self.move,
|
end
|
||||||
self:getLockDelay(), self:getDropSpeed(),
|
ruleset:dropPiece(
|
||||||
self.lock_drop, self.lock_hard_drop, self.big_mode,
|
inputs, self.piece, self.grid, self:getGravity(),
|
||||||
self.irs
|
self:getDropSpeed(), self.drop_locked, self.hard_drop_locked
|
||||||
)
|
)
|
||||||
if self.lock_drop then
|
end
|
||||||
|
|
||||||
|
function GameMode:initializeNextPiece(
|
||||||
|
inputs, ruleset, piece_data, generate_next_piece
|
||||||
|
)
|
||||||
|
if not inputs.hold and not self.buffer_soft_drop and self.lock_drop or (
|
||||||
|
not ruleset.are or self:getARE() == 0
|
||||||
|
) then
|
||||||
self.drop_locked = true
|
self.drop_locked = true
|
||||||
end
|
end
|
||||||
if self.lock_hard_drop then
|
if not inputs.hold and not self.buffer_hard_drop and self.lock_hard_drop or (
|
||||||
|
not ruleset.are or self:getARE() == 0
|
||||||
|
) then
|
||||||
self.hard_drop_locked = true
|
self.hard_drop_locked = true
|
||||||
end
|
end
|
||||||
|
self.piece = ruleset:initializePiece(
|
||||||
|
inputs, piece_data, self.grid, self:getGravity(),
|
||||||
|
self.prev_inputs, self.move,
|
||||||
|
self:getLockDelay(), self:getDropSpeed(),
|
||||||
|
self.drop_locked, self.hard_drop_locked, self.big_mode,
|
||||||
|
(
|
||||||
|
self.frames == 0 or (ruleset.are and self:getARE() ~= 0)
|
||||||
|
) and self.irs or false
|
||||||
|
)
|
||||||
|
if config.gamesettings.buffer_lock == 3 then
|
||||||
|
if self.buffer_hard_drop then
|
||||||
|
local prev_y = self.piece.position.y
|
||||||
|
self.piece:dropToBottom(self.grid)
|
||||||
|
self.piece.locked = self.lock_on_hard_drop
|
||||||
|
self:onHardDrop(self.piece.position.y - prev_y)
|
||||||
|
end
|
||||||
|
if self.buffer_soft_drop then
|
||||||
|
if (
|
||||||
|
self.lock_on_soft_drop and
|
||||||
|
self.piece:isDropBlocked(self.grid)
|
||||||
|
) then
|
||||||
|
self.piece.locked = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.piece_hard_dropped = false
|
||||||
|
self.piece_soft_locked = false
|
||||||
|
self.buffer_hard_drop = false
|
||||||
|
self.buffer_soft_drop = false
|
||||||
|
if self.piece:isDropBlocked(self.grid) and
|
||||||
|
self.grid:canPlacePiece(self.piece) then
|
||||||
|
playSE("bottom")
|
||||||
|
end
|
||||||
if generate_next_piece == nil then
|
if generate_next_piece == nil then
|
||||||
table.remove(self.next_queue, 1)
|
table.remove(self.next_queue, 1)
|
||||||
table.insert(self.next_queue, self:getNextPiece(ruleset))
|
table.insert(self.next_queue, self:getNextPiece(ruleset))
|
||||||
end
|
end
|
||||||
if config.gamesettings.next_se == 1 then self:playNextSound() end
|
self:playNextSound(ruleset)
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameMode:playNextSound()
|
function GameMode:playNextSound(ruleset)
|
||||||
playSE("blocks", self.next_queue[1].shape)
|
playSE("blocks", ruleset.next_sounds[self.next_queue[1].shape])
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameMode:getHighScoreData()
|
function GameMode:getHighScoreData()
|
||||||
@@ -438,19 +600,98 @@ function GameMode:getHighScoreData()
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function GameMode:animation(x, y, skin, colour)
|
||||||
|
return {
|
||||||
|
1, 1, 1,
|
||||||
|
-0.25 + 1.25 * (self.lcd / self.last_lcd),
|
||||||
|
skin, colour,
|
||||||
|
48 + x * 16, y * 16
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function GameMode:canDrawLCA()
|
||||||
|
return self.lcd > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function GameMode:drawLineClearAnimation()
|
||||||
|
-- animation function
|
||||||
|
-- params: block x, y, skin, colour
|
||||||
|
-- returns: table with RGBA, skin, colour, x, y
|
||||||
|
|
||||||
|
-- Fadeout (default)
|
||||||
|
--[[
|
||||||
|
function animation(x, y, skin, colour)
|
||||||
|
return {
|
||||||
|
1, 1, 1,
|
||||||
|
-0.25 + 1.25 * (self.lcd / self.last_lcd),
|
||||||
|
skin, colour,
|
||||||
|
48 + x * 16, y * 16
|
||||||
|
}
|
||||||
|
end
|
||||||
|
--]]
|
||||||
|
|
||||||
|
-- Flash
|
||||||
|
--[[
|
||||||
|
function animation(x, y, skin, colour)
|
||||||
|
return {
|
||||||
|
1, 1, 1,
|
||||||
|
self.lcd % 6 < 3 and 1 or 0.25,
|
||||||
|
skin, colour,
|
||||||
|
48 + x * 16, y * 16
|
||||||
|
}
|
||||||
|
end
|
||||||
|
--]]
|
||||||
|
|
||||||
|
-- TGM1 pop-out
|
||||||
|
--[[
|
||||||
|
function animation(x, y, skin, colour)
|
||||||
|
local p = 0.5
|
||||||
|
local l = (
|
||||||
|
(self.last_lcd - self.lcd) / self.last_lcd
|
||||||
|
)
|
||||||
|
local dx = l * (x - (1 + self.grid.width) / 2)
|
||||||
|
local dy = l * (y - (1 + self.grid.height) / 2)
|
||||||
|
return {
|
||||||
|
1, 1, 1, 1, skin, colour,
|
||||||
|
48 + (x + dx) * 16,
|
||||||
|
(y + dy) * 16 + (464 / (p - 1)) * l * (p - l)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
--]]
|
||||||
|
|
||||||
|
for y, row in pairs(self.cleared_block_table) do
|
||||||
|
for x, block in pairs(row) do
|
||||||
|
local animation_table = self:animation(x, y, block.skin, block.colour)
|
||||||
|
love.graphics.setColor(
|
||||||
|
animation_table[1], animation_table[2],
|
||||||
|
animation_table[3], animation_table[4]
|
||||||
|
)
|
||||||
|
love.graphics.draw(
|
||||||
|
blocks[animation_table[5]][animation_table[6]],
|
||||||
|
animation_table[7], animation_table[8]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function GameMode:drawPiece()
|
function GameMode:drawPiece()
|
||||||
if self.piece ~= nil then
|
if self.piece ~= nil then
|
||||||
self.piece:draw(
|
local b = (
|
||||||
1,
|
self.classic_lock and
|
||||||
self:getLockDelay() == 0 and 1 or
|
(
|
||||||
(0.25 + 0.75 * math.max(1 - self.piece.gravity, 1 - (self.piece.lock_delay / self:getLockDelay()))),
|
self.piece:isDropBlocked(self.grid) and
|
||||||
self.grid
|
1 - self.piece.gravity or 1
|
||||||
|
) or
|
||||||
|
1 - (self.piece.lock_delay / self:getLockDelay())
|
||||||
)
|
)
|
||||||
|
self.piece:draw(1, 0.25 + 0.75 * b, self.grid)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameMode:drawGhostPiece(ruleset)
|
function GameMode:drawGhostPiece(ruleset)
|
||||||
if self.piece == nil then return end
|
if self.piece == nil or not self.grid:canPlacePiece(self.piece) then
|
||||||
|
return
|
||||||
|
end
|
||||||
local ghost_piece = self.piece:withOffset({x=0, y=0})
|
local ghost_piece = self.piece:withOffset({x=0, y=0})
|
||||||
ghost_piece.ghost = true
|
ghost_piece.ghost = true
|
||||||
ghost_piece:dropToBottom(self.grid)
|
ghost_piece:dropToBottom(self.grid)
|
||||||
@@ -458,11 +699,16 @@ function GameMode:drawGhostPiece(ruleset)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function GameMode:drawNextQueue(ruleset)
|
function GameMode:drawNextQueue(ruleset)
|
||||||
local colourscheme = ({ruleset.colourscheme, ColourSchemes.Arika, ColourSchemes.TTC})[config.gamesettings.piece_colour]
|
local colourscheme
|
||||||
|
if ruleset.pieces == 7 then
|
||||||
|
colourscheme = ({ruleset.colourscheme, ColourSchemes.Arika, ColourSchemes.TTC})[config.gamesettings.piece_colour]
|
||||||
|
else
|
||||||
|
colourscheme = ruleset.colourscheme
|
||||||
|
end
|
||||||
function drawPiece(piece, skin, offsets, pos_x, pos_y)
|
function drawPiece(piece, skin, offsets, pos_x, pos_y)
|
||||||
for index, offset in pairs(offsets) do
|
for index, offset in pairs(offsets) do
|
||||||
local x = offset.x + ruleset.spawn_positions[piece].x
|
local x = offset.x + ruleset:getDrawOffset(piece, rotation).x + ruleset.spawn_positions[piece].x
|
||||||
local y = offset.y + 4.7
|
local y = offset.y + ruleset:getDrawOffset(piece, rotation).y + 4.7
|
||||||
love.graphics.draw(blocks[skin][colourscheme[piece]], pos_x+x*16, pos_y+y*16)
|
love.graphics.draw(blocks[skin][colourscheme[piece]], pos_x+x*16, pos_y+y*16)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -478,8 +724,7 @@ function GameMode:drawNextQueue(ruleset)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
if self.hold_queue ~= nil and self.enable_hold then
|
if self.hold_queue ~= nil and self.enable_hold then
|
||||||
local hold_color = self.held and 0.6 or 1
|
self:setHoldOpacity()
|
||||||
self:setHoldOpacity(1, hold_color)
|
|
||||||
drawPiece(
|
drawPiece(
|
||||||
self.hold_queue.shape,
|
self.hold_queue.shape,
|
||||||
self.hold_queue.skin,
|
self.hold_queue.skin,
|
||||||
@@ -490,15 +735,25 @@ function GameMode:drawNextQueue(ruleset)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameMode:setNextOpacity(i, j)
|
function GameMode:setNextOpacity(i)
|
||||||
i = i ~= nil and i or 1
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
j = j ~= nil and j or 1
|
|
||||||
love.graphics.setColor(j, j, j, i)
|
|
||||||
end
|
end
|
||||||
function GameMode:setHoldOpacity(i, j)
|
|
||||||
i = i ~= nil and i or 1
|
function GameMode:setHoldOpacity()
|
||||||
j = j ~= nil and j or 1
|
local colour = self.held and 0.6 or 1
|
||||||
love.graphics.setColor(j, j, j, i)
|
love.graphics.setColor(colour, colour, colour, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function GameMode:getBackground()
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function GameMode:getHighscoreData()
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function GameMode:drawGrid()
|
||||||
|
self.grid:draw()
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameMode:drawScoringInfo()
|
function GameMode:drawScoringInfo()
|
||||||
@@ -538,7 +793,8 @@ function GameMode:sectionColourFunction(section)
|
|||||||
return { 1, 1, 1, 1 }
|
return { 1, 1, 1, 1 }
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameMode:drawSectionTimesWithSecondary(current_section)
|
function GameMode:drawSectionTimesWithSecondary(current_section, section_limit)
|
||||||
|
section_limit = section_limit or math.huge
|
||||||
local section_x = 530
|
local section_x = 530
|
||||||
local section_secondary_x = 440
|
local section_secondary_x = 440
|
||||||
|
|
||||||
@@ -563,10 +819,14 @@ function GameMode:drawSectionTimesWithSecondary(current_section)
|
|||||||
current_x = section_secondary_x
|
current_x = section_secondary_x
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if current_section <= section_limit then
|
||||||
love.graphics.printf(formatTime(self.frames - self.section_start_time), current_x, 40 + 20 * current_section, 90, "left")
|
love.graphics.printf(formatTime(self.frames - self.section_start_time), current_x, 40 + 20 * current_section, 90, "left")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function GameMode:drawSectionTimesWithSplits(current_section, section_limit)
|
||||||
|
section_limit = section_limit or math.huge
|
||||||
|
|
||||||
function GameMode:drawSectionTimesWithSplits(current_section)
|
|
||||||
local section_x = 440
|
local section_x = 440
|
||||||
local split_x = 530
|
local split_x = 530
|
||||||
|
|
||||||
@@ -574,16 +834,118 @@ function GameMode:drawSectionTimesWithSplits(current_section)
|
|||||||
|
|
||||||
for section, time in pairs(self.section_times) do
|
for section, time in pairs(self.section_times) do
|
||||||
if section > 0 then
|
if section > 0 then
|
||||||
|
love.graphics.setColor(self:sectionColourFunction(section))
|
||||||
love.graphics.printf(formatTime(time), section_x, 40 + 20 * section, 90, "left")
|
love.graphics.printf(formatTime(time), section_x, 40 + 20 * section, 90, "left")
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
split_time = split_time + time
|
split_time = split_time + time
|
||||||
love.graphics.printf(formatTime(split_time), split_x, 40 + 20 * section, 90, "left")
|
love.graphics.printf(formatTime(split_time), split_x, 40 + 20 * section, 90, "left")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if (current_section <= section_limit) then
|
||||||
love.graphics.printf(formatTime(self.frames - self.section_start_time), section_x, 40 + 20 * current_section, 90, "left")
|
love.graphics.printf(formatTime(self.frames - self.section_start_time), section_x, 40 + 20 * current_section, 90, "left")
|
||||||
love.graphics.printf(formatTime(self.frames), split_x, 40 + 20 * current_section, 90, "left")
|
love.graphics.printf(formatTime(self.frames), split_x, 40 + 20 * current_section, 90, "left")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function GameMode:drawBackground()
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
love.graphics.draw(
|
||||||
|
backgrounds[self:getBackground()],
|
||||||
|
0, 0, 0,
|
||||||
|
0.5, 0.5
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function GameMode:drawFrame()
|
||||||
|
-- game frame
|
||||||
|
if self.grid.width == 10 and self.grid.height == 24 then
|
||||||
|
love.graphics.draw(misc_graphics["frame"], 48, 64)
|
||||||
|
else
|
||||||
|
love.graphics.setColor(174/255, 83/255, 76/255, 1)
|
||||||
|
love.graphics.setLineWidth(8)
|
||||||
|
love.graphics.line(
|
||||||
|
60,76,
|
||||||
|
68+16*self.grid.width,76,
|
||||||
|
68+16*self.grid.width,84+16*(self.grid.height-4),
|
||||||
|
60,84+16*(self.grid.height-4),
|
||||||
|
60,76
|
||||||
|
)
|
||||||
|
love.graphics.setColor(203/255, 137/255, 111/255, 1)
|
||||||
|
love.graphics.setLineWidth(4)
|
||||||
|
love.graphics.line(
|
||||||
|
60,76,
|
||||||
|
68+16*self.grid.width,76,
|
||||||
|
68+16*self.grid.width,84+16*(self.grid.height-4),
|
||||||
|
60,84+16*(self.grid.height-4),
|
||||||
|
60,76
|
||||||
|
)
|
||||||
|
love.graphics.setLineWidth(1)
|
||||||
|
love.graphics.setColor(0, 0, 0, 200)
|
||||||
|
love.graphics.rectangle(
|
||||||
|
"fill", 64, 80,
|
||||||
|
16 * self.grid.width, 16 * (self.grid.height - 4)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function GameMode:drawReadyGo()
|
||||||
|
-- ready/go graphics
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
|
||||||
|
if self.ready_frames <= 100 and self.ready_frames > 52 then
|
||||||
|
love.graphics.draw(misc_graphics["ready"], 144 - 50, 240 - 14)
|
||||||
|
elseif self.ready_frames <= 50 and self.ready_frames > 2 then
|
||||||
|
love.graphics.draw(misc_graphics["go"], 144 - 27, 240 - 14)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function GameMode:drawCustom() end
|
function GameMode:drawCustom() end
|
||||||
|
|
||||||
|
function GameMode:drawIfPaused()
|
||||||
|
love.graphics.setFont(font_3x5_3)
|
||||||
|
love.graphics.printf("GAME PAUSED!", 64, 160, 160, "center")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- transforms specified in here will transform the whole screen
|
||||||
|
-- if you want a transform for a particular component, push the
|
||||||
|
-- default transform by using love.graphics.push(), do your
|
||||||
|
-- transform, and then love.graphics.pop() at the end of that
|
||||||
|
-- component's draw call!
|
||||||
|
function GameMode:transformScreen() end
|
||||||
|
|
||||||
|
function GameMode:draw(paused)
|
||||||
|
self:transformScreen()
|
||||||
|
self:drawBackground()
|
||||||
|
self:drawFrame()
|
||||||
|
self:drawGrid()
|
||||||
|
self:drawPiece()
|
||||||
|
self:drawNextQueue(self.ruleset)
|
||||||
|
self:drawScoringInfo()
|
||||||
|
self:drawReadyGo()
|
||||||
|
self:drawCustom()
|
||||||
|
if self:canDrawLCA() then
|
||||||
|
self:drawLineClearAnimation()
|
||||||
|
end
|
||||||
|
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
love.graphics.setFont(font_3x5_2)
|
||||||
|
if config.gamesettings.display_gamemode == 1 then
|
||||||
|
love.graphics.printf(
|
||||||
|
self.name .. " - " .. self.ruleset.name,
|
||||||
|
0, 460, 640, "left"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
if paused then
|
||||||
|
self:drawIfPaused()
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.completed then
|
||||||
|
self:onGameComplete()
|
||||||
|
elseif self.game_over then
|
||||||
|
self:onGameOver()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return GameMode
|
return GameMode
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ function Marathon2020Game:advanceOneFrame()
|
|||||||
if self.roll_frames < 0 then
|
if self.roll_frames < 0 then
|
||||||
return false
|
return false
|
||||||
elseif self.roll_frames > 4000 then
|
elseif self.roll_frames > 4000 then
|
||||||
if self.grade >= 30 and self.section_cool_count >= 20 then self.grade = 31 end
|
if self:qualifiesForMRoll() then self.grade = 31 end
|
||||||
self.completed = true
|
self.completed = true
|
||||||
end
|
end
|
||||||
elseif self.ready_frames == 0 then
|
elseif self.ready_frames == 0 then
|
||||||
@@ -154,11 +154,11 @@ function Marathon2020Game:advanceOneFrame()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local cool_cutoffs = {
|
local cool_cutoffs = {
|
||||||
frameTime(0,45,00), frameTime(0,41,50), frameTime(0,38,50), frameTime(0,35,00), frameTime(0,32,50),
|
[0] = frameTime(0,45,00),
|
||||||
frameTime(0,29,20), frameTime(0,27,20), frameTime(0,24,80), frameTime(0,22,80), frameTime(0,20,60),
|
frameTime(0,41,50), frameTime(0,38,50), frameTime(0,35,00), frameTime(0,32,50), frameTime(0,29,20),
|
||||||
frameTime(0,19,60), frameTime(0,19,40), frameTime(0,19,40), frameTime(0,18,40), frameTime(0,18,20),
|
frameTime(0,27,20), frameTime(0,24,80), frameTime(0,22,80), frameTime(0,20,60), frameTime(0,19,60),
|
||||||
frameTime(0,16,20), frameTime(0,16,20), frameTime(0,16,20), frameTime(0,16,20), frameTime(0,16,20),
|
frameTime(0,19,40), frameTime(0,19,40), frameTime(0,18,40), frameTime(0,18,20), frameTime(0,16,20),
|
||||||
frameTime(0,15,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 }
|
local levels_for_cleared_rows = { 1, 2, 4, 6 }
|
||||||
@@ -227,13 +227,14 @@ local mid_cleared_line_points = {2, 6, 12, 24}
|
|||||||
local high_cleared_line_points = {1, 4, 9, 20}
|
local high_cleared_line_points = {1, 4, 9, 20}
|
||||||
|
|
||||||
local function getGradeForGradePoints(points)
|
local function getGradeForGradePoints(points)
|
||||||
return math.floor(math.sqrt((points / 50) * 8 + 1) / 2 - 0.5)
|
return math.min(30, math.floor(math.sqrt((points / 50) * 8 + 1) / 2 - 0.5))
|
||||||
-- Don't be afraid of the above function. All it does is make it so that
|
-- Don't be afraid of the above function. All it does is make it so that
|
||||||
-- you need 50 points to get to grade 1, 100 points to grade 2, etc.
|
-- you need 50 points to get to grade 1, 100 points to grade 2, etc.
|
||||||
end
|
end
|
||||||
|
|
||||||
function Marathon2020Game:updateGrade(cleared_lines)
|
function Marathon2020Game:updateGrade(cleared_lines)
|
||||||
-- update grade points and max grade points
|
-- update grade points and max grade points
|
||||||
|
if self.clear then return end
|
||||||
local point_level = math.floor(self.level / 100) + self.delay_level
|
local point_level = math.floor(self.level / 100) + self.delay_level
|
||||||
local plus_points = math.max(
|
local plus_points = math.max(
|
||||||
low_cleared_line_points[cleared_lines],
|
low_cleared_line_points[cleared_lines],
|
||||||
@@ -249,15 +250,16 @@ function Marathon2020Game:updateGrade(cleared_lines)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Marathon2020Game:getTotalGrade()
|
function Marathon2020Game:getTotalGrade()
|
||||||
if self.grade + self.section_cool_count > 50 then return "GM" end
|
|
||||||
return self.grade + self.section_cool_count
|
return self.grade + self.section_cool_count
|
||||||
end
|
end
|
||||||
|
|
||||||
local function getSectionForLevel(level)
|
local function getSectionForLevel(level)
|
||||||
if level < 2001 then
|
if level < 2000 then
|
||||||
return math.floor(level / 100) + 1
|
return math.floor(level / 100) + 1
|
||||||
else
|
elseif level < 2020 then
|
||||||
return 20
|
return 20
|
||||||
|
else
|
||||||
|
return 21
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -288,10 +290,10 @@ function Marathon2020Game:sectionPassed(old_level, new_level)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Marathon2020Game:checkTorikan(section)
|
function Marathon2020Game:checkTorikan(section)
|
||||||
if section == 5 and self.frames < frameTime(6,00,00) then self.torikan_passed[500] = true end
|
if section == 5 and self.frames < frameTime(8,00,00) then self.torikan_passed[500] = true end
|
||||||
if section == 9 and self.frames < frameTime(8,30,00) then self.torikan_passed[900] = true end
|
if section == 9 and self.frames < frameTime(10,30,00) then self.torikan_passed[900] = true end
|
||||||
if section == 10 and self.frames < frameTime(8,45,00) then self.torikan_passed[1000] = true end
|
if section == 10 and self.frames < frameTime(10,45,00) then self.torikan_passed[1000] = true end
|
||||||
if section == 15 and self.frames < frameTime(11,30,00) then self.torikan_passed[1500] = true end
|
if section == 15 and self.frames < frameTime(12,30,00) then self.torikan_passed[1500] = true end
|
||||||
if section == 19 and self.frames < frameTime(13,15,00) then self.torikan_passed[1900] = true end
|
if section == 19 and self.frames < frameTime(13,15,00) then self.torikan_passed[1900] = true end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -329,14 +331,16 @@ end
|
|||||||
function Marathon2020Game:updateSectionTimes(old_level, new_level)
|
function Marathon2020Game:updateSectionTimes(old_level, new_level)
|
||||||
function sectionCool(section)
|
function sectionCool(section)
|
||||||
self.section_cool_count = self.section_cool_count + 1
|
self.section_cool_count = self.section_cool_count + 1
|
||||||
|
if section <= 10 then
|
||||||
self.delay_level = math.min(20, self.delay_level + 1)
|
self.delay_level = math.min(20, self.delay_level + 1)
|
||||||
if section < 10 then table.insert(self.section_status, "cool") end
|
end
|
||||||
|
table.insert(self.section_status, "cool")
|
||||||
self.cool_timer = 300
|
self.cool_timer = 300
|
||||||
end
|
end
|
||||||
|
|
||||||
local section = getSectionForLevel(old_level)
|
local section = getSectionForLevel(old_level)
|
||||||
|
|
||||||
if section <= 19 and old_level % 100 < 70 and new_level >= math.floor(old_level / 100) * 100 + 70 then
|
if old_level % 100 < 70 and new_level >= math.floor(old_level / 100) * 100 + 70 then
|
||||||
-- record section 70 time
|
-- record section 70 time
|
||||||
section_70_time = self.frames - self.section_start_time
|
section_70_time = self.frames - self.section_start_time
|
||||||
table.insert(self.secondary_section_times, section_70_time)
|
table.insert(self.secondary_section_times, section_70_time)
|
||||||
@@ -348,23 +352,25 @@ function Marathon2020Game:updateSectionTimes(old_level, new_level)
|
|||||||
table.insert(self.section_times, section_time)
|
table.insert(self.section_times, section_time)
|
||||||
self.section_start_time = self.frames
|
self.section_start_time = self.frames
|
||||||
|
|
||||||
if section > 5 then self.delay_level = math.min(20, self.delay_level + 1) end
|
|
||||||
self:checkTorikan(section)
|
|
||||||
self:checkClear(new_level)
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
section <= 19 and self.section_status[section - 1] == "cool" and
|
self.section_status[section - 1] == "cool" and
|
||||||
self.secondary_section_times[section] < self.secondary_section_times[section - 1] + 120 and
|
self.secondary_section_times[section] <= self.secondary_section_times[section - 1] + 120 and
|
||||||
self.secondary_section_times[section] < cool_cutoffs[section]
|
self.secondary_section_times[section] < cool_cutoffs[self.delay_level]
|
||||||
) then
|
) then
|
||||||
sectionCool(section)
|
sectionCool(section)
|
||||||
elseif self.section_status[section - 1] == "cool" then
|
elseif self.section_status[section - 1] == "cool" then
|
||||||
table.insert(self.section_status, "none")
|
table.insert(self.section_status, "none")
|
||||||
elseif section <= 19 and self.secondary_section_times[section] < cool_cutoffs[section] then
|
elseif self.secondary_section_times[section] < cool_cutoffs[self.delay_level] then
|
||||||
sectionCool(section)
|
sectionCool(section)
|
||||||
else
|
else
|
||||||
table.insert(self.section_status, "none")
|
table.insert(self.section_status, "none")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if section > 5 then
|
||||||
|
self.delay_level = math.min(20, self.delay_level + 1)
|
||||||
|
end
|
||||||
|
self:checkTorikan(section)
|
||||||
|
self:checkClear(new_level)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -401,11 +407,12 @@ GM-roll requirements
|
|||||||
You qualify for the GM roll if you:
|
You qualify for the GM roll if you:
|
||||||
- Reach level 2020
|
- Reach level 2020
|
||||||
- with a grade of 50
|
- with a grade of 50
|
||||||
|
- and at least 25,000 grade points
|
||||||
- in less than 13:30.00 total.
|
- in less than 13:30.00 total.
|
||||||
|
|
||||||
]]--
|
]]--
|
||||||
|
|
||||||
return self.level >= 2020 and self:getTotalGrade() == 50 and self.frames <= frameTime(13,30)
|
return self.level >= 2020 and self:getTotalGrade() == 50 and self.grade_points >= 25000 and self.frames <= frameTime(13,30)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Marathon2020Game:drawGrid()
|
function Marathon2020Game:drawGrid()
|
||||||
@@ -442,7 +449,7 @@ function Marathon2020Game:drawScoringInfo()
|
|||||||
love.graphics.printf("GRADE PTS.", text_x, 200, 90, "left")
|
love.graphics.printf("GRADE PTS.", text_x, 200, 90, "left")
|
||||||
love.graphics.printf("LEVEL", text_x, 320, 40, "left")
|
love.graphics.printf("LEVEL", text_x, 320, 40, "left")
|
||||||
|
|
||||||
self:drawSectionTimesWithSecondary(current_section)
|
self:drawSectionTimesWithSecondary(current_section, 20)
|
||||||
|
|
||||||
if (self.cool_timer > 0) then
|
if (self.cool_timer > 0) then
|
||||||
love.graphics.printf("COOL!!", 64, 400, 160, "center")
|
love.graphics.printf("COOL!!", 64, 400, 160, "center")
|
||||||
@@ -450,7 +457,13 @@ function Marathon2020Game:drawScoringInfo()
|
|||||||
end
|
end
|
||||||
|
|
||||||
love.graphics.setFont(font_3x5_3)
|
love.graphics.setFont(font_3x5_3)
|
||||||
love.graphics.printf(self:getTotalGrade(), text_x, 120, 90, "left")
|
|
||||||
|
local grade = self:getTotalGrade()
|
||||||
|
love.graphics.printf(
|
||||||
|
grade > 50 and "GM" or grade,
|
||||||
|
text_x, 120, 90, "left"
|
||||||
|
)
|
||||||
|
|
||||||
love.graphics.printf(self.grade_points, text_x, 220, 90, "left")
|
love.graphics.printf(self.grade_points, text_x, 220, 90, "left")
|
||||||
love.graphics.printf(self.level, text_x, 340, 50, "right")
|
love.graphics.printf(self.level, text_x, 340, 50, "right")
|
||||||
|
|
||||||
@@ -464,7 +477,7 @@ end
|
|||||||
|
|
||||||
function Marathon2020Game:getHighscoreData()
|
function Marathon2020Game:getHighscoreData()
|
||||||
return {
|
return {
|
||||||
grade = self.grade,
|
grade = self:getTotalGrade(),
|
||||||
level = self.level,
|
level = self.level,
|
||||||
frames = self.frames,
|
frames = self.frames,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ require 'funcs'
|
|||||||
|
|
||||||
local GameMode = require 'tetris.modes.gamemode'
|
local GameMode = require 'tetris.modes.gamemode'
|
||||||
local Piece = require 'tetris.components.piece'
|
local Piece = require 'tetris.components.piece'
|
||||||
|
local Grid = require 'tetris.components.grid'
|
||||||
|
|
||||||
local History4RollsRandomizer = require 'tetris.randomizers.history_4rolls'
|
local History4RollsRandomizer = require 'tetris.randomizers.history_4rolls'
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ function MarathonA1Game:new()
|
|||||||
|
|
||||||
self.randomizer = History4RollsRandomizer()
|
self.randomizer = History4RollsRandomizer()
|
||||||
|
|
||||||
|
self.additive_gravity = false
|
||||||
self.lock_drop = false
|
self.lock_drop = false
|
||||||
self.enable_hard_drop = false
|
self.enable_hard_drop = false
|
||||||
self.enable_hold = false
|
self.enable_hold = false
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ function MarathonA2Game:new()
|
|||||||
|
|
||||||
self.roll_frames = 0
|
self.roll_frames = 0
|
||||||
self.combo = 1
|
self.combo = 1
|
||||||
|
self.grade_combo = 1
|
||||||
self.randomizer = History6RollsRandomizer()
|
self.randomizer = History6RollsRandomizer()
|
||||||
self.grade = 0
|
self.grade = 0
|
||||||
self.grade_points = 0
|
self.grade_points = 0
|
||||||
@@ -26,6 +27,7 @@ function MarathonA2Game:new()
|
|||||||
self.section_start_time = 0
|
self.section_start_time = 0
|
||||||
self.section_times = { [0] = 0 }
|
self.section_times = { [0] = 0 }
|
||||||
self.section_tetrises = { [0] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
|
self.section_tetrises = { [0] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||||
|
self.tetris_count = 0
|
||||||
|
|
||||||
self.SGnames = {
|
self.SGnames = {
|
||||||
"9", "8", "7", "6", "5", "4", "3", "2", "1",
|
"9", "8", "7", "6", "5", "4", "3", "2", "1",
|
||||||
@@ -33,6 +35,7 @@ function MarathonA2Game:new()
|
|||||||
"GM"
|
"GM"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.additive_gravity = false
|
||||||
self.lock_drop = false
|
self.lock_drop = false
|
||||||
self.lock_hard_drop = false
|
self.lock_hard_drop = false
|
||||||
self.enable_hold = false
|
self.enable_hold = false
|
||||||
@@ -130,21 +133,33 @@ end
|
|||||||
function MarathonA2Game:updateScore(level, drop_bonus, cleared_lines)
|
function MarathonA2Game:updateScore(level, drop_bonus, cleared_lines)
|
||||||
if not self.clear then
|
if not self.clear then
|
||||||
self:updateGrade(cleared_lines)
|
self:updateGrade(cleared_lines)
|
||||||
if self.grid:checkForBravo(cleared_lines) then self.bravo = 4 else self.bravo = 1 end
|
if cleared_lines >= 4 then
|
||||||
|
self.tetris_count = self.tetris_count + 1
|
||||||
|
end
|
||||||
|
if self.grid:checkForBravo(cleared_lines) then
|
||||||
|
self.bravo = 4
|
||||||
|
else
|
||||||
|
self.bravo = 1
|
||||||
|
end
|
||||||
if cleared_lines > 0 then
|
if cleared_lines > 0 then
|
||||||
self.combo = self.combo + (cleared_lines - 1) * 2
|
self.combo = self.combo + (cleared_lines - 1) * 2
|
||||||
|
if cleared_lines > 1 then
|
||||||
|
self.grade_combo = self.grade_combo + 1
|
||||||
|
end
|
||||||
self.score = self.score + (
|
self.score = self.score + (
|
||||||
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
|
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
|
||||||
cleared_lines * self.combo * self.bravo
|
cleared_lines * self.combo * self.bravo
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
self.combo = 1
|
self.combo = 1
|
||||||
|
self.grade_combo = 1
|
||||||
end
|
end
|
||||||
self.drop_bonus = 0
|
self.drop_bonus = 0
|
||||||
else self.lines = self.lines + cleared_lines end
|
else self.lines = self.lines + cleared_lines end
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonA2Game:onLineClear(cleared_row_count)
|
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)
|
self.level = math.min(self.level + cleared_row_count, 999)
|
||||||
if self.level == 999 and not self.clear then
|
if self.level == 999 and not self.clear then
|
||||||
self.clear = true
|
self.clear = true
|
||||||
@@ -164,6 +179,8 @@ function MarathonA2Game:updateSectionTimes(old_level, new_level)
|
|||||||
section_time = self.frames - self.section_start_time
|
section_time = self.frames - self.section_start_time
|
||||||
self.section_times[math.floor(old_level / 100)] = section_time
|
self.section_times[math.floor(old_level / 100)] = section_time
|
||||||
self.section_start_time = self.frames
|
self.section_start_time = self.frames
|
||||||
|
self.section_tetrises[math.floor(old_level / 100)] = self.tetris_count
|
||||||
|
self.tetris_count = 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -231,19 +248,21 @@ local grade_conversion = {
|
|||||||
17, 18, 19
|
17, 18, 19
|
||||||
}
|
}
|
||||||
|
|
||||||
function MarathonA2Game:updateGrade(cleared_lines)
|
function MarathonA2Game:whilePieceActive()
|
||||||
if self.clear then return end
|
|
||||||
if cleared_lines == 0 then
|
|
||||||
self.grade_point_decay_counter = self.grade_point_decay_counter + 1
|
self.grade_point_decay_counter = self.grade_point_decay_counter + 1
|
||||||
if self.grade_point_decay_counter >= grade_point_decays[self.grade + 1] then
|
if self.grade_point_decay_counter >= grade_point_decays[self.grade + 1] then
|
||||||
self.grade_point_decay_counter = 0
|
self.grade_point_decay_counter = 0
|
||||||
self.grade_points = math.max(0, self.grade_points - 1)
|
self.grade_points = math.max(0, self.grade_points - 1)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MarathonA2Game:updateGrade(cleared_lines)
|
||||||
|
if self.clear or cleared_lines == 0 then return
|
||||||
else
|
else
|
||||||
self.grade_points = self.grade_points + (
|
self.grade_points = self.grade_points + (
|
||||||
math.ceil(
|
math.ceil(
|
||||||
grade_point_bonuses[self.grade + 1][cleared_lines] *
|
grade_point_bonuses[self.grade + 1][cleared_lines] *
|
||||||
combo_multipliers[math.min(self.combo, 10)][cleared_lines]
|
combo_multipliers[math.min(self.grade_combo, 10)][cleared_lines]
|
||||||
) * (1 + math.floor(self.level / 250))
|
) * (1 + math.floor(self.level / 250))
|
||||||
)
|
)
|
||||||
if self.grade_points >= 100 and self.grade < 31 then
|
if self.grade_points >= 100 and self.grade < 31 then
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ function MarathonA3Game:new()
|
|||||||
self.speed_level = 0
|
self.speed_level = 0
|
||||||
self.roll_frames = 0
|
self.roll_frames = 0
|
||||||
self.combo = 1
|
self.combo = 1
|
||||||
|
self.grade_combo = 1
|
||||||
self.grade = 0
|
self.grade = 0
|
||||||
self.grade_points = 0
|
self.grade_points = 0
|
||||||
self.roll_points = 0
|
self.roll_points = 0
|
||||||
@@ -27,7 +28,7 @@ function MarathonA3Game:new()
|
|||||||
self.section_cool_grade = 0
|
self.section_cool_grade = 0
|
||||||
self.section_status = { [0] = "none" }
|
self.section_status = { [0] = "none" }
|
||||||
self.section_start_time = 0
|
self.section_start_time = 0
|
||||||
self.section_70_times = { [0] = 0 }
|
self.secondary_section_times = { [0] = 0 }
|
||||||
self.section_times = { [0] = 0 }
|
self.section_times = { [0] = 0 }
|
||||||
self.section_cool = false
|
self.section_cool = false
|
||||||
|
|
||||||
@@ -39,6 +40,7 @@ self.SGnames = {
|
|||||||
"GM"
|
"GM"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.additive_gravity = false
|
||||||
self.lock_drop = true
|
self.lock_drop = true
|
||||||
self.lock_hard_drop = true
|
self.lock_hard_drop = true
|
||||||
self.enable_hold = true
|
self.enable_hold = true
|
||||||
@@ -163,8 +165,8 @@ function MarathonA3Game:onLineClear(cleared_row_count)
|
|||||||
self:updateSectionTimes(self.level, self.level + advanced_levels)
|
self:updateSectionTimes(self.level, self.level + advanced_levels)
|
||||||
if not self.clear then
|
if not self.clear then
|
||||||
self.level = math.min(self.level + advanced_levels, 999)
|
self.level = math.min(self.level + advanced_levels, 999)
|
||||||
end
|
|
||||||
self.speed_level = self.speed_level + advanced_levels
|
self.speed_level = self.speed_level + advanced_levels
|
||||||
|
end
|
||||||
if self.level == 999 and not self.clear then
|
if self.level == 999 and not self.clear then
|
||||||
self.clear = true
|
self.clear = true
|
||||||
self.grid:clear()
|
self.grid:clear()
|
||||||
@@ -194,7 +196,7 @@ function MarathonA3Game:updateSectionTimes(old_level, new_level)
|
|||||||
-- record new section
|
-- record new section
|
||||||
section_time = self.frames - self.section_start_time
|
section_time = self.frames - self.section_start_time
|
||||||
table.insert(self.section_times, section_time)
|
table.insert(self.section_times, section_time)
|
||||||
self.section_start_time = self.frames
|
if new_level < 999 then self.section_start_time = self.frames end
|
||||||
|
|
||||||
self.speed_level = self.section_cool and self.speed_level + 100 or self.speed_level
|
self.speed_level = self.section_cool and self.speed_level + 100 or self.speed_level
|
||||||
|
|
||||||
@@ -214,15 +216,10 @@ function MarathonA3Game:updateSectionTimes(old_level, new_level)
|
|||||||
elseif old_level % 100 < 70 and new_level % 100 >= 70 then
|
elseif old_level % 100 < 70 and new_level % 100 >= 70 then
|
||||||
-- record section 70 time
|
-- record section 70 time
|
||||||
section_70_time = self.frames - self.section_start_time
|
section_70_time = self.frames - self.section_start_time
|
||||||
table.insert(self.section_70_times, section_70_time)
|
table.insert(self.secondary_section_times, section_70_time)
|
||||||
|
|
||||||
if section <= 9 and self.section_status[section - 1] == "cool" and
|
if section <= 9 and self.secondary_section_times[section] < cool_cutoffs[section] and
|
||||||
self.section_70_times[section] < self.section_70_times[section - 1] + 120 then
|
(section == 1 or self.secondary_section_times[section] <= self.secondary_section_times[section - 1] + 120) then
|
||||||
self.section_cool = true
|
|
||||||
self.coolregret_message = "COOL!!"
|
|
||||||
self.coolregret_timer = 300
|
|
||||||
elseif self.section_status[section - 1] == "cool" then self.section_cool = false
|
|
||||||
elseif section <= 9 and self.section_70_times[section] < cool_cutoffs[section] then
|
|
||||||
self.section_cool = true
|
self.section_cool = true
|
||||||
self.coolregret_message = "COOL!!"
|
self.coolregret_message = "COOL!!"
|
||||||
self.coolregret_timer = 300
|
self.coolregret_timer = 300
|
||||||
@@ -235,12 +232,16 @@ function MarathonA3Game:updateScore(level, drop_bonus, cleared_lines)
|
|||||||
if not self.clear then
|
if not self.clear then
|
||||||
if cleared_lines > 0 then
|
if cleared_lines > 0 then
|
||||||
self.combo = self.combo + (cleared_lines - 1) * 2
|
self.combo = self.combo + (cleared_lines - 1) * 2
|
||||||
|
if cleared_lines > 1 then
|
||||||
|
self.grade_combo = self.grade_combo + 1
|
||||||
|
end
|
||||||
self.score = self.score + (
|
self.score = self.score + (
|
||||||
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
|
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
|
||||||
cleared_lines * self.combo
|
cleared_lines * self.combo
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
self.combo = 1
|
self.combo = 1
|
||||||
|
self.grade_combo = 1
|
||||||
end
|
end
|
||||||
self.drop_bonus = 0
|
self.drop_bonus = 0
|
||||||
end
|
end
|
||||||
@@ -313,13 +314,16 @@ local grade_conversion = {
|
|||||||
17
|
17
|
||||||
}
|
}
|
||||||
|
|
||||||
function MarathonA3Game:updateGrade(cleared_lines)
|
function MarathonA3Game:whilePieceActive()
|
||||||
if cleared_lines == 0 then
|
|
||||||
self.grade_point_decay_counter = self.grade_point_decay_counter + 1
|
self.grade_point_decay_counter = self.grade_point_decay_counter + 1
|
||||||
if self.grade_point_decay_counter >= grade_point_decays[self.grade + 1] then
|
if self.grade_point_decay_counter >= grade_point_decays[self.grade + 1] then
|
||||||
self.grade_point_decay_counter = 0
|
self.grade_point_decay_counter = 0
|
||||||
self.grade_points = math.max(0, self.grade_points - 1)
|
self.grade_points = math.max(0, self.grade_points - 1)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function MarathonA3Game:updateGrade(cleared_lines)
|
||||||
|
if cleared_lines == 0 then return
|
||||||
else
|
else
|
||||||
if self.clear then
|
if self.clear then
|
||||||
if self:qualifiesForMRoll() then
|
if self:qualifiesForMRoll() then
|
||||||
@@ -331,7 +335,7 @@ function MarathonA3Game:updateGrade(cleared_lines)
|
|||||||
self.grade_points = self.grade_points + (
|
self.grade_points = self.grade_points + (
|
||||||
math.ceil(
|
math.ceil(
|
||||||
grade_point_bonuses[self.grade + 1][cleared_lines] *
|
grade_point_bonuses[self.grade + 1][cleared_lines] *
|
||||||
combo_multipliers[math.min(self.combo, 10)][cleared_lines]
|
combo_multipliers[math.min(self.grade_combo, 10)][cleared_lines]
|
||||||
) * (1 + math.floor(self.level / 250))
|
) * (1 + math.floor(self.level / 250))
|
||||||
)
|
)
|
||||||
if self.grade_points >= 100 and self.grade < 31 then
|
if self.grade_points >= 100 and self.grade < 31 then
|
||||||
@@ -347,7 +351,12 @@ function MarathonA3Game:qualifiesForMRoll()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function MarathonA3Game:getAggregateGrade()
|
function MarathonA3Game:getAggregateGrade()
|
||||||
return self.section_cool_grade + math.floor(self.roll_points / 100) + grade_conversion[self.grade]
|
return math.min(
|
||||||
|
self.section_cool_grade +
|
||||||
|
math.floor(self.roll_points / 100) +
|
||||||
|
grade_conversion[self.grade],
|
||||||
|
self.roll_frames > 3238 and 32 or 31
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
local master_grades = { "M", "MK", "MV", "MO", "MM" }
|
local master_grades = { "M", "MK", "MV", "MO", "MM" }
|
||||||
@@ -362,8 +371,6 @@ function MarathonA3Game:getLetterGrade()
|
|||||||
return "M" .. tostring(grade - 17)
|
return "M" .. tostring(grade - 17)
|
||||||
elseif grade < 32 then
|
elseif grade < 32 then
|
||||||
return master_grades[grade - 26]
|
return master_grades[grade - 26]
|
||||||
elseif grade >= 32 and self.roll_frames < 3238 then
|
|
||||||
return "MM"
|
|
||||||
else
|
else
|
||||||
return "GM"
|
return "GM"
|
||||||
end
|
end
|
||||||
@@ -395,6 +402,16 @@ MarathonA3Game.mRollOpacityFunction = function(age)
|
|||||||
else return 1 - age / 4 end
|
else return 1 - age / 4 end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function MarathonA3Game:sectionColourFunction(section)
|
||||||
|
if self.section_status[section] == "cool" then
|
||||||
|
return { 0, 1, 0, 1 }
|
||||||
|
elseif self.section_status[section] == "regret" then
|
||||||
|
return { 1, 0, 0, 1 }
|
||||||
|
else
|
||||||
|
return { 1, 1, 1, 1 }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function MarathonA3Game:drawScoringInfo()
|
function MarathonA3Game:drawScoringInfo()
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
|
||||||
@@ -414,7 +431,9 @@ function MarathonA3Game:drawScoringInfo()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- draw section time data
|
-- draw section time data
|
||||||
current_section = math.floor(self.level / 100) + 1
|
current_section = self.level >= 999 and 11 or math.floor(self.level / 100) + 1
|
||||||
|
self:drawSectionTimesWithSecondary(current_section, 10)
|
||||||
|
--[[
|
||||||
|
|
||||||
section_x = 530
|
section_x = 530
|
||||||
section_70_x = 440
|
section_70_x = 440
|
||||||
@@ -425,20 +444,21 @@ function MarathonA3Game:drawScoringInfo()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for section, time in pairs(self.section_70_times) do
|
for section, time in pairs(self.secondary_section_times) do
|
||||||
if section > 0 then
|
if section > 0 then
|
||||||
love.graphics.printf(formatTime(time), section_70_x, 40 + 20 * section, 90, "left")
|
love.graphics.printf(formatTime(time), section_70_x, 40 + 20 * section, 90, "left")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local current_x
|
local current_x
|
||||||
if table.getn(self.section_times) < table.getn(self.section_70_times) then
|
if table.getn(self.section_times) < table.getn(self.secondary_section_times) then
|
||||||
current_x = section_x
|
current_x = section_x
|
||||||
else
|
else
|
||||||
current_x = section_70_x
|
current_x = section_70_x
|
||||||
end
|
end
|
||||||
|
|
||||||
if not self.clear then love.graphics.printf(formatTime(self.frames - self.section_start_time), current_x, 40 + 20 * current_section, 90, "left") end
|
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
|
if(self.coolregret_timer > 0) then
|
||||||
love.graphics.printf(self.coolregret_message, 64, 400, 160, "center")
|
love.graphics.printf(self.coolregret_message, 64, 400, 160, "center")
|
||||||
@@ -448,7 +468,7 @@ function MarathonA3Game:drawScoringInfo()
|
|||||||
love.graphics.setFont(font_3x5_3)
|
love.graphics.setFont(font_3x5_3)
|
||||||
love.graphics.printf(self.score, 240, 220, 90, "left")
|
love.graphics.printf(self.score, 240, 220, 90, "left")
|
||||||
if self.roll_frames > 3238 then love.graphics.setColor(1, 0.5, 0, 1)
|
if self.roll_frames > 3238 then love.graphics.setColor(1, 0.5, 0, 1)
|
||||||
elseif self.level >= 999 and self.clear then love.graphics.setColor(0, 1, 0, 1) end
|
elseif self.level >= 999 then love.graphics.setColor(0, 1, 0, 1) end
|
||||||
love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left")
|
love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left")
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
love.graphics.printf(self.level, 240, 340, 40, "right")
|
love.graphics.printf(self.level, 240, 340, 40, "right")
|
||||||
@@ -475,7 +495,7 @@ function MarathonA3Game:getSectionEndLevel()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function MarathonA3Game:getBackground()
|
function MarathonA3Game:getBackground()
|
||||||
return math.floor(self.level / 100)
|
return math.floor(self.speed_level / 100)
|
||||||
end
|
end
|
||||||
|
|
||||||
return MarathonA3Game
|
return MarathonA3Game
|
||||||
|
|||||||
@@ -19,13 +19,16 @@ function PhantomManiaGame:new()
|
|||||||
self.next_queue_length = 1
|
self.next_queue_length = 1
|
||||||
|
|
||||||
self.SGnames = {
|
self.SGnames = {
|
||||||
"9", "8", "7", "6", "5", "4", "3", "2", "1",
|
|
||||||
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
|
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
|
||||||
|
"M1", "M2", "M3", "M4", "M5", "M6", "M7", "M8", "M9",
|
||||||
"GM"
|
"GM"
|
||||||
}
|
}
|
||||||
|
|
||||||
self.roll_frames = 0
|
self.roll_frames = 0
|
||||||
self.combo = 1
|
self.combo = 1
|
||||||
|
self.tetrises = 0
|
||||||
|
self.section_tetrises = {[0] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||||
|
self.section_req = true
|
||||||
self.randomizer = History6RollsRandomizer()
|
self.randomizer = History6RollsRandomizer()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -38,7 +41,7 @@ function PhantomManiaGame:getARE()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function PhantomManiaGame:getLineARE()
|
function PhantomManiaGame:getLineARE()
|
||||||
if self.level < 100 then return 18
|
if self.level < 100 then return 14
|
||||||
elseif self.level < 400 then return 8
|
elseif self.level < 400 then return 8
|
||||||
elseif self.level < 500 then return 7
|
elseif self.level < 500 then return 7
|
||||||
else return 6 end
|
else return 6 end
|
||||||
@@ -105,12 +108,23 @@ end
|
|||||||
|
|
||||||
function PhantomManiaGame:onLineClear(cleared_row_count)
|
function PhantomManiaGame:onLineClear(cleared_row_count)
|
||||||
if not self.clear then
|
if not self.clear then
|
||||||
|
if cleared_row_count >= 4 then
|
||||||
|
self.tetrises = self.tetrises + 1
|
||||||
|
self.section_tetrises[math.floor(self.level / 100)] = (
|
||||||
|
self.section_tetrises[math.floor(self.level / 100)] + 1
|
||||||
|
)
|
||||||
|
end
|
||||||
local new_level = self.level + cleared_row_count
|
local new_level = self.level + cleared_row_count
|
||||||
if new_level >= 999 or self:hitTorikan(self.level, new_level) then
|
if new_level >= 999 or self:hitTorikan(self.level, new_level) then
|
||||||
if new_level >= 999 then
|
if new_level >= 999 then
|
||||||
self.level = 999
|
self.level = 999
|
||||||
end
|
end
|
||||||
self.clear = true
|
self.clear = true
|
||||||
|
for i = 0, 9 do
|
||||||
|
if self.section_tetrises[i] < (i == 9 and 1 or 2) then
|
||||||
|
self.section_req = false
|
||||||
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
self.level = new_level
|
self.level = new_level
|
||||||
end
|
end
|
||||||
@@ -138,7 +152,7 @@ PhantomManiaGame.rollOpacityFunction = function(age)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function PhantomManiaGame:drawGrid()
|
function PhantomManiaGame:drawGrid()
|
||||||
if not (self.game_over or self.clear) then
|
if not (self.game_over or self.completed or (self.clear and self.level < 999)) then
|
||||||
self.grid:drawInvisible(self.rollOpacityFunction, nil, false)
|
self.grid:drawInvisible(self.rollOpacityFunction, nil, false)
|
||||||
else
|
else
|
||||||
self.grid:draw()
|
self.grid:draw()
|
||||||
@@ -150,16 +164,14 @@ local function getLetterGrade(level, clear)
|
|||||||
return ""
|
return ""
|
||||||
elseif level < 500 or level == 500 and clear then
|
elseif level < 500 or level == 500 and clear then
|
||||||
return "M"
|
return "M"
|
||||||
elseif level < 700 then
|
elseif level < 600 then
|
||||||
return "MK"
|
return "MK"
|
||||||
elseif level < 800 or level == 800 and clear then
|
elseif level < 700 then
|
||||||
return "MV"
|
return "MV"
|
||||||
elseif level < 900 then
|
elseif level < 800 or level == 800 and clear then
|
||||||
return "MO"
|
return "MO"
|
||||||
elseif level < 999 then
|
elseif level <= 999 then
|
||||||
return "MM"
|
return "MM"
|
||||||
elseif level == 999 then
|
|
||||||
return "GM"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -169,7 +181,9 @@ function PhantomManiaGame:drawScoringInfo()
|
|||||||
local text_x = config["side_next"] and 320 or 240
|
local text_x = config["side_next"] and 320 or 240
|
||||||
|
|
||||||
love.graphics.setFont(font_3x5_2)
|
love.graphics.setFont(font_3x5_2)
|
||||||
if getLetterGrade(self.level, self.clear) ~= "" then love.graphics.printf("GRADE", text_x, 120, 40, "left") end
|
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("SCORE", text_x, 200, 40, "left")
|
||||||
love.graphics.printf("LEVEL", text_x, 320, 40, "left")
|
love.graphics.printf("LEVEL", text_x, 320, 40, "left")
|
||||||
local sg = self.grid:checkSecretGrade()
|
local sg = self.grid:checkSecretGrade()
|
||||||
@@ -178,7 +192,16 @@ function PhantomManiaGame:drawScoringInfo()
|
|||||||
end
|
end
|
||||||
|
|
||||||
love.graphics.setFont(font_3x5_3)
|
love.graphics.setFont(font_3x5_3)
|
||||||
if getLetterGrade(self.level, self.clear) ~= "" then love.graphics.printf(getLetterGrade(self.level, self.clear), text_x, 140, 90, "left") end
|
if getLetterGrade(self.level, self.clear) ~= "" then
|
||||||
|
if self.roll_frames > 1982 then love.graphics.setColor(1, 0.5, 0, 1)
|
||||||
|
elseif self.level == 999 and self.clear then love.graphics.setColor(0, 1, 0, 1) end
|
||||||
|
if self.level == 999 and self.section_req and self.tetrises >= 31 then
|
||||||
|
love.graphics.printf("GM", text_x, 140, 90, "left")
|
||||||
|
else
|
||||||
|
love.graphics.printf(getLetterGrade(self.level, self.clear), text_x, 140, 90, "left")
|
||||||
|
end
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
end
|
||||||
love.graphics.printf(self.score, text_x, 220, 90, "left")
|
love.graphics.printf(self.score, text_x, 220, 90, "left")
|
||||||
love.graphics.printf(self.level, text_x, 340, 40, "right")
|
love.graphics.printf(self.level, text_x, 340, 40, "right")
|
||||||
if self.clear then
|
if self.clear then
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ require 'funcs'
|
|||||||
local GameMode = require 'tetris.modes.gamemode'
|
local GameMode = require 'tetris.modes.gamemode'
|
||||||
local Piece = require 'tetris.components.piece'
|
local Piece = require 'tetris.components.piece'
|
||||||
|
|
||||||
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls'
|
local History6RollsRandomizer = require 'tetris.randomizers.history_6rolls_35bag'
|
||||||
|
|
||||||
local PhantomMania2Game = GameMode:extend()
|
local PhantomMania2Game = GameMode:extend()
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ function PhantomMania2Game:new()
|
|||||||
|
|
||||||
self.SGnames = {
|
self.SGnames = {
|
||||||
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
|
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
|
||||||
"m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m9",
|
"M1", "M2", "M3", "M4", "M5", "M6", "M7", "M8", "M9",
|
||||||
"GM"
|
"GM"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,12 +86,8 @@ function PhantomMania2Game:getGarbageLimit()
|
|||||||
else return 8 end
|
else return 8 end
|
||||||
end
|
end
|
||||||
|
|
||||||
function PhantomMania2Game:getNextPiece(ruleset)
|
function PhantomMania2Game:getSkin()
|
||||||
return {
|
return self.level >= 1000 and "bone" or "2tie"
|
||||||
skin = self.level >= 1000 and "bone" or "2tie",
|
|
||||||
shape = self.randomizer:nextPiece(),
|
|
||||||
orientation = ruleset:getDefaultOrientation(),
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function PhantomMania2Game:hitTorikan(old_level, new_level)
|
function PhantomMania2Game:hitTorikan(old_level, new_level)
|
||||||
@@ -183,7 +179,7 @@ function PhantomMania2Game:onPieceLock(piece, cleared_row_count)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function PhantomMania2Game:onHold()
|
function PhantomMania2Game:onHold()
|
||||||
self.super.onHold()
|
self.super:onHold()
|
||||||
self.hold_age = 0
|
self.hold_age = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -255,7 +251,7 @@ PhantomMania2Game.garbageOpacityFunction = function(age)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function PhantomMania2Game:drawGrid()
|
function PhantomMania2Game:drawGrid()
|
||||||
if not (self.game_over) then
|
if not (self.game_over or self.completed or (self.clear and self.level < 1300)) then
|
||||||
self.grid:drawInvisible(self.rollOpacityFunction, self.garbageOpacityFunction)
|
self.grid:drawInvisible(self.rollOpacityFunction, self.garbageOpacityFunction)
|
||||||
else
|
else
|
||||||
self.grid:draw()
|
self.grid:draw()
|
||||||
@@ -291,7 +287,8 @@ function PhantomMania2Game:setHoldOpacity()
|
|||||||
if self.level > 1000 and self.level < 1300 then
|
if self.level > 1000 and self.level < 1300 then
|
||||||
love.graphics.setColor(1, 1, 1, 1 - math.min(1, self.hold_age / 15))
|
love.graphics.setColor(1, 1, 1, 1 - math.min(1, self.hold_age / 15))
|
||||||
else
|
else
|
||||||
self.super:setHoldOpacity(1, self.held and 0.6 or 1)
|
local colour = self.held and 0.6 or 1
|
||||||
|
love.graphics.setColor(colour, colour, colour, 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,6 @@ PhantomManiaNGame.tagline = "The old mode from Nullpomino, for Ti-ARS and SRS su
|
|||||||
function PhantomManiaNGame:new()
|
function PhantomManiaNGame:new()
|
||||||
PhantomManiaNGame.super: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"
|
|
||||||
}
|
|
||||||
|
|
||||||
self.next_queue_length = 3
|
self.next_queue_length = 3
|
||||||
self.enable_hold = true
|
self.enable_hold = true
|
||||||
end
|
end
|
||||||
|
|||||||
155
tetris/modes/race_40.lua
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
require 'funcs'
|
||||||
|
|
||||||
|
local GameMode = require 'tetris.modes.gamemode'
|
||||||
|
local Piece = require 'tetris.components.piece'
|
||||||
|
|
||||||
|
local Bag7Randomiser = require 'tetris.randomizers.bag7noSZOstart'
|
||||||
|
|
||||||
|
local Race40Game = GameMode:extend()
|
||||||
|
|
||||||
|
Race40Game.name = "Race 40"
|
||||||
|
Race40Game.hash = "Race40"
|
||||||
|
Race40Game.tagline = "How fast can you clear 40 lines?"
|
||||||
|
|
||||||
|
|
||||||
|
function Race40Game:new()
|
||||||
|
Race40Game.super:new()
|
||||||
|
|
||||||
|
self.lines = 0
|
||||||
|
self.line_goal = 40
|
||||||
|
self.pieces = 0
|
||||||
|
self.randomizer = Bag7Randomiser()
|
||||||
|
|
||||||
|
self.roll_frames = 0
|
||||||
|
|
||||||
|
self.SGnames = {
|
||||||
|
[0] = "",
|
||||||
|
"9", "8", "7", "6", "5", "4", "3", "2", "1",
|
||||||
|
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
|
||||||
|
"GM"
|
||||||
|
}
|
||||||
|
self.upstacked = false
|
||||||
|
|
||||||
|
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 = 6
|
||||||
|
end
|
||||||
|
|
||||||
|
function Race40Game:getDropSpeed()
|
||||||
|
return 20
|
||||||
|
end
|
||||||
|
|
||||||
|
function Race40Game:getARR()
|
||||||
|
return config.arr
|
||||||
|
end
|
||||||
|
|
||||||
|
function Race40Game:getARE()
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function Race40Game:getLineARE()
|
||||||
|
return self:getARE()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Race40Game:getDasLimit()
|
||||||
|
return config.das
|
||||||
|
end
|
||||||
|
|
||||||
|
function Race40Game:getLineClearDelay()
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function Race40Game:getLockDelay()
|
||||||
|
return 30
|
||||||
|
end
|
||||||
|
|
||||||
|
function Race40Game:getGravity()
|
||||||
|
return 1/64
|
||||||
|
end
|
||||||
|
|
||||||
|
function Race40Game:getDasCutDelay()
|
||||||
|
return config.dcd
|
||||||
|
end
|
||||||
|
|
||||||
|
function Race40Game:advanceOneFrame()
|
||||||
|
if self.clear then
|
||||||
|
self.roll_frames = self.roll_frames + 1
|
||||||
|
if self.roll_frames > 150 then
|
||||||
|
self.completed = true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
elseif self.ready_frames == 0 then
|
||||||
|
self.frames = self.frames + 1
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function Race40Game:onPieceLock()
|
||||||
|
self.super:onPieceLock()
|
||||||
|
self.pieces = self.pieces + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
function Race40Game:onLineClear(cleared_row_count)
|
||||||
|
if not self.clear then
|
||||||
|
self.lines = self.lines + cleared_row_count
|
||||||
|
if self.lines >= self.line_goal then
|
||||||
|
self.clear = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Race40Game:drawGrid(ruleset)
|
||||||
|
self.grid:draw()
|
||||||
|
if self.piece ~= nil then
|
||||||
|
self:drawGhostPiece(ruleset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Race40Game:getHighscoreData()
|
||||||
|
return {
|
||||||
|
level = self.level,
|
||||||
|
frames = self.frames,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function Race40Game:getSecretGrade(sg)
|
||||||
|
if sg == 19 then self.upstacked = true end
|
||||||
|
if self.upstacked then return self.SGnames[14 + math.floor((20 - sg) / 4)]
|
||||||
|
else return self.SGnames[math.floor((sg / 19) * 14)] end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Race40Game:drawScoringInfo()
|
||||||
|
Race40Game.super.drawScoringInfo(self)
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
|
||||||
|
local text_x = config["side_next"] and 320 or 240
|
||||||
|
|
||||||
|
love.graphics.setFont(font_3x5_2)
|
||||||
|
love.graphics.printf("NEXT", 64, 40, 40, "left")
|
||||||
|
love.graphics.printf("LINES", text_x, 320, 40, "left")
|
||||||
|
love.graphics.printf("line/min", text_x, 160, 80, "left")
|
||||||
|
love.graphics.printf("piece/sec", text_x, 220, 80, "left")
|
||||||
|
local sg = self.grid:checkSecretGrade()
|
||||||
|
if sg >= 7 or self.upstacked then
|
||||||
|
love.graphics.printf("SECRET GRADE", 240, 430, 180, "left")
|
||||||
|
end
|
||||||
|
|
||||||
|
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")
|
||||||
|
if sg >= 7 or self.upstacked then
|
||||||
|
love.graphics.printf(self:getSecretGrade(sg), 240, 450, 180, "left")
|
||||||
|
end
|
||||||
|
|
||||||
|
love.graphics.setFont(font_3x5_4)
|
||||||
|
love.graphics.printf(math.max(0, self.line_goal - self.lines), text_x, 340, 40, "left")
|
||||||
|
end
|
||||||
|
|
||||||
|
function Race40Game:getBackground()
|
||||||
|
return 2
|
||||||
|
end
|
||||||
|
|
||||||
|
return Race40Game
|
||||||
@@ -52,23 +52,14 @@ function StrategyGame:getLineClearDelay()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function StrategyGame:getLockDelay()
|
function StrategyGame:getLockDelay()
|
||||||
if self.level < 500 then return 8
|
if self.level < 700 then return 8
|
||||||
elseif self.level < 700 then return 6
|
else return 6 end
|
||||||
else return 4 end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function StrategyGame:getGravity()
|
function StrategyGame:getGravity()
|
||||||
return 20
|
return 20
|
||||||
end
|
end
|
||||||
|
|
||||||
function StrategyGame:getNextPiece(ruleset)
|
|
||||||
return {
|
|
||||||
skin = "2tie",
|
|
||||||
shape = self.randomizer:nextPiece(),
|
|
||||||
orientation = ruleset:getDefaultOrientation(),
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
function StrategyGame:advanceOneFrame()
|
function StrategyGame:advanceOneFrame()
|
||||||
if self.clear then
|
if self.clear then
|
||||||
self.roll_frames = self.roll_frames + 1
|
self.roll_frames = self.roll_frames + 1
|
||||||
|
|||||||
@@ -94,12 +94,8 @@ function Survival2020Game:getGravity()
|
|||||||
return 20
|
return 20
|
||||||
end
|
end
|
||||||
|
|
||||||
function Survival2020Game:getNextPiece(ruleset)
|
function Survival2020Game:getSkin()
|
||||||
return {
|
return self.level >= 1000 and "bone" or "2tie"
|
||||||
skin = self.level >= 1000 and "bone" or "2tie",
|
|
||||||
shape = self.randomizer:nextPiece(),
|
|
||||||
orientation = ruleset:getDefaultOrientation(),
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Survival2020Game:hitTorikan(old_level, new_level)
|
function Survival2020Game:hitTorikan(old_level, new_level)
|
||||||
|
|||||||
@@ -1,218 +1,18 @@
|
|||||||
require 'funcs'
|
require 'funcs'
|
||||||
|
|
||||||
local GameMode = require 'tetris.modes.gamemode'
|
local MarathonA1Game = require 'tetris.modes.marathon_a1'
|
||||||
local Piece = require 'tetris.components.piece'
|
local Piece = require 'tetris.components.piece'
|
||||||
|
|
||||||
local History4RollsRandomizer = require 'tetris.randomizers.history_4rolls'
|
local History4RollsRandomizer = require 'tetris.randomizers.history_4rolls'
|
||||||
|
|
||||||
local SurvivalA1Game = GameMode:extend()
|
local SurvivalA1Game = MarathonA1Game:extend()
|
||||||
|
|
||||||
SurvivalA1Game.name = "Survival A1"
|
SurvivalA1Game.name = "Survival A1"
|
||||||
SurvivalA1Game.hash = "SurvivalA1"
|
SurvivalA1Game.hash = "SurvivalA1"
|
||||||
SurvivalA1Game.tagline = "The game starts fast and only gets faster!"
|
SurvivalA1Game.tagline = "A constant high-speed marathon!"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function SurvivalA1Game:new()
|
|
||||||
SurvivalA1Game.super:new()
|
|
||||||
|
|
||||||
self.roll_frames = 0
|
|
||||||
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.enable_hold = false
|
|
||||||
self.next_queue_length = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function SurvivalA1Game:getARE()
|
|
||||||
return 30
|
|
||||||
end
|
|
||||||
|
|
||||||
function SurvivalA1Game:getLineARE()
|
|
||||||
return 27
|
|
||||||
end
|
|
||||||
|
|
||||||
function SurvivalA1Game:getDasLimit()
|
|
||||||
return 15
|
|
||||||
end
|
|
||||||
|
|
||||||
function SurvivalA1Game:getLineClearDelay()
|
|
||||||
return 44
|
|
||||||
end
|
|
||||||
|
|
||||||
function SurvivalA1Game:getLockDelay()
|
|
||||||
return 30
|
|
||||||
end
|
|
||||||
|
|
||||||
function SurvivalA1Game:getGravity()
|
function SurvivalA1Game:getGravity()
|
||||||
return 20
|
return 20
|
||||||
end
|
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
|
|
||||||
end
|
|
||||||
|
|
||||||
function SurvivalA1Game:advanceOneFrame()
|
|
||||||
if self.clear then
|
|
||||||
self.roll_frames = self.roll_frames + 1
|
|
||||||
if self.roll_frames > 2968 then
|
|
||||||
self.completed = true
|
|
||||||
end
|
|
||||||
elseif self.ready_frames == 0 then
|
|
||||||
self.frames = self.frames + 1
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function SurvivalA1Game: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 SurvivalA1Game:onLineClear(cleared_row_count)
|
|
||||||
self:checkGMRequirements(self.level, self.level + cleared_row_count)
|
|
||||||
if not self.clear then
|
|
||||||
local new_level = math.min(self.level + cleared_row_count, 999)
|
|
||||||
if new_level == 999 then
|
|
||||||
self.clear = true
|
|
||||||
else
|
|
||||||
self.level = new_level
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function SurvivalA1Game:updateScore(level, drop_bonus, cleared_lines)
|
|
||||||
if not self.clear then
|
|
||||||
if self.grid:checkForBravo(cleared_lines) then
|
|
||||||
self.bravo = 4
|
|
||||||
self.bravos = self.bravos + 1
|
|
||||||
else self.bravo = 1 end
|
|
||||||
if cleared_lines > 0 then
|
|
||||||
self.combo = self.combo + (cleared_lines - 1) * 2
|
|
||||||
self.score = self.score + (
|
|
||||||
(math.ceil((level + cleared_lines) / 4) + drop_bonus) *
|
|
||||||
cleared_lines * self.combo * self.bravo
|
|
||||||
)
|
|
||||||
else
|
|
||||||
self.combo = 1
|
|
||||||
end
|
|
||||||
self.drop_bonus = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function SurvivalA1Game:checkGMRequirements(old_level, new_level)
|
|
||||||
if old_level < 300 and new_level >= 300 then
|
|
||||||
if self.score >= 12000 and self.frames <= frameTime(4,15) then
|
|
||||||
self.gm_conditions["level300"] = true
|
|
||||||
end
|
|
||||||
elseif old_level < 500 and new_level >= 500 then
|
|
||||||
if self.score >= 40000 and self.frames <= frameTime(7,30) then
|
|
||||||
self.gm_conditions["level500"] = true
|
|
||||||
end
|
|
||||||
elseif old_level < 999 and new_level >= 999 then
|
|
||||||
if self.score >= 126000 and self.frames <= frameTime(13,30) then
|
|
||||||
self.gm_conditions["level999"] = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function SurvivalA1Game:drawGrid()
|
|
||||||
self.grid:draw()
|
|
||||||
end
|
|
||||||
|
|
||||||
function SurvivalA1Game:drawScoringInfo()
|
|
||||||
SurvivalA1Game.super.drawScoringInfo(self)
|
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
|
||||||
|
|
||||||
love.graphics.setFont(font_3x5_2)
|
|
||||||
love.graphics.print(
|
|
||||||
self.das.direction .. " " ..
|
|
||||||
self.das.frames .. " " ..
|
|
||||||
strTrueValues(self.prev_inputs)
|
|
||||||
)
|
|
||||||
love.graphics.printf("NEXT", 64, 40, 40, "left")
|
|
||||||
love.graphics.printf("GRADE", 240, 120, 40, "left")
|
|
||||||
love.graphics.printf("SCORE", 240, 200, 40, "left")
|
|
||||||
love.graphics.printf("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["level999"] then
|
|
||||||
love.graphics.printf("GM", 240, 140, 90, "left")
|
|
||||||
else
|
|
||||||
love.graphics.printf(getRankForScore(self.score).rank, 240, 140, 90, "left")
|
|
||||||
end
|
|
||||||
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
|
|
||||||
|
|
||||||
function SurvivalA1Game:getSectionEndLevel()
|
|
||||||
if self.level >= 900 then return 999
|
|
||||||
else return math.floor(self.level / 100 + 1) * 100 end
|
|
||||||
end
|
|
||||||
|
|
||||||
function SurvivalA1Game:getBackground()
|
|
||||||
return math.floor(self.level / 100)
|
|
||||||
end
|
|
||||||
|
|
||||||
function SurvivalA1Game:getHighscoreData()
|
|
||||||
return {
|
|
||||||
grade = self.grade,
|
|
||||||
score = self.score,
|
|
||||||
level = self.level,
|
|
||||||
frames = self.frames,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
return SurvivalA1Game
|
return SurvivalA1Game
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ end
|
|||||||
function SurvivalA2Game:advanceOneFrame()
|
function SurvivalA2Game:advanceOneFrame()
|
||||||
if self.clear then
|
if self.clear then
|
||||||
self.roll_frames = self.roll_frames + 1
|
self.roll_frames = self.roll_frames + 1
|
||||||
if self.roll_frames > 2968 then
|
if self.roll_frames > 1800 then
|
||||||
self.completed = true
|
self.completed = true
|
||||||
end
|
end
|
||||||
elseif self.ready_frames == 0 then
|
elseif self.ready_frames == 0 then
|
||||||
@@ -97,14 +97,13 @@ end
|
|||||||
function SurvivalA2Game:onLineClear(cleared_row_count)
|
function SurvivalA2Game:onLineClear(cleared_row_count)
|
||||||
if not self.clear then
|
if not self.clear then
|
||||||
local new_level = math.min(self.level + cleared_row_count, 999)
|
local new_level = math.min(self.level + cleared_row_count, 999)
|
||||||
if self.level == 999 or self:hitTorikan(self.level, new_level) then
|
if new_level == 999 or self:hitTorikan(self.level, new_level) then
|
||||||
self.clear = true
|
self.clear = true
|
||||||
if self.level < 999 then
|
if new_level < 999 then
|
||||||
self.game_over = true
|
self.game_over = true
|
||||||
end
|
end
|
||||||
else
|
|
||||||
self.level = new_level
|
|
||||||
end
|
end
|
||||||
|
self.level = new_level
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -158,7 +157,7 @@ function SurvivalA2Game:drawScoringInfo()
|
|||||||
|
|
||||||
love.graphics.setFont(font_3x5_3)
|
love.graphics.setFont(font_3x5_3)
|
||||||
love.graphics.printf(self.score, text_x, 220, 90, "left")
|
love.graphics.printf(self.score, text_x, 220, 90, "left")
|
||||||
if self.roll_frames > 2968 then love.graphics.setColor(1, 0.5, 0, 1)
|
if self.roll_frames > 1800 then love.graphics.setColor(1, 0.5, 0, 1)
|
||||||
elseif self.level >= 999 and self.clear then love.graphics.setColor(0, 1, 0, 1) end
|
elseif self.level >= 999 and 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
|
if self:getLetterGrade() ~= "" then love.graphics.printf(self:getLetterGrade(), text_x, 140, 90, "left") end
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function SurvivalA3Game:new()
|
|||||||
|
|
||||||
self.SGnames = {
|
self.SGnames = {
|
||||||
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
|
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
|
||||||
"m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "m9",
|
"M1", "M2", "M3", "M4", "M5", "M6", "M7", "M8", "M9",
|
||||||
"GM"
|
"GM"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,11 +38,9 @@ function SurvivalA3Game:new()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function SurvivalA3Game:initialize(ruleset)
|
function SurvivalA3Game:initialize(ruleset)
|
||||||
|
|
||||||
self.torikan_time = frameTime(2,28)
|
self.torikan_time = frameTime(2,28)
|
||||||
if ruleset.world then self.torikan_time = frameTime(3,03) end
|
if ruleset.world then self.torikan_time = frameTime(3,03) end
|
||||||
self.super.initialize(self, ruleset)
|
GameMode.initialize(self, ruleset)
|
||||||
-- ^ notice the . here instead of the :
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function SurvivalA3Game:getARE()
|
function SurvivalA3Game:getARE()
|
||||||
@@ -92,12 +90,8 @@ function SurvivalA3Game:getGarbageLimit()
|
|||||||
else return 8 end
|
else return 8 end
|
||||||
end
|
end
|
||||||
|
|
||||||
function SurvivalA3Game:getNextPiece(ruleset)
|
function SurvivalA3Game:getSkin()
|
||||||
return {
|
return self.level >= 1000 and "bone" or "2tie"
|
||||||
skin = self.level >= 1000 and "bone" or "2tie",
|
|
||||||
shape = self.randomizer:nextPiece(),
|
|
||||||
orientation = ruleset:getDefaultOrientation(),
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function SurvivalA3Game:hitTorikan(old_level, new_level)
|
function SurvivalA3Game:hitTorikan(old_level, new_level)
|
||||||
@@ -243,7 +237,7 @@ function SurvivalA3Game:drawScoringInfo()
|
|||||||
|
|
||||||
love.graphics.setFont(font_3x5_3)
|
love.graphics.setFont(font_3x5_3)
|
||||||
if self.roll_frames > 3238 then love.graphics.setColor(1, 0.5, 0, 1)
|
if self.roll_frames > 3238 then love.graphics.setColor(1, 0.5, 0, 1)
|
||||||
elseif self.level >= 1300 and self.clear then love.graphics.setColor(0, 1, 0, 1) end
|
elseif self.level >= 1300 then love.graphics.setColor(0, 1, 0, 1) end
|
||||||
love.graphics.printf(getLetterGrade(math.floor(self.grade)), text_x, 140, 90, "left")
|
love.graphics.printf(getLetterGrade(math.floor(self.grade)), text_x, 140, 90, "left")
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
love.graphics.printf(self.score, text_x, 220, 90, "left")
|
love.graphics.printf(self.score, text_x, 220, 90, "left")
|
||||||
|
|||||||
@@ -5,17 +5,16 @@ local Piece = require 'tetris.components.piece'
|
|||||||
|
|
||||||
local Bag7NoSZOStartRandomizer = require 'tetris.randomizers.bag7noSZOstart'
|
local Bag7NoSZOStartRandomizer = require 'tetris.randomizers.bag7noSZOstart'
|
||||||
|
|
||||||
local MarathonAX4Game = GameMode:extend()
|
local SurvivalAXGame = GameMode:extend()
|
||||||
|
|
||||||
MarathonAX4Game.name = "Marathon AX4"
|
SurvivalAXGame.name = "Survival AX"
|
||||||
MarathonAX4Game.hash = "MarathonAX4"
|
SurvivalAXGame.hash = "SurvivalAX"
|
||||||
MarathonAX4Game.tagline = "Can you clear the time hurdles when the game goes this fast?"
|
SurvivalAXGame.tagline = "Can you clear the time hurdles when the game goes this fast?"
|
||||||
|
|
||||||
|
|
||||||
function MarathonAX4Game:new()
|
function SurvivalAXGame:new()
|
||||||
MarathonAX4Game.super:new()
|
SurvivalAXGame.super:new()
|
||||||
|
|
||||||
self.roll_frames = 0
|
|
||||||
self.randomizer = Bag7NoSZOStartRandomizer()
|
self.randomizer = Bag7NoSZOStartRandomizer()
|
||||||
|
|
||||||
self.section_time_limit = 3600
|
self.section_time_limit = 3600
|
||||||
@@ -29,7 +28,7 @@ function MarathonAX4Game:new()
|
|||||||
self.next_queue_length = 3
|
self.next_queue_length = 3
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonAX4Game:getARE()
|
function SurvivalAXGame:getARE()
|
||||||
if self.lines < 10 then return 18
|
if self.lines < 10 then return 18
|
||||||
elseif self.lines < 40 then return 14
|
elseif self.lines < 40 then return 14
|
||||||
elseif self.lines < 60 then return 12
|
elseif self.lines < 60 then return 12
|
||||||
@@ -39,24 +38,24 @@ function MarathonAX4Game:getARE()
|
|||||||
else return 6 end
|
else return 6 end
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonAX4Game:getLineARE()
|
function SurvivalAXGame:getLineARE()
|
||||||
return self:getARE()
|
return self:getARE()
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonAX4Game:getDasLimit()
|
function SurvivalAXGame:getDasLimit()
|
||||||
if self.lines < 20 then return 10
|
if self.lines < 20 then return 10
|
||||||
elseif self.lines < 50 then return 9
|
elseif self.lines < 50 then return 9
|
||||||
elseif self.lines < 70 then return 8
|
elseif self.lines < 70 then return 8
|
||||||
else return 7 end
|
else return 7 end
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonAX4Game:getLineClearDelay()
|
function SurvivalAXGame:getLineClearDelay()
|
||||||
if self.lines < 10 then return 14
|
if self.lines < 10 then return 14
|
||||||
elseif self.lines < 30 then return 9
|
elseif self.lines < 30 then return 9
|
||||||
else return 5 end
|
else return 5 end
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonAX4Game:getLockDelay()
|
function SurvivalAXGame:getLockDelay()
|
||||||
if self.lines < 10 then return 28
|
if self.lines < 10 then return 28
|
||||||
elseif self.lines < 20 then return 24
|
elseif self.lines < 20 then return 24
|
||||||
elseif self.lines < 30 then return 22
|
elseif self.lines < 30 then return 22
|
||||||
@@ -66,23 +65,16 @@ function MarathonAX4Game:getLockDelay()
|
|||||||
else return 13 end
|
else return 13 end
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonAX4Game:getGravity()
|
function SurvivalAXGame:getGravity()
|
||||||
return 20
|
return 20
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonAX4Game:getSection()
|
function SurvivalAXGame:getSection()
|
||||||
return math.floor(level / 100) + 1
|
return math.floor(level / 100) + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonAX4Game:advanceOneFrame()
|
function SurvivalAXGame:advanceOneFrame()
|
||||||
if self.clear then
|
if self.ready_frames == 0 then
|
||||||
self.roll_frames = self.roll_frames + 1
|
|
||||||
if self.roll_frames < 0 then
|
|
||||||
return false
|
|
||||||
elseif self.roll_frames > 2968 then
|
|
||||||
self.completed = true
|
|
||||||
end
|
|
||||||
elseif self.ready_frames == 0 then
|
|
||||||
if not self.section_clear then
|
if not self.section_clear then
|
||||||
self.frames = self.frames + 1
|
self.frames = self.frames + 1
|
||||||
end
|
end
|
||||||
@@ -93,7 +85,7 @@ function MarathonAX4Game:advanceOneFrame()
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonAX4Game:onLineClear(cleared_row_count)
|
function SurvivalAXGame:onLineClear(cleared_row_count)
|
||||||
if not self.clear then
|
if not self.clear then
|
||||||
local new_lines = self.lines + cleared_row_count
|
local new_lines = self.lines + cleared_row_count
|
||||||
self:updateSectionTimes(self.lines, new_lines)
|
self:updateSectionTimes(self.lines, new_lines)
|
||||||
@@ -101,16 +93,16 @@ function MarathonAX4Game:onLineClear(cleared_row_count)
|
|||||||
if self.lines == 150 then
|
if self.lines == 150 then
|
||||||
self.grid:clear()
|
self.grid:clear()
|
||||||
self.clear = true
|
self.clear = true
|
||||||
self.roll_frames = -150
|
self.completed = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonAX4Game:getSectionTime()
|
function SurvivalAXGame:getSectionTime()
|
||||||
return self.frames - self.section_start_time
|
return self.frames - self.section_start_time
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonAX4Game:updateSectionTimes(old_lines, new_lines)
|
function SurvivalAXGame:updateSectionTimes(old_lines, new_lines)
|
||||||
if math.floor(old_lines / 10) < math.floor(new_lines / 10) then
|
if math.floor(old_lines / 10) < math.floor(new_lines / 10) then
|
||||||
-- record new section
|
-- record new section
|
||||||
table.insert(self.section_times, self:getSectionTime())
|
table.insert(self.section_times, self:getSectionTime())
|
||||||
@@ -119,23 +111,23 @@ function MarathonAX4Game:updateSectionTimes(old_lines, new_lines)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonAX4Game:onPieceEnter()
|
function SurvivalAXGame:onPieceEnter()
|
||||||
self.section_clear = false
|
self.section_clear = false
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonAX4Game:drawGrid(ruleset)
|
function SurvivalAXGame:drawGrid(ruleset)
|
||||||
self.grid:draw()
|
self.grid:draw()
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonAX4Game:getHighscoreData()
|
function SurvivalAXGame:getHighscoreData()
|
||||||
return {
|
return {
|
||||||
lines = self.lines,
|
lines = self.lines,
|
||||||
frames = self.frames,
|
frames = self.frames,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonAX4Game:drawScoringInfo()
|
function SurvivalAXGame:drawScoringInfo()
|
||||||
MarathonAX4Game.super.drawScoringInfo(self)
|
SurvivalAXGame.super.drawScoringInfo(self)
|
||||||
|
|
||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
|
||||||
@@ -165,12 +157,12 @@ function MarathonAX4Game:drawScoringInfo()
|
|||||||
love.graphics.setColor(1, 1, 1, 1)
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonAX4Game:getSectionEndLines()
|
function SurvivalAXGame:getSectionEndLines()
|
||||||
return math.floor(self.lines / 10 + 1) * 10
|
return math.floor(self.lines / 10 + 1) * 10
|
||||||
end
|
end
|
||||||
|
|
||||||
function MarathonAX4Game:getBackground()
|
function SurvivalAXGame:getBackground()
|
||||||
return math.floor(self.lines / 10)
|
return math.floor(self.lines / 10)
|
||||||
end
|
end
|
||||||
|
|
||||||
return MarathonAX4Game
|
return SurvivalAXGame
|
||||||
24
tetris/randomizers/bag.lua
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
local Randomizer = require 'tetris.randomizers.randomizer'
|
||||||
|
|
||||||
|
local BagRandomizer = Randomizer:extend()
|
||||||
|
|
||||||
|
function BagRandomizer:new(pieces)
|
||||||
|
self.bag = {}
|
||||||
|
self.possible_pieces = pieces
|
||||||
|
self.pieces = pieces
|
||||||
|
for i = 1, self.pieces do
|
||||||
|
table.insert(self.bag, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function BagRandomizer:generatePiece()
|
||||||
|
if next(self.bag) == nil then
|
||||||
|
for i = 1, self.pieces do
|
||||||
|
table.insert(self.bag, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local x = math.random(table.getn(self.bag))
|
||||||
|
return table.remove(self.bag, x)
|
||||||
|
end
|
||||||
|
|
||||||
|
return BagRandomizer
|
||||||
16
tetris/randomizers/fixed_sequence.lua
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
local Randomizer = require 'tetris.randomizers.randomizer'
|
||||||
|
|
||||||
|
local Sequence = Randomizer:extend()
|
||||||
|
|
||||||
|
function Sequence:initialize()
|
||||||
|
self.sequence = "IJLOT"
|
||||||
|
self.counter = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function Sequence:generatePiece()
|
||||||
|
local piece = string.sub(self.sequence, self.counter + 1, self.counter + 1)
|
||||||
|
self.counter = (self.counter + 1) % string.len(self.sequence)
|
||||||
|
return piece
|
||||||
|
end
|
||||||
|
|
||||||
|
return Sequence
|
||||||
@@ -28,11 +28,8 @@ end
|
|||||||
function History6Rolls35PoolRandomizer:generatePiece()
|
function History6Rolls35PoolRandomizer:generatePiece()
|
||||||
local index, x
|
local index, x
|
||||||
if self.first then
|
if self.first then
|
||||||
local prevent = {"S", "Z", "O"}
|
index = math.random(20)
|
||||||
repeat
|
|
||||||
index = math.random(#self.pool)
|
|
||||||
x = self.pool[index]
|
x = self.pool[index]
|
||||||
until not inHistory(x, prevent)
|
|
||||||
self.first = false
|
self.first = false
|
||||||
else
|
else
|
||||||
for i = 1, 6 do
|
for i = 1, 6 do
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ local Object = require 'libs.classic'
|
|||||||
local Randomizer = Object:extend()
|
local Randomizer = Object:extend()
|
||||||
|
|
||||||
function Randomizer:new()
|
function Randomizer:new()
|
||||||
|
self.possible_pieces = 7
|
||||||
self:initialize()
|
self:initialize()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
10
tetris/randomizers/sakura.lua
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
local Sequence = require 'tetris.randomizers.fixed_sequence'
|
||||||
|
|
||||||
|
local Sakura = Sequence:extend()
|
||||||
|
|
||||||
|
function Sakura:initialize()
|
||||||
|
self.super:initialize()
|
||||||
|
self.sequence = "LIJOTSZILJOTISJZLOIJSZTIOJZTLSOZTISOLTJSIZTOJLIZSTOIZLTJOSILTZSOITJLZSTJJISOLJITSLZOIZSJOITSZLJTSZLISTJLZOTIOZSJILTZSOITZJSOLTJSZIOJLZIOJTZIZLOSIZTJOILZSOJIOSZTJILOSSILZOTJIZTSOLZTSOIJTZSILTZOSIJZTOLJISOLJTZSOLTZJSOTILZJTOLZIJSOZTJLOZSTLOZITSOLZTJIOSLZJTO"
|
||||||
|
end
|
||||||
|
|
||||||
|
return Sakura
|
||||||
@@ -110,13 +110,7 @@ function ARS:onPieceDrop(piece, grid)
|
|||||||
piece.lock_delay = 0 -- step reset
|
piece.lock_delay = 0 -- step reset
|
||||||
end
|
end
|
||||||
|
|
||||||
function ARS:get180RotationValue()
|
function ARS:get180RotationValue() return 3 end
|
||||||
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
|
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
local Piece = require 'tetris.components.piece'
|
local Piece = require 'tetris.components.piece'
|
||||||
local Ruleset = require 'tetris.rulesets.arika_ti'
|
local Ruleset = require 'tetris.rulesets.arika_ace2'
|
||||||
|
|
||||||
local ARS = Ruleset:extend()
|
local ARS = Ruleset:extend()
|
||||||
|
|
||||||
@@ -19,52 +19,4 @@ ARS.colourscheme = {
|
|||||||
ARS.softdrop_lock = false
|
ARS.softdrop_lock = false
|
||||||
ARS.harddrop_lock = true
|
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 },
|
|
||||||
}
|
|
||||||
|
|
||||||
function ARS:onPieceCreate(piece, grid)
|
|
||||||
piece.floorkick = 0
|
|
||||||
piece.manipulations = 0
|
|
||||||
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 >= 128 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 >= 128 then
|
|
||||||
piece.locked = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if piece.floorkick >= 1 then
|
|
||||||
piece.floorkick = piece.floorkick + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return ARS
|
return ARS
|
||||||
|
|||||||
@@ -5,37 +5,92 @@ local ARS = Ruleset:extend()
|
|||||||
|
|
||||||
ARS.name = "ACE-ARS2"
|
ARS.name = "ACE-ARS2"
|
||||||
ARS.hash = "ArikaACE2"
|
ARS.hash = "ArikaACE2"
|
||||||
|
ARS.spawn_above_field = true
|
||||||
|
|
||||||
ARS.spawn_positions = {
|
function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
||||||
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 = {
|
-- O doesn't kick
|
||||||
I = { x=3, y=0 },
|
if (piece.shape == "O") then return end
|
||||||
J = { x=2, y=1 },
|
|
||||||
L = { x=2, y=1 },
|
-- center column rule
|
||||||
O = { x=3, y=1 },
|
if (
|
||||||
S = { x=2, y=1 },
|
piece.shape == "J" or piece.shape == "T" or piece.shape == "L"
|
||||||
T = { x=2, y=1 },
|
) and (
|
||||||
Z = { x=2, y=1 },
|
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) and
|
||||||
|
(piece:isMoveBlocked(grid, {x=-1, y=0}) or piece:isMoveBlocked(grid, {x=1, y=0})) 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) 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)
|
||||||
|
elseif grid:canPlacePiece(new_piece:withOffset({x=0, y=-2})) then
|
||||||
|
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-2})
|
||||||
|
self:onPieceRotate(piece, grid)
|
||||||
|
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})
|
||||||
|
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)
|
||||||
|
elseif piece.shape == "T"
|
||||||
|
and new_piece.rotation == 0
|
||||||
|
and piece:isDropBlocked(grid)
|
||||||
|
and grid:canPlacePiece(new_piece:withOffset({x=0, y=-1}))
|
||||||
|
then
|
||||||
|
-- T floorkick
|
||||||
|
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
|
||||||
|
self:onPieceRotate(piece, grid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
function ARS:onPieceCreate(piece, grid)
|
function ARS:onPieceCreate(piece, grid)
|
||||||
piece.floorkick = 0
|
|
||||||
piece.manipulations = 0
|
piece.manipulations = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function ARS:onPieceDrop(piece, grid)
|
||||||
|
piece.lock_delay = 0
|
||||||
|
end
|
||||||
|
|
||||||
function ARS:onPieceMove(piece, grid)
|
function ARS:onPieceMove(piece, grid)
|
||||||
piece.lock_delay = 0 -- move reset
|
piece.lock_delay = 0 -- move reset
|
||||||
if piece:isDropBlocked(grid) then
|
if piece:isDropBlocked(grid) then
|
||||||
piece.manipulations = piece.manipulations + 1
|
piece.manipulations = piece.manipulations + 1
|
||||||
if piece.manipulations >= 128 then
|
if piece.manipulations >= 128 then
|
||||||
|
piece:dropToBottom(grid)
|
||||||
piece.locked = true
|
piece.locked = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -46,12 +101,14 @@ function ARS:onPieceRotate(piece, grid)
|
|||||||
if piece:isDropBlocked(grid) then
|
if piece:isDropBlocked(grid) then
|
||||||
piece.manipulations = piece.manipulations + 1
|
piece.manipulations = piece.manipulations + 1
|
||||||
if piece.manipulations >= 128 then
|
if piece.manipulations >= 128 then
|
||||||
|
piece:dropToBottom(grid)
|
||||||
piece.locked = true
|
piece.locked = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if piece.floorkick >= 1 then
|
|
||||||
piece.floorkick = piece.floorkick + 1
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function ARS:get180RotationValue() return 3 end
|
||||||
|
|
||||||
|
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
|
||||||
|
|
||||||
return ARS
|
return ARS
|
||||||
|
|||||||
@@ -4,46 +4,35 @@ local Ruleset = require 'tetris.rulesets.ti_srs'
|
|||||||
local SRS = Ruleset:extend()
|
local SRS = Ruleset:extend()
|
||||||
|
|
||||||
SRS.name = "ACE-SRS"
|
SRS.name = "ACE-SRS"
|
||||||
SRS.hash = "ACE Standard"
|
SRS.hash = "StandardACE"
|
||||||
|
SRS.world = true
|
||||||
SRS.spawn_positions = {
|
SRS.colourscheme = {
|
||||||
I = { x=5, y=2 },
|
I = "C",
|
||||||
J = { x=4, y=3 },
|
L = "O",
|
||||||
L = { x=4, y=3 },
|
J = "B",
|
||||||
O = { x=5, y=3 },
|
S = "G",
|
||||||
S = { x=4, y=3 },
|
Z = "R",
|
||||||
T = { x=4, y=3 },
|
O = "Y",
|
||||||
Z = { x=4, y=3 },
|
T = "M",
|
||||||
}
|
}
|
||||||
|
SRS.softdrop_lock = false
|
||||||
|
SRS.harddrop_lock = true
|
||||||
|
SRS.spawn_above_field = true
|
||||||
|
|
||||||
SRS.big_spawn_positions = {
|
SRS.MANIPULATIONS_MAX = 128
|
||||||
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 },
|
|
||||||
}
|
|
||||||
|
|
||||||
function SRS:onPieceMove(piece, grid)
|
function SRS:onPieceRotate(piece, grid, upward)
|
||||||
piece.lock_delay = 0 -- move reset
|
|
||||||
if piece:isDropBlocked(grid) then
|
|
||||||
piece.manipulations = piece.manipulations + 1
|
|
||||||
if piece.manipulations >= 128 then
|
|
||||||
piece.locked = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function SRS:onPieceRotate(piece, grid)
|
|
||||||
piece.lock_delay = 0 -- rotate reset
|
piece.lock_delay = 0 -- rotate reset
|
||||||
if piece:isDropBlocked(grid) then
|
if upward or piece:isDropBlocked(grid) then
|
||||||
piece.manipulations = piece.manipulations + 1
|
piece.manipulations = piece.manipulations + 1
|
||||||
if piece.manipulations >= 128 then
|
if piece.manipulations >= self.MANIPULATIONS_MAX and piece:isDropBlocked(grid) then
|
||||||
piece.locked = true
|
piece.locked = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function SRS:canPieceRotate(piece)
|
||||||
|
return piece.manipulations < self.MANIPULATIONS_MAX
|
||||||
|
end
|
||||||
|
|
||||||
return SRS
|
return SRS
|
||||||
|
|||||||
@@ -38,35 +38,35 @@ function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
|||||||
(piece:isMoveBlocked(grid, {x=-1, y=0}) or piece:isMoveBlocked(grid, {x=1, y=0})) then
|
(piece:isMoveBlocked(grid, {x=-1, y=0}) or piece:isMoveBlocked(grid, {x=1, y=0})) then
|
||||||
-- kick right, right2, left
|
-- kick right, right2, left
|
||||||
if grid:canPlacePiece(new_piece:withOffset({x=1, y=0})) then
|
if grid:canPlacePiece(new_piece:withOffset({x=1, y=0})) then
|
||||||
self:onPieceRotate(piece, grid)
|
|
||||||
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
|
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
|
||||||
|
self:onPieceRotate(piece, grid)
|
||||||
elseif grid:canPlacePiece(new_piece:withOffset({x=2, y=0})) then
|
elseif grid:canPlacePiece(new_piece:withOffset({x=2, y=0})) then
|
||||||
self:onPieceRotate(piece, grid)
|
|
||||||
piece:setRelativeRotation(rot_dir):setOffset({x=2, y=0})
|
piece:setRelativeRotation(rot_dir):setOffset({x=2, y=0})
|
||||||
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
|
|
||||||
self:onPieceRotate(piece, grid)
|
self:onPieceRotate(piece, grid)
|
||||||
|
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
|
||||||
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
|
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
|
||||||
|
self:onPieceRotate(piece, grid)
|
||||||
end
|
end
|
||||||
elseif piece:isDropBlocked(grid) and (new_piece.rotation == 1 or new_piece.rotation == 3) and piece.floorkick == 0 then
|
elseif piece:isDropBlocked(grid) and (new_piece.rotation == 1 or new_piece.rotation == 3) and piece.floorkick == 0 then
|
||||||
-- kick up, up2
|
-- kick up, up2
|
||||||
if grid:canPlacePiece(new_piece:withOffset({x=0, y=-1})) then
|
if grid:canPlacePiece(new_piece:withOffset({x=0, y=-1})) then
|
||||||
self:onPieceRotate(piece, grid)
|
|
||||||
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
|
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
|
||||||
piece.floorkick = 1
|
piece.floorkick = 1
|
||||||
|
self:onPieceRotate(piece, grid, true)
|
||||||
elseif grid:canPlacePiece(new_piece:withOffset({x=0, y=-2})) then
|
elseif grid:canPlacePiece(new_piece:withOffset({x=0, y=-2})) then
|
||||||
self:onPieceRotate(piece, grid)
|
|
||||||
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-2})
|
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-2})
|
||||||
piece.floorkick = 1
|
piece.floorkick = 1
|
||||||
|
self:onPieceRotate(piece, grid, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- kick right, kick left
|
-- kick right, kick left
|
||||||
if grid:canPlacePiece(new_piece:withOffset({x=1, y=0})) then
|
if grid:canPlacePiece(new_piece:withOffset({x=1, y=0})) then
|
||||||
self:onPieceRotate(piece, grid)
|
|
||||||
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
|
piece:setRelativeRotation(rot_dir):setOffset({x=1, y=0})
|
||||||
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
|
|
||||||
self:onPieceRotate(piece, grid)
|
self:onPieceRotate(piece, grid)
|
||||||
|
elseif grid:canPlacePiece(new_piece:withOffset({x=-1, y=0})) then
|
||||||
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
|
piece:setRelativeRotation(rot_dir):setOffset({x=-1, y=0})
|
||||||
|
self:onPieceRotate(piece, grid)
|
||||||
elseif piece.shape == "T"
|
elseif piece.shape == "T"
|
||||||
and new_piece.rotation == 0
|
and new_piece.rotation == 0
|
||||||
and piece.floorkick == 0
|
and piece.floorkick == 0
|
||||||
@@ -75,8 +75,8 @@ function ARS:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
|||||||
then
|
then
|
||||||
-- T floorkick
|
-- T floorkick
|
||||||
piece.floorkick = piece.floorkick + 1
|
piece.floorkick = piece.floorkick + 1
|
||||||
self:onPieceRotate(piece, grid)
|
|
||||||
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
|
piece:setRelativeRotation(rot_dir):setOffset({x=0, y=-1})
|
||||||
|
self:onPieceRotate(piece, grid, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -93,10 +93,16 @@ function ARS:onPieceDrop(piece, grid)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function ARS:onPieceRotate(piece, grid)
|
function ARS:onPieceRotate(piece, grid, floorkick)
|
||||||
if piece.floorkick >= 1 then
|
if piece.floorkick >= 2 and piece:isDropBlocked(grid) then
|
||||||
|
piece.locked = true
|
||||||
|
elseif piece.floorkick >= 1 and not floorkick then
|
||||||
piece.floorkick = piece.floorkick + 1
|
piece.floorkick = piece.floorkick + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function ARS:get180RotationValue() return 3 end
|
||||||
|
|
||||||
|
function ARS:getDefaultOrientation() return 3 end -- downward facing pieces by default
|
||||||
|
|
||||||
return ARS
|
return ARS
|
||||||
|
|||||||
@@ -364,7 +364,7 @@ function CRS:attemptRotate(new_inputs, piece, grid, initial)
|
|||||||
|
|
||||||
if rot_dir == 0 then return end
|
if rot_dir == 0 then return end
|
||||||
|
|
||||||
if self.world and config.gamesettings.world_reverse == 2 then
|
if config.gamesettings.world_reverse == 3 or (self.world and config.gamesettings.world_reverse == 2) then
|
||||||
rot_dir = 4 - rot_dir
|
rot_dir = 4 - rot_dir
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -375,7 +375,10 @@ end
|
|||||||
|
|
||||||
function CRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
function CRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
||||||
|
|
||||||
if piece.shape == "O" then return end
|
if piece.shape == "O" then
|
||||||
|
self:onPieceRotate(piece, grid)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local kicks = CRS.wallkicks[piece.shape][piece:isDropBlocked(grid)][piece.rotation][new_piece.rotation]
|
local kicks = CRS.wallkicks[piece.shape][piece:isDropBlocked(grid)][piece.rotation][new_piece.rotation]
|
||||||
|
|
||||||
@@ -384,9 +387,9 @@ function CRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
|||||||
for idx, offset in pairs(kicks) do
|
for idx, offset in pairs(kicks) do
|
||||||
kicked_piece = new_piece:withOffset(offset)
|
kicked_piece = new_piece:withOffset(offset)
|
||||||
if grid:canPlacePiece(kicked_piece) then
|
if grid:canPlacePiece(kicked_piece) then
|
||||||
self:onPieceRotate(piece, grid)
|
|
||||||
piece:setRelativeRotation(rot_dir)
|
piece:setRelativeRotation(rot_dir)
|
||||||
piece:setOffset(offset)
|
piece:setOffset(offset)
|
||||||
|
self:onPieceRotate(piece, grid)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -406,6 +409,7 @@ function CRS:onPieceMove(piece, grid)
|
|||||||
if piece:isDropBlocked(grid) then
|
if piece:isDropBlocked(grid) then
|
||||||
piece.move_counter = piece.move_counter + 1
|
piece.move_counter = piece.move_counter + 1
|
||||||
if piece.move_counter >= 24 then
|
if piece.move_counter >= 24 then
|
||||||
|
piece:dropToBottom(grid)
|
||||||
piece.locked = true
|
piece.locked = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -415,6 +419,7 @@ function CRS:onPieceRotate(piece, grid)
|
|||||||
if piece:isDropBlocked(grid) then
|
if piece:isDropBlocked(grid) then
|
||||||
piece.rotate_counter = piece.rotate_counter + 1
|
piece.rotate_counter = piece.rotate_counter + 1
|
||||||
if piece.rotate_counter >= 12 then
|
if piece.rotate_counter >= 12 then
|
||||||
|
piece:dropToBottom(grid)
|
||||||
piece.locked = true
|
piece.locked = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -22,60 +22,35 @@ Ruleset.harddrop_lock = false
|
|||||||
|
|
||||||
Ruleset.enable_IRS_wallkicks = false
|
Ruleset.enable_IRS_wallkicks = false
|
||||||
Ruleset.are_cancel = false
|
Ruleset.are_cancel = false
|
||||||
|
Ruleset.are = true
|
||||||
|
Ruleset.spawn_above_field = false
|
||||||
|
|
||||||
|
Ruleset.next_sounds = {
|
||||||
|
I = "I",
|
||||||
|
L = "L",
|
||||||
|
J = "J",
|
||||||
|
S = "S",
|
||||||
|
Z = "Z",
|
||||||
|
O = "O",
|
||||||
|
T = "T"
|
||||||
|
}
|
||||||
|
|
||||||
|
Ruleset.pieces = 7
|
||||||
|
|
||||||
-- Component functions.
|
-- Component functions.
|
||||||
|
|
||||||
function Ruleset:new()
|
function Ruleset:new(game_mode)
|
||||||
|
self.game = game_mode
|
||||||
|
local bones
|
||||||
if config.gamesettings.piece_colour == 1 then
|
if config.gamesettings.piece_colour == 1 then
|
||||||
blocks["bone"] = (not self.world) and
|
bones = self.world and "w" or ""
|
||||||
{
|
|
||||||
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
|
else
|
||||||
blocks["bone"] = (config.gamesettings.piece_colour == 2) and
|
bones = config.gamesettings.piece_colour == 3 and "w" or ""
|
||||||
{
|
end
|
||||||
R = love.graphics.newImage("res/img/bone.png"),
|
for colour in pairs(blocks["2tie"]) do
|
||||||
O = love.graphics.newImage("res/img/bone.png"),
|
blocks.bone[colour] = love.graphics.newImage(
|
||||||
Y = love.graphics.newImage("res/img/bone.png"),
|
"res/img/bone" .. bones .. ".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
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -88,7 +63,15 @@ function Ruleset:rotatePiece(inputs, piece, grid, prev_inputs, initial)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local was_drop_blocked = piece:isDropBlocked(grid)
|
||||||
|
|
||||||
|
if self:canPieceRotate(piece, grid) then
|
||||||
self:attemptRotate(new_inputs, piece, grid, initial)
|
self:attemptRotate(new_inputs, piece, grid, initial)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not initial and not was_drop_blocked and piece:isDropBlocked(grid) then
|
||||||
|
playSE("bottom")
|
||||||
|
end
|
||||||
|
|
||||||
-- prev_inputs becomes the previous inputs
|
-- prev_inputs becomes the previous inputs
|
||||||
for input, value in pairs(inputs) do
|
for input, value in pairs(inputs) do
|
||||||
@@ -115,8 +98,8 @@ function Ruleset:attemptRotate(new_inputs, piece, grid, initial)
|
|||||||
local new_piece = piece:withRelativeRotation(rot_dir)
|
local new_piece = piece:withRelativeRotation(rot_dir)
|
||||||
|
|
||||||
if (grid:canPlacePiece(new_piece)) then
|
if (grid:canPlacePiece(new_piece)) then
|
||||||
self:onPieceRotate(piece, grid)
|
|
||||||
piece:setRelativeRotation(rot_dir)
|
piece:setRelativeRotation(rot_dir)
|
||||||
|
self:onPieceRotate(piece, grid)
|
||||||
else
|
else
|
||||||
if not(initial and self.enable_IRS_wallkicks == false) then
|
if not(initial and self.enable_IRS_wallkicks == false) then
|
||||||
self:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
self:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
||||||
@@ -129,54 +112,88 @@ function Ruleset:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Ruleset:movePiece(piece, grid, move, instant)
|
function Ruleset:movePiece(piece, grid, move, instant)
|
||||||
local x = piece.position.x
|
if not self:canPieceMove(piece, grid) then return end
|
||||||
|
local was_drop_blocked = piece:isDropBlocked(grid)
|
||||||
|
local offset = ({x=0, y=0})
|
||||||
|
local moves = 0
|
||||||
|
local y = piece.position.y
|
||||||
if move == "left" then
|
if move == "left" then
|
||||||
piece:moveInGrid({x=-1, y=0}, 1, grid, false)
|
offset.x = -1
|
||||||
|
moves = 1
|
||||||
elseif move == "right" then
|
elseif move == "right" then
|
||||||
piece:moveInGrid({x=1, y=0}, 1, grid, false)
|
offset.x = 1
|
||||||
|
moves = 1
|
||||||
elseif move == "speedleft" then
|
elseif move == "speedleft" then
|
||||||
piece:moveInGrid({x=-1, y=0}, 10, grid, instant)
|
offset.x = -1
|
||||||
|
moves = grid.width
|
||||||
elseif move == "speedright" then
|
elseif move == "speedright" then
|
||||||
piece:moveInGrid({x=1, y=0}, 10, grid, instant)
|
offset.x = 1
|
||||||
|
moves = grid.width
|
||||||
|
end
|
||||||
|
for i = 1, moves do
|
||||||
|
local x = piece.position.x
|
||||||
|
if moves ~= 1 then
|
||||||
|
piece:moveInGrid(offset, 1, grid, instant)
|
||||||
|
else
|
||||||
|
piece:moveInGrid(offset, 1, grid, false)
|
||||||
end
|
end
|
||||||
if piece.position.x ~= x then
|
if piece.position.x ~= x then
|
||||||
self:onPieceMove(piece, grid)
|
self:onPieceMove(piece, grid)
|
||||||
|
if piece.locked then break end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not was_drop_blocked and piece:isDropBlocked(grid) then
|
||||||
|
playSE("bottom")
|
||||||
|
end
|
||||||
|
if instant and piece.position.y ~= y then
|
||||||
|
self:onPieceDrop(piece, grid)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Ruleset:dropPiece(
|
function Ruleset:dropPiece(
|
||||||
inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
|
inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
|
||||||
hard_drop_enabled, additive_gravity
|
hard_drop_enabled, additive_gravity, classic_lock
|
||||||
)
|
)
|
||||||
local y = piece.position.y
|
local y = piece.position.y
|
||||||
if inputs["down"] == true and drop_locked == false then
|
if inputs["up"] == true and hard_drop_enabled == true then
|
||||||
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
|
if hard_drop_locked == true or piece:isDropBlocked(grid) then
|
||||||
piece:addGravity(gravity, grid)
|
piece:addGravity(gravity, grid, classic_lock)
|
||||||
else
|
else
|
||||||
piece:dropToBottom(grid)
|
piece:dropToBottom(grid)
|
||||||
end
|
end
|
||||||
|
elseif inputs["down"] == true and drop_locked == false then
|
||||||
|
if additive_gravity then
|
||||||
|
piece:addGravity(gravity + drop_speed, grid, classic_lock)
|
||||||
else
|
else
|
||||||
piece:addGravity(gravity, grid)
|
piece:addGravity(math.max(gravity, drop_speed), grid, classic_lock)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
piece:addGravity(gravity, grid, classic_lock)
|
||||||
end
|
end
|
||||||
if piece.position.y ~= y then
|
if piece.position.y ~= y then
|
||||||
self:onPieceDrop(piece, grid)
|
self:onPieceDrop(piece, grid)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Ruleset:lockPiece(piece, grid, lock_delay)
|
function Ruleset:lockPiece(piece, grid, lock_delay, classic_lock)
|
||||||
if piece:isDropBlocked(grid) and piece.gravity >= 1 and piece.lock_delay >= lock_delay then
|
if piece:isDropBlocked(grid) and (
|
||||||
|
(classic_lock and piece.gravity >= 1) or
|
||||||
|
(not classic_lock and piece.lock_delay >= lock_delay)
|
||||||
|
) then
|
||||||
piece.locked = true
|
piece.locked = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Ruleset:get180RotationValue() return 2 end
|
function Ruleset:get180RotationValue() return 2 end
|
||||||
function Ruleset:getDefaultOrientation() return 1 end
|
function Ruleset:getDefaultOrientation() return 1 end
|
||||||
|
function Ruleset:getDrawOffset(shape, orientation) return { x=0, y=0 } end
|
||||||
|
function Ruleset:getAboveFieldOffset(shape, orientation)
|
||||||
|
if shape == "I" then
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
return 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function Ruleset:initializePiece(
|
function Ruleset:initializePiece(
|
||||||
inputs, data, grid, gravity, prev_inputs,
|
inputs, data, grid, gravity, prev_inputs,
|
||||||
@@ -189,23 +206,41 @@ function Ruleset:initializePiece(
|
|||||||
else
|
else
|
||||||
spawn_positions = self.spawn_positions
|
spawn_positions = self.spawn_positions
|
||||||
end
|
end
|
||||||
local colours = ({self.colourscheme, ColourSchemes.Arika, ColourSchemes.TTC})[config.gamesettings.piece_colour]
|
|
||||||
|
local colours
|
||||||
|
if self.pieces == 7 then
|
||||||
|
colours = ({self.colourscheme, ColourSchemes.Arika, ColourSchemes.TTC})[config.gamesettings.piece_colour]
|
||||||
|
else
|
||||||
|
colours = self.colourscheme
|
||||||
|
end
|
||||||
|
|
||||||
|
local spawn_x = math.floor(spawn_positions[data.shape].x * grid.width / 10)
|
||||||
|
|
||||||
|
local spawn_dy
|
||||||
|
if (config.gamesettings.spawn_positions == 1) then
|
||||||
|
spawn_dy = (
|
||||||
|
self.spawn_above_field and
|
||||||
|
self:getAboveFieldOffset(data.shape, data.orientation) or 0
|
||||||
|
)
|
||||||
|
else
|
||||||
|
spawn_dy = (
|
||||||
|
config.gamesettings.spawn_positions == 3 and
|
||||||
|
self:getAboveFieldOffset(data.shape, data.orientation) or 0
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
local piece = Piece(data.shape, data.orientation - 1, {
|
local piece = Piece(data.shape, data.orientation - 1, {
|
||||||
x = spawn_positions[data.shape].x,
|
x = spawn_x,
|
||||||
y = spawn_positions[data.shape].y
|
y = spawn_positions[data.shape].y - spawn_dy
|
||||||
}, self.block_offsets, 0, 0, data.skin, colours[data.shape], big)
|
}, self.block_offsets, 0, 0, data.skin, colours[data.shape], big)
|
||||||
|
|
||||||
self:onPieceCreate(piece)
|
self:onPieceCreate(piece)
|
||||||
if irs then
|
if irs then
|
||||||
if inputs.rotate_left or inputs.rotate_left2 or
|
self:rotatePiece(inputs, piece, grid, {}, true)
|
||||||
inputs.rotate_right or inputs.rotate_right2 or
|
if (data.orientation - 1) ~= piece.rotation then
|
||||||
inputs.rotate_180 then
|
|
||||||
playSE("irs")
|
playSE("irs")
|
||||||
end
|
end
|
||||||
self:rotatePiece(inputs, piece, grid, {}, true)
|
|
||||||
end
|
end
|
||||||
self:dropPiece(inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked)
|
|
||||||
return piece
|
return piece
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -216,25 +251,26 @@ function Ruleset:processPiece(
|
|||||||
inputs, piece, grid, gravity, prev_inputs,
|
inputs, piece, grid, gravity, prev_inputs,
|
||||||
move, lock_delay, drop_speed,
|
move, lock_delay, drop_speed,
|
||||||
drop_locked, hard_drop_locked,
|
drop_locked, hard_drop_locked,
|
||||||
hard_drop_enabled, additive_gravity
|
hard_drop_enabled, additive_gravity, classic_lock
|
||||||
)
|
)
|
||||||
|
|
||||||
local synchroes_allowed = ({not self.world, true, false})[config.gamesettings.synchroes_allowed]
|
local synchroes_allowed = ({not self.world, true, false})[config.gamesettings.synchroes_allowed]
|
||||||
|
|
||||||
if synchroes_allowed then
|
if synchroes_allowed then
|
||||||
self:rotatePiece(inputs, piece, grid, prev_inputs, false)
|
self:rotatePiece(inputs, piece, grid, prev_inputs, false)
|
||||||
self:movePiece(piece, grid, move, gravity >= 20)
|
self:movePiece(piece, grid, move, gravity >= grid.height - 4)
|
||||||
else
|
else
|
||||||
self:movePiece(piece, grid, move, gravity >= 20)
|
self:movePiece(piece, grid, move, gravity >= grid.height - 4)
|
||||||
self:rotatePiece(inputs, piece, grid, prev_inputs, false)
|
self:rotatePiece(inputs, piece, grid, prev_inputs, false)
|
||||||
end
|
end
|
||||||
self:dropPiece(
|
self:dropPiece(
|
||||||
inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
|
inputs, piece, grid, gravity, drop_speed, drop_locked, hard_drop_locked,
|
||||||
hard_drop_enabled, additive_gravity
|
hard_drop_enabled, additive_gravity, classic_lock
|
||||||
)
|
)
|
||||||
self:lockPiece(piece, grid, lock_delay)
|
self:lockPiece(piece, grid, lock_delay, classic_lock)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Ruleset:canPieceMove(piece, grid) return true end
|
||||||
|
function Ruleset:canPieceRotate(piece, grid) return true end
|
||||||
function Ruleset:onPieceMove(piece) end
|
function Ruleset:onPieceMove(piece) end
|
||||||
function Ruleset:onPieceRotate(piece) end
|
function Ruleset:onPieceRotate(piece) end
|
||||||
function Ruleset:onPieceDrop(piece) end
|
function Ruleset:onPieceDrop(piece) end
|
||||||
|
|||||||
105
tetris/rulesets/standard.lua
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
local Piece = require 'tetris.components.piece'
|
||||||
|
local Ruleset = require 'tetris.rulesets.standard_exp'
|
||||||
|
|
||||||
|
local SRS = Ruleset:extend()
|
||||||
|
|
||||||
|
SRS.name = "Guideline SRS"
|
||||||
|
SRS.hash = "Standard"
|
||||||
|
SRS.softdrop_lock = false
|
||||||
|
SRS.harddrop_lock = true
|
||||||
|
|
||||||
|
SRS.MANIPULATIONS_MAX = 15
|
||||||
|
|
||||||
|
SRS.wallkicks_line = {
|
||||||
|
[0]={
|
||||||
|
[1]={{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=0},{x=2,y=0},{x=0,y=1}},
|
||||||
|
[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=2},{x=0,y=-1},{x=0,y=-2},{x=-1,y=0}},
|
||||||
|
},
|
||||||
|
[2]={
|
||||||
|
[0]={{x=1,y=0},{x=2,y=0},{x=-1,y=0},{x=-2,y=0},{x=0,y=-1}},
|
||||||
|
[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=2},{x=0,y=-1},{x=0,y=-2},{x=1,y=0}},
|
||||||
|
[2]={{x=-2, y=0}, {x=1, y=0}, {x=-2, y=1}, {x=1, y=-2}},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
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:checkNewLow(piece)
|
||||||
|
for _, block in pairs(piece:getBlockOffsets()) do
|
||||||
|
local y = piece.position.y + block.y
|
||||||
|
if y > piece.lowest_y then
|
||||||
|
piece.manipulations = 0
|
||||||
|
piece.rotations = 0
|
||||||
|
piece.lowest_y = y
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function SRS:onPieceDrop(piece, grid)
|
||||||
|
self:checkNewLow(piece)
|
||||||
|
if piece.manipulations >= self.MANIPULATIONS_MAX 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.manipulations = piece.manipulations + 1
|
||||||
|
if piece.manipulations >= SRS.MANIPULATIONS_MAX then
|
||||||
|
piece.locked = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function SRS:onPieceRotate(piece, grid)
|
||||||
|
piece.lock_delay = 0 -- rotate reset
|
||||||
|
self:checkNewLow(piece)
|
||||||
|
piece.manipulations = piece.manipulations + 1
|
||||||
|
if piece.manipulations >= self.MANIPULATIONS_MAX then
|
||||||
|
piece:moveInGrid({ x = 0, y = 1 }, 1, grid)
|
||||||
|
if piece:isDropBlocked(grid) then
|
||||||
|
piece.locked = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function SRS:canPieceRotate() return true end
|
||||||
|
|
||||||
|
return SRS
|
||||||
@@ -3,44 +3,37 @@ local Ruleset = require 'tetris.rulesets.arika_srs'
|
|||||||
|
|
||||||
local SRS = Ruleset:extend()
|
local SRS = Ruleset:extend()
|
||||||
|
|
||||||
SRS.name = "Guideline SRS"
|
SRS.name = "SRS-X"
|
||||||
SRS.hash = "Standard"
|
SRS.hash = "StandardEXP"
|
||||||
|
SRS.world = true
|
||||||
|
SRS.colourscheme = {
|
||||||
|
I = "C",
|
||||||
|
L = "O",
|
||||||
|
J = "B",
|
||||||
|
S = "G",
|
||||||
|
Z = "R",
|
||||||
|
O = "Y",
|
||||||
|
T = "M",
|
||||||
|
}
|
||||||
|
SRS.softdrop_lock = true
|
||||||
|
SRS.harddrop_lock = false
|
||||||
|
|
||||||
SRS.enable_IRS_wallkicks = true
|
SRS.enable_IRS_wallkicks = true
|
||||||
|
|
||||||
function SRS:check_new_low(piece)
|
SRS.MANIPULATIONS_MAX = 24
|
||||||
|
SRS.ROTATIONS_MAX = 12
|
||||||
|
|
||||||
|
function SRS:checkNewLow(piece)
|
||||||
for _, block in pairs(piece:getBlockOffsets()) do
|
for _, block in pairs(piece:getBlockOffsets()) do
|
||||||
local y = piece.position.y + block.y
|
local y = piece.position.y + block.y
|
||||||
if y > piece.lowest_y then
|
if y > piece.lowest_y then
|
||||||
piece.manipulations = 0
|
--piece.manipulations = 0
|
||||||
|
--piece.rotations = 0
|
||||||
piece.lowest_y = y
|
piece.lowest_y = y
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
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}},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
-- Component functions.
|
-- Component functions.
|
||||||
|
|
||||||
function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
||||||
@@ -61,7 +54,7 @@ function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
|||||||
if grid:canPlacePiece(kicked_piece) then
|
if grid:canPlacePiece(kicked_piece) then
|
||||||
piece:setRelativeRotation(rot_dir)
|
piece:setRelativeRotation(rot_dir)
|
||||||
piece:setOffset(offset)
|
piece:setOffset(offset)
|
||||||
self:onPieceRotate(piece, grid)
|
self:onPieceRotate(piece, grid, offset.y < 0)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -70,12 +63,16 @@ end
|
|||||||
|
|
||||||
function SRS:onPieceCreate(piece, grid)
|
function SRS:onPieceCreate(piece, grid)
|
||||||
piece.manipulations = 0
|
piece.manipulations = 0
|
||||||
|
piece.rotations = 0
|
||||||
piece.lowest_y = -math.huge
|
piece.lowest_y = -math.huge
|
||||||
end
|
end
|
||||||
|
|
||||||
function SRS:onPieceDrop(piece, grid)
|
function SRS:onPieceDrop(piece, grid)
|
||||||
self:check_new_low(piece)
|
self:checkNewLow(piece)
|
||||||
if piece.manipulations >= 15 and piece:isDropBlocked(grid) then
|
if (
|
||||||
|
piece.manipulations >= self.MANIPULATIONS_MAX or
|
||||||
|
piece.rotations >= self.ROTATIONS_MAX
|
||||||
|
) and piece:isDropBlocked(grid) then
|
||||||
piece.locked = true
|
piece.locked = true
|
||||||
else
|
else
|
||||||
piece.lock_delay = 0 -- step reset
|
piece.lock_delay = 0 -- step reset
|
||||||
@@ -86,23 +83,26 @@ function SRS:onPieceMove(piece, grid)
|
|||||||
piece.lock_delay = 0 -- move reset
|
piece.lock_delay = 0 -- move reset
|
||||||
if piece:isDropBlocked(grid) then
|
if piece:isDropBlocked(grid) then
|
||||||
piece.manipulations = piece.manipulations + 1
|
piece.manipulations = piece.manipulations + 1
|
||||||
if piece.manipulations >= 15 then
|
if piece.manipulations >= SRS.MANIPULATIONS_MAX then
|
||||||
piece.locked = true
|
piece.locked = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function SRS:onPieceRotate(piece, grid)
|
function SRS:onPieceRotate(piece, grid, upward)
|
||||||
piece.lock_delay = 0 -- rotate reset
|
piece.lock_delay = 0 -- rotate reset
|
||||||
self:check_new_low(piece)
|
if upward or piece:isDropBlocked(grid) then
|
||||||
piece.manipulations = piece.manipulations + 1
|
piece.rotations = piece.rotations + 1
|
||||||
if piece:isDropBlocked(grid) then
|
if piece.rotations >= self.ROTATIONS_MAX and piece:isDropBlocked(grid) then
|
||||||
if piece.manipulations >= 15 then
|
|
||||||
piece.locked = true
|
piece.locked = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function SRS:canPieceRotate(piece)
|
||||||
|
return piece.rotations < self.ROTATIONS_MAX
|
||||||
|
end
|
||||||
|
|
||||||
function SRS:get180RotationValue() return 2 end
|
function SRS:get180RotationValue() return 2 end
|
||||||
|
|
||||||
return SRS
|
return SRS
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ local Ruleset = require 'tetris.rulesets.ruleset'
|
|||||||
local SRS = Ruleset:extend()
|
local SRS = Ruleset:extend()
|
||||||
|
|
||||||
SRS.name = "Ti-World"
|
SRS.name = "Ti-World"
|
||||||
SRS.hash = "Bad I-kicks"
|
SRS.hash = "StandardTI"
|
||||||
SRS.world = true
|
SRS.world = true
|
||||||
SRS.colourscheme = {
|
SRS.colourscheme = {
|
||||||
I = "C",
|
I = "C",
|
||||||
@@ -18,6 +18,9 @@ SRS.colourscheme = {
|
|||||||
SRS.softdrop_lock = false
|
SRS.softdrop_lock = false
|
||||||
SRS.harddrop_lock = true
|
SRS.harddrop_lock = true
|
||||||
|
|
||||||
|
SRS.MANIPULATIONS_MAX = 10
|
||||||
|
SRS.ROTATIONS_MAX = 8
|
||||||
|
|
||||||
SRS.spawn_positions = {
|
SRS.spawn_positions = {
|
||||||
I = { x=5, y=4 },
|
I = { x=5, y=4 },
|
||||||
J = { x=4, y=5 },
|
J = { x=4, y=5 },
|
||||||
@@ -86,22 +89,22 @@ SRS.block_offsets = {
|
|||||||
SRS.wallkicks_3x3 = {
|
SRS.wallkicks_3x3 = {
|
||||||
[0]={
|
[0]={
|
||||||
[1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}},
|
[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}},
|
[2]={{x=1,y=0},{x=2,y=0},{x=1,y=1},{x=2,y=1},{x=-1,y=0},{x=-2,y=0},{x=-1,y=1},{x=-2,y=1},{x=0,y=-1},{x=3,y=0},{x=-3,y=0}},
|
||||||
[3]={{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}},
|
||||||
},
|
},
|
||||||
[1]={
|
[1]={
|
||||||
[0]={{x=1, y=0}, {x=1, y=1}, {x=0, y=-2}, {x=1, y=-2}},
|
[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}},
|
[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}},
|
[3]={{x=0,y=1},{x=0,y=2},{x=-1,y=1},{x=-1,y=2},{x=0,y=-1},{x=0,y=-2},{x=-1,y=-1},{x=-1,y=-2},{x=1,y=0},{x=0,y=3},{x=0,y=-3}},
|
||||||
},
|
},
|
||||||
[2]={
|
[2]={
|
||||||
[0]={{x=0, y=1}, {x=0, y=-1}},
|
[0]={{x=-1,y=0},{x=-2,y=0},{x=-1,y=-1},{x=-2,y=-1},{x=1,y=0},{x=2,y=0},{x=1,y=-1},{x=2,y=-1},{x=0,y=1},{x=-3,y=0},{x=3,y=0}},
|
||||||
[1]={{x=-1, y=0}, {x=-1, y=-1}, {x=0, y=2}, {x=-1, y=2}},
|
[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]={{x=1, y=0}, {x=1, y=-1}, {x=0, y=2}, {x=1, y=2}},
|
||||||
},
|
},
|
||||||
[3]={
|
[3]={
|
||||||
[0]={{x=-1, y=0}, {x=-1, y=1}, {x=0, y=-2}, {x=-1, y=-2}},
|
[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}},
|
[1]={{x=0,y=1},{x=0,y=2},{x=1,y=1},{x=1,y=2},{x=0,y=-1},{x=0,y=-2},{x=1,y=-1},{x=1,y=-2},{x=-1,y=0},{x=0,y=3},{x=0,y=-3}},
|
||||||
[2]={{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}},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -109,22 +112,22 @@ SRS.wallkicks_3x3 = {
|
|||||||
SRS.wallkicks_line = {
|
SRS.wallkicks_line = {
|
||||||
[0]={
|
[0]={
|
||||||
[1]={{x=-2, y= 0}, {x= 1, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
|
[1]={{x=-2, y= 0}, {x= 1, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
|
||||||
[2]={},
|
[2]={{x=-1,y=0},{x=-2,y=0},{x=1,y=0},{x=2,y=0},{x=0,y=1}},
|
||||||
[3]={{x= 2, y= 0}, {x=-1, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
|
[3]={{x= 2, y= 0}, {x=-1, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
|
||||||
},
|
},
|
||||||
[1]={
|
[1]={
|
||||||
[0]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 2}},
|
[0]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 2}},
|
||||||
[2]={{x=-1, y= 0}, {x= 2, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
|
[2]={{x=-1, y= 0}, {x= 2, y= 0}, {x=-1, y=-2}, {x= 2, y= 1}},
|
||||||
[3]={},
|
[3]={{x=0,y=1},{x=0,y=2},{x=0,y=-1},{x=0,y=-2},{x=-1,y=0}},
|
||||||
},
|
},
|
||||||
[2]={
|
[2]={
|
||||||
[0]={},
|
[0]={{x=1,y=0},{x=2,y=0},{x=-1,y=0},{x=-2,y=0},{x=0,y=-1}},
|
||||||
[1]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 1}},
|
[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]={{x= 2, y= 0}, {x=-1, y= 0}, {x= 2, y=-1}, {x=-1, y= 1}},
|
||||||
},
|
},
|
||||||
[3]={
|
[3]={
|
||||||
[0]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 2}},
|
[0]={{x=-2, y= 0}, {x= 1, y= 0}, {x=-2, y=-1}, {x= 1, y= 2}},
|
||||||
[1]={},
|
[1]={{x=0,y=1},{x=0,y=2},{x=0,y=-1},{x=0,y=-2},{x=1,y=0}},
|
||||||
[2]={{x= 1, y= 0}, {x=-2, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
|
[2]={{x= 1, y= 0}, {x=-2, y= 0}, {x= 1, y=-2}, {x=-2, y= 1}},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -147,9 +150,9 @@ function SRS:attemptWallkicks(piece, new_piece, rot_dir, grid)
|
|||||||
for idx, offset in pairs(kicks) do
|
for idx, offset in pairs(kicks) do
|
||||||
kicked_piece = new_piece:withOffset(offset)
|
kicked_piece = new_piece:withOffset(offset)
|
||||||
if grid:canPlacePiece(kicked_piece) then
|
if grid:canPlacePiece(kicked_piece) then
|
||||||
self:onPieceRotate(piece, grid)
|
|
||||||
piece:setRelativeRotation(rot_dir)
|
piece:setRelativeRotation(rot_dir)
|
||||||
piece:setOffset(offset)
|
piece:setOffset(offset)
|
||||||
|
self:onPieceRotate(piece, grid, offset.y < 0)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -162,35 +165,37 @@ function SRS:onPieceCreate(piece, grid)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function SRS:onPieceDrop(piece, grid)
|
function SRS:onPieceDrop(piece, grid)
|
||||||
|
if (piece.manipulations >= self.MANIPULATIONS_MAX or piece.rotations >= self.ROTATIONS_MAX) and piece:isDropBlocked(grid) then
|
||||||
|
piece.locked = true
|
||||||
|
else
|
||||||
piece.lock_delay = 0 -- step reset
|
piece.lock_delay = 0 -- step reset
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function SRS:onPieceMove(piece, grid)
|
function SRS:onPieceMove(piece, grid)
|
||||||
piece.lock_delay = 0 -- move reset
|
piece.lock_delay = 0 -- move reset
|
||||||
if piece:isDropBlocked(grid) then
|
if piece:isDropBlocked(grid) then
|
||||||
piece.manipulations = piece.manipulations + 1
|
piece.manipulations = piece.manipulations + 1
|
||||||
if piece.manipulations >= 10 then
|
if piece.manipulations >= self.MANIPULATIONS_MAX then
|
||||||
piece.locked = true
|
piece.locked = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function SRS:onPieceRotate(piece, grid)
|
function SRS:onPieceRotate(piece, grid, upward)
|
||||||
piece.lock_delay = 0 -- rotate reset
|
piece.lock_delay = 0 -- rotate reset
|
||||||
if piece:isDropBlocked(grid) then
|
if upward or piece:isDropBlocked(grid) then
|
||||||
piece.rotations = piece.rotations + 1
|
piece.rotations = piece.rotations + 1
|
||||||
if piece.rotations >= 8 then
|
if piece.rotations >= self.ROTATIONS_MAX and piece:isDropBlocked(grid) then
|
||||||
piece.locked = true
|
piece.locked = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function SRS:get180RotationValue()
|
function SRS:canPieceRotate(piece)
|
||||||
if config.gamesettings.world_reverse == 1 then
|
return piece.rotations < self.ROTATIONS_MAX
|
||||||
return 1
|
|
||||||
else
|
|
||||||
return 3
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function SRS:get180RotationValue() return 3 end
|
||||||
|
|
||||||
return SRS
|
return SRS
|
||||||
|
|||||||