├── 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 | 
21 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Snakes & Ladders
6 |
7 |
8 |
9 |
10 |
11 |
12 |
Player:
13 |
14 |
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();
--------------------------------------------------------------------------------