Skip to content

Commit

Permalink
Merge pull request #84 from toddmedema/create-react-app
Browse files Browse the repository at this point in the history
Fix bugs from CRA migration with score submission and each game tick counting as new day
  • Loading branch information
toddmedema authored Apr 4, 2024
2 parents 415eadc + cd5cda6 commit a500927
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 38 deletions.
2 changes: 1 addition & 1 deletion src/Types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export interface ScoreType {
score: number;
scoreBreakdown: any;
difficulty: string;
date: string;
date: any; // serverTimestamp from Firestore
uid: string;
}

Expand Down
4 changes: 2 additions & 2 deletions src/components/views/LoadingContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {initFuelPrices} from '../../data/FuelPrices';
import {initWeather} from '../../data/Weather';
import {LOCATIONS} from '../../Constants';
import {SCENARIOS} from '../../Scenarios';
import {newGame, loaded, delta} from '../../reducers/Game';
import {initGame, loaded, delta} from '../../reducers/Game';
import {AppStateType, GameType} from '../../Types';
import Loading, {DispatchProps, StateProps} from './Loading';

Expand Down Expand Up @@ -34,7 +34,7 @@ const mapDispatchToProps = (dispatch: Redux.Dispatch<any>): DispatchProps => {
initFuelPrices(() => {
// Otherwise, generate from scratch
// TODO different scenarios - for example, start with Natural Gas if year is 2000+, otherwise coal
dispatch(newGame({
dispatch(initGame({
facilities: scenario.facilities,
cash: 200000000,
customers: 1030000,
Expand Down
48 changes: 48 additions & 0 deletions src/helpers/Format.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { formatWatts } from './Format';

describe('formatWatts', () => {
it('should correctly format numbers in the tens', () => {
const result = formatWatts(10);
expect(result).toEqual('10W');
});

it('should correctly format numbers in the thousands', () => {
const result = formatWatts(1500);
expect(result).toEqual('1.5kW');
});

it('should correctly format numbers in the millions', () => {
const result = formatWatts(1500000);
expect(result).toEqual('1.5MW');
});

it('should correctly format numbers in the billions', () => {
const result = formatWatts(1500000000);
expect(result).toEqual('1.5GW');
});

it('should correctly format numbers in the trillions', () => {
const result = formatWatts(1500000000000);
expect(result).toEqual('1.5TW');
});

it('should correctly handle the mantissa parameter when 0', () => {
const result = formatWatts(1001, 0);
expect(result).toEqual('1kW');
});

it('should correctly handle the mantissa parameter when 2', () => {
const result = formatWatts(1521, 0);
expect(result).toEqual('1.5kW');
});

it('should correctly handle the mantissa parameter when 3', () => {
const result = formatWatts(1521, 3);
expect(result).toEqual('1.52kW');
});

it('should correctly handle the mantissa parameter when 4', () => {
const result = formatWatts(1521, 4);
expect(result).toEqual('1.521kW');
});
});
12 changes: 11 additions & 1 deletion src/helpers/Format.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
const numbro = require('numbro');

/**
* This function formats a number representing watts into a string with appropriate units.
* It uses the numbro library to format the number with a specified mantissa and a maximum length of max(2, mantissa).
* The function also replaces certain characters to use the correct abbreviations for MegaWatts, GigaWatts, and TeraWatts.
* The result is appended with 'W' to indicate watts.
*
* @param {number} i - The number to be formatted.
* @param {number} mantissa - The number of significant digits to display after the decimal point. Default is 1.
* @returns {string} - The formatted string representing the number in watts with appropriate units.
*/
export function formatWatts(i: number, mantissa = 1): string {
return numbro(i)
.format({
spaceSeparated: false,
average: true,
trimMantissa: true,
totalLength: 2,
totalLength: Math.max(2, mantissa),
mantissa,
})
// lowercase k for thousands in both cases
Expand Down
55 changes: 23 additions & 32 deletions src/reducers/Game.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import numbro from 'numbro';
import { useAppSelector } from '../Hooks';
import {selectUid} from './User';
import {submitHighscore} from './User';
import {getDateFromMinute, getTimeFromTimeline, summarizeHistory, summarizeTimeline} from '../helpers/DateTime';
import {customersFromMarketingSpend, facilityCashBack, getMonthlyPayment, getPaymentInterest} from '../helpers/Financials';
import {formatMoneyConcise, formatWatts} from '../helpers/Format';
import {formatMoneyConcise} from '../helpers/Format';
import {arrayMove} from '../helpers/Math';
import {getFuelPricesPerMBTU} from '../data/FuelPrices';
import {getRawSunlightPercent, getWeather} from '../data/Weather';
import {dialogOpen, dialogClose, snackbarOpen} from './UI';
import {dialogOpen, dialogClose} from './UI';
import {navigate} from './Card';
import {DIFFICULTIES, DOWNPAYMENT_PERCENT, FUELS, GAME_TO_REAL_YEARS, GENERATOR_SELL_MULTIPLIER, INTEREST_RATE_YEARLY, LOAN_MONTHS, ORGANIC_GROWTH_MAX_ANNUAL, RESERVE_MARGIN, TICK_MINUTES, TICK_MS, TICKS_PER_DAY, TICKS_PER_HOUR, TICKS_PER_MONTH, TICKS_PER_YEAR, YEARS_PER_TICK} from '../Constants';
import {GENERATORS, STORAGE} from '../Facilities';
import {logEvent, getDb} from '../Globals';
import {logEvent} from '../Globals';
import {getStorageJson, setStorageKeyValue} from '../LocalStorage';
import {SCENARIOS} from '../Scenarios';
import {store} from '../Store';
import {DateType, FacilityOperatingType, FacilityShoppingType, LocationType, GameType, GeneratorOperatingType, LocalStoragePlayedType, MonthlyHistoryType, ScenarioType, SpeedType, StorageOperatingType, TickPresentFutureType, ScoreType} from '../Types';
import {DateType, FacilityOperatingType, FacilityShoppingType, LocationType, GameType, GeneratorOperatingType, LocalStoragePlayedType, MonthlyHistoryType, ScenarioType, SpeedType, StorageOperatingType, TickPresentFutureType} from '../Types';
const cloneDeep = require('lodash.clonedeep');
const firebase = require('firebase/app');

interface BuildFacilityAction {
facility: FacilityShoppingType;
Expand All @@ -38,6 +36,7 @@ interface NewGameAction {
}

let previousSpeed = 'PAUSED' as SpeedType;
let previousSunrise = 0;
const initialGame: GameType = {
scenarioId: 100,
location: {
Expand Down Expand Up @@ -70,9 +69,8 @@ export const gameSlice = createSlice({
if (now && prev) {
updateSupplyFacilitiesFinances(state, prev, now);

// TODO how to tell if it's a new day without previous state?
// if (state.date.sunrise !== state.date.sunrise) { // If it's a new day / month
if (true) {
if (previousSunrise !== state.date.sunrise) { // If it's a new day / month
previousSunrise = state.date.sunrise;
const history = state.monthlyHistory;
const {cash, customers} = now;

Expand Down Expand Up @@ -144,23 +142,17 @@ export const gameSlice = createSlice({
blackouts: Math.round(-8 * blackoutsTWh),
};
const finalScore = Object.values(score).reduce((a: number, b: number) => a + b);

// TODO - Submit score to highscores - probably need to do this in a thunk or separate action/reducer, since it needs access to user state?
// ideally there would be some way to include this line at the bottom of the dialog below, but I can't figure out how to get access to user state here
// {(!uid) && <p>Your score was not submitted because you were not logged in!</p>}

// const uid = useAppSelector(selectUid);
// if (uid && !scenario.tutorialSteps) {
// const scoreSubmission = {
// score: finalScore,
// scoreBreakdown: score, // For analytics purposes only
// scenarioId: state.scenarioId,
// difficulty: state.difficulty,
// date: firebase.firestore.Timestamp.fromDate(new Date()),
// uid: uid,
// } as ScoreType;
// getDb().collection('scores').add(scoreSubmission);
// }
const difficulty = state.difficulty; // pulling out of state for functions running inside of setTimeout
const scenarioId = state.scenarioId; // pulling out of state for functions running inside of setTimeout

if (!scenario.tutorialSteps) {
setTimeout(() => store.dispatch(submitHighscore({
score: finalScore,
scoreBreakdown: score, // For analytics purposes only
scenarioId,
difficulty,
})), 1);
}

logEvent('scenario_end', {id: state.scenarioId, type: 'win', difficulty: state.difficulty, score: finalScore});
setTimeout(() => store.dispatch(dialogOpen({
Expand All @@ -169,8 +161,8 @@ export const gameSlice = createSlice({
+{score.supply} pts from electricity supplied<br/>
+{score.netWorth} pts from final net worth<br/>
+{score.customers} pts from final customers<br/>
-{score.emissions} pts from emissions<br/>
-{score.blackouts} pts from blackouts<br/>
{score.emissions} pts from emissions<br/>
{score.blackouts} pts from blackouts<br/>
</div>,
open: true,
closeText: 'Keep playing',
Expand All @@ -196,7 +188,7 @@ export const gameSlice = createSlice({
start: (state, action: PayloadAction<Partial<GameType>>) => {
return {...state, ...action.payload};
},
newGame: (state, action: PayloadAction<NewGameAction>) => {
initGame: (state, action: PayloadAction<NewGameAction>) => {
const a = action.payload;
const scenario = SCENARIOS.find((s) => s.id === state.scenarioId) || SCENARIOS[0];
state.timeline = [] as TickPresentFutureType[];
Expand Down Expand Up @@ -285,7 +277,6 @@ export const gameSlice = createSlice({
},
extraReducers:(builder) => {
builder.addCase(dialogOpen, (state) => {
console.log('dialog paus')
previousSpeed = state.speed;
state.speed = 'PAUSED';
return state;
Expand All @@ -297,7 +288,7 @@ export const gameSlice = createSlice({
},
});

export const { tick, delta, newGame, start, quit, buildFacility, sellFacility, reprioritizeFacility, loaded, setSpeed } = gameSlice.actions;
export const { tick, delta, initGame, start, quit, buildFacility, sellFacility, reprioritizeFacility, loaded, setSpeed } = gameSlice.actions;

export default gameSlice.reducer;

Expand Down
19 changes: 17 additions & 2 deletions src/reducers/User.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import {UserType} from '../Types';
import {UserType, ScoreType} from '../Types';
import {RootState} from '../Store';
import { collection, addDoc, serverTimestamp } from 'firebase/firestore';
import {getDb} from '../Globals';

export const initialUser: UserType = {};

Expand All @@ -11,10 +13,23 @@ export const userSlice = createSlice({
delta: (state, action: PayloadAction<Partial<UserType>>) => {
return {...state, ...action.payload};
},
submitHighscore: (state, action: PayloadAction<Partial<ScoreType>>) => {
if (state.uid) {
const scoreSubmission = {
score: action.payload.score,
scoreBreakdown: action.payload.scoreBreakdown, // For analytics purposes only
scenarioId: action.payload.scenarioId,
difficulty: action.payload.difficulty,
date: serverTimestamp(),
uid: state.uid,
} as ScoreType;
addDoc(collection(getDb(), 'scores'), scoreSubmission);
}
}
},
});

export const { delta } = userSlice.actions;
export const { delta, submitHighscore } = userSlice.actions;

export const selectUid = (state: RootState) => state.user.uid;

Expand Down

0 comments on commit a500927

Please sign in to comment.