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
@@ -0,0 +1,38 @@
1
+ module RSokoban
2
+
3
+ # I can record the moves of a game and restitute them one by one,
4
+ # started from the last one. I am very usefull to implement an
5
+ # undo feature.
6
+ class MoveRecorder
7
+
8
+ def initialize
9
+ @queue = []
10
+ end
11
+
12
+ # @return [:up, :down, :left, :right]
13
+ # @raise EmptyMoveQueueError
14
+ # @since 0.73
15
+ def undo
16
+ raise EmptyMoveQueueError if @queue.empty?
17
+ @queue.pop
18
+ end
19
+
20
+ # Record the move +direction+
21
+ # @param [:up, :down, :left, :right] direction
22
+ # @raise ArgumentError if +direction+ is not included in [:up, :down, :left, :right]
23
+ # @since 0.73
24
+ def record direction, push = nil
25
+ raise ArgumentError unless [:up, :down, :left, :right].include?(direction)
26
+ if push
27
+ @queue.push :UP if direction == :up
28
+ @queue.push :DOWN if direction == :down
29
+ @queue.push :LEFT if direction == :left
30
+ @queue.push :RIGHT if direction == :right
31
+ else
32
+ @queue.push direction
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -5,41 +5,50 @@ class Option
5
5
 
6
6
  # Here is a list of command line options :
7
7
  # * --curses
8
- # * --version
9
- # * --license
10
8
  # * --help
11
9
  # * --help-output
10
+ # * --license
12
11
  # * --portable
12
+ # * --tk
13
+ # * --version
13
14
  # @todo refactoring
14
15
  def initialize
15
- @options = {:ui => :curses}
16
+ # Default option(s)
17
+ @options = {:ui => :tk}
16
18
 
17
19
  optparse = OptionParser.new do|opts|
18
20
  opts.banner = "Usage: #{$0} [options]"
19
21
 
22
+ # Options that define the UI
20
23
  opts.on( '-c', '--curses', 'Use curses console for user interface (default)' ) do
21
24
  @options[:ui] = :curses
22
25
  end
23
26
 
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
+ opts.on( '-p', '--portable', 'Use standard console for user interface' ) do
28
+ @options[:ui] = :portable
27
29
  end
28
30
 
31
+ opts.on( '-t', '--tk', 'Make use of Tk library for a graphical user interface' ) do
32
+ @options[:ui] = :tk
33
+ end
34
+
35
+ # Options that display a few information
29
36
  @options[:license] = false
30
37
  opts.on( '-l', '--license', 'Print program\'s license and exit' ) do
31
38
  @options[:license] = true
32
39
  end
33
40
 
34
- opts.on( '-p', '--portable', 'Use standard console for user interface' ) do
35
- @options[:ui] = :portable
36
- end
37
-
38
41
  @options[:version] = false
39
42
  opts.on( '-v', '--version', 'Print version number and exit' ) do
40
43
  @options[:version] = true
41
44
  end
42
45
 
46
+ # Options that display some help
47
+ @options[:help_output] = false
48
+ opts.on( '-o', '--help-output', 'Print help on output options and exit' ) do
49
+ @options[:help_output] = true
50
+ end
51
+
43
52
  opts.on( '-h', '--help', 'Display this screen' ) do
44
53
  puts opts
45
54
  exit
@@ -62,10 +71,14 @@ class Option
62
71
  @options[k]
63
72
  end
64
73
 
74
+ def interface=(k)
75
+ @options[:ui] = k
76
+ end
77
+
65
78
  private
66
79
 
67
80
  def print_version
68
- puts RSokoban::VERSION
81
+ puts File.read($RSOKOBAN_PATH + '/VERSION').strip
69
82
  exit
70
83
  end
71
84
 
@@ -77,7 +90,7 @@ private
77
90
  def print_help_output
78
91
  help=<<EOS
79
92
 
80
- RSokoban can use 2 user interfaces :
93
+ RSokoban can use 3 user interfaces :
81
94
 
82
95
  --curses
83
96
  This is the default UI. It uses the curses library in a console window.
@@ -86,6 +99,10 @@ RSokoban can use 2 user interfaces :
86
99
  --portable
87
100
  It uses a plain console window. This UI is boring but should work
88
101
  everywhere.
102
+
103
+ --tk
104
+ The only graphical UI for now. Make sure tk is installed on your computer
105
+ along with the libtk-img extension library.
89
106
  EOS
90
107
  puts help
91
108
  exit
@@ -1,14 +1,25 @@
1
1
  module RSokoban
2
2
 
3
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.
4
+ # I have a title and a map.
5
5
  # @todo document and give some examples
6
6
  class RawLevel
7
- attr_accessor :title, :picture
7
+ attr_reader :map
8
+ attr_accessor :title
8
9
 
9
- def initialize title = 'Unknown level title', picture = []
10
+ def initialize title = 'Unknown level title', map = Map.new
10
11
  @title = title
11
- @picture = picture
12
+ @map = map
13
+ end
14
+
15
+ def map=(val)
16
+ if val.kind_of?(Map)
17
+ @map = val
18
+ elsif val.kind_of?(Array)
19
+ @map = Map.new val
20
+ else
21
+ raise ArgumentError
22
+ end
12
23
  end
13
24
 
14
25
  end
@@ -0,0 +1,37 @@
1
+ module RSokoban::UI
2
+
3
+ # Every concrete UI should inherits from me.
4
+ # @abstract Subclass and override {#get_action} to implement a working UI.
5
+ class BaseUI
6
+
7
+ def initialize
8
+ @level_title = ''
9
+ end
10
+
11
+ # Based on things found in the +hash+ argument, I display the game
12
+ # to the user. Then he can tell what he wants to do. Whatever
13
+ # my childs permit the user to do, they can only return an
14
+ # ActionPlayer object.
15
+ #
16
+ # @param [Hash] hash
17
+ # :type => type of message, :win or :start or :display or :end_of_set
18
+ # :map => current game map
19
+ # @param [Hash] hash the options passed to the UI.
20
+ # @option hash [:win|:start|:display|:end_of_set] :type The type of the message (always +requiered+)
21
+ # @option hash [Map] :map The current map of the game (always +requiered+)
22
+ # @option hash [String] :title The level's title (requiered if type==:start)
23
+ # @option hash [String] :set The set's title (requiered if type==:start)
24
+ # @option hash [Fixnum] :number The level's number (requiered if type==:start)
25
+ # @option hash [Fixnum] :total Number of level in this set (requiered if type==:start)
26
+ # @option hash [Fixnum] :move The move's number (requiered when type is :display or :win)
27
+ # @option hash [String] :error An error message. Could happen when type is :display or :start
28
+ # @return [PlayerAction] the user's action
29
+ # @since 0.73
30
+ # @todo write some examples
31
+ def get_action(hash)
32
+ raise "Please implement me !"
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -11,51 +11,60 @@ module RSokoban::UI
11
11
  super()
12
12
  end
13
13
 
14
- def get_action(type, level, message)
15
- @level_title = message if type == 'START'
16
- message = 'OK move 0' if type == 'START'
17
- display level, message
18
- if type == 'DISPLAY' or type == 'START'
19
- askPlayer
14
+ def get_action(hash)
15
+ if hash[:type] == :start
16
+ @level_title = hash[:title]
17
+ @set_title = hash[:set]
18
+ @level_number = hash[:number]
19
+ @set_total = hash[:total]
20
+ end
21
+ display hash
22
+ if [:display, :start].include?(hash[:type])
23
+ ask_player
20
24
  else
21
25
  # assuming type == 'WIN'
22
- askForNextLevel
26
+ ask_for_next_level
23
27
  end
24
28
  end
25
29
 
26
30
  private
27
31
 
28
- def display level, message
32
+ def display hash
29
33
  blankConsole = "\n" * 24 # assuming a console window of 24 lines height
30
34
  puts blankConsole
31
- puts @level_title
35
+ puts "Set: #{@set_title}"
36
+ puts "Level: #{@level_title} (#{@level_number}/#{@set_total})"
32
37
  puts "--------------------"
33
- puts message
38
+ unless hash[:move].nil?
39
+ puts "move #{hash[:move]}"
40
+ else
41
+ puts ''
42
+ end
34
43
  puts ''
35
- level.each {|line| puts line }
44
+ hash[:map].each {|line| puts line }
36
45
  puts ''
37
46
  end
38
47
 
39
- def askForNextLevel
48
+ def ask_for_next_level
40
49
  printf "Play next level ? "
41
50
  line = readline.chomp
42
51
  if ['yes', 'ye', 'y', 'YES', 'YE', 'Y'].include?(line)
43
- :next
52
+ PlayerAction.new(:next)
44
53
  else
45
- :quit
54
+ PlayerAction.new(:quit)
46
55
  end
47
56
  end
48
57
 
49
- def askPlayer
58
+ def ask_player
50
59
  printf "Your choice ? "
51
60
  line = readline.chomp
52
61
  response = parse line
53
62
  if response.nil?
54
63
  puts "Error : #{line}"
55
- askPlayer
56
- elsif response == :help
57
- displayHelp
58
- askPlayer
64
+ ask_player
65
+ elsif response.instance_of?(Symbol) and response == :help
66
+ display_help
67
+ ask_player
59
68
  else
60
69
  response
61
70
  end
@@ -63,24 +72,24 @@ module RSokoban::UI
63
72
 
64
73
  def parse str
65
74
  case str
66
- when 'quit', 'up', 'down', 'right', 'left', 'retry', 'help'
67
- str.to_sym
68
- when 'z'
69
- :up
70
- when 's'
71
- :down
72
- when 'q'
73
- :left
74
- when 'd'
75
- :right
75
+ when 'quit', 'up', 'down', 'right', 'left', 'retry', 'undo'
76
+ PlayerAction.new(str.to_sym)
77
+ when 'help' then :help
78
+ when 'z' then PlayerAction.new(:up)
79
+ when 's' then PlayerAction.new(:down)
80
+ when 'q' then PlayerAction.new(:left)
81
+ when 'd' then PlayerAction.new(:right)
76
82
  when '1'..'999'
77
- str.to_i
78
- when /\.xsb$/
79
- str
83
+ if str.to_i > @set_total
84
+ ask_player
85
+ else
86
+ PlayerAction.new(str.to_i)
87
+ end
88
+ when /\.xsb$/ then PlayerAction.new(str)
80
89
  end
81
90
  end
82
91
 
83
- def displayHelp
92
+ def display_help
84
93
  help=<<EOS
85
94
  ------------------------------
86
95
  General commands :
@@ -88,6 +97,7 @@ General commands :
88
97
  quit : Quit game
89
98
  help : Display this screen
90
99
  retry : Restart level
100
+ undo : Undo last move
91
101
  1 to 999 : Play this level
92
102
  file.xsb : Load this set of levels
93
103
 
@@ -1,4 +1,4 @@
1
- require 'curses'
1
+
2
2
 
3
3
  module RSokoban::UI
4
4
 
@@ -6,10 +6,11 @@ module RSokoban::UI
6
6
  # I assume 24 lines height.
7
7
  class CursesConsole < BaseUI
8
8
 
9
- @@TITLE_LINE = 0
10
- @@MOVES_LINE = 1
11
- @@STATUS_LINE = 2
12
- @@PICTURE_LINE = 4
9
+ @@SET_LINE = 0
10
+ @@TITLE_LINE = 1
11
+ @@MOVES_LINE = 2
12
+ @@STATUS_LINE = 3
13
+ @@PICTURE_LINE = 5
13
14
 
14
15
  def initialize
15
16
  super()
@@ -18,17 +19,19 @@ module RSokoban::UI
18
19
  Curses.close_screen
19
20
  end
20
21
 
21
- def get_action(type, level, message)
22
- if type == 'START' or type == 'END_OF_SET'
23
- @level_title = message
24
- message = 'OK move 0'
22
+ def get_action(hash)
23
+ if hash[:type] == :start
24
+ @level_title = hash[:title]
25
+ @set_title = hash[:set]
26
+ @level_number = hash[:number]
27
+ @set_total = hash[:total]
25
28
  Curses.clear
26
29
  end
27
- if type == 'DISPLAY' or type == 'START' or type == 'END_OF_SET'
28
- ask_player level, message
30
+ if [:display, :start].include?(hash[:type])
31
+ ask_player hash
29
32
  else
30
- # assuming type == 'WIN'
31
- askForNextLevel level, message
33
+ # assuming :win
34
+ ask_for_next_level hash
32
35
  end
33
36
  end
34
37
 
@@ -41,13 +44,13 @@ module RSokoban::UI
41
44
  Curses.curs_set 0
42
45
  end
43
46
 
44
- def display level, message
45
- write @@TITLE_LINE, 0, @level_title
46
- move_index = message =~ /\d+/
47
- write @@MOVES_LINE, 0, 'moves : ' + message[move_index..-1] if move_index
48
- write @@STATUS_LINE, 0, 'arrows=move (q)uit (r)etry (l)oad level/set'
47
+ def display hash
48
+ write @@SET_LINE, 0, "Set: #{@set_title}"
49
+ write @@TITLE_LINE, 0, "Level: #{@level_title} (#{@level_number}/#{@set_total})"
50
+ write @@MOVES_LINE, 0, "moves : #{hash[:move].to_s} " unless hash[:move].nil?
51
+ write @@STATUS_LINE, 0, 'arrows=move (q)uit (r)etry (u)ndo (l)oad level/set'
49
52
  line_num = @@PICTURE_LINE
50
- level.each {|line|
53
+ hash[:map].each {|line|
51
54
  write line_num, 0, line
52
55
  line_num += 1
53
56
  }
@@ -58,22 +61,22 @@ module RSokoban::UI
58
61
  Curses.addstr(text);
59
62
  end
60
63
 
61
- def askForNextLevel level, message
62
- display level, message
64
+ def ask_for_next_level hash
65
+ display hash
63
66
  write @@STATUS_LINE, 0, "LEVEL COMPLETED ! Play next level ? (yes, no) "
64
67
  case Curses.getch
65
- when ?n, ?N then :quit
66
- when ?y, ?Y then :next
68
+ when ?n, ?N then PlayerAction.new(:quit)
69
+ when ?y, ?Y then PlayerAction.new(:next)
67
70
  else
68
- askForNextLevel level, message
71
+ ask_for_next_level hash
69
72
  end
70
73
  end
71
74
 
72
- def ask_player level, message
73
- display level, message
75
+ def ask_player hash
76
+ display hash
74
77
  response = get_player_input
75
78
  if response.nil?
76
- ask_player level, message
79
+ ask_player hash
77
80
  else
78
81
  response
79
82
  end
@@ -81,13 +84,14 @@ module RSokoban::UI
81
84
 
82
85
  def get_player_input
83
86
  case Curses.getch
84
- when Curses::Key::UP then :up
85
- when Curses::Key::DOWN then :down
86
- when Curses::Key::LEFT then :left
87
- when Curses::Key::RIGHT then :right
88
- when ?q, ?Q then :quit
89
- when ?r, ?R then :retry
87
+ when Curses::Key::UP then PlayerAction.new(:up)
88
+ when Curses::Key::DOWN then PlayerAction.new(:down)
89
+ when Curses::Key::LEFT then PlayerAction.new(:left)
90
+ when Curses::Key::RIGHT then PlayerAction.new(:right)
91
+ when ?q, ?Q then PlayerAction.new(:quit)
92
+ when ?r, ?R then PlayerAction.new(:retry)
90
93
  when ?l, ?L then ask_level_or_set
94
+ when ?u, ?U then PlayerAction.new(:undo)
91
95
  else
92
96
  nil
93
97
  end
@@ -111,10 +115,15 @@ EOS
111
115
  Curses.curs_set 0
112
116
  Curses.noecho
113
117
  case str
114
- when '1'..'999' then str.to_i
115
- when /\.xsb$/ then str
118
+ when '1'..'999'
119
+ if str.to_i > @set_total
120
+ ask_level_or_set
121
+ else
122
+ PlayerAction.new(str.to_i)
123
+ end
124
+ when /\.xsb$/ then PlayerAction.new(str)
116
125
  else
117
- :retry
126
+ PlayerAction.new(:retry)
118
127
  end
119
128
  end
120
129
 
@@ -0,0 +1,74 @@
1
+ module RSokoban::UI
2
+
3
+ # I am an action from the player.
4
+ #
5
+ # List of possible actions :
6
+ # [:quit] to quit game
7
+ # [:next] to load and play next level
8
+ # [:retry] to restart the current level
9
+ # [:up, :down, :left, :right] to move the man
10
+ # [:undo] to undo a move
11
+ # [a number] to load this level number
12
+ # [an .xsb filename] to load the set with this name
13
+ #
14
+ # @since 0.73
15
+ class PlayerAction
16
+ # @param [Object] the action
17
+ attr_reader :action
18
+
19
+ @@Allowed_symbols = [ :up, :down, :left, :right, :quit, :next, :retry, :undo ]
20
+
21
+ # You can the the {class description}[PlayerAction] for an allowed list of value.
22
+ # @param [Object] value optional initial action
23
+ # @raise ArgumentError if value is not allowed
24
+ def initialize value = nil
25
+ self.action = value unless value.nil?
26
+ end
27
+
28
+ def ==(obj)
29
+ @action == obj.action
30
+ end
31
+
32
+ # Set the player action.
33
+ # You can the the {class description}[PlayerAction] for an allowed list of value.
34
+ # @param [Object] value the player action
35
+ def action=(value)
36
+ if value.instance_of?(Symbol)
37
+ raise ArgumentError unless @@Allowed_symbols.include?(value)
38
+ end
39
+ if value.instance_of?(String)
40
+ raise ArgumentError unless value =~ /\.xsb$/
41
+ end
42
+ @action = value
43
+ end
44
+
45
+ def level_number?
46
+ @action.instance_of?(Fixnum)
47
+ end
48
+
49
+ def set_name?
50
+ @action.instance_of?(String)
51
+ end
52
+
53
+ def quit?
54
+ (not @action.nil?) and @action == :quit
55
+ end
56
+
57
+ def next?
58
+ (not @action.nil?) and @action == :next
59
+ end
60
+
61
+ def retry?
62
+ (not @action.nil?) and @action == :retry
63
+ end
64
+
65
+ def move?
66
+ (not @action.nil?) and [:down, :up, :left, :right].include?(@action)
67
+ end
68
+
69
+ def undo?
70
+ (not @action.nil?) and @action == :undo
71
+ end
72
+ end
73
+
74
+ end