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.
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:- Board Class to stand for the board.
- Position Class to stand for the all possible positions to move.
- Piece Class to stand for the pieces controlled by players.
- 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.
- POSITION_MOVABLES, this Map constant defines the connectivities amongst adjacent positions.
- 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)
- 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.
- 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.
- 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.
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
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.
Nice article, you want to know more about seo tricks and blogging tips for latest Computer related tricks and for my top article which are in Google first page results
ReplyDeleteYou want to know about Tatkal ticket tricks for India Trains and Aadhaar card solution in India and for latest Computer related tricks