Compare commits
413 Commits
v0.1
...
v0.3-beta3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ac60cbb5e | ||
|
|
cdd846c3e6 | ||
|
|
33d260b753 | ||
|
|
1644fcdf8e | ||
|
|
f3c1cf6e1f | ||
|
|
06a8a2ebf7 | ||
|
|
15354ce004 | ||
|
|
af02cd3467 | ||
|
|
acb05918c1 | ||
|
|
b644c8e457 | ||
|
|
288961e12a | ||
|
|
a047e51681 | ||
|
|
77f24f5ee5 | ||
|
|
32c2274bef | ||
|
|
4920e5de1c | ||
|
|
8418fc8ab7 | ||
|
|
711a5120f1 | ||
|
|
e7c3c9446a | ||
|
|
3ac39acd7a | ||
|
|
d0505251b3 | ||
|
|
bb0fe2ac20 | ||
|
|
986ebac47f | ||
|
|
9799147f96 | ||
|
|
1dda12e4be | ||
|
|
38947e00c0 | ||
|
|
035f6dd7b4 | ||
|
|
aa3eadc93d | ||
|
|
cb6962825f | ||
|
|
b5e7ce5be6 | ||
|
|
1ccd6a09d3 | ||
|
|
5a074f77cf | ||
|
|
81677221f1 | ||
|
|
a998be6f7b | ||
|
|
9c1c8eea21 | ||
|
|
f022c6c4b7 | ||
|
|
38f3d23b95 | ||
|
|
816d27db39 | ||
|
|
ce08ffd3da | ||
|
|
f0e84a8874 | ||
|
|
5e02471fb4 | ||
|
|
fa2fe77081 | ||
|
|
682c4a485a | ||
|
|
68760105cc | ||
|
|
e19da98ea1 | ||
|
|
e8904b92ed | ||
|
|
4f574e7716 | ||
|
|
f1528e8d71 | ||
|
|
79a25c3954 | ||
|
|
0f3883e18d | ||
|
|
1acd0ec65a | ||
|
|
b22f671409 | ||
|
|
0b6f62d50e | ||
|
|
086f327371 | ||
|
|
3c83ae0bf4 | ||
|
|
450833b246 | ||
|
|
8e7a5418dc | ||
|
|
6609b642dc | ||
|
|
452879ebab | ||
|
|
70a827b477 | ||
|
|
d281a732db | ||
|
|
01e91fbd93 | ||
|
|
ece853c9d3 | ||
|
|
ea8d008370 | ||
|
|
e20eb048c8 | ||
|
|
a33ca1af24 | ||
|
|
664bca2282 | ||
|
|
fc8fb8b66f | ||
|
|
fc58e6e908 | ||
|
|
061f6f5164 | ||
|
|
4e9cea7dda | ||
|
|
fa97216167 | ||
|
|
3f8d68cc9d | ||
|
|
6639d73c1c | ||
|
|
668f061077 | ||
|
|
cb70967b82 | ||
|
|
0c2ba5f0cc | ||
|
|
6d07a3b820 | ||
|
|
2de13a97f0 | ||
|
|
512c2149f0 | ||
|
|
6fb19220b7 | ||
|
|
08da67c434 | ||
|
|
2d63ca8ee1 | ||
|
|
0f09d47e60 | ||
|
|
9d44d1e771 | ||
|
|
5d022f9037 | ||
|
|
818743fe77 | ||
|
|
f22424d671 | ||
|
|
dd6baf1fe6 | ||
|
|
11cf5a9d55 | ||
|
|
5642ed1326 | ||
|
|
c0888c484f | ||
|
|
3ef3b193fd | ||
|
|
0c2e3efd1a | ||
|
|
5076adf022 | ||
|
|
1a75d983dc | ||
|
|
5b8e9586bd | ||
|
|
7d7dd8c3c2 | ||
|
|
29afdcecfc | ||
|
|
8b09833ae6 | ||
|
|
64047eaf9c | ||
|
|
125488b4d9 | ||
|
|
1fdd091456 | ||
|
|
ced40297cc | ||
|
|
32f2a0b3e7 | ||
|
|
dd5347ad8d | ||
|
|
b732ebb213 | ||
|
|
84634d6933 | ||
|
|
0d13a9f236 | ||
|
|
45120bc9f7 | ||
|
|
57c7d9c4c3 | ||
|
|
9f52d8bf10 | ||
|
|
57bd6a8286 | ||
|
|
1a68cd8fce | ||
|
|
56baf46839 | ||
|
|
305d07e10a | ||
|
|
8d954cabc2 | ||
|
|
0281220ea0 | ||
|
|
aef5d88d3f | ||
|
|
3676f7697c | ||
|
|
acb0eb1a71 | ||
|
|
a89bf05cab | ||
|
|
8008315994 | ||
|
|
90f62cb7dd | ||
|
|
eaee5fc7f0 | ||
|
|
e3b038b5a7 | ||
|
|
083693496e | ||
|
|
ba576dfc77 | ||
|
|
e195ccd721 | ||
|
|
70f703eb2f | ||
|
|
dc4d4a8259 | ||
|
|
565510c7b2 | ||
|
|
c26a3f37de | ||
|
|
0c1ce2f717 | ||
|
|
f14ab2a328 | ||
|
|
042dbd220b | ||
|
|
548612123a | ||
|
|
f4675da0b0 | ||
|
|
511e9592bc | ||
|
|
5f3990ff58 | ||
|
|
50ff4adf27 | ||
|
|
87b88f4b42 | ||
|
|
130c2ea403 | ||
|
|
1ea304916e | ||
|
|
e26b094830 | ||
|
|
bcb44725bf | ||
|
|
2990844c52 | ||
|
|
c343014d6f | ||
|
|
605add7e94 | ||
|
|
d3b647ca71 | ||
|
|
1101aa467d | ||
|
|
ce27a7ed18 | ||
|
|
f31beffab8 | ||
|
|
2ff8fb5edc | ||
|
|
1bf8f91ef2 | ||
|
|
ba5f78d5f1 | ||
|
|
f7c4908062 | ||
|
|
3aa5bae7be | ||
|
|
40a2e78280 | ||
|
|
696da3fa3f | ||
|
|
4afe9f2bd4 | ||
|
|
1f686fb5d4 | ||
|
|
f4779c9847 | ||
|
|
06cbec4bc8 | ||
|
|
668564ffb0 | ||
|
|
e6edeea3d1 | ||
|
|
513cd6ba90 | ||
|
|
1beef8f157 | ||
|
|
d3b2b4c2d9 | ||
|
|
2b8b9d5084 | ||
|
|
2728780c45 | ||
|
|
ca592a3bcf | ||
|
|
b6f4158d70 | ||
|
|
e43f5c470a | ||
|
|
7bcdc517c0 | ||
|
|
1d30987f9a | ||
|
|
1dd46a11ef | ||
|
|
935c7aa14c | ||
|
|
aea115d953 | ||
|
|
3d5b33f41a | ||
|
|
29f07bb6ab | ||
|
|
891f96e814 | ||
|
|
36837a3af5 | ||
|
|
01b0f9f618 | ||
|
|
7c8c5bb11d | ||
|
|
acaa6bdbbf | ||
|
|
c37757f592 | ||
|
|
905e4bcc77 | ||
|
|
d956647678 | ||
|
|
10f032b49b | ||
|
|
5590e6c89b | ||
|
|
0393396d74 | ||
|
|
8c1eaec1aa | ||
|
|
957802a78e | ||
|
|
169a4e4d2f | ||
|
|
48aee18340 | ||
|
|
7b496d9412 | ||
|
|
7abb861446 | ||
|
|
21f8769228 | ||
|
|
44423fd2e8 | ||
|
|
351fb4cfe9 | ||
|
|
103f04ceaa | ||
|
|
88d2f0d8d1 | ||
|
|
e100289c82 | ||
|
|
e38da49180 | ||
|
|
b03473d2fe | ||
|
|
cf6e0be4e7 | ||
|
|
2bc9dc179c | ||
|
|
d626926d5a | ||
|
|
721acefea0 | ||
|
|
b9b71e90bb | ||
|
|
9f61b139fd | ||
|
|
3b0fdba27d | ||
|
|
d8fad3dc37 | ||
|
|
6d326a142c | ||
|
|
b6f1072587 | ||
|
|
eef04ebf05 | ||
|
|
e24737a3b8 | ||
|
|
f9368fa806 | ||
|
|
189feb1802 | ||
|
|
dc09dabacb | ||
|
|
e13278c6a8 | ||
|
|
f7f11b0e22 | ||
|
|
869a0f7ec5 | ||
|
|
10a9d97848 | ||
|
|
a470b40def | ||
|
|
fd739dcfdf | ||
|
|
e1dc01d0d0 | ||
|
|
7228707241 | ||
|
|
af86ce3a98 | ||
|
|
78ae0ae671 | ||
|
|
c614e9c4cd | ||
|
|
b27ef0e9f4 | ||
|
|
843b1e108a | ||
|
|
a8d697064c | ||
|
|
cf32474898 | ||
|
|
6a295cad59 | ||
|
|
2d80e20c82 | ||
|
|
2279c24d11 | ||
|
|
8510ad9bea | ||
|
|
6b77ad8547 | ||
|
|
6834e92674 | ||
|
|
3479374686 | ||
|
|
863c614a4c | ||
|
|
49e52c6a39 | ||
|
|
a105086ca6 | ||
|
|
1b381c4bf3 | ||
|
|
91a87fea73 | ||
|
|
28b455fcc0 | ||
|
|
2e3eff025f | ||
|
|
4670cb7c15 | ||
|
|
9b04e14388 | ||
|
|
f52da36bf7 | ||
|
|
76142c1dff | ||
|
|
a3458e2413 | ||
|
|
7eba9c012f | ||
|
|
4d2868b7b6 | ||
|
|
2e6fcd232b | ||
|
|
10833f2ec1 | ||
|
|
abb2b9491e | ||
|
|
062ab2005e | ||
|
|
468025fc80 | ||
|
|
c8544975d6 | ||
|
|
6776229bfb | ||
|
|
84b4dc5073 | ||
|
|
35dafb6615 | ||
|
|
3641d85fcb | ||
|
|
9b89c4d1de | ||
|
|
2dba120919 | ||
|
|
9224f271b1 | ||
|
|
febb5d546c | ||
|
|
c6482c423e | ||
|
|
6beb313c6b | ||
|
|
eb70f55b6e | ||
|
|
0badcde9ad | ||
|
|
6f39b591d3 | ||
|
|
129237f0b0 | ||
|
|
741c246244 | ||
|
|
b5937af8b2 | ||
|
|
33b8533d8e | ||
|
|
69959ff687 | ||
|
|
f91cd99dfd | ||
|
|
be59727ca5 | ||
|
|
cca295066c | ||
|
|
f2862b4d93 | ||
|
|
2aafd30253 | ||
|
|
b27ba335ba | ||
|
|
33244736b8 | ||
|
|
a324e0015a | ||
|
|
285108ca08 | ||
|
|
4b1fed727c | ||
|
|
d38168ca00 | ||
|
|
b0ce0f17f5 | ||
|
|
9fca272e8d | ||
|
|
5a21c8244b | ||
|
|
4923b2e2d4 | ||
|
|
8810f24e7a | ||
|
|
57a9f6ef55 | ||
|
|
342036bc28 | ||
|
|
78dcfe43c4 | ||
|
|
cdf6b5cf33 | ||
|
|
e6a60b0021 | ||
|
|
5f29c987f2 | ||
|
|
608d75b1ac | ||
|
|
1427c0d19e | ||
|
|
e221a91d73 | ||
|
|
bdcd25b82c | ||
|
|
a5158e0994 | ||
|
|
d946b17e13 | ||
|
|
69a5c0a21a | ||
|
|
b6423c3335 | ||
|
|
5b960d7291 | ||
|
|
54f4b0b890 | ||
|
|
8c62f321a0 | ||
|
|
fdffd2cd9a | ||
|
|
8ddf468121 | ||
|
|
8e77407ff2 | ||
|
|
92c852d178 | ||
|
|
f658ed63f2 | ||
|
|
c2d1c1183c | ||
|
|
36c568feaf | ||
|
|
bf30fcefbd | ||
|
|
d9f5bd16d7 | ||
|
|
d978ff8d87 | ||
|
|
b47d0f36b9 | ||
|
|
abc210c69c | ||
|
|
436e4ac861 | ||
|
|
a48d7c67b5 | ||
|
|
f6ca79ff91 | ||
|
|
4eb3901610 | ||
|
|
3f7fc4b622 | ||
|
|
ac7ae91c39 | ||
|
|
0c8e910245 | ||
|
|
6233ffb12d | ||
|
|
1f78bb9e99 | ||
|
|
a125c09106 | ||
|
|
090ffa5126 | ||
|
|
12a6f42198 | ||
|
|
0c317d9ce1 | ||
|
|
eddfee566d | ||
|
|
7fe366a8de | ||
|
|
55be30c99f | ||
|
|
36ceef8488 | ||
|
|
b59edb5e8e | ||
|
|
5d32b6a3e7 | ||
|
|
05230ac046 | ||
|
|
f28dc08ae2 | ||
|
|
ecd958bdc5 | ||
|
|
43f59cfde8 | ||
|
|
00c46961f9 | ||
|
|
0a0053276b | ||
|
|
d0f1d869a8 | ||
|
|
29ee000998 | ||
|
|
995fd7fee9 | ||
|
|
67abf35a28 | ||
|
|
9982613e26 | ||
|
|
629beb7240 | ||
|
|
4cb20101b0 | ||
|
|
63c0721978 | ||
|
|
1366451a3d | ||
|
|
6b624b9853 | ||
|
|
b0bda25466 | ||
|
|
2bde9d1378 | ||
|
|
6178b2cee9 | ||
|
|
4e8a237de3 | ||
|
|
5606251ea7 | ||
|
|
a4984fd687 | ||
|
|
b7ef7d1976 | ||
|
|
293b7398a2 | ||
|
|
8a2237a77c | ||
|
|
bdad32ac79 | ||
|
|
3cc918841f | ||
|
|
5f7ea0648e | ||
|
|
5d34218b97 | ||
|
|
fcd8b0f360 | ||
|
|
b983e1c108 | ||
|
|
36ab451b70 | ||
|
|
8fef7faa6a | ||
|
|
f13e2096b2 | ||
|
|
6b7f18d58a | ||
|
|
d5ce2ee9ba | ||
|
|
f04b57e7eb | ||
|
|
be644bf57b | ||
|
|
9d15feef33 | ||
|
|
634a5bc03b | ||
|
|
f1ad1f0ea4 | ||
|
|
a534331b11 | ||
|
|
d602fdfc7e | ||
|
|
971151e210 | ||
|
|
593cad0e71 | ||
|
|
1254de15d5 | ||
|
|
5c5ffc6887 | ||
|
|
5131061e42 | ||
|
|
209e60e82e | ||
|
|
c4ba80b60d | ||
|
|
2ba957f65a | ||
|
|
321de8564c | ||
|
|
96ac054cf6 | ||
|
|
b7fc51f4bd | ||
|
|
04ccd628be | ||
|
|
704e6dae55 | ||
|
|
19d1686b9d | ||
|
|
aa01479c12 | ||
|
|
5a1b137c2a | ||
|
|
956e826bb2 | ||
|
|
cebe57dd1e | ||
|
|
1db0a0ef05 | ||
|
|
0e7a2bb9fe | ||
|
|
31b0f60475 | ||
|
|
1d6ac62d7d | ||
|
|
b316d9617b | ||
|
|
99f7ff72fd | ||
|
|
df489a2603 | ||
|
|
4105040334 |
2
.gitignore
vendored
@@ -2,3 +2,5 @@
|
||||
*.love
|
||||
dist/*.zip
|
||||
dist/**/cambridge.exe
|
||||
dist/**/libs
|
||||
dist/**/*.md
|
||||
102
CONTRIBUTING.md
@@ -1,13 +1,3 @@
|
||||
Coding conventions
|
||||
------------------
|
||||
|
||||
* Use tabs to indent, spaces to align.
|
||||
* Specifically, spaces should not appear at the beginning of a line, and tabs should not appear _except_ at the beginning of a line.
|
||||
* The sole exception is in a multiline `if` statement; the initial `if` should have four spaces before it to align it with an `elseif` on the next line.
|
||||
* Comments with lines at the end of code must be one line long. Multi-line comments must appeare in their own block.
|
||||
* Use `snake_case` for variables, `camelCase` for functions.
|
||||
|
||||
|
||||
Contributor's License Agreement
|
||||
-------------------------------
|
||||
|
||||
@@ -15,4 +5,94 @@ By contributing source code or other assets (e.g. music, artwork, graphics) to C
|
||||
|
||||
You also waive all moral rights to your contributions insofar as they are used in the Cambridge repository or in any code or works deriving therefrom.
|
||||
|
||||
(Regarding the above clause, I will still make my best effort to provide sufficient attribution to all contributions. At the very least you'll get documentation of your contributions under SOURCES, and probably a special place in the credit roll as well.)
|
||||
(Notwithstanding the above clause, I will still make my best effort to provide sufficient attribution to all contributions. At the very least you'll get documentation of your contributions under SOURCES, and probably a special place in the credit roll as well.)
|
||||
|
||||
|
||||
Git / Repo conventions
|
||||
----------------------
|
||||
|
||||
In general, use `kebab-case` for branch names. Also, make sure they're concise and descriptive - like 2 or 3 words is usually good.
|
||||
|
||||
```
|
||||
* badbeef (badBranchName) This branch name is bad.
|
||||
| * defaced (another_bad_branch_name) This branch name is also bad because it uses snake case.
|
||||
|/
|
||||
| * deadcab (generic) This branch name isn't very descriptive.
|
||||
|/
|
||||
| * bac0040 (this-long-winded-branch-name-that-could-be-its-own-commit-message) Self-explanatory.
|
||||
|/
|
||||
| * 600db01 (good-branch-name) This branch name is good.
|
||||
|/
|
||||
* 0000420 (HEAD -> master, tag: v0.6.9) This is a sexy root commit.
|
||||
```
|
||||
|
||||
The top line of a commit message should generally be one full sentence long, without too many subordinate clauses. Don't sweat 50/72, but try not go over about 100 characters either.
|
||||
* If the message starts with a verb, it should be written in the past tense, as a description of what the commit _did_ to the commit tree. (e.g. _Made_ a change, _Fixed_ a bug, _Added_ a feature)
|
||||
* Alternatively, include a description (in the present tense) of what is now true thanks to this commit. (e.g. "The Puyo Puyo mode can now support up to 50 players.")
|
||||
|
||||
```
|
||||
* 800000d (message-too-long) Made multiplayer stuff play well with the new v0.2.5 server by fixing a problem the client was having with sending multiple 4-KB packets within 2 milliseconds of each other.
|
||||
| * defaced (not-descriptive-enough) Fixed stuff.
|
||||
|/
|
||||
| * bad0003 (present-tense) Lengthens the retry period of the server connection to 15 seconds.
|
||||
|/
|
||||
| * bad0004 (imperative-mood) Force the credit roll to end after 67 seconds if no input is detected.
|
||||
|/
|
||||
| * 600d001 (good-commit-summary) Made the Jenny Marathon mode not top out randomly at level 600.
|
||||
| * 600d002 (also-good) Backgrounds don't suck anymore.
|
||||
|/
|
||||
* 1234567 (HEAD -> master, tag: v0.4.2) Updated docs in preparation for a new release.
|
||||
```
|
||||
|
||||
When making pull requests, always include:
|
||||
|
||||
* A title that works well as a commit title, since that's what's going to appear when it's merged.
|
||||
* A full description of the problem that the pull request solves or the feature that it implements.
|
||||
* If the whole purpose of the pull request is to resolve a particular issue and nothing else, "Fixes #[issue number]" counts as a full description. Otherwise if there's anything else in the pull request, make a short note of "also [did this other thing]".
|
||||
|
||||
|
||||
Coding conventions
|
||||
------------------
|
||||
|
||||
Use tabs to indent, spaces to align.
|
||||
|
||||
* Specifically, spaces should not appear at the beginning of a line, and tabs should not appear _except_ at the beginning of a line.
|
||||
* The sole exception is in a multiline `if` statement; the initial `if` should have four spaces before it to align it with an `elseif` on the next line. For example:
|
||||
|
||||
```lua
|
||||
---- 4 spaces
|
||||
if self.level < 900 then return 12
|
||||
elseif self.level < 1200 then return 8
|
||||
else return 6 end
|
||||
```
|
||||
|
||||
Comments at the end of lines of code must be one line long. Multi-line comments must appear in their own block.
|
||||
|
||||
```lua
|
||||
if not self.piece:isDropBlocked(self.grid) then
|
||||
-- this is a comment that appears in a block of its own, separate from any code
|
||||
-- consecutive multiline comments must have the same indentation level and
|
||||
-- not appear next on the same line as actual code
|
||||
self.drop_bonus = 0 -- comments at the end of a line must stay on that line
|
||||
else
|
||||
if piece_dy >= 1 then -- basically
|
||||
self.drop_bonus = self.drop_bonus + piece_dy * 20 -- this sort of
|
||||
end -- multiline comment
|
||||
self.drop_bonus = self.drop_bonus + 1 -- is completely
|
||||
end -- unacceptable
|
||||
```
|
||||
|
||||
Use `snake_case` for variables, `camelCase` for functions.
|
||||
|
||||
```lua
|
||||
function MyGameMode:on_activate_bleep_bloop()
|
||||
-- no, bad, use "onActivateBleepBloop"
|
||||
|
||||
local bleepBloopFrames = 240
|
||||
-- this is also bad, use "bleep_bloop_frames"
|
||||
|
||||
local bleep_bloop_bonus = self.lock_delay * 150
|
||||
self.bleepBloopSubscore = self.bleepBloopSubscore + bleep_bloop_bonus
|
||||
-- this should be self."bleep_bloop_subscore", member variables are also variables
|
||||
end
|
||||
```
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
The code in Cambridge is licensed under the MIT license.
|
||||
|
||||
Copyright (c) 2018 Joe Zeng
|
||||
Copyright (c) 2018-2019 Joe Zeng
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -18,12 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
-------------------------
|
||||
|
||||
Some code and assets in this repository are contributed by members of the
|
||||
community, as well as borrowed from other places, either with licensing
|
||||
or as placeholders until suitable material can be found that is properly
|
||||
licensed. Their original sources, and copyright notices if applicable, are
|
||||
listed in the file SOURCES.
|
||||
SOFTWARE.
|
||||
102
README.md
Normal file
@@ -0,0 +1,102 @@
|
||||

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

|
||||
|
||||
Playing the game
|
||||
----------------
|
||||
|
||||
### Windows
|
||||
|
||||
You do not need LÖVE on Windows, as it comes bundled with the program.
|
||||
|
||||
#### Stable release
|
||||
|
||||
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:
|
||||
|
||||
dist\windows\love.exe .
|
||||
|
||||
Alternatively, if you're on a 32-bit system, run this instead:
|
||||
|
||||
dist\win32\love.exe .
|
||||
|
||||
32-bit systems do not support rich presence integration.
|
||||
|
||||
Then, check the mod pack section at the bottom of this page.
|
||||
|
||||
### macOS, Linux
|
||||
|
||||
If you haven't already, install `love` with your favourite package manager (Homebrew on macOS, your system's default on Linux). **Make sure you're using LÖVE 11, because it won't work with earlier versions!**
|
||||
|
||||
#### 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:
|
||||
|
||||
git clone https://github.com/SashLilac/cambridge
|
||||
|
||||
Alternatively, download the source code ZIP in the latest release.
|
||||
|
||||
Then, navigate to the root directory that you just cloned, and type:
|
||||
|
||||
love .
|
||||
|
||||
It should run automatically!
|
||||
|
||||
## Installing modpacks
|
||||
|
||||
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.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
The Cambridge project is licensed under the MIT license (included in LICENSE.md).
|
||||
|
||||
Some code and assets in this repository are contributed by members of the
|
||||
community, as well as borrowed from other places, either with licensing
|
||||
or as placeholders until suitable material can be found that is properly
|
||||
licensed. Their original sources, and copyright notices if applicable, are
|
||||
listed in the file SOURCES.
|
||||
68
SOURCES.md
@@ -8,7 +8,7 @@ Some of the assets are used without proper licenses. We aim to have fully licens
|
||||
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/
|
||||
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.
|
||||
|
||||
|
||||
Sounds
|
||||
------
|
||||
|
||||
All piece sounds are (c) 2020 Damian Yerrick.
|
||||
Other sounds from:
|
||||
- NullpoMino
|
||||
- DTET, (c) 2003 Mihys.
|
||||
|
||||
Music
|
||||
-----
|
||||
|
||||
1. TGM3 credit roll music.
|
||||
1. Second Reality opening scene music (1993).
|
||||
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.
|
||||
@@ -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 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.
|
||||
11
clean.bat
Normal file
@@ -0,0 +1,11 @@
|
||||
@del cambridge.love
|
||||
@del dist\windows\cambridge.exe
|
||||
@del dist\windows\SOURCES.md
|
||||
@del dist\windows\LICENSE.md
|
||||
@rmdir /Q /S dist\windows\libs
|
||||
@del dist\win32\cambridge.exe
|
||||
@del dist\win32\SOURCES.md
|
||||
@del dist\win32\LICENSE.md
|
||||
@rmdir /Q /S dist\win32\libs
|
||||
@del dist\cambridge-windows.zip
|
||||
@del dist\cambridge-win32.zip
|
||||
2
conf.lua
@@ -1,6 +1,8 @@
|
||||
function love.conf(t)
|
||||
t.identity = "cambridge"
|
||||
|
||||
t.console = true
|
||||
|
||||
t.window.title = "Cambridge"
|
||||
t.window.width = 640
|
||||
t.window.height = 480
|
||||
|
||||
BIN
dist/win32/OpenAL32.dll
vendored
Normal file
BIN
dist/win32/SDL2.dll
vendored
Normal file
BIN
dist/win32/love.dll
vendored
Normal file
BIN
dist/win32/lua51.dll
vendored
Normal file
BIN
dist/win32/mpg123.dll
vendored
Normal file
BIN
dist/win32/msvcp120.dll
vendored
Normal file
BIN
dist/win32/msvcr120.dll
vendored
Normal file
BIN
dist/windows/OpenAL32.dll
vendored
Normal file
BIN
dist/windows/SDL2.dll
vendored
Normal file
BIN
dist/windows/love.dll
vendored
Normal file
BIN
dist/windows/lua51.dll
vendored
Normal file
BIN
dist/windows/mpg123.dll
vendored
Normal file
BIN
dist/windows/msvcp120.dll
vendored
Normal file
BIN
dist/windows/msvcr120.dll
vendored
Normal file
@@ -1,43 +1,55 @@
|
||||
Game modes
|
||||
==========
|
||||
|
||||
There are several classes of game modes.
|
||||
There are several classes of game modes. The modes that originate from other games are organized by suffix:
|
||||
|
||||
* The "A" series stand for "Arika" games, or games in the Tetris the Grand Master series.
|
||||
* A1 - Tetris The Grand Master (the original from 1998).
|
||||
* A2 - Tetris The Absolute The Grand Master 2 PLUS.
|
||||
* A3 - Tetris The Grand Master 3 Terror-Instinct.
|
||||
* AX - Tetris The Grand Master ACE (X for Xbox).
|
||||
|
||||
MARATHON
|
||||
--------
|
||||
|
||||
Modes in which the goal is to play as well as possible over a limited game interval, to ultimately achieve the title of Grand Master.
|
||||
Modes in which the goal is to play as well as possible over a limited game interval.
|
||||
|
||||
* **MARATHON**: Get to level 999!
|
||||
* **MARATHON 2020**: 2020 levels of pure pain.
|
||||
* **MARATHON 2020**: 2020 levels of pure pain. Can you make it all the way?
|
||||
|
||||
From other games:
|
||||
* **MARATHON A1**: Tetris the Grand Master 1.
|
||||
* **MARATHON A2**: Tetris the Grand Master 2 (TAP Master).
|
||||
* **MARATHON A3**: Tetris the Grand Master 3 (no exams).
|
||||
|
||||
* **MARATHON N1**: NES Tetris A-type.
|
||||
|
||||
|
||||
SURVIVAL
|
||||
--------
|
||||
|
||||
Modes that concentrate on how long you can survive an increasingly fast and difficult game.
|
||||
|
||||
* **SURVIVAL**:
|
||||
* **SURVIVAL 2020**: It only gets worse from Marathon 2020. Beware of the total delay!
|
||||
|
||||
From other games:
|
||||
* **SURVIVAL A1**: 20G mode from Tetris the Grand Master.
|
||||
* **SURVIVAL A2**: T.A. Death.
|
||||
* **SURVIVAL A3**: Ti Shirase.
|
||||
* **SURVIVAL AX**: Another mode from TGM Ace.
|
||||
|
||||
|
||||
RACE
|
||||
----
|
||||
PHANTOM MANIA
|
||||
-------------
|
||||
|
||||
Modes in which the goal is to achieve a fixed goal in the shortest time.
|
||||
Modes where pieces turn invisible as soon as you lock them. One of Cambridge's signature features.
|
||||
|
||||
* **RACE 40L**: Clear 40 lines as fast as possible.
|
||||
* **RACE 100L**: Clear 100 lines as fast as possible.
|
||||
* **RACE 5K**: Clear 5,000 lines as fast as possible. Don't worry about topping out!
|
||||
* **RACE 10K**: Clear 10,000 lines as fast as possible. Don't worry about topping out!
|
||||
* **Phantom Mania**: The classic invisible mode from Nullpomino. Can you handle 999 levels of "the M roll"?
|
||||
|
||||
* **Phantom Mania 2**: Phantom Mania but way faster! Can you face a mode where even the garbage and the next preview turn invisible?
|
||||
|
||||
|
||||
|
||||
OTHER MODES
|
||||
-----------
|
||||
|
||||
* **Strategy**: How well can you plan ahead your movements? Can you handle only having a short time to place each piece?
|
||||
|
||||
* **Big A2**: Marathon A2 but all the pieces are BIG!
|
||||
@@ -1,207 +0,0 @@
|
||||
Marathon 2020
|
||||
=============
|
||||
|
||||
To celebrate the coming of the year 2020, I've created a new "extended Tetris the Grand Master" mode where the level counter goes up twice as far as normal, all the way up to 2020.
|
||||
|
||||
|
||||
Gameplay
|
||||
--------
|
||||
|
||||
The goal of this game is to reach the end at level 2020.
|
||||
|
||||
Every piece placed increases the level by 1, and every line cleared also increases the level by 1, with bonuses for large numbers of lines:
|
||||
|
||||
| Lines cleared | Levels advanced |
|
||||
|---------------|-----------------|
|
||||
| 1 | 1 |
|
||||
| 2 | 2 |
|
||||
| 3 | 4 |
|
||||
| 4 | 6 |
|
||||
|
||||
When the current level reaches one less than the level at the bottom of the display (usually a multiple of 100), the level will not advance until a line is cleared.
|
||||
|
||||
|
||||
Levels
|
||||
------
|
||||
|
||||
Each section is 100 levels long, except for the last section, whose levels go from 1900 all the way to 2020.
|
||||
|
||||
However, it is possible to be stopped early on if you do not play fast enough.
|
||||
|
||||
|
||||
### Torikans
|
||||
|
||||
There are certain checkpoints at which your current time will be checked and you will be stopped if your time is over a set objective time.
|
||||
|
||||
| Level | Time limit |
|
||||
|-------|------------|
|
||||
| 500 | 6:00.00 |
|
||||
| 900 | 8:30.00 |
|
||||
| 1000 | 8:45.00 |
|
||||
| 1500 | 11:30.00 |
|
||||
| 1900 | 13:15.00 |
|
||||
|
||||
At levels 500, 1000, and 1500, you will be stopped immediately if your time is not under the objective.
|
||||
|
||||
At levels 900 and 1900, the next section will be capped at 999 or 1999 respectively, and you will get a short credit roll when the section is over.
|
||||
|
||||
|
||||
Speed
|
||||
-----
|
||||
|
||||
Marathon 2020 gets faster in two different, independent ways, the gravity curve and the delay curve.
|
||||
|
||||
The gravity curve is always the same at a particular level, while the delay curve can vary based on your previous section time.
|
||||
|
||||
### Gravity Curve
|
||||
|
||||
The gravity curve is the same as it is in the original TGM and TAP.
|
||||
|
||||
### Delay Curve
|
||||
|
||||
The delay curve is shown as in the following table. Line ARE is always equal to ARE.
|
||||
|
||||
If your time in a particular section from 0 to 70 is smaller than the "cool" requirement at that level, your delay curve will be bumped up an extra level at the end of the section.
|
||||
|
||||
The delay curve always advances at least 1 level past level 500, and if you get a section cool when the level is past 500, it will advance 2 levels instead.
|
||||
|
||||
| Level | ARE | Lock | DAS | Line | Cool |
|
||||
|-------|------|------|------|------|------|
|
||||
|0|27|30|15|40|45.00|
|
||||
|100|24|30|12|25|41.50|
|
||||
|200|21|30|12|25|38.50|
|
||||
|300|18|30|9|20|35.00|
|
||||
|400|16|30|9|15|32.50|
|
||||
|500|14|30|8|12|29.20|
|
||||
|600|12|26|8|12|27.20|
|
||||
|700|10|22|8|8|24.80|
|
||||
|800|8|19|7|8|22.80|
|
||||
|900|6|17|7|6|20.60|
|
||||
|1000|6|15|6|6|19.60|
|
||||
|1100|6|15|6|4|19.40|
|
||||
|1200|6|15|6|4|19.40|
|
||||
|1300|5|15|5|4|18.40|
|
||||
|1400|5|15|5|2|18.20|
|
||||
|1500|4|15|4|2|16.20|
|
||||
|1600|4|13|4|2|16.20|
|
||||
|1700|4|11|4|2|16.20|
|
||||
|1800|4|10|4|2|16.20|
|
||||
|1900|4|9|4|2|16.20|
|
||||
|2000|4|8|3|2|15.20|
|
||||
|
||||
In order to get a section cool, your 0-70 section time must be below the cutoff *and* no more than 2 seconds slower than your previous 0-70 time.
|
||||
|
||||
|
||||
Grading
|
||||
-------
|
||||
|
||||
|
||||
|
||||
|
||||
### Basic grades
|
||||
|
||||
Internally, the grade counter is a number that can range from 0 to 30.
|
||||
|
||||
At the beginning of the game, it starts at 0. To increase it, you must bring an internal grade point counter past a certain threshold.
|
||||
|
||||
The threshold is set at 50 points for the first grade, then 100 more points for the next grade, then 150 points for the grade after that, and so on. You reach the maximum level of 30 at a total of 23,250 points.
|
||||
|
||||
A table of the thresholds in grade points required to reach each level is provided below:
|
||||
|
||||
Grade|Threshold
|
||||
-|-
|
||||
0|0
|
||||
1|50
|
||||
2|150
|
||||
3|300
|
||||
4|500
|
||||
5|750
|
||||
6|1050
|
||||
7|1400
|
||||
8|1800
|
||||
9|2250
|
||||
10|2750
|
||||
11|3300
|
||||
12|3900
|
||||
13|4550
|
||||
14|5250
|
||||
15|6000
|
||||
16|6800
|
||||
17|7650
|
||||
18|8550
|
||||
19|9500
|
||||
20|10500
|
||||
21|11550
|
||||
22|12650
|
||||
23|13800
|
||||
24|15000
|
||||
25|16250
|
||||
26|17550
|
||||
27|18900
|
||||
28|20300
|
||||
29|21750
|
||||
**30**|**23250**
|
||||
|
||||
Points are given according to a different scale, the point level. The point level is calculated by taking your current actual level, and adding (100 * the delay level) to it.
|
||||
|
||||
The points given for clearing certain amounts of lines is given as follows:
|
||||
|
||||
Level| x1 | x2 | x3 | x4
|
||||
-|-|-|-|-
|
||||
0|10|20|30|40
|
||||
100|10|20|30|40
|
||||
200|10|20|30|48
|
||||
300|10|20|30|60
|
||||
400|10|20|36|72
|
||||
500|10|21|42|84
|
||||
600|10|24|48|96
|
||||
700|10|27|54|108
|
||||
800|10|30|60|120
|
||||
900|11|33|66|140
|
||||
1000|12|36|72|160
|
||||
1100|13|39|81|180
|
||||
1200|14|42|90|200
|
||||
1300|15|45|99|220
|
||||
1400|16|48|108|240
|
||||
1500|17|52|117|260
|
||||
1600|18|56|126|280
|
||||
1700|19|60|135|300
|
||||
1800|20|64|144|320
|
||||
1900|21|68|153|340
|
||||
2000|22|72|162|360
|
||||
2100|23|76|171|380
|
||||
2200|24|80|180|400
|
||||
2300|25|84|189|420
|
||||
2400|26|88|198|440
|
||||
2500|27|92|207|460
|
||||
2600|28|96|216|480
|
||||
2700|29|100|225|500
|
||||
2800|30|104|234|520
|
||||
2900|31|108|243|540
|
||||
3000|32|112|252|560
|
||||
3100|33|116|261|580
|
||||
3200|34|120|270|600
|
||||
3300|35|124|279|620
|
||||
3400|36|128|288|640
|
||||
3500|37|132|297|660
|
||||
3600|38|136|306|680
|
||||
3700|39|140|315|700
|
||||
3800|40|144|324|720
|
||||
3900|41|148|333|740
|
||||
|
||||
Past level 1000, a 4-line clear will always give (30 * current grade), regardless of point level.
|
||||
|
||||
The remaining 20 grades come from section cools. Every section cool you get boosts your score by one grade. There are no regrets.
|
||||
|
||||
Points are also taken away with the time it takes to lock down a piece. The delay counter starts at 0, and increases by (current grade + 2) every frame. When the counter reaches or exceeds 240, it resets to 0, and 1 grade point is taken away.
|
||||
|
||||
Grades are based on the maximum grade points achieved. Once a grade has been attained, it cannot be lost even if grade points drop below the threshold for that grade.
|
||||
|
||||
|
||||
|
||||
Stats
|
||||
-----
|
||||
|
||||
* Fewest number of lines/pieces to reach 2020: 1263 pieces / 505 lines [all Tetrises]
|
||||
|
||||
* Most number of lines/pieces to reach 2020: 1448 pieces / 572 lines [all singles / doubles, full board]
|
||||
@@ -1,104 +0,0 @@
|
||||
Phantom Mania 2
|
||||
===============
|
||||
|
||||
The Phantom Mania mode in Nullpomino is based on T.A. Death (Speed Mania), but where everything is invisible. The obvious sequel to such a mode is Phantom Mania 2, which is based on Shirase (Speed Mania 2), but again where everything is invisible.
|
||||
|
||||
|
||||
|
||||
Gameplay
|
||||
--------
|
||||
|
||||
The goal of this game is to reach the end at level 1300, and then score as many grades as possible in the ending credit roll.
|
||||
|
||||
Each piece disappears as soon as it is placed down. Only the active piece is visible at any given time.
|
||||
|
||||
Every piece placed increases the level by 1, and every line cleared also increases the level by 1, with bonuses for large numbers of lines:
|
||||
|
||||
| Lines cleared | Levels advanced |
|
||||
|---------------|-----------------|
|
||||
| 1 | 1 |
|
||||
| 2 | 2 |
|
||||
| 3 | 4 |
|
||||
| 4 | 6 |
|
||||
|
||||
When the current level reaches one less than the level at the bottom of the display (usually a multiple of 100), the level will not advance until a line is cleared.
|
||||
|
||||
|
||||
Levels
|
||||
------
|
||||
|
||||
Each section is 100 levels long. At certain levels, the speed may get faster, or the difficulty may increase some other way.
|
||||
|
||||
| Level | What happens |
|
||||
|-------|------------|
|
||||
| 500 | Rows of garbage start to advance on the board. The bottom row is copied, appearing for a brief flash before disappearing again. |
|
||||
| 1000 | The garbage stops, but the hold queue turns invisible. |
|
||||
| 1100 | The first next preview turns invisible. |
|
||||
| 1200 | The second next preview turns invisible. |
|
||||
|
||||
Unlike in Shirase mode, the speed of the game does not get faster past level 1000.
|
||||
|
||||
### Torikans
|
||||
|
||||
There are certain checkpoints at which your current time will be checked and you will be stopped if your time is over a set objective time.
|
||||
|
||||
| Level | Time limit |
|
||||
|-------|------------|
|
||||
| 300 | 2:02.00 |
|
||||
| 500 | 3:03.00 |
|
||||
| 800 | 4:45.00 |
|
||||
| 1000 | 5:38.00 |
|
||||
|
||||
At each torikan, you will get a 54-second invisible roll which is worth grade points.
|
||||
|
||||
Only the 1300 roll is in Big Mode.
|
||||
|
||||
|
||||
Grading
|
||||
-------
|
||||
|
||||
Your grade starts at 1, and goes on from S1 to S9, and then M1 upwards indefinitely.
|
||||
|
||||
Your grade advances each section based on your section time. If you achieve a section COOL, you get 2 grade points. If you instead incur a section REGRET, you get no grade points. The normal section time (in between COOL and REGRET) is worth one grade point.
|
||||
|
||||
| Level | Section COOL | Section REGRET |
|
||||
|-------|--------------|----------------|
|
||||
| 0-4 | 36.00 | 48.00 |
|
||||
| 5-9 | 30.00 | 39.00 |
|
||||
| 10-12 | 27.00 | 35.00 |
|
||||
|
||||
|
||||
Then, the invisible roll is also worth grade points.
|
||||
|
||||
| Lines cleared | Grade points |
|
||||
|---------------|--------------|
|
||||
| 1 | 0.02 |
|
||||
| 2 | 0.06 |
|
||||
| 3 | 0.15 |
|
||||
| 4 | 0.40 |
|
||||
| Clear | 1.00 |
|
||||
|
||||
|
||||
The number of grade points you earn correspond to your grade as follows:
|
||||
|
||||
| Points | Grade |
|
||||
|--------|--------|
|
||||
| 0 | 1 |
|
||||
| 1 | S1 |
|
||||
| 2 | S2 |
|
||||
| 3 | S3 |
|
||||
| 4 | S4 |
|
||||
| 5 | S5 |
|
||||
| 6 | S6 |
|
||||
| 7 | S7 |
|
||||
| 8 | S8 |
|
||||
| 9 | S9 |
|
||||
| 10 | m1 |
|
||||
| 11 | m2 |
|
||||
| 12 | m3 |
|
||||
| 13 | m4 |
|
||||
| 14 | m5 |
|
||||
|
||||
And so on. For *x* > 9 grade points, your grade is m(*x*-9).
|
||||
|
||||
If you achieve a section COOL on each section, your rank will be m17 entering the credit roll.
|
||||
@@ -1,32 +0,0 @@
|
||||
Phantom Mania comparison
|
||||
========================
|
||||
|
||||
## Features
|
||||
|
||||
| | PM | PM2 |
|
||||
|------------|-----|------|
|
||||
| Max level | 999 | 1300 |
|
||||
| Piece preview | 1 | 3 |
|
||||
| Hold | No | Yes |
|
||||
| Graded roll | No | Yes |
|
||||
|
||||
## Torikans
|
||||
|
||||
| Level | PM | PM2 |
|
||||
|------------|-------|-------|
|
||||
| 300 | 2'28" | 2'02" |
|
||||
| 500 | 3'38" | 3'03" |
|
||||
| 800 | 5'23" | 4'40" |
|
||||
| 1000 | --- | 5'38" |
|
||||
|
||||
## Speed Curve
|
||||
|
||||
| Level | PM | PM2 |
|
||||
|------------|-------|-------|
|
||||
| 0 | 16/30 | 10/18 |
|
||||
| 100 | 12/26 | 10/18 |
|
||||
| 200 | 6/22 | 10/17 |
|
||||
| 300 | 6/18 | 4/15 |
|
||||
| 400 | 5/15 | 4/15 |
|
||||
| 500 | 4/15 | 4/13 |
|
||||
| 600+ | 4/15 | 4/12 |
|
||||
@@ -17,17 +17,9 @@ A rotation system defines the following things:
|
||||
* The block offsets of each piece orientation.
|
||||
* The wall or floor kicks that will be attempted for each type of rotation.
|
||||
|
||||
There are three main classes/families of rotation systems:
|
||||
There are four rotation systems currently supported:
|
||||
|
||||
* **ARIKA**, commonly known as ARS.
|
||||
* **ARIKA-CLASSIC**, commonly known as Classic ARS.
|
||||
* **ARIKA-TI**, commonly known as Ti-ARS, or "ARS with floorkicks".
|
||||
* **STANDARD**, commonly known as SRS.
|
||||
* **STANDARD**, or normal SRS.
|
||||
* **STANDARD-EXP**, known as SRS-X in its original Heboris incarnation.
|
||||
* **STANDARD-WORLD**, known as World Rule in TGM3.
|
||||
* **CLASSIC**, commonly known as ORS or NRS (Nintendo). Also houses some traditional rotation systems.
|
||||
* **CLASSIC-1989**, the no-wallkick system used by NES Tetris.
|
||||
* **CLASSIC-1984**, the Electonika-60 system, where the I piece is one space higher than in CLASSIC-1989.
|
||||
* **CLASSIC-SEGA**, the original Sega rotation system that spawned Arika.
|
||||
* **CLASSIC-TENGEN**, the weird one with orientation problems.
|
||||
* Cambridge
|
||||
* Classic ARS
|
||||
* Ti-ARS
|
||||
* SRS
|
||||
71
funcs.lua
@@ -1,13 +1,15 @@
|
||||
function copy(t)
|
||||
-- returns deep copy of t (as opposed to the shallow copy you get from var = t)
|
||||
if type(t) ~= "table" then return t end
|
||||
local meta = getmetatable(t)
|
||||
local target = {}
|
||||
for k, v in pairs(t) do target[k] = v end
|
||||
setmetatable(target, meta)
|
||||
return target
|
||||
local meta = getmetatable(t)
|
||||
local target = {}
|
||||
for k, v in pairs(t) do target[k] = v end
|
||||
setmetatable(target, meta)
|
||||
return target
|
||||
end
|
||||
|
||||
function st(tbl)
|
||||
function strTrueValues(tbl)
|
||||
-- returns a concatenation of all the keys in tbl with value true, separated with spaces
|
||||
str = ""
|
||||
for k, v in pairs(tbl) do
|
||||
if v == true then
|
||||
@@ -17,14 +19,16 @@ function st(tbl)
|
||||
return str
|
||||
end
|
||||
|
||||
function sp(m, s, f)
|
||||
if m == nil then m = 0 end
|
||||
if s == nil then s = 0 end
|
||||
if f == nil then f = 0 end
|
||||
return m*3600 + s*60 + math.ceil(f * 0.6)
|
||||
function frameTime(min, sec, hth)
|
||||
-- returns a time in frames from a time in minutes-seconds-hundredths format
|
||||
if min == nil then min = 0 end
|
||||
if sec == nil then sec = 0 end
|
||||
if hth == nil then hth = 0 end
|
||||
return min*3600 + sec*60 + math.ceil(hth * 0.6)
|
||||
end
|
||||
|
||||
function vAdd(v1, v2)
|
||||
-- returns the sum of vectors v1 and v2
|
||||
return {
|
||||
x = v1.x + v2.x,
|
||||
y = v1.y + v2.y
|
||||
@@ -32,6 +36,7 @@ function vAdd(v1, v2)
|
||||
end
|
||||
|
||||
function vNeg(v)
|
||||
-- returns the opposite of vector v
|
||||
return {
|
||||
x = -v.x,
|
||||
y = -v.y
|
||||
@@ -39,17 +44,49 @@ function vNeg(v)
|
||||
end
|
||||
|
||||
function formatTime(frames)
|
||||
-- returns a mm:ss:hh (h=hundredths) representation of the time in frames given
|
||||
if frames < 0 then return formatTime(0) end
|
||||
str = string.format("%02d", math.floor(frames / 3600)) .. ":"
|
||||
.. string.format("%02d", math.floor(frames / 60) % 60) .. "."
|
||||
.. string.format("%02d", math.floor(frames / 0.6) % 100)
|
||||
local min, sec, hund
|
||||
min = math.floor(frames/3600)
|
||||
sec = math.floor(frames/60) % 60
|
||||
hund = math.floor(frames/.6) % 100
|
||||
str = string.format("%02d:%02d.%02d", min, sec, hund)
|
||||
return str
|
||||
end
|
||||
|
||||
function formatBigNum(number)
|
||||
local s = string.format("%d", number)
|
||||
local pos = string.len(s) % 3
|
||||
if pos == 0 then pos = 3 end
|
||||
-- returns a string representing a number with commas as thousands separator (e.g. 12,345,678)
|
||||
local s
|
||||
if type(number) == "number" then
|
||||
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)
|
||||
.. string.gsub(string.sub(s, pos+1), "(...)", ",%1")
|
||||
end
|
||||
|
||||
function Mod1(n, m)
|
||||
-- returns a number congruent to n modulo m in the range [1;m] (as opposed to [0;m-1])
|
||||
return ((n-1) % m) + 1
|
||||
end
|
||||
|
||||
function table.contains(table, element)
|
||||
for _, value in pairs(table) do
|
||||
if value == element then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function clamp(a, b, c)
|
||||
return math.min(a, math.max(b, c))
|
||||
end
|
||||
547
libs/bigint/bigint.lua
Normal file
@@ -0,0 +1,547 @@
|
||||
#!/usr/bin/env lua
|
||||
-- If this variable is true, then strict type checking is performed for all
|
||||
-- operations. This may result in slower code, but it will allow you to catch
|
||||
-- errors and bugs earlier.
|
||||
local strict = true
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local bigint = {}
|
||||
|
||||
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, {
|
||||
__add = function(lhs, rhs)
|
||||
return bigint.add(lhs, rhs)
|
||||
end,
|
||||
__unm = function()
|
||||
if (self.sign == "+") then
|
||||
self.sign = "-"
|
||||
else
|
||||
self.sign = "+"
|
||||
end
|
||||
return 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
|
||||
})
|
||||
|
||||
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(#big.digits > 0, "bigint is empty")
|
||||
assert(type(big.sign) == "string", "bigint is unsigned")
|
||||
for _, digit in pairs(big.digits) do
|
||||
assert(type(digit) == "number", digit .. " is not a number")
|
||||
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 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
|
||||
3340
libs/bigint/named-powers-of-ten.lua
Normal file
BIN
libs/discord-rpc.dll
Normal file
BIN
libs/discord-rpc.dylib
Normal file
BIN
libs/discord-rpc.so
Normal file
282
libs/discordRPC.lua
Normal file
@@ -0,0 +1,282 @@
|
||||
local ffi = require "ffi"
|
||||
|
||||
|
||||
-- Get the host os to load correct lib
|
||||
local osname = love.system.getOS()
|
||||
local discordRPClib = nil
|
||||
|
||||
-- FFI requires the libraries really be files just sitting in the filesystem. It
|
||||
-- can't load libraries from a .love archive, nor a fused executable on Windows.
|
||||
-- Merely using love.filesystem.getSource() only works when running LOVE with
|
||||
-- the game unarchived from command line, like "love .".
|
||||
--
|
||||
-- The code here setting "source" will set the directory where the game was run
|
||||
-- from, so FFI can load discordRPC. We assume that the discordRPC library's
|
||||
-- libs directory is in the same directory as the .love archive; if it's
|
||||
-- missing, it just won't load.
|
||||
local source = love.filesystem.getSource()
|
||||
if string.sub(source, -5) == ".love" or love.filesystem.isFused() then
|
||||
source = love.filesystem.getSourceBaseDirectory()
|
||||
end
|
||||
|
||||
if osname == "Linux" then
|
||||
discordRPClib = ffi.load(source.."/libs/discord-rpc.so")
|
||||
elseif osname == "OS X" then
|
||||
discordRPClib = ffi.load(source.."/libs/discord-rpc.dylib")
|
||||
elseif osname == "Windows" then
|
||||
discordRPClib = ffi.load(source.."/libs/discord-rpc.dll")
|
||||
else
|
||||
-- Else it crashes later on
|
||||
error(string.format("Discord rpc not supported on platform (%s)", osname))
|
||||
end
|
||||
|
||||
|
||||
ffi.cdef[[
|
||||
typedef struct DiscordRichPresence {
|
||||
const char* state; /* max 128 bytes */
|
||||
const char* details; /* max 128 bytes */
|
||||
int64_t startTimestamp;
|
||||
int64_t endTimestamp;
|
||||
const char* largeImageKey; /* max 32 bytes */
|
||||
const char* largeImageText; /* max 128 bytes */
|
||||
const char* smallImageKey; /* max 32 bytes */
|
||||
const char* smallImageText; /* max 128 bytes */
|
||||
const char* partyId; /* max 128 bytes */
|
||||
int partySize;
|
||||
int partyMax;
|
||||
const char* matchSecret; /* max 128 bytes */
|
||||
const char* joinSecret; /* max 128 bytes */
|
||||
const char* spectateSecret; /* max 128 bytes */
|
||||
int8_t instance;
|
||||
} DiscordRichPresence;
|
||||
|
||||
typedef struct DiscordUser {
|
||||
const char* userId;
|
||||
const char* username;
|
||||
const char* discriminator;
|
||||
const char* avatar;
|
||||
} DiscordUser;
|
||||
|
||||
typedef void (*readyPtr)(const DiscordUser* request);
|
||||
typedef void (*disconnectedPtr)(int errorCode, const char* message);
|
||||
typedef void (*erroredPtr)(int errorCode, const char* message);
|
||||
typedef void (*joinGamePtr)(const char* joinSecret);
|
||||
typedef void (*spectateGamePtr)(const char* spectateSecret);
|
||||
typedef void (*joinRequestPtr)(const DiscordUser* request);
|
||||
|
||||
typedef struct DiscordEventHandlers {
|
||||
readyPtr ready;
|
||||
disconnectedPtr disconnected;
|
||||
erroredPtr errored;
|
||||
joinGamePtr joinGame;
|
||||
spectateGamePtr spectateGame;
|
||||
joinRequestPtr joinRequest;
|
||||
} DiscordEventHandlers;
|
||||
|
||||
void Discord_Initialize(const char* applicationId,
|
||||
DiscordEventHandlers* handlers,
|
||||
int autoRegister,
|
||||
const char* optionalSteamId);
|
||||
|
||||
void Discord_Shutdown(void);
|
||||
|
||||
void Discord_RunCallbacks(void);
|
||||
|
||||
void Discord_UpdatePresence(const DiscordRichPresence* presence);
|
||||
|
||||
void Discord_ClearPresence(void);
|
||||
|
||||
void Discord_Respond(const char* userid, int reply);
|
||||
|
||||
void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
|
||||
]]
|
||||
|
||||
local discordRPC = {} -- module table
|
||||
|
||||
-- proxy to detect garbage collection of the module
|
||||
discordRPC.gcDummy = newproxy(true)
|
||||
|
||||
local function unpackDiscordUser(request)
|
||||
return ffi.string(request.userId), ffi.string(request.username),
|
||||
ffi.string(request.discriminator), ffi.string(request.avatar)
|
||||
end
|
||||
|
||||
-- callback proxies
|
||||
-- note: callbacks are not JIT compiled (= SLOW), try to avoid doing performance critical tasks in them
|
||||
-- luajit.org/ext_ffi_semantics.html
|
||||
local ready_proxy = ffi.cast("readyPtr", function(request)
|
||||
if discordRPC.ready then
|
||||
discordRPC.ready(unpackDiscordUser(request))
|
||||
end
|
||||
end)
|
||||
|
||||
local disconnected_proxy = ffi.cast("disconnectedPtr", function(errorCode, message)
|
||||
if discordRPC.disconnected then
|
||||
discordRPC.disconnected(errorCode, ffi.string(message))
|
||||
end
|
||||
end)
|
||||
|
||||
local errored_proxy = ffi.cast("erroredPtr", function(errorCode, message)
|
||||
if discordRPC.errored then
|
||||
discordRPC.errored(errorCode, ffi.string(message))
|
||||
end
|
||||
end)
|
||||
|
||||
local joinGame_proxy = ffi.cast("joinGamePtr", function(joinSecret)
|
||||
if discordRPC.joinGame then
|
||||
discordRPC.joinGame(ffi.string(joinSecret))
|
||||
end
|
||||
end)
|
||||
|
||||
local spectateGame_proxy = ffi.cast("spectateGamePtr", function(spectateSecret)
|
||||
if discordRPC.spectateGame then
|
||||
discordRPC.spectateGame(ffi.string(spectateSecret))
|
||||
end
|
||||
end)
|
||||
|
||||
local joinRequest_proxy = ffi.cast("joinRequestPtr", function(request)
|
||||
if discordRPC.joinRequest then
|
||||
discordRPC.joinRequest(unpackDiscordUser(request))
|
||||
end
|
||||
end)
|
||||
|
||||
-- helpers
|
||||
local function checkArg(arg, argType, argName, func, maybeNil)
|
||||
assert(type(arg) == argType or (maybeNil and arg == nil),
|
||||
string.format("Argument \"%s\" to function \"%s\" has to be of type \"%s\"",
|
||||
argName, func, argType))
|
||||
end
|
||||
|
||||
local function checkStrArg(arg, maxLen, argName, func, maybeNil)
|
||||
if maxLen then
|
||||
assert(type(arg) == "string" and arg:len() <= maxLen or (maybeNil and arg == nil),
|
||||
string.format("Argument \"%s\" of function \"%s\" has to be of type string with maximum length %d",
|
||||
argName, func, maxLen))
|
||||
else
|
||||
checkArg(arg, "string", argName, func, true)
|
||||
end
|
||||
end
|
||||
|
||||
local function checkIntArg(arg, maxBits, argName, func, maybeNil)
|
||||
maxBits = math.min(maxBits or 32, 52) -- lua number (double) can only store integers < 2^53
|
||||
local maxVal = 2^(maxBits-1) -- assuming signed integers, which, for now, are the only ones in use
|
||||
assert(type(arg) == "number" and math.floor(arg) == arg
|
||||
and arg < maxVal and arg >= -maxVal
|
||||
or (maybeNil and arg == nil),
|
||||
string.format("Argument \"%s\" of function \"%s\" has to be a whole number <= %d",
|
||||
argName, func, maxVal))
|
||||
end
|
||||
|
||||
-- function wrappers
|
||||
function discordRPC.initialize(applicationId, autoRegister, optionalSteamId)
|
||||
local func = "discordRPC.Initialize"
|
||||
checkStrArg(applicationId, nil, "applicationId", func)
|
||||
checkArg(autoRegister, "boolean", "autoRegister", func)
|
||||
if optionalSteamId ~= nil then
|
||||
checkStrArg(optionalSteamId, nil, "optionalSteamId", func)
|
||||
end
|
||||
|
||||
local eventHandlers = ffi.new("struct DiscordEventHandlers")
|
||||
eventHandlers.ready = ready_proxy
|
||||
eventHandlers.disconnected = disconnected_proxy
|
||||
eventHandlers.errored = errored_proxy
|
||||
eventHandlers.joinGame = joinGame_proxy
|
||||
eventHandlers.spectateGame = spectateGame_proxy
|
||||
eventHandlers.joinRequest = joinRequest_proxy
|
||||
|
||||
discordRPClib.Discord_Initialize(applicationId, eventHandlers,
|
||||
autoRegister and 1 or 0, optionalSteamId)
|
||||
end
|
||||
|
||||
function discordRPC.shutdown()
|
||||
discordRPClib.Discord_Shutdown()
|
||||
end
|
||||
|
||||
function discordRPC.runCallbacks()
|
||||
discordRPClib.Discord_RunCallbacks()
|
||||
end
|
||||
-- http://luajit.org/ext_ffi_semantics.html#callback :
|
||||
-- It is not allowed, to let an FFI call into a C function (runCallbacks)
|
||||
-- get JIT-compiled, which in turn calls a callback, calling into Lua again (e.g. discordRPC.ready).
|
||||
-- Usually this attempt is caught by the interpreter first and the C function
|
||||
-- is blacklisted for compilation.
|
||||
-- solution:
|
||||
-- "Then you'll need to manually turn off JIT-compilation with jit.off() for
|
||||
-- the surrounding Lua function that invokes such a message polling function."
|
||||
jit.off(discordRPC.runCallbacks)
|
||||
|
||||
function discordRPC.updatePresence(presence)
|
||||
local func = "discordRPC.updatePresence"
|
||||
checkArg(presence, "table", "presence", func)
|
||||
|
||||
-- -1 for string length because of 0-termination
|
||||
checkStrArg(presence.state, 127, "presence.state", func, true)
|
||||
checkStrArg(presence.details, 127, "presence.details", func, true)
|
||||
|
||||
checkIntArg(presence.startTimestamp, 64, "presence.startTimestamp", func, true)
|
||||
checkIntArg(presence.endTimestamp, 64, "presence.endTimestamp", func, true)
|
||||
|
||||
checkStrArg(presence.largeImageKey, 31, "presence.largeImageKey", func, true)
|
||||
checkStrArg(presence.largeImageText, 127, "presence.largeImageText", func, true)
|
||||
checkStrArg(presence.smallImageKey, 31, "presence.smallImageKey", func, true)
|
||||
checkStrArg(presence.smallImageText, 127, "presence.smallImageText", func, true)
|
||||
checkStrArg(presence.partyId, 127, "presence.partyId", func, true)
|
||||
|
||||
checkIntArg(presence.partySize, 32, "presence.partySize", func, true)
|
||||
checkIntArg(presence.partyMax, 32, "presence.partyMax", func, true)
|
||||
|
||||
checkStrArg(presence.matchSecret, 127, "presence.matchSecret", func, true)
|
||||
checkStrArg(presence.joinSecret, 127, "presence.joinSecret", func, true)
|
||||
checkStrArg(presence.spectateSecret, 127, "presence.spectateSecret", func, true)
|
||||
|
||||
checkIntArg(presence.instance, 8, "presence.instance", func, true)
|
||||
|
||||
local cpresence = ffi.new("struct DiscordRichPresence")
|
||||
cpresence.state = presence.state
|
||||
cpresence.details = presence.details
|
||||
cpresence.startTimestamp = presence.startTimestamp or 0
|
||||
cpresence.endTimestamp = presence.endTimestamp or 0
|
||||
cpresence.largeImageKey = presence.largeImageKey
|
||||
cpresence.largeImageText = presence.largeImageText
|
||||
cpresence.smallImageKey = presence.smallImageKey
|
||||
cpresence.smallImageText = presence.smallImageText
|
||||
cpresence.partyId = presence.partyId
|
||||
cpresence.partySize = presence.partySize or 0
|
||||
cpresence.partyMax = presence.partyMax or 0
|
||||
cpresence.matchSecret = presence.matchSecret
|
||||
cpresence.joinSecret = presence.joinSecret
|
||||
cpresence.spectateSecret = presence.spectateSecret
|
||||
cpresence.instance = presence.instance or 0
|
||||
|
||||
discordRPClib.Discord_UpdatePresence(cpresence)
|
||||
end
|
||||
|
||||
function discordRPC.clearPresence()
|
||||
discordRPClib.Discord_ClearPresence()
|
||||
end
|
||||
|
||||
local replyMap = {
|
||||
no = 0,
|
||||
yes = 1,
|
||||
ignore = 2
|
||||
}
|
||||
|
||||
-- maybe let reply take ints too (0, 1, 2) and add constants to the module
|
||||
function discordRPC.respond(userId, reply)
|
||||
checkStrArg(userId, nil, "userId", "discordRPC.respond")
|
||||
assert(replyMap[reply], "Argument 'reply' to discordRPC.respond has to be one of \"yes\", \"no\" or \"ignore\"")
|
||||
discordRPClib.Discord_Respond(userId, replyMap[reply])
|
||||
end
|
||||
|
||||
-- garbage collection callback
|
||||
getmetatable(discordRPC.gcDummy).__gc = function()
|
||||
discordRPC.shutdown()
|
||||
ready_proxy:free()
|
||||
disconnected_proxy:free()
|
||||
errored_proxy:free()
|
||||
joinGame_proxy:free()
|
||||
spectateGame_proxy:free()
|
||||
joinRequest_proxy:free()
|
||||
end
|
||||
|
||||
return discordRPC
|
||||
138
libs/simple-slider.lua
Normal file
@@ -0,0 +1,138 @@
|
||||
--[[
|
||||
Copyright (c) 2016 George Prosser
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
]]
|
||||
|
||||
local slider = {}
|
||||
slider.__index = slider
|
||||
|
||||
function newSlider(x, y, length, value, min, max, setter, style)
|
||||
local s = {}
|
||||
s.value = (value - min) / (max - min)
|
||||
s.min = min
|
||||
s.max = max
|
||||
s.setter = setter
|
||||
s.x = x
|
||||
s.y = y
|
||||
s.length = length
|
||||
|
||||
local p = style or {}
|
||||
s.width = p.width or length * 0.1
|
||||
s.orientation = p.orientation or 'horizontal'
|
||||
s.track = p.track or 'rectangle'
|
||||
s.knob = p.knob or 'rectangle'
|
||||
|
||||
s.grabbed = false
|
||||
s.wasDown = true
|
||||
s.ox = 0
|
||||
s.oy = 0
|
||||
|
||||
return setmetatable(s, slider)
|
||||
end
|
||||
|
||||
function slider:update(mouseX, mouseY, mouseDown)
|
||||
local x = mouseX or love.mouse.getX()
|
||||
local y = mouseY or love.mouse.getY()
|
||||
local down = love.mouse.isDown(1)
|
||||
if mouseDown ~= nil then
|
||||
down = mouseDown
|
||||
end
|
||||
|
||||
local knobX = self.x
|
||||
local knobY = self.y
|
||||
if self.orientation == 'horizontal' then
|
||||
knobX = self.x - self.length/2 + self.length * self.value
|
||||
elseif self.orientation == 'vertical' then
|
||||
knobY = self.y + self.length/2 - self.length * self.value
|
||||
end
|
||||
|
||||
local ox = x - knobX
|
||||
local oy = y - knobY
|
||||
|
||||
local dx = ox - self.ox
|
||||
local dy = oy - self.oy
|
||||
|
||||
if down then
|
||||
if self.grabbed then
|
||||
if self.orientation == 'horizontal' then
|
||||
self.value = self.value + dx / self.length
|
||||
elseif self.orientation == 'vertical' then
|
||||
self.value = self.value - dy / self.length
|
||||
end
|
||||
elseif (x > knobX - self.width/2 and x < knobX + self.width/2 and y > knobY - self.width/2 and y < knobY + self.width/2) and not self.wasDown then
|
||||
self.ox = ox
|
||||
self.oy = oy
|
||||
self.grabbed = true
|
||||
end
|
||||
else
|
||||
self.grabbed = false
|
||||
end
|
||||
|
||||
self.value = math.max(0, math.min(1, self.value))
|
||||
|
||||
if self.setter ~= nil then
|
||||
self.setter(self.min + self.value * (self.max - self.min))
|
||||
end
|
||||
|
||||
self.wasDown = down
|
||||
end
|
||||
|
||||
function slider:draw()
|
||||
if self.track == 'rectangle' then
|
||||
if self.orientation == 'horizontal' then
|
||||
love.graphics.rectangle('line', self.x - self.length/2 - self.width/2, self.y - self.width/2, self.length + self.width, self.width)
|
||||
elseif self.orientation == 'vertical' then
|
||||
love.graphics.rectangle('line', self.x - self.width/2, self.y - self.length/2 - self.width/2, self.width, self.length + self.width)
|
||||
end
|
||||
elseif self.track == 'line' then
|
||||
if self.orientation == 'horizontal' then
|
||||
love.graphics.line(self.x - self.length/2, self.y, self.x + self.length/2, self.y)
|
||||
elseif self.orientation == 'vertical' then
|
||||
love.graphics.line(self.x, self.y - self.length/2, self.x, self.y + self.length/2)
|
||||
end
|
||||
elseif self.track == 'roundrect' then
|
||||
if self.orientation == 'horizontal' then
|
||||
love.graphics.rectangle('line', self.x - self.length/2 - self.width/2, self.y - self.width/2, self.length + self.width, self.width, self.width/2, self.width)
|
||||
elseif self.orientation == 'vertical' then
|
||||
love.graphics.rectangle('line', self.x - self.width/2, self.y - self.length/2 - self.width/2, self.width, self.length + self.width, self.width, self.width/2)
|
||||
end
|
||||
end
|
||||
|
||||
local knobX = self.x
|
||||
local knobY = self.y
|
||||
if self.orientation == 'horizontal' then
|
||||
knobX = self.x - self.length/2 + self.length * self.value
|
||||
elseif self.orientation == 'vertical' then
|
||||
knobY = self.y + self.length/2 - self.length * self.value
|
||||
end
|
||||
|
||||
if self.knob == 'rectangle' then
|
||||
love.graphics.rectangle('fill', knobX - self.width/2, knobY - self.width/2, self.width, self.width)
|
||||
elseif self.knob == 'circle' then
|
||||
love.graphics.circle('fill', knobX, knobY, self.width/2)
|
||||
end
|
||||
end
|
||||
|
||||
function slider:getValue()
|
||||
return self.min + self.value * (self.max - self.min)
|
||||
end
|
||||
@@ -47,7 +47,7 @@ function fadeoutBGM(time)
|
||||
end
|
||||
|
||||
function resetBGMFadeout(time)
|
||||
current_bgm:setVolume(1)
|
||||
current_bgm:setVolume(config.bgm_volume)
|
||||
fading_bgm = false
|
||||
current_bgm:play()
|
||||
end
|
||||
@@ -59,7 +59,7 @@ function processBGMFadeout(dt)
|
||||
fadeout_time = 0
|
||||
fading_bgm = false
|
||||
end
|
||||
current_bgm:setVolume(fadeout_time / total_fadeout_time)
|
||||
current_bgm:setVolume(fadeout_time * config.bgm_volume / total_fadeout_time)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
2
load/bigint.lua
Normal file
@@ -0,0 +1,2 @@
|
||||
bigint = require "libs.bigint.bigint"
|
||||
number_names = require "libs.bigint.named-powers-of-ten"
|
||||
@@ -1,58 +1,106 @@
|
||||
backgrounds = {
|
||||
[0] = love.graphics.newImage("res/backgrounds/0-quantum-foam.png"),
|
||||
love.graphics.newImage("res/backgrounds/100-big-bang.png"),
|
||||
love.graphics.newImage("res/backgrounds/200-spiral-galaxy.png"),
|
||||
love.graphics.newImage("res/backgrounds/300-sun-and-dust.png"),
|
||||
love.graphics.newImage("res/backgrounds/400-earth-and-moon.png"),
|
||||
love.graphics.newImage("res/backgrounds/500-cambrian-explosion.png"),
|
||||
love.graphics.newImage("res/backgrounds/600-dinosaurs.png"),
|
||||
love.graphics.newImage("res/backgrounds/700-asteroid.png"),
|
||||
love.graphics.newImage("res/backgrounds/800-human-fire.png"),
|
||||
love.graphics.newImage("res/backgrounds/900-early-civilization.png"),
|
||||
love.graphics.newImage("res/backgrounds/1000-vikings.png"),
|
||||
love.graphics.newImage("res/backgrounds/1100-crusades.png"),
|
||||
love.graphics.newImage("res/backgrounds/1200-genghis-khan.png"),
|
||||
love.graphics.newImage("res/backgrounds/1300-black-death.png"),
|
||||
love.graphics.newImage("res/backgrounds/1400-columbus-discovery.png"),
|
||||
love.graphics.newImage("res/backgrounds/1500-aztecas.png"),
|
||||
love.graphics.newImage("res/backgrounds/1600-telescope.png"),
|
||||
love.graphics.newImage("res/backgrounds/1700-american-revolution.png"),
|
||||
love.graphics.newImage("res/backgrounds/1800-railways.png"),
|
||||
love.graphics.newImage("res/backgrounds/1900-world-wide-web.png"),
|
||||
title = love.graphics.newImage("res/backgrounds/title_v0.1.png"),
|
||||
[0] = love.graphics.newImage("res/backgrounds/0.png"),
|
||||
love.graphics.newImage("res/backgrounds/100.png"),
|
||||
love.graphics.newImage("res/backgrounds/200.png"),
|
||||
love.graphics.newImage("res/backgrounds/300.png"),
|
||||
love.graphics.newImage("res/backgrounds/400.png"),
|
||||
love.graphics.newImage("res/backgrounds/500.png"),
|
||||
love.graphics.newImage("res/backgrounds/600.png"),
|
||||
love.graphics.newImage("res/backgrounds/700.png"),
|
||||
love.graphics.newImage("res/backgrounds/800.png"),
|
||||
love.graphics.newImage("res/backgrounds/900.png"),
|
||||
love.graphics.newImage("res/backgrounds/1000.png"),
|
||||
love.graphics.newImage("res/backgrounds/1100.png"),
|
||||
love.graphics.newImage("res/backgrounds/1200.png"),
|
||||
love.graphics.newImage("res/backgrounds/1300.png"),
|
||||
love.graphics.newImage("res/backgrounds/1400.png"),
|
||||
love.graphics.newImage("res/backgrounds/1500.png"),
|
||||
love.graphics.newImage("res/backgrounds/1600.png"),
|
||||
love.graphics.newImage("res/backgrounds/1700.png"),
|
||||
love.graphics.newImage("res/backgrounds/1800.png"),
|
||||
love.graphics.newImage("res/backgrounds/1900.png"),
|
||||
title = love.graphics.newImage("res/backgrounds/title.png"),
|
||||
snow = love.graphics.newImage("res/backgrounds/snow.png"),
|
||||
input_config = love.graphics.newImage("res/backgrounds/options-input.png"),
|
||||
game_config = love.graphics.newImage("res/backgrounds/options-game.png"),
|
||||
}
|
||||
|
||||
blocks = {
|
||||
["2tie"] = {
|
||||
I = love.graphics.newImage("res/img/s1.png"),
|
||||
J = love.graphics.newImage("res/img/s4.png"),
|
||||
L = love.graphics.newImage("res/img/s3.png"),
|
||||
O = love.graphics.newImage("res/img/s7.png"),
|
||||
S = love.graphics.newImage("res/img/s5.png"),
|
||||
T = love.graphics.newImage("res/img/s2.png"),
|
||||
Z = love.graphics.newImage("res/img/s6.png"),
|
||||
R = love.graphics.newImage("res/img/s1.png"),
|
||||
O = love.graphics.newImage("res/img/s3.png"),
|
||||
Y = love.graphics.newImage("res/img/s7.png"),
|
||||
G = love.graphics.newImage("res/img/s6.png"),
|
||||
C = love.graphics.newImage("res/img/s2.png"),
|
||||
B = love.graphics.newImage("res/img/s4.png"),
|
||||
M = love.graphics.newImage("res/img/s5.png"),
|
||||
F = love.graphics.newImage("res/img/s9.png"),
|
||||
G = love.graphics.newImage("res/img/s9.png"),
|
||||
A = love.graphics.newImage("res/img/s8.png"),
|
||||
X = love.graphics.newImage("res/img/s9.png"),
|
||||
},
|
||||
["bone"] = {
|
||||
I = love.graphics.newImage("res/img/bone.png"),
|
||||
J = love.graphics.newImage("res/img/bone.png"),
|
||||
L = love.graphics.newImage("res/img/bone.png"),
|
||||
R = love.graphics.newImage("res/img/bone.png"),
|
||||
O = love.graphics.newImage("res/img/bone.png"),
|
||||
S = love.graphics.newImage("res/img/bone.png"),
|
||||
T = love.graphics.newImage("res/img/bone.png"),
|
||||
Z = love.graphics.newImage("res/img/bone.png"),
|
||||
F = love.graphics.newImage("res/img/bone.png"),
|
||||
Y = love.graphics.newImage("res/img/bone.png"),
|
||||
G = love.graphics.newImage("res/img/bone.png"),
|
||||
C = love.graphics.newImage("res/img/bone.png"),
|
||||
B = love.graphics.newImage("res/img/bone.png"),
|
||||
M = love.graphics.newImage("res/img/bone.png"),
|
||||
F = love.graphics.newImage("res/img/bone.png"),
|
||||
A = love.graphics.newImage("res/img/bone.png"),
|
||||
X = love.graphics.newImage("res/img/bone.png"),
|
||||
},
|
||||
["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"),
|
||||
}
|
||||
}
|
||||
|
||||
ColourSchemes = {
|
||||
Arika = {
|
||||
I = "R",
|
||||
L = "O",
|
||||
J = "B",
|
||||
S = "M",
|
||||
Z = "G",
|
||||
O = "Y",
|
||||
T = "C",
|
||||
},
|
||||
TTC = {
|
||||
I = "C",
|
||||
L = "O",
|
||||
J = "B",
|
||||
S = "G",
|
||||
Z = "R",
|
||||
O = "Y",
|
||||
T = "M",
|
||||
},
|
||||
}
|
||||
|
||||
for name, blockset in pairs(blocks) do
|
||||
for shape, image in pairs(blockset) do
|
||||
image:setFilter("nearest")
|
||||
end
|
||||
end
|
||||
|
||||
misc_graphics = {
|
||||
frame = love.graphics.newImage("res/img/frame.png"),
|
||||
ready = love.graphics.newImage("res/img/ready.png"),
|
||||
go = love.graphics.newImage("res/img/go.png"),
|
||||
select_mode = love.graphics.newImage("res/img/select_mode.png"),
|
||||
strike = love.graphics.newImage("res/img/strike.png"),
|
||||
}
|
||||
santa = love.graphics.newImage("res/img/santa.png")
|
||||
}
|
||||
58
load/rpc.lua
Normal file
@@ -0,0 +1,58 @@
|
||||
print("Loading discord RPC...")
|
||||
DiscordRPC = {
|
||||
loaded = false
|
||||
}
|
||||
local success, RPC = pcall(require, "libs.discordRPC")
|
||||
if success then
|
||||
DiscordRPC.loaded = true
|
||||
DiscordRPC.appId = "599778517789573120"
|
||||
|
||||
function RPC.ready(userId, username, discriminator, avatar)
|
||||
print(string.format("Discord: ready (%s, %s, %s, %s)", userId, username, discriminator, avatar))
|
||||
end
|
||||
|
||||
function RPC.disconnected(errorCode, message)
|
||||
print(string.format("Discord: disconnected (%d: %s)", errorCode, message))
|
||||
end
|
||||
|
||||
function RPC.errored(errorCode, message)
|
||||
print(string.format("Discord: error (%d: %s)", errorCode, message))
|
||||
end
|
||||
|
||||
function RPC.joinGame(joinSecret)
|
||||
print(string.format("Discord: join (%s)", joinSecret))
|
||||
end
|
||||
|
||||
function RPC.spectateGame(spectateSecret)
|
||||
print(string.format("Discord: spectate (%s)", spectateSecret))
|
||||
end
|
||||
|
||||
function RPC.joinRequest(userId, username, discriminator, avatar)
|
||||
print(string.format("Discord: join request (%s, %s, %s, %s)", userId, username, discriminator, avatar))
|
||||
RPC.respond(userId, "yes")
|
||||
end
|
||||
|
||||
RPC.initialize(DiscordRPC.appId, true)
|
||||
local now = os.time(os.date("*t"))
|
||||
|
||||
DiscordRPC.RPC = RPC
|
||||
print("DiscordRPC successfully loaded.")
|
||||
else
|
||||
print("DiscordRPC failed to load!")
|
||||
print(RPC)
|
||||
end
|
||||
|
||||
DiscordRPC.presence = {
|
||||
startTimestamp = now,
|
||||
details = "Loading game...",
|
||||
state = "",
|
||||
largeImageKey = "icon2",
|
||||
largeImageText = "Arcade Stacker",
|
||||
smallImageKey = "",
|
||||
smallImageText = ""
|
||||
}
|
||||
|
||||
function DiscordRPC:update(newstuff)
|
||||
for k, v in pairs(newstuff) do self.presence[k] = v end
|
||||
if self.loaded then self.RPC.updatePresence(self.presence) end
|
||||
end
|
||||
@@ -10,20 +10,50 @@ sounds = {
|
||||
},
|
||||
move = love.audio.newSource("res/se/move.wav", "static"),
|
||||
bottom = love.audio.newSource("res/se/bottom.wav", "static"),
|
||||
cursor = love.audio.newSource("res/se/cursor.wav", "static"),
|
||||
cursor_lr = love.audio.newSource("res/se/cursor_lr.wav", "static"),
|
||||
main_decide = love.audio.newSource("res/se/main_decide.wav", "static"),
|
||||
mode_decide = love.audio.newSource("res/se/mode_decide.wav", "static"),
|
||||
lock = love.audio.newSource("res/se/lock.wav", "static"),
|
||||
hold = love.audio.newSource("res/se/hold.wav", "static"),
|
||||
erase = love.audio.newSource("res/se/erase.wav", "static"),
|
||||
fall = love.audio.newSource("res/se/fall.wav", "static"),
|
||||
ready = love.audio.newSource("res/se/ready.wav", "static"),
|
||||
go = love.audio.newSource("res/se/go.wav", "static"),
|
||||
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)
|
||||
if subsound == nil then
|
||||
sounds[sound]:setVolume(0.1)
|
||||
sounds[sound]:setVolume(config.sfx_volume)
|
||||
if sounds[sound]:isPlaying() then
|
||||
sounds[sound]:stop()
|
||||
end
|
||||
sounds[sound]:play()
|
||||
else
|
||||
sounds[sound][subsound]:setVolume(0.1)
|
||||
sounds[sound][subsound]:setVolume(config.sfx_volume)
|
||||
if sounds[sound][subsound]:isPlaying() then
|
||||
sounds[sound][subsound]:stop()
|
||||
end
|
||||
sounds[sound][subsound]:play()
|
||||
end
|
||||
end
|
||||
|
||||
function playSEOnce(sound, subsound)
|
||||
if subsound == nil then
|
||||
sounds[sound]:setVolume(config.sfx_volume)
|
||||
if sounds[sound]:isPlaying() then
|
||||
return
|
||||
end
|
||||
sounds[sound]:play()
|
||||
else
|
||||
sounds[sound][subsound]:setVolume(config.sfx_volume)
|
||||
if sounds[sound][subsound]:isPlaying() then
|
||||
return
|
||||
end
|
||||
sounds[sound][subsound]:play()
|
||||
end
|
||||
end
|
||||
228
main.lua
@@ -1,24 +1,75 @@
|
||||
function love.load()
|
||||
math.randomseed(os.time())
|
||||
highscores = {}
|
||||
require "load.rpc"
|
||||
require "load.graphics"
|
||||
require "load.fonts"
|
||||
require "load.sounds"
|
||||
require "load.bgm"
|
||||
require "load.save"
|
||||
require "load.bigint"
|
||||
loadSave()
|
||||
require "scene"
|
||||
config["side_next"] = false
|
||||
config["reverse_rotate"] = true
|
||||
--config["side_next"] = false
|
||||
--config["reverse_rotate"] = true
|
||||
config["fullscreen"] = false
|
||||
|
||||
love.window.setMode(love.graphics.getWidth(), love.graphics.getHeight(), {resizable = true});
|
||||
|
||||
-- used for screenshots
|
||||
GLOBAL_CANVAS = love.graphics.newCanvas()
|
||||
|
||||
-- init config
|
||||
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.secret == nil then config.secret = false
|
||||
elseif config.secret == true then playSE("welcome") end
|
||||
|
||||
if not config.gamesettings then
|
||||
config.gamesettings = {}
|
||||
config["das_last_key"] = false
|
||||
else
|
||||
config["das_last_key"] = config.gamesettings.das_last_key == 2
|
||||
end
|
||||
for _, option in ipairs(GameConfigScene.options) do
|
||||
if not config.gamesettings[option[1]] then
|
||||
config.gamesettings[option[1]] = 1
|
||||
end
|
||||
end
|
||||
|
||||
if not config.input then
|
||||
config.input = {}
|
||||
scene = InputConfigScene()
|
||||
else
|
||||
if config.current_mode then current_mode = config.current_mode end
|
||||
if config.current_ruleset then current_ruleset = config.current_ruleset end
|
||||
scene = TitleScene()
|
||||
end
|
||||
|
||||
game_modes = {}
|
||||
mode_list = love.filesystem.getDirectoryItems("tetris/modes")
|
||||
for i=1,#mode_list do
|
||||
if(mode_list[i] ~= "gamemode.lua" and mode_list[i] ~= "unrefactored_modes") then
|
||||
game_modes[#game_modes+1] = require ("tetris.modes."..string.sub(mode_list[i],1,-5))
|
||||
end
|
||||
end
|
||||
rulesets = {}
|
||||
rule_list = love.filesystem.getDirectoryItems("tetris/rulesets")
|
||||
for i=1,#rule_list do
|
||||
if(rule_list[i] ~= "ruleset.lua" and rule_list[i] ~= "unrefactored_rulesets") then
|
||||
rulesets[#rulesets+1] = require ("tetris.rulesets."..string.sub(rule_list[i],1,-5))
|
||||
end
|
||||
end
|
||||
--sort mode/rule lists
|
||||
local function padnum(d) return ("%03d%s"):format(#d, d) end
|
||||
table.sort(game_modes, function(a,b)
|
||||
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end)
|
||||
table.sort(rulesets, function(a,b)
|
||||
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end)
|
||||
|
||||
end
|
||||
|
||||
local TARGET_FPS = 60
|
||||
@@ -60,37 +111,176 @@ function love.update(dt)
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
love.graphics.push()
|
||||
if love.window.getFullscreen() then
|
||||
-- get offset matrix
|
||||
love.graphics.setDefaultFilter("linear", "nearest")
|
||||
local width = love.graphics.getWidth()
|
||||
local height = love.graphics.getHeight()
|
||||
local scale_factor = math.min(width / 640, height / 480)
|
||||
love.graphics.translate(
|
||||
(width - scale_factor * 640) / 2,
|
||||
(height - scale_factor * 480) / 2
|
||||
)
|
||||
love.graphics.scale(scale_factor)
|
||||
end
|
||||
love.graphics.setCanvas(GLOBAL_CANVAS)
|
||||
love.graphics.clear()
|
||||
|
||||
love.graphics.push()
|
||||
|
||||
-- get offset matrix
|
||||
love.graphics.setDefaultFilter("linear", "nearest")
|
||||
local width = love.graphics.getWidth()
|
||||
local height = love.graphics.getHeight()
|
||||
local scale_factor = math.min(width / 640, height / 480)
|
||||
love.graphics.translate(
|
||||
(width - scale_factor * 640) / 2,
|
||||
(height - scale_factor * 480) / 2
|
||||
)
|
||||
love.graphics.scale(scale_factor)
|
||||
|
||||
scene:render()
|
||||
love.graphics.pop()
|
||||
|
||||
love.graphics.setCanvas()
|
||||
love.graphics.setColor(1,1,1,1)
|
||||
love.graphics.draw(GLOBAL_CANVAS)
|
||||
end
|
||||
|
||||
function love.keypressed(key, scancode, isrepeat)
|
||||
function love.keypressed(key, scancode)
|
||||
-- global hotkeys
|
||||
if scancode == "f4" then
|
||||
config["fullscreen"] = not config["fullscreen"]
|
||||
love.window.setFullscreen(config["fullscreen"])
|
||||
elseif scancode == "f2" and scene.title ~= "Input Config" and scene.title ~= "Game" then
|
||||
scene = InputConfigScene()
|
||||
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")
|
||||
if not love.filesystem.getInfo("ss") then
|
||||
love.filesystem.createDirectory("ss")
|
||||
end
|
||||
print("Saving screenshot as "..ss_name)
|
||||
GLOBAL_CANVAS:newImageData():encode("png", ss_name)
|
||||
-- function keys are reserved
|
||||
elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f[1-9][0-9]+$") then
|
||||
return
|
||||
-- escape is reserved for menu_back
|
||||
elseif scancode == "escape" then
|
||||
scene:onInputPress({input="menu_back", type="key", key=key, scancode=scancode})
|
||||
-- pass any other key to the scene, with its configured mapping
|
||||
else
|
||||
scene:onKeyPress({key=key, scancode=scancode, isRepeat=isrepeat})
|
||||
local input_pressed = nil
|
||||
if config.input and config.input.keys then
|
||||
input_pressed = config.input.keys[scancode]
|
||||
end
|
||||
scene:onInputPress({input=input_pressed, type="key", key=key, scancode=scancode})
|
||||
end
|
||||
end
|
||||
|
||||
function love.keyreleased(key, scancode)
|
||||
-- escape is reserved for menu_back
|
||||
if scancode == "escape" then
|
||||
scene:onInputRelease({input="menu_back", type="key", key=key, scancode=scancode})
|
||||
-- function keys are reserved
|
||||
elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f[1-9][0-9]+$") then
|
||||
return
|
||||
-- handle all other keys; tab is reserved, but the input config scene keeps it from getting configured as a game input, so pass tab to the scene here
|
||||
else
|
||||
local input_released = nil
|
||||
if config.input and config.input.keys then
|
||||
input_released = config.input.keys[scancode]
|
||||
end
|
||||
scene:onInputRelease({input=input_released, type="key", key=key, scancode=scancode})
|
||||
end
|
||||
end
|
||||
|
||||
function love.joystickpressed(joystick, button)
|
||||
local input_pressed = nil
|
||||
if
|
||||
config.input and
|
||||
config.input.joysticks and
|
||||
config.input.joysticks[joystick:getName()] and
|
||||
config.input.joysticks[joystick:getName()].buttons
|
||||
then
|
||||
input_pressed = config.input.joysticks[joystick:getName()].buttons[button]
|
||||
end
|
||||
scene:onInputPress({input=input_pressed, type="joybutton", name=joystick:getName(), button=button})
|
||||
end
|
||||
|
||||
function love.joystickreleased(joystick, button)
|
||||
local input_released = nil
|
||||
if
|
||||
config.input and
|
||||
config.input.joysticks and
|
||||
config.input.joysticks[joystick:getName()] and
|
||||
config.input.joysticks[joystick:getName()].buttons
|
||||
then
|
||||
input_released = config.input.joysticks[joystick:getName()].buttons[button]
|
||||
end
|
||||
scene:onInputRelease({input=input_released, type="joybutton", name=joystick:getName(), button=button})
|
||||
end
|
||||
|
||||
function love.joystickaxis(joystick, axis, value)
|
||||
local input_pressed = nil
|
||||
local positive_released = nil
|
||||
local negative_released = nil
|
||||
if
|
||||
config.input and
|
||||
config.input.joysticks and
|
||||
config.input.joysticks[joystick:getName()] and
|
||||
config.input.joysticks[joystick:getName()].axes and
|
||||
config.input.joysticks[joystick:getName()].axes[axis]
|
||||
then
|
||||
if math.abs(value) >= 0.5 then
|
||||
input_pressed = config.input.joysticks[joystick:getName()].axes[axis][value >= 0.5 and "positive" or "negative"]
|
||||
end
|
||||
positive_released = config.input.joysticks[joystick:getName()].axes[axis].positive
|
||||
negative_released = config.input.joysticks[joystick:getName()].axes[axis].negative
|
||||
end
|
||||
if math.abs(value) >= 0.5 then
|
||||
scene:onInputPress({input=input_pressed, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
|
||||
else
|
||||
scene:onInputRelease({input=positive_released, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
|
||||
scene:onInputRelease({input=negative_released, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
|
||||
end
|
||||
end
|
||||
|
||||
function love.joystickhat(joystick, hat, direction)
|
||||
local input_pressed = nil
|
||||
local has_hat = false
|
||||
if
|
||||
config.input and
|
||||
config.input.joysticks and
|
||||
config.input.joysticks[joystick:getName()] and
|
||||
config.input.joysticks[joystick:getName()].hats and
|
||||
config.input.joysticks[joystick:getName()].hats[hat]
|
||||
then
|
||||
if direction ~= "c" then
|
||||
input_pressed = config.input.joysticks[joystick:getName()].hats[hat][direction]
|
||||
end
|
||||
has_hat = true
|
||||
end
|
||||
if input_pressed then
|
||||
scene:onInputPress({input=input_pressed, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
|
||||
elseif has_hat then
|
||||
for i, direction in ipairs{"d", "l", "ld", "lu", "r", "rd", "ru", "u"} do
|
||||
scene:onInputRelease({input=config.input.joysticks[joystick:getName()].hats[hat][direction], type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
|
||||
end
|
||||
elseif direction ~= "c" then
|
||||
scene:onInputPress({input=nil, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
|
||||
else
|
||||
for i, direction in ipairs{"d", "l", "ld", "lu", "r", "rd", "ru", "u"} do
|
||||
scene:onInputRelease({input=nil, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function love.focus(f)
|
||||
if f then
|
||||
if f and (scene.title ~= "Game" or not scene.paused) then
|
||||
resumeBGM()
|
||||
else
|
||||
pauseBGM()
|
||||
end
|
||||
end
|
||||
|
||||
function love.resize(w, h)
|
||||
GLOBAL_CANVAS:release()
|
||||
GLOBAL_CANVAS = love.graphics.newCanvas(w, h)
|
||||
end
|
||||
2
package.bat
Normal file
@@ -0,0 +1,2 @@
|
||||
tar -a -c -f cambridge.zip libs load res scene tetris conf.lua main.lua scene.lua funcs.lua
|
||||
rename cambridge.zip cambridge.love
|
||||
4
release
@@ -4,6 +4,6 @@ mkdir dist/windows
|
||||
mkdir dist/win32
|
||||
cp cambridge.love dist/
|
||||
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
|
||||
zip dist/cambridge-win32.zip dist/win32/* SOURCES.md LICENSE
|
||||
zip dist/cambridge-win32.zip dist/win32/* SOURCES.md LICENSE.md
|
||||
|
||||
26
release.bat
Normal file
@@ -0,0 +1,26 @@
|
||||
call package.bat
|
||||
|
||||
mkdir dist
|
||||
mkdir dist\windows
|
||||
mkdir dist\windows\libs
|
||||
mkdir dist\win32
|
||||
mkdir dist\win32\libs
|
||||
|
||||
copy /b dist\windows\love.exe+cambridge.love dist\windows\cambridge.exe
|
||||
copy /b dist\win32\love.exe+cambridge.love dist\win32\cambridge.exe
|
||||
|
||||
copy libs\discord-rpc.dll dist\windows\libs
|
||||
copy libs\discord-rpc.dll dist\win32\libs
|
||||
|
||||
copy SOURCES.md dist\windows
|
||||
copy LICENSE.md dist\windows
|
||||
copy SOURCES.md dist\win32
|
||||
copy LICENSE.md dist\win32
|
||||
|
||||
cd dist\windows
|
||||
tar -a -c -f ..\cambridge-windows.zip cambridge.exe *.dll libs *.md
|
||||
cd ..\..
|
||||
|
||||
cd dist\win32
|
||||
tar -a -c -f ..\cambridge-win32.zip cambridge.exe *.dll libs *.md
|
||||
cd ..\..
|
||||
0
res/backgrounds/0-quantum-foam.png → res/backgrounds/0.png
Executable file → Normal file
|
Before Width: | Height: | Size: 2.4 MiB After Width: | Height: | Size: 2.4 MiB |
0
res/backgrounds/100-big-bang.png → res/backgrounds/100.png
Executable file → Normal file
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
0
res/backgrounds/1000-vikings.png → res/backgrounds/1000.png
Executable file → Normal file
|
Before Width: | Height: | Size: 2.0 MiB After Width: | Height: | Size: 2.0 MiB |
0
res/backgrounds/1100-crusades.png → res/backgrounds/1100.png
Executable file → Normal file
|
Before Width: | Height: | Size: 2.9 MiB After Width: | Height: | Size: 2.9 MiB |
0
res/backgrounds/1200-genghis-khan.png → res/backgrounds/1200.png
Executable file → Normal file
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 2.1 MiB |
0
res/backgrounds/1300-black-death.png → res/backgrounds/1300.png
Executable file → Normal file
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
0
res/backgrounds/1400-columbus-discovery.png → res/backgrounds/1400.png
Executable file → Normal file
|
Before Width: | Height: | Size: 2.6 MiB After Width: | Height: | Size: 2.6 MiB |
0
res/backgrounds/1500-aztecas.png → res/backgrounds/1500.png
Executable file → Normal file
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 2.1 MiB |
0
res/backgrounds/1600-telescope.png → res/backgrounds/1600.png
Executable file → Normal file
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 2.1 MiB |
0
res/backgrounds/1700-american-revolution.png → res/backgrounds/1700.png
Executable file → Normal file
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 2.1 MiB |
0
res/backgrounds/1800-railways.png → res/backgrounds/1800.png
Executable file → Normal file
|
Before Width: | Height: | Size: 2.4 MiB After Width: | Height: | Size: 2.4 MiB |
0
res/backgrounds/1900-world-wide-web.png → res/backgrounds/1900.png
Executable file → Normal file
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
0
res/backgrounds/200-spiral-galaxy.png → res/backgrounds/200.png
Executable file → Normal file
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
0
res/backgrounds/300-sun-and-dust.png → res/backgrounds/300.png
Executable file → Normal file
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
0
res/backgrounds/400-earth-and-moon.png → res/backgrounds/400.png
Executable file → Normal file
|
Before Width: | Height: | Size: 2.0 MiB After Width: | Height: | Size: 2.0 MiB |
0
res/backgrounds/500-cambrian-explosion.png → res/backgrounds/500.png
Executable file → Normal file
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 2.1 MiB |
0
res/backgrounds/600-dinosaurs.png → res/backgrounds/600.png
Executable file → Normal file
|
Before Width: | Height: | Size: 3.1 MiB After Width: | Height: | Size: 3.1 MiB |
0
res/backgrounds/700-asteroid.png → res/backgrounds/700.png
Executable file → Normal file
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
0
res/backgrounds/800-human-fire.png → res/backgrounds/800.png
Executable file → Normal file
|
Before Width: | Height: | Size: 2.7 MiB After Width: | Height: | Size: 2.7 MiB |
0
res/backgrounds/900-early-civilization.png → res/backgrounds/900.png
Executable file → Normal file
|
Before Width: | Height: | Size: 3.6 MiB After Width: | Height: | Size: 3.6 MiB |
BIN
res/backgrounds/options-game.png
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
BIN
res/backgrounds/options-input.png
Normal file
|
After Width: | Height: | Size: 3.0 MiB |
BIN
res/backgrounds/snow.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
res/backgrounds/title.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.4 KiB |
BIN
res/img/bonew.png
Normal file
|
After Width: | Height: | Size: 229 B |
BIN
res/img/gem1.png
Normal file
|
After Width: | Height: | Size: 462 B |
BIN
res/img/gem2.png
Normal file
|
After Width: | Height: | Size: 388 B |
BIN
res/img/gem3.png
Normal file
|
After Width: | Height: | Size: 445 B |
BIN
res/img/gem4.png
Normal file
|
After Width: | Height: | Size: 426 B |
BIN
res/img/gem5.png
Normal file
|
After Width: | Height: | Size: 376 B |
BIN
res/img/gem6.png
Normal file
|
After Width: | Height: | Size: 377 B |
BIN
res/img/gem7.png
Normal file
|
After Width: | Height: | Size: 399 B |
BIN
res/img/gem9.png
Normal file
|
After Width: | Height: | Size: 354 B |
BIN
res/img/s8.png
Normal file
|
After Width: | Height: | Size: 233 B |
BIN
res/img/santa.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
res/img/squareg.png
Normal file
|
After Width: | Height: | Size: 708 B |
BIN
res/img/squares.png
Normal file
|
After Width: | Height: | Size: 639 B |