How to implement board games by Swing

Overview

Programming games could be super complicated, just imagine how fancy the scenes you can see in the Counter-Strike are, how sophisticated the weapon facilities are available in the Warcraft. However, on the flip side, building a game based on computer can also be reasonably easy and entertaining.

Board games come into this category, in this article, I am going to use Java Swing to build a simple board game involved with board, layout and pieces, etc, which can serve as the entry level game programming that certainly paves the way to more advanced stages of game programming.

Of note, Java Swing gains massive performance improvements in recent years due to the optimisations across multiple levels.

The game rules and illustrations

Game Board:

There are two players, for each player there are three pieces. There are four borderlines forming square of the board; one horizontal line, one vertical line and two sloped lines are crossed at the centre point on the board.

Setup:

The pieces are placed on the points of top borderline and bottom borderline.

Movement:

Each player moves a piece alternately; pieces moves along lines. Piece moves one step at one time, pieces can't be captured by moving into the occupied position of the opponent. Moving pieces can't cross over another piece.

The objective of the game:

Once all three pieces of one player are lined up along any of horizontal lines, vertical lines or sloped lines, except the top and bottom lines, this player wins; the game ends.

setup

one of winning positions

another winning position

Finally, let's assign this game a fabulous name -- Three Kings in that each player controls three pieces moving around the board.

Basic programming patterns in board games

Most of game programmings are made up of drawing graphics, events responding, moving graphics and timing etc, this game is not the outsider.
  • drawing graphics can be accomplished by java.awt.Graphics and java.awt.Graphics2D, both provide drawing shapes, rendering with colors, etc.
  • Events responding is underpinned by java.awt.event.MouseListener and java.awt.event.MouseEvent, etc, which capture and send peripheral devices' messages to application.
  • Moving graphics can be achieved by adjusting coordinates of certain swing components where the graphics are drawn.
  • Timing can be implemented by javax.swing.Timer, java.awt.event.ActionEvent and java.awt.event.ActionListener.

Anatomy of Three Kings implementation

It's natural to come up with following classes to forge this game:
  1. Board Class to stand for the board.
  2. Position Class to stand for the all possible positions to move.
  3. Piece Class to stand for the pieces controlled by players.
  4. Controller Class to coordinate movement upon the layout where pieces scatter around, alternate turns between players and decided winning positions.

Game Components

Let's create one class to represent the board -- BoardComponent.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.boye.threekings.component;

import javax.swing.*;
import java.awt.*;

public class BoardComponent extends JComponent {

    private final int boardLength;

    private BoardComponent(int boardLength) {
        super.setBounds(0, 0, boardLength, boardLength);
        this.boardLength = boardLength;
    }
    
    public static BoardComponent createInstance(int boardLength){
        return new BoardComponent(boardLength);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.BLACK);
        g.drawLine(0, 0, boardLength, boardLength);
        g.drawLine(boardLength, 0, 0, boardLength);
        g.drawLine(boardLength / 2, 0, boardLength / 2, boardLength);
        g.drawLine(0, boardLength / 2, boardLength, boardLength / 2);
    }
}

This class uses the board length to determine the size of board, moreover, several lines cross at the centre point of board, which are depicted by paintComponent method. Since the board is static component in the game, neither are the event listeners needed, nor is the movement required. Therefore, corresponding methods are ignored.

Next, let's create one to class to represent positions -- PositionComponent.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.boye.threekings.component;

import com.boye.threekings.Controller;
import com.boye.threekings.PiecePosition;

import javax.swing.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

public class PositionComponent extends JComponent implements MouseListener {

    private final PiecePosition piecePosition;

    private PositionComponent(PiecePosition piecePosition) {
        super.setBounds(piecePosition.getXCoordinate(), piecePosition.getYCoordinate(), piecePosition.getWidth(), piecePosition.getHeight());
        this.piecePosition = piecePosition;
    }

    public static PositionComponent createInstance(PiecePosition piecePosition) {
        PositionComponent instance = new PositionComponent(piecePosition);
        instance.addMouseListener(instance);
        return instance;
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        Controller.getInstance().clickPosition((PositionComponent) e.getComponent());
    }

    public PiecePosition getPiecePosition() {
        return this.piecePosition;
    }

    //empty events
    @Override
    public void mousePressed(MouseEvent e) {
    }

    @Override
    public void mouseReleased(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }
}

This class initialises with an enum parameter that combines digitals 1, 2, 3 and letters A, B, C to easily pinpoint the positions on the board. Whenever players click each position, the application needs to respond whether the selected piece should move or not, hence, MouseListener needs to captures all of mouse events which comes from current player acting upon positions.

Finally, let's create a class to represent pieces or kings -- PieceComponent.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package com.boye.threekings.component;

import com.boye.threekings.Controller;
import com.boye.threekings.PiecePosition;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

public class PieceComponent extends JComponent implements MouseListener, ActionListener {

    private PiecePosition currentPosition;
    private final int diameter;
    private final Color color;
    private Timer flashTimer;

    private PieceComponent(PiecePosition piecePosition, int diameter, Color color) {
        moveTo(piecePosition);
        this.diameter = diameter;
        this.color = color;
    }

    public static PieceComponent createInstance(PiecePosition piecePosition, int diameter, Color color) {
        PieceComponent pieceComponent = new PieceComponent(piecePosition, diameter, color);
        pieceComponent.addMouseListener(pieceComponent);
        return pieceComponent;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(color);
        g.fillOval(0, 0, diameter, diameter);
    }

    public Color getColor() {
        return color;
    }

    public PiecePosition getPiecePosition() {
        return this.currentPosition;
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        Controller.getInstance().clickPiece((PieceComponent) e.getComponent());
    }

    //flashing effect
    public void select() {
        flashTimer = new Timer(500, this);
        flashTimer.start();
    }

    @Override
    public void actionPerformed(ActionEvent event) {
        this.setVisible(!this.isVisible());
    }

    public void deSelect() {
        flashTimer.stop();
        this.setVisible(true);
    }

    //end
    //move piece
    public final void moveTo(PiecePosition piecePosition) {
        super.setBounds(piecePosition.getXCoordinate(), piecePosition.getYCoordinate(), piecePosition.getWidth(), piecePosition.getHeight());
        this.currentPosition = piecePosition;
    }

    //empty events
    @Override
    public void mousePressed(MouseEvent e) {
    }

    @Override
    public void mouseReleased(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }
}

In this class, as usual, the JComponent class is inherited. Enum PiecePosition specifies the current position of the piece. paintComponent method is overrided for rendering round and coloured shape to represent pieces. MouseListener is used to capture message of selecting a piece as well as a Timer is scheduled to flash the piece at the fixed interval to highlight which piece is currently selected and which player is at the turn. moveTo method moves pieces around the whole board by changing coordinates for locating.

The components above establish the visual effect as well as events responding mechanism, all of which are grouped as independent logic units. Nevertheless, we still lack a class to interweave those classes to enable them communicate with each other and apply game rules upon them.

Controller

Last but not lease, here comes a foremost class that applies game rules as well as processes all messages among all components so as to invoke correct behaviours -- Controller.java

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package com.boye.threekings;

import com.boye.threekings.component.PieceComponent;
import com.boye.threekings.component.PositionComponent;

import java.awt.*;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JOptionPane;

public class Controller {

    private static final Map<PiecePosition, PiecePosition[]> POSITION_MOVABLES;
    private static final List<PiecePosition[]> WINNING_POSITIONS;

    static {
        //initialize connectivities
        Map<PiecePosition, PiecePosition[]> map = new HashMap<>();
        map.put(PiecePosition.A1, new PiecePosition[]{PiecePosition.A2, PiecePosition.B1, PiecePosition.B2});
        map.put(PiecePosition.A2, new PiecePosition[]{PiecePosition.A1, PiecePosition.A3, PiecePosition.B2});
        map.put(PiecePosition.A3, new PiecePosition[]{PiecePosition.A2, PiecePosition.B2, PiecePosition.B3});
        map.put(PiecePosition.B1, new PiecePosition[]{PiecePosition.A1, PiecePosition.B2, PiecePosition.C1});
        map.put(PiecePosition.B2, new PiecePosition[]{PiecePosition.A1, PiecePosition.A2, PiecePosition.A3, PiecePosition.B1, PiecePosition.B3, PiecePosition.C1,
            PiecePosition.C2, PiecePosition.C3});
        map.put(PiecePosition.B3, new PiecePosition[]{PiecePosition.A3, PiecePosition.B2, PiecePosition.C3});
        map.put(PiecePosition.C1, new PiecePosition[]{PiecePosition.B1, PiecePosition.B2, PiecePosition.C2});
        map.put(PiecePosition.C2, new PiecePosition[]{PiecePosition.B2, PiecePosition.C1, PiecePosition.C3});
        map.put(PiecePosition.C3, new PiecePosition[]{PiecePosition.B2, PiecePosition.B3, PiecePosition.C2});
        POSITION_MOVABLES = Collections.unmodifiableMap(map);
        //initialize winning positions
        List<PiecePosition[]> list = new ArrayList<>();
        list.add(new PiecePosition[]{PiecePosition.B1, PiecePosition.B2, PiecePosition.B3});
        list.add(new PiecePosition[]{PiecePosition.A1, PiecePosition.B1, PiecePosition.C1});
        list.add(new PiecePosition[]{PiecePosition.A2, PiecePosition.B2, PiecePosition.C2});
        list.add(new PiecePosition[]{PiecePosition.A3, PiecePosition.B3, PiecePosition.C3});
        list.add(new PiecePosition[]{PiecePosition.A1, PiecePosition.B2, PiecePosition.C3});
        list.add(new PiecePosition[]{PiecePosition.A3, PiecePosition.B2, PiecePosition.C1});
        WINNING_POSITIONS = Collections.unmodifiableList(list);
    }

    private static Controller controller;

    private Controller() {
    }

    public synchronized static Controller getInstance() {
        if (controller == null) {
            controller = new Controller();
        }
        return controller;
    }
    ///////
    private PieceComponent[] greenKings = new PieceComponent[3];
    private PieceComponent[] redKings = new PieceComponent[3];
    private Color currentPlayer;
    private PieceComponent currentSelected;
    private boolean isGameEnd;

    public void setUp(PieceComponent[] greenKings, PieceComponent[] redKings) {
        //set up initial layout
        this.greenKings = greenKings;
        this.redKings = redKings;
        this.currentPlayer = Color.GREEN;
    }

    private void reset() {
        greenKings[0].moveTo(PiecePosition.C1);
        greenKings[1].moveTo(PiecePosition.C2);
        greenKings[2].moveTo(PiecePosition.C3);
        redKings[0].moveTo(PiecePosition.A1);
        redKings[1].moveTo(PiecePosition.A2);
        redKings[2].moveTo(PiecePosition.A3);
        this.currentPlayer = Color.GREEN;
        this.currentSelected = null;
        this.isGameEnd = false;
    }

    private boolean isMovable(PiecePosition from, PiecePosition to) {
        PiecePosition[] movables = POSITION_MOVABLES.get(from);
        return Arrays.binarySearch(movables, to) > -1;
    }

    private void changePlayer() {
        if (currentPlayer.equals(Color.GREEN)) {
            currentPlayer = Color.RED;
        } else {
            currentPlayer = Color.GREEN;
        }
    }

    /**
     * Tell if one player win.
     */
    private boolean existWinningPosition() {
        PieceComponent[] kings = currentPlayer.equals(Color.GREEN) ? greenKings : redKings;
        List<PiecePosition> kingPositions = new ArrayList<>();
        kingPositions.add(kings[0].getPiecePosition());
        kingPositions.add(kings[1].getPiecePosition());
        kingPositions.add(kings[2].getPiecePosition());
        // check win patterns
        for (PiecePosition[] winningPosition : WINNING_POSITIONS) {
            boolean flag = true;
            for (PiecePosition position : winningPosition) {
                if (!kingPositions.contains(position)) {
                    flag = false;
                    break;
                }
            }
            if (flag) {
                return true;
            }
        }
        return false;
    }

    /**
    current player selects the piece and flashes the piece to indicate 
    @param pieceComponent 
     */
    public void clickPiece(PieceComponent pieceComponent) {
        if (!isGameEnd) {
            Color selectedColor = pieceComponent.getColor();
            if (selectedColor.equals(currentPlayer)) {
                if (currentSelected != null) {
                    currentSelected.deSelect();
                }
                pieceComponent.select();
                currentSelected = pieceComponent;
            }
        } else {
            reset();
        }
    }

    /**
    current player moves the piece to targeted position
    @param positionComponent
     */
    public void clickPosition(PositionComponent positionComponent) {
        if (currentSelected != null) {
            PiecePosition from = currentSelected.getPiecePosition();
            PiecePosition to = positionComponent.getPiecePosition();
            if (isMovable(from, to)) {
                currentSelected.moveTo(positionComponent.getPiecePosition());
                currentSelected.deSelect();
                currentSelected = null;
                if (!existWinningPosition()) {
                    changePlayer();
                } else {
                    JOptionPane.showMessageDialog(null, "Congratulation! " + (this.currentPlayer.equals(Color.GREEN) ? "Green kings" : "Red kings") + " WON!");
                    isGameEnd = true;
                }
            }
        }
    }
}

In this class, we've defined several important variables and methods, let's examine them one by one.
  1. POSITION_MOVABLES, this Map constant defines the connectivities amongst adjacent positions.
  2. WINNING_POSITIONS, this Map constant defines how three pieces or kings form a line in order to defeat the opponent. (refer to the game rules for details)
  3. clickPiece method, this method handles messages generated by clicking piece. Consequently, it checks if the clicking is at the current turn. If so, controller sends message back to piece to demand flashing the piece to indicate the current player selected current piece ready to move.
  4. clickPosition method, this method handles messages generated by clicking positions. Once a piece is selected, it checks if the selected piece is movable to the targeted position. If so, controller sends message to selected piece to demand a movement. Afterwards, checks if current player forms a winning position.
  5. Some other methods are auxiliary to support aforementioned two primary methods.

Summary

The source code of this game can be downloaded via this link: https://github.com/Bo-Ye/three-kings.

After git clone, let's run the following command to build and launch this game to have a bit fun.

 mvn clean package exec:java  

The game shows up:

Three Kings

This game utilises some importance classes in Swing: JComponent, Graphics, MouseListener, Timer, ActionListener, JFrame, JLayeredPane, etc. All of them are indispensable and powerful at all levels of Java game programming.

Comments

Post a Comment

Popular posts from this blog

A trick for connecting spring security oauth2 and wso2 api manager

How to use bitcoinj to implement multisig transactions

How to develop Tomcat-based WebSocket application on Heroku