smagacor 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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