Lesson 16 AI logic 3, favicon

Mark Harder, 25 January 2019

Tic Tac Toe Game AI Logic Part 3 of 3

Let’s Continue Building our AI Player Code.

Each step might return an array of possible moves which we will store in a variable named PlayLocations. If none of the steps choose 1 or more locations, then the last step (6) will add all of the available locations.

// Play the next players move Lesson16-Final
function AIPlayerMove() {
    // Strategy Steps - assumption  of all steps is that we test only empty locations

    // 1. Look for a winning location, by testing for a win.  
    // 2. Look for a blocking move, by testing as other players for a win.
    // 3. If the center location is available, choose it.
    // 4. Setup for a double potential win.
    // 5. check for available opposing corners.  
    // 6. randomly select a location from the empty spots
};

In part 2 we covered steps 3 and 6.
In steps 6 we created an array from all the empty board locations. Then we randomly choose one of the empty board locations.
For step 1, 2 we also need to look at all of the available locations, and then test if it’s either a winning location (step 1) or a blocking location (step 2). There are many ways to loop through all the board locations and test, but we are again going to use the array.reduce() method. Let’s walk through step 1 and test to see if there are available winning locations.

// Play the next players move Lesson16-Final
function AIPlayerMove() {
    let PlayLocations = [];
    let PlayLocation = { row: null, col: null };
    let NextPlayer = gameState.Next;
    let LastPlayer = gameState.Next === "X" ? "O" : "X";

    // Strategy Steps - assumption  of all steps is that we test only empty locations

    // 1. Look for a winning location, by testing for a win.  
    PlayLocations = gameState.board.reduce((accumulator, rowArray, rowIndex) => {
        return rowArray.reduce((cellAccumulator, cell, colIndex) => {
            if (cell === "") {
                // Test each empty location by setting it as then testing for a win, making sure to clean it out after.
                gameState.board[rowIndex][colIndex] = NextPlayer;
                if (TestForWin(gameState.board) === NextPlayer) {
                    // Add this location as a winning location.
                    cellAccumulator.push( { row: rowIndex, col: colIndex} );
                };
                gameState.board[rowIndex][colIndex] = "";
            };
            return cellAccumulator;
        }, accumulator);
    }, []);
  1. At the outer level We see gameState.board.reduce()
  2. This call starts with an empty array [] which comes into the function as the parameter accumulator
  3. The next parameter rowArray is the second array of our board 2D array. In order to test each cell in our board, we need to process each rowArray with a second reduce() call.
  4. So the return response inside the first reduce() call will be the results of the second reduce call.
  5. You can think of it this way, inside the second reduce() we look at each cell, evaluate if we want to add it to our final answer array and if yes push it onto the accumulator that we return to the next function iteration.
  6. When a winning position is found, it is added to the cellAccumulator, which is then returned to the gameState.board.reduce((accumulator which is then returned as a result array and becomes PlayLocations
  • How do we know if an empty cell is a winning position? We already wrote a function to test the board for a win, so we can reuse that function. We will (1) temporarily set the position on the board to our player, then (2) test for a win, and then (3) set the temporary position back to empty.
     1.   gameState.board[rowIndex][colIndex] = NextPlayer;
     2.   if (TestForWin(gameState.board) === NextPlayer) {
    ...
    3.    gameState.board[rowIndex][colIndex] = "";
    

We will now fill in the rest of our strategy steps.

Here is the code for the AI Player move with the step details.

// Play the next players move Lesson16-Final
function AIPlayerMove() {
    let PlayLocations = [];
    let PlayLocation = { row: null, col: null };
    let NextPlayer = gameState.Next;
    let LastPlayer = gameState.Next === "X" ? "O" : "X";

    // Strategy Steps - assumption  of all steps is that we test only empty locations

    // 1. Look for a winning location, by testing for a win.  
    PlayLocations = gameState.board.reduce((accumulator, rowArray, rowIndex) => {
        return rowArray.reduce((cellAccumulator, cell, colIndex) => {
            if (cell === "") {
                // Test each empty location by setting it as then testing for a win, making sure to clean it out after.
                gameState.board[rowIndex][colIndex] = NextPlayer;
                if (TestForWin(gameState.board) === NextPlayer) {
                    // Add this location as a winning location.
                    cellAccumulator.push( { row: rowIndex, col: colIndex} );
                };
                gameState.board[rowIndex][colIndex] = "";
            };
            return cellAccumulator;
        }, accumulator);
    }, []);

    // 2. Look for a blocking move, by testing as other players for a win.
    if (PlayLocations.length === 0) {
        PlayLocations = gameState.board.reduce((accumulator, rowArray, rowIndex) => {
            return rowArray.reduce((cellAccumulator, cell, colIndex) => {
                if (cell === "") {
                    // Test each empty location by setting it as then testing for a win, making sure to clean it out after.
                    gameState.board[rowIndex][colIndex] = LastPlayer;
                    if (TestForWin(gameState.board) === LastPlayer) {
                        // Add this location as a winning location.
                        cellAccumulator.push({ row: rowIndex, col: colIndex });
                    };
                    gameState.board[rowIndex][colIndex] = "";
                };
                return cellAccumulator;
            }, accumulator);
        }, []);
    };

    // 3. If the center location is available, choose it.
    if (PlayLocations.length === 0 && gameState.board[1][1] === "") {
        PlayLocations.push({ row: 1, col: 1 });
    };

    // 4. Setup for a double potential win.
    if (PlayLocations.length === 0 && gameState.board[1][1] === NextPlayer) {
        // Check to see if there is an opponent on the side next to the location, this is preferred
        if (gameState.board[0][0] === "" && gameState.board[2][2] === "") {
            if (gameState.board[0][1] === LastPlayer || gameState.board[1][0] === LastPlayer) {
                PlayLocations.push({ row: 0, col: 0});
            }
            if (gameState.board[1][2] === LastPlayer || gameState.board[2][1] === LastPlayer) {
                PlayLocations.push({ row: 2, col: 2});
            }
        }
        if (gameState.board[0][2] === "" && gameState.board[2][0] === "") {
            if (gameState.board[0][1] === LastPlayer || gameState.board[1][2] === LastPlayer) {
                PlayLocations.push({ row: 0, col: 2});
            }
            if (gameState.board[1][0] === LastPlayer || gameState.board[2][1] === LastPlayer) {
                PlayLocations.push({ row: 2, col: 0});
            }
        }
    }

    // 5. check for available opposing corners.  
    if (PlayLocations.length === 0) {
        // Check to see if there is an opponent on the side next to the location, this is preferred
        if (gameState.board[0][0] === "" && gameState.board[2][2] === "") {
            PlayLocations.push({ row: 0, col: 0});
            PlayLocations.push({ row: 2, col: 2});
        }
        if (gameState.board[0][2] === "" && gameState.board[2][0] === "") {
            PlayLocations.push({ row: 0, col: 2});
            PlayLocations.push({ row: 2, col: 0});
        }
    }

    // 6. randomly select a location
    if (PlayLocations.length === 0) {
        PlayLocations = gameState.board.reduce((accumulator, rowArray, rowIndex) => {
            // Call map on the rowArray to add any empty location to an array we are returning.
            return rowArray.reduce( (cellAccumulator, cell, colIndex) => {
                return (cell === "") ? cellAccumulator.concat( { row: rowIndex, col: colIndex} ) : cellAccumulator;
            }, accumulator );
        }, []);
    }
    if (PlayLocations.length > 0) {
        // randomly choose a location from your choices
        let index = Math.floor(Math.random() * PlayLocations.length);
        PlayLocation = { row: PlayLocations[index].row, col: PlayLocations[index].col };

        // asign the ai location
        gameState.board[PlayLocation.row][PlayLocation.col] = NextPlayer;
        // change the next X or O
        gameState.Next = gameState.Next === "X" ? "O" : "X";
        // remove the event from the grid so there is no new event
        document.getElementById(`${PlayLocation.row}${PlayLocation.col}`).removeEventListener('click', MainGameLogic);
        // Check to see if we have a winner
        if (CheckForWinner()) {
            // After we have a winner remove the events for remaining squares so they don't fire.
            for (let row = 0; row <= 2; row++) {
                for (let col = 0; col <= 2; col++) {
                    if (gameState.board[row][col] === "") {
                        document.getElementById(`${row}${col}`).removeEventListener('click', MainGameLogic);
                    }
                }
            }
        };
        SaveBoardState();
    };
};

You can find the final version of the Tic-Tac-Toe game at https://mhintegrity.com/Tic-Tac-Toe-Pro/
Load this web app up in Chrome and play with it.
Then using the developer tools walk through using breakpoints see the AIPlayer code in action.


Add Icon’s to our web page

A Progressive Web Apps is a web app that acts more like a phone app than an old style web page.
We have already covered using localStorage to keep or variable data local on the user’s machine.
We are now going to cover adding an icon to our page which will show up on the tab of our browser.
In most browsers, you can tell them to display an icon.
This is done in the HTML page with a link element inside the header.
eg.

<html>
<head>
  <link rel="icon" type="image/png" sizes="32x32" href="favicon.png">
</head>
</html>

There are multiple good websites that will generate the icon image files and HTML text for you.
Here is one that I use https://www.favicon-generator.org/

When you get the HTML link elements from favicon-generator, you may need to alter the path of the links, depending on where you save the files.
For example, see this one href that is stored in a folder named icons

<html>
<head>
  <link rel="apple-touch-icon" sizes="57x57" href="icons/apple-icon-57x57.png">
</head>
</html>

  • Video Link favicon
    • Length: 5:16 minutes
    • Size: 27.5 MB

Assignment due for discussion next class and checked into GitHub by the Monday after that.

  • Create a new repo called lesson16
  • Make a simple website that interests you, include some CSS and JavaScript.
    • This is a good opportunity to start thinking about what you want to do for your class project.
    • I would recommend maybe a simple game like “choose your own adventure”.
  • Use the favicon-generator to add an interesting icon to your page.
  • Share up your page in GitHub.com, it will have a link like this https://youruser.github.io/lesson16