Skip to content

Commit

Permalink
feat: added loser screen, mistakes logic and some styling
Browse files Browse the repository at this point in the history
  • Loading branch information
vitalyiegorov committed May 20, 2023
1 parent 207e238 commit 89c986f
Show file tree
Hide file tree
Showing 18 changed files with 132 additions and 36 deletions.
11 changes: 10 additions & 1 deletion app/game/game.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {}
});
18 changes: 15 additions & 3 deletions app/game/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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', [
Expand All @@ -35,7 +42,12 @@ export default function Game() {
return (
<SafeAreaView style={styles.container}>
<StatusBar style="auto" />
<BlackButton text="Exit" onPress={handleExit} />
<View style={styles.controls}>
<Text style={styles.mistakesText}>
Mistakes: <Text style={styles.mistakesCountText}>{mistakes}</Text>/{MaxMistakesConstant}
</Text>
<BlackButton text="Exit" onPress={handleExit} />
</View>
<Field field={field} selectedCell={selectedCell} />
<AvailableValues />
</SafeAreaView>
Expand Down
17 changes: 17 additions & 0 deletions app/loser/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<View style={styles.container}>
<StatusBar style="auto" />
<Header text="Better next time! Loooooser =)" />
<BlackButton text={'Play again'} href={'/'} />
</View>
);
}
9 changes: 9 additions & 0 deletions app/loser/loser.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { StyleSheet } from 'react-native';

export const LoserStyles = StyleSheet.create({
container: {
alignItems: 'center',
flex: 1,
justifyContent: 'center'
}
});
12 changes: 6 additions & 6 deletions app/winner/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
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';

export default function Winner() {
return (
<View style={styles.container}>
<StatusBar style="auto" />
<Text style={styles.header}>Winner-winner, chicken dinner!</Text>
<Link href="/" style={styles.button}>
<Text>Play again</Text>
</Link>
<Header text="Winner-winner, chicken dinner!" />
<BlackButton text={'Play again'} href={'/'} />
</View>
);
}
11 changes: 0 additions & 11 deletions app/winner/winner.styles.ts
Original file line number Diff line number Diff line change
@@ -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
}
});
6 changes: 3 additions & 3 deletions components/available-values/available-values.styles.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
27 changes: 24 additions & 3 deletions components/black-button/black-button.tsx
Original file line number Diff line number Diff line change
@@ -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<ViewStyle>;
const textStyles = [styles.buttonText, styleText];

const handlePress = (event: GestureResponderEvent) => {
onPress?.(event);

if (isNotEmptyString(href)) {
router.push(href);
}
};

return (
<Pressable style={wrapperStyles} {...props}>
<Pressable style={wrapperStyles} onPress={handlePress} {...props}>
<Text style={textStyles}>{text}</Text>
</Pressable>
);
Expand Down
6 changes: 3 additions & 3 deletions components/cell/cell.styles.ts
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -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'
Expand Down
9 changes: 9 additions & 0 deletions components/header/header.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { StyleSheet } from 'react-native';

export const HeaderStyles = StyleSheet.create({
container: {
fontFamily: 'Inter_700Bold',
fontSize: 22,
marginBottom: 20
}
});
15 changes: 15 additions & 0 deletions components/header/header.tsx
Original file line number Diff line number Diff line change
@@ -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 style={styles.container} {...props}>
{text}
</Text>
);
};
2 changes: 1 addition & 1 deletion constants/dimensions.contant.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const cellSize = 50;
export const CellSizeConstant = 50;
1 change: 1 addition & 0 deletions constants/max-mistakes.constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const MaxMistakesConstant = 3;
8 changes: 5 additions & 3 deletions store/app-root/actions/app-root-select-value.action.ts
Original file line number Diff line number Diff line change
@@ -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<boolean, number, { dispatch: AppDispatch; state: RootState }>(
'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));
Expand All @@ -21,10 +22,11 @@ export const appRootSelectValueAction = createAsyncThunk<boolean, number, { disp

return true;
} else {
thunkAPI.dispatch(appRootMadeAMistake());
// TODO: Add logic for mistake and game over
}
}

return false;
return true;
}
);
7 changes: 6 additions & 1 deletion store/app-root/app-root.actions.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { appRootSlice } from './app-root.slice';

export const { load: appRootLoadAction, selectCell: appRootSelectCellAction, setValue: appRootSetValueAction } = appRootSlice.actions;
export const {
load: appRootLoadAction,
selectCell: appRootSelectCellAction,
setValue: appRootSetValueAction,
madeAMistake: appRootMadeAMistake
} = appRootSlice.actions;
1 change: 1 addition & 0 deletions store/app-root/app-root.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export const appRootFieldSelector = createSelector(appRootSelector, state => 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);
4 changes: 4 additions & 0 deletions store/app-root/app-root.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<CellInterface | undefined>) => {
state.selectedCell = action.payload;
Expand All @@ -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++;
}
}
});
4 changes: 3 additions & 1 deletion store/app-root/app-root.state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ export interface AppRootState {
gameField: CellInterface[][];
selectedCell?: CellInterface;
selectedValue?: number;
mistakes: number;
}

export const appRootInitialState: AppRootState = {
filledField: [],
gameField: []
gameField: [],
mistakes: 0
};

0 comments on commit 89c986f

Please sign in to comment.