From b69bd28887e3ec17c7a76e72dbfc354c45b363ef Mon Sep 17 00:00:00 2001 From: Michael Tintiuc Date: Tue, 11 Oct 2022 01:38:16 +0300 Subject: [PATCH] Refactor 2 --- src/components/App/index.tsx | 6 +- src/components/CellarDoor/index.tsx | 82 ++-- src/components/Coin/index.tsx | 123 +++--- src/components/Fire/index.tsx | 159 +++----- src/components/Heart/index.tsx | 122 ++---- src/components/House/index.tsx | 69 ++-- src/components/Lever/index.tsx | 95 ++--- src/components/Npc/index.tsx | 104 ++--- src/components/Player/index.tsx | 613 +++++++++++----------------- src/components/World/index.tsx | 80 ++-- 10 files changed, 595 insertions(+), 858 deletions(-) diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx index 579590b..e14f5d6 100644 --- a/src/components/App/index.tsx +++ b/src/components/App/index.tsx @@ -30,19 +30,21 @@ export default function App() { {gameState === GAME_STATES.GameOver && } - + - + diff --git a/src/components/CellarDoor/index.tsx b/src/components/CellarDoor/index.tsx index 3c37be8..77b3161 100644 --- a/src/components/CellarDoor/index.tsx +++ b/src/components/CellarDoor/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, FunctionComponent } from "react"; +import { useEffect, useRef, FC } from "react"; import { TILE_SETS } from "../../constants"; import "./style.css"; @@ -7,63 +7,51 @@ const HEIGHT = 64; const TILE_X = 992; const TILE_Y = 160; +type CellarDoorProps = { top: number; left: number; isOpen?: boolean }; + /* * TODO: - * - useRef instead of getElementById * - util function for tile set, tiles and animation - * - create global constants for tile sets and tile size - * - prefer to return early, flip the if condition * - track state internally */ -const CellarDoor: FunctionComponent<{ isOpen?: boolean }> = ({ - isOpen = false, -}) => { +const CellarDoor: FC = ({ isOpen = false, top, left }) => { + const canvasRef = useRef(null); useEffect(() => { - const canvas = document.getElementById( - "cellar-door-canvas" - ) as HTMLCanvasElement | null; - const ctx = canvas?.getContext("2d"); + const ctx = canvasRef.current?.getContext("2d"); + + if (!canvasRef.current || !ctx) { + return; + } - if (canvas && ctx) { - canvas.style.left = "528px"; - canvas.style.top = "272px"; + canvasRef.current.style.left = `${left}px`; + canvasRef.current.style.top = `${top}px`; - const tileSet = new Image(); - tileSet.src = TILE_SETS.World; - tileSet.onload = () => { - ctx.clearRect(0, 0, WIDTH, HEIGHT); + const tileSet = new Image(); + tileSet.src = TILE_SETS.World; + tileSet.onload = () => { + ctx.clearRect(0, 0, WIDTH, HEIGHT); - if (isOpen) { - ctx.drawImage( - tileSet, - TILE_X + WIDTH, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } else { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - }; - } - }, [isOpen]); + ctx.drawImage( + tileSet, + isOpen ? TILE_X + WIDTH : TILE_X, + TILE_Y, + WIDTH, + HEIGHT, + 0, + 0, + WIDTH, + HEIGHT + ); + }; + }, [isOpen, left, top]); return ( - + ); }; diff --git a/src/components/Coin/index.tsx b/src/components/Coin/index.tsx index 52c0673..b2ee5b8 100644 --- a/src/components/Coin/index.tsx +++ b/src/components/Coin/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, FunctionComponent } from "react"; +import { useEffect, useRef, FC } from "react"; import { TILE_SIZE, TILE_SETS } from "../../constants"; import "./style.css"; @@ -8,94 +8,63 @@ const TILE_X = 0; const TILE_Y = 128; const ANIMATION_LENGTH = 3; +type CoinProps = { left: number; top: number }; + /* * TODO: - * - useRef instead of getElementById * - util function for tile set, tiles and animation - * - create global constants for tile sets and tile size - * - prefer to return early, flip the if condition - * - clear interval on component destroy */ -const Coin: FunctionComponent<{ left: number; top: number }> = ({ - left, - top, -}) => { +const Coin: FC = ({ left, top }) => { + const canvasRef = useRef(null); + useEffect(() => { - const canvas = document.getElementById( - "coin-canvas" - ) as HTMLCanvasElement | null; - const ctx = canvas?.getContext("2d"); + const ctx = canvasRef.current?.getContext("2d"); + let intervalId: number; + + if (!canvasRef.current || !ctx) { + return; + } - if (canvas && ctx) { - canvas.style.left = `${left}px`; - canvas.style.top = `${top}px`; + canvasRef.current.style.left = `${left}px`; + canvasRef.current.style.top = `${top}px`; - const tileSet = new Image(); - tileSet.src = TILE_SETS.Objects; - tileSet.onload = () => { - let currentFrame = 0; + const tileSet = new Image(); + tileSet.src = TILE_SETS.Objects; + tileSet.onload = () => { + let currentFrame = 0; - setInterval(() => { - ctx.clearRect(0, 0, WIDTH, HEIGHT); + intervalId = window.setInterval(() => { + ctx.clearRect(0, 0, WIDTH, HEIGHT); - if (currentFrame === 0) { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } else if (currentFrame === 1) { - ctx.drawImage( - tileSet, - TILE_X + WIDTH, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } else if (currentFrame === 2) { - ctx.drawImage( - tileSet, - TILE_X + WIDTH * 2, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } else if (currentFrame === 3) { - ctx.drawImage( - tileSet, - TILE_X + WIDTH * 3, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } + ctx.drawImage( + tileSet, + TILE_X + WIDTH * currentFrame, + TILE_Y, + WIDTH, + HEIGHT, + 0, + 0, + WIDTH, + HEIGHT + ); - currentFrame = - currentFrame === ANIMATION_LENGTH ? 0 : currentFrame + 1; - }, 100); - }; - } + currentFrame = currentFrame === ANIMATION_LENGTH ? 0 : currentFrame + 1; + }, 100); + }; + + return () => { + clearInterval(intervalId); + }; }, [left, top]); - return ; + return ( + + ); }; export default Coin; diff --git a/src/components/Fire/index.tsx b/src/components/Fire/index.tsx index 8a0bdc7..f9cd81d 100644 --- a/src/components/Fire/index.tsx +++ b/src/components/Fire/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, FunctionComponent } from "react"; +import { useEffect, useRef, FC } from "react"; import { TILE_SIZE, TILE_SETS } from "../../constants"; import "./style.css"; @@ -8,130 +8,63 @@ const TILE_X = 130; const TILE_Y = 98; const ANIMATION_LENGTH = 6; +type FireProps = { left: number; top: number }; + /* * TODO: - * - useRef instead of getElementById * - util function for tile set, tiles and animation - * - create global constants for tile sets and tile size - * - prefer to return early, flip the if condition - * - clear interval on component destroy */ -const Fire: FunctionComponent<{ left: number; top: number }> = ({ - left, - top, -}) => { +const Fire: FC = ({ left, top }) => { + const canvasRef = useRef(null); + useEffect(() => { - const canvas = document.getElementById( - "fire-canvas" - ) as HTMLCanvasElement | null; - const ctx = canvas?.getContext("2d"); + const ctx = canvasRef.current?.getContext("2d"); + let intervalId: number; + + if (!canvasRef.current || !ctx) { + return; + } - if (canvas && ctx) { - canvas.style.left = `${left}px`; - canvas.style.top = `${top}px`; + canvasRef.current.style.left = `${left}px`; + canvasRef.current.style.top = `${top}px`; - const tileSet = new Image(); - tileSet.src = TILE_SETS.Objects; - tileSet.onload = () => { - let currentFrame = 0; + const tileSet = new Image(); + tileSet.src = TILE_SETS.Objects; + tileSet.onload = () => { + let currentFrame = 0; - setInterval(() => { - ctx.clearRect(0, 0, WIDTH, HEIGHT); + intervalId = window.setInterval(() => { + ctx.clearRect(0, 0, WIDTH, HEIGHT); - if (currentFrame === 0) { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } else if (currentFrame === 1) { - ctx.drawImage( - tileSet, - TILE_X + WIDTH, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } else if (currentFrame === 2) { - ctx.drawImage( - tileSet, - TILE_X + WIDTH * 2, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } else if (currentFrame === 3) { - ctx.drawImage( - tileSet, - TILE_X + WIDTH * 3, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } else if (currentFrame === 4) { - ctx.drawImage( - tileSet, - TILE_X + WIDTH * 4, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } else if (currentFrame === 5) { - ctx.drawImage( - tileSet, - TILE_X + WIDTH * 5, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } else if (currentFrame === 6) { - ctx.drawImage( - tileSet, - TILE_X + WIDTH * 6, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } + ctx.drawImage( + tileSet, + TILE_X + WIDTH * currentFrame, + TILE_Y, + WIDTH, + HEIGHT, + 0, + 0, + WIDTH, + HEIGHT + ); - currentFrame = - currentFrame === ANIMATION_LENGTH ? 0 : currentFrame + 1; - }, 125); - }; - } + currentFrame = currentFrame === ANIMATION_LENGTH ? 0 : currentFrame + 1; + }, 125); + }; + + return () => { + clearInterval(intervalId); + }; }, [left, top]); - return ; + return ( + + ); }; export default Fire; diff --git a/src/components/Heart/index.tsx b/src/components/Heart/index.tsx index 12e19ac..2d22edb 100644 --- a/src/components/Heart/index.tsx +++ b/src/components/Heart/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, FunctionComponent } from "react"; +import { useEffect, useRef, FC } from "react"; import { TILE_SIZE, TILE_SETS } from "../../constants"; import "./style.css"; @@ -8,94 +8,62 @@ const TILE_X = 0; const TILE_Y = 96; const ANIMATION_LENGTH = 3; +type HeartProps = { left: number; top: number }; + /* * TODO: - * - useRef instead of getElementById * - util function for tile set, tiles and animation - * - create global constants for tile sets and tile size - * - prefer to return early, flip the if condition - * - clear interval on component destroy */ -const Heart: FunctionComponent<{ left: number; top: number }> = ({ - left, - top, -}) => { +const Heart: FC = ({ left, top }) => { + const canvasRef = useRef(null); + useEffect(() => { - const canvas = document.getElementById( - "heart-canvas" - ) as HTMLCanvasElement | null; - const ctx = canvas?.getContext("2d"); + const ctx = canvasRef.current?.getContext("2d"); + let intervalId: number; - if (canvas && ctx) { - canvas.style.left = `${left}px`; - canvas.style.top = `${top}px`; + if (!canvasRef.current || !ctx) { + return; + } + canvasRef.current.style.left = `${left}px`; + canvasRef.current.style.top = `${top}px`; - const tileSet = new Image(); - tileSet.src = TILE_SETS.Objects; - tileSet.onload = () => { - let currentFrame = 0; + const tileSet = new Image(); + tileSet.src = TILE_SETS.Objects; + tileSet.onload = () => { + let currentFrame = 0; - setInterval(() => { - ctx.clearRect(0, 0, WIDTH, HEIGHT); + intervalId = window.setInterval(() => { + ctx.clearRect(0, 0, WIDTH, HEIGHT); - if (currentFrame === 0) { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } else if (currentFrame === 1) { - ctx.drawImage( - tileSet, - TILE_X + WIDTH, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } else if (currentFrame === 2) { - ctx.drawImage( - tileSet, - TILE_X + WIDTH * 2, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } else if (currentFrame === 3) { - ctx.drawImage( - tileSet, - TILE_X + WIDTH * 3, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } + ctx.drawImage( + tileSet, + TILE_X + WIDTH * currentFrame, + TILE_Y, + WIDTH, + HEIGHT, + 0, + 0, + WIDTH, + HEIGHT + ); - currentFrame = - currentFrame === ANIMATION_LENGTH ? 0 : currentFrame + 1; - }, 75); - }; - } + currentFrame = currentFrame === ANIMATION_LENGTH ? 0 : currentFrame + 1; + }, 75); + }; + + return () => { + clearInterval(intervalId); + }; }, [left, top]); - return ; + return ( + + ); }; export default Heart; diff --git a/src/components/House/index.tsx b/src/components/House/index.tsx index 55af9d5..0cdacc1 100644 --- a/src/components/House/index.tsx +++ b/src/components/House/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, FunctionComponent } from "react"; +import { useEffect, useRef, FC } from "react"; import { TILE_SETS } from "../../constants"; import "./style.css"; @@ -7,43 +7,50 @@ const HEIGHT = 160; const TILE_X = 198; const TILE_Y = 0; +type HouseProps = { left: number; top: number }; + /* * TODO: - * - useRef instead of getElementById * - util function for tile set, tiles and animation - * - create global constants for tile sets and tile size - * - prefer to return early, flip the if condition */ -const House: FunctionComponent = () => { +const House: FC = ({ left, top }) => { + const canvasRef = useRef(null); + useEffect(() => { - const canvas = document.getElementById( - "house-canvas" - ) as HTMLCanvasElement | null; - const ctx = canvas?.getContext("2d"); - - if (canvas && ctx) { - canvas.style.left = "372px"; - canvas.style.top = "192px"; - - const tileSet = new Image(); - tileSet.src = TILE_SETS.World; - tileSet.onload = () => { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - }; + const ctx = canvasRef.current?.getContext("2d"); + + if (!canvasRef.current || !ctx) { + return; } - }, []); - return ; + canvasRef.current.style.left = `${left}px`; + canvasRef.current.style.top = `${top}px`; + + const tileSet = new Image(); + tileSet.src = TILE_SETS.World; + tileSet.onload = () => { + ctx.drawImage( + tileSet, + TILE_X, + TILE_Y, + WIDTH, + HEIGHT, + 0, + 0, + WIDTH, + HEIGHT + ); + }; + }, [left, top]); + + return ( + + ); }; export default House; diff --git a/src/components/Lever/index.tsx b/src/components/Lever/index.tsx index c69c16c..bf40c47 100644 --- a/src/components/Lever/index.tsx +++ b/src/components/Lever/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, FunctionComponent } from "react"; +import { useEffect, useRef, FC } from "react"; import { TILE_SIZE, TILE_SETS } from "../../constants"; import "./style.css"; @@ -7,65 +7,58 @@ const HEIGHT = TILE_SIZE; const TILE_X = 64; const TILE_Y = 288; -/* - * TODO: - * - useRef instead of getElementById - * - util function for tile set, tiles and animation - * - create global constants for tile sets and tile size - * - prefer to return early, flip the if condition - * - clear interval on component destroy - */ -const Lever: FunctionComponent<{ +type LeverProps = { left: number; top: number; used: boolean; onInteract: (value: boolean | ((prev: boolean) => boolean)) => void; -}> = ({ left, top, used, onInteract }) => { - useEffect(() => { - const canvas = document.getElementById( - "lever-canvas" - ) as HTMLCanvasElement | null; - const ctx = canvas?.getContext("2d"); +}; - if (canvas && ctx) { - canvas.style.left = `${left}px`; - canvas.style.top = `${top}px`; +/* + * TODO: + * - util function for tile set, tiles and animation + */ +const Lever: FC = ({ left, top, used, onInteract }) => { + const canvasRef = useRef(null); + + useEffect(() => { + const ctx = canvasRef.current?.getContext("2d"); - const tileSet = new Image(); - tileSet.src = TILE_SETS.Objects; - tileSet.onload = () => { - ctx.clearRect(0, 0, WIDTH, HEIGHT); - onInteract(used); - if (used) { - ctx.drawImage( - tileSet, - TILE_X + WIDTH, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } else { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - }; + if (!canvasRef.current || !ctx) { + return; } + + canvasRef.current.style.left = `${left}px`; + canvasRef.current.style.top = `${top}px`; + + const tileSet = new Image(); + tileSet.src = TILE_SETS.Objects; + tileSet.onload = () => { + ctx.clearRect(0, 0, WIDTH, HEIGHT); + onInteract(used); + + ctx.drawImage( + tileSet, + used ? TILE_X + WIDTH : TILE_X, + TILE_Y, + WIDTH, + HEIGHT, + 0, + 0, + WIDTH, + HEIGHT + ); + }; }, [left, top, used, onInteract]); - return ; + return ( + + ); }; export default Lever; diff --git a/src/components/Npc/index.tsx b/src/components/Npc/index.tsx index ed03469..c6be0a0 100644 --- a/src/components/Npc/index.tsx +++ b/src/components/Npc/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, FunctionComponent } from "react"; +import { useEffect, useRef, FC } from "react"; import { TILE_SETS } from "../../constants"; import "./style.css"; @@ -10,60 +10,72 @@ const HUE_STEP = 10; let increment = HUE_STEP; +type NpcProps = { left: number; top: number }; + /* * TODO: - * - useRef instead of getElementById * - util function for tile set, tiles and animation - * - create global constants for tile sets and tile size - * - prefer to return early, flip the if condition - * - clear interval on component destroy */ -const Npc: FunctionComponent<{ - left: number; - top: number; -}> = ({ left, top }) => { +const Npc: FC = ({ left, top }) => { + const canvasRef = useRef(null); + useEffect(() => { - const canvas = document.getElementById( - "npc-canvas" - ) as HTMLCanvasElement | null; - const ctx = canvas?.getContext("2d"); + const ctx = canvasRef.current?.getContext("2d"); + let intervalId: number; - if (canvas && ctx) { - canvas.style.left = `${left}px`; - canvas.style.top = `${top}px`; + if (!canvasRef.current || !ctx) { + return; + } - const tileSet = new Image(); - tileSet.src = TILE_SETS.Npc; - tileSet.onload = () => { - ctx.clearRect(0, 0, WIDTH, HEIGHT); - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - }; + canvasRef.current.style.left = `${left}px`; + canvasRef.current.style.top = `${top}px`; - window.setInterval(() => { - const currentHue = parseInt( - canvas.style.filter.match(/\d+/)?.[0] || "0" - ); - if (currentHue === 360) { - increment = -HUE_STEP; - } else if (currentHue === 0) { - increment = HUE_STEP; - } - const hue = Math.max(0, Math.min(360, currentHue + increment)); - canvas.style.filter = `hue-rotate(${hue}deg)`; - }, 100); - } + const tileSet = new Image(); + tileSet.src = TILE_SETS.Npc; + tileSet.onload = () => { + ctx.clearRect(0, 0, WIDTH, HEIGHT); + ctx.drawImage( + tileSet, + TILE_X, + TILE_Y, + WIDTH, + HEIGHT, + 0, + 0, + WIDTH, + HEIGHT + ); + }; + + intervalId = window.setInterval(() => { + if (!canvasRef.current) { + return; + } + + const currentHue = parseInt( + canvasRef.current.style.filter.match(/\d+/)?.[0] || "0" + ); + if (currentHue === 360) { + increment = -HUE_STEP; + } else if (currentHue === 0) { + increment = HUE_STEP; + } + const hue = Math.max(0, Math.min(360, currentHue + increment)); + canvasRef.current.style.filter = `hue-rotate(${hue}deg)`; + }, 100); + + return () => { + clearInterval(intervalId); + }; }, [left, top]); - return ; + return ( + + ); }; export default Npc; diff --git a/src/components/Player/index.tsx b/src/components/Player/index.tsx index 13b98ae..53ede43 100644 --- a/src/components/Player/index.tsx +++ b/src/components/Player/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, FunctionComponent, useContext } from "react"; +import { useEffect, useRef, FC, useContext } from "react"; import { GAME_STATES, TILE_SETS, TILE_SIZE } from "../../constants"; import { GlobalContext } from "../../contexts"; import "./style.css"; @@ -9,29 +9,35 @@ const TILE_X = 0; const TILE_Y = 8; const ANIMATION_LENGTH = 3; +type PlayerProps = { + top: number; + left: number; + health: number; + onInteract: (isOpen: boolean | ((wasOpen: boolean) => boolean)) => void; + onCollision: (health: number | ((prev: number) => number)) => void; +}; + /* * TODO: * - 2D Vectors for movement direction - * - useRef instead of getElementById * - util function for tile set, tiles and animation - * - create global constants for tile sets and tile size - * - prefer to return early, flip the if condition * - move object specific interactions outside of Player * - move player controls to global context * - use input loop to remove keydown delay * - create util function for collisions */ let invulnerable = false; -const Player: FunctionComponent<{ - health: number; - onInteract: (isOpen: boolean | ((wasOpen: boolean) => boolean)) => void; - onCollision: (health: number | ((prev: number) => number)) => void; -}> = ({ health, onInteract, onCollision }) => { +const Player: FC = ({ + health, + onInteract, + onCollision, + top, + left, +}) => { + const canvasRef = useRef(null); const { setGameState } = useContext(GlobalContext); + useEffect(() => { - const canvas = document.getElementById( - "player-canvas" - ) as HTMLCanvasElement | null; const fireCanvas = document.getElementById( "fire-canvas" ) as HTMLCanvasElement | null; @@ -67,18 +73,66 @@ const Player: FunctionComponent<{ } } - if (canvas) { - canvas.style.top = canvas.style.top || "328px"; - canvas.style.left = canvas.style.left || "420px"; - const ctx = canvas.getContext("2d"); + if (!canvasRef.current) { + return; + } - if (ctx) { - const tileSet = new Image(); - tileSet.src = TILE_SETS.Player; - tileSet.onload = () => { - let keyPressed = false; - let direction = "down"; - let currentFrame = 0; + canvasRef.current.style.top = canvasRef.current.style.top || `${top}px`; + canvasRef.current.style.left = canvasRef.current.style.left || `${left}px`; + const ctx = canvasRef.current.getContext("2d"); + + if (!ctx) { + return; + } + + const tileSet = new Image(); + tileSet.src = TILE_SETS.Player; + tileSet.onload = () => { + let keyPressed = false; + let direction = "down"; + let currentFrame = 0; + ctx.drawImage( + tileSet, + TILE_X, + TILE_Y, + WIDTH, + HEIGHT, + 0, + 0, + WIDTH, + HEIGHT + ); + + window.onkeyup = () => { + currentFrame = 0; + keyPressed = false; + ctx.clearRect(0, 0, WIDTH, HEIGHT); + + if (direction === "up") { + ctx.drawImage( + tileSet, + TILE_X, + TILE_Y + TILE_SIZE * 4, + WIDTH, + HEIGHT, + 0, + 0, + WIDTH, + HEIGHT + ); + } else if (direction === "left") { + ctx.drawImage( + tileSet, + TILE_X, + TILE_Y + TILE_SIZE * 6, + WIDTH, + HEIGHT, + 0, + 0, + WIDTH, + HEIGHT + ); + } else if (direction === "down") { ctx.drawImage( tileSet, TILE_X, @@ -90,15 +144,149 @@ const Player: FunctionComponent<{ WIDTH, HEIGHT ); + } else if (direction === "right") { + ctx.drawImage( + tileSet, + TILE_X, + TILE_Y + TILE_SIZE * 2, + WIDTH, + HEIGHT, + 0, + 0, + WIDTH, + HEIGHT + ); + } + }; + + window.onkeydown = (event) => { + if (!canvasRef.current) { + return; + } + + if (health > 0) { + if (health < 4) { + if ( + heartCanvas && + parseInt(canvasRef.current.style.left || "0") + 6 <= + parseInt(heartCanvas.style.left || "0") + 16 && + parseInt(canvasRef.current.style.left || "0") + 36 >= + parseInt(heartCanvas.style.left || "0") && + parseInt(canvasRef.current.style.top || "0") + 36 <= + parseInt(heartCanvas.style.top || "0") + 32 && + parseInt(canvasRef.current.style.top || "0") + 36 >= + parseInt(heartCanvas.style.top || "0") + 16 + ) { + onCollision((health) => Math.min(4, health + 1)); + heartCanvas.remove(); + } + } + + if ( + coinCanvas && + parseInt(canvasRef.current.style.left || "0") + 6 <= + parseInt(coinCanvas.style.left || "0") + 16 && + parseInt(canvasRef.current.style.left || "0") + 36 >= + parseInt(coinCanvas.style.left || "0") && + parseInt(canvasRef.current.style.top || "0") + 36 <= + parseInt(coinCanvas.style.top || "0") + 32 && + parseInt(canvasRef.current.style.top || "0") + 36 >= + parseInt(coinCanvas.style.top || "0") + 16 + ) { + coinCanvas.remove(); + } - window.onkeyup = () => { - currentFrame = 0; - keyPressed = false; + if (event.key === " " || event.key === "Enter") { + onInteract((wasOpen) => !wasOpen); + } else if (event.key === "w" || event.key === "ArrowUp") { + direction = "up"; + canvasRef.current.style.top = `${ + parseInt(canvasRef.current.style.top || "0") - 4 + }px`; + } else if (event.key === "s" || event.key === "ArrowDown") { + direction = "down"; + canvasRef.current.style.top = `${ + parseInt(canvasRef.current.style.top || "0") + 4 + }px`; + } else if (event.key === "a" || event.key === "ArrowLeft") { + direction = "left"; + canvasRef.current.style.left = `${ + parseInt(canvasRef.current.style.left || "0") - 4 + }px`; + } else if (event.key === "d" || event.key === "ArrowRight") { + direction = "right"; + canvasRef.current.style.left = `${ + parseInt(canvasRef.current.style.left || "0") + 4 + }px`; + } + + if ( + fireCanvas && + !invulnerable && + parseInt(canvasRef.current.style.left || "0") + 6 <= + parseInt(fireCanvas.style.left || "0") + 16 && + parseInt(canvasRef.current.style.left || "0") + 36 >= + parseInt(fireCanvas.style.left || "0") && + parseInt(canvasRef.current.style.top || "0") + 36 <= + parseInt(fireCanvas.style.top || "0") + 32 && + parseInt(canvasRef.current.style.top || "0") + 36 >= + parseInt(fireCanvas.style.top || "0") + 16 + ) { + if (event.key === "w" || event.key === "ArrowUp") { + canvasRef.current.style.top = `${Math.min( + window.innerHeight, + parseInt(canvasRef.current.style.top || "0") + 48 + )}px`; + } else if (event.key === "s" || event.key === "ArrowDown") { + canvasRef.current.style.top = `${Math.max( + 0, + parseInt(canvasRef.current.style.top || "0") - 48 + )}px`; + } else if (event.key === "a" || event.key === "ArrowLeft") { + canvasRef.current.style.left = `${Math.min( + window.innerWidth, + parseInt(canvasRef.current.style.left || "0") + 48 + )}px`; + } else if (event.key === "d" || event.key === "ArrowRight") { + canvasRef.current.style.left = `${Math.max( + 0, + parseInt(canvasRef.current.style.left || "0") - 48 + )}px`; + } + + onCollision((health) => Math.max(0, health - 1)); + invulnerable = true; + canvasRef.current.style.filter = "brightness(6)"; + + const interval = setInterval(() => { + if (!canvasRef.current) { + return; + } + + canvasRef.current.style.filter = + canvasRef.current.style.filter.includes("1") + ? "brightness(6)" + : "brightness(1)"; + }, 100); + + setTimeout(() => { + clearInterval(interval); + if (!canvasRef.current) { + return; + } + canvasRef.current.style.filter = "brightness(1)"; + invulnerable = false; + }, 1500); + } + + if (!keyPressed) { + keyPressed = true; ctx.clearRect(0, 0, WIDTH, HEIGHT); + if (direction === "up") { ctx.drawImage( tileSet, - TILE_X, + TILE_X + WIDTH * currentFrame, TILE_Y + TILE_SIZE * 4, WIDTH, HEIGHT, @@ -107,10 +295,11 @@ const Player: FunctionComponent<{ WIDTH, HEIGHT ); - } else if (direction === "left") { + } + if (direction === "left") { ctx.drawImage( tileSet, - TILE_X, + TILE_X + WIDTH * currentFrame, TILE_Y + TILE_SIZE * 6, WIDTH, HEIGHT, @@ -119,10 +308,11 @@ const Player: FunctionComponent<{ WIDTH, HEIGHT ); - } else if (direction === "down") { + } + if (direction === "down") { ctx.drawImage( tileSet, - TILE_X, + TILE_X + WIDTH * currentFrame, TILE_Y, WIDTH, HEIGHT, @@ -131,10 +321,11 @@ const Player: FunctionComponent<{ WIDTH, HEIGHT ); - } else if (direction === "right") { + } + if (direction === "right") { ctx.drawImage( tileSet, - TILE_X, + TILE_X + WIDTH * currentFrame, TILE_Y + TILE_SIZE * 2, WIDTH, HEIGHT, @@ -144,348 +335,28 @@ const Player: FunctionComponent<{ HEIGHT ); } - }; - - window.onkeydown = (event) => { - if (health > 0) { - if (health < 4) { - if ( - heartCanvas && - parseInt(canvas.style.left || "0") + 6 <= - parseInt(heartCanvas.style.left || "0") + 16 && - parseInt(canvas.style.left || "0") + 36 >= - parseInt(heartCanvas.style.left || "0") && - parseInt(canvas.style.top || "0") + 36 <= - parseInt(heartCanvas.style.top || "0") + 32 && - parseInt(canvas.style.top || "0") + 36 >= - parseInt(heartCanvas.style.top || "0") + 16 - ) { - onCollision((health) => Math.min(4, health + 1)); - heartCanvas.remove(); - } - } - - if ( - coinCanvas && - parseInt(canvas.style.left || "0") + 6 <= - parseInt(coinCanvas.style.left || "0") + 16 && - parseInt(canvas.style.left || "0") + 36 >= - parseInt(coinCanvas.style.left || "0") && - parseInt(canvas.style.top || "0") + 36 <= - parseInt(coinCanvas.style.top || "0") + 32 && - parseInt(canvas.style.top || "0") + 36 >= - parseInt(coinCanvas.style.top || "0") + 16 - ) { - coinCanvas.remove(); - } - - if (event.key === " " || event.key === "Enter") { - onInteract((wasOpen) => !wasOpen); - } else if (event.key === "w" || event.key === "ArrowUp") { - direction = "up"; - canvas.style.top = `${parseInt(canvas.style.top || "0") - 4}px`; - } else if (event.key === "s" || event.key === "ArrowDown") { - direction = "down"; - canvas.style.top = `${parseInt(canvas.style.top || "0") + 4}px`; - } else if (event.key === "a" || event.key === "ArrowLeft") { - direction = "left"; - canvas.style.left = `${ - parseInt(canvas.style.left || "0") - 4 - }px`; - } else if (event.key === "d" || event.key === "ArrowRight") { - direction = "right"; - canvas.style.left = `${ - parseInt(canvas.style.left || "0") + 4 - }px`; - } - - if ( - fireCanvas && - !invulnerable && - parseInt(canvas.style.left || "0") + 6 <= - parseInt(fireCanvas.style.left || "0") + 16 && - parseInt(canvas.style.left || "0") + 36 >= - parseInt(fireCanvas.style.left || "0") && - parseInt(canvas.style.top || "0") + 36 <= - parseInt(fireCanvas.style.top || "0") + 32 && - parseInt(canvas.style.top || "0") + 36 >= - parseInt(fireCanvas.style.top || "0") + 16 - ) { - if (event.key === "w" || event.key === "ArrowUp") { - canvas.style.top = `${Math.min( - window.innerHeight, - parseInt(canvas.style.top || "0") + 48 - )}px`; - } else if (event.key === "s" || event.key === "ArrowDown") { - canvas.style.top = `${Math.max( - 0, - parseInt(canvas.style.top || "0") - 48 - )}px`; - } else if (event.key === "a" || event.key === "ArrowLeft") { - canvas.style.left = `${Math.min( - window.innerWidth, - parseInt(canvas.style.left || "0") + 48 - )}px`; - } else if (event.key === "d" || event.key === "ArrowRight") { - canvas.style.left = `${Math.max( - 0, - parseInt(canvas.style.left || "0") - 48 - )}px`; - } - - onCollision((health) => Math.max(0, health - 1)); - invulnerable = true; - canvas.style.filter = "brightness(6)"; - const interval = setInterval(() => { - canvas.style.filter = canvas.style.filter.includes("1") - ? "brightness(6)" - : "brightness(1)"; - }, 100); - - setTimeout(() => { - clearInterval(interval); - canvas.style.filter = "brightness(1)"; - invulnerable = false; - }, 1500); - } - - if (!keyPressed) { - keyPressed = true; - ctx.clearRect(0, 0, WIDTH, HEIGHT); - - if (currentFrame === 0) { - if (direction === "up") { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y + TILE_SIZE * 4, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - if (direction === "left") { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y + TILE_SIZE * 6, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - if (direction === "down") { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - if (direction === "right") { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y + TILE_SIZE * 2, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - } else if (currentFrame === 1) { - if (direction === "up") { - ctx.drawImage( - tileSet, - TILE_X + WIDTH, - TILE_Y + TILE_SIZE * 4, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - if (direction === "left") { - ctx.drawImage( - tileSet, - TILE_X + WIDTH, - TILE_Y + TILE_SIZE * 6, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - if (direction === "down") { - ctx.drawImage( - tileSet, - TILE_X + WIDTH, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - if (direction === "right") { - ctx.drawImage( - tileSet, - TILE_X + WIDTH, - TILE_Y + TILE_SIZE * 2, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - } else if (currentFrame === 2) { - if (direction === "up") { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y + TILE_SIZE * 4, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - if (direction === "left") { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y + TILE_SIZE * 6, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - if (direction === "down") { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - if (direction === "right") { - ctx.drawImage( - tileSet, - TILE_X, - TILE_Y + TILE_SIZE * 2, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - } else if (currentFrame === 3) { - if (direction === "up") { - ctx.drawImage( - tileSet, - TILE_X + WIDTH * 3, - TILE_Y + TILE_SIZE * 4, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - if (direction === "left") { - ctx.drawImage( - tileSet, - TILE_X + WIDTH * 3, - TILE_Y + TILE_SIZE * 6, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - if (direction === "down") { - ctx.drawImage( - tileSet, - TILE_X + WIDTH * 3, - TILE_Y, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - if (direction === "right") { - ctx.drawImage( - tileSet, - TILE_X + WIDTH * 3, - TILE_Y + TILE_SIZE * 2, - WIDTH, - HEIGHT, - 0, - 0, - WIDTH, - HEIGHT - ); - } - } - - setTimeout(() => { - keyPressed = false; - currentFrame = - currentFrame === ANIMATION_LENGTH ? 0 : currentFrame + 1; - }, 125); - } - } else { - setGameState(GAME_STATES.GameOver); - } - }; - }; - } - } - }, [onInteract, onCollision, health, setGameState]); + setTimeout(() => { + keyPressed = false; + currentFrame = + currentFrame === ANIMATION_LENGTH ? 0 : currentFrame + 1; + }, 125); + } + } else { + setGameState(GAME_STATES.GameOver); + } + }; + }; + }, [onInteract, onCollision, health, setGameState, top, left]); return ( <> - + ); diff --git a/src/components/World/index.tsx b/src/components/World/index.tsx index 43d6f60..017e052 100644 --- a/src/components/World/index.tsx +++ b/src/components/World/index.tsx @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import { WORLD_WIDTH, WORLD_HEIGHT, @@ -11,58 +11,52 @@ import "./style.css"; // Number of tiles in the tilset const TILES_COUNT = 40; -/* - * Refactor: - * - useRef instead of getElementById - * - useContext to provide global tile count for rows and columns - * - create global constants for tile sets and tile size - * - create util to draw different tiles of a tile set: drawGrass(x, y), drawStone(x, y) - * - prefer to return early, flip the if condition - */ export default function World() { - useEffect(() => { - const canvas = document.getElementById( - "world-canvas" - ) as HTMLCanvasElement | null; - const ctx = canvas?.getContext("2d"); + const canvasRef = useRef(null); - if (canvas && ctx) { - const tileSet = new Image(); - tileSet.src = TILE_SETS.World; - tileSet.onload = () => { - level.layers.forEach(({ chunks }) => { - let i = 0; - for (let y = 0; y < level.height; y++) { - for (let x = 0; x < level.width; x++) { - // Tiled exports tiles counting from 1 rather than 0 - const tile = chunks[0].data[i++] - 1; - if (tile < 0) { - continue; - } + useEffect(() => { + const ctx = canvasRef.current?.getContext("2d"); - const tileX = (tile % TILES_COUNT) * TILE_SIZE; - const tileY = Math.floor(tile / TILES_COUNT) * TILE_SIZE; + if (!canvasRef.current || !ctx) { + return; + } - ctx.drawImage( - tileSet, - tileX, - tileY, - TILE_SIZE, - TILE_SIZE, - x * TILE_SIZE, - y * TILE_SIZE, - TILE_SIZE, - TILE_SIZE - ); + const tileSet = new Image(); + tileSet.src = TILE_SETS.World; + tileSet.onload = () => { + level.layers.forEach(({ chunks }) => { + let i = 0; + for (let y = 0; y < level.height; y++) { + for (let x = 0; x < level.width; x++) { + // Tiled exports tiles counting from 1 rather than 0 + const tile = chunks[0].data[i++] - 1; + if (tile < 0) { + continue; } + + const tileX = (tile % TILES_COUNT) * TILE_SIZE; + const tileY = Math.floor(tile / TILES_COUNT) * TILE_SIZE; + + ctx.drawImage( + tileSet, + tileX, + tileY, + TILE_SIZE, + TILE_SIZE, + x * TILE_SIZE, + y * TILE_SIZE, + TILE_SIZE, + TILE_SIZE + ); } - }); - }; - } + } + }); + }; }, []); return (