Last active
December 9, 2023 04:59
-
-
Save AdventureBear/31153d6287c466922f154fb8a86a8d10 to your computer and use it in GitHub Desktop.
React Brick Breaker fixes (sort of)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, {useState, useEffect, useRef, useCallback} from "react"; | |
const BrickBreaker = () => { | |
const canvasRef = useRef<HTMLCanvasElement | null>(null); | |
const ballPosition = useRef({ x: 200, y: 150 }); // Ref for ball position | |
const ballDirection = useRef({dx: 1, dy: -3}) //Ref for ball direction | |
// const [x, setX] = useState(200); // starting horizontal position of ball | |
// const [y, setY] = useState(150); // starting vertical position of ball | |
// const [dx, setDx] = useState(1); // amount ball should move horizontally | |
// const [dy, setDy] = useState(-3); // amount ball should move vertically | |
// variables set in init() | |
const [ctx, setCtx] = useState<CanvasRenderingContext2D | null>(null); | |
const [width, setWidth] = useState<number | null>(null); // canvas width | |
const [height, setHeight] = useState<number | null>(null); // canvas height | |
const [paddleX, setPaddleX] = useState<number | null>(null); // paddle position | |
const [bricks, setBricks] = useState<boolean[][]>([]); // array representing the state of bricks | |
// const [brickWidth, setBrickWidth] = useState<number | null>(null); | |
const paddleH = 10; // paddle height | |
const paddleW = 75; // paddle width | |
const [canvasMinX, setCanvasMinX] = useState<number>(0); // minimum canvas x bounds | |
const [canvasMaxX, setCanvasMaxX] = useState<number>(0); // maximum canvas x bounds | |
const [intervalId, setIntervalId] = useState<number | null>(null); | |
const nrows = 6; // number of rows of bricks | |
const ncols = 6; // number of columns of bricks | |
const brickHeight = 15; // height of each brick | |
const brickWidth = 75; // width of each brick | |
const padding = 1; // spacing between each brick | |
const ballRadius = 10; // size of ball (pixels) | |
const brickColors = ["#80CBC4", "#455A64 ", "#FFE0B2", "#A5D6A7", "#D43ED8"]; | |
const paddleColor = "black"; | |
const ballColor = "black"; | |
const backColor = "lightgrey"; | |
const initBricks = useCallback(() => { | |
const bricks = []; | |
for (let i = 0; i < nrows; i++) { | |
const row = []; | |
for (let j = 0; j < ncols; j++) { | |
row.push(true); | |
} | |
bricks.push(row); | |
} | |
return bricks; | |
}, [nrows, ncols]); | |
// Used to draw the ball | |
const drawCircle = (x: number, y: number, r: number) => { | |
// no update needed | |
if (ctx) { | |
ctx.fillStyle = ballColor; | |
ctx.beginPath(); | |
ctx.arc(x, y, r, 0, Math.PI * 2, true); | |
ctx.closePath(); | |
ctx.fill(); | |
} | |
}; | |
// Used to draw each brick & the paddle | |
const drawRect = (x: number, y: number, w: number, h: number) => { | |
// no update needed | |
if (ctx) { | |
ctx.beginPath(); | |
ctx.rect(x, y, w, h); | |
ctx.closePath(); | |
ctx.fill(); | |
} | |
}; | |
// Clear the screen in between drawing each animation | |
const clearCanvas = () => { | |
if (ctx && width && height) { | |
ctx.fillStyle = backColor; | |
ctx.clearRect(0, 0, width, height); | |
drawRect(0, 0, width, height); | |
} | |
}; | |
const drawBricks = () => { | |
console.log("drawing bricks") | |
for (let i = 0; i < nrows; i++) { | |
for (let j = 0; j < ncols; j++) { | |
if (ctx) { | |
ctx.fillStyle = brickColors[(i + j) % brickColors.length]; | |
} | |
if (bricks[i][j]) { | |
drawRect( | |
j * (brickWidth + padding) + padding, | |
i * (brickHeight + padding) + padding, | |
brickWidth, | |
brickHeight | |
); | |
} | |
} | |
} | |
}; | |
function brickCollision() { | |
console.log("hit brick") | |
const { x, y } = ballPosition.current; | |
const { dy } = ballDirection.current; | |
const rowHeight = brickHeight + padding; | |
const colWidth = brickWidth + padding; | |
const row = Math.floor(y / rowHeight); | |
const col = Math.floor(x / colWidth); | |
// if so reverse the ball and mark the brick as broken | |
if (y < nrows * rowHeight && row >= 0 && col >= 0 && bricks[row][col]) { | |
ballDirection.current.dy = -dy; | |
setBricks((prevBricks) => { | |
const newBricks = [...prevBricks]; | |
newBricks[row][col] = false; | |
return newBricks; | |
}); | |
// score++; // add a point every time a brick is broken | |
// updateScoreText(); // show the current score | |
} | |
} | |
function hitWall() { | |
if (!width || !height || !paddleX) return | |
console.log("hit wall?") | |
const { x, y } = ballPosition.current; | |
const { dx, dy } = ballDirection.current; | |
if (x + dx > width || x + dx < 0) ballDirection.current.dx = -dx; | |
if (y + dy < 0) { | |
ballDirection.current.dy = -dy; | |
console.log("hit top of frame") | |
} else if (y + dy > height - paddleH) { | |
// check if the ball is hitting the | |
// paddle and if it is rebound it | |
// if (x > paddleX && x < paddleX + paddleW) { | |
// console.log("hit paddle") | |
ballDirection.current.dy = -dy; | |
// } | |
} | |
// if (y + dy > height) { | |
// console.log("game over!") | |
// //game over, so stop the animation | |
// stopAnimation(); | |
// // alert("Game Over! Your score is: " + score); | |
// document.location.reload(); | |
// } | |
} | |
const draw = useCallback(() => { | |
if (!ctx || !width || !height || !paddleX) return; | |
console.log("Drawing..."); | |
// Access the latest ball position from the ref | |
const { x, y } = ballPosition.current; | |
const { dx, dy } = ballDirection.current; | |
console.log("Ball position (x, y):", x, y); | |
console.log("Ball speed (dx, dy):", dx, dy); | |
// Your draw function logic here | |
clearCanvas(); | |
drawCircle(x, y, ballRadius); | |
//draw the paddle | |
ctx.fillStyle = paddleColor; | |
drawRect(paddleX, height - paddleH, paddleW, paddleH); | |
// drawBricks(); | |
//collision logic | |
// brickCollision(); | |
hitWall(); | |
console.log("updating speed & position") | |
// Update the ball position for the next frame | |
ballPosition.current = { | |
x: x + dx, | |
y: y + dy, | |
}; | |
}, [ctx ,bricks, paddleX, width, height]); | |
//setup Canvas | |
useEffect(() => { | |
console.log("Setting up canvas and context"); | |
const canvas = canvasRef.current; | |
const context = canvas?.getContext("2d"); | |
if (canvas && context) { | |
setCtx(context); | |
setWidth(canvas.offsetWidth); | |
setHeight(canvas.offsetHeight); | |
setPaddleX(canvas.offsetWidth / 2); | |
setCanvasMinX(canvas.offsetLeft); | |
setCanvasMaxX(canvas.offsetLeft + canvas.offsetWidth); | |
setBricks(initBricks()); | |
} | |
}, []); | |
const startAnimation = useCallback(() => { | |
console.log("Start Animation"); | |
const id = window.setInterval(()=> { | |
draw()}, 1000/60); | |
setIntervalId(id); | |
}, [draw]); | |
const stopAnimation = () => { | |
console.log("Stop animation"); | |
if (intervalId != null) { | |
window.clearInterval(intervalId); | |
setIntervalId(null); | |
} | |
}; | |
// // start & stop animation | |
useEffect(() => { | |
startAnimation(); | |
return () => { | |
stopAnimation(); | |
}; | |
}, []); | |
return ( | |
<div> | |
<h1>Brick Breaker</h1> | |
<canvas | |
ref={canvasRef} | |
width={width || 500} | |
height={height || 300} | |
tabIndex={0} | |
/> | |
</div> | |
); | |
}; | |
export default BrickBreaker; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment