King-Pen! (Points et boîtes)


23

C'est un roi du défi de la colline pour Dots and Boxes (alias Pen the Pig). Le jeu est simple, à votre tour, tracez une ligne sur une clôture vide. Chaque fois que vous terminez un carré, vous obtenez un point. De plus, puisque nous jouons selon les règles du championnat , si vous terminez au moins une case à votre tour, vous obtenez un tour supplémentaire. Il s'agit d'un tournoi à la ronde, où chaque bot se joue deux fois 12 fois sur une grille 9x9. Découvrez ce match entre deux titans lourds, où ChainCollector fait de la viande hachée du co-champion en titre Asdf: entrez la description de l'image ici

Règles

  1. 0,5 seconde limite de temps par coup.
  2. Aucune interférence avec d'autres robots.
  3. Utilisez PigPen.random () et PigPen.random (int) pour l'aléatoire.
  4. Pas d'écriture dans les fichiers.
  5. Le bot et toutes ses données persistantes seront réinitialisés à chaque fois que l'adversaire change (tous les 12 tours).

Bots

Chaque bot étend Player.java:

package pigpen;

public abstract class Player {

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

}

Boardest le plateau de jeu, qui sert principalement à vous donner accès aux Penclasses, et idvotre ID joueur (vous indique si vous êtes premier ou deuxième), roundvous indique le tour de votre jeu contre le même adversaire (1 ou 2). La valeur de retour est un int[], où le premier élément est le penID (indexé 1) et le deuxième élément est le fenceID (indexé 0). Voir Pen.pick(int)pour un moyen simple de générer cette valeur de retour. Voir la page Github pour les exemples de joueurs et JavaDoc. Puisque nous n'utilisons qu'une grille carrée, ignorez toutes les fonctions et tous les champs liés aux hexagones.

Comment exécuter

  1. Téléchargez la source depuis Github.
  2. Écrivez votre robot contrôleur (assurez-vous de l'inclure package pigpen.players) et placez-le dans le src/dossier;
  3. Compilez avec javac -cp src/* -d . src/*.java. Exécuter avec java pigpen.Tournament 4 9 9 false(les deux derniers nombres peuvent être modifiés pour ajuster la taille de la grille. La dernière variable ne doit être définie que truesi vous souhaitez utiliser le logiciel pp_record.)

Les scores

  1. ChainCollector: 72
  2. Asdf: 57
  3. Paresseux: 51
  4. Finisseur: 36
  5. = LinearPlayer: 18
  6. = BackwardPlayer: 18
  7. RandomPlayer: 0

Voir également:

Remarque : ce jeu est un défi compétitif et difficile à résoudre, car il donne aux joueurs un tour supplémentaire pour compléter une boîte.

Merci à Nathan Merrill et Darrel Hoffman pour avoir consulté ce défi!

Mises à jour :

  • Ajout d'une moves(int player)méthode à la classe Board pour obtenir une liste de chaque mouvement effectué par un joueur.

Prime indéfinie (100 Rep) :

Première personne à publier une solution qui gagne à chaque tour et utilise la stratégie (ajuster le jeu en fonction de l'observation du jeu de l'adversaire).


2
LA BONTÉ. Le finisseur est waaayyyy OP! : P
El'endia Starman

@ El'endiaStarman Lol, tout ce qu'il fait, c'est terminer un stylo avec une clôture disponible, ou sinon choisir un stylo avec le plus de clôtures restantes. RandomPlayer est juste aléatoire.
geokavel

2
Ouais je sais. C'est juste que le score final est de 79-2 et RandomPlayer n'a obtenu ces deux dernières cases que parce qu'il le fallait . : P
El'endia Starman

Bonjour! Je veux créer mon propre bot, mais j'ai une question. Pen.BOTTOM à la ligne 0 col 0 renverra-t-il les mêmes valeurs que Pen.TOP à la ligne 1 col 0?
tuskiomi

@tusk Oui, c'est le cas
geokavel

Réponses:


6

Fainéant

Ce bot est paresseux. Il choisit un endroit et une direction aléatoires et continue de construire dans cette direction sans trop bouger. Il n'y a que 2 cas où il fait quelque chose de différent:

  • "gagner de l'argent" en fermant une cheville avec seulement 1 clôture restante
  • choisir un nouvel endroit et une nouvelle direction si la pose de la clôture n'est pas possible ou permettrait à l'autre bot de "gagner de l'argent"
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, beau travail! LazyBones possède le finisseur (voir la nouvelle animation).
geokavel

Soit dit en passant, pour que tout le monde le sache, une autre façon de placer le stylo à gauche d'un stylo donné est pen.n(Pen.LEFT)(fonction voisine).
geokavel

De plus, je pense que ce n'est pas nécessaire lorsque vous vérifiez la clôture inférieure d'un stylo et la clôture supérieure de celui en dessous, elles sont garanties d'avoir la même valeur!
geokavel

La pick()méthode a maintenant un int roundparamètre à la fin, donc si vous pouviez ajouter cela.
geokavel

Je dois vérifier les deux clôtures, car n'importe quel objet stylo peut être en dehors du tableau (id == -1). Pour la même raison, je ne peux pas utiliser la fonction voisine.
Sleafar

6

ChainCollector

Ce bot aime les chaînes 1 . Il en veut autant que possible. Parfois, il sacrifie même une petite partie d'une chaîne pour en gagner une plus grande.

[1] Une chaîne se compose de enclos reliés par des clôtures ouvertes, où chaque enclos a 1 ou 2 clôtures ouvertes. Si un seul stylo appartenant à la chaîne peut être terminé, alors en raison de la règle du championnat, toute la chaîne peut également être terminée.

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, merci pour ton entrée. Je suis honoré que quelqu'un ait consacré autant de temps à un projet que j'ai créé. Je pense que l'introduction de ce bot a affecté la génération de nombres aléatoires, de sorte qu'Asdf bat désormais Lazybones les deux fois avec une légère marge.
geokavel

Eh bien, l'idée du bot avait l'air assez simple avant de commencer, puis j'ai voulu la terminer. ;) Avec l'aléatoire impliqué, vous devriez probablement laisser les bots jouer plus de 2 parties pour obtenir des résultats plus précis.
Sleafar

Bonne pensée. Je l'ai augmenté à 12 rounds par match, et maintenant, comme vous pouvez le voir, Asdf a un léger avantage. Même à 100 tours, il ne gagne que 13 matchs de plus que Lazybones.
geokavel

3

Finisseur

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);
 }


}

Utilise un comparateur pour choisir le stylo avec les clôtures les plus disponibles, mais donne la priorité aux stylos avec une seule clôture disponible. (7 est utilisé au lieu de 5 pour permettre à ce code de fonctionner également en mode hexagone)


3

Asdf

Attribue un score à chaque clôture, puis sélectionne le meilleur d'entre eux. Par exemple: un stylo avec une clôture ouverte a un score de 10, tandis qu'un stylo avec 2 clôtures ouvertes a un score de -8.

Il semble que Lazybones utilise une stratégie similaire, car elle est liée à ce 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;
        }
    }
}

Voici les scores. Il est intéressant de noter que celui qui passe deuxième obtient deux fois plus de points. Asdf contre Lazybones: 27 - 54; Lazybones vs Asdf: 27 - 54
geokavel

@geokavel Oui, car alors les bots sont obligés de faire un "mauvais tour", donc l'adversaire peut fermer un stylo.
CommonGuy

Est-ce donc le meilleur algorithme possible?
moitié du

@justhalf Ce n'est pas le cas, car les gens jouent à ce jeu en championnat. Je pense que ces algorithmes peuvent certainement être étendus. Voir les liens que j'ai fournis pour plus d'informations.
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};
    } 
}

La façon la plus simple d'écrire ce bot est en fait return null, car une entrée non valide sélectionnera automatiquement la première clôture disponible. Ce code n'utilise aucune méthode de raccourci et génère manuellement la valeur de retour.


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};
}
}

Ce code utilise la méthode de raccourci Pen.pick(int)pour générer la valeur de retour. Si la clôture supérieure n'est pas disponible, elle sélectionnera la clôture disponible la plus proche dans le sens des aiguilles d'une montre.


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);
    }
}

Même idée que BackwardPlayer, mais sélectionne au hasard un stylo. Notez le +1parce que les stylos sont indexés 1.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.