sokoban.rb 0.0.1

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/bin/sokoban ADDED
@@ -0,0 +1,485 @@
1
+ #!/usr/bin/env ruby
2
+
3
+
4
+ #######################################
5
+ #about switching SHOW_FFI :
6
+ #if working with ruby 1.9.1-p376 + gem: ruby-opengl :
7
+ #read: http://rubygamedev.wordpress.com/2010/04/16/ruby-opengl-on-ruby-1-9/
8
+ #
9
+ #if working with ruby 1.9.2 + gem: ffi-opengl + stil buggy(for me..)
10
+
11
+ ########################################
12
+
13
+ SHOW_GEARS = false #to show the original ffi-opengl gears example OR the sokoban game
14
+
15
+
16
+
17
+
18
+
19
+ # some undeleted notes from original creator of the original sokoban project:
20
+ ##########################################################################
21
+ # 3-D gear wheels. This program is in the public domain.
22
+ #
23
+ # Command line options:
24
+ # -info print GL implementation information
25
+ # -exit automatically exit after 30 seconds
26
+ #
27
+ # 2005-05-01 Ruby version by Arto Bendiken based on gears.c rev 1.8.
28
+ # 2005-01-09 Original C version (gears.c) by Brian Paul et al.
29
+ # http://cvs.freedesktop.org/mesa/Mesa/progs/demos/gears.c?rev=1.8
30
+ ###########################################################################
31
+
32
+
33
+ #$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
34
+ #require 'ffi-opengl'
35
+
36
+ #require './render'
37
+
38
+
39
+ def convertf args
40
+ if SHOW_FFI
41
+ MemoryPointer.new(:float, 4).put_array_of_float(0,args )
42
+ else
43
+ args
44
+ end
45
+ end
46
+
47
+ class Gears
48
+ attr_accessor :mPOS , :mRED , :mGREEN , :mBLUE
49
+ #include FFI, GL, GLU, GLUT, Math
50
+
51
+ #include Render
52
+ #unless SHOW_GEARS
53
+
54
+
55
+
56
+ #end
57
+
58
+ #puts 'create Constants for color with ffi pointer array'
59
+ #POS = MemoryPointer.new(:float, 4).put_array_of_float(0, [5.0, 5.0, 10.0, 0.0])
60
+ #RED = MemoryPointer.new(:float, 4).put_array_of_float(0, [0.8, 0.1, 0.0, 1.0])
61
+ #GREEN = MemoryPointer.new(:float, 4).put_array_of_float(0, [0.0, 0.8, 0.2, 1.0])
62
+ #BLUE = MemoryPointer.new(:float, 4).put_array_of_float(0, [0.2, 0.2, 1.0, 1.0])
63
+
64
+
65
+ #puts self.POS
66
+ #include Math
67
+
68
+ # Draw a gear wheel. You'll probably want to call this function when
69
+ # building a display list since we do a lot of trig here.
70
+ #
71
+ # Input: inner_radius - radius of hole at center
72
+ # outer_radius - radius at center of teeth
73
+ # width - width of gear
74
+ # teeth - number of teeth
75
+ # tooth_depth - depth of tooth
76
+ def gear(inner_radius, outer_radius, width, teeth, tooth_depth)
77
+ r0 = inner_radius
78
+ r1 = outer_radius - tooth_depth / 2.0
79
+ r2 = outer_radius + tooth_depth / 2.0
80
+
81
+ da = 2.0 * PI / teeth / 4.0
82
+
83
+ glShadeModel(GL_FLAT)
84
+
85
+ glNormal3f(0.0, 0.0, 1.0)
86
+
87
+ # Draw front face
88
+ glBegin(GL_QUAD_STRIP)
89
+ for i in 0..teeth
90
+ angle = i * 2.0 * PI / teeth
91
+ glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5)
92
+ glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5)
93
+ if i < teeth
94
+ glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5)
95
+ glVertex3f(r1 * cos(angle + 3 * da),
96
+ r1 * sin(angle + 3 * da), width * 0.5)
97
+ end
98
+ end
99
+ glEnd()
100
+
101
+ # Draw front sides of teeth
102
+ glBegin(GL_QUADS)
103
+ for i in 0...teeth
104
+ angle = i * 2.0 * PI / teeth
105
+ glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5)
106
+ glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5)
107
+ glVertex3f(r2 * cos(angle + 2 * da),
108
+ r2 * sin(angle + 2 * da), width * 0.5)
109
+ glVertex3f(r1 * cos(angle + 3 * da),
110
+ r1 * sin(angle + 3 * da), width * 0.5)
111
+ end
112
+ glEnd()
113
+
114
+ glNormal3f(0.0, 0.0, -1.0)
115
+
116
+ # Draw back face
117
+ glBegin(GL_QUAD_STRIP)
118
+ for i in 0..teeth
119
+ angle = i * 2.0 * PI / teeth
120
+ glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5)
121
+ glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5)
122
+ if i < teeth
123
+ glVertex3f(r1 * cos(angle + 3 * da),
124
+ r1 * sin(angle + 3 * da), -width * 0.5)
125
+ glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5)
126
+ end
127
+ end
128
+ glEnd()
129
+
130
+ # Draw back sides of teeth
131
+ glBegin(GL_QUADS)
132
+ for i in 0...teeth
133
+ angle = i * 2.0 * PI / teeth
134
+ glVertex3f(r1 * cos(angle + 3 * da),
135
+ r1 * sin(angle + 3 * da), -width * 0.5)
136
+ glVertex3f(r2 * cos(angle + 2 * da),
137
+ r2 * sin(angle + 2 * da), -width * 0.5)
138
+ glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5)
139
+ glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5)
140
+ end
141
+ glEnd()
142
+
143
+ # Draw outward faces of teeth
144
+ glBegin(GL_QUAD_STRIP)
145
+ for i in 0...teeth
146
+ angle = i * 2.0 * PI / teeth
147
+ glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5)
148
+ glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5)
149
+ u = r2 * cos(angle + da) - r1 * cos(angle)
150
+ v = r2 * sin(angle + da) - r1 * sin(angle)
151
+ len = sqrt(u * u + v * v)
152
+ u /= len
153
+ v /= len
154
+ glNormal3f(v, -u, 0.0)
155
+ glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5)
156
+ glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5)
157
+ glNormal3f(cos(angle), sin(angle), 0.0)
158
+ glVertex3f(r2 * cos(angle + 2 * da),
159
+ r2 * sin(angle + 2 * da), width * 0.5)
160
+ glVertex3f(r2 * cos(angle + 2 * da),
161
+ r2 * sin(angle + 2 * da), -width * 0.5)
162
+ u = r1 * cos(angle + 3 * da) - r2 * cos(angle + 2 * da)
163
+ v = r1 * sin(angle + 3 * da) - r2 * sin(angle + 2 * da)
164
+ glNormal3f(v, -u, 0.0)
165
+ glVertex3f(r1 * cos(angle + 3 * da),
166
+ r1 * sin(angle + 3 * da), width * 0.5)
167
+ glVertex3f(r1 * cos(angle + 3 * da),
168
+ r1 * sin(angle + 3 * da), -width * 0.5)
169
+ glNormal3f(cos(angle), sin(angle), 0.0)
170
+ end
171
+ glVertex3f(r1 * cos(0), r1 * sin(0), width * 0.5)
172
+ glVertex3f(r1 * cos(0), r1 * sin(0), -width * 0.5)
173
+ glEnd()
174
+
175
+ glShadeModel(GL_SMOOTH)
176
+
177
+ # Draw inside radius cylinder
178
+ glBegin(GL_QUAD_STRIP)
179
+ for i in 0..teeth
180
+ angle = i * 2.0 * PI / teeth
181
+ glNormal3f(-cos(angle), -sin(angle), 0.0)
182
+ glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5)
183
+ glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5)
184
+ end
185
+ glEnd()
186
+ end
187
+
188
+ def draw_gears
189
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
190
+
191
+ glPushMatrix()
192
+ glRotatef(@view_rotx, 1.0, 0.0, 0.0)
193
+ glRotatef(@view_roty, 0.0, 1.0, 0.0)
194
+ glRotatef(@view_rotz, 0.0, 0.0, 1.0)
195
+
196
+ glPushMatrix()
197
+ glTranslatef(-3.0, -2.0, 0.0)
198
+ glRotatef(@angle, 0.0, 0.0, 1.0)
199
+ glCallList(@gear1)
200
+ glPopMatrix()
201
+
202
+ glPushMatrix()
203
+ glTranslatef(3.1, -2.0, 0.0)
204
+ glRotatef(-2.0 * @angle - 9.0, 0.0, 0.0, 1.0)
205
+ glCallList(@gear2)
206
+ glPopMatrix()
207
+
208
+ glPushMatrix()
209
+ glTranslatef(-3.1, 4.2, 0.0)
210
+ glRotatef(-2.0 * @angle - 25.0, 0.0, 0.0, 1.0)
211
+ glCallList(@gear3)
212
+ glPopMatrix()
213
+
214
+ glPopMatrix()
215
+
216
+ glutSwapBuffers()
217
+
218
+ @frames = 0 if not defined? @frames
219
+ @t0 = 0 if not defined? @t0
220
+
221
+ @frames += 1
222
+ t = glutGet(GLUT_ELAPSED_TIME)
223
+ if t - @t0 >= 5000
224
+ seconds = (t - @t0) / 1000.0
225
+ fps = @frames / seconds
226
+ printf("%d frames in %6.3f seconds = %6.3f FPS\n",
227
+ @frames, seconds, fps)
228
+ @t0, @frames = t, 0
229
+ exit if defined? @autoexit and t >= 999.0 * @autoexit
230
+ end
231
+ end
232
+
233
+ def idle
234
+ t = glutGet(GLUT_ELAPSED_TIME) / 1000.0
235
+ @t0_idle = t if !defined? @t0_idle
236
+ # 90 degrees per second
237
+ @angle += 70.0 * (t - @t0_idle)
238
+ @t0_idle = t
239
+ glutPostRedisplay()
240
+ end
241
+
242
+ # Change view angle, exit upon ESC
243
+ def key(k, x, y)
244
+ case k
245
+ when ?z
246
+ @view_rotz += 5.0
247
+ when ?Z
248
+ @view_rotz -= 5.0
249
+ when 27 # Escape
250
+ exit
251
+ end
252
+ glutPostRedisplay()
253
+ end
254
+
255
+ # Change view angle
256
+ def special(k, x, y)
257
+ case k
258
+ when GLUT_KEY_UP
259
+ @view_rotx += 5.0
260
+ when GLUT_KEY_DOWN
261
+ @view_rotx -= 5.0
262
+ when GLUT_KEY_LEFT
263
+ @view_roty += 5.0
264
+ when GLUT_KEY_RIGHT
265
+ @view_roty -= 5.0
266
+ end
267
+ glutPostRedisplay()
268
+ end
269
+
270
+ # New window size or exposure
271
+ def reshape_gears(width, height)
272
+
273
+ h = height.to_f / width.to_f
274
+ glViewport(0, 0, width, height)
275
+ glMatrixMode(GL_PROJECTION)
276
+ glLoadIdentity()
277
+ glFrustum(-1.0, 1.0, -h, h, 5.0, 60.0)
278
+ glMatrixMode(GL_MODELVIEW)
279
+ glLoadIdentity()
280
+ glTranslatef(0.0, 0.0, -40.0)
281
+ end
282
+ def enable_light_for_gears
283
+ puts '@mPOS'
284
+ puts @mPOS
285
+
286
+ glLightfv(GL_LIGHT0, GL_POSITION, @mPOS)
287
+ glEnable(GL_CULL_FACE)
288
+ glEnable(GL_LIGHTING)
289
+ glEnable(GL_LIGHT0)
290
+ glEnable(GL_DEPTH_TEST)
291
+
292
+ end
293
+ def init_gears
294
+
295
+ @angle = 0.0
296
+ @view_rotx, @view_roty, @view_rotz = 20.0, 30.0, 0.0
297
+
298
+
299
+ # Make the gears
300
+ @gear1 = glGenLists(1)
301
+ glNewList(@gear1, GL_COMPILE)
302
+ glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE,@mRED)
303
+ gear(1.0, 4.0, 1.0, 20, 0.7)
304
+ glEndList()
305
+
306
+ @gear2 = glGenLists(1)
307
+ glNewList(@gear2, GL_COMPILE)
308
+ glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, @mGREEN)
309
+ gear(0.5, 2.0, 2.0, 10, 0.7)
310
+ glEndList()
311
+
312
+ @gear3 = glGenLists(1)
313
+ glNewList(@gear3, GL_COMPILE)
314
+ glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, @mBLUE)
315
+ gear(1.3, 2.0, 0.5, 10, 0.7)
316
+ glEndList()
317
+
318
+ glEnable(GL_NORMALIZE)
319
+
320
+ ARGV.each do |arg|
321
+ case arg
322
+ when '-info'
323
+ printf("GL_RENDERER = %s\n", glGetString(GL_RENDERER))
324
+ printf("GL_VERSION = %s\n", glGetString(GL_VERSION))
325
+ printf("GL_VENDOR = %s\n", glGetString(GL_VENDOR))
326
+ printf("GL_EXTENSIONS = %s\n", glGetString(GL_EXTENSIONS))
327
+ when '-exit'
328
+ @autoexit = 30
329
+ printf("Auto Exit after %i seconds.\n", @autoexit);
330
+ end
331
+ end
332
+ end
333
+
334
+ def visible(vis)
335
+ glutIdleFunc((vis == GLUT_VISIBLE ? method(:idle).to_proc : nil))
336
+ end
337
+
338
+ def mouse(button, state, x, y)
339
+ @mouse = state
340
+ @x0, @y0 = x, y
341
+ end
342
+
343
+ def motion(x, y)
344
+ if @mouse == GLUT_DOWN then
345
+ @view_roty += @x0 - x
346
+ @view_rotx += @y0 - y
347
+ end
348
+ @x0, @y0 = x, y
349
+ end
350
+
351
+
352
+ def glut_init
353
+ if SHOW_FFI
354
+ glutInit(MemoryPointer.new(:int, 1).put_int(0, 0),
355
+ MemoryPointer.new(:pointer, 1).put_pointer(0, nil))
356
+ else
357
+ glutInit
358
+
359
+ end
360
+ end
361
+
362
+
363
+ def initialize
364
+ #POS = @mPOS
365
+ #RED = @mRED
366
+ #GREEN = @mGREEN
367
+ #BLUE = @mBLUE
368
+
369
+ puts 'initialize() call glut_init :: init_soko|no gears equivalent '
370
+ glut_init
371
+ # argc, argv parameters
372
+
373
+ puts 'initialize() set DisplayMode'
374
+ if SHOW_GEARS
375
+
376
+
377
+ glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE)
378
+ else
379
+
380
+ glutInitDisplayMode GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH
381
+
382
+ #glutInitDisplayMode GLUT_DOUBLE | GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH
383
+ Sokoban1::Render.init_soko
384
+ end
385
+
386
+
387
+
388
+ glutInitWindowPosition(0, 0)
389
+ glutInitWindowSize(300, 300)
390
+
391
+
392
+
393
+
394
+ end
395
+
396
+ def register_glut_with_gears
397
+ glutDisplayFunc(method(:draw_gears).to_proc)
398
+
399
+ glutReshapeFunc(method(:reshape_gears).to_proc)
400
+ glutKeyboardFunc(method(:key).to_proc)
401
+ glutSpecialFunc(method(:special).to_proc)
402
+ glutVisibilityFunc(method(:visible).to_proc)
403
+ glutMouseFunc(method(:mouse).to_proc)
404
+ glutMotionFunc(method(:motion).to_proc)
405
+ end
406
+ def start
407
+
408
+
409
+ puts 'start() enable lights'
410
+ puts 'start() register glut with methods'
411
+ if SHOW_GEARS
412
+
413
+ glutCreateWindow('Gears')
414
+ enable_light_for_gears
415
+ init_gears
416
+
417
+ register_glut_with_gears
418
+ else
419
+ init_gears
420
+ glutCreateWindow('SokoBan')
421
+ Sokoban1::Render.enable_light_soko
422
+ Sokoban1::Render.register_glut_with_soko
423
+ end
424
+ glutMainLoop()
425
+ end
426
+
427
+ end
428
+
429
+ #Gears.new.start
430
+
431
+ #puts 'initialize() call init_soko , glut_init'
432
+ #puts 'start() set DisplayMode'
433
+ #puts 'start() enable lights'
434
+ #puts 'start() register glut with methods'
435
+ if __FILE__ == $0
436
+
437
+
438
+
439
+ require File.dirname(__FILE__) + '/../lib/sokoban'
440
+ #include Config1
441
+ puts 'SHOW FFI?'
442
+ Sokoban1::Config.detect
443
+
444
+ if Sokoban1::Config.show_ffi
445
+ #$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
446
+ SHOW_FFI = true
447
+ gem 'ffi-opengl'
448
+ require 'ffi-opengl'
449
+ include FFI
450
+ puts true
451
+ else
452
+ SHOW_FFI = false
453
+ require 'opengl'
454
+ puts false
455
+ end
456
+
457
+
458
+
459
+
460
+
461
+
462
+
463
+
464
+ include GL, GLU, GLUT
465
+ include Math
466
+ #include Render
467
+
468
+
469
+ app = Gears.new
470
+
471
+ #app = Soko.new
472
+ #app.light_diffuse = MemoryPointer.new(:float, 4).put_array_of_float(0, [1.0, 1.0, 1.0, 1.0])
473
+ #app.light_position = MemoryPointer.new(:float, 4).put_array_of_float(0, [1.0, 1.0, 1.0, 0.0])
474
+
475
+ puts 'create Constants for color with ffi pointer array'
476
+ app.mPOS = convertf( [5.0, 5.0, 10.0, 0.0] )
477
+ app.mRED = convertf( [0.8, 0.1, 0.0, 1.0] )
478
+ app.mGREEN = convertf( [0.0, 0.8, 0.2, 1.0])
479
+ app.mBLUE = convertf ([0.2, 0.2, 1.0, 1.0])
480
+
481
+
482
+ #app.init_ffi
483
+ app.start
484
+ end
485
+
@@ -0,0 +1,22 @@
1
+ module Sokoban1
2
+ class Config
3
+ class << self
4
+ attr_accessor :show_ffi
5
+ def detect
6
+ puts 'ruby in use:'
7
+ rubyv = RUBY_VERSION
8
+ if rubyv == "1.9.1"
9
+ puts "1.9.1"
10
+ @show_ffi = false
11
+ elsif rubyv == "1.9.2"
12
+ puts "1.9.2"
13
+
14
+ @show_ffi = true
15
+ else
16
+ exit
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "yaml"
4
+
5
+ module Sokoban1
6
+ class Sokoban
7
+ WALL = "#"
8
+ OPEN_FLOOR = " "
9
+
10
+ MAN = "@"
11
+ CRATE = "o"
12
+
13
+ STORAGE = "."
14
+ MAN_ON_STORAGE = "+"
15
+ CRATE_ON_STORAGE = "*"
16
+
17
+ MAX_UNDO = 10
18
+
19
+ PATH = File.expand_path(File.dirname(__FILE__))
20
+
21
+ attr_reader :level, :moves
22
+
23
+ def self.load( file = File.join(PATH, "sokoban_saved_game.yaml") )
24
+ game = nil
25
+
26
+ File.open file do |f|
27
+ game = YAML.load(f)
28
+ end
29
+
30
+ game ||= Sokoban.new
31
+ game
32
+ end
33
+
34
+ def initialize( file = File.join(PATH, "sokoban_levels.txt") )
35
+ @level_file = file
36
+
37
+ @board = [ ]
38
+ @level = 0
39
+ @over = false
40
+
41
+ @undos = [ ]
42
+ @moves = 0
43
+
44
+ load_level
45
+ end
46
+
47
+ def can_move_down?( ) can_move? :down end
48
+ def can_move_left?( ) can_move? :left end
49
+ def can_move_right?( ) can_move? :right end
50
+ def can_move_up?( ) can_move? :up end
51
+
52
+ def display
53
+ @board.inject("") { |dis, row| dis + row.join + "\n" }
54
+ end
55
+
56
+ def level_solved?
57
+ @board.each_with_index do |row, y|
58
+ row.each_with_index do |cell, x|
59
+ return false if cell == CRATE
60
+ end
61
+ end
62
+ true
63
+ end
64
+
65
+ def load_level( level = @level += 1, file = @level_file )
66
+ loaded = false
67
+
68
+ File.open file do |f|
69
+ count = 0
70
+ while lvl = f.gets("")
71
+ count += 1
72
+ if count == level
73
+ @board = [ ]
74
+ lvl.chomp!
75
+ lvl.each_line { |e| @board << e.chomp.split("") }
76
+ loaded = true
77
+ break
78
+ end
79
+ end
80
+ end
81
+
82
+ if loaded
83
+ @undos = [ ]
84
+ @moves = 0
85
+ else
86
+ @over = true
87
+ end
88
+
89
+ loaded
90
+ end
91
+
92
+ def move_down( ) move :down end
93
+ def move_left( ) move :left end
94
+ def move_right( ) move :right end
95
+ def move_up( ) move :up end
96
+
97
+ def over?
98
+ @over
99
+ end
100
+
101
+ def restart_level
102
+ load_level @level
103
+ end
104
+
105
+ def save( file = File.join(PATH, "sokoban_saved_game.yaml") )
106
+ File.open(file, "w") do |f|
107
+ f << YAML.dump(self)
108
+ end
109
+ end
110
+
111
+ def undo
112
+ if @undos.size > 0
113
+ @board = @undos.pop
114
+ @moves -= 1
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ def can_move?( dir )
121
+ x, y = where_am_i
122
+ case dir
123
+ when :down
124
+ first = @board[y + 1][x]
125
+ second = y < @board.size - 2 ? @board[y + 2][x] : nil
126
+ when :left
127
+ first = @board[y][x - 1]
128
+ second = x >= 2 ? @board[y][x - 2] : nil
129
+ when :right
130
+ first = @board[y][x + 1]
131
+ second = x < @board[y].size - 2 ? @board[y][x + 2] : nil
132
+ when :up
133
+ first = @board[y - 1][x]
134
+ second = y >= 2 ? @board[y - 2][x] : nil
135
+ end
136
+
137
+ if first == OPEN_FLOOR or first == STORAGE
138
+ true
139
+ elsif not second.nil? and
140
+ (first == CRATE or first == CRATE_ON_STORAGE) and
141
+ (second == OPEN_FLOOR or second == STORAGE)
142
+ true
143
+ else
144
+ false
145
+ end
146
+ end
147
+
148
+ def move( dir )
149
+ return false unless can_move? dir
150
+
151
+ @undos << Marshal.load(Marshal.dump(@board))
152
+ @undos.shift if @undos.size > MAX_UNDO
153
+ @moves += 1
154
+
155
+ x, y = where_am_i
156
+ case dir
157
+ when :down
158
+ if @board[y + 1][x] == CRATE or @board[y + 1][x] == CRATE_ON_STORAGE
159
+ move_crate x, y + 1, x, y + 2
160
+ end
161
+ move_man x, y, x, y + 1
162
+ when :left
163
+ if @board[y][x - 1] == CRATE or @board[y][x - 1] == CRATE_ON_STORAGE
164
+ move_crate x - 1, y, x - 2, y
165
+ end
166
+ move_man x, y, x - 1, y
167
+ when :right
168
+ if @board[y][x + 1] == CRATE or @board[y][x + 1] == CRATE_ON_STORAGE
169
+ move_crate x + 1, y, x + 2, y
170
+ end
171
+ move_man x, y, x + 1, y
172
+ when :up
173
+ if @board[y - 1][x] == CRATE or @board[y - 1][x] == CRATE_ON_STORAGE
174
+ move_crate x, y - 1, x, y - 2
175
+ end
176
+ move_man x, y, x, y - 1
177
+ end
178
+ true
179
+ end
180
+
181
+ def move_crate( from_x, from_y, to_x, to_y )
182
+ if @board[to_y][to_x] == STORAGE
183
+ @board[to_y][to_x] = CRATE_ON_STORAGE
184
+ else
185
+ @board[to_y][to_x] = CRATE
186
+ end
187
+ if @board[from_y][from_x] == CRATE_ON_STORAGE
188
+ @board[from_y][from_x] = STORAGE
189
+ else
190
+ @board[from_y][from_x] = OPEN_FLOOR
191
+ end
192
+ end
193
+
194
+ def move_man( from_x, from_y, to_x, to_y )
195
+ if @board[to_y][to_x] == STORAGE
196
+ @board[to_y][to_x] = MAN_ON_STORAGE
197
+ else
198
+ @board[to_y][to_x] = MAN
199
+ end
200
+ if @board[from_y][from_x] == MAN_ON_STORAGE
201
+ @board[from_y][from_x] = STORAGE
202
+ else
203
+ @board[from_y][from_x] = OPEN_FLOOR
204
+ end
205
+ end
206
+
207
+ def where_am_i
208
+ @board.each_with_index do |row, y|
209
+ row.each_with_index do |cell, x|
210
+ return x, y if cell == MAN or cell == MAN_ON_STORAGE
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end