Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Winery #122

Merged
merged 5 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,20 @@ The app is online and free to play at <a href="https://playmachikoro.herokuapp.c

### Supply variants

- **Total**: All establishments are available for purchase in the supply. This is the official supply variant of Machi Koro.
- **Variable**: 10 establishments are available for purchase from the supply. This is the official supply variant of the expansions.
- **Hybrid**: 5 establishments with rolls 1-6, 5 establishments with rolls 7+, and 2 major establishments are available for purchase from the supply. This is the official supply variant of Machi Koro 2.
- **Total**: All establishments are available for purchase. This is the official supply variant of Machi Koro.
- **Variable**: 10 establishments are available for purchase. This is the official supply variant of the expansions.
- **Hybrid**: 5 establishments with rolls 1-6, 5 establishments with rolls 7+, and 2 major establishments are available for purchase. This is the official supply variant of Machi Koro 2.

### Implementation details
### Millionaire's Row expansion implementation details

Because this game was implemented to be automatic as much as possible, there are certain uncommon plays that are not possible.
Some rulings that are not explicit in the game rules:

- `Winery` cards will activate and close for renovations even if the player owns no `Vineyard` cards.

Because this implementation is automated as much as possible, there are certain uncommon plays that are not possible:

- `Loan Office` cannot activate after `Forest` or `Flower Shop`.
- `Moving Company` cannot activate before `Mine`, `Winery`, or `Apple Orchard`.

## Development

Expand Down
13 changes: 7 additions & 6 deletions src/board/PlayerInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,29 +102,30 @@ export default class PlayerInfo extends React.Component<PlayerInfoProps, object>
for (let i = 0; i < ownedEsts.length; i++) {
const est = ownedEsts[i];
const count = Est.countOwned(G, player, est);
const countRenovation = Est.countRenovation(G, player, est);

let canDoOffice: boolean;
let doOffice: (est: Est.Establishment) => void;
let doOffice: (est: Est.Establishment, renovation: boolean) => void;
if (player === currentPlayer) {
canDoOffice = isActive && Game.canDoOfficeGive(G, ctx, est);
doOffice = (est) => moves.doOfficeGive(est);
doOffice = (est, renovation) => moves.doOfficeGive(est, renovation);
} else {
canDoOffice = isActive && Game.canDoOfficeTake(G, ctx, player, est);
doOffice = (est) => moves.doOfficeTake(player, est);
doOffice = (est, renovation) => moves.doOfficeTake(player, est, renovation);
}

const estColor = estColorToClass(est.color, canDoOffice);

const estRollBoxes = formatRollBoxes(est.rolls, 'mini_roll_box');
const estDescription = parseMaterialSymbols(est.name + '\n\n' + est.description);

for (let j = 0; j < count; j++) {
const key = `${i}_${j}`;
const renovation = j < countRenovation; // true if establishment should display as "closed under renovations"
const estColor = estColorToClass(est.color, canDoOffice, renovation);
minis.push(
<td
key={key}
className={classNames('mini_td', estColor, { clickable: canDoOffice })}
onClick={() => doOffice(est)}
onClick={() => doOffice(est, renovation)}
>
<div className='mini_roll'>{estRollBoxes}</div>
<div className='mini_type'>
Expand Down
6 changes: 5 additions & 1 deletion src/board/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ import classNames from 'classnames';
* Convert `Est.EstColor` to CSS class name.
* @param color
* @param darker - If true, uses darker variant.
* @param renovation - True if establishment is closed under renovations.
* @returns
*/
export const estColorToClass = (color: Est.EstColor, darker: boolean): string => {
export const estColorToClass = (color: Est.EstColor, darker: boolean, renovation = false): string => {
if (renovation) {
return darker ? 'est_img_grey' : 'est_img_grey_light';
}
switch (color) {
case Est.EstColor.Blue:
return darker ? 'est_img_pri' : 'est_img_pri_light';
Expand Down
49 changes: 43 additions & 6 deletions src/game/establishments/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ export const isInUse = (est: Establishment, version: Version, expansions: Expans
*/
export const countRemaining = (G: MachikoroG, est: Establishment): number => {
if (G.version !== est.version) {
console.warn(`Establishment id=${est._id} ver=${est.version} does not match the game version, ${G.version}.`);
return 0;
}
return G.estData._remainingCount[est._id];
Expand All @@ -73,7 +72,6 @@ export const countRemaining = (G: MachikoroG, est: Establishment): number => {
*/
export const countAvailable = (G: MachikoroG, est: Establishment): number => {
if (G.version !== est.version) {
console.warn(`Establishment id=${est._id} ver=${est.version} does not match the game version, ${G.version}.`);
return 0;
}
return G.estData._availableCount[est._id];
Expand All @@ -88,7 +86,6 @@ export const countAvailable = (G: MachikoroG, est: Establishment): number => {
*/
export const countOwned = (G: MachikoroG, player: number, est: Establishment): number => {
if (G.version !== est.version) {
console.warn(`Establishment id=${est._id} ver=${est.version} does not match the game version, ${G.version}.`);
return 0;
}
return G.estData._ownedCount[player][est._id];
Expand Down Expand Up @@ -159,7 +156,7 @@ export const countTypeOwned = (G: MachikoroG, player: number, type: EstType): nu
*/
export const buy = (G: MachikoroG, player: number, est: Establishment): void => {
if (G.version !== est.version) {
throw new Error(`Establishment id=${est._id} ver=${est.version} does not match the game version, ${G.version}.`);
throw new Error(`Establishment ${est.name} does not match the game version, ${G.version}.`);
}
G.estData._remainingCount[est._id] -= 1;
G.estData._availableCount[est._id] -= 1;
Expand All @@ -172,13 +169,53 @@ export const buy = (G: MachikoroG, player: number, est: Establishment): void =>
* @param args.from - Source player.
* @param args.to - Destination player.
* @param est - Establishment in question.
* @param renovation - True if the establishment being transferred is closed
* for renovations.
*/
export const transfer = (G: MachikoroG, args: { from: number; to: number }, est: Establishment): void => {
export const transfer = (
G: MachikoroG,
args: { from: number; to: number },
est: Establishment,
renovation: boolean,
): void => {
if (G.version !== est.version) {
throw new Error(`Establishment id=${est._id} ver=${est.version} does not match the game version, ${G.version}.`);
throw new Error(`Establishment ${est.name} does not match the game version, ${G.version}.`);
}
G.estData._ownedCount[args.from][est._id] -= 1;
G.estData._ownedCount[args.to][est._id] += 1;
if (renovation) {
G.estData._renovationCount[args.from][est._id] -= 1;
G.estData._renovationCount[args.to][est._id] += 1;
}
};

/**
* @param G
* @param player
* @param est
* @returns The number of establishments of this kind that are owned by the
* player and are under renovations.
*/
export const countRenovation = (G: MachikoroG, player: number, est: Establishment): number => {
if (G.version !== est.version) {
return 0;
}
return G.estData._renovationCount[player][est._id];
};

/**
* Update `G` to reflect the number of establishments of this kind that are
* owned by the player and are under renovations.
* @param G
* @param player
* @param est
* @param count
*/
export const setRenovationCount = (G: MachikoroG, player: number, est: Establishment, count: number): void => {
if (G.version !== est.version) {
throw new Error(`Establishment ${est.name} does not match the game version, ${G.version}.`);
}
G.estData._renovationCount[player][est._id] = count;
};

/**
Expand Down
1 change: 0 additions & 1 deletion src/game/establishments/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,6 @@ export const Mine: Establishment = {
_initial: 6,
};

// TODO: implement this
export const Winery: Establishment = {
_id: 21,
version: Version.MK1,
Expand Down
5 changes: 1 addition & 4 deletions src/game/landmarks/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export const isInUse = (land: Landmark, version: Version, expansions: Expansion[
*/
export const isAvailable = (G: MachikoroG, land: Landmark): boolean => {
if (G.version !== land.version) {
console.warn(`Landmark id=${land._id} ver=${land.version} does not match the game version, ${G.version}.`);
return false;
}
return G.landData._available[land._id];
Expand All @@ -51,7 +50,6 @@ export const isAvailable = (G: MachikoroG, land: Landmark): boolean => {
*/
export const owns = (G: MachikoroG, player: number, land: Landmark): boolean => {
if (G.version !== land.version) {
// this is used often for hard-coded landmarks, so no need to warn
return false;
}
return G.landData._owned[player][land._id];
Expand All @@ -64,7 +62,6 @@ export const owns = (G: MachikoroG, player: number, land: Landmark): boolean =>
*/
export const isOwned = (G: MachikoroG, land: Landmark): boolean => {
if (G.version !== land.version) {
// this is used often for hard-coded landmarks, so no need to warn
return false;
}
return G.landData._owned.some((ownedArr) => ownedArr[land._id]);
Expand Down Expand Up @@ -179,7 +176,7 @@ export const costArray = (G: MachikoroG, land: Landmark, player: number | null):
export const buy = (G: MachikoroG, player: number, land: Landmark): void => {
const version = G.version;
if (version !== land.version) {
throw new Error(`Landmark id=${land._id} ver=${land.version} does not match the game version, ${G.version}.`);
throw new Error(`Landmark ${land.name} does not match the game version, ${G.version}.`);
}
G.landData._owned[player][land._id] = true;
// in Machi Koro 2, each landmark can only be bought by one player
Expand Down
Loading
Loading