github.com/joshprzybyszewski/masyu@v0.0.0-20230508015604-f31a025f6e7e/solve/permutations.go (about) 1 package solve 2 3 import ( 4 "fmt" 5 6 "github.com/joshprzybyszewski/masyu/model" 7 ) 8 9 type applyFn func(*state) 10 11 type permutationsFactorySubstate struct { 12 perms applyFn 13 14 // known is the last known row/col with a value 15 known model.Dimension 16 // numCrossings is the number of lines currently known to be in this row/col 17 numCrossings int 18 } 19 20 const ( 21 permutationsFactoryNumVals = 8 22 ) 23 24 type permutationsFactory struct { 25 vals [permutationsFactoryNumVals]applyFn 26 numVals uint16 27 } 28 29 func newPermutationsFactory() permutationsFactory { 30 return permutationsFactory{} 31 } 32 33 func (pf *permutationsFactory) save( 34 a applyFn, 35 ) { 36 pf.vals[pf.numVals] = a 37 pf.numVals++ 38 } 39 40 func (pf *permutationsFactory) hasRoomForNumEmpty( 41 numEmpty int, 42 ) bool { 43 if numEmpty == 0 { 44 return false 45 } 46 numPerms := 1 << (numEmpty - 1) 47 rem := len(pf.vals) - int(pf.numVals) 48 return numPerms <= rem 49 } 50 51 func (pf *permutationsFactory) populate( 52 cur *state, 53 ) { 54 numEmptyInCol, col := pf.getBestNextStartingCol(cur) 55 if col == 0 || !pf.hasRoomForNumEmpty(numEmptyInCol) { 56 pf.populateBestRow(cur) 57 pf.populateSimple(cur) 58 return 59 } 60 61 numEmptyInRow, row := pf.getBestNextStartingRow(cur) 62 if row == 0 || !pf.hasRoomForNumEmpty(numEmptyInRow) { 63 pf.populateBestColumn(cur) 64 pf.populateSimple(cur) 65 return 66 } 67 68 if numEmptyInCol < numEmptyInRow { 69 pf.populateBestColumn(cur) 70 } else { 71 pf.populateBestRow(cur) 72 } 73 74 pf.populateSimple(cur) 75 } 76 77 func (pf *permutationsFactory) populateBestColumn( 78 cur *state, 79 ) { 80 if pf.numVals > 0 { 81 return 82 } 83 84 numEmpty, col := pf.getBestNextStartingCol(cur) 85 if col == 0 || !pf.hasRoomForNumEmpty(numEmpty) { 86 return 87 } 88 89 pf.buildColumn( 90 cur, 91 col, 92 permutationsFactorySubstate{ 93 known: 0, 94 numCrossings: getNumLinesInCol(cur, col), 95 perms: func(s *state) { 96 if s.hasInvalid { 97 return 98 } 99 if getNextEmptyRow(s, col, 0) != 0 { 100 fmt.Printf("Didn't fill the whole column\nColumn: %d\n%s\n", col, s) 101 panic(`didn't fill the whole col?`) 102 } 103 if getNumLinesInCol(s, col)%2 != 0 { 104 fmt.Printf("Wrong Number of Lines\nColumn: %d\n%s\n", col, s) 105 panic(`didn't place the right amount of lines`) 106 } 107 }, 108 }, 109 ) 110 111 } 112 113 func (pf *permutationsFactory) buildColumn( 114 s *state, 115 col model.Dimension, 116 cur permutationsFactorySubstate, 117 ) { 118 row := getNextEmptyRow(s, cur.known+1, col) 119 if row == 0 { 120 // there wasn't an empty column found. 121 if cur.numCrossings%2 == 0 { 122 pf.save(cur.perms) 123 } 124 return 125 } 126 127 a := permutationsFactorySubstate{ 128 known: row, 129 numCrossings: cur.numCrossings, 130 perms: func(s *state) { 131 s.avoidHor(row, col) 132 cur.perms(s) 133 }, 134 } 135 l := permutationsFactorySubstate{ 136 known: row, 137 numCrossings: cur.numCrossings + 1, 138 perms: func(s *state) { 139 s.lineHor(row, col) 140 cur.perms(s) 141 }, 142 } 143 144 if a.numCrossings >= int(s.size)/2 { 145 pf.buildColumn(s, col, a) 146 pf.buildColumn(s, col, l) 147 } else { 148 pf.buildColumn(s, col, l) 149 pf.buildColumn(s, col, a) 150 } 151 } 152 153 func (pf *permutationsFactory) populateBestRow( 154 cur *state, 155 ) { 156 if pf.numVals > 0 { 157 return 158 } 159 160 numEmpty, row := pf.getBestNextStartingRow(cur) 161 if row == 0 || !pf.hasRoomForNumEmpty(numEmpty) { 162 // couldn't find a good starting row? 163 return 164 } 165 166 pf.buildRow( 167 cur, 168 row, 169 permutationsFactorySubstate{ 170 known: 0, 171 numCrossings: getNumLinesInRow(cur, row), 172 perms: func(s *state) { 173 if s.hasInvalid { 174 return 175 } 176 if getNextEmptyCol(s, row, 0) != 0 { 177 panic(`didn't fill the whole row?`) 178 } 179 if getNumLinesInRow(s, row)%2 != 0 { 180 fmt.Printf("Wrong Number of Lines\nRow: %d\n%s\n", row, s) 181 panic(`didn't place the right amount of lines`) 182 } 183 }, 184 }, 185 ) 186 } 187 188 func (pf *permutationsFactory) buildRow( 189 s *state, 190 row model.Dimension, 191 cur permutationsFactorySubstate, 192 ) { 193 194 col := getNextEmptyCol(s, row, cur.known+1) 195 if col == 0 { 196 if cur.numCrossings%2 == 0 { 197 pf.save(cur.perms) 198 } 199 return 200 } 201 202 a := permutationsFactorySubstate{ 203 known: col, 204 numCrossings: cur.numCrossings, 205 perms: func(s *state) { 206 s.avoidVer(row, col) 207 cur.perms(s) 208 }, 209 } 210 211 l := permutationsFactorySubstate{ 212 known: col, 213 numCrossings: cur.numCrossings + 1, 214 perms: func(s *state) { 215 s.lineVer(row, col) 216 cur.perms(s) 217 }, 218 } 219 220 if a.numCrossings >= int(s.size)/2 { 221 pf.buildRow(s, row, a) 222 pf.buildRow(s, row, l) 223 } else { 224 pf.buildRow(s, row, l) 225 pf.buildRow(s, row, a) 226 } 227 } 228 229 func (pf *permutationsFactory) populateSimple( 230 s *state, 231 ) { 232 pf.populateNextNode(s) 233 234 if pf.numVals > 0 { 235 return 236 } 237 238 c, isHor, ok := s.getMostInterestingPath() 239 if !ok { 240 return 241 } 242 243 constantDim := c.Row 244 travelDim := c.Col 245 if isHor { 246 constantDim = c.Col 247 travelDim = c.Row 248 } 249 250 pf.buildSimple( 251 s, 252 isHor, 253 constantDim, 254 permutationsFactorySubstate{ 255 known: travelDim, 256 numCrossings: 0, 257 perms: func(s *state) { 258 }, 259 }, 260 ) 261 } 262 263 func (pf *permutationsFactory) populateNextNode( 264 s *state, 265 ) { 266 267 if pf.numVals > 0 { 268 return 269 } 270 271 var wn, bn model.Node 272 for _, n := range s.nodes { 273 if !isNodeSolved(s, n) { 274 if n.IsBlack { 275 bn = n 276 break 277 } else { 278 wn = n 279 } 280 } 281 } 282 node := bn 283 if node.Row == 0 { 284 node = wn 285 } 286 if node.Row == 0 { 287 // did not find an unsolved node 288 return 289 } 290 291 if node.IsBlack { 292 // RD 293 pf.save(func(s *state) { 294 s.avoidHor(node.Row, node.Col-1) 295 s.lineHor(node.Row, node.Col) 296 s.lineHor(node.Row, node.Col+1) 297 s.avoidVer(node.Row-1, node.Col+1) 298 s.avoidVer(node.Row, node.Col+1) 299 300 s.avoidVer(node.Row-1, node.Col) 301 s.lineVer(node.Row, node.Col) 302 s.lineVer(node.Row+1, node.Col) 303 s.avoidHor(node.Row+1, node.Col-1) 304 s.avoidHor(node.Row+1, node.Col) 305 }) 306 // DL 307 if node.Col > 1 { 308 pf.save(func(s *state) { 309 s.avoidHor(node.Row, node.Col) 310 s.lineHor(node.Row, node.Col-1) 311 s.lineHor(node.Row, node.Col-2) 312 s.avoidVer(node.Row-1, node.Col-1) 313 s.avoidVer(node.Row, node.Col-1) 314 315 s.avoidVer(node.Row-1, node.Col) 316 s.lineVer(node.Row, node.Col) 317 s.lineVer(node.Row+1, node.Col) 318 s.avoidHor(node.Row+1, node.Col-1) 319 s.avoidHor(node.Row+1, node.Col) 320 }) 321 322 // LU 323 if node.Row > 1 { 324 pf.save(func(s *state) { 325 s.avoidHor(node.Row, node.Col) 326 s.lineHor(node.Row, node.Col-1) 327 s.lineHor(node.Row, node.Col-2) 328 s.avoidVer(node.Row-1, node.Col-1) 329 s.avoidVer(node.Row, node.Col-1) 330 331 s.avoidVer(node.Row, node.Col) 332 s.lineVer(node.Row-1, node.Col) 333 s.lineVer(node.Row-2, node.Col) 334 s.avoidHor(node.Row-1, node.Col-1) 335 s.avoidHor(node.Row-1, node.Col) 336 }) 337 } 338 } 339 // UR 340 if node.Row > 1 { 341 pf.save(func(s *state) { 342 s.avoidHor(node.Row, node.Col-1) 343 s.lineHor(node.Row, node.Col) 344 s.lineHor(node.Row, node.Col+1) 345 s.avoidVer(node.Row-1, node.Col+1) 346 s.avoidVer(node.Row, node.Col+1) 347 348 s.avoidVer(node.Row, node.Col) 349 s.lineVer(node.Row-1, node.Col) 350 s.lineVer(node.Row-2, node.Col) 351 s.avoidHor(node.Row-1, node.Col-1) 352 s.avoidHor(node.Row-1, node.Col) 353 }) 354 } 355 } else { 356 // horizontal 357 pf.save(func(s *state) { 358 s.lineHor(node.Row, node.Col) 359 s.lineHor(node.Row, node.Col-1) 360 s.avoidVer(node.Row-1, node.Col) 361 s.avoidVer(node.Row, node.Col) 362 }) 363 // vertical 364 pf.save(func(s *state) { 365 s.lineVer(node.Row, node.Col) 366 s.lineVer(node.Row-1, node.Col) 367 s.avoidHor(node.Row, node.Col-1) 368 s.avoidHor(node.Row, node.Col) 369 }) 370 } 371 } 372 373 func isNodeSolved( 374 s *state, 375 n model.Node, 376 ) bool { 377 if !s.hasHorDefined(n.Row, n.Col) { 378 return false 379 } 380 381 if !s.hasVerDefined(n.Row, n.Col) { 382 return false 383 } 384 385 if !s.hasHorDefined(n.Row, n.Col-1) { 386 return false 387 } 388 389 return true 390 } 391 392 func (pf *permutationsFactory) buildSimple( 393 s *state, 394 travelCol bool, 395 constantDim model.Dimension, 396 cur permutationsFactorySubstate, 397 ) { 398 399 var travelDim model.Dimension 400 if travelCol { 401 travelDim = getNextEmptyRow(s, cur.known+1, constantDim) 402 } else { 403 travelDim = getNextEmptyCol(s, constantDim, cur.known+1) 404 } 405 406 ap := func(s *state) { 407 if travelCol { 408 s.avoidHor(cur.known, constantDim) 409 } else { 410 s.avoidVer(constantDim, cur.known) 411 } 412 cur.perms(s) 413 } 414 415 lp := func(s *state) { 416 if travelCol { 417 s.lineHor(cur.known, constantDim) 418 } else { 419 s.lineVer(constantDim, cur.known) 420 } 421 cur.perms(s) 422 } 423 424 if travelDim == 0 || cur.numCrossings >= 4 { 425 pf.save(ap) 426 pf.save(lp) 427 return 428 } 429 430 a := permutationsFactorySubstate{ 431 known: travelDim, 432 numCrossings: cur.numCrossings + 1, 433 perms: ap, 434 } 435 436 l := permutationsFactorySubstate{ 437 known: travelDim, 438 numCrossings: cur.numCrossings + 1, 439 perms: lp, 440 } 441 442 pf.buildSimple(s, travelCol, constantDim, a) 443 pf.buildSimple(s, travelCol, constantDim, l) 444 } 445 446 func (pf *permutationsFactory) getBestNextStartingRow( 447 s *state, 448 ) (int, model.Dimension) { 449 var rowByNumEmpty [maxPinsPerLine]model.Dimension 450 var ne int 451 452 for row := model.Dimension(1); row < model.Dimension(s.size); row++ { 453 ne = int(s.size) - int(s.crossings.rows[row]) - int(s.crossings.rowsAvoid[row]) 454 rowByNumEmpty[ne] = row 455 } 456 457 return pf.chooseStart(rowByNumEmpty) 458 } 459 460 func (pf *permutationsFactory) getBestNextStartingCol( 461 s *state, 462 ) (int, model.Dimension) { 463 var colByNumEmpty [maxPinsPerLine]model.Dimension 464 var ne int 465 466 for col := model.Dimension(1); col < model.Dimension(s.size); col++ { 467 ne = int(s.size) - int(s.crossings.cols[col]) - int(s.crossings.colsAvoid[col]) 468 colByNumEmpty[ne] = col 469 } 470 471 return pf.chooseStart(colByNumEmpty) 472 } 473 474 func (pf *permutationsFactory) chooseStart( 475 byNumEmpty [maxPinsPerLine]model.Dimension, 476 ) (int, model.Dimension) { 477 478 for numEmpty := 2; numEmpty < len(byNumEmpty); numEmpty++ { 479 if byNumEmpty[numEmpty] > 0 { 480 return numEmpty, byNumEmpty[numEmpty] 481 } 482 } 483 484 // it's unlikely that all rows are filled... 485 return 0, 0 486 } 487 488 func getNextEmptyRow( 489 s *state, 490 row, col model.Dimension, 491 ) model.Dimension { 492 for ; row <= model.Dimension(s.size); row++ { 493 if !s.hasHorDefined(row, col) { 494 return row 495 } 496 } 497 return 0 498 } 499 500 func getNumLinesInCol( 501 s *state, 502 col model.Dimension, 503 ) int { 504 return int(s.crossings.cols[col]) 505 } 506 507 func getNextEmptyCol( 508 s *state, 509 row, col model.Dimension, 510 ) model.Dimension { 511 for ; col <= model.Dimension(s.size); col++ { 512 if !s.hasVerDefined(row, col) { 513 return col 514 } 515 } 516 return 0 517 } 518 519 func getNumLinesInRow( 520 s *state, 521 row model.Dimension, 522 ) int { 523 return int(s.crossings.rows[row]) 524 }