Back to Freecodecamp

Step 14

curriculum/challenges/english/blocks/review-algorithmic-thinking-by-building-a-dice-game/657e230500602983e01fff6e.md

latest14.4 KB
Original Source

--description--

For the last portion of the game, you will need to create an algorithm that checks for the presence of a straight. A small straight is when four of the dice have consecutive values in any order (Ex. in a roll of 41423, we have 1234) resulting in a score of 30 points. A large straight is when all five dice have consecutive values in any order (Ex. in a roll of 35124, we have 12345) resulting in a score of 40 points.

Declare a checkForStraights function which accepts an array of numbers. If the user gets a large straight, update the fifth radio button with a score of 40. If the user gets a small straight, update the fourth radio button with a score of 30. Regardless, it should always update the last radio button to display a score of 0, with the correct attributes.

Call your checkForStraights function when the rollDiceBtn is clicked to complete your dice game!

--hints--

Your checkForStraights variable should be a function.

js
assert.isFunction(checkForStraights);

If a small straight is rolled, your checkForStraights function should enable the fourth radio button, set the value to 30, update the displayed text to , score = 30 and leave the fifth radio button disabled.

js
const assertSmallStraight = (_diceValuesArr) => {
  resetRadioOptions();
  checkForStraights(_diceValuesArr);
  assert.isTrue(scoreInputs[4].disabled);
  assert.isFalse(scoreInputs[3].disabled);
  assert.strictEqual(scoreInputs[3].value, "30");
  assert.strictEqual(scoreSpans[3].innerText, ", score = 30");
}
// Basic straights
assertSmallStraight([1,1,2,3,4])
assertSmallStraight([2,3,4,5,5])
assertSmallStraight([3,4,5,6,6])
// 5 unique numbers, but only small straight
assertSmallStraight([1,2,3,4,6])
assertSmallStraight([1,3,4,5,6])
// Straights with duplicates in middle
assertSmallStraight([1,2,2,3,4])
assertSmallStraight([2,3,3,4,5])
assertSmallStraight([3,4,5,5,6])
// Out of order straights
assertSmallStraight([1,3,2,1,4])
assertSmallStraight([5,4,3,3,2])
assertSmallStraight([3,4,5,6,1])

If a large straight is rolled, your checkForStraights function should enable the fourth button, set the value to 30, update the displayed text to , score = 30. Additionally, the function should enable the fifth radio button, set the value to 40, and update the displayed text to , score = 40.

js
const assertLargeStraight = (_diceValuesArr) => {
  resetRadioOptions();
  checkForStraights(_diceValuesArr);
  assert.isFalse(scoreInputs[3].disabled);
  assert.strictEqual(scoreInputs[3].value, "30");
  assert.strictEqual(scoreSpans[3].innerText, ", score = 30");
  assert.isFalse(scoreInputs[4].disabled);
  assert.strictEqual(scoreInputs[4].value, "40");
  assert.strictEqual(scoreSpans[4].innerText, ", score = 40");
}
// Basic straights
assertLargeStraight([1,2,3,4,5])
assertLargeStraight([2,3,4,5,6])
// Backward straights
assertLargeStraight([5,4,3,2,1])
assertLargeStraight([6,5,4,3,2])
// Out of order straights
assertLargeStraight([1,5,3,4,2])
assertLargeStraight([2,4,6,5,3])

If no straight is rolled, your checkForStraights function should not enable the fourth or fifth radio button.

js
const assertNoStraight = (_diceValuesArr) => {
  resetRadioOptions();
  checkForStraights(_diceValuesArr);
  assert.isTrue(scoreInputs[3].disabled);
  assert.isTrue(scoreInputs[4].disabled);
}
// Simple cases
assertNoStraight([1,4,4,4,4]);
assertNoStraight([2,2,3,3,3]);
assertNoStraight([5,5,5,5,5]);
assertNoStraight([6,5,1,5,6]);
// Almost a straight, but broken in middle
assertNoStraight([1,2,3,5,6]);
assertNoStraight([1,2,4,5,6]);
// Almost a straight with duplicates in middle
assertNoStraight([1,2,2,3,5]);
assertNoStraight([2,4,4,5,6]);
// Repeat of last 4 cases, but not in order
assertNoStraight([1,5,6,2,3]);
assertNoStraight([5,2,4,1,6]);
assertNoStraight([2,1,5,3,2]);
assertNoStraight([2,4,5,4,6]);

If no straight is rolled, your checkForStraights function should enable the final radio button set the value to 0, and update the displayed text to , score = 0.

js
resetRadioOptions();
checkForStraights([1,1,1,1,1]);
assert.isFalse(scoreInputs[5].disabled);
assert.strictEqual(scoreInputs[5].value, "0");
assert.strictEqual(scoreSpans[5].innerText, ", score = 0");

You should call the checkForStraights function when the rollDiceBtn is clicked.

js
resetRadioOptions();
const origMathRandom = Math.random;
// Temporarily modifies Math.random to guarantee a straight.
const myMathRandom = (() => {
  let i = 0;
  const values = [4/6,2/6,5/6,3/6,1/6];
  return () => values[i++ % 5];
})();

Math.random = myMathRandom;

try {
  rollDiceBtn.click();
} finally {
  Math.random = origMathRandom;
}

assert.isFalse(scoreInputs[4].disabled);
assert.strictEqual(scoreInputs[4].value, "40");
assert.strictEqual(scoreSpans[4].innerText, ", score = 40");

--seed--

--seed-contents--

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Advanced Dice Game</title>
  <link rel="stylesheet" href="styles.css" />
</head>

<body>
  <header>
    <h1>Advanced Dice Game</h1>
    <button class="btn" id="rules-btn" type="button">Show rules</button>
    <div class="rules-container">
      <h2>Rules</h2>
      <ul>
        <li>There are total of six rounds</li>
        <li>You can only roll the dice three times per round</li>
        <li>To start the game, roll the dice</li>
        <li>
          Then, choose from one of the selected scores or roll the dice again
        </li>
        <li>
          If you choose a selected score, then you will move to the next round
        </li>
        <li>
          If you decline to choose a selected score, then you can roll the
          dice again two more times
        </li>
      </ul>
      <h2 class="points">Points</h2>
      <ul>
        <li>Three of a kind: Sum of all five dice</li>
        <li>Four of a kind: Sum of all five dice</li>
        <li>Full house: Three of a kind and a pair - 25 points</li>
        <li>
          Small straight: Four of the dice have consecutive values - 30 points
        </li>
        <li>
          Large straight: All five dice have consecutive values - 40 points
        </li>
      </ul>
    </div>
  </header>

  <main>
    <form id="game">
      <div id="dice">
        <div class="die"></div>
        <div class="die"></div>
        <div class="die"></div>
        <div class="die"></div>
        <div class="die"></div>
      </div>

      <p class="rounds-text">
        <strong>Rolls:</strong> <span id="current-round-rolls">0</span> |
        <strong>Round:</strong> <span id="current-round">1</span>
      </p>

      <div id="score-options">
        <div>
          <input type="radio" name="score-options" id="three-of-a-kind" value="three-of-a-kind" disabled />
          <label for="three-of-a-kind">Three of a kind<span></span></label>
        </div>
        <div>
          <input type="radio" name="score-options" id="four-of-a-kind" value="four-of-a-kind" disabled />
          <label for="four-of-a-kind">Four of a kind<span></span></label>
        </div>
        <div>
          <input type="radio" name="score-options" id="full-house" value="full-house" disabled />
          <label for="full-house">Full house<span></span></label>
        </div>
        <div>
          <input type="radio" name="score-options" id="small-straight" value="small-straight" disabled />
          <label for="small-straight">Small straight<span></span></label>
        </div>
        <div>
          <input type="radio" name="score-options" id="large-straight" value="large-straight" disabled />
          <label for="large-straight">Large straight<span></span></label>
        </div>

        <div>
          <input type="radio" name="score-options" id="none" value="none" disabled />
          <label for="none">None of the above<span></span></label>
        </div>
      </div>

      <button class="btn" id="keep-score-btn" type="button">
        Keep the above selected score
      </button>
      <button class="btn" id="roll-dice-btn" type="button">
        Roll the dice
      </button>
    </form>

    <div id="scores">
      <h3>Score history (Total score: <span id="total-score">0</span>)</h3>
      <ol id="score-history"></ol>
    </div>
  </main>
  <script src="./script.js"></script>
</body>

</html>
css
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

:root {
  --dark-grey: #1b1b32;
  --light-grey: #f5f6f7;
  --black: #000;
  --white: #fff;
  --grey: #3b3b4f;
  --golden-yellow: #fecc4c;
  --yellow: #ffcc4c;
  --gold: #feac32;
  --orange: #ffac33;
  --dark-orange: #f89808;
}

body {
  background-color: var(--dark-grey);
}

header {
  color: var(--light-grey);
  text-align: center;
}

h1 {
  font-size: 2.5rem;
  margin: 25px 0;
}

.rules-container {
  display: none;
  background-color: var(--light-grey);
  color: var(--black);
  width: 50%;
  margin: 20px auto;
  height: 300px;
  border-radius: 10px;
  overflow-y: scroll;
}

.rules-container ul {
  list-style-type: none;
}

.points {
  margin-top: 15px;
}

main {
  background-color: var(--light-grey);
  padding: 20px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
  gap: 1rem;
  margin: auto;
  justify-items: center;
  width: 50%;
  border-radius: 10px;
}

#dice {
  display: flex;
  justify-content: space-around;
  margin-bottom: 15px;
}

.die {
  width: 40px;
  height: 40px;
  text-align: center;
  margin-right: 15px;
  border: 4px solid var(--black);
  padding: 10px;
}

.rounds-text {
  text-align: center;
}

input[type="radio"]:disabled + label {
  color: var(--grey);
}

#score-history {
  margin-top: 15px;
  text-align: center;
  list-style-position: inside;
}

.btn {
  cursor: pointer;
  width: 200px;
  margin: 10px 0 10px 0.5rem;
  color: var(--black);
  background-color: var(--gold);
  background-image: linear-gradient(var(--golden-yellow), var(--orange));
  border-color: var(--gold);
  border-width: 3px;
}

.btn:hover:enabled {
  background-image: linear-gradient(var(--yellow), var(--dark-orange));
}

@media (max-width: 992px) {
  main {
    width: 100%;
  }
}

@media (max-width: 500px) {
  .btn {
    width: 120px;
  }
}

js
const listOfAllDice = document.querySelectorAll(".die");
const scoreInputs = document.querySelectorAll("#score-options input");
const scoreSpans = document.querySelectorAll("#score-options span");
const roundElement = document.getElementById("current-round");
const rollsElement = document.getElementById("current-round-rolls");
const totalScoreElement = document.getElementById("total-score");
const scoreHistory = document.getElementById("score-history");
const rollDiceBtn = document.getElementById("roll-dice-btn");
const keepScoreBtn = document.getElementById("keep-score-btn");
const rulesContainer = document.querySelector(".rules-container");
const rulesBtn = document.getElementById("rules-btn");

let diceValuesArr = [];
let isModalShowing = false;
let score = 0;
let round = 1;
let rolls = 0;

const rollDice = () => {
  diceValuesArr = [];

  for (let i = 0; i < 5; i++) {
    const randomDice = Math.floor(Math.random() * 6) + 1;
    diceValuesArr.push(randomDice);
  };

  listOfAllDice.forEach((dice, index) => {
    dice.textContent = diceValuesArr[index];
  });
};

const updateStats = () => {
  rollsElement.textContent = rolls;
  roundElement.textContent = round;
};

const updateRadioOption = (index, score) => {
  scoreInputs[index].disabled = false;
  scoreInputs[index].value = score;
  scoreSpans[index].textContent = `, score = ${score}`;
};

const updateScore = (selectedValue, achieved) => {
  score += parseInt(selectedValue);
  totalScoreElement.textContent = score;

  scoreHistory.innerHTML += `<li>${achieved} : ${selectedValue}</li>`;
};

const getHighestDuplicates = (arr) => {
  const counts = {};

  for (const num of arr) {
    if (counts[num]) {
      counts[num]++;
    } else {
      counts[num] = 1;
    }
  }

  let highestCount = 0;

  for (const num of arr) {
    const count = counts[num];
    if (count >= 3 && count > highestCount) {
      highestCount = count;
    }
    if (count >= 4 && count > highestCount) {
      highestCount = count;
    }
  }

  const sumOfAllDice = arr.reduce((a, b) => a + b, 0);

  if (highestCount >= 4) {
    updateRadioOption(1, sumOfAllDice);
  }

  if (highestCount >= 3) {
    updateRadioOption(0, sumOfAllDice);
  }

  updateRadioOption(5, 0);
};

const detectFullHouse = (arr) => {
  const counts = {};

  for (const num of arr) {
    counts[num] = counts[num] ? counts[num] + 1 : 1;
  }

  const hasThreeOfAKind = Object.values(counts).includes(3);
  const hasPair = Object.values(counts).includes(2);

  if (hasThreeOfAKind && hasPair) {
    updateRadioOption(2, 25);
  }

  updateRadioOption(5, 0);
};

const resetRadioOptions = () => {
  scoreInputs.forEach((input) => {
    input.disabled = true;
    input.checked = false;
  });

  scoreSpans.forEach((span) => {
    span.textContent = "";
  });
};

const resetGame = () => {
  diceValuesArr = [0, 0, 0, 0, 0];
  score = 0;
  round = 1;
  rolls = 0;

  listOfAllDice.forEach((dice, index) => {
    dice.textContent = diceValuesArr[index];
  });

  totalScoreElement.textContent = score;
  scoreHistory.innerHTML = "";

  rollsElement.textContent = rolls;
  roundElement.textContent = round;

  resetRadioOptions();
};

--fcc-editable-region--


rollDiceBtn.addEventListener("click", () => {
  if (rolls === 3) {
    alert("You have made three rolls this round. Please select a score.");
  } else {
    rolls++;
    resetRadioOptions();
    rollDice();
    updateStats();
    getHighestDuplicates(diceValuesArr);
    detectFullHouse(diceValuesArr);

  }
});
--fcc-editable-region--

rulesBtn.addEventListener("click", () => {
  isModalShowing = !isModalShowing;

  if (isModalShowing) {
    rulesBtn.textContent = "Hide rules";
    rulesContainer.style.display = "block";
  } else {
    rulesBtn.textContent = "Show rules";
    rulesContainer.style.display = "none";
  }
});

keepScoreBtn.addEventListener("click", () => {
  let selectedValue;
  let achieved;

  for (const radioButton of scoreInputs) {
    if (radioButton.checked) {
      selectedValue = radioButton.value;
      achieved = radioButton.id;
      break;
    }
  }

  if (selectedValue) {
    rolls = 0;
    round++;
    updateStats();
    resetRadioOptions();
    updateScore(selectedValue, achieved);
    if (round > 6) {
      setTimeout(() => {
        alert(`Game Over! Your total score is ${score}`);
        resetGame();
      }, 500);
    }
  } else {
    alert("Please select an option or roll the dice");
  }
});