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 }