UNPKG

react-ts-asteroids

Version:
437 lines (436 loc) 15.7 kB
var et = Object.defineProperty; var ot = (n, t, i) => t in n ? et(n, t, { enumerable: !0, configurable: !0, writable: !0, value: i }) : n[t] = i; var s = (n, t, i) => ot(n, typeof t != "symbol" ? t + "" : t, i); import { jsxs as M, jsx as S } from "react/jsx-runtime"; import { useRef as y, useState as x, useMemo as st, useCallback as u, useEffect as v } from "react"; function X(n, t, i) { return { x: (n.x - t.x) * Math.cos(i) - (n.y - t.y) * Math.sin(i) + t.x, y: (n.x - t.x) * Math.sin(i) + (n.y - t.y) * Math.cos(i) + t.y }; } function h(n, t) { return Math.random() * (t - n + 1) + n; } function Q(n, t, i, a) { let p = h(n, t); for (; p > i && p < a; ) p = Math.random() * (t - n + 1) + n; return p; } function nt(n, t) { const i = []; for (let a = 0; a < n; a++) i[a] = { x: (-Math.sin(360 / n * a * Math.PI / 180) + Math.random() * 0.2) * t, y: (-Math.cos(360 / n * a * Math.PI / 180) + Math.random() * 0.2) * t }; return i; } class rt { constructor(t) { s(this, "position"); s(this, "velocity"); s(this, "rotation"); s(this, "radius"); s(this, "delete"); const i = X({ x: 0, y: -20 }, { x: 0, y: 0 }, t.ship.rotation * Math.PI / 180); this.position = { x: t.ship.position.x + i.x, y: t.ship.position.y + i.y }, this.rotation = t.ship.rotation, this.velocity = { x: i.x / 2, y: i.y / 2 }, this.radius = 2; } destroy() { this.delete = !0; } render(t) { this.position.x += this.velocity.x, this.position.y += this.velocity.y, (this.position.x < 0 || this.position.y < 0 || this.position.x > t.screen.width || this.position.y > t.screen.height) && this.destroy(); const i = t.context; i.save(), i.translate(this.position.x, this.position.y), i.rotate(this.rotation * Math.PI / 180), i.fillStyle = t.colors.text, i.lineWidth = 0.5, i.beginPath(), i.arc(0, 0, 2, 0, 2 * Math.PI), i.closePath(), i.fill(), i.restore(); } } class L { // Required by GameObject interface constructor(t) { s(this, "position"); s(this, "velocity"); s(this, "radius"); s(this, "lifeSpan"); s(this, "inertia"); s(this, "delete"); s(this, "rotation"); this.position = t.position, this.velocity = t.velocity, this.radius = t.size, this.lifeSpan = t.lifeSpan, this.inertia = 0.98, this.rotation = 0; } destroy() { this.delete = !0; } render(t) { this.position.x += this.velocity.x, this.position.y += this.velocity.y, this.velocity.x *= this.inertia, this.velocity.y *= this.inertia, this.radius -= 0.1, this.radius < 0.1 && (this.radius = 0.1), this.lifeSpan-- < 0 && this.destroy(); const i = t.context; i.save(), i.translate(this.position.x, this.position.y), i.fillStyle = t.colors.text, i.lineWidth = 2, i.beginPath(), i.moveTo(0, -this.radius), i.arc(0, 0, this.radius, 0, 2 * Math.PI), i.closePath(), i.fill(), i.restore(); } } class ht { constructor(t) { s(this, "position"); s(this, "velocity"); s(this, "rotation"); s(this, "rotationSpeed"); s(this, "speed"); s(this, "inertia"); s(this, "radius"); s(this, "lastShot"); s(this, "create"); s(this, "onDie"); s(this, "delete"); this.position = t.position, this.velocity = { x: 0, y: 0 }, this.rotation = 0, this.rotationSpeed = 6, this.speed = 0.15, this.inertia = 0.99, this.radius = 20, this.lastShot = 0, this.create = t.create, this.onDie = t.onDie; } destroy() { this.delete = !0, this.onDie(); for (let t = 0; t < 60; t++) { const i = new L({ lifeSpan: h(60, 100), size: h(1, 4), position: { x: this.position.x + h(-this.radius / 4, this.radius / 4), y: this.position.y + h(-this.radius / 4, this.radius / 4) }, velocity: { x: h(-1.5, 1.5), y: h(-1.5, 1.5) } }); this.create(i, "particles"); } } rotate(t) { t === "LEFT" && (this.rotation -= this.rotationSpeed), t === "RIGHT" && (this.rotation += this.rotationSpeed); } accelerate() { this.velocity.x -= Math.sin(-this.rotation * Math.PI / 180) * this.speed, this.velocity.y -= Math.cos(-this.rotation * Math.PI / 180) * this.speed; const t = X({ x: 0, y: -10 }, { x: 0, y: 0 }, (this.rotation - 180) * Math.PI / 180), i = new L({ lifeSpan: h(20, 40), size: h(1, 3), position: { x: this.position.x + t.x + h(-2, 2), y: this.position.y + t.y + h(-2, 2) }, velocity: { x: t.x / h(3, 5), y: t.y / h(3, 5) } }); this.create(i, "particles"); } render(t) { if (t.keys.up && this.accelerate(), t.keys.left && this.rotate("LEFT"), t.keys.right && this.rotate("RIGHT"), t.keys.space && Date.now() - this.lastShot > 300) { const a = new rt({ ship: this }); this.create(a, "bullets"), this.lastShot = Date.now(); } this.position.x += this.velocity.x, this.position.y += this.velocity.y, this.velocity.x *= this.inertia, this.velocity.y *= this.inertia, this.rotation >= 360 && (this.rotation -= 360), this.rotation < 0 && (this.rotation += 360), this.position.x > t.screen.width ? this.position.x = 0 : this.position.x < 0 && (this.position.x = t.screen.width), this.position.y > t.screen.height ? this.position.y = 0 : this.position.y < 0 && (this.position.y = t.screen.height); const i = t.context; i.save(), i.translate(this.position.x, this.position.y), i.rotate(this.rotation * Math.PI / 180), i.strokeStyle = t.colors.text, i.fillStyle = t.colors.background, i.lineWidth = 2, i.beginPath(), i.moveTo(0, -15), i.lineTo(10, 10), i.lineTo(5, 7), i.lineTo(-5, 7), i.lineTo(-10, 10), i.closePath(), i.fill(), i.stroke(), i.restore(); } } class F { constructor(t) { s(this, "position"); s(this, "velocity"); s(this, "rotation"); s(this, "rotationSpeed"); s(this, "radius"); s(this, "score"); s(this, "create"); s(this, "addScore"); s(this, "vertices"); s(this, "delete"); this.position = t.position, this.velocity = { x: h(-1.5, 1.5), y: h(-1.5, 1.5) }, this.rotation = 0, this.rotationSpeed = h(-1, 1), this.radius = t.size, this.score = 80 / this.radius * 5, this.create = t.create, this.addScore = t.addScore, this.vertices = nt(8, t.size); } destroy() { this.addScore(this.score); for (let t = 0; t < this.radius; t++) { const i = new L({ lifeSpan: h(60, 100), size: h(1, 3), position: { x: this.position.x + h(-this.radius / 4, this.radius / 4), y: this.position.y + h(-this.radius / 4, this.radius / 4) }, velocity: { x: h(-1.5, 1.5), y: h(-1.5, 1.5) } }); this.create(i, "particles"); } if (this.radius > 10) for (let t = 0; t < 2; t++) { const i = new F({ size: this.radius / 2, position: { x: h(-10, 20) + this.position.x, y: h(-10, 20) + this.position.y }, create: this.create, addScore: this.addScore }); this.create(i, "asteroids"); } } render(t) { this.position.x += this.velocity.x, this.position.y += this.velocity.y, this.rotation += this.rotationSpeed, this.rotation >= 360 && (this.rotation -= 360), this.rotation < 0 && (this.rotation += 360), this.position.x > t.screen.width + this.radius ? this.position.x = -this.radius : this.position.x < -this.radius && (this.position.x = t.screen.width + this.radius), this.position.y > t.screen.height + this.radius ? this.position.y = -this.radius : this.position.y < -this.radius && (this.position.y = t.screen.height + this.radius); const i = t.context; i.save(), i.translate(this.position.x, this.position.y), i.rotate(this.rotation * Math.PI / 180), i.strokeStyle = t.colors.text, i.lineWidth = 2, i.beginPath(), i.moveTo(0, -this.radius); for (let a = 1; a < this.vertices.length; a++) i.lineTo(this.vertices[a].x, this.vertices[a].y); i.closePath(), i.stroke(), i.restore(); } } const g = { LEFT: 37, RIGHT: 39, UP: 38, A: 65, D: 68, W: 87, SPACE: 32 }, ct = (n, t, i, a) => ({ screen: n, context: t, keys: i, colors: a }), pt = ({ darkMode: n = !1 }) => { const t = y(null), i = y(null), a = y([]), p = y([]), k = y([]), z = y([]), T = y(), [c, Z] = x({ width: window.innerWidth, height: window.innerHeight, ratio: window.devicePixelRatio || 1 }), [d, K] = x(null), [W, _] = x({ left: 0, right: 0, up: 0, down: 0, space: 0 }), [B] = x(3), [m, O] = x(0), [tt, it] = x(() => Number(localStorage.topscore) || 0), [b, N] = x(!1), U = y(!0), C = y({ inGame: !1 }), [q, V] = x(!1), f = st(() => ({ text: n ? "#ffffff" : "#000000", background: n ? "#000000" : "#ffffff", border: n ? "#ffffff" : "#000000" }), [n]), E = u(() => { if (i.current) { const { width: e, height: o } = i.current.getBoundingClientRect(); Z({ width: e, height: o, ratio: window.devicePixelRatio || 1 }); } }, []), A = u((e, o) => { const { keyCode: r } = o; _((l) => ({ ...l, left: r === g.LEFT || r === g.A ? e : l.left, right: r === g.RIGHT || r === g.D ? e : l.right, up: r === g.UP || r === g.W ? e : l.up, space: r === g.SPACE ? e : l.space })); }, []), P = u((e, o) => { o === "ship" && a.current.push(e), o === "asteroids" && p.current.push(e), o === "bullets" && k.current.push(e), o === "particles" && z.current.push(e); }, []), Y = u((e) => { C.current.inGame && O((o) => o + e); }, []); v(() => { C.current.inGame = b; }, [b]), v(() => { }, [m]); const $ = u(() => { C.current.inGame = !1, N(!1), it((e) => { const o = Math.max(m, e); return o > e && (localStorage.topscore = o), o; }); }, [m]), j = u((e) => { const o = (r) => { Y(r); }; for (let r = 0; r < e; r++) { const l = new F({ size: 80, position: { x: Q(0, c.width, c.width / 2 - 60, c.width / 2 + 60), y: Q(0, c.height, c.height / 2 - 60, c.height / 2 + 60) }, create: P, addScore: o // Use the wrapped version }); P(l, "asteroids"); } }, [c.width, c.height, P, Y]), D = u(() => { var l, w; C.current.inGame = !0, N(!0), O(0), a.current = [], p.current = [], k.current = [], z.current = []; const e = ((l = i.current) == null ? void 0 : l.getBoundingClientRect().width) ?? c.width, o = ((w = i.current) == null ? void 0 : w.getBoundingClientRect().height) ?? c.height, r = new ht({ position: { x: e / 2, y: o / 2 }, create: P, onDie: $ }); P(r, "ship"), j(B); }, [B, P, $, j, c.width, c.height]), J = u((e, o) => { const r = e.position.x - o.position.x, l = e.position.y - o.position.y; return Math.sqrt(r * r + l * l) < e.radius + o.radius; }, []), I = u((e) => { e.forEach((r) => { !r.delete && d && r.render(ct(c, d, W, f)); }); const o = e.filter((r) => !r.delete); o.length !== e.length && (e.length = 0, e.push(...o)); }, [c, d, W, f]), G = u((e, o) => { let r = e.length - 1, l; for (r; r > -1; --r) for (l = o.length - 1, l; l > -1; --l) { const w = e[r], R = o[l]; J(w, R) && (w.destroy(), R.destroy(), w.delete = !0, R.delete = !0); } }, [J]), H = u(() => { d && (d.save(), d.scale(c.ratio, c.ratio), d.fillStyle = f.background, d.globalAlpha = 0.4, d.fillRect(0, 0, c.width, c.height), d.globalAlpha = 1, b && (I(a.current), I(p.current), I(k.current), I(z.current), k.current.length > 0 && p.current.length > 0 && G(k.current, p.current), a.current.length > 0 && p.current.length > 0 && G(a.current, p.current)), d.restore(), T.current = requestAnimationFrame(H)); }, [d, c, b, I, G]); return v(() => { E(); const e = new ResizeObserver(E); return i.current && e.observe(i.current), () => { e.disconnect(); }; }, [E]), v(() => { const e = (r) => A(0, r), o = (r) => A(1, r); return window.addEventListener("keyup", e), window.addEventListener("keydown", o), () => { window.removeEventListener("keyup", e), window.removeEventListener("keydown", o); }; }, [A]), v(() => { var o; const e = (o = t.current) == null ? void 0 : o.getContext("2d"); e && K(e); }, []), v(() => { d && U.current && (U.current = !1, D()); }, [d, D]), v(() => { if (d) return T.current = requestAnimationFrame(H), () => { T.current && cancelAnimationFrame(T.current); }; }, [d, H]), v(() => { }, [b, m]), /* @__PURE__ */ M( "div", { ref: i, id: "react-ts-asteroids", style: { height: "100%", width: "100%", position: "relative", overflow: "hidden", color: f.text, padding: 0 }, children: [ !b && /* @__PURE__ */ M("div", { className: "endgame", style: { position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)", padding: "16px", zIndex: 1, textAlign: "center" }, children: [ /* @__PURE__ */ S("p", { children: "Game over!" }), /* @__PURE__ */ M("p", { children: [ "Score: ", m ] }), /* @__PURE__ */ S( "button", { onClick: D, onMouseEnter: () => V(!0), onMouseLeave: () => V(!1), style: { border: `4px solid ${f.border}`, backgroundColor: q ? f.text : "transparent", color: q ? f.background : f.text, fontSize: "20px", padding: "10px 20px", margin: "10px", cursor: "pointer" }, children: "Try again?" } ) ] }), /* @__PURE__ */ S("div", { style: { position: "absolute", top: 15, left: 20, zIndex: 1, fontSize: 20 }, children: /* @__PURE__ */ M("div", { className: "score current-score", children: [ "Score: ", m ] }) }), /* @__PURE__ */ S("div", { style: { position: "absolute", top: 15, right: 20, zIndex: 1, fontSize: 20 }, children: /* @__PURE__ */ M("div", { className: "score top-score", children: [ "Top Score: ", tt ] }) }), /* @__PURE__ */ S("div", { style: { position: "absolute", top: 15, left: "50%", transform: "translate(-50%, 0)", zIndex: 1, fontSize: 11, textAlign: "center", lineHeight: 1.6 }, children: /* @__PURE__ */ M("span", { className: "controls", children: [ "Use [A][S][W][D] or [←][↑][↓][→] to MOVE", /* @__PURE__ */ S("br", {}), "Use [SPACE] to SHOOT" ] }) }), /* @__PURE__ */ S( "canvas", { ref: t, id: "react-ts-asteroids--canvas", width: c.width * c.ratio, height: c.height * c.ratio, style: { width: "100%", height: "100%", display: "block", backgroundColor: f.background, position: "absolute", top: 0, bottom: 0, left: 0, right: 0 } } ) ] } ); }; export { pt as Asteroids };