Skip to content

Instantly share code, notes, and snippets.

@sebastian-palma
Last active May 21, 2021 20:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sebastian-palma/e0ee73da804929a2193f23ea83f0f6bc to your computer and use it in GitHub Desktop.
Save sebastian-palma/e0ee73da804929a2193f23ea83f0f6bc to your computer and use it in GitHub Desktop.
{"id":"06a4194c4b016917","slug":null,"trashed":false,"description":"","like":false,"likes":0,"publish_level":"private","forks":0,"fork_of":{"id":"17468271a273819d","slug":"visualizing-chess-games","title":"Visualizing chess games","owner":{"id":"9ba5e0b68d3e7972","github_login":"EE2dev","avatar_url":"https://avatars0.githubusercontent.com/u/7562919?v=4","login":"ee2dev","name":"","bio":"","home_url":"https://github.com/EE2dev/","type":"individual"},"version":698},"update_time":"2021-05-21T20:12:48.480Z","publish_time":null,"publish_version":null,"thumbnail":null,"default_thumbnail":null,"roles":["editor","owner"],"sharing":"private","subscription":"comments","edit_unpublished":false,"owner":{"id":"77ad7d1d53486a0d","github_login":"sebastian-palma","avatar_url":"https://avatars.githubusercontent.com/u/11888191?v=4","login":"sebastian-palma","name":"Sebastián","bio":"","home_url":"","type":"individual"},"creator":{"id":"77ad7d1d53486a0d","github_login":"sebastian-palma","avatar_url":"https://avatars.githubusercontent.com/u/11888191?v=4","login":"sebastian-palma","name":"Sebastián","bio":"","home_url":""},"collections":[],"files":[],"comments":[],"commenting_lock":null,"version":755,"title":"","license":null,"copyright":"","nodes":[{"id":123,"value":"md `## Choose a chess game to be visualized`","pinned":false,"mode":"js"},{"id":204,"value":"viewof gameInputSelection = form(html`<form>\n <div>\n Provide the chess game by:\n <br><label><input name=\"game input\" type=\"radio\" value=\"lichess\" checked> <i>copy/paste link from lichess.org</i></label>\n<br><label><input name=\"game input\" type=\"radio\" value=\"pgn\"> <i>copy/paste a game in PGN</i></label>\n <!--br><label><input name=\"game input\" type=\"radio\" value=\"board\"> <i>enter moves on a chess board</i></label-->\n </div>\n</form>`)","pinned":false,"mode":"js"},{"id":207,"value":"viewof myGame = {\n if (gameInputSelection[\"game input\"] === \"lichess\") {\n return form(html`<form>\n <div><label><input name=\"message\" type=\"text\" value=\"https://lichess.org/jHJ5wEwP/black\"> <i>Copy the link to your Lichess game here</i></label></div>\n</form>`)\n } else if (gameInputSelection[\"game input\"] === \"pgn\") { \n return textarea({\n title: \"Chess game to be visualized\", \n placeholder: \"Insert game in PGN format here...\", \n width: \"100%\",\n rows: 10,\n });\n }\n}","pinned":false,"mode":"js"},{"id":128,"value":"md`## Visualizing chess games (Work in progress)`","pinned":false,"mode":"js"},{"id":72,"value":"viewof squareWidth = slider({\n min: 1, \n max: maxWidth, \n step: 1, \n value: defaultWidth, \n title: \"Square width\", \n description: \"adjust the slider to change the width of a chessboard square\"\n})","pinned":false,"mode":"js"},{"id":338,"value":"md`### Version 8: drawing chess pieces, a few and their paths with higher opacity`","pinned":false,"mode":"js"},{"id":342,"value":"version8();","pinned":false,"mode":"js"},{"id":5,"value":"md`## Appendix`","pinned":false,"mode":"js"},{"id":53,"value":"md`#### Libraries and imports` ","pinned":false,"mode":"js"},{"id":8,"value":"d3 = require(\"d3\")","pinned":false,"mode":"js"},{"id":41,"value":"myChess = require(\"chess.js\")","pinned":false,"mode":"js"},{"id":101,"value":"import {form} from \"@mbostock/form-input\"","pinned":true,"mode":"js"},{"id":79,"value":"import {text, textarea, radio, slider} from \"@jashkenas/inputs\"","pinned":false,"mode":"js"},{"id":57,"value":"md`#### Code` ","pinned":false,"mode":"js"},{"id":640,"value":"maxWidth = { return width < (50 + squarePadding) * 9 ? width / 9 : 100;}","pinned":false,"mode":"js"},{"id":647,"value":"defaultWidth = { return maxWidth < 50 ? maxWidth : 50;}","pinned":false,"mode":"js"},{"id":69,"value":"squarePadding = 1","pinned":false,"mode":"js"},{"id":9,"value":"function drawChessboard( squareColors = {white: \"#030\", black: \"black\"}, paddingX = 0, paddingY = 0) {\n \n const squareColorWhite = squareColors.white;\n const squareColorBlack = squareColors.black; \n const squareTotal = squareWidth + squarePadding;\n const w = squareTotal * 8 + squarePadding + paddingX;\n const h = squareTotal * 8 + squarePadding + paddingY;\n const svg = DOM.svg(w, h);\n \n // specify defs \n getDefsForSVG(svg);\n\n d3.select(svg).append(\"rect\")\n .style(\"fill\",squareColorBlack)\n .attr(\"width\", w)\n .attr(\"height\", h);\n \n const numSquares = d3.range(64);\n \n // draw squares\n d3.select(svg)\n .append(\"g\")\n .attr(\"transform\", \"translate(\" + squarePadding + \", \" + squarePadding + \")\")\n .selectAll(\"rect.square\")\n .data(numSquares)\n .join(\"rect\")\n .attr(\"class\", \"square\")\n .style(\"fill\", d => (d % 16 < 8) ? ((d % 2) ? squareColorBlack : squareColorWhite) \n : (!(d % 2) ? squareColorBlack : squareColorWhite))\n .style(\"stroke\", \"none\")\n .attr(\"x\", d => (d % 8) * squareTotal)\n .attr(\"y\", d => Math.floor(d / 8) * squareTotal)\n .attr(\"width\", squareWidth)\n .attr(\"height\", squareWidth); \n return svg;\n}","pinned":false,"mode":"js"},{"id":11,"value":"function getDefsForSVG(svg){\n const defs = d3.select(svg)\n .append(\"defs\");\n \n const filter = defs.append(\"filter\")\n .attr(\"filterUnits\", \"userSpaceOnUse\")\n\t\t.attr(\"id\",\"glow\");\n\n // glow filter as proposed by Nadieh Bremer\n\tfilter.append(\"feGaussianBlur\")\n\t\t.attr(\"class\", \"blur\")\n\t\t.attr(\"stdDeviation\",\"4.5\")\n\t\t.attr(\"result\",\"coloredBlur\");\n\n const feMerge = filter.append(\"feMerge\");\n feMerge.append(\"feMergeNode\")\n .attr(\"in\",\"coloredBlur\");\n feMerge.append(\"feMergeNode\")\n .attr(\"in\",\"SourceGraphic\");\n \n // simple alternative glow filter\n const filter2 = defs.append(\"filter\")\n .attr(\"filterUnits\", \"userSpaceOnUse\")\n\t\t.attr(\"id\",\"glow2\");\n\n\tfilter2.append(\"feGaussianBlur\")\n\t\t.attr(\"class\", \"blur\")\n\t\t.attr(\"stdDeviation\",\"4.5\");\n \n const filter3 = defs.append(\"filter\")\n .attr(\"filterUnits\", \"userSpaceOnUse\")\n\t\t.attr(\"id\",\"glow3\");\n\n\tfilter3.append(\"feGaussianBlur\")\n\t\t.attr(\"class\", \"blur3\")\n\t\t.attr(\"stdDeviation\",\"4.5\");\n \n /*\n filter2.append(\"feColorMatrix\")\n .attr(\"type\", \"matrix\")\n .attr(\"values\", \"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1.5 0\");\n */\n \n defs\n .append('marker')\n .attr('id', 'arrowhead')\n .attr('viewBox', '-0 -5 10 10')\n // .attr('viewBox', '-0 0 10 15')\n .attr('refX', 13)\n .attr('refY', 0)\n .attr('orient', 'auto')\n .attr('markerWidth', 13)\n .attr('markerHeight',13)\n .attr('xoverflow','visible')\n .append('svg:path')\n .attr('d', 'M 0,-5 L 10 ,0 L 0,5')\n .attr('fill', '#999')\n .style('stroke','none');\n \n return svg;\n}","pinned":false,"mode":"js"},{"id":19,"value":"function squareToPosition(square) { \n const letters = [\"a\",\"b\",\"c\",\"d\",\"e\",\"f\",\"g\",\"h\"];\n const x = (letters.indexOf(square.charAt(0))) * (squareWidth + squarePadding) + squarePadding + squareWidth / 2;\n const y = (7 - (Number(square.charAt(1)) - 1)) * (squareWidth + squarePadding) + squarePadding + squareWidth / 2;\n return {x: x, y: y};\n}","pinned":false,"mode":"js"},{"id":25,"value":"piecePositions = {\n const myMap = new Map();\n let str = \"\";\n let pos = {};\n for (let [key, ar] of pieceMoves) {\n str = \"\";\n ar.forEach( function(ele, index) {\n pos = squareToPosition(ele.square);\n if (index === 0) {\n str += \"M \" + pos.x + \" \" + pos.y;\n } else {\n str += \" L \" + pos.x + \" \" + pos.y;\n }\n myMap.set(key, str);\n });\n };\n return myMap;\n}","pinned":false,"mode":"js"},{"id":27,"value":"pieceMoves = {\n const myMap = new Map();\n \n // initialize\n const pieces = [\"wRa1\", \"wNb1\", \"wBc1\", \"wQd1\", \"wKe1\", \"wBf1\", \"wNg1\", \"wRh1\", \n \"wPa2\", \"wPb2\", \"wPc2\", \"wPd2\", \"wPe2\", \"wPf2\", \"wPg2\", \"wPh2\",\n \"bRa8\", \"bNb8\", \"bBc8\", \"bQd8\", \"bKe8\", \"bBf8\", \"bNg8\", \"bRh8\", \n \"bPa7\", \"bPb7\", \"bPc7\", \"bPd7\", \"bPe7\", \"bPf7\", \"bPg7\", \"bPh7\"\n ];\n let obj, ar;\n \n for (let p of pieces) {\n ar = [];\n obj = {\"square\": p.slice(-2), moves: 0.5, isCaptured: false, \n hasCaptured: false, hasCapturedPiece: \"\", hasEval: 0, hasTimeSpent: 0};\n ar.push(obj);\n myMap.set(p, ar);\n }\n \n // populate with game moves\n let value = {};\n let newValue = {};\n for (let move of currentGame) {\n for (let [key, ar] of myMap) {\n value = ar[ar.length - 1];\n newValue = {};\n if (value.isCaptured) continue; // piece does not exist any more\n if (move.color !== key.charAt(0)) { // wrong color\n if (move.to === value.square) {\n value.isCaptured = true;\n } else {\n value.moves = value.moves + .5;\n }\n ar[ar.length - 1] = value;\n myMap.set(key, ar);\n } else { //same color\n if (move.from === value.square) {\n Object.assign(newValue, obj);\n newValue.square = move.to;\n if (move.captured) {\n newValue.hasCaptured = true;\n newValue.hasCapturedPiece = move.captured.toUpperCase();\n }\n ar.push(newValue);\n } else {\n value.moves = value.moves + .5;\n ar[ar.length - 1] = value;\n }\n myMap.set(key, ar);\n }\n } // end for .. myMap\n } // end for .. game1\n \n return myMap;\n}","pinned":false,"mode":"js"},{"id":35,"value":"currentGame = {\n const chess = new myChess();\n chess.load_pgn(anyPgn);\n \n /*\n // make some moves\n chess.move('e4');\n chess.move('c5');\n chess.move('Nf3');\n chess.move('Nc6');\n chess.move('d4');\n chess.move('cxd4');\n */\n \n return chess.history({ verbose: true });\n}","pinned":false,"mode":"js"},{"id":36,"value":"anyPgn = { \n let lichessAPI;\n let ret;\n let params = new URLSearchParams(window.location.search);\n const game = params.get(\"g\"); \n \n if (!game) {\n if (typeof myGame.message !== \"undefined\") {\n lichessAPI = \"https://lichess.org/game/export/\" + myGame.message.substr(20, 8);\n ret = d3.text(lichessAPI);\n } else {\n ret = myGame;\n }\n } else {\n lichessAPI = \"https://lichess.org/game/export/\" + game.slice(-8);\n ret = d3.text(lichessAPI);\n }\n return ret;\n}","pinned":false,"mode":"js"},{"id":48,"value":"colorOfPieces = { \n const myMap = new Map();\n myMap.set(\"pw\", \"#9ecae1\");\n myMap.set(\"rw\", \"#636363\");\n myMap.set(\"nw\", \"#3182bd\");\n myMap.set(\"bw\", \"#a1d99b\");\n myMap.set(\"qw\", \"#31a354\");\n myMap.set(\"kw\", \"#bdbdbd\");\n \n myMap.set(\"kb\", \"#fdae6b\");\n myMap.set(\"qb\", \"#e6550d\");\n myMap.set(\"bb\", \"#bcbddc\");\n myMap.set(\"nb\", \"#756bb1\");\n myMap.set(\"rb\", \"#fc9272\");\n myMap.set(\"pb\", \"#de2d26\");\n return myMap;\n }","pinned":false,"mode":"js"},{"id":452,"value":"colorOfPieces2 = { \n const myMap = new Map();\n myMap.set(\"pw\", \"#9ecae1\");\n myMap.set(\"rw\", \"#31a354\");\n myMap.set(\"nw\", \"#756bb1\");\n myMap.set(\"bw\", \"#e6550d\");\n myMap.set(\"qw\", \"#de2d26\");\n myMap.set(\"kw\", \"#fdae6b\");\n \n myMap.set(\"kb\", \"#fdae6b\");\n myMap.set(\"qb\", \"#de2d26\");\n myMap.set(\"bb\", \"#e6550d\");\n myMap.set(\"nb\", \"#756bb1\");\n myMap.set(\"rb\", \"#31a354\");\n myMap.set(\"pb\", \"#9ecae1\");\n return myMap;\n }","pinned":false,"mode":"js"},{"id":490,"value":"colorOfPieces3 = { \n const p = \"#e41a1c\";\n const r = \"#377eb8\";\n const n = \"#4daf4a\";\n const b = \"#984ea3\";\n const q = \"#ff7f00\";\n const k = \"#ffff33\";\n \n const myMap = new Map();\n /*\n myMap.set(\"pw\", \"#9ecae1\");\n myMap.set(\"rw\", \"#31a354\");\n myMap.set(\"nw\", \"#756bb1\");\n myMap.set(\"bw\", \"#e6550d\");\n myMap.set(\"qw\", \"#de2d26\");\n myMap.set(\"kw\", \"#fdae6b\");\n \n myMap.set(\"pb\", \"#9ecae1\"); \n myMap.set(\"rb\", \"#31a354\");\n myMap.set(\"nb\", \"#756bb1\");\n myMap.set(\"bb\", \"#e6550d\");\n myMap.set(\"qb\", \"#de2d26\");\n myMap.set(\"kb\", \"#fdae6b\");\n */\n myMap.set(\"pw\", p);\n myMap.set(\"rw\", r);\n myMap.set(\"nw\", n);\n myMap.set(\"bw\", b);\n myMap.set(\"qw\", q);\n myMap.set(\"kw\", k);\n \n myMap.set(\"pb\", p); \n myMap.set(\"rb\", r);\n myMap.set(\"nb\", n);\n myMap.set(\"bb\", b);\n myMap.set(\"qb\", q);\n myMap.set(\"kb\", k);\n \n return myMap;\n }","pinned":false,"mode":"js"},{"id":539,"value":"function positionAt(moveNr) {\n const chess = new myChess(); \n currentGame.forEach((e, i) => { if (i < moveNr) { chess.move(e.san); } });\n \n const rows = [1,2,3,4,5,6,7,8];\n const cols = [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\"];\n \n let piece, square;\n const prefix = \"https://cdn.jsdelivr.net/gh/oakmac/chessboardjs/website/img/chesspieces/wikipedia/\";\n const pieces = [];\n \n for(let r = 0; r < rows.length; r++){\n for(let c = 0; c < cols.length; c++){\n square = cols[c] + rows[r];\n piece = chess.get(square);\n if (piece) { \n let p = {pieceLink: prefix + [piece.color] + piece.type.toUpperCase() + \".png\"\n , piece: piece.type + piece.color\n , x: squareToPosition(square).x\n , y: squareToPosition(square).y};\n pieces.push(p);\n }\n }\n }\n \n return pieces;\n}","pinned":false,"mode":"js"},{"id":254,"value":"function rotate(d) {\n const xFrom = squareToPosition(d.from).x;\n const xTo = squareToPosition(d.to).x;\n if (xTo < xFrom) { \n const rx = xTo + ((xFrom - xTo) / 2);\n const yFrom = squareToPosition(d.from).y;\n const yTo = squareToPosition(d.to).y;\n const ry = (yTo > yFrom) \n ? yFrom + ((yTo - yFrom) / 2)\n : yTo + ((yFrom - yTo) / 2);\n return 'rotate(180 ' + rx + ' ' + ry + ')';\n }\n else {\n return 'rotate(0)';\n }\n}","pinned":false,"mode":"js"},{"id":347,"value":"function version8() { \n // const svg = drawChessboard();\n const svg = drawChessboard({\"white\": \"white\", \"black\": \"lightgrey\"});\n const dataArray = currentGame;\n \n // paths\n const sel1 = d3.select(svg)\n .selectAll(\"path.piece-move\")\n .data(dataArray);\n \n sel1.join(\"path\")\n .attr(\"class\", \"piece-move\")\n .attr(\"d\", (d, i) => {\n let str = {};\n const dx = (i % 2) ? -2 : 2;\n const dy = (i % 2) ? -2 : 2;\n str = \"M \" + (squareToPosition(d.from).x + dx) + \" \" + (squareToPosition(d.from).y + dy);\n str += \" L \" + (squareToPosition(d.to).x + dx) + \" \" + (squareToPosition(d.to).y + dy);\n return str;\n })\n //.style(\"stroke\", (d, i) => (i % 2) ? \"steelblue\" : \"orange\")\n .style(\"stroke\", (d, i) => (i % 2) ? \"white\" : \"black\")\n .style(\"stroke-width\", squareWidth / 60 * 7)\n .style(\"stroke-linejoin\", \"round\")\n .style(\"opacity\", (d,i) => (i > 40 && i < 45) ? 0.9 : 0.2)\n .style(\"fill\", \"none\");\n \n sel1.join(\"path\")\n .attr(\"class\", \"piece-move\")\n .attr(\"d\", (d, i) => {\n let str = {};\n const dx = (i % 2) ? -2 : 2;\n const dy = (i % 2) ? -2 : 2;\n str = \"M \" + (squareToPosition(d.from).x + dx) + \" \" + (squareToPosition(d.from).y + dy);\n str += \" L \" + (squareToPosition(d.to).x + dx) + \" \" + (squareToPosition(d.to).y + dy);\n return str;\n })\n .style(\"stroke\", (d, i) => (i % 2) ? \"black\" : \"white\")\n .style(\"stroke-width\", squareWidth / 60 * 5)\n .style(\"stroke-linejoin\", \"round\")\n .style(\"opacity\", (d,i) => (i > 40 && i < 45) ? 0.9 : 0.2)\n .style(\"fill\", \"none\");\n \n \n // pieces from, to\n const sel2 = d3.select(svg)\n .selectAll(\"circle\")\n .data(dataArray);\n \n sel2\n .join(\"circle\")\n .attr(\"class\", \"piece-square from\")\n .style(\"fill\", (d, i) => (i % 2) ? \"black\" : \"white\")\n .attr(\"cx\", (d, i) => {\n const dx = (i % 2) ? -2 : 2;\n return squareToPosition(d.from).x + dx;\n })\n .attr(\"cy\", (d, i) => {\n const dy = (i % 2) ? -2 : 2;\n return squareToPosition(d.from).y + dy;\n })\n .attr(\"r\", squareWidth / 60 * 8); \n\n sel2\n .join(\"circle\")\n .attr(\"class\", \"piece-square to\")\n .style(\"fill\", (d, i) => d.captured ? ((i % 2) ? \"white\" : \"black\") : (i % 2) ? \"black\" : \"white\")\n .style(\"stroke\", (d, i) => d.captured ? ((i % 2) ? \"black\" : \"white\") : \"none\")\n .style(\"stroke-width\", 6)\n .attr(\"cx\", (d, i) => {\n const dx = (i % 2) ? -2 : 2;\n return squareToPosition(d.to).x + dx;\n })\n .attr(\"cy\", (d, i) => {\n const dy = (i % 2) ? -2 : 2;\n return squareToPosition(d.to).y + dy;\n })\n .attr(\"r\", squareWidth / 60 * 8); \n \n const imageWidth = squareWidth * .7;\n const sel3 = d3.select(svg)\n .selectAll(\"image\")\n .data(dataArray);\n \n sel3\n .join(\"image\")\n //.filter(d => d.piece === \"b\")\n .attr(\"class\", \"piece-square from\")\n .attr(\"xlink:href\", (d, i) => {\n const prefix = \"https://cdn.jsdelivr.net/gh/oakmac/chessboardjs/website/img/chesspieces/wikipedia/\";\n const piece = prefix + d.color + d.piece.toUpperCase() + \".png\";\n return piece;\n })\n .style(\"opacity\", (d,i) => (i > 40 && i < 45) ? 0.9 : 0.2)\n .attr(\"x\", d => squareToPosition(d.from).x - imageWidth / 2)\n .attr(\"y\", d => squareToPosition(d.from).y - imageWidth / 2)\n .attr(\"height\", imageWidth)\n .attr(\"width\", imageWidth);\n\n \n d3.select(svg).selectAll(\".piece-move\")\n // .style(\"filter\",\"url(#glow)\");\n .style(\"filter\",\"url(\" + window.location + \"#glow)\");\n \n return svg;\n}","pinned":false,"mode":"js"}]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment