├── .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 | [](https://codeclimate.com/github/kelvinkamau/TicTacToe/maintainability) [](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 |
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 | });
--------------------------------------------------------------------------------