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 * 7turno, 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 1hay un movimiento del primer jugador, 2un movimiento del segundo jugador y 0una 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 Nonecomienzo 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 0y 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.
importar pprint, matemáticas, colecciones, copiar def zsani_bot_2 (ver, girar, estado): if state == None: #primer turno propio - siempre para el medio state = (1, 2) if turn == 0 else (2, 1) # (my_symbol, your symbol) #print (pprint.pformat (view) + 'Turn:' + str (turn) + 'Player:' + str (state [0])) retorno 3, estado # ubicar puntos obvios para i en rango (1, 6): #skip primera fila para j en rango (len (ver [i])): #TODO: Optimizar con zip. Ve por la claridad ahora if view [i] [j]! = 0 y view [i-1] [j] == 0: vista [i-1] [j] = estado [1] puntos_enemigos = piso de matemáticas (turno / 2) ++ puntos_enemigos si el estado [0] == 2 más puntos_enemigos puntos_conocidos = suma ([i.count (estado [1]) para i a la vista]) missing_points = puntos_enemigos - puntos_conocidos # Asegúrate de ganar en cualquier dirección para j en rango (0, 7): # cada columna para i en rango (4, -1, -1): si vista [i] [j]! = 0: romper # encontrar el punto relleno más alto conocido if (no faltan_puntos o i + 1 en {1, 3, 5}): view1 = copy.deepcopy (ver) intento = apply_move (view1, estado [0], j) si intento == GANADO: # print (pprint.pformat (ver) + 'Turno:' + str (turno) + 'Jugador:' + str (estado [0]) + 'movimiento ganador') retorno j, estado #bloquea que el enemigo gane en cualquier dirección para j en el rango (0, 7): para i en rango (4, -1, -1): si vista [i] [j]! = 0: romper # encontrar el punto relleno más alto conocido if (no faltan_puntos o (i + 1 en {1, 3, 5})): view1 = copy.deepcopy (ver) intento = apply_move (view1, estado [1], j) si intento == GANADO: # print (pprint.pformat (ver) + 'Turno:' + str (turno) + 'Jugador:' + str (estado [0]) + 'movimiento de guardado') retorno j, estado #muros de bloque para i en el rango (0, 3): # imposible obtener 4 en una fila cuando la columna está llena para j en el rango (0, 6): if view [i] [j]! = 0 y view [i] [j] == view [i + 1] [j] y view [i + 2] [j] == view [i + 3] [j ] == 0: # print (pprint.pformat (view) + 'Turn:' + str (turn) + 'Player:' + str (state [0]) + 'column move') retorno j, estado #bloquear plataformas si posee información perfecta en la fila de abajo y el punto de caída para i en rango (0, 5): para j en el rango (0, 3): stats = collections.Counter ([ver [i] [j], ver [i] [j + 1], ver [i] [j + 2], ver [i] [j + 3]]) if stats [0] == 2 and (stats [state [0]] == 2 o stats [state [0]] == 2): para k en el rango (0, 3): si ver [i] [j + k] == 0: rotura if (i == 0 o view [i-1] [j + k]! = 0) y (no faltan_puntos o i en {1, 3, 5}): #print (pprint.pformat (ver) + 'Turno:' + str (turno) + 'Jugador:' + str (estado [0]) + 'movimiento de plataforma') devuelve j + k, estado más: para l en rango (k, 3): si ver [i] [j + l] == 0: rotura if (i == 0 o view [i-1] [j + l]! = 0) y (no faltan_puntos o i en {1, 3, 5}): # print (pprint.pformat (ver) + 'Turno:' + str (turno) + 'Jugador:' + str (estado [0]) + 'movimiento de plataforma') devuelve j + l, estado #backback -> aleatorio mientras cierto: j = random.randrange (0, 7) si la vista [-1] [j] == 0: #print (pprint.pformat (ver) + 'Turno:' + str (turno) + 'Jugador:' + str (estado [0]) + 'movimiento aleatorio') retorno j, estadoGracias 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