From 8c1ab4c5756ac43d52bc8773f5e72dde90f79e77 Mon Sep 17 00:00:00 2001
From: Joris
Date: Tue, 5 Jul 2022 22:11:00 +0200
Subject: WIP
---
README.md | 6 +++++
public/index.html | 18 +------------
src/controls.ts | 8 +++---
src/main.ts | 7 +++++
src/model/size.ts | 8 ++++++
src/model/vec2.ts | 73 ++++++++++++++++++++++++++++++++++++++++++++++----
src/screen.ts | 11 +++++---
src/util/physics.ts | 40 ++++++++++++++++++++++++++++
src/view/colors.ts | 2 +-
src/view/scene.ts | 32 +++++++++++++++++-----
src/view/ship.ts | 76 +++++++++++++++++++++++++++++++++--------------------
tsconfig.json | 2 +-
12 files changed, 216 insertions(+), 67 deletions(-)
create mode 100644 src/model/size.ts
create mode 100644 src/util/physics.ts
diff --git a/README.md b/README.md
index 47c92d0..2b7f37e 100644
--- a/README.md
+++ b/README.md
@@ -7,3 +7,9 @@ nix develop --command bin/watch.sh
```
Then, open your browser at `http://localhost:8000`.
+
+# Todo
+
+[ ] Screen size equivalence
+[ ] Add rocks to avoid
+[ ] Destroy rocks with missiles
diff --git a/public/index.html b/public/index.html
index bcedaf9..32f4bc7 100644
--- a/public/index.html
+++ b/public/index.html
@@ -6,22 +6,6 @@
-
-
-
+
diff --git a/src/controls.ts b/src/controls.ts
index b575b3f..8ae0dbf 100644
--- a/src/controls.ts
+++ b/src/controls.ts
@@ -3,7 +3,7 @@ export interface Controls {
right: boolean,
down: boolean,
left: boolean,
- space: boolean,
+ spaceCount: number,
}
export let current = {
@@ -11,7 +11,7 @@ export let current = {
right: false,
down: false,
left: false,
- space: false,
+ spaceCount: 0,
}
document.addEventListener('keydown', event => {
@@ -31,8 +31,8 @@ function update(current: Controls, key: string, isDown: boolean): Controls {
return { ...current, down: isDown }
else if (key === 'ArrowLeft')
return { ...current, left: isDown }
- else if (key === ' ')
- return { ...current, space: isDown }
+ else if (key === ' ' && !isDown)
+ return { ...current, spaceCount: current.spaceCount + 1 }
else
return current
}
diff --git a/src/main.ts b/src/main.ts
index 53bc487..c235929 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -9,3 +9,10 @@ function loop(timestamp: number) {
}
window.requestAnimationFrame(loop)
+
+window.onresize = () => Scene.resize(
+ scene,
+ { width: window.innerWidth,
+ height: window.innerHeight
+ }
+)
diff --git a/src/model/size.ts b/src/model/size.ts
new file mode 100644
index 0000000..5e8a778
--- /dev/null
+++ b/src/model/size.ts
@@ -0,0 +1,8 @@
+export interface Size {
+ width: number,
+ height: number,
+}
+
+export function diagonal(size: Size): number {
+ return Math.sqrt(size.width ** 2 + size.height ** 2)
+}
diff --git a/src/model/vec2.ts b/src/model/vec2.ts
index 4bec53d..46ffc7e 100644
--- a/src/model/vec2.ts
+++ b/src/model/vec2.ts
@@ -1,15 +1,78 @@
+import * as Number from 'util/number'
+import * as Size from 'model/size'
+
export interface Vec2 {
x: number,
y: number,
}
-export function zero(): Vec2 {
- return {
- x: 0,
- y: 0,
- }
+export const zero: Vec2 = {
+ x: 0,
+ y: 0,
}
export function equals(v1: Vec2, v2: Vec2): boolean {
return v1.x === v2.x && v1.y === v2.y
}
+
+export function length(v: Vec2): number {
+ return Math.sqrt(v.x ** 2 + v.y ** 2)
+}
+
+export function normalize(v: Vec2): Vec2 {
+ if (v.x === 0 && v.y === 0) {
+ return zero
+ } else {
+ const im = 1 / length(v)
+ return {
+ x: v.x * im,
+ y: v.y * im
+ }
+ }
+}
+
+export function applyOnLength(f: (n: number) => number, v: Vec2): Vec2 {
+ return scale(normalize(v), f(length(v)))
+}
+
+export function sub(v1: Vec2, v2: Vec2): Vec2 {
+ return {
+ x: v1.x - v2.x,
+ y: v1.y - v2.y
+ }
+}
+
+export function div(v: Vec2, k: number): Vec2 {
+ return {
+ x: v.x / k,
+ y: v.y / k
+ }
+}
+
+export function add(v1: Vec2, v2: Vec2): Vec2 {
+ return {
+ x: v1.x + v2.x,
+ y: v1.y + v2.y
+ }
+}
+
+export function scale(v: Vec2, k: number): Vec2 {
+ return {
+ x: v.x * k,
+ y: v.y * k
+ }
+}
+
+export function clamp(v: Vec2, vMin: Vec2, vMax: Vec2): Vec2 {
+ return {
+ x: Number.clamp(v.x, vMin.x, vMax.x),
+ y: Number.clamp(v.y, vMin.y, vMax.y)
+ }
+}
+
+export function project(from: Size.Size, to: Size.Size, v: Vec2): Vec2 {
+ return {
+ x: v.x / from.width * to.width,
+ y: v.y / from.height * to.height
+ }
+}
diff --git a/src/screen.ts b/src/screen.ts
index 930534e..0567ad9 100644
--- a/src/screen.ts
+++ b/src/screen.ts
@@ -1,4 +1,9 @@
-let canvas = document.querySelector('canvas') as HTMLCanvasElement
+export let size = {
+ x: window.innerWidth,
+ y: window.innerHeight
+}
-export let width: number = canvas.width
-export let height: number = canvas.height
+window.onresize = () => {
+ size.x = window.innerWidth
+ size.y = window.innerHeight
+}
diff --git a/src/util/physics.ts b/src/util/physics.ts
new file mode 100644
index 0000000..cbc82f7
--- /dev/null
+++ b/src/util/physics.ts
@@ -0,0 +1,40 @@
+import * as Vec2 from 'model/vec2'
+
+interface AccParams {
+ dt: number,
+ speed: Vec2.Vec2,
+ dir: Vec2.Vec2
+}
+
+// max_acc = v_length(board_size) / 90000
+const maxAcc: number = 0.04
+const inertia: number = 200
+
+export function acc({ dt, speed, dir }: AccParams): Vec2.Vec2 {
+ const v = Vec2.applyOnLength((x) => x * maxAcc, dir)
+ return Vec2.div(Vec2.sub(Vec2.scale(v, dt), speed), inertia)
+}
+
+interface SpeedParams {
+ dt: number,
+ acc: Vec2.Vec2,
+ speed: Vec2.Vec2
+}
+
+export function speed({ dt, acc, speed }: SpeedParams): Vec2.Vec2 {
+ return Vec2.add(Vec2.scale(acc, dt), speed)
+}
+
+interface PosParams {
+ dt: number,
+ acc: Vec2.Vec2,
+ speed: Vec2.Vec2,
+ pos: Vec2.Vec2
+}
+
+export function pos({ dt, acc, speed, pos }: PosParams): Vec2.Vec2 {
+ return Vec2.add(
+ Vec2.scale(acc, (dt ** 2) / 2),
+ Vec2.add(Vec2.scale(speed, dt), pos)
+ )
+}
diff --git a/src/view/colors.ts b/src/view/colors.ts
index c663d25..725e981 100644
--- a/src/view/colors.ts
+++ b/src/view/colors.ts
@@ -1,4 +1,4 @@
-export let colors = {
+export const colors = {
blue: "#333388",
red: "#CC3333",
}
diff --git a/src/view/scene.ts b/src/view/scene.ts
index fe88c12..6d35edc 100644
--- a/src/view/scene.ts
+++ b/src/view/scene.ts
@@ -1,35 +1,53 @@
import * as Ship from 'view/ship'
import * as Colors from 'view/colors'
-import * as Screen from 'screen'
+import * as Vec2 from 'model/vec2'
+import * as Size from 'model/size'
export interface State {
+ canvas: HTMLCanvasElement,
context: CanvasRenderingContext2D,
timestamp: number,
ship: Ship.State,
+ windowSize: Size.Size,
}
export function init(): State {
- let canvas = document.querySelector('canvas') as HTMLCanvasElement
- let context = canvas.getContext("2d") as CanvasRenderingContext2D
+ const canvas = document.querySelector('canvas') as HTMLCanvasElement
+ const context = canvas.getContext("2d") as CanvasRenderingContext2D
+ const windowSize = {
+ width: window.innerWidth,
+ height: window.innerHeight
+ }
+ canvas.width = windowSize.width
+ canvas.height = windowSize.height
return {
+ canvas,
context,
timestamp: 0,
- ship: Ship.init(),
+ ship: Ship.init(windowSize),
+ windowSize
}
}
export function update(state: State, timestamp: number) {
- let delta = timestamp - state.timestamp
+ const dt = timestamp - state.timestamp
state.timestamp = timestamp
- Ship.update(state.ship, state.timestamp, delta)
+ Ship.update(state.ship, dt, state.windowSize)
+}
+
+export function resize(state: State, size: Size.Size) {
+ Ship.project(state.ship, state.windowSize, size)
+ state.windowSize = size
+ state.canvas.width = size.width
+ state.canvas.height = size.height
}
export function view(state: State) {
// Clear
state.context.fillStyle = Colors.colors.blue
- state.context.fillRect(0, 0, Screen.width, Screen.height)
+ state.context.fillRect(0, 0, state.windowSize.width, state.windowSize.height)
Ship.view(state.context, state.ship)
}
diff --git a/src/view/ship.ts b/src/view/ship.ts
index 92590d3..5a8b86f 100644
--- a/src/view/ship.ts
+++ b/src/view/ship.ts
@@ -1,51 +1,64 @@
import * as Controls from 'controls'
import * as Vec2 from 'model/vec2'
+import * as Size from 'model/size'
import * as Number from 'util/number'
-import * as Screen from 'screen'
import * as Colors from 'view/colors'
+import * as Physics from 'util/physics'
-export const radius: number = 30
-export const fireDelay: number = 200
export const missileWidth: number = 10
export const missileHeight: number = 5
export interface State {
+ speed: Vec2.Vec2,
pos: Vec2.Vec2,
+ radius: number,
missiles: Array,
lastFired: number,
}
-export function init(): State {
+export function init(windowSize: Size.Size): State {
return {
+ speed: { x: 0, y: 0 },
pos: {
- x: Screen.width / 6,
- y: Screen.height / 2,
+ x: windowSize.width / 6,
+ y: windowSize.height / 2,
},
+ radius: Size.diagonal(windowSize) / 30,
missiles: [],
lastFired: 0,
}
}
-export function update(state: State, timestamp: number, delta: number) {
- move(state, delta)
- updateMissiles(state, timestamp, delta)
+export function update(
+ state: State,
+ dt: number,
+ windowSize: Size.Size
+) {
+ move(state, dt, windowSize)
+ updateMissiles(state, dt, windowSize)
}
-function move(state: State, delta: number) {
- let dir = controlsDir(Controls.current)
+function move(state: State, dt: number, windowSize: Size.Size) {
+ const dir = controlsDir(Controls.current)
- if (!Vec2.equals(dir, Vec2.zero())) {
- let teta = Math.atan2(dir.y, dir.x)
- state.pos.x += Math.cos(teta) * delta / 3
- state.pos.y += Math.sin(teta) * delta / 3
- }
+ const unitDt = 0.5
+ const steps = Math.round(dt / unitDt)
+
+ const acc = Physics.acc({ dt, speed: state.speed, dir })
+ Array(steps).fill(1).forEach(_ => {
+ state.speed = Physics.speed({ dt: unitDt, acc, speed: state.speed })
+ state.pos = Physics.pos({ dt: unitDt, acc, speed: state.speed, pos: state.pos })
+ })
- state.pos.x = Number.clamp(state.pos.x, radius, Screen.width - radius)
- state.pos.y = Number.clamp(state.pos.y, radius, Screen.height - radius)
+ state.pos = Vec2.clamp(
+ state.pos,
+ { x: state.radius, y: state.radius },
+ { x: windowSize.width - state.radius, y: windowSize.height - state.radius }
+ )
}
function controlsDir(c: Controls.Controls): Vec2.Vec2 {
- let dir = Vec2.zero()
+ let dir = { x: 0, y: 0 }
if (c.up && !c.down)
dir.y = -1
@@ -57,26 +70,31 @@ function controlsDir(c: Controls.Controls): Vec2.Vec2 {
else if (c.left && !c.right)
dir.x = -1
- return dir
+ return Vec2.normalize(dir)
}
-function updateMissiles(state: State, timestamp: number, delta: number) {
- if (Controls.current.space && state.lastFired + fireDelay < timestamp) {
- state.missiles.push({x: state.pos.x + radius, y: state.pos.y})
- state.lastFired = timestamp
+function updateMissiles(state: State, dt: number, windowSize: Size.Size) {
+ if (Controls.current.spaceCount > state.lastFired) {
+ state.missiles.push({x: state.pos.x + state.radius, y: state.pos.y})
+ state.lastFired = Controls.current.spaceCount
}
state.missiles = state.missiles
- .map(missile => ({ ...missile, x: missile.x + delta}))
- .filter(missile => missile.x < Screen.width)
+ .map(missile => ({ ...missile, x: missile.x + dt}))
+ .filter(missile => missile.x < windowSize.width)
+}
+
+export function project(state: State, from: Size.Size, to: Size.Size) {
+ state.pos = Vec2.project(from, to, state.pos)
+ state.radius = state.radius / Size.diagonal(from) * Size.diagonal(to)
}
export function view(context: CanvasRenderingContext2D, state: State) {
context.fillStyle = Colors.colors.red
context.beginPath()
- context.moveTo(state.pos.x - radius, state.pos.y - radius)
- context.lineTo(state.pos.x + radius, state.pos.y)
- context.lineTo(state.pos.x - radius, state.pos.y + radius)
+ context.moveTo(state.pos.x - state.radius, state.pos.y - state.radius)
+ context.lineTo(state.pos.x + state.radius, state.pos.y)
+ context.lineTo(state.pos.x - state.radius, state.pos.y + state.radius)
context.closePath()
context.fill()
diff --git a/tsconfig.json b/tsconfig.json
index 3e7f32b..85c0a86 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"module": "amd",
- "target": "es5",
+ "target": "es2016",
"baseUrl": "src",
"outFile": "public/main.js",
"noImplicitAny": true,
--
cgit v1.2.3