Quarto มีการจับรางวัลกี่ครั้ง?


9

บทนำ

ความท้าทายนี้คล้ายกับปัญหาของProject Euler ฉันคิดขึ้นมาเพราะเล่นเกมกระดานง่าย ๆ ที่หลอกลวงและไม่สามารถหาวิธีแก้ปัญหาที่มีประสิทธิภาพเพื่อตอบคำถามง่ายๆเกี่ยวกับกลไกของมัน

Quartoเป็นเกมที่สนุก 4 เกมติดต่อกัน มันเล่นบนกระดานขนาด 4 x 4 ตัวมี 16 ชิ้นที่ไม่ซ้ำกัน ทุกเทิร์นผู้เล่นแต่ละคนวาง 1 ชิ้นบนกระดาน แต่ละชิ้นมี 4 ลักษณะไบนารี (สั้น / สูง, ดำ / ขาว, สแควร์ / วงกลม, กลวง / ของแข็ง) เป้าหมายคือการทำให้สี่ในแถวทั้งแนวนอนแนวตั้งหรือแนวทแยงมุม 2 สำหรับลักษณะใด ๆ ของสี่! ชิ้นส่วนสีดำ 4 ชิ้นชิ้นส่วนสีขาว 4 ชิ้นชิ้นส่วนสูง 4 ชิ้นชิ้นสั้น 4 ชิ้นชิ้นสี่เหลี่ยมจัตุรัส 4 ชิ้นชิ้นวงกลม 4 ชิ้นชิ้นกลวง 4 ชิ้นหรือชิ้นทึบ 4 ชิ้น

ภาพด้านบนแสดงเกมที่เสร็จแล้วมีสี่ในแถวเนื่องจาก 4 ตารางชิ้น

ท้าทาย

ใน Quarto เกมบางเกมอาจจบลงด้วยผลเสมอ

จำนวนทั้งหมดของตำแหน่งสิ้นสุดที่เป็นไปได้คือ16!ประมาณ 20 ล้านล้าน

มีตำแหน่งสิ้นสุดจำนวนเท่าใดที่ดึงดูด?

กฎระเบียบ

  1. การแก้ปัญหาจะต้องเป็นโปรแกรมที่คำนวณและส่งออกจำนวนตำแหน่งทั้งหมดที่ถูกดึงออกมา คำตอบที่ถูกต้องคือ414298141056

  2. คุณสามารถใช้ข้อมูลของกฎของเกมที่ได้รับการอนุมานด้วยตนเอง (ไม่มีหลักฐานช่วยคอมพิวเตอร์)

  3. อนุญาตให้ใช้การทำให้เข้าใจปัญหาทางคณิตศาสตร์ได้ง่าย แต่ต้องอธิบายและพิสูจน์ด้วยตนเอง (ด้วยตนเอง) ในโซลูชันของคุณ

  4. ผู้ชนะคือผู้ที่มีทางออกที่ดีที่สุดในแง่ของเวลาในการทำงานของ CPU

  5. เพื่อหาผู้ชนะผมจะทำงานทุกโซลูชั่นเดียวกับรายงานเวลาทำงานน้อยกว่า 30 เมตรบน MacBook Pro i7 2,5 GHz Intel Core กับ 16 GB RAM

  6. ไม่มีคะแนนโบนัสสำหรับวิธีการแก้ปัญหาที่สามารถใช้ได้กับขนาดกระดานอื่น ๆ แม้ว่ามันจะดี

  7. หากทำได้โปรแกรมของคุณจะต้องรวบรวมภายใน 1 นาทีบนฮาร์ดแวร์ที่กล่าวถึงข้างต้น

  8. ไม่อนุญาตช่องโหว่เริ่มต้น

การส่ง

กรุณาโพสต์:

  1. รหัสหรือลิงค์ github / bitbucket ไปยังรหัส
  2. ผลลัพธ์ของรหัส
  3. เวลาที่ใช้ในการวัดในท้องถิ่นของคุณ
  4. คำอธิบายวิธีการของคุณ

วันกำหนดส่ง

กำหนดส่งผลงานคือวันที่ 1 มีนาคมยังมีเวลาอีกมาก


ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
Martin Ender

คำตอบ:


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

คะแนน (ผู้ใช้ + sys): 1m35.727s


ดูเหมือนโซลูชันที่ยอดเยี่ยม อย่างไรก็ตามคุณสามารถขยายคำอธิบายให้กว้างขึ้นได้ไหม? คุณรู้ได้อย่างไรว่าวิธีแก้ปัญหานั้นถูกต้อง?
wvdz

ควรใช้ค่าสถานะคอมไพเลอร์ใดในเวลานี้ ฉันลองด้วย-O3 -march=nativeและได้ 1m48s บนเครื่องของฉัน (CC @wvdz)
เดนนิส

@ เดนนิสนั่นคือสิ่งที่ฉันไปด้วย
user1502040

@Dennis ฉันไม่มีความเชี่ยวชาญในการรวบรวม C. ฉันไม่ได้ใช้ธงแปลใด ๆ ฉันจะอัปเดตการแก้ไขของฉัน
wvdz

1

Java, 414298141056 เสมอ, 23m42.272s

ฉันหวังว่ามันจะไม่ขมวดคิ้วในการโพสต์วิธีการแก้ปัญหาของตัวเอง แต่เหตุผลที่ฉันโพสต์ความท้าทายนี้ในตอนแรกก็คือมันทำให้ฉันบ้าที่ฉันไม่สามารถหาวิธีที่มีประสิทธิภาพด้วยตัวเอง ความพยายามอย่างที่สุดของฉันจะใช้เวลาหลายวันกว่าจะเสร็จสมบูรณ์

หลังจากศึกษาคำตอบของผู้ใช้1502040ฉันได้ทำการแก้ไขโค้ดให้ทำงานภายในเวลาที่เหมาะสม โซลูชันของฉันยังคงแตกต่างกันมาก แต่ฉันได้ขโมยความคิดบางอย่าง:

  • แทนที่จะมุ่งเน้นไปที่ตำแหน่งสุดท้ายฉันมุ่งเน้นไปที่การเล่นเกมจริงวางหนึ่งชิ้นหลังจากที่อื่นบนกระดาน สิ่งนี้ทำให้ฉันสามารถสร้างตารางของตำแหน่งที่เหมือนกันทางความหมายโดยมีการนับที่ถูกต้อง
  • ตระหนักถึงลำดับที่ชิ้นส่วนถูกวาง: ควรวางสิ่งเหล่านั้นเพื่อให้คุณเพิ่มโอกาสชนะสูงสุด

ความแตกต่างที่สำคัญระหว่างการแก้ปัญหานี้และผู้ใช้1502040คือฉันไม่ได้ใช้ตาราง Zobrist แต่เป็นตัวแทนที่เป็นที่ยอมรับของบอร์ดซึ่งฉันคิดว่าแต่ละบอร์ดจะมีการโยกย้ายที่เป็นไปได้ 48 แบบ (2 * 4!) ฉันไม่หมุนหรือย้ายทั้งกระดาน แต่เพียงลักษณะของชิ้นส่วน

นี่คือสิ่งที่ดีที่สุดที่ฉันสามารถทำได้ แนวคิดสำหรับการเพิ่มประสิทธิภาพที่ชัดเจนหรือชัดเจนน้อยที่สุดยินดีต้อนรับมากที่สุด!

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

คะแนน (ผู้ใช้ + sys): 23m42.272s

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.