berlin-ai 0.0.20 → 0.0.21

Sign up to get free protection for your applications and to get access to all the features.
data/lib/ai/fake.rb ADDED
@@ -0,0 +1,243 @@
1
+ module Berlin
2
+ module Fake
3
+
4
+ MAP_DEFINITION = {
5
+ "types" => [
6
+ {"name" => "node", "points" => 0, "soldiers_per_turn" => 0},
7
+ {"name" => "city", "points" => 1, "soldiers_per_turn" => 1}
8
+ ],
9
+
10
+ "nodes" => [
11
+ {"id" => 1, "type" => "city"},
12
+ {"id" => 2, "type" => "node"},
13
+ {"id" => 3, "type" => "city"},
14
+ {"id" => 4, "type" => "node"},
15
+ {"id" => 5, "type" => "node"},
16
+ {"id" => 6, "type" => "city"},
17
+ {"id" => 7, "type" => "node"},
18
+ {"id" => 8, "type" => "city"}
19
+ ],
20
+
21
+ "paths" => [
22
+ {"from" => 1, "to" => 2},
23
+ {"from" => 2, "to" => 3},
24
+ {"from" => 2, "to" => 5},
25
+ {"from" => 3, "to" => 5},
26
+ {"from" => 5, "to" => 8},
27
+ {"from" => 8, "to" => 7},
28
+ {"from" => 7, "to" => 4},
29
+ {"from" => 6, "to" => 7},
30
+ {"from" => 6, "to" => 4},
31
+ {"from" => 4, "to" => 1},
32
+ ]
33
+ }
34
+
35
+ GAME_INFO = {
36
+ "game_id" => "7c7905c6-2423-4a91-b5e7-44ff10cddd5d",
37
+ "current_turn" => nil,
38
+ "maximum_number_of_turns" => 1000,
39
+ "number_of_players" => 2,
40
+ "time_limit_per_turn" => 5000,
41
+ "directed" => false,
42
+ "player_id" => nil
43
+ }
44
+
45
+ GAME_STATE = [
46
+ {"node_id" => 1, "player_id" => nil, "number_of_soldiers" => 0},
47
+ {"node_id" => 2, "player_id" => nil, "number_of_soldiers" => 0},
48
+ {"node_id" => 3, "player_id" => nil, "number_of_soldiers" => 0},
49
+ {"node_id" => 4, "player_id" => nil, "number_of_soldiers" => 0},
50
+ {"node_id" => 5, "player_id" => nil, "number_of_soldiers" => 0},
51
+ {"node_id" => 6, "player_id" => nil, "number_of_soldiers" => 0},
52
+ {"node_id" => 7, "player_id" => nil, "number_of_soldiers" => 0},
53
+ {"node_id" => 8, "player_id" => nil, "number_of_soldiers" => 0}
54
+ ]
55
+
56
+ Move = Struct.new(:player_id, :from, :to, :number_of_soldiers)
57
+
58
+ NodeState = Struct.new(:node_id, :player_id, :number_of_soldiers)
59
+
60
+ ConflictState = Struct.new(:node_id, :soldiers_per_player) do
61
+ def initialize(node_id)
62
+ super(node_id)
63
+ self.soldiers_per_player = Hash.new(0)
64
+ end
65
+
66
+ def add_soldiers(player_id, number_of_soldiers)
67
+ self.soldiers_per_player[player_id] += number_of_soldiers
68
+ end
69
+
70
+ def process(node)
71
+ add_soldiers(node.player_id, node.number_of_soldiers) if node.player_id
72
+
73
+ puts "[Conflict] Resolving conflicts for ##{node.node_id}"
74
+
75
+ losses = soldiers_per_player.values.sort.reverse[1] || 0
76
+
77
+ soldiers_per_player.each do |player_id, number_of_soldiers|
78
+ if number_of_soldiers < losses || number_of_soldiers == losses && node.player_id != player_id
79
+ puts "\t[#{player_id}] loses #{number_of_soldiers} soldiers"
80
+ else
81
+ node.number_of_soldiers = number_of_soldiers - losses
82
+ puts "\t[#{player_id}] wins the combat with #{number_of_soldiers - losses} soldiers left"
83
+ node.player_id = player_id
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ class Random
90
+ def self.on_turn(game)
91
+ game.map.controlled_nodes.each do |node|
92
+ soldiers = node.number_of_soldiers
93
+
94
+ node.adjacent_nodes.each do |adj|
95
+ num = rand(0...soldiers)
96
+ game.add_move(node.id, adj.id, num)
97
+ soldiers -= num
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ class State
104
+ def initialize(from_json)
105
+ @state = from_json.inject({}) do |h, node|
106
+ h[node['node_id']] = NodeState.new(node['node_id'], node['player_id'], node['number_of_soldiers'])
107
+ h
108
+ end
109
+ end
110
+
111
+ def apply_moves(moves)
112
+ conflicts = {}
113
+ errors = []
114
+ puts "[Moves]"
115
+ moves.each do |move|
116
+ conflict = (conflicts[move.to] ||= ConflictState.new(move.to))
117
+ remove_soldiers(move)
118
+ conflict.add_soldiers(move.player_id, move.number_of_soldiers)
119
+ end
120
+
121
+ conflicts.each { |node_id, conflict| conflict.process(@state[node_id]) }
122
+ end
123
+
124
+ def remove_soldiers(move)
125
+ origin = @state[move.from]
126
+ if origin.player_id != move.player_id
127
+ errors << "Trying to move #{move.number_of_soldiers} soldiers from ##{move.from}. Node ##{move.from} belongs to #{origin.player_id}"
128
+ elsif origin.number_of_soldiers < move.number_of_soldiers
129
+ errors << "Trying to move #{move.number_of_soldiers} soldiers from ##{move.from}. Only #{origin.number_of_soldiers} soldiers available"
130
+ else
131
+ origin.number_of_soldiers -= move.number_of_soldiers
132
+ puts "\t[#{move.player_id}] Moves #{move.number_of_soldiers} soldiers from ##{move.from} to ##{move.to}"
133
+ end
134
+ end
135
+
136
+ def spawn(node_ids)
137
+ node_ids.each { |id| @state[id].number_of_soldiers += 1 if @state[id].player_id }
138
+ end
139
+
140
+ def as_json
141
+ @state.map do |node_id, node_state|
142
+ {
143
+ 'node_id' => node_state.node_id,
144
+ 'player_id' => node_state.player_id,
145
+ 'number_of_soldiers' => node_state.number_of_soldiers
146
+ }
147
+ end
148
+ end
149
+
150
+ def inspect
151
+ as_json
152
+ end
153
+
154
+ def winner?
155
+ @state.map{ |node_id, node_state| node_state.player_id }.compact.length == 1
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ class Berlin::Fake::Game
162
+
163
+ def initialize(number_of_ai)
164
+ @turn = 0
165
+
166
+ @city_nodes = Berlin::Fake::MAP_DEFINITION['nodes'].select{ |node| node['type'] == 'city' }.map{ |node| node['id'] }
167
+ @ai_games = 1.upto(number_of_ai).map do |n|
168
+ ai_name = "AI ##{n}"
169
+ node = Berlin::Fake::GAME_STATE.detect{ |node| node['node_id'] == @city_nodes[n - 1] }
170
+ node['player_id'] = ai_name
171
+ node['number_of_soldiers'] = 5
172
+ ai_info = Berlin::Fake::GAME_INFO.dup
173
+ ai_info['player_id'] = ai_name
174
+ ai_info['game_id'] = n
175
+
176
+ Berlin::AI::Game.new(ai_info['game_id'], Berlin::Fake::MAP_DEFINITION, ai_info)
177
+ end
178
+
179
+ player_name = "Player"
180
+ player_info = Berlin::Fake::GAME_INFO.dup
181
+ player_info['player_id'] = player_name
182
+ player_info['game_id'] = 0
183
+ node = Berlin::Fake::GAME_STATE.detect{ |node| node['node_id'] == @city_nodes[number_of_ai] }
184
+ node['player_id'] = player_name
185
+ node['number_of_soldiers'] = 5
186
+
187
+ @player_game = Berlin::AI::Game.new(player_info['game_id'], Berlin::Fake::MAP_DEFINITION, player_info)
188
+
189
+ @state = Berlin::Fake::State.new(Berlin::Fake::GAME_STATE.dup)
190
+ end
191
+
192
+ def run
193
+ while !@state.winner? && @turn < Berlin::Fake::GAME_INFO['maximum_number_of_turns']
194
+ turn
195
+ puts "Press any key"
196
+ gets
197
+ end
198
+ end
199
+
200
+ def turn
201
+ @turn += 1
202
+ generate_moves
203
+ player_moves = buffer_moves
204
+
205
+ @state.apply_moves(player_moves.values.flatten)
206
+
207
+ spawn
208
+ end
209
+
210
+ def spawn
211
+ @state.spawn(@city_nodes)
212
+ end
213
+
214
+ def buffer_moves
215
+ moves = {}
216
+ [@player_game, *@ai_games].each do |game|
217
+ player_moves = {}
218
+
219
+ game.moves.each do |move|
220
+ move[:player_id] = game.player_id
221
+ ref = "#{move[:from]}_#{move[:to]}"
222
+ player_moves[ref] ||= Berlin::Fake::Move.new(game.player_id, move[:from], move[:to], 0)
223
+ player_moves[ref].number_of_soldiers += move[:number_of_soldiers]
224
+ end
225
+ moves[game.player_id] = player_moves.values
226
+ end
227
+ moves
228
+ end
229
+
230
+ def generate_moves
231
+ info = {'current_turn' => @turn}
232
+
233
+ @ai_games.each do |game|
234
+ game.clear_moves
235
+ game.update(info, @state.as_json)
236
+ Berlin::Fake::Random.on_turn(game)
237
+ end
238
+
239
+ @player_game.update(info, @state.as_json)
240
+ @player_game.clear_moves
241
+ Berlin::AI::Player.on_turn(@player_game)
242
+ end
243
+ end
data/lib/berlin-ai.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'pry'
1
2
  require 'optparse'
2
3
  require 'sinatra'
3
4
  require 'yajl/json_gem'
@@ -8,40 +9,43 @@ puts "| _ || -__|| _|| | || | | |_| |_ "
8
9
  puts "|_____||_____||__| |__|__||__|__| |___|___|_______|"
9
10
  puts
10
11
 
11
- %w(game map node).each do |file|
12
+ %w(game map node fake).each do |file|
12
13
  require File.expand_path( File.dirname( __FILE__ ) ) + "/ai/#{file}"
13
14
  end
14
15
 
15
- # Sinatra options
16
- set :app_file, $0
17
- set :verbose, false
18
- set :logger, false
19
-
20
16
  # Parse options
21
17
  OptionParser.new do |opts|
22
18
  opts.on("-h", "--help", "Display this screen" ) do
23
19
  puts opts
24
20
  exit
25
21
  end
26
-
22
+
27
23
  opts.on("-p N", "--port N", Integer, "Set running port to N") do |p|
28
24
  set :port, p
29
25
  end
30
-
26
+
31
27
  opts.on("-d", "--debug", "Run in debug mode (reloads code at each request)") do
32
28
  require 'sinatra/reloader'
33
-
29
+
34
30
  configure do |c|
35
31
  c.also_reload $0
36
32
  end
37
33
  end
38
-
34
+
39
35
  opts.on("-l", "--log [LOGFILE]", "Create a log file for incoming requests (defaults to 'berlin.log')") do |l|
40
36
  require 'logger'
41
-
37
+
42
38
  set :logger, Logger.new( l || 'berlin.log' )
43
39
  end
44
-
40
+
41
+ opts.on("-t N", "--test N", Integer, "Test against N random AI") do |t|
42
+ if t < 0 || t > 3
43
+ puts "This map supports a maximum of 3 AI"
44
+ exit 1
45
+ end
46
+ $test_ais = t
47
+ end
48
+
45
49
  opts.on("-v", "--verbose", "Print information to STDOUT") do
46
50
  enable :verbose
47
51
  end
@@ -52,22 +56,22 @@ post '/' do
52
56
  # Check if it's one of the four Berlin keywords
53
57
  if ['ping', 'turn', 'game_start', 'game_over'].include? params[:action]
54
58
  log :info, "New request of type #{params[:action]} : #{params.inspect}"
55
-
59
+
56
60
  game = Berlin::AI::Game.create_or_update params[:action], params[:infos], params[:map], params[:state]
57
61
 
58
62
  if ['ping', 'turn'].include? params[:action]
59
63
  # Clear old moves
60
64
  game.clear_moves
61
-
65
+
62
66
  # Let the player decides his moves
63
67
  Berlin::AI::Player.on_turn( game )
64
-
68
+
65
69
  # Get moves from AI
66
70
  moves = game.moves.to_json
67
-
71
+
68
72
  # Log time!
69
73
  log :info, "Respond with: #{moves}"
70
-
74
+
71
75
  # Return the response to Berlin
72
76
  return moves
73
77
  end
@@ -79,7 +83,7 @@ post '/' do
79
83
  200
80
84
  rescue Exception => e
81
85
  log :fatal, "#{e.inspect}\n#{e.backtrace}"
82
-
86
+
83
87
  # Internal server error
84
88
  500
85
89
  end
@@ -88,7 +92,15 @@ end
88
92
  def log level, message
89
93
  # verbose
90
94
  puts message if settings.verbose
91
-
95
+
92
96
  # logger
93
97
  settings.logger.send( level, message ) if settings.logger
94
98
  end
99
+
100
+ if $test_ais
101
+ at_exit {
102
+ Berlin::Fake::Game.new($test_ais).run
103
+ }
104
+ else
105
+ set :app_file, $0
106
+ end
data/lib/version.rb CHANGED
@@ -2,7 +2,7 @@ module Berlin
2
2
  module AI
3
3
  MAJOR = 0
4
4
  MINOR = 0
5
- BUILD = 20
5
+ BUILD = 21
6
6
 
7
7
  VERSION = "#{MAJOR}.#{MINOR}.#{BUILD}"
8
8
  end
data/test/test_ai.rb CHANGED
@@ -1,8 +1,9 @@
1
1
  require 'rubygems'
2
2
  require 'berlin-ai'
3
3
 
4
- class Berlin::AI::Player
5
- def self.on_turn( game )
6
- game.add_move( 1, 2, 3 )
4
+ class Berlin::AI::Player
5
+ def self.on_turn( game )
6
+ game.add_move( 1, 2, 12 )
7
+ game.add_move( 3, 4, 10 )
7
8
  end
8
9
  end
metadata CHANGED
@@ -1,84 +1,79 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: berlin-ai
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 0
8
- - 20
9
- version: 0.0.20
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.21
10
6
  platform: ruby
11
- authors:
7
+ authors:
12
8
  - Christian Blais
13
9
  - Guillaume Malette
14
10
  - Jodi Giordano
15
11
  autorequire:
16
12
  bindir: bin
17
13
  cert_chain: []
18
-
19
- date: 2011-07-06 00:00:00 -04:00
20
- default_executable:
21
- dependencies:
22
- - !ruby/object:Gem::Dependency
23
- name: sinatra
24
- prerelease: false
25
- requirement: &id001 !ruby/object:Gem::Requirement
26
- none: false
27
- requirements:
28
- - - ">="
29
- - !ruby/object:Gem::Version
30
- segments:
31
- - 1
32
- - 2
33
- - 6
14
+ date: 2013-07-20 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ version_requirements: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
34
21
  version: 1.2.6
22
+ none: false
23
+ name: sinatra
35
24
  type: :runtime
36
- version_requirements: *id001
37
- - !ruby/object:Gem::Dependency
38
- name: yajl-ruby
39
25
  prerelease: false
40
- requirement: &id002 !ruby/object:Gem::Requirement
26
+ requirement: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: 1.2.6
41
31
  none: false
42
- requirements:
43
- - - ">="
44
- - !ruby/object:Gem::Version
45
- segments:
46
- - 0
47
- - 8
48
- - 2
32
+ - !ruby/object:Gem::Dependency
33
+ version_requirements: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
49
37
  version: 0.8.2
38
+ none: false
39
+ name: yajl-ruby
50
40
  type: :runtime
51
- version_requirements: *id002
52
- - !ruby/object:Gem::Dependency
53
- name: sinatra-reloader
54
41
  prerelease: false
55
- requirement: &id003 !ruby/object:Gem::Requirement
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: 0.8.2
56
47
  none: false
57
- requirements:
58
- - - ">="
59
- - !ruby/object:Gem::Version
60
- segments:
61
- - 0
62
- - 5
63
- - 0
48
+ - !ruby/object:Gem::Dependency
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
64
53
  version: 0.5.0
54
+ none: false
55
+ name: sinatra-reloader
65
56
  type: :runtime
66
- version_requirements: *id003
57
+ prerelease: false
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: 0.5.0
63
+ none: false
67
64
  description: Berlin Artificial Intelligence
68
- email:
65
+ email:
69
66
  - christ.blais@gmail.com
70
67
  - gmalette@gmail.com
71
68
  - giordano.jodi@gmail.com
72
69
  executables: []
73
-
74
70
  extensions: []
75
-
76
71
  extra_rdoc_files: []
77
-
78
- files:
72
+ files:
79
73
  - LICENSE
80
74
  - README
81
75
  - berlin-ai.gemspec
76
+ - lib/ai/fake.rb
82
77
  - lib/ai/game.rb
83
78
  - lib/ai/map.rb
84
79
  - lib/ai/node.rb
@@ -86,38 +81,29 @@ files:
86
81
  - lib/berlin-ai.rb
87
82
  - lib/version.rb
88
83
  - test/test_ai.rb
89
- has_rdoc: true
90
84
  homepage: http://github.com/christianblais/berlin-ai
91
85
  licenses: []
92
-
93
86
  post_install_message:
94
87
  rdoc_options: []
95
-
96
- require_paths:
88
+ require_paths:
97
89
  - lib
98
90
  - test
99
- required_ruby_version: !ruby/object:Gem::Requirement
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
100
96
  none: false
101
- requirements:
102
- - - ">="
103
- - !ruby/object:Gem::Version
104
- segments:
105
- - 0
106
- version: "0"
107
- required_rubygems_version: !ruby/object:Gem::Requirement
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
108
102
  none: false
109
- requirements:
110
- - - ">="
111
- - !ruby/object:Gem::Version
112
- segments:
113
- - 0
114
- version: "0"
115
103
  requirements: []
116
-
117
104
  rubyforge_project:
118
- rubygems_version: 1.3.7
105
+ rubygems_version: 1.8.23
119
106
  signing_key:
120
107
  specification_version: 3
121
108
  summary: Berlin Artificial Intelligence
122
109
  test_files: []
123
-