RSokoban 0.71 → 0.73

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 (50) hide show
  1. data/NEWS +22 -0
  2. data/README.rdoc +19 -19
  3. data/RSokoban-0.73.gem +0 -0
  4. data/TODO +25 -18
  5. data/VERSION +1 -0
  6. data/bin/rsokoban +65 -1
  7. data/lib/rsokoban/exception.rb +4 -0
  8. data/lib/rsokoban/game.rb +42 -29
  9. data/lib/rsokoban/level.rb +67 -30
  10. data/lib/rsokoban/level_loader.rb +4 -4
  11. data/lib/rsokoban/level_set.rb +20 -10
  12. data/lib/rsokoban/map.rb +51 -0
  13. data/lib/rsokoban/move_recorder.rb +38 -0
  14. data/lib/rsokoban/option.rb +29 -12
  15. data/lib/rsokoban/raw_level.rb +15 -4
  16. data/lib/rsokoban/ui/base_ui.rb +37 -0
  17. data/lib/rsokoban/ui/console.rb +43 -33
  18. data/lib/rsokoban/ui/curses_console.rb +45 -36
  19. data/lib/rsokoban/ui/player_action.rb +74 -0
  20. data/lib/rsokoban/ui/tk_ui.rb +474 -0
  21. data/lib/rsokoban/ui.rb +10 -0
  22. data/lib/rsokoban.rb +4 -5
  23. data/skins/default/crate.bmp +0 -0
  24. data/skins/default/crate_store.bmp +0 -0
  25. data/skins/default/floor.bmp +0 -0
  26. data/skins/default/man_down.bmp +0 -0
  27. data/skins/default/man_left.bmp +0 -0
  28. data/skins/default/man_right.bmp +0 -0
  29. data/skins/default/man_store_down.bmp +0 -0
  30. data/skins/default/man_store_left.bmp +0 -0
  31. data/skins/default/man_store_right.bmp +0 -0
  32. data/skins/default/man_store_up.bmp +0 -0
  33. data/skins/default/man_up.bmp +0 -0
  34. data/skins/default/outside.bmp +0 -0
  35. data/skins/default/readme +1 -0
  36. data/skins/default/store.bmp +0 -0
  37. data/skins/default/wall.bmp +0 -0
  38. data/test/original.xsb +0 -2
  39. data/test/tc_level.rb +37 -37
  40. data/test/tc_level_loader.rb +14 -2
  41. data/test/tc_level_set.rb +8 -0
  42. data/test/tc_map.rb +40 -0
  43. data/test/tc_move_recorder.rb +100 -0
  44. data/test/tc_raw_level.rb +5 -5
  45. data/test/test.rb +3 -1
  46. data/test/test_file2.xsb +2 -0
  47. data/test/ui/tc_console.rb +27 -13
  48. data/test/ui/tc_player_action.rb +156 -0
  49. metadata +38 -25
  50. data/lib/rsokoban/ui/ui.rb +0 -44
data/NEWS ADDED
@@ -0,0 +1,22 @@
1
+ version 0.73 2011-??-??
2
+
3
+ * New: undo
4
+
5
+ * New: default GUI with Tk library
6
+
7
+ * Ruby requiered version down to 1.8.7
8
+
9
+ * Curses and Portable user interface display a few more information
10
+
11
+
12
+ version 0.72 2011-01-21
13
+
14
+ * New console output using curses
15
+
16
+ * New command line options
17
+ --curses : Use curses as user interface. Default option.
18
+ --portable : Use a simplest user interface, should work on any platform.
19
+ --help-output : Display help on available user interfaces.
20
+
21
+ * Installing via RubyGem
22
+
data/README.rdoc CHANGED
@@ -1,35 +1,31 @@
1
1
  = Welcome to RSokoban !
2
2
 
3
- RSokoban is a clone of the famous {Sokoban game}[http://en.wikipedia.org/wiki/Sokoban].
4
- I wrote this program just to improve my skills in Ruby. Currently, you can play only in a console
5
- window.
6
-
7
- #####
8
- # #
9
- #$ #
10
- ### $##
11
- # $ $ #
12
- ### # ## # ######
13
- # # ## ##### ..#
14
- # $ $ ..#
15
- ##### ### #@## ..#
16
- # #########
17
- #######
3
+ RSokoban is a clone of the famous {Sokoban}[http://en.wikipedia.org/wiki/Sokoban] game.
4
+ I wrote this program just to improve my skills in Ruby.
5
+
6
+ *Features*
7
+ * User interface comes in 3 flavors (tk, curses, straight text mode)
8
+ * Installing via RubyGem
9
+ * Undo
10
+ * Use xsb file format to load sets of levels
18
11
 
19
12
  Enjoy the game.
20
13
 
21
14
  == Documentation
22
15
  Players can look at {the wiki}[https://github.com/lkdjiin/RSokoban/wiki].
23
16
 
24
- Developpers can look at Level and Game.
17
+ Developpers can look at Level, Game and TkUI.
25
18
 
26
19
 
27
20
  == Dependancies
28
- Ruby >= 1.9
21
+ Ruby >= 1.8.7
29
22
 
30
23
  == Installing RSokoban
31
- Installing via a gem is easy.
32
- Go to the RSokoban folder and build the gem :
24
+ Installing via RubyGem is easy doing in one command:
25
+
26
+ gem install RSokoban
27
+
28
+ Or you can install RSokoban from the source. To do this, go to the RSokoban folder and build the gem :
33
29
 
34
30
  gem build rsokoban.gemspec
35
31
 
@@ -41,6 +37,10 @@ Now start playing :
41
37
 
42
38
  rsokoban
43
39
 
40
+ Or you can play without installing it. Go to the RSokoban/bin folder and type:
41
+
42
+ ./rsokoban
43
+
44
44
  == Questions and/or Comments
45
45
  Feel free to email {Xavier Nayrac}[mailto:xavier.nayrac@gmail.com]
46
46
  with any questions.
data/RSokoban-0.73.gem ADDED
Binary file
data/TODO CHANGED
@@ -1,34 +1,41 @@
1
1
 
2
- --- v0.71 ---
2
+ doing gem and upload it
3
3
 
4
- (done)CursesConsole
5
- (done)--portable
6
- (done)--curses (default)
7
- (done)--help-output
8
- (done)curses: level completed (yes/no) > y or n
9
- (done)gem
4
+ --- next ---
5
+ tk: view description of level set in 'load set' dialog
10
6
 
11
- wiki installing
7
+ tk: add a scrollbar to the 'load set' dialog.
12
8
 
13
- wiki curses console
9
+ tk: adjust window size to map size
14
10
 
15
- Picture must be a class
11
+ tk: level preview
16
12
 
17
- --- v0.90 ---
13
+ redo
18
14
 
19
- undo
15
+ --gnome
20
16
 
21
- --- v1.0 ---
17
+ skining (several size of tile, resizing ?)
22
18
 
23
- --tk
19
+ Console UI should provide a way to know names of sets
24
20
 
25
- skining
21
+ save & reload
26
22
 
27
- --- next ---
23
+ multi player
24
+
25
+ save score
26
+
27
+ don't restrict map size
28
+
29
+ solver (plugin ?)
28
30
 
29
31
  format fichier niveau xml (.slc)
32
+
30
33
  --windows ?
31
- --gnome ?
34
+
32
35
  --kde ?
36
+
33
37
  --fxruby ?
34
- save, reload
38
+
39
+ mouse support
40
+
41
+ i18n
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.73
data/bin/rsokoban CHANGED
@@ -1,10 +1,74 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # This file is part of RSokoban, a clone of the famous sokoban game.
4
+ # Copyright 2011 Xavier Nayrac
5
+ #
6
+ # RSokoban is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+
3
19
  $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
20
+
21
+ $RSOKOBAN_PATH = File.expand_path(File.dirname(__FILE__)) + '/..'
22
+
4
23
  $RSOKOBAN_DATA_PATH = File.expand_path(File.dirname(__FILE__) + '/../data')
24
+
5
25
  require 'rsokoban'
6
26
 
27
+ tk_failed=<<EOS
28
+ Sorry, failed to load tk or tkimg library.
29
+ Please, be sure that libtcltk-ruby and libtk-img are correctly installed on
30
+ your system.
31
+ Now trying to start with curses library:
32
+ rsokoban --curses
33
+
34
+ EOS
35
+
36
+ curses_failed=<<EOS
37
+ Sorry, failed to load curses library.
38
+ Please, be sure that curses are correctly installed on your system.
39
+ You can try the game with the portable UI.
40
+ rsokoban --portable
41
+
42
+ EOS
43
+
44
+ # If player don't specify an explicit user interface,
45
+ # one will use tk.
7
46
  option = Option.new
8
47
 
48
+ # I try to load the needed tk bindings. If it fails,
49
+ # one can try with curses.
50
+ if option[:ui] == :tk
51
+ begin
52
+ require 'tk'
53
+ require 'tkextlib/tkimg'
54
+ rescue LoadError => e
55
+ puts tk_failed
56
+ option.interface = :curses
57
+ end
58
+ end
59
+
60
+ # I try to load the needed curses binding. If it fails,
61
+ # inform user and abandon.
62
+ if option[:ui] == :curses
63
+ begin
64
+ require 'curses'
65
+ rescue LoadError => e
66
+ puts curses_failed
67
+ exit
68
+ end
69
+ end
70
+
9
71
  game = RSokoban::Game.new option[:ui]
10
- game.run
72
+
73
+ # It seems I have somme troubles to unify GUI and non-GUI.
74
+ game.run if [:curses, :portable].include?(option[:ui])
@@ -4,4 +4,8 @@ module RSokoban
4
4
 
5
5
  class LevelNumberTooHighError < ArgumentError
6
6
  end
7
+
8
+ # Used by {MoveRecorder}. Raise me if one try to undo when there is no move left.
9
+ class EmptyMoveQueueError < StandardError
10
+ end
7
11
  end
data/lib/rsokoban/game.rb CHANGED
@@ -4,10 +4,10 @@ module RSokoban
4
4
  class Game
5
5
 
6
6
  # Construct a new game that you can later run.
7
- # @param [:curses|:portable] ui_as_symbol the user interface for the game
7
+ # @param [:curses|:portable|:tk] ui_as_symbol the user interface for the game
8
8
  def initialize ui_as_symbol
9
- @levelLoader = LevelLoader.new "original.xsb"
10
- @levelNumber = 1
9
+ @level_loader = LevelLoader.new "original.xsb"
10
+ @level_number = 1
11
11
  case ui_as_symbol
12
12
  when :curses
13
13
  require "rsokoban/ui/curses_console"
@@ -15,36 +15,45 @@ module RSokoban
15
15
  when :portable
16
16
  require "rsokoban/ui/console"
17
17
  @ui = UI::Console.new
18
+ when :tk
19
+ require "rsokoban/ui/tk_ui"
20
+ @ui = UI::TkUI.new
18
21
  end
19
22
  end
20
23
 
21
24
  # I am the game loop.
22
25
  def run
23
- action = start_level
26
+ player_action = start_level
24
27
  loop do
25
- if action.is_a?(Fixnum)
26
- action = load_level action
28
+ if player_action.level_number?
29
+ player_action = load_level player_action.action
27
30
  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
+ elsif player_action.set_name?
32
+ player_action = load_a_new_set player_action.action
31
33
  next
32
- elsif action == :quit
34
+ elsif player_action.quit?
33
35
  break
34
- elsif action == :next
35
- action = next_level
36
+ elsif player_action.next?
37
+ player_action = next_level
36
38
  next
37
- elsif action == :retry
38
- action = try_again
39
+ elsif player_action.retry?
40
+ player_action = try_again
39
41
  next
40
- elsif [:down, :up, :left, :right].include?(action)
41
- result = @level.move(action)
42
+ elsif player_action.move?
43
+ result = @level.move(player_action.action)
44
+ elsif player_action.undo?
45
+ result = @level.undo
42
46
  end
43
47
 
48
+ move_index = result =~ /\d+/
44
49
  if result.start_with?('WIN')
45
- action = @ui.get_action('WIN', @level.picture, result)
50
+ player_action = @ui.get_action(:type=>:win, :map=>@level.map, :move=>result[move_index..-1])
46
51
  else
47
- action = @ui.get_action('DISPLAY', @level.picture, result)
52
+ if move_index
53
+ player_action = @ui.get_action(:type=>:display, :map=>@level.map, :move=>result[move_index..-1])
54
+ else
55
+ player_action = @ui.get_action(:type=>:display, :map=>@level.map, :error=>result)
56
+ end
48
57
  end
49
58
  end
50
59
  end
@@ -54,7 +63,7 @@ module RSokoban
54
63
  # Load and start the next level of the set
55
64
  # @return [Object] the user's {action}[Console#get_action]
56
65
  def next_level
57
- @levelNumber += 1
66
+ @level_number += 1
58
67
  start_level
59
68
  end
60
69
 
@@ -68,22 +77,25 @@ module RSokoban
68
77
  # @param [String] setname the name of the set (with .xsb extension)
69
78
  # @return [Object] the user's {action}[Console#get_action]
70
79
  def load_a_new_set setname
80
+ title = nil
81
+ error = nil
71
82
  begin
72
- @levelLoader = LevelLoader.new setname
73
- @levelNumber = 1
74
- @level = @levelLoader.level(@levelNumber)
75
- message = "Level : #{@level.title}"
83
+ @level_loader = LevelLoader.new setname
84
+ @level_number = 1
85
+ @level = @level_loader.level(@level_number)
86
+ title = @level.title
76
87
  rescue NoFileError
77
- message = "Error, no such file : #{setname}"
88
+ error = "Error, no such file : #{setname}"
78
89
  end
79
- @ui.get_action('START', @level.picture, message)
90
+ @ui.get_action(:type=>:start, :map=>@level.map, :title=>title, :error=>error, :set=>@level_loader.set.title,
91
+ :number=>@level_number, :total=>@level_loader.set.size)
80
92
  end
81
93
 
82
94
  # Load a level from the current set.
83
95
  # @param [Fixnum] num the number of the set (base 1)
84
96
  # @return [Object] the user's {action}[Console#get_action]
85
97
  def load_level num
86
- @levelNumber = num
98
+ @level_number = num
87
99
  start_level
88
100
  end
89
101
 
@@ -91,10 +103,11 @@ module RSokoban
91
103
  # @return [Object] the user's {action}[Console#get_action]
92
104
  def start_level
93
105
  begin
94
- @level = @levelLoader.level(@levelNumber)
95
- @ui.get_action('START', @level.picture, "Level : #{@level.title}")
106
+ @level = @level_loader.level(@level_number)
107
+ @ui.get_action(:type=>:start, :map=>@level.map, :title=>@level.title, :set=>@level_loader.set.title,
108
+ :number=>@level_number, :total=>@level_loader.set.size)
96
109
  rescue LevelNumberTooHighError
97
- @ui.get_action('END_OF_SET', ['####'], "No more levels in this set")
110
+ @ui.get_action(:type=>:end_of_set, :map=>Map.new)
98
111
  end
99
112
  end
100
113
 
@@ -21,13 +21,14 @@ module RSokoban
21
21
  # @param [RawLevel] rawLevel
22
22
  def initialize rawLevel
23
23
  @title = rawLevel.title
24
- @floor = init_floor rawLevel.picture
25
- @man = init_man rawLevel.picture
24
+ @floor = init_floor rawLevel.map
25
+ @man = init_man rawLevel.map
26
26
  @crates = []
27
27
  @storages = []
28
- init_crates_and_storages rawLevel.picture
28
+ init_crates_and_storages rawLevel.map
29
29
  @move = 0
30
- @picture = nil
30
+ @map = nil
31
+ @move_recorder = MoveRecorder.new
31
32
  end
32
33
 
33
34
  # Two Level objects are equals if their @title, @floor, @man, @crates and @storages are equals.
@@ -44,14 +45,14 @@ module RSokoban
44
45
  self == obj
45
46
  end
46
47
 
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
48
+ # Get an instant map of the game.
49
+ # @return [Map] the map, after X turns of game.
50
+ def map
51
+ @map = init_floor @floor
51
52
  draw_crates
52
53
  draw_storages
53
54
  draw_man
54
- @picture
55
+ @map
55
56
  end
56
57
 
57
58
  # Move the man one box up.
@@ -90,15 +91,51 @@ module RSokoban
90
91
  return 'ERROR wall behind crate' if wall_behind_crate?(direction)
91
92
  return 'ERROR double crate' if double_crate?(direction)
92
93
  @move += 1
94
+
93
95
  @man.send(direction)
94
96
  if @crates.include?(Crate.new(@man.x, @man.y))
95
97
  i = @crates.index(Crate.new(@man.x, @man.y))
96
98
  @crates[i].send(direction)
99
+ @move_recorder.record direction, :push
100
+ else
101
+ @move_recorder.record direction
97
102
  end
98
103
  return "WIN move #{@move}" if win?
99
104
  "OK move #{@move}"
100
105
  end
101
106
 
107
+ # Undo last move
108
+ def undo
109
+ begin
110
+ case @move_recorder.undo
111
+ when :up then @man.down
112
+ when :down then @man.up
113
+ when :left then @man.right
114
+ when :right then @man.left
115
+ when :UP
116
+ i = @crates.index(Crate.new(@man.x, @man.y-1))
117
+ @crates[i].down
118
+ @man.down
119
+ when :DOWN
120
+ i = @crates.index(Crate.new(@man.x, @man.y+1))
121
+ @crates[i].up
122
+ @man.up
123
+ when :LEFT
124
+ i = @crates.index(Crate.new(@man.x-1, @man.y))
125
+ @crates[i].right
126
+ @man.right
127
+ when :RIGHT
128
+ i = @crates.index(Crate.new(@man.x+1, @man.y))
129
+ @crates[i].left
130
+ @man.left
131
+ end
132
+ @move -= 1
133
+ rescue EmptyMoveQueueError
134
+ # Nothing to do
135
+ end
136
+ "OK move #{@move}"
137
+ end
138
+
102
139
  private
103
140
 
104
141
  # @return true if all crates are on a storage location
@@ -173,29 +210,29 @@ module RSokoban
173
210
  box == CRATE or box == CRATE_ON_STORAGE
174
211
  end
175
212
 
176
- # Draw the man for @picture output
213
+ # Draw the man for map output
177
214
  def draw_man
178
215
  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
216
+ put_man_in_map if box == FLOOR
217
+ put_man_on_storage_in_map if box == STORAGE
181
218
  end
182
219
 
183
- def put_man_in_picture
184
- @picture[@man.y][@man.x] = MAN
220
+ def put_man_in_map
221
+ @map[@man.y][@man.x] = MAN
185
222
  end
186
223
 
187
- def put_man_on_storage_in_picture
188
- @picture[@man.y][@man.x] = MAN_ON_STORAGE
224
+ def put_man_on_storage_in_map
225
+ @map[@man.y][@man.x] = MAN_ON_STORAGE
189
226
  end
190
227
 
191
- # Draw the crates for @picture output
228
+ # Draw the crates for map output
192
229
  def draw_crates
193
- @crates.each {|crate| @picture[crate.y][crate.x] = what_is_on(crate.x, crate.y) }
230
+ @crates.each {|crate| @map[crate.y][crate.x] = what_is_on(crate.x, crate.y) }
194
231
  end
195
232
 
196
- # Draw the storages location for @picture output
233
+ # Draw the storages location for map output
197
234
  def draw_storages
198
- @storages.each {|st| @picture[st.y][st.x] = what_is_on(st.x, st.y) }
235
+ @storages.each {|st| @map[st.y][st.x] = what_is_on(st.x, st.y) }
199
236
  end
200
237
 
201
238
  # Get the content of box x, y
@@ -220,21 +257,21 @@ module RSokoban
220
257
 
221
258
  # Removes all storages locations, all crates and the man, leaving only walls and floor.
222
259
  #
223
- # @param [Array<String>] picture
224
- # @return [Array<String>] picture with only walls and floor
225
- def init_floor picture
260
+ # @param [Map] map
261
+ # @return [Map] map with only walls and floor
262
+ def init_floor map
226
263
  floor = []
227
- picture.each {|x| floor.push x.tr("#{STORAGE}#{CRATE}#{MAN}#{CRATE_ON_STORAGE}", FLOOR) }
264
+ map.each {|x| floor.push x.tr("#{STORAGE}#{CRATE}#{MAN}#{CRATE_ON_STORAGE}", FLOOR) }
228
265
  floor
229
266
  end
230
267
 
231
268
  # Find the man's position, at the begining of the level.
232
269
  #
233
- # @param [Array<String>] picture
270
+ # @param [Map] map
234
271
  # @return [Man] an initialised man
235
- def init_man picture
272
+ def init_man map
236
273
  x = y = 0
237
- picture.each {|line|
274
+ map.each {|line|
238
275
  if line.include?(MAN)
239
276
  x = line.index(MAN)
240
277
  break
@@ -246,10 +283,10 @@ module RSokoban
246
283
 
247
284
  # Find position of crates and storages, at the begining of the level.
248
285
  #
249
- # @param [Array<String>] picture
250
- def init_crates_and_storages picture
286
+ # @param [Map] map
287
+ def init_crates_and_storages map
251
288
  y = 0
252
- picture.each do |line|
289
+ map.each do |line|
253
290
  count = 0
254
291
  line.each_char do |c|
255
292
  @crates.push Crate.new(count, y) if c == CRATE
@@ -7,10 +7,10 @@ module RSokoban
7
7
  class LevelLoader
8
8
  attr_reader :level, :set
9
9
 
10
- # @filename [String] le nom du fichier où trouver les niveaux.
10
+ # @param [String] filename le nom du fichier où trouver les niveaux.
11
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
12
+ # @raise [RSokoban::NoFileError] si le fichier n'existe pas
13
+ # @see LevelSet overview of .xsb file format
14
14
  def initialize filename
15
15
  filename = "#{$RSOKOBAN_DATA_PATH}/" + filename
16
16
  raise NoFileError unless File.exist?(filename)
@@ -35,7 +35,7 @@ module RSokoban
35
35
  line = file.readline.chomp
36
36
  end
37
37
  line = line.chomp.sub(/;/, '').sub(/\s*/, '')
38
- @set.rawLevels.push RawLevel.new(line, raw)
38
+ @set.rawLevels.push RawLevel.new(line, raw) unless raw.empty?
39
39
 
40
40
  line = file.readline
41
41
  while line[0, 1] == ';'
@@ -3,22 +3,28 @@ module RSokoban
3
3
  # I am a set of sokoban levels.
4
4
  # Level set are found in .xsb files.
5
5
  #
6
- # =xsb file format
6
+ # = xsb file format
7
7
  #
8
8
  # Info lines begins with semi-colon (;)
9
- # Map lines begins with a # (that's a wall !)
9
+ # Map lines begins with a # (that's a wall !) preceded by 0, 1 or more spaces.
10
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
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
18
  #
19
19
  # From 4 to 7 again for each supplementary level
20
20
  class LevelSet
21
- attr_accessor :title, :description, :rawLevels
21
+
22
+ # @param [String] title get/set the title of this level set.
23
+ attr_accessor :title
24
+ # @param [String] description get/set the description of this level set.
25
+ attr_accessor :description
26
+ # @param [Array<RawLevel>] rawLevels get/set the raw levels of this set
27
+ attr_accessor :rawLevels
22
28
 
23
29
  def initialize
24
30
  @title = 'Unknown set title'
@@ -26,6 +32,10 @@ module RSokoban
26
32
  @rawLevels = []
27
33
  end
28
34
 
35
+ def size
36
+ @rawLevels.size
37
+ end
38
+
29
39
  end
30
40
 
31
41
  end
@@ -0,0 +1,51 @@
1
+ module RSokoban
2
+
3
+ # I am the map of a level.
4
+ # @since 0.73
5
+ class Map
6
+ # @param [Array<String>]
7
+ attr_reader :rows
8
+
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
+ # @param [Array<String>]
17
+ def initialize rows = []
18
+ @rows= rows
19
+ @width = 0
20
+ end
21
+
22
+ def rows=(rows)
23
+ @rows = rows
24
+ @width = 0
25
+ @rows.each {|row| @width = row.size if row.size > @width }
26
+ end
27
+
28
+ def height
29
+ @rows.size
30
+ end
31
+
32
+ def width
33
+ @width
34
+ end
35
+
36
+ def [](num)
37
+ @rows[num]
38
+ end
39
+
40
+ def each(&block)
41
+ @rows.each(&block)
42
+ end
43
+
44
+ def ==(obj)
45
+ return false unless obj.kind_of?(Map)
46
+ @rows == obj.rows
47
+ end
48
+
49
+ end
50
+
51
+ end