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  }