RSokoban 0.71
Sign up to get free protection for your applications and to get access to all the features.
- 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
|