RSokoban 0.71 → 0.73

Sign up to get free protection for your applications and to get access to all the features.
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,474 @@
1
+ #require 'tk'
2
+ #require 'tkextlib/tkimg'
3
+
4
+ module RSokoban::UI
5
+
6
+ # I am an image of the game who knows how to display itself on an array
7
+ # of TkLabel items.
8
+ # @since 0.73
9
+ class Image
10
+
11
+ # @param [String] file path of image file
12
+ # @param [Array<Array<TkLabel>>] output an array to display myself
13
+ def initialize file, output
14
+ @box = TkPhotoImage.new('file' => file, 'height' => 0, 'width' => 0)
15
+ @output = output
16
+ end
17
+
18
+ # Display myself at x,y coordinate
19
+ def display_at x, y
20
+ @output[y][x].configure('image' => @box)
21
+ end
22
+
23
+ end
24
+
25
+ class LevelDialog < TkToplevel
26
+ def initialize(root, title)
27
+ super(root)
28
+ title(title)
29
+ minsize(200, 100)
30
+ @state = 'CANCEL'
31
+ grab
32
+
33
+ $spinval = TkVariable.new
34
+ @spin = TkSpinbox.new(self) do
35
+ textvariable($spinval)
36
+ grid('row'=>0, 'column'=>0)
37
+ end
38
+ @spin.to(999)
39
+ @spin.from(1)
40
+
41
+ @ok = TkButton.new(self) do
42
+ text 'OK'
43
+ grid('row'=>1, 'column'=>0)
44
+ end
45
+ @ok.command { ok_on_clic }
46
+
47
+ @cancel = TkButton.new(self) do
48
+ text 'Cancel'
49
+ grid('row'=>1, 'column'=>1)
50
+ end
51
+ @cancel.command { cancel_on_clic }
52
+
53
+ wait_destroy
54
+ end
55
+
56
+ def ok_on_clic
57
+ @state = 'OK'
58
+ destroy
59
+ end
60
+
61
+ def cancel_on_clic
62
+ @state = 'CANCEL'
63
+ destroy
64
+ end
65
+
66
+ def ok?
67
+ @state == 'OK'
68
+ end
69
+
70
+ def value
71
+ $spinval.to_i
72
+ end
73
+ end
74
+
75
+ class SetDialog < TkToplevel
76
+ def initialize(root, title)
77
+ super(root)
78
+ title(title)
79
+ minsize(200, 100)
80
+ @state = 'CANCEL'
81
+ grab
82
+
83
+ @value = nil
84
+ @xsb = get_xsb
85
+ $listval = TkVariable.new(@xsb)
86
+
87
+ @list = TkListbox.new(self) do
88
+ width 20
89
+ height 8
90
+ listvariable $listval
91
+ grid('row'=>0, 'column'=>0)
92
+ end
93
+
94
+ @ok = TkButton.new(self) do
95
+ text 'OK'
96
+ grid('row'=>1, 'column'=>0)
97
+ end
98
+ @ok.command { ok_on_clic }
99
+
100
+ @cancel = TkButton.new(self) do
101
+ text 'Cancel'
102
+ grid('row'=>1, 'column'=>1)
103
+ end
104
+ @cancel.command { cancel_on_clic }
105
+
106
+ wait_destroy
107
+ end
108
+
109
+ def ok_on_clic
110
+ @state = 'OK'
111
+ idx = @list.curselection
112
+ unless idx.empty?
113
+ @value = @xsb[idx[0]]
114
+ end
115
+ destroy
116
+ end
117
+
118
+ def cancel_on_clic
119
+ @state = 'CANCEL'
120
+ destroy
121
+ end
122
+
123
+ def ok?
124
+ @state == 'OK'
125
+ end
126
+
127
+ def value
128
+ @value
129
+ end
130
+
131
+ def get_xsb
132
+ current = Dir.pwd
133
+ Dir.chdir $RSOKOBAN_DATA_PATH
134
+ ret = Dir.glob '*.xsb'
135
+ Dir.chdir current
136
+ ret
137
+ end
138
+ end
139
+
140
+ class HelpDialog < TkToplevel
141
+ def initialize(root, title)
142
+ super(root)
143
+ title(title)
144
+ minsize(200, 100)
145
+
146
+ text = TkText.new(self) do
147
+ borderwidth 1
148
+ font TkFont.new('times 12 bold')
149
+ grid('row' => 0, 'column' => 0)
150
+ end
151
+
152
+ help=<<EOS
153
+ Welcome to RSokoban !
154
+
155
+ Goal of Sokoban game is to place each crate on a storage location.
156
+ Move the man using the arrow keys.
157
+ For a more comprehensive help, please visit the wiki at https://github.com/lkdjiin/RSokoban/wiki.
158
+ EOS
159
+
160
+ text.insert 'end', help
161
+
162
+ @ok = TkButton.new(self) do
163
+ text 'OK'
164
+ grid('row'=>1, 'column'=>0)
165
+ end
166
+ @ok.command { ok_on_clic }
167
+ end
168
+
169
+ def ok_on_clic
170
+ destroy
171
+ end
172
+ end
173
+
174
+ # I am a GUI using tk library.
175
+ # @note I need the tk-img extension library.
176
+ # @since 0.73
177
+ # @todo a lot of refactoring is needed. There is a lot of duplicated code with Game,
178
+ # and maybe Level and BaseUI.
179
+ # @todo need some more documentation
180
+ class TkUI
181
+
182
+ def initialize
183
+ @level_loader = RSokoban::LevelLoader.new "microban.xsb"
184
+ @level_number = 1
185
+ @level = @level_loader.level(@level_number)
186
+ @move = 0
187
+ @last_move = :up
188
+ @tk_map = []
189
+ init_gui
190
+ display
191
+ Tk.mainloop
192
+ end
193
+
194
+ private
195
+
196
+ def next_level
197
+ @level_number += 1
198
+ start_level
199
+ end
200
+
201
+ def start_level
202
+ begin
203
+ @level = @level_loader.level(@level_number)
204
+ @move = 0
205
+ reset_labels
206
+ reset_map
207
+ display
208
+ rescue RSokoban::LevelNumberTooHighError
209
+ Tk::messageBox :message => "Sorry, no level ##{@level_number} in this set."
210
+ end
211
+ end
212
+
213
+ def load_level
214
+ d = LevelDialog.new(@tk_root, "Load a level")
215
+ if d.ok?
216
+ @level_number = d.value
217
+ start_level
218
+ end
219
+ end
220
+
221
+ def load_set
222
+ d = SetDialog.new(@tk_root, "Load a set")
223
+ new_set = d.value
224
+ if d.ok? and new_set != nil
225
+ @level_loader = RSokoban::LevelLoader.new new_set
226
+ @level_number = 1
227
+ start_level
228
+ end
229
+ end
230
+
231
+ # Display the current map (current state of the level) on screen.
232
+ def display
233
+ y = 0
234
+ @level.map.each do |row|
235
+ # find first wall
236
+ x = row.index(RSokoban::WALL)
237
+ line = row.strip
238
+ line.each_char do |char|
239
+ case char
240
+ when RSokoban::WALL then @wall.display_at x, y
241
+ when RSokoban::FLOOR then @floor.display_at x, y
242
+ when RSokoban::CRATE then @crate.display_at x, y
243
+ when RSokoban::STORAGE then @store.display_at x, y
244
+ when RSokoban::MAN then display_man_at x, y
245
+ when RSokoban::MAN_ON_STORAGE then display_man_on_storage_at x, y
246
+ when RSokoban::CRATE_ON_STORAGE then @crate_store.display_at x, y
247
+ end
248
+ x += 1
249
+ end
250
+ y += 1
251
+ end
252
+ end
253
+
254
+ def display_man_at x, y
255
+ case @last_move
256
+ when :up then @man_up.display_at x, y
257
+ when :down then @man_down.display_at x, y
258
+ when :left then @man_left.display_at x, y
259
+ else
260
+ @man_right.display_at x, y
261
+ end
262
+ end
263
+
264
+ def display_man_on_storage_at x, y
265
+ case @last_move
266
+ when :up then @man_store_up.display_at x, y
267
+ when :down then @man_store_down.display_at x, y
268
+ when :left then @man_store_left.display_at x, y
269
+ else
270
+ @man_store_right.display_at x, y
271
+ end
272
+ end
273
+
274
+ def init_gui
275
+ init_root
276
+ init_menu
277
+ init_labels
278
+ preload_images
279
+ init_map
280
+ init_buttons
281
+ make_binding
282
+ end
283
+
284
+ def init_root
285
+ @tk_root = TkRoot.new do
286
+ title "RSokoban " + File.read($RSOKOBAN_PATH + '/VERSION').strip
287
+ minsize(400, 400)
288
+ resizable(false, false)
289
+ end
290
+ end
291
+
292
+ def init_menu
293
+ TkOption.add '*tearOff', 0
294
+ menubar = TkMenu.new(@tk_root)
295
+ @tk_root['menu'] = menubar
296
+
297
+ file = TkMenu.new(menubar)
298
+ helpm = TkMenu.new(menubar)
299
+ menubar.add :cascade, :menu => file, :label => 'File'
300
+ menubar.add :cascade, :menu => helpm, :label => 'Help'
301
+
302
+ file.add :command, :label => 'Load level', :command => proc{load_level}, :accelerator => 'Ctrl+L'
303
+ file.add :command, :label => 'Load set', :command => proc{load_set}
304
+ file.add :separator
305
+ file.add :command, :label => 'Undo', :command => proc{undo}, :accelerator => 'Ctrl+U'
306
+ file.add :command, :label => 'Restart level', :command => proc{start_level}, :accelerator => 'Ctrl+R'
307
+ file.add :separator
308
+ file.add :command, :label => 'Quit', :command => proc{exit}
309
+
310
+ helpm.add :command, :label => 'Help', :command => proc{help}, :accelerator => 'F1'
311
+ helpm.add :separator
312
+ helpm.add :command, :label => 'About', :command => proc{about}
313
+ end
314
+
315
+ def init_labels
316
+ @tk_label_set = TkLabel.new(@tk_root) do
317
+ grid('row' => 0, 'column' => 0, 'columnspan' => 19, 'sticky' => 'w')
318
+ end
319
+ @tk_label_level = TkLabel.new(@tk_root) do
320
+ grid('row'=>1, 'column'=>0, 'columnspan' => 19, 'sticky' => 'w')
321
+ end
322
+ @tk_label_move = TkLabel.new(@tk_root) do
323
+ grid('row'=>2, 'column'=>0, 'columnspan' => 19, 'sticky' => 'w')
324
+ end
325
+ @tk_label_separator = TkLabel.new(@tk_root) do
326
+ text("")
327
+ grid('row'=>4, 'column'=>0, 'columnspan' => 19)
328
+ end
329
+ reset_labels
330
+ end
331
+
332
+ def reset_labels
333
+ @tk_label_set.configure('text' => "Set: #{@level_loader.set.title}")
334
+ @tk_label_level.configure('text' => "Level: #{@level.title} (#{@level_number}/#{@level_loader.set.size})")
335
+ update_move_information
336
+ end
337
+
338
+ def update_move_information
339
+ @tk_label_move.configure('text' => "Move: #{@move}")
340
+ end
341
+
342
+ # Build the map of labels. Images of the game will be displayed on those labels.
343
+ def init_map
344
+ row_in_grid = 5
345
+ (0...16).each {|row_index|
346
+ row = []
347
+ (0...19).each {|col_index|
348
+ label = TkLabel.new(@tk_root) do
349
+ grid('row'=> row_in_grid, 'column'=> col_index, 'padx' => 0, 'pady' => 0, 'ipadx' => 0, 'ipady' => 0)
350
+ end
351
+ label['borderwidth'] = 0
352
+ row.push label
353
+ }
354
+ @tk_map.push row
355
+ row_in_grid += 1
356
+ }
357
+ reset_map
358
+ end
359
+
360
+ # Reset all the map with 'outside' tile.
361
+ # @todo little improvement : reload @outside image only if there is something else
362
+ # in the current map.
363
+ def reset_map
364
+ @tk_map.each_index {|y|
365
+ @tk_map[y].each_index {|x| @outside.display_at(x, y) }
366
+ }
367
+ end
368
+
369
+ def init_buttons
370
+ @tk_undo_button = TkButton.new(@tk_root) do
371
+ text 'Undo'
372
+ grid('row'=>3, 'column'=>0, 'columnspan' => 3)
373
+ end
374
+
375
+ @tk_retry_button = TkButton.new(@tk_root) do
376
+ text 'Retry'
377
+ grid('row'=>3, 'column'=>3, 'columnspan' => 3)
378
+ end
379
+
380
+ @tk_level_button = TkButton.new(@tk_root) do
381
+ text 'Level'
382
+ grid('row'=>3, 'column'=>6, 'columnspan' => 3)
383
+ end
384
+
385
+ @tk_set_button = TkButton.new(@tk_root) do
386
+ text 'Set'
387
+ grid('row'=>3, 'column'=>9, 'columnspan' => 3)
388
+ end
389
+ end
390
+
391
+ # Bind user's actions
392
+ def make_binding
393
+ @tk_root.bind('Up') { move :up }
394
+ @tk_root.bind('Down') { move :down }
395
+ @tk_root.bind('Left') { move :left }
396
+ @tk_root.bind('Right') { move :right }
397
+ @tk_root.bind('Control-u') { undo }
398
+ @tk_root.bind('Control-r') { start_level }
399
+ @tk_root.bind('Control-l') { load_level }
400
+ @tk_root.bind('F1') { help }
401
+ @tk_undo_button.command { undo }
402
+ @tk_retry_button.command { start_level }
403
+ @tk_level_button.command { load_level }
404
+ @tk_set_button.command { load_set }
405
+ end
406
+
407
+ def undo
408
+ result = @level.undo
409
+ @move = get_move_number_from_result_of_last_move result
410
+ update_move_information
411
+ display
412
+ end
413
+
414
+ # Send the move to Level and process response.
415
+ # @todo the last move is recorded here to permit the display of the man
416
+ # in the 4 direction. This is a bad thing ! Level#move should returns a hash
417
+ # with all needed information (status, move number, last move, error message, etc.)
418
+ def move symb
419
+ @last_move = symb
420
+ result = @level.move symb
421
+ unless result.start_with?('ERROR')
422
+ @move = get_move_number_from_result_of_last_move result
423
+ update_move_information
424
+ display
425
+ end
426
+ if result.start_with?('WIN')
427
+ response = Tk::messageBox :type => 'yesno', :message => "Level completed !\nPlay next level ?",
428
+ :icon => 'question', :title => 'You win !', :parent => @tk_root, :default => 'yes'
429
+ next_level if response == 'yes'
430
+ start_level if response == 'no'
431
+ end
432
+ end
433
+
434
+ # Assuming that result start with 'OK' or 'WIN'
435
+ # @return [Fixnum] current move number
436
+ def get_move_number_from_result_of_last_move result
437
+ move_index = result =~ /\d+/
438
+ result[move_index..-1].to_i
439
+ end
440
+
441
+ def preload_images
442
+ dir = $RSOKOBAN_PATH + '/skins/default/'
443
+ @wall = Image.new(dir + 'wall.bmp', @tk_map)
444
+ @crate = Image.new(dir + 'crate.bmp', @tk_map)
445
+ @floor = Image.new(dir + 'floor.bmp', @tk_map)
446
+ @store = Image.new(dir + 'store.bmp', @tk_map)
447
+ @man_up = Image.new(dir + 'man_up.bmp', @tk_map)
448
+ @man_down = Image.new(dir + 'man_down.bmp', @tk_map)
449
+ @man_left = Image.new(dir + 'man_left.bmp', @tk_map)
450
+ @man_right = Image.new(dir + 'man_right.bmp', @tk_map)
451
+ @crate_store = Image.new(dir + 'crate_store.bmp', @tk_map)
452
+ @man_store_up = Image.new(dir + 'man_store_up.bmp', @tk_map)
453
+ @man_store_down = Image.new(dir + 'man_store_down.bmp', @tk_map)
454
+ @man_store_left = Image.new(dir + 'man_store_left.bmp', @tk_map)
455
+ @man_store_right = Image.new(dir + 'man_store_right.bmp', @tk_map)
456
+ @outside = Image.new(dir + 'outside.bmp', @tk_map)
457
+ end
458
+
459
+ def help
460
+ HelpDialog.new(@tk_root, "RSokoban Help")
461
+ end
462
+
463
+ def about
464
+ text = "RSokoban #{File.read($RSOKOBAN_PATH + '/VERSION').strip} \n"
465
+ text += "This is free software !\n"
466
+ text += "Copyright 2011, Xavier Nayrac\n"
467
+ text += "Licensed under the GPL-3\n"
468
+ text += "Contact: xavier.nayrac@gmail.com"
469
+ Tk::messageBox :message => text, :title => 'About'
470
+ end
471
+
472
+ end
473
+
474
+ end
@@ -0,0 +1,10 @@
1
+ require "rsokoban/ui/base_ui"
2
+ require "rsokoban/ui/player_action"
3
+
4
+ module RSokoban
5
+
6
+ # Module dedicated to user interfaces.
7
+ module UI
8
+ end
9
+
10
+ end
data/lib/rsokoban.rb CHANGED
@@ -9,13 +9,12 @@ require "rsokoban/game"
9
9
  require "rsokoban/option"
10
10
  require "rsokoban/level_set"
11
11
  require "rsokoban/raw_level"
12
- require "rsokoban/ui/ui"
12
+ require "rsokoban/ui"
13
+ require "rsokoban/move_recorder"
14
+ require "rsokoban/map"
13
15
 
14
16
  # I am the main module of the game.
15
- module RSokoban
16
- # Version of the program
17
- VERSION = '0.71'
18
-
17
+ module RSokoban
19
18
  # Game elements.
20
19
  # Those constants are used intensively.
21
20
  MAN = '@'
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1 @@
1
+ Blue Granite by MerlijnVV
Binary file
Binary file
data/test/original.xsb CHANGED
@@ -1493,5 +1493,3 @@
1493
1493
  ####################
1494
1494
  ; 90
1495
1495
  ; Copyright: Thinking Rabbit
1496
-
1497
-