diff options
Diffstat (limited to 'app/assets/javascripts/display2.js')
| -rw-r--r-- | app/assets/javascripts/display2.js | 316 |
1 files changed, 316 insertions, 0 deletions
| diff --git a/app/assets/javascripts/display2.js b/app/assets/javascripts/display2.js new file mode 100644 index 0000000..ddf3968 --- /dev/null +++ b/app/assets/javascripts/display2.js | |||
| @@ -0,0 +1,316 @@ | |||
| 1 | var SYM_TYPE_NONE = 0 | ||
| 2 | var SYM_TYPE_HORIZONTAL = 1 | ||
| 3 | var SYM_TYPE_VERTICAL = 2 | ||
| 4 | var SYM_TYPE_ROTATIONAL = 3 | ||
| 5 | var SYM_TYPE_ROTATE_LEFT = 4 | ||
| 6 | var SYM_TYPE_ROTATE_RIGHT = 5 | ||
| 7 | var SYM_TYPE_FLIP_XY = 6 | ||
| 8 | var SYM_TYPE_FLIP_NEG_XY = 7 | ||
| 9 | var SYM_TYPE_PARALLEL_H = 8 | ||
| 10 | var SYM_TYPE_PARALLEL_V = 9 | ||
| 11 | var SYM_TYPE_PARALLEL_H_FLIP = 10 | ||
| 12 | var SYM_TYPE_PARALLEL_V_FLIP = 11 | ||
| 13 | var SYM_TYPE_PILLAR_PARALLEL = 12 | ||
| 14 | var SYM_TYPE_PILLAR_HORIZONTAL = 13 | ||
| 15 | var SYM_TYPE_PILLAR_VERTICAL = 14 | ||
| 16 | var SYM_TYPE_PILLAR_ROTATIONAL = 15 | ||
| 17 | |||
| 18 | namespace(function() { | ||
| 19 | |||
| 20 | window.draw = function(puzzle, target='puzzle') { | ||
| 21 | if (puzzle == null) return | ||
| 22 | var svg = document.getElementById(target) | ||
| 23 | console.info('Drawing', puzzle, 'into', svg) | ||
| 24 | while (svg.firstChild) svg.removeChild(svg.firstChild) | ||
| 25 | |||
| 26 | // Prevent context menu popups within the puzzle | ||
| 27 | svg.oncontextmenu = function(event) { | ||
| 28 | event.preventDefault() | ||
| 29 | } | ||
| 30 | |||
| 31 | if (puzzle.pillar === true) { | ||
| 32 | // 41*width + 30*2 (padding) + 10*2 (border) | ||
| 33 | var pixelWidth = 41 * puzzle.width + 80 | ||
| 34 | } else { | ||
| 35 | // 41*(width-1) + 24 (extra edge) + 30*2 (padding) + 10*2 (border) | ||
| 36 | var pixelWidth = 41 * puzzle.width + 63 | ||
| 37 | } | ||
| 38 | var pixelHeight = 41 * puzzle.height + 63 | ||
| 39 | svg.setAttribute('viewbox', '0 0 ' + pixelWidth + ' ' + pixelHeight) | ||
| 40 | svg.setAttribute('width', pixelWidth) | ||
| 41 | svg.setAttribute('height', pixelHeight) | ||
| 42 | |||
| 43 | var rect = createElement('rect') | ||
| 44 | svg.appendChild(rect) | ||
| 45 | rect.setAttribute('stroke-width', 10) | ||
| 46 | rect.setAttribute('stroke', window.BORDER) | ||
| 47 | rect.setAttribute('fill', window.OUTER_BACKGROUND) | ||
| 48 | // Accounting for the border thickness | ||
| 49 | rect.setAttribute('x', 5) | ||
| 50 | rect.setAttribute('y', 5) | ||
| 51 | rect.setAttribute('width', pixelWidth - 10) // Removing border | ||
| 52 | rect.setAttribute('height', pixelHeight - 10) // Removing border | ||
| 53 | |||
| 54 | drawCenters(puzzle, svg) | ||
| 55 | drawGrid(puzzle, svg, target) | ||
| 56 | drawStartAndEnd(puzzle, svg) | ||
| 57 | // Draw cell symbols after so they overlap the lines, if necessary | ||
| 58 | drawSymbols(puzzle, svg, target) | ||
| 59 | |||
| 60 | // For pillar puzzles, add faders for the left and right sides | ||
| 61 | if (puzzle.pillar === true) { | ||
| 62 | var defs = window.createElement('defs') | ||
| 63 | defs.id = 'cursorPos' | ||
| 64 | defs.innerHTML = '' + | ||
| 65 | '<linearGradient id="fadeInLeft">\n' + | ||
| 66 | ' <stop offset="0%" stop-opacity="1.0" stop-color="' + window.OUTER_BACKGROUND + '"></stop>\n' + | ||
| 67 | ' <stop offset="25%" stop-opacity="1.0" stop-color="' + window.OUTER_BACKGROUND + '"></stop>\n' + | ||
| 68 | ' <stop offset="100%" stop-opacity="0.0" stop-color="' + window.OUTER_BACKGROUND + '"></stop>\n' + | ||
| 69 | '</linearGradient>\n' + | ||
| 70 | '<linearGradient id="fadeOutRight">\n' + | ||
| 71 | ' <stop offset="0%" stop-opacity="0.0" stop-color="' + window.OUTER_BACKGROUND + '"></stop>\n' + | ||
| 72 | ' <stop offset="100%" stop-opacity="1.0" stop-color="' + window.OUTER_BACKGROUND + '"></stop>\n' + | ||
| 73 | '</linearGradient>\n' | ||
| 74 | svg.appendChild(defs) | ||
| 75 | |||
| 76 | var leftBox = window.createElement('rect') | ||
| 77 | leftBox.setAttribute('x', 16) | ||
| 78 | leftBox.setAttribute('y', 10) | ||
| 79 | leftBox.setAttribute('width', 48) | ||
| 80 | leftBox.setAttribute('height', 41 * puzzle.height + 43) | ||
| 81 | leftBox.setAttribute('fill', 'url(#fadeInLeft)') | ||
| 82 | leftBox.setAttribute('style', 'pointer-events: none') | ||
| 83 | svg.appendChild(leftBox) | ||
| 84 | |||
| 85 | var rightBox = window.createElement('rect') | ||
| 86 | rightBox.setAttribute('x', 41 * puzzle.width + 22) | ||
| 87 | rightBox.setAttribute('y', 10) | ||
| 88 | rightBox.setAttribute('width', 30) | ||
| 89 | rightBox.setAttribute('height', 41 * puzzle.height + 43) | ||
| 90 | rightBox.setAttribute('fill', 'url(#fadeOutRight)') | ||
| 91 | rightBox.setAttribute('style', 'pointer-events: none') | ||
| 92 | svg.appendChild(rightBox) | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | function drawCenters(puzzle, svg) { | ||
| 97 | // @Hack that I am not fixing. This switches the puzzle's grid to a floodfilled grid | ||
| 98 | // where null represents cells which are part of the outside | ||
| 99 | var savedGrid = puzzle.switchToMaskedGrid() | ||
| 100 | if (puzzle.pillar === true) { | ||
| 101 | for (var y=1; y<puzzle.height; y += 2) { | ||
| 102 | if (puzzle.getCell(-1, y) == null) continue // Cell borders the outside | ||
| 103 | |||
| 104 | var rect = createElement('rect') | ||
| 105 | rect.setAttribute('x', 28) | ||
| 106 | rect.setAttribute('y', 41 * y + 11) | ||
| 107 | rect.setAttribute('width', 24) | ||
| 108 | rect.setAttribute('height', 82) | ||
| 109 | rect.setAttribute('fill', window.BACKGROUND) | ||
| 110 | svg.appendChild(rect) | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | for (var x=1; x<puzzle.width; x += 2) { | ||
| 115 | for (var y=1; y<puzzle.height; y += 2) { | ||
| 116 | if (puzzle.grid[x][y] == null) continue // Cell borders the outside | ||
| 117 | |||
| 118 | var rect = createElement('rect') | ||
| 119 | rect.setAttribute('x', 41 * x + 11) | ||
| 120 | rect.setAttribute('y', 41 * y + 11) | ||
| 121 | rect.setAttribute('width', 82) | ||
| 122 | rect.setAttribute('height', 82) | ||
| 123 | rect.setAttribute('fill', window.BACKGROUND) | ||
| 124 | rect.setAttribute('shape-rendering', 'crispedges') // Otherwise they don't meet behind gaps | ||
| 125 | svg.appendChild(rect) | ||
| 126 | } | ||
| 127 | } | ||
| 128 | puzzle.grid = savedGrid | ||
| 129 | } | ||
| 130 | |||
| 131 | function drawGrid(puzzle, svg, target) { | ||
| 132 | for (var x=0; x<puzzle.width; x++) { | ||
| 133 | for (var y=0; y<puzzle.height; y++) { | ||
| 134 | var cell = puzzle.grid[x][y] | ||
| 135 | if (cell != null && cell.gap === window.GAP_FULL) continue | ||
| 136 | if (cell != null && cell.gap === window.GAP_BREAK) { | ||
| 137 | var params = { | ||
| 138 | 'width':58, | ||
| 139 | 'height':58, | ||
| 140 | 'x': x*41 + 23, | ||
| 141 | 'y': y*41 + 23, | ||
| 142 | 'class': target + '_' + x + '_' + y, | ||
| 143 | 'type': 'gap', | ||
| 144 | } | ||
| 145 | if (x%2 === 0 && y%2 === 1) params.rot = 1 | ||
| 146 | drawSymbolWithSvg(svg, params) | ||
| 147 | continue | ||
| 148 | } | ||
| 149 | |||
| 150 | var line = createElement('line') | ||
| 151 | line.setAttribute('stroke-width', 24) | ||
| 152 | line.setAttribute('stroke-linecap', 'round') | ||
| 153 | line.setAttribute('stroke', window.FOREGROUND) | ||
| 154 | if (x%2 === 1 && y%2 === 0) { // Horizontal | ||
| 155 | if (cell.gap === window.GAP_BREAK) continue | ||
| 156 | line.setAttribute('x1', (x-1)*41 + 52) | ||
| 157 | // Adjust the length if it's a pillar -- the grid is not as wide! | ||
| 158 | if (puzzle.pillar === true && x === puzzle.width - 1) { | ||
| 159 | line.setAttribute('x2', (x+1)*41 + 40) | ||
| 160 | } else { | ||
| 161 | line.setAttribute('x2', (x+1)*41 + 52) | ||
| 162 | } | ||
| 163 | line.setAttribute('y1', y*41 + 52) | ||
| 164 | line.setAttribute('y2', y*41 + 52) | ||
| 165 | svg.appendChild(line) | ||
| 166 | } else if (x%2 === 0 && y%2 === 1) { // Vertical | ||
| 167 | if (cell.gap === window.GAP_BREAK) continue | ||
| 168 | line.setAttribute('x1', x*41 + 52) | ||
| 169 | line.setAttribute('x2', x*41 + 52) | ||
| 170 | line.setAttribute('y1', (y-1)*41 + 52) | ||
| 171 | line.setAttribute('y2', (y+1)*41 + 52) | ||
| 172 | svg.appendChild(line) | ||
| 173 | } else if (x%2 === 0 && y%2 === 0) { // Intersection | ||
| 174 | var surroundingLines = 0 | ||
| 175 | if (cell.end != null) surroundingLines++ | ||
| 176 | var leftCell = puzzle.getCell(x - 1, y) | ||
| 177 | if (leftCell != null && leftCell.gap !== window.GAP_FULL) surroundingLines++ | ||
| 178 | var rightCell = puzzle.getCell(x + 1, y) | ||
| 179 | if (rightCell != null && rightCell.gap !== window.GAP_FULL) surroundingLines++ | ||
| 180 | var topCell = puzzle.getCell(x, y - 1) | ||
| 181 | if (topCell != null && topCell.gap !== window.GAP_FULL) surroundingLines++ | ||
| 182 | var bottomCell = puzzle.getCell(x, y + 1) | ||
| 183 | if (bottomCell != null && bottomCell.gap !== window.GAP_FULL) surroundingLines++ | ||
| 184 | |||
| 185 | if (surroundingLines === 1) { | ||
| 186 | // Add square caps for dead ends which are non-endpoints | ||
| 187 | var rect = createElement('rect') | ||
| 188 | rect.setAttribute('x', x*41 + 40) | ||
| 189 | rect.setAttribute('y', y*41 + 40) | ||
| 190 | rect.setAttribute('width', 24) | ||
| 191 | rect.setAttribute('height', 24) | ||
| 192 | rect.setAttribute('fill', window.FOREGROUND) | ||
| 193 | svg.appendChild(rect) | ||
| 194 | } else if (surroundingLines > 1) { | ||
| 195 | // Add rounding for other intersections (handling gap-only corners) | ||
| 196 | var circ = createElement('circle') | ||
| 197 | circ.setAttribute('cx', x*41 + 52) | ||
| 198 | circ.setAttribute('cy', y*41 + 52) | ||
| 199 | circ.setAttribute('r', 12) | ||
| 200 | circ.setAttribute('fill', window.FOREGROUND) | ||
| 201 | svg.appendChild(circ) | ||
| 202 | } | ||
| 203 | } | ||
| 204 | } | ||
| 205 | } | ||
| 206 | // Determine if left-side needs a 'wrap indicator' | ||
| 207 | if (puzzle.pillar === true) { | ||
| 208 | var x = 0; | ||
| 209 | for (var y=0; y<puzzle.height; y+=2) { | ||
| 210 | var cell = puzzle.getCell(x-1, y) | ||
| 211 | if (cell == null || cell.gap === window.GAP_FULL) continue | ||
| 212 | var line = createElement('line') | ||
| 213 | line.setAttribute('stroke-width', 24) | ||
| 214 | line.setAttribute('stroke-linecap', 'round') | ||
| 215 | line.setAttribute('stroke', window.FOREGROUND) | ||
| 216 | line.setAttribute('x1', x*41 + 40) | ||
| 217 | line.setAttribute('x2', x*41 + 52) | ||
| 218 | line.setAttribute('y1', y*41 + 52) | ||
| 219 | line.setAttribute('y2', y*41 + 52) | ||
| 220 | svg.appendChild(line) | ||
| 221 | } | ||
| 222 | } | ||
| 223 | } | ||
| 224 | |||
| 225 | function drawSymbols(puzzle, svg, target) { | ||
| 226 | for (var x=0; x<puzzle.width; x++) { | ||
| 227 | for (var y=0; y<puzzle.height; y++) { | ||
| 228 | var cell = puzzle.grid[x][y] | ||
| 229 | if (cell == null) continue | ||
| 230 | var params = { | ||
| 231 | 'width':58, | ||
| 232 | 'height':58, | ||
| 233 | 'x': x*41 + 23, | ||
| 234 | 'y': y*41 + 23, | ||
| 235 | 'class': target + '_' + x + '_' + y, | ||
| 236 | } | ||
| 237 | if (cell.dot > window.DOT_NONE) { | ||
| 238 | params.type = 'dot' | ||
| 239 | if (cell.dot === window.DOT_BLACK) params.color = 'black' | ||
| 240 | else if (cell.dot === window.DOT_BLUE) params.color = window.LINE_PRIMARY | ||
| 241 | else if (cell.dot === window.DOT_YELLOW) params.color = window.LINE_SECONDARY | ||
| 242 | else if (cell.dot === window.DOT_INVISIBLE) { | ||
| 243 | params.color = window.FOREGROUND | ||
| 244 | // This makes the invisible dots visible, but only while we're in the editor. | ||
| 245 | if (document.getElementById('metaButtons') != null) { | ||
| 246 | params.stroke = 'black' | ||
| 247 | params.strokeWidth = '2px' | ||
| 248 | } | ||
| 249 | } | ||
| 250 | drawSymbolWithSvg(svg, params) | ||
| 251 | } else if (cell.gap === window.GAP_BREAK) { | ||
| 252 | // Gaps were handled above, while drawing the grid. | ||
| 253 | } else if (x%2 === 1 && y%2 === 1) { | ||
| 254 | // Generic draw for all other elements | ||
| 255 | Object.assign(params, cell) | ||
| 256 | window.drawSymbolWithSvg(svg, params, puzzle.settings.CUSTOM_MECHANICS) | ||
| 257 | } | ||
| 258 | } | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | function drawStartAndEnd(puzzle, svg) { | ||
| 263 | for (var x=0; x<puzzle.width; x++) { | ||
| 264 | for (var y=0; y<puzzle.height; y++) { | ||
| 265 | var cell = puzzle.grid[x][y] | ||
| 266 | if (cell == null) continue | ||
| 267 | if (cell.end != null) { | ||
| 268 | window.drawSymbolWithSvg(svg, { | ||
| 269 | 'type': 'end', | ||
| 270 | 'width': 58, | ||
| 271 | 'height': 58, | ||
| 272 | 'dir': cell.end, | ||
| 273 | 'x': x*41 + 23, | ||
| 274 | 'y': y*41 + 23, | ||
| 275 | }) | ||
| 276 | } | ||
| 277 | |||
| 278 | if (cell.start === true) { | ||
| 279 | var symStart = null | ||
| 280 | if (puzzle.symType != SYM_TYPE_NONE) { | ||
| 281 | var sym = puzzle.getSymmetricalPos(x, y) | ||
| 282 | window.drawSymbolWithSvg(svg, { | ||
| 283 | 'type': 'start', | ||
| 284 | 'width': 58, | ||
| 285 | 'height': 58, | ||
| 286 | 'x': sym.x*41 + 23, | ||
| 287 | 'y': sym.y*41 + 23, | ||
| 288 | }) | ||
| 289 | symStart = svg.lastChild | ||
| 290 | symStart.style.display = 'none' | ||
| 291 | symStart.id = 'symStart_' + svg.id + '_' + x + '_' + y | ||
| 292 | } | ||
| 293 | |||
| 294 | window.drawSymbolWithSvg(svg, { | ||
| 295 | 'type': 'start', | ||
| 296 | 'width': 58, | ||
| 297 | 'height': 58, | ||
| 298 | 'x': x*41 + 23, | ||
| 299 | 'y': y*41 + 23, | ||
| 300 | }) | ||
| 301 | var start = svg.lastChild | ||
| 302 | start.id = 'start_' + svg.id + '_' + x + '_' + y | ||
| 303 | |||
| 304 | // ;(function(a){}(a)) | ||
| 305 | // This syntax is used to forcibly copy all of the arguments | ||
| 306 | ;(function(puzzle, x, y, start, symStart) { | ||
| 307 | start.onpointerdown = function(event) { | ||
| 308 | window.trace(event, puzzle, {'x':x, 'y':y}, start, symStart) | ||
| 309 | } | ||
| 310 | }(puzzle, x, y, start, symStart)) | ||
| 311 | } | ||
| 312 | } | ||
| 313 | } | ||
| 314 | } | ||
| 315 | |||
| 316 | }) | ||
