Nén trò chơi cờ vua nhỏ nhất


8

Cảm hứng:

Lấy cảm hứng mạnh mẽ từ việc nén bàn cờ nhỏ nhất, tôi quyết định thực hiện một cuộc thi tương tự nhưng khác biệt rõ rệt.

tl; dr

Lấy tệp Chess_Games.txt và nén nó nhiều nhất có thể để bạn có thể mở rộng nó thành tệp gốc.

Mục tiêu:

Viết một thuật toán để mã hóa và giải mã toàn bộ cơ sở dữ liệu cờ vua từ vị trí bắt đầu đến kết thúc

Mã hóa phải có khả năng xác định cho tất cả các trò chơi tất cả các vị trí:

  • Vị trí tất cả các mảnh
  • Nó là của ai
  • Cho dù người chơi có thể lâu đài ở mỗi bên.
  • Liệu người chơi có thể thực hiện một en-passant không, và nếu vậy, con tốt nào của họ?
  • Vị trí trước

Ngoài ra:

  • Mỗi trò chơi cũng nên bao gồm ai thắng và kết thúc như thế nào (bị tịch thu, bốc thăm, chiếu tướng, bế tắc, v.v ...)

Đầu ra đầu vào:

Cần có 2 thuật toán Nén / Mở rộng thỏa mãn các thuộc tính sau:

  • Nén có một tệp trò chơi thông qua một chuỗi các bước di chuyển thông qua ký hiệu cờ vua và tệp nén đầu ra

  • Mở rộng thì ngược lại, lấy một tệp nén và xuất ra tệp gốc với tất cả các trò chơi theo cùng một thứ tự

  • Tính chính xác: Mở rộng (Nén (tệp)) = tệp cho tất cả các tệp được tạo đúng

Bất kỳ trò chơi nào không được hình thành tốt hoặc vi phạm các quy tắc của cờ vua đều bị coi là xấu. Tất cả các trò chơi xấu có thể được bỏ qua.

Nó phải có khả năng phân tích ký hiệu sen. Hãy xem Chessgames.comhttps://database.lichess.org/ để biết một số ví dụ.

Tôi đã biên soạn một tệp trong số 10000 trò chơi đầu tiên từ "tháng 5 năm 2017" trong Chess_Games.txt

Các tập tin sẽ trông như sau:

e4 d5 exd5 Nf6 d3 Qxd5 Nc3 Qf5 Be2 Bd7 g4 Qe6 g5 Nd5 Ne4 Bc6 Bg4 Qe5 f4 Qd4 Nf3 Qb6 Qe2 e6 Be3 Qa6 O-O Nd7 c4 Ne7 f5 Bxe4 dxe4 exf5 exf5 O-O-O f6 gxf6 gxf6 Nc6 Nd4 Nxd4 Bxd4 Bc5 Bxc5 Rhg8 Be7 Rde8 b4 Qxf6 Bxf6 Rxe2 h3 h5 Kh1 hxg4 Rg1 Nxf6 Rae1 Rxe1 Rxe1 gxh3 Kh2 Ng4+ Kxh3 Nf2+ Kh2 Ng4+ Kg3 f5 Kf4 Nh6 Re7 Rg4+ Ke5 Kd8 Kf6 Ng8+ Kf7 Nxe7 Kf8 f4 c5 f3 c6 f2 cxb7 Nc6 b8=Q+ Nxb8 Kf7 Kd7 0-1
d3 e6 e3 d5 Nf3 Nf6 Be2 Be7 O-O O-O Nc3 Nbd7 Ne1 c5 f3 e5 e4 d4 Nb1 b6 f4 exf4 Rxf4 Qc7 Rf3 Bd6 g3 Ng4 Rf1 Ndf6 Bxg4 Bxg4 Qd2 Rae8 Qf2 Re5 Bf4 Rh5 Bxd6 Qxd6 Nf3 c4 e5 Rxe5 Nxe5 Qxe5 Nd2 cxd3 cxd3 Be2 Rfe1 Qe3 Qxe3 dxe3 Ne4 Bxd3 Nxf6+ gxf6 Rxe3 Bg6 Rae1 Kg7 h4 h5 R1e2 Bf5 Rg2 Bg4 Re4 Kg6 Rge2 f5 Re5 f6 Re6 Rg8 Re7 Rg7 Re8 1-0
d4 d5 e4 dxe4 c4 Nf6 Qc2 Bf5 Nc3 Qxd4 Be3 Qe5 Nge2 e6 Ng3 Bb4 a3 Bxc3+ bxc3 Nc6 Be2 Na5 O-O Nxc4 Bd4 Qb5 Nxf5 exf5 Bxf6 gxf6 Rab1 Nxa3 Qa2 Qxb1 Rxb1 Nxb1 Qxb1 O-O-O Qb3 b6 Ba6+ Kb8 Qb5 Rhe8 Qc6 1-0
e3 c5 d3 d5 Nf3 Nc6 Be2 Nf6 O-O g6 h3 Bg7 Re1 O-O Nbd2 Re8 Nf1 e5 c3 b6 N3h2 Bb7 Qc2 Qc7 b3 Rac8 Bb2 a5 Rac1 a4 Qb1 axb3 axb3 Ba6 d4 Bxe2 Rxe2 exd4 cxd4 Qb8 dxc5 d4 Ree1 dxe3 Rxe3 Nd5 Rxe8+ Rxe8 Bxg7 Kxg7 Re1 Rxe1 Qxe1 bxc5 Qd1 Nd4 Ne3 Nf4 Neg4 Qxb3 Qe1 Qd5 Ne3 Qe6 Qf1 c4 Nhg4 Nde2+ Kh2 c3 g3 c2 gxf4 c1=Q Qxc1 Nxc1 Ng2 Qe4 Kg3 Nd3 f3 Qe2 Nh4 h5 Nh2 Qe1+ Kg2 Nf2 Nf5+ gxf5 Kg3 Qg1+ Kh4 Nxh3 Kxh5 1-0
e4 d5 exd5 Qxd5 Nc3 Qd8 d4 Bf5 Nf3 e6 Nh4 Bg6 Nxg6 hxg6 Be3 Bd6 Bc4 a6 Qf3 c6 O-O-O Nd7 Ne4 Qc7 Nxd6+ Qxd6 Bf4 Qb4 Bb3 Ngf6 Rhe1 O-O-O Bg3 a5 Qf4 Qb6 Re3 Rh5 Bc4 Rf5 Qd6 Ne8 Qa3 Qa7 Red3 b5 Bxb5 cxb5 Rc3+ Kb7 Qe7 b4 Rc5 Rxc5 dxc5 Qxc5 Qxd8 Ndf6 Qb8+ Ka6 Rd8 Qa7 Rxe8 Nxe8 Qxe8 Qc5 Qa8+ Kb5 Qb8+ Ka4 b3+ Ka3 Qf4 Qc3 Qe5 1-0
e4 d5 exd5 Qxd5 Nf3 Qd8 Nc3 e6 Bc4 Nf6 Bb3 Be7 a3 a6 O-O O-O h3 b6 d3 Bb7 Bg5 Nbd7 Ne4 h6 Bh4 Nxe4 Bxe7 Qxe7 dxe4 Bxe4 Re1 Bb7 Ba2 Nf6 b4 Rfd8 Qc1 Qd6 c4 Qc6 Qc2 Rd7 Rac1 Rad8 c5 bxc5 Qxc5 Qb5 Qxb5 axb5 Ne5 Rd2 Bb3 Rb2 Bd1 Rdd2 Re2 Rxe2 Bxe2 Rxe2 Nf3 Ra2 Rxc7 Bd5 Rc8+ Kh7 Ne5 Rxa3 Rf8 Ra1+ Kh2 h5 Rxf7 Kh6 Rf8 Kg5 g3 Kf5 Nd7 Ra2 Nxf6 gxf6 Rg8 Rxf2+ Kg1 Rg2+ Kf1 Rh2 g4+ hxg4 hxg4+ Ke5 Re8 Rh1+ Kf2 Rh2+ Kg3 Rg2+ Kh4 Rf2 Kg3 Rf4 g5 Re4 gxf6 Kxf6 Rf8+ Ke5 Rh8 Re3+ Kf2 Re4 Rh5+ Kd4 Rh6 Rf4+ Kg3 Re4 Rh8 Re3+ 0-1
...

Ghi điểm:

Để làm cho mọi thứ trở nên khách quan, người chiến thắng là thuật toán có thể nén các tệp tại https://database.lichess.org/ càng nhỏ càng tốt. Cơ sở dữ liệu mục tiêu là "tháng 5 năm 2017" . Người chiến thắng là bất cứ ai có tệp nhỏ nhất mở rộng đúng cách.

Tệp để sử dụng là Chess_Games.txt , đây là 10000 trò chơi đầu tiên từ cơ sở dữ liệu "tháng 5 năm 2017" tại https://database.lichess.org/ với tất cả thông tin tiêu đề đã bị xóa. Tập tin này sẽ là điểm chuẩn để sử dụng. Nó phải là 2.789.897 byte với hàm băm SHA-256 56b6d2fffc7644bcd126becd03d859a3cf6880fa01a47fa08d4d6a823a4866bc(Pastebin có thể xóa dòng mới cuối cùng sau trò chơi 10000)

Giải pháp ngây thơ:

Sử dụng thuật toán nén chung:

  • zip: 647.772 byte (76.841%)
  • 7z: 652.813 byte (76,661%)
  • bz2: 722.158 byte (74.182%)
  • xz: 784.980 byte (71.936%)
  • rar: 853.482 byte (69.487%)
  • gz: 923.474 byte (66.985%)

Không nên có một khoảng trống sauhttp://
JungHwan Min

Cảm ơn đã sửa nó. Trang web sẽ không cho phép tôi đăng câu hỏi có nhiều hơn 1 liên kết vì tôi không có danh tiếng phù hợp. Vì vậy, tôi đã thêm một khoảng trắng và hy vọng người khác sẽ sửa nó hoặc tôi có thể sửa nó sau.
edggy

2
Câu hỏi hay! Bạn có thể giải thích thêm về cách tính điểm? Ngoài ra, biến thể của ký hiệu cờ tiêu chuẩn nào chúng ta nên mong đợi và đầu ra? Trong tương lai, bạn có thể thấy Sandbox hữu ích :)
musicman523 16/07/17

1
Chúng tôi có thể lấy trò chơi cờ / ký hiệu bạn sẽ sử dụng để ghi câu trả lời không?
wrymug

1
Tôi đã loại bỏ các chú thích từ tệp điểm chuẩn và xóa các tham chiếu khó hiểu các quy tắc vẽ để làm cho mọi thứ rõ ràng hơn. Tôi đã thay đổi tuyên bố về phân tích cú pháp để sử dụng các trang web đó làm tài liệu tham khảo.
edggy

Câu trả lời:


3

Python, điểm = 426508 byte

Hàm nén: (m + 1) log 2 (b + 1)

m là số lần di chuyển và b là hệ số phân nhánh

Nỗ lực 1:

Tôi đã trả lời câu hỏi tại nén bàn cờ nhỏ nhất vì vậy tôi đang cố gắng sửa nó để đăng ở đây.

Tôi nghĩ rằng tôi có thể mã hóa mỗi lần di chuyển trong 12 bit, 4 bộ ba có dạng (bắt đầu x, bắt đầu y, kết thúc x, kết thúc y) trong đó mỗi lần là 3 bit.

Chúng tôi sẽ đảm nhận vị trí bắt đầu và di chuyển các mảnh từ đó với màu trắng đi trước. Bảng được sắp xếp sao cho (0, 0) là góc dưới bên trái màu trắng.

Ví dụ: trò chơi:

  e4    e5
 Nf3    f6
Nxe5  fxe5
...    ...

Sẽ được mã hóa thành:

100001 100010 100110 100100
110000 101010 101110 101101
101010 100100 101101 100100
...

Điều này dẫn đến mã hóa 12 m bit trong đó m là số lần di chuyển được thực hiện.

Cố gắng 2:

Tôi nhận ra rằng mỗi động thái trong lần thử trước đều mã hóa nhiều động thái bất hợp pháp. Vì vậy, tôi quyết định chỉ mã hóa các động thái hợp pháp. Chúng tôi liệt kê các chuyển động có thể như sau, đánh số từng ô vuông sao cho (0, 0) → 0, (1, 0) → 1, (x, y) → x + 8 y. Lặp lại qua các ô và kiểm tra nếu một mảnh ở đó và nếu nó có thể di chuyển. Nếu vậy, thêm các vị trí nó có thể đi đến một danh sách. Chọn chỉ mục danh sách đó là động thái bạn muốn thực hiện. Thêm số đó vào tổng số lần di chuyển có trọng số bằng 1 cộng với số lần di chuyển có thể.

Ví dụ như trên: Từ vị trí bắt đầu, mảnh đầu tiên có thể di chuyển là hiệp sĩ trên ô vuông 1, nó có thể di chuyển đến ô vuông 16 hoặc 18, vì vậy hãy thêm chúng vào danh sách [(1,16),(1,18)]. Tiếp theo là hiệp sĩ trên ô vuông 6, thêm di chuyển của nó. Nhìn chung, chúng tôi nhận được:

[(1,16),(1,18),(6,21),(6,23),(8,16),(8,24),(9,17),(9,25),(10,18),(10,26),(11,19),(11,27),(12,20),(12,28),(13,21),(13,29),(14,22),(14,30),(15,23),(15,31)]

Vì chúng tôi muốn di chuyển (12, 28), chúng tôi mã hóa số này thành 13 trong cơ sở 20 vì có 20 di chuyển có thể.

Vì vậy, bây giờ chúng tôi nhận được số trò chơi g 0 = 13

Tiếp theo, chúng tôi làm tương tự cho màu đen ngoại trừ chúng tôi đánh số các ô ngược lại (để dễ dàng hơn, không bắt buộc) để có được danh sách di chuyển:

[(1,16),(1,18),(6,21),(6,23),(8,16),(8,24),(9,17),(9,25),(10,18),(10,26),(11,19),(11,27),(12,20),(12,28),(13,21),(13,29),(14,22),(14,30),(15,23),(15,31)]

Vì chúng tôi muốn di chuyển (11, 27), chúng tôi mã hóa số này thành 11 trong cơ sở 20 vì có 20 di chuyển có thể.

Vì vậy, bây giờ chúng tôi nhận được số trò chơi g 1 = (11 20) + 13 = 233

Tiếp theo, chúng tôi nhận được danh sách các động thái sau đây cho màu trắng:

[(1,16),(1,18),(3,12),(3,21),(3,30),(3,39),(4,12),(5,12),(5,19),(5,26),(5,33),(5,40),(6,12),(6,21),(6,23),(8,16),(8,24),(9,17),(9,25),(10,18),(10,26),(11,19),(11,27)(13,21),(13,29),(14,22),(14,30),(15,23),(15,31)]

Vì chúng tôi muốn di chuyển (6, 21), chúng tôi mã hóa số này thành 13 trong cơ sở 29 vì có 29 di chuyển có thể.

Vì vậy, bây giờ chúng tôi nhận được số trò chơi g 2 = ((13 ⋅ 20) + 11) 20 + 13 = 5433

Tiếp theo, chúng tôi nhận được danh sách di chuyển sau đây cho màu đen: [(1,11),(1,16),(1,18),(2,11),(2,20),(2,29),(2,38),(2,47),(3,11),(4,11),(4,18),(4,25),(4,32),(6,21),(6,23),(8,16),(8,24),(9,17),(9,25),(10,18),(10,26),(12,20),(12,28),(13,21),(13,29),(14,22),(14,30),(15,23),(15,31)]

Vì chúng tôi muốn di chuyển (10, 18), chúng tôi mã hóa số này thành 19 trong cơ sở 29 vì có 29 di chuyển có thể.

Vì vậy, bây giờ chúng tôi nhận được số trò chơi g 3 = (((19 ⋅ 29 + 13) 20) + 11) 20 + 13 = 225833

Và tiếp tục quá trình này cho tất cả các động thái còn lại. Bạn có thể nghĩ g là hàm g (x, y, z) = x y + z. Do đó g 0 = g (1, 1, 13), g 1 = g (g (1, 1, 11), 20, 13), g 2 = g (g (g (1, 1, 13), 20, 11), 20, 13), g 3 = g (g (g (g (1, 1, 19), 29, 13), 20, 11), 20, 13)

Để giải mã số trò chơi g 0 , chúng tôi bắt đầu ở vị trí ban đầu và liệt kê tất cả các di chuyển có thể. Sau đó, chúng tôi tính g 1 = g 0 // l , m 0 = g 0 % l , trong đó l là số lần di chuyển có thể, '//' là toán tử chia số nguyên và '%' là toán tử mô đun. Cần giữ g 0 = g 1 + m 0 . Tiếp theo chúng ta thực hiện di chuyển m 0 và lặp lại.

Từ ví dụ trên nếu g 0 = 225833 thì g 1 = 225833 // 20 = 11291 và m 0 = 225833% 20 = 13. Tiếp theo g 2 = 11291 // 20 = 564 và m 1 = 11291% 20 = 11. Sau đó g 3 = 11291 // 20 = 564 và m 2 = 11291% 20 = 11. Do đó g 4 = 564 // 29 = 19 và_m_ 3 = 564% 29 = 13. Cuối cùng g 5 = 19 // 29 = 0 và m 4 = 19% 29 = 19.

Vậy có bao nhiêu bit được sử dụng để mã hóa một trò chơi theo cách này?

Để đơn giản, giả sử luôn có 35 lượt di chuyển mỗi lượt ( https://en.wikipedia.org/wiki/Branching_factor ) và trong trường hợp xấu nhất, chúng tôi luôn chọn số lớn nhất, 34. Số chúng tôi sẽ nhận được là 34 35 m + 34 35 m - 1 + 34 35 m - 2 + ⋯ + 34 ⋅ 35 + 34 = 35 m + 1 - 1 trong đó _m là số lần di chuyển. Để mã hóa 35 m + 1 - 1, chúng ta cần khoảng các bit log 2 (35 m + 1 ) khoảng (m + 1) ⋅ log 2 (35) = 5.129 (m + 1)

Trung bình m = 80 (40 di chuyển cho mỗi người chơi), do đó, sẽ mất tới 420 bit để mã hóa. Nếu chúng tôi ghi lại nhiều trò chơi, chúng tôi sẽ cần mã hóa toàn cầu vì chúng tôi không biết mỗi số sẽ cần bao nhiêu bit

Trường hợp xấu nhất khi m = 11741 vì vậy điều này sẽ mất 60229 bit để mã hóa.

Ghi chú bổ sung:

Lưu ý rằng g = 0 biểu thị một trò chơi hợp lệ, trò chơi mà mảnh trên hình vuông thấp nhất di chuyển đến hình vuông thấp nhất có thể.

Nếu bạn muốn tham khảo một vị trí cụ thể trong trò chơi, bạn có thể cần phải mã hóa chỉ mục. Điều này có thể được thêm bằng tay, ví dụ: nối chỉ mục vào trò chơi hoặc thêm một động thái "kết thúc" bổ sung khi di chuyển cuối cùng có thể mỗi lượt. Điều này hiện có thể giải thích cho các cầu thủ bị thủng lưới, hoặc 2 liên tiếp để biểu thị các cầu thủ đã đồng ý rút thăm. Điều này chỉ cần thiết nếu trò chơi không kết thúc trong một ván cờ hoặc bế tắc dựa trên vị trí, trong trường hợp này là ngụ ý. Trong trường hợp này, nó đưa số bit cần thiết trung bình lên tới 419 và trong trường hợp xấu nhất là 60706.

Một cách để xử lý các nhánh trong kịch bản what-if là mã hóa trò chơi lên ngã ba và sau đó mã hóa từng nhánh bắt đầu từ vị trí rẽ nhánh thay vì vị trí ban đầu.

Thực hiện:

Hãy xem repo github của tôi để biết mã: https://github.com/edggy/ChessCompress


3

Python, điểm = 418581 byte

Điều này sử dụng một biến thể phỏng đoán của các hệ thống số không đối xứng . Vì đó là tính từ, bạn không chỉ có thể nén bất kỳ danh sách trò chơi cờ hợp lệ nào vào một tệp và mở rộng nó trở lại cùng một danh sách mà bạn cũng có thể mở rộng bất kỳ tệp nào vào danh sách trò chơi cờ hợp lệ và nén lại vào cùng một tệp! Hoàn hảo để ẩn bộ sưu tập khiêu dâm của bạn.

Yêu cầu trăn-cờ vua . Chạy với python script.py compresshoặc python script.py expand, cả hai sẽ đọc từ đầu vào tiêu chuẩn và ghi vào đầu ra tiêu chuẩn.

import chess
import sys

RESULTS = ['1-0\n', '1/2-1/2\n', '0-1\n', '*\n']
BITS = 24

def get_moves(board):
    if board.is_insufficient_material() or not board.legal_moves:
        return [board.result() + '\n']
    else:
        return RESULTS + sorted(
            board.legal_moves,
            key=lambda move: (move.from_square, move.to_square, move.promotion))

def read_bijective():
    buf = bytearray(getattr(sys.stdin, 'buffer', sys.stdin).read())
    carry = 0
    for i in range(len(buf)):
        carry += buf[i] + 1
        buf[i] = carry & 0xff
        carry >>= 8
    if carry:
        buf.append(carry)
    return buf

def write_bijective(buf):
    carry = 0
    for i in range(len(buf)):
        carry += buf[i] - 1
        buf[i] = carry & 0xff
        carry >>= 8
    while carry:
        carry = (carry << 8) + buf.pop() + 1
    getattr(sys.stdout, 'buffer', sys.stdout).write(buf)

def add_carry(buf, carry):
    for i in range(len(buf)):
        if carry == 0:
            break
        carry += buf[i]
        buf[i] = carry & 0xff
        carry >>= 8
    return carry

def do_compress():
    board = chess.Board()
    state = 0
    buf = bytearray()

    games = []
    for sans in sys.stdin:
        game = []
        for san in sans.split(' '):
            move = san if san in RESULTS else board.parse_san(san)
            moves = get_moves(board)
            game.append((len(moves), moves.index(move)))
            if move in RESULTS:
                board.reset()
            else:
                board.push(move)
        games.append(game)

    for game in reversed(games):
        for (n, i) in reversed(game):
            q = ((1 << BITS) - 1 - i) // n + 1
            while state >= q << 8:
                buf.append(state & 0xff)
                state >>= 8
            hi, j = divmod(state, q)
            lo = n * j + i
            state = hi << BITS | lo
        state += add_carry(buf, 1)

    while state:
        buf.append(state & 0xff)
        state >>= 8
    write_bijective(buf)

def do_expand():
    board = chess.Board()
    state = 0
    buf = read_bijective()

    while True:
        while buf and state < 1 << BITS:
            state = state << 8 | buf.pop()
        if state == 0:
            break
        state += add_carry(buf, -1)

        while True:
            moves = get_moves(board)
            while buf and state < 1 << BITS:
                state = state << 8 | buf.pop()
            n = len(moves)
            hi, lo = divmod(state, 1 << BITS)
            j, i = divmod(lo, n)
            q = ((1 << BITS) - 1 - i) // n + 1
            state = j + q * hi
            move = moves[i]
            if move in RESULTS:
                sys.stdout.write(move)
                board.reset()
                break
            else:
                sys.stdout.write(board.san(move).rstrip('+#') + ' ')
                board.push(move)

if __name__ == '__main__':
    {'compress': do_compress, 'expand': do_expand}[sys.argv[1]]()

1
Đã xác minh trên Ubuntu 17.04 bằng Python 2.7.13 và chạy từng lệnh riêng biệt.
edggy

Điều này thực sự tuyệt vời! Tôi cũng tò mò những trò chơi cờ vua khiêu dâm sẽ như thế nào trong thực tế.
Anush
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.