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/ui/tk_ui.rb
CHANGED
@@ -1,19 +1,45 @@
|
|
1
1
|
module RSokoban::UI
|
2
2
|
|
3
3
|
# I am a GUI using tk library.
|
4
|
+
#
|
5
|
+
# The grid is:
|
6
|
+
# * a frame with some labels (game info)
|
7
|
+
# * a frame with some buttons (menu shortcuts)
|
8
|
+
# * a frame with the rendered map
|
9
|
+
#
|
4
10
|
# @note I need the tk-img extension library.
|
11
|
+
# @note This code is untestable and untested. In fact, I don't know HOW to test it !
|
5
12
|
# @since 0.73
|
6
|
-
# @todo need some examples and more documentation for private methods
|
7
13
|
class TkUI
|
8
14
|
include RSokoban
|
9
15
|
|
16
|
+
# Number of maximum displayed cells in a row
|
17
|
+
MAP_WIDTH = 19
|
18
|
+
# Number of maximum displayed cells in a column
|
19
|
+
MAP_HEIGHT = 16
|
20
|
+
|
10
21
|
# Build and initialize a GUI with the Tk tool kit.
|
11
22
|
# @param [Game] game Where we get the logic.
|
12
23
|
def initialize game
|
13
24
|
@game = game
|
14
25
|
@last_move = :up
|
15
|
-
@
|
16
|
-
|
26
|
+
@images = {}
|
27
|
+
@x_bounds = []
|
28
|
+
@y_bounds = []
|
29
|
+
@cell_size = 30
|
30
|
+
init_root
|
31
|
+
Menu.new(@tk_root, self)
|
32
|
+
@tk_frame_label = FrameOfLabels.new @tk_root
|
33
|
+
|
34
|
+
config = Config.new
|
35
|
+
skin_name = config['skin']
|
36
|
+
load_skin Skin.new.path_of_skin skin_name
|
37
|
+
|
38
|
+
#load_skin File.join($RSOKOBAN_PATH, 'skins', 'default')
|
39
|
+
init_map
|
40
|
+
@tk_frame_button = FrameOfButtons.new @tk_root
|
41
|
+
make_binding
|
42
|
+
|
17
43
|
start_level
|
18
44
|
end
|
19
45
|
|
@@ -23,7 +49,30 @@ module RSokoban::UI
|
|
23
49
|
Tk.mainloop
|
24
50
|
end
|
25
51
|
|
26
|
-
|
52
|
+
def undo
|
53
|
+
result = @game.undo
|
54
|
+
@tk_frame_label.update_move_information @game
|
55
|
+
display_update_after_undo
|
56
|
+
end
|
57
|
+
|
58
|
+
def my_redo
|
59
|
+
result = @game.redo
|
60
|
+
@tk_frame_label.update_move_information @game
|
61
|
+
display_update_after_undo
|
62
|
+
end
|
63
|
+
|
64
|
+
def help
|
65
|
+
HelpDialog.new(@tk_root, "RSokoban Help")
|
66
|
+
end
|
67
|
+
|
68
|
+
def about
|
69
|
+
text = "RSokoban #{File.read($RSOKOBAN_PATH + '/VERSION').strip} \n"
|
70
|
+
text += "This is free software !\n"
|
71
|
+
text += "Copyright 2011, Xavier Nayrac\n"
|
72
|
+
text += "Licensed under the GPL-3\n"
|
73
|
+
text += "Contact: xavier.nayrac@gmail.com"
|
74
|
+
Tk::messageBox :message => text, :title => 'About'
|
75
|
+
end
|
27
76
|
|
28
77
|
def next_level
|
29
78
|
begin
|
@@ -44,10 +93,10 @@ module RSokoban::UI
|
|
44
93
|
end
|
45
94
|
|
46
95
|
def load_level
|
47
|
-
|
48
|
-
return unless
|
96
|
+
dial = TkLevelDialog.new(@tk_root, "Load a level")
|
97
|
+
return unless dial.ok?
|
49
98
|
begin
|
50
|
-
@game.load_level
|
99
|
+
@game.load_level dial.value
|
51
100
|
init_level
|
52
101
|
rescue LevelNumberTooHighError
|
53
102
|
Tk::messageBox :message => "Sorry, no level ##{@game.level_number} in this set."
|
@@ -55,244 +104,260 @@ module RSokoban::UI
|
|
55
104
|
end
|
56
105
|
|
57
106
|
def load_set
|
58
|
-
|
59
|
-
return unless
|
60
|
-
return if
|
61
|
-
@game.load_a_new_set
|
107
|
+
dial = SetDialog.new(@tk_root, "Load a set")
|
108
|
+
return unless dial.ok?
|
109
|
+
return if dial.value.nil?
|
110
|
+
@game.load_a_new_set dial.value
|
62
111
|
init_level
|
63
112
|
end
|
64
113
|
|
65
|
-
|
66
|
-
|
67
|
-
if @
|
68
|
-
|
69
|
-
|
114
|
+
def scroll_right
|
115
|
+
@left_window += 10
|
116
|
+
if @left_window + MAP_WIDTH > @game.level_width
|
117
|
+
@left_window = @game.level_width - MAP_WIDTH
|
118
|
+
end
|
119
|
+
@left_window = 0 if @left_window < 0
|
120
|
+
window_update
|
121
|
+
end
|
122
|
+
|
123
|
+
def scroll_left
|
124
|
+
@left_window -= 10
|
125
|
+
@left_window = 0 if @left_window < 0
|
126
|
+
window_update
|
127
|
+
end
|
128
|
+
|
129
|
+
def scroll_up
|
130
|
+
@top_window -= 10
|
131
|
+
@top_window = 0 if @top_window < 0
|
132
|
+
window_update
|
133
|
+
end
|
134
|
+
|
135
|
+
def scroll_down
|
136
|
+
@top_window += 10
|
137
|
+
if @top_window + MAP_HEIGHT > @game.level_height
|
138
|
+
@top_window = @game.level_height - MAP_HEIGHT
|
70
139
|
end
|
71
|
-
|
140
|
+
@top_window = 0 if @top_window < 0
|
141
|
+
window_update
|
142
|
+
end
|
143
|
+
|
144
|
+
# @since 0.76
|
145
|
+
def select_skin
|
146
|
+
dial = SkinDialog.new(@tk_root, "Change skin")
|
147
|
+
return unless dial.ok?
|
148
|
+
return if dial.value.nil?
|
149
|
+
load_skin dial.value
|
150
|
+
conf = Config.new
|
151
|
+
conf['skin'] = File.basename dial.value
|
152
|
+
init_level
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def init_level
|
158
|
+
width, height = get_dimension_in_cells
|
159
|
+
compute_bounds width, height
|
160
|
+
@top_window = @left_window = 0
|
161
|
+
@tk_frame_label.reset_labels @game
|
162
|
+
@tk_frame_render.geometry width, height, @cell_size
|
72
163
|
reset_map
|
73
164
|
display_initial
|
74
165
|
end
|
75
166
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
167
|
+
def get_dimension_in_cells
|
168
|
+
width = @game.level_width > MAP_WIDTH ? MAP_WIDTH : @game.level_width
|
169
|
+
height = @game.level_height > MAP_HEIGHT ? MAP_HEIGHT : @game.level_height
|
170
|
+
[width, height]
|
171
|
+
end
|
172
|
+
|
173
|
+
def compute_bounds width, height
|
174
|
+
@x_bounds = []
|
175
|
+
(0...width).each {|idx| @x_bounds.push idx * @cell_size}
|
176
|
+
@y_bounds = []
|
177
|
+
(0...height).each {|idx| @y_bounds.push idx * @cell_size}
|
178
|
+
end
|
179
|
+
|
180
|
+
# @todo no needs to update wall tiles
|
181
|
+
def display update_locations
|
182
|
+
return if man_goes_offscreen?
|
183
|
+
map = window
|
184
|
+
update_locations.each do |x, y|
|
185
|
+
next if y >= @top_window + MAP_HEIGHT
|
186
|
+
next if x >= @left_window + MAP_WIDTH
|
187
|
+
next if x < 0 or y < 0
|
188
|
+
# I think there should be something wrong with the tests above.
|
189
|
+
# Else I don't need the two following tests.
|
190
|
+
#row = window[y]
|
191
|
+
row = map[y]
|
192
|
+
next if row.nil?
|
193
|
+
cell = row[x]
|
194
|
+
next if cell.nil?
|
195
|
+
display_cell_taking_care_of_content cell.chr, x, y
|
93
196
|
end
|
94
197
|
end
|
95
198
|
|
199
|
+
# We need only to update man's location and north, south, west
|
200
|
+
# and east of him.
|
201
|
+
def display_update
|
202
|
+
x = @game.man_x - @left_window
|
203
|
+
y = @game.man_y - @top_window
|
204
|
+
display [[x,y], [x+1,y], [x-1,y], [x,y+1], [x,y-1]]
|
205
|
+
end
|
206
|
+
|
96
207
|
def display_update_after_undo
|
97
|
-
x = @game.man_x
|
98
|
-
y = @game.man_y
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
208
|
+
x = @game.man_x - @left_window
|
209
|
+
y = @game.man_y - @top_window
|
210
|
+
display [[x,y], [x+1,y], [x+2,y], [x-1,y], [x-2,y], [x,y+1], [x,y+2], [x,y-1], [x,y-2]]
|
211
|
+
end
|
212
|
+
|
213
|
+
# If the man goes offscreen, do an autoscrolling.
|
214
|
+
# @return [Boolean]
|
215
|
+
def man_goes_offscreen?
|
216
|
+
if @game.man_x < @left_window
|
217
|
+
scroll_left
|
218
|
+
elsif @game.man_x >= @left_window + MAP_WIDTH
|
219
|
+
scroll_right
|
220
|
+
elsif @game.man_y < @top_window
|
221
|
+
scroll_up
|
222
|
+
elsif @game.man_y >= @top_window + MAP_HEIGHT
|
223
|
+
scroll_down
|
224
|
+
else
|
225
|
+
return false
|
104
226
|
end
|
227
|
+
true
|
228
|
+
end
|
229
|
+
|
230
|
+
def render_cell image, x, y
|
231
|
+
@render.copy(@images[image], :to => [@x_bounds[x], @y_bounds[y]])
|
105
232
|
end
|
106
233
|
|
107
234
|
# Display the initial map on screen.
|
108
235
|
def display_initial
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
line.each_char do |char|
|
115
|
-
display_cell_taking_care_of_outside char, x, y
|
116
|
-
x += 1
|
236
|
+
window.each_with_index do |row, y_coord|
|
237
|
+
x_coord = 0
|
238
|
+
row.each_char do |char|
|
239
|
+
display_cell_taking_care_of_content char, x_coord, y_coord
|
240
|
+
x_coord += 1
|
117
241
|
end
|
118
|
-
y += 1
|
119
242
|
end
|
120
243
|
end
|
121
244
|
|
122
|
-
|
245
|
+
# Get the game map through a window
|
246
|
+
# @todo refactor. All this stuff (#window, @top_window, MAP_WIDTH, etc) don't belong to this
|
247
|
+
# class. Maybe it belongs to Map or LayeredMap or a new class named Window ?
|
248
|
+
def window
|
249
|
+
ret = []
|
250
|
+
@game.map_as_array.each_with_index do |row, y|
|
251
|
+
if y >= @top_window and y < @top_window + MAP_HEIGHT
|
252
|
+
ret.push row[@left_window, MAP_WIDTH]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
ret
|
256
|
+
end
|
257
|
+
|
258
|
+
def window_update
|
259
|
+
reset_map
|
260
|
+
display_initial
|
261
|
+
end
|
262
|
+
|
263
|
+
def display_cell_taking_care_of_content char, x_coord, y_coord
|
123
264
|
case char
|
124
|
-
when WALL then
|
125
|
-
when FLOOR then
|
126
|
-
when CRATE then
|
127
|
-
when STORAGE then
|
128
|
-
when MAN then display_man_at
|
129
|
-
when MAN_ON_STORAGE then display_man_on_storage_at
|
130
|
-
when CRATE_ON_STORAGE then
|
265
|
+
when WALL then render_wall x_coord, y_coord
|
266
|
+
when FLOOR then render_cell :floor, x_coord, y_coord
|
267
|
+
when CRATE then render_cell :crate, x_coord, y_coord
|
268
|
+
when STORAGE then render_cell :store, x_coord, y_coord
|
269
|
+
when MAN then display_man_at x_coord, y_coord
|
270
|
+
when MAN_ON_STORAGE then display_man_on_storage_at x_coord, y_coord
|
271
|
+
when CRATE_ON_STORAGE then render_cell :crate_store, x_coord, y_coord
|
131
272
|
end
|
132
273
|
end
|
133
274
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
275
|
+
# taking care of neighbors
|
276
|
+
# @todo this is truly a poorly design method
|
277
|
+
def render_wall x, y
|
278
|
+
up_down_left_right = []
|
279
|
+
near = [[x,y-1], [x,y+1], [x-1,y], [x+1,y]]
|
280
|
+
map = window
|
281
|
+
near.each do |xx, yy|
|
282
|
+
if map[yy].nil? or yy < 0
|
283
|
+
up_down_left_right << false
|
284
|
+
elsif map[yy][xx].nil? or xx < 0
|
285
|
+
up_down_left_right << false
|
286
|
+
elsif map[yy][xx].chr == WALL
|
287
|
+
up_down_left_right << true
|
288
|
+
else
|
289
|
+
up_down_left_right << false
|
142
290
|
end
|
143
|
-
|
291
|
+
end
|
292
|
+
|
293
|
+
case up_down_left_right
|
294
|
+
when [false, false, false, false] then render_cell :wall, x, y
|
295
|
+
when [true, false, false, false] then render_cell :wall_u, x, y
|
296
|
+
when [false, true, false, false] then render_cell :wall_d, x, y
|
297
|
+
when [false, false, true, false] then render_cell :wall_l, x, y
|
298
|
+
when [false, false, false, true] then render_cell :wall_r, x, y
|
299
|
+
when [true, true, false, false] then render_cell :wall_ud, x, y
|
300
|
+
when [true, false, true, false] then render_cell :wall_ul, x, y
|
301
|
+
when [true, false, false, true] then render_cell :wall_ur, x, y
|
302
|
+
when [false, true, true, false] then render_cell :wall_dl, x, y
|
303
|
+
when [false, true, false, true] then render_cell :wall_dr, x, y
|
304
|
+
when [false, false, true, true] then render_cell :wall_lr, x, y
|
305
|
+
when [true, true, true, false] then render_cell :wall_udl, x, y
|
306
|
+
when [true, true, false, true] then render_cell :wall_udr, x, y
|
307
|
+
when [true, false, true, true] then render_cell :wall_ulr, x, y
|
308
|
+
when [false, true, true, true] then render_cell :wall_dlr, x, y
|
309
|
+
when [true, true, true, true] then render_cell :wall_udlr, x, y
|
310
|
+
end
|
144
311
|
end
|
145
312
|
|
146
313
|
def display_man_at x, y
|
147
314
|
case @last_move
|
148
|
-
when :up then
|
149
|
-
when :down then
|
150
|
-
when :left then
|
315
|
+
when :up then render_cell :man_up, x, y
|
316
|
+
when :down then render_cell :man_down, x, y
|
317
|
+
when :left then render_cell :man_left, x, y
|
151
318
|
else
|
152
|
-
|
319
|
+
render_cell :man_right, x, y
|
153
320
|
end
|
154
321
|
end
|
155
322
|
|
156
323
|
def display_man_on_storage_at x, y
|
157
324
|
case @last_move
|
158
|
-
when :up then
|
159
|
-
when :down then
|
160
|
-
when :left then
|
325
|
+
when :up then render_cell :man_store_up, x, y
|
326
|
+
when :down then render_cell :man_store_down, x, y
|
327
|
+
when :left then render_cell :man_store_left, x, y
|
161
328
|
else
|
162
|
-
|
329
|
+
render_cell :man_store_right, x, y
|
163
330
|
end
|
164
331
|
end
|
165
332
|
|
166
|
-
def init_gui
|
167
|
-
init_root
|
168
|
-
init_menu
|
169
|
-
init_labels
|
170
|
-
preload_images
|
171
|
-
init_map
|
172
|
-
init_buttons
|
173
|
-
make_binding
|
174
|
-
end
|
175
|
-
|
176
333
|
def init_root
|
177
334
|
@tk_root = TkRoot.new do
|
178
335
|
title "RSokoban " + File.read($RSOKOBAN_PATH + '/VERSION').strip
|
179
|
-
minsize(400, 400)
|
180
336
|
resizable(false, false)
|
181
337
|
end
|
182
338
|
end
|
183
339
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
@
|
188
|
-
|
189
|
-
file = TkMenu.new(menubar)
|
190
|
-
helpm = TkMenu.new(menubar)
|
191
|
-
menubar.add :cascade, :menu => file, :label => 'File'
|
192
|
-
menubar.add :cascade, :menu => helpm, :label => 'Help'
|
193
|
-
|
194
|
-
file.add :command, :label => 'Load level', :command => proc{load_level}, :accelerator => 'Ctrl+L'
|
195
|
-
file.add :command, :label => 'Load set', :command => proc{load_set}
|
196
|
-
file.add :separator
|
197
|
-
file.add :command, :label => 'Undo', :command => proc{undo}, :accelerator => 'Ctrl+Z'
|
198
|
-
file.add :command, :label => 'Redo', :command => proc{my_redo}, :accelerator => 'Ctrl+Y'
|
199
|
-
file.add :command, :label => 'Restart level', :command => proc{start_level}, :accelerator => 'Ctrl+R'
|
200
|
-
file.add :command, :label => 'Next level', :command => proc{next_level}, :accelerator => 'Ctrl+N'
|
201
|
-
file.add :separator
|
202
|
-
file.add :command, :label => 'Quit', :command => proc{exit}
|
203
|
-
|
204
|
-
helpm.add :command, :label => 'Help', :command => proc{help}, :accelerator => 'F1'
|
205
|
-
helpm.add :separator
|
206
|
-
helpm.add :command, :label => 'About', :command => proc{about}
|
207
|
-
end
|
208
|
-
|
209
|
-
def init_labels
|
210
|
-
@tk_frame_label = TkFrame.new(@tk_root) do
|
211
|
-
grid('row' => 0, 'column' => 0, 'columnspan' => 19, 'sticky' => 'w')
|
212
|
-
padx 5
|
213
|
-
pady 5
|
214
|
-
end
|
215
|
-
@tk_label_set = TkLabel.new(@tk_frame_label) do
|
216
|
-
grid('row' => 0, 'column' => 0, 'sticky' => 'w')
|
217
|
-
end
|
218
|
-
@tk_label_level = TkLabel.new(@tk_frame_label) do
|
219
|
-
grid('row'=>1, 'column'=> 0, 'sticky' => 'w')
|
220
|
-
end
|
221
|
-
@tk_label_move = TkLabel.new(@tk_frame_label) do
|
222
|
-
grid('row'=>2, 'column'=>0, 'sticky' => 'w')
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
def reset_labels
|
227
|
-
@tk_label_set.configure('text' => "Set: #{@game.set_title}")
|
228
|
-
@tk_label_level.configure('text' => "Level: #{@game.level_title} (#{@game.level_number}/#{@game.set_size})")
|
229
|
-
update_move_information
|
230
|
-
end
|
231
|
-
|
232
|
-
def update_move_information
|
233
|
-
@tk_label_move.configure('text' => "Move: #{@game.move_number}")
|
234
|
-
end
|
235
|
-
|
236
|
-
# Build the map of labels. TkBoxs of the game will be displayed on those labels.
|
237
|
-
def init_map
|
238
|
-
row_in_grid = 5
|
239
|
-
(0...16).each {|row_index|
|
240
|
-
row = []
|
241
|
-
(0...19).each {|col_index|
|
242
|
-
label = TkLabel.new(@tk_root) do
|
243
|
-
grid('row'=> row_in_grid, 'column'=> col_index, 'padx' => 0, 'pady' => 0, 'ipadx' => 0, 'ipady' => 0)
|
244
|
-
end
|
245
|
-
label['borderwidth'] = 0
|
246
|
-
row.push label
|
247
|
-
}
|
248
|
-
@tk_map.push row
|
249
|
-
row_in_grid += 1
|
250
|
-
}
|
340
|
+
# Let FrameRender build an image to render game in.
|
341
|
+
# @todo we don't need the @render member.
|
342
|
+
def init_map
|
343
|
+
@tk_frame_render = FrameRender.new @tk_root, @cell_size
|
344
|
+
@render = @tk_frame_render.render
|
251
345
|
reset_map
|
252
346
|
end
|
253
347
|
|
254
348
|
# Reset all the map with 'outside' tile.
|
255
|
-
# @todo little improvement : reload @outside image only if there is something else
|
256
|
-
# in the current map.
|
257
349
|
def reset_map
|
258
|
-
@
|
259
|
-
|
260
|
-
|
261
|
-
end
|
262
|
-
|
263
|
-
def init_buttons
|
264
|
-
@tk_frame_button = TkFrame.new(@tk_root) do
|
265
|
-
grid('row' => 1, 'column' => 0, 'columnspan' => 19, 'sticky' => 'w')
|
266
|
-
padx 5
|
267
|
-
pady 5
|
268
|
-
end
|
269
|
-
@tk_undo_button = TkButton.new(@tk_frame_button) do
|
270
|
-
text 'Undo'
|
271
|
-
grid('row'=> 0, 'column'=> 0)
|
272
|
-
end
|
273
|
-
@tk_redo_button = TkButton.new(@tk_frame_button) do
|
274
|
-
text 'Redo'
|
275
|
-
grid('row'=> 0, 'column'=> 1)
|
276
|
-
end
|
277
|
-
|
278
|
-
@tk_retry_button = TkButton.new(@tk_frame_button) do
|
279
|
-
text 'Retry'
|
280
|
-
grid('row'=> 0, 'column'=> 2)
|
281
|
-
end
|
282
|
-
|
283
|
-
@tk_level_button = TkButton.new(@tk_frame_button) do
|
284
|
-
text 'Level'
|
285
|
-
grid('row'=> 0, 'column'=> 3)
|
286
|
-
end
|
287
|
-
|
288
|
-
@tk_next_level_button = TkButton.new(@tk_frame_button) do
|
289
|
-
text 'Next'
|
290
|
-
grid('row'=> 0, 'column'=> 4)
|
291
|
-
end
|
350
|
+
width = MAP_WIDTH * @cell_size - 1
|
351
|
+
height = MAP_HEIGHT * @cell_size - 1
|
352
|
+
@render.copy(@images[:outside], :to => [0, 0, width, height])
|
292
353
|
end
|
293
354
|
|
294
355
|
# Bind user's actions
|
295
356
|
def make_binding
|
357
|
+
@tk_root.bind('Control-Right') { scroll_right }
|
358
|
+
@tk_root.bind('Control-Left') { scroll_left }
|
359
|
+
@tk_root.bind('Control-Up') { scroll_up }
|
360
|
+
@tk_root.bind('Control-Down') { scroll_down }
|
296
361
|
@tk_root.bind('Up') { move :up }
|
297
362
|
@tk_root.bind('Down') { move :down }
|
298
363
|
@tk_root.bind('Left') { move :left }
|
@@ -303,23 +368,11 @@ module RSokoban::UI
|
|
303
368
|
@tk_root.bind('Control-l') { load_level }
|
304
369
|
@tk_root.bind('Control-n') { next_level }
|
305
370
|
@tk_root.bind('F1') { help }
|
306
|
-
@
|
307
|
-
@
|
308
|
-
@
|
309
|
-
@
|
310
|
-
@
|
311
|
-
end
|
312
|
-
|
313
|
-
def undo
|
314
|
-
result = @game.undo
|
315
|
-
update_move_information
|
316
|
-
display_update_after_undo
|
317
|
-
end
|
318
|
-
|
319
|
-
def my_redo
|
320
|
-
result = @game.redo
|
321
|
-
update_move_information
|
322
|
-
display_update_after_undo
|
371
|
+
@tk_frame_button.undo_button.command { undo }
|
372
|
+
@tk_frame_button.redo_button.command { my_redo }
|
373
|
+
@tk_frame_button.retry_button.command { start_level }
|
374
|
+
@tk_frame_button.level_button.command { load_level }
|
375
|
+
@tk_frame_button.next_level_button.command { next_level }
|
323
376
|
end
|
324
377
|
|
325
378
|
# Send the move to Level and process response.
|
@@ -328,48 +381,172 @@ module RSokoban::UI
|
|
328
381
|
@last_move = symb
|
329
382
|
result = @game.move symb
|
330
383
|
unless result.error?
|
331
|
-
update_move_information
|
384
|
+
@tk_frame_label.update_move_information @game
|
332
385
|
display_update
|
333
386
|
end
|
334
387
|
if result.win?
|
335
|
-
|
388
|
+
message = if @game.update_record
|
389
|
+
"Congratulations ! New record !\n"
|
390
|
+
else
|
391
|
+
"Level completed !\n"
|
392
|
+
end
|
393
|
+
# ask for continue ?
|
394
|
+
response = Tk::messageBox :type => 'yesno', :message => message + "Play next level ?",
|
336
395
|
:icon => 'question', :title => 'You win !', :parent => @tk_root, :default => 'yes'
|
337
396
|
next_level if response == 'yes'
|
338
397
|
start_level if response == 'no'
|
339
398
|
end
|
340
399
|
end
|
341
400
|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
@
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
@man_down = TkBox.new(dir + 'man_down.bmp', @tk_map)
|
350
|
-
@man_left = TkBox.new(dir + 'man_left.bmp', @tk_map)
|
351
|
-
@man_right = TkBox.new(dir + 'man_right.bmp', @tk_map)
|
352
|
-
@crate_store = TkBox.new(dir + 'crate_store.bmp', @tk_map)
|
353
|
-
@man_store_up = TkBox.new(dir + 'man_store_up.bmp', @tk_map)
|
354
|
-
@man_store_down = TkBox.new(dir + 'man_store_down.bmp', @tk_map)
|
355
|
-
@man_store_left = TkBox.new(dir + 'man_store_left.bmp', @tk_map)
|
356
|
-
@man_store_right = TkBox.new(dir + 'man_store_right.bmp', @tk_map)
|
357
|
-
@outside = TkBox.new(dir + 'outside.bmp', @tk_map)
|
401
|
+
# @todo Let the Skin class to give the list of image's names.
|
402
|
+
def load_skin dir
|
403
|
+
skin = Skin.new
|
404
|
+
@cell_size = skin.size_of dir
|
405
|
+
skin.filenames(dir).each do |key, value|
|
406
|
+
@images[key] = TkPhotoImage.new('file' => value)
|
407
|
+
end
|
358
408
|
end
|
359
409
|
|
360
|
-
|
361
|
-
|
410
|
+
end
|
411
|
+
|
412
|
+
# Menu bar attached to tk gui.
|
413
|
+
# @since 0.74.1
|
414
|
+
class Menu
|
415
|
+
|
416
|
+
# @param [TkRoot] tk_root
|
417
|
+
# @param [TkUI] object_root
|
418
|
+
def initialize tk_root, object_root
|
419
|
+
TkOption.add '*tearOff', 0
|
420
|
+
menubar = TkMenu.new(tk_root)
|
421
|
+
menubar[:font] = 'TkMenuFont'
|
422
|
+
tk_root['menu'] = menubar
|
423
|
+
|
424
|
+
file = TkMenu.new(menubar)
|
425
|
+
file[:font] = 'TkMenuFont'
|
426
|
+
window = TkMenu.new(menubar)
|
427
|
+
window[:font] = 'TkMenuFont'
|
428
|
+
options = TkMenu.new(menubar)
|
429
|
+
options[:font] = 'TkMenuFont'
|
430
|
+
helpm = TkMenu.new(menubar)
|
431
|
+
helpm[:font] = 'TkMenuFont'
|
432
|
+
menubar.add :cascade, :menu => file, :label => 'File'
|
433
|
+
menubar.add :cascade, :menu => window, :label => 'Window'
|
434
|
+
menubar.add :cascade, :menu => options, :label => 'Options'
|
435
|
+
menubar.add :cascade, :menu => helpm, :label => 'Help'
|
436
|
+
|
437
|
+
file.add :command, :label => 'Load level', :command => proc{object_root.load_level}, :accelerator => 'Ctrl+L'
|
438
|
+
file.add :command, :label => 'Load set', :command => proc{object_root.load_set}
|
439
|
+
file.add :separator
|
440
|
+
file.add :command, :label => 'Undo', :command => proc{object_root.undo}, :accelerator => 'Ctrl+Z'
|
441
|
+
file.add :command, :label => 'Redo', :command => proc{object_root.my_redo}, :accelerator => 'Ctrl+Y'
|
442
|
+
file.add :command, :label => 'Restart level', :command => proc{object_root.start_level}, :accelerator => 'Ctrl+R'
|
443
|
+
file.add :command, :label => 'Next level', :command => proc{object_root.next_level}, :accelerator => 'Ctrl+N'
|
444
|
+
file.add :separator
|
445
|
+
file.add :command, :label => 'Quit', :command => proc{exit}
|
446
|
+
|
447
|
+
window.add :command, :label => 'Scroll left', :command => proc{object_root.scroll_left}, :accelerator => 'Ctrl+Left'
|
448
|
+
window.add :command, :label => 'Scroll right', :command => proc{object_root.scroll_right}, :accelerator => 'Ctrl+Right'
|
449
|
+
window.add :command, :label => 'Scroll up', :command => proc{object_root.scroll_up}, :accelerator => 'Ctrl+Up'
|
450
|
+
window.add :command, :label => 'Scroll down', :command => proc{object_root.scroll_down}, :accelerator => 'Ctrl+Down'
|
451
|
+
|
452
|
+
options.add :command, :label => 'Change skin...', :command => proc{object_root.select_skin}
|
453
|
+
|
454
|
+
helpm.add :command, :label => 'Help', :command => proc{object_root.help}, :accelerator => 'F1'
|
455
|
+
helpm.add :separator
|
456
|
+
helpm.add :command, :label => 'About', :command => proc{object_root.about}
|
362
457
|
end
|
363
458
|
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
459
|
+
end
|
460
|
+
|
461
|
+
# @since 0.75
|
462
|
+
class FrameRender
|
463
|
+
attr_reader :render
|
464
|
+
|
465
|
+
def initialize tk_root, cell_size
|
466
|
+
@frame = Tk::Tile::Frame.new(tk_root) do
|
467
|
+
grid(:row => 2, :column => 0, :sticky => 'w')
|
468
|
+
padding 5
|
469
|
+
end
|
470
|
+
|
471
|
+
@render_label = TkLabel.new(@frame) do
|
472
|
+
grid(:row => 0, :column => 0, :padx => 0, :pady => 0, :ipadx => 0, :ipady => 0)
|
473
|
+
end
|
474
|
+
@render = TkPhotoImage.new(:height => TkUI::MAP_HEIGHT * cell_size, :width => TkUI::MAP_WIDTH * cell_size)
|
475
|
+
@render_label .configure(:image => @render)
|
476
|
+
end
|
477
|
+
|
478
|
+
def geometry width_in_cells, height_in_cells, cell_size
|
479
|
+
@render[:height] = height_in_cells * cell_size
|
480
|
+
@render[:width] = width_in_cells * cell_size
|
481
|
+
end
|
482
|
+
|
483
|
+
end
|
484
|
+
|
485
|
+
# I am a frame attached to tk gui. I display information through some labels.
|
486
|
+
# @since 0.74.1
|
487
|
+
class FrameOfLabels
|
488
|
+
|
489
|
+
def initialize tk_root
|
490
|
+
@frame = Tk::Tile::Frame.new(tk_root) do
|
491
|
+
grid(:row => 0, :column => 0, :sticky => 'w')
|
492
|
+
padding 5
|
493
|
+
end
|
494
|
+
@label_set = Tk::Tile::Label.new(@frame) do
|
495
|
+
grid(:row => 0, :column => 0, :sticky => 'w')
|
496
|
+
end
|
497
|
+
@label_level = Tk::Tile::Label.new(@frame) do
|
498
|
+
grid(:row => 1, :column => 0, :sticky => 'w')
|
499
|
+
end
|
500
|
+
@label_move = Tk::Tile::Label.new(@frame) do
|
501
|
+
grid(:row => 2, :column => 0, :sticky => 'w')
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
def reset_labels game
|
506
|
+
@label_set.configure('text' => "Set: #{game.set_title}")
|
507
|
+
@label_level.configure('text' => "Level: #{game.level_title} (#{game.level_number}/#{game.set_size}) Record: #{game.record}")
|
508
|
+
update_move_information game
|
509
|
+
end
|
510
|
+
|
511
|
+
def update_move_information game
|
512
|
+
@label_move.configure('text' => "Move: #{game.move_number}")
|
371
513
|
end
|
372
514
|
|
373
515
|
end
|
374
516
|
|
517
|
+
# @since 0.74.1
|
518
|
+
class FrameOfButtons
|
519
|
+
attr_reader :undo_button, :redo_button, :retry_button, :level_button, :next_level_button
|
520
|
+
|
521
|
+
def initialize tk_root
|
522
|
+
@frame = Tk::Tile::Frame.new(tk_root) do
|
523
|
+
grid(:row => 1, :column => 0, :sticky => 'w')
|
524
|
+
padding 5
|
525
|
+
end
|
526
|
+
@undo_button = Tk::Tile::Button.new(@frame) do
|
527
|
+
text 'Undo'
|
528
|
+
grid(:row => 0, :column => 0)
|
529
|
+
end
|
530
|
+
@redo_button = Tk::Tile::Button.new(@frame) do
|
531
|
+
text 'Redo'
|
532
|
+
grid(:row => 0, :column => 1)
|
533
|
+
end
|
534
|
+
|
535
|
+
@retry_button = Tk::Tile::Button.new(@frame) do
|
536
|
+
text 'Retry'
|
537
|
+
grid(:row => 0, :column => 2)
|
538
|
+
end
|
539
|
+
|
540
|
+
@level_button = Tk::Tile::Button.new(@frame) do
|
541
|
+
text 'Level'
|
542
|
+
grid(:row => 0, :column => 3)
|
543
|
+
end
|
544
|
+
|
545
|
+
@next_level_button = Tk::Tile::Button.new(@frame) do
|
546
|
+
text 'Next'
|
547
|
+
grid(:row => 0, :column => 4)
|
548
|
+
end
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
375
552
|
end
|