Parzialmente osservabile Connect-4


8

Il gioco

Giocherai a un gioco (quasi) standard di Connect-4 . Sfortunatamente, si tratta di un gioco di corrispondenza e qualcuno ha inserito un nastro nero su ogni seconda fila a partire dal basso, in modo da non poter vedere nessuna delle mosse del tuo avversario all'interno di queste file.

Qualsiasi mossa all'interno di colonne già piene verrà considerata come passare il tuo turno, e se una partita dura più a lungo dei 6 * 7turni verrà giudicata come un pareggio.

Specifica della sfida

Il tuo programma dovrebbe essere implementato come una funzione Python 3. Il primo argomento è una "vista" del tabellone, che rappresenta lo stato del tabellone noto come un elenco 2D di righe dal basso verso l'alto in cui si 1trova una mossa del primo giocatore, 2una mossa del secondo giocatore e 0una posizione vuota o nascosta mossa dal tuo avversario.

Il secondo argomento è un numero di turno indicizzato da 0, e la sua parità ti dice quale giocatore sei.

L'argomento finale è uno stato arbitrario, inizializzato Noneall'inizio di ogni partita, che puoi usare per preservare lo stato tra i turni.

Dovresti restituire una 2 tupla dell'indice della colonna che desideri giocare e il nuovo stato deve essere restituito al tuo prossimo turno.

punteggio

Una vittoria vale come +1, un pareggio come 0e una perdita come -1. Il tuo obiettivo è raggiungere il punteggio medio più alto in un torneo round robin. Cercherò di eseguire tutte le partite necessarie per identificare un chiaro vincitore.

Regole

Ogni concorrente dovrebbe avere al massimo un bot concorrente alla volta, ma è OK aggiornare la tua iscrizione se apporti miglioramenti. Prova a limitare il tuo bot a ~ 1 secondo di tempo di riflessione per turno.

analisi

Ecco il codice sorgente per il controller, insieme ad alcuni bot di esempio non concorrenti per riferimento:

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!

Risultati provvisori

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 

Potresti spiegare perché controlli la vista [-1] [j] == 0? Non sono del tutto sicuro di vedere dove li hai riempiti e la mia conoscenza di Python sembra essere un po 'arrugginita.
Barbarian772,

@ Barbarian772 Sto verificando se c'è ancora spazio in quella colonna. Si noti che ci sono 6 righe in modo che la riga superiore sia completamente osservata.
user1502040

1
Non dovresti contare l'inserimento in colonne già piene come passaggio. Molte partite connesse 4 terminano con una sola colonna non riempita e se un giocatore perderà giocando in quella colonna, ciò farà pareggiare la partita quando altrimenti si tratterebbe di una vittoria forzata per un giocatore.
soktinpk,

@soktinpk Questo non aggiunge semplicemente un altro livello di strategia? Dopo tutto, Connect-4 è un gioco risolto, quindi il fattore di salto dei turni potrebbe essere sufficiente per un cambio di regola che i partecipanti non possono semplicemente usare gli algoritmi standard.
mypetlion,

1
Indicizzazione zero dal basso, le righe registrate (0,2,4,6) o (1,3,5)? Qualche arte ASCII sarebbe utile.
SIGSTACKFAULT,

Risposte:


6

Questo robot prende tutte le vittorie e torna indietro per bloccare i rivali, indovinandoli in verticale e in orizzontale o facendo mosse casuali.

importare pprint, matematica, raccolte, copia
def zsani_bot_2 (vista, svolta, stato):
    if state == None: #first proprio turno - sempre per il centro
        state = (1, 2) se turn == 0 else (2, 1) # (my_symbol, il tuo simbolo)
        #print (pprint.pformat (visualizza) + 'Turn:' + str (turn) + 'Player:' + str (state [0]))
        ritorno 3, stato

    #locate punti ovvi
    per i nell'intervallo (1, 6): #skip prima riga
        per j nell'intervallo (len (visualizza [i])): #TODO: ottimizza con zip. Vai per chiarezza ora
            se visualizza [i] [j]! = 0 e visualizza [i-1] [j] == 0:
                visualizza [i-1] [j] = state [1]
    nemico_punti = piano matematico (turno / 2)
    ++ nemici_punti se state [0] == 2 altri nemici_punti
    known_points = sum ([i.count (state [1]) per i in vista])
    missing_points = nemici_punti - known_points

    #get sicuro vince in qualsiasi direzione
    per j nell'intervallo (0, 7): # ogni colonna
        per i nell'intervallo (4, -1, -1):
            se view [i] [j]! = 0:
                rompi # trova il punto più alto conosciuto
        if (not missing_points o i + 1 in {1, 3, 5}):
            view1 = copy.deepcopy (visualizza)
            tentativo = apply_move (view1, state [0], j)
            se tentativo == VINTO:
               # print (pprint.pformat (visualizza) + 'Turn:' + str (turn) + 'Player:' + str (state [0]) + 'move move')
                return j, state

    #blocca che il nemico vince in qualsiasi direzione
    per j nell'intervallo (0, 7):
        per i nell'intervallo (4, -1, -1):
            se view [i] [j]! = 0:
                rompi # trova il punto più alto conosciuto
        if (not missing_points o (i + 1 in {1, 3, 5})):
            view1 = copy.deepcopy (visualizza)
            tentativo = apply_move (view1, state [1], j)
            se tentativo == VINTO:
              # print (pprint.pformat (view) + 'Turn:' + str (turn) + 'Player:' + str (state [0]) + 'save move')
                return j, state

    #blocchi pareti
    per i nell'intervallo (0, 3): #impossibile ottenere 4 di fila quando la colonna è piena
        per j nell'intervallo (0, 6):
            if view [i] [j]! = 0 e visualizza [i] [j] == visualizza [i + 1] [j] e visualizza [i + 2] [j] == visualizza [i + 3] [j ] == 0:
             # print (pprint.pformat (view) + 'Turn:' + str (turn) + 'Player:' + str (state [0]) + 'move move')
                return j, state

    #blocca le piattaforme se inserisci informazioni perfette sulla riga sottostante e sul punto di rilascio
    per i nell'intervallo (0, 5):
        per j nell'intervallo (0, 3):
            stats = collections.Counter ([visualizza [i] [j], visualizza [i] [j + 1], visualizza [i] [j + 2], visualizza [i] [j + 3]])
            se stats [0] == 2 e (stats [stato [0]] == 2 o stats [stato [0]] == 2):
                per k nell'intervallo (0, 3):
                    se visualizza [i] [j + k] == 0:
                        rompere
                if (i == 0 o visualizza [i-1] [j + k]! = 0) e (not missing_points o i in {1, 3, 5}):
                    #print (pprint.pformat (visualizza) + 'Turn:' + str (turn) + 'Player:' + str (state [0]) + 'move platform')
                    ritorna j + k, state
                altro:
                    per l nell'intervallo (k, 3):
                        se visualizza [i] [j + l] == 0:
                            rompere
                        if (i == 0 o visualizza [i-1] [j + l]! = 0) e (non missing_points o i in {1, 3, 5}):
                     # print (pprint.pformat (view) + 'Turn:' + str (turn) + 'Player:' + str (state [0]) + 'move platform')
                            ritorna j + l, stato

    #fallback -> random
    mentre vero:
        j = random.randrange (0, 7)
        se vista [-1] [j] == 0:
            #print (pprint.pformat (view) + 'Turn:' + str (turn) + 'Player:' + str (state [0]) + 'move random')
            return j, state

Grazie per aver risolto run_game!

changelog:

  • v2 aggiunge il blocco orizzontale - se, in una riga di 4, ci sono due punti vuoti e due punti riempiti dallo stesso giocatore, tenterà di riempire uno di essi per avere tre di fila / bloccare la fila degli avversari, che si spera essere capitalizzato nei turni seguenti.

3
Benvenuti nel sito. Ho votato per rifiutare la modifica per passare al codice, sarebbe meglio lasciarlo come commento, in questo modo l'OP può decidere cosa fare del codice.
Ad Hoc Garf Hunter

Non ho abbastanza reputazione per commentare il post principale. Come posso ritirare una modifica?
Syfer Polski,

Non è necessario ritirare la modifica (non credo che tu possa comunque). In futuro i commenti saranno sufficienti, ma dal momento che l'hai detto nella tua risposta è probabile che l'OP vedrà. Inoltre, penso che l'OP vedrà che hai suggerito e modificato anche se è stato rifiutato.
Ad Hoc Garf Hunter

Il motivo per cui mi piacerebbe ritirare la modifica è perché ho perso una riga nelle modifiche, senza la quale il codice modificato non sarà completamente eseguito. L'ho incluso nel suggerimento di modifica nel mio post. Grazie per l'aiuto!
Syfer Polski,

2

normalBot gioca sul presupposto che i punti nel mezzo sono più preziosi dei punti alle estremità. Pertanto, utilizza una distribuzione normale centrata nel mezzo per determinare le sue scelte.

def normalBot(view, turn, state):
    randomNumber = round(np.random.normal(3, 1.25))
    fullColumns = []
    for i in range(7):
        if view[-1][i] != 0:
            fullColumns.append(i)
    while (randomNumber > 6) or (randomNumber < 0) or (randomNumber in fullColumns):
        randomNumber = round(np.random.normal(3, 1.25))
    return randomNumber, state

-1

Questo è ovviamente non competitivo, ma comunque molto utile per il debug ... e sorprendentemente divertente, se conosci il tuo bot abbastanza bene da imbrogliare: D, quindi sto postando nella speranza di ispirare più invii:

import math, pprint
def manual_bot(view, turn, state):
    if state == None:
        state = (1, 2) if turn == 0 else (2, 1) #(my_symbol, your symbol)

#locate obvious points
    for row in range (1, 6):
        for j in range(len(view[row])):
            if view[row][j] != 0 and view[row-1][j] == 0:
                view[row-1][j] = state[1]

#if you're second, the opponent has one more point than half the turns
    enemy_points = math.ceil(turn/2)
    known_points = sum([row.count(state[1]) for row in view])
    missing_points = enemy_points - known_points

    print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0]) + ' Missing points: ' + str(missing_points))
    while True:
        try:
            move = int(input("What is your move?(0-6) "))
        except ValueError:
            continue
        if move in {0, 1, 2, 3, 4, 5, 6}:
            return move, state

La griglia è capovolta (la riga inferiore è la più alta). Per ottenere gli annunci dei vincitori, devi patchare il controller di gioco, aggiungendo una dichiarazione di stampa prima di restituire la vincita:

elif outcome == WON:
    print(pprint.pformat(board) + ' Turn: ' + str(turn) +' Winner: '+ str(p))
    return p

[[0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Giro: 0 Giocatore: 1 Punti mancanti: 0
Qual è la tua mossa? (0-6) 3
[[0, 0, 0, 1, 0, 0, 0],
 [0, 0, 0, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turno: 2 Giocatore: 1 Punti mancanti: 0
Qual è la tua mossa? (0-6) 2
[[0, 0, 1, 1, 0, 0, 0],
 [0, 0, 0, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turno: 4 Giocatore: 1 Punti mancanti: 1
Qual è la tua mossa? (0-6) 4
[[0, 0, 1, 1, 1, 0, 0],
 [0, 0, 0, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turno: 6 Giocatore: 1 Punti mancanti: 2
Qual è la tua mossa? (0-6) 1
[[2, 1, 1, 1, 1, 2, 0],
 [0, 0, 0, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Giro: 6 Vincitore: 1
[[0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turno: 1 giocatore: 2 punti mancanti: 1
Qual è la tua mossa? (0-6) 2
[[0, 0, 2, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turno: 3 Giocatore: 2 Punti mancanti: 2
Qual è la tua mossa? (0-6) 3
[[0, 0, 2, 1, 0, 0, 0],
 [0, 0, 1, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turno: 5 Giocatore: 2 Punti mancanti: 1
Qual è la tua mossa? (0-6) 4
[[0, 0, 2, 1, 2, 0, 0],
 [0, 0, 1, 2, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turno: 7 Giocatore: 2 Punti mancanti: 2
Qual è la tua mossa? (0-6) 1
[[0, 2, 2, 1, 2, 0, 0],
 [0, 0, 1, 2, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0]] Turno: 9 Giocatore: 2 Punti mancanti: 1
Qual è la tua mossa? (0-6) 2
[[0, 2, 2, 1, 2, 0, 0],
 [0, 0, 1, 2, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Turno: 11 Giocatore: 2 Punti mancanti: 1
Qual è la tua mossa? (0-6) 4
[[0, 2, 2, 1, 2, 0, 0],
 [0, 0, 1, 2, 2, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Turno: 13 Giocatore: 2 Punti mancanti: 2
Qual è la tua mossa? (0-6) 4
[[0, 2, 2, 1, 2, 0, 0],
 [0, 1, 1, 2, 2, 0, 0],
 [0, 0, 1, 0, 1, 0, 0],
 [0, 0, 1, 0, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Turno: 15 Giocatore: 2 Punti mancanti: 1
Qual è la tua mossa? (0-6) 3
[[0, 2, 2, 1, 2, 0, 0],
 [0, 1, 1, 2, 2, 0, 0],
 [0, 0, 1, 2, 1, 0, 0],
 [0, 0, 1, 0, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Turno: 17 Giocatore: 2 Punti mancanti: 2
Qual è la tua mossa? (0-6) 5
[[0, 2, 2, 1, 2, 1, 1],
 [0, 1, 1, 2, 2, 2, 1],
 [0, 0, 1, 2, 1, 0, 0],
 [0, 0, 1, 0, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Turno: 19 Giocatore: 2 Punti mancanti: 0
Qual è la tua mossa? (0-6) 
Qual è la tua mossa? (0-6) 6
[[0, 2, 2, 1, 2, 1, 1],
 [0, 1, 1, 2, 2, 2, 1],
 [0, 0, 1, 2, 1, 0, 2],
 [0, 0, 1, 0, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Turno: 21 Giocatore: 2 Punti mancanti: 1
Qual è la tua mossa? (0-6) 1
[[0, 2, 2, 1, 2, 1, 1],
 [0, 1, 1, 2, 2, 2, 1],
 [0, 2, 1, 2, 1, 0, 2],
 [0, 1, 1, 0, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Turno: 23 Giocatore: 2 Punti mancanti: 1
Qual è la tua mossa? (0-6) 3
[[0, 2, 2, 1, 2, 1, 1],
 [0, 1, 1, 2, 2, 2, 1],
 [0, 2, 1, 2, 1, 0, 2],
 [0, 1, 1, 2, 2, 0, 0],
 [0, 0, 2, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0]] Turno: 25 Giocatore: 2 Punti mancanti: 2
Qual è la tua mossa? (0-6) 6
[[0, 2, 2, 1, 2, 1, 1],
 [0, 1, 1, 2, 2, 2, 1],
 [0, 2, 1, 2, 1, 0, 2],
 [0, 1, 1, 2, 2, 0, 2],
 [0, 0, 2, 1, 0, 0, 0],
 [0, 0, 1, 1, 0, 0, 0]] Turno: 27 Giocatore: 2 Punti mancanti: 1
Qual è la tua mossa? (0-6) 5
[[1, 2, 2, 1, 2, 1, 1],
 [1, 1, 1, 2, 2, 2, 1],
 [0, 2, 1, 2, 1, 2, 2],
 [0, 1, 1, 2, 2, 0, 2],
 [0, 0, 2, 1, 0, 0, 0],
 [0, 0, 1, 1, 0, 0, 0]] Turno: 29 Giocatore: 2 Punti mancanti: 0
Qual è la tua mossa? (0-6) 5
[[1, 2, 2, 1, 2, 1, 1],
 [1, 1, 1, 2, 2, 2, 1],
 [0, 2, 1, 2, 1, 2, 2],
 [0, 1, 1, 2, 2, 2, 2],
 [0, 0, 2, 1, 0, 0, 0],
 [0, 0, 1, 1, 0, 0, 0]] Giro: 29 Vincitore: 2
Round 1 di 1

Il nome disegna il punteggio delle vittorie perse
manual_bot: 0 0 2 1.000 zsani_bot_2: 0 2 0 -1.000

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.