berlin-ai 0.0.29 → 0.0.32
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/Gemfile +3 -0
- data/Gemfile.lock +56 -0
- data/README.md +117 -0
- data/berlin-ai.gemspec +3 -0
- data/lib/ai/fake.rb +19 -6
- data/lib/ai/game.rb +5 -61
- data/lib/ai/game_internal.rb +65 -0
- data/lib/ai/map.rb +3 -41
- data/lib/ai/map_internal.rb +67 -0
- data/lib/ai/node.rb +3 -32
- data/lib/ai/node_internal.rb +59 -0
- data/lib/berlin-ai.rb +1 -1
- data/lib/version.rb +1 -1
- data/test/map_test.rb +35 -0
- data/test/node_test.rb +77 -0
- metadata +42 -3
- data/README +0 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
berlin-ai (0.0.29)
|
5
|
+
rainbow (~> 1.1.4)
|
6
|
+
sinatra (= 1.4.3)
|
7
|
+
sinatra-contrib (= 1.4.1)
|
8
|
+
thin (= 1.5.1)
|
9
|
+
yajl-ruby (= 1.1.0)
|
10
|
+
|
11
|
+
GEM
|
12
|
+
remote: http://www.rubygems.org/
|
13
|
+
specs:
|
14
|
+
backports (3.3.3)
|
15
|
+
coderay (1.0.9)
|
16
|
+
daemons (1.1.9)
|
17
|
+
eventmachine (1.0.3)
|
18
|
+
method_source (0.8.2)
|
19
|
+
minitest (4.7.5)
|
20
|
+
multi_json (1.7.9)
|
21
|
+
pry (0.9.12.2)
|
22
|
+
coderay (~> 1.0.5)
|
23
|
+
method_source (~> 0.8)
|
24
|
+
slop (~> 3.4)
|
25
|
+
rack (1.5.2)
|
26
|
+
rack-protection (1.5.0)
|
27
|
+
rack
|
28
|
+
rack-test (0.6.2)
|
29
|
+
rack (>= 1.0)
|
30
|
+
rainbow (1.1.4)
|
31
|
+
sinatra (1.4.3)
|
32
|
+
rack (~> 1.4)
|
33
|
+
rack-protection (~> 1.4)
|
34
|
+
tilt (~> 1.3, >= 1.3.4)
|
35
|
+
sinatra-contrib (1.4.1)
|
36
|
+
backports (>= 2.0)
|
37
|
+
multi_json
|
38
|
+
rack-protection
|
39
|
+
rack-test
|
40
|
+
sinatra (~> 1.4.0)
|
41
|
+
tilt (~> 1.3)
|
42
|
+
slop (3.4.6)
|
43
|
+
thin (1.5.1)
|
44
|
+
daemons (>= 1.0.9)
|
45
|
+
eventmachine (>= 0.12.6)
|
46
|
+
rack (>= 1.0.0)
|
47
|
+
tilt (1.4.1)
|
48
|
+
yajl-ruby (1.1.0)
|
49
|
+
|
50
|
+
PLATFORMS
|
51
|
+
ruby
|
52
|
+
|
53
|
+
DEPENDENCIES
|
54
|
+
berlin-ai!
|
55
|
+
minitest
|
56
|
+
pry
|
data/README.md
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# Berlin-Ai
|
2
|
+
|
3
|
+
Berlin-Ai is a gem to quickly ramp up on the [Berlin](http://www.berlin-ai.com) game.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
Create your AI in a simple `.rb` file:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require 'berlin-ai' # Require the berlin-ai library.
|
11
|
+
|
12
|
+
class Berlin::AI::Player
|
13
|
+
def on_turn(game) # Implement the on_turn method of Berlin::AI::Player.
|
14
|
+
# Do your magic here.
|
15
|
+
|
16
|
+
# Here's an AI that randomly moves soldiers from node to node.
|
17
|
+
game.map.controlled_nodes.each do |node|
|
18
|
+
node.adjacent_nodes.shuffle.each do |other_node|
|
19
|
+
rand(0..(node.available_soldiers))
|
20
|
+
game.add_move(node.id, other_node.id, soldiers)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
You're now ready to host your AI locally or with [Heroku](https://devcenter.heroku.com/articles/rack). We use [Sinatra](http://www.sinatrarb.com) in this gem, so a simple `ruby your_ai_file.rb` will launch the web server.
|
28
|
+
|
29
|
+
To modify some defaults, you can execute `ruby your_ai_file.rb -h` for a list of available options.
|
30
|
+
|
31
|
+
## API
|
32
|
+
|
33
|
+
### game (Berlin::AI::Game)
|
34
|
+
|
35
|
+
Entry point in the API from `Berlin::AI::Player#on_turn`.
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
# General information on the game.
|
39
|
+
game.id # This game unique id.
|
40
|
+
game.number_of_players # Number of players in this game.
|
41
|
+
game.maximum_number_of_turns # Maximum number of turns for this game.
|
42
|
+
game.player_id # Your player id for this game.
|
43
|
+
game.time_limit_per_turn # Maximum number of time (ms) to make your moves, per turn.
|
44
|
+
|
45
|
+
# Information on the current turn.
|
46
|
+
game.current_turn # The current turn count.
|
47
|
+
game.turns_left # Remaining turns before the end of the game.
|
48
|
+
|
49
|
+
# Add a move to perform this turn by your AI.
|
50
|
+
# IMPORTANT: 'from' and 'to' must be node objects.
|
51
|
+
game.add_move(from, to, number_of_soldiers)
|
52
|
+
|
53
|
+
# List of moves to perform this turn by your AI.
|
54
|
+
game.moves # => [{ from: node_id, to: node_id, number_of_soldiers: int }]
|
55
|
+
|
56
|
+
# The game's map with its own set of useful methods.
|
57
|
+
game.map
|
58
|
+
```
|
59
|
+
|
60
|
+
### map (Berlin::AI::Map)
|
61
|
+
|
62
|
+
Helper methods to work with nodes.
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
map.directed? # Determine if a path from A to B is A -> B (directed) or A <-> B (not directed)
|
66
|
+
map.nodes # All the nodes.
|
67
|
+
map.owned_nodes # Owned nodes, including those with 0 soldiers.
|
68
|
+
map.enemy_nodes # Enemy nodes.
|
69
|
+
map.free_nodes # Nodes not controlled by anyone.
|
70
|
+
map.foreign_nodes # Nodes not owned (i.e. enemy nodes and free nodes).
|
71
|
+
map.controlled_nodes # Owned nodes, excluding those with 0 soldiers.
|
72
|
+
```
|
73
|
+
|
74
|
+
### node (Berlin::AI::Node)
|
75
|
+
|
76
|
+
Node objects obtained when querying the map.
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
node.id # Id of the node.
|
80
|
+
node.type # Type of node.
|
81
|
+
node.player_id # Owner of the node.
|
82
|
+
node.number_of_soldiers # Number of soldiers on the node.
|
83
|
+
node.incoming_soldiers # Owned soldiers coming to this node (result from add_move calls).
|
84
|
+
node.available_soldiers # Owned remaining soldiers on this node (result from add_move calls).
|
85
|
+
node.==(other) # Check if two nodes are the same.
|
86
|
+
node.adjacent?(other_node) # Check if two nodes are adjacents.
|
87
|
+
node.occupied? # Check if some soldiers are on the node.
|
88
|
+
node.owned? # Check if you own the node.
|
89
|
+
node.free? # Check if no one own the node.
|
90
|
+
node.owned_by?(player_id) # Check if the node is owned by a given player.
|
91
|
+
node.adjacent_nodes # Get a list of adjacent nodes.
|
92
|
+
node.adjacent_nodes_and_self # Get a list of adjacent nodes, including this node.
|
93
|
+
node.soldiers_per_turn # Spawned soldiers per turn if you own this node.
|
94
|
+
node.points # Given points by this node at the end of the game.
|
95
|
+
```
|
96
|
+
|
97
|
+
## Installation
|
98
|
+
|
99
|
+
Add this line to your application's Gemfile:
|
100
|
+
|
101
|
+
gem 'berlin-ai'
|
102
|
+
|
103
|
+
And then execute:
|
104
|
+
|
105
|
+
$ bundle
|
106
|
+
|
107
|
+
Or install it yourself as:
|
108
|
+
|
109
|
+
$ gem install berlin-ai
|
110
|
+
|
111
|
+
## Contributing
|
112
|
+
|
113
|
+
1. Fork it
|
114
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
115
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
116
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
117
|
+
5. Create new Pull Request
|
data/berlin-ai.gemspec
CHANGED
@@ -20,6 +20,9 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.add_dependency 'rainbow', '~>1.1.4'
|
21
21
|
s.add_dependency 'thin', '1.5.1'
|
22
22
|
|
23
|
+
s.add_development_dependency 'minitest'
|
24
|
+
s.add_development_dependency 'pry'
|
25
|
+
|
23
26
|
s.files = `git ls-files`.split("\n")
|
24
27
|
|
25
28
|
s.require_paths = ['lib', 'test']
|
data/lib/ai/fake.rb
CHANGED
@@ -4,6 +4,8 @@ module Berlin
|
|
4
4
|
module Fake
|
5
5
|
|
6
6
|
MAP_DEFINITION = {
|
7
|
+
"directed" => false,
|
8
|
+
|
7
9
|
"types" => [
|
8
10
|
{"name" => "node", "points" => 0, "soldiers_per_turn" => 0},
|
9
11
|
{"name" => "city", "points" => 1, "soldiers_per_turn" => 1}
|
@@ -228,7 +230,14 @@ class Berlin::Fake::Game
|
|
228
230
|
ai_info['player_id'] = ai_name
|
229
231
|
ai_info['game_id'] = n
|
230
232
|
|
231
|
-
Berlin::AI::Game.new
|
233
|
+
ai_game = Berlin::AI::Game.new
|
234
|
+
ai_game.id = ai_info['game_id']
|
235
|
+
ai_game.map = Berlin::AI::Map.parse(Berlin::Fake::MAP_DEFINITION.merge('player_id' => ai_info['player_id']))
|
236
|
+
ai_game.player_id = ai_info['player_id']
|
237
|
+
ai_game.time_limit_per_turn = ai_info['time_limit_per_turn']
|
238
|
+
ai_game.maximum_number_of_turns = ai_info['maximum_number_of_turns']
|
239
|
+
ai_game.number_of_players = ai_info['number_of_players']
|
240
|
+
ai_game
|
232
241
|
end
|
233
242
|
|
234
243
|
player_name = "Player"
|
@@ -239,7 +248,13 @@ class Berlin::Fake::Game
|
|
239
248
|
node['player_id'] = player_name
|
240
249
|
node['number_of_soldiers'] = 5
|
241
250
|
|
242
|
-
@player_game = Berlin::AI::Game.new
|
251
|
+
@player_game = Berlin::AI::Game.new
|
252
|
+
@player_game.id = player_info['game_id']
|
253
|
+
@player_game.map = Berlin::AI::Map.parse(Berlin::Fake::MAP_DEFINITION.merge('player_id' => player_info['player_id']))
|
254
|
+
@player_game.player_id = player_info['player_id']
|
255
|
+
@player_game.time_limit_per_turn = player_info['time_limit_per_turn']
|
256
|
+
@player_game.maximum_number_of_turns = player_info['maximum_number_of_turns']
|
257
|
+
@player_game.number_of_players = player_info['number_of_players']
|
243
258
|
|
244
259
|
@state = Berlin::Fake::State.new(Berlin::Fake::GAME_STATE.dup)
|
245
260
|
end
|
@@ -291,15 +306,13 @@ class Berlin::Fake::Game
|
|
291
306
|
end
|
292
307
|
|
293
308
|
def generate_moves
|
294
|
-
info = {'current_turn' => @turn}
|
295
|
-
|
296
309
|
@ai_games.each do |game|
|
297
310
|
game.reset!
|
298
|
-
game.update(
|
311
|
+
game.update(@turn, @state.as_json)
|
299
312
|
Berlin::Fake::Random.on_turn(game)
|
300
313
|
end
|
301
314
|
|
302
|
-
@player_game.update(
|
315
|
+
@player_game.update(@turn, @state.as_json)
|
303
316
|
@player_game.reset!
|
304
317
|
Berlin::AI::Player.on_turn(@player_game)
|
305
318
|
end
|
data/lib/ai/game.rb
CHANGED
@@ -2,56 +2,13 @@ module Berlin
|
|
2
2
|
module AI
|
3
3
|
# Game keeps track of current games played by the server, indexing them on their uniq id.
|
4
4
|
class Game
|
5
|
-
|
6
|
-
|
7
|
-
# Keep track of all current games
|
8
|
-
@@games = {}
|
9
|
-
|
10
|
-
def self.create_or_update action, infos, map, state
|
11
|
-
# Check for params and quit on errors
|
12
|
-
return if action.nil? || infos.nil? || map.nil? || state.nil?
|
13
|
-
|
14
|
-
# First, we parse the received request
|
15
|
-
infos = JSON.parse( infos )
|
16
|
-
map = JSON.parse( map )
|
17
|
-
state = JSON.parse( state )
|
18
|
-
|
19
|
-
# Game id, set with player_id as well so an AI can fight with himself
|
20
|
-
game_id = "#{infos['game_id']}-#{infos['player_id']}"
|
21
|
-
|
22
|
-
# Then, let's see if we can find that game. If not, register it.
|
23
|
-
if action == "ping"
|
24
|
-
game = Berlin::AI::Game.new game_id, map, infos
|
25
|
-
else
|
26
|
-
game = (@@games[game_id] ||= Berlin::AI::Game.new( game_id, map, infos ))
|
27
|
-
end
|
28
|
-
|
29
|
-
if action == "game_over"
|
30
|
-
# Release the game to avoid memory leaks
|
31
|
-
@@games.delete game_id
|
32
|
-
elsif infos && state
|
33
|
-
# Now, we want to update the current state of the game with the new content, as well as other infos
|
34
|
-
game.update infos, state
|
35
|
-
end
|
5
|
+
include Internal
|
36
6
|
|
37
|
-
|
38
|
-
|
7
|
+
attr_accessor :id, :map, :moves, :player_id, :current_turn, :number_of_players,
|
8
|
+
:time_limit_per_turn, :maximum_number_of_turns, :turns_left
|
39
9
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
# Extract usefull static informations
|
44
|
-
@player_id = infos['player_id']
|
45
|
-
@time_limit_per_turn = infos['time_limit_per_turn']
|
46
|
-
@maximum_number_of_turns = infos['maximum_number_of_turns'].to_i
|
47
|
-
@number_of_players = infos['number_of_players']
|
48
|
-
|
49
|
-
# Create the map
|
50
|
-
@map = Berlin::AI::Map.new map, infos
|
51
|
-
end
|
52
|
-
|
53
|
-
def add_move from, to, number_of_soldiers
|
54
|
-
# remove moving soldiers from from node
|
10
|
+
def add_move(from, to, number_of_soldiers)
|
11
|
+
# remove moving soldiers from from node
|
55
12
|
from.available_soldiers -= number_of_soldiers
|
56
13
|
|
57
14
|
# adding incoming soldiers to next node
|
@@ -61,19 +18,6 @@ module Berlin
|
|
61
18
|
@moves << {:from => from.to_i, :to => to.to_i, :number_of_soldiers => number_of_soldiers.to_i}
|
62
19
|
end
|
63
20
|
|
64
|
-
def update infos, state
|
65
|
-
# Update turn infos
|
66
|
-
@current_turn = infos['current_turn'].to_i
|
67
|
-
@turns_left = @maximum_number_of_turns - @current_turn
|
68
|
-
|
69
|
-
# Update map state
|
70
|
-
@map.update state
|
71
|
-
end
|
72
|
-
|
73
|
-
def reset!
|
74
|
-
@moves = []
|
75
|
-
@map.nodes.each(&:reset!)
|
76
|
-
end
|
77
21
|
end
|
78
22
|
end
|
79
23
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Berlin
|
2
|
+
module AI
|
3
|
+
class Game
|
4
|
+
module Internal
|
5
|
+
module ClassMethods
|
6
|
+
# Keep track of all current games
|
7
|
+
@@games = {}
|
8
|
+
|
9
|
+
def create_or_update(action, infos, map, state)
|
10
|
+
# Check for params and quit on errors
|
11
|
+
return if action.nil? || infos.nil? || map.nil? || state.nil?
|
12
|
+
|
13
|
+
# First, we parse the received request
|
14
|
+
infos = JSON.parse( infos )
|
15
|
+
map = JSON.parse( map )
|
16
|
+
state = JSON.parse( state )
|
17
|
+
|
18
|
+
# Game id, set with player_id as well so an AI can fight with himself
|
19
|
+
game_id = "#{infos['game_id']}-#{infos['player_id']}"
|
20
|
+
|
21
|
+
# Then, let's see if we can find that game. If not, register it.
|
22
|
+
game = @@games[game_id] ||= begin
|
23
|
+
game = Berlin::AI::Game.new
|
24
|
+
game.id = game_id
|
25
|
+
game.map = Berlin::AI::Map.parse(map.merge(infos.select{ |k,v| ['directed', 'player_id'].include?(k) }))
|
26
|
+
game.player_id = infos['player_id']
|
27
|
+
game.time_limit_per_turn = infos['time_limit_per_turn']
|
28
|
+
game.maximum_number_of_turns = infos['maximum_number_of_turns']
|
29
|
+
game.number_of_players = infos['number_of_players']
|
30
|
+
game
|
31
|
+
end
|
32
|
+
|
33
|
+
if action == "game_over"
|
34
|
+
# Release the game to avoid memory leaks
|
35
|
+
@@games.delete(game_id)
|
36
|
+
elsif infos && state
|
37
|
+
# Now, we want to update the current state of the game with the new content, as well as other infos
|
38
|
+
game.update(infos['current_turn'], state)
|
39
|
+
end
|
40
|
+
|
41
|
+
game
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.included(base)
|
46
|
+
base.extend(ClassMethods)
|
47
|
+
end
|
48
|
+
|
49
|
+
def update(current_turn, state)
|
50
|
+
# Update turn infos
|
51
|
+
@current_turn = current_turn.to_i
|
52
|
+
@turns_left = @maximum_number_of_turns - @current_turn
|
53
|
+
|
54
|
+
# Update map state
|
55
|
+
@map.update(state)
|
56
|
+
end
|
57
|
+
|
58
|
+
def reset!
|
59
|
+
@moves = []
|
60
|
+
@map.nodes.each(&:reset!)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/ai/map.rb
CHANGED
@@ -4,39 +4,13 @@ module Berlin
|
|
4
4
|
# nodes, points, soldiers, etc. Game will then be able to pick any information
|
5
5
|
# it wants from map to decide what are the best moves to do.
|
6
6
|
class Map
|
7
|
-
|
8
|
-
@player_id = infos['player_id']
|
9
|
-
@nodes = {}
|
10
|
-
@types = {}
|
11
|
-
@directed = infos['directed'] || false
|
12
|
-
|
13
|
-
# Node types
|
14
|
-
map['types'].each do |type|
|
15
|
-
@types[type['name']] = type
|
16
|
-
end
|
17
|
-
|
18
|
-
# Let's parse map['nodes'] and register all nodes we can find.
|
19
|
-
# We'll keep track of them in @nodes so we can find them later.
|
20
|
-
# At this step (Map creation...), we still don't know who possess
|
21
|
-
# the node and how many soldiers there is. We'll get back to that later.
|
22
|
-
# map['nodes'] => [{:id => STRING}, ...]
|
23
|
-
map['nodes'].each do |node|
|
24
|
-
@nodes[node['id']] = Berlin::AI::Node.new node, @types[node['type']]
|
25
|
-
end
|
7
|
+
include Internal
|
26
8
|
|
27
|
-
|
28
|
-
# map['paths'] => [{:from => INTEGER, :to => INTEGER}, ...]
|
29
|
-
map['paths'].each do |path|
|
30
|
-
@nodes[path['from']].link_to @nodes[path['to']]
|
31
|
-
|
32
|
-
# Don't forget! If the map is not directed, we must create the reverse link!
|
33
|
-
@nodes[path['to']].link_to @nodes[path['from']] unless directed?
|
34
|
-
end
|
35
|
-
end
|
9
|
+
attr_accessor :player_id, :nodes_hash, :directed
|
36
10
|
|
37
11
|
# Returns an array of all nodes of the map
|
38
12
|
def nodes
|
39
|
-
@
|
13
|
+
@nodes_hash.values
|
40
14
|
end
|
41
15
|
|
42
16
|
# Returns an array of all owned nodes
|
@@ -78,18 +52,6 @@ module Berlin
|
|
78
52
|
def directed?
|
79
53
|
@directed
|
80
54
|
end
|
81
|
-
|
82
|
-
# Let's update the current state with the latest provided info! With this step,
|
83
|
-
# we'll now know who possess the node and how many soldiers there is.
|
84
|
-
# state contains an array of nodes, so we just have to loop on it.
|
85
|
-
# state => [{:node_id => STRING, :number_of_soldiers => INTEGER, :player_id => INTEGER}, ...]
|
86
|
-
def update state
|
87
|
-
state.each do |n|
|
88
|
-
node = @nodes[n['node_id']]
|
89
|
-
node.number_of_soldiers = n['number_of_soldiers']
|
90
|
-
node.player_id = n['player_id']
|
91
|
-
end
|
92
|
-
end
|
93
55
|
end
|
94
56
|
end
|
95
57
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Berlin
|
2
|
+
module AI
|
3
|
+
class Map
|
4
|
+
module Internal
|
5
|
+
module ClassMethods
|
6
|
+
def parse(data)
|
7
|
+
map = Map.new
|
8
|
+
|
9
|
+
# Map details
|
10
|
+
map.directed = data['directed']
|
11
|
+
map.player_id = data['player_id']
|
12
|
+
|
13
|
+
# Node types
|
14
|
+
types = data['types'].each.with_object({}) do |type, types|
|
15
|
+
types[type['name']] = type
|
16
|
+
end
|
17
|
+
|
18
|
+
# Let's parse data['nodes'] and register all nodes we can find.
|
19
|
+
# We'll keep track of them in @nodes so we can find them later.
|
20
|
+
# At this step (Map creation...), we still don't know who possess
|
21
|
+
# the node and how many soldiers there is. We'll get back to that later.
|
22
|
+
# map['nodes'] => [{:id => STRING}, ...]
|
23
|
+
data['nodes'].each do |node|
|
24
|
+
map.nodes_hash[node['id']] = Berlin::AI::Node.parse(node.merge(types[node['type']]))
|
25
|
+
end
|
26
|
+
|
27
|
+
# Same thing here, with paths.
|
28
|
+
# map['paths'] => [{:from => INTEGER, :to => INTEGER}, ...]
|
29
|
+
data['paths'].each do |path|
|
30
|
+
map.nodes_hash[path['from']].link_to(map.nodes_hash[path['to']])
|
31
|
+
|
32
|
+
# Don't forget! If the map is not directed, we must create the reverse link!
|
33
|
+
map.nodes_hash[path['to']].link_to(map.nodes_hash[path['from']]) unless map.directed?
|
34
|
+
end
|
35
|
+
|
36
|
+
# and... return the newly created map
|
37
|
+
map
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.included(base)
|
42
|
+
base.extend(ClassMethods)
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(options={})
|
46
|
+
@nodes_hash = {}
|
47
|
+
|
48
|
+
options.each do |k,v|
|
49
|
+
self.send("#{k}=", v)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Let's update the current state with the latest provided info! With this step,
|
54
|
+
# we'll now know who possess the node and how many soldiers there is.
|
55
|
+
# state contains an array of nodes, so we just have to loop on it.
|
56
|
+
# state => [{:node_id => STRING, :number_of_soldiers => INTEGER, :player_id => INTEGER}, ...]
|
57
|
+
def update(state)
|
58
|
+
state.each do |n|
|
59
|
+
node = @nodes_hash[n['node_id']]
|
60
|
+
node.number_of_soldiers = n['number_of_soldiers']
|
61
|
+
node.player_id = n['player_id']
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/ai/node.rb
CHANGED
@@ -4,39 +4,10 @@ module Berlin
|
|
4
4
|
# We'll be able to use it in order to know if two
|
5
5
|
# nodes are adjacent, how much points worth a node, etc.
|
6
6
|
class Node
|
7
|
-
|
8
|
-
attr_reader :soldiers_per_turn, :points
|
7
|
+
include Internal
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
@type = node['type']
|
13
|
-
@points = type['points']
|
14
|
-
@soldiers_per_turn = type['soldiers_per_turn']
|
15
|
-
@number_of_soldiers = 0
|
16
|
-
@player_id = nil
|
17
|
-
@links = []
|
18
|
-
end
|
19
|
-
|
20
|
-
# Reset information for new turn
|
21
|
-
def reset!
|
22
|
-
self.incoming_soldiers = 0
|
23
|
-
self.available_soldiers = self.number_of_soldiers
|
24
|
-
end
|
25
|
-
|
26
|
-
# Somewhat useful
|
27
|
-
def to_i
|
28
|
-
@id.to_i
|
29
|
-
end
|
30
|
-
|
31
|
-
# Used to compare if two nodes are the same
|
32
|
-
def ==(other)
|
33
|
-
other.id == @id
|
34
|
-
end
|
35
|
-
|
36
|
-
# Registers a given node as an adjacent one.
|
37
|
-
def link_to(other_node)
|
38
|
-
@links << other_node
|
39
|
-
end
|
9
|
+
attr_accessor :id, :player_id, :number_of_soldiers, :incoming_soldiers,
|
10
|
+
:available_soldiers, :type, :soldiers_per_turn, :points
|
40
11
|
|
41
12
|
# Returns true if other_node is adjacent to self
|
42
13
|
def adjacent?(other_node)
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Berlin
|
2
|
+
module AI
|
3
|
+
class Node
|
4
|
+
module Internal
|
5
|
+
module ClassMethods
|
6
|
+
def parse(data)
|
7
|
+
node = Node.new
|
8
|
+
|
9
|
+
node.id = data['id']
|
10
|
+
node.type = data['type']
|
11
|
+
node.points = data['points']
|
12
|
+
node.soldiers_per_turn = data['soldiers_per_turn']
|
13
|
+
|
14
|
+
node
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.included(base)
|
19
|
+
base.extend(ClassMethods)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(options={})
|
23
|
+
@number_of_soldiers = 0
|
24
|
+
@player_id = nil
|
25
|
+
@links = []
|
26
|
+
|
27
|
+
options.each do |k,v|
|
28
|
+
self.send("#{k}=", v)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
"<Berlin::AI::Node @id=#{@id} @player_id='#{@player_id}' @type='#{@type}' @points=#{@points} @soldiers_per_turn=#{@soldiers_per_turn} @number_of_soldiers=#{@number_of_soldiers} @incoming_soldiers=#{@incoming_soldiers} @available_soldiers=#{@available_soldiers} @adjacent_nodes=#{adjacent_nodes.map(&:id)}>"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Reset information for new turn
|
37
|
+
def reset!
|
38
|
+
self.incoming_soldiers = 0
|
39
|
+
self.available_soldiers = self.number_of_soldiers
|
40
|
+
end
|
41
|
+
|
42
|
+
# Somewhat useful
|
43
|
+
def to_i
|
44
|
+
@id.to_i
|
45
|
+
end
|
46
|
+
|
47
|
+
# Used to compare if two nodes are the same
|
48
|
+
def ==(other)
|
49
|
+
other.id == @id
|
50
|
+
end
|
51
|
+
|
52
|
+
# Registers a given node as an adjacent one.
|
53
|
+
def link_to(other_node)
|
54
|
+
@links << other_node
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/berlin-ai.rb
CHANGED
@@ -9,7 +9,7 @@ puts "| _ || -__|| _|| | || | | |_| |_ "
|
|
9
9
|
puts "|_____||_____||__| |__|__||__|__| |___|___|_______|"
|
10
10
|
puts
|
11
11
|
|
12
|
-
%w(game map node fake).each do |file|
|
12
|
+
%w(game_internal game map_internal map node_internal node fake).each do |file|
|
13
13
|
require File.expand_path(File.dirname( __FILE__ )) + "/ai/#{file}"
|
14
14
|
end
|
15
15
|
|
data/lib/version.rb
CHANGED
data/test/map_test.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
require_relative '../lib/ai/node_internal'
|
3
|
+
require_relative '../lib/ai/node'
|
4
|
+
require_relative '../lib/ai/map_internal'
|
5
|
+
require_relative '../lib/ai/map'
|
6
|
+
|
7
|
+
require "test/unit"
|
8
|
+
|
9
|
+
class MapTest < Test::Unit::TestCase
|
10
|
+
|
11
|
+
def setup
|
12
|
+
@node1 = Berlin::AI::Node.new(:id => 1, :player_id => 1)
|
13
|
+
@node2 = Berlin::AI::Node.new(:id => 2, :player_id => 1)
|
14
|
+
@node3 = Berlin::AI::Node.new(:id => 3, :player_id => nil)
|
15
|
+
@node4 = Berlin::AI::Node.new(:id => 4, :player_id => 2)
|
16
|
+
@node5 = Berlin::AI::Node.new(:id => 5, :player_id => 3)
|
17
|
+
|
18
|
+
@map = Berlin::AI::Map.new
|
19
|
+
@map.player_id = 1
|
20
|
+
@map.nodes = {1 => @node1, 2 => @node2, 3 => @node3, 4 => @node4, 5 => @node5}
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_nodes_returns_an_array_of_all_nodes
|
24
|
+
assert_equal [@node1, @node2, @node3, @node4, @node5], @map.nodes
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_owned_nodes_returns_an_array_of_owned_nodes_for_current_player
|
28
|
+
assert_equal [@node1, @node2], @map.owned_nodes
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_foreign_nodes_returns_and_array_of_nodes_that_the_current_player_does_not_owned
|
32
|
+
assert_equal [@node3, @node4, @node5], @map.foreign_nodes
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
data/test/node_test.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require_relative '../lib/ai/node_internal'
|
2
|
+
require_relative '../lib/ai/node'
|
3
|
+
|
4
|
+
require "test/unit"
|
5
|
+
|
6
|
+
class NodeTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def test_equals_returns_true_if_two_nodes_have_the_same_id
|
9
|
+
node1 = Berlin::AI::Node.new(:id => 1)
|
10
|
+
node2 = Berlin::AI::Node.new(:id => 2)
|
11
|
+
|
12
|
+
assert node1 != node2
|
13
|
+
|
14
|
+
node2.id = 1
|
15
|
+
|
16
|
+
assert node1 == node2
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_reset_resets_all_turn_relative_data
|
20
|
+
node = Berlin::AI::Node.new(:number_of_soldiers => 2, :incoming_soldiers => 3, :available_soldiers => 4)
|
21
|
+
node.reset!
|
22
|
+
assert_equal 0, node.incoming_soldiers
|
23
|
+
assert_equal node.number_of_soldiers, node.available_soldiers
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_adjacent_returns_weither_or_not_a_node_is_adjacent
|
27
|
+
node1 = Berlin::AI::Node.new(:id => 1)
|
28
|
+
node2 = Berlin::AI::Node.new(:id => 2)
|
29
|
+
node3 = Berlin::AI::Node.new(:id => 3)
|
30
|
+
|
31
|
+
node1.link_to(node2)
|
32
|
+
|
33
|
+
assert node1.adjacent?(node2)
|
34
|
+
assert !node1.adjacent?(node3)
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_occupied_returns_true_if_the_node_has_at_least_one_soldiers
|
38
|
+
node = Berlin::AI::Node.new
|
39
|
+
|
40
|
+
assert !node.occupied?
|
41
|
+
|
42
|
+
node.number_of_soldiers = 10
|
43
|
+
|
44
|
+
assert node.occupied?
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_free_returns_true_if_the_node_is_not_owned_by_any_player
|
48
|
+
node = Berlin::AI::Node.new
|
49
|
+
|
50
|
+
assert node.free?
|
51
|
+
|
52
|
+
node.player_id = 1
|
53
|
+
|
54
|
+
assert !node.free?
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_owned_by_returns_true_if_owned_by_the_provided_player
|
58
|
+
node = Berlin::AI::Node.new
|
59
|
+
|
60
|
+
assert !node.owned_by?(1)
|
61
|
+
|
62
|
+
node.player_id = 1
|
63
|
+
|
64
|
+
assert node.owned_by?(1)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_owned_returns_true_if_owned_by_a_player
|
68
|
+
node = Berlin::AI::Node.new
|
69
|
+
|
70
|
+
assert !node.owned?
|
71
|
+
|
72
|
+
node.player_id = 1
|
73
|
+
|
74
|
+
assert node.owned?
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: berlin-ai
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.32
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2013-
|
14
|
+
date: 2013-09-13 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: sinatra
|
@@ -93,6 +93,38 @@ dependencies:
|
|
93
93
|
- - '='
|
94
94
|
- !ruby/object:Gem::Version
|
95
95
|
version: 1.5.1
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: minitest
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ! '>='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: pry
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
116
|
+
requirements:
|
117
|
+
- - ! '>='
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
type: :development
|
121
|
+
prerelease: false
|
122
|
+
version_requirements: !ruby/object:Gem::Requirement
|
123
|
+
none: false
|
124
|
+
requirements:
|
125
|
+
- - ! '>='
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
96
128
|
description: Berlin Artificial Intelligence
|
97
129
|
email:
|
98
130
|
- christ.blais@gmail.com
|
@@ -102,16 +134,23 @@ executables: []
|
|
102
134
|
extensions: []
|
103
135
|
extra_rdoc_files: []
|
104
136
|
files:
|
137
|
+
- Gemfile
|
138
|
+
- Gemfile.lock
|
105
139
|
- LICENSE
|
106
|
-
- README
|
140
|
+
- README.md
|
107
141
|
- berlin-ai.gemspec
|
108
142
|
- lib/ai/fake.rb
|
109
143
|
- lib/ai/game.rb
|
144
|
+
- lib/ai/game_internal.rb
|
110
145
|
- lib/ai/map.rb
|
146
|
+
- lib/ai/map_internal.rb
|
111
147
|
- lib/ai/node.rb
|
148
|
+
- lib/ai/node_internal.rb
|
112
149
|
- lib/ai/player.rb
|
113
150
|
- lib/berlin-ai.rb
|
114
151
|
- lib/version.rb
|
152
|
+
- test/map_test.rb
|
153
|
+
- test/node_test.rb
|
115
154
|
- test/test_ai.rb
|
116
155
|
homepage: http://github.com/thirdside/berlin-ai
|
117
156
|
licenses: []
|
data/README
DELETED
File without changes
|