diff --git a/src/index.tsx b/src/index.tsx
index e69de29..36580fd 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -0,0 +1,2 @@
+import React from 'react';
+import { render } from 'react-dom';
diff --git a/src/lesson4/Cell.css b/src/lesson4/Cell.css
new file mode 100644
index 0000000..b239643
--- /dev/null
+++ b/src/lesson4/Cell.css
@@ -0,0 +1,23 @@
+.cell {
+ background: #fff;
+ border: 1px solid #999;
+ font-size: 24px;
+ font-weight: bold;
+ line-height: 34px;
+ height: 34px;
+ margin-right: -1px;
+ margin-top: -1px;
+ padding: 0;
+ text-align: center;
+ width: 34px;
+ display: inline-block;
+}
+
+.cell-empty {
+ border-color: gray;
+}
+
+.cell-filled {
+ border-color: #fff;
+ background: black;
+}
diff --git a/src/lesson4/Cell.stories.tsx b/src/lesson4/Cell.stories.tsx
new file mode 100644
index 0000000..35a4394
--- /dev/null
+++ b/src/lesson4/Cell.stories.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import { Cell } from './Cell';
+import { withKnobs, number } from '@storybook/addon-knobs';
+
+// eslint-disable-next-line no-restricted-syntax
+export default {
+ title: 'Lesson 4 / Cell',
+ decorators: [withKnobs],
+};
+
+export const nonFilled = () => [
+ | ,
+];
+
+export const Filled = () => [
+ | ,
+];
diff --git a/src/lesson4/Cell.test.tsx b/src/lesson4/Cell.test.tsx
new file mode 100644
index 0000000..4ced06b
--- /dev/null
+++ b/src/lesson4/Cell.test.tsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import { mount } from 'enzyme';
+import { Cell } from './Cell';
+
+test.each`
+ inputFilled | expected
+ ${true} | ${'
'}
+ ${false} | ${''}
+`('renders cell for empty or filled', ({ inputFilled, expected }) => {
+ const cellItem = mount( | );
+
+ expect(cellItem.html()).toBe(expected);
+});
diff --git a/src/lesson4/Cell.tsx b/src/lesson4/Cell.tsx
new file mode 100644
index 0000000..6c3a4c3
--- /dev/null
+++ b/src/lesson4/Cell.tsx
@@ -0,0 +1,13 @@
+import React, { FC } from 'react';
+import './Cell.css';
+import type { CellProps } from './Interfaces';
+
+const Cell: FC = ({ filled, x, y }) => {
+ if (filled) {
+ return {filled}
;
+ }
+
+ return ;
+};
+
+export { Cell };
diff --git a/src/lesson4/Field.css b/src/lesson4/Field.css
new file mode 100644
index 0000000..99705aa
--- /dev/null
+++ b/src/lesson4/Field.css
@@ -0,0 +1,10 @@
+p {
+ margin: 0;
+ padding: 0;
+ line-height: 0;
+}
+div {
+ margin: 0;
+ padding: 0;
+ line-height: 0;
+}
diff --git a/src/lesson4/Field.stories.tsx b/src/lesson4/Field.stories.tsx
new file mode 100644
index 0000000..c437d62
--- /dev/null
+++ b/src/lesson4/Field.stories.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { Field } from './Field';
+import { withKnobs, object } from '@storybook/addon-knobs';
+
+// eslint-disable-next-line no-restricted-syntax
+export default {
+ title: 'Lesson 4 / Field',
+ decorators: [withKnobs],
+};
+
+const cellGridFillRandom = (cellStatus = () => Math.random() < 0.3) => {
+ const grid: boolean[][] = [];
+ for (let y = 0; y < 10; y++) {
+ grid[y] = [];
+ for (let x = 0; x < 10; x++) {
+ grid[y][x] = cellStatus();
+ }
+ }
+ return grid;
+};
+
+const cellGridNonFill = () =>
+ Array.from({ length: 10 }).fill(
+ Array.from({ length: 10 }).fill(false),
+ );
+
+export const emptyCell = () => [
+ ,
+];
+
+export const notEmptyCell = () => [
+ ,
+];
diff --git a/src/lesson4/Field.test.tsx b/src/lesson4/Field.test.tsx
new file mode 100644
index 0000000..22845e8
--- /dev/null
+++ b/src/lesson4/Field.test.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import { mount } from 'enzyme';
+import { Field } from './Field';
+
+test.each`
+ inputFilled | expectedBr | expectedDiv | expectedCellEmpty | expectedCellFilled
+ ${[
+ [true, false, true, true, false, true, true, true, false, true],
+ [true, true, true, true, false, false, false, true, false, true],
+]} | ${2} | ${20} | ${7} | ${13}
+`(
+ 'testing rendering field',
+ ({
+ inputFilled,
+ expectedBr,
+ expectedDiv,
+ expectedCellEmpty,
+ expectedCellFilled,
+ }) => {
+ const field = mount();
+ expect(field.find('p').length).toBe(expectedBr);
+ expect(field.find('.cell').length).toBe(expectedDiv);
+ expect(field.find('.cell-empty').length).toBe(expectedCellEmpty);
+ expect(field.find('.cell-filled').length).toBe(expectedCellFilled);
+ },
+);
diff --git a/src/lesson4/Field.tsx b/src/lesson4/Field.tsx
new file mode 100644
index 0000000..8d35853
--- /dev/null
+++ b/src/lesson4/Field.tsx
@@ -0,0 +1,17 @@
+import React, { FC } from 'react';
+import { Cell } from './Cell';
+import './Field.css';
+import type { FieldProps } from './Interfaces';
+
+const Field: FC = ({ field }) => (
+
+ {field.map((row, y) => [
+ ...row.map((filled: boolean, x) => (
+
|
+ )),
+ y !== row.length - 1 ?
: null,
+ ])}
+
+);
+
+export { Field };
diff --git a/src/lesson4/GameOfLifeProto.css b/src/lesson4/GameOfLifeProto.css
new file mode 100644
index 0000000..9a2646e
--- /dev/null
+++ b/src/lesson4/GameOfLifeProto.css
@@ -0,0 +1,3 @@
+.btn {
+ margin: 20px 0;
+}
diff --git a/src/lesson4/GameOfLifeProto.stories.tsx b/src/lesson4/GameOfLifeProto.stories.tsx
new file mode 100644
index 0000000..f9e8f1b
--- /dev/null
+++ b/src/lesson4/GameOfLifeProto.stories.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import { withKnobs, number } from '@storybook/addon-knobs';
+import { Field } from './Field';
+import { GameOfLifeProto } from './GameOfLifeProto';
+
+// eslint-disable-next-line no-restricted-syntax
+export default {
+ title: 'Lesson 4 / GameOfLifeProto',
+ decorators: [withKnobs],
+};
+
+export const GameOfLifePrototip = () => (
+
+);
diff --git a/src/lesson4/GameOfLifeProto.test.tsx b/src/lesson4/GameOfLifeProto.test.tsx
new file mode 100644
index 0000000..46bbd1b
--- /dev/null
+++ b/src/lesson4/GameOfLifeProto.test.tsx
@@ -0,0 +1,5 @@
+import React from 'react';
+import { mount, shallow } from 'enzyme';
+import GameOfLifeProto from './GameOfLifeProto';
+
+describe('GameOfLifeComponent testing', () => {});
diff --git a/src/lesson4/GameOfLifeProto.tsx b/src/lesson4/GameOfLifeProto.tsx
new file mode 100644
index 0000000..0281061
--- /dev/null
+++ b/src/lesson4/GameOfLifeProto.tsx
@@ -0,0 +1,70 @@
+import React from 'react';
+import './GameOfLifeProto.css';
+
+type FieldPropsComponent = React.FC<{
+ field: boolean[][];
+}>;
+
+interface FieldPropsInterface {
+ rows: number;
+ columns: number;
+ fieldComponent: FieldPropsComponent;
+}
+
+interface FieldState {
+ fieldState: boolean[][];
+}
+
+const cellGridFillRandom = (
+ rows: number,
+ columns: number,
+ cellStatus = () => Math.random() < 0.3,
+) => {
+ const grid: boolean[][] = [];
+ for (let y = 0; y < rows; y++) {
+ grid[y] = [];
+ for (let x = 0; x < columns; x++) {
+ grid[y][x] = cellStatus();
+ }
+ }
+ return grid;
+};
+
+class GameOfLifeProto extends React.Component {
+ private fieldComponent: FieldPropsComponent;
+
+ constructor(props: FieldPropsInterface) {
+ super(props);
+ this.fieldComponent = props.fieldComponent;
+ this.state = {
+ fieldState: cellGridFillRandom(this.props.rows, this.props.columns),
+ };
+ }
+
+ public onClick() {
+ this.setState(() => {
+ const cloneFieldState = cellGridFillRandom(
+ this.props.rows,
+ this.props.columns,
+ );
+
+ return {
+ fieldState: cloneFieldState,
+ };
+ });
+ }
+
+ render() {
+ const FieldComponent = this.fieldComponent;
+ return (
+ <>
+
+
+ >
+ );
+ }
+}
+
+export { GameOfLifeProto };
diff --git a/src/lesson4/Interfaces.ts b/src/lesson4/Interfaces.ts
new file mode 100644
index 0000000..6ecc75c
--- /dev/null
+++ b/src/lesson4/Interfaces.ts
@@ -0,0 +1,9 @@
+export interface CellProps {
+ filled?: boolean;
+ x?: number;
+ y?: number;
+}
+
+export interface FieldProps {
+ field: boolean[][];
+}
diff --git a/tsconfig.json b/tsconfig.json
index 4ef2a6d..40d51d9 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,7 +4,7 @@
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
- "target": "ES2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
+ "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
// "lib": [], /* Specify library files to be included in the compilation. */
"allowJs": true /* Allow javascript files to be compiled. */,