# Tic Tac Toe implemented With jQuery table pseudo-buttons

I made this code myself and was experimenting with making buttons from a table, and decided to make a game of Tic-Tac-Toe with it.

The code loads from JSON a grid, and then makes a Tic-Tac-Toe game with populated as a 3x3 table with empty spaces. This takes place in the getJSON. Inside each cell are events, namely the click event, which simulates a turn.

It is complete with a home page and styling. Perhaps the most notable feature is the border-style: inset and border-style: outset, which make tds appear as buttons, which can be utilized with jQuery.

// retrieve game state from json, and append a clickable tic tac toe table
$.getJSON('asset/json/tictactoe.json', function(data) { // add a table to the grid (3x3)$("#grid").append('<table/>');
var table = $("#grid").find("table"); for (var i=0; i<data.length; ++i) { // add a row in the table$(table).append('<tr/>');
for (var j=0; j<data[i].length; ++j)
{
var row = $(table).children()[i];$(row).append('<td/>');
var td = $(row).children()[j]; // add the 2D grid from JSON object$(td).html(data[i][j]);

//this is used to assist in checking game state for the end of the game
$(td).attr("id","b"+i+j); // add hover feature to table buttons$(td).hover(function() { $(this).toggleClass("hovered"); }); // make table buttons clickable$(td).click(
function()
{
var X = "&nbsp;X&nbsp;"; var O = "&nbsp;O&nbsp;"; var V = "&nbsp;&nbsp;&nbsp;"; var D = "DRAW!";
if (($("#winner").html() == X) || ($("#winner").html() == O) || ($("#winner").html() == D)) { window.alert('game is complete.'); return true; } if ($(this).html() != V)
{
return false;
}
var turn = $("#turn").html();$(this).html(turn);
turn = (turn == O) ? X : O;
$("#turn").html(turn);$(this).toggleClass("pushed");
$("#winner").trigger("click"); } ); } } } ); // check game state when trigger or if user clicks the winner box$("#winner").click(
function() {

// initialize variables
var board = [[[],[],[]],[[],[],[]],[[],[],[]]];
var X = "&nbsp;X&nbsp;"; var O = "&nbsp;O&nbsp;"; var V = "&nbsp;&nbsp;&nbsp;"; var D = "DRAW!";

// if there is already a winner, or it is drawn, then do not bother
if (($("#winner").html() == X) || ($("#winner").html() == O) || ($("#winner").html() == D)) return true; // retrieve board from td's. could be done by grabbing #grid var occupied = 0; for (var i=0; i<board.length; ++i) for (var j=0; j<board[i].length; ++j) board[i][j] =$("#"+"b"+i+j).html();

var winner = V;

// check for non-diagonal wins
for (var i=0; i<board.length; ++i)
{
// horizontally
if (((board[i][0] == X) || (board[i][0] == O)) && ((board[i][0] == board[i][1]) && (board[i][1] == board[i][2])))
{
winner = board[i][0];
break;
}

// vertically
if (((board[0][i] == X) || (board[0][i] == O)) && (board[0][i] == board[1][i]) && (board[1][i] == board[2][i]))
{
winner = board[0][i];
break;
}
}

// if no winner yet
if (winner == V)
{

// check for diagonal wins
if ((board[0][0] == X) || (board[0][0] == O))
if ((board[0][0] == board[1][1]) && (board[1][1] == board[2][2]))
winner = board[0][0];
if ((board[0][2] == X) || (board[0][2] == O))
if ((board[0][2] == board[1][1]) && (board[1][1] == board[2][0]))
winner = board[0][2];

}

// is there a winner?
if (winner != V)
{
$("#turn").html("Game Over");$("#winner").html(winner);
return true;
}

// count how many suqares are occupied
var occupied = 0;
for (var i=0; i<board.length; ++i)
for (var j=0; j<board[i].length; ++j)
if ((board[i][j] == X) || (board[i][j] == O)) ++occupied;

// is the game drawn?
if (occupied >= 9)
{
$("#turn").html("Game Over");$("#winner").html(D);
return true;
}
}
);
#grid table {
border-spacing: 3px;
}

#grid table > tr > td {
border-color: #92A5BD;
border-width: 3px;
border-style: outset;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 16px;
text-align: center;
cursor: pointer;
}

#grid table > tr > td.hovered {
border-style: inset; /* button appearance w/ border style */
background-color: #F99;
}

#grid table > tr > td.pushed {
border-style: outset; /* button appearance w/ border style */
background-color: #999;
}
<!doctype html>
<html>
<body>
<div id="grid"><!-- tic-tac-toe board is populated here -->
</div>
<div id="game">
<table>
<tr>
<td>Turn: </td><td id="turn">&nbsp;X&nbsp;</td>
</tr>
<tr>
<td>Winner: </td><td id="winner">&nbsp;&nbsp;&nbsp;</td>
</tr>
</table>
</div>
<!--
<div>
<textarea id="debug" rows="10" cols="50"></textarea>
</div>
-->
<script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="asset/js/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="asset/js/tictactoe.js"></script>
</body>
</html>

It can perhaps be improved upon with gifs, and externalizing variables somehow, namely the X,O,V,D variables. Also, what is annoying is that the td's don't stay still when the text inside changes, which is why I used the "nbsp" character. A better choice could've been made. All in all, decent result.

I found some improvements to make to your Tic Tac Toe game. I think that makes a great example for many things.

Looking at the JS, the first thing I noticed was the .getJSON(), mainly to dynamically create the play grid. I wondered if the intent was to pre-fill the grid, like a saved game, possibly. But I found nothing to save... So I passed on it.

The famous JSON was not provided... So I had to look further anyway to re-create your game.

Next thing I found, and it took quite long to figure out, was the use of the #winner element, which had a click handler on it... But was empty.

Then I found out that its use was because of a programmatically triggered click, just to continue on the next part of what I consider to being part of the same function.

And I really found it strange...

So I took the decision to get rid of the JSON. But not right now. I had to first make it run to actually see the beast. I found a "definition" of it, so I just used it:

var board = [[[],[],[]],[[],[],[]],[[],[],[]]];


Then grid appeared. And I understood how you where using the &nbsp; you mentioned. So I filled the all the level 3 depth arrays with &nbsp;&nbsp;&nbsp;, the V variable.

So that was my start.

I took several decisions then:

1. I got rid of the JSON (I'm repeating, just to have it listed here)
2. I got rid of the dynamic table creation... A Tic Tac Toe grid always is 3X3, so why not just place that in the HTML markup?
3. I apply CSS width and height to the cells. You mentionned the "annoying" effect of the cells not staying still. That fixed it. And all the &nbsp; were then useless.
4. So I got rid of the X,O,V,D variables too...
5. I made it only one function, a click handler on the cells.
6. I also got rid of the hover handler, which CSS does marvelously.
7. I used a boolean variable to toggle the turns.
8. I used .data() to set/get the X or O on the cells, instead of .html(). That can enable one to upgrade the game a bit more with some images. I didn't, but it is now possible since the "value" is stored otherwise.

So... Quite a lot, right?

Let's see the code and the comments within:

// Lookup for the cell collection
var cells = $("#grid td"); // Lookup for the game indicators turn_indicator =$("#turn");
winner_indicator = $("#winner"); // Player turn boolean O = true, X = false var turn = false; // X starts // Cell click handler cells.on("click",function(){ // Who's the active/next player now? var active_player = (turn)?"O":"X"; var next_player = (turn)?"X":"O"; // If cell is occupied. if ($(this).data("player") != null){
return;
}

// Check if the game's already over
if ((winner_indicator.html() != "")){
return;
}

// Mark the cell and change the class to show it's now occupied.
$(this).data("player",active_player).html(active_player).toggleClass("pushed"); // Switch turn. turn = !turn; turn_indicator.html(next_player); // // Check for a victory condition from the cells data // // Horizontally. for(i=0;i<3;i++){ var k = 3*i; if( cells.eq(k).data("player") == cells.eq(k+1).data("player") && cells.eq(k).data("player") == cells.eq(k+2).data("player") && cells.eq(k).data("player") != null ){ // Coulour wining cells cells.eq(k).addClass("winning_cell"); cells.eq(k+1).addClass("winning_cell"); cells.eq(k+2).addClass("winning_cell"); // Announce the winner! turn_indicator.html("Game Over!"); winner_indicator.html(cells.eq(i).data("player")); console.log("-") return true; } } // vertically for(i=0;i<3;i++){ if( cells.eq(i).data("player") == cells.eq(i+3).data("player") && cells.eq(i).data("player") == cells.eq(i+6).data("player") && cells.eq(i).data("player") != null ){ // Coulour wining cells cells.eq(i).addClass("winning_cell"); cells.eq(i+3).addClass("winning_cell"); cells.eq(i+6).addClass("winning_cell"); // Announce the winner! turn_indicator.html("Game Over!"); winner_indicator.html(cells.eq(i).data("player")); console.log("|"); return true; } } // Diagonal "\" if( cells.eq(0).data("player") == cells.eq(4).data("player") && cells.eq(0).data("player") == cells.eq(8).data("player") && cells.eq(0).data("player") != null ){ // Coulour wining cells cells.eq(0).addClass("winning_cell"); cells.eq(4).addClass("winning_cell"); cells.eq(8).addClass("winning_cell"); // Announce the winner! turn_indicator.html("Game Over!"); winner_indicator.html(cells.eq(0).data("player")); console.log("\\") return true; } // Diagonal "/" if( cells.eq(2).data("player") == cells.eq(4).data("player") && cells.eq(2).data("player") == cells.eq(6).data("player") && cells.eq(2).data("player") != null ){ // Coulour wining cells cells.eq(2).addClass("winning_cell"); cells.eq(4).addClass("winning_cell"); cells.eq(6).addClass("winning_cell"); // Announce the winner! turn_indicator.html("Game Over!"); winner_indicator.html(cells.eq(2).data("player")); console.log("/") return true; } // Is there still empty cells? if($(".pushed").length==9){
turn_indicator.html("Game Over!");
winner_indicator.html("Drawn!");
}

// Game continues...
});
#grid{
border-spacing: 3px;
}
#grid td{
border-color: #92A5BD;
border-width: 3px;
border-style: outset;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 16px;
text-align: center;
cursor: pointer;
width: 3em;
height: 3em;
}
#grid td:hover{
border-style: inset; /* button appearance w/ border style */
background-color: #F99;
}
.pushed{
border-style: outset; /* button appearance w/ border style */
background-color: #999;
}
.winning_cell{
background-color: #1FC01F !important;
}
.indicator{
display:inline-block;
min-width:3em;
}
<!doctype html>
<html>
<body>
<table id="grid">
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
</table>
<div class="indicator">Turn:</div><div id="turn" class="indicator">X</div><br>
<div class="indicator">Winner</div><div id="winner" class="indicator"></div>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="asset/js/tictactoe.js"></script>
</body>
</html>

So as you can see... The function reads now from top to bottom and it is easy to understand since it is sequential. The use of return; here, is just to stop the code execution. There is absolutely nothing returned nowhere. So just return; is good. The false is useless in a click handler, except if that is to stop an event. But in this case, it is better to use .preventDefault(). In some other cases, you can return to the caller a boolean, a value, an object, etc....

From the user click, everything executes until that hits a return;. That saves from a big bunch of nested if/else... or calls to a function defined above or below. When you can do it sequentially (it's not always the case), do it!

So that reduces the code a lot. I used 7 if against 13... And 2 for loops against 7.

And I added a "winning cells colouring" feature! ;)

Important to mention, you had the jQuery library loaded twice. It's always odd to do so. It may work... May not.

I reduced the amount of jQuery lookups on the DOM to the minimum. Every time you use $("some_selector"), jQuery works in the background to get you the element's object. If you already have it, you can save on "resources". That is a good habit to develop... It's probably why they call it a "good practice". And when you have a jQuery object in variable, like var table =$("#grid").find("table");, no need to perform a lookup again on it, like $(table). That's an horrible resource waste. ;) -- I had to edit... So why not give out a trick of mine, to avoid stupid errors in loops, particularly on nested loops. Don't use j as loop index!! It's terribly too similar to i and so hard to find! I use k... The letter after. ;) Louys Patrice Bessette already mentioned in the answer many ways to clean up the code - including eliminating the table creation in JavaScript. However, if that is something that needed to be done, I suggest creating the table and adding all rows and cells before adding the table to the DOM. So these lines : $("#grid").append('<table/>');
var table = $("#grid").find("table");  Can be updated to a single call to the jQuery element creation function: var table =$("<table/>");


That way, we keep a reference the element that was just created and don't have to query the DOM for a reference to it. The same is true for adding rows (and cells). The following lines:

$(table).append('<tr/>'); for (var j=0; j<data[i].length; ++j) { var row =$(table).children()[i];
$(row).append('<td/>');  Can be replaced with these: for (var i=0; i<data.length; ++i) { var row =$('<tr/>');
table.append(row);


And the table cells can be added directly to row:

var td = $('<td/>'); row.append(td);  Then there is no need to wrap td in calls to the jQuery Selector function (i.e. $()).

And after the loop, then add that table element to the DOM:

$("#grid").append(table);  See that portion updated in the snippet below. Note that it doesn't have the AJAX call because there is no local endpoint to supply that. Instead, the function is called with the sample data structure Louys suggested. If you weren't using jQuery, a DocumentFragment could be used to create the elements and add them all at once to the page. See the section "Use a DOM DocumentFragment in this article for more information about that. // retrieve game state from json, and append a clickable tic tac toe table var board = [[[],[],[]],[[],[],[]],[[],[],[]]]; const winnerElement =$("#winner");
const turnElement = $('#turn'); //$.getJSON('asset/json/tictactoe.json',
(function processData(data)
{
// add a table to the grid (3x3)
//$("#grid").append('<table/>'); //var table =$("#grid").find("table");
var table = $("<table/>"); for (var i=0; i<data.length; ++i) { // add a row in the table //table.append('<tr/>'); var row =$('<tr/>');
table.append(row);
for (var j=0; j<data[i].length; ++j)
{
//var row = table.children()[i];
var td = $('<td/>'); row.append(td); //var td =$(row).children()[j];

// add the 2D grid from JSON object
td.html(data[i][j]);

//this is used to assist in checking game state for the end of the game
td.attr("id","b"+i+j);

// add hover feature to table buttons
td.hover(function() { $(this).toggleClass("hovered"); }); // make table buttons clickable td.click( function() { var X = "&nbsp;X&nbsp;"; var O = "&nbsp;O&nbsp;"; var V = "";/*"&nbsp;&nbsp;&nbsp;";*/ var D = "DRAW!"; if ((winnerElement.html() == X) || (winnerElement.html() == O) || (winnerElement.html() == D)) { window.alert('game is complete.'); return true; } if ($(this).html() != V)
{
return false;
}
var turn = turnElement.html();
$(this).html(turn); turn = (turn == O) ? X : O; turnElement.html(turn);$(this).toggleClass("pushed");
winnerElement.trigger("click");
}
);
}
}
$("#grid").append(table); } )(board); // check game state when trigger or if user clicks the winner box$("#winner").click(
function() {

// initialize variables
var board = [[[],[],[]],[[],[],[]],[[],[],[]]];
var X = "&nbsp;X&nbsp;"; var O = "&nbsp;O&nbsp;"; var V = "&nbsp;&nbsp;&nbsp;"; var D = "DRAW!";

// if there is already a winner, or it is drawn, then do not bother
if ((winnerElement.html() == X) || (winnerElement.html() == O) || (winnerElement.html() == D)) return true;

// retrieve board from td's. could be done by grabbing #grid
var occupied = 0;
for (var i=0; i<board.length; ++i)
for (var j=0; j<board[i].length; ++j)
board[i][j] = \$("#"+"b"+i+j).html();

var winner = V;

// check for non-diagonal wins
for (var i=0; i<board.length; ++i)
{
// horizontally
if (((board[i][0] == X) || (board[i][0] == O)) && ((board[i][0] == board[i][1]) && (board[i][1] == board[i][2])))
{
winner = board[i][0];
break;
}

// vertically
if (((board[0][i] == X) || (board[0][i] == O)) && (board[0][i] == board[1][i]) && (board[1][i] == board[2][i]))
{
winner = board[0][i];
break;
}
}

// if no winner yet
if (winner == V)
{

// check for diagonal wins
if ((board[0][0] == X) || (board[0][0] == O))
if ((board[0][0] == board[1][1]) && (board[1][1] == board[2][2]))
winner = board[0][0];
if ((board[0][2] == X) || (board[0][2] == O))
if ((board[0][2] == board[1][1]) && (board[1][1] == board[2][0]))
winner = board[0][2];

}

// is there a winner?
if (winner != V)
{
turnElement.html("Game Over");
winnerElement.html(winner);
return true;
}

// count how many suqares are occupied
var occupied = 0;
for (var i=0; i<board.length; ++i)
for (var j=0; j<board[i].length; ++j)
if ((board[i][j] == X) || (board[i][j] == O)) ++occupied;

// is the game drawn?
if (occupied >= 9)
{
turnElement.html("Game Over");
winnerElement.html(D);
return true;
}
}
);
#grid table {
border-spacing: 3px;
}

#grid table > tr > td {
border-color: #92A5BD;
border-width: 3px;
border-style: outset;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 16px;
text-align: center;
cursor: pointer;
}

#grid table > tr > td.hovered {
border-style: inset; /* button appearance w/ border style */
background-color: #F99;
}

#grid table > tr > td.pushed {
border-style: outset; /* button appearance w/ border style */
background-color: #999;
}
<!doctype html>
<html>
<body>
<div id="grid"><!-- tic-tac-toe board is populated here -->
</div>
<div id="game">
<table>
<tr>
<td>Turn: </td><td id="turn">&nbsp;X&nbsp;</td>
</tr>
<tr>
<td>Winner: </td><td id="winner">&nbsp;&nbsp;&nbsp;</td>
</tr>
</table>
</div>
<!--
<div>
<textarea id="debug" rows="10" cols="50"></textarea>
</div>
-->
<script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="asset/js/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="asset/js/tictactoe.js"></script>
</body>
</html>