RSokoban 0.74 → 0.76
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 +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
|