Browse Source

Improve Game files to match linter

Arnaud Vergnet 1 year ago
parent
commit
cbe3777957

+ 90
- 88
src/screens/Game/Shapes/BaseShape.js View File

@@ -1,13 +1,13 @@
1 1
 // @flow
2 2
 
3
-import type {CustomTheme} from "../../../managers/ThemeManager";
3
+import type {CustomThemeType} from '../../../managers/ThemeManager';
4 4
 
5
-export type Coordinates = {
6
-    x: number,
7
-    y: number,
8
-}
5
+export type CoordinatesType = {
6
+  x: number,
7
+  y: number,
8
+};
9 9
 
10
-type Shape = Array<Array<number>>;
10
+export type ShapeType = Array<Array<number>>;
11 11
 
12 12
 /**
13 13
  * Abstract class used to represent a BaseShape.
@@ -15,96 +15,98 @@ type Shape = Array<Array<number>>;
15 15
  * and in methods to implement
16 16
  */
17 17
 export default class BaseShape {
18
+  #currentShape: ShapeType;
18 19
 
19
-    #currentShape: Shape;
20
-    #rotation: number;
21
-    position: Coordinates;
22
-    theme: CustomTheme;
20
+  #rotation: number;
23 21
 
24
-    /**
25
-     * Prevent instantiation if classname is BaseShape to force class to be abstract
26
-     */
27
-    constructor(theme: CustomTheme) {
28
-        if (this.constructor === BaseShape)
29
-            throw new Error("Abstract class can't be instantiated");
30
-        this.theme = theme;
31
-        this.#rotation = 0;
32
-        this.position = {x: 0, y: 0};
33
-        this.#currentShape = this.getShapes()[this.#rotation];
34
-    }
22
+  position: CoordinatesType;
35 23
 
36
-    /**
37
-     * Gets this shape's color.
38
-     * Must be implemented by child class
39
-     */
40
-    getColor(): string {
41
-        throw new Error("Method 'getColor()' must be implemented");
42
-    }
24
+  theme: CustomThemeType;
43 25
 
44
-    /**
45
-     * Gets this object's all possible shapes as an array.
46
-     * Must be implemented by child class.
47
-     *
48
-     * Used by tests to read private fields
49
-     */
50
-    getShapes(): Array<Shape> {
51
-        throw new Error("Method 'getShapes()' must be implemented");
52
-    }
26
+  /**
27
+   * Prevent instantiation if classname is BaseShape to force class to be abstract
28
+   */
29
+  constructor(theme: CustomThemeType) {
30
+    if (this.constructor === BaseShape)
31
+      throw new Error("Abstract class can't be instantiated");
32
+    this.theme = theme;
33
+    this.#rotation = 0;
34
+    this.position = {x: 0, y: 0};
35
+    this.#currentShape = this.getShapes()[this.#rotation];
36
+  }
53 37
 
54
-    /**
55
-     * Gets this object's current shape.
56
-     */
57
-    getCurrentShape(): Shape {
58
-        return this.#currentShape;
59
-    }
38
+  /**
39
+   * Gets this shape's color.
40
+   * Must be implemented by child class
41
+   */
42
+  // eslint-disable-next-line class-methods-use-this
43
+  getColor(): string {
44
+    throw new Error("Method 'getColor()' must be implemented");
45
+  }
60 46
 
61
-    /**
62
-     * Gets this object's coordinates.
63
-     * This will return an array of coordinates representing the positions of the cells used by this object.
64
-     *
65
-     * @param isAbsolute Should we take into account the current position of the object?
66
-     * @return {Array<Coordinates>} This object cells coordinates
67
-     */
68
-    getCellsCoordinates(isAbsolute: boolean): Array<Coordinates> {
69
-        let coordinates = [];
70
-        for (let row = 0; row < this.#currentShape.length; row++) {
71
-            for (let col = 0; col < this.#currentShape[row].length; col++) {
72
-                if (this.#currentShape[row][col] === 1)
73
-                    if (isAbsolute)
74
-                        coordinates.push({x: this.position.x + col, y: this.position.y + row});
75
-                    else
76
-                        coordinates.push({x: col, y: row});
77
-            }
78
-        }
79
-        return coordinates;
80
-    }
47
+  /**
48
+   * Gets this object's all possible shapes as an array.
49
+   * Must be implemented by child class.
50
+   *
51
+   * Used by tests to read private fields
52
+   */
53
+  // eslint-disable-next-line class-methods-use-this
54
+  getShapes(): Array<ShapeType> {
55
+    throw new Error("Method 'getShapes()' must be implemented");
56
+  }
81 57
 
82
-    /**
83
-     * Rotate this object
84
-     *
85
-     * @param isForward Should we rotate clockwise?
86
-     */
87
-    rotate(isForward: boolean) {
88
-        if (isForward)
89
-            this.#rotation++;
90
-        else
91
-            this.#rotation--;
92
-        if (this.#rotation > 3)
93
-            this.#rotation = 0;
94
-        else if (this.#rotation < 0)
95
-            this.#rotation = 3;
96
-        this.#currentShape = this.getShapes()[this.#rotation];
97
-    }
58
+  /**
59
+   * Gets this object's current shape.
60
+   */
61
+  getCurrentShape(): ShapeType {
62
+    return this.#currentShape;
63
+  }
98 64
 
99
-    /**
100
-     * Move this object
101
-     *
102
-     * @param x Position X offset to add
103
-     * @param y Position Y offset to add
104
-     */
105
-    move(x: number, y: number) {
106
-        this.position.x += x;
107
-        this.position.y += y;
65
+  /**
66
+   * Gets this object's coordinates.
67
+   * This will return an array of coordinates representing the positions of the cells used by this object.
68
+   *
69
+   * @param isAbsolute Should we take into account the current position of the object?
70
+   * @return {Array<CoordinatesType>} This object cells coordinates
71
+   */
72
+  getCellsCoordinates(isAbsolute: boolean): Array<CoordinatesType> {
73
+    const coordinates = [];
74
+    for (let row = 0; row < this.#currentShape.length; row += 1) {
75
+      for (let col = 0; col < this.#currentShape[row].length; col += 1) {
76
+        if (this.#currentShape[row][col] === 1) {
77
+          if (isAbsolute) {
78
+            coordinates.push({
79
+              x: this.position.x + col,
80
+              y: this.position.y + row,
81
+            });
82
+          } else coordinates.push({x: col, y: row});
83
+        }
84
+      }
108 85
     }
86
+    return coordinates;
87
+  }
88
+
89
+  /**
90
+   * Rotate this object
91
+   *
92
+   * @param isForward Should we rotate clockwise?
93
+   */
94
+  rotate(isForward: boolean) {
95
+    if (isForward) this.#rotation += 1;
96
+    else this.#rotation -= 1;
97
+    if (this.#rotation > 3) this.#rotation = 0;
98
+    else if (this.#rotation < 0) this.#rotation = 3;
99
+    this.#currentShape = this.getShapes()[this.#rotation];
100
+  }
109 101
 
102
+  /**
103
+   * Move this object
104
+   *
105
+   * @param x Position X offset to add
106
+   * @param y Position Y offset to add
107
+   */
108
+  move(x: number, y: number) {
109
+    this.position.x += x;
110
+    this.position.y += y;
111
+  }
110 112
 }

+ 39
- 38
src/screens/Game/Shapes/ShapeI.js View File

@@ -1,45 +1,46 @@
1 1
 // @flow
2 2
 
3
-import BaseShape from "./BaseShape";
4
-import type {CustomTheme} from "../../../managers/ThemeManager";
3
+import BaseShape from './BaseShape';
4
+import type {CustomThemeType} from '../../../managers/ThemeManager';
5
+import type {ShapeType} from './BaseShape';
5 6
 
6 7
 export default class ShapeI extends BaseShape {
8
+  constructor(theme: CustomThemeType) {
9
+    super(theme);
10
+    this.position.x = 3;
11
+  }
7 12
 
8
-    constructor(theme: CustomTheme) {
9
-        super(theme);
10
-        this.position.x = 3;
11
-    }
13
+  getColor(): string {
14
+    return this.theme.colors.tetrisI;
15
+  }
12 16
 
13
-    getColor(): string {
14
-        return this.theme.colors.tetrisI;
15
-    }
16
-
17
-    getShapes() {
18
-        return [
19
-            [
20
-                [0, 0, 0, 0],
21
-                [1, 1, 1, 1],
22
-                [0, 0, 0, 0],
23
-                [0, 0, 0, 0],
24
-            ],
25
-            [
26
-                [0, 0, 1, 0],
27
-                [0, 0, 1, 0],
28
-                [0, 0, 1, 0],
29
-                [0, 0, 1, 0],
30
-            ],
31
-            [
32
-                [0, 0, 0, 0],
33
-                [0, 0, 0, 0],
34
-                [1, 1, 1, 1],
35
-                [0, 0, 0, 0],
36
-            ],
37
-            [
38
-                [0, 1, 0, 0],
39
-                [0, 1, 0, 0],
40
-                [0, 1, 0, 0],
41
-                [0, 1, 0, 0],
42
-            ],
43
-        ];
44
-    }
17
+  // eslint-disable-next-line class-methods-use-this
18
+  getShapes(): Array<ShapeType> {
19
+    return [
20
+      [
21
+        [0, 0, 0, 0],
22
+        [1, 1, 1, 1],
23
+        [0, 0, 0, 0],
24
+        [0, 0, 0, 0],
25
+      ],
26
+      [
27
+        [0, 0, 1, 0],
28
+        [0, 0, 1, 0],
29
+        [0, 0, 1, 0],
30
+        [0, 0, 1, 0],
31
+      ],
32
+      [
33
+        [0, 0, 0, 0],
34
+        [0, 0, 0, 0],
35
+        [1, 1, 1, 1],
36
+        [0, 0, 0, 0],
37
+      ],
38
+      [
39
+        [0, 1, 0, 0],
40
+        [0, 1, 0, 0],
41
+        [0, 1, 0, 0],
42
+        [0, 1, 0, 0],
43
+      ],
44
+    ];
45
+  }
45 46
 }

+ 35
- 34
src/screens/Game/Shapes/ShapeJ.js View File

@@ -1,41 +1,42 @@
1 1
 // @flow
2 2
 
3
-import BaseShape from "./BaseShape";
4
-import type {CustomTheme} from "../../../managers/ThemeManager";
3
+import BaseShape from './BaseShape';
4
+import type {CustomThemeType} from '../../../managers/ThemeManager';
5
+import type {ShapeType} from './BaseShape';
5 6
 
6 7
 export default class ShapeJ extends BaseShape {
8
+  constructor(theme: CustomThemeType) {
9
+    super(theme);
10
+    this.position.x = 3;
11
+  }
7 12
 
8
-    constructor(theme: CustomTheme) {
9
-        super(theme);
10
-        this.position.x = 3;
11
-    }
13
+  getColor(): string {
14
+    return this.theme.colors.tetrisJ;
15
+  }
12 16
 
13
-    getColor(): string {
14
-        return this.theme.colors.tetrisJ;
15
-    }
16
-
17
-    getShapes() {
18
-        return [
19
-            [
20
-                [1, 0, 0],
21
-                [1, 1, 1],
22
-                [0, 0, 0],
23
-            ],
24
-            [
25
-                [0, 1, 1],
26
-                [0, 1, 0],
27
-                [0, 1, 0],
28
-            ],
29
-            [
30
-                [0, 0, 0],
31
-                [1, 1, 1],
32
-                [0, 0, 1],
33
-            ],
34
-            [
35
-                [0, 1, 0],
36
-                [0, 1, 0],
37
-                [1, 1, 0],
38
-            ],
39
-        ];
40
-    }
17
+  // eslint-disable-next-line class-methods-use-this
18
+  getShapes(): Array<ShapeType> {
19
+    return [
20
+      [
21
+        [1, 0, 0],
22
+        [1, 1, 1],
23
+        [0, 0, 0],
24
+      ],
25
+      [
26
+        [0, 1, 1],
27
+        [0, 1, 0],
28
+        [0, 1, 0],
29
+      ],
30
+      [
31
+        [0, 0, 0],
32
+        [1, 1, 1],
33
+        [0, 0, 1],
34
+      ],
35
+      [
36
+        [0, 1, 0],
37
+        [0, 1, 0],
38
+        [1, 1, 0],
39
+      ],
40
+    ];
41
+  }
41 42
 }

+ 35
- 34
src/screens/Game/Shapes/ShapeL.js View File

@@ -1,41 +1,42 @@
1 1
 // @flow
2 2
 
3
-import BaseShape from "./BaseShape";
4
-import type {CustomTheme} from "../../../managers/ThemeManager";
3
+import BaseShape from './BaseShape';
4
+import type {CustomThemeType} from '../../../managers/ThemeManager';
5
+import type {ShapeType} from './BaseShape';
5 6
 
6 7
 export default class ShapeL extends BaseShape {
8
+  constructor(theme: CustomThemeType) {
9
+    super(theme);
10
+    this.position.x = 3;
11
+  }
7 12
 
8
-    constructor(theme: CustomTheme) {
9
-        super(theme);
10
-        this.position.x = 3;
11
-    }
13
+  getColor(): string {
14
+    return this.theme.colors.tetrisL;
15
+  }
12 16
 
13
-    getColor(): string {
14
-        return this.theme.colors.tetrisL;
15
-    }
16
-
17
-    getShapes() {
18
-        return [
19
-            [
20
-                [0, 0, 1],
21
-                [1, 1, 1],
22
-                [0, 0, 0],
23
-            ],
24
-            [
25
-                [0, 1, 0],
26
-                [0, 1, 0],
27
-                [0, 1, 1],
28
-            ],
29
-            [
30
-                [0, 0, 0],
31
-                [1, 1, 1],
32
-                [1, 0, 0],
33
-            ],
34
-            [
35
-                [1, 1, 0],
36
-                [0, 1, 0],
37
-                [0, 1, 0],
38
-            ],
39
-        ];
40
-    }
17
+  // eslint-disable-next-line class-methods-use-this
18
+  getShapes(): Array<ShapeType> {
19
+    return [
20
+      [
21
+        [0, 0, 1],
22
+        [1, 1, 1],
23
+        [0, 0, 0],
24
+      ],
25
+      [
26
+        [0, 1, 0],
27
+        [0, 1, 0],
28
+        [0, 1, 1],
29
+      ],
30
+      [
31
+        [0, 0, 0],
32
+        [1, 1, 1],
33
+        [1, 0, 0],
34
+      ],
35
+      [
36
+        [1, 1, 0],
37
+        [0, 1, 0],
38
+        [0, 1, 0],
39
+      ],
40
+    ];
41
+  }
41 42
 }

+ 31
- 30
src/screens/Game/Shapes/ShapeO.js View File

@@ -1,37 +1,38 @@
1 1
 // @flow
2 2
 
3
-import BaseShape from "./BaseShape";
4
-import type {CustomTheme} from "../../../managers/ThemeManager";
3
+import BaseShape from './BaseShape';
4
+import type {CustomThemeType} from '../../../managers/ThemeManager';
5
+import type {ShapeType} from './BaseShape';
5 6
 
6 7
 export default class ShapeO extends BaseShape {
8
+  constructor(theme: CustomThemeType) {
9
+    super(theme);
10
+    this.position.x = 4;
11
+  }
7 12
 
8
-    constructor(theme: CustomTheme) {
9
-        super(theme);
10
-        this.position.x = 4;
11
-    }
13
+  getColor(): string {
14
+    return this.theme.colors.tetrisO;
15
+  }
12 16
 
13
-    getColor(): string {
14
-        return this.theme.colors.tetrisO;
15
-    }
16
-
17
-    getShapes() {
18
-        return [
19
-            [
20
-                [1, 1],
21
-                [1, 1],
22
-            ],
23
-            [
24
-                [1, 1],
25
-                [1, 1],
26
-            ],
27
-            [
28
-                [1, 1],
29
-                [1, 1],
30
-            ],
31
-            [
32
-                [1, 1],
33
-                [1, 1],
34
-            ],
35
-        ];
36
-    }
17
+  // eslint-disable-next-line class-methods-use-this
18
+  getShapes(): Array<ShapeType> {
19
+    return [
20
+      [
21
+        [1, 1],
22
+        [1, 1],
23
+      ],
24
+      [
25
+        [1, 1],
26
+        [1, 1],
27
+      ],
28
+      [
29
+        [1, 1],
30
+        [1, 1],
31
+      ],
32
+      [
33
+        [1, 1],
34
+        [1, 1],
35
+      ],
36
+    ];
37
+  }
37 38
 }

+ 35
- 34
src/screens/Game/Shapes/ShapeS.js View File

@@ -1,41 +1,42 @@
1 1
 // @flow
2 2
 
3
-import BaseShape from "./BaseShape";
4
-import type {CustomTheme} from "../../../managers/ThemeManager";
3
+import BaseShape from './BaseShape';
4
+import type {CustomThemeType} from '../../../managers/ThemeManager';
5
+import type {ShapeType} from './BaseShape';
5 6
 
6 7
 export default class ShapeS extends BaseShape {
8
+  constructor(theme: CustomThemeType) {
9
+    super(theme);
10
+    this.position.x = 3;
11
+  }
7 12
 
8
-    constructor(theme: CustomTheme) {
9
-        super(theme);
10
-        this.position.x = 3;
11
-    }
13
+  getColor(): string {
14
+    return this.theme.colors.tetrisS;
15
+  }
12 16
 
13
-    getColor(): string {
14
-        return this.theme.colors.tetrisS;
15
-    }
16
-
17
-    getShapes() {
18
-        return [
19
-            [
20
-                [0, 1, 1],
21
-                [1, 1, 0],
22
-                [0, 0, 0],
23
-            ],
24
-            [
25
-                [0, 1, 0],
26
-                [0, 1, 1],
27
-                [0, 0, 1],
28
-            ],
29
-            [
30
-                [0, 0, 0],
31
-                [0, 1, 1],
32
-                [1, 1, 0],
33
-            ],
34
-            [
35
-                [1, 0, 0],
36
-                [1, 1, 0],
37
-                [0, 1, 0],
38
-            ],
39
-        ];
40
-    }
17
+  // eslint-disable-next-line class-methods-use-this
18
+  getShapes(): Array<ShapeType> {
19
+    return [
20
+      [
21
+        [0, 1, 1],
22
+        [1, 1, 0],
23
+        [0, 0, 0],
24
+      ],
25
+      [
26
+        [0, 1, 0],
27
+        [0, 1, 1],
28
+        [0, 0, 1],
29
+      ],
30
+      [
31
+        [0, 0, 0],
32
+        [0, 1, 1],
33
+        [1, 1, 0],
34
+      ],
35
+      [
36
+        [1, 0, 0],
37
+        [1, 1, 0],
38
+        [0, 1, 0],
39
+      ],
40
+    ];
41
+  }
41 42
 }

+ 35
- 34
src/screens/Game/Shapes/ShapeT.js View File

@@ -1,41 +1,42 @@
1 1
 // @flow
2 2
 
3
-import BaseShape from "./BaseShape";
4
-import type {CustomTheme} from "../../../managers/ThemeManager";
3
+import BaseShape from './BaseShape';
4
+import type {CustomThemeType} from '../../../managers/ThemeManager';
5
+import type {ShapeType} from './BaseShape';
5 6
 
6 7
 export default class ShapeT extends BaseShape {
8
+  constructor(theme: CustomThemeType) {
9
+    super(theme);
10
+    this.position.x = 3;
11
+  }
7 12
 
8
-    constructor(theme: CustomTheme) {
9
-        super(theme);
10
-        this.position.x = 3;
11
-    }
13
+  getColor(): string {
14
+    return this.theme.colors.tetrisT;
15
+  }
12 16
 
13
-    getColor(): string {
14
-        return this.theme.colors.tetrisT;
15
-    }
16
-
17
-    getShapes() {
18
-        return [
19
-            [
20
-                [0, 1, 0],
21
-                [1, 1, 1],
22
-                [0, 0, 0],
23
-            ],
24
-            [
25
-                [0, 1, 0],
26
-                [0, 1, 1],
27
-                [0, 1, 0],
28
-            ],
29
-            [
30
-                [0, 0, 0],
31
-                [1, 1, 1],
32
-                [0, 1, 0],
33
-            ],
34
-            [
35
-                [0, 1, 0],
36
-                [1, 1, 0],
37
-                [0, 1, 0],
38
-            ],
39
-        ];
40
-    }
17
+  // eslint-disable-next-line class-methods-use-this
18
+  getShapes(): Array<ShapeType> {
19
+    return [
20
+      [
21
+        [0, 1, 0],
22
+        [1, 1, 1],
23
+        [0, 0, 0],
24
+      ],
25
+      [
26
+        [0, 1, 0],
27
+        [0, 1, 1],
28
+        [0, 1, 0],
29
+      ],
30
+      [
31
+        [0, 0, 0],
32
+        [1, 1, 1],
33
+        [0, 1, 0],
34
+      ],
35
+      [
36
+        [0, 1, 0],
37
+        [1, 1, 0],
38
+        [0, 1, 0],
39
+      ],
40
+    ];
41
+  }
41 42
 }

+ 35
- 34
src/screens/Game/Shapes/ShapeZ.js View File

@@ -1,41 +1,42 @@
1 1
 // @flow
2 2
 
3
-import BaseShape from "./BaseShape";
4
-import type {CustomTheme} from "../../../managers/ThemeManager";
3
+import BaseShape from './BaseShape';
4
+import type {CustomThemeType} from '../../../managers/ThemeManager';
5
+import type {ShapeType} from './BaseShape';
5 6
 
6 7
 export default class ShapeZ extends BaseShape {
8
+  constructor(theme: CustomThemeType) {
9
+    super(theme);
10
+    this.position.x = 3;
11
+  }
7 12
 
8
-    constructor(theme: CustomTheme) {
9
-        super(theme);
10
-        this.position.x = 3;
11
-    }
13
+  getColor(): string {
14
+    return this.theme.colors.tetrisZ;
15
+  }
12 16
 
13
-    getColor(): string {
14
-        return this.theme.colors.tetrisZ;
15
-    }
16
-
17
-    getShapes() {
18
-        return [
19
-            [
20
-                [1, 1, 0],
21
-                [0, 1, 1],
22
-                [0, 0, 0],
23
-            ],
24
-            [
25
-                [0, 0, 1],
26
-                [0, 1, 1],
27
-                [0, 1, 0],
28
-            ],
29
-            [
30
-                [0, 0, 0],
31
-                [1, 1, 0],
32
-                [0, 1, 1],
33
-            ],
34
-            [
35
-                [0, 1, 0],
36
-                [1, 1, 0],
37
-                [1, 0, 0],
38
-            ],
39
-        ];
40
-    }
17
+  // eslint-disable-next-line class-methods-use-this
18
+  getShapes(): Array<ShapeType> {
19
+    return [
20
+      [
21
+        [1, 1, 0],
22
+        [0, 1, 1],
23
+        [0, 0, 0],
24
+      ],
25
+      [
26
+        [0, 0, 1],
27
+        [0, 1, 1],
28
+        [0, 1, 0],
29
+      ],
30
+      [
31
+        [0, 0, 0],
32
+        [1, 1, 0],
33
+        [0, 1, 1],
34
+      ],
35
+      [
36
+        [0, 1, 0],
37
+        [1, 1, 0],
38
+        [1, 0, 0],
39
+      ],
40
+    ];
41
+  }
41 42
 }

+ 23
- 27
src/screens/Game/components/CellComponent.js View File

@@ -3,34 +3,30 @@
3 3
 import * as React from 'react';
4 4
 import {View} from 'react-native';
5 5
 import {withTheme} from 'react-native-paper';
6
-import type {CustomTheme} from "../../../managers/ThemeManager";
7
-
8
-export type Cell = {color: string, isEmpty: boolean, key: string};
9
-
10
-type Props = {
11
-    cell: Cell,
12
-    theme: CustomTheme,
13
-}
14
-
15
-class CellComponent extends React.PureComponent<Props> {
16
-
17
-    render() {
18
-        const item = this.props.cell;
19
-        return (
20
-            <View
21
-                style={{
22
-                    flex: 1,
23
-                    backgroundColor: item.isEmpty ? 'transparent' : item.color,
24
-                    borderColor: 'transparent',
25
-                    borderRadius: 4,
26
-                    borderWidth: 1,
27
-                    aspectRatio: 1,
28
-                }}
29
-            />
30
-        );
31
-    }
32
-
33 6
 
7
+export type CellType = {color: string, isEmpty: boolean, key: string};
8
+
9
+type PropsType = {
10
+  cell: CellType,
11
+};
12
+
13
+class CellComponent extends React.PureComponent<PropsType> {
14
+  render(): React.Node {
15
+    const {props} = this;
16
+    const item = props.cell;
17
+    return (
18
+      <View
19
+        style={{
20
+          flex: 1,
21
+          backgroundColor: item.isEmpty ? 'transparent' : item.color,
22
+          borderColor: 'transparent',
23
+          borderRadius: 4,
24
+          borderWidth: 1,
25
+          aspectRatio: 1,
26
+        }}
27
+      />
28
+    );
29
+  }
34 30
 }
35 31
 
36 32
 export default withTheme(CellComponent);

+ 48
- 49
src/screens/Game/components/GridComponent.js View File

@@ -3,56 +3,55 @@
3 3
 import * as React from 'react';
4 4
 import {View} from 'react-native';
5 5
 import {withTheme} from 'react-native-paper';
6
-import type {Cell} from "./CellComponent";
7
-import CellComponent from "./CellComponent";
8
-import type {ViewStyle} from "react-native/Libraries/StyleSheet/StyleSheet";
9
-
10
-export type Grid = Array<Array<CellComponent>>;
11
-
12
-type Props = {
13
-    grid: Array<Array<Object>>,
14
-    height: number,
15
-    width: number,
16
-    style: ViewStyle,
17
-}
18
-
19
-class GridComponent extends React.Component<Props> {
20
-
21
-    getRow(rowNumber: number) {
22
-        let cells = this.props.grid[rowNumber].map(this.getCellRender);
23
-        return (
24
-            <View
25
-                style={{flexDirection: 'row',}}
26
-                key={rowNumber.toString()}
27
-            >
28
-                {cells}
29
-            </View>
30
-        );
31
-    }
32
-
33
-    getCellRender = (item: Cell) => {
34
-        return <CellComponent cell={item} key={item.key}/>;
35
-    };
36
-
37
-    getGrid() {
38
-        let rows = [];
39
-        for (let i = 0; i < this.props.height; i++) {
40
-            rows.push(this.getRow(i));
41
-        }
42
-        return rows;
43
-    }
44
-
45
-    render() {
46
-        return (
47
-            <View style={{
48
-                aspectRatio: this.props.width / this.props.height,
49
-                borderRadius: 4,
50
-                ...this.props.style
51
-            }}>
52
-                {this.getGrid()}
53
-            </View>
54
-        );
6
+import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet';
7
+import type {CellType} from './CellComponent';
8
+import CellComponent from './CellComponent';
9
+
10
+export type GridType = Array<Array<CellComponent>>;
11
+
12
+type PropsType = {
13
+  grid: Array<Array<CellType>>,
14
+  height: number,
15
+  width: number,
16
+  style: ViewStyle,
17
+};
18
+
19
+class GridComponent extends React.Component<PropsType> {
20
+  getRow(rowNumber: number): React.Node {
21
+    const {grid} = this.props;
22
+    return (
23
+      <View style={{flexDirection: 'row'}} key={rowNumber.toString()}>
24
+        {grid[rowNumber].map(this.getCellRender)}
25
+      </View>
26
+    );
27
+  }
28
+
29
+  getCellRender = (item: CellType): React.Node => {
30
+    return <CellComponent cell={item} key={item.key} />;
31
+  };
32
+
33
+  getGrid(): React.Node {
34
+    const {height} = this.props;
35
+    const rows = [];
36
+    for (let i = 0; i < height; i += 1) {
37
+      rows.push(this.getRow(i));
55 38
     }
39
+    return rows;
40
+  }
41
+
42
+  render(): React.Node {
43
+    const {style, width, height} = this.props;
44
+    return (
45
+      <View
46
+        style={{
47
+          aspectRatio: width / height,
48
+          borderRadius: 4,
49
+          ...style,
50
+        }}>
51
+        {this.getGrid()}
52
+      </View>
53
+    );
54
+  }
56 55
 }
57 56
 
58 57
 export default withTheme(GridComponent);

+ 41
- 44
src/screens/Game/components/Preview.js View File

@@ -3,51 +3,48 @@
3 3
 import * as React from 'react';
4 4
 import {View} from 'react-native';
5 5
 import {withTheme} from 'react-native-paper';
6
-import type {Grid} from "./GridComponent";
7
-import GridComponent from "./GridComponent";
8
-import type {ViewStyle} from "react-native/Libraries/StyleSheet/StyleSheet";
9
-
10
-type Props = {
11
-    items: Array<Grid>,
12
-    style: ViewStyle
13
-}
14
-
15
-class Preview extends React.PureComponent<Props> {
16
-
17
-    getGrids() {
18
-        let grids = [];
19
-        for (let i = 0; i < this.props.items.length; i++) {
20
-            grids.push(this.getGridRender(this.props.items[i], i));
21
-        }
22
-        return grids;
6
+import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet';
7
+import type {GridType} from './GridComponent';
8
+import GridComponent from './GridComponent';
9
+
10
+type PropsType = {
11
+  items: Array<GridType>,
12
+  style: ViewStyle,
13
+};
14
+
15
+class Preview extends React.PureComponent<PropsType> {
16
+  getGrids(): React.Node {
17
+    const {items} = this.props;
18
+    const grids = [];
19
+    items.forEach((item: GridType, index: number) => {
20
+      grids.push(Preview.getGridRender(item, index));
21
+    });
22
+    return grids;
23
+  }
24
+
25
+  static getGridRender(item: GridType, index: number): React.Node {
26
+    return (
27
+      <GridComponent
28
+        width={item[0].length}
29
+        height={item.length}
30
+        grid={item}
31
+        style={{
32
+          marginRight: 5,
33
+          marginLeft: 5,
34
+          marginBottom: 5,
35
+        }}
36
+        key={index.toString()}
37
+      />
38
+    );
39
+  }
40
+
41
+  render(): React.Node {
42
+    const {style, items} = this.props;
43
+    if (items.length > 0) {
44
+      return <View style={style}>{this.getGrids()}</View>;
23 45
     }
24
-
25
-    getGridRender(item: Grid, index: number) {
26
-        return <GridComponent
27
-            width={item[0].length}
28
-            height={item.length}
29
-            grid={item}
30
-            style={{
31
-                marginRight: 5,
32
-                marginLeft: 5,
33
-                marginBottom: 5,
34
-            }}
35
-            key={index.toString()}
36
-        />;
37
-    };
38
-
39
-    render() {
40
-        if (this.props.items.length > 0) {
41
-            return (
42
-                <View style={this.props.style}>
43
-                    {this.getGrids()}
44
-                </View>
45
-            );
46
-        } else
47
-            return null;
48
-    }
49
-
50
-
46
+    return null;
47
+  }
51 48
 }
52 49
 
53 50
 export default withTheme(Preview);

+ 284
- 209
src/screens/Game/logic/GameLogic.js View File

@@ -1,243 +1,318 @@
1 1
 // @flow
2 2
 
3
-import Piece from "./Piece";
4
-import ScoreManager from "./ScoreManager";
5
-import GridManager from "./GridManager";
6
-import type {CustomTheme} from "../../../managers/ThemeManager";
3
+import Piece from './Piece';
4
+import ScoreManager from './ScoreManager';
5
+import GridManager from './GridManager';
6
+import type {CustomThemeType} from '../../../managers/ThemeManager';
7
+import type {GridType} from '../components/GridComponent';
8
+
9
+export type TickCallbackType = (
10
+  score: number,
11
+  level: number,
12
+  grid: GridType,
13
+) => void;
14
+
15
+export type ClockCallbackType = (time: number) => void;
16
+
17
+export type EndCallbackType = (
18
+  time: number,
19
+  score: number,
20
+  isRestart: boolean,
21
+) => void;
22
+
23
+export type MovementCallbackType = (grid: GridType, score?: number) => void;
7 24
 
8 25
 export default class GameLogic {
26
+  static levelTicks = [1000, 800, 600, 400, 300, 200, 150, 100];
9 27
 
10
-    static levelTicks = [
11
-        1000,
12
-        800,
13
-        600,
14
-        400,
15
-        300,
16
-        200,
17
-        150,
18
-        100,
19
-    ];
20
-
21
-    #scoreManager: ScoreManager;
22
-    #gridManager: GridManager;
23
-
24
-    #height: number;
25
-    #width: number;
26
-
27
-    #gameRunning: boolean;
28
-    #gamePaused: boolean;
29
-    #gameTime: number;
30
-
31
-    #currentObject: Piece;
32
-
33
-    #gameTick: number;
34
-    #gameTickInterval: IntervalID;
35
-    #gameTimeInterval: IntervalID;
36
-
37
-    #pressInInterval: TimeoutID;
38
-    #isPressedIn: boolean;
39
-    #autoRepeatActivationDelay: number;
40
-    #autoRepeatDelay: number;
41
-
42
-    #nextPieces: Array<Piece>;
43
-    #nextPiecesCount: number;
44
-
45
-    #onTick: Function;
46
-    #onClock: Function;
47
-    endCallback: Function;
48
-
49
-    #theme: CustomTheme;
50
-
51
-    constructor(height: number, width: number, theme: CustomTheme) {
52
-        this.#height = height;
53
-        this.#width = width;
54
-        this.#gameRunning = false;
55
-        this.#gamePaused = false;
56
-        this.#theme = theme;
57
-        this.#autoRepeatActivationDelay = 300;
58
-        this.#autoRepeatDelay = 50;
59
-        this.#nextPieces = [];
60
-        this.#nextPiecesCount = 3;
61
-        this.#scoreManager = new ScoreManager();
62
-        this.#gridManager = new GridManager(this.getWidth(), this.getHeight(), this.#theme);
63
-    }
28
+  scoreManager: ScoreManager;
64 29
 
65
-    getHeight(): number {
66
-        return this.#height;
67
-    }
30
+  gridManager: GridManager;
68 31
 
69
-    getWidth(): number {
70
-        return this.#width;
71
-    }
32
+  height: number;
72 33
 
73
-    getCurrentGrid() {
74
-        return this.#gridManager.getCurrentGrid();
75
-    }
34
+  width: number;
76 35
 
77
-    isGameRunning(): boolean {
78
-        return this.#gameRunning;
79
-    }
36
+  gameRunning: boolean;
80 37
 
81
-    isGamePaused(): boolean {
82
-        return this.#gamePaused;
83
-    }
38
+  gamePaused: boolean;
84 39
 
85
-    onFreeze() {
86
-        this.#gridManager.freezeTetromino(this.#currentObject, this.#scoreManager);
87
-        this.createTetromino();
88
-    }
40
+  gameTime: number;
89 41
 
90
-    setNewGameTick(level: number) {
91
-        if (level >= GameLogic.levelTicks.length)
92
-            return;
93
-        this.#gameTick = GameLogic.levelTicks[level];
94
-        clearInterval(this.#gameTickInterval);
95
-        this.#gameTickInterval = setInterval(this.#onTick, this.#gameTick);
96
-    }
42
+  currentObject: Piece;
97 43
 
98
-    onTick(callback: Function) {
99
-        this.#currentObject.tryMove(0, 1,
100
-            this.#gridManager.getCurrentGrid(), this.getWidth(), this.getHeight(),
101
-            () => this.onFreeze());
102
-        callback(
103
-            this.#scoreManager.getScore(),
104
-            this.#scoreManager.getLevel(),
105
-            this.#gridManager.getCurrentGrid());
106
-        if (this.#scoreManager.canLevelUp())
107
-            this.setNewGameTick(this.#scoreManager.getLevel());
108
-    }
44
+  gameTick: number;
109 45
 
110
-    onClock(callback: Function) {
111
-        this.#gameTime++;
112
-        callback(this.#gameTime);
113
-    }
46
+  gameTickInterval: IntervalID;
114 47
 
115
-    canUseInput() {
116
-        return this.#gameRunning && !this.#gamePaused
117
-    }
48
+  gameTimeInterval: IntervalID;
118 49
 
119
-    rightPressed(callback: Function) {
120
-        this.#isPressedIn = true;
121
-        this.movePressedRepeat(true, callback, 1, 0);
122
-    }
50
+  pressInInterval: TimeoutID;
123 51
 
124
-    leftPressedIn(callback: Function) {
125
-        this.#isPressedIn = true;
126
-        this.movePressedRepeat(true, callback, -1, 0);
127
-    }
52
+  isPressedIn: boolean;
128 53
 
129
-    downPressedIn(callback: Function) {
130
-        this.#isPressedIn = true;
131
-        this.movePressedRepeat(true, callback, 0, 1);
132
-    }
54
+  autoRepeatActivationDelay: number;
133 55
 
134
-    movePressedRepeat(isInitial: boolean, callback: Function, x: number, y: number) {
135
-        if (!this.canUseInput() || !this.#isPressedIn)
136
-            return;
137
-        const moved = this.#currentObject.tryMove(x, y,
138
-            this.#gridManager.getCurrentGrid(), this.getWidth(), this.getHeight(),
139
-            () => this.onFreeze());
140
-        if (moved) {
141
-            if (y === 1) {
142
-                this.#scoreManager.incrementScore();
143
-                callback(this.#gridManager.getCurrentGrid(), this.#scoreManager.getScore());
144
-            } else
145
-                callback(this.#gridManager.getCurrentGrid());
146
-        }
147
-        this.#pressInInterval = setTimeout(() =>
148
-                this.movePressedRepeat(false, callback, x, y),
149
-            isInitial ? this.#autoRepeatActivationDelay : this.#autoRepeatDelay
150
-        );
151
-    }
56
+  autoRepeatDelay: number;
152 57
 
153
-    pressedOut() {
154
-        this.#isPressedIn = false;
155
-        clearTimeout(this.#pressInInterval);
156
-    }
58
+  nextPieces: Array<Piece>;
157 59
 
158
-    rotatePressed(callback: Function) {
159
-        if (!this.canUseInput())
160
-            return;
60
+  nextPiecesCount: number;
161 61
 
162
-        if (this.#currentObject.tryRotate(this.#gridManager.getCurrentGrid(), this.getWidth(), this.getHeight()))
163
-            callback(this.#gridManager.getCurrentGrid());
164
-    }
62
+  tickCallback: TickCallbackType;
165 63
 
166
-    getNextPiecesPreviews() {
167
-        let finalArray = [];
168
-        for (let i = 0; i < this.#nextPieces.length; i++) {
169
-            const gridSize = this.#nextPieces[i].getCurrentShape().getCurrentShape()[0].length;
170
-            finalArray.push(this.#gridManager.getEmptyGrid(gridSize, gridSize));
171
-            this.#nextPieces[i].toGrid(finalArray[i], true);
172
-        }
64
+  clockCallback: ClockCallbackType;
173 65
 
174
-        return finalArray;
175
-    }
66
+  endCallback: EndCallbackType;
176 67
 
177
-    recoverNextPiece() {
178
-        this.#currentObject = this.#nextPieces.shift();
179
-        this.generateNextPieces();
180
-    }
68
+  theme: CustomThemeType;
181 69
 
182
-    generateNextPieces() {
183
-        while (this.#nextPieces.length < this.#nextPiecesCount) {
184
-            this.#nextPieces.push(new Piece(this.#theme));
185
-        }
186
-    }
70
+  constructor(height: number, width: number, theme: CustomThemeType) {
71
+    this.height = height;
72
+    this.width = width;
73
+    this.gameRunning = false;
74
+    this.gamePaused = false;
75
+    this.theme = theme;
76
+    this.autoRepeatActivationDelay = 300;
77
+    this.autoRepeatDelay = 50;
78
+    this.nextPieces = [];
79
+    this.nextPiecesCount = 3;
80
+    this.scoreManager = new ScoreManager();
81
+    this.gridManager = new GridManager(
82
+      this.getWidth(),
83
+      this.getHeight(),
84
+      this.theme,
85
+    );
86
+  }
187 87
 
188
-    createTetromino() {
189
-        this.pressedOut();
190
-        this.recoverNextPiece();
191
-        if (!this.#currentObject.isPositionValid(this.#gridManager.getCurrentGrid(), this.getWidth(), this.getHeight()))
192
-            this.endGame(false);
193
-    }
88
+  getHeight(): number {
89
+    return this.height;
90
+  }
194 91
 
195
-    togglePause() {
196
-        if (!this.#gameRunning)
197
-            return;
198
-        this.#gamePaused = !this.#gamePaused;
199
-        if (this.#gamePaused) {
200
-            clearInterval(this.#gameTickInterval);
201
-            clearInterval(this.#gameTimeInterval);
202
-        } else {
203
-            this.#gameTickInterval = setInterval(this.#onTick, this.#gameTick);
204
-            this.#gameTimeInterval = setInterval(this.#onClock, 1000);
205
-        }
92
+  getWidth(): number {
93
+    return this.width;
94
+  }
95
+
96
+  getCurrentGrid(): GridType {
97
+    return this.gridManager.getCurrentGrid();
98
+  }
99
+
100
+  isGamePaused(): boolean {
101
+    return this.gamePaused;
102
+  }
103
+
104
+  onFreeze = () => {
105
+    this.gridManager.freezeTetromino(this.currentObject, this.scoreManager);
106
+    this.createTetromino();
107
+  };
108
+
109
+  setNewGameTick(level: number) {
110
+    if (level >= GameLogic.levelTicks.length) return;
111
+    this.gameTick = GameLogic.levelTicks[level];
112
+    this.stopTick();
113
+    this.startTick();
114
+  }
115
+
116
+  startClock() {
117
+    this.gameTimeInterval = setInterval(() => {
118
+      this.onClock(this.clockCallback);
119
+    }, 1000);
120
+  }
121
+
122
+  startTick() {
123
+    this.gameTickInterval = setInterval(() => {
124
+      this.onTick(this.tickCallback);
125
+    }, this.gameTick);
126
+  }
127
+
128
+  stopClock() {
129
+    clearInterval(this.gameTimeInterval);
130
+  }
131
+
132
+  stopTick() {
133
+    clearInterval(this.gameTickInterval);
134
+  }
135
+
136
+  stopGameTime() {
137
+    this.stopClock();
138
+    this.stopTick();
139
+  }
140
+
141
+  startGameTime() {
142
+    this.startClock();
143
+    this.startTick();
144
+  }
145
+
146
+  onTick(callback: TickCallbackType) {
147
+    this.currentObject.tryMove(
148
+      0,
149
+      1,
150
+      this.gridManager.getCurrentGrid(),
151
+      this.getWidth(),
152
+      this.getHeight(),
153
+      this.onFreeze,
154
+    );
155
+    callback(
156
+      this.scoreManager.getScore(),
157
+      this.scoreManager.getLevel(),
158
+      this.gridManager.getCurrentGrid(),
159
+    );
160
+    if (this.scoreManager.canLevelUp())
161
+      this.setNewGameTick(this.scoreManager.getLevel());
162
+  }
163
+
164
+  onClock(callback: ClockCallbackType) {
165
+    this.gameTime += 1;
166
+    callback(this.gameTime);
167
+  }
168
+
169
+  canUseInput(): boolean {
170
+    return this.gameRunning && !this.gamePaused;
171
+  }
172
+
173
+  rightPressed(callback: MovementCallbackType) {
174
+    this.isPressedIn = true;
175
+    this.movePressedRepeat(true, callback, 1, 0);
176
+  }
177
+
178
+  leftPressedIn(callback: MovementCallbackType) {
179
+    this.isPressedIn = true;
180
+    this.movePressedRepeat(true, callback, -1, 0);
181
+  }
182
+
183
+  downPressedIn(callback: MovementCallbackType) {
184
+    this.isPressedIn = true;
185
+    this.movePressedRepeat(true, callback, 0, 1);
186
+  }
187
+
188
+  movePressedRepeat(
189
+    isInitial: boolean,
190
+    callback: MovementCallbackType,
191
+    x: number,
192
+    y: number,
193
+  ) {
194
+    if (!this.canUseInput() || !this.isPressedIn) return;
195
+    const moved = this.currentObject.tryMove(
196
+      x,
197
+      y,
198
+      this.gridManager.getCurrentGrid(),
199
+      this.getWidth(),
200
+      this.getHeight(),
201
+      this.onFreeze,
202
+    );
203
+    if (moved) {
204
+      if (y === 1) {
205
+        this.scoreManager.incrementScore();
206
+        callback(
207
+          this.gridManager.getCurrentGrid(),
208
+          this.scoreManager.getScore(),
209
+        );
210
+      } else callback(this.gridManager.getCurrentGrid());
206 211
     }
207
-
208
-    stopGame() {
209
-        this.#gameRunning = false;
210
-        this.#gamePaused = false;
211
-        clearInterval(this.#gameTickInterval);
212
-        clearInterval(this.#gameTimeInterval);
212
+    this.pressInInterval = setTimeout(
213
+      () => {
214
+        this.movePressedRepeat(false, callback, x, y);
215
+      },
216
+      isInitial ? this.autoRepeatActivationDelay : this.autoRepeatDelay,
217
+    );
218
+  }
219
+
220
+  pressedOut() {
221
+    this.isPressedIn = false;
222
+    clearTimeout(this.pressInInterval);
223
+  }
224
+
225
+  rotatePressed(callback: MovementCallbackType) {
226
+    if (!this.canUseInput()) return;
227
+
228
+    if (
229
+      this.currentObject.tryRotate(
230
+        this.gridManager.getCurrentGrid(),
231
+        this.getWidth(),
232
+        this.getHeight(),
233
+      )
234
+    )
235
+      callback(this.gridManager.getCurrentGrid());
236
+  }
237
+
238
+  getNextPiecesPreviews(): Array<GridType> {
239
+    const finalArray = [];
240
+    for (let i = 0; i < this.nextPieces.length; i += 1) {
241
+      const gridSize = this.nextPieces[i].getCurrentShape().getCurrentShape()[0]
242
+        .length;
243
+      finalArray.push(this.gridManager.getEmptyGrid(gridSize, gridSize));
244
+      this.nextPieces[i].toGrid(finalArray[i], true);
213 245
     }
246
+    return finalArray;
247
+  }
214 248
 
215
-    endGame(isRestart: boolean) {
216
-        this.stopGame();
217
-        this.endCallback(this.#gameTime, this.#scoreManager.getScore(), isRestart);
218
-    }
249
+  recoverNextPiece() {
250
+    this.currentObject = this.nextPieces.shift();
251
+    this.generateNextPieces();
252
+  }
219 253
 
220
-    startGame(tickCallback: Function, clockCallback: Function, endCallback: Function) {
221
-        if (this.#gameRunning)
222
-            this.endGame(true);
223
-        this.#gameRunning = true;
224
-        this.#gamePaused = false;
225
-        this.#gameTime = 0;
226
-        this.#scoreManager = new ScoreManager();
227
-        this.#gameTick = GameLogic.levelTicks[this.#scoreManager.getLevel()];
228
-        this.#gridManager = new GridManager(this.getWidth(), this.getHeight(), this.#theme);
229
-        this.#nextPieces = [];
230
-        this.generateNextPieces();
231
-        this.createTetromino();
232
-        tickCallback(
233
-            this.#scoreManager.getScore(),
234
-            this.#scoreManager.getLevel(),
235
-            this.#gridManager.getCurrentGrid());
236
-        clockCallback(this.#gameTime);
237
-        this.#onTick = this.onTick.bind(this, tickCallback);
238
-        this.#onClock = this.onClock.bind(this, clockCallback);
239
-        this.#gameTickInterval = setInterval(this.#onTick, this.#gameTick);
240
-        this.#gameTimeInterval = setInterval(this.#onClock, 1000);
241
-        this.endCallback = endCallback;
254
+  generateNextPieces() {
255
+    while (this.nextPieces.length < this.nextPiecesCount) {
256
+      this.nextPieces.push(new Piece(this.theme));
242 257
     }
258
+  }
259
+
260
+  createTetromino() {
261
+    this.pressedOut();
262
+    this.recoverNextPiece();
263
+    if (
264
+      !this.currentObject.isPositionValid(
265
+        this.gridManager.getCurrentGrid(),
266
+        this.getWidth(),
267
+        this.getHeight(),
268
+      )
269
+    )
270
+      this.endGame(false);
271
+  }
272
+
273
+  togglePause() {
274
+    if (!this.gameRunning) return;
275
+    this.gamePaused = !this.gamePaused;
276
+    if (this.gamePaused) this.stopGameTime();
277
+    else this.startGameTime();
278
+  }
279
+
280
+  endGame(isRestart: boolean) {
281
+    this.gameRunning = false;
282
+    this.gamePaused = false;
283
+    this.stopGameTime();
284
+    this.endCallback(this.gameTime, this.scoreManager.getScore(), isRestart);
285
+  }
286
+
287
+  startGame(
288
+    tickCallback: TickCallbackType,
289
+    clockCallback: ClockCallbackType,
290
+    endCallback: EndCallbackType,
291
+  ) {
292
+    if (this.gameRunning) this.endGame(true);
293
+    this.gameRunning = true;
294
+    this.gamePaused = false;
295
+    this.gameTime = 0;
296
+    this.scoreManager = new ScoreManager();
297
+    this.gameTick = GameLogic.levelTicks[this.scoreManager.getLevel()];
298
+    this.gridManager = new GridManager(
299
+      this.getWidth(),
300
+      this.getHeight(),
301
+      this.theme,
302
+    );
303
+    this.nextPieces = [];
304
+    this.generateNextPieces();
305
+    this.createTetromino();
306
+    tickCallback(
307
+      this.scoreManager.getScore(),
308
+      this.scoreManager.getLevel(),
309
+      this.gridManager.getCurrentGrid(),
310
+    );
311
+    clockCallback(this.gameTime);
312
+    this.startTick();
313
+    this.startClock();
314
+    this.tickCallback = tickCallback;
315
+    this.clockCallback = clockCallback;
316
+    this.endCallback = endCallback;
317
+  }
243 318
 }

+ 101
- 99
src/screens/Game/logic/GridManager.js View File

@@ -1,120 +1,122 @@
1 1
 // @flow
2 2
 
3
-import Piece from "./Piece";
4
-import ScoreManager from "./ScoreManager";
5
-import type {Coordinates} from '../Shapes/BaseShape';
6
-import type {Grid} from "../components/GridComponent";
7
-import type {Cell} from "../components/CellComponent";
8
-import type {CustomTheme} from "../../../managers/ThemeManager";
3
+import Piece from './Piece';
4
+import ScoreManager from './ScoreManager';
5
+import type {CoordinatesType} from '../Shapes/BaseShape';
6
+import type {GridType} from '../components/GridComponent';
7
+import type {CellType} from '../components/CellComponent';
8
+import type {CustomThemeType} from '../../../managers/ThemeManager';
9 9
 
10 10
 /**
11 11
  * Class used to manage the game grid
12 12
  */
13 13
 export default class GridManager {
14
+  #currentGrid: GridType;
14 15
 
15
-    #currentGrid: Grid;
16
-    #theme: CustomTheme;
16
+  #theme: CustomThemeType;
17 17
 
18
-    /**
19
-     * Initializes a grid of the given size
20
-     *
21
-     * @param width The grid width
22
-     * @param height The grid height
23
-     * @param theme Object containing current theme
24
-     */
25
-    constructor(width: number, height: number, theme: CustomTheme) {
26
-        this.#theme = theme;
27
-        this.#currentGrid = this.getEmptyGrid(height, width);
28
-    }
18
+  /**
19
+   * Initializes a grid of the given size
20
+   *
21
+   * @param width The grid width
22
+   * @param height The grid height
23
+   * @param theme Object containing current theme
24
+   */
25
+  constructor(width: number, height: number, theme: CustomThemeType) {
26
+    this.#theme = theme;
27
+    this.#currentGrid = this.getEmptyGrid(height, width);
28
+  }
29 29
 
30
-    /**
31
-     * Get the current grid
32
-     *
33
-     * @return {Grid} The current grid
34
-     */
35
-    getCurrentGrid(): Grid {
36
-        return this.#currentGrid;
37
-    }
30
+  /**
31
+   * Get the current grid
32
+   *
33
+   * @return {GridType} The current grid
34
+   */
35
+  getCurrentGrid(): GridType {
36
+    return this.#currentGrid;
37
+  }
38 38
 
39
-    /**
40
-     * Get a new empty grid line of the given size
41
-     *
42
-     * @param width The line size
43
-     * @return {Array<Cell>}
44
-     */
45
-    getEmptyLine(width: number): Array<Cell> {
46
-        let line = [];
47
-        for (let col = 0; col < width; col++) {
48
-            line.push({
49
-                color: this.#theme.colors.tetrisBackground,
50
-                isEmpty: true,
51
-                key: col.toString(),
52
-            });
53
-        }
54
-        return line;
39
+  /**
40
+   * Get a new empty grid line of the given size
41
+   *
42
+   * @param width The line size
43
+   * @return {Array<CellType>}
44
+   */
45
+  getEmptyLine(width: number): Array<CellType> {
46
+    const line = [];
47
+    for (let col = 0; col < width; col += 1) {
48
+      line.push({
49
+        color: this.#theme.colors.tetrisBackground,
50
+        isEmpty: true,
51
+        key: col.toString(),
52
+      });
55 53
     }
54
+    return line;
55
+  }
56 56
 
57
-    /**
58
-     * Gets a new empty grid
59
-     *
60
-     * @param width The grid width
61
-     * @param height The grid height
62
-     * @return {Grid} A new empty grid
63
-     */
64
-    getEmptyGrid(height: number, width: number): Grid {
65
-        let grid = [];
66
-        for (let row = 0; row < height; row++) {
67
-            grid.push(this.getEmptyLine(width));
68
-        }
69
-        return grid;
57
+  /**
58
+   * Gets a new empty grid
59
+   *
60
+   * @param width The grid width
61
+   * @param height The grid height
62
+   * @return {GridType} A new empty grid
63
+   */
64
+  getEmptyGrid(height: number, width: number): GridType {
65
+    const grid = [];
66
+    for (let row = 0; row < height; row += 1) {
67
+      grid.push(this.getEmptyLine(width));
70 68
     }
69
+    return grid;
70
+  }
71 71
 
72
-    /**
73
-     * Removes the given lines from the grid,
74
-     * shifts down every line on top and adds new empty lines on top.
75
-     *
76
-     * @param lines An array of line numbers to remove
77
-     * @param scoreManager A reference to the score manager
78
-     */
79
-    clearLines(lines: Array<number>, scoreManager: ScoreManager) {
80
-        lines.sort();
81
-        for (let i = 0; i < lines.length; i++) {
82
-            this.#currentGrid.splice(lines[i], 1);
83
-            this.#currentGrid.unshift(this.getEmptyLine(this.#currentGrid[0].length));
84
-        }
85
-        scoreManager.addLinesRemovedPoints(lines.length);
72
+  /**
73
+   * Removes the given lines from the grid,
74
+   * shifts down every line on top and adds new empty lines on top.
75
+   *
76
+   * @param lines An array of line numbers to remove
77
+   * @param scoreManager A reference to the score manager
78
+   */
79
+  clearLines(lines: Array<number>, scoreManager: ScoreManager) {
80
+    lines.sort();
81
+    for (let i = 0; i < lines.length; i += 1) {
82
+      this.#currentGrid.splice(lines[i], 1);
83
+      this.#currentGrid.unshift(this.getEmptyLine(this.#currentGrid[0].length));
86 84
     }
85
+    scoreManager.addLinesRemovedPoints(lines.length);
86
+  }
87 87
 
88
-    /**
89
-     * Gets the lines to clear around the given piece's coordinates.
90
-     * The piece's coordinates are used for optimization and to prevent checking the whole grid.
91
-     *
92
-     * @param pos The piece's coordinates to check lines at
93
-     * @return {Array<number>} An array containing the line numbers to clear
94
-     */
95
-    getLinesToClear(pos: Array<Coordinates>): Array<number> {
96
-        let rows = [];
97
-        for (let i = 0; i < pos.length; i++) {
98
-            let isLineFull = true;
99
-            for (let col = 0; col < this.#currentGrid[pos[i].y].length; col++) {
100
-                if (this.#currentGrid[pos[i].y][col].isEmpty) {
101
-                    isLineFull = false;
102
-                    break;
103
-                }
104
-            }
105
-            if (isLineFull && rows.indexOf(pos[i].y) === -1)
106
-                rows.push(pos[i].y);
88
+  /**
89
+   * Gets the lines to clear around the given piece's coordinates.
90
+   * The piece's coordinates are used for optimization and to prevent checking the whole grid.
91
+   *
92
+   * @param pos The piece's coordinates to check lines at
93
+   * @return {Array<number>} An array containing the line numbers to clear
94
+   */
95
+  getLinesToClear(pos: Array<CoordinatesType>): Array<number> {
96
+    const rows = [];
97
+    for (let i = 0; i < pos.length; i += 1) {
98
+      let isLineFull = true;
99
+      for (let col = 0; col < this.#currentGrid[pos[i].y].length; col += 1) {
100
+        if (this.#currentGrid[pos[i].y][col].isEmpty) {
101
+          isLineFull = false;
102
+          break;
107 103
         }
108
-        return rows;
104
+      }
105
+      if (isLineFull && rows.indexOf(pos[i].y) === -1) rows.push(pos[i].y);
109 106
     }
107
+    return rows;
108
+  }
110 109
 
111
-    /**
112
-     * Freezes the given piece to the grid
113
-     *
114
-     * @param currentObject The piece to freeze
115
-     * @param scoreManager A reference to the score manager
116
-     */
117
-    freezeTetromino(currentObject: Piece, scoreManager: ScoreManager) {
118
-        this.clearLines(this.getLinesToClear(currentObject.getCoordinates()), scoreManager);
119
-    }
110
+  /**
111
+   * Freezes the given piece to the grid
112
+   *
113
+   * @param currentObject The piece to freeze
114
+   * @param scoreManager A reference to the score manager
115
+   */
116
+  freezeTetromino(currentObject: Piece, scoreManager: ScoreManager) {
117
+    this.clearLines(
118
+      this.getLinesToClear(currentObject.getCoordinates()),
119
+      scoreManager,
120
+    );
121
+  }
120 122
 }

+ 85
- 83
src/screens/Game/logic/ScoreManager.js View File

@@ -4,98 +4,100 @@
4 4
  * Class used to manage game score
5 5
  */
6 6
 export default class ScoreManager {
7
+  #scoreLinesModifier = [40, 100, 300, 1200];
7 8
 
8
-    #scoreLinesModifier = [40, 100, 300, 1200];
9
+  #score: number;
9 10
 
10
-    #score: number;
11
-    #level: number;
12
-    #levelProgression: number;
11
+  #level: number;
13 12
 
14
-    /**
15
-     * Initializes score to 0
16
-     */
17
-    constructor() {
18
-        this.#score = 0;
19
-        this.#level = 0;
20
-        this.#levelProgression = 0;
21
-    }
13
+  #levelProgression: number;
22 14
 
23
-    /**
24
-     * Gets the current score
25
-     *
26
-     * @return {number} The current score
27
-     */
28
-    getScore(): number {
29
-        return this.#score;
30
-    }
15
+  /**
16
+   * Initializes score to 0
17
+   */
18
+  constructor() {
19
+    this.#score = 0;
20
+    this.#level = 0;
21
+    this.#levelProgression = 0;
22
+  }
31 23
 
32
-    /**
33
-     * Gets the current level
34
-     *
35
-     * @return {number} The current level
36
-     */
37
-    getLevel(): number {
38
-        return this.#level;
39
-    }
24
+  /**
25
+   * Gets the current score
26
+   *
27
+   * @return {number} The current score
28
+   */
29
+  getScore(): number {
30
+    return this.#score;
31
+  }
40 32
 
41
-    /**
42
-     * Gets the current level progression
43
-     *
44
-     * @return {number} The current level progression
45
-     */
46
-    getLevelProgression(): number {
47
-        return this.#levelProgression;
48
-    }
33
+  /**
34
+   * Gets the current level
35
+   *
36
+   * @return {number} The current level
37
+   */
38
+  getLevel(): number {
39
+    return this.#level;
40
+  }
49 41
 
50
-    /**
51
-     * Increments the score by one
52
-     */
53
-    incrementScore() {
54
-        this.#score++;
55
-    }
42
+  /**
43
+   * Gets the current level progression
44
+   *
45
+   * @return {number} The current level progression
46
+   */
47
+  getLevelProgression(): number {
48
+    return this.#levelProgression;
49
+  }
56 50
 
57
-    /**
58
-     * Add score corresponding to the number of lines removed at the same time.
59
-     * Also updates the level progression.
60
-     *
61
-     * The more lines cleared at the same time, the more points and level progression the player gets.
62
-     *
63
-     * @param numberRemoved The number of lines removed at the same time
64
-     */
65
-    addLinesRemovedPoints(numberRemoved: number) {
66
-        if (numberRemoved < 1 || numberRemoved > 4)
67
-            return;
68
-        this.#score += this.#scoreLinesModifier[numberRemoved-1] * (this.#level + 1);
69
-        switch (numberRemoved) {
70
-            case 1:
71
-                this.#levelProgression += 1;
72
-                break;
73
-            case 2:
74
-                this.#levelProgression += 3;
75
-                break;
76
-            case 3:
77
-                this.#levelProgression += 5;
78
-                break;
79
-            case 4: // Did a tetris !
80
-                this.#levelProgression += 8;
81
-                break;
82
-        }
83
-    }
51
+  /**
52
+   * Increments the score by one
53
+   */
54
+  incrementScore() {
55
+    this.#score += 1;
56
+  }
84 57
 
85
-    /**
86
-     * Checks if the player can go to the next level.
87
-     *
88
-     * If he can, change the level.
89
-     *
90
-     * @return {boolean} True if the current level has changed
91
-     */
92
-    canLevelUp() {
93
-        let canLevel = this.#levelProgression > this.#level * 5;
94
-        if (canLevel){
95
-            this.#levelProgression -= this.#level * 5;
96
-            this.#level++;
97
-        }
98
-        return canLevel;
58
+  /**
59
+   * Add score corresponding to the number of lines removed at the same time.
60
+   * Also updates the level progression.
61
+   *
62
+   * The more lines cleared at the same time, the more points and level progression the player gets.
63
+   *
64
+   * @param numberRemoved The number of lines removed at the same time
65
+   */
66
+  addLinesRemovedPoints(numberRemoved: number) {
67
+    if (numberRemoved < 1 || numberRemoved > 4) return;
68
+    this.#score +=
69
+      this.#scoreLinesModifier[numberRemoved - 1] * (this.#level + 1);
70
+    switch (numberRemoved) {
71
+      case 1:
72
+        this.#levelProgression += 1;
73
+        break;
74
+      case 2:
75
+        this.#levelProgression += 3;
76
+        break;
77
+      case 3:
78
+        this.#levelProgression += 5;
79
+        break;
80
+      case 4: // Did a tetris !
81
+        this.#levelProgression += 8;
82
+        break;
83
+      default:
84
+        break;
99 85
     }
86
+  }
100 87
 
88
+  /**
89
+   * Checks if the player can go to the next level.
90
+   *
91
+   * If he can, change the level.
92
+   *
93
+   * @return {boolean} True if the current level has changed
94
+   */
95
+  canLevelUp(): boolean {
96
+    const canLevel = this.#levelProgression > this.#level * 5;
97
+    if (canLevel) {
98
+      this.#levelProgression -= this.#level * 5;
99
+      this.#level += 1;
100
+    }
101
+    return canLevel;
102
+  }
101 103
 }

+ 438
- 391
src/screens/Game/screens/GameMainScreen.js View File

@@ -3,400 +3,447 @@
3 3
 import * as React from 'react';
4 4
 import {View} from 'react-native';
5 5
 import {Caption, IconButton, Text, withTheme} from 'react-native-paper';
6
-import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
7
-import GameLogic from "../logic/GameLogic";
8
-import type {Grid} from "../components/GridComponent";
9
-import GridComponent from "../components/GridComponent";
10
-import Preview from "../components/Preview";
11
-import i18n from "i18n-js";
12
-import MaterialHeaderButtons, {Item} from "../../../components/Overrides/CustomHeaderButton";
13
-import {StackNavigationProp} from "@react-navigation/stack";
14
-import type {CustomTheme} from "../../../managers/ThemeManager";
15
-import type {OptionsDialogButton} from "../../../components/Dialogs/OptionsDialog";
16
-import OptionsDialog from "../../../components/Dialogs/OptionsDialog";
17
-
18
-type Props = {
19
-    navigation: StackNavigationProp,
20
-    route: { params: { highScore: number }, ... },
21
-    theme: CustomTheme,
22
-}
23
-
24
-type State = {
25
-    grid: Grid,
26
-    gameRunning: boolean,
27
-    gameTime: number,
28
-    gameScore: number,
29
-    gameLevel: number,
30
-
31
-    dialogVisible: boolean,
32
-    dialogTitle: string,
33
-    dialogMessage: string,
34
-    dialogButtons: Array<OptionsDialogButton>,
35
-    onDialogDismiss: () => void,
36
-}
37
-
38
-class GameMainScreen extends React.Component<Props, State> {
39
-
40
-    logic: GameLogic;
41
-    highScore: number | null;
42
-
43
-    constructor(props) {
44
-        super(props);
45
-        this.logic = new GameLogic(20, 10, this.props.theme);
46
-        this.state = {
47
-            grid: this.logic.getCurrentGrid(),
48
-            gameRunning: false,
49
-            gameTime: 0,
50
-            gameScore: 0,
51
-            gameLevel: 0,
52
-            dialogVisible: false,
53
-            dialogTitle: "",
54
-            dialogMessage: "",
55
-            dialogButtons: [],
56
-            onDialogDismiss: () => {
57
-            },
58
-        };
59
-        if (this.props.route.params != null)
60
-            this.highScore = this.props.route.params.highScore;
61
-    }
62
-
63
-    componentDidMount() {
64
-        this.props.navigation.setOptions({
65
-            headerRight: this.getRightButton,
66
-        });
67
-        this.startGame();
68
-    }
69
-
70
-    componentWillUnmount() {
71
-        this.logic.stopGame();
72
-    }
73
-
74
-    getRightButton = () => {
75
-        return <MaterialHeaderButtons>
76
-            <Item title="pause" iconName="pause" onPress={this.togglePause}/>
77
-        </MaterialHeaderButtons>;
78
-    }
79
-
80
-    getFormattedTime(seconds: number) {
81
-        let date = new Date();
82
-        date.setHours(0);
83
-        date.setMinutes(0);
84
-        date.setSeconds(seconds);
85
-        let format;
86
-        if (date.getHours())
87
-            format = date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds();
88
-        else if (date.getMinutes())
89
-            format = date.getMinutes() + ':' + date.getSeconds();
90
-        else
91
-            format = date.getSeconds();
92
-        return format;
93
-    }
94
-
95
-    onTick = (score: number, level: number, newGrid: Grid) => {
96
-        this.setState({
97
-            gameScore: score,
98
-            gameLevel: level,
99
-            grid: newGrid,
100
-        });
101
-    }
102
-
103
-    onClock = (time: number) => {
104
-        this.setState({
105
-            gameTime: time,
106
-        });
107
-    }
108
-
109
-    updateGrid = (newGrid: Grid) => {
110
-        this.setState({
111
-            grid: newGrid,
112
-        });
113
-    }
114
-
115
-    updateGridScore = (newGrid: Grid, score: number) => {
116
-        this.setState({
117
-            grid: newGrid,
118
-            gameScore: score,
119
-        });
120
-    }
121
-
122
-    togglePause = () => {
123
-        this.logic.togglePause();
124
-        if (this.logic.isGamePaused())
125
-            this.showPausePopup();
126
-    }
127
-
128
-    onDialogDismiss = () => this.setState({dialogVisible: false});
129
-
130
-    showPausePopup = () => {
131
-        const onDismiss = () => {
132
-            this.togglePause();
133
-            this.onDialogDismiss();
134
-        };
135
-        this.setState({
136
-            dialogVisible: true,
137
-            dialogTitle: i18n.t("screens.game.pause"),
138
-            dialogMessage: i18n.t("screens.game.pauseMessage"),
139
-            dialogButtons: [
140
-                {
141
-                    title: i18n.t("screens.game.restart.text"),
142
-                    onPress: this.showRestartConfirm
143
-                },
144
-                {
145
-                    title: i18n.t("screens.game.resume"),
146
-                    onPress: onDismiss
147
-                }
148
-            ],
149
-            onDialogDismiss: onDismiss,
150
-        });
151
-    }
152
-
153
-    showRestartConfirm = () => {
154
-        this.setState({
155
-            dialogVisible: true,
156
-            dialogTitle: i18n.t("screens.game.restart.confirm"),
157
-            dialogMessage: i18n.t("screens.game.restart.confirmMessage"),
158
-            dialogButtons: [
159
-                {
160
-                    title: i18n.t("screens.game.restart.confirmYes"),
161
-                    onPress: () => {
162
-                        this.onDialogDismiss();
163
-                        this.startGame();
164
-                    }
165
-                },
166
-                {
167
-                    title: i18n.t("screens.game.restart.confirmNo"),
168
-                    onPress: this.showPausePopup
169
-                }
170
-            ],
171
-            onDialogDismiss: this.showPausePopup,
172
-        });
173
-    }
174
-
175
-    startGame = () => {
176
-        this.logic.startGame(this.onTick, this.onClock, this.onGameEnd);
177
-        this.setState({
178
-            gameRunning: true,
179
-        });
180
-    }
181
-
182
-    onGameEnd = (time: number, score: number, isRestart: boolean) => {
183
-        this.setState({
184
-            gameTime: time,
185
-            gameScore: score,
186
-            gameRunning: false,
187
-        });
188
-        if (!isRestart)
189
-            this.props.navigation.replace(
190
-                "game-start",
191
-                {
192
-                    score: this.state.gameScore,
193
-                    level: this.state.gameLevel,
194
-                    time: this.state.gameTime,
195
-                }
196
-            );
197
-    }
198
-
199
-    getStatusIcons() {
200
-        return (
201
-            <View style={{
202
-                flex: 1,
203
-                marginTop: "auto",
204
-                marginBottom: "auto"
6
+import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
7
+import i18n from 'i18n-js';
8
+import {StackNavigationProp} from '@react-navigation/stack';
9
+import GameLogic from '../logic/GameLogic';
10
+import type {GridType} from '../components/GridComponent';
11
+import GridComponent from '../components/GridComponent';
12
+import Preview from '../components/Preview';
13
+import MaterialHeaderButtons, {
14
+  Item,
15
+} from '../../../components/Overrides/CustomHeaderButton';
16
+import type {CustomThemeType} from '../../../managers/ThemeManager';
17
+import type {OptionsDialogButtonType} from '../../../components/Dialogs/OptionsDialog';
18
+import OptionsDialog from '../../../components/Dialogs/OptionsDialog';
19
+
20
+type PropsType = {
21
+  navigation: StackNavigationProp,
22
+  route: {params: {highScore: number}},
23
+  theme: CustomThemeType,
24
+};
25
+
26
+type StateType = {
27
+  grid: GridType,
28
+  gameTime: number,
29
+  gameScore: number,
30
+  gameLevel: number,
31
+
32
+  dialogVisible: boolean,
33
+  dialogTitle: string,
34
+  dialogMessage: string,
35
+  dialogButtons: Array<OptionsDialogButtonType>,
36
+  onDialogDismiss: () => void,
37
+};
38
+
39
+class GameMainScreen extends React.Component<PropsType, StateType> {
40
+  static getFormattedTime(seconds: number): string {
41
+    const date = new Date();
42
+    date.setHours(0);
43
+    date.setMinutes(0);
44
+    date.setSeconds(seconds);
45
+    let format;
46
+    if (date.getHours())
47
+      format = `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
48
+    else if (date.getMinutes())
49
+      format = `${date.getMinutes()}:${date.getSeconds()}`;
50
+    else format = date.getSeconds().toString();
51
+    return format;
52
+  }
53
+
54
+  logic: GameLogic;
55
+
56
+  highScore: number | null;
57
+
58
+  constructor(props: PropsType) {
59
+    super(props);
60
+    this.logic = new GameLogic(20, 10, props.theme);
61
+    this.state = {
62
+      grid: this.logic.getCurrentGrid(),
63
+      gameTime: 0,
64
+      gameScore: 0,
65
+      gameLevel: 0,
66
+      dialogVisible: false,
67
+      dialogTitle: '',
68
+      dialogMessage: '',
69
+      dialogButtons: [],
70
+      onDialogDismiss: () => {},
71
+    };
72
+    if (props.route.params != null)
73
+      this.highScore = props.route.params.highScore;
74
+  }
75
+
76
+  componentDidMount() {
77
+    const {navigation} = this.props;
78
+    navigation.setOptions({
79
+      headerRight: this.getRightButton,
80
+    });
81
+    this.startGame();
82
+  }
83
+
84
+  componentWillUnmount() {
85
+    this.logic.endGame(false);
86
+  }
87
+
88
+  getRightButton = (): React.Node => {
89
+    return (
90
+      <MaterialHeaderButtons>
91
+        <Item title="pause" iconName="pause" onPress={this.togglePause} />
92
+      </MaterialHeaderButtons>
93
+    );
94
+  };
95
+
96
+  onTick = (score: number, level: number, newGrid: GridType) => {
97
+    this.setState({
98
+      gameScore: score,
99
+      gameLevel: level,
100
+      grid: newGrid,
101
+    });
102
+  };
103
+
104
+  onClock = (time: number) => {
105
+    this.setState({
106
+      gameTime: time,
107
+    });
108
+  };
109
+
110
+  onDialogDismiss = () => {
111
+    this.setState({dialogVisible: false});
112
+  };
113
+
114
+  onGameEnd = (time: number, score: number, isRestart: boolean) => {
115
+    const {props, state} = this;
116
+    this.setState({
117
+      gameTime: time,
118
+      gameScore: score,
119
+    });
120
+    if (!isRestart)
121
+      props.navigation.replace('game-start', {
122
+        score: state.gameScore,
123
+        level: state.gameLevel,
124
+        time: state.gameTime,
125
+      });
126
+  };
127
+
128
+  getStatusIcons(): React.Node {
129
+    const {props, state} = this;
130
+    return (
131
+      <View
132
+        style={{
133
+          flex: 1,
134
+          marginTop: 'auto',
135
+          marginBottom: 'auto',
136
+        }}>
137
+        <View
138
+          style={{
139
+            marginLeft: 'auto',
140
+            marginRight: 'auto',
141
+          }}>
142
+          <Caption
143
+            style={{
144
+              marginLeft: 'auto',
145
+              marginRight: 'auto',
146
+              marginBottom: 5,
205 147
             }}>
206
-                <View style={{
207
-                    marginLeft: 'auto',
208
-                    marginRight: 'auto',
209
-                }}>
210
-                    <Caption style={{
211
-                        marginLeft: "auto",
212
-                        marginRight: "auto",
213
-                        marginBottom: 5,
214
-                    }}>{i18n.t("screens.game.time")}</Caption>
215
-                    <View style={{
216
-                        flexDirection: "row"
217
-                    }}>
218
-                        <MaterialCommunityIcons
219
-                            name={'timer'}
220
-                            color={this.props.theme.colors.subtitle}
221
-                            size={20}/>
222
-                        <Text style={{
223
-                            marginLeft: 5,
224
-                            color: this.props.theme.colors.subtitle
225
-                        }}>{this.getFormattedTime(this.state.gameTime)}</Text>
226
-                    </View>
227
-
228
-                </View>
229
-                <View style={{
230
-                    marginLeft: 'auto',
231
-                    marginRight: 'auto',
232
-                    marginTop: 20,
233
-                }}>
234
-                    <Caption style={{
235
-                        marginLeft: "auto",
236
-                        marginRight: "auto",
237
-                        marginBottom: 5,
238
-                    }}>{i18n.t("screens.game.level")}</Caption>
239
-                    <View style={{
240
-                        flexDirection: "row"
241
-                    }}>
242
-                        <MaterialCommunityIcons
243
-                            name={'gamepad-square'}
244
-                            color={this.props.theme.colors.text}
245
-                            size={20}/>
246
-                        <Text style={{
247
-                            marginLeft: 5
248
-                        }}>{this.state.gameLevel}</Text>
249
-                    </View>
250
-                </View>
251
-            </View>
252
-        );
253
-    }
254
-
255
-    getScoreIcon() {
256
-        let highScore = this.highScore == null || this.state.gameScore > this.highScore
257
-            ? this.state.gameScore
258
-            : this.highScore;
259
-        return (
260
-            <View style={{
261
-                marginTop: 10,
262
-                marginBottom: 10,
148
+            {i18n.t('screens.game.time')}
149
+          </Caption>
150
+          <View
151
+            style={{
152
+              flexDirection: 'row',
263 153
             }}>
264
-                <View style={{
265
-                    flexDirection: "row",
266
-                    marginLeft: "auto",
267
-                    marginRight: "auto",
268
-                }}>
269
-                    <Text style={{
270
-                        marginLeft: 5,
271
-                        fontSize: 20,
272
-                    }}>{i18n.t("screens.game.score", {score: this.state.gameScore})}</Text>
273
-                    <MaterialCommunityIcons
274
-                        name={'star'}
275
-                        color={this.props.theme.colors.tetrisScore}
276
-                        size={20}
277
-                        style={{
278
-                            marginTop: "auto",
279
-                            marginBottom: "auto",
280
-                            marginLeft: 5
281
-                        }}/>
282
-                </View>
283
-                <View style={{
284
-                    flexDirection: "row",
285
-                    marginLeft: "auto",
286
-                    marginRight: "auto",
287
-                    marginTop: 5,
288
-                }}>
289
-                    <Text style={{
290
-                        marginLeft: 5,
291
-                        fontSize: 10,
292
-                        color: this.props.theme.colors.textDisabled
293
-                    }}>{i18n.t("screens.game.highScore", {score: highScore})}</Text>
294
-                    <MaterialCommunityIcons
295
-                        name={'star'}
296
-                        color={this.props.theme.colors.tetrisScore}
297
-                        size={10}
298
-                        style={{
299
-                            marginTop: "auto",
300
-                            marginBottom: "auto",
301
-                            marginLeft: 5
302
-                        }}/>
303
-                </View>
304
-            </View>
305
-
306
-        );
307
-    }
308
-
309
-    getControlButtons() {
310
-        return (
311
-            <View style={{
312
-                height: 80,
313
-                flexDirection: "row"
154
+            <MaterialCommunityIcons
155
+              name="timer"
156
+              color={props.theme.colors.subtitle}
157
+              size={20}
158
+            />
159
+            <Text
160
+              style={{
161
+                marginLeft: 5,
162
+                color: props.theme.colors.subtitle,
163
+              }}>
164
+              {GameMainScreen.getFormattedTime(state.gameTime)}
165
+            </Text>
166
+          </View>
167
+        </View>
168
+        <View
169
+          style={{
170
+            marginLeft: 'auto',
171
+            marginRight: 'auto',
172
+            marginTop: 20,
173
+          }}>
174
+          <Caption
175
+            style={{
176
+              marginLeft: 'auto',
177
+              marginRight: 'auto',
178
+              marginBottom: 5,
314 179
             }}>
315
-                <IconButton
316
-                    icon="rotate-right-variant"
317
-                    size={40}
318
-                    onPress={() => this.logic.rotatePressed(this.updateGrid)}
319
-                    style={{flex: 1}}
320
-                />
321
-                <View style={{
322
-                    flexDirection: 'row',
323
-                    flex: 4
324
-                }}>
325
-                    <IconButton
326
-                        icon="chevron-left"
327
-                        size={40}
328
-                        style={{flex: 1}}
329
-                        onPress={() => this.logic.pressedOut()}
330
-                        onPressIn={() => this.logic.leftPressedIn(this.updateGrid)}
331
-
332
-                    />
333
-                    <IconButton
334
-                        icon="chevron-right"
335
-                        size={40}
336
-                        style={{flex: 1}}
337
-                        onPress={() => this.logic.pressedOut()}
338
-                        onPressIn={() => this.logic.rightPressed(this.updateGrid)}
339
-                    />
340
-                </View>
341
-                <IconButton
342
-                    icon="arrow-down-bold"
343
-                    size={40}
344
-                    onPressIn={() => this.logic.downPressedIn(this.updateGridScore)}
345
-                    onPress={() => this.logic.pressedOut()}
346
-                    style={{flex: 1}}
347
-                    color={this.props.theme.colors.tetrisScore}
348
-                />
349
-            </View>
350
-        );
351
-    }
352
-
353
-    render() {
354
-        return (
355
-            <View style={{flex: 1}}>
356
-                <View style={{
357
-                    flex: 1,
358
-                    flexDirection: "row",
359
-                }}>
360
-                    {this.getStatusIcons()}
361
-                    <View style={{flex: 4}}>
362
-                        {this.getScoreIcon()}
363
-                        <GridComponent
364
-                            width={this.logic.getWidth()}
365
-                            height={this.logic.getHeight()}
366
-                            grid={this.state.grid}
367
-                            style={{
368
-                                backgroundColor: this.props.theme.colors.tetrisBackground,
369
-                                flex: 1,
370
-                                marginLeft: "auto",
371
-                                marginRight: "auto",
372
-                            }}
373
-                        />
374
-                    </View>
375
-
376
-                    <View style={{flex: 1}}>
377
-                        <Preview
378
-                            items={this.logic.getNextPiecesPreviews()}
379
-                            style={{
380
-                                marginLeft: 'auto',
381
-                                marginRight: 'auto',
382
-                                marginTop: 10,
383
-                            }}
384
-                        />
385
-                    </View>
386
-                </View>
387
-                {this.getControlButtons()}
388
-
389
-                <OptionsDialog
390
-                    visible={this.state.dialogVisible}
391
-                    title={this.state.dialogTitle}
392
-                    message={this.state.dialogMessage}
393
-                    buttons={this.state.dialogButtons}
394
-                    onDismiss={this.state.onDialogDismiss}
395
-                />
396
-            </View>
397
-        );
398
-    }
399
-
180
+            {i18n.t('screens.game.level')}
181
+          </Caption>
182
+          <View
183
+            style={{
184
+              flexDirection: 'row',
185
+            }}>
186
+            <MaterialCommunityIcons
187
+              name="gamepad-square"
188
+              color={props.theme.colors.text}
189
+              size={20}
190
+            />
191
+            <Text
192
+              style={{
193
+                marginLeft: 5,
194
+              }}>
195
+              {state.gameLevel}
196
+            </Text>
197
+          </View>
198
+        </View>
199
+      </View>
200
+    );
201
+  }
202
+
203
+  getScoreIcon(): React.Node {
204
+    const {props, state} = this;
205
+    const highScore =
206
+      this.highScore == null || state.gameScore > this.highScore
207
+        ? state.gameScore
208
+        : this.highScore;
209
+    return (
210
+      <View
211
+        style={{
212
+          marginTop: 10,
213
+          marginBottom: 10,
214
+        }}>
215
+        <View
216
+          style={{
217
+            flexDirection: 'row',
218
+            marginLeft: 'auto',
219
+            marginRight: 'auto',
220
+          }}>
221
+          <Text
222
+            style={{
223
+              marginLeft: 5,
224
+              fontSize: 20,
225
+            }}>
226
+            {i18n.t('screens.game.score', {score: state.gameScore})}
227
+          </Text>
228
+          <MaterialCommunityIcons
229
+            name="star"
230
+            color={props.theme.colors.tetrisScore}
231
+            size={20}
232
+            style={{
233
+              marginTop: 'auto',
234
+              marginBottom: 'auto',
235
+              marginLeft: 5,
236
+            }}
237
+          />
238
+        </View>
239
+        <View
240
+          style={{
241
+            flexDirection: 'row',
242
+            marginLeft: 'auto',
243
+            marginRight: 'auto',
244
+            marginTop: 5,
245
+          }}>
246
+          <Text
247
+            style={{
248
+              marginLeft: 5,
249
+              fontSize: 10,
250
+              color: props.theme.colors.textDisabled,
251
+            }}>
252
+            {i18n.t('screens.game.highScore', {score: highScore})}
253
+          </Text>
254
+          <MaterialCommunityIcons
255
+            name="star"
256
+            color={props.theme.colors.tetrisScore}
257
+            size={10}
258
+            style={{
259
+              marginTop: 'auto',
260
+              marginBottom: 'auto',
261
+              marginLeft: 5,
262
+            }}
263
+          />
264
+        </View>
265
+      </View>
266
+    );
267
+  }
268
+
269
+  getControlButtons(): React.Node {
270
+    const {props} = this;
271
+    return (
272
+      <View
273
+        style={{
274
+          height: 80,
275
+          flexDirection: 'row',
276
+        }}>
277
+        <IconButton
278
+          icon="rotate-right-variant"
279
+          size={40}
280
+          onPress={() => {
281
+            this.logic.rotatePressed(this.updateGrid);