about summary refs log tree commit diff stats
path: root/app/assets/javascripts/serializer.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/serializer.js')
-rw-r--r--app/assets/javascripts/serializer.js365
1 files changed, 365 insertions, 0 deletions
diff --git a/app/assets/javascripts/serializer.js b/app/assets/javascripts/serializer.js new file mode 100644 index 0000000..70c7f0f --- /dev/null +++ b/app/assets/javascripts/serializer.js
@@ -0,0 +1,365 @@
1namespace(function() {
2
3window.serializePuzzle = function(puzzle) {
4 var s = new Serializer('w')
5 var version = 0
6
7 s.writeInt(version)
8 s.writeByte(puzzle.width)
9 s.writeByte(puzzle.height)
10 s.writeString(puzzle.name)
11
12 var genericFlags = 0
13 if (puzzle.autoSolved) genericFlags |= GENERIC_FLAG_AUTOSOLVED
14 if (puzzle.symmetry) {
15 genericFlags |= GENERIC_FLAG_SYMMETRICAL
16 if (puzzle.symmetry.x) genericFlags |= GENERIC_FLAG_SYMMETRY_X
17 if (puzzle.symmetry.y) genericFlags |= GENERIC_FLAG_SYMMETRY_Y
18 }
19 if (puzzle.pillar) genericFlags |= GENERIC_FLAG_PILLAR
20 s.writeByte(genericFlags)
21 for (var x=0; x<puzzle.width; x++) {
22 for (var y=0; y<puzzle.height; y++) {
23 s.writeCell(puzzle.grid[x][y])
24 }
25 }
26
27 if (puzzle.path != null) {
28 var startPos = puzzle.path.pop()
29 if (puzzle.path.length > 0) {
30 s.writeInt(puzzle.path.length)
31 s.writeByte(startPos.x)
32 s.writeByte(startPos.y)
33 for (var dir of puzzle.path) s.writeByte(dir)
34 }
35 } else {
36 s.writeInt(0)
37 }
38
39 var settingsFlags = 0
40 if (puzzle.settings.NEGATIONS_CANCEL_NEGATIONS) settingsFlags |= SETTINGS_FLAG_NCN
41 if (puzzle.settings.SHAPELESS_ZERO_POLY) settingsFlags |= SETTINGS_FLAG_SZP
42 if (puzzle.settings.PRECISE_POLYOMINOS) settingsFlags |= SETTINGS_FLAG_PP
43 if (puzzle.settings.FLASH_FOR_ERRORS) settingsFlags |= SETTINGS_FLAG_FFE
44 if (puzzle.settings.FAT_STARTPOINTS) settingsFlags |= SETTINGS_FLAG_FS
45 if (puzzle.settings.CUSTOM_MECHANICS) settingsFlags |= SETTINGS_FLAG_CM
46 if (puzzle.settings.INVISIBLE_SYMMETRY) settingsFlags |= SETTINGS_FLAG_IS
47 s.writeByte(settingsFlags)
48
49 s.writeByte(puzzle.symType)
50
51 return s.str()
52}
53
54window.deserializePuzzle = function(data) {
55 // Data is JSON, so decode it with the old deserializer
56 if (data[0] == '{') return Puzzle.deserialize(data)
57
58 var s = new Serializer('r', data)
59 var version = s.readInt()
60 if (version > 0) throw Error('Cannot read data from unknown version: ' + version)
61
62 var width = s.readByte()
63 var height = s.readByte()
64 var puzzle = new Puzzle(Math.floor(width / 2), Math.floor(height / 2))
65 puzzle.name = s.readString()
66
67 var genericFlags = s.readByte()
68 puzzle.autoSolved = genericFlags & GENERIC_FLAG_AUTOSOLVED
69 puzzle.symType = SYM_TYPE_NONE
70 if ((genericFlags & GENERIC_FLAG_SYMMETRICAL) != 0) {
71 if ((genericFlags & GENERIC_FLAG_SYMMETRY_X) != 0) {
72 if ((genericFlags & GENERIC_FLAG_SYMMETRY_Y) != 0) {
73 puzzle.symType = SYM_TYPE_ROTATIONAL
74 } else {
75 puzzle.symType = SYM_TYPE_VERTICAL
76 }
77 } else if ((genericFlags & GENERIC_FLAG_SYMMETRY_Y) != 0) {
78 puzzle.symType = SYM_TYPE_HORIZONTAL
79 }
80 }
81 puzzle.pillar = (genericFlags & GENERIC_FLAG_PILLAR) != 0
82 for (var x=0; x<width; x++) {
83 for (var y=0; y<height; y++) {
84 puzzle.grid[x][y] = s.readCell()
85 }
86 }
87
88 var pathLength = s.readInt()
89 if (pathLength > 0) {
90 var path = [{
91 'x': s.readByte(),
92 'y': s.readByte(),
93 }]
94 for (var i=0; i<pathLength; i++) path.push(s.readByte())
95 }
96
97 var settingsFlags = s.readByte()
98 puzzle.settings = {
99 NEGATIONS_CANCEL_NEGATIONS: (settingsFlags & SETTINGS_FLAG_NCN) != 0,
100 SHAPELESS_ZERO_POLY: (settingsFlags & SETTINGS_FLAG_SZP) != 0,
101 PRECISE_POLYOMINOS: (settingsFlags & SETTINGS_FLAG_PP) != 0,
102 FLASH_FOR_ERRORS: (settingsFlags & SETTINGS_FLAG_FFE) != 0,
103 FAT_STARTPOINTS: (settingsFlags & SETTINGS_FLAG_FS) != 0,
104 CUSTOM_MECHANICS: (settingsFlags & SETTINGS_FLAG_CM) != 0,
105 INVISIBLE_SYMMETRY: (settingsFlags & SETTINGS_FLAG_IS) != 0,
106 }
107
108 if (s.hasLeft(1)) {
109 puzzle.symType = s.readByte()
110 }
111
112 s.destroy()
113 return puzzle
114}
115
116class Serializer {
117 constructor(mode, data) {
118 this.mode = mode
119 if (mode == 'r') {
120 if (data == null) throw Error('No data provided to a read constructor')
121 if (data[0] != '_') throw Error('Cannot read data, improperly prefixed')
122 this.data = window.atob(data.slice(1))
123 this.index = 0
124 } else if (mode == 'w') {
125 this.data = []
126
127 var canvas = document.createElement('canvas')
128 canvas.height = 1
129 canvas.width = 1
130 this.colorConverter = canvas.getContext('2d')
131 } else {
132 throw Error('Unknown serializer mode: ' + mode)
133 }
134
135 }
136
137 destroy() {
138 if (this.mode == 'r' && this.index < this.data.length) {
139 throw Error('Read not done, ' + (this.data.length - this.index) + ' bytes remain')
140 }
141 }
142
143 str() {
144 if (this.mode != 'w') throw Error('Cannot get string from a serializer opened in mode: ' + this.mode)
145 return '_' + window.btoa(this.data)
146 }
147
148 _checkRead(numBytes = 1) {
149 if (this.mode != 'r') throw Error('Cannot read data from a serializer opened in mode: ' + this.mode)
150 if (this.data.length < numBytes) throw Error('Cannot read ' + numBytes + ' bytes from a stream with only '+ this.data.length + ' bytes')
151 }
152
153 hasLeft(numBytes = 1) {
154 return ((this.data.length - this.index) >= numBytes)
155 }
156
157 readByte() {
158 this._checkRead()
159 return this.data.charCodeAt(this.index++)
160 }
161
162 writeByte(b) {
163 if (b < 0 || b > 0xFF) throw Error('Cannot write out-of-range byte ' + b)
164 this.data += String.fromCharCode(b)
165 }
166
167 readInt() {
168 var b1 = this.readByte() << 0
169 var b2 = this.readByte() << 8
170 var b3 = this.readByte() << 16
171 var b4 = this.readByte() << 24
172 return b1 | b2 | b3 | b4
173 }
174
175 writeInt(i) {
176 if (i < 0 || i > 0xFFFFFFFF) throw Error('Cannot write out-of-range int ' + i)
177 var b1 = (i & 0x000000FF) >> 0
178 var b2 = (i & 0x0000FF00) >> 8
179 var b3 = (i & 0x00FF0000) >> 16
180 var b4 = (i & 0xFF000000) >> 24
181 this.writeByte(b1)
182 this.writeByte(b2)
183 this.writeByte(b3)
184 this.writeByte(b4)
185 }
186
187 readLong() {
188 var i1 = this.readInt() << 32
189 var i2 = this.readInt()
190 return i1 | i2
191 }
192
193 writeLong(l) {
194 if (l < 0 || l > 0xFFFFFFFFFFFFFFFF) throw Error('Cannot write out-of-range long ' + l)
195 var i1 = l & 0xFFFFFFFF
196 var i2 = (l - i1) / 0x100000000
197 this.writeInt(i1)
198 this.writeInt(i2)
199 }
200
201 readString() {
202 var len = this.readInt()
203 this._checkRead(len)
204 var str = this.data.substr(this.index, len)
205 this.index += len
206 return str
207 }
208
209 writeString(s) {
210 if (s == null) {
211 this.writeInt(0)
212 return
213 }
214 this.writeInt(s.length)
215 this.data += s
216 }
217
218 readColor() {
219 var r = this.readByte().toString()
220 var g = this.readByte().toString()
221 var b = this.readByte().toString()
222 var a = this.readByte().toString()
223 return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + a + ')'
224 }
225
226 writeColor(c) {
227 // Adapted from https://gist.github.com/njvack/02ad8efcb0d552b0230d
228 this.colorConverter.fillStyle = 'rgba(0, 0, 0, 0)' // Load a default in case we are passed garbage
229 this.colorConverter.clearRect(0, 0, 1, 1)
230 this.colorConverter.fillStyle = c
231 this.colorConverter.fillRect(0, 0, 1, 1)
232 var rgba = this.colorConverter.getImageData(0, 0, 1, 1).data
233 this.writeByte(rgba[0])
234 this.writeByte(rgba[1])
235 this.writeByte(rgba[2])
236 this.writeByte(rgba[3])
237 }
238
239 readCell() {
240 var cellType = this.readByte()
241 if (cellType === CELL_TYPE_NULL) return null
242
243 var cell = {}
244 cell.dir = null
245 cell.line = 0
246 if (cellType === CELL_TYPE_LINE) {
247 cell.type = 'line'
248 cell.line = this.readByte()
249 var dot = this.readByte()
250 if (dot != 0) cell.dot = dot
251 var gap = this.readByte()
252 if (gap != 0) cell.gap = gap
253 } else if (cellType === CELL_TYPE_SQUARE) {
254 cell.type = 'square'
255 cell.color = this.readColor()
256 } else if (cellType === CELL_TYPE_STAR) {
257 cell.type = 'star'
258 cell.color = this.readColor()
259 } else if (cellType === CELL_TYPE_NEGA) {
260 cell.type = 'nega'
261 cell.color = this.readColor()
262 } else if (cellType === CELL_TYPE_TRIANGLE) {
263 cell.type = 'triangle'
264 cell.color = this.readColor()
265 cell.count = this.readByte()
266 } else if (cellType === CELL_TYPE_POLY) {
267 cell.type = 'poly'
268 cell.color = this.readColor()
269 cell.polyshape = this.readLong()
270 } else if (cellType === CELL_TYPE_YLOP) {
271 cell.type = 'ylop'
272 cell.color = this.readColor()
273 cell.polyshape = this.readLong()
274 } else if (cellType == CELL_TYPE_NONCE) {
275 cell.type = 'nonce'
276 }
277
278 var startEnd = this.readByte()
279 if (startEnd & CELL_START) cell.start = true
280 if (startEnd & CELL_END_LEFT) cell.end = 'left'
281 if (startEnd & CELL_END_RIGHT) cell.end = 'right'
282 if (startEnd & CELL_END_TOP) cell.end = 'top'
283 if (startEnd & CELL_END_BOTTOM) cell.end = 'bottom'
284
285 return cell
286 }
287
288
289 writeCell(cell) {
290 if (cell == null) {
291 this.writeByte(CELL_TYPE_NULL)
292 return
293 }
294
295 // Write cell type, then cell data, then generic data.
296 // Note that cell type starts at 1, since 0 is the "null type".
297 if (cell.type == 'line') {
298 this.writeByte(CELL_TYPE_LINE)
299 this.writeByte(cell.line)
300 this.writeByte(cell.dot)
301 this.writeByte(cell.gap)
302 } else if (cell.type == 'square') {
303 this.writeByte(CELL_TYPE_SQUARE)
304 this.writeColor(cell.color)
305 } else if (cell.type == 'star') {
306 this.writeByte(CELL_TYPE_STAR)
307 this.writeColor(cell.color)
308 } else if (cell.type == 'nega') {
309 this.writeByte(CELL_TYPE_NEGA)
310 this.writeColor(cell.color)
311 } else if (cell.type == 'triangle') {
312 this.writeByte(CELL_TYPE_TRIANGLE)
313 this.writeColor(cell.color)
314 this.writeByte(cell.count)
315 } else if (cell.type == 'poly') {
316 this.writeByte(CELL_TYPE_POLY)
317 this.writeColor(cell.color)
318 this.writeLong(cell.polyshape)
319 } else if (cell.type == 'ylop') {
320 this.writeByte(CELL_TYPE_YLOP)
321 this.writeColor(cell.color)
322 this.writeLong(cell.polyshape)
323 }
324
325 var startEnd = 0
326 if (cell.start === true) startEnd |= CELL_START
327 if (cell.end == 'left') startEnd |= CELL_END_LEFT
328 if (cell.end == 'right') startEnd |= CELL_END_RIGHT
329 if (cell.end == 'top') startEnd |= CELL_END_TOP
330 if (cell.end == 'bottom') startEnd |= CELL_END_BOTTOM
331 this.writeByte(startEnd)
332 }
333}
334
335var CELL_TYPE_NULL = 0
336var CELL_TYPE_LINE = 1
337var CELL_TYPE_SQUARE = 2
338var CELL_TYPE_STAR = 3
339var CELL_TYPE_NEGA = 4
340var CELL_TYPE_TRIANGLE = 5
341var CELL_TYPE_POLY = 6
342var CELL_TYPE_YLOP = 7
343var CELL_TYPE_NONCE = 8
344
345var CELL_START = 1
346var CELL_END_LEFT = 2
347var CELL_END_RIGHT = 4
348var CELL_END_TOP = 8
349var CELL_END_BOTTOM = 16
350
351var GENERIC_FLAG_AUTOSOLVED = 1
352var GENERIC_FLAG_SYMMETRICAL = 2
353var GENERIC_FLAG_SYMMETRY_X = 4
354var GENERIC_FLAG_SYMMETRY_Y = 8
355var GENERIC_FLAG_PILLAR = 16
356
357var SETTINGS_FLAG_NCN = 1
358var SETTINGS_FLAG_SZP = 2
359var SETTINGS_FLAG_PP = 4
360var SETTINGS_FLAG_FFE = 8
361var SETTINGS_FLAG_FS = 16
362var SETTINGS_FLAG_CM = 32
363var SETTINGS_FLAG_IS = 64
364
365})