diff --git a/.gitignore b/.gitignore index 414c49a..ab39c6e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .vscode/ package-lock.json -Cargo.lock \ No newline at end of file +Cargo.lock +README.md \ No newline at end of file diff --git a/LICENSE b/LICENSE index 85a0235..c11413b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2024 TitanPlayz - -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. +MIT License + +Copyright (c) 2024 TitanPlayz + +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. diff --git a/README.md b/README.md index 60af4c1..973b966 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,50 @@ -# Teti README - -Hosted on github pages [here](https://titanplayz100.github.io/teti/) - -The info page can be found [here](https://titanplayz100.github.io/teti/info.html) - -> [!WARNING] -> Firefox is not supported (due to import assertions) - -## Desktop App -### NEW! - -Releases are now run through a workflow. They are **up to date** and contain all the latest features. - -App build using Tauri, feel free to open issues and PRs. - -## Data Formats (for my convenience) -### Gamemode Structure (gamemodes.json) -```js -gamemodes = { - "gamemode_name": { - settings: {}, // settings that override the default * settings - displayName: "", // name shown on gamemode selection - objectiveText: "", // subtext displayed on right side - goalStat: "", // stat being tracked (valid property in stats class) - target: "", // target (valid target in settings) - result: "", // displayed as result (another valid stat in stats class) - - // TO BE ADDED LATER - music: "", // custom song that can play - compmusic: "", // custom song that played on pb pace - startBoard: "", // starting board, tetrio map format - effects: [], // custom background / effects - } -} -``` - -Add functionality mainly in `features/modes.js`. -You can modify existing modules as well from other files - -### Adding Audio (sfxlist.json) -```json -{ - { - "name": "", - "path": "assets/sfx/." - } -} -``` -Use with `this.game.sounds.playSound()` +# Teti README + +Hosted on github pages [here](https://titanplayz100.github.io/teti/) + +The info page can be found [here](https://titanplayz100.github.io/teti/info.html) + +> [!WARNING] +> Firefox is not supported (due to import assertions) + +## Desktop App +### NEW! + +Releases are now run through a workflow. They are **up to date** and contain all the latest features. + +App build using Tauri, feel free to open issues and PRs. + +## Data Formats (for my convenience) +### Gamemode Structure (gamemodes.json) +```js +gamemodes = { + "gamemode_name": { + settings: {}, // settings that override the default * settings + displayName: "", // name shown on gamemode selection + objectiveText: "", // subtext displayed on right side + goalStat: "", // stat being tracked (valid property in stats class) + target: "", // target (valid target in settings) + result: "", // displayed as result (another valid stat in stats class) + + // TO BE ADDED LATER + music: "", // custom song that can play + compmusic: "", // custom song that played on pb pace + startBoard: "", // starting board, tetrio map format + effects: [], // custom background / effects + } +} +``` + +Add functionality mainly in `features/modes.js`. +You can modify existing modules as well from other files + +### Adding Audio (sfxlist.json) +```json +{ + { + "name": "", + "path": "assets/sfx/." + } +} +``` +Use with `this.game.sounds.playSound()` diff --git a/assets/icons/cleargarb.svg b/assets/icons/cleargarb.svg index 0bf1351..ba06cdb 100644 --- a/assets/icons/cleargarb.svg +++ b/assets/icons/cleargarb.svg @@ -1,6 +1,6 @@ - - - - - + + + + + \ No newline at end of file diff --git a/assets/icons/download.svg b/assets/icons/download.svg index 1cd579b..5c4fd7d 100644 --- a/assets/icons/download.svg +++ b/assets/icons/download.svg @@ -1 +1 @@ - + diff --git a/assets/icons/info.svg b/assets/icons/info.svg index a4d9d1a..dbcf9fc 100644 --- a/assets/icons/info.svg +++ b/assets/icons/info.svg @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/assets/icons/next.svg b/assets/icons/next.svg index 4e2db95..ebb3fce 100644 --- a/assets/icons/next.svg +++ b/assets/icons/next.svg @@ -1,6 +1,6 @@ - - - - - + + + + + \ No newline at end of file diff --git a/assets/sfx/zenith_speedrun_end.mp3 b/assets/sfx/zenith_speedrun_end.mp3 new file mode 100644 index 0000000..27eaddf Binary files /dev/null and b/assets/sfx/zenith_speedrun_end.mp3 differ diff --git a/assets/sfx/zenith_speedrun_start.mp3 b/assets/sfx/zenith_speedrun_start.mp3 new file mode 100644 index 0000000..cf5bd99 Binary files /dev/null and b/assets/sfx/zenith_speedrun_start.mp3 differ diff --git a/assets/skins/tgm_c.png b/assets/skins/tgm_c.png new file mode 100644 index 0000000..5e7e0fc Binary files /dev/null and b/assets/skins/tgm_c.png differ diff --git a/assets/skins/tgm_w.png b/assets/skins/tgm_w.png new file mode 100644 index 0000000..b4fafec Binary files /dev/null and b/assets/skins/tgm_w.png differ diff --git a/index.html b/index.html index 058ad03..7628220 100644 --- a/index.html +++ b/index.html @@ -182,6 +182,8 @@

Teti

Allow Hold Queue

Infinite Hold

Allow Queue Modification

+

Line Clear Delay

+

Line Clear Delay

Allspin

Allspin's are mini

Allow history (undo/redo)

diff --git a/info.html b/info.html index 8a8665d..34eac65 100644 --- a/info.html +++ b/info.html @@ -1,88 +1,88 @@ - - - - - - - - - Teti - Info - - - - - -
- -
- Back to teti - - - - - - -
-

css using chota

-

md renderer with zero-md

- Top of Page - - - - + + + + + + + + + Teti - Info + + + + + +
+ +
+ Back to teti + + + + + + +
+

css using chota

+

md renderer with zero-md

+ Top of Page + + + + \ No newline at end of file diff --git a/info.md b/info.md index 6ee1f5a..89123ee 100644 --- a/info.md +++ b/info.md @@ -16,11 +16,11 @@ This project started out as a learning experience, because surprisingly it was m ## Acknowledgements Thanks [existancepy](https://github.com/existancepy) for all the fixes. -Thanks [ItzBlack6093](https://github.com/ItzBlack6093) for adding TGM Race mode and other fixes. +Thanks [ItzBlack6093](https://github.com/ItzBlack6093) for adding many modes and fixes. Feel free to contribute with features and fixes, and open issues. -Design inspired by [Strangelinx's 'blocks'](https://strangelinx.github.io/blocks/) and [Tetra Legends.](https://tetralegend.app) +Design inspired by [Strangelinx's 'blocks'](https://strangelinx.github.io/blocks/) and [Tetra Legends.](https://tetralegends.app) Icons from [The Noun Project.](https://thenounproject.com) Sound effects from [TETR.IO.](https://tetr.io) Skins from [YHF](https://you.have.fail/ed/at/tetrioplus/) @@ -67,10 +67,28 @@ Skins from [YHF](https://you.have.fail/ed/at/tetrioplus/) ## TODO list Things that I am working on based on other changes -- wow finished +- ready set go start option +- arcade mode + +Maybe: +- cooler action text (maybe if i switch to pixijs then i can make text better) + +Future thoughts (honestly only if i have time): +- I honestly might want to break my rule and start using a framework for rendering. + - (turns out pixijs is much better for games than canvas 2d, and howler for audio) +- I have mixed feelings, because this would make the project technically no longer purely made by me (though the info page is a bit iffy) +- Maybe I don't care and will switch anyway, and maybe in the end it will pay off. +- this would make my most recent changes irrelivent + - such as piece flash and reset animation cause i have to redo them in pixi + - in the future itll probably pay off +*** ## Updates +#### v1.3.3 +- Thanks to itzblack for the Zenith Tower mode +- added option for line clear delay + #### v1.3.2 - reset animation - toggles with stride mode @@ -80,7 +98,7 @@ Things that I am working on based on other changes - added notifications - displays errors - shows export and import messages -- removed assets loading screen, cause it was unecessary +- removed assets loading screen, cause it was unnecessary - added different grid patterns #### v1.3.1 @@ -159,8 +177,8 @@ Things that I am working on based on other changes - can easily add custom gamemodes now using gamemodes JSON - competitive mode - sets certain gamerules, disables custom game settings - - PBs are onyl saved when used -- seperated goals into its own menu + - PBs are only saved when used +- separated goals into its own menu - view pbs in competitive mode menu ### v1.2.0 => COMPETITIVE MODE @@ -203,8 +221,6 @@ Things that I am working on based on other changes *** ## Feature Wishlist Future wants for game, kinda ordered by ease and desire for feature -- ready set go start option -- cooler action text - finesse detection - allow importing tetrio settings and custom game files - small guide on essential things for game @@ -212,14 +228,20 @@ Future wants for game, kinda ordered by ease and desire for feature - WIKI for technical docs about project - glossary of useful terms - more rotation systems (ars, trs, srs/srsX, none) +- more bag systems (pure random, nes random, 14 bag, 7+2 bag) - replay functionality (either save gamestate or save keystrokes idk yet) + - statistics graph - more unique gamemodes (techmino styled) - misdrop remover mode - holdless and next queueless gamemode kinda like qp2 cards - colourblind gamemode -- guide like progression thing? using custom boards (kinda like tetris tres bien) +- guide like progression using custom maps + - maybe like tetris tres bien - achievements, progression tree -- maybe play around with server api stuff, like adding a leaderboard or connecting tetrio stats + - the jstris map creator looks really nice + - could have user made maps section as well +- maybe play around with api stuff + - leaderboards + - connecting tetrio stats - touch settings -- bot to play against -- statistics graph \ No newline at end of file +- bot to play against \ No newline at end of file diff --git a/src/data/attacktable.json b/src/data/attacktable.json index 53e6e3a..647ecad 100644 --- a/src/data/attacktable.json +++ b/src/data/attacktable.json @@ -1,15 +1,15 @@ -{ - "": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - "TSPIN": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - "TSPIN MINI": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - "SINGLE": [0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3], - "DOUBLE": [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6], - "TRIPLE": [2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12], - "QUAD": [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24], - "TSPIN SINGLE": [2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12], - "TSPIN DOUBLE": [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24], - "TSPIN TRIPLE": [6, 7, 9, 10, 12, 13, 15, 16, 18, 19, 21, 22, 24, 25, 27, 28, 30, 31, 33, 34, 36], - "TSPIN MINI SINGLE": [0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3], - "TSPIN MINI DOUBLE": [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6], - "ALL CLEAR": 10 +{ + "": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "TSPIN": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "TSPIN MINI": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "SINGLE": [0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3], + "DOUBLE": [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6], + "TRIPLE": [2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12], + "QUAD": [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24], + "TSPIN SINGLE": [2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12], + "TSPIN DOUBLE": [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24], + "TSPIN TRIPLE": [6, 7, 9, 10, 12, 13, 15, 16, 18, 19, 21, 22, 24, 25, 27, 28, 30, 31, 33, 34, 36], + "TSPIN MINI SINGLE": [0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3], + "TSPIN MINI DOUBLE": [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6], + "ALL CLEAR": 10 } \ No newline at end of file diff --git a/src/data/data.js b/src/data/data.js index bb5f696..b01804d 100644 --- a/src/data/data.js +++ b/src/data/data.js @@ -1,139 +1,141 @@ -export const cleartypes = { 0: "", 1: "Single", 2: "Double", 3: "Triple", 4: "Quad", 5: "Teti-tris" }; - -export const defaultSkins = ['tetrio', 'jstris', 'plain']; - -export const scoringTable = { - "": 0, - TSPIN: 400, - "TSPIN MINI": 100, - SINGLE: 100, - DOUBLE: 300, - TRIPLE: 500, - QUAD: 800, - "TSPIN SINGLE": 800, - "TSPIN DOUBLE": 1200, - "TSPIN TRIPLE": 1600, - "TSPIN MINI SINGLE": 200, - "TSPIN MINI DOUBLE": 400, - "ALL CLEAR": 3500, -}; - -export const levellingTable = { - 0: 0, - 1: 1, - 2: 2, - 3: 4, - 4: 6, -}; - -export const disabledKeys = [ - "ArrowRight", - "ArrowLeft", - "ArrowUp", - "ArrowDown", - " ", - "Enter", - "Escape", - "Tab", -]; - -export const spinChecks = [ - [ - [0, 2], - [2, 2], - ], - [ - [2, 2], - [2, 0], - ], - [ - [0, 0], - [2, 0], - ], - [ - [0, 0], - [0, 2], - ], -]; - -export const gameoverText = { - clearlines: "Cleared _ lines", - attack: "Sent _ damage", - cleargarbage: "Dug _ lines", - ended: "Survived _ lines", - level: "Reached level _", - time: "Spent _ seconds", - altitude: "Climbed _ meters", -} - -export const gameoverResultText = { - time: " in _ seconds", - score: " to score _ points", - maxCombo: " to get _ combo", -} - -export const lowerIsBetter = { - time: true, - score: false, - maxCombo: false -} - -export const pbTrackingStat = { - clearlines: "pps", - time: "score", - attack: "apm", - cleargarbage: "dss", - combo: "maxCombo", - level: "pps" -} - -export const resultSuffix = { - time: 's', - score: ' Points', - maxCombo: ' Combo' -} - -export const statDecimals = { - 0: ["clearlines", "pieceCount", "score", "pcs", "quads", "allspins", "level", "attack", "cleargarbage", "sent", "recieved", "combo", "maxCombo", "btbCount", "maxBTB", "tpE", "ipE", "inputs", "holds", "rotates", "ppb",], - 1: ["time", "vs", "chzind", "garbeff",], - 2: ["pps", "apm", "lpm", "app", "apl", "appw", "dss", "dsp", "vsOnApm", "kps", "kpp"] -} - -export const statsSecondary = { - pps: "pieceCount", - apm: "attack", - lpm: "clearlines", - app: "attack", - apl: "attack", - appw: "attack", - dss: "cleargarbage", - dsp: "cleargarbage", - kps: "inputs", - kpp: "inputs", - tpE: "tspins", - ipE: "quads", - ppb: "score" -} - - -// const sfx = await fetch("https://api.github.com/repos/titanplayz100/teti/contents/assets/sfx") -// const combo = await fetch("https://api.github.com/repos/titanplayz100/teti/contents/assets/sfx/combo") -// const songs = await fetch("https://api.github.com/repos/titanplayz100/teti/contents/assets/songs") -// manually remove dirs -export const songsobj = [ - { - "name": "Cafe de Touhou 3 - The Girl's Secret Room.mp3", - "path": "assets/songs/Cafe de Touhou 3 - The Girl's Secret Room.mp3", - "type": "file" - }, - { - "name": "ShibayanRecords - Acoustic Image.mp3", - "path": "assets/songs/ShibayanRecords - Acoustic Image.mp3", - "type": "file" - }, - { - "name": "ShibayanRecords - Close to your Mind.mp3", - "path": "assets/songs/ShibayanRecords - Close to your Mind.mp3", - "type": "file" - } -]; +export const cleartypes = { 0: "", 1: "Single", 2: "Double", 3: "Triple", 4: "Quad", 5: "Teti-tris" }; + +export const defaultSkins = ['tetrio', 'jstris', 'plain', 'tgm_c', 'tgm_w']; + +export const scoringTable = { + "": 0, + TSPIN: 400, + "TSPIN MINI": 100, + SINGLE: 100, + DOUBLE: 300, + TRIPLE: 500, + QUAD: 800, + "TSPIN SINGLE": 800, + "TSPIN DOUBLE": 1200, + "TSPIN TRIPLE": 1600, + "TSPIN MINI SINGLE": 200, + "TSPIN MINI DOUBLE": 400, + "ALL CLEAR": 3500, +}; + +export const levellingTable = { + 0: 0, + 1: 1, + 2: 2, + 3: 4, + 4: 6, +}; + +export const disabledKeys = [ + "ArrowRight", + "ArrowLeft", + "ArrowUp", + "ArrowDown", + " ", + "Enter", + "Escape", + "Tab", +]; + +export const spinChecks = [ + [ + [0, 2], + [2, 2], + ], + [ + [2, 2], + [2, 0], + ], + [ + [0, 0], + [2, 0], + ], + [ + [0, 0], + [0, 2], + ], +]; + +export const gameoverText = { + clearlines: "Cleared _ lines", + attack: "Sent _ damage", + cleargarbage: "Dug _ lines", + ended: "Survived _ lines", + tgm_level: "Reached level _", + time: "Spent _ seconds", + altitude: "Climbed _ meters", +} + +export const gameoverResultText = { + time: " in _ seconds", + score: " to score _ points", + maxCombo: " to get _ combo", + grade: " to get grade _" +} + +export const lowerIsBetter = { + time: true, + score: false, + maxCombo: false +} + +export const pbTrackingStat = { + clearlines: "pps", + time: "score", + attack: "apm", + cleargarbage: "dss", + combo: "maxCombo", + level: "pps" +} + +export const resultSuffix = { + time: 's', + score: ' Points', + maxCombo: ' Combo', + grade: "" +} + +export const statDecimals = { + 0: ["clearlines", "pieceCount", "score", "pcs", "quads", "allspins", "level", "attack", "cleargarbage", "sent", "recieved", "combo", "maxCombo", "btbCount", "maxBTB", "tpE", "ipE", "inputs", "holds", "rotates", "ppb",], + 1: ["time", "vs", "chzind", "garbeff",], + 2: ["pps", "apm", "lpm", "app", "apl", "appw", "dss", "dsp", "vsOnApm", "kps", "kpp"] +} + +export const statsSecondary = { + pps: "pieceCount", + apm: "attack", + lpm: "clearlines", + app: "attack", + apl: "attack", + appw: "attack", + dss: "cleargarbage", + dsp: "cleargarbage", + kps: "inputs", + kpp: "inputs", + tpE: "tspins", + ipE: "quads", + ppb: "score" +} + + +// const sfx = await fetch("https://api.github.com/repos/titanplayz100/teti/contents/assets/sfx") +// const combo = await fetch("https://api.github.com/repos/titanplayz100/teti/contents/assets/sfx/combo") +// const songs = await fetch("https://api.github.com/repos/titanplayz100/teti/contents/assets/songs") +// manually remove dirs +export const songsobj = [ + { + "name": "Cafe de Touhou 3 - The Girl's Secret Room.mp3", + "path": "assets/songs/Cafe de Touhou 3 - The Girl's Secret Room.mp3", + "type": "file" + }, + { + "name": "ShibayanRecords - Acoustic Image.mp3", + "path": "assets/songs/ShibayanRecords - Acoustic Image.mp3", + "type": "file" + }, + { + "name": "ShibayanRecords - Close to your Mind.mp3", + "path": "assets/songs/ShibayanRecords - Close to your Mind.mp3", + "type": "file" + } +]; diff --git a/src/data/defaultSettings.json b/src/data/defaultSettings.json index eb5926c..ade8d14 100644 --- a/src/data/defaultSettings.json +++ b/src/data/defaultSettings.json @@ -27,7 +27,14 @@ "preserveARR": true, "allowHold": true, "infiniteHold": true, + "clearDelay": 0, "gamemode": "sprint", + "allspin": true, + "allspinminis": true, + "history": true, + "competitiveMode": false, + "sidebar": ["time", "apm", "pps"], + "stride": false, "requiredLines": 40, "timeLimit": 120, "requiredAttack": 40, @@ -37,13 +44,7 @@ "allowQueueModify": true, "lookAheadPieces": 3, "raceTarget": 500, - "requiredAltitude": 1650, - "allspin": true, - "allspinminis": true, - "history": true, - "competitiveMode": false, - "sidebar": ["time", "apm", "pps"], - "stride": false + "requiredAltitude": 1650 }, "control": { "rightKey": "ArrowRight", diff --git a/src/data/gamemodes.json b/src/data/gamemodes.json index edb901a..3be3c76 100644 --- a/src/data/gamemodes.json +++ b/src/data/gamemodes.json @@ -1,146 +1,147 @@ -{ - "*": { - "settings": { - "gravitySpeed": 950, - "lockDelay": 600, - "maxLockMovements": 15, - "nextPieces": 5, - "allowLockout": false, - "preserveARR": true, - "allowHold": true, - "infiniteHold": false, - "allowQueueModify": false, - "allspin": false, - "allspinminis": false, - "history": false, - "sidebar": ["time", "apm", "pps"], - "stride": false - }, - "displayName": "Unset", - "objectiveText": "", - "goalStat": "", - "target": "", - "result": "", - "music": "", - "compmusic": "", - "startBoard": "", - "effects": [] - }, - "custom": { - "displayName": "Zen / Custom" - }, - "sprint": { - "displayName": "Sprint", - "objectiveText": "Lines", - "goalStat": "clearlines", - "target": "requiredLines", - "result": "time", - "settings": { - "requiredLines": 40, - "stride":true - } - }, - "ultra": { - "displayName": "Ultra", - "objectiveText": "Score", - "goalStat": "time", - "target": "timeLimit", - "result": "score", - "settings": { - "timeLimit": 120, - "sidebar": ["time", "score", "pps"] - } - }, - "attacker": { - "displayName": "Attacker", - "objectiveText": "Damage", - "goalStat": "attack", - "target": "requiredAttack", - "result": "time", - "settings": { - "requiredAttack": 100 - } - }, - "digger": { - "displayName": "Digger", - "objectiveText": "Remaining", - "goalStat": "cleargarbage", - "target": "requiredGarbage", - "result": "time", - "settings": { - "requiredGarbage": 100, - "sidebar": ["time", "dss", "pps"] - } - }, - "survival": { - "displayName": "Survival", - "objectiveText": "received", - "goalStat": "clearlines", - "target": "gameEnd", - "result": "time", - "settings": { - "survivalRate": 60, - "sidebar": ["time", "lpm", "pps"] - } - }, - "backfire": { - "displayName": "Backfire", - "objectiveText": "Sent", - "goalStat": "attack", - "target": "requiredAttack", - "result": "time", - "settings": { - "requiredAttack": 100, - "backfireMulti": 1 - } - }, - "combo": { - "displayName": "4w / Combo", - "objectiveText": "Time", - "goalStat": "time", - "target": "combobreak", - "result": "maxCombo", - "settings": { - "allspin": true, - "allspinminis": false, - "sidebar": ["combo", "pps"] - } - }, - "lookahead": { - "displayName": "Lookahead", - "objectiveText": "Lines", - "goalStat": "clearlines", - "target": "requiredLines", - "result": "time", - "settings": { - "requiredLines": 40, - "lookAheadPieces": 3, - "gravitySpeed": 1001, - "sidebar": ["time", "kpp", "pps"] - } - }, - "race": { - "displayName": "Race", - "objectiveText": "Level", - "goalStat": "level", - "target": "raceTarget", - "result": "time", - "settings": { - "raceTarget": 500, - "gravitySpeed": 0 - } - }, - "zenith": { - "displayName": "Climb", - "objectiveText": "Altitude", - "goalStat": "altitude", - "target": "requiredAltitude", - "result": "time", - "settings": { - "requiredAltitude": 1650, - "gravitySpeed": 950, - "allspin": true, - "allspinminis": true - } - } -} \ No newline at end of file +{ + "*": { + "settings": { + "gravitySpeed": 950, + "lockDelay": 600, + "maxLockMovements": 15, + "nextPieces": 5, + "allowLockout": false, + "preserveARR": true, + "allowHold": true, + "infiniteHold": false, + "allowQueueModify": false, + "allspin": false, + "allspinminis": false, + "history": false, + "sidebar": ["time", "apm", "pps"], + "stride": false + }, + "displayName": "Unset", + "objectiveText": "", + "goalStat": "", + "target": "", + "result": "", + "music": "", + "compmusic": "", + "startBoard": "", + "effects": [] + }, + "custom": { + "displayName": "Zen / Custom" + }, + "sprint": { + "displayName": "Sprint", + "objectiveText": "Lines", + "goalStat": "clearlines", + "target": "requiredLines", + "result": "time", + "settings": { + "requiredLines": 40, + "stride":true + } + }, + "ultra": { + "displayName": "Ultra", + "objectiveText": "Score", + "goalStat": "time", + "target": "timeLimit", + "result": "score", + "settings": { + "timeLimit": 120, + "sidebar": ["time", "score", "pps"] + } + }, + "attacker": { + "displayName": "Attacker", + "objectiveText": "Damage", + "goalStat": "attack", + "target": "requiredAttack", + "result": "time", + "settings": { + "requiredAttack": 100 + } + }, + "digger": { + "displayName": "Digger", + "objectiveText": "Remaining", + "goalStat": "cleargarbage", + "target": "requiredGarbage", + "result": "time", + "settings": { + "requiredGarbage": 100, + "sidebar": ["time", "dss", "pps"] + } + }, + "survival": { + "displayName": "Survival", + "objectiveText": "received", + "goalStat": "clearlines", + "target": "gameEnd", + "result": "time", + "settings": { + "survivalRate": 60, + "sidebar": ["time", "lpm", "pps"] + } + }, + "backfire": { + "displayName": "Backfire", + "objectiveText": "Sent", + "goalStat": "attack", + "target": "requiredAttack", + "result": "time", + "settings": { + "requiredAttack": 100, + "backfireMulti": 1 + } + }, + "combo": { + "displayName": "4w / Combo", + "objectiveText": "Time", + "goalStat": "time", + "target": "combobreak", + "result": "maxCombo", + "settings": { + "allspin": true, + "allspinminis": false, + "sidebar": ["combo", "pps"] + } + }, + "lookahead": { + "displayName": "Lookahead", + "objectiveText": "Lines", + "goalStat": "clearlines", + "target": "requiredLines", + "result": "time", + "settings": { + "requiredLines": 40, + "lookAheadPieces": 3, + "gravitySpeed": 1001, + "sidebar": ["time", "kpp", "pps"] + } + }, + "race": { + "displayName": "Race", + "objectiveText": "Level", + "goalStat": "tgm_level", + "target": "raceTarget", + "result": "grade", + "settings": { + "raceTarget": 999, + "gravitySpeed": 0, + "clearDelay": 420 + } + }, + "zenith": { + "displayName": "Climb", + "objectiveText": "Altitude", + "goalStat": "altitude", + "target": "requiredAltitude", + "result": "time", + "settings": { + "requiredAltitude": 1650, + "gravitySpeed": 950, + "allspin": true, + "allspinminis": true + } + } +} diff --git a/src/data/kicks.js b/src/data/kicks.js index 79e2f33..b56e122 100644 --- a/src/data/kicks.js +++ b/src/data/kicks.js @@ -1,25 +1,25 @@ -export const KickData = [[ // srs+ (tetrio) - [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]], // 4 -> 1, 1 is north, ccw is 1 -> 4, ccw is * -1 - [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]], // 1 -> 2 - [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]], // 2 -> 3 - [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]] // 3 -> 4 -], [ // I piece kicks - [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]], // 4 -> 1 - [[0, 0], [1, 0], [-2, 0], [1, 2], [-2, -1]], // 1 -> 2 - [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]], // 2 -> 3 - [[0, 0], [-1, 0], [2, 0], [-1, -2], [2, 1]] // 3 -> 4 -]]; - -export const KickData180 = [[ - [[0, 0], [0, -1], [-1, -1], [1, -1], [-1, 0], [1, 0]], // 3 -> 1 - [[0, 0], [-1, 0], [-1, 2], [-1, 1], [0, 2], [0, 1]], // 4 -> 2 - [[0, 0], [0, 1], [1, 1], [-1, 1], [1, 0], [-1, 0]], // 1 -> 3 - [[0, 0], [1, 0], [1, 2], [1, 1], [0, 2], [0, 1]] // 2 -> 4 -], [ // I piece kicks - [[0, 0], [0, -1]], // 3 -> 1 - [[0, 0], [-1, 0]], // 4 -> 2 - [[0, 0], [0, 1]], // 1 -> 3 - [[0, 0], [1, 0]], // 2 -> 4 -]]; - +export const KickData = [[ // srs+ (tetrio) + [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]], // 4 -> 1, 1 is north, ccw is 1 -> 4, ccw is * -1 + [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]], // 1 -> 2 + [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]], // 2 -> 3 + [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]] // 3 -> 4 +], [ // I piece kicks + [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]], // 4 -> 1 + [[0, 0], [1, 0], [-2, 0], [1, 2], [-2, -1]], // 1 -> 2 + [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]], // 2 -> 3 + [[0, 0], [-1, 0], [2, 0], [-1, -2], [2, 1]] // 3 -> 4 +]]; + +export const KickData180 = [[ + [[0, 0], [0, -1], [-1, -1], [1, -1], [-1, 0], [1, 0]], // 3 -> 1 + [[0, 0], [-1, 0], [-1, 2], [-1, 1], [0, 2], [0, 1]], // 4 -> 2 + [[0, 0], [0, 1], [1, 1], [-1, 1], [1, 0], [-1, 0]], // 1 -> 3 + [[0, 0], [1, 0], [1, 2], [1, 1], [0, 2], [0, 1]] // 2 -> 4 +], [ // I piece kicks + [[0, 0], [0, -1]], // 3 -> 1 + [[0, 0], [-1, 0]], // 4 -> 2 + [[0, 0], [0, 1]], // 1 -> 3 + [[0, 0], [1, 0]], // 2 -> 4 +]]; + // json has no comments :( \ No newline at end of file diff --git a/src/data/pieces.json b/src/data/pieces.json index d908b82..f4af680 100644 --- a/src/data/pieces.json +++ b/src/data/pieces.json @@ -1,47 +1,47 @@ -[{ - "name": "z", - "shape1": [[1, 1, 0], [0, 1, 1], [0, 0, 0]], - "shape2": [[0, 0, 1], [0, 1, 1], [0, 1, 0]], - "shape3": [[0, 0, 0], [1, 1, 0], [0, 1, 1]], - "shape4": [[0, 1, 0], [1, 1, 0], [1, 0, 0]], - "colour": "#D83A28" -}, { - "name": "s", - "shape1": [[0, 1, 1], [1, 1, 0], [0, 0, 0]], - "shape2": [[0, 1, 0], [0, 1, 1], [0, 0, 1]], - "shape3": [[0, 0, 0], [0, 1, 1], [1, 1, 0]], - "shape4": [[1, 0, 0], [1, 1, 0], [0, 1, 0]], - "colour": "#7ACD44" -}, { - "name": "o", - "shape1": [[1, 1], [1, 1]], - "colour": "#F2D74C" -}, { - "name": "t", - "shape1": [[0, 1, 0], [1, 1, 1], [0, 0, 0]], - "shape2": [[0, 1, 0], [0, 1, 1], [0, 1, 0]], - "shape3": [[0, 0, 0], [1, 1, 1], [0, 1, 0]], - "shape4": [[0, 1, 0], [1, 1, 0], [0, 1, 0]], - "colour": "#C132D0" -}, { - "name": "j", - "shape1": [[1, 0, 0], [1, 1, 1], [0, 0, 0]], - "shape2": [[0, 1, 1], [0, 1, 0], [0, 1, 0]], - "shape3": [[0, 0, 0], [1, 1, 1], [0, 0, 1]], - "shape4": [[0, 1, 0], [0, 1, 0], [1, 1, 0]], - "colour": "#3358DD" -}, { - "name": "l", - "shape1": [[0, 0, 1], [1, 1, 1], [0, 0, 0]], - "shape2": [[0, 1, 0], [0, 1, 0], [0, 1, 1]], - "shape3": [[0, 0, 0], [1, 1, 1], [1, 0, 0]], - "shape4": [[1, 1, 0], [0, 1, 0], [0, 1, 0]], - "colour": "#EDA93F" -}, { - "name": "i", - "shape1": [[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]], - "shape2": [[0, 0, 1, 0], [0, 0, 1, 0], [0, 0, 1, 0], [0, 0, 1, 0]], - "shape3": [[0, 0, 0, 0], [0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0]], - "shape4": [[0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0]], - "colour": "#65DBC8" +[{ + "name": "z", + "shape1": [[1, 1, 0], [0, 1, 1], [0, 0, 0]], + "shape2": [[0, 0, 1], [0, 1, 1], [0, 1, 0]], + "shape3": [[0, 0, 0], [1, 1, 0], [0, 1, 1]], + "shape4": [[0, 1, 0], [1, 1, 0], [1, 0, 0]], + "colour": "#D83A28" +}, { + "name": "s", + "shape1": [[0, 1, 1], [1, 1, 0], [0, 0, 0]], + "shape2": [[0, 1, 0], [0, 1, 1], [0, 0, 1]], + "shape3": [[0, 0, 0], [0, 1, 1], [1, 1, 0]], + "shape4": [[1, 0, 0], [1, 1, 0], [0, 1, 0]], + "colour": "#7ACD44" +}, { + "name": "o", + "shape1": [[1, 1], [1, 1]], + "colour": "#F2D74C" +}, { + "name": "t", + "shape1": [[0, 1, 0], [1, 1, 1], [0, 0, 0]], + "shape2": [[0, 1, 0], [0, 1, 1], [0, 1, 0]], + "shape3": [[0, 0, 0], [1, 1, 1], [0, 1, 0]], + "shape4": [[0, 1, 0], [1, 1, 0], [0, 1, 0]], + "colour": "#C132D0" +}, { + "name": "j", + "shape1": [[1, 0, 0], [1, 1, 1], [0, 0, 0]], + "shape2": [[0, 1, 1], [0, 1, 0], [0, 1, 0]], + "shape3": [[0, 0, 0], [1, 1, 1], [0, 0, 1]], + "shape4": [[0, 1, 0], [0, 1, 0], [1, 1, 0]], + "colour": "#3358DD" +}, { + "name": "l", + "shape1": [[0, 0, 1], [1, 1, 1], [0, 0, 0]], + "shape2": [[0, 1, 0], [0, 1, 0], [0, 1, 1]], + "shape3": [[0, 0, 0], [1, 1, 1], [1, 0, 0]], + "shape4": [[1, 1, 0], [0, 1, 0], [0, 1, 0]], + "colour": "#EDA93F" +}, { + "name": "i", + "shape1": [[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]], + "shape2": [[0, 0, 1, 0], [0, 0, 1, 0], [0, 0, 1, 0], [0, 0, 1, 0]], + "shape3": [[0, 0, 0, 0], [0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0]], + "shape4": [[0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0]], + "colour": "#65DBC8" }] \ No newline at end of file diff --git a/src/data/sfxlist.json b/src/data/sfxlist.json index f7bfecf..af07c37 100644 --- a/src/data/sfxlist.json +++ b/src/data/sfxlist.json @@ -1,194 +1,194 @@ -[ - { - "name": "allclear.mp3", - "path": "assets/sfx/allclear.mp3" - }, - { - "name": "btb_1.mp3", - "path": "assets/sfx/btb_1.mp3" - }, - { - "name": "clearbtb.mp3", - "path": "assets/sfx/clearbtb.mp3" - }, - { - "name": "clearline.mp3", - "path": "assets/sfx/clearline.mp3" - }, - { - "name": "clearquad.mp3", - "path": "assets/sfx/clearquad.mp3" - }, - { - "name": "clearspin.mp3", - "path": "assets/sfx/clearspin.mp3" - }, - { - "name": "damage_alert.mp3", - "path": "assets/sfx/damage_alert.mp3" - }, - { - "name": "failure.mp3", - "path": "assets/sfx/failure.mp3" - }, - { - "name": "finish.mp3", - "path": "assets/sfx/finish.mp3" - }, - { - "name": "garbage_in_large.mp3", - "path": "assets/sfx/garbage_in_large.mp3" - }, - { - "name": "garbage_in_small.mp3", - "path": "assets/sfx/garbage_in_small.mp3" - }, - { - "name": "harddrop.mp3", - "path": "assets/sfx/harddrop.mp3" - }, - { - "name": "hold.mp3", - "path": "assets/sfx/hold.mp3" - }, - { - "name": "menutap.mp3", - "path": "assets/sfx/menutap.mp3" - }, - { - "name": "menuclick.mp3", - "path": "assets/sfx/menuclick.mp3" - }, - { - "name": "move.mp3", - "path": "assets/sfx/move.mp3" - }, - { - "name": "retry.mp3", - "path": "assets/sfx/retry.mp3" - }, - { - "name": "rotate.mp3", - "path": "assets/sfx/rotate.mp3" - }, - { - "name": "spin.mp3", - "path": "assets/sfx/spin.mp3" - }, - { - "name": "thunder.mp3", - "path": "assets/sfx/thunder.mp3" - }, - { - "name": "topout.mp3", - "path": "assets/sfx/topout.mp3" - }, - { - "name": "undo.mp3", - "path": "assets/sfx/undo.mp3" - }, - { - "name": "redo.mp3", - "path": "assets/sfx/redo.mp3" - }, - { - "name": "personalbest.mp3", - "path": "assets/sfx/personalbest.mp3" - }, - { - "name": "pbstart.mp3", - "path": "assets/sfx/pbstart.mp3" - }, - { - "name": "pbend.mp3", - "path": "assets/sfx/pbend.mp3" - }, - { - "name": "speed_up.mp3", - "path": "assets/sfx/speed_up.mp3", - "type": "file" - }, - { - "name": "speed_down.mp3", - "path": "assets/sfx/speed_down.mp3", - "type": "file" - }, - { - "name": "levelup.mp3", - "path": "assets/sfx/levelup.mp3", - "type": "file" - }, - { - "name": "zenith_levelup.mp3", - "path": "assets/sfx/zenith_levelup.mp3", - "type": "file" - }, - { - "name": "combo_1.mp3", - "path": "assets/sfx/combo/combo_1.mp3" - }, - { - "name": "combo_10.mp3", - "path": "assets/sfx/combo/combo_10.mp3" - }, - { - "name": "combo_11.mp3", - "path": "assets/sfx/combo/combo_11.mp3" - }, - { - "name": "combo_12.mp3", - "path": "assets/sfx/combo/combo_12.mp3" - }, - { - "name": "combo_13.mp3", - "path": "assets/sfx/combo/combo_13.mp3" - }, - { - "name": "combo_14.mp3", - "path": "assets/sfx/combo/combo_14.mp3" - }, - { - "name": "combo_15.mp3", - "path": "assets/sfx/combo/combo_15.mp3" - }, - { - "name": "combo_16.mp3", - "path": "assets/sfx/combo/combo_16.mp3" - }, - { - "name": "combo_2.mp3", - "path": "assets/sfx/combo/combo_2.mp3" - }, - { - "name": "combo_3.mp3", - "path": "assets/sfx/combo/combo_3.mp3" - }, - { - "name": "combo_4.mp3", - "path": "assets/sfx/combo/combo_4.mp3" - }, - { - "name": "combo_5.mp3", - "path": "assets/sfx/combo/combo_5.mp3" - }, - { - "name": "combo_6.mp3", - "path": "assets/sfx/combo/combo_6.mp3" - }, - { - "name": "combo_7.mp3", - "path": "assets/sfx/combo/combo_7.mp3" - }, - { - "name": "combo_8.mp3", - "path": "assets/sfx/combo/combo_8.mp3" - }, - { - "name": "combo_9.mp3", - "path": "assets/sfx/combo/combo_9.mp3" - }, - { - "name": "hyperalert", - "path": "assets/sfx/hyperalert.mp3" - } +[ + { + "name": "allclear.mp3", + "path": "assets/sfx/allclear.mp3" + }, + { + "name": "btb_1.mp3", + "path": "assets/sfx/btb_1.mp3" + }, + { + "name": "clearbtb.mp3", + "path": "assets/sfx/clearbtb.mp3" + }, + { + "name": "clearline.mp3", + "path": "assets/sfx/clearline.mp3" + }, + { + "name": "clearquad.mp3", + "path": "assets/sfx/clearquad.mp3" + }, + { + "name": "clearspin.mp3", + "path": "assets/sfx/clearspin.mp3" + }, + { + "name": "damage_alert.mp3", + "path": "assets/sfx/damage_alert.mp3" + }, + { + "name": "failure.mp3", + "path": "assets/sfx/failure.mp3" + }, + { + "name": "finish.mp3", + "path": "assets/sfx/finish.mp3" + }, + { + "name": "garbage_in_large.mp3", + "path": "assets/sfx/garbage_in_large.mp3" + }, + { + "name": "garbage_in_small.mp3", + "path": "assets/sfx/garbage_in_small.mp3" + }, + { + "name": "harddrop.mp3", + "path": "assets/sfx/harddrop.mp3" + }, + { + "name": "hold.mp3", + "path": "assets/sfx/hold.mp3" + }, + { + "name": "menutap.mp3", + "path": "assets/sfx/menutap.mp3" + }, + { + "name": "menuclick.mp3", + "path": "assets/sfx/menuclick.mp3" + }, + { + "name": "move.mp3", + "path": "assets/sfx/move.mp3" + }, + { + "name": "retry.mp3", + "path": "assets/sfx/retry.mp3" + }, + { + "name": "rotate.mp3", + "path": "assets/sfx/rotate.mp3" + }, + { + "name": "spin.mp3", + "path": "assets/sfx/spin.mp3" + }, + { + "name": "thunder.mp3", + "path": "assets/sfx/thunder.mp3" + }, + { + "name": "topout.mp3", + "path": "assets/sfx/topout.mp3" + }, + { + "name": "undo.mp3", + "path": "assets/sfx/undo.mp3" + }, + { + "name": "redo.mp3", + "path": "assets/sfx/redo.mp3" + }, + { + "name": "personalbest.mp3", + "path": "assets/sfx/personalbest.mp3" + }, + { + "name": "pbstart.mp3", + "path": "assets/sfx/pbstart.mp3" + }, + { + "name": "pbend.mp3", + "path": "assets/sfx/pbend.mp3" + }, + { + "name": "speed_up.mp3", + "path": "assets/sfx/speed_up.mp3", + "type": "file" + }, + { + "name": "speed_down.mp3", + "path": "assets/sfx/speed_down.mp3", + "type": "file" + }, + { + "name": "levelup.mp3", + "path": "assets/sfx/levelup.mp3", + "type": "file" + }, + { + "name": "zenith_levelup.mp3", + "path": "assets/sfx/zenith_levelup.mp3", + "type": "file" + }, + { + "name": "combo_1.mp3", + "path": "assets/sfx/combo/combo_1.mp3" + }, + { + "name": "combo_10.mp3", + "path": "assets/sfx/combo/combo_10.mp3" + }, + { + "name": "combo_11.mp3", + "path": "assets/sfx/combo/combo_11.mp3" + }, + { + "name": "combo_12.mp3", + "path": "assets/sfx/combo/combo_12.mp3" + }, + { + "name": "combo_13.mp3", + "path": "assets/sfx/combo/combo_13.mp3" + }, + { + "name": "combo_14.mp3", + "path": "assets/sfx/combo/combo_14.mp3" + }, + { + "name": "combo_15.mp3", + "path": "assets/sfx/combo/combo_15.mp3" + }, + { + "name": "combo_16.mp3", + "path": "assets/sfx/combo/combo_16.mp3" + }, + { + "name": "combo_2.mp3", + "path": "assets/sfx/combo/combo_2.mp3" + }, + { + "name": "combo_3.mp3", + "path": "assets/sfx/combo/combo_3.mp3" + }, + { + "name": "combo_4.mp3", + "path": "assets/sfx/combo/combo_4.mp3" + }, + { + "name": "combo_5.mp3", + "path": "assets/sfx/combo/combo_5.mp3" + }, + { + "name": "combo_6.mp3", + "path": "assets/sfx/combo/combo_6.mp3" + }, + { + "name": "combo_7.mp3", + "path": "assets/sfx/combo/combo_7.mp3" + }, + { + "name": "combo_8.mp3", + "path": "assets/sfx/combo/combo_8.mp3" + }, + { + "name": "combo_9.mp3", + "path": "assets/sfx/combo/combo_9.mp3" + }, + { + "name": "hyperalert", + "path": "assets/sfx/hyperalert.mp3" + } ] \ No newline at end of file diff --git a/src/display/boardEffects.js b/src/display/boardEffects.js index e11887a..72ad0de 100644 --- a/src/display/boardEffects.js +++ b/src/display/boardEffects.js @@ -1,112 +1,112 @@ -import { pbTrackingStat } from "../data/data.js"; -import { Game } from "../game.js"; - -export class BoardEffects { - X = 0; - Y = 0; - dX = 0; - dY = 0; - friction = 0.75; - springConstant = 0.02; - targetX = 0; - targetY = 0; - R = 0; - dR = 0; - targetR = 0; - - hasPace = true; - paceCooldown = 0; - - divBoard = document.getElementById("board"); - divDanger = document.getElementById("dangerOverlay"); - border = document.getElementById('backborder') - backboard = document.getElementById('backboard') - - /** - * - * @param {Game} game - */ - constructor(game) { - this.game = game; - } - - move(forceX, forceY) { - this.dX += forceX; - this.dY += forceY; - - const newdx = this.targetX - this.X; - const newdy = this.targetY - this.Y; - const fX = newdx * this.springConstant; - const fY = newdy * this.springConstant; - - this.dX += fX; - this.dY += fY; - this.dX *= this.friction; - this.dY *= this.friction; - this.X += this.dX; - this.Y += this.dY; - - this.X = this.clamp(this.X, 0.5); - this.Y = this.clamp(this.Y, 0.5); - - if (this.X != 0 || this.Y != 0) { - this.divBoard.style.translate = `${this.X}px ${this.Y}px` - } - } - - rotate(torque) { - this.dR += torque; - let newangle = this.targetR - this.R; - const fangle = newangle * this.springConstant; - - this.dR += fangle; - this.dR *= this.friction; - this.R += this.dR; - this.R = this.clamp(this.R, 0.1); - - if (this.R != 0) { - this.divBoard.style.rotate = `${this.R}deg` - } - } - - clamp(num, min) { - if (num < min && num > -min) return 0; - return num - } - - rainbowBoard() { - const stats = this.game.stats; - const pbs = this.game.profilestats.personalBests; - const gamemode = this.game.settings.game.gamemode; - - if (!this.game.settings.display.rainbowPB || - !this.game.settings.game.competitiveMode || - stats.time < 0.5 || pbs[gamemode] == undefined) return; - if (this.paceCooldown > 0) { this.paceCooldown--; return; } - - const trackingStat = pbTrackingStat[this.game.modes.modeJSON.goalStat]; - const current = stats[trackingStat]; - const pbpace = pbs[gamemode].pbstats[trackingStat]; - if (current < pbpace && this.hasPace) { - this.game.sounds.playSound("pbend"); - this.toggleRainbow(false); - } else if (current >= pbpace && !this.hasPace) { - this.game.sounds.playSound("pbstart"); - this.toggleRainbow(true); - } - - this.paceCooldown = this.game.tickrate * 3; - } - - toggleRainbow(pace) { - this.border.style.setProperty('--blur-size', pace ? `0.3vmin` : `0vmin`) - this.border.style.setProperty('--blur-strength', pace ? '0.7vmin' : '0') - this.backboard.style.setProperty('--blur-strength', pace ? '0.5vmin' : '0') - this.hasPace = pace; - } - - toggleDangerBoard(inDanger) { - this.border.classList.toggle("boardDanger", inDanger); - this.divDanger.style.opacity = inDanger ? "0.1" : "0"; - } +import { pbTrackingStat } from "../data/data.js"; +import { Game } from "../game.js"; + +export class BoardEffects { + X = 0; + Y = 0; + dX = 0; + dY = 0; + friction = 0.75; + springConstant = 0.02; + targetX = 0; + targetY = 0; + R = 0; + dR = 0; + targetR = 0; + + hasPace = true; + paceCooldown = 0; + + divBoard = document.getElementById("board"); + divDanger = document.getElementById("dangerOverlay"); + border = document.getElementById('backborder') + backboard = document.getElementById('backboard') + + /** + * + * @param {Game} game + */ + constructor(game) { + this.game = game; + } + + move(forceX, forceY) { + this.dX += forceX; + this.dY += forceY; + + const newdx = this.targetX - this.X; + const newdy = this.targetY - this.Y; + const fX = newdx * this.springConstant; + const fY = newdy * this.springConstant; + + this.dX += fX; + this.dY += fY; + this.dX *= this.friction; + this.dY *= this.friction; + this.X += this.dX; + this.Y += this.dY; + + this.X = this.clamp(this.X, 0.5); + this.Y = this.clamp(this.Y, 0.5); + + if (this.X != 0 || this.Y != 0) { + this.divBoard.style.translate = `${this.X}px ${this.Y}px` + } + } + + rotate(torque) { + this.dR += torque; + let newangle = this.targetR - this.R; + const fangle = newangle * this.springConstant; + + this.dR += fangle; + this.dR *= this.friction; + this.R += this.dR; + this.R = this.clamp(this.R, 0.1); + + if (this.R != 0) { + this.divBoard.style.rotate = `${this.R}deg` + } + } + + clamp(num, min) { + if (num < min && num > -min) return 0; + return num + } + + rainbowBoard() { + const stats = this.game.stats; + const pbs = this.game.profilestats.personalBests; + const gamemode = this.game.settings.game.gamemode; + + if (!this.game.settings.display.rainbowPB || + !this.game.settings.game.competitiveMode || + stats.time < 0.5 || pbs[gamemode] == undefined) return; + if (this.paceCooldown > 0) { this.paceCooldown--; return; } + + const trackingStat = pbTrackingStat[this.game.modes.modeJSON.goalStat]; + const current = stats[trackingStat]; + const pbpace = pbs[gamemode].pbstats[trackingStat]; + if (current < pbpace && this.hasPace) { + this.game.sounds.playSound("pbend"); + this.toggleRainbow(false); + } else if (current >= pbpace && !this.hasPace) { + this.game.sounds.playSound("pbstart"); + this.toggleRainbow(true); + } + + this.paceCooldown = this.game.tickrate * 3; + } + + toggleRainbow(pace) { + this.border.style.setProperty('--blur-size', pace ? `0.3vmin` : `0vmin`) + this.border.style.setProperty('--blur-strength', pace ? '0.7vmin' : '0') + this.backboard.style.setProperty('--blur-strength', pace ? '0.5vmin' : '0') + this.hasPace = pace; + } + + toggleDangerBoard(inDanger) { + this.border.classList.toggle("boardDanger", inDanger); + this.divDanger.style.opacity = inDanger ? "0.1" : "0"; + } } \ No newline at end of file diff --git a/src/display/particles.js b/src/display/particles.js index f87b165..f2d3f87 100644 --- a/src/display/particles.js +++ b/src/display/particles.js @@ -1,227 +1,227 @@ -import { Game } from "../game.js"; - -class Point { - constructor(particleInfo, ctx) { - const { x, y, colour, size, life, dx, dy, sway, xF, yF, swayF, gravity, twinkle, twinkleTime } = particleInfo; - this.x = x; - this.y = y; - this.size = size; - this.colour = colour; - this.maxLife = life; - this.life = life; - this.dx = dx; - this.dy = dy; - this.sway = sway ?? 0; - this.frictionX = xF ?? 1; - this.frictionY = yF ?? 1; - this.frictionSway = swayF ?? 1; - this.gravity = gravity ?? 0; - this.twinkle = twinkle ?? false; - this.twinkleTime = twinkleTime ?? this.life; - - this.ctx = ctx; - } - - draw() { - this.ctx.globalAlpha = Math.max(0, this.life / this.maxLife); - this.ctx.fillStyle = this.colour; - this.ctx.beginPath(); - this.ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); - this.ctx.fill(); - } - - update() { - this.life -= 1; - this.x += this.dx + Math.sin(this.sway * this.life); - this.y += this.dy; - this.dx *= this.frictionX; - this.dy *= this.frictionY; - this.sway *= this.frictionSway - this.dy += this.gravity; - if (this.twinkle && this.life < this.twinkleTime) this.size = Math.abs((Math.sin(this.life / 15))) * 2; - } -} - -export class Particles { - particles = []; - - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - this.ctx = this.game.renderer.ctx; - } - - initBoard() { - this.boardWidth = this.game.renderer.boardWidth; - this.boardHeight = this.game.renderer.boardHeight; - this.minosize = this.game.boardrender.minoSize; - } - - spawnParticles(posX, posY, type, pieceWidth = 1, cw = false, colour = "white") { - if (!this.game.settings.display.particles) return - this.volume = this.game.settings.display.particleVolume; - this.size = this.game.settings.display.particleSize; - const [x, y] = [posX * this.minosize, (40 - posY) * this.minosize]; - if (type == "drop") this.creatDropParticles(x, y, colour, this.minosize * pieceWidth, -this.boardHeight); - if (type == "lock") this.createLockParticles(x, y, colour, this.minosize * pieceWidth, 10); - if (type == "clear") this.createClearParticles(x, y, colour, this.boardWidth, -10); - if (type == "pc") this.createPCParticles(x, y, this.boardWidth, 10); - if (type == "dangerboard") this.createDangerBoardParticles(x, this.boardHeight, colour, this.boardWidth, 10); - if (type == "dangersides") this.createDangerSidesParticles(x, y, "red", this.boardWidth, 0, 1); - if (type == "spin") this.createSpinParticles(x, y, colour, cw, this.minosize * pieceWidth, -this.minosize * pieceWidth); - if (type == "spike") this.createSpikeParticles(x, this.boardHeight, colour, this.boardWidth, -this.boardHeight); - if (type == "BTB") this.createBTBParticle(x, y, "gold", this.boardWidth, 0, this.boardHeight); - } - - creatDropParticles(x, y, colour, len, height) { - for (let i = 0; i < this.volume / 3; i++) { - const posX = x + Math.random() * len - len / 2; - const posY = y + Math.random() * height / 2; - const life = Math.random() * 35 + 70; - const dx = Math.random() * 1 - 0.5; - const dy = Math.random() * -1.2 - 2.4; - const sway = Math.random() * 0.04 - 0.02; - - const placeParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, sway, xF: 0.95, yF: 0.95, swayF: 0.96 } - const particle = new Point(placeParticle, this.ctx); - this.particles.push(particle); - } - } - - createLockParticles(x, y, colour, len, height) { - for (let i = 0; i < this.volume / 4; i++) { - const posX = x + Math.random() * len - len / 2; - const posY = y + Math.random() * height; - const life = Math.random() * 15 + 30; - const dx = Math.random() * 1 - 0.5 + (posX - x) / 50; - const dy = Math.random() * -0.7 - 1.4; - - const clearParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, xF: 0.96, yF: 0.96, gravity: 0.05 } - const particle = new Point(clearParticle, this.ctx); - this.particles.push(particle); - } - } - - createClearParticles(x, y, colour, len, height) { - for (let i = 0; i < this.volume; i++) { - const posX = x + Math.random() * len - const posY = y + Math.random() * height; - const life = Math.random() * 20 + 40; - const dx = Math.random() * 1.5 - 0.75 + (posX - x) / 200; - const dy = Math.random() * -0.8 - 1.5; - - const clearParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, yF: 0.99, gravity: 0.1 } - const particle = new Point(clearParticle, this.ctx); - this.particles.push(particle); - } - } - - createPCParticles(x, y, len, height) { - for (let i = 0; i < this.volume; i++) { - const posX = x + Math.random() * len - const posY = y + Math.random() * height; - const life = Math.random() * 100 + 200; - const dx = Math.random() * 1.5 - 0.75; - const dy = Math.random() * -8 - 2; - const colour = `hsl(${Math.random() * 360}, 80%, 60%)` - - const pcParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, xF: 0.98, yF: 0.98, twinkle: true, twinkleTime: 130 } - const particle = new Point(pcParticle, this.ctx); - this.particles.push(particle); - } - } - - createDangerBoardParticles(x, y, colour, len, height) { - for (let i = 0; i < this.volume / 10; i++) { - if (Math.random() > this.volume / 500) continue - const posX = x + Math.random() * len - const posY = y + Math.random() * height / 2; - const life = Math.random() * 100 + 200; - const dx = Math.random() * 1 - 0.5; - const dy = Math.random() * -1.2 - 2.4; - const sway = Math.random() * 0.005 - 0.0025; - - const dangerParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, sway, swayF: 0.98 } - const particle = new Point(dangerParticle, this.ctx); - this.particles.push(particle); - } - } - - createDangerSidesParticles(x, y, colour, width, len, height) { - for (let i = 0; i < this.volume / 10; i++) { - if (Math.random() > this.volume / 250) continue - const direction = Math.random() > 0.5; - - const posX = (direction ? 0 : width) + x + Math.random() * len - const posY = y + Math.random() * height; - const life = Math.random() * 15 + 30; - const dx = (direction ? 1 : -1) * (Math.random() * 2 + 2); - const dy = Math.random() * 2 - 1; - - const dangerSideParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, gravity: 0.05 } - const particle = new Point(dangerSideParticle, this.ctx); - this.particles.push(particle); - } - } - - createSpinParticles(x, y, colour, cw, len, height) { - len *= 0.5; - height *= 0.5; - for (let i = 0; i < this.volume / 3; i++) { - const posX = x + Math.random() * len - len / 2 - const posY = y + Math.random() * height - height / 2; - const life = Math.random() * 35 + 70; - let dx = Math.random() * 1 - 0.5 + (posY - y) / 30; - let dy = Math.random() * 1 - 0.5 + (posX - x) / 30; - if (cw) { dx *= -1 } else { dy *= -1 } - - const spinParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, xF: 0.98, yF: 0.98 } - const particle = new Point(spinParticle, this.ctx); - this.particles.push(particle); - } - } - - createSpikeParticles(x, y, colour, len, height) { - for (let i = 0; i < this.volume / 2; i++) { - const posX = x + Math.random() * len - const posY = y + Math.random() * height / 2; - const life = Math.random() * 35 + 70; - const dx = Math.random() * 1 - 0.5; - const dy = Math.random() * 1 - 0.5; - - const spikeParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, xF: 0.96, yF: 0.96, twinkle: true } - const particle = new Point(spikeParticle, this.ctx); - this.particles.push(particle); - } - } - - createBTBParticle(x, y, colour, width, len, height) { - for (let i = 0; i < this.volume * 2; i++) { - const direction = Math.random() > 0.5; - - const posX = (direction ? 0 : width) + x + Math.random() * len - const posY = y + Math.random() * height; - const life = Math.random() * 25 + 50; - const dx = (direction ? 1 : -1) * (Math.random() * 2); - const dy = Math.random() * 2 - 1; - - const BTBParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, gravity: 0.15 } - const particle = new Point(BTBParticle, this.ctx); - this.particles.push(particle); - } - } - - clearParticles() { - this.particles = []; - } - - update() { - this.particles = this.particles.filter(p => p.life > 0); - this.particles.forEach(particle => { - particle.update(); - particle.draw(); - }); - } +import { Game } from "../game.js"; + +class Point { + constructor(particleInfo, ctx) { + const { x, y, colour, size, life, dx, dy, sway, xF, yF, swayF, gravity, twinkle, twinkleTime } = particleInfo; + this.x = x; + this.y = y; + this.size = size; + this.colour = colour; + this.maxLife = life; + this.life = life; + this.dx = dx; + this.dy = dy; + this.sway = sway ?? 0; + this.frictionX = xF ?? 1; + this.frictionY = yF ?? 1; + this.frictionSway = swayF ?? 1; + this.gravity = gravity ?? 0; + this.twinkle = twinkle ?? false; + this.twinkleTime = twinkleTime ?? this.life; + + this.ctx = ctx; + } + + draw() { + this.ctx.globalAlpha = Math.max(0, this.life / this.maxLife); + this.ctx.fillStyle = this.colour; + this.ctx.beginPath(); + this.ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); + this.ctx.fill(); + } + + update() { + this.life -= 1; + this.x += this.dx + Math.sin(this.sway * this.life); + this.y += this.dy; + this.dx *= this.frictionX; + this.dy *= this.frictionY; + this.sway *= this.frictionSway + this.dy += this.gravity; + if (this.twinkle && this.life < this.twinkleTime) this.size = Math.abs((Math.sin(this.life / 15))) * 2; + } +} + +export class Particles { + particles = []; + + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + this.ctx = this.game.renderer.ctx; + } + + initBoard() { + this.boardWidth = this.game.renderer.boardWidth; + this.boardHeight = this.game.renderer.boardHeight; + this.minosize = this.game.boardrender.minoSize; + } + + spawnParticles(posX, posY, type, pieceWidth = 1, cw = false, colour = "white") { + if (!this.game.settings.display.particles) return + this.volume = this.game.settings.display.particleVolume; + this.size = this.game.settings.display.particleSize; + const [x, y] = [posX * this.minosize, (40 - posY) * this.minosize]; + if (type == "drop") this.creatDropParticles(x, y, colour, this.minosize * pieceWidth, -this.boardHeight); + if (type == "lock") this.createLockParticles(x, y, colour, this.minosize * pieceWidth, 10); + if (type == "clear") this.createClearParticles(x, y, colour, this.boardWidth, -10); + if (type == "pc") this.createPCParticles(x, y, this.boardWidth, 10); + if (type == "dangerboard") this.createDangerBoardParticles(x, this.boardHeight, colour, this.boardWidth, 10); + if (type == "dangersides") this.createDangerSidesParticles(x, y, "red", this.boardWidth, 0, 1); + if (type == "spin") this.createSpinParticles(x, y, colour, cw, this.minosize * pieceWidth, -this.minosize * pieceWidth); + if (type == "spike") this.createSpikeParticles(x, this.boardHeight, colour, this.boardWidth, -this.boardHeight); + if (type == "BTB") this.createBTBParticle(x, y, "gold", this.boardWidth, 0, this.boardHeight); + } + + creatDropParticles(x, y, colour, len, height) { + for (let i = 0; i < this.volume / 3; i++) { + const posX = x + Math.random() * len - len / 2; + const posY = y + Math.random() * height / 2; + const life = Math.random() * 35 + 70; + const dx = Math.random() * 1 - 0.5; + const dy = Math.random() * -1.2 - 2.4; + const sway = Math.random() * 0.04 - 0.02; + + const placeParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, sway, xF: 0.95, yF: 0.95, swayF: 0.96 } + const particle = new Point(placeParticle, this.ctx); + this.particles.push(particle); + } + } + + createLockParticles(x, y, colour, len, height) { + for (let i = 0; i < this.volume / 4; i++) { + const posX = x + Math.random() * len - len / 2; + const posY = y + Math.random() * height; + const life = Math.random() * 15 + 30; + const dx = Math.random() * 1 - 0.5 + (posX - x) / 50; + const dy = Math.random() * -0.7 - 1.4; + + const clearParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, xF: 0.96, yF: 0.96, gravity: 0.05 } + const particle = new Point(clearParticle, this.ctx); + this.particles.push(particle); + } + } + + createClearParticles(x, y, colour, len, height) { + for (let i = 0; i < this.volume; i++) { + const posX = x + Math.random() * len + const posY = y + Math.random() * height; + const life = Math.random() * 20 + 40; + const dx = Math.random() * 1.5 - 0.75 + (posX - x) / 200; + const dy = Math.random() * -0.8 - 1.5; + + const clearParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, yF: 0.99, gravity: 0.1 } + const particle = new Point(clearParticle, this.ctx); + this.particles.push(particle); + } + } + + createPCParticles(x, y, len, height) { + for (let i = 0; i < this.volume; i++) { + const posX = x + Math.random() * len + const posY = y + Math.random() * height; + const life = Math.random() * 100 + 200; + const dx = Math.random() * 1.5 - 0.75; + const dy = Math.random() * -8 - 2; + const colour = `hsl(${Math.random() * 360}, 80%, 60%)` + + const pcParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, xF: 0.98, yF: 0.98, twinkle: true, twinkleTime: 130 } + const particle = new Point(pcParticle, this.ctx); + this.particles.push(particle); + } + } + + createDangerBoardParticles(x, y, colour, len, height) { + for (let i = 0; i < this.volume / 10; i++) { + if (Math.random() > this.volume / 500) continue + const posX = x + Math.random() * len + const posY = y + Math.random() * height / 2; + const life = Math.random() * 100 + 200; + const dx = Math.random() * 1 - 0.5; + const dy = Math.random() * -1.2 - 2.4; + const sway = Math.random() * 0.005 - 0.0025; + + const dangerParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, sway, swayF: 0.98 } + const particle = new Point(dangerParticle, this.ctx); + this.particles.push(particle); + } + } + + createDangerSidesParticles(x, y, colour, width, len, height) { + for (let i = 0; i < this.volume / 10; i++) { + if (Math.random() > this.volume / 250) continue + const direction = Math.random() > 0.5; + + const posX = (direction ? 0 : width) + x + Math.random() * len + const posY = y + Math.random() * height; + const life = Math.random() * 15 + 30; + const dx = (direction ? 1 : -1) * (Math.random() * 2 + 2); + const dy = Math.random() * 2 - 1; + + const dangerSideParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, gravity: 0.05 } + const particle = new Point(dangerSideParticle, this.ctx); + this.particles.push(particle); + } + } + + createSpinParticles(x, y, colour, cw, len, height) { + len *= 0.5; + height *= 0.5; + for (let i = 0; i < this.volume / 3; i++) { + const posX = x + Math.random() * len - len / 2 + const posY = y + Math.random() * height - height / 2; + const life = Math.random() * 35 + 70; + let dx = Math.random() * 1 - 0.5 + (posY - y) / 30; + let dy = Math.random() * 1 - 0.5 + (posX - x) / 30; + if (cw) { dx *= -1 } else { dy *= -1 } + + const spinParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, xF: 0.98, yF: 0.98 } + const particle = new Point(spinParticle, this.ctx); + this.particles.push(particle); + } + } + + createSpikeParticles(x, y, colour, len, height) { + for (let i = 0; i < this.volume / 2; i++) { + const posX = x + Math.random() * len + const posY = y + Math.random() * height / 2; + const life = Math.random() * 35 + 70; + const dx = Math.random() * 1 - 0.5; + const dy = Math.random() * 1 - 0.5; + + const spikeParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, xF: 0.96, yF: 0.96, twinkle: true } + const particle = new Point(spikeParticle, this.ctx); + this.particles.push(particle); + } + } + + createBTBParticle(x, y, colour, width, len, height) { + for (let i = 0; i < this.volume * 2; i++) { + const direction = Math.random() > 0.5; + + const posX = (direction ? 0 : width) + x + Math.random() * len + const posY = y + Math.random() * height; + const life = Math.random() * 25 + 50; + const dx = (direction ? 1 : -1) * (Math.random() * 2); + const dy = Math.random() * 2 - 1; + + const BTBParticle = { x: posX, y: posY, colour, size: this.size, life, dx, dy, gravity: 0.15 } + const particle = new Point(BTBParticle, this.ctx); + this.particles.push(particle); + } + } + + clearParticles() { + this.particles = []; + } + + update() { + this.particles = this.particles.filter(p => p.life > 0); + this.particles.forEach(particle => { + particle.update(); + particle.draw(); + }); + } } \ No newline at end of file diff --git a/src/display/renderBoard.js b/src/display/renderBoard.js index 5853757..f62d7e1 100644 --- a/src/display/renderBoard.js +++ b/src/display/renderBoard.js @@ -1,160 +1,160 @@ -import { Game } from "../game.js"; - -export class BoardRenderer { - boardAlpha = 1; - queueAlpha = 1; - justPlacedCoords = []; - justPlacedAlpha = 1; - minoSize; - texture; - flashTimes = []; - - divlock = document.getElementById("lockTimer"); - - /** - * - * @param {Game} game - */ - constructor(game) { - this.game = game; - } - - // board rendering - getOpacity(cell, cntx, x, y) { - if (cntx != this.game.renderer.ctx) return; - if (this.divlock.value != 0 && cell.includes("A") && this.game.settings.game.gamemode != "lookahead") { - return 1 - (this.divlock.value / 250); - } - if (this.game.settings.game.gamemode == "lookahead") { - for (let [posX, posY] of this.justPlacedCoords) { - if (posX == x && posY == y && cntx == this.game.renderer.ctx) { - return Math.max(this.justPlacedAlpha, this.boardAlpha).toFixed(2); - } - } - } - - return this.boardAlpha.toFixed(2); - } - - setMinoFlash(cntx, x, y, posX, posY) { - if (cntx != this.game.renderer.ctx) return; - for (let { c, t } of this.flashTimes) { - if (c[0] == x && c[1] == y) { - const dx = (t / 15) * this.minoSize; - posX = posX + this.minoSize; - posY = posY + this.minoSize; - this.drawTriangle(posX, posY, posX - dx, posY - dx, cntx); - } - } - } - - drawTriangle(posX, posY, x, y, cntx) { - cntx.globalAlpha = 0.4; - cntx.fillStyle = "#ffffff" - cntx.beginPath(); - cntx.moveTo(posX, posY); - cntx.lineTo(posX, y); - cntx.lineTo(x, posY); - cntx.lineTo(posX, posY); - cntx.fill(); - } - - toHex(num) { - const hex = Math.round((+num * 255) / 100).toString(16); - return hex.length > 1 ? hex : 0 + hex; - } - - loadImage(src) { - this.texture = new Image(372, 30); - this.texture.src = src; - this.texture.onload = () => { - this.game.renderer.updateNext(); - } - } - - getPiece(cntx, cell) { - return this.game.hold.occured && cntx == this.game.renderer.ctxH ? "hold" : cell; - } - - getTexture(name) { - const pieces = ["z", "l", "o", "s", "i", "j", "t", "shadow", "hold", "g", "darkg", "topout"] - const x = pieces.indexOf(name.toLowerCase()) * 31; - const y = 0; - const width = 30; - const height = 30; - return { x, y, width, height }; - } - - getShadowOpacity() { - const opacity = this.game.settings.display.shadowOpacity / 100; - if (this.game.settings.game.gamemode == "lookahead") return (opacity * this.boardAlpha).toFixed(2); - return opacity; - } - - removeCoords([x, y]) { - this.justPlacedCoords = this.justPlacedCoords.filter(c => !(c[0] == x && c[1] == y)); - this.flashTimes = this.flashTimes.filter(({ c, t }) => !(c[0] == x && c[1] == y)); - } - - renderBorder(ctx, x, y) { - const type = this.game.settings.display.gridType; - if (type == "round") { - ctx.beginPath(); - ctx.roundRect(x, y, this.minoSize - 1, this.minoSize - 1, this.minoSize / 4); - ctx.stroke(); - } else if (type == "square") { - ctx.beginPath(); - ctx.strokeRect(x, y, this.minoSize - 1, this.minoSize - 1, this.minoSize / 4); - ctx.stroke(); - } else if (type == "dot") { - ctx.beginPath(); - ctx.arc(x + this.minoSize, y + this.minoSize, 1, 0, 2 * Math.PI); - ctx.stroke(); - ctx.beginPath(); - ctx.arc(x, y, 1, 0, 2 * Math.PI); - ctx.stroke(); - } - } - - /** - * @param {CanvasRenderingContext2D} cntx - */ - renderToCanvas(cntx, grid, yPosChange, [dx, dy] = [0, 0], width, height) { - cntx.clearRect(0, 0, width, height); - grid.forEach((row, y) => { - row.forEach((col, x) => { - const [posX, posY] = [x * this.minoSize, (yPosChange - y) * this.minoSize]; - const cell = col.split(" "); - cntx.lineWidth = 1; - - if (cell.includes("A") || cell.includes("S")) { // active piece or stopped piece - cntx.globalAlpha = this.getOpacity(cell, cntx, x, y) ?? this.queueAlpha.toFixed(2); - const p = this.getTexture(this.getPiece(cntx, cell[1])); - cntx.drawImage(this.texture, p.x, p.y, p.width, p.height, posX + dx, posY + dy, this.minoSize, this.minoSize); - this.setMinoFlash(cntx, x, y, posX + dx, posY + dy); - } - else if (cell.includes("NP") && this.game.renderer.inDanger) { // next piece overlay - cntx.globalAlpha = 0.32; - const p = this.getTexture("topout"); - cntx.drawImage(this.texture, p.x, p.y, p.width, p.height, posX + dx, posY + dy, this.minoSize, this.minoSize); - } - else if (cell.includes("Sh")) { // shadow piece - cntx.globalAlpha = this.getShadowOpacity(); - const piece = this.game.settings.display.colouredShadow ? this.game.falling.piece.name : "shadow"; - const p = this.getTexture(piece); - cntx.drawImage(this.texture, p.x, p.y, p.width, p.height, posX + dx, posY + dy, this.minoSize, this.minoSize); - } - else if (y < 20 && this.game.settings.display.showGrid && cntx == this.game.renderer.ctx) { // grid - cntx.globalAlpha = 1 - cntx.strokeStyle = "#ffffff" + this.toHex(this.game.settings.display.gridopacity); - this.renderBorder(cntx, posX, posY); - } - }); - }); - - // flash - this.flashTimes = this.flashTimes - .filter(({ c, t }) => t > 0) - .map(({ c, t }) => { return { c, t: t - 1 }; }); - } +import { Game } from "../game.js"; + +export class BoardRenderer { + boardAlpha = 1; + queueAlpha = 1; + justPlacedCoords = []; + justPlacedAlpha = 1; + minoSize; + texture; + flashTimes = []; + + divlock = document.getElementById("lockTimer"); + + /** + * + * @param {Game} game + */ + constructor(game) { + this.game = game; + } + + // board rendering + getOpacity(cell, cntx, x, y) { + if (cntx != this.game.renderer.ctx) return; + if (this.divlock.value != 0 && cell.includes("A") && this.game.settings.game.gamemode != "lookahead") { + return 1 - (this.divlock.value / 250); + } + if (this.game.settings.game.gamemode == "lookahead") { + for (let [posX, posY] of this.justPlacedCoords) { + if (posX == x && posY == y && cntx == this.game.renderer.ctx) { + return Math.max(this.justPlacedAlpha, this.boardAlpha).toFixed(2); + } + } + } + + return this.boardAlpha.toFixed(2); + } + + setMinoFlash(cntx, x, y, posX, posY) { + if (cntx != this.game.renderer.ctx) return; + for (let { c, t } of this.flashTimes) { + if (c[0] == x && c[1] == y) { + const dx = (t / 15) * this.minoSize; + posX = posX + this.minoSize; + posY = posY + this.minoSize; + this.drawTriangle(posX, posY, posX - dx, posY - dx, cntx); + } + } + } + + drawTriangle(posX, posY, x, y, cntx) { + cntx.globalAlpha = 0.4; + cntx.fillStyle = "#ffffff" + cntx.beginPath(); + cntx.moveTo(posX, posY); + cntx.lineTo(posX, y); + cntx.lineTo(x, posY); + cntx.lineTo(posX, posY); + cntx.fill(); + } + + toHex(num) { + const hex = Math.round((+num * 255) / 100).toString(16); + return hex.length > 1 ? hex : 0 + hex; + } + + loadImage(src) { + this.texture = new Image(372, 30); + this.texture.src = src; + this.texture.onload = () => { + this.game.renderer.updateNext(); + } + } + + getPiece(cntx, cell) { + return this.game.hold.occured && cntx == this.game.renderer.ctxH ? "hold" : cell; + } + + getTexture(name) { + const pieces = ["z", "l", "o", "s", "i", "j", "t", "shadow", "hold", "g", "darkg", "topout"] + const x = pieces.indexOf(name.toLowerCase()) * 31; + const y = 0; + const width = 30; + const height = 30; + return { x, y, width, height }; + } + + getShadowOpacity() { + const opacity = this.game.settings.display.shadowOpacity / 100; + if (this.game.settings.game.gamemode == "lookahead") return (opacity * this.boardAlpha).toFixed(2); + return opacity; + } + + removeCoords([x, y]) { + this.justPlacedCoords = this.justPlacedCoords.filter(c => !(c[0] == x && c[1] == y)); + this.flashTimes = this.flashTimes.filter(({ c, t }) => !(c[0] == x && c[1] == y)); + } + + renderBorder(ctx, x, y) { + const type = this.game.settings.display.gridType; + if (type == "round") { + ctx.beginPath(); + ctx.roundRect(x, y, this.minoSize - 1, this.minoSize - 1, this.minoSize / 4); + ctx.stroke(); + } else if (type == "square") { + ctx.beginPath(); + ctx.strokeRect(x, y, this.minoSize - 1, this.minoSize - 1, this.minoSize / 4); + ctx.stroke(); + } else if (type == "dot") { + ctx.beginPath(); + ctx.arc(x + this.minoSize, y + this.minoSize, 1, 0, 2 * Math.PI); + ctx.stroke(); + ctx.beginPath(); + ctx.arc(x, y, 1, 0, 2 * Math.PI); + ctx.stroke(); + } + } + + /** + * @param {CanvasRenderingContext2D} cntx + */ + renderToCanvas(cntx, grid, yPosChange, [dx, dy] = [0, 0], width, height) { + cntx.clearRect(0, 0, width, height); + grid.forEach((row, y) => { + row.forEach((col, x) => { + const [posX, posY] = [x * this.minoSize, (yPosChange - y) * this.minoSize]; + const cell = col.split(" "); + cntx.lineWidth = 1; + + if (cell.includes("A") || cell.includes("S")) { // active piece or stopped piece + cntx.globalAlpha = this.getOpacity(cell, cntx, x, y) ?? this.queueAlpha.toFixed(2); + const p = this.getTexture(this.getPiece(cntx, cell[1])); + cntx.drawImage(this.texture, p.x, p.y, p.width, p.height, posX + dx, posY + dy, this.minoSize, this.minoSize); + this.setMinoFlash(cntx, x, y, posX + dx, posY + dy); + } + else if (cell.includes("NP") && this.game.renderer.inDanger) { // next piece overlay + cntx.globalAlpha = 0.32; + const p = this.getTexture("topout"); + cntx.drawImage(this.texture, p.x, p.y, p.width, p.height, posX + dx, posY + dy, this.minoSize, this.minoSize); + } + else if (cell.includes("Sh")) { // shadow piece + cntx.globalAlpha = this.getShadowOpacity(); + const piece = this.game.settings.display.colouredShadow ? this.game.falling.piece.name : "shadow"; + const p = this.getTexture(piece); + cntx.drawImage(this.texture, p.x, p.y, p.width, p.height, posX + dx, posY + dy, this.minoSize, this.minoSize); + } + else if (y < 20 && this.game.settings.display.showGrid && cntx == this.game.renderer.ctx) { // grid + cntx.globalAlpha = 1 + cntx.strokeStyle = "#ffffff" + this.toHex(this.game.settings.display.gridopacity); + this.renderBorder(cntx, posX, posY); + } + }); + }); + + // flash + this.flashTimes = this.flashTimes + .filter(({ c, t }) => t > 0) + .map(({ c, t }) => { return { c, t: t - 1 }; }); + } } \ No newline at end of file diff --git a/src/display/renderer.js b/src/display/renderer.js index d761704..fd2a6fb 100644 --- a/src/display/renderer.js +++ b/src/display/renderer.js @@ -1,369 +1,369 @@ -import { Game } from "../game.js"; -import pieces from "../data/pieces.json" with { type: "json" }; -import { defaultSkins, statDecimals, statsSecondary as statsSecondaries } from "../data/data.js"; - -export class Renderer { - boardHeight; - boardWidth; - holdHeight; - holdWidth; - nextHeight; - nextWidth; - holdQueueGrid = []; - nextQueueGrid = []; - inDanger; - texttimeouts = {}; - resetAnimLength = 30; - resetAnimCurrent = 30; - - sidebarStats; - sidebarFixed; - sidebarSecondary; - /** @type {CanvasRenderingContext2D} */ - ctx; - ctxN; - ctxH; - - canvasField = document.getElementById("playingfield"); - canvasNext = document.getElementById("next"); - canvasHold = document.getElementById("hold"); - divBoard = document.getElementById("board"); - divBackboard = document.getElementById("backboard"); - divLinesSent = document.getElementById("linessent"); - elementEditPieces = document.getElementById("editMenuPieces"); - - elementStats1 = document.getElementById("stats1"); - elementStats2 = document.getElementById("stats2"); - elementStats3 = document.getElementById("stats3"); - elementStatname1 = document.getElementById("statName1"); - elementStatname2 = document.getElementById("statName2"); - elementStatname3 = document.getElementById("statName3"); - elementSmallStat1 = document.getElementById("smallStat1"); - elementSmallStat2 = document.getElementById("smallStat2"); - elementSmallStat3 = document.getElementById("smallStat3"); - - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - this.board = game.board; - - this.ctx = this.canvasField.getContext("2d"); - this.ctxN = this.canvasNext.getContext("2d"); - this.ctxH = this.canvasHold.getContext("2d"); - } - - renderingLoop() { - this.game.boardrender.renderToCanvas(this.ctx, this.game.board.boardState, 39, [0, 0], this.boardWidth, this.boardHeight); - this.game.boardeffects.move(0, 0); - this.game.boardeffects.rotate(0); - this.game.particles.update(); - this.dangerParticles(); - this.resetAnimation(); - requestAnimationFrame(this.renderingLoop.bind(this)) - if(this.game.settings.game.gamemode == "ultra" && Math.floor(this.game.stats.time) == 60) this.renderTimeLeft("60S LEFT") - if(this.game.settings.game.gamemode == "ultra" && Math.floor(this.game.stats.time) == 90) this.renderTimeLeft("30S LEFT") - } - - sizeCanvas() { - this.renderStyles(); - [this.canvasField, this.canvasNext, this.canvasHold].forEach(c => { - c.width = Math.round(c.offsetWidth / 10) * 10; - c.height = Math.round(c.offsetHeight / 40) * 40; - }); - this.divBoard.style.width = `${this.canvasField.width}px`; - this.divBoard.style.height = `${this.canvasField.height / 2}px`; - this.game.boardrender.minoSize = this.canvasField.width / 10; - this.boardWidth = this.canvasField.offsetWidth; - this.boardHeight = this.canvasField.offsetHeight; - this.nextWidth = this.canvasNext.offsetWidth; - this.nextHeight = this.canvasNext.offsetHeight; - this.holdWidth = this.canvasHold.offsetWidth; - this.holdHeight = this.canvasHold.offsetHeight; - } - - updateNext() { - this.nextQueueGrid = [...Array(15)].map(() => [...Array(4)].map(() => "")); - - const next5 = this.game.bag.getFirstFive(); - next5.forEach((name, idx) => { - const piece = this.getPiece(name); - let [dx, dy] = [0, 3 * (4 - idx)]; - if (piece.name == "o") [dx, dy] = [dx + 1, dy + 1]; // shift o piece - const coords = this.board.pieceToCoords(piece.shape1); - coords.forEach(([x, y]) => (this.nextQueueGrid[y + dy][x + dx] = "A " + piece.name)); - }); - - this.game.boardrender.renderToCanvas(this.ctxN, this.nextQueueGrid, 15, [0, 0], this.nextWidth, this.nextHeight); - if (this.game.settings.game.gamemode == 'lookahead' || !this.game.settings.display.colouredQueues) return; - this.canvasNext.style.outlineColor = this.game.bag.nextPiece().colour; - } - - getPiece(name) { - if (name == "G") return { colour: "gray" } - return pieces.filter(p => p.name == name)[0]; - } - - updateHold() { - this.holdQueueGrid = [...Array(3)].map(() => [...Array(4)].map(() => "")); - this.clearHold(); - if (this.game.hold.piece == undefined) return; - - const name = this.game.hold.piece.name; - const isO = name == "o", - isI = name == "i"; - const [dx, dy] = [isO ? 1 : 0, isO ? 1 : isI ? -1 : 0]; - const coords = this.board.pieceToCoords(this.game.hold.piece.shape1); - - coords.forEach(([x, y]) => (this.holdQueueGrid[y + dy][x + dx] = "A " + name)); - const len = Math.round(this.game.boardrender.minoSize / 2); - const [shiftX, shiftY] = [isO || isI ? 0 : len, isI ? 0 : len]; - - this.game.boardrender.renderToCanvas(this.ctxH, this.holdQueueGrid, 2, [shiftX, shiftY], this.holdWidth, this.holdHeight); - if (this.game.settings.game.gamemode == 'lookahead' || !this.game.settings.display.colouredQueues) return; - const colour = this.game.hold.occured ? "gray" : this.game.hold.piece.colour - this.canvasHold.style.outline = `0.2vh solid ${colour}`; - } - - clearHold() { - this.ctxH.clearRect(0, 0, this.canvasHold.offsetWidth + 10, this.canvasHold.offsetHeight); - } - - renderDanger() { - const condition = - this.game.board.getMinos("S").some(c => c[1] > 16) && // any mino if above row 16 - this.game.settings.game.gamemode != 'combo'; // not combo mode - if (condition && !this.inDanger) { - this.game.sounds.playSound("damage_alert"); - } - this.game.boardeffects.toggleDangerBoard(condition) - this.inDanger = condition; - } - - renderActionText(damagetype, isBTB, isPC, damage, linecount) { - if (damagetype != "") this.setText("cleartext", damagetype, 2000); - if (this.game.stats.combo > 0) - this.setText("combotext", `Combo ${this.game.stats.combo}`, 2000); - if (isBTB && this.game.stats.btbCount > 0) - this.setText("btbtext", `BTB ${this.game.stats.btbCount} `, 2000); - if (isPC) this.setText("pctext", "Perfect Clear", 2000); - if (damage > 0) this.setText("linessent", `${this.game.mechanics.spikeCounter}`, 1500); - - if (this.game.mechanics.spikeCounter > 0) this.spikePattern("white", 1); - if (this.game.mechanics.spikeCounter >= 10) this.spikePattern("red", 1.1); - if (this.game.mechanics.spikeCounter >= 20) this.spikePattern("lime", 1.2); - - // audio - if (isPC) this.game.sounds.playSound("allclear"); - if (this.game.stats.btbCount == 2 && isBTB) this.game.sounds.playSound("btb_1"); - if (linecount >= 4 && this.game.stats.btbCount > 0) { - this.game.sounds.playSound("clearbtb"); - } else if (linecount >= 4) { - this.game.sounds.playSound("clearquad"); - } else if (linecount > 0 && this.game.mechanics.isTspin) { - this.game.sounds.playSound("clearspin"); - } else if (linecount > 0 && this.game.mechanics.isAllspin && this.game.settings.game.allspin) { - this.game.sounds.playSound("clearspin"); - } else if (linecount > 0) { - this.game.sounds.playSound("clearline"); - } - if (this.game.mechanics.spikeCounter >= 15) this.game.sounds.playSound("thunder", false); - if (this.game.stats.combo > 0) - this.game.sounds.playSound(`combo_${this.game.stats.combo > 16 ? 16 : this.game.stats.combo}`); - } - - resetActionText() { - ['btbtext', 'cleartext', 'combotext', 'pctext', 'linessent'].forEach(id => { - document.getElementById(id).style.opacity = "0"; - }) - } - - spikePattern(colour, size) { - this.divLinesSent.style.color = colour; - this.divLinesSent.style.textShadow = `0 0 1vh ${colour}`; - this.divLinesSent.style.fontSize = `${3.5 * size}vh`; - } - - setText(id, text, duration) { - const textbox = document.getElementById(id); - textbox.textContent = text; - textbox.style.transform = "translateX(-2%)"; - textbox.style.opacity = "1"; - if (this.texttimeouts[id] != 0) this.stopTimeout(id); - this.texttimeouts[id] = setTimeout(() => { - textbox.style.opacity = "0"; - textbox.style.transform = "translateX(2%)"; - this.game.mechanics.spikeCounter = 0; - }, duration); - } - - stopTimeout(name) { - clearTimeout(this.texttimeouts[name]); - this.texttimeouts[name] = 0; - } - - renderStyles() { - // custom background - const bg = this.game.settings.display.background; - if (bg == "") bg = "#080B0C"; - document.body.style.background = (bg[0] == "#") ? bg : `url("${bg}") no-repeat center center` - document.body.style.backgroundSize = "cover"; - - const height = Number(this.game.settings.display.boardHeight) + 10; - this.divBoard.style.transform = `scale(${height}%) translate(-50%, -50%)`; - this.canvasHold.style.outline = `0.2vh solid #dbeaf3`; - - // board opacity - const background = `rgba(0, 0, 0, ${Number(this.game.settings.display.boardOpacity) / 100})`; - this.divBackboard.style.backgroundColor = background; - document.body.style.setProperty('--background', background); - - // skins - let skin = this.game.settings.display.skin; - if (defaultSkins.includes(skin)) skin = `./assets/skins/${skin}.png`; - this.game.boardrender.loadImage(skin); - - // sidebar constants - this.sidebarStats = this.game.settings.game.sidebar; - this.sidebarFixed = this.sidebarStats.map(stat => this.createReverseLookup(statDecimals)[stat]); - this.sidebarSecondary = this.sidebarStats.map(stat => statsSecondaries[stat] ?? "None"); - - this.sidebarStats.forEach((stat, index) => { - if (stat == "None") stat = "" - this[`elementStatname${index + 1}`].textContent = stat; - }) - } - - renderSidebar() { - this.sidebarStats.forEach((stat, index) => { - if (stat == "None") { // no stat - this[`elementStats${index + 1}`].textContent = ""; - return; - }; - const displayStat = this.game.stats[stat].toFixed(this.sidebarFixed[index]); - this[`elementStats${index + 1}`].textContent = displayStat; - - if (this.sidebarSecondary[index]) { - const displaySecond = this.game.stats[this.sidebarSecondary[index]] - this[`elementSmallStat${index + 1}`].textContent = displaySecond; - } - }) - } - - renderTimeLeft(text){ - const e = document.getElementById("timeLeftText") - if (this.texttimeouts["timeLeft"] != 0){ - this.stopTimeout("timeLeft"); - //e.classList.remove("warn"); - } - e.textContent = text - e.classList.add("warn") - this.texttimeouts["timeLeft"] = setTimeout(() => { - e.classList.remove("warn"); - }, 3000); - } - - createReverseLookup(obj) { - const reverseLookup = {} - for (const [key, array] of Object.entries(obj)) { - array.forEach(item => { - reverseLookup[item] = key; - }); - } - return reverseLookup - } - - updateAlpha() { - if (this.game.settings.game.gamemode != 'lookahead') return; - const update = (type, amount) => { - if (this.game.stats.checkInvis()) { - if (this.game.boardrender[type] <= 0) { - this.game.boardrender[type] = 1; - this.updateNext(); - this.updateHold(); - } - } else { - if (this.game.boardrender[type] > 0) { - this.game.boardrender[type] += -amount / this.game.tickrate; - this.updateNext(); - this.updateHold(); - } else { - this.game.boardrender[type] = 0; - } - } - } - update("boardAlpha", 3) - update("queueAlpha", 3) - update("justPlacedAlpha", 6) - - } - - setEditPieceColours() { - const elPieces = [...this.elementEditPieces.children]; - elPieces.forEach(elpiece => { - const pieceid = elpiece.id.split("_")[0]; - elpiece.style.backgroundColor = this.getPiece(pieceid).colour - }) - } - - bounceBoard(direction) { - const force = Number(this.game.settings.display.boardBounce); - const forces = { "LEFT": [-force, 0], "RIGHT": [force, 0], "DOWN": [0, force], }; - this.game.boardeffects.move(...forces[direction]); - } - - rotateBoard(type) { - const force = Number(this.game.settings.display.boardBounce) * 0.5; - const forces = { "CW": force, "CCW": -force } - this.game.boardeffects.rotate(forces[type]); - } - - dangerParticles() { - if (!this.inDanger) return; - this.game.particles.spawnParticles(0, 0, "dangerboard"); - this.game.particles.spawnParticles(0, 20, "dangersides"); - } - - resetAnimation() { - if (this.resetAnimCurrent >= this.resetAnimLength * 2) return; - this.resetAnimCurrent++; - if (this.game.boardrender.boardAlpha < 0.99) this.game.boardrender.boardAlpha += 2 / this.resetAnimLength; - if (this.resetAnimCurrent > this.resetAnimLength) return; - - - const progress = this.resetAnimCurrent / this.resetAnimLength; - const startY = this.boardHeight / 2; - const dx = this.boardWidth; - const dy = dx + this.boardHeight / 2; - - const fillTriangle = (p, colour) => { - this.ctx.globalAlpha = 1; - this.ctx.fillStyle = colour; - this.ctx.beginPath(); - this.ctx.moveTo(0, startY - dy * p); - this.ctx.lineTo(dy * p, startY); - this.ctx.lineTo(0, startY + dy * p); - this.ctx.lineTo(0, 0); - this.ctx.fill(); - } - // fill triangle - const progress1 = this.easeInOutCubic(progress); - fillTriangle(progress1, 'white'); - - // clear smaller triangle - const progress2 = this.easeInOutCubic(progress - 0.1); - fillTriangle(progress2, 'black'); - - - if (this.resetAnimCurrent == this.resetAnimLength) { // finished - this.game.startGame(); - this.game.controls.resetting = false; - this.game.boardrender.boardAlpha = 0; - } - } - - easeInOutCubic(x) { - return -(Math.cos(Math.PI * x) - 1) / 2; - } -} +import { Game } from "../game.js"; +import pieces from "../data/pieces.json" with { type: "json" }; +import { defaultSkins, statDecimals, statsSecondary as statsSecondaries } from "../data/data.js"; + +export class Renderer { + boardHeight; + boardWidth; + holdHeight; + holdWidth; + nextHeight; + nextWidth; + holdQueueGrid = []; + nextQueueGrid = []; + inDanger; + texttimeouts = {}; + resetAnimLength = 30; + resetAnimCurrent = 30; + + sidebarStats; + sidebarFixed; + sidebarSecondary; + /** @type {CanvasRenderingContext2D} */ + ctx; + ctxN; + ctxH; + + canvasField = document.getElementById("playingfield"); + canvasNext = document.getElementById("next"); + canvasHold = document.getElementById("hold"); + divBoard = document.getElementById("board"); + divBackboard = document.getElementById("backboard"); + divLinesSent = document.getElementById("linessent"); + elementEditPieces = document.getElementById("editMenuPieces"); + + elementStats1 = document.getElementById("stats1"); + elementStats2 = document.getElementById("stats2"); + elementStats3 = document.getElementById("stats3"); + elementStatname1 = document.getElementById("statName1"); + elementStatname2 = document.getElementById("statName2"); + elementStatname3 = document.getElementById("statName3"); + elementSmallStat1 = document.getElementById("smallStat1"); + elementSmallStat2 = document.getElementById("smallStat2"); + elementSmallStat3 = document.getElementById("smallStat3"); + + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + this.board = game.board; + + this.ctx = this.canvasField.getContext("2d"); + this.ctxN = this.canvasNext.getContext("2d"); + this.ctxH = this.canvasHold.getContext("2d"); + } + + renderingLoop() { + this.game.boardrender.renderToCanvas(this.ctx, this.game.board.boardState, 39, [0, 0], this.boardWidth, this.boardHeight); + this.game.boardeffects.move(0, 0); + this.game.boardeffects.rotate(0); + this.game.particles.update(); + this.dangerParticles(); + this.resetAnimation(); + requestAnimationFrame(this.renderingLoop.bind(this)) + if(this.game.settings.game.gamemode == "ultra" && Math.floor(this.game.stats.time) == 60) this.renderTimeLeft("60S LEFT") + if(this.game.settings.game.gamemode == "ultra" && Math.floor(this.game.stats.time) == 90) this.renderTimeLeft("30S LEFT") + } + + sizeCanvas() { + this.renderStyles(); + [this.canvasField, this.canvasNext, this.canvasHold].forEach(c => { + c.width = Math.round(c.offsetWidth / 10) * 10; + c.height = Math.round(c.offsetHeight / 40) * 40; + }); + this.divBoard.style.width = `${this.canvasField.width}px`; + this.divBoard.style.height = `${this.canvasField.height / 2}px`; + this.game.boardrender.minoSize = this.canvasField.width / 10; + this.boardWidth = this.canvasField.offsetWidth; + this.boardHeight = this.canvasField.offsetHeight; + this.nextWidth = this.canvasNext.offsetWidth; + this.nextHeight = this.canvasNext.offsetHeight; + this.holdWidth = this.canvasHold.offsetWidth; + this.holdHeight = this.canvasHold.offsetHeight; + } + + updateNext() { + this.nextQueueGrid = [...Array(15)].map(() => [...Array(4)].map(() => "")); + + const next5 = this.game.bag.getFirstFive(); + next5.forEach((name, idx) => { + const piece = this.getPiece(name); + let [dx, dy] = [0, 3 * (4 - idx)]; + if (piece.name == "o") [dx, dy] = [dx + 1, dy + 1]; // shift o piece + const coords = this.board.pieceToCoords(piece.shape1); + coords.forEach(([x, y]) => (this.nextQueueGrid[y + dy][x + dx] = "A " + piece.name)); + }); + + this.game.boardrender.renderToCanvas(this.ctxN, this.nextQueueGrid, 15, [0, 0], this.nextWidth, this.nextHeight); + if (this.game.settings.game.gamemode == 'lookahead' || !this.game.settings.display.colouredQueues) return; + this.canvasNext.style.outlineColor = this.game.bag.nextPiece().colour; + } + + getPiece(name) { + if (name == "G") return { colour: "gray" } + return pieces.filter(p => p.name == name)[0]; + } + + updateHold() { + this.holdQueueGrid = [...Array(3)].map(() => [...Array(4)].map(() => "")); + this.clearHold(); + if (this.game.hold.piece == undefined) return; + + const name = this.game.hold.piece.name; + const isO = name == "o", + isI = name == "i"; + const [dx, dy] = [isO ? 1 : 0, isO ? 1 : isI ? -1 : 0]; + const coords = this.board.pieceToCoords(this.game.hold.piece.shape1); + + coords.forEach(([x, y]) => (this.holdQueueGrid[y + dy][x + dx] = "A " + name)); + const len = Math.round(this.game.boardrender.minoSize / 2); + const [shiftX, shiftY] = [isO || isI ? 0 : len, isI ? 0 : len]; + + this.game.boardrender.renderToCanvas(this.ctxH, this.holdQueueGrid, 2, [shiftX, shiftY], this.holdWidth, this.holdHeight); + if (this.game.settings.game.gamemode == 'lookahead' || !this.game.settings.display.colouredQueues) return; + const colour = this.game.hold.occured ? "gray" : this.game.hold.piece.colour + this.canvasHold.style.outline = `0.2vh solid ${colour}`; + } + + clearHold() { + this.ctxH.clearRect(0, 0, this.canvasHold.offsetWidth + 10, this.canvasHold.offsetHeight); + } + + renderDanger() { + const condition = + this.game.board.getMinos("S").some(c => c[1] > 16) && // any mino if above row 16 + this.game.settings.game.gamemode != 'combo'; // not combo mode + if (condition && !this.inDanger) { + this.game.sounds.playSound("damage_alert"); + } + this.game.boardeffects.toggleDangerBoard(condition) + this.inDanger = condition; + } + + renderActionText(damagetype, isBTB, isPC, damage, linecount) { + if (damagetype != "") this.setText("cleartext", damagetype, 2000); + if (this.game.stats.combo > 0) + this.setText("combotext", `Combo ${this.game.stats.combo}`, 2000); + if (isBTB && this.game.stats.btbCount > 0) + this.setText("btbtext", `BTB ${this.game.stats.btbCount} `, 2000); + if (isPC) this.setText("pctext", "Perfect Clear", 2000); + if (damage > 0) this.setText("linessent", `${this.game.mechanics.spikeCounter}`, 1500); + + if (this.game.mechanics.spikeCounter > 0) this.spikePattern("white", 1); + if (this.game.mechanics.spikeCounter >= 10) this.spikePattern("red", 1.1); + if (this.game.mechanics.spikeCounter >= 20) this.spikePattern("lime", 1.2); + + // audio + if (isPC) this.game.sounds.playSound("allclear"); + if (this.game.stats.btbCount == 2 && isBTB) this.game.sounds.playSound("btb_1"); + if (linecount >= 4 && this.game.stats.btbCount > 0) { + this.game.sounds.playSound("clearbtb"); + } else if (linecount >= 4) { + this.game.sounds.playSound("clearquad"); + } else if (linecount > 0 && this.game.mechanics.isTspin) { + this.game.sounds.playSound("clearspin"); + } else if (linecount > 0 && this.game.mechanics.isAllspin && this.game.settings.game.allspin) { + this.game.sounds.playSound("clearspin"); + } else if (linecount > 0) { + this.game.sounds.playSound("clearline"); + } + if (this.game.mechanics.spikeCounter >= 15) this.game.sounds.playSound("thunder", false); + if (this.game.stats.combo > 0) + this.game.sounds.playSound(`combo_${this.game.stats.combo > 16 ? 16 : this.game.stats.combo}`); + } + + resetActionText() { + ['btbtext', 'cleartext', 'combotext', 'pctext', 'linessent'].forEach(id => { + document.getElementById(id).style.opacity = "0"; + }) + } + + spikePattern(colour, size) { + this.divLinesSent.style.color = colour; + this.divLinesSent.style.textShadow = `0 0 1vh ${colour}`; + this.divLinesSent.style.fontSize = `${3.5 * size}vh`; + } + + setText(id, text, duration) { + const textbox = document.getElementById(id); + textbox.textContent = text; + textbox.style.transform = "translateX(-2%)"; + textbox.style.opacity = "1"; + if (this.texttimeouts[id] != 0) this.stopTimeout(id); + this.texttimeouts[id] = setTimeout(() => { + textbox.style.opacity = "0"; + textbox.style.transform = "translateX(2%)"; + this.game.mechanics.spikeCounter = 0; + }, duration); + } + + stopTimeout(name) { + clearTimeout(this.texttimeouts[name]); + this.texttimeouts[name] = 0; + } + + renderStyles() { + // custom background + const bg = this.game.settings.display.background; + if (bg == "") bg = "#080B0C"; + document.body.style.background = (bg[0] == "#") ? bg : `url("${bg}") no-repeat center center` + document.body.style.backgroundSize = "cover"; + + const height = Number(this.game.settings.display.boardHeight) + 10; + this.divBoard.style.transform = `scale(${height}%) translate(-50%, -50%)`; + this.canvasHold.style.outline = `0.2vh solid #dbeaf3`; + + // board opacity + const background = `rgba(0, 0, 0, ${Number(this.game.settings.display.boardOpacity) / 100})`; + this.divBackboard.style.backgroundColor = background; + document.body.style.setProperty('--background', background); + + // skins + let skin = this.game.settings.display.skin; + if (defaultSkins.includes(skin)) skin = `./assets/skins/${skin}.png`; + this.game.boardrender.loadImage(skin); + + // sidebar constants + this.sidebarStats = this.game.settings.game.sidebar; + this.sidebarFixed = this.sidebarStats.map(stat => this.createReverseLookup(statDecimals)[stat]); + this.sidebarSecondary = this.sidebarStats.map(stat => statsSecondaries[stat] ?? "None"); + + this.sidebarStats.forEach((stat, index) => { + if (stat == "None") stat = "" + this[`elementStatname${index + 1}`].textContent = stat; + }) + } + + renderSidebar() { + this.sidebarStats.forEach((stat, index) => { + if (stat == "None") { // no stat + this[`elementStats${index + 1}`].textContent = ""; + return; + }; + const displayStat = this.game.stats[stat].toFixed(this.sidebarFixed[index]); + this[`elementStats${index + 1}`].textContent = displayStat; + + if (this.sidebarSecondary[index]) { + const displaySecond = this.game.stats[this.sidebarSecondary[index]] + this[`elementSmallStat${index + 1}`].textContent = displaySecond; + } + }) + } + + renderTimeLeft(text){ + const e = document.getElementById("timeLeftText") + if (this.texttimeouts["timeLeft"] != 0){ + this.stopTimeout("timeLeft"); + //e.classList.remove("warn"); + } + e.textContent = text + e.classList.add("warn") + this.texttimeouts["timeLeft"] = setTimeout(() => { + e.classList.remove("warn"); + }, 3000); + } + + createReverseLookup(obj) { + const reverseLookup = {} + for (const [key, array] of Object.entries(obj)) { + array.forEach(item => { + reverseLookup[item] = key; + }); + } + return reverseLookup + } + + updateAlpha() { + if (this.game.settings.game.gamemode != 'lookahead') return; + const update = (type, amount) => { + if (this.game.stats.checkInvis()) { + if (this.game.boardrender[type] <= 0) { + this.game.boardrender[type] = 1; + this.updateNext(); + this.updateHold(); + } + } else { + if (this.game.boardrender[type] > 0) { + this.game.boardrender[type] += -amount / this.game.tickrate; + this.updateNext(); + this.updateHold(); + } else { + this.game.boardrender[type] = 0; + } + } + } + update("boardAlpha", 3) + update("queueAlpha", 3) + update("justPlacedAlpha", 6) + + } + + setEditPieceColours() { + const elPieces = [...this.elementEditPieces.children]; + elPieces.forEach(elpiece => { + const pieceid = elpiece.id.split("_")[0]; + elpiece.style.backgroundColor = this.getPiece(pieceid).colour + }) + } + + bounceBoard(direction) { + const force = Number(this.game.settings.display.boardBounce); + const forces = { "LEFT": [-force, 0], "RIGHT": [force, 0], "DOWN": [0, force], }; + this.game.boardeffects.move(...forces[direction]); + } + + rotateBoard(type) { + const force = Number(this.game.settings.display.boardBounce) * 0.5; + const forces = { "CW": force, "CCW": -force } + this.game.boardeffects.rotate(forces[type]); + } + + dangerParticles() { + if (!this.inDanger) return; + this.game.particles.spawnParticles(0, 0, "dangerboard"); + this.game.particles.spawnParticles(0, 20, "dangersides"); + } + + resetAnimation() { + if (this.resetAnimCurrent >= this.resetAnimLength * 2) return; + this.resetAnimCurrent++; + if (this.game.boardrender.boardAlpha < 0.99) this.game.boardrender.boardAlpha += 2 / this.resetAnimLength; + if (this.resetAnimCurrent > this.resetAnimLength) return; + + + const progress = this.resetAnimCurrent / this.resetAnimLength; + const startY = this.boardHeight / 2; + const dx = this.boardWidth; + const dy = dx + this.boardHeight / 2; + + const fillTriangle = (p, colour) => { + this.ctx.globalAlpha = 1; + this.ctx.fillStyle = colour; + this.ctx.beginPath(); + this.ctx.moveTo(0, startY - dy * p); + this.ctx.lineTo(dy * p, startY); + this.ctx.lineTo(0, startY + dy * p); + this.ctx.lineTo(0, 0); + this.ctx.fill(); + } + // fill triangle + const progress1 = this.easeInOutCubic(progress); + fillTriangle(progress1, 'white'); + + // clear smaller triangle + const progress2 = this.easeInOutCubic(progress - 0.1); + fillTriangle(progress2, 'black'); + + + if (this.resetAnimCurrent == this.resetAnimLength) { // finished + this.game.startGame(); + this.game.controls.resetting = false; + this.game.boardrender.boardAlpha = 0; + } + } + + easeInOutCubic(x) { + return -(Math.cos(Math.PI * x) - 1) / 2; + } +} diff --git a/src/features/editboard.js b/src/features/editboard.js index 194d45e..ebff330 100644 --- a/src/features/editboard.js +++ b/src/features/editboard.js @@ -1,119 +1,119 @@ -import { Game } from "../game.js"; - -export class BoardEditor { - clickareasdiv = document.getElementById("clickareas"); - mousedown = false; - currentMode = "fill"; - fillPiece = 'G'; - fillRow = false; - override = false; - - elementEditButton = document.getElementById("editButton"); - - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - this.board = game.board; - } - - addListeners() { - // delegate listeners for efficiency - document.body.addEventListener("mousedown", (e) => { - if (e.target.classList.contains('clickmino')) { - const j = Number(e.target.dataset.x) - const i = Number(e.target.dataset.y) - if (this.game.settings.game.gamemode != 'custom') return; - if (this.fillRow) { this.fillWholeRow([j, 19 - i]) } - else { this.fillCell([j, 19 - i]); } - } - }); - - document.body.addEventListener("mouseenter", (e) => { - if (e.target.classList.contains('clickmino')) { - const j = Number(e.target.dataset.x) - const i = Number(e.target.dataset.y) - if (this.game.settings.game.gamemode != 'custom') return; - e.target.classList.add('highlighting') - if (this.mousedown) { - if (this.fillRow) { this.fillWholeRow([j, 19 - i]) } - else { this.fillCell([j, 19 - i]); } - } - } - }, true); - - document.body.addEventListener("mouseleave", (e) => { - if (e.target.classList.contains('clickmino')) { - e.target.classList.remove('highlighting') - } - }, true); - - document.body.addEventListener("mouseup", () => { - if (this.mousedown) this.game.history.save(); - this.mousedown = false; - }); - - for (let i = 0; i < 20; i++) { - for (let j = 0; j < 10; j++) { - const clickarea = document.createElement("div"); - clickarea.classList.add("clickmino"); - clickarea.dataset.x = j.toString(); - clickarea.dataset.y = i.toString(); - this.clickareasdiv.appendChild(clickarea); - } - } - } - - fillCell([x, y]) { - if (!this.mousedown) this.currentMode = this.board.checkMino([x, y], "S") ? "remove" : "fill"; - this.mousedown = true; - if (this.board.checkMino([x, y], "A")) return; - if (!this.override && this.currentMode == "fill" && this.board.checkMino([x, y], "S")) return; - this.board.setValue([x, y], this.currentMode == "fill" ? "S " + this.fillPiece : ""); - this.game.mechanics.setShadow(); - } - - fillWholeRow([x, y]) { - for (let i = 0; i < 10; i++) { - this.board.setValue([i, y], x == i ? "" : "S G"); - this.mousedown = true; - } - } - - convertToMap() { - const board = this.game.board.boardState; - const next = this.game.bag.nextPieces; - const hold = this.game.hold.piece == null ? "" : this.game.hold.piece.name; - const currPiece = this.game.falling.piece.name; - - let boardstring = board.toReversed().flatMap(row => { - return row.map(col => { - col = col.replace("Sh", "").replace("NP", "").replace("G", "#"); - if (col.length == 1) col = "" - if (col[0] == "A") col = ""; - if (col[0] == "S") col = col[2]; - if (col.trim() == "") col = "_"; - return col; - }) - }).join("") - return `${boardstring}?${currPiece},${next.flat()}?${hold}` - - } - - convertFromMap(string) { - let [board, next, hold] = string.split("?"); - board = board.match(/.{1,10}/g).toReversed().map(row => { - return row.split("").map(col => { - col = col.replace("#", "G").replace("_", "") - if (col != "") col = `S ${col}` - return col - }); - }) - return { board, next, hold } - } - - setEditButton(bool) { - this.elementEditButton.style.display = bool ? "flex" : "none"; - } +import { Game } from "../game.js"; + +export class BoardEditor { + clickareasdiv = document.getElementById("clickareas"); + mousedown = false; + currentMode = "fill"; + fillPiece = 'G'; + fillRow = false; + override = false; + + elementEditButton = document.getElementById("editButton"); + + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + this.board = game.board; + } + + addListeners() { + // delegate listeners for efficiency + document.body.addEventListener("mousedown", (e) => { + if (e.target.classList.contains('clickmino')) { + const j = Number(e.target.dataset.x) + const i = Number(e.target.dataset.y) + if (this.game.settings.game.gamemode != 'custom') return; + if (this.fillRow) { this.fillWholeRow([j, 19 - i]) } + else { this.fillCell([j, 19 - i]); } + } + }); + + document.body.addEventListener("mouseenter", (e) => { + if (e.target.classList.contains('clickmino')) { + const j = Number(e.target.dataset.x) + const i = Number(e.target.dataset.y) + if (this.game.settings.game.gamemode != 'custom') return; + e.target.classList.add('highlighting') + if (this.mousedown) { + if (this.fillRow) { this.fillWholeRow([j, 19 - i]) } + else { this.fillCell([j, 19 - i]); } + } + } + }, true); + + document.body.addEventListener("mouseleave", (e) => { + if (e.target.classList.contains('clickmino')) { + e.target.classList.remove('highlighting') + } + }, true); + + document.body.addEventListener("mouseup", () => { + if (this.mousedown) this.game.history.save(); + this.mousedown = false; + }); + + for (let i = 0; i < 20; i++) { + for (let j = 0; j < 10; j++) { + const clickarea = document.createElement("div"); + clickarea.classList.add("clickmino"); + clickarea.dataset.x = j.toString(); + clickarea.dataset.y = i.toString(); + this.clickareasdiv.appendChild(clickarea); + } + } + } + + fillCell([x, y]) { + if (!this.mousedown) this.currentMode = this.board.checkMino([x, y], "S") ? "remove" : "fill"; + this.mousedown = true; + if (this.board.checkMino([x, y], "A")) return; + if (!this.override && this.currentMode == "fill" && this.board.checkMino([x, y], "S")) return; + this.board.setValue([x, y], this.currentMode == "fill" ? "S " + this.fillPiece : ""); + this.game.mechanics.setShadow(); + } + + fillWholeRow([x, y]) { + for (let i = 0; i < 10; i++) { + this.board.setValue([i, y], x == i ? "" : "S G"); + this.mousedown = true; + } + } + + convertToMap() { + const board = this.game.board.boardState; + const next = this.game.bag.nextPieces; + const hold = this.game.hold.piece == null ? "" : this.game.hold.piece.name; + const currPiece = this.game.falling.piece.name; + + let boardstring = board.toReversed().flatMap(row => { + return row.map(col => { + col = col.replace("Sh", "").replace("NP", "").replace("G", "#"); + if (col.length == 1) col = "" + if (col[0] == "A") col = ""; + if (col[0] == "S") col = col[2]; + if (col.trim() == "") col = "_"; + return col; + }) + }).join("") + return `${boardstring}?${currPiece},${next.flat()}?${hold}` + + } + + convertFromMap(string) { + let [board, next, hold] = string.split("?"); + board = board.match(/.{1,10}/g).toReversed().map(row => { + return row.split("").map(col => { + col = col.replace("#", "G").replace("_", "") + if (col != "") col = `S ${col}` + return col + }); + }) + return { board, next, hold } + } + + setEditButton(bool) { + this.elementEditButton.style.display = bool ? "flex" : "none"; + } } \ No newline at end of file diff --git a/src/features/history.js b/src/features/history.js index 2df0386..e758536 100644 --- a/src/features/history.js +++ b/src/features/history.js @@ -1,191 +1,191 @@ -import { Game } from "../game.js"; - -export class History { - /** - * stores every game state indexed - * @type {string[]} - */ - historyStates = []; - /** - * stores connections by assigning index as the node, and the value as an array of every connection to other nodes - * @type {Number[][]} - */ - historyConnections = []; - currentState = 0; - selectedbranch = 0; - - historyelement = document.getElementById("history"); - choiceselement = document.getElementById("redochoices"); - - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - } - - save() { - if (!this.game.settings.game.history) return; - const map = this.convertToMapCompressed(); - this.pushHistory(map); - this.updateUI(); - } - - pushHistory(map) { - this.historyStates.push(map); - const prevState = this.currentState; - this.currentState = this.historyStates.length - 1; - if (this.currentState == 0) return; - - if (this.historyConnections[prevState] == null) this.historyConnections[prevState] = []; - this.historyConnections[prevState].push(this.currentState); - } - - load() { - const state = this.historyStates[this.currentState] - this.convertFromMapCompressed(state); - this.updateUI() - } - - undo() { - if (this.currentState == 0 || !this.game.settings.game.history) return; - this.historyConnections.forEach((next, ind) => { - if (next.includes(this.currentState)) { - this.currentState = ind; - } - }) - this.game.sounds.playSound("undo"); - this.game.boardeffects.move(-1, 0); - this.load() - } - - redo() { - if (!this.game.settings.game.history) return; - const connection = this.historyConnections[this.currentState]; - if (connection == undefined) return; - this.currentState = this.selectedbranch || Math.max(...connection); - this.game.sounds.playSound("redo"); - this.game.boardeffects.move(1, 0); - this.load() - } - - goto(num) { - this.currentState = num; - this.load(); - } - - clearFuture(node) { - const futureNodes = this.historyConnections[node]; - if (futureNodes != undefined) { - futureNodes.forEach(node => this.clearFuture(node)) - } - this.historyStates[node] = undefined; - this.historyConnections[node] = undefined; - } - - updateUI() { - const branches = this.historyConnections[this.currentState] ?? []; - this.selectedbranch = Math.max(...branches); - this.historyelement.textContent = `history: ${this.currentState}`; - if (branches.length <= 1) { - this.choiceselement.style.opacity = "0"; - this.choiceselement.style.pointerEvents = "none"; - this.selectedbranch = 0; - return; - } - - this.choiceselement.style.opacity = "1"; - this.choiceselement.style.pointerEvents = "all"; - [...this.choiceselement.children].forEach(button => button.remove()) - branches.forEach(state => { - const button = document.createElement("button"); - button.classList.add("redochoice"); - if (state == this.selectedbranch) button.classList.add("selected"); - button.textContent = state.toString(); - button.onclick = () => this.setSelected(state, button); - this.choiceselement.appendChild(button) - }) - } - - setSelected(state, button) { - this.selectedbranch = state; - [...this.choiceselement.children].forEach(el => { - el.classList.remove("selected"); - }) - button.classList.add("selected"); - } - - compress(s) { - // saves anywhere from 50% worst case to 90% on average - // 250 blocks is 30kB ~~ 5 min of 3.3pps play is 120kB - let cs = ""; - let count = 1; - for (let i = 1; i < s.length; i++) { - if (s[i] == s[i - 1]) { - count++; - } else { - cs += `${count}${s[i - 1]}`; - count = 1; - } - } - cs += `${count}${s[s.length - 1]}`; - return cs; - } - - decompress(s) { - let ds = ""; - let int = ''; - for (let i = 0; i < s.length; i++) { - if (!isNaN(parseInt(s[i]))) { - int += s[i]; - continue; - } - const count = parseInt(int); - const char = s[i]; - ds += char.repeat(count); - int = ''; - } - return ds; - } - - convertToMapCompressed() { - const board = this.game.board.boardState; - const next = this.game.bag.nextPieces; - const hold = this.game.hold.piece == null ? "" : this.game.hold.piece.name; - const currPiece = this.game.falling.piece == null ? "" : this.game.falling.piece.name; - let boardstring = board.toReversed().flatMap(row => { - return row.map(col => { - col = col.replace("Sh", "").replace("NP", "").replace("G", "#"); - col = col.trim(); - if (col.length == 1) col = "" - if (col[0] == "A") col = ""; - if (col[0] == "S") col = col[2]; - if (col == "") col = "_"; - return col; - }) - }).join("") - return `${this.compress(boardstring)}?${currPiece},${next.flat()}?${hold}` - - } - - convertFromMapCompressed(string) { - let [board, next, hold] = string.split("?"); - board = this.decompress(board); - board = board.match(/.{1,10}/g).toReversed().map(row => { - return row.split("").map(col => { - col = col.replace("#", "G").replace("_", "") - if (col != "") col = `S ${col}` - return col - }); - }) - this.game.board.boardState = board; - this.game.bag.nextPieces = [next.split(","), []]; - this.game.hold.piece = this.game.renderer.getPiece(hold); - this.game.mechanics.spawnPiece(this.game.bag.randomiser()); - } - - setHistoryDiv(bool) { - this.historyelement.style.display = bool ? "block" : "none"; - } - +import { Game } from "../game.js"; + +export class History { + /** + * stores every game state indexed + * @type {string[]} + */ + historyStates = []; + /** + * stores connections by assigning index as the node, and the value as an array of every connection to other nodes + * @type {Number[][]} + */ + historyConnections = []; + currentState = 0; + selectedbranch = 0; + + historyelement = document.getElementById("history"); + choiceselement = document.getElementById("redochoices"); + + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + } + + save() { + if (!this.game.settings.game.history) return; + const map = this.convertToMapCompressed(); + this.pushHistory(map); + this.updateUI(); + } + + pushHistory(map) { + this.historyStates.push(map); + const prevState = this.currentState; + this.currentState = this.historyStates.length - 1; + if (this.currentState == 0) return; + + if (this.historyConnections[prevState] == null) this.historyConnections[prevState] = []; + this.historyConnections[prevState].push(this.currentState); + } + + load() { + const state = this.historyStates[this.currentState] + this.convertFromMapCompressed(state); + this.updateUI() + } + + undo() { + if (this.currentState == 0 || !this.game.settings.game.history) return; + this.historyConnections.forEach((next, ind) => { + if (next.includes(this.currentState)) { + this.currentState = ind; + } + }) + this.game.sounds.playSound("undo"); + this.game.boardeffects.move(-1, 0); + this.load() + } + + redo() { + if (!this.game.settings.game.history) return; + const connection = this.historyConnections[this.currentState]; + if (connection == undefined) return; + this.currentState = this.selectedbranch || Math.max(...connection); + this.game.sounds.playSound("redo"); + this.game.boardeffects.move(1, 0); + this.load() + } + + goto(num) { + this.currentState = num; + this.load(); + } + + clearFuture(node) { + const futureNodes = this.historyConnections[node]; + if (futureNodes != undefined) { + futureNodes.forEach(node => this.clearFuture(node)) + } + this.historyStates[node] = undefined; + this.historyConnections[node] = undefined; + } + + updateUI() { + const branches = this.historyConnections[this.currentState] ?? []; + this.selectedbranch = Math.max(...branches); + this.historyelement.textContent = `history: ${this.currentState}`; + if (branches.length <= 1) { + this.choiceselement.style.opacity = "0"; + this.choiceselement.style.pointerEvents = "none"; + this.selectedbranch = 0; + return; + } + + this.choiceselement.style.opacity = "1"; + this.choiceselement.style.pointerEvents = "all"; + [...this.choiceselement.children].forEach(button => button.remove()) + branches.forEach(state => { + const button = document.createElement("button"); + button.classList.add("redochoice"); + if (state == this.selectedbranch) button.classList.add("selected"); + button.textContent = state.toString(); + button.onclick = () => this.setSelected(state, button); + this.choiceselement.appendChild(button) + }) + } + + setSelected(state, button) { + this.selectedbranch = state; + [...this.choiceselement.children].forEach(el => { + el.classList.remove("selected"); + }) + button.classList.add("selected"); + } + + compress(s) { + // saves anywhere from 50% worst case to 90% on average + // 250 blocks is 30kB ~~ 5 min of 3.3pps play is 120kB + let cs = ""; + let count = 1; + for (let i = 1; i < s.length; i++) { + if (s[i] == s[i - 1]) { + count++; + } else { + cs += `${count}${s[i - 1]}`; + count = 1; + } + } + cs += `${count}${s[s.length - 1]}`; + return cs; + } + + decompress(s) { + let ds = ""; + let int = ''; + for (let i = 0; i < s.length; i++) { + if (!isNaN(parseInt(s[i]))) { + int += s[i]; + continue; + } + const count = parseInt(int); + const char = s[i]; + ds += char.repeat(count); + int = ''; + } + return ds; + } + + convertToMapCompressed() { + const board = this.game.board.boardState; + const next = this.game.bag.nextPieces; + const hold = this.game.hold.piece == null ? "" : this.game.hold.piece.name; + const currPiece = this.game.falling.piece == null ? "" : this.game.falling.piece.name; + let boardstring = board.toReversed().flatMap(row => { + return row.map(col => { + col = col.replace("Sh", "").replace("NP", "").replace("G", "#"); + col = col.trim(); + if (col.length == 1) col = "" + if (col[0] == "A") col = ""; + if (col[0] == "S") col = col[2]; + if (col == "") col = "_"; + return col; + }) + }).join("") + return `${this.compress(boardstring)}?${currPiece},${next.flat()}?${hold}` + + } + + convertFromMapCompressed(string) { + let [board, next, hold] = string.split("?"); + board = this.decompress(board); + board = board.match(/.{1,10}/g).toReversed().map(row => { + return row.split("").map(col => { + col = col.replace("#", "G").replace("_", "") + if (col != "") col = `S ${col}` + return col + }); + }) + this.game.board.boardState = board; + this.game.bag.nextPieces = [next.split(","), []]; + this.game.hold.piece = this.game.renderer.getPiece(hold); + this.game.mechanics.spawnPiece(this.game.bag.randomiser()); + } + + setHistoryDiv(bool) { + this.historyelement.style.display = bool ? "block" : "none"; + } + } \ No newline at end of file diff --git a/src/features/modes.js b/src/features/modes.js index e89226a..51d0c57 100644 --- a/src/features/modes.js +++ b/src/features/modes.js @@ -1,141 +1,141 @@ -import { Game } from "../game.js"; -import gamemodeJSON from "../data/gamemodes.json" with { type: "json" }; -import { gameoverResultText, gameoverText, resultSuffix } from "../data/data.js"; - -export class Modes { - elementobjectives = document.getElementById("objective"); - divObjectiveText = document.getElementById("objectiveText"); - modeJSON; - customSettings; - - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - } - - checkFinished() { - const goals = this.game.settings.game; - const stats = this.game.stats; - - // hardcoded objectives - let combobreak = this.game.stats.combo == -1 && stats.clearlines >= 1 && this.modeJSON.target == 'combobreak'; - let gameend = this.game.ended && this.modeJSON.target == 'gameEnd'; - - let stat = stats[this.modeJSON.goalStat] - let goal = goals[this.modeJSON.target] - let result = stats[this.modeJSON.result] - - if (stat >= goal || combobreak || gameend) { - result = Math.round(result * 1000) / 1000 - stat = Math.round(stat * 1000) / 1000 - this.game.profilestats.setPB(result); - const text = this.statText(this.modeJSON.goalStat, stat, this.modeJSON.result, result) - const suffix = resultSuffix[this.modeJSON.result] - this.game.endGame(result + suffix, text); - } - - if (this.game.settings.game.gamemode == 'ultra') { // changes ultra sidebar - stat = stats.score; - goal = undefined - } - this.setObjectiveText(stat, goal); - } - - statText(stat, value, result, resultvalue) { - const front = gameoverText[stat].replace("_", value); - const back = gameoverResultText[result].replace("_", resultvalue); - return front + back; - } - - setObjectiveText(statValue, resultValue) { - if (statValue != undefined) statValue = Math.round(statValue * 1000) / 1000 - let modetext = (statValue == undefined ? '' : statValue) - + (resultValue == undefined ? '' : `/${resultValue}`) - this.elementobjectives.textContent = modetext; - } - - loadModes() { - let currentGamemode = this.game.settings.game.gamemode; - if (typeof currentGamemode == 'number') { // backwards compatibility - this.game.settings.game.gamemode = 'sprint' - currentGamemode = 'sprint' - } - this.setGamemode(currentGamemode); - this.divObjectiveText.textContent = this.modeJSON.objectiveText; - } - - setGamemode(mode) { - this.game.settings.game.gamemode = mode; - const competitive = this.game.settings.game.competitiveMode; - const custom = JSON.parse(localStorage.getItem('customGame')); - - if (competitive) { - if (custom == null) { - localStorage.setItem('customGame', JSON.stringify(this.game.settings.game)); - } - this.modeJSON = this.getGamemodeJSON(mode); - this.game.settings.game = { ...this.game.settings.game, ...this.modeJSON.settings }; - } else { - if (custom != null) { - this.game.settings.game = custom; - this.game.settings.game.competitiveMode = false; - localStorage.removeItem('customGame'); - } - this.modeJSON = this.getGamemodeJSON(mode); - } - this.toggleDialogState(competitive); - this.game.menuactions.saveSettings(); - } - - toggleDialogState(enabled) { - document.getElementById('game').disabled = enabled; - document.getElementById('goals').disabled = enabled; - } - - getGamemodeJSON(mode) { - const modeinfo = gamemodeJSON[mode]; - const allinfo = gamemodeJSON["*"]; - - let info = {} - Object.keys(allinfo).forEach(key => info[key] = modeinfo[key] ?? allinfo[key]); - info.settings = { ...allinfo.settings, ...modeinfo.settings } - - return info; - } - - getGamemodeNames() { - return Object.keys(gamemodeJSON).filter(key => key != "*"); - } - - getSuffix(mode) { - const modeinfo = gamemodeJSON[mode] ?? {}; - return resultSuffix[modeinfo.result] ?? " (legacy)"; - } - - diggerAddGarbage(removed) { - if (this.game.stats.getRemainingGarbage() > 10 && this.game.settings.game.gamemode == "digger") - this.game.mechanics.addGarbage(removed); - } - - set4WCols(start) { - if (this.game.settings.game.gamemode == 'combo') this.game.board.setComboBoard(start); - - } - - startSurvival() { - const time = (60 * 1000) / this.game.settings.game.survivalRate; - if (this.game.settings.game.gamemode == 'survival') - this.game.survivalTimer = setInterval(() => this.game.mechanics.addGarbage(1), time); - } - - diggerGarbageSet(start) { - const rows = - this.game.settings.game.requiredGarbage < 10 - ? this.game.settings.game.requiredGarbage - : 10; - if (this.game.stats.getRemainingGarbage() > 0 && start && this.game.settings.game.gamemode == 'digger') - this.game.mechanics.addGarbage(rows); - } +import { Game } from "../game.js"; +import gamemodeJSON from "../data/gamemodes.json" with { type: "json" }; +import { gameoverResultText, gameoverText, resultSuffix } from "../data/data.js"; + +export class Modes { + elementobjectives = document.getElementById("objective"); + divObjectiveText = document.getElementById("objectiveText"); + modeJSON; + customSettings; + + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + } + + checkFinished() { + const goals = this.game.settings.game; + const stats = this.game.stats; + + // hardcoded objectives + let combobreak = this.game.stats.combo == -1 && stats.clearlines >= 1 && this.modeJSON.target == 'combobreak'; + let gameend = this.game.ended && this.modeJSON.target == 'gameEnd'; + + let stat = stats[this.modeJSON.goalStat] + let goal = goals[this.modeJSON.target] + let result = stats[this.modeJSON.result] + + if (stat >= goal || combobreak || gameend) { + if(this.game.settings.game.gamemode != "race" ) result = Math.round(result * 1000) / 1000 + stat = Math.round(stat * 1000) / 1000 + this.game.profilestats.setPB(result); + const text = this.statText(this.modeJSON.goalStat, stat, this.modeJSON.result, result) + const suffix = resultSuffix[this.modeJSON.result] + this.game.endGame(result + suffix, text); + } + + if (this.game.settings.game.gamemode == 'ultra') { // changes ultra sidebar + stat = stats.score; + goal = undefined + } + this.setObjectiveText(stat, goal); + } + + statText(stat, value, result, resultvalue) { + const front = gameoverText[stat].replace("_", value); + const back = gameoverResultText[result].replace("_", resultvalue); + return front + back; + } + + setObjectiveText(statValue, resultValue) { + if (statValue != undefined) statValue = Math.round(statValue * 1000) / 1000 + let modetext = (statValue == undefined ? '' : statValue) + + (resultValue == undefined ? '' : `/${resultValue}`) + this.elementobjectives.textContent = modetext; + } + + loadModes() { + let currentGamemode = this.game.settings.game.gamemode; + if (typeof currentGamemode == 'number') { // backwards compatibility + this.game.settings.game.gamemode = 'sprint' + currentGamemode = 'sprint' + } + this.setGamemode(currentGamemode); + this.divObjectiveText.textContent = this.modeJSON.objectiveText; + } + + setGamemode(mode) { + this.game.settings.game.gamemode = mode; + const competitive = this.game.settings.game.competitiveMode; + const custom = JSON.parse(localStorage.getItem('customGame')); + + if (competitive) { + if (custom == null) { + localStorage.setItem('customGame', JSON.stringify(this.game.settings.game)); + } + this.modeJSON = this.getGamemodeJSON(mode); + this.game.settings.game = { ...this.game.settings.game, ...this.modeJSON.settings }; + } else { + if (custom != null) { + this.game.settings.game = custom; + this.game.settings.game.competitiveMode = false; + localStorage.removeItem('customGame'); + } + this.modeJSON = this.getGamemodeJSON(mode); + } + this.toggleDialogState(competitive); + this.game.menuactions.saveSettings(); + } + + toggleDialogState(enabled) { + document.getElementById('game').disabled = enabled; + document.getElementById('goals').disabled = enabled; + } + + getGamemodeJSON(mode) { + const modeinfo = gamemodeJSON[mode]; + const allinfo = gamemodeJSON["*"]; + + let info = {} + Object.keys(allinfo).forEach(key => info[key] = modeinfo[key] ?? allinfo[key]); + info.settings = { ...allinfo.settings, ...modeinfo.settings } + + return info; + } + + getGamemodeNames() { + return Object.keys(gamemodeJSON).filter(key => key != "*"); + } + + getSuffix(mode) { + const modeinfo = gamemodeJSON[mode] ?? {}; + return resultSuffix[modeinfo.result] ?? " (legacy)"; + } + + diggerAddGarbage(removed) { + if (this.game.stats.getRemainingGarbage() > 10 && this.game.settings.game.gamemode == "digger") + this.game.mechanics.addGarbage(removed); + } + + set4WCols(start) { + if (this.game.settings.game.gamemode == 'combo') this.game.board.setComboBoard(start); + + } + + startSurvival() { + const time = (60 * 1000) / this.game.settings.game.survivalRate; + if (this.game.settings.game.gamemode == 'survival') + this.game.survivalTimer = setInterval(() => this.game.mechanics.addGarbage(1), time); + } + + diggerGarbageSet(start) { + const rows = + this.game.settings.game.requiredGarbage < 10 + ? this.game.settings.game.requiredGarbage + : 10; + if (this.game.stats.getRemainingGarbage() > 0 && start && this.game.settings.game.gamemode == 'digger') + this.game.mechanics.addGarbage(rows); + } } \ No newline at end of file diff --git a/src/features/profileStats.js b/src/features/profileStats.js index 4427eb4..9baf6b9 100644 --- a/src/features/profileStats.js +++ b/src/features/profileStats.js @@ -1,86 +1,86 @@ -import { lowerIsBetter } from "../data/data.js"; -import { Game } from "../game.js"; - -export class ProfileStats { - personalBests = {}; - notSaved = ['game', 'level', 'combo'] - - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - } - - setPB(score) { - this.game.elementGameEndTitle.textContent = 'GAME ENDED'; - const gamemode = this.game.settings.game.gamemode - const gamemodeStats = this.personalBests[gamemode] ?? {}; - const currentScore = Number(gamemodeStats.score); - const lower = lowerIsBetter[this.game.modes.modeJSON.result]; - - if (!this.game.settings.game.competitiveMode) return; - - if (isNaN(currentScore) || (lower && score < currentScore) || (!lower && score > currentScore)) { - let gameStatsKeys = Object.getOwnPropertyNames(this.game.stats) - gameStatsKeys = gameStatsKeys.filter(key => key != 'game') - const gameStats = {}; - gameStatsKeys.forEach(key => gameStats[key] = this.game.stats[key]) - const ts = new Date().toJSON(); - this.personalBests[gamemode] = { score, pbstats: gameStats, version: this.game.version, ts }; - this.game.elementGameEndTitle.textContent = 'NEW PB!'; - setTimeout(() => this.game.sounds.playSound("personalbest"), 1000); - - this.game.modals.generate.notif("PB Saved", `PB on ${gamemode} saved`, "success"); - } - } - - loadPBs() { - const stats = JSON.parse(localStorage.getItem("stats")) ?? {}; - this.personalBests = stats.pbs ?? {}; - } - - removePB(mode) { - delete this.personalBests[mode]; - this.saveSession(); - } - - saveSession() { - const prevstats = JSON.parse(localStorage.getItem("stats")) ?? {}; - const statsData = prevstats.lifetime ?? {}; - - Object.getOwnPropertyNames(this.game.stats).forEach(key => { - if (this.notSaved.includes(key)) return; - - if (key == 'clearCols' || key == 'tspins') { - if (statsData[key] == undefined || typeof statsData[key] != 'object') statsData[key] = [] - this.game.stats[key].forEach((_, index) => { - if (statsData[key][index] == undefined) statsData[key][index] = 0 - statsData[key][index] += this.game.stats[key][index]; - }); - return; - } - if (key == 'clearPieces') { - if (statsData[key] == undefined) { - statsData[key] = this.game.stats[key]; - return; - } - Object.keys(this.game.stats[key]).forEach(piece => { - this.game.stats[key][piece].forEach((_, index) => { - if (statsData[key][piece] == undefined) statsData[key][piece] = [0, 0, 0, 0]; - statsData[key][piece][index] += this.game.stats[key][piece][index]; - }); - }); - return; - } - if (key == "maxBTB" || key == "maxCombo") { - if (statsData[key] < this.game.stats[key] ) statsData[key] = this.game.stats[key]; - return; - } - if (this.game.stats[key] < 0) this.game.stats[key] = 0 - statsData[key] += this.game.stats[key]; - }) - const stats = { pbs: this.personalBests, lifetime: statsData } - localStorage.setItem("stats", JSON.stringify(stats)); - } +import { lowerIsBetter } from "../data/data.js"; +import { Game } from "../game.js"; + +export class ProfileStats { + personalBests = {}; + notSaved = ['game', 'level', 'combo'] + + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + } + + setPB(score) { + this.game.elementGameEndTitle.textContent = 'GAME ENDED'; + const gamemode = this.game.settings.game.gamemode + const gamemodeStats = this.personalBests[gamemode] ?? {}; + const currentScore = Number(gamemodeStats.score); + const lower = lowerIsBetter[this.game.modes.modeJSON.result]; + + if (!this.game.settings.game.competitiveMode) return; + + if (isNaN(currentScore) || (lower && score < currentScore) || (!lower && score > currentScore)) { + let gameStatsKeys = Object.getOwnPropertyNames(this.game.stats) + gameStatsKeys = gameStatsKeys.filter(key => key != 'game') + const gameStats = {}; + gameStatsKeys.forEach(key => gameStats[key] = this.game.stats[key]) + const ts = new Date().toJSON(); + this.personalBests[gamemode] = { score, pbstats: gameStats, version: this.game.version, ts }; + this.game.elementGameEndTitle.textContent = 'NEW PB!'; + setTimeout(() => this.game.sounds.playSound("personalbest"), 1000); + + this.game.modals.generate.notif("PB Saved", `PB on ${gamemode} saved`, "success"); + } + } + + loadPBs() { + const stats = JSON.parse(localStorage.getItem("stats")) ?? {}; + this.personalBests = stats.pbs ?? {}; + } + + removePB(mode) { + delete this.personalBests[mode]; + this.saveSession(); + } + + saveSession() { + const prevstats = JSON.parse(localStorage.getItem("stats")) ?? {}; + const statsData = prevstats.lifetime ?? {}; + + Object.getOwnPropertyNames(this.game.stats).forEach(key => { + if (this.notSaved.includes(key)) return; + + if (key == 'clearCols' || key == 'tspins') { + if (statsData[key] == undefined || typeof statsData[key] != 'object') statsData[key] = [] + this.game.stats[key].forEach((_, index) => { + if (statsData[key][index] == undefined) statsData[key][index] = 0 + statsData[key][index] += this.game.stats[key][index]; + }); + return; + } + if (key == 'clearPieces') { + if (statsData[key] == undefined) { + statsData[key] = this.game.stats[key]; + return; + } + Object.keys(this.game.stats[key]).forEach(piece => { + this.game.stats[key][piece].forEach((_, index) => { + if (statsData[key][piece] == undefined) statsData[key][piece] = [0, 0, 0, 0]; + statsData[key][piece][index] += this.game.stats[key][piece][index]; + }); + }); + return; + } + if (key == "maxBTB" || key == "maxCombo") { + if (statsData[key] < this.game.stats[key] ) statsData[key] = this.game.stats[key]; + return; + } + if (this.game.stats[key] < 0) this.game.stats[key] = 0 + statsData[key] += this.game.stats[key]; + }) + const stats = { pbs: this.personalBests, lifetime: statsData } + localStorage.setItem("stats", JSON.stringify(stats)); + } } \ No newline at end of file diff --git a/src/features/settings.js b/src/features/settings.js index 59abc67..fec2775 100644 --- a/src/features/settings.js +++ b/src/features/settings.js @@ -1,74 +1,74 @@ -import { Game } from "../game.js"; -import defaultSettings from "../data/defaultSettings.json" with { type: "json" }; - -export class Settings { - /** - * @param {Game} game - */ - constructor(game) { - this.gameObject = game; - this.loadDefault(); - } - - loadDefault() { - Object.keys(defaultSettings).forEach(type => { - this[type] = defaultSettings[type]; - }) - - // this is for type checking lmao - return; - this.game = defaultSettings.game; - this.display = defaultSettings.display; - this.control = defaultSettings.control; - this.handling = defaultSettings.handling; - this.volume = defaultSettings.volume; - } - - load(data) { - if (data instanceof Array) data = this.convert(data); - Object.keys(data).forEach(type => { - Object.keys(data[type]).forEach(setting => { - if (data[type][setting] === undefined || data[type][setting] === "") return; - this[type][setting] = data[type][setting]; - }) - }) - } - - save() { - const data = {}; - Object.getOwnPropertyNames(this).forEach(key => { - if (key == 'gameObject') return; - data[key] = this[key]; - }) - return data; - } - - // for backwards compatibility - convert(arr) { - const display = arr[0] - const game = arr[1] - const control = arr[2] - const handling = { - das: game.das, - arr: game.arr, - sdarr: game.sdarr - } - const volume = { - audioLevel: display.audioLevel, - sfxLevel: display.sfxLevel - } - game.das = undefined - game.arr = undefined - game.sdarr = undefined - display.audioLevel = undefined - display.sfxLevel = undefined - - return { display, game, control, handling, volume }; - } - - reset(group) { - for (let setting in this[group]) { - this[group][setting] = ""; - } - } +import { Game } from "../game.js"; +import defaultSettings from "../data/defaultSettings.json" with { type: "json" }; + +export class Settings { + /** + * @param {Game} game + */ + constructor(game) { + this.gameObject = game; + this.loadDefault(); + } + + loadDefault() { + Object.keys(defaultSettings).forEach(type => { + this[type] = defaultSettings[type]; + }) + + // this is for type checking lmao + return; + this.game = defaultSettings.game; + this.display = defaultSettings.display; + this.control = defaultSettings.control; + this.handling = defaultSettings.handling; + this.volume = defaultSettings.volume; + } + + load(data) { + if (data instanceof Array) data = this.convert(data); + Object.keys(data).forEach(type => { + Object.keys(data[type]).forEach(setting => { + if (data[type][setting] === undefined || data[type][setting] === "") return; + this[type][setting] = data[type][setting]; + }) + }) + } + + save() { + const data = {}; + Object.getOwnPropertyNames(this).forEach(key => { + if (key == 'gameObject') return; + data[key] = this[key]; + }) + return data; + } + + // for backwards compatibility + convert(arr) { + const display = arr[0] + const game = arr[1] + const control = arr[2] + const handling = { + das: game.das, + arr: game.arr, + sdarr: game.sdarr + } + const volume = { + audioLevel: display.audioLevel, + sfxLevel: display.sfxLevel + } + game.das = undefined + game.arr = undefined + game.sdarr = undefined + display.audioLevel = undefined + display.sfxLevel = undefined + + return { display, game, control, handling, volume }; + } + + reset(group) { + for (let setting in this[group]) { + this[group][setting] = ""; + } + } } \ No newline at end of file diff --git a/src/features/sounds.js b/src/features/sounds.js index 2a1d360..8ac415b 100644 --- a/src/features/sounds.js +++ b/src/features/sounds.js @@ -1,127 +1,127 @@ -import sfxobj from "../data/sfxlist.json" with { type: "json" }; -import { songsobj } from "../data/data.js"; -import { Game } from "../game.js"; - -export class Sounds { - sfx = {}; - songs = []; - songNames = []; - curSongIdx = 0; - elSongProgress = document.getElementById("songProgress"); - elSongText = document.getElementById("songText"); - - lowpassfilter; - audioContext; - - - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - } - - /** - * - * @param {string} audioName - * Name of audio as specified in sfxlist.json - * @param {Boolean} replace - * If true, stops currently playing audio and starts new one - * If false, skips if audio is already playing - */ - playSound(audioName, replace = true, silent = false) { - if (this.sfx[audioName] == undefined) return; - this.sfx[audioName].muted = silent; - this.sfx[audioName].volume = this.game.settings.volume.sfxLevel / 1000; - if (!replace && !this.sfx[audioName].ended && this.sfx[audioName].currentTime != 0) return; - this.sfx[audioName].currentTime = 0; - this.sfx[audioName].play(); - } - - startSong() { - this.elSongText.textContent = `Now Playing ${this.songNames[this.curSongIdx]}`; - this.songs[this.curSongIdx].onended = () => { - this.endSong(); - this.startSong(); - }; - this.songs[this.curSongIdx].volume = this.game.settings.volume.audioLevel / 1000; - this.songs[this.curSongIdx].play(); - } - - endSong() { - this.songs[this.curSongIdx].pause(); - this.songs[this.curSongIdx].currentTime = 0; - this.songs[this.curSongIdx].onended = () => { }; - this.curSongIdx = (this.curSongIdx + 1) % this.songs.length; - } - - pauseSong() { - if (this.songs[this.curSongIdx].paused) { - this.songs[this.curSongIdx].play(); - this.elSongText.textContent = `Playing ${this.songNames[this.curSongIdx]}`; - } else { - this.songs[this.curSongIdx].pause(); - this.elSongText.textContent = `Not Playing`; - - } - } - - addMenuSFX() { - let hoverSFX = (e) => { - document.querySelectorAll(e).forEach(el => (el.addEventListener("mouseenter", () => this.game.sounds.playSound("menutap")))); - }; - let clickSFX = (e) => { - document.querySelectorAll(e).forEach(el => (el.addEventListener("click", () => this.game.sounds.playSound("menuclick")))); - }; - hoverSFX(".settingRow"); - hoverSFX(".closeDialogButton"); - hoverSFX(".gamemodeSelect"); - hoverSFX(".settingPanelButton"); - clickSFX(".settingPanelButton"); - clickSFX(".closeDialogButton"); - } - - initSounds() { - setInterval(() => { - if (this.songs[this.curSongIdx].currentTime == 0) return; - this.elSongProgress.value = - (this.songs[this.curSongIdx].currentTime * 100) / this.songs[this.curSongIdx].duration; - }, 2000); - - // preload all sfx - sfxobj.forEach(file => { - const name = file.name.split(".")[0]; - const a = new Audio(file.path); - this.sfx[name] = a; - this.playSound(name, false, true); - }) - - - this.audioContext = new window.AudioContext(); - this.lowpassfilter = this.audioContext.createBiquadFilter(); - this.lowpassfilter.type = "lowpass"; - this.lowpassfilter.frequency.value = 20000; - - songsobj.forEach(file => { - const songaudio = new Audio(file.path); - this.songs.push(songaudio); - this.songNames.push(file.name.split(".")[0]); - - const track = this.audioContext.createMediaElementSource(songaudio); - track.connect(this.lowpassfilter); - this.lowpassfilter.connect(this.audioContext.destination); - }) - // this.playSound("allclear") // wait literally how is this fine by chrome - } - - setAudioLevel() { - this.songs[this.curSongIdx].volume = Number(this.game.settings.volume.audioLevel) / 1000; - } - - toggleSongMuffle(muffled) { - const currentTime = this.audioContext.currentTime; - this.lowpassfilter.frequency.cancelScheduledValues(currentTime); - this.lowpassfilter.frequency.setValueAtTime(this.lowpassfilter.frequency.value, currentTime); - this.lowpassfilter.frequency.exponentialRampToValueAtTime(muffled ? 300 : 20000, currentTime + 1); - } -} +import sfxobj from "../data/sfxlist.json" with { type: "json" }; +import { songsobj } from "../data/data.js"; +import { Game } from "../game.js"; + +export class Sounds { + sfx = {}; + songs = []; + songNames = []; + curSongIdx = 0; + elSongProgress = document.getElementById("songProgress"); + elSongText = document.getElementById("songText"); + + lowpassfilter; + audioContext; + + + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + } + + /** + * + * @param {string} audioName + * Name of audio as specified in sfxlist.json + * @param {Boolean} replace + * If true, stops currently playing audio and starts new one + * If false, skips if audio is already playing + */ + playSound(audioName, replace = true, silent = false) { + if (this.sfx[audioName] == undefined) return; + this.sfx[audioName].muted = silent; + this.sfx[audioName].volume = this.game.settings.volume.sfxLevel / 1000; + if (!replace && !this.sfx[audioName].ended && this.sfx[audioName].currentTime != 0) return; + this.sfx[audioName].currentTime = 0; + this.sfx[audioName].play(); + } + + startSong() { + this.elSongText.textContent = `Now Playing ${this.songNames[this.curSongIdx]}`; + this.songs[this.curSongIdx].onended = () => { + this.endSong(); + this.startSong(); + }; + this.songs[this.curSongIdx].volume = this.game.settings.volume.audioLevel / 1000; + this.songs[this.curSongIdx].play(); + } + + endSong() { + this.songs[this.curSongIdx].pause(); + this.songs[this.curSongIdx].currentTime = 0; + this.songs[this.curSongIdx].onended = () => { }; + this.curSongIdx = (this.curSongIdx + 1) % this.songs.length; + } + + pauseSong() { + if (this.songs[this.curSongIdx].paused) { + this.songs[this.curSongIdx].play(); + this.elSongText.textContent = `Playing ${this.songNames[this.curSongIdx]}`; + } else { + this.songs[this.curSongIdx].pause(); + this.elSongText.textContent = `Not Playing`; + + } + } + + addMenuSFX() { + let hoverSFX = (e) => { + document.querySelectorAll(e).forEach(el => (el.addEventListener("mouseenter", () => this.game.sounds.playSound("menutap")))); + }; + let clickSFX = (e) => { + document.querySelectorAll(e).forEach(el => (el.addEventListener("click", () => this.game.sounds.playSound("menuclick")))); + }; + hoverSFX(".settingRow"); + hoverSFX(".closeDialogButton"); + hoverSFX(".gamemodeSelect"); + hoverSFX(".settingPanelButton"); + clickSFX(".settingPanelButton"); + clickSFX(".closeDialogButton"); + } + + initSounds() { + setInterval(() => { + if (this.songs[this.curSongIdx].currentTime == 0) return; + this.elSongProgress.value = + (this.songs[this.curSongIdx].currentTime * 100) / this.songs[this.curSongIdx].duration; + }, 2000); + + // preload all sfx + sfxobj.forEach(file => { + const name = file.name.split(".")[0]; + const a = new Audio(file.path); + this.sfx[name] = a; + this.playSound(name, false, true); + }) + + + this.audioContext = new window.AudioContext(); + this.lowpassfilter = this.audioContext.createBiquadFilter(); + this.lowpassfilter.type = "lowpass"; + this.lowpassfilter.frequency.value = 20000; + + songsobj.forEach(file => { + const songaudio = new Audio(file.path); + this.songs.push(songaudio); + this.songNames.push(file.name.split(".")[0]); + + const track = this.audioContext.createMediaElementSource(songaudio); + track.connect(this.lowpassfilter); + this.lowpassfilter.connect(this.audioContext.destination); + }) + // this.playSound("allclear") // wait literally how is this fine by chrome + } + + setAudioLevel() { + this.songs[this.curSongIdx].volume = Number(this.game.settings.volume.audioLevel) / 1000; + } + + toggleSongMuffle(muffled) { + const currentTime = this.audioContext.currentTime; + this.lowpassfilter.frequency.cancelScheduledValues(currentTime); + this.lowpassfilter.frequency.setValueAtTime(this.lowpassfilter.frequency.value, currentTime); + this.lowpassfilter.frequency.exponentialRampToValueAtTime(muffled ? 300 : 20000, currentTime + 1); + } +} diff --git a/src/features/stats.js b/src/features/stats.js index 3563979..1ffbd8b 100644 --- a/src/features/stats.js +++ b/src/features/stats.js @@ -1,134 +1,137 @@ -import { levellingTable } from "../data/data.js"; -import { Game } from "../game.js"; - -export class GameStats { - // game stats - time = 0; - clearlines = 0; - pieceCount = 0; - score = 0; - pcs = 0; - quads = 0; - tspins = [0, 0, 0, 0]; - allspins = 0; - level = 0; - altitude = 0; - floor = 1; - climbSpeed = 1; - - // garbage stats - attack = 0; - cleargarbage = 0; - sent = 0; - recieved = 0; - - // modifier stats - combo = -1; - maxCombo = -1; - btbCount = -1; - maxBTB = -1; - - // calculated stats - pps = 0; - apm = 0; - vs = 0; // tetrio versus score - lpm = 0; // lines per minute - app = 0; - apl = 0; // attack per line - appw = 0; // weighted attack per piece - ppb = 0; - dss = 0; // garbage per second - dsp = 0; // garbage per piece - chzind = 0; // cheese index - garbeff = 0; // garbage efficiency - vsOnApm = 0; // vs / apm - - // x piece efficiency - tpE = 0; - ipE = 0; - - // input stats - inputs = 0; - holds = 0; - rotates = 0; - kps = 0; - kpp = 0; - - clearCols = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // amount cleared in a col = clearCols[col - 1] - clearPieces = { // lineclears by piece = clearPieces[piece][line_count - 1] - i: [0, 0, 0, 0], - j: [0, 0, 0, 0], - l: [0, 0, 0, 0], - o: [0, 0, 0, 0], - t: [0, 0, 0, 0], - s: [0, 0, 0, 0], - z: [0, 0, 0, 0], - }; - - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - } - - updateStats() { - this.time += 1 / this.game.tickrate; - - this.pps = this.pieceCount / this.time; - this.apm = this.attack * 60 / this.time; - this.vs = (this.attack + this.cleargarbage) * 100 / this.time; - - this.lpm = this.clearlines * 60 / this.time; - this.app = this.attack / this.pieceCount || 0; - this.apl = this.attack / this.clearlines || 0; - this.ppb = this.score / this.pieceCount || 0; - this.dss = this.cleargarbage / this.time - this.dsp = this.cleargarbage / this.pieceCount || 0; - this.vsOnApm = this.vs / this.apm || 0; - this.chzind = 25 * (this.dsp * 6 + this.vsOnApm * 2 - this.app * 5 - 1); - this.garbeff = this.app * this.dsp * 2; - this.appw = this.app - 5 * Math.tan(1 - this.chzind / 30) || 0; - this.kps = this.inputs / this.time; - this.kpp = this.inputs / this.pieceCount || 0; - - this.tpE = this.tspins.reduce((a, b) => a + b, 0) * 700 / this.pieceCount || 0; - this.ipE = this.quads * 700 / this.pieceCount || 0; - // use of || 0 to not show NaN - } - - checkInvis() { - return this.pieceCount % this.game.settings.game.lookAheadPieces == 0 && !this.game.falling.moved - } - - getRemainingGarbage() { - return this.game.settings.game.requiredGarbage - this.cleargarbage - } - - updateBTB(isBTB, count) { - this.btbCount = isBTB ? - this.btbCount + 1 : - count == 0 ? this.btbCount : -1; - if (this.btbCount > this.maxBTB) this.maxBTB = this.btbCount; - } - - updateCombo(count) { - this.combo = count == 0 ? -1 : this.combo + 1; - if (this.combo > this.maxCombo) this.maxCombo = this.combo; - } - - incrementStats(score, count, damage, isPC, isTspin, isAllspin, garb) { - this.score += score; - this.clearlines += count; - this.attack += damage; - this.quads += count >= 4 ? 1 : 0; - this.pcs += isPC ? 1 : 0; - this.cleargarbage += garb; - - if (isTspin) this.tspins[count]++; - this.allspins += isAllspin ? 1 : 0; - this.level += levellingTable[count]; - if (count > 0) this.clearPieces[this.game.falling.piece.name][count - 1]++; - } - +import { levellingTable } from "../data/data.js"; +import { Game } from "../game.js"; + +export class GameStats { + // game stats + time = 0; + clearlines = 0; + pieceCount = 0; + score = 0; + pcs = 0; + quads = 0; + tspins = [0, 0, 0, 0]; + allspins = 0; + tgm_level = 0; + altitude = 0; + floor = 1; + grade = "9"; + climbSpeed = 1; + + // garbage stats + attack = 0; + cleargarbage = 0; + sent = 0; + recieved = 0; + + // modifier stats + combo = -1; + maxCombo = -1; + btbCount = -1; + maxBTB = -1; + + // calculated stats + pps = 0; + apm = 0; + vs = 0; // tetrio versus score + lpm = 0; // lines per minute + app = 0; + apl = 0; // attack per line + appw = 0; // weighted attack per piece + ppb = 0; + dss = 0; // garbage per second + dsp = 0; // garbage per piece + chzind = 0; // cheese index + garbeff = 0; // garbage efficiency + vsOnApm = 0; // vs / apm + + // x piece efficiency + tpE = 0; + ipE = 0; + + // input stats + inputs = 0; + holds = 0; + rotates = 0; + kps = 0; + kpp = 0; + + clearCols = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // amount cleared in a col = clearCols[col - 1] + clearPieces = { // lineclears by piece = clearPieces[piece][line_count - 1] + i: [0, 0, 0, 0], + j: [0, 0, 0, 0], + l: [0, 0, 0, 0], + o: [0, 0, 0, 0], + t: [0, 0, 0, 0], + s: [0, 0, 0, 0], + z: [0, 0, 0, 0], + }; + + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + } + + updateStats() { + this.time += 1 / this.game.tickrate; + this.game.grandmaster.sectionTime += 1 / this.game.tickrate; + + this.pps = this.pieceCount / this.time; + this.apm = this.attack * 60 / this.time; + this.vs = (this.attack + this.cleargarbage) * 100 / this.time; + + this.lpm = this.clearlines * 60 / this.time; + this.app = this.attack / this.pieceCount || 0; + this.apl = this.attack / this.clearlines || 0; + this.ppb = this.score / this.pieceCount || 0; + this.dss = this.cleargarbage / this.time + this.dsp = this.cleargarbage / this.pieceCount || 0; + this.vsOnApm = this.vs / this.apm || 0; + this.chzind = 25 * (this.dsp * 6 + this.vsOnApm * 2 - this.app * 5 - 1); + this.garbeff = this.app * this.dsp * 2; + this.appw = this.app - 5 * Math.tan(1 - this.chzind / 30) || 0; + this.kps = this.inputs / this.time; + this.kpp = this.inputs / this.pieceCount || 0; + + this.tpE = this.tspins.reduce((a, b) => a + b, 0) * 700 / this.pieceCount || 0; + this.ipE = this.quads * 700 / this.pieceCount || 0; + // use of || 0 to not show NaN + } + + checkInvis() { + return this.pieceCount % this.game.settings.game.lookAheadPieces == 0 && !this.game.falling.moved + } + + getRemainingGarbage() { + return this.game.settings.game.requiredGarbage - this.cleargarbage + } + + updateBTB(isBTB, count) { + this.btbCount = isBTB ? + this.btbCount + 1 : + count == 0 ? this.btbCount : -1; + if (this.btbCount > this.maxBTB) this.maxBTB = this.btbCount; + } + + updateCombo(count) { + this.combo = count == 0 ? -1 : this.combo + 1; + if (this.combo > this.maxCombo) this.maxCombo = this.combo; + } + + incrementStats(score, count, damage, isPC, isTspin, isAllspin, garb) { + this.score += score; + this.clearlines += count; + this.attack += damage; + this.quads += count >= 4 ? 1 : 0; + this.pcs += isPC ? 1 : 0; + this.cleargarbage += garb; + + if (isTspin) this.tspins[count]++; + this.allspins += isAllspin ? 1 : 0; + this.tgm_level += levellingTable[count]; + this.game.grandmaster.addGrade(count, this.combo, this.tgm_level) + if (count > 0) this.clearPieces[this.game.falling.piece.name][count - 1]++; + } + } \ No newline at end of file diff --git a/src/game.js b/src/game.js index 5da791e..3d8776f 100644 --- a/src/game.js +++ b/src/game.js @@ -1,164 +1,170 @@ -import { Bag } from "./mechanics/bag.js"; -import { Board } from "./mechanics/board.js"; -import { Controls } from "./movement/controls.js"; -import { Hold } from "./mechanics/hold.js"; -import { Mechanics } from "./mechanics/mechanics.js"; -import { MenuActions } from "./menus/menuactions.js"; -import { ModalActions } from "./menus/modals.js"; -import { Movement } from "./movement/movement.js"; -import { Renderer } from "./display/renderer.js"; -import { Settings } from "./features/settings.js"; -import { Sounds } from "./features/sounds.js"; -import { Falling } from "./mechanics/fallingpiece.js"; -import { GameStats } from "./features/stats.js"; -import { BoardEditor } from "./features/editboard.js"; -import { History } from "./features/history.js"; -import { BoardEffects } from "./display/boardEffects.js"; -import { ProfileStats } from "./features/profileStats.js"; -import { Modes } from "./features/modes.js"; -import { BoardRenderer } from "./display/renderBoard.js"; -import { Particles } from "./display/particles.js"; -import { Zenith } from "./mechanics/zenith.js"; - -export class Game { - started; - ended; - gameTimer = 0; // id of timeout - survivalTimer = 0; // id of timeout - gravityTimer = 0; - version = '1.3.2'; - tickrate = 60; - - elementReason = document.getElementById("reason"); - elementResult = document.getElementById("result"); - elementGameEndTitle = document.getElementById("gameEndTitle"); - - - constructor() { - this.boardeffects = new BoardEffects(this); - this.profilestats = new ProfileStats(this); - this.stats = new GameStats(this); - this.falling = new Falling(this); - this.settings = new Settings(this); - this.hold = new Hold(this); - this.sounds = new Sounds(this); - this.board = new Board(this); - this.bag = new Bag(this); - this.mechanics = new Mechanics(this); - this.menuactions = new MenuActions(this); - this.modals = new ModalActions(this); - this.movement = new Movement(this); - this.renderer = new Renderer(this); - this.boardrender = new BoardRenderer(this); - this.particles = new Particles(this); - this.boardeditor = new BoardEditor(this); - this.controls = new Controls(this); - this.history = new History(this); - this.modes = new Modes(this); - this.zenith = new Zenith(this) - - this.renderer.sizeCanvas(); - this.particles.initBoard(); - this.renderer.setEditPieceColours(); - this.sounds.initSounds(); - this.startGame(); - this.renderer.renderingLoop(); - this.boardeditor.addListeners(); - this.menuactions.addRangeListener(); - this.modals.generate.addMenuListeners(); - this.modals.generate.generateGamemodeMenu(); - this.modals.generate.generateStatList(); - this.modals.generate.generateSkinList(); - this.sounds.addMenuSFX(); - this.profilestats.loadPBs(); - this.versionChecker(); - } - - startGame() { - this.menuactions.loadSettings(); - this.resetState(); - this.renderer.renderStyles(); - this.mechanics.spawnPiece(this.bag.randomiser(true), true); - this.history.save(); - } - - stopGameTimers() { //stop all the game's timers - clearInterval(this.gravityTimer); - clearInterval(this.gameTimer); - clearInterval(this.survivalTimer); - clearInterval(this.mechanics.zenithTimer) - this.mechanics.locking.lockingPause(); - } - - endGame(top, bottom = "Better luck next time") { - const dead = ["Lockout", "Topout", "Blockout"].includes(top); // survival mode end instead of lose - if (this.settings.game.gamemode == 'survival' && dead) { - this.ended = true; - return; - } - - if (top == "Topout" || top == "Blockout" || top == "Lockout") { - this.sounds.playSound("topout"); - this.sounds.playSound("failure"); - } else if (top == undefined) { - return; - } else { - this.sounds.playSound("finish"); - } - - this.ended = true; - this.modals.openModal("gameEnd"); - this.stopGameTimers() - this.elementReason.textContent = top; - this.elementResult.textContent = bottom; - this.profilestats.saveSession(); - } - - resetState() { - this.boardeffects.hasPace = true; - this.boardeffects.paceCooldown = 0; - this.boardrender.boardAlpha = 1; - this.boardrender.queueAlpha = 1; - this.renderer.inDanger = false; - this.started = false; - this.ended = false; - - this.board.resetBoard(); - this.mechanics.locking.clearLockDelay(); - this.boardeffects.toggleRainbow(false); - this.renderer.resetActionText(); - this.renderer.renderDanger(); - this.particles.clearParticles(); - this.renderer.clearHold(); - this.stopGameTimers(); - - this.bag = new Bag(this); - this.mechanics = new Mechanics(this); - this.falling = new Falling(this); - this.hold = new Hold(this); - this.stats = new GameStats(this); - this.history = new History(this); - this.zenith = new Zenith(this); - - this.renderer.renderSidebar(); - this.modes.checkFinished(); - this.stats.updateStats(); - this.renderer.updateAlpha(); - this.boardeffects.rainbowBoard(); - } - - gameClock() { - this.renderer.renderSidebar(); - this.modes.checkFinished(); - this.stats.updateStats(); - this.renderer.updateAlpha(); - this.boardeffects.rainbowBoard(); - } - - versionChecker() { - const userver = window.localStorage.getItem('version'); - document.getElementById('updatetext').style.display = this.version == userver ? "none" : "block"; - window.localStorage.setItem('version', this.version); - } - -} +import { Bag } from "./mechanics/bag.js"; +import { Board } from "./mechanics/board.js"; +import { Controls } from "./movement/controls.js"; +import { Hold } from "./mechanics/hold.js"; +import { Mechanics } from "./mechanics/mechanics.js"; +import { MenuActions } from "./menus/menuactions.js"; +import { ModalActions } from "./menus/modals.js"; +import { Movement } from "./movement/movement.js"; +import { Renderer } from "./display/renderer.js"; +import { Settings } from "./features/settings.js"; +import { Sounds } from "./features/sounds.js"; +import { Falling } from "./mechanics/fallingpiece.js"; +import { GameStats } from "./features/stats.js"; +import { BoardEditor } from "./features/editboard.js"; +import { History } from "./features/history.js"; +import { BoardEffects } from "./display/boardEffects.js"; +import { ProfileStats } from "./features/profileStats.js"; +import { Modes } from "./features/modes.js"; +import { BoardRenderer } from "./display/renderBoard.js"; +import { Particles } from "./display/particles.js"; +import { Zenith, Grandmaster } from "./mechanics/gamemode_extended.js"; + +export class Game { + started; + ended; + gameTimer = 0; // id of timeout + survivalTimer = 0; // id of timeout + gravityTimer = 0; + zenithTimer = 0; + grandmasterTimer = 0; + version = '1.3.2'; + tickrate = 60; + + elementReason = document.getElementById("reason"); + elementResult = document.getElementById("result"); + elementGameEndTitle = document.getElementById("gameEndTitle"); + + + constructor() { + this.boardeffects = new BoardEffects(this); + this.profilestats = new ProfileStats(this); + this.stats = new GameStats(this); + this.falling = new Falling(this); + this.settings = new Settings(this); + this.hold = new Hold(this); + this.sounds = new Sounds(this); + this.board = new Board(this); + this.bag = new Bag(this); + this.mechanics = new Mechanics(this); + this.menuactions = new MenuActions(this); + this.modals = new ModalActions(this); + this.movement = new Movement(this); + this.renderer = new Renderer(this); + this.boardrender = new BoardRenderer(this); + this.particles = new Particles(this); + this.boardeditor = new BoardEditor(this); + this.controls = new Controls(this); + this.history = new History(this); + this.modes = new Modes(this); + this.zenith = new Zenith(this); + this.grandmaster = new Grandmaster(this); + + this.renderer.sizeCanvas(); + this.particles.initBoard(); + this.renderer.setEditPieceColours(); + this.sounds.initSounds(); + this.startGame(); + this.renderer.renderingLoop(); + this.boardeditor.addListeners(); + this.menuactions.addRangeListener(); + this.modals.generate.addMenuListeners(); + this.modals.generate.generateGamemodeMenu(); + this.modals.generate.generateStatList(); + this.modals.generate.generateSkinList(); + this.sounds.addMenuSFX(); + this.profilestats.loadPBs(); + this.versionChecker(); + } + + startGame() { + this.menuactions.loadSettings(); + this.resetState(); + this.renderer.renderStyles(); + this.mechanics.spawnPiece(this.bag.randomiser(true), true); + this.history.save(); + } + + stopGameTimers() { //stop all the game's timers + clearInterval(this.gravityTimer); + clearInterval(this.gameTimer); + clearInterval(this.survivalTimer); + clearInterval(this.zenithTimer); + clearInterval(this.grandmasterTimer); + this.mechanics.locking.lockingPause(); + } + + endGame(top, bottom = "Better luck next time") { + const dead = ["Lockout", "Topout", "Blockout"].includes(top); // survival mode end instead of lose + if (this.settings.game.gamemode == 'survival' && dead) { + this.ended = true; + return; + } + + if (top == "Topout" || top == "Blockout" || top == "Lockout") { + this.sounds.playSound("topout"); + this.sounds.playSound("failure"); + } else if (top == undefined) { + return; + } else { + this.sounds.playSound("finish"); + } + + this.ended = true; + this.modals.openModal("gameEnd"); + this.stopGameTimers() + this.elementReason.textContent = top; + this.elementResult.textContent = bottom; + this.profilestats.saveSession(); + } + + resetState() { + this.boardeffects.hasPace = true; + this.boardeffects.paceCooldown = 0; + this.boardrender.boardAlpha = 1; + this.boardrender.queueAlpha = 1; + this.renderer.inDanger = false; + this.started = false; + this.ended = false; + + this.board.resetBoard(); + this.mechanics.locking.clearLockDelay(); + this.boardeffects.toggleRainbow(false); + this.renderer.resetActionText(); + this.renderer.renderDanger(); + this.particles.clearParticles(); + this.renderer.clearHold(); + this.stopGameTimers(); + + this.bag = new Bag(this); + this.mechanics = new Mechanics(this); + this.falling = new Falling(this); + this.hold = new Hold(this); + this.stats = new GameStats(this); + this.history = new History(this); + this.zenith = new Zenith(this); + this.grandmaster = new Grandmaster(this); + + + this.renderer.renderSidebar(); + this.modes.checkFinished(); + this.stats.updateStats(); + this.renderer.updateAlpha(); + this.boardeffects.rainbowBoard(); + } + + gameClock() { + this.renderer.renderSidebar(); + this.modes.checkFinished(); + this.stats.updateStats(); + this.renderer.updateAlpha(); + this.boardeffects.rainbowBoard(); + } + + versionChecker() { + const userver = window.localStorage.getItem('version'); + document.getElementById('updatetext').style.display = this.version == userver ? "none" : "block"; + window.localStorage.setItem('version', this.version); + } + +} diff --git a/src/main.js b/src/main.js index d36e77d..fa50ce4 100644 --- a/src/main.js +++ b/src/main.js @@ -1,60 +1,63 @@ -import { Game } from "./game.js"; - -const game = new Game(); - -// allow html to access functions -window["menu"] = game.menuactions; -window["modal"] = game.modals; -window["songs"] = game.sounds; - -const elementSplashScreen = document.getElementById("splashScreen"); -const elementSplashText = document.getElementById("splashText"); - -window.addEventListener("keydown", event => { - if (event.key == undefined) return; - let key = event.key.length > 1 ? event.key : event.key.toLowerCase(); // 1 letter words are lowercase - if (event.altKey) key = "Alt+" + key; - if (event.ctrlKey) key = "Ctrl+" + key; - - game.controls.onKeyDownRepeat(event, key); - if (event.repeat) return; - game.controls.onKeyDown(event, key); -}) - -window.addEventListener("keyup", event => { - if (event.key == undefined) return; - let key = event.key.length > 1 ? event.key : event.key.toLowerCase(); - game.controls.onKeyUp(event, key); -}); - -window.addEventListener('mousemove', () => { - game.controls.toggleCursor(true); -}) - -document.onresize = () => { - game.renderer.sizeCanvas(); - game.renderer.updateNext(); - game.renderer.updateHold(); -} - -// splash menu -window.addEventListener("DOMContentLoaded", () => { - elementSplashText.textContent = "Ready"; - elementSplashScreen.style.opacity = 0; - elementSplashScreen.style.scale = 1.2; - elementSplashScreen.style.display = "none"; - document.getElementById("ignoreText").style.opacity = 0.5; -}) - -window.addEventListener("focus", function () { - document.getElementById("nofocus").style.display = "none"; -}); - -window.addEventListener("blur", function () { - if (!game.settings.display.outoffocus) return - document.getElementById("nofocus").style.display = "block"; -}); - -window.onerror = (msg, url, lineNo, columnNo, error) => { - game.modals.generate.notif(error, msg + ". ln "+ lineNo, "error"); -} \ No newline at end of file +import { Game } from "./game.js"; + +const game = new Game(); + +console.log("%cTETI", "color: #DFC0F3;\n\t\t\t\t\t display: block;\n\t\t\t\t\t font-size: 5em;\n\t\t\t\t\t font-weight: 900;\n\t\t\t\t\t text-shadow: 0px 0px 2px #9150BA;\n\t\t\t\t\t background-color: #45345088;\n\t\t\t\t\t padding: 0 0.25em;\n\t\t\t\t\t border-radius: 3px;"), + + +// allow html to access functions +window["menu"] = game.menuactions; +window["modal"] = game.modals; +window["songs"] = game.sounds; + +const elementSplashScreen = document.getElementById("splashScreen"); +const elementSplashText = document.getElementById("splashText"); + +window.addEventListener("keydown", event => { + if (event.key == undefined) return; + let key = event.key.length > 1 ? event.key : event.key.toLowerCase(); // 1 letter words are lowercase + if (event.altKey) key = "Alt+" + key; + if (event.ctrlKey) key = "Ctrl+" + key; + + game.controls.onKeyDownRepeat(event, key); + if (event.repeat) return; + game.controls.onKeyDown(event, key); +}) + +window.addEventListener("keyup", event => { + if (event.key == undefined) return; + let key = event.key.length > 1 ? event.key : event.key.toLowerCase(); + game.controls.onKeyUp(event, key); +}); + +window.addEventListener('mousemove', () => { + game.controls.toggleCursor(true); +}) + +document.onresize = () => { + game.renderer.sizeCanvas(); + game.renderer.updateNext(); + game.renderer.updateHold(); +} + +// splash menu +window.addEventListener("DOMContentLoaded", () => { + elementSplashText.textContent = "Ready"; + elementSplashScreen.style.opacity = 0; + elementSplashScreen.style.scale = 1.2; + elementSplashScreen.style.display = "none"; + document.getElementById("ignoreText").style.opacity = 0.5; +}) + +window.addEventListener("focus", function () { + document.getElementById("nofocus").style.display = "none"; +}); + +window.addEventListener("blur", function () { + if (!game.settings.display.outoffocus) return + document.getElementById("nofocus").style.display = "block"; +}); + +window.onerror = (msg, url, lineNo, columnNo, error) => { + game.modals.generate.notif(error, msg + ". ln "+ lineNo, "error"); +} diff --git a/src/mechanics/bag.js b/src/mechanics/bag.js index 5a022c8..ac4b6a3 100644 --- a/src/mechanics/bag.js +++ b/src/mechanics/bag.js @@ -1,79 +1,79 @@ -import { Game } from "../game.js"; -import pieces from "../data/pieces.json" with { type: "json" }; - -export class Bag { - /** - * @type {Array>} - */ - nextPieces = [[], []]; - - - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - } - - randomiser(start = false) { - if (this.nextPieces[1].length == 0) this.shuffleRemainingPieces(); - if (this.nextPieces[0].length == 0) { - this.nextPieces = [this.nextPieces[1], []]; - this.shuffleRemainingPieces(); - } - const piece = this.nextPieces[0].splice(0, 1)[0]; - - if (["o", "s", "z"].includes(piece) && this.game.settings.game.stride && start) { // stride mode - this.nextPieces = [[], []]; - return this.randomiser(start); - } - - return pieces.filter(element => { - return element.name == piece; - })[0]; - } - - shuffleRemainingPieces() { - pieces.forEach(piece => this.nextPieces[1].push(piece.name)); - this.nextPieces[1] = this.nextPieces[1] - .map(value => ({ value, sort: Math.random() })) - .sort((a, b) => a.sort - b.sort) - .map(({ value }) => value); - } - - getFirstFive() { - return this.nextPieces[0] - .concat(this.nextPieces[1]) - .slice(0, this.game.settings.game.nextPieces); - } - - getQueue() { - return this.game.falling.piece.name - + this.nextPieces[0] - .concat(this.nextPieces[1]) - .splice(0, 6) - .join(""); - } - - setQueue(value, names) { - this.nextPieces[0] = value - .split("") - .filter(p => names.includes(p)); - this.shuffleRemainingPieces(); - this.game.renderer.updateNext(); - - this.game.mechanics.locking.clearLockDelay(); - this.game.board.MinoToNone("A"); - this.game.mechanics.isTspin = false; - this.game.mechanics.isAllspin = false; - this.game.mechanics.isMini = false; - this.game.mechanics.spawnPiece(this.game.bag.randomiser()); - this.game.history.save(); - } - - nextPiece() { - return pieces.filter( - p => p.name == this.nextPieces[0].concat(this.nextPieces[1])[0] - )[0]; - } +import { Game } from "../game.js"; +import pieces from "../data/pieces.json" with { type: "json" }; + +export class Bag { + /** + * @type {Array>} + */ + nextPieces = [[], []]; + + + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + } + + randomiser(start = false) { + if (this.nextPieces[1].length == 0) this.shuffleRemainingPieces(); + if (this.nextPieces[0].length == 0) { + this.nextPieces = [this.nextPieces[1], []]; + this.shuffleRemainingPieces(); + } + const piece = this.nextPieces[0].splice(0, 1)[0]; + + if (["o", "s", "z"].includes(piece) && this.game.settings.game.stride && start) { // stride mode + this.nextPieces = [[], []]; + return this.randomiser(start); + } + + return pieces.filter(element => { + return element.name == piece; + })[0]; + } + + shuffleRemainingPieces() { + pieces.forEach(piece => this.nextPieces[1].push(piece.name)); + this.nextPieces[1] = this.nextPieces[1] + .map(value => ({ value, sort: Math.random() })) + .sort((a, b) => a.sort - b.sort) + .map(({ value }) => value); + } + + getFirstFive() { + return this.nextPieces[0] + .concat(this.nextPieces[1]) + .slice(0, this.game.settings.game.nextPieces); + } + + getQueue() { + return this.game.falling.piece.name + + this.nextPieces[0] + .concat(this.nextPieces[1]) + .splice(0, 6) + .join(""); + } + + setQueue(value, names) { + this.nextPieces[0] = value + .split("") + .filter(p => names.includes(p)); + this.shuffleRemainingPieces(); + this.game.renderer.updateNext(); + + this.game.mechanics.locking.clearLockDelay(); + this.game.board.MinoToNone("A"); + this.game.mechanics.isTspin = false; + this.game.mechanics.isAllspin = false; + this.game.mechanics.isMini = false; + this.game.mechanics.spawnPiece(this.game.bag.randomiser()); + this.game.history.save(); + } + + nextPiece() { + return pieces.filter( + p => p.name == this.nextPieces[0].concat(this.nextPieces[1])[0] + )[0]; + } } \ No newline at end of file diff --git a/src/mechanics/board.js b/src/mechanics/board.js index cf1532d..23016e3 100644 --- a/src/mechanics/board.js +++ b/src/mechanics/board.js @@ -1,127 +1,127 @@ -import { Game } from "../game.js"; - -export class Board { - /** - * @type {string[][]} - */ - boardState = []; - - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - } - // modify board - checkMino([x, y], val) { - return this.boardState[y][x].split(" ").includes(val); - } - - MinoToNone(val) { - this.getMinos(val).forEach(([x, y]) => this.rmValue([x, y], val)); - } - - EradicateMinoCells(val) { - this.getCoords(this.boardState, c => c.includes(val), [0, 0]).forEach(([x, y]) => this.rmValue([x, y], val)); - } - - addMinos(val, c, [dx, dy]) { - c.forEach(([x, y]) => this.setValue([x + dx, y + dy], val)); - } - - addValFront([x, y], val) { - this.boardState[y][x] = `${val} ${this.boardState[y][x]}`; - } - - addValue([x, y], val) { - this.boardState[y][x] = (this.boardState[y][x] + " " + val).trim(); - } - - setValue([x, y], val) { - this.boardState[y][x] = val; - } - - rmValue([x, y], val) { - this.boardState[y][x] = this.boardState[y][x].replace(val, "").trim(); - } - - getMinos(name) { - return this.getCoords(this.boardState, c => c.split(" ").includes(name), [0, 0]); - } - - pieceToCoords(arr, [dx, dy] = [0, 0]) { - return this.getCoords(arr.toReversed(), c => c == 1, [dx, dy]); - } - - setCoordEmpty([x, y]) { - this.boardState[y][x] = ""; - } - - resetBoard() { - this.boardState = [...Array(40)].map(() => [...Array(10)].map(() => "")); - } - - getFullRows() { - const rows = this.getMinos("S") - .map(coord => coord[1]) - .reduce((prev, curr) => ((prev[curr] = ++prev[curr] || 1), prev), {}); - return Object.keys(rows) - .filter(key => rows[key] >= 10) - .map(row => +row) - .toReversed(); - } - - getCoords(array, filter, [dx, dy]) { - const coords = []; - array.forEach((row, y) => - row.forEach((col, x) => { - if (filter(col)) coords.push([x + dx, y + dy]); - }) - ); - return coords; - } - - moveMinos(coords, dir, size, value = "") { - const getChange = ([x, y], a) => { - return { RIGHT: [x + a, y], LEFT: [x - a, y], DOWN: [x, y - a], UP: [x, y + a] }; - }; - const newcoords = coords.map(c => getChange(c, size)[dir]); - - if (newcoords.some(([x, y]) => y > 39)) { - this.game.endGame("Topout"); - return; - } - - const valTable = coords.map(([x, y]) => (value ? value : this.boardState[y][x])); - coords.forEach((c, idx) => this.rmValue(c, valTable[idx])); - - newcoords.forEach((c, idx) => - value ? this.addValue(c, valTable[idx]) : this.setValue(c, valTable[idx]) - ); - this.game.mechanics.spawnOverlay(); - } - - setComboBoard(start) { - // 4w sides - this.boardState.forEach((row, y) => - row.forEach((col, x) => { - if (x < 3 || x > 6) this.addMinos("S G", [[x, y]], [0, 0]); - }) - ); - - if (!start) return; - // garbage pattern - const validCoords = [[[0, 0], [1, 0], [2, 0], [3, 0]], [[0, 1], [1, 1], [2, 1], [3, 1]]]; - const garbAmount = Math.random() > 0.5 ? 3 : 6; - const garbCoords = []; - for (let i = 0; i < garbAmount; i++) { - const y = Math.random() > 0.5 ? 0 : 1; - if (validCoords[y].length == 1) { i--; continue; } - const coord = validCoords[y].splice(Math.floor(Math.random() * validCoords[y].length), 1); - garbCoords.push(coord[0]); - } - - this.addMinos("S G", garbCoords.map(([x, y]) => [x + 3, y]), [0, 0]); - this.game.mechanics.setShadow(); - } -} +import { Game } from "../game.js"; + +export class Board { + /** + * @type {string[][]} + */ + boardState = []; + + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + } + // modify board + checkMino([x, y], val) { + return this.boardState[y][x].split(" ").includes(val); + } + + MinoToNone(val) { + this.getMinos(val).forEach(([x, y]) => this.rmValue([x, y], val)); + } + + EradicateMinoCells(val) { + this.getCoords(this.boardState, c => c.includes(val), [0, 0]).forEach(([x, y]) => this.rmValue([x, y], val)); + } + + addMinos(val, c, [dx, dy]) { + c.forEach(([x, y]) => this.setValue([x + dx, y + dy], val)); + } + + addValFront([x, y], val) { + this.boardState[y][x] = `${val} ${this.boardState[y][x]}`; + } + + addValue([x, y], val) { + this.boardState[y][x] = (this.boardState[y][x] + " " + val).trim(); + } + + setValue([x, y], val) { + this.boardState[y][x] = val; + } + + rmValue([x, y], val) { + this.boardState[y][x] = this.boardState[y][x].replace(val, "").trim(); + } + + getMinos(name) { + return this.getCoords(this.boardState, c => c.split(" ").includes(name), [0, 0]); + } + + pieceToCoords(arr, [dx, dy] = [0, 0]) { + return this.getCoords(arr.toReversed(), c => c == 1, [dx, dy]); + } + + setCoordEmpty([x, y]) { + this.boardState[y][x] = ""; + } + + resetBoard() { + this.boardState = [...Array(40)].map(() => [...Array(10)].map(() => "")); + } + + getFullRows() { + const rows = this.getMinos("S") + .map(coord => coord[1]) + .reduce((prev, curr) => ((prev[curr] = ++prev[curr] || 1), prev), {}); + return Object.keys(rows) + .filter(key => rows[key] >= 10) + .map(row => +row) + .toReversed(); + } + + getCoords(array, filter, [dx, dy]) { + const coords = []; + array.forEach((row, y) => + row.forEach((col, x) => { + if (filter(col)) coords.push([x + dx, y + dy]); + }) + ); + return coords; + } + + moveMinos(coords, dir, size, value = "") { + const getChange = ([x, y], a) => { + return { RIGHT: [x + a, y], LEFT: [x - a, y], DOWN: [x, y - a], UP: [x, y + a] }; + }; + const newcoords = coords.map(c => getChange(c, size)[dir]); + + if (newcoords.some(([x, y]) => y > 39)) { + this.game.endGame("Topout"); + return; + } + + const valTable = coords.map(([x, y]) => (value ? value : this.boardState[y][x])); + coords.forEach((c, idx) => this.rmValue(c, valTable[idx])); + + newcoords.forEach((c, idx) => + value ? this.addValue(c, valTable[idx]) : this.setValue(c, valTable[idx]) + ); + this.game.mechanics.spawnOverlay(); + } + + setComboBoard(start) { + // 4w sides + this.boardState.forEach((row, y) => + row.forEach((col, x) => { + if (x < 3 || x > 6) this.addMinos("S G", [[x, y]], [0, 0]); + }) + ); + + if (!start) return; + // garbage pattern + const validCoords = [[[0, 0], [1, 0], [2, 0], [3, 0]], [[0, 1], [1, 1], [2, 1], [3, 1]]]; + const garbAmount = Math.random() > 0.5 ? 3 : 6; + const garbCoords = []; + for (let i = 0; i < garbAmount; i++) { + const y = Math.random() > 0.5 ? 0 : 1; + if (validCoords[y].length == 1) { i--; continue; } + const coord = validCoords[y].splice(Math.floor(Math.random() * validCoords[y].length), 1); + garbCoords.push(coord[0]); + } + + this.addMinos("S G", garbCoords.map(([x, y]) => [x + 3, y]), [0, 0]); + this.game.mechanics.setShadow(); + } +} diff --git a/src/mechanics/clearlines.js b/src/mechanics/clearlines.js index 15eeab1..3964865 100644 --- a/src/mechanics/clearlines.js +++ b/src/mechanics/clearlines.js @@ -37,6 +37,7 @@ export class ClearLines { if (clearRows.length > 0) this.game.renderer.bounceBoard("DOWN"); this.game.particles.spawnParticles(0, Math.min(...clearRows), "clear") this.processLineClear(removedGarbage, clearRows); + return clearRows.length; } clearRow(rowNumber) { @@ -66,6 +67,7 @@ export class ClearLines { this.manageGarbageSent(damage); this.game.zenith.AwardLines(damage); + if (linecount == 1) this.game.zenith.AwardLines(1); // render action text if (mech.isAllspin) damagetype = damagetype.replace("Tspin ", this.game.falling.piece.name + " spin "); this.game.renderer.renderActionText(damagetype, isBTB, isPC, damage, linecount); diff --git a/src/mechanics/fallingpiece.js b/src/mechanics/fallingpiece.js index b145676..f578a9e 100644 --- a/src/mechanics/fallingpiece.js +++ b/src/mechanics/fallingpiece.js @@ -1,64 +1,64 @@ -import { KickData, KickData180 } from "../data/kicks.js"; -import { Game } from "../game.js"; - -export class Falling { - piece = null; - location = []; - moved = false; - rotation = 1; - - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - } - - spawn(piece) { - const dx = piece.name == "o" ? 4 : 3; - const dy = piece.name == "o" ? 21 : piece.name == "i" ? 19 : 20; - const coords = this.game.mechanics.board.pieceToCoords(piece.shape1); - this.game.mechanics.board.addMinos("A " + piece.name, coords, [dx, dy]); - this.location = [dx, dy]; - this.piece = piece; - this.rotation = 1; - } - - getKickData(rotationType, shapeNo) { - const isI = this.piece.name == "i" ? 1 : 0; - const direction = rotationType == "CCW" ? (shapeNo > 3 ? 0 : shapeNo) : shapeNo - 1; - return { - 180: KickData180[isI][direction], - CW: KickData[isI][direction], - CCW: KickData[isI][direction].map(row => row.map(x => x * -1)), - }[rotationType]; - } - - getRotateState(type) { - const newState = (this.rotation + { CW: 1, CCW: -1, 180: 2 }[type]) % 4; - return newState == 0 ? 4 : newState; - } - - getNewCoords(rotation) { - return this.game.board.pieceToCoords( - this.piece[`shape${rotation}`], - this.location - ); - } - - newName() { - return "A " + this.piece.name; - } - - updateLocation([dx, dy]) { - this.location = [ - this.location[0] + dx, - this.location[1] + dy, - ]; - if (dx != 0 || dy != 0) { - this.moved = true; - } - } - - +import { KickData, KickData180 } from "../data/kicks.js"; +import { Game } from "../game.js"; + +export class Falling { + piece = null; + location = []; + moved = false; + rotation = 1; + + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + } + + spawn(piece) { + const dx = piece.name == "o" ? 4 : 3; + const dy = piece.name == "o" ? 21 : piece.name == "i" ? 19 : 20; + const coords = this.game.mechanics.board.pieceToCoords(piece.shape1); + this.game.mechanics.board.addMinos("A " + piece.name, coords, [dx, dy]); + this.location = [dx, dy]; + this.piece = piece; + this.rotation = 1; + } + + getKickData(rotationType, shapeNo) { + const isI = this.piece.name == "i" ? 1 : 0; + const direction = rotationType == "CCW" ? (shapeNo > 3 ? 0 : shapeNo) : shapeNo - 1; + return { + 180: KickData180[isI][direction], + CW: KickData[isI][direction], + CCW: KickData[isI][direction].map(row => row.map(x => x * -1)), + }[rotationType]; + } + + getRotateState(type) { + const newState = (this.rotation + { CW: 1, CCW: -1, 180: 2 }[type]) % 4; + return newState == 0 ? 4 : newState; + } + + getNewCoords(rotation) { + return this.game.board.pieceToCoords( + this.piece[`shape${rotation}`], + this.location + ); + } + + newName() { + return "A " + this.piece.name; + } + + updateLocation([dx, dy]) { + this.location = [ + this.location[0] + dx, + this.location[1] + dy, + ]; + if (dx != 0 || dy != 0) { + this.moved = true; + } + } + + } \ No newline at end of file diff --git a/src/mechanics/gamemode_extended.js b/src/mechanics/gamemode_extended.js new file mode 100644 index 0000000..251c268 --- /dev/null +++ b/src/mechanics/gamemode_extended.js @@ -0,0 +1,277 @@ +import { Game } from "../game.js"; + +export class Zenith { + + /** + * @param {Game} game + */ + + constructor(game) { + this.game = game + } + + climbPoints = 0; + isLastRankChangePromote = !0; + isHyperspeed = true; + rankLock = 0; + promotionFatigue = 0; + rankLock = 0; + tickPass = 0; + tempAltitude = 0 + + FloorDistance = [0, 50, 150, 300, 450, 650, 850, 1100, 1350, 1650, 1 / 0]; + SpeedrunReq = [7, 8, 8, 9, 9, 10, 0, 0, 0, 0, 0]; + + + GetSpeedCap(e) { + const t = this.FloorDistance.find((t => e < t)) - e; + return Math.max(0, Math.min(1, t / 5 - .2)) + } + + GetFloorLevel(e) { + return this.FloorDistance.filter((t => e >= t)).length || 1 + } + + AwardLines(e, t=!0, n=!0) { + const s = .25 * Math.floor(this.game.stats.climbSpeed); + this.GiveBonus(s * e * (t ? 1 : 0)); + if (e <= 0 ) return + this.GiveClimbPts((e + .05) * (n ? 1 : 0)) + } + + GiveBonus(e) { + this.tempAltitude += e + } + + GiveClimbPts(e) { + this.climbPoints += e + } + + startZenithMode() { + clearInterval(this.game.zenithTimer); + document.getElementById("climbSpeedBar").style.display = "none" + if(this.game.settings.game.gamemode != "zenith") return + document.getElementById("climbSpeedBar").style.display = "block" + this.game.zenithTimer = setInterval( + () => { + let t = Math.floor(this.game.stats.climbSpeed), + o = .25 * t, + a = this.GetSpeedCap(this.tempAltitude); + + if (this.tickPass >= this.rankLock) { + let e = 3; + this.climbPoints -= e * (t ** 2 + t) / 3600 + } + const s = 4 * t, + i = 4 * (t - 1) + + if (this.climbPoints < 0){ + if (t <= 1){ + this.climbPoints = 0; + } + else { + this.climbPoints += i, + this.game.sounds.playSound("speed_down") + this.isLastRankChangePromote = !1, + t-- + } + } + else if (this.climbPoints >= s) { + this.climbPoints -= s, + this.game.sounds.playSound("speed_up") + this.isLastRankChangePromote = !0, + t++; + this.rankLock = this.tickPass + Math.max(60, 60 * (5 - this.promotionFatigue)); + this.promotionFatigue++; + } + + this.game.stats.climbSpeed = t + this.climbPoints / (4 * t); + + this.tempAltitude += o / 60 * a + + if(this.game.stats.floor != this.GetFloorLevel(this.tempAltitude)){ + this.startZenithMode() + this.game.stats.floor = this.GetFloorLevel(this.tempAltitude) + this.game.sounds.playSound("zenith_levelup") + this.game.renderer.renderTimeLeft("FLOOR " + this.game.stats.floor) + } + const g = this.tempAltitude + this.game.stats.altitude = this.tempAltitude + this.tickPass++ + this.drawClimbSpeedBar(Math.floor(this.game.stats.climbSpeed), this.climbPoints, s) + } + , 1000 / this.game.tickrate); + } + + drawClimbSpeedBar(speed, point, require){ // todo: drawing polygons (parallelogram) cus idk + const color = ["var(--invis)", "red", "orange", "green", "blue", "#FF1493", "tan", "lightgreen", "lightblue", "pink", "white", "white", "white", "white", "white", "white", "white", "white", "white", "white", "white", "white", "white", "white", "white", "white"] + const climbSpeedBar = document.getElementById("climbSpeedBar") + + climbSpeedBar.value = point + climbSpeedBar.max = require + document.styleSheets[1].cssRules[24].style.backgroundColor = color[speed - 1] + document.styleSheets[1].cssRules[23].style.backgroundColor = color[speed] + } + +} + +export class Grandmaster { + + /** + * @param {Game} game + */ + + constructor(game) { + this.game = game + } + + gradeBoost = 0; // the one used to determine which grade to shown in the array + gradePoint = 0; + internalGrade = 0; // the one to determine how many grade to boost + isCoolCheck = false; + coolsCount = 0; + regretsCount = 0; + sectionTarget = 100; + sectionTime = 0; + + grades = [ + "9","8","7","6","5","4","3","2","1", + "S1","S2","S3","S4","S5","S6","S7","S8","S9", + "m1","m2","m3","m4","m5","m6","m7","m8","m9", + "M","MK","MV","MO","MM-","MM","MM+","GM-","GM","GM+","TM-","TM","TM+" + ]; + gradePointDecay = [ + 125, 80, 80, 50, 45, 45, 45, + 40, 40, 40, 40, 40, 30, 30, 30, + 20, 20, 20, 20, 20, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 10, 10 + ]; + mult = [ + [1.0, 1.0, 1.0, 1.0], + [1.0, 1.2, 1.4, 1.5], + [1.0, 1.2, 1.5, 1.8], + [1.0, 1.4, 1.6, 2.0], + [1.0, 1.4, 1.7, 2.2], + [1.0, 1.4, 1.8, 2.3], + [1.0, 1.4, 1.9, 2.4], + [1.0, 1.5, 2.0, 2.5], + [1.0, 1.5, 2.1, 2.6], + [1.0, 2.0, 2.5, 3.0], + ]; + gradePointBonus = [ + [10, 20, 40, 50], + [10, 20, 30, 40], + [10, 20, 30, 40], + [10, 15, 30, 40], + [10, 15, 20, 40], + [5, 15, 20, 30], + [5, 10, 20, 30], + [5, 10, 15, 30], + [5, 10, 15, 30], + [5, 10, 15, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], + [2, 12, 13, 30], // is this long enough? + ]; + gradeBoostTable = [ + 0,1,2,3,4,5,5,6,6,7,7,7,8,8,8,9,9,9,10,11,12,12,12,13,13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25,25,26,26,26,27,27,27,27,28,28,28,28,28,29,29,29,29,29,30,30,30,30,30 + ]; + + coolsTable = [52, 52, 49, 45, 45, 42, 42, 38, 38, 0]; + regretsTable = [90, 75, 75, 68, 60, 60, 50, 50, 50, 50]; + + addGrade(row, cmb, lvl){ + if(this.game.settings.game.gamemode != "race") return + this.checkSectionCleared(); + this.checkCool(); + this.game.stats.grade = this.grades[this.gradeBoost + this.coolsCount - this.regretsCount]; + if (row<1) return; + + const pts = this.gradePointBonus[this.internalGrade][row - 1]; + const cmb_mult = this.mult[Math.min(9, cmb)][row - 1]; + const lvl_mult = Math.floor(lvl / 250) + 1; + + this.gradePoint += pts*cmb_mult*lvl_mult; + + if (this.gradePoint >= 100) { + this.gradePoint = 0; + this.internalGrade++; + this.gradeBoost = this.gradeBoostTable[this.internalGrade]; + this.startGrandmasterTimer(this.gradePointDecay[this.internalGrade]); + }; + } + + startGrandmasterTimer(){ + clearInterval(this.game.grandmasterTimer); + if(this.game.settings.game.gamemode != "race") return + this.game.grandmasterTimer = setInterval(() => { + this.gradePoint = Math.max(0, this.gradePoint - 1); + }, (1000 / 60 * this.gradePointDecay[this.internalGrade]) ) + } + + checkSectionCleared(){ + if(this.game.stats.tgm_level >= this.sectionTarget){ + this.game.renderer.renderTimeLeft("SECTION " + this.sectionTarget / 100 + " CLEAR"); + this.game.sounds.playSound("levelup"); + if(this.sectionTime >= this.regretsTable[(this.sectionTarget / 100) - 1]){ + this.game.renderer.renderTimeLeft("REGRET"); + this.regretsCount++; + } + this.sectionTime = 0; + this.isCoolCheck = false; + this.sectionTarget = Math.min(this.sectionTarget + 100, this.game.settings.game[this.game.modes.modeJSON.target]) + } + } + + checkCool(){ + if(this.game.stats.tgm_level % 100 >= 70 && !this.isCoolCheck){ + this.isCoolCheck = true; + if(this.sectionTime <= this.coolsTable[(this.sectionTarget / 100) - 1]){ + this.game.renderer.renderTimeLeft("COOL!"); + this.coolsCount++; + } + } + } +} diff --git a/src/mechanics/hold.js b/src/mechanics/hold.js index b64b07b..bf1e05f 100644 --- a/src/mechanics/hold.js +++ b/src/mechanics/hold.js @@ -1,43 +1,43 @@ -import { Game } from "../game.js"; -import pieces from "../data/pieces.json" with { type: "json" }; - - -export class Hold { - piece; - occured = false; - pieceNames = ["s", "z", "i", "j", "l", "o", "t"]; - - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - this.curr = this.game.falling; - } - - setHold() { - this.piece = this.curr.piece; - } - - swapHold() { - [this.game.hold.piece, this.curr.piece] - = [this.curr.piece, this.game.hold.piece,]; - } - - getHold() { - return this.game.hold.piece ? this.game.hold.piece.name : "" - } - - setNewHold(val) { - const validPiece = [val].filter(p => this.pieceNames.includes(p)); - this.piece = this.getPiece(validPiece); - this.occured = false; - this.game.renderer.updateHold(); - this.game.history.save(); - } - - getPiece(name) { - return pieces.filter(p => p.name == name)[0]; - } - +import { Game } from "../game.js"; +import pieces from "../data/pieces.json" with { type: "json" }; + + +export class Hold { + piece; + occured = false; + pieceNames = ["s", "z", "i", "j", "l", "o", "t"]; + + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + this.curr = this.game.falling; + } + + setHold() { + this.piece = this.curr.piece; + } + + swapHold() { + [this.game.hold.piece, this.curr.piece] + = [this.curr.piece, this.game.hold.piece,]; + } + + getHold() { + return this.game.hold.piece ? this.game.hold.piece.name : "" + } + + setNewHold(val) { + const validPiece = [val].filter(p => this.pieceNames.includes(p)); + this.piece = this.getPiece(validPiece); + this.occured = false; + this.game.renderer.updateHold(); + this.game.history.save(); + } + + getPiece(name) { + return pieces.filter(p => p.name == name)[0]; + } + } \ No newline at end of file diff --git a/src/mechanics/locking.js b/src/mechanics/locking.js index c6fe641..add4963 100644 --- a/src/mechanics/locking.js +++ b/src/mechanics/locking.js @@ -1,127 +1,135 @@ -import { Game } from "../game.js"; - -export class LockPiece { - divLockTimer = document.getElementById("lockTimer"); - divLockCounter = document.getElementById("lockCounter"); - lockCount; - timings = { lockdelay: 0, lockingTimer: 0 } - - startTime = 0; - remaining = 0; - - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - } - - incrementLock() { - if (this.timings.lockdelay != 0) { - this.lockCount++; - this.game.mechanics.locking.clearLockDelay(false); - if (this.game.settings.game.maxLockMovements != 0 && this.game.settings.display.lockBar) { - const amountToAdd = 100 / this.game.settings.game.maxLockMovements; - this.divLockCounter.value += amountToAdd; - } - } - if (this.game.movement.checkCollision(this.game.mechanics.board.getMinos("A"), "DOWN")) { - this.game.mechanics.locking.scheduleLock(); - } - } - - scheduleLock() { - const LockMoves = - this.game.settings.game.maxLockMovements == 0 - ? Infinity - : this.game.settings.game.maxLockMovements; - if (this.lockCount >= LockMoves) { - this.game.mechanics.locking.lockPiece(); - return; - } - if (this.game.settings.game.lockDelay == 0) return; - - this.lockDelayStart(this.game.settings.game.lockDelay); - } - - lockDelayStart(delay) { - clearTimeout(this.timings.lockdelay); - clearInterval(this.timings.lockingTimer); - this.startTime = Date.now(); - this.timings.lockdelay = setTimeout( - () => this.game.mechanics.locking.lockPiece(), - delay); - this.timings.lockingTimer = setInterval(() => { - const amountToAdd = 1000 / this.game.settings.game.lockDelay; - if (this.game.settings.display.lockBar) this.divLockTimer.value += amountToAdd; - }, 10); - } - - lockingPause() { - if (this.timings.lockdelay == 0) return; - this.remaining = this.game.settings.game.lockDelay - (Date.now() - this.startTime); - clearTimeout(this.timings.lockdelay); - clearInterval(this.timings.lockingTimer); - } - - lockingResume() { - if (this.timings.lockdelay == 0) return; - this.lockDelayStart(this.remaining); - } - - lockPiece() { - const lockCoords = this.game.mechanics.board.getMinos("A"); - this.game.boardrender.justPlacedCoords = lockCoords; - this.game.boardrender.justPlacedAlpha = 1; - - lockCoords.forEach(([x, y]) => { - this.game.boardrender.flashTimes.push({ c:[x, y], t: 15 }) - this.game.mechanics.board.rmValue([x, y], "A"); - this.game.mechanics.board.addValFront([x, y], "S"); - }); - this.game.mechanics.locking.clearLockDelay(); - clearInterval(this.game.gravityTimer); - this.game.mechanics.clear.clearLines(lockCoords); - this.game.endGame( // stopped overlap next - this.game.mechanics.checkDeath( - this.game.mechanics.board.getMinos("S"), - this.game.mechanics.board.getMinos("NP") - ) - ); - this.game.endGame( // check lockout - this.game.mechanics.checkDeath( - lockCoords, - this.game.mechanics.board.getMinos("NP") - ) - ); - this.game.stats.pieceCount++; - this.game.hold.occured = false; - this.game.mechanics.isTspin = false; - this.game.mechanics.isAllspin = false; - this.game.mechanics.isMini = false; - this.game.falling.moved = false; - if (this.game.stats.level % 100 != 99 && this.game.stats.level != this.game.settings.game.raceTarget - 1) this.game.stats.level++; - const xvals = [...new Set(lockCoords.map(([x, y]) => x))]; - const yval = Math.min(...lockCoords.map(([x, y]) => y)); - this.game.particles.spawnParticles(Math.min(...xvals) + 1, yval, "lock", xvals.length); - this.game.mechanics.spawnPiece(this.game.bag.randomiser()); - this.game.history.save(); - this.game.renderer.renderDanger(); - } - - clearLockDelay(clearCount = true) { - clearInterval(this.timings.lockingTimer); - this.stopTimeout("lockdelay"); - this.divLockTimer.value = 0; - if (!clearCount) return; - this.divLockCounter.value = 0; - this.lockCount = 0; - if (this.game.settings.game.preserveARR) return; - this.game.controls.resetMovements(); - } - - stopTimeout(name) { - clearTimeout(this.timings[name]); - this.timings[name] = 0; - } -} +import { Game } from "../game.js"; + +export class LockPiece { + divLockTimer = document.getElementById("lockTimer"); + divLockCounter = document.getElementById("lockCounter"); + lockCount; + timings = { lockdelay: 0, lockingTimer: 0, clearDelay: 0 } + + startTime = 0; + remaining = 0; + + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + } + + incrementLock() { + if (this.timings.lockdelay != 0) { + this.lockCount++; + this.game.mechanics.locking.clearLockDelay(false); + if (this.game.settings.game.maxLockMovements != 0 && this.game.settings.display.lockBar) { + const amountToAdd = 100 / this.game.settings.game.maxLockMovements; + this.divLockCounter.value += amountToAdd; + } + } + if (this.game.movement.checkCollision(this.game.mechanics.board.getMinos("A"), "DOWN")) { + this.game.mechanics.locking.scheduleLock(); + } + } + + scheduleLock() { + const LockMoves = + this.game.settings.game.maxLockMovements == 0 + ? Infinity + : this.game.settings.game.maxLockMovements; + if (this.lockCount >= LockMoves) { + this.game.mechanics.locking.lockPiece(); + return; + } + if (this.game.settings.game.lockDelay == 0) return; + + this.lockDelayStart(this.game.settings.game.lockDelay); + } + + lockDelayStart(delay) { + clearTimeout(this.timings.lockdelay); + clearInterval(this.timings.lockingTimer); + this.startTime = Date.now(); + this.timings.lockdelay = setTimeout( + () => this.game.mechanics.locking.lockPiece(), + delay); + this.timings.lockingTimer = setInterval(() => { + const amountToAdd = 1000 / this.game.settings.game.lockDelay; + if (this.game.settings.display.lockBar) this.divLockTimer.value += amountToAdd; + }, 10); + } + + lockingPause() { + if (this.timings.lockdelay == 0) return; + this.remaining = this.game.settings.game.lockDelay - (Date.now() - this.startTime); + clearTimeout(this.timings.lockdelay); + clearInterval(this.timings.lockingTimer); + } + + lockingResume() { + if (this.timings.lockdelay == 0) return; + this.lockDelayStart(this.remaining); + } + + lockPiece() { + const lockCoords = this.game.mechanics.board.getMinos("A"); + this.game.boardrender.justPlacedCoords = lockCoords; + this.game.boardrender.justPlacedAlpha = 1; + + lockCoords.forEach(([x, y]) => { + this.game.boardrender.flashTimes.push({ c: [x, y], t: 15 }) + this.game.mechanics.board.rmValue([x, y], "A"); + this.game.mechanics.board.addValFront([x, y], "S"); + }); + + this.game.mechanics.locking.clearLockDelay(); + clearInterval(this.game.gravityTimer); + const cleared = this.game.mechanics.clear.clearLines(lockCoords); + + this.game.endGame( // check stopped overlap next + this.game.mechanics.checkDeath( + this.game.mechanics.board.getMinos("S"), + this.game.mechanics.board.getMinos("NP") + ) + ); + this.game.endGame( // check lockout + this.game.mechanics.checkDeath( + lockCoords, + this.game.mechanics.board.getMinos("NP") + ) + ); + this.game.stats.pieceCount++; + this.game.hold.occured = false; + this.game.mechanics.isTspin = false; + this.game.mechanics.isAllspin = false; + this.game.mechanics.isMini = false; + this.game.falling.moved = false; + if (this.game.stats.tgm_level % 100 != 99 && this.game.stats.tgm_level != this.game.settings.game.raceTarget - 1) this.game.stats.tgm_level++; + + const xvals = [...new Set(lockCoords.map(([x, y]) => x))]; + const yval = Math.min(...lockCoords.map(([x, y]) => y)); + this.game.particles.spawnParticles(Math.min(...xvals) + 1, yval, "lock", xvals.length); + this.game.renderer.renderDanger(); + + const delay = (cleared > 0) ? this.game.settings.game.clearDelay : 0; + this.timings.clearDelay = setTimeout(() => { + this.game.mechanics.spawnPiece(this.game.bag.randomiser()); + this.game.history.save(); + this.timings.clearDelay = 0; + }, delay); + } + + clearLockDelay(clearCount = true) { + clearInterval(this.timings.lockingTimer); + this.stopTimeout("lockdelay"); + this.divLockTimer.value = 0; + if (!clearCount) return; + this.divLockCounter.value = 0; + this.lockCount = 0; + if (this.game.settings.game.preserveARR) return; + this.game.controls.resetMovements(); + } + + stopTimeout(name) { + clearTimeout(this.timings[name]); + this.timings[name] = 0; + } +} diff --git a/src/mechanics/mechanics.js b/src/mechanics/mechanics.js index a468c9a..403409b 100644 --- a/src/mechanics/mechanics.js +++ b/src/mechanics/mechanics.js @@ -1,141 +1,140 @@ -import { Game } from "../game.js"; -import { ClearLines } from "./clearlines.js"; -import { LockPiece } from "./locking.js"; - -export class Mechanics { - board; - isTspin = false; - isAllspin = false; - isMini = false; - garbageQueue = 0; - spikeCounter = 0; - toppingOut = false; - zenithTimer = 0; - - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - this.board = game.board; - this.clear = new ClearLines(game); - this.locking = new LockPiece(game); - } - - checkDeath(coords, collider) { - if (coords.length == 0) return; - const collision = coords.every(c => this.game.movement.checkCollision([c], "PLACE", [])); - const collision2 = this.game.movement.checkCollision(coords, "SPAWN", collider); - const isGarbage = collider.some(c => this.board.checkMino(c, "G")); - if (collision && this.game.settings.game.allowLockout) return "Lockout"; - if (collision2 && isGarbage) return "Topout"; - if (collision2) return "Blockout"; - } - - deathAlert() { - const check = this.checkDeath(this.board.getMinos('Sh'), this.board.getMinos('NP')); - const check2 = this.checkDeath(this.board.getMinos('G'), this.board.getMinos('NP')); - const warn = document.getElementById('warningText'); - if (!!(check || check2)) { - if (this.toppingOut) return; - this.game.sounds.playSound('hyperalert'); - this.toppingOut = true; - warn.classList.toggle('warn', true); - } else { - this.toppingOut = false; - warn.classList.toggle('warn', false); - } - } - - spawnPiece(piece, start = false) { - if (this.game.ended) return; - this.game.falling.spawn(piece); - this.spawnOverlay(); - this.game.renderer.updateNext(); - this.game.renderer.updateHold(); - this.setShadow(); - this.locking.incrementLock(); - this.game.modes.diggerGarbageSet(start); - this.game.modes.set4WCols(start); - if (this.game.settings.game.preserveARR) this.game.controls.startArr("current"); - if (this.game.started) this.startGravity(); - } - - spawnOverlay() { - this.board.MinoToNone("NP"); - const next = this.game.bag.nextPiece(); - const x = next.name == "o" ? 4 : 3; - const y = next.name == "o" ? 21 : next.name == "i" ? 19 : 20; - this.board.pieceToCoords(next.shape1, [x, y]).forEach(([x, y]) => this.board.addValue([x, y], "NP")); - } - - setShadow() { - this.board.MinoToNone("Sh"); - const coords = this.board.getMinos("A"); - if (coords.length == 0) return; - coords.forEach(([x, y]) => this.board.addValue([x, y], "Sh")); - let count = 0; - const shadow = this.board.getMinos("Sh"); - while (!this.game.movement.checkCollision(shadow.map(c => [c[0], c[1] - count]), "DOWN")) - count++; - this.board.moveMinos(shadow, "DOWN", count, "Sh"); - this.deathAlert(); - } - - startGravity() { - clearInterval(this.game.gravityTimer); - if (this.game.settings.game.gravitySpeed > 1000) return; - if (this.game.settings.game.gravitySpeed == 0) { - this.game.movement.movePieceDown(true); - return; - } - this.game.movement.movePieceDown(false); - this.game.gravityTimer = setInterval( - () => this.game.movement.movePieceDown(false), - this.game.settings.game.gravitySpeed - ); - } - - addGarbage(lines, messiness = 100) { - let randCol = Math.floor(Math.random() * 10); - for (let i = 0; i < lines; i++) { - if (this.game.movement.checkCollision(this.board.getMinos("A"), "DOWN")) { - if (this.locking.timings.lockdelay == 0) this.locking.scheduleLock(); - this.board.moveMinos(this.board.getMinos("A"), "UP", 1); - } - this.board.moveMinos(this.board.getMinos("S"), "UP", 1); - const mustchange = Math.floor(Math.random() * 100); - if (mustchange < messiness) randCol = Math.floor(Math.random() * 10); - for (let col = 0; col < 10; col++) { - if (col != randCol) this.board.addMinos("S G", [[col, 0]], [0, 0]); - } - } - this.setShadow(); - } - - switchHold() { - if (this.game.hold.occured || !this.game.settings.game.allowHold) return; - this.locking.clearLockDelay(); - this.board.MinoToNone("A"); - this.isTspin = false; - this.isAllspin = false; - this.isMini = false; - this.game.stats.holds++; - if (this.game.hold.piece == null) { - this.game.hold.setHold(); - this.spawnPiece(this.game.bag.randomiser()); - } else { - this.game.hold.swapHold(); - this.spawnPiece(this.game.falling.piece); - } - if (this.checkDeath(this.board.getMinos("A"), this.board.getMinos("S")) == "Blockout") { - this.game.endGame("Blockout"); - return; - } - if (!this.game.settings.game.infiniteHold) this.game.hold.occured = true; - this.game.sounds.playSound("hold"); - this.game.renderer.renderDanger(); - this.startGravity(); - this.game.renderer.updateHold(); - } -} +import { Game } from "../game.js"; +import { ClearLines } from "./clearlines.js"; +import { LockPiece } from "./locking.js"; + +export class Mechanics { + board; + isTspin = false; + isAllspin = false; + isMini = false; + garbageQueue = 0; + spikeCounter = 0; + toppingOut = false; + + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + this.board = game.board; + this.clear = new ClearLines(game); + this.locking = new LockPiece(game); + } + + checkDeath(coords, collider) { + if (coords.length == 0) return; + const collision = coords.every(c => this.game.movement.checkCollision([c], "PLACE", [])); + const collision2 = this.game.movement.checkCollision(coords, "SPAWN", collider); + const isGarbage = collider.some(c => this.board.checkMino(c, "G")); + if (collision && this.game.settings.game.allowLockout) return "Lockout"; + if (collision2 && isGarbage) return "Topout"; + if (collision2) return "Blockout"; + } + + deathAlert() { + const check = this.checkDeath(this.board.getMinos('Sh'), this.board.getMinos('NP')); + const check2 = this.checkDeath(this.board.getMinos('G'), this.board.getMinos('NP')); + const warn = document.getElementById('warningText'); + if (!!(check || check2)) { + if (this.toppingOut) return; + this.game.sounds.playSound('hyperalert'); + this.toppingOut = true; + warn.classList.toggle('warn', true); + } else { + this.toppingOut = false; + warn.classList.toggle('warn', false); + } + } + + spawnPiece(piece, start = false) { + if (this.game.ended) return; + this.game.falling.spawn(piece); + this.spawnOverlay(); + this.game.renderer.updateNext(); + this.game.renderer.updateHold(); + this.setShadow(); + this.locking.incrementLock(); + this.game.modes.diggerGarbageSet(start); + this.game.modes.set4WCols(start); + if (this.game.settings.game.preserveARR) this.game.controls.startArr("current"); + if (this.game.started) this.startGravity(); + } + + spawnOverlay() { + this.board.MinoToNone("NP"); + const next = this.game.bag.nextPiece(); + const x = next.name == "o" ? 4 : 3; + const y = next.name == "o" ? 21 : next.name == "i" ? 19 : 20; + this.board.pieceToCoords(next.shape1, [x, y]).forEach(([x, y]) => this.board.addValue([x, y], "NP")); + } + + setShadow() { + this.board.MinoToNone("Sh"); + const coords = this.board.getMinos("A"); + if (coords.length == 0) return; + coords.forEach(([x, y]) => this.board.addValue([x, y], "Sh")); + let count = 0; + const shadow = this.board.getMinos("Sh"); + while (!this.game.movement.checkCollision(shadow.map(c => [c[0], c[1] - count]), "DOWN")) + count++; + this.board.moveMinos(shadow, "DOWN", count, "Sh"); + this.deathAlert(); + } + + startGravity() { + clearInterval(this.game.gravityTimer); + if (this.game.settings.game.gravitySpeed > 1000) return; + if (this.game.settings.game.gravitySpeed == 0) { + this.game.movement.movePieceDown(true); + return; + } + this.game.movement.movePieceDown(false); + this.game.gravityTimer = setInterval( + () => this.game.movement.movePieceDown(false), + this.game.settings.game.gravitySpeed + ); + } + + addGarbage(lines, messiness = 100) { + let randCol = Math.floor(Math.random() * 10); + for (let i = 0; i < lines; i++) { + if (this.game.movement.checkCollision(this.board.getMinos("A"), "DOWN")) { + if (this.locking.timings.lockdelay == 0) this.locking.scheduleLock(); + this.board.moveMinos(this.board.getMinos("A"), "UP", 1); + } + this.board.moveMinos(this.board.getMinos("S"), "UP", 1); + const mustchange = Math.floor(Math.random() * 100); + if (mustchange < messiness) randCol = Math.floor(Math.random() * 10); + for (let col = 0; col < 10; col++) { + if (col != randCol) this.board.addMinos("S G", [[col, 0]], [0, 0]); + } + } + this.setShadow(); + } + + switchHold() { + if (this.game.hold.occured || !this.game.settings.game.allowHold) return; + this.locking.clearLockDelay(); + this.board.MinoToNone("A"); + this.isTspin = false; + this.isAllspin = false; + this.isMini = false; + this.game.stats.holds++; + if (this.game.hold.piece == null) { + this.game.hold.setHold(); + this.spawnPiece(this.game.bag.randomiser()); + } else { + this.game.hold.swapHold(); + this.spawnPiece(this.game.falling.piece); + } + if (this.checkDeath(this.board.getMinos("A"), this.board.getMinos("S")) == "Blockout") { + this.game.endGame("Blockout"); + return; + } + if (!this.game.settings.game.infiniteHold) this.game.hold.occured = true; + this.game.sounds.playSound("hold"); + this.game.renderer.renderDanger(); + this.startGravity(); + this.game.renderer.updateHold(); + } +} diff --git a/src/mechanics/zenith.js b/src/mechanics/zenith.js deleted file mode 100644 index b4cb54f..0000000 --- a/src/mechanics/zenith.js +++ /dev/null @@ -1,116 +0,0 @@ -import { Game } from "../game.js"; - -export class Zenith { - - /** - * @param {Game} game - */ - - constructor(game) { - this.game = game - } - - climbPoints = 0; - isLastRankChangePromote = !0; - isHyperspeed = true; - rankLock = 0; - promotionFatigue = 0; - rankLock = 0; - tickPass = 0; - tempAltitude = 0 - - FloorDistance = [0, 50, 150, 300, 450, 650, 850, 1100, 1350, 1650, 1 / 0]; - SpeedrunReq = [7, 8, 8, 9, 9, 10, 0, 0, 0, 0, 0]; - - - GetSpeedCap(e) { - const t = this.FloorDistance.find((t => e < t)) - e; - return Math.max(0, Math.min(1, t / 5 - .2)) - } - - GetFloorLevel(e) { - return this.FloorDistance.filter((t => e >= t)).length || 1 - } - - AwardLines(e, t=!0, n=!0) { - const s = .25 * Math.floor(this.game.stats.climbSpeed); - this.GiveBonus(s * e * (t ? 1 : 0)); - if (e <= 0 ) return - this.GiveClimbPts((e + .05) * (n ? 1 : 0)) - } - - GiveBonus(e) { - this.tempAltitude += e - } - - GiveClimbPts(e) { - this.climbPoints += e - } - - startZenithMode() { - clearInterval(this.game.mechanics.zenithTimer); - document.getElementById("climbSpeedBar").style.display = "none" - if(this.game.settings.game.gamemode != "zenith") return - document.getElementById("climbSpeedBar").style.display = "block" - this.game.mechanics.zenithTimer = setInterval( - () => { - let t = Math.floor(this.game.stats.climbSpeed), - o = .25 * t, - a = this.GetSpeedCap(this.tempAltitude); - - if (this.tickPass >= this.rankLock) { - let e = 3; - this.climbPoints -= e * (t ** 2 + t) / 3600 - } - const s = 4 * t, - i = 4 * (t - 1) - - if (this.climbPoints < 0){ - if (t <= 1){ - this.climbPoints = 0; - } - else { - this.climbPoints += i, - this.game.sounds.playSound("speed_down") - this.isLastRankChangePromote = !1, - t-- - } - } - else if (this.climbPoints >= s) { - this.climbPoints -= s, - this.game.sounds.playSound("speed_up") - this.isLastRankChangePromote = !0, - t++; - this.rankLock = this.tickPass + Math.max(60, 60 * (5 - this.promotionFatigue)); - this.promotionFatigue++; - } - - this.game.stats.climbSpeed = t + this.climbPoints / (4 * t); - - this.tempAltitude += o / 60 * a - - if(this.game.stats.floor != this.GetFloorLevel(this.tempAltitude)){ - this.startZenithMode() - this.game.stats.floor = this.GetFloorLevel(this.tempAltitude) - this.game.sounds.playSound("zenith_levelup") - this.game.renderer.renderTimeLeft("FLOOR " + this.game.stats.floor) - } - - this.game.stats.altitude = Math.floor(this.tempAltitude) - this.tickPass++ - this.drawClimbSpeedBar(Math.floor(this.game.stats.climbSpeed), this.climbPoints, s) - } - , 1000 / this.game.tickrate); - } - - drawClimbSpeedBar(speed, point, require){ - const color = ["var(--invis)", "red", "orange", "green", "blue", "#FF1493", "tan", "lightgreen", "lightblue", "pink", "white", "white", "white", "white", "white", "white", "white", "white", "white", "white", "white", "white", "white", "white", "white", "white"] - const climbSpeedBar = document.getElementById("climbSpeedBar") - - climbSpeedBar.value = point - climbSpeedBar.max = require - document.styleSheets[1].cssRules[24].style.backgroundColor = color[speed - 1] - document.styleSheets[1].cssRules[23].style.backgroundColor = color[speed] - } - -} \ No newline at end of file diff --git a/src/menus/generate.js b/src/menus/generate.js index 51dac74..fdb2ebe 100644 --- a/src/menus/generate.js +++ b/src/menus/generate.js @@ -1,217 +1,217 @@ -import { defaultSkins } from "../data/data.js"; -import { Game } from "../game.js"; - - -export class GenerateMenus { - gamemodeStart = document.getElementById("startGamemodeList"); - pblistStart = document.getElementById("PBlist"); - statsStart = document.getElementById("startStatsList"); - notifStack = document.getElementById("notifications"); - - settingDialogs = [...document.getElementsByClassName("settingsBox")]; - settings = [...document.getElementsByClassName("settingRow")]; - - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - } - - generateGamemodeMenu() { - this.game.modes.getGamemodeNames().forEach(name => { - const setting = this.game.modes.getGamemodeJSON(name); - const button = document.createElement("button"); - button.id = name; - button.classList.add("gamemodeSelect"); - button.textContent = setting.displayName; - button.addEventListener("click", () => { - menu.setGamemode(name); - modal.closeModal("gamemodeDialog"); - }); - this.gamemodeStart.parentNode.insertBefore(button, this.gamemodeStart); - }) - this.gamemodeStart.remove(); - } - - highlightGamemodeInMenu() { - const gamemodeSelect = [...document.getElementsByClassName("gamemodeSelect")]; - gamemodeSelect.forEach(setting => { - setting.classList.remove("selected"); - if (setting.id == this.game.settings.game.gamemode) - setting.classList.add("selected"); - }); - } - - generateSkinList() { - const el = document.getElementById("skin").parentElement; - const list = document.createElement("datalist"); - list.id = "options"; - defaultSkins.forEach(skin => { - const option = document.createElement("option"); - option.value = skin; - list.appendChild(option); - }) - el.appendChild(list); - } - - generateStatList() { - const statoptions = [...document.getElementsByClassName("statoption")]; - const options = Object.getOwnPropertyNames(this.game.stats); - options.sort(); - options.unshift("None"); - - statoptions.forEach(setting => { - options.forEach(stat => { - const skip = ['clearCols', 'clearPieces', 'game', 'tspins']; - if (skip.includes(stat)) return; - const option = document.createElement("option"); - option.textContent = stat; - setting.appendChild(option); - }); - }); - } - - renderPBs() { - const previous = [...document.getElementsByClassName("pbbox")]; - previous.forEach(el => el.remove()); - - const pbs = this.game.profilestats.personalBests; - Object.keys(pbs).forEach(mode => { - const score = pbs[mode].score - const pbbox = document.createElement("div"); - - const text1 = document.createElement("h2") - text1.textContent = mode[0].toUpperCase() + mode.slice(1) + ': '; - const text2 = document.createElement("h2") - text2.textContent = score + this.game.modes.getSuffix(mode); - const clearbutton = document.createElement("button"); - clearbutton.textContent = "X"; - clearbutton.addEventListener("click", (event) => { - event.stopPropagation(); - this.game.profilestats.removePB(mode); - pbbox.remove() - }); - pbbox.appendChild(text1); - pbbox.appendChild(text2); - pbbox.appendChild(clearbutton); - pbbox.addEventListener("click", () => { - let el = document.createElement("a"); - el.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(JSON.stringify(pbs[mode]))); - el.setAttribute("download", `${mode}_pb.json`); - document.body.appendChild(el); - el.click(); - document.body.removeChild(el); - }) - - pbbox.classList.add("pbbox"); - - this.pblistStart.parentNode.insertBefore(pbbox, this.pblistStart); - }) - } - - displayStats() { - const previous = [...document.getElementsByClassName("statText")]; - previous.forEach(el => el.remove()); - - const stats = Object.getOwnPropertyNames(this.game.stats); - const skip = ['clearCols', 'clearPieces', 'game'] - stats.forEach(stat => { - if (skip.includes(stat)) return; - let score = this.game.stats[stat] - if (stat == "tspins") score = score.reduce((a, b) => a + b, 0) - const statItem = document.createElement("p"); - statItem.classList = "statText"; - - const text1 = document.createElement("span") - text1.classList = "spanleft" - text1.textContent = stat + ":" - const text2 = document.createElement("span") - text2.classList = "spanright" - text2.textContent = Math.round(score * 1000) / 1000 - statItem.appendChild(text1); - statItem.appendChild(text2); - this.statsStart.parentNode.insertBefore(statItem, this.statsStart); - }) - } - - updateSizes(box, settings) { - const totalHeight = box.scrollHeight; - settings.forEach(setting => { - const position = setting.getBoundingClientRect().top - let newpos = 1 - (position - window.innerHeight * 0.46) / totalHeight - if (newpos > 1) newpos = newpos - 2 * (newpos - 1); - - setting.style.scale = newpos < 0.7 ? 0.9 : 1 - setting.style.opacity = newpos < 0.7 ? 0.3 : 1 - }) - } - - addMenuListeners() { - this.settingDialogs.forEach(box => { - const boxsettings = this.settings.filter(item => item.parentElement.parentElement.id == box.parentElement.id); - box.addEventListener("scroll", () => { - this.updateSizes(box, boxsettings); - }) - }) - - const selectKeys = [...document.getElementsByClassName("keybind")]; - selectKeys.forEach(key => { - key.parentElement.addEventListener("click", () => { - menu.buttonInput(key); - }) - }) - - const sliders = [...document.getElementsByClassName("range")]; - sliders.forEach(slider => { - slider.addEventListener("input", () => { - menu.sliderChange(slider); - }) - }) - - const limiter = document.getElementById("limiter"); - const limiter2 = document.getElementById("limiter2"); - const numberInput = [...document.getElementsByClassName("number")]; - - numberInput.forEach(input => { - input.addEventListener("input", () => { - if (input.id == "backfireMulti") { menu.checkValue(input, limiter2) } - else if (input.id == "rangeValue") { menu.checkValue(input) } - else { menu.checkValue(input, limiter); } - }) - }) - - const gridType = document.getElementById("gridType"); - const types = ["round", "square", "dot"]; - types.forEach(type => { - const option = document.createElement("option"); - option.textContent = type; - gridType.appendChild(option); - }) - } - - notif(heading, message, type) { - const types = { "message": "white", "success": "lightgreen", "error": "red", } - const notif = document.createElement("div"); // notif box - notif.classList.add("notif"); - notif.style.setProperty("--color", types[type]); - const title = document.createElement("p"); // heading - title.classList.add("notif_title"); - title.textContent = heading; - notif.appendChild(title); - const text = document.createElement("p"); - text.classList.add("notif_text"); - text.textContent = message; - notif.appendChild(text); // text - this.notifStack.appendChild(notif); - - const remove = () => { - notif.style.animation = "fadeout 0.5s forwards"; - setTimeout(() => notif.remove(), 1000) - } - - setTimeout(() => remove(), 10 * 1000) - notif.addEventListener("click", () => remove()) - - } +import { defaultSkins } from "../data/data.js"; +import { Game } from "../game.js"; + + +export class GenerateMenus { + gamemodeStart = document.getElementById("startGamemodeList"); + pblistStart = document.getElementById("PBlist"); + statsStart = document.getElementById("startStatsList"); + notifStack = document.getElementById("notifications"); + + settingDialogs = [...document.getElementsByClassName("settingsBox")]; + settings = [...document.getElementsByClassName("settingRow")]; + + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + } + + generateGamemodeMenu() { + this.game.modes.getGamemodeNames().forEach(name => { + const setting = this.game.modes.getGamemodeJSON(name); + const button = document.createElement("button"); + button.id = name; + button.classList.add("gamemodeSelect"); + button.textContent = setting.displayName; + button.addEventListener("click", () => { + menu.setGamemode(name); + modal.closeModal("gamemodeDialog"); + }); + this.gamemodeStart.parentNode.insertBefore(button, this.gamemodeStart); + }) + this.gamemodeStart.remove(); + } + + highlightGamemodeInMenu() { + const gamemodeSelect = [...document.getElementsByClassName("gamemodeSelect")]; + gamemodeSelect.forEach(setting => { + setting.classList.remove("selected"); + if (setting.id == this.game.settings.game.gamemode) + setting.classList.add("selected"); + }); + } + + generateSkinList() { + const el = document.getElementById("skin").parentElement; + const list = document.createElement("datalist"); + list.id = "options"; + defaultSkins.forEach(skin => { + const option = document.createElement("option"); + option.value = skin; + list.appendChild(option); + }) + el.appendChild(list); + } + + generateStatList() { + const statoptions = [...document.getElementsByClassName("statoption")]; + const options = Object.getOwnPropertyNames(this.game.stats); + options.sort(); + options.unshift("None"); + + statoptions.forEach(setting => { + options.forEach(stat => { + const skip = ['clearCols', 'clearPieces', 'game', 'tspins']; + if (skip.includes(stat)) return; + const option = document.createElement("option"); + option.textContent = stat; + setting.appendChild(option); + }); + }); + } + + renderPBs() { + const previous = [...document.getElementsByClassName("pbbox")]; + previous.forEach(el => el.remove()); + + const pbs = this.game.profilestats.personalBests; + Object.keys(pbs).forEach(mode => { + const score = pbs[mode].score + const pbbox = document.createElement("div"); + + const text1 = document.createElement("h2") + text1.textContent = mode[0].toUpperCase() + mode.slice(1) + ': '; + const text2 = document.createElement("h2") + text2.textContent = score + this.game.modes.getSuffix(mode); + const clearbutton = document.createElement("button"); + clearbutton.textContent = "X"; + clearbutton.addEventListener("click", (event) => { + event.stopPropagation(); + this.game.profilestats.removePB(mode); + pbbox.remove() + }); + pbbox.appendChild(text1); + pbbox.appendChild(text2); + pbbox.appendChild(clearbutton); + pbbox.addEventListener("click", () => { + let el = document.createElement("a"); + el.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(JSON.stringify(pbs[mode]))); + el.setAttribute("download", `${mode}_pb.json`); + document.body.appendChild(el); + el.click(); + document.body.removeChild(el); + }) + + pbbox.classList.add("pbbox"); + + this.pblistStart.parentNode.insertBefore(pbbox, this.pblistStart); + }) + } + + displayStats() { + const previous = [...document.getElementsByClassName("statText")]; + previous.forEach(el => el.remove()); + + const stats = Object.getOwnPropertyNames(this.game.stats); + const skip = ['clearCols', 'clearPieces', 'game'] + stats.forEach(stat => { + if (skip.includes(stat)) return; + let score = this.game.stats[stat] + if (stat == "tspins") score = score.reduce((a, b) => a + b, 0) + const statItem = document.createElement("p"); + statItem.classList = "statText"; + + const text1 = document.createElement("span") + text1.classList = "spanleft" + text1.textContent = stat + ":" + const text2 = document.createElement("span") + text2.classList = "spanright" + text2.textContent = Math.round(score * 1000) / 1000 + statItem.appendChild(text1); + statItem.appendChild(text2); + this.statsStart.parentNode.insertBefore(statItem, this.statsStart); + }) + } + + updateSizes(box, settings) { + const totalHeight = box.scrollHeight; + settings.forEach(setting => { + const position = setting.getBoundingClientRect().top + let newpos = 1 - (position - window.innerHeight * 0.46) / totalHeight + if (newpos > 1) newpos = newpos - 2 * (newpos - 1); + + setting.style.scale = newpos < 0.7 ? 0.9 : 1 + setting.style.opacity = newpos < 0.7 ? 0.3 : 1 + }) + } + + addMenuListeners() { + this.settingDialogs.forEach(box => { + const boxsettings = this.settings.filter(item => item.parentElement.parentElement.id == box.parentElement.id); + box.addEventListener("scroll", () => { + this.updateSizes(box, boxsettings); + }) + }) + + const selectKeys = [...document.getElementsByClassName("keybind")]; + selectKeys.forEach(key => { + key.parentElement.addEventListener("click", () => { + menu.buttonInput(key); + }) + }) + + const sliders = [...document.getElementsByClassName("range")]; + sliders.forEach(slider => { + slider.addEventListener("input", () => { + menu.sliderChange(slider); + }) + }) + + const limiter = document.getElementById("limiter"); + const limiter2 = document.getElementById("limiter2"); + const numberInput = [...document.getElementsByClassName("number")]; + + numberInput.forEach(input => { + input.addEventListener("input", () => { + if (input.id == "backfireMulti") { menu.checkValue(input, limiter2) } + else if (input.id == "rangeValue") { menu.checkValue(input) } + else { menu.checkValue(input, limiter); } + }) + }) + + const gridType = document.getElementById("gridType"); + const types = ["round", "square", "dot"]; + types.forEach(type => { + const option = document.createElement("option"); + option.textContent = type; + gridType.appendChild(option); + }) + } + + notif(heading, message, type) { + const types = { "message": "white", "success": "lightgreen", "error": "red", } + const notif = document.createElement("div"); // notif box + notif.classList.add("notif"); + notif.style.setProperty("--color", types[type]); + const title = document.createElement("p"); // heading + title.classList.add("notif_title"); + title.textContent = heading; + notif.appendChild(title); + const text = document.createElement("p"); + text.classList.add("notif_text"); + text.textContent = message; + notif.appendChild(text); // text + this.notifStack.appendChild(notif); + + const remove = () => { + notif.style.animation = "fadeout 0.5s forwards"; + setTimeout(() => notif.remove(), 1000) + } + + setTimeout(() => remove(), 10 * 1000) + notif.addEventListener("click", () => remove()) + + } } \ No newline at end of file diff --git a/src/menus/menuactions.js b/src/menus/menuactions.js index 3120e4b..96f5e5c 100644 --- a/src/menus/menuactions.js +++ b/src/menus/menuactions.js @@ -1,240 +1,240 @@ -import { Game } from "../game.js"; -import { toExpValue } from "./modals.js"; - -export class MenuActions { - bindingKey; - menus; - elementSelectKeyText = document.getElementById("selectkeytext"); - controlUsed = false; - altUsed = false; - - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - } - - // sliders - sliderChange(el) { - const text = el.parentElement.children[0].textContent.split(":")[0]; - let value = el.value; - if (el.classList[2] == "exp") value = toExpValue(value); - if (el.classList[2] == "exp" && value > 1000) value = "None"; - el.parentElement.children[0].textContent = `${text}: ${value}`; - } - - addRangeListener() { - document.body.addEventListener("click", (event) => { - if (event.target.dataset.isRange == "true") { - const el = event.target.children[1] - this.game.modals.selectedRangeElement = el; - this.menus.openModal("changeRangeValue"); - document.getElementById("rangeValue").value = el.value; - } - }) - } - - rangeClickInit(el) { - el.parentElement.dataset.isRange = true; - } - - buttonInput(el) { - document.getElementById("frontdrop").showModal(); - this.bindingKey = el.id; - } - - checkValue(el, el2 = this.game.modals.selectedRangeElement) { - this.game.modals.selectedRangeElement = el2; - if (el.value == "") return; - if (el.value < Number(el2.min)) el.value = Number(el2.min); - if (el.value > Number(el2.max)) el.value = Number(el2.max); - } - - // keybinds - checkKeybind(event) { - if (!event.ctrlKey) this.controlUsed = false; - if (!event.altKey) this.altUsed = false; - this.elementSelectKeyText.textContent = "Click to remove keybind"; - - } - - setKeybind(event) { - if (!this.controlUsed && event.ctrlKey) { - this.controlUsed = true; - this.elementSelectKeyText.textContent += ". Control modifier used"; - return; - } - if (!this.altUsed && event.altKey) { - this.altUsed = true; - this.elementSelectKeyText.textContent += ". Alt modifier used"; - return; - } - - const key = (event.ctrlKey ? "Ctrl+" : "") + (event.altKey ? "Alt+" : "") + event.key; - document.getElementById(this.bindingKey).textContent = key; - for (let i in this.game.settings.control) { - if (i == this.bindingKey) continue; - const otherKeys = document.getElementById(i); - if (otherKeys.textContent == key) otherKeys.textContent = "None"; - } - this.menus.closeDialog(document.getElementById("frontdrop")); - this.game.modals.open = true; - this.bindingKey = undefined; - this.controlUsed = false; - this.altUsed = false; - this.elementSelectKeyText.textContent = "Click to remove keybind"; - } - - // settings - saveSettings() { - const data = this.game.settings.save(); - localStorage.setItem("settings", JSON.stringify(data)); - } - - loadSettings() { - const data = localStorage.getItem("settings") ?? "{}"; - this.game.settings.load(JSON.parse(data)) - this.game.modes.loadModes(); - this.game.history.setHistoryDiv(this.game.settings.game.gamemode == 'custom'); - this.game.boardeditor.setEditButton(this.game.settings.game.gamemode == 'custom'); - } - - setGamemode(mode) { - this.game.modes.setGamemode(mode); - this.game.modes.loadModes(); - this.game.history.setHistoryDiv(this.game.settings.game.gamemode == 'custom'); - this.game.boardeditor.setEditButton(this.game.settings.game.gamemode == 'custom'); - } - - downloadSettings() { - this.saveSettings(); - let el = document.createElement("a"); - const text = localStorage.getItem("settings"); - el.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(text)); - el.setAttribute("download", "settings.teti"); - document.body.appendChild(el); - el.click(); - document.body.removeChild(el); - } - - uploadSettings(el) { - const reader = new FileReader(); - reader.readAsText(el.files[0]); - reader.onload = () => { - localStorage.setItem("settings", reader.result.toString()); - this.loadSettings(); - this.game.modals.generate.notif("Settings Loaded", "User settings have successfully loaded", "message"); - }; - } - - resetSettings(group) { - this.game.settings.reset(group); - this.saveSettings(); - location.reload(); - this.game.modals.generate.notif("Settings Reset", `${group} settings have been reset to default`, "message"); - } - - // menu - toggleDialog() { - if (this.game.menuactions.bindingKey != undefined) return; - if (!this.game.modals.open) { - this.menus.openModal("settingsPanel"); - return; - } - document.querySelectorAll("dialog[open]").forEach(e => this.menus.closeDialog(e)); - document.querySelectorAll("scrollSettings[open]").forEach(e => this.menus.closeDialog(e)); - if (this.game.started && !this.game.ended) this.game.movement.firstMovement(); - } - - newGame(key, modal) { - if (key == this.game.settings.control.resetKey) { - this.game.modals.closeModal(modal); - } - } - - openPage(url) { - window.open("https://" + url, "blank_") - } - - // edit menu - openEditMenu() { - if (this.game.modals.open) { - if (document.querySelectorAll("#editMenu[open]").length == 0) return; - this.toggleDialog(); - return; - } - if (this.game.settings.game.gamemode != 'custom') return - this.menus.openModal("editMenu"); - } - - changeEditPiece(pieceName) { this.game.boardeditor.fillPiece = pieceName; } - - addGarbageRow() { - this.game.mechanics.addGarbage(1); - this.game.mechanics.setShadow(); - } - - removeLastRow() { - this.game.mechanics.clear.clearRow(0); - this.game.mechanics.setShadow(); - } - - clearGarbage() { - this.game.mechanics.board.EradicateMinoCells("S G"); - this.game.mechanics.setShadow(); - } - - setBoard() { - const input = prompt("Enter Map String Here:") - const { board, next, hold } = this.game.boardeditor.convertFromMap(input); - this.game.board.boardState = board; - this.game.bag.nextPieces = [next.split(","), []]; - this.game.hold.piece = this.game.renderer.getPiece(hold); - this.game.mechanics.spawnPiece(this.game.bag.randomiser()); - this.game.modals.generate.notif("Map Loaded", "Custom map has successfully loaded", "message"); - } - - getBoardString() { - const exportstring = this.game.boardeditor.convertToMap(); - navigator.clipboard.writeText(exportstring) - this.game.modals.generate.notif("Map Exported", "Custom map has been copied to your clipboard", "message"); - alert("TETR.IO Map String:\n" + exportstring) - } - - // stats - exportStats() { - let stats = {} - Object.getOwnPropertyNames(this.game.stats).forEach(key => { - if (key == "game") return; - stats[key] = this.game.stats[key]; - }) - - let el = document.createElement("a"); - el.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(JSON.stringify(stats))); - el.setAttribute("download", "stats.json"); - document.body.appendChild(el); - el.click(); - document.body.removeChild(el); - this.game.modals.generate.notif("Stats Exported", "The current game's stats have been exported.", "message"); - } - - closeStats() { - this.menus.closeDialog(document.getElementById("gameStatsDialog")); - this.game.modals.open = true; - } - - exportLifetime() { - this.game.profilestats.saveSession(); - const data = localStorage.getItem("stats"); - const day = (new Date()).toLocaleDateString().replace("/", "-"); - - let el = document.createElement("a"); - el.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(JSON.stringify(data))); - el.setAttribute("download", `teti_stats_${day}.json`); - document.body.appendChild(el); - el.click(); - document.body.removeChild(el); - this.game.modals.generate.notif("Lifetime Stats Exported", "All your lifetime stats and PBs have been exported. Enjoy the many stats you can analyse!", "success"); - } -} +import { Game } from "../game.js"; +import { toExpValue } from "./modals.js"; + +export class MenuActions { + bindingKey; + menus; + elementSelectKeyText = document.getElementById("selectkeytext"); + controlUsed = false; + altUsed = false; + + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + } + + // sliders + sliderChange(el) { + const text = el.parentElement.children[0].textContent.split(":")[0]; + let value = el.value; + if (el.classList[2] == "exp") value = toExpValue(value); + if (el.classList[2] == "exp" && value > 1000) value = "None"; + el.parentElement.children[0].textContent = `${text}: ${value}`; + } + + addRangeListener() { + document.body.addEventListener("click", (event) => { + if (event.target.dataset.isRange == "true") { + const el = event.target.children[1] + this.game.modals.selectedRangeElement = el; + this.menus.openModal("changeRangeValue"); + document.getElementById("rangeValue").value = el.value; + } + }) + } + + rangeClickInit(el) { + el.parentElement.dataset.isRange = true; + } + + buttonInput(el) { + document.getElementById("frontdrop").showModal(); + this.bindingKey = el.id; + } + + checkValue(el, el2 = this.game.modals.selectedRangeElement) { + this.game.modals.selectedRangeElement = el2; + if (el.value == "") return; + if (el.value < Number(el2.min)) el.value = Number(el2.min); + if (el.value > Number(el2.max)) el.value = Number(el2.max); + } + + // keybinds + checkKeybind(event) { + if (!event.ctrlKey) this.controlUsed = false; + if (!event.altKey) this.altUsed = false; + this.elementSelectKeyText.textContent = "Click to remove keybind"; + + } + + setKeybind(event) { + if (!this.controlUsed && event.ctrlKey) { + this.controlUsed = true; + this.elementSelectKeyText.textContent += ". Control modifier used"; + return; + } + if (!this.altUsed && event.altKey) { + this.altUsed = true; + this.elementSelectKeyText.textContent += ". Alt modifier used"; + return; + } + + const key = (event.ctrlKey ? "Ctrl+" : "") + (event.altKey ? "Alt+" : "") + event.key; + document.getElementById(this.bindingKey).textContent = key; + for (let i in this.game.settings.control) { + if (i == this.bindingKey) continue; + const otherKeys = document.getElementById(i); + if (otherKeys.textContent == key) otherKeys.textContent = "None"; + } + this.menus.closeDialog(document.getElementById("frontdrop")); + this.game.modals.open = true; + this.bindingKey = undefined; + this.controlUsed = false; + this.altUsed = false; + this.elementSelectKeyText.textContent = "Click to remove keybind"; + } + + // settings + saveSettings() { + const data = this.game.settings.save(); + localStorage.setItem("settings", JSON.stringify(data)); + } + + loadSettings() { + const data = localStorage.getItem("settings") ?? "{}"; + this.game.settings.load(JSON.parse(data)) + this.game.modes.loadModes(); + this.game.history.setHistoryDiv(this.game.settings.game.gamemode == 'custom'); + this.game.boardeditor.setEditButton(this.game.settings.game.gamemode == 'custom'); + } + + setGamemode(mode) { + this.game.modes.setGamemode(mode); + this.game.modes.loadModes(); + this.game.history.setHistoryDiv(this.game.settings.game.gamemode == 'custom'); + this.game.boardeditor.setEditButton(this.game.settings.game.gamemode == 'custom'); + } + + downloadSettings() { + this.saveSettings(); + let el = document.createElement("a"); + const text = localStorage.getItem("settings"); + el.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(text)); + el.setAttribute("download", "settings.teti"); + document.body.appendChild(el); + el.click(); + document.body.removeChild(el); + } + + uploadSettings(el) { + const reader = new FileReader(); + reader.readAsText(el.files[0]); + reader.onload = () => { + localStorage.setItem("settings", reader.result.toString()); + this.loadSettings(); + this.game.modals.generate.notif("Settings Loaded", "User settings have successfully loaded", "message"); + }; + } + + resetSettings(group) { + this.game.settings.reset(group); + this.saveSettings(); + location.reload(); + this.game.modals.generate.notif("Settings Reset", `${group} settings have been reset to default`, "message"); + } + + // menu + toggleDialog() { + if (this.game.menuactions.bindingKey != undefined) return; + if (!this.game.modals.open) { + this.menus.openModal("settingsPanel"); + return; + } + document.querySelectorAll("dialog[open]").forEach(e => this.menus.closeDialog(e)); + document.querySelectorAll("scrollSettings[open]").forEach(e => this.menus.closeDialog(e)); + if (this.game.started && !this.game.ended) this.game.movement.firstMovement(); + } + + newGame(key, modal) { + if (key == this.game.settings.control.resetKey) { + this.game.modals.closeModal(modal); + } + } + + openPage(url) { + window.open("https://" + url, "blank_") + } + + // edit menu + openEditMenu() { + if (this.game.modals.open) { + if (document.querySelectorAll("#editMenu[open]").length == 0) return; + this.toggleDialog(); + return; + } + if (this.game.settings.game.gamemode != 'custom') return + this.menus.openModal("editMenu"); + } + + changeEditPiece(pieceName) { this.game.boardeditor.fillPiece = pieceName; } + + addGarbageRow() { + this.game.mechanics.addGarbage(1); + this.game.mechanics.setShadow(); + } + + removeLastRow() { + this.game.mechanics.clear.clearRow(0); + this.game.mechanics.setShadow(); + } + + clearGarbage() { + this.game.mechanics.board.EradicateMinoCells("S G"); + this.game.mechanics.setShadow(); + } + + setBoard() { + const input = prompt("Enter Map String Here:") + const { board, next, hold } = this.game.boardeditor.convertFromMap(input); + this.game.board.boardState = board; + this.game.bag.nextPieces = [next.split(","), []]; + this.game.hold.piece = this.game.renderer.getPiece(hold); + this.game.mechanics.spawnPiece(this.game.bag.randomiser()); + this.game.modals.generate.notif("Map Loaded", "Custom map has successfully loaded", "message"); + } + + getBoardString() { + const exportstring = this.game.boardeditor.convertToMap(); + navigator.clipboard.writeText(exportstring) + this.game.modals.generate.notif("Map Exported", "Custom map has been copied to your clipboard", "message"); + alert("TETR.IO Map String:\n" + exportstring) + } + + // stats + exportStats() { + let stats = {} + Object.getOwnPropertyNames(this.game.stats).forEach(key => { + if (key == "game") return; + stats[key] = this.game.stats[key]; + }) + + let el = document.createElement("a"); + el.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(JSON.stringify(stats))); + el.setAttribute("download", "stats.json"); + document.body.appendChild(el); + el.click(); + document.body.removeChild(el); + this.game.modals.generate.notif("Stats Exported", "The current game's stats have been exported.", "message"); + } + + closeStats() { + this.menus.closeDialog(document.getElementById("gameStatsDialog")); + this.game.modals.open = true; + } + + exportLifetime() { + this.game.profilestats.saveSession(); + const data = localStorage.getItem("stats"); + const day = (new Date()).toLocaleDateString().replace("/", "-"); + + let el = document.createElement("a"); + el.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(JSON.stringify(data))); + el.setAttribute("download", `teti_stats_${day}.json`); + document.body.appendChild(el); + el.click(); + document.body.removeChild(el); + this.game.modals.generate.notif("Lifetime Stats Exported", "All your lifetime stats and PBs have been exported. Enjoy the many stats you can analyse!", "success"); + } +} diff --git a/src/menus/modals.js b/src/menus/modals.js index a3f1f1b..223a7fb 100644 --- a/src/menus/modals.js +++ b/src/menus/modals.js @@ -1,128 +1,128 @@ -import { Game } from "../game.js"; -import { GenerateMenus } from "./generate.js"; - -export class ModalActions { - open; - closing; - selectedRangeElement; - pieceNames = ["s", "z", "i", "j", "l", "o", "t"]; - - settingPanel = document.getElementById("settingsPanel"); - options = [...document.getElementsByClassName("option")]; - - - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - this.actions = this.game.menuactions; - game.menuactions.menus = this; - this.generate = new GenerateMenus(game); - } - - openModal(id) { - //ensure that everything has been closed before trying to open the settings panel - if (id == "settingsPanel" && this.closing) return; - if (id == "queueModify" && !this.game.settings.game.allowQueueModify) return; - this.game.stopGameTimers() - this.getOptions(id).forEach(setting => { - let settingType = this.getSettingType(id); - let newval; - if (this.game.settings.hasOwnProperty(settingType)) newval = this.game.settings[settingType][setting.id] - if (setting.classList[2] == "exp") newval = toLogValue(newval); - if (setting.classList[2] == "statoption") newval = this.game.settings.game.sidebar[setting.id[10]-1]; - if (setting.id == "nextQueue") newval = this.game.bag.getQueue(); - if (setting.id == "holdQueue") newval = this.game.hold.getHold(); - if (setting.id == "rowfillmode") newval = this.game.boardeditor.fillRow; - setting.value = newval; - if (setting.classList[1] == "keybind") setting.textContent = newval; - if (setting.classList[1] == "check") setting.checked = newval; - if (setting.classList[1] == "range") { - this.actions.sliderChange(setting); - this.actions.rangeClickInit(setting); - } - }); - - document.getElementById(id).showModal(); - - if (id == "gameStatsDialog") this.generate.displayStats(); - if (id == "gamemodeDialog") this.generate.highlightGamemodeInMenu(); - if (id == "competitiveDialog") this.generate.renderPBs(); - if (id != "settingsPanel" && this.settingPanel.open) this.closeDialog(this.settingPanel); - this.open = true; - this.game.sounds.toggleSongMuffle(this.open); - } - - getOptions(id) { - const settings = [...this.options].filter(item => item.parentElement.parentElement.parentElement.id == id) - return settings; - } - - getSettingType(id) { - let type = id.replace("Dialog", ""); - if (id == "gamemodeDialog" || id == "goalsDialog" || id == "competitiveDialog") type = "game"; - return type; - } - - closeModal(id) { - this.getOptions(id).forEach(setting => { - let settingType = this.getSettingType(id); - let val = setting.value; - if (setting.classList[1] == "number" && val == "") val = this.selectedRangeElement.min; - if (setting.classList[1] == "check") val = setting.checked; - if (setting.classList[1] == "keybind") { - val = setting.textContent.length > 1 - ? setting.textContent - : setting.textContent.toLowerCase(); - } - if (setting.classList[2] == "exp") val = toExpValue(val); - if (setting.classList[2] == "statoption") this.game.settings.game.sidebar[setting.id[10]-1] = val; - if (setting.id == "nextQueue") this.game.bag.setQueue(val, this.pieceNames); - if (setting.id == "holdQueue") this.game.hold.setNewHold(val); - if (setting.id == "rowfillmode") this.game.boardeditor.fillRow = val; - if (setting.id == "override") this.game.boardeditor.override = val; - - if (id == "changeRangeValue") { - this.selectedRangeElement.value = document.getElementById("rangeValue").value; - this.actions.sliderChange(this.selectedRangeElement); - } - if (setting.id == "audioLevel") this.game.sounds.setAudioLevel(); - - if (!this.game.settings.hasOwnProperty(settingType)) return; - this.game.settings[settingType][setting.id] = val; - }); - - this.closeDialog(document.getElementById(id)); - if (id != 'changeRangeValue' && id != "frontdrop" && this.game.started && !this.game.ended) - this.game.movement.firstMovement(); - this.actions.saveSettings(); - if (id == "displayDialog") this.game.renderer.renderStyles(); - - const restartMenus = ["gameDialog", "gamemodeDialog", "gameEnd", "goalsDialog", "competitiveDialog"]; - if (restartMenus.includes(id)) this.game.controls.retry(false); - if (id == "changeRangeValue") this.open = true; - } - - closeDialog(element) { - this.closing = true //track if the closing animation is still ongoing - const closingAnimation = () => { - element.removeEventListener("animationend", closingAnimation); - element.classList.remove("closingAnimation"); - element.close(); - this.closing = false - }; - this.open = false; - this.game.sounds.toggleSongMuffle(this.open); - element.classList.add("closingAnimation"); - element.addEventListener("animationend", closingAnimation); - } -} - -function toLogValue(y) { - return Math.round(Math.log2(y + 1) * 10); -} - -export function toExpValue(x) { - return Math.round(Math.pow(2, 0.1 * x) - 1); +import { Game } from "../game.js"; +import { GenerateMenus } from "./generate.js"; + +export class ModalActions { + open; + closing; + selectedRangeElement; + pieceNames = ["s", "z", "i", "j", "l", "o", "t"]; + + settingPanel = document.getElementById("settingsPanel"); + options = [...document.getElementsByClassName("option")]; + + + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + this.actions = this.game.menuactions; + game.menuactions.menus = this; + this.generate = new GenerateMenus(game); + } + + openModal(id) { + //ensure that everything has been closed before trying to open the settings panel + if (id == "settingsPanel" && this.closing) return; + if (id == "queueModify" && !this.game.settings.game.allowQueueModify) return; + this.game.stopGameTimers() + this.getOptions(id).forEach(setting => { + let settingType = this.getSettingType(id); + let newval; + if (this.game.settings.hasOwnProperty(settingType)) newval = this.game.settings[settingType][setting.id] + if (setting.classList[2] == "exp") newval = toLogValue(newval); + if (setting.classList[2] == "statoption") newval = this.game.settings.game.sidebar[setting.id[10]-1]; + if (setting.id == "nextQueue") newval = this.game.bag.getQueue(); + if (setting.id == "holdQueue") newval = this.game.hold.getHold(); + if (setting.id == "rowfillmode") newval = this.game.boardeditor.fillRow; + setting.value = newval; + if (setting.classList[1] == "keybind") setting.textContent = newval; + if (setting.classList[1] == "check") setting.checked = newval; + if (setting.classList[1] == "range") { + this.actions.sliderChange(setting); + this.actions.rangeClickInit(setting); + } + }); + + document.getElementById(id).showModal(); + + if (id == "gameStatsDialog") this.generate.displayStats(); + if (id == "gamemodeDialog") this.generate.highlightGamemodeInMenu(); + if (id == "competitiveDialog") this.generate.renderPBs(); + if (id != "settingsPanel" && this.settingPanel.open) this.closeDialog(this.settingPanel); + this.open = true; + this.game.sounds.toggleSongMuffle(this.open); + } + + getOptions(id) { + const settings = [...this.options].filter(item => item.parentElement.parentElement.parentElement.id == id) + return settings; + } + + getSettingType(id) { + let type = id.replace("Dialog", ""); + if (id == "gamemodeDialog" || id == "goalsDialog" || id == "competitiveDialog") type = "game"; + return type; + } + + closeModal(id) { + this.getOptions(id).forEach(setting => { + let settingType = this.getSettingType(id); + let val = setting.value; + if (setting.classList[1] == "number" && val == "") val = this.selectedRangeElement.min; + if (setting.classList[1] == "check") val = setting.checked; + if (setting.classList[1] == "keybind") { + val = setting.textContent.length > 1 + ? setting.textContent + : setting.textContent.toLowerCase(); + } + if (setting.classList[2] == "exp") val = toExpValue(val); + if (setting.classList[2] == "statoption") this.game.settings.game.sidebar[setting.id[10]-1] = val; + if (setting.id == "nextQueue") this.game.bag.setQueue(val, this.pieceNames); + if (setting.id == "holdQueue") this.game.hold.setNewHold(val); + if (setting.id == "rowfillmode") this.game.boardeditor.fillRow = val; + if (setting.id == "override") this.game.boardeditor.override = val; + + if (id == "changeRangeValue") { + this.selectedRangeElement.value = document.getElementById("rangeValue").value; + this.actions.sliderChange(this.selectedRangeElement); + } + if (setting.id == "audioLevel") this.game.sounds.setAudioLevel(); + + if (!this.game.settings.hasOwnProperty(settingType)) return; + this.game.settings[settingType][setting.id] = val; + }); + + this.closeDialog(document.getElementById(id)); + if (id != 'changeRangeValue' && id != "frontdrop" && this.game.started && !this.game.ended) + this.game.movement.firstMovement(); + this.actions.saveSettings(); + if (id == "displayDialog") this.game.renderer.renderStyles(); + + const restartMenus = ["gameDialog", "gamemodeDialog", "gameEnd", "goalsDialog", "competitiveDialog"]; + if (restartMenus.includes(id)) this.game.controls.retry(false); + if (id == "changeRangeValue") this.open = true; + } + + closeDialog(element) { + this.closing = true //track if the closing animation is still ongoing + const closingAnimation = () => { + element.removeEventListener("animationend", closingAnimation); + element.classList.remove("closingAnimation"); + element.close(); + this.closing = false + }; + this.open = false; + this.game.sounds.toggleSongMuffle(this.open); + element.classList.add("closingAnimation"); + element.addEventListener("animationend", closingAnimation); + } +} + +function toLogValue(y) { + return Math.round(Math.log2(y + 1) * 10); +} + +export function toExpValue(x) { + return Math.round(Math.pow(2, 0.1 * x) - 1); } \ No newline at end of file diff --git a/src/movement/controls.js b/src/movement/controls.js index 0d4346e..fc44288 100644 --- a/src/movement/controls.js +++ b/src/movement/controls.js @@ -25,7 +25,7 @@ export class Controls { if (key == this.menuKey) this.game.menuactions.toggleDialog(); else if (key == keys.editMenuKey) this.game.menuactions.openEditMenu(); - if (this.game.modals.open || this.game.modals.closing) return; + if (this.game.modals.open || this.game.modals.closing || this.game.mechanics.locking.timings.clearDelay != 0) return; if (event.key != this.menuKey && !this.game.started) this.moves.firstMovement(); if (key == keys.resetKey) this.retry(true); if (this.game.ended) return; diff --git a/src/movement/movement.js b/src/movement/movement.js index a43fe55..2b29bcc 100644 --- a/src/movement/movement.js +++ b/src/movement/movement.js @@ -1,161 +1,162 @@ -import { spinChecks } from "../data/data.js"; -import { Game } from "../game.js"; - -export class Movement { - /** - * @param {Game} game - */ - constructor(game) { - this.game = game; - } - - firstMovement() { - this.game.zenith.startZenithMode(); - if(this.game.zenith.tickPass == 0 && this.game.settings.game.gamemode == "zenith") this.game.renderer.renderTimeLeft("FLOOR 1") - this.game.started = true; - this.game.mechanics.startGravity(); - this.game.modes.startSurvival(); - this.game.mechanics.locking.lockingResume(); - this.game.gameTimer = setInterval(() => - this.game.gameClock(), (1000 / this.game.tickrate) - ); - } - - checkCollision(coords, action, collider) { - collider = collider ?? this.game.board.getMinos("S"); - for (let [x, y] of coords) { - if ( - (action == "RIGHT" && x > 8) || - (action == "LEFT" && x < 1) || - (action == "DOWN" && y < 1) || - (action == "ROTATE" && x < 0) || - x > 9 || - y < 0 || - (action == "PLACE" && y > 19) - ) - return true; - for (let [x2, y2] of collider) { - const col = (dx, dy) => x + dx == x2 && y + dy == y2; - if ( - (action == "RIGHT" && col(1, 0)) || - (action == "LEFT" && col(-1, 0)) || - (action == "DOWN" && col(0, -1)) || - ((action == "ROTATE" || action == "SPAWN") && col(0, 0)) - ) - return true; - } - } - } - - checkTspin(rotation, [x, y], [dx, dy]) { - if (this.game.falling.piece.name != "t") return false; - this.game.mechanics.isMini = false; - const minos = spinChecks[(rotation + 1) % 4] - .concat(spinChecks[rotation - 1]) - .map(([ddx, ddy]) => this.checkCollision([[ddx + x, ddy + y]], "ROTATE")); - if (minos[2] && minos[3] && (minos[0] || minos[1])) return true; - if ((minos[2] || minos[3]) && minos[0] && minos[1]) { - if ((dx == 1 || dx == -1) && dy == -2) return true; - this.game.mechanics.isMini = true; - return true; - } - } - - checkAllspin(pieceCoords) { - if (this.game.falling.piece.name == "t") return false; - const directions = [[1, 0], [0, 1], [-1, 0], [0, -1]]; - const validSpin = directions.every(([dx, dy]) => - this.checkCollision(pieceCoords.map(([x, y]) => [x + dx, y + dy]), "ROTATE")); - if (validSpin && this.game.settings.game.allspinminis) this.game.mechanics.isMini = true; - return validSpin; - } - - rotate(type) { - if (this.game.falling.piece.name == "o") return; - const newRotation = this.game.falling.getRotateState(type); - const kickdata = this.game.falling.getKickData(type, newRotation); - const rotatingCoords = this.game.falling.getNewCoords(newRotation); - const change = kickdata.find(([dx, dy]) => - !this.checkCollision(rotatingCoords.map(c => [c[0] + dx, c[1] + dy]), "ROTATE")); - if (!change) return; - this.game.board.MinoToNone("A"); - this.game.board.addMinos(this.game.falling.newName(), rotatingCoords, change); - this.game.falling.updateLocation(change); - this.game.mechanics.isTspin = this.checkTspin(newRotation, this.game.falling.location, change); - this.game.mechanics.isAllspin = this.checkAllspin(this.game.board.getMinos("A")); - this.game.falling.rotation = newRotation; - this.game.mechanics.locking.incrementLock(); - this.game.stats.rotates++; - this.game.sounds.playSound("rotate"); - this.game.mechanics.setShadow(); - if (this.game.settings.game.gravitySpeed == 0) this.game.mechanics.startGravity(); - this.game.controls.startArr("current"); - this.game.controls.checkSD(); - if (this.game.mechanics.isTspin || (this.game.mechanics.isAllspin && this.game.settings.game.allspin)) { - this.game.renderer.rotateBoard(type); - this.game.particles.spawnParticles(this.game.falling.location[0], this.game.falling.location[1] + 2, - "spin", 5, type == "CW", this.game.falling.piece.colour); - this.game.sounds.playSound("spin"); - } - } - - movePieceSide(direction, max = 1) { - this.game.controls.checkSD(); - const minos = this.game.board.getMinos("A"); - let amount = 0; - const check = dx => !this.checkCollision(minos.map(([x, y]) => [x + dx, y]), direction); - while (check(amount) && Math.abs(amount) < max) direction == "RIGHT" ? amount++ : amount--; - if (!check(amount)) this.game.renderer.bounceBoard(direction); - if (amount == 0) { - this.game.controls.stopInterval("arr"); - return; - } - this.game.board.moveMinos(minos, "RIGHT", amount); - this.game.falling.updateLocation([amount, 0]); - this.game.mechanics.isTspin = false; - this.game.mechanics.isAllspin = false; - this.game.mechanics.isMini = false; - this.game.mechanics.locking.incrementLock(); - this.game.sounds.playSound("move"); - this.game.mechanics.setShadow(); - this.game.controls.checkSD(); - if (this.game.settings.game.gravitySpeed == 0) this.game.mechanics.startGravity(); - } - - movePieceDown(sonic, scoring = false) { - const minos = this.game.board.getMinos("A"); - if (this.checkCollision(minos, "DOWN")) return; - this.game.board.moveMinos(minos, "DOWN", 1); - - this.game.mechanics.isTspin = false; - this.game.mechanics.isAllspin = false; - this.game.mechanics.isMini = false; - this.game.falling.updateLocation([0, -1]); - if (this.checkCollision(this.game.board.getMinos("A"), "DOWN")) { - this.game.mechanics.locking.scheduleLock(); - this.game.renderer.bounceBoard("DOWN"); - this.game.controls.startArr("current"); - } - if (scoring && sonic) this.game.stats.score += 1; - if (sonic) this.movePieceDown(true, scoring); - } - - harddrop() { - const minos = this.game.board.getMinos("A"); - let amount = 0; - while (!this.checkCollision(minos.map(([x, y]) => [x, y - amount]), "DOWN")) amount++; - if (amount > 0) { - this.game.mechanics.isTspin = false; - this.game.mechanics.isAllspin = false; - this.game.mechanics.isMini = false; - } - this.game.board.moveMinos(minos, "DOWN", amount); - this.game.falling.updateLocation([0, -amount]); - this.game.stats.score += 2 * amount; - this.game.sounds.playSound("harddrop"); - this.game.renderer.bounceBoard('DOWN'); - const xvals = [...new Set(minos.map(([x, y]) => x))]; - this.game.particles.spawnParticles(Math.min(...xvals)+1, this.game.falling.location[1], "drop", xvals.length); - this.game.mechanics.locking.lockPiece(); - } -} +import { spinChecks } from "../data/data.js"; +import { Game } from "../game.js"; + +export class Movement { + /** + * @param {Game} game + */ + constructor(game) { + this.game = game; + } + + firstMovement() { + this.game.zenith.startZenithMode(); + this.game.grandmaster.startGrandmasterTimer(); + if(this.game.zenith.tickPass == 0 && this.game.settings.game.gamemode == "zenith") this.game.renderer.renderTimeLeft("FLOOR 1") + this.game.started = true; + this.game.mechanics.startGravity(); + this.game.modes.startSurvival(); + this.game.mechanics.locking.lockingResume(); + this.game.gameTimer = setInterval(() => + this.game.gameClock(), (1000 / this.game.tickrate) + ); + } + + checkCollision(coords, action, collider) { + collider = collider ?? this.game.board.getMinos("S"); + for (let [x, y] of coords) { + if ( + (action == "RIGHT" && x > 8) || + (action == "LEFT" && x < 1) || + (action == "DOWN" && y < 1) || + (action == "ROTATE" && x < 0) || + x > 9 || + y < 0 || + (action == "PLACE" && y > 19) + ) + return true; + for (let [x2, y2] of collider) { + const col = (dx, dy) => x + dx == x2 && y + dy == y2; + if ( + (action == "RIGHT" && col(1, 0)) || + (action == "LEFT" && col(-1, 0)) || + (action == "DOWN" && col(0, -1)) || + ((action == "ROTATE" || action == "SPAWN") && col(0, 0)) + ) + return true; + } + } + } + + checkTspin(rotation, [x, y], [dx, dy]) { + if (this.game.falling.piece.name != "t") return false; + this.game.mechanics.isMini = false; + const minos = spinChecks[(rotation + 1) % 4] + .concat(spinChecks[rotation - 1]) + .map(([ddx, ddy]) => this.checkCollision([[ddx + x, ddy + y]], "ROTATE")); + if (minos[2] && minos[3] && (minos[0] || minos[1])) return true; + if ((minos[2] || minos[3]) && minos[0] && minos[1]) { + if ((dx == 1 || dx == -1) && dy == -2) return true; + this.game.mechanics.isMini = true; + return true; + } + } + + checkAllspin(pieceCoords) { + if (this.game.falling.piece.name == "t") return false; + const directions = [[1, 0], [0, 1], [-1, 0], [0, -1]]; + const validSpin = directions.every(([dx, dy]) => + this.checkCollision(pieceCoords.map(([x, y]) => [x + dx, y + dy]), "ROTATE")); + if (validSpin && this.game.settings.game.allspinminis) this.game.mechanics.isMini = true; + return validSpin; + } + + rotate(type) { + if (this.game.falling.piece.name == "o") return; + const newRotation = this.game.falling.getRotateState(type); + const kickdata = this.game.falling.getKickData(type, newRotation); + const rotatingCoords = this.game.falling.getNewCoords(newRotation); + const change = kickdata.find(([dx, dy]) => + !this.checkCollision(rotatingCoords.map(c => [c[0] + dx, c[1] + dy]), "ROTATE")); + if (!change) return; + this.game.board.MinoToNone("A"); + this.game.board.addMinos(this.game.falling.newName(), rotatingCoords, change); + this.game.falling.updateLocation(change); + this.game.mechanics.isTspin = this.checkTspin(newRotation, this.game.falling.location, change); + this.game.mechanics.isAllspin = this.checkAllspin(this.game.board.getMinos("A")); + this.game.falling.rotation = newRotation; + this.game.mechanics.locking.incrementLock(); + this.game.stats.rotates++; + this.game.sounds.playSound("rotate"); + this.game.mechanics.setShadow(); + if (this.game.settings.game.gravitySpeed == 0) this.game.mechanics.startGravity(); + this.game.controls.startArr("current"); + this.game.controls.checkSD(); + if (this.game.mechanics.isTspin || (this.game.mechanics.isAllspin && this.game.settings.game.allspin)) { + this.game.renderer.rotateBoard(type); + this.game.particles.spawnParticles(this.game.falling.location[0], this.game.falling.location[1] + 2, + "spin", 5, type == "CW", this.game.falling.piece.colour); + this.game.sounds.playSound("spin"); + } + } + + movePieceSide(direction, max = 1) { + this.game.controls.checkSD(); + const minos = this.game.board.getMinos("A"); + let amount = 0; + const check = dx => !this.checkCollision(minos.map(([x, y]) => [x + dx, y]), direction); + while (check(amount) && Math.abs(amount) < max) direction == "RIGHT" ? amount++ : amount--; + if (!check(amount)) this.game.renderer.bounceBoard(direction); + if (amount == 0) { + this.game.controls.stopInterval("arr"); + return; + } + this.game.board.moveMinos(minos, "RIGHT", amount); + this.game.falling.updateLocation([amount, 0]); + this.game.mechanics.isTspin = false; + this.game.mechanics.isAllspin = false; + this.game.mechanics.isMini = false; + this.game.mechanics.locking.incrementLock(); + this.game.sounds.playSound("move"); + this.game.mechanics.setShadow(); + this.game.controls.checkSD(); + if (this.game.settings.game.gravitySpeed == 0) this.game.mechanics.startGravity(); + } + + movePieceDown(sonic, scoring = false) { + const minos = this.game.board.getMinos("A"); + if (this.checkCollision(minos, "DOWN")) return; + this.game.board.moveMinos(minos, "DOWN", 1); + + this.game.mechanics.isTspin = false; + this.game.mechanics.isAllspin = false; + this.game.mechanics.isMini = false; + this.game.falling.updateLocation([0, -1]); + if (this.checkCollision(this.game.board.getMinos("A"), "DOWN")) { + this.game.mechanics.locking.scheduleLock(); + this.game.renderer.bounceBoard("DOWN"); + this.game.controls.startArr("current"); + } + if (scoring && sonic) this.game.stats.score += 1; + if (sonic) this.movePieceDown(true, scoring); + } + + harddrop() { + const minos = this.game.board.getMinos("A"); + let amount = 0; + while (!this.checkCollision(minos.map(([x, y]) => [x, y - amount]), "DOWN")) amount++; + if (amount > 0) { + this.game.mechanics.isTspin = false; + this.game.mechanics.isAllspin = false; + this.game.mechanics.isMini = false; + } + this.game.board.moveMinos(minos, "DOWN", amount); + this.game.falling.updateLocation([0, -amount]); + this.game.stats.score += 2 * amount; + this.game.sounds.playSound("harddrop"); + this.game.renderer.bounceBoard('DOWN'); + const xvals = [...new Set(minos.map(([x, y]) => x))]; + this.game.particles.spawnParticles(Math.min(...xvals)+1, this.game.falling.location[1], "drop", xvals.length); + this.game.mechanics.locking.lockPiece(); + } +} diff --git a/styles/boards.css b/styles/boards.css index e6f70ca..5ac38c4 100644 --- a/styles/boards.css +++ b/styles/boards.css @@ -1,437 +1,437 @@ -#board { - position: fixed; - top: 50%; - left: 50%; - transform-origin: 0 0; - transform: scale(1.1) translate(-50%, -50%); - height: 60vh; - aspect-ratio: 0.5; -} - -#backboard, -#backborder { - position: absolute; - height: 100%; - width: 100%; - background-color: var(--background); - outline: 0.2vh solid var(--cl-blue); - box-shadow: 0 0 5vh var(--l-gray); -} - -@property --angle { - syntax: ""; - initial-value: 0deg; - inherits: false; -} - -#backboard { - --blur-size: 7vmin; - --spin-speed: 6s; - --blur-radius: 20vmin; - --blur-strength: 0; - --colours: #ff4545, #00ff99, #006aff, #ff0095, #ff4545; -} - -#backborder { - --blur-size: 0.3vmin; - --spin-speed: 2s; - --blur-radius: 0.3vmin; - --blur-strength: 0; - --colours: #ff4545, #00ff99, #006aff, #ff0095, #ff4545; -} - -#backboard::before, -#backborder::before { - content: " "; - position: absolute; - width: 100%; - height: 100%; - background-image: conic-gradient(from var(--angle), var(--colours)); - padding: var(--blur-size); - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - border-radius: 1vmin; - animation: var(--spin-speed) spin linear infinite; - filter: blur(var(--blur-radius)); - opacity: var(--blur-strength); - transition: all 1s ease; -} - -#backboard { - outline: none; -} - -@keyframes spin { - from { - --angle: 0deg; - } - - to { - --angle: 360deg; - } -} - -#backboard::after, -#backborder::after { - content: " "; - position: absolute; - width: 100%; - height: 100%; - background-color: var(--background); -} - -#backborder.boardDanger { - outline: 0.2vh solid red !important; - box-shadow: 0 0 3vh red !important; - transition: all 0.5s ease; -} - -#dangerOverlay { - position: absolute; - background-color: red; - transition: all 0.2s ease; - width: 100%; - height: 100%; - opacity: 0; -} - -#playingfield { - position: fixed; - width: 100%; - height: 200%; - top: -100%; -} - -#clickareas { - position: absolute; - width: 100%; - height: 100%; - display: grid; - grid-template-columns: repeat(10, 1fr); - grid-template-rows: repeat(20, 1fr); -} - -.clickmino { - user-select: none; - z-index: 10; -} - -.highlighting { - background-color: #7d7d7d4e; -} - -#grid { - position: fixed; - width: 100%; - height: 100%; -} - -#next { - position: fixed; - left: 101%; - display: grid; - grid-template-columns: repeat(4, 1fr); - grid-template-rows: repeat(15, 1fr); - height: 79%; - width: 40%; - border-radius: 0 1.5vh 1.5vh 0; - outline: 0.2vh solid var(--cl-blue); - background-color: black; - transition: all 0.3s ease; -} - -#hold { - position: fixed; - left: -40%; - display: grid; - grid-template-columns: repeat(4, 1fr); - grid-template-rows: repeat(3, 1fr); - height: 15%; - width: 39.5%; - border-radius: 1.5vh 0 0 1.5vh; - background-color: black; -} - -#lockTimer, -#lockCounter { - position: absolute; - bottom: -0.8vh; - width: 100%; - height: 0.8vh; - border-radius: 0.5vh; -} - -#lockCounter { - bottom: -1.4vh; - height: 0.6vh; -} - -#lockTimer::-webkit-progress-bar, -#lockCounter::-webkit-progress-bar, -#garbageQueue::-webkit-progress-bar { - background-color: var(--invis); -} - -#lockTimer::-moz-progress-bar, -#lockCounter::-moz-progress-bar, -#garbageQueue::-moz-progress-bar { - background-color: var(--invis); -} - -#lockTimer::-webkit-progress-value, -#lockCounter::-webkit-progress-value { - background-color: var(--cl-blue); - border-radius: 0.7vh; -} - -#lockTimer::-moz-progress-value, -#lockCounter::-moz-progress-value { - background-color: var(--cl-blue); - border-radius: 0.7vh; -} - -#climbSpeedBar { - position: absolute; - width: 32vh; - height: 1vh; - border-radius: 0.5vh; - transform-origin: 0 0; - left: -2%; - bottom: -5%; -} - -#climbSpeedBar::-webkit-progress-value { - background-color: red; - border-radius: 0.7vh; -} - -#climbSpeedBar::-webkit-progress-bar { - background-color: var(--invis); -} - -#timeLeftText { - font-family: "Montserrat", sans-serif; - font-weight: bold; - color: gold; - font-size: 1em; - width: 100%; - text-align: center; - bottom: 75%; - opacity: 0; - transition: all 0.3s ease; -} - -#timeLeftText.warn { - animation: timeLeft 3s; -} - -@keyframes timeLeft { - 0% { - opacity: 1; - letter-spacing: 0px; - color: gold; - } - - 5%{ - color: red; - } - - 10%{ - color: gold; - - } - - 15%{ - color: red; - } - - 20%{ - color: gold; - - } - - 25%{ - color: red; - } - - 30%{ - color: gold; - - } - - 35%{ - color: red; - } - - 40%{ - color: gold; - - } - - 45%{ - color: red; - } - - 50%{ - color: gold; - - } - - 55%{ - color: red; - } - - 60%{ - color: gold; - - } - - 65%{ - color: red; - } - - 70% { - opacity: 1; - color:gold - } - - 75%{ - color: red; - } - - 80%{ - color: gold; - - } - - 85%{ - color: red; - } - - 90%{ - color: gold; - - } - - 95%{ - color: red; - } - - - 100% { - opacity: 0; - letter-spacing: 5px; - color: gold; - } -} - - -#garbageQueue { - position: absolute; - width: 122vh; - height: 0.5vh; - border-radius: 0.5vh; - transform-origin: 0 0; - transform: rotate(-90deg); - left: -2%; - bottom: -1%; -} - -#garbageQueue::-webkit-progress-value { - background-color: red; - border-radius: 0.7vh; -} - -#garbageQueue::-moz-progress-value { - background-color: red; - border-radius: 0.7vh; -} - -.text { - position: fixed; - font-size: 1.6em; - margin: 0; - user-select: none; -} - -#warningText { - font-family: "Montserrat", sans-serif; - font-weight: bold; - color: red; - font-size: 1em; - width: 100%; - text-align: center; - bottom: 100%; - opacity: 0; - transition: all 0.3s ease; -} - -#warningText.warn { - opacity: 0.5; - animation: warn 0.3s infinite; -} - -@keyframes warn { - 0% { - opacity: 0.5; - } - - 50% { - opacity: 0; - } -} - -.nextText { - left: 110%; - bottom: 100%; -} - -.holdText { - right: 110%; - bottom: 100%; -} - -.objectiveText { - left: 110%; - bottom: 10%; -} - -#redochoices { - position: absolute; - top: 100%; - left: 110%; - width: 40vw; - opacity: 0; - pointer-events: none; - transition: all 0.3s ease; -} - -.redochoice { - height: 3.5vh; - aspect-ratio: 1/1; - border: 0.3vh solid var(--l-gray); - border-radius: 10vmin; - transition: all 0.3s ease-out; - background-color: var(--invis); - outline: none; - color: var(--l-gray); - margin: 0.3vmin; -} - -.redochoice:hover { - border-color: var(--cl-blue); - color: var(--cl-blue); - transition: all 0.05s ease; -} - -.redochoice.selected { - border-color: var(--p-green); - color: var(--p-green); -} - -.redochoice.selected:hover { - border-color: var(--green); - color: var(--green); - transition: all 0.05s ease; +#board { + position: fixed; + top: 50%; + left: 50%; + transform-origin: 0 0; + transform: scale(1.1) translate(-50%, -50%); + height: 60vh; + aspect-ratio: 0.5; +} + +#backboard, +#backborder { + position: absolute; + height: 100%; + width: 100%; + background-color: var(--background); + outline: 0.2vh solid var(--cl-blue); + box-shadow: 0 0 5vh var(--l-gray); +} + +@property --angle { + syntax: ""; + initial-value: 0deg; + inherits: false; +} + +#backboard { + --blur-size: 7vmin; + --spin-speed: 6s; + --blur-radius: 20vmin; + --blur-strength: 0; + --colours: #ff4545, #00ff99, #006aff, #ff0095, #ff4545; +} + +#backborder { + --blur-size: 0.3vmin; + --spin-speed: 2s; + --blur-radius: 0.3vmin; + --blur-strength: 0; + --colours: #ff4545, #00ff99, #006aff, #ff0095, #ff4545; +} + +#backboard::before, +#backborder::before { + content: " "; + position: absolute; + width: 100%; + height: 100%; + background-image: conic-gradient(from var(--angle), var(--colours)); + padding: var(--blur-size); + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border-radius: 1vmin; + animation: var(--spin-speed) spin linear infinite; + filter: blur(var(--blur-radius)); + opacity: var(--blur-strength); + transition: all 1s ease; +} + +#backboard { + outline: none; +} + +@keyframes spin { + from { + --angle: 0deg; + } + + to { + --angle: 360deg; + } +} + +#backboard::after, +#backborder::after { + content: " "; + position: absolute; + width: 100%; + height: 100%; + background-color: var(--background); +} + +#backborder.boardDanger { + outline: 0.2vh solid red !important; + box-shadow: 0 0 3vh red !important; + transition: all 0.5s ease; +} + +#dangerOverlay { + position: absolute; + background-color: red; + transition: all 0.2s ease; + width: 100%; + height: 100%; + opacity: 0; +} + +#playingfield { + position: fixed; + width: 100%; + height: 200%; + top: -100%; +} + +#clickareas { + position: absolute; + width: 100%; + height: 100%; + display: grid; + grid-template-columns: repeat(10, 1fr); + grid-template-rows: repeat(20, 1fr); +} + +.clickmino { + user-select: none; + z-index: 10; +} + +.highlighting { + background-color: #7d7d7d4e; +} + +#grid { + position: fixed; + width: 100%; + height: 100%; +} + +#next { + position: fixed; + left: 101%; + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-template-rows: repeat(15, 1fr); + height: 79%; + width: 40%; + border-radius: 0 1.5vh 1.5vh 0; + outline: 0.2vh solid var(--cl-blue); + background-color: black; + transition: all 0.3s ease; +} + +#hold { + position: fixed; + left: -40%; + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-template-rows: repeat(3, 1fr); + height: 15%; + width: 39.5%; + border-radius: 1.5vh 0 0 1.5vh; + background-color: black; +} + +#lockTimer, +#lockCounter { + position: absolute; + bottom: -0.8vh; + width: 100%; + height: 0.8vh; + border-radius: 0.5vh; +} + +#lockCounter { + bottom: -1.4vh; + height: 0.6vh; +} + +#lockTimer::-webkit-progress-bar, +#lockCounter::-webkit-progress-bar, +#garbageQueue::-webkit-progress-bar { + background-color: var(--invis); +} + +#lockTimer::-moz-progress-bar, +#lockCounter::-moz-progress-bar, +#garbageQueue::-moz-progress-bar { + background-color: var(--invis); +} + +#lockTimer::-webkit-progress-value, +#lockCounter::-webkit-progress-value { + background-color: var(--cl-blue); + border-radius: 0.7vh; +} + +#lockTimer::-moz-progress-value, +#lockCounter::-moz-progress-value { + background-color: var(--cl-blue); + border-radius: 0.7vh; +} + +#climbSpeedBar { + position: absolute; + width: 32vh; + height: 1vh; + border-radius: 0.5vh; + transform-origin: 0 0; + left: -2%; + bottom: -5%; +} + +#climbSpeedBar::-webkit-progress-value { + background-color: red; + border-radius: 0.7vh; +} + +#climbSpeedBar::-webkit-progress-bar { + background-color: var(--invis); +} + +#timeLeftText { + font-family: "Montserrat", sans-serif; + font-weight: bold; + color: gold; + font-size: 1em; + width: 100%; + text-align: center; + bottom: 75%; + opacity: 0; + transition: all 0.3s ease; +} + +#timeLeftText.warn { + animation: timeLeft 3s; +} + +@keyframes timeLeft { + 0% { + opacity: 1; + letter-spacing: 0px; + color: gold; + } + + 5%{ + color: red; + } + + 10%{ + color: gold; + + } + + 15%{ + color: red; + } + + 20%{ + color: gold; + + } + + 25%{ + color: red; + } + + 30%{ + color: gold; + + } + + 35%{ + color: red; + } + + 40%{ + color: gold; + + } + + 45%{ + color: red; + } + + 50%{ + color: gold; + + } + + 55%{ + color: red; + } + + 60%{ + color: gold; + + } + + 65%{ + color: red; + } + + 70% { + opacity: 1; + color:gold + } + + 75%{ + color: red; + } + + 80%{ + color: gold; + + } + + 85%{ + color: red; + } + + 90%{ + color: gold; + + } + + 95%{ + color: red; + } + + + 100% { + opacity: 0; + letter-spacing: 5px; + color: gold; + } +} + + +#garbageQueue { + position: absolute; + width: 122vh; + height: 0.5vh; + border-radius: 0.5vh; + transform-origin: 0 0; + transform: rotate(-90deg); + left: -2%; + bottom: -1%; +} + +#garbageQueue::-webkit-progress-value { + background-color: red; + border-radius: 0.7vh; +} + +#garbageQueue::-moz-progress-value { + background-color: red; + border-radius: 0.7vh; +} + +.text { + position: fixed; + font-size: 1.6em; + margin: 0; + user-select: none; +} + +#warningText { + font-family: "Montserrat", sans-serif; + font-weight: bold; + color: red; + font-size: 1em; + width: 100%; + text-align: center; + bottom: 100%; + opacity: 0; + transition: all 0.3s ease; +} + +#warningText.warn { + opacity: 0.5; + animation: warn 0.3s infinite; +} + +@keyframes warn { + 0% { + opacity: 0.5; + } + + 50% { + opacity: 0; + } +} + +.nextText { + left: 110%; + bottom: 100%; +} + +.holdText { + right: 110%; + bottom: 100%; +} + +.objectiveText { + left: 110%; + bottom: 10%; +} + +#redochoices { + position: absolute; + top: 100%; + left: 110%; + width: 40vw; + opacity: 0; + pointer-events: none; + transition: all 0.3s ease; +} + +.redochoice { + height: 3.5vh; + aspect-ratio: 1/1; + border: 0.3vh solid var(--l-gray); + border-radius: 10vmin; + transition: all 0.3s ease-out; + background-color: var(--invis); + outline: none; + color: var(--l-gray); + margin: 0.3vmin; +} + +.redochoice:hover { + border-color: var(--cl-blue); + color: var(--cl-blue); + transition: all 0.05s ease; +} + +.redochoice.selected { + border-color: var(--p-green); + color: var(--p-green); +} + +.redochoice.selected:hover { + border-color: var(--green); + color: var(--green); + transition: all 0.05s ease; } \ No newline at end of file diff --git a/styles/menus.css b/styles/menus.css index 3b3a563..f4cd5bb 100644 --- a/styles/menus.css +++ b/styles/menus.css @@ -1,349 +1,349 @@ -/* main menu */ -#settingsPanel[open] { - display: flex; - align-items: center; - justify-content: center; - flex-wrap: wrap; - flex-direction: row; - border: none; - width: 100vw; - height: 50vh; - background-color: var(--invis); - overflow: visible; -} - -#settingsPanel>br { - width: 100%; - content: ' '; -} - -button>img { - -webkit-user-drag: none; -} - -.settingPanelButton { - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - margin: 1%; - height: 18vh; - aspect-ratio: 1/1; - border: 0.3vh solid var(--l-gray); - border-radius: 1.5vh; - transition: all 0.5s ease-out; - background-color: var(--invis); - padding: 0.5vh; - outline: none; -} - - -.settingPanelButton>p { - color: white; - margin: 0; - padding: 0; - opacity: 0.3; - transform: translateY(1vh); - letter-spacing: 0.1em; -} - -.settingPanelButton>img { - height: 75%; -} - -.settingPanelButton:hover { - border: 0.3vh solid var(--cl-blue); - transition: all 0.1s ease; -} - -.settingPanelButton:disabled, -.smallPanelButton:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.smallPanelButton, -.smallerPanelButton { - height: 6vh; - width: 6vh; - border-radius: 1vmin; - display: flex; - align-items: center; - justify-content: center; - flex-wrap: wrap; - flex-direction: row; -} - -.smallerPanelButton { - height: 4vh; - width: 4vh; - border-radius: 0.7vh; - border-width: 0.2vh; -} - -.smallerPanelButton:hover { - border-width: 0.2vh; -} - -.smallerPanelButton>img { - height: 100% -} - -.spanleft { - flex: 1; - text-align: right; - padding-right: 1vw; - width: 9vw; -} - -.spanright { - flex: 1; - padding-left: 1vw; -} - -/* songs */ -#songSelector { - display: flex; - align-items: center; - justify-content: center; - width: 40vw; - height: 15vh; - overflow: visible; - white-space: nowrap; - opacity: 0.4; - transition: all 0.5s ease; -} - -#songSelector:hover { - opacity: 0.8; - transition: all 0.1s ease; -} - -#songText { - position: absolute; - transform: translateY(-4vh); - align-self: center; - padding: 0; - margin: 0; -} - -#songProgress { - height: 0.8vh; - border-radius: 5vh; -} - -#songProgress::-webkit-progress-bar { - background-color: var(--e-black); - border-radius: 5vh; -} - -#songProgress::-moz-progress-bar { - background-color: var(--e-black); - border-radius: 5vh; -} - - -#songProgress::-webkit-progress-value { - background-color: var(--cl-blue); - border-radius: 5vh; -} - -#songProgress::-moz-progress-value { - background-color: var(--cl-blue); - border-radius: 5vh; -} - -/* option styles */ -.range { - appearance: none; - -webkit-appearance: none; - height: 1vh; - width: 40%; - right: 3%; - border: 0.2vmin solid white; - background: var(--e-black); - outline: none; - -webkit-transition: 0.2s; - transition: opacity 0.2s; -} - -.range:hover { - border-color: var(--p-green); -} - -.check { - width: 3.5vh; - height: 3.5vh; - appearance: none; - -webkit-appearance: none; - background-color: var(--almost-invis); - border: 0.2vmin solid white; - border-radius: 3vh; - transition: all 0.1s ease; -} - -.check:hover { - border-color: var(--p-green); -} - -.check:checked { - background-color: var(--p-green); - border: none; -} - -.textInput, -.dropdown, -.number { - padding: 0.6vh; - background-color: var(--almost-invis); - border: 0.1vmin solid white; - color: rgb(225, 225, 225); - font-size: 1em; -} - -.number { - width: 20%; - appearance: initial; - -moz-appearance: textfield; -} - -.textInput:hover, -.textInput:focus, -.dropdown:hover, -.dropdown:focus, -.number:hover, -.number:focus { - border-radius: 0; - outline: none; - border-color: var(--p-green); -} - -.keybind { - padding: 0.5vh; - background-color: var(--invis); - border: none; - font-size: 1em; - color: var(--c-blue); -} - -.settingRow:hover>.keybind { - color: var(--p-green); -} - -/* change keybinds */ -#frontdrop { - width: 100%; - height: 100%; - position: fixed; - background-color: var(--slight-invis); - overflow: hidden; - color: var(--cl-blue); - outline: none; - user-select: none; - border: none; - animation: fadein 0.4s forwards ease; - font-family: "Montserrat", sans-serif; -} - -#frontdrop.closingAnimation { - animation: fadeout 0.2s forwards ease; -} - -#selectkeydiv { - display: flex; - width: 100%; - height: 100%; - justify-content: center; - align-items: center; - font-size: 5em; - text-shadow: 0 0 3vh white; -} - -#selectkeybigtext { - transform: translateY(-8vh); -} - -#selectkeytext { - position: absolute; - top: 46vh; - font-size: 0.3em; - text-shadow: 0 0 3vh white; -} - -/* stats */ - -.actiontext, -.statstext { - position: fixed; - font-size: 3vh; - text-shadow: 0 0 3vh var(--vl-gray); - margin: 0; - text-align: right; - width: 200%; - user-select: none; - transition: all 0.2s ease; - right: 105%; -} - -.actiontext { - opacity: 0; -} - -.smalltext { - position: absolute; - font-size: 0.9em; - color: var(--l-gray); - user-select: none; -} - -#smallStat1, -#smallStat2, -#smallStat3 { - right: 150%; - font-size: 2.1vh; - text-align: right; -} - -#statName1, -#statName2, -#statName3 { - text-align: right; - right: 105%; - transform: translateY(0.5vh); -} - -.statText, -#explanationText, -#updatetext { - display: flex; - margin: 0; - opacity: 0.4; -} - -.statText { - opacity: 1; -} - -/* focus */ -#nofocus { - font-family: "Montserrat", sans-serif; - display: none; - opacity: 1; - text-align: center; - position: fixed; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - border: 3px solid red; - color: red; - font-size: 2.5em; - padding: .2em; - background-color: #000A; - transition: 0.2s; -} - -#nofocus span { - display: block; - color: #fff; - font-size: .6em; +/* main menu */ +#settingsPanel[open] { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + flex-direction: row; + border: none; + width: 100vw; + height: 50vh; + background-color: var(--invis); + overflow: visible; +} + +#settingsPanel>br { + width: 100%; + content: ' '; +} + +button>img { + -webkit-user-drag: none; +} + +.settingPanelButton { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + margin: 1%; + height: 18vh; + aspect-ratio: 1/1; + border: 0.3vh solid var(--l-gray); + border-radius: 1.5vh; + transition: all 0.5s ease-out; + background-color: var(--invis); + padding: 0.5vh; + outline: none; +} + + +.settingPanelButton>p { + color: white; + margin: 0; + padding: 0; + opacity: 0.3; + transform: translateY(1vh); + letter-spacing: 0.1em; +} + +.settingPanelButton>img { + height: 75%; +} + +.settingPanelButton:hover { + border: 0.3vh solid var(--cl-blue); + transition: all 0.1s ease; +} + +.settingPanelButton:disabled, +.smallPanelButton:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.smallPanelButton, +.smallerPanelButton { + height: 6vh; + width: 6vh; + border-radius: 1vmin; + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + flex-direction: row; +} + +.smallerPanelButton { + height: 4vh; + width: 4vh; + border-radius: 0.7vh; + border-width: 0.2vh; +} + +.smallerPanelButton:hover { + border-width: 0.2vh; +} + +.smallerPanelButton>img { + height: 100% +} + +.spanleft { + flex: 1; + text-align: right; + padding-right: 1vw; + width: 9vw; +} + +.spanright { + flex: 1; + padding-left: 1vw; +} + +/* songs */ +#songSelector { + display: flex; + align-items: center; + justify-content: center; + width: 40vw; + height: 15vh; + overflow: visible; + white-space: nowrap; + opacity: 0.4; + transition: all 0.5s ease; +} + +#songSelector:hover { + opacity: 0.8; + transition: all 0.1s ease; +} + +#songText { + position: absolute; + transform: translateY(-4vh); + align-self: center; + padding: 0; + margin: 0; +} + +#songProgress { + height: 0.8vh; + border-radius: 5vh; +} + +#songProgress::-webkit-progress-bar { + background-color: var(--e-black); + border-radius: 5vh; +} + +#songProgress::-moz-progress-bar { + background-color: var(--e-black); + border-radius: 5vh; +} + + +#songProgress::-webkit-progress-value { + background-color: var(--cl-blue); + border-radius: 5vh; +} + +#songProgress::-moz-progress-value { + background-color: var(--cl-blue); + border-radius: 5vh; +} + +/* option styles */ +.range { + appearance: none; + -webkit-appearance: none; + height: 1vh; + width: 40%; + right: 3%; + border: 0.2vmin solid white; + background: var(--e-black); + outline: none; + -webkit-transition: 0.2s; + transition: opacity 0.2s; +} + +.range:hover { + border-color: var(--p-green); +} + +.check { + width: 3.5vh; + height: 3.5vh; + appearance: none; + -webkit-appearance: none; + background-color: var(--almost-invis); + border: 0.2vmin solid white; + border-radius: 3vh; + transition: all 0.1s ease; +} + +.check:hover { + border-color: var(--p-green); +} + +.check:checked { + background-color: var(--p-green); + border: none; +} + +.textInput, +.dropdown, +.number { + padding: 0.6vh; + background-color: var(--almost-invis); + border: 0.1vmin solid white; + color: rgb(225, 225, 225); + font-size: 1em; +} + +.number { + width: 20%; + appearance: initial; + -moz-appearance: textfield; +} + +.textInput:hover, +.textInput:focus, +.dropdown:hover, +.dropdown:focus, +.number:hover, +.number:focus { + border-radius: 0; + outline: none; + border-color: var(--p-green); +} + +.keybind { + padding: 0.5vh; + background-color: var(--invis); + border: none; + font-size: 1em; + color: var(--c-blue); +} + +.settingRow:hover>.keybind { + color: var(--p-green); +} + +/* change keybinds */ +#frontdrop { + width: 100%; + height: 100%; + position: fixed; + background-color: var(--slight-invis); + overflow: hidden; + color: var(--cl-blue); + outline: none; + user-select: none; + border: none; + animation: fadein 0.4s forwards ease; + font-family: "Montserrat", sans-serif; +} + +#frontdrop.closingAnimation { + animation: fadeout 0.2s forwards ease; +} + +#selectkeydiv { + display: flex; + width: 100%; + height: 100%; + justify-content: center; + align-items: center; + font-size: 5em; + text-shadow: 0 0 3vh white; +} + +#selectkeybigtext { + transform: translateY(-8vh); +} + +#selectkeytext { + position: absolute; + top: 46vh; + font-size: 0.3em; + text-shadow: 0 0 3vh white; +} + +/* stats */ + +.actiontext, +.statstext { + position: fixed; + font-size: 3vh; + text-shadow: 0 0 3vh var(--vl-gray); + margin: 0; + text-align: right; + width: 200%; + user-select: none; + transition: all 0.2s ease; + right: 105%; +} + +.actiontext { + opacity: 0; +} + +.smalltext { + position: absolute; + font-size: 0.9em; + color: var(--l-gray); + user-select: none; +} + +#smallStat1, +#smallStat2, +#smallStat3 { + right: 150%; + font-size: 2.1vh; + text-align: right; +} + +#statName1, +#statName2, +#statName3 { + text-align: right; + right: 105%; + transform: translateY(0.5vh); +} + +.statText, +#explanationText, +#updatetext { + display: flex; + margin: 0; + opacity: 0.4; +} + +.statText { + opacity: 1; +} + +/* focus */ +#nofocus { + font-family: "Montserrat", sans-serif; + display: none; + opacity: 1; + text-align: center; + position: fixed; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + border: 3px solid red; + color: red; + font-size: 2.5em; + padding: .2em; + background-color: #000A; + transition: 0.2s; +} + +#nofocus span { + display: block; + color: #fff; + font-size: .6em; } \ No newline at end of file diff --git a/styles/miscmenus.css b/styles/miscmenus.css index ec152f2..647882b 100644 --- a/styles/miscmenus.css +++ b/styles/miscmenus.css @@ -1,146 +1,146 @@ -.dialog { - font-family: "Montserrat", sans-serif; - text-align: center; - height: 35vh; - aspect-ratio: 1.3/1; - background-color: var(--almost-invis); - color: white; - user-select: none; - animation: zoomin 0.3s forwards ease; - border-radius: 2vh; - border: 0.3vh solid var(--vl-gray); - padding: 0; - outline: none; -} - -.dialog.closingAnimation { - animation: zoomout 0.3s forwards ease; -} - -.dialog::backdrop { - background-color: #000000b5; - backdrop-filter: blur(5px); - animation: fadein 0.4s forwards ease; -} - -.dialog.closingAnimation::backdrop { - animation: fadeout 0.4s forwards ease; -} - -/* pbs */ -.pbbox { - display: grid; - margin: 1vmin; - grid-template-columns: 2fr 2fr 1fr; - gap: 2vw; - padding: 1vmin; - border: 0.1vh solid white; -} - -.pbbox:hover { - border: 0.1vh solid var(--p-green); -} - -.pbbox>h2 { - margin: 0; - pointer-events: none; -} - -.pbbox>button { - height: 4vh; - aspect-ratio: 1/1; - border: 0.3vh solid var(--l-gray); - color: var(--cl-blue); - border-radius: 1vmin; - transition: all 0.1s ease-out; - background-color: var(--invis); - padding: 0.5vh; - outline: none; -} - -.pbbox>button:hover { - border: 0.3vh solid var(--cl-blue); -} - - -/* edit menu */ -.pieceselection { - height: 5vmin; - aspect-ratio: 1/1; - margin: 0.3vw; - border: none; - outline: none; - transition: all 0.3s ease; - margin-bottom: 2vh; -} - -.pieceselection:hover { - transform: scale(1.1); -} - -.pieceselection:active { - transform: scale(0.9); -} - -#editbuttons { - display: flex; - gap: 1vw; - width: 100%; - align-items: center; - justify-content: center; - flex-wrap: wrap; - margin-top: 3vh; -} - -#editbuttons>br { - width: 100%; - content: ' '; -} - -/* notifications */ -#notifications { - position: absolute; - right: 0; - bottom: 0; -} - -.notif { - font-family: "Montserrat", sans-serif; - animation: error 0.6s forwards; - background-color: black; - color: white; - border-left-color: var(--color); - border-left-width: 0.3vw; - border-left-style: solid; - padding: 1vmin; - border-radius: 1vh; - margin: 2vmin; - width: 30vw; - cursor: pointer; - transition: opacity 1s ease; - z-index: 10; -} - -@keyframes error { - from { - opacity: 0; - transform: translate(10vw); - } - - to { - opacity: 1; - transform: translate(0); - } -} - -.notif_title { - margin: 0.5vmin; - font-size: 1em; - font-weight: bold; -} - -.notif_text { - margin: 0.3vmin; - font-size: 0.8em; - opacity: 0.8; +.dialog { + font-family: "Montserrat", sans-serif; + text-align: center; + height: 35vh; + aspect-ratio: 1.3/1; + background-color: var(--almost-invis); + color: white; + user-select: none; + animation: zoomin 0.3s forwards ease; + border-radius: 2vh; + border: 0.3vh solid var(--vl-gray); + padding: 0; + outline: none; +} + +.dialog.closingAnimation { + animation: zoomout 0.3s forwards ease; +} + +.dialog::backdrop { + background-color: #000000b5; + backdrop-filter: blur(5px); + animation: fadein 0.4s forwards ease; +} + +.dialog.closingAnimation::backdrop { + animation: fadeout 0.4s forwards ease; +} + +/* pbs */ +.pbbox { + display: grid; + margin: 1vmin; + grid-template-columns: 2fr 2fr 1fr; + gap: 2vw; + padding: 1vmin; + border: 0.1vh solid white; +} + +.pbbox:hover { + border: 0.1vh solid var(--p-green); +} + +.pbbox>h2 { + margin: 0; + pointer-events: none; +} + +.pbbox>button { + height: 4vh; + aspect-ratio: 1/1; + border: 0.3vh solid var(--l-gray); + color: var(--cl-blue); + border-radius: 1vmin; + transition: all 0.1s ease-out; + background-color: var(--invis); + padding: 0.5vh; + outline: none; +} + +.pbbox>button:hover { + border: 0.3vh solid var(--cl-blue); +} + + +/* edit menu */ +.pieceselection { + height: 5vmin; + aspect-ratio: 1/1; + margin: 0.3vw; + border: none; + outline: none; + transition: all 0.3s ease; + margin-bottom: 2vh; +} + +.pieceselection:hover { + transform: scale(1.1); +} + +.pieceselection:active { + transform: scale(0.9); +} + +#editbuttons { + display: flex; + gap: 1vw; + width: 100%; + align-items: center; + justify-content: center; + flex-wrap: wrap; + margin-top: 3vh; +} + +#editbuttons>br { + width: 100%; + content: ' '; +} + +/* notifications */ +#notifications { + position: absolute; + right: 0; + bottom: 0; +} + +.notif { + font-family: "Montserrat", sans-serif; + animation: error 0.6s forwards; + background-color: black; + color: white; + border-left-color: var(--color); + border-left-width: 0.3vw; + border-left-style: solid; + padding: 1vmin; + border-radius: 1vh; + margin: 2vmin; + width: 30vw; + cursor: pointer; + transition: opacity 1s ease; + z-index: 10; +} + +@keyframes error { + from { + opacity: 0; + transform: translate(10vw); + } + + to { + opacity: 1; + transform: translate(0); + } +} + +.notif_title { + margin: 0.5vmin; + font-size: 1em; + font-weight: bold; +} + +.notif_text { + margin: 0.3vmin; + font-size: 0.8em; + opacity: 0.8; } \ No newline at end of file diff --git a/styles/settings.css b/styles/settings.css index 3937a09..57aa1b0 100644 --- a/styles/settings.css +++ b/styles/settings.css @@ -1,226 +1,226 @@ -/* dialog */ -.scrollSettings { - font-family: "Montserrat", sans-serif; - height: 90vh; - aspect-ratio: 2/3; - background-color: var(--almost-invis); - padding: 0.5vmin; - color: white; - user-select: none; - outline: none; - text-shadow: 0 0 5vh var(--vl-gray); - border: 0.5vmin solid transparent; - animation: zoomin 0.3s forwards ease; - background-clip: padding-box; - overflow: hidden; -} - -.scrollSettings.closingAnimation { - animation: zoomout 0.3s forwards ease; -} - -.scrollSettings::backdrop { - background-color: #000000b5; - backdrop-filter: blur(5px); - animation: fadein 0.4s forwards ease; -} - -.scrollSettings.closingAnimation::backdrop { - animation: fadeout 0.4s forwards ease; -} - -/* border */ -.scrollSettings::before, -.scrollSettings::after { - content: ''; - position: absolute; - top: 0; - bottom: 0; - width: 0.3vmin; - background: linear-gradient(to bottom, transparent, white 35%, white 65%, transparent); - box-sizing: border-box; -} - -.scrollSettings::before { - left: 0; -} - -.scrollSettings::after { - right: 0; -} - -/* top */ -.settingsTop { - font-size: 1.5em; - text-align: center; - height: 10%; - width: 100%; -} - -/* settings list */ -.settingsBox { - height: 70%; - width: 100%; - overflow-x: hidden; - overflow-y: scroll; - font-size: 1.1em; - padding: 0px; - margin: 0px; - display: flex; - flex-direction: column; - box-sizing: border-box; - padding-top: 8vh; - padding-bottom: 10vh; -} - -.settingRow, -.closeDialogButton, -.settingsReset, -.gamemodeSelect { - display: flex; - align-items: center; - height: 4.2vh; - --s: 2vmin; - --t: 0.3vmin; - --g: 1vmin; - - padding: calc(var(--g) + var(--t)); - outline: var(--t) solid #ffffff; - outline-offset: calc(var(--g)/-1); - mask: conic-gradient(at var(--s) var(--s), #0000 75%, #000 0) 0 0/calc(100% - var(--s)) calc(100% - var(--s)), - linear-gradient(#000 0 0) content-box; - transition: scale 0.6s, opacity 0.6s, outline-offset 0.3s; -} - -.settingRow:hover, -.closeDialogButton:hover, -.settingsReset:hover, -.gamemodeSelect:hover { - outline-offset: calc(-1*var(--t)); - animation: jump 1.5s infinite ease 0.4s; - transition: outline-offset 0.1s, scale 0.6s; -} - -.settingRow>p { - width: 50%; - display: flex; - justify-content: flex-end; - text-align: right; - padding-right: 1.5vw; - pointer-events: none; -} - -.settingRow>.option { - position: unset; - display: flex; - justify-content: flex-start; - transform: none; -} - -/* footer */ -.settingsFooter { - height: 20%; - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 1vh; -} - -.settingsFooter>p { - margin: 0; - font-size: 0.8em; - opacity: 0.4; - width: 100%; - text-align: center; - -} - -.closeDialogButton, -.settingsReset { - height: auto; - background-color: var(--invis); - border: none; - padding: 2vh; - font-size: 1.4em; - color: white; - transition: all 0.4s ease; -} - -/* buttons and misc */ -.settingsReset { - position: absolute; - right: 1vw; - bottom: 1vh; - padding: 1vh; - padding-top: 1.5vh; - display: flex; - justify-content: center; - align-items: center; - height: 6vh; - aspect-ratio: 1/1; -} - -#gamemodeDialog>.settingsBox { - align-items: center; - padding-top: 0; - padding-bottom: 0; -} - -.gamemodeSelect { - height: 8vh; - width: 80%; - background-color: var(--invis); - border: none; - padding: 1.3vh; - font-size: 1.3em; - color: white; - transition: all 0.4s ease; - justify-content: center; -} - -.gamemodeSelect:hover { - outline-color: var(--p-green); - color: var(--p-green); -} - -.gamemodeSelect.selected { - outline-color: var(--green); - color: var(--green); -} - -#editMenu>.settingsBox { - padding-top: 0; -} - -#gameStatsDialog>.settingsFooter { - flex-direction: row; - height: 15%; -} - -#gameEnd>.settingsFooter { - position: absolute; - bottom: 10%; - width: 100%; - flex-direction: row; - height: auto; -} - -#changeRangeValue>.settingsBox { - padding-top: 0; - padding-bottom: 0; - height: auto; - align-items: center; -} - -.settingRow:has(.dropdown) { - animation: none; -} - -.dropdown { - width: 20%; - margin: 1%; - background: black; - color: white; -} +/* dialog */ +.scrollSettings { + font-family: "Montserrat", sans-serif; + height: 90vh; + aspect-ratio: 2/3; + background-color: var(--almost-invis); + padding: 0.5vmin; + color: white; + user-select: none; + outline: none; + text-shadow: 0 0 5vh var(--vl-gray); + border: 0.5vmin solid transparent; + animation: zoomin 0.3s forwards ease; + background-clip: padding-box; + overflow: hidden; +} + +.scrollSettings.closingAnimation { + animation: zoomout 0.3s forwards ease; +} + +.scrollSettings::backdrop { + background-color: #000000b5; + backdrop-filter: blur(5px); + animation: fadein 0.4s forwards ease; +} + +.scrollSettings.closingAnimation::backdrop { + animation: fadeout 0.4s forwards ease; +} + +/* border */ +.scrollSettings::before, +.scrollSettings::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + width: 0.3vmin; + background: linear-gradient(to bottom, transparent, white 35%, white 65%, transparent); + box-sizing: border-box; +} + +.scrollSettings::before { + left: 0; +} + +.scrollSettings::after { + right: 0; +} + +/* top */ +.settingsTop { + font-size: 1.5em; + text-align: center; + height: 10%; + width: 100%; +} + +/* settings list */ +.settingsBox { + height: 70%; + width: 100%; + overflow-x: hidden; + overflow-y: scroll; + font-size: 1.1em; + padding: 0px; + margin: 0px; + display: flex; + flex-direction: column; + box-sizing: border-box; + padding-top: 8vh; + padding-bottom: 10vh; +} + +.settingRow, +.closeDialogButton, +.settingsReset, +.gamemodeSelect { + display: flex; + align-items: center; + height: 4.2vh; + --s: 2vmin; + --t: 0.3vmin; + --g: 1vmin; + + padding: calc(var(--g) + var(--t)); + outline: var(--t) solid #ffffff; + outline-offset: calc(var(--g)/-1); + mask: conic-gradient(at var(--s) var(--s), #0000 75%, #000 0) 0 0/calc(100% - var(--s)) calc(100% - var(--s)), + linear-gradient(#000 0 0) content-box; + transition: scale 0.6s, opacity 0.6s, outline-offset 0.3s; +} + +.settingRow:hover, +.closeDialogButton:hover, +.settingsReset:hover, +.gamemodeSelect:hover { + outline-offset: calc(-1*var(--t)); + animation: jump 1.5s infinite ease 0.4s; + transition: outline-offset 0.1s, scale 0.6s; +} + +.settingRow>p { + width: 50%; + display: flex; + justify-content: flex-end; + text-align: right; + padding-right: 1.5vw; + pointer-events: none; +} + +.settingRow>.option { + position: unset; + display: flex; + justify-content: flex-start; + transform: none; +} + +/* footer */ +.settingsFooter { + height: 20%; + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1vh; +} + +.settingsFooter>p { + margin: 0; + font-size: 0.8em; + opacity: 0.4; + width: 100%; + text-align: center; + +} + +.closeDialogButton, +.settingsReset { + height: auto; + background-color: var(--invis); + border: none; + padding: 2vh; + font-size: 1.4em; + color: white; + transition: all 0.4s ease; +} + +/* buttons and misc */ +.settingsReset { + position: absolute; + right: 1vw; + bottom: 1vh; + padding: 1vh; + padding-top: 1.5vh; + display: flex; + justify-content: center; + align-items: center; + height: 6vh; + aspect-ratio: 1/1; +} + +#gamemodeDialog>.settingsBox { + align-items: center; + padding-top: 0; + padding-bottom: 0; +} + +.gamemodeSelect { + height: 8vh; + width: 80%; + background-color: var(--invis); + border: none; + padding: 1.3vh; + font-size: 1.3em; + color: white; + transition: all 0.4s ease; + justify-content: center; +} + +.gamemodeSelect:hover { + outline-color: var(--p-green); + color: var(--p-green); +} + +.gamemodeSelect.selected { + outline-color: var(--green); + color: var(--green); +} + +#editMenu>.settingsBox { + padding-top: 0; +} + +#gameStatsDialog>.settingsFooter { + flex-direction: row; + height: 15%; +} + +#gameEnd>.settingsFooter { + position: absolute; + bottom: 10%; + width: 100%; + flex-direction: row; + height: auto; +} + +#changeRangeValue>.settingsBox { + padding-top: 0; + padding-bottom: 0; + height: auto; + align-items: center; +} + +.settingRow:has(.dropdown) { + animation: none; +} + +.dropdown { + width: 20%; + margin: 1%; + background: black; + color: white; +} diff --git a/styles/style.css b/styles/style.css index b3b09af..ff7a80c 100644 --- a/styles/style.css +++ b/styles/style.css @@ -1,241 +1,241 @@ -@import url("https://fonts.googleapis.com/css2?family=Montserrat&display=swap"); -@import url("https://fonts.googleapis.com/css2?family=Major+Mono+Display&display=swap"); - -:root { - --night: #080b0c; - --e-black: #1b1e22; - --c-blue: #accbe1; - --cl-blue: #dbeaf3; - --l-gray: #ffffff50; - --vl-gray: #ffffffc4; - --invis: #00000000; - --almost-invis: #00000024; - --slight-invis: #000000cf; - --p-green: #53b565; - --green: #30f24d; - --gray: #808080; -} - -body { - color: var(--cl-blue); - font-family: "Major Mono Display", monospace; - margin: 0; - height: 100vh; - width: 100vw; - overflow: hidden; - background: var(--night); - --background: black; -} - -#splashScreen { - position: fixed; - width: 100vw; - height: 100vh; - top: 0; - left: 0; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - background-color: var(--slight-invis); - backdrop-filter: blur(10px); - z-index: 1; - text-shadow: 0 0 8vh rgb(255, 255, 255); - transition: all 0.5s ease, display 1.5s allow-discrete; - scale: 1; -} - -#splashScreen>h1 { - font-size: 7em; - animation: pulse 2s infinite ease; - opacity: 1; -} - -#ignoreText { - opacity: 0; - transition: all 1s ease; -} - -#openSettingsButton, -#editButton { - top: 5%; - position: fixed; - left: 127%; - display: flex; - justify-content: right; - padding-right: 3%; - align-items: center; - height: 8%; - width: 24%; - border: 0.2vh solid white; - border-radius: 0 1.5vh 1.5vh 0; - transition: all 0.5s ease-out; - background-color: var(--invis); - outline: none; - opacity: 0.6; -} - -#editButton { - top: 15%; -} - -#openSettingsButton:hover, -#editButton:hover { - left: 133%; - opacity: 1; - transition: all 0.2s ease; -} - -#openSettingsButton>img { - height: 70%; - user-select: none; -} - -#editButton>img { - height: 100%; - user-select: none; -} - -.dialog img, -.scrollSettings img { - opacity: 0.6; - transition: all 0.3s ease; -} - -.dialog>*:hover img, -.scrollSettings img:hover { - opacity: 1; -} - -/* scrollbars */ -::-webkit-scrollbar { - width: 0.4vw; -} - -::-webkit-scrollbar-track { - border-radius: 1vh; - background: var(--invis); - margin-top: 1vh; - margin-bottom: 1vh; -} - -::-webkit-scrollbar-thumb { - border-radius: 1vh; - background: var(--vl-gray); -} - -::-webkit-scrollbar-thumb:hover { - background: white; - transition: all 0.2s ease; -} - -@-moz-document url-prefix() { - * { - scrollbar-width: thin; - scrollbar-color: var(--vl-gray) var(--invis); - } -} - -/* sliders */ -.range::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 1.5vh; - height: 2.5vh; - background: white; - cursor: pointer; -} - -.range::-moz-range-thumb { - -moz-appearance: none; - appearance: none; - width: 1.5vh; - height: 2.5vh; - background: white; - cursor: pointer; -} - -.range::-webkit-slider-thumb:hover { - background: var(--p-green); -} - -/* remove number arrows */ -.number::-webkit-outer-spin-button, -.number::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} - -/* animations */ -@keyframes jump { - 0% { - transform: translateY(0vmin); - } - - 50% { - transform: translateY(0.2vmin); - } - - 100% { - transform: translateY(0vmin); - } -} - -@keyframes fadein { - from { - opacity: 0; - } - - to { - opacity: 1; - } -} - - -@keyframes zoomin { - from { - opacity: 0; - transform: scale(0.9); - } - - to { - opacity: 1; - transform: scale(1); - } -} - -@keyframes fadeout { - from { - opacity: 1; - } - - to { - opacity: 0; - } -} - -@keyframes zoomout { - from { - opacity: 1; - transform: scale(1); - } - - to { - opacity: 0; - transform: scale(0.9); - } -} - -@keyframes pulse { - 0% { - opacity: 1; - } - - 50% { - opacity: 0.75; - } - - 100% { - opacity: 1; - } +@import url("https://fonts.googleapis.com/css2?family=Montserrat&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Major+Mono+Display&display=swap"); + +:root { + --night: #080b0c; + --e-black: #1b1e22; + --c-blue: #accbe1; + --cl-blue: #dbeaf3; + --l-gray: #ffffff50; + --vl-gray: #ffffffc4; + --invis: #00000000; + --almost-invis: #00000024; + --slight-invis: #000000cf; + --p-green: #53b565; + --green: #30f24d; + --gray: #808080; +} + +body { + color: var(--cl-blue); + font-family: "Major Mono Display", monospace; + margin: 0; + height: 100vh; + width: 100vw; + overflow: hidden; + background: var(--night); + --background: black; +} + +#splashScreen { + position: fixed; + width: 100vw; + height: 100vh; + top: 0; + left: 0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: var(--slight-invis); + backdrop-filter: blur(10px); + z-index: 1; + text-shadow: 0 0 8vh rgb(255, 255, 255); + transition: all 0.5s ease, display 1.5s allow-discrete; + scale: 1; +} + +#splashScreen>h1 { + font-size: 7em; + animation: pulse 2s infinite ease; + opacity: 1; +} + +#ignoreText { + opacity: 0; + transition: all 1s ease; +} + +#openSettingsButton, +#editButton { + top: 5%; + position: fixed; + left: 127%; + display: flex; + justify-content: right; + padding-right: 3%; + align-items: center; + height: 8%; + width: 24%; + border: 0.2vh solid white; + border-radius: 0 1.5vh 1.5vh 0; + transition: all 0.5s ease-out; + background-color: var(--invis); + outline: none; + opacity: 0.6; +} + +#editButton { + top: 15%; +} + +#openSettingsButton:hover, +#editButton:hover { + left: 133%; + opacity: 1; + transition: all 0.2s ease; +} + +#openSettingsButton>img { + height: 70%; + user-select: none; +} + +#editButton>img { + height: 100%; + user-select: none; +} + +.dialog img, +.scrollSettings img { + opacity: 0.6; + transition: all 0.3s ease; +} + +.dialog>*:hover img, +.scrollSettings img:hover { + opacity: 1; +} + +/* scrollbars */ +::-webkit-scrollbar { + width: 0.4vw; +} + +::-webkit-scrollbar-track { + border-radius: 1vh; + background: var(--invis); + margin-top: 1vh; + margin-bottom: 1vh; +} + +::-webkit-scrollbar-thumb { + border-radius: 1vh; + background: var(--vl-gray); +} + +::-webkit-scrollbar-thumb:hover { + background: white; + transition: all 0.2s ease; +} + +@-moz-document url-prefix() { + * { + scrollbar-width: thin; + scrollbar-color: var(--vl-gray) var(--invis); + } +} + +/* sliders */ +.range::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 1.5vh; + height: 2.5vh; + background: white; + cursor: pointer; +} + +.range::-moz-range-thumb { + -moz-appearance: none; + appearance: none; + width: 1.5vh; + height: 2.5vh; + background: white; + cursor: pointer; +} + +.range::-webkit-slider-thumb:hover { + background: var(--p-green); +} + +/* remove number arrows */ +.number::-webkit-outer-spin-button, +.number::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +/* animations */ +@keyframes jump { + 0% { + transform: translateY(0vmin); + } + + 50% { + transform: translateY(0.2vmin); + } + + 100% { + transform: translateY(0vmin); + } +} + +@keyframes fadein { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + + +@keyframes zoomin { + from { + opacity: 0; + transform: scale(0.9); + } + + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes fadeout { + from { + opacity: 1; + } + + to { + opacity: 0; + } +} + +@keyframes zoomout { + from { + opacity: 1; + transform: scale(1); + } + + to { + opacity: 0; + transform: scale(0.9); + } +} + +@keyframes pulse { + 0% { + opacity: 1; + } + + 50% { + opacity: 0.75; + } + + 100% { + opacity: 1; + } } \ No newline at end of file diff --git a/tauri/package.json b/tauri/package.json index 59c0c41..c1d80b7 100644 --- a/tauri/package.json +++ b/tauri/package.json @@ -1,14 +1,14 @@ -{ - "name": "teti", - "private": true, - "version": "1.0.0", - "type": "module", - "scripts": { - "tauri": "tauri", - "build": "build.bat", - "dev": "dev.bat" - }, - "devDependencies": { - "@tauri-apps/cli": "^1" - } -} +{ + "name": "teti", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "tauri": "tauri", + "build": "build.bat", + "dev": "dev.bat" + }, + "devDependencies": { + "@tauri-apps/cli": "^1" + } +} diff --git a/tauri/src-tauri/Cargo.toml b/tauri/src-tauri/Cargo.toml index dc17b90..a8cb52b 100644 --- a/tauri/src-tauri/Cargo.toml +++ b/tauri/src-tauri/Cargo.toml @@ -1,20 +1,20 @@ -[package] -name = "Teti" -version = "1.0.0" -description = "Teti - A tetris client by Titan" -authors = ["TitanPlayz"] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[build-dependencies] -tauri-build = { version = "1", features = [] } - -[dependencies] -tauri = { version = "1", features = ["shell-open"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" - -[features] -# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! -custom-protocol = ["tauri/custom-protocol"] +[package] +name = "Teti" +version = "1.0.0" +description = "Teti - A tetris client by Titan" +authors = ["TitanPlayz"] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[build-dependencies] +tauri-build = { version = "1", features = [] } + +[dependencies] +tauri = { version = "1", features = ["shell-open"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +[features] +# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! +custom-protocol = ["tauri/custom-protocol"] diff --git a/tauri/src-tauri/build.rs b/tauri/src-tauri/build.rs index d860e1e..2ba80a8 100644 --- a/tauri/src-tauri/build.rs +++ b/tauri/src-tauri/build.rs @@ -1,3 +1,3 @@ -fn main() { - tauri_build::build() -} +fn main() { + tauri_build::build() +} diff --git a/tauri/src-tauri/src/main.rs b/tauri/src-tauri/src/main.rs index e6ad770..db1310c 100644 --- a/tauri/src-tauri/src/main.rs +++ b/tauri/src-tauri/src/main.rs @@ -1,8 +1,8 @@ -// Prevents additional console window on Windows in release, DO NOT REMOVE!! -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - -fn main() { - tauri::Builder::default() - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + tauri::Builder::default() + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/tauri/src-tauri/tauri.conf.json b/tauri/src-tauri/tauri.conf.json index 56eccfa..fb90467 100644 --- a/tauri/src-tauri/tauri.conf.json +++ b/tauri/src-tauri/tauri.conf.json @@ -1,44 +1,44 @@ -{ - "build": { - "devPath": "../src", - "distDir": "../src", - "withGlobalTauri": true - }, - "package": { - "productName": "Teti", - "version": "1.0.0" - }, - "tauri": { - "allowlist": { - "all": false, - "shell": { - "all": false, - "open": true - } - }, - "windows": [ - { - "title": "TETI", - "width": 800, - "height": 600, - "resizable": true, - "maximized": true - } - ], - "security": { - "csp": null - }, - "bundle": { - "active": true, - "targets": "all", - "identifier": "com.titanplayz", - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ] - } - } +{ + "build": { + "devPath": "../src", + "distDir": "../src", + "withGlobalTauri": true + }, + "package": { + "productName": "Teti", + "version": "1.0.0" + }, + "tauri": { + "allowlist": { + "all": false, + "shell": { + "all": false, + "open": true + } + }, + "windows": [ + { + "title": "TETI", + "width": 800, + "height": 600, + "resizable": true, + "maximized": true + } + ], + "security": { + "csp": null + }, + "bundle": { + "active": true, + "targets": "all", + "identifier": "com.titanplayz", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + } + } } \ No newline at end of file