require 'gtk2'
require 'gnomecanvas2'
require 'gconf2'
module AntGame
module ServerView
Gtk.init
class ColorList
def initialize
@array = [[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[1.0, 1.0, 0.0],
[1.0, 0.0, 1.0],
[0.0, 1.0, 1.0]] end
def [](index, type = :normal)
@array[index] ||= [rand, rand, rand]
case type
when :light
"#%02x%02x%02x" % @array[index].map{ | c | 0.5 * (c + 1.0) }.map{|c| c * 255 }
else
"#%02x%02x%02x" % @array[index].map{|c| c * 255 }
end
end
end
PLAYER_COLORS = ColorList.new
class Sprite < Gnome::CanvasGroup
include Observable;
attr_accessor :width, :height
attr_reader :object
def initialize(root, object)
@object = object self.width = 30
self.height = 30
super(root, :x => object.x * self.width + 0.5 * self.width, :y => object.y * self.height + 0.5 * self.height)
object.register(:remove) do self.destroy end
object.register(:update) do self.update end
end
def update
end
end
class AntSprite < Sprite
def initialize(root, object)
super
@ant_elements = []
@ant_elements << Gnome::CanvasEllipse.new(self,
:x1 => 3.0 / 8.0 * self.width - 0.5 * self.width, :y1 => 1.0 / 4.0 * self.height - 0.5 * self.height,
:x2 => 5.0 / 8.0 * self.width - 0.5 * self.width, :y2 => 2.0 / 4.0 * self.height - 0.5 * self.height,
:fill_color => PLAYER_COLORS[@object.player.id],
:outline_color => "black",
:width_pixels => 1.0)
@ant_elements << Gnome::CanvasEllipse.new(self,
:x1 => 3.0 / 8.0 * self.width - 0.5 * self.width, :y1 => 2.0 / 4.0 * self.height - 0.5 * self.height,
:x2 => 5.0 / 8.0 * self.width - 0.5 * self.width, :y2 => 3.0 / 4.0 * self.height - 0.5 * self.height,
:fill_color => PLAYER_COLORS[@object.player.id],
:outline_color => "black",
:width_pixels => 1.0)
@ant_elements << Gnome::CanvasEllipse.new(self,
:x1 => 3.0 / 8.0 * self.width - 0.5 * self.width, :y1 => 3.0 / 4.0 * self.height - 0.5 * self.height,
:x2 => 5.0 / 8.0 * self.width - 0.5 * self.width, :y2 => 4.0 / 4.0 * self.height - 0.5 * self.height,
:fill_color => PLAYER_COLORS[@object.player.id],
:outline_color => "black",
:width_pixels => 1.0)
Gnome::CanvasLine.new(self,
:points => [[3.0 / 8.0 * self.width - 0.5 * self.width, 1.0 / 4.0 * self.height - 0.5 * self.height],
[1.0 / 4.0 * self.width - 0.5 * self.width, 0 * self.height - 0.5 * self.height]],
:fill_color => 'black',
:width_pixels => 1.0)
Gnome::CanvasLine.new(self,
:points => [[5.0 / 8.0 * self.width - 0.5 * self.width, 1.0 / 4.0 * self.height - 0.5 * self.height],
[3.0 / 4.0 * self.width - 0.5 * self.width, 0 * self.height - 0.5 * self.height]],
:fill_color => 'black',
:width_pixels => 1.0)
@food = Gnome::CanvasEllipse.new(self,
:x1 => 4.0 / 5.0 * self.width - 0.5 * self.width,
:y1 => 1.0 / 10.0 * self.height - 0.5 * self.height,
:x2 => 5.0 / 5.0 * self.width - 0.5 * self.width,
:y2 => 1.0 / 10.0 * self.height + 1.0 / 5.0 * self.height - 0.5 * self.height,
:fill_color => 'brown',
:outline_color => 'yellow')
@food.hide
@energie = Gnome::CanvasRect.new(self,
:x1 => 0.9 * self.width - 0.5 * self.width,
:y1 => 0.0 * self.height - 0.5 * self.height,
:x2 => 1.0 * self.width - 0.5 * self.width,
:y2 => 1.0 * self.height - 0.5 * self.height,
:fill_color => 'red',
:outline_color => '#eeeeee')
update
end
DIRECTIONS = {:north => 0, :east => 90, :south => 180, :west => 270}
def update
super
self.affine_absolute(Art::Affine.translate(self.object.x * self.width + 0.5 * self.width,
self.object.y * self.height + 0.5 * self.height) *
Art::Affine.translate(-3.0 + 6.0 * rand, -3.0 + 6.0 * rand) *
Art::Affine.rotate(DIRECTIONS[self.object.direction]))
if self.object.has_food then @food.show else @food.hide end
if self.object.dead?
@ant_elements.each do | ae | ae.fill_color = '#eeeeee' if ae.respond_to?:fill_color= end
self.affine_relative(Art::Affine.scale(0.5, 0.5))
end
@energie.y1 = (1.0 - self.object.energie) * self.height - 0.5 * self.height
end
end
class CellSprite < Sprite
EPSILON = 0.5
def initialize(root, object)
super
@rect = Gnome::CanvasRect.new(self, :x1 => -0.5 * self.width, :y1 => -0.5 * self.height,
:x2 => 0.5 * self.width, :y2 => 0.5 * self.height,
:fill_color => "white",
:outline_color => "black",
:width_pixels => 0.5)
@food = []
@pheromone_dots = {}
self.lower_to_bottom
update
end
def update
super
self.affine_absolute(Art::Affine.translate(self.object.x * self.width, self.object.y * self.height) *
Art::Affine.translate(0.5 * self.width, 0.5 * self.height))
@object.pheromone.each do | player, pheromone |
size = 0.3 * self.width * pheromone
player = player.id
unless @pheromone_dots[player]
x, y = 0.5 * self.width * rand - 0.25 * self.width, 0.5 * self.height * rand - 0.25 * self.height
@pheromone_dots[player] = {:center => [x, y,],
:item => Gnome::CanvasEllipse.new(self,
:x1 => x - size,
:y1 => y - size,
:x2 => x + size,
:y2 => y + size,
:fill_color => PLAYER_COLORS[player, :light],
:outline_color => '#eeeeee')}
end
x, y = @pheromone_dots[player][:center]
item = @pheromone_dots[player][:item]
if size < EPSILON
item.hide
else
item.x1 = x - size
item.y1 = y - size
item.x2 = x + size
item.y2 = y + size
item.show
end
end
@rect.fill_color = @object.home ? PLAYER_COLORS[@object.home.id] : 'white'
while @food.length < @object.food
@food << Gnome::CanvasEllipse.new(self,
:x1 => -0.5 * self.width,
:y1 => 0.5 * self.height - @food.length * 1.0 / 10.0 * self.height - 1.0 / 5.0 * self.height,
:x2 => -0.5 * self.width + 0.3 * self.width,
:y2 => 0.5 * self.height - @food.length * 1.0 / 10.0 * self.height,
:fill_color => 'brown',
:outline_color => 'yellow')
end
while @food.length > @object.food
@food.pop.destroy
end
end
end
class WorldView < Gnome::Canvas
attr_reader :world
def initialize(world)
super(true)
@world = world
world.each_ant do | ant | add_ant(ant) end
world.register(:ant_added) do | world, ant | add_ant(ant) end
width = 30 * self.world.width + 1
height = 30 * self.world.height + 1
self.set_size_request(width, height)
self.set_scroll_region(0, 0, width, height)
self.world.each do | cell | CellSprite.new(self.root, cell) end
end
def to_png(file)
pixbuf = Gdk::Pixbuf.from_drawable(Gdk::Colormap.system, self.window,
0, 0, self.width, self.height)
pixbuf.save(file, "png")
end
def grab_frame
self.update_now
@image_number ||= 0
@image_number += 1
puts "Saving #{"ants_%03d.png" % @image_number}"
to_png("ants_%03d.png" % @image_number)
end
protected
def add_ant(ant)
AntSprite.new(self.root, ant)
end
end
class MainWindow < Gtk::Window
include Gtk
include Observable
def initialize(title = nil)
super()
set_title("#{title}") if title
signal_connect('destroy') do
signal_event(:quit)
Gtk.main_quit
end
end
def quit
destroy
true
end
end
class AntServerView < MainWindow
attr_accessor :grab_video
def initialize(server)
super('Arca Server')
@server = server
@server.register(:new_round) do | round | new_round() end
@server.register(:finished) do finished() end
self.border_width = 2
hbox = HBox.new(false, 2)
hbox.border_width = 2
self.add(hbox)
vbox = VBox.new(false, 2)
hbox.pack_start(vbox, true, false, 0)
@worldview = WorldView.new(@server.world)
vbox.pack_start(@worldview, true, false, 0)
@status = Label.new('Waiting...')
vbox.pack_start(@status, false, false, 0)
@settingsBox = VBox.new(false, 20)
@settingsBox.border_width = 0
hbox.pack_start(@settingsBox, false, true, 0)
button = Button.new("Quit")
button.signal_connect("clicked") do |widget, event| quit end
@settingsBox.pack_start(button, false, false, 0)
button = Button.new("Snapshot")
button.signal_connect("clicked") do |widget, event| @worldview.to_png("snapshot.png") end
@settingsBox.pack_start(button, false, false, 0)
@settingsBox.pack_start(Label.new('Rounds: '), false, false, 0)
@progress = ProgressBar.new()
@settingsBox.pack_start(@progress, false, false, 0)
@playerprogress = {}
end
def start
self.show_all
Gtk.main
end
def new_round
@worldview.grab_frame if self.grab_video
@progress.fraction = @server.round.to_f / @server.rounds.to_f
@status.text = "Round #{@server.round} of #{@server.rounds}"
@server.players.each do | player |
unless @playerprogress[player.id]
@settingsBox.pack_start(Label.new("Player: #{player.id}"), false, false, 0)
@playerprogress[player.id] = ProgressBar.new()
@settingsBox.pack_start(@playerprogress[player.id], false, false, 0)
@settingsBox.show_all
end
end
@server.players.each do | player | @playerprogress[player.id].fraction = player.food.to_f / @server.food.to_f end
end
def finished
@status.text = 'Finished'
end
end
end
end