キングペン!(ドットとボックス)


23

これはドットとボックス(別名、ペンザピッグ)のキングオブザヒルチャレンジです。ゲームは簡単です。あなたの番では、空のフェンスに線を引くだけです。正方形を完成するたびにポイントを獲得します。また、私たちはチャンピオンシップルールに従ってプレーしているため、自分のターンで少なくとも1つのスクエアを完了すると、追加のターンが得られます。これはラウンドロビントーナメントで、各ボットは9x9グリッドで12回、お互いのボットを2回プレイします。ChainCollectorが共同チャンピオンAsdfのミンチ肉を作る2つのヘビー級タイタンの間のこの試合をチェックしてください: ここに画像の説明を入力してください

ルール

  1. 移動ごとに0.5秒の制限時間。
  2. 他のボットと干渉しません。
  3. ランダム性のためにPigPen.random()およびPigPen.random(int)を使用します。
  4. ファイルへの書き込みはありません。
  5. ボットとそのすべての永続データは、対戦相手が変わるたびにリセットされます(12ラウンドごと)。

ボット

すべてのボットはPlayer.javaを拡張します。

package pigpen;

public abstract class Player {

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

}

Board主にPenクラスへのアクセスを提供するゲームボードでありid、playerID(1番目または2番目の場合roundはあなたに伝えます)は、同じ対戦相手(1または2)に対してどのラウンドをプレイしているかを示します。戻り値はです。int[]最初の要素はpenID(1から始まる)で、2番目の要素はfenceID(0から始まる)です。Pen.pick(int)この戻り値を生成する簡単な方法を参照してください。プレーヤーとJavaDocの例については、Githubページを参照してください。正方形グリッドのみを使用しているため、六角形に関連する関数とフィールドは無視されます。

実行方法

  1. Githubからソースをダウンロードします。
  2. コントローラーボットを作成し(必ずを含めてくださいpackage pigpen.players)、src/フォルダーに入れます。
  3. でコンパイルしjavac -cp src/* -d . src/*.javaます。実行java pigpen.Tournament 4 9 9 false(グリッドサイズを調整するために最後の2つの数値を変更できます。最後の変数はtrue、pp_recordソフトウェアを使用する場合にのみ設定する必要があります。)

スコア

  1. チェーンコレクター:72
  2. Asdf:57
  3. レイジーボーン:51
  4. フィニッシャー:36
  5. = LinearPlayer:18
  6. = BackwardPlayer:18
  7. RandomPlayer:0

参照:

:このゲームは競争力のある課題であり、プレイヤーにボックスを完成させるための追加のターンを与えるため、簡単に解決することはできません。

この課題に関するコンサルティングをしてくれたNathan MerrillとDarrel Hoffmanに感謝します!

アップデート

  • moves(int player)Boardクラスにメソッドを追加して、プレーヤーが行ったすべての動きのリストを取得します。

不定の報奨金(100 Rep)

すべてのラウンドで勝ち、戦略を使用するソリューションを投稿する最初の人(対戦相手のプレイ方法の観察に基づいてプレイを調整)。


2
良さ。フィニッシャーはwaaayyyy OPです!:P
エレンディアスターマン

@ El'endiaStarman Lol、彼がすることは、利用可能な1つのフェンスでペンを仕上げるか、さもなければほとんどのフェンスが残っているペンを選ぶことです。RandomPlayerはランダムです。
geokavel

2
ええ、知っています。最終スコアは79-2で、RandomPlayerは最後の2つのボックスしか持っていなかったからです。:P
El'endia Starman

こんにちは!自分でボットを作りたいのですが、質問があります。行0列0のPen.BOTTOMは行1列0のPen.TOPと同じ値を返しますか?
tuskiomi

@tuskはい、それはありません
geokavel

回答:


6

怠zyな

このボットは怠け者です。彼はランダムなスポットと方向を選び、あまり動かずにその方向に構築し続けます。彼が何か違うことをするのはたった2つの場合だけです:

  • 残り1つだけのフェンスでペグを閉じて「お金を稼ぐ」
  • フェンスを配置できない場合、または他のボットが「お金を稼ぐ」ことを許可する場合は、新しいスポットと方向を選択します
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);
    }
}

うわー、いい仕事だ!LazyBones フィニッシャーを所有しています(新しいアニメーションを参照)。
geokavel

ところで、誰もが知っているように、ペンを特定のペンの左側に移動する別の方法がありますpen.n(Pen.LEFT)(隣接関数)。
geokavel

また、ペンの一番下のフェンスとその下の一番上のフェンスをチェックするときは不要だと思います。それらは同じ値を持つことが保証されています!
geokavel

pick()この方法は、今持っているint roundあなたはそれを追加してください可能性がありそうだとすれば、最後にパラメータを。
geokavel

ペンオブジェクトのいずれかがボードの外側にある可能性があるため(id == -1)、両方のフェンスをチェックする必要があります。同じ理由で、ネイバー関数を使用できません。
Sleafar

6

チェーンコレクター

このボットはチェーンが好きです1。彼はそれらをできるだけ多く望んでいます。時々、彼はチェーンの小さな部分を犠牲にして大きなチェーンを獲得することさえします。

[1]チェーンは、オープンフェンスで接続されたペンで構成され、各ペンには1つまたは2つのオープンフェンスがあります。チェーンに属する単一のペンが終了できる場合、チャンピオンシップルールにより、チェーン全体も同様に終了できます。

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

わあ、エントリーありがとうございます。誰かが私が作成したプロジェクトに多くの時間を割いてくれたことを謙虚に思っています。このボットの導入は乱数生成に影響を与えたと思います。そのため、Asdfは両方の時点でわずかなマージンでLazybonesを破りました。
geokavel

さて、ボットのアイデアは、始める前は非常にシンプルに見えましたが、それを終わらせたかったのです。;)ランダム性が関係している場合、より正確な結果を得るには、おそらくボットに2ゲーム以上プレイさせる必要があります。
Sleafar

いい考えだ。マッチアップごとに12ラウンドに増やしましたが、今では、ご覧のとおり、Asdfにはわずかな優位性があります。100ラウンドでも、レイジーボーンよりも13ゲーム多いだけです。
geokavel

3

フィニッシャー

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


}

コンパレータを使用して、最も利用可能なフェンスを持つペンを選択しますが、利用可能なフェンスが1つしかないペンを優先します。(このコードを六角形モードでも動作させるために、5ではなく7が使用されます)


3

Asdf

各フェンスにスコアを割り当ててから、それらの中からベストを選びます。たとえば、オープンフェンスが1つあるペンのスコアは10で、オープンフェンスが2つのペンのスコアは-8です。

Lazybonesはこのボットと結びついているため、同様の戦略を使用しているようです

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

ここにスコアがあります。2番目に進んだ人が2倍のポイントを獲得するのは興味深いことです。Asdf対Lazybones:27-54; Lazybones vs. Asdf:
27-54

@geokavelはい。そうすると、ボットは「悪いターン」を強いられるため、相手はペンを閉じることができます。
CommonGuy

それでは、これが最良のアルゴリズムですか?
justhalf

@justhalfそれはそうではありません。人々が選手権でこのゲームをプレイするからです。これらのアルゴリズムは間違いなく拡張できると思います。詳細については、提供したリンクを参照してください。
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};
    } 
}

このボットを記述する最も簡単な方法は、実際にreturn nullは、無効なエントリが最初に利用可能なフェンスを自動的に選択するためです。このコードはショートカットメソッドを使用せず、手動で戻り値を生成します。


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

このコードは、ショートカットメソッドPen.pick(int)を使用して戻り値を生成します。上部のフェンスが利用できない場合、時計回りに行く最も近い利用可能なフェンスを選択します。


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と同じ考え方ですが、ペンをランダムに選択します。+1ペンのインデックスは1であるため注意してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.