mirror of
https://github.com/Sheldan/canvas.git
synced 2026-01-01 06:49:08 +00:00
survivors: adding first version
This commit is contained in:
24
.github/workflows/build.yml
vendored
24
.github/workflows/build.yml
vendored
@@ -29,59 +29,65 @@ jobs:
|
||||
with:
|
||||
node-version: 21
|
||||
- name: Orbits Install dependencies
|
||||
run: npm install
|
||||
run: npm ci
|
||||
working-directory: orbits
|
||||
- name: Orbits Build
|
||||
run: npx vite build
|
||||
working-directory: orbits
|
||||
- name: recBubbles Install dependencies
|
||||
run: npm install
|
||||
run: npm ci
|
||||
working-directory: recBubbles
|
||||
- name: recBubbles Build
|
||||
run: npx vite build
|
||||
working-directory: recBubbles
|
||||
- name: balls Install dependencies
|
||||
run: npm install
|
||||
run: npm ci
|
||||
working-directory: balls
|
||||
- name: balls Build
|
||||
run: npx vite build
|
||||
working-directory: balls
|
||||
- name: fireWorks Install dependencies
|
||||
run: npm install
|
||||
run: npm ci
|
||||
working-directory: fireWorks
|
||||
- name: fireWorks Build
|
||||
run: npx vite build
|
||||
working-directory: fireWorks
|
||||
- name: bubbles Install dependencies
|
||||
run: npm install
|
||||
run: npm ci
|
||||
working-directory: bubbles
|
||||
- name: bubbles Build
|
||||
run: npx vite build
|
||||
working-directory: bubbles
|
||||
- name: circleBs Install dependencies
|
||||
run: npm install
|
||||
run: npm ci
|
||||
working-directory: circleBs
|
||||
- name: circleBs Build
|
||||
run: npx vite build
|
||||
working-directory: circleBs
|
||||
- name: clusterFilter Install dependencies
|
||||
run: npm install
|
||||
run: npm ci
|
||||
working-directory: clusterFilter
|
||||
- name: clusterFilter Build
|
||||
run: npx vite build
|
||||
working-directory: clusterFilter
|
||||
- name: collatzConjecture Install dependencies
|
||||
run: npm install
|
||||
run: npm ci
|
||||
working-directory: collatzConjecture
|
||||
- name: collatzConjecture Build
|
||||
run: npx vite build
|
||||
working-directory: collatzConjecture
|
||||
- name: dotLines Install dependencies
|
||||
run: npm install
|
||||
run: npm ci
|
||||
working-directory: dotLines
|
||||
- name: dotLines Build
|
||||
run: npx vite build
|
||||
working-directory: dotLines
|
||||
- name: survivors install dependencies
|
||||
run: npm ci
|
||||
working-directory: absurd-survivors
|
||||
- name: survivors build
|
||||
run: npx vite build
|
||||
working-directory: absurd-survivors
|
||||
- name: Move index
|
||||
run: cp index.html dist/
|
||||
- name: Move overview images
|
||||
|
||||
24
absurd-survivors/.gitignore
vendored
Normal file
24
absurd-survivors/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
13
absurd-survivors/index.html
Normal file
13
absurd-survivors/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html class="no-js" lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>overtuned survivors</title>
|
||||
<meta name="description" content="">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas" style="display: block; background-color: black"></canvas>
|
||||
<script type="module" src="src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
1088
absurd-survivors/package-lock.json
generated
Normal file
1088
absurd-survivors/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
absurd-survivors/package.json
Normal file
16
absurd-survivors/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "overtuned-survivors",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"canvas-common": "file:../canvas-common",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "^7.1.2"
|
||||
}
|
||||
}
|
||||
144
absurd-survivors/src/Enemies.ts
Normal file
144
absurd-survivors/src/Enemies.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import type {Acting, Drawable, Healthy, Moving, Shooting} from "./interfaces.ts";
|
||||
import {drawDot, moveInDirectionOf} from "./utils.ts";
|
||||
import {Vector} from "./base.ts";
|
||||
import {World} from "./World.ts";
|
||||
import type {Projectile} from "./projectile.ts";
|
||||
import {HomingProjectile, StraightProjectile} from "./projectile.ts";
|
||||
|
||||
export abstract class Enemy implements Moving, Drawable, Acting, Healthy {
|
||||
protected _position: Vector;
|
||||
protected speed: number;
|
||||
protected world: World;
|
||||
protected status: EnemyStatus = new EnemyStatus(10);
|
||||
|
||||
constructor(position: Vector) {
|
||||
this._position = position;
|
||||
}
|
||||
|
||||
draw(ctx: CanvasRenderingContext2D) {
|
||||
}
|
||||
|
||||
act() {
|
||||
this.move()
|
||||
}
|
||||
|
||||
move() {
|
||||
}
|
||||
|
||||
|
||||
getPosition(): Vector {
|
||||
return this._position;
|
||||
}
|
||||
|
||||
takeDamage(damage: number) {
|
||||
this.status.health -= damage;
|
||||
if(this.status.dead) {
|
||||
this.world.removeEnemy(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class BasicEnemy extends Enemy {
|
||||
|
||||
constructor(position: Vector) {
|
||||
super(position);
|
||||
}
|
||||
|
||||
protected size: number;
|
||||
protected color: string;
|
||||
protected impactDamage: number;
|
||||
protected impactCooldown: number = 0;
|
||||
protected impactInterval: number = 60;
|
||||
|
||||
draw(ctx: CanvasRenderingContext2D) {
|
||||
drawDot(this._position, this.size, this.color, ctx)
|
||||
}
|
||||
|
||||
|
||||
move() {
|
||||
this._position = moveInDirectionOf(this._position, this.world.player.position, this.speed)
|
||||
}
|
||||
|
||||
act() {
|
||||
super.act();
|
||||
if(this._position.distanceTo(this.world.player.position) < this.size && this.impactCooldown <= 0) {
|
||||
this.world.player.takeDamage(this.impactDamage)
|
||||
this.impactCooldown = this.impactInterval;
|
||||
}
|
||||
this.impactCooldown -= 1;
|
||||
}
|
||||
|
||||
static generateBasicEnemy(world: World, position?: Vector): BasicEnemy {
|
||||
if(position === undefined) {
|
||||
position = new Vector(250, 250)
|
||||
}
|
||||
let basicEnemy = new BasicEnemy(position);
|
||||
basicEnemy.size = 5;
|
||||
basicEnemy.world = world;
|
||||
basicEnemy.speed = 0.5;
|
||||
basicEnemy.color = 'orange'
|
||||
basicEnemy.impactDamage = 2;
|
||||
return basicEnemy;
|
||||
}
|
||||
}
|
||||
|
||||
export class ShootingEnemy extends BasicEnemy implements Shooting {
|
||||
private shootCooldown: number = 0;
|
||||
private shootInterval: number;
|
||||
private projectiles: Projectile[] = []
|
||||
|
||||
constructor(position: Vector) {
|
||||
super(position);
|
||||
}
|
||||
|
||||
removeProjectile(projectile: Projectile) {
|
||||
this.projectiles = this.projectiles.filter(item => item !== projectile)
|
||||
}
|
||||
|
||||
act() {
|
||||
super.act();
|
||||
if(this.shootCooldown <= 0) {
|
||||
this.createProjectile()
|
||||
this.shootCooldown = this.shootInterval;
|
||||
}
|
||||
this.shootCooldown -= 1;
|
||||
}
|
||||
|
||||
createProjectile() {
|
||||
let projectile = StraightProjectile.createStraightProjectile(this.world, this._position, this.world.player.position, this)
|
||||
this.projectiles.push(projectile)
|
||||
return projectile
|
||||
}
|
||||
|
||||
static generateShootingEnemy(world: World, position?: Vector) {
|
||||
if(position === undefined) {
|
||||
position = new Vector(250, 250)
|
||||
}
|
||||
let basicEnemy = new ShootingEnemy(position);
|
||||
basicEnemy.size = 5;
|
||||
basicEnemy.world = world;
|
||||
basicEnemy.speed = 0.5;
|
||||
basicEnemy.color = 'green'
|
||||
basicEnemy.impactDamage = 2;
|
||||
basicEnemy.shootInterval = 100
|
||||
return basicEnemy;
|
||||
}
|
||||
}
|
||||
|
||||
export class EnemyStatus {
|
||||
constructor(private _health: number) {
|
||||
}
|
||||
|
||||
|
||||
get health(): number {
|
||||
return this._health;
|
||||
}
|
||||
|
||||
get dead(): boolean {
|
||||
return this._health <= 0;
|
||||
}
|
||||
|
||||
set health(value: number) {
|
||||
this._health = value;
|
||||
}
|
||||
}
|
||||
116
absurd-survivors/src/Player.ts
Normal file
116
absurd-survivors/src/Player.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import type {Acting, Drawable, Healthy, Weapon} from "./interfaces.ts";
|
||||
import {Vector} from "./base.ts";
|
||||
import {drawDot} from "./utils.ts";
|
||||
|
||||
export class Player implements Drawable, Acting, Healthy {
|
||||
private _position: Vector;
|
||||
private _stats: Stats;
|
||||
private _color: string;
|
||||
private _status: Status;
|
||||
private _weapons: [Weapon] = []
|
||||
|
||||
// temp
|
||||
private _speed: Vector;
|
||||
|
||||
|
||||
constructor(position: Vector) {
|
||||
this._position = position;
|
||||
}
|
||||
|
||||
draw(ctx: CanvasRenderingContext2D) {
|
||||
drawDot(this.position, this._stats.size, this._color, ctx)
|
||||
this._weapons.forEach(weapon => weapon.draw(ctx))
|
||||
}
|
||||
|
||||
public static generatePlayer(): Player {
|
||||
let player = new Player(new Vector(500, 500));
|
||||
player._color = 'blue';
|
||||
player._stats = Stats.defaultPlayerStats();
|
||||
player._speed = new Vector(0, 0)
|
||||
player._status = new Status(10);
|
||||
return player;
|
||||
}
|
||||
|
||||
addWeapon(weapon: Weapon) {
|
||||
this._weapons.push(weapon)
|
||||
}
|
||||
|
||||
move(direction: Vector) {
|
||||
this._position = this.position.add(direction)
|
||||
}
|
||||
|
||||
takeDamage(damage: number) {
|
||||
this._status.health -= damage;
|
||||
}
|
||||
|
||||
get health(): number {
|
||||
return this._status.health;
|
||||
}
|
||||
|
||||
get position(): Vector {
|
||||
return this._position;
|
||||
}
|
||||
|
||||
get color(): string {
|
||||
return this._color;
|
||||
}
|
||||
|
||||
get stats(): Stats {
|
||||
return this._stats;
|
||||
}
|
||||
|
||||
get speed(): Vector {
|
||||
return this._speed;
|
||||
}
|
||||
|
||||
act() {
|
||||
this._weapons.forEach(weapon => weapon.act())
|
||||
}
|
||||
}
|
||||
|
||||
export class Status {
|
||||
constructor(private _health: number) {
|
||||
}
|
||||
|
||||
|
||||
get health(): number {
|
||||
return this._health;
|
||||
}
|
||||
|
||||
|
||||
set health(value: number) {
|
||||
this._health = value;
|
||||
}
|
||||
}
|
||||
|
||||
export class Stats {
|
||||
constructor(private _speed: number,
|
||||
private _size: number,
|
||||
private _health: number) {
|
||||
}
|
||||
|
||||
get speed(): number {
|
||||
return this._speed;
|
||||
}
|
||||
|
||||
set speed(value: number) {
|
||||
this._speed = value;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this._size;
|
||||
}
|
||||
|
||||
set size(value: number) {
|
||||
this._size = value;
|
||||
}
|
||||
|
||||
|
||||
get health(): number {
|
||||
return this._health;
|
||||
}
|
||||
|
||||
public static defaultPlayerStats(): Stats {
|
||||
return new Stats(2, 5, 10);
|
||||
}
|
||||
}
|
||||
70
absurd-survivors/src/World.ts
Normal file
70
absurd-survivors/src/World.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import {Enemy} from "./Enemies.ts";
|
||||
import type {Player} from "./Player.ts";
|
||||
import {Player} from "./Player.ts";
|
||||
import {Projectile} from "./projectile.ts";
|
||||
import {Vector} from "./base.ts";
|
||||
import type {Moving} from "./interfaces.ts";
|
||||
|
||||
export class World {
|
||||
private _enemies: [Enemy] = [];
|
||||
private _projectiles: [Projectile] = [];
|
||||
private _player: Player;
|
||||
private _ctx: CanvasRenderingContext2D;
|
||||
|
||||
|
||||
constructor(player: Player, ctx: CanvasRenderingContext2D) {
|
||||
this._player = player;
|
||||
this._ctx = ctx;
|
||||
}
|
||||
|
||||
enemiesAct() {
|
||||
this._enemies.forEach(enemy => enemy.act())
|
||||
this._projectiles.forEach(projectile => projectile.act())
|
||||
}
|
||||
|
||||
draw() {
|
||||
this._enemies.forEach(enemy => enemy.draw(this._ctx))
|
||||
this._projectiles.forEach(projectile => projectile.draw(this._ctx))
|
||||
this._player.draw(this._ctx);
|
||||
}
|
||||
|
||||
addProjectile(projectile: Projectile) {
|
||||
this._projectiles.push(projectile)
|
||||
}
|
||||
|
||||
removeProjectile(projectile: Projectile) {
|
||||
this._projectiles = this._projectiles.filter(item => item !== projectile)
|
||||
}
|
||||
|
||||
removeEnemy(enemy: Enemy) {
|
||||
this._enemies = this._enemies.filter(item => item !== enemy)
|
||||
}
|
||||
|
||||
|
||||
get enemies(): [Enemy] {
|
||||
return this._enemies;
|
||||
}
|
||||
|
||||
addEnemy(enemy: Enemy) {
|
||||
this._enemies.push(enemy)
|
||||
}
|
||||
|
||||
getClosestTargetTo(point: Vector): [number, Moving | undefined] | undefined {
|
||||
let currentTarget;
|
||||
let currentDistance = Number.MAX_SAFE_INTEGER;
|
||||
this._enemies.forEach(enemy => {
|
||||
let distance = point.distanceTo(enemy.getPosition());
|
||||
if(distance < currentDistance) {
|
||||
currentDistance = distance;
|
||||
currentTarget = enemy
|
||||
}
|
||||
})
|
||||
if(currentTarget) {
|
||||
return [currentDistance, currentTarget];
|
||||
}
|
||||
}
|
||||
|
||||
get player(): Player {
|
||||
return this._player;
|
||||
}
|
||||
}
|
||||
59
absurd-survivors/src/base.ts
Normal file
59
absurd-survivors/src/base.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
export class Vector {
|
||||
|
||||
constructor(private _x: number, private _y: number) {
|
||||
}
|
||||
|
||||
static createVector(tip: Vector, shaft: Vector): Vector {
|
||||
return new Vector(tip.x - shaft.x, tip.y - shaft.y);
|
||||
}
|
||||
|
||||
normalize(): Vector {
|
||||
let length = this.vecLength();
|
||||
return new Vector(this.x / length, this.y / length)
|
||||
}
|
||||
|
||||
vecLength(): number {
|
||||
return Math.sqrt(this.x * this.x + this.y * this.y);
|
||||
}
|
||||
|
||||
distanceTo(point: Vector): number {
|
||||
return Math.sqrt(Math.pow(this.x - point.x, 2) + Math.pow(this.y - point.y, 2));
|
||||
}
|
||||
|
||||
add(vec: Vector): Vector {
|
||||
return new Vector(this._x + vec._x, this._y + vec._y)
|
||||
}
|
||||
|
||||
minus(vec: Vector): Vector {
|
||||
return new Vector(this.x - vec._x, this.y - vec.y)
|
||||
}
|
||||
|
||||
multiply(number: number): Vector {
|
||||
return new Vector(this.x * number, this.y * number)
|
||||
}
|
||||
|
||||
multiplyVec(vec: Vector): Vector {
|
||||
return new Vector(this.x * vec._x, this.y * vec.y)
|
||||
}
|
||||
|
||||
negate(): Vector {
|
||||
return this.multiply(-1)
|
||||
}
|
||||
|
||||
|
||||
get x(): number {
|
||||
return this._x;
|
||||
}
|
||||
|
||||
set x(value: number) {
|
||||
this._x = value;
|
||||
}
|
||||
|
||||
get y(): number {
|
||||
return this._y;
|
||||
}
|
||||
|
||||
set y(value: number) {
|
||||
this._y = value;
|
||||
}
|
||||
}
|
||||
7
absurd-survivors/src/instance.ts
Normal file
7
absurd-survivors/src/instance.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type {Healthy} from "./interfaces.ts";
|
||||
|
||||
export class InstanceOfUtils {
|
||||
static instanceOfHealthy(object: any): object is Healthy {
|
||||
return 'takeDamage' in object;
|
||||
}
|
||||
}
|
||||
31
absurd-survivors/src/interfaces.ts
Normal file
31
absurd-survivors/src/interfaces.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {Vector} from "./base.ts";
|
||||
|
||||
export interface Acting {
|
||||
act()
|
||||
}
|
||||
|
||||
export interface Healthy {
|
||||
takeDamage(damage: number);
|
||||
}
|
||||
|
||||
export interface Moving {
|
||||
move()
|
||||
getPosition(): Vector;
|
||||
}
|
||||
|
||||
export interface Projectile extends Drawable {
|
||||
|
||||
}
|
||||
|
||||
export interface Weapon extends Drawable{
|
||||
act()
|
||||
}
|
||||
|
||||
export interface Shooting {
|
||||
createProjectile(): Projectile;
|
||||
removeProjectile(projectile: Projectile)
|
||||
}
|
||||
|
||||
export interface Drawable {
|
||||
draw(ctx: CanvasRenderingContext2D);
|
||||
}
|
||||
133
absurd-survivors/src/main.ts
Normal file
133
absurd-survivors/src/main.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import './style.css'
|
||||
|
||||
import {docReady} from "canvas-common";
|
||||
import {World} from "./World.ts";
|
||||
import {Player} from "./Player.ts";
|
||||
import {Vector} from "./base.ts";
|
||||
import {BasicEnemy, Enemy, ShootingEnemy} from "./Enemies.ts";
|
||||
import {HUD} from "./ui.ts";
|
||||
import {Pistol} from "./weapons.ts";
|
||||
|
||||
|
||||
let hud: HUD;
|
||||
let world: World;
|
||||
let config: Config;
|
||||
let state: WorldState;
|
||||
let ctx: CanvasRenderingContext2D;
|
||||
let canvas;
|
||||
|
||||
export class Config {
|
||||
private _size: Vector = new Vector(window.innerWidth, window.innerHeight)
|
||||
private _fps: number = 60;
|
||||
|
||||
|
||||
get size(): Vector {
|
||||
return this._size;
|
||||
}
|
||||
|
||||
get fps(): number {
|
||||
return this._fps;
|
||||
}
|
||||
}
|
||||
|
||||
export class WorldState {
|
||||
private _ended: boolean = false;
|
||||
|
||||
|
||||
get ended(): boolean {
|
||||
return this._ended;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function updateCanvas() {
|
||||
ctx.clearRect(0, 0, config.size.x, config.size.y);
|
||||
hud.draw(ctx)
|
||||
if(!state.ended) {
|
||||
world.enemiesAct()
|
||||
world.player.act()
|
||||
world.draw()
|
||||
for(let key in keys) {
|
||||
if(keys[key].state) {
|
||||
keys[key].fun()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx.fillText('End', 15, 15)
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
requestAnimationFrame(updateCanvas);
|
||||
}, 1000 / config.fps)
|
||||
|
||||
}
|
||||
|
||||
function makeKey(char, fun) {
|
||||
keys[char] = {
|
||||
state: false,
|
||||
fun: fun
|
||||
}
|
||||
}
|
||||
|
||||
let keys = {};
|
||||
makeKey('w', function () {
|
||||
world.player.position.y += -world.player.stats.speed
|
||||
})
|
||||
makeKey('s', function () {
|
||||
world.player.position.y += world.player.stats.speed
|
||||
})
|
||||
makeKey('a', function () {
|
||||
world.player.position.x += -world.player.stats.speed
|
||||
})
|
||||
makeKey('d', function () {
|
||||
world.player.position.x += world.player.stats.speed
|
||||
})
|
||||
|
||||
|
||||
function keyUp(event) {
|
||||
if(event.key in keys) {
|
||||
keys[event.key].state = false;
|
||||
}
|
||||
}
|
||||
|
||||
function keyDown(event) {
|
||||
if(event.key in keys) {
|
||||
keys[event.key].state = true;
|
||||
}
|
||||
}
|
||||
|
||||
document.onkeyup = keyUp;
|
||||
document.onkeydown = keyDown;
|
||||
docReady(function () {
|
||||
canvas = document.getElementById('canvas');
|
||||
|
||||
config = new Config();
|
||||
canvas.width = config.size.x;
|
||||
|
||||
canvas.height = config.size.y;
|
||||
|
||||
|
||||
ctx = canvas.getContext("2d");
|
||||
|
||||
let player = Player.generatePlayer();
|
||||
|
||||
world = new World(player, ctx);
|
||||
state = new WorldState();
|
||||
|
||||
world.addEnemy(BasicEnemy.generateBasicEnemy(world))
|
||||
world.addEnemy(ShootingEnemy.generateShootingEnemy(world, new Vector(350, 350)))
|
||||
setInterval(() => {
|
||||
world.addEnemy(ShootingEnemy.generateShootingEnemy(world, new Vector(Math.random() * config.size.x, Math.random() * config.size.y)))
|
||||
}, 1000)
|
||||
player.addWeapon(Pistol.spawnPistol(world))
|
||||
let secondPistol = Pistol.spawnPistol(world, new Vector(-5, -5));
|
||||
player.addWeapon(secondPistol)
|
||||
hud = new HUD(world);
|
||||
|
||||
|
||||
requestAnimationFrame(updateCanvas);
|
||||
|
||||
|
||||
|
||||
})
|
||||
|
||||
100
absurd-survivors/src/projectile.ts
Normal file
100
absurd-survivors/src/projectile.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import type {Acting, Moving, Healthy} from "./interfaces.ts";
|
||||
import type {Vector} from "./base.ts";
|
||||
import {World} from "./World.ts";
|
||||
import {Vector} from "./base.ts";
|
||||
import {drawDot, moveInDirectionOf, straightMove} from "./utils.ts";
|
||||
import {InstanceOfUtils} from "./instance.ts";
|
||||
|
||||
export abstract class Projectile implements Acting, Moving {
|
||||
|
||||
protected position: Vector;
|
||||
protected speedVec: Vector;
|
||||
protected impact: number;
|
||||
protected world: World;
|
||||
protected size: number;
|
||||
protected parent: any;
|
||||
protected color: string
|
||||
|
||||
|
||||
constructor(position: Vector, speedVec: Vector, world: World, parent: any) {
|
||||
this.position = position;
|
||||
this.speedVec = speedVec;
|
||||
this.world = world;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
act() {
|
||||
this.move()
|
||||
if(this.parent != this.world.player) {
|
||||
if(this.position.distanceTo(this.world.player.position) < (this.size + this.world.player.stats.size)) {
|
||||
this.impactPlayer()
|
||||
}
|
||||
}
|
||||
if(this.parent == this.world.player) {
|
||||
let closestTargetTo = this.world.getClosestTargetTo(this.position);
|
||||
if(closestTargetTo !== undefined && closestTargetTo[1] !== undefined && closestTargetTo[1]?.getPosition().distanceTo(this.position) < (this.size + this.world.player.stats.size)) {
|
||||
let target: Moving = closestTargetTo[1]!;
|
||||
if(InstanceOfUtils.instanceOfHealthy(target)) {
|
||||
let healthy = target as Healthy;
|
||||
healthy.takeDamage(this.impact)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impactPlayer() {
|
||||
this.world.player.takeDamage(this.impact)
|
||||
this.world.removeProjectile(this)
|
||||
};
|
||||
|
||||
draw(ctx: CanvasRenderingContext2D) {
|
||||
drawDot(this.position, this.size, this.color, ctx)
|
||||
}
|
||||
|
||||
move() {
|
||||
}
|
||||
|
||||
getPosition(): Vector {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class StraightProjectile extends Projectile {
|
||||
|
||||
|
||||
constructor(position: Vector, dirVector: Vector, world: World, parent: any) {
|
||||
super(position, dirVector, world, parent);
|
||||
}
|
||||
|
||||
move() {
|
||||
this.position = straightMove(this.position, this.speedVec)
|
||||
}
|
||||
|
||||
static createStraightProjectile(world: World, start: Vector, targetPosition: Vector, parent: any) {
|
||||
let projectile = new StraightProjectile(start, Vector.createVector(targetPosition, start).normalize().multiply(5), world, parent)
|
||||
projectile.impact = 1;
|
||||
projectile.size = 1
|
||||
projectile.color = 'red';
|
||||
world.addProjectile(projectile)
|
||||
return projectile;
|
||||
}
|
||||
}
|
||||
|
||||
export class HomingProjectile extends Projectile {
|
||||
|
||||
move() {
|
||||
this.position = moveInDirectionOf(this.position, this.world.player.position, this.speedVec.vecLength())
|
||||
}
|
||||
|
||||
static createHomingProjectile(world: World, start: Vector, parent: any) {
|
||||
let projectile = new HomingProjectile(start, new Vector(5, 1), world, parent)
|
||||
projectile.impact = 1;
|
||||
projectile.size = 1
|
||||
projectile.color = 'red';
|
||||
world.addProjectile(projectile)
|
||||
return projectile;
|
||||
}
|
||||
|
||||
}
|
||||
4
absurd-survivors/src/style.css
Normal file
4
absurd-survivors/src/style.css
Normal file
@@ -0,0 +1,4 @@
|
||||
html, body, div, canvas {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
72
absurd-survivors/src/ui.ts
Normal file
72
absurd-survivors/src/ui.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import type {World} from "./World.ts";
|
||||
import type {Drawable} from "./interfaces.ts";
|
||||
import {World} from "./World.ts";
|
||||
import type {Vector} from "./base.ts";
|
||||
import {Vector} from "./base.ts";
|
||||
|
||||
export class HUD implements Drawable{
|
||||
private health: HealthInfo;
|
||||
private world: World;
|
||||
|
||||
|
||||
constructor(world: World) {
|
||||
this.world = world;
|
||||
this.health = new HealthInfo(world);
|
||||
}
|
||||
|
||||
draw(ctx: CanvasRenderingContext2D) {
|
||||
this.health.draw(ctx)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export class HealthInfo implements Drawable{
|
||||
private bar: InfoBar;
|
||||
private world: World;
|
||||
|
||||
constructor(world: World) {
|
||||
this.world = world;
|
||||
this.bar = new InfoBar(new Vector(0, 50), 50, 150, () => 'Health', () => this.world.player.health, () => this.world.player.stats.health)
|
||||
}
|
||||
|
||||
draw(ctx: CanvasRenderingContext2D) {
|
||||
this.bar.draw(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
export class InfoBar implements Drawable {
|
||||
private position: Vector;
|
||||
private height: number;
|
||||
private width: number;
|
||||
private fillColor: string = 'green';
|
||||
private borderColor: string = 'black';
|
||||
private textLambda: () => string;
|
||||
private valueLambda: () => number;
|
||||
private totalValueLambda: () => number;
|
||||
|
||||
|
||||
constructor(position: Vector, height: number, width: number, textLambda: () => string, valueLambda: () => number, totalValueLambda: () => number) {
|
||||
this.position = position;
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.textLambda = textLambda;
|
||||
this.valueLambda = valueLambda;
|
||||
this.totalValueLambda = totalValueLambda;
|
||||
}
|
||||
|
||||
draw(ctx: CanvasRenderingContext2D) {
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = this.borderColor
|
||||
ctx.strokeRect(this.position.x, this.position.y, this.width, this.height)
|
||||
ctx.fillStyle = this.fillColor;
|
||||
let value = this.valueLambda();
|
||||
let totalValue = this.totalValueLambda();
|
||||
let usedWidth = (value / totalValue) * this.width;
|
||||
ctx.fillRect(this.position.x, this.position.y, usedWidth, this.height)
|
||||
ctx.fillStyle = this.borderColor
|
||||
ctx.fillText(`${value}/${totalValue}`, this.position.x + this.width / 2, this.position.y + this.height / 2)
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
}
|
||||
18
absurd-survivors/src/utils.ts
Normal file
18
absurd-survivors/src/utils.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import {Vector} from "./base.ts";
|
||||
|
||||
export function drawDot(position: Vector, size: number, color: string, ctx: CanvasRenderingContext2D) {
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = color;
|
||||
ctx.arc(position.x, position.y, size, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
export function moveInDirectionOf(position: Vector, target: Vector, speedFactor): Vector {
|
||||
let playerVector = Vector.createVector(target, position);
|
||||
let direction = playerVector.normalize()
|
||||
return position.add(direction.multiply(speedFactor))
|
||||
}
|
||||
|
||||
export function straightMove(position: Vector, speed: Vector): Vector {
|
||||
return position.add(speed)
|
||||
}
|
||||
1
absurd-survivors/src/vite-env.d.ts
vendored
Normal file
1
absurd-survivors/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
59
absurd-survivors/src/weapons.ts
Normal file
59
absurd-survivors/src/weapons.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type {Weapon} from "./interfaces.ts";
|
||||
import {drawDot} from "./utils.ts";
|
||||
import {Player} from "./Player.ts";
|
||||
import {Projectile, StraightProjectile} from "./projectile.ts";
|
||||
import {World} from "./World.ts";
|
||||
import {Vector} from "./base.ts";
|
||||
|
||||
export class Pistol implements Weapon {
|
||||
|
||||
private player: Player
|
||||
private shootInterval: number;
|
||||
private shootCooldown: number = 0;
|
||||
private world: World;
|
||||
private offset: Vector;
|
||||
private projectiles: [Projectile] = []
|
||||
private color: string;
|
||||
private size: number;
|
||||
|
||||
constructor(world: World) {
|
||||
this.player = world.player;
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
draw(ctx: CanvasRenderingContext2D) {
|
||||
drawDot(this.player.position.add(this.offset), this.size, this.color, ctx)
|
||||
}
|
||||
|
||||
act() {
|
||||
if(this.shootCooldown <= 0) {
|
||||
if(this.createProjectile()) {
|
||||
this.shootCooldown = this.shootInterval;
|
||||
}
|
||||
}
|
||||
this.shootCooldown -= 1;
|
||||
}
|
||||
|
||||
private createProjectile(): boolean {
|
||||
let closestTargetTo = this.world.getClosestTargetTo(this.world.player.position);
|
||||
if(closestTargetTo !== undefined && closestTargetTo[1] !== undefined) {
|
||||
let projectile = StraightProjectile.createStraightProjectile(this.world, this.player.position.add(this.offset), closestTargetTo[1]!.getPosition(), this.player)
|
||||
this.projectiles.push(projectile)
|
||||
return true
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static spawnPistol(world: World, offset?: Vector) {
|
||||
if(!offset) {
|
||||
offset = new Vector(5, 5)
|
||||
}
|
||||
let pistol = new Pistol(world)
|
||||
pistol.offset = offset;
|
||||
pistol.size = 1;
|
||||
pistol.color = 'yellow';
|
||||
pistol.shootInterval = 10;
|
||||
return pistol;
|
||||
}
|
||||
}
|
||||
25
absurd-survivors/tsconfig.json
Normal file
25
absurd-survivors/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
7
absurd-survivors/vite.config.js
Normal file
7
absurd-survivors/vite.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
outDir: '../../dist/survivors'
|
||||
},
|
||||
})
|
||||
BIN
img/survivors.png
Normal file
BIN
img/survivors.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
@@ -18,5 +18,6 @@
|
||||
<a href="/clusterFilter"><img src="img/clusterFilter.png" alt="clusterFilter" class="preview" title="clusterFilter"></a>
|
||||
<a href="/collatzConjecture"><img src="img/collatzConjecture.png" alt="collatzConjecture" class="preview" title="collatzConjecture"></a>
|
||||
<a href="/dotLines"><img src="img/dotLines.png" alt="dotLines" class="preview" title="dotLines"></a>
|
||||
<a href="/survivors"><img src="img/survivors.png" alt="survivors" class="preview" title="survivors"></a>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user