Pistos-weewar-ai 2008.06.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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