-1

I found one game on js, and I want to add it to myself by redoing it a bit

https://jsfiddle.net/a7Lx1c98/

So, I want to replace emoji with pictures here, I do this

const emojis = ['https://i.imgur.com/GLS9S5f.jpg', 'https://i.imgur.com/IN9C2qz.jpg', 'https://i.imgur.com/Ke2ubzv.jpg', 'https://i.imgur.com/PbvJDyR.jpg', 'https://i.imgur.com/L3ysai2.jpg',
'https://i.imgur.com/1NxzhTV.jpg', 'https://i.imgur.com/aksV9O3.jpg', 'https://i.imgur.com/gYsZdE4.jpg', 'https://i.imgur.com/LXo6iW3.jpg', 'https://i.imgur.com/wYrEwNR.jpg']
const picks = pickRandom(emojis, (dimensions * dimensions) / 2) 
const items = shuffle([...picks, ...picks])
const cards = `
  <div class="board" style="grid-template-columns: repeat(${dimensions}, auto)">
    ${items.map(item => `
      <div class="card">
        <div class="card-front"></div>
        <div class="card-back"><img src="${item}"></div>
      </div>
    `).join('')}
  </div>
`

As a result, the pictures appear, but the game itself does not work, when you select two pictures and they are different, they do not close, but you can choose everything in a row

What could be wrong?

const selectors = {
    boardContainer: document.querySelector('.board-container'),
    board: document.querySelector('.board'),
    moves: document.querySelector('.moves'),
    timer: document.querySelector('.timer'),
    start: document.querySelector('button'),
    win: document.querySelector('.win')
}

const state = {
    gameStarted: false,
    flippedCards: 0,
    totalFlips: 0,
    totalTime: 0,
    loop: null
}

const shuffle = array => {
    const clonedArray = [...array]

    for (let index = clonedArray.length - 1; index > 0; index--) {
        const randomIndex = Math.floor(Math.random() * (index + 1))
        const original = clonedArray[index]

        clonedArray[index] = clonedArray[randomIndex]
        clonedArray[randomIndex] = original
    }

    return clonedArray
}

const pickRandom = (array, items) => {
    const clonedArray = [...array]
    const randomPicks = []

    for (let index = 0; index < items; index++) {
        const randomIndex = Math.floor(Math.random() * clonedArray.length)
        
        randomPicks.push(clonedArray[randomIndex])
        clonedArray.splice(randomIndex, 1)
    }

    return randomPicks
}

const generateGame = () => {
    const dimensions = selectors.board.getAttribute('data-dimension')

    if (dimensions % 2 !== 0) {
        throw new Error("The dimension of the board must be an even number.")
    }

    const emojis = ['https://i.imgur.com/GLS9S5f.jpg', 'https://i.imgur.com/IN9C2qz.jpg', 'https://i.imgur.com/Ke2ubzv.jpg', 'https://i.imgur.com/PbvJDyR.jpg', 'https://i.imgur.com/L3ysai2.jpg',
'https://i.imgur.com/1NxzhTV.jpg', 'https://i.imgur.com/aksV9O3.jpg', 'https://i.imgur.com/gYsZdE4.jpg', 'https://i.imgur.com/LXo6iW3.jpg', 'https://i.imgur.com/wYrEwNR.jpg']
const picks = pickRandom(emojis, (dimensions * dimensions) / 2) 
const items = shuffle([...picks, ...picks])
const cards = `
  <div class="board" style="grid-template-columns: repeat(${dimensions}, auto)">
    ${items.map(item => `
      <div class="card">
        <div class="card-front"></div>
        <div class="card-back"><img src="${item}"></div>
      </div>
    `).join('')}
  </div>
`
    
    const parser = new DOMParser().parseFromString(cards, 'text/html')

    selectors.board.replaceWith(parser.querySelector('.board'))
}

const startGame = () => {
    state.gameStarted = true
    selectors.start.classList.add('disabled')

    state.loop = setInterval(() => {
        state.totalTime++

        selectors.moves.innerText = `${state.totalFlips} moves`
        selectors.timer.innerText = `time: ${state.totalTime} sec`
    }, 1000)
}

const flipBackCards = () => {
    document.querySelectorAll('.card:not(.matched)').forEach(card => {
        card.classList.remove('flipped')
    })

    state.flippedCards = 0
}

const flipCard = card => {
    state.flippedCards++
    state.totalFlips++

    if (!state.gameStarted) {
        startGame()
    }

    if (state.flippedCards <= 2) {
        card.classList.add('flipped')
    }

    if (state.flippedCards === 2) {
        const flippedCards = document.querySelectorAll('.flipped:not(.matched)')

        if (flippedCards[0].innerText === flippedCards[1].innerText) {
            flippedCards[0].classList.add('matched')
            flippedCards[1].classList.add('matched')
        }

        setTimeout(() => {
            flipBackCards()
        }, 1000)
    }

    // If there are no more cards that we can flip, we won the game
    if (!document.querySelectorAll('.card:not(.flipped)').length) {
        setTimeout(() => {
            selectors.boardContainer.classList.add('flipped')
            selectors.win.innerHTML = `
                <span class="win-text">
                    You won!<br />
                    with <span class="highlight">${state.totalFlips}</span> moves<br />
                    under <span class="highlight">${state.totalTime}</span> seconds
                </span>
            `

            clearInterval(state.loop)
        }, 1000)
    }
}

const attachEventListeners = () => {
    document.addEventListener('click', event => {
        const eventTarget = event.target
        const eventParent = eventTarget.parentElement

        if (eventTarget.className.includes('card') && !eventParent.className.includes('flipped')) {
            flipCard(eventParent)
        } else if (eventTarget.nodeName === 'BUTTON' && !eventTarget.className.includes('disabled')) {
            startGame()
        }
    })
}

generateGame()
attachEventListeners()
html {
    width: 100%;
    height: 100%;
    background: linear-gradient(325deg,  #6f00fc 0%,#fc7900 50%,#fcc700 100%);
    font-family: Fredoka;
}

.game {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

.controls {
    display: flex;
    gap: 20px;
    margin-bottom: 20px;
}

button {
    background: #282A3A;
    color: #FFF;
    border-radius: 5px;
    padding: 10px 20px;
    border: 0;
    cursor: pointer;
    font-family: Fredoka;
    font-size: 18pt;
}

.disabled {
    color: #757575;
}

.stats {
    color: #FFF;
    font-size: 14pt;
}

.board-container {
    position: relative;
}

.board,
.win {
    border-radius: 5px;
    box-shadow: 0 25px 50px rgb(33 33 33 / 25%);
    background: linear-gradient(135deg,  #6f00fc 0%,#fc7900 50%,#fcc700 100%);
    transition: transform .6s cubic-bezier(0.4, 0.0, 0.2, 1);
    backface-visibility: hidden;
}

.board {
    padding: 20px;
    display: grid;
    grid-template-columns: repeat(4, auto);
    grid-gap: 20px;
}

.board-container.flipped .board {
    transform: rotateY(180deg) rotateZ(50deg);
}

.board-container.flipped .win {
    transform: rotateY(0) rotateZ(0);
}

.card {
    position: relative;
    width: 100px;
    height: 100px;
    cursor: pointer;
}

.card-front,
.card-back {
    position: absolute;
    border-radius: 5px;
    width: 100%;
    height: 100%;
    background: #282A3A;
    transition: transform .6s cubic-bezier(0.4, 0.0, 0.2, 1);
    backface-visibility: hidden;
}

.card-back {
    transform: rotateY(180deg) rotateZ(50deg);
    font-size: 28pt;
    user-select: none;
    text-align: center;
    line-height: 100px;
    background: #FDF8E6;
}

.card.flipped .card-front {
    transform: rotateY(180deg) rotateZ(50deg);
}

.card.flipped .card-back {
    transform: rotateY(0) rotateZ(0);
}

.win {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    text-align: center;
    background: #FDF8E6;
    transform: rotateY(180deg) rotateZ(50deg);
}

.win-text {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 21pt;
    color: #282A3A;
}

.highlight {
    color: #6f00fc;
}

img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title> Memory Game in JavaScript</title>

        <link rel="stylesheet" href="assets/styles.css" />
        <script src="assets/game.js" defer></script>
    </head>
    <body>
        <div class="game">
            <div class="controls">
                <button>Start</button>
                <div class="stats">
                    <div class="moves">0 moves</div>
                    <div class="timer">time: 0 sec</div>
                </div>
            </div>
            <div class="board-container">
                <div class="board" data-dimension="4"></div>
                <div class="win">You won!</div>
            </div>
        </div>
    </body>
</html>
davy jones
  • 33
  • 5
  • did you see the `Dev Ed` tutorial on youtube? – Kunal Tanwar Jul 19 '22 at 12:01
  • What has your debugging revealed? When you step through the `flipCard` and `flipBackCards` functions, what specifically fails or doesn't work as expected? – David Jul 19 '22 at 12:03
  • @David I did not meet any errors, just all the pictures are considered correct for some reason – davy jones Jul 19 '22 at 12:06
  • Maybe it's because flippedCards is read only as text – davy jones Jul 19 '22 at 12:08
  • 1
    @davyjones: "No errors" doesn't mean "works correctly". This is a good opportunity for you to start familiarizing yourself with [using a debugger](https://stackoverflow.com/q/25385173/328193). With a debugger you can step through the code line by line as it executes and observe the runtime behaviors. – David Jul 19 '22 at 12:08

2 Answers2

1

Because every pair is a match:

if (flippedCards[0].innerText === flippedCards[1].innerText)

Your elements have no text, so innerText is always an empty string. So any two cards, regardless of their images, match.

Probably the quickest solution is to compare the HTML instead:

if (flippedCards[0].innerHTML === flippedCards[1].innerHTML)

Assuming the rest of the HTML is always the same, the only different should be the src on the <img> element(s).


As an added exercise, you could also look into being more explicit in that comparison. Perhaps give each element a data-* property and compare those instead of relying on the text or HTML. Or perhaps specifically read the src property and compare those values instead of comparing the entire contents of the element(s).

David
  • 208,112
  • 36
  • 198
  • 279
1

The issue is comparing the innerText of the two cards. There is no inner text, so you can compare the card back image URLs.

const selected = [...flippedCards].map(card => card.querySelector('.card-back img').src);

if (allEqual(selected)) {
  flippedCards[0].classList.add('matched')
  flippedCards[1].classList.add('matched')
}

I also added an allEqual function to make sure all the items match:

const allEqual = arr => arr.every(v => v === arr[0]);

Working example

const allEqual = arr => arr.every(v => v === arr[0]);

const selectors = {
  boardContainer: document.querySelector('.board-container'),
  board: document.querySelector('.board'),
  moves: document.querySelector('.moves'),
  timer: document.querySelector('.timer'),
  start: document.querySelector('button'),
  win: document.querySelector('.win')
};

const state = {
  gameStarted: false,
  flippedCards: 0,
  totalFlips: 0,
  totalTime: 0,
  loop: null
};

const shuffle = array => {
  const clonedArray = [...array];

  for (let index = clonedArray.length - 1; index > 0; index--) {
    const randomIndex = Math.floor(Math.random() * (index + 1));
    const original = clonedArray[index];

    clonedArray[index] = clonedArray[randomIndex];
    clonedArray[randomIndex] = original;
  }

  return clonedArray;
}

const pickRandom = (array, items) => {
  const clonedArray = [...array];
  const randomPicks = [];

  for (let index = 0; index < items; index++) {
    const randomIndex = Math.floor(Math.random() * clonedArray.length);

    randomPicks.push(clonedArray[randomIndex]);
    clonedArray.splice(randomIndex, 1);
  }

  return randomPicks;
}

const generateGame = () => {
  const dimensions = selectors.board.getAttribute('data-dimension');

  if (dimensions % 2 !== 0) {
    throw new Error("The dimension of the board must be an even number.");
  }

  const emojis = [
    'https://i.imgur.com/GLS9S5f.jpg',
    'https://i.imgur.com/IN9C2qz.jpg',
    'https://i.imgur.com/Ke2ubzv.jpg',
    'https://i.imgur.com/PbvJDyR.jpg',
    'https://i.imgur.com/L3ysai2.jpg',
    'https://i.imgur.com/1NxzhTV.jpg',
    'https://i.imgur.com/aksV9O3.jpg',
    'https://i.imgur.com/gYsZdE4.jpg',
    'https://i.imgur.com/LXo6iW3.jpg',
    'https://i.imgur.com/wYrEwNR.jpg'
  ];
  const picks = pickRandom(emojis, (dimensions * dimensions) / 2);
  const items = shuffle([...picks, ...picks]);
  const cards = `
  <div class="board" style="grid-template-columns: repeat(${dimensions}, auto)">
    ${items.map(item => `
      <div class="card">
        <div class="card-front"></div>
        <div class="card-back"><img src="${item}"></div>
      </div>
    `).join('')}
  </div>
`;

  const parser = new DOMParser().parseFromString(cards, 'text/html');

  selectors.board.replaceWith(parser.querySelector('.board'));
}

const startGame = () => {
  state.gameStarted = true
  selectors.start.classList.add('disabled');

  state.loop = setInterval(() => {
    state.totalTime++;
    selectors.moves.innerText = `${state.totalFlips} moves`;
    selectors.timer.innerText = `time: ${state.totalTime} sec`;
  }, 1000);
}

const flipBackCards = () => {
  document.querySelectorAll('.card:not(.matched)').forEach(card => {
    card.classList.remove('flipped');
  })

  state.flippedCards = 0;
}

const flipCard = card => {
  state.flippedCards++;
  state.totalFlips++;

  if (!state.gameStarted) {
    startGame();
  }

  if (state.flippedCards <= 2) {
    card.classList.add('flipped');
  }

  if (state.flippedCards === 2) {
    const flippedCards = document.querySelectorAll('.flipped:not(.matched)')
    const selected = [...flippedCards].map(card => card.querySelector('.card-back img').src);

    if (allEqual(selected)) {
      flippedCards[0].classList.add('matched')
      flippedCards[1].classList.add('matched')
    }

    setTimeout(() => {
      flipBackCards();
    }, 1000);
  }

  // If there are no more cards that we can flip, we won the game
  if (!document.querySelectorAll('.card:not(.flipped)').length) {
    setTimeout(() => {
      selectors.boardContainer.classList.add('flipped');
      selectors.win.innerHTML = `
                <span class="win-text">
                    You won!<br />
                    with <span class="highlight">${state.totalFlips}</span> moves<br />
                    under <span class="highlight">${state.totalTime}</span> seconds
                </span>
            `;

      clearInterval(state.loop);
    }, 1000);
  }
}

const attachEventListeners = () => {
  document.addEventListener('click', event => {
    const eventTarget = event.target;
    const eventParent = eventTarget.parentElement;

    if (eventTarget.className.includes('card') && !eventParent.className.includes('flipped')) {
      flipCard(eventParent);
    } else if (eventTarget.nodeName === 'BUTTON' && !eventTarget.className.includes('disabled')) {
      startGame();
    }
  })
}

generateGame();
attachEventListeners();
html {
  width: 100%;
  height: 100%;
  background: linear-gradient(325deg, #6f00fc 0%, #fc7900 50%, #fcc700 100%);
  font-family: Fredoka;
}

.game {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.controls {
  display: flex;
  gap: 20px;
  margin-bottom: 20px;
}

button {
  background: #282A3A;
  color: #FFF;
  border-radius: 5px;
  padding: 10px 20px;
  border: 0;
  cursor: pointer;
  font-family: Fredoka;
  font-size: 18pt;
}

.disabled {
  color: #757575;
}

.stats {
  color: #FFF;
  font-size: 14pt;
}

.board-container {
  position: relative;
}

.board,
.win {
  border-radius: 5px;
  box-shadow: 0 25px 50px rgb(33 33 33 / 25%);
  background: linear-gradient(135deg, #6f00fc 0%, #fc7900 50%, #fcc700 100%);
  transition: transform .6s cubic-bezier(0.4, 0.0, 0.2, 1);
  backface-visibility: hidden;
}

.board {
  padding: 20px;
  display: grid;
  grid-template-columns: repeat(4, auto);
  grid-gap: 20px;
}

.board-container.flipped .board {
  transform: rotateY(180deg) rotateZ(50deg);
}

.board-container.flipped .win {
  transform: rotateY(0) rotateZ(0);
}

.card {
  position: relative;
  width: 100px;
  height: 100px;
  cursor: pointer;
}

.card-front,
.card-back {
  position: absolute;
  border-radius: 5px;
  width: 100%;
  height: 100%;
  background: #282A3A;
  transition: transform .6s cubic-bezier(0.4, 0.0, 0.2, 1);
  backface-visibility: hidden;
}

.card-back {
  transform: rotateY(180deg) rotateZ(50deg);
  font-size: 28pt;
  user-select: none;
  text-align: center;
  line-height: 100px;
  background: #FDF8E6;
}

.card.flipped .card-front {
  transform: rotateY(180deg) rotateZ(50deg);
}

.card.flipped .card-back {
  transform: rotateY(0) rotateZ(0);
}

.win {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  text-align: center;
  background: #FDF8E6;
  transform: rotateY(180deg) rotateZ(50deg);
}

.win-text {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 21pt;
  color: #282A3A;
}

.highlight {
  color: #6f00fc;
}

img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
<div class="game">
  <div class="controls">
    <button>Start</button>
    <div class="stats">
      <div class="moves">0 moves</div>
      <div class="timer">time: 0 sec</div>
    </div>
  </div>
  <div class="board-container">
    <div class="board" data-dimension="4"></div>
    <div class="win">You won!</div>
  </div>
</div>
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132