Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a9958a659 | ||
|
|
48b47e9bdb | ||
|
|
41aa205d04 | ||
|
|
388c25451d | ||
|
|
fa827ac792 | ||
|
|
c26138ffb5 |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,3 +1,24 @@
|
||||
## [2.16.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v2.16.3...v2.16.4) (2025-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* correct Docker CMD to use root-level server.js ([48b47e9](https://github.com/antialias/soroban-abacus-flashcards/commit/48b47e9bdb0da44746282cd7cf7599a69bf5130d))
|
||||
|
||||
## [2.16.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v2.16.2...v2.16.3) (2025-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use game state playerMetadata instead of GameModeContext in UI components ([388c254](https://github.com/antialias/soroban-abacus-flashcards/commit/388c25451d11b85236c1f7682fe2f7a62a15d5eb))
|
||||
|
||||
## [2.16.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v2.16.1...v2.16.2) (2025-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use only local user's players in LocalMemoryPairsProvider ([c26138f](https://github.com/antialias/soroban-abacus-flashcards/commit/c26138ffb55a237a99cb6ff399c8a2ac54a22b51))
|
||||
|
||||
## [2.16.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v2.16.0...v2.16.1) (2025-10-09)
|
||||
|
||||
|
||||
|
||||
@@ -50,4 +50,4 @@ ENV PORT 3000
|
||||
ENV HOSTNAME "0.0.0.0"
|
||||
|
||||
# Start the application
|
||||
CMD ["node", "apps/web/server.js"]
|
||||
CMD ["node", "server.js"]
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useSpring, animated } from '@react-spring/web'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { css } from '../../../../../styled-system/css'
|
||||
import { useGameMode } from '../../../../contexts/GameModeContext'
|
||||
import { useViewerId } from '@/hooks/useViewerId'
|
||||
import { useMemoryPairs } from '../context/MemoryPairsContext'
|
||||
import { getGridConfiguration } from '../utils/cardGeneration'
|
||||
import { GameCard } from './GameCard'
|
||||
@@ -173,7 +173,7 @@ function HoverAvatar({
|
||||
|
||||
export function MemoryGrid() {
|
||||
const { state, flipCard, hoverCard, gameMode } = useMemoryPairs()
|
||||
const { players: playerMap } = useGameMode()
|
||||
const { data: viewerId } = useViewerId()
|
||||
|
||||
// Track card element refs for positioning hover avatars
|
||||
const cardRefs = useRef<Map<string, HTMLElement>>(new Map())
|
||||
@@ -182,9 +182,11 @@ export function MemoryGrid() {
|
||||
const isMyTurn = useMemo(() => {
|
||||
if (gameMode === 'single') return true // Always your turn in single player
|
||||
|
||||
const currentPlayerData = playerMap.get(state.currentPlayer)
|
||||
return currentPlayerData?.isLocal === true
|
||||
}, [state.currentPlayer, playerMap, gameMode])
|
||||
// In local games, all players belong to current user, so always their turn
|
||||
// In room games, check if current player belongs to this user
|
||||
const currentPlayerMetadata = state.playerMetadata?.[state.currentPlayer]
|
||||
return currentPlayerMetadata?.userId === viewerId
|
||||
}, [state.currentPlayer, state.playerMetadata, viewerId, gameMode])
|
||||
|
||||
// Hooks must be called before early return
|
||||
const gridConfig = useMemo(() => getGridConfiguration(state.difficulty), [state.difficulty])
|
||||
@@ -200,16 +202,8 @@ export function MemoryGrid() {
|
||||
|
||||
// Get player metadata for hover avatars
|
||||
const getPlayerHoverInfo = (playerId: string) => {
|
||||
// Check playerMetadata first (from room members)
|
||||
if (state.playerMetadata && state.playerMetadata[playerId]) {
|
||||
return {
|
||||
emoji: state.playerMetadata[playerId].emoji,
|
||||
name: state.playerMetadata[playerId].name,
|
||||
color: state.playerMetadata[playerId].color,
|
||||
}
|
||||
}
|
||||
// Fall back to local player map
|
||||
const player = playerMap.get(playerId)
|
||||
// Get player info from game state metadata
|
||||
const player = state.playerMetadata?.[playerId]
|
||||
return player
|
||||
? {
|
||||
emoji: player.emoji,
|
||||
@@ -387,8 +381,10 @@ export function MemoryGrid() {
|
||||
Object.entries(state.playerHovers)
|
||||
.filter(([playerId]) => {
|
||||
// Don't show your own hover avatar (only show remote players)
|
||||
const player = playerMap.get(playerId)
|
||||
return player?.isLocal !== true
|
||||
// In local games, all players belong to this user
|
||||
// In room games, check if player belongs to different user
|
||||
const player = state.playerMetadata?.[playerId]
|
||||
return player?.userId !== viewerId
|
||||
})
|
||||
.map(([playerId, cardId]) => {
|
||||
const playerInfo = getPlayerHoverInfo(playerId)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { css } from '../../../../../styled-system/css'
|
||||
import { useGameMode } from '../../../../contexts/GameModeContext'
|
||||
import { gamePlurals } from '../../../../utils/pluralization'
|
||||
import { useMemoryPairs } from '../context/MemoryPairsContext'
|
||||
|
||||
@@ -10,12 +9,12 @@ interface PlayerStatusBarProps {
|
||||
}
|
||||
|
||||
export function PlayerStatusBar({ className }: PlayerStatusBarProps) {
|
||||
const { players: playerMap, activePlayers: activePlayerIds } = useGameMode()
|
||||
const { state } = useMemoryPairs()
|
||||
|
||||
// Get active players array
|
||||
const activePlayersData = Array.from(activePlayerIds)
|
||||
.map((id) => playerMap.get(id))
|
||||
// Get active players from game state (not GameModeContext)
|
||||
// This ensures we only show players actually in this game
|
||||
const activePlayersData = state.activePlayers
|
||||
.map((id) => state.playerMetadata?.[id])
|
||||
.filter((p): p is NonNullable<typeof p> => p !== undefined)
|
||||
|
||||
// Map active players to display data with scores
|
||||
@@ -26,7 +25,8 @@ export function PlayerStatusBar({ className }: PlayerStatusBarProps) {
|
||||
displayEmoji: player.emoji,
|
||||
score: state.scores[player.id] || 0,
|
||||
consecutiveMatches: state.consecutiveMatches?.[player.id] || 0,
|
||||
isLocalPlayer: player.isLocal !== false, // Local if not explicitly marked as remote
|
||||
// In local games all players are local, in room games check metadata
|
||||
isLocalPlayer: state.gameMode === 'single' || state.gameMode === 'multiplayer',
|
||||
}))
|
||||
|
||||
// Check if current player is local (your turn) or remote (waiting)
|
||||
|
||||
@@ -4,7 +4,7 @@ import { type ReactNode, useCallback, useEffect, useMemo, useReducer } from 'rea
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useArcadeRedirect } from '@/hooks/useArcadeRedirect'
|
||||
import { useViewerId } from '@/hooks/useViewerId'
|
||||
import { useGameMode } from '../../../../contexts/GameModeContext'
|
||||
import { useUserPlayers } from '@/hooks/useUserPlayers'
|
||||
import { generateGameCards } from '../utils/cardGeneration'
|
||||
import { validateMatch } from '../utils/matchValidation'
|
||||
import { MemoryPairsContext } from './MemoryPairsContext'
|
||||
@@ -283,16 +283,35 @@ function localMemoryPairsReducer(state: MemoryPairsState, action: LocalAction):
|
||||
export function LocalMemoryPairsProvider({ children }: { children: ReactNode }) {
|
||||
const router = useRouter()
|
||||
const { data: viewerId } = useViewerId()
|
||||
const { activePlayerCount, activePlayers: activePlayerIds, players } = useGameMode()
|
||||
|
||||
// LOCAL-ONLY: Get only the current user's players (no room members)
|
||||
const { data: userPlayers = [] } = useUserPlayers()
|
||||
|
||||
// Use arcade redirect to determine button visibility
|
||||
const { canModifyPlayers } = useArcadeRedirect({ currentGame: 'matching' })
|
||||
|
||||
// Get active player IDs as array
|
||||
const activePlayers = Array.from(activePlayerIds)
|
||||
// Build players map from current user's players only
|
||||
const players = useMemo(() => {
|
||||
const map = new Map()
|
||||
userPlayers.forEach((player) => {
|
||||
map.set(player.id, {
|
||||
id: player.id,
|
||||
name: player.name,
|
||||
emoji: player.emoji,
|
||||
color: player.color,
|
||||
isLocal: true,
|
||||
})
|
||||
})
|
||||
return map
|
||||
}, [userPlayers])
|
||||
|
||||
// Get active player IDs from current user's players only
|
||||
const activePlayers = useMemo(() => {
|
||||
return userPlayers.filter((p) => p.isActive).map((p) => p.id)
|
||||
}, [userPlayers])
|
||||
|
||||
// Derive game mode from active player count
|
||||
const gameMode = activePlayerCount > 1 ? 'multiplayer' : 'single'
|
||||
const gameMode = activePlayers.length > 1 ? 'multiplayer' : 'single'
|
||||
|
||||
// Pure client-side state with useReducer
|
||||
const [state, dispatch] = useReducer(localMemoryPairsReducer, initialState)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "soroban-monorepo",
|
||||
"version": "2.16.1",
|
||||
"version": "2.16.4",
|
||||
"private": true,
|
||||
"description": "Beautiful Soroban Flashcard Generator - Monorepo",
|
||||
"workspaces": [
|
||||
|
||||
Reference in New Issue
Block a user