Draw the Board | Modern Ludo
Today we start making our Modern Ludo Game from scratch.
The first thing to do is draw the board. For this part, we’ll simply use the
The MarkUp is very simple:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>ModernLudo</title>
<link rel="stylesheet" type="text/css" href="Styles/modernludo.css" />
</head>
<body>
<div id="playGround" style="display:none;">
<div id="main">
<canvas id="gameboard"></canvas>
</div>
</div>
<script type="text/javascript" src="Scripts/modernludo.js"></script>
</body>
</html>
CSS:
body {
padding: 0;
margin: 0 auto;
overflow-y: hidden;
background-color: #0D0D0D;
}
div#main {
background-color: khaki;
margin: 0 auto;
padding: 0;
}
#gameboard {
position: absolute;
margin: 0;
padding: 0;
box-sizing: padding-box;
}
The important part is the JavaScript. How to actually draw the board:
-
Placing the board in the browser window. Considering that we want to build an application that runs in web browsers, we need to make sure that our board can adjust itself to fit a browser window of any size:
function refreshBoard() { canvasWidth = window.innerHeight - 10; canvasHeight = window.innerHeight; maindiv.style.width = canvasWidth + "px"; maindiv.style.height = canvasWidth + "px"; ctx.canvas.width = canvasWidth; ctx.canvas.height = canvasWidth; }
-
Creating the background. Our game is Modern Ludo, so a blue sky makes for a good background:
maindiv = document.getElementById("main"); canvas = document.getElementById("gameboard"); ctx = canvas.getContext("2d"); function drawSkyGradient() { var skyGradient = ctx.createLinearGradient(0, 0, 0, canvasHeight); skyGradient.addColorStop(0, '#00aaff'); skyGradient.addColorStop(1, '#ffffff'); ctx.fillStyle = skyGradient; ctx.fillRect(0, 0, canvasWidth, canvasHeight); }
Let’s see what we’ve done so far in the web browser window:
-
Deciding on the layout of the board. A typical Modern Ludo game board has 52 tiles which is normally laid out like this: In our project, all the tiles are squares so they should resize together with the board, which is why we put the following code in the function refreshBoard():
tileWidth = Math.ceil(canvasWidth / 16);
-
First, let’s see how we can place a red tile of size tileWidth*tileWidth on the canvas:
function drawARegularTile(color, width) { var imgData = ctx.createImageData(width, width); var pos = 0; for (var x = 0; x < width; x++) { for (var y = 0; y < width; y++) { var x2 = x - Math.ceil(width / 2); var y2 = y - Math.ceil(width / 2); var distance = Math.ceil(Math.sqrt(x2 * x2 + y2 * y2)); var circlewall = Math.ceil(width / 2 * 0.8); var circleWidth = Math.ceil(width / 20); ys = new Array(); for (var j = 0; j < circleWidth; j++) { ys.push(y - Math.ceil(circleWidth / 2 * 0.9) - +circleWidth + j); } if ((circlewall - circleWidth) < distance && distance < circlewall) { setColor("white"); } else { setColor(color); } imgData.data[pos++] = colorR; imgData.data[pos++] = colorG; imgData.data[pos++] = colorB; imgData.data[pos++] = colorA; } } return imgData; }
The above function creates a tile of a certain color and width using the method createImageData. Then we can place this tile in a certain location with the method putImageData. Let’s see how the following line works:
ctx.putImageData(drawARegularTile("purple ", tileWidth), 50,50);
You can see a purple tile with a white circle inside is drawn on the screen.
-
Now how do we go about placing all these tiles? As shown above, now we can draw a square easily on the board, to place 52 tiles, we need to let the computer know all the coordinates and the color to be used for each and every tile. Thinking of the canvas as a grid, we can provide a map to draw stuff:
function createMap() { var mapxy = new Array(); //notile:0, purple:1,green:2,red:3,yellow:4; mapxy.push([0, 0, 0, 0, 1, 2, 3, 4, 1, 2, 3, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 4, 0, 0, 4, 0, 0, 4, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 3, 0, 0, 4, 0, 0, 1, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 2, 0, 0, 4, 0, 0, 2, 0, 0, 0, 0]); mapxy.push([2, 3, 4, 1, 0, 0, 0, 4, 0, 0, 0, 3, 4, 1, 2]); mapxy.push([1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 3]); mapxy.push([4, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 4]); mapxy.push([3, 3, 3, 3, 3, 3, 3, 0, 1, 1, 1, 1, 1, 1, 1]); mapxy.push([2, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2]); mapxy.push([1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 3]); mapxy.push([4, 3, 2, 1, 0, 0, 0, 2, 0, 0, 0, 3, 2, 1, 4]); mapxy.push([0, 0, 0, 0, 4, 0, 0, 2, 0, 0, 4, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 3, 0, 0, 2, 0, 0, 1, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 1, 4, 3, 2, 1, 4, 3, 0, 0, 0, 0]); return mapxy; }
As the comment shows, 1 is purple, 2 is green, 3 is red, and 4 is yellow. So with the following code, we can draw all the tiles:
var boardmap = createMap(); for (var x = 0; x < 15; x++) { for (var y = 0; y < 15; y++) { switch (boardmap[y][x]) { case 0: break; case 1: ctx.putImageData(drawARegularTile("purple ", tileWidth), tileWidth / 2 + tileWidth * x, tileWidth / 2 + tileWidth * y); break; case 2: ctx.putImageData(drawARegularTile("green", tileWidth), tileWidth / 2 + tileWidth * x, tileWidth / 2 + tileWidth * y); break; case 3: ctx.putImageData(drawARegularTile("red", tileWidth), tileWidth / 2 + tileWidth * x, tileWidth / 2 + tileWidth * y); break; case 4: ctx.putImageData(drawARegularTile("yellow", tileWidth), tileWidth / 2 + tileWidth * x, tileWidth / 2 + tileWidth * y); break; default: break; } } }
-
Still we need to work on some special shapes. For example, 4 tiles should have two colors and the center tile should have 4 colors:
function drawTwoColorTile(color, width, keepColorSequence) { var imgData = ctx.createImageData(width, width); var pos = 0; for (var x = 0; x < width; x++) { for (var y = 0; y < width; y++) { if (x < width - y) { switch (color) { case "yb": keepColorSequence ? setColor("yellow") : setColor("purple "); break; case "rg": keepColorSequence ? setColor("red") : setColor("green"); break; default: break; } } else { switch (color) { case "yb": keepColorSequence ? setColor("purple ") : setColor("yellow"); break; case "rg": keepColorSequence ? setColor("green") : setColor("red"); break; default: break; } } if (x < y) { switch (color) { case "ry": keepColorSequence ? setColor("yellow") : setColor("red"); break; case "gb": keepColorSequence ? setColor("purple ") : setColor("green"); break; default: break; } } else { switch (color) { case "ry": keepColorSequence ? setColor("red") : setColor("yellow"); break; case "gb": keepColorSequence ? setColor("green") : setColor("purple "); break; default: break; } } imgData.data[pos++] = colorR; imgData.data[pos++] = colorG; imgData.data[pos++] = colorB; imgData.data[pos++] = colorA; } } return imgData; } function drawCenterTile(width) { var imgData = ctx.createImageData(width, width); var pos = 0; for (var x = 0; x < width; x++) { for (var y = 0; y < width; y++) { if (x > y - 1 && x < width - y) { setColor("red"); } else if (x < y && x > width - y - 1) { setColor("purple "); } else if (x > y - 1 && x < width) { setColor("green"); } else { setColor("yellow"); } imgData.data[pos++] = colorR; imgData.data[pos++] = colorG; imgData.data[pos++] = colorB; imgData.data[pos++] = colorA; } } return imgData; }
Now following the same logic, we just put different values for these tiles:
function createMap() { var mapxy = new Array(); //notile:0, purple:1,green:2,red:3,yellow:4; mapxy.push([0, 0, 0, 0, 1, 2, 3, 4, 1, 2, 3, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 4, 0, 0, 4, 0, 0, 4, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 3, 0, 0, 4, 0, 0, 1, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 2, 0, 0, 4, 0, 0, 2, 0, 0, 0, 0]); mapxy.push([2, 3, 4, 1, 80, 0, 0, 4, 0, 0, 70, 3, 4, 1, 2]); mapxy.push([1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 3]); mapxy.push([4, 0, 0, 0, 0, 0, 5, 4, 6, 0, 0, 0, 0, 0, 4]); mapxy.push([3, 3, 3, 3, 3, 3, 3, 9, 1, 1, 1, 1, 1, 1, 1]); mapxy.push([2, 0, 0, 0, 0, 0, 7, 2, 8, 0, 0, 0, 0, 0, 2]); mapxy.push([1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 3]); mapxy.push([4, 3, 2, 1, 0, 60, 0, 2, 0, 0, 50, 3, 2, 1, 4]); mapxy.push([0, 0, 0, 0, 4, 0, 0, 2, 0, 0, 4, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 3, 0, 0, 2, 0, 0, 1, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 1, 4, 3, 2, 1, 4, 3, 0, 0, 0, 0]); return mapxy; }
And the code to draw:
case 5: ctx.putImageData(drawTwoColorTile("ry", tileWidth, true), tileWidth / 2 + tileWidth * x, tileWidth / 2 + tileWidth * y); break; case 6: ctx.putImageData(drawTwoColorTile("yb", tileWidth, true), tileWidth / 2 + tileWidth * x, tileWidth / 2 + tileWidth * y); break; case 7: ctx.putImageData(drawTwoColorTile("rg", tileWidth, true), tileWidth / 2 + tileWidth * x, tileWidth / 2 + tileWidth * y); break; case 8: ctx.putImageData(drawTwoColorTile("gb", tileWidth, true), tileWidth / 2 + tileWidth * x, tileWidth / 2 + tileWidth * y); break; case 9: ctx.putImageData(drawCenterTile(tileWidth), tileWidth / 2 + tileWidth * x, tileWidth / 2 + tileWidth * y); break; case 50: ctx.putImageData(drawTwoColorTile("ry", tileWidth, false), tileWidth / 2 + tileWidth * x, tileWidth / 2 + tileWidth * y); break; case 60: ctx.putImageData(drawTwoColorTile("yb", tileWidth, false), tileWidth / 2 + tileWidth * x, tileWidth / 2 + tileWidth * y); break; case 70: ctx.putImageData(drawTwoColorTile("rg", tileWidth, false), tileWidth / 2 + tileWidth * x, tileWidth / 2 + tileWidth * y); break; case 80: ctx.putImageData(drawTwoColorTile("gb", tileWidth, false), tileWidth / 2 + tileWidth * x, tileWidth / 2 + tileWidth * y); break;
-
Then there are also express ways that need to be drawn. Same logic just some math involved:
function drawArrow(direction, width) { switch (direction) { case "up": color = " purple "; break; case "down": color = "red"; break; case "right": color = "green"; break; case "left": color = "yellow"; break; default: color = "white"; break; } var imgData = ctx.createImageData(width, width); var pos = 0; for (var x = 0; x < width; x++) { for (var y = 0; y < width; y++) { switch (direction) { case "right": if (x > y - 1 / 3 * width && x < 4 / 3 * width - y) { setColor(color); if (x > y && x < width - y) { setColor("transparent"); } } else { setColor("transparent"); } break; case "down": if (x < y + 1 / 3 * width && x < 4 / 3 * width - y) { setColor(color); if (x < y && x < width - y) { setColor("transparent"); } } else { setColor("transparent"); } break; case "left": if (x < y + 1 / 3 * width && x > 2 / 3 * width - y) { setColor(color); if (x < y && x > width - y) { setColor("transparent"); } } else { setColor("transparent"); } break; case "up": if (x > y - 1 / 3 * width && x > 2 / 3 * width - y) { setColor(color); if (x > y && x > width - y) { setColor("transparent"); } } else { setColor("transparent"); } break; } imgData.data[pos++] = colorR; imgData.data[pos++] = colorG; imgData.data[pos++] = colorB; imgData.data[pos++] = colorA; } } return imgData; }
And:
case 10: ctx.putImageData(drawArrow("up", tileWidth), tileWidth / 2 + tileWidth * x, tileWidth / 2 + tileWidth * y); break; case 20: ctx.putImageData(drawArrow("right", tileWidth), tileWidth / 2 + tileWidth * x, tileWidth / 2 + tileWidth * y); break; case 30: ctx.putImageData(drawArrow("down", tileWidth), tileWidth / 2 + tileWidth * x, tileWidth / 2 + tileWidth * y); break; case 40: ctx.putImageData(drawArrow("left", tileWidth), tileWidth / 2 + tileWidth * x, tileWidth / 2 + tileWidth * y); break;
Updated map:
function createMap() { var mapxy = new Array(); //notile:0, purple:1,green:2,red:3,yello:4; mapxy.push([0, 0, 0, 0, 1, 2, 3, 4, 1, 2, 3, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 4, 0, 0, 4, 0, 0, 4, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 3, 0, 0, 4, 0, 0, 1, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 2, 20, 20, 4, 20, 20, 2, 0, 0, 0, 0]); mapxy.push([2, 3, 4, 1, 80, 0, 0, 4, 0, 0, 70, 3, 4, 1, 2]); mapxy.push([1, 0, 0, 10, 0, 0, 0, 4, 0, 0, 0, 30, 0, 0, 3]); mapxy.push([4, 0, 0, 10, 0, 0, 5, 4, 6, 0, 0, 30, 0, 0, 4]); mapxy.push([3, 3, 3, 3, 3, 3, 3, 9, 1, 1, 1, 1, 1, 1, 1]); mapxy.push([2, 0, 0, 10, 0, 0, 7, 2, 8, 0, 0, 30, 0, 0, 2]); mapxy.push([1, 0, 0, 10, 0, 0, 0, 2, 0, 0, 0, 30, 0, 0, 3]); mapxy.push([4, 3, 2, 1, 60, 0, 0, 2, 0, 0, 50, 3, 2, 1, 4]); mapxy.push([0, 0, 0, 0, 4, 40, 40, 2, 40, 40, 4, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 3, 0, 0, 2, 0, 0, 1, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0]); mapxy.push([0, 0, 0, 0, 1, 4, 3, 2, 1, 4, 3, 0, 0, 0, 0]); return mapxy; }