about summary refs log tree commit diff stats
path: root/app/assets/javascripts/custom_mechanics.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/custom_mechanics.js')
-rw-r--r--app/assets/javascripts/custom_mechanics.js201
1 files changed, 201 insertions, 0 deletions
diff --git a/app/assets/javascripts/custom_mechanics.js b/app/assets/javascripts/custom_mechanics.js new file mode 100644 index 0000000..d4733db --- /dev/null +++ b/app/assets/javascripts/custom_mechanics.js
@@ -0,0 +1,201 @@
1namespace(function() {
2
3function isCellBridgePathFriendly(puzzle, color, pos) {
4 if (pos.x%2 === 0 && pos.y%2 === 0) return false
5 var cell = puzzle.getCell(pos.x, pos.y)
6 return cell == null || cell.color == null || cell.color === color
7}
8
9function makeMinimalTree(graph, root, required) {
10 var seen = Array(graph.length).fill(false)
11 var result = Array(graph.length).fill(false)
12 result[root] = true
13 function dfs(node) {
14 seen[node] = true
15 result[node] = required[node]
16 for (var child of graph[node]) {
17 if (!seen[child]) {
18 dfs(child)
19 result[node] = result[node] || result[child]
20 }
21 }
22 }
23 dfs(root)
24 return result
25}
26
27function isTreeUnique(graph, isInTree) {
28 var seen = isInTree.slice()
29 function dfs(node) {
30 seen[node] = true
31 var reachableTreeNode = null
32 for (var child of graph[node]) {
33 var candidate = null
34 if (isInTree[child]) {
35 candidate = child
36 } else if (!seen[child]) {
37 candidate = dfs(child)
38 }
39 if (candidate != null && candidate !== reachableTreeNode) {
40 if (reachableTreeNode == null) {
41 reachableTreeNode = candidate
42 } else {
43 return -1
44 }
45 }
46 }
47 return reachableTreeNode
48 }
49 for (var i = 0; i < graph.length; i++) {
50 if (!seen[i]) {
51 if (dfs(i) === -1) return false
52 }
53 }
54 return true
55}
56
57function puzzleCellsAdjacent(first, second, pillar) {
58 if (pillar && first.y == second.y && Math.abs(second.x - first.x) === puzzle.width - 1)
59 return true
60 return Math.abs(second.x - first.x) + Math.abs(second.y - first.y) === 1
61}
62
63function bridgeTest(region, puzzle, color, bridges) {
64 var nodes = region.cells.filter(pos => isCellBridgePathFriendly(puzzle, color, pos))
65 var graph = Array.from(Array(nodes.length), () => [])
66 for (var ir = 1; ir < nodes.length; ir++) {
67 var right = nodes[ir]
68 for (var il = 0; il < ir; il++) {
69 var left = nodes[il]
70 if (puzzleCellsAdjacent(left, right, puzzle.pillar)) {
71 graph[il].push(ir)
72 graph[ir].push(il)
73 }
74 }
75 }
76 var isBridge = nodes.map(node => bridges.some(bridge => node.x === bridge.x && node.y === bridge.y))
77 var isInTree = makeMinimalTree(graph, isBridge.indexOf(true), isBridge)
78 for (var i = 0; i < nodes.length; i++) {
79 if (isBridge[i] && !isInTree[i]) return false
80 }
81 return isTreeUnique(graph, isInTree)
82}
83
84window.validateBridges = function(puzzle, region, regionData) {
85 var bridges = {}
86 for (var pos of region) {
87 var cell = puzzle.getCell(pos.x, pos.y)
88 if (cell == null) continue
89
90 // Count color-based elements
91 if (cell.color != null) {
92 if (cell.type === 'bridge') {
93 if (bridges[cell.color] == null) {
94 bridges[cell.color] = []
95 }
96 bridges[cell.color].push(pos)
97 }
98 }
99 }
100
101 for (var color in bridges) {
102 var total = 0
103 var discardable = 0
104 for (var x=1; x < puzzle.width; x+=2) {
105 for (var y=1; y < puzzle.height; y+=2) {
106 var cell = puzzle.getCell(x, y)
107 if (cell != null) {
108 if (cell.type === 'bridge' && cell.color === color) total++
109 if (cell.type === 'nega') discardable++
110 }
111 }
112 }
113
114 if (bridges[color].length != total) {
115 if (bridges[color].length >= total - discardable) {
116 // TODO: Negations in other regions can validate the solution
117 for (var bridge of bridges[color]) {
118 regionData.addInvalid(bridge)
119 }
120 } else {
121 for (var bridge of bridges[color]) {
122 regionData.addVeryInvalid(bridge)
123 }
124 }
125 } else if (!window.bridgeTest(region, puzzle, color, bridges[color])) {
126 for (var bridge of bridges[color]) {
127 regionData.addInvalid(bridge)
128 }
129 }
130 }
131}
132
133var DIRECTIONS = [
134 {'x': 0, 'y':-1},
135 {'x': 1, 'y':-1},
136 {'x': 1, 'y': 0},
137 {'x': 1, 'y': 1},
138 {'x': 0, 'y': 1},
139 {'x':-1, 'y': 1},
140 {'x':-1, 'y': 0},
141 {'x':-1, 'y':-1},
142]
143
144window.validateArrows = function(puzzle, region, regionData) {
145 for (var pos of region) {
146 var cell = puzzle.getCell(pos.x, pos.y)
147 if (cell == null) continue
148 if (cell.type != 'arrow') continue
149 dir = DIRECTIONS[cell.rot]
150
151 var count = 0
152 var x = pos.x + dir.x
153 var y = pos.y + dir.y
154 for (var i=0; i<100; i++) { // 100 is arbitrary, it's just here to avoid infinite loops.
155 var line = puzzle.getLine(x, y)
156 console.spam('Testing', x, y, 'for arrow at', pos.x, pos.y, 'found', line)
157 if (line == null && (x%2 !== 1 || y%2 !== 1)) break
158 if (line > window.LINE_NONE) count++
159 if (count > cell.count) break
160 x += dir.x * 2
161 y += dir.y * 2
162 if (puzzle.matchesSymmetricalPos(x, y, pos.x + dir.x, pos.y + dir.y)) break // Pillar exit condition (in case of looping)
163 }
164 if (count !== cell.count) {
165 console.log('Arrow at', pos.x, pos.y, 'crosses', count, 'lines, but should cross', cell.count)
166 regionData.addInvalid(pos)
167 }
168 }
169}
170
171window.validateSizers = function(puzzle, region, regionData) {
172 var sizers = []
173 var regionSize = 0
174 for (var pos of region) {
175 if (pos.x%2 === 1 && pos.y%2 === 1) regionSize++ // Only count cells for the region
176 var cell = puzzle.getCell(pos.x, pos.y)
177 if (cell == null) continue
178 if (cell.type == 'sizer') sizers.push(pos)
179 }
180 console.debug('Found', sizers.length, 'sizers')
181 if (sizers.length == 0) return // No sizers -- no impact on sizer validity
182
183 var sizerCount = regionSize / sizers.length
184 if (sizerCount % 1 != 0) {
185 console.log('Region size', regionSize, 'is not a multiple of # sizers', sizers.length)
186 for (var sizer of sizers) {
187 regionData.addInvalid(sizer)
188 }
189 return
190 }
191
192 if (puzzle.sizerCount == null) puzzle.sizerCount = sizerCount // No other sizes have been defined
193 if (puzzle.sizerCount != sizerCount) {
194 console.log('sizerCount', sizerCount, 'does not match puzzle sizerCount', puzzle.sizerCount)
195 for (var sizer of sizers) {
196 regionData.addInvalid(sizer)
197 }
198 }
199}
200
201})