sokoban 0.0.16 → 0.0.17

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7fc20a6c91e17b03742439b6efd20795ce307551
4
- data.tar.gz: f1e3f9517ca58f4434cb0c76cdc139beec833440
3
+ metadata.gz: 0a921867ffc4091581ea2ba07a96d0fd98ede029
4
+ data.tar.gz: 7a66174afaff10b372deedbe8dc342348ee0bf57
5
5
  SHA512:
6
- metadata.gz: 3168eeffb1063706c72efc042b6f3d4a2164011cd24cda0eda8cc63c80dce2bf9b79252054da31888312ea2a7645f5c0c9335e599b7690eb0c3d83eaeb691a55
7
- data.tar.gz: 8412f24863ac3923a1b1f8df2645632ecc5e782827fc958197550aee1647c7a6daff07b7f497001810e2dd2249ab24264ddea5d4709302273f1c0d922c7229fa
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("../../lib/sokoban/version.rb", __FILE__)
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("../../sokoban_levels.txt", __FILE__)
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
- # -l and -r opts may change lnum
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.each_char {|c| parse_move(c.downcase) }
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 unless $batch
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 @level @state @lnum from file
201
- @lnum = file.shift # @lnum is the first byte
202
- @level = LEVELS[@lnum - 1].ljust(19 * 16)
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'; restart(@lnum)
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
- # @lnum is one byte and each move is 4 bits
227
- def unpack_nibs(c)
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
- 0.upto(@level.size - 1) do |i|
259
- if @level[i].match(/o|\*/)
260
- return true if @state.last[i] != @level[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
@@ -1,3 +1,3 @@
1
1
  module Sokoban
2
- VERSION = '0.0.16'
2
+ VERSION = '0.0.17'
3
3
  end
@@ -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
- input_moves(level)
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
- input_moves(level)
51
- level.parse_move('f') # shouldn't increment moves
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
- input_moves(level)
53
+ level.parse_move_string(INPUT)
58
54
  expect(level.free_boxes).to eq(1)
59
- level.parse_move('r') # restart
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
- input_moves(level)
66
- level.parse_move('d') # right
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
- input_moves(level)
68
+ level.parse_move_string(INPUT)
73
69
  expect(level.pushes).to eq(96)
74
- level.parse_move('d') # right
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
- input_moves(level)
81
- level.parse_move('d') # right
76
+ level.parse_move_string(INPUT)
77
+ level.parse_move("D") # right
82
78
  expect(level.pushes).to eq(97)
83
- level.parse_move('u') # undo
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
- input_moves(level)
90
- level.parse_move('a') # left
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('w') # up
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
- input_moves(level)
100
- level.parse_move('s') # down
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('d') # right
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
- input_moves(level)
111
- level.parse_move('s') # down
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('w') # up
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
- input_moves(level)
123
- level.parse_move('w') # up
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.parse_move('a') # left
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('d') # right
120
+ level.parse_move("D") # right
131
121
  expect(level.pushes).to eq(97)
132
122
  end
133
123
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sokoban
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.16
4
+ version: 0.0.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Boohbah