Кінг-Пен! (Крапки та крапки)


23

Це виклик короля пагорба для Dots and Boxes (він же Перо Свині). Гра проста, на свою чергу просто намалюйте лінію на порожньому паркані. Кожен раз, коли ви заповнюєте квадрат, ви отримуєте бал. Крім того, оскільки ми граємо за правилами чемпіонату , якщо ви заповнили хоча б один квадрат на черзі, ви отримаєте додаткову чергу. Це турнір з круглим роботом, де кожен бот два рази по 12 разів грає по сітці 9x9. Ознайомтеся з цим поєдинком двох важких титанів, де 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-індексований), а другим елементом єIDID (0-індексований). Ознайомтеся Pen.pick(int)з простим способом отримання цього поверненого значення. Дивіться сторінку Github, наприклад, програвачі та JavaDoc. Оскільки ми використовуємо лише квадратну сітку, ігноруємо будь-які функції та поля, пов’язані з шестикутниками.

Як бігати

  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. ChainCollector: 72
  2. Asdf: 57
  3. Ледачі: 51
  4. Фінішер: 36
  5. = LinearPlayer: 18
  6. = Задній гравець: 18
  7. Випадковий гравець: 0

Дивись також:

Примітка . Ця гра є конкурентним завданням і не легко вирішується, що дає гравцям додатковий поворот для заповнення коробки.

Дякуємо Натану Мерріл та Даррелу Гофману за консультації з цього виклику!

Оновлення :

  • Додано moves(int player)метод класу Board, щоб отримати список кожного ходу, який зробив гравець.

Невизначений Баунті (100 відбітків) :

Перша людина опублікує рішення, яке виграє кожен раунд, і використовує стратегію (коригування гри на основі спостереження за тим, як грає опонент).


2
ДОБРІСТЬ. Фінішер вааайій ОП! : P
El'endia Starman

@ El'endiaStarman Lol, все, що він робить, це закінчити ручку з одним доступним парканом або інакше вибирає ручку з найбільшою кількістю огорож. RandomPlayer просто випадковий.
geokavel

2
Так, я знаю. Просто підсумковий рахунок - 79-2, а RandomPlayer отримав лише два останні вікна, тому що це довелося . : P
El'endia Starman

Привіт! Я хочу зробити власного бота, але у мене є питання. поверне Pen.BOTTOM у рядку 0 col 0 повернення тих же значень, що й Pen.TOP у рядку 1 col 0?
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)(функція сусіда).
геокавель

Крім того, я вважаю, що це зайве, коли ви перевіряєте нижню огорожу ручки та верхню огорожу тієї, що знаходиться під нею, вони гарантовано мають однакове значення!
геокавель

Тепер у pick()методу є int roundпараметр наприкінці, тож якщо ви можете, будь ласка, додайте його.
geokavel

Я повинен перевірити обидві огорожі, оскільки будь-який із предметів пера може знаходитися поза дошкою (id == -1). З тієї ж причини я не можу використовувати функцію сусіда.
Sleafar

6

ChainCollector

Цей бот любить ланцюжки 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 ігор більше, ніж у Lazybones.
геокавель

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


}

Використовує компаратор, щоб вибрати ручку з найбільш доступними огорожами, але надає пріоритет ручкам із лише одним парканом. (7 використовується замість 5, щоб цей код також працював у шестикутнику)


3

Asdf

Призначає бал кожному паркану, а потім вибирає найкраще з них. Наприклад: ручка з одним відкритим парканом має бал 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 Це не так, адже люди грають у цю гру в чемпіонатах. Я думаю, що ці алгоритми точно можна розширити. Перегляньте посилання, які я надав для отримання додаткової інформації.
геокавель

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

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-індексованим.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.