takuzu.go
changeset 0 00371339bbcc
child 2 d41c4f8fe066
equal deleted inserted replaced
-1:000000000000 0:00371339bbcc
       
     1 package takuzu
       
     2 
       
     3 import (
       
     4 	"bytes"
       
     5 	"fmt"
       
     6 	"log"
       
     7 	"math"
       
     8 	"runtime"
       
     9 	"sync"
       
    10 	"time"
       
    11 
       
    12 	"github.com/pkg/errors"
       
    13 )
       
    14 
       
    15 var verbosity int
       
    16 var schrodLvl uint
       
    17 
       
    18 // Cell is a single cell of a Takuzu game board
       
    19 type Cell struct {
       
    20 	Defined bool
       
    21 	Value   int
       
    22 }
       
    23 
       
    24 // Takuzu is a Takuzu game board (Size x Size)
       
    25 type Takuzu struct {
       
    26 	Size  int
       
    27 	Board [][]Cell
       
    28 }
       
    29 
       
    30 // New creates a new Takuzu board
       
    31 func New(size int) Takuzu {
       
    32 	t := Takuzu{Size: size}
       
    33 	t.Board = make([][]Cell, size)
       
    34 	for l := range t.Board {
       
    35 		t.Board[l] = make([]Cell, size)
       
    36 	}
       
    37 	return t
       
    38 }
       
    39 
       
    40 // NewFromString creates a new Takuzu board from a string definition
       
    41 func NewFromString(s string) (*Takuzu, error) {
       
    42 	l := len(s)
       
    43 	// TODO: validate chars ([.01OI])
       
    44 	size := int(math.Sqrt(float64(l)))
       
    45 	if size*size != l {
       
    46 		return nil, errors.New("bad string length")
       
    47 	}
       
    48 
       
    49 	i := 0
       
    50 	t := New(size)
       
    51 
       
    52 	for line := 0; line < size; line++ {
       
    53 		for col := 0; col < size; col++ {
       
    54 			switch s[i] {
       
    55 			case '0', 'O':
       
    56 				t.Board[line][col].Defined = true
       
    57 				t.Board[line][col].Value = 0
       
    58 			case '1', 'I':
       
    59 				t.Board[line][col].Defined = true
       
    60 				t.Board[line][col].Value = 1
       
    61 			case '.':
       
    62 			default:
       
    63 				return nil, errors.New("invalid char in string")
       
    64 			}
       
    65 			i++
       
    66 		}
       
    67 	}
       
    68 	return &t, nil
       
    69 }
       
    70 
       
    71 // ToString converts a takuzu board to its string representation
       
    72 func (b Takuzu) ToString() string {
       
    73 	var sbuf bytes.Buffer
       
    74 	for line := 0; line < b.Size; line++ {
       
    75 		for col := 0; col < b.Size; col++ {
       
    76 			if b.Board[line][col].Defined {
       
    77 				sbuf.WriteString(fmt.Sprintf("%d", b.Board[line][col].Value))
       
    78 				continue
       
    79 			}
       
    80 			sbuf.WriteByte('.')
       
    81 		}
       
    82 	}
       
    83 	return sbuf.String()
       
    84 }
       
    85 
       
    86 // DumpString writes the content of the board as a stream
       
    87 func (b Takuzu) DumpString() {
       
    88 	fmt.Println(b.ToString())
       
    89 }
       
    90 
       
    91 // Clone returns a copy of the Takuzu board
       
    92 func (b Takuzu) Clone() Takuzu {
       
    93 	c := New(b.Size)
       
    94 	for line := range b.Board {
       
    95 		for col := range b.Board[line] {
       
    96 			c.Board[line][col] = b.Board[line][col]
       
    97 		}
       
    98 	}
       
    99 	return c
       
   100 }
       
   101 
       
   102 // Copy copies a Takuzu board to another existing board
       
   103 func Copy(src, dst *Takuzu) error {
       
   104 	if src.Size != dst.Size {
       
   105 		return errors.New("sizes do not match")
       
   106 	}
       
   107 	for line := range src.Board {
       
   108 		for col := range src.Board[line] {
       
   109 			dst.Board[line][col] = src.Board[line][col]
       
   110 		}
       
   111 	}
       
   112 	return nil
       
   113 }
       
   114 
       
   115 // BoardsMatch compares a Takuzu board to another, optionally ignoring
       
   116 // empty cells.  Returns true if the two boards match.
       
   117 func BoardsMatch(t1, t2 *Takuzu, ignoreUndefined bool) (match bool, line, col int) {
       
   118 	match = true
       
   119 
       
   120 	if t1.Size != t2.Size {
       
   121 		line, col = -1, -1
       
   122 		match = false
       
   123 		return
       
   124 	}
       
   125 	for line = range t1.Board {
       
   126 		for col = range t1.Board[line] {
       
   127 			if !t1.Board[line][col].Defined || !t2.Board[line][col].Defined {
       
   128 				// At least one of the cells is empty
       
   129 				if ignoreUndefined ||
       
   130 					!(t1.Board[line][col].Defined || t2.Board[line][col].Defined) {
       
   131 					// Both cells are empty or we ignore empty cells
       
   132 					continue
       
   133 				}
       
   134 				match = false
       
   135 				return
       
   136 			}
       
   137 			// Both cells are defined
       
   138 			if t1.Board[line][col].Value != t2.Board[line][col].Value {
       
   139 				match = false
       
   140 				return
       
   141 			}
       
   142 		}
       
   143 	}
       
   144 
       
   145 	line, col = -1, -1
       
   146 	return
       
   147 }
       
   148 
       
   149 // Set sets the value of the cell; a value -1 will set the cell as undefined
       
   150 func (c *Cell) Set(value int) {
       
   151 	if value != 0 && value != 1 {
       
   152 		c.Defined = false
       
   153 		return
       
   154 	}
       
   155 	c.Defined = true
       
   156 	c.Value = value
       
   157 }
       
   158 
       
   159 // Set sets the value of a specific cell
       
   160 // A value -1 will undefine the cell
       
   161 func (b Takuzu) Set(l, c, value int) {
       
   162 	if value != 0 && value != 1 {
       
   163 		b.Board[l][c].Defined = false
       
   164 		return
       
   165 	}
       
   166 	b.Board[l][c].Defined = true
       
   167 	b.Board[l][c].Value = value
       
   168 }
       
   169 
       
   170 // GetLine returns a slice of cells containing the ith line of the board
       
   171 func (b Takuzu) GetLine(i int) []Cell {
       
   172 	return b.Board[i]
       
   173 }
       
   174 
       
   175 // GetColumn returns a slice of cells containing the ith column of the board
       
   176 func (b Takuzu) GetColumn(i int) []Cell {
       
   177 	c := make([]Cell, b.Size)
       
   178 	for l := range b.Board {
       
   179 		c[l] = b.Board[l][i]
       
   180 	}
       
   181 	return c
       
   182 }
       
   183 
       
   184 // GetLinePointers returns a slice of pointers to the cells of the ith line of the board
       
   185 func (b Takuzu) GetLinePointers(i int) []*Cell {
       
   186 	r := make([]*Cell, b.Size)
       
   187 	for l := range b.Board[i] {
       
   188 		r[l] = &b.Board[i][l]
       
   189 	}
       
   190 	return r
       
   191 }
       
   192 
       
   193 // GetColumnPointers returns a slice of pointers to the cells of the ith column of the board
       
   194 func (b Takuzu) GetColumnPointers(i int) []*Cell {
       
   195 	r := make([]*Cell, b.Size)
       
   196 	for l := range b.Board {
       
   197 		r[l] = &b.Board[l][i]
       
   198 	}
       
   199 	return r
       
   200 }
       
   201 
       
   202 // FillLineColumn add missing 0s or 1s if all 1s or 0s are there.
       
   203 // Note: This method can update b.
       
   204 func (b Takuzu) FillLineColumn(l, c int) {
       
   205 	fillRange := func(r []*Cell) {
       
   206 		size := len(r)
       
   207 		var notFull bool
       
   208 		var n [2]int
       
   209 		for x := 0; x < size; x++ {
       
   210 			if r[x].Defined {
       
   211 				n[r[x].Value]++
       
   212 			} else {
       
   213 				notFull = true
       
   214 			}
       
   215 		}
       
   216 		if !notFull {
       
   217 			return
       
   218 		}
       
   219 		if n[0] == size/2 {
       
   220 			// Let's fill the 1s
       
   221 			for _, x := range r {
       
   222 				if !x.Defined {
       
   223 					x.Defined = true
       
   224 					x.Value = 1
       
   225 				}
       
   226 			}
       
   227 		} else if n[1] == size/2 {
       
   228 			// Let's fill the 0s
       
   229 			for _, x := range r {
       
   230 				if !x.Defined {
       
   231 					x.Defined = true
       
   232 					x.Value = 0
       
   233 				}
       
   234 			}
       
   235 		}
       
   236 	}
       
   237 
       
   238 	var cells []*Cell
       
   239 
       
   240 	// Fill line
       
   241 	cells = b.GetLinePointers(l)
       
   242 	fillRange(cells)
       
   243 	// Fill column
       
   244 	cells = b.GetColumnPointers(c)
       
   245 	fillRange(cells)
       
   246 }
       
   247 
       
   248 // DumpBoard displays the Takuzu board
       
   249 func (b Takuzu) DumpBoard() {
       
   250 	fmt.Println()
       
   251 	for i := range b.Board {
       
   252 		dumpRange(b.Board[i])
       
   253 	}
       
   254 }
       
   255 
       
   256 // CheckLine returns an error if the line i fails validation
       
   257 func (b Takuzu) CheckLine(i int) error {
       
   258 	_, err := checkRange(b.GetLine(i))
       
   259 	return err
       
   260 }
       
   261 
       
   262 // CheckColumn returns an error if the column i fails validation
       
   263 func (b Takuzu) CheckColumn(i int) error {
       
   264 	_, err := checkRange(b.GetColumn(i))
       
   265 	return err
       
   266 }
       
   267 
       
   268 // Validate checks a whole board for errors (not completeness)
       
   269 // Returns true if all cells are defined.
       
   270 func (b Takuzu) Validate() (bool, error) {
       
   271 	finished := true
       
   272 
       
   273 	computeVal := func(cells []Cell) (val int) {
       
   274 		for i := 0; i < len(cells); i++ {
       
   275 			val += cells[i].Value * 1 << uint(i)
       
   276 		}
       
   277 		return
       
   278 	}
       
   279 
       
   280 	lineVals := make(map[int]bool)
       
   281 	colVals := make(map[int]bool)
       
   282 
       
   283 	for i := 0; i < b.Size; i++ {
       
   284 		var d []Cell
       
   285 		var full bool
       
   286 		var err error
       
   287 
       
   288 		// Let's check line i
       
   289 		d = b.GetLine(i)
       
   290 		full, err = checkRange(d)
       
   291 		if err != nil {
       
   292 			return false, errors.Wrapf(err, "line %d", i)
       
   293 		}
       
   294 		if full {
       
   295 			hv := computeVal(d)
       
   296 			if lineVals[hv] {
       
   297 				return false, fmt.Errorf("duplicate lines (%d)", i)
       
   298 			}
       
   299 			lineVals[hv] = true
       
   300 		} else {
       
   301 			finished = false
       
   302 		}
       
   303 
       
   304 		// Let's check column i
       
   305 		d = b.GetColumn(i)
       
   306 		full, err = checkRange(d)
       
   307 		if err != nil {
       
   308 			return false, errors.Wrapf(err, "column %d", i)
       
   309 		}
       
   310 		if full {
       
   311 			hv := computeVal(d)
       
   312 			if colVals[hv] {
       
   313 				return false, fmt.Errorf("duplicate colums (%d)", i)
       
   314 			}
       
   315 			colVals[hv] = true
       
   316 		} else {
       
   317 			finished = false
       
   318 		}
       
   319 	}
       
   320 	return finished, nil
       
   321 }
       
   322 
       
   323 func dumpRange(cells []Cell) {
       
   324 	for _, c := range cells {
       
   325 		if !c.Defined {
       
   326 			fmt.Printf(". ")
       
   327 			continue
       
   328 		}
       
   329 		fmt.Printf("%d ", c.Value)
       
   330 	}
       
   331 	fmt.Println()
       
   332 }
       
   333 
       
   334 // checkRange returns true if the range is completely defined, and an error
       
   335 // if it doesn't follow the rules for a takuzu line or column
       
   336 // Note that the boolean might be invalid if the error is not nil.
       
   337 func checkRange(cells []Cell) (bool, error) {
       
   338 	full := true
       
   339 	size := len(cells)
       
   340 	counters := []int{0, 0}
       
   341 
       
   342 	var prevCell Cell
       
   343 	var prevCellCount int
       
   344 
       
   345 	for _, c := range cells {
       
   346 		if !c.Defined {
       
   347 			full = false
       
   348 			prevCell.Defined = false
       
   349 			prevCellCount = 0
       
   350 			continue
       
   351 		}
       
   352 		counters[c.Value]++
       
   353 		if prevCellCount == 0 {
       
   354 			prevCellCount = 1
       
   355 		} else {
       
   356 			if c.Value == prevCell.Value {
       
   357 				prevCellCount++
       
   358 				if prevCellCount > 2 {
       
   359 					return full, errors.Errorf("3+ same values %d", c.Value)
       
   360 				}
       
   361 			} else {
       
   362 				prevCellCount = 1
       
   363 			}
       
   364 
       
   365 		}
       
   366 		prevCell = c
       
   367 	}
       
   368 	if counters[0] > size/2 {
       
   369 		return full, errors.Errorf("too many zeroes")
       
   370 	}
       
   371 	if counters[1] > size/2 {
       
   372 		return full, errors.Errorf("too many ones")
       
   373 	}
       
   374 	return full, nil
       
   375 }
       
   376 
       
   377 func checkRangeCounts(cells []Cell) (full bool, n0, n1 int) {
       
   378 	counters := []int{0, 0}
       
   379 	full = true
       
   380 
       
   381 	for _, c := range cells {
       
   382 		if c.Defined {
       
   383 			counters[c.Value]++
       
   384 		} else {
       
   385 			full = false
       
   386 		}
       
   387 	}
       
   388 	return full, counters[0], counters[1]
       
   389 }
       
   390 
       
   391 func (b Takuzu) guessPos(l, c int) int {
       
   392 	if b.Board[l][c].Defined {
       
   393 		return b.Board[l][c].Value
       
   394 	}
       
   395 
       
   396 	bx := b.Clone()
       
   397 	bx.Set(l, c, 0)
       
   398 	bx.FillLineColumn(l, c)
       
   399 	if bx.CheckLine(l) != nil || bx.CheckColumn(c) != nil {
       
   400 		return 1
       
   401 	}
       
   402 	Copy(&b, &bx)
       
   403 	bx.Set(l, c, 1)
       
   404 	bx.FillLineColumn(l, c)
       
   405 	if bx.CheckLine(l) != nil || bx.CheckColumn(c) != nil {
       
   406 		return 0
       
   407 	}
       
   408 
       
   409 	return -1 // dunno
       
   410 }
       
   411 
       
   412 // trySolveTrivialPass does 1 pass over the takuzu board and tries to find
       
   413 // values using simple guesses.
       
   414 func (b Takuzu) trySolveTrivialPass() (changed bool) {
       
   415 	for line := 0; line < b.Size; line++ {
       
   416 		for col := 0; col < b.Size; col++ {
       
   417 			if b.Board[line][col].Defined {
       
   418 				continue
       
   419 			}
       
   420 			if guess := b.guessPos(line, col); guess != -1 {
       
   421 				b.Set(line, col, guess)
       
   422 				if verbosity > 3 {
       
   423 					log.Printf("Trivial: Setting [%d,%d] to %d", line, col, guess)
       
   424 				}
       
   425 				changed = true // Ideally remember l,c
       
   426 			}
       
   427 		}
       
   428 	}
       
   429 	return changed
       
   430 }
       
   431 
       
   432 // TrySolveTrivial tries to solve the takuzu using a loop over simple methods
       
   433 // It returns true if all cells are defined, and an error if the grid breaks the rules.
       
   434 func (b Takuzu) TrySolveTrivial() (bool, error) {
       
   435 	for {
       
   436 		changed := b.trySolveTrivialPass()
       
   437 		if verbosity > 3 {
       
   438 			var status string
       
   439 			if changed {
       
   440 				status = "ongoing"
       
   441 			} else {
       
   442 				status = "stuck"
       
   443 			}
       
   444 			log.Println("Trivial resolution -", status)
       
   445 		}
       
   446 		if !changed {
       
   447 			break
       
   448 		}
       
   449 
       
   450 		if verbosity > 3 {
       
   451 			b.DumpBoard()
       
   452 			fmt.Println()
       
   453 		}
       
   454 	}
       
   455 	full, err := b.Validate()
       
   456 	if err != nil {
       
   457 		return full, errors.Wrap(err, "the takuzu looks wrong")
       
   458 	}
       
   459 	return full, nil
       
   460 }
       
   461 
       
   462 // TrySolveRecurse tries to solve the takuzu recursively, using trivial
       
   463 // method first and using guesses if it fails.
       
   464 func (b Takuzu) TrySolveRecurse(allSolutions *[]Takuzu, timeout time.Duration) (*Takuzu, error) {
       
   465 
       
   466 	var solutionsMux sync.Mutex
       
   467 	var singleSolution *Takuzu
       
   468 	var solutionMap map[string]*Takuzu
       
   469 
       
   470 	var globalSearch bool
       
   471 	// globalSearch doesn't need to use a mutex and is more convenient
       
   472 	// to use than allSolutions.
       
   473 	if allSolutions != nil {
       
   474 		globalSearch = true
       
   475 		solutionMap = make(map[string]*Takuzu)
       
   476 	}
       
   477 
       
   478 	startTime := time.Now()
       
   479 
       
   480 	var recurseSolve func(level int, t Takuzu, errStatus chan<- error) error
       
   481 
       
   482 	recurseSolve = func(level int, t Takuzu, errStatus chan<- error) error {
       
   483 
       
   484 		reportStatus := func(failure error) {
       
   485 			// Report status to the caller's channel
       
   486 			if errStatus != nil {
       
   487 				errStatus <- failure
       
   488 			}
       
   489 		}
       
   490 
       
   491 		// In Schröndinger mode we check concurrently both values for a cell
       
   492 		var schrodinger bool
       
   493 		concurrentRoutines := 1
       
   494 		if level < int(schrodLvl) {
       
   495 			schrodinger = true
       
   496 			concurrentRoutines = 2
       
   497 		}
       
   498 
       
   499 		var status [2]chan error
       
   500 		status[0] = make(chan error)
       
   501 		status[1] = make(chan error)
       
   502 
       
   503 		for {
       
   504 			// Try simple resolution first
       
   505 			full, err := t.TrySolveTrivial()
       
   506 			if err != nil {
       
   507 				reportStatus(err)
       
   508 				return err
       
   509 			}
       
   510 
       
   511 			if full { // We're done
       
   512 				if verbosity > 1 {
       
   513 					log.Printf("{%d} The takuzu is correct and complete.", level)
       
   514 				}
       
   515 				solutionsMux.Lock()
       
   516 				singleSolution = &t
       
   517 				if globalSearch {
       
   518 					solutionMap[t.ToString()] = &t
       
   519 				}
       
   520 				solutionsMux.Unlock()
       
   521 
       
   522 				reportStatus(nil)
       
   523 				return nil
       
   524 			}
       
   525 
       
   526 			if verbosity > 2 {
       
   527 				log.Printf("{%d} Trivial resolution did not complete.", level)
       
   528 			}
       
   529 
       
   530 			// Trivial method is stuck, let's use recursion
       
   531 
       
   532 			changed := false
       
   533 
       
   534 			// Looking for first empty cell
       
   535 			var line, col int
       
   536 		firstClear:
       
   537 			for line = 0; line < t.Size; line++ {
       
   538 				for col = 0; col < t.Size; col++ {
       
   539 					if !t.Board[line][col].Defined {
       
   540 						break firstClear
       
   541 					}
       
   542 				}
       
   543 			}
       
   544 
       
   545 			if line == t.Size || col == t.Size {
       
   546 				break
       
   547 			}
       
   548 
       
   549 			if verbosity > 2 {
       
   550 				log.Printf("{%d} GUESS - Trying values for [%d,%d]", level, line, col)
       
   551 			}
       
   552 
       
   553 			var val int
       
   554 			err = nil
       
   555 			errCount := 0
       
   556 
       
   557 			for testval := 0; testval < 2; testval++ {
       
   558 				if !globalSearch && t.Board[line][col].Defined {
       
   559 					// No need to "guess" here anymore
       
   560 					break
       
   561 				}
       
   562 
       
   563 				// Launch goroutines for cell values of 0 and/or 1
       
   564 				for testCase := 0; testCase < 2; testCase++ {
       
   565 					if schrodinger || testval == testCase {
       
   566 						tx := t.Clone()
       
   567 						tx.Set(line, col, testCase)
       
   568 						go recurseSolve(level+1, tx, status[testCase])
       
   569 					}
       
   570 				}
       
   571 
       
   572 				// Let's collect the goroutines' results
       
   573 				for i := 0; i < concurrentRoutines; i++ {
       
   574 					if schrodinger && verbosity > 1 { // XXX
       
   575 						log.Printf("{%d} Schrodinger waiting for result #%d for cell [%d,%d]", level, i, line, col)
       
   576 					}
       
   577 					select {
       
   578 					case e := <-status[0]:
       
   579 						err = e
       
   580 						val = 0
       
   581 					case e := <-status[1]:
       
   582 						err = e
       
   583 						val = 1
       
   584 					}
       
   585 
       
   586 					if schrodinger && verbosity > 1 { // XXX
       
   587 						log.Printf("{%d} Schrodinger result #%d/2 for cell [%d,%d]=%d - err=%v", level, i+1, line, col, val, err)
       
   588 					}
       
   589 
       
   590 					if err == nil {
       
   591 						if !globalSearch {
       
   592 							reportStatus(nil)
       
   593 							if i+1 < concurrentRoutines {
       
   594 								// Schröndinger mode and we still have one status to fetch
       
   595 								<-status[1-val]
       
   596 							}
       
   597 							return nil
       
   598 						}
       
   599 						continue
       
   600 					}
       
   601 					if timeout > 0 && level > 2 && time.Since(startTime) > timeout {
       
   602 						if errors.Cause(err).Error() != "timeout" {
       
   603 							if verbosity > 0 {
       
   604 								log.Printf("{%d} Timeout, giving up", level)
       
   605 							}
       
   606 							err := errors.New("timeout")
       
   607 							reportStatus(err)
       
   608 							if i+1 < concurrentRoutines {
       
   609 								// Schröndinger mode and we still have one status to fetch
       
   610 								<-status[1-val]
       
   611 							}
       
   612 							// XXX actually can't close the channel and leave, can I?
       
   613 							return err
       
   614 						}
       
   615 					}
       
   616 
       
   617 					// err != nil: we can set a value --  unless this was a timeout
       
   618 					if errors.Cause(err).Error() == "timeout" {
       
   619 						if verbosity > 1 {
       
   620 							log.Printf("{%d} Timeout propagation", level)
       
   621 						}
       
   622 						reportStatus(err)
       
   623 						if i+1 < concurrentRoutines {
       
   624 							// Schröndinger mode and we still have one status to fetch
       
   625 							<-status[1-val]
       
   626 						}
       
   627 						// XXX actually can't close the channel and leave, can I?
       
   628 						return err
       
   629 					}
       
   630 
       
   631 					errCount++
       
   632 					if verbosity > 2 {
       
   633 						log.Printf("{%d} Bad outcome (%v)", level, err)
       
   634 						log.Printf("{%d} GUESS was wrong - Setting [%d,%d] to %d",
       
   635 							level, line, col, 1-val)
       
   636 					}
       
   637 
       
   638 					t.Set(line, col, 1-val)
       
   639 					changed = true
       
   640 
       
   641 				} // concurrentRoutines
       
   642 
       
   643 				if (changed && !globalSearch) || schrodinger {
       
   644 					// Let's loop again with the new board
       
   645 					break
       
   646 				}
       
   647 			}
       
   648 
       
   649 			if verbosity > 2 {
       
   650 				log.Printf("{%d} End of cycle.\n\n", level)
       
   651 			}
       
   652 
       
   653 			if errCount == 2 {
       
   654 				// Both values failed
       
   655 				err := errors.New("dead end")
       
   656 				reportStatus(err)
       
   657 				return err
       
   658 			}
       
   659 
       
   660 			// If we cannot fill more cells (!changed) or if we've made a global search with
       
   661 			// both values, the search is complete.
       
   662 			if schrodinger || globalSearch || !changed {
       
   663 				break
       
   664 			}
       
   665 
       
   666 			if verbosity > 2 {
       
   667 				t.DumpBoard()
       
   668 				fmt.Println()
       
   669 			}
       
   670 		}
       
   671 
       
   672 		// Try to force garbage collection
       
   673 		runtime.GC()
       
   674 
       
   675 		full, err := t.Validate()
       
   676 		if err != nil {
       
   677 			if verbosity > 1 {
       
   678 				log.Println("The takuzu looks wrong - ", err)
       
   679 			}
       
   680 			err := errors.Wrap(err, "the takuzu looks wrong")
       
   681 			reportStatus(err)
       
   682 			return err
       
   683 		}
       
   684 		if full {
       
   685 			if verbosity > 1 {
       
   686 				log.Println("The takuzu is correct and complete")
       
   687 			}
       
   688 			solutionsMux.Lock()
       
   689 			singleSolution = &t
       
   690 			if globalSearch {
       
   691 				solutionMap[t.ToString()] = &t
       
   692 			}
       
   693 			solutionsMux.Unlock()
       
   694 		}
       
   695 
       
   696 		reportStatus(nil)
       
   697 		return nil
       
   698 	}
       
   699 
       
   700 	status := make(chan error)
       
   701 	go recurseSolve(0, b, status)
       
   702 
       
   703 	err := <-status // Wait for it...
       
   704 
       
   705 	firstSol := singleSolution
       
   706 	if globalSearch {
       
   707 		for _, tp := range solutionMap {
       
   708 			*allSolutions = append(*allSolutions, *tp)
       
   709 		}
       
   710 	}
       
   711 
       
   712 	if err != nil {
       
   713 		return firstSol, err
       
   714 	}
       
   715 
       
   716 	if globalSearch && len(*allSolutions) > 0 {
       
   717 		firstSol = &(*allSolutions)[0]
       
   718 	}
       
   719 	return firstSol, nil
       
   720 }
       
   721 
       
   722 // SetSchrodingerLevel initializes the "Schrödinger" level (0 means disabled)
       
   723 // It must be called before any board generation or reduction.
       
   724 func SetSchrodingerLevel(level uint) {
       
   725 	schrodLvl = level
       
   726 }
       
   727 
       
   728 // SetVerbosityLevel initializes the verbosity level
       
   729 func SetVerbosityLevel(level int) {
       
   730 	verbosity = level
       
   731 }