Найдите оптимальный стартовый ход Chomp


14

Chomp - игра для двух игроков с настройкой прямоугольника фигур. Каждый игрок по очереди удаляет любую фигуру вместе со всеми фигурами над ней и справа от нее. Тот, кто берет нижний левый кусок, проигрывает. Достаточно легко доказать, что у первого игрока всегда есть выигрышный ход (за исключением прямоугольника 1 на 1); Найди это.

  1. Входными данными являются размеры прямоугольника (два числа)
  2. Выход - местоположение выигрышного хода (два числа)
  3. Если выигрышных ходов более одного, вы можете вывести любой из них.

Это код гольф; самый короткий код (любой язык) выигрывает.

Примеры

Примечание: выходы - только два числа; искусство ASCII ниже просто демонстрирует, что означают числа.

Ввод: 5 3 (индексы на основе 1, начиная с левого нижнего угла)

Выход: 4 3

XXX--
XXXXX
XXXXX

Вход: 4 4

Выход: 2 2

X---
X---
X---
XXXX

бонус

Вычтите 15 символов из вашего счета, если вы выводите все выигрышные ходы. Каждая пара чисел должна быть отделена новой строкой.


В вашем первом примере, я думаю, что у вас есть слишком много тире
kitcar2000

@Kitcar Ты прав; фиксированный.
Ypnypn

Я не могу понять формат вывода. Как эти цифры соответствуют этим позициям?
подземный

@undergroundmonorail 1 основанный индекс внизу слева. первый индекс - горизонтальная ось, а второй индекс - вертикальный.
Мартин Эндер

2
В ответ на ваше вознаграждение: в шахматах у вас есть менее 119 возможных ходов в любой момент времени (обычно намного меньше), и ни один суперкомпьютер до сегодняшнего дня не приблизился к решению шахмат с использованием даже самых известных алгоритмов. На сетке 10 на 10 Chomp есть 100 возможных первых ходов, и каждый из них имеет 1-99 потенциальных вторых ходов. Что заставляет вас думать, что это будет легко перебор? Я рекомендую ограничить размер вашей сетки, если вы хотите получить грубые ответы. РЕДАКТИРОВАТЬ: Но не делай этого. Изменение требований в середине конкурса плохо.
Rainbolt

Ответы:


7

GolfScript, 82 ( 108 97 символов - 15 бонусов)

~),1/{{:F$0=),{F\+}/}%}@(*(0*\{1${1$\{\(@<},=},{1$\{\(@>},+(-!},:Y!{.,/+0}*;}/;Y{.-1=.@?)' '@)n}/

Поскольку я не знал никакой эвристики, это решение выполняет исчерпывающий поиск в пространстве решений. Вы можете попробовать код онлайн . Хотя реализация очень эффективна, пространство поиска растет очень быстро с увеличением входных данных.

Примеры:

> 5 3
4 3

> 5 4
3 3

> 6 6
2 2

Как упоминалось выше, реализация не полагается на рекурсию, а посещает каждый узел пространства поиска только один раз. Ниже вы можете найти аннотированную версию кода, которая более подробно описывает строительные блоки.

Представление одной доски размером w * h задается списком из w чисел в диапазоне от 0 до h . Каждое число дает количество штук в соответствующем столбце. Таким образом, действительная конфигурация - это список, в котором числа не увеличиваются от начала до конца (при любом перемещении вы гарантируете, что все столбцы справа не больше, чем выбранный).

~                   # Evaluate the input (stack is now w h)

# BUILDING THE COMPLETE STATE SPACE
# Iteratively builds the states starting with 1xh board, then 2xh board, ...

),1/                # Generate the array [[0] [1] ... [h]] which is the space for 1xh
{                   # This loop is now ran w-1 times and each run adds all states for the 
                    # board with one additional column
  {                 # The {}/] block simply runs for each of the existing states
    :F$0=           #   Take the smallest entry (which has to be the last one)
    ),              #   For the last column all values 0..x are possible
    {F\+}/          #     Append each of these values to the smaller state
  }%
}@(*

# The order ensures that the less occupied boards are first in the list.
# Thus each game runs from the end of the list (where [h h ... h] is) to 
# the start (where [0 0 ... 0] is located).

# RUN THROUGH THE SEARCH SPACE
# The search algorithm therefore starts with the empty board and works through all
# possible states by simply looping over this list. It builds a list of those states
# which are known as non-winning states, i.e. those states where a player should 
# aim to end after the move

(                   # Skips the empty board (which is a winning configuration)
0*\                 # and makes an empty list out of it (which will be the list of
                    # known non-winning states (initially empty))
{                   # Loop over all possible states
  1$                #   Copy of the list of non-winning states
  {                 #   Filter those which are not reachable from the current state,
                    #   because at least one column has more pieces that the current
                    #   board has
    1$\{\(@<},=
  },
  {                 #   Filter those which are not reachable from the current state,
                    #   because no valid move exists
    1$\{\(@>},+     #     Filter those columns which are different between start and
                    #     end state
    (-!             #     If those columns are all of same height it is possible to move
  },
  :Y                #   Assign the result (list of all non-winning states which are
                    #   reachable from the current configuration within one move)
                    #   to variable Y

  !{                #   If Y is non-empty this one is a winning move, otherwise 
                    #   add it to the list
    .,/+
    0               #     Push dummy value
  }*;
}/
;                   # Discard the list (interesting data was saved to variable Y)

# OUTPUT LOOP
# Since the states were ordered the last one was the starting state. The list of 
# non-winning states were saved to variable Y each time, thus the winning moves 
# from the initial configuration is contained in this variable.

Y{                  # For each item in Y
  .-1=.@?)          #   Get the index (1-based) of the first non-h value
  ' '               #   Append a space
  @)                #   Get the non-h value itself (plus one)
  n                 #   Append a newline
}/

+1 За само решение и за очень хорошо прокомментированный код
Xuntar

Динамическое программирование снизу вверх, а не сверху вниз. Ницца. Я подумал сделать это, но генерация состояний в правильном порядке для обхода снизу вверх была более трудоемкой и более запутанной, чем рекурсивный поиск. Я удивлен, что код закончился так долго; Я ожидал, что такой краткий язык, как Golfscript, мог бы дать гораздо более короткое решение.
user2357112 поддерживает Monica

Очень хорошо и хорошо продумано
Mouq

8

Python 2 3, 141-15 = 126

def win(x,y):w([y]*x)
w=lambda b,f=print:not[f(r+1,c+1)for r,p in enumerate(b)for c in range(p)if(r+c)*w(b[:r]+[min(i,c)for i in b[r:]],max)]

Минимаксный поиск методом грубой силы. Для каждого возможного хода мы рекурсивно видим, сможет ли противник выиграть после того, как мы сделаем этот ход. Довольно слабо играли в гольф; кто-то должен быть в состоянии сделать намного лучше. Это похоже на работу для APL.

  • winэто публичный интерфейс. Он берет размеры доски, преобразует ее в представление доски и передает ее в w.
  • wэто минимаксный алгоритм. Он принимает состояние доски, пробует все ходы, строит список, элементы которого соответствуют выигрышным ходам, и возвращает True, если список пуст. По умолчанию f=printпостроение списка имеет побочный эффект печати выигрышных ходов. Имя функции раньше имело смысл, когда она возвращала список выигрышных ходов, но затем я переместил notперед списком, чтобы сэкономить место.
  • for r,p in enumerate(b)for c in xrange(p) if(r+c): Перебрать все возможные ходы. 1 1рассматривается как недопустимый шаг, немного упрощающий базовый вариант.
  • b[:r]+[min(i,c)for i in b[r:]]: Построить состояние доски после хода, представленного координатами rи c.
  • w(b[:r]+[min(i,c)for i in b[r:]],max): Проверьте, является ли новое состояние убыточным. maxэто самая короткая функция, которую я смог найти, которая бы принимала два целочисленных аргумента и не жаловалась.
  • f(r+1,c+1): Если fпечать, печатает ход. Что бы fэто ни было , оно выдает значение для дополнения длины списка.
  • not [...]: notвозвращает Trueдля пустых списков и Falseдля непустых.

Оригинальный код Python 2, полностью безглобный, включая памятки для обработки гораздо больших входных данных:

def win(x, y):
    for row, column in _win(Board([y]*x)):
        print row+1, column+1

class MemoDict(dict):
    def __init__(self, func):
        self.memofunc = func
    def __missing__(self, key):
        self[key] = retval = self.memofunc(key)
        return retval

def memoize(func):
    return MemoDict(func).__getitem__

def _normalize(state):
    state = tuple(state)
    if 0 in state:
        state = state[:state.index(0)]
    return state

class Board(object):
    def __init__(self, state):
        self.state = _normalize(state)
    def __eq__(self, other):
        if not isinstance(other, Board):
            return NotImplemented
        return self.state == other.state
    def __hash__(self):
        return hash(self.state)
    def after(self, move):
        row, column = move
        newstate = list(self.state)
        for i in xrange(row, len(newstate)):
            newstate[i] = min(newstate[i], column)
        return Board(newstate)
    def moves(self):
        for row, pieces in enumerate(self.state):
            for column in xrange(pieces):
                if (row, column) != (0, 0):
                    yield row, column
    def lost(self):
        return self.state == (1,)

@memoize
def _win(board):
    return [move for move in board.moves() if not _win(board.after(move))]

Демо-версия:

>>> for i in xrange(7, 11):
...     for j in xrange(7, 11):
...         print 'Dimensions: {} by {}'.format(i, j)
...         win(i, j)
...
Dimensions: 7 by 7
2 2
Dimensions: 7 by 8
3 3
Dimensions: 7 by 9
3 4
Dimensions: 7 by 10
2 3
Dimensions: 8 by 7
3 3
Dimensions: 8 by 8
2 2
Dimensions: 8 by 9
6 7
Dimensions: 8 by 10
4 9
5 6
Dimensions: 9 by 7
4 3
Dimensions: 9 by 8
7 6
Dimensions: 9 by 9
2 2
Dimensions: 9 by 10
7 8
9 5
Dimensions: 10 by 7
3 2
Dimensions: 10 by 8
6 5
9 4
Dimensions: 10 by 9
5 9
8 7
Dimensions: 10 by 10
2 2

За 13x13дубль 2x2и ты выиграл.
Давидсбро

@davidsbro: Да, это выигрышный ход для любой квадратной доски размером больше 1x1, но мой код еще не вычислил ее.
user2357112 поддерживает Monica

2

Perl 6: 113 108 символов - 15 = 93 балла

Этот был жестким! Вот некэшированная версия, которая технически правильна, но займет очень много времени для нетривиальных вводов.

sub win(*@b){map ->\i,\j{$(i+1,j+1) if @b[i][j]&&!win @b[^i],@b[i..*].map({[.[^j]]})},(^@b X ^@b[0])[1..*]}

Он работает так же, как и реализация Python @ user2357112 (обоснуйте его / ее, я не мог бы понять это без его / ее работы!) За исключением того, что win () использует доску Chomp (массив) вместо ширины и длины. Может использоваться как:

loop {
    my ($y, $x) = get.words;
    .say for @(win [1 xx $x] xx $y)
}

Версия с памяткой, которая на самом деле может обрабатывать приличные входные данные (хотя и не оптимизирована для удобства чтения):

my %cache;
sub win (*@b) {
    %cache{
        join 2, map {($^e[$_]??1!!0 for ^@b[0]).join}, @b
    } //= map ->\i,\j{
        $(i+1,j+1) if @b[i][j] and not win
            @b[^i], @b[i..*].map({[.[^(* min j)]]}).grep: +*;
    },(^@b X ^@b[0])[1..*]
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.