From b4464ddb51afa1d31b4524bb80ef6dceeef5726e Mon Sep 17 00:00:00 2001 From: Vitalii Yehorov Date: Fri, 26 May 2023 08:35:54 +0200 Subject: [PATCH] feat: added score calculation and timer --- .eslintrc.js | 6 ++- app/game/game.styles.ts | 11 ++++- app/game/index.tsx | 25 ++++++++++-- app/winner/index.tsx | 5 +++ .../available-values/available-values.tsx | 5 ++- .../elapsed-time/elapsed-time.styles.ts | 14 +++++++ components/elapsed-time/elapsed-time.tsx | 36 +++++++++++++++++ components/field/field.styles.ts | 2 +- components/field/field.tsx | 3 +- constants/field.constant.ts | 3 ++ constants/score.constant.ts | 6 +++ enums/difficulty.enum.tsx | 12 +++--- interfaces/field.interface.ts | 3 ++ interfaces/game-state.interface.ts | 5 ++- readme.md | 18 ++++----- .../actions/app-root-select-value.action.ts | 7 +++- store/app-root/app-root.actions.ts | 3 +- store/app-root/app-root.selectors.ts | 1 + store/app-root/app-root.slice.ts | 6 ++- utils/calculate-score.util.ts | 40 +++++++++++++++++++ utils/cell/create-cell.util.ts | 4 +- utils/cell/is-group-end.util.ts | 4 +- utils/cell/is-last-in-col.util.ts | 4 ++ utils/cell/is-last-in-group.util.ts | 4 ++ utils/cell/is-last-in-row.util.ts | 4 ++ utils/field/create-field.util.ts | 5 ++- utils/field/create-game-field.util.ts | 8 ++-- utils/field/fill-field.util.ts | 3 +- utils/field/has-blank-cells.util.ts | 4 +- utils/field/has-value-in-column.util.ts | 7 ++-- utils/field/has-value-in-group.util.ts | 14 ++++--- utils/field/has-value-in-row.util.ts | 7 ++-- utils/field/is-correct-cell.util.ts | 5 ++- utils/get-available-field-values.util.ts | 4 +- 34 files changed, 229 insertions(+), 59 deletions(-) create mode 100644 components/elapsed-time/elapsed-time.styles.ts create mode 100644 components/elapsed-time/elapsed-time.tsx create mode 100644 constants/field.constant.ts create mode 100644 constants/score.constant.ts create mode 100644 interfaces/field.interface.ts create mode 100644 utils/calculate-score.util.ts create mode 100644 utils/cell/is-last-in-col.util.ts create mode 100644 utils/cell/is-last-in-group.util.ts create mode 100644 utils/cell/is-last-in-row.util.ts diff --git a/.eslintrc.js b/.eslintrc.js index 8e42c22..5544c04 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -23,7 +23,11 @@ module.exports = { jsx: true } }, - settings: {}, + settings: { + react: { + version: 'detect' + } + }, plugins: ['react', 'react-native', 'import', 'prettier'], rules: { 'no-void': 'off', diff --git a/app/game/game.styles.ts b/app/game/game.styles.ts index 145246e..df9db0e 100644 --- a/app/game/game.styles.ts +++ b/app/game/game.styles.ts @@ -12,11 +12,18 @@ export const GameStyles = StyleSheet.create({ flexDirection: 'row', justifyContent: 'space-between' }, + controlsWrapper: { + alignItems: 'center' + }, + headerText: { + color: Colors.black + }, mistakesCountText: { color: Colors.black, fontWeight: 'bold' }, - mistakesText: { - color: Colors.black + scoreText: { + color: Colors.black, + fontWeight: 'bold' } }); diff --git a/app/game/index.tsx b/app/game/index.tsx index c63a9f9..ddf9ae2 100644 --- a/app/game/index.tsx +++ b/app/game/index.tsx @@ -8,13 +8,20 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { Alert } from '../../components/alert/alert'; import { AvailableValues } from '../../components/available-values/available-values'; import { BlackButton } from '../../components/black-button/black-button'; +import { ElapsedTime } from '../../components/elapsed-time/elapsed-time'; import { Field } from '../../components/field/field'; import { PageHeader } from '../../components/page-header/page-header'; import { MaxMistakesConstant } from '../../constants/max-mistakes.constant'; import { useAppDispatch, useAppSelector } from '../../hooks/redux.hook'; import { type CellInterface } from '../../interfaces/cell.interface'; import { appRootResetAction, appRootSelectCellAction } from '../../store/app-root/app-root.actions'; -import { appRootFieldSelector, appRootMistakesSelector, appRootSelectedCellSelector } from '../../store/app-root/app-root.selectors'; +import { + appRootFieldSelector, + appRootGameStartedAtSelector, + appRootMistakesSelector, + appRootScoreSelector, + appRootSelectedCellSelector +} from '../../store/app-root/app-root.selectors'; import { hasBlankCells } from '../../utils/field/has-blank-cells.util'; import { hapticImpact } from '../../utils/haptic.utils'; @@ -27,6 +34,8 @@ export default function Game() { const field = useAppSelector(appRootFieldSelector); const selectedCell = useAppSelector(appRootSelectedCellSelector); const mistakes = useAppSelector(appRootMistakesSelector); + const currentScore = useAppSelector(appRootScoreSelector); + const startedAt = useAppSelector(appRootGameStartedAtSelector); const handleWin = async () => { if (!hasBlankCells(field)[0]) { @@ -63,12 +72,20 @@ export default function Game() { - - Mistakes: {mistakes} / {MaxMistakesConstant} - + + Mistakes + + {mistakes} / {MaxMistakesConstant} + + + + Score + {currentScore} + + ); diff --git a/app/winner/index.tsx b/app/winner/index.tsx index fd6df99..a1af96d 100644 --- a/app/winner/index.tsx +++ b/app/winner/index.tsx @@ -1,16 +1,21 @@ import { View } from 'react-native'; +import { useSelector } from 'react-redux'; import { Header } from '../../components/header/header'; import { PageHeader } from '../../components/page-header/page-header'; import { PlayAgainButton } from '../../components/play-again-button/play-again-button'; +import { appRootScoreSelector } from '../../store/app-root/app-root.selectors'; import { WinnerStyles as styles } from './winner.styles'; export default function Winner() { + const score = useSelector(appRootScoreSelector); + return (
+
); diff --git a/components/available-values/available-values.tsx b/components/available-values/available-values.tsx index 6e06ca6..d535c45 100644 --- a/components/available-values/available-values.tsx +++ b/components/available-values/available-values.tsx @@ -3,6 +3,7 @@ import { View } from 'react-native'; import { useSelector } from 'react-redux'; import { BlankCellValueConstant } from '../../constants/blank-cell-value.constant'; +import { FieldSizeConstant } from '../../constants/field.constant'; import { useAppDispatch } from '../../hooks/redux.hook'; import { appRootSelectValueAction } from '../../store/app-root/actions/app-root-select-value.action'; import { @@ -14,10 +15,10 @@ import { AvailableValuesItem } from '../available-values-item/available-values-i import { AvailableValuesStyles as styles } from './available-values.styles'; -const getValueProgress = (allValues: Record, value: number) => (allValues[value] / 9) * 100; +const getValueProgress = (allValues: Record, value: number) => (allValues[value] / FieldSizeConstant) * 100; const getAvailableValues = (allValues: Record) => Object.keys(allValues) - .filter(key => allValues[Number(key)] < 9) + .filter(key => allValues[Number(key)] < FieldSizeConstant) .map(Number); export const AvailableValues = () => { diff --git a/components/elapsed-time/elapsed-time.styles.ts b/components/elapsed-time/elapsed-time.styles.ts new file mode 100644 index 0000000..7b638cb --- /dev/null +++ b/components/elapsed-time/elapsed-time.styles.ts @@ -0,0 +1,14 @@ +import { StyleSheet } from 'react-native'; + +import { Colors } from '../theme'; + +export const ElapsedTimeStyles = StyleSheet.create({ + container: { + alignItems: 'center', + flex: 1, + justifyContent: 'center' + }, + text: { + color: Colors.black + } +}); diff --git a/components/elapsed-time/elapsed-time.tsx b/components/elapsed-time/elapsed-time.tsx new file mode 100644 index 0000000..bcfea12 --- /dev/null +++ b/components/elapsed-time/elapsed-time.tsx @@ -0,0 +1,36 @@ +import { differenceInSeconds, format, setMinutes, setSeconds } from 'date-fns'; +import { useEffect, useState } from 'react'; +import { Text, View } from 'react-native'; + +import { ElapsedTimeStyles as styles } from './elapsed-time.styles'; + +const getElapsedTime = (start: Date) => (end: Date) => { + const currentTime = new Date(); + + const remainingSeconds = differenceInSeconds(end, start); + const minutes = Math.floor(remainingSeconds / 60); + const seconds = remainingSeconds % 60; + + return format(setMinutes(setSeconds(currentTime, seconds), minutes), 'mm:ss'); +}; + +interface Props { + startedAt: Date; +} + +export const ElapsedTime = ({ startedAt }: Props) => { + const [currentTime, setCurrentTime] = useState(new Date()); + const elapsedFormatted = getElapsedTime(startedAt); + + useEffect(() => { + const handle = setInterval(() => setCurrentTime(new Date()), 1000); + + return () => clearTimeout(handle); + }, []); + + return ( + + ({elapsedFormatted(currentTime)}) + + ); +}; diff --git a/components/field/field.styles.ts b/components/field/field.styles.ts index 465e857..3716f0e 100644 --- a/components/field/field.styles.ts +++ b/components/field/field.styles.ts @@ -6,7 +6,7 @@ export const FieldStyles = StyleSheet.create({ }, wrapper: { alignItems: 'center', - flex: 5, + flex: 7, flexDirection: 'column', justifyContent: 'center', margin: 'auto', diff --git a/components/field/field.tsx b/components/field/field.tsx index 150e995..a97d93e 100644 --- a/components/field/field.tsx +++ b/components/field/field.tsx @@ -2,6 +2,7 @@ import { type OnEventFn } from '@rnw-community/shared'; import { View } from 'react-native'; import { type CellInterface } from '../../interfaces/cell.interface'; +import { type FieldInterface } from '../../interfaces/field.interface'; import { isCellHighlighted } from '../../utils/cell/is-cell-highlighted.util'; import { isSameCellValue } from '../../utils/cell/is-same-cell-value.util'; import { isSameCell } from '../../utils/cell/is-same-cell.util'; @@ -10,7 +11,7 @@ import { Cell } from '../field-cell/cell'; import { FieldStyles as styles } from './field.styles'; interface Props { - field: CellInterface[][]; + field: FieldInterface; selectedCell?: CellInterface; onSelect: OnEventFn; } diff --git a/constants/field.constant.ts b/constants/field.constant.ts new file mode 100644 index 0000000..d3df574 --- /dev/null +++ b/constants/field.constant.ts @@ -0,0 +1,3 @@ +export const FieldSizeConstant = 9; +export const FieldCellCountConstant = FieldSizeConstant * FieldSizeConstant; +export const FieldGroupSizeConstant = 3; diff --git a/constants/score.constant.ts b/constants/score.constant.ts new file mode 100644 index 0000000..36f5a6f --- /dev/null +++ b/constants/score.constant.ts @@ -0,0 +1,6 @@ +export const ScoreCorrectValueConstant = 500; +export const ScoreElapsedCoefficientConstant = 0.001; +export const ScoreMistakesCoefficientConstant = 0.05; +export const ScoreLastInRowCoefficientConstant = 2; +export const ScoreLastInColCoefficientConstant = 3; +export const ScoreLastInGroupCoefficientConstant = 3; diff --git a/enums/difficulty.enum.tsx b/enums/difficulty.enum.tsx index be2af0e..26deea4 100644 --- a/enums/difficulty.enum.tsx +++ b/enums/difficulty.enum.tsx @@ -1,3 +1,5 @@ +import { FieldCellCountConstant } from '../constants/field.constant'; + export enum DifficultyEnum { Newbie = 'Newbie', Easy = 'Easy', @@ -7,9 +9,9 @@ export enum DifficultyEnum { } export const difficultyValues = { - [DifficultyEnum.Newbie]: Math.ceil(9 * 9 * 0.1), - [DifficultyEnum.Easy]: Math.ceil(9 * 9 * 0.2), - [DifficultyEnum.Medium]: Math.ceil(9 * 9 * 0.4), - [DifficultyEnum.Hard]: Math.ceil(9 * 9 * 0.5), - [DifficultyEnum.Nightmare]: Math.ceil(9 * 9 * 0.7) + [DifficultyEnum.Newbie]: Math.ceil(FieldCellCountConstant * 0.1), + [DifficultyEnum.Easy]: Math.ceil(FieldCellCountConstant * 0.2), + [DifficultyEnum.Medium]: Math.ceil(FieldCellCountConstant * 0.4), + [DifficultyEnum.Hard]: Math.ceil(FieldCellCountConstant * 0.5), + [DifficultyEnum.Nightmare]: Math.ceil(FieldCellCountConstant * 0.8) }; diff --git a/interfaces/field.interface.ts b/interfaces/field.interface.ts new file mode 100644 index 0000000..365df63 --- /dev/null +++ b/interfaces/field.interface.ts @@ -0,0 +1,3 @@ +import { type CellInterface } from './cell.interface'; + +export type FieldInterface = CellInterface[][]; diff --git a/interfaces/game-state.interface.ts b/interfaces/game-state.interface.ts index 239a553..4a53d69 100644 --- a/interfaces/game-state.interface.ts +++ b/interfaces/game-state.interface.ts @@ -1,6 +1,7 @@ import { InitialDateConstant } from '../constants/date.constant'; import { type CellInterface } from './cell.interface'; +import { type FieldInterface } from './field.interface'; /** * General game state, used for logic and persisting @@ -11,8 +12,8 @@ export interface GameStateInterface { calculatedAt: Date; startedAt: Date; completionPercent: number; - filledField: CellInterface[][]; - gameField: CellInterface[][]; + filledField: FieldInterface; + gameField: FieldInterface; selectedCell?: CellInterface; selectedValue?: number; } diff --git a/readme.md b/readme.md index 28f2916..06c347d 100644 --- a/readme.md +++ b/readme.md @@ -11,20 +11,15 @@ Sudoku game to help Ukraine win the war against Russia. - [ ] add gamification and percentage of completeness ### Frontend -- [ ] add game logic: - - [ ] timer - - [ ] score and its calculation based on errors, timer, row/col/group finish - Scoring logic v1: - - base is `100` points for correctly solved cell - - with each second(10 seconds?) score is decreased by `1` point(check min value of `1`) - - with each mistake score is decreased by `10` points(check min value of `1`) - - with each row/col/group solved score is increased by `100` points - [ ] add animations - - [ ] add number flying to its stop? - - [ ] add more fun to winner page(ZSU, Ukraine, donation CTA) - - [ ] add more fun to looser page(ZSU, Ukraine, donation CTA) + - [ ] add number flying to its stop? + - [ ] add more fun to winner page(ZSU, Ukraine, donation CTA) + - [ ] add more fun to looser page(ZSU, Ukraine, donation CTA) - [ ] add successful run count and longest run count history on main screen? - [ ] add donation CTA on main screen +- [x] add game logic: + - [x] timer + - [x] score and its calculation based on errors, timer, row/col/group finish - [x] optimize rendering(why does it lag? =) #### Web @@ -45,6 +40,7 @@ Sudoku game to help Ukraine win the war against Russia. - [ ] setup github actions for PRs, create web, expo previews - [ ] add e2e tests(maestro) - [ ] setup android build and deployment +- [ ] setup [eas submit](https://docs.expo.dev/submit/eas-json/) credentials and github action - [x] setup eas - [x] setup iphone deployment - [x] add github actions 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 df4fae3..dc79eed 100644 --- a/store/app-root/actions/app-root-select-value.action.ts +++ b/store/app-root/actions/app-root-select-value.action.ts @@ -3,9 +3,10 @@ import { isDefined } from '@rnw-community/shared'; import * as Haptics from 'expo-haptics'; import { BlankCellValueConstant } from '../../../constants/blank-cell-value.constant'; +import { calculateScore } from '../../../utils/calculate-score.util'; import { hapticNotification } from '../../../utils/haptic.utils'; import { type AppDispatch, type RootState } from '../../app.store'; -import { appRootMadeAMistake, appRootSetValueAction } from '../app-root.actions'; +import { appRootIncreaseScoreAction, appRootMadeAMistake, appRootSetValueAction } from '../app-root.actions'; export const appRootSelectValueAction = createAsyncThunk( 'appRoot/selectValue', @@ -19,6 +20,10 @@ export const appRootSelectValueAction = createAsyncThunk getAvailableFieldValues(state.gameField)); export const appRootMistakesSelector = createSelector(appRootSelector, state => state.mistakes); export const appRootGameStartedAtSelector = createSelector(appRootSelector, state => new Date(state.startedAt)); +export const appRootScoreSelector = createSelector(appRootSelector, state => state.score); diff --git a/store/app-root/app-root.slice.ts b/store/app-root/app-root.slice.ts index 3b3b01e..9c0ed12 100644 --- a/store/app-root/app-root.slice.ts +++ b/store/app-root/app-root.slice.ts @@ -1,5 +1,6 @@ import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; +import { FieldSizeConstant } from '../../constants/field.constant'; import { type DifficultyEnum, difficultyValues } from '../../enums/difficulty.enum'; import { type CellInterface } from '../../interfaces/cell.interface'; import { createField } from '../../utils/field/create-field.util'; @@ -14,7 +15,7 @@ export const appRootSlice = createSlice({ load: (state, action: PayloadAction) => { const blankCellsCount = difficultyValues[action.payload]; - state.filledField = createField(9); + state.filledField = createField(FieldSizeConstant); state.gameField = createGameField(state.filledField, blankCellsCount); state.startedAt = new Date(); }, @@ -33,6 +34,9 @@ export const appRootSlice = createSlice({ state.selectedCell = state.gameField[cell.y][cell.x]; state.gameField[cell.y][cell.x].value = cell.value; }, + increaseScore: (state, action: PayloadAction) => { + state.score += action.payload; + }, madeAMistake: state => { state.mistakes++; } diff --git a/utils/calculate-score.util.ts b/utils/calculate-score.util.ts new file mode 100644 index 0000000..4402466 --- /dev/null +++ b/utils/calculate-score.util.ts @@ -0,0 +1,40 @@ +import { + ScoreCorrectValueConstant, + ScoreElapsedCoefficientConstant, + ScoreLastInColCoefficientConstant, + ScoreLastInGroupCoefficientConstant, + ScoreLastInRowCoefficientConstant, + ScoreMistakesCoefficientConstant +} from '../constants/score.constant'; +import { type CellInterface } from '../interfaces/cell.interface'; +import { type FieldInterface } from '../interfaces/field.interface'; + +import { isLastInCol } from './cell/is-last-in-col.util'; +import { isLastInGroup } from './cell/is-last-in-group.util'; +import { isLastInRow } from './cell/is-last-in-row.util'; + +export const calculateScore = (gameField: FieldInterface, selectedCell: CellInterface, mistakes: number, startedAt: Date): number => { + const elapsedS = (new Date().getTime() - startedAt.getTime()) / 1000; + + const valueDecreasedByMistakes = Math.floor( + ScoreCorrectValueConstant - ScoreCorrectValueConstant * mistakes * ScoreMistakesCoefficientConstant + ); + const valueDecreasedByElapsed = Math.floor( + valueDecreasedByMistakes - valueDecreasedByMistakes * elapsedS * ScoreElapsedCoefficientConstant + ); + + let totalEarned = valueDecreasedByElapsed; + if (isLastInRow(gameField, selectedCell)) { + totalEarned += ScoreLastInRowCoefficientConstant; + } + + if (isLastInGroup(gameField, selectedCell)) { + totalEarned += ScoreLastInGroupCoefficientConstant; + } + + if (isLastInCol(gameField, selectedCell)) { + totalEarned += ScoreLastInColCoefficientConstant; + } + + return totalEarned; +}; diff --git a/utils/cell/create-cell.util.ts b/utils/cell/create-cell.util.ts index 20c4d74..d42d709 100644 --- a/utils/cell/create-cell.util.ts +++ b/utils/cell/create-cell.util.ts @@ -1,5 +1,7 @@ import { BlankCellValueConstant } from '../../constants/blank-cell-value.constant'; +import { FieldGroupSizeConstant } from '../../constants/field.constant'; -const getGroupValue = (x: number, y: number): number => Math.floor(x / 3) * 3 + Math.floor(y / 3) + 1; +const getGroupValue = (x: number, y: number): number => + Math.floor(x / FieldGroupSizeConstant) * FieldGroupSizeConstant + Math.floor(y / FieldGroupSizeConstant) + 1; export const createCell = (x: number, y: number, value = BlankCellValueConstant) => ({ group: getGroupValue(x, y), value, x, y }); diff --git a/utils/cell/is-group-end.util.ts b/utils/cell/is-group-end.util.ts index 0327aa9..8828a56 100644 --- a/utils/cell/is-group-end.util.ts +++ b/utils/cell/is-group-end.util.ts @@ -1,3 +1,5 @@ +import { FieldGroupSizeConstant, FieldSizeConstant } from '../../constants/field.constant'; + export const isGroupEnd = (index: number): boolean => { - return index < 8 && (index + 1) % 3 === 0; + return index < FieldSizeConstant - 1 && (index + 1) % FieldGroupSizeConstant === 0; }; diff --git a/utils/cell/is-last-in-col.util.ts b/utils/cell/is-last-in-col.util.ts new file mode 100644 index 0000000..9b59347 --- /dev/null +++ b/utils/cell/is-last-in-col.util.ts @@ -0,0 +1,4 @@ +import { type CellInterface } from '../../interfaces/cell.interface'; +import { type FieldInterface } from '../../interfaces/field.interface'; + +export const isLastInCol = (_gameField: FieldInterface, _selectedCell: CellInterface): boolean => true; diff --git a/utils/cell/is-last-in-group.util.ts b/utils/cell/is-last-in-group.util.ts new file mode 100644 index 0000000..778d822 --- /dev/null +++ b/utils/cell/is-last-in-group.util.ts @@ -0,0 +1,4 @@ +import { type CellInterface } from '../../interfaces/cell.interface'; +import { type FieldInterface } from '../../interfaces/field.interface'; + +export const isLastInGroup = (_gameField: FieldInterface, _selectedCell: CellInterface): boolean => true; diff --git a/utils/cell/is-last-in-row.util.ts b/utils/cell/is-last-in-row.util.ts new file mode 100644 index 0000000..5175789 --- /dev/null +++ b/utils/cell/is-last-in-row.util.ts @@ -0,0 +1,4 @@ +import { type CellInterface } from '../../interfaces/cell.interface'; +import { type FieldInterface } from '../../interfaces/field.interface'; + +export const isLastInRow = (_gameField: FieldInterface, _selectedCell: CellInterface): boolean => true; diff --git a/utils/field/create-field.util.ts b/utils/field/create-field.util.ts index d0d1db4..9c5bacf 100644 --- a/utils/field/create-field.util.ts +++ b/utils/field/create-field.util.ts @@ -1,4 +1,5 @@ import { type CellInterface } from '../../interfaces/cell.interface'; +import { type FieldInterface } from '../../interfaces/field.interface'; import { createCell } from '../cell/create-cell.util'; import { fillField } from './fill-field.util'; @@ -13,8 +14,8 @@ const createFillingValues = (length: number): number[] => { return values; }; -export const createField = (size: number): CellInterface[][] => { - const field: CellInterface[][] = []; +export const createField = (size: number): FieldInterface => { + const field: FieldInterface = []; for (let y = 0; y < size; y++) { const row: CellInterface[] = []; diff --git a/utils/field/create-game-field.util.ts b/utils/field/create-game-field.util.ts index 0fcac51..e02b778 100644 --- a/utils/field/create-game-field.util.ts +++ b/utils/field/create-game-field.util.ts @@ -1,10 +1,10 @@ import { BlankCellValueConstant } from '../../constants/blank-cell-value.constant'; -import { type CellInterface } from '../../interfaces/cell.interface'; +import { type FieldInterface } from '../../interfaces/field.interface'; -const cloneField = (field: CellInterface[][]): CellInterface[][] => field.map(row => row.map(cell => ({ ...cell }))); -const getRandomPosition = (field: CellInterface[][]): number => Math.floor(Math.random() * field.length); +const cloneField = (field: FieldInterface): FieldInterface => field.map(row => row.map(cell => ({ ...cell }))); +const getRandomPosition = (field: FieldInterface): number => Math.floor(Math.random() * field.length); -export const createGameField = (originalField: CellInterface[][], blankCellsCount: number): CellInterface[][] => { +export const createGameField = (originalField: FieldInterface, blankCellsCount: number): FieldInterface => { const newField = cloneField(originalField); for (let i = 0; i < blankCellsCount; i++) { diff --git a/utils/field/fill-field.util.ts b/utils/field/fill-field.util.ts index 856d00e..543d5a9 100644 --- a/utils/field/fill-field.util.ts +++ b/utils/field/fill-field.util.ts @@ -1,11 +1,12 @@ import { BlankCellValueConstant } from '../../constants/blank-cell-value.constant'; import { type CellInterface } from '../../interfaces/cell.interface'; +import { type FieldInterface } from '../../interfaces/field.interface'; import { createCell } from '../cell/create-cell.util'; import { hasBlankCells } from './has-blank-cells.util'; import { isCorrectCell } from './is-correct-cell.util'; -export const fillField = (field: CellInterface[][], values: number[]): boolean => { +export const fillField = (field: FieldInterface, values: number[]): boolean => { const [needsFilling, y, x] = hasBlankCells(field); if (!needsFilling) { diff --git a/utils/field/has-blank-cells.util.ts b/utils/field/has-blank-cells.util.ts index 597f086..f95778a 100644 --- a/utils/field/has-blank-cells.util.ts +++ b/utils/field/has-blank-cells.util.ts @@ -1,7 +1,7 @@ import { BlankCellValueConstant } from '../../constants/blank-cell-value.constant'; -import { type CellInterface } from '../../interfaces/cell.interface'; +import { type FieldInterface } from '../../interfaces/field.interface'; -export const hasBlankCells = (field: CellInterface[][]): [hasBlankCells: boolean, lastY: number, lastX: number] => { +export const hasBlankCells = (field: FieldInterface): [hasBlankCells: boolean, lastY: number, lastX: number] => { let y = 0; let x = 0; diff --git a/utils/field/has-value-in-column.util.ts b/utils/field/has-value-in-column.util.ts index 28fde73..65c9b1e 100644 --- a/utils/field/has-value-in-column.util.ts +++ b/utils/field/has-value-in-column.util.ts @@ -1,8 +1,9 @@ import { type CellInterface } from '../../interfaces/cell.interface'; +import { type FieldInterface } from '../../interfaces/field.interface'; -export const hasValueInColumn = (cell: CellInterface, matrix: CellInterface[][]): boolean => { - for (let row = 0; row < matrix.length; row++) { - if (matrix[row][cell.x].value === cell.value) { +export const hasValueInColumn = (cell: CellInterface, field: FieldInterface): boolean => { + for (let row = 0; row < field.length; row++) { + if (field[row][cell.x].value === cell.value) { return true; } } diff --git a/utils/field/has-value-in-group.util.ts b/utils/field/has-value-in-group.util.ts index 9731eb3..280712f 100644 --- a/utils/field/has-value-in-group.util.ts +++ b/utils/field/has-value-in-group.util.ts @@ -1,12 +1,14 @@ +import { FieldGroupSizeConstant } from '../../constants/field.constant'; import { type CellInterface } from '../../interfaces/cell.interface'; +import { type FieldInterface } from '../../interfaces/field.interface'; -export const hasValueInGroup = (cell: CellInterface, matrix: CellInterface[][]): boolean => { - const boxStartY = cell.y - (cell.y % 3); - const boxStartX = cell.x - (cell.x % 3); +export const hasValueInGroup = (cell: CellInterface, field: FieldInterface): boolean => { + const boxStartY = cell.y - (cell.y % FieldGroupSizeConstant); + const boxStartX = cell.x - (cell.x % FieldGroupSizeConstant); - for (let row = 0; row < 3; row++) { - for (let col = 0; col < 3; col++) { - if (matrix[row + boxStartY][col + boxStartX].value === cell.value) { + for (let row = 0; row < FieldGroupSizeConstant; row++) { + for (let col = 0; col < FieldGroupSizeConstant; col++) { + if (field[row + boxStartY][col + boxStartX].value === cell.value) { return true; } } diff --git a/utils/field/has-value-in-row.util.ts b/utils/field/has-value-in-row.util.ts index b48b7d6..6e3adf4 100644 --- a/utils/field/has-value-in-row.util.ts +++ b/utils/field/has-value-in-row.util.ts @@ -1,8 +1,9 @@ import { type CellInterface } from '../../interfaces/cell.interface'; +import { type FieldInterface } from '../../interfaces/field.interface'; -export const hasValueInRow = (cell: CellInterface, matrix: CellInterface[][]): boolean => { - for (let col = 0; col < matrix.length; col++) { - if (matrix[cell.y][col].value === cell.value) { +export const hasValueInRow = (cell: CellInterface, field: FieldInterface): boolean => { + for (let col = 0; col < field.length; col++) { + if (field[cell.y][col].value === cell.value) { return true; } } diff --git a/utils/field/is-correct-cell.util.ts b/utils/field/is-correct-cell.util.ts index bdf5e1f..8d7e0a2 100644 --- a/utils/field/is-correct-cell.util.ts +++ b/utils/field/is-correct-cell.util.ts @@ -1,9 +1,10 @@ import { type CellInterface } from '../../interfaces/cell.interface'; +import { type FieldInterface } from '../../interfaces/field.interface'; import { hasValueInColumn } from './has-value-in-column.util'; import { hasValueInGroup } from './has-value-in-group.util'; import { hasValueInRow } from './has-value-in-row.util'; -export const isCorrectCell = (cell: CellInterface, matrix: CellInterface[][]): boolean => { - return !hasValueInRow(cell, matrix) && !hasValueInColumn(cell, matrix) && !hasValueInGroup(cell, matrix); +export const isCorrectCell = (cell: CellInterface, field: FieldInterface): boolean => { + return !hasValueInRow(cell, field) && !hasValueInColumn(cell, field) && !hasValueInGroup(cell, field); }; diff --git a/utils/get-available-field-values.util.ts b/utils/get-available-field-values.util.ts index 7b3c5a3..ecde1e1 100644 --- a/utils/get-available-field-values.util.ts +++ b/utils/get-available-field-values.util.ts @@ -1,9 +1,9 @@ import { isDefined } from '@rnw-community/shared'; import { BlankCellValueConstant } from '../constants/blank-cell-value.constant'; -import { type CellInterface } from '../interfaces/cell.interface'; +import { type FieldInterface } from '../interfaces/field.interface'; -export const getAvailableFieldValues = (field: CellInterface[][]): Record => { +export const getAvailableFieldValues = (field: FieldInterface): Record => { const availableValues: Record = {}; for (let x = 0; x < field.length; x++) { for (let y = 0; y < field[x].length; y++) {