UNPKG

besogo

Version:

Embeddable SGF player for the game of Go (aka Weiqi, Baduk)

573 lines (507 loc) 23 kB
besogo.makeBoardDisplay = function(container, editor) { 'use strict'; var CELL_SIZE = 88, // Including line width COORD_MARGIN = 75, // Margin for coordinate labels EXTRA_MARGIN = 6, // Extra margin on the edge of board BOARD_MARGIN, // Total board margin // Board size parameters sizeX = editor.getCurrent().getSize().x, sizeY = editor.getCurrent().getSize().y, svg, // Holds the overall board display SVG element stoneGroup, // Group for stones markupGroup, // Group for markup hoverGroup, // Group for hover layer markupLayer, // Array of markup layer elements hoverLayer, // Array of hover layer elements randIndex, // Random index for stone images TOUCH_FLAG = false; // Flag for touch interfaces initializeBoard(editor.getCoordStyle()); // Initialize SVG element and draw the board container.appendChild(svg); // Add the SVG element to the document editor.addListener(update); // Register listener to handle editor/game state updates redrawAll(editor.getCurrent()); // Draw stones, markup and hover layer // Set listener to detect touch interfaces container.addEventListener('touchstart', setTouchFlag); // Function for setting the flag for touch interfaces function setTouchFlag () { TOUCH_FLAG = true; // Set flag to prevent needless function calls hoverLayer = []; // Drop hover layer references, kills events svg.removeChild(hoverGroup); // Remove hover group from SVG // Remove self when done container.removeEventListener('touchstart', setTouchFlag); } // Initializes the SVG and draws the board function initializeBoard(coord) { drawBoard(coord); // Initialize the SVG element and draw the board stoneGroup = besogo.svgEl("g"); markupGroup = besogo.svgEl("g"); svg.appendChild(stoneGroup); // Add placeholder group for stone layer svg.appendChild(markupGroup); // Add placeholder group for markup layer if (!TOUCH_FLAG) { hoverGroup = besogo.svgEl("g"); svg.appendChild(hoverGroup); } addEventTargets(); // Add mouse event listener layer if (editor.REAL_STONES) { // Generate index for realistic stone images randomizeIndex(); } } // Callback for board display redraws function update(msg) { var current = editor.getCurrent(), currentSize = current.getSize(), reinit = false, // Board redraw flag oldSvg = svg; // Check if board size has changed if (currentSize.x !== sizeX || currentSize.y !== sizeY || msg.coord) { sizeX = currentSize.x; sizeY = currentSize.y; initializeBoard(msg.coord || editor.getCoordStyle()); // Reinitialize board container.replaceChild(svg, oldSvg); reinit = true; // Flag board redrawn } // Redraw stones only if needed if (reinit || msg.navChange || msg.stoneChange) { redrawStones(current); redrawMarkup(current); redrawHover(current); } else if (msg.markupChange || msg.treeChange) { redrawMarkup(current); redrawHover(current); } else if (msg.tool || msg.label) { redrawHover(current); } } function redrawAll(current) { redrawStones(current); redrawMarkup(current); redrawHover(current); } // Initializes the SVG element and draws the board function drawBoard(coord) { var boardWidth, boardHeight, string = "", // Path string for inner board lines i; // Scratch iteration variable BOARD_MARGIN = (coord === 'none' ? 0 : COORD_MARGIN) + EXTRA_MARGIN; boardWidth = 2*BOARD_MARGIN + sizeX*CELL_SIZE; boardHeight = 2*BOARD_MARGIN + sizeY*CELL_SIZE; svg = besogo.svgEl("svg", { // Initialize the SVG element width: "100%", height: "100%", viewBox: "0 0 " + boardWidth + " " + boardHeight }); svg.appendChild(besogo.svgEl("rect", { // Fill background color width: boardWidth, height: boardHeight, 'class': 'besogo-svg-board' }) ); svg.appendChild(besogo.svgEl("rect", { // Draw outer square of board width: CELL_SIZE*(sizeX - 1), height: CELL_SIZE*(sizeY - 1), x: svgPos(1), y: svgPos(1), 'class': 'besogo-svg-lines' }) ); for (i = 2; i <= (sizeY - 1); i++) { // Horizontal inner lines string += "M" + svgPos(1) + "," + svgPos(i) + "h" + CELL_SIZE*(sizeX - 1); } for (i = 2; i <= (sizeX - 1); i++) { // Vertical inner lines string += "M" + svgPos(i) + "," + svgPos(1) + "v" + CELL_SIZE*(sizeY - 1); } svg.appendChild( besogo.svgEl("path", { // Draw inner lines of board d: string, 'class': 'besogo-svg-lines' }) ); drawHoshi(); // Draw the hoshi points if (coord !== 'none') { drawCoords(coord); // Draw the coordinate labels } } // Draws coordinate labels on the board function drawCoords(coord) { var labels = besogo.coord[coord](sizeX, sizeY), labelXa = labels.x, // Top edge labels labelXb = labels.xb || labels.x, // Bottom edge labelYa = labels.y, // Left edge labelYb = labels.yb || labels.y, // Right edge shift = COORD_MARGIN + 10, i, x, y; // Scratch iteration variable for (i = 1; i <= sizeX; i++) { // Draw column coordinate labels x = svgPos(i); drawCoordLabel(x, svgPos(1) - shift, labelXa[i]); drawCoordLabel(x, svgPos(sizeY) + shift, labelXb[i]); } for (i = 1; i <= sizeY; i++) { // Draw row coordinate labels y = svgPos(i); drawCoordLabel(svgPos(1) - shift, y, labelYa[i]); drawCoordLabel(svgPos(sizeX) + shift, y, labelYb[i]); } function drawCoordLabel(x, y, label) { var element = besogo.svgEl("text", { x: x, y: y, dy: ".65ex", // Seems to work for vertically centering these fonts "font-size": 32, "text-anchor": "middle", // Horizontal centering "font-family": "Helvetica, Arial, sans-serif", fill: 'black' }); element.appendChild( document.createTextNode(label) ); svg.appendChild(element); } } // Draws hoshi onto the board at procedurally generated locations function drawHoshi() { var cx, cy, // Center point calculation pathStr = ""; // Path string for drawing star points if (sizeX % 2 && sizeY % 2) { // Draw center hoshi if both dimensions are odd cx = (sizeX - 1)/2 + 1; // Calculate the center of the board cy = (sizeY - 1)/2 + 1; drawStar(cx, cy); if (sizeX >= 17 && sizeY >= 17) { // Draw side hoshi if at least 17x17 and odd drawStar(4, cy); drawStar(sizeX - 3, cy); drawStar(cx, 4); drawStar(cx, sizeY - 3); } } if (sizeX >= 11 && sizeY >= 11) { // Corner hoshi at (4, 4) for larger sizes drawStar(4, 4); drawStar(4, sizeY - 3); drawStar(sizeX - 3, 4); drawStar(sizeX - 3, sizeY - 3); } else if (sizeX >= 8 && sizeY >= 8) { // Corner hoshi at (3, 3) for medium sizes drawStar(3, 3); drawStar(3, sizeY - 2); drawStar(sizeX - 2, 3); drawStar(sizeX - 2, sizeY - 2); } // No corner hoshi for smaller sizes if (pathStr) { // Only need to add if hoshi drawn svg.appendChild( besogo.svgEl('path', { // Drawing circles via path points d: pathStr, // Hack to allow radius adjustment via stroke-width 'stroke-linecap': 'round', // Makes the points round 'class': 'besogo-svg-hoshi' }) ); } function drawStar(i, j) { // Extend path string to draw star point pathStr += "M" + svgPos(i) + ',' + svgPos(j) + 'l0,0'; // Draws a point } } // Remakes the randomized index for stone images function randomizeIndex() { var maxIndex = besogo.BLACK_STONES * besogo.WHITE_STONES, i, j; randIndex = []; for (i = 1; i <= sizeX; i++) { for (j = 1; j <= sizeY; j++) { randIndex[fromXY(i, j)] = Math.floor(Math.random() * maxIndex); } } } // Adds a grid of squares to register mouse events function addEventTargets() { var element, i, j; for (i = 1; i <= sizeX; i++) { for (j = 1; j <= sizeY; j++) { element = besogo.svgEl("rect", { // Make a transparent event target x: svgPos(i) - CELL_SIZE/2, y: svgPos(j) - CELL_SIZE/2, width: CELL_SIZE, height: CELL_SIZE, opacity: 0 }); // Add event listeners, using closures to decouple (i, j) element.addEventListener("click", handleClick(i, j)); if (!TOUCH_FLAG) { // Skip hover listeners for touch interfaces element.addEventListener("mouseover", handleOver(i, j)); element.addEventListener("mouseout", handleOut(i, j)); } svg.appendChild(element); } } } function handleClick(i, j) { // Returns function for click handling return function(event) { // Call click handler in editor editor.click(i, j, event.ctrlKey, event.shiftKey); if(!TOUCH_FLAG) { (handleOver(i, j))(); // Ensures that any updated tool is visible } }; } function handleOver(i, j) { // Returns function for mouse over return function() { var element = hoverLayer[ fromXY(i, j) ]; if (element) { // Make tool action visible on hover over element.setAttribute('visibility', 'visible'); } }; } function handleOut(i, j) { // Returns function for mouse off return function() { var element = hoverLayer[ fromXY(i, j) ]; if (element) { // Make tool action invisible on hover off element.setAttribute('visibility', 'hidden'); } }; } // Redraws the stones function redrawStones(current) { var group = besogo.svgEl("g"), // New stone layer group shadowGroup, // Group for shadow layer i, j, x, y, color; // Scratch iteration variables if (editor.SHADOWS) { // Add group for shawdows shadowGroup = besogo.svgShadowGroup(); group.appendChild(shadowGroup); } for (i = 1; i <= sizeX; i++) { for (j = 1; j <= sizeY; j++) { color = current.getStone(i, j); if (color) { x = svgPos(i); y = svgPos(j); if (editor.REAL_STONES) { // Realistic stone group.appendChild(besogo.realStone(x, y, color, randIndex[fromXY(i, j)])); } else { // SVG stone group.appendChild(besogo.svgStone(x, y, color)); } if (editor.SHADOWS) { // Draw shadows shadowGroup.appendChild(besogo.svgShadow(x - 2, y - 4)); shadowGroup.appendChild(besogo.svgShadow(x + 2, y + 4)); } } } } svg.replaceChild(group, stoneGroup); // Replace the stone group stoneGroup = group; } // Redraws the markup function redrawMarkup(current) { var element, i, j, x, y, // Scratch iteration variables group = besogo.svgEl("g"), // Group holding markup layer elements lastMove = current.move, variants = editor.getVariants(), mark, // Scratch mark state {0, 1, 2, 3, 4, 5} stone, // Scratch stone state {0, -1, 1} color; // Scratch color string markupLayer = []; // Clear the references to the old layer for (i = 1; i <= sizeX; i++) { for (j = 1; j <= sizeY; j++) { mark = current.getMarkup(i, j); if (mark) { x = svgPos(i); y = svgPos(j); stone = current.getStone(i, j); color = (stone === -1) ? "white" : "black"; // White on black if (lastMove && lastMove.x === i && lastMove.y === j) { // Mark last move blue or violet if also a variant color = checkVariants(variants, current, i, j) ? besogo.PURP : besogo.BLUE; } else if (checkVariants(variants, current, i, j)) { color = besogo.RED; // Natural variant marks are red } if (typeof mark === 'number') { // Markup is a basic shape switch(mark) { case 1: element = besogo.svgCircle(x, y, color); break; case 2: element = besogo.svgSquare(x, y, color); break; case 3: element = besogo.svgTriangle(x, y, color); break; case 4: element = besogo.svgCross(x, y, color); break; case 5: element = besogo.svgBlock(x, y, color); break; } } else { // Markup is a label if (!stone) { // If placing label on empty spot element = makeBacker(x, y); group.appendChild(element); } element = besogo.svgLabel(x, y, color, mark); } group.appendChild(element); markupLayer[ fromXY(i, j) ] = element; } // END if (mark) } // END for j } // END for i // Mark last move with plus if not already marked if (lastMove && lastMove.x !== 0 && lastMove.y !== 0) { i = lastMove.x; j = lastMove.y; if (!markupLayer[ fromXY(i, j) ]) { // Last move not marked color = checkVariants(variants, current, i, j) ? besogo.PURP : besogo.BLUE; element = besogo.svgPlus(svgPos(i), svgPos(j), color); group.appendChild(element); markupLayer[ fromXY(i, j) ] = element; } } // Mark variants that have not already been marked above markRemainingVariants(variants, current, group); svg.replaceChild(group, markupGroup); // Replace the markup group markupGroup = group; } // END function redrawMarkup function makeBacker(x, y) { // Makes a label markup backer at (x, y) return besogo.svgEl("rect", { x: x - CELL_SIZE/2, y: y - CELL_SIZE/2, height: CELL_SIZE, width: CELL_SIZE, opacity: 0.85, stroke: "none", 'class': 'besogo-svg-board besogo-svg-backer' }); } // Checks if (x, y) is in variants function checkVariants(variants, current, x, y) { var i, move; for (i = 0; i < variants.length; i++) { if (variants[i] !== current) { // Skip current (within siblings) move = variants[i].move; if (move && move.x === x && move.y === y) { return true; } } } return false; } // Marks variants that have not already been marked function markRemainingVariants(variants, current, group) { var element, move, // Variant move label, // Variant label stone, // Stone state i, x, y; // Scratch iteration variables for (i = 0; i < variants.length; i++) { if (variants[i] !== current) { // Skip current (within siblings) move = variants[i].move; // Check if move, not a pass, and no mark yet if (move && move.x !== 0 && !markupLayer[ fromXY(move.x, move.y) ]) { stone = current.getStone(move.x, move.y); x = svgPos(move.x); // Get SVG positions y = svgPos(move.y); if (!stone) { // If placing label on empty spot element = makeBacker(x, y); group.appendChild(element); } // Label variants with letters A-Z cyclically label = String.fromCharCode('A'.charCodeAt(0) + (i % 26)); element = besogo.svgLabel(x, y, besogo.LRED, label); group.appendChild(element); markupLayer[ fromXY(move.x, move.y) ] = element; } } } } // END function markRemainingVariants // Redraws the hover layer function redrawHover(current) { if (TOUCH_FLAG) { return; // Do nothing for touch interfaces } var element, i, j, x, y, // Scratch iteration variables group = besogo.svgEl("g"), // Group holding hover layer elements tool = editor.getTool(), children, stone, // Scratch stone state {0, -1, 1} or move color; // Scratch color string hoverLayer = []; // Clear the references to the old layer group.setAttribute('opacity', '0.35'); if (tool === 'navOnly') { // Render navOnly hover by iterating over children children = current.children; for (i = 0; i < children.length; i++) { stone = children[i].move; if (stone && stone.x !== 0) { // Child node is move and not a pass x = svgPos(stone.x); y = svgPos(stone.y); element = besogo.svgStone(x, y, stone.color); element.setAttribute('visibility', 'hidden'); group.appendChild(element); hoverLayer[ fromXY(stone.x, stone.y) ] = element; } } } else { // Render hover for other tools by iterating over grid for (i = 1; i <= sizeX; i++) { for (j = 1; j <= sizeY; j++) { element = null; x = svgPos(i); y = svgPos(j); stone = current.getStone(i, j); color = (stone === -1) ? "white" : "black"; // White on black switch(tool) { case 'auto': element = besogo.svgStone(x, y, current.nextMove()); break; case 'playB': element = besogo.svgStone(x, y, -1); break; case 'playW': element = besogo.svgStone(x, y, 1); break; case 'addB': if (stone === -1) { element = besogo.svgCross(x, y, besogo.RED); } else { element = besogo.svgEl('g'); element.appendChild(besogo.svgStone(x, y, -1)); element.appendChild(besogo.svgPlus(x, y, besogo.RED)); } break; case 'addW': if (stone === 1) { element = besogo.svgCross(x, y, besogo.RED); } else { element = besogo.svgEl('g'); element.appendChild(besogo.svgStone(x, y, 1)); element.appendChild(besogo.svgPlus(x, y, besogo.RED)); } break; case 'addE': if (stone) { element = besogo.svgCross(x, y, besogo.RED); } break; case 'clrMark': break; // Nothing case 'circle': element = besogo.svgCircle(x, y, color); break; case 'square': element = besogo.svgSquare(x, y, color); break; case 'triangle': element = besogo.svgTriangle(x, y, color); break; case 'cross': element = besogo.svgCross(x, y, color); break; case 'block': element = besogo.svgBlock(x, y, color); break; case 'label': element = besogo.svgLabel(x, y, color, editor.getLabel()); break; } // END switch (tool) if (element) { element.setAttribute('visibility', 'hidden'); group.appendChild(element); hoverLayer[ fromXY(i, j) ] = element; } } // END for j } // END for i } // END else svg.replaceChild(group, hoverGroup); // Replace the hover layer group hoverGroup = group; } // END function redrawHover function svgPos(x) { // Converts (x, y) coordinates to SVG position return BOARD_MARGIN + CELL_SIZE/2 + (x-1) * CELL_SIZE; } function fromXY(x, y) { // Converts (x, y) coordinates to linear index return (x - 1)*sizeY + (y - 1); } };