-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBoard.java
607 lines (532 loc) · 18.3 KB
/
Board.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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
import ml.classifiers.GeneticNN;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Random;
import javax.swing.*;
public class Board extends JPanel implements ActionListener {
//constants representing items that can occupy the game grid
private static final int EMPTY = 0;
private static final int SNAKE = 1;
private static final int FOOD = 2;
private static final int WALL = 3;
//constants for the UI and game play
private static final long serialVersionUID = -3199194738524258272L;
private final int B_WIDTH = 300;
private final int B_HEIGHT = 300;
private final int DOT_SIZE = 10;
private final int ALL_DOTS = 900;
private final int RAND_POS = 29;
private final int DELAY = 120;
private int runtime = 15;
Random r = new Random();
//keep track of the number of snakes that have died in each generation
public static int numFinished = 0;
//arrays to keep track of all the points in the snake
private final int x[] = new int[ALL_DOTS];
private final int y[] = new int[ALL_DOTS];
//the grid which contains all items in the game
private int[][] grid = new int[B_WIDTH / DOT_SIZE + 2][B_HEIGHT / DOT_SIZE + 2];
//variables to keep track of the apple and how many apples have been eaten
public int appleCount = 0;
private int dots;
private int apple_x;
private int apple_y;
private int gridAppleX;
private int gridAppleY;
private int xDistApple;
private int yDistApple;
private int xDistAppleOld;
private int yDistAppleOld;
//variables to keep track of the condition of the snake
private Boolean hasDied = false;
private boolean leftDirection = false;
private boolean rightDirection = true;
private boolean upDirection = false;
private boolean downDirection = false;
private boolean inGame = true;
//images and timer for the UI
private Timer timer;
private Image ball;
private Image apple;
private Image head;
private JFrame frame;
//variables to keep track of the network for the snake
private GeneticNN network;
private int numFeatures;
int[] features;
//variables that keep track of game status
long gameStart;
long gameEnd;
long start;
long elapsedTime;
/**
* @throws AWTException
*/
public Board() throws AWTException {
initBoard();
}
/**
* load the images and set up the UI
*/
private void initBoard() {
addKeyListener(new TAdapter());
setBackground(Color.black);
setFocusable(true);
setPreferredSize(new Dimension(B_WIDTH, B_HEIGHT));
loadImages();
initGame();
}
/**
* load the images for the snake and apple
*/
private void loadImages() {
ImageIcon iid = new ImageIcon("dot.png");
ball = iid.getImage();
ImageIcon iia = new ImageIcon("apple.png");
apple = iia.getImage();
ImageIcon iih = new ImageIcon("head.png");
head = iih.getImage();
}
/**
* initialize the game by setting the initial snake and apple positions
*/
private void initGame() {
//fill the grid with wall on the outside and empty inside
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[i].length; j++) {
if (i == 0 || j == 0 || i == grid.length - 1 || j == grid[i].length - 1) {
grid[i][j] = WALL;
} else {
grid[i][j] = EMPTY;
}
}
}
//initial snake size
dots = 3;
//put the snake in the game
for (int z = 0; z < dots; z++) {
x[z] = (r.nextInt(25) + 3) * 10 - z * 10;
y[z] = (r.nextInt(28) + 1) * 10;
grid[4 - z][4] = SNAKE;
}
//create the first apple
locateApple();
//start the game timer
timer = new Timer(DELAY, this);
timer.start();
start = System.currentTimeMillis() / 1000;
gameStart = System.currentTimeMillis() / 1000;
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
private void doDrawing(Graphics g) {
if (inGame) {
g.drawImage(apple, apple_x, apple_y, this);
for (int z = 0; z < dots; z++) {
if (z == 0) {
g.drawImage(head, x[z], y[z], this);
} else {
g.drawImage(ball, x[z], y[z], this);
}
}
yDistApple = Math.abs(y[0] / 10 + 1 - gridAppleY);
xDistApple = Math.abs(x[0] / 10 + 1 - gridAppleX);
Toolkit.getDefaultToolkit().sync();
} else {
gameOver(g);
}
}
/**
* set the network of this instance of the game
* @param theNetwork
*/
public void setNetwork(GeneticNN theNetwork) {
network = theNetwork;
}
/**
* end the game once the snake has died or the game times out
*/
public void endGame() {
inGame = false;
}
/**
* set the JFrame of this instance of the game
* @param frame
*/
public void setJFrame(JFrame frame) {
this.frame = frame;
}
/**
* calculate the final fitness of the snake and stop the game from running
* @param g
*/
private void gameOver(Graphics g) {
String msg = "Game Over";
Font small = new Font("Helvetica", Font.BOLD, 14);
FontMetrics metr = getFontMetrics(small);
g.setColor(Color.white);
g.setFont(small);
g.drawString(msg, (B_WIDTH - metr.stringWidth(msg)) / 2, B_HEIGHT / 2);
gameEnd = System.currentTimeMillis() / 1000 - gameStart;
//alter the fitness depending on if the snake died or not
if (hasDied == true)
network.deathFitness();
network.applesEaten(appleCount);
//indicate that this snake has stopped running
numFinished++;
timer.stop();
frame.setVisible(false);
frame.dispose();
}
/**
* check if the snake is eating the apple
*/
private void checkApple() {
if ((x[0] == apple_x) && (y[0] == apple_y)) {
start = System.currentTimeMillis() / 1000;
appleCount++;
dots++;
locateApple();
}
}
/**
* take one move for the snake
*/
private void move() {
//shift all the snake dots along by one
for (int z = dots; z > 0; z--) {
x[z] = x[(z - 1)];
y[z] = y[(z - 1)];
}
//update the grid to show this change
grid[(x[dots] / 10 + 1)][(y[dots] / 10) + 1] = EMPTY;
//calculate the new position of the head given a direction
if (leftDirection) {
x[0] -= DOT_SIZE;
grid[(x[0] / 10) + 1][(y[0] / 10) + 1] = SNAKE;
}
if (rightDirection) {
x[0] += DOT_SIZE;
grid[(x[0] / 10) + 1][(y[0] / 10) + 1] = SNAKE;
}
if (upDirection) {
y[0] -= DOT_SIZE;
grid[(x[0] / 10) + 1][(y[0] / 10) + 1] = SNAKE;
}
if (downDirection) {
y[0] += DOT_SIZE;
grid[(x[0] / 10) + 1][(y[0] / 10) + 1] = SNAKE;
}
}
/**
* @return the item in the position to the relative left of the snake
*/
public int getLeft() {
if (leftDirection) {
return grid[x[0] / 10 + 1][y[0] / 10 + 1 + 1];
} else if (rightDirection) {
return grid[x[0] / 10 + 1][y[0] / 10 + 1 - 1];
} else if (upDirection) {
return grid[x[0] / 10 + 1 - 1][y[0] / 10 + 1];
} else {
return grid[x[0] / 10 + 1 + 1][y[0] / 10 + 1];
}
}
/**
* @return the item in the position to the relative right of the snake
*/
public int getRight() {
if (leftDirection) {
return grid[x[0] / 10 + 1][y[0] / 10 + 1 - 1];
} else if (rightDirection) {
return grid[x[0] / 10 + 1][y[0] / 10 + 1 + 1];
} else if (upDirection) {
return grid[x[0] / 10 + 1 + 1][y[0] / 10 + 1];
} else {
return grid[x[0] / 10 + 1 - 1][y[0] / 10 + 1];
}
}
/**
* @return the item in the position to the relative front of the snake
*/
public int getFront() {
if (leftDirection) {
return grid[x[0] / 10 + 1 - 1][y[0] / 10 + 1];
} else if (rightDirection) {
return grid[x[0] / 10 + 1 + 1][y[0] / 10 + 1];
} else if (upDirection) {
return grid[x[0] / 10 + 1][y[0] / 10 + 1 - 1];
} else {
return grid[x[0] / 10 + 1][y[0] / 10 + 1 + 1];
}
}
/**
* @return whether the apple is to the relative left of the snake
*/
public boolean appleLeft() {
if (leftDirection) {
return y[0] / 10 + 1 < gridAppleY;
} else if (rightDirection) {
return y[0] / 10 + 1 > gridAppleY;
} else if (downDirection) {
return x[0] / 10 + 1 < gridAppleX;
} else {
return x[0] / 10 + 1 > gridAppleX;
}
}
/**
* @return whether the apple is to the relative right of the snake
*/
public boolean appleRight() {
if (leftDirection) {
return y[0] / 10 + 1 > gridAppleY;
} else if (rightDirection) {
return y[0] / 10 + 1 < gridAppleY;
} else if (downDirection) {
return x[0] / 10 + 1 > gridAppleX;
} else {
return x[0] / 10 + 1 < gridAppleX;
}
}
/**
* @return whether the apple is to the relative front of the snake
*/
public boolean appleUp() {
if (leftDirection) {
return x[0] / 10 + 1 > gridAppleX;
} else if (rightDirection) {
return x[0] / 10 + 1 < gridAppleX;
} else if (downDirection) {
return y[0] / 10 + 1 < gridAppleY;
} else {
return y[0] / 10 + 1 > gridAppleY;
}
}
/**
* @return whether the apple is to the relative back of the snake
*/
public boolean appleDown() {
if (leftDirection) {
return x[0] / 10 + 1 < gridAppleX;
} else if (rightDirection) {
return x[0] / 10 + 1 > gridAppleX;
} else if (downDirection) {
return y[0] / 10 + 1 > gridAppleY;
} else {
return y[0] / 10 + 1 < gridAppleY;
}
}
/**
* @return is the snake going left
*/
public boolean isLeftDirection() {
return leftDirection;
}
/**
* @return is the snake going right
*/
public boolean isRightDirection() {
return rightDirection;
}
/**
* @return is the snake going up
*/
public boolean isUpDirection() {
return upDirection;
}
/**
* check to see if the snake is hitting itself of the wall
*/
private void checkCollision() {
//check if the snake's head is hitting its body
for (int z = dots; z > 0; z--) {
if ((z > 0) && (x[0] == x[z]) && (y[0] == y[z])) {
inGame = false;
hasDied = true;
}
}
//check if the snake's head has gone out of bounds
if (y[0] >= B_HEIGHT || y[0] < 0 || x[0] >= B_WIDTH || x[0] < 0) {
inGame = false;
hasDied = true;
}
//stop the game if the snake has hit anything
if (!inGame) {
timer.stop();
}
}
/**
* reposition the apple
*/
private void locateApple() {
int rx = (int) (Math.random() * RAND_POS);
int ry = (int) (Math.random() * RAND_POS);
//make sure the new position is not hitting the snake
while (grid[rx + 1][ry + 1] == SNAKE) {
rx = (int) (Math.random() * RAND_POS);
ry = (int) (Math.random() * RAND_POS);
}
//update the apple position
apple_x = ((rx * DOT_SIZE));
apple_y = ((ry * DOT_SIZE));
gridAppleX = rx + 1;
gridAppleY = ry + 1;
grid[rx + 1][ry + 1] = FOOD;
}
@Override
public void actionPerformed(ActionEvent e) {
if (inGame) {
checkApple();
checkCollision();
if (inGame) {
determineMove(); //use the network to determine the snake's next move
//calculate old distance from snake's head to apple
yDistAppleOld = Math.abs(y[0] / 10 + 1 - gridAppleY);
xDistAppleOld = Math.abs(x[0] / 10 + 1 - gridAppleX);
move(); //take the determined move
//calculate new distance from snake's head to apple after the move
yDistApple = Math.abs(y[0] / 10 + 1 - gridAppleY);
xDistApple = Math.abs(x[0] / 10 + 1 - gridAppleX);
//calculate the old and current distance from the apple
double currentDist = Math.sqrt(Math.pow((double) xDistApple, 2) + Math.pow((double) yDistApple, 2));
double oldDist = Math.sqrt(Math.pow((double) xDistAppleOld, 2) + Math.pow((double) yDistAppleOld, 2));
//increase the fit if the snake has moved toward the apple, decrease otherwise
if (currentDist < oldDist)
network.increaseFitness(2);
else
network.decreaseFitness(3);
//end the game if time is up and snake has not died yet
elapsedTime = System.currentTimeMillis() / 1000 - start;
if (elapsedTime > runtime) {
endGame();
}
}
}
repaint();
}
/**
* @param i number of features this network should have
*/
public void setNumFeatures(int i) {
numFeatures = i;
}
/**
* determine the next move the snake should make given the input features and the network
*/
public void determineMove() {
features = new int[numFeatures];
setExample(features); //set the input features
//get the output and confidences of the network given the features
double move[] = network.classify(features);
double confidence[] = network.confidence(features);
int theMove; //the move the network will ultimately decide
//get the most confident output node and set theMove to be the move corresponding to this move
if (move[0] * confidence[0] > move[1] * confidence[1] && move[0] * confidence[0] > move[2] * confidence[2]) {
theMove = 1;
} else if (move[1] * confidence[1] > move[0] * confidence[0]
&& move[1] * confidence[1] > move[2] * confidence[2]) {
theMove = 2;
} else {
theMove = 3;
}
//chance the direction of the snake given the move (left or right) if appropriate
if (theMove == 1) {
if (this.isLeftDirection()) {
this.pressKey(KeyEvent.VK_DOWN);
} else if (this.isRightDirection()) {
this.pressKey(KeyEvent.VK_UP);
} else if (this.isUpDirection()) {
this.pressKey(KeyEvent.VK_LEFT);
} else {
this.pressKey(KeyEvent.VK_RIGHT);
}
} else if (theMove == 2) {
if (this.isLeftDirection()) {
this.pressKey(KeyEvent.VK_UP);
} else if (this.isRightDirection()) {
this.pressKey(KeyEvent.VK_DOWN);
} else if (this.isUpDirection()) {
this.pressKey(KeyEvent.VK_RIGHT);
} else {
this.pressKey(KeyEvent.VK_LEFT);
}
}
}
/**
* set the features given the current game state
* @param features the array to be filled with features
*/
private void setExample(int[] features) {
//variables holding the items surrounding the snake
int left,right,front;
left = this.getLeft();
right = this.getRight();
front = this.getFront();
//set features based on snake, wall, and apple positions
features[0] = ((front == SNAKE || front == WALL) ? 1 : 0);
features[1] = ((left == SNAKE || left == WALL) ? 1 : 0);
features[2] = ((right == SNAKE || (right == WALL)) ? 1 : 0);
features[3] = ((this.appleLeft()) ? 1 : 0);
features[4] = ((this.appleRight()) ? 1 : 0);
features[5] = ((this.appleUp()) ? 1 : 0);
features[6] = ((this.appleDown()) ? 1 : 0);
}
/**
* change the direction of the snake given a key
* @param key the key that is pressed
*/
public void pressKey(int key) {
if ((key == KeyEvent.VK_LEFT) && (!rightDirection)) {
leftDirection = true;
upDirection = false;
downDirection = false;
}
if ((key == KeyEvent.VK_RIGHT) && (!leftDirection)) {
rightDirection = true;
upDirection = false;
downDirection = false;
}
if ((key == KeyEvent.VK_UP) && (!downDirection)) {
upDirection = true;
rightDirection = false;
leftDirection = false;
}
if ((key == KeyEvent.VK_DOWN) && (!upDirection)) {
downDirection = true;
rightDirection = false;
leftDirection = false;
}
}
private class TAdapter extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if ((key == KeyEvent.VK_LEFT) && (!rightDirection)) {
leftDirection = true;
upDirection = false;
downDirection = false;
}
if ((key == KeyEvent.VK_RIGHT) && (!leftDirection)) {
rightDirection = true;
upDirection = false;
downDirection = false;
}
if ((key == KeyEvent.VK_UP) && (!downDirection)) {
upDirection = true;
rightDirection = false;
leftDirection = false;
}
if ((key == KeyEvent.VK_DOWN) && (!upDirection)) {
downDirection = true;
rightDirection = false;
leftDirection = false;
}
}
}
}