├── .gitignore ├── images ├── bg.png └── ttc.png ├── IMG-20171204-WA0019.jpg ├── manifest.json ├── readme.md ├── sw.js ├── index.html ├── style.css └── script.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinkamau/TicTacToe/HEAD/images/bg.png -------------------------------------------------------------------------------- /images/ttc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinkamau/TicTacToe/HEAD/images/ttc.png -------------------------------------------------------------------------------- /IMG-20171204-WA0019.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kelvinkamau/TicTacToe/HEAD/IMG-20171204-WA0019.jpg -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "background_color": "#fff", 3 | "display": "standalone", 4 | "theme_color": "#fff", 5 | 6 | "short_name": "TicTacToe", 7 | "name": "TicTacToe", 8 | "icons": [ 9 | { 10 | "src": "images/ttc.png", 11 | "type": "image/png", 12 | "sizes": "48x48" 13 | }, 14 | { 15 | "src": "images/ttc.png", 16 | "type": "image/png", 17 | "sizes": "72x72" 18 | }, 19 | { 20 | "src": "images/ttc.png", 21 | "type": "image/png", 22 | "sizes": "96x96" 23 | } 24 | , 25 | { 26 | "src": "images/ttc.png", 27 | "type": "image/png", 28 | "sizes": "144x144" 29 | } 30 | , 31 | { 32 | "src": "images/ttc.png", 33 | "type": "image/png", 34 | "sizes": "168x168" 35 | } 36 | , 37 | { 38 | "src": "images/ttc.png", 39 | "type": "image/png", 40 | "sizes": "196x196" 41 | } 42 | , 43 | { 44 | "src": "images/ttc.png", 45 | "type": "image/png", 46 | "sizes": "256x256" 47 | } 48 | 49 | ], 50 | "start_url": "index.html?launcher=true" 51 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## TicTacToe Titan (v1.0) 2 | 3 | Immerse yourself in thrilling matches against a cunning AI adversary designed to challenge your strategic prowess. Prepare to outsmart your digital opponent and claim victory in this classic battle of Xs and Os! 4 | 5 | [Play TicTacToe Titan](https://kelvinkamau.github.io/tictactoe/) 6 | 7 | [![Maintainability](https://api.codeclimate.com/v1/badges/9fab1b9689fb3d534758/maintainability)](https://codeclimate.com/github/kelvinkamau/TicTacToe/maintainability) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fkelvinkamau%2Ftictactoe.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fkelvinkamau%2Ftictactoe?ref=badge_shield) 8 | 9 | #### Ways I plan to improve the game: 10 | * Add an optional signup/login with FirebaseAuth & a leaderboard where various players can get ranked. 11 | * Add SSL to secure with HTTPS 12 | * Allow players to personalize the game's appearance by selecting different themes, colors, and styles for the game board and pieces. 13 | * Introduce multiplayer functionality, enabling players to compete against friends or other online opponents in real-time matches. 14 | * Enhance the AI's decision-making process by using optimized algorithms such as the Minimax algorithm with alpha-beta pruning for efficient and strategic gameplay. -------------------------------------------------------------------------------- /sw.js: -------------------------------------------------------------------------------- 1 | var consoleMessages = ["Hey hey there buddy! :)", "Hope you're having a great day 😊", "How do you comfort a JavaScript bug? You console it 😎"], 2 | consoleMessage = consoleMessages[Math.floor(Math.random() * consoleMessages.length)]; 3 | console.log(consoleMessage); 4 | self.addEventListener('fetch', function (event) { 5 | event.respondWith(caches.open('cache').then(function (cache) { 6 | return cache.match(event.request).then(function (response) { 7 | console.log("cache request: " + event.request.url); 8 | var fetchPromise = fetch(event.request).then(function (networkResponse) { 9 | console.log("fetch completed: " + event.request.url, networkResponse); 10 | if (networkResponse) { 11 | console.debug("updated cached page: " + event.request.url, networkResponse); 12 | cache.put(event.request, networkResponse.clone()); 13 | } 14 | return networkResponse; 15 | }, function (e) { 16 | console.log("Error in fetch()", e); 17 | 18 | e.waitUntil( 19 | caches.open('cache').then(function (cache) { 20 | return cache.addAll([ 21 | '/', 22 | 23 | 'AunderwinceTicTacToe/index.html', 24 | 'AunderwinceTicTacToe/index.html?homescreen=1', 25 | '/?homescreen=1', 26 | 'AunderwinceTicTacToe/script.js', 27 | 'AunderwinceTicTacToe/style.css', 28 | 'AunderwinceTicTacToe/images/ttc.png', 29 | 'https://fonts.googleapis.com/css?family=Ubuntu:400,700', 30 | 'https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js', 31 | 'AunderwinceTicTacToe/manifest.json', 32 | 'AunderwinceTicTacToe/sw.js', 33 | 'https://static.tumblr.com/03fbbc566b081016810402488936fbae/pqpk3dn/MRSmlzpj3/tumblr_static_bg3.png' 34 | ]); 35 | }) 36 | ); 37 | }); 38 | return response || fetchPromise; 39 | }); 40 | })); 41 | }); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TicTacToe Titan 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |

TicTacToe Titan

28 | 29 |

Begin by choosing a square for your move.
You play as X and the computer as O
30 |

31 | 32 |

[ You: 0 / Computer: 0 ]

33 | 34 |
35 | 36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | 48 |
49 |

Built by Kamau.
Contribute to the project

50 |
51 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin:0; 3 | padding:0; 4 | font-size:12px; 5 | background: #2980b9 6 | url('images/bg.png') repeat 0 0; 7 | background-size: auto 100%; 8 | -webkit-animation: 10s linear 0s normal none infinite animate; 9 | -moz-animation: 10s linear 0s normal none infinite animate; 10 | -ms-animation: 10s linear 0s normal none infinite animate; 11 | -o-animation: 10s linear 0s normal none infinite animate; 12 | animation: 10s linear 0s normal none infinite animate; 13 | } 14 | 15 | html { 16 | font-family: 'Ubuntu', sans-serif; 17 | font-weight: 400; 18 | font-size: 14px; 19 | color: white; 20 | } 21 | 22 | .container { 23 | max-width: 400px; 24 | margin: 2em auto; 25 | text-align: center; 26 | line-height: 1.5; 27 | } 28 | 29 | h1 { 30 | margin: 6em 1em auto; 31 | } 32 | 33 | a { 34 | text-decoration: none; 35 | color: rgb(255, 255, 255); 36 | font-weight: 700; 37 | } 38 | 39 | span.purple { 40 | color: purple; 41 | font-weight: 700; 42 | } 43 | 44 | span.black { 45 | color: black; 46 | font-weight: 700; 47 | } 48 | 49 | .tic-tac-toe { 50 | width: 315px; 51 | height: auto; 52 | margin: 0 auto; 53 | 54 | } 55 | 56 | .tile { 57 | height: 100px; 58 | width: 100px; 59 | display: inline-block; 60 | cursor: pointer; 61 | 62 | position: relative; 63 | vertical-align: top; 64 | 65 | padding: 15px; 66 | margin-bottom: 5px; 67 | box-sizing: border-box; 68 | 69 | color: #fff; 70 | font-size: 60px; 71 | text-align: center; 72 | } 73 | 74 | .free { 75 | background-color: #fff; 76 | } 77 | 78 | .O-play { 79 | background-color: black; 80 | } 81 | 82 | .X-play { 83 | background-color: purple; 84 | } 85 | 86 | @-webkit-keyframes animate { 87 | from {background-position:0 0;} 88 | to {background-position: 500px 0;} 89 | } 90 | @-moz-keyframes animate { 91 | from {background-position:0 0;} 92 | to {background-position: 500px 0;} 93 | } 94 | @-ms-keyframes animate { 95 | from {background-position:0 0;} 96 | to {background-position: 500px 0;} 97 | } 98 | @-o-keyframes animate { 99 | from {background-position:0 0;} 100 | to {background-position: 500px 0;} 101 | } 102 | @keyframes animate { 103 | from {background-position:0 0;} 104 | to {background-position: 500px 0;} 105 | } 106 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function () { 2 | const sq1 = document.getElementById('square1'); 3 | const sq2 = document.getElementById('square2'); 4 | const sq3 = document.getElementById('square3'); 5 | const sq4 = document.getElementById('square4'); 6 | const sq5 = document.getElementById('square5'); 7 | const sq6 = document.getElementById('square6'); 8 | const sq7 = document.getElementById('square7'); 9 | const sq8 = document.getElementById('square8'); 10 | const sq9 = document.getElementById('square9'); 11 | 12 | let scorex = 0; 13 | let scoreo = 0; 14 | let playValid = false; 15 | let win = false; 16 | 17 | function validatePlay(squareplayed) { 18 | if (squareplayed.classList.contains('free')) { 19 | playValid = true; 20 | } else { 21 | playValid = false; 22 | } 23 | } 24 | 25 | function clearBoard() { 26 | const tiles = document.querySelectorAll('.tile'); 27 | tiles.forEach(tile => { 28 | tile.classList.remove('played', 'O-play', 'X-play'); 29 | tile.textContent = ''; 30 | tile.classList.add('free'); 31 | }); 32 | win = false; 33 | } 34 | 35 | function updateScoreDisplay() { 36 | const scoreDisplay = document.getElementById('score-display'); 37 | scoreDisplay.textContent = `[ You: ${scorex} / Computer: ${scoreo} ]`; 38 | } 39 | 40 | function winAlert(player) { 41 | win = true; 42 | if (player === "X") { 43 | scorex++; 44 | alert(`Yay, you beat the computer! Your score is ${scorex} Computer score is ${scoreo}`); 45 | } else { 46 | scoreo++; 47 | alert(`You lost! Computer score is ${scoreo} Your score is ${scorex}`); 48 | } 49 | updateScoreDisplay(); 50 | clearBoard(); 51 | } 52 | 53 | function checkWin() { 54 | const winConditions = [ 55 | [sq1, sq2, sq3], 56 | [sq4, sq5, sq6], 57 | [sq7, sq8, sq9], 58 | [sq1, sq4, sq7], 59 | [sq2, sq5, sq8], 60 | [sq3, sq6, sq9], 61 | [sq1, sq5, sq9], 62 | [sq3, sq5, sq7] 63 | ]; 64 | 65 | for (let i = 0; i < winConditions.length; i++) { 66 | if ( 67 | winConditions[i][0].classList.contains('X-play') && 68 | winConditions[i][1].classList.contains('X-play') && 69 | winConditions[i][2].classList.contains('X-play') 70 | ) { 71 | winAlert("X"); 72 | return true; 73 | } else if ( 74 | winConditions[i][0].classList.contains('O-play') && 75 | winConditions[i][1].classList.contains('O-play') && 76 | winConditions[i][2].classList.contains('O-play') 77 | ) { 78 | winAlert("O"); 79 | return true; 80 | } 81 | } 82 | return false; 83 | } 84 | 85 | function checkDraw() { 86 | const freeSquares = document.querySelectorAll('.tile.free'); 87 | if (freeSquares.length === 0 && !win) { 88 | alert("Draw! Try playing again!"); 89 | clearBoard(); 90 | return true; 91 | } 92 | return false; 93 | } 94 | 95 | function Oplay() { 96 | function Oplaying(square) { 97 | validatePlay(square); 98 | if (playValid) { 99 | square.classList.remove('free'); 100 | square.classList.add('played', 'O-play'); 101 | square.textContent = "O"; 102 | if (checkWin()) return; 103 | if (checkDraw()) return; 104 | return true; 105 | } 106 | return false; 107 | } 108 | 109 | function Orandomplay() { 110 | for (let i = 0; i < 10; i++) { 111 | const randomNumber = Math.floor(Math.random() * 9) + 1; 112 | const randomSquare = document.getElementById(`square${randomNumber}`); 113 | if (Oplaying(randomSquare)) { 114 | return true; 115 | } 116 | } 117 | return false; 118 | } 119 | 120 | const winConditions = [ 121 | [[sq1, sq2, sq3], sq3, sq2, sq1], 122 | [[sq4, sq5, sq6], sq6, sq5, sq4], 123 | [[sq7, sq8, sq9], sq9, sq8, sq7], 124 | [[sq1, sq4, sq7], sq7, sq4, sq1], 125 | [[sq2, sq5, sq8], sq8, sq2, sq5], 126 | [[sq3, sq6, sq9], sq9, sq6, sq3], 127 | [[sq1, sq5, sq9], sq9, sq5, sq1], 128 | [[sq3, sq5, sq7], sq7, sq5, sq3] 129 | ]; 130 | 131 | for (let i = 0; i < winConditions.length; i++) { 132 | if ( 133 | (winConditions[i][0][0].classList.contains('X-play') && 134 | winConditions[i][0][1].classList.contains('X-play') && 135 | !winConditions[i][1].classList.contains('played')) || 136 | (winConditions[i][0][0].classList.contains('O-play') && 137 | winConditions[i][0][1].classList.contains('O-play') && 138 | !winConditions[i][1].classList.contains('played')) 139 | ) { 140 | if (Oplaying(winConditions[i][1])) return; 141 | } else if ( 142 | (winConditions[i][0][0].classList.contains('X-play') && 143 | winConditions[i][0][2].classList.contains('X-play') && 144 | !winConditions[i][2].classList.contains('played')) || 145 | (winConditions[i][0][0].classList.contains('O-play') && 146 | winConditions[i][0][2].classList.contains('O-play') && 147 | !winConditions[i][2].classList.contains('played')) 148 | ) { 149 | if (Oplaying(winConditions[i][2])) return; 150 | } else if ( 151 | (winConditions[i][0][1].classList.contains('X-play') && 152 | winConditions[i][0][2].classList.contains('X-play') && 153 | !winConditions[i][3].classList.contains('played')) || 154 | (winConditions[i][0][1].classList.contains('O-play') && 155 | winConditions[i][0][2].classList.contains('O-play') && 156 | !winConditions[i][3].classList.contains('played')) 157 | ) { 158 | if (Oplaying(winConditions[i][3])) return; 159 | } 160 | } 161 | 162 | Orandomplay(); 163 | checkWin(); 164 | checkDraw(); 165 | } 166 | 167 | const tiles = document.querySelectorAll('.tile'); 168 | tiles.forEach(tile => { 169 | tile.addEventListener('click', function () { 170 | validatePlay(this); 171 | if (playValid) { 172 | this.classList.remove('free'); 173 | this.classList.add('played', 'X-play'); 174 | this.textContent = "X"; 175 | if (!checkWin() && !checkDraw()) { 176 | Oplay(); 177 | } 178 | } else { 179 | alert("Square has already been played. Please select another square"); 180 | } 181 | }); 182 | }); 183 | 184 | const resetButton = document.getElementById('reset-button'); 185 | resetButton.addEventListener('click', function () { 186 | clearBoard(); 187 | }); 188 | 189 | updateScoreDisplay(); 190 | }); --------------------------------------------------------------------------------