UNPKG

chess-tools

Version:

A collection of tools / libraries for programming chess applications.

315 lines (309 loc) 12.5 kB
"use strict"; var debug = require('debug')('CTG'); var utils = require(__dirname + '/../../utils.js'); const CTGEntry = require("./entry.js"); //Based on notes from http://rybkaforum.net/cgi-bin/rybkaforum/topic_show.pl?tid=2319 const Transform = require('stream').Transform; const EventEmitter = require('events'); const Chess = require('chess.js').Chess; const chess = new Chess(); const chess_black = new Chess(); const files = utils.board.FILES; const ranks = utils.board.RANKS; const board_index = utils.board.BOARD_INDEX; const flip_board = utils.board.FLIP_BOARD; const mirror_file = utils.board.MIRROR_FILE; const CTGMoveService = require("./moves.js"); const moveService = new CTGMoveService(); const {peice_encoding, peice_encoding_black, flip_ep_column, castle_encoding,en_passant_encoding,en_passant_encoding_black,ep_mask, castle_mask, po, ep, ca } = require("./encoding.js"); class CTGStream extends Transform { constructor() { super({readableObjectMode : true }); this.page = -1; this.entry_num = 0; } _flush(callback) { callback(); } _transform(chunk, encoding, callback) { if (this._data) { this._data = Buffer.concat(this._data, chunk); } else { this._data = chunk; } let dataview = new DataView(this._data.buffer); if (this.page === -1) { if (this._data.length > 32 && typeof this.number_of_games === 'undefined') { this.number_of_games = dataview.getUint32(28); // console.log("Number of Games", this.number_of_games); } if (this._data.length >= 4096) { this.page++; this._data = Buffer.from(this._data.buffer.slice(4096, this._data.length)); dataview = new DataView(this._data.buffer); } } if (this.page > -1 && this._data.length >= 4096) { //we have pages. let num_pages = parseInt(this._data.length / 4096); // console.log("Got " + num_pages + " pages", this._data.length ); let remainder = this._data.length % 4096; // console.log("remainder length is", remainder); let extra_data; if (remainder > 0) { extra_data = new Buffer(this._data.buffer.slice(this._data.buffer.length - remainder, this._data.buffer.length)); } for (let page_num = 0 ; page_num < num_pages; page_num++) { this.process_page(page_num, dataview); } this._data = extra_data; } callback(); } process_page(page_num, dataView) { this.page++; let page_start = page_num * 4096; let number_of_positions = dataView.getUint16(page_start); let bytes_in_page = dataView.getUint16(page_start+2); // console.log("Page Start", page_start, page_start + bytes_in_page) let page = this._data.buffer.slice(page_start, page_start + bytes_in_page); let pageView = new DataView(page); // console.log("Page Len", page); // console.log("page", this.page, "number_of_positions", number_of_positions); // console.log("page", this.page, "bytes_in_page", bytes_in_page); this.record_start = 4; this.last_record_start =4; for (let pos = 0; pos < number_of_positions; pos++) { this.process_entry(pos, page, pageView); } } process_entry(pos, page, pageView) { this.entry_num++; let entry = new CTGEntry(); let entry_black = new CTGEntry('b'); entry.entry_num = this.entry_num; entry.page = this.page; entry.record_start = this.record_start; entry.pos = pos; entry_black.entry_num = this.entry_num; entry_black.page = this.page; entry_black.record_start = this.record_start; entry_black.pos = pos; this.last_record_start = this.record_start; let header_byte = pageView.getUint8(this.record_start); let position_length = header_byte & 0x1f; let en_passant = header_byte & 0x20; let castling = header_byte & 0x40; if (!header_byte) { // console.log("INVALID HEADER BYTE WTF!!!", this.record_start); utils.debug_buffer_to_string(page.slice(this.record_start-3, this.record_start+3)); process.exit(); } // console.log(utils.pad_number_string(header_byte.toString(2), 8)); entry.position_length = position_length; // console.log("POSITION LENGTH", position_length); entry.has_en_passant = en_passant; entry.has_castling = castling; entry_black.position_length = position_length; entry_black.has_en_passant = en_passant; entry_black.has_castling = castling; let p1 = this.record_start + 1; let p2 = p1 + position_length; let position_buffer = page.slice(p1, p2); let position_view = new Uint8Array(position_buffer); let binary_string = ""; position_view.forEach((element)=>{ binary_string += utils.pad_number_string(element.toString(2), 8); }); entry.encoded_position = binary_string; entry_black.encoded_position = binary_string; let board = []; let board_black = []; let str_position = 0; let max = 6; let rank = 0; let file = 0; let black_is_mirrored = false; chess.clear(); chess_black.clear(); POSITION_LOOP: for (let board_position = 0; board_position < 64; board_position++) { EVAL_LOOP: for (let str_len = 1; str_len <= max; str_len++) { let eval_string = binary_string.substring(str_position, str_position+str_len); for (let peice_code of Object.keys(peice_encoding)) { if (String(eval_string) === String(peice_code)) { if (rank == 8) { file++; rank = 0; } let algebraic_position = files[file] + "" + ranks[rank]; let black_position = flip_board[algebraic_position]; if (peice_encoding[String(peice_code)] && peice_encoding[String(peice_code)].txt !== ' ') { chess.put(peice_encoding[String(peice_code)], algebraic_position); if (peice_encoding_black[String(peice_code)].txt == 'K') { if (black_position.match(/[abcd]\d/)) { if (!entry.has_castling) { black_is_mirrored = true; } } } chess_black.put(peice_encoding_black[String(peice_code)], black_position); }; board.push({ peice: peice_encoding[String(peice_code)], position : algebraic_position }); board_black[board_index[black_position]] = { peice: peice_encoding_black[String(peice_code)], position : black_position } str_position += String(peice_code).length; str_len=1; rank++; continue POSITION_LOOP; } } } } if (black_is_mirrored) { entry_black.is_mirrored = true; chess_black.clear(); let tmp_board_black = []; entry_black.has_castling = 0; for (let position of board_black) { let pos_elements = position.position.split(""); pos_elements[0] = mirror_file[pos_elements[0]]; position.position = pos_elements.join(""); let updated = board_index[position.position]; tmp_board_black[updated] = position; if (position.peice.txt != ' ') { chess_black.put(position.peice, position.position); } } board_black = tmp_board_black; } entry.board = board; entry_black.board = board_black; this.record_start += position_length; if (en_passant || castling) { let ep_castle = pageView.getUint8(this.record_start-1); let ep_value = ep_castle & ep_mask; ep_value = ep_value >> 5; entry.en_passant_data = ep_value; entry_black.en_passant_data = black_is_mirrored ? flip_ep_column[ep_value] : ep_value; let castle_value = ep_castle & castle_mask; castle_value = castle_value >> 1; entry.castling_data = castle_value; entry_black.castling_data = castle_value; } entry.setFen(chess.fen()); entry_black.setFen(chess_black.fen()); let book_moves_size = pageView.getUint8(this.record_start); let number_book_moves = 0; if (book_moves_size > 0) { number_book_moves = ( book_moves_size - 1 ) /2; } let move_start = this.record_start + 1; for (let m = 0; m < number_book_moves; m++) { let book_move = pageView.getUint8(move_start+(m*2)); let book_annotation = pageView.getUint8(move_start+(m*2)+1); let move_and_analysis = moveService.decode_move(book_move, book_annotation, board); let move_and_analysis_black = moveService.decode_move(book_move, book_annotation, board_black, 'b', black_is_mirrored); entry.book_moves.push(move_and_analysis); entry_black.book_moves.push(move_and_analysis_black); } this.record_start += book_moves_size; let num_games = read_24(pageView, this.record_start); entry.total_games = num_games; this.record_start += 3; let num_white_wins = read_24(pageView, this.record_start); entry.white_wins = num_white_wins; entry_black.black_wins = num_white_wins; this.record_start += 3; let num_black_wins = read_24(pageView, this.record_start); entry.black_wins = num_black_wins; entry_black.white_wins = num_black_wins; this.record_start += 3; let num_draws = read_24(pageView, this.record_start); entry.draws = num_draws; entry_black.draws = entry.draws; this.record_start += 3; let unkown_integer = pageView.getUint32(this.record_start); entry.unknown1 = unkown_integer this.record_start += 4; let rating1_num_games = read_24(pageView, this.record_start); this.record_start += 3; let rating1_rating_total = pageView.getUint32(this.record_start); let rating1_rating = rating1_rating_total/rating1_num_games; entry.ratings.push({ games : rating1_num_games, rating : rating1_rating, total_ratings : rating1_rating_total }); this.record_start += 4; let rating2_num_games = read_24(pageView, this.record_start); this.record_start += 3; let rating2_rating_total = pageView.getUint32(this.record_start); this.record_start += 4; let rating2_rating = rating2_rating_total / rating2_num_games; entry.ratings.push({ games : rating2_num_games, rating : rating2_rating, total_ratings : rating2_rating_total }); entry_black.ratings = entry.ratings; let recommendation = pageView.getUint8(this.record_start); entry.recommendation = moveService.decode_analysis(recommendation); entry_black.recommendation; this.record_start += 1; let unknown2 = pageView.getUint8(this.record_start); entry.unknown2 = unknown2; this.record_start += 1; let commentary = pageView.getUint8(this.record_start); entry.commentary = moveService.decode_analysis(commentary); entry_black.commentary = entry.commentary; this.record_start +=1; let statistics_size = 12 + 4 + 14 + 3; //stats, unknown, ratings, recommendation, commentary; let record_offset = ( position_length + book_moves_size + statistics_size); entry.record_offset = record_offset; entry_black.record_offset = record_offset; // console.log(entry.record_start, entry.record_offset); this.push(entry); this.push(entry_black); } } class CTG extends EventEmitter { constructor() { super(); this.loaded = false; this.stream = new CTGStream(); this.entries = { b : {}, w : {}, } } load_book(stream) { this.stream.on( "data", (entry)=>{ if (this.entries[entry.to_move][entry.key]) { console.log("possible duplicate for entry") console.log("New Entry:", JSON.stringify(entry, null, ' ')); console.log("OLD ENTRY:", JSON.stringify(this.entries[entry.to_move][entry.key])); } this.entries[entry.to_move][entry.key] = entry; }); this.stream.on('finish', ()=>{ this.loaded= true; this.emit("loaded"); }); this.stream.on('error', (error)=>{ console.log("error", error); this.emit("error", error); }) stream.pipe(this.stream); } find(fen) { if (!this.loaded) { throw new Error("No book is loaded") } let to_move = fen.split(" ")[1]; let key = utils.key_from_fen(fen); return this.entries[to_move][key]; } } module.exports=CTG; CTG.CTGStream = CTGStream; CTG.CTGEntry = CTGEntry; function read_24(dataview, start) { let byte1 = dataview.getUint8(start); let byte2 = dataview.getUint8(start+2); let byte3 = dataview.getUint8(start+4); let res = (byte1 << 16) + (byte2 << 8) + byte3; return res; }