tmemory
Version:
A terminal-based Memory card game built with React Ink. Features multiple grid sizes, AI opponent, and high scores.
146 lines (145 loc) • 5.23 kB
JavaScript
import { createStandardDeck } from 'ink-playing-cards';
import { isValidGrid } from "../../constants/gridPresets.js";
export const createPairedDeck = () => {
// Create multiple standard decks to ensure we have enough cards
const standardDeck = [
...createStandardDeck(),
...createStandardDeck(),
...createStandardDeck(),
...createStandardDeck(),
];
// Group cards by their value
const groupedByValue = standardDeck.reduce((acc, card) => {
if ('value' in card && 'suit' in card) {
const value = card.value;
const cards = acc.get(value) || [];
cards.push({
value,
suit: card.suit,
faceUp: false,
selected: false,
});
acc.set(value, cards);
}
return acc;
}, new Map());
// Create array of pairs
const pairs = [];
groupedByValue.forEach((cards) => {
while (cards.length >= 2) {
const card1 = cards.pop();
const card2 = cards.pop();
pairs.push([
{ ...card1, faceUp: false },
{ ...card2, faceUp: false },
]);
}
});
// Randomize and flatten
return pairs.sort(() => Math.random() - 0.5).flat();
};
export const cycleGameMode = (current) => {
switch (current) {
case 'single':
return 'vs-player';
case 'vs-player':
return 'vs-ai';
case 'vs-ai':
return 'single';
}
};
export const getNextPlayer = (currentPlayer, gameMode) => {
switch (gameMode) {
case 'single':
return 'p1';
case 'vs-player':
return currentPlayer === 'p1' ? 'p2' : 'p1';
case 'vs-ai':
return currentPlayer === 'p1' ? 'ai' : 'p1';
}
};
export const findAIMove = (grid, matchedIndices, flippedIndices = []) => {
// Get all unmatched cards
const unmatched = grid
.map((card, index) => ({ card, index }))
.filter(({ index }) => !matchedIndices.includes(index));
if (unmatched.length < 2)
return [0, 1];
// Get cards that have been seen (previously flipped)
const seenCards = new Map();
grid.forEach((card, index) => {
if (card.faceUp || flippedIndices.includes(index)) {
seenCards.set(card.value, index);
}
});
// Check for a matching pair among seen cards
for (const [value, index] of seenCards.entries()) {
const matchingCard = unmatched.find(({ card, index: idx }) => idx !== index &&
!matchedIndices.includes(idx) &&
!flippedIndices.includes(idx) &&
card.value === value);
if (matchingCard) {
return [index, matchingCard.index];
}
}
// If no known pairs, try to flip a card we haven't seen yet
const unseenCards = unmatched.filter(({ card }) => !seenCards.has(card.value));
if (unseenCards.length > 0) {
// Pick one unseen card
const randomUnseenIndex = Math.floor(Math.random() * unseenCards.length);
const randomUnseen = unseenCards[randomUnseenIndex];
if (!randomUnseen) {
// Fallback to random selection if something went wrong
return [unmatched[0]?.index ?? 0, unmatched[1]?.index ?? 1];
}
// And one random card from remaining unmatched cards
const otherCards = unmatched.filter(({ index }) => index !== randomUnseen.index);
if (otherCards.length === 0) {
// Fallback if no other cards available
return [randomUnseen.index, unmatched[0]?.index ?? 0];
}
const randomOtherIndex = Math.floor(Math.random() * otherCards.length);
const randomOther = otherCards[randomOtherIndex];
if (!randomOther) {
// Fallback if random selection failed
return [randomUnseen.index, otherCards[0]?.index ?? 0];
}
return [randomUnseen.index, randomOther.index];
}
// Fallback to random selection if all cards have been seen
const shuffledIndices = unmatched
.map(({ index }) => index)
.sort(() => Math.random() - 0.5)
.slice(0, 2);
return [shuffledIndices[0] ?? 0, shuffledIndices[1] ?? 1];
};
export const adjustGridDimension = (current, direction) => {
const next = { ...current };
switch (direction) {
case 'up':
next.rows = Math.min(next.rows + 1, 12);
break;
case 'down':
next.rows = Math.max(next.rows - 1, 1);
break;
case 'left':
next.cols = Math.max(next.cols - 1, 1);
break;
case 'right':
next.cols = Math.min(next.cols + 1, 12);
break;
}
return isValidGrid(next) ? next : current;
};
export const determineWinner = (state) => {
const { gameMode, scores } = state;
if (gameMode === 'vs-ai') {
return scores.p1 > scores.ai ? 'Player' : scores.ai > scores.p1 ? 'AI' : 'Nobody';
}
else if (gameMode === 'vs-player') {
return scores.p1 > scores.p2 ? 'P1' : scores.p2 > scores.p1 ? 'P2' : 'Nobody';
}
else {
return 'Player';
}
};