콰 르토에는 몇 개의 무승부가 있습니까?


9

소개

이 과제는 Project Euler 문제 와 유사합니다 . 나는 기만적으로 단순한 보드 게임을하고 있었기 때문에 그 문제에 대해 간단한 질문에 대답 할 수있는 효율적인 해결책을 찾지 못했습니다.

Quarto 는 4 행의 재미있는 변형입니다. 16 개의 고유 한 조각이있는 4 x 4 보드에서 재생됩니다 (복제되지 않음). 매 턴마다 각 플레이어는 보드에 1 조각을 놓습니다. 각 조각에는 4 가지 이진 특성 (짧은 / 높이, 검은 색 / 흰색, 사각형 / 원형, 중공 / 고체)이 있습니다. 목표는 네 가지 특성 중 하나에 대해 가로, 세로 또는 2 개의 대각선을 따라 4 개를 연속으로 만드는 것입니다! 따라서 4 개의 검은 조각, 4 개의 흰색 조각, 4 개의 긴 조각, 4 개의 짧은 조각, 4 개의 사각형 조각, 4 개의 원형 조각, 4 개의 빈 조각 또는 4 개의 단단한 조각.

위의 그림은 완성 된 게임을 보여줍니다. 4 개의 사각형 조각으로 인해 4 개의 행이 있습니다.

도전

Quarto에서 일부 게임은 무승부로 끝날 수 있습니다.

가능한 최종 위치의 총 수는 16!약 20 조입니다.

마지막 포지션은 몇 명입니까?

규칙

  1. 솔루션은 그려지는 총 끝 위치 수 를 계산 하고 출력 하는 프로그램이어야합니다 . 정답은414298141056

  2. 수동으로 추론 한 게임 규칙 정보 만 사용할 수 있습니다 (컴퓨터 지원 증거는 없음).

  3. 문제의 수학적 단순화가 허용되지만 솔루션에서 (수동으로) 설명되고 입증되어야합니다.

  4. 승자가 CPU 실행 시간 측면에서 가장 최적의 솔루션입니다.

  5. 우승자를 결정하기 위해 16GB RAM의 MacBook Pro 2,5 GHz Intel Core i7에서 30m 미만의보고 된 실행 시간으로 모든 단일 솔루션을 실행 합니다.

  6. 다른 보드 크기에서도 작동하는 솔루션을 제공 할 경우 보너스 포인트가 없습니다. 그래도 좋을 것입니다.

  7. 해당되는 경우, 프로그램은 위에서 언급 한 하드웨어에서 1 분 이내에 컴파일해야합니다 (컴파일러 최적화 남용을 피하기 위해)

  8. 기본 허점 은 허용되지 않습니다

제출물

게시하십시오 :

  1. 코드 또는 코드에 대한 github / bitbucket 링크.
  2. 코드의 출력
  3. 현지에서 측정 한 실행 시간
  4. 당신의 접근 방식에 대한 설명.

마감 시간

제출 마감일은 3 월 1 일이므로 여전히 많은 시간입니다.


의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
Martin Ender 2013

답변:


3

C : 414298141056 추첨이 약 5 2.5 분 안에 발견되었습니다 .

대칭 인식 전치 테이블을 사용한 간단한 깊이 우선 검색. 우리는 순열에서 속성의 대칭과 보드의 8 배 다면체 대칭을 사용합니다.

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

typedef uint16_t u8;
typedef uint16_t u16;
typedef uint64_t u64;

#define P(i, j) (1 << (4 * (i) + (j)))

#define DIAG0 (P(0, 0) | P(1, 1) | P(2, 2) | P(3, 3))
#define DIAG1 (P(3, 0) | P(2, 1) | P(1, 2) | P(0, 3))

u64 rand_state;

u64 mix(u64 x) {
    u64 a = x >> 32;
    u64 b = x >> 60;
    x ^= (a >> b);
    return x * 7993060983890856527ULL;
}

u64 rand_u64() {
    u64 x = rand_state;
    rand_state = x * 6364136223846793005ULL + 1442695040888963407ULL;
    return mix(x);
}

u64 ZOBRIST_TABLE[(1 << 16)][8];

u16 transpose(u16 x) {
    u16 t = 0;
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            if (x & P(j, i)) {
                t |= P(i, j);
            }
        }
    }
    return t;
}

u16 rotate(u16 x) {
   u16 r = 0;
   for (int i = 0; i < 4; i++) {
       for (int j = 0; j < 4; j++) {
           if (x & P(3 - j, i)) {
                r |= P(i, j);
            }
       }
   } 
   return r;
}

void initialize_zobrist_table(void) {
    for (int i = 0; i < 1 << 16; i++) {
        ZOBRIST_TABLE[i][0] = rand_u64();
    }
    for (int i = 0; i < 1 << 16; i++) {
        int j = i;
        for (int r = 1; r < 8; r++) {
            j = rotate(j);
            if (r == 4) {
                j = transpose(i);
            }
            ZOBRIST_TABLE[i][r] = ZOBRIST_TABLE[j][0];
        }
    }
}

u64 hash_board(u16* x) {
    u64 hash = 0;
    for (int r = 0; r < 8; r++) {
        u64 h = 0;
        for (int i = 0; i < 8; i++) {
            h += ZOBRIST_TABLE[x[i]][r];
        }
        hash ^= mix(h);
    }
    return mix(hash);
}

u8 IS_WON[(1 << 16) / 8];

void initialize_is_won(void) {
    for (int x = 0; x < 1 << 16; x++) {
        bool is_won = false;
        for (int i = 0; i < 4; i++) {
            u16 stride = 0xF << (4 * i);
            if ((x & stride) == stride) {
                is_won = true;
                break;
            }
            stride = 0x1111 << i;
            if ((x & stride) == stride) {
                is_won = true;
                break;
            }
        }
        if (is_won == false) {
            if (((x & DIAG0) == DIAG0) || ((x & DIAG1) == DIAG1)) {
                is_won = true;
            }
        }
        if (is_won) {
            IS_WON[x / 8] |= (1 << (x % 8));
        }
    }
}

bool is_won(u16 x) {
    return (IS_WON[x / 8] >> (x % 8)) & 1;
}

bool make_move(u16* board, u8 piece, u8 position) {
    u16 p = 1 << position;
    for (int i = 0; i < 4; i++) {
        bool a = (piece >> i) & 1;
        int j = 2 * i + a;
        u16 x = board[j] | p;
        if (is_won(x)) {
            return false;
        }
        board[j] = x;
    }
    return true;
}

typedef struct {
    u64 hash;
    u64 count;
} Entry;

typedef struct {
    u64 mask;
    Entry* entries;
} TTable;

Entry* lookup(TTable* table, u64 hash, u64 count) {
    Entry* to_replace;
    u64 min_count = count + 1;
    for (int d = 0; d < 8; d++) {
        u64 i = (hash + d) & table->mask;
        Entry* entry = &table->entries[i];
        if (entry->hash == 0 || entry->hash == hash) {
            return entry;
        }
        if (entry->count < min_count) {
            min_count = entry->count;
            to_replace = entry;
        }
    }
    if (to_replace) {
        to_replace->hash = 0;
        to_replace->count = 0;
        return to_replace;
    }
    return NULL;
}

u64 count_solutions(TTable* ttable, u16* board, u8* pieces, u8 position) {
    u64 hash = 0;
    if (position <= 10) {
        hash = hash_board(board);
        Entry* entry = lookup(ttable, hash, 0);
        if (entry && entry->hash) {
            return entry->count;        
        }
    }
    u64 n = 0;
    for (int i = position; i < 16; i++) {
        u8 piece = pieces[i];
        u16 board1[8];
        memcpy(board1, board, sizeof(board1));
        u8 variable_ordering[16] = {0, 1, 2, 3, 4, 8, 12, 6, 9, 5, 7, 13, 10, 11, 15, 14};
        if (!make_move(board1, piece, variable_ordering[position])) {
            continue;
        }
        if (position == 15) {
            n += 1;
        } else {
            pieces[i] = pieces[position];
            n += count_solutions(ttable, board1, pieces, position + 1); 
            pieces[i] = piece;
        }
    }
    if (hash) {
        Entry* entry = lookup(ttable, hash, n);
        if (entry) {
            entry->hash = hash;
            entry->count = n;
        }
    }
    return n;
}

int main(void) {
    TTable ttable;
    int ttable_size = 1 << 28;
    ttable.mask = ttable_size - 1;
    ttable.entries = calloc(ttable_size, sizeof(Entry));
    initialize_zobrist_table();
    initialize_is_won();
    u8 pieces[16];
    for (int i = 0; i < 16; i++) {pieces[i] = i;}
    u16 board[8] = {0};
    printf("count: %lu\n", count_solutions(&ttable, board, pieces, 0));
}

측정 된 점수 (@wvdz) :

$ clang -O3 -march=native quarto_user1502040.c
$ time ./a.out
count: 414298141056

real    1m37.299s
user    1m32.797s
sys     0m2.930s

점수 (사용자 + 시스템) : 1 분 35.727 초


멋진 솔루션처럼 보입니다. 그러나 설명을 조금 확장 할 수 있습니까? 솔루션이 올바른지 어떻게 알 수 있습니까?
wvdz

이 시간을 정하기 위해 어떤 컴파일러 플래그를 사용해야합니까? 나는 -O3 -march=native내 컴퓨터에서 1m48s를 사용해 보았습니다 . (CC @wvdz)
Dennis

@Dennis, 저도 함께갔습니다.
user1502040

@Dennis 저는 C 컴파일에 전문가가 아닙니다. 컴파일러 플래그를 사용하지 않았습니다. 편집 내용을 업데이트하겠습니다.
wvdz

1

자바, 414298141056 무승부, 23 분 42.272 초

나는 자신의 도전에 대한 해결책을 게시하는 것에 눈을 돌리지 않기를 희망하지만, 처음 에이 도전을 게시 한 이유는 내가 효율적인 해결책을 스스로 만들 수 없었기 때문에 나를 미치게했기 때문입니다. 최선을 다하기 위해 며칠이 걸릴 것입니다.

user1502040 의 답변을 연구 한 후 실제로 합리적인 시간 내에 실행되도록 코드를 수정했습니다. 내 솔루션은 여전히 ​​크게 다르지만 몇 가지 아이디어를 훔쳤습니다.

  • 최종 포지션에 집중하는 대신 실제로 게임을하는 데 집중하여 보드에 하나씩 잇습니다. 이를 통해 올바른 개수로 의미 적으로 동일한 위치 테이블을 만들 수 있습니다.
  • 피스가 배치되는 순서를 실현하는 것이 중요합니다. 조기에 승리 할 가능성을 최대화하도록 배치해야합니다.

이 솔루션과 user1502040 의 가장 큰 차이점은 Zobrist 테이블을 사용하지 않고 보드의 표준 표현으로 각 보드가 특성 (2 * 4!)에 대해 48 가지 가능한 전치가 있다고 생각한다는 것입니다. 나는 보드 전체를 회전 시키거나 바꾸지 않고 조각의 특성만을 바꿉니다.

이것이 내가 생각해 낼 수있는 최선입니다. 명백하거나 덜 명확한 최적화에 대한 아이디어가 가장 환영합니다!

public class Q {

    public static void main(String[] args) {
        System.out.println(countDraws(getStartBoard(), 0));
    }

    /** Order of squares being filled, chosen to maximize the chance of an early win */
    private static int[] indexShuffle = {0, 5, 10, 15, 14, 13, 12, 9, 1, 6, 3, 2, 7, 11, 4, 8};

    /** Highest depth for using the lookup */
    private static final int MAX_LOOKUP_INDEX = 10;

    public static long countDraws(long board, int turn) {
        long signature = 0;
        if (turn < MAX_LOOKUP_INDEX) {
            signature = getSignature(board, turn);
            if (cache.get(turn).containsKey(signature))
                return cache.get(turn).get(signature);
        }
        int indexShuffled = indexShuffle[turn];
        long count = 0;
        for (int n = turn; n < 16; n++) {
            long newBoard = swap(board, indexShuffled, indexShuffle[n]);
            if (partialEvaluate(newBoard, indexShuffled))
                continue;
            if (turn == 15)
                count++;
            else
                count += countDraws(newBoard, turn + 1);
        }
        if (turn < MAX_LOOKUP_INDEX)
            cache.get(turn).put(signature, count);
        return count;
    }

    /** Get the canonical representation for this board and turn */
    private static long getSignature(long board, int turn) {
        int firstPiece = getPiece(board, indexShuffle[0]);
        long signature = minTranspositionValues[firstPiece];
        List<Integer> ts = minTranspositions.get(firstPiece);
        for (int n = 1; n < turn; n++) {
            int min = 16;
            List<Integer> ts2 = new ArrayList<>();
            for (int t : ts) {
                int piece = getPiece(board, indexShuffle[n]);
                int posId = transpositions[piece][t];
                if (posId == min) {
                    ts2.add(t);
                } else if (posId < min) {
                    min = posId;
                    ts2.clear();
                    ts2.add(t);
                }
            }
            ts = ts2;
            signature = signature << 4 | min;
        }
        return signature;
    }

    private static int getPiece(long board, int position) {
        return (int) (board >>> (position << 2)) & 0xf;
    }

    /** Only evaluate the relevant winning possibilities for a certain turn */
    private static boolean partialEvaluate(long board, int turn) {
        switch (turn) {
            case 15:
                return evaluate(board, masks[8]);
            case 12:
                return evaluate(board, masks[3]);
            case 1:
                return evaluate(board, masks[5]);
            case 3:
                return evaluate(board, masks[9]);
            case 2:
                return evaluate(board, masks[0]) || evaluate(board, masks[6]);
            case 11:
                return evaluate(board, masks[7]);
            case 4:
                return evaluate(board, masks[1]);
            case 8:
                return evaluate(board, masks[4]) || evaluate(board, masks[2]);
        }
        return false;
    }

    private static List<Map<Long, Long>> cache = new ArrayList<>();
    static {
        for (int i = 0; i < 16; i++)
            cache.add(new HashMap<>());
    }

    private static boolean evaluate(long board, long[] masks) {
        return _evaluate(board, masks) || _evaluate(~board, masks);
    }

    private static boolean _evaluate(long board, long[] masks) {
        for (long mask : masks)
            if ((board & mask) == mask)
                return true;
        return false;
    }

    private static long swap(long board, int x, int y) {
        if (x == y)
            return board;
        if (x > y)
            return swap(board, y, x);
        long xValue = (board & swapMasks[1][x]) << ((y - x) * 4);
        long yValue = (board & swapMasks[1][y]) >>> ((y - x) * 4);
        return board & swapMasks[0][x] & swapMasks[0][y] | xValue | yValue;
    }

    private static long getStartBoard() {
        long board = 0;
        for (long n = 0; n < 16; n++)
            board |= n << (n * 4);
        return board;
    }

    private static List<Integer> allPermutations(int input, int size, int idx, List<Integer> permutations) {
        for (int n = idx; n < size; n++) {
            if (idx == 3)
                permutations.add(input);
            allPermutations(swapBit(input, idx, n), size, idx + 1, permutations);
        }
        return permutations;
    }

    private static int swapBit(int in, int x, int y) {
        if (x == y)
            return in;
        int xMask = 1 << x;
        int yMask = 1 << y;
        int xValue = (in & xMask) << (y - x);
        int yValue = (in & yMask) >>> (y - x);
        return in & ~xMask & ~yMask | xValue | yValue;
    }

    private static int[][] transpositions = new int[16][48];
    static {
        for (int piece = 0; piece < 16; piece++) {
            transpositions[piece][0] = piece;
            List<Integer> permutations = allPermutations(piece, 4, 0, new ArrayList<>());
            for (int n = 1; n < 24; n++)
                transpositions[piece][n] = permutations.get(n);
            permutations = allPermutations(~piece & 0xf, 4, 0, new ArrayList<>());
            for (int n = 24; n < 48; n++)
                transpositions[piece][n] = permutations.get(n - 24);
        }
    }

    private static int[] minTranspositionValues = new int[16];
    private static List<List<Integer>> minTranspositions = new ArrayList<>();
    static {
        for (int n = 0; n < 16; n++) {
            int min = 16;
            List<Integer> elems = new ArrayList<>();
            for (int t = 0; t < 48; t++) {
                int elem = transpositions[n][t];
                if (elem < min) {
                    min = elem;
                    elems.clear();
                    elems.add(t);
                } else if (elem == min)
                    elems.add(t);
            }
            minTranspositionValues[n] = min;
            minTranspositions.add(elems);
        }
    }

    private static final long ROW_MASK = 1L | 1L << 4 | 1L << 8 | 1L << 12;
    private static final long COL_MASK = 1L | 1L << 16 | 1L << 32 | 1L << 48;
    private static final long FIRST_DIAG_MASK = 1L | 1L << 20 | 1L << 40 | 1L << 60;
    private static final long SECOND_DIAG_MASK = 1L << 12 | 1L << 24 | 1L << 36 | 1L << 48;

    private static long[][] masks = new long[10][4];
    static {
        for (int m = 0; m < 4; m++) {
            long row = ROW_MASK << (16 * m);
            for (int n = 0; n < 4; n++)
                masks[m][n] = row << n;
        }
        for (int m = 0; m < 4; m++) {
            long row = COL_MASK << (4 * m);
            for (int n = 0; n < 4; n++)
                masks[m + 4][n] = row << n;
        }
        for (int n = 0; n < 4; n++)
            masks[8][n] = FIRST_DIAG_MASK << n;
        for (int n = 0; n < 4; n++)
            masks[9][n] = SECOND_DIAG_MASK << n;
    }

    private static long[][] swapMasks;
    static {
        swapMasks = new long[2][16];
        for (int n = 0; n < 16; n++)
            swapMasks[1][n] = 0xfL << (n * 4);
        for (int n = 0; n < 16; n++)
            swapMasks[0][n] = ~swapMasks[1][n];
    }
}

측정 점수 :

$ time java -jar quarto.jar 
414298141056

real    20m51.492s
user    23m32.289s
sys     0m9.983s

점수 (사용자 + 시스템) : 23m42.272s

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.