github.com/joshprzybyszewski/masyu@v0.0.0-20230508015604-f31a025f6e7e/solve/rules.go (about) 1 package solve 2 3 import ( 4 "sort" 5 6 "github.com/joshprzybyszewski/masyu/model" 7 ) 8 9 var ( 10 emptyApply = func(*state) {} 11 ) 12 13 type path struct { 14 model.Coord 15 IsHorizontal bool 16 } 17 18 type affectsApply struct { 19 affects int 20 21 fn applyFn 22 } 23 24 type rules struct { 25 // if "this" row/col changes, then run these other checks 26 // [row][col] 27 horizontals [maxPinsPerLine][maxPinsPerLine]affectsApply 28 verticals [maxPinsPerLine][maxPinsPerLine]affectsApply 29 30 // unknowns describes the paths that aren't initialized known. 31 // They should exist in a sorted manner, where the first one has the most 32 // rules associated with it, and so on. This can be used to find "the most 33 // interesting space to investigate next." 34 unknowns []path 35 } 36 37 func newRules( 38 size model.Size, 39 ) *rules { 40 r := rules{ 41 unknowns: make([]path, 0, 2*int(size)*int(size-1)), 42 } 43 44 for i := range r.horizontals { 45 for j := range r.horizontals[i] { 46 r.horizontals[i][j].fn = emptyApply 47 r.verticals[i][j].fn = emptyApply 48 } 49 } 50 51 return &r 52 } 53 54 func (r *rules) populateRules( 55 s *state, 56 ) { 57 58 for i := range s.nodes { 59 if s.nodes[i].IsBlack { 60 r.addBlackNode(s.nodes[i].Row, s.nodes[i].Col) 61 } else { 62 r.addWhiteNode(s.nodes[i].Row, s.nodes[i].Col) 63 } 64 } 65 66 var pins [maxPinsPerLine][maxPinsPerLine]rule 67 for row := model.Dimension(1); row <= model.Dimension(s.size); row++ { 68 for col := model.Dimension(1); col <= model.Dimension(s.size); col++ { 69 pins[row][col] = newDefaultRule(row, col) 70 } 71 } 72 73 for row := model.Dimension(1); row <= model.Dimension(s.size); row++ { 74 for col := model.Dimension(1); col < model.Dimension(s.size); col++ { 75 r.addHorizontalRule(row, col, &pins[row][col]) 76 r.addHorizontalRule(row, col, &pins[row][col+1]) 77 } 78 } 79 80 for col := model.Dimension(1); col <= model.Dimension(s.size); col++ { 81 for row := model.Dimension(1); row < model.Dimension(s.size); row++ { 82 r.addVerticalRule(row, col, &pins[row][col]) 83 r.addVerticalRule(row, col, &pins[row+1][col]) 84 } 85 } 86 } 87 88 func (r *rules) populateUnknowns( 89 s *state, 90 ) { 91 92 for row := model.Dimension(1); row <= model.Dimension(s.size); row++ { 93 for col := model.Dimension(1); col <= model.Dimension(s.size); col++ { 94 95 if !s.hasHorDefined(row, col) { 96 r.unknowns = append(r.unknowns, path{ 97 Coord: model.Coord{ 98 Row: row, 99 Col: col, 100 }, 101 IsHorizontal: true, 102 }) 103 } 104 105 if !s.hasVerDefined(row, col) { 106 r.unknowns = append(r.unknowns, path{ 107 Coord: model.Coord{ 108 Row: row, 109 Col: col, 110 }, 111 IsHorizontal: false, 112 }) 113 } 114 } 115 } 116 117 var ni, nj int 118 var dri, dci, drj, dcj, tmp int 119 is := int(s.size) 120 121 sort.Slice(r.unknowns, func(i, j int) bool { 122 if r.unknowns[i].IsHorizontal { 123 ni = r.horizontals[r.unknowns[i].Row][r.unknowns[i].Col].affects 124 } else { 125 ni = r.verticals[r.unknowns[i].Row][r.unknowns[i].Col].affects 126 } 127 128 if r.unknowns[j].IsHorizontal { 129 nj = r.horizontals[r.unknowns[j].Row][r.unknowns[j].Col].affects 130 } else { 131 nj = r.verticals[r.unknowns[j].Row][r.unknowns[j].Col].affects 132 } 133 134 if ni != nj { 135 return ni < nj 136 } 137 138 // There are the same number of rules for this segment. 139 // Prioritize a segment that is closer to the outer wall 140 dri = int(r.unknowns[i].Row) - 1 141 if tmp = is - int(r.unknowns[i].Row); tmp < dri { 142 dri = tmp 143 } 144 dci = int(r.unknowns[i].Col) - 1 145 if tmp = is - int(r.unknowns[i].Col); tmp < dci { 146 dci = tmp 147 } 148 149 drj = int(r.unknowns[j].Row) - 1 150 if tmp = is - int(r.unknowns[j].Row); tmp < drj { 151 drj = tmp 152 } 153 dcj = int(r.unknowns[j].Col) - 1 154 if tmp = is - int(r.unknowns[j].Col); tmp < dcj { 155 dcj = tmp 156 } 157 ni = dri + dci 158 nj = drj + dcj 159 if ni != nj { 160 return ni < nj 161 } 162 163 // They are equally close to the outer wall. 164 // Prioritize the one in the top left. 165 if r.unknowns[i].Row != r.unknowns[j].Row { 166 return r.unknowns[i].Row < r.unknowns[j].Row 167 } 168 if r.unknowns[i].Col != r.unknowns[j].Col { 169 return r.unknowns[i].Col < r.unknowns[j].Col 170 } 171 172 // Check horizontal first. 173 return r.unknowns[i].IsHorizontal 174 }) 175 } 176 177 func (r *rules) addHorizontalRule( 178 row, col model.Dimension, 179 rule *rule, 180 ) { 181 182 r.horizontals[row][col].affects += rule.affects 183 prev := r.horizontals[row][col].fn 184 r.horizontals[row][col].fn = func(s *state) { 185 rule.check(s) 186 if !s.hasInvalid { 187 prev(s) 188 } 189 } 190 } 191 192 func (r *rules) addVerticalRule( 193 row, col model.Dimension, 194 rule *rule, 195 ) { 196 r.verticals[row][col].affects += rule.affects 197 prev := r.verticals[row][col].fn 198 r.verticals[row][col].fn = func(s *state) { 199 rule.check(s) 200 if !s.hasInvalid { 201 prev(s) 202 } 203 } 204 } 205 206 func (r *rules) addBlackNode( 207 row, col model.Dimension, 208 ) { 209 210 // ensure the black node is valid 211 bv := newBlackValidator(row, col) 212 if col > 1 { 213 r.addHorizontalRule(row, col-2, &bv) 214 } 215 r.addHorizontalRule(row, col-1, &bv) 216 r.addHorizontalRule(row, col, &bv) 217 r.addHorizontalRule(row, col+1, &bv) 218 if row > 1 { 219 r.addVerticalRule(row-2, col, &bv) 220 } 221 r.addVerticalRule(row-1, col, &bv) 222 r.addVerticalRule(row, col, &bv) 223 r.addVerticalRule(row+1, col, &bv) 224 225 // Look at extended "avoids" 226 if col > 1 { 227 left2 := newBlackL2Rule(row, col) 228 r.addHorizontalRule(row, col-2, &left2) 229 right2 := newBlackR2Rule(row, col) 230 r.addHorizontalRule(row, col+1, &right2) 231 } 232 233 if row > 1 { 234 up2 := newBlackU2Rule(row, col) 235 r.addVerticalRule(row-2, col, &up2) 236 down2 := newBlackD2Rule(row, col) 237 r.addVerticalRule(row+1, col, &down2) 238 } 239 240 // Look at branches off the adjacencies. 241 leftBranch := newBlackLBranchRule(row, col) 242 r.addVerticalRule(row-1, col-1, &leftBranch) 243 r.addVerticalRule(row, col-1, &leftBranch) 244 245 rightBranch := newBlackRBranchRule(row, col) 246 r.addVerticalRule(row-1, col+1, &rightBranch) 247 r.addVerticalRule(row, col+1, &rightBranch) 248 249 upBranch := newBlackUBranchRule(row, col) 250 r.addHorizontalRule(row-1, col, &upBranch) 251 r.addHorizontalRule(row-1, col-1, &upBranch) 252 253 downBranch := newBlackDBranchRule(row, col) 254 r.addHorizontalRule(row+1, col, &downBranch) 255 r.addHorizontalRule(row+1, col-1, &downBranch) 256 257 // look at inversions for black nodes 258 if row > 1 && col > 1 { 259 ih := newInvertHorizontalBlack(row, col) 260 r.addHorizontalRule(row, col-2, &ih) 261 r.addHorizontalRule(row, col+1, &ih) 262 r.addVerticalRule(row-2, col, &ih) 263 r.addVerticalRule(row+1, col, &ih) 264 265 iv := newInvertVerticalBlack(row, col) 266 r.addHorizontalRule(row, col-2, &iv) 267 r.addHorizontalRule(row, col+1, &iv) 268 r.addVerticalRule(row-2, col, &iv) 269 r.addVerticalRule(row+1, col, &iv) 270 } 271 } 272 273 func (r *rules) addWhiteNode( 274 row, col model.Dimension, 275 ) { 276 277 wv := newWhiteValidator(row, col) 278 if col > 1 { 279 r.addHorizontalRule(row, col-2, &wv) 280 } 281 r.addHorizontalRule(row, col-1, &wv) 282 r.addHorizontalRule(row, col, &wv) 283 r.addHorizontalRule(row, col+1, &wv) 284 if row > 1 { 285 r.addVerticalRule(row-2, col, &wv) 286 } 287 r.addVerticalRule(row-1, col, &wv) 288 r.addVerticalRule(row, col, &wv) 289 r.addVerticalRule(row+1, col, &wv) 290 291 hb := newWhiteHorizontalBranchRule(row, col) 292 if col > 1 { 293 r.addHorizontalRule(row, col-2, &hb) 294 } 295 r.addHorizontalRule(row, col+1, &hb) 296 r.addVerticalRule(row, col-1, &hb) 297 r.addVerticalRule(row-1, col-1, &hb) 298 r.addVerticalRule(row, col+1, &hb) 299 r.addVerticalRule(row-1, col+1, &hb) 300 301 vb := newWhiteVerticalBranchRule(row, col) 302 if row > 1 { 303 r.addVerticalRule(row-2, col, &vb) 304 } 305 r.addVerticalRule(row+1, col, &vb) 306 r.addVerticalRule(row-1, col, &vb) 307 r.addVerticalRule(row-1, col-1, &vb) 308 r.addVerticalRule(row+1, col, &vb) 309 r.addVerticalRule(row+1, col-1, &vb) 310 }