SlideShare una empresa de Scribd logo
1 de 130
Descargar para leer sin conexión
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

Más contenido relacionado

La actualidad más candente

La actualidad más candente (16)

The Ring programming language version 1.7 book - Part 53 of 196
The Ring programming language version 1.7 book - Part 53 of 196The Ring programming language version 1.7 book - Part 53 of 196
The Ring programming language version 1.7 book - Part 53 of 196
 
The Ring programming language version 1.5.2 book - Part 48 of 181
The Ring programming language version 1.5.2 book - Part 48 of 181The Ring programming language version 1.5.2 book - Part 48 of 181
The Ring programming language version 1.5.2 book - Part 48 of 181
 
The Ring programming language version 1.5.3 book - Part 49 of 184
The Ring programming language version 1.5.3 book - Part 49 of 184The Ring programming language version 1.5.3 book - Part 49 of 184
The Ring programming language version 1.5.3 book - Part 49 of 184
 
If your Champ goes missing
If your Champ goes missingIf your Champ goes missing
If your Champ goes missing
 
The Ring programming language version 1.6 book - Part 52 of 189
The Ring programming language version 1.6 book - Part 52 of 189The Ring programming language version 1.6 book - Part 52 of 189
The Ring programming language version 1.6 book - Part 52 of 189
 
Unity2015_No9_~Photonでオンラインゲーム~
 Unity2015_No9_~Photonでオンラインゲーム~  Unity2015_No9_~Photonでオンラインゲーム~
Unity2015_No9_~Photonでオンラインゲーム~
 
The Ring programming language version 1.5.3 book - Part 50 of 184
The Ring programming language version 1.5.3 book - Part 50 of 184The Ring programming language version 1.5.3 book - Part 50 of 184
The Ring programming language version 1.5.3 book - Part 50 of 184
 
The Ring programming language version 1.9 book - Part 59 of 210
The Ring programming language version 1.9 book - Part 59 of 210The Ring programming language version 1.9 book - Part 59 of 210
The Ring programming language version 1.9 book - Part 59 of 210
 
The Ring programming language version 1.8 book - Part 56 of 202
The Ring programming language version 1.8 book - Part 56 of 202The Ring programming language version 1.8 book - Part 56 of 202
The Ring programming language version 1.8 book - Part 56 of 202
 
The Ring programming language version 1.9 book - Part 60 of 210
The Ring programming language version 1.9 book - Part 60 of 210The Ring programming language version 1.9 book - Part 60 of 210
The Ring programming language version 1.9 book - Part 60 of 210
 
The Ring programming language version 1.5.4 book - Part 50 of 185
The Ring programming language version 1.5.4 book - Part 50 of 185The Ring programming language version 1.5.4 book - Part 50 of 185
The Ring programming language version 1.5.4 book - Part 50 of 185
 
What FizzBuzz can teach us about design
What FizzBuzz can teach us about designWhat FizzBuzz can teach us about design
What FizzBuzz can teach us about design
 
The Ring programming language version 1.2 book - Part 37 of 84
The Ring programming language version 1.2 book - Part 37 of 84The Ring programming language version 1.2 book - Part 37 of 84
The Ring programming language version 1.2 book - Part 37 of 84
 
The Ring programming language version 1.5.2 book - Part 49 of 181
The Ring programming language version 1.5.2 book - Part 49 of 181The Ring programming language version 1.5.2 book - Part 49 of 181
The Ring programming language version 1.5.2 book - Part 49 of 181
 
The Ring programming language version 1.3 book - Part 39 of 88
The Ring programming language version 1.3 book - Part 39 of 88The Ring programming language version 1.3 book - Part 39 of 88
The Ring programming language version 1.3 book - Part 39 of 88
 
30 hidden gems_in_python_3
30 hidden gems_in_python_330 hidden gems_in_python_3
30 hidden gems_in_python_3
 

Destacado

Photo mosaics With Ruby And Yahoo! BOSS
Photo mosaics With Ruby And Yahoo! BOSSPhoto mosaics With Ruby And Yahoo! BOSS
Photo mosaics With Ruby And Yahoo! BOSS
Sau Sheong Chang
 
W dorsey week4 assignment
W dorsey week4 assignmentW dorsey week4 assignment
W dorsey week4 assignment
BigWil
 
Blocks and loops.pptx
Blocks and loops.pptxBlocks and loops.pptx
Blocks and loops.pptx
sandeep kumar
 
Slides chapter3part1 ruby-forjavaprogrammers
Slides chapter3part1 ruby-forjavaprogrammersSlides chapter3part1 ruby-forjavaprogrammers
Slides chapter3part1 ruby-forjavaprogrammers
Giovanni924
 

Destacado (14)

Ruby and R
Ruby and RRuby and R
Ruby and R
 
Ruby, Rock & Roll
Ruby, Rock & RollRuby, Rock & Roll
Ruby, Rock & Roll
 
Ruby And The Cloud
Ruby And The CloudRuby And The Cloud
Ruby And The Cloud
 
Why JRuby?
Why JRuby?Why JRuby?
Why JRuby?
 
Photo mosaics With Ruby And Yahoo! BOSS
Photo mosaics With Ruby And Yahoo! BOSSPhoto mosaics With Ruby And Yahoo! BOSS
Photo mosaics With Ruby And Yahoo! BOSS
 
Ruby is Magic - Episode #7: Closures
Ruby is Magic - Episode #7: ClosuresRuby is Magic - Episode #7: Closures
Ruby is Magic - Episode #7: Closures
 
W dorsey week4 assignment
W dorsey week4 assignmentW dorsey week4 assignment
W dorsey week4 assignment
 
Blocks and loops.pptx
Blocks and loops.pptxBlocks and loops.pptx
Blocks and loops.pptx
 
Blocks by Lachs Cox
Blocks by Lachs CoxBlocks by Lachs Cox
Blocks by Lachs Cox
 
Slides chapter3part1 ruby-forjavaprogrammers
Slides chapter3part1 ruby-forjavaprogrammersSlides chapter3part1 ruby-forjavaprogrammers
Slides chapter3part1 ruby-forjavaprogrammers
 
Metaprogramming in Ruby
Metaprogramming in RubyMetaprogramming in Ruby
Metaprogramming in Ruby
 
Money, Sex and Evolution - Simulation and data analysis with Ruby and R
Money, Sex and Evolution - Simulation and data analysis with Ruby and RMoney, Sex and Evolution - Simulation and data analysis with Ruby and R
Money, Sex and Evolution - Simulation and data analysis with Ruby and R
 
Rapid Game Development with RUby and Gosu – Ruby Manor 4
Rapid Game Development with RUby and Gosu – Ruby Manor 4Rapid Game Development with RUby and Gosu – Ruby Manor 4
Rapid Game Development with RUby and Gosu – Ruby Manor 4
 
Study: The Future of VR, AR and Self-Driving Cars
Study: The Future of VR, AR and Self-Driving CarsStudy: The Future of VR, AR and Self-Driving Cars
Study: The Future of VR, AR and Self-Driving Cars
 

Similar a Playing With Ruby

ontents · Introduction· Objectives·.docx
ontents  ·      Introduction·      Objectives·.docxontents  ·      Introduction·      Objectives·.docx
ontents · Introduction· Objectives·.docx
cherishwinsland
 
4.4 advanced games
4.4   advanced games4.4   advanced games
4.4 advanced games
allenbailey
 
Galactic Wars XNA Game
Galactic Wars XNA GameGalactic Wars XNA Game
Galactic Wars XNA Game
Sohil Gupta
 

Similar a Playing With Ruby (20)

Unity
UnityUnity
Unity
 
GR8Conf 2009: Groovy Usage Patterns by Dierk König
GR8Conf 2009: Groovy Usage Patterns by Dierk KönigGR8Conf 2009: Groovy Usage Patterns by Dierk König
GR8Conf 2009: Groovy Usage Patterns by Dierk König
 
ontents · Introduction· Objectives·.docx
ontents  ·      Introduction·      Objectives·.docxontents  ·      Introduction·      Objectives·.docx
ontents · Introduction· Objectives·.docx
 
Taming Pythons with ZooKeeper
Taming Pythons with ZooKeeperTaming Pythons with ZooKeeper
Taming Pythons with ZooKeeper
 
Basics cocos2d
Basics cocos2dBasics cocos2d
Basics cocos2d
 
Fun With Ruby And Gosu Javier Ramirez
Fun With Ruby And Gosu Javier RamirezFun With Ruby And Gosu Javier Ramirez
Fun With Ruby And Gosu Javier Ramirez
 
Multiplayer games on iOS
Multiplayer games on iOSMultiplayer games on iOS
Multiplayer games on iOS
 
4.4 advanced games
4.4   advanced games4.4   advanced games
4.4 advanced games
 
07 problem-solving
07 problem-solving07 problem-solving
07 problem-solving
 
Monogame and xna
Monogame and xnaMonogame and xna
Monogame and xna
 
Galactic Wars XNA Game
Galactic Wars XNA GameGalactic Wars XNA Game
Galactic Wars XNA Game
 
Street runner final
Street runner finalStreet runner final
Street runner final
 
XNA On Windows Phone 7
XNA On Windows Phone 7XNA On Windows Phone 7
XNA On Windows Phone 7
 
Adobe: Changing the game
Adobe: Changing the gameAdobe: Changing the game
Adobe: Changing the game
 
WP7 HUB_XNA overview
WP7 HUB_XNA overviewWP7 HUB_XNA overview
WP7 HUB_XNA overview
 
Bucc Toy Project: Learn programming through Game Development
Bucc  Toy Project: Learn programming through Game DevelopmentBucc  Toy Project: Learn programming through Game Development
Bucc Toy Project: Learn programming through Game Development
 
[Longer ver] From Software to Hardware: How Do I Track My Cat with JavaScript
[Longer ver] From Software to Hardware: How Do I Track My Cat with JavaScript[Longer ver] From Software to Hardware: How Do I Track My Cat with JavaScript
[Longer ver] From Software to Hardware: How Do I Track My Cat with JavaScript
 
Game Development With Python and Pygame
Game Development With Python and PygameGame Development With Python and Pygame
Game Development With Python and Pygame
 
School For Games 2015 - Unity Engine Basics
School For Games 2015 - Unity Engine BasicsSchool For Games 2015 - Unity Engine Basics
School For Games 2015 - Unity Engine Basics
 
WP7 HUB_XNA
WP7 HUB_XNAWP7 HUB_XNA
WP7 HUB_XNA
 

Último

Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Victor Rentea
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
WSO2
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
?#DUbAI#??##{{(☎️+971_581248768%)**%*]'#abortion pills for sale in dubai@
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
panagenda
 

Último (20)

Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
 
FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
Artificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyArtificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : Uncertainty
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 
CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistan
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor Presentation
 

Playing With Ruby

  • 1. Playing with Ruby How to write an online, real-time multi-player game with Ruby @sausheong Sunday, 9 June, 13
  • 10. About me Sunday, 9 June, 13
  • 11. About me Sunday, 9 June, 13
  • 14. 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
  • 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
  • 18. update method called at every frame (60 frames per second) Contains game logic The main ‘controller’ of the game Sunday, 9 June, 13
  • 19. draw method Does the actual drawing of the game window Called after update Can also be called when necessary Sunday, 9 June, 13
  • 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
  • 23. Add a player Sunday, 9 June, 13
  • 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
  • 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
  • 38. 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
  • 39. 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
  • 41. 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
  • 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
  • 45. Editable maps Allows user to 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
  • 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 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
  • 52. 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
  • 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 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
  • 57. 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
  • 58. 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
  • 60. 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
  • 61. 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
  • 64. 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
  • 72. Client sends messages to the 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
  • 83. Client reads messages from the 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
  • 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
  • 97. 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
  • 98. 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
  • 99. 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
  • 100. 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
  • 101. 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
  • 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_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
  • 109. . . . 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
  • 110. . . . 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
  • 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
  • 116. Advanced stuff (a bit more) Sunday, 9 June, 13
  • 117. 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
  • 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