Rustで騎士の周遊(Closed knight's tour)

エイトクイーンやったら次はこれでしょ。出発地点に戻ってくるので騎士の巡回ではなく周遊というらしい。BOARD_SIZE=6なら解が求められるけど、8では返ってこない。ロジックを少し変えないとダメ。それでもBOARD_SIZE=6ならJava版の2倍の速さで全パターン見つけ出す。

use std::time::Instant;

// チェス盤の大きさ
const BOARD_SIZE: i32 = 6;

// 次の配置場所計算用
const NEXT_X: [i32; 8] = [1, 2, 2, 1, -1, -2, -2, -1];
const NEXT_Y: [i32; 8] = [-2, -1, 1, 2, 2, 1, -1, -2];

// 見つけた解の数
static mut NUM_OF_ANSWER: i32 = 0;

fn main() {
    // チェス盤
    let mut board = [[0 as i32; BOARD_SIZE as usize]; BOARD_SIZE as usize];
    let st = Instant::now();
    board[0][0] = 1;
    tour(0, 0, 1, &mut board);

    let et = Instant::now();
    unsafe {
        println!("num of path={}, elapsed: {:?}", NUM_OF_ANSWER, (et - st));
    }
}

// ナイトを次に置く場所を探す
fn tour(y: i32, x: i32, step: i32, board: &mut [[i32; BOARD_SIZE as usize]; BOARD_SIZE as usize]) {
    let mut x2; //  let mut x2, y2; とは書けない
    let mut y2;
    // すべてのマス目を巡回した
    if step == BOARD_SIZE * BOARD_SIZE {
        // 次の移動先候補に1があればClosed knight's tour
        for i in 0..NEXT_X.len() {
            x2 = x + NEXT_X[i];
            y2 = y + NEXT_Y[i];
            // 盤面の中
            if (-1 < x2 && x2 < BOARD_SIZE) && (-1 < y2 && y2 < BOARD_SIZE) {
                // 開始位置か?
                if board[y2 as usize][x2 as usize] == 1 {
                    // 結果を出力
                    print_board(board);
                    return;
                }
            }
        }
        // Closed knight's tourではない
        return;
    }
    // 次に置く場所を探す
    for i in 0..NEXT_X.len() {
        x2 = x + NEXT_X[i];
        y2 = y + NEXT_Y[i];
        // 盤面の中
        if (-1 < x2 && x2 < BOARD_SIZE) && (-1 < y2 && y2 < BOARD_SIZE) {
            // まだナイトを置いていない
            if board[y2 as usize][x2 as usize] == 0 {
                // ナイトを置く
                board[y2 as usize][x2 as usize] = step + 1;
                // 次の場所を探す
                tour(y2, x2, step + 1, board);
                // ナイトを取る
                board[y2 as usize][x2 as usize] = 0;
            }
        }
    }
}

///    結果を整形して出力する
fn print_board(board: &[[i32; BOARD_SIZE as usize]; BOARD_SIZE as usize]) {
    unsafe {
        NUM_OF_ANSWER += 1;
        //    println!("[{}] {}", NUM_OF_ANSWER, Local::now().format("%H:%M:%S"));
    }
    let mut sb: String = "+".to_string();
    sb.push_str(&"---+".repeat(BOARD_SIZE as usize));

    println!("{}", sb);
    for y in 0..BOARD_SIZE as usize {
        print!("|");
        for x in 0..BOARD_SIZE as usize {
            print!("{:>3}|", board[y][x]);
        }
        println!("");
        println!("{}", sb);
    }
}

これも先にJava版を作ってRustに書き直した。元プログラムはこれ。

package com.example;

import java.util.Collections;
import java.util.Date;

public class KnightsTour {
    // チェス盤の大きさ
    private static final int BOARD_SIZE = 6;

    // 次の配置場所計算用
    private static final int[] nextX = { 1, 2, 2, 1, -1, -2, -2, -1 };
    private static final int[] nextY = { -2, -1, 1, 2, 2, 1, -1, -2 };

    // 見つけた解の数
    private static int numOfAnswer = 0;

    public static void main(String[] args) {
        // チェス盤
        int[][] board = new int[BOARD_SIZE][BOARD_SIZE];

        long st = System.currentTimeMillis();

        board[0][0] = 1;
        tour(0, 0, 1, board);

        long et = System.currentTimeMillis();
        System.out.printf("num of path=%,d elapsed: %,d(ms)\n", numOfAnswer, (et - st));
    }

    /**
    * ナイトを次に置く場所を探す
    * @param x
    * @param y
    * @param step
    * @param board
    */
    private static void tour(int y, int x, int step, int[][] board) {
        int x2, y2;
        // すべてのマス目を巡回した
        if (step == BOARD_SIZE * BOARD_SIZE) {
            // 次の移動先候補に1があればClosed knight's tour
            for (int i = 0; i < nextX.length; i++) {
                x2 = x + nextX[i];
                y2 = y + nextY[i];
                // 盤面の中
                if ((-1 < x2 && x2 < BOARD_SIZE) && (-1 < y2 && y2 < BOARD_SIZE)) {
                    // 開始位置か?
                    if (board[y2][x2] == 1) {
                        // 結果を出力
                        printBoard(board);
                        //System.exit(0);
                        return;
                    }
                }
            }
            // Closed knight's tourではない
            return;
        }

        // 次に置く場所を探す
        for (int i = 0; i < nextX.length; i++) {
            x2 = x + nextX[i];
            y2 = y + nextY[i];
            // 盤面の中
            if ((-1 < x2 && x2 < BOARD_SIZE) && (-1 < y2 && y2 < BOARD_SIZE)) {
                // まだナイトを置いていない
                if (board[y2][x2] == 0) {
                    // ナイトを置く
                    board[y2][x2] = step + 1;
                    // 次の場所を探す
                    tour(y2, x2, step + 1, board);
                    // ナイトを取る
                    board[y2][x2] = 0;
                }
            }
        }
    }

    /**
    * 結果を整形して出力する
    * @param board
    */
    private static void printBoard(int[][] board) {
        ++numOfAnswer;
        System.out.printf("[%,d] %tT\n", numOfAnswer, new Date());
        StringBuffer sb = new StringBuffer();
        sb.append("+");
        sb.append(String.join("", Collections.nCopies(BOARD_SIZE, "---+")));

        System.out.println(sb.toString());
        for (int y = 0; y < BOARD_SIZE; ++y) {
            System.out.print("|");
            for (int x = 0; x < BOARD_SIZE; ++x) {
                System.out.printf("%3d|", board[y][x]);
            }
            System.out.println();
            System.out.println(sb.toString());
        }

    }
}