Build a two-player Tic Tac Toe game with a score board. A fun project that teaches game logic: taking turns, checking for a winner and resetting the board.
In Android Studio choose New Project β Empty Views Activity, with Language Java and Minimum SDK API 24. Nothing extra to add to Gradle.
2. Design the board
In activity_main.xml, put two score boxes at the top, a status line ("X's turn"), then a GridLayout with columnCount="3" holding nine Buttons. Give all nine the same Cell style from themes.xml, so you write the size, corners and text size once instead of nine times.
3. Keep the rules in their own class
Game knows nothing about Android. It holds the board as an array of 9 numbers: 0 empty, 1 X, 2 O. Keeping the rules separate makes them easy to read and test.
4. Whose turn is it?
X always starts, so if both players have the same number of marks it's X's turn, otherwise O's. Working it out from the board means there's no extra variable to get out of step.
5. Find the winner
There are only 8 ways to win: 3 rows, 3 columns and 2 diagonals. winningLine() checks each one: if all three cells hold the same mark, that player won. If no one won and no cell is empty, it's a draw.
6. Connect the cells
In MainActivity, loop over the grid's children: board.getChildAt(i) is cell i. Each tap calls game.play(i), which refuses taken cells and finished games.
7. Draw the screen from the game
render() sets every cell's text and colour from the game, highlights the winning three, and updates the status and scores. Every change goes through this one method, so the screen always matches the game.
8. Survive rotation
Rotating the phone recreates the activity. Save the board and scores in onSaveInstanceState and restore them in onCreate.
9. Run it
Play a few rounds, rotate the phone mid-game, then tap Reset scores.
Make it yours
Let O start every other round.
Add a computer player: pick a random empty cell, or block X when it has two in a row.
What's improved over the video
The rules live in their own small class, the winning line lights up, draws are detected, and the game survives rotation.
Files affected
Check what to add and what to replace before copying code.
Connects the nine cells to the game, shows whose turn it is, and keeps the score.
MainActivity.java
packagecom.example.tictactoe;importandroid.content.res.ColorStateList;importandroid.graphics.Color;importandroid.os.Bundle;importandroid.widget.Button;importandroid.widget.GridLayout;importandroid.widget.TextView;importandroidx.appcompat.app.AppCompatActivity;importandroidx.core.content.ContextCompat;/**
* The game screen: draws the board from Game, passes taps to it, and keeps the score.
* The board, score and turn survive rotating the phone.
*/publicclassMainActivityextendsAppCompatActivity{privateGame game;privateint scoreX, scoreO;privatefinalButton[] cells =newButton[9];privateTextView status, textScoreX, textScoreO;@OverrideprotectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);Screens.setUp(this,R.layout.activity_main);
status =findViewById(R.id.textStatus);
textScoreX =findViewById(R.id.textScoreX);
textScoreO =findViewById(R.id.textScoreO);// The nine cells are the children of the GridLayout, in order 0 to 8.GridLayout board =findViewById(R.id.board);for(int i =0; i < cells.length; i++){int index = i;
cells[i]=(Button) board.getChildAt(i);
cells[i].setOnClickListener(v ->play(index));}findViewById(R.id.btnPlayAgain).setOnClickListener(v ->newRound());findViewById(R.id.btnReset).setOnClickListener(v ->{
scoreX =0;
scoreO =0;newRound();});if(savedInstanceState ==null){
game =newGame();}else{
game =newGame(savedInstanceState.getIntArray("cells"));
scoreX = savedInstanceState.getInt("scoreX");
scoreO = savedInstanceState.getInt("scoreO");}render();}@OverrideprotectedvoidonSaveInstanceState(Bundle outState){super.onSaveInstanceState(outState);
outState.putIntArray("cells", game.cells());
outState.putInt("scoreX", scoreX);
outState.putInt("scoreO", scoreO);}privatevoidplay(int index){if(!game.play(index))return;if(game.winner()==Game.X) scoreX++;elseif(game.winner()==Game.O) scoreO++;render();}privatevoidnewRound(){
game =newGame();render();}/** Makes the screen match the game: marks, colours, highlighted winning line, status and score. */privatevoidrender(){int[] line = game.winningLine();for(int i =0; i < cells.length; i++){int mark = game.cell(i);
cells[i].setText(mark ==Game.X?"X": mark ==Game.O?"O":"");
cells[i].setTextColor(colour(mark));boolean winning = line !=null&&(i == line[0]|| i == line[1]|| i == line[2]);
cells[i].setBackgroundTintList(ColorStateList.valueOf(
winning ?ContextCompat.getColor(this,R.color.winning_cell):Color.TRANSPARENT));}String player = game.turn()==Game.X?"X":"O";if(game.winner()!=Game.EMPTY){
status.setText(getString(R.string.status_win, game.winner()==Game.X?"X":"O"));}elseif(game.isDraw()){
status.setText(R.string.status_draw);}else{
status.setText(getString(R.string.status_turn, player));}
textScoreX.setText(getString(R.string.score,"X", scoreX));
textScoreO.setText(getString(R.string.score,"O", scoreO));}privateintcolour(int mark){returnContextCompat.getColor(this, mark ==Game.O?R.color.player_o :R.color.player_x);}}