Skip to content

Commit

Permalink
migration to ts
Browse files Browse the repository at this point in the history
  • Loading branch information
gtanczyk committed Oct 5, 2024
1 parent 9404643 commit aec8e5b
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { ARROW_RANGE, BALL_RANGE, EDGE_RADIUS, MIN_TICK, UPDATE_TICK } from '../consts';
import { VMath } from '../util/vmath';
import { GameObject } from './objects/game-object';
import { ArrowObject, ExplosionObject } from './objects/object-arrow';
import { SoldierObject } from './objects/object-soldier';

export class GameWorld {
objects: GameObject[];
objectsByType: { [key: string]: GameObject[] };
collisionHandlers: any[];
edgeRadius: number;
worldTime: number;

constructor() {
this.objects = [];
this.objectsByType = {
Expand All @@ -30,7 +37,7 @@ export class GameWorld {
return this.edgeRadius;
}

addObject(...args) {
addObject(...args: GameObject[]) {
for (var object of args) {
this.objects.push(object);
this.objectsByType[object.getClass()].push(object);
Expand All @@ -40,26 +47,26 @@ export class GameWorld {
/**
* @param {GameObject} object
*/
removeObject(object) {
removeObject(object: GameObject) {
this.objects.splice(this.objects.indexOf(object), 1);
}

queryObjects(type, fn) {
return this.objectsByType[type].filter(function (object) {
return !fn || fn(object);
});
queryObjects<T extends GameObject>(type: string, fn?: (object: T) => boolean): T[] {
return this.objectsByType[type].filter((object) => {
return !fn || fn(object as T);
}) as T[];
}

/**
* Update game state
* @param elapsedTime how much time elapsed since last update
* @return {Number} elapsedTime not consumed
*/
update(elapsedTime) {
update(elapsedTime: number) {
var deltaTime = Math.min(elapsedTime, MIN_TICK);
this.objects.forEach(function (object) {
this.objects.forEach((object) => {
this.updateObject(object, deltaTime / UPDATE_TICK);
}, this);
});
elapsedTime -= deltaTime;
this.worldTime += deltaTime;

Expand All @@ -74,14 +81,14 @@ export class GameWorld {
* Collision check
*/
collisions() {
var hitArrows = this.queryObjects('Arrow', (arrow) => arrow.isHit());
this.queryObjects('Soldier').forEach(function (soldier) {
var hitArrows = this.queryObjects('Arrow', (arrow: ArrowObject) => arrow.isHit());
this.queryObjects<SoldierObject>('Soldier').forEach((soldier) => {
if (soldier.life <= 0) {
return;
}

// soldier -> soldier
this.queryObjects('Soldier').forEach((soldierLeft, idxLeft) => {
this.queryObjects<SoldierObject>('Soldier').forEach((soldierLeft, idxLeft) => {
if (
idxLeft <= this.objectsByType['Soldier'].indexOf(soldier) ||
soldierLeft.life <= 0 ||
Expand Down Expand Up @@ -117,7 +124,7 @@ export class GameWorld {
});
}

triggerCollisions(leftObject, rightObject) {
triggerCollisions(leftObject: GameObject, rightObject: GameObject) {
this.collisionHandlers.forEach(function (collisionHandler) {
if (leftObject instanceof collisionHandler.left && rightObject instanceof collisionHandler.right) {
collisionHandler.handler(leftObject, rightObject);
Expand All @@ -128,15 +135,19 @@ export class GameWorld {
});
}

onCollision(leftObjectType, rightObjectType, handler) {
onCollision<L extends GameObject, R extends GameObject>(
leftObjectType: new (...args: any[]) => L,
rightObjectType: new (...args: any[]) => R,
handler: (left: L, right: R) => void,
) {
this.collisionHandlers.push({
left: leftObjectType,
right: rightObjectType,
handler: handler,
});
}

onSoldierCollision(leftSoldier, rightSoldier) {
onSoldierCollision(leftSoldier: SoldierObject, rightSoldier: SoldierObject) {
// soldiers should bounce off each other
var distance = VMath.distance(leftSoldier.vec, rightSoldier.vec);
if (distance === 0) {
Expand All @@ -156,7 +167,7 @@ export class GameWorld {
}
}

onArrowCollision(soldier, arrow) {
onArrowCollision(soldier: SoldierObject, arrow: ArrowObject) {
var distance = VMath.distance(arrow.vec, soldier.vec);
if (arrow.type === 'arrow') {
arrow.hit(soldier, distance);
Expand All @@ -172,17 +183,17 @@ export class GameWorld {
* @param object
* @param deltaTime
*/
updateObject(object, deltaTime) {
updateObject(object: GameObject, deltaTime: number) {
object.update(deltaTime);
}

getAlive() {
return this.queryObjects('Soldier', (soldier) => soldier.life > 0).reduce(
return this.queryObjects<SoldierObject>('Soldier', (soldier) => soldier.life > 0).reduce(
(r, soldier) => ((r[soldier.color] = (r[soldier.color] || 0) + 1), r),
{
'#ff0000': 0,
'#00ff00': 0,
},
} as Record<string, number>,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class SoldierPlan {
return this.currentCommand;
}

canClaim(enemy: SoldierObject, soldier: SoldierObject) {
canClaim(enemy: SoldierObject, soldier: SoldierObject): boolean {
if (!this.claims[enemy.soldierId]) {
this.claims[enemy.soldierId] = [];
}
Expand All @@ -48,6 +48,8 @@ export class SoldierPlan {
if (distant.length === this.claims[enemy.soldierId].length) {
return true;
}

return false;
}

unclaim(enemy: SoldierObject, soldier: SoldierObject) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class ArrowObject extends GameObject {
maxDist: number;
attackBase: number;
type: string;
v: number[];
v: [number, number];
adir: number | undefined;
ay: number | undefined;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,50 @@ import {
import { updateState } from '../../states';
import { ArrowObject } from './object-arrow';
import { $ } from '../../util/dom';
import { GameWorld } from '../game-world';
import { SoldierPlan } from '../masterplan/soldierplan';
import { Canvas } from '../../util/canvas';

let soldierID = 0;

export class SoldierObject extends GameObject {
constructor(x, y, direction, plan, world, color, type) {
soldierId: number;
plan: SoldierPlan;
world: GameWorld;
type: string;
velocity: number;
targetVelocity: number;
force: [number, number];
targetDirection: number;
image: any;
imageDead: any;
color: string;
life: number;
newLife: number;
defenceCooldown: number;
weight: number;
baseSpeed: number;
seekRange: number = 0;
attackRange: number = 0;
rangeDefence: number = 0;
meleeDefence: number = 0;
meleeAttack: number = 0;
canCharge: boolean = false;
isMelee: boolean = false;
rangeAttack: number | undefined;
rangedCooldown: number | undefined;
rangeType: 'arrow' | 'ball' | undefined;
cooldowns: Record<string, number> = {};
enemy: any;
constructor(
x: number,
y: number,
direction: number,
plan: SoldierPlan,
world: GameWorld,
color: string,
type: 'tank' | 'warrior' | 'archer' | 'artillery',
) {
direction += (Math.random() - Math.random()) / 1000;
super(x, y, SOLDIER_WIDTH, SOLDIER_HEIGHT, direction);
this.soldierId = soldierID++;
Expand Down Expand Up @@ -108,40 +147,40 @@ export class SoldierObject extends GameObject {
/**
* @param {Canvas} canvas
*/
render(canvas) {
render(canvas: Canvas) {
canvas
.save()
.translate(-this.getWidth() / 2, -this.getHeight() / 2)
.drawImage(this.life > 0 ? this.image : this.imageDead, 0, 0);

if (this.life > 0) {
if (this.life < MAX_LIFE) {
canvas.drawText(10, 0, this.life << 0);
canvas.drawText(10, 0, String(this.life << 0));
}
canvas.fillStyle(this.color).fillRect(0, 10, 10, (5 * this.life) / MAX_LIFE);
}

canvas.restore();
}

cooldown(name, maxValue) {
cooldown(name: string, maxValue: number) {
if (!this.cooldowns[name] || this.world.getTime() > this.cooldowns[name]) {
this.cooldowns[name] = this.world.getTime() + maxValue;
return true;
}
}

// controls
setTargetDirection(targetDirection) {
setTargetDirection(targetDirection: number) {
this.targetDirection = targetDirection;
}

setTargetVelocity(targetVelocity) {
setTargetVelocity(targetVelocity: number) {
this.targetVelocity = targetVelocity * this.baseSpeed;
}

// update
updateVelocity(deltaTime) {
updateVelocity(deltaTime: number) {
this.velocity = this.getTargetVelocity() * deltaTime + this.velocity * (1 - deltaTime);
}

Expand All @@ -153,9 +192,9 @@ export class SoldierObject extends GameObject {
return this.velocity;
}

update(deltaTime) {
update(deltaTime: number) {
if (this.life > 0) {
this.updatePlan(deltaTime);
this.updatePlan();
} else if (this.enemy) {
this.setEnemy(null);
}
Expand Down Expand Up @@ -191,12 +230,12 @@ export class SoldierObject extends GameObject {
this.life = this.newLife;
}

distance(soldier) {
distance(soldier: SoldierObject) {
return VMath.distance(this.vec, soldier.vec);
}

queryEnemy(distance) {
var enemies = this.world.queryObjects(
queryEnemy(distance: number) {
var enemies = this.world.queryObjects<SoldierObject>(
'Soldier',
(soldier) =>
soldier.isEnemy(this) &&
Expand All @@ -207,15 +246,16 @@ export class SoldierObject extends GameObject {
if (enemies.length > 0) {
var fn;
if (this.type === 'artillery') {
fn = (r, soldier) => (soldier.distance(this) > r.distance(this) ? soldier : r);
fn = (r: SoldierObject, soldier: SoldierObject) => (soldier.distance(this) > r.distance(this) ? soldier : r);
} else {
fn = (r, soldier) => (soldier.distance(this) < r.distance(this) ? soldier : r);
fn = (r: SoldierObject, soldier: SoldierObject) => (soldier.distance(this) < r.distance(this) ? soldier : r);
}
return enemies.reduce(fn, enemies[0]);
}
return null;
}

seekEnemy(distance) {
seekEnemy(distance: number) {
// are there any enemies?
if (this.enemy && (this.enemy.life <= 0 || this.enemy.distance(this) > distance)) {
this.setEnemy(null);
Expand All @@ -241,9 +281,9 @@ export class SoldierObject extends GameObject {
this.rangeAttack &&
dist < this.attackRange &&
dist > MIN_RANGE_ATTACK &&
this.cooldown('arrow', this.rangedCooldown)
this.cooldown('arrow', this.rangedCooldown!)
) {
this.world.addObject(new ArrowObject(this.vec, this.enemy.vec, this.world, this.rangeAttack, this.rangeType));
this.world.addObject(new ArrowObject(this.vec, this.enemy.vec, this.world, this.rangeAttack, this.rangeType!));
aa.play('arrow');
if (this.type === 'artillery') {
this.hitBy(50);
Expand All @@ -264,22 +304,22 @@ export class SoldierObject extends GameObject {
}
}

addForce(vec) {
addForce(vec: [number, number]) {
this.force = VMath.add(this.force, vec);
}

getDefence(soldier, factor) {
getDefence(soldier: SoldierObject | ArrowObject, factor: number) {
var baseDefence =
Math.abs(VMath.angle(VMath.sub(soldier.vec, this.vec), [Math.cos(this.direction), Math.sin(this.direction)])) /
Math.PI;
return Math.min(baseDefence / factor, 0.3);
}

getAttack(soldier) {
getAttack(soldier: SoldierObject) {
return (1 - this.getDefence(soldier, this.meleeDefence)) * this.meleeAttack;
}

hit(bySoldier) {
hit(bySoldier: SoldierObject) {
var damage =
(this.cooldown('defence', this.defenceCooldown) ? this.getDefence(bySoldier, this.meleeDefence) : 1) *
bySoldier.getAttack(this);
Expand All @@ -289,33 +329,33 @@ export class SoldierObject extends GameObject {
aa.play('damage');
}

hitByArrow(arrow, distance) {
hitByArrow(arrow: ArrowObject, distance: number) {
var damage;
if (arrow.type === 'ball') {
damage = arrow.getAttack(arrow) / Math.pow(distance, 1 / 4);
damage = arrow.getAttack() / Math.pow(distance, 1 / 4);
} else {
damage =
arrow.getAttack(arrow) *
arrow.getAttack() *
(this.cooldown('defence', this.defenceCooldown) ? this.getDefence(arrow, this.rangeDefence) : 1);
}
this.hitBy(damage);
updateState(EVENT_DAMAGE_ARROW, { soldier: this, damage: damage });
aa.play('hitarrow');
}

hitBy(value) {
hitBy(value: number) {
this.newLife = Math.max(this.newLife - value, 0);
}

isEnemy(ofSoldier) {
isEnemy(ofSoldier: SoldierObject) {
return this.plan.masterPlan !== ofSoldier.plan.masterPlan;
}

getClass() {
return 'Soldier';
}

setEnemy(enemy) {
setEnemy(enemy: SoldierObject | null) {
if (this.enemy) {
this.plan.unclaim(this.enemy, this);
this.enemy = null;
Expand Down
Loading

0 comments on commit aec8e5b

Please sign in to comment.