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 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