const PACK = { u8(value) { return new Uint8Array([ value & 0xFF ]); }, u16(value) { return new Uint8Array([ (value >> 8) & 0xFF, value & 0xFF ]); }, u32(value) { return new Uint8Array([ (value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF ]); }, string(text, pack) { let enc = new TextEncoder(); let bytes = enc.encode(text); let size_bytes = pack(bytes.length); let length = size_bytes.length + bytes.length; let result = new Uint8Array(length); result.set(size_bytes, 0); result.set(bytes, size_bytes.length); return result; }, base64(bytes) { let str = ""; for(let i = 0; i < bytes.length; ++i) { str += String.fromCharCode(bytes[i]); } let enc = window.btoa(str); enc.replace("/", "-"); return enc; }, }; const UNPACK = { u8(data, index) { let result = 0; if(index + 1 <= data.length) { result = data[index]; index += 1; } return { data: result, index: index }; }, u16(data, index) { let result = 0; if(index + 2 <= data.length) { result = (data[index] << 8) + data[index + 1]; index += 2; } return { data: result, index: index }; }, u32(data, index) { let result = 0; if(index + 4 <= data.length) { result = (data[index] << 24) + (data[index + 1] << 16) + (data[index + 2] << 8) + data[index + 3]; index += 4; } return { data: result, index: index }; }, string(data, index, unpack=UNPACK.u16) { let result = unpack(data, index); index = result.index; let length = result.data; let dec = new TextDecoder(); let result_str = ""; if(index + length <= data.length) { let bytes = new Uint8Array(length); for(let i = 0; i < length; ++i) { bytes[i] = data[index + i]; } index += length; result_str = dec.decode(bytes); } else { console.log("error: unexpected end of data (" + data.length + " / " + length + ")"); } return { data: result_str, index: index }; }, move(bytes) { function piece_by_id(id) { switch(id) { case 0: return ""; case 1: return "M"; case 2: return "N"; case 3: return "L"; case 4: return "T"; case 5: return "C"; case 6: return "D"; case 7: return "K"; } } /* ** From [6] ** To [6] ** Piece [3] ** Take [3] ** */ let from = (bytes[0] & 0xFC) >> 2; let to = ((bytes[0] & 0x03) << 4) | ((bytes[1] & 0xC0) >> 6); let piece = piece_by_id((bytes[1] & 0x38) >> 3); let take = piece_by_id(bytes[1] & 0x07); let source = (bytes[2] & 0x80) >> 7; switch((bytes[2] & 0x60) >> 5) { case 0: state = ""; case 1: state = "◇"; break; case 2: state = "◈"; break; case 3: state = "◆"; break; } let str = ""; if(state.length > 0) { if(source == 1) { str = "" + piece + " " + state + " " + to; } else { str = "" + from + " " + piece + " " + state + " " + to + " " + take; } if(take != "") { str += " " + take; } } return str; }, base64(data) { data.replace("-", "/"); let str = window.atob(data); let bytes = new Uint8Array(str.length); for(let i = 0; i < bytes.length; ++i) { bytes[i] = str.charCodeAt(i); } return bytes; }, }; const BITWISE = { lsb(x) { // Least significant bit return x & -x; }, ffs(x) { // Find first set return 31 - Math.clz32(x & -x); }, count(mask) { // source: https://graphics.stanford.edu/~seander/bithacks.html mask = mask|0; mask = mask - ((mask >> 1) & 0x55555555); mask = (mask & 0x33333333) + ((mask >> 2) & 0x33333333); return ((mask + (mask >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; }, rotate_blocks(mask) { const r1 = 0x00003F; // first 6 bits const r2 = 0x000FC0; // second 6 bits const r3 = 0x03F000; // third 6 bits let v1 = (r1 & mask) << 3; let v2 = (r2 & mask) << 3; let v3 = (r3 & mask) << 3; v1 = (v1 & r1) | ((v1 & ~r1) >> 6); v2 = (v2 & r2) | ((v2 & ~r2) >> 6); v3 = (v3 & r3) | ((v3 & ~r3) >> 6); return v1 | v2 | v3; }, }; const MATH = { Vec2: class { constructor(x, y) { this.x = x; this.y = y; } add(v) { this.x += v.x; this.y += v.y; } mul(v) { this.x *= v; this.y *= v; } copy() { return new MATH.Vec2(this.x, this.y); } }, sign(a) { return 1 - ((a < 0) << 1); }, mod(a, b) { return ((a % b) + b) % b }, lerp(a, b, t) { return a + ((b - a) * t); }, sign_branch(v) { return (v * 2) - 1; }, }; const COLOR = { rgba(r, g, b, a) { let ur = Math.floor(r * 255); let ug = Math.floor(g * 255); let ub = Math.floor(b * 255); let ua = Math.floor(a * 255); return "rgba(" + ur + "," + ug + "," + ub + "," + ua + ")"; }, } const HEX = { hex_to_tile(hex) { let a = ((hex.x + 4) * (hex.x + 5) / 2) - 10; let b = (hex.x > 4) * ((hex.x - 4) + ((hex.x - 5) * (hex.x - 4))); return a - b + hex.y; }, tile_to_hex(tile) { const ROWS = [ 0, 5, 11, 18, 26, 35, 43, 50, 56, 61 ]; let column = 0; while(tile >= ROWS[column + 1]) { column += 1; } let row = tile - ROWS[column] + ((column > 4) * (column - 4)); return new MATH.Vec2(column, row); }, is_valid_board(hex) { // x = minimum // y = maximum const COLUMNS = [ new MATH.Vec2(0, 4), new MATH.Vec2(0, 5), new MATH.Vec2(0, 6), new MATH.Vec2(0, 7), new MATH.Vec2(0, 8), new MATH.Vec2(1, 8), new MATH.Vec2(2, 8), new MATH.Vec2(3, 8), new MATH.Vec2(4, 8), ]; return (hex.x >= 0 && hex.x < 9 && hex.y >= COLUMNS[hex.x].x && hex.y <= COLUMNS[hex.x].y); }, is_valid_pool(hex) { // x = minimum // y = maximum const COLUMNS = [ new MATH.Vec2(0, 1), new MATH.Vec2(0, 2), new MATH.Vec2(1, 2), ]; return (hex.x >= 0 && hex.x < 3 && hex.y >= COLUMNS[hex.x].x && hex.y <= COLUMNS[hex.x].y); }, };