Browse Source

Improve Game files to match linter

Arnaud Vergnet 3 years ago
parent
commit
cbe3777957

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

1
 // @flow
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
  * Abstract class used to represent a BaseShape.
13
  * Abstract class used to represent a BaseShape.
15
  * and in methods to implement
15
  * and in methods to implement
16
  */
16
  */
17
 export default class BaseShape {
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
 // @flow
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
 export default class ShapeI extends BaseShape {
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
 // @flow
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
 export default class ShapeJ extends BaseShape {
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
 // @flow
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
 export default class ShapeL extends BaseShape {
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
 // @flow
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
 export default class ShapeO extends BaseShape {
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
 // @flow
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
 export default class ShapeS extends BaseShape {
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
 // @flow
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
 export default class ShapeT extends BaseShape {
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
 // @flow
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
 export default class ShapeZ extends BaseShape {
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
 import * as React from 'react';
3
 import * as React from 'react';
4
 import {View} from 'react-native';
4
 import {View} from 'react-native';
5
 import {withTheme} from 'react-native-paper';
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
 export default withTheme(CellComponent);
32
 export default withTheme(CellComponent);

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

3
 import * as React from 'react';
3
 import * as React from 'react';
4
 import {View} from 'react-native';
4
 import {View} from 'react-native';
5
 import {withTheme} from 'react-native-paper';
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
 export default withTheme(GridComponent);
57
 export default withTheme(GridComponent);

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

3
 import * as React from 'react';
3
 import * as React from 'react';
4
 import {View} from 'react-native';
4
 import {View} from 'react-native';
5
 import {withTheme} from 'react-native-paper';
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
 export default withTheme(Preview);
50
 export default withTheme(Preview);

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

1
 // @flow
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
 export default class GameLogic {
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
 // @flow
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
  * Class used to manage the game grid
11
  * Class used to manage the game grid
12
  */
12
  */
13
 export default class GridManager {
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
  * Class used to manage game score
4
  * Class used to manage game score
5
  */
5
  */
6
 export default class ScoreManager {
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
 import * as React from 'react';
3
 import * as React from 'react';
4
 import {View} from 'react-native';
4
 import {View} from 'react-native';
5
 import {Caption, IconButton, Text, withTheme} from 'react-native-paper';
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);
282
+          }}
283
+          style={{flex: 1}}
284
+        />
285
+        <View
286
+          style={{
287
+            flexDirection: 'row',
288
+            flex: 4,
289
+          }}>
290
+          <IconButton
291
+            icon="chevron-left"
292
+            size={40}
293
+            style={{flex: 1}}
294
+            onPress={() => {
295
+              this.logic.pressedOut();
296
+            }}
297
+            onPressIn={() => {
298
+              this.logic.leftPressedIn(this.updateGrid);
299
+            }}
300
+          />
301
+          <IconButton
302
+            icon="chevron-right"
303
+            size={40}
304
+            style={{flex: 1}}
305
+            onPress={() => {
306
+              this.logic.pressedOut();
307
+            }}
308
+            onPressIn={() => {
309
+              this.logic.rightPressed(this.updateGrid);
310
+            }}
311
+          />
312
+        </View>
313
+        <IconButton
314
+          icon="arrow-down-bold"
315
+          size={40}
316
+          onPressIn={() => {
317
+            this.logic.downPressedIn(this.updateGridScore);
318
+          }}
319
+          onPress={() => {
320
+            this.logic.pressedOut();
321
+          }}
322
+          style={{flex: 1}}
323
+          color={props.theme.colors.tetrisScore}
324
+        />
325
+      </View>
326
+    );
327
+  }
328
+
329
+  updateGrid = (newGrid: GridType) => {
330
+    this.setState({
331
+      grid: newGrid,
332
+    });
333
+  };
334
+
335
+  updateGridScore = (newGrid: GridType, score?: number) => {
336
+    this.setState((prevState: StateType): {
337
+      grid: GridType,
338
+      gameScore: number,
339
+    } => ({
340
+      grid: newGrid,
341
+      gameScore: score != null ? score : prevState.gameScore,
342
+    }));
343
+  };
344
+
345
+  togglePause = () => {
346
+    this.logic.togglePause();
347
+    if (this.logic.isGamePaused()) this.showPausePopup();
348
+  };
349
+
350
+  showPausePopup = () => {
351
+    const onDismiss = () => {
352
+      this.togglePause();
353
+      this.onDialogDismiss();
354
+    };
355
+    this.setState({
356
+      dialogVisible: true,
357
+      dialogTitle: i18n.t('screens.game.pause'),
358
+      dialogMessage: i18n.t('screens.game.pauseMessage'),
359
+      dialogButtons: [
360
+        {
361
+          title: i18n.t('screens.game.restart.text'),
362
+          onPress: this.showRestartConfirm,
363
+        },
364
+        {
365
+          title: i18n.t('screens.game.resume'),
366
+          onPress: onDismiss,
367
+        },
368
+      ],
369
+      onDialogDismiss: onDismiss,
370
+    });
371
+  };
372
+
373
+  showRestartConfirm = () => {
374
+    this.setState({
375
+      dialogVisible: true,
376
+      dialogTitle: i18n.t('screens.game.restart.confirm'),
377
+      dialogMessage: i18n.t('screens.game.restart.confirmMessage'),
378
+      dialogButtons: [
379
+        {
380
+          title: i18n.t('screens.game.restart.confirmYes'),
381
+          onPress: () => {
382
+            this.onDialogDismiss();
383
+            this.startGame();
384
+          },
385
+        },
386
+        {
387
+          title: i18n.t('screens.game.restart.confirmNo'),
388
+          onPress: this.showPausePopup,
389
+        },
390
+      ],
391
+      onDialogDismiss: this.showPausePopup,
392
+    });
393
+  };
394
+
395
+  startGame = () => {
396
+    this.logic.startGame(this.onTick, this.onClock, this.onGameEnd);
397
+  };
398
+
399
+  render(): React.Node {
400
+    const {props, state} = this;
401
+    return (
402
+      <View style={{flex: 1}}>
403
+        <View
404
+          style={{
405
+            flex: 1,
406
+            flexDirection: 'row',
407
+          }}>
408
+          {this.getStatusIcons()}
409
+          <View style={{flex: 4}}>
410
+            {this.getScoreIcon()}
411
+            <GridComponent
412
+              width={this.logic.getWidth()}
413
+              height={this.logic.getHeight()}
414
+              grid={state.grid}
415
+              style={{
416
+                backgroundColor: props.theme.colors.tetrisBackground,
417
+                flex: 1,
418
+                marginLeft: 'auto',
419
+                marginRight: 'auto',
420
+              }}
421
+            />
422
+          </View>
423
+
424
+          <View style={{flex: 1}}>
425
+            <Preview
426
+              items={this.logic.getNextPiecesPreviews()}
427
+              style={{
428
+                marginLeft: 'auto',
429
+                marginRight: 'auto',
430
+                marginTop: 10,
431
+              }}
432
+            />
433
+          </View>
434
+        </View>
435
+        {this.getControlButtons()}
436
+
437
+        <OptionsDialog
438
+          visible={state.dialogVisible}
439
+          title={state.dialogTitle}
440
+          message={state.dialogMessage}
441
+          buttons={state.dialogButtons}
442
+          onDismiss={state.onDialogDismiss}
443
+        />
444
+      </View>
445
+    );
446
+  }
400
 }
447
 }
401
 
448
 
402
 export default withTheme(GameMainScreen);
449
 export default withTheme(GameMainScreen);

+ 413
- 400
src/screens/Game/screens/GameStartScreen.js View File

1
 // @flow
1
 // @flow
2
 
2
 
3
-import * as React from "react";
4
-import {StackNavigationProp} from "@react-navigation/stack";
5
-import type {CustomTheme} from "../../../managers/ThemeManager";
6
-import {Button, Card, Divider, Headline, Paragraph, Text, withTheme} from "react-native-paper";
7
-import {View} from "react-native";
8
-import i18n from "i18n-js";
9
-import Mascot, {MASCOT_STYLE} from "../../../components/Mascot/Mascot";
10
-import MascotPopup from "../../../components/Mascot/MascotPopup";
11
-import AsyncStorageManager from "../../../managers/AsyncStorageManager";
12
-import type {Grid} from "../components/GridComponent";
13
-import GridComponent from "../components/GridComponent";
14
-import GridManager from "../logic/GridManager";
15
-import Piece from "../logic/Piece";
16
-import * as Animatable from "react-native-animatable";
17
-import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
18
-import LinearGradient from "react-native-linear-gradient";
19
-import SpeechArrow from "../../../components/Mascot/SpeechArrow";
20
-import CollapsibleScrollView from "../../../components/Collapsible/CollapsibleScrollView";
3
+import * as React from 'react';
4
+import {StackNavigationProp} from '@react-navigation/stack';
5
+import {
6
+  Button,
7
+  Card,
8
+  Divider,
9
+  Headline,
10
+  Paragraph,
11
+  Text,
12
+  withTheme,
13
+} from 'react-native-paper';
14
+import {View} from 'react-native';
15
+import i18n from 'i18n-js';
16
+import * as Animatable from 'react-native-animatable';
17
+import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
18
+import LinearGradient from 'react-native-linear-gradient';
19
+import type {CustomThemeType} from '../../../managers/ThemeManager';
20
+import Mascot, {MASCOT_STYLE} from '../../../components/Mascot/Mascot';
21
+import MascotPopup from '../../../components/Mascot/MascotPopup';
22
+import AsyncStorageManager from '../../../managers/AsyncStorageManager';
23
+import type {GridType} from '../components/GridComponent';
24
+import GridComponent from '../components/GridComponent';
25
+import GridManager from '../logic/GridManager';
26
+import Piece from '../logic/Piece';
27
+import SpeechArrow from '../../../components/Mascot/SpeechArrow';
28
+import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
21
 
29
 
22
-type GameStats = {
23
-    score: number,
24
-    level: number,
25
-    time: number,
26
-}
30
+type GameStatsType = {
31
+  score: number,
32
+  level: number,
33
+  time: number,
34
+};
27
 
35
 
28
-type Props = {
29
-    navigation: StackNavigationProp,
30
-    route: {
31
-        params: GameStats
32
-    },
33
-    theme: CustomTheme,
34
-}
36
+type PropsType = {
37
+  navigation: StackNavigationProp,
38
+  route: {
39
+    params: GameStatsType,
40
+  },
41
+  theme: CustomThemeType,
42
+};
35
 
43
 
36
-class GameStartScreen extends React.Component<Props> {
44
+class GameStartScreen extends React.Component<PropsType> {
45
+  gridManager: GridManager;
37
 
46
 
38
-    gridManager: GridManager;
39
-    scores: Array<number>;
47
+  scores: Array<number>;
40
 
48
 
41
-    gameStats: GameStats | null;
42
-    isHighScore: boolean;
49
+  gameStats: GameStatsType | null;
43
 
50
 
44
-    constructor(props: Props) {
45
-        super(props);
46
-        this.gridManager = new GridManager(4, 4, props.theme);
47
-        this.scores = AsyncStorageManager.getObject(AsyncStorageManager.PREFERENCES.gameScores.key);
48
-        this.scores.sort((a, b) => b - a);
49
-        if (this.props.route.params != null)
50
-            this.recoverGameScore();
51
-    }
51
+  isHighScore: boolean;
52
 
52
 
53
-    recoverGameScore() {
54
-        this.gameStats = this.props.route.params;
55
-        this.isHighScore = this.scores.length === 0 || this.gameStats.score > this.scores[0];
56
-        for (let i = 0; i < 3; i++) {
57
-            if (this.scores.length > i && this.gameStats.score > this.scores[i]) {
58
-                this.scores.splice(i, 0, this.gameStats.score);
59
-                break;
60
-            } else if (this.scores.length <= i) {
61
-                this.scores.push(this.gameStats.score);
62
-                break;
63
-            }
64
-        }
65
-        if (this.scores.length > 3)
66
-            this.scores.splice(3, 1);
67
-        AsyncStorageManager.set(AsyncStorageManager.PREFERENCES.gameScores.key, this.scores);
68
-    }
53
+  constructor(props: PropsType) {
54
+    super(props);
55
+    this.gridManager = new GridManager(4, 4, props.theme);
56
+    this.scores = AsyncStorageManager.getObject(
57
+      AsyncStorageManager.PREFERENCES.gameScores.key,
58
+    );
59
+    this.scores.sort((a: number, b: number): number => b - a);
60
+    if (props.route.params != null) this.recoverGameScore();
61
+  }
69
 
62
 
70
-    getPiecesBackground() {
71
-        let gridList = [];
72
-        for (let i = 0; i < 18; i++) {
73
-            gridList.push(this.gridManager.getEmptyGrid(4, 4));
74
-            const piece = new Piece(this.props.theme);
75
-            piece.toGrid(gridList[i], true);
76
-        }
77
-        return (
78
-            <View style={{
79
-                position: "absolute",
80
-                width: "100%",
81
-                height: "100%",
82
-            }}>
83
-                {gridList.map((item: Grid, index: number) => {
84
-                    const size = 10 + Math.floor(Math.random() * 30);
85
-                    const top = Math.floor(Math.random() * 100);
86
-                    const rot = Math.floor(Math.random() * 360);
87
-                    const left = (index % 6) * 20;
88
-                    const animDelay = size * 20;
89
-                    const animDuration = 2 * (2000 - (size * 30));
90
-                    return (
91
-                        <Animatable.View
92
-                            animation={"fadeInDownBig"}
93
-                            delay={animDelay}
94
-                            duration={animDuration}
95
-                            key={"piece" + index.toString()}
96
-                            style={{
97
-                                width: size + "%",
98
-                                position: "absolute",
99
-                                top: top + "%",
100
-                                left: left + "%",
101
-                            }}
102
-                        >
103
-                            <GridComponent
104
-                                width={4}
105
-                                height={4}
106
-                                grid={item}
107
-                                style={{
108
-                                    transform: [{rotateZ: rot + "deg"}],
109
-                                }}
110
-                            />
111
-                        </Animatable.View>
112
-                    );
113
-                })}
114
-            </View>
115
-        );
63
+  getPiecesBackground(): React.Node {
64
+    const {theme} = this.props;
65
+    const gridList = [];
66
+    for (let i = 0; i < 18; i += 1) {
67
+      gridList.push(this.gridManager.getEmptyGrid(4, 4));
68
+      const piece = new Piece(theme);
69
+      piece.toGrid(gridList[i], true);
116
     }
70
     }
71
+    return (
72
+      <View
73
+        style={{
74
+          position: 'absolute',
75
+          width: '100%',
76
+          height: '100%',
77
+        }}>
78
+        {gridList.map((item: GridType, index: number): React.Node => {
79
+          const size = 10 + Math.floor(Math.random() * 30);
80
+          const top = Math.floor(Math.random() * 100);
81
+          const rot = Math.floor(Math.random() * 360);
82
+          const left = (index % 6) * 20;
83
+          const animDelay = size * 20;
84
+          const animDuration = 2 * (2000 - size * 30);
85
+          return (
86
+            <Animatable.View
87
+              animation="fadeInDownBig"
88
+              delay={animDelay}
89
+              duration={animDuration}
90
+              key={`piece${index.toString()}`}
91
+              style={{
92
+                width: `${size}%`,
93
+                position: 'absolute',
94
+                top: `${top}%`,
95
+                left: `${left}%`,
96
+              }}>
97
+              <GridComponent
98
+                width={4}
99
+                height={4}
100
+                grid={item}
101
+                style={{
102
+                  transform: [{rotateZ: `${rot}deg`}],
103
+                }}
104
+              />
105
+            </Animatable.View>
106
+          );
107
+        })}
108
+      </View>
109
+    );
110
+  }
117
 
111
 
118
-    getPostGameContent(stats: GameStats) {
119
-        return (
120
-            <View style={{
121
-                flex: 1
122
-            }}>
123
-                <Mascot
124
-                    emotion={this.isHighScore ? MASCOT_STYLE.LOVE : MASCOT_STYLE.NORMAL}
125
-                    animated={this.isHighScore}
126
-                    style={{
127
-                        width: this.isHighScore ? "50%" : "30%",
128
-                        marginLeft: this.isHighScore ? "auto" : null,
129
-                        marginRight: this.isHighScore ? "auto" : null,
130
-                    }}/>
131
-                <SpeechArrow
132
-                    style={{marginLeft: this.isHighScore ? "60%" : "20%"}}
133
-                    size={20}
134
-                    color={this.props.theme.colors.mascotMessageArrow}
135
-                />
136
-                <Card style={{
137
-                    borderColor: this.props.theme.colors.mascotMessageArrow,
138
-                    borderWidth: 2,
139
-                    marginLeft: 20,
140
-                    marginRight: 20,
112
+  getPostGameContent(stats: GameStatsType): React.Node {
113
+    const {props} = this;
114
+    return (
115
+      <View
116
+        style={{
117
+          flex: 1,
118
+        }}>
119
+        <Mascot
120
+          emotion={this.isHighScore ? MASCOT_STYLE.LOVE : MASCOT_STYLE.NORMAL}
121
+          animated={this.isHighScore}
122
+          style={{
123
+            width: this.isHighScore ? '50%' : '30%',
124
+            marginLeft: this.isHighScore ? 'auto' : null,
125
+            marginRight: this.isHighScore ? 'auto' : null,
126
+          }}
127
+        />
128
+        <SpeechArrow
129
+          style={{marginLeft: this.isHighScore ? '60%' : '20%'}}
130
+          size={20}
131
+          color={props.theme.colors.mascotMessageArrow}
132
+        />
133
+        <Card
134
+          style={{
135
+            borderColor: props.theme.colors.mascotMessageArrow,
136
+            borderWidth: 2,
137
+            marginLeft: 20,
138
+            marginRight: 20,
139
+          }}>
140
+          <Card.Content>
141
+            <Headline
142
+              style={{
143
+                textAlign: 'center',
144
+                color: this.isHighScore
145
+                  ? props.theme.colors.gameGold
146
+                  : props.theme.colors.primary,
147
+              }}>
148
+              {this.isHighScore
149
+                ? i18n.t('screens.game.newHighScore')
150
+                : i18n.t('screens.game.gameOver')}
151
+            </Headline>
152
+            <Divider />
153
+            <View
154
+              style={{
155
+                flexDirection: 'row',
156
+                marginLeft: 'auto',
157
+                marginRight: 'auto',
158
+                marginTop: 10,
159
+                marginBottom: 10,
160
+              }}>
161
+              <Text
162
+                style={{
163
+                  fontSize: 20,
141
                 }}>
164
                 }}>
142
-                    <Card.Content>
143
-                        <Headline
144
-                            style={{
145
-                                textAlign: "center",
146
-                                color: this.isHighScore
147
-                                    ? this.props.theme.colors.gameGold
148
-                                    : this.props.theme.colors.primary
149
-                            }}>
150
-                            {this.isHighScore
151
-                                ? i18n.t("screens.game.newHighScore")
152
-                                : i18n.t("screens.game.gameOver")}
153
-                        </Headline>
154
-                        <Divider/>
155
-                        <View style={{
156
-                            flexDirection: "row",
157
-                            marginLeft: "auto",
158
-                            marginRight: "auto",
159
-                            marginTop: 10,
160
-                            marginBottom: 10,
161
-                        }}>
162
-                            <Text style={{
163
-                                fontSize: 20,
164
-                            }}>
165
-                                {i18n.t("screens.game.score", {score: stats.score})}
166
-                            </Text>
167
-                            <MaterialCommunityIcons
168
-                                name={'star'}
169
-                                color={this.props.theme.colors.tetrisScore}
170
-                                size={30}
171
-                                style={{
172
-                                    marginLeft: 5
173
-                                }}/>
174
-                        </View>
175
-                        <View style={{
176
-                            flexDirection: "row",
177
-                            marginLeft: "auto",
178
-                            marginRight: "auto",
179
-                        }}>
180
-                            <Text>{i18n.t("screens.game.level")}</Text>
181
-                            <MaterialCommunityIcons
182
-                                style={{
183
-                                    marginRight: 5,
184
-                                    marginLeft: 5,
185
-                                }}
186
-                                name={"gamepad-square"}
187
-                                size={20}
188
-                                color={this.props.theme.colors.textDisabled}
189
-                            />
190
-                            <Text>
191
-                                {stats.level}
192
-                            </Text>
193
-                        </View>
194
-                        <View style={{
195
-                            flexDirection: "row",
196
-                            marginLeft: "auto",
197
-                            marginRight: "auto",
198
-                        }}>
199
-                            <Text>{i18n.t("screens.game.time")}</Text>
200
-                            <MaterialCommunityIcons
201
-                                style={{
202
-                                    marginRight: 5,
203
-                                    marginLeft: 5,
204
-                                }}
205
-                                name={"timer"}
206
-                                size={20}
207
-                                color={this.props.theme.colors.textDisabled}
208
-                            />
209
-                            <Text>
210
-                                {stats.time}
211
-                            </Text>
212
-                        </View>
213
-                    </Card.Content>
214
-                </Card>
165
+                {i18n.t('screens.game.score', {score: stats.score})}
166
+              </Text>
167
+              <MaterialCommunityIcons
168
+                name="star"
169
+                color={props.theme.colors.tetrisScore}
170
+                size={30}
171
+                style={{
172
+                  marginLeft: 5,
173
+                }}
174
+              />
215
             </View>
175
             </View>
216
-        )
217
-    }
218
-
219
-    getWelcomeText() {
220
-        return (
221
-            <View>
222
-                <Mascot emotion={MASCOT_STYLE.COOL} style={{
223
-                    width: "40%",
224
-                    marginLeft: "auto",
225
-                    marginRight: "auto",
226
-                }}/>
227
-                <SpeechArrow
228
-                    style={{marginLeft: "60%"}}
229
-                    size={20}
230
-                    color={this.props.theme.colors.mascotMessageArrow}
231
-                />
232
-                <Card style={{
233
-                    borderColor: this.props.theme.colors.mascotMessageArrow,
234
-                    borderWidth: 2,
235
-                    marginLeft: 10,
236
-                    marginRight: 10,
237
-                }}>
238
-                    <Card.Content>
239
-                        <Headline
240
-                            style={{
241
-                                textAlign: "center",
242
-                                color: this.props.theme.colors.primary
243
-                            }}>
244
-                            {i18n.t("screens.game.welcomeTitle")}
245
-                        </Headline>
246
-                        <Divider/>
247
-                        <Paragraph
248
-                            style={{
249
-                                textAlign: "center",
250
-                                marginTop: 10,
251
-                            }}>
252
-                            {i18n.t("screens.game.welcomeMessage")}
253
-                        </Paragraph>
254
-                    </Card.Content>
255
-                </Card>
176
+            <View
177
+              style={{
178
+                flexDirection: 'row',
179
+                marginLeft: 'auto',
180
+                marginRight: 'auto',
181
+              }}>
182
+              <Text>{i18n.t('screens.game.level')}</Text>
183
+              <MaterialCommunityIcons
184
+                style={{
185
+                  marginRight: 5,
186
+                  marginLeft: 5,
187
+                }}
188
+                name="gamepad-square"
189
+                size={20}
190
+                color={props.theme.colors.textDisabled}
191
+              />
192
+              <Text>{stats.level}</Text>
256
             </View>
193
             </View>
257
-        );
258
-    }
194
+            <View
195
+              style={{
196
+                flexDirection: 'row',
197
+                marginLeft: 'auto',
198
+                marginRight: 'auto',
199
+              }}>
200
+              <Text>{i18n.t('screens.game.time')}</Text>
201
+              <MaterialCommunityIcons
202
+                style={{
203
+                  marginRight: 5,
204
+                  marginLeft: 5,
205
+                }}
206
+                name="timer"
207
+                size={20}
208
+                color={props.theme.colors.textDisabled}
209
+              />
210
+              <Text>{stats.time}</Text>
211
+            </View>
212
+          </Card.Content>
213
+        </Card>
214
+      </View>
215
+    );
216
+  }
259
 
217
 
260
-    getPodiumRender(place: 1 | 2 | 3, score: string) {
261
-        let icon = "podium-gold";
262
-        let color = this.props.theme.colors.gameGold;
263
-        let fontSize = 20;
264
-        let size = 70;
265
-        if (place === 2) {
266
-            icon = "podium-silver";
267
-            color = this.props.theme.colors.gameSilver;
268
-            fontSize = 18;
269
-            size = 60;
270
-        } else if (place === 3) {
271
-            icon = "podium-bronze";
272
-            color = this.props.theme.colors.gameBronze;
273
-            fontSize = 15;
274
-            size = 50;
275
-        }
276
-        return (
277
-            <View style={{
278
-                marginLeft: place === 2 ? 20 : "auto",
279
-                marginRight: place === 3 ? 20 : "auto",
280
-                flexDirection: "column",
281
-                alignItems: "center",
282
-                justifyContent: "flex-end",
283
-            }}>
284
-                {
285
-                    this.isHighScore && place === 1
286
-                        ?
287
-                        <Animatable.View
288
-                            animation={"swing"}
289
-                            iterationCount={"infinite"}
290
-                            duration={2000}
291
-                            delay={1000}
292
-                            useNativeDriver={true}
293
-                            style={{
294
-                                position: "absolute",
295
-                                top: -20
296
-                            }}
297
-                        >
298
-                            <Animatable.View
299
-                                animation={"pulse"}
300
-                                iterationCount={"infinite"}
301
-                                useNativeDriver={true}
302
-                            >
303
-                                <MaterialCommunityIcons
304
-                                    name={"decagram"}
305
-                                    color={this.props.theme.colors.gameGold}
306
-                                    size={150}
307
-                                />
308
-                            </Animatable.View>
309
-                        </Animatable.View>
218
+  getWelcomeText(): React.Node {
219
+    const {props} = this;
220
+    return (
221
+      <View>
222
+        <Mascot
223
+          emotion={MASCOT_STYLE.COOL}
224
+          style={{
225
+            width: '40%',
226
+            marginLeft: 'auto',
227
+            marginRight: 'auto',
228
+          }}
229
+        />
230
+        <SpeechArrow
231
+          style={{marginLeft: '60%'}}
232
+          size={20}
233
+          color={props.theme.colors.mascotMessageArrow}
234
+        />
235
+        <Card
236
+          style={{
237
+            borderColor: props.theme.colors.mascotMessageArrow,
238
+            borderWidth: 2,
239
+            marginLeft: 10,
240
+            marginRight: 10,
241
+          }}>
242
+          <Card.Content>
243
+            <Headline
244
+              style={{
245
+                textAlign: 'center',
246
+                color: props.theme.colors.primary,
247
+              }}>
248
+              {i18n.t('screens.game.welcomeTitle')}
249
+            </Headline>
250
+            <Divider />
251
+            <Paragraph
252
+              style={{
253
+                textAlign: 'center',
254
+                marginTop: 10,
255
+              }}>
256
+              {i18n.t('screens.game.welcomeMessage')}
257
+            </Paragraph>
258
+          </Card.Content>
259
+        </Card>
260
+      </View>
261
+    );
262
+  }
310
 
263
 
311
-                        : null
312
-                }
313
-                <MaterialCommunityIcons
314
-                    name={icon}
315
-                    color={this.isHighScore && place === 1 ? "#fff" : color}
316
-                    size={size}
317
-                />
318
-                <Text style={{
319
-                    textAlign: "center",
320
-                    fontWeight: place === 1 ? "bold" : null,
321
-                    fontSize: fontSize,
322
-                }}>{score}</Text>
323
-            </View>
324
-        );
264
+  getPodiumRender(place: 1 | 2 | 3, score: string): React.Node {
265
+    const {props} = this;
266
+    let icon = 'podium-gold';
267
+    let color = props.theme.colors.gameGold;
268
+    let fontSize = 20;
269
+    let size = 70;
270
+    if (place === 2) {
271
+      icon = 'podium-silver';
272
+      color = props.theme.colors.gameSilver;
273
+      fontSize = 18;
274
+      size = 60;
275
+    } else if (place === 3) {
276
+      icon = 'podium-bronze';
277
+      color = props.theme.colors.gameBronze;
278
+      fontSize = 15;
279
+      size = 50;
325
     }
280
     }
326
-
327
-    getTopScoresRender() {
328
-        const gold = this.scores.length > 0
329
-            ? this.scores[0]
330
-            : "-";
331
-        const silver = this.scores.length > 1
332
-            ? this.scores[1]
333
-            : "-";
334
-        const bronze = this.scores.length > 2
335
-            ? this.scores[2]
336
-            : "-";
337
-        return (
338
-            <View style={{
339
-                marginBottom: 20,
340
-                marginTop: 20
281
+    return (
282
+      <View
283
+        style={{
284
+          marginLeft: place === 2 ? 20 : 'auto',
285
+          marginRight: place === 3 ? 20 : 'auto',
286
+          flexDirection: 'column',
287
+          alignItems: 'center',
288
+          justifyContent: 'flex-end',
289
+        }}>
290
+        {this.isHighScore && place === 1 ? (
291
+          <Animatable.View
292
+            animation="swing"
293
+            iterationCount="infinite"
294
+            duration={2000}
295
+            delay={1000}
296
+            useNativeDriver
297
+            style={{
298
+              position: 'absolute',
299
+              top: -20,
341
             }}>
300
             }}>
342
-                {this.getPodiumRender(1, gold.toString())}
343
-                <View style={{
344
-                    flexDirection: "row",
345
-                    marginLeft: "auto",
346
-                    marginRight: "auto",
347
-                }}>
348
-                    {this.getPodiumRender(3, bronze.toString())}
349
-                    {this.getPodiumRender(2, silver.toString())}
350
-                </View>
351
-            </View>
352
-        );
353
-    }
301
+            <Animatable.View
302
+              animation="pulse"
303
+              iterationCount="infinite"
304
+              useNativeDriver>
305
+              <MaterialCommunityIcons
306
+                name="decagram"
307
+                color={props.theme.colors.gameGold}
308
+                size={150}
309
+              />
310
+            </Animatable.View>
311
+          </Animatable.View>
312
+        ) : null}
313
+        <MaterialCommunityIcons
314
+          name={icon}
315
+          color={this.isHighScore && place === 1 ? '#fff' : color}
316
+          size={size}
317
+        />
318
+        <Text
319
+          style={{
320
+            textAlign: 'center',
321
+            fontWeight: place === 1 ? 'bold' : null,
322
+            fontSize,
323
+          }}>
324
+          {score}
325
+        </Text>
326
+      </View>
327
+    );
328
+  }
354
 
329
 
355
-    getMainContent() {
356
-        return (
357
-            <View style={{flex: 1}}>
358
-                {
359
-                    this.gameStats != null
360
-                        ? this.getPostGameContent(this.gameStats)
361
-                        : this.getWelcomeText()
362
-                }
363
-                <Button
364
-                    icon={"play"}
365
-                    mode={"contained"}
366
-                    onPress={() => this.props.navigation.replace(
367
-                        "game-main",
368
-                        {
369
-                            highScore: this.scores.length > 0
370
-                                ? this.scores[0]
371
-                                : null
372
-                        }
373
-                    )}
374
-                    style={{
375
-                        marginLeft: "auto",
376
-                        marginRight: "auto",
377
-                        marginTop: 10,
378
-                    }}
379
-                >
380
-                    {i18n.t("screens.game.play")}
381
-                </Button>
382
-                {this.getTopScoresRender()}
383
-            </View>
384
-        )
385
-    }
330
+  getTopScoresRender(): React.Node {
331
+    const gold = this.scores.length > 0 ? this.scores[0] : '-';
332
+    const silver = this.scores.length > 1 ? this.scores[1] : '-';
333
+    const bronze = this.scores.length > 2 ? this.scores[2] : '-';
334
+    return (
335
+      <View
336
+        style={{
337
+          marginBottom: 20,
338
+          marginTop: 20,
339
+        }}>
340
+        {this.getPodiumRender(1, gold.toString())}
341
+        <View
342
+          style={{
343
+            flexDirection: 'row',
344
+            marginLeft: 'auto',
345
+            marginRight: 'auto',
346
+          }}>
347
+          {this.getPodiumRender(3, bronze.toString())}
348
+          {this.getPodiumRender(2, silver.toString())}
349
+        </View>
350
+      </View>
351
+    );
352
+  }
386
 
353
 
387
-    keyExtractor = (item: number) => item.toString();
354
+  getMainContent(): React.Node {
355
+    const {props} = this;
356
+    return (
357
+      <View style={{flex: 1}}>
358
+        {this.gameStats != null
359
+          ? this.getPostGameContent(this.gameStats)
360
+          : this.getWelcomeText()}
361
+        <Button
362
+          icon="play"
363
+          mode="contained"
364
+          onPress={() => {
365
+            props.navigation.replace('game-main', {
366
+              highScore: this.scores.length > 0 ? this.scores[0] : null,
367
+            });
368
+          }}
369
+          style={{
370
+            marginLeft: 'auto',
371
+            marginRight: 'auto',
372
+            marginTop: 10,
373
+          }}>
374
+          {i18n.t('screens.game.play')}
375
+        </Button>
376
+        {this.getTopScoresRender()}
377
+      </View>
378
+    );
379
+  }
388
 
380
 
389
-    render() {
390
-        return (
391
-            <View style={{flex: 1}}>
392
-                {this.getPiecesBackground()}
393
-                <LinearGradient
394
-                    style={{flex: 1}}
395
-                    colors={[
396
-                        this.props.theme.colors.background + "00",
397
-                        this.props.theme.colors.background
398
-                    ]}
399
-                    start={{x: 0, y: 0}}
400
-                    end={{x: 0, y: 1}}
401
-                >
402
-                    <CollapsibleScrollView>
403
-                        {this.getMainContent()}
404
-                        <MascotPopup
405
-                            prefKey={AsyncStorageManager.PREFERENCES.gameStartShowBanner.key}
406
-                            title={i18n.t("screens.game.mascotDialog.title")}
407
-                            message={i18n.t("screens.game.mascotDialog.message")}
408
-                            icon={"gamepad-variant"}
409
-                            buttons={{
410
-                                action: null,
411
-                                cancel: {
412
-                                    message: i18n.t("screens.game.mascotDialog.button"),
413
-                                    icon: "check",
414
-                                }
415
-                            }}
416
-                            emotion={MASCOT_STYLE.COOL}
417
-                        />
418
-                    </CollapsibleScrollView>
419
-                </LinearGradient>
420
-            </View>
381
+  keyExtractor = (item: number): string => item.toString();
421
 
382
 
422
-        );
383
+  recoverGameScore() {
384
+    const {route} = this.props;
385
+    this.gameStats = route.params;
386
+    this.isHighScore =
387
+      this.scores.length === 0 || this.gameStats.score > this.scores[0];
388
+    for (let i = 0; i < 3; i += 1) {
389
+      if (this.scores.length > i && this.gameStats.score > this.scores[i]) {
390
+        this.scores.splice(i, 0, this.gameStats.score);
391
+        break;
392
+      } else if (this.scores.length <= i) {
393
+        this.scores.push(this.gameStats.score);
394
+        break;
395
+      }
423
     }
396
     }
397
+    if (this.scores.length > 3) this.scores.splice(3, 1);
398
+    AsyncStorageManager.set(
399
+      AsyncStorageManager.PREFERENCES.gameScores.key,
400
+      this.scores,
401
+    );
402
+  }
403
+
404
+  render(): React.Node {
405
+    const {props} = this;
406
+    return (
407
+      <View style={{flex: 1}}>
408
+        {this.getPiecesBackground()}
409
+        <LinearGradient
410
+          style={{flex: 1}}
411
+          colors={[
412
+            `${props.theme.colors.background}00`,
413
+            props.theme.colors.background,
414
+          ]}
415
+          start={{x: 0, y: 0}}
416
+          end={{x: 0, y: 1}}>
417
+          <CollapsibleScrollView>
418
+            {this.getMainContent()}
419
+            <MascotPopup
420
+              prefKey={AsyncStorageManager.PREFERENCES.gameStartShowBanner.key}
421
+              title={i18n.t('screens.game.mascotDialog.title')}
422
+              message={i18n.t('screens.game.mascotDialog.message')}
423
+              icon="gamepad-variant"
424
+              buttons={{
425
+                action: null,
426
+                cancel: {
427
+                  message: i18n.t('screens.game.mascotDialog.button'),
428
+                  icon: 'check',
429
+                },
430
+              }}
431
+              emotion={MASCOT_STYLE.COOL}
432
+            />
433
+          </CollapsibleScrollView>
434
+        </LinearGradient>
435
+      </View>
436
+    );
437
+  }
424
 }
438
 }
425
 
439
 
426
 export default withTheme(GameStartScreen);
440
 export default withTheme(GameStartScreen);
427
-

Loading…
Cancel
Save