empi 0.17 → 0.22.3

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.
@@ -0,0 +1,106 @@
1
+ require_relative './army'
2
+ require_relative './ship'
3
+ require_relative './unit'
4
+
5
+ require_relative './../game_states/game_state'
6
+ require_relative './../game_states/build_state'
7
+
8
+ class Town < Unit
9
+ @name = 'town'
10
+ @map_symbol = 'T'
11
+ @value = 20
12
+
13
+ attr_accessor :project, :parts_built, :parts_needed
14
+
15
+ def initialize(x, y, faction, map, infopane)
16
+ super
17
+ dir_path = File.dirname(__FILE__)
18
+ @image = Gosu::Image.new(dir_path + '/../../media/town.png')
19
+
20
+ @armor_left = @armor_max = 1
21
+ @moves_max = 0
22
+ @cargo_max = 10
23
+
24
+ @starting_project = Army unless @faction == 0
25
+ @project = nil
26
+ @parts_built = 0
27
+ @parts_needed = 0
28
+
29
+ set_function!(FUNCBUILD) unless @faction == 0
30
+ end
31
+
32
+ def can_build?
33
+ true
34
+ end
35
+
36
+ def can_be_captured?
37
+ true
38
+ end
39
+
40
+ # Tell the state of current build project
41
+ def build_info
42
+ "#{@parts_built}/#{@parts_needed}"
43
+ end
44
+
45
+ # Process capture targeted at this town and reset build process
46
+ def capture!(by_whom)
47
+ super
48
+
49
+ # Reset build process
50
+ @function.func = FUNCNONE
51
+ @project = nil
52
+ @parts_built = 0
53
+
54
+ set_function!(FUNCBUILD)
55
+ end
56
+
57
+ # Set desired function and possibly also project
58
+ def set_function!(func)
59
+ super
60
+
61
+ if @faction != 0 and func == FUNCBUILD # neutral towns don't need projects
62
+ # Set starting project once or ask player about next project
63
+ if @starting_project
64
+ set_project!(@starting_project)
65
+ @starting_project = nil
66
+ else
67
+ GameState.switch!(BuildState.instance)
68
+ BuildState.instance.unit = self
69
+ end
70
+ end
71
+ end
72
+
73
+ # Set desired project
74
+ def set_project!(desired_project)
75
+ unless price_list.key?(desired_project)
76
+ abort("town.set_project!(): Unknown project (#{desired_project})")
77
+ end
78
+ @parts_needed = price_list[desired_project]
79
+
80
+ # Compare new setting with the old one
81
+ if desired_project == @project
82
+ puts PROMPT + to_s + ": project has already been set to #{@project.name} (#{build_info} done)"
83
+ else
84
+ previous_project = @project
85
+ @project = desired_project
86
+ lost_parts = @parts_built
87
+ @parts_built = 0
88
+
89
+ new_project_set_text = PROMPT + to_s + ": project set to #{@project.name} (#{build_info} done)"
90
+ unless lost_parts > 0
91
+ puts new_project_set_text
92
+ else
93
+ puts new_project_set_text + ", losing #{lost_parts} " +
94
+ "part#{ 's' unless lost_parts == 1 } of #{previous_project.name}"
95
+
96
+ end
97
+ end
98
+ end
99
+
100
+ # Load all prices
101
+ def price_list
102
+ prices = Hash[
103
+ [Army, Ship].collect { |ii| [ii, ii.price] } # TODO all subclasses of Unit which can_be_built?
104
+ ]
105
+ end
106
+ end
@@ -1,9 +1,12 @@
1
1
  require_relative './unitFunction'
2
2
 
3
- # All capturable game pieces
3
+ # Both capturable and movable game pieces
4
4
  class Unit
5
- attr_accessor :x, :y, :faction, :function, :cargo
5
+ class << self
6
+ attr_reader :name, :map_symbol, :price, :value
7
+ end
6
8
 
9
+ attr_accessor :x, :y, :faction, :function, :cargo, :cargo_max
7
10
 
8
11
  def initialize(x, y, faction, map, infopane)
9
12
  @x = x
@@ -12,17 +15,13 @@ class Unit
12
15
  @map = map
13
16
  @infopane = infopane
14
17
 
15
- @name = 'unit'
16
- @value = 1
17
18
  @armor_max = 1
18
19
  @armor_left = @armor_max
19
20
  @moves_max = 1
20
21
  @moves_left = @moves_max
21
22
  @cargo = [] # transported units
22
23
  @cargo_max = 0
23
- @function = UnitFunction.new(FUNCNONE)
24
-
25
-
24
+ @function = UnitFunction.new(FUNCNONE, self)
26
25
 
27
26
  # Store yourself
28
27
  coords_unit = @map.get_unit(@x, @y)
@@ -33,75 +32,68 @@ class Unit
33
32
  end
34
33
  end
35
34
 
36
- # Add <value> to the other faction
37
- def destroy
38
- @infopane.add_score(3 - @faction - 1, @value) # TODO more factions?
39
- # TODO is this deleted enough?
40
- @map.set_unit(@x, @y, nil)
41
- end
42
-
43
- def capture(by_whom)
44
- # add <value> to the capturing faction
45
- @infopane.add_score(by_whom - 1, @value)
46
- @faction = by_whom
35
+ # Process engagement targeted at this unit
36
+ def engage!(by_whom)
37
+ if by_whom == @faction
38
+ abort("unit.engage!(): Engaging unit is of the same faction as this one (#{@faction})")
39
+ end
47
40
 
48
- # if you are a town, start building units
49
- if can_build?
50
- set_function(FUNCBUILD)
41
+ # Can it be captured and is it without defenders?
42
+ if can_be_captured?
43
+ if is_transporting?
44
+ cargo[0].attack!(by_whom)
45
+ else # then capture it if you can
46
+ if by_whom.can_capture?
47
+ puts PROMPT + by_whom.to_s + ' is capturing ' + to_s
48
+ capture!(by_whom.faction)
49
+ else
50
+ puts PROMPT + by_whom.to_s + ' can\'t capture other units'
51
+ end
52
+ end
53
+ else
54
+ attack!(by_whom) # uncapturable transporters can't get help from their cargo
51
55
  end
52
56
  end
53
57
 
54
- def update(key)
55
- old_x = @x
56
- old_y = @y
57
-
58
- case key
59
- # Cardinal directions
60
- when Gosu::KbLeft, Gosu::KbA then
61
- @x -= 1 unless @x <= 0
62
- when Gosu::KbRight, Gosu::KbD then
63
- @x += 1 unless @x >= MAPX
64
- when Gosu::KbUp, Gosu::KbW then
65
- @y -= 1 unless @y <= 0
66
- when Gosu::KbDown, Gosu::KbX then
67
- @y += 1 unless @y >= MAPY
68
-
69
- # Intercardinal directions
70
- when Gosu::KbQ then
71
- unless @x <= 0 || @y <= 0
72
- @x -= 1
73
- @y -= 1
74
- end
75
- when Gosu::KbE then
76
- unless @x >= MAPX || @y <= 0
77
- @x += 1
78
- @y -= 1
79
- end
80
- when Gosu::KbZ then
81
- unless @x <= 0 || @y >= MAPY
82
- @x -= 1
83
- @y += 1
84
- end
85
- when Gosu::KbC then
86
- unless @x >= MAPX || @y >= MAPY
87
- @x += 1
88
- @y += 1
89
- end
58
+ # Process attack targeted at this unit
59
+ def attack!(by_whom)
60
+ puts PROMPT + by_whom.to_s + ' is attacking ' + to_s
61
+ destroy! # TODO proper combat
62
+ end
90
63
 
91
- # Functions
92
- when Gosu::KbS then
93
- set_function(FUNCSENTRY)
94
- when Gosu::KbB then
95
- set_function(FUNCBUILD)
64
+ # Process capture targeted at this unit
65
+ def capture!(by_whom)
66
+ unless can_be_captured?
67
+ abort("unit.capture!(): This unit can\'t be captured")
96
68
  end
97
69
 
98
- check_movement(old_x, old_y)
70
+ if by_whom == @faction
71
+ abort("unit.capture!(): This unit already belongs to faction #{@faction}")
72
+ end
73
+
74
+ # Add <value> to the capturing faction
75
+ @infopane.add_score(by_whom - 1, self.class.value)
76
+ @faction = by_whom
99
77
  end
100
78
 
79
+ # Add <value> to the other faction and remove links to given unit
80
+ def destroy!
81
+ # If you are transporting somebody, destroy them first
82
+ @cargo.each { |uu| uu.destroy! }
83
+
84
+ @infopane.add_score(3 - @faction - 1, self.class.value) # TODO more factions?
85
+
86
+ if is_transported?
87
+ coords_unit = @map.get_unit(@x, @y)
88
+ coords_unit.cargo -= [self]
89
+ else
90
+ @map.set_unit(@x, @y, nil)
91
+ end
92
+ end
101
93
 
102
- # Processes move of unit and takes care of its (un)loading or attack
94
+ # Process move of unit and take care of its (un)loading or start of engagement
103
95
  def check_movement(old_x, old_y)
104
- if @x != old_x || @y != old_y
96
+ if @x != old_x || @y != old_y # only if it moved
105
97
  if @moves_left <= 0
106
98
  abort("unit.check_movement(): Moving unit does not have enough move points (#{@moves_left} moves)")
107
99
  end
@@ -109,65 +101,61 @@ class Unit
109
101
  oldcoords_unit = @map.get_unit(old_x, old_y)
110
102
  newcoords_unit = @map.get_unit(@x, @y)
111
103
 
112
- # If there is nobody or is there friendly unit with some cargo space
104
+ # If there is nobody or is there friendly unit with some cargo space (and bigger cargo space)
113
105
  if !newcoords_unit || \
114
106
  (newcoords_unit.faction == @faction && \
115
107
  newcoords_unit.can_transport? && \
108
+ @cargo_max < newcoords_unit.cargo_max && \
116
109
  !newcoords_unit.is_full?)
117
110
 
118
111
  # Leave old coordinates
119
- if oldcoords_unit == self
112
+ if oldcoords_unit == self
120
113
  @map.set_unit(old_x, old_y, nil)
121
114
  else # if you have been transported
122
115
  oldcoords_unit.cargo.delete(self)
123
116
  end
124
117
 
125
118
  # Get to new coordinates
126
- if newcoords_unit == nil
119
+ if !newcoords_unit
127
120
  @map.set_unit(@x, @y, self)
128
121
  @cargo.each { |uu|
129
122
  uu.x = @x
130
123
  uu.y = @y
131
124
  }
132
125
 
133
- else # if you are going to be transported
126
+ else # if you are going to be transported
134
127
  newcoords_unit.cargo.insert(-1, self) # -1 = to the end
135
128
  puts PROMPT + to_s + ' got loaded into '+ newcoords_unit.to_s
129
+ @moves_left = 1 unless newcoords_unit.can_build? # use all your left moves unless you are getting loaded into a town
136
130
  end
137
131
 
138
132
  else # if there already is somebody that can't transport you (enemy or full friend)
139
- # Stay on your original tile
140
- @x = old_x
141
- @y = old_y
142
-
143
- # If it was a friend unit
144
- if newcoords_unit.faction == @faction
145
- if !newcoords_unit.can_transport?
146
- puts PROMPT + newcoords_unit.to_s + ' can\'t transport other units'
147
- else # newcoords_unit.can_full?; has to be full then
148
- puts PROMPT + newcoords_unit.to_s + ' is already full'
149
- end
133
+ # Stay on your original tile
134
+ @x = old_x
135
+ @y = old_y
136
+
137
+ # If it was a friend unit
138
+ if newcoords_unit.faction == @faction
139
+ if !newcoords_unit.can_transport?
140
+ puts PROMPT + newcoords_unit.to_s + ' can\'t transport other units'
150
141
  else
151
- # Can it be captured?
152
- if newcoords_unit.can_be_captured?
153
- if can_capture? # then capture it if you can
154
- puts PROMPT + to_s + ' is capturing ' + newcoords_unit.to_s
155
- newcoords_unit.capture(@faction)
156
- else
157
- puts PROMPT + to_s + ' can\'t capture other units'
158
- end
159
- else # then battle it
160
- puts PROMPT + to_s + ' is battling ' + newcoords_unit.to_s
161
- newcoords_unit.destroy # TODO randomized combat
142
+ if @cargo_max >= newcoords_unit.cargo_max
143
+ puts PROMPT + "#{self.class.name} can\'t fit in #{newcoords_unit.class.name}"
144
+ else # thus newcoords_unit.is_full? is true
145
+ puts PROMPT + newcoords_unit.to_s + ' is already full'
162
146
  end
163
- end
147
+ end
148
+ else
149
+ # Enemy!
150
+ newcoords_unit.engage!(self)
151
+ end
164
152
  end
165
153
  @moves_left -= 1
166
154
 
167
155
  # Check if you are on an invalid type of terrain (unless transported)
168
- unless tile_check == true
156
+ unless is_terrain_suitable?
169
157
  puts PROMPT + to_s + " found itself in a bad place"
170
- destroy
158
+ destroy!
171
159
  end
172
160
  end
173
161
  end
@@ -198,6 +186,10 @@ class Unit
198
186
  false
199
187
  end
200
188
 
189
+ def can_be_built?
190
+ self.class.price > 0
191
+ end
192
+
201
193
  def can_transport?
202
194
  @cargo_max > 0
203
195
  end
@@ -223,7 +215,7 @@ class Unit
223
215
  end
224
216
 
225
217
  # Checks if unit is on the right type of terrain (not for transported units)
226
- def tile_check
218
+ def is_terrain_suitable?
227
219
  unless is_transported?
228
220
  case @map.tile(@x, @y).terrain
229
221
  when TILE_SEA
@@ -239,14 +231,19 @@ class Unit
239
231
  @function.func
240
232
  end
241
233
 
242
- def set_function(func)
243
- unless @faction == 0 # neutral faction doesn't need functions
244
- if (func == FUNCBUILD && !can_build?)
234
+ # Set desired function
235
+ def set_function!(func)
236
+ if @faction == 0
237
+ puts PROMPT + to_s + ": neutral towns can't have functions set"
238
+ else
239
+ if func == FUNCBUILD and !can_build?
245
240
  puts PROMPT + to_s + ": this unit can't build other units"
241
+ return
246
242
  end
247
243
 
244
+ # Check current function and set the new one
248
245
  if @function.func == func
249
- puts PROMPT + to_s + ": function has already been set to #{@function.func}"
246
+ puts PROMPT + to_s + ": function is already set to #{@function.func}"
250
247
  else
251
248
  @function.func = func
252
249
  puts PROMPT + to_s + ": function set to #{@function.func}"
@@ -255,27 +252,27 @@ class Unit
255
252
  end
256
253
 
257
254
  def function!
258
- ret = @function.func!(@x, @y, @map, @infopane)
255
+ ret = @function.func!(@map, @infopane)
259
256
  puts to_s + ret
260
257
  end
261
258
 
262
259
  # Set short info string: type, faction, coordinates
263
260
  def to_s
264
- "#{@name} (FAC#{@faction} #{@x}-#{@y})"
261
+ "#{self.class.name} (FAC#{@faction} #{@x}-#{@y})"
265
262
  end
266
263
 
267
264
  # Set long info string: short info string, armor, moves, function, cargo
268
265
  def info
269
266
  ret = to_s + ": armor #{@armor_left}/#{@armor_max}"
270
-
271
- if can_move?
267
+
268
+ if @moves_max > 0
272
269
  ret = ret + ", moves #{@moves_left}/#{@moves_max}"
273
270
  end
274
271
 
275
- ret = ret + ", func #{@function.func}"
272
+ ret = ret + ", #{@function.info}"
276
273
 
277
- if @cargo.size > 0
278
- ret = ret + ", transports #{@cargo.size}/#{@cargo_max} units"
274
+ if @cargo_max > 0
275
+ ret = ret + ", cargo #{@cargo.size}/#{@cargo_max}"
279
276
  end
280
277
 
281
278
  ret
@@ -0,0 +1,63 @@
1
+ class UnitFunction
2
+ attr_accessor :func
3
+
4
+ def initialize(function = FUNCNONE, unit)
5
+ @func = function
6
+ @unit = unit
7
+ end
8
+
9
+ # Set function part of long info string of unit
10
+ def info
11
+ case @func
12
+ when FUNCNONE
13
+ "no function"
14
+ when FUNCBUILD
15
+ unless @unit.project
16
+ abort("unitFunction.info(): No project set (" + @unit.to_s + ")")
17
+ end
18
+ "building #{@unit.project.name} (#{@unit.build_info})"
19
+ when FUNCSENTRY
20
+ "sentrying"
21
+ end
22
+ end
23
+
24
+ def func!(map, infopane)
25
+ case @func
26
+ # Build given unit
27
+ when FUNCBUILD
28
+ unless @unit.project
29
+ abort("unitFunction.func!(): No project set (" + @unit.to_s + ")")
30
+ end
31
+
32
+ @unit.parts_built += 1
33
+
34
+ unless @unit.parts_built >= @unit.parts_needed # just == should be enough
35
+ ret = " built next part of #{@unit.project.name} (#{@unit.build_info} done)"
36
+ else
37
+ ret = " completed building of #{@unit.project.name}"
38
+ @unit.parts_built = 0
39
+ @unit.project.new(@unit.x, @unit.y, @unit.faction, map, infopane)
40
+ end
41
+
42
+ # Wake when enemies are nearby
43
+ when FUNCSENTRY
44
+ units_around = map.all_units.select { |uu|
45
+ (uu.x - @unit.x).abs <= 1 &&
46
+ (uu.y - @unit.y).abs <= 1 &&
47
+ uu.faction != @unit.faction
48
+ }
49
+ if units_around.size > 0
50
+ ret = " woke up"
51
+ @func = FUNCNONE
52
+ else
53
+ ret = " was on sentry duty"
54
+ end
55
+ end
56
+
57
+ unless ret
58
+ abort("unitFunction.func!(): Functionable unit didn't function (" + @unit.to_s + ")")
59
+ end
60
+
61
+ ret
62
+ end
63
+ end