Le jeu
Vous jouerez à un jeu (presque) standard de Connect-4 . Malheureusement, c'est un jeu de correspondance et quelqu'un a placé du ruban noir sur chaque deuxième rangée en partant du bas, de sorte que vous ne pouvez voir aucun des mouvements de votre adversaire dans ces rangées.
Tout mouvement dans des colonnes déjà pleines comptera comme passant votre tour, et si un jeu dure plus longtemps que les 6 * 7
tours, il sera jugé comme un match nul.
Spécifications du défi
Votre programme doit être implémenté en tant que fonction Python 3. Le premier argument est une «vue» du plateau, représentant l'état connu du plateau sous la forme d'une liste 2D de lignes de bas en haut où se 1
trouve un mouvement du premier joueur, 2
un mouvement du deuxième joueur, et 0
une position vide ou cachée bougez par votre adversaire.
Le deuxième argument est un numéro de tour indexé 0
, et sa parité vous indique de quel joueur vous êtes.
Le dernier argument est un état arbitraire, initialisé None
au début de chaque partie, que vous pouvez utiliser pour conserver l'état entre les tours.
Vous devez retourner un 2-tuple de l'index de la colonne que vous souhaitez jouer, et le nouvel état à vous rendre au prochain tour.
Notation
Une victoire compte pour +1
, un nul pour 0
et une perte pour -1
. Votre objectif est d'atteindre le score moyen le plus élevé dans un tournoi à la ronde. J'essaierai d'organiser autant de matchs que nécessaire pour identifier un vainqueur clair.
Règles
Tout concurrent doit avoir au plus un bot concurrent à la fois, mais il est OK de mettre à jour votre inscription si vous apportez des améliorations. Veuillez essayer de limiter votre bot à environ 1 seconde de temps de réflexion par tour.
Essai
Voici le code source du contrôleur, avec quelques exemples de robots non concurrents pour référence:
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)
Joyeux KoTHing!
Résultats provisoires
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
la source
Réponses:
Ce bot prend toutes les victoires, et recule pour bloquer les rivaux, les deviner ensuite verticalement et horizontalement ou faire des mouvements aléatoires.
Merci d'avoir réparé run_game!
Journal des modifications:
la source
normalBot joue sur l'hypothèse que les taches au milieu ont plus de valeur que les taches aux extrémités. Ainsi, il utilise une distribution normale centrée au milieu pour déterminer ses choix.
la source
C'est évidemment non compétitif, mais néanmoins très utile pour le débogage ... et étonnamment amusant, si vous connaissez assez bien votre bot pour tricher: D donc je poste dans l'espoir d'inspirer plus de soumissions:
La grille est à l'envers (la rangée du bas est la plus haute). Pour obtenir les annonces des gagnants, vous devez patcher le contrôleur de jeu, en ajoutant une déclaration d'impression avant de retourner la victoire:
la source