diff --git a/app/game/game.styles.ts b/app/game/game.styles.ts
index e2ed9d4..5e22ee0 100644
--- a/app/game/game.styles.ts
+++ b/app/game/game.styles.ts
@@ -4,5 +4,14 @@ export const GameStyles = StyleSheet.create({
container: {
flex: 1,
padding: 10
- }
+ },
+ controls: {
+ alignItems: 'center',
+ flexDirection: 'row',
+ justifyContent: 'space-between'
+ },
+ mistakesCountText: {
+ fontWeight: 'bold'
+ },
+ mistakesText: {}
});
diff --git a/app/game/index.tsx b/app/game/index.tsx
index d847603..4d646ea 100644
--- a/app/game/index.tsx
+++ b/app/game/index.tsx
@@ -1,14 +1,15 @@
import { useRouter } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { useEffect } from 'react';
-import { Alert } from 'react-native';
+import { Alert, View, Text } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { AvailableValues } from '../../components/available-values/available-values';
import { BlackButton } from '../../components/black-button/black-button';
import { Field } from '../../components/field/field';
+import { MaxMistakesConstant } from '../../constants/max-mistakes.constant';
import { useAppSelector } from '../../hooks/redux.hook';
-import { appRootFieldSelector, appRootSelectedCellSelector } from '../../store/app-root/app-root.selectors';
+import { appRootFieldSelector, appRootMistakesSelector, appRootSelectedCellSelector } from '../../store/app-root/app-root.selectors';
import { hasBlankCells } from '../../utils/field/has-blank-cells.util';
import { GameStyles as styles } from './game.styles';
@@ -18,12 +19,18 @@ export default function Game() {
const field = useAppSelector(appRootFieldSelector);
const selectedCell = useAppSelector(appRootSelectedCellSelector);
+ const mistakes = useAppSelector(appRootMistakesSelector);
useEffect(() => {
if (!hasBlankCells(field)[0]) {
router.push('winner');
}
}, [field]);
+ useEffect(() => {
+ if (mistakes >= MaxMistakesConstant) {
+ router.push('loser');
+ }
+ }, [mistakes]);
const handleExit = () => {
Alert.alert('Stop current run?', 'All progress will be lost', [
@@ -35,7 +42,12 @@ export default function Game() {
return (
-
+
+
+ Mistakes: {mistakes}/{MaxMistakesConstant}
+
+
+
diff --git a/app/loser/index.tsx b/app/loser/index.tsx
new file mode 100644
index 0000000..6a7f660
--- /dev/null
+++ b/app/loser/index.tsx
@@ -0,0 +1,17 @@
+import { StatusBar } from 'expo-status-bar';
+import { View } from 'react-native';
+
+import { BlackButton } from '../../components/black-button/black-button';
+import { Header } from '../../components/header/header';
+
+import { LoserStyles as styles } from './loser.styles';
+
+export default function Loser() {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/app/loser/loser.styles.ts b/app/loser/loser.styles.ts
new file mode 100644
index 0000000..fd716e7
--- /dev/null
+++ b/app/loser/loser.styles.ts
@@ -0,0 +1,9 @@
+import { StyleSheet } from 'react-native';
+
+export const LoserStyles = StyleSheet.create({
+ container: {
+ alignItems: 'center',
+ flex: 1,
+ justifyContent: 'center'
+ }
+});
diff --git a/app/winner/index.tsx b/app/winner/index.tsx
index 83ee63b..233a911 100644
--- a/app/winner/index.tsx
+++ b/app/winner/index.tsx
@@ -1,6 +1,8 @@
-import { Link } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
-import { Text, View } from 'react-native';
+import { View } from 'react-native';
+
+import { BlackButton } from '../../components/black-button/black-button';
+import { Header } from '../../components/header/header';
import { WinnerStyles as styles } from './winner.styles';
@@ -8,10 +10,8 @@ export default function Winner() {
return (
- Winner-winner, chicken dinner!
-
- Play again
-
+
+
);
}
diff --git a/app/winner/winner.styles.ts b/app/winner/winner.styles.ts
index 651a961..9b72f31 100644
--- a/app/winner/winner.styles.ts
+++ b/app/winner/winner.styles.ts
@@ -1,20 +1,9 @@
import { StyleSheet } from 'react-native';
export const WinnerStyles = StyleSheet.create({
- button: {
- backgroundColor: '#000',
- color: 'white',
- paddingHorizontal: 20,
- paddingVertical: 10
- },
container: {
alignItems: 'center',
flex: 1,
justifyContent: 'center'
- },
- header: {
- fontFamily: 'Inter_700Bold',
- fontSize: 22,
- marginBottom: 10
}
});
diff --git a/components/available-values/available-values.styles.ts b/components/available-values/available-values.styles.ts
index c8335f1..5b4ae51 100644
--- a/components/available-values/available-values.styles.ts
+++ b/components/available-values/available-values.styles.ts
@@ -1,12 +1,12 @@
import { StyleSheet } from 'react-native';
-import { cellSize } from '../../constants/dimensions.contant';
+import { CellSizeConstant } from '../../constants/dimensions.contant';
export const AvailableValuesStyles = StyleSheet.create({
valueWrapper: {
borderWidth: 1,
- height: cellSize,
- width: cellSize
+ height: CellSizeConstant,
+ width: CellSizeConstant
},
wrapper: {
flex: 1,
diff --git a/components/black-button/black-button.tsx b/components/black-button/black-button.tsx
index 0409f17..bf8773f 100644
--- a/components/black-button/black-button.tsx
+++ b/components/black-button/black-button.tsx
@@ -1,18 +1,39 @@
-import { Pressable, type PressableProps, type StyleProp, Text, type TextProps, type ViewStyle } from 'react-native';
+import { isNotEmptyString } from '@rnw-community/shared';
+import { useRouter } from 'expo-router';
+import {
+ type GestureResponderEvent,
+ Pressable,
+ type PressableProps,
+ type StyleProp,
+ Text,
+ type TextProps,
+ type ViewStyle
+} from 'react-native';
import { BlackButtonStyles as styles } from './black-button.styles';
interface Props extends PressableProps {
text: string;
styleText?: TextProps['style'];
+ href?: string;
}
-export const BlackButton = ({ text, style, styleText, ...props }: Props) => {
+export const BlackButton = ({ text, style, href, styleText, onPress, ...props }: Props) => {
+ const router = useRouter();
+
const wrapperStyles = [styles.button, style] as StyleProp;
const textStyles = [styles.buttonText, styleText];
+ const handlePress = (event: GestureResponderEvent) => {
+ onPress?.(event);
+
+ if (isNotEmptyString(href)) {
+ router.push(href);
+ }
+ };
+
return (
-
+
{text}
);
diff --git a/components/cell/cell.styles.ts b/components/cell/cell.styles.ts
index 8525090..5f56d89 100644
--- a/components/cell/cell.styles.ts
+++ b/components/cell/cell.styles.ts
@@ -1,6 +1,6 @@
import { StyleSheet } from 'react-native';
-import { cellSize } from '../../constants/dimensions.contant';
+import { CellSizeConstant } from '../../constants/dimensions.contant';
// TODO: Add style theming support
export const CellStyles = StyleSheet.create({
@@ -12,9 +12,9 @@ export const CellStyles = StyleSheet.create({
borderTopWidth: 1,
flex: 1,
fontFamily: 'Inter_500Medium',
- height: cellSize,
+ height: CellSizeConstant,
justifyContent: 'center',
- width: cellSize
+ width: CellSizeConstant
},
cellActive: {
backgroundColor: 'green'
diff --git a/components/header/header.styles.ts b/components/header/header.styles.ts
new file mode 100644
index 0000000..e7f868d
--- /dev/null
+++ b/components/header/header.styles.ts
@@ -0,0 +1,9 @@
+import { StyleSheet } from 'react-native';
+
+export const HeaderStyles = StyleSheet.create({
+ container: {
+ fontFamily: 'Inter_700Bold',
+ fontSize: 22,
+ marginBottom: 20
+ }
+});
diff --git a/components/header/header.tsx b/components/header/header.tsx
new file mode 100644
index 0000000..51236c1
--- /dev/null
+++ b/components/header/header.tsx
@@ -0,0 +1,15 @@
+import { Text, type TextProps } from 'react-native';
+
+import { HeaderStyles as styles } from './header.styles';
+
+interface Props extends TextProps {
+ text: string;
+}
+
+export const Header = ({ text, ...props }: Props) => {
+ return (
+
+ {text}
+
+ );
+};
diff --git a/constants/dimensions.contant.ts b/constants/dimensions.contant.ts
index e9bad51..2ebddff 100644
--- a/constants/dimensions.contant.ts
+++ b/constants/dimensions.contant.ts
@@ -1 +1 @@
-export const cellSize = 50;
+export const CellSizeConstant = 50;
diff --git a/constants/max-mistakes.constant.ts b/constants/max-mistakes.constant.ts
new file mode 100644
index 0000000..a38ebea
--- /dev/null
+++ b/constants/max-mistakes.constant.ts
@@ -0,0 +1 @@
+export const MaxMistakesConstant = 3;
diff --git a/store/app-root/actions/app-root-select-value.action.ts b/store/app-root/actions/app-root-select-value.action.ts
index 4800ba9..bee29db 100644
--- a/store/app-root/actions/app-root-select-value.action.ts
+++ b/store/app-root/actions/app-root-select-value.action.ts
@@ -1,16 +1,17 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import { isDefined } from '@rnw-community/shared';
+import { BlankCellValueContant } from '../../../constants/blank-cell-value.contant';
import { isCorrectCell } from '../../../utils/field/is-correct-cell.util';
import { type AppDispatch, type RootState } from '../../create-store';
-import { appRootSetValueAction } from '../app-root.actions';
+import { appRootMadeAMistake, appRootSetValueAction } from '../app-root.actions';
export const appRootSelectValueAction = createAsyncThunk(
'appRoot/selectValue',
async (value: number, thunkAPI) => {
const state = thunkAPI.getState().appRoot;
- if (isDefined(state.selectedCell)) {
+ if (isDefined(state.selectedCell) && state.selectedCell.value === BlankCellValueContant) {
const newCell = { ...state.selectedCell, value };
if (isCorrectCell(newCell, state.gameField)) {
thunkAPI.dispatch(appRootSetValueAction(newCell));
@@ -21,10 +22,11 @@ export const appRootSelectValueAction = createAsyncThunk sta
export const appRootSelectedCellSelector = createSelector(appRootSelector, state => state.selectedCell);
export const appRootSelectedValueSelector = createSelector(appRootSelector, state => state.selectedValue);
export const appRootAvailableValuesSelector = createSelector(appRootSelector, state => getAvailableFieldValues(state.gameField));
+export const appRootMistakesSelector = createSelector(appRootSelector, state => state.mistakes);
diff --git a/store/app-root/app-root.slice.ts b/store/app-root/app-root.slice.ts
index 5edd6ed..e27bd7c 100644
--- a/store/app-root/app-root.slice.ts
+++ b/store/app-root/app-root.slice.ts
@@ -16,6 +16,7 @@ export const appRootSlice = createSlice({
state.filledField = createField(9);
state.gameField = createGameField(state.filledField, blankCellsCount);
+ state.mistakes = 0;
},
selectCell: (state, action: PayloadAction) => {
state.selectedCell = action.payload;
@@ -24,6 +25,9 @@ export const appRootSlice = createSlice({
const cell = action.payload;
state.selectedCell = state.gameField[cell.y][cell.x];
state.gameField[cell.y][cell.x].value = cell.value;
+ },
+ madeAMistake: state => {
+ state.mistakes++;
}
}
});
diff --git a/store/app-root/app-root.state.ts b/store/app-root/app-root.state.ts
index 9928ef7..1f88ada 100644
--- a/store/app-root/app-root.state.ts
+++ b/store/app-root/app-root.state.ts
@@ -5,9 +5,11 @@ export interface AppRootState {
gameField: CellInterface[][];
selectedCell?: CellInterface;
selectedValue?: number;
+ mistakes: number;
}
export const appRootInitialState: AppRootState = {
filledField: [],
- gameField: []
+ gameField: [],
+ mistakes: 0
};