Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Playing with Ruby

Playing with Ruby

A talk I gave at the RedDotRubyConf 2013 on how to create a simple online, real-time multi-player game using Gosu and Celluloid::IO.

Avatar for Sau Sheong Chang

Sau Sheong Chang

June 08, 2013
Tweet

More Decks by Sau Sheong Chang

Other Decks in Technology

Transcript

  1. Playing with Ruby How to write an online, real-time multi-player

    game with Ruby @sausheong Sunday, 9 June, 13
  2. 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
  3. 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
  4. update method called at every frame (60 frames per second)

    Contains game logic The main ‘controller’ of the game Sunday, 9 June, 13
  5. draw method Does the actual drawing of the game window

    Called after update Can also be called when necessary Sunday, 9 June, 13
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 59 48 25 26 27 28 29 30 31 32

    33 34 35 36 37 38 39 24 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 0 72 100 101 102 96 Sunday, 9 June, 13
  19. 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
  20. 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
  21. Editable maps Allows user to customize maps and backgrounds, using

    tiled sprites ..................... ..................... ..................... ..................... ..................... ..##............##... ...#............#.... ...#............#.... ...#............#.... ..##............##... ..................... ..................... ..................... ..................... ..................... Sunday, 9 June, 13
  22. ..................... ..................... ..................... ..................... ..................... ..##............##... ...#............#.... ...#............#.... ...#............#.... ..##............##...

    ..................... ..................... ..................... ..................... ..................... 20 x 32 = 640 15 x 32 = 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  51. @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
  52. 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
  53. 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
  54. 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
  55. 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
  56. 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
  57. 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
  58. 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
  59. 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
  60. 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
  61. 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
  62. 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
  63. . . . 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
  64. . . . 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
  65. . . . 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
  66. 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
  67. 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
  68. 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
  69. 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
  70. 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
  71. 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
  72. 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
  73. 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
  74. 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
  75. 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
  76. 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
  77. 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