Playing with Ruby
How to write an online, real-time
multi-player game with Ruby
@sausheong
Sunday, 9 June, 13
About me
Sunday, 9 June, 13
About me
Sunday, 9 June, 13
About me
Sunday, 9 June, 13
About me
Sunday, 9 June, 13
About me
Sunday, 9 June, 13
About me
Sunday, 9 June, 13
About me
Sunday, 9 June, 13
About me
Sunday, 9 June, 13
About me
Sunday, 9 June, 13
About me
Sunday, 9 June, 13
Press start
Sunday, 9 June, 13
Sunday, 9 June, 13
2D game development library
C++-based, with Ruby wrapper
OS X, Windows and Linux
Works with MRI, MacRuby, Rubinius
(but not JRuby)
Sunday, 9 June, 13
Draw the game
window
Sunday, 9 June, 13
require 'gosu'
class GameWindow < Gosu::Window
def initialize
super 640, 480, false
self.caption = "Tutorial 1"
end
def update
end
def draw
end
end
window = GameWindow.new
window.show
Sunday, 9 June, 13
Sunday, 9 June, 13
update method
called at every frame (60 frames per
second)
Contains game logic
The main ‘controller’ of the game
Sunday, 9 June, 13
draw method
Does the actual drawing of the game
window
Called after update
Can also be called when necessary
Sunday, 9 June, 13
Add background
Sunday, 9 June, 13
require 'gosu'
class GameWindow < Gosu::Window
def initialize
super 640, 480, false
self.caption = "Tutorial 2"
@background_image = Gosu::Image.new(self, "bg1.jpg", true)
end
def update
end
def draw
@background_image.draw(0, 0, 0)
end
end
window = GameWindow.new
window.show
x
y
z
Sunday, 9 June, 13
Sunday, 9 June, 13
Add a player
Sunday, 9 June, 13
class Player
def initialize(window)
@image = Image.new window, "plane.png", false
@x = @y = @vel_x = @vel_y = @angle = 0.0
end
def warp(x, y)
@x, @y = x, y
end
def turn_left
@angle -= 5
end
def turn_right
@angle += 5
end
def accelerate
@vel_x += offset_x(@angle, 5)
@vel_y += offset_y(@angle, 5)
end
def move
@x += @vel_x
@y += @vel_y
@x %= 640
@y %= 480
@vel_x, @vel_y = 0, 0
end
def draw
@image.draw_rot(@x, @y, 1, @angle)
end
end
@angle
5
offset_y
offset_x
Sunday, 9 June, 13
class GameWindow < Window
def initialize
super(640, 480, false)
self.caption = "Tutorial 3"
@background_image = Image.new(self, "bg1.jpg", true)
@player = Player.new(self)
@player.warp(320, 240)
end
def update
@player.turn_left if button_down? KbLeft
@player.turn_right if button_down? KbRight
@player.accelerate if button_down? KbUp
@player.move
end
def draw
@player.draw
@background_image.draw(0, 0, 0)
end
end
Sunday, 9 June, 13
class GameWindow < Window
def initialize
super(640, 480, false)
self.caption = "Tutorial 3"
@background_image = Image.new(self, "bg1.jpg", true)
@player = Player.new(self)
@player.warp(320, 240)
end
def update
@player.turn_left if button_down? KbLeft
@player.turn_right if button_down? KbRight
@player.accelerate if button_down? KbUp
@player.move
end
def draw
@player.draw
@background_image.draw(0, 0, 0)
end
end
create player
Sunday, 9 June, 13
class GameWindow < Window
def initialize
super(640, 480, false)
self.caption = "Tutorial 3"
@background_image = Image.new(self, "bg1.jpg", true)
@player = Player.new(self)
@player.warp(320, 240)
end
def update
@player.turn_left if button_down? KbLeft
@player.turn_right if button_down? KbRight
@player.accelerate if button_down? KbUp
@player.move
end
def draw
@player.draw
@background_image.draw(0, 0, 0)
end
end
place him in middle of screen
Sunday, 9 June, 13
class GameWindow < Window
def initialize
super(640, 480, false)
self.caption = "Tutorial 3"
@background_image = Image.new(self, "bg1.jpg", true)
@player = Player.new(self)
@player.warp(320, 240)
end
def update
@player.turn_left if button_down? KbLeft
@player.turn_right if button_down? KbRight
@player.accelerate if button_down? KbUp
@player.move
end
def draw
@player.draw
@background_image.draw(0, 0, 0)
end
end
} move according to
user input
Sunday, 9 June, 13
class GameWindow < Window
def initialize
super(640, 480, false)
self.caption = "Tutorial 3"
@background_image = Image.new(self, "bg1.jpg", true)
@player = Player.new(self)
@player.warp(320, 240)
end
def update
@player.turn_left if button_down? KbLeft
@player.turn_right if button_down? KbRight
@player.accelerate if button_down? KbUp
@player.move
end
def draw
@player.draw
@background_image.draw(0, 0, 0)
end
end
draw the player
Sunday, 9 June, 13
Sunday, 9 June, 13
Add sound
Sunday, 9 June, 13
def initialize(window)
@image = Image.new window, "plane.png", false
@sound = Sample.new window, "plane.wav"
@x = @y = @vel_x = @vel_y = @angle = 0.0
end
.
.
.
def accelerate
@sound.play
@vel_x += offset_x(@angle, 5)
@vel_y += offset_y(@angle, 5)
end
Sunday, 9 June, 13
def initialize(window)
@image = Image.new window, "plane.png", false
@sound = Sample.new window, "plane.wav"
@x = @y = @vel_x = @vel_y = @angle = 0.0
end
.
.
.
def accelerate
@sound.play
@vel_x += offset_x(@angle, 5)
@vel_y += offset_y(@angle, 5)
end
Load the sound
Sunday, 9 June, 13
def initialize(window)
@image = Image.new window, "plane.png", false
@sound = Sample.new window, "plane.wav"
@x = @y = @vel_x = @vel_y = @angle = 0.0
end
.
.
.
def accelerate
@sound.play
@vel_x += offset_x(@angle, 5)
@vel_y += offset_y(@angle, 5)
end
play the sound!
Sunday, 9 June, 13
Demo
Sunday, 9 June, 13
Level 1
Complete!
Sunday, 9 June, 13
Use sprite sheets
Sunday, 9 June, 13
Sprites
An image or animation that’s overlaid
on the background
Use single sprites (as before) or use
sprite sheets
Sprites normally represented by a
square image
Sunday, 9 June, 13
Sprite sheet
A bunch of images in a single file, used
as sprites
Often placed in sequence, image can be
retrieved from knowing the location
Reduces memory usage and increase
drawing speed
Sunday, 9 June, 13
Sunday, 9 June, 13
5948
25 26 27 28 29 30 31 32 33 34 35 36 37 38 3924
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 230
72
100 101 10296
Sunday, 9 June, 13
module SpriteImage
Grass = 102
Earth = 101
Gravel = 100
Wall = 59
Bullet= 28
Tank = 39
end
Locate sprites in a
sprite sheet
Sunday, 9 June, 13
Player:
def initialize(window)
@image = Image.new window, "plane.png", false
end
def draw
@image.draw_rot(@x, @y, 1, @angle)
end
GameWindow:
@spritesheet = Image.load_tiles(self, 'sprites.png', 32, 32,
true)
Player:
@window.spritesheet[SpriteImage::Tank].draw_rot(@x, @y, 1,
@angle)
Sunday, 9 June, 13
Create editable maps
Sunday, 9 June, 13
Editable maps
Allows user to customize maps and
backgrounds, using tiled sprites
.....................
.....................
.....................
.....................
.....................
..##............##...
...#............#....
...#............#....
...#............#....
..##............##...
.....................
.....................
.....................
.....................
.....................
Sunday, 9 June, 13
.....................
.....................
.....................
.....................
.....................
..##............##...
...#............#....
...#............#....
...#............#....
..##............##...
.....................
.....................
.....................
.....................
.....................
20 x 32 = 640
15x32=480 class Map
def initialize(window, mapfile)
lines = File.readlines(mapfile).map
{ |line| line.chomp }
@window, @height, @width = window,
lines.size, lines.first.size
@tiles = Array.new(@width) do |x|
Array.new(@height) do |y|
case lines[y][x]
when '.'
SpriteImage::Earth
when "#"
SpriteImage::Wall
when '"'
SpriteImage::Grass
end
end
end
end
def draw
@height.times do |y|
@width.times do |x|
tile = @tiles[x][y]
@window.spritesheet[tile].draw(x *
32, y * 32, 1)
end
end
Sunday, 9 June, 13
Level 2
Complete!
Sunday, 9 June, 13
Let’s play with
others
Sunday, 9 June, 13
Sunday, 9 June, 13
Design
Real-time vs turn-based (immediate
response)
Latency (speed) is critical
‘Dead’ players can still observe the game
Game spectators
Sunday, 9 June, 13
Design
Client-server
All artifacts are local
Only messages sent back and forth the
client-server
Minimal size messages
Messages sent from client -> server once
every frame refresh
Sunday, 9 June, 13
Design
Server should have minimal processing, all
game logic should be in the client
Server should only receive messages and
broadcast to all clients
Messages not compressed/encoded (takes
time at the server)
Don’t send useless messages
Sunday, 9 June, 13
Game flow
Sunday, 9 June, 13
Game server starts
Game
Server
Sunday, 9 June, 13
Tank 1 starts, sends
message to server
Game
Server
Tank1
object:tank1
object:tank1
Tank 1 ignores
messages that is
about itself
Sunday, 9 June, 13
Server simply stores and broadcasts all
messages sent to it to reduce processing
Logic to process or ignore messages are
in the client
Sunday, 9 June, 13
Tank 2 starts
Game
Server
Tank1
object:tank1
object:tank1
Tank2
object:tank2
object:tank1
object:tank2
object:tank2
Tank 1 receives messages
from server about Tank 2,
starts drawing Tank 2
Sunday, 9 June, 13
Tank 2 moves
Game
Server
Tank1
object:tank1
object:tank1
Tank2
object:tank2
object:tank1
object:tank2
object:tank2
When Tank 2 moves, its
position is sent to the server
and broadcast to everyone
Sunday, 9 June, 13
Tank 1 shoots
Game
Server
Tank1
object:tank1
object:tank1
Tank2
object:tank2
object:tank1
object:tank2
object:tank2
object:shot1
object:shot1
object:shot1
Tank 1 creates a shot,
message sent to server and
broadcast to everyone
Sunday, 9 June, 13
Shot goes out of range
Game
Server
Tank1
object:tank1
object:tank1
Tank2
object:tank2
object:tank1
object:tank2
object:tank2
delete:shot1
delete:shot1
delete:shot1
When the shot goes out of
range, Tank 1 sends a delete
message to the server,
broadcasted to everyone
Sunday, 9 June, 13
Tank 1 shot hits Tank 2
Game
Server
Tank1
object:tank1
object:tank1
Tank2
object:tank2
object:tank1
object:tank2
object:tank2
object:shot1
object:shot1
object:shot1
If Tank 1’s shot hits Tank 2,
reduce hit points from Tank1
Sunday, 9 June, 13
Tank 2 destroyed
Game
Server
Tank1
object:tank1
object:tank1
Tank2
object:tank1
object:shot1
object:shot1
object:shot1
When Tank 2’s hit points fall
below 0 it is destroyed
Sunday, 9 June, 13
Message passing
Sunday, 9 June, 13
Messages are string delimited with
vertical bar (|)
Messages are accumulated till and sent
only 1 time in a frame refresh
Messages from client -> server :
message type + sprite
Message from server -> client : sprite
only
Sunday, 9 June, 13
"#{msg_type}|
#{sprite.uuid}|
#{sprite.type}|
#{sprite.sprite_image}|
#{sprite.player}|
#{sprite.x}|
#{sprite.y}|
#{sprite.angle}|
#{sprite.points}|
#{sprite.color}"
Sunday, 9 June, 13
"#{msg_type}|
#{sprite.uuid}|
#{sprite.type}|
#{sprite.sprite_image}|
#{sprite.player}|
#{sprite.x}|
#{sprite.y}|
#{sprite.angle}|
#{sprite.points}|
#{sprite.color}"
‘obj’ or ‘del’
Sunday, 9 June, 13
"#{msg_type}|
#{sprite.uuid}|
#{sprite.type}|
#{sprite.sprite_image}|
#{sprite.player}|
#{sprite.x}|
#{sprite.y}|
#{sprite.angle}|
#{sprite.points}|
#{sprite.color}"
a unique identifier
for the sprite
Sunday, 9 June, 13
"#{msg_type}|
#{sprite.uuid}|
#{sprite.type}|
#{sprite.sprite_image}|
#{sprite.player}|
#{sprite.x}|
#{sprite.y}|
#{sprite.angle}|
#{sprite.points}|
#{sprite.color}"
tank or shot
Sunday, 9 June, 13
"#{msg_type}|
#{sprite.uuid}|
#{sprite.type}|
#{sprite.sprite_image}|
#{sprite.player}|
#{sprite.x}|
#{sprite.y}|
#{sprite.angle}|
#{sprite.points}|
#{sprite.color}"
player name
Sunday, 9 June, 13
"#{msg_type}|
#{sprite.uuid}|
#{sprite.type}|
#{sprite.sprite_image}|
#{sprite.player}|
#{sprite.x}|
#{sprite.y}|
#{sprite.angle}|
#{sprite.points}|
#{sprite.color}"
only valid
for tanks
Sunday, 9 June, 13
Game Client
Sunday, 9 June, 13
Client sends
messages to the
server
Sunday, 9 June, 13
def update
begin
move_tank
px, py = @me.x, @me.y
@me.move
@me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield?
@other_tanks.each do |player, tank|
@me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30)
end
add_to_message_queue('obj', @me)
@other_shots.each_value do |shot|
if @me.alive? and @me.collide_with?(shot, 16)
@me.hit
add_to_message_queue('obj', @me)
end
end
@me_shots.each do |shot|
shot.move # move the bullet
if shot.hit_wall? or shot.outside_battlefield?
@me_shots.delete shot
add_to_message_queue('del', shot)
else
add_to_message_queue('obj', shot)
end
end
@client.send_message @messages.join("n")
@messages.clear
Sunday, 9 June, 13
def update
begin
move_tank
px, py = @me.x, @me.y
@me.move
@me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield?
@other_tanks.each do |player, tank|
@me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30)
end
add_to_message_queue('obj', @me)
@other_shots.each_value do |shot|
if @me.alive? and @me.collide_with?(shot, 16)
@me.hit
add_to_message_queue('obj', @me)
end
end
@me_shots.each do |shot|
shot.move # move the bullet
if shot.hit_wall? or shot.outside_battlefield?
@me_shots.delete shot
add_to_message_queue('del', shot)
else
add_to_message_queue('obj', shot)
end
end
@client.send_message @messages.join("n")
@messages.clear
store my previous coordinates
Sunday, 9 June, 13
def update
begin
move_tank
px, py = @me.x, @me.y
@me.move
@me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield?
@other_tanks.each do |player, tank|
@me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30)
end
add_to_message_queue('obj', @me)
@other_shots.each_value do |shot|
if @me.alive? and @me.collide_with?(shot, 16)
@me.hit
add_to_message_queue('obj', @me)
end
end
@me_shots.each do |shot|
shot.move # move the bullet
if shot.hit_wall? or shot.outside_battlefield?
@me_shots.delete shot
add_to_message_queue('del', shot)
else
add_to_message_queue('obj', shot)
end
end
@client.send_message @messages.join("n")
@messages.clear
move!
Sunday, 9 June, 13
def update
begin
move_tank
px, py = @me.x, @me.y
@me.move
@me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield?
@other_tanks.each do |player, tank|
@me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30)
end
add_to_message_queue('obj', @me)
@other_shots.each_value do |shot|
if @me.alive? and @me.collide_with?(shot, 16)
@me.hit
add_to_message_queue('obj', @me)
end
end
@me_shots.each do |shot|
shot.move # move the bullet
if shot.hit_wall? or shot.outside_battlefield?
@me_shots.delete shot
add_to_message_queue('del', shot)
else
add_to_message_queue('obj', shot)
end
end
@client.send_message @messages.join("n")
@messages.clear
go back to previous
coordinates if I hit the
wall, go out or hit
another tank
Sunday, 9 June, 13
def update
begin
move_tank
px, py = @me.x, @me.y
@me.move
@me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield?
@other_tanks.each do |player, tank|
@me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30)
end
add_to_message_queue('obj', @me)
@other_shots.each_value do |shot|
if @me.alive? and @me.collide_with?(shot, 16)
@me.hit
add_to_message_queue('obj', @me)
end
end
@me_shots.each do |shot|
shot.move # move the bullet
if shot.hit_wall? or shot.outside_battlefield?
@me_shots.delete shot
add_to_message_queue('del', shot)
else
add_to_message_queue('obj', shot)
end
end
@client.send_message @messages.join("n")
@messages.clear
add me to the list of
messages to send to
server
Sunday, 9 June, 13
def update
begin
move_tank
px, py = @me.x, @me.y
@me.move
@me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield?
@other_tanks.each do |player, tank|
@me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30)
end
add_to_message_queue('obj', @me)
@other_shots.each_value do |shot|
if @me.alive? and @me.collide_with?(shot, 16)
@me.hit
add_to_message_queue('obj', @me)
end
end
@me_shots.each do |shot|
shot.move # move the bullet
if shot.hit_wall? or shot.outside_battlefield?
@me_shots.delete shot
add_to_message_queue('del', shot)
else
add_to_message_queue('obj', shot)
end
end
@client.send_message @messages.join("n")
@messages.clear
check the other shots
on screen to see if it
hits me, if it does, tell
the server I was hit
Sunday, 9 June, 13
def update
begin
move_tank
px, py = @me.x, @me.y
@me.move
@me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield?
@other_tanks.each do |player, tank|
@me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30)
end
add_to_message_queue('obj', @me)
@other_shots.each_value do |shot|
if @me.alive? and @me.collide_with?(shot, 16)
@me.hit
add_to_message_queue('obj', @me)
end
end
@me_shots.each do |shot|
shot.move # move the bullet
if shot.hit_wall? or shot.outside_battlefield?
@me_shots.delete shot
add_to_message_queue('del', shot)
else
add_to_message_queue('obj', shot)
end
end
@client.send_message @messages.join("n")
@messages.clear
move my shots, if it
hits the wall or goes
out, remove it
Sunday, 9 June, 13
def update
begin
move_tank
px, py = @me.x, @me.y
@me.move
@me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield?
@other_tanks.each do |player, tank|
@me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30)
end
add_to_message_queue('obj', @me)
@other_shots.each_value do |shot|
if @me.alive? and @me.collide_with?(shot, 16)
@me.hit
add_to_message_queue('obj', @me)
end
end
@me_shots.each do |shot|
shot.move # move the bullet
if shot.hit_wall? or shot.outside_battlefield?
@me_shots.delete shot
add_to_message_queue('del', shot)
else
add_to_message_queue('obj', shot)
end
end
@client.send_message @messages.join("n")
@messages.clear
if not, tell the server
its new position
Sunday, 9 June, 13
def update
begin
move_tank
px, py = @me.x, @me.y
@me.move
@me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield?
@other_tanks.each do |player, tank|
@me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30)
end
add_to_message_queue('obj', @me)
@other_shots.each_value do |shot|
if @me.alive? and @me.collide_with?(shot, 16)
@me.hit
add_to_message_queue('obj', @me)
end
end
@me_shots.each do |shot|
shot.move # move the bullet
if shot.hit_wall? or shot.outside_battlefield?
@me_shots.delete shot
add_to_message_queue('del', shot)
else
add_to_message_queue('obj', shot)
end
end
@client.send_message @messages.join("n")
@messages.clear
all my actions are
processed, now to
send messages to
server
Sunday, 9 June, 13
"msg_type|uuid|type|sprite_image|player|x|y|angle|points|color"
"msg_type|uuid|type|sprite_image|player|x|y|angle|points|color"
"msg_type|uuid|type|sprite_image|player|x|y|angle|points|color"
"msg_type|uuid|type|sprite_image|player|x|y|angle|points|color"
"msg_type|uuid|type|sprite_image|player|x|y|angle|points|color"
clientmessage
Sunday, 9 June, 13
Client reads
messages from the
server
Sunday, 9 June, 13
if msg = @client.read_message
@valid_sprites.clear
data = msg.split("n")
data.each do |row|
sprite = row.split("|")
if sprite.size == 9
player = sprite[3]
@valid_sprites << sprite[0]
case sprite[1]
when 'tank'
unless player == @player
if @other_tanks[player]
@other_tanks[player].points = sprite[7].to_i
@other_tanks[player].warp_to(sprite[4], sprite[5], sprite[6])
else
@other_tanks[player] = Tank.from_sprite(self, sprite)
end
else
@me.points = sprite[7].to_i
end
when 'shot'
unless player == @player
shot = Shot.from_sprite(self, sprite)
@other_shots[shot.uuid] = shot
shot.warp_to(sprite[4], sprite[5], sprite[6])
end
end
end
end
Sunday, 9 June, 13
if msg = @client.read_message
@valid_sprites.clear
data = msg.split("n")
data.each do |row|
sprite = row.split("|")
if sprite.size == 9
player = sprite[3]
@valid_sprites << sprite[0]
case sprite[1]
when 'tank'
unless player == @player
if @other_tanks[player]
@other_tanks[player].points = sprite[7].to_i
@other_tanks[player].warp_to(sprite[4], sprite[5], sprite[6])
else
@other_tanks[player] = Tank.from_sprite(self, sprite)
end
else
@me.points = sprite[7].to_i
end
when 'shot'
unless player == @player
shot = Shot.from_sprite(self, sprite)
@other_shots[shot.uuid] = shot
shot.warp_to(sprite[4], sprite[5], sprite[6])
end
end
end
end
read messages from the server
Sunday, 9 June, 13
if msg = @client.read_message
@valid_sprites.clear
data = msg.split("n")
data.each do |row|
sprite = row.split("|")
if sprite.size == 9
player = sprite[3]
@valid_sprites << sprite[0]
case sprite[1]
when 'tank'
unless player == @player
if @other_tanks[player]
@other_tanks[player].points = sprite[7].to_i
@other_tanks[player].warp_to(sprite[4], sprite[5], sprite[6])
else
@other_tanks[player] = Tank.from_sprite(self, sprite)
end
else
@me.points = sprite[7].to_i
end
when 'shot'
unless player == @player
shot = Shot.from_sprite(self, sprite)
@other_shots[shot.uuid] = shot
shot.warp_to(sprite[4], sprite[5], sprite[6])
end
end
end
end
parse the server messages into
sprites
Sunday, 9 June, 13
if msg = @client.read_message
@valid_sprites.clear
data = msg.split("n")
data.each do |row|
sprite = row.split("|")
if sprite.size == 9
player = sprite[3]
@valid_sprites << sprite[0]
case sprite[1]
when 'tank'
unless player == @player
if @other_tanks[player]
@other_tanks[player].points = sprite[7].to_i
@other_tanks[player].warp_to(sprite[4], sprite[5], sprite[6])
else
@other_tanks[player] = Tank.from_sprite(self, sprite)
end
else
@me.points = sprite[7].to_i
end
when 'shot'
unless player == @player
shot = Shot.from_sprite(self, sprite)
@other_shots[shot.uuid] = shot
shot.warp_to(sprite[4], sprite[5], sprite[6])
end
end
end
end
for tank sprites other than me,
set the properties and move it
Sunday, 9 June, 13
if msg = @client.read_message
@valid_sprites.clear
data = msg.split("n")
data.each do |row|
sprite = row.split("|")
if sprite.size == 9
player = sprite[3]
@valid_sprites << sprite[0]
case sprite[1]
when 'tank'
unless player == @player
if @other_tanks[player]
@other_tanks[player].points = sprite[7].to_i
@other_tanks[player].warp_to(sprite[4], sprite[5], sprite[6])
else
@other_tanks[player] = Tank.from_sprite(self, sprite)
end
else
@me.points = sprite[7].to_i
end
when 'shot'
unless player == @player
shot = Shot.from_sprite(self, sprite)
@other_shots[shot.uuid] = shot
shot.warp_to(sprite[4], sprite[5], sprite[6])
end
end
end
end
only time the server tells
me about my changes is
when I’m hit
Sunday, 9 June, 13
if msg = @client.read_message
@valid_sprites.clear
data = msg.split("n")
data.each do |row|
sprite = row.split("|")
if sprite.size == 9
player = sprite[3]
@valid_sprites << sprite[0]
case sprite[1]
when 'tank'
unless player == @player
if @other_tanks[player]
@other_tanks[player].points = sprite[7].to_i
@other_tanks[player].warp_to(sprite[4], sprite[5], sprite[6])
else
@other_tanks[player] = Tank.from_sprite(self, sprite)
end
else
@me.points = sprite[7].to_i
end
when 'shot'
unless player == @player
shot = Shot.from_sprite(self, sprite)
@other_shots[shot.uuid] = shot
shot.warp_to(sprite[4], sprite[5], sprite[6])
end
end
end
end
move the shot
sprites
Sunday, 9 June, 13
if msg = @client.read_message
@valid_sprites.clear
data = msg.split("n")
data.each do |row|
sprite = row.split("|")
if sprite.size == 9
player = sprite[3]
@valid_sprites << sprite[0]
case sprite[1]
when 'tank'
unless player == @player
if @other_tanks[player]
@other_tanks[player].points = sprite[7].to_i
@other_tanks[player].warp_to(sprite[4], sprite[5], sprite[6])
else
@other_tanks[player] = Tank.from_sprite(self, sprite)
end
else
@me.points = sprite[7].to_i
end
when 'shot'
unless player == @player
shot = Shot.from_sprite(self, sprite)
@other_shots[shot.uuid] = shot
shot.warp_to(sprite[4], sprite[5], sprite[6])
end
end
end
end
Sunday, 9 June, 13
"uuid|type|sprite_image|player|x|y|angle|points|color"
servermessage
"uuid|type|sprite_image|player|x|y|angle|points|color"
"uuid|type|sprite_image|player|x|y|angle|points|color"
"uuid|type|sprite_image|player|x|y|angle|points|color"
"uuid|type|sprite_image|player|x|y|angle|points|color"
Sunday, 9 June, 13
@other_shots.delete_if do |uuid, shot|
!@valid_sprites.include?(uuid)
end
@other_tanks.delete_if do |user, tank|
!@valid_sprites.include?(tank.uuid)
end
end
if shots and tanks (other than myself)
weren’t broadcast from the server, this
means they’ve been removed
Sunday, 9 June, 13
Level 3
Complete!
Sunday, 9 June, 13
Game Server
Sunday, 9 June, 13
Sunday, 9 June, 13
Sunday, 9 June, 13
Event-driven IO library based on
Celluloid
Duck types Ruby IO classes (TCPSocket,
TCPServer etc)
Celluloid combines OO with concurrent
programming, simplifies building
multithreaded programs
Sunday, 9 June, 13
require 'celluloid/io'
class Arena
include Celluloid::IO
finalizer :shutdown
def initialize(host, port)
puts "Starting Tanks Arena at #{host}:#{port}."
@server = TCPServer.new(host, port)
@sprites = Hash.new
@players = Hash.new
async.run
end
def shutdown
@server.close if @server
end
def run
loop { async.handle_connection @server.accept }
end
Sunday, 9 June, 13
require 'celluloid/io'
class Arena
include Celluloid::IO
finalizer :shutdown
def initialize(host, port)
puts "Starting Tanks Arena at #{host}:#{port}."
@server = TCPServer.new(host, port)
@sprites = Hash.new
@players = Hash.new
async.run
end
def shutdown
@server.close if @server
end
def run
loop { async.handle_connection @server.accept }
end
What to do when the
server terminates
Sunday, 9 June, 13
require 'celluloid/io'
class Arena
include Celluloid::IO
finalizer :shutdown
def initialize(host, port)
puts "Starting Tanks Arena at #{host}:#{port}."
@server = TCPServer.new(host, port)
@sprites = Hash.new
@players = Hash.new
async.run
end
def shutdown
@server.close if @server
end
def run
loop { async.handle_connection @server.accept }
end
Run the Arena object
in a new thread
Sunday, 9 June, 13
require 'celluloid/io'
class Arena
include Celluloid::IO
finalizer :shutdown
def initialize(host, port)
puts "Starting Tanks Arena at #{host}:#{port}."
@server = TCPServer.new(host, port)
@sprites = Hash.new
@players = Hash.new
async.run
end
def shutdown
@server.close if @server
end
def run
loop { async.handle_connection @server.accept }
end
When a client connects,
handle the connection in
a new thread
Sunday, 9 June, 13
def handle_connection(socket)
_, port, host = socket.peeraddr
user = "#{host}:#{port}"
puts "#{user} has joined the arena."
loop do
data = socket.readpartial(4096)
data_array = data.split("n")
if data_array and !data_array.empty?
begin
data_array.each do |row|
message = row.split("|")
if message.size == 10
case message[0]
when 'obj'
@players[user] = message[1..9] unless @players[user]
@sprites[message[1]] = message[1..9]
when 'del'
@sprites.delete message[1]
end
end
.
.
.
Sunday, 9 June, 13
def handle_connection(socket)
_, port, host = socket.peeraddr
user = "#{host}:#{port}"
puts "#{user} has joined the arena."
loop do
data = socket.readpartial(4096)
data_array = data.split("n")
if data_array and !data_array.empty?
begin
data_array.each do |row|
message = row.split("|")
if message.size == 10
case message[0]
when 'obj'
@players[user] = message[1..9] unless @players[user]
@sprites[message[1]] = message[1..9]
when 'del'
@sprites.delete message[1]
end
end
.
.
.
Uniquely
identifies a user
Sunday, 9 June, 13
def handle_connection(socket)
_, port, host = socket.peeraddr
user = "#{host}:#{port}"
puts "#{user} has joined the arena."
loop do
data = socket.readpartial(4096)
data_array = data.split("n")
if data_array and !data_array.empty?
begin
data_array.each do |row|
message = row.split("|")
if message.size == 10
case message[0]
when 'obj'
@players[user] = message[1..9] unless @players[user]
@sprites[message[1]] = message[1..9]
when 'del'
@sprites.delete message[1]
end
end
.
.
.
Get data from
the client
Sunday, 9 June, 13
def handle_connection(socket)
_, port, host = socket.peeraddr
user = "#{host}:#{port}"
puts "#{user} has joined the arena."
loop do
data = socket.readpartial(4096)
data_array = data.split("n")
if data_array and !data_array.empty?
begin
data_array.each do |row|
message = row.split("|")
if message.size == 10
case message[0]
when 'obj'
@players[user] = message[1..9] unless @players[user]
@sprites[message[1]] = message[1..9]
when 'del'
@sprites.delete message[1]
end
end
.
.
.
Add to list of players
if player is new
Sunday, 9 June, 13
def handle_connection(socket)
_, port, host = socket.peeraddr
user = "#{host}:#{port}"
puts "#{user} has joined the arena."
loop do
data = socket.readpartial(4096)
data_array = data.split("n")
if data_array and !data_array.empty?
begin
data_array.each do |row|
message = row.split("|")
if message.size == 10
case message[0]
when 'obj'
@players[user] = message[1..9] unless @players[user]
@sprites[message[1]] = message[1..9]
when 'del'
@sprites.delete message[1]
end
end
.
.
.
Add to list of
sprites in
this server
Sunday, 9 June, 13
def handle_connection(socket)
_, port, host = socket.peeraddr
user = "#{host}:#{port}"
puts "#{user} has joined the arena."
loop do
data = socket.readpartial(4096)
data_array = data.split("n")
if data_array and !data_array.empty?
begin
data_array.each do |row|
message = row.split("|")
if message.size == 10
case message[0]
when 'obj'
@players[user] = message[1..9] unless @players[user]
@sprites[message[1]] = message[1..9]
when 'del'
@sprites.delete message[1]
end
end
.
.
.
Remove sprite
from this server
Sunday, 9 June, 13
.
.
.
response = String.new
@sprites.each_value do |sprite|
(response << sprite.join("|") << "n") if sprite
end
socket.write response
end
rescue Exception => exception
puts exception.backtrace
end
end # end data
end # end loop
rescue EOFError => err
player = @players[user]
puts "#{player[3]} has left arena."
@sprites.delete player[0]
@players.delete user
socket.close
end
end
Sunday, 9 June, 13
.
.
.
response = String.new
@sprites.each_value do |sprite|
(response << sprite.join("|") << "n") if sprite
end
socket.write response
end
rescue Exception => exception
puts exception.backtrace
end
end # end data
end # end loop
rescue EOFError => err
player = @players[user]
puts "#{player[3]} has left arena."
@sprites.delete player[0]
@players.delete user
socket.close
end
end
Send list of sprites
to the client
Sunday, 9 June, 13
.
.
.
response = String.new
@sprites.each_value do |sprite|
(response << sprite.join("|") << "n") if sprite
end
socket.write response
end
rescue Exception => exception
puts exception.backtrace
end
end # end data
end # end loop
rescue EOFError => err
player = @players[user]
puts "#{player[3]} has left arena."
@sprites.delete player[0]
@players.delete user
socket.close
end
end
If client disconnects,
remove the player
and sprite
Sunday, 9 June, 13
server, port = ARGV[0] || "0.0.0.0", ARGV[1] || 1234
supervisor = Arena.supervise(server, port.to_i)
trap("INT") do
supervisor.terminate
exit
end
sleep
Sunday, 9 June, 13
server, port = ARGV[0] || "0.0.0.0", ARGV[1] || 1234
supervisor = Arena.supervise(server, port.to_i)
trap("INT") do
supervisor.terminate
exit
end
sleep
Monitors and restarts
the server if it crashes
Sunday, 9 June, 13
server, port = ARGV[0] || "0.0.0.0", ARGV[1] || 1234
supervisor = Arena.supervise(server, port.to_i)
trap("INT") do
supervisor.terminate
exit
end
sleep
Nothing for the main
thread to do so, sleep and
let the other threads run
Sunday, 9 June, 13
Demo
Sunday, 9 June, 13
Level 4
Complete!
Sunday, 9 June, 13
Advanced stuff
(a bit more)
Sunday, 9 June, 13
Run more than 1 game server?
Provide custom maps and sprites for
every server?
Manage and monitor game servers (not
through a console)?
Sunday, 9 June, 13
Web-based game
server console
Sunday, 9 June, 13
Sunday, 9 June, 13
configure do
@@port_range = (10000..11000).to_a
end
get "/" do
@arenas = Celluloid::Actor.all
haml :arenas
end
post "/arena/start" do
port = @@port_range.delete @@port_range.sample
arena = Arena.new(request.host, port, request.port)
arena.map_url = params[:map_url]
arena.spritesheet_url = params[:spritesheet_url]
arena.default_hp = params[:default_hp].to_i
Celluloid::Actor[:"arena_#{port}"] = arena
redirect "/"
end
get "/arena/stop/:name" do
raise "No such arena" unless Celluloid::Actor[params[:name].to_sym]
Celluloid::Actor[params[:name].to_sym].terminate
redirect "/"
end
get "/config/:name" do
arena = Celluloid::Actor[params[:name].to_sym]
array = [arena.map_url, arena.spritesheet_url,
arena.default_hp.to_s].join("|")
end
Sunday, 9 June, 13
configure do
@@port_range = (10000..11000).to_a
end
get "/" do
@arenas = Celluloid::Actor.all
haml :arenas
end
post "/arena/start" do
port = @@port_range.delete @@port_range.sample
arena = Arena.new(request.host, port, request.port)
arena.map_url = params[:map_url]
arena.spritesheet_url = params[:spritesheet_url]
arena.default_hp = params[:default_hp].to_i
Celluloid::Actor[:"arena_#{port}"] = arena
redirect "/"
end
get "/arena/stop/:name" do
raise "No such arena" unless Celluloid::Actor[params[:name].to_sym]
Celluloid::Actor[params[:name].to_sym].terminate
redirect "/"
end
get "/config/:name" do
arena = Celluloid::Actor[params[:name].to_sym]
array = [arena.map_url, arena.spritesheet_url,
arena.default_hp.to_s].join("|")
end
Start server at
any of these ports
Sunday, 9 June, 13
configure do
@@port_range = (10000..11000).to_a
end
get "/" do
@arenas = Celluloid::Actor.all
haml :arenas
end
post "/arena/start" do
port = @@port_range.delete @@port_range.sample
arena = Arena.new(request.host, port, request.port)
arena.map_url = params[:map_url]
arena.spritesheet_url = params[:spritesheet_url]
arena.default_hp = params[:default_hp].to_i
Celluloid::Actor[:"arena_#{port}"] = arena
redirect "/"
end
get "/arena/stop/:name" do
raise "No such arena" unless Celluloid::Actor[params[:name].to_sym]
Celluloid::Actor[params[:name].to_sym].terminate
redirect "/"
end
get "/config/:name" do
arena = Celluloid::Actor[params[:name].to_sym]
array = [arena.map_url, arena.spritesheet_url,
arena.default_hp.to_s].join("|")
end
Registry of all arenas
Sunday, 9 June, 13
configure do
@@port_range = (10000..11000).to_a
end
get "/" do
@arenas = Celluloid::Actor.all
haml :arenas
end
post "/arena/start" do
port = @@port_range.delete @@port_range.sample
arena = Arena.new(request.host, port, request.port)
arena.map_url = params[:map_url]
arena.spritesheet_url = params[:spritesheet_url]
arena.default_hp = params[:default_hp].to_i
Celluloid::Actor[:"arena_#{port}"] = arena
redirect "/"
end
get "/arena/stop/:name" do
raise "No such arena" unless Celluloid::Actor[params[:name].to_sym]
Celluloid::Actor[params[:name].to_sym].terminate
redirect "/"
end
get "/config/:name" do
arena = Celluloid::Actor[params[:name].to_sym]
array = [arena.map_url, arena.spritesheet_url,
arena.default_hp.to_s].join("|")
end
Start arena
Sunday, 9 June, 13
configure do
@@port_range = (10000..11000).to_a
end
get "/" do
@arenas = Celluloid::Actor.all
haml :arenas
end
post "/arena/start" do
port = @@port_range.delete @@port_range.sample
arena = Arena.new(request.host, port, request.port)
arena.map_url = params[:map_url]
arena.spritesheet_url = params[:spritesheet_url]
arena.default_hp = params[:default_hp].to_i
Celluloid::Actor[:"arena_#{port}"] = arena
redirect "/"
end
get "/arena/stop/:name" do
raise "No such arena" unless Celluloid::Actor[params[:name].to_sym]
Celluloid::Actor[params[:name].to_sym].terminate
redirect "/"
end
get "/config/:name" do
arena = Celluloid::Actor[params[:name].to_sym]
array = [arena.map_url, arena.spritesheet_url,
arena.default_hp.to_s].join("|")
end
Register arena
Sunday, 9 June, 13
configure do
@@port_range = (10000..11000).to_a
end
get "/" do
@arenas = Celluloid::Actor.all
haml :arenas
end
post "/arena/start" do
port = @@port_range.delete @@port_range.sample
arena = Arena.new(request.host, port, request.port)
arena.map_url = params[:map_url]
arena.spritesheet_url = params[:spritesheet_url]
arena.default_hp = params[:default_hp].to_i
Celluloid::Actor[:"arena_#{port}"] = arena
redirect "/"
end
get "/arena/stop/:name" do
raise "No such arena" unless Celluloid::Actor[params[:name].to_sym]
Celluloid::Actor[params[:name].to_sym].terminate
redirect "/"
end
get "/config/:name" do
arena = Celluloid::Actor[params[:name].to_sym]
array = [arena.map_url, arena.spritesheet_url,
arena.default_hp.to_s].join("|")
end
Terminate arena
Sunday, 9 June, 13
configure do
@@port_range = (10000..11000).to_a
end
get "/" do
@arenas = Celluloid::Actor.all
haml :arenas
end
post "/arena/start" do
port = @@port_range.delete @@port_range.sample
arena = Arena.new(request.host, port, request.port)
arena.map_url = params[:map_url]
arena.spritesheet_url = params[:spritesheet_url]
arena.default_hp = params[:default_hp].to_i
Celluloid::Actor[:"arena_#{port}"] = arena
redirect "/"
end
get "/arena/stop/:name" do
raise "No such arena" unless Celluloid::Actor[params[:name].to_sym]
Celluloid::Actor[params[:name].to_sym].terminate
redirect "/"
end
get "/config/:name" do
arena = Celluloid::Actor[params[:name].to_sym]
array = [arena.map_url, arena.spritesheet_url,
arena.default_hp.to_s].join("|")
end
Let client know
about the
customizations
Sunday, 9 June, 13
configure do
@@port_range = (10000..11000).to_a
end
get "/" do
@arenas = Celluloid::Actor.all
haml :arenas
end
post "/arena/start" do
port = @@port_range.delete @@port_range.sample
arena = Arena.new(request.host, port, request.port)
arena.map_url = params[:map_url]
arena.spritesheet_url = params[:spritesheet_url]
arena.default_hp = params[:default_hp].to_i
Celluloid::Actor[:"arena_#{port}"] = arena
redirect "/"
end
get "/arena/stop/:name" do
raise "No such arena" unless Celluloid::Actor[params[:name].to_sym]
Celluloid::Actor[params[:name].to_sym].terminate
redirect "/"
end
get "/config/:name" do
arena = Celluloid::Actor[params[:name].to_sym]
array = [arena.map_url, arena.spritesheet_url,
arena.default_hp.to_s].join("|")
end
Sunday, 9 June, 13
Demo
Sunday, 9 June, 13
Thank you for
listening
Sunday, 9 June, 13
sausheong@gmail.com
@sausheong
http://github.com/sausheong/tanks
http://github.com/sausheong/tanksworld
http://libgosu.org
http://celluloid.io
œœœ
Sunday, 9 June, 13

Playing With Ruby

  • 1.
    Playing with Ruby Howto write an online, real-time multi-player game with Ruby @sausheong Sunday, 9 June, 13
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
    2D game developmentlibrary C++-based, with Ruby wrapper OS X, Windows and Linux Works with MRI, MacRuby, Rubinius (but not JRuby) Sunday, 9 June, 13
  • 15.
  • 16.
    require 'gosu' class GameWindow< Gosu::Window def initialize super 640, 480, false self.caption = "Tutorial 1" end def update end def draw end end window = GameWindow.new window.show Sunday, 9 June, 13
  • 17.
  • 18.
    update method called atevery frame (60 frames per second) Contains game logic The main ‘controller’ of the game Sunday, 9 June, 13
  • 19.
    draw method Does theactual drawing of the game window Called after update Can also be called when necessary Sunday, 9 June, 13
  • 20.
  • 21.
    require 'gosu' class GameWindow< Gosu::Window def initialize super 640, 480, false self.caption = "Tutorial 2" @background_image = Gosu::Image.new(self, "bg1.jpg", true) end def update end def draw @background_image.draw(0, 0, 0) end end window = GameWindow.new window.show x y z Sunday, 9 June, 13
  • 22.
  • 23.
  • 24.
    class Player def initialize(window) @image= Image.new window, "plane.png", false @x = @y = @vel_x = @vel_y = @angle = 0.0 end def warp(x, y) @x, @y = x, y end def turn_left @angle -= 5 end def turn_right @angle += 5 end def accelerate @vel_x += offset_x(@angle, 5) @vel_y += offset_y(@angle, 5) end def move @x += @vel_x @y += @vel_y @x %= 640 @y %= 480 @vel_x, @vel_y = 0, 0 end def draw @image.draw_rot(@x, @y, 1, @angle) end end @angle 5 offset_y offset_x Sunday, 9 June, 13
  • 25.
    class GameWindow <Window def initialize super(640, 480, false) self.caption = "Tutorial 3" @background_image = Image.new(self, "bg1.jpg", true) @player = Player.new(self) @player.warp(320, 240) end def update @player.turn_left if button_down? KbLeft @player.turn_right if button_down? KbRight @player.accelerate if button_down? KbUp @player.move end def draw @player.draw @background_image.draw(0, 0, 0) end end Sunday, 9 June, 13
  • 26.
    class GameWindow <Window def initialize super(640, 480, false) self.caption = "Tutorial 3" @background_image = Image.new(self, "bg1.jpg", true) @player = Player.new(self) @player.warp(320, 240) end def update @player.turn_left if button_down? KbLeft @player.turn_right if button_down? KbRight @player.accelerate if button_down? KbUp @player.move end def draw @player.draw @background_image.draw(0, 0, 0) end end create player Sunday, 9 June, 13
  • 27.
    class GameWindow <Window def initialize super(640, 480, false) self.caption = "Tutorial 3" @background_image = Image.new(self, "bg1.jpg", true) @player = Player.new(self) @player.warp(320, 240) end def update @player.turn_left if button_down? KbLeft @player.turn_right if button_down? KbRight @player.accelerate if button_down? KbUp @player.move end def draw @player.draw @background_image.draw(0, 0, 0) end end place him in middle of screen Sunday, 9 June, 13
  • 28.
    class GameWindow <Window def initialize super(640, 480, false) self.caption = "Tutorial 3" @background_image = Image.new(self, "bg1.jpg", true) @player = Player.new(self) @player.warp(320, 240) end def update @player.turn_left if button_down? KbLeft @player.turn_right if button_down? KbRight @player.accelerate if button_down? KbUp @player.move end def draw @player.draw @background_image.draw(0, 0, 0) end end } move according to user input Sunday, 9 June, 13
  • 29.
    class GameWindow <Window def initialize super(640, 480, false) self.caption = "Tutorial 3" @background_image = Image.new(self, "bg1.jpg", true) @player = Player.new(self) @player.warp(320, 240) end def update @player.turn_left if button_down? KbLeft @player.turn_right if button_down? KbRight @player.accelerate if button_down? KbUp @player.move end def draw @player.draw @background_image.draw(0, 0, 0) end end draw the player Sunday, 9 June, 13
  • 30.
  • 31.
  • 32.
    def initialize(window) @image =Image.new window, "plane.png", false @sound = Sample.new window, "plane.wav" @x = @y = @vel_x = @vel_y = @angle = 0.0 end . . . def accelerate @sound.play @vel_x += offset_x(@angle, 5) @vel_y += offset_y(@angle, 5) end Sunday, 9 June, 13
  • 33.
    def initialize(window) @image =Image.new window, "plane.png", false @sound = Sample.new window, "plane.wav" @x = @y = @vel_x = @vel_y = @angle = 0.0 end . . . def accelerate @sound.play @vel_x += offset_x(@angle, 5) @vel_y += offset_y(@angle, 5) end Load the sound Sunday, 9 June, 13
  • 34.
    def initialize(window) @image =Image.new window, "plane.png", false @sound = Sample.new window, "plane.wav" @x = @y = @vel_x = @vel_y = @angle = 0.0 end . . . def accelerate @sound.play @vel_x += offset_x(@angle, 5) @vel_y += offset_y(@angle, 5) end play the sound! Sunday, 9 June, 13
  • 35.
  • 36.
  • 37.
  • 38.
    Sprites An image oranimation that’s overlaid on the background Use single sprites (as before) or use sprite sheets Sprites normally represented by a square image Sunday, 9 June, 13
  • 39.
    Sprite sheet A bunchof images in a single file, used as sprites Often placed in sequence, image can be retrieved from knowing the location Reduces memory usage and increase drawing speed Sunday, 9 June, 13
  • 40.
  • 41.
    5948 25 26 2728 29 30 31 32 33 34 35 36 37 38 3924 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 230 72 100 101 10296 Sunday, 9 June, 13
  • 42.
    module SpriteImage Grass =102 Earth = 101 Gravel = 100 Wall = 59 Bullet= 28 Tank = 39 end Locate sprites in a sprite sheet Sunday, 9 June, 13
  • 43.
    Player: def initialize(window) @image =Image.new window, "plane.png", false end def draw @image.draw_rot(@x, @y, 1, @angle) end GameWindow: @spritesheet = Image.load_tiles(self, 'sprites.png', 32, 32, true) Player: @window.spritesheet[SpriteImage::Tank].draw_rot(@x, @y, 1, @angle) Sunday, 9 June, 13
  • 44.
  • 45.
    Editable maps Allows userto customize maps and backgrounds, using tiled sprites ..................... ..................... ..................... ..................... ..................... ..##............##... ...#............#.... ...#............#.... ...#............#.... ..##............##... ..................... ..................... ..................... ..................... ..................... Sunday, 9 June, 13
  • 46.
    ..................... ..................... ..................... ..................... ..................... ..##............##... ...#............#.... ...#............#.... ...#............#.... ..##............##... ..................... ..................... ..................... ..................... ..................... 20 x 32= 640 15x32=480 class Map def initialize(window, mapfile) lines = File.readlines(mapfile).map { |line| line.chomp } @window, @height, @width = window, lines.size, lines.first.size @tiles = Array.new(@width) do |x| Array.new(@height) do |y| case lines[y][x] when '.' SpriteImage::Earth when "#" SpriteImage::Wall when '"' SpriteImage::Grass end end end end def draw @height.times do |y| @width.times do |x| tile = @tiles[x][y] @window.spritesheet[tile].draw(x * 32, y * 32, 1) end end Sunday, 9 June, 13
  • 47.
  • 48.
  • 49.
  • 50.
    Design Real-time vs turn-based(immediate response) Latency (speed) is critical ‘Dead’ players can still observe the game Game spectators Sunday, 9 June, 13
  • 51.
    Design Client-server All artifacts arelocal Only messages sent back and forth the client-server Minimal size messages Messages sent from client -> server once every frame refresh Sunday, 9 June, 13
  • 52.
    Design Server should haveminimal processing, all game logic should be in the client Server should only receive messages and broadcast to all clients Messages not compressed/encoded (takes time at the server) Don’t send useless messages Sunday, 9 June, 13
  • 53.
  • 54.
  • 55.
    Tank 1 starts,sends message to server Game Server Tank1 object:tank1 object:tank1 Tank 1 ignores messages that is about itself Sunday, 9 June, 13
  • 56.
    Server simply storesand broadcasts all messages sent to it to reduce processing Logic to process or ignore messages are in the client Sunday, 9 June, 13
  • 57.
    Tank 2 starts Game Server Tank1 object:tank1 object:tank1 Tank2 object:tank2 object:tank1 object:tank2 object:tank2 Tank1 receives messages from server about Tank 2, starts drawing Tank 2 Sunday, 9 June, 13
  • 58.
    Tank 2 moves Game Server Tank1 object:tank1 object:tank1 Tank2 object:tank2 object:tank1 object:tank2 object:tank2 WhenTank 2 moves, its position is sent to the server and broadcast to everyone Sunday, 9 June, 13
  • 59.
  • 60.
    Shot goes outof range Game Server Tank1 object:tank1 object:tank1 Tank2 object:tank2 object:tank1 object:tank2 object:tank2 delete:shot1 delete:shot1 delete:shot1 When the shot goes out of range, Tank 1 sends a delete message to the server, broadcasted to everyone Sunday, 9 June, 13
  • 61.
    Tank 1 shothits Tank 2 Game Server Tank1 object:tank1 object:tank1 Tank2 object:tank2 object:tank1 object:tank2 object:tank2 object:shot1 object:shot1 object:shot1 If Tank 1’s shot hits Tank 2, reduce hit points from Tank1 Sunday, 9 June, 13
  • 62.
  • 63.
  • 64.
    Messages are stringdelimited with vertical bar (|) Messages are accumulated till and sent only 1 time in a frame refresh Messages from client -> server : message type + sprite Message from server -> client : sprite only Sunday, 9 June, 13
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
    Client sends messages tothe server Sunday, 9 June, 13
  • 73.
    def update begin move_tank px, py= @me.x, @me.y @me.move @me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield? @other_tanks.each do |player, tank| @me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30) end add_to_message_queue('obj', @me) @other_shots.each_value do |shot| if @me.alive? and @me.collide_with?(shot, 16) @me.hit add_to_message_queue('obj', @me) end end @me_shots.each do |shot| shot.move # move the bullet if shot.hit_wall? or shot.outside_battlefield? @me_shots.delete shot add_to_message_queue('del', shot) else add_to_message_queue('obj', shot) end end @client.send_message @messages.join("n") @messages.clear Sunday, 9 June, 13
  • 74.
    def update begin move_tank px, py= @me.x, @me.y @me.move @me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield? @other_tanks.each do |player, tank| @me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30) end add_to_message_queue('obj', @me) @other_shots.each_value do |shot| if @me.alive? and @me.collide_with?(shot, 16) @me.hit add_to_message_queue('obj', @me) end end @me_shots.each do |shot| shot.move # move the bullet if shot.hit_wall? or shot.outside_battlefield? @me_shots.delete shot add_to_message_queue('del', shot) else add_to_message_queue('obj', shot) end end @client.send_message @messages.join("n") @messages.clear store my previous coordinates Sunday, 9 June, 13
  • 75.
    def update begin move_tank px, py= @me.x, @me.y @me.move @me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield? @other_tanks.each do |player, tank| @me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30) end add_to_message_queue('obj', @me) @other_shots.each_value do |shot| if @me.alive? and @me.collide_with?(shot, 16) @me.hit add_to_message_queue('obj', @me) end end @me_shots.each do |shot| shot.move # move the bullet if shot.hit_wall? or shot.outside_battlefield? @me_shots.delete shot add_to_message_queue('del', shot) else add_to_message_queue('obj', shot) end end @client.send_message @messages.join("n") @messages.clear move! Sunday, 9 June, 13
  • 76.
    def update begin move_tank px, py= @me.x, @me.y @me.move @me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield? @other_tanks.each do |player, tank| @me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30) end add_to_message_queue('obj', @me) @other_shots.each_value do |shot| if @me.alive? and @me.collide_with?(shot, 16) @me.hit add_to_message_queue('obj', @me) end end @me_shots.each do |shot| shot.move # move the bullet if shot.hit_wall? or shot.outside_battlefield? @me_shots.delete shot add_to_message_queue('del', shot) else add_to_message_queue('obj', shot) end end @client.send_message @messages.join("n") @messages.clear go back to previous coordinates if I hit the wall, go out or hit another tank Sunday, 9 June, 13
  • 77.
    def update begin move_tank px, py= @me.x, @me.y @me.move @me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield? @other_tanks.each do |player, tank| @me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30) end add_to_message_queue('obj', @me) @other_shots.each_value do |shot| if @me.alive? and @me.collide_with?(shot, 16) @me.hit add_to_message_queue('obj', @me) end end @me_shots.each do |shot| shot.move # move the bullet if shot.hit_wall? or shot.outside_battlefield? @me_shots.delete shot add_to_message_queue('del', shot) else add_to_message_queue('obj', shot) end end @client.send_message @messages.join("n") @messages.clear add me to the list of messages to send to server Sunday, 9 June, 13
  • 78.
    def update begin move_tank px, py= @me.x, @me.y @me.move @me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield? @other_tanks.each do |player, tank| @me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30) end add_to_message_queue('obj', @me) @other_shots.each_value do |shot| if @me.alive? and @me.collide_with?(shot, 16) @me.hit add_to_message_queue('obj', @me) end end @me_shots.each do |shot| shot.move # move the bullet if shot.hit_wall? or shot.outside_battlefield? @me_shots.delete shot add_to_message_queue('del', shot) else add_to_message_queue('obj', shot) end end @client.send_message @messages.join("n") @messages.clear check the other shots on screen to see if it hits me, if it does, tell the server I was hit Sunday, 9 June, 13
  • 79.
    def update begin move_tank px, py= @me.x, @me.y @me.move @me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield? @other_tanks.each do |player, tank| @me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30) end add_to_message_queue('obj', @me) @other_shots.each_value do |shot| if @me.alive? and @me.collide_with?(shot, 16) @me.hit add_to_message_queue('obj', @me) end end @me_shots.each do |shot| shot.move # move the bullet if shot.hit_wall? or shot.outside_battlefield? @me_shots.delete shot add_to_message_queue('del', shot) else add_to_message_queue('obj', shot) end end @client.send_message @messages.join("n") @messages.clear move my shots, if it hits the wall or goes out, remove it Sunday, 9 June, 13
  • 80.
    def update begin move_tank px, py= @me.x, @me.y @me.move @me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield? @other_tanks.each do |player, tank| @me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30) end add_to_message_queue('obj', @me) @other_shots.each_value do |shot| if @me.alive? and @me.collide_with?(shot, 16) @me.hit add_to_message_queue('obj', @me) end end @me_shots.each do |shot| shot.move # move the bullet if shot.hit_wall? or shot.outside_battlefield? @me_shots.delete shot add_to_message_queue('del', shot) else add_to_message_queue('obj', shot) end end @client.send_message @messages.join("n") @messages.clear if not, tell the server its new position Sunday, 9 June, 13
  • 81.
    def update begin move_tank px, py= @me.x, @me.y @me.move @me.warp_to(px, py) if @me.hit_wall? or @me.outside_battlefield? @other_tanks.each do |player, tank| @me.warp_to(px, py) if tank.alive? and @me.collide_with?(tank, 30) end add_to_message_queue('obj', @me) @other_shots.each_value do |shot| if @me.alive? and @me.collide_with?(shot, 16) @me.hit add_to_message_queue('obj', @me) end end @me_shots.each do |shot| shot.move # move the bullet if shot.hit_wall? or shot.outside_battlefield? @me_shots.delete shot add_to_message_queue('del', shot) else add_to_message_queue('obj', shot) end end @client.send_message @messages.join("n") @messages.clear all my actions are processed, now to send messages to server Sunday, 9 June, 13
  • 82.
  • 83.
    Client reads messages fromthe server Sunday, 9 June, 13
  • 84.
    if msg =@client.read_message @valid_sprites.clear data = msg.split("n") data.each do |row| sprite = row.split("|") if sprite.size == 9 player = sprite[3] @valid_sprites << sprite[0] case sprite[1] when 'tank' unless player == @player if @other_tanks[player] @other_tanks[player].points = sprite[7].to_i @other_tanks[player].warp_to(sprite[4], sprite[5], sprite[6]) else @other_tanks[player] = Tank.from_sprite(self, sprite) end else @me.points = sprite[7].to_i end when 'shot' unless player == @player shot = Shot.from_sprite(self, sprite) @other_shots[shot.uuid] = shot shot.warp_to(sprite[4], sprite[5], sprite[6]) end end end end Sunday, 9 June, 13
  • 85.
    if msg =@client.read_message @valid_sprites.clear data = msg.split("n") data.each do |row| sprite = row.split("|") if sprite.size == 9 player = sprite[3] @valid_sprites << sprite[0] case sprite[1] when 'tank' unless player == @player if @other_tanks[player] @other_tanks[player].points = sprite[7].to_i @other_tanks[player].warp_to(sprite[4], sprite[5], sprite[6]) else @other_tanks[player] = Tank.from_sprite(self, sprite) end else @me.points = sprite[7].to_i end when 'shot' unless player == @player shot = Shot.from_sprite(self, sprite) @other_shots[shot.uuid] = shot shot.warp_to(sprite[4], sprite[5], sprite[6]) end end end end read messages from the server Sunday, 9 June, 13
  • 86.
    if msg =@client.read_message @valid_sprites.clear data = msg.split("n") data.each do |row| sprite = row.split("|") if sprite.size == 9 player = sprite[3] @valid_sprites << sprite[0] case sprite[1] when 'tank' unless player == @player if @other_tanks[player] @other_tanks[player].points = sprite[7].to_i @other_tanks[player].warp_to(sprite[4], sprite[5], sprite[6]) else @other_tanks[player] = Tank.from_sprite(self, sprite) end else @me.points = sprite[7].to_i end when 'shot' unless player == @player shot = Shot.from_sprite(self, sprite) @other_shots[shot.uuid] = shot shot.warp_to(sprite[4], sprite[5], sprite[6]) end end end end parse the server messages into sprites Sunday, 9 June, 13
  • 87.
    if msg =@client.read_message @valid_sprites.clear data = msg.split("n") data.each do |row| sprite = row.split("|") if sprite.size == 9 player = sprite[3] @valid_sprites << sprite[0] case sprite[1] when 'tank' unless player == @player if @other_tanks[player] @other_tanks[player].points = sprite[7].to_i @other_tanks[player].warp_to(sprite[4], sprite[5], sprite[6]) else @other_tanks[player] = Tank.from_sprite(self, sprite) end else @me.points = sprite[7].to_i end when 'shot' unless player == @player shot = Shot.from_sprite(self, sprite) @other_shots[shot.uuid] = shot shot.warp_to(sprite[4], sprite[5], sprite[6]) end end end end for tank sprites other than me, set the properties and move it Sunday, 9 June, 13
  • 88.
    if msg =@client.read_message @valid_sprites.clear data = msg.split("n") data.each do |row| sprite = row.split("|") if sprite.size == 9 player = sprite[3] @valid_sprites << sprite[0] case sprite[1] when 'tank' unless player == @player if @other_tanks[player] @other_tanks[player].points = sprite[7].to_i @other_tanks[player].warp_to(sprite[4], sprite[5], sprite[6]) else @other_tanks[player] = Tank.from_sprite(self, sprite) end else @me.points = sprite[7].to_i end when 'shot' unless player == @player shot = Shot.from_sprite(self, sprite) @other_shots[shot.uuid] = shot shot.warp_to(sprite[4], sprite[5], sprite[6]) end end end end only time the server tells me about my changes is when I’m hit Sunday, 9 June, 13
  • 89.
    if msg =@client.read_message @valid_sprites.clear data = msg.split("n") data.each do |row| sprite = row.split("|") if sprite.size == 9 player = sprite[3] @valid_sprites << sprite[0] case sprite[1] when 'tank' unless player == @player if @other_tanks[player] @other_tanks[player].points = sprite[7].to_i @other_tanks[player].warp_to(sprite[4], sprite[5], sprite[6]) else @other_tanks[player] = Tank.from_sprite(self, sprite) end else @me.points = sprite[7].to_i end when 'shot' unless player == @player shot = Shot.from_sprite(self, sprite) @other_shots[shot.uuid] = shot shot.warp_to(sprite[4], sprite[5], sprite[6]) end end end end move the shot sprites Sunday, 9 June, 13
  • 90.
    if msg =@client.read_message @valid_sprites.clear data = msg.split("n") data.each do |row| sprite = row.split("|") if sprite.size == 9 player = sprite[3] @valid_sprites << sprite[0] case sprite[1] when 'tank' unless player == @player if @other_tanks[player] @other_tanks[player].points = sprite[7].to_i @other_tanks[player].warp_to(sprite[4], sprite[5], sprite[6]) else @other_tanks[player] = Tank.from_sprite(self, sprite) end else @me.points = sprite[7].to_i end when 'shot' unless player == @player shot = Shot.from_sprite(self, sprite) @other_shots[shot.uuid] = shot shot.warp_to(sprite[4], sprite[5], sprite[6]) end end end end Sunday, 9 June, 13
  • 91.
  • 92.
    @other_shots.delete_if do |uuid,shot| !@valid_sprites.include?(uuid) end @other_tanks.delete_if do |user, tank| !@valid_sprites.include?(tank.uuid) end end if shots and tanks (other than myself) weren’t broadcast from the server, this means they’ve been removed Sunday, 9 June, 13
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
    Event-driven IO librarybased on Celluloid Duck types Ruby IO classes (TCPSocket, TCPServer etc) Celluloid combines OO with concurrent programming, simplifies building multithreaded programs Sunday, 9 June, 13
  • 98.
    require 'celluloid/io' class Arena includeCelluloid::IO finalizer :shutdown def initialize(host, port) puts "Starting Tanks Arena at #{host}:#{port}." @server = TCPServer.new(host, port) @sprites = Hash.new @players = Hash.new async.run end def shutdown @server.close if @server end def run loop { async.handle_connection @server.accept } end Sunday, 9 June, 13
  • 99.
    require 'celluloid/io' class Arena includeCelluloid::IO finalizer :shutdown def initialize(host, port) puts "Starting Tanks Arena at #{host}:#{port}." @server = TCPServer.new(host, port) @sprites = Hash.new @players = Hash.new async.run end def shutdown @server.close if @server end def run loop { async.handle_connection @server.accept } end What to do when the server terminates Sunday, 9 June, 13
  • 100.
    require 'celluloid/io' class Arena includeCelluloid::IO finalizer :shutdown def initialize(host, port) puts "Starting Tanks Arena at #{host}:#{port}." @server = TCPServer.new(host, port) @sprites = Hash.new @players = Hash.new async.run end def shutdown @server.close if @server end def run loop { async.handle_connection @server.accept } end Run the Arena object in a new thread Sunday, 9 June, 13
  • 101.
    require 'celluloid/io' class Arena includeCelluloid::IO finalizer :shutdown def initialize(host, port) puts "Starting Tanks Arena at #{host}:#{port}." @server = TCPServer.new(host, port) @sprites = Hash.new @players = Hash.new async.run end def shutdown @server.close if @server end def run loop { async.handle_connection @server.accept } end When a client connects, handle the connection in a new thread Sunday, 9 June, 13
  • 102.
    def handle_connection(socket) _, port,host = socket.peeraddr user = "#{host}:#{port}" puts "#{user} has joined the arena." loop do data = socket.readpartial(4096) data_array = data.split("n") if data_array and !data_array.empty? begin data_array.each do |row| message = row.split("|") if message.size == 10 case message[0] when 'obj' @players[user] = message[1..9] unless @players[user] @sprites[message[1]] = message[1..9] when 'del' @sprites.delete message[1] end end . . . Sunday, 9 June, 13
  • 103.
    def handle_connection(socket) _, port,host = socket.peeraddr user = "#{host}:#{port}" puts "#{user} has joined the arena." loop do data = socket.readpartial(4096) data_array = data.split("n") if data_array and !data_array.empty? begin data_array.each do |row| message = row.split("|") if message.size == 10 case message[0] when 'obj' @players[user] = message[1..9] unless @players[user] @sprites[message[1]] = message[1..9] when 'del' @sprites.delete message[1] end end . . . Uniquely identifies a user Sunday, 9 June, 13
  • 104.
    def handle_connection(socket) _, port,host = socket.peeraddr user = "#{host}:#{port}" puts "#{user} has joined the arena." loop do data = socket.readpartial(4096) data_array = data.split("n") if data_array and !data_array.empty? begin data_array.each do |row| message = row.split("|") if message.size == 10 case message[0] when 'obj' @players[user] = message[1..9] unless @players[user] @sprites[message[1]] = message[1..9] when 'del' @sprites.delete message[1] end end . . . Get data from the client Sunday, 9 June, 13
  • 105.
    def handle_connection(socket) _, port,host = socket.peeraddr user = "#{host}:#{port}" puts "#{user} has joined the arena." loop do data = socket.readpartial(4096) data_array = data.split("n") if data_array and !data_array.empty? begin data_array.each do |row| message = row.split("|") if message.size == 10 case message[0] when 'obj' @players[user] = message[1..9] unless @players[user] @sprites[message[1]] = message[1..9] when 'del' @sprites.delete message[1] end end . . . Add to list of players if player is new Sunday, 9 June, 13
  • 106.
    def handle_connection(socket) _, port,host = socket.peeraddr user = "#{host}:#{port}" puts "#{user} has joined the arena." loop do data = socket.readpartial(4096) data_array = data.split("n") if data_array and !data_array.empty? begin data_array.each do |row| message = row.split("|") if message.size == 10 case message[0] when 'obj' @players[user] = message[1..9] unless @players[user] @sprites[message[1]] = message[1..9] when 'del' @sprites.delete message[1] end end . . . Add to list of sprites in this server Sunday, 9 June, 13
  • 107.
    def handle_connection(socket) _, port,host = socket.peeraddr user = "#{host}:#{port}" puts "#{user} has joined the arena." loop do data = socket.readpartial(4096) data_array = data.split("n") if data_array and !data_array.empty? begin data_array.each do |row| message = row.split("|") if message.size == 10 case message[0] when 'obj' @players[user] = message[1..9] unless @players[user] @sprites[message[1]] = message[1..9] when 'del' @sprites.delete message[1] end end . . . Remove sprite from this server Sunday, 9 June, 13
  • 108.
    . . . response = String.new @sprites.each_valuedo |sprite| (response << sprite.join("|") << "n") if sprite end socket.write response end rescue Exception => exception puts exception.backtrace end end # end data end # end loop rescue EOFError => err player = @players[user] puts "#{player[3]} has left arena." @sprites.delete player[0] @players.delete user socket.close end end Sunday, 9 June, 13
  • 109.
    . . . response = String.new @sprites.each_valuedo |sprite| (response << sprite.join("|") << "n") if sprite end socket.write response end rescue Exception => exception puts exception.backtrace end end # end data end # end loop rescue EOFError => err player = @players[user] puts "#{player[3]} has left arena." @sprites.delete player[0] @players.delete user socket.close end end Send list of sprites to the client Sunday, 9 June, 13
  • 110.
    . . . response = String.new @sprites.each_valuedo |sprite| (response << sprite.join("|") << "n") if sprite end socket.write response end rescue Exception => exception puts exception.backtrace end end # end data end # end loop rescue EOFError => err player = @players[user] puts "#{player[3]} has left arena." @sprites.delete player[0] @players.delete user socket.close end end If client disconnects, remove the player and sprite Sunday, 9 June, 13
  • 111.
    server, port =ARGV[0] || "0.0.0.0", ARGV[1] || 1234 supervisor = Arena.supervise(server, port.to_i) trap("INT") do supervisor.terminate exit end sleep Sunday, 9 June, 13
  • 112.
    server, port =ARGV[0] || "0.0.0.0", ARGV[1] || 1234 supervisor = Arena.supervise(server, port.to_i) trap("INT") do supervisor.terminate exit end sleep Monitors and restarts the server if it crashes Sunday, 9 June, 13
  • 113.
    server, port =ARGV[0] || "0.0.0.0", ARGV[1] || 1234 supervisor = Arena.supervise(server, port.to_i) trap("INT") do supervisor.terminate exit end sleep Nothing for the main thread to do so, sleep and let the other threads run Sunday, 9 June, 13
  • 114.
  • 115.
  • 116.
    Advanced stuff (a bitmore) Sunday, 9 June, 13
  • 117.
    Run more than1 game server? Provide custom maps and sprites for every server? Manage and monitor game servers (not through a console)? Sunday, 9 June, 13
  • 118.
  • 119.
  • 120.
    configure do @@port_range =(10000..11000).to_a end get "/" do @arenas = Celluloid::Actor.all haml :arenas end post "/arena/start" do port = @@port_range.delete @@port_range.sample arena = Arena.new(request.host, port, request.port) arena.map_url = params[:map_url] arena.spritesheet_url = params[:spritesheet_url] arena.default_hp = params[:default_hp].to_i Celluloid::Actor[:"arena_#{port}"] = arena redirect "/" end get "/arena/stop/:name" do raise "No such arena" unless Celluloid::Actor[params[:name].to_sym] Celluloid::Actor[params[:name].to_sym].terminate redirect "/" end get "/config/:name" do arena = Celluloid::Actor[params[:name].to_sym] array = [arena.map_url, arena.spritesheet_url, arena.default_hp.to_s].join("|") end Sunday, 9 June, 13
  • 121.
    configure do @@port_range =(10000..11000).to_a end get "/" do @arenas = Celluloid::Actor.all haml :arenas end post "/arena/start" do port = @@port_range.delete @@port_range.sample arena = Arena.new(request.host, port, request.port) arena.map_url = params[:map_url] arena.spritesheet_url = params[:spritesheet_url] arena.default_hp = params[:default_hp].to_i Celluloid::Actor[:"arena_#{port}"] = arena redirect "/" end get "/arena/stop/:name" do raise "No such arena" unless Celluloid::Actor[params[:name].to_sym] Celluloid::Actor[params[:name].to_sym].terminate redirect "/" end get "/config/:name" do arena = Celluloid::Actor[params[:name].to_sym] array = [arena.map_url, arena.spritesheet_url, arena.default_hp.to_s].join("|") end Start server at any of these ports Sunday, 9 June, 13
  • 122.
    configure do @@port_range =(10000..11000).to_a end get "/" do @arenas = Celluloid::Actor.all haml :arenas end post "/arena/start" do port = @@port_range.delete @@port_range.sample arena = Arena.new(request.host, port, request.port) arena.map_url = params[:map_url] arena.spritesheet_url = params[:spritesheet_url] arena.default_hp = params[:default_hp].to_i Celluloid::Actor[:"arena_#{port}"] = arena redirect "/" end get "/arena/stop/:name" do raise "No such arena" unless Celluloid::Actor[params[:name].to_sym] Celluloid::Actor[params[:name].to_sym].terminate redirect "/" end get "/config/:name" do arena = Celluloid::Actor[params[:name].to_sym] array = [arena.map_url, arena.spritesheet_url, arena.default_hp.to_s].join("|") end Registry of all arenas Sunday, 9 June, 13
  • 123.
    configure do @@port_range =(10000..11000).to_a end get "/" do @arenas = Celluloid::Actor.all haml :arenas end post "/arena/start" do port = @@port_range.delete @@port_range.sample arena = Arena.new(request.host, port, request.port) arena.map_url = params[:map_url] arena.spritesheet_url = params[:spritesheet_url] arena.default_hp = params[:default_hp].to_i Celluloid::Actor[:"arena_#{port}"] = arena redirect "/" end get "/arena/stop/:name" do raise "No such arena" unless Celluloid::Actor[params[:name].to_sym] Celluloid::Actor[params[:name].to_sym].terminate redirect "/" end get "/config/:name" do arena = Celluloid::Actor[params[:name].to_sym] array = [arena.map_url, arena.spritesheet_url, arena.default_hp.to_s].join("|") end Start arena Sunday, 9 June, 13
  • 124.
    configure do @@port_range =(10000..11000).to_a end get "/" do @arenas = Celluloid::Actor.all haml :arenas end post "/arena/start" do port = @@port_range.delete @@port_range.sample arena = Arena.new(request.host, port, request.port) arena.map_url = params[:map_url] arena.spritesheet_url = params[:spritesheet_url] arena.default_hp = params[:default_hp].to_i Celluloid::Actor[:"arena_#{port}"] = arena redirect "/" end get "/arena/stop/:name" do raise "No such arena" unless Celluloid::Actor[params[:name].to_sym] Celluloid::Actor[params[:name].to_sym].terminate redirect "/" end get "/config/:name" do arena = Celluloid::Actor[params[:name].to_sym] array = [arena.map_url, arena.spritesheet_url, arena.default_hp.to_s].join("|") end Register arena Sunday, 9 June, 13
  • 125.
    configure do @@port_range =(10000..11000).to_a end get "/" do @arenas = Celluloid::Actor.all haml :arenas end post "/arena/start" do port = @@port_range.delete @@port_range.sample arena = Arena.new(request.host, port, request.port) arena.map_url = params[:map_url] arena.spritesheet_url = params[:spritesheet_url] arena.default_hp = params[:default_hp].to_i Celluloid::Actor[:"arena_#{port}"] = arena redirect "/" end get "/arena/stop/:name" do raise "No such arena" unless Celluloid::Actor[params[:name].to_sym] Celluloid::Actor[params[:name].to_sym].terminate redirect "/" end get "/config/:name" do arena = Celluloid::Actor[params[:name].to_sym] array = [arena.map_url, arena.spritesheet_url, arena.default_hp.to_s].join("|") end Terminate arena Sunday, 9 June, 13
  • 126.
    configure do @@port_range =(10000..11000).to_a end get "/" do @arenas = Celluloid::Actor.all haml :arenas end post "/arena/start" do port = @@port_range.delete @@port_range.sample arena = Arena.new(request.host, port, request.port) arena.map_url = params[:map_url] arena.spritesheet_url = params[:spritesheet_url] arena.default_hp = params[:default_hp].to_i Celluloid::Actor[:"arena_#{port}"] = arena redirect "/" end get "/arena/stop/:name" do raise "No such arena" unless Celluloid::Actor[params[:name].to_sym] Celluloid::Actor[params[:name].to_sym].terminate redirect "/" end get "/config/:name" do arena = Celluloid::Actor[params[:name].to_sym] array = [arena.map_url, arena.spritesheet_url, arena.default_hp.to_s].join("|") end Let client know about the customizations Sunday, 9 June, 13
  • 127.
    configure do @@port_range =(10000..11000).to_a end get "/" do @arenas = Celluloid::Actor.all haml :arenas end post "/arena/start" do port = @@port_range.delete @@port_range.sample arena = Arena.new(request.host, port, request.port) arena.map_url = params[:map_url] arena.spritesheet_url = params[:spritesheet_url] arena.default_hp = params[:default_hp].to_i Celluloid::Actor[:"arena_#{port}"] = arena redirect "/" end get "/arena/stop/:name" do raise "No such arena" unless Celluloid::Actor[params[:name].to_sym] Celluloid::Actor[params[:name].to_sym].terminate redirect "/" end get "/config/:name" do arena = Celluloid::Actor[params[:name].to_sym] array = [arena.map_url, arena.spritesheet_url, arena.default_hp.to_s].join("|") end Sunday, 9 June, 13
  • 128.
  • 129.
  • 130.