Jazzy663
Jazzy663

Reputation: 105

JavaScript TicTacToe if... Winner detection

I just have a small problem.

The final assignment in my computer science class is to write a game of Tic-Tac-Toe in JavaScript. The game works by clicking on cells within a table and using a function() to switch the img source (a blank image until clicked on) to an X or an O. It's just about done, but there's one thing I can't get.

The game is played, obviously, by two players. The last thing I need is to get it (a function) to detect when a player wins and kill the script when this happens. I just can't figure this out. I know it involves if statements, and I've attempted several different codes, but everything I code just breaks the game.

I do see that this question has been asked before, and I've tried clicking on several links in the 'similar questions' box to the right, but all of them are in languages I haven't learned anything about (such as C#).

Here is my script:

<script type="text/javascript" charset="ISO-8859-1">
function placeMove(value)
{
    if (document.getElementById(value).src.indexOf("FC")!=-1) //"FC" because of the FC contained in the file name. I intended for it to stand for "free cell", for easy indentification.
    {
        var turn = document.getElementById("playerturn").innerHTML;
        if (turn == "X")
        {
            document.getElementById(value).src="../Images/TicTacToeX.jpg";
            document.getElementById("playerturn").innerHTML="O";
        }
        if (turn == "O")
        {
            document.getElementById(value).src="../Images/TicTacToeO.jpg";
            document.getElementById("playerturn").innerHTML="X";
        }
    }
    else
    {
        window.alert("Tile is in use. Please select another tile.");
    }
}
function detectWin()
{

}
</script>

This is the table that the game takes place in, if it helps:

<table class="gametable" id="gametable" border="1">
<tr>
    <td><img onclick="placeMove('r1c1')" id="r1c1" class="blank" alt="blank space" src="../Images/TicTacToeFC.jpg"></img></td>
    <td><img onclick="placeMove('r1c2')" id="r1c2" class="blank" alt="blank space" src="../Images/TicTacToeFC.jpg"></img></td>
    <td><img onclick="placeMove('r1c3')" id="r1c3" class="blank" alt="blank space" src="../Images/TicTacToeFC.jpg"></img></td>
</tr> 
<tr>
    <td><img onclick="placeMove('r2c1')" id="r2c1" class="blank" alt="blank space" src="../Images/TicTacToeFC.jpg"></img></td>
    <td><img onclick="placeMove('r2c2')" id="r2c2" class="blank" alt="blank space" src="../Images/TicTacToeFC.jpg"></img></td>
    <td><img onclick="placeMove('r2c3')" id="r2c3" class="blank" alt="blank space" src="../Images/TicTacToeFC.jpg"></img></td>
</tr>
<tr>
    <td><img onclick="placeMove('r3c1')" id="r3c1" class="blank" alt="blank space" src="../Images/TicTacToeFC.jpg"></img></td>
    <td><img onclick="placeMove('r3c2')" id="r3c2" class="blank" alt="blank space" src="../Images/TicTacToeFC.jpg"></img></td>
    <td><img onclick="placeMove('r3c3')" id="r3c3" class="blank" alt="blank space" src="../Images/TicTacToeFC.jpg"></img></td>
</tr>
</table>

The assignment is due tomorrow, and all I need is this last little bit of code.

Any help is greatly appreciated. This is my last assignment of the semester.

Thanks,

Kyle

EDIT: After some suggestions, here is what I've been trying. Entering this code will not allow the game to be played. If it's just a matter of missing semicolons, please forgive me. It involves setting the img source in each cell to a variable, and checking to see if those variables match.

There are 9 variables I created, one for each cell/X or O image:

var pic1 = document.getElementById("r1c1").src;
var pic2 = document.getElementById("r1c2").src;
var pic3 = document.getElementById("r1c3").src;
var pic4 = document.getElementById("r2c1").src;
var pic5 = document.getElementById("r2c2").src;
var pic6 = document.getElementById("r2c3").src;
var pic7 = document.getElementById("r3c1").src;
var pic8 = document.getElementById("r3c2").src;
var pic9 = document.getElementById("r3c3").src;

This is at the very end of the first script:

function detectWin()
{
if (var pic1 == var pic2 && var pic3)
{
window.alert("Game won. Please reset the game.");
}
}

This obviously only detects a win for the first row. If I can get this to work, I'll know what the rest of the code will have to be. I still don't know how to kill the script, though.

Edited to remove some unnecessary remarks and grammatical errors.

Upvotes: 5

Views: 35546

Answers (7)

Gregor
Gregor

Reputation: 388

Assume 'squares' is a Array of Arrays of square size (e.g. 3), where 0 for not filled , 1 for player 1 (x) and -1 for the other player (O).

e.g. for size 3 by 3

let squares= Array(3).fill(Array(3).fill(0))

would be the starting board.

The following works for board size 1 by 1 ,2 by 2, 3 by 3, 4 by 4 ... where for a board of size n, n consecutive x's or o's need to be in a row, column or diagonal.

Returns 0 if nobody won yet and 1 for player 1 and -1 for the other player.

First defining two helper functions to make the code more readable.

const add = (a, b) => a + b

function sum(array){  
   return array.reduce(add);
}

function calculateWinner(squares) {
  // check for horizontal wins along rows and diagonals
  let winner = calculateWinnerInner(squares);
  if (winner !== 0) return winner;
  // check for possible vertical wins as well
  const stranspose = squares.map((col, i) => squares.map(row => row[i]));
  return calculateWinnerInner(stranspose);
}
 
function calculateWinnerInner(squares) {
  for (let r = 0; r < squares.length; r++) {
    if (squares[r].length === sum(squares[r])) {
      return 1;
    }
    if (squares[r].length === - sum(squares[r])) {
      return -1;
    }
  }

  const diagonal = squares.map((row, r) => squares[r][r]);

  if (squares[0].length === sum(diagonal)) {
    return 1;
  }
  if (squares[0].length === -sum(diagonal)) {
    return -1;
  }
  
 const len=squares.length;
 const crossdiagonal = squares.map((row, r) => squares[r][len-r-1]);

 if (squares[0].length === sum(crossdiagonal)) {
  return 1;
 }
 if (squares[0].length === -sum(crossdiagonal)) {
  return -1;
 }

  return 0;
}

Upvotes: 2

Alex Stancu
Alex Stancu

Reputation: 53

My version, but it lacks the tie game check:

var table = [
    ['', '', ''],
    ['', '', ''],
    ['', '', '']
]

function GameIsOver(player) {
    var result = true;
    for (var j = 0; j < 3; j++) {     //first diagonal
        result = result && (table[j][j] == player);
    }
    if (result) {
        return gameResult = {
            result: result,
            player: player
        };
    }
    result = true;
    for (var j = 0; j < 3; j++) {  //second diagonal
        result = result && (table[2 - j][j] == player);
    }
    if (result) {
        return gameResult = {
            result: result,
            player: player
        };
    }
    for (var k = 0; k < 3; k++) {
        result = true;
        for (var j = 0; j < 3; j++) {      //lines 
            result = result && (table[k][j] == player);
        }
        if (result) {
            return gameResult = {
                result: result,
                player: player
            };
        }
        result = true;
        for (var j = 0; j < 3; j++) {      //colums
            result = result && (table[j][k] == player);
        }
        if (result) {
            return gameResult = {
                result: result,
                player: player
            };
        }
    }
    return false;
}

Upvotes: 2

vsync
vsync

Reputation: 130085

Depending on which way you prefer the board to be represented.

I've coded a Tic-Tac-Toe game (in React), and chose a flat array, because I believe it is easier to work with (also in other aspects of the game)

I've authored my win-detection code in a manner where all possible winning positions are pre-defined, and the Array representing the game board is converted into an array of two strings (one per player), so each of the strings has the cells indices of which this particular player had selected.

/**
 * 
 * @param {Array} board - flat array representation of the board, 
 * from right to left, top to bottom, Ex.
 * [1,2,2,1,1,2,1,2,1] 
 */
function checkWin(board){
  // all possible winning combinations (of cells filled by the same player)
  const winMap = [123, 456, 789, 147, 258, 369, 357, 159]

  // convert the board to array represening the filled cells, per player.
  // each array item is a string of only the cells (indices) filled by a player
  const moves = board.reduce((players, v, i) => {
    if(v) players[v-1] += i+1
    return players
  }, ['', ''])

// console.log(JSON.stringify(moves))

  // find & return the winning combination
  const winningMove = winMap.find(comb =>
    moves.some(m => // there are only 2 sets of moves, one for each player
      // break down the current combination to array and check if every item exists
      // also in the current set of moves. quit on first match.
      comb.toString().split('').every(c => m.includes(c))
    )
  )

  return winningMove ?
    { // get the first number of the winning-move, 
      // substract 1 from it, and use as index to find which
      // player played that move from the board Array
      player: board[winningMove.toString()[0] - 1],
      move: winningMove
    } 
    : false
}


// sample tests:
[
  [1,1,1,2,2,2],       // player 1 wins, horizontal, row 1
  [1,2,2,1,,,1],       // player 1 wins, vertical, col 1
  [2,1,1,1,2,1,2,1,2], // player 2 wins, diagonal to right
  [1,1,2,1,2,1,2,1,2], // player 2 wins, diagonal to left
  [1,2,1,1,2,1,2,1,2]  // no win
].forEach(board => console.log(
  checkWin(board)
))

Upvotes: 3

A few more optimizations.

  1. Add mechanism to identify which "lines" on a board can never be winning because they contain at least 1 of both players' pieces. These are cached to prevent future checks of that line and improve speed.
  2. Ignore expensive win check if there are not enough pieces for a win to be possible (while following the rules). For example, the soonest a player could win on a 3x3 board is when marking the 5th square.

Upvotes: 1

WooCaSh
WooCaSh

Reputation: 5212

I write from begining code where you can check my version of checking who win game. Dont downvote if my version of game is another than you. I just want to show you how you can write code without knowledge of any algorithm.

You just need motivation. Don't give up next time so fast.

jsFiddle

My version for checking who win:

var checkResult = function(){
    $("table tr").each(function(i, val){
        $(this).find('td').each(function(j, val2){
            arr[i][j] = parseInt($(this).attr("data-points"));
        });
    });

    for(var i = 0; i<3;i++){
        var rowSum = 0;
        for(var j = 0; j<3;j++){
            rowSum += arr[i][j];
        }
        if(rowSum === 3)
            alert("Circle WIN!");
        else if(rowSum === -3)
            alert("Cross WIN!");
    }

    for(var i = 0; i<3;i++){
        var colSum = 0;
        for(var j = 0; j<3;j++){
            colSum += arr[j][i];
        }
        if(colSum === 3)
            alert("Circle WIN!");
        else if(colSum === -3)
            alert("Cross WIN!");
    }

    if(arr[0][0] + arr[1][1] + arr[2][2] === 3)
        alert("Circle WIN!");
    else if(arr[0][0] + arr[1][1] + arr[2][2] === -3)
        alert("Cross WIN!");

    if(arr[2][0] + arr[1][1] + arr[0][2] === 3)
        alert("Circle WIN!");
    else if(arr[2][0] + arr[1][1] + arr[0][2] === -3)
        alert("Cross WIN!");
};

Upvotes: 8

Jordan NG
Jordan NG

Reputation: 96

I see you said you have searched Stack Overflow for similar questions. I understand how being new to programming, it's not easy to read something in another language but the fundamental idea is there. Here is a link where this is already done: Algorithm for Determining Tic Tac Toe Game Over.

That being said, there are basically 3 ways to win in the game, and you are headed in the right direction with your vars.

The three ways to win are

  1. 3 matches across
  2. 3 matches down
  3. 3 matches diagonal.

The easy parts are the rows and columns, simply for loop through each row/column and see if you get three matches, if you do, then you can declare a winner.

Non-efficient Pseudo-code Example:

if pic1, pic2, and pic3 are the same
alert user X or O has won
end the game.

Repeat for row 2, and row 3.

if pic1, pic4, and pic7 are the same
alert user X or O has won
end the game.

Repeat for column 2 and 3.

The diagonal can be done in a simple fashion as well, without using the two dimensional array in the example. There are basically only two diagonal win possibilities:

  1. var 1, 5, 9 match
  2. var 3, 5, 7 match.

Anything else you can end the game in a draw. I would recommend using the counter as shown in the link example.

Good luck! -- edit for clarity --

Upvotes: 2

Bucket
Bucket

Reputation: 7521

I can't in good faith give you the answer to this, but I can walk you through one way of thinking about this problem; by no means is it the best solution, but it could get you at least a start.

To detect a winner, it must be true that one of the following holds:

  1. All three cells in any row are the same
  2. All three cells in any column are the same
  3. All three cells traversing the board diagonally are the same.

Fortunately, you can loop over your table elements to make this check easily.

The if statement you provided is flawed. You do not need to precede your variables with var after you have declared them already. Additionally, your use of && is wrong. What this will do is check to see if the left statement is true, which in this case is var pic1 == var pic2, then checks if the right statement is also true, which is simply var pic3. By itself, this is not a good statement, since it will be automatically cast to Javascript's best interpretation of a boolean, which in this case is true as long as pic3 is defined. Instead, you will need something like if(pic1 == pic2 && pic2 == pic3), but I would use something besides comparing the images, which is what you're doing. You could change the class of each cell to "X" or "O" depending on which piece goes there, which would be a little more elegant.

Class names can be accessed via the .className call:

<div id="foo" class="bar"></div>

<script>
    document.getElementById("foo").className; //returns "bar"
</script>

Here is a more in-depth description of how to change an element's class.

Upvotes: 2

Related Questions