Skip to content

Commit

Permalink
add solveall, fix tons of bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
deanveloper committed Sep 12, 2021
1 parent 08b59dc commit 75cded2
Show file tree
Hide file tree
Showing 12 changed files with 217 additions and 42 deletions.
5 changes: 3 additions & 2 deletions gameutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
4 changes: 2 additions & 2 deletions rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
34 changes: 31 additions & 3 deletions solve/crown.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
}
Expand Down
7 changes: 7 additions & 0 deletions solve/dots.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 10 additions & 2 deletions solve/goals.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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()
}
}
Expand Down
63 changes: 58 additions & 5 deletions solve/join.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
}
Expand All @@ -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() {
Expand Down
3 changes: 1 addition & 2 deletions solve/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
26 changes: 10 additions & 16 deletions solve/path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,40 +43,40 @@ 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)
}

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)
}
Expand All @@ -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]) {
Expand Down
12 changes: 8 additions & 4 deletions solve/shapes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}()

Expand Down Expand Up @@ -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)
})
Expand Down
42 changes: 42 additions & 0 deletions solve/solveall.go
Original file line number Diff line number Diff line change
@@ -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
}
30 changes: 30 additions & 0 deletions solve/solveall_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit 75cded2

Please sign in to comment.