El juego
Jugarás un juego (casi) estándar de Connect-4 . Desafortunadamente, es un juego por correspondencia y alguien ha colocado cinta negra en cada segunda fila comenzando desde abajo, de modo que no puede ver ninguno de los movimientos de su oponente dentro de estas filas.
Cualquier movimiento dentro de columnas ya completas contará como pasar tu turno, y si un juego dura más de un 6 * 7
turno, se adjudicará como un empate.
Especificación de desafío
Su programa debe implementarse como una función de Python 3. El primer argumento es una 'vista' del tablero, que representa el estado conocido del tablero como una lista 2D de filas de abajo hacia arriba donde 1
hay un movimiento del primer jugador, 2
un movimiento del segundo jugador y 0
una posición vacía u oculta moverse por tu oponente.
El segundo argumento es un número de turno indexado 0
, y su paridad te dice de qué jugador eres.
El argumento final es un estado arbitrario, inicializado al None
comienzo de cada juego, que puedes usar para preservar el estado entre turnos.
Debes devolver una tupla de 2 tuplas del índice de columna que deseas jugar y el nuevo estado que se te devolverá el próximo turno.
Puntuación
Una victoria cuenta como +1
, un empate como 0
y una pérdida como -1
. Su objetivo es lograr el puntaje promedio más alto en un torneo round-robin. Trataré de ejecutar tantos partidos como sea necesario para identificar un ganador claro.
Reglas
Cualquier competidor debe tener como máximo un robot competidor en cualquier momento, pero está bien actualizar su entrada si realiza mejoras. Intenta limitar tu bot a ~ 1 segundo de tiempo de reflexión por turno.
Pruebas
Aquí está el código fuente del controlador, junto con algunos ejemplos de bots no competitivos para referencia:
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)
Happy KoTHing!
Resultados provisionales
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
fuente
Respuestas:
Este bot se lleva todas las victorias seguras y retrocede para bloquear a los rivales, luego adivinarlos vertical y horizontalmente o hacer movimientos aleatorios.
Gracias por arreglar run_game!
Registro de cambios:
fuente
normalBot juega bajo el supuesto de que los puntos en el medio son más valiosos que los puntos en los extremos. Por lo tanto, utiliza una distribución normal centrada en el medio para determinar sus elecciones.
fuente
Obviamente, esto no es competitivo, pero de todos modos es muy útil para la depuración ... y sorprendentemente divertido, si conoces a tu bot lo suficientemente bien como para engañar: D, así que publico con la esperanza de inspirar más presentaciones:
La cuadrícula está al revés (la fila inferior es la más alta). Para obtener los anuncios de los ganadores, debe parchear el controlador del juego, agregando una declaración de impresión antes de devolver la victoria:
fuente