github.com/joshprzybyszewski/masyu@v0.0.0-20230508015604-f31a025f6e7e/solve/state.go (about)

     1  package solve
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/joshprzybyszewski/masyu/model"
     8  )
     9  
    10  const (
    11  	all64Bits model.DimensionBit = 0xFFFFFFFFFFFFFFFF
    12  )
    13  
    14  type state struct {
    15  	rules ruleCheckCollector
    16  	nodes []model.Node
    17  
    18  	size       model.Size
    19  	hasInvalid bool
    20  
    21  	paths pathCollector
    22  
    23  	crossings crossings
    24  
    25  	// [row]colBitMask
    26  	horizontalLines  [maxPinsPerLine]model.DimensionBit
    27  	horizontalAvoids [maxPinsPerLine]model.DimensionBit
    28  
    29  	// [col]rowBitMask
    30  	verticalLines  [maxPinsPerLine]model.DimensionBit
    31  	verticalAvoids [maxPinsPerLine]model.DimensionBit
    32  }
    33  
    34  func newState(
    35  	size model.Size,
    36  	ns []model.Node,
    37  ) state {
    38  	r := newRules(size)
    39  	rcc := newRuleCheckCollector(r)
    40  
    41  	s := state{
    42  		nodes:     make([]model.Node, len(ns)),
    43  		size:      size,
    44  		crossings: newCrossings(size),
    45  		rules:     rcc,
    46  	}
    47  
    48  	// offset all of the input nodes by positive one
    49  	for i := range ns {
    50  		s.nodes[i] = ns[i]
    51  		s.nodes[i].Row++
    52  		s.nodes[i].Col++
    53  	}
    54  	s.paths = newPathCollector(s.nodes)
    55  
    56  	findGimmes(&s)
    57  
    58  	r.populateRules(&s)
    59  
    60  	s.initialize()
    61  
    62  	r.populateUnknowns(&s)
    63  
    64  	return s
    65  }
    66  
    67  func (s *state) initialize() {
    68  	avoid := model.DimensionBit(1 | (1 << s.size))
    69  	for i := 1; i <= int(s.size); i++ {
    70  		s.verticalAvoids[i] |= avoid
    71  		s.horizontalAvoids[i] |= avoid
    72  	}
    73  	s.horizontalAvoids[0] = all64Bits
    74  	s.verticalAvoids[0] = all64Bits
    75  	s.horizontalAvoids[s.size+1] = all64Bits
    76  	s.verticalAvoids[s.size+1] = all64Bits
    77  
    78  	if checkEntireRuleset(s) == invalid {
    79  		fmt.Printf("Invalid State:\n%s\n", s)
    80  		panic(`state initialization is not valid?`)
    81  	}
    82  }
    83  
    84  func (s *state) toSolution() model.Solution {
    85  	sol := model.Solution{
    86  		Size: s.size,
    87  	}
    88  
    89  	// each line needs to be shifted by one.
    90  	for i := 0; i < int(s.size); i++ {
    91  		sol.Horizontals[i] = (s.horizontalLines[i+1]) >> 1
    92  		sol.Verticals[i] = (s.verticalLines[i+1]) >> 1
    93  	}
    94  
    95  	return sol
    96  }
    97  
    98  func (s *state) getMostInterestingPath() (model.Coord, bool, bool) {
    99  	c, isHor, ok := s.paths.getInteresting(s)
   100  	if ok {
   101  		return c, isHor, true
   102  	}
   103  
   104  	for _, pp := range s.rules.rules.unknowns {
   105  		if pp.IsHorizontal {
   106  			if !s.hasHorDefined(pp.Row, pp.Col) {
   107  				return pp.Coord, pp.IsHorizontal, true
   108  			}
   109  		} else {
   110  			if !s.hasVerDefined(pp.Row, pp.Col) {
   111  				return pp.Coord, pp.IsHorizontal, true
   112  			}
   113  		}
   114  	}
   115  	// there are no more interesting paths left. Likely this means that there's
   116  	// an error in the state and we need to abort.
   117  	return model.Coord{}, false, false
   118  }
   119  
   120  func (s *state) hasHorDefined(r, c model.Dimension) bool {
   121  	return (s.horizontalLines[r]|s.horizontalAvoids[r])&c.Bit() != 0
   122  }
   123  
   124  func (s *state) horAt(r, c model.Dimension) (bool, bool) {
   125  	return s.horLineAt(r, c), s.horAvoidAt(r, c)
   126  }
   127  
   128  func (s *state) horLineAt(r, c model.Dimension) bool {
   129  	return s.horizontalLines[r]&c.Bit() != 0
   130  }
   131  
   132  func (s *state) horAvoidAt(r, c model.Dimension) bool {
   133  	return s.horizontalAvoids[r]&c.Bit() != 0
   134  }
   135  
   136  func (s *state) avoidHor(r, c model.Dimension) {
   137  	b := c.Bit()
   138  	if s.hasInvalid || s.horizontalAvoids[r]&b == b {
   139  		// already avoided
   140  		return
   141  	}
   142  	s.horizontalAvoids[r] |= b
   143  	if s.horizontalLines[r]&s.horizontalAvoids[r] != 0 {
   144  		// invalid
   145  		s.hasInvalid = true
   146  		return
   147  	}
   148  
   149  	s.rules.checkHorizontal(r, b)
   150  	s.crossings.avoidHor(c, s)
   151  }
   152  
   153  func (s *state) lineHor(r, c model.Dimension) {
   154  	b := c.Bit()
   155  	if s.hasInvalid || s.horizontalLines[r]&b == b {
   156  		// already a line
   157  		return
   158  	}
   159  	s.horizontalLines[r] |= b
   160  	if s.horizontalLines[r]&s.horizontalAvoids[r] != 0 {
   161  		// invalid
   162  		s.hasInvalid = true
   163  		return
   164  	}
   165  
   166  	s.rules.checkHorizontal(r, b)
   167  	s.crossings.lineHor(c, s)
   168  	s.paths.addHorizontal(r, c, s)
   169  }
   170  
   171  func (s *state) hasVerDefined(r, c model.Dimension) bool {
   172  	return (s.verticalLines[c]|s.verticalAvoids[c])&r.Bit() != 0
   173  }
   174  
   175  func (s *state) verAt(r, c model.Dimension) (bool, bool) {
   176  	return s.verLineAt(r, c), s.verAvoidAt(r, c)
   177  }
   178  
   179  func (s *state) verLineAt(r, c model.Dimension) bool {
   180  	return s.verticalLines[c]&r.Bit() != 0
   181  }
   182  
   183  func (s *state) verAvoidAt(r, c model.Dimension) bool {
   184  	return s.verticalAvoids[c]&r.Bit() != 0
   185  }
   186  
   187  func (s *state) avoidVer(r, c model.Dimension) {
   188  	b := r.Bit()
   189  	if s.hasInvalid || s.verticalAvoids[c]&b == b {
   190  		// already avoided
   191  		return
   192  	}
   193  	s.verticalAvoids[c] |= b
   194  	if s.verticalLines[c]&s.verticalAvoids[c] != 0 {
   195  		// invalid
   196  		s.hasInvalid = true
   197  		return
   198  	}
   199  
   200  	s.rules.checkVertical(b, c)
   201  	s.crossings.avoidVer(r, s)
   202  }
   203  
   204  func (s *state) lineVer(r, c model.Dimension) {
   205  	b := r.Bit()
   206  	if s.hasInvalid || s.verticalLines[c]&b == b {
   207  		// already avoided
   208  		return
   209  	}
   210  	s.verticalLines[c] |= b
   211  	if s.verticalLines[c]&s.verticalAvoids[c] != 0 {
   212  		// invalid
   213  		s.hasInvalid = true
   214  		return
   215  	}
   216  
   217  	s.rules.checkVertical(b, c)
   218  	s.crossings.lineVer(r, s)
   219  	s.paths.addVertical(r, c, s)
   220  }
   221  
   222  const (
   223  	confusedSpace       byte = '@'
   224  	horizontalLineSpace byte = '-'
   225  	verticalLineSpace   byte = '|'
   226  	avoidSpace          byte = 'X'
   227  )
   228  
   229  func (s *state) String() string {
   230  	var sb strings.Builder
   231  
   232  	var isLine, isAvoid bool
   233  
   234  	for r := 0; r <= int(s.size+1); r++ {
   235  		for c := 0; c <= int(s.size+1); c++ {
   236  			sb.WriteByte(s.getNode(model.Dimension(r), model.Dimension(c)))
   237  			sb.WriteByte(' ')
   238  			isLine, isAvoid = s.horAt(model.Dimension(r), model.Dimension(c))
   239  			if isLine && isAvoid {
   240  				sb.WriteByte(confusedSpace)
   241  			} else if isLine {
   242  				sb.WriteByte(horizontalLineSpace)
   243  			} else if isAvoid {
   244  				sb.WriteByte(avoidSpace)
   245  			} else {
   246  				sb.WriteByte(' ')
   247  			}
   248  			sb.WriteByte(' ')
   249  		}
   250  		sb.WriteByte('\n')
   251  
   252  		for c := 0; c <= int(s.size+1); c++ {
   253  			isLine, isAvoid = s.verAt(model.Dimension(r), model.Dimension(c))
   254  			if isLine && isAvoid {
   255  				sb.WriteByte(confusedSpace)
   256  			} else if isLine {
   257  				sb.WriteByte(verticalLineSpace)
   258  			} else if isAvoid {
   259  				sb.WriteByte(avoidSpace)
   260  			} else {
   261  				sb.WriteByte(' ')
   262  			}
   263  			sb.WriteByte(' ')
   264  			sb.WriteByte(' ')
   265  			sb.WriteByte(' ')
   266  		}
   267  		sb.WriteByte('\n')
   268  	}
   269  
   270  	return sb.String()
   271  }
   272  
   273  func (s *state) getNode(r, c model.Dimension) byte {
   274  	for _, n := range s.nodes {
   275  		if n.Row != r || n.Col != c {
   276  			continue
   277  		}
   278  		if n.IsBlack {
   279  			return 'B'
   280  		}
   281  		return 'W'
   282  	}
   283  	if r == 0 {
   284  		return '0' + byte(c%10)
   285  	}
   286  	if c == 0 {
   287  		return '0' + byte(r%10)
   288  	}
   289  	if r > model.Dimension(s.size) || c > model.Dimension(s.size) {
   290  		return ' '
   291  	}
   292  	return '*'
   293  }