Грайте в тик-нок і ніколи не програйте


14

(Існують деякі виклики, які потребують використання найкращої стратегії, але тут ми цього не робимо. Навіть якщо ви зможете перемогти, вам дозволяється зробити нічию)

Виклик

Напишіть програму, яка грає в гру tic-tac-toe. Він не повинен програти (отже, він повинен закінчувати гру або нічиєю, або виграючи).

Дозволені методи вводу / виводу

  1. Вхід може бути поточною дошкою. Ви можете припустити, що всі попередні ходи 2-го гравця виконували ваш двигун.
  2. Вхід може бути кроком першого гравця, а ваша функція зберігає, які рухи відбувалися в минулому. У цьому випадку функція викликається кілька разів, один раз за кожен хід; або швидке введення функції / програми кілька разів.
  3. Вам дозволяється взяти додатковий вклад, якщо ви є першим гравцем, або написати дві (можливо, пов'язані) функції для вирішення проблеми першого гравця та другого гравця. Якщо вашій програмі потрібно використовувати метод введення 2 (багаторазовий дзвінок), ви можете вирішити, що передано під час першого дзвінка.
  4. Вихід може бути дошкою після вашої черги.
  5. Вихід може бути вашим ходом.
  6. Рух може бути представлений у вигляді пари чисел (може бути 0-індексуванням або 1-індексуванням), числа в діапазоні 0 ~ 8 або числа в діапазоні 1 ~ 9.
  7. Дошка може бути представлена ​​у вигляді масиву 3 × 3 або масиву довжиною 9. Навіть якщо мова має масив 0-індексації, ви можете використовувати 1-індексацію.
  8. Клітини на сітці можуть використовувати будь-які 3 різних значень , щоб вказати X, Oі спорожнити.

Критерії виграшу

Найкоротший код в кожній мові виграш.


Якщо вам дано програш, то рішення недійсне. Ви граєте з іншими, тому шахова дошка не вмить зміниться, томуwe can assume that all previous moves of the 2nd player were also played by our engine
l4m2


1
@ l4m2 Просто перезапустіть перекладач. Зроблено. Навіщо це турбуватися? Це просто зайве збільшення кількості байтів ні за що.
користувач202729


4
Не робіть бонус. Або вимагати його, або видаляти, не робити це необов’язковим. Бонус губить виклик ..
Rɪᴋᴇʀ

Відповіді:


4

Befunge, 181 168 байт

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

Позиції на дошці пронумеровані від 1 до 9. За замовчуванням ви отримуєте перший хід, але якщо ви хочете дозволити комп’ютеру першим рухатись, ви можете просто ввести 0 для першого переміщення. Коли ви зробили крок, комп’ютер відповість цифрою, що вказує на їх переїзд.

Немає перевірок, щоб переконатися, що ти не вступив у дійсний хід, а також немає перевірок, щоб хтось виграв чи програв. Як тільки їх більше не буде зроблено, програма просто переходить у нескінченний цикл.

Трохи складно перевірити це в Інтернеті, оскільки немає онлайн-перекладачів з інтерактивним введенням. Однак, якщо ви знаєте, які рухи ви збираєтеся зробити заздалегідь (що передбачає, що ви знаєте, як комп’ютер буде реагувати), ви можете надати тест на TIO за допомогою попередньо запрограмованих кроків.

Користувач грає першим: спробуйте онлайн!
Перший комп'ютер грає: спробуйте онлайн!

Щоб було легше бачити, що відбувається, я також отримав версію, яка виводить плату між ходами.

Користувач грає першим: спробуйте онлайн!
Перший комп'ютер грає: спробуйте онлайн!

Зауважте, що вам доведеться зачекати, коли TIO вичерпається, щоб побачити результати.

Пояснення

Плата зберігається в області пам'яті Befunge як плоский масив з 9 значень, індексований від 1 до 9. Це дозволяє нам використовувати нульове зміщення як особливий випадок "без руху", коли ми хочемо спочатку відпустити комп'ютер. Рухи гравця зберігаються як 4, а комп'ютер - як 5. Для початку всі позиції ініціалізуються на 32 (за замовчуванням пам'яті Befunge), тому кожен раз, коли ми отримуємо доступ до плати, ми модифікуємо 8, тому ми повернемося або 0, 4 або 5.

Враховуючи це розташування, якщо ми підсумовуємо значення будь-яких трьох позицій на дошці, ми знаємо, що комп’ютер знаходиться в одному кроці від виграшу, якщо загальна сума 10, гравець знаходиться в одному кроці від виграшу, якщо загальний 8 позиції діляться між комп'ютером та гравцем (але все ж одна позиція вільна), якщо загальна сума 9.

Вся наша стратегія базується на цій концепції. У нас є розпорядок, який займає перелік трійки із зазначенням наборів з трьох позицій на дошці, ми обчислюємо суму цих позицій, і якщо сума дорівнює певній загальній сумі, комп'ютер рухається до того, який із позицій у наборі вільний.

Основний список трійки, яку ми тестуємо, - це виграшні комбінації (1/2/3, 1/5/9, 1/4/7 тощо). Спочатку шукаємо в цілому 10 (комп'ютер збирається виграти), а потім 8 (гравець збирається виграти, і нам потрібно заблокувати цей хід). Менш очевидно, ми також перевіряємо на загальну кількість 9 (якщо у гравця та комп'ютера є одна з позицій, це хороша стратегія, щоб комп'ютер зайняв третє).

До цього останнього сценарію, другий стратегічний крок, який ми робимо, - це перевірити всі кутові набори (1/2/4, 2/3/6 тощо), а також дві протилежні кутові комбінації (1/8/9 та 3 / 7/8). Якщо будь-яка з цих комбінацій дорівнює 8, тобто гравець зайняв дві позиції, комп’ютер буде гарною стратегією зайняти решту вільної позиції.

Нарешті, є два особливих кроки справи. По-перше, ми завжди намагаємося займати центральне положення перед будь-яким іншим рухом. Це досягається тим самим рутином, що і всі наші інші рухи, просто пройшовши в одній трійці, 5/5/5, і цільовою сумою 0. Крім того, якщо всі інші тести не змогли знайти хід, ми намагаємося взяти один із верхніх кутів в крайньому випадку. Знову ж цього просто досягається шляхом тестування потрій 1/1/1 та 3/3/3, цільова сума 0.

Я не думаю, що це обов'язково ідеальна стратегія - можуть бути ігри, в яких комп'ютер малює, які потенційно можна було б виграти, - але це досить добре, щоб ніколи не програти матч. Я запустив тестовий сценарій, який намагався відтворити всі можливі рухи проти комп'ютера, і за кожну дійсну послідовність рухів комп'ютер або виграв, або виграв гру.


Я не зовсім знаю Befunge, але, можливо, ви можете пройти тестування всього можливого входу ( зразок )
l4m2

@ l4m2 FYI, я зараз запустив тестовий сценарій, який намагався всіляко рухатися проти комп'ютера і можу підтвердити, що він ніколи не програє.
Джеймс Холдернесс

2

Пітон 2: 399 401 349 333 317 370 байт

2x Виправлення помилок: кредит до l4m2

-52 символів: кредит на підземниймонорельс

-16 символів: кредит Джонатану Фреху

-26 знаків: кредит користувачеві202729

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

Спробуйте в Інтернеті!

У перший день курсу лінійної алгебри, який я пройшов у минулому семестрі, мій вибагливий викладач-аспірант запропонував, якщо ви представляєте дошку тик-так-носок в якості матриці:

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

то отримання трьох підряд є еквівалентним добору трьох чисел у діапазоні [1,9], які складають до 15. Ця відповідь використовує цю ідею. Функція приймає список, що містить дев'ять цифр, що представляють дошку. 0 позначає порожнє місце, 1 займає опонент, а 2 являє собою попередню гру, виконану програмою. Перші 3 рядки визначають, які числа вибрала програма (p), опозиція вибрала (o), і вони все ще доступні (a). Потім він переглядає наявні номери і бачить, чи будь-який з них у поєднанні з двома номерами, які він вже вибрав, додає до п'ятнадцяти. Якщо це вийде, він вибере цю площу і виграє. Якщо немає негайних виграшних кроків, він перевірить, чи може перемогти опонент тим же методом. Якщо вони зможуть, це займе їхній виграшний квадрат. Якщо немає переможного чи блокувального кроку, вона рухатиметься в кутку. Це запобігає дурню:

- - - 
- X -
- - -

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

- O X
- X -
- - -

- O X
- X -
O - -

- O X
- X -
O - X

Якщо жодна з цих ситуацій не відбудеться, він вибере квадрат довільно. Функція виводить число [0,8], що представляє 0 індексований квадрат, обраний алгоритмом.

Редагувати: Тепер алгоритм розставляє пріоритет по центру по діагоналі, що перешкоджатиме черговій можливості дурня, зазначеному l4m2 та пов'язаними стратегіями.

Редагувати: Для уточнення функція приймає дошку у вигляді масиву і видає рух як ціле число на [0,8]. Оскільки ця стратегія вводу-виводу настільки незграбна, ось сценарій обгортки робить її більш інтерактивною. Він займає один аргумент командного рядка, який повинен бути 1, якщо гравець піде першим, і 0, якщо програма виходить першою.

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
Усі ваші returnлінії, окрім останньої, можна поставити на лінію перед ними, економлячи пробіл
підземний

1
Крім того, я не можу допомогти , але цікаво , якщо це дозволить заощадити байти, а не робити e=enumerate, робити f=lambda n:[t[i]for i,j in enumerate(b)if j==n]і правонаступник p, oі з aдопомогою функції. Не рахували, хоча
підземниймонорельс

3
Ще зламали . xkcd.com/832 дуже допомагає
l4m2

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.