georgevil73
georgevil73

Reputation: 15

The minimum number of moves for which two knights will meet

On a chessboard consisting of M rows and N columns (for example, 8x10), there are two knights, the user enters their coordinates himself (for example, (2, 4) is a white knight and (7, 9) is a black knight). Each knight is located in the it's cell, but it is possible that both knights are in the same cell. The knights take turns making moves in accordance with the rules of the chess knight's movement (the white knight goes first). The goal of the game is to place both horses in the same cell as quickly as possible.

Input format

The first line of the file contains the values M and N (2≤M,N≤1000). The second and third lines contain the coordinates of the cells in which the white and black knight are located, respectively. The first coordinate is in the range from 1 to M, the second is in the range from 1 to N.

Output format

Print a single number — the number of moves required to complete the game. If the knights can never be placed in the same square, print -1.

Since I'm new to algorithms and data structures, I tried to solve this problem like this: run for loop on all 64 possible combinations of two moves of a white and black knight, make a move for each knight (checking if it goes beyond the scope), check if there is a match and, if there is, then output it. Then run the same cycle inside of the current. At the same time, the moves are counted and it is also output. However, I have encountered such a problem that I cannot automate the process of running this loop inside the loop, I cannot know the number of times that this loop needs to be run. I tried to create a function with recursion in which it was possible to call this loop if a match has not yet been found, but I failed. I decided that it would not work that way to solve this problem, so I looked at the algorithms that are usually used in such tasks. I was thinking of somehow creating an adjacency list for two horses, where the vertices are all the calculated positions of the horse; use BFS, or the Dijkstra algorithm.

Solved. Here is my swift code:

import Foundation

let mnstr = readLine()?.components(separatedBy: " ")

let m = Int(mnstr![0])!
let n = Int(mnstr![1])!

let wstr = readLine()?.components(separatedBy: " ")
let bstr = readLine()?.components(separatedBy: " ")

var w: [Int] = []
var b: [Int] = []
var count: Int = 0
let moves: [[Int]] = [[2, -1], [1, 2], [-2, -1], [1, -2], [2, 1], [-1, 2], [-2, 1], [-1, -2]]

w.append(Int(wstr![0])!)
w.append(Int(wstr![1])!)
b.append(Int(bstr![0])!)
b.append(Int(bstr![1])!)

var wp: Set = [w]
var bp: Set = [b]

func oneMove(lst: Set<[Int]>) -> Set<[Int]>{
    let curr = lst
    var out = lst
    for i in curr {
        for move in moves {
            let item = [i[0] + move[0], i[1] + move[1]]
            if item[0] < 1 || item[0] > m || item[1] < 1 || item[1] > n {
                continue
            }
            out.insert(item)
        }
    }
    return out
}

while bp.intersection(wp).isEmpty == true {
    wp = oneMove(lst: wp)
    count += 1
    if wp.intersection(bp).isEmpty != true {
        break
    }
    bp = oneMove(lst: bp)
    count += 1
    if wp.intersection(bp).isEmpty != true {
        break
    }
    if wp.count == 1 || bp.count == 1 {
        count = -1
        break
    }
}

print(count)

Upvotes: 1

Views: 641

Answers (2)

trincot
trincot

Reputation: 350137

I know that an answer was already accepted, but for large distances between the pieces a BFS or Dijkstra's algorithm will use considerable time and resources.

There is however a pattern: when there is enough distance between the pieces (both in X and Y direction), an optimal path can be found within the bounding box of the two pieces and can be derived by a closed formula. And the more constrained or unsolvable situations can be identified also in constant time. The code for distinguishing the different patterns is quite "dull", but it will certainly run faster for when the paths are long: in constant time (if we assume arithmetic operations use constant time).

Here is some JavaScript code, which also includes the BFS algorithm so the outcome can be compared. It includes an interactive part, so that you can play with the board sizes and the positioning of the two pieces and check the results:

function knightDistance(rowCount, colCount, whiteX, whiteY, blackX, blackY) {
    // Convert the state so to ensure that black is at the right & upper side of white, and below the diagonal
    if (blackX < whiteX) return knightDistance(rowCount, colCount, blackX, blackY, whiteX, whiteY); // Swap pieces
    if (blackY < whiteY) return knightDistance(rowCount, colCount, whiteX, rowCount - 1 - whiteY, blackX, rowCount - 1 - blackY); // Mirror against X axis
    let diffX = blackX - whiteX;
    let diffY = blackY - whiteY;
    if (diffX < diffY) return knightDistance(colCount, rowCount, whiteY, whiteX, blackY, blackX); // mirror along diagonal
    

    if (diffX == 2 && diffY == 2) return 4;
    if (diffX <= 2 * diffY && diffX != 1) {
        if ((diffX + diffY) % 2) return Math.floor((diffX + diffY + 1) / 6) * 2 + 1;
        return Math.floor((diffX + diffY + 4) / 6) * 2;
    }
    if (rowCount == 1 || colCount == 2) return -1;
    if (rowCount == 2 && diffX % 4 != 2 * diffY) return -1;
    if (diffX + diffY > 3) {
        if ((diffX + diffY) % 2) return Math.floor((diffX + 1) / 4) * 2 + 1;
        return Math.floor((diffX + 3) / 4) * 2;
    }
    // Now rowCount > 2 and colCount > 2
    // Other cases where lack of space plays a role
    if (diffY == 1) {
        // Now diffX == 1
        if (rowCount == 3 && colCount == 3 && whiteX == whiteY) return -1;
        if (whiteX == 0 && whiteY == 0 || blackX == colCount - 1 && blackY == rowCount - 1) return 4;
        return 2;
    }
    // Now diffY == 0
    if (diffX == 1) {
        if (whiteY == 1 && rowCount == 3 && colCount == 3) return -1;
        if (whiteY == 1 && rowCount == 3 && colCount == 4 && whiteX == 1) return 5;
        return 3;
    }
    if (diffX == 2) {
        if (whiteY == 1 && rowCount == 3) return 4;
        return 2;
    }
    // Now diffY == 3
    if (colCount == 4 && (whiteY == 0 || whiteY == rowCount - 1)) return 5;
    return 3;
}

// The BFS algorithm for verification of the above function
function knightDistanceBfs(rowCount, colCount, whiteX, whiteY, blackX, blackY) {
    let visited = new Set;
    let frontier = [[whiteX, whiteY]];
    visited.add(whiteX + whiteY * colCount);
    let steps = 0;
    while (frontier.length) {
        let newFrontier = [];
        for (let [whiteX, whiteY] of frontier) {
            if (whiteX == blackX && whiteY == blackY) return steps;
            for (let [dx, dy] of [[-2, -1], [2, -1], [2, 1], [-2, 1], [-1, -2], [1, -2], [1, 2], [-1, 2]]) {
                let newX = whiteX + dx;
                let newY = whiteY + dy;
                if (newX < 0 || newY < 0 || newX >= colCount || newY >= rowCount) continue;
                let key = newX + newY * colCount;
                if (visited.has(key)) continue;
                visited.add(key);
                newFrontier.push([newX, newY]);
            }
        }
        steps++;
        frontier = newFrontier;
    }
    return -1;
}

// Quick test of all possibilities on boards with at most 5 rows and 5 columns:
for (let rowCount = 1; rowCount <= 5; rowCount++) {
    for (let colCount = 1; colCount <= 5; colCount++) {
        for (let whiteX = 0; whiteX < colCount; whiteX++) {
            for (let whiteY = 0; whiteY < rowCount; whiteY++) {
                for (let blackX = 0; blackX < colCount; blackX++) {
                    for (let blackY = 0; blackY < rowCount; blackY++) {
                        let answer = knightDistanceBfs(rowCount, colCount, whiteX, whiteY, blackX, blackY);
                        let answer2 = knightDistance(rowCount, colCount, whiteX, whiteY, blackX, blackY);
                        if (answer !== answer2) {
                            console.log({rowCount, colCount, whiteX, whiteY, blackX, blackY});
                            throw "Test case failed";
                        }
                    }
                }
            }
        }
    }
}

// I/O handling

let [rowInput, colInput] = document.querySelectorAll("input");
let table = document.querySelector("table");
let outputs = document.querySelectorAll("span");
let whiteX, whiteY, blackX, blackY;

rowInput.oninput = colInput.oninput = function () {
    // Create table
    table.innerHTML = "";
    for (let i = +rowInput.value; i > 0; i--) {
        let row = table.insertRow();
        for (let j = +colInput.value; j > 0; j--) {
            row.insertCell();
        }
    }
    whiteX = -1;
    blackX = -1;
};

table.onclick = function (e) {
    if (e.target.tagName != "TD") return;
    let x = e.target.cellIndex;
    let y = e.target.parentNode.rowIndex;
    if (x == whiteX && y == whiteY) {
        e.target.textContent = "";
        whiteX = -1;
        whiteY = -1;
    } else if (x == blackX && y == blackY) {
        e.target.textContent = "";
        blackX = -1;
        blackY = -1;
    } else if (whiteX == -1) {
        e.target.textContent = "♘";
        whiteX = x;
        whiteY = y;
    } else {
        if (blackX != -1) {  // Remove black piece first
            table.rows[blackY].cells[blackX].textContent = "";
        }
        e.target.textContent = "♞";
        blackX = x;
        blackY = y;
    }
    if (blackX != -1 && whiteX != -1) {
        outputs[0].textContent = knightDistanceBfs(+rowInput.value, +colInput.value, whiteX, whiteY, blackX, blackY);
        outputs[1].textContent = knightDistance(+rowInput.value, +colInput.value, whiteX, whiteY, blackX, blackY);
    } else {
        outputs[0].textContent = outputs[1].textContent = "--";
    }
}

rowInput.oninput();
table { border-collapse: collapse; cursor: pointer; margin: 2px }
td { border: 1px solid; width: 22px; height: 22px; padding: 0 }
input { width: 3em }
<div>Rows: <input id="rows" type="number" value="3"> Columns: <input id="cols" type="number" value="3"></div>
<table></table>
Number of moves: <span>--</span> (with BFS: <span>--</span>)
<div>Click on the board to place/remove pieces</div>

Upvotes: 1

Scott Hunter
Scott Hunter

Reputation: 49803

This would seem to be the basic logic you want, where ?_locs is a set of the locations a particular knight can be in (initialized to its initial location) and one_move yields a set of the locations that can be reached in 1 move from one of the locations in the argument:

while bk_locs intersect wh_locs is empty:
    bk_locs = one_move(bk_locs)
    wh_locs = one_move(wh_locs)

What this doesn't handle is counting moves (trivial) or identifying when to give up (harder).

Upvotes: 0

Related Questions