summaryrefslogtreecommitdiffstats
path: root/lib/colonial_twilight/board.rb
blob: f080a543d8ce387962cb24984931302d66969ab6 (plain)
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
285
286
287
288
289
290
291
#! /usr/bin/env ruby
# frozen_string_literal: true

require 'colonial_twilight/spaces'

module ColonialTwilight
  class Board
    FRANCE_TRACK = %w[A B C D E F].freeze
    TRACKS = %i[support_commitment opposition_bases fln_resources gov_resources commitment france_track border_zone_track].freeze

    attr_reader :spaces

    %i[commitment gov_resources fln_resources france_track border_zone_track
       support_commitment opposition_bases].each do |sym|
      define_method(sym) { instance_variable_get("@#{sym}").v }
    end

    %i[gov_bases french_troops french_police algerian_troops algerian_police fln_bases fln_underground].each do |sym|
      define_method("available_#{sym}") { @available.send(sym) }
      # define_method "casualties_#{sym}" do @casualties.send(sym) end
      # define_method "out_of_play_#{sym}" do @out_of_play.send(sym) end
    end

    def initialize
      @spaces = []
      @capabilities = []
      @available = Box.new :available
      @casualties = Box.new :casualties
      @out_of_play = Box.new :out_of_play
      @support_commitment = Track.new 50
      @opposition_bases = Track.new 50
      @fln_resources = Track.new 50
      @gov_resources = Track.new 50
      @commitment = Track.new 50
      @france_track = Track.new 5
      @border_zone_track = Track.new 4
      set_spaces
      set_adjacents
    end

    def inspect
      'Board'
    end

    def load(scenario)
      case scenario
      when :short then short
      when :medium then medium
      when :full then full
      else raise "unknown scenario : #{scenario}"
      end
    end

    def by_name(name)
      @spaces.find { |s| s.name == name }
    end

    def sector
      @spaces.select(&:sector?)
    end
    alias sectors sector

    def city
      @spaces.select(&:city?)
    end
    alias cities city

    def country
      @spaces.select(&:country?)
    end
    alias countries country

    def has(where = :spaces, &block)
      !send(where).select(&block).empty?
    end

    def search(where = :spaces, &block)
      send(where).select(&block)
    end

    def count(where = :spaces, &block)
      send(where).inject(0) { |i, s| i + block.call(s) }
    end

    def compute_opposition_bases
      count { |s| s.oppose? ? s.pop : 0 } + count(&:fln_bases)
    end

    def compute_support_commitment
      count { |s| s.support? ? s.pop : 0 } + @commitment.v
    end

    def shift(space, towards, num = 1)
      num.times { space.shift towards }
    end

    def shift_track(what, amount)
      raise "unknown track : #{what}" unless TRACKS.include? what

      instance_variable_get("@#{what}").shift amount
    end

    def apply(action)
      action.steps.each do |step|
        case step[:kind]
        when :transfer then transfer(step)
        else raise "unknow action step #{step}"
        end
      end
    end

    # def terror(where, num)
    #   where.terror += num
    # end

    # def data
    #   h = {}
    #   %i[gov_resources fln_resources commitment support_commitment opposition_bases
    #     france_track border_zone_track available casualties out_of_play].each do |sym|
    #     h[sym] = instance_variable_get("@#{sym}").data
    #   end
    #   h[:capabilities] = @capabilities
    #   h[:spaces] = @spaces.inject([]) { |a, s| a << s.data }
    #   h
    # end

    private

    def transfer(data)
      src = get_obj(data[:src])
      dst = get_obj(data[:dst])
      src.add data[:what], -data[:num]
      dst.add flip?(data), data[:num]
    end

    def flip?(data)
      !data[:flip] ? data[:what] : data[:flip]
    end

    def get_obj(obj)
      return obj if obj.is_a? ColonialTwilight::Sector

      case obj
      when :available then @available
      when :casualties then @casualties
      when :out_of_play then @out_of_play
      else
        raise "unknown Board variable named #{obj}"
      end
    end

    def add(kls, *args)
      @spaces << kls.new(*args)
    end

    def set_spaces
      mountain = Sector::MOUNTAIN
      border = Sector::BORDER
      coastal = Sector::COASTAL
      add Sector, 'Barika', 'I', 1, 1, mountain
      add Sector, 'Batna', 'I', 2, 0, mountain
      add Sector, 'Biskra', 'I', 3, 0, border
      add Sector, 'Oum El Bouaghi', 'I', 4, 0, mountain
      add Sector, 'Tebessa', 'I', 5, 1, mountain | border
      add Sector, 'Negrine', 'I', 6, 0, mountain | border
      add City, 'Constantine', 'II', 2
      add Sector, 'Setif', 'II', 1, 1, mountain | coastal
      add Sector, 'Philippeville', 'II', 2, 2, mountain | coastal
      add Sector, 'Souk Ahras', 'II', 3, 2, coastal | border
      add Sector, 'Tizi Ouzou', 'III', 1, 2, mountain | coastal
      add Sector, 'Bordj Bou Arreridj', 'III', 2, 1, mountain
      add Sector, 'Bougie', 'III', 3, 2, mountain | coastal
      add City, 'Algiers', 'IV', 3, coastal
      add Sector, 'Medea', 'IV', 1, 2, mountain | coastal
      add Sector, 'Orleansville', 'IV', 2, 2, mountain | coastal
      add City, 'Oran', 'V', 2, coastal
      add Sector, 'Mecheria', 'V', 1, 0, mountain | border
      add Sector, 'Tlemcen', 'V', 2, 1, border | coastal
      add Sector, 'Sidi Bel Abbes', 'V', 3, 1, coastal
      add Sector, 'Mostaganem', 'V', 4, 2, mountain | coastal
      add Sector, 'Saida', 'V', 5, 0, mountain
      add Sector, 'Mascara', 'V', 6, 0, mountain
      add Sector, 'Tiaret', 'V', 7, 0, mountain
      add Sector, 'Ain Sefra', 'V', 8, 0, border
      add Sector, 'Laghouat', 'V', 9, 0
      add Sector, 'Sidi Aissa', 'VI', 1, 0, mountain
      add Sector, 'Ain Oussera', 'VI', 2, 1, mountain
      add Country, 'Morocco'
      add Country, 'Tunisia'
    end

    def adjacents(idx, *args)
      @spaces[idx].adjacents = args
    end

    def set_adjacents
      adjacents  0, 1, 2, 3, 7, 8, 11, 26
      adjacents  1, 0, 2, 3, 5
      adjacents  2, 0, 1, 5, 25, 26, 29
      adjacents  3, 0, 1, 4, 5, 8, 9
      adjacents  4, 3, 5, 9, 29
      adjacents  5, 1, 2, 3, 4, 29
      adjacents  6, 7, 8
      adjacents  7, 0, 6, 8, 11, 12
      adjacents  8, 0, 3, 7, 6, 9
      adjacents  9, 3, 4, 8, 29
      adjacents 10, 11, 12, 14
      adjacents 11, 0, 7, 10, 12, 14, 26
      adjacents 12, 7, 10, 11
      adjacents 13, 14
      adjacents 14, 10, 11, 13, 15, 26, 27
      adjacents 15, 14, 20, 23, 27
      adjacents 16, 19
      adjacents 17, 18, 21, 24, 28
      adjacents 18, 17, 19, 21, 28
      adjacents 19, 16, 18, 20, 21, 22
      adjacents 20, 15, 19, 22, 23
      adjacents 21, 17, 18, 19, 22, 24
      adjacents 22, 19, 20, 21, 23, 24
      adjacents 23, 15, 20, 22, 24, 27
      adjacents 24, 17, 21, 22, 23, 25, 27, 28
      adjacents 25, 2, 24, 26, 27
      adjacents 26, 0, 2, 11, 14, 25, 27
      adjacents 27, 14, 15, 23, 24, 25, 26
      adjacents 28, 17, 18, 24
      adjacents 29, 2, 4, 5, 9
    end

    def resettle(name)
      by_name(name).resettle!
    end

    def set_space(idx, opts, align = nil)
      s = @spaces[idx]
      s.alignment = align unless align.nil?
      %i[gov_base fln_base french_troops french_police algerian_troops algerian_police
         fln_underground].each { |sym| s.add(sym, opts[sym]) if opts.key? sym }
    end

    def short
      @opposition_bases.v = 19
      @support_commitment.v = 22
      @commitment.v = 15
      @fln_resources.v = 15
      @gov_resources.v = 20
      @france_track.v = 4
      @border_zone_track.v = 3
      @out_of_play.init({ fln_underground: 5 })
      @available.init({ gov_base: 2, french_police: 4, fln_base: 7, fln_underground: 8 })
      resettle 'Setif'
      resettle 'Tlemcen'
      resettle 'Bordj Bou Arreridj'

      set_space  0, { algerian_police: 1, fln_underground: 1 }, :oppose
      set_space  2, { french_police: 1 }
      set_space  4, { algerian_police: 1, fln_underground: 1 }, :oppose
      set_space  5, { french_police: 1 }
      set_space  6, { french_police: 1 }, :support
      set_space  7, { fln_underground: 1 }
      set_space  8, { french_troops: 4, algerian_police: 1, gov_base: 1 }
      set_space  9, { french_troops: 1, algerian_police: 1, gov_base: 1, fln_underground: 1, fln_base: 1 }, :oppose
      set_space 10, { french_police: 1, fln_underground: 1, fln_base: 1 }, :oppose
      set_space 11, { french_police: 1 }
      set_space 12, { french_police: 1, fln_underground: 1, fln_base: 1 }, :oppose
      set_space 13, { french_troops: 4, algerian_troops: 1, french_police: 1 }, :support
      set_space 14, { algerian_troops: 1, gov_base: 1 }
      set_space 15, { french_police: 1, algerian_police: 1, fln_underground: 1, fln_base: 1 }, :oppose
      set_space 16, { algerian_troops: 1, french_police: 1, algerian_police: 1 }, :support
      set_space 17, { french_police: 1, algerian_police: 1 }
      set_space 18, { french_police: 2, fln_underground: 1 }
      set_space 19, { french_police: 1, gov_base: 1 }
      set_space 20, { french_police: 1 }
      set_space 22, { french_police: 1 }
      set_space 23, { french_police: 1 }
      set_space 24, { french_police: 1 }
      set_space 27, {}, :oppose
      set_space 28, { fln_underground: 4, fln_base: 2 }
      set_space 29, { fln_underground: 5, fln_base: 2 }
      spaces[28].independent!
      spaces[29].independent!
    end

    def medium
      raise 'MEDIUM scenario net implemented yet'
    end

    def full
      raise 'FULL scenario net implemented yet'
    end
  end
end