diff --git a/package.json b/package.json
index aec91fa..25edcaf 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"test": "vitest"
},
"dependencies": {
+ "clsx": "^1.2.1",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
diff --git a/src/common/GameStateInterface.ts b/src/common/GameStateInterface.ts
index a14c3c7..0b0121a 100644
--- a/src/common/GameStateInterface.ts
+++ b/src/common/GameStateInterface.ts
@@ -2,10 +2,11 @@ import { Children } from 'react';
export interface GameState {
antimatter: number;
tickspeedPrice: number;
- tickspeedDeceaseRate: number,
+ tickspeedDeceaseRate: number;
resetGameCounter: number;
galaxyCounter: number;
lastSavedTime: number;
+ showGameSavedNotification: boolean;
dims: Dim[];
}
@@ -20,6 +21,6 @@ export interface Dim {
export type DimProps = {
nthDim: number;
gs: GameState;
- setGameState: (fn: (gameState: GameState) => void) => void;
+ dispatch: Function;
children: string;
};
diff --git a/src/common/initialGameState.ts b/src/common/initialGameState.ts
index 08e6dba..7589ca7 100644
--- a/src/common/initialGameState.ts
+++ b/src/common/initialGameState.ts
@@ -6,7 +6,8 @@ let initialGameState: GameState = {
tickspeedDeceaseRate: 0.11,
resetGameCounter: 2,
galaxyCounter: 0,
- lastSavedTime: 0,
+ lastSavedTime: Date.now(),
+ showGameSavedNotification: false,
dims: [
{
nthDim: 0,
diff --git a/src/common/reducer.tsx b/src/common/reducer.tsx
new file mode 100644
index 0000000..1fe3630
--- /dev/null
+++ b/src/common/reducer.tsx
@@ -0,0 +1,122 @@
+import { Dim, GameState } from './GameStateInterface';
+import initialGameState from './initialGameState';
+
+export const ACTIONS = {
+ TIMER_CALLBACK: 'TIMER_CALLBACK',
+ UPDATE_DIM: 'UPDATE_DIM',
+ UPDATE_10TH_DIM: 'UPDATE_10TH_DIM',
+ UPDATE_TICKSPEED_ONCE: 'UPDATE_TICKSPEED_ONCE',
+ UPDATE_TICKSPEED_MAX: 'UPDATE_TICKSPEED_MAX',
+ RESET_TO_UNLOCK_DIM: 'RESET_TO_UNLOCK_DIM',
+ RESET_TO_UNLOCK_TICKSPEED: 'RESET_TO_UNLOCK_TICKSPEED',
+ RESET_TO_INITIAL_VALUES: 'RESET_TO_INITIAL_VALUES',
+ POP_UP: 'POP_UP',
+ SAVE_DATA: 'SAVE_DATA',
+ TOOGLE_GAME_NOTIFICATION_OPEN: 'TOOGLE_GAME_NOTIFICATION_OPEN',
+ TOOGLE_GAME_NOTIFICATION_CLOSE: 'TOOGLE_GAME_NOTIFICATION_CLOSE',
+};
+
+export function reducer(state: GameState, action: { type: string; payload?: any }) {
+ switch (action.type) {
+ case ACTIONS.TIMER_CALLBACK:
+ return {
+ ...state,
+ antimatter: state.antimatter + state.dims[0].dimCount * state.dims[0].dimFactor,
+ dims: state.dims.map((dim: Dim, index: number) => {
+ return {
+ ...dim,
+ dimCount:
+ dim.dimCount +
+ ((state.dims[index + 1]?.dimCount ?? 0) *
+ (state.dims[index + 1]?.dimFactor ?? 0)) /
+ Math.pow(10, index + 1),
+ };
+ }),
+ };
+
+ case ACTIONS.UPDATE_DIM:
+ return {
+ ...state,
+ antimatter:
+ state.antimatter -
+ state.dims[action.payload.nthDim].dimPrice * action.payload.quantity,
+ dims: state.dims.map((dim: Dim) => {
+ if (dim.nthDim !== action.payload.nthDim) return dim;
+ return {
+ ...dim,
+ dimCount: dim.dimCount + action.payload.quantity,
+ dimFactorCount: dim.dimFactorCount + action.payload.quantity,
+ };
+ }),
+ };
+
+ case ACTIONS.UPDATE_10TH_DIM:
+ return {
+ ...state,
+ dims: state.dims.map((dim: Dim) => {
+ if (dim.nthDim !== action.payload.nthDim) return dim;
+ return {
+ ...dim,
+ dimPrice: dim.dimPrice * 10,
+ dimFactor: dim.dimFactor * 2,
+ dimFactorCount: 0,
+ };
+ }),
+ };
+
+ case ACTIONS.UPDATE_TICKSPEED_ONCE:
+ return {
+ ...state,
+ antimatter: state.antimatter - state.tickspeedPrice,
+ tickspeedPrice: state.tickspeedPrice * 10,
+ };
+ case ACTIONS.UPDATE_TICKSPEED_MAX:
+ return {
+ ...state,
+ antimatter: state.antimatter - action.payload.purchasePrice,
+ tickspeedPrice: state.tickspeedPrice * 10 ** action.payload.maxPurchaseQtys,
+ };
+
+ case ACTIONS.RESET_TO_UNLOCK_DIM:
+ return {
+ ...JSON.parse(initialGameState),
+ galaxyCounter: state.galaxyCounter,
+ tickspeedDeceaseRate: state.tickspeedDeceaseRate,
+ resetGameCounter: state.resetGameCounter + 1,
+ lastSavedTime: state.lastSavedTime,
+ };
+
+ case ACTIONS.RESET_TO_UNLOCK_TICKSPEED:
+ return {
+ ...JSON.parse(initialGameState),
+ galaxyCounter: state.galaxyCounter + 1,
+ tickspeedDeceaseRate: state.tickspeedDeceaseRate * 1.1,
+ lastSavedTime: state.lastSavedTime,
+ };
+
+ case ACTIONS.RESET_TO_INITIAL_VALUES:
+ return JSON.parse(initialGameState);
+
+ case ACTIONS.SAVE_DATA:
+ return {
+ ...state,
+ lastSavedTime: Date.now(),
+ };
+
+ case ACTIONS.TOOGLE_GAME_NOTIFICATION_CLOSE: {
+ return {
+ ...state,
+ showGameSavedNotification: false,
+ };
+ }
+ case ACTIONS.TOOGLE_GAME_NOTIFICATION_OPEN: {
+ return {
+ ...state,
+ showGameSavedNotification: true,
+ };
+ }
+
+ default:
+ return state;
+ }
+}
diff --git a/src/components/App.tsx b/src/components/App.tsx
index f44f9e2..78d3f7f 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -1,47 +1,33 @@
-import { useState, useEffect, useRef } from 'react';
+import { useState, useEffect, useRef, useReducer } from 'react';
import '../styles/App.css';
-import Dimension from './Dimension';
-import GameResets from './GameResets';
-import Tickspeed from './TickSpeed';
-
-import { GameState, Dim } from '../common/GameStateInterface';
+import Dimension from './Dimention/Dimension';
+import GameResets from './GameResetBtns/GameResets';
+import Tickspeed from './Tickspeed/TickSpeed';
+import DisplayAntimatter from './DisplayAntimatter/DisplayAntimatter';
import initialGameState from '../common/initialGameState';
-import DisplayAntimatter from './DisplayAntimatter';
-import { useSaveToLocalStorage } from './useSaveToLocalStorage';
+
+import { useLocalStorage } from '../customeHooks/useLocalStorage';
+import { ACTIONS, reducer } from '../common/reducer';
+import GameSavedNotification from './GameSavedNotification/GameSavedNotification';
function App() {
- const [gameState, setGameState] = useState(() =>
+ const [state, dispatch] = useReducer(
+ reducer,
JSON.parse(localStorage.getItem('data') || initialGameState)
);
- /* JSON.parse(localStorage.getItem('data')) */
const timerExpiredCallback = useRef(() => {});
- const clockSpeedRef = useRef(2000);
+ const tickspeedRef = useRef(2000);
const timerIdRef = useRef(-1);
- useSaveToLocalStorage(gameState, setGameState);
+ useLocalStorage(state, dispatch);
- timerExpiredCallback.current = () => {
- setGameState((prevGS: GameState) => ({
- ...prevGS,
- antimatter: prevGS.antimatter + prevGS.dims[0].dimCount * prevGS.dims[0].dimFactor,
- dims: gameState.dims.map((dim: Dim, index: number) => {
- return {
- ...dim,
- dimCount:
- dim.dimCount +
- ((gameState.dims[index + 1]?.dimCount ?? 0) *
- (gameState.dims[index + 1]?.dimFactor ?? 0)) /
- Math.pow(10, index + 1),
- };
- }),
- }));
- };
+ timerExpiredCallback.current = () => dispatch({ type: ACTIONS.TIMER_CALLBACK });
useEffect(() => {
const startTimer = () => {
- timerIdRef.current = setTimeout(() => {
+ timerIdRef.current = window.setTimeout(() => {
timerExpiredCallback.current();
startTimer();
- }, clockSpeedRef.current);
+ }, tickspeedRef.current);
};
startTimer();
@@ -51,60 +37,65 @@ function App() {
return (
-
+
+
+
+ gs={state} // gs = gamestate
+ dispatch={dispatch}
+ tickspeedRef={tickspeedRef}>
-
- {`First Dimension Cost: ${gameState.dims[0].dimPrice}`}
+
+ {`First Dimension Cost: ${state.dims[0].dimPrice}`}
-
- {`Second Dimension Cost: ${gameState.dims[1].dimPrice}`}
+
+ {`Second Dimension Cost: ${state.dims[1].dimPrice}`}
{
//the 3. dimension must be unlocked
}
- {gameState.resetGameCounter > 2 && (
-
- {`Third Dimension Cost: ${gameState.dims[2].dimPrice}`}
+ {state.resetGameCounter > 2 && (
+
+ {`Third Dimension Cost: ${state.dims[2].dimPrice}`}
)}
- {gameState.resetGameCounter > 3 && (
-
- {`Forth Dimension Cost: ${gameState.dims[2].dimPrice}`}
+ {state.resetGameCounter > 3 && (
+
+ {`Forth Dimension Cost: ${state.dims[2].dimPrice}`}
)}
- {gameState.resetGameCounter > 4 && (
-
- {`Fifth Dimension Cost: ${gameState.dims[2].dimPrice}`}
+ {state.resetGameCounter > 4 && (
+
+ {`Fifth Dimension Cost: ${state.dims[2].dimPrice}`}
)}
- {gameState.resetGameCounter > 5 && (
-
- {`Sixth Dimension Cost: ${gameState.dims[2].dimPrice}`}
+ {state.resetGameCounter > 5 && (
+
+ {`Sixth Dimension Cost: ${state.dims[2].dimPrice}`}
)}
- {gameState.resetGameCounter > 6 && (
-
- {`Seventh Dimension Cost: ${gameState.dims[2].dimPrice}`}
+ {state.resetGameCounter > 6 && (
+
+ {`Seventh Dimension Cost: ${state.dims[2].dimPrice}`}
)}
- {gameState.resetGameCounter > 7 && (
-
- {`Eight Dimension Cost: ${gameState.dims[2].dimPrice}`}
+ {state.resetGameCounter > 7 && (
+
+ {`Eight Dimension Cost: ${state.dims[2].dimPrice}`}
)}
-
+
-
+
);
}
export default App;
+function clsx(arg0: any) {
+ throw new Error('Function not implemented.');
+}
diff --git a/src/components/Dimension.tsx b/src/components/Dimention/Dimension.tsx
similarity index 51%
rename from src/components/Dimension.tsx
rename to src/components/Dimention/Dimension.tsx
index e06022d..0d099a4 100644
--- a/src/components/Dimension.tsx
+++ b/src/components/Dimention/Dimension.tsx
@@ -1,49 +1,21 @@
-import { GameState, DimProps, Dim } from '../common/GameStateInterface';
+import { DimProps } from '../../common/GameStateInterface';
+import { ACTIONS } from '../../common/reducer';
export default function Dimension(props: DimProps) {
- const { nthDim, gs, setGameState } = props;
+ const { nthDim, gs, dispatch } = props;
const handleDimBuy = (quantity: number) => {
- const newDim = [gs.dims[nthDim].dimCount + quantity];
-
if (gs.antimatter >= gs.dims[nthDim].dimPrice) {
- /*
- setDimCount(dimCount => dimCount + quantity);
- setAntimatter(prevValue => prevValue - price * quantity);
- setDimFactorCount(prevCount => prevCount + 1); */
- setGameState((prevGS: GameState) => ({
- ...prevGS,
- antimatter: prevGS.antimatter - prevGS.dims[nthDim].dimPrice * quantity,
- dims: prevGS.dims.map((dim: Dim, index: number) => {
- if (dim.nthDim !== nthDim) return dim;
- return {
- ...dim,
- dimCount: dim.dimCount + quantity,
- dimFactorCount: dim.dimFactorCount + quantity,
- };
- }),
- }));
+ dispatch({ type: ACTIONS.UPDATE_DIM, payload: { nthDim: nthDim, quantity: quantity } });
}
if (
((gs.dims[nthDim].dimCount + 1) % 10 === 0 && gs.dims[nthDim].dimCount > 1) ||
quantity === 10
) {
- /*
- setPrice(prevPrice => prevPrice * 10);
- setFactor(prevFactor => prevFactor * 2)
- setDimFactorCount(0) */
- setGameState((prevGS: GameState) => ({
- ...prevGS,
- dims: prevGS.dims.map((dim: Dim, index: number) => {
- if (dim.nthDim !== nthDim) return dim;
- return {
- ...dim,
- dimPrice: dim.dimPrice * 10,
- dimFactor: dim.dimFactor * 2,
- dimFactorCount: 0,
- };
- }),
- }));
+ dispatch({
+ type: ACTIONS.UPDATE_10TH_DIM,
+ payload: { nthDim: nthDim, quantity: quantity },
+ });
}
};
diff --git a/src/components/DisplayAntimatter.tsx b/src/components/DisplayAntimatter/DisplayAntimatter.tsx
similarity index 87%
rename from src/components/DisplayAntimatter.tsx
rename to src/components/DisplayAntimatter/DisplayAntimatter.tsx
index 4e4c685..750a861 100644
--- a/src/components/DisplayAntimatter.tsx
+++ b/src/components/DisplayAntimatter/DisplayAntimatter.tsx
@@ -1,4 +1,4 @@
-import { GameState } from '../common/GameStateInterface';
+import { GameState } from '../../common/GameStateInterface';
export default function DisplayAntimatter(props: { gameState: GameState }) {
const { gameState } = props;
diff --git a/src/components/GameResetBtns/GameResets.tsx b/src/components/GameResetBtns/GameResets.tsx
new file mode 100644
index 0000000..cea88f2
--- /dev/null
+++ b/src/components/GameResetBtns/GameResets.tsx
@@ -0,0 +1,77 @@
+import { useEffect, useRef, useState } from 'react';
+import { GameState } from '../../common/GameStateInterface';
+import initialGameState from '../../common/initialGameState';
+import { ACTIONS } from '../../common/reducer';
+
+export default function GameResets(props: { gs: GameState; dispatch: Function }) {
+ const { gs, dispatch } = props;
+ const [popUp, setPopUp] = useState(false);
+ const resetGameCounterRef = useRef(gs.resetGameCounter);
+
+ useEffect(() => {
+ resetGameCounterRef.current = gs.resetGameCounter;
+ }, [gs.resetGameCounter]);
+
+ const handleResetGameClick = () => {
+ dispatch({ type: ACTIONS.RESET_TO_UNLOCK_DIM });
+ };
+
+ const handleGalaxyBtn = () => {
+ dispatch({ type: ACTIONS.RESET_TO_UNLOCK_TICKSPEED });
+ };
+
+ const disableResetBtn = () => {
+ if (gs.resetGameCounter < 7 && gs.dims[gs.resetGameCounter - 1].dimCount < 20) return true;
+ else return false;
+ };
+
+ return (
+ <>
+
+
{`Dimension Shift (${gs.resetGameCounter}) `}
+
{`requires 20 ${gs.resetGameCounter}. Dimension `}
+
+
+
+
{`Antimatter Galaxies (${gs.galaxyCounter})`}
+
{`requires 80 8. Dimension `}
+
+
+
+
{`reset game`}
+
{`resets everything`}
+
+
+ {popUp && (
+
+
+
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/components/GameResets.tsx b/src/components/GameResets.tsx
deleted file mode 100644
index cdb2364..0000000
--- a/src/components/GameResets.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { useEffect, useRef } from 'react';
-import { GameState } from '../common/GameStateInterface';
-import initialGameState from '../common/initialGameState';
-
-export default function GameResets(props: { gameState: GameState; setGameState: any }) {
- const { gameState, setGameState } = props;
- const resetGameCounterRef = useRef(gameState.resetGameCounter);
-
- useEffect(() => {
- resetGameCounterRef.current = gameState.resetGameCounter;
- }, [gameState.resetGameCounter]);
-
- const handleResetGameClick = () => {
- setGameState((prevGS: GameState) => ({
- ...JSON.parse(initialGameState),
- galaxyCounter: prevGS.galaxyCounter,
- resetGameCounter: prevGS.resetGameCounter + 1,
- lastSavedTime: prevGS.lastSavedTime,
- }));
- };
-
- const handleGalaxyBtn = () => {
- setGameState((prevGS: GameState) => ({
- ...JSON.parse(initialGameState),
- galaxyCounter: prevGS.galaxyCounter + 1,
- tickspeedDeceaseRate: 0.12,
- resetGameCounter: prevGS.resetGameCounter,
- lastSavedTime: prevGS.lastSavedTime,
- }));
- };
-
- const disableResetBtn = () => {
- if (
- gameState.resetGameCounter < 7 &&
- gameState.dims[gameState.resetGameCounter - 1].dimCount < 20
- )
- return true;
- else return false;
- };
-
- return (
- <>
-
-
{`Dimension Shift (${gameState.resetGameCounter}) `}
-
{`requires 20 ${gameState.resetGameCounter}. Dimension `}
-
-
-
-
{`Antimatter Galaxies (${gameState.galaxyCounter})`}
-
{`requires 80 8. Dimension `}
-
-
-
-
{`reset game`}
-
{`resets everything`}
-
-
- >
- );
-}
diff --git a/src/components/GameSavedNotification/GameSavedNotification.tsx b/src/components/GameSavedNotification/GameSavedNotification.tsx
new file mode 100644
index 0000000..4aee238
--- /dev/null
+++ b/src/components/GameSavedNotification/GameSavedNotification.tsx
@@ -0,0 +1,42 @@
+import React, { useEffect, useState } from 'react';
+import { GameState } from '../../common/GameStateInterface';
+import { ACTIONS } from '../../common/reducer';
+import '../../styles/GameSavedNotification.css';
+
+export default function GameSavedNotification(gs: GameState, dispatch: Function) {
+ let TIMER: number = 0;
+ function handleTimeout() {
+ TIMER = window.setTimeout(() => {
+ dispatch({ type: ACTIONS.TOOGLE_GAME_NOTIFICATION_CLOSE });
+ }, 3500);
+ }
+
+ useEffect(() => {
+ if (Date.now() <= gs.lastSavedTime + 5) {
+ dispatch({ type: ACTIONS.TOOGLE_GAME_NOTIFICATION_OPEN });
+ }
+ }, [gs.lastSavedTime]);
+
+ useEffect(() => {
+ console.log(gs.showGameSavedNotification);
+ }, [gs.showGameSavedNotification]);
+
+ useEffect(() => {
+ if (gs.showGameSavedNotification) {
+ handleTimeout();
+ }
+ return () => {
+ clearTimeout(TIMER);
+ };
+ }, [gs.showGameSavedNotification, TIMER]);
+
+ return (
+ gs.showGameSavedNotification && (
+
+ )
+ );
+}
diff --git a/src/components/TickSpeed.tsx b/src/components/TickSpeed.tsx
deleted file mode 100644
index 0111ad1..0000000
--- a/src/components/TickSpeed.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { useRef, useEffect, useState } from 'react';
-import { GameState } from '../common/GameStateInterface';
-
-export default function Tickspeed(props: {
- gameState: GameState;
- setGameState: any;
- clockSpeedRef: any;
-}) {
- const { gameState, setGameState, clockSpeedRef } = props;
- const [buyMax, setBuyMax] = useState(
- Math.log10(gameState.antimatter / gameState.tickspeedPrice)
- );
- // const maxPossibleBuys = useRef(~~Math.log10(gameState.antimatter / gameState.tickspeedPrice));
- const handleTickBtnClick = () => {
- clockSpeedRef.current = clockSpeedRef.current * (1 - gameState.tickspeedDeceaseRate);
- setGameState((prevGS: GameState) => ({
- ...prevGS,
- antimatter: prevGS.antimatter - prevGS.tickspeedPrice,
- tickspeedPrice: prevGS.tickspeedPrice * 10,
- }));
- };
-
- const getMaxPurchaseQtyRecursive = (price: number, antimatter: number, quantity = 0): number => {
- if (antimatter < price) {
- return quantity
- } else {
- return getMaxPurchaseQtyRecursive(price * 10, antimatter - price, quantity + 1);
- }
- };
-
- const maxPurchasableQuantity = getMaxPurchaseQtyRecursive(
- gameState.tickspeedPrice,
- gameState.antimatter,
- 0
- );
-
- const getCostForPurchaseQty = (firstCost: number, quantity: number): number => {
- if (quantity == 1) {
- return firstCost;
- } else {
- return firstCost + getCostForPurchaseQty(firstCost * 10, quantity - 1);
- }
- }
-
- const handleBuyMaxClick = () => {
- clockSpeedRef.current = clockSpeedRef.current * (1 - gameState.tickspeedDeceaseRate) ** maxPurchasableQuantity;
- setGameState((prevGS: GameState) => ({
- ...prevGS,
- antimatter: prevGS.antimatter - getCostForPurchaseQty(maxPurchasableQuantity, prevGS.tickspeedPrice),
- tickspeedPrice: prevGS.tickspeedPrice * 10 ** maxPurchasableQuantity,
- }));
- };
-
-
- return (
-
-
{`The current clockspeed is ${clockSpeedRef.current.toFixed(
- 0
- )} ms. Reduce the tickspeed by ${gameState.tickspeedDeceaseRate * 100}%.`}
-
-
-
-
-
- );
-}
diff --git a/src/components/Tickspeed/TickSpeed.tsx b/src/components/Tickspeed/TickSpeed.tsx
new file mode 100644
index 0000000..b87a4ad
--- /dev/null
+++ b/src/components/Tickspeed/TickSpeed.tsx
@@ -0,0 +1,52 @@
+import { useState } from 'react';
+import { GameState } from '../../common/GameStateInterface';
+import { getCostForPurchaseQty } from './getCostForPurchaseQty';
+import { calcMaxPurchasableQty } from './calcMaxPurchasableQty';
+import { ACTIONS } from '../../common/reducer';
+
+export default function Tickspeed(props: { gs: GameState; dispatch: Function; tickspeedRef: any }) {
+ const { gs, dispatch, tickspeedRef } = props;
+
+ // calculte the maximal purchasable
+ const maxPurchaseQtys = calcMaxPurchasableQty({
+ cost: gs.tickspeedPrice,
+ balance: gs.antimatter,
+ qty: 0,
+ });
+ const purchasePrice = getCostForPurchaseQty(gs.tickspeedPrice, maxPurchaseQtys);
+
+ const handleTickBtnClick = () => {
+ tickspeedRef.current = tickspeedRef.current * (1 - gs.tickspeedDeceaseRate);
+ dispatch({ type: ACTIONS.UPDATE_TICKSPEED_ONCE });
+ };
+ const handleBuyMaxClick = () => {
+ tickspeedRef.current =
+ tickspeedRef.current * (1 - gs.tickspeedDeceaseRate) ** maxPurchaseQtys;
+ dispatch({
+ type: ACTIONS.UPDATE_TICKSPEED_MAX,
+ payload: { maxPurchaseQtys: maxPurchaseQtys, purchasePrice: purchasePrice },
+ });
+ };
+
+ return (
+
+
{`The current clockspeed is ${tickspeedRef.current.toFixed(
+ 0
+ )} ms. Reduce the tickspeed by ${(gs.tickspeedDeceaseRate* 100).toFixed(1)}%.`}
+
+
+
+
+
+ );
+}
diff --git a/src/components/Tickspeed/calcMaxPurchasableQty.test.ts b/src/components/Tickspeed/calcMaxPurchasableQty.test.ts
new file mode 100644
index 0000000..862afff
--- /dev/null
+++ b/src/components/Tickspeed/calcMaxPurchasableQty.test.ts
@@ -0,0 +1,19 @@
+import { calcMaxPurchasableQty } from './calcMaxPurchasableQty';
+import { describe, expect, it } from 'vitest';
+
+describe('maxPurchaseQty', () => {
+ it('return zero if we have no money', () => {
+ expect(calcMaxPurchasableQty({ cost: 1234242, balance: 0, qty: 0 })).toEqual(0);
+ });
+ it('return one if we can afford only one', () => {
+ expect(calcMaxPurchasableQty({ cost: 10, balance: 100, qty: 0 })).toEqual(1);
+ });
+
+ it('return 2 if we can afford only 2', () => {
+ expect(calcMaxPurchasableQty({ cost: 10, balance: 110, qty: 0 })).toEqual(2);
+ });
+ it('return 3 if we can afford only 3', () => {
+ expect(calcMaxPurchasableQty
+ ({ cost: 10, balance: 1110, qty: 0 })).toEqual(3);
+ });
+});
diff --git a/src/components/Tickspeed/calcMaxPurchasableQty.ts b/src/components/Tickspeed/calcMaxPurchasableQty.ts
new file mode 100644
index 0000000..95f23c0
--- /dev/null
+++ b/src/components/Tickspeed/calcMaxPurchasableQty.ts
@@ -0,0 +1,10 @@
+interface calcMaxPurchasableQtyInterface {
+ cost: number;
+ balance: number;
+ qty: number;
+}
+
+export const calcMaxPurchasableQty = ({ cost, balance, qty = 0 }: calcMaxPurchasableQtyInterface): number => {
+ if (cost > balance) return qty;
+ else return calcMaxPurchasableQty({ cost: cost * 10, balance: balance - cost, qty: qty + 1 });
+};
diff --git a/src/components/Tickspeed/getCostForPurchaseQty.test.tsx b/src/components/Tickspeed/getCostForPurchaseQty.test.tsx
new file mode 100644
index 0000000..3a4c67f
--- /dev/null
+++ b/src/components/Tickspeed/getCostForPurchaseQty.test.tsx
@@ -0,0 +1,17 @@
+import { describe, expect, it } from 'vitest';
+import { getCostForPurchaseQty } from './getCostForPurchaseQty';
+
+describe('getCostForPurchaseQty', () => {
+ it('if nothing is bought, there is no cost increase', () => {
+ expect(getCostForPurchaseQty(10, 0)).toEqual(0);
+ });
+ it('if 1 Qty is bought, the cost is the initial cost', () => {
+ expect(getCostForPurchaseQty(10, 1)).toEqual(10);
+ });
+ it('if 2 Qty are bought, the cost is the initial cost + 10 *initial cost', () => {
+ expect(getCostForPurchaseQty(10, 2)).toEqual(110);
+ });
+ it('if 5 Qty are bought, the cost is the initial cost + 10 *initial cost + ... + 10^5*initial cost', () => {
+ expect(getCostForPurchaseQty(10, 2)).toEqual(111110);
+ });
+});
diff --git a/src/components/Tickspeed/getCostForPurchaseQty.tsx b/src/components/Tickspeed/getCostForPurchaseQty.tsx
new file mode 100644
index 0000000..295c8f4
--- /dev/null
+++ b/src/components/Tickspeed/getCostForPurchaseQty.tsx
@@ -0,0 +1,7 @@
+export const getCostForPurchaseQty = (firstCost: number, quantity: number): number => {
+ if (quantity === 0) return 0;
+ else if (quantity === 1) {
+ return firstCost;
+ } else return firstCost + getCostForPurchaseQty(firstCost * 10, quantity - 1)
+};
+
\ No newline at end of file
diff --git a/src/components/getMaxPurchaseQtyRecursive.test.ts b/src/components/getMaxPurchaseQtyRecursive.test.ts
deleted file mode 100644
index 0e45733..0000000
--- a/src/components/getMaxPurchaseQtyRecursive.test.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { maxPurchaseQty } from "./getMaxPurchaseQtyRecursive";
-import {describe, expect, it} from "vitest";
-
-describe("getMaxPurchaseQtyRecursive ", ()=>{
-
- it('return zero if we have no money', () => {
- expect(maxPurchaseQty({cost: 1234242, balance: 0})).toEqual(0)
- })
- it('return one if we can afoard only one', () => {
- expect(maxPurchaseQty({ cost: 100, balance: 100})).toEqual(1)
- })
-
-});
diff --git a/src/components/getMaxPurchaseQtyRecursive.ts b/src/components/getMaxPurchaseQtyRecursive.ts
deleted file mode 100644
index cc9a8e3..0000000
--- a/src/components/getMaxPurchaseQtyRecursive.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-export const maxPurchaseQty = ({cost, balance}) => {
-
- return 0;
-
-};
diff --git a/src/components/useSaveToLocalStorage.tsx b/src/components/useSaveToLocalStorage.tsx
deleted file mode 100644
index 67fba85..0000000
--- a/src/components/useSaveToLocalStorage.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { useEffect, useRef } from 'react';
-import { Dim, GameState } from '../common/GameStateInterface';
-
-export function useSaveToLocalStorage(gameState: GameState, setGameState) {
- const saveLocStorageRef = useRef(-1);
- const displayLocStorageRef = useRef(-1);
-
- // saves the created object every minuit to localStorage
-
- useEffect(() => {
- const saveDateRecursive = (gameState: GameState) => {
- if (Date.now() > gameState.lastSavedTime + 60 * 1000) {
- localStorage.setItem('data', JSON.stringify(gameState));
- console.log('data saved', gameState.lastSavedTime);
- setGameState((prevGS: GameState) => ({
- ...prevGS,
- lastSavedTime: Date.now(),
- }));
- }
- };
- saveDateRecursive(gameState);
-
- }, [gameState]);
-
- // this prints the saved object from local storage. It is just for dev. No final purpose
- /* useEffect(() => {
- const printStorage = () => {
- displayLocStorageRef.current = setTimeout(() => {
- let savedData= JSON.parse(localStorage.getItem('data') ?? '');
- console.log('dataRef from storage', savedData);
- printStorage();
- }, 10000);
- };
- printStorage();
-
- return () => clearTimeout(displayLocStorageRef.current);
- }, []); */
-}
diff --git a/src/customeHooks/useLocalStorage.tsx b/src/customeHooks/useLocalStorage.tsx
new file mode 100644
index 0000000..98659f1
--- /dev/null
+++ b/src/customeHooks/useLocalStorage.tsx
@@ -0,0 +1,21 @@
+import { useEffect, useRef } from 'react';
+import { Dim, GameState } from '../common/GameStateInterface';
+import { ACTIONS } from '../common/reducer';
+
+export function useLocalStorage(gameState: GameState, dispatch: Function): void {
+ //const saveLocStorageRef = useRef(-1);
+ //const displayLocStorageRef = useRef(-1);
+
+ // saves the created object every minuit to localStorage
+
+ useEffect(() => {
+ const saveDateRecursive = (gameState: GameState) => {
+ if (Date.now() > gameState.lastSavedTime + 5 * 60 * 1000) {
+ localStorage.setItem('data', JSON.stringify(gameState));
+ console.log('data saved', gameState.lastSavedTime);
+ dispatch({ type: ACTIONS.SAVE_DATA });
+ }
+ };
+ saveDateRecursive(gameState);
+ }, [gameState]);
+}
diff --git a/src/styles/App.css b/src/styles/App.css
index 3ee50dc..afb338e 100644
--- a/src/styles/App.css
+++ b/src/styles/App.css
@@ -1,5 +1,6 @@
* {
-/* border: 1px solid black; */
+ /* border: 1px solid black; */
+ box-sizing: border-box;
}
.App {
@@ -24,7 +25,6 @@
grid-template-columns: repeat(4, 1fr);
gap: 10px;
grid-auto-rows: minmax(40px, auto);
-
}
.gridContainer3Rows {
@@ -42,12 +42,44 @@
align-items: center;
text-align: center;
}
+.spaceAround {
+ justify-content: space-around;
+}
-.cols-2{
+.cols-2 {
grid-template-columns: repeat(3, 1fr);
margin: 2rem 5rem;
}
+.overlay {
+ position: fixed;
+ width: 100%;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+
+ z-index: 10000;
+}
+
+.pop-up {
+ position: absolute;
+ top: 45%;
+ left: 0;
+
+ width: 50%;
+ height: 10%;
+ margin-left: 25%;
+ margin-right: 25%;
+ padding: 15px;
+
+ z-index: 100001;
+ background: grey;
+ border-radius: 10px;
+
+ box-shadow: 5px 7px 5px rgba(0, 0, 0, 0.75);
+}
+
.btn {
font-size: calc(10px + 1vmin);
padding: 0.2em 1em 0.2em 1em;
@@ -60,16 +92,100 @@
background-image: linear-gradient(to right, #29323c, #485563, #2b5876, #4e4376);
box-shadow: 0 4px 15px 0 rgba(45, 54, 65, 0.75);
}
-.btn:disabled{
+.btn:disabled {
background: initial;
box-shadow: initial;
}
-.sm{
+.sm {
font-size: calc(10px + 0.5vmin);
}
-.btn:disabled{
+.btn:disabled {
background-color: rgb(255, 254, 254);
border: 1px rgb(240, 238, 238) solid;
color: rgb(180, 177, 177);
}
+
+.red {
+ background-color: red;
+ font-style: bold;
+ color: white;
+}
+.black {
+ background: rgba(0, 0, 0, 0.85);
+}
+
+.snackbar-overlay {
+ position: fixed;
+ left: 0;
+ bottom: 0;
+ top: 0;
+ right: 0;
+ z-index: -1;
+}
+
+.gameSaved {
+ position: absolute;
+ top: 2%;
+ right: 1%;
+ visibility: hidden;
+ width: 10%;
+ height: 5%;
+ padding: 15px;
+
+ z-index: 90001;
+ background-color: rgb(2, 23, 157);
+ color: white;
+ font-weight: 700;
+ border-radius: 5px;
+ -webkit-animation: fadein 3s, fadeout 3s 3s;
+ animation: fadein 3s linear, fadeout 3s linear 3s;
+}
+.visible {
+ visibility: visible;
+}
+
+/* Animations to fade the snackbar in and out */
+@-webkit-keyframes fadein {
+ from {
+ top: 0%;
+ opacity: 0;
+ }
+ to {
+ top: 2%;
+ opacity: 1;
+ }
+}
+
+@keyframes fadein {
+ from {
+ top: -0%;
+ opacity: 0;
+ }
+ to {
+ top: 2%;
+ opacity: 1;
+ }
+}
+
+@-webkit-keyframes fadeout {
+ from {
+ top: 2%;
+ opacity: 1;
+ }
+ to {
+ top: -3%;
+ opacity: 0;
+ }
+}
+
+@keyframes fadeout {
+ from {
+ top: 2%;
+ opacity: 1;
+ }
+ to {
+ top: -3%;
+ opacity: 0;
+ }
+}
diff --git a/src/styles/GameSavedNotification.css b/src/styles/GameSavedNotification.css
new file mode 100644
index 0000000..61d694f
--- /dev/null
+++ b/src/styles/GameSavedNotification.css
@@ -0,0 +1,55 @@
+* {
+ box-sizing: border-box;
+}
+
+.snackbar-overlay {
+ position: fixed;
+ left: 0;
+ bottom: 0;
+ top: 0;
+ right: 0;
+ z-index: -1;
+}
+
+.snackbar {
+ position: absolute;
+ z-index: 1000;
+ top: 2%;
+ right: 7%;
+ transform: translateX(50%);
+ height: auto;
+ padding: 0.625rem 1rem;
+ border-radius: 0.75rem;
+ border: transparent;
+ background-color: hsl(200, 100%, 65%);
+ color: white;
+ box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ animation: fadein 1s, fadeout 1s 3s;
+}
+
+@keyframes fadein {
+ from {
+ top: 0;
+ opacity: 0;
+ }
+ to {
+ top: 2%;
+ opacity: 1;
+ }
+}
+
+@keyframes fadeout {
+ from {
+ top: 2%;
+ opacity: 1;
+ }
+ to {
+ top: 0;
+ opacity: 0;
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index 861fba5..fc0aedd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -394,6 +394,11 @@ check-error@^1.0.2:
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==
+clsx@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmmirror.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
+ integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
+
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"