RSokoban 0.73 → 0.74
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/NEWS +16 -1
- data/README.rdoc +25 -8
- data/Rakefile +51 -0
- data/TODO +19 -41
- data/VERSION +1 -1
- data/bin/rsokoban +1 -3
- data/lib/rsokoban.rb +2 -0
- data/lib/rsokoban/exception.rb +4 -0
- data/lib/rsokoban/extensions.rb +26 -0
- data/lib/rsokoban/game.rb +161 -41
- data/lib/rsokoban/level.rb +54 -37
- data/lib/rsokoban/level_loader.rb +34 -11
- data/lib/rsokoban/move_recorder.rb +14 -2
- data/lib/rsokoban/move_result.rb +37 -0
- data/lib/rsokoban/ui/base_ui.rb +0 -3
- data/lib/rsokoban/ui/console.rb +2 -2
- data/lib/rsokoban/ui/curses_console.rb +0 -2
- data/lib/rsokoban/ui/player_action.rb +2 -2
- data/lib/rsokoban/ui/tk_box.rb +21 -0
- data/lib/rsokoban/ui/tk_dialogs.rb +240 -0
- data/lib/rsokoban/ui/tk_ui.rb +170 -269
- data/test/tc_extensions.rb +26 -0
- data/test/tc_game.rb +28 -0
- data/test/tc_level.rb +187 -112
- data/test/tc_level_loader.rb +12 -17
- data/test/tc_move_recorder.rb +41 -7
- data/test/tc_move_result.rb +45 -0
- data/test/test.rb +3 -0
- data/test/ui/tc_console.rb +3 -1
- metadata +10 -3
- data/RSokoban-0.73.gem +0 -0
data/NEWS
CHANGED
@@ -1,4 +1,19 @@
|
|
1
|
-
version 0.
|
1
|
+
version 0.74 2011-01-31
|
2
|
+
|
3
|
+
* New: redo feature (only available with Tk GUI)
|
4
|
+
|
5
|
+
* Tk GUI
|
6
|
+
+ add a 'next level' feature
|
7
|
+
+ undo shortcut change to Ctrl+Z
|
8
|
+
+ view description of a level set in the 'load set' dialog box
|
9
|
+
+ minor improvements in dialog boxes
|
10
|
+
|
11
|
+
* Fix
|
12
|
+
+ bug#1
|
13
|
+
+ bug#2
|
14
|
+
|
15
|
+
|
16
|
+
version 0.73 2011-01-27
|
2
17
|
|
3
18
|
* New: undo
|
4
19
|
|
data/README.rdoc
CHANGED
@@ -4,32 +4,35 @@ RSokoban is a clone of the famous {Sokoban}[http://en.wikipedia.org/wiki/Sokoban
|
|
4
4
|
I wrote this program just to improve my skills in Ruby.
|
5
5
|
|
6
6
|
*Features*
|
7
|
-
*
|
8
|
-
*
|
9
|
-
*
|
7
|
+
* Graphical user interface with Tk
|
8
|
+
* Two console user interfaces (curses and straight text mode)
|
9
|
+
* 200+ levels
|
10
|
+
* Undo/redo
|
10
11
|
* Use xsb file format to load sets of levels
|
11
12
|
|
12
13
|
Enjoy the game.
|
13
14
|
|
15
|
+
|
14
16
|
== Documentation
|
17
|
+
|
15
18
|
Players can look at {the wiki}[https://github.com/lkdjiin/RSokoban/wiki].
|
16
19
|
|
17
20
|
Developpers can look at Level, Game and TkUI.
|
18
21
|
|
19
22
|
|
20
23
|
== Dependancies
|
24
|
+
|
21
25
|
Ruby >= 1.8.7
|
22
26
|
|
23
|
-
== Installing RSokoban
|
24
|
-
Installing via RubyGem is easy doing in one command:
|
25
27
|
|
26
|
-
|
28
|
+
== Installing RSokoban
|
27
29
|
|
28
|
-
|
30
|
+
*First method*
|
31
|
+
To install RSokoban from the source, go to the RSokoban folder and build the gem:
|
29
32
|
|
30
33
|
gem build rsokoban.gemspec
|
31
34
|
|
32
|
-
Then install it :
|
35
|
+
Then install it (maybe you have to be root):
|
33
36
|
|
34
37
|
gem install RSokoban-x-xx.gem
|
35
38
|
|
@@ -37,11 +40,25 @@ Now start playing :
|
|
37
40
|
|
38
41
|
rsokoban
|
39
42
|
|
43
|
+
If you prefer using rake to build and install:
|
44
|
+
|
45
|
+
rake build
|
46
|
+
rake install
|
47
|
+
|
48
|
+
*Second method*
|
49
|
+
Or install via RubyGem in one command (you need ruby 1.9 or higher).
|
50
|
+
Note that I don't certify you will get the latest release:
|
51
|
+
|
52
|
+
gem install RSokoban
|
53
|
+
|
54
|
+
*Third method*
|
40
55
|
Or you can play without installing it. Go to the RSokoban/bin folder and type:
|
41
56
|
|
42
57
|
./rsokoban
|
43
58
|
|
59
|
+
|
44
60
|
== Questions and/or Comments
|
61
|
+
|
45
62
|
Feel free to email {Xavier Nayrac}[mailto:xavier.nayrac@gmail.com]
|
46
63
|
with any questions.
|
47
64
|
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
desc 'Unit tests'
|
2
|
+
task :default => :unit
|
3
|
+
|
4
|
+
# Running RSokoban with the different (G)UIs
|
5
|
+
|
6
|
+
desc 'Run RSokoban with Tk GUI'
|
7
|
+
task :tk do
|
8
|
+
exec "./bin/rsokoban --tk"
|
9
|
+
end
|
10
|
+
|
11
|
+
desc 'Run RSokoban with curses UI'
|
12
|
+
task :curses do
|
13
|
+
exec "./bin/rsokoban --curses"
|
14
|
+
end
|
15
|
+
# Aliases to rake curses
|
16
|
+
task :curse => :curses
|
17
|
+
task :curs => :curses
|
18
|
+
task :cur => :curses
|
19
|
+
|
20
|
+
desc 'Run RSokoban in straight text mode'
|
21
|
+
task :portable do
|
22
|
+
exec "./bin/rsokoban --portable"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Build & Install RSokoban
|
26
|
+
|
27
|
+
desc 'Build the gem'
|
28
|
+
task :build do
|
29
|
+
exec "gem build rsokoban.gemspec"
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'Install the gem (maybe you have to be root)'
|
33
|
+
task :install do
|
34
|
+
require 'rake'
|
35
|
+
f = FileList['RSokoban*gem'].to_a
|
36
|
+
exec "gem install #{f.first}"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Testing RSokoban
|
40
|
+
|
41
|
+
desc 'Unit tests'
|
42
|
+
task :unit do
|
43
|
+
Dir.chdir 'test'
|
44
|
+
exec "./test.rb"
|
45
|
+
end
|
46
|
+
|
47
|
+
# Documentation
|
48
|
+
desc 'Generate yard documentation'
|
49
|
+
task :doc do
|
50
|
+
exec 'yardoc --title "RSokoban Documentation" - NEWS COPYING VERSION'
|
51
|
+
end
|
data/TODO
CHANGED
@@ -1,41 +1,19 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
save & reload
|
22
|
-
|
23
|
-
multi player
|
24
|
-
|
25
|
-
save score
|
26
|
-
|
27
|
-
don't restrict map size
|
28
|
-
|
29
|
-
solver (plugin ?)
|
30
|
-
|
31
|
-
format fichier niveau xml (.slc)
|
32
|
-
|
33
|
-
--windows ?
|
34
|
-
|
35
|
-
--kde ?
|
36
|
-
|
37
|
-
--fxruby ?
|
38
|
-
|
39
|
-
mouse support
|
40
|
-
|
41
|
-
i18n
|
1
|
+
* tk: use one image for rendering, not an array (if possible ?)
|
2
|
+
* don't restrict map size
|
3
|
+
* gnome GUI (gtk only to be windows compatible ?)
|
4
|
+
* give a try to rubygame ?
|
5
|
+
* skining
|
6
|
+
+ load skin
|
7
|
+
+ several size of tiles or resizing ?)
|
8
|
+
* Console UI should provide a way to know names of sets
|
9
|
+
* save a game
|
10
|
+
* reload a saved game
|
11
|
+
* multi player support
|
12
|
+
* save score
|
13
|
+
* scoring moves and pushes
|
14
|
+
* level preview
|
15
|
+
* solver (plugin ?)
|
16
|
+
* format fichier niveau xml (.slc)
|
17
|
+
* other GUI(s) ?
|
18
|
+
* mouse support
|
19
|
+
* i18n
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.74
|
data/bin/rsokoban
CHANGED
data/lib/rsokoban.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require "rsokoban/extensions"
|
1
2
|
require "rsokoban/level"
|
2
3
|
require "rsokoban/level_loader"
|
3
4
|
require "rsokoban/exception"
|
@@ -12,6 +13,7 @@ require "rsokoban/raw_level"
|
|
12
13
|
require "rsokoban/ui"
|
13
14
|
require "rsokoban/move_recorder"
|
14
15
|
require "rsokoban/map"
|
16
|
+
require "rsokoban/move_result"
|
15
17
|
|
16
18
|
# I am the main module of the game.
|
17
19
|
module RSokoban
|
data/lib/rsokoban/exception.rb
CHANGED
@@ -8,4 +8,8 @@ module RSokoban
|
|
8
8
|
# Used by {MoveRecorder}. Raise me if one try to undo when there is no move left.
|
9
9
|
class EmptyMoveQueueError < StandardError
|
10
10
|
end
|
11
|
+
|
12
|
+
# Used by {MoveRecorder}. Raise me if one try to redo when there is nothing to redo.
|
13
|
+
class EmptyRedoError < StandardError
|
14
|
+
end
|
11
15
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
class String
|
3
|
+
# In xsb files, an information line start with a semi-colon.
|
4
|
+
# I remove it, remove leading and trailing spaces and inject a newline to the end.
|
5
|
+
# @example
|
6
|
+
# > a = "; hello world ! \n"
|
7
|
+
# > a.get_xsb_info_line
|
8
|
+
# => "hello world !\n"
|
9
|
+
# @since 0.74
|
10
|
+
# @see #get_xsb_info_line_chomp
|
11
|
+
def get_xsb_info_line
|
12
|
+
self.get_xsb_info_line_chomp + "\n"
|
13
|
+
end
|
14
|
+
|
15
|
+
# In xsb files, an information line start with a semi-colon.
|
16
|
+
# I remove it and also remove leading and trailing spaces.
|
17
|
+
# @example
|
18
|
+
# > a = "; hello world ! \n"
|
19
|
+
# > a.get_xsb_info_line_chomp
|
20
|
+
# => "hello world !"
|
21
|
+
# @since 0.74
|
22
|
+
# @see #get_xsb_info_line
|
23
|
+
def get_xsb_info_line_chomp
|
24
|
+
self.sub(/;/, '').strip
|
25
|
+
end
|
26
|
+
end
|
data/lib/rsokoban/game.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
module RSokoban
|
2
2
|
|
3
|
-
# I
|
3
|
+
# I manage the game.
|
4
4
|
class Game
|
5
|
+
# @return [Fixnum]
|
6
|
+
# @since 0.74
|
7
|
+
attr_reader :level_number
|
5
8
|
|
6
9
|
# Construct a new game that you can later run.
|
7
10
|
# @param [:curses|:portable|:tk] ui_as_symbol the user interface for the game
|
8
|
-
|
9
|
-
|
11
|
+
# @param [String] setname only used during testing
|
12
|
+
def initialize ui_as_symbol, setname = 'microban.xsb'
|
13
|
+
@ui_as_symbol = ui_as_symbol
|
14
|
+
@level_loader = LevelLoader.new setname
|
10
15
|
@level_number = 1
|
11
16
|
case ui_as_symbol
|
12
17
|
when :curses
|
@@ -17,12 +22,28 @@ module RSokoban
|
|
17
22
|
@ui = UI::Console.new
|
18
23
|
when :tk
|
19
24
|
require "rsokoban/ui/tk_ui"
|
20
|
-
|
25
|
+
require "rsokoban/ui/tk_box"
|
26
|
+
require "rsokoban/ui/tk_dialogs"
|
27
|
+
@gui = UI::TkUI.new self
|
21
28
|
end
|
22
29
|
end
|
23
30
|
|
24
|
-
#
|
31
|
+
# Start the game loop.
|
32
|
+
#
|
33
|
+
# For GUI game (like Tk), the tool kit provide its own event-loop. In that case, I simply
|
34
|
+
# call this event-loop via the run() method (for example see {RSokoban::UI::TkUI#run}.
|
35
|
+
#
|
36
|
+
# For UI game (like Curses) which are text based, Game provide the loop.
|
25
37
|
def run
|
38
|
+
if [:curses, :portable].include?(@ui_as_symbol)
|
39
|
+
ui_mainloop
|
40
|
+
else
|
41
|
+
@gui.run
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# I am the game loop for UIs.
|
46
|
+
def ui_mainloop
|
26
47
|
player_action = start_level
|
27
48
|
loop do
|
28
49
|
if player_action.level_number?
|
@@ -45,40 +66,151 @@ module RSokoban
|
|
45
66
|
result = @level.undo
|
46
67
|
end
|
47
68
|
|
48
|
-
|
49
|
-
|
50
|
-
|
69
|
+
if result[:status] == :win
|
70
|
+
player_action = @ui.get_action(:type=>:win, :map=>@level.map, :move=>result[:move_number])
|
71
|
+
elsif result[:status] == :ok
|
72
|
+
player_action = @ui.get_action(:type=>:display, :map=>@level.map, :move=>result[:move_number])
|
51
73
|
else
|
52
|
-
|
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
|
74
|
+
player_action = @ui.get_action(:type=>:display, :map=>@level.map, :error=>result[:message])
|
57
75
|
end
|
58
76
|
end
|
77
|
+
true
|
59
78
|
end
|
60
79
|
|
61
|
-
|
80
|
+
# @return [Fixnum]
|
81
|
+
# @since 0.74
|
82
|
+
# @see Level#width
|
83
|
+
def level_width
|
84
|
+
@level.width
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [Fixnum]
|
88
|
+
# @since 0.74
|
89
|
+
# @see Level#height
|
90
|
+
def level_height
|
91
|
+
@level.height
|
92
|
+
end
|
93
|
+
|
94
|
+
# @return [String]
|
95
|
+
# @since 0.74
|
96
|
+
# @see Level#title
|
97
|
+
def level_title
|
98
|
+
@level.title
|
99
|
+
end
|
100
|
+
|
101
|
+
# Get current map of the game
|
102
|
+
# @return [Map]
|
103
|
+
# @since 0.74
|
104
|
+
def map
|
105
|
+
@level.map
|
106
|
+
end
|
107
|
+
|
108
|
+
# Get x coordinate of the man
|
109
|
+
# @return [Fixnum]
|
110
|
+
# @since 0.74
|
111
|
+
def man_x
|
112
|
+
@level.man.x
|
113
|
+
end
|
114
|
+
|
115
|
+
# Get y coordinate of the man
|
116
|
+
# @return [Fixnum]
|
117
|
+
# @since 0.74
|
118
|
+
def man_y
|
119
|
+
@level.man.y
|
120
|
+
end
|
121
|
+
|
122
|
+
# Get result of the move
|
123
|
+
# @param [:up|:down|:right|:left] direction
|
124
|
+
# @return [Object]
|
125
|
+
# @since 0.74
|
126
|
+
def move direction
|
127
|
+
@level.move direction
|
128
|
+
end
|
129
|
+
|
130
|
+
# Get current move number
|
131
|
+
# @return [Fixnum]
|
132
|
+
# @since 0.74
|
133
|
+
def move_number
|
134
|
+
@level.move_number
|
135
|
+
end
|
136
|
+
|
137
|
+
# Get result of undo last move
|
138
|
+
# @return [MoveResult]
|
139
|
+
# @since 0.74
|
140
|
+
def undo
|
141
|
+
@level.undo
|
142
|
+
end
|
143
|
+
|
144
|
+
# Get result of redo last undone move
|
145
|
+
# @return [MoveResult]
|
146
|
+
# @since 0.74
|
147
|
+
def redo
|
148
|
+
@level.redo
|
149
|
+
end
|
150
|
+
|
151
|
+
# Get title of the current set
|
152
|
+
# @return [String]
|
153
|
+
# @since 0.74
|
154
|
+
def set_title
|
155
|
+
@level_loader.title
|
156
|
+
end
|
157
|
+
|
158
|
+
# Get numbers of level from the current set
|
159
|
+
# @return [Fixnum]
|
160
|
+
# @since 0.74
|
161
|
+
def set_size
|
162
|
+
@level_loader.size
|
163
|
+
end
|
164
|
+
|
165
|
+
# Start a level, according to some instance members.
|
166
|
+
# @return [PlayerAction|nil] the user's action for console window
|
167
|
+
# interface, or nil for GUI.
|
168
|
+
def start_level
|
169
|
+
begin
|
170
|
+
@level = @level_loader.level(@level_number)
|
171
|
+
if @ui
|
172
|
+
@ui.get_action(:type=>:start, :map=>@level.map, :title=>@level.title, :set=>@level_loader.title,
|
173
|
+
:number=>@level_number, :total=>@level_loader.size)
|
174
|
+
end
|
175
|
+
rescue LevelNumberTooHighError
|
176
|
+
if @ui
|
177
|
+
@ui.get_action(:type=>:end_of_set, :map=>Map.new)
|
178
|
+
else
|
179
|
+
raise LevelNumberTooHighError
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
62
183
|
|
63
184
|
# Load and start the next level of the set
|
64
|
-
# @return [
|
185
|
+
# @return [PlayerAction|nil] the user's action for console window
|
186
|
+
# interface, or nil for GUI.
|
65
187
|
def next_level
|
66
188
|
@level_number += 1
|
67
189
|
start_level
|
68
190
|
end
|
69
191
|
|
70
|
-
#
|
71
|
-
# @
|
72
|
-
|
192
|
+
# Load a level from the current set.
|
193
|
+
# @param [Fixnum] num the number of the set (base 1)
|
194
|
+
# @return [PlayerAction|nil] the user's action for console window
|
195
|
+
# interface, or nil for GUI.
|
196
|
+
def load_level num
|
197
|
+
@level_number = num
|
198
|
+
start_level
|
199
|
+
end
|
200
|
+
|
201
|
+
# Restart from level 1.
|
202
|
+
# @return [PlayerAction|nil] the user's action for console window
|
203
|
+
# interface, or nil for GUI.
|
204
|
+
def restart_set
|
205
|
+
@level_number = 1
|
73
206
|
start_level
|
74
207
|
end
|
75
208
|
|
76
209
|
# Load a new set of levels and start its first level.
|
77
210
|
# @param [String] setname the name of the set (with .xsb extension)
|
78
|
-
# @return [
|
211
|
+
# @return [PlayerAction|nil] the user's action for console window
|
212
|
+
# interface, or nil for GUI.
|
79
213
|
def load_a_new_set setname
|
80
|
-
title = nil
|
81
|
-
error = nil
|
82
214
|
begin
|
83
215
|
@level_loader = LevelLoader.new setname
|
84
216
|
@level_number = 1
|
@@ -87,32 +219,20 @@ module RSokoban
|
|
87
219
|
rescue NoFileError
|
88
220
|
error = "Error, no such file : #{setname}"
|
89
221
|
end
|
90
|
-
@ui
|
91
|
-
|
222
|
+
if @ui
|
223
|
+
@ui.get_action(:type=>:start, :map=>@level.map, :title=>title, :error=>error, :set=>@level_loader.title,
|
224
|
+
:number=>@level_number, :total=>@level_loader.size)
|
225
|
+
end
|
92
226
|
end
|
93
227
|
|
94
|
-
|
95
|
-
# @param [Fixnum] num the number of the set (base 1)
|
96
|
-
# @return [Object] the user's {action}[Console#get_action]
|
97
|
-
def load_level num
|
98
|
-
@level_number = num
|
99
|
-
start_level
|
100
|
-
end
|
228
|
+
private
|
101
229
|
|
102
|
-
#
|
230
|
+
# Restart the current level
|
103
231
|
# @return [Object] the user's {action}[Console#get_action]
|
104
|
-
def
|
105
|
-
|
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)
|
109
|
-
rescue LevelNumberTooHighError
|
110
|
-
@ui.get_action(:type=>:end_of_set, :map=>Map.new)
|
111
|
-
end
|
232
|
+
def try_again
|
233
|
+
start_level
|
112
234
|
end
|
113
235
|
|
114
236
|
end
|
115
237
|
|
116
|
-
|
117
|
-
|
118
238
|
end
|