Skip to content

FrogHunter Memory game / Milestone 3#11

Open
Iryna-git-hub wants to merge 40 commits intosprint1from
countdown-timer
Open

FrogHunter Memory game / Milestone 3#11
Iryna-git-hub wants to merge 40 commits intosprint1from
countdown-timer

Conversation

@Iryna-git-hub
Copy link
Copy Markdown
Collaborator

Hi, dear mentor!

In this PR, we have implemented the assignments according to Milestones 2 and 3 for the Memory game project.

What has been done:

  • Designed a database schema to store the cards.
  • Created the database and added sample cards.
  • Implemented a backend route that returns all cards from the database.
  • Refactored the frontend to fetch cards via our new API, replacing the hardcoded data.

Game logic implemented:

  • Only two cards can be flipped at a time.
  • If the cards match, they disappear.
  • Once all cards have disappeared, the player wins and the game ends.
  • After winning, the player can restart the game.

Additional features implemented:

  • Added a countdown timer to increase the challenge.
  • Introduced three levels to enhance gameplay variety.

Looking forward to your review.

Kind regards,
Paloma & Iryna

Paloma-Cardozo and others added 30 commits February 9, 2026 17:43
@Iryna-git-hub Iryna-git-hub changed the base branch from sprint2 to sprint1 February 12, 2026 21:04
Copy link
Copy Markdown

@ElizabethSh ElizabethSh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! 👍
Areas for improvement:

  • You overcomplicate your code (see comments). Make it simple and easier to read.
  • You use inline styles a lot instead of using css. It also make your code difficult to read. Please use css instead of inline styles.
  • You use js for layout but you can use media queries in css and change layout depending on breakpoint.
  • Please create separate callback functions and use them in event listeners. It makes code easier to read.
  • Please don't use function before it's initialization.
  • Please create variables for 'magic numbers'.

Comment thread app/styles.css Outdated
}

.level-btn {
background: #10574d;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could create variables for colors. Example:

:root {
  --primary-color: #3498db;
}

button {
  background-color: var(--primary-color);
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your feedback! 😊

We’ve created CSS variables for all main colors and fonts in the code, so it’s now easier to maintain and adjust.

Comment thread app/script.js

const cardFrontImageSrc = "Images/lotus-flower.png";

const levelSettings = {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are these magic numbers 6, 8, 10 and 60, 90, 120?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Lisa!
We’ve implemented the requested changes to remove magic numbers.

Comment thread app/script.js Outdated
}

secondCard = card;
checkForMatch();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use function before initialization.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We reordered functions so checkForMatch() is fully defined before flipCard() calls it.

Comment thread app/script.js Outdated
}

function setGridColumns(numberOfPairs = defaultNumberOfPairs) {
const isMobile = window.innerWidth <= 600;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is 600? Please create variable for it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for highlighting this. We implemented another solution using CSS:
.container {
grid-template-columns: repeat(4, 1fr);
}

@media (min-width: 600px) {
.container {
grid-template-columns: repeat(var(--columns, 4), 1fr);
}
}

Comment thread app/script.js Outdated
Comment on lines +186 to +194
let columns;

if (isMobile) {
columns = 4;
} else {
if (numberOfPairs <= 8) columns = 4;
else if (numberOfPairs <= 10) columns = 5;
else columns = 6;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks too complicated and formatting is off. You could define default value for columns e.g.
let columns = 4;
and then change it in if statements. In this case you don't need to use 1 if.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean. We improved the formatting:
function setGridColumns(numberOfPairs = defaultNumberOfPairs) {
let columns = 4;
if (numberOfPairs === 10) columns = 5;

gameBoard.style.setProperty("--columns", columns);
}

Comment thread app/script.js Outdated
Comment thread app/script.js
btn.addEventListener("click", () => {
currentPairs = parseInt(btn.dataset.pairs, 10);

levelButtons.forEach((level) => level.classList.remove("active"));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why you use levelButtons inside 'specific' button?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We extracted the active state logic into a dedicated updateActiveLevel() function so the button handler only manages state changes and delegates the group UI update.

Comment thread app/script.js
winner.style.display = "flex";
}

document.addEventListener("DOMContentLoaded", () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I encourage you to create a separate function for this callback.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We extracted the DOMContentLoaded callback into an initializeGame() function to improve readability and keep the event listener focused only on triggering initialization.

Comment thread app/script.js Outdated
createGame(currentPairs);
});

window.addEventListener("resize", () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could just use media queries in css and change layout depending on breakpoint. In this case you don't need setGridColumns function.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the suggestion! Since we solved the setting of the column number for mobile through CSS, the eventListener is not needed and was deleted.

In our solution, number of columns depends on the screen size as well as on number of card pairs:
let columns = 4; (for 6 and 8 pairs)
if (numberOfPairs === 10) columns = 5;

Comment thread app/script.js Outdated
lockBoard = true;

setTimeout(() => {
firstCard.style.transform = "scale(0.5) rotateY(180deg)";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why you don't do it in css? You could create class fr that and add or remove it depending on condition.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for pointing that out. We’ve moved this property to CSS.

@Iryna-git-hub
Copy link
Copy Markdown
Collaborator Author

Hi @ElizabethSh,

Thank you so much for taking the time to review our pull request. Your feedback was really helpful. You pointed out exactly the areas where we had some doubts — like using CSS media queries instead of JS, the “magic numbers,” and refactoring the setGridColumns().
We also really appreciate those improvement hints — that meant a lot.
We’ve taken everything into account and are already working on improving the code.

Comment thread app/script.js
Comment on lines +180 to +185
if (
lockBoard ||
card === firstCard ||
card.classList.contains("flipped") ||
card.classList.contains("matched")
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if statement block is duplicated here

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the current version, and the condition only exists inside flipCard().
I’ve grouped it into a single conditional for clarity.

Comment thread app/script.js
}

function showTimeout() {
lockBoard = true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think lockBoard = true isn't doing anything here, because resetBoard() flips it immediately to false again.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!! resetBoard() was implicitly flipping lockBoard back to false, which made lockBoard = true inside showTimeout() ineffective.
We refactored resetBoard() to clear only the selected cards, and now explicitly manage lockBoard in unflipCards(), createGame(), and disableCards().

Comment thread app/script.js
resetBoard();
};

secondCard.addEventListener("transitionend", handleTransitionEnd);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip for future projects :) - Don't need to change this now:

I can see why this works here, but I would encourage to not tie logic to animations changes.

The problem is that transitionend is not always "guaranteed" to fire.

  • There are browser settings like "prefer reduced motion" that will make animations not execute.
  • Battery saver (low battery on mobile devices, fx.) can cause animations to be skipped
  • other things like changing the focused tab to another tab in your browser might also make this event not run.

An alternative tradeoff here (less "elegant", but more reliable) would be to use a setTimeout that matches the duration of your expected animation.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Jesús,

Thanks for the tip! We actually refactored the card-flipping logic in the current version. Instead of relying on transitionend, we now use setTimeout with the duration matching the expected animation (flipBackDelay for unflipping cards and matchedDelay for matched cards). This way, the game flow and resetBoard() work reliably even if animations are skipped due to browser settings, battery saver, or reduced motion preferences.

Just to let you know, your concern about transitionend not firing is already addressed in this version. 😊

Thanks for the guidance!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jesusoterogomez thank you for explaining the drawbacks of this approach and for suggesting a more appropriate solution!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I used the transitionend for this functionality, my reasoning was that relying on setTimeout and hardcoding it to match the CSS animation duration could make the logic fragile. For example, if the animation duration in CSS is 0.8 seconds, we would set the timeout to 800ms. But if someone later changes the animation duration in CSS to 1 second, the JavaScript logic would no longer be in sync and could break. That’s why I preferred using transitionend, so the logic reacts to the actual end of the animation rather than a fixed time value. @jesusoterogomez could you please share your thoughts on this?

Copy link
Copy Markdown

@jesusoterogomez jesusoterogomez Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your logic makes sense here, but the tradeoff is that matching it to the CSS makes it easier to modify in one place, but at the cost of reliability.

Commonly, in bigger projects you would use an "animation library", that controls all animations via JS, which would eliminate this issue of matching JS and CSS variables.

I'm thinking of alternate approaches, and I think it's possible to solve it with JS and CSS variables:

I haven't tried this myself, but I believe this works, and it could be a neat and pretty elegant solution

  1. Define your time variable in JS and set it as CSS variable.
// animation duration in ms
const durationInMs = 800;

const card = ... // reference to the element in HTML;

// Set the CSS variable property
card.style.setProperty(
  "--flip-duration",
  durationInMs + "ms"
);
  1. Use the property from the variable in CSS
.card {
  transition: transform var(--flip-duration) ease;
}
  1. Use the same JS variable for the timeout
...
setTimeout(() => {...}, durationInMs);
...

That would make the same values match in CSS and JS, and only defined in one place.

Comment thread postman/postman-tests.md
@@ -0,0 +1,94 @@
# Postman – Test Scripts
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome that you included tests for your endpoints :)

Later, you'll also learn about testing frameworks and how you can do this within your project.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, Jesús! Looking forward to learning more about testing frameworks and integrating them directly into the project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants