Add pegs
This commit is contained in:
parent
a38b5f81cd
commit
307be47251
|
|
@ -142,6 +142,16 @@ EXPORT(rigid_body_add_force) void rigid_body_add_force(rigid_body* rb, float fx,
|
||||||
rb->force.y += fy;
|
rb->force.y += fy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
EXPORT(rigid_body_add_global_force) void rigid_body_add_global_force(float fx, float fy) {
|
||||||
|
rigid_body* current = rigid_body_head;
|
||||||
|
|
||||||
|
while (current) {
|
||||||
|
rigid_body_add_force(current, fx, fy);
|
||||||
|
current = current->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EXPORT(rigid_body_set_collision_callback) void rigid_body_set_collision_callback(rigid_body_collision_callback_t callback) {
|
EXPORT(rigid_body_set_collision_callback) void rigid_body_set_collision_callback(rigid_body_collision_callback_t callback) {
|
||||||
rigid_body_collision_callback = callback;
|
rigid_body_collision_callback = callback;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,22 +3,18 @@ import E from './engine.c';
|
||||||
namespace Physics {
|
namespace Physics {
|
||||||
const TYPE_CIRCLE = 1;
|
const TYPE_CIRCLE = 1;
|
||||||
const TYPE_PLANE = 2;
|
const TYPE_PLANE = 2;
|
||||||
const bodies = new Set<number>();
|
|
||||||
|
|
||||||
export function newCircle(x: number, y: number, radius: number, mass: number = 1.0): number {
|
export function newCircle(x: number, y: number, radius: number, mass: number = 1.0): number {
|
||||||
const body = E.rigid_body_new_circle(x, y, 0, 0, mass, radius);
|
const body = E.rigid_body_new_circle(x, y, 0, 0, mass, radius);
|
||||||
bodies.add(body);
|
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function newPlane(x: number, y: number, nx: number, ny: number): number {
|
export function newPlane(x: number, y: number, nx: number, ny: number): number {
|
||||||
const body = E.rigid_body_new_plane(x, y, nx, ny);
|
const body = E.rigid_body_new_plane(x, y, nx, ny);
|
||||||
bodies.add(body);
|
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteBody(body: number) {
|
export function deleteBody(body: number) {
|
||||||
bodies.delete(body);
|
|
||||||
E.rigid_body_free(body);
|
E.rigid_body_free(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,9 +23,7 @@ namespace Physics {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addGlobalForce(fx: number, fy: number) {
|
export function addGlobalForce(fx: number, fy: number) {
|
||||||
for (const body of bodies) {
|
E.rigid_body_add_global_force(fx, fy);
|
||||||
E.rigid_body_add_force(body, fx, fy);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function update(dt: number) {
|
export function update(dt: number) {
|
||||||
|
|
@ -58,10 +52,6 @@ namespace Physics {
|
||||||
return { id: body, x, y, vx, vy, mass };
|
return { id: body, x, y, vx, vy, mass };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBodies() {
|
|
||||||
return Array.from(bodies).map(getBody);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setCollisionCallback(cb: (a: number, b: number) => void) {
|
export function setCollisionCallback(cb: (a: number, b: number) => void) {
|
||||||
const cbPtr = E.table.grow(1, new WebAssembly.Function({ parameters: ['i32', 'i32'], results: [] }, cb));
|
const cbPtr = E.table.grow(1, new WebAssembly.Function({ parameters: ['i32', 'i32'], results: [] }, cb));
|
||||||
E.rigid_body_set_collision_callback(cbPtr);
|
E.rigid_body_set_collision_callback(cbPtr);
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ export const shuffle = <T>(array: T[]): T[] => {
|
||||||
|
|
||||||
return shuffledArray;
|
return shuffledArray;
|
||||||
}
|
}
|
||||||
|
export const mapNumber = (num: number, inMin: number, inMax: number, outMin: number, outMax: number) =>
|
||||||
|
(num - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
|
||||||
|
|
||||||
export function zip<T1, T2, T3>(a1: Iterable<T1>, a2: Iterable<T2>, a3: Iterable<T3>): Generator<[T1, T2, T3]>;
|
export function zip<T1, T2, T3>(a1: Iterable<T1>, a2: Iterable<T2>, a3: Iterable<T3>): Generator<[T1, T2, T3]>;
|
||||||
export function zip<T1, T2>(a1: Iterable<T1>, a2: Iterable<T2>): Generator<[T1, T2]>;
|
export function zip<T1, T2>(a1: Iterable<T1>, a2: Iterable<T2>): Generator<[T1, T2]>;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,30 @@
|
||||||
import { createCanvas } from "@common/display/canvas";
|
import { createCanvas } from "@common/display/canvas";
|
||||||
import { gameLoop } from "@common/game";
|
import { gameLoop } from "@common/game";
|
||||||
import Physics from "@common/physics";
|
import Physics from "@common/physics";
|
||||||
|
import { mapNumber } from "@common/utils";
|
||||||
|
|
||||||
|
interface Ball {
|
||||||
|
body: number;
|
||||||
|
damage: number;
|
||||||
|
health: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Peg {
|
||||||
|
body: number;
|
||||||
|
health: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State extends Record<string, unknown> {
|
||||||
|
canvas: HTMLCanvasElement;
|
||||||
|
ctx: CanvasRenderingContext2D;
|
||||||
|
bottom: number;
|
||||||
|
balls: Set<Ball>;
|
||||||
|
pegs: Set<Peg>;
|
||||||
|
|
||||||
|
pegHealth: number;
|
||||||
|
ballHealth: number;
|
||||||
|
ballDamage: number;
|
||||||
|
}
|
||||||
|
|
||||||
let lastTick = performance.now();
|
let lastTick = performance.now();
|
||||||
function update() {
|
function update() {
|
||||||
|
|
@ -13,46 +36,126 @@ function update() {
|
||||||
setTimeout(update, 5);
|
setTimeout(update, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const killBody = (state: State, body: Peg | Ball) => {
|
||||||
|
state.balls.delete(body as Ball);
|
||||||
|
state.pegs.delete(body as Peg);
|
||||||
|
Physics.deleteBody(body.body);
|
||||||
|
}
|
||||||
|
|
||||||
const setup = () => {
|
const onCollision = (state: State, b1: number, b2: number) => {
|
||||||
|
const ball = state.balls.values().find(b => b.body === b1 || b.body === b2);
|
||||||
|
const peg = state.pegs.values().find(b => b.body === b1 || b.body === b2);
|
||||||
|
|
||||||
|
if (ball && (b1 === state.bottom || b2 === state.bottom)) {
|
||||||
|
killBody(state, ball);
|
||||||
|
} else if (peg && ball) {
|
||||||
|
peg.health -= ball.damage;
|
||||||
|
ball.health -= 1;
|
||||||
|
if (peg.health <= 0) {
|
||||||
|
killBody(state, peg);
|
||||||
|
}
|
||||||
|
if (ball.health <= 0) {
|
||||||
|
killBody(state, ball);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const generatePegs = (state: State) => {
|
||||||
|
const { canvas } = state;
|
||||||
|
state.pegs.clear();
|
||||||
|
|
||||||
|
const gap = 45;
|
||||||
|
const vgap = gap * Math.cos(Math.PI / 6);
|
||||||
|
let offset = 0;
|
||||||
|
for (let y = canvas.height / 3; y <= canvas.height - gap; y += vgap) {
|
||||||
|
for (let i = offset; i <= canvas.width / 2; i += gap) {
|
||||||
|
const x1 = canvas.width / 2 + i;
|
||||||
|
state.pegs.add({
|
||||||
|
body: Physics.newCircle(x1, y, 3, Number.POSITIVE_INFINITY),
|
||||||
|
health: state.pegHealth,
|
||||||
|
});
|
||||||
|
|
||||||
|
const x2 = canvas.width / 2 - i;
|
||||||
|
if (Math.abs(x1 - x2) < gap) continue;
|
||||||
|
state.pegs.add({
|
||||||
|
body: Physics.newCircle(x2, y, 3, Number.POSITIVE_INFINITY),
|
||||||
|
health: state.pegHealth,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
offset = gap / 2 - offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setup = (): State => {
|
||||||
const canvas = createCanvas(640, 480);
|
const canvas = createCanvas(640, 480);
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
throw new Error('Failed to get canvas context');
|
throw new Error('Failed to get canvas context');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const state: State = {
|
||||||
|
canvas,
|
||||||
|
ctx,
|
||||||
|
bottom: 0,
|
||||||
|
pegs: new Set(),
|
||||||
|
balls: new Set(),
|
||||||
|
pegHealth: 3,
|
||||||
|
ballHealth: 10,
|
||||||
|
ballDamage: 1,
|
||||||
|
};
|
||||||
|
|
||||||
ctx.imageSmoothingEnabled = false;
|
ctx.imageSmoothingEnabled = false;
|
||||||
canvas.addEventListener('contextmenu', e => {
|
canvas.addEventListener('contextmenu', e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
canvas.addEventListener('mouseup', () => {
|
canvas.addEventListener('mouseup', () => {
|
||||||
Physics.newCircle(canvas.width / 2 + Math.random() - 0.5, 50, 10, 1);
|
const body = Physics.newCircle(canvas.width / 2 + Math.random() - 0.5, 50, 10, 1);
|
||||||
|
const health = state.ballHealth;
|
||||||
|
const damage = state.ballDamage;
|
||||||
|
const ball: Ball = {
|
||||||
|
body,
|
||||||
|
health,
|
||||||
|
damage,
|
||||||
|
}
|
||||||
|
state.balls.add(ball);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
generatePegs(state);
|
||||||
|
|
||||||
|
state.bottom = Physics.newPlane(0, canvas.height, 0, -1);
|
||||||
Physics.newPlane(0, 0, 1, 0);
|
Physics.newPlane(0, 0, 1, 0);
|
||||||
Physics.newPlane(0, 0, 0, 1);
|
Physics.newPlane(0, 0, 0, 1);
|
||||||
Physics.newPlane(0, canvas.height, 0, -1);
|
|
||||||
Physics.newPlane(canvas.width, 0, -1, 0);
|
Physics.newPlane(canvas.width, 0, -1, 0);
|
||||||
|
|
||||||
|
Physics.setCollisionCallback((a, b) => onCollision(state, a, b));
|
||||||
|
|
||||||
update();
|
update();
|
||||||
|
|
||||||
return { canvas, ctx };
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
const frame = (dt: number, state: ReturnType<typeof setup>) => {
|
const frame = (dt: number, state: State) => {
|
||||||
Physics.addGlobalForce(0, 100);
|
Physics.addGlobalForce(0, 100);
|
||||||
Physics.update(dt);
|
Physics.update(dt);
|
||||||
|
|
||||||
const { ctx, canvas } = state;
|
const { ctx, canvas } = state;
|
||||||
ctx.fillStyle = `black`;
|
ctx.fillStyle = `#111`;
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
for (const { id, x, y, radius } of Physics.getBodies()) {
|
for (const peg of state.pegs) {
|
||||||
ctx.fillStyle = `hsl(${id % 360}, 100%, 50%)`;
|
const { x, y, radius } = Physics.getBody(peg.body);
|
||||||
if (radius) {
|
if (!radius) continue;
|
||||||
|
|
||||||
|
ctx.fillStyle = `hsl(0, 0%, ${mapNumber(peg.health, 0, state.pegHealth, 0, 100)}%)`;
|
||||||
fillCircle(ctx, x, y, radius);
|
fillCircle(ctx, x, y, radius);
|
||||||
}
|
}
|
||||||
|
for (const ball of state.balls) {
|
||||||
|
const { id, x, y, radius } = Physics.getBody(ball.body);
|
||||||
|
if (!radius) continue;
|
||||||
|
|
||||||
|
ctx.fillStyle = `hsl(${id % 360}, 100%, 50%)`;
|
||||||
|
fillCircle(ctx, x, y, radius);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue