Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Rockets #3

Merged
merged 18 commits into from

2 participants

@waltsu
Collaborator

Rockets can kill players.

@OsQu OsQu commented on the diff
client/app/index.html
@@ -19,6 +19,13 @@
<h1>Sinkohippa client</h1>
<div id="game-container"></div>
</div>
+ <div id="player-info">
@OsQu Owner
OsQu added a note

Some kind of HUD inside the game would be nice for the future!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
client/app/scripts/game.coffee
((13 lines not shown))
- @gameSocket = @connectToServer()
- @bindEvents()
+ render: ->
+ _.forEach @players, (p) => p.render(@display)
@OsQu Owner
OsQu added a note

What about coffee's list comprehensions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
client/app/scripts/game.coffee
((13 lines not shown))
- @gameSocket = @connectToServer()
- @bindEvents()
+ render: ->
+ _.forEach @players, (p) => p.render(@display)
+ _.forEach @items, (i) => i.render(@display)
@OsQu Owner
OsQu added a note

Same here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
client/app/scripts/game.coffee
((19 lines not shown))
- bindEvents: ->
- gameEvents.socketMessage(@gameSocket, 'game-state').onValue @gotGameState
- gameEvents.socketMessage(@gameSocket, 'map').onValue @updateMap
- gameEvents.socketMessage(@gameSocket, 'info').onValue @gotServerInfo
- gameEvents.socketMessage(@gameSocket, 'new-player').onValue @addNewPlayer
- gameEvents.socketMessage(@gameSocket, 'player-leaving').onValue @playerLeaving
- gameEvents.socketMessage(@gameSocket, 'player-state-changed').onValue @playerStateChanged
+ gameLoop: ->
+ setTimeout =>
+ @requestAnimationFrame(_.bind(@gameLoop, @))
@OsQu Owner
OsQu added a note

No need to use _.bind. Just use fat-arrow for gameLoop e.g.

gameLoop: =>
  setTimeout =>
      .....
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
client/app/scripts/game.coffee
((32 lines not shown))
- gameEvents.globalBus.filter((ev) -> ev.target == 'server').onValue @sendToServer
+ start: ->
+ console.log "Starting engine"
+ @requestAnimationFrame(_.bind(@gameLoop, @))
@OsQu Owner
OsQu added a note

Same here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@OsQu OsQu commented on the diff
client/app/scripts/hud.coffee
@@ -0,0 +1,12 @@
+gameEvents = require('./game-events')
@OsQu Owner
OsQu added a note

Nice one!

Any ideas how this could be integrated inside the canvas display so it looks more natural? Like in nethack:

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@OsQu OsQu commented on the diff
client/app/scripts/main.coffee
@@ -2,6 +2,9 @@
console.log "Real mainjs"
Game = require('./game')
+Hud = require('./hud')
@OsQu Owner
OsQu added a note

Since Hud looks like a singleton to me, would it make sense to initialize it already to module.exports? Those are evaluated only once right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@OsQu OsQu commented on the diff
client/app/scripts/rocket.coffee
@@ -0,0 +1,25 @@
+class Rocket
+ constructor: (@id, @x, @y, @shooter, @direction) ->
+
+ getChar: ->
+ if @direction == 'up' || @direction == 'down'
@OsQu Owner
OsQu added a note

TODO: Diagonal movement :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@OsQu OsQu commented on the diff
server/app/actors/player-actor.coffee
((6 lines not shown))
@manager.globalBus.push { type: 'BROADCAST', key: 'new-player', data: @getState() }
@bindEvents()
bindEvents: ->
@unsubscribeMovePlayer = @manager.globalBus.filter((ev) => ev.id == @id).filter((ev) => ev.type == 'PLAYER_MOVE').onValue @movePlayer
- @unsubscribeShoot = @manager.globalBus.filter((ev) => ev.id == @id).filter((ev) => ev.type == 'PLAYER_SHOOT').onValue @shootWithPlayer
+ @unsubscribeShoot = @manager.globalBus.filter((ev) => ev.id == @id).filter((ev) => ev.type == 'PLAYER_SHOOT').debounceImmediate(@shootCooldown).onValue @shootWithPlayer
@OsQu Owner
OsQu added a note

SWEEEET!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@OsQu
Owner

Nitpicked about few things, but in overall it looked very good to me!

@waltsu waltsu merged commit e694443 into master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 4, 2013
  1. Increased speed of the rocket

    Valtteri Virtanen authored
  2. Clients destroys the rocket after the tick where destroying move has …

    Valtteri Virtanen authored
    …happened
  3. Client renders rockets. Still need some work

    Valtteri Virtanen authored
  4. Client destroys rockets

    Valtteri Virtanen authored
Commits on Aug 5, 2013
  1. Add git and webkit-devtools-agent to server-project

    Valtteri Virtanen authored
  2. Add message handler between game and the server

    Valtteri Virtanen authored
  3. Message handler spec

    Valtteri Virtanen authored
  4. Fix game spec

    Valtteri Virtanen authored
  5. small refactoring

    Valtteri Virtanen authored
  6. Add health to player actor

    Valtteri Virtanen authored
  7. Add damage to rocket actor

    Valtteri Virtanen authored
  8. Rocket hit reduces health of the player

    Valtteri Virtanen authored
  9. Player will die if health is reduced to 0 (or less)

    Valtteri Virtanen authored
Commits on Aug 6, 2013
  1. Throttling the rocket shooting

    Valtteri Virtanen authored
  2. Add simple hud

    Valtteri Virtanen authored
  3. Broadcasting the reduce health to clients

    Valtteri Virtanen authored
  4. Rendering only needed tiles when destroying items

    Valtteri Virtanen authored
Commits on Aug 13, 2013
  1. Fix few issues that were pointed out in the comments

    Valtteri Virtanen authored
This page is out of date. Refresh to see the latest.
View
7 client/app/index.html
@@ -19,6 +19,13 @@
<h1>Sinkohippa client</h1>
<div id="game-container"></div>
</div>
+ <div id="player-info">
@OsQu Owner
OsQu added a note

Some kind of HUD inside the game would be nice for the future!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ <h2>Player info</h2>
+ <ul>
+ <li>Health: <b class="player-health"></b></li>
+ </ul>
+
+ </div>
</div>
<!--[if lt IE 7]>
View
118 client/app/scripts/game.coffee
@@ -1,11 +1,11 @@
ROT = require('./vendor/rot.js/rot')
-Bacon = require('baconjs')
_ = require('underscore')
-io = require('socket.io-client')
+
+MessageHandler = require('./message-handler')
Map = require('./map')
Player = require('./player')
-KeyboardController = require('./keyboard-controller')
+Rocket = require('./rocket')
gameEvents = require('./game-events')
@@ -13,92 +13,82 @@ class Game
init: ->
@fps = 30
@players = []
+ @items = []
@display = new ROT.Display()
@gameContainer = $('#game-container')
@gameContainer.append @display.getContainer()
- @keyboardController = new KeyboardController()
+ @messageHandler = new MessageHandler(@)
+ @messageHandler.connect()
- @gameSocket = @connectToServer()
- @bindEvents()
+ render: ->
+ for player in @players
+ player.render(@display)
+ for item in @items
+ item.render(@display)
- bindEvents: ->
- gameEvents.socketMessage(@gameSocket, 'game-state').onValue @gotGameState
- gameEvents.socketMessage(@gameSocket, 'map').onValue @updateMap
- gameEvents.socketMessage(@gameSocket, 'info').onValue @gotServerInfo
- gameEvents.socketMessage(@gameSocket, 'new-player').onValue @addNewPlayer
- gameEvents.socketMessage(@gameSocket, 'player-leaving').onValue @playerLeaving
- gameEvents.socketMessage(@gameSocket, 'player-state-changed').onValue @playerStateChanged
+ gameLoop: =>
+ setTimeout =>
+ @requestAnimationFrame(@gameLoop)
+ @render()
+ , 1000 / @fps
- gameEvents.globalBus.filter((ev) -> ev.target == 'server').onValue @sendToServer
+ start: =>
+ console.log "Starting engine"
+ @requestAnimationFrame(@gameLoop)
- gotGameState: (event) =>
- state = event.data
- for part in state
- switch part.type
- when 'map' then @updateMap { data: part.state }
- when 'player' then @addNewPlayer { data: part.state }
+ requestAnimationFrame: (cb) ->
+ window.requestAnimationFrame(cb)
- updateMap: (event) =>
- @map = new Map(event.data)
- @renderMap()
+ setNewMap: (data) ->
+ @map = new Map(data)
+ @map.render(@display)
console.log "New map set and rendered"
- gotServerInfo: (event) =>
- console.log event.data
-
- addNewPlayer: (ev) =>
- playerData = ev.data
-
+ addNewPlayer: (playerData) ->
if _.some(@players, (existingPlayer) -> existingPlayer.id == playerData.id)
return
console.log "Adding new player"
player = new Player(playerData.id, playerData.x, playerData.y)
- if player.id == @gameSocket.socket.sessionid
+ if player.id == @messageHandler.ourId()
console.log "Found our player!"
player.initButtons()
@players.push(player)
- playerLeaving: (ev) =>
- console.log "Removing player"
- playerId = ev.data
- @players = _.filter @players, (p) -> p.id != playerId
- @renderMap()
+ removePlayer: (playerId) ->
+ player = _.find @players, (p) -> p.id == playerId
+ @players = _.without @players, player
+ @map?.renderTile(@display, player.x, player.y)
-
- playerStateChanged: (ev) =>
- newData = ev.data
+ playerStateChanged: (newData) ->
player = _.find(@players, (p) -> p.id == newData.id)
player.newX = newData.x
player.newY = newData.y
-
- sendToServer: (event) =>
- serverData = event.data
- @gameSocket.emit(serverData.key, serverData.data)
-
- render: ->
- _.forEach @players, (p) => p.render(@display)
-
- renderMap: ->
- @map?.render(@display)
-
- gameLoop: ->
- setTimeout =>
- @requestAnimationFrame(_.bind(@gameLoop, @))
- @render()
- , 1000 / @fps
-
- start: ->
- console.log "Starting engine"
- @requestAnimationFrame(_.bind(@gameLoop, @))
-
- connectToServer: ->
- io.connect('http://localhost:5000')
-
- requestAnimationFrame: (cb) ->
- window.requestAnimationFrame(cb)
+ player.health = newData.health
+ if player.id == @messageHandler.ourId()
+ gameEvents.globalBus.push { target: 'hud', data: player }
+
+
+ addNewRocket: (data) ->
+ rocket = new Rocket(data.id, data.x, data.y, data.shooter, data.direction)
+ @items.push(rocket)
+ rocket
+
+ removeRocket: (rocketId) ->
+ console.log "Destroying rocket"
+ item = _.find @items, (i) -> i.id == rocketId
+ @items = _.without @items, item
+ @map?.renderTile(@display, item.x, item.y)
+
+ moveRocket: (data) ->
+ rocket = _.find @items, (i) -> i.id == data.id
+ if rocket
+ rocket.newX = data.x
+ rocket.newY = data.y
+ else
+ @addNewRocket data
module.exports = Game
View
12 client/app/scripts/hud.coffee
@@ -0,0 +1,12 @@
+gameEvents = require('./game-events')
@OsQu Owner
OsQu added a note

Nice one!

Any ideas how this could be integrated inside the canvas display so it looks more natural? Like in nethack:

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+class Hud
+ constructor: ->
+ gameEvents.globalBus.filter((ev) -> ev.target == 'hud').onValue @updateHud
+
+ updateHud: (ev) =>
+ hudData = ev.data
+ if hudData.health
+ $('.player-health').html(hudData.health)
+
+module.exports = Hud
View
2  client/app/scripts/main.coffee
@@ -2,7 +2,9 @@
console.log "Real mainjs"
Game = require('./game')
+Hud = require('./hud')
@OsQu Owner
OsQu added a note

Since Hud looks like a singleton to me, would it make sense to initialize it already to module.exports? Those are evaluated only once right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+hud = new Hud()
sinkohippa = new Game
sinkohippa.init()
View
3  client/app/scripts/map.coffee
@@ -22,4 +22,7 @@ class Map
_.each @tiles, (tile) =>
display.draw tile.x, tile.y, tile.getChar()
+ renderTile: (display, x, y) ->
+ tile = _.find @tiles, (t) -> t.x == x && t.y == y
+ display.draw tile.x, tile.y, tile.getChar()
module.exports = Map
View
67 client/app/scripts/message-handler.coffee
@@ -0,0 +1,67 @@
+gameEvents = require('./game-events')
+io = require('socket.io-client')
+
+Bacon = require('baconjs')
+_ = require('underscore')
+
+class MessageHandler
+ constructor: (@game) ->
+
+ connect: ->
+ @gameSocket = @connectToServer()
+ @bindEvents()
+
+ connectToServer: ->
+ io.connect('http://localhost:5000')
+
+ bindEvents: ->
+ gameEvents.socketMessage(@gameSocket, 'game-state').onValue @gotGameState
+ gameEvents.socketMessage(@gameSocket, 'map').onValue @updateMap
+ gameEvents.socketMessage(@gameSocket, 'info').onValue @gotServerInfo
+ gameEvents.socketMessage(@gameSocket, 'new-player').onValue @addNewPlayer
+ gameEvents.socketMessage(@gameSocket, 'player-leaving').onValue @playerLeaving
+ gameEvents.socketMessage(@gameSocket, 'player-state-changed').onValue @playerStateChanged
+ gameEvents.socketMessage(@gameSocket, 'rocket-moved').onValue @rocketMoved
+ gameEvents.socketMessage(@gameSocket, 'rocket-destroyed').onValue @rocketDestroyed
+
+ gameEvents.globalBus.filter((ev) -> ev.target == 'server').onValue @sendToServer
+ gotGameState: (event) =>
+ state = event.data
+ for part in state
+ switch part.type
+ when 'map' then @updateMap { data: part.state }
+ when 'player' then @addNewPlayer { data: part.state }
+
+ updateMap: (event) =>
+ @game.setNewMap(event.data)
+
+ gotServerInfo: (event) =>
+ console.log event.data
+
+ addNewPlayer: (ev) =>
+ playerData = ev.data
+ @game.addNewPlayer(playerData)
+
+ playerLeaving: (ev) =>
+ console.log "Removing player"
+ playerId = ev.data
+ @game.removePlayer(playerId)
+
+ playerStateChanged: (ev) =>
+ newData = ev.data
+ @game.playerStateChanged(newData)
+
+ rocketMoved: (ev) =>
+ @game.moveRocket(ev.data)
+
+ rocketDestroyed: (ev) =>
+ @game.removeRocket(ev.data.id)
+
+ sendToServer: (event) =>
+ serverData = event.data
+ @gameSocket.emit(serverData.key, serverData.data)
+
+ ourId: ->
+ @gameSocket.socket.sessionid
+
+module.exports = MessageHandler
View
25 client/app/scripts/rocket.coffee
@@ -0,0 +1,25 @@
+class Rocket
+ constructor: (@id, @x, @y, @shooter, @direction) ->
+
+ getChar: ->
+ if @direction == 'up' || @direction == 'down'
@OsQu Owner
OsQu added a note

TODO: Diagonal movement :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ '|'
+ else
+ '-'
+
+ render: (display) ->
+ @handleNewPosition(display)
+ display.draw(@x, @y, @getChar())
+
+ clearCurrentPosition: (display) ->
+ display.draw(@x, @y, '.')
+
+ handleNewPosition: (display) ->
+ if @newX or @newY
+ @clearCurrentPosition(display)
+ @x = @newX
+ @y = @newY
+ delete @newX
+ delete @newY
+
+module.exports = Rocket
View
9 client/app/styles/main.scss
@@ -1,7 +1,14 @@
+$pageWidth: 720px;
+
h1 {
text-align: center;
}
#game-container {
- width: 720px;
+ width: $pageWidth;
+ margin: 0 auto;
+}
+
+#player-info {
+ width: $pageWidth;
margin: 0 auto;
}
View
3  client/test/scripts/main.coffee
@@ -6,5 +6,8 @@ require('../spec/game-spec')
require('../spec/map-spec')
require('../spec/player-spec')
require('../spec/events-spec')
+require('../spec/rocket-spec')
+require('../spec/message-handler-spec')
+require('../spec/hud-spec')
mocha.run()
View
129 client/test/spec/game-spec.coffee
@@ -4,104 +4,107 @@ ROT = require('../scripts/vendor/rot.js/rot')
Bacon= require('baconjs')
Game = require('../scripts/game')
Player = require('../scripts/player')
-io = require('socket.io-client')
+Rocket = require('../scripts/rocket')
+
+MessageHandler = require('../scripts/message-handler')
+gameEvents = require('../scripts/game-events')
'use strict'
describe 'Game', ->
beforeEach ->
- mockSocket =
- on: ->
- emit: ->
- socket:
- sessionid: 'player-1'
- @connectStub = sinon.stub(io, 'connect', -> mockSocket)
+ @oldBus = gameEvents.globalBus
+ gameEvents.globalBus = new Bacon.Bus()
+
@requestAnimStub = sinon.stub(Game.prototype, 'requestAnimationFrame')
+ @ourId = 'player-1'
+ sinon.stub(MessageHandler.prototype, 'connect')
+ sinon.stub(MessageHandler.prototype, 'ourId', => @ourId)
@game = new Game()
afterEach ->
- @connectStub.restore()
+ gameEvents.globalBus = @oldBus
@requestAnimStub.restore()
+ MessageHandler.prototype.connect.restore?()
+ MessageHandler.prototype.ourId.restore?()
describe 'After initialization', ->
beforeEach ->
@game.init()
- it 'should have created map correctly', ->
- expect(@game.map).not.to.be.null
-
it 'should start polling when calling start', ->
@game.start()
expect(@requestAnimStub.called).to.be.true
- it 'should connect to web socket server', ->
- expect(@connectStub.called).to.be.true
-
- describe 'Got socket event', ->
- it 'should add players from game-state event', ->
- state =
- data:
- [
- {
- type: 'player'
- state:
- id: 'player-1'
- x: '5'
- y: '6'
- }
- ]
- @game.gotGameState(state)
- expect(@game.players.length).to.be.equals(1)
-
- it 'should update map from game-state event', ->
- state =
- data:
- [
- {
- type: 'map'
- state: [{x: 0, y: 0, wall: 1}]
- }
- ]
- @game.gotGameState(state)
+ describe 'Change game state', ->
+ it 'should set new map', ->
+ @game.setNewMap([{x: 0, y: 0, wall: 1}])
expect(@game.map).to.be.not.undefined
- it 'should update map from map event', ->
- @game.updateMap([{x: 0, y: 0, wall: 1}])
- expect(@game.map).to.be.not.undefined
-
- it 'should add new player from new-player event', ->
+ it 'should add new player', ->
@game.addNewPlayer
- data:
- id: 'player1'
- x: 1
- y: 1
-
+ id: 'player1'
+ x: 1
+ y: 1
expect(@game.players.length).to.be.equals(1)
- it 'should not add player from new-player event if player exists', ->
+ it 'should not add player from if player exists', ->
@game.players.push(new Player('player', x: 1, y: 1))
expect(@game.players.length).to.be.equals(1)
@game.addNewPlayer
- data:
- id: 'player'
- x: 1
- y: 1
+ id: 'player'
+ x: 1
+ y: 1
expect(@game.players.length).to.be.equals(1)
- it 'should delete player from player-leaving event', ->
+ it 'should remove player', ->
@game.players.push(new Player('player', x: 1, y: 1))
expect(@game.players.length).to.be.equals(1)
- @game.playerLeaving
- data: 'player'
+ @game.removePlayer 'player'
expect(@game.players.length).to.be.equals(0)
- it 'should change player state from player-state-changed event', ->
+ it 'should change player state', ->
@game.players.push(new Player 'player', 100, 100)
@game.playerStateChanged
- data:
- id: 'player'
- x: 99
- y: 101
+ id: 'player'
+ x: 99
+ y: 101
+ health: 4
expect(@game.players[0].newX).to.be.equals(99)
expect(@game.players[0].newY).to.be.equals(101)
+ expect(@game.players[0].health).to.be.equals(4)
+ it 'should push hud event if own player state changed', (done) ->
+ player = new Player @ourId, 100, 100
+ @game.players.push(player)
+ gameEvents.globalBus.filter((ev) -> ev.target == 'hud').onValue ->
+ done()
+ @game.playerStateChanged
+ id: @ourId
+ x: 99
+ y: 101
+
+ it 'should add rocket as an item if moving unknown rocket', ->
+ @game.moveRocket
+ direction: 'down'
+ id: 0
+ shooter: 'shooter-1'
+ x: 2
+ y: 5
+ expect(@game.items.length).to.be.equals(1)
+
+ it 'should move existing rocket', ->
+ @game.items.push new Rocket(0, 5, 5, 'shooter-1', 'right')
+ @game.moveRocket
+ direction: 'right'
+ id: 0
+ shooter: 'shooter-1'
+ x: 6
+ y: 5
+ expect(@game.items.length).to.be.equals(1)
+ expect(@game.items[0].newX).to.be.equals(6)
+
+ it 'should remove rocket', ->
+ @game.items.push new Rocket(0, 5, 5, 'shooter-1', 'right')
+ @game.removeRocket 0
+ expect(@game.items.length).to.be.equals(0)
View
39 client/test/spec/hud-spec.coffee
@@ -0,0 +1,39 @@
+expect = require('chai').expect
+Bacon = require('baconjs')
+Hud = require('../scripts/hud')
+Player = require('../scripts/player')
+gameEvents = require('../scripts/game-events')
+
+describe 'HUD', ->
+ beforeEach ->
+ @oldBus = gameEvents.globalBus
+ gameEvents.globalBus = new Bacon.Bus()
+
+ # Construting mock hud
+ @hudHtml = $('<div></div>')
+ @hudHtml.append($('<b class="player-health"></b>'))
+ $('body').append(@hudHtml)
+
+ sinon.spy(Hud.prototype, 'updateHud')
+ @hud = new Hud()
+
+ afterEach ->
+ gameEvents.globalBus = @oldBus
+ Hud.prototype.updateHud.restore?()
+ @hudHtml.remove()
+
+ it 'should be not undefined', ->
+ expect(@hud).to.be.not.undefined
+
+ it 'should update hud after hud-event', ->
+ player = new Player 'player-1', 100, 100
+ player.health = 3
+ gameEvents.globalBus.push { target: 'hud', data: player }
+ expect(Hud.prototype.updateHud.called).to.be.true
+
+ it 'should update player health', ->
+ @hud.updateHud
+ data:
+ health: 2
+ expect($('.player-health').html()).to.be.equals('2')
+
View
16 client/test/spec/map-spec.coffee
@@ -2,7 +2,8 @@
expect = require('chai').expect
ROT = require('../scripts/vendor/rot.js/rot')
-Bacon= require('baconjs')
+Bacon = require('baconjs')
+_ = require('underscore')
Map = require('../scripts/map')
describe 'Map', ->
beforeEach ->
@@ -22,6 +23,17 @@ describe 'Map', ->
displaySpy = sinon.spy ROT.Display.prototype, 'draw'
@map.render(new ROT.Display())
expect(displaySpy.called).to.be.true
- displaySpy.reset()
+ displaySpy.restore()
+ it 'should be able to render specific tile', ->
+ displaySpy = sinon.spy ROT.Display.prototype, 'draw'
+ tile = _.find @map.tiles, (t) -> t.x == 4 && t.y == 5
+ @map.renderTile(new ROT.Display(), 4, 5)
+ expect(displaySpy.called).to.be.true
+ args = displaySpy.firstCall.args
+ expect(args[0]).to.be.equals(4)
+ expect(args[1]).to.be.equals(5)
+ expect(args[2]).to.be.equals(tile.getChar())
+
+ displaySpy.restore()
View
114 client/test/spec/message-handler-spec.coffee
@@ -0,0 +1,114 @@
+expect = require('chai').expect
+io = require('socket.io-client')
+
+MessageHandler = require('../scripts/message-handler')
+gameEvents = require('../scripts/game-events')
+
+describe 'MessageHandler', ->
+ beforeEach ->
+ sinon.stub io, 'connect', ->
+ on: ->
+ socket: { sessionid: 'ownId' }
+
+ sinon.spy(gameEvents, 'socketMessage')
+ @game =
+ setNewMap: sinon.spy()
+ addNewPlayer: sinon.spy()
+ removePlayer: sinon.spy()
+ playerStateChanged: sinon.spy()
+ addNewRocket: sinon.spy()
+ removeRocket: sinon.spy()
+ moveRocket: sinon.spy()
+
+ @messageHandler = new MessageHandler @game
+
+ afterEach ->
+ io.connect.restore?()
+ gameEvents.socketMessage.restore?()
+
+ it 'should connect to server', ->
+ @messageHandler.connect()
+ expect(@messageHandler.gameSocket).to.be.not.undefined
+ expect(io.connect.called).to.be.true
+
+ it 'should bind socket events', ->
+ @messageHandler.connect()
+ expect(gameEvents.socketMessage.called).to.be.true
+
+ describe 'Got socket event', ->
+ it 'should add new player to Game when got game state', ->
+ state =
+ data:
+ [
+ {
+ type: 'player'
+ state:
+ id: 'player-1'
+ x: '5'
+ y: '6'
+ }
+ ]
+ @messageHandler.gotGameState(state)
+ expect(@game.addNewPlayer.called).to.be.true
+
+ it 'should update map to Game from game state event', ->
+ state =
+ data:
+ [
+ {
+ type: 'map'
+ state: [{x: 0, y: 0, wall: 1}]
+ }
+ ]
+ @messageHandler.gotGameState(state)
+ expect(@game.setNewMap.called).to.be.true
+
+ it 'should update map to Game from map event', ->
+ @messageHandler.updateMap([{x: 0, y: 0, wall: 1}])
+ expect(@game.setNewMap.called).to.be.true
+
+ it 'should add new player to Game from new-player event', ->
+ @messageHandler.addNewPlayer
+ data:
+ id: 'player1'
+ x: 1
+ y: 1
+ expect(@game.addNewPlayer.called).to.be.true
+
+ it 'should delete player to Game from player-leaving event', ->
+ @messageHandler.playerLeaving
+ data: 'player'
+
+ expect(@game.removePlayer.called).to.be.true
+
+ it 'should change player state to Game from player-state-changed event', ->
+ @messageHandler.playerStateChanged
+ data:
+ id: 'player'
+ x: 99
+ y: 101
+ expect(@game.playerStateChanged.called).to.be.true
+
+ it 'should move rocket in Game from rocket-moving event', ->
+ @messageHandler.rocketMoved
+ data:
+ direction: 'down'
+ id: 0
+ shooter: 'shooter-1'
+ x: 2
+ y: 5
+ expect(@game.moveRocket.called).to.be.true
+
+ it 'should remove rocket in Game from rocket-destroyed event', ->
+ @messageHandler.rocketDestroyed
+ data:
+ shooter: 'shooter-1'
+ id: 0
+ x: 5
+ y: 5
+ direction: 'up'
+ expect(@game.removeRocket.called).to.be.true
+
+ it 'should give our id when asking it', ->
+ @messageHandler.connect()
+ expect(@messageHandler.ourId()).to.be.equals('ownId')
View
1  client/test/spec/player-spec.coffee
@@ -4,6 +4,7 @@ Bacon = require('baconjs')
Player = require('../scripts/player')
gameEvents = require('../scripts/game-events')
+
describe 'Player', ->
beforeEach ->
@player = new Player '', 0, 0
View
39 client/test/spec/rocket-spec.coffee
@@ -0,0 +1,39 @@
+expect = require('chai').expect
+
+ROT = require('../scripts/vendor/rot.js/rot')
+Bacon= require('baconjs')
+Rocket = require('../scripts/rocket')
+
+describe 'Rocket', ->
+ beforeEach ->
+ @rocket = new Rocket(0, 1, 1, 'shooter-1', 'right')
+
+ it 'should have correct char when flying horizontally', ->
+ expect(@rocket.getChar()).to.be.equal '-'
+
+ it 'should have correct char when flying vertically', ->
+ @rocket.direction = 'down'
+ expect(@rocket.getChar()).to.be.equal '|'
+
+ it 'should render rocket', (done) ->
+ mockDisplay =
+ draw: (x, y, char) ->
+ expect(char).to.be.equal '-'
+ done()
+ @rocket.render(mockDisplay)
+
+ it 'should move rocket to new position if possible', ->
+ @rocket.newX = 2
+ @rocket.newY = 1
+ @rocket.handleNewPosition { draw: -> }
+ expect(@rocket.x).to.be.equal(2)
+ expect(@rocket.y).to.be.equal(1)
+
+ it 'should clear current position if new position exists', ->
+ @rocket.newX = 2
+ drawSpy = sinon.spy()
+ @rocket.handleNewPosition { draw: drawSpy }
+ expect(drawSpy.called).to.be.true
+ expect(drawSpy.firstCall.args[0]).to.be.equals(1)
+ expect(drawSpy.firstCall.args[2]).to.be.equals('.')
+
View
27 server/app/actors/player-actor.coffee
@@ -5,13 +5,15 @@ class PlayerActor
@type = 'player'
@x = 1
@y = 1
+ @health = 5
+ @shootCooldown = 500
@manager.globalBus.push { type: 'BROADCAST', key: 'new-player', data: @getState() }
@bindEvents()
bindEvents: ->
@unsubscribeMovePlayer = @manager.globalBus.filter((ev) => ev.id == @id).filter((ev) => ev.type == 'PLAYER_MOVE').onValue @movePlayer
- @unsubscribeShoot = @manager.globalBus.filter((ev) => ev.id == @id).filter((ev) => ev.type == 'PLAYER_SHOOT').onValue @shootWithPlayer
+ @unsubscribeShoot = @manager.globalBus.filter((ev) => ev.id == @id).filter((ev) => ev.type == 'PLAYER_SHOOT').debounceImmediate(@shootCooldown).onValue @shootWithPlayer
@OsQu Owner
OsQu added a note

SWEEEET!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@unsubscribeRocketMoved = @manager.globalBus.filter((ev) => ev.type == 'ROCKET_MOVED').filter((ev) => ev.x == @x && ev.y == @y).onValue @rocketHit
destroy: ->
@@ -25,8 +27,13 @@ class PlayerActor
id: @id
x: @x
y: @y
+ health: @health
state
+
+ broadcastStateChanged: ->
+ @manager.globalBus.push { type: 'BROADCAST', key: 'player-state-changed', data: @getState() }
+
movePlayer: (ev) =>
debug "Moving player #{@id}"
mapActor = @manager.getMapActor()
@@ -35,8 +42,7 @@ class PlayerActor
when 'down' then if mapActor.canMove(@x, @y + 1) then @y++
when 'left' then if mapActor.canMove(@x - 1, @y) then @x--
when 'right' then if mapActor.canMove(@x + 1, @y) then @x++
-
- @manager.globalBus.push { type: 'BROADCAST', key: 'player-state-changed', data: @getState() }
+ @broadcastStateChanged()
shootWithPlayer: (ev) =>
debug "Player #{@id} is shooting"
@@ -45,5 +51,20 @@ class PlayerActor
rocketHit: (ev) =>
debug "Player #{@id} got hit by rocket #{ev.rocketId}"
@manager.deleteRocketActor(ev.rocketId)
+ @reduceHealth ev.damage
+
+ reduceHealth: (amount) ->
+ @health = @health - amount
+ debug "Reduced player #{@id} health to #{@health}"
+ if @health <= 0 then @die()
+ @broadcastStateChanged()
+
+ # For now just respawns player back to starting point
+ die: ->
+ debug "Crap! (For player #{@id}). It died :("
+ @x = 1
+ @y = 1
+ @health = 5
+ @broadcastStateChanged()
module.exports = PlayerActor
View
14 server/app/actors/rocket-actor.coffee
@@ -3,7 +3,8 @@ debug = require('debug')('sh:rocket-actor')
class RocketActor
constructor: (@manager, @id, @shooterId, @x, @y, @direction) ->
@type = 'rocket'
- @speed = 200 # 200ms / square
+ @speed = 100 # ms / square
+ @damage = 1
@startMoving()
getState: ->
@@ -13,12 +14,15 @@ class RocketActor
x: @x
y: @y
direction: @direction
+ damage: @damage
state
destroy: ->
- @manager.globalBus.push { type: 'BROADCAST', key: 'rocket-destroyed', data: @getState() }
- debug("Destroying rocket #{@id}")
- @stopMoving()
+ # We need to destroy rocket after the moving tick has happened
+ process.nextTick =>
+ debug("Destroying rocket #{@id}")
+ @stopMoving()
+ @manager.globalBus.push { type: 'BROADCAST', key: 'rocket-destroyed', data: @getState() }
startMoving: ->
@intervalId = setInterval(@move, @speed)
@@ -32,7 +36,7 @@ class RocketActor
when 'down' then @y++
when 'left' then @x--
when 'right' then @x++
- @manager.globalBus.push { type: 'ROCKET_MOVED', rocketId: @id, x: @x, y: @y }
+ @manager.globalBus.push { type: 'ROCKET_MOVED', rocketId: @id, x: @x, y: @y, damage: @damage }
@manager.globalBus.push { type: 'BROADCAST', key: 'rocket-moved', data: @getState() }
debug("Moved rocket #{@id}")
View
3  server/package.json
@@ -16,7 +16,8 @@
"grunt-mocha-test": "~0.6.2",
"grunt": "~0.4.1",
"grunt-contrib-watch": "~0.5.1",
- "matchdep": "~0.1.1"
+ "matchdep": "~0.1.1",
+ "webkit-devtools-agent": "~0.1.1"
},
"dependencies": {
"coffee-script": "~1.6.1",
View
45 server/test/spec/player-actor-spec.coffee
@@ -8,18 +8,23 @@ PlayerActor = require('../../app/actors/player-actor')
describe 'PlayerActor', ->
beforeEach ->
+ @clock = sinon.useFakeTimers(0)
+
@oldBus = actorManager.globalBus
actorManager.globalBus = new Bacon.Bus()
@movePlayerSpy = sinon.spy PlayerActor.prototype, 'movePlayer'
@rocketHitSpy = sinon.spy PlayerActor.prototype, 'rocketHit'
+ @shootWithPlayerSpy = sinon.spy PlayerActor.prototype, 'shootWithPlayer'
@playerActor = new PlayerActor(actorManager, '123')
afterEach ->
+ @clock.restore()
actorManager.globalBus = @oldBus
@movePlayerSpy.restore()
@rocketHitSpy.restore()
+ @shootWithPlayerSpy.restore()
it 'should be correct type', ->
@playerActor.type.should.be.eql('player')
@@ -29,6 +34,7 @@ describe 'PlayerActor', ->
state.id.should.be.eql('123')
state.x.should.be.eql(1)
state.y.should.be.eql(1)
+ state.health.should.be.eql(5)
it 'should respond to own player_move event', ->
actorManager.globalBus.push
@@ -73,6 +79,43 @@ describe 'PlayerActor', ->
rocketActor = _.last actorManager.actors
rocketActor.type.should.be.eql('rocket')
- it 'should be able to hit player with rocket', ->
+ it 'should throttle rocket shootings', ->
+ actorManager.globalBus.push { type: "PLAYER_SHOOT", id: @playerActor.id }
+ actorManager.globalBus.push { type: "PLAYER_SHOOT", id: @playerActor.id }
+ @shootWithPlayerSpy.callCount.should.be.eql(1)
+ @clock.tick(@playerActor.shootCooldown)
+ actorManager.globalBus.push { type: "PLAYER_SHOOT", id: @playerActor.id }
+ @shootWithPlayerSpy.callCount.should.be.eql(2)
+
+ it 'should be able to be hit by rocket', ->
actorManager.globalBus.push { type: 'ROCKET_MOVED', x: @playerActor.x, y: @playerActor.y }
@rocketHitSpy.called.should.be.true
+
+ it 'should reduce health when hit by rocket', ->
+ @playerActor.rocketHit
+ rocketId: 'rocket-1'
+ damage: 1
+ x: @playerActor.x
+ y: @playerActor.y
+ @playerActor.health.should.be.eql(4)
+
+ it 'should broadcast player-state-changed when losing health', (done) ->
+ actorManager.globalBus.filter((ev) -> ev.type == 'BROADCAST').onValue (ev) ->
+ done()
+
+ @playerActor.reduceHealth(1)
+
+ it 'should die if health is reduced to zero', ->
+ @playerActor.x = 10
+ @playerActor.y = 12
+ @playerActor.health = 3
+ @playerActor.reduceHealth(3)
+ @playerActor.x.should.be.eql(1)
+ @playerActor.y.should.be.eql(1)
+ @playerActor.health.should.be.eql(5)
+
+ it 'should broadcast player-state-changed when dying', (done) ->
+ actorManager.globalBus.filter((ev) -> ev.type == 'BROADCAST').onValue (ev) ->
+ done()
+ @playerActor.die()
+
View
15 server/test/spec/rocket-actor-spec.coffee
@@ -27,6 +27,7 @@ describe 'RocketActor', ->
state.x.should.be.eql(5)
state.y.should.be.eql(4)
state.direction.should.be.eql('right')
+ state.damage.should.be.eql(1)
it 'should have rocket speed', ->
should.exist(@rocketActor.speed)
@@ -46,7 +47,12 @@ describe 'RocketActor', ->
@rocketActor.x.should.be.eql(7)
it 'should emit rocket-moved event when moving the rocket', (done) ->
- actorManager.globalBus.filter((ev) -> ev.type == 'ROCKET_MOVED').onValue (ev) -> done()
+ actorManager.globalBus.filter((ev) -> ev.type == 'ROCKET_MOVED').onValue (ev) ->
+ ev.rocketId.should.be.eql('rocket-1')
+ ev.x.should.be.eql(6)
+ ev.y.should.be.eql(4)
+ ev.damage.should.be.eql(1)
+ done()
@rocketActor.move()
@@ -64,10 +70,11 @@ describe 'RocketActor', ->
@clock.tick(@rocketActor.speed)
@rocketActor.x.should.be.eql(6)
- it 'should be able to destroy rocket', ->
+ it 'should be able to destroy rocket', (done) ->
+ actorManager.globalBus.filter((ev) => ev.type == 'BROADCAST').onValue (ev) =>
+ ev.key.should.be.eql('rocket-destroyed')
+ done()
@rocketActor.destroy()
- @clock.tick(@rocketActor.speed)
- @rocketActor.x.should.be.eql(5)
it 'should broadcast rocket-destroyed event when rocket is destroyed', (done) ->
actorManager.globalBus.filter((ev) => ev.type == 'BROADCAST').onValue (ev) ->
View
1  server/vagrant-utils.sh
@@ -10,6 +10,7 @@ then
echo "alias debug='DEBUG=sh:* PORT=5000 ./node_modules/.bin/coffee --nodejs debug app/app.coffee'" >> /home/vagrant/.bash_aliases
chown vagrant:vagrant /home/vagrant/.bash_aliases
+ aptitude install -y git
aptitude install -y ruby
aptitude install -y rubygems1.8
gem install foreman
Something went wrong with that request. Please try again.