O jogo
Você estará jogando um jogo (quase) padrão do Connect-4 . Infelizmente, é um jogo por correspondência e alguém colocou fita preta em cada segunda linha começando de baixo, para que você não possa ver nenhum dos movimentos de seu oponente nessas linhas.
Quaisquer movimentos dentro de colunas já cheias contarão como passando o seu turno, e se um jogo durar mais do que os 6 * 7
turnos, será considerado um empate.
Especificação do desafio
Seu programa deve ser implementado como uma função Python 3. O primeiro argumento é uma "visão" do tabuleiro, representando o estado conhecido do tabuleiro como uma lista 2D de linhas de baixo para cima, onde 1
é um movimento do primeiro jogador, 2
um movimento do segundo jogador e 0
uma posição vazia ou oculta mova pelo seu oponente.
O segundo argumento é um número de turno indexado 0
e sua paridade informa qual jogador você é.
O argumento final é um estado arbitrário, inicializado None
no início de cada jogo, que você pode usar para preservar o estado entre os turnos.
Você deve retornar duas tuplas do índice da coluna que deseja reproduzir e o novo estado a ser retornado no próximo turno.
Pontuação
Uma vitória conta como +1
, um empate como 0
e uma perda como -1
. Seu objetivo é alcançar a maior pontuação média em um torneio round-robin. Vou tentar executar quantas partidas forem necessárias para identificar um vencedor claro.
Regras
Qualquer concorrente deve ter no máximo um bot concorrente ao mesmo tempo, mas não há problema em atualizar sua inscrição se você fizer melhorias. Por favor, tente limitar seu bot a ~ 1 segundo de tempo de raciocínio por turno.
Teste
Aqui está o código fonte do controlador, juntamente com alguns exemplos de bots não concorrentes para referência:
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)
Coisa feliz!
Resultados Provisórios
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
fonte
Respostas:
Este bot leva todas as vitórias seguras e volta a bloquear os rivais, adivinhe-os vertical e horizontalmente ou faça movimentos aleatórios.
Obrigado por corrigir o run_game!
Changelog:
fonte
O normalBot se baseia na suposição de que pontos no meio são mais valiosos do que pontos nas extremidades. Assim, ele usa uma distribuição normal centralizada no meio para determinar suas escolhas.
fonte
Isso é obviamente não competitivo, mas ainda assim muito útil para depuração ... e surpreendentemente divertido, se você conhece bem o seu bot para enganar: D, então estou postando na esperança de inspirar mais envios:
A grade está de cabeça para baixo (a linha inferior é mais alta). Para receber os anúncios dos vencedores, você precisa corrigir o controlador do jogo, adicionando uma declaração de impressão antes de retornar a vitória:
fonte