Thread.abort_on_exception = true
require 'socket' require 'thread' require 'ants'
require 'observable'
require 'ants_server.view'
module AntGame::Server
include AntGame
class Cell
attr_reader :ants, :pheromone, :food, :home
attr_reader :x, :y
include Observable
def initialize(x, y)
@x = x
@y = y
@ants = []
@pheromone = {}
self.food = 0
self.home = nil
end
def food=(food)
@food = food
self.home.food = food if self.home
signal_event(:update)
end
def home=(player)
@home = player
signal_event(:update)
end
def to_s
self.ants.map{|a|a.player.id}.uniq.join.ljust(2) + \
self.pheromone.keys.map{|p|p.id}.join.ljust(2) + \
(self.food > 0 ? self.food.to_s : ' ') + \
(self.home ? self.home.id.to_s : ' ')
end
def add_ant(ant)
self.pheromone[ant.player] = 1.0
self.ants << ant
signal_event(:update)
end
def remove_ant(ant)
self.ants.delete(ant)
signal_event(:update)
end
def age
self.pheromone.each_key do | player | self.pheromone[player] *= 0.9 end
signal_event(:update)
end
end
class World
attr_reader :width, :height
include Observable
include Enumerable
def initialize(width, height)
@width = width; @height = height
@cells = Array.new(width) { | x | Array.new(height) { | y | Cell.new(x, y) } }
@ants = []
end
def to_s
@cells.collect { | col | col.collect { | cell | cell.to_s }.join('|') }.join("\n" + ('------+' * (self.width)) + "\n")
end
def [](x, y)
return nil if (x < 0) or (y < 0) or (width <= x) or (height <= y)
@cells[x][y]
end
def each
@cells.each do | col | col.each do | cell | yield cell end end
end
def create_ant(player, x, y)
ant = ServerAnt.new(self, player, x, y, @ants.length)
self[x, y].add_ant(ant)
@ants << ant
signal_event(:ant_added, ant)
ant
end
def each_ant
@ants.each do |ant| yield ant end
end
def act
self.each do | cell | cell.age end
@ants.sort_by{ rand }.each do | ant | ant.act end
end
def add_food(count)
while count > 0 do
x, y = rand(self.width), rand(self.height)
unless self[x,y].home
self[x,y].food += 1
count -= 1
end
end
end
end
class ServerAnt < Ant
attr_accessor :player, :world, :next_action
attr_reader :id
include Observable
def initialize(world, player, x, y, id)
super(x, y)
@id = id
self.world = world
self.player = player
end
def act
return if dead?
begin
self.send self.next_action
self.next_action = Actions::WAIT
signal_event(:update)
rescue => e
$stderr.puts "Ant should act unknown action #{@next_action} (#{e})"
raise
end
end
def wait
if cell.home == self.player
self.energie += (1.0 - self.energie) * 0.25
else
self.energie += (1.0 - self.energie) * 0.1
end
end
def take_food
if !self.has_food and self.world[self.x, self.y].food > 0
self.world[self.x, self.y].food -= 1
self.has_food = true
end
end
def attack
friend, foe = cell.ants.partition { | ant | ant.player == self.player }
return if foe.empty?
other = foe[rand(foe.length)]
self.drop_food
other.drop_food
e_s, e_o = self.energie, other.energie
penalty_s = (2.0 * (e_o / e_s) + (foe.length / friend.length) / 3.0)
penalty_o = (2.0 * (e_s / e_o) + (foe.length / friend.length) / 3.0)
penalty_s = 2 if penalty_s > 2
penalty_o = 2 if penalty_o > 2
self.energie -= 0.5 * rand * penalty_s
other.energie -= 0.5 * rand * penalty_o
end
def energie=(energie)
energie = 0 if energie < 0
@energie = energie
cell.remove_ant(self) if dead?
signal_event(:update)
end
def drop_food
if self.has_food
self.world[self.x, self.y].food += 1
self.has_food = false
end
end
def go
oldcell = cell
case self.direction
when :west
self.x -= 1 if self.x > 0
when :east
self.x += 1 if self.x < self.world.width - 1
when :north
self.y -= 1 if self.y > 0
when :south
self.y += 1 if self.y < self.world.height - 1
end
oldcell.remove_ant(self)
cell.add_ant(self)
end
def turn_left
self.direction = case self.direction
when :west then :south
when :south then :east
when :east then :north
when :north then :west
end
end
def turn_right
self.direction = case self.direction
when :west then :north
when :north then :east
when :east then :south
when :south then :west
end
end
def encode
result = AntPropertys.from_ant(self)
end
private
def cell
cell = self.world[self.x, self.y]
end
def cell_north
cell = self.world[self.x, self.y - 1]
end
def cell_east
cell = self.world[self.x + 1, self.y]
end
def cell_south
cell = self.world[self.x, self.y + 1]
end
def cell_west
cell = self.world[self.x - 1, self.y]
end
public
def has_food=(has_food)
@has_food = has_food
signal_event(:update)
end
end
class AntServer
attr_reader :world, :round, :rounds, :players, :food
include Observable
def initialize(host, port, playercount, rounds)
@server = TCPServer.new(host, port)
@rounds = rounds
@round = 0
@semaphore = Mutex.new
@playercount = playercount
@players = []
@world = World.new(16, 16)
@food = 30
end
def wait_for_players
while (self.players.length < @playercount) and (socket = @server.accept)
client = Player.new(socket)
client.id = self.players.length
client.ants = create_ants(client)
add_client(client)
end
play
end
def create_ants(player)
begin
x = rand(self.world.width)
y = rand(self.world.height)
end while self.world[x, y].home
self.world[x, y].home = player
(0..5).collect{ self.world.create_ant(player, x, y) }
end
def play
puts "Creating food"
self.world.add_food(self.food)
puts "Starting game"
self.rounds.times do
signal_event(:new_round)
@round += 1
self.players.map do | client | Thread.new(client) do | c | c.request_actions end
end.each do | thread | thread.join
end
self.world.act
end
signal_event(:finished)
end
def add_client(client)
puts "Client connected"
@semaphore.synchronize do self.players << client end
client.on_terminate do | c | remove_client(c) end
end
def remove_client(client)
puts "Client disconnected"
@semaphore.synchronize do self.players.delete(client) end
end
end
class Player
attr_accessor :ants
attr_reader :id
attr_accessor :food
def initialize(socket)
@socket = socket
@on_received = @on_terminate = nil
@food = 0
end
def id=(id)
@id = id
@socket.print "#{Protocoll::PLAYER_ID} <#{self.id}>\n"
end
def close
@on_terminate.call(self) if @on_terminate
@socket.close
end
def request_actions
@socket.print "#{Protocoll::NEXT_ROUND}\n"
self.ants.each_with_index do | ant, index |
next if ant.dead?
@socket.print "#{Protocoll::ANT} <#{Marshal.dump(Protocoll::AntPropertys.from_ant(ant)).hex_encode}>\n"
line = @socket.gets("\n")
case line
when /^#{Protocoll::QUIT} raise "Client wants to quit" when /^#{Protocoll::ANT} ant.next_action = $1.to_sym
else
puts "Command not understood '#{line}'"
end
end
end
def on_terminate(&on_terminate)
@on_terminate = on_terminate
end
end
end
include AntGame::Server
host, port = ARGV[0] || 'localhost', ARGV[1] || 11112
player_count = (ARGV[2] || 2).to_i
rounds = (ARGV[3] || 1000).to_i
cs = AntServer.new(host, port, player_count, rounds)
view = AntGame::ServerView::AntServerView.new(cs)
server_thread = Thread.new(cs) do | s | s.wait_for_players end
view.register(:quit) do server_thread.exit end view.start
server_thread.join