Chơi Wythoff's Nim một cách hoàn hảo


16

Mục tiêu của bạn là viết một người chơi hoàn hảo cho trò chơi Wythoff's Nim .

Quy tắc của Wythoff Nim

Wythoff 'Nim là một trò chơi hai người chơi quyết định được chơi với hai đống quầy giống hệt nhau. Người chơi thay phiên nhau, trong đó họ thực hiện một trong những điều sau:

  1. Loại bỏ một hoặc nhiều quầy từ đống đầu tiên
  2. Loại bỏ một hoặc nhiều quầy từ đống thứ hai
  3. Loại bỏ một số lượng bằng nhau của quầy (một hoặc nhiều), từ cả cọc thứ nhất và cọc thứ hai.

Tất nhiên, các cọc không thể âm, nhưng chúng có thể xuống 0. Bất kỳ người chơi nào loại bỏ bộ đếm cuối cùng sẽ thắng trò chơi.

Đối với những người có đầu óc hình học hơn, đây là một công thức tương đương của trò chơi mà bạn có thể chơi trên applet này . Một nữ hoàng duy nhất bắt đầu trên một số hình vuông của bàn cờ vô hạn có góc nằm ở góc dưới bên trái. Người chơi luân phiên di chuyển nữ hoàng, di chuyển như nữ hoàng cờ vua nhưng bị giới hạn ở ba hướng:

  1. Xuống
  2. Trái
  3. Theo đường chéo xuống và trái

Ai di chuyển nữ hoàng đến góc sẽ thắng.

Liên kết tọa độ của nữ hoàng (với góc (0,0)) với kích thước của các cọc tương ứng, thật dễ dàng để thấy cả hai trò chơi đều giống nhau.

Chơi hoàn hảo

(Bạn có thể bỏ qua điều này nếu quen thuộc với các khái niệm chơi hoàn hảo và di chuyển chiến thắng.)

Vì Wythoff 'Nim là một trò chơi hữu hạn và mang tính quyết định, nên nó có một khái niệm về lối chơi hoàn hảo . Một người chơi hoàn hảo là một chiến lược sẽ luôn giành chiến thắng từ vị trí giành được về mặt lý thuyết, nghĩa là một vị trí trong đó tồn tại một chiến lược đảm bảo chiến thắng.

Để trở thành một chiến lược chiến thắng, nó đủ để di chuyển để luôn luôn di chuyển đến một vị trí chiến thắng về mặt lý thuyết cho người chơi vừa di chuyển, và do đó người chơi sẽ không đi tiếp. Vị trí đầu tiên trong số các vị trí chiến thắng (còn được gọi là vị trí lạnh ) là (0,0), (1,2), (2,1), (3,5), (5,3). Xem bài viết trên Wikipedia để biết giải thích về thuật toán để tìm chiến lược chiến thắng cho Wythoff's Nim, cũng như một công thức để tạo ra các vị trí giành chiến thắng.

Yêu cầu chương trình

Viết chương trình hoặc hàm lấy một vị trí làm đầu vào và xuất ra một nước đi chiến thắng dưới dạng vị trí sau khi di chuyển đó. Ít byte nhất sẽ thắng.

Nếu không có động thái chiến thắng tồn tại, tức là, vị trí là một mất mát về mặt lý thuyết, chương trình của bạn sẽ chỉ ra như vậy và bị mất.

Chương trình của bạn phải chạy trong một khoảng thời gian hợp lý. Vì vậy, một tìm kiếm cây đệ quy theo cấp số nhân sẽ không đủ. Nếu bạn muốn tính toán trước và mã hóa một chiến lược, điều đó tốt.

Đầu vào

Một cặp (i,j)số không âm biểu thị kích thước cọc, mỗi số nhiều nhất 99. Đây có thể là hai số, một tuple, một danh sách hoặc bất kỳ vùng chứa nào bạn thích.

Đầu ra

In hoặc xuất vị trí sau khi bạn di chuyển, một lần nữa dưới dạng hai số hoặc một thùng chứa. Đây phải là một bước đi hợp pháp đến một vị trí chiến thắng. Nếu có nhiều động thái như vậy, bất kỳ một động tác nào cũng tốt, nhưng chỉ một.

Nếu không có động thái chiến thắng, bạn phải chỉ ra điều này trong đầu ra. Bất kỳ đầu ra nào False, None0, hoặc (-1,-1)sẽ làm, miễn là nó không phải là một vị trí hợp pháp, và đều giống nhau cho mọi đầu vào bị mất.

Ví dụ chạy

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)    

2
+1, một phần vì tôi thích ý tưởng về một phần tư vô cùng.
Cấp sông St

định nghĩa "lượng thời gian hợp lý". Là vài giây cho (100,50) một khoảng thời gian hợp lý?
John Dvorak

Oh. Chờ đợi. đầu vào bị giới hạn bởi ... 30 ??? Đó là một chút thấp, phải không?
John Dvorak

@JanDvorak Bạn nói đúng, nó có thể cho phép các phím tắt giá rẻ. Thay đổi thành 99 - Tôi nghĩ rằng đủ? Xin lỗi để chỉnh sửa thông số kỹ thuật sau khi đăng.
xnor

@PeterTaylor Cảm ơn, đã sửa.
xnor

Câu trả lời:


6

Haskell, 167 165 ký tự

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

Thuật toán không hiệu quả, nhưng nó vẫn chạy trong vòng một giây (mặc dù không có trong bảng điều khiển tương tác) cho các đầu vào <100.

Giải trình:

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

Danh sách các vị trí lạnh được cung cấp một tập hợp các vị trí lạnh trước đó là một vị trí lạnh theo sau là danh sách các vị trí lạnh được cung cấp cho vị trí này và các vị trí trước đó (Không hiệu quả: vị trí này được tạo hai lần)

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

Một vị trí lạnh là cặp đầu tiên sao cho không có vị trí lạnh nào có thể tiếp cận được từ cặp đó (Không hiệu quả: chúng ta nên tìm kiếm từ cặp trước đó)

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

Vị trí có thể tiếp cận từ một cặp là những vị trí sao cho các phần tử đầu tiên của chúng khớp với nhau, các phần tử thứ hai của chúng khớp với nhau, phần tử khác nhau hoặc phần heap nhỏ hơn trước khi loại bỏ là phần lớn hơn sau khi loại bỏ.

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

(phương thức chính) Nếu các đống không đúng thứ tự, trao đổi chúng. Khác, nếu vị trí lạnh đầu tiên có thể tiếp cận từ một vị trí là chính vị trí đó, biểu thị sự thất bại (lý tưởng nhất là người ta sẽ quay trở lại Maybe (Int,Int)). Khác trả lại vị trí lạnh đó (Không hiệu quả: cặp nói được nhìn lên hai lần. Tệ hơn, danh sách các vị trí lạnh được tạo hai lần)


Có vẻ như " Vì vậy, một tìm kiếm cây trò chơi đệ quy theo cấp số nhân sẽ không đủ " là lạc quan, bởi vì những gì bạn mô tả âm thanh chính xác như thế.
Peter Taylor

@PeterTaylor đây là O (n ^ 4). Mỗi cặp lạnh cần O (n ^ 3) thời gian để tìm và có O (n) trong số chúng. Tối ưu hóa thế hệ sẽ đưa nó đến O (n ^ 2) (nếu tôi đọc đúng trình tự). Một thuật toán thời gian theo cấp số nhân sẽ chậm hơn nhiều. Tôi có nên chạy thử nghiệm không?
John Dvorak

Không sao đâu, tôi tin bạn.
Peter Taylor

bạn có thể xóa x@từx@(a,b)?p=...
tự hào

Không chắc làm thế nào nó đạt được điều đó. Sửa chữa, cảm ơn.
John Dvorak

5

GolfScript ( 63 57 byte)

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

Yêu cầu đầu vào từ stdin trong biểu mẫu [a b]và để lại đầu ra trên thiết bị xuất chuẩn ở dạng đó hoặc 0nếu đó là một vị trí bị mất. Bản demo trực tuyến

Tổng quat

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

tính toán danh sách các vị trí lạnh (bao gồm cả phiên bản lật [b a]cho từng vị trí lạnh [a b]) bằng cách sử dụng thuộc tính chuỗi Beatty .

Sau đó ?tìm kiếm vị trí lạnh đầu tiên thỏa mãn khối được tạo bởi

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

mà về cơ bản kiểm tra rằng vị trí có thể truy cập từ vị trí đầu vào bằng cách tính toán sự khác biệt vector và sau đó xác minh rằng nó là một trong hai [0 x], [x 0]hoặc [x x]cho một số x > 0. IMO bài kiểm tra đó là các bit thông minh: 0|$lực lượng bất kỳ mảng trong một trong những định dạng thành dạng [0 x]khi lập bản đồ [0 0]đến [0], [a b]là nơi chẳng có acũng không b0một mảng ba yếu tố, và [-x 0]hay [-x -x]đến [-x 0]. Sau đó (!*,1=kiểm tra mà chúng tôi có [0 x].

Cuối cùng 0]0=`, trường hợp dự phòng và định dạng cho đầu ra.


4

Bình 57 57 61 62

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

Hãy thử trực tuyến.

Khá giống với các câu trả lời khác, nhưng trang wikipedia đó không đưa ra nhiều thứ khác để tiếp tục;) Số ma thuật 39là số vị trí lạnh có giá trị < 99.

Xác định một chức năng gmà bạn có thể gọi như thế g 30 25. Trả về []cho thất bại, [(13,8)]về thành công.

Giải trình

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

sđược chuyển thành int - lưu một vài ký tự /____1. rZ39có thể được thay thế bởi U39, sử dụng phạm vi unary. Tương tự như vậy, bạn có thể thay thế r99)bằng U99.
isaacg

@isaacg Cảm ơn! Tôi hoàn toàn quên mất U. Tôi thực sự nên cập nhật lời giải thích: P
FryAmTheEggman

@isaacg Chỉ cần suy nghĩ về Pyth, tôi nghĩ bạn có thể thực @hiện giao cắt thiết lập nếu đối số thứ hai của nó là một danh sách ngay bây giờ. Có một chút lúng túng bị bỏ đi kể từ khi ađược thay đổi: P
FryAmTheEggman

Đó là một ý tưởng tốt - tôi đã thực hiện nó. Tôi cũng đã thay đổi mã giao lộ để cho phép một vài thủ thuật không thể có trước đây, bao gồm cả việc lấy giao điểm của hai danh sách.
isaacg

2

Javascript ES6 - 280 byte

Giảm thiểu

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)

Mở rộng

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
);

Thuật toán đẹp và nhanh chóng. Chạy trong O (n), nhưng sẽ chạy trong thời gian không đổi nếu không phải là vòng lặp tiết kiệm byte. Vòng lặp được triển khai như một phần tăng đệ quy, do đó tập lệnh cuối cùng sẽ thất bại với lỗi đệ quy cho n trong hàng trăm hoặc hàng nghìn. Sử dụng cùng một thuộc tính chuỗi Beatty mà ông Taylor đề cập, nhưng thay vì tính toán trình tự, nó chỉ đơn giản là bước tới các thuật ngữ chính xác.

Chạy đúng trên tất cả các đầu vào thử nghiệm và nhiều hàng chục bên cạnh đó tôi đã thử nghiệm.

Hàm để gọi là f. Nó trả về một mảng thành công và 0sau khi từ bỏ.


Chờ đã, xuất ra một mảng là ok?
John Dvorak

@JanDvorak: xnor có một bộ được liệt kê trong danh sách các đầu ra hợp lệ, do đó tôi đã hiểu như vậy. Có lẽ anh ta có thể làm rõ vấn đề. Đó là một sửa chữa tầm thường, ở mức nào.
COTO

Một mảng hoặc một mảng đơn của cặp là tốt; nhiều động thái chiến thắng là không.
xnor

1

Perl 5 - 109 (có 2 cờ)

#!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}}$_=$$_

Sử dụng:

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

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

Đơn giản chỉ cần tính toán giải pháp cho từng đầu vào có thể sau đó chỉ cần thực hiện một tra cứu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.