킹펜! (도트 및 박스)


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.