RSokoban 0.74 → 0.76
Sign up to get free protection for your applications and to get access to all the features.
- data/NEWS +33 -1
- data/README.rdoc +21 -14
- data/TODO +6 -12
- data/VERSION +1 -1
- data/bin/rsokoban +40 -3
- data/data/big.xsb +35 -0
- data/lib/rsokoban/config.rb +34 -0
- data/lib/rsokoban/crate.rb +4 -5
- data/lib/rsokoban/exception.rb +2 -0
- data/lib/rsokoban/game/game.rb +153 -0
- data/lib/rsokoban/game/game_curses.rb +11 -0
- data/lib/rsokoban/game/game_factory.rb +23 -0
- data/lib/rsokoban/game/game_gui.rb +27 -0
- data/lib/rsokoban/game/game_portable.rb +10 -0
- data/lib/rsokoban/game/game_tk.rb +11 -0
- data/lib/rsokoban/game/game_ui.rb +81 -0
- data/lib/rsokoban/game.rb +7 -238
- data/lib/rsokoban/install.rb +22 -0
- data/lib/rsokoban/layered_map.rb +139 -0
- data/lib/rsokoban/level.rb +112 -166
- data/lib/rsokoban/level_set.rb +4 -4
- data/lib/rsokoban/man.rb +3 -4
- data/lib/rsokoban/map.rb +91 -15
- data/lib/rsokoban/move_result.rb +2 -2
- data/lib/rsokoban/option.rb +4 -4
- data/lib/rsokoban/position.rb +1 -1
- data/lib/rsokoban/raw_level.rb +11 -2
- data/lib/rsokoban/record.rb +51 -0
- data/lib/rsokoban/set_loader.rb +108 -0
- data/lib/rsokoban/ui/base_ui.rb +1 -0
- data/lib/rsokoban/ui/console.rb +56 -25
- data/lib/rsokoban/ui/curses_console.rb +59 -33
- data/lib/rsokoban/ui/player_action.rb +13 -7
- data/lib/rsokoban/ui/skin.rb +105 -0
- data/lib/rsokoban/ui/tk_dialogs.rb +104 -18
- data/lib/rsokoban/ui/tk_ui.rb +410 -233
- data/lib/rsokoban/ui.rb +1 -0
- data/lib/rsokoban.rb +14 -2
- data/skins/AntiqueDesk/crate.bmp +0 -0
- data/skins/AntiqueDesk/crate_store.bmp +0 -0
- data/skins/AntiqueDesk/floor.bmp +0 -0
- data/skins/AntiqueDesk/man_down.bmp +0 -0
- data/skins/AntiqueDesk/man_left.bmp +0 -0
- data/skins/AntiqueDesk/man_right.bmp +0 -0
- data/skins/AntiqueDesk/man_store_down.bmp +0 -0
- data/skins/AntiqueDesk/man_store_left.bmp +0 -0
- data/skins/AntiqueDesk/man_store_right.bmp +0 -0
- data/skins/AntiqueDesk/man_store_up.bmp +0 -0
- data/skins/AntiqueDesk/man_up.bmp +0 -0
- data/skins/AntiqueDesk/outside.bmp +0 -0
- data/skins/AntiqueDesk/skin.conf +3 -0
- data/skins/AntiqueDesk/store.bmp +0 -0
- data/skins/AntiqueDesk/wall.bmp +0 -0
- data/skins/BlueGranite/outside.bmp +0 -0
- data/skins/BlueGranite/skin.conf +3 -0
- data/skins/HeavyMetal/crate.bmp +0 -0
- data/skins/HeavyMetal/crate_store.bmp +0 -0
- data/skins/HeavyMetal/floor.bmp +0 -0
- data/skins/HeavyMetal/man.bmp +0 -0
- data/skins/HeavyMetal/man_store.bmp +0 -0
- data/skins/HeavyMetal/outside.bmp +0 -0
- data/skins/HeavyMetal/skin.conf +3 -0
- data/skins/HeavyMetal/store.bmp +0 -0
- data/skins/HeavyMetal/wall.bmp +0 -0
- data/skins/HeavyMetal/wall_d.bmp +0 -0
- data/skins/HeavyMetal/wall_dl.bmp +0 -0
- data/skins/HeavyMetal/wall_dlr.bmp +0 -0
- data/skins/HeavyMetal/wall_dr.bmp +0 -0
- data/skins/HeavyMetal/wall_l.bmp +0 -0
- data/skins/HeavyMetal/wall_lr.bmp +0 -0
- data/skins/HeavyMetal/wall_r.bmp +0 -0
- data/skins/HeavyMetal/wall_u.bmp +0 -0
- data/skins/HeavyMetal/wall_ud.bmp +0 -0
- data/skins/HeavyMetal/wall_udl.bmp +0 -0
- data/skins/HeavyMetal/wall_udlr.bmp +0 -0
- data/skins/HeavyMetal/wall_udr.bmp +0 -0
- data/skins/HeavyMetal/wall_ul.bmp +0 -0
- data/skins/HeavyMetal/wall_ulr.bmp +0 -0
- data/skins/HeavyMetal/wall_ur.bmp +0 -0
- data/test/record/original.yaml +2 -0
- data/test/tc_game.rb +24 -15
- data/test/tc_game_factory.rb +40 -0
- data/test/tc_game_gui.rb +26 -0
- data/test/tc_game_ui.rb +153 -0
- data/test/tc_install.rb +12 -0
- data/test/tc_layered_map.rb +105 -0
- data/test/tc_level.rb +109 -107
- data/test/tc_level_set.rb +4 -4
- data/test/tc_map.rb +46 -10
- data/test/tc_record.rb +100 -0
- data/test/{tc_level_loader.rb → tc_set_loader.rb} +25 -24
- data/test/test.rb +9 -2
- data/test/ui/tc_skin.rb +71 -0
- metadata +89 -26
- data/lib/rsokoban/level_loader.rb +0 -81
- data/lib/rsokoban/ui/tk_box.rb +0 -21
- data/skins/default/outside.bmp +0 -0
- data/skins/default/readme +0 -1
- /data/skins/{default → BlueGranite}/crate.bmp +0 -0
- /data/skins/{default → BlueGranite}/crate_store.bmp +0 -0
- /data/skins/{default → BlueGranite}/floor.bmp +0 -0
- /data/skins/{default → BlueGranite}/man_down.bmp +0 -0
- /data/skins/{default → BlueGranite}/man_left.bmp +0 -0
- /data/skins/{default → BlueGranite}/man_right.bmp +0 -0
- /data/skins/{default → BlueGranite}/man_store_down.bmp +0 -0
- /data/skins/{default → BlueGranite}/man_store_left.bmp +0 -0
- /data/skins/{default → BlueGranite}/man_store_right.bmp +0 -0
- /data/skins/{default → BlueGranite}/man_store_up.bmp +0 -0
- /data/skins/{default → BlueGranite}/man_up.bmp +0 -0
- /data/skins/{default → BlueGranite}/store.bmp +0 -0
- /data/skins/{default → BlueGranite}/wall.bmp +0 -0
data/lib/rsokoban/game.rb
CHANGED
@@ -1,238 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
# Construct a new game that you can later run.
|
10
|
-
# @param [:curses|:portable|:tk] ui_as_symbol the user interface for the game
|
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
|
15
|
-
@level_number = 1
|
16
|
-
case ui_as_symbol
|
17
|
-
when :curses
|
18
|
-
require "rsokoban/ui/curses_console"
|
19
|
-
@ui = UI::CursesConsole.new
|
20
|
-
when :portable
|
21
|
-
require "rsokoban/ui/console"
|
22
|
-
@ui = UI::Console.new
|
23
|
-
when :tk
|
24
|
-
require "rsokoban/ui/tk_ui"
|
25
|
-
require "rsokoban/ui/tk_box"
|
26
|
-
require "rsokoban/ui/tk_dialogs"
|
27
|
-
@gui = UI::TkUI.new self
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
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.
|
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
|
47
|
-
player_action = start_level
|
48
|
-
loop do
|
49
|
-
if player_action.level_number?
|
50
|
-
player_action = load_level player_action.action
|
51
|
-
next
|
52
|
-
elsif player_action.set_name?
|
53
|
-
player_action = load_a_new_set player_action.action
|
54
|
-
next
|
55
|
-
elsif player_action.quit?
|
56
|
-
break
|
57
|
-
elsif player_action.next?
|
58
|
-
player_action = next_level
|
59
|
-
next
|
60
|
-
elsif player_action.retry?
|
61
|
-
player_action = try_again
|
62
|
-
next
|
63
|
-
elsif player_action.move?
|
64
|
-
result = @level.move(player_action.action)
|
65
|
-
elsif player_action.undo?
|
66
|
-
result = @level.undo
|
67
|
-
end
|
68
|
-
|
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])
|
73
|
-
else
|
74
|
-
player_action = @ui.get_action(:type=>:display, :map=>@level.map, :error=>result[:message])
|
75
|
-
end
|
76
|
-
end
|
77
|
-
true
|
78
|
-
end
|
79
|
-
|
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
|
183
|
-
|
184
|
-
# Load and start the next level of the set
|
185
|
-
# @return [PlayerAction|nil] the user's action for console window
|
186
|
-
# interface, or nil for GUI.
|
187
|
-
def next_level
|
188
|
-
@level_number += 1
|
189
|
-
start_level
|
190
|
-
end
|
191
|
-
|
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
|
206
|
-
start_level
|
207
|
-
end
|
208
|
-
|
209
|
-
# Load a new set of levels and start its first level.
|
210
|
-
# @param [String] setname the name of the set (with .xsb extension)
|
211
|
-
# @return [PlayerAction|nil] the user's action for console window
|
212
|
-
# interface, or nil for GUI.
|
213
|
-
def load_a_new_set setname
|
214
|
-
begin
|
215
|
-
@level_loader = LevelLoader.new setname
|
216
|
-
@level_number = 1
|
217
|
-
@level = @level_loader.level(@level_number)
|
218
|
-
title = @level.title
|
219
|
-
rescue NoFileError
|
220
|
-
error = "Error, no such file : #{setname}"
|
221
|
-
end
|
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
|
226
|
-
end
|
227
|
-
|
228
|
-
private
|
229
|
-
|
230
|
-
# Restart the current level
|
231
|
-
# @return [Object] the user's {action}[Console#get_action]
|
232
|
-
def try_again
|
233
|
-
start_level
|
234
|
-
end
|
235
|
-
|
236
|
-
end
|
237
|
-
|
238
|
-
end
|
1
|
+
require "rsokoban/game/game"
|
2
|
+
require "rsokoban/game/game_ui"
|
3
|
+
require "rsokoban/game/game_gui"
|
4
|
+
require "rsokoban/game/game_factory"
|
5
|
+
require "rsokoban/game/game_portable"
|
6
|
+
require "rsokoban/game/game_curses"
|
7
|
+
require "rsokoban/game/game_tk"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "yaml"
|
3
|
+
|
4
|
+
module RSokoban
|
5
|
+
|
6
|
+
class Install
|
7
|
+
|
8
|
+
def self.create_folder path
|
9
|
+
FileUtils.makedirs path
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.create_config path
|
13
|
+
self.create_folder path
|
14
|
+
config = {'skin' => 'BlueGranite', 'set' => 'microban.xsb', 'level' => 1}
|
15
|
+
f = File.new(File.join(path, 'config'), "w")
|
16
|
+
f.write config.to_yaml
|
17
|
+
f.close
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module RSokoban
|
2
|
+
|
3
|
+
# I separate elements of the map from the floor/wall.
|
4
|
+
# @since 0.74.1
|
5
|
+
class LayeredMap
|
6
|
+
# @return [Array<String>] map with only walls and floor
|
7
|
+
attr_reader :floor
|
8
|
+
|
9
|
+
# @return [Man]
|
10
|
+
attr_reader :man
|
11
|
+
|
12
|
+
# @return [Array<Crate>]
|
13
|
+
attr_reader :crates
|
14
|
+
|
15
|
+
# @return [Array<Storage>]
|
16
|
+
attr_reader :storages
|
17
|
+
|
18
|
+
# @param [Map|Array<String>] map
|
19
|
+
def initialize map
|
20
|
+
raise ArgumentError unless [Map, Array].include?(map.class)
|
21
|
+
@map = map
|
22
|
+
@floor = nil
|
23
|
+
@map_array = []
|
24
|
+
@man = nil
|
25
|
+
@crates = []
|
26
|
+
@storages = []
|
27
|
+
init_floor
|
28
|
+
init_man
|
29
|
+
init_crates_and_storages
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get an instant map of the game.
|
33
|
+
# @return [Array<String>] the map, after X turns of game.
|
34
|
+
def map_as_array
|
35
|
+
@map_array = init_floor
|
36
|
+
draw_crates
|
37
|
+
draw_storages
|
38
|
+
draw_man
|
39
|
+
@map_array
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get the content of box x_coord, y_coord
|
43
|
+
# @param [Fixnum] x_coord x coordinate in the map
|
44
|
+
# @param [Fixnum] y_coord y coordinate in the map
|
45
|
+
# @return [' ' | '#' | '.' | 'o' | '*']
|
46
|
+
# @todo I think we can optimize this algo
|
47
|
+
# @todo should accept too only one argument: a Position (or an object inherited from Position)
|
48
|
+
def what_is_on x_coord, y_coord
|
49
|
+
return WALL if (@floor[y_coord][x_coord]).chr == WALL
|
50
|
+
|
51
|
+
position = Position.new(x_coord, y_coord)
|
52
|
+
if @storages.include?(position)
|
53
|
+
return STORAGE unless @crates.include?(position)
|
54
|
+
CRATE_ON_STORAGE
|
55
|
+
elsif @crates.include?(position)
|
56
|
+
CRATE
|
57
|
+
else
|
58
|
+
FLOOR
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Draw the crates for map output
|
66
|
+
def draw_crates
|
67
|
+
@crates.each {|crate| @map_array[crate.y][crate.x] = what_is_on(crate.x, crate.y) }
|
68
|
+
end
|
69
|
+
|
70
|
+
# Draw the storages location for map output
|
71
|
+
def draw_storages
|
72
|
+
@storages.each {|st| @map_array[st.y][st.x] = what_is_on(st.x, st.y) }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Draw the man for map output
|
76
|
+
def draw_man
|
77
|
+
box = what_is_on @man.x, @man.y
|
78
|
+
put_man_in_map if box == FLOOR
|
79
|
+
put_man_on_storage_in_map if box == STORAGE
|
80
|
+
end
|
81
|
+
|
82
|
+
def put_man_in_map
|
83
|
+
@map_array[@man.y][@man.x] = MAN
|
84
|
+
end
|
85
|
+
|
86
|
+
def put_man_on_storage_in_map
|
87
|
+
@map_array[@man.y][@man.x] = MAN_ON_STORAGE
|
88
|
+
end
|
89
|
+
|
90
|
+
# Removes all storages locations, all crates and the man, leaving only walls and floor.
|
91
|
+
#
|
92
|
+
# @return [Array<String>] map with only walls and floor
|
93
|
+
def init_floor
|
94
|
+
@floor = []
|
95
|
+
@map.each {|row|
|
96
|
+
@floor.push row.tr("#{STORAGE}#{CRATE}#{MAN}#{CRATE_ON_STORAGE}", FLOOR)
|
97
|
+
}
|
98
|
+
@floor
|
99
|
+
end
|
100
|
+
|
101
|
+
# Find the man's position, at the begining of the level.
|
102
|
+
def init_man
|
103
|
+
x_coord = y_coord = 0
|
104
|
+
@map.each {|row|
|
105
|
+
if row.include?(MAN)
|
106
|
+
x_coord = row.index(MAN)
|
107
|
+
break
|
108
|
+
elsif row.include?(MAN_ON_STORAGE)
|
109
|
+
x_coord = row.index(MAN_ON_STORAGE)
|
110
|
+
break
|
111
|
+
end
|
112
|
+
y_coord += 1
|
113
|
+
}
|
114
|
+
@man = Man.new x_coord, y_coord
|
115
|
+
end
|
116
|
+
|
117
|
+
# Find position of crates and storages, at the begining of the level.
|
118
|
+
def init_crates_and_storages
|
119
|
+
y_coord = 0
|
120
|
+
@map.each do |line|
|
121
|
+
count = 0
|
122
|
+
line.each_char do |char|
|
123
|
+
if char == CRATE
|
124
|
+
@crates.push Crate.new(count, y_coord)
|
125
|
+
elsif char == STORAGE or char == MAN_ON_STORAGE
|
126
|
+
@storages.push Storage.new(count, y_coord)
|
127
|
+
elsif char == CRATE_ON_STORAGE
|
128
|
+
@crates.push Crate.new(count, y_coord)
|
129
|
+
@storages.push Storage.new(count, y_coord)
|
130
|
+
end
|
131
|
+
count += 1
|
132
|
+
end
|
133
|
+
y_coord += 1
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|