Class: WeewarAI::Unit

Inherits:
Object show all
Defined in:
lib/weewar-ai/unit.rb

Overview

An instance of the Unit class corresponds to a single unit in a game.

The Unit class provides access to Unit attributes like coordinates (x, y), health (hp), and type (trooper, raider, etc.). Also available are tactical calculation data, such as enemy targets that can be attacked, and hexes that can be reached in the current turn.

Unit s can be ordered to move, attack or repair.

Read the full method listing to see everything you can do with a Unit.

Constant Summary collapse

SYMBOL_FOR_UNIT =
{
  'Trooper' => :linf,
  'Heavy Trooper' => :hinf,
  'Raider' => :raider,
  'Assault Artillery' => :aart,
  'Tank' => :tank,
  'Heavy Tank' => :htank,
  'Berserker' => :bers,
  'Light Artillery' => :lart,
  'Heavy Artillery' => :hart,
  'DFA' => :dfa,
  'Hovercraft' => :hover,
  #'capturing' => :capturing,
}
TYPE_FOR_SYMBOL =
{
  :linf => 'Trooper',
  :hinf => 'Heavy Trooper',
  :raider => 'Raider',
  :tank => 'Tank',
  :htank => 'Heavy Tank',
  :lart => 'Light Artillery',
  :hart => 'Heavy Artillery',
  # TODO: rest
}
UNIT_CLASSES =
{
  :linf => :soft,
  :hinf => :soft,
  :raider => :hard,
  :aart => :hard,
  :tank => :hard,
  :htank => :hard,
  :bers => :hard,
  :lart => :hard,
  :hart => :hard,
  :dfa => :hard,
  :capturing => :soft,
  :hover => :amphibic,
}
UNIT_COSTS =

<Pistos> These need to be checked, I was just going by memory

{
  :linf => 75,
  :hinf => 150,
  :raider => 200,
  :tank => 300,
  :hover => 300,
  :htank => 600,
  :lart => 200,
  :aart => 450,
  :hart => 600,
  :dfa => 1200,
  :bers => 900,
  :sboat => 200,
  :dest => 1100,
  :bship => 2000,
  :sub => 1200,
  :jet => 800,
  :heli => 600,
  :bomber => 900,
  :aa => 300,
}
REPAIR_RATE =

<Pistos> These need to be checked, I was just going by memory

{
  :linf => 1,
  :hinf => 1,
  :raider => 2,
  :tank => 2,
  :hover => 2,
  :htank => 2,
  :lart => 1,
  :aart => 2,
  :hart => 1,
  :dfa => 1,
  :bers => 1,
  :sboat => 2,
  :dest => 1,
  :bship => 1,
  :sub => 1,
  :jet => 3,
  :heli => 3,
  :bomber => 3,
  :aa => 1,
}
INFINITY =
99999999

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(game, hex, faction, type, hp, finished, capturing = false) ⇒ Unit

Units are created by the Map class. No need to instantiate any on your own.



107
108
109
110
111
112
113
114
115
# File 'lib/weewar-ai/unit.rb', line 107

def initialize( game, hex, faction, type, hp, finished, capturing = false )
  sym = SYMBOL_FOR_UNIT[ type ]
  if sym.nil?
    raise "Unknown type: '#{type}'"
  end
  
  @game, @hex, @faction, @type, @hp, @finished, @capturing =
    game, hex, faction, sym, hp.to_i, finished, capturing
end

Instance Attribute Details

#factionObject (readonly)

Returns the value of attribute faction.



14
15
16
# File 'lib/weewar-ai/unit.rb', line 14

def faction
  @faction
end

#hexObject (readonly)

Returns the value of attribute hex.



14
15
16
# File 'lib/weewar-ai/unit.rb', line 14

def hex
  @hex
end

#hpObject

Returns the value of attribute hp.



15
16
17
# File 'lib/weewar-ai/unit.rb', line 15

def hp
  @hp
end

#typeObject (readonly)

Returns the value of attribute type.



14
15
16
# File 'lib/weewar-ai/unit.rb', line 14

def type
  @type
end

Instance Method Details

#==(other) ⇒ Object

Comparison for equality with another Unit. A Unit equals another Unit if it is standing on the same Hex, is of the same Faction, and is the same type.

if new_unit == old_unit
end


162
163
164
165
166
# File 'lib/weewar-ai/unit.rb', line 162

def ==( other )
  @hex == other.hex and
  @faction == other.faction and
  @type == other.type
end

#allied_unitsObject

An Array of the Unit s of the Game which are on the same side as this Unit.

friends = my_unit.allied_units


231
232
233
# File 'lib/weewar-ai/unit.rb', line 231

def allied_units
  @game.units.find_all { |u| u.faction == @faction }
end

#allied_with?(unit) ⇒ Boolean

Whether or not the given unit is an ally of this Unit.

if not my_unit.allied_with?( other_unit )
  my_unit.attack other_unit
end

Returns:

  • (Boolean)


239
240
241
# File 'lib/weewar-ai/unit.rb', line 239

def allied_with?( unit )
  @faction == unit.faction
end

#attack(unit) ⇒ Object

Commands this Unit to attack another Unit. This Unit will not move anywhere in the attempt to attack. Provide either a Unit or a Hex to attack as a method argument.

my_unit.attack enemy_unit


503
504
505
506
507
508
509
510
511
# File 'lib/weewar-ai/unit.rb', line 503

def attack( unit )
  x = unit.x
  y = unit.y
  
  result = send "<attack x='#{x}' y='#{y}'/>"
  process_attack result
  @game.last_attacked = @game.map[ x, y ].unit
  true
end

#can_attack?(target) ⇒ Boolean

Whether or not the Unit can attack the given target. Returns true iff the Unit can still take action in the current round, and the target is in range.

if my_unit.can_attack? enemy_unit
  my_unit.attack enemy_unit
end

Returns:

  • (Boolean)


204
205
206
# File 'lib/weewar-ai/unit.rb', line 204

def can_attack?( target )
  not @finished and targets.include?( target )
end

#can_capture?Boolean

Whether or not the Unit type can capture bases or not. Be aware that this can return true even if the Unit can no longer take action during the current turn.

if my_unit.can_capture?
  my_unit.move_to enemy_base
end

Returns:

  • (Boolean)


174
175
176
# File 'lib/weewar-ai/unit.rb', line 174

def can_capture?
  [ :linf, :hinf, :hover ].include? @type
end

#can_reach?(hex) ⇒ Boolean

Whether or not the Unit can reach the given Hex in the current turn.

if my_unit.can_reach? the_hex
  my_unit.move_to the_hex
end

Returns:

  • (Boolean)


225
226
227
# File 'lib/weewar-ai/unit.rb', line 225

def can_reach?( hex )
  destinations.include? hex
end

#capturing?Boolean

Whether or not the unit is capturing a base at the moment.

if not my_trooper.capturing?
  # do stuff with my_trooper
end

Returns:

  • (Boolean)


145
146
147
# File 'lib/weewar-ai/unit.rb', line 145

def capturing?
  @capturing
end

#destinationsObject Also known as: movement_options, movementOptions

An Array of the Hex es which the given Unit can move to in the current turn.

possible_moves = my_unit.destinations


210
211
212
213
214
215
216
217
# File 'lib/weewar-ai/unit.rb', line 210

def destinations
  coords = XmlSimple.xml_in(
    @game.send( "<movementOptions x='#{x}' y='#{y}' type='#{TYPE_FOR_SYMBOL[@type]}'/>" )
  )[ 'coordinate' ]
  coords.map { |c|
    @game.map[ c[ 'x' ], c[ 'y' ] ]
  }
end

#entrance_cost(hex) ⇒ Object

The cost in movement points for the unit to enter the given Hex. This is an internal method used for travel-related calculations; you should not normally need to use this yourself.



250
251
252
253
254
255
256
257
258
# File 'lib/weewar-ai/unit.rb', line 250

def entrance_cost( hex )
  return nil if hex.nil?
  
  specs_for_type = Hex.terrain_specs[ hex.type ]
  if specs_for_type.nil?
    raise "No specs for type '#{hex.type.inspect}': #{Hex.terrain_specs.inspect}"
  end
  specs_for_type[ :movement ][ unit_class ]
end

#finished?Boolean

Whether or not the unit can be ordered to do anything further.

if not my_unit.finished?
  # do stuff with my_unit
end

Returns:

  • (Boolean)


137
138
139
# File 'lib/weewar-ai/unit.rb', line 137

def finished?
  @finished
end

#move_to(destination, options = {}) ⇒ Object Also known as: move

Moves the given Unit to the given destination if it is reachable in one turn, otherwise moves the Unit towards it using the optimal path.

this_unit.move_to some_hex
that_unit.move_to enemy_unit

If a Unit or an Array of Units is passed as the :also_attack option, those Units will be prioritized for attack after moving, with the Units assumed to be given from highest priority (index 0) to lowest.

another_unit.move_to(
  enemy_unit,
  :also_attack => [ enemy_unit ] + enemy_artillery )
)

If an Array of hexes is provided as the :exclusions option, the Unit will not pass through any of the exclusion Hex es on its way to the destination.

spy_unit.move_to(
  enemy_base,
  :exclusions => well_defended_choke_point_hexes
)

By default, moving onto a base with a capturing unit will attempt a capture. Set the :no_capture option to true to prevent this.

my_trooper.move_to( enemy_base, :no_capture => true )

navy_seal.move_to(
  enemy_base,
  :also_attack => hard_targets,
  :exclusions => fortified_hexes,
  :no_capture => true
)


393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
# File 'lib/weewar-ai/unit.rb', line 393

def move_to( destination, options = {} )
  command = ""
  options[ :exclusions ] ||= []
  
  new_hex = @hex
  
  if destination != @hex
    # Travel
    
    path = shortest_path( destination, options[ :exclusions ] )
    if path.empty?
      $stderr.puts "No path from #{self} to #{destination}"
    else
      dests = destinations
      new_dest = path.pop
      while new_dest and not dests.include?( new_dest )
        new_dest = path.pop
      end
    end
    
    if new_dest.nil?
      $stderr.puts "  Can't move #{self} to #{destination}"
    else
      o = new_dest.unit
      if o and allied_with?( o )
        # Can't move through allied units
        options[ :exclusions ] << new_dest
        return move_to( destination, options )
      else
        x = new_dest.x
        y = new_dest.y
        new_hex = new_dest
        command << "<move x='#{x}' y='#{y}'/>"
      end
    end
  end
  
  target = nil
  also_attack = options[ :also_attack ]
  if also_attack
    enemies = targets( new_hex )
    if not enemies.empty?
      case also_attack
      when Array
        preferred = also_attack & enemies
      else
        preferred = [ also_attack ] & enemies
      end
      target = preferred.first# || enemies.random
      
      if target
        command << "<attack x='#{target.x}' y='#{target.y}'/>"
      end
    end
  end
  
  if(
    not options[ :no_capture ] and
    can_capture? and
    new_hex == destination and
    new_hex.capturable?
  )
    puts "#{self} capturing #{new_hex}"
    command << "<capture/>"
  end

  if not command.empty?
    result = send( command )
    puts "Moved #{self} to #{new_hex}"
    @hex.unit = nil
    new_hex.unit = self
    @hex = new_hex
    if target
      #<attack target='[3,4]' damageReceived='2' damageInflicted='7' remainingQuantity='8' />
      process_attack result
      @game.last_attacked = target
    end
    
    # Success
    true
  end
end

#path_cost(path) ⇒ Object

The cost in movement points for the unit to travel along the given path. The path given should be an Array of Hexes. This is an internal method used for travel-related calculations; you should not normally need to use this yourself.



264
265
266
267
268
# File 'lib/weewar-ai/unit.rb', line 264

def path_cost( path )
  path.inject( 0 ) { |sum,hex|
    sum + entrance_cost( hex )
  }
end

#process_attack(xml_text) ⇒ Object

This is an internal method used to update the Unit attributes after a command is sent to the weewar server. You should not call this yourself.



479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
# File 'lib/weewar-ai/unit.rb', line 479

def process_attack( xml_text )
  xml = XmlSimple.xml_in( xml_text, { 'ForceArray' => false } )[ 'attack' ]
  if xml[ 'target' ] =~ /\[(\d+),(\d+)\]/
    x, y = $1, $2
    enemy = @game.map[ x, y ].unit
  end
  
  if enemy.nil?
    raise "Server says enemy attacked was at (#{x},#{y}), but we have no record of an enemy there."
  end
  
  damage_inflicted = xml[ 'damageInflicted' ].to_i
  enemy.hp -= damage_inflicted
  
  damage_received = xml[ 'damageReceived' ].to_i
  @hp = xml[ 'remainingQuantity' ].to_i
  
  puts "  #{self} (-#{damage_received}: #{@hp}) ATTACKED #{enemy} (-#{damage_inflicted}: #{enemy.hp})" 
end

#repairObject

Commands the Unit to undergo repairs.

my_hurt_unit.repair


515
516
517
518
# File 'lib/weewar-ai/unit.rb', line 515

def repair
  send "<repair/>"
  @hp += REPAIR_RATE[ @type ]
end

#send(xml) ⇒ Object

Sends an XML command to the server regarding this Unit. This is an internal method that you should normally not need to call yourself.



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/weewar-ai/unit.rb', line 339

def send( xml )
  command = "<unit x='#{x}' y='#{y}'>#{xml}</unit>"
  response = @game.send command
  doc = Hpricot.XML( response )
  @finished = !! doc.at( 'finished' )
  if not @finished
    $stderr.puts "  #{self} NOT FINISHED:\n\t#{response}"
  end
  if not doc.at( 'ok' )
    error = doc.at 'error'
    if error
      message = "ERROR from server: #{error.inner_html}"
    else
      message = "RECEIVED:\n#{response}"
    end
    raise "Failed to execute:\n#{command}\n#{message}"
  end
  response
end

#shortest_path(dest, exclusions = []) ⇒ Object

The shortest path (as an Array of Hexes) from the Unit’s current location to the given destination.

If the optional exclusion array is provided, the path will not pass through any Hex in the exclusion array.

best_path = my_trooper.shortest_path( enemy_base )


284
285
286
287
288
289
290
291
292
293
294
# File 'lib/weewar-ai/unit.rb', line 284

def shortest_path( dest, exclusions = [] )
  exclusions ||= []
  previous = shortest_paths( exclusions )
  s = []
  u = dest.hex
  while previous[ u ]
    s.unshift u
    u = previous[ u ]
  end
  s
end

#shortest_paths(exclusions = []) ⇒ Object

Calculate all shortest paths from the Unit’s current Hex to every other Hex, as per Dijkstra’s algorithm ( en.wikipedia.org/wiki/Dijkstra’s_algorithm ). Most AIs will only need to make use of the shortest_path method instead.



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/weewar-ai/unit.rb', line 300

def shortest_paths( exclusions = [] )
  # Initialization
  exclusions ||= []
  source = hex
  dist = Hash.new
  previous = Hash.new
  q = []
  @game.map.each do |h|
    if not exclusions.include? h
      dist[ h ] = INFINITY
      q << h
    end
  end
  dist[ source ] = 0
  
  # Work
  while not q.empty?
    u = q.inject { |best,h| dist[ h ] < dist[ best ] ? h : best }
    q.delete u
    @game.map.hex_neighbours( u ).each do |v|
      next if exclusions.include? v
      alt = dist[ u ] + entrance_cost( v )
      if alt < dist[ v ]
        dist[ v ] = alt
        previous[ v ] = u
      end
    end
  end
  
  # Results
  previous
end

#targets(origin = @hex) ⇒ Object Also known as: attack_options, attackOptions

An Array of the Units which this Unit can attack in the current turn. If the optional origin Hex is provided, the target list is calculated as if the unit were on that Hex instead of its current Hex.

enemies_in_range = my_unit.targets
enemies_in_range_from_there = my_unit.targets possible_attack_position


183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/weewar-ai/unit.rb', line 183

def targets( origin = @hex )
  coords = XmlSimple.xml_in(
    @game.send( "<attackOptions x='#{origin.x}' y='#{origin.y}' type='#{TYPE_FOR_SYMBOL[@type]}'/>" )
  )[ 'coordinate' ]
  if coords
    coords.map { |c|
      @game.map[ c[ 'x' ], c[ 'y' ] ].unit
    }.compact
  else
    []
  end
end

#to_sObject



117
118
119
# File 'lib/weewar-ai/unit.rb', line 117

def to_s
  "#{@faction} #{@type} @ (#{@hex.x},#{@hex.y})"
end

#travel_cost(dest) ⇒ Object

The cost in movement points for this unit to travel to the given destination.



272
273
274
275
# File 'lib/weewar-ai/unit.rb', line 272

def travel_cost( dest )
  sp = shortest_path( dest )
  path_cost( sp )
end

#unit_classObject

The unit class of this unit. i.e. :soft, :hard, etc.

if my_unit.unit_class == :hard
  # attack some troopers!
end


153
154
155
# File 'lib/weewar-ai/unit.rb', line 153

def unit_class
  UNIT_CLASSES[ @type ]
end

#xObject

The Unit’s current x coordinate (column).

my_unit.x


123
124
125
# File 'lib/weewar-ai/unit.rb', line 123

def x
  @hex.x
end

#yObject

The Unit’s current y coordinate (row).

my unit.y


129
130
131
# File 'lib/weewar-ai/unit.rb', line 129

def y
  @hex.y
end