#77qfigl5 — this one is kept as-is (links and embeds never break) but no longer shows in browse.
/*{ "DESCRIPTION": "Playable chess entirely in GLSL. Choose From and To, then press Move. Promotion defaults to Queen. Rules, history, checkmate and draws live in feedback textures. No mouse binding is required. Changing resolution or seeking resets the game; keep live playback running.", "ISFVSN": "2", "CATEGORIES": [ "Game", "Chess", "Interactive" ], "INPUTS": [ { "NAME": "uFrom", "TYPE": "long", "LABEL": "From", "DEFAULT": 12, "VALUES": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 ], "LABELS": [ "a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1", "a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2", "a3", "b3", "c3", "d3", "e3", "f3", "g3", "h3", "a4", "b4", "c4", "d4", "e4", "f4", "g4", "h4", "a5", "b5", "c5", "d5", "e5", "f5", "g5", "h5", "a6", "b6", "c6", "d6", "e6", "f6", "g6", "h6", "a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7", "a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8" ] }, { "NAME": "uTo", "TYPE": "long", "LABEL": "To", "DEFAULT": 28, "VALUES": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 ], "LABELS": [ "a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1", "a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2", "a3", "b3", "c3", "d3", "e3", "f3", "g3", "h3", "a4", "b4", "c4", "d4", "e4", "f4", "g4", "h4", "a5", "b5", "c5", "d5", "e5", "f5", "g5", "h5", "a6", "b6", "c6", "d6", "e6", "f6", "g6", "h6", "a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7", "a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8" ] }, { "NAME": "uPromotion", "TYPE": "long", "LABEL": "Promote to", "DEFAULT": 5, "VALUES": [ 5, 4, 3, 2 ], "LABELS": [ "Queen", "Rook", "Bishop", "Knight" ] }, { "NAME": "uMove", "TYPE": "event", "LABEL": "Move" }, { "NAME": "uNewGame", "TYPE": "event", "LABEL": "New game" }, { "NAME": "uFlip", "TYPE": "bool", "LABEL": "Flip board", "DEFAULT": false }, { "NAME": "uClaim", "TYPE": "event", "LABEL": "Claim draw / arm next move" }, { "NAME": "uResign", "TYPE": "event", "LABEL": "Resign" }, { "NAME": "uDrawAgreement", "TYPE": "event", "LABEL": "Both players agree to draw" } ], "PASSES": [ { "TARGET": "moveState", "FLOAT": true, "FILTER": "NEAREST" }, { "TARGET": "legalMoves", "PERSISTENT": true, "FLOAT": true, "FILTER": "NEAREST" }, { "TARGET": "facts", "PERSISTENT": true, "FLOAT": true, "FILTER": "NEAREST" }, { "TARGET": "gameState", "PERSISTENT": true, "FLOAT": true, "FILTER": "NEAREST" }, {} ] }*/ // CHESS — a complete local game in one glslop ISF fragment shader. // No custom JavaScript, HTML, vertex shader, images, or external textures. // Use glslop's From/To dropdowns, then press Move. Promotion is a preset. // Requires glslop FLOAT targets (RGBA16F) and a render size of at least 64x64. // All state values are small exact integers; the ply counter is split base1024. #ifdef GL_ES precision highp float; precision highp int; #endif // Data occupies a 64-column table in each full-resolution target's corner. // 0..15: packed pieces;16:side,rights,ep,halfmove;17:selected,pendingF,pendingT,result; // 18:lastF,lastT,plyLow,check;19:historyCursor,historyCount,repeats,claimFlags; // 20:event,mover,captured,promoted;21:claimArmed,claimAfterMove,0,0; // 22:positionChanged,0,plyHigh,123;23/24:previous event inputs (edge detection). // 64+17*i: 16 exact board texels plus (side,rights,legalEp,0), i=0..150. vec2 dataPixel(int index) { float n = float(index); return vec2(mod(n, 64.0), floor(n / 64.0)) + 0.5; } vec4 gameData(int index) { return IMG_PIXEL(gameState, dataPixel(index)); } vec4 moveData(int index) { return IMG_PIXEL(moveState, dataPixel(index)); } vec4 factData(int index) { return IMG_PIXEL(facts, dataPixel(index)); } int packedPiece(vec4 sampleValue, int component) { if (component == 0) return int(sampleValue.r); if (component == 1) return int(sampleValue.g); if (component == 2) return int(sampleValue.b); return int(sampleValue.a); } int rulesBasePiece(int square) { if (square < 0 || square >= 64) return 0; int column = square / 4; vec4 sampleValue = PASSINDEX == 0 ? gameData(column) : moveData(column); return packedPiece(sampleValue, square - column * 4); } float storedLegal(int from, int to) { if (from < 0 || from >= 64 || to < 0 || to >= 64) return 0.0; return IMG_PIXEL(legalMoves, vec2(float(to), float(from)) + 0.5).r; } // Rendering adapters. The final pass reads the completed gameState. int uiPiece(int square) { if (square < 0 || square >= 64) return 0; int column = square / 4; return packedPiece(gameData(column), square - column * 4); } int uiData(int key) { ivec4 meta = ivec4(gameData(17)); ivec4 last = ivec4(gameData(18)); int side = int(gameData(16).x); if (key == 0) return side; if (key == 1) return meta.w == 0 && uiPiece(uFrom) * side > 0 ? uFrom : -1; if (key == 2 || key == 3) return -1; if (key == 4) return meta.w; if (key == 5) return last.x; if (key == 6) return last.y; if (key == 7) return last.w; if (key == 8) return int(gameData(19).w); if (key == 9) return uFlip ? 1 : 0; if (key == 10) return uTo; if (key == 11) return last.z + 1024 * int(gameData(22).z); if (key == 12) return uPromotion; if (key == 13) return gameData(22).w > 122.0 ? 1 : 0; if (key == 14) return int(gameData(20).x); if (key == 15) return int(gameData(21).x); return 0; } float uiLegal(int from, int to) { return storedLegal(from, to); } // Chess rules for GLSL ES 1.00 / WebGL 1. // No arrays, integer textures, bitwise operators, or dynamic array indices. // Piece values: pawn 1, knight 2, bishop 3, rook 4, queen 5, king 6. // White is positive, Black is negative; a1 = 0 and h8 = 63. // The containing shader supplies this float-texture decoder: int rulesBasePiece(int square); int ruleAbs(int value) { return value < 0 ? -value : value; } int ruleSide(int value) { return value > 0 ? 1 : (value < 0 ? -1 : 0); } int ruleFile(int square) { return square - (square / 8) * 8; } int ruleRank(int square) { return square / 8; } bool ruleInside(int file, int rank) { return file >= 0 && file < 8 && rank >= 0 && rank < 8; } bool ruleHasRight(int rights, int bitValue) { return mod(floor(float(rights) / float(bitValue)), 2.0) > 0.5; } int ruleRemoveRight(int rights, int bitValue) { return ruleHasRight(rights, bitValue) ? rights - bitValue : rights; } // An overlay changes at most four squares without copying the board. // Two layers allow a committed move plus a hypothetical reply, including // the legal en-passant test needed for exact repetition identity. struct RuleMove { int fromSquare; int toSquare; int movingPiece; int epVictim; int rookFrom; int rookTo; int rookPiece; }; struct RulePosition { RuleMove first; RuleMove second; }; RuleMove ruleNoMove() { return RuleMove(-1, -1, 0, -1, -1, -1, 0); } RulePosition ruleCurrent() { return RulePosition(ruleNoMove(), ruleNoMove()); } int rulePiece(RulePosition position, int square) { if (square < 0 || square >= 64) return 0; if (square == position.second.toSquare) return position.second.movingPiece; if (square == position.second.rookTo) return position.second.rookPiece; if (square == position.second.fromSquare || square == position.second.epVictim || square == position.second.rookFrom) return 0; if (square == position.first.toSquare) return position.first.movingPiece; if (square == position.first.rookTo) return position.first.rookPiece; if (square == position.first.fromSquare || square == position.first.epVictim || square == position.first.rookFrom) return 0; return rulesBasePiece(square); } // Apply a legal move to a position with zero or one existing overlay. // promotion is an absolute piece type 2..5; all other values mean queen. // The caller owns turn, clocks, castling rights, and en-passant metadata. RulePosition ruleAfterMove(RulePosition position, int from, int to, int promotion, int ep) { int moving = rulePiece(position, from); int side = ruleSide(moving); int kind = ruleAbs(moving); RuleMove move = RuleMove(from, to, moving, -1, -1, -1, 0); if (kind == 1 && to == ep && rulePiece(position, to) == 0 && ruleAbs(ruleFile(to) - ruleFile(from)) == 1 && ruleRank(to) - ruleRank(from) == side) { move.epVictim = to - side * 8; } if (kind == 6 && ruleAbs(to - from) == 2) { move.rookFrom = to > from ? from + 3 : from - 4; move.rookTo = to > from ? from + 1 : from - 1; move.rookPiece = rulePiece(position, move.rookFrom); } if (kind == 1 && (ruleRank(to) == 0 || ruleRank(to) == 7)) { int promotedKind = promotion >= 2 && promotion <= 5 ? promotion : 5; move.movingPiece = side * promotedKind; } if (position.first.fromSquare < 0) position.first = move; else position.second = move; return position; } ivec2 ruleKnightStep(int direction) { if (direction == 0) return ivec2(1, 2); if (direction == 1) return ivec2(2, 1); if (direction == 2) return ivec2(2, -1); if (direction == 3) return ivec2(1, -2); if (direction == 4) return ivec2(-1, -2); if (direction == 5) return ivec2(-2, -1); if (direction == 6) return ivec2(-2, 1); return ivec2(-1, 2); } ivec2 ruleRayStep(int direction) { if (direction == 0) return ivec2(1, 0); if (direction == 1) return ivec2(-1, 0); if (direction == 2) return ivec2(0, 1); if (direction == 3) return ivec2(0, -1); if (direction == 4) return ivec2(1, 1); if (direction == 5) return ivec2(1, -1); if (direction == 6) return ivec2(-1, 1); return ivec2(-1, -1); } // Geometric attacks include those of pinned pieces, as required for // king movement and castling. This function does not call legalMove. bool attacked(RulePosition position, int square, int bySide) { if (square < 0 || square >= 64) return false; int file = ruleFile(square); int rank = ruleRank(square); int pawnRank = rank - bySide; if (ruleInside(file - 1, pawnRank) && rulePiece(position, pawnRank * 8 + file - 1) == bySide) return true; if (ruleInside(file + 1, pawnRank) && rulePiece(position, pawnRank * 8 + file + 1) == bySide) return true; for (int direction = 0; direction < 8; ++direction) { ivec2 p = ivec2(file, rank) + ruleKnightStep(direction); if (ruleInside(p.x, p.y) && rulePiece(position, p.y * 8 + p.x) == bySide * 2) return true; } for (int direction = 0; direction < 8; ++direction) { ivec2 step = ruleRayStep(direction); for (int distance = 1; distance <= 7; ++distance) { ivec2 p = ivec2(file, rank) + step * distance; if (!ruleInside(p.x, p.y)) break; int piece = rulePiece(position, p.y * 8 + p.x); if (piece == 0) continue; if (ruleSide(piece) == bySide) { int kind = ruleAbs(piece); if (distance == 1 && kind == 6) return true; if (kind == 5) return true; if (direction < 4 && kind == 4) return true; if (direction >= 4 && kind == 3) return true; } break; } } return false; } int kingSquare(RulePosition position, int side) { for (int square = 0; square < 64; ++square) { if (rulePiece(position, square) == side * 6) return square; } return -1; } bool inCheck(RulePosition position, int side) { int king = kingSquare(position, side); return king < 0 || attacked(position, king, -side); } // Call only after validating rook, bishop, or queen movement geometry. bool rulePathClear(RulePosition position, int from, int to) { int df = ruleFile(to) - ruleFile(from); int dr = ruleRank(to) - ruleRank(from); int step = ruleSide(df) + 8 * ruleSide(dr); for (int distance = 1; distance <= 7; ++distance) { int square = from + step * distance; if (square == to) return true; if (square < 0 || square >= 64 || rulePiece(position, square) != 0) return false; } return false; } bool legalMove(RulePosition position, int side, int rights, int ep, int from, int to) { if (from < 0 || from >= 64 || to < 0 || to >= 64 || from == to) return false; if (side != 1 && side != -1) return false; int moving = rulePiece(position, from); int target = rulePiece(position, to); if (ruleSide(moving) != side || ruleSide(target) == side || ruleAbs(target) == 6) return false; int kind = ruleAbs(moving); int ff = ruleFile(from); int fr = ruleRank(from); int tf = ruleFile(to); int tr = ruleRank(to); int df = tf - ff; int dr = tr - fr; bool shape = false; bool castle = false; if (kind == 1) { int homeRank = side == 1 ? 1 : 6; if (df == 0 && target == 0) { shape = dr == side || (fr == homeRank && dr == 2 * side && rulePiece(position, from + side * 8) == 0); } else if (ruleAbs(df) == 1 && dr == side) { bool ordinaryCapture = ruleSide(target) == -side; bool enPassant = to == ep && target == 0 && tr == (side == 1 ? 5 : 2) && rulePiece(position, to - side * 8) == -side; shape = ordinaryCapture || enPassant; } } else if (kind == 2) { shape = (ruleAbs(df) == 1 && ruleAbs(dr) == 2) || (ruleAbs(df) == 2 && ruleAbs(dr) == 1); } else if (kind == 3) { shape = ruleAbs(df) == ruleAbs(dr) && rulePathClear(position, from, to); } else if (kind == 4) { shape = (df == 0 || dr == 0) && rulePathClear(position, from, to); } else if (kind == 5) { shape = (df == 0 || dr == 0 || ruleAbs(df) == ruleAbs(dr)) && rulePathClear(position, from, to); } else if (kind == 6) { shape = ruleAbs(df) <= 1 && ruleAbs(dr) <= 1; int base = side == 1 ? 0 : 56; if (from == base + 4 && tr == fr && ruleAbs(df) == 2) { bool kingside = df > 0; int neededRight = side == 1 ? (kingside ? 1 : 2) : (kingside ? 4 : 8); int rookSquare = base + (kingside ? 7 : 0); bool empty = kingside ? (rulePiece(position, base + 5) == 0 && rulePiece(position, base + 6) == 0) : (rulePiece(position, base + 1) == 0 && rulePiece(position, base + 2) == 0 && rulePiece(position, base + 3) == 0); castle = ruleHasRight(rights, neededRight) && empty && rulePiece(position, rookSquare) == side * 4; shape = castle; } } if (!shape) return false; if (castle) { if (attacked(position, from, -side)) return false; int transit = from + (to > from ? 1 : -1); RulePosition crossing = ruleAfterMove(position, from, transit, 5, ep); if (attacked(crossing, transit, -side)) return false; } // Underpromotions have the same occupancy as a queen, hence the same // answer for the moving side's king safety. Choice happens on commit. RulePosition trial = ruleAfterMove(position, from, to, 5, ep); return !inCheck(trial, side); } // Supply the PREMOVE position. A removed castling right never returns. int nextCastlingRights(RulePosition position, int rights, int from, int to) { int moving = rulePiece(position, from); int captured = rulePiece(position, to); if (moving == 6) { rights = ruleRemoveRight(rights, 1); rights = ruleRemoveRight(rights, 2); } if (moving == -6) { rights = ruleRemoveRight(rights, 4); rights = ruleRemoveRight(rights, 8); } if ((moving == 4 && from == 7) || (captured == 4 && to == 7)) rights = ruleRemoveRight(rights, 1); if ((moving == 4 && from == 0) || (captured == 4 && to == 0)) rights = ruleRemoveRight(rights, 2); if ((moving == -4 && from == 63) || (captured == -4 && to == 63)) rights = ruleRemoveRight(rights, 4); if ((moving == -4 && from == 56) || (captured == -4 && to == 56)) rights = ruleRemoveRight(rights, 8); return rights; } // Only an actually legal EP capture changes the repetition identity. // In particular, a pinned pawn next to the double-pushed pawn is not enough. int effectiveEp(RulePosition position, int side, int rights, int ep) { if (ep < 0 || ep >= 64 || (side != 1 && side != -1)) return -1; if (ruleRank(ep) != (side == 1 ? 5 : 2) || rulePiece(position, ep) != 0 || rulePiece(position, ep - side * 8) != -side) return -1; int epFile = ruleFile(ep); for (int direction = -1; direction <= 1; direction += 2) { int file = epFile + direction; if (file < 0 || file >= 8) continue; int from = (ruleRank(ep) - side) * 8 + file; if (rulePiece(position, from) == side && legalMove(position, side, rights, ep, from, ep)) return ep; } return -1; } // Provably dead material classes only: K/K, K+B/K, K+N/K, or bishop-only // positions in which every bishop occupies the same square color. // K+NN/K and K+N/K+N are not automatic draws: mate remains possible. bool insufficientMaterial(RulePosition position) { int whiteKings = 0; int blackKings = 0; int minors = 0; int knights = 0; int bishopColor = -1; bool bishopsSameColor = true; for (int square = 0; square < 64; ++square) { int piece = rulePiece(position, square); int kind = ruleAbs(piece); if (kind == 0) continue; if (kind == 6) { if (piece > 0) ++whiteKings; else ++blackKings; } else if (kind == 2) { ++minors; ++knights; } else if (kind == 3) { ++minors; int sum = ruleFile(square) + ruleRank(square); int color = sum - 2 * (sum / 2); if (bishopColor < 0) bishopColor = color; else if (bishopColor != color) bishopsSameColor = false; } else { return false; } } if (whiteKings != 1 || blackKings != 1) return false; return minors <= 1 || (knights == 0 && bishopsSameColor); } int initialPiece(int square) { int rank = ruleRank(square), file = ruleFile(square); if (rank == 1) return 1; if (rank == 6) return -1; if (rank != 0 && rank != 7) return 0; int p = 6; if (file == 0 || file == 7) p = 4; if (file == 1 || file == 6) p = 2; if (file == 2 || file == 5) p = 3; if (file == 3) p = 5; return rank == 0 ? p : -p; } vec4 initialPacked(int column) { return vec4(float(initialPiece(column * 4)), float(initialPiece(column * 4 + 1)), float(initialPiece(column * 4 + 2)), float(initialPiece(column * 4 + 3))); } vec4 positionPacked(RulePosition position, int column) { return vec4(float(rulePiece(position, column * 4)), float(rulePiece(position, column * 4 + 1)), float(rulePiece(position, column * 4 + 2)), float(rulePiece(position, column * 4 + 3))); } vec4 currentEventInputs() { return vec4(uMove ? 1.0 : 0.0, uNewGame ? 1.0 : 0.0, uClaim ? 1.0 : 0.0, uResign ? 1.0 : 0.0); } // PASS 0: process only rising edges of the host's ISF event uniforms. // The old completed position and old legal-move table are from last frame. vec4 advanceGame(int index) { if (index >= 2631) return vec4(0.0); vec4 oldValue = gameData(index); ivec4 system = ivec4(gameData(22)); vec4 previousEvents = gameData(23); bool resetNow = system.w != 123 || (uNewGame && previousEvents.y < 0.5); if (resetNow) { if (index < 16) return initialPacked(index); if (index == 16) return vec4(1.0, 15.0, -1.0, 0.0); if (index == 17) return vec4(-1.0, -1.0, -1.0, 0.0); if (index == 18) return vec4(-1.0, -1.0, 0.0, 0.0); if (index == 19) return vec4(0.0, 1.0, 1.0, 0.0); if (index == 22) return vec4(1.0, 0.0, 0.0, 123.0); if (index == 23) return currentEventInputs(); if (index == 24) return vec4(uDrawAgreement ? 1.0 : 0.0, 0.0, 0.0, 0.0); return vec4(0.0); } // History is updated in pass 3; no rule work for historical cells here. if (index >= 25) return oldValue; if (index == 23) return currentEventInputs(); if (index == 24) return vec4(uDrawAgreement ? 1.0 : 0.0, 0.0, 0.0, 0.0); ivec4 rules = ivec4(gameData(16)); ivec4 ui = ivec4(gameData(17)); ivec4 last = ivec4(gameData(18)); ivec4 hist = ivec4(gameData(19)); ivec4 event = ivec4(gameData(20)); ivec4 claim = ivec4(gameData(21)); system.x = 0; bool moveNow = uMove && previousEvents.x < 0.5; bool claimNow = uClaim && previousEvents.z < 0.5; bool resignNow = uResign && previousEvents.w < 0.5; bool agreeNow = uDrawAgreement && gameData(24).x < 0.5; bool committed = false; RulePosition position = ruleCurrent(); if (ui.w == 0) { if (claimNow) { if (hist.w > 0) ui.w = ruleHasRight(hist.w, 1) ? 7 : 8; else { claim.x = 1 - claim.x; event.x = claim.x == 1 ? 5 : 0; } } else if (resignNow) { bool opponentHasMaterial = false; for (int square = 0; square < 64; ++square) { int p = rulePiece(position, square); if (p * rules.x < 0 && ruleAbs(p) != 6) opponentHasMaterial = true; } ui.w = opponentHasMaterial ? (rules.x == 1 ? 10 : 9) : 4; } else if (agreeNow) ui.w = 11; else if (moveNow) { if (storedLegal(uFrom, uTo) < 0.5) event.x = 2; else { int moving = rulePiece(position, uFrom); int captured = rulePiece(position, uTo); if (ruleAbs(moving) == 1 && uTo == rules.z && captured == 0 && ruleFile(uFrom) != ruleFile(uTo)) captured = -rules.x; bool irreversible = ruleAbs(moving) == 1 || captured != 0; int rights = nextCastlingRights(position, rules.y, uFrom, uTo); position = ruleAfterMove(position, uFrom, uTo, uPromotion, rules.z); int ep = ruleAbs(moving) == 1 && ruleAbs(uTo - uFrom) == 16 ? (uTo + uFrom) / 2 : -1; rules = ivec4(-rules.x, rights, ep, irreversible ? 0 : rules.w + 1); int low = last.z + 1; if (low >= 1024) { low = 0; system.z += 1; } last = ivec4(uFrom, uTo, low, 0); int cursor = irreversible ? 0 : hist.x + 1; if (cursor >= 151) cursor = 0; int count = irreversible ? 1 : hist.y + 1; if (count > 151) count = 151; hist = ivec4(cursor, count, 0, 0); event = ivec4(3, moving, captured, ruleAbs(moving) == 1 && (ruleRank(uTo) == 0 || ruleRank(uTo) == 7) ? uPromotion : 0); claim = ivec4(0, claim.x, 0, 0); system.x = 1; committed = true; } } } if (ui.w != 0) { claim = ivec4(0); hist.w = 0; } if (index < 16) return committed ? positionPacked(position, index) : oldValue; if (index == 16) return vec4(rules); if (index == 17) return vec4(ui); if (index == 18) return vec4(last); if (index == 19) return vec4(hist); if (index == 20) return vec4(event); if (index == 21) return vec4(claim); if (index == 22) return vec4(system); return oldValue; } // PASS 1: one fragment per ordered (from,to) pair, 64x64 in the corner. // Cache it across idle frames; only a changed board requires rule evaluation. vec4 calculateLegal(ivec2 pixel) { if (pixel.x >= 64 || pixel.y >= 64) return vec4(0.0); if (moveData(22).x < 0.5) return IMG_THIS_PIXEL(legalMoves); int from = pixel.y, to = pixel.x; ivec4 rules = ivec4(moveData(16)); RulePosition position = ruleCurrent(); if (!legalMove(position, rules.x, rules.y, rules.z, from, to)) return vec4(0.0); bool promotion = ruleAbs(rulePiece(position, from)) == 1 && (ruleRank(to) == 0 || ruleRank(to) == 7); return vec4(1.0, promotion ? 1.0 : 0.0, 0.0, 0.0); } // PASS 2: parallel reductions keep large nested loops out of adjudication. // row 0: legal-move counts by source square. Rows1..3: historical matches. // row4/column0: check and provably insufficient material. vec4 calculateFacts(int index) { if (index > 256) return vec4(0.0); if (moveData(22).x < 0.5) return IMG_THIS_PIXEL(facts); if (index < 64) { float count = 0.0; for (int to = 0; to < 64; ++to) count += storedLegal(index, to); return vec4(count, 0.0, 0.0, 0.0); } if (index < 215) { int slot = index - 64; ivec4 hist = ivec4(moveData(19)); if (slot >= hist.y || slot == hist.x) return vec4(0.0); ivec4 rules = ivec4(moveData(16)); // A newly available EP target always follows a pawn move, which resets // history to one position. Other history comparisons have ep == -1. if (rules.z >= 0) return vec4(0.0); vec4 key = vec4(float(rules.x), float(rules.y), -1.0, 0.0); if (any(notEqual(moveData(64 + slot * 17 + 16), key))) return vec4(0.0); bool same = true; for (int column = 0; column < 16; ++column) { if (any(notEqual(moveData(column), moveData(64 + slot * 17 + column)))) { same = false; break; } } return vec4(same ? 1.0 : 0.0, 0.0, 0.0, 0.0); } if (index == 256) { RulePosition position = ruleCurrent(); return vec4(inCheck(position, int(moveData(16).x)) ? 1.0 : 0.0, insufficientMaterial(position) ? 1.0 : 0.0, 0.0, 0.0); } return vec4(0.0); } // PASS 3: complete the new state and write one exact historical position. // A game-ending result locks all subsequent move events until New Game. vec4 finishGame(int index) { if (index >= 2631) return vec4(0.0); vec4 value = moveData(index); if (moveData(22).x < 0.5) return value; ivec4 hist = ivec4(moveData(19)); int historyStart = 64 + hist.x * 17; if (index >= historyStart && index < historyStart + 17) { int column = index - historyStart; if (column < 16) return moveData(column); ivec4 rules = ivec4(moveData(16)); int ep = effectiveEp(ruleCurrent(), rules.x, rules.y, rules.z); return vec4(float(rules.x), float(rules.y), float(ep), 0.0); } if (index < 17 || index > 21) return value; ivec4 ui = ivec4(moveData(17)); ivec4 last = ivec4(moveData(18)); ivec4 event = ivec4(moveData(20)); ivec4 claim = ivec4(moveData(21)); ivec4 rules = ivec4(moveData(16)); float available = 0.0; for (int from = 0; from < 64; ++from) available += factData(from).r; int repeats = 1; for (int slot = 0; slot < 151; ++slot) repeats += int(factData(64 + slot).r); hist.z = repeats; hist.w = (repeats >= 3 ? 1 : 0) + (rules.w >= 100 ? 2 : 0); vec4 factsNow = factData(256); last.w = factsNow.r > 0.5 ? 1 : 0; if (ui.w == 0) { if (claim.y != 0 && hist.w > 0) ui.w = ruleHasRight(hist.w, 1) ? 7 : 8; else if (available < 0.5) ui.w = last.w != 0 ? (rules.x == 1 ? 2 : 1) : 3; else if (factsNow.g > 0.5) ui.w = 4; else if (repeats >= 5) ui.w = 5; else if (rules.w >= 150) ui.w = 6; if (claim.y != 0 && ui.w == 0) event.x = 6; } claim.y = 0; if (ui.w != 0) { claim = ivec4(0); hist.w = 0; } if (index == 17) return vec4(ui); if (index == 18) return vec4(last); if (index == 19) return vec4(hist); if (index == 20) return vec4(event); if (index == 21) return vec4(claim); return value; } // GLSLOP / ISF compatible rendering: GLSL ES 1.00, no derivative extension, // integer textures, dynamically indexed arrays, or external image/font assets. // RENDERSIZE, TIME, FRAMEINDEX and PASSINDEX are supplied by ISF. const float INSET = 0.055; const float BOARD_SIZE = 0.89; float chessSize() { return min(RENDERSIZE.x * 0.94, RENDERSIZE.y * 0.79); } vec2 chessOrigin() { float size = chessSize(); return vec2((RENDERSIZE.x - size) * 0.5, (RENDERSIZE.y - size) * 0.5 + 0.015 * size); } vec2 chessPoint(vec2 pixel) { return (pixel - chessOrigin()) / chessSize(); } float sdBox(vec2 p, vec2 b) { vec2 q = abs(p) - b; return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0); } float sdRoundBox(vec2 p, vec2 b, float r) { return sdBox(p, b - r) - r; } float sdCircle(vec2 p, float r) { return length(p) - r; } float sdCapsule(vec2 p, vec2 a, vec2 b, float r) { vec2 pa = p - a, ba = b - a; return length(pa - ba * clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0)) - r; } // Bottom half-width, top half-width, half-height. float sdTrapezoid(vec2 p, float bottom, float top, float height) { vec2 k1 = vec2(top, height); vec2 k2 = vec2(top - bottom, 2.0 * height); p.x = abs(p.x); vec2 ca = vec2(max(0.0, p.x - ((p.y < 0.0) ? bottom : top)), abs(p.y) - height); vec2 cb = p - k1 + k2 * clamp(dot(k1 - p, k2) / dot(k2, k2), 0.0, 1.0); float signValue = (cb.x < 0.0 && ca.y < 0.0) ? -1.0 : 1.0; return signValue * sqrt(min(dot(ca, ca), dot(cb, cb))); } float sdEllipse(vec2 p, vec2 radii) { // A distance approximation is sufficient for these small, softly beveled // silhouettes and avoids the cost of an exact ellipse solver per pixel. float k0 = length(p / radii); float k1 = length(p / (radii * radii)); if (k0 < 0.00001) return -min(radii.x, radii.y); return k0 * (k0 - 1.0) / max(k1, 0.00001); } void polygonEdge(vec2 p, vec2 a, vec2 b, inout float d, inout float side) { vec2 e = b - a; vec2 w = p - a; vec2 q = w - e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0); d = min(d, dot(q, q)); bool up = p.y >= a.y; bool down = p.y < b.y; bool crossPositive = e.x * w.y > e.y * w.x; if ((up && down && crossPositive) || (!up && !down && !crossPositive)) side = -side; } float knightOutline(vec2 p) { float d = 1.0e5; float side = 1.0; polygonEdge(p, vec2(-0.174, -0.218), vec2(0.184, -0.218), d, side); polygonEdge(p, vec2(-0.135, -0.106), vec2(-0.174, -0.218), d, side); polygonEdge(p, vec2(-0.101, -0.021), vec2(-0.135, -0.106), d, side); polygonEdge(p, vec2(-0.156, 0.049), vec2(-0.101, -0.021), d, side); polygonEdge(p, vec2(-0.218, 0.035), vec2(-0.156, 0.049), d, side); polygonEdge(p, vec2(-0.277, 0.082), vec2(-0.218, 0.035), d, side); polygonEdge(p, vec2(-0.253, 0.146), vec2(-0.277, 0.082), d, side); polygonEdge(p, vec2(-0.158, 0.221), vec2(-0.253, 0.146), d, side); polygonEdge(p, vec2(-0.097, 0.255), vec2(-0.158, 0.221), d, side); polygonEdge(p, vec2(-0.078, 0.347), vec2(-0.097, 0.255), d, side); polygonEdge(p, vec2(-0.017, 0.292), vec2(-0.078, 0.347), d, side); polygonEdge(p, vec2(0.059, 0.299), vec2(-0.017, 0.292), d, side); polygonEdge(p, vec2(0.148, 0.232), vec2(0.059, 0.299), d, side); polygonEdge(p, vec2(0.206, 0.124), vec2(0.148, 0.232), d, side); polygonEdge(p, vec2(0.217, -0.036), vec2(0.206, 0.124), d, side); polygonEdge(p, vec2(0.146, -0.189), vec2(0.217, -0.036), d, side); polygonEdge(p, vec2(0.184, -0.218), vec2(0.146, -0.189), d, side); return side * sqrt(d) - 0.006; } float queenCrown(vec2 p) { float d = 1.0e5; float side = 1.0; polygonEdge(p, vec2(-0.147, 0.082), vec2(0.147, 0.082), d, side); polygonEdge(p, vec2(-0.217, 0.273), vec2(-0.147, 0.082), d, side); polygonEdge(p, vec2(-0.163, 0.218), vec2(-0.217, 0.273), d, side); polygonEdge(p, vec2(-0.118, 0.331), vec2(-0.163, 0.218), d, side); polygonEdge(p, vec2(-0.060, 0.230), vec2(-0.118, 0.331), d, side); polygonEdge(p, vec2(0.000, 0.305), vec2(-0.060, 0.230), d, side); polygonEdge(p, vec2(0.060, 0.230), vec2(0.000, 0.305), d, side); polygonEdge(p, vec2(0.118, 0.331), vec2(0.060, 0.230), d, side); polygonEdge(p, vec2(0.163, 0.218), vec2(0.118, 0.331), d, side); polygonEdge(p, vec2(0.217, 0.273), vec2(0.163, 0.218), d, side); polygonEdge(p, vec2(0.147, 0.082), vec2(0.217, 0.273), d, side); d = side * sqrt(d); d = min(d, sdCircle(p - vec2(-0.217, 0.273), 0.028)); d = min(d, sdCircle(p - vec2(-0.118, 0.331), 0.028)); d = min(d, sdCircle(p - vec2( 0.000, 0.305), 0.028)); d = min(d, sdCircle(p - vec2( 0.118, 0.331), 0.028)); d = min(d, sdCircle(p - vec2( 0.217, 0.273), 0.028)); return d; } float pieceDistance(vec2 p, int type) { // Shared turned pedestal; all pieces keep the same optical baseline. float d = sdRoundBox(p - vec2(0.0, -0.313), vec2(0.245, 0.041), 0.021); d = min(d, sdRoundBox(p - vec2(0.0, -0.250), vec2(0.205, 0.028), 0.015)); d = min(d, sdTrapezoid(p - vec2(0.0, -0.213), 0.181, 0.155, 0.022)); if (type == 1) { d = min(d, sdTrapezoid(p - vec2(0.0, -0.094), 0.158, 0.065, 0.111)); d = min(d, sdRoundBox(p - vec2(0.0, 0.033), vec2(0.124, 0.027), 0.012)); d = min(d, sdCircle(p - vec2(0.0, 0.166), 0.110)); } else if (type == 2) { d = min(d, knightOutline(p)); } else if (type == 3) { d = min(d, sdTrapezoid(p - vec2(0.0, -0.071), 0.158, 0.062, 0.137)); d = min(d, sdRoundBox(p - vec2(0.0, 0.061), vec2(0.133, 0.025), 0.012)); float mitre = sdEllipse(p - vec2(0.0, 0.197), vec2(0.108, 0.143)); mitre = min(mitre, sdCircle(p - vec2(0.0, 0.333), 0.030)); mitre = max(mitre, -sdCapsule(p, vec2(-0.027, 0.176), vec2(0.106, 0.300), 0.017)); d = min(d, mitre); } else if (type == 4) { d = min(d, sdTrapezoid(p - vec2(0.0, -0.080), 0.157, 0.131, 0.132)); d = min(d, sdRoundBox(p - vec2(0.0, 0.046), vec2(0.163, 0.023), 0.008)); float tower = sdTrapezoid(p - vec2(0.0, 0.135), 0.140, 0.182, 0.087); tower = min(tower, sdRoundBox(p - vec2(0.0, 0.211), vec2(0.205, 0.081), 0.006)); tower = max(tower, -sdBox(p - vec2(-0.071, 0.285), vec2(0.033, 0.065))); tower = max(tower, -sdBox(p - vec2( 0.071, 0.285), vec2(0.033, 0.065))); d = min(d, tower); } else if (type == 5) { d = min(d, sdTrapezoid(p - vec2(0.0, -0.072), 0.170, 0.101, 0.140)); d = min(d, sdRoundBox(p - vec2(0.0, 0.073), vec2(0.155, 0.024), 0.010)); d = min(d, queenCrown(p)); } else if (type == 6) { d = min(d, sdTrapezoid(p - vec2(0.0, -0.077), 0.169, 0.091, 0.136)); d = min(d, sdRoundBox(p - vec2(0.0, 0.061), vec2(0.145, 0.024), 0.010)); d = min(d, sdEllipse(p - vec2(0.0, 0.135), vec2(0.133, 0.088))); d = min(d, sdRoundBox(p - vec2(0.0, 0.217), vec2(0.069, 0.020), 0.009)); d = min(d, sdRoundBox(p - vec2(0.0, 0.294), vec2(0.027, 0.085), 0.004)); d = min(d, sdRoundBox(p - vec2(0.0, 0.322), vec2(0.082, 0.024), 0.004)); } return d; } float pieceDetail(vec2 p, int type) { float d = sdCapsule(p, vec2(-0.200, -0.286), vec2(0.200, -0.286), 0.0050); d = min(d, sdCapsule(p, vec2(-0.160, -0.228), vec2(0.160, -0.228), 0.0045)); if (type == 1) { d = min(d, sdCapsule(p, vec2(-0.097, 0.026), vec2(0.097, 0.026), 0.004)); } else if (type == 2) { d = min(d, sdCircle(p - vec2(-0.105, 0.182), 0.016)); d = min(d, sdCapsule(p, vec2(-0.238, 0.100), vec2(-0.191, 0.112), 0.006)); d = min(d, sdCapsule(p, vec2(0.055, 0.247), vec2(0.120, 0.171), 0.005)); d = min(d, sdCapsule(p, vec2(0.120, 0.171), vec2(0.151, 0.065), 0.005)); d = min(d, sdCapsule(p, vec2(0.151, 0.065), vec2(0.139, -0.050), 0.005)); } else if (type == 3) { d = min(d, sdCapsule(p, vec2(-0.102, 0.054), vec2(0.102, 0.054), 0.004)); } else if (type == 4) { d = min(d, sdCapsule(p, vec2(-0.135, 0.058), vec2(0.135, 0.058), 0.0045)); d = min(d, sdCapsule(p, vec2(-0.157, 0.153), vec2(0.157, 0.153), 0.004)); } else if (type == 5) { d = min(d, sdCapsule(p, vec2(-0.122, 0.087), vec2(0.122, 0.087), 0.005)); } else if (type == 6) { d = min(d, sdCapsule(p, vec2(-0.115, 0.057), vec2(0.115, 0.057), 0.0045)); } return d; } float coverage(float d, float aa) { return 1.0 - smoothstep(-aa, aa, d); } vec3 drawPiece(vec3 under, vec2 p, int piece, float aa) { int type = piece < 0 ? -piece : piece; float shadow = pieceDistance(p - vec2(0.022, -0.034), type); under *= 1.0 - 0.27 * (1.0 - smoothstep(-0.015, 0.052, shadow)); float d = pieceDistance(p, type); float white = piece > 0 ? 1.0 : 0.0; vec3 outline = mix(vec3(0.59, 0.65, 0.56), vec3(0.19, 0.23, 0.19), white); under = mix(under, outline, coverage(d - 0.009, aa)); vec3 material = mix(vec3(0.145, 0.195, 0.172), vec3(0.929, 0.905, 0.821), white); material += (p.y + 0.15) * mix(0.09, 0.055, white); material -= p.x * 0.055; material += 0.027 * exp(-90.0 * pow(p.x + 0.065 + 0.11 * p.y, 2.0)); float bevel = exp(-max(-d, 0.0) * 80.0); material += bevel * (0.023 + 0.065 * p.y - 0.068 * p.x); float detail = coverage(pieceDetail(p, type), aa * 0.8); vec3 engraving = mix(vec3(0.48, 0.56, 0.47), vec3(0.43, 0.46, 0.36), white); material = mix(material, engraving, detail * mix(0.55, 0.45, white)); return mix(under, material, coverage(d, aa)); } // A 5 x 7 font: every glyphData integer is exactly representable by highp float. vec2 fontBits(int glyph) { if (glyph == 1) return vec2(1033777.0, 14897.0); // A if (glyph == 2) return vec2(1001022.0, 31281.0); // B if (glyph == 3) return vec2(541199.0, 15888.0); // C if (glyph == 4) return vec2(575038.0, 31281.0); // D if (glyph == 5) return vec2(999967.0, 32272.0); // E if (glyph == 6) return vec2(999952.0, 32272.0); // F if (glyph == 7) return vec2(771631.0, 15888.0); // G if (glyph == 8) return vec2(1033777.0, 17969.0); // H if (glyph == 9) return vec2(135327.0, 31876.0); // I if (glyph == 10) return vec2(84556.0, 7234.0); // J if (glyph == 11) return vec2(807505.0, 18004.0); // K if (glyph == 12) return vec2(541215.0, 16912.0); // L if (glyph == 13) return vec2(706097.0, 18293.0); // M if (glyph == 14) return vec2(708209.0, 18233.0); // N if (glyph == 15) return vec2(575022.0, 14897.0); // O if (glyph == 16) return vec2(999952.0, 31281.0); // P if (glyph == 17) return vec2(579149.0, 14897.0); // Q if (glyph == 18) return vec2(1004113.0, 31281.0); // R if (glyph == 19) return vec2(459838.0, 15888.0); // S if (glyph == 20) return vec2(135300.0, 31876.0); // T if (glyph == 21) return vec2(575022.0, 17969.0); // U if (glyph == 22) return vec2(574788.0, 17969.0); // V if (glyph == 23) return vec2(710314.0, 17969.0); // W if (glyph == 24) return vec2(141873.0, 17962.0); // X if (glyph == 25) return vec2(135300.0, 17962.0); // Y if (glyph == 26) return vec2(139807.0, 31778.0); // Z if (glyph == 27) return vec2(714286.0, 14899.0); // 0 if (glyph == 28) return vec2(135310.0, 4484.0); // 1 if (glyph == 29) return vec2(69919.0, 14881.0); // 2 if (glyph == 30) return vec2(459838.0, 30753.0); // 3 if (glyph == 31) return vec2(621634.0, 2250.0); // 4 if (glyph == 32) return vec2(984126.0, 32272.0); // 5 if (glyph == 33) return vec2(1001006.0, 14864.0); // 6 if (glyph == 34) return vec2(139528.0, 31778.0); // 7 if (glyph == 35) return vec2(476718.0, 14897.0); // 8 if (glyph == 36) return vec2(492590.0, 14897.0); // 9 if (glyph == 37) return vec2(139792.0, 1058.0); // / if (glyph == 38) return vec2(1015808.0, 0.0); // - if (glyph == 39) return vec2(198.0, 0.0); // . if (glyph == 40) return vec2(6336.0, 198.0); // : if (glyph == 41) return vec2(1020032.0, 132.0); // + if (glyph == 42) return vec2(69904.0, 16644.0); // > if (glyph == 43) return vec2(270466.0, 2184.0); // ( if (glyph == 44) return vec2(67720.0, 8322.0); // ) return vec2(0.0); } vec4 labelPack(int label, int part) { if (label == 0) { // GLSL CHESS if (part == 0) return vec4(3224327.0, 1343680.0, 1235.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 1) { // WHITE TO MOVE if (part == 0) return vec4(5280279.0, 4014085.0, 5829440.0, 5.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 2) { // BLACK TO MOVE if (part == 0) return vec4(791298.0, 4014091.0, 5829440.0, 5.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 3) { // WHITE IN CHECK if (part == 0) return vec4(5280279.0, 3706885.0, 1343680.0, 707.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 4) { // BLACK IN CHECK if (part == 0) return vec4(791298.0, 3706891.0, 1343680.0, 707.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 5) { // WHITE WINS if (part == 0) return vec4(5280279.0, 2453509.0, 1230.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 6) { // BLACK WINS if (part == 0) return vec4(791298.0, 2453515.0, 1230.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 7) { // DRAW if (part == 0) return vec4(6034564.0, 0.0, 0.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 8) { // CHECKMATE if (part == 0) return vec4(807427.0, 5247819.0, 5.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 9) { // STALEMATE if (part == 0) return vec4(3151123.0, 5247813.0, 5.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 10) { // DEAD POSITION if (part == 0) return vec4(1052996.0, 5043200.0, 3970313.0, 14.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 11) { // FIVEFOLD REPETITION if (part == 0) return vec4(1401414.0, 1098694.0, 4215936.0, 5281029.0); if (part == 1) return vec4(58313.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 12) { // 75 MOVE RULE if (part == 0) return vec4(3409954.0, 21903.0, 1361234.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 13) { // DRAW CLAIMED if (part == 0) return vec4(6034564.0, 311488.0, 1069897.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 14) { // NEW GAME if (part == 0) return vec4(94542.0, 1364039.0, 0.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 15) { // FLIP if (part == 0) return vec4(4231942.0, 0.0, 0.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 16) { // CLAIM DRAW if (part == 0) return vec4(2364163.0, 4734989.0, 1473.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 17) { // PROMOTE PAWN if (part == 0) return vec4(3470480.0, 21775.0, 3764304.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 18) { // CANCEL if (part == 0) return vec4(843843.0, 773.0, 0.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 19) { // QUEEN if (part == 0) return vec4(1332561.0, 14.0, 0.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 20) { // ROOK if (part == 0) return vec4(2946002.0, 0.0, 0.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 21) { // BISHOP if (part == 0) return vec4(2175554.0, 1039.0, 0.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 22) { // KNIGHT if (part == 0) return vec4(1872779.0, 1288.0, 0.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 23) { // FROM if (part == 0) return vec4(3470470.0, 0.0, 0.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 24) { // TO if (part == 0) return vec4(980.0, 0.0, 0.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 25) { // PROMOTION if (part == 0) return vec4(3470480.0, 3970319.0, 14.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 26) { // CHOOSE FROM / TO IN CONTROLS - PRESS MOVE if (part == 0) return vec4(3994115.0, 1573203.0, 54226.0, 4014117.0); if (part == 1) return vec4(57920.0, 5301187.0, 5030866.0, 4196736.0); if (part == 2) return vec4(5058898.0, 5829440.0, 5.0, 0.0); } if (label == 27) { // PRESS NEW GAME IN CONTROLS TO RESTART if (part == 0) return vec4(5002384.0, 1368083.0, 290839.0, 2359629.0); if (part == 1) return vec4(3944462.0, 4007182.0, 5244108.0, 1384463.0); if (part == 2) return vec4(4723987.0, 20.0, 0.0, 0.0); } if (label == 28) { // TWO PLAYER CHESS if (part == 0) return vec4(62932.0, 6558480.0, 787589.0, 5058888.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 29) { // DRAW BY AGREEMENT if (part == 0) return vec4(6034564.0, 102528.0, 1384897.0, 3691333.0); if (part == 1) return vec4(20.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 30) { // RESIGNATION if (part == 0) return vec4(2437458.0, 5247879.0, 58313.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 31) { // 50 MOVE RULE if (part == 0) return vec4(3409632.0, 21903.0, 1361234.0, 0.0); if (part == 1) return vec4(0.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 32) { // THREEFOLD REPETITION if (part == 0) return vec4(1384980.0, 3207557.0, 1384452.0, 2441552.0); if (part == 1) return vec4(3732052.0, 0.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 33) { // DRAW AVAILABLE IN CONTROLS if (part == 0) return vec4(6034564.0, 352320.0, 529161.0, 2359628.0); if (part == 1) return vec4(3944462.0, 4007182.0, 1228.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 34) { // ILLEGAL MOVE - TRY A HIGHLIGHTED SQUARE if (part == 0) return vec4(1360649.0, 49223.0, 1401805.0, 5245312.0); if (part == 1) return vec4(263762.0, 1872384.0, 1872648.0, 1070344.0); if (part == 2) return vec4(5575872.0, 21633.0, 0.0, 0.0); } if (label == 35) { // DRAW CLAIM ARMED - SELECT MOVE AND PRESS MOVE if (part == 0) return vec4(6034564.0, 311488.0, 262985.0, 1069906.0); if (part == 1) return vec4(4983168.0, 807685.0, 3985428.0, 262486.0); if (part == 2) return vec4(4194574.0, 5058898.0, 5829440.0, 5.0); } if (label == 36) { // NO DRAW CLAIM AVAILABLE FOR THIS POSITION if (part == 0) return vec4(1049550.0, 94290.0, 2364163.0, 5771277.0); if (part == 1) return vec4(311873.0, 21250.0, 74694.0, 5018132.0); if (part == 2) return vec4(5043200.0, 3970313.0, 14.0, 0.0); } if (label == 37) { // FLOAT BUFFERS REQUIRED if (part == 0) return vec4(324358.0, 5513236.0, 4739462.0, 1384467.0); if (part == 1) return vec4(4756817.0, 261.0, 0.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } if (label == 38) { // THIS HOST CANNOT STORE THE CHESS POSITION if (part == 0) return vec4(5018132.0, 5042688.0, 274452.0, 5305230.0); if (part == 1) return vec4(4015296.0, 5243218.0, 786760.0, 5058888.0); if (part == 2) return vec4(5043200.0, 3970313.0, 14.0, 0.0); } if (label == 39) { // ENABLE FLOAT TARGETS TO PLAY if (part == 0) return vec4(529285.0, 1573196.0, 5247948.0, 4723968.0); if (part == 1) return vec4(5062983.0, 62720.0, 6558480.0, 0.0); if (part == 2) return vec4(0.0, 0.0, 0.0, 0.0); } return vec4(0.0); } float labelLength(int label) { if (label == 0) return 10.0; if (label == 1) return 13.0; if (label == 2) return 13.0; if (label == 3) return 14.0; if (label == 4) return 14.0; if (label == 5) return 10.0; if (label == 6) return 10.0; if (label == 7) return 4.0; if (label == 8) return 9.0; if (label == 9) return 9.0; if (label == 10) return 13.0; if (label == 11) return 19.0; if (label == 12) return 12.0; if (label == 13) return 12.0; if (label == 14) return 8.0; if (label == 15) return 4.0; if (label == 16) return 10.0; if (label == 17) return 12.0; if (label == 18) return 6.0; if (label == 19) return 5.0; if (label == 20) return 4.0; if (label == 21) return 6.0; if (label == 22) return 6.0; if (label == 23) return 4.0; if (label == 24) return 2.0; if (label == 25) return 9.0; if (label == 26) return 41.0; if (label == 27) return 37.0; if (label == 28) return 16.0; if (label == 29) return 17.0; if (label == 30) return 11.0; if (label == 31) return 12.0; if (label == 32) return 20.0; if (label == 33) return 26.0; if (label == 34) return 39.0; if (label == 35) return 45.0; if (label == 36) return 41.0; if (label == 37) return 22.0; if (label == 38) return 41.0; if (label == 39) return 28.0; return 0.0; } float glyphCoverage(vec2 p, int glyph, float pixel) { vec2 q = p / pixel; if (q.x < -0.8 || q.x > 5.8 || q.y < -0.8 || q.y > 7.8) return 0.0; vec2 glyphData = fontBits(glyph); float baseX = floor(q.x), baseY = floor(q.y); float aa = 0.65 / (chessSize() * pixel); float alpha = 0.0; // Adjacent lit pixels add their coverages, keeping connected strokes solid. for (int dy = -1; dy <= 1; ++dy) { for (int dx = -1; dx <= 1; ++dx) { float x = baseX + float(dx), y = baseY + float(dy); if (x >= 0.0 && x < 5.0 && y >= 0.0 && y < 7.0) { float value = y < 4.0 ? glyphData.x : glyphData.y; float shift = (y < 4.0 ? y : y - 4.0) * 5.0 + 4.0 - x; float bit = mod(floor(value / exp2(shift)), 2.0); if (bit > 0.5) { float d = sdBox(q - vec2(x + 0.5, y + 0.5), vec2(0.5)); alpha += coverage(d, aa); } } } } return min(alpha, 1.0); } float packedCharacter(vec4 glyphData, float index) { float value = glyphData.x; if (index >= 4.0) value = glyphData.y; if (index >= 8.0) value = glyphData.z; if (index >= 12.0) value = glyphData.w; return mod(floor(value / exp2(mod(index, 4.0) * 6.0)), 64.0); } float labelCoverage(vec2 p, int label, float pixel) { float count = labelLength(label); if (p.x < 0.0 || p.x >= count * 6.0 * pixel || p.y < -pixel || p.y > 8.0 * pixel) return 0.0; float character = floor(p.x / (6.0 * pixel)); int part = int(floor(character / 16.0)); vec4 glyphData = labelPack(label, part); int glyph = int(packedCharacter(glyphData, mod(character, 16.0))); vec2 local = vec2(p.x - character * 6.0 * pixel, p.y); return glyphCoverage(local, glyph, pixel); } vec3 labelLeft(vec3 color, vec2 uv, vec2 anchor, int label, float pixel, vec3 ink) { return mix(color, ink, labelCoverage(uv - anchor, label, pixel)); } vec3 labelRight(vec3 color, vec2 uv, vec2 anchor, int label, float pixel, vec3 ink) { anchor.x -= (labelLength(label) * 6.0 - 1.0) * pixel; return labelLeft(color, uv, anchor, label, pixel, ink); } vec3 labelCenter(vec3 color, vec2 uv, vec2 anchor, int label, float pixel, vec3 ink) { anchor.x -= (labelLength(label) * 6.0 - 1.0) * pixel * 0.5; return labelLeft(color, uv, anchor, label, pixel, ink); } float boardCoordinate(vec2 p, int glyph) { float pixel = 0.00215; return glyphCoverage(p + vec2(2.5, 3.5) * pixel, glyph, pixel); } vec3 squareReadout(vec3 color, vec2 uv, vec2 anchor, int square, vec3 ink) { float pixel = 0.0035; float alpha; if (square < 0 || square > 63) { alpha = glyphCoverage(uv - anchor, 38, pixel); } else { int rank = square / 8; int file = square - rank * 8; alpha = glyphCoverage(uv - anchor, file + 1, pixel); alpha += glyphCoverage(uv - anchor - vec2(6.0 * pixel, 0.0), rank + 28, pixel); } return mix(color, ink, min(alpha, 1.0)); } // Parent supplies uiPiece(square), uiLegal(from,to), and uiData(key). // Keys: turn, selection, pending from/to, result, last from/to, check, // claim flags, flip, destination, ply, promotion, float support, event, armed. vec4 drawChess(vec2 fragCoord) { vec2 uv = chessPoint(fragCoord); float pixelAA = 0.70 / chessSize(); vec2 screenUV = fragCoord / RENDERSIZE; vec3 color = vec3(0.066, 0.095, 0.080); color += 0.014 * (1.0 - screenUV.y); color *= 1.0 - 0.08 * length(screenUV - 0.5); vec3 cream = vec3(0.86, 0.88, 0.77); vec3 muted = vec3(0.47, 0.57, 0.48); vec3 gold = vec3(0.83, 0.77, 0.48); vec3 red = vec3(0.94, 0.49, 0.40); int turn = uiData(0); int selectedSquare = uiData(1); int status = uiData(4); int flip = uiData(9); int destination = uiData(10); bool inCheck = uiData(7) != 0; vec2 boardUV = (uv - INSET) / BOARD_SIZE; vec2 grid = boardUV * 8.0; bool inside = boardUV.x >= 0.0 && boardUV.y >= 0.0 && boardUV.x < 1.0 && boardUV.y < 1.0; bool frame = uv.x >= 0.0 && uv.x < 1.0 && uv.y >= 0.0 && uv.y < 1.0; if (frame) { color = vec3(0.092, 0.124, 0.111) + 0.014 * (1.0 - uv.y); } if (inside) { int displayFile = int(floor(grid.x)); int displayRank = int(floor(grid.y)); int file = flip == 0 ? displayFile : 7 - displayFile; int rank = flip == 0 ? displayRank : 7 - displayRank; int square = file + rank * 8; int piece = uiPiece(square); int type = piece < 0 ? -piece : piece; vec2 p = fract(grid) - 0.5; float aa = pixelAA * 8.0 / BOARD_SIZE; bool lightSquare = mod(float(file + rank), 2.0) > 0.5; color = lightSquare ? vec3(0.871, 0.863, 0.791) : vec3(0.431, 0.552, 0.480); color += 0.015 * (boardUV.y - boardUV.x); if (square == uiData(5) || square == uiData(6)) color = mix(color, vec3(0.81, 0.75, 0.40), 0.31); if (square == destination && square != selectedSquare && status == 0) color = mix(color, vec3(0.96, 0.96, 0.88), 0.13); if (square == selectedSquare && status == 0) color = mix(color, vec3(0.88, 0.80, 0.44), 0.49); if (inCheck && type == 6 && (piece > 0 ? 1 : -1) == turn) { float glow = exp(-4.1 * dot(p, p)); color = mix(color, vec3(0.79, 0.23, 0.20), 0.40 + 0.29 * glow); } if (square == selectedSquare && status == 0) { float border = abs(sdBox(p, vec2(0.468))) - 0.009; color = mix(color, vec3(0.95, 0.90, 0.60), 0.85 * coverage(border, aa)); } bool legal = false; if (selectedSquare >= 0 && selectedSquare < 64 && status == 0) legal = uiLegal(selectedSquare, square) > 0.5; if (legal) { float marker; if (piece == 0) marker = coverage(sdCircle(p, 0.069), aa); else { float ring = abs(length(p) - 0.442) - 0.027; marker = coverage(ring, aa) * smoothstep(0.230, 0.260, min(abs(p.x), abs(p.y))); } color = mix(color, vec3(0.10, 0.25, 0.18), marker * 0.49); } if (piece != 0) color = drawPiece(color, p, piece, aa); float edge = min(min(boardUV.x, boardUV.y), min(1.0 - boardUV.x, 1.0 - boardUV.y)); color *= 1.0 - 0.12 * (1.0 - smoothstep(0.0, 0.006, edge)); } else if (frame) { float rim = abs(sdBox(uv - 0.5, vec2(BOARD_SIZE * 0.5 + 0.003))) - 0.0006; color = mix(color, vec3(0.54, 0.60, 0.49), 0.28 * coverage(rim, pixelAA)); if (uv.x >= INSET && uv.x < 1.0 - INSET) { int file = int(floor(grid.x)); int actual = flip == 0 ? file : 7 - file; float x = INSET + (float(file) + 0.5) * BOARD_SIZE / 8.0; float lower = boardCoordinate(uv - vec2(x, 0.0275), actual + 1); float upper = boardCoordinate(uv - vec2(x, 0.9725), actual + 1); color = mix(color, vec3(0.76, 0.80, 0.69), lower * 0.85 + upper * 0.53); } if (uv.y >= INSET && uv.y < 1.0 - INSET) { int rank = int(floor(grid.y)); int actual = flip == 0 ? rank : 7 - rank; float y = INSET + (float(rank) + 0.5) * BOARD_SIZE / 8.0; float left = boardCoordinate(uv - vec2(0.0275, y), actual + 28); float right = boardCoordinate(uv - vec2(0.9725, y), actual + 28); color = mix(color, vec3(0.76, 0.80, 0.69), left * 0.85 + right * 0.53); } } color = labelLeft(color, uv, vec2(0.055, 1.042), 0, 0.0040, cream); color = labelLeft(color, uv, vec2(0.055, 1.016), 28, 0.00215, muted); int statusLabel = turn > 0 ? 1 : 2; int reason = -1; vec3 statusInk = cream; if (inCheck) { statusLabel = turn > 0 ? 3 : 4; statusInk = red; } if (status == 1 || status == 9) statusLabel = 5; else if (status == 2 || status == 10) statusLabel = 6; else if (status != 0) statusLabel = 7; if (status != 0) statusInk = gold; if (status == 1 || status == 2) reason = 8; else if (status == 3) reason = 9; else if (status == 4) reason = 10; else if (status == 5) reason = 11; else if (status == 6) reason = 12; else if (status == 7) reason = 32; else if (status == 8) reason = 31; else if (status == 9 || status == 10) reason = 30; else if (status == 11) reason = 29; color = labelRight(color, uv, vec2(0.945, 1.044), statusLabel, 0.0035, statusInk); if (reason >= 0) color = labelRight(color, uv, vec2(0.945, 1.016), reason, 0.00215, muted); // These are readouts for the host's native ISF controls, not canvas buttons. float ruleLine = coverage(sdBox(uv - vec2(0.5, -0.020), vec2(0.445, 0.00055)), pixelAA); color = mix(color, muted, ruleLine * 0.25); color = labelLeft(color, uv, vec2(0.055, -0.064), 23, 0.0026, muted); color = squareReadout(color, uv, vec2(0.149, -0.066), selectedSquare, cream); color = labelLeft(color, uv, vec2(0.266, -0.064), 24, 0.0026, muted); color = squareReadout(color, uv, vec2(0.320, -0.066), destination, cream); color = labelLeft(color, uv, vec2(0.582, -0.064), 25, 0.0023, muted); int promotion = uiData(12); int promotionLabel = 19; if (promotion == 4) promotionLabel = 20; else if (promotion == 3) promotionLabel = 21; else if (promotion == 2) promotionLabel = 22; color = labelRight(color, uv, vec2(0.945, -0.066), promotionLabel, 0.0031, cream); int footer = 26; vec3 footerInk = muted; if (uiData(8) != 0) { footer = 33; footerInk = gold; } if (uiData(14) == 2) { footer = 34; footerInk = red; } if (uiData(14) == 6) { footer = 36; footerInk = red; } if (uiData(15) != 0) { footer = 35; footerInk = gold; } if (status != 0) { footer = 27; footerInk = muted; } color = labelCenter(color, uv, vec2(0.5, -0.117), footer, 0.00255, footerInk); if (uiData(13) == 0) { color *= 0.20; float panel = coverage(sdRoundBox(uv - vec2(0.5, 0.5), vec2(0.46, 0.115), 0.012), pixelAA); color = mix(color, vec3(0.10, 0.14, 0.115), panel); color = labelCenter(color, uv, vec2(0.5, 0.545), 37, 0.0040, red); color = labelCenter(color, uv, vec2(0.5, 0.477), 38, 0.0027, cream); color = labelCenter(color, uv, vec2(0.5, 0.426), 39, 0.00265, muted); } return vec4(color, 1.0); } void main() { ivec2 pixel = ivec2(floor(gl_FragCoord.xy)); int index = pixel.y * 64 + pixel.x; if (PASSINDEX == 0) gl_FragColor = pixel.x < 64 ? advanceGame(index) : vec4(0.0); else if (PASSINDEX == 1) gl_FragColor = calculateLegal(pixel); else if (PASSINDEX == 2) gl_FragColor = pixel.x < 64 ? calculateFacts(index) : vec4(0.0); else if (PASSINDEX == 3) gl_FragColor = pixel.x < 64 ? finishGame(index) : vec4(0.0); else gl_FragColor = drawChess(gl_FragCoord.xy); }
Playable chess entirely in GLSL. Choose From and To, then press Move. Promotion defaults to Queen. Rules, history, checkmate and draws live in feedback textures. No mouse binding is required. Changing resolution or seeking resets the game; keep live playback running.
export clip · render a video in your browser
Renders to video in your browser (no upload), stepping the real renderer frame by frame so multipass + feedback come out right. motion auto-animates the shader's own knobs — each export rolls a fresh take (🎲 re-roll, 🔒 to pin one you like). GIF is slower with a tighter length cap; MP4/WebM are faster. Some devices record in real time (noted below if so) — keep the tab visible.
embed · drop this shader into any page
Renders client-side in the reader's browser (CC0 — no attribution required, but the backlink is appreciated).