From 75cded24b0b1170dd8cc1f624591c0113fa4d5ec Mon Sep 17 00:00:00 2001 From: dean Date: Sun, 12 Sep 2021 15:45:40 -0700 Subject: [PATCH] add solveall, fix tons of bugs --- gameutil.go | 5 ++-- rules.go | 4 +-- solve/crown.go | 34 +++++++++++++++++++++-- solve/dots.go | 7 +++++ solve/goals.go | 12 ++++++-- solve/join.go | 63 ++++++++++++++++++++++++++++++++++++++---- solve/path.go | 3 +- solve/path_test.go | 26 +++++++---------- solve/shapes.go | 12 +++++--- solve/solveall.go | 42 ++++++++++++++++++++++++++++ solve/solveall_test.go | 30 ++++++++++++++++++++ temp/main.go | 21 ++++++++++---- 12 files changed, 217 insertions(+), 42 deletions(-) create mode 100644 solve/solveall.go create mode 100644 solve/solveall_test.go diff --git a/gameutil.go b/gameutil.go index 4ede9f1..820adf2 100644 --- a/gameutil.go +++ b/gameutil.go @@ -260,14 +260,15 @@ func (g Grid) BlobWith(coord TileCoord, filter func(o Tile) bool) TileSet { } func (g Grid) blobRecur(coord TileCoord, ts *TileSet, filter func(o Tile) bool) { - t := g.TileAtCoord(coord) + t := *g.TileAtCoord(coord) + ts.Add(t) + neighbors := g.NeighborSetWith(coord, func(o Tile) bool { return o.Data.Color == t.Data.Color && filter(o) }) for _, neighbor := range neighbors.Slice() { if !ts.Has(neighbor) { - ts.Add(neighbor) g.blobRecur(neighbor.Coord, ts, filter) } } diff --git a/rules.go b/rules.go index 017b09c..7317f6b 100644 --- a/rules.go +++ b/rules.go @@ -118,10 +118,10 @@ func (g Grid) validJoin(t Tile, n int) bool { for _, blobTile := range g.Blob(t.Coord).Slice() { if blobTile.Data.Type != TypeHole && blobTile.Data.Type != TypeBlank { found++ - if found > n { + if found > n+1 { return false } } } - return found == n + return found == n+1 } diff --git a/solve/crown.go b/solve/crown.go index 8eb7947..6e88460 100644 --- a/solve/crown.go +++ b/solve/crown.go @@ -12,6 +12,13 @@ func (g GridSolver) SolveCrowns() <-chan gs.TileSet { return o.Data.Type == gs.TypeCrown }).Slice() + if len(crownTiles) == 0 { + ch := make(chan gs.TileSet, 1) + ch <- gs.NewTileSet() + close(ch) + return ch + } + tilesToSolutions := make([]<-chan gs.TileSet, len(crownTiles)) for i, tile := range crownTiles { tilesToSolutions[i] = g.SolveCrown(tile.Coord) @@ -55,11 +62,32 @@ func (g GridSolver) SolveCrown(crown gs.TileCoord) <-chan gs.TileSet { // prune if: // - this shape contains a separate crown of the same color -// - this list used to be longer lmao +// - if there is a goal tile, the shape must be a path func shouldPruneCrown(g GridSolver, crown gs.TileCoord, shape gs.TileSet, color gs.TileColor) bool { + shapeAsSlice := shape.Slice() + shapeCoords := shape.ToTileCoordSet() + + var containsGoalTile bool + var containsTrineighborTile bool + for _, tile := range shapeAsSlice { + if tile.Data.Type == gs.TypeCrown && tile.Coord != crown { + return true + } + + sameColorNeighborsInShape := g.Grid.NeighborSetWith(tile.Coord, func(o gs.Tile) bool { + return shapeCoords.Has(o.Coord) + }) + if sameColorNeighborsInShape.Len() > 2 { + containsTrineighborTile = true + } + if tile.Data.Type == gs.TypeGoal { + if sameColorNeighborsInShape.Len() > 1 { + return true + } + containsGoalTile = true + } - for _, tile := range shape.Slice() { - if tile.Data.Type == gs.TypeCrown && tile.Data.Color == color && tile.Coord != crown { + if containsGoalTile && containsTrineighborTile { return true } } diff --git a/solve/dots.go b/solve/dots.go index 0592d66..05fca74 100644 --- a/solve/dots.go +++ b/solve/dots.go @@ -15,6 +15,13 @@ func (g GridSolver) SolveDots() <-chan gs.TileSet { return o.Data.Type == gridspech.TypeDot1 || o.Data.Type == gridspech.TypeDot2 || o.Data.Type == gridspech.TypeDot3 }).Slice() + if len(dotTiles) == 0 { + ch := make(chan gs.TileSet, 1) + ch <- gs.NewTileSet() + close(ch) + return ch + } + tilesToSolutions := make([]<-chan gs.TileSet, len(dotTiles)) for i, tile := range dotTiles { tilesToSolutions[i] = g.SolveDot(tile) diff --git a/solve/goals.go b/solve/goals.go index 56a9461..3be4416 100644 --- a/solve/goals.go +++ b/solve/goals.go @@ -12,8 +12,8 @@ func (g GridSolver) SolveGoals() <-chan gs.TileSet { iter := make(chan gs.TileSet, 4) go func() { + defer close(iter) g.solveGoals(iter) - close(iter) }() return iter @@ -23,6 +23,12 @@ func (g GridSolver) solveGoals(ch chan<- gs.TileSet) { goalTiles := g.Grid.TilesWith(func(o gs.Tile) bool { return o.Data.Type == gs.TypeGoal }).Slice() + + if len(goalTiles) == 0 { + ch <- gs.NewTileSet() + return + } + goalTileCoords := make([]gs.TileCoord, len(goalTiles)) for i := range goalTiles { goalTileCoords[i] = goalTiles[i].Coord @@ -39,7 +45,9 @@ func (g GridSolver) solveGoals(ch chan<- gs.TileSet) { for c := 0; c < g.Grid.MaxColors; c++ { for path := range g.PathsIter(goalPairCoords[0], goalPairCoords[1], gs.TileColor(c)) { pairsToSolutionMx.Lock() - pairsToSolutions[goalPairCoords] = append(pairsToSolutions[goalPairCoords], path) + for decorated := range decorateSetBorder(g, gs.TileColor(c), path) { + pairsToSolutions[goalPairCoords] = append(pairsToSolutions[goalPairCoords], decorated) + } pairsToSolutionMx.Unlock() } } diff --git a/solve/join.go b/solve/join.go index 2d35d88..6e30757 100644 --- a/solve/join.go +++ b/solve/join.go @@ -10,6 +10,13 @@ func (g GridSolver) SolveJoins() <-chan gs.TileSet { return o.Data.Type == gs.TypeJoin1 || o.Data.Type == gs.TypeJoin2 }).Slice() + if len(joinTiles) == 0 { + ch := make(chan gs.TileSet, 1) + ch <- gs.NewTileSet() + close(ch) + return ch + } + tilesToSolutions := make([]<-chan gs.TileSet, len(joinTiles)) for i, tile := range joinTiles { tilesToSolutions[i] = g.SolveJoin(tile) @@ -42,13 +49,15 @@ func (g GridSolver) SolveJoin(join gs.Tile) <-chan gs.TileSet { } for c := 0; c < g.Grid.MaxColors; c++ { - shapeCh, pruneCh := g.ShapesIter(join.Coord, gs.TileColor(c)) + color := gs.TileColor(c) + shapeCh, pruneCh := g.ShapesIter(join.Coord, color) for shape := range shapeCh { - specialTiles := numSpecialTiles(g, shape, joinNum) - pruneCh <- (specialTiles > joinNum+1) + prune := shouldPruneJoin(g, shape, color, joinNum) + pruneCh <- prune - if specialTiles == joinNum+1 { - for decorated := range decorateSetBorder(g, gs.TileColor(c), shape) { + specialTiles := numSpecialTiles(g, shape, joinNum) + if !prune && specialTiles == joinNum+1 { + for decorated := range decorateSetBorder(g, color, shape) { joinIter <- decorated } } @@ -59,6 +68,50 @@ func (g GridSolver) SolveJoin(join gs.Tile) <-chan gs.TileSet { return joinIter } +// trim if: +// - too many special tiles in the shape +// - if the shape contains a goal tile, it must be a path +// - if joinNum is 1, it cannot contain a goal tile +func shouldPruneJoin(g GridSolver, shape gs.TileSet, color gs.TileColor, joinNum int) bool { + shapeAsSlice := shape.Slice() + shapeCoords := shape.ToTileCoordSet() + + var containsGoalTile bool + var containsTrineighborTile bool + var specialTiles int + for _, tile := range shapeAsSlice { + + if tile.Data.Type != gs.TypeBlank { + specialTiles++ + if specialTiles > joinNum+1 { + return true + } + } + + sameColorNeighborsInShape := g.Grid.NeighborSetWith(tile.Coord, func(o gs.Tile) bool { + return shapeCoords.Has(o.Coord) + }) + if sameColorNeighborsInShape.Len() > 2 { + containsTrineighborTile = true + } + if tile.Data.Type == gs.TypeGoal { + if joinNum == 1 { + return true + } + if sameColorNeighborsInShape.Len() > 1 { + return true + } + containsGoalTile = true + } + + if containsGoalTile && containsTrineighborTile { + return true + } + } + + return false +} + func numSpecialTiles(g GridSolver, shape gs.TileSet, joinNum int) int { var numSpecialTiles int for _, tile := range shape.Slice() { diff --git a/solve/path.go b/solve/path.go index c835952..9f93c9a 100644 --- a/solve/path.go +++ b/solve/path.go @@ -23,8 +23,7 @@ func (g GridSolver) PathsIter(start, end gs.TileCoord, color gs.TileColor) <-cha g.dfsDirectPaths(color, startTile, endTile, gs.NewTileCoordSet(start), pathIter) }() - withBorderIter := decorateSetIterBorders(g, color, pathIter) - return withBorderIter + return pathIter } // we do not iterate in any particular order since it does not matter. diff --git a/solve/path_test.go b/solve/path_test.go index a053a10..52289e5 100644 --- a/solve/path_test.go +++ b/solve/path_test.go @@ -43,31 +43,31 @@ func TestPathsIter_levelA2(t *testing.T) { func TestPathsIter_levelA3(t *testing.T) { const level = example.LevelA3 solutions := []string{ - " 111 |11 11| 0 0 ", - " 0 0 |11 11| 111 ", + " 111 |11 11| ", + " |11 11| 111 ", } testPathsIterAbstract(t, level, 0, 1, 4, 1, solutions) } func TestPathsIter_levelA4(t *testing.T) { const level = example.LevelA4 - solutions := []string{"101|111"} + solutions := []string{"1 1|111"} testPathsIterAbstract(t, level, 0, 1, 2, 1, solutions) } func TestPathsIter_levelA5(t *testing.T) { const level = example.LevelA5 - solutions := []string{"1110|1011|10 1"} + solutions := []string{"111 |1 11|1 1"} testPathsIterAbstract(t, level, 0, 0, 3, 0, solutions) } func TestPathsIter_levelA6(t *testing.T) { const level = example.LevelA6 solutions := []string{ - "11101110|10101011|10111001", - "11101110|10101010|10111011", - "00001110|11101011|10111001", - "00001110|11101010|10111011", + "111 111 |1 1 1 11|1 111 1", + "111 111 |1 1 1 1 |1 111 11", + " 111 |111 1 11|1 111 1", + " 111 |111 1 1 |1 111 11", } testPathsIterAbstract(t, level, 0, 0, 7, 0, solutions) } @@ -75,8 +75,8 @@ func TestPathsIter_levelA6(t *testing.T) { func TestPathsIter_levelA9(t *testing.T) { const level = example.LevelA9 solutions := []string{ - "0 01111|1011001|1110 0", - "0 01110|1011011|1110 00", + " 1111|1 11 1|111 ", + " 111 |1 11 11|111 ", } testPathsIterAbstract(t, level, 0, 1, 6, 1, solutions) } @@ -98,16 +98,10 @@ func TestPathsIter_basicColorNonePath(t *testing.T) { t.Fatalf("solutions length expected to be 1 but was %d", len(solutions)) } expected := gs.NewTileSet( - gs.Tile{Coord: gs.TileCoord{X: 1, Y: 2}, Data: gs.TileData{Color: 1, Type: gs.TypeBlank}}, - gs.Tile{Coord: gs.TileCoord{X: 2, Y: 2}, Data: gs.TileData{Color: 1, Type: gs.TypeBlank}}, - gs.Tile{Coord: gs.TileCoord{X: 0, Y: 1}, Data: gs.TileData{Sticky: true, Type: gs.TypeGoal}}, gs.Tile{Coord: gs.TileCoord{X: 1, Y: 1}, Data: gs.TileData{Type: gs.TypeBlank}}, gs.Tile{Coord: gs.TileCoord{X: 2, Y: 1}, Data: gs.TileData{Type: gs.TypeBlank}}, gs.Tile{Coord: gs.TileCoord{X: 3, Y: 1}, Data: gs.TileData{Type: gs.TypeGoal}}, - - gs.Tile{Coord: gs.TileCoord{X: 1, Y: 0}, Data: gs.TileData{Color: 1, Type: gs.TypeBlank}}, - gs.Tile{Coord: gs.TileCoord{X: 2, Y: 0}, Data: gs.TileData{Color: 1, Type: gs.TypeBlank}}, ) if !expected.Eq(solutions[0]) { diff --git a/solve/shapes.go b/solve/shapes.go index 3ec36e1..c4821da 100644 --- a/solve/shapes.go +++ b/solve/shapes.go @@ -45,6 +45,10 @@ func (g GridSolver) ShapesIter(start gs.TileCoord, color gs.TileColor) (<-chan g defer close(solutionsChan) defer close(pruneChan) + if !g.UnknownTiles.Has(start) && g.Grid.TileAtCoord(start).Data.Color != color { + return + } + g.bfsShapes(start, color, solutionsChan, pruneChan) }() @@ -95,11 +99,11 @@ func (g GridSolver) bfsShapes(start gs.TileCoord, color gs.TileColor, solutions newShape.Add(nextNeighbor) // special behavior: if nextNeighbor has any neighbors which we know are the same color, - // add the blob of each of those neighbors to ne`wShape - transitiveNeighbors := g.Grid.NeighborSetWith(nextNeighbor, func(o gs.Tile) bool { - return o.Data.Color == color && !g.UnknownTiles.Has(o.Coord) + // add the blob of each of those neighbors to newShape + nextNextNeighbors := g.Grid.NeighborSetWith(nextNeighbor, func(o gs.Tile) bool { + return o.Data.Color == color && !g.UnknownTiles.Has(o.Coord) && !newShape.Has(o.Coord) }) - for _, transitiveNeighbor := range transitiveNeighbors.Slice() { + for _, transitiveNeighbor := range nextNextNeighbors.Slice() { neighborBlob := g.Grid.BlobWith(transitiveNeighbor.Coord, func(o gs.Tile) bool { return !g.UnknownTiles.Has(o.Coord) }) diff --git a/solve/solveall.go b/solve/solveall.go new file mode 100644 index 0000000..c6193be --- /dev/null +++ b/solve/solveall.go @@ -0,0 +1,42 @@ +package solve + +import ( + gs "github.com/deanveloper/gridspech-go" +) + +// SolveAllTiles returns a channel which will return a TileSet of all tiles in g. +func (g GridSolver) SolveAllTiles() <-chan gs.TileSet { + solutionIter := make(chan gs.TileSet) + + go func() { + defer close(solutionIter) + + for goalsAndDots := range mergeSolutionsIters(g.SolveGoals(), g.SolveDots()) { + newGrid := g.Clone() + newGrid.Grid.ApplyTileSet(goalsAndDots) + newGrid.UnknownTiles.RemoveAll(goalsAndDots.ToTileCoordSet()) + + for joinsSolution := range newGrid.SolveJoins() { + joinsSolved := newGrid.Clone() + joinsSolved.Grid.ApplyTileSet(joinsSolution) + joinsSolved.UnknownTiles.RemoveAll(joinsSolution.ToTileCoordSet()) + + for crownsSolution := range joinsSolved.SolveCrowns() { + crownsSolved := joinsSolved.Clone() + crownsSolved.Grid.ApplyTileSet(crownsSolution) + crownsSolved.UnknownTiles.RemoveAll(crownsSolution.ToTileCoordSet()) + + if crownsSolved.Grid.Valid() { + var merged gs.TileSet + merged.Merge(goalsAndDots) + merged.Merge(joinsSolution) + merged.Merge(crownsSolution) + solutionIter <- merged + } + } + } + } + }() + + return solutionIter +} diff --git a/solve/solveall_test.go b/solve/solveall_test.go new file mode 100644 index 0000000..c075196 --- /dev/null +++ b/solve/solveall_test.go @@ -0,0 +1,30 @@ +package solve_test + +import ( + "testing" + + "github.com/deanveloper/gridspech-go/solve" +) + +func testSolveAllTilesAbstract(t *testing.T, level string, expectedSolutionsStrings []string, maxColors int) { + t.Helper() + + testSolveAbstract(t, level, expectedSolutionsStrings, maxColors, solve.GridSolver.SolveAllTiles) +} + +func TestSolveAllTiles_levelF10(t *testing.T) { + const level = ` + 0 0 0 0 0 0 + 0 0e 0k 0 0 0 + 0 0 0k 0 0 0 + 0 0 0k 0 0 0 + 0 0 0k 0e 0 0 + 0j1 0e 0 0 0j1 0e + ` + solutions := []string{ + "111111|100001|111100|100010|101010|101110", + "000000|011110|000011|011101|010101|010001", + } + + testSolveAllTilesAbstract(t, level, solutions, 2) +} diff --git a/temp/main.go b/temp/main.go index 403e061..6e2e2f7 100644 --- a/temp/main.go +++ b/temp/main.go @@ -43,12 +43,21 @@ func tryE8() { } func tryTest() { - const lvl = `0 0 0k` - grid := gridspech.MakeGridFromString(lvl, 3) - ch := solve.NewGridSolver(grid).SolveCrowns() - for solvedGrid := range ch { - fmt.Println(solvedGrid.MultiLineString()) - fmt.Println("=============") + const lvl = ` + 0 0 0 0 0 0 + 0 0e 0k 0 0 0 + 0 0 0k 0 0 0 + 0 0 0k 0 0 0 + 0 0 0k 0e 0 0 + 0j1 0e 0 0 0j1 0e + ` + grid := gridspech.MakeGridFromString(lvl, 2) + ch := solve.NewGridSolver(grid).SolveAllTiles() + for solved := range ch { + newGrid := grid.Clone() + newGrid.ApplyTileSet(solved) + fmt.Println("======") + fmt.Println(newGrid) } }