build.go
changeset 0 00371339bbcc
child 10 8dc05ff5dbe2
equal deleted inserted replaced
-1:000000000000 0:00371339bbcc
       
     1 package takuzu
       
     2 
       
     3 import (
       
     4 	"fmt"
       
     5 	"log"
       
     6 	"math/rand"
       
     7 	"time"
       
     8 
       
     9 	"github.com/pkg/errors"
       
    10 )
       
    11 
       
    12 func init() {
       
    13 	rand.Seed(time.Now().UTC().UnixNano())
       
    14 }
       
    15 
       
    16 type buildTakuzuOptions struct {
       
    17 	size                                  int
       
    18 	minRatio, maxRatio                    int
       
    19 	simple                                bool
       
    20 	buildBoardTimeout, reduceBoardTimeout time.Duration
       
    21 }
       
    22 
       
    23 // ReduceBoard randomly removes as many numbers as possible from the
       
    24 // takuzu board and returns a pointer to the new board.
       
    25 // The initial takuzu might be modified.
       
    26 func (tak Takuzu) ReduceBoard(trivial bool, wid string, buildBoardTimeout, reduceBoardTimeout time.Duration) (*Takuzu, error) {
       
    27 
       
    28 	size := tak.Size
       
    29 
       
    30 	// First check if the board is correct
       
    31 	if verbosity > 0 {
       
    32 		log.Printf("[%v]ReduceBoard: Checking for all grid solutions...", wid)
       
    33 	}
       
    34 
       
    35 	allSol := &[]Takuzu{}
       
    36 	_, err := tak.Clone().TrySolveRecurse(allSol, buildBoardTimeout)
       
    37 	ns := len(*allSol)
       
    38 	if err != nil && errors.Cause(err).Error() == "timeout" {
       
    39 		if verbosity > 0 {
       
    40 			log.Printf("[%v]ReduceBoard: There was a timeout (%d resolution(s) found).", wid, ns)
       
    41 		}
       
    42 		if ns == 0 {
       
    43 			return nil, err
       
    44 		}
       
    45 		//if ns < 10 { return nil, err }
       
    46 		if verbosity > 0 {
       
    47 			log.Printf("[%v]ReduceBoard: Going on anyway...", wid)
       
    48 		}
       
    49 	}
       
    50 
       
    51 	if verbosity > 0 {
       
    52 		log.Printf("[%v]ReduceBoard: %d solution(s) found.", wid, ns)
       
    53 	}
       
    54 
       
    55 	if ns == 0 {
       
    56 		return nil, err
       
    57 	} else if ns > 1 {
       
    58 		tak = (*allSol)[rand.Intn(ns)]
       
    59 		if verbosity > 0 {
       
    60 			log.Printf("[%v]ReduceBoard: Warning: there are %d solutions.", wid, ns)
       
    61 			log.Printf("[%v]ReduceBoard: Picking one randomly.", wid)
       
    62 
       
    63 			if verbosity > 1 {
       
    64 				tak.DumpBoard()
       
    65 				fmt.Println()
       
    66 			}
       
    67 		}
       
    68 		allSol = nil
       
    69 	} else {
       
    70 		// 1 and only 1 solution
       
    71 		if verbosity > 1 {
       
    72 			tak.DumpBoard()
       
    73 			fmt.Println()
       
    74 		}
       
    75 	}
       
    76 
       
    77 	if verbosity > 0 {
       
    78 		log.Printf("[%v]ReduceBoard: Grid reduction...", wid)
       
    79 	}
       
    80 	fields := make([]*Cell, size*size)
       
    81 	n := 0
       
    82 	for l := range tak.Board {
       
    83 		for c := range tak.Board[l] {
       
    84 			if tak.Board[l][c].Defined {
       
    85 				fields[n] = &tak.Board[l][c]
       
    86 				n++
       
    87 			}
       
    88 		}
       
    89 	}
       
    90 
       
    91 	nDigits := 0
       
    92 	initialDigits := n
       
    93 	ratio := 0
       
    94 	if verbosity > 0 {
       
    95 		log.Printf("[%v]ReduceBoard: %d%%", wid, ratio)
       
    96 	}
       
    97 
       
    98 	for ; n > 0; n-- {
       
    99 		var rollback bool
       
   100 		i := rand.Intn(n)
       
   101 		fields[i].Defined = false
       
   102 		if trivial {
       
   103 			full, err := tak.Clone().TrySolveTrivial()
       
   104 			if err != nil || !full {
       
   105 				rollback = true
       
   106 			}
       
   107 		} else {
       
   108 			allSol = &[]Takuzu{}
       
   109 			_, err := tak.Clone().TrySolveRecurse(allSol, reduceBoardTimeout)
       
   110 			if err != nil || len(*allSol) != 1 {
       
   111 				rollback = true
       
   112 			}
       
   113 		}
       
   114 
       
   115 		if rollback {
       
   116 			if verbosity > 1 {
       
   117 				log.Printf("[%v]ReduceBoard: Backing out", wid)
       
   118 			}
       
   119 			fields[i].Defined = true // Back out!
       
   120 			nDigits++
       
   121 		}
       
   122 		fields = append(fields[:i], fields[i+1:]...)
       
   123 
       
   124 		if verbosity > 0 {
       
   125 			nr := (initialDigits - n) * 100 / initialDigits
       
   126 			if nr > ratio {
       
   127 				ratio = nr
       
   128 				log.Printf("[%v]ReduceBoard: %d%%", wid, ratio)
       
   129 			}
       
   130 		}
       
   131 	}
       
   132 
       
   133 	if verbosity > 0 {
       
   134 		log.Printf("[%v]ReduceBoard: I have left %d digits.", wid, nDigits)
       
   135 	}
       
   136 
       
   137 	return &tak, nil
       
   138 }
       
   139 
       
   140 // newRandomTakuzu creates a new Takuzu board with a given size
       
   141 // It is intended to be called by NewRandomTakuzu only.
       
   142 func newRandomTakuzu(wid string, buildOpts buildTakuzuOptions) (*Takuzu, error) {
       
   143 	size := buildOpts.size
       
   144 	easy := buildOpts.simple
       
   145 	buildBoardTimeout := buildOpts.buildBoardTimeout
       
   146 	reduceBoardTimeout := buildOpts.reduceBoardTimeout
       
   147 	minRatio := buildOpts.minRatio
       
   148 	maxRatio := buildOpts.maxRatio
       
   149 
       
   150 	tak := New(size)
       
   151 	n := size * size
       
   152 	fields := make([]*Cell, n)
       
   153 
       
   154 	i := 0
       
   155 	for l := range tak.Board {
       
   156 		for c := range tak.Board[l] {
       
   157 			fields[i] = &tak.Board[l][c]
       
   158 			i++
       
   159 		}
       
   160 	}
       
   161 
       
   162 	if verbosity > 0 {
       
   163 		log.Printf("[%v]NewRandomTakuzu: Filling new board (%dx%[2]d)...", wid, size)
       
   164 	}
       
   165 
       
   166 	nop := 0
       
   167 
       
   168 	// #1. Loop until the ratio of empty cells is less than minRatio% (e.g. 55%)
       
   169 
       
   170 	for n > size*size*minRatio/100 {
       
   171 		i := rand.Intn(n)
       
   172 		fields[i].Defined = true
       
   173 		fields[i].Value = rand.Intn(2)
       
   174 
       
   175 		var err error
       
   176 
       
   177 		if _, err = tak.Validate(); err != nil {
       
   178 			if verbosity > 1 {
       
   179 				log.Printf("[%v]NewRandomTakuzu: Could not set cell value to %v", wid, fields[i].Value)
       
   180 			}
       
   181 		} else if _, err = tak.Clone().TrySolveTrivial(); err != nil {
       
   182 			if verbosity > 1 {
       
   183 				log.Printf("[%v]NewRandomTakuzu: Trivial checks: Could not set cell value to %v", wid, fields[i].Value)
       
   184 			}
       
   185 		}
       
   186 
       
   187 		if err == nil {
       
   188 			fields = append(fields[:i], fields[i+1:]...)
       
   189 			n--
       
   190 			continue
       
   191 		}
       
   192 
       
   193 		// If any of the above checks fails, we roll back
       
   194 		fields[i].Defined = false
       
   195 		fields[i].Value = 0 // Let's reset but it is useless
       
   196 
       
   197 		// Safety check to avoid deadlock on bad boards
       
   198 		nop++
       
   199 		if nop > 2*size*size {
       
   200 			log.Printf("[%v]NewRandomTakuzu: Could not fill up board!", wid)
       
   201 			// Givin up on this board
       
   202 			return nil, errors.New("could not fill up board") // Try again
       
   203 		}
       
   204 
       
   205 	}
       
   206 
       
   207 	var ptak *Takuzu
       
   208 	var removed int
       
   209 
       
   210 	// #2. Try to solve the current board; try to remove some cells if it fails
       
   211 
       
   212 	// Initial empty cells count
       
   213 	iecc := n
       
   214 
       
   215 	for {
       
   216 		// Current count of empty (i.e. undefined) cells
       
   217 		ec := iecc + removed
       
   218 		ecpc := ec * 100 / (size * size)
       
   219 		if verbosity > 0 {
       
   220 			log.Printf("[%v]NewRandomTakuzu: Empty cells: %d (%d%%)", wid, ec, ecpc)
       
   221 		}
       
   222 		if ecpc > maxRatio {
       
   223 			if verbosity > 0 {
       
   224 				log.Printf("[%v]NewRandomTakuzu: Too many empty cells (%d); giving up on this board", wid, ec)
       
   225 			}
       
   226 			break
       
   227 		}
       
   228 		var err error
       
   229 		ptak, err = tak.ReduceBoard(easy, wid, buildBoardTimeout, reduceBoardTimeout)
       
   230 		if err != nil && errors.Cause(err).Error() == "timeout" {
       
   231 			break
       
   232 		}
       
   233 		if err == nil && ptak != nil {
       
   234 			break
       
   235 		}
       
   236 		if verbosity > 0 {
       
   237 			log.Printf("[%v]NewRandomTakuzu: Could not use this grid", wid)
       
   238 		}
       
   239 		inc := size * size / 150
       
   240 		if inc == 0 {
       
   241 			inc = 1
       
   242 		}
       
   243 		tak.removeRandomCell(inc)
       
   244 		removed += inc
       
   245 		if verbosity > 1 {
       
   246 			log.Printf("[%v]NewRandomTakuzu: Removed %d numbers", wid, removed)
       
   247 			if verbosity > 1 {
       
   248 				tak.DumpBoard()
       
   249 			}
       
   250 		}
       
   251 	}
       
   252 
       
   253 	if ptak == nil {
       
   254 		if verbosity > 0 {
       
   255 			log.Printf("[%v]NewRandomTakuzu: Couldn't use this board, restarting from scratch...", wid)
       
   256 		}
       
   257 		return nil, errors.New("could not use current board") // Try again
       
   258 	}
       
   259 
       
   260 	return ptak, nil
       
   261 }
       
   262 
       
   263 // NewRandomTakuzu creates a new Takuzu board with a given size
       
   264 func NewRandomTakuzu(size int, simple bool, wid string, buildBoardTimeout, reduceBoardTimeout time.Duration, minRatio, maxRatio int) (*Takuzu, error) {
       
   265 	if size%2 != 0 {
       
   266 		return nil, errors.New("board size should be an even value")
       
   267 	}
       
   268 
       
   269 	if size < 4 {
       
   270 		return nil, errors.New("board size is too small")
       
   271 	}
       
   272 
       
   273 	// minRatio : percentage (1-100) of empty cells when creating a new board
       
   274 	// If the board is wrong the cells will be removed until we reach maxRatio
       
   275 
       
   276 	if minRatio < 40 {
       
   277 		minRatio = 40
       
   278 	}
       
   279 	if minRatio > maxRatio {
       
   280 		return nil, errors.New("minRatio/maxRatio incorrect")
       
   281 	}
       
   282 
       
   283 	if maxRatio > 99 {
       
   284 		maxRatio = 99
       
   285 	}
       
   286 
       
   287 	buildOptions := buildTakuzuOptions{
       
   288 		size:               size,
       
   289 		minRatio:           minRatio,
       
   290 		maxRatio:           maxRatio,
       
   291 		simple:             simple,
       
   292 		buildBoardTimeout:  buildBoardTimeout,
       
   293 		reduceBoardTimeout: reduceBoardTimeout,
       
   294 	}
       
   295 
       
   296 	var takP *Takuzu
       
   297 
       
   298 	for {
       
   299 		var err error
       
   300 		takP, err = newRandomTakuzu(wid, buildOptions)
       
   301 		if err == nil {
       
   302 			break
       
   303 		}
       
   304 	}
       
   305 
       
   306 	return takP, nil
       
   307 }
       
   308 
       
   309 func (tak Takuzu) removeRandomCell(number int) {
       
   310 	size := tak.Size
       
   311 	fields := make([]*Cell, size*size)
       
   312 	n := 0
       
   313 	for l := range tak.Board {
       
   314 		for c := range tak.Board[l] {
       
   315 			if tak.Board[l][c].Defined {
       
   316 				fields[n] = &tak.Board[l][c]
       
   317 				n++
       
   318 			}
       
   319 		}
       
   320 	}
       
   321 	for i := 0; i < number; i++ {
       
   322 		if n == 0 {
       
   323 			return
       
   324 		}
       
   325 		fields[rand.Intn(n)].Defined = false
       
   326 		fields = append(fields[:i], fields[i+1:]...)
       
   327 		n--
       
   328 	}
       
   329 }