Pistos-weewar-ai 2008.06.10.0

Sign up to get free protection for your applications and to get access to all the features.
data/READTHAT ADDED
@@ -0,0 +1 @@
1
+ Don't read ME; read THAT!
data/Rakefile ADDED
@@ -0,0 +1,62 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/rdoctask'
5
+
6
+ $:.unshift File.join( File.dirname(__FILE__), "lib" )
7
+
8
+ root = File.expand_path( File.dirname(__FILE__) )
9
+
10
+ # ------------------
11
+
12
+ #task :default => ['spec']
13
+ #task :test => ['spec']
14
+
15
+ desc "generate rdoc"
16
+ Rake::RDocTask.new do |rdoc|
17
+ files = [
18
+ 'lib/**/*.rb',
19
+ #'examples/*.rb',
20
+ 'THAT',
21
+ 'READTHAT'
22
+ #'spec/**/*.rb',
23
+ ]
24
+ rdoc.rdoc_files.add( files )
25
+ rdoc.main = "WeewarAI::AI" # page to start on
26
+ rdoc.title = "Weewar AI Ruby library"
27
+ # rdoc.template = "/misc/pistos/unpack/allison-2.3/allison.rb"
28
+ rdoc.template = "jamis"
29
+ rdoc.rdoc_dir = '/var/www/localhost/htdocs/weewar/ai-doc' # rdoc output folder
30
+ rdoc.options << '--line-numbers' << '--inline-source'
31
+ end
32
+
33
+ #desc 'Run coverage examiner (rcov)'
34
+ #task 'rcov' do
35
+ # exec( "rcov -o /var/www/localhost/htdocs/m4dbi/rcov spec/*.rb" )
36
+ #end
37
+
38
+ #desc 'Run all specs'
39
+ #task 'spec' do
40
+ # exec "bacon #{root}/spec/*.rb"
41
+ #end
42
+
43
+ #desc 'Build nightly gem'
44
+ #task 'nightly' do
45
+ # output = `gem build #{root}/gemspecs/m4dbi-nightly.gemspec`
46
+ # version = Time.now.strftime( "%Y.%m.%d" )
47
+ # `mv m4dbi-#{version}.gem m4dbi-nightly.gem`
48
+ #end
49
+
50
+ desc 'Make release'
51
+ task 'release' do
52
+ output = `gem build weewar-ai.gemspec`
53
+ end
54
+
55
+ #desc 'Build examples from specs'
56
+ #task 'examples' do
57
+ # Dir[ 'spec/*.rb' ].each do |specfile|
58
+ # next if specfile =~ /helper\.rb/
59
+ # base = File.basename( specfile, ".rb" )
60
+ # `ruby -I /misc/svn/specs2examples/lib /misc/svn/specs2examples/bin/specs2examples #{specfile} > /var/www/localhost/htdocs/m4dbi/examples/#{base}.html`
61
+ # end
62
+ #end
data/THAT ADDED
@@ -0,0 +1,23 @@
1
+ == Dependencies
2
+
3
+ - XMLSimple
4
+ - Mechanize
5
+ - Hpricot
6
+
7
+ The dependencies should be pulled along with the main gem installation.
8
+
9
+ == Installation
10
+
11
+ gem install weewar-ai --source http://purepistos.net
12
+
13
+ == Usage
14
+
15
+ See examples/ dir. See rdoc at http://weewar.purepistos.net/ai-doc/ .
16
+
17
+ == Source Code
18
+
19
+ git clone git://github.com/Pistos/weewar-ai.git
20
+ cd /usr/lib/ruby/site_ruby/1.8
21
+ ln -s /path/to/cloned/weewar-ai/lib/weewar-ai.rb
22
+ ln -s /path/to/cloned/weewar-ai/lib/weewar-ai
23
+
data/examples/basic.rb ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'weewar-ai'
4
+ #$debug = true
5
+
6
+ # A simple, working example of using the WeewarAI library.
7
+ # This bot's strategy is simply to build infantry and move them onto any
8
+ # bases it does not own. It will attack any enemies it meets along the way.
9
+
10
+ class AIBasic < WeewarAI::AI
11
+
12
+ def initialize
13
+ super(
14
+ :server => 'test.weewar.com',
15
+ :username => 'aiBasic', # change this to your bot's username
16
+ :api_key => 'GMmTBxE2ztNbdABAW5vgVZVhH' # change this to your bot's API key
17
+ )
18
+ end
19
+
20
+ def run_once
21
+ one_accepted = accept_all_invitations
22
+ if one_accepted
23
+ puts "Accepted some invitations."
24
+ refresh
25
+ end
26
+
27
+ @needy_games.each do |g|
28
+ take_turn g
29
+ end
30
+ end
31
+
32
+ def take_turn( game )
33
+ puts
34
+ puts "*" * 80
35
+ puts "Taking turn for game #{game.id}"
36
+ i = me = my = game.my_faction
37
+
38
+ # Find a place to go, things to shoot
39
+ destination = game.enemy_bases.first
40
+ enemies = game.enemy_units
41
+
42
+ # Move units
43
+ my.units.find_all { |u| not u.finished? }.each do |unit|
44
+ unit.move_to(
45
+ destination,
46
+ :also_attack => enemies
47
+ )
48
+ end
49
+
50
+ # Build
51
+ game.my_bases.each do |base|
52
+ next if base.occupied?
53
+
54
+ if i.can_afford?( :linf )
55
+ base.build :linf
56
+ end
57
+ end
58
+
59
+ puts "Ending turn for game #{game.id}"
60
+ game.finish_turn
61
+ end
62
+
63
+ end
64
+
65
+ begin
66
+ bot = AIBasic.new
67
+ bot.run_once
68
+ rescue Exception => e
69
+ $stderr.puts "#{e.class}: #{e.message}"
70
+ $stderr.puts e.backtrace.join( "\n\t" )
71
+ end
data/lib/weewar-ai.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'xmlsimple'
2
+
3
+ _DIR = File.expand_path( File.dirname( __FILE__ ) )
4
+
5
+ require "#{_DIR}/weewar-ai/__dir__"
6
+ require "#{__DIR__}/weewar-ai/traits"
7
+ require "#{__DIR__}/weewar-ai/player"
8
+ require "#{__DIR__}/weewar-ai/faction"
9
+ require "#{__DIR__}/weewar-ai/hex"
10
+ require "#{__DIR__}/weewar-ai/unit"
11
+ require "#{__DIR__}/weewar-ai/map"
12
+ require "#{__DIR__}/weewar-ai/game"
13
+ require "#{__DIR__}/weewar-ai/api"
14
+ require "#{__DIR__}/weewar-ai/ai"
@@ -0,0 +1,23 @@
1
+ # From Ramaze ( http://ramaze.net )
2
+ # Copyright (c) 2008 Michael Fellinger m.fellinger@gmail.com
3
+
4
+ # Extensions for Kernel
5
+
6
+ module Kernel
7
+ unless defined?__DIR__
8
+
9
+ # This is similar to +__FILE__+ and +__LINE__+, and returns a String
10
+ # representing the directory of the current file is.
11
+ # Unlike +__FILE__+ the path returned is absolute.
12
+ #
13
+ # This method is convenience for the
14
+ # File.expand_path(File.dirname(__FILE__))
15
+ # idiom.
16
+ #
17
+
18
+ def __DIR__()
19
+ filename = caller[0][/^(.*):/, 1]
20
+ File.expand_path(File.dirname(filename))
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # All parts of the library reside in the WeewarAI module.
4
+ module WeewarAI
5
+
6
+ # Begin creating your bot by subclassing the WeewarAI::AI class.
7
+ #
8
+ # class MyBot < WeewarAI::AI
9
+ # end
10
+ #
11
+ # See examples/basic.rb for a simple working example.
12
+ class AI
13
+ # In your bot's initialize method, call the AI superclass's
14
+ # initialize method with these parameters (values are examples):
15
+ #
16
+ # super(
17
+ # :server => 'test.weewar.com',
18
+ # :username => 'aiMyBot',
19
+ # :api_key => 'r0goujPhKJEM6udL3RNfBtcS9',
20
+ # )
21
+ #
22
+ # Retrieve your API key from http://test.weewar.com/apiToken .
23
+ #
24
+ # Once constructed, your AI instance will have the following instance
25
+ # variables available:
26
+ #
27
+ # @games : An Array of the Game s your AI is in.
28
+ #
29
+ # @needy_games : The subset of Game s in which it is your AI's turn.
30
+ #
31
+ # @invitations : The ids of Game s to which your AI is invited.
32
+ def initialize( params )
33
+ @username = params[ :username ]
34
+ WeewarAI::API.init( params )
35
+ refresh
36
+ end
37
+
38
+ # Retrieves your AI's headquarters data from the game server,
39
+ # and updates the instance variables @games, @needy_games and
40
+ # @invitations.
41
+ # bot.refresh
42
+ def refresh
43
+ xml = XmlSimple.xml_in(
44
+ WeewarAI::API.get( "/headquarters" ),
45
+ { 'ForceArray' => [ 'game' ], }
46
+ )
47
+ gxml = xml[ 'game' ]
48
+ @invitations = gxml.find_all { |g|
49
+ g[ 'link' ] =~ /join/
50
+ }.map { |g|
51
+ g[ 'id' ].to_i
52
+ }
53
+ gxml = gxml.reject { |g|
54
+ @invitations.include? g[ 'id' ].to_i
55
+ }
56
+ @games = gxml.map { |g|
57
+ WeewarAI::Game.new( g[ 'id' ] )
58
+ }
59
+ @needy_games = @games.find_all { |g|
60
+ g.current_player and g.current_player.name == @username
61
+ }
62
+ end
63
+
64
+ # Accepts the invitation to join the game identified by the game_id.
65
+ # bot.accept_invitation 165
66
+ def accept_invitation( game_id )
67
+ response = WeewarAI::API.accept_invitation( game_id )
68
+ %r{<ok/>} === response
69
+ end
70
+
71
+ # Accepts all game invitations.
72
+ # bot.accept_all_invitations
73
+ def accept_all_invitations
74
+ one_accepted = false
75
+ @invitations.each do |invitation|
76
+ one_accepted ||= accept_invitation( invitation )
77
+ end
78
+ one_accepted
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,100 @@
1
+ require 'mechanize'
2
+
3
+ module WeewarAI
4
+ GOD_LOVES_YOU = true
5
+ VERSION = '2008.06.09'
6
+
7
+ # The API class contains some lower-level methods. You should not normally
8
+ # need to use them yourself. Instead, use the methods of your AI instance.
9
+ class API
10
+ # Initializes the connection to the weewar.com API. You do not need to
11
+ # call this yourself; it is called for you when you subclass AI.
12
+ def self.init( params )
13
+ [ :server, :username, :api_key ].each do |required_param|
14
+ if params[ required_param ].nil? or params[ required_param ].strip.empty?
15
+ raise "Missing #{required_param}."
16
+ end
17
+ end
18
+
19
+ trait[ :agent ] = agent = WWW::Mechanize.new
20
+ trait[ :username ], trait[ :api_key ] = params[ :username ], params[ :api_key ]
21
+ agent.basic_auth( params[ :username ], params[ :api_key ] )
22
+ trait[ :server ] = params[ :server ]
23
+
24
+ Hex.initialize_specs
25
+ end
26
+
27
+ def self.username
28
+ trait[ :username ]
29
+ end
30
+ def self.agent
31
+ trait[ :agent ]
32
+ end
33
+ def self.server
34
+ trait[ :server ]
35
+ end
36
+
37
+ def self.get( path )
38
+ result = nil
39
+ retries = 0
40
+ while GOD_LOVES_YOU
41
+ url = "http://#{server}/api1/#{path}"
42
+ begin
43
+ result = agent.get( url ).body
44
+ break
45
+ rescue EOFError, Errno::EPIPE => e
46
+ if retries < 10
47
+ $stderr.puts "Communications error fetching '#{url}'. Retrying (#{retries})..."
48
+ sleep retries + 3
49
+ retries += 1
50
+ else
51
+ break
52
+ end
53
+ end
54
+ end
55
+ if $debug
56
+ $stderr.puts "XML RECEIVE: #{result}"
57
+ end
58
+ result
59
+ end
60
+
61
+ def self.send( xml )
62
+ url = URI.parse( "http://#{server}/api1/eliza" )
63
+ req = Net::HTTP::Post.new( url.path )
64
+ req.basic_auth( trait[ :username ], trait[ :api_key ] )
65
+ req[ 'Content-Type' ] = 'application/xml'
66
+ result = Net::HTTP.new( url.host, url.port ).start { |http|
67
+ if $debug
68
+ $stderr.puts "XML SEND: #{xml}"
69
+ end
70
+ http.request( req, xml )
71
+ }.body
72
+ if $debug
73
+ $stderr.puts "XML RECEIVE: #{result}"
74
+ end
75
+ result
76
+ end
77
+
78
+ # Accepts an invitation to a game.
79
+ def self.accept_invitation( game_id )
80
+ send "<weewar game='#{game_id}'><acceptInvitation/></weewar>"
81
+ end
82
+
83
+ # Declines an invitation to a game.
84
+ def self.decline_invitation( game_id )
85
+ send "<weewar game='#{game_id}'><declineInvitation/></weewar>"
86
+ end
87
+
88
+ # Removes a game from your AI's headquarters.
89
+ def self.remove_game( game_id )
90
+ send "<weewar game='#{game_id}'><removeGame/></weewar>"
91
+ end
92
+
93
+ class << self
94
+ alias acceptInvitation accept_invitation
95
+ alias declineInvitation decline_invitation
96
+ alias removeGame remove_game
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,57 @@
1
+ module WeewarAI
2
+
3
+ # Instances of the Faction class correspond to factions in a Game.
4
+ # They provide some utility methods about factions, such as whether
5
+ # or not a faction is currently playing in a Game, or whether it can
6
+ # afford to buy a unit type.
7
+ class Faction
8
+ attr_reader :credits, :player_id, :player_name, :state
9
+
10
+ # You should not need to instantiate any Faction s on your own;
11
+ # they are created for you by Game instances.
12
+ def initialize( game, xml, ordinal )
13
+ @game, @ordinal = game, ordinal
14
+ @credits = xml[ 'credits' ].to_i
15
+ @current = ( xml[ 'current' ] == 'true' )
16
+ @player_id = xml[ 'playerId' ].to_i
17
+ @player_name = xml[ 'playerName' ]
18
+ @state = xml[ 'state' ]
19
+ rescue Exception => e
20
+ $stderr.puts "Input XML: " + xml.inspect
21
+ raise e
22
+ end
23
+ alias playerId player_id
24
+ alias playerName player_name
25
+
26
+ # Whether or not this Faction is the one whose turn it is in the Game.
27
+ # i = me = my = game.my_faction
28
+ # is_my_turn = i.current?
29
+ def current?
30
+ @current
31
+ end
32
+
33
+ def playing?
34
+ @state == 'playing'
35
+ end
36
+
37
+ # True iff the faction has enough credits to purchase a unit of the given type.
38
+ # i = me = my = game.my_faction
39
+ # if i.can_afford? :hart
40
+ # my_base.build :hart
41
+ # end
42
+ def can_afford?( type )
43
+ @credits >= WeewarAI::Unit::UNIT_COSTS[ type ]
44
+ end
45
+
46
+ # An Array of the Unit s in the Game belonging to this Faction.
47
+ # i = me = my = game.my_faction
48
+ # my_units = my.units
49
+ def units
50
+ @game.units.find_all { |u| u.faction == self }
51
+ end
52
+
53
+ def to_s
54
+ @player_name
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,173 @@
1
+ require 'net/http'
2
+ require 'pistos'
3
+
4
+ module WeewarAI
5
+
6
+ # The Game class is your interface to a game on the weewar server.
7
+ # Game instances are used to do such things as finish turns, surrender,
8
+ # and abandon. Also, you access game maps and units through a Game
9
+ # instance.
10
+ class Game
11
+ attr_reader :id, :name, :round, :state, :pending_invites, :pace, :type,
12
+ :url, :map, :map_url, :credits_per_base, :initial_credits, :playing_since,
13
+ :players, :units
14
+ attr_accessor :last_attacked
15
+
16
+ # Instantiate a new Game instance corresponding to the weewar game
17
+ # with the given id number.
18
+ # game = WeewarAI::Game.new( 132 )
19
+ def initialize( id )
20
+ @id = id.to_i
21
+ refresh
22
+ end
23
+ alias pendingInvites pending_invites
24
+ alias mapUrl map_url
25
+ alias creditsPerBase credits_per_base
26
+ alias initialCredits initial_credits
27
+ alias playingSince playing_since
28
+
29
+ # Hits the weewar server for all the game state data as it sees it.
30
+ # All internal variables are updated to match.
31
+ # my_game.refresh
32
+ def refresh
33
+ xml = XmlSimple.xml_in(
34
+ WeewarAI::API.get( "/gamestate/#{id}" ),
35
+ { 'ForceArray' => [ 'faction', 'player', 'terrain', 'unit' ], }
36
+ )
37
+ #$stderr.puts xml.nice_inspect
38
+ @name = xml[ 'name' ]
39
+ @round = xml[ 'round' ].to_i
40
+ @state = xml[ 'state' ]
41
+ @pending_invites = ( xml[ 'pendingInvites' ] == 'true' )
42
+ @pace = xml[ 'pace' ].to_i
43
+ @type = xml[ 'type' ]
44
+ @url = xml[ 'url' ]
45
+ @players = xml[ 'players' ][ 'player' ].map { |p| WeewarAI::Player.new( p ) }
46
+ @map = WeewarAI::Map.new( self, xml[ 'map' ].to_i )
47
+ @map_url = xml[ 'mapUrl' ]
48
+ @credits_per_base = xml[ 'creditsPerBase' ]
49
+ @initial_credits = xml[ 'initialCredits' ]
50
+ @playing_since = Time.parse( xml[ 'playingSince' ] )
51
+
52
+ @units = Array.new
53
+ @factions = Array.new
54
+ xml[ 'factions' ][ 'faction' ].each_with_index do |faction_xml,ordinal|
55
+ faction = Faction.new( self, faction_xml, ordinal )
56
+ @factions << faction
57
+
58
+ faction_xml[ 'unit' ].each do |u|
59
+ hex = @map[ u[ 'x' ], u[ 'y' ] ]
60
+ unit = Unit.new(
61
+ self,
62
+ hex,
63
+ faction,
64
+ u[ 'type' ],
65
+ u[ 'quantity' ].to_i,
66
+ u[ 'finished' ] == 'true',
67
+ u[ 'capturing' ] == 'true'
68
+ )
69
+ @units << unit
70
+ hex.unit = unit
71
+ end
72
+
73
+ faction_xml[ 'terrain' ].each do |terrain|
74
+ hex = @map[ terrain[ 'x' ], terrain[ 'y' ] ]
75
+ if hex.type == :base
76
+ hex.faction = faction
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ # Sends some command XML for this game to the server. You should
83
+ # generally never need to call this method directly; it is used
84
+ # internally by the Game class.
85
+ def send( command_xml )
86
+ WeewarAI::API.send "<weewar game='#{@id}'>#{command_xml}</weewar>"
87
+ end
88
+
89
+ #-- -------------------------
90
+ # API Commands
91
+ #++
92
+
93
+ # End turn in this game.
94
+ # game.finish_turn
95
+ def finish_turn
96
+ send "<finishTurn/>"
97
+ end
98
+ alias finishTurn finish_turn
99
+
100
+ # Surrender in this game.
101
+ # game.surrender
102
+ def surrender
103
+ send "<surrender/>"
104
+ end
105
+
106
+ # Abandon this game.
107
+ # game.abandon
108
+ def abandon
109
+ send "<abandon/>"
110
+ end
111
+
112
+ #-- -------------------------
113
+ # Game state
114
+ #++
115
+
116
+ # The Player whose turn it is.
117
+ # turn_taker = game.current_player
118
+ def current_player
119
+ @players.find { |p| p.current? }
120
+ end
121
+
122
+ #-- --------------------------------------------------
123
+ # Utilities
124
+ #++
125
+
126
+ # The Faction of the given player.
127
+ # pistos_faction = game.faction_for_player 'Pistos'
128
+ def faction_for_player( player_name )
129
+ @factions.find { |f| f.player_name == player_name }
130
+ end
131
+
132
+ # Your AI's Faction in this game.
133
+ # me = my = i = game.my_faction
134
+ # puts "My name is #{my.player_name}."
135
+ # if i.can_afford? :htank
136
+ # my_base.build :htank
137
+ # end
138
+ def my_faction
139
+ faction_for_player WeewarAI::API.username
140
+ end
141
+
142
+ # An Array of the Units not belonging to your AI.
143
+ # bad_guys = game.enemy_units
144
+ def enemy_units
145
+ @units.find_all { |u| u.faction != my_faction }
146
+ end
147
+
148
+ # An Array of the base Hexes for this game.
149
+ # bases = game.bases
150
+ def bases
151
+ @map.bases
152
+ end
153
+
154
+ # An Array of the base Hexes owned by the given faction.
155
+ # their_bases = game.bases enemy_faction
156
+ def bases_of( faction )
157
+ @map.bases.find_all { |b| b.faction == faction }
158
+ end
159
+
160
+ # Your AI's bases in this game.
161
+ # good_bases = game.my_bases
162
+ def my_bases
163
+ bases_of my_faction
164
+ end
165
+
166
+ # An Array of bases not owned by your AI (including neutral bases).
167
+ # capturable_bases = game.enemy_bases
168
+ def enemy_bases
169
+ @map.bases.find_all { |b| b.faction != my_faction }
170
+ end
171
+
172
+ end
173
+ end