Seeking Leapers

19

I recently got a really weird irregular chess board. It's squares are all over the place and not even all connected. At least they're still laid out on a regular grid. I want to adapt the chess rules to be able to play on the board, but to start with, I need a piece that can actually go anywhere on the board, and it seems a leaper is my best bet for that.

Leapers are the fairy chess generalisation of knights. Leapers are parameterised by two integers m and n and can move m squares in one direction and then another n squares in either perpendicular direction. For the standard knight, we have (m, n) = (2, 1). The entire move is considered a single jump such that none of the squares on the way to the target need to be empty or even exist.

The Challenge

You're given a "chess board" in the form of a list of positive 2D integer coordinates which represent the squares that are part of the board. Your task is to find a leaper which, given enough moves, can reach any square on the board.

Let's look at some examples. The standard chessboard uses a regular grid of 8x8 squares (note that we don't distinguish between white and black squares for this challenge):

########
########
########
########
########
########
########
########

The standard knight can reach all of those, so (2, 1) would be a valid output. However, (1, 1) for example wouldn't be valid, since such a piece can only reach half of the squares no matter where it starts. (1, 0) on the other hand would also be a valid output, since all the squares are orthogonally connected.

Now if we have an irregular board like:

#   #
 # # #
  # # #
 # #
    #

Then possible solutions are (1, 1) and (3, 1). We can also have a board with completely disconnected regions like:

#### ####
#### ####
#### ####
#### ####

The standard knight (2, 1) can still reach all of the squares here, which is in fact the only solution.

And finally, the following simple board cannot be completely reached by any leaper at all:

#
 ##

Note that the input format will not be as an ASCII representation but a list of coordinates instead. E.g. the second example above could be given as:

[[1, 1], [5, 1], [2, 2], [4, 2], [6, 2], [3, 3], [5, 3], [7, 3], [2, 4], [4, 4], [5, 5]]

Rules

You may write a program or function, taking input via STDIN (or closest alternative), command-line argument or function argument and outputting the result via STDOUT (or closest alternative), function return value or function (out) parameter.

The input coordinates can be taken in any convenient list format (flat list, list of pairs, list of complex integers, string with consistent separators, etc.).

The output should be the two integers m and n that identify the leaper if a solution exists (as two separate integers, a list, a string with non-numeric delimiter, etc.). If no solution exists, you may output any consistent value which cannot possibly be a valid leaper. This includes the pair of integers (0, 0) in your normal format, as well as anything that isn't a pair of non-negative integers.

Your program needs to handle any of the test cases within a minute. This is a somewhat fuzzy restriction, but use common sense: if it takes 2 minutes on your machine, I think we can assume that it might run within 1 on someone else's, but if it takes 20 that's less likely. It shouldn't be hard to solve each test case in a matter of seconds, so this rule only acts to rule out naive brute force.

Standard rules apply.

Test Cases

Each test case is of the form board => all valid leapers. Remember that you only need to output one of those. If the list of leapers is empty, make sure to return something that isn't a valid leaper.

Examples above:
[[1, 1], [1, 2], [1, 3], [1, 4], [1, 5], [1, 6], [1, 7], [1, 8], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5], [2, 6], [2, 7], [2, 8], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5], [3, 6], [3, 7], [3, 8], [4, 1], [4, 2], [4, 3], [4, 4], [4, 5], [4, 6], [4, 7], [4, 8], [5, 1], [5, 2], [5, 3], [5, 4], [5, 5], [5, 6], [5, 7], [5, 8], [6, 1], [6, 2], [6, 3], [6, 4], [6, 5], [6, 6], [6, 7], [6, 8], [7, 1], [7, 2], [7, 3], [7, 4], [7, 5], [7, 6], [7, 7], [7, 8], [8, 1], [8, 2], [8, 3], [8, 4], [8, 5], [8, 6], [8, 7], [8, 8]] => [[0, 1], [1, 2], [1, 4], [2, 3], [3, 4]]
[[1, 1], [5, 1], [2, 2], [4, 2], [6, 2], [3, 3], [5, 3], [7, 3], [2, 4], [4, 4], [5, 5]] => [[1, 1], [1, 3]]
[[1, 1], [2, 2], [3, 2]] => []
[[1, 1], [1, 2], [1, 3], [1, 4], [2, 1], [2, 2], [2, 3], [2, 4], [3, 1], [3, 2], [3, 3], [3, 4], [4, 1], [4, 2], [4, 3], [4, 4], [6, 1], [6, 2], [6, 3], [6, 4], [7, 1], [7, 2], [7, 3], [7, 4], [8, 1], [8, 2], [8, 3], [8, 4], [9, 1], [9, 2], [9, 3], [9, 4]] => [[1, 2]]

Square boards:
[[1, 1], [1, 2], [2, 1], [2, 2]] => [[0, 1]]
[[1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], [3, 1], [3, 2], [3, 3]] => [[0, 1]]
[[1, 1], [1, 2], [1, 3], [1, 4], [2, 1], [2, 2], [2, 3], [2, 4], [3, 1], [3, 2], [3, 3], [3, 4], [4, 1], [4, 2], [4, 3], [4, 4]] => [[0, 1], [1, 2]]
[[1, 1], [1, 2], [1, 3], [1, 4], [1, 5], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5], [4, 1], [4, 2], [4, 3], [4, 4], [4, 5], [5, 1], [5, 2], [5, 3], [5, 4], [5, 5]] => [[0, 1], [1, 2]]
[[1, 1], [1, 2], [1, 3], [1, 4], [1, 5], [1, 6], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5], [2, 6], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5], [3, 6], [4, 1], [4, 2], [4, 3], [4, 4], [4, 5], [4, 6], [5, 1], [5, 2], [5, 3], [5, 4], [5, 5], [5, 6], [6, 1], [6, 2], [6, 3], [6, 4], [6, 5], [6, 6]] => [[0, 1], [1, 2], [2, 3]]
[[1, 1], [1, 2], [1, 3], [1, 4], [1, 5], [1, 6], [1, 7], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5], [2, 6], [2, 7], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5], [3, 6], [3, 7], [4, 1], [4, 2], [4, 3], [4, 4], [4, 5], [4, 6], [4, 7], [5, 1], [5, 2], [5, 3], [5, 4], [5, 5], [5, 6], [5, 7], [6, 1], [6, 2], [6, 3], [6, 4], [6, 5], [6, 6], [6, 7], [7, 1], [7, 2], [7, 3], [7, 4], [7, 5], [7, 6], [7, 7]] => [[0, 1], [1, 2], [2, 3]]

Miscellaneous:
[[1, 1], [2, 1]] => [[0, 1]]
[[1, 1], [1, 2]] => [[0, 1]]
[[1, 1], [12, 35]] => [[11, 34]]
[[1, 1], [1, 2], [2, 1], [2, 2], [6, 1], [6, 2], [6, 3], [6, 4], [7, 1], [7, 2], [7, 3], [7, 4], [8, 1], [8, 2], [8, 3], [8, 4], [9, 1], [9, 2], [9, 3], [9, 4]] => []
[[1, 1], [1, 2], [1, 3], [1, 4], [1, 5], [1, 6], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5], [2, 6], [3, 1], [3, 2], [3, 5], [3, 6], [4, 1], [4, 2], [4, 5], [4, 6], [5, 1], [5, 2], [5, 3], [5, 4], [5, 5], [5, 6], [6, 1], [6, 2], [6, 3], [6, 4], [6, 5], [6, 6]] => [[0, 1], [1, 2], [1, 4]]
[[2, 2], [2, 4], [2, 6], [2, 8], [4, 2], [4, 4], [4, 6], [4, 8], [6, 2], [6, 4], [6, 6], [6, 8], [8, 2], [8, 4], [8, 6], [8, 8]] => [[0, 2], [2, 4]]

Random boards:
[[1, 5], [1, 9], [2, 6], [2, 8], [2, 10], [2, 12], [3, 5], [3, 7], [3, 9], [3, 11], [3, 13], [4, 2], [4, 4], [4, 6], [4, 8], [4, 14], [5, 1], [5, 3], [5, 5], [5, 7], [6, 2], [6, 4], [7, 1], [8, 2]] => [[1, 1], [1, 3]]
[[1, 3], [1, 4], [1, 5], [1, 6], [1, 7], [2, 1], [2, 2], [2, 3], [2, 4], [2, 7], [3, 1], [3, 2], [3, 3], [3, 4], [3, 6], [3, 7], [4, 2], [4, 3], [4, 4], [4, 5], [4, 6], [5, 3], [5, 4], [5, 6]] => [[0, 1], [1, 2]]
[[1, 8], [2, 6], [2, 10], [3, 3], [3, 4], [3, 8], [4, 1], [4, 11], [5, 3], [5, 9], [6, 12], [8, 11], [10, 10], [11, 12], [12, 6], [12, 8], [13, 6], [13, 8], [13, 10], [13, 11], [14, 5], [14, 7], [14, 8], [14, 13], [14, 14], [15, 7], [15, 9], [15, 11], [15, 12], [16, 6], [16, 7], [16, 9], [16, 13], [16, 14], [17, 10], [17, 12], [18, 8], [18, 12], [20, 9], [21, 11], [22, 13], [23, 10], [23, 11], [23, 15], [24, 12]] => [[1, 2]]
[[1, 17], [1, 21], [3, 11], [3, 15], [3, 19], [3, 23], [5, 13], [5, 21], [7, 11], [7, 15], [7, 19], [9, 1], [9, 13], [9, 17], [11, 3], [11, 7], [11, 15], [11, 19], [13, 5], [13, 9], [13, 13], [13, 17], [13, 21], [15, 11], [15, 15], [15, 19], [17, 13], [17, 17]] => [[2, 2], [2, 6], [2, 10]]
[[1, 3], [2, 4], [2, 5], [3, 6], [4, 1], [5, 3], [5, 6], [5, 7], [6, 12], [6, 14], [6, 21], [7, 9], [7, 19], [8, 9], [8, 15], [8, 17], [8, 18], [8, 24], [9, 12], [9, 19], [10, 12], [10, 14], [10, 17], [10, 21], [11, 22], [12, 15], [12, 17], [12, 24], [13, 16], [14, 20], [14, 21], [14, 26], [15, 13], [15, 19], [16, 18], [16, 23], [17, 16], [17, 24]] => [[2, 3]]
[[1, 11], [3, 13], [4, 10], [6, 14], [8, 12], [9, 9], [9, 15], [12, 8], [13, 5], [13, 19], [13, 21], [14, 8], [15, 1], [15, 17], [16, 4], [16, 14], [16, 18], [16, 20], [17, 21], [18, 2], [18, 16], [18, 18], [19, 9], [19, 13], [19, 15], [20, 12], [21, 1], [21, 17], [22, 4], [22, 10], [23, 7]] => [[1, 3]]
[[1, 39], [6, 37], [8, 32], [10, 27], [11, 31], [11, 35], [12, 22], [16, 21], [16, 29], [16, 33], [18, 34], [21, 3], [21, 9], [21, 19], [23, 8], [23, 14], [23, 22], [23, 24], [23, 36], [24, 6], [25, 13], [25, 17], [26, 1], [26, 11], [28, 6], [28, 20], [28, 26], [28, 30], [28, 34], [30, 11], [30, 15], [30, 21], [32, 6], [33, 28], [33, 32], [35, 13], [35, 23]] => [[2, 5]]

As a special case, note that for a board consisting of only one cell, any leaper works, but your output must correspond to an actual leaper, so [0, 0] is not valid output.

Martin Ender

Posted 2016-04-18T17:14:41.017

Reputation: 184 808

Quick question. How is a knight (2,1)? Correct me if I'm wrong, but I'm pretty sure knights can move 3 squares in any one direction, and then 1 square in any direction perpendicular to the previous one, so it should instead be (3,1). – R. Kap – 2016-04-19T03:35:45.367

1

@R.Kap You're wrong. ;) https://en.wikipedia.org/wiki/Knight_(chess)#Movement

– DLosc – 2016-04-19T03:47:27.187

@DLosc Ok, wow. I guess I was. Thanks for that! – R. Kap – 2016-04-19T03:52:14.023

May we output all valid leapers in a list? If we do, can we output equivalent leapers like [[1, 0], [0, 1]]? – FryAmTheEggman – 2016-04-19T15:02:42.830

@FryAmTheEggman Just (any) one of them, please. – Martin Ender – 2016-04-19T15:04:46.160

Answers

12

Pyth, 41 35

hfqQu@+G+VM*s_BM*F_BMTGQ]hQ)maVhQdt

Exits on error if there are no valid leapers, giving the empty string if STDERR is ignored.

Try it here or run a Test Suite

Saved 6 bytes thanks to isaacg! Basically just finds all leaper candidates by selecting each valid leaper from the first tile to each other tile. Then for each of these, it makes all eight configurations of [x, y] offsets that the leaper could take. It then finds all moves starting from the first tile that follow after the move, and discards those that are not in the input. It keeps doing this until the result doesn't change. If this final list is the same as the input then the leaper was valid.

The standard chess board took the longest when I was testing, it took about 3 seconds on my not very impressive computer.

FryAmTheEggman

Posted 2016-04-18T17:14:41.017

Reputation: 16 206