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;
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
rigid_body_collision_callback = callback;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,22 +3,18 @@ import E from './engine.c';
|
|||
namespace Physics {
|
||||
const TYPE_CIRCLE = 1;
|
||||
const TYPE_PLANE = 2;
|
||||
const bodies = new Set<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);
|
||||
bodies.add(body);
|
||||
return body;
|
||||
}
|
||||
|
||||
export function newPlane(x: number, y: number, nx: number, ny: number): number {
|
||||
const body = E.rigid_body_new_plane(x, y, nx, ny);
|
||||
bodies.add(body);
|
||||
return body;
|
||||
}
|
||||
|
||||
export function deleteBody(body: number) {
|
||||
bodies.delete(body);
|
||||
E.rigid_body_free(body);
|
||||
}
|
||||
|
||||
|
|
@ -27,9 +23,7 @@ namespace Physics {
|
|||
}
|
||||
|
||||
export function addGlobalForce(fx: number, fy: number) {
|
||||
for (const body of bodies) {
|
||||
E.rigid_body_add_force(body, fx, fy);
|
||||
}
|
||||
E.rigid_body_add_global_force(fx, fy);
|
||||
}
|
||||
|
||||
export function update(dt: number) {
|
||||
|
|
@ -58,10 +52,6 @@ namespace Physics {
|
|||
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) {
|
||||
const cbPtr = E.table.grow(1, new WebAssembly.Function({ parameters: ['i32', 'i32'], results: [] }, cb));
|
||||
E.rigid_body_set_collision_callback(cbPtr);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ export const shuffle = <T>(array: T[]): T[] => {
|
|||
|
||||
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>(a1: Iterable<T1>, a2: Iterable<T2>): Generator<[T1, T2]>;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,30 @@
|
|||
import { createCanvas } from "@common/display/canvas";
|
||||
import { gameLoop } from "@common/game";
|
||||
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();
|
||||
function update() {
|
||||
|
|
@ -13,46 +36,126 @@ function update() {
|
|||
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 ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
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;
|
||||
canvas.addEventListener('contextmenu', e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
});
|
||||
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;
|
||||
});
|
||||
|
||||
generatePegs(state);
|
||||
|
||||
state.bottom = Physics.newPlane(0, canvas.height, 0, -1);
|
||||
Physics.newPlane(0, 0, 1, 0);
|
||||
Physics.newPlane(0, 0, 0, 1);
|
||||
Physics.newPlane(0, canvas.height, 0, -1);
|
||||
Physics.newPlane(canvas.width, 0, -1, 0);
|
||||
|
||||
Physics.setCollisionCallback((a, b) => onCollision(state, a, b));
|
||||
|
||||
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.update(dt);
|
||||
|
||||
const { ctx, canvas } = state;
|
||||
ctx.fillStyle = `black`;
|
||||
ctx.fillStyle = `#111`;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
for (const { id, x, y, radius } of Physics.getBodies()) {
|
||||
for (const peg of state.pegs) {
|
||||
const { x, y, radius } = Physics.getBody(peg.body);
|
||||
if (!radius) continue;
|
||||
|
||||
ctx.fillStyle = `hsl(0, 0%, ${mapNumber(peg.health, 0, state.pegHealth, 0, 100)}%)`;
|
||||
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%)`;
|
||||
if (radius) {
|
||||
fillCircle(ctx, x, y, radius);
|
||||
}
|
||||
fillCircle(ctx, x, y, radius);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue