Частично наблюдаемый Connect-4

8

Игра

Вы будете играть (почти) в стандартную игру Connect-4 . К сожалению, это игра по переписке, и кто-то поместил черную ленту на каждом втором ряду, начиная снизу, чтобы вы не могли видеть какие-либо движения вашего противника в этих рядах.

Любые ходы в уже заполненных столбцах будут считаться проходящими ваш ход, и если игра длится дольше 6 * 7ходов, она будет рассматриваться как ничья.

Спецификация вызова

Ваша программа должна быть реализована как функция Python 3. Первый аргумент - это «представление» доски, представляющее известное состояние доски в виде двухмерного списка строк снизу вверх, где 1происходит движение первым игроком, 2движение вторым игроком и 0пустая позиция или скрытая двигаться вашим противником.

Второй аргумент - это номер хода, индексируемый 0, и его четность говорит вам, какой вы игрок.

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

Вы должны вернуть 2 кортежа индекса столбца, который вы хотите воспроизвести, и новое состояние, которое будет возвращено вам в следующем ходу.

счет

Выигрыш считается как +1, ничья как 0, а проигрыш как -1. Ваша цель - набрать наивысший средний балл в круговом турнире. Я постараюсь провести столько матчей, сколько потребуется, чтобы определить явного победителя.

правила

Любой участник должен иметь не более одного конкурирующего бота одновременно, но все в порядке, если вы вносите улучшения. Пожалуйста, постарайтесь ограничить вашего бота до ~ 1 секунды времени на обдумывание.

тестирование

Вот исходный код для контроллера, вместе с несколькими неконкурентными примерами ботов для справки:

import itertools
import random

def get_strides(board, i, j):
    yield ((i, k) for k in range(j + 1, 7))
    yield ((i, k) for k in range(j - 1, -1, -1))
    yield ((k, j) for k in range(i + 1, 6))
    yield ((k, j) for k in range(i - 1, -1, -1))
    directions = [(1, 1), (-1, -1), (1, -1), (-1, 1)]
    def diag(di, dj):
        i1 = i
        j1 = j
        while True:
            i1 += di
            if i1 < 0 or i1 >= 6:
                break
            j1 += dj
            if j1 < 0 or j1 >= 7:
                break
            yield (i1, j1)
    for d in directions:
        yield diag(*d)

DRAWN = 0
LOST = 1
WON = 2
UNDECIDED = 3

def get_outcome(board, i, j):
    if all(board[-1]):
        return DRAWN
    player = board[i][j]
    strides = get_strides(board, i, j)
    for _ in range(4):
        s0 = next(strides)
        s1 = next(strides)
        n = 1
        for s in (s0, s1):
            for i1, j1 in s:
                if board[i1][j1] == player:
                    n += 1
                    if n >= 4:
                        return WON
                else:
                    break
    return UNDECIDED

def apply_move(board, player, move):
    for i, row in enumerate(board):
        if board[i][move] == 0:
            board[i][move] = player
            outcome = get_outcome(board, i, move)
            return outcome
    if all(board[-1]):
        return DRAWN
    return UNDECIDED

def get_view(board, player):
    view = [list(row) for row in board]
    for i, row in enumerate(view):
        if i % 2:
            continue
        for j, x in enumerate(row):
            if x == 3 - player:
                row[j] = 0
    return view

def run_game(player1, player2):
    players = {1 : player1, 2 : player2}
    board = [[0] * 7 for _ in range(6)]
    states = {1 : None, 2 : None}
    for turn in range(6 * 7):
        p = (turn % 2) + 1
        player = players[p]
        view = get_view(board, p)
        move, state = player(view, turn, states[p])
        outcome = apply_move(board, p, move)
        if outcome == DRAWN:
            return DRAWN
        elif outcome == WON:
            return p
        else:
            states[p] = state
    return DRAWN

def get_score(counts):
    return (counts[WON] - counts[LOST]) / float(sum(counts))

def run_tournament(players, rounds=10000):
    counts = [[0] * 3 for _ in players]
    for r in range(rounds):
        for i, player1 in enumerate(players):
            for j, player2 in enumerate(players):
                if i == j:
                    continue
                outcome = run_game(player1, player2)
                if outcome == DRAWN:
                    for k in i, j:
                        counts[k][DRAWN] += 1
                else:
                    if outcome == 1:
                        w, l = i, j
                    else:
                        w, l = j, i
                    counts[w][WON] += 1
                    counts[l][LOST] += 1
        ranks = sorted(range(len(players)), key = lambda i: get_score(counts[i]), reverse=True)
        print("Round %d of %d\n" % (r + 1, rounds))
        rows = [("Name", "Draws", "Losses", "Wins", "Score")]
        for i in ranks:
            name = players[i].__name__
            score = get_score(counts[i])
            rows.append([name + ":"] + [str(n) for n in counts[i]] + ["%6.3f" % score])
        lengths = [max(len(s) for s in col) + 1 for col in zip(*rows)]
        for i, row in enumerate(rows):
            padding = ((n - len(s)) * ' ' for s, n in zip(row, lengths))
            print(''.join(s + p for s, p in zip(row, padding)))
            if i == 0:
                print()
        print()

def random_player(view, turn, state):
    return random.randrange(0, 7), state

def constant_player(view, turn, state):
    return 0, state

def better_random_player(view, turn, state):
    while True:
        j = random.randrange(0, 7)
        if view[-1][j] == 0:
            return j, state

def better_constant_player(view, turn, state):
    for j in range(7):
        if view[-1][j] == 0:
            return j, state

players = [random_player, constant_player, better_random_player, better_constant_player]

run_tournament(players)

С Днем Рождения!

Предварительные результаты

Name                    Draws Losses Wins  Score  

zsani_bot:              40    5377   94583  0.892 
better_constant_player: 0     28665  71335  0.427 
constant_player:        3     53961  46036 -0.079 
normalBot:              38    64903  35059 -0.298 
better_random_player:   192   71447  28361 -0.431 
random_player:          199   75411  24390 -0.510 
user1502040
источник
Не могли бы вы объяснить, почему вы проверяете представление [-1] [j] == 0? Я не совсем уверен, что вижу, где вы их заполнили, и мои знания Python кажутся немного ржавыми.
Barbarian772
@ Barbarian772 Я проверяю, есть ли еще место в этом столбце. Обратите внимание, что есть 6 рядов, поэтому верхний ряд полностью соблюдается.
user1502040
1
Вы не должны рассчитывать размещение в уже заполненных столбцах как проход. Многие из 4 подключенных игр заканчиваются только одним заполненным столбцом, и если один игрок проиграет, играя в этом столбце, это приведет к игровому ничьему, если в противном случае это будет вынужденный выигрыш одного игрока.
soktinpk
@soktinpk Разве это не добавляет еще один уровень стратегии? В конце концов, Connect-4 - решенная игра, поэтому коэффициент пропуска хода может быть достаточным для изменения правила, так что участники не могут просто использовать стандартные алгоритмы.
mypetlion
1
Нулевая индексация снизу, это проклеенные ряды (0,2,4,6) или (1,3,5)? Некоторое искусство ASCII было бы полезно.
SIGSTACKFAULT

Ответы:

6

Этот бот берет все верные победы и отступает, чтобы блокировать соперников, затем угадывать их по вертикали и горизонтали или делать случайные шаги.

импорт печатных изданий, математика, коллекции, копия
def zsani_bot_2 (просмотр, поворот, состояние):
    если состояние == Нет: #first собственный ход - всегда для среднего
        state = (1, 2) если turn == 0 else (2, 1) # (my_symbol, ваш символ)
        #print (pprint.pformat (view) + 'Turn:' + str (turn) + 'Player:' + str (state [0]))
        возврат 3, состояние

    # найти очевидные моменты
    для i в диапазоне (1, 6): # пропустить первый ряд
        для j в диапазоне (len (view [i])): #TODO: оптимизировать с помощью zip. Идти для ясности сейчас
            если посмотреть [i] [j]! = 0 и посмотреть [i-1] [j] == 0:
                view [i-1] [j] = state [1]
    враг_поинтов = math.floor (ход / 2)
    ++ вражеские точки, если состояние [0] == 2, еще вражеские точки
    known_points = sum ([i.count (состояние [1]) для i в представлении])
    отсутствующие_поинты = враг_поинты - известные_пункты

    # уверен, что победит в любом направлении
    для j в диапазоне (0, 7): # каждый столбец
        для i в диапазоне (4, -1, -1):
            если посмотреть [i] [j]! = 0:
                перерыв # найти самую высокую известную заполненную точку
        if (not missing_points или i + 1 в {1, 3, 5}):
            view1 = copy.deepcopy (view)
            попытка = apply_move (view1, состояние [0], j)
            если попытка == ВЫИГРАЛА:
               # print (pprint.pformat (view) + 'Turn:' + str (turn) + 'Player:' + str (state [0]) + 'ход победителя')
                возврат j, состояние

    # блок уверен, что враг победит в любом направлении
    для j в диапазоне (0, 7):
        для i в диапазоне (4, -1, -1):
            если посмотреть [i] [j]! = 0:
                перерыв # найти самую высокую известную заполненную точку
        if (not missing_points или (i + 1 в {1, 3, 5})):
            view1 = copy.deepcopy (view)
            попытка = apply_move (view1, состояние [1], j)
            если попытка == ВЫИГРАЛА:
              # print (pprint.pformat (view) + 'Turn:' + str (turn) + 'Player:' + str (state [0]) + 'спасительный ход')
                возврат j, состояние

    # блоки стен
    для i в диапазоне (0, 3): # невозможно получить 4 подряд, когда столбец заполнен
        для j в диапазоне (0, 6):
            если посмотреть [i] [j]! = 0 и посмотреть [i] [j] == посмотреть [i + 1] [j] и посмотреть [i + 2] [j] == посмотреть [i + 3] [j ] == 0:
             # print (pprint.pformat (view) + 'Turn:' + str (turn) + 'Player:' + str (state [0]) + 'перемещение столбца')
                возврат j, состояние

    # блокировать платформы, если обладаете идеальной информацией в строке ниже и точка отбрасывания
    для я в диапазоне (0, 5):
        для j в диапазоне (0, 3):
            stats = collection.Counter ([просмотр [i] [j], просмотр [i] [j + 1], просмотр [i] [j + 2], просмотр [i] [j + 3]])
            если stats [0] == 2 и (stats [состояние [0]] == 2 или stats [состояние [0]] == 2):
                для k в диапазоне (0, 3):
                    если посмотреть [i] [j + k] == 0:
                        перемена
                если (i == 0 или просмотр [i-1] [j + k]! = 0) и (не пропущенные_точки или i в {1, 3, 5}):
                    #print (pprint.pformat (view) + 'Turn:' + str (turn) + 'Player:' + str (state [0]) + 'перемещение платформы')
                    возврат j + k, состояние
                еще:
                    для l в диапазоне (k, 3):
                        если посмотреть [i] [j + l] == 0:
                            перемена
                        если (i == 0 или просмотр [i-1] [j + l]! = 0) и (не пропущенные_точки или i в {1, 3, 5}):
                     # print (pprint.pformat (view) + 'Turn:' + str (turn) + 'Player:' + str (state [0]) + 'перемещение платформы')
                            возврат j + l, состояние

    #fallback -> random
    пока верно:
        j = random.randrange (0, 7)
        если посмотреть [-1] [j] == 0:
            #print (pprint.pformat (view) + 'Turn:' + str (turn) + 'Player:' + str (state [0]) + 'random move')
            возврат j, состояние

Спасибо за исправление run_game!

Changelog:

  • v2 добавляет горизонтальную блокировку - если в ряду 4 есть два пустых пятна и два пятна, заполненные одним и тем же игроком, он попытается заполнить одно из них, чтобы иметь три в ряду / заблокировать ряд противников, что, будем надеяться, использовать в следующих поворотах.
Syfer Polski
источник
3
Добро пожаловать на сайт. Я проголосовал за то, чтобы отклонить редактирование и изменить его на код, лучше всего оставить комментарий, чтобы ОП мог решить, что делать с кодом.
Специальный охотник на Гарф
У меня недостаточно репутации, чтобы комментировать основной пост. Как мне отменить редактирование?
Syfer Polski
Нет необходимости снимать изменения (я не думаю, что вы можете в любом случае). В будущем будет достаточно комментирования, но поскольку вы сказали это в своем ответе, вполне вероятно, что ОП увидит. Плюс я думаю, что ОП увидит то, что вы предложили и отредактируете, даже если оно было отклонено.
Специальный охотник на Гарф
Причина, по которой я хотел бы отменить редактирование, заключается в том, что я пропустил одну строку в изменениях, без которых отредактированный код не сможет работать полностью. Я включил это в предложение по редактированию в своем посте. Спасибо за помощь!
Syfer Polski
2

normalBot играет при условии, что пятна в середине более ценны, чем пятна на концах. Таким образом, он использует нормальное распределение по центру, чтобы определить свой выбор.

def normalBot(view, turn, state):
    randomNumber = round(np.random.normal(3, 1.25))
    fullColumns = []
    for i in range(7):
        if view[-1][i] != 0:
            fullColumns.append(i)
    while (randomNumber > 6) or (randomNumber < 0) or (randomNumber in fullColumns):
        randomNumber = round(np.random.normal(3, 1.25))
    return randomNumber, state
Боб Кратчит
источник
-1

Это явно не конкурирует, но, тем не менее, очень полезно для отладки ... и удивительно весело, если вы достаточно хорошо знаете своего бота, чтобы обманывать: D, поэтому я пишу в надежде вдохновить больше представлений:

import math, pprint
def manual_bot(view, turn, state):
    if state == None:
        state = (1, 2) if turn == 0 else (2, 1) #(my_symbol, your symbol)

#locate obvious points
    for row in range (1, 6):
        for j in range(len(view[row])):
            if view[row][j] != 0 and view[row-1][j] == 0:
                view[row-1][j] = state[1]

#if you're second, the opponent has one more point than half the turns
    enemy_points = math.ceil(turn/2)
    known_points = sum([row.count(state[1]) for row in view])
    missing_points = enemy_points - known_points

    print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0]) + ' Missing points: ' + str(missing_points))
    while True:
        try:
            move = int(input("What is your move?(0-6) "))
        except ValueError:
            continue
        if move in {0, 1, 2, 3, 4, 5, 6}:
            return move, state

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

elif outcome == WON:
    print(pprint.pformat(board) + ' Turn: ' + str(turn) +' Winner: '+ str(p))
    return p

[[0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Ход: 0 Игрок: 1 Недостающих очков: 0
Каков твой ход? (0-6) 3
[[0, 0, 0, 1, 0, 0, 0],
 [0, 0, 0, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Ход: 2 Игрок: 1 Недостающих очков: 0
Каков твой ход? (0-6) 2
[[0, 0, 1, 1, 0, 0, 0],
 [0, 0, 0, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Ход: 4 Игрок: 1 Недостающих очков: 1
Каков твой ход? (0-6) 4
[[0, 0, 1, 1, 1, 0, 0],
 [0, 0, 0, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Ход: 6 Игрок: 1 Недостающих очков: 2
Какой твой ход? (0-6) 1
[[2, 1, 1, 1, 1, 2, 0],
 [0, 0, 0, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Ход: 6 Победитель: 1
[[0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Ход: 1 Игрок: 2 Недостающих очков: 1
Каков твой ход? (0-6) 2
[[0, 0, 2, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Ход: 3 Игрок: 2 Недостающих очков: 2
Каков твой ход? (0-6) 3
[[0, 0, 2, 1, 0, 0, 0],
 [0, 0, 1, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Ход: 5 Игрок: 2 Недостающих очков: 1
Каков твой ход? (0-6) 4
[[0, 0, 2, 1, 2, 0, 0],
 [0, 0, 1, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Ход: 7 Игрок: 2 Недостающих очков: 2
Какой твой ход? (0-6) 1
[[0, 2, 2, 1, 2, 0, 0],
 [0, 0, 1, 2, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Ход: 9 Игрок: 2 Недостающих очков: 1
Каков твой ход? (0-6) 2
[[0, 2, 2, 1, 2, 0, 0],
 [0, 0, 1, 2, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Ход: 11 Игрок: 2 Недостающих очков: 1
Каков твой ход? (0-6) 4
[[0, 2, 2, 1, 2, 0, 0],
 [0, 0, 1, 2, 2, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Ход: 13 Игрок: 2 Недостающих очков: 2
Каков твой ход? (0-6) 4
[[0, 2, 2, 1, 2, 0, 0],
 [0, 1, 1, 2, 2, 0, 0],
 [0, 0, 1, 0, 1, 0, 0],
 [0, 0, 1, 0, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Ход: 15 Игрок: 2 Недостающих очков: 1
Каков твой ход? (0-6) 3
[[0, 2, 2, 1, 2, 0, 0],
 [0, 1, 1, 2, 2, 0, 0],
 [0, 0, 1, 2, 1, 0, 0],
 [0, 0, 1, 0, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Ход: 17 Игрок: 2 Недостающих очков: 2
Каков твой ход? (0-6) 5
[[0, 2, 2, 1, 2, 1, 1],
 [0, 1, 1, 2, 2, 2, 1],
 [0, 0, 1, 2, 1, 0, 0],
 [0, 0, 1, 0, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Ход: 19 Игрок: 2 Недостающих очков: 0
Каков твой ход? (0-6) 
Каков твой ход? (0-6) 6
[[0, 2, 2, 1, 2, 1, 1],
 [0, 1, 1, 2, 2, 2, 1],
 [0, 0, 1, 2, 1, 0, 2],
 [0, 0, 1, 0, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Ход: 21 Игрок: 2 Недостающих очков: 1
Какой твой ход? (0-6) 1
[[0, 2, 2, 1, 2, 1, 1],
 [0, 1, 1, 2, 2, 2, 1],
 [0, 2, 1, 2, 1, 0, 2],
 [0, 1, 1, 0, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Ход: 23 Игрок: 2 Недостающих очков: 1
Каков твой ход? (0-6) 3
[[0, 2, 2, 1, 2, 1, 1],
 [0, 1, 1, 2, 2, 2, 1],
 [0, 2, 1, 2, 1, 0, 2],
 [0, 1, 1, 2, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Ход: 25 Игрок: 2 Недостающих очков: 2
Каков твой ход? (0-6) 6
[[0, 2, 2, 1, 2, 1, 1],
 [0, 1, 1, 2, 2, 2, 1],
 [0, 2, 1, 2, 1, 0, 2],
 [0, 1, 1, 2, 2, 0, 2],
 [0, 0, 2, 1, 0, 0, 0],
 [0, 0, 1, 1, 0, 0, 0]] Ход: 27 Игрок: 2 Недостающих очков: 1
Каков твой ход? (0-6) 5
[[1, 2, 2, 1, 2, 1, 1],
 [1, 1, 1, 2, 2, 2, 1],
 [0, 2, 1, 2, 1, 2, 2],
 [0, 1, 1, 2, 2, 0, 2],
 [0, 0, 2, 1, 0, 0, 0],
 [0, 0, 1, 1, 0, 0, 0]] Ход: 29 Игрок: 2 Недостающих очков: 0
Каков твой ход? (0-6) 5
[[1, 2, 2, 1, 2, 1, 1],
 [1, 1, 1, 2, 2, 2, 1],
 [0, 2, 1, 2, 1, 2, 2],
 [0, 1, 1, 2, 2, 2, 2],
 [0, 0, 2, 1, 0, 0, 0],
 [0, 0, 1, 1, 0, 0, 0]] Ход: 29 Победитель: 2
Раунд 1 из 1

Имя Рисует Потери Победы Счет
manual_bot: 0 0 2 1.000 zsani_bot_2: 0 2 0 -1,000

Syfer Polski
источник