킹펜! (도트 및 박스)


23

이것은 Dots and Boxes (일명 Pen the Pig)의 언덕 도전 왕입니다. 게임은 간단합니다. 차례에 빈 울타리에 선을 그립니다. 사각형을 완성 할 때마다 점수를 얻습니다. 또한, 우리는 챔피언십 규칙에 따라 플레이하고 있기 때문에 턴에서 하나 이상의 사각형을 완성하면 추가 턴을 얻습니다. 이것은 라운드 로빈 토너먼트로, 각 봇은 9x9 그리드 에서 서로 다른 봇을 12 번 두 번 플레이 합니다. ChainCollector가 치열한 공동 챔피언 Asdf의 고기를 만드는 두 개의 헤비급 타이탄 사이의 일치를 확인하십시오. 여기에 이미지 설명을 입력하십시오

규칙

  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 (첫 번째 또는 두 번째 여부를 round알려줍니다 )이며 같은 상대 (1 또는 2)에 대해 어떤 라운드를하는지 알려줍니다. 반환 값은입니다 int[]. 여기서 첫 번째 요소는 penID (1- 인덱스)이고 두 번째 요소는 fenceID (0- 인덱스)입니다. Pen.pick(int)이 반환 값을 생성하는 쉬운 방법을 참조하십시오 . 플레이어 및 JavaDoc의 예 는 Github 페이지를 참조하십시오 . 우리는 정사각형 그리드 만 사용하기 때문에 육각형과 관련된 기능과 필드는 무시하십시오.

실행하는 방법

  1. Github에서 소스를 다운로드하십시오.
  2. 컨트롤러 봇을 작성하고 (포함해야 함 package pigpen.players) src/폴더에 넣습니다 .
  3. 로 컴파일하십시오 javac -cp src/* -d . src/*.java. Run with java pigpen.Tournament 4 9 9 false(마지막 두 숫자는 그리드 크기를 조정하기 위해 변경 될 수 있습니다. 마지막 변수는 truepp_record 소프트웨어를 사용하려는 경우 에만 설정해야 합니다.)

점수

  1. 체인 컬렉터 : 72
  2. Asdf : 57
  3. 레이지 본 : 51
  4. 피니셔 : 36
  5. = 선형 플레이어 : 18
  6. 뒤로 플레이어 : 18
  7. 랜덤 플레이어 : 0

참조 :

참고 :이 게임은 경쟁이 치열하며 플레이어가 상자를 완성하기 위해 여분의 회전을 제공하기 때문에 쉽게 해결할 수 없습니다.

이 도전에 대한 상담을 한 Nathan Merrill과 Darrel Hoffman에게 감사합니다!

업데이트 :

  • moves(int player)플레이어가 한 모든 움직임의 목록을 얻기 위해 Board 클래스에 메소드를 추가했습니다 .

무기한 바운티 (100 회) :

매 라운드마다 승리하고 전략을 사용하는 솔루션을 게시 한 첫 번째 사람 (상대자가 어떻게 플레이하는지 관찰하여 플레이 조정).


2
선량. 피니셔는 waaayyyy OP입니다! : P
— El'endia Starman

@ El'endiaStarman Lol, 그가 할 수있는 일은 울타리가 하나있는 펜으로 마무리하거나 울타리가 가장 많은 펜을 선택하는 것입니다. RandomPlayer는 무작위입니다.
— geokavel

2
그래, 알아 그것은 최종 점수는 79-2 단지 것을 그리고 그것 때문에 RandomPlayer은 그 마지막 두 개의 상자를 가지고 있었다 합니다. : P
— El'endia Starman 5

안녕하세요! 내 봇을 만들고 싶지만 질문이 있습니다. 0 행 0의 Pen.BOTTOM이 1 행 0의 Pen.TOP과 동일한 값을 반환합니까?
— tuskiomi

@tusk 네, 그렇습니다
— geokavel

답변:


6

게으른 뼈

이 봇은 게으르다. 그는 임의의 지점과 방향을 고르고 너무 많이 움직이지 않고 계속 그 방향으로 쌓입니다. 그가 다른 일을하는 경우는 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;
        }
    }
}

점수는 다음과 같습니다. 두 번째로가는 사람이 두 배나 많은 점수를 얻는다는 것은 흥미 롭습니다. Asdf vs. Lazybones : 27-54; Lazybones vs. Asdf : 27-54
— geokavel

@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

랜덤 플레이어

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.