├── README.md ├── index.html ├── style.css └── script.js /README.md: -------------------------------------------------------------------------------- 1 | # snakes-ladders-game 2 | 3 | snakes ladders game using JavaScript and Gsap 4 | 5 | 6 | ## Technologies used 7 | 8 | * HTML 9 | * CSS 10 | * JavaScript 11 | * GSAP 12 | 13 | ## Live link 14 | 15 | https://peter-kimanzi.github.io/snakes-ladders-game/ 16 | 17 | 18 | ## Screenshot 19 | 20 | ![snake](https://user-images.githubusercontent.com/71552773/203475338-0fb190cc-34e6-4017-bfbc-47c31ea5382a.PNG) 21 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Snakes & Ladders 6 | 7 | 8 | 9 | 10 | 11 |
12 |

Player:

13 |

 

14 |
15 |
16 | 17 |
18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | html { 2 | background-color: #eed; 3 | font-family: sans-serif; 4 | font-size: 64.5%; 5 | } 6 | 7 | .wrapper { 8 | font-size: 1.5rem; 9 | height: 500px; 10 | margin: 0 auto; 11 | width: 500px; 12 | } 13 | 14 | canvas { 15 | background-image: url("https://assets.codepen.io/215128/snakesAndLaddersBoard.jpg"); 16 | border: 5px solid grey; 17 | border-radius: 7px; 18 | height: 500px; 19 | width: 500px; 20 | } 21 | 22 | .playerYou .wrapper { 23 | color: #8a0022; 24 | } 25 | .playerYou canvas { 26 | border-color: #f36; 27 | } 28 | .playerYou #diceThrow { 29 | background-color: #f361; 30 | background-blend-mode: multiply; 31 | border-color: #d60036; 32 | } 33 | 34 | .playerAutoBot .wrapper { 35 | color: #3a480e; 36 | } 37 | .playerAutoBot canvas { 38 | border-color: #8a2; 39 | } 40 | .playerAutoBot #diceThrow { 41 | background-color: #8a22; 42 | background-blend-mode: multiply; 43 | border-color: #556a15; 44 | } 45 | 46 | #diceThrow { 47 | background-image: url("https://assets.codepen.io/215128/diceSpots.png"); 48 | background-size: 50px; 49 | border: 2px solid black; 50 | border-radius: 7px; 51 | cursor: pointer; 52 | display: inline-block; 53 | height: 50px; 54 | width: 50px; 55 | } 56 | #diceThrow.s6 { 57 | background-position: 0 0; 58 | } 59 | #diceThrow.s5 { 60 | background-position: 0 -50px; 61 | } 62 | #diceThrow.s4 { 63 | background-position: 0 -100px; 64 | } 65 | #diceThrow.s3 { 66 | background-position: 0 -150px; 67 | } 68 | #diceThrow.s2 { 69 | background-position: 0 -200px; 70 | } 71 | #diceThrow.s1 { 72 | background-position: 0 -250px; 73 | } 74 | 75 | .buttonWrapper { 76 | display: flex; 77 | margin: 15px 15px; 78 | } 79 | 80 | button { 81 | border-radius: 7px; 82 | margin-left: 100px; 83 | padding: 10px 30px; 84 | } 85 | 86 | .hidden { 87 | display: none; 88 | } 89 | 90 | h1 { 91 | margin-bottom: 5px; 92 | } 93 | 94 | h2 { 95 | margin-top: 0; 96 | } -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | let canvas = document.querySelector('canvas'); 2 | let wrapper = document.querySelector('.wrapper'); 3 | let resetGameBtn = document.querySelector('#reset'); 4 | let diceDisplay = document.querySelector('#diceThrow'); 5 | let playerDisplay = document.querySelector('.playerName'); 6 | let message = document.querySelector('.message'); 7 | let ctx = canvas.getContext('2d'); 8 | let height = 500; 9 | let width = 500; 10 | let gridSize = 50; 11 | let gridMid = 25; 12 | let walking, walkSpeed = 450; 13 | let locked = false; 14 | let slideSpeed = .5; 15 | let rolled = '', rolling, rollCount, rollMax, rollSpeed=85; 16 | 17 | let player1 = {current:0, target:0, x:0, y:0, colour:'#f36d', id:'You'}; 18 | let player2 = {current:0, target:0, x:0, y:0, colour:'#8a2d', id:'AutoBot'}; 19 | let activePlayer = player1; 20 | 21 | const obstacles = [ 22 | {type:'snake', start:97, end:78}, 23 | {type:'snake', start:95, end:56}, 24 | {type:'snake', start:88, end:24}, 25 | {type:'snake', start:62, end:18}, 26 | {type:'snake', start:48, end:26}, 27 | {type:'snake', start:36, end:6}, 28 | {type:'snake', start:32, end:10}, 29 | {type:'ladder', start:1, end:38}, 30 | {type:'ladder', start:4, end:14}, 31 | {type:'ladder', start:8, end:30}, 32 | {type:'ladder', start:21, end:42}, 33 | {type:'ladder', start:28, end:76}, 34 | {type:'ladder', start:50, end:67}, 35 | {type:'ladder', start:71, end:92}, 36 | {type:'ladder', start:80, end:99} 37 | ]; 38 | 39 | canvas.width = width; 40 | canvas.height = height; 41 | wrapper.style.width = `${width}px`; 42 | ctx.strokeStyle = '#555'; 43 | ctx.lineWidth = 2; 44 | 45 | const setLocked = (tf) => { 46 | locked = tf; 47 | } 48 | 49 | const boustrophedonWalk = (cols, rows) => { 50 | let temp = []; 51 | for(let row=0; row { 53 | return {id:col+row*cols, y:height - gridSize - row*gridSize, x:col*gridSize}; 54 | }); 55 | t = row % 2 ? t.reverse() : t; 56 | temp = [...temp, ...t]; 57 | } 58 | return temp; 59 | } 60 | 61 | const drawPlayers = () => { 62 | ctx.clearRect(0, 0, width, height); 63 | if(player1.current > 0) { 64 | ctx.fillStyle = player1.colour; 65 | ctx.beginPath(); 66 | ctx.arc(player1.x+gridMid, player1.y+gridMid, 16, 0, 2 * Math.PI); 67 | ctx.fill(); 68 | ctx.stroke(); 69 | } 70 | if(player2.current > 0) { 71 | ctx.fillStyle = player2.colour; 72 | ctx.beginPath(); 73 | if(player2.current === player1.current){ 74 | ctx.arc(player2.x+gridMid, player2.y+gridMid, 16, 45, Math.PI + 45); 75 | } 76 | else { 77 | ctx.arc(player2.x+gridMid, player2.y+gridMid, 16, 0, 2 * Math.PI); 78 | } 79 | ctx.fill(); 80 | ctx.stroke(); 81 | } 82 | } 83 | 84 | const walk = () => { 85 | let activeCounter = activePlayer.current++; 86 | let sliding = false; 87 | activePlayer.x = walkSequence[activeCounter].x; 88 | activePlayer.y = walkSequence[activeCounter].y; 89 | drawPlayers(); 90 | 91 | if(activeCounter === 99){ 92 | clearInterval(walking); 93 | showWinner(); 94 | return; 95 | } 96 | 97 | if(activePlayer.current >= activePlayer.target){ 98 | clearInterval(walking); 99 | 100 | // check obstacles 101 | for(let i=0; i < obstacles.length; i++){ 102 | if(obstacles[i].start === activePlayer.target){ 103 | let endSquare = obstacles[i].end; 104 | activePlayer.target = obstacles[i].end; 105 | sliding = true; 106 | slide(activePlayer, walkSequence[endSquare-1].x, walkSequence[endSquare-1].y, slideSpeed); 107 | break; 108 | } 109 | } 110 | if(!sliding){ 111 | resetTurn(); 112 | togglePlayer(); 113 | } 114 | } 115 | } 116 | 117 | const showWinner = () => { 118 | 119 | const messageElement = document.getElementsByClassName("playerName")[0] 120 | 121 | if (messageElement.innerText === 'You') { 122 | messageElement.innerText += ' are the winner'; 123 | } else { 124 | messageElement.innerText += ' is the winner'; 125 | } 126 | 127 | resetGameBtn.classList.remove('hidden'); 128 | } 129 | 130 | const setPlayerID = (msg='') => { 131 | playerDisplay.innerHTML = `${activePlayer.id} ${msg}`; 132 | message.innerHTML = "Click dice to play"; 133 | document.body.classList = `player${activePlayer.id}`; 134 | } 135 | 136 | const resetTurn = () => { 137 | setLocked(false); 138 | } 139 | 140 | const slide = (element, dX, dY, dur=1) => { 141 | gsap.to(element, {x:dX, y:dY, duration:dur, delay: 0.25, onUpdate:doOnUpdate, onComplete:doOnComplete}); 142 | } 143 | const doOnUpdate = () => { 144 | drawPlayers(); 145 | } 146 | const doOnComplete = () => { 147 | activePlayer.current = activePlayer.target; 148 | drawPlayers(); 149 | resetTurn(); 150 | togglePlayer(); 151 | } 152 | 153 | const togglePlayer = () => { 154 | activePlayer = activePlayer.id === player1.id ? player2 : player1; 155 | setPlayerID(); 156 | 157 | if(activePlayer === player2){ 158 | rollDice(); 159 | } 160 | } 161 | 162 | const rollDice = (evt) => { 163 | if(evt) evt.preventDefault(); 164 | if (locked) return; 165 | setLocked(true); 166 | 167 | message.innerHTML = activePlayer === player1 ? "Rolling..." : 'Auto rolling...'; 168 | 169 | rollCount = 0; 170 | rollMax = Math.random()*10 + 15; 171 | rolling = setInterval(doRoll, rollSpeed); 172 | } 173 | 174 | const doRoll = () => { 175 | rolled = Math.floor(Math.random() * 6 + 1); 176 | diceRollDisplay(rolled); 177 | if(rollCount++ >= rollMax){ 178 | clearInterval(rolling); 179 | message.innerHTML = "Moving..."; 180 | activePlayer.target += rolled; 181 | walking = setInterval(walk, walkSpeed); 182 | } 183 | } 184 | 185 | const diceRollDisplay = (spots) => { 186 | diceDisplay.classList = `s${spots}` 187 | } 188 | 189 | const resetGame = () => { 190 | player1.current = 0; 191 | player1.target = 0; 192 | player1.x = 0; 193 | player1.y = 0; 194 | player2.current = 0; 195 | player2.target = 0; 196 | player2.x = 0; 197 | player2.y = 0; 198 | activePlayer = player1; 199 | locked = false; 200 | diceRollDisplay(''); 201 | setPlayerID(); 202 | 203 | drawPlayers(); 204 | 205 | resetGameBtn.classList.add('hidden'); 206 | } 207 | 208 | diceDisplay.addEventListener('click', rollDice); 209 | resetGameBtn.addEventListener('click', resetGame); 210 | 211 | let walkSequence = boustrophedonWalk(10, 10); 212 | setPlayerID(); 213 | 214 | // Test method to show obstacles 215 | const drawObstacles = () => { 216 | ctx.clearRect(0, 0, width, height); 217 | for(let i=0; i < obstacles.length; i++){ 218 | let ob = obstacles[i]; 219 | ctx.strokeStyle = ob.type === 'snake' ? '#d00' : '#0d0'; 220 | ctx.beginPath(); 221 | ctx.moveTo(walkSequence[ob.start-1].x+gridSize*.5, walkSequence[ob.start-1].y+gridSize*.5); 222 | ctx.lineTo(walkSequence[ob.end-1].x+gridSize*.5, walkSequence[ob.end-1].y+gridSize*.5); 223 | ctx.stroke(); 224 | ctx.closePath(); 225 | } 226 | } 227 | // drawObstacles(); --------------------------------------------------------------------------------