Skip to content

Commit

Permalink
Don't rely on the server always sending full updates
Browse files Browse the repository at this point in the history
Instead, process the local state and process partial state updates

The update state function is called at 60 FPS
Should probably look to have that tickPeriod passed from the server, but
currently we do everything @ 60 FPS

Undo everything set in setupGame() in stopGame()

Respect welcome config

Add shitty software notice to client README

Use bower
Use Array.find and a polyfill therefor
Minify JS output if on CI
  • Loading branch information
thecoshman authored and nabijaczleweli committed Apr 17, 2016
1 parent 9ba2444 commit ac2b5f2
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 2,516 deletions.
3 changes: 3 additions & 0 deletions .editorconfig
Expand Up @@ -6,3 +6,6 @@ insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 4

[Makefile]
indent_style = tab
18 changes: 11 additions & 7 deletions client/Makefile
@@ -1,7 +1,7 @@
ifeq "$(OS)" "Windows_NT"
PATSEP := ;
PATSEP := ;
else
PATSEP := :
PATSEP := :
endif

NPM_BIN := $(shell npm bin)/
Expand All @@ -11,30 +11,34 @@ JS_OUT := $(patsubst src/%.ts,out/%.js,$(SOURCES))
export PATH := $(NPM_BIN)$(PATSEP)$(PATH)


.PHONY: all clean build install
.PHONY: all clean install

all: install tatsoryk.js

clean:
rm -rf out/ tatsoryk.js

clean-install: clean
rm -rf $(abspath $(NPM_BIN)..) typings
rm -rf $(abspath $(NPM_BIN)..) typings/ bower_components/

build: $(SOURCES)

install: $(NPM_BIN)tsc typings/browser.d.ts
install: $(NPM_BIN)tsc typings/browser.d.ts bower_components/ts-polyfills/index.js


tatsoryk.js: $(JS_OUT)
browserify -o $@ $^
ifneq "$(CI)" ""
yuglify --terminal --output $@ < $@
endif

$(NPM_BIN)tsc: package.json
npm install

typings/browser.d.ts: typings.json
typings install

bower_components/ts-polyfills/index.js: bower.json
bower install


$(JS_OUT): $(SOURCES)
tsc --newLine LF --noEmitOnError --outDir out/ --noImplicitAny --noImplicitReturns --pretty -t ES5 $^
4 changes: 3 additions & 1 deletion client/README
Expand Up @@ -2,4 +2,6 @@
2. Install GNU make version 3.81 at least.
3. Running GNU make in client root will install dependencies and build the client. With tyeps.

If you're not in the contributors section in package.json, add yourself there if you commit any code.
If npm fails on Vagrant VM with a message containing "symlink", that means you need to run make install from your host OS (after make clean-install, of course). This is a known (and unfixed) issue.

If you're not in the contributors section in package.json and bower.json, add yourself there if you commit any code.
22 changes: 22 additions & 0 deletions client/bower.json
@@ -0,0 +1,22 @@
{
"name": "tatsoryk-client",
"description": "Implementation of the client for Tatsoryk",
"authors": [
"nabijaczleweli <nabijaczleweli@gmail.com>",
"Cat Plus Plus <piotrlegnica@piotrl.pl>",
"Lalaland <ethan.steinberg@gmail.com"
],
"license": "MIT",
"keywords": [
"game",
"lounge",
"client"
],
"homepage": "https://github.com/LoungeCPP/Tatsoryk",
"private": true,
"dependencies": {
"ts-polyfills": "git://github.com/nabijaczleweli/ts-polyfills.git#deploy",
"victor": "^1.1.0",
"Keypress": "^2.1.4"
}
}
5 changes: 3 additions & 2 deletions client/index.html
Expand Up @@ -5,8 +5,9 @@
<title>Tatsoryk Client</title>

<link rel="stylesheet" href="game.css">
<script src="vendor/keypress.js"></script>
<script src="vendor/victor.js"></script>
<script src="bower_components/ts-polyfills/index.min.js"></script>
<script src="bower_components/Keypress/keypress-2.1.3.min.js"></script>
<script src="bower_components/victor/build/victor.min.js"></script>
<script src="tatsoryk.js"></script>
</head>
<body>
Expand Down
4 changes: 3 additions & 1 deletion client/package.json
Expand Up @@ -20,9 +20,11 @@
],
"repository": "LoungeCPP/Tatsoryk",
"devDependencies": {
"bower": "^1.7.9",
"browserify": "^13.0.0",
"typescript": "^1.8.9",
"typings": "^0.7.12"
"typings": "^0.7.12",
"yuglify": "^0.1.4"
},
"dependencies": {
"wolfy87-eventemitter": "^4.3.0"
Expand Down
117 changes: 109 additions & 8 deletions client/src/game.ts
Expand Up @@ -3,9 +3,11 @@
import {GameInput, MousePos, getRelativeMouseCords} from './input';
import {GameWSTransport} from './transport';
import {GameScreen} from './gamescreen';
import {GameSocket, MessageData} from './protocol';
import {GameSocket, MessageData, Entity} from './protocol';

class Game {
static tickPeriod: number = 1.0 / 60;

transport: GameWSTransport = null;
socket: GameSocket = null;
canvas: HTMLCanvasElement = null;
Expand Down Expand Up @@ -47,6 +49,81 @@ class Game {
e.stopPropagation();
}

//
// Updates without full state
//

update = (): void => {
this.updatePlayerStates();
this.updateBulletStates();
this.doBulletCollisionChecks();
}

updatePlayerStates(): void {
var minDistance = this.welcomeMessage.size;
var minPlayerDistanceSq = 4 * minDistance * minDistance;
var maxDistanceX = this.canvas.width - this.welcomeMessage.size;
var maxDistanceY = this.canvas.height - this.welcomeMessage.size;
this.game.state.alivePlayers.forEach((player: Entity): void => {
if (player.direction != null) {
var move = player.direction.clone().multiplyScalar(this.welcomeMessage.speed);
var newpos = player.position.clone().add(move);
if (!this.game.state.alivePlayers.some((cmpPlayer: Entity) => {
return cmpPlayer.id != player.id && cmpPlayer.distanceSq(newpos) <= minPlayerDistanceSq;
})) {
player.position.x = Math.min(Math.max(newpos.x, minDistance), maxDistanceX);
player.position.y = Math.min(Math.max(newpos.y, minDistance), maxDistanceY);
}
}
});
}

updateBulletStates(): void {
this.game.state.aliveBullets.forEach((bullet: Entity): void => {
if (bullet.direction != null) {
bullet.position.x += bullet.direction.x * this.welcomeMessage.bulletSpeed;
bullet.position.y += bullet.direction.y * this.welcomeMessage.bulletSpeed;
}
});
}

doBulletCollisionChecks(): void {
var killedPlayers: Array<number> = [];
var destroyedBullets: Array<number> = [];
this.game.state.aliveBullets.forEach((bullet: Entity): void => {
if (this.bulletOutOfBounds(bullet)) {
destroyedBullets.push(bullet.id);
return; // if the bullet just went OOB, no point checking for player hits... right?
}
this.game.state.alivePlayers.forEach((player: Entity): void => {
if (this.bulletHitPlayer(bullet, player)) {
destroyedBullets.push(bullet.id);
killedPlayers.push(player.id);
}
});
});

let removeKilledEntity = (killedEntities: Array<number>): ((entity: Entity) => boolean) => {
return function(entity: Entity): boolean {
return killedEntities.indexOf(entity.id) == -1;
};
};
this.game.state.alivePlayers = this.game.state.alivePlayers.filter(removeKilledEntity(killedPlayers));
this.game.state.aliveBullets = this.game.state.aliveBullets.filter(removeKilledEntity(destroyedBullets));
}

bulletOutOfBounds(bullet: Entity): boolean {
var pos = bullet.position;
return pos.x < 0 || pos.y < 0 || pos.x > this.canvas.width || pos.y > this.canvas.height;
}

bulletHitPlayer(bullet: Entity, player: Entity): boolean {
var distanceSq = bullet.distanceSq(player);
var collisionRange = this.welcomeMessage.size + this.welcomeMessage.bulletSize;
// calculation is done on distance squared as it avoids the relatively costly square toot
return distanceSq < collisionRange * collisionRange;
}

//
// Networking
//
Expand All @@ -66,31 +143,49 @@ class Game {
}

handlePlayerJoined = (msg: MessageData.PlayerJoined): void => {
// TODO do stuff~
++this.game.state.playerCount;
}

handlePlayerLeft = (msg: MessageData.PlayerLeft): void => {
// TODO do stuff~
--this.game.state.playerCount;
this.game.state.alivePlayers = this.game.state.alivePlayers.filter((player: Entity): boolean => {
return player.id != msg.id;
});
}

handleShotsFired = (msg: MessageData.ShotsFired): void => {
// TODO do stuff~
this.game.state.aliveBullets.push(new Entity(msg.bulletID, msg.position, msg.aim));
}

handlePlayerSpawned = (msg: MessageData.PlayerSpawned): void => {
// TODO do stuff~
this.game.state.alivePlayers.push(new Entity(msg.id, msg.position));
}

handlePlayerDestroyed = (msg: MessageData.PlayerDestroyed): void => {
// TODO do stuff~
this.game.state.alivePlayers = this.game.state.alivePlayers.filter((player: Entity): boolean => {
return player.id != msg.id;
});
if (msg.bulletID != null) {
this.game.state.aliveBullets = this.game.state.aliveBullets.filter((bullet: Entity): boolean => {
return bullet.id != msg.bulletID;
});
}
}

handlePlayerMoving = (msg: MessageData.PlayerMoving): void => {
// TODO do stuff~
var player = this.game.state.alivePlayers.find((player: Entity): boolean => {
return player.id == msg.id;
});
player.position = msg.position;
player.direction = msg.direction;
}

handlePlayerStopped = (msg: MessageData.PlayerStopped): void => {
// TODO do stuff~
var player = this.game.state.alivePlayers.find((player: Entity): boolean => {
return player.id == msg.id;
});
player.position = msg.position;
player.direction = null;
}

handleWorldState = (msg: MessageData.WorldState): void => {
Expand Down Expand Up @@ -161,18 +256,24 @@ class Game {
// Game logic
//

updateInterval: number;

// Create the game given a welcome message and an initial state.
// Welcome must be a welcome message.
// initialState must be a world state message.
setupGame(welcome: MessageData.Welcome, initialState: MessageData.WorldState): void {
// TODO
this.game = new GameScreen(welcome.id, welcome.size, welcome.bulletSize, initialState, this.socket);
this.bindInput();
this.updateInterval = setInterval(this.update, Game.tickPeriod * 1000);
}

stopGame = (): void => {
// TODO
clearInterval(this.updateInterval);
this.updateInterval = null;
this.unbindInput();
this.game = null;
}

//
Expand Down
8 changes: 8 additions & 0 deletions client/src/protocol.ts
Expand Up @@ -64,6 +64,14 @@ export class GameSocket extends EventEmitter {

export class Entity {
constructor(public id: number, public position: Victor, public direction?: Victor) { }

public distanceSq(to: Entity | Victor): number {
if ('position' in to) {
return this.position.distanceSq((<Entity>to).position);
} else {
return this.position.distanceSq(<Victor>to);
}
}
}

export class Message {
Expand Down
1 change: 0 additions & 1 deletion client/src/transport.ts
Expand Up @@ -41,7 +41,6 @@ export class GameWSTransport extends EventEmitter {
};

connect(): void {
console.log(this)
console.log('WSTransport: connect');
this.explicitDisconnect = false;

Expand Down
1 change: 1 addition & 0 deletions client/typings.json
@@ -1,6 +1,7 @@
{
"ambientDependencies": {
"keypress": "registry:dt/keypress#2.0.3+20160317120654",
"ts-polyfills": "github:nabijaczleweli/ts-polyfills#deploy",
"victor": "registry:dt/victor#0.2.5+20160316155526",
"wolfy87-eventemitter": "registry:dt/wolfy87-eventemitter#4.2.9+20160317120654"
}
Expand Down

0 comments on commit ac2b5f2

Please sign in to comment.