Re-Pen! (Punti e scatole)


23

Questa è una sfida del re della collina per Dots and Boxes (aka Pen the Pig). Il gioco è semplice, al tuo turno basta tracciare una linea su una recinzione vuota. Ogni volta che completi un quadrato ottieni un punto. Inoltre, dato che stiamo giocando secondo le regole del campionato , se completi almeno un quadrato nel tuo turno otterrai un turno extra. Questo è un torneo round robin, in cui ogni bot si gioca l'un l'altro due volte 12 volte su una griglia 9x9. Dai un'occhiata a questa partita tra due titani dei pesi massimi, in cui ChainCollector produce carne tritata del co-campione in carica Asdf: inserisci qui la descrizione dell'immagine

Regole

  1. 0,5 secondi limite di tempo per mossa.
  2. Non interferire con altri robot.
  3. Utilizzare PigPen.random () e PigPen.random (int) per casualità.
  4. Nessuna scrittura su file.
  5. Il bot e tutti i suoi dati persistenti verranno resettati ogni volta che cambia l'avversario (ogni 12 round).

Motori di ricerca

Ogni bot estende Player.java:

package pigpen;

public abstract class Player {

public abstract int[] pick(Board board, int id, int round); 

}

Boardè il tabellone, che serve principalmente per darti accesso alle Penclassi, ed idè il tuo ID giocatore (ti dice se sei il primo o il secondo), roundti dice in quale round di gioco contro lo stesso avversario (1 o 2). Il valore restituito è an int[], dove il primo elemento è penID (1 indicizzato) e il secondo elemento è fenceID (0 indicizzato). Vedere Pen.pick(int)per un modo semplice per generare questo valore di ritorno. Vedi la pagina di Github per esempio giocatori e JavaDoc. Poiché stiamo usando solo una griglia quadrata, ignoriamo tutte le funzioni e i campi relativi agli esagoni.

Come correre

  1. Scarica Source da Github.
  2. Scrivi il tuo robot controller (assicurati di includerlo package pigpen.players) e mettilo nella src/cartella;
  3. Compila con javac -cp src/* -d . src/*.java. Esegui con java pigpen.Tournament 4 9 9 false(gli ultimi due numeri possono essere modificati per regolare la dimensione della griglia. L'ultima variabile deve essere impostata su truese si desidera utilizzare il software pp_record.)

I punteggi

  1. ChainCollector: 72
  2. Asdf: 57
  3. Lazybones: 51
  4. Stazione di finitura: 36
  5. = LinearPlayer: 18
  6. = BackwardPlayer: 18
  7. RandomPlayer: 0

Guarda anche:

Nota : questo gioco è una sfida competitiva e non facilmente risolvibile, dato che offre ai giocatori un turno extra per completare una scatola.

Grazie a Nathan Merrill e Darrel Hoffman per la consulenza su questa sfida!

Aggiornamenti :

  • Aggiunto un moves(int player)metodo alla classe Board per ottenere un elenco di ogni mossa che un giocatore ha fatto.

Taglie indefinite (100 Rep) :

La prima persona a pubblicare una soluzione che vince ogni round e utilizza la strategia (adeguando il gioco in base all'osservazione di come gioca l'avversario).


2
BONTÀ. Il finisher è waaayyyy OP! : P
El'endia Starman,

@ El'endiaStarman Lol, tutto ciò che fa è finire una Penna con un recinto disponibile, oppure scegliere una Penna con il maggior numero di recinti rimanenti. RandomPlayer è semplicemente casuale.
geokavel,

2
Si lo so. È solo che il punteggio finale è 79-2 e RandomPlayer ha ottenuto solo quelle ultime due caselle perché doveva . : P
El'endia Starman,

Ciao! Voglio creare il mio bot, ma ho una domanda. Pen.BOTTOM alla riga 0 col 0 restituirà gli stessi valori di Pen.TOP alla riga 1 col 0?
tuskiomi,

@tusk Sì, lo fa
geokavel,

Risposte:


6

pigrone

Questo bot è pigro. Prende un punto e una direzione casuali e continua a costruire in quella direzione senza muoversi troppo. Ci sono solo 2 casi in cui fa qualcosa di diverso:

  • "guadagna" chiudendo un piolo con solo 1 recinto rimanente
  • scegli un nuovo punto e una nuova direzione se non è possibile posizionare la recinzione o consentirebbe all'altro bot di "fare soldi"
package pigpen.players;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import pigpen.Board;
import pigpen.Pen;
import pigpen.PigPen;
import pigpen.Player;

public class Lazybones extends Player {
    private static class Fence {
        private static boolean isOk(Board board, boolean vertical, int row, int col) {
            if (vertical) {
                Pen left = board.getPenAt(row, col - 1);
                Pen right = board.getPenAt(row, col);
                if (left.id() < 0 && right.id() < 0 ||
                        left.fences()[Pen.RIGHT] > 0 ||
                        right.fences()[Pen.LEFT] > 0 ||
                        left.remaining() == 2 ||
                        right.remaining() == 2) {
                    return false;
                }
            } else {
                Pen top = board.getPenAt(row - 1, col);
                Pen bottom = board.getPenAt(row, col);
                if (top.id() < 0 && bottom.id() < 0 ||
                        top.fences()[Pen.BOTTOM] > 0 ||
                        bottom.fences()[Pen.TOP] > 0 ||
                        top.remaining() == 2 ||
                        bottom.remaining() == 2) {
                    return false;
                }
            }
            return true;
        }

        private static Fence pickRandom(Board board) {
            List<Fence> ok = new ArrayList<>();
            List<Fence> notOk = new ArrayList<>();
            for (int row = 0; row < board.rows; row ++) {
                for (int col = 0; col < board.cols; col ++) {
                    (isOk(board, false, row, col) ? ok : notOk)
                            .add(new Fence(false, row, col));
                    (isOk(board, true, row, col) ? ok : notOk)
                            .add(new Fence(true, row, col));
                }
                (isOk(board, true, row, board.cols) ? ok : notOk)
                        .add(new Fence(true, row, board.cols));
            }
            for (int col = 0; col < board.cols; col ++) {
                (isOk(board, false, board.rows, col) ? ok : notOk)
                        .add(new Fence(false, board.rows, col));
            }
            if (ok.isEmpty()) {
                return notOk.get(PigPen.random(notOk.size()));
            } else {
                return ok.get(PigPen.random(ok.size()));
            }
        }

        private final boolean vertical;
        private final int row;
        private final int col;

        public Fence(boolean vertical, int row, int col) {
            super();
            this.vertical = vertical;
            this.row = row;
            this.col = col;
        }

        private Fence next(Board board, boolean negative) {
            int newRow = vertical ? (negative ? row - 1 : row + 1) : row;
            int newCol = vertical ? col : (negative ? col - 1 : col + 1);
            if (isOk(board, vertical, newRow, newCol)) {
                return new Fence(vertical, newRow, newCol);
            } else {
                return null;
            }
        }

        private int[] getResult(Board board) {
            if (vertical) {
                if (col < board.cols) {
                    return board.getPenAt(row, col).pick(Pen.LEFT);
                } else {
                    return board.getPenAt(row, col - 1).pick(Pen.RIGHT);
                }
            } else {
                if (row < board.rows) {
                    return board.getPenAt(row, col).pick(Pen.TOP);
                } else {
                    return board.getPenAt(row - 1, col).pick(Pen.BOTTOM);
                }
            }
        }
    }

    private Fence lastFence = null;
    private boolean negative = false;

    @Override
    public int[] pick(Board board, int id, int round) {
        List<Pen> money = board.getList().stream()
                .filter(p -> p.remaining() == 1).collect(Collectors.toList());
        if (!money.isEmpty()) {
            return money.get(PigPen.random(money.size())).pick(Pen.TOP);
        }
        if (lastFence != null) {
            lastFence = lastFence.next(board, negative);
        }
        if (lastFence == null) {
            lastFence = Fence.pickRandom(board);
            negative = PigPen.random(2) == 0;
        }
        return lastFence.getResult(board);
    }
}

Wow, bel lavoro! LazyBones possiede il finisher (vedi nuova animazione).
geokavel,

A proposito, proprio così tutti lo sanno, un altro modo per portare la penna a sinistra di una data penna è pen.n(Pen.LEFT)(funzione vicino).
geokavel,

Inoltre, penso che non sia necessario quando controlli la recinzione inferiore di una penna e la recinzione superiore di quella sottostante, sono garantiti per avere lo stesso valore!
geokavel,

Il pick()metodo ora ha un int roundparametro alla fine, quindi se potessi aggiungerlo.
geokavel,

Devo controllare entrambi i recinti, perché uno qualsiasi degli oggetti penna può essere al di fuori del tabellone (id == -1). Per lo stesso motivo non posso usare la funzione vicino.
Sleafar,

6

ChainCollector

A questo bot piacciono le catene 1 . Ne vuole il maggior numero possibile. A volte sacrifica persino una piccola parte di una catena per vincerne una più grande.

[1] Una catena è costituita da penne collegate da recinzioni aperte, in cui ogni penna ha 1 o 2 recinzioni aperte. Se una singola penna appartenente alla catena può essere finita, a causa della regola del campionato anche l'intera catena può essere finita.

package pigpen.players;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.TreeMap;

import pigpen.Board;
import pigpen.Pen;
import pigpen.Player;

public class ChainCollector extends Player {
    private enum Direction {
        TOP, RIGHT, BOTTOM, LEFT;

        public Direction opposite() {
            return values()[(ordinal() + 2) % 4];
        }
    }

    private enum ChainEndType {
        OPEN, CLOSED, LOOP
    }

    private static class PenEx {
        private final int id;
        private final List<Fence> openFences = new ArrayList<>();
        private boolean used = false;

        public PenEx(int id) {
            super();
            this.id = id;
        }

        public void addOpenFence(Direction direction, PenEx child) {
            openFences.add(new Fence(this, direction, child));
            if (child != null) {
                child.openFences.add(new Fence(child, direction.opposite(), this));
            }
        }
    }

    private static class Fence {
        public final PenEx parent;
        public final Direction direction;
        public final PenEx child;

        public Fence(PenEx parent, Direction direction, PenEx child) {
            super();
            this.parent = parent;
            this.direction = direction;
            this.child = child;
        }

        public int[] getMove() {
            if (parent == null) {
                return new int[] { child.id, direction.opposite().ordinal() };
            } else {
                return new int[] { parent.id, direction.ordinal() };
            }
        }
    }

    private static class Moves {
        private final TreeMap<Integer, List<Fence>> map = new TreeMap<>();

        public void add(int score, Fence move) {
            List<Fence> list = map.get(score);
            if (list == null) {
                list = new ArrayList<>();
                map.put(score, list);
            }
            list.add(move);
        }

        public boolean isEmpty() {
            return map.isEmpty();
        }

        public boolean hasExactlyOne() {
            return map.size() == 1 && map.firstEntry().getValue().size() == 1;
        }

        public int getLowestScore() {
            return map.firstKey();
        }

        public int[] getLowMove() {
            return map.firstEntry().getValue().get(0).getMove();
        }

        public int[] getHighMove() {
            return map.lastEntry().getValue().get(0).getMove();
        }
    }

    private static class BoardEx {
        private final List<PenEx> pens = new ArrayList<>();
        private final Moves neutralMoves = new Moves();
        private final Moves finisherMoves = new Moves();
        private final Moves safeFinisherMoves = new Moves();
        private final Moves sacrificeMoves = new Moves();
        private final Moves badMoves = new Moves();

        public BoardEx(Board board) {
            super();
            PenEx[][] tmp = new PenEx[board.rows][board.cols];
            for (int row = 0; row < board.rows; ++row) {
                for (int col = 0; col < board.cols; ++col) {
                    Pen pen = board.getPenAt(row, col);
                    int[] fences = pen.fences();
                    PenEx penEx = new PenEx(pen.id());
                    tmp[row][col] = penEx;
                    pens.add(penEx);
                    if (fences[Pen.TOP] == 0) {
                        penEx.addOpenFence(Direction.TOP, row == 0 ? null : tmp[row - 1][col]);
                    }
                    if (row == board.rows - 1 && fences[Pen.BOTTOM] == 0) {
                        penEx.addOpenFence(Direction.BOTTOM, null);
                    }
                    if (fences[Pen.LEFT] == 0) {
                        penEx.addOpenFence(Direction.LEFT, col == 0 ? null : tmp[row][col - 1]);
                    }
                    if (col == board.cols - 1 && fences[Pen.RIGHT] == 0) {
                        penEx.addOpenFence(Direction.RIGHT, null);
                    }
                }
            }
        }

        private ChainEndType followChain(Fence begin, List<Fence> result) {
            Fence current = begin;
            for (;;) {
                current.parent.used = true;
                result.add(current);
                if (current.child == null) {
                    return ChainEndType.OPEN;
                }
                List<Fence> childFences = current.child.openFences;
                switch (childFences.size()) {
                    case 1:
                        current.child.used = true;
                        return ChainEndType.CLOSED;
                    case 2:
                        if (current.child == begin.parent) {
                            return ChainEndType.LOOP;
                        } else {
                            current = current.direction.opposite() == childFences.get(0).direction ?
                                    childFences.get(1) : childFences.get(0);
                        }
                        break;
                    case 3:
                    case 4:
                        return ChainEndType.OPEN;
                    default:
                        throw new IllegalStateException();
                }
            }
        }

        public void findChains() {
            for (PenEx pen : pens) {
                if (!pen.used && pen.openFences.size() > 0) {
                    if (pen.openFences.size() < 3) {
                        List<Fence> fences = new ArrayList<>();
                        ChainEndType type1 = pen.openFences.size() == 1 ?
                                ChainEndType.CLOSED : followChain(pen.openFences.get(1), fences);
                        if (type1 == ChainEndType.LOOP) {
                            badMoves.add(fences.size(), fences.get(0));
                        } else {
                            Collections.reverse(fences);
                            ChainEndType type2 = followChain(pen.openFences.get(0), fences);
                            if (type1 == ChainEndType.OPEN && type2 == ChainEndType.CLOSED) {
                                type1 = ChainEndType.CLOSED;
                                type2 = ChainEndType.OPEN;
                                Collections.reverse(fences);
                            }
                            if (type1 == ChainEndType.OPEN) {
                                badMoves.add(fences.size() - 1, fences.get(fences.size() / 2));
                            } else if (type2 == ChainEndType.CLOSED) {
                                finisherMoves.add(fences.size() + 1, fences.get(0));
                                if (fences.size() == 3) {
                                    sacrificeMoves.add(fences.size() + 1, fences.get(1));
                                } else {
                                    safeFinisherMoves.add(fences.size() + 1, fences.get(0));
                                }

                            } else {
                                finisherMoves.add(fences.size(), fences.get(0));
                                if (fences.size() == 2) {
                                    sacrificeMoves.add(fences.size(), fences.get(1));
                                } else {
                                    safeFinisherMoves.add(fences.size(), fences.get(0));
                                }
                            }
                        }
                    } else {
                        pen.used = true;
                        for (Fence fence : pen.openFences) {
                            if (fence.child == null || fence.child.openFences.size() > 2) {
                                neutralMoves.add(fence.child == null ? 0 : fence.child.openFences.size(), fence);
                            }
                        }
                    }
                }
            }
        }

        public int[] bestMove() {
            if (!neutralMoves.isEmpty()) {
                if (!finisherMoves.isEmpty()) {
                    return finisherMoves.getHighMove();
                }
                return neutralMoves.getHighMove();
            }
            if (!safeFinisherMoves.isEmpty()) {
                return safeFinisherMoves.getHighMove();
            }
            if (badMoves.isEmpty() && !finisherMoves.isEmpty()) {
                return finisherMoves.getHighMove();
            }
            if (!sacrificeMoves.isEmpty()) {
                if (sacrificeMoves.hasExactlyOne()) {
                    if (badMoves.getLowestScore() - sacrificeMoves.getLowestScore() >= 2) {
                        return sacrificeMoves.getLowMove();
                    } else {
                        return finisherMoves.getHighMove();
                    }
                } else {
                    return finisherMoves.getHighMove();
                }
            }
            if (!badMoves.isEmpty()) {
                return badMoves.getLowMove();
            }
            return null;
        }
    }

    @Override
    public int[] pick(Board board, int id, int round) {
        BoardEx boardEx = new BoardEx(board);
        boardEx.findChains();
        return boardEx.bestMove();
    }
}

Wow, grazie per il tuo contributo. Sono onorato che qualcuno abbia dedicato così tanto tempo a un progetto che ho creato. Penso che l'introduzione di questo bot abbia influito sulla generazione di numeri casuali, in modo tale che Asdf ora batte Lazybones entrambe le volte con un leggero margine.
geokavel,

Bene, l'idea per il bot sembrava piuttosto semplice prima di iniziare, e poi volevo finirla. ;) Con la casualità coinvolta dovresti probabilmente lasciare che i robot giochino più di 2 giochi per ottenere risultati più accurati.
Sleafar,

Buon pensiero. L'ho aumentato a 12 round per matchup e ora, come puoi vedere, Asdf ha un leggero vantaggio. Anche a 100 round, vince solo 13 partite in più rispetto a Lazybones.
geokavel,

3

finitore

package pigpen.players;

import pigpen.*;

import java.util.*;

/**
 * Picks a Pen with only one fence remaining. 
 * Otherwise picks one with the most fences remaining
 */
public class Finisher extends Player implements Comparator<Pen> {


  public int[] pick(Board board, int id) {
     return Collections.max(board.getList(),this).pick(Pen.TOP);

  }

  @Override
  public int compare(Pen p1, Pen p2) {
    //1 remaining is best, all remaining is second.
    int r1 = p1.remaining();
    int r2 = p2.remaining();
    if(r1 == 1) r1 = 7;
    if(r2 == 1) r2 = 7;
    return Integer.compare(r1,r2);
 }


}

Utilizza un comparatore per selezionare la penna con le recinzioni più disponibili, ma dà la priorità alla penna con solo 1 recinto disponibile. (7 viene utilizzato anziché 5 per consentire a questo codice di funzionare anche in modalità esagonale)


3

asdf

Assegna un punteggio a ciascuna recinzione e quindi sceglie il meglio da esse. Ad esempio: una penna con una recinzione aperta ha un punteggio di 10, mentre una penna con 2 recinzioni aperte ha un punteggio di -8.

Sembra che Lazybones usi una strategia simile, perché si lega a questo bot.

package pigpen.players;

import java.util.*;
import pigpen.*;

public class Asdf extends Player {
    private final List<Score> scores = new ArrayList<>();

    @Override
    public int[] pick(Board board, int id, int round) {
        scores.clear();
        List<Pen> pens = board.getList();

        pens.stream().filter(x -> !x.closed()).forEach((Pen p) -> evaluate(p));
        Optional<Score> best = scores.stream().max(Comparator.comparingInt(p -> p.points));

        if (best.isPresent()) {
            Score score = best.get();
            return score.pen.pick(score.fence);
        }
        return null;
    }

    private void evaluate(Pen pen) {
        int[] fences = pen.fences();
        for (int i = 0; i < fences.length; i++) {
            if (fences[i] == 0) {
                int points = getPoints(pen);
                Pen neighbour = pen.n(i);
                if (neighbour.id() != -1) {
                    points += getPoints(neighbour);
                }
                scores.add(new Score(pen, i, points));
            }
        }
    }

    private int getPoints(Pen pen) {
        switch (pen.remaining()) {
            case 1: return 10;
            case 2: return -1;
            case 3: return 1;
        }
        return 0;
    }

    class Score {
        private Pen pen;
        private int fence;
        private int points;

        Score(Pen pen, int fence, int points) {
            this.pen = pen;
            this.fence = fence;
            this.points = points;
        }
    }
}

Ecco i punteggi. È interessante notare che chi va secondo ottiene il doppio dei punti. Asdf vs. Lazybones: 27-54; Lazybones vs. Asdf: 27-54
geokavel

@geokavel Sì, perché poi i robot sono costretti a fare un "brutto turno", quindi l'avversario può chiudere una penna.
CommonGuy

È questo il miglior algoritmo possibile, allora?
solo il

@justhalf Non lo è, perché la gente gioca a questo gioco nei campionati. Penso che questi algoritmi possano essere sicuramente ampliati. Vedi i link che ho fornito per maggiori informazioni.
geokavel,

0

LinearPlayer

package pigpen.players;

import pigpen.*;

/**
 * Picks the first available fence in the first available Pen
 */ 
public class LinearPlayer extends Player {


@Override
public int[] pick(Board board, int id) {
    for(int p = 1;p<=board.size;p++) {
        Pen pen = board.get(p);
            if(!pen.closed()) {
                int[] fences = pen.fences();
                    for(int i =0;i<fences.length;i++) {
                        if(fences[i] == 0) {
                            return new int[]{pen.id(),i};
                        }
                    }
                }
        }
    return new int[]{1,0};
    } 
}

Il modo più semplice per scrivere questo bot è in realtà return null, perché una voce non valida selezionerà automaticamente il primo recinto disponibile. Questo codice non utilizza alcun metodo di scelta rapida e genera manualmente il valore restituito.


0

BackwardPlayer

package pigpen.players;

import pigpen.*;

/**
 * Picks the first available fence in the last available Pen
 */
 public class BackwardPlayer extends Player {

public int[] pick(Board board, int id) {
    for(int i = board.size;i>0;i--) {
        Pen p = board.get(i);
        if(!p.closed()) {
            return p.pick(Pen.TOP);
        }
    }
    return new int[] {1,0};
}
}

Questo codice utilizza il metodo di scelta rapida Pen.pick(int)per generare il valore restituito. Se la recinzione superiore non è disponibile, selezionerà la recinzione più vicina disponibile andando in senso orario.


0

RandomPlayer

package pigpen.players;

import pigpen.*;


/** 
 * Picks the first available fence in a random Pen 
 */
public class RandomPlayer extends Player {
    public int[] pick(Board board, int id) {
        int pen = PigPen.random(board.size)+1;
        return board.get(pen).pick(Pen.TOP);
    }
}

Stessa idea di BackwardPlayer, ma seleziona casualmente una penna. Nota +1perché le penne sono 1 indicizzate.

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.