Vua-Bút! (Dấu chấm và hộp)


23

Đây là một vị vua của thử thách đồi cho Dots và Hộp (hay còn gọi là Pen the Pig). Trò chơi rất đơn giản, đến lượt bạn chỉ cần vẽ một đường trên một hàng rào trống. Mỗi khi bạn hoàn thành một hình vuông, bạn nhận được một điểm. Ngoài ra, vì chúng tôi đang chơi theo luật vô địch , nếu bạn hoàn thành ít nhất một ô vuông trong lượt của mình, bạn sẽ có thêm một lượt chơi. Đây là một giải đấu vòng tròn, trong đó mỗi bot chơi với nhau bot hai lần 12 lần trên lưới 9x9. Kiểm tra trận đấu này giữa hai người khổng lồ hạng nặng, trong đó ChainCollector làm thịt băm của nhà vô địch trị vì Asdf: nhập mô tả hình ảnh ở đây

Quy tắc

  1. Giới hạn thời gian 0,5 giây cho mỗi lần di chuyển.
  2. Không can thiệp vào các bot khác.
  3. Sử dụng PigPen.random () và PigPen.random (int) cho sự ngẫu nhiên.
  4. Không ghi vào tập tin.
  5. Bot và tất cả dữ liệu liên tục của nó sẽ được đặt lại mỗi khi đối thủ thay đổi (cứ sau 12 vòng).

Bots

Mỗi bot mở rộng Player.java:

package pigpen;

public abstract class Player {

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

}

Boardlà bảng trò chơi, chủ yếu phục vụ cho bạn quyền truy cập vào Pencác lớp và idlà playerID của bạn (cho bạn biết nếu bạn là người đầu tiên hay người thứ hai), sẽ roundcho bạn biết bạn chơi vòng nào với cùng một đối thủ (1 hoặc 2). Giá trị trả về là một int[], trong đó phần tử đầu tiên là penID (1-index) và phần tử thứ hai là fenceID (0-index). Xem Pen.pick(int)cho một cách dễ dàng để tạo ra giá trị trả lại này. Xem trang Github để biết người chơi và JavaDoc. Vì chúng ta chỉ sử dụng lưới ô vuông bỏ qua mọi chức năng và trường liên quan đến hình lục giác.

Cách chạy

  1. Tải xuống Nguồn từ Github.
  2. Viết bot điều khiển của bạn (đảm bảo bao gồm package pigpen.players) và đặt nó vào src/thư mục;
  3. Biên dịch với javac -cp src/* -d . src/*.java. Chạy với java pigpen.Tournament 4 9 9 false(hai số cuối có thể được thay đổi để điều chỉnh kích thước lưới. Biến cuối cùng chỉ nên được đặt thành truenếu bạn muốn sử dụng phần mềm pp_record.)

Điểm số

  1. ChainCollector: 72
  2. Asdf: 57
  3. Giày lười: 51
  4. Hoàn thiện: 36
  5. = Tuyến tính: 18
  6. = BackwardPlayer: 18
  7. Ngẫu nhiên: 0

Xem thêm:

Lưu ý : trò chơi này là một thử thách cạnh tranh và không dễ giải quyết, do cho người chơi thêm một lượt để hoàn thành một hộp.

Cảm ơn Nathan Merrill và Darrel Hoffman đã tư vấn cho thử thách này!

Cập nhật :

  • Đã thêm một moves(int player)phương thức vào lớp Board để có danh sách mọi di chuyển mà người chơi đã thực hiện.

Tiền thưởng không xác định (100 Rep) :

Người đầu tiên đăng một giải pháp chiến thắng mọi vòng và sử dụng chiến lược (điều chỉnh cách chơi dựa trên việc quan sát cách đối thủ chơi).


2
TỐT. Kết thúc là waaayyyy OP! : P
El'endia Starman

@ El'endiaStarman Lol, tất cả những gì anh ta làm là hoàn thành một Cây bút với một hàng rào có sẵn, hoặc nếu không thì chọn một Cây bút có hàng rào còn lại nhiều nhất. RandomPlayer chỉ là ngẫu nhiên.
geokavel

2
Vâng, tôi biết. Chỉ là điểm số cuối cùng là 79-2 và RandomPlayer chỉ có hai hộp cuối cùng vì nó phải như vậy. : P
El'endia Starman

Xin chào! Tôi muốn làm bot của riêng tôi, nhưng tôi có một câu hỏi. Pen.BOTTOM ở hàng 0 col 0 sẽ trả về các giá trị giống như Pen.TOP ở hàng 1 col 0?
tuskiomi

@tusk Vâng, đúng vậy
geokavel

Câu trả lời:


6

Giày lười

Bot này là lười biếng. Anh ta chọn một vị trí và hướng ngẫu nhiên và tiếp tục xây dựng theo hướng đó mà không di chuyển quá nhiều. Chỉ có 2 trường hợp anh ta làm điều gì đó khác biệt:

  • "kiếm tiền" bằng cách đóng chốt chỉ với 1 hàng rào còn lại
  • chọn một vị trí và hướng mới nếu không thể đặt hàng rào hoặc sẽ cho phép các bot khác "kiếm tiền"
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, làm tốt lắm! LazyBones sở hữu bộ hoàn thiện (xem hình ảnh động mới).
geokavel

Nhân tiện, chỉ để mọi người biết, một cách khác để đưa Bút sang bên trái của một cây bút nhất định là pen.n(Pen.LEFT)(chức năng hàng xóm).
geokavel

Ngoài ra, tôi nghĩ không cần thiết khi bạn kiểm tra hàng rào dưới cùng của Bút và hàng rào trên cùng của bút bên dưới, chúng được đảm bảo có cùng giá trị!
geokavel

Các pick()phương pháp hiện nay có một int roundtham số ở cuối, vì vậy nếu bạn có thể xin vui lòng thêm rằng.
geokavel

Tôi phải kiểm tra cả hai hàng rào, bởi vì bất kỳ đối tượng bút nào cũng có thể nằm ngoài bảng (id == -1). Vì lý do tương tự, tôi không thể sử dụng chức năng hàng xóm.
Sleafar

6

ChainCollector

Bot này thích chuỗi 1 . Anh ấy muốn càng nhiều trong số họ càng tốt. Đôi khi anh ta thậm chí hy sinh một phần nhỏ của chuỗi để giành được phần lớn hơn.

[1] Một chuỗi bao gồm các cây bút được nối với nhau bằng hàng rào mở, trong đó mỗi cây bút có 1 hoặc 2 hàng rào mở. Nếu một cây bút duy nhất thuộc về chuỗi có thể được hoàn thành, thì do quy tắc vô địch, toàn bộ chuỗi cũng có thể được hoàn thành.

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, cảm ơn bạn đã nhập cảnh. Tôi khiêm tốn rằng ai đó đã dành quá nhiều thời gian cho một dự án do tôi tạo ra. Tôi nghĩ rằng sự ra đời của bot này đã ảnh hưởng đến việc tạo số ngẫu nhiên, do đó Asdf bây giờ đánh bại Lazybones cả hai lần một chút.
geokavel

Chà, ý tưởng cho bot trông khá đơn giản trước khi tôi bắt đầu, và sau đó tôi muốn hoàn thành nó. ;) Với sự ngẫu nhiên có liên quan, có lẽ bạn nên để các bot chơi nhiều hơn 2 trò chơi để có kết quả chính xác hơn.
Sleafar

Tư tưởng tốt. Tôi đã tăng nó lên 12 vòng mỗi trận đấu và bây giờ, như bạn có thể thấy, Asdf có một lợi thế không hề nhỏ. Ngay cả ở 100 vòng, nó chỉ thắng được 13 trận so với Lazybones.
geokavel

3

Bộ hoàn thiện

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


}

Sử dụng Bộ so sánh để chọn Bút có hàng rào có sẵn nhất, nhưng ưu tiên cho Bút chỉ có 1 hàng rào. (7 được sử dụng thay vì 5 để cho phép mã này hoạt động ở chế độ lục giác)


3

Asdf

Chỉ định một số điểm cho mỗi hàng rào và sau đó chọn ra tốt nhất trong số họ. Ví dụ: Một cây bút có một hàng rào mở có số điểm là 10, trong khi một cây bút có 2 hàng rào mở có số điểm là -8.

Có vẻ như Lazybones sử dụng một chiến lược tương tự, bởi vì nó liên kết với bot này.

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

Dưới đây là điểm số. Thật thú vị khi ai đi thứ hai sẽ nhận được gấp đôi số điểm. Asdf so với Lazybones: 27 - 54; Lazybones so với Asdf: 27 - 54
geokavel

@geokavel Vâng, bởi vì sau đó các bot buộc phải thực hiện một "bước ngoặt xấu", vì vậy đối thủ có thể đóng bút.
CommonGuy

Đây có phải là thuật toán tốt nhất có thể, sau đó?
vừa rồi

@justhalf Không phải vậy, vì mọi người chơi trò chơi này trong giải vô địch. Tôi nghĩ rằng các thuật toán này chắc chắn có thể được mở rộng trên. Xem các liên kết tôi cung cấp để biết thêm.
geokavel

0

Tuyến tính

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

Cách dễ nhất để viết bot này là trên thực tế return null, bởi vì một mục không hợp lệ sẽ tự động chọn hàng rào có sẵn đầu tiên. Mã này không sử dụng bất kỳ phương pháp phím tắt nào và tự tạo giá trị trả về.


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

Mã này sử dụng phương thức phím tắt Pen.pick(int)để tạo giá trị trả về. Nếu hàng rào trên cùng không có sẵn, nó sẽ chọn hàng rào có sẵn gần nhất theo chiều kim đồng hồ.


0

Ngẫu nhiên

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

Ý tưởng tương tự như BackwardPlayer, nhưng chọn ngẫu nhiên một cây bút. Lưu ý +1vì Pen là 1 chỉ mục.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.