Skip to content

Commit

Permalink
Add turn to log entries, show turn number in next turn, fix linked ac…
Browse files Browse the repository at this point in the history
…tion issues in player component and in log
  • Loading branch information
JessicaMulein committed Oct 24, 2024
1 parent ba145a8 commit 7f4a18e
Show file tree
Hide file tree
Showing 25 changed files with 296 additions and 161 deletions.
1 change: 1 addition & 0 deletions src/__fixtures__/dominion-lib-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export function createMockGame(playerCount: number, overrides?: Partial<IGame>):
timestamp: new Date(),
playerIndex: 0,
currentPlayerIndex: 0,
turn: 1,
action: GameLogActionWithCount.START_GAME,
},
],
Expand Down
26 changes: 7 additions & 19 deletions src/components/DominionAssistant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,32 +68,20 @@ const DominionAssistant: React.FC<DominionAssistantProps> = ({ route, navigation
const nextTurn = () => {
setGameState((prevGame: IGame) => {
const nextPlayerIndex = getNextPlayerIndex(prevGame);
addLogEntry(
prevGame,
nextPlayerIndex,
prevGame.currentPlayerIndex,
GameLogActionWithCount.NEXT_TURN,
{
playerTurnDetails: gameState.players.map((player) => player.turn),
prevPlayerIndex: gameState.currentPlayerIndex,
}
);
addLogEntry(prevGame, nextPlayerIndex, GameLogActionWithCount.NEXT_TURN, {
playerTurnDetails: gameState.players.map((player) => player.turn),
prevPlayerIndex: gameState.currentPlayerIndex,
});
const updatedGame = incrementTurnCountersAndPlayerIndices(prevGame);
return resetPlayerTurnCounters(updatedGame);
});
};

const endGame = () => {
setGameState((prevState: IGame) => {
addLogEntry(
prevState,
NO_PLAYER,
prevState.currentPlayerIndex,
GameLogActionWithCount.END_GAME,
{
prevPlayerIndex: gameState.currentPlayerIndex,
}
);
addLogEntry(prevState, NO_PLAYER, GameLogActionWithCount.END_GAME, {
prevPlayerIndex: gameState.currentPlayerIndex,
});

return {
...prevState,
Expand Down
22 changes: 14 additions & 8 deletions src/components/GameLog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,20 @@ const GameLog: React.FC = () => {
</TableRow>
</TableHead>
<TableBody>
{gameState.log.map((entry, index) => (
<GameLogEntry
key={entry.id || index}
logIndex={index}
entry={entry}
isCurrentPlayer={index === gameState.currentPlayerIndex}
/>
))}
{gameState.log.map((entry, index) => {
const hasLinkedAction = gameState.log.some(
(logEntry) => logEntry.linkedActionId === entry.id
);
return (
<GameLogEntry
key={entry.id || index}
logIndex={index}
entry={entry}
isCurrentPlayer={index === gameState.currentPlayerIndex}
hasLinkedAction={hasLinkedAction}
/>
);
})}
</TableBody>
</Table>
</TableContainer>
Expand Down
14 changes: 12 additions & 2 deletions src/components/GameLogEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,15 @@ interface GameLogEntryProps {
logIndex: number;
entry: ILogEntry;
isCurrentPlayer: boolean;
hasLinkedAction: boolean;
}

const GameLogEntry: React.FC<GameLogEntryProps> = ({ logIndex, entry, isCurrentPlayer }) => {
const GameLogEntry: React.FC<GameLogEntryProps> = ({
logIndex,
entry,
isCurrentPlayer,
hasLinkedAction,
}) => {
const { gameState, setGameState } = useGameContext();
const [openUndoDialog, setOpenUndoDialog] = useState(false);

Expand Down Expand Up @@ -127,6 +133,8 @@ const GameLogEntry: React.FC<GameLogEntryProps> = ({ logIndex, entry, isCurrentP
{isNotTriggeredByPlayer && relevantPlayer !== undefined && (
<ColoredPlayerName player={relevantPlayer} marginDirection="left" />
)}
{entry.action === GameLogActionWithCount.NEXT_TURN &&
`\u00A0(\u00A0${entry.turn}\u00A0)`}
{isAttributeChangeOutOfTurn && (
<ChangeCircleIcon
fontSize="small"
Expand All @@ -143,7 +151,9 @@ const GameLogEntry: React.FC<GameLogEntryProps> = ({ logIndex, entry, isCurrentP
</Box>
</TableCell>
<TableCell style={{ width: '10%', textAlign: 'right' }}>
{entry.linkedActionId && <LinkIcon fontSize="small" color="action" />}
{(hasLinkedAction || entry.linkedActionId) && (
<LinkIcon fontSize="small" color="action" />
)}
{canUndoAction(gameState, logIndex) && (
<IconButton onClick={handleUndoClick} size="small">
<Tooltip title="Undo this entry">
Expand Down
157 changes: 103 additions & 54 deletions src/components/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,11 @@ import SettingsIcon from '@mui/icons-material/Settings';
import { useGameContext } from '@/components/GameContext';
import SuperCapsText from '@/components/SuperCapsText';
import IncrementDecrementControl from '@/components/IncrementDecrementControl';
import { ILogEntry } from '@/game/interfaces/log-entry';
import { updatePlayerField } from '@/game/dominion-lib';
import { addLogEntry, victoryFieldToGameLogAction } from '@/game/dominion-lib-log';
import { addLogEntry, fieldSubfieldToGameLogAction } from '@/game/dominion-lib-log';
import { GameLogActionWithCount } from '@/game/enumerations/game-log-action-with-count';
import { PlayerFieldMap } from '@/game/types';
import { useAlert } from '@/components/AlertContext';
import { FailedAddLogEntryError } from '@/game/errors/failed-add-log';
import '@/styles.scss';
import { IGame } from '@/game/interfaces/game';

Expand Down Expand Up @@ -76,46 +74,97 @@ const Player: React.FC = () => {
subfield: PlayerFieldMap[T],
increment: number,
linkedActionId?: string
): ILogEntry => {
let logEntry: ILogEntry | undefined;
setGameState((prevState: IGame) => {
try {
const updatedGame = updatePlayerField(
prevState,
prevState.selectedPlayerIndex,
field,
subfield,
increment
);
if (!updatedGame) {
return prevState;
}
const action = victoryFieldToGameLogAction(field, subfield, increment);
logEntry = addLogEntry(
updatedGame,
updatedGame.selectedPlayerIndex,
updatedGame.currentPlayerIndex,
action,
{
count: Math.abs(increment),
correction: isCorrection,
linkedActionId,
}
);
return updatedGame;
} catch (error) {
if (error instanceof Error) {
showAlert('Could not increment', error.message);
} else {
showAlert('Could not increment', 'Unknown error');
): void => {
const prevGame = { ...gameState };
try {
const updatedGame = updatePlayerField(
prevGame,
prevGame.selectedPlayerIndex,
field,
subfield,
increment
);
const action = fieldSubfieldToGameLogAction(field, subfield, increment);
addLogEntry(updatedGame, updatedGame.selectedPlayerIndex, action, {
count: Math.abs(increment),
correction: isCorrection,
linkedActionId,
});
setGameState(updatedGame);
} catch (error) {
console.error('Error updating game state:', error);
if (error instanceof Error) {
showAlert('Could not increment', error.message);
} else {
showAlert('Could not increment', 'Unknown error');
}
setGameState(prevGame);
}
};

const handleCombinedFieldChange = <T extends keyof PlayerFieldMap>(
decrementField: T,
decrementSubfield: PlayerFieldMap[T],
decrement: number,
incrementField: T,
incrementSubfield: PlayerFieldMap[T],
increment: number
): void => {
const prevGame = { ...gameState };
try {
// Perform the decrement action
const tempGame = updatePlayerField(
prevGame,
prevGame.selectedPlayerIndex,
decrementField,
decrementSubfield,
decrement
);
const decrementAction = fieldSubfieldToGameLogAction(
decrementField,
decrementSubfield,
decrement
);
const decrementLogEntry = addLogEntry(
tempGame,
tempGame.selectedPlayerIndex,
decrementAction,
{
count: Math.abs(decrement),
correction: isCorrection,
}
return prevState;
);

// Perform the increment action using the logEntry ID from the decrement action
const updatedGame = updatePlayerField(
tempGame,
tempGame.selectedPlayerIndex,
incrementField,
incrementSubfield,
increment
);
const incrementAction = fieldSubfieldToGameLogAction(
incrementField,
incrementSubfield,
increment
);
addLogEntry(updatedGame, updatedGame.selectedPlayerIndex, incrementAction, {
count: Math.abs(increment),
correction: isCorrection,
linkedActionId: decrementLogEntry.id,
});

// Update the actual game state with the final updated game
setGameState(updatedGame);
} catch (error) {
if (error instanceof Error) {
showAlert('Could not update field', error.message);
} else {
showAlert('Could not update field', 'Unknown error');
}
});
if (!logEntry) {
throw new FailedAddLogEntryError();
// Rollback to the previous game state
setGameState(prevGame);
}
return logEntry;
};

const handleCorrectionChange = (event: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -137,7 +186,6 @@ const Player: React.FC = () => {
addLogEntry(
newGameState,
newGameState.selectedPlayerIndex,
newGameState.currentPlayerIndex,
GameLogActionWithCount.ADD_PROPHECY,
{ count: 1 }
);
Expand All @@ -158,7 +206,6 @@ const Player: React.FC = () => {
addLogEntry(
newGameState,
newGameState.selectedPlayerIndex,
newGameState.currentPlayerIndex,
GameLogActionWithCount.REMOVE_PROPHECY,
{ count: 1 }
);
Expand Down Expand Up @@ -241,12 +288,14 @@ const Player: React.FC = () => {
value={player.turn.actions}
onIncrement={() => handleFieldChange('turn', 'actions', 1)}
onDecrement={() => {
const record = handleFieldChange('turn', 'actions', -1);
// greatLeaderProphecy gives unlimited actions when the prophecy is empty
if (
gameState.risingSun.greatLeaderProphecy &&
gameState.risingSun.prophecy.suns === 0
) {
handleFieldChange('turn', 'actions', 1, record.id);
handleCombinedFieldChange('turn', 'actions', -1, 'turn', 'actions', 1);
} else {
handleFieldChange('turn', 'actions', -1);
}
}}
/>
Expand All @@ -265,11 +314,13 @@ const Player: React.FC = () => {
</ColumnBox>
{(showMats || showGlobalMats) && (
<ColumnBox>
<CenteredTitle>
<Tooltip title="These player mat values persist between turns">
<SuperCapsText className={`typography-large-title`}>Mats</SuperCapsText>
</Tooltip>
</CenteredTitle>
{showMats && (
<CenteredTitle>
<Tooltip title="These player mat values persist between turns">
<SuperCapsText className={`typography-large-title`}>Mats</SuperCapsText>
</Tooltip>
</CenteredTitle>
)}
{gameState.options.mats.coffersVillagers && (
<>
<IncrementDecrementControl
Expand All @@ -279,8 +330,7 @@ const Player: React.FC = () => {
onIncrement={() => handleFieldChange('mats', 'coffers', 1)}
onDecrement={() => {
// spending a coffer gives a coin
const record = handleFieldChange('mats', 'coffers', -1);
handleFieldChange('turn', 'coins', 1, record.id);
handleCombinedFieldChange('mats', 'coffers', -1, 'turn', 'coins', 1);
}}
/>
<IncrementDecrementControl
Expand All @@ -290,8 +340,7 @@ const Player: React.FC = () => {
onIncrement={() => handleFieldChange('mats', 'villagers', 1)}
onDecrement={() => {
// spending a villager gives an action
const record = handleFieldChange('mats', 'villagers', -1);
handleFieldChange('turn', 'actions', 1, record.id);
handleCombinedFieldChange('mats', 'villagers', -1, 'turn', 'actions', 1);
}}
/>
</>
Expand All @@ -314,7 +363,7 @@ const Player: React.FC = () => {
)}
{showGlobalMats && (
<>
<Box sx={{ paddingTop: 2 }}>
<Box sx={showMats ? { paddingTop: 2 } : {}}>
<CenteredTitle>
<Tooltip title="Global Mats affect all players and persist between turns">
<SuperCapsText className={`typography-large-title`}>
Expand Down
12 changes: 3 additions & 9 deletions src/components/Scoreboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,9 @@ const Scoreboard: React.FC = () => {

const handlePlayerSelect = (index: number) => {
setGameState((prevState: IGame) => {
addLogEntry(
prevState,
index,
prevState.currentPlayerIndex,
GameLogActionWithCount.SELECT_PLAYER,
{
prevPlayerIndex: prevState.selectedPlayerIndex,
}
);
addLogEntry(prevState, index, GameLogActionWithCount.SELECT_PLAYER, {
prevPlayerIndex: prevState.selectedPlayerIndex,
});
return { ...prevState, selectedPlayerIndex: index };
});
};
Expand Down
1 change: 1 addition & 0 deletions src/game/__tests__/dominion-lib-NewGameState.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ describe('NewGameState', () => {
action: GameLogActionWithCount.START_GAME,
playerIndex: initialGameState.firstPlayerIndex,
currentPlayerIndex: initialGameState.currentPlayerIndex,
turn: 1,
} as ILogEntry,
]);
expect(result.supply).toBeDefined();
Expand Down
Loading

0 comments on commit 7f4a18e

Please sign in to comment.