Compare commits

..

20 commits

Author SHA1 Message Date
3bc45704f6 Improved ui on larger screens 2020-03-20 21:15:37 +01:00
7da30e0af6 Added previews 2020-03-17 14:22:49 +01:00
7a00452cc0 Improved level cap 2020-03-17 11:12:55 +01:00
879ae46abe Improved score management 2020-03-17 11:00:45 +01:00
dbe03a2a2d Fixed level 6 missing 2020-03-17 00:25:44 +01:00
7d718141e7 Added touch and hold controls 2020-03-17 00:24:57 +01:00
79e72784d1 Improved piece rotation 2020-03-16 23:36:01 +01:00
b29973189f Use pretty date formatting 2020-03-16 20:23:29 +01:00
07d8fb8d15 Added levels 2020-03-16 20:10:54 +01:00
e5bde81964 Improved light mode colors and game over message 2020-03-16 19:48:03 +01:00
7980b8b422 Made clock use seconds not game ticks 2020-03-16 19:40:52 +01:00
3affbfcb40 Improved buttons position 2020-03-16 19:29:57 +01:00
c9237cc824 Improved score updates 2020-03-16 19:26:42 +01:00
7f33c8376d Improved game UI and state management 2020-03-16 19:10:32 +01:00
8fc5cfb25e Remove full lines 2020-03-16 14:58:13 +01:00
3fe1d85eec Fixed start position 2020-03-16 08:22:18 +01:00
a32294e394 Improved colors 2020-03-15 20:34:20 +01:00
7b45841c30 Use pure component for cells 2020-03-15 20:18:48 +01:00
bb54186d9e Added rotation feature 2020-03-15 19:28:41 +01:00
3aaf56a660 Added basic tetris functionality 2020-03-15 18:44:32 +01:00
9 changed files with 1125 additions and 1 deletions

View file

@ -6,6 +6,7 @@ import i18n from "i18n-js";
import * as WebBrowser from 'expo-web-browser';
import SidebarDivider from "./SidebarDivider";
import SidebarItem from "./SidebarItem";
import {TouchableRipple} from "react-native-paper";
const deviceWidth = Dimensions.get("window").width;
@ -154,9 +155,17 @@ export default class SideBar extends React.PureComponent<Props, State> {
}
render() {
const onPress = this.onListItemPress.bind(this, {route: 'TetrisScreen'});
return (
<View style={{height: '100%'}}>
<Image source={require("../assets/drawer-cover.png")} style={styles.drawerCover}/>
<TouchableRipple
onPress={onPress}
>
<Image
source={require("../assets/drawer-cover.png")}
style={styles.drawerCover}
/>
</TouchableRipple>
<FlatList
data={this.dataSet}
extraData={this.state}

View file

@ -9,6 +9,7 @@ import AboutDependenciesScreen from '../screens/About/AboutDependenciesScreen';
import SelfMenuScreen from '../screens/SelfMenuScreen';
import AvailableRoomScreen from "../screens/Websites/AvailableRoomScreen";
import BibScreen from "../screens/Websites/BibScreen";
import TetrisScreen from "../screens/Tetris/TetrisScreen";
import DebugScreen from '../screens/About/DebugScreen';
import Sidebar from "../components/Sidebar";
import {createStackNavigator, TransitionPresets} from "@react-navigation/stack";
@ -161,6 +162,30 @@ function BibStackComponent() {
);
}
const TetrisStack = createStackNavigator();
function TetrisStackComponent() {
return (
<TetrisStack.Navigator
initialRouteName="TetrisScreen"
headerMode="float"
screenOptions={defaultScreenOptions}
>
<TetrisStack.Screen
name="TetrisScreen"
component={TetrisScreen}
options={({navigation}) => {
const openDrawer = getDrawerButton.bind(this, navigation);
return {
title: 'Tetris',
headerLeft: openDrawer
};
}}
/>
</TetrisStack.Navigator>
);
}
const Drawer = createDrawerNavigator();
function getDrawerContent(props) {
@ -202,6 +227,10 @@ export default function DrawerNavigator() {
name="BibScreen"
component={BibStackComponent}
/>
<Drawer.Screen
name="TetrisScreen"
component={TetrisStackComponent}
/>
</Drawer.Navigator>
);
}

370
screens/Tetris/GameLogic.js Normal file
View file

@ -0,0 +1,370 @@
// @flow
import Tetromino from "./Tetromino";
export default class GameLogic {
static levelTicks = [
1000,
800,
600,
400,
300,
200,
150,
100,
];
static scoreLinesModifier = [40, 100, 300, 1200];
currentGrid: Array<Array<Object>>;
height: number;
width: number;
gameRunning: boolean;
gamePaused: boolean;
gameTime: number;
score: number;
level: number;
currentObject: Tetromino;
gameTick: number;
gameTickInterval: IntervalID;
gameTimeInterval: IntervalID;
pressInInterval: TimeoutID;
isPressedIn: boolean;
autoRepeatActivationDelay: number;
autoRepeatDelay: number;
nextPieces: Array<Tetromino>;
nextPiecesCount: number;
onTick: Function;
onClock: Function;
endCallback: Function;
colors: Object;
levelProgression: number;
constructor(height: number, width: number, colors: Object) {
this.height = height;
this.width = width;
this.gameRunning = false;
this.gamePaused = false;
this.colors = colors;
this.autoRepeatActivationDelay = 300;
this.autoRepeatDelay = 50;
this.nextPieces = [];
this.nextPiecesCount = 3;
}
getNextPieces() {
let finalArray = [];
for (let i = 0; i < this.nextPieces.length; i++) {
finalArray.push(this.getEmptyGrid(4, 4));
let coord = this.nextPieces[i].getCellsCoordinates(false);
this.tetrominoToGrid(this.nextPieces[i], coord, finalArray[i]);
}
return finalArray;
}
getHeight(): number {
return this.height;
}
getWidth(): number {
return this.width;
}
isGameRunning(): boolean {
return this.gameRunning;
}
isGamePaused(): boolean {
return this.gamePaused;
}
getEmptyLine(width: number) {
let line = [];
for (let col = 0; col < width; col++) {
line.push({
color: this.colors.tetrisBackground,
isEmpty: true,
});
}
return line;
}
getEmptyGrid(height: number, width: number) {
let grid = [];
for (let row = 0; row < height; row++) {
grid.push(this.getEmptyLine(width));
}
return grid;
}
getGridCopy() {
return JSON.parse(JSON.stringify(this.currentGrid));
}
getFinalGrid() {
let coord = this.currentObject.getCellsCoordinates(true);
let finalGrid = this.getGridCopy();
for (let i = 0; i < coord.length; i++) {
finalGrid[coord[i].y][coord[i].x] = {
color: this.currentObject.getColor(),
isEmpty: false,
};
}
return finalGrid;
}
getLinesRemovedPoints(numberRemoved: number) {
if (numberRemoved < 1 || numberRemoved > 4)
return 0;
return GameLogic.scoreLinesModifier[numberRemoved-1] * (this.level + 1);
}
canLevelUp() {
let canLevel = this.levelProgression > this.level * 5;
if (canLevel)
this.levelProgression -= this.level * 5;
return canLevel;
}
tetrominoToGrid(object: Object, coord : Array<Object>, grid: Array<Array<Object>>) {
for (let i = 0; i < coord.length; i++) {
grid[coord[i].y][coord[i].x] = {
color: object.getColor(),
isEmpty: false,
};
}
}
freezeTetromino() {
let coord = this.currentObject.getCellsCoordinates(true);
this.tetrominoToGrid(this.currentObject, coord, this.currentGrid);
this.clearLines(this.getLinesToClear(coord));
}
clearLines(lines: Array<number>) {
lines.sort();
for (let i = 0; i < lines.length; i++) {
this.currentGrid.splice(lines[i], 1);
this.currentGrid.unshift(this.getEmptyLine(this.getWidth()));
}
switch (lines.length) {
case 1:
this.levelProgression += 1;
break;
case 2:
this.levelProgression += 3;
break;
case 3:
this.levelProgression += 5;
break;
case 4: // Did a tetris !
this.levelProgression += 8;
break;
}
this.score += this.getLinesRemovedPoints(lines.length);
}
getLinesToClear(coord: Object) {
let rows = [];
for (let i = 0; i < coord.length; i++) {
let isLineFull = true;
for (let col = 0; col < this.getWidth(); col++) {
if (this.currentGrid[coord[i].y][col].isEmpty) {
isLineFull = false;
break;
}
}
if (isLineFull && rows.indexOf(coord[i].y) === -1)
rows.push(coord[i].y);
}
return rows;
}
isTetrominoPositionValid() {
let isValid = true;
let coord = this.currentObject.getCellsCoordinates(true);
for (let i = 0; i < coord.length; i++) {
if (coord[i].x >= this.getWidth()
|| coord[i].x < 0
|| coord[i].y >= this.getHeight()
|| coord[i].y < 0
|| !this.currentGrid[coord[i].y][coord[i].x].isEmpty) {
isValid = false;
break;
}
}
return isValid;
}
tryMoveTetromino(x: number, y: number) {
if (x > 1) x = 1; // Prevent moving from more than one tile
if (x < -1) x = -1;
if (y > 1) y = 1;
if (y < -1) y = -1;
if (x !== 0 && y !== 0) y = 0; // Prevent diagonal movement
this.currentObject.move(x, y);
let isValid = this.isTetrominoPositionValid();
if (!isValid && x !== 0)
this.currentObject.move(-x, 0);
else if (!isValid && y !== 0) {
this.currentObject.move(0, -y);
this.freezeTetromino();
this.createTetromino();
} else
return true;
return false;
}
tryRotateTetromino() {
this.currentObject.rotate(true);
if (!this.isTetrominoPositionValid()) {
this.currentObject.rotate(false);
return false;
}
return true;
}
setNewGameTick(level: number) {
if (level >= GameLogic.levelTicks.length)
return;
this.gameTick = GameLogic.levelTicks[level];
clearInterval(this.gameTickInterval);
this.gameTickInterval = setInterval(this.onTick, this.gameTick);
}
onTick(callback: Function) {
this.tryMoveTetromino(0, 1);
callback(this.score, this.level, this.getFinalGrid());
if (this.canLevelUp()) {
this.level++;
this.setNewGameTick(this.level);
}
}
onClock(callback: Function) {
this.gameTime++;
callback(this.gameTime);
}
canUseInput() {
return this.gameRunning && !this.gamePaused
}
rightPressed(callback: Function) {
this.isPressedIn = true;
this.movePressedRepeat(true, callback, 1, 0);
}
leftPressedIn(callback: Function) {
this.isPressedIn = true;
this.movePressedRepeat(true, callback, -1, 0);
}
downPressedIn(callback: Function) {
this.isPressedIn = true;
this.movePressedRepeat(true, callback, 0, 1);
}
movePressedRepeat(isInitial: boolean, callback: Function, x: number, y: number) {
if (!this.canUseInput() || !this.isPressedIn)
return;
if (this.tryMoveTetromino(x, y)) {
if (y === 1) {
this.score++;
callback(this.getFinalGrid(), this.score);
} else
callback(this.getFinalGrid());
}
this.pressInInterval = setTimeout(() => this.movePressedRepeat(false, callback, x, y), isInitial ? this.autoRepeatActivationDelay : this.autoRepeatDelay);
}
pressedOut() {
this.isPressedIn = false;
clearTimeout(this.pressInInterval);
}
rotatePressed(callback: Function) {
if (!this.canUseInput())
return;
if (this.tryRotateTetromino())
callback(this.getFinalGrid());
}
recoverNextPiece() {
this.currentObject = this.nextPieces.shift();
this.generateNextPieces();
}
generateNextPieces() {
while (this.nextPieces.length < this.nextPiecesCount) {
let shape = Math.floor(Math.random() * 7);
this.nextPieces.push(new Tetromino(shape, this.colors));
}
}
createTetromino() {
this.pressedOut();
this.recoverNextPiece();
if (!this.isTetrominoPositionValid())
this.endGame(false);
}
togglePause() {
if (!this.gameRunning)
return;
this.gamePaused = !this.gamePaused;
if (this.gamePaused) {
clearInterval(this.gameTickInterval);
clearInterval(this.gameTimeInterval);
} else {
this.gameTickInterval = setInterval(this.onTick, this.gameTick);
this.gameTimeInterval = setInterval(this.onClock, 1000);
}
}
endGame(isRestart: boolean) {
this.gameRunning = false;
this.gamePaused = false;
clearInterval(this.gameTickInterval);
clearInterval(this.gameTimeInterval);
this.endCallback(this.gameTime, this.score, isRestart);
}
startGame(tickCallback: Function, clockCallback: Function, endCallback: Function) {
if (this.gameRunning)
this.endGame(true);
this.gameRunning = true;
this.gamePaused = false;
this.gameTime = 0;
this.score = 0;
this.level = 0;
this.levelProgression = 0;
this.gameTick = GameLogic.levelTicks[this.level];
this.currentGrid = this.getEmptyGrid(this.getHeight(), this.getWidth());
this.nextPieces = [];
this.generateNextPieces();
this.createTetromino();
tickCallback(this.score, this.level, this.getFinalGrid());
clockCallback(this.gameTime);
this.onTick = this.onTick.bind(this, tickCallback);
this.onClock = this.onClock.bind(this, clockCallback);
this.gameTickInterval = setInterval(this.onTick, this.gameTick);
this.gameTimeInterval = setInterval(this.onClock, 1000);
this.endCallback = endCallback;
}
}

View file

@ -0,0 +1,303 @@
// @flow
import * as React from 'react';
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";
import Preview from "./components/Preview";
type Props = {
navigation: Object,
}
type State = {
grid: Array<Array<Object>>,
gameRunning: boolean,
gameTime: number,
gameScore: number,
gameLevel: number,
}
class TetrisScreen extends React.Component<Props, State> {
colors: Object;
logic: GameLogic;
onTick: Function;
onClock: Function;
onGameEnd: Function;
updateGrid: Function;
updateGridScore: Function;
constructor(props) {
super(props);
this.colors = props.theme.colors;
this.logic = new GameLogic(20, 10, this.colors);
this.state = {
grid: this.logic.getEmptyGrid(this.logic.getHeight(), this.logic.getWidth()),
gameRunning: false,
gameTime: 0,
gameScore: 0,
gameLevel: 0,
};
this.onTick = this.onTick.bind(this);
this.onClock = this.onClock.bind(this);
this.onGameEnd = this.onGameEnd.bind(this);
this.updateGrid = this.updateGrid.bind(this);
this.updateGridScore = this.updateGridScore.bind(this);
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 (
<View
style={{
flexDirection: 'row',
}}>
<HeaderButton icon={'pause'} onPress={() => this.togglePause()}/>
</View>
);
}
/**
* Remove any interval on un-focus
*/
onScreenBlur() {
if (!this.logic.isGamePaused())
this.logic.togglePause();
}
onScreenFocus() {
if (!this.logic.isGameRunning())
this.startGame();
else if (this.logic.isGamePaused())
this.showPausePopup();
}
getFormattedTime(seconds: number) {
let date = new Date();
date.setHours(0);
date.setMinutes(0);
date.setSeconds(seconds);
let format;
if (date.getHours())
format = date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds();
else if (date.getMinutes())
format = date.getMinutes() + ':' + date.getSeconds();
else
format = date.getSeconds();
return format;
}
onTick(score: number, level: number, newGrid: Array<Array<Object>>) {
this.setState({
gameScore: score,
gameLevel: level,
grid: newGrid,
});
}
onClock(time: number) {
this.setState({
gameTime: time,
});
}
updateGrid(newGrid: Array<Array<Object>>) {
this.setState({
grid: newGrid,
});
}
updateGridScore(newGrid: Array<Array<Object>>, score: number) {
this.setState({
grid: newGrid,
gameScore: score,
});
}
togglePause() {
this.logic.togglePause();
if (this.logic.isGamePaused())
this.showPausePopup();
}
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() {
let message = 'SCORE: ' + this.state.gameScore + '\n';
message += 'LEVEL: ' + this.state.gameLevel + '\n';
message += 'TIME: ' + this.getFormattedTime(this.state.gameTime) + '\n';
Alert.alert(
'GAME OVER',
message,
[
{text: 'LEAVE', onPress: () => this.props.navigation.goBack()},
{text: 'RESTART', onPress: () => this.startGame()},
],
{cancelable: false},
);
}
startGame() {
this.logic.startGame(this.onTick, this.onClock, 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() {
return (
<View style={{
width: '100%',
height: '100%',
}}>
<View style={{
flexDirection: 'row',
position: 'absolute',
top: 5,
left: 10,
}}>
<MaterialCommunityIcons
name={'timer'}
color={this.colors.subtitle}
size={20}/>
<Text style={{
marginLeft: 5,
color: this.colors.subtitle
}}>{this.getFormattedTime(this.state.gameTime)}</Text>
</View>
<View style={{
flexDirection: 'row',
position: 'absolute',
top: 50,
left: 10,
}}>
<MaterialCommunityIcons
name={'gamepad'}
color={this.colors.text}
size={20}/>
<Text style={{
marginLeft: 5
}}>{this.state.gameLevel}</Text>
</View>
<View style={{
flexDirection: 'row',
marginRight: 'auto',
marginLeft: 'auto',
}}>
<MaterialCommunityIcons
name={'star'}
color={this.colors.tetrisScore}
size={30}/>
<Text style={{
marginLeft: 5,
fontSize: 22,
}}>{this.state.gameScore}</Text>
</View>
<Grid
width={this.logic.getWidth()}
height={this.logic.getHeight()}
containerMaxHeight={'80%'}
containerMaxWidth={'60%'}
grid={this.state.grid}
backgroundColor={this.colors.tetrisBackground}
/>
<View style={{
position: 'absolute',
top: 50,
right: 5,
}}>
<Preview
next={this.logic.getNextPieces()}
/>
</View>
<View style={{
position: 'absolute',
bottom: 0,
flexDirection: 'row',
width: '100%',
}}>
<IconButton
icon="format-rotate-90"
size={40}
onPress={() => this.logic.rotatePressed(this.updateGrid)}
style={{marginRight: 'auto'}}
/>
<View style={{
flexDirection: 'row',
}}>
<IconButton
icon="arrow-left"
size={40}
onPress={() => this.logic.pressedOut()}
onPressIn={() => this.logic.leftPressedIn(this.updateGrid)}
/>
<IconButton
icon="arrow-right"
size={40}
onPress={() => this.logic.pressedOut()}
onPressIn={() => this.logic.rightPressed(this.updateGrid)}
/>
</View>
<IconButton
icon="arrow-down"
size={40}
onPressIn={() => this.logic.downPressedIn(this.updateGridScore)}
onPress={() => this.logic.pressedOut()}
style={{marginLeft: 'auto'}}
color={this.colors.tetrisScore}
/>
</View>
</View>
);
}
}
export default withTheme(TetrisScreen);

228
screens/Tetris/Tetromino.js Normal file
View file

@ -0,0 +1,228 @@
export default class Tetromino {
static types = {
'I': 0,
'O': 1,
'T': 2,
'S': 3,
'Z': 4,
'J': 5,
'L': 6,
};
static shapes = [
[
[
[0, 0, 0, 0],
[1, 1, 1, 1],
[0, 0, 0, 0],
[0, 0, 0, 0],
],
[
[1, 1],
[1, 1],
],
[
[0, 1, 0],
[1, 1, 1],
[0, 0, 0],
],
[
[0, 1, 1],
[1, 1, 0],
[0, 0, 0],
],
[
[1, 1, 0],
[0, 1, 1],
[0, 0, 0],
],
[
[1, 0, 0],
[1, 1, 1],
[0, 0, 0],
],
[
[0, 0, 1],
[1, 1, 1],
[0, 0, 0],
],
],
[
[
[0, 0, 1, 0],
[0, 0, 1, 0],
[0, 0, 1, 0],
[0, 0, 1, 0],
],
[
[1, 1],
[1, 1],
],
[
[0, 1, 0],
[0, 1, 1],
[0, 1, 0],
],
[
[0, 1, 0],
[0, 1, 1],
[0, 0, 1],
],
[
[0, 0, 1],
[0, 1, 1],
[0, 1, 0],
],
[
[0, 1, 1],
[0, 1, 0],
[0, 1, 0],
],
[
[0, 1, 0],
[0, 1, 0],
[0, 1, 1],
],
],
[
[
[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 1, 1, 1],
[0, 0, 0, 0],
],
[
[1, 1],
[1, 1],
],
[
[0, 0, 0],
[1, 1, 1],
[0, 1, 0],
],
[
[0, 0, 0],
[0, 1, 1],
[1, 1, 0],
],
[
[0, 0, 0],
[1, 1, 0],
[0, 1, 1],
],
[
[0, 0, 0],
[1, 1, 1],
[0, 0, 1],
],
[
[0, 0, 0],
[1, 1, 1],
[1, 0, 0],
],
],
[
[
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0],
],
[
[1, 1],
[1, 1],
],
[
[0, 1, 0],
[1, 1, 0],
[0, 1, 0],
],
[
[1, 0, 0],
[1, 1, 0],
[0, 1, 0],
],
[
[0, 1, 0],
[1, 1, 0],
[1, 0, 0],
],
[
[0, 1, 0],
[0, 1, 0],
[1, 1, 0],
],
[
[1, 1, 0],
[0, 1, 0],
[0, 1, 0],
],
],
];
static colors: Object;
currentType: Object;
currentShape: Object;
currentRotation: number;
position: Object;
colors: Object;
constructor(type: Tetromino.types, colors: Object) {
this.currentType = type;
this.currentRotation = 0;
this.currentShape = Tetromino.shapes[this.currentRotation][type];
this.position = {x: 0, y: 0};
if (this.currentType === Tetromino.types.O)
this.position.x = 4;
else
this.position.x = 3;
this.colors = colors;
Tetromino.colors = {
0: colors.tetrisI,
1: colors.tetrisO,
2: colors.tetrisT,
3: colors.tetrisS,
4: colors.tetrisZ,
5: colors.tetrisJ,
6: colors.tetrisL,
};
}
getColor() {
return Tetromino.colors[this.currentType];
}
getCellsCoordinates(isAbsolute: boolean) {
let coordinates = [];
for (let row = 0; row < this.currentShape.length; row++) {
for (let col = 0; col < this.currentShape[row].length; col++) {
if (this.currentShape[row][col] === 1)
if (isAbsolute)
coordinates.push({x: this.position.x + col, y: this.position.y + row});
else
coordinates.push({x: col, y: row});
}
}
return coordinates;
}
rotate(isForward) {
if (isForward)
this.currentRotation++;
else
this.currentRotation--;
if (this.currentRotation > 3)
this.currentRotation = 0;
else if (this.currentRotation < 0)
this.currentRotation = 3;
this.currentShape = Tetromino.shapes[this.currentRotation][this.currentType];
}
move(x: number, y: number) {
this.position.x += x;
this.position.y += y;
}
}

View file

@ -0,0 +1,41 @@
// @flow
import * as React from 'react';
import {View} from 'react-native';
import {withTheme} from 'react-native-paper';
type Props = {
color: string,
isEmpty: boolean,
id: string,
}
class Cell extends React.PureComponent<Props> {
colors: Object;
constructor(props) {
super(props);
this.colors = props.theme.colors;
}
render() {
return (
<View
style={{
flex: 1,
backgroundColor: this.props.isEmpty ? 'transparent' : this.props.color,
borderColor: this.props.isEmpty ? 'transparent' : this.colors.tetrisBorder,
borderStyle: 'solid',
borderRadius: 2,
borderWidth: 1,
aspectRatio: 1,
}}
/>
);
}
}
export default withTheme(Cell);

View file

@ -0,0 +1,68 @@
// @flow
import * as React from 'react';
import {View} from 'react-native';
import {withTheme} from 'react-native-paper';
import Cell from "./Cell";
type Props = {
navigation: Object,
grid: Array<Array<Object>>,
backgroundColor: string,
height: number,
width: number,
containerMaxHeight: number|string,
containerMaxWidth: number|string,
}
class Grid extends React.Component<Props>{
colors: Object;
constructor(props) {
super(props);
this.colors = props.theme.colors;
}
getRow(rowNumber: number) {
let cells = [];
for (let i = 0; i < this.props.width; i++) {
let cell = this.props.grid[rowNumber][i];
let key = rowNumber + ':' + i;
cells.push(<Cell color={cell.color} isEmpty={cell.isEmpty} id={key}/>);
}
return(
<View style={{
flexDirection: 'row',
backgroundColor: this.props.backgroundColor,
}}>
{cells}
</View>
);
}
getGrid() {
let rows = [];
for (let i = 0; i < this.props.height; i++) {
rows.push(this.getRow(i));
}
return rows;
}
render() {
return (
<View style={{
flexDirection: 'column',
maxWidth: this.props.containerMaxWidth,
maxHeight: this.props.containerMaxHeight,
aspectRatio: this.props.width/this.props.height,
marginLeft: 'auto',
marginRight: 'auto',
}}>
{this.getGrid()}
</View>
);
}
}
export default withTheme(Grid);

View file

@ -0,0 +1,52 @@
// @flow
import * as React from 'react';
import {View} from 'react-native';
import {withTheme} from 'react-native-paper';
import Grid from "./Grid";
type Props = {
next: Object,
}
class Preview extends React.PureComponent<Props> {
colors: Object;
constructor(props) {
super(props);
this.colors = props.theme.colors;
}
getGrids() {
let grids = [];
for (let i = 0; i < this.props.next.length; i++) {
grids.push(
<Grid
width={this.props.next[i][0].length}
height={this.props.next[i].length}
grid={this.props.next[i]}
containerMaxHeight={50}
containerMaxWidth={50}
backgroundColor={'transparent'}
/>
);
}
return grids;
}
render() {
if (this.props.next.length > 0) {
return (
<View>
{this.getGrids()}
</View>
);
} else
return null;
}
}
export default withTheme(Preview);

View file

@ -54,6 +54,18 @@ export default class ThemeManager {
proxiwashColor: '#1fa5ee',
menuColor: '#e91314',
tutorinsaColor: '#f93943',
// Tetris
tetrisBackground:'#e6e6e6',
tetrisBorder:'#2f2f2f',
tetrisScore:'#e2bd33',
tetrisI : '#3cd9e6',
tetrisO : '#ffdd00',
tetrisT : '#a716e5',
tetrisS : '#09c528',
tetrisZ : '#ff0009',
tetrisJ : '#2a67e3',
tetrisL : '#da742d',
},
};
}
@ -94,6 +106,18 @@ export default class ThemeManager {
proxiwashColor: '#1fa5ee',
menuColor: '#b81213',
tutorinsaColor: '#f93943',
// Tetris
tetrisBackground:'#2c2c2c',
tetrisBorder:'#1b1b1b',
tetrisScore:'#e2d707',
tetrisI : '#30b3be',
tetrisO : '#c1a700',
tetrisT : '#9114c7',
tetrisS : '#08a121',
tetrisZ : '#b50008',
tetrisJ : '#0f37b9',
tetrisL : '#b96226',
},
};
}