Изображение на растровой доске для игры Nine Men Morris

Я пишу игру Nine Men's Morris на Java и уже реализовал правила игры. и ИИ, использующий негамакс. Однако игра основана на массивах, и генерация ходов занимает довольно много времени, когда ИИ думает (начиная с 6-го слоя).

Мой массив позиций основан на следующей диаграмме:

// 0        1        2
//    3     4     5
//       6  7  8
// 9  10 11    12 13 14
//       15 16 17
//    18    19    20
// 21       22       23

У меня есть дополнительные массивы, заполненные возможными мельницами и соседними позициями.

Я решил изменить игру с массивов на использование битовых досок, чтобы генерация ходов и другие области, которые в настоящее время используют массивы, были намного быстрее.

Моим первым шагом было бы иметь битовую доску для каждого игрока, которая будет отслеживать фигуры игрока на доске.

Вторым шагом будет определение свободных позиций. Я знаю, что могу сделать это, выполнив:

freepositions = ~(player1bb | player2bb);

Итак, мой вопрос: как я могу настроить/обновить битовые доски плеера, чтобы отслеживать их фигуры?


person Ivan-Mark Debono    schedule 14.03.2013    source источник


Ответы (4)


Принимая во внимание, что битовая доска игрока имеет длину 24 бита, а позиция 0 на доске является первым битом, установка битовой доски, когда игрок делает ход, оказывается довольно простой, выполнив следующие действия:

player1bb |= (1 << pos);
person Ivan-Mark Debono    schedule 15.03.2013

Битборды - это весело :-)

Я готовлю некоторый код для этой проблемы, но, надеюсь, вот что-то, что поможет вам начать:

public class BitBoard {
    public static final int White = 0;
    public static final int Black = 1;

    public long[] board = { 0, 0 };
    public int Player = 0;
    public int[] left = { 9, 9 };

    public int Opponent()
    {
        return Player == White ? Black : White;
    }

    public void makemove(int from, int to, int capture)
    {
        if (from == 0) { 
            assert left[Player] > 0 : "makemove: no left";
            left[Player]--;
        }       
        if (from != 0)
        {
            assert (board[Player] & from) != 0 : "makemove: source empty";
            board[Player] &= ~from;
        }
        assert (board[Player] & to) == 0 : "makemove: target must be empty";
        board[Player] |= to;
        if (capture != 0)
        {
            assert (board[Opponent()] & capture) != 0 : "makemove: capture empty";
            board[Opponent()] &= ~capture;
        }
    }

    public void unmakemove(int from, int to, int capture)
    {
        if (capture != 0)
        {
            assert (board[Opponent()] & capture) == 0 : "unmakemove: capture empty";
            board[Opponent()] |= capture;
        }
        assert (board[Player] & to) != 0 : "unmakemove: target empty";
        board[Player] &= ~to;
        if (from != 0)
        {
            assert (board[Opponent()] & capture) != 0 : "unmakemove: source must be empty empty";
            board[Player] |= from;
        }
        if (from == 0) { 
            assert left[Player] < 9 : "unmakemove: too many left";
            left[Player]++;
        }           
    }

    public void generatemoves()
    {
        // determine phase

        // 

    }
}

Не тестировал это полностью, но следующий код показывает, как использовать:

import java.math.*;

public class NineMenBitboard {
    static int tst;

    //   A  B  C  D  E  F  G
    // 1 0        1        2
    // 2    3     4     5
    // 3       6  7  8
    // 4 9 10 11    12 13 14
    // 5      15 16 17
    // 6   18    19    20
    // 7 21       22       23

    // positions

    static int A1 = 1 << 0;
    static int D1 = 1 << 1;
    static int G1 = 1 << 2;
    static int B2 = 1 << 3;
    static int D2 = 1 << 4;
    static int F2 = 1 << 5;
    static int C3 = 1 << 6;
    static int D3 = 1 << 7;
    static int E3 = 1 << 8;
    static int A4 = 1 << 9;
    static int B4 = 1 << 10;
    static int C4 = 1 << 11;
    static int E4 = 1 << 12;
    static int F4 = 1 << 13;
    static int G4 = 1 << 14;
    static int C5 = 1 << 15;
    static int D5 = 1 << 16;
    static int E5 = 1 << 17;
    static int B6 = 1 << 18;
    static int D6 = 1 << 19;
    static int F6 = 1 << 20;
    static int A7 = 1 << 21;
    static int D7 = 1 << 22;
    static int G7 = 1 << 23;

    // mills

    static int hor1 = A1 | D1 | G1;
    static int hor2 = B2 | D2 | F2;
    static int hor3 = C3 | D3 | E3;
    static int hor4_1 = A4 | B4 | C4;
    static int hor4_2 = E4 | F4 | G4;
    static int hor5 = C5 | D5 | E5;
    static int hor6 = B6 | D6 | F6;
    static int hor7 = A7 | D7 | G7;

    static int ver1 = A1 | A4 | A7;
    static int ver2 = B2 | B4 | B6;
    static int ver3 = C3 | C4 | C5;
    static int ver4_1 = D1 | D2 | D3;
    static int ver4_2 = D5 | D6 | D7;
    static int ver5 = E3 | E4 | E5;
    static int ver6 = F2 | F4 | F6;
    static int ver7 = G1 | G4 | G7; 

    public static void main(String[] args) {
        // sample on how to loop bits

        BitBoard game = new BitBoard();
        game.makemove(0, A1, 0);
        game.makemove(A1, A4, 0);


        System.out.println();

        tst = 255;

        for(int looper = tst, i = Integer.highestOneBit(looper); looper != 0; looper &= ~i, i = Integer.highestOneBit(looper))
        {
            System.out.println(i);
        }       

        System.out.println(tst);
      }
}

Также добавлен цикл, чтобы показать, как вы можете циклически перемещаться по позициям.

Повеселись. Я тоже буду кодить эту игру, так как хочу обновить обрезку AB :-)

person Jacco    schedule 16.03.2013
comment
Реальным преимуществом битовых досок является сохранение некоторых дополнительных данных при выполнении makemove и unmakemove. Мое первое дополнение состоит в том, чтобы отслеживать число сформированных мельниц и количество фрезерованных позиций для каждого игрока. Это значительно улучшит генерацию ходов (и последующую оценку доски). - person Jacco; 17.03.2013
comment
У меня есть аналогичный код для битовых досок. Я работаю над Negascout с TT. Дайте мне знать, если вы хотите поделиться еще немного кода. - person Ivan-Mark Debono; 17.03.2013
comment
Мы можем поделиться кодом. Думаю выложить на гитхаб позже. Я закончил makemove/unmakemove с отслеживанием размолотых камней. Вроде работает, но нужна проверка работоспособности. Я запустил Perft(7) и получил 1.873.562.112 ходов за 53 секунды. Мне опубликовать код Perft, чтобы вы могли проверить? - person Jacco; 18.03.2013
comment
напишите мне на blastedw (at) hotmail (dot) com или добавьте меня на FB - person Ivan-Mark Debono; 18.03.2013

Вот код Перфта. Я также делаю все первые ходы, но в будущей версии я, вероятно, сделаю только 6 уникальных (остальные просто приведут к оценке зеркал).

public long Perft(int depth)
{
    long nodes = 0;

    ArrayList<Integer> moves = generateMoves();

    if (depth == 1) 
    {
        //for(int move : moves)
        //  System.out.println(moveStr(move));
        return moves.size();
    }

    for (int move : moves) {
        int capture = 1 << (move >> 10) >> 1; 
        int to = 1 << ((move >> 5) & 31) >> 1;
        int from = 1 << (move & 31) >> 1;           
        makemove(from, to, capture);
        //System.out.print(this);
        nodes += Perft(depth - 1);
        unmakemove(from, to, capture);
    }
    return nodes;       
}

Как видите, я упаковал ходы в целые числа.

Для проверки работоспособности вот мои первые результаты:

  • 1 24
  • 2 552
  • 3 12144
  • 4 255024
  • 5 5140800
  • 6 99274176 (3.107ms)
  • 7 1873562112 (53 сек)
  • 8 занимает слишком много времени
person Jacco    schedule 17.03.2013
comment
Моя производительность на слое 6 возвращает 2164535 узлов за 5,79 секунды. Но мое мастерство копирует доску, генерирует ходы, делает ход и отменяет его. - person Ivan-Mark Debono; 18.03.2013
comment
Хм, pitty nodecounts не совпадают :S Хорошо, я углублюсь в отладку :-) - person Jacco; 18.03.2013

Я знаю, что вы уже реализовали это, но вы должны реализовать это так

//  0        1           2
//     8     9       10
//        16  17  18
//  7 15  23      19 11  3
//        22   21 20
//    14    13       12
//  6       5            4

Таким образом, вы можете перемещать брошенные позиции pos >> 1 для предыдущей позиции ‹‹ 1 для следующей, pos ‹‹ 8 для следующего байта той же позиции pos >> 8 предыдущего для байта той же позиции

person noam    schedule 24.03.2013