Skip to content

Commit

Permalink
Merge pull request #14 from cophilot/13-extension-support
Browse files Browse the repository at this point in the history
added extension
  • Loading branch information
cophilot authored Sep 14, 2024
2 parents 01fdf9a + 138d13d commit d8a4b82
Show file tree
Hide file tree
Showing 12 changed files with 219 additions and 81 deletions.
183 changes: 102 additions & 81 deletions src/api/BoardScoreTable/BoardScoreTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useEffect, useState } from 'react';
import './BoardScoreTable.scss';
import GameStorage from '../utils/GameStorage';
import { getFunctionForWinMode, WinMode } from '../types/WinMode';
import ExtensionButtons from '../ExtensionButtons/ExtensionButtons';

interface BoardScoreTableProps {
definition: any;
Expand All @@ -24,9 +25,8 @@ function BoardScoreTable({
onCellChange,
}: BoardScoreTableProps) {
const playerSizes = Array.from(Array(playerSize).keys());
const rows = definition.rows || [];
const [rows, setRows] = useState(definition.rows || []);
const tableStyle = parseTableStyle(definition);

const [tableMatrix, setTableMatrix] = useState(
GameStorage.getGameMatrix(
definition.title,
Expand Down Expand Up @@ -106,87 +106,108 @@ function BoardScoreTable({
};

return (
<table className="board-score-table" style={tableStyle}>
<thead>
<tr key="header">
<th key="-1"></th>
{playerSizes.map((index) => (
<th key={index}>
<input
type="text"
placeholder={'P' + (index + 1)}
value={playerNames[index] || ''}
onChange={(e) => {
const newPlayerNames = playerNames.slice();
newPlayerNames[index] =
e.target.value.toUpperCase();
setPlayerNames(newPlayerNames);
GameStorage.setPlayerNames(
definition.title,
newPlayerNames
);
}}
/>
</th>
))}
</tr>
</thead>
<tbody>
{rows.map(
(row: any, index: number) =>
(rounds === -1 || index < rounds) && (
<>
{gameSettings.showHelp && row.icon && (
<>
<ExtensionButtons
definition={definition}
onExtensionOn={(extensionName, extensionDefinition) => {
const extRows = extensionDefinition.rows || [];
extRows.forEach((row: any) => {
row.__extName = extensionName;
});
setRows([...rows, ...extRows]);
}}
onExtensionOff={(extensionName) => {
setRows(
rows.filter(
(row: any) => row.__extName !== extensionName
)
);
}}
/>
<table className="board-score-table" style={tableStyle}>
<thead>
<tr key="header">
<th key="-1"></th>
{playerSizes.map((index) => (
<th key={index}>
<input
type="text"
placeholder={'P' + (index + 1)}
value={playerNames[index] || ''}
onChange={(e) => {
const newPlayerNames =
playerNames.slice();
newPlayerNames[index] =
e.target.value.toUpperCase();
setPlayerNames(newPlayerNames);
GameStorage.setPlayerNames(
definition.title,
newPlayerNames
);
}}
/>
</th>
))}
</tr>
</thead>
<tbody>
{rows.map(
(row: any, index: number) =>
(rounds === -1 || index < rounds) && (
<>
{gameSettings.showHelp && row.icon && (
<tr
className="help-row"
key={'help-row-' + index}>
<td colSpan={playerSize + 1}>
<b>{row.name}</b>
<i>
{row.description
? ' - ' +
row.description
: ''}
</i>
</td>
</tr>
)}
<tr
className="help-row"
key={'help-row-' + index}>
<td colSpan={playerSize + 1}>
<b>{row.name}</b>
<i>
{row.description
? ' - ' + row.description
: ''}
</i>
</td>
key={index}
style={getStyleFromRow(
row,
definition,
index
)}>
<FirstRowCell row={row} />
{playerSizes.map((playerIndex) => (
<InputCell
row={row}
key={playerIndex}
rowIndex={index}
playerIndex={playerIndex}
getValueFunction={getTableValue}
setValueFunction={setTableValue}
/>
))}
</tr>
)}
<tr
key={index}
style={getStyleFromRow(
row,
definition,
index
)}>
<FirstRowCell row={row} />
{playerSizes.map((playerIndex) => (
<InputCell
row={row}
key={playerIndex}
rowIndex={index}
playerIndex={playerIndex}
getValueFunction={getTableValue}
setValueFunction={setTableValue}
/>
))}
</tr>
</>
)
)}
<tr key="total" className="total-row">
<FirstRowCell row={{ name: 'Total' }} />
{totalRow.map((value, playerIndex) => (
<td
key={playerIndex}
className={
'total-cell ' +
(getWinningScore() == value ? 'win' : '')
}>
{isNaN(value) ? 0 : value}
</td>
))}
</tr>
</tbody>
</table>
</>
)
)}
<tr key="total" className="total-row">
<FirstRowCell row={{ name: 'Total' }} />
{totalRow.map((value, playerIndex) => (
<td
key={playerIndex}
className={
'total-cell ' +
(getWinningScore() == value ? 'win' : '')
}>
{isNaN(value) ? 0 : value}
</td>
))}
</tr>
</tbody>
</table>
</>
);
}
export default BoardScoreTable;
Expand Down
Empty file.
78 changes: 78 additions & 0 deletions src/api/ExtensionButtons/ExtensionButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useEffect, useState } from 'react';
import './ExtensionButtons.scss';
import GameStorage from '../utils/GameStorage';

interface ExtensionButtonsProps {
definition: any;
onExtensionOn?: (extensionName: string, extensionDefinition: any) => void;
onExtensionOff?: (extensionName: string) => void;
}

/**
* This is a ExtensionButtons component
* @author cophilot
* @version 1.0.0
* @created 2024-7-21
*/
function ExtensionButtons({
definition,
onExtensionOn,
onExtensionOff,
}: ExtensionButtonsProps) {
const extensionsDefinition = definition.extensions || {};
const extensionsNames = Object.keys(extensionsDefinition);

const [selectedExtensions, setSelectedExtensions] = useState<string[]>(
GameStorage.getSelectedExtension(definition.title, [])
);

const handleExtensionClick = (extensionName: string) => {
let newSelectedExtensions = [...selectedExtensions];
if (selectedExtensions.includes(extensionName)) {
newSelectedExtensions = selectedExtensions.filter(
(name) => name !== extensionName
);
onExtensionOff?.(extensionName);
} else {
newSelectedExtensions.push(extensionName);
onExtensionOn?.(extensionName, extensionsDefinition[extensionName]);
}

GameStorage.setSelectedExtension(
definition.title,
newSelectedExtensions
);
setSelectedExtensions(newSelectedExtensions);
};

useEffect(() => {
GameStorage.getSelectedExtension(definition.title, []).forEach(
(extensionName: string) => {
onExtensionOn?.(
extensionName,
extensionsDefinition[extensionName]
);
}
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<div className="print-hide">
{extensionsNames.length > 0 && <h2>Extensions</h2>}
{extensionsNames.map((name) => (
<button
key={name}
className={
'btn nav-btn ' +
(selectedExtensions.includes(name) ? 'selected' : '')
}
onClick={() => handleExtensionClick(name)}>
{name}
</button>
))}
</div>
);
}
export default ExtensionButtons;
20 changes: 20 additions & 0 deletions src/api/utils/GameStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,26 @@ export default class GameStorage {
);
}

static getSelectedExtension(gameTitle: string, fallback: string[] = []) {
const selectedExtension = localStorage.getItem(
GameStorage.getStorageKeyFromTitle(gameTitle, 'selected-extension')
);
if (selectedExtension === null) {
return fallback;
}
return JSON.parse(selectedExtension);
}

static setSelectedExtension(
gameTitle: string,
selectedExtension: string[]
) {
localStorage.setItem(
GameStorage.getStorageKeyFromTitle(gameTitle, 'selected-extension'),
JSON.stringify(selectedExtension)
);
}

static getGameMatrix(gameTitle: string, fallback: number[][] = []) {
const matrix = localStorage.getItem(
GameStorage.getStorageKeyFromTitle(gameTitle, 'matrix')
Expand Down
Binary file modified src/games/wingspan/assets/bird.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/games/wingspan/assets/bonus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/games/wingspan/assets/eggs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/games/wingspan/assets/nectar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/games/wingspan/assets/resources.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/games/wingspan/assets/round-goals.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/games/wingspan/assets/tucked-card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions src/games/wingspan/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import roundGoalIcon from './assets/round-goals.png';
import bonusIcon from './assets/bonus.png';
import tuckedCardIcon from './assets/tucked-card.png';
import birdIcon from './assets/bird.png';
import nectarIcon from './assets/nectar.png';

export default function getDefinition() {
return {
Expand All @@ -20,28 +21,46 @@ export default function getDefinition() {
rows: [
{
name: 'Birds',
description: 'Birds in the players collection',
icon: birdIcon,
},
{
name: 'Bonus cards',
description: 'Bonus cards in the players collection',
icon: bonusIcon,
},
{
name: 'End-of-round goals',

icon: roundGoalIcon,
},
{
name: 'Eggs on cards',
description: '1 point per egg on cards',
icon: eggIcon,
},
{
name: 'Food on cards',
description: '1 point per food token on cards',
icon: resourceIcon,
},
{
name: 'Tucked cards',
description: '1 point per card tucked under another card',
icon: tuckedCardIcon,
},
],
extensions: {
Oceania: {
rows: [
{
name: 'Nectar',
description:
'5 points for the player with the most nectar and 2 points for the player with the second most nectar per region',
icon: nectarIcon,
},
],
},
},
};
}

0 comments on commit d8a4b82

Please sign in to comment.