generated from michael/webpack-base
use canvas element
This commit is contained in:
parent
94e844f121
commit
32d5ea292d
33
src/components/snake/canvas.ts
Normal file
33
src/components/snake/canvas.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { Engine, Vec2, vec2 } from './game-engine';
|
||||
|
||||
const BOARD_SIZE = 600; // px
|
||||
const SQUARE_SIZE = 30; // px
|
||||
|
||||
export default function runCanvas(canvas: HTMLCanvasElement) {
|
||||
const engine = new Engine(canvas);
|
||||
|
||||
const snake = [vec2(1, 1), vec2(2, 1)];
|
||||
const apple = vec2(8, 8);
|
||||
|
||||
function clearCanvas() {
|
||||
engine.rect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
function drawSquare(pos: Vec2) {
|
||||
engine.rect(pos.x * SQUARE_SIZE, pos.y * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE);
|
||||
}
|
||||
|
||||
function render() {
|
||||
engine.setFillStyle('#333333');
|
||||
clearCanvas();
|
||||
|
||||
engine.setFillStyle('#277edb');
|
||||
for (const square of snake) {
|
||||
drawSquare(square);
|
||||
}
|
||||
|
||||
engine.setFillStyle('#e01851');
|
||||
drawSquare(apple);
|
||||
}
|
||||
|
||||
engine.run(render);
|
||||
}
|
98
src/components/snake/game-engine.ts
Normal file
98
src/components/snake/game-engine.ts
Normal file
@ -0,0 +1,98 @@
|
||||
export const TAU = 6.283185307179586;
|
||||
|
||||
export class Vec2 {
|
||||
x: number;
|
||||
y: number;
|
||||
|
||||
constructor(x: number, y: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
str() {
|
||||
return `<${this.x}, ${this.y}>`;
|
||||
}
|
||||
|
||||
eq(o: Vec2) {
|
||||
return this.x === o.x && this.y === o.y;
|
||||
}
|
||||
|
||||
add(o: Vec2) {
|
||||
return new Vec2(this.x + o.x, this.y + o.y);
|
||||
}
|
||||
|
||||
sub(o: Vec2) {
|
||||
return new Vec2(this.x - o.x, this.y - o.y);
|
||||
}
|
||||
|
||||
mul(scalar: number) {
|
||||
return new Vec2(this.x * scalar, this.y * scalar);
|
||||
}
|
||||
|
||||
div(scalar: number) {
|
||||
return new Vec2(this.x / scalar, this.y / scalar);
|
||||
}
|
||||
|
||||
length() {
|
||||
return Math.sqrt(this.x * this.x + this.y * this.y);
|
||||
}
|
||||
|
||||
norm() {
|
||||
const length = this.length();
|
||||
return new Vec2(this.x / length, this.y / length);
|
||||
}
|
||||
|
||||
static dot(a: Vec2, b: Vec2) {
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
// magnitude of 3d cross product's z-term
|
||||
static crossMag(a: Vec2, b: Vec2) {
|
||||
return a.x * b.y - a.y * b.x;
|
||||
}
|
||||
|
||||
// returns the angle needed to rotate a to b about the origin
|
||||
static angle(a: Vec2, b: Vec2) {
|
||||
const an = a.norm();
|
||||
const bn = b.norm();
|
||||
|
||||
const cos = Vec2.dot(an, bn);
|
||||
const sin = Vec2.crossMag(an, bn);
|
||||
|
||||
const theta = Math.atan2(sin, cos);
|
||||
return theta;
|
||||
}
|
||||
}
|
||||
|
||||
export function vec2(x: number, y: number) {
|
||||
return new Vec2(x, y);
|
||||
}
|
||||
|
||||
export class Engine {
|
||||
ctx: CanvasRenderingContext2D;
|
||||
|
||||
camera = vec2(0, 0);
|
||||
|
||||
constructor(canvasElement: HTMLCanvasElement) {
|
||||
const ctx = canvasElement.getContext('2d');
|
||||
if (ctx === null) throw Error('unable to get 2d canvas context');
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
setFillStyle(style: string) {
|
||||
this.ctx.fillStyle = style;
|
||||
}
|
||||
|
||||
rect(x: number, y: number, w: number, h: number) {
|
||||
const c = this.camera;
|
||||
this.ctx.fillRect(x - c.x, y - c.y, w, h);
|
||||
}
|
||||
|
||||
run(render: () => void) {
|
||||
function loop() {
|
||||
render();
|
||||
window.requestAnimationFrame(loop);
|
||||
}
|
||||
loop();
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { FC, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import './index.scss';
|
||||
import runCanvas from './canvas';
|
||||
|
||||
const BOARD_SIZE = 600; // px
|
||||
const SQUARE_SIZE = 30; // px
|
||||
@ -48,7 +49,6 @@ const SnakePage: FC = () => {
|
||||
const move = moves[dir];
|
||||
const nextSquare = { x: head.x + move.x, y: head.y + move.y };
|
||||
const newSnake = [...snake.slice(1), nextSquare];
|
||||
console.log({ snake, newSnake });
|
||||
setSnake(newSnake);
|
||||
},
|
||||
[snake],
|
||||
@ -90,8 +90,16 @@ const SnakePage: FC = () => {
|
||||
),
|
||||
[apple],
|
||||
);
|
||||
|
||||
const boardRef = useRef(null);
|
||||
useLayoutEffect(() => {
|
||||
if (boardRef.current === null) throw Error('boardRef.current is null');
|
||||
runCanvas(boardRef.current);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div id="snake">
|
||||
<canvas id="board" ref={boardRef} width={BOARD_SIZE} height={BOARD_SIZE} />
|
||||
<svg id="board" width={BOARD_SIZE} height={BOARD_SIZE}>
|
||||
{snakeElements}
|
||||
{appleElement}
|
||||
|
Loading…
Reference in New Issue
Block a user