How do I generate Scratch Card Algorithm on NodeJS?

85 Views Asked by At

Greetings to everyone,

There is a game frequently played in Turkey. Its name is Scratch.

The rules of the game are as follows:

  • If the same three numbers appear in any matrix (3x3, 4x4, 4x3), that number wins.
  • There cannot be more than one winning number in a matrix.

Let's consider a 3x3 matrix.

// In this matrix, both 50, 20, and 5 win. This is a mistake.
[ [ 50, 20, 50 ], [ 5, 5, 20 ], [ 50, 5, 20 ] ] 

// There are four 50's in this matrix. There can be a maximum of three of a number.    
[ [ 50, 20, 50 ], [ 5, 50, 20 ], [ 10, 5, 50 ] ] 

// There is no winner in this matrix, but the rules are correct.
[ [ 50, 20, 50 ], [ 5, 5, 20 ], [ 10, 40, 10 ] ] 

// The number 10 wins in this matrix. And it is true.
[ [ 50, 20, 50 ], [ 5, 10, 20 ], [ 10, 40, 10 ] ] 

I couldn't write a NodeJS algorithm that created this.

exports.scratchCard = async (req, res) => {
  let numArray = [5, 10, 20, 40, 100, 5, 5, 10, 5, 20, 5, 40];
  let matrix;

  matrix = create3x3Matrix(numArray);

  console.log("Matrix:", matrix);

  let winningNumber = findWinningNumber(matrix);
  console.log("Winning Number:", winningNumber);
  return res.json({ matrix, winningNumber });
};
function create3x3Matrix(numArray) {
  let matrix = [];

  for (let i = 0; i < 3; i++) {
    let row = [];

    for (let j = 0; j < 3; j++) {
      let randomNumber;

      do {
        let randomIndex = Math.floor(Math.random() * numArray.length);
        randomNumber = numArray[randomIndex];
      } while (
        (findNumberCount(matrix, randomNumber) >= 3) &&
        (findWinningNumber(matrix) != undefined)
      );

      row.push(randomNumber);
    }

    matrix.push(row);
  }

  return matrix;
}
function findNumberCount(matrix, searchingNumber) {
  let count = 0;

  for (let i = 0; i < matrix.length; i++) {
    for (let j = 0; j < matrix[i].length; j++) {
      if (matris[i][j] === searchingNumber) {
        count++;
      }
    }
  }

  return count;
}
function findWinningNumber(matrix) {
  let numberCounts = {};
  for (let i = 0; i < matrix.length; i++) {
    for (let j = 0; j < matrix.length; j++) {
      let number = matrix[i][j];

      if (numberCounts[number]) {
        numberCounts[number]++;
      } else {
        numberCounts[number] = 1;
      }
    }
  }

  let winningNumber;

  for (let number in numberCounts) {
    if (numberCounts[number] === 3) {
      winningNumber = number;
      break;
    }
  }

  return winningNumber;
}

Some returns

Matrix: [ [ 40, 5, 20 ], [ 40, 10, 5 ], [ 10, 20, 100 ] ]
Winning Number: undefined
Matrix: [ [ 5, 5, 40 ], [ 5, 5, 5 ], [ 20, 20, 40 ] ]
Winning Number: undefined
Matrix: [ [ 5, 5, 5 ], [ 10, 10, 40 ], [ 10, 20, 100 ] ]
Winning Number: 5
Matrix: [ [ 40, 10, 20 ], [ 40, 5, 5 ], [ 20, 10, 10 ] ]
Winning Number: 10
Matrix: [ [ 5, 20, 5 ], [ 5, 20, 5 ], [ 20, 10, 40 ] ]
Winning Number: 20
Matrix: [ [ 10, 5, 10 ], [ 5, 40, 40 ], [ 5, 10, 5 ] ]
Winning Number: 10
Matrix: [ [ 5, 5, 40 ], [ 20, 20, 20 ], [ 100, 5, 5 ] ]
Winning Number: 20
Matrix: [ [ 10, 40, 5 ], [ 5, 100, 10 ], [ 10, 100, 5 ] ]
Winning Number: 5
Matrix: [ [ 5, 40, 100 ], [ 40, 5, 10 ], [ 5, 5, 5 ] ]
Winning Number: undefined

I want to make a valid scratch card. It must comply with the rules I mentioned above. There is no win condition. It may lose.

1

There are 1 best solutions below

0
On BEST ANSWER

If you just want to check a given card if there is a winning situation on it, the simplest probably would be the following

function checkCard(card) {
  let flat = card.flat().sort((a, b) => a - b);
  let winningNumber = -1;
  for (let i = 2; i < flat.length; i++) {
    //compare the number at index i with its two predecessors
    //if you have three equal numbers next to each other, this is a possible winning situation
    if (flat[i] == flat[i - 1] && flat[i] == flat[i - 2]) {
      //if you already found a winning Number earlier, that's an invalid scratch card, return "no win"
      if (winningNumber >= 0)
        return -1;

      //otherwise remember the current winning number
      winningNumber = flat[i];
    }
  }

  //once the loop has finished, the winningNumber will either be -1
  //if there was no winning situation or the winning number in the card
  return winningNumber;
}

console.log(checkCard([[40, 5, 20], [40, 10, 5], [10, 20, 100]])); // -1 no number appears 3 times
console.log(checkCard([[5, 5, 40], [5, 5, 5], [20, 20, 40]]));  // -1  5 appears more than 3 times
console.log(checkCard([[5, 5, 5], [10, 10, 40], [10, 20, 100]]));  // -1   5 and 10 appear 3 times
console.log(checkCard([[40, 10, 20], [40, 5, 5], [20, 10, 10]]));  // 10  

Ie flatten the nested array in a flat array of numbers and sort them ascending. Then iterate over the array and whenever you find an index i where the number at index i is equal to its two precessors, it's a possible winning number.

  • If you find such a possible winning number, but you already discovered a potential winning number earlier, you have an invalid scratch card, so return a "no win" situation (I encode this with a return value of -1).

  • If you find exactly one winning situation return the winning number

  • If you find no winning situation at all, return again -1 for "no win"

To create a valid scratch card you can use this simple algorithm

function createCard(x,y) {
  let usedNumbers = new Map();  //holds the count of all numbers used so far
  let hasWinner = false;  //holds whether there is a winning number on the card or not
  
  let card = []; //the card
  for (let i = 0; i < x; i++) {
    let j = 0;
    card[i] = []; //initialize the current row with an empty array
    while (j < y) {  //do while the row isn't fully filled
      let num = Math.ceil(Math.random() * 10);  //create a random number
      
      //if the number has been used less that 2 times
      //or it has been used exactly 2 times and there is no winner yet
      //it can be added to the scratch card
      //if the usedcount grows to 3, we have a winning situation
      let usedCount = usedNumbers.get(num) || 0;
      if (usedCount < 2 || (usedCount == 2 && !hasWinner)) {
        usedNumbers.set(num, ++usedCount);
        card[i][j++] = num;
        if (usedCount === 3) hasWinner = true;
      }
    }
  }
  
  return card;
}

console.log(createCard(3,3))

  • Initialize an empty scratch card with rows and columns

  • Whenever you create a new random number check, if you can add it to the card without violating the rules

    • It's either used less than 2 times currently

    • It's used exactly two times and there is no other winning number already on the card

Be aware, this way of filling the scratch card may lead to an infinite loop if the size of the card and the range of possible numbers don't fit together. For instance you won't be able to fill a 10x10 card with numbers only in the range 1..20