เล่น Nim ของ Wythoff อย่างสมบูรณ์แบบ


16

เป้าหมายของคุณคือการเขียนเป็นผู้เล่นที่สมบูรณ์แบบสำหรับการเล่นเกมของWythoff ของสะเดา

กฎของ NIM ของ Wythoff

Wythoff's Nim เป็นเกมที่มีผู้เล่นสองคนที่เล่นกับกองที่เหมือนกันสองกอง ผู้เล่นผลัดกันซึ่งพวกเขาทำอย่างใดอย่างหนึ่งเหล่านี้

  1. ลบหนึ่งเคาน์เตอร์ขึ้นไปจากกองแรก
  2. ลบหนึ่งเคาน์เตอร์ขึ้นไปจากกองที่สอง
  3. ลบจำนวนตัวนับเท่ากัน (อย่างน้อยหนึ่งตัว) จากทั้งกองแรกและกองที่สอง

แน่นอนว่ากองไม่สามารถติดลบได้ แต่พวกเขาสามารถทำได้ถึง 0 ผู้เล่นใดก็ตามที่นับคะแนนสุดท้ายโดยรวมชนะเกม

สำหรับความคิดเชิงเรขาคณิตที่มากขึ้นนี่คือสูตรที่เทียบเท่าของเกมที่คุณสามารถเล่นบนแอปเพล็ตนี้ ราชินีเดี่ยวเริ่มต้นบนกระดานหมากรุกที่ไม่มีที่สิ้นสุดของสี่เหลี่ยมจัตุรัสซึ่งมีมุมอยู่ทางซ้ายล่าง ผู้เล่นเลือกที่จะย้ายราชินีซึ่งเคลื่อนไหวเหมือนราชินีหมากรุก แต่ถูก จำกัด ไว้ที่สามทิศทาง:

  1. ลง
  2. ซ้าย
  3. ทแยงมุมลงและซ้าย

ใครก็ตามที่ย้ายราชินีไปยังมุมที่ชนะ

เชื่อมโยงพิกัดของราชินี (กับมุม(0,0)) กับขนาดของเสาเข็มที่เกี่ยวข้องมันเป็นเรื่องง่ายที่จะเห็นทั้งสองเกมเหมือนกัน

การเล่นที่สมบูรณ์แบบ

(คุณสามารถข้ามสิ่งนี้ได้หากคุ้นเคยกับแนวคิดของการเล่นที่สมบูรณ์แบบและการเคลื่อนไหวที่ชนะ)

ตั้งแต่ Wythoff ของสะเดาเป็นเกมที่ จำกัด และกำหนดก็มีความคิดของการเล่นที่สมบูรณ์แบบ ผู้เล่นที่สมบูรณ์แบบคือกลยุทธ์ที่มักจะชนะจากตำแหน่งที่ชนะในทางทฤษฎีซึ่งหมายถึงตำแหน่งที่มีกลยุทธ์ที่รับประกันการชนะ

เพื่อเป็นกลยุทธ์ในการชนะมันพอเพียงที่จะย้ายไปยังตำแหน่งที่ชนะตามทฤษฎีสำหรับผู้เล่นที่เพิ่งย้ายไปและทำให้ผู้เล่นไม่ไปต่อไป ครั้งแรกของตำแหน่งชนะเลิศเหล่านี้ (หรือที่เรียกว่าตำแหน่งเย็น ) (0,0), (1,2), (2,1), (3,5), (5,3)เป็น ดูบทความวิกิพีเดียสำหรับคำอธิบายเกี่ยวกับอัลกอริทึมเพื่อค้นหากลยุทธ์การชนะสำหรับนิมของ Wythoff รวมถึงสูตรการสร้างตำแหน่งการชนะ

ข้อกำหนดของโปรแกรม

เขียนโปรแกรมหรือฟังก์ชั่นใช้ตำแหน่งเป็นอินพุทและเอาท์พุทย้ายชนะในรูปแบบของตำแหน่งหลังจากที่ย้าย ไบต์ที่น้อยที่สุดจะเป็นผู้ชนะ

หากไม่มีการย้ายที่ชนะอยู่นั่นคือตำแหน่งเป็นการสูญเสียทางทฤษฎีโปรแกรมของคุณควรระบุเช่นนั้นและปรับเสีย

โปรแกรมของคุณต้องทำงานภายในระยะเวลาที่เหมาะสม ดังนั้นการค้นหาทรีเกมแบบเรียกซ้ำแบบเอ็กซ์โพเนนเชียลจะไม่เพียงพอ หากคุณต้องการคำนวณกลยุทธ์ล่วงหน้าและกำหนดรหัสให้ยาก

อินพุต

คู่ของตัวเลขที่ไม่ใช่เชิงลบที่เป็นตัวแทนของขนาดกองแต่ละที่มากที่สุด(i,j) 99นี่อาจเป็นตัวเลขสองตัว tuple รายการหรือคอนเทนเนอร์ที่คุณต้องการ

เอาท์พุต

พิมพ์หรือส่งออกตำแหน่งหลังจากที่คุณย้ายอีกครั้งเป็นตัวเลขสองหรือภาชนะดังกล่าว นี่จะเป็นการย้ายที่ถูกกฎหมายไปยังตำแหน่งที่ชนะ หากมีการเคลื่อนไหวดังกล่าวหลายครั้งผู้ใดทำได้ดี แต่มีเพียงผู้เดียวเท่านั้น

หากไม่มีการย้ายที่ชนะคุณต้องระบุสิ่งนี้ในผลลัพธ์ เอาต์พุตใด ๆ เช่นFalse, None0, หรือ(-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)    

2
+1 ส่วนหนึ่งเป็นเพราะฉันชอบความคิดที่ว่าหนึ่งในสี่ของอินฟินิตี้
เลเวลริเวอร์เซนต์

กำหนด "ระยะเวลาที่เหมาะสม" หลายวินาทีสำหรับ (100,50) ระยะเวลาพอสมควรหรือไม่?
John Dvorak

โอ้ รอ. อินพุตถูก จำกัด โดย ... 30 ??? มันค่อนข้างต่ำใช่มั้ย
John Dvorak

@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)แทน) มิฉะนั้นจะกลับมาที่ตำแหน่งความเย็น (ความไม่มีประสิทธิภาพ: คู่ที่กล่าวมานั้นถูกค้นหาสองครั้งยิ่งแย่กว่านั้นรายการของตำแหน่งที่เย็นจะถูกสร้างขึ้นสองครั้ง)


ดูเหมือนว่า " ดังนั้นการค้นหาทรีเกมแบบวนซ้ำแบบเอ็กซ์โพเนนเชียลจะไม่พอเพียง " เป็นแง่ดีเพราะสิ่งที่คุณอธิบายฟังดูเป็นอย่างนั้น
Peter Taylor

@PeterTaylor นี่คือ O (n ^ 4) คู่เย็นแต่ละคู่ใช้เวลา O (n ^ 3) ในการค้นหาและมี O (n) ของพวกเขา การปรับให้เหมาะสมของรุ่นจะนำไปที่ O (n ^ 2) (ถ้าฉันอ่านลำดับอย่างถูกต้อง) อัลกอริธึมเวลาอธิบายจะช้ากว่ามาก ฉันควรจะทำการทดสอบบ้างไหม?
John Dvorak

ไม่เป็นไรฉันเชื่อว่าคุณ
Peter Taylor

คุณสามารถลบx@จากx@(a,b)?p=...
ภูมิใจ Haskeller

ไม่แน่ใจว่ามันไปถึงที่นั่นได้อย่างไร แก้ไขขอบคุณ
John Dvorak

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 > 0IMO ทดสอบที่มีบิตฉลาด: 0|$กองกำลังใด ๆ ในอาร์เรย์หนึ่งในรูปแบบที่เป็นรูปแบบ[0 x]ในขณะที่การทำแผนที่[0 0]การ[0], [a b]ที่ค่าaมิได้bคือ0เป็นแถวสามองค์ประกอบและ[-x 0]หรือเพื่อ[-x -x] [-x 0]จากนั้นตรวจสอบว่าเรามี(!*,1=[0 x]

ในที่สุด0]0=`เคส fallback และการจัดรูปแบบสำหรับเอาต์พุต


4

Pyth 57 58 61 62 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

sถูกโยนไป int - /____1ประหยัดไม่กี่ตัวอักษรมากกว่า rZ39สามารถถูกแทนที่ด้วยโดยU39ใช้ช่วง unary ในทำนองเดียวกันคุณสามารถแทนที่ด้วยr99) U99
isaacg

@isaacg ขอบคุณ! Uฉันทั้งหมดลืมเกี่ยวกับ ฉันควรอัพเดตคำอธิบายจริงๆ: P
FryAmTheEggman

@isaacg แค่คิดเกี่ยวกับ Pyth ฉันคิดว่าคุณสามารถ@แยกการตั้งค่าได้ถ้าอาร์กิวเมนต์ที่สองเป็นรายการตอนนี้ มันออกไปเล็กน้อยอย่างเชื่องช้าตั้งแต่aเปลี่ยนไป: P
FryAmTheEggman

นั่นเป็นความคิดที่ดี - ฉันได้ติดตั้งแล้ว ฉันยังเปลี่ยนรหัสทางแยกเพื่ออนุญาตให้ใช้เทคนิคบางอย่างที่ไม่เคยเกิดขึ้นมาก่อนรวมถึงการแยกรายการสองรายการ
isaacg

2

Javascript ES6 - 280 ไบต์

minified

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ยอมแพ้


เดี๋ยวก่อนการส่งออกอาร์เรย์ก็โอเค?
John Dvorak

@JanDvorak: xnor มี tuple อยู่ในรายการผลลัพธ์ที่ใช้ได้ดังนั้นฉันจึงคิดว่า บางทีเขาสามารถชี้แจงเรื่องนี้ มันเป็นเรื่องเล็กน้อยในทุก ๆ ด้าน
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

เพียงคำนวณวิธีแก้ปัญหาสำหรับแต่ละอินพุตที่เป็นไปได้จากนั้นทำการค้นหาเพียงครั้งเดียว

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.