-
Notifications
You must be signed in to change notification settings - Fork 0
/
GameWindow.java
269 lines (234 loc) · 8.33 KB
/
GameWindow.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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.KeyEventDispatcher;
import java.awt.event.KeyEvent;
import java.awt.KeyboardFocusManager;
import java.util.HashMap;
import java.util.HashSet;
import java.util.ArrayList;
/**
* This is the main class of this project.
* It creates the GUI and the game application, and connects them together.
* Also acts as the centralised listener for ActionEvents and KeyEvents.
*
* @author Huy Nguyen
* @version (a version number or a date)
*/
public class GameWindow implements ActionListener
{
//GUI components
private int size; //The size of the game grid
private HashMap colours; //Sets the colours of the occupied tiles
private WindowBuilder window;
private ScoreKeeper scDisplay;
//Components required to run the game
private boolean running;
private final int delay = 100;
private Timer timer; //Allows game to run with delay between steps
private Direction direction;
private Game game;
//Occupied tiles on the grid.
private ArrayList<SnakeTile> snake;
private ArrayList<FruitTile> fruit;
/**
* Sets up the initial game state and creates all GUI components required.
*/
public GameWindow()
{
init(false, false);
}
/**
* Initialises the window with all GUI and game components.
* @param recreate If true, initialise new top-level window. If not, only initialise game application and
* Component objects.
* @param resize If true, the game grid reinitialises with the size specified by user input.
*/
private void init(boolean recreate, boolean resize){
//Sets up GUI with the specified dimension
if(!recreate){
window = new WindowBuilder(25, this);
size = window.getOptions().getGridSize();
}
else {
size = window.getOptions().getGridSize();
window.resize(size);
}
setColours();
//Sets up the initial state of the game logic
running = false;
direction = Direction.WEST; //Defaults to West because Snake initialises facing West.
game = new Game(size);
//Displays score
int score = game.getScore();
scDisplay = window.getScoreDisplay();
scDisplay.setText("Score: " + score +
" HighScore: " + scDisplay.getHighScore());
//Painting occupied tiles
snake = game.getSnake().getSnakeTiles();
fruit = game.getFruit();
window.getDisplay().paintComponents(colours, snake, fruit);
//Sets up keyboard listener
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyboardListener());
}
//Displaying components of the game logic-------------------------------------------------------------
/**
* Assigns colours to each tile type.
*/
private void setColours()
{
colours = new HashMap();
int scheme = window.getOptions().getColourScheme();
assert scheme == 1 || scheme == 2 || scheme == 3 : "Scheme not valid!";
switch (scheme) {
case 1 : colours.put("FruitTile", Color.YELLOW);
colours.put("SnakeTile", Color.RED);
break;
case 2 : colours.put("FruitTile", Color.BLUE);
colours.put("SnakeTile", Color.BLACK);
break;
case 3 : colours.put("FruitTile", Color.MAGENTA);
colours.put("SnakeTile", Color.CYAN);
break;
}
}
//Gameplay and interactions
/**
* Mutator method for the direction variable, ensures snake cannot go back on itself.
* @param direction The new direction
*/
private void changeDirection(Direction direction)
{
switch (this.direction) {
case NORTH : if (direction == Direction.SOUTH) {}
else { this.direction = direction; }
break;
case SOUTH : if (direction == Direction.NORTH) {}
else { this.direction = direction; }
break;
case EAST : if (direction == Direction.WEST) {}
else { this.direction = direction; }
break;
case WEST : if (direction == Direction.EAST) {}
else { this.direction = direction; }
break;
}
}
/**
* Runs the game one step.
* Moves and repaints any occupied tiles, as well as checking whether game is still running.
* If player lost, display a dialogue box.
*/
private void runOneStep()
{
if (running) {
//Repainting the tailTile of the snake
SnakeTile tailTile = snake.get(0);
int row = tailTile.getLocation().getRow();
int col = tailTile.getLocation().getCol();
window.getDisplay().paintComponent(row, col, size, Color.WHITE);
game.runOneStep(direction);
//Gets new game information after the step
snake = game.getSnake().getSnakeTiles();
fruit = game.getFruit();
int score = game.getScore();
//Update GUI accordingly
window.getDisplay().paintComponents(colours, snake, fruit);
int highScore = scDisplay.getHighScore();
if (score > highScore) {
scDisplay.setHighScore(score);
}
window.getScoreDisplay().setText("Score: " + score +
" HighScore: " + scDisplay.getHighScore());
//Checking whether the game has paused or ended after this step
running = game.getRunning();
if (game.isOver()) {
new JOptionPane().showMessageDialog(window.getJFrame(),
"Game Over! \nScore: " + game.getScore());
reset();
}
}
else {
stop();
}
}
/**
* Runs the game with a delay. Sets the delay of each step in the game to 200ms.
*/
private void start()
{
running = true;
game.setRunning(true);
//Disable buttons and spinners to prevent errorenous input
window.startButtons();
window.getOptions().getColourSpinner().setEnabled(false);
window.getOptions().getSizeSpinner().setEnabled(false);
ActionListener runGame = new ActionListener() {
public void actionPerformed(ActionEvent e) {
runOneStep();
}
};
timer = new Timer(delay, runGame);
timer.start();
}
/**
* Stops the game and all UI components
*/
private void stop()
{
timer.stop();
timer = null;
game.setRunning(false);
window.stopButtons();
window.getOptions().getColourSpinner().setEnabled(true);
window.getOptions().getSizeSpinner().setEnabled(true);
}
/**
* Resets the game to its initial state, doesn't recreate the top-level window.
*/
private void reset()
{
init(true, true);
}
//Listening to hand handling events from GUI interaction
/**
* Handles ActionEvents occuring when a game-controlling button or menu item is interacted with
* @param e The event to handle
*/
public void actionPerformed(ActionEvent e) {
switch (e.getActionCommand()) {
case "Start Game" : start();
break;
case "Stop Game" : stop();
break;
case "Reset Game" : reset();
break;
}
}
/**
* Nested class, implemented because the KeyListener objects didn't function when game starts/stop/resets.
*/
private class KeyboardListener implements KeyEventDispatcher {
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getID() == KeyEvent.KEY_PRESSED && running) {
int x = event.getKeyCode();
switch(x){
case 38:
changeDirection(Direction.NORTH);
break;
case 40:
changeDirection(Direction.SOUTH);
break;
case 37:
changeDirection(Direction.WEST);
break;
case 39:
changeDirection(Direction.EAST);
break;
}
}
return false;
}
}
}