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.
- data/NEWS +33 -1
- data/README.rdoc +21 -14
- data/TODO +6 -12
- data/VERSION +1 -1
- data/bin/rsokoban +40 -3
- data/data/big.xsb +35 -0
- data/lib/rsokoban/config.rb +34 -0
- data/lib/rsokoban/crate.rb +4 -5
- data/lib/rsokoban/exception.rb +2 -0
- data/lib/rsokoban/game/game.rb +153 -0
- data/lib/rsokoban/game/game_curses.rb +11 -0
- data/lib/rsokoban/game/game_factory.rb +23 -0
- data/lib/rsokoban/game/game_gui.rb +27 -0
- data/lib/rsokoban/game/game_portable.rb +10 -0
- data/lib/rsokoban/game/game_tk.rb +11 -0
- data/lib/rsokoban/game/game_ui.rb +81 -0
- data/lib/rsokoban/game.rb +7 -238
- data/lib/rsokoban/install.rb +22 -0
- data/lib/rsokoban/layered_map.rb +139 -0
- data/lib/rsokoban/level.rb +112 -166
- data/lib/rsokoban/level_set.rb +4 -4
- data/lib/rsokoban/man.rb +3 -4
- data/lib/rsokoban/map.rb +91 -15
- data/lib/rsokoban/move_result.rb +2 -2
- data/lib/rsokoban/option.rb +4 -4
- data/lib/rsokoban/position.rb +1 -1
- data/lib/rsokoban/raw_level.rb +11 -2
- data/lib/rsokoban/record.rb +51 -0
- data/lib/rsokoban/set_loader.rb +108 -0
- data/lib/rsokoban/ui/base_ui.rb +1 -0
- data/lib/rsokoban/ui/console.rb +56 -25
- data/lib/rsokoban/ui/curses_console.rb +59 -33
- data/lib/rsokoban/ui/player_action.rb +13 -7
- data/lib/rsokoban/ui/skin.rb +105 -0
- data/lib/rsokoban/ui/tk_dialogs.rb +104 -18
- data/lib/rsokoban/ui/tk_ui.rb +410 -233
- data/lib/rsokoban/ui.rb +1 -0
- data/lib/rsokoban.rb +14 -2
- data/skins/AntiqueDesk/crate.bmp +0 -0
- data/skins/AntiqueDesk/crate_store.bmp +0 -0
- data/skins/AntiqueDesk/floor.bmp +0 -0
- data/skins/AntiqueDesk/man_down.bmp +0 -0
- data/skins/AntiqueDesk/man_left.bmp +0 -0
- data/skins/AntiqueDesk/man_right.bmp +0 -0
- data/skins/AntiqueDesk/man_store_down.bmp +0 -0
- data/skins/AntiqueDesk/man_store_left.bmp +0 -0
- data/skins/AntiqueDesk/man_store_right.bmp +0 -0
- data/skins/AntiqueDesk/man_store_up.bmp +0 -0
- data/skins/AntiqueDesk/man_up.bmp +0 -0
- data/skins/AntiqueDesk/outside.bmp +0 -0
- data/skins/AntiqueDesk/skin.conf +3 -0
- data/skins/AntiqueDesk/store.bmp +0 -0
- data/skins/AntiqueDesk/wall.bmp +0 -0
- data/skins/BlueGranite/outside.bmp +0 -0
- data/skins/BlueGranite/skin.conf +3 -0
- data/skins/HeavyMetal/crate.bmp +0 -0
- data/skins/HeavyMetal/crate_store.bmp +0 -0
- data/skins/HeavyMetal/floor.bmp +0 -0
- data/skins/HeavyMetal/man.bmp +0 -0
- data/skins/HeavyMetal/man_store.bmp +0 -0
- data/skins/HeavyMetal/outside.bmp +0 -0
- data/skins/HeavyMetal/skin.conf +3 -0
- data/skins/HeavyMetal/store.bmp +0 -0
- data/skins/HeavyMetal/wall.bmp +0 -0
- data/skins/HeavyMetal/wall_d.bmp +0 -0
- data/skins/HeavyMetal/wall_dl.bmp +0 -0
- data/skins/HeavyMetal/wall_dlr.bmp +0 -0
- data/skins/HeavyMetal/wall_dr.bmp +0 -0
- data/skins/HeavyMetal/wall_l.bmp +0 -0
- data/skins/HeavyMetal/wall_lr.bmp +0 -0
- data/skins/HeavyMetal/wall_r.bmp +0 -0
- data/skins/HeavyMetal/wall_u.bmp +0 -0
- data/skins/HeavyMetal/wall_ud.bmp +0 -0
- data/skins/HeavyMetal/wall_udl.bmp +0 -0
- data/skins/HeavyMetal/wall_udlr.bmp +0 -0
- data/skins/HeavyMetal/wall_udr.bmp +0 -0
- data/skins/HeavyMetal/wall_ul.bmp +0 -0
- data/skins/HeavyMetal/wall_ulr.bmp +0 -0
- data/skins/HeavyMetal/wall_ur.bmp +0 -0
- data/test/record/original.yaml +2 -0
- data/test/tc_game.rb +24 -15
- data/test/tc_game_factory.rb +40 -0
- data/test/tc_game_gui.rb +26 -0
- data/test/tc_game_ui.rb +153 -0
- data/test/tc_install.rb +12 -0
- data/test/tc_layered_map.rb +105 -0
- data/test/tc_level.rb +109 -107
- data/test/tc_level_set.rb +4 -4
- data/test/tc_map.rb +46 -10
- data/test/tc_record.rb +100 -0
- data/test/{tc_level_loader.rb → tc_set_loader.rb} +25 -24
- data/test/test.rb +9 -2
- data/test/ui/tc_skin.rb +71 -0
- metadata +89 -26
- data/lib/rsokoban/level_loader.rb +0 -81
- data/lib/rsokoban/ui/tk_box.rb +0 -21
- data/skins/default/outside.bmp +0 -0
- data/skins/default/readme +0 -1
- /data/skins/{default → BlueGranite}/crate.bmp +0 -0
- /data/skins/{default → BlueGranite}/crate_store.bmp +0 -0
- /data/skins/{default → BlueGranite}/floor.bmp +0 -0
- /data/skins/{default → BlueGranite}/man_down.bmp +0 -0
- /data/skins/{default → BlueGranite}/man_left.bmp +0 -0
- /data/skins/{default → BlueGranite}/man_right.bmp +0 -0
- /data/skins/{default → BlueGranite}/man_store_down.bmp +0 -0
- /data/skins/{default → BlueGranite}/man_store_left.bmp +0 -0
- /data/skins/{default → BlueGranite}/man_store_right.bmp +0 -0
- /data/skins/{default → BlueGranite}/man_store_up.bmp +0 -0
- /data/skins/{default → BlueGranite}/man_up.bmp +0 -0
- /data/skins/{default → BlueGranite}/store.bmp +0 -0
- /data/skins/{default → BlueGranite}/wall.bmp +0 -0
data/lib/rsokoban/level.rb
CHANGED
@@ -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 :
|
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
|
-
# @
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
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
|
60
|
-
@
|
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
|
-
|
78
|
-
@crates[
|
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
|
-
|
102
|
-
@crates[
|
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
|
-
|
120
|
-
@crates[
|
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
|
-
|
124
|
-
@crates[
|
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
|
-
|
128
|
-
@crates[
|
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
|
-
|
132
|
-
@crates[
|
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 {|
|
155
|
-
return false unless @storages.include?(
|
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 =
|
184
|
-
|
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 =
|
187
|
-
|
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 =
|
190
|
-
|
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 =
|
193
|
-
|
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
|
-
|
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
|
-
|
205
|
-
when :
|
206
|
-
|
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
|
215
|
-
# @param [Fixnum]
|
216
|
-
# @param [Fixnum]
|
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?(
|
219
|
-
box = what_is_on(
|
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
|
-
|
224
|
-
|
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
|
231
|
-
@
|
239
|
+
def crate_two_steps_up?
|
240
|
+
crate?(@layered_map.man.x, @layered_map.man.y-2)
|
232
241
|
end
|
233
242
|
|
234
|
-
def
|
235
|
-
@
|
243
|
+
def crate_down?
|
244
|
+
crate?(@layered_map.man.x, @layered_map.man.y+1)
|
236
245
|
end
|
237
246
|
|
238
|
-
|
239
|
-
|
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
|
-
|
244
|
-
|
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
|
-
|
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
|
-
|
279
|
-
|
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
|
-
|
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
|
data/lib/rsokoban/level_set.rb
CHANGED
@@ -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>]
|
27
|
-
attr_accessor :
|
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
|
-
@
|
32
|
+
@raw_levels = []
|
33
33
|
end
|
34
34
|
|
35
35
|
def size
|
36
|
-
@
|
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
|
-
#
|
10
|
-
|
11
|
-
|
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
|
-
|
47
|
+
transform
|
20
48
|
end
|
21
49
|
|
22
50
|
def rows=(rows)
|
23
|
-
|
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
|
data/lib/rsokoban/move_result.rb
CHANGED
data/lib/rsokoban/option.rb
CHANGED
@@ -67,12 +67,12 @@ class Option
|
|
67
67
|
print_help_output if @options[:help_output]
|
68
68
|
end
|
69
69
|
|
70
|
-
def [](
|
71
|
-
@options[
|
70
|
+
def [](key)
|
71
|
+
@options[key]
|
72
72
|
end
|
73
73
|
|
74
|
-
def interface=(
|
75
|
-
@options[:ui] =
|
74
|
+
def interface=(value)
|
75
|
+
@options[:ui] = value
|
76
76
|
end
|
77
77
|
|
78
78
|
private
|
data/lib/rsokoban/position.rb
CHANGED
data/lib/rsokoban/raw_level.rb
CHANGED
@@ -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
|
-
|
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
|