Czas na połączenie!

20

https://en.wikipedia.org/wiki/Connect_Four

Czy ktoś pamięta, że ​​gra dla 2 graczy łączy 4? Dla tych, którzy tego nie zrobili, była to tablica 6x7, która stoi pionowo na powierzchni. Celem Connect 4 jest, dobrze połączyć 4! Połączenie jest liczone, jeśli jest poziome, ukośne lub pionowe. Umieszczasz swoje pionki na planszy, wstawiając pion na górze kolumny, gdzie spada na dół tej kolumny. Nasze zasady zmieniają 3 rzeczy w Connect 4.

  • Zmiana nr 1 Wygrana jest definiowana jako gracz z największą liczbą punktów. Otrzymujesz punkty, łącząc 4 jak w regulaminie - więcej na ten temat później.
  • Zmiana # 2 W każdej rundzie masz 3 graczy.
  • Zmień nr 3 Rozmiar planszy to 9x9.

Punktacja:

Wynik opiera się na liczbie zdobytych z rzędu. Jeśli masz grupę 4 z rzędu, otrzymasz 1 punkt. Jeśli masz grupę 5 w rzędzie, dostajesz 2 punkty, 6 w rzędzie 3 i tak dalej.

Przykłady:

Uwaga oi xsą zastępowane #i ~odpowiednio dla lepszego kontrastu

Przykład pustej planszy: (wszystkie przykłady to plansza o standardowym rozmiarze dla 2 graczy)

   a b c d e f g
6 | | | | | | | |
5 | | | | | | | |
4 | | | | | | | |
3 | | | | | | | |
2 | | | | | | | |
1 |_|_|_|_|_|_|_|

Jeśli upuścimy kawałek w kol d, wyląduje on na miejscu 1d.

   a b c d e f g
6 | | | | | | | |
5 | | | | | | | |
4 | | | | | | | |
3 | | | | | | | |
2 | | | | | | | |
1 |_|_|_|#|_|_|_|

Jeśli teraz upuścimy kawałek dponownie w kol , wyląduje on na miejscu 2d. Oto przykłady 4 pozycji w rzędzie:

   a b c d e f g
6 | | | | | | | |
5 | | | | | | | |
4 | | | |~| | | |
3 | | |~|#| | | |
2 | |~|#|~| |#| |
1 |~|#|~|#|_|#|_|

W tym przypadku xdostaje 1 punkt po przekątnej ( 1a 2b 3c 4d).

  a b c d e f g
6 | | | | | | | |
5 | | | | | | | |
4 | | | |#| | | |
3 | | | |#| | | |
2 | | | |#| | | |
1 |_|~|_|#|~|_|~|

W takim przypadku odostaje 1 punkt w pionie ( 1d 2d 3d 4d).

   a b c d e f g
6 | | | | | | | |
5 | | | | | | | |
4 | | | | | | | |
3 | | | | | | | |
2 | | |#|#|#|#| |
1 |_|_|~|~|~|~|~|

W tym przypadku ootrzymuje 2 punkty w poziomie ( 1c 1d 1e 1f 1g) i xotrzymuje 1 punkt w poziomie ( 2c 2d 2e 2f).

   a b c d e f g
6 | | |#| | | | |
5 | | |#| | | | |
4 | | |#| | | | |
3 | | |#| | |~| |
2 |~| |#| | |#|~|
1 |~|_|#|~| |~|~|

Tym razem xdostaje 3 punkty za 6 z rzędu ( 1c 2c 3c 4c 5c 6c).

Wejście wyjście

Będziesz miał dostęp do tablicy za pomocą tablicy 2d. Każda lokalizacja będzie intreprezentowana przez identyfikator gracza. Będziesz także przekazywał swój identyfikator gracza do swojej funkcji. Poruszasz się, zwracając do której kolekcji chcesz wrzucić swój kawałek. W każdej rundzie do gry zostaną wybrani 3 gracze. Pod koniec gry wszyscy gracze rozegrają parzystą liczbę gier.

W tej chwili zostanie uruchomionych 100 000 rund (pamiętaj, że zajmuje to dużo czasu, możesz chcieć je zmniejszyć w celu szybkiego przetestowania). Ogólnie rzecz biorąc, zwycięzcą zostaje gracz z największą liczbą wygranych.

Kontroler można znaleźć tutaj: https://github.com/JJ-Atkinson/Connect-n/tree/master .

Pisanie bota:

Aby napisać bota, musisz rozszerzyć Playerklasę. Playerjest abstrakcyjna i ma jedną metodę wdrożenia, int makeMove(void). W makeMovesam zdecydujesz, do której kolekcji chcesz wrzucić swój kawałek. Jeśli wybrałeś niepoprawne coll (np. Coll nie istnieje, coll jest już wypełniony), twoja kolej zostanie pominięta . W Playerklasie masz wiele przydatnych metod pomocniczych. Oto lista najważniejszych z nich:

  • boolean ensureValidMove(int coll): Zwraca true, jeśli coll jest na planszy, a coll nie jest jeszcze wypełniony.
  • int[] getBoardSize(): Zwraca tablicę int, gdzie [0]jest liczba kolumn i [1]liczba wierszy.
  • int[][] getBoard(): Zwróć kopię planszy. Należy do niego dostęp tak: [coll number][row number from bottom].
  • Aby znaleźć resztę, spójrz na Playerklasę.
  • EMPTY_CELL: Wartość pustej komórki

Ponieważ będzie to wielowątkowe, dołączyłem również randomfunkcję, jeśli jej potrzebujesz.

Debugowanie bota:

Dodałem kilka rzeczy do kontrolera, aby ułatwić debugowanie bota. Pierwszy to Runner#SHOW_STATISTICS. Jeśli ta opcja jest włączona, zobaczysz wydruk grup graczy, w tym liczbę zwycięstw botów. Przykład:

OnePlayBot, PackingBot, BuggyBot, 
OnePlayBot -> 6
PackingBot -> 5
BuggyBot -> 3
Draw -> 1

Możesz również stworzyć niestandardową grę z connectn.game.CustomGameklasą, możesz zobaczyć wyniki i zwycięzcę każdej rundy. Możesz nawet dodać siebie do miksu za pomocą UserBot.

Dodanie bota:

Aby dodać bota do składu, przejdź do PlayerFactorybloku statycznego i dodaj następujący wiersz:

playerCreator.put(MyBot.class, MyBot::new);

Inne rzeczy do zapamiętania:

  • Symulacje są wielowątkowe. Jeśli chcesz to wyłączyć, przejdź do Runner#runGames()i skomentuj ten wiersz ( .parallel()).
  • Aby zmienić liczbę gier, ustaw Runner#MINIMUM_NUMBER_OF_GAMESwedług własnych upodobań.

Dodano później:

  • Komunikacja między botami jest niedozwolona.

Powiązane: Zagraj w Connect 4!

================================

Tablica wyników: (100 000 gier)

MaxGayne -> 22662
RowBot -> 17884
OnePlayBot -> 10354
JealousBot -> 10140
Progressive -> 7965
Draw -> 7553
StraightForwardBot -> 7542
RandomBot -> 6700
PackingBot -> 5317
BasicBlockBot -> 1282
BuggyBot -> 1114
FairDiceRoll -> 853
Steve -> 634

================================

J Atkin
źródło
Czy możesz dodać funkcjonalność, aby określić, która tura jest włączona?
Conor O'Brien,
Już gotowe, sprawdź Playerklasę, aby zobaczyć wszystkie dostępne metody.
J Atkin,
7
„kwadrat 6x7”, który nie jest kwadratem
ev3commander
1
Danie graczom możliwości „podania” przez wykonanie nielegalnego ruchu nieco zmienia dynamikę. Czy gra kończy się, jeśli wszyscy spasują?
histocrat
1
Tak, dlatego bardzo ważne jest, aby używać ensureValidMove(chyba że twoją strategią jest oczywiście przejście tej kolejki).
J Atkin,

Odpowiedzi:

11

MaxGayne

Ten bot przypisuje ocenę do każdej pozycji, w oparciu głównie o długość połączonych części. Wygląda na 3 ruchy, sprawdzając 3 najlepiej wyglądające ruchy na każdym etapie i wybiera ten z maksymalnym oczekiwanym wynikiem.

package connectn.players;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class MaxGayne extends Player {
    private static final int PLAYERS = 3;

    private static class Result {
        protected final int[] score;
        protected int lastCol;

        public Result(int[] score, int lastCol) {
            super();
            this.score = score;
            this.lastCol = lastCol;
        }

        public Result() {
            this(new int[PLAYERS], -1);
        }

        public Result(Result other) {
            this(new int[PLAYERS], other.lastCol);
            System.arraycopy(other.score, 0, this.score, 0, PLAYERS);
        }

        public int getRelativeScore(int player) {
            int max = Integer.MIN_VALUE;
            for (int i = 0; i < PLAYERS; ++ i) {
                if (i != player && score[i] > max) {
                    max = score[i];
                }
            }
            return score[player] - max;
        }
    }

    private static class Board extends Result {
        private final int cols;
        private final int rows;
        private final int[] data;
        private final int[] used;

        public Board(int cols, int rows) {
            super();
            this.cols = cols;
            this.rows = rows;
            this.data = new int[cols * rows];
            Arrays.fill(this.data, -1);
            this.used = new int[cols];
        }

        public Board(Board other) {
            super(other);
            this.cols = other.cols;
            this.rows = other.rows;
            this.data = new int[cols * rows];
            System.arraycopy(other.data, 0, this.data, 0, this.data.length);
            this.used = new int[cols];
            System.arraycopy(other.used, 0, this.used, 0, this.used.length);
        }

        private void updatePartScore(int player, int length, int open, int factor) {
            switch (length) {
                case 1:
                    score[player] += factor * open;
                    break;
                case 2:
                    score[player] += factor * (100 + open * 10);
                    break;
                case 3:
                    score[player] += factor * (10_000 + open * 1_000);
                    break;
                default:
                    score[player] += factor * ((length - 3) * 1_000_000 + open * 100_000);
                    break;
            }
        }

        private void updateLineScore(int col, int row, int colOff, int rowOff, int length, int factor) {
            int open = 0;
            int player = -1;
            int partLength = 0;
            for (int i = 0; i < length; ++ i) {
                int newPlayer = data[(col + i * colOff) * rows + row + i * rowOff];
                if (newPlayer < 0) {
                    if (player < 0) {
                        if (i == 0) {
                            open = 1;
                        }
                    } else {
                        updatePartScore(player, partLength, open + 1, factor);
                        open = 1;
                        player = newPlayer;
                        partLength = 0;
                    }
                } else {
                    if (newPlayer == player) {
                        ++ partLength;
                    } else {
                        if (player >= 0) {
                            updatePartScore(player, partLength, open, factor);
                            open = 0;
                        }
                        player = newPlayer;
                        partLength = 1;
                    }
                }
            }
            if (player >= 0) {
                updatePartScore(player, partLength, open, factor);
            }
        }

        private void updateIntersectionScore(int col, int row, int factor) {
            updateLineScore(col, 0, 0, 1, rows, factor);
            updateLineScore(0, row, 1, 0, cols, factor);
            if (row > col) {
                updateLineScore(0, row - col, 1, 1, Math.min(rows - row, cols), factor);
            } else {
                updateLineScore(col - row, 0, 1, 1, Math.min(cols - col, rows), factor);
            }
            if (row > cols - col - 1) {
                updateLineScore(cols - 1, row - (cols - col - 1), -1, 1, Math.min(rows - row, cols), factor);
            } else {
                updateLineScore(col + row, 0, -1, 1, Math.min(col + 1, rows), factor);
            }
        }

        private void updatePiece(int player, int col, int row) {
            updateIntersectionScore(col, row, -1);
            data[col * rows + row] = player;
            ++ used[col];
            lastCol = col;
            updateIntersectionScore(col, row, 1);
        }

        public Board updatePiece(int player, int col) {
            int row = used[col];
            if (row >= rows) {
                return null;
            } else {
                Board result = new Board(this);
                result.updatePiece(player, col, row);
                return result;
            }
        }

        private void updateBoard(int[][] board) {
            for (int col = 0; col < cols; ++ col) {
                for (int row = 0; row < rows; ++ row) {
                    int oldPlayer = data[col * rows + row];
                    int newPlayer = board[col][row] - 1;
                    if (newPlayer < 0) {
                        if (oldPlayer < 0) {
                            break;
                        } else {
                            throw new RuntimeException("[" + col + ", " + row + "] == "  + oldPlayer + " >= 0");
                        }
                    } else {
                        if (oldPlayer < 0) {
                            updatePiece(newPlayer, col, row);
                        } else if (newPlayer != oldPlayer) {
                            throw new RuntimeException("[" + col + ", " + row + "] == "  + oldPlayer + " >= " + newPlayer);
                        }
                    }
                }
            }
        }

        private Result bestMove(int depth, int player) {
            List<Board> boards = new ArrayList<>();
            for (int col = 0; col < cols; ++ col) {
                Board board = updatePiece(player, col);
                if (board != null) {
                    boards.add(board);
                }
            }
            if (boards.isEmpty()) {
                return null;
            }
            Collections.sort(boards, (o1, o2) -> Integer.compare(o2.getRelativeScore(player), o1.getRelativeScore(player)));
            if (depth <= 1) {
                return new Result(boards.get(0).score, boards.get(0).lastCol);
            }
            List<Result> results = new ArrayList<>();
            for (int i = 0; i < 3 && i < boards.size(); ++ i) {
                Board board = boards.get(i);
                Result result = board.bestMove(depth - 1, (player + 1) % PLAYERS);
                if (result == null) {
                    results.add(new Result(board.score, board.lastCol));
                } else {
                    results.add(new Result(result.score, board.lastCol));
                }
            }
            Collections.sort(results, (o1, o2) -> Integer.compare(o2.getRelativeScore(player), o1.getRelativeScore(player)));
            return results.get(0);
        }
    }

    private Board board = null;

    @Override
    public int makeMove() {
        if (board == null) {
            int[][] data = getBoard();
            board = new Board(data.length, data[0].length);
            board.updateBoard(data);
        } else {
            board.updateBoard(getBoard());
        }

        Result result = board.bestMove(3, getID() - 1);
        return result == null ? -1 : result.lastCol;
    }
}
Sleafar
źródło
Bardzo bardzo dobrze! +1
J Atkin
Coś, co zauważyłem podczas zabawy, UserBota twoim botem było to, że po pewnym czasie MaxGaynewyrzuci tury (np. Po 15 ruchach przeskakuje co turę, aż do końca gry).
J Atkin,
Przyczyną tego jest prawdopodobnie błąd w CustomGame. Używa identyfikatorów graczy opartych na 0 zamiast 1 - jak w głównej grze. To po prostu psuje mojego bota. Są jeszcze 2 problemy. javafx.util.Pairnie działa w Eclipse, ponieważ nie jest uważany za część publicznego API. I nie mam pojęcia, gdzie szukać sun.plugin.dom.exception.InvalidStateException. Prawdopodobnie miałeś na myśli java.lang.IllegalStateException.
Sleafar,
To wydaje się trochę dziwne ... W każdym razie Pair, to jest tak blisko, jak mogę dostać się do typu danych, który chcę, bez kręcenia własnym, więc jeśli zaćmienie się nie skompiluje, myślę, że jest OK. Jeśli chodzi o # 3, masz rację, moje autouzupełnianie w IntelliJ nie zawsze ma rację. (przez większość czasu tak nie sprawdzałem)
J Atkin,
@JAtkin W rzeczywistości Pairproblem naprawdę uniemożliwia kompilację w środowisku Eclipse, chyba że znasz obejście .
Sleafar,
6

RowBot

Patrzy we wszystkich kierunkach i określa optymalną kolumnę. Próbuje połączyć swoje pionki, nie pozwalając przeciwnikom robić tego samego.

package connectn.players;

import connectn.game.Game;
import java.util.ArrayList;
import java.util.List;

public class RowBot extends Player {

    @Override
    public int makeMove() {
        int[][] board = getBoard();
        int best = -1;
        int bestScore = -10;
        for (int col = 0; col < board.length; col++) {
            if (ensureValidMove(col)) {
                int score = score(board, col, false);
                score -= score(board, col, true);
                if (score > bestScore) {
                    bestScore = score;
                    best = col;
                }
            }
        }
        return best;
    }

    private int score(int[][] board, int col, boolean simulateMode) {
        int me = getID();
        int row = getLowestEmptyRow(board, col);
        List<Score> scores = new ArrayList<>();
        if (!simulateMode) {
            scores.add(getScoreVertical(board, col, row));
        } else {
            row += 1;
        }
        scores.addAll(getScoreHorizontal(board, col, row));
        scores.addAll(getScoreDiagonal(board, col, row));
        int score = 0;
        for (Score s : scores) {
            if (s.player == me) {
                score += s.points > 2 ? 100 : s.points * 5;
            } else if (s.player != Game.EMPTY_CELL) {
                score += s.points > 2 ? 50 : 0;
            } else {
                score += 1;
            }
        }
        return score;
    }

    private Score getScoreVertical(int[][] board, int col, int row) {
        return getScore(board, col, row, 0, -1);
    }

    private List<Score> getScoreHorizontal(int[][] board, int col, int row) {
        List<Score> scores = new ArrayList<>();

        Score left = getScore(board, col, row, -1, 0);
        Score right = getScore(board, col, row, 1, 0);
        if (left.player == right.player) {
            left.points += right.points;
            scores.add(left);
        } else {
            scores.add(left);
            scores.add(right);
        }
        return scores;
    }

    private List<Score> getScoreDiagonal(int[][] board, int col, int row) {
        List<Score> scores = new ArrayList<>();

        Score leftB = getScore(board, col, row, -1, -1);
        Score rightU = getScore(board, col, row, 1, 1);
        Score leftBottomToRightUp = leftB;
        if (leftB.player == rightU.player) {
            leftBottomToRightUp.points += rightU.points;
        } else if (leftB.points < rightU.points || leftB.player == Game.EMPTY_CELL) {
            leftBottomToRightUp = rightU;
        }

        Score leftU = getScore(board, col, row, -1, 1);
        Score rightB = getScore(board, col, row, 1, -1);
        Score rightBottomToLeftUp = leftU;
        if (leftU.player == rightB.player) {
            rightBottomToLeftUp.points += rightB.points;
        } else if (leftU.points < rightB.points || leftU.player == Game.EMPTY_CELL) {
            rightBottomToLeftUp = rightB;
        }

        if (leftBottomToRightUp.player == rightBottomToLeftUp.player) {
            leftBottomToRightUp.points += rightBottomToLeftUp.points;
            scores.add(leftBottomToRightUp);
        } else {
            scores.add(leftBottomToRightUp);
            scores.add(rightBottomToLeftUp);
        }
        return scores;
    }

    private Score getScore(int[][] board, int initCol, int initRow, int colOffset, int rowOffset) {
        Score score = new Score();
        outerLoop: for (int c = initCol + colOffset;; c += colOffset) {
            for (int r = initRow + rowOffset;; r += rowOffset) {
                if (outside(c, r) || board[c][r] == Game.EMPTY_CELL) {
                    break outerLoop;
                }
                if (score.player == Game.EMPTY_CELL) {
                    score.player = board[c][r];
                }

                if (score.player == board[c][r]) {
                    score.points++;
                } else {
                    break outerLoop;
                }

                if (rowOffset == 0) {
                    break;
                }
            }
            if (colOffset == 0) {
                break;
            }
        }
        return score;
    }

    private boolean outside(int col, int row) {
        return !boardContains(col, row);
    }

    private int getLowestEmptyRow(int[][] board, int col) {
        int[] rows = board[col];
        for (int row = 0; row < rows.length; row++) {
            if (rows[row] == Game.EMPTY_CELL){
                return row;
            }
        }
        return -1;
    }

    private class Score {
        private int player = Game.EMPTY_CELL;
        private int points = 0;
    }
}
CommonGuy
źródło
5

OnePlayBot

Ten bot ma tylko jedną grę - umieść swój kawałek w poprawnej lewej komórce. Co dziwne, robi to całkiem dobrze;)

static class OnePlayBot extends Player {
    @Override
    int makeMove() {
        int attemptedMove = 0;

        for (int i = 0; i < getBoardSize()[0]; i++)
            if (ensureValidMove(i)) {
                attemptedMove = i;
                break;
            }

        return attemptedMove;
    }
}
J Atkin
źródło
3

RandomBot

Po prostu umieść kawałek w dowolnym miejscu, które jest ważne.

static class RandomBot extends Player {
    @Override
    int makeMove() {
        int attemptedMove = (int)Math.round(random() * getBoardSize()[0]);
        while (!ensureValidMove(attemptedMove))
            attemptedMove = (int)Math.round(random() * getBoardSize()[0]);

        return attemptedMove;
    }
}
J Atkin
źródło
3

StraightForwardBot

Podobne do OnePlayBot, ale uwzględnia ostatni ruch i odtwarza następną kolumnę, która jest poprawna.

static class StraightForwardBot extends Player {
    private int lastMove = 0;

    @Override
    int makeMove() { 
        for (int i = lastMove + 1; i < getBoardSize()[0]; i++) {
            if (ensureValidMove(i)) {
                lastMove = i;
                return i;
            }
        }
        for (int i = 0; i < lastMove; i++) {
            if (ensureValidMove(i)) {
                lastMove = i;
                return i;
            }
        }
        return 0;
    }
}
Dz.U. 7
źródło
3

JealousBot

Ten bot nienawidzi drugiego gracza. I nie podoba mu się to, że upuszcza kawałki na planszę. Więc stara się być ostatnim, który upuścił kawałek w kolumnie.

public class JealousBot extends Player {

    @Override
    public int makeMove() {
        int move = 0;
        boolean madeMove = false;
        int[] boardSize = getBoardSize();
        int id = getID();
        int[][] board = getBoard();

        if(getTurn()!=0) {
            for(int col = 0; col<boardSize[0]; col++) {
                for(int row = 0; row<boardSize[1]; row++) {
                    if(ensureValidMove(col)) {
                        if(board[col][row]!=EMPTY_CELL && board[col][row]!=id) {
                            move = col;
                            madeMove = true;
                            break;
                        }
                    }
                }
                if(madeMove) break;
            }

            if(!madeMove) {
                int temp = (int)Math.round(random()*boardSize[0]);
                while(madeMove!=true) {
                    temp = (int)Math.round(random()*boardSize[0]);
                    if(ensureValidMove(temp)) {
                        madeMove = true;
                    }
                }
                move = temp;
            }
        } else {
            move = (int)Math.round(random()*boardSize[0]);
        }

        return move;
    }
}

To mój pierwszy raz w CodeGolf, więc mam nadzieję, że ta odpowiedź będzie wystarczająco dobra. Nie mogłem tego jeszcze przetestować, więc przepraszam, jeśli są jakieś błędy.

EDYCJA : Dodano linię, aby przełamać sekundę for.

EDYCJA 2 : Zrozumiałem, dlaczego whilebył nieskończony. Jest teraz kompletny i można go używać!

Keker
źródło
Witamy w PPCG, rozśmieszyłeś mnie tą odpowiedzią, to świetnie! Uważaj tylko na swoje warunki. Myślę, że tablica jest domyślnie wypełniona wartościami -1, więc if(board[col][row]!=null && board[col][row]!=id)powinna zostać zmieniona na if(board[col][row]!=-1..... Sprawdź game.Game.genBoard () w github OP, jeśli chcesz być pewien. Nie wiem też, czy random()zrobisz, co chcesz, może wykorzystasz (int)Math.random()*col?
Katenkyo,
@Katenkyo Dziękuję bardzo, cieszę się, że to cię rozśmieszyło! random()Metoda jest w Playerklasie! Więc myślę, że to zadziała =) Ale tak, nie byłem pewien swoich warunków. Nie znalazłem, jak to jest zdefiniowane w kodzie OP, ale sprawdzę ponownie. Dziękuję Ci bardzo!
Keker,
Klasa Player definiuje random () jako public double random() {return ThreadLocalRandom.current().nextDouble();}. Nie wiem dokładnie, jak to działa, ale zakładam, że zwraca wartość z zakresu od 0 do 1, więc może być konieczne (int)random()*col:)
Katenkyo
@Katenkyo Oh, myślałem, że już to zrobiłem ... Mój zły. Zedytuję go, gdy znajdę odpowiednią wartość dla pustej komórki na tablicy, jeszcze raz dziękuję!
Keker,
@Katenkyo Masz rację, nextDoublezwraca liczbę pomiędzy 0a 1. Dołączyłem go, ponieważ symulacje są uruchamiane równolegle i Math.random()nie są bezpieczne dla wątków.
J Atkin,
3

BasicBlockBot

Prosty (i naiwny) bot blokowy. Nie wie, że możesz zrobić 4 z rzędu poziomo lub ukośnie!

static class BasicBlockBot extends Player {
    @Override
    int makeMove() {
        List<Integer> inARows = detectInARows();
        double chanceOfBlock = 0.5;

        if (inARows.isEmpty())
            chanceOfBlock = 0;

        if (random() < chanceOfBlock) {
            return inARows.get((int)Math.round(random() * (inARows.size() - 1)));
        } else {
            return (int)Math.round(random() * getBoardSize()[0]);
        }
    }


    /**
     * Very limited - just detects vertical in a rows
     *
     * @return A list of colls that have 4 in a row vertical
     */
    private List<Integer> detectInARows() {
        List<Integer> ret = new ArrayList<>();
        int[][] board = getBoard();

        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[i].length; j++) {
                int currId = board[i][j];
                if (currId != -1 && is4InARowVertical(i, j, board)) {
                    ret.add(i);
                }
            }
        }

        return ret;
    }

    private boolean is4InARowVertical(int coll, int row, int[][] board) {
        int id = board[coll][row];

        for (int i = 0; i < 4; i++) {
            int y = row + i;
            if (!boardContains(coll,y) || board[coll][y] != id)
                return false;
        }
        return true;
    }

}
J Atkin
źródło
3

Progresywny

Postęp jest ... postępowy. Lubi patrzeć na wszystko i niektóre! (Nie jestem pewien tej metodologii. Raz działało to przeciwko przyjacielowi.) I z jakiegoś powodu działa przyzwoicie.

static class Progressive extends Player{
    @Override
    int makeMove(){
        int move = 0;
        boolean statusBroken = false;
        for(int n=getBoardSize()[0];n>2;n-=2){
            for(int i=0;i<getBoardSize()[0];i+=n){
                if(ensureValidMove(i)){
                    move = i;
                    statusBroken = true;
                    break;
                }
                if(statusBroken) break;
            }
        }
        return move;
    }
}
Conor O'Brien
źródło
@JAtkin Przepraszam, miałem starszą wersję kodu.
Conor O'Brien,
3
@JAtkin Odrzuciłem twoją zmianę. Powinieneś pozwolić im naprawić kod w swoim poście. Jeśli chcesz to naprawić dla swojego kontrolera, to w porządku (osobiście nadal zostawiłbym notatkę), ale bezpośrednia modyfikacja czyjegoś kodu w SE jest niedozwolona.
Nathan Merrill,
3

FairDiceRoll

Zawsze zwraca 4.

static class FairDiceRoll extends Player {
    private int lastMove = 0;
    @Override
    int makeMove() { 
        return 4;
    }
}
ev3commander
źródło
2

BuggyBot

Przykładowy bot do pokonania (FYI: to nie jest trudne;)

static class BuggyBot extends Player {
    @Override
    int makeMove() {
        return getBoardSize()[1] - 1;
    }
}
J Atkin
źródło
2

PackingBot

Ten bot nie celuje bezpośrednio w punkty. Próbuje spakować maksymalnie tokenów, dopóki plansza nie zostanie zapełniona. Zrozumiał, że po prostu ciągłe podnoszenie się jest głupie, więc losowo umieszcza tokeny wokół swojej „domeny”.

Powinien być w stanie zdobyć punkty we wszystkich kierunkach, ale nie będzie najlepszy!

(Nie testowany)

package connectn.players;

static class PackingBot extends Player
{
    @Override
    int makeMove()
    {
        int move = 0;
        int[] sizes = getBoardSize();
        if(getTurn()==0)
            return sizes[0]/2+sizes[0]%2;

        int[][] board = getBoard();
        int[] flatBoard =new int[sizes[0]];
        //Creating a flat mapping of my tokens
        for(int i=0;i<sizes[0];i++)
            for (int j=0;j<sizes[1];j++)
                if(board[i][j]!=getID())
                    flatBoard[i]++;

        int max=0;
        int range=0;
        for(int i=0;i<flatBoard.length;i++)
        {
            if(flatBoard[i]!=0)
                range++;
            if(flatBoard[i]>flatBoard[max])
                max=i;
        }

        int sens = (Math.random()>0.5)?1:-1;
        move=((int)(Math.random()*(range+1)*sens))+max;

        while(!ensureValidMove(move))
        {
            move=(move+1*sens)%sizes[0];
            if(move<0)
                move=sizes[0]-1;
        }
        return move;
    }


}
Katenkyo
źródło
@JAtkin Dzięki za wskazanie tego, naprawiono :)
Katenkyo
2

Steve

package connectn.players;

import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

import connectn.game.Game;

public class Steve extends Player {
    @Override
    public int makeMove() {
        Random r=ThreadLocalRandom.current();
        int attemptedMove = 0;
        int[][]board=getBoard();
        int ec=Game.EMPTY_CELL;
        for(int c=0;c<board.length;c++){
            int j=board[c].length-1;
            for(;j>=0;j--){
                if(board[c][j]!=ec)break;
            }

            if(j>2+r.nextInt(3)&&r.nextDouble()<0.8)return c;
        }
        int k=-2+board.length/2+r.nextInt(5);
        if(ensureValidMove(k))return k;
        for (int i = 0; i < getBoardSize()[0]; i++)
            if (ensureValidMove(i)) {
                attemptedMove = i;
                break;
            }

        return attemptedMove;
    }
}
SuperJedi224
źródło
2
Steve ma trudności, zdobywa punkty poniżej BasicBlockBot.
J Atkin,