Skip to content

Instantly share code, notes, and snippets.

@avdgaag
Created March 2, 2012 08:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save avdgaag/1956972 to your computer and use it in GitHub Desktop.
Save avdgaag/1956972 to your computer and use it in GitHub Desktop.
Text based fighting game
# Make sure you have eventmachine installed and run the server:
#
# ruby -rubygems game.rb
#
# When the server is running, clients can connect and issue commands.
# Currently supported commands:
#
# * look: list the players in the arena.
# * join [nickname]: join the arena using a nickname
# * leave: leave the arena
# * atack [player]: try to attack another player by name
#
# Players attack with a random number of hit points. When your hit points
# run out, you die and you leave the arena.
require 'eventmachine'
# Simple events module so that objects can observe other objects with
# any kind of handler object that responds to #call.
module Observable
def on(event, handler)
@observable_events ||= {}
@observable_events[event] ||= []
@observable_events[event] << handler
self
end
def trigger(event, *args)
@observable_events ||= {}
@observable_events[event] ||= []
@observable_events[event].each do |handler|
handler.call(self, *args)
end
end
end
# The game keeps track of players and makes them interact.
class Game
include Observable
attr_reader :players
def initialize
@players = []
end
def add_player(connection, name)
unless player = find_player(connection)
player = Player.new(connection, name)
@players << player
trigger :player_added, player
end
end
def retire_player(connection)
if player = find_player(connection)
@players.delete(player)
trigger :player_retired, player
end
end
def find_player(connection)
@players.find do |player|
player.connected_via?(connection)
end
end
def find_player_by_name(name)
@players.find do |player|
player.name == name
end
end
def player_list
@players.map { |player|
"#{player.name} (#{player.health})"
}.sort.join(', ')
end
def player_count
players.count
end
def make_move(connection, move, target = nil)
if player = find_player(connection)
case move
when :attack then
if other_player = find_player_by_name(target)
power = player.attack_power
begin
other_player.take_damage(power)
trigger :attack_hit, player, other_player, power
rescue Player::Death
trigger :attack_mortal_blow, player, other_player
retire_player other_player.connection
end
else
trigger :attack_miss, player
end
end
end
end
end
# The player is an actor in the game that can attack, take damage
# and die.
class Player
attr_reader :connection, :name, :health
Death = Class.new(StandardError)
def initialize(connection, name)
@connection, @name = connection, name
@health = 20
end
def connected_via?(connection)
@connection == connection
end
def take_damage(hit_points)
@health -= hit_points
raise Death if @health <= 0
end
def attack_power
rand(1..15)
end
def render(msg)
connection.render(msg)
end
end
# The server manages all connections and the game. It intercepts
# incoming commands and generates the output information.
class Server
attr_reader :connections
attr_reader :game
def initialize
@connections = []
@game = Game.new
@game.on :player_added, ->(game, player) {
broadcast "#{player.name} has entered the arena."
}
@game.on :player_retired, ->(game, player) {
broadcast "#{player.name} has left the arena."
}
@game.on :attack_hit, ->(game, player, other_player, power) {
broadcast "#{player.name} dealt #{power} damage to #{other_player.name}"
}
@game.on :attack_miss, ->(game, player) {
broadcast "#{player.name} swung his sword in the void and made a utter fool of himself."
}
@game.on :attack_mortal_blow, ->(game, player, other_player) {
other_player.render "You were killed by #{player.name}"
player.render "You killed #{other_player.name}"
broadcast "#{player.name} dealt a mortal blow to #{other_player.name}"
}
end
def start
@signature = EventMachine.start_server('0.0.0.0', 8081, Connection) do |connection|
add_connection(connection)
connection.on :init, public_method(:add_connection)
connection.on :leave, public_method(:remove_connection)
connection.on :input, public_method(:perform_action)
end
end
def stop
EventMachine.stop_server(@signature)
end
def perform_action(connection, data)
case data
when /^join (\w+)$/ then
game.add_player(connection, $1)
when /^look$/i then
connection.render "There are #{game.player_count} players in the arena:"
connection.render(game.player_list)
when /^attack (\w+)$/ then
game.make_move connection, :attack, $1
when /^leave$/i then
game.retire_player(connection)
else
connection.render 'Unrecognized command. Please try again.'
end
end
def add_connection(connection)
@connections << connection
connection.render 'Welcome to the game.'
broadcast "#{@connections.size} users in the lounge."
end
def remove_connection(connection)
@connections.delete(connection)
broadcast "#{@connections.size} users left in the lounge."
end
private
def broadcast(msg)
@connections.each do |connection|
connection.render msg
end
end
end
# The connection is a single client connected to the server
# and is responsible for taking input and relaying output.
class Connection < EventMachine::Connection
include Observable
attr_accessor :server
def post_init
trigger :init
end
def unbind
trigger :leave
end
def render(msg)
send_data ">> #{msg}\n"
end
def receive_data(data)
trigger :input, data.strip
end
end
Signal.trap('SIGINT') {
puts "Game stopped at #{Time.now}"
EventMachine.stop
}
EM.run {
Server.new.start
puts "Game started at #{Time.now}"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment