-
Notifications
You must be signed in to change notification settings - Fork 0
Lesson 18 #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Lesson 18 #11
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,15 @@ | ||
| import React from 'react'; | ||
| import { Router } from 'react-router-dom'; | ||
| import { createMemoryHistory } from 'history'; | ||
| import { BrowserRouter as Router } from 'react-router-dom'; | ||
| import { AppContainer } from './AppContainer'; | ||
| import { Provider } from 'react-redux'; | ||
| import { store } from './rdx/store'; | ||
|
|
||
| export const App: React.FC<{}> = () => { | ||
| const history = createMemoryHistory(); | ||
| return ( | ||
| <Router history={history}> | ||
| <AppContainer /> | ||
| </Router> | ||
| <Provider store={store}> | ||
| <Router> | ||
| <AppContainer /> | ||
| </Router> | ||
| </Provider> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| import React from 'react'; | ||
| import styled from '@emotion/styled'; | ||
| import { GameOfLifeState } from '@/rdx/reducer'; | ||
| import { | ||
| setFill, | ||
| clearBoard, | ||
| updateBoard, | ||
| changeSpeed, | ||
| gameRun, | ||
| isGame, | ||
| } from '@/rdx/actions'; | ||
| import { Field } from '@/components/Field/Field'; | ||
| import { connect } from 'react-redux'; | ||
| import { InterfaceLayout } from './Interfaces/Interfaces'; | ||
|
|
||
| const GameOfLifeProtoWrapper = styled.div` | ||
| display: flex; | ||
| flex-direction: column; | ||
| justify-content: center; | ||
| align-items: center; | ||
| margin-right: 10px; | ||
| `; | ||
|
|
||
| function mapStateToProps(state: GameOfLifeState) { | ||
| return { | ||
| gameField: state.field, | ||
| speed: state.speed, | ||
| run: state.game, | ||
| }; | ||
| } | ||
|
|
||
| const mapDispatchToProps = { | ||
| setFill, | ||
| clearBoard, | ||
| updateBoard, | ||
| changeSpeed, | ||
| gameRun, | ||
| isGame, | ||
| }; | ||
|
|
||
| type GameOfLifeWithReduxProps = ReturnType<typeof mapStateToProps> & | ||
| typeof mapDispatchToProps; | ||
|
|
||
| export class GameOfLife extends React.Component<GameOfLifeWithReduxProps, {}> { | ||
| private timerID!: NodeJS.Timeout; | ||
|
|
||
| onClick = (x: number, y: number) => { | ||
| this.props['setFill']({ x, y }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. почему скобочная нотация? точечная же короче
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Взял из примера, а потом когда разобрался, забыл исправить. |
||
| }; | ||
|
|
||
| speedChange = (ev: React.ChangeEvent) => { | ||
| this.props.changeSpeed((ev.target as HTMLInputElement).value); | ||
| }; | ||
|
|
||
| componentDidUpdate(prevProps: typeof mapDispatchToProps) { | ||
| const isRunningGame = this.props.run.gameRun; | ||
| const speed = this.props.speed.value; | ||
| const gameStarted = !prevProps.gameRun && isRunningGame; | ||
| const gameStopped = prevProps.gameRun && !isRunningGame; | ||
| if (isRunningGame || gameStopped) { | ||
| clearInterval(this.timerID); | ||
| } | ||
|
|
||
| if (isRunningGame || gameStarted) { | ||
| this.timerID = setInterval(() => { | ||
| this.props.isGame(); | ||
| }, speed); | ||
| } | ||
| } | ||
|
|
||
| render() { | ||
| return ( | ||
| <GameOfLifeProtoWrapper> | ||
| <Field field={this.props.gameField} onClick={this.onClick} /> | ||
| <InterfaceLayout | ||
| button1={{ | ||
| text: this.props.run.gameRun ? 'Остановить' : 'Начать', | ||
| onClick: this.props.gameRun, | ||
| }} | ||
| button2={{ | ||
| text: 'Очистить', | ||
| onClick: this.props.clearBoard, | ||
| }} | ||
| button3={{ | ||
| text: 'Обновить', | ||
| onClick: this.props.updateBoard, | ||
| }} | ||
| input={{ | ||
| onChange: this.speedChange, | ||
| value: this.props.speed.value, | ||
| name: 'speed', | ||
| type: 'range', | ||
| min: '50', | ||
| max: '1000', | ||
| step: '50', | ||
| }} | ||
| /> | ||
| </GameOfLifeProtoWrapper> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| export const GameOfLifeWithRedux = connect( | ||
| mapStateToProps, | ||
| mapDispatchToProps, | ||
| )(GameOfLife); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| export const SET_CELL = 'SET_CELL'; | ||
| export const CLEAR_BOARD = 'CLEAR_BOARD'; | ||
| export const UPDATE_BOARD = 'UPDATE_BOARD'; | ||
| export const CHANGE_SPEED = 'CHANGE_SPEED'; | ||
| export const GAME_RUN = 'GAME_RUN'; | ||
| export const IS_GAME = 'IS_GAME'; | ||
|
|
||
| export type Coordinates = { x: number; y: number }; | ||
| export type SpeedState = number | string; | ||
|
|
||
| export function setFill(payload: Coordinates) { | ||
| return { | ||
| type: SET_CELL, | ||
| payload, | ||
| }; | ||
| } | ||
|
|
||
| export function clearBoard() { | ||
| return { | ||
| type: CLEAR_BOARD, | ||
| }; | ||
| } | ||
|
Comment on lines
+11
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. как на счет redux-toolkit? ну или хотя бы свой хелпер сделать, это же просто куча однотипных действий
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Я пробовал предварительно без toolkit, чтобы разобратьтся в общем с Redux. Следующим шагом хотел уже с toolkit сделать. |
||
|
|
||
| export function updateBoard() { | ||
| return { | ||
| type: UPDATE_BOARD, | ||
| }; | ||
| } | ||
|
|
||
| export function changeSpeed(payload: SpeedState) { | ||
| return { | ||
| type: CHANGE_SPEED, | ||
| payload, | ||
| }; | ||
| } | ||
|
|
||
| export function gameRun() { | ||
| return { | ||
| type: GAME_RUN, | ||
| }; | ||
| } | ||
|
|
||
| export function isGame() { | ||
| return { | ||
| type: IS_GAME, | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| import { Action } from 'redux'; | ||
| import * as actionTypes from '@/rdx/actions'; | ||
|
|
||
| type FieldState = boolean[][]; | ||
|
|
||
| const cellGridFillRandom = ( | ||
| rows: number, | ||
| columns: number, | ||
| cellStatus = () => Math.random() < 0.3, | ||
| ) => { | ||
| const grid: FieldState = []; | ||
| for (let y = 0; y < rows; y++) { | ||
| grid[y] = []; | ||
| for (let x = 0; x < columns; x++) { | ||
| grid[y][x] = cellStatus(); | ||
| } | ||
| } | ||
| return grid; | ||
| }; | ||
|
|
||
| const defaultState: FieldState = cellGridFillRandom(20, 20); | ||
|
|
||
| export function field( | ||
| state: FieldState = defaultState, | ||
| action: Action & { payload?: any }, | ||
| ): FieldState { | ||
| switch (action.type) { | ||
| case actionTypes.SET_CELL: { | ||
| const { x, y } = action.payload; | ||
| const newState = state.map((row) => [...row]); | ||
| newState[x][y] = !newState[x][y]; | ||
| return newState; | ||
| } | ||
|
|
||
| case actionTypes.CLEAR_BOARD: { | ||
| const newState = cellGridFillRandom(20, 20, () => false); | ||
| return newState; | ||
| } | ||
|
|
||
| case actionTypes.UPDATE_BOARD: { | ||
| const newState = cellGridFillRandom(20, 20); | ||
| return newState; | ||
| } | ||
|
|
||
| case actionTypes.IS_GAME: { | ||
| const nextStep = (prevState: FieldState) => { | ||
| const prevBoard = prevState; | ||
| const cloneBoard = state.map((row) => [...row]); | ||
|
|
||
| const amountAliveNeighbors = (x: number, y: number) => { | ||
| const eightNeighbors = [ | ||
| [-1, -1], | ||
| [-1, 0], | ||
| [-1, 1], | ||
| [0, 1], | ||
| [1, 1], | ||
| [1, 0], | ||
| [1, -1], | ||
| [0, -1], | ||
| ]; | ||
|
|
||
| return eightNeighbors.reduce((aliveNeighbors, neighbor) => { | ||
| const xCell = x + neighbor[0]; | ||
| const yCell = y + neighbor[1]; | ||
| const endBoard = | ||
| xCell >= 0 && | ||
| xCell < 20 && | ||
| yCell >= 0 && | ||
| yCell < 20; | ||
| if ( | ||
| aliveNeighbors < 4 && | ||
| endBoard && | ||
| prevBoard[xCell][yCell] | ||
|
Comment on lines
+50
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. хорошо бы это все вынести в отдельную функцию и покрыть ее тестами метод должен помещаться на экран - те не больше 25-35 строк
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Согласен, сделаю. |
||
| ) { | ||
| return aliveNeighbors + 1; | ||
| } else { | ||
| return aliveNeighbors; | ||
| } | ||
| }, 0); | ||
| }; | ||
|
|
||
| for (let rows = 0; rows < 20; rows++) { | ||
| for (let columns = 0; columns < 20; columns++) { | ||
| const totalAliveNeighbors = amountAliveNeighbors( | ||
| rows, | ||
| columns, | ||
| ); | ||
|
|
||
| if (!prevBoard[rows][columns]) { | ||
| if (totalAliveNeighbors === 3) | ||
| cloneBoard[rows][columns] = true; | ||
| } else { | ||
| if ( | ||
| totalAliveNeighbors < 2 || | ||
| totalAliveNeighbors > 3 | ||
| ) | ||
| cloneBoard[rows][columns] = false; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return cloneBoard; | ||
| }; | ||
| return nextStep(state); | ||
| } | ||
| } | ||
|
|
||
| return state; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { Action } from 'redux'; | ||
| import * as actionTypes from '@/rdx/actions'; | ||
|
|
||
| export type GameState = { | ||
| gameRun: boolean; | ||
| }; | ||
|
|
||
| const defaultState: GameState = { | ||
| gameRun: false, | ||
| }; | ||
|
|
||
| export function game( | ||
| state: GameState = defaultState, | ||
| action: Action & { payload?: any }, | ||
| ): GameState { | ||
| switch (action.type) { | ||
| case actionTypes.GAME_RUN: { | ||
| return { | ||
| gameRun: !state.gameRun, | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| return state; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
а почему тут появились закрывающие теги? зачем?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Странно, сейчас уберу, сами добавились автоматом.