Page 1 of 1

2048 Game in Java Swing Rate Topic: -----

#1 snoopy11   User is online

  • Engineering ● Software
  • member icon

Reputation: 1547
  • View blog
  • Posts: 4,918
  • Joined: 20-March 10

Posted 03 November 2018 - 12:43 AM

2048 Game in Java Swing.

Recently in the Java Forum, I came across a Question about a Game I never heard of, The question was about how to check moves and slide the relevant tiles across in a game of 2048. Never having heard of the game my answer was pretty terrible as I had not fully understood the problem.

The game is played in a 4 by 4 grid, and you start the game with two initial tiles these can be 4 or 2 and are picked at random after each move by the user a random 4 or 2 tile will appear in an empty place until no more tiles can be placed or you reach the magical 2048 tile.

To get to the 2048 tile you combine tiles of the same value ie 4 can be combined with another 4 to make 8, two 8's make 16, two 16's make 32 all the way up to 2048.

The Game was invented by Italian web designer, Gabriele Cirulli over a weekend and got over 4 million hits in a few days it became an internet sensation, even more, puzzling that I had never heard of it.

So I played it a couple of times and was fascinated with it and the simple rules to the point I decided to make my own version which took me a day of programming on and off in Java Swing.

I am known more in the C and C++ forum so this is actually my first Java Tutorial, my Java is not that great, I'm ok and get by in Java but no more than that, but I feel its time to spread my Java wings as it were.

Here is a screenshot of the game we will make.
Posted Image

Now the source code is freely available to look at but decided just to make my own version without looking at the original code.

So my code may actually be quite verbose.

But onwards the program package is called twentyfortyeight

The program is split into six classes
Grid.java
keyBoard.java
scoreBoard.java
aTile.java
Matrix.java
and the main class
Twentyfortyeight.java

first I will discuss Grid.java

 /*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package twentyfortyeight;

import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;

/**
 *
 * @author snoopy
 */
public final class Grid extends JPanel {

    Grid() {
        update();
    }

    public void update() {
        this.repaint();
    }

    @Override
    public void paintComponent(Graphics g) {
        g.setColor(Color.BLACK);
        g.drawRect(0, 0, 160, 160);
        g.drawLine(40, 0, 40, 160);
        g.drawLine(80, 0, 80, 160);
        g.drawLine(120, 0, 120, 160);
        g.drawLine(0, 40, 160, 40);
        g.drawLine(0, 80, 160, 80);
        g.drawLine(0, 120, 160, 120);
    }

}




Its really short and just draws a 4by4 Grid on a JPanel. each grid cell is 40 by 40 px.

next keyBoard.java

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package twentyfortyeight;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author snoopy
 */
public class keyBoard implements KeyListener {

    Matrix mat;
    scoreBoard board;

    keyBoard(Matrix myMatrix, scoreBoard score) {
        mat = myMatrix;
        board = score;
    }

    private void doGameOver() {
        if (mat.game_Over) {
            board.showGameOver = true;
        } else if(mat.game_Won) {
            board.showGameWon = true;
        }

        board.Update();

    }

    @Override
    public void keyTyped(KeyEvent e) {

    }

    @Override
    public void keyPressed(KeyEvent e) {
        int c = e.getKeyCode();
        switch (c) {
            case KeyEvent.VK_UP:
                if (!mat.game_Over) {
                    board.Update();
                    mat.slide_Up();
                    if(mat.game_Won){
                        doGameOver();
                    }
                } else {
                    doGameOver();
                }
                break;
            case KeyEvent.VK_DOWN:
                if (!mat.game_Over) {
                    board.Update();
                    mat.slide_Down();
                    if(mat.game_Won){
                        doGameOver();
                    }
                } else {
                    doGameOver();
                }
                break;
            case KeyEvent.VK_LEFT: {
                if (!mat.game_Over) {
                    board.Update();
                    try {
                        mat.slide_Left();
                        if(mat.game_Won){
                            doGameOver();
                        }
                    } catch (InterruptedException ex) {
                        Logger.getLogger(keyBoard.class.getName()).log(Level.SEVERE, null, ex);
                    }
                } else {
                    doGameOver();
                }
            }

            break;
            case KeyEvent.VK_RIGHT:
                if (!mat.game_Over) {
                    board.Update();
                    mat.slide_Right();
                    if(mat.game_Won){
                        doGameOver();
                    }
                } else {
                    doGameOver();
                }
                break;
            default:
                break;
        }

    }

    @Override
    public void keyReleased(KeyEvent e) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

}




It takes a Matrix object and a scoreBoard object in its constructor, this is to facilitate changes to the game board and the scoreboard.
It responds to the four arrow keys calling functions inside of Matrix to update gameplay.

Next the scoreBoard class,

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package twentyfortyeight;

import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;

/**
 *
 * @author snoopy
 */
public class scoreBoard extends JPanel {

    Matrix matrix;
    public boolean showGameOver = false;
    public boolean showGameWon = false;

    scoreBoard(Matrix mat) {
        matrix = mat;
       
    }

    @Override
    public void paintComponent(Graphics g) {
        g.setColor(Color.YELLOW);

        g.fillRoundRect(280, 100, 120, 120, 15, 15);
        g.setFont(new Font("Arial", Font.PLAIN, 48));
        g.setColor(Color.WHITE);
        g.drawString("2048", 284, 180);

        g.setColor(Color.DARK_GRAY);

        g.fillRoundRect(280, 25, 120, 40, 15, 15);
        g.setFont(new Font("Arial", Font.BOLD, 14));
        g.setColor(Color.WHITE);
        g.drawString("SCORE: ", 284, 38);
        g.drawString(matrix.getScore(), 284, 54);

        if (showGameOver) {
            g.setColor(Color.BLACK);
            g.setFont(new Font("Arial", Font.BOLD, 64));
            g.drawString("GAME OVER", 30, 240);

        }
        if (showGameWon) {
            g.setColor(Color.BLACK);
            g.setFont(new Font("Arial", Font.BOLD, 64));
            g.drawString("YOU WON!!", 30, 240);

        }
    }

    public void Update() {
        repaint();
    }
}




Basically just shows the current score, also shows game over or game won signs as appropriate.

next aTile.java

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package twentyfortyeight;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JPanel;
import javax.swing.Timer;

/**
 *
 * @author snoopy
 */
public class aTile extends JPanel implements ActionListener {

    private int _x = 0;
    private int _y = 0;
    private int tile_value = 0;
    private boolean tile = false;
    private int font_offset_x = 14;
    private boolean tile_glow = false;
    private int counter = 0;
    private final Timer timer = new Timer(250, this);

    aTile(int x, int y) throws InterruptedException {

        _x = x;
        _y = y;

    }

    public void initTile() throws InterruptedException {

        int rand = (int) (Math.random() * 2);
        if (rand == 0) {
            drawTile2();
        } else {
            drawTile4();
        }
        timer.setRepeats(true);
        timer.start();
    }

    private void drawTile2() throws InterruptedException {

        tile = true;
        tile_value = 2;
        repaint();
    }

    private void drawTile4() throws InterruptedException {

        tile = true;
        tile_value = 4;
        repaint();
    }

    @Override
    public void paintComponent(Graphics g) {

        if (tile && !tile_glow && tile_value != 0) {

            g.setColor(Color.BLACK);
            g.drawRect(_x, _y, 40, 40);

            if (tile_value != 0) {
                g.setColor(getTileColor(tile_value));
            } else {
                g.setColor(new Color(238, 238, 238));
            }
            g.fillRect(_x + 2, _y + 2, 37, 37);
            g.setColor(Color.WHITE);
            g.setFont(setFontOnValue(tile_value));
            if (tile_value != 0) {
                g.drawString(String.valueOf(tile_value), _x + font_offset_x, _y + 27);
            }

        }

        if (tile_glow) {
            /*g.setColor(Color.BLACK);
                g.drawRect(_x, _y, 40, 40);*/
            g.setColor(Color.getHSBColor(40, 240, 128));

            g.fillRect(_x + 2, _y + 2, 37, 37);
            if (tile) {
                g.setColor(Color.WHITE);
                g.setFont(setFontOnValue(tile_value));
                if (tile_value != 0) {
                    g.drawString(String.valueOf(tile_value), _x + 14, _y + 27);
                }
            }
        }

    }

    void updateTile() {
        tile_glow = false;
        tile = true;
        this.repaint();
    }

    void setGlow(boolean value) {
        tile_glow = value;
        tile = true;
        timer.setRepeats(true);
        timer.start();

    }

    int getTileValue() {
        return tile_value;
    }

    void setTileValue(int val) {
        tile_value = val;
    }

    int getCurrentx() {
        return _x;
    }

    Color getTileColor(int value) {

        if (value == 8) {
            return new Color(255, 127, 39);
        }
        if (value == 16) {
            return new Color(128, 255, 0);
        }
        if (value == 32) {
            return new Color(255, 0, 0);
        }
        if (value == 64) {
            return new Color(0, 0, 255);
        }
        if (value == 128) {
            return new Color(255, 128, 128);
        }
        if (value == 256) {
            return new Color(128, 0, 255);
        }
        if (value == 512) {
            return new Color(128, 128, 0);
        }
        if (value == 1024) {
            return new Color(0, 255, 255);
        }
        if (value == 2048) {
            return new Color(255, 128, 0);
        }
        return Color.GRAY;
    }

    Font setFontOnValue(int value) {
        if (value > 8 && value < 128) {
            font_offset_x = 12;
            return new Font("Arial", Font.PLAIN, 18);

        }
        if (value >= 128 && value < 1024) {
            font_offset_x = 8;
            return new Font("Arial", Font.BOLD, 14);
        }
        if (value >= 1024) {
            font_offset_x = 2;
            return new Font("Arial", Font.BOLD, 14);
        }
        font_offset_x = 14;
        return new Font("Arial", Font.PLAIN, 20);
    }

    @Override
    public void actionPerformed(ActionEvent event) {
        if (event.getSource() == timer && tile_glow == false) {
            tile_glow = true;
            this.repaint();
        } else if (event.getSource() == timer && tile_glow == true) {
            tile_glow = false;
            this.repaint();
        }
        if (event.getSource() == timer) {
            counter++;
        }

        if (counter > 5) {
            timer.stop();
            counter = 0;
            tile_glow = false;
        }

    }
}




basically handles the graphical image of the tiles whilst the Matrix class holds the physical values of the board,
produces random 4 or 2 on request and the 'timer' is being used as a mechanism to put a flashing or glowing tile effect on the initial tile ie when it is first shown.

The main gameplay is all handled in the Matrix class however, which I will discuss in some detail here,

Matrix.java
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package twentyfortyeight;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author snoopy
 */
public final class Matrix {

    
   
    private final int target_score = 2048;
    private final int[][] matrix = new int[4][4];
    private int score = 0;
    private int row = 0;
    private int column = 0;
    public boolean game_Over = false;
    public boolean game_Won = false;
    private List<aTile> tiles = new ArrayList<>();

    Matrix(List<aTile> Tiles) {

        tiles = Tiles;
        init_matrix();
    }

    public void init_matrix() {
        for (column = 0; column < 4; column++) {
            for (row = 0; row < 4; row++) {
                matrix[row][column] = 0;
            }
        }
    }

    public void reset()
    {
        init_matrix();
        score =0;
        for (int i =0; i<16; i++)
        {
            tiles.get(i).setTileValue(0);
            tiles.get(i).updateTile();
        }
        
    }
    public String getScore() {
        return String.valueOf(score);
    }

    public void setMatrixValue(int row, int column, int value) {
        matrix[row][column] = value;
    }

    private void createNewTile() throws InterruptedException  {
        int number = (int) (Math.random() * 16);
        int get_row = number / 4;
        int get_column = number - (get_row * 4);
        while (this.matrix[get_row][get_column] != 0) {

            number = (int) (Math.random() * 16);
            get_row = number / 4;
            get_column = number - (get_row * 4);
        }

        tiles.get(number).initTile();
        this.setMatrixValue(get_row, get_column, tiles.get(number).getTileValue());
    }

    private boolean checkNewTile() {
        int number = 0;
        for (int i = 0; i < 4; i++) {

            for (int j = 0; j < 4; j++) {
                if (this.matrix[i][j] != 0) {

                    number++;
                }
            }
        }
        if (number >= 16) {
            return false;
        }

        return true;
    }

    public void slide_Left() throws InterruptedException {
        //Check each row and column

        for (int j = 0; j < 4; j++) {
            for (int i = 0; i < 4; i++) {
                check_move_left(i, 3, 2);
                check_move_left(i, 2, 1);
                check_move_left(i, 1, 0);

            }
        }
        if (checkNewTile()) {
            createNewTile();
        } else {
            game_Over = true;
        }

    }

    public void slide_Right() {
        //Check each row and column

        for (int j = 0; j < 4; j++) {
            for (int i = 0; i < 4; i++) {
                check_move_right(i, 0, 1);
                check_move_right(i, 1, 2);
                check_move_right(i, 2, 3);

            }
        }

        if (checkNewTile()) {
            try {
                createNewTile();
            } catch (InterruptedException ex) {
                Logger.getLogger(Matrix.class.getName()).log(Level.SEVERE, null, ex);
            }
        } else {
            game_Over = true;
        }

    }

    public void slide_Up() {
        //Check each row and column

        for (int j = 0; j < 4; j++) {
            for (int i = 0; i < 4; i++) {
                check_move_up(i, 3, 2);
                check_move_up(i, 2, 1);
                check_move_up(i, 1, 0);

            }
        }

        if (checkNewTile()) {
            try {
                createNewTile();
            } catch (InterruptedException ex) {
                Logger.getLogger(Matrix.class.getName()).log(Level.SEVERE, null, ex);
            }
        } else {
            game_Over = true;
        }

    }

    public void slide_Down() {
        //Check each row and column

        for (int j = 0; j < 4; j++) {
            for (int i = 0; i < 4; i++) {
                check_move_down(i, 0, 1);
                check_move_down(i, 1, 2);
                check_move_down(i, 2, 3);

            }
        }

        if (checkNewTile()) {
            try {
                createNewTile();
            } catch (InterruptedException ex) {
                Logger.getLogger(Matrix.class.getName()).log(Level.SEVERE, null, ex);
            }
        } else {
            game_Over = true;
        }

    }

    private void check_move_left(int row, int column1, int column2) {
        if (matrix[row][column1] != 0 && matrix[row][column2] != 0) {
            if (matrix[row][column1] == matrix[row][column2]) {

                score = score + matrix[row][column2] + matrix[row][column1];
                if((matrix[row][column2]+ matrix[row][column1])==target_score)
                {
                    game_Won = true;
                }
                matrix[row][column2] = matrix[row][column2] + matrix[row][column1];
                
                matrix[row][column1] = 0;
                tiles.get((row * 4) + column2).setTileValue(matrix[row][column2]);
                tiles.get((row * 4) + column1).setTileValue(matrix[row][column1]);

                tiles.get((row * 4) + column2).updateTile();
                tiles.get((row * 4) + column1).updateTile();
            }

        } else if (matrix[row][column1] != 0 && matrix[row][column2] == 0) {

            /*tiles.get((row * 4) + column1).setDesiredx((column2 * 40));
            tiles.get((row * 4) + column1).slide_Tile_Left();*/
            matrix[row][column2] = matrix[row][column1];

            matrix[row][column1] = 0;
            tiles.get((row * 4) + column1).setTileValue(matrix[row][column1]);
            tiles.get((row * 4) + column2).setTileValue(matrix[row][column2]);
            tiles.get((row * 4) + column1).updateTile();
            tiles.get((row * 4) + column2).updateTile();

        }
    }

    private void check_move_right(int row, int column1, int column2)  {
        if (matrix[row][column1] != 0 && matrix[row][column2] != 0) {
            if (matrix[row][column1] == matrix[row][column2]) {

                score = score + matrix[row][column2] + matrix[row][column1];
                if((matrix[row][column2]+ matrix[row][column1])==target_score)
                {
                    game_Won = true;
                }
                matrix[row][column2] = matrix[row][column2] + matrix[row][column1];
               
                matrix[row][column1] = 0;
                tiles.get((row * 4) + column2).setTileValue(matrix[row][column2]);
                tiles.get((row * 4) + column1).setTileValue(matrix[row][column1]);

                tiles.get((row * 4) + column2).updateTile();
                tiles.get((row * 4) + column1).updateTile();
            }

        } else if (matrix[row][column1] != 0 && matrix[row][column2] == 0) {

            
            matrix[row][column2] = matrix[row][column1];

            matrix[row][column1] = 0;
            tiles.get((row * 4) + column1).setTileValue(matrix[row][column1]);
            tiles.get((row * 4) + column2).setTileValue(matrix[row][column2]);
            tiles.get((row * 4) + column1).updateTile();
            tiles.get((row * 4) + column2).updateTile();

        }
    }

    private void check_move_up(int column, int row1, int row2)  {
        if (matrix[row1][column] != 0 && matrix[row2][column] != 0) {
            if (matrix[row1][column] == matrix[row2][column]) {

                score = score + matrix[row1][column] + matrix[row2][column];
                if((matrix[row2][column]+ matrix[row1][column])==target_score)
                {
                    game_Won = true;
                }
                matrix[row2][column] = matrix[row1][column] + matrix[row2][column];
                
                matrix[row1][column] = 0;
                tiles.get((row2 * 4) + column).setTileValue(matrix[row2][column]);
                tiles.get((row1 * 4) + column).setTileValue(matrix[row1][column]);

                tiles.get((row2 * 4) + column).updateTile();
                tiles.get((row1 * 4) + column).updateTile();
            }

        } else if (matrix[row1][column] != 0 && matrix[row2][column] == 0) {

            
            matrix[row2][column] = matrix[row1][column];

            matrix[row1][column] = 0;
            tiles.get((row1 * 4) + column).setTileValue(matrix[row1][column]);
            tiles.get((row2 * 4) + column).setTileValue(matrix[row2][column]);
            tiles.get((row1 * 4) + column).updateTile();
            tiles.get((row2 * 4) + column).updateTile();

        }
    }

    private void check_move_down(int column, int row1, int row2) {
        if (matrix[row1][column] != 0 && matrix[row2][column] != 0) {
            if (matrix[row1][column] == matrix[row2][column]) {

                score = score + matrix[row1][column] + matrix[row2][column];
                if((matrix[row2][column]+ matrix[row1][column])==target_score)
                {
                    game_Won = true;
                }
                matrix[row2][column] = matrix[row2][column] + matrix[row1][column];
                
                matrix[row1][column] = 0;
                tiles.get((row2 * 4) + column).setTileValue(matrix[row2][column]);
                tiles.get((row1 * 4) + column).setTileValue(matrix[row1][column]);

                tiles.get((row1 * 4) + column).updateTile();
                tiles.get((row2 * 4) + column).updateTile();
            }

        } else if (matrix[row1][column] != 0 && matrix[row2][column] == 0) {

           
            matrix[row2][column] = matrix[row1][column];

            matrix[row1][column] = 0;
            tiles.get((row2 * 4) + column).setTileValue(matrix[row2][column]);
            tiles.get((row1 * 4) + column).setTileValue(matrix[row1][column]);
            tiles.get((row2 * 4) + column).updateTile();
            tiles.get((row1 * 4) + column).updateTile();

        }
    }
}




the real heavy lifting is done in the four functions
void check_move_left();
void check_move_right();
void check_move_up();
and
void check_move_down();


and was the actual original question of Chebs

That is how to write the legal move checking for the game of 2048,

well here is your answer Chebs

The checking requirement is more difficult than it first seems in a slide left move, you have to check the columns horizontal neighbors to decide how it moves you have to check them in this order assuming a matrix of 4by4 or 0 to 3 2d arrays.

Anyway the order of the move is thus

3,2
2,1
1,0

that is we slide in from array position 3 and check against array position 2 first of all then the check is 2,1 then lastly 1,0.

This is achieved by calling the function slide_Left()

which in turn calls check_move_left()

in check_move_left()

first of all, we check if the matrix at the position of row and column are different to zero ie if both array positions are filled

then we check if the values at those array points are equal to each other, if they are great we add them together in the lower column value ie if we are checking 3, 2 then we put it in the answer in column2, if the values are not equal we do nothing.

Then we consider the third case if the value of the higher column is not zero but the lower one is, in this case we want to swap the values of the two cells.

next, we will consider check_move_down()

in a down move the order of the rows being passed into the function

are

0,1
1,2
2,3

again we do something similar with rows instead of the columns we used in check_move_left()


first of all, we check if the matrix at the position of column and row are different to zero ie if both array positions are filled

then we check if the values at those array points are equal to each other, if they are great we add them together in the higher row value ie if we are checking 3, 2 then we put it in the answer in row3, if the values are not equal we do nothing.

Then we consider the third case if the value of the lower row is not zero but the higher one is, in this case, we want to swap the values of the two cells.

so pretty straightforward last of all is

Twentyfortyeight.java

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package twentyfortyeight;

import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 *
 * @author snoopy
 */
public class Twentyfortyeight {

    /**
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        JFrame window = new JFrame();
        JPanel panel = new JPanel();
        
        panel.setSize(new Dimension(161, 161));
        
        List<aTile> Tiles = new ArrayList<>();
        int marker = 0;
        for (int j = 0; j <= 120; j = j + 40) {
            for (int i = 0; i <= 120; i = i + 40) {
                
                Tiles.add(new aTile(i, j));
                Tiles.get(marker).setSize(new Dimension(161, 161));
                Tiles.get(marker).setOpaque(false);
                marker++;
            }
        }
        Matrix mat = new Matrix(Tiles);
        mat.init_matrix();
        int number = (int) (Math.random() * 16);
        int get_row = number / 4;
        int get_column = number - (get_row * 4);
        
        Tiles.get(number).initTile();
        mat.setMatrixValue(get_row, get_column, Tiles.get(number).getTileValue());
        
        int number2 = (int) ((Math.random() * 16));
        while (number2 == number) {
            number2 = (int) ((Math.random() * 16));
        }
        get_row = number2 / 4;
        get_column = number2 - (get_row * 4);
        
        Tiles.get(number2).initTile();
        mat.setMatrixValue(get_row, get_column, Tiles.get(number2).getTileValue());
        
        Grid grid = new Grid();
        grid.setSize(new Dimension(161, 161));
        scoreBoard score = new scoreBoard(mat);
        score.setSize(new Dimension(480, 380));
        panel.setLayout(null);
        for (int i = 0; i < Tiles.size(); i++) {
            panel.add(Tiles.get(i));
        }
        score.setLayout(null);
        score.setOpaque(false);
        panel.add(score);
        panel.add(grid);
        window.setContentPane(panel);
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setSize(new Dimension(480, 380));
        window.setLocationRelativeTo(null);
        
        window.setVisible(true);
        keyBoard keyb = new keyBoard(mat, score);
        window.addKeyListener(keyb);
        window.setTitle("2048 Game.");
        
    }
    
}




This adds a new JFrame window and a JPanel we set the relative position to null as we want to layer the various JPanels to build our game up we also set opaque to false so we can view the layers we build up.

We add a grid some Tiles a scoreBoard and a keyBoard handler we set the Title last of all and we are good to go.

This post has been edited by snoopy11: 04 November 2018 - 03:51 PM


Is This A Good Question/Topic? 3
  • +

Page 1 of 1