From 7f33c8376d45e368a41a05f154f57082e65ebc58 Mon Sep 17 00:00:00 2001 From: Arnaud Vergnet Date: Mon, 16 Mar 2020 19:10:32 +0100 Subject: [PATCH] Improved game UI and state management --- screens/Tetris/GameLogic.js | 41 +++++++-- screens/Tetris/TetrisScreen.js | 151 +++++++++++++++++++++++++++------ utils/ThemeManager.js | 2 + 3 files changed, 162 insertions(+), 32 deletions(-) diff --git a/screens/Tetris/GameLogic.js b/screens/Tetris/GameLogic.js index 05a14eb..4863e01 100644 --- a/screens/Tetris/GameLogic.js +++ b/screens/Tetris/GameLogic.js @@ -10,6 +10,7 @@ export default class GameLogic { width: number; gameRunning: boolean; + gamePaused: boolean; gameTime: number; score: number; @@ -27,6 +28,7 @@ export default class GameLogic { this.height = height; this.width = width; this.gameRunning = false; + this.gamePaused = false; this.gameTick = 250; this.colors = colors; } @@ -43,6 +45,10 @@ export default class GameLogic { return this.gameRunning; } + isGamePaused(): boolean { + return this.gamePaused; + } + getEmptyLine() { let line = []; for (let col = 0; col < this.getWidth(); col++) { @@ -161,17 +167,30 @@ export default class GameLogic { callback(this.gameTime, this.score, this.getFinalGrid()); } + canUseInput() { + return this.gameRunning && !this.gamePaused + } + rightPressed(callback: Function) { + if (!this.canUseInput()) + return; + this.tryMoveTetromino(1, 0); callback(this.getFinalGrid()); } leftPressed(callback: Function) { + if (!this.canUseInput()) + return; + this.tryMoveTetromino(-1, 0); callback(this.getFinalGrid()); } rotatePressed(callback: Function) { + if (!this.canUseInput()) + return; + this.tryRotateTetromino(); callback(this.getFinalGrid()); } @@ -180,20 +199,32 @@ export default class GameLogic { let shape = Math.floor(Math.random() * 7); this.currentObject = new Tetromino(shape, this.colors); if (!this.isTetrominoPositionValid()) - this.endGame(); + this.endGame(false); } - endGame() { - console.log('Game Over!'); + togglePause() { + if (!this.gameRunning) + return; + this.gamePaused = !this.gamePaused; + if (this.gamePaused) { + clearInterval(this.gameTickInterval); + } else { + this.gameTickInterval = setInterval(this.onTick, this.gameTick); + } + } + + endGame(isRestart: boolean) { this.gameRunning = false; + this.gamePaused = false; clearInterval(this.gameTickInterval); - this.endCallback(this.gameTime, this.score); + this.endCallback(this.gameTime, this.score, isRestart); } startGame(tickCallback: Function, endCallback: Function) { if (this.gameRunning) - return; + this.endGame(true); this.gameRunning = true; + this.gamePaused = false; this.gameTime = 0; this.score = 0; this.currentGrid = this.getEmptyGrid(); diff --git a/screens/Tetris/TetrisScreen.js b/screens/Tetris/TetrisScreen.js index c0bb6dc..f9675de 100644 --- a/screens/Tetris/TetrisScreen.js +++ b/screens/Tetris/TetrisScreen.js @@ -1,10 +1,12 @@ // @flow import * as React from 'react'; -import {View} from 'react-native'; +import {Alert, View} from 'react-native'; import {IconButton, Text, withTheme} from 'react-native-paper'; +import {MaterialCommunityIcons} from "@expo/vector-icons"; import GameLogic from "./GameLogic"; import Grid from "./components/Grid"; +import HeaderButton from "../../components/HeaderButton"; type Props = { navigation: Object, @@ -12,6 +14,7 @@ type Props = { type State = { grid: Array>, + gameRunning: boolean, gameTime: number, gameScore: number } @@ -31,22 +34,49 @@ class TetrisScreen extends React.Component { this.logic = new GameLogic(20, 10, this.colors); this.state = { grid: this.logic.getEmptyGrid(), + gameRunning: false, gameTime: 0, gameScore: 0, }; this.onTick = this.onTick.bind(this); this.onGameEnd = this.onGameEnd.bind(this); this.updateGrid = this.updateGrid.bind(this); - const onScreenBlur = this.onScreenBlur.bind(this); - this.props.navigation.addListener('blur', onScreenBlur); + this.props.navigation.addListener('blur', this.onScreenBlur.bind(this)); + this.props.navigation.addListener('focus', this.onScreenFocus.bind(this)); } + componentDidMount() { + const rightButton = this.getRightButton.bind(this); + this.props.navigation.setOptions({ + headerRight: rightButton, + }); + this.startGame(); + } + + getRightButton() { + return ( + + this.togglePause()}/> + + ); + } /** * Remove any interval on un-focus */ onScreenBlur() { - this.logic.endGame(); + if (!this.logic.isGamePaused()) + this.logic.togglePause(); + } + + onScreenFocus() { + if (!this.logic.isGameRunning()) + this.startGame(); + else if (this.logic.isGamePaused()) + this.showPausePopup(); } onTick(time: number, score: number, newGrid: Array>) { @@ -63,17 +93,63 @@ class TetrisScreen extends React.Component { }); } - startGame() { - if (!this.logic.isGameRunning()) { - this.logic.startGame(this.onTick, this.onGameEnd); - } + togglePause() { + this.logic.togglePause(); + if (this.logic.isGamePaused()) + this.showPausePopup(); } - onGameEnd(time: number, score: number) { + showPausePopup() { + Alert.alert( + 'PAUSE', + 'GAME PAUSED', + [ + {text: 'RESTART', onPress: () => this.showRestartConfirm()}, + {text: 'RESUME', onPress: () => this.togglePause()}, + ], + {cancelable: false}, + ); + } + + showRestartConfirm() { + Alert.alert( + 'RESTART?', + 'WHOA THERE', + [ + {text: 'NO', onPress: () => this.showPausePopup()}, + {text: 'YES', onPress: () => this.startGame()}, + ], + {cancelable: false}, + ); + } + + showGameOverConfirm() { + Alert.alert( + 'GAME OVER', + 'NOOB', + [ + {text: 'LEAVE', onPress: () => this.props.navigation.goBack()}, + {text: 'RESTART', onPress: () => this.startGame()}, + ], + {cancelable: false}, + ); + } + + startGame() { + this.logic.startGame(this.onTick, this.onGameEnd); + this.setState({ + gameRunning: true, + }); + } + + onGameEnd(time: number, score: number, isRestart: boolean) { this.setState({ gameTime: time, gameScore: score, - }) + gameRunning: false, + }); + if (!isRestart) + this.showGameOverConfirm(); } render() { @@ -82,28 +158,49 @@ class TetrisScreen extends React.Component { width: '100%', height: '100%', }}> - - Score: {this.state.gameScore} - - + {this.state.gameTime} + + - time: {this.state.gameTime} - + + {this.state.gameScore} + this.logic.rightPressed(this.updateGrid)} + onPress={() => this.logic.rotatePressed(this.updateGrid)} /> { onPress={() => this.logic.leftPressed(this.updateGrid)} /> this.logic.rotatePressed(this.updateGrid)} + onPress={() => this.logic.rightPressed(this.updateGrid)} /> this.startGame()} - /> + icon="arrow-down" + size={40} + onPress={() => this.logic.rightPressed(this.updateGrid)} + /> ); diff --git a/utils/ThemeManager.js b/utils/ThemeManager.js index eb3f914..977df0d 100644 --- a/utils/ThemeManager.js +++ b/utils/ThemeManager.js @@ -58,6 +58,7 @@ export default class ThemeManager { // Tetris tetrisBackground:'#d1d1d1', tetrisBorder:'#afafaf', + tetrisScore:'#fff307', tetrisI : '#42f1ff', tetrisO : '#ffdd00', tetrisT : '#ba19ff', @@ -109,6 +110,7 @@ export default class ThemeManager { // Tetris tetrisBackground:'#2c2c2c', tetrisBorder:'#1b1b1b', + tetrisScore:'#e2d707', tetrisI : '#30b3be', tetrisO : '#c1a700', tetrisT : '#9114c7',