RSokoban 0.74 → 0.76

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.
Files changed (111) hide show
  1. data/NEWS +33 -1
  2. data/README.rdoc +21 -14
  3. data/TODO +6 -12
  4. data/VERSION +1 -1
  5. data/bin/rsokoban +40 -3
  6. data/data/big.xsb +35 -0
  7. data/lib/rsokoban/config.rb +34 -0
  8. data/lib/rsokoban/crate.rb +4 -5
  9. data/lib/rsokoban/exception.rb +2 -0
  10. data/lib/rsokoban/game/game.rb +153 -0
  11. data/lib/rsokoban/game/game_curses.rb +11 -0
  12. data/lib/rsokoban/game/game_factory.rb +23 -0
  13. data/lib/rsokoban/game/game_gui.rb +27 -0
  14. data/lib/rsokoban/game/game_portable.rb +10 -0
  15. data/lib/rsokoban/game/game_tk.rb +11 -0
  16. data/lib/rsokoban/game/game_ui.rb +81 -0
  17. data/lib/rsokoban/game.rb +7 -238
  18. data/lib/rsokoban/install.rb +22 -0
  19. data/lib/rsokoban/layered_map.rb +139 -0
  20. data/lib/rsokoban/level.rb +112 -166
  21. data/lib/rsokoban/level_set.rb +4 -4
  22. data/lib/rsokoban/man.rb +3 -4
  23. data/lib/rsokoban/map.rb +91 -15
  24. data/lib/rsokoban/move_result.rb +2 -2
  25. data/lib/rsokoban/option.rb +4 -4
  26. data/lib/rsokoban/position.rb +1 -1
  27. data/lib/rsokoban/raw_level.rb +11 -2
  28. data/lib/rsokoban/record.rb +51 -0
  29. data/lib/rsokoban/set_loader.rb +108 -0
  30. data/lib/rsokoban/ui/base_ui.rb +1 -0
  31. data/lib/rsokoban/ui/console.rb +56 -25
  32. data/lib/rsokoban/ui/curses_console.rb +59 -33
  33. data/lib/rsokoban/ui/player_action.rb +13 -7
  34. data/lib/rsokoban/ui/skin.rb +105 -0
  35. data/lib/rsokoban/ui/tk_dialogs.rb +104 -18
  36. data/lib/rsokoban/ui/tk_ui.rb +410 -233
  37. data/lib/rsokoban/ui.rb +1 -0
  38. data/lib/rsokoban.rb +14 -2
  39. data/skins/AntiqueDesk/crate.bmp +0 -0
  40. data/skins/AntiqueDesk/crate_store.bmp +0 -0
  41. data/skins/AntiqueDesk/floor.bmp +0 -0
  42. data/skins/AntiqueDesk/man_down.bmp +0 -0
  43. data/skins/AntiqueDesk/man_left.bmp +0 -0
  44. data/skins/AntiqueDesk/man_right.bmp +0 -0
  45. data/skins/AntiqueDesk/man_store_down.bmp +0 -0
  46. data/skins/AntiqueDesk/man_store_left.bmp +0 -0
  47. data/skins/AntiqueDesk/man_store_right.bmp +0 -0
  48. data/skins/AntiqueDesk/man_store_up.bmp +0 -0
  49. data/skins/AntiqueDesk/man_up.bmp +0 -0
  50. data/skins/AntiqueDesk/outside.bmp +0 -0
  51. data/skins/AntiqueDesk/skin.conf +3 -0
  52. data/skins/AntiqueDesk/store.bmp +0 -0
  53. data/skins/AntiqueDesk/wall.bmp +0 -0
  54. data/skins/BlueGranite/outside.bmp +0 -0
  55. data/skins/BlueGranite/skin.conf +3 -0
  56. data/skins/HeavyMetal/crate.bmp +0 -0
  57. data/skins/HeavyMetal/crate_store.bmp +0 -0
  58. data/skins/HeavyMetal/floor.bmp +0 -0
  59. data/skins/HeavyMetal/man.bmp +0 -0
  60. data/skins/HeavyMetal/man_store.bmp +0 -0
  61. data/skins/HeavyMetal/outside.bmp +0 -0
  62. data/skins/HeavyMetal/skin.conf +3 -0
  63. data/skins/HeavyMetal/store.bmp +0 -0
  64. data/skins/HeavyMetal/wall.bmp +0 -0
  65. data/skins/HeavyMetal/wall_d.bmp +0 -0
  66. data/skins/HeavyMetal/wall_dl.bmp +0 -0
  67. data/skins/HeavyMetal/wall_dlr.bmp +0 -0
  68. data/skins/HeavyMetal/wall_dr.bmp +0 -0
  69. data/skins/HeavyMetal/wall_l.bmp +0 -0
  70. data/skins/HeavyMetal/wall_lr.bmp +0 -0
  71. data/skins/HeavyMetal/wall_r.bmp +0 -0
  72. data/skins/HeavyMetal/wall_u.bmp +0 -0
  73. data/skins/HeavyMetal/wall_ud.bmp +0 -0
  74. data/skins/HeavyMetal/wall_udl.bmp +0 -0
  75. data/skins/HeavyMetal/wall_udlr.bmp +0 -0
  76. data/skins/HeavyMetal/wall_udr.bmp +0 -0
  77. data/skins/HeavyMetal/wall_ul.bmp +0 -0
  78. data/skins/HeavyMetal/wall_ulr.bmp +0 -0
  79. data/skins/HeavyMetal/wall_ur.bmp +0 -0
  80. data/test/record/original.yaml +2 -0
  81. data/test/tc_game.rb +24 -15
  82. data/test/tc_game_factory.rb +40 -0
  83. data/test/tc_game_gui.rb +26 -0
  84. data/test/tc_game_ui.rb +153 -0
  85. data/test/tc_install.rb +12 -0
  86. data/test/tc_layered_map.rb +105 -0
  87. data/test/tc_level.rb +109 -107
  88. data/test/tc_level_set.rb +4 -4
  89. data/test/tc_map.rb +46 -10
  90. data/test/tc_record.rb +100 -0
  91. data/test/{tc_level_loader.rb → tc_set_loader.rb} +25 -24
  92. data/test/test.rb +9 -2
  93. data/test/ui/tc_skin.rb +71 -0
  94. metadata +89 -26
  95. data/lib/rsokoban/level_loader.rb +0 -81
  96. data/lib/rsokoban/ui/tk_box.rb +0 -21
  97. data/skins/default/outside.bmp +0 -0
  98. data/skins/default/readme +0 -1
  99. /data/skins/{default → BlueGranite}/crate.bmp +0 -0
  100. /data/skins/{default → BlueGranite}/crate_store.bmp +0 -0
  101. /data/skins/{default → BlueGranite}/floor.bmp +0 -0
  102. /data/skins/{default → BlueGranite}/man_down.bmp +0 -0
  103. /data/skins/{default → BlueGranite}/man_left.bmp +0 -0
  104. /data/skins/{default → BlueGranite}/man_right.bmp +0 -0
  105. /data/skins/{default → BlueGranite}/man_store_down.bmp +0 -0
  106. /data/skins/{default → BlueGranite}/man_store_left.bmp +0 -0
  107. /data/skins/{default → BlueGranite}/man_store_right.bmp +0 -0
  108. /data/skins/{default → BlueGranite}/man_store_up.bmp +0 -0
  109. /data/skins/{default → BlueGranite}/man_up.bmp +0 -0
  110. /data/skins/{default → BlueGranite}/store.bmp +0 -0
  111. /data/skins/{default → BlueGranite}/wall.bmp +0 -0
@@ -1,9 +1,8 @@
1
1
  module RSokoban
2
2
 
3
3
  # I am a level of the game.
4
- # To complete a level, place each crate ('$') on a storage location ('.').
5
4
  class Level
6
- attr_reader :floor, :man, :crates, :storages, :title
5
+ attr_reader :title
7
6
 
8
7
  # Get map width of this level, in cells
9
8
  # @return [Fixnum]
@@ -13,39 +12,55 @@ module RSokoban
13
12
  # @return [Fixnum]
14
13
  attr_reader :height
15
14
 
15
+ attr_accessor :number
16
+
16
17
  # I build the level from a RawLevel object.
17
- # @example a RawLevel object
18
- # A RawLevel object have got one title and one 'picture'. A 'picture' is an array of string.
19
- # Each string contain one line of the level map.
20
- # 'Level 1', ['#####', '#.o@#', '#####']
21
- #
22
- # * '#' is a wal
23
- # * '.' is a storage location
24
- # * '$' is a crate
25
- # * '*' is a crate on storage location
26
- # * '@' is the man
27
- # * ' ' is an empty floor
28
- #
29
- # @param [RawLevel] rawLevel
30
- def initialize rawLevel
31
- @title = rawLevel.title
32
- init_dimension rawLevel.map
33
- @floor = init_floor rawLevel.map
34
- @man = init_man rawLevel.map
35
- @crates = []
36
- @storages = []
37
- init_crates_and_storages rawLevel.map
18
+ # @param [RawLevel] raw_level A RawLevel object, containing a title and a map.
19
+ def initialize raw_level
20
+ @title = raw_level.title
21
+ @width = raw_level.map.width
22
+ @height = raw_level.map.height
23
+ @layered_map = LayeredMap.new raw_level.map
38
24
  @move = 0
39
25
  @map = nil
40
26
  @move_recorder = MoveRecorder.new
41
27
  end
42
28
 
43
- # Two Level objects are equals if their @title, @floor, @man, @crates and @storages are equals.
29
+ def set_filename= filename
30
+ begin
31
+ full_path = File.join(RECORD_FOLDER, filename + '.yaml')
32
+ @record = Record.load_file full_path
33
+ rescue ArgumentError => e
34
+ @record = Record.create full_path
35
+ end
36
+ end
37
+
38
+ def record
39
+ @record.record_of_level @number
40
+ end
41
+
42
+ def storages
43
+ @layered_map.storages
44
+ end
45
+
46
+ def floor
47
+ @layered_map.floor
48
+ end
49
+
50
+ def man
51
+ @layered_map.man
52
+ end
53
+
54
+ def crates
55
+ @layered_map.crates
56
+ end
57
+
58
+ # Two Level objects are equals if their @title, @floor, @layered_map.man, @layered_map.crates and @layered_map.storages are equals.
44
59
  # @param [Object] obj
45
60
  # @return [false|true]
46
61
  def ==(obj)
47
62
  return false unless obj.kind_of?(Level)
48
- @floor == obj.floor and @man == obj.man and @crates == obj.crates and @storages == obj.storages and @title == obj.title
63
+ @layered_map.floor == obj.floor and @layered_map.man == obj.man and @layered_map.crates == obj.crates and @layered_map.storages == obj.storages and @title == obj.title
49
64
  end
50
65
 
51
66
  # Synonym of #==
@@ -56,12 +71,8 @@ module RSokoban
56
71
 
57
72
  # Get an instant map of the game.
58
73
  # @return [Map] the map, after X turns of game.
59
- def map
60
- @map = init_floor @floor
61
- draw_crates
62
- draw_storages
63
- draw_man
64
- @map
74
+ def map_as_array
75
+ @layered_map.map_as_array
65
76
  end
66
77
 
67
78
  # Move the man one box +direction+.
@@ -72,10 +83,10 @@ module RSokoban
72
83
  return MoveResult.new(:status => :error, :message => 'wall behind crate') if wall_behind_crate?(direction)
73
84
  return MoveResult.new(:status => :error, :message => 'double crate') if double_crate?(direction)
74
85
  @move += 1
75
- @man.send(direction)
76
- if @crates.include?(Crate.new(@man.x, @man.y))
77
- i = @crates.index(Crate.new(@man.x, @man.y))
78
- @crates[i].send(direction)
86
+ @layered_map.man.send(direction)
87
+ if @layered_map.crates.include?(Crate.new(@layered_map.man.x, @layered_map.man.y))
88
+ idx = @layered_map.crates.index(Crate.new(@layered_map.man.x, @layered_map.man.y))
89
+ @layered_map.crates[idx].send(direction)
79
90
  @move_recorder.record direction, :push
80
91
  else
81
92
  @move_recorder.record direction
@@ -95,11 +106,11 @@ module RSokoban
95
106
  when :left, :LEFT then direction = :left
96
107
  when :right, :RIGHT then direction = :right
97
108
  end
98
- @man.send(direction)
109
+ @layered_map.man.send(direction)
99
110
  @move += 1
100
- if @crates.include?(Crate.new(@man.x, @man.y))
101
- i = @crates.index(Crate.new(@man.x, @man.y))
102
- @crates[i].send(direction)
111
+ if @layered_map.crates.include?(Crate.new(@layered_map.man.x, @layered_map.man.y))
112
+ idx = @layered_map.crates.index(Crate.new(@layered_map.man.x, @layered_map.man.y))
113
+ @layered_map.crates[idx].send(direction)
103
114
  end
104
115
  rescue EmptyRedoError
105
116
  # Nothing to do
@@ -111,26 +122,26 @@ module RSokoban
111
122
  def undo
112
123
  begin
113
124
  case @move_recorder.undo
114
- when :up then @man.down
115
- when :down then @man.up
116
- when :left then @man.right
117
- when :right then @man.left
125
+ when :up then @layered_map.man.down
126
+ when :down then @layered_map.man.up
127
+ when :left then @layered_map.man.right
128
+ when :right then @layered_map.man.left
118
129
  when :UP
119
- i = @crates.index(Crate.new(@man.x, @man.y-1))
120
- @crates[i].down
121
- @man.down
130
+ idx = @layered_map.crates.index(Crate.new(@layered_map.man.x, @layered_map.man.y-1))
131
+ @layered_map.crates[idx].down
132
+ @layered_map.man.down
122
133
  when :DOWN
123
- i = @crates.index(Crate.new(@man.x, @man.y+1))
124
- @crates[i].up
125
- @man.up
134
+ idx = @layered_map.crates.index(Crate.new(@layered_map.man.x, @layered_map.man.y+1))
135
+ @layered_map.crates[idx].up
136
+ @layered_map.man.up
126
137
  when :LEFT
127
- i = @crates.index(Crate.new(@man.x-1, @man.y))
128
- @crates[i].right
129
- @man.right
138
+ idx = @layered_map.crates.index(Crate.new(@layered_map.man.x-1, @layered_map.man.y))
139
+ @layered_map.crates[idx].right
140
+ @layered_map.man.right
130
141
  when :RIGHT
131
- i = @crates.index(Crate.new(@man.x+1, @man.y))
132
- @crates[i].left
133
- @man.left
142
+ idx = @layered_map.crates.index(Crate.new(@layered_map.man.x+1, @layered_map.man.y))
143
+ @layered_map.crates[idx].left
144
+ @layered_map.man.left
134
145
  end
135
146
  @move -= 1
136
147
  rescue EmptyMoveQueueError
@@ -146,13 +157,18 @@ module RSokoban
146
157
  @move
147
158
  end
148
159
 
160
+ # @since 0.76
161
+ def update_record
162
+ @record.add @number, @move
163
+ end
164
+
149
165
  private
150
166
 
151
167
  # @return true if all crates are on a storage location
152
168
  def win?
153
- return false if @crates.size == 0 # needed for testing purpose.
154
- @crates.each {|c|
155
- return false unless @storages.include?(c)
169
+ return false if @layered_map.crates.size == 0 # needed for testing purpose.
170
+ @layered_map.crates.each {|crate|
171
+ return false unless @layered_map.storages.include?(crate)
156
172
  }
157
173
  true
158
174
  end
@@ -163,13 +179,13 @@ module RSokoban
163
179
  def wall? direction
164
180
  case direction
165
181
  when :up
166
- box = what_is_on(@man.x, @man.y-1)
182
+ box = @layered_map.what_is_on(@layered_map.man.x, @layered_map.man.y-1)
167
183
  when :down
168
- box = what_is_on(@man.x, @man.y+1)
184
+ box = @layered_map.what_is_on(@layered_map.man.x, @layered_map.man.y+1)
169
185
  when :left
170
- box = what_is_on(@man.x-1, @man.y)
186
+ box = @layered_map.what_is_on(@layered_map.man.x-1, @layered_map.man.y)
171
187
  when :right
172
- box = what_is_on(@man.x+1, @man.y)
188
+ box = @layered_map.what_is_on(@layered_map.man.x+1, @layered_map.man.y)
173
189
  end
174
190
  return(box == WALL)
175
191
  end
@@ -180,19 +196,19 @@ module RSokoban
180
196
  def wall_behind_crate?(direction)
181
197
  case direction
182
198
  when :up
183
- near = crate?(@man.x, @man.y-1)
184
- boxBehind = what_is_on(@man.x, @man.y-2)
199
+ near = crate_up?
200
+ box_behind = @layered_map.what_is_on(@layered_map.man.x, @layered_map.man.y-2)
185
201
  when :down
186
- near = crate?(@man.x, @man.y+1)
187
- boxBehind = what_is_on(@man.x, @man.y+2)
202
+ near = crate_down?
203
+ box_behind = @layered_map.what_is_on(@layered_map.man.x, @layered_map.man.y+2)
188
204
  when :left
189
- near = crate?(@man.x-1, @man.y)
190
- boxBehind = what_is_on(@man.x-2, @man.y)
205
+ near = crate_left?
206
+ box_behind = @layered_map.what_is_on(@layered_map.man.x-2, @layered_map.man.y)
191
207
  when :right
192
- near = crate?(@man.x+1, @man.y)
193
- boxBehind = what_is_on(@man.x+2, @man.y)
208
+ near = crate_right?
209
+ box_behind = @layered_map.what_is_on(@layered_map.man.x+2, @layered_map.man.y)
194
210
  end
195
- return(near and boxBehind == WALL)
211
+ near and box_behind == WALL
196
212
  end
197
213
 
198
214
  # Is there a crate followed by a crate near the man, in the direction pointed to by +direction+ ?
@@ -200,122 +216,52 @@ module RSokoban
200
216
  # @return [true|nil]
201
217
  def double_crate?(direction)
202
218
  case direction
203
- when :up
204
- true if crate?(@man.x, @man.y-1) and crate?(@man.x, @man.y-2)
205
- when :down
206
- true if crate?(@man.x, @man.y+1) and crate?(@man.x, @man.y+2)
207
- when :left
208
- true if crate?(@man.x-1, @man.y) and crate?(@man.x-2, @man.y)
209
- when :right
210
- true if crate?(@man.x+1, @man.y) and crate?(@man.x+2, @man.y)
219
+ when :up then crate_up? and crate_two_steps_up?
220
+ when :down then crate_down? and crate_two_steps_down?
221
+ when :left then crate_left? and crate_two_steps_left?
222
+ when :right then crate_right? and crate_two_steps_right?
211
223
  end
212
224
  end
213
225
 
214
- # Is there a crate ('o' or '*') in the box pointed to by x, y ?
215
- # @param [Fixnum] x x coordinate in the map
216
- # @param [Fixnum] y y coordinate in the map
226
+ # Is there a crate ('o' or '*') in the box pointed to by x_coord, y_coord ?
227
+ # @param [Fixnum] x_coord x coordinate in the map
228
+ # @param [Fixnum] y_coord y coordinate in the map
217
229
  # @return [true|false]
218
- def crate?(x, y)
219
- box = what_is_on(x, y)
230
+ def crate?(x_coord, y_coord)
231
+ box = @layered_map.what_is_on(x_coord, y_coord)
220
232
  box == CRATE or box == CRATE_ON_STORAGE
221
233
  end
222
234
 
223
- # Draw the man for map output
224
- def draw_man
225
- box = what_is_on @man.x, @man.y
226
- put_man_in_map if box == FLOOR
227
- put_man_on_storage_in_map if box == STORAGE
235
+ def crate_up?
236
+ crate?(@layered_map.man.x, @layered_map.man.y-1)
228
237
  end
229
238
 
230
- def put_man_in_map
231
- @map[@man.y][@man.x] = MAN
239
+ def crate_two_steps_up?
240
+ crate?(@layered_map.man.x, @layered_map.man.y-2)
232
241
  end
233
242
 
234
- def put_man_on_storage_in_map
235
- @map[@man.y][@man.x] = MAN_ON_STORAGE
243
+ def crate_down?
244
+ crate?(@layered_map.man.x, @layered_map.man.y+1)
236
245
  end
237
246
 
238
- # Draw the crates for map output
239
- def draw_crates
240
- @crates.each {|crate| @map[crate.y][crate.x] = what_is_on(crate.x, crate.y) }
247
+ def crate_two_steps_down?
248
+ crate?(@layered_map.man.x, @layered_map.man.y+2)
241
249
  end
242
250
 
243
- # Draw the storages location for map output
244
- def draw_storages
245
- @storages.each {|st| @map[st.y][st.x] = what_is_on(st.x, st.y) }
246
- end
247
-
248
- # Get the content of box x, y
249
- # @param [Fixnum] x x coordinate in the map
250
- # @param [Fixnum] y y coordinate in the map
251
- # @return [' ' | '#' | '.' | 'o' | '*']
252
- def what_is_on x, y
253
- box = (@floor[y][x]).chr
254
- if box == FLOOR
255
- s = Storage.new(x, y)
256
- c = Crate.new(x, y)
257
- if @storages.include?(s) and @crates.include?(c)
258
- box = CRATE_ON_STORAGE
259
- elsif @storages.include?(s)
260
- box = STORAGE
261
- elsif @crates.include?(c)
262
- box = CRATE
263
- end
264
- end
265
- box
251
+ def crate_left?
252
+ crate?(@layered_map.man.x-1, @layered_map.man.y)
266
253
  end
267
254
 
268
- # Removes all storages locations, all crates and the man, leaving only walls and floor.
269
- #
270
- # @param [Map] map
271
- # @return [Map] map with only walls and floor
272
- def init_floor map
273
- floor = []
274
- map.each {|x| floor.push x.tr("#{STORAGE}#{CRATE}#{MAN}#{CRATE_ON_STORAGE}", FLOOR) }
275
- floor
255
+ def crate_two_steps_left?
256
+ crate?(@layered_map.man.x-2, @layered_map.man.y)
276
257
  end
277
258
 
278
- # Initialize map width and map height of this level
279
- def init_dimension map
280
- @width = 0
281
- map.each {|y| @width = y.size if y.size > @width }
282
- @height = map.size
259
+ def crate_right?
260
+ crate?(@layered_map.man.x+1, @layered_map.man.y)
283
261
  end
284
262
 
285
- # Find the man's position, at the begining of the level.
286
- #
287
- # @param [Map] map
288
- # @return [Man] an initialised man
289
- def init_man map
290
- x = y = 0
291
- map.each {|line|
292
- if line.include?(MAN)
293
- x = line.index(MAN)
294
- break
295
- end
296
- y += 1
297
- }
298
- Man.new x, y
299
- end
300
-
301
- # Find position of crates and storages, at the begining of the level.
302
- #
303
- # @param [Map] map
304
- def init_crates_and_storages map
305
- y = 0
306
- map.each do |line|
307
- count = 0
308
- line.each_char do |c|
309
- @crates.push Crate.new(count, y) if c == CRATE
310
- @storages.push Storage.new(count, y) if c == STORAGE
311
- if c == CRATE_ON_STORAGE
312
- @crates.push Crate.new(count, y)
313
- @storages.push Storage.new(count, y)
314
- end
315
- count += 1
316
- end
317
- y += 1
318
- end
263
+ def crate_two_steps_right?
264
+ crate?(@layered_map.man.x+2, @layered_map.man.y)
319
265
  end
320
266
 
321
267
  end
@@ -23,17 +23,17 @@ module RSokoban
23
23
  attr_accessor :title
24
24
  # @param [String] description get/set the description of this level set.
25
25
  attr_accessor :description
26
- # @param [Array<RawLevel>] rawLevels get/set the raw levels of this set
27
- attr_accessor :rawLevels
26
+ # @param [Array<RawLevel>] raw_levels get/set the raw levels of this set
27
+ attr_accessor :raw_levels
28
28
 
29
29
  def initialize
30
30
  @title = 'Unknown set title'
31
31
  @description = 'Empty description'
32
- @rawLevels = []
32
+ @raw_levels = []
33
33
  end
34
34
 
35
35
  def size
36
- @rawLevels.size
36
+ @raw_levels.size
37
37
  end
38
38
 
39
39
  end
data/lib/rsokoban/man.rb CHANGED
@@ -6,10 +6,9 @@ module RSokoban
6
6
  class Man < Position
7
7
  include RSokoban::Moveable
8
8
 
9
- # @param [Fixnum] x la coordonnée x
10
- # @param [Fixnum] y la coordonnée y
11
- def initialize x, y
12
- super(x, y)
9
+ # Coordinates must be Fixnum
10
+ def initialize x_coord, y_coord
11
+ super(x_coord, y_coord)
13
12
  end
14
13
 
15
14
  end
data/lib/rsokoban/map.rb CHANGED
@@ -1,38 +1,60 @@
1
1
  module RSokoban
2
2
 
3
3
  # I am the map of a level.
4
+ # I am constructed from an array of strings, taken from an xsb file. I transform
5
+ # this array to equalize the length of all strings and I put a speciam mark in all
6
+ # cells that are outside of the walls.
7
+ #
8
+ # @example the first map from the original levels
9
+ # level_1 = [
10
+ # ' #####',
11
+ # ' # #',
12
+ # ' #$ #',
13
+ # ' ### $##',
14
+ # ' # $ $ #',
15
+ # '### # ## # ######',
16
+ # '# # ## ##### ..#',
17
+ # '# $ $ ..#',
18
+ # '##### ### #@## ..#',
19
+ # ' # #########',
20
+ # ' #######']
21
+ #
22
+ # map = Map.new level_1
23
+ # map.rows =>
24
+ # [
25
+ # 'oooo#####oooooooooo',
26
+ # 'oooo# #oooooooooo',
27
+ # 'oooo#$ #oooooooooo',
28
+ # 'oo### $##ooooooooo',
29
+ # 'oo# $ $ #ooooooooo',
30
+ # '### # ## #ooo######',
31
+ # '# # ## ##### ..#',
32
+ # '# $ $ ..#',
33
+ # '##### ### #@## ..#',
34
+ # 'oooo# #########',
35
+ # 'oooo#######oooooooo']
4
36
  # @since 0.73
5
37
  class Map
6
38
  # @param [Array<String>]
7
39
  attr_reader :rows
40
+
41
+ attr_reader :width
8
42
 
9
- # Construct a map from an array of strings.
10
- # @example very simple maps
11
- # map1 = Map.new['###', '#@#', '###']
12
- #
13
- # map2 = Map.new
14
- # map2.rows = ['###', '#@#', '###']
15
- #
16
43
  # @param [Array<String>]
17
44
  def initialize rows = []
45
+ raise ArgumentError unless rows.instance_of?(Array)
18
46
  @rows= rows
19
- @width = 0
47
+ transform
20
48
  end
21
49
 
22
50
  def rows=(rows)
23
- @rows = rows
24
- @width = 0
25
- @rows.each {|row| @width = row.size if row.size > @width }
51
+ initialize rows
26
52
  end
27
53
 
28
54
  def height
29
55
  @rows.size
30
56
  end
31
57
 
32
- def width
33
- @width
34
- end
35
-
36
58
  def [](num)
37
59
  @rows[num]
38
60
  end
@@ -46,6 +68,60 @@ module RSokoban
46
68
  @rows == obj.rows
47
69
  end
48
70
 
71
+ private
72
+
73
+ def transform
74
+ compute_width
75
+ mark_outside
76
+ end
77
+
78
+ def compute_width
79
+ @width = 0
80
+ @rows.each {|row| @width = row.size if row.size > @width }
81
+ end
82
+
83
+ # Mark the cells of floor which live outside of the walls, and make all rows the same size.
84
+ def mark_outside
85
+ mark_start_and_end_of_rows
86
+ mark_middle_of_rows
87
+ end
88
+
89
+ def mark_start_and_end_of_rows
90
+ (0...@rows.size).each do |num|
91
+ mark_start_of_the_row num
92
+ mark_end_of_the_row num
93
+ end
94
+ end
95
+
96
+ def mark_start_of_the_row num
97
+ first_wall = @rows[num].index(WALL)
98
+ @rows[num][0, first_wall] = OUTSIDE * first_wall
99
+ end
100
+
101
+ def mark_end_of_the_row num
102
+ @rows[num] = @rows[num] + OUTSIDE * (@width - @rows[num].size)
103
+ end
104
+
105
+ def mark_middle_of_rows
106
+ @rows.each_with_index do |row, y|
107
+ x = 0
108
+ row.each_char do |cell|
109
+ if cell == FLOOR
110
+ @rows[y][x] = OUTSIDE if neighbors_outside?(x, y)
111
+ end
112
+ x += 1
113
+ end
114
+ end
115
+ end
116
+
117
+ def neighbors_outside? x, y
118
+ neighbors_out_of_map?(y) or (@rows[y-1][x].chr == OUTSIDE) or (@rows[y+1][x].chr == OUTSIDE)
119
+ end
120
+
121
+ def neighbors_out_of_map? y
122
+ (y < 1) or (y+1 >= @rows.size)
123
+ end
124
+
49
125
  end
50
126
 
51
127
  end
@@ -13,8 +13,8 @@ module RSokoban
13
13
  @hash = hash
14
14
  end
15
15
 
16
- def [](k)
17
- @hash[k]
16
+ def [](key)
17
+ @hash[key]
18
18
  end
19
19
 
20
20
  # @return true if move is ok
@@ -67,12 +67,12 @@ class Option
67
67
  print_help_output if @options[:help_output]
68
68
  end
69
69
 
70
- def [](k)
71
- @options[k]
70
+ def [](key)
71
+ @options[key]
72
72
  end
73
73
 
74
- def interface=(k)
75
- @options[:ui] = k
74
+ def interface=(value)
75
+ @options[:ui] = value
76
76
  end
77
77
 
78
78
  private
@@ -1,10 +1,10 @@
1
1
  module RSokoban
2
2
 
3
3
  # I represent an x,y coordinate.
4
- # @todo document
5
4
  class Position
6
5
  attr_reader :x, :y
7
6
 
7
+ # Coordinates must be Fixnum
8
8
  def initialize x = 0, y = 0
9
9
  @x = x
10
10
  @y = y
@@ -2,16 +2,25 @@ module RSokoban
2
2
 
3
3
  # I figure out a level in a very simple format.
4
4
  # I have a title and a map.
5
- # @todo document and give some examples
6
5
  class RawLevel
6
+ # @return [Map]
7
7
  attr_reader :map
8
8
  attr_accessor :title
9
9
 
10
+ # If map is an array of string, it will be converted to a Map object.
11
+ # @param [String] title
12
+ # @param [Map|Array<String>] map
10
13
  def initialize title = 'Unknown level title', map = Map.new
11
14
  @title = title
12
- @map = map
15
+ if map.instance_of?(Map)
16
+ @map = map
17
+ else
18
+ @map = Map.new map
19
+ end
13
20
  end
14
21
 
22
+ # If val is an array of string, it will be converted to a Map object.
23
+ # @param [Map|Array<String>] val
15
24
  def map=(val)
16
25
  if val.kind_of?(Map)
17
26
  @map = val
@@ -0,0 +1,51 @@
1
+ require "fileutils"
2
+ require 'yaml'
3
+
4
+ module RSokoban
5
+
6
+ # I keep trace of your records !
7
+ # @since 0.76
8
+ class Record
9
+
10
+ # Load the yaml file named <tt>filename</tt>, which keep trace of all records from a
11
+ # special set of levels. <tt>filename</tt> must have the same name that the xsb file but
12
+ # with the <tt>.yaml</tt> extension.
13
+ # @param [String] filename Full path of the yaml file
14
+ # @raise ArgumentError if <tt>filename</tt> doesn't exist.
15
+ def initialize filename
16
+ raise ArgumentError unless File.exist? filename
17
+ @filename = filename
18
+ @dict = YAML.load_file(filename)
19
+ # because yaml returns nil/false if filename is empty.
20
+ @dict = {} unless @dict
21
+ end
22
+
23
+ # @see #initialize
24
+ def self.load_file filename
25
+ new filename
26
+ end
27
+
28
+ # Get the record for the level number <tt>num</tt>.
29
+ def record_of_level num
30
+ @dict[num]
31
+ end
32
+
33
+ # Create the file <tt>filename</tt> before loading it as in {#initialize}.
34
+ # @raise ArgumentError if <tt>filename</tt> exist.
35
+ def self.create filename
36
+ raise ArgumentError if File.exist? filename
37
+ FileUtils.touch filename
38
+ new filename
39
+ end
40
+
41
+ # Add or update the record for level number <tt>level_number</tt>
42
+ def add level_number, moves
43
+ @dict.merge!({level_number => moves})
44
+ f = File.new(@filename, "w")
45
+ f.write @dict.to_yaml
46
+ f.close
47
+ end
48
+
49
+ end
50
+
51
+ end