Skip to content

Instantly share code, notes, and snippets.

@AdventureBear
Last active December 9, 2023 04:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AdventureBear/31153d6287c466922f154fb8a86a8d10 to your computer and use it in GitHub Desktop.
Save AdventureBear/31153d6287c466922f154fb8a86a8d10 to your computer and use it in GitHub Desktop.
React Brick Breaker fixes (sort of)
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