Compare commits

...

278 Commits

Author SHA1 Message Date
Ishaan Bhardwaj
e1741440f2 Fixed an edge case with the last save commit 2021-05-22 21:42:14 -04:00
Ishaan Bhardwaj
c56f290921 Fixed a bug where the game would sometimes not save on macOS
Version bump to beta5.2
2021-05-22 21:19:33 -04:00
Ishaan Bhardwaj
86e975f929 Fixed up an edge case in immobile spin 2021-05-22 14:41:51 -04:00
Ishaan Bhardwaj
9f8e9a9778 Changed additive gravity behavior for main TGM modes 2021-05-21 15:34:50 -04:00
Ishaan Bhardwaj
62f9475fa9 Small cosmetic change to input config menus 2021-05-21 15:32:28 -04:00
Ishaan Bhardwaj
99d3732d00 Bump to v0.3-beta5.1, release tomorrow 2021-05-20 23:25:24 -04:00
Ishaan Bhardwaj
f5121b62e5 Added bigint comparison metamethods 2021-05-15 22:39:15 -04:00
Ishaan Bhardwaj
cbdbfa6633 Fixed a small cosmetic issue in Survival A1 2021-04-26 21:51:42 -04:00
Ishaan Bhardwaj
1b1abc9792 Fixed an issue with buffer lock inputs 2021-04-20 16:11:49 -04:00
Ishaan Bhardwaj
894e99e677 Cambridge has a logo now! 2021-04-16 22:14:59 -04:00
Ishaan Bhardwaj
d4b619da89 Fixed an edge case with last commit 2021-04-08 13:17:34 -04:00
Ishaan Bhardwaj
3766149cb7 Fixed a 0 ARR gravity bug 2021-04-08 11:55:36 -04:00
Ishaan Bhardwaj
449ca16bc4 Updated to be on rebrand 2021-04-01 14:11:38 -04:00
Ishaan Bhardwaj
71d76e8a6b Fixed Death torikan (why was this changed?) 2021-03-30 22:08:48 -04:00
Ishaan Bhardwaj
3bdc6e1b2d HOTFIX TO BETA5: Fixed another floorkick issue with ARS 2021-03-30 21:29:09 -04:00
Ishaan Bhardwaj
d9b6c85704 Fix up credits a bit, add new people 2021-03-28 10:03:27 -04:00
Ishaan Bhardwaj
5ce0686e1a Update version to beta5 2021-03-26 23:11:59 -04:00
Ishaan Bhardwaj
3cf496ba98 Fixed a Ti-ARS floorkick issue 2021-03-16 14:13:44 -04:00
Ishaan Bhardwaj
5ddc6ec561 Fixed a priority order bug with Ti-ARS and ACE-ARS 2021-03-11 20:16:23 -05:00
Ishaan Bhardwaj
b91ffc913b CRS no longer locks in midair 2021-03-11 15:29:24 -05:00
Ishaan Bhardwaj
ab445ff699 Cleaned up love.load 2021-03-11 09:24:19 -05:00
Ishaan Bhardwaj
7b7a255bf8 Fullscreen swap now persists between reboots 2021-03-11 08:33:05 -05:00
Ishaan Bhardwaj
57721ed35d Shrunk the bone coloring code 2021-03-10 16:30:45 -05:00
Ishaan Bhardwaj
8383d3f445 Debug print statement removal 2021-03-10 14:06:57 -05:00
Ishaan Bhardwaj
116284f31c Fixed colour scheme issues for non-standard piece sets 2021-03-10 13:58:08 -05:00
Ishaan Bhardwaj
2189e3a7b8 Made a previous fix to soft drop points obsolete w/ new fix 2021-03-10 13:30:29 -05:00
Ishaan Bhardwaj
b1d325b714 Fixed negative soft drop and hard drop points again
For finer control of piece drops, use GameMode:onPieceDrop
2021-03-09 16:35:57 -05:00
Ishaan Bhardwaj
4ab5e3747a Fixed an issue with the generic bag randomizer 2021-03-09 13:00:43 -05:00
Ishaan Bhardwaj
9761ead48f Fixed an odd bug where the score wouldn't reset in Big A2 2021-03-08 21:12:05 -05:00
Ishaan Bhardwaj
8dedc8a70e Cut down Big A2's size 2021-03-08 20:59:51 -05:00
Ishaan Bhardwaj
21769f21c8 Sakura removed from the main game, will be re-added after mass bugfix 2021-03-08 18:53:01 -05:00
Ishaan Bhardwaj
5cf26b4500 Merge branch 'master' of https://github.com/sashlilac/cambridge 2021-03-08 12:41:54 -05:00
Ishaan Bhardwaj
4b4a968632 Fixed a bone block drawing issue 2021-03-08 12:41:46 -05:00
Ishaan Bhardwaj
e0d98de50d The Discord server has been reopened! 2021-03-08 11:11:26 -05:00
Ishaan Bhardwaj
4992ea733c Made the bone block world sprite a bit brighter 2021-03-08 10:20:25 -05:00
Ishaan Bhardwaj
30ca434027 Fixed ACE-ARS to floorkick infinitely 2021-03-07 22:24:11 -05:00
Ishaan Bhardwaj
502a50d004 Merge pull request #16 from SashLilac/hat_handling
Fixed the handling of joystick hats.
2021-03-07 21:17:59 -05:00
Joe Z
36f5287a39 Fixed the hat input mapping. 2021-03-07 20:43:55 -05:00
Ishaan Bhardwaj
a9bbe4a08d Init hat handling 2021-03-07 16:42:33 -05:00
Ishaan Bhardwaj
ee431f5fd8 Revert "(Hopefully) Fixed an obscure bug with SOCD and joystick hats"
This did not fix it.
This reverts commit 36f2672e06.
2021-03-07 16:29:01 -05:00
Ishaan Bhardwaj
36f2672e06 (Hopefully) Fixed an obscure bug with SOCD and joystick hats 2021-03-07 16:20:43 -05:00
Ishaan Bhardwaj
6ecea7edb1 Fixed an issue where axes would not detect left or up 2021-03-07 16:03:02 -05:00
Ishaan Bhardwaj
dc764b9177 Fixed the timer in Sakura to be the correct value 2021-03-07 15:52:55 -05:00
Ishaan Bhardwaj
5a1494cb5a Fixed a timer display issue in Sakura 2021-03-07 15:52:24 -05:00
Ishaan Bhardwaj
684c4f5b78 Sakura stage time limit fix 2021-03-07 15:18:13 -05:00
Ishaan Bhardwaj
b568c0fe69 Removed the credit roll from AX 2021-03-07 09:49:07 -05:00
Ishaan Bhardwaj
2ea75cdfaf Fixed a corner case in the last commit 2021-03-06 22:13:38 -05:00
Ishaan Bhardwaj
1f0b43f1b7 ACTUALLY fixed negative drop points 2021-03-06 22:00:30 -05:00
Ishaan Bhardwaj
40bdc5ed99 Revert "Fixed negative drop points"
This commit didn't actually fix the issue.
This reverts commit 33f2a96ae8.
2021-03-06 21:54:36 -05:00
Ishaan Bhardwaj
33f2a96ae8 Fixed negative drop points 2021-03-06 21:39:13 -05:00
Ishaan Bhardwaj
846013ce7a Indentation fix in funcs.lua 2021-03-04 19:05:43 -05:00
Ishaan Bhardwaj
37c85adc75 Fixed clamping once more 2021-03-04 19:05:20 -05:00
Ishaan Bhardwaj
e7bb44deb4 Fixed clamping 2021-03-04 18:44:30 -05:00
Ishaan Bhardwaj
57518dc299 Removed some future features that I committed by accident 2021-03-04 15:18:32 -05:00
Ishaan Bhardwaj
0453a3db97 Fixed an issue where first piece IHS was possible when it shouldn't have been 2021-03-04 15:16:23 -05:00
Ishaan Bhardwaj
b85de17e51 Last LCD added to fix up line clear animations 2021-03-03 11:54:43 -05:00
Ishaan Bhardwaj
163b8f6cc5 Added a version display 2021-03-03 10:33:10 -05:00
Ishaan Bhardwaj
7250bee619 Ti randomizer no longer draws Z as first piece 2021-03-02 20:31:56 -05:00
Ishaan Bhardwaj
83de216408 Touched up screenshotting a bit 2021-03-01 20:37:44 -05:00
Ishaan Bhardwaj
ba235c8a41 Credits update <3 2021-02-28 18:40:53 -05:00
Ishaan Bhardwaj
ca18d090c9 Split keyboard and joystick input config screens 2021-02-25 14:41:13 -05:00
Ishaan Bhardwaj
a3a27d2566 Refactored joystick input handling 2021-02-24 16:58:42 -05:00
Ishaan Bhardwaj
b15cd9802f Updated DAS last key setting to not use hacky workaround
DAS last key is off by default
2021-02-22 21:43:01 -05:00
Ishaan Bhardwaj
4c4a818c5c Race 40 added to main game, PAIRS moved to modpack 2021-02-21 23:19:53 -05:00
Ishaan Bhardwaj
716de2814b More bigint type checks added
Strict checking is still off, however a check can be coerced
2021-02-21 20:38:16 -05:00
Ishaan Bhardwaj
bf19f49323 Add piece last rotated events 2021-02-21 10:48:15 -05:00
Ishaan Bhardwaj
1234e78354 Refactored immobile detection 2021-02-21 10:41:05 -05:00
Ishaan Bhardwaj
9129503d54 Fixed a sound effect handle with negative gravity 2021-02-21 10:08:58 -05:00
Ishaan Bhardwaj
eae58f11e9 Fixed a clipping issue with negative gravity
Gamemodes are able to define their own piece class behavior to override negative gravity handling
2021-02-21 10:05:09 -05:00
Ishaan Bhardwaj
3cf5daeb2e Piece class now handles negative gravity correctly 2021-02-21 09:52:50 -05:00
Ishaan Bhardwaj
1dfe68ccff onExit call for exiting prematurely 2021-02-19 15:58:00 -05:00
Ishaan Bhardwaj
8a459b68ba Allowed gamemode and ruleset objects to control each other
Also added GameMode:onExit(), which triggers on game exit or retry
2021-02-19 11:01:18 -05:00
Ishaan Bhardwaj
cb2b693bcb Fixed T-floorkick behavior in Ti/ACE ARS 2021-02-18 21:04:03 -05:00
Ishaan Bhardwaj
ef6d156d38 Turned draw offsets and above field offsets into function calls 2021-02-18 15:09:27 -05:00
Ishaan Bhardwaj
83e498534c Merge branch 'master' of https://github.com/sashlilac/cambridge 2021-02-18 12:01:05 -05:00
Ishaan Bhardwaj
8f19c73e2a Simultaneous keyboard and joystick inputs implemented!
Implements #9!!!
2021-02-18 12:00:57 -05:00
Ishaan Bhardwaj
e36b855ff7 The Discord server is no longer sponsored by the project. 2021-02-18 10:42:19 -05:00
Ishaan Bhardwaj
23b58951cb World rule Survival A2 has a lenient torikan time 2021-02-17 22:46:33 -05:00
Ishaan Bhardwaj
1d73916b7c Arika-SRS rulesets no longer lock immediately 2021-02-17 18:29:14 -05:00
Ishaan Bhardwaj
3947e9f02f Fix the drop block lock rotation with SRS 2021-02-17 17:31:16 -05:00
Ishaan Bhardwaj
99b15803ee Adjusted 0 ARR to trigger onPieceMove multiple times 2021-02-17 17:21:51 -05:00
Ishaan Bhardwaj
d350b25726 Forgot to set guideline SRS to always rotate 2021-02-17 14:52:05 -05:00
Ishaan Bhardwaj
44e4d00172 Merge branch 'master' of https://github.com/sashlilac/cambridge 2021-02-17 14:48:42 -05:00
Ishaan Bhardwaj
31e2529265 Upward kicks for SRS count toward rotation limit 2021-02-17 14:48:35 -05:00
Ishaan Bhardwaj
ea7c75f0b3 Cambridge Discord Server temp. decommissioned
Please contact Milla#7746 on Discord for help.
2021-02-17 10:45:07 -05:00
Ishaan Bhardwaj
714c6b5e99 Floorkicks reworked (read comments)
If not classic lock, upward kicks reset to the top of the tile
2021-02-16 23:28:54 -05:00
Ishaan Bhardwaj
6a5d5a9c88 Fixed some modes' getNextPiece routines 2021-02-16 17:02:13 -05:00
Ishaan Bhardwaj
03491ba151 Strategy mode endgame nerfed 2021-02-16 16:57:31 -05:00
Ishaan Bhardwaj
6e22e3d15b Ti-ARS autolock fix 2021-02-16 16:19:51 -05:00
Ishaan Bhardwaj
66ab5992ad Added onPieceMove/Rotate/Drop for gamemodes 2021-02-16 15:27:57 -05:00
Ishaan Bhardwaj
2c07c2a58c BigInt changes, read extended description
Disabled strict type checking, can be re-enabled in bleeding edge. (This is done so bigint ops run faster)
Added a negation method and updated the corresponding metamethod to use it.
2021-02-16 13:03:53 -05:00
Ishaan Bhardwaj
a4d3f3bffc Update README.md 2021-02-16 13:00:07 -05:00
Ishaan Bhardwaj
9ac60cbb5e afterLineClear func added and splits time draw fix 2021-02-15 12:26:52 -05:00
Ishaan Bhardwaj
cdd846c3e6 Made the volume sliders scroll more consistently 2021-02-13 22:00:45 -05:00
Ishaan Bhardwaj
33d260b753 Removed the print statement from A2 2021-02-12 23:31:13 -05:00
Ishaan Bhardwaj
1644fcdf8e Bigint exponentiation by 1 now returns a clone 2021-02-12 10:05:04 -05:00
Ishaan Bhardwaj
f3c1cf6e1f Fixed an issue where DS-World wouldn't harddrop 2021-02-11 22:11:35 -05:00
Ishaan Bhardwaj
06a8a2ebf7 Mandate safelock on 0 ARE rulesets/modes 2021-02-11 22:08:52 -05:00
Ishaan Bhardwaj
15354ce004 dropToBottom no longer resets lock delay
it's already handled by the rulesets anyhow
2021-02-11 21:20:23 -05:00
Ishaan Bhardwaj
af02cd3467 Classic lock (GB/NES-like) added as a gamemode var 2021-02-11 15:46:56 -05:00
Ishaan Bhardwaj
acb05918c1 Custom line clear animations 2021-02-10 23:10:10 -05:00
Ishaan Bhardwaj
b644c8e457 Revert "Default line clear animation set to fadeout"
Please, reminder to self, TEST YOUR COMMITS.
This reverts commit 288961e12a.
2021-02-10 22:46:58 -05:00
Ishaan Bhardwaj
288961e12a Default line clear animation set to fadeout 2021-02-10 22:41:07 -05:00
Ishaan Bhardwaj
a047e51681 Framework for custom line clear animations added
Colored fadeout is the default
2021-02-10 18:35:51 -05:00
Ishaan Bhardwaj
77f24f5ee5 Human readable bigint output changes 2021-02-10 12:45:55 -05:00
Ishaan Bhardwaj
32c2274bef Optimized bigint exponentiation (again) 2021-02-10 11:38:10 -05:00
Ishaan Bhardwaj
4920e5de1c Added another type check to the bigint 2021-02-10 11:15:56 -05:00
Ishaan Bhardwaj
8418fc8ab7 Update README.md 2021-02-10 10:32:18 -05:00
Ishaan Bhardwaj
711a5120f1 Update README.md 2021-02-10 10:31:52 -05:00
Ishaan Bhardwaj
e7c3c9446a Cambridge banner looks better on dark theme now
Courtesy of @sinefuse
2021-02-10 09:05:10 -05:00
Ishaan Bhardwaj
3ac39acd7a Removed bigint comparison metamethods (read below)
Use bigint.compare from now on
2021-02-09 12:27:57 -05:00
Ishaan Bhardwaj
d0505251b3 Spawn positions now ruleset dependent
Is configurable in options
2021-02-08 23:23:50 -05:00
Ishaan Bhardwaj
bb0fe2ac20 BigInt now has a digits method (read comments)
Kind of unnecessary but included for completeness
2021-02-08 16:56:06 -05:00
Ishaan Bhardwaj
986ebac47f BigInt division fixed 2021-02-08 16:07:48 -05:00
Ishaan Bhardwaj
9799147f96 Revert "BigInt fixes and optimization (read comments)"
Apparently division *still* isn't being handled correctly.
This reverts commit 1dda12e4be.
2021-02-08 14:53:19 -05:00
Ishaan Bhardwaj
1dda12e4be BigInt fixes and optimization (read comments)
Fixed a nasty division bug where intermediate operations could result in negative zero. Optimized exponentiation to use exponentiation by squaring.
2021-02-08 14:10:34 -05:00
Ishaan Bhardwaj
38947e00c0 Added a tostring function for bigints 2021-02-08 10:34:47 -05:00
Ishaan Bhardwaj
035f6dd7b4 Fixed big division when (big1 < big2) 2021-02-08 10:23:10 -05:00
Ishaan Bhardwaj
aa3eadc93d Update README.md 2021-02-08 09:00:51 -05:00
Ishaan Bhardwaj
cb6962825f Update package.bat script 2021-02-07 20:50:27 -05:00
Ishaan Bhardwaj
b5e7ce5be6 Grid outline draw refactorization 2021-02-05 22:13:10 -05:00
Ishaan Bhardwaj
1ccd6a09d3 Gamemodes have a default (empty) name 2021-02-05 21:44:29 -05:00
Ishaan Bhardwaj
5a074f77cf Adjusted how DAS cut subtracts from the counter 2021-02-03 16:50:03 -05:00
Ishaan Bhardwaj
81677221f1 Fixed 0 next queue modes 2021-02-03 11:42:21 -05:00
Ishaan Bhardwaj
a998be6f7b Global vars suck. Nothing more 2021-02-02 22:30:28 -05:00
Ishaan Bhardwaj
9c1c8eea21 Added default high score retrieval method 2021-02-02 14:51:49 -05:00
Ishaan Bhardwaj
f022c6c4b7 Sakura no longer draws game over effect on completion 2021-02-01 15:58:30 -05:00
Ishaan Bhardwaj
38f3d23b95 More default methods for gamemodes provided 2021-02-01 15:41:43 -05:00
Ishaan Bhardwaj
816d27db39 Set default gravity for gamemode 2021-02-01 14:50:31 -05:00
Ishaan Bhardwaj
ce08ffd3da SRS-X fixed to use symmetric wallkicks 2021-01-30 22:28:34 -05:00
Ishaan Bhardwaj
f0e84a8874 SRS-X rotate lock reset behavior fixed 2021-01-30 16:54:09 -05:00
Ishaan Bhardwaj
5e02471fb4 SRS now has upgraded 180s 2021-01-30 16:49:52 -05:00
Oshisaure
fa2fe77081 Apparently macs don't have a printscreen key, screenshot bound to f12 now instead 2021-01-29 22:29:27 +00:00
Joe Z
682c4a485a Updated fonts. 2021-01-29 12:24:54 -05:00
Oshisaure
68760105cc Bound printscreen to saving screenshots 2021-01-29 04:13:17 +00:00
Ishaan Bhardwaj
e19da98ea1 Standard SRS now has correct amount of move resets 2021-01-28 21:19:47 -05:00
Ishaan Bhardwaj
e8904b92ed check_new_low doesn't exist! 2021-01-28 21:15:04 -05:00
Ishaan Bhardwaj
4f574e7716 Guideline SRS now specifies dependency 2021-01-28 21:13:31 -05:00
Ishaan Bhardwaj
f1528e8d71 Fixed the SRS variants from latest commit. 2021-01-28 21:05:36 -05:00
Joe Zeng
79a25c3954 Renamed Marathon AX4 to Survival AX, among other things. 2021-01-28 01:15:21 -05:00
Ishaan Bhardwaj
0f3883e18d Sakura ghost piece fix 2021-01-27 18:28:12 -05:00
Ishaan Bhardwaj
1acd0ec65a Holding a piece that would block you out now works 2021-01-27 13:29:53 -05:00
Ishaan Bhardwaj
b22f671409 2020, A2, A3 section time draw fixes 2021-01-25 22:26:55 -05:00
Ishaan Bhardwaj
0b6f62d50e Applied a fix for locking big pieces out of the grid 2021-01-25 16:34:22 -05:00
Ishaan Bhardwaj
086f327371 Large commit, read below
DAS Cut Delay added and configurable (like ARR and DAS)
BigInt lib added
IRS / IHS do not take effect when ARE = 0
Game now saves highscore correctly on game over
2021-01-24 14:55:35 -05:00
Ishaan Bhardwaj
3c83ae0bf4 Fixed stray ends 2021-01-23 13:50:40 -05:00
Ishaan Bhardwaj
450833b246 Instant ARR fix on grids not 10-wide 2021-01-23 11:35:07 -05:00
Ishaan Bhardwaj
8e7a5418dc Fixed how grade points decay in A2 and A3 2021-01-23 11:34:46 -05:00
Ishaan Bhardwaj
6609b642dc formatBigNum prettifier 2021-01-20 10:53:39 -05:00
Ishaan Bhardwaj
452879ebab Fixed Marathon A3 section times, read comments
Some modes may not launch currently, will fix
2021-01-20 10:53:22 -05:00
Ishaan Bhardwaj
70a827b477 fixed A2 point decay 2021-01-16 13:27:07 -05:00
Ishaan Bhardwaj
d281a732db fixed A2 M-roll reqs again 2021-01-16 12:57:20 -05:00
Ishaan Bhardwaj
01e91fbd93 Fixes issues with retrying modes with BGM 2021-01-16 09:34:41 -05:00
Ishaan Bhardwaj
ece853c9d3 Swapped opacity and brightness for hold color 2021-01-15 16:00:15 -05:00
Ishaan Bhardwaj
ea8d008370 Set piece opacity fixes 2021-01-15 15:46:28 -05:00
Ishaan Bhardwaj
e20eb048c8 Game over animation (customizable per mode) 2021-01-14 21:51:47 -05:00
Ishaan Bhardwaj
a33ca1af24 Fixed a bug where you could not get M-roll in A2 2021-01-14 19:34:02 -05:00
Ishaan Bhardwaj
664bca2282 Fixed a notorious ARR bug 2021-01-14 19:27:20 -05:00
Ishaan Bhardwaj
fc8fb8b66f Added immobile spin bonus toggle (read comments)
Use piece.spin in your onPieceLock method to check for a spin
2021-01-14 19:22:53 -05:00
Ishaan Bhardwaj
fc58e6e908 Square mode added as toggle
Other things may get toggles too
Immobile spins, cascade, credit roll?
2021-01-14 17:52:23 -05:00
Ishaan Bhardwaj
061f6f5164 Square mode update 2021-01-14 16:28:18 -05:00
Ishaan Bhardwaj
4e9cea7dda Another bottom SFX bug fix 2021-01-12 15:57:45 -05:00
Ishaan Bhardwaj
fa97216167 Minor piece bottom SFX fix 2021-01-12 15:20:22 -05:00
Ishaan Bhardwaj
3f8d68cc9d Small game / settings fix 2021-01-12 14:32:10 -05:00
Ishaan Bhardwaj
6639d73c1c Spawn positions are now configurable 2021-01-12 13:47:03 -05:00
Ishaan Bhardwaj
668f061077 Fixed drawing frame on non-standard grids 2021-01-11 22:40:48 -05:00
Ishaan Bhardwaj
cb70967b82 Default field graphic fix 2021-01-11 15:52:11 -05:00
Ishaan Bhardwaj
0c2ba5f0cc Custom field heights implemented 2021-01-11 15:46:43 -05:00
Ishaan Bhardwaj
6d07a3b820 Removed outdated functions 2021-01-11 15:27:18 -05:00
Ishaan Bhardwaj
2de13a97f0 10-wide graphic restored 2021-01-11 15:17:32 -05:00
Ishaan Bhardwaj
512c2149f0 Adjusted spawn x positions 2021-01-11 14:48:03 -05:00
Ishaan Bhardwaj
6fb19220b7 Marathon A1 is back to 10 wide 2021-01-10 23:00:03 -05:00
Ishaan Bhardwaj
08da67c434 Merge pull request #13 from SashLilac/arbitrary-widths
Init arbitrary widths functionality.
2021-01-10 22:59:05 -05:00
Joe Zeng
2d63ca8ee1 Changed row initialization to also use parametrized width. 2021-01-10 22:52:56 -05:00
Ishaan Bhardwaj
0f09d47e60 Init arbitrary widths 2021-01-10 22:40:13 -05:00
Ishaan Bhardwaj
9d44d1e771 Fixed big mode gravity being twice as big 2021-01-10 22:01:25 -05:00
Ishaan Bhardwaj
5d022f9037 Rulesets can offset next queue draws (read below)
A ruleset can now have offsets for where pieces should be drawn in queue
No rulesets use this *yet*
2021-01-10 16:42:48 -05:00
Ishaan Bhardwaj
818743fe77 No "RANDOM PIECES ACTIVE!" on Sakura for pentos 2021-01-10 16:31:48 -05:00
Ishaan Bhardwaj
f22424d671 Update README.md - loading custom assets 2021-01-10 13:39:28 -05:00
Ishaan Bhardwaj
dd6baf1fe6 Draw outline now has line clear anim 2021-01-10 11:41:34 -05:00
Ishaan Bhardwaj
11cf5a9d55 Spawn SE bugfix 2021-01-10 11:15:36 -05:00
Ishaan Bhardwaj
5642ed1326 Added a ruleset toggle for ARE. 2021-01-09 23:17:24 -05:00
Ishaan Bhardwaj
c0888c484f Fixed the first easter egg 2021-01-08 20:33:44 -05:00
Ishaan Bhardwaj
3ef3b193fd 3-tall pentoes spawn highest on 21 now 2021-01-08 17:16:15 -05:00
Ishaan Bhardwaj
0c2e3efd1a PAIRS anti-stall added 2021-01-08 16:46:19 -05:00
Ishaan Bhardwaj
5076adf022 Secret inputs fix 2021-01-08 13:59:42 -05:00
Ishaan Bhardwaj
1a75d983dc Corrected PAIRS big spawns 2021-01-07 20:53:36 -05:00
Ishaan Bhardwaj
5b8e9586bd Sakura bugfixes 2021-01-07 19:59:11 -05:00
Ishaan Bhardwaj
7d7dd8c3c2 Roll roll bugfixes 2021-01-07 19:52:36 -05:00
Ishaan Bhardwaj
29afdcecfc PAIRS I5 and U spawns fixed 2021-01-07 19:06:37 -05:00
Ishaan Bhardwaj
8b09833ae6 PAIRS added, with bugfixes 2021-01-07 18:42:49 -05:00
Ishaan Bhardwaj
64047eaf9c Slight randomizer logic change, PAIRS incoming 2021-01-07 16:53:46 -05:00
Ishaan Bhardwaj
125488b4d9 Can no longer buffer a hard drop when not allowed 2021-01-06 23:06:51 -05:00
Ishaan Bhardwaj
1fdd091456 Ruleset and randomizer refactoring (Read comments)
You can now specify an arbitrary number of pieces for a ruleset.
The randomizers will adjust accordingly.
Expect a pento ruleset in the modpack soon!
Also, gamemode skin selection has been refactored.
2021-01-06 22:53:44 -05:00
Ishaan Bhardwaj
ced40297cc Line clear anim part 3 2021-01-06 21:37:51 -05:00
Ishaan Bhardwaj
32f2a0b3e7 Line clear anim part 2 2021-01-06 18:01:56 -05:00
Ishaan Bhardwaj
dd5347ad8d (Beta) line clear animation 2021-01-06 16:56:44 -05:00
Ishaan Bhardwaj
b732ebb213 Credits scene no longer plays while not focused 2021-01-06 16:10:01 -05:00
Ishaan Bhardwaj
84634d6933 Added an option to control buffer locking.
You can now choose if you want a drop input
during ARE to lock the piece on the first frame it is active.
2021-01-06 16:06:17 -05:00
Ishaan Bhardwaj
0d13a9f236 Can send inputs from mode select to game
Warning: this may break some things
2021-01-05 21:59:50 -05:00
Ishaan Bhardwaj
45120bc9f7 Update README MacOS instructions 2021-01-05 08:59:22 -05:00
Ishaan Bhardwaj
57c7d9c4c3 v0.3-beta1: Sakura done 2021-01-04 18:01:29 -05:00
Ishaan Bhardwaj
9f52d8bf10 relocated non bgm to modpack 2021-01-04 14:43:39 -05:00
Ishaan Bhardwaj
57bd6a8286 Almost done with Sakura 2021-01-03 23:18:57 -05:00
Oshisaure
1a68cd8fce More work on sakura, Xray and colour block effects implemented. Added Grid:drawCustom() to handle custom opacity/tint effects. 2021-01-03 00:05:54 +00:00
Ishaan Bhardwaj
56baf46839 Sakura *should* be complete now, please bug-report 2021-01-02 14:31:53 -05:00
Ishaan Bhardwaj
305d07e10a Sakura beta v2.k 2021-01-02 14:10:01 -05:00
Ishaan Bhardwaj
8d954cabc2 Sakura Beta v2.j 2021-01-02 12:56:52 -05:00
Ishaan Bhardwaj
0281220ea0 Sakura Beta v2.i 2021-01-02 12:51:00 -05:00
Ishaan Bhardwaj
aef5d88d3f Sakura Beta v2.h 2021-01-02 12:36:26 -05:00
Ishaan Bhardwaj
3676f7697c Sakura Beta v2 2021-01-02 12:21:10 -05:00
Ishaan Bhardwaj
acb0eb1a71 Sakura mode beta 2020-12-30 15:19:53 -05:00
Ishaan Bhardwaj
a89bf05cab Fixed an issue with controllers on the menu 2020-12-29 22:55:51 -05:00
Ishaan Bhardwaj
8008315994 Condensed ARE canceling code a bit 2020-12-29 14:00:11 -05:00
Ishaan Bhardwaj
90f62cb7dd Refactored ARE cancel 2020-12-28 23:32:41 -05:00
Oshisaure
eaee5fc7f0 Tweaked rotation/manipulation behaviour on SRS rules.
Also changed order of operations to call onPieceRotate in Rulesets after actually rotating the piece.
2020-12-28 03:41:26 +00:00
Ishaan Bhardwaj
e3b038b5a7 A festive easter egg has arrived! (v0.2.6.1)
Good luck hunting for the egg!
2020-12-24 22:58:06 -05:00
Ishaan Bhardwaj
083693496e Grid piece placement conditions 2020-12-22 22:04:06 -05:00
Ishaan Bhardwaj
ba576dfc77 Allow sliders to be controlled with keyboard
Credits to Phoenix Flare
2020-12-22 14:43:59 -05:00
Ishaan Bhardwaj
e195ccd721 Marathon A3 fixes 2020-12-21 23:32:39 -05:00
Ishaan Bhardwaj
70f703eb2f Fixed piece fade out when paused 2020-12-21 16:20:25 -05:00
Ishaan Bhardwaj
dc4d4a8259 Credits now stops music when you exit the screen 2020-12-21 16:00:03 -05:00
Ishaan Bhardwaj
565510c7b2 Credits and credit roll music updated 2020-12-21 15:48:34 -05:00
Ishaan Bhardwaj
c26a3f37de Update README.md 2020-12-20 20:35:36 -05:00
Ishaan Bhardwaj
0c1ce2f717 Fix package script not packing slider lib 2020-12-20 20:06:16 -05:00
Ishaan Bhardwaj
f14ab2a328 BGM focus fix 2020-12-20 16:55:34 -05:00
Ishaan Bhardwaj
042dbd220b text was slightly off-center 2020-12-20 15:31:42 -05:00
Ishaan Bhardwaj
548612123a SFX and BGM are now separate sliders 2020-12-20 15:26:32 -05:00
Ishaan Bhardwaj
f4675da0b0 Unlock and fix BGM, add pause button 2020-12-20 15:08:53 -05:00
Ishaan Bhardwaj
511e9592bc Fixed next piece sounds not playing 2020-12-20 10:47:24 -05:00
Ishaan Bhardwaj
5f3990ff58 Small credits update 2020-12-20 10:35:05 -05:00
Ishaan Bhardwaj
50ff4adf27 Credits scene <3 2020-12-20 10:28:34 -05:00
Ishaan Bhardwaj
87b88f4b42 Refactored settings menus 2020-12-20 09:45:49 -05:00
Ishaan Bhardwaj
130c2ea403 Easier easter egg #2 2020-12-19 20:44:24 -05:00
Ishaan Bhardwaj
1ea304916e Made it easier to see the egg 2020-12-19 20:43:57 -05:00
Ishaan Bhardwaj
e26b094830 A little easter egg... 2020-12-19 20:31:14 -05:00
Ishaan Bhardwaj
bcb44725bf Cambridge RS fix lock in midair 2020-12-19 14:04:08 -05:00
Ishaan Bhardwaj
2990844c52 Adjusted tuning scene 2020-12-18 23:17:53 -05:00
Ishaan Bhardwaj
c343014d6f Tuning scene 2020-12-18 21:28:30 -05:00
Ishaan Bhardwaj
605add7e94 Added customizable DAS and ARR! (read comments)
This only applies to modes that allow it.
This feature does not apply to main modes (yet)
2020-12-18 21:25:09 -05:00
Ishaan Bhardwaj
d3b647ca71 Fixed certain rulesets locking in midair 2020-12-18 21:24:10 -05:00
Ishaan Bhardwaj
1101aa467d Smooth piece drop 2020-12-17 18:00:07 -05:00
Ishaan Bhardwaj
ce27a7ed18 Update Tetra Online README 2020-12-17 10:51:08 -05:00
Ishaan Bhardwaj
f31beffab8 Update release script 2020-12-16 22:47:56 -05:00
Ishaan Bhardwaj
2ff8fb5edc Modpack README update 2020-12-16 22:33:48 -05:00
Boshi
1bf8f91ef2 Displays current gamemode in game (toggle) 2020-12-16 22:21:26 -05:00
Ishaan Bhardwaj
ba5f78d5f1 Merge branch 'master' of https://github.com/sashlilac/cambridge 2020-12-14 22:44:16 -05:00
Ishaan Bhardwaj
f7c4908062 Added an option to disable diagonal input 2020-12-14 22:43:50 -05:00
Ishaan Bhardwaj
3aa5bae7be Tetra Online notice and stuff 2020-12-10 21:22:16 -05:00
Ishaan Bhardwaj
40a2e78280 Marathon 2020 section colour function fixed 2020-12-06 11:38:45 -05:00
Ishaan Bhardwaj
696da3fa3f Marathon 2020 colour function removed 2020-12-06 11:27:44 -05:00
Ishaan Bhardwaj
4afe9f2bd4 Major sound effect update (closes #7?)
Sound effects can still be changed, and #7 can still be reopened.
2020-12-05 20:30:59 -05:00
Ishaan Bhardwaj
1f686fb5d4 Ti-ARS: T can no longer floorkick the air 2020-12-05 18:32:06 -05:00
Ishaan Bhardwaj
f4779c9847 Added the ability to toggle next piece SFX 2020-12-05 17:32:15 -05:00
Ishaan Bhardwaj
06cbec4bc8 Guideline SRS kicks fixed 2020-12-05 17:15:28 -05:00
Ishaan Bhardwaj
668564ffb0 Revert "big a3! (but its buggy??)"
This reverts commit 513cd6ba90.
Hailey, please do not add modes.
2020-12-05 16:56:12 -05:00
Hailey
e6edeea3d1 Merge branch 'master' of https://github.com/SashLilac/cambridge 2020-12-05 12:50:45 +10:00
Hailey
513cd6ba90 big a3! (but its buggy??) 2020-12-05 12:49:57 +10:00
Ishaan Bhardwaj
1beef8f157 Guideline SRS has 180s now 2020-12-04 21:36:25 -05:00
Ishaan Bhardwaj
d3b2b4c2d9 Ruleset refactoring! 2020-12-04 20:36:11 -05:00
Ishaan Bhardwaj
2b8b9d5084 Reduced the sound volume a little bit. 2020-12-04 20:12:36 -05:00
Ishaan Bhardwaj
2728780c45 Raised the piece sound volume by a factor of 10. 2020-12-04 19:42:33 -05:00
Ishaan Bhardwaj
ca592a3bcf Changed piece sounds, added sound sources 2020-12-04 19:27:02 -05:00
Ishaan Bhardwaj
b6f4158d70 Fixed SRS infinity bug! 2020-12-04 16:51:53 -05:00
Ishaan Bhardwaj
e43f5c470a Renaming backgrounds 2020-12-04 16:32:29 -05:00
Ishaan Bhardwaj
7bcdc517c0 Revert "Merge pull request #11 from Rexxt/master"
Reverting this pull request for a few reasons:
The piece sounds were too quiet.
The piece landing sound was distasteful.
2020-12-04 16:22:22 -05:00
Ishaan Bhardwaj
1d30987f9a Merge pull request #11 from Rexxt/master
Uncopyrightening and making the game slightly friendlier to mod
2020-12-04 16:13:38 -05:00
Ishaan Bhardwaj
1dd46a11ef Fixed torikans giving you a green line 2020-12-04 15:41:44 -05:00
Ishaan Bhardwaj
935c7aa14c Default secret grade names 2020-12-04 15:16:13 -05:00
Ishaan Bhardwaj
aea115d953 ACE-SRS has correct number of resets 2020-12-04 11:22:32 -05:00
Ishaan Bhardwaj
3d5b33f41a Added ability to enable/disable synchroes
On by default in anything but world rulesets.
Gamemodes / rulesets can override this setting.
2020-12-04 10:57:43 -05:00
Mizu
29f07bb6ab Update piece sounds 2020-12-04 15:38:34 +01:00
Ishaan Bhardwaj
891f96e814 I broke the DAS switch functionality 2020-12-03 14:10:46 -05:00
Ishaan Bhardwaj
36837a3af5 Update main.lua 2020-12-03 13:45:23 -05:00
Mizu
6d326a142c Update some sounds (not the pieces 2020-11-11 18:38:45 +01:00
Mizu
b6f1072587 Merge branch 'master' of https://github.com/Rexxt/cambridge 2020-11-11 17:43:53 +01:00
Mizu
eef04ebf05 Update graphic names and SOURCES.md 2020-11-11 17:42:48 +01:00
Mizu
e24737a3b8 Merge pull request #1 from SashLilac/master
Update fork
2020-11-11 17:17:22 +01:00
117 changed files with 6380 additions and 1606 deletions

View File

@@ -1,13 +1,15 @@
![Cambridge Banner](https://cdn.discordapp.com/attachments/764432435802013709/767724895076614154/cambridge_logo_lt.png) ![Cambridge Banner](https://t-sp.in/public/img/cambridge.png)
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 [SashLilac](https://github.com/SashLilac), [joezeng](https://github.com/joezeng) and [Oshisaure](https://github.com/oshisaure)!
Join our Discord server for help and a welcoming community! https://discord.gg/mteMJw4 The Discord server has been reopened! https://discord.gg/AADZUmgsph
The game also has a website now with more detail than seen on this README: https://t-sp.in/cambridge
Credits Credits
------- -------
@@ -17,19 +19,7 @@ Credits
- [The Tetra Legends Discord](http://discord.com/invite/7hMx5r2) for supporting me and playtesting! - [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 Absolute Plus](https://discord.gg/6Gf2awJ) for being another source of motivation!
The following people in no particular order also helped with the project: More special thanks can be found in-game, under the "Credits" menu.
- [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)
![Cambridge Logo](https://cdn.discordapp.com/attachments/625496179433668635/763363717730664458/Icon_2.png) ![Cambridge Logo](https://cdn.discordapp.com/attachments/625496179433668635/763363717730664458/Icon_2.png)
@@ -40,9 +30,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, or want mod pack support, 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/sashlilac/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/SashLilac/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:
@@ -60,6 +56,14 @@ 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/SashLilac/cambridge
@@ -74,9 +78,7 @@ It should run automatically!
## Installing modpacks ## Installing modpacks
Simply drag your mode, ruleset, and randomizer Lua files into their respective directory, and they should appear automatically. For instructions on how to install modpacks, go to [this](https://github.com/SashLilac/cambridge-modpack) mod pack to get a taste of the mod potential.
Alternatively, install [this](https://github.com/SashLilac/cambridge-modpack) mod pack to get a taste of the mod potential.
License License
------- -------

View File

@@ -8,7 +8,7 @@ Some of the assets are used without proper licenses. We aim to have fully licens
Backgrounds Backgrounds
----------- -----------
1. Title: "Motus Glacies." Contributed by Daniel "Explo" McCarthy. 1. Title: Original picrute found on the Wikipedia article for Cambridge
1. *Gameplay level 0: "Quantum foam." Alex Sukontsev. https://www.flickr.com/photos/control9/14957509814/ 1. *Gameplay level 0: "Quantum foam." Alex Sukontsev. https://www.flickr.com/photos/control9/14957509814/
2. *Gameplay level 1: No name. http://www.onekind.tv/univision-mqb/q5mqh5brlvuuj2nhdx7ch7eum183uu 2. *Gameplay level 1: No name. http://www.onekind.tv/univision-mqb/q5mqh5brlvuuj2nhdx7ch7eum183uu
@@ -34,10 +34,18 @@ Backgrounds
Backgrounds marked with a * are placeholders that will be replaced in later versions due to incompatible licenses. We are generally aiming for public domain background images, but will also accept backgrounds given proper licenses to be included within Cambridge. Backgrounds marked with a * are placeholders that will be replaced in later versions due to incompatible licenses. We are generally aiming for public domain background images, but will also accept backgrounds given proper licenses to be included within Cambridge.
Sounds
------
All piece sounds are (c) 2020 Damian Yerrick.
Other sounds from:
- NullpoMino
- DTET, (c) 2003 Mihys.
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.
@@ -106,3 +114,59 @@ 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.

View File

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

View File

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

View File

@@ -56,9 +56,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
@@ -66,4 +76,20 @@ end
function Mod1(n, m) 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
View 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 = false
--------------------------------------------------------------------------------
local bigint = {}
local mt = {
__add = function(lhs, rhs)
return bigint.add(lhs, rhs)
end,
__unm = function()
return bigint.negate(self)
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()
return bigint.unserialize(self, "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 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
-- 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
----------------------------------------------------------------------------
-- Strip leading zeroes if any, but not if 0 is the only digit
while (#result.digits > 1) and (result.digits[1] == 0) do
table.remove(result.digits, 1)
end
return 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
-- Remove leading zeros from result
while (result.digits[1] == 0) do
table.remove(result.digits, 1)
end
return 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

File diff suppressed because it is too large Load Diff

138
libs/simple-slider.lua Normal file
View 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

View File

@@ -6,7 +6,7 @@ bgm = {
} }
local current_bgm = nil local current_bgm = nil
local bgm_locked = true local bgm_locked = false
function switchBGM(sound, subsound) function switchBGM(sound, subsound)
if bgm_locked then return end if bgm_locked then return end
@@ -47,7 +47,7 @@ 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() current_bgm:play()
end end
@@ -59,7 +59,7 @@ function processBGMFadeout(dt)
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

2
load/bigint.lua Normal file
View File

@@ -0,0 +1,2 @@
bigint = require "libs.bigint.bigint"
number_names = require "libs.bigint.named-powers-of-ten"

View File

@@ -1,27 +1,28 @@
backgrounds = { backgrounds = {
[0] = love.graphics.newImage("res/backgrounds/0-quantum-foam.png"), [0] = love.graphics.newImage("res/backgrounds/0.png"),
love.graphics.newImage("res/backgrounds/100-big-bang.png"), love.graphics.newImage("res/backgrounds/100.png"),
love.graphics.newImage("res/backgrounds/200-spiral-galaxy.png"), love.graphics.newImage("res/backgrounds/200.png"),
love.graphics.newImage("res/backgrounds/300-sun-and-dust.png"), love.graphics.newImage("res/backgrounds/300.png"),
love.graphics.newImage("res/backgrounds/400-earth-and-moon.png"), love.graphics.newImage("res/backgrounds/400.png"),
love.graphics.newImage("res/backgrounds/500-cambrian-explosion.png"), love.graphics.newImage("res/backgrounds/500.png"),
love.graphics.newImage("res/backgrounds/600-dinosaurs.png"), love.graphics.newImage("res/backgrounds/600.png"),
love.graphics.newImage("res/backgrounds/700-asteroid.png"), love.graphics.newImage("res/backgrounds/700.png"),
love.graphics.newImage("res/backgrounds/800-human-fire.png"), love.graphics.newImage("res/backgrounds/800.png"),
love.graphics.newImage("res/backgrounds/900-early-civilization.png"), love.graphics.newImage("res/backgrounds/900.png"),
love.graphics.newImage("res/backgrounds/1000-vikings.png"), love.graphics.newImage("res/backgrounds/1000.png"),
love.graphics.newImage("res/backgrounds/1100-crusades.png"), love.graphics.newImage("res/backgrounds/1100.png"),
love.graphics.newImage("res/backgrounds/1200-genghis-khan.png"), love.graphics.newImage("res/backgrounds/1200.png"),
love.graphics.newImage("res/backgrounds/1300-black-death.png"), love.graphics.newImage("res/backgrounds/1300.png"),
love.graphics.newImage("res/backgrounds/1400-columbus-discovery.png"), love.graphics.newImage("res/backgrounds/1400.png"),
love.graphics.newImage("res/backgrounds/1500-aztecas.png"), love.graphics.newImage("res/backgrounds/1500.png"),
love.graphics.newImage("res/backgrounds/1600-telescope.png"), love.graphics.newImage("res/backgrounds/1600.png"),
love.graphics.newImage("res/backgrounds/1700-american-revolution.png"), love.graphics.newImage("res/backgrounds/1700.png"),
love.graphics.newImage("res/backgrounds/1800-railways.png"), love.graphics.newImage("res/backgrounds/1800.png"),
love.graphics.newImage("res/backgrounds/1900-world-wide-web.png"), love.graphics.newImage("res/backgrounds/1900.png"),
title = love.graphics.newImage("res/backgrounds/title_v0.1.png"), title = love.graphics.newImage("res/backgrounds/title.png"),
input_config = love.graphics.newImage("res/backgrounds/options-pcb.png"), snow = love.graphics.newImage("res/backgrounds/snow.png"),
game_config = love.graphics.newImage("res/backgrounds/options-gears.png"), input_config = love.graphics.newImage("res/backgrounds/options-input.png"),
game_config = love.graphics.newImage("res/backgrounds/options-game.png"),
} }
blocks = { blocks = {
@@ -48,6 +49,23 @@ blocks = {
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"),
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"] = {
F = love.graphics.newImage("res/img/squares.png"),
Y = love.graphics.newImage("res/img/squareg.png"),
X = love.graphics.newImage("res/img/squares.png"),
} }
} }
@@ -84,4 +102,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")
} }

View File

@@ -1,8 +1,17 @@
local binser = require 'libs.binser' local binser = require 'libs.binser'
function loadSave() function loadSave()
config = loadFromFile('config.sav') local info = love.filesystem.getInfo(love.filesystem.getSaveDirectory())
highscores = loadFromFile('highscores.sav') if not info or info.type ~= "directory" then
love.filesystem.remove(love.filesystem.getSaveDirectory())
love.filesystem.createDirectory(love.filesystem.getSaveDirectory())
end
config = loadFromFile(
love.filesystem.getSaveDirectory() .. '/config.sav'
)
highscores = loadFromFile(
love.filesystem.getSaveDirectory() .. '/highscores.sav'
)
end end
function loadFromFile(filename) function loadFromFile(filename)
@@ -13,12 +22,40 @@ function loadFromFile(filename)
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) binser.writeFile(
love.filesystem.getSaveDirectory() .. '/config.sav', config
)
end end
function saveHighscores() function saveHighscores()
binser.writeFile('highscores.sav', highscores) binser.writeFile(
love.filesystem.getSaveDirectory() .. '/highscores.sav', highscores
)
end end

View File

@@ -20,17 +20,21 @@ sounds = {
fall = love.audio.newSource("res/se/fall.wav", "static"), fall = love.audio.newSource("res/se/fall.wav", "static"),
ready = love.audio.newSource("res/se/ready.wav", "static"), ready = love.audio.newSource("res/se/ready.wav", "static"),
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"),
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 subsound == nil then
sounds[sound]:setVolume(0.5) sounds[sound]:setVolume(config.sfx_volume)
if sounds[sound]:isPlaying() then if sounds[sound]:isPlaying() then
sounds[sound]:stop() sounds[sound]:stop()
end end
sounds[sound]:play() sounds[sound]:play()
else else
sounds[sound][subsound]:setVolume(0.1) sounds[sound][subsound]:setVolume(config.sfx_volume)
if sounds[sound][subsound]:isPlaying() then if sounds[sound][subsound]:isPlaying() then
sounds[sound][subsound]:stop() sounds[sound][subsound]:stop()
end end
@@ -40,13 +44,13 @@ end
function playSEOnce(sound, subsound) function playSEOnce(sound, subsound)
if subsound == nil then if subsound == nil then
sounds[sound]:setVolume(0.5) sounds[sound]:setVolume(config.sfx_volume)
if sounds[sound]:isPlaying() then if sounds[sound]:isPlaying() then
return return
end end
sounds[sound]:play() sounds[sound]:play()
else else
sounds[sound][subsound]:setVolume(0.5) sounds[sound][subsound]:setVolume(config.sfx_volume)
if sounds[sound][subsound]:isPlaying() then if sounds[sound][subsound]:isPlaying() then
return return
end end

1
load/version.lua Normal file
View File

@@ -0,0 +1 @@
version = "v0.3-beta5.2"

124
main.lua
View File

@@ -7,30 +7,32 @@ 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 "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"] = config.gamesettings.das_last_key == 2 --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});
-- used for screenshots
GLOBAL_CANVAS = love.graphics.newCanvas()
if not config.gamesettings then config.gamesettings = {} end -- init config
for _, option in ipairs(GameConfigScene.options) do initConfig()
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
love.window.setFullscreen(config["fullscreen"])
if config.secret then playSE("welcome") end
-- import custom modules
initModules()
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
@@ -51,7 +53,6 @@ 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 end
local TARGET_FPS = 60 local TARGET_FPS = 60
@@ -93,7 +94,10 @@ function love.update(dt)
end end
function love.draw() function love.draw()
love.graphics.push() love.graphics.setCanvas(GLOBAL_CANVAS)
love.graphics.clear()
love.graphics.push()
-- get offset matrix -- get offset matrix
love.graphics.setDefaultFilter("linear", "nearest") love.graphics.setDefaultFilter("linear", "nearest")
@@ -105,18 +109,41 @@ function love.draw()
(height - scale_factor * 480) / 2 (height - scale_factor * 480) / 2
) )
love.graphics.scale(scale_factor) love.graphics.scale(scale_factor)
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 == "f4" 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")
if not info or info.type ~= "directory" 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
@@ -187,13 +214,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})
@@ -201,6 +228,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
@@ -217,24 +252,59 @@ 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.focus(f) function love.focus(f)
if f then if f and (scene.title ~= "Game" or not scene.paused) then
resumeBGM() resumeBGM()
else else
pauseBGM() pauseBGM()
end end
end end
function love.resize(w, h)
GLOBAL_CANVAS:release()
GLOBAL_CANVAS = love.graphics.newCanvas(w, h)
end

View File

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

View File

@@ -4,6 +4,6 @@ mkdir dist/windows
mkdir dist/win32 mkdir dist/win32
cp cambridge.love dist/ cp cambridge.love dist/
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 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 zip dist/cambridge-win32.zip dist/win32/* SOURCES.md LICENSE.md

View File

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

View File

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

Before

Width:  |  Height:  |  Size: 2.9 MiB

After

Width:  |  Height:  |  Size: 2.9 MiB

View File

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 2.6 MiB

View File

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

View File

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

Before

Width:  |  Height:  |  Size: 3.1 MiB

After

Width:  |  Height:  |  Size: 3.1 MiB

View File

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 2.7 MiB

View File

Before

Width:  |  Height:  |  Size: 3.6 MiB

After

Width:  |  Height:  |  Size: 3.6 MiB

View File

Before

Width:  |  Height:  |  Size: 2.9 MiB

After

Width:  |  Height:  |  Size: 2.9 MiB

View File

Before

Width:  |  Height:  |  Size: 3.0 MiB

After

Width:  |  Height:  |  Size: 3.0 MiB

BIN
res/backgrounds/snow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 B

After

Width:  |  Height:  |  Size: 153 B

BIN
res/img/cambridge_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
res/img/gem1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

BIN
res/img/gem2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

BIN
res/img/gem3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

BIN
res/img/gem4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

BIN
res/img/gem5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

BIN
res/img/gem6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

BIN
res/img/gem7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

BIN
res/img/gem9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

BIN
res/img/santa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
res/img/squareg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 B

BIN
res/img/squares.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

Binary file not shown.

BIN
res/se/ihs.wav Normal file

Binary file not shown.

BIN
res/se/irs.wav Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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"

73
scene/credits.lua Normal file
View File

@@ -0,0 +1,73 @@
local CreditsScene = Scene:extend()
CreditsScene.title = "Credits"
function CreditsScene:new()
self.frames = 0
switchBGM("credit_roll", "gm3")
end
function CreditsScene:update()
if love.window.hasFocus() then
self.frames = self.frames + 1
end
if self.frames >= 4200 then
playSE("mode_decide")
scene = TitleScene()
switchBGM(nil)
elseif self.frames == 3600 then
fadeoutBGM(2)
end
end
function CreditsScene:render()
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 - self.frames / 2)
love.graphics.print("THANK YOU\nFOR PLAYING!", 320, math.max(1770 - self.frames / 2, 240))
love.graphics.setFont(font_3x5_3)
love.graphics.print("Game Developers", 320, 550 - self.frames / 2)
love.graphics.print("Project Heads", 320, 640 - self.frames / 2)
love.graphics.print("Other Game Developers", 320, 730 - self.frames / 2)
love.graphics.print("Special Thanks", 320, 950 - self.frames / 2)
love.graphics.print("- SashLilac / TS3 / Milla", 320, math.max(1850 - self.frames / 2, 320))
love.graphics.setFont(font_3x5_2)
love.graphics.print("Oshisaure\nJoe Zeng", 320, 590 - self.frames / 2)
love.graphics.print("Mizu\nHailey", 320, 680 - self.frames / 2)
love.graphics.print(
"Axel Fox - Multimino\nMine - Tetra Online\nDr Ocelot - Tetra Legends\n" ..
"Felicity / nightmareci - Shiromino\n2Tie - TGMsim\nPhoenix Flare - Master of Blocks\n" ..
"RayRay26 - Spirit Drop\nosk - TETR.IO\nMarkGamed7794 - Picoris 2",
320, 770 - self.frames / 2
)
love.graphics.print(
"RocketLanterns\nCylinderKnot\nHammrTime\nKirby703\nMattMayuga\nMyPasswordIsWeak\n" ..
"Nikki Karissa\noffwo\nsinefuse\nTetro48\nTimmSkiller\nuser74003\nAgentBasey\n" ..
"CheeZed_Fish\neightsixfivezero\nEricICX\ngizmo4487\nM1ssing0\n" ..
"pokemonfan1937\nSimon\nstratus\nZaptorZap\nArchina\nOliver\ncolour_thief\n" ..
"Caithness\nkdex\nzid\nsaphie\nSuper302\nAurora\nswitchpalacecorner\nKitaru\n" ..
"JBroms\nMany more I definitely missed!\n" ..
"The Absolute PLUS Discord\nTetra Legends Discord\nTetra Online Discord\n" ..
"Multimino Discord\nHard Drop Discord\nCambridge Discord\n" ..
"And to you, the player!",
320, 990 - self.frames / 2
)
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

View File

@@ -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,15 +31,14 @@ 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)
self.game.grid:update()
end end
self.game.grid:update()
end end
function GameScene:render() function GameScene:render()
@@ -49,11 +50,40 @@ function GameScene:render()
) )
-- game frame -- game frame
love.graphics.draw(misc_graphics["frame"], 48, 64) if self.game.grid.width == 10 and self.game.grid.height == 24 then
love.graphics.draw(misc_graphics["frame"], 48, 64)
end
love.graphics.setColor(0, 0, 0, 200) love.graphics.setColor(0, 0, 0, 200)
love.graphics.rectangle("fill", 64, 80, 160, 320) love.graphics.rectangle(
"fill", 64, 80,
16 * self.game.grid.width, 16 * (self.game.grid.height - 4)
)
if self.game.grid.width ~= 10 or self.game.grid.height ~= 24 then
love.graphics.setColor(174/255, 83/255, 76/255, 1)
love.graphics.setLineWidth(8)
love.graphics.line(
60,76,
68+16*self.game.grid.width,76,
68+16*self.game.grid.width,84+16*(self.game.grid.height-4),
60,84+16*(self.game.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.game.grid.width,76,
68+16*self.game.grid.width,84+16*(self.game.grid.height-4),
60,84+16*(self.game.grid.height-4),
60,76
)
love.graphics.setLineWidth(1)
end
self.game:drawGrid() self.game:drawGrid()
if self.game.lcd > 0 then self.game:drawLineClearAnimation() end
self.game:drawPiece() self.game:drawPiece()
self.game:drawNextQueue(self.ruleset) self.game:drawNextQueue(self.ruleset)
self.game:drawScoringInfo() self.game:drawScoringInfo()
@@ -68,17 +98,38 @@ function GameScene:render()
self.game:drawCustom() 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
love.graphics.setFont(font_3x5_3)
if self.paused then love.graphics.print("PAUSED!", 80, 100) end
if self.game.completed then
self.game:onGameComplete()
elseif self.game.game_over then
self.game:onGameOver()
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

View File

@@ -3,13 +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"}},
{"das_last_key", "DAS Switch", {"Default", "Instant"}} {"world_reverse", "A Button Rotation", false, {"Left", "Auto", "Right"}},
{"spawn_positions", "Spawn Positions", false, {"Per ruleset", "In field", "Out of field"}},
{"display_gamemode", "Display Gamemode", false, {"On", "Off"}},
{"das_last_key", "DAS Last Key", false, {"Off", "On"}},
{"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 Lock Inputs", false, {"On", "Off"}},
{"sfx_volume", "SFX", true, "sfxSlider"},
{"bgm_volume", "BGM", true, "bgmSlider"},
} }
local optioncount = #ConfigScene.options local optioncount = #ConfigScene.options
@@ -22,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()
@@ -35,29 +49,45 @@ function ConfigScene:render()
0, 0, 0, 0, 0, 0,
0.5, 0.5 0.5, 0.5
) )
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
love.graphics.setColor(1, 1, 1, 1) if not option[3] then
love.graphics.printf(option[2], 40, 100 + i * 20, 150, "left") love.graphics.setColor(1, 1, 1, 1)
for j, setting in ipairs(option[3]) do love.graphics.printf(option[2], 40, 100 + i * 20, 150, "left")
love.graphics.setColor(1, 1, 1, config.gamesettings[option[1]] == j and 1 or 0.5) for j, setting in ipairs(option[4]) do
love.graphics.printf(setting, 100 + 110 * j, 100 + i * 20, 100, "center") love.graphics.setColor(1, 1, 1, config.gamesettings[option[1]] == j and 1 or 0.5)
love.graphics.printf(setting, 100 + 110 * j, 100 + i * 20, 100, "center")
end
end end
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 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)
@@ -65,16 +95,28 @@ 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
playSE("cursor_lr") if not self.options[self.highlight][3] then
local option = ConfigScene.options[self.highlight] playSE("cursor_lr")
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]-1, #option[3]) local option = ConfigScene.options[self.highlight]
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]-1, #option[4])
else
playSE("cursor")
sld = self[self.options[self.highlight][4]]
sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() - 5) / (sld.max - sld.min)))
end
elseif e.input == "right" or e.scancode == "right" then elseif e.input == "right" or e.scancode == "right" then
playSE("cursor_lr") if not self.options[self.highlight][3] then
local option = ConfigScene.options[self.highlight] playSE("cursor_lr")
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]+1, #option[3]) local option = ConfigScene.options[self.highlight]
config.gamesettings[option[1]] = Mod1(config.gamesettings[option[1]]+1, #option[4])
else
playSE("cursor")
sld = self[self.options[self.highlight][4]]
sld.value = math.max(sld.min, math.min(sld.max, (sld:getValue() + 5) / (sld.max - sld.min)))--math.max(0, (math.floor(sld:getValue())+2)/(sld.max-sld.min))
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

View File

@@ -2,161 +2,65 @@ 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() DiscordRPC:update({
self.new_input = {} details = "In menus",
self.axis_timer = 0 state = "Changing input config",
})
DiscordRPC:update({
details = "In menus",
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)
love.graphics.draw( love.graphics.draw(
backgrounds["input_config"], backgrounds["input_config"],
0, 0, 0, 0, 0, 0,
0.5, 0.5 0.5, 0.5
) )
love.graphics.setFont(font_3x5_2) love.graphics.setFont(font_3x5_4)
for i, input in ipairs(configurable_inputs) do love.graphics.print("INPUT CONFIG", 80, 40)
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 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 love.graphics.setFont(font_3x5_2)
love.graphics.print("Which controls do you want to configure?", 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 end
local function addJoystick(input, name) function ConfigScene:changeOption(rel)
if not input.joysticks then local len = table.getn(menu_screens)
input.joysticks = {} self.menu_state = (self.menu_state + len + rel - 1) % len + 1
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
return ConfigScene return ConfigScene

100
scene/key_config.lua Normal file
View 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" 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

View File

@@ -11,6 +11,14 @@ function ModeSelectScene:new()
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,
}
DiscordRPC:update({ DiscordRPC:update({
details = "In menus", details = "In menus",
state = "Choosing a mode", state = "Choosing a mode",
@@ -18,6 +26,7 @@ function ModeSelectScene:new()
end end
function ModeSelectScene:update() function ModeSelectScene:update()
switchBGM(nil) -- experimental
end end
function ModeSelectScene:render() function ModeSelectScene:render()
@@ -66,7 +75,7 @@ function ModeSelectScene:onInputPress(e)
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") playSE("cursor")
@@ -78,6 +87,14 @@ function ModeSelectScene:onInputPress(e)
playSE("cursor_lr") 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
end end
end end

65
scene/settings.lua Normal file
View 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

152
scene/stick_config.lua Normal file
View File

@@ -0,0 +1,152 @@
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
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
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
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

View File

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

View File

@@ -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
self.grid[24] = {empty, empty, empty, empty, empty, empty, empty, empty, empty, empty}
self.grid_age[24] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
for col = 1, 10 do
self.grid[24][col] = (row_vals[col] == "e") and empty or block
end end
end self.grid[self.height] = {}
self.grid_age[self.height] = {}
function Grid:applyFourWide() for i = 1, self.width do
for row = 1, 24 do self.grid[self.height][i] = (row_vals[i] == "e") and empty or block
local x = self.grid[row] self.grid_age[self.height][i] = 0
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 = "F"
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

View File

@@ -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,36 @@ function Piece:lockIfBottomed(grid)
return self return self
end end
function Piece:addGravity(gravity, grid) function Piece:addGravity(gravity, grid, classic_lock)
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 = math.min(1, new_gravity)
else else
local dropped_squares = math.floor(new_gravity) self.gravity = 0
local new_frac_gravity = new_gravity - dropped_squares self.lock_delay = self.lock_delay + 1
self.gravity = new_frac_gravity
self:dropSquares(dropped_squares, grid)
if self:isDropBlocked(grid) then
playSE("bottom")
end 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
self.gravity = new_frac_gravity
self:dropSquares(dropped_squares, grid)
if self:isDropBlocked(grid) then
playSE("bottom")
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 +159,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

View File

@@ -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 cleared_lines = cleared_lines / 2
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 if not self.clear then
cleared_lines = cleared_lines / 2
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
else self.lines = self.lines + cleared_lines end
end
function BigA2Game:onLineClear(cleared_row_count)
cleared_row_count = cleared_row_count / 2
self:updateSectionTimes(self.level, self.level + cleared_row_count)
self.level = math.min(self.level + cleared_row_count, 999)
if self.level == 999 and not self.clear then
self.clear = true
self.grid:clear()
if self:qualifiesForMRoll() then self.grade = 32 end
self.roll_frames = -150
end end
self.lock_drop = self.level >= 900
self.lock_hard_drop = self.level >= 900
end end
local grade_point_bonuses = { return BigA2Game
{10, 20, 40, 50},
{10, 20, 30, 40},
{10, 20, 30, 40},
{10, 15, 30, 40},
{10, 15, 20, 40},
{5, 15, 20, 30},
{5, 10, 20, 30},
{5, 10, 15, 30},
{5, 10, 15, 30},
{5, 10, 15, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
{2, 12, 13, 30},
}
local grade_point_decays = {
125, 80, 80, 50, 45, 45, 45,
40, 40, 40, 40, 40, 30, 30, 30,
20, 20, 20, 20, 20,
15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
10, 10
}
local combo_multipliers = {
{1.0, 1.0, 1.0, 1.0},
{1.2, 1.4, 1.5, 1.0},
{1.2, 1.5, 1.8, 1.0},
{1.4, 1.6, 2.0, 1.0},
{1.4, 1.7, 2.2, 1.0},
{1.4, 1.8, 2.3, 1.0},
{1.4, 1.9, 2.4, 1.0},
{1.5, 2.0, 2.5, 1.0},
{1.5, 2.1, 2.6, 1.0},
{2.0, 2.5, 3.0, 1.0},
}
local grade_conversion = {
[0] = 0,
1, 2, 3, 4, 5, 5, 6, 6, 7, 7,
7, 8, 8, 8, 9, 9, 9, 10, 11, 12,
12, 12, 13, 13, 14, 14, 15, 15, 16, 16,
17
}
function MarathonA2Game:updateGrade(cleared_lines)
if self.clear then return end
if cleared_lines == 0 then
self.grade_point_decay_counter = self.grade_point_decay_counter + 1
if self.grade_point_decay_counter >= grade_point_decays[self.grade + 1] then
self.grade_point_decay_counter = 0
self.grade_points = math.max(0, self.grade_points - 1)
end
else
self.grade_points = self.grade_points + (
math.ceil(
grade_point_bonuses[self.grade + 1][cleared_lines] *
combo_multipliers[math.min(self.combo, 10)][cleared_lines]
) * (1 + math.floor(self.level / 250))
)
if self.grade_points >= 100 and self.grade < 31 then
self.grade_points = 0
self.grade = self.grade + 1
end
end
end
function MarathonA2Game:getLetterGrade()
local grade = grade_conversion[self.grade]
if grade < 9 then
return tostring(9 - grade)
elseif grade < 18 then
return "S" .. tostring(grade - 8)
end
end
MarathonA2Game.rollOpacityFunction = function(age)
if age < 240 then return 1
elseif age > 300 then return 0
else return 1 - (age - 240) / 60 end
end
function MarathonA2Game:drawGrid(ruleset)
if self.clear and not (self.completed or self.game_over) then
self.grid:drawInvisible(self.rollOpacityFunction, nil, false)
else
self.grid:draw()
if self.piece ~= nil and self.level < 100 then
self:drawGhostPiece(ruleset)
end
end
end
function MarathonA2Game:drawScoringInfo()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setFont(font_3x5_2)
love.graphics.print(
self.das.direction .. " " ..
self.das.frames .. " " ..
strTrueValues(self.prev_inputs)
)
love.graphics.printf("NEXT", 64, 40, 40, "left")
love.graphics.printf("GRADE", 240, 120, 40, "left")
love.graphics.printf("SCORE", 240, 200, 40, "left")
love.graphics.printf("LEVEL", 240, 320, 40, "left")
love.graphics.setFont(font_3x5_3)
if self.roll_frames > 3694 then love.graphics.setColor(1, 0.5, 0, 1)
elseif self.clear then love.graphics.setColor(0, 1, 0, 1) end
love.graphics.printf(self:getLetterGrade(), 240, 140, 90, "left")
love.graphics.setColor(1, 1, 1, 1)
love.graphics.printf(self.score, 240, 220, 90, "left")
love.graphics.printf(self.level, 240, 340, 40, "right")
love.graphics.printf(self:getSectionEndLevel(), 240, 370, 40, "right")
love.graphics.setFont(font_8x11)
love.graphics.printf(formatTime(self.frames), 64, 420, 160, "center")
end
function MarathonA2Game:getHighscoreData()
return {
grade = grade_conversion[self.grade],
score = self.score,
level = self.level,
frames = self.frames,
}
end
function MarathonA2Game:getSectionEndLevel()
if self.level >= 900 then return 999
else return math.floor(self.level / 100 + 1) * 100 end
end
function MarathonA2Game:getBackground()
return math.floor(self.level / 100)
end
return MarathonA2Game

View File

@@ -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,17 +45,28 @@ 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 = {
"9", "8", "7", "6", "5", "4", "3", "2", "1",
"S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8", "S9",
"GM"
}
-- variables related to configurable parameters -- variables related to configurable parameters
self.drop_locked = false self.drop_locked = false
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
@@ -65,20 +81,34 @@ 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)
return { return {
skin = "2tie", skin = self:getSkin(),
shape = self.randomizer:nextPiece(), shape = self.used_randomizer:nextPiece(),
orientation = ruleset:getDefaultOrientation(), orientation = ruleset:getDefaultOrientation(),
} }
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]
@@ -86,28 +116,34 @@ 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 inputs["left"] or inputs["right"] then
inputs["up"] = false
inputs["down"] = false
elseif inputs["up"] or inputs["down"] then
inputs["left"] = false
inputs["right"] = false
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
@@ -120,39 +156,82 @@ function GameMode:update(inputs, ruleset)
if self.enable_hold and inputs["hold"] == true and self.held == false and self.prev_inputs["hold"] == false then if self.enable_hold and inputs["hold"] == true and self.held == false and self.prev_inputs["hold"] == false then
self:hold(inputs, ruleset) self:hold(inputs, ruleset)
self.prev_inputs = inputs self.prev_inputs = inputs
if not self.grid:canPlacePiece(self.piece) then
self.game_over = true
end
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(), 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
self:onSoftDrop(piece_dy) if not (
self.piece:isDropBlocked(self.grid) and
piece_drot ~= 0
) then
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
@@ -162,14 +241,29 @@ function GameMode:update(inputs, ruleset)
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
@@ -178,16 +272,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()
@@ -210,18 +308,20 @@ 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() function GameMode:onHold() end
playSE("hold")
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 + 1 * dropped_row_count
@@ -233,8 +333,19 @@ 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()
@@ -275,7 +386,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
@@ -298,12 +409,34 @@ 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 self.piece_hard_dropped and
not self.prev_inputs.up and
strTrueValues(inputs) ~= "" then
self.lcd = 0
self.are = 0
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
if not self.prev_inputs["up"] and inputs["up"] and self.enable_hard_drop then
self.buffer_hard_drop = true
end
if not self.prev_inputs["down"] and inputs["down"] then
self.buffer_soft_drop = true
end
if not playedReadySE then if not playedReadySE then
playedReadySE = true playedReadySE = true
playSEOnce("ready") playSEOnce("ready")
@@ -317,39 +450,32 @@ 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.lcd = self.lcd - 1 if not self.prev_inputs["up"] and inputs["up"] and self.enable_hard_drop then
if ruleset.are_cancel and self.buffer_hard_drop = true
(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 end
if not self.prev_inputs["down"] and inputs["down"] then
self.buffer_soft_drop = true
end
self.lcd = self.lcd - 1
self:areCancel(inputs, ruleset)
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.are = self.are - 1 if not self.prev_inputs["up"] and inputs["up"] and self.enable_hard_drop then
if ruleset.are_cancel and self.buffer_hard_drop = true
(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 end
if not self.prev_inputs["down"] and inputs["down"] then
self.buffer_soft_drop = true
end
self.are = self.are - 1
self:areCancel(inputs, ruleset)
if self.are == 0 then if self.are == 0 then
self:initializeOrHold(inputs, ruleset) self:initializeOrHold(inputs, ruleset)
end end
@@ -357,19 +483,20 @@ 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:hold(inputs, ruleset) (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)
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 if not self.grid:canPlacePiece(self.piece) then
self:onGameOver()
self.game_over = true self.game_over = true
end end
end end
function GameMode:hold(inputs, ruleset) function GameMode:hold(inputs, ruleset, ihs)
local data = copy(self.hold_queue) local data = copy(self.hold_queue)
if self.piece == nil then if self.piece == nil then
self.hold_queue = self.next_queue[1] self.hold_queue = self.next_queue[1]
@@ -388,33 +515,76 @@ function GameMode:hold(inputs, ruleset)
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()
end end
function GameMode:initializeNextPiece(inputs, ruleset, piece_data, generate_next_piece) function GameMode:initializeNextPiece(inputs, ruleset, piece_data, generate_next_piece)
self.piece_hard_dropped = false
local gravity = self:getGravity() local gravity = self:getGravity()
self.piece = ruleset:initializePiece( self.piece = ruleset:initializePiece(
inputs, piece_data, self.grid, gravity, inputs, piece_data, self.grid, gravity,
self.prev_inputs, self.move, self.prev_inputs, self.move,
self:getLockDelay(), self:getDropSpeed(), self:getLockDelay(), self:getDropSpeed(),
self.lock_drop, self.lock_hard_drop, self.big_mode, self.lock_drop, self.lock_hard_drop, self.big_mode,
self.irs (
self.frames == 0 or (ruleset.are and self:getARE() ~= 0)
) and self.irs or false
) )
if self.lock_drop then if self.buffer_hard_drop then
if config.gamesettings.buffer_lock == 1 then
self.piece:dropToBottom(self.grid)
if self.lock_on_hard_drop then self.piece.locked = true end
end
local above_field = (
(config.gamesettings.spawn_positions == 1 and
ruleset.spawn_above_field) or
config.gamesettings.spawn_positions == 3
)
self:onHardDrop(self.piece.position.y - (
self.piece.big and
ruleset.big_spawn_positions[self.piece.shape].y or
ruleset.spawn_positions[self.piece.shape].y) +
(above_field and ruleset:getAboveFieldOffset(
piece_data.shape, piece_data.orientation
) or 0)
)
self.buffer_hard_drop = false
end
if self.buffer_soft_drop then
if (
self.lock_on_soft_drop and
self.piece:isDropBlocked(self.grid) and
config.gamesettings.buffer_lock == 1
) then
self.piece.locked = true
end
self.buffer_soft_drop = false
end
if self.piece:isDropBlocked(self.grid) and
self.grid:canPlacePiece(self.piece) then
playSE("bottom")
end
if 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 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
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
self:playNextSound() 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()
@@ -423,14 +593,87 @@ 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: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
@@ -443,11 +686,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
@@ -463,8 +711,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,
@@ -475,15 +722,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()
@@ -519,7 +776,12 @@ function GameMode:drawSectionTimes(current_section)
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")
end end
function GameMode:drawSectionTimesWithSecondary(current_section, section_colour_function) function GameMode:sectionColourFunction(section)
return { 1, 1, 1, 1 }
end
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
@@ -530,12 +792,11 @@ function GameMode:drawSectionTimesWithSecondary(current_section, section_colour_
end end
for section, time in pairs(self.secondary_section_times) do for section, time in pairs(self.secondary_section_times) do
if self.section_colour_function then love.graphics.setColor(self:sectionColourFunction(section))
love.graphics.setColor(self:section_colour_function(section))
end
if section > 0 then if section > 0 then
love.graphics.printf(formatTime(time), section_secondary_x, 40 + 20 * section, 90, "left") love.graphics.printf(formatTime(time), section_secondary_x, 40 + 20 * section, 90, "left")
end end
love.graphics.setColor(1, 1, 1, 1)
end end
local current_x local current_x
@@ -545,10 +806,14 @@ function GameMode:drawSectionTimesWithSecondary(current_section, section_colour_
current_x = section_secondary_x current_x = section_secondary_x
end end
love.graphics.printf(formatTime(self.frames - self.section_start_time), current_x, 40 + 20 * current_section, 90, "left") if current_section <= section_limit then
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) function GameMode:drawSectionTimesWithSplits(current_section, section_limit)
section_limit = section_limit or math.huge
local section_x = 440 local section_x = 440
local split_x = 530 local split_x = 530
@@ -556,14 +821,18 @@ 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
love.graphics.printf(formatTime(self.frames - self.section_start_time), section_x, 40 + 20 * current_section, 90, "left") if (current_section <= section_limit) then
love.graphics.printf(formatTime(self.frames), split_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")
end
end end
function GameMode:drawCustom() end function GameMode:drawCustom() end

View File

@@ -256,8 +256,10 @@ end
local function getSectionForLevel(level) local function getSectionForLevel(level)
if level < 2001 then if level < 2001 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
@@ -442,7 +444,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.sectionColourFunction) 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")

View File

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

View File

@@ -26,6 +26,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 +34,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,6 +132,9 @@ 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 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
@@ -145,6 +150,7 @@ function MarathonA2Game:updateScore(level, drop_bonus, cleared_lines)
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 +170,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,14 +239,16 @@ local grade_conversion = {
17, 18, 19 17, 18, 19
} }
function MarathonA2Game:whilePieceActive()
self.grade_point_decay_counter = self.grade_point_decay_counter + 1
if self.grade_point_decay_counter >= grade_point_decays[self.grade + 1] then
self.grade_point_decay_counter = 0
self.grade_points = math.max(0, self.grade_points - 1)
end
end
function MarathonA2Game:updateGrade(cleared_lines) function MarathonA2Game:updateGrade(cleared_lines)
if self.clear then return end if self.clear or cleared_lines == 0 then return
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 else
self.grade_points = self.grade_points + ( self.grade_points = self.grade_points + (
math.ceil( math.ceil(

View File

@@ -27,7 +27,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 +39,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 +164,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)
self.speed_level = self.speed_level + advanced_levels
end end
self.speed_level = self.speed_level + advanced_levels
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 +195,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 +215,15 @@ 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.section_status[section - 1] == "cool" and
self.section_70_times[section] < self.section_70_times[section - 1] + 120 then self.secondary_section_times[section] < self.secondary_section_times[section - 1] + 120 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
elseif self.section_status[section - 1] == "cool" then self.section_cool = false 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 elseif section <= 9 and self.secondary_section_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
@@ -313,13 +314,16 @@ local grade_conversion = {
17 17
} }
function MarathonA3Game:whilePieceActive()
self.grade_point_decay_counter = self.grade_point_decay_counter + 1
if self.grade_point_decay_counter >= grade_point_decays[self.grade + 1] then
self.grade_point_decay_counter = 0
self.grade_points = math.max(0, self.grade_points - 1)
end
end
function MarathonA3Game:updateGrade(cleared_lines) function MarathonA3Game:updateGrade(cleared_lines)
if cleared_lines == 0 then if cleared_lines == 0 then return
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 else
if self.clear then if self.clear then
if self:qualifiesForMRoll() then if self:qualifiesForMRoll() then
@@ -395,6 +399,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 +428,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,21 +441,22 @@ 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")
self.coolregret_timer = self.coolregret_timer - 1 self.coolregret_timer = self.coolregret_timer - 1
@@ -448,7 +465,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.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
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 +492,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

View File

@@ -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)
@@ -291,7 +287,7 @@ 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
love.graphics.setColor(1, 1, 1, 1) self.super:setHoldOpacity(1, self.held and 0.6 or 1)
end end
end end

155
tetris/modes/race_40.lua Normal file
View 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

View File

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

View File

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

View File

@@ -111,9 +111,8 @@ function SurvivalA1Game:onLineClear(cleared_row_count)
local new_level = math.min(self.level + cleared_row_count, 999) local new_level = math.min(self.level + cleared_row_count, 999)
if new_level == 999 then if new_level == 999 then
self.clear = true self.clear = true
else
self.level = new_level
end end
self.level = new_level
end end
end end

View File

@@ -159,7 +159,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 > 2968 then love.graphics.setColor(1, 0.5, 0, 1)
elseif 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)
love.graphics.printf(self.level, text_x, 340, 40, "right") love.graphics.printf(self.level, text_x, 340, 40, "right")

Some files were not shown because too many files have changed in this diff Show More