smagacor 0.0.1 → 0.0.2

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.
Binary file
Binary file
@@ -0,0 +1,597 @@
1
+ #
2
+ #--
3
+ #
4
+ # $Id: sokobanui.rb 363 2005-11-26 18:16:30Z thomas $
5
+ #
6
+ # smagacor - a collection of small games in ruby
7
+ # Copyright (C) 2004 Thomas Leitner
8
+ #
9
+ # This program is free software; you can redistribute it and/or modify it under the terms of the GNU
10
+ # General Public License as published by the Free Software Foundation; either version 2 of the
11
+ # License, or (at your option) any later version.
12
+ #
13
+ # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
14
+ # even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
+ # General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License along with this program; if not,
18
+ # write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
+ #
20
+ #++
21
+ #
22
+ require 'fileutils'
23
+ require 'smagacor/listener'
24
+
25
+ module Smagacor::Sokoban
26
+
27
+ Position = Struct.new( :x, :y )
28
+ CellChange = Struct.new( :pos, :old_obj, :new_obj )
29
+ LevelChange = Struct.new( :old_cell, :new_cell )
30
+
31
+ class Map
32
+
33
+ Man = ?@
34
+ Crate = ?$
35
+ Wall = ?#
36
+ Storage = ?.
37
+ Floor = ?\s
38
+ Empty = ?-
39
+ ManOnStorage = ?+
40
+ CrateOnStorage = ?*
41
+
42
+ include Enumerable
43
+
44
+ attr_reader :width
45
+ attr_reader :height
46
+ attr_reader :name
47
+
48
+ def initialize( str_map, name = '' )
49
+ @name = name
50
+ create_map( str_map )
51
+ end
52
+
53
+ def set_pos( pos, item )
54
+ @map[pos.y][pos.x] = item
55
+ end
56
+
57
+ def get_pos( pos )
58
+ @map[pos.y][pos.x]
59
+ end
60
+
61
+ def each
62
+ @map.each {|row| row.each {|field| yield field } }
63
+ end
64
+
65
+ def each_row
66
+ @map.each {|row| yield row}
67
+ end
68
+
69
+ def each_with_pos
70
+ @map.each_with_index do |row, y|
71
+ row.each_with_index do |cell, x|
72
+ yield cell, Position.new( x, y )
73
+ end
74
+ end
75
+ end
76
+
77
+ #######
78
+ private
79
+ #######
80
+
81
+ def create_map( str_map )
82
+ @map = str_map.split( /\n/ ).collect { |row| row.unpack( 'C*' ) }
83
+ @width = @map.max {|a,b| a.length <=> b.length}.length
84
+ @height = @map.length
85
+
86
+ # remove false floor tiles
87
+ @map.each {|row| row.each_with_index {|c,i| break if c == Wall; row[i] = Empty } }
88
+ rows = (0..(@height - 1)).to_a
89
+ (0..(@width - 1)).each do |col|
90
+ rows.each { |row| break if @map[row][col] == Wall; next if @map[row][col].nil?; @map[row][col] = Empty }
91
+ rows.reverse.each { |row| break if @map[row][col] == Wall; next if @map[row][col].nil?; @map[row][col] = Empty }
92
+ end
93
+ end
94
+
95
+ end
96
+
97
+
98
+ class Level
99
+
100
+ include Listener
101
+
102
+ attr_reader :map
103
+ attr_reader :man_pos
104
+
105
+ def initialize( map, name = '' )
106
+ @original_map = Map.new( map, name )
107
+ reset
108
+ add_msg_name( :level_changed )
109
+ add_msg_name( :move_impossible )
110
+ add_msg_name( :level_finished )
111
+ end
112
+
113
+ def move( direction )
114
+ move_possible = true
115
+
116
+ newpos = Level.new_pos( @man_pos, direction )
117
+ case @map.get_pos( newpos )
118
+ when Map::Floor, Map::Storage
119
+ lc = move_man( newpos )
120
+ dispatch_msg( :level_changed, [lc] )
121
+
122
+ when Map::Wall
123
+ dispatch_msg( :move_impossible )
124
+ move_possible = false
125
+
126
+ when Map::Crate, Map::CrateOnStorage
127
+ crate_new_pos = Level.new_pos( newpos, direction )
128
+ case @map.get_pos( crate_new_pos )
129
+ when Map::Wall, Map::Crate, Map::CrateOnStorage
130
+ dispatch_msg( :move_impossible )
131
+ move_possible = false
132
+ else
133
+ lc_crate = move_crate( newpos, crate_new_pos )
134
+ lc_man = move_man( newpos )
135
+ dispatch_msg( :level_changed, [lc_man, lc_crate] )
136
+ end
137
+ end
138
+ dispatch_msg( :level_finished ) if level_finished?
139
+ return move_possible
140
+ end
141
+
142
+ def move_man( newpos )
143
+ n = CellChange.new( newpos, @map.get_pos( newpos ) )
144
+ o = CellChange.new( @man_pos, @map.get_pos( @man_pos ) )
145
+ case @map.get_pos( newpos )
146
+ when Map::Floor then @map.set_pos( newpos, Map::Man )
147
+ when Map::Storage then @map.set_pos( newpos, Map::ManOnStorage )
148
+ end
149
+ case @map.get_pos( @man_pos )
150
+ when Map::Man then @map.set_pos( @man_pos, Map::Floor )
151
+ when Map::ManOnStorage then @map.set_pos( @man_pos, Map::Storage )
152
+ end
153
+ n.new_obj = @map.get_pos( newpos )
154
+ o.new_obj = @map.get_pos( @man_pos )
155
+
156
+ @man_pos = newpos
157
+ LevelChange.new( o, n )
158
+ end
159
+
160
+ def move_crate( oldpos, newpos )
161
+ n = CellChange.new( newpos, @map.get_pos( newpos ) )
162
+ o = CellChange.new( oldpos, @map.get_pos( oldpos ) )
163
+ case @map.get_pos( newpos )
164
+ when Map::Floor then @map.set_pos( newpos, Map::Crate )
165
+ when Map::Storage then @map.set_pos( newpos, Map::CrateOnStorage )
166
+ end
167
+ case @map.get_pos( oldpos )
168
+ when Map::Crate then @map.set_pos( oldpos, Map::Floor )
169
+ when Map::CrateOnStorage then @map.set_pos( oldpos, Map::Storage )
170
+ end
171
+ n.new_obj = @map.get_pos( newpos )
172
+ o.new_obj = @map.get_pos( oldpos )
173
+ LevelChange.new( o, n )
174
+ end
175
+
176
+ def level_finished?
177
+ !( @map.any? {|item| item == Map::Storage || item == Map::ManOnStorage } )
178
+ end
179
+
180
+ def reset
181
+ @map = Marshal.load( Marshal.dump( @original_map ) )
182
+ recalc_man_pos
183
+ end
184
+
185
+ def recalc_man_pos
186
+ @man_pos = Level.find_man( @map )
187
+ end
188
+
189
+ def Level.find_man( map )
190
+ map.each_with_pos {|cell, pos| return pos if cell == Map::Man || cell == Map::ManOnStorage }
191
+ end
192
+
193
+
194
+ def Level.new_pos( pos, direction )
195
+ case direction
196
+ when :left then Position.new( pos.x - 1, pos.y )
197
+ when :right then Position.new( pos.x + 1, pos.y )
198
+ when :up then Position.new( pos.x, pos.y - 1 )
199
+ when :down then Position.new( pos.x, pos.y + 1)
200
+ end
201
+ end
202
+
203
+ end
204
+
205
+
206
+ class LevelCollection < Array
207
+
208
+ attr_reader :name
209
+
210
+ def initialize( data, name )
211
+ super()
212
+ @name = name
213
+ leveldata = data.split( /\n;.*\n('.*?'\n)?\n/ )
214
+ i = 0
215
+ while i < leveldata.length
216
+ if !leveldata[i].empty?
217
+ name = if leveldata[i][0] == ?'
218
+ i += 1
219
+ leveldata[i-1][1..-3]
220
+ else
221
+ ''
222
+ end
223
+ self << Level.new( leveldata[i], name )
224
+ end
225
+ i += 1
226
+ end
227
+ end
228
+
229
+ end
230
+
231
+
232
+ class Sokoban
233
+
234
+ include Listener
235
+ include Enumerable
236
+
237
+ attr_reader :collections
238
+
239
+ def initialize
240
+ add_msg_name( :level_selected )
241
+ @collection_index = -1
242
+ @level_index = -1
243
+ @collections = []
244
+ end
245
+
246
+ def load_level_collection( data, name )
247
+ @collections << LevelCollection.new( data, name )
248
+ end
249
+
250
+ def cur_level
251
+ @collections[@collection_index][@level_index] if @collection_index >= 0
252
+ end
253
+
254
+ def each
255
+ @collections.each {|c| c.each {|l| yield( c, l ) } }
256
+ end
257
+
258
+ def each_with_index
259
+ @collections.each_with_index {|c, ci| c.each_with_index {|l, li| yield( c, ci, l, li ) } }
260
+ end
261
+
262
+ def select_collection( index )
263
+ init_level( 0, index )
264
+ end
265
+
266
+ def next_collection
267
+ init_level( 0, @collection_index + 1 )
268
+ end
269
+
270
+ def prev_collection
271
+ init_level( 0, @collection_index - 1 )
272
+ end
273
+
274
+ def select_level( index, colindex = @collection_index )
275
+ init_level( index, colindex )
276
+ end
277
+
278
+ def next_level
279
+ init_level( @level_index + 1 )
280
+ end
281
+
282
+ def prev_level
283
+ init_level( @level_index - 1 )
284
+ end
285
+
286
+ #######
287
+ private
288
+ #######
289
+
290
+ def init_level( levelindex, colindex = @collection_index )
291
+ oldcolindex = @collection_index
292
+ oldlevelindex = @level_index
293
+ @collection_index = colindex % @collections.length
294
+ @level_index = levelindex % @collections[@collection_index].length
295
+ cur_level.reset
296
+ dispatch_msg( :level_selected, (oldcolindex == -1 ? nil : @collections[oldcolindex][oldlevelindex]), cur_level )
297
+ end
298
+
299
+ end
300
+
301
+
302
+ class SokobanCanvas < Qt::Widget
303
+
304
+ def initialize( parent, sokoban )
305
+ super( parent )
306
+ @sokoban = sokoban
307
+ @sokoban.add_msg_listener( :level_selected, method(:on_level_selected) )
308
+ setBackgroundMode( Qt::NoBackground )
309
+ setFocusPolicy( Qt::Widget::StrongFocus )
310
+ setFocus
311
+
312
+ @wall = @swall = Qt::Image.new( File.join( File.dirname( __FILE__ ), 'wall.png' ) )
313
+ @man = @sman = Qt::Image.new( File.join( File.dirname( __FILE__ ), 'man.png' ) )
314
+ @storage = @sstorage = Qt::Image.new( File.join( File.dirname( __FILE__ ), 'storage.png' ) )
315
+ @crate = @scrate = Qt::Image.new( File.join( File.dirname( __FILE__ ), 'crate.png' ) )
316
+ @floor = @sfloor = Qt::Image.new( File.join( File.dirname( __FILE__ ), 'floor.png' ) )
317
+
318
+ @color_bg = Qt::Brush.new( Qt::white )
319
+ end
320
+
321
+ def on_level_selected( oldlevel, curlevel )
322
+ oldlevel.del_msg_listener( :level_changed, method(:on_level_changed) ) if oldlevel
323
+ curlevel.add_msg_listener( :level_changed, method(:on_level_changed) )
324
+ resizeEvent( Qt::ResizeEvent.new( self.size, self.size ) )
325
+ update
326
+ end
327
+
328
+ def on_level_changed( changes )
329
+ p = Qt::Painter.new( self )
330
+ xoff, yoff = offset( @sokoban.cur_level.map )
331
+ changes.each do |lc|
332
+ draw_item( p, @sokoban.cur_level.map.get_pos( lc.old_cell.pos ), xoff + lc.old_cell.pos.x * @tile_size, yoff + lc.old_cell.pos.y * @tile_size )
333
+ draw_item( p, @sokoban.cur_level.map.get_pos( lc.new_cell.pos ), xoff + lc.new_cell.pos.x * @tile_size, yoff + lc.new_cell.pos.y * @tile_size )
334
+ end
335
+ end
336
+
337
+ def draw_man( painter, x, y, tile_size = @tile_size )
338
+ @sman = @man.smoothScale( tile_size, tile_size ) if @sman.width != tile_size
339
+ painter.drawImage( x, y, @sman )
340
+ end
341
+
342
+ def draw_crate( painter, x, y, tile_size = @tile_size )
343
+ @scrate = @crate.smoothScale( tile_size, tile_size ) if @scrate.width != tile_size
344
+ painter.drawImage( x, y, @scrate )
345
+ end
346
+
347
+ def draw_storage( painter, x, y, tile_size = @tile_size )
348
+ @sstorage = @storage.smoothScale( tile_size, tile_size ) if @sstorage.width != tile_size
349
+ painter.drawImage( x, y, @sstorage )
350
+ end
351
+
352
+ def draw_wall( painter, x, y, tile_size = @tile_size )
353
+ @swall = @wall.smoothScale( tile_size, tile_size ) if @swall.width != tile_size
354
+ painter.drawImage( x, y, @swall )
355
+ end
356
+
357
+ def draw_floor( painter, x, y, tile_size = @tile_size )
358
+ @sfloor = @floor.smoothScale( tile_size, tile_size ) if @sfloor.width != tile_size
359
+ painter.drawImage( x, y, @sfloor )
360
+ end
361
+
362
+ def draw_map( painter, map, w = self.width, h = self.height, tile_size = @tile_size )
363
+ painter.fillRect( 0, 0, w, h, @color_bg )
364
+ xoff, yoff = offset( map, w, h, tile_size )
365
+ y = yoff
366
+ map.each_row do |row|
367
+ x = xoff
368
+ row.each do |item|
369
+ draw_item( painter, item, x, y, tile_size )
370
+ x += tile_size
371
+ end
372
+ y += tile_size
373
+ end
374
+ end
375
+
376
+ def draw_item( painter, item, x, y, tile_size = @tile_size)
377
+ case item
378
+ when Map::Wall
379
+ draw_wall( painter, x, y, tile_size )
380
+ when Map::Storage
381
+ draw_floor( painter, x, y, tile_size )
382
+ draw_storage( painter, x, y, tile_size )
383
+ when Map::Crate
384
+ draw_floor( painter, x, y, tile_size )
385
+ draw_crate( painter, x, y, tile_size )
386
+ when Map::Man
387
+ draw_floor( painter, x, y, tile_size )
388
+ draw_man( painter, x, y, tile_size )
389
+ when Map::CrateOnStorage
390
+ draw_floor( painter, x, y, tile_size )
391
+ draw_storage( painter, x, y, tile_size )
392
+ draw_crate( painter, x, y, tile_size )
393
+ when Map::ManOnStorage
394
+ draw_floor( painter, x, y, tile_size )
395
+ draw_storage( painter, x, y, tile_size )
396
+ draw_man( painter, x, y, tile_size )
397
+ when Map::Floor
398
+ draw_floor( painter, x, y, tile_size )
399
+ end
400
+ end
401
+
402
+ def draw_all( painter, map, w, h )
403
+ draw_map( painter, map, w, h, tile_size( w, h, map ) )
404
+ end
405
+
406
+ def tile_size( w, h, map )
407
+ dx = w / map.width
408
+ dy = h / map.height
409
+ ( dx > dy ? dy : dx )
410
+ end
411
+
412
+ def offset( map, w = self.width, h = self.height, tile_size = @tile_size )
413
+ [(w - map.width * tile_size) / 2, (h - map.height * tile_size) / 2]
414
+ end
415
+
416
+ def paintEvent( event )
417
+ pix = Qt::Pixmap.new( width, height )
418
+ p = Qt::Painter.new
419
+ p.begin( pix )
420
+ draw_map( p, @sokoban.cur_level.map ) if @sokoban && @sokoban.cur_level
421
+ p.end
422
+
423
+ painter = Qt::Painter.new( self )
424
+ painter.drawPixmap( 0, 0, pix )
425
+ end
426
+
427
+ def resizeEvent( event )
428
+ @tile_size = tile_size( self.width, self.height, @sokoban.cur_level.map ) if @sokoban && @sokoban.cur_level
429
+ end
430
+
431
+ def keyPressEvent( event )
432
+ case event.key
433
+ when Qt::Key_Left then @sokoban.cur_level.move( :left )
434
+ when Qt::Key_Right then @sokoban.cur_level.move( :right )
435
+ when Qt::Key_Up then @sokoban.cur_level.move( :up )
436
+ when Qt::Key_Down then @sokoban.cur_level.move( :down )
437
+ end
438
+ end
439
+
440
+ end
441
+
442
+
443
+ class SokobanUI < Qt::Widget
444
+
445
+ slots 'next_level()', 'prev_level()', 'select_level(int)', 'reset_level()'
446
+ slots 'next_collection()', 'prev_collection()'
447
+
448
+ attr_reader :canvas
449
+ attr_reader :sokoban
450
+
451
+ def initialize( parent )
452
+ super( parent )
453
+ @sokoban = Sokoban.new
454
+ Dir[File.join( File.dirname( __FILE__ ), 'levels', '*.lvl')].sort.each do |file|
455
+ @sokoban.load_level_collection( File.read( file ), File.basename( file, '.*').capitalize.tr( '_', ' ') )
456
+ end
457
+ @sokoban.add_msg_listener( :level_selected, method(:on_level_selected) )
458
+ @canvas = SokobanCanvas.new( self, @sokoban )
459
+ @sokoban.select_level( 0, 0 )
460
+
461
+ layout = Qt::HBoxLayout.new( self )
462
+ layout.addWidget( @canvas )
463
+ end
464
+
465
+ def next_collection
466
+ @sokoban.next_collection
467
+ end
468
+
469
+ def prev_collection
470
+ @sokoban.prev_collection
471
+ end
472
+
473
+ def next_level
474
+ @sokoban.next_level
475
+ end
476
+
477
+ def prev_level
478
+ @sokoban.prev_level
479
+ end
480
+
481
+ def select_level( calc_index )
482
+ @sokoban.select_level( calc_index % 1000, calc_index / 1000 )
483
+ end
484
+
485
+ def reset_level
486
+ @sokoban.cur_level.reset
487
+ @canvas.update
488
+ end
489
+
490
+ def undo( info )
491
+ info.each do |lc|
492
+ @sokoban.cur_level.map.set_pos( lc.old_cell.pos, lc.old_cell.old_obj )
493
+ @sokoban.cur_level.map.set_pos( lc.new_cell.pos, lc.new_cell.old_obj )
494
+ end
495
+ @sokoban.cur_level.recalc_man_pos
496
+ @canvas.update
497
+ end
498
+
499
+ def redo( info )
500
+ info.reverse.each do |lc|
501
+ @sokoban.cur_level.map.set_pos( lc.old_cell.pos, lc.old_cell.new_obj )
502
+ @sokoban.cur_level.map.set_pos( lc.new_cell.pos, lc.new_cell.new_obj )
503
+ end
504
+ @sokoban.cur_level.recalc_man_pos
505
+ @canvas.update
506
+ end
507
+
508
+ def set_undo_manager( manager )
509
+ @manager.del_msg_listener( :undo, method(:undo) ) if @manager
510
+ @manager.del_msg_listener( :redo, method(:redo) ) if @manager
511
+ @manager = manager
512
+ @manager.add_msg_listener( :undo, method(:undo) )
513
+ @manager.add_msg_listener( :redo, method(:redo) )
514
+ end
515
+
516
+ def on_level_selected( oldlevel, curlevel )
517
+ oldlevel.del_msg_listener( :level_changed, method(:on_level_changed) ) if oldlevel
518
+ curlevel.add_msg_listener( :level_changed, method(:on_level_changed) )
519
+ @manager.clear if @manager
520
+ end
521
+
522
+ def on_level_changed( changes )
523
+ @manager.add_undo_info( changes )
524
+ end
525
+
526
+ end
527
+
528
+
529
+ class SokobanGame < Smagacor::GamePlugin
530
+
531
+ def initialize( parent, gameinfo )
532
+ @parent = parent
533
+ @widget = SokobanUI.new( parent )
534
+ Qt::MessageBox.information( parent, "Credits", "All included Sokoban levels have been made by David W. Skinner (sasquatch@bentonrea.com)" )
535
+ end
536
+
537
+ def game_widget
538
+ @widget
539
+ end
540
+
541
+ def set_undo_manager( manager )
542
+ @widget.set_undo_manager( manager )
543
+ end
544
+
545
+ def menu
546
+ colmenu = Qt::PopupMenu.new( @parent )
547
+ oldcollection = nil
548
+ menu = nil
549
+ @widget.sokoban.each_with_index do |collection, col_index, level, level_index|
550
+ if oldcollection != collection
551
+ menu = Qt::PopupMenu.new( @parent )
552
+ colmenu.insertItem( collection.name, menu )
553
+ oldcollection = collection
554
+ end
555
+
556
+ icon = level_pix( collection, level, level_index )
557
+ item = menu.insertItem( icon, @widget, SLOT('select_level(int)') ) #"Level #{level.map.name} (#{level_index+1})"
558
+ #TODO use small icon + text in menu item, big icon in whatsthis text
559
+ menu.setWhatsThis( item, "Hallo #{level_index}")
560
+ menu.setItemParameter( item, col_index * 1000 + level_index )
561
+ end
562
+
563
+ levelmenu = Qt::PopupMenu.new( @parent )
564
+ levelmenu.insertItem( "Select level", colmenu )
565
+ levelmenu.insertItem( "Reset level", @widget, SLOT('reset_level()'), Qt::KeySequence.new( Qt::Key_Escape ) )
566
+ levelmenu.insertItem( "Next collection", @widget, SLOT('next_collection()') )
567
+ levelmenu.insertItem( "Previous collection", @widget, SLOT('prev_collection()') )
568
+ levelmenu.insertItem( "Next level in collection", @widget, SLOT('next_level()'), Qt::KeySequence.new( Qt::Key_N ) )
569
+ levelmenu.insertItem( "Previous level in collection", @widget, SLOT('prev_level()'), Qt::KeySequence.new( Qt::Key_P ) )
570
+ levelmenu
571
+ end
572
+
573
+ #######
574
+ private
575
+ #######
576
+
577
+ def level_pix( collection, level, level_index )
578
+ cachefile = File.join( Smagacor::Config.homepath, 'sokoban', 'level_icon_cache', collection.name + level_index.to_s )
579
+ if File.exists?( cachefile )
580
+ Qt::Pixmap.new( cachefile )
581
+ else
582
+ tilesize = @widget.canvas.tile_size( 50, 50, level.map )
583
+ painter = Qt::Painter.new
584
+ icon = Qt::Pixmap.new( tilesize*level.map.width, tilesize*level.map.height )
585
+ painter.begin( icon )
586
+ @widget.canvas.draw_all( painter, level.map, icon.width, icon.height )
587
+ painter.end
588
+ FileUtils.makedirs( File.dirname( cachefile ) )
589
+ icon.save( cachefile, "PNG" )
590
+ icon
591
+ end
592
+ end
593
+
594
+ end
595
+
596
+
597
+ end