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 +243 -0
- data/lib/berlin-ai.rb +31 -19
- data/lib/version.rb +1 -1
- data/test/test_ai.rb +4 -3
- metadata +60 -74
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
data/test/test_ai.rb
CHANGED
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:
|
5
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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:
|
26
|
+
requirement: !ruby/object:Gem::Requirement
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 1.2.6
|
41
31
|
none: false
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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:
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.8.2
|
56
47
|
none: false
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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.
|
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
|
-
|