sokoban 0.0.16 → 0.0.17
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.
- checksums.yaml +4 -4
- data/lib/sokoban.rb +32 -23
- data/lib/sokoban/version.rb +1 -1
- data/spec/lib/sokoban_spec.rb +25 -35
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a921867ffc4091581ea2ba07a96d0fd98ede029
|
4
|
+
data.tar.gz: 7a66174afaff10b372deedbe8dc342348ee0bf57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 52e6766c2d227cc6aa5beb2afa5c83b22df5156af6413421b2c4bcc5edc3d1f2d8512965fb2f042328e738d085e4e47e3b222355a013b848ece8f5c287140a0d
|
7
|
+
data.tar.gz: c9aaea1824af81935a5dedd4d45855c0787c0d98e2355cd88340b3bed1c15cf6e2b31ace35c700b70449b0878c69472e208552327d1d113513024f00f4de8ef0
|
data/lib/sokoban.rb
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
# Copyright 2004 Dennis Ranke <dennis.ranke at epost.de>
|
8
8
|
# Copyright 2013 Boohbah <boohbah at gmail.com>
|
9
9
|
|
10
|
-
require File.expand_path(
|
10
|
+
require File.expand_path('../../lib/sokoban/version.rb', __FILE__)
|
11
11
|
require 'io/console'
|
12
12
|
require 'optparse'
|
13
13
|
require 'logger'
|
@@ -18,7 +18,7 @@ module Sokoban
|
|
18
18
|
BASENAME = File.basename($0, '.rb')
|
19
19
|
SAV_FILE = "#{HOME}/sokoban.save.gz"
|
20
20
|
LOG_FILE = "#{HOME}/sokoban-#{Time.now.to_i}.log"
|
21
|
-
LVL_FILE = File.expand_path(
|
21
|
+
LVL_FILE = File.expand_path('../../sokoban_levels.txt', __FILE__)
|
22
22
|
|
23
23
|
LEVELS = File.open(LVL_FILE).readlines.map do |l|
|
24
24
|
l.chomp.ljust(19)
|
@@ -33,11 +33,16 @@ module Sokoban
|
|
33
33
|
D: { x: 1, y: 0 } # 4
|
34
34
|
}
|
35
35
|
|
36
|
+
# Global-ish variables to ease testing
|
37
|
+
$batch, $hax = false, false
|
38
|
+
|
39
|
+
# Game class separates logger and optparse from internal
|
40
|
+
# Level logic and handles level changes and win condition
|
36
41
|
class Game
|
37
42
|
def initialize
|
38
43
|
@level = Level.new
|
39
44
|
parse_opts
|
40
|
-
#
|
45
|
+
# --level and --resume may change lnum
|
41
46
|
# set @lnum after parse_opts
|
42
47
|
@lnum = @level.lnum
|
43
48
|
play
|
@@ -151,16 +156,21 @@ module Sokoban
|
|
151
156
|
@moves.size
|
152
157
|
end
|
153
158
|
|
159
|
+
# The public interface to initialize()
|
154
160
|
def restart(lnum)
|
155
161
|
initialize(lnum)
|
156
162
|
end
|
157
163
|
|
164
|
+
def parse_move_string(string)
|
165
|
+
string.each_char {|c| parse_move(c) }
|
166
|
+
end
|
167
|
+
|
158
168
|
def play
|
159
169
|
while free_boxes > 0
|
160
170
|
print self
|
161
171
|
if $batch
|
162
172
|
# Input a string terminated by newline
|
163
|
-
gets.strip
|
173
|
+
parse_move_string(gets.strip)
|
164
174
|
else
|
165
175
|
# Handle single key press
|
166
176
|
parse_move(STDIN.getch)
|
@@ -174,7 +184,7 @@ module Sokoban
|
|
174
184
|
unless @lnum == 50
|
175
185
|
print "Congratulations, on to level #{@lnum + 1}. "
|
176
186
|
end
|
177
|
-
STDIN.getch
|
187
|
+
STDIN.getch
|
178
188
|
end
|
179
189
|
|
180
190
|
def free_boxes
|
@@ -195,12 +205,10 @@ module Sokoban
|
|
195
205
|
|
196
206
|
def load_saved_game
|
197
207
|
file = Zlib::GzipReader.open(SAV_FILE) {|f| f.read }
|
198
|
-
# Gzip becomes space-efficient when @moves is ~100 or more
|
199
208
|
file = file.unpack('C' * file.size)
|
200
|
-
# Initialize
|
201
|
-
@lnum = file.shift
|
202
|
-
@
|
203
|
-
@state = [@level.dup]
|
209
|
+
# Initialize Level with @lnum from file
|
210
|
+
@lnum = file.shift
|
211
|
+
initialize(@lnum)
|
204
212
|
# Rebuild @moves
|
205
213
|
moves = []
|
206
214
|
file.map {|c| moves << unpack_nibs(c) }
|
@@ -208,13 +216,13 @@ module Sokoban
|
|
208
216
|
end
|
209
217
|
|
210
218
|
def parse_move(input)
|
211
|
-
case input
|
219
|
+
case input.downcase
|
212
220
|
when 'b'; $batch = ! $batch
|
213
221
|
when 'w', 'k', '8'; move(:W) #UP)
|
214
222
|
when 'a', 'h', '4'; move(:A) #LEFT)
|
215
223
|
when 's', 'j', '2'; move(:S) #DOWN)
|
216
224
|
when 'd', 'l', '6'; move(:D) #RIGHT)
|
217
|
-
when 'r';
|
225
|
+
when 'r'; initialize(@lnum)
|
218
226
|
when 'u'; undo
|
219
227
|
when 'q'; quit
|
220
228
|
when "\u0003"; puts; exit # ^C - Quit without save
|
@@ -223,28 +231,29 @@ module Sokoban
|
|
223
231
|
|
224
232
|
private
|
225
233
|
|
226
|
-
|
227
|
-
|
228
|
-
a, b = ("%2X" % c).split(//).map(&:to_i)
|
229
|
-
b.zero? ? a : [a, b]
|
234
|
+
def clear_screen
|
235
|
+
30.times { puts }
|
230
236
|
end
|
231
237
|
|
232
238
|
def pack_nibs(a, b)
|
233
239
|
b.nil? ? (a << 4) | 0 : (a << 4) | b
|
234
240
|
end
|
235
241
|
|
242
|
+
def unpack_nibs(c)
|
243
|
+
a, b = ("%2X" % c).split(//).map(&:to_i)
|
244
|
+
b.zero? ? a : [a, b]
|
245
|
+
end
|
246
|
+
|
236
247
|
def save_game
|
237
248
|
file = [@lnum]
|
249
|
+
# @moves are 1, 2, 3, or 4 to enable easy bit packing
|
238
250
|
@moves.each_slice(2) {|s| file << pack_nibs(s[0], s[1]) }
|
239
251
|
file = file.pack('C' * file.size)
|
252
|
+
# Gzip becomes space-efficient when @moves is ~99
|
240
253
|
Zlib::GzipWriter.open(SAV_FILE) {|f| f.write(file) }
|
241
254
|
# $ zcat sokoban.save.gz | hexdump
|
242
255
|
end
|
243
256
|
|
244
|
-
def clear_screen
|
245
|
-
30.times { puts }
|
246
|
-
end
|
247
|
-
|
248
257
|
def undo
|
249
258
|
if @moves.size > 0
|
250
259
|
@moves.pop
|
@@ -255,9 +264,9 @@ module Sokoban
|
|
255
264
|
end
|
256
265
|
|
257
266
|
def box_moved?
|
258
|
-
|
259
|
-
if
|
260
|
-
return true if @state.last[i] !=
|
267
|
+
@level.split(//).each_with_index do |char, i|
|
268
|
+
if char =~ /o|\*/
|
269
|
+
return true if @state.last[i] != char
|
261
270
|
end
|
262
271
|
end
|
263
272
|
return false
|
data/lib/sokoban/version.rb
CHANGED
data/spec/lib/sokoban_spec.rb
CHANGED
@@ -35,99 +35,89 @@ describe Level do
|
|
35
35
|
expect { Level.new(54) }.to raise_error(RangeError)
|
36
36
|
end
|
37
37
|
|
38
|
-
def input_moves(level)
|
39
|
-
INPUT.each_char {|c| level.parse_move(c.downcase) }
|
40
|
-
end
|
41
|
-
|
42
38
|
it "accepts valid moves" do
|
43
39
|
level = Level.new
|
44
|
-
|
40
|
+
level.parse_move_string(INPUT)
|
45
41
|
expect(level.moves).to eq(INPUT.size)
|
46
42
|
end
|
47
43
|
|
48
44
|
it "ignores invalid moves" do
|
49
45
|
level = Level.new
|
50
|
-
|
51
|
-
level.parse_move(
|
46
|
+
level.parse_move_string(INPUT)
|
47
|
+
level.parse_move("F") # shouldn"t increment moves
|
52
48
|
expect(level.moves).to eq(INPUT.size)
|
53
49
|
end
|
54
50
|
|
55
51
|
it "restarts" do
|
56
52
|
level = Level.new
|
57
|
-
|
53
|
+
level.parse_move_string(INPUT)
|
58
54
|
expect(level.free_boxes).to eq(1)
|
59
|
-
level.parse_move(
|
55
|
+
level.parse_move("R") # restart
|
60
56
|
expect(level.free_boxes).to eq(6)
|
61
57
|
end
|
62
58
|
|
63
59
|
it "plays level 1" do
|
64
60
|
level = Level.new
|
65
|
-
|
66
|
-
level.parse_move(
|
61
|
+
level.parse_move_string(INPUT)
|
62
|
+
level.parse_move("D") # right
|
67
63
|
expect(level.free_boxes).to eq(0) # Victory!
|
68
64
|
end
|
69
65
|
|
70
66
|
it "increments pushes on box move" do
|
71
67
|
level = Level.new
|
72
|
-
|
68
|
+
level.parse_move_string(INPUT)
|
73
69
|
expect(level.pushes).to eq(96)
|
74
|
-
level.parse_move(
|
70
|
+
level.parse_move("D") # right
|
75
71
|
expect(level.pushes).to eq(97)
|
76
72
|
end
|
77
73
|
|
78
74
|
it "decrements pushes on box move undo" do
|
79
75
|
level = Level.new
|
80
|
-
|
81
|
-
level.parse_move(
|
76
|
+
level.parse_move_string(INPUT)
|
77
|
+
level.parse_move("D") # right
|
82
78
|
expect(level.pushes).to eq(97)
|
83
|
-
level.parse_move(
|
79
|
+
level.parse_move("U") # undo
|
84
80
|
expect(level.pushes).to eq(96)
|
85
81
|
end
|
86
82
|
|
87
83
|
it "ignores moves into walls" do
|
88
84
|
level = Level.new
|
89
|
-
|
90
|
-
level.parse_move(
|
85
|
+
level.parse_move_string(INPUT)
|
86
|
+
level.parse_move("A") # left
|
91
87
|
expect(level.moves).to eq(INPUT.size + 1)
|
92
88
|
# Try to move into wall
|
93
|
-
level.parse_move(
|
89
|
+
level.parse_move("W") # up
|
94
90
|
expect(level.moves).to eq(INPUT.size + 1)
|
95
91
|
end
|
96
92
|
|
97
93
|
it "ignores moves into non-free boxes" do
|
98
94
|
level = Level.new
|
99
|
-
|
100
|
-
level.
|
101
|
-
level.parse_move('d') # right
|
95
|
+
level.parse_move_string(INPUT)
|
96
|
+
level.parse_move_string("SD")
|
102
97
|
expect(level.moves).to eq(INPUT.size + 2)
|
103
98
|
# Try to move into non-free box
|
104
|
-
level.parse_move(
|
99
|
+
level.parse_move("D") # right
|
105
100
|
expect(level.moves).to eq(INPUT.size + 2)
|
106
101
|
end
|
107
102
|
|
108
103
|
it "ignores pushes into walls" do
|
109
104
|
level = Level.new
|
110
|
-
|
111
|
-
level.
|
112
|
-
level.parse_move('d') # right
|
113
|
-
level.parse_move('w') # up
|
105
|
+
level.parse_move_string(INPUT)
|
106
|
+
level.parse_move_string("SDW") # down, right, up
|
114
107
|
expect(level.pushes).to eq(97)
|
115
108
|
# Try to push box into wall
|
116
|
-
level.parse_move(
|
109
|
+
level.parse_move("W") # up
|
117
110
|
expect(level.pushes).to eq(97)
|
118
111
|
end
|
119
112
|
|
120
113
|
it "ignores pushes into non-free boxes" do
|
121
114
|
level = Level.new
|
122
|
-
|
123
|
-
level.
|
124
|
-
level.parse_move('d') # right
|
125
|
-
level.parse_move('s') # down
|
115
|
+
level.parse_move_string(INPUT)
|
116
|
+
level.parse_move_string("WDS") # up, right, down
|
126
117
|
expect(level.pushes).to eq(97)
|
127
|
-
level.
|
128
|
-
level.parse_move('s') # down
|
118
|
+
level.parse_move_string("AS") # left, down
|
129
119
|
# Try to push box into non-free box
|
130
|
-
level.parse_move(
|
120
|
+
level.parse_move("D") # right
|
131
121
|
expect(level.pushes).to eq(97)
|
132
122
|
end
|
133
123
|
end
|