├── .gitignore
├── README.md
├── index.html
├── css
└── master.css
└── js
└── main.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tic Tac Toe
2 | ## Tic Tac Toe Game using JS
3 |
4 | ## Synopsis
5 | * Allow multi-player
6 | * When winner, cells highlight
7 | * Include button to reset board at any time during game
8 |
9 | ## Links
10 |
11 | * Live Example - https://robhitt.github.io/tic-tac-toe-js
12 | * Github - https://www.github.com/robhitt/tic-tac-toe-js
13 |
14 | ## Installation
15 |
16 | * Fire up the [index.html](https://robhitt.github.io/tic-tac-toe-js) file in any web browser and you'll be good to go!
17 |
18 | ## Contributors
19 |
20 | #### Rob Hitt
21 | * [E-mail](mailto:robhitt@gmail.com)
22 | * [Website](https://www.robhitt.com/)
23 | * [Resume](http://www.robhitt.com/resume)
24 | * [LinkedIn](http://www.linkedin.com/in/robhitt)
25 | * [Twitter](http://www.twitter.com/robhitt)
26 | * [Instagram](http://www.instagram.com/robhitt)
27 | * [Bodega Cats](http://www.instagram.com/bodegacatsofinstagram)
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
37 |
38 |
39 |
40 |
41 |
42 |
45 |
48 |
51 |
52 |
55 |
58 |
61 |
62 |
65 |
68 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/css/master.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | font-family: "Helvetica", sans-serif;
5 | box-sizing: border-box;
6 | }
7 |
8 | .hide-container {
9 | display: none;
10 | }
11 |
12 | .header {
13 | text-align: center;
14 | margin: 15px 0;
15 | font-family: "Permanent Marker";
16 | }
17 |
18 | .enter-players {
19 | margin: 0 auto;
20 | width: 80%;
21 | text-align: center;
22 | position: relative;
23 | }
24 |
25 | .input-field {
26 | border: 2px solid #000;
27 | outline: none;
28 | padding: 4px 8px;
29 | margin: 0 0 10px 4px;
30 | font-size: 18px;
31 | }
32 |
33 | .input-field:focus {
34 | border: 2px solid #E15E32;
35 | }
36 |
37 | .submit-btn {
38 | border: 2px solid #000;
39 | padding: 8px 8px;
40 | font-size: 18px;
41 | width: 250px;
42 | border-radius: 5px;
43 | margin-top: 10px;
44 | background-color: #fff;
45 | }
46 |
47 | .submit-btn:active,
48 | .submit-btn:focus {
49 | outline: none;
50 | border: 2px solid #E15E32;
51 | }
52 |
53 | .submit-btn:hover {
54 | background-color: #ece9e9;
55 | }
56 |
57 | .board___player-turn {
58 | text-align: center;
59 | margin: 10px 0 10px;
60 | height: 54px;
61 | }
62 |
63 | .name--style {
64 | font-size: 22px;
65 | }
66 |
67 | .board__container {
68 | width: 40%;
69 | background-color: pink;
70 | margin: 0 auto;
71 | font-size: 0;
72 | border: 2px solid #000;
73 | -webkit-box-shadow: 3px 3px 3px 0px rgba(0,0,0,0.75);
74 | -moz-box-shadow: 3px 3px 3px 0px rgba(0,0,0,0.75);
75 | box-shadow: 3px 3px 3px 0px rgba(0,0,0,0.75);
76 | }
77 |
78 | .board__cell {
79 | width: calc(100% / 3);
80 | display: inline-block;
81 | font-size: 40px;
82 | text-align: center;
83 | border: 2px solid #000;
84 | padding: 20px;
85 | vertical-align: top;
86 | font-family: "Permanent Marker";
87 | }
88 |
89 | .board__cell--winner {
90 | background-color: #f9738a;
91 | }
92 |
93 | .letter {
94 | position: relative;
95 | top: 50%;
96 | transform: translateY(-50%);
97 | font-family: "Permanent Marker";
98 | }
99 |
100 | /* WINNER CONTAINER */
101 | .reset {
102 | text-align: center;
103 | margin: 20px auto 0;
104 | }
105 |
106 | .reset--hidden {
107 | display: none;
108 | }
109 |
110 | .replay-btn {
111 | width: 25%;
112 | padding: 10px 20px;
113 | border: 2px solid #000;
114 | border-radius: 5px;
115 | outline: none;
116 | letter-spacing: 0;
117 | text-transform: uppercase;
118 | font-size: 16px;
119 | margin-top: 12px;
120 | word-spacing: 3px;
121 | background-color: #fff;
122 | }
123 |
124 | .replay-btn:hover,
125 | .replay-btn:active {
126 | outline: none;
127 | color: #fff;
128 | background-color: #000;
129 | }
130 |
131 | .congratulations {
132 | font-size: 24px;
133 | }
134 |
135 | .u-r-winner {
136 | font-size: 18px;
137 | height: 18px;
138 | line-height: 18px;
139 | margin: 2px 0;
140 | }
141 |
142 | @media only screen and (max-width: 767px) {
143 | .board__cell {
144 | font-size: 16px;
145 | padding: 5px;
146 | }
147 |
148 | .replay-btn {
149 | width: 50%;
150 | }
151 | }
--------------------------------------------------------------------------------
/js/main.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | window.addEventListener('load', app);
4 |
5 | let gameBoard = ['', '', '', '', '', '', '', '', ''];
6 | let turn = 0; // Keeps track if X or O player's turn
7 | let winner = false;
8 |
9 | // CREATE PLAYER
10 | const player = (name) => {
11 | name = name;
12 | return {name};
13 | };
14 |
15 | let playerX = player("");
16 | let playerY = player("");
17 |
18 | // INITIALIZE APP
19 | function app() {
20 | let inputField = document.querySelector('.input-field').focus();
21 |
22 | const addPlayerForm = document.getElementById('player-form');
23 | addPlayerForm.addEventListener('submit', addPlayers);
24 |
25 | let replayButton = document.querySelector('.replay-btn');
26 | replayButton.addEventListener('click', resetBoard);
27 | }
28 |
29 | // Add PLAYERS
30 | function addPlayers(event) {
31 | event.preventDefault();
32 |
33 | if (this.player1.value === '' || this.player2.value === '') {
34 | alert('You Must Enter a Name for Each Field');
35 | return;
36 | }
37 |
38 | const playerFormContainer = document.querySelector('.enter-players');
39 | const boardMain = document.querySelector('.board__main');
40 | playerFormContainer.classList.add('hide-container');
41 | boardMain.classList.remove('hide-container');
42 |
43 | playerX.name = this.player1.value;
44 | playerY.name = this.player2.value;
45 | buildBoard();
46 | }
47 |
48 | // RETURN CURRENT PLAYER
49 | function currentPlayer() {
50 | return turn % 2 === 0 ? 'X' : 'O';
51 | }
52 |
53 | // Resize squares in event browser is resized
54 | window.addEventListener("resize", onResize);
55 | function onResize() {
56 | let allCells = document.querySelectorAll('.board__cell');
57 | let cellHeight = allCells[0].offsetWidth;
58 |
59 | allCells.forEach( cell => {
60 | cell.style.height = `${cellHeight}px`;
61 | });
62 | }
63 |
64 | // Build Board
65 | function buildBoard() {
66 | let resetContainer = document.querySelector('.reset');
67 | resetContainer.classList.remove('reset--hidden');
68 |
69 | onResize();
70 | addCellClickListener();
71 | changeBoardHeaderNames();
72 | }
73 |
74 | // CELL CLICK EVENT FOR PLAYER TO ATTEMPT TO MAKE MOVE
75 | function makeMove(event) {
76 | console.log(turn);
77 |
78 | let currentCell = parseInt(event.currentTarget.firstElementChild.dataset.id);
79 | let cellToAddToken = document.querySelector(`[data-id='${currentCell}']`);
80 |
81 | if (cellToAddToken.innerHTML !== '') {
82 | console.log('This cell is already taken.');
83 | return;
84 | } else {
85 | if (currentPlayer() === 'X') {
86 | cellToAddToken.textContent = currentPlayer();
87 | gameBoard[currentCell] = 'X';
88 | } else {
89 | cellToAddToken.textContent = currentPlayer();
90 | gameBoard[currentCell] = 'O';
91 | }
92 | }
93 |
94 | // CHECK IF WE HAVE A WINNER
95 | isWinner();
96 |
97 | // Update turn count so next player can choose
98 | turn ++;
99 |
100 | // CHANGE BOARD HEADER INFO
101 | changeBoardHeaderNames();
102 | }
103 |
104 | function checkIfTie() {
105 | if (turn > 7) {
106 | alert('game over a tie')
107 | }
108 | }
109 |
110 | function isWinner() {
111 | const winningSequences = [
112 | [0, 1, 2],
113 | [3, 4, 5],
114 | [6, 7, 8],
115 | [0, 3, 6],
116 | [1, 4, 7],
117 | [2, 5, 8],
118 | [0, 4, 8],
119 | [2, 4, 6]
120 | ];
121 |
122 | winningSequences.forEach( winningCombos => {
123 | let cell1 = winningCombos[0];
124 | let cell2 = winningCombos[1];
125 | let cell3 = winningCombos[2];
126 | if (
127 | gameBoard[cell1] === currentPlayer() &&
128 | gameBoard[cell2] === currentPlayer() &&
129 | gameBoard[cell3] === currentPlayer()
130 | ) {
131 |
132 |
133 | const cells = document.querySelectorAll('.board__cell');
134 | let letterId1 = document.querySelector(`[data-id='${cell1}']`);
135 | let letterId2 = document.querySelector(`[data-id='${cell2}']`);
136 | let letterId3 = document.querySelector(`[data-id='${cell3}']`);
137 |
138 | cells.forEach( cell => {
139 | let cellId = cell.firstElementChild.dataset.id;
140 |
141 | if (cellId == cell1 || cellId == cell2 || cellId == cell3 ) {
142 | cell.classList.add('board__cell--winner');
143 | }
144 | });
145 |
146 | let currentPlayerText = document.querySelector('.board___player-turn');
147 | if (currentPlayer() === 'X') {
148 | currentPlayerText.innerHTML = `
149 | Congratulations ${playerX.name}
150 | You are our winner!
151 | `;
152 | winner = true;
153 | removeCellClickListener();
154 | return true;
155 | } else {
156 | currentPlayerText.innerHTML = `
157 | Congratulations ${playerY.name}
158 | You are our winner!
159 | `;
160 | winner = true;
161 | removeCellClickListener();
162 | return true;
163 | }
164 | }
165 | });
166 |
167 | if (!winner) {
168 | checkIfTie();
169 | }
170 |
171 | return false;
172 | }
173 |
174 | function changeBoardHeaderNames() {
175 | if (!winner) {
176 | let currentPlayerText = document.querySelector('.board___player-turn');
177 | if (currentPlayer() === 'X') {
178 | currentPlayerText.innerHTML = `
179 | ${playerX.name}, you are up!
180 |
181 | `
182 | } else {
183 | currentPlayerText.innerHTML = `
184 | ${playerY.name}, you are up.
185 |
186 | `
187 | }
188 | }
189 | }
190 |
191 | function resetBoard() {
192 | console.log('resetting');
193 |
194 | gameBoard = ['', '', '', '', '', '', '', '', ''];
195 |
196 | let cellToAddToken = document.querySelectorAll('.letter');
197 | cellToAddToken.forEach( square => {
198 | square.textContent = '';
199 | square.parentElement.classList.remove('board__cell--winner');
200 | });
201 |
202 | turn = 0;
203 | winner = false;
204 |
205 | let currentPlayerText = document.querySelector('.board___player-turn');
206 | currentPlayerText.innerHTML = `
207 | ${playerX.name}, you are up!
208 |
209 | `
210 |
211 | addCellClickListener();
212 | }
213 |
214 | function addCellClickListener() {
215 | const cells = document.querySelectorAll('.board__cell');
216 | cells.forEach( cell => {
217 | cell.addEventListener('click', makeMove);
218 | });
219 | }
220 |
221 | function removeCellClickListener() {
222 | let allCells = document.querySelectorAll('.board__cell');
223 | allCells.forEach( cell => {
224 | cell.removeEventListener('click', makeMove);
225 | });
226 | }
227 |
228 |
--------------------------------------------------------------------------------