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  }