2
\$\begingroup\$

This code is able to create a tic tac toe game, user vs a computer. For every move the user makes in a check board 3⨉3 abc⨉123. The functions setup the instance of the game along with creating the computer position response after user input.

I would like help in refactoring code to use better mechanisms of loops instead of if statements.

var human = 'x'; 
var computer = 'o'; 
var compMove;
var turn = 0; 

var boardCheck; 
var a1; 
var a2;
var a3;
var b1;
var b2;
var b3;
var c1;
var c2;
var c3;

var checkWin; 
var xWin = false; 
var oWin = false; 
var winAlert; 

var newGame;
var clearBoard;



var newGame = function () {
    $('td').one('click', function (event) {
        if (turn == 0) {
            $(this).text(human);
            boardCheck();
            checkWin();
            turn == 1;
            compMove();
            boardCheck();
            checkWin();
        }
    });
};



$(document).ready(function () {
    newGame();
});


var compMove = function () {
    if (a1 == "" && ((a3 == "x" && a2 == "x") || (c3 == "x" && b2 == "x") || (c1 == "x" && b1 == "x"))) {
        $('#a1').text("o");
        turn = 0;
    } else {
      if (a2 == "" && ((a1 == "x" && a3 == "x") || (c2 == "x" && b2 == "x"))) {
        $('#a2').text("o");
        turn = 0;
        }
        else{
        if (a3 == "" && ((a1 == "x" && a2 == "x") || (c1 == "x" && b2 == "x") || (c3 == "x" && b3 == "x"))) {
            $('#a3').text("o");
            turn = 0;
        }
            else{
            if (c3 == "" && ((c1 == "x" && c2 == "x") || (a1 == "x" && b2 == "x") || (a3 == "x" && b3 == "x"))) {
                $('#c3').text("o");
                turn = 0;
        }
                else{
                if (c1 == "" && ((c3 == "x" && c2 == "x") || (a3 == "x" && b2 == "x") || (a1 == "x" && b1 == "x"))) {
                    $('#c1').text("o");
                    turn = 0;
        }
                    else{
                    if (c2 == "" && ((c3 == "x" && c1 == "x") || (a2 == "x" && b2 == "x"))) {
                        $('#c2').text("o");
                        turn = 0;
        }
                        else{
                        if (b1 == "" && ((b3 == "x" && b2 == "x") || (a1 == "x" && c1 == "x"))) {
                            $('#b1').text("o");
                            turn = 0;
        }
                            else{
                            if (b3 == "" && ((a3 == "x" && c3 == "x") || (b2 == "x" && b1 == "x"))) {
                                $('#b3').text("o");
                                turn = 0;
        }
                                else{
                                if (b2 == "" && ((a3 == "x" && c1 == "x") || (c3 == "x" && a1 == "x") || (b3 == "x" && b1 == "x") || (c2 == "x" && a2 == "x"))) {
                                    $('#b2').text("o");
                                    turn = 0;
        }
                                   else{ 
                                    if (b2 == "") {
                                        $('#b2').text("o");
                                        turn = 0;

                                    }
                                        else{
                                        if (a1 == "") {
                                            $('#a1').text("o");
                                            turn = 0;

                                    }
                                            else{
                                            if (c3 == "") {
                                            $('#c3').text("o");
                                            turn = 0;

                                    } 
                                                else {
                                                if (c2 == "") {
                                            $('#c2').text("o");
                                            turn = 0;

                                    }
                                                    else{
                                                    if (b1 == "") {
                                            $('#b1').text("o");
                                            turn = 0;

                                    }
                                                    }
                                                }
                                            }


                                        }
                                   }
                                }
                            }
                        }
                    }
                }
            }
        }
    }   
};



boardCheck = function () {
    a1 = $('#a1').html();
    a2 = $('#a2').html();
    a3 = $('#a3').html();
    b1 = $('#b1').html();
    b2 = $('#b2').html();
    b3 = $('#b3').html();
    c1 = $('#c1').html();
    c2 = $('#c2').html();
    c3 = $('#c3').html();
};


checkWin = function () { 
    if ((a1 == a2 && a1 == a3 && (a1 == "x")) || 
    (b1 == b2 && b1 == b3 && (b1 == "x")) || 
    (c1 == c2 && c1 == c3 && (c1 == "x")) || 
    (a1 == b1 && a1 == c1 && (a1 == "x")) || 
    (a2 == b2 && a2 == c2 && (a2 == "x")) || 
    (a3 == b3 && a3 == c3 && (a3 == "x")) || 
    (a1 == b2 && a1 == c3 && (a1 == "x")) || 
    (a3 == b2 && a3 == c1 && (a3 == "x")) 
    ) {
        xWin = true;
        winAlert();

    } else { 
        if ((a1 == a2 && a1 == a3 && (a1 == "o")) || 
        (b1 == b2 && b1 == b3 && (b1 == "o")) || 
        (c1 == c2 && c1 == c3 && (c1 == "o")) || 
        (a1 == b1 && a1 == c1 && (a1 == "o")) || 
        (a2 == b2 && a2 == c2 && (a2 == "o")) || 
        (a3 == b3 && a3 == c3 && (a3 == "o")) || 
        (a1 == b2 && a1 == c3 && (a1 == "o")) || 
        (a3 == b2 && a3 == c1 && (a3 == "o")) 
        ) {
            oWin = true;
            winAlert();

        } else { 
            if (((a1 == "x") || (a1 == "o")) && ((b1 == "x") || (b1 == "o")) && ((c1 == "x") || (c1 == "o")) && ((a2 == "x") || (a2 == "o")) && ((b2 == "x") || (b2 == "o")) && ((c2 == "x") || (c2 == "o")) && ((a3 == "x") || (a3 == "o")) && ((b3 == "x") || (b3 == "o")) && ((c3 == "x") || (c3 == "o"))) {
                alert("It's a tie!");
            }
        }
    }
};


var winAlert = function () {
    if (xWin == true) {
        alert("You won!");
        clearBoard(); 
    } else {
        if (oWin == true) {
            alert("Sorry, you lose!");
            clearBoard(); 
        }
    }
};



var clearBoard = $('#restart').click(function (event) {
    a1 = $('#a1').text("");
    b1 = $('#b1').text("");
    c1 = $('#c1').text("");
    a2 = $('#a2').text("");
    b2 = $('#b2').text("");
    c2 = $('#c2').text("");
    a3 = $('#a3').text("");
    b3 = $('#b3').text("");
    c3 = $('#c3').text("");
    xWin = false;
    oWin = false;
    newGame();
    location.reload(); 
});
\$\endgroup\$
1
\$\begingroup\$

I think Alex is absolutely right that it's helpful to be able to do your own code and to practice, but some people far smarter than I and most programmers have recommended reading others code, so I took a swing at it, and here's what I came up with.

First, I added a few new arrays to hold the piece values and the jQuery objects:

var pieces = [['','',''],['','',''],['','','']];
var jQueryObjects = [[$('#a1'), $('#a2'), $('#a3')], [$('#b1'), $('#b2'), $('#b3')], [$('#c1'), $('#c2'), $('#c3')]];

That allowed me to make a simpler checkBoard function:

var boardCheck = function () {
    for (var i = 0; i < 3; i++) {
        for (var j = 0; j < 3; j++) {
            pieces[i][j] = jQueryObjects[i][j].html();
        }
    }
}

Next, I added a function to check each row or column of the pieces array:

function check3(rowIndex, row) {
    //This function is the best way I could think of to abstractualize winning or approaching it. It will check along either a row or a column, based on a boolean paramater 'row', at a certain index, and will return the number of human squares minus the number of computer squares
    var count = 0;
    for (var i = 0; i < 3; i++) {
        if (row) {
            if (pieces[rowIndex][i] == human) {
                count += 1;
            } else if (pieces[rowIndex][i] == computer) {
                count -= 1;
            }
        } else {
            //Here, we switch the rowIndex and i paramaters, in case we're checking along columns
            if (pieces[i][rowIndex] == human) {
                count += 1;
            } else if (pieces[i][rowIndex] == computer) {
                count -= 1;
            }
        }
    }
    return count;
}

function firstEmpty(rowIndex, row) {
    //This supplements the other function. Here, we'll return the first empty index we can find along the given paramaters (same rowIndex, boolean row we had above)
    for (var i = 0; i < 3; i++) {
        if (row) {
            if (pieces[rowIndex][i] == '') {
                return i;
            }
        } else {
            if (pieces[i][rowIndex] == '') {
                return i;
            }
        }
    }
    //If we don't find one, return -1
    return -1;
}

I also did the same thing for the two diagonals:

function checkDiagonal(downRight) {
    //We pass in a down and right boolean so we can set it true or false to check both diagonals. This returns, again, the number of human pieces minus the number of computer pieces in a diagonal
    var count = 0;
    for (var i = 0; i < 3; i++) {
        if (downRight) {
            //If it's going down and to the right, the row and column indicies will be equal
            if (pieces[i][i] == human) {
                count += 1;
            } else if (pieces[i][i] == computer) {
                count -= 1;
            }
        } else {
            //Here, we do pieces[i][2-i], because that means it will go down one row and one row to the left, checking the other diagonal
            if (pieces[i][2 - i] == human) {
                count += 1;
            } else if (pieces[i][2 - i] == computer) {
                count -= 1;
            }
        }
    }
    return count;
}

function firstEmptyDiagonal(downRight) {
    //This is essentially the same as firstEmpty, just for diagonal lines
    for (var i = 0; i < 3; i++) {
        if (downRight) {
            if (pieces[i][i] == '') {
                return i;
            }
        } else {
            if (pieces[i][2 - i] == '') {
                return i;
            }
        }
    }
    return -1;
}

I also added a function to place a piece along a row and column index for the computer:

function compPlace(xIndex, yIndex) {
    jQueryObjects[xIndex][yIndex].text(computer);
    turn = 0;
    return;
}

This made everything else much simpler. Here's a new version of compMove:

var compMove = function () {
    // Look for human victory
    for (var i = 0; i < 3; i++) {
        //We iterate through the loop to find any rows that have 2 player pieces in them and no computer pieces in them
        if (check3(i, true) == 2) {
            //If we find one, then we place a computer piece on the first empty spot in row i, which we find with the firstEmpty function
            compPlace(i, firstEmpty(i, true));
            //We also need to return nothing. This is a way to get out of the function so the computer doesn't move twice
            return;
        }
        //Here, we do the same thing for columns
        if (check3(i, false) == 2) {
            //Again, we switch the row and column indicies
            compPlace(firstEmpty(i, false), i);
            return;
        }
    }

    //Now, we need to check for diagonals. Look at my comments for these functions to understand what it does
    if (checkDiagonal(true) == 2) {
        var emptyInd = firstEmptyDiagonal(true);
        compPlace(emptyInd, emptyInd);
        return;
    } 
    if (checkDiagonal(false) == 2) {
        var emptyInd = firstEmptyDiagonal(false);
        compPlace(emptyInd, emptyInd);
        return;
    }

    //This keeps the order for checking you created if there's not a potential victory, just slightly neater
    if (pieces[1][1] == "") {
        //Here, I just combine the compPlace and return functions
        return compPlace(1, 1);
    } else if (pieces[0][0] == "") {
        return compPlace(0, 0);
    } else if (pieces[2][1] == "") {
        return compPlace(2, 2);
    } else if (pieces[1][0] == "") {
        return compPlace(1, 0);
    }

    //There's still a possibility that there will be something else open, so we try to find any empty spot left
    for (var i = 0; i < 3; i++) {
        var emptyIndex = firstEmpty(i, true);
        if (emptyIndex != -1) {
            compPlace(i, emptyIndex)
        }
    }
};

And here's a new version for checkWin:

checkWin = function () { 
    for (var i = 0; i < pieces.length; i++) {
        // Here, we check if, along index i, there is a column or row with 3 player pieces
        if (check3(i, true) == 3 || check3(i, false) == 3) {
            xWin = true;
            winAlert();
            return;
        }
        if (check3(i, true) == -3 || check3(i, false) == -3) {
            oWin = true;
            winAlert();
            return;
        }
    }
    if (checkDiagonal(true) == 3 || checkDiagonal(false) == 3) {
        xWin = true;
        winAlert();
        return;
    }
    if (checkDiagonal(true) == -3 || checkDiagonal(false) == -3) {
        oWin = true;
        winAlert();
        return;
    }
};

Finally, here's clearBoard:

var clearBoard = $('#restart').click(function (event) {
    for (var i = 0; i < 3; i++) {
        for (var j = 0; j < 3; j++) {
            pieces[i][j] == jQueryObjects[i][j].text("");
        }
    }
    xWin = false;
    oWin = false;
    newGame();
    location.reload(); 
});

Now, I'm sure a number of people will say you won't learn anything from this. And if you copy and paste it, that's absolutely true. But I tried to comment everything that I could, so if you read all of those, follow all of the code, and understand how it fits together, that will be very helpful for you. Plus, I left you one thing for you to do on your own: You'll notice that the check3 and checkDiagonal functions have very similar methods in multiple places within both in order to add one if it's a player square, subtract one if it's a computer square. I think it would be incredibly helpful for you to write a function that can replace these lines of code:

if (pieces[rowIndex][i] == human) {
    count += 1;
} else if (pieces[rowIndex][i] == computer) {
    count -= 1;
}

with just one call to that function. Also make sure you can use that same function in the other three places that code that's very similar to this appears. I really hope you found this helpful. Please let me know if you have any questions.

\$\endgroup\$
0
\$\begingroup\$

I believe your best option in this case would be to transform your individual, repetitive variables (a1 ... c3) into a matrix, or multi-dimensional array. By doing so, you'll be able to take advantage of the power of loops, most likely you'll want to explore $.each(). (Explore the different jQuery traversing methods too)

Keep in mind while you're refactoring your code that you can dynamically reference your HTML elements. For example, you can replace $('#a1').html(); with a more general $('#a' + i).html();. Where i is a incremental value given by some original array or a for loop. Here's the MDN docs for loops and iterations in JS for you to check out.

I'm refraining from giving too much code because I think this is a great opportunity to learn about the different looping functions in both JavaScript and jQuery. If you'd prefer to just see code, comment and I'll edit this post!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.