Skip to content

Commit

Permalink
Merge pull request #30 from TitanPlayz100/replays
Browse files Browse the repository at this point in the history
Added Replays
  • Loading branch information
TitanPlayz100 authored Nov 20, 2024
2 parents 7ae9173 + 7cdd7a0 commit fac5d42
Show file tree
Hide file tree
Showing 15 changed files with 237 additions and 41 deletions.
1 change: 1 addition & 0 deletions assets/icons/history.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 25 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ <h1>Teti</h1>
<button id="competitive" class="settingPanelButton smallPanelButton" onclick="modal.openModal('competitiveDialog')" title="Toggle competitive mode and view PBs">
<img src="./assets/icons/medal.svg" style="transform: translateY(-8%); height: 140%;">
</button>
<button id="replays" class="settingPanelButton smallPanelButton" onclick="modal.openModal('replaysDialog')" title="Play replay">
<img src="./assets/icons/history.svg" style="transform: translateY(10%); height: 100%;">
</button>
<br>

<!-- main row -->
Expand Down Expand Up @@ -197,6 +200,7 @@ <h1>Teti</h1>
<div class="settingRow"><p>Open Edit Menu (Custom)</p> <button class="option keybind" id="editMenuKey"></button></div>
<div class="settingRow"><p>Undo</p> <button class="option keybind" id="undoKey"></button></div>
<div class="settingRow"><p>Redo</p> <button class="option keybind" id="redoKey"></button></div>
<div class="settingRow"><p>Pause and Play in Replays</p> <button class="option keybind" id="pauseReplayKey"></button></div>
</div>
<div class="settingsFooter">
<p>Press Esc to close without saving</p>
Expand Down Expand Up @@ -291,6 +295,25 @@ <h3>Personal Bests</h3>
</div>
</dialog>

<dialog id="replaysDialog" class="scrollSettings">
<div class="settingsTop"><h2>REPLAYS<h2></div>

<div class="settingsBox" style="align-items: center;">
<p>Replays are currently in beta</p>
<p>They probably are not able to be shared yet <br><i>(due to settings and fps related issues)</i></p>
<p>They may break from upcoming updates</p>
<div id="uploadReplay" class="closeDialogButton" onclick="document.getElementById('replayUpload').click()">
<input type="file" id="replayUpload" onchange="menu.uploadReplay(this)" hidden accept=".trf">
<p>Open Replay</p>
</div>
</div>

<div class="settingsFooter">
<p>Press Esc to close without saving</p>
<button class="closeDialogButton" onclick="modal.closeModal('replaysDialog')">Save and Close</button>
</div>
</dialog>

<!-- GAME MENUS -->
<dialog id="changeRangeValue" class="dialog" style="height: 25vh;">
<h1>Enter Value</h1>
Expand Down Expand Up @@ -370,8 +393,9 @@ <h1 id="gameEndTitle">GAME ENDED</h1>
<p id="reason" style="font-size: 3.6vh;"></p>
<p id="result"></p>
<div class="settingsFooter">
<button class="closeDialogButton" onclick="menu.saveReplay()">Save Replay</button>
<button id="seeStats" class="closeDialogButton" onclick="modal.openModal('gameStatsDialog')">See Stats</button>
<button class="closeDialogButton" onclick="modal.closeModal('gameEnd')" style="bottom: 10%;" >Play Again</button>
<button class="closeDialogButton" onclick="modal.closeModal('gameEnd')">Play Again</button>
</div>
</dialog>
</body>
Expand Down
3 changes: 2 additions & 1 deletion src/data/defaultSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@
"rotate180Key": "a",
"editMenuKey": "e",
"undoKey": "Ctrl+z",
"redoKey": "Ctrl+y"
"redoKey": "Ctrl+y",
"pauseReplayKey": " "
},
"volume": {
"audioLevel": 15,
Expand Down
3 changes: 2 additions & 1 deletion src/display/pixirender.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class PixiRender {
}

const reset = iconframe(this.resetIcon, 0.23, 0)
reset.on("pointerdown", () => this.game.controls.retry(true));
reset.on("pointerdown", () => { console.log("test"); this.game.controls.retry(true) });
const settings = iconframe(this.settingsIcon, 0.18, width * 3 / 20)
settings.on("pointerdown", () => this.game.modals.openModal("settingsPanel"));
const edit = iconframe(this.editIcon, 0.21, width * 6 / 20)
Expand Down Expand Up @@ -208,6 +208,7 @@ export class PixiRender {

// RENDER CLOCK
tick(time) {
this.game.replay.tick();
this.game.controls.runKeyQueue();
this.game.controls.timer();
this.render("board", this.game.board.boardState);
Expand Down
3 changes: 2 additions & 1 deletion src/features/modes.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export class Modes {
if (competitive) {
if (custom == null) {
localStorage.setItem('customGame', JSON.stringify(this.game.settings.game));
this.game.menuactions.saveSettings();
}
this.modeJSON = this.getGamemodeJSON(mode);
this.game.settings.game = { ...this.game.settings.game, ...this.modeJSON.settings };
Expand All @@ -83,11 +84,11 @@ export class Modes {
this.game.settings.game = custom;
this.game.settings.game.competitiveMode = false;
localStorage.removeItem('customGame');
this.game.menuactions.saveSettings();
}
this.modeJSON = this.getGamemodeJSON(mode);
}
this.toggleDialogState(competitive);
this.game.menuactions.saveSettings();
}

toggleDialogState(enabled) {
Expand Down
103 changes: 103 additions & 0 deletions src/features/replays.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { Game } from "../game.js";

// start recording replay with start()
// save replay with copyReplay()
// run replay with runReplay()
export class Replay {
events = {};
currentFrame = 0;
/**@type { "idle" | "running" | "replaying" } */
state = "idle";

totalLength;

/**
* @param {Game} game
*/
constructor(game) {
this.game = game;
}

start() {
if (this.state == "replaying") return;
this.state = "running";
this.currentFrame = 0;
this.events = {};
}

stop() {
this.state = "idle";
this.game.menuactions.loadSettings();
}

togglePause() {
this.state = (this.state == "replaying") ? "idle" : "replaying";
if (this.state == "idle") {
this.game.stopGameTimers();
} else {
this.game.movement.startTimers();
}
}

tick() {
if (this.state == "running") {
this.currentFrame++;
this.saveKeys();
} else if (this.state == "replaying") {
this.currentFrame++;
const event = this.events[this.currentFrame];
if (event != undefined) this.replayKey(event);
}
}

saveKeys() {
const keyDowns = this.game.controls.keyDownQueue;
const keyUps = this.game.controls.keyUpQueue;

const event = {}
if (keyDowns.length > 0) event.keydown = keyDowns;
if (keyUps.length > 0) event.keyup = keyUps;

if (Object.keys(event).length == 0) return;
this.events[this.currentFrame] = event;
}

saveReplay() {
const date = (new Date()).toISOString();
const fps = Math.round(this.game.pixi.app.ticker.FPS);
const replay = {
events: this.events,
header: {
date,
version: this.game.version,
fps,
seed: this.game.bag.genseed,
},
handling: this.game.settings.handling,
settings: this.game.settings.game,
length: this.currentFrame
};
return JSON.stringify(replay);
}

runReplay(replayString) {
const replay = JSON.parse(replayString);
this.events = replay.events;
this.currentFrame = 0;
this.state = "replaying";
this.game.settings.handling = replay.handling;
this.game.settings.game = replay.settings;
this.totalLength = replay.length;

this.game.startGame(replay.header.seed);
}

replayKey(event) {
const keydown = event.keydown ?? [];
const keyup = event.keyup ?? [];
this.game.controls.keyDownQueue = keydown;
this.game.controls.keyUpQueue = keyup;

if (!this.game.started && keydown.length > 0) this.game.movement.startTimers();
}
}
20 changes: 16 additions & 4 deletions src/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Particles } from "./display/particles.js";
import { Zenith, Grandmaster } from "./mechanics/gamemode_extended.js";
import { PixiRender } from "./display/pixirender.js";
import { Animations } from "./display/animations.js";
import { Replay } from "./features/replays.js";

export class Game {
started;
Expand Down Expand Up @@ -60,6 +61,7 @@ export class Game {
this.grandmaster = new Grandmaster(this);
this.pixi = new PixiRender(this);
this.animations = new Animations(this);
this.replay = new Replay(this);
this.init();
}

Expand All @@ -82,13 +84,14 @@ export class Game {
this.versionChecker();
}

startGame() {
startGame(seed = undefined) {
this.menuactions.loadSettings();
this.modes.loadModes();
this.resetState();
this.resetState(seed);
this.renderer.renderStyles();
this.mechanics.spawnPiece(this.bag.cycleNext(true), true);
this.history.save();
this.replay.start();
}

stopGameTimers() { //stop all the game's timers
Expand All @@ -101,6 +104,7 @@ export class Game {
}

endGame(top, bottom = "Better luck next time") {
if (this.ended) return;
const dead = ["Lockout", "Topout", "Blockout"].includes(top); // survival mode end instead of lose
if (this.settings.game.gamemode == 'survival' && dead) {
this.ended = true;
Expand All @@ -116,15 +120,23 @@ export class Game {
this.sounds.playSound("finish");
}

if (this.replay.state == "replaying") { // replay ended
this.ended = true;
this.modals.openModal("replaysDialog");
this.replay.stop();
return;
}

this.ended = true;
this.replay.stop();
this.modals.openModal("gameEnd");
this.stopGameTimers()
this.elementReason.textContent = top;
this.elementResult.textContent = bottom;
this.profilestats.saveSession();
}

resetState() {
resetState(seed = undefined) {
this.boardeffects.hasPace = true;
this.boardeffects.paceCooldown = 0;
this.pixi.boardAlpha = 1;
Expand All @@ -142,7 +154,7 @@ export class Game {
this.stopGameTimers();
this.animations.resetActionTexts();

this.bag = new Bag(this);
this.bag = new Bag(this, seed);
this.mechanics = new Mechanics(this);
this.falling = new Falling(this);
this.hold = new Hold(this);
Expand Down
5 changes: 3 additions & 2 deletions src/mechanics/randomisers.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ export class Bag {
queue = [];
history = ["s", "z", "s", "z"]
type;
genseed;

/** @param {Game} game */
constructor(game, seed = null) {
this.game = game;
this.type = game.settings.game.randomiser;
this.stride = seed == null ? game.settings.game.stride : false;
const genSeed = seed ?? Math.floor((maxInt - 1) * Math.random() + 1);
this.rng = new RNG(genSeed);
this.genseed = seed ?? Math.floor((maxInt - 1) * Math.random() + 1);
this.rng = new RNG(this.genseed);
this.PopulateBag();
}

Expand Down
33 changes: 30 additions & 3 deletions src/menus/menuactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export class MenuActions {
this.loadSettings();
this.game.modes.loadModes();
this.game.modals.generate.notif("Settings Loaded", "User settings have successfully loaded", "message");
el.value = "";
};
}

Expand All @@ -141,7 +142,7 @@ export class MenuActions {
}
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();
if (this.game.started && !this.game.ended) this.game.movement.startTimers();
}

newGame(key, modal) {
Expand Down Expand Up @@ -209,7 +210,7 @@ export class MenuActions {

let el = document.createElement("a");
el.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(JSON.stringify(stats)));
el.setAttribute("download", "stats.json");
el.setAttribute("download", "stats.tsf");
document.body.appendChild(el);
el.click();
document.body.removeChild(el);
Expand All @@ -228,10 +229,36 @@ export class MenuActions {

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`);
el.setAttribute("download", `teti_stats_${day}.tlsf`);
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");
}

saveReplay() {
const replay = this.game.replay.saveReplay();

let el = document.createElement("a");
el.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(replay));
el.setAttribute("download", `replay.trf`);
document.body.appendChild(el);
el.click();
document.body.removeChild(el);
this.game.modals.generate.notif("Replay Exported", "Your replay was successfully exported", "success");
}

uploadReplay(el) {
const reader = new FileReader();
reader.readAsText(el.files[0]);
reader.onload = () => {
this.game.modals.generate.notif("Replay Loaded", "Replay successfully loaded", "message");
this.game.modals.closeModal("replaysDialog");
setTimeout(() => {
this.game.replay.runReplay(reader.result.toString());
}, 1000);
el.value = "";
};
}

}
2 changes: 1 addition & 1 deletion src/menus/modals.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export class ModalActions {

this.closeDialog(document.getElementById(id));
if (id != 'changeRangeValue' && id != "frontdrop" && this.game.started && !this.game.ended)
this.game.movement.firstMovement();
this.game.movement.startTimers();
this.actions.saveSettings();
if (id == "displayDialog") this.game.renderer.renderStyles(true);

Expand Down
Loading

0 comments on commit fac5d42

Please sign in to comment.