RSokoban 0.71
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/COPYING +674 -0
- data/README.rdoc +47 -0
- data/TODO +34 -0
- data/bin/rsokoban +10 -0
- data/data/microban.xsb +1838 -0
- data/data/original.xsb +1497 -0
- data/data/test.xsb +30 -0
- data/lib/rsokoban/crate.rb +16 -0
- data/lib/rsokoban/exception.rb +7 -0
- data/lib/rsokoban/game.rb +105 -0
- data/lib/rsokoban/level.rb +269 -0
- data/lib/rsokoban/level_loader.rb +58 -0
- data/lib/rsokoban/level_set.rb +31 -0
- data/lib/rsokoban/man.rb +17 -0
- data/lib/rsokoban/moveable.rb +22 -0
- data/lib/rsokoban/option.rb +93 -0
- data/lib/rsokoban/position.rb +24 -0
- data/lib/rsokoban/raw_level.rb +16 -0
- data/lib/rsokoban/storage.rb +14 -0
- data/lib/rsokoban/ui/console.rb +107 -0
- data/lib/rsokoban/ui/curses_console.rb +123 -0
- data/lib/rsokoban/ui/ui.rb +44 -0
- data/lib/rsokoban.rb +29 -0
- data/test/original.xsb +1497 -0
- data/test/tc_level.rb +773 -0
- data/test/tc_level_loader.rb +88 -0
- data/test/tc_level_set.rb +42 -0
- data/test/tc_man.rb +55 -0
- data/test/tc_moveable.rb +41 -0
- data/test/tc_position.rb +75 -0
- data/test/tc_raw_level.rb +35 -0
- data/test/test.rb +16 -0
- data/test/test_file1.xsb +9 -0
- data/test/test_file2.xsb +49 -0
- data/test/ui/tc_console.rb +75 -0
- metadata +102 -0
data/data/test.xsb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
; another test file
|
2
|
+
|
3
|
+
; 6 levels
|
4
|
+
; blablabla
|
5
|
+
; bla
|
6
|
+
|
7
|
+
#####
|
8
|
+
#.$@#
|
9
|
+
#####
|
10
|
+
; 1
|
11
|
+
; blabla
|
12
|
+
|
13
|
+
######
|
14
|
+
#. $@#
|
15
|
+
######
|
16
|
+
; 2
|
17
|
+
|
18
|
+
#####
|
19
|
+
# #
|
20
|
+
#$ #
|
21
|
+
#. @#
|
22
|
+
#####
|
23
|
+
; 3
|
24
|
+
|
25
|
+
######
|
26
|
+
#.$ #
|
27
|
+
#.$ #
|
28
|
+
#.$ @#
|
29
|
+
######
|
30
|
+
; 4
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "rsokoban/moveable"
|
2
|
+
|
3
|
+
module RSokoban
|
4
|
+
|
5
|
+
# I am a moveable crate.
|
6
|
+
class Crate < Position
|
7
|
+
include RSokoban::Moveable
|
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)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module RSokoban
|
2
|
+
|
3
|
+
# I am mostly the game loop.
|
4
|
+
class Game
|
5
|
+
|
6
|
+
# Construct a new game that you can later run.
|
7
|
+
# @param [:curses|:portable] ui_as_symbol the user interface for the game
|
8
|
+
def initialize ui_as_symbol
|
9
|
+
@levelLoader = LevelLoader.new "original.xsb"
|
10
|
+
@levelNumber = 1
|
11
|
+
case ui_as_symbol
|
12
|
+
when :curses
|
13
|
+
require "rsokoban/ui/curses_console"
|
14
|
+
@ui = UI::CursesConsole.new
|
15
|
+
when :portable
|
16
|
+
require "rsokoban/ui/console"
|
17
|
+
@ui = UI::Console.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# I am the game loop.
|
22
|
+
def run
|
23
|
+
action = start_level
|
24
|
+
loop do
|
25
|
+
if action.is_a?(Fixnum)
|
26
|
+
action = load_level action
|
27
|
+
next
|
28
|
+
elsif action.instance_of?(String)
|
29
|
+
# Assuming we recieve a filename of level's set to load
|
30
|
+
action = load_a_new_set action
|
31
|
+
next
|
32
|
+
elsif action == :quit
|
33
|
+
break
|
34
|
+
elsif action == :next
|
35
|
+
action = next_level
|
36
|
+
next
|
37
|
+
elsif action == :retry
|
38
|
+
action = try_again
|
39
|
+
next
|
40
|
+
elsif [:down, :up, :left, :right].include?(action)
|
41
|
+
result = @level.move(action)
|
42
|
+
end
|
43
|
+
|
44
|
+
if result.start_with?('WIN')
|
45
|
+
action = @ui.get_action('WIN', @level.picture, result)
|
46
|
+
else
|
47
|
+
action = @ui.get_action('DISPLAY', @level.picture, result)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# Load and start the next level of the set
|
55
|
+
# @return [Object] the user's {action}[Console#get_action]
|
56
|
+
def next_level
|
57
|
+
@levelNumber += 1
|
58
|
+
start_level
|
59
|
+
end
|
60
|
+
|
61
|
+
# Restart the current level
|
62
|
+
# @return [Object] the user's {action}[Console#get_action]
|
63
|
+
def try_again
|
64
|
+
start_level
|
65
|
+
end
|
66
|
+
|
67
|
+
# Load a new set of levels and start its first level.
|
68
|
+
# @param [String] setname the name of the set (with .xsb extension)
|
69
|
+
# @return [Object] the user's {action}[Console#get_action]
|
70
|
+
def load_a_new_set setname
|
71
|
+
begin
|
72
|
+
@levelLoader = LevelLoader.new setname
|
73
|
+
@levelNumber = 1
|
74
|
+
@level = @levelLoader.level(@levelNumber)
|
75
|
+
message = "Level : #{@level.title}"
|
76
|
+
rescue NoFileError
|
77
|
+
message = "Error, no such file : #{setname}"
|
78
|
+
end
|
79
|
+
@ui.get_action('START', @level.picture, message)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Load a level from the current set.
|
83
|
+
# @param [Fixnum] num the number of the set (base 1)
|
84
|
+
# @return [Object] the user's {action}[Console#get_action]
|
85
|
+
def load_level num
|
86
|
+
@levelNumber = num
|
87
|
+
start_level
|
88
|
+
end
|
89
|
+
|
90
|
+
# Start a level, according to some instance members.
|
91
|
+
# @return [Object] the user's {action}[Console#get_action]
|
92
|
+
def start_level
|
93
|
+
begin
|
94
|
+
@level = @levelLoader.level(@levelNumber)
|
95
|
+
@ui.get_action('START', @level.picture, "Level : #{@level.title}")
|
96
|
+
rescue LevelNumberTooHighError
|
97
|
+
@ui.get_action('END_OF_SET', ['####'], "No more levels in this set")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
|
105
|
+
end
|
@@ -0,0 +1,269 @@
|
|
1
|
+
module RSokoban
|
2
|
+
|
3
|
+
# I am a level of the game.
|
4
|
+
# To complete a level, place each crate ('$') on a storage location ('.').
|
5
|
+
class Level
|
6
|
+
attr_reader :floor, :man, :crates, :storages, :title
|
7
|
+
|
8
|
+
# I build the level from a RawLevel object.
|
9
|
+
# @example a RawLevel object
|
10
|
+
# A RawLevel object have got one title and one 'picture'. A 'picture' is an array of string.
|
11
|
+
# Each string contain one line of the level map.
|
12
|
+
# 'Level 1', ['#####', '#.o@#', '#####']
|
13
|
+
#
|
14
|
+
# * '#' is a wal
|
15
|
+
# * '.' is a storage location
|
16
|
+
# * '$' is a crate
|
17
|
+
# * '*' is a crate on storage location
|
18
|
+
# * '@' is the man
|
19
|
+
# * ' ' is an empty floor
|
20
|
+
#
|
21
|
+
# @param [RawLevel] rawLevel
|
22
|
+
def initialize rawLevel
|
23
|
+
@title = rawLevel.title
|
24
|
+
@floor = init_floor rawLevel.picture
|
25
|
+
@man = init_man rawLevel.picture
|
26
|
+
@crates = []
|
27
|
+
@storages = []
|
28
|
+
init_crates_and_storages rawLevel.picture
|
29
|
+
@move = 0
|
30
|
+
@picture = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
# Two Level objects are equals if their @title, @floor, @man, @crates and @storages are equals.
|
34
|
+
# @param [Object] obj
|
35
|
+
# @return [false|true]
|
36
|
+
def ==(obj)
|
37
|
+
return false unless obj.kind_of?(Level)
|
38
|
+
@floor == obj.floor and @man == obj.man and @crates == obj.crates and @storages == obj.storages and @title == obj.title
|
39
|
+
end
|
40
|
+
|
41
|
+
# Synonym of #==
|
42
|
+
# @see #==
|
43
|
+
def eql?(obj)
|
44
|
+
self == obj
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get an instant picture of the game.
|
48
|
+
# @return [Array<String>] the picture, after X turns of game.
|
49
|
+
def picture
|
50
|
+
@picture = init_floor @floor
|
51
|
+
draw_crates
|
52
|
+
draw_storages
|
53
|
+
draw_man
|
54
|
+
@picture
|
55
|
+
end
|
56
|
+
|
57
|
+
# Move the man one box up.
|
58
|
+
# @return [String] the move's result
|
59
|
+
# ["ERROR wall"] if the player is stopped by a wall
|
60
|
+
# ['ERROR wall behind crate'] si le joueur est stoppé par une caisse suivie d'un mur
|
61
|
+
# ['ERROR double crate'] if the player is stopped by a crate followed by a wall
|
62
|
+
# ['OK move ?'] if the move is accepted (? is replaced by the number of the move)
|
63
|
+
# ['WIN move ?'] if the level is completed (? is replaced by the number of the move)
|
64
|
+
def moveUp
|
65
|
+
move :up
|
66
|
+
end
|
67
|
+
|
68
|
+
# Move the man one box down.
|
69
|
+
# @see #moveUp for more explanation
|
70
|
+
def moveDown
|
71
|
+
move :down
|
72
|
+
end
|
73
|
+
|
74
|
+
# Move the man one box left.
|
75
|
+
# @see #moveUp for more explanation
|
76
|
+
def moveLeft
|
77
|
+
move :left
|
78
|
+
end
|
79
|
+
|
80
|
+
# Move the man one box right.
|
81
|
+
# @see #moveUp for more explanation
|
82
|
+
def moveRight
|
83
|
+
move :right
|
84
|
+
end
|
85
|
+
|
86
|
+
# Move the man one box +direction+.
|
87
|
+
# @see #moveUp for more explanation
|
88
|
+
def move direction
|
89
|
+
return 'ERROR wall' if wall?(direction)
|
90
|
+
return 'ERROR wall behind crate' if wall_behind_crate?(direction)
|
91
|
+
return 'ERROR double crate' if double_crate?(direction)
|
92
|
+
@move += 1
|
93
|
+
@man.send(direction)
|
94
|
+
if @crates.include?(Crate.new(@man.x, @man.y))
|
95
|
+
i = @crates.index(Crate.new(@man.x, @man.y))
|
96
|
+
@crates[i].send(direction)
|
97
|
+
end
|
98
|
+
return "WIN move #{@move}" if win?
|
99
|
+
"OK move #{@move}"
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
# @return true if all crates are on a storage location
|
105
|
+
def win?
|
106
|
+
return false if @crates.size == 0 # needed for testing purpose.
|
107
|
+
@crates.each {|c|
|
108
|
+
return false unless @storages.include?(c)
|
109
|
+
}
|
110
|
+
true
|
111
|
+
end
|
112
|
+
|
113
|
+
# Is there a wall near the man, in the direction pointed to by +direction+ ?
|
114
|
+
# @param [:up|:down|:left|:right] direction
|
115
|
+
# @return [true|false]
|
116
|
+
def wall? direction
|
117
|
+
case direction
|
118
|
+
when :up
|
119
|
+
box = what_is_on(@man.x, @man.y-1)
|
120
|
+
when :down
|
121
|
+
box = what_is_on(@man.x, @man.y+1)
|
122
|
+
when :left
|
123
|
+
box = what_is_on(@man.x-1, @man.y)
|
124
|
+
when :right
|
125
|
+
box = what_is_on(@man.x+1, @man.y)
|
126
|
+
end
|
127
|
+
return(box == WALL)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Is there a crate followed by a wall near the man, in the direction pointed to by +direction+ ?
|
131
|
+
# @param [:up|:down|:left|:right] direction
|
132
|
+
# @return [true|false]
|
133
|
+
def wall_behind_crate?(direction)
|
134
|
+
case direction
|
135
|
+
when :up
|
136
|
+
near = crate?(@man.x, @man.y-1)
|
137
|
+
boxBehind = what_is_on(@man.x, @man.y-2)
|
138
|
+
when :down
|
139
|
+
near = crate?(@man.x, @man.y+1)
|
140
|
+
boxBehind = what_is_on(@man.x, @man.y+2)
|
141
|
+
when :left
|
142
|
+
near = crate?(@man.x-1, @man.y)
|
143
|
+
boxBehind = what_is_on(@man.x-2, @man.y)
|
144
|
+
when :right
|
145
|
+
near = crate?(@man.x+1, @man.y)
|
146
|
+
boxBehind = what_is_on(@man.x+2, @man.y)
|
147
|
+
end
|
148
|
+
return(near and boxBehind == WALL)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Is there a crate followed by a crate near the man, in the direction pointed to by +direction+ ?
|
152
|
+
# @param [:up|:down|:left|:right] direction
|
153
|
+
# @return [true|nil]
|
154
|
+
def double_crate?(direction)
|
155
|
+
case direction
|
156
|
+
when :up
|
157
|
+
true if crate?(@man.x, @man.y-1) and crate?(@man.x, @man.y-2)
|
158
|
+
when :down
|
159
|
+
true if crate?(@man.x, @man.y+1) and crate?(@man.x, @man.y+2)
|
160
|
+
when :left
|
161
|
+
true if crate?(@man.x-1, @man.y) and crate?(@man.x-2, @man.y)
|
162
|
+
when :right
|
163
|
+
true if crate?(@man.x+1, @man.y) and crate?(@man.x+2, @man.y)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Is there a crate ('o' or '*') in the box pointed to by x, y ?
|
168
|
+
# @param [Fixnum] x x coordinate in the map
|
169
|
+
# @param [Fixnum] y y coordinate in the map
|
170
|
+
# @return [true|false]
|
171
|
+
def crate?(x, y)
|
172
|
+
box = what_is_on(x, y)
|
173
|
+
box == CRATE or box == CRATE_ON_STORAGE
|
174
|
+
end
|
175
|
+
|
176
|
+
# Draw the man for @picture output
|
177
|
+
def draw_man
|
178
|
+
box = what_is_on @man.x, @man.y
|
179
|
+
put_man_in_picture if box == FLOOR
|
180
|
+
put_man_on_storage_in_picture if box == STORAGE
|
181
|
+
end
|
182
|
+
|
183
|
+
def put_man_in_picture
|
184
|
+
@picture[@man.y][@man.x] = MAN
|
185
|
+
end
|
186
|
+
|
187
|
+
def put_man_on_storage_in_picture
|
188
|
+
@picture[@man.y][@man.x] = MAN_ON_STORAGE
|
189
|
+
end
|
190
|
+
|
191
|
+
# Draw the crates for @picture output
|
192
|
+
def draw_crates
|
193
|
+
@crates.each {|crate| @picture[crate.y][crate.x] = what_is_on(crate.x, crate.y) }
|
194
|
+
end
|
195
|
+
|
196
|
+
# Draw the storages location for @picture output
|
197
|
+
def draw_storages
|
198
|
+
@storages.each {|st| @picture[st.y][st.x] = what_is_on(st.x, st.y) }
|
199
|
+
end
|
200
|
+
|
201
|
+
# Get the content of box x, y
|
202
|
+
# @param [Fixnum] x x coordinate in the map
|
203
|
+
# @param [Fixnum] y y coordinate in the map
|
204
|
+
# @return [' ' | '#' | '.' | 'o' | '*']
|
205
|
+
def what_is_on x, y
|
206
|
+
box = (@floor[y][x]).chr
|
207
|
+
if box == FLOOR
|
208
|
+
s = Storage.new(x, y)
|
209
|
+
c = Crate.new(x, y)
|
210
|
+
if @storages.include?(s) and @crates.include?(c)
|
211
|
+
box = CRATE_ON_STORAGE
|
212
|
+
elsif @storages.include?(s)
|
213
|
+
box = STORAGE
|
214
|
+
elsif @crates.include?(c)
|
215
|
+
box = CRATE
|
216
|
+
end
|
217
|
+
end
|
218
|
+
box
|
219
|
+
end
|
220
|
+
|
221
|
+
# Removes all storages locations, all crates and the man, leaving only walls and floor.
|
222
|
+
#
|
223
|
+
# @param [Array<String>] picture
|
224
|
+
# @return [Array<String>] picture with only walls and floor
|
225
|
+
def init_floor picture
|
226
|
+
floor = []
|
227
|
+
picture.each {|x| floor.push x.tr("#{STORAGE}#{CRATE}#{MAN}#{CRATE_ON_STORAGE}", FLOOR) }
|
228
|
+
floor
|
229
|
+
end
|
230
|
+
|
231
|
+
# Find the man's position, at the begining of the level.
|
232
|
+
#
|
233
|
+
# @param [Array<String>] picture
|
234
|
+
# @return [Man] an initialised man
|
235
|
+
def init_man picture
|
236
|
+
x = y = 0
|
237
|
+
picture.each {|line|
|
238
|
+
if line.include?(MAN)
|
239
|
+
x = line.index(MAN)
|
240
|
+
break
|
241
|
+
end
|
242
|
+
y += 1
|
243
|
+
}
|
244
|
+
Man.new x, y
|
245
|
+
end
|
246
|
+
|
247
|
+
# Find position of crates and storages, at the begining of the level.
|
248
|
+
#
|
249
|
+
# @param [Array<String>] picture
|
250
|
+
def init_crates_and_storages picture
|
251
|
+
y = 0
|
252
|
+
picture.each do |line|
|
253
|
+
count = 0
|
254
|
+
line.each_char do |c|
|
255
|
+
@crates.push Crate.new(count, y) if c == CRATE
|
256
|
+
@storages.push Storage.new(count, y) if c == STORAGE
|
257
|
+
if c == CRATE_ON_STORAGE
|
258
|
+
@crates.push Crate.new(count, y)
|
259
|
+
@storages.push Storage.new(count, y)
|
260
|
+
end
|
261
|
+
count += 1
|
262
|
+
end
|
263
|
+
y += 1
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module RSokoban
|
2
|
+
|
3
|
+
# Je charge le fichier contenant les niveaux du jeu. À ma création, vous m'indiquez un fichier
|
4
|
+
# au format xsb contenant un ou plusieurs niveaux.
|
5
|
+
# Vous pouvez ensuite me demander un niveau particulier (un objet Level).
|
6
|
+
# @todo translate, document and refactor
|
7
|
+
class LevelLoader
|
8
|
+
attr_reader :level, :set
|
9
|
+
|
10
|
+
# @filename [String] le nom du fichier où trouver les niveaux.
|
11
|
+
# Ce fichier est cherché dans le dossier data/.
|
12
|
+
# @throws [RSokoban::NoFileError] si le fichier n'existe pas
|
13
|
+
# @see LevelSet for an overview of .xsb file format
|
14
|
+
def initialize filename
|
15
|
+
filename = "#{$RSOKOBAN_DATA_PATH}/" + filename
|
16
|
+
raise NoFileError unless File.exist?(filename)
|
17
|
+
@set = LevelSet.new
|
18
|
+
file = open(filename)
|
19
|
+
@set.title = file.readline.chomp.sub(/;/, '').strip
|
20
|
+
file.readline # must be blank line
|
21
|
+
line = file.readline
|
22
|
+
desc = ''
|
23
|
+
while line[0] == ?; do
|
24
|
+
desc += line.sub(/;/, '').sub(/\s*/, '')
|
25
|
+
line = file.readline
|
26
|
+
end
|
27
|
+
@set.description = desc
|
28
|
+
|
29
|
+
loop do
|
30
|
+
begin
|
31
|
+
line = file.readline.chomp
|
32
|
+
raw = []
|
33
|
+
while line =~ /^ *#/
|
34
|
+
raw.push line
|
35
|
+
line = file.readline.chomp
|
36
|
+
end
|
37
|
+
line = line.chomp.sub(/;/, '').sub(/\s*/, '')
|
38
|
+
@set.rawLevels.push RawLevel.new(line, raw)
|
39
|
+
|
40
|
+
line = file.readline
|
41
|
+
while line[0, 1] == ';'
|
42
|
+
line = file.readline
|
43
|
+
end
|
44
|
+
# must be a blank line here
|
45
|
+
rescue EOFError
|
46
|
+
break
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def level num
|
52
|
+
raise LevelNumberTooHighError if num > @set.rawLevels.size
|
53
|
+
Level.new @set.rawLevels[num-1]
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module RSokoban
|
2
|
+
|
3
|
+
# I am a set of sokoban levels.
|
4
|
+
# Level set are found in .xsb files.
|
5
|
+
#
|
6
|
+
# =xsb file format
|
7
|
+
#
|
8
|
+
# Info lines begins with semi-colon (;)
|
9
|
+
# Map lines begins with a # (that's a wall !)
|
10
|
+
#
|
11
|
+
# 1: First info is title of the set
|
12
|
+
# 2: Blank line
|
13
|
+
# 3: List of info lines : description
|
14
|
+
# 4: Blank line
|
15
|
+
# 5: Level map
|
16
|
+
# 6: info title of this level
|
17
|
+
# 7: List of info lines : blabla about this level
|
18
|
+
#
|
19
|
+
# From 4 to 7 again for each supplementary level
|
20
|
+
class LevelSet
|
21
|
+
attr_accessor :title, :description, :rawLevels
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@title = 'Unknown set title'
|
25
|
+
@description = 'Empty description'
|
26
|
+
@rawLevels = []
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
data/lib/rsokoban/man.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "rsokoban/moveable"
|
2
|
+
|
3
|
+
module RSokoban
|
4
|
+
|
5
|
+
# Je suis le bonhomme qui pousse les caisses...
|
6
|
+
class Man < Position
|
7
|
+
include RSokoban::Moveable
|
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)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
# I parse the command line.
|
4
|
+
class Option
|
5
|
+
|
6
|
+
# Here is a list of command line options :
|
7
|
+
# * --curses
|
8
|
+
# * --version
|
9
|
+
# * --license
|
10
|
+
# * --help
|
11
|
+
# * --help-output
|
12
|
+
# * --portable
|
13
|
+
# @todo refactoring
|
14
|
+
def initialize
|
15
|
+
@options = {:ui => :curses}
|
16
|
+
|
17
|
+
optparse = OptionParser.new do|opts|
|
18
|
+
opts.banner = "Usage: #{$0} [options]"
|
19
|
+
|
20
|
+
opts.on( '-c', '--curses', 'Use curses console for user interface (default)' ) do
|
21
|
+
@options[:ui] = :curses
|
22
|
+
end
|
23
|
+
|
24
|
+
@options[:help_output] = false
|
25
|
+
opts.on( '-o', '--help-output', 'Print help on output options and exit' ) do
|
26
|
+
@options[:help_output] = true
|
27
|
+
end
|
28
|
+
|
29
|
+
@options[:license] = false
|
30
|
+
opts.on( '-l', '--license', 'Print program\'s license and exit' ) do
|
31
|
+
@options[:license] = true
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on( '-p', '--portable', 'Use standard console for user interface' ) do
|
35
|
+
@options[:ui] = :portable
|
36
|
+
end
|
37
|
+
|
38
|
+
@options[:version] = false
|
39
|
+
opts.on( '-v', '--version', 'Print version number and exit' ) do
|
40
|
+
@options[:version] = true
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on( '-h', '--help', 'Display this screen' ) do
|
44
|
+
puts opts
|
45
|
+
exit
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
begin
|
50
|
+
optparse.parse!
|
51
|
+
rescue OptionParser::InvalidOption => e
|
52
|
+
puts e.to_s
|
53
|
+
exit 1
|
54
|
+
end
|
55
|
+
|
56
|
+
print_version if @options[:version]
|
57
|
+
print_license if @options[:license]
|
58
|
+
print_help_output if @options[:help_output]
|
59
|
+
end
|
60
|
+
|
61
|
+
def [](k)
|
62
|
+
@options[k]
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def print_version
|
68
|
+
puts RSokoban::VERSION
|
69
|
+
exit
|
70
|
+
end
|
71
|
+
|
72
|
+
def print_license
|
73
|
+
puts "RSokoban is licensed under the GPL 3. See the COPYING's file."
|
74
|
+
exit
|
75
|
+
end
|
76
|
+
|
77
|
+
def print_help_output
|
78
|
+
help=<<EOS
|
79
|
+
|
80
|
+
RSokoban can use 2 user interfaces :
|
81
|
+
|
82
|
+
--curses
|
83
|
+
This is the default UI. It uses the curses library in a console window.
|
84
|
+
It works on Linux. I don't know if it works on Windows or OSX.
|
85
|
+
|
86
|
+
--portable
|
87
|
+
It uses a plain console window. This UI is boring but should work
|
88
|
+
everywhere.
|
89
|
+
EOS
|
90
|
+
puts help
|
91
|
+
exit
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module RSokoban
|
2
|
+
|
3
|
+
# I represent an x,y coordinate.
|
4
|
+
# @todo document
|
5
|
+
class Position
|
6
|
+
attr_reader :x, :y
|
7
|
+
|
8
|
+
def initialize x = 0, y = 0
|
9
|
+
@x = x
|
10
|
+
@y = y
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(obj)
|
14
|
+
return false unless obj.kind_of?(Position)
|
15
|
+
@x == obj.x and @y == obj.y
|
16
|
+
end
|
17
|
+
|
18
|
+
def eql?(obj)
|
19
|
+
self == obj
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module RSokoban
|
2
|
+
|
3
|
+
# I figure out a level in a very simple format.
|
4
|
+
# I have a title and an array of string (named +picture+), representing the map.
|
5
|
+
# @todo document and give some examples
|
6
|
+
class RawLevel
|
7
|
+
attr_accessor :title, :picture
|
8
|
+
|
9
|
+
def initialize title = 'Unknown level title', picture = []
|
10
|
+
@title = title
|
11
|
+
@picture = picture
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module RSokoban
|
2
|
+
|
3
|
+
# Je suis un emplacement de stockage, c'est à dire un endroit où il faut placer une caisse
|
4
|
+
# pour gagner le niveau.
|
5
|
+
class Storage < Position
|
6
|
+
|
7
|
+
# @param [Fixnum] x la coordonnée x
|
8
|
+
# @param [Fixnum] y la coordonnée y
|
9
|
+
def initialize x, y
|
10
|
+
super(x, y)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|