Skip to content

Instantly share code, notes, and snippets.

@rmmh
Last active April 25, 2021 02:39
Show Gist options
  • Save rmmh/91fe79182dfb47859a8ce02e47e91028 to your computer and use it in GitHub Desktop.
Save rmmh/91fe79182dfb47859a8ce02e47e91028 to your computer and use it in GitHub Desktop.
worm-polyomino counting
// How many distinct closed paths visit each cell in an NxN grid exactly once?
package main
import (
"fmt"
"math/bits"
)
type walls struct {
vert, horiz uint64
}
type openentry struct {
open uint64
boards []*walls
}
var emcount int
var N int
var DEBUG int
func printBoard(open, vert, horiz uint64) {
fmt.Printf("╔")
for i := 0; i < N; i++ {
fmt.Printf("═")
}
fmt.Printf("╗ %x %x %x\n", open, vert, horiz)
for y := 0; y < N; y++ {
fmt.Printf("║")
for x := 0; x < N; x++ {
i := x + y*8
if open&(1<<i) != 0 {
fmt.Printf(" ")
continue
}
box := 0
if vert&(1<<i) != 0 {
box |= 1
}
if y > 0 && vert&(1<<(i-8)) != 0 {
box |= 2
}
if horiz&(1<<i) != 0 {
box |= 4
}
if x > 0 && horiz&(1<<(i-1)) != 0 {
box |= 8
}
fmt.Printf("%c", []rune("•╷╵│╶╭╰7╴╮╯b─def")[box])
}
fmt.Printf("║\n")
}
fmt.Printf("╚")
for i := 0; i < N; i++ {
fmt.Printf("═")
}
fmt.Printf("╝\n")
}
// swap(1, 8); swap(2, 16); swap(3, 24); ... swap(7, 52)
// swap(10, 17)
func transpose(x uint64) uint64 {
// transpose x, as an 8x8 bitarray
// 1) transpose as a 2x2 array of 4x4
// 2) transpose as a 4x4 array of 2x2
// 3) transpose within each 2x2 array
// 01234567
//8+ 01234567
//16+ 01234567
//24+ 01234567
//32+ 01234567
//40+ 01234567
//48+ 01234567
//54+ 01234567
// 01 => 02
// 23 13
x = x&0xf0f0f0f00f0f0f0f | (x&0xf0f0f0f0)<<28 | (x&0x0f0f0f0f00000000)>>28
// 0123 => 0426
// 4567 1537
// 89AB 8CAE
// CDEF 9DBF
x = x&0xcccc3333cccc3333 | (x&0xcccc0000cccc)<<14 | (x&0x3333000033330000)>>14
x = x&0xaa55aa55aa55aa55 | (x&0x00aa00aa00aa00aa)<<7 | (x&0x5500550055005500)>>7
return x
}
func wallLte(a, b walls) bool {
if a.vert != b.vert {
return a.vert <= b.vert
}
return a.horiz <= b.horiz
}
func canonical(vert, horiz uint64) bool {
vertFlipV := bits.ReverseBytes64(vert) >> (64 - 8*(N-1))
horizFlipV := bits.ReverseBytes64(horiz) >> (64 - 8*(N))
vertFlipH := bits.ReverseBytes64(bits.Reverse64(vert)) >> (8 - N)
horizFlipH := bits.ReverseBytes64(bits.Reverse64(horiz)) >> (8 - N + 1)
vertRot180 := bits.ReverseBytes64(vertFlipH) >> (64 - 8*(N-1))
horizRot180 := bits.ReverseBytes64(horizFlipH) >> (64 - 8*(N))
vertRot90 := transpose(horiz)
horizRot90 := transpose(vert)
vertRot270 := transpose(horizRot180)
horizRot270 := transpose(vertRot180)
vertRot90FlipV := bits.ReverseBytes64(vertRot90) >> (64 - 8*(N-1))
horizRot90FlipV := bits.ReverseBytes64(horizRot90) >> (64 - 8*(N))
vertRot90FlipH := bits.ReverseBytes64(bits.Reverse64(vertRot90)) >> (8 - N)
horizRot90FlipH := bits.ReverseBytes64(bits.Reverse64(horizRot90)) >> (8 - N + 1)
if DEBUG > 1 {
fmt.Printf("canonical %b %b flipV: %b %b flipH: %b %b rot90: %b %b\n", vert, horiz, vertFlipV, horizFlipV, vertFlipH, horizFlipH, vertRot90, horizRot90)
printBoard(0, vert, horiz)
printBoard(0, vertFlipV, horizFlipV)
printBoard(0, vertFlipH, horizFlipH)
printBoard(0, vertRot90, horizRot90)
printBoard(0, vertRot180, horizRot180)
printBoard(0, vertRot270, horizRot270)
printBoard(0, vertRot90FlipV, horizRot90FlipV)
printBoard(0, vertRot90FlipH, horizRot90FlipH)
}
return wallLte(walls{vert, horiz}, walls{vertFlipV, horizFlipV}) &&
wallLte(walls{vert, horiz}, walls{vertFlipH, horizFlipH}) &&
wallLte(walls{vert, horiz}, walls{vertRot180, horizRot180}) &&
wallLte(walls{vert, horiz}, walls{vertRot90, horizRot90}) &&
wallLte(walls{vert, horiz}, walls{vertRot270, horizRot270}) &&
wallLte(walls{vert, horiz}, walls{vertRot90FlipV, horizRot90FlipV}) &&
wallLte(walls{vert, horiz}, walls{vertRot90FlipH, horizRot90FlipH})
}
func impossible(open uint64, pos int) bool {
// is the board impossible to complete since it has an isolated hole or the start/end aren't reachable?
group := uint64(1) << bits.TrailingZeros64(open)
last := uint64(0)
for last != group {
last = group
group = open & (group |
(group&0x7f7f7f7f7f7f7f7f)<<1 | (group&0xfefefefefefefefe)>>1 |
group>>8 | group<<8)
}
groupAdjacent := (group |
(group&0x7f7f7f7f7f7f7f7f)<<1 | (group&0xfefefefefefefefe)>>1 |
group>>8 | group<<8)
return group != open || groupAdjacent&1 == 0 || groupAdjacent&(1<<pos) == 0
}
func recur(open, vert, horiz uint64, pos, left int) int {
//fmt.Printf("%32b %16b %16b %d\n", open, vert, horiz, bits.OnesCount64(open))
// printBoard(open, vert, horiz)
if open == 0 {
if pos != 8 {
return 0
}
emcount++
if canonical(vert, horiz) {
if DEBUG > 0 {
fmt.Printf("UNIQUE %b %b\n", vert, horiz) //, vertRev, horiz, horizRev)
printBoard(open, vert, horiz)
}
return 1
}
return 0
}
if impossible(open, pos) {
// printBoard(open, vert, horiz)
return 0
}
val := 0
if pos >= N && open&(1<<(pos-8)) != 0 {
val += recur(open&^(1<<(pos-8)), vert|(1<<(pos-8)), horiz, pos-8, left-1)
}
if open&(1<<(pos+8)) != 0 {
val += recur(open&^(1<<(pos+8)), vert|(1<<(pos)), horiz, pos+8, left-1)
}
if (pos%8) < 7 && open&(1<<(pos+1)) != 0 {
val += recur(open&^(1<<(pos+1)), vert, horiz|(1<<(pos)), pos+1, left-1)
}
if (pos%8) > 0 && open&(1<<(pos-1)) != 0 {
val += recur(open&^(1<<(pos-1)), vert, horiz|(1<<(pos-1)), pos-1, left-1)
}
return val
}
func main() {
DEBUG = 1
for N = 1; N <= 8; N++ {
emcount = 0
var open uint64 = (1 << N) - 1
for i := 1; i < N; i++ {
open |= open << 8
}
count := recur(open&^3, 1, 1, 1, N*N-2) // start with a corner
fmt.Printf("###N=%d %b has %d distinct solutions (%d total)\n", N, open, count, emcount)
}
}
package main
import (
"fmt"
"math/bits"
"sort"
)
type walls struct {
vert, horiz uint64
}
type openentry struct {
open uint64
boards []*walls
}
var emcount int
var N int
var DEBUG int
func printBoard(open, vert, horiz uint64) {
fmt.Printf("╔")
for i := 0; i < N; i++ {
fmt.Printf("═")
}
fmt.Printf("╗ %x %x %x\n", open, vert, horiz)
for y := 0; y < N; y++ {
fmt.Printf("║")
for x := 0; x < N; x++ {
i := x + y*8
if open&(1<<i) != 0 {
fmt.Printf(" ")
continue
}
box := 0
if vert&(1<<i) != 0 {
box |= 1
}
if y > 0 && vert&(1<<(i-8)) != 0 {
box |= 2
}
if horiz&(1<<i) != 0 {
box |= 4
}
if x > 0 && horiz&(1<<(i-1)) != 0 {
box |= 8
}
fmt.Printf("%c", []rune("•╷╵│╶╭╰7╴╮╯b─def")[box])
}
fmt.Printf("║\n")
}
fmt.Printf("╚")
for i := 0; i < N; i++ {
fmt.Printf("═")
}
fmt.Printf("╝\n")
}
// swap(1, 8); swap(2, 16); swap(3, 24); ... swap(7, 52)
// swap(10, 17)
func transpose(x uint64) uint64 {
// transpose x, as an 8x8 bitarray
// 1) transpose as a 2x2 array of 4x4
// 2) transpose as a 4x4 array of 2x2
// 3) transpose within each 2x2 array
// 01234567
//8+ 01234567
//16+ 01234567
//24+ 01234567
//32+ 01234567
//40+ 01234567
//48+ 01234567
//54+ 01234567
// 01 => 02
// 23 13
x = x&0xf0f0f0f00f0f0f0f | (x&0xf0f0f0f0)<<28 | (x&0x0f0f0f0f00000000)>>28
// 0123 => 0426
// 4567 1537
// 89AB 8CAE
// CDEF 9DBF
x = x&0xcccc3333cccc3333 | (x&0xcccc0000cccc)<<14 | (x&0x3333000033330000)>>14
x = x&0xaa55aa55aa55aa55 | (x&0x00aa00aa00aa00aa)<<7 | (x&0x5500550055005500)>>7
return x
}
func wallLte(a, b walls) bool {
if a.vert != b.vert {
return a.vert <= b.vert
}
return a.horiz <= b.horiz
}
func canonical(vert, horiz uint64) bool {
vertFlipV := bits.ReverseBytes64(vert) >> (64 - 8*(N-1))
horizFlipV := bits.ReverseBytes64(horiz) >> (64 - 8*(N))
vertFlipH := bits.ReverseBytes64(bits.Reverse64(vert)) >> (8 - N)
horizFlipH := bits.ReverseBytes64(bits.Reverse64(horiz)) >> (8 - N + 1)
vertRot180 := bits.ReverseBytes64(vertFlipH) >> (64 - 8*(N-1))
horizRot180 := bits.ReverseBytes64(horizFlipH) >> (64 - 8*(N))
vertRot90 := transpose(horiz)
horizRot90 := transpose(vert)
vertRot270 := transpose(horizRot180)
horizRot270 := transpose(vertRot180)
vertRot90FlipV := bits.ReverseBytes64(vertRot90) >> (64 - 8*(N-1))
horizRot90FlipV := bits.ReverseBytes64(horizRot90) >> (64 - 8*(N))
vertRot90FlipH := bits.ReverseBytes64(bits.Reverse64(vertRot90)) >> (8 - N)
horizRot90FlipH := bits.ReverseBytes64(bits.Reverse64(horizRot90)) >> (8 - N + 1)
if DEBUG > 1 {
fmt.Printf("canonical %b %b flipV: %b %b flipH: %b %b rot90: %b %b\n", vert, horiz, vertFlipV, horizFlipV, vertFlipH, horizFlipH, vertRot90, horizRot90)
printBoard(0, vert, horiz)
printBoard(0, vertFlipV, horizFlipV)
printBoard(0, vertFlipH, horizFlipH)
printBoard(0, vertRot90, horizRot90)
printBoard(0, vertRot180, horizRot180)
printBoard(0, vertRot270, horizRot270)
printBoard(0, vertRot90FlipV, horizRot90FlipV)
printBoard(0, vertRot90FlipH, horizRot90FlipH)
}
return wallLte(walls{vert, horiz}, walls{vertFlipV, horizFlipV}) &&
wallLte(walls{vert, horiz}, walls{vertFlipH, horizFlipH}) &&
wallLte(walls{vert, horiz}, walls{vertRot180, horizRot180}) &&
wallLte(walls{vert, horiz}, walls{vertRot90, horizRot90}) &&
wallLte(walls{vert, horiz}, walls{vertRot270, horizRot270}) &&
wallLte(walls{vert, horiz}, walls{vertRot90FlipV, horizRot90FlipV}) &&
wallLte(walls{vert, horiz}, walls{vertRot90FlipH, horizRot90FlipH})
}
func impossible(open uint64) bool {
// is the board impossible to complete since it has an isolated hole of the wrong size?
for open != 0 {
group := uint64(1) << bits.TrailingZeros64(open)
last := uint64(0)
for last != group {
last = group
group = open & (group |
(group&0x7f7f7f7f7f7f7f7f)<<1 | (group&0xfefefefefefefefe)>>1 |
group>>8 | group<<8)
}
if bits.OnesCount64(group)%N != 0 {
return true
}
open &^= group
}
return false
}
func recur(open, vert, horiz uint64) int {
// fmt.Printf("%32b %16b %16b %d\n", open, vert, horiz, bits.OnesCount64(open))
// printBoard(open, vert, horiz)
val := 0
if open == 0 {
emcount++
if canonical(vert, horiz) {
if DEBUG > 0 {
fmt.Printf("UNIQUE %b %b\n", vert, horiz) //, vertRev, horiz, horizRev)
printBoard(open, vert, horiz)
}
return 1
}
return 0
}
if impossible(open) {
// printBoard(open, vert, horiz)
return 0
}
target := bits.TrailingZeros64(open)
for _, w := range tiles[target] {
if w.cells&open == w.cells {
val += recur(open&^w.cells, vert|w.vert, horiz|w.horiz)
}
}
return val
}
type tileshape struct{ cells, vert, horiz uint64 }
var tiles = [64][]tileshape{}
func worm(fullopen, open, vert, horiz uint64, pos, left int) {
if left == -1 {
for i := 0; i < 64; i++ {
if fullopen&(1<<i) != 0 {
worm(fullopen, open&^(1<<i), 0, 0, i, N-1)
}
}
} else if left == 0 {
cells := fullopen ^ open
for _, w := range tiles[bits.TrailingZeros64(cells)] {
if cells == w.cells && vert == w.vert && horiz == w.horiz {
return
}
}
for i := 0; i < 64; i++ {
if cells&(1<<i) != 0 {
tiles[i] = append(tiles[i], tileshape{cells, vert, horiz})
}
}
} else {
if pos >= N && open&(1<<(pos-8)) != 0 {
worm(fullopen, open&^(1<<(pos-8)), vert|(1<<(pos-8)), horiz, pos-8, left-1)
}
if open&(1<<(pos+8)) != 0 {
worm(fullopen, open&^(1<<(pos+8)), vert|(1<<(pos)), horiz, pos+8, left-1)
}
if (pos%8) < 7 && open&(1<<(pos+1)) != 0 {
worm(fullopen, open&^(1<<(pos+1)), vert, horiz|(1<<(pos)), pos+1, left-1)
}
if (pos%8) > 0 && open&(1<<(pos-1)) != 0 {
worm(fullopen, open&^(1<<(pos-1)), vert, horiz|(1<<(pos-1)), pos-1, left-1)
}
}
}
func main() {
DEBUG = 1
for N = 1; N <= 8; N++ {
tiles = [64][]tileshape{}
emcount = 0
var open uint64 = (1 << N) - 1
for i := 1; i < N; i++ {
open |= open << 8
}
worm(open, open, 0, 0, 0, -1)
count := recur(open, 0, 0)
emstack := []walls{}
sort.Slice(emstack, func(i, j int) bool {
a := emstack[i]
b := emstack[j]
if a.vert != b.vert {
return a.vert < b.vert
}
return a.horiz < b.horiz
})
fmt.Printf("###N=%d %b has %d distinct solutions (%d total)\n", N, open, count, emcount)
if false {
for _, wall := range emstack {
printBoard(0, wall.vert, wall.horiz)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment