Skip to content

Instantly share code, notes, and snippets.

@rppowell-lasfs
Last active March 23, 2021 14:38
Show Gist options
  • Save rppowell-lasfs/3f449ac49d529d5dc1926ebc8480775a to your computer and use it in GitHub Desktop.
Save rppowell-lasfs/3f449ac49d529d5dc1926ebc8480775a to your computer and use it in GitHub Desktop.
Android Tic Tac Toe Game Notes

Android TicTacToe Game

These are notes on making a TicTacToe game on Android.

Based on:

Initial App

Create Android App with Empty Activity

Creating the GameEngine

Create the GameEngine class. The GameEngine class will store the board as elements of 3x3 grid in an array and will contain a boolean indicating if the game is ended.

The play method will let you set the mark of the currentPlayer on the board at a given (x,y) position.

package com.learning.tictactoegame;

import java.util.Random;

public class GameEngine {
    private static final Random RANDOM = new Random();
    private char [] elements;
    private char currentPlayer;
    private boolean ended;

    public GameEngine() {
        elements = new char[9];
        newGame();
    }

    public boolean isEnded() {
        return ended;
    }

    public char play(int x, int y) {
        if(!ended && elements[3*y+x] == ' ') {
            elements[3*y+x] = currentPlayer;
            changePlayer();
        }
        return checkEnd();
    }

    public void changePlayer() {
        currentPlayer = (currentPlayer == 'X' ? 'O' : 'X');
    }

    public char getElement(int x, int y) {
        return elements[3*y+x];
    }

    public void newGame() {
        for(int i = 0; i < elements.length; i++) {
            elements[i] = ' ';
        }
        currentPlayer = 'X';
        ended = false;
    }

    public char checkEnd() {
        for (int i = 0; i < 3; i++) {
            // columns
            if (getElement(i, 0) != ' ' &&
                    getElement(i, 0) == getElement(i, 1) &&
                    getElement(i, 1) == getElement(i, 2)) {
                ended = true;
                return getElement(i, 0);
            }
            // rows
            if (getElement(0, i) != ' ' &&
                    getElement(0, i) == getElement(1, i) &&
                    getElement(1, i) == getElement(2, i)) {
                ended = true;
                return getElement(0, i);
            }
        }
        // backslash
        if (getElement(0, 0) != ' ' &&
                getElement(0, 0) == getElement(1, 1) &&
                getElement(1, 1) == getElement(2, 2)) {
            ended = true;
            return getElement(0, 0);
        }
        // slash
        if (getElement(2, 0) != ' ' &&
                getElement(2, 0) == getElement(1, 1) &&
                getElement(1, 1) == getElement(0, 2)) {
            ended = true;
            return getElement(2, 0);
        }
        // tie
        for (int i = 0; i < 9; i++) {
            if (elements[i] == ' ') {
                return ' ';
            }
        }
        return 'T';
    }

    public char computer() {
        if (!ended) {
            int position = -1;
            do {
                position = RANDOM.nextInt(9);
            } while (elements[position] != ' ');
            elements[position] = currentPlayer;
            changePlayer();
        }
        return checkEnd();
    }
}

Create BoardView.class based off of View. Create drawGrid() and drawBoard() and paint game elements. Use onTouchEvent with MotionEvent.ACTION_DOWN to determine user input.

package com.learning.tictactoegame;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class BoardView extends View {
    private static final int LINE_THICK = 5;
    private static final int ELEMENT_MARGIN = 20;
    private static final int ELEMENT_STROKE_WIDTH = 15;
    private int width, height, elementWidth, elementHeight;
    private Paint gridPaint, oPaint, xPaint;
    private GameEngine gameEngine;
    private MainActivity activity;

    public BoardView(Context context) {
        super(context);
    }

    public BoardView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        gridPaint = new Paint();
        oPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        oPaint.setColor(Color.RED);
        oPaint.setStyle(Paint.Style.STROKE);
        oPaint.setStrokeWidth(ELEMENT_STROKE_WIDTH);
        xPaint = new Paint(oPaint);
        xPaint.setColor(Color.BLUE);
    }

    public void setMainActivity(MainActivity a) {
        activity = a;
    }

    public void setGameEngine(GameEngine g) {
        gameEngine = g;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        width = View.MeasureSpec.getSize(widthMeasureSpec);
        height = View.MeasureSpec.getSize(heightMeasureSpec);
        elementWidth = (width - LINE_THICK) / 3;
        elementHeight = (height - LINE_THICK) / 3;

        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        drawGrid(canvas);
        drawBoard(canvas);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(!gameEngine.isEnded() && event.getAction() == MotionEvent.ACTION_DOWN) {
            int x = (int) (event.getX() / elementWidth);
            int y = (int) (event.getY() / elementHeight);
            char win = gameEngine.play(x, y);
            invalidate();

            if (win != ' ') {
                activity.gameEnded(win);
            } else {
                // computer plays...
                win = gameEngine.computer();
                invalidate();

                if (win != ' ') {
                    activity.gameEnded(win);
                }
            }
        }
        return super.onTouchEvent(event);
    }

    private void drawBoard(Canvas canvas) {
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                drawElement(canvas, gameEngine.getElement(i, j), i, j);
            }
        }
    }

    private void drawGrid(Canvas canvas) {
        for (int i = 0; i < 2; i++) {
            // vertical lines
            float left = elementWidth * (i + 1);
            float right = left + LINE_THICK;
            float top = 0;
            float bottom = height;

            canvas.drawRect(left, top, right, bottom, gridPaint);

            //horizintal lines
            float left2 = 0;
            float right2 = width;
            float top2 = elementHeight * (i + 1);
            float bottom2 = top2 + LINE_THICK;

            canvas.drawRect(left2, top2, right2, bottom2, gridPaint);
        }
    }

    private void drawElement(Canvas canvas, char c, int x, int y) {
        if (c == 'O') {
            float cx = (elementWidth * x) + elementWidth / 2;
            float cy = (elementHeight * y) + elementHeight / 2;

            canvas.drawCircle(cx, cy, Math.min(elementWidth, elementHeight) / 2 - ELEMENT_MARGIN * 2, oPaint);
        } else if (c == 'X') {
            float startX = (elementWidth * x) + ELEMENT_MARGIN;
            float startY = (elementHeight * y) + ELEMENT_MARGIN;
            float endX = startX + elementWidth - ELEMENT_MARGIN * 2;
            float endY = startY + elementHeight - ELEMENT_MARGIN;

            canvas.drawLine(startX, startY, endX, endY, xPaint);

            float startX2 = (elementWidth * (x + 1)) - ELEMENT_MARGIN;
            float startY2 = (elementHeight * y) + ELEMENT_MARGIN;
            float endX2 = startX2 - elementWidth + ELEMENT_MARGIN * 2;
            float endY2 = startY2 + elementHeight - ELEMENT_MARGIN;

            canvas.drawLine(startX2, startY2, endX2, endY2, xPaint);
        }
    }
}

Use the BoardView in res/layout/activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.learning.tictactoegame.MainActivity">

    <com.learning.tictactoegame.BoardView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/board"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />

</RelativeLayout>

Add BoardView to MainActivity:

package com.learning.tictactoegame;

import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    private BoardView boardView;
    private GameEngine gameEngine;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        boardView = (BoardView) findViewById(R.id.board);

        gameEngine = new GameEngine();
        boardView.setGameEngine(gameEngine);
        boardView.setMainActivity(this);

    }

    public void gameEnded(char c) {
        String msg = (c == 'T') ? "Game Ended in a Tie" : "Game Ended - " + c + " is the winner";
        new AlertDialog.Builder(this).setTitle("Tic Tac Toe")
        .setMessage(msg)
        .setOnDismissListener(new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialog) {
                newGame();
            }
        })
        .show();
    }

    private void newGame() {
        gameEngine.newGame();
        boardView.invalidate();
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment