King-Pen! (Noktalar ve Kutular)


23

Bu, Dots and Boxes (Tepedeki Pen Pen) için yapılan tepenin kralı. Oyun basit, sıra sende sadece boş bir çitin üzerine bir çizgi çiz. Bir kareyi her tamamladığınızda bir puan alırsınız. Ayrıca, şampiyonluk kurallarına göre oynadığımızdan, sıranızda en az bir kare tamamlarsanız ekstra bir dönüş elde edersiniz. Bu, her bir botun birbirlerini 9x9 ızgarada iki kez 12 kez oynadığı yuvarlak bir turnuvadır . ChainCollector'ün eşlik eden şampiyonu Asdf'i kıyma et yaptığı iki ağır devasa tita arasında bu maçı kontrol edin: görüntü tanımını buraya girin

kurallar

  1. Hareket başına 0.5 saniye zaman sınırı.
  2. Diğer botlara müdahale etmeyin.
  3. Rastgele olmak için PigPen.random () ve PigPen.random (int) kullanın.
  4. Dosyalara yazı yok.
  5. Bot ve tüm kalıcı verileri, rakip her değiştiğinde (her 12 turda bir) sıfırlanır.

botlar

Her bot Player.java'yı genişletir:

package pigpen;

public abstract class Player {

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

}

Boardesas olarak size Penderslere erişim sağlamaya idyarayan oyun tahtasıdır ve oyuncu kimliğinizdir (birinci veya ikinci olup olmadığınızı roundsöyler ), aynı rakibe (1 veya 2) karşı hangi turda oynayacağınızı söyler. Dönüş değeri, int[]birinci elemanın penID (1-indexed) olduğu ve ikinci elemanın fenceID (0-indexed) olduğu bir değerdir. Pen.pick(int)Bu dönüş değerini oluşturmanın kolay bir yolunu görün . Örneğin oyuncular ve JavaDoc için Github sayfasına bakın . Yalnızca kare bir ızgara kullandığımızdan, altıgenlerle ilgili işlevleri ve alanları yok sayın.

Nasıl koşulur

  1. Github Kaynak indirin.
  2. Denetleyici botunuzu yazın (eklediğinizden emin olun package pigpen.players) ve src/klasöre yerleştirin;
  3. İle derleyin javac -cp src/* -d . src/*.java. Çalıştır java pigpen.Tournament 4 9 9 false(ızgara boyutunu ayarlamak için son iki sayı değiştirilebilir. Son değişken yalnızca truepp_record yazılımını kullanmak istiyorsanız ayarlanmalıdır .)

Skorlar

  1. Zincir Kollektör: 72
  2. Asdf: 57
  3. Lazybones: 51
  4. Son İşlemci: 36
  5. = LinearPlayer: 18
  6. = GeriyePlayer: 18
  7. RandomPlayer: 0

Ayrıca bakınız:

Not : Bu oyun rekabetçi bir mücadeledir ve oyunculara bir kutuyu tamamlamaları için ekstra bir dönüş sağlaması nedeniyle kolayca çözülemez.

Nathan Merrill ve Darrel Hoffman'a bu zorluğun danışmanlığı için teşekkür ederiz!

Güncellemeler :

  • moves(int player)Bir oyuncunun yaptığı her hareketin bir listesini almak için Board sınıfına bir yöntem eklendi .

Belirsiz Ödül (100 Rep) :

Her turu kazanan bir çözüm gönderen ve stratejiyi kullanan (ilk olarak rakibin nasıl oynadığını gözlemleyerek oyunu ayarlama).


2
İYİLİK. Son İşlemci waaayyyy OP! : P
El'endia Starman

@ El'endiaStarman Lol, tüm yaptığı bir çiti olan bir Pen'i bitirmek ya da en fazla çiti kalan bir Pen'i seçmektir. RandomPlayer sadece rastgeledir.
geokavel

2
Evet biliyorum. Sadece final skoru 79-2 oldu ve RandomPlayer, olması gereken son iki kutuyu aldı . : P
El'endia Starman

Merhaba! Kendi botumu kendim yapmak istiyorum ama bir sorum var. 0 col 0 satırındaki Pen.BOTTOM, 1 col 0 satırındaki Pen.TOP ile aynı değerleri döndürür mü?
tuskiomi

@tusk Evet, öyle
geokavel

Yanıtlar:


6

tembel

Bu bot tembel. Rastgele bir nokta ve yön seçer ve fazla hareket etmeden bu yönde inşa etmeye devam eder. Farklı bir şey yaptığı sadece 2 vaka var:

  • sadece 1 kalan çite sahip bir çiviyi kapatarak "para kazanın"
  • çit yerleştirmek mümkün değilse veya diğer botun "para kazanmasına" izin veriyorsa, yeni bir nokta ve yön seçin
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);
    }
}

Vay, güzel iş! Lazybones sahibi sonlandırıcı (yeni animasyon bakınız).
geokavel,

Bu arada, herkesin bilmesi için, Kalemi belirli bir kalemin soluna getirmenin başka bir yolu da pen.n(Pen.LEFT)(komşu işlevi).
geokavel,

Ayrıca, bir Pen'in alt çitini ve altındaki bir üst çitini kontrol etmenin gereksiz olduğunu düşünüyorum, aynı değerde olmaları garanti edilir!
geokavel,

Artık pick()yöntemin int roundsonunda bir parametre var.
geokavel

Her iki çiti de kontrol etmeliyim, çünkü herhangi bir kalem nesnesi tahtanın dışında olabilir (id == -1). Aynı sebepten komşu işlevini kullanamıyorum.
Sleafar

6

ChainCollector

Bu bot zincirleri 1 seviyor . Onları mümkün olduğunca istiyor. Bazen daha büyük bir tane kazanmak için zincirin küçük bir bölümünü bile feda eder.

[1] Bir zincir, her kalemin 1 veya 2 açık çite sahip olduğu açık çitler ile bağlanmış kalemlerden oluşur. Eğer zincire ait tek bir kalem bitirilebilirse, şampiyonluk kuralı nedeniyle tüm zincir de bitebilir.

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

Vay, girişiniz için teşekkür ederim. Birisinin yarattığım bir projeye çok zaman harcadığı konusunda endişeliyim. Sanırım bu botun tanıtımı rastgele sayı oluşumunu etkiledi, Asdf şimdi Lazybonları iki kere hafif bir oranda atar.
geokavel

Bot başlamadan önce botun fikri oldukça basit görünüyordu ve sonra bitirmek istedim. ;) Rastlantısallık söz konusu olduğunda, daha kesin sonuçlar almak için botların 2'den fazla oyun oynamasına izin vermelisiniz.
Sleafar

Güzel düşünce. Eşleşme başına 12 raund'a yükselttim ve şimdi, gördüğünüz gibi, Asdf'in küçük bir kenarı var. 100 rauntta bile, Lazybon'lardan yalnızca 13 maç daha kazanır.
geokavel

3

İşlemcisi

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


}

En uygun çitler ile Pen'i seçmek için bir Karşılaştırıcı kullanır, ancak sadece 1 çit ile Pen'in önceliğini verir. (Bu kodun altıgen modunda da çalışmasına izin vermek için 7 yerine 5 kullanılır)


3

asdf

Her çit için bir puan atar ve sonra en iyisini seçer. Örneğin: Bir açık çiti olan bir kalem 10, 2 açık çiti olan bir kalem ise -8 puan alır.

Lazybones benzer bir strateji kullanıyor gibi görünüyor , çünkü bu botla bağları var.

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

İşte puanlar. Kim ikinci olursa, iki kat daha fazla puan alması ilginç. Asdf ve Lazybonlara karşı: 27 - 54; Lazybones vs. Asdf: 27 - 54
geokavel

@geokavel Evet, çünkü o zaman botlar "kötü dönüş" yapmaya zorlanır, böylece rakip bir kalemi kapatabilir.
CommonGuy

O zaman mümkün olan en iyi algoritma bu mu?
justhalf

@justhalf Değil, çünkü insanlar bu oyunu şampiyonalarda oynuyorlar. Bu algoritmaların kesinlikle genişletilebileceğini düşünüyorum. Daha fazla bilgi için verdiğim bağlantılara bakın.
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};
    } 
}

Bu botu yazmanın en kolay yolu aslında return null, çünkü geçersiz bir giriş ilk kullanılabilir çiti otomatik olarak seçecektir. Bu kod herhangi bir kısayol yöntemi kullanmaz ve dönüş değerini el ile oluşturur.


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

Bu kod Pen.pick(int), dönüş değerini üretmek için kısayol yöntemini kullanır . Üst çit kullanılamıyorsa, saat yönünde giden en yakın çit seçilecektir.


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

BackwardPlayer ile aynı fikir, ancak rastgele bir kalem seçer. +1Pen'in 1 indeksli olduğundan not alın .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.