berlin-ai 0.0.20 → 0.0.21

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/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
-