diff options
Diffstat (limited to 'app/assets/javascripts/serializer.js')
| -rw-r--r-- | app/assets/javascripts/serializer.js | 365 | 
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 @@ | |||
| 1 | namespace(function() { | ||
| 2 | |||
| 3 | window.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 | |||
| 54 | window.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 | |||
| 116 | class 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 | |||
| 335 | var CELL_TYPE_NULL = 0 | ||
| 336 | var CELL_TYPE_LINE = 1 | ||
| 337 | var CELL_TYPE_SQUARE = 2 | ||
| 338 | var CELL_TYPE_STAR = 3 | ||
| 339 | var CELL_TYPE_NEGA = 4 | ||
| 340 | var CELL_TYPE_TRIANGLE = 5 | ||
| 341 | var CELL_TYPE_POLY = 6 | ||
| 342 | var CELL_TYPE_YLOP = 7 | ||
| 343 | var CELL_TYPE_NONCE = 8 | ||
| 344 | |||
| 345 | var CELL_START = 1 | ||
| 346 | var CELL_END_LEFT = 2 | ||
| 347 | var CELL_END_RIGHT = 4 | ||
| 348 | var CELL_END_TOP = 8 | ||
| 349 | var CELL_END_BOTTOM = 16 | ||
| 350 | |||
| 351 | var GENERIC_FLAG_AUTOSOLVED = 1 | ||
| 352 | var GENERIC_FLAG_SYMMETRICAL = 2 | ||
| 353 | var GENERIC_FLAG_SYMMETRY_X = 4 | ||
| 354 | var GENERIC_FLAG_SYMMETRY_Y = 8 | ||
| 355 | var GENERIC_FLAG_PILLAR = 16 | ||
| 356 | |||
| 357 | var SETTINGS_FLAG_NCN = 1 | ||
| 358 | var SETTINGS_FLAG_SZP = 2 | ||
| 359 | var SETTINGS_FLAG_PP = 4 | ||
| 360 | var SETTINGS_FLAG_FFE = 8 | ||
| 361 | var SETTINGS_FLAG_FS = 16 | ||
| 362 | var SETTINGS_FLAG_CM = 32 | ||
| 363 | var SETTINGS_FLAG_IS = 64 | ||
| 364 | |||
| 365 | }) | ||
