金笔!(点和盒)


23

这是“点和盒子”(又名“笔猪”)挑战山丘之王。游戏很简单,轮到您只需在空的栅栏上画一条线。每次完成一个正方形,您都会得到一个分数。另外,由于我们按照锦标赛的规则进行比赛,如果您在转弯中至少完成一个平方,则您将获得一个额外的转弯。这是一次循环锦标赛,每个漫游器在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是您的玩家ID(告诉您是第一还是第二,它round告诉您在哪个回合中与同一个对手(1或2)对战。返回值为an int[],其中第一个元素是penID(1索引),第二个元素是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(可以更改最后两个数字以调整网格大小。仅true当您希望使用pp_record软件时,才应将最后一个变量设置为。)

分数

  1. 连锁收藏家:72
  2. 助理秘书长:57
  3. 懒骨头:51
  4. 装订器:36
  5. = LinearPlayer:18
  6. = BackwardPlayer:18
  7. 随机播放器:0

也可以看看:

注意:此游戏是一项竞争性挑战,不易解决,因为它会给玩家额外的回合来完成一个盒子。

感谢Nathan Merrill和Darrel Hoffman为这个挑战提供咨询!

更新内容

  • moves(int player)在Board类中添加了一个方法,以获取玩家所做的每一个动作的列表。

无限赏金(100代表)

第一个发布解决方案的人,该解决方案将赢得每一轮胜利,并使用策略(根据观察对手的比赛来调整比赛)。


2
好。整理器是waaayyyy OP!:P
El'endia Starman 2015年

@ El'endiaStarman Lol,他所做的只是用一根可用的围栏完成一支钢笔,否则选择剩下的围栏最多的一支钢笔。RandomPlayer只是随机的。
geokavel

2
是的,我知道。只是最终比分是79-2,RandomPlayer只得了最后两个盒子,因为它必须这样做。:P
El'endia Starman 2015年

你好!我想做一个自己的机器人,但是我有一个问题。第0行第0行的Pen.BOTTOM是否将返回与第1行第0行的Pen.TOP相同的值?
tuskiomi

@tusk是的,确实如此
geokavel

Answers:


6

懒骨头

这个机器人很懒。他选择了一个随机的点和方向,并继续沿该方向建立而不会移动太多。在只有两种情况下,他做了一些不同的事情:

  • 通过只剩下一个围栏的钉子来“赚钱”
  • 如果无法放置围栏或允许其他机器人“赚钱”,请选择新的地点和方向
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 2015年

顺便说一下,众所周知,另一种将Pen移到给定笔的左侧的方法是pen.n(Pen.LEFT)(邻居功能)。
geokavel

另外,我认为在检查笔的底部围栏和其下方的围栏的顶部围栏时没有必要,它们保证具有相同的价值!
geokavel

pick()方法现在int round在末尾有一个参数,因此,请添加该参数。
geokavel

我必须检查两个围栏,因为任何笔对象都可以在板子外部(id == -1)。出于同样的原因,我不能使用邻居功能。
Sleafar 2015年

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

好吧,在开始之前,该机器人的想法非常简单,然后我想完成它。;)考虑到随机性,您可能应该让机器人玩两个以上的游戏,以获得更准确的结果。
Sleafar 2015年

好主意 每场比赛我将它增加到12发,现在,正如您所看到的,Asdf有一点优势。即使在100回合中,它也只能比Lazybones多赢13场比赛。
geokavel 2015年

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个围栏的钢笔。(使用7而不是5允许此代码也可以在六边形模式下工作)


3

自卫队

给每个围栏分配一个分数,然后从中选出最佳。例如:带有一个打开的围栏的笔的得分为10,而带有两个打开的围栏的笔的得分为-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与懒骨头:27-54; Lazybones vs. Asdf:27-54
geokavel 2015年

@geokavel是的,因为然后机器人被迫进行“坏转弯”,因此对手可以闭合笔形。
CommonGuy 2015年

那是最好的算法吗?
justhalf 2015年

@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

向后播放器

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索引的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.