Skip to content

Commit 025053e

Browse files
committed
Basic implementation of game server with move handling
1 parent 1052cb7 commit 025053e

File tree

21 files changed

+3401
-1
lines changed

21 files changed

+3401
-1
lines changed

package-lock.json

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
"name": "battle-city-react-server",
33
"private": true,
44
"dependencies": {
5-
"socket.io": "^2.2.0"
5+
"socket.io": "^2.2.0",
6+
"uuid": "^3.3.2"
67
},
78
"devDependencies": {
89
"@types/node": "^11.13.7",
10+
"@types/socket.io": "^2.1.2",
11+
"@types/uuid": "^3.4.4",
912
"nodemon": "^1.18.11",
1013
"ts-node": "^8.1.0",
1114
"typescript": "^3.4.4"

src/constants.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export const GAME_SERVER_ADDRESS: string = '192.168.1.101';
2+
export const GAME_SERVER_PORT: number = 3100;
3+
export const GAME_FRAMERATE: number = Math.floor(1000 / 60);
4+
export const BOARD_WIDTH: number = 1080 + (2 * 24);
5+
export const BOARD_HEIGHT: number = 720 + (2 * 24);
6+
export const TANK_WIDTH: number = 42;
7+
export const TANK_HEIGHT: number = 42;
8+
export const TANK_MOVE_STEP: number = Math.floor(GAME_FRAMERATE / 5);
9+
export const MISSILE_WIDTH: number = 6;
10+
export const MISSILE_HEIGHT: number = 6;
11+
export const MISSILE_MOVE_STEP: number = Math.floor(GAME_FRAMERATE / 2);
12+
export const MISSILE_THROTTLE_TIME: number = 300;
13+
export const OBSTACLE_WIDTH: number = 24;
14+
export const OBSTACLE_HEIGHT: number = 24;

src/game/classes/World/World.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import {Collision} from "../../enums/Collision";
2+
import Structure from "../../models/Structure";
3+
import Point from "../../models/Point";
4+
import {BOARD_HEIGHT} from "../../../constants";
5+
6+
/**
7+
* Class World - class representing world.
8+
*/
9+
export default class World {
10+
/**
11+
* Objects registry.
12+
*/
13+
protected readonly REGISTRY: {
14+
[index: string]: {
15+
structure: Structure,
16+
collisions: Array<Collision>
17+
}
18+
} = {};
19+
20+
/**
21+
* Collision map.
22+
*/
23+
protected readonly COLLISION_MAP = {
24+
[Collision.BLOCK_ALL]: [Collision.BLOCK_MOVE, Collision.BLOCK_SHOT],
25+
[Collision.BLOCK_MOVE]: [Collision.BLOCK_MOVE],
26+
[Collision.BLOCK_SHOT]: [Collision.BLOCK_SHOT],
27+
[Collision.BLOCK_NONE]: []
28+
};
29+
30+
/**
31+
* Register object in world.
32+
*
33+
* @param object - world object definition
34+
* @param collision - object collision type
35+
*/
36+
public registerObject(object: Structure, collision: Collision): void {
37+
const collisions: Array<Collision> = [];
38+
if (collision === Collision.BLOCK_ALL) {
39+
collisions.push(Collision.BLOCK_MOVE, Collision.BLOCK_SHOT);
40+
} else {
41+
collisions.push(collision)
42+
}
43+
this.REGISTRY[object.id] = {structure: object, collisions: collisions};
44+
}
45+
46+
/**
47+
* Update object position.
48+
*
49+
* @param id - object id
50+
* @param location - new location
51+
*/
52+
public updateObject(id: string, location: Point): void {
53+
this.REGISTRY[id].structure.location = location;
54+
}
55+
56+
/**
57+
* Remove object from world.
58+
*
59+
* @param id - object id
60+
*/
61+
public removeObject(id: string): void {
62+
delete this.REGISTRY[id];
63+
}
64+
65+
/**
66+
* Check if object is intersecting with anything else in the world using given collision type.
67+
*
68+
* @param actor - world object definition
69+
* @param collision - collision type
70+
*/
71+
public isIntersecting(actor: Structure, collision: Collision): Array<Structure> {
72+
// object position (y axis inversed)
73+
const tp = {
74+
l: {x: actor.location.x, y: BOARD_HEIGHT - actor.location.y}, // top left
75+
r: {
76+
x: actor.location.x + actor.dimension.width,
77+
y: BOARD_HEIGHT - actor.location.y - actor.dimension.height
78+
} // bottom right
79+
};
80+
const hits = [];
81+
for (const oid in this.REGISTRY) {
82+
if (actor.id !== oid && this.REGISTRY.hasOwnProperty(oid)) {
83+
const object = this.REGISTRY[oid];
84+
if (this.COLLISION_MAP[collision].filter(x => object.collisions.includes(x)).length) {
85+
// obstacle position (y axis inversed)
86+
const loc = object.structure.location;
87+
const dim = object.structure.dimension;
88+
const op = {
89+
l: {x: loc.x, y: BOARD_HEIGHT - loc.y}, // top left
90+
r: {x: loc.x + dim.width, y: BOARD_HEIGHT - loc.y - dim.height} // bottom right
91+
};
92+
let intersecting = true;
93+
// aside collision check
94+
if (tp.l.x > op.r.x || op.l.x > tp.r.x) {
95+
intersecting = false;
96+
}
97+
// top/bottom collision check
98+
if (tp.l.y < op.r.y || op.l.y < tp.r.y) {
99+
intersecting = false;
100+
}
101+
if (intersecting) {
102+
hits.push(object.structure);
103+
}
104+
}
105+
}
106+
}
107+
return hits;
108+
}
109+
}

src/game/classes/World/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {default} from './World';

src/game/enums/Collision.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Enum Collision - available collisions.
3+
*/
4+
export enum Collision {
5+
BLOCK_ALL = 'block_all',
6+
BLOCK_SHOT = 'block_shot',
7+
BLOCK_MOVE = 'block_move',
8+
BLOCK_NONE = 'block_none'
9+
}

src/game/enums/NetworkPacket.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Enum NetworkPacket - network packet.
3+
*/
4+
export enum NetworkPacket {
5+
CONNECTED = 'connect',
6+
HANDSHAKING = 'handshaking',
7+
BOARD_STATE_OBSTACLES = 'board.state.obstacles',
8+
BOARD_STATE_TANKS = 'board.state.tanks',
9+
BOARD_STATE_MISSILES = 'board.state.missiles',
10+
TANK_EVENT_KEYBOARD = 'tank.event.keyboard'
11+
}

src/game/enums/ObstacleType.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Enum ObstacleType - obstacle types.
3+
*/
4+
export enum ObstacleType {
5+
BRICK = 'brick',
6+
METAL = 'metal',
7+
WATER = 'water',
8+
FOREST = 'forest',
9+
TRANSPARENT = 'transparent'
10+
}

src/game/enums/TankActor.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* Enum TankActor - tank actor.
3+
*/
4+
export enum TankActor {
5+
SELF = 'self',
6+
PLAYER = 'player',
7+
AI = 'ai'
8+
}

src/game/handlers/TankMoveHandler.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import {OBSTACLE_HEIGHT, OBSTACLE_WIDTH, TANK_HEIGHT, TANK_MOVE_STEP, TANK_WIDTH} from "../../constants";
2+
import {Collision} from "../enums/Collision";
3+
import {TankActor} from "../enums/TankActor";
4+
import World from "../classes/World";
5+
import TankModel from "../models/components/TankModel";
6+
import Move from "../models/Move";
7+
8+
/**
9+
* Tank move handler.
10+
*
11+
* @param tank - tank model
12+
* @param world - world
13+
* @param isStuck - stuck state
14+
* @param activeKey - active key
15+
*/
16+
export const TANK_MOVE_HANDLER = (tank: TankModel, world: World, isStuck: boolean, activeKey: string): Move => {
17+
let move = {
18+
location: tank.location,
19+
rotation: tank.rotation,
20+
isStuck: isStuck
21+
};
22+
if (activeKey) {
23+
let x = tank.location.x;
24+
let y = tank.location.y;
25+
let r = tank.rotation;
26+
let initialDirection = r;
27+
let correctionAxis = 'x';
28+
switch (activeKey) {
29+
case 'ArrowUp':
30+
y -= TANK_MOVE_STEP;
31+
r = 0;
32+
break;
33+
case 'ArrowRight':
34+
x += TANK_MOVE_STEP;
35+
r = 90;
36+
correctionAxis = 'y';
37+
break;
38+
case 'ArrowDown':
39+
y += TANK_MOVE_STEP;
40+
r = 180;
41+
break;
42+
case 'ArrowLeft':
43+
x -= TANK_MOVE_STEP;
44+
r = 270;
45+
correctionAxis = 'y';
46+
break;
47+
default:
48+
}
49+
50+
// move correction (stick to grid)
51+
if (initialDirection !== r) {
52+
if (correctionAxis === 'x') {
53+
x = 3 + (Math.round(x / OBSTACLE_WIDTH) * OBSTACLE_WIDTH);
54+
} else {
55+
y = 3 + (Math.round(y / OBSTACLE_HEIGHT) * OBSTACLE_HEIGHT);
56+
}
57+
}
58+
59+
// intersection check
60+
if (world.isIntersecting({
61+
id: tank.id,
62+
location: {
63+
x: x,
64+
y: y
65+
},
66+
dimension: {
67+
width: TANK_WIDTH,
68+
height: TANK_HEIGHT
69+
}
70+
},
71+
Collision.BLOCK_MOVE
72+
).length === 0) {
73+
move.location = {x: x, y: y};
74+
move.rotation = r;
75+
move.isStuck = false;
76+
world.updateObject(tank.id, tank.location);
77+
} else {
78+
move.isStuck = true;
79+
// just rotate in case of intersection
80+
// but prevent rotation of stucked AI as it looks like glitch
81+
if (tank.actor !== TankActor.AI || !move.isStuck) {
82+
move.rotation = r;
83+
}
84+
}
85+
}
86+
return move;
87+
};

0 commit comments

Comments
 (0)