Gra
Będziesz grał w (prawie) standardową grę Connect-4 . Niestety, jest to gra korespondencyjna i ktoś umieścił czarną taśmę w co drugim rzędzie, zaczynając od dołu, abyś nie widział żadnego ruchu przeciwnika w tych rzędach.
Wszelkie ruchy w już wypełnionych kolumnach będą liczone jako przejście twojej tury, a jeśli gra trwa dłużej niż 6 * 7
tury, zostanie uznana za remis.
Specyfikacja wyzwania
Twój program powinien zostać zaimplementowany jako funkcja Python 3. Pierwszym argumentem jest „widok” planszy, reprezentujący znany stan planszy jako dwuwymiarową listę rzędów od dołu do góry, gdzie 1
jest ruch pierwszego gracza, 2
ruch drugiego gracza oraz 0
pusta pozycja lub ukryta pozycja porusz się przez przeciwnika.
Drugim argumentem jest indeksowany numer tury 0
, a jego parzystość mówi ci, którym jesteś graczem.
Ostatnim argumentem jest dowolny stan, inicjowany None
na początku każdej gry, którego można użyć do zachowania stanu między kolejkami.
Powinieneś zwrócić 2-krotkę indeksu kolumny, w który chcesz zagrać, i nowy stan, który zostanie ci zwrócony w następnej turze.
Punktacja
Wygrana liczy się jako +1
, remis jako 0
, a przegrana jako -1
. Twoim celem jest osiągnięcie jak najwyższego średniego wyniku w turnieju round-robin. Postaram się rozegrać tyle meczów, ile potrzeba, aby znaleźć wyraźnego zwycięzcę.
Zasady
Każdy zawodnik powinien mieć co najwyżej jednego konkurującego bota w tym samym czasie, ale poprawienie wpisu jest poprawne, jeśli wprowadzasz poprawki. Spróbuj ograniczyć bota do ~ 1 sekundy myślenia na turę.
Testowanie
Oto kod źródłowy kontrolera wraz z kilkoma niekonkurencyjnymi przykładowymi botami w celach informacyjnych:
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!
Wstępne wyniki
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
źródło
Odpowiedzi:
Ten bot bierze wszystkie pewne wygrane i cofa się, aby zablokować rywali, po drugie zgadnij ich w pionie i poziomie lub wykonuj losowe ruchy.
Dziękujemy za naprawienie gry run_game!
Dziennik zmian:
źródło
normalBot przyjmuje założenie, że plamy na środku są cenniejsze niż plamy na końcach. W związku z tym używa normalnego rozkładu wyśrodkowanego w środku, aby określić swoje wybory.
źródło
Jest to oczywiście niekonkurencyjne, ale jednak bardzo przydatne do debugowania ... i zaskakująco zabawne, jeśli znasz swojego bota wystarczająco dobrze, aby oszukiwać: D, więc mam nadzieję zainspirować więcej zgłoszeń:
Siatka jest odwrócona (dolny rząd jest najwyższy). Aby otrzymać ogłoszenie o zwycięzcy, musisz załatać kontroler gier, dodając instrukcję drukowania przed zwróceniem wygranej:
źródło