Слушать Wythoff's Nim отлично

16

Ваша цель - написать идеального игрока для игры в Wythoff's Nim .

Правила Витоффа Нима

Ним из Wythoff - это детерминированная игра для двух игроков, в которую играют двумя кучами одинаковых фишек. Игроки чередуются ходами, в которых они выполняют одно из следующих действий:

  1. Удалите один или несколько жетонов из первой стопки.
  2. Убрать один или несколько жетонов из второй стопки.
  3. Удалите равное количество жетонов (один или несколько) как из первой, так и из второй стопки.

Конечно, стопки не могут быть отрицательными, но они могут равняться 0. Любой игрок, удаляющий последний счетчик, выигрывает в игре.

Для более геометрически мыслящих, вот эквивалентная формулировка игры, в которую вы можете играть в этом апплете . Одна королева начинается на некотором квадрате четверти бесконечной шахматной доски, угол которой находится внизу слева. Игроки поочередно перемещают королеву, которая движется как шахматная королева, но ограничена тремя направлениями:

  1. вниз
  2. Осталось
  3. По диагонали вниз и влево

Кто бы ни перемещает королеву в угол, побеждает.

Сопоставляя координаты королевы (с углом (0,0)) с размерами соответствующих куч, легко увидеть, что обе игры одинаковы.

Идеальная игра

(Вы можете пропустить это, если знакомы с идеями идеальной игры и выигрышных ходов.)

Поскольку игра Нима Витоффа является конечной и детерминированной игрой, в ней есть понятие идеальной игры . Идеальный игрок - это стратегия, которая всегда выигрывает с теоретически выигранной позиции, то есть позиции, в которой существует стратегия, гарантирующая победу.

Чтобы быть выигрышной стратегией, достаточно двигаться, чтобы всегда перемещаться в теоретическую выигрышную позицию для игрока, который только что перешел, и, таким образом, игрок не пойдет дальше. Первая из этих выигрышных позиций (также называемых холодными позициями ) (0,0), (1,2), (2,1), (3,5), (5,3). См. Статью в Википедии для объяснения алгоритма, чтобы найти выигрышную стратегию для Wythoff's Nim, а также формулы для генерации выигрышных позиций.

Требования к программе

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

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

Ваша программа должна быть запущена в течение разумного периода времени. Таким образом, экспоненциального рекурсивного поиска по дереву игр будет недостаточно. Если вы хотите предварительно вычислить и жестко закодировать стратегию, это нормально.

вход

Пара (i,j)неотрицательных чисел, представляющих размеры стопки, каждое не более 99. Это может быть два числа, кортеж, список или любой другой контейнер, который вы предпочитаете.

Выход

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

Если выигрышного хода нет, вы должны указать это в выходных данных. Любой выход , как False, None, 0, или (-1,-1)будет делать, пока это не правовая позиция, и то же самое для каждого входа проигравших.

Пример работает

f(5,0)   = (0,0)
f(2,2)   = (1,2)   # Or (2,1) or (0,0) 
f(1,2)   = False
f(13,9)  = (13,8)  # Or (10,6)
f(10,6)  = False
f(5,10)  = (5,3)
f(25,30) = (8,13)    
XNOR
источник
2
+1, отчасти потому, что мне нравится идея четверти бесконечности.
Уровень Ривер Ст
определить «разумное количество времени». Несколько секунд для (100,50) разумного количества времени?
Джон Дворжак
Ой. Подождите. вход ограничен ... 30 ??? Это немного низко, не так ли?
Джон Дворжак
@JanDvorak Вы правы, это может позволить дешевые ярлыки. Поменял на 99 - думаю этого хватит? Извиняюсь за редактирование спецификации после публикации.
xnor
@PeterTaylor Спасибо, исправлено.
xnor

Ответы:

6

Haskell, 167 165 знаков

c p=o p:c(o p:p)
o p=[(a,b)|a<-[0..],b<-[0..a],(a,b)?p==[]]!!0
(a,b)?p=[y|y@(c,d)<-p,c==a||c==b||d==b||a+d==b+c]
f x@(a,b)|a<b=f(b,a)|x?c[]!!0==x=(0,-1)|1>0=x?c[]!!0

Алгоритм неэффективен, но он все еще работает в течение секунды (хотя и не в интерактивной консоли) для входов <100.

Объяснение:

c p=o p:c(o p:p)

Список холодных позиций с учетом набора предыдущих холодных позиций представляет собой одну холодную позицию, за которой следует список холодных позиций с данной и предыдущими позициями (Неэффективность: эта позиция генерируется дважды).

o p=[(a,b)|a<-[0..],b<-[0..a],(a,b)?p==[]]!!0

Одна холодная позиция - это первая пара, так что из этой пары нет доступных холодных позиций (неэффективность: вместо этого мы должны искать из предыдущей пары)

(a,b)?p=[y|y@(c,d)<-p,c==a||c==b||d==b||a+d==b+c]

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

f x@(a,b)|a<b=f(b,a)
         |x?c[]!!0==x=(0,-1)
         |1>0=x?c[]!!0

(основной метод) Если кучи находятся в неправильном порядке, меняйте их местами. В противном случае, если первой холодной позицией, достижимой из позиции, является сама позиция, укажите сбой (в идеале Maybe (Int,Int)вместо этого следует вернуться ). Еще верните эту холодную позицию (Неэффективность: указанная пара ищется дважды. Хуже того, список холодных позиций генерируется дважды)

Джон дворак
источник
Кажется, что « Таким образом, поиск по дереву экспоненциальной рекурсивной игры не будет достаточным » был оптимистичным, потому что то, что вы описываете, звучит именно так.
Питер Тейлор
@PeterTaylor это O (n ^ 4). Каждая холодная пара занимает O (n ^ 3) времени, чтобы найти, и есть O (n) из них. Оптимизация генерации привела бы к O (n ^ 2) (если я правильно прочитал последовательность). Алгоритм экспоненциального времени будет намного медленнее. Должен ли я запустить некоторые тесты?
Джон Дворжак
Все хорошо, я тебе верю.
Питер Тейлор
Вы можете удалить x@изx@(a,b)?p=...
гордый Haskeller
Не уверен, как он туда попал. Исправление, спасибо.
Джон Дворжак
5

GolfScript ( 63 57 байт)

{\]zip{~-}%0|$(!*,1=}+1,4*{..,,^[(.@,2/+.2$]+}38*2/?0]0=`

Ожидает ввод от stdin в форме [a b]и оставляет вывод на stdout в этой форме или 0если это проигрышная позиция. Онлайн демо

обзор

,4*{..,,^[(.@,2/+.2$]+}38*2/

вычисляет список холодных позиций (включая перевернутую версию [b a]для каждой холодной позиции [a b]), используя свойство последовательности Beatty .

Затем ?ищет первую холодную позицию, удовлетворяющую блоку, созданному

{\]zip{~-}%0|$(!*,1=}+

которые в основном проверяет , что позиция достижима из позиции ввода путем вычисления разности векторов , а затем проверить , что это либо [0 x], [x 0]или [x x]для некоторых x > 0. ИМО , что тест является самыми умным битом: 0|$силы любого массива в одном из этих форматов в форму во [0 x]время отображения [0 0]к [0], [a b]где ни aни bнаходится 0в массив из трех элементов, и [-x 0]или [-x -x]с [-x 0]. Затем (!*,1=проверяет, что у нас есть [0 x].

Наконец 0]0=`делает запасной вариант и форматирование для вывода.

Питер Тейлор
источник
4

Pyth 57 58 61 62

K1.618Jm,s*dKs*d*KKU39M<smf|}TJ}_TJm,-Ghb-Hebt^,0hk2U99 1

Попробуйте онлайн.

Очень похоже на другие ответы, но на этой странице википедии больше ничего не оставалось;) Волшебное число 39- это количество холодных позиций со значениями < 99.

Определяет функцию, gкоторую вы можете вызвать как g 30 25. Возвращает []в случае неудачи, [(13,8)]в случае успеха.

объяснение

K1.618                            : K=phi (close enough for first 39 values)
      Jm,s*dKs*d*KKU39            : J=cold positions with the form (small,big)
M<s                              1: Define g(G,H) to return this slice: [:1] of the list below 
   mf|}TJ}_TJ                     : map(filter: T or reversed(T) in J, where T is each member of..
             m,-Ghb-Hebt^,0hk2    : [(G H) - (0 x+1),(x+1 0) and (x+1 x+1)]
                              U99 : for each x from 0 - 98
FryAmTheEggman
источник
sприведен к int - сохраняет несколько символов /____1. rZ39можно заменить U39, используя одинарный диапазон. Точно так же вы можете заменить r99)на U99.
Исаак
@isaacg Спасибо! Я полностью забыл о U. Я должен действительно обновить объяснение: P
FryAmTheEggman
@isaacg Просто мысль о Пите, я думаю, что вы могли бы сделать @пересечение наборов, если теперь его вторым аргументом является список. Это немного неловко a
опущено
Это хорошая идея - я ее реализовал. Я также изменил код пересечения, чтобы разрешить несколько уловок, которые раньше были невозможны, включая пересечение двух списков списков.
Исаак
2

Javascript ES6 - 280 байт

Минимизированный

r=x=>~~(x*1.618);g=(y,x)=>y(x)?g(y,x+1):x;s=A=>A?[A[1],A[0]]:A;f=(a,b)=>j([a,b])||j([a,b],1);j=(A,F)=>l(A,F)||s(l(s(A),F));l=(A,F)=>([a,b]=A,c=(F&&a+b>=r(b)&&(e=g(x=>a+b-2*x-r(b-x),0))?[a-e,b-e]:(e=g(x=>r(a+x)-2*a-x,0))+a<b?[a,a+e]:(e=r(b)-b)<a?[e,b]:0),c&&r(c[1]-c[0])==c[0]?c:0)

расширенный

r = x => ~~(x*1.618);
g = (y,x) => y(x) ? g(y,x+1) : x;
s = A =>A ? [A[1],A[0]] : A;
f = (a,b) => j([a,b]) || j([a,b],1);
j = (A,F) => l(A,F) || s(l(s(A),F));
l = (A,F) => (
    [a,b] = A,
    c = (
        F && a+b >= r(b) && (e = g( x => a+b - 2*x - r(b - x), 0 )) ? [a-e,b-e] :
        (e = g( x => r(a+x) - 2*a - x, 0)) + a < b ? [a,a+e] :
        (e = r(b) - b) < a ? [e,b] : 0
    ),
    c && r(c[1] - c[0]) == c[0] ? c : 0
);

Хороший и быстрый алгоритм. Работает в O (n), но будет работать в постоянное время, если бы не цикл сохранения байтов. Цикл реализован в виде рекурсивного инкремента, поэтому скрипт в конечном итоге завершится с ошибкой рекурсии для n в сотнях или тысячах. Использует то же свойство последовательности Битти, которое упоминает мистер Тейлор, но вместо того, чтобы вычислять последовательность, оно просто подходит к правильному термину (ам).

Работает правильно на всех входах теста и многих десятках, кроме того, что я тестировал.

Функция для вызова f . Возвращает массив в случае успеха и 0после отказа.

COTO
источник
Подождите, вывод массива в порядке?
Джон Дворжак
@JanDvorak: у xnor есть кортеж, указанный в списке допустимых выходных данных, поэтому я так и решил. Может быть, он сможет прояснить этот вопрос. Во всяком случае, это тривиальное исправление.
COTO
Массив или массив из одной пары в порядке; многократных выигрышных ходов нет.
xnor
1

Perl 5 - 109 (с 2 флагами)

#!perl -pl
for$a(@v=0..99){for$b(@v){$c=$a;$d=$b;${$:="$a $b"}||
map{$$_||=$:for++$c.$".++$d,"$a $d","$c $b"}@v}}$_=$$_

Использование:

$ perl wyt.pl <<<'3 5'

$ perl wyt.pl <<<'4 5'
1 2

Просто рассчитывает решение для каждого возможного ввода, а затем просто выполняет поиск.

nutki
источник