RSokoban 0.71 → 0.73

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