Lesson 15 AI logic 2, Random

Mark Harder, 18 January 2019

Math.random()

The Math.random() function returns a floating-point, pseudo-random number in the range 0–1 (inclusive of 0, but not 1)

Math.random()
// Example results 0.8279182094894322
Math.random() * 10
7.800843553548189
Math.floor(Math.random() * 10)
1  // values between 0 and 9

see Math.floor below

If we want a number in the range of 6-12 how do we do that?

Math.floor(Math.random() * 7) + 6
6  // values between 6 and 12

Lets make a function that returns a random integer in a range we choose.

function randomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}
// randomInt(6, 12) returns a number between 6 and 12 inclusive of 6 and 12.  

Math Library

There are a lot of math functions like abs() and cos() which you can find detailed at the MDN site.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math
Please go to the site and read about Math functions.

I would like to point out a few of them.

  • Math.abs(x) Returns the absolute value of a number.
  • Math.ceil(x) Returns the smallest integer greater than or equal to a number.
  • Math.floor(x) Returns the largest integer less than or equal to a number.
  • Math.max([x[, y[, …]]]) Returns the largest of zero or more numbers.
  • Math.min([x[, y[, …]]]) Returns the smallest of zero or more numbers.
  • Math.round(x) Returns the value of a number rounded to the nearest integer.
  • Math.trunc(x) Returns the integer part of the number x, removing any fractional digits.

Special note: You can send an array into Math.min or Math.max like this:

let array1 = [1, 3, 2];
Math.min(...array1); 
// returns 1
Math.max(...array1);
// returns 3

In order to use an array you must put three periods first in the parentheses, this is called the spread operator, it converts your array into individual items before passing them in.


  • Video Link Math.random
    • Length: 5:17 minutes
    • Size: 17.5 MB

array.sort()

The sort() method sorts the elements of an array in place and returns the array. The default sort order is built upon converting the elements into strings, then comparing their sequences.

Simple example:

let ary1 = [1, 45, 2, 34, 2];  
ary1.sort();
// results: Array [1, 2, 2, 34, 45]

Custom sort, we can use a function.

arr.sort([compareFunction])

compareFunction Specifies a function that defines the sort order. If omitted, the array is sorted according to each character’s Unicode code point value, according to the string conversion of each element. firstEl: The first element for comparison. secondEl: The second element for comparison.

If compareFunction(a, b) is less than 0, sort a to an index lower than b (i.e. a comes first). If compareFunction(a, b) returns 0, leave a and b unchanged with respect to each other, but sorted with respect to all different elements. If compareFunction(a, b) is greater than 0, sort b to an index lower than a (i.e. b comes first).

Lets do an example where sort compares properties in objects in an array.

let people = [{ id: 1, name: 'Mark Harder', position: 'Teacher', age: 40 },
    { id: 2, name: 'John Shy', position: 'Student', age: 14 },
    { id: 3, name: 'Berry Ray', position: 'Student', age: 15 }
];
let results = people.sort( (a, b) => {  
  if (a.name < b.name) {
    return -1;
  }
  if (a.name > b.name) {
    return 1;
  }
  return 0; 
} );

Returns: Sorted by name property

0: {id: 3, name: "Berry Ray", position: "Student", age: 15}
1: {id: 2, name: "John Shy", position: "Student", age: 14}
2: {id: 1, name: "Mark Harder", position: "Teacher", age: 40}

  • Video Link array.sort
    • Length: 9:36 minutes
    • Size: 25.0 MB

Another example of a sort function we can reuse that converts strings to upper case for comparing.

function compareStrUpper(a, b) {
    let stra = a.toUpperCase();
    let strb = b.toUpperCase();
    if (stra.name < strb.name) {
        return -1;
    }
    if (stra.name > strb.name) {
        return 1;
    }
    return 0; 
};
// example of using this function
let a2  = ['fred', 'adam', 'greg', 'Frank'];
let r = a2.sort(compareStrUpper);

Tic Tac Toe Game AI Logic Part 2 of 3

Let’s Start Building our AI Player Code.

The flow of our code for the AI player will be as follows.
Call in succession a series of steps. Each step might return an array of possible moves which we will store in a variable named PlayLocations. If none of steps choose 1 or more locations, then the last step will add all of the available locations.

  • With our array “PlayLocations”, which contains 1 or more locations, we will randomly choose one of the arrays rows (PlayLocation).
  • We will then put the AI player into that location in our gameState.board variable.
  • Next we will remove the click event from that location on the screen so the user can’t choose it.
  • Finally we will check for a winner and update the screen state.

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

// Play the next players move
function AIPlayerMove() {
    let PlayLocations = [];
    let PlayLocation = { row: null, col: null };
    let NextPlayer = gameState.Next;

    // Strategy Steps - sssumtion 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 player for a win.

    // 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.

    // 5. randomly select a location
    
    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);
                }
            }
        }
    };
    UpdateScreenState();
}

Combine .reduce() to look for winning Play Locations

  • Start with a full 2D array of board state.
  • call .reduce() so that we can return a results array of PlayLocation = { row: null, col: null };
  • Reduce adds items together for returning.
  • We can use .concat() to add objects in an array.

For results, we need an array of locations.

Reduce is a special function, it calls its internal function for all items in the array.
Reduce then keeps adding results to an accumulator which ends up being returned.
Now a special way we can use the accumulator is not to return a number or an object but an array because an array is an object. We can even custom make the items we return in the array.
Lets try some simple examples and build up to returning the results of testing for a win.

let people = [{ id: 1, name: 'Mark Harder', position: 'Teacher', age: 40 },
    { id: 2, name: 'John Shy', position: 'Student', age: 14 },
    { id: 3, name: 'Berry Ray', position: 'Student', age: 15 }
];
let results = people.reduce( (accumulator, person) => { return accumulator.concat( { FullName: person.name } ); }, []);

Returns:

0: {FullName: "Mark Harder"}
1: {FullName: "John Shy"}
2: {FullName: "Berry Ray"}

How does .reduce work in our example?

.reduce() calls its inner function, passing in the results of the last call. Lets walk through how this code runs.

  1. people.reduce( (accumulator, person) => { }, []); The first time into the function the accumulator is the value from initialValue.

    accumulator = []

  2. return accumulator.concat( { FullName: person.name } ) the accumulator is an empty array to which we add a new object with a property named FullName

    accumulator = [ {FullName: “Mark Harder”} ]

  3. second call into function passed in the arrays second row, which is then added to the accumulator array using .concat()

    accumulator = [ {FullName: “Mark Harder”}, {FullName: “John Shy”} ]

  4. Third and last row

    accumulator = [ {FullName: “Mark Harder”}, {FullName: “John Shy”}, {FullName: “Berry Ray”} ]

  5. Finally the new array is assigned to let results variable as a new array.

Lets try reduce on our two dimensional board

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 cellAccumulator.concat( { row: rowIndex, col: colIndex, value: cell } );
    }, accumulator );
}, []);

This returns a full array of all locations of the board.

0: {row: 0, col: 0, value: ""}
1: {row: 0, col: 1, value: ""}
2: {row: 0, col: 2, value: ""}
3: {row: 1, col: 0, value: ""}
4: {row: 1, col: 1, value: ""}
5: {row: 1, col: 2, value: ""}
6: {row: 2, col: 0, value: ""}
7: {row: 2, col: 1, value: ""}
8: {row: 2, col: 2, value: ""}

Now we add an if check to our inner function, so that we only return cells that are empty.

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 we add this logic to AIPlayer step number 5 our AI will randomly pick an available spot, starting with the center.

You can fined this code so far at https://github.com/mhintegrity/lesson15-TicTacToe


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

  • Create a new repo called lesson15
  • Lets practice using random and sort. There is an index,html and main.js file that contain 4 code questions that you used for lesson 14. Copy into a new repo called lesson15. Make sure to re-name the header and title. Also change the text and code for each of the 4 questions to these new ones.
    • Download repo https://github.com/mhintegrity/lesson14
      1. Return one random person from the array of people.`
      2. Return the people sorted by the number if skills they have.
      3. Return the people sorted by their name, starting with lastName then firstName.
      4. Return an array of the people with the fields: name, job and salary. Make name the combination of first and last name and the salary a random number between 60000 and 120000