The Board and the Grid
Tetris is a perfect second mobile project after the React-Expo course: it's built almost entirely from things you already know (state, useEffect, View/Text, touch handlers), just combined into a real-time game loop. This course builds the classic version from scratch: a 10x20 board, all 7 tetrominoes with rotation, gravity, touch controls, line-clearing, and scoring.
Modeling the board
Classic Tetris uses a board 10 columns wide and 20 rows tall. Just like the Match-3 course's approach to a game board, keep it as plain data first, completely separate from how it's drawn:
const BOARD_WIDTH = 10;
const BOARD_HEIGHT = 20;
function createEmptyBoard(): number[][] {
return Array.from({ length: BOARD_HEIGHT }, () => Array(BOARD_WIDTH).fill(0));
}
Each cell is a number: 0 means empty, any other value (1-7) identifies which tetromino color locked into that cell. Array.from({ length: BOARD_HEIGHT }, () => Array(BOARD_WIDTH).fill(0)) is important here, not Array(BOARD_HEIGHT).fill(Array(BOARD_WIDTH).fill(0)), the latter would make every row the same array reference, mutating one row would silently mutate all of them. Array.from calls its callback fresh for every row, giving each one its own independent array.
Rendering the grid
import { View, StyleSheet } from 'react-native';
const CELL_SIZE = 16;
function Board({ board }: { board: number[][] }) {
return (
<View style={styles.board}>
{board.map((row, r) => (
<View key={r} style={styles.row}>
{row.map((cell, c) => (
<View key={c} style={[styles.cell, cell !== 0 && styles.filled]} />
))}
</View>
))}
</View>
);
}
const styles = StyleSheet.create({
board: { borderWidth: 2, borderColor: '#334155' },
row: { flexDirection: 'row' },
cell: { width: CELL_SIZE, height: CELL_SIZE, borderWidth: 0.5, borderColor: '#1e293b' },
filled: { backgroundColor: '#38bdf8' },
});
Each row is a <View> with flexDirection: 'row', stacked vertically by the outer <View>'s default flexDirection: 'column', exactly how a 2D array naturally maps to nested flexbox rows and columns. cell !== 0 && styles.filled is a common React Native idiom: an array of styles where false entries are simply ignored, so a cell only gets the filled background when it's actually occupied.
NOTE
This course builds on the React-Expo course's setup (npx create-expo-app, running on a device with Expo Go), and needs a real Expo project to run, it can't run in this course's browser sandbox. Treat it like the Kivy or Pygame projects: read, understand, and run the code on your own machine.