Quarto có bao nhiêu trận hòa?


9

Giới thiệu

Thách thức này tương tự như các vấn đề của Project Euler . Tôi nghĩ ra nó bởi vì tôi đang chơi một trò chơi đơn giản lừa dối và không thể đưa ra một giải pháp hiệu quả để trả lời một câu hỏi đơn giản về cơ chế của nó.

Quarto là một biến thể thú vị của 4 liên tiếp. Nó được chơi trên một bảng 4 x 4 với 16 mảnh duy nhất (không có mảnh nào được nhân đôi). Mỗi lượt người chơi đặt 1 miếng lên bảng. Mỗi mảnh có 4 đặc điểm nhị phân (ngắn / cao, đen / trắng, vuông / tròn, rỗng / rắn). Mục tiêu là thực hiện bốn liên tiếp, theo chiều ngang, chiều dọc hoặc dọc theo 2 đường chéo, cho bất kỳ trong bốn đặc điểm! Vì vậy, 4 mảnh đen, 4 mảnh trắng, 4 mảnh cao, 4 mảnh ngắn, 4 mảnh vuông, 4 mảnh tròn, 4 mảnh rỗng hoặc 4 mảnh rắn.

Hình trên cho thấy một trò chơi đã hoàn thành, có bốn liên tiếp vì 4 mảnh vuông.

Thử thách

Trong Quarto, một số trò chơi có thể kết thúc với tỷ số hòa.

Tổng số vị trí cuối có thể là 16!, khoảng 20 nghìn tỷ.

Có bao nhiêu trong số các vị trí cuối được rút ra?

Quy tắc

  1. Giải pháp phải là một chương trình tính toán và đưa ra tổng số vị trí kết thúc được vẽ. Đáp án đúng là414298141056

  2. Bạn chỉ có thể sử dụng thông tin về các quy tắc của trò chơi đã được suy luận thủ công (không có bằng chứng hỗ trợ máy tính).

  3. Đơn giản hóa toán học của vấn đề được cho phép, nhưng phải được giải thích và chứng minh (bằng tay) trong giải pháp của bạn.

  4. Người chiến thắng là người có giải pháp tối ưu nhất về thời gian chạy CPU.

  5. Để xác định người chiến thắng, tôi sẽ chạy mọi giải pháp duy nhất với thời gian chạy được báo cáo là dưới 30m trên MacBook Pro 2,5 GHz Intel Core i7 với RAM 16 GB .

  6. Không có điểm thưởng cho việc đưa ra một giải pháp cũng hoạt động với các kích thước bảng khác. Mặc dù điều đó sẽ tốt đẹp.

  7. Nếu có thể, chương trình của bạn phải biên dịch trong vòng 1 phút trên phần cứng được đề cập ở trên (để tránh lạm dụng tối ưu hóa trình biên dịch)

  8. Lỗ hổng mặc định không được phép

Đệ trình

Xin vui lòng gửi:

  1. Mã hoặc một liên kết github / bitbucket với mã.
  2. Đầu ra của mã.
  3. Thời gian chạy cục bộ của bạn
  4. Một lời giải thích về cách tiếp cận của bạn.

Hạn chót

Hạn chót nộp hồ sơ là ngày 1 tháng 3, vì vậy vẫn còn nhiều thời gian.


Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Martin Ender

Câu trả lời:


3

C: 414298141056 rút ra được tìm thấy trong khoảng 5 2,5 phút.

Chỉ cần tìm kiếm theo chiều sâu đơn giản với bảng chuyển vị nhận biết đối xứng. Chúng tôi sử dụng tính đối xứng của các thuộc tính theo hoán vị và đối xứng dih thờ 8 lần của bảng.

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

Điểm số đo được (@wvdz):

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

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

Điểm (người dùng + sys): 1m35.727s


Trông giống như một giải pháp tuyệt vời. Tuy nhiên, bạn có thể mở rộng một chút về lời giải thích của bạn? Làm thế nào để bạn biết giải pháp là chính xác?
wvdz

Những cờ biên dịch nên được sử dụng để thời gian này? Tôi đã thử với -O3 -march=nativevà có 1m48 trên máy của tôi. (CC @wvdz)
Dennis

@Dennis, đó là những gì tôi đã đi với quá.
user1502040

@Dennis Tôi không phải là chuyên gia biên dịch C. Tôi không sử dụng bất kỳ cờ biên dịch nào. Tôi sẽ cập nhật chỉnh sửa của tôi.
wvdz

1

Java, rút ​​thăm 414298141056, 23m42.272s

Tôi hy vọng nó không cau mày khi đăng một giải pháp cho thử thách của chính mình, nhưng lý do khiến tôi đăng thử thách này ngay từ đầu là nó khiến tôi phát điên vì tôi không thể tự mình đưa ra giải pháp hiệu quả. Cố gắng tốt nhất của tôi sẽ mất nhiều ngày để hoàn thành.

Sau khi nghiên cứu câu trả lời của user1502040 , tôi thực sự đã xoay sở để sửa đổi mã của mình để chạy trong một khoảng thời gian hợp lý. Giải pháp của tôi vẫn khác biệt đáng kể, nhưng tôi đã đánh cắp một số ý tưởng:

  • Thay vì tập trung vào các vị trí kết thúc, tôi tập trung vào việc thực sự chơi trò chơi, đặt từng miếng một lên bảng. Điều này cho phép tôi xây dựng một bảng các vị trí giống hệt nhau về mặt ngữ nghĩa với số đếm chính xác.
  • Nhận ra thứ tự các phần được đặt vấn đề: chúng nên được đặt sao cho bạn tối đa hóa cơ hội giành chiến thắng sớm.

Sự khác biệt chính giữa giải pháp này và người dùng1502040 là tôi không sử dụng bảng Zobrist, nhưng đại diện chính tắc của một bảng, trong đó tôi coi mỗi bảng có 48 lần chuyển vị có thể qua các đặc điểm (2 * 4!). Tôi không xoay hoặc hoán chuyển toàn bộ bảng, mà chỉ là đặc điểm của các mảnh.

Đây là điều tốt nhất tôi có thể nghĩ ra. Ý tưởng cho tối ưu hóa rõ ràng hoặc ít rõ ràng được chào đón nhất!

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

Điểm số đo được:

$ time java -jar quarto.jar 
414298141056

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

Điểm (người dùng + sys): 23m42.272s

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.