Skip to content

Commit

Permalink
Implement Exhibit Hall (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinddchen authored Oct 17, 2023
1 parent aff9606 commit 8768e2f
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 76 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ The app is online and free to play at <a href="https://playmachikoro.herokuapp.c

In the Millionaire's Row expansion, there are certain uncommon plays that are not possible:

- `Demolition Company` cannot be activated before `Corn Field`.
- `Loan Office` cannot be activated after `Forest` or `Flower Shop`.
- `Moving Company` cannot be activated before `Mine`, `Winery`, or `Apple Orchard`.
- `Renovation Company` cannot be activated before `Tax Office`.
- `Demolition Company` cannot be activated after `Corn Field`. This is uncommon because you could get fewer coins.
- `Loan Office` cannot be activated after `Forest` or `Flower Shop`. This is uncommon because you could get fewer coins.
- `Moving Company` cannot be activated before `Mine`, `Winery`, or `Apple Orchard`. This is done because (i) you could get fewer coins, and (ii) it is ambiguous whether establishments you give away should be activated, so this keeps things simple.
- `Renovation Company` cannot be activated before `Tax Office`. This is uncommon because you could get fewer coins.
- `Exhibit Hall` cannot be activated before `Tech Startup` (e.g. to activate `Loan Office`). While this could give you more coins, (i) it is quite rare, and (ii) activating `Tech Startup` first makes it more clean how many coins you have which can help with picking the establishment to activate.

## Development

Expand Down
5 changes: 4 additions & 1 deletion src/board/Buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ export default class Buttons extends React.Component<BoardProps<MachikoroG>, obj

const canSkipOffice = isActive && Game.canSkipOffice(G);
const canSkipRenovationCompany = isActive && skipRenovationCompanyEst !== null;
const canSkipExhibitHall = isActive && Game.canSkipExhibitHall(G);

const skipButtonActive = canSkipOffice || canSkipRenovationCompany;
const skipButtonActive = canSkipOffice || canSkipRenovationCompany || canSkipExhibitHall;
let onClickSkipEvent: () => void;
if (canSkipOffice) {
onClickSkipEvent = () => moves.skipOffice();
} else if (canSkipRenovationCompany) {
onClickSkipEvent = () => moves.doRenovationCompany(skipRenovationCompanyEst);
} else if (canSkipExhibitHall) {
onClickSkipEvent = () => moves.skipExhibitHall();
} else {
onClickSkipEvent = () => void 0;
}
Expand Down
6 changes: 5 additions & 1 deletion src/board/PlayerInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,21 @@ export default class PlayerInfo extends React.Component<PlayerInfoProps, object>
const canDoOfficeGive = isActive && player === currentPlayer && Game.canDoOfficeGive(G, ctx, est);
const canDoOfficeTake = isActive && player !== currentPlayer && Game.canDoOfficeTake(G, ctx, player, est);
const canDoRenovationCompany = isActive && Game.canDoRenovationCompany(G, est);
const canDoExhibitHall = isActive && player === currentPlayer && Game.canDoExhibitHall(G, ctx, est);
const canInvestTechStartup =
isActive && player === currentPlayer && Est.isEqual(est, Est.TechStartup) && Game.canInvestTechStartup(G, ctx);

const estClickable = canDoOfficeGive || canDoOfficeTake || canDoRenovationCompany || canInvestTechStartup;
const estClickable =
canDoOfficeGive || canDoOfficeTake || canDoRenovationCompany || canDoExhibitHall || canInvestTechStartup;
let onClickEstEvent: (est: Est.Establishment, renovation: boolean) => void;
if (canDoOfficeGive) {
onClickEstEvent = (est, renovation) => moves.doOfficeGive(est, renovation);
} else if (canDoOfficeTake) {
onClickEstEvent = (est, renovation) => moves.doOfficeTake(player, est, renovation);
} else if (canDoRenovationCompany) {
onClickEstEvent = (est) => moves.doRenovationCompany(est);
} else if (canDoExhibitHall) {
onClickEstEvent = (est) => moves.doExhibitHall(est);
} else if (canInvestTechStartup) {
onClickEstEvent = (est) => moves.investTechStartup(est);
} else {
Expand Down
7 changes: 7 additions & 0 deletions src/board/StatusBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ export default class StatusBar extends React.Component<StatusBarProps, object> {
return currentPlayerName + ' is making a move: Renovation company';
}
}
case Game.TurnState.ExhibitHall: {
if (isActive) {
return 'Exhibit hall: Select an establishment to activate.';
} else {
return currentPlayerName + ' is making a move: Exhibit hall';
}
}
case Game.TurnState.End: {
if (isActive) {
return 'No actions left. End turn?';
Expand Down
23 changes: 23 additions & 0 deletions src/game/establishments/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,29 @@ export const unownedRedBlueGreenEst = (G: MachikoroG): Establishment | null => {
return null;
};

/**
* Update `G` to reflect a player demolishing an establishment. Only
* implemented for Machi Koro 1.
* @param G
* @param player
* @param est
* @param renovation - True if the establishment is closed for renovations.
*/
export const demolish = (G: MachikoroG, player: number, est: Establishment, renovation: boolean): void => {
const version = G.version;
if (version !== Version.MK1) {
throw new Error('Demolishing establishments is only implemented for Machi Koro 1.');
} else if (version !== est.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;
G.estData._ownedCount[player][est._id] -= 1;
if (renovation) {
G.estData._renovationCount[player][est._id] -= 1;
}
};

/**
* @param G
* @param player
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 @@ -532,7 +532,6 @@ export const TechStartup: Establishment = {
_initial: null,
};

// TODO: implement this
export const ExhibitHall: Establishment = {
_id: 37,
version: Version.MK1,
Expand Down
2 changes: 1 addition & 1 deletion src/game/landmarks/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ export const buy = (G: MachikoroG, player: number, land: Landmark): void => {
*/
export const demolish = (G: MachikoroG, player: number, land: Landmark): void => {
const version = G.version;
if (version === Version.MK2) {
if (version !== Version.MK1) {
throw new Error('Demolishing landmarks is only implemented for Machi Koro 1.');
} else if (version !== land.version) {
throw new Error(`Landmark ${land.name} does not match the game version, ${G.version}.`);
Expand Down
41 changes: 35 additions & 6 deletions src/game/log/parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const LogEventType = {
DemolitionCompany: 'DemolitionCompany',
MovingCompany: 'MovingCompany',
Park: 'Park',
ExhibitHall: 'ExhibitHall',
TechStartup: 'TechStartup',
TunaRoll: 'TunaRoll',
EndGame: 'EndGame',
Expand All @@ -48,6 +49,7 @@ export type LogEvent =
| DemolitionCompany
| MovingCompany
| Park
| ExhibitHall
| TechStartup
| TunaRoll
| EndGame
Expand All @@ -74,6 +76,8 @@ export const parseLogEvent = (logEvent: LogEvent, names: string[]): string => {
return parseMovingCompany(logEvent, names);
} else if (logEvent.type === LogEventType.Park) {
return parsePark(logEvent);
} else if (logEvent.type === LogEventType.ExhibitHall) {
return parseExhibitHall(logEvent);
} else if (logEvent.type === LogEventType.TechStartup) {
return parseInvestTechStartup(logEvent);
} else if (logEvent.type === LogEventType.TunaRoll) {
Expand Down Expand Up @@ -325,18 +329,18 @@ const parseDemolitionCompany = (logEvent: DemolitionCompany): string => {

interface MovingCompany extends BaseLogEvent {
type: typeof LogEventType.MovingCompany;
est_name: string;
estName: string;
opponent: number;
}

/**
* Log the effect of the Moving Company establishment / landmark.
* @param G
* @param est_name
* @param estName
* @param opponent
*/
export const logMovingCompany = (G: MachikoroG, est_name: string, opponent: number): void => {
const logEvent: MovingCompany = { type: LogEventType.MovingCompany, est_name, opponent };
export const logMovingCompany = (G: MachikoroG, estName: string, opponent: number): void => {
const logEvent: MovingCompany = { type: LogEventType.MovingCompany, estName, opponent };
G._logBuffer.push(logEvent);
};

Expand All @@ -346,8 +350,8 @@ export const logMovingCompany = (G: MachikoroG, est_name: string, opponent: numb
* @returns Displayed log text for the Moving Company establishment / landmark.
*/
const parseMovingCompany = (logEvent: MovingCompany, names: string[]): string => {
const { est_name, opponent } = logEvent;
return `\tgave ${est_name} to ${names[opponent]} (Moving Company)`;
const { estName, opponent } = logEvent;
return `\tgave ${estName} to ${names[opponent]} (Moving Company)`;
};

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -377,6 +381,31 @@ const parsePark = (logEvent: Park): string => {

// ----------------------------------------------------------------------------

interface ExhibitHall extends BaseLogEvent {
type: typeof LogEventType.ExhibitHall;
estName: string;
}

/**
* Log the effect of the Exhibit Hall establishment (Machi Koro 1)
* @param G
* @param estName
*/
export const logExhibitHall = (G: MachikoroG, estName: string): void => {
const logEvent: ExhibitHall = { type: LogEventType.ExhibitHall, estName };
G._logBuffer.push(logEvent);
};

/**
* @param logEvent
* @returns Displayed log text for the Exhibit Hall establishment (Machi Koro 1).
*/
const parseExhibitHall = (logEvent: ExhibitHall): string => {
return `\tdemolished Exhibit Hall to activate ${logEvent.estName}.`;
};

// ----------------------------------------------------------------------------

interface TechStartup extends BaseLogEvent {
type: typeof LogEventType.TechStartup;
newInvestment: number;
Expand Down
Loading

0 comments on commit 8768e2f

Please sign in to comment.