forked from pitluga/ants-ruby
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathants.rb
295 lines (232 loc) · 6.94 KB
/
ants.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# Ants AI Challenge framework
# by Matma Rex ([email protected])
# Released under CC-BY 3.0 license
# Represents a single ant.
class Ant
# Owner of this ant. If it's 0, it's your ant.
attr_accessor :owner
# Square this ant sits on.
attr_accessor :square
attr_accessor :alive, :ai
def initialize alive, owner, square, ai
@alive, @owner, @square, @ai = alive, owner, square, ai
end
# True if ant is alive.
def alive?; @alive; end
# True if ant is not alive.
def dead?; !@alive; end
# Equivalent to ant.owner==0.
def mine?; owner==0; end
# Equivalent to ant.owner!=0.
def enemy?; owner!=0; end
# Returns the row of square this ant is standing at.
def row; @square.row; end
# Returns the column of square this ant is standing at.
def col; @square.col; end
# Order this ant to go in given direction. Equivalent to ai.order ant, direction.
def order direction
@ai.order self, direction
end
end
# Represent a single field of the map.
class Square
# Ant which sits on this square, or nil. The ant may be dead.
attr_accessor :ant
# Which row this square belongs to.
attr_accessor :row
# Which column this square belongs to.
attr_accessor :col
attr_accessor :water, :food, :ai
def initialize water, food, ant, row, col, ai
@water, @food, @ant, @row, @col, @ai = water, food, ant, row, col, ai
end
# Returns true if this square is not water. Square is passable if it's not water, it doesn't contain alive ants and it doesn't contain food.
def land?; !@water; end
# Returns true if this square is water.
def water?; @water; end
# Returns true if this square contains food.
def food?; @food; end
# Returns true if this square has an alive ant.
def ant?; @ant and @ant.alive?; end;
# Returns a square neighboring this one in given direction.
def neighbor direction
direction=direction.to_s.upcase.to_sym # canonical: :N, :E, :S, :W
case direction
when :N
row, col = @ai.normalize @row-1, @col
when :E
row, col = @ai.normalize @row, @col+1
when :S
row, col = @ai.normalize @row+1, @col
when :W
row, col = @ai.normalize @row, @col-1
else
raise 'incorrect direction'
end
return @ai.map[row][col]
end
end
class AI
# Map, as an array of arrays.
attr_accessor :map
# Number of current turn. If it's 0, we're in setup turn. If it's :game_over, you don't need to give any orders; instead, you can find out the number of players and their scores in this game.
attr_accessor :turn_number
# Game settings. Integers.
attr_accessor :loadtime, :turntime, :rows, :cols, :turns, :viewradius2, :attackradius2, :spawnradius2, :seed
# Radii, unsquared. Floats.
attr_accessor :viewradius, :attackradius, :spawnradius
# Number of players. Available only after game's over.
attr_accessor :players
# Array of scores of players (you are player 0). Available only after game's over.
attr_accessor :score
# Initialize a new AI object. Arguments are streams this AI will read from and write to.
def initialize stdin=$stdin, stdout=$stdout
@stdin, @stdout = stdin, stdout
@map=nil
@turn_number=0
@my_ants=[]
@enemy_ants=[]
@did_setup=false
end
# Returns a read-only hash of all settings.
def settings
{
:loadtime => @loadtime,
:turntime => @turntime,
:rows => @rows,
:cols => @cols,
:turns => @turns,
:viewradius2 => @viewradius2,
:attackradius2 => @attackradius2,
:spawnradius2 => @spawnradius2,
:viewradius => @viewradius,
:attackradius => @attackradius,
:spawnradius => @spawnradius,
:seed => @seed
}.freeze
end
# Zero-turn logic.
def setup # :yields: self
read_intro
yield self
@stdout.puts 'go'
@stdout.flush
@map=Array.new(@rows){|row| Array.new(@cols){|col| Square.new false, false, nil, row, col, self } }
@did_setup=true
end
# Turn logic. If setup wasn't yet called, it will call it (and yield the block in it once).
def run &b # :yields: self
setup &b if !@did_setup
over=false
until over
over = read_turn
yield self
@stdout.puts 'go'
@stdout.flush
end
end
# Internal; reads zero-turn input (game settings).
def read_intro
warn "unexpected: #{rd}" unless rd=='turn 0'
until(([email protected])=='ready')
_, name, value = *rd.match(/\A([a-z0-9]+) (\d+)\Z/)
case name
when 'loadtime'; @loadtime=value.to_i
when 'turntime'; @turntime=value.to_i
when 'rows'; @rows=value.to_i
when 'cols'; @cols=value.to_i
when 'turns'; @turns=value.to_i
when 'viewradius2'; @viewradius2=value.to_i
when 'attackradius2'; @attackradius2=value.to_i
when 'spawnradius2'; @spawnradius2=value.to_i
when 'seed'; @seed=value.to_i
else
warn "unexpected: #{rd}"
end
end
@viewradius=Math.sqrt @viewradius2
@attackradius=Math.sqrt @attackradius2
@spawnradius=Math.sqrt @spawnradius2
end
# Internal; reads turn input (map state).
def read_turn
ret=false
if rd=='end'
@turn_number=:game_over
_, players = *rd.match(/\Aplayers (\d+)\Z/)
@players = players.to_i
_, score = *rd.match(/\Ascore (\d+(?: \d+)+)\Z/)
@score = score.split(' ').map{|s| s.to_i}
ret=true
else
_, num = *rd.match(/\Aturn (\d+)\Z/)
@turn_number=num.to_i
end
# reset the map data
@map.each do |row|
row.each do |square|
square.food=false
square.ant=nil
end
end
@my_ants=[]
@enemy_ants=[]
until(([email protected])=='go')
_, type, row, col, owner = *rd.match(/(w|f|a|d) (\d+) (\d+)(?: (\d+)|)/)
row, col = row.to_i, col.to_i
owner = owner.to_i if owner
case type
when 'w'
@map[row][col].water=true
when 'f'
@map[row][col].food=true
when 'a'
a=Ant.new true, owner, @map[row][col], self
@map[row][col].ant = a
if owner==0
my_ants.push a
else
enemy_ants.push a
end
when 'd'
d=Ant.new false, owner, @map[row][col], self
@map[row][col].ant = d
when 'r'
# pass
else
warn "unexpected: #{rd}"
end
end
return ret
end
# call-seq:
# order(ant, direction)
# order(row, col, direction)
#
# Give orders to an ant, or to whatever happens to be in the given square (and it better be an ant).
def order a, b, c=nil
if !c # assume two-argument form: ant, direction
ant, direction = a, b
@stdout.puts "o #{ant.row} #{ant.col} #{direction.to_s.upcase}"
else # assume three-argument form: row, col, direction
col, row, direction = a, b, c
@stdout.puts "o #{row} #{col} #{direction.to_s.upcase}"
end
end
# Returns an array of your alive ants on the gamefield.
def my_ants; @my_ants; end
# Returns an array of alive enemy ants on the gamefield.
def enemy_ants; @enemy_ants; end
# If row or col are greater than or equal map width/height, makes them fit the map.
#
# Handles negative values correctly (it may return a negative value, but always one that is a correct index).
#
# Returns [row, col].
def normalize row, col
[row % @rows, col % @cols]
end
end