Skip to content

Commit

Permalink
Add falling block animation to player instant drop action
Browse files Browse the repository at this point in the history
  • Loading branch information
bytewife committed Sep 6, 2022
1 parent 195d9b6 commit 73cd4cc
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 30 deletions.
91 changes: 65 additions & 26 deletions src/GameLoop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
rotateCells,
spawnPos,
} from "./util/playerUtil";
import { createBoard, getGroundHeight, getFallDurationMilliseconds } from "./util/boardUtil";
import { createBoard, getGroundHeight } from "./util/boardUtil";
import { BoardCell } from "./util/BoardCell";
import { WordList } from "./components/WordList";
import { useInterval } from "./util/useInterval";
Expand All @@ -46,9 +46,10 @@ import {
lockMax,
matchAnimLength,
MIN_WORD_LENGTH,
boardCellFallDurationMillisecondsRate,
playerCellFallDurationMillisecondsRate
} from "./setup";


// Terminology: https://tetris.fandom.com/wiki/Glossary
// Declaration of game states.
const stateMachine = createMachine({
Expand All @@ -58,7 +59,10 @@ const stateMachine = createMachine({
countdown: { on: { DONE: "spawningBlock" } },
spawningBlock: { on: { SPAWN: "placingBlock" } },
placingBlock: {
on: { TOUCHING_BLOCK: "lockDelay", BLOCKED: "gameOver" },
on: { TOUCHING_BLOCK: "lockDelay", BLOCKED: "gameOver", DO_INSTANT_DROP_ANIM: "playerInstantDropAnim"},
},
playerInstantDropAnim: {
on: { TOUCHING_BLOCK: "lockDelay"},
},
lockDelay: { on: { LOCK: "fallingLetters", UNLOCK: "placingBlock" } },
fallingLetters: { on: { DO_ANIM: "fallingLettersAnim" } },
Expand Down Expand Up @@ -95,6 +99,8 @@ const timestamps = {
countdownMillisecondsElapsed: 0,
fallingLettersAnimStartMilliseconds: 0,
fallingLettersAnimDurationMilliseconds: 0,
playerInstantDropAnimStart: 0,
playerInstantDropAnimDurationMilliseconds: 0,
};

export function GameLoop() {
Expand Down Expand Up @@ -146,7 +152,8 @@ export function GameLoop() {

const [didInstantDrop, setDidInstantDrop] = useState(false);

const [fallingLettersBeforeAndAfter, setFallingLettersBeforeAndAfter] = useState([] as [BoardCell, BoardCell][]);
const [fallingLettersBeforeAndAfter, setFallingLetters] = useState([]);
const [playerFallingLettersBeforeAndAfter, setPlayerFallingLettersBeforeAndAfter] = useState([]);

useEffect(() => {
globalThis.addEventListener("keydown", updatePlayerPos);
Expand Down Expand Up @@ -307,24 +314,6 @@ export function GameLoop() {
} else if ("Space" === code) {
// Instant drop.
if (ENABLE_INSTANT_DROP) {
let ground_row = BOARD_ROWS;
playerAdjustedCells.forEach((cell) =>
ground_row = Math.min(
ground_row,
getGroundHeight(cell.c, cell.r, boardCellMatrix),
)
);
const mid = Math.floor(layout.length / 2);
// Offset with the lowest cell, centered around layout's midpoint.
let dy = 0;
playerCells.forEach((cell) => dy = Math.max(dy, cell.r - mid));
setPlayerPos((prev) => {
const pos = [ground_row - dy, prev[1]] as [number, number];
setPlayerAdjustedCells(
convertCellsToAdjusted(playerCells, pos),
);
return pos;
});
setDidInstantDrop(true);
} else if (
_ENABLE_UP_KEY &&
Expand Down Expand Up @@ -471,6 +460,50 @@ export function GameLoop() {
timestamps.lockStart = performance.now();
stateHandler.send("TOUCHING_BLOCK");
}

if (didInstantDrop) {
setPlayerVisibility(false);
const closestPlayerCellToGround = playerAdjustedCells.reduce((prev, cur) =>
getGroundHeight(prev.c, prev.r, boardCellMatrix) - prev.r < getGroundHeight(cur.c, cur.r, boardCellMatrix) - cur.r ? prev : cur
);
const closestGround = getGroundHeight(closestPlayerCellToGround.c, closestPlayerCellToGround.r, boardCellMatrix);
const minDist = closestGround - closestPlayerCellToGround.r;
timestamps.playerInstantDropAnimStart = performance.now();
timestamps.playerInstantDropAnimDurationMilliseconds = 25 * minDist;
setPlayerFallingLettersBeforeAndAfter(
playerAdjustedCells.map(cell =>
[
{...cell},
{...cell, r: closestGround}
]
)
);
stateHandler.send("DO_INSTANT_DROP_ANIM");
}
} else if ("playerInstantDropAnim" === stateHandler.state.value) {
if (timestamps.playerInstantDropAnimDurationMilliseconds < performance.now() - timestamps.playerInstantDropAnimStart) {
setPlayerVisibility(true);
setPlayerFallingLettersBeforeAndAfter([]);
let ground_row = BOARD_ROWS;
playerAdjustedCells.forEach((cell) =>
ground_row = Math.min(
ground_row,
getGroundHeight(cell.c, cell.r, boardCellMatrix),
)
);
const mid = Math.floor(layout.length / 2);
// Offset with the lowest cell, centered around layout's midpoint.
let dy = 0;
playerCells.forEach((cell) => dy = Math.max(dy, cell.r - mid));
setPlayerPos((prev) => {
const pos = [ground_row - dy, prev[1]] as [number, number];
setPlayerAdjustedCells(
convertCellsToAdjusted(playerCells, pos),
);
return pos;
});
stateHandler.send("TOUCHING_BLOCK");
}
} else if ("lockDelay" === stateHandler.state.value) {
const lockTime = performance.now() - timestamps.lockStart +
groundExitPenalty;
Expand Down Expand Up @@ -505,7 +538,7 @@ export function GameLoop() {
);

// Update falling letters & animation information.
setFallingLettersBeforeAndAfter(_ => {
setFallingLetters(_ => {
const newFallingLettersBeforeAndAfter = removed.map((k, i) => [k, added[i]]);

// Handle animation duration.
Expand All @@ -514,15 +547,15 @@ export function GameLoop() {
const [maxFallBeforeCell, maxFallAfterCell] = newFallingLettersBeforeAndAfter.reduce((prev, cur) =>
prev[1].r - prev[0].r > cur[1].r - cur[0].r ? prev : cur
);
animDuration = getFallDurationMilliseconds(maxFallBeforeCell.r, maxFallAfterCell.r);
animDuration = boardCellFallDurationMillisecondsRate * (maxFallAfterCell.r - maxFallBeforeCell.r);
}
timestamps.fallingLettersAnimDurationMilliseconds = animDuration;
timestamps.fallingLettersAnimStartMilliseconds = performance.now();

return newFallingLettersBeforeAndAfter;
});

setBoardCellMatrix(newBoardWithDrops);
setBoardCellMatrix(newBoardWithDrops);

setPlacedCells((prev) => {
added.forEach((boardCell) => prev.add([boardCell.r, boardCell.c]));
Expand Down Expand Up @@ -677,7 +710,7 @@ export function GameLoop() {
gridTemplateColumns: `repeat(${BOARD_COLS}, 30px)`,
border: "solid red 4px",
position: "relative",
} as const;
};

return (
<div style={appStyle}>
Expand All @@ -692,8 +725,14 @@ export function GameLoop() {
adjustedCells={playerAdjustedCells}
/>

<FallingBlock
fallingLetters={playerFallingLettersBeforeAndAfter}
durationRate={playerCellFallDurationMillisecondsRate}
/>

<FallingBlock
fallingLetters={fallingLettersBeforeAndAfter}
durationRate={boardCellFallDurationMillisecondsRate}
/>

<BoardCells
Expand Down
3 changes: 3 additions & 0 deletions src/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ export const lockMax = 1500;
export const matchAnimLength = 750;
export const groundExitPenaltyRate = 250;
export const countdownTotalSecs = 3;

export const boardCellFallDurationMillisecondsRate = 75;
export const playerCellFallDurationMillisecondsRate = 10;
4 changes: 0 additions & 4 deletions src/util/boardUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,3 @@ export function getGroundHeight(
}
return board.length - 1;
}

export function getFallDurationMilliseconds(startRow: number, endRow: number) {
return 75 * Math.abs(startRow - endRow);
}

0 comments on commit 73cd4cc

Please sign in to comment.