数独のソルバーを深さ優先で再帰で作ったからそのノリで作ってみました。3x3、4x4、5x5 と一応対応しております。
シャッフルして問題も供給する形にしました。
package main import ( "fmt" "math" "math/rand" "slices" "strings" "time" ) const goal = "123456780" // const goal = "123456789abcdef0" // const goal = "123456789abcdefghijklmno0" func main() { // fmt.Println(len(goal)) // パズルをシャッフルする回数 var count = 7 q := repeatShuffle(count) size := sqrtInt(len(goal)) fmt.Println("shuffle:", count) start := time.Now() // パズルを深さ優先で検索するときの深さの初期設定 count = 10 fmt.Println("count:", count) l: checkSlice := []string{q} checkSlice, ok := calc(checkSlice, 1, count) if !ok { count += 10 fmt.Println("count:", count) goto l } else { t := time.Since(start) fmt.Println("q.") for i := range size { fmt.Println("q", q[i*size:(i+1)*size]) } fmt.Println("\nans.") for i, c := range checkSlice { for j := range size { fmt.Println(i, c[j*size:(j+1)*size]) } fmt.Println() } fmt.Println(t) } } func sqrtInt(i int) int { return int(math.Sqrt(float64(i))) } func calc(checkSlice []string, l int, count int) ([]string, bool) { size := sqrtInt(len(goal)) if l > count { return checkSlice, false } q := strings.Split(checkSlice[len(checkSlice)-1], "") index := slices.Index(q, "0") rows, columns := index/size, index%size // 0を動かす方向 上右下左 0123 // 移動できる方向のスライスを作る movementSlice := []int{} if rows == 0 { movementSlice = append(movementSlice, 2) } else if rows == size-1 { movementSlice = append(movementSlice, 0) } else { movementSlice = append(movementSlice, 0, 2) } if columns == 0 { movementSlice = append(movementSlice, 1) } else if columns == size-1 { movementSlice = append(movementSlice, 3) } else { movementSlice = append(movementSlice, 1, 3) } for _, move := range movementSlice { if len(checkSlice) > l { checkSlice = checkSlice[:l] } after := -1 // 0を動かす方向 上右下左 0123 if move == 0 { after = index - size } else if move == 1 { after = index + 1 } else if move == 2 { after = index + size } else if move == 3 { after = index - 1 } q[index] = q[after] q[after] = "0" s := strings.Join(q, "") if slices.Contains(checkSlice, s) { q[after] = q[index] q[index] = "0" continue } checkSlice = append(checkSlice, s) if s == goal { return checkSlice, true } checkSlice, ok := calc(checkSlice, l+1, count) if ok { return checkSlice, true } q[after] = q[index] q[index] = "0" } return checkSlice, false } func repeatShuffle(count int) string { q := strings.Split(goal, "") prev := 0 for range count { q, prev = shuffle(q, prev) } return strings.Join(q, "") } func shuffle(s []string, prev int) ([]string, int) { size := sqrtInt(len(goal)) index := slices.Index(s, "0") rows, columns := index/size, index%size // 0を動かす方向 上右下左 0123 // 移動できる方向のスライスを作る。 movementSlice := []int{} if rows == 0 { movementSlice = append(movementSlice, 2) } else if rows == size-1 { movementSlice = append(movementSlice, 0) } else { movementSlice = append(movementSlice, 0, 2) } if columns == 0 { movementSlice = append(movementSlice, 1) } else if columns == size-1 { movementSlice = append(movementSlice, 3) } else { movementSlice = append(movementSlice, 1, 3) } // 来た方向に戻さない i := (prev + 2) % 4 ii := slices.Index(movementSlice, i) if ii > -1 { movementSlice = slices.Delete(movementSlice, ii, ii+1) } // 動く方向を決める move := movementSlice[rand.Intn((len(movementSlice)))] after := -1 // 0を動かす方向 上右下左 0123 if move == 0 { after = index - size } else if move == 1 { after = index + 1 } else if move == 2 { after = index + size } else if move == 3 { after = index - 1 } s[index] = s[after] s[after] = "0" return s, move }
表示は
shuffle: 7 count: 10 q. q 412 q 753 q 806 ans. 0 412 0 753 0 806 1 412 1 753 1 086 2 412 2 053 2 786 3 012 3 453 3 786 4 102 4 453 4 786 5 120 5 453 5 786 6 123 6 450 6 786 7 123 7 456 7 780 556.5µs
こんな感じで。
試しにやってみたスマホアプリのくじ付きスライドパズルは僕の環境だと3x3は時間内に回答が出て入力できるけれど、4x4は時間内に回答が出ませんでした。