Chơi tic-tac-toe và không bao giờ thua


14

(Có tồn tại một số thách thức đòi hỏi phải sử dụng chiến lược tốt nhất, nhưng ở đây chúng tôi không có. Ngay cả khi bạn có thể giành chiến thắng, bạn được phép thực hiện hòa)

Thử thách

Viết một chương trình chơi trò chơi tic-tac-toe. Nó không được thua (do đó, nó sẽ kết thúc trò chơi bằng một trận hòa hoặc bằng chiến thắng).

Phương pháp I / O được phép

  1. Đầu vào có thể là bảng hiện tại. Bạn có thể cho rằng tất cả các động tác trước đó của người chơi thứ 2 đã được chơi bởi động cơ của bạn.
  2. Đầu vào có thể là di chuyển của người chơi đầu tiên và chức năng của bạn lưu trữ các di chuyển đã xảy ra trong quá khứ. Trong trường hợp này, hàm được gọi nhiều lần, một lần cho mỗi lần di chuyển; hoặc đầu vào nhắc nhở chức năng / chương trình nhiều lần.
  3. Bạn được phép có thêm một đầu vào cho biết nếu bạn là người chơi đầu tiên hoặc viết hai hàm (có thể liên quan) để giải quyết vấn đề của người chơi thứ nhất và người chơi thứ hai. Nếu chương trình của bạn cần sử dụng phương thức nhập 2 (nhiều cuộc gọi), bạn có thể quyết định những gì được chuyển trong cuộc gọi đầu tiên.
  4. Đầu ra có thể là bảng sau lượt của bạn.
  5. Đầu ra có thể là động thái của bạn.
  6. Di chuyển có thể được biểu diễn dưới dạng một cặp số (có thể là lập chỉ mục 0 hoặc lập chỉ mục 1), một số trong phạm vi 0 ~ 8 hoặc một số trong phạm vi 1 ~ 9.
  7. Bảng có thể được biểu diễn dưới dạng mảng 3 × 3 hoặc mảng có độ dài 9. Ngay cả khi ngôn ngữ có mảng chỉ mục 0, bạn có thể sử dụng lập chỉ mục 1.
  8. Các tế bào trên lưới có thể sử dụng bất kỳ 3 giá trị khác nhau để chỉ ra X, Ovà trống rỗng.

Tiêu chí chiến thắng

Mã ngắn nhất trong mỗi ngôn ngữ giành chiến thắng.


Nếu một mất mát được trao cho bạn thì giải pháp của bạn không hợp lệ. Bạn đang chơi với người khác, vì vậy bàn cờ sẽ không thay đổi ngay lập tức, vì vậywe can assume that all previous moves of the 2nd player were also played by our engine
l4m2


1
@ l4m2 Chỉ cần khởi động lại trình thông dịch. Làm xong. Tại sao phải bận tâm với nó? Nó chỉ cần tăng số byte một cách không cần thiết.
dùng202729


4
Đừng làm phần thưởng. Hoặc yêu cầu hoặc loại bỏ nó, không làm cho nó tùy chọn. Phần thưởng phá hỏng thử thách ..
Rɪᴋᴇʀ

Câu trả lời:


4

Befunge, 181 168 byte

>>4&5pp20555>>03>16p\::5g8%6p5v
 ^p5.:g605$_ #!<^_|#:-1g61+%8g<
543217539511|:_^#->#g0<>8+00p3+5%09638527419876
v<304p$_v#:->#$$:2`#3_:^
>#\3#13#<111v124236478689189378

Các vị trí trên bảng được đánh số từ 1 đến 9. Theo mặc định, bạn có được bước di chuyển đầu tiên, nhưng nếu bạn muốn cho phép máy tính đi trước, bạn chỉ cần nhập 0 cho lần di chuyển đầu tiên. Khi bạn đã thực hiện một động thái, máy tính sẽ phản hồi với một số chỉ ra di chuyển của họ.

Không có kiểm tra để đảm bảo bạn không nhập một nước đi hợp lệ và cũng không có kiểm tra để xem có ai thắng hay thua. Một khi chúng không còn được thực hiện nữa, chương trình sẽ đi vào một vòng lặp vô hạn.

Thử nghiệm trực tuyến này hơi khó, vì không có thông dịch viên trực tuyến nào có đầu vào tương tác. Tuy nhiên, nếu bạn biết trước những chuyển động nào bạn sẽ thực hiện (giả định rằng bạn biết máy tính sẽ phản hồi như thế nào), bạn có thể thử nghiệm trên TIO với những di chuyển được lập trình sẵn.

Người dùng chơi đầu tiên: Hãy thử trực tuyến!
Máy tính chơi đầu tiên: Hãy thử trực tuyến!

Để dễ dàng nhìn thấy những gì đang diễn ra, tôi cũng đã có một phiên bản xuất ra bảng giữa các lần di chuyển.

Người dùng chơi đầu tiên: Hãy thử trực tuyến!
Máy tính chơi đầu tiên: Hãy thử trực tuyến!

Lưu ý rằng bạn sẽ phải đợi TIO hết thời gian chờ trước khi bạn có thể xem kết quả.

Giải trình

Bảng được lưu trữ trong vùng bộ nhớ Befunge dưới dạng một mảng phẳng gồm 9 giá trị, được lập chỉ mục từ 1 đến 9. Điều này cho phép chúng tôi sử dụng bù trừ 0 như một trường hợp đặc biệt "không di chuyển" khi chúng tôi muốn cho máy tính chơi trước. Di chuyển của người chơi được lưu trữ là 4 và máy tính di chuyển là 5. Để bắt đầu với tất cả các vị trí được khởi tạo thành 32 (mặc định bộ nhớ Befunge), vì vậy, bất cứ khi nào chúng tôi truy cập bảng, chúng tôi sửa đổi bằng 8, vì vậy chúng tôi sẽ quay lại 0, 4 hoặc 5.

Với sự sắp xếp đó, nếu chúng ta tổng hợp các giá trị của bất kỳ ba vị trí nào trên bảng, chúng ta biết rằng máy tính sẽ di chuyển khỏi chiến thắng nếu tổng số là 10, người chơi sẽ di chuyển khỏi chiến thắng nếu tổng số là 8 và các vị trí được chia sẻ giữa máy tính và trình phát (nhưng vẫn còn một vị trí miễn phí) nếu tổng số là 9.

Toàn bộ chiến lược của chúng tôi dựa trên khái niệm này. Chúng tôi có một thói quen lấy một danh sách các bộ ba chỉ ra ba vị trí trên bảng, chúng tôi tính tổng của các vị trí đó và nếu tổng bằng một tổng số nhất định, máy tính sẽ di chuyển đến bất kỳ vị trí nào trong tập hợp là miễn phí.

Danh sách chính của bộ ba mà chúng tôi kiểm tra là các kết hợp chiến thắng (1/2/3, 1/5/9, 1/4/7, v.v.). Trước tiên chúng tôi tìm kiếm tổng cộng 10 (máy tính sắp thắng) và sau đó tổng cộng là 8 (người chơi sắp thắng và chúng tôi cần chặn bước di chuyển đó). Ít rõ ràng hơn, chúng tôi cũng kiểm tra tổng cộng 9 (nếu mỗi người chơi và máy tính có một trong các vị trí, đó là một chiến lược tốt để máy tính giành vị trí thứ ba).

Trước kịch bản cuối cùng đó, động thái chiến lược khác mà chúng tôi thực hiện là kiểm tra tất cả các bộ góc (1/2/4, 2/3/6, v.v.) cũng như hai kết hợp góc đối diện (1/8/9 và 3 / 7/8). Nếu bất kỳ kết hợp nào trong số này kết hợp thành 8, tức là người chơi đã đảm nhiệm hai vị trí, đó là một chiến lược tốt để máy tính có được vị trí miễn phí còn lại.

Cuối cùng, có hai trường hợp đặc biệt di chuyển. Đầu tiên, chúng tôi luôn cố gắng và giữ vị trí trung tâm trước bất kỳ động thái nào khác. Điều này đạt được với cùng một thói quen như tất cả các động tác khác của chúng tôi, chỉ cần vượt qua trong một bộ ba duy nhất, 5/5/5 và tổng số mục tiêu là 0. Ngoài ra, nếu tất cả các thử nghiệm khác không tìm thấy di chuyển, chúng tôi cố gắng thực hiện một trong những góc trên cùng như một phương sách cuối cùng Một lần nữa, điều này chỉ đơn giản đạt được bằng cách kiểm tra bộ ba 1/1/1 và 3/3/3, với tổng mục tiêu là 0.

Tôi không nghĩ rằng đây thực sự là một chiến lược hoàn hảo - có thể có những trò chơi mà máy tính rút ra có khả năng chiến thắng - nhưng nó đủ tốt để không bao giờ thua trận đấu. Tôi đã chạy một kịch bản thử nghiệm cố gắng chơi mọi di chuyển có thể có trên máy tính và với mỗi chuỗi di chuyển hợp lệ, máy tính sẽ thắng hoặc hòa trò chơi.


Tôi hoàn toàn không biết Befunge, nhưng có lẽ bạn có thể kiểm tra tất cả các đầu vào có thể ( Mẫu )
l4m2

@ l4m2 FYI, tôi hiện đã chạy một kịch bản thử nghiệm đã thử mọi động thái có thể đối với máy tính và có thể xác nhận rằng nó không bao giờ thua.
James Holdiness

2

Python 2: 399 401 349 333 317 370 byte

Sửa lỗi 2x: tín dụng cho l4m2

-52 ký tự: tín dụng vào ngầm

-16 ký tự: tín dụng cho Jonathan Frech

-26 ký tự: tín dụng cho người dùng202729

def f(b):
 t=4,9,2,3,5,7,8,1,6;n=lambda k:[t[i]for i,j in enumerate(b)if j==k];p,o,a,I=n(2),n(1),n(0),t.index
 for i in p:
    for j in p:
     for k in a:
        if i+j+k==15and-j+i:return I(k)
 for i in o:
    for j in o:
     for k in a:
        if i+j+k==15and-j+i:return I(k)
 for i in 9,3,7,1:
    if i in a and 5 in p:return I(i)
 for i in 5,4,2,8,6:
    if i in a:return I(i)
 return I(a[0])

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

Vào ngày đầu tiên của khóa học đại số tuyến tính tôi đã học vào học kỳ trước, người hướng dẫn sinh viên tốt nghiệp sắc sảo của tôi đã đề xuất rằng nếu bạn đại diện cho bảng tic-tac-toe là ma trận:

4 | 9 | 2
--+---+--
3 | 5 | 7
--+---+--
8 | 1 | 6

sau đó nhận được ba liên tiếp tương đương với việc chọn ba số trong phạm vi [1,9] cộng với 15. Câu trả lời này khai thác ý tưởng này. Hàm lấy một danh sách chứa chín số đại diện cho bảng. 0 chỉ ra một khoảng trống, 1 bị chiếm bởi đối thủ và 2 đại diện cho một lần chơi trước đó được thực hiện bởi chương trình. 3 dòng đầu tiên chỉ ra những con số mà chương trình đã chọn (p), phe đối lập đã chọn (o) và vẫn có sẵn (a). Sau đó, nó nhìn qua các số có sẵn và xem nếu có bất kỳ số nào trong số chúng, kết hợp với hai số mà nó đã chọn thêm vào mười lăm. Nếu có, nó sẽ chọn hình vuông đó và giành chiến thắng. Nếu không có động thái chiến thắng ngay lập tức, nó sẽ kiểm tra xem liệu đối thủ có thể giành chiến thắng bằng cách sử dụng cùng một phương pháp hay không. Nếu họ có thể, nó sẽ mất quảng trường chiến thắng của họ. Nếu không có chiến thắng hay chặn di chuyển có sẵn, nó sẽ di chuyển trong một góc Điều này ngăn cản một người bạn đời ngu ngốc:

- - - 
- X -
- - -

- O -             # Bad Move
- X -
- - -

- O X
- X -
- - -

- O X
- X -
O - -

- O X
- X -
O - X

Nếu không có tình huống nào xảy ra, nó sẽ chọn một hình vuông tùy ý. Hàm xuất ra một số [0,8] đại diện cho bình phương 0 được chọn bởi thuật toán.

Chỉnh sửa: Thuật toán hiện ưu tiên trung tâm trên đường chéo, điều này sẽ ngăn khả năng người bạn đời khác được chỉ ra bởi l4m2 và các chiến lược liên quan.

Chỉnh sửa: Để làm rõ, hàm sẽ lấy một bảng dưới dạng một mảng và xuất ra một bước di chuyển như một số nguyên trên [0,8]. Bởi vì chiến lược I / O này rất khó hiểu, đây là một kịch bản trình bao bọc giúp nó tương tác nhiều hơn. Nó nhận một đối số dòng lệnh, sẽ là 1 nếu người chơi đi trước và 0 nếu chương trình đi trước.

import sys

def f(b):
 t=4,9,2,3,5,7,8,1,6;n=lambda k:[t[i]for i,j in enumerate(b)if j==k];p,o,a,I=n(2),n(1),n(0),t.index
 for i in p:
    for j in p:
     for k in a:
        if i+j+k==15and-j+i:return I(k)
 for i in o:
    for j in o:
     for k in a:
        if i+j+k==15and-j+i:return I(k)
 for i in 9,3,7,1:
    if i in a and 5 in p:return I(i)
     for i in 5,4,2,8,6:
        if i in a:return I(i)
 return I(a[0])

board = [0,0,0,0,0,0,0,0,0]
rep = {0:"-",1:"X",2:"O"}

turn = int(sys.argv[1])
while True:
    for i in range(3):
        print rep[board[i*3]]+" "+rep[board[i*3+1]]+" "+rep[board[i*3+2]]
        print
    if turn:
        move = int(raw_input("Enter Move [0-8]: "))
    else:
        move = f(board)
    board[move] = turn+1
    turn = (turn+1)%2 

1
hack

1
Tất cả các returndòng của bạn ngoại trừ dòng cuối cùng có thể được đặt trên dòng trước chúng, tiết kiệm khoảng trắng
ngầm trong

1
Ngoài ra tôi không thể không tự hỏi nếu nó sẽ tiết kiệm byte để, thay vì làm e=enumerate, làm f=lambda n:[t[i]for i,j in enumerate(b)if j==n]và assign p, oasử dụng các chức năng. Chưa tính nó mặc dù
undergroundmonorail

3
Vẫn bị hack . xkcd.com/832 thực sự có ích
l4m2

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.