From 6e4fd538949ffdf4e9f5a50ee0e4e0d60ea70416 Mon Sep 17 00:00:00 2001 From: Lyonic Date: Mon, 10 Apr 2023 23:38:06 -0400 Subject: [PATCH] Several basic changes to the game's code, with various quality-of-life and code readability improvements. --- src/com/zetcode/Board.java | 184 ++++++++++++++++++++----------------- src/com/zetcode/Snake.java | 32 +++++-- src/resources/apple.png | Bin 2681 -> 2717 bytes src/resources/head.png | Bin 211 -> 229 bytes src/resources/icon.png | Bin 0 -> 379 bytes 5 files changed, 121 insertions(+), 95 deletions(-) create mode 100644 src/resources/icon.png diff --git a/src/com/zetcode/Board.java b/src/com/zetcode/Board.java index e81c4e9..6b1b03d 100644 --- a/src/com/zetcode/Board.java +++ b/src/com/zetcode/Board.java @@ -1,12 +1,6 @@ package com.zetcode; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Graphics; -import java.awt.Image; -import java.awt.Toolkit; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; @@ -17,68 +11,62 @@ public class Board extends JPanel implements ActionListener { - 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 = 140; + private final int BOARD_WIDTH = 300; + private final int BOARD_HEIGHT = 300; + private final int DOT_SIZE = 10; //A 'dot' is a single segmentation of the snake's body. + private final int MAX_LENGTH = 900; + private final int RAND_POS = 29; //The value assigned here is the multiplier for the random value generated later. + private final int DELAY = 140; //The delay between 'ticks' of the game (refreshes of the game board) - private final int x[] = new int[ALL_DOTS]; - private final int y[] = new int[ALL_DOTS]; + private final int[] dotXPos = new int[MAX_LENGTH]; //Holds the x position of each dot on the snake. + private final int[] dotYPos = new int[MAX_LENGTH]; //Holds the y position of each dot on the snake. - private int dots; - private int apple_x; - private int apple_y; + //Property variables + private int currentSnakeLength; + private int appleXPos, appleYPos; + //Movement variables private boolean leftDirection = false; private boolean rightDirection = true; private boolean upDirection = false; private boolean downDirection = false; - private boolean inGame = true; - + //Game-management variables + private boolean gameRunning = true; private Timer timer; - private Image ball; - private Image apple; - private Image head; + private Image body, head, apple; public Board() { - initBoard(); } - + private void initBoard() { - addKeyListener(new TAdapter()); + addKeyListener(new InputHandler()); setBackground(Color.black); setFocusable(true); - setPreferredSize(new Dimension(B_WIDTH, B_HEIGHT)); + setPreferredSize(new Dimension(BOARD_WIDTH, BOARD_HEIGHT)); loadImages(); initGame(); } private void loadImages() { + body = new ImageIcon("src/resources/dot.png").getImage(); - ImageIcon iid = new ImageIcon("src/resources/dot.png"); - ball = iid.getImage(); - - ImageIcon iia = new ImageIcon("src/resources/apple.png"); - apple = iia.getImage(); + apple = new ImageIcon("src/resources/apple.png").getImage(); - ImageIcon iih = new ImageIcon("src/resources/head.png"); - head = iih.getImage(); + head = new ImageIcon("src/resources/head.png").getImage(); } private void initGame() { - dots = 3; + currentSnakeLength = 3; - for (int z = 0; z < dots; z++) { - x[z] = 50 - z * 10; - y[z] = 50; + for (int z = 0; z < currentSnakeLength; z++) { + dotXPos[z] = 50 - z * 10; + dotYPos[z] = 50; } - + locateApple(); timer = new Timer(DELAY, this); @@ -91,18 +79,18 @@ public void paintComponent(Graphics g) { doDrawing(g); } - + private void doDrawing(Graphics g) { - - if (inGame) { - g.drawImage(apple, apple_x, apple_y, this); + if (gameRunning) { - for (int z = 0; z < dots; z++) { + g.drawImage(apple, appleXPos, appleYPos, this); + + for (int z = 0; z < currentSnakeLength; z++) { if (z == 0) { - g.drawImage(head, x[z], y[z], this); + g.drawImage(head, dotXPos[z], dotYPos[z], this); } else { - g.drawImage(ball, x[z], y[z], this); + g.drawImage(body, dotXPos[z], dotYPos[z], this); } } @@ -111,97 +99,115 @@ private void doDrawing(Graphics g) { } else { gameOver(g); - } + } } private void gameOver(Graphics g) { - - String msg = "Game Over"; - Font small = new Font("Helvetica", Font.BOLD, 14); - FontMetrics metr = getFontMetrics(small); + + String gameOverMessage = "Game Over"; + String restartMessage = "Press 'R' to Restart"; + + Font endGameFont = new Font("Helvetica", Font.BOLD, 28); + + FontMetrics fontMetric = getFontMetrics(endGameFont); + + int gameOverTextXPos = (BOARD_WIDTH - fontMetric.stringWidth(gameOverMessage)) / 2; + int gameOverTextYPos = (BOARD_HEIGHT / 2) - 30; g.setColor(Color.white); - g.setFont(small); - g.drawString(msg, (B_WIDTH - metr.stringWidth(msg)) / 2, B_HEIGHT / 2); + g.setFont(endGameFont); + g.drawString(gameOverMessage, gameOverTextXPos, gameOverTextYPos); + g.drawString(restartMessage, gameOverTextXPos - 50, gameOverTextYPos + 60); } private void checkApple() { - if ((x[0] == apple_x) && (y[0] == apple_y)) { + //If the snake's head is at the same x and y pos of the apple, add to the length, and move the apple. + if ((dotXPos[0] == appleXPos) && (dotYPos[0] == appleYPos)) { - dots++; + currentSnakeLength++; locateApple(); } } private void move() { - for (int z = dots; z > 0; z--) { - x[z] = x[(z - 1)]; - y[z] = y[(z - 1)]; + //Moves every piece of the snake's body to the x and y pos of the body-part in-front of it, creating the effect + //of the snake moving forward. This must be done *before* the snake's head is moved. + for (int z = currentSnakeLength; z > 0; z--) { + dotXPos[z] = dotXPos[(z - 1)]; + dotYPos[z] = dotYPos[(z - 1)]; } + //Moves the head according to the enabled direction. if (leftDirection) { - x[0] -= DOT_SIZE; + dotXPos[0] -= DOT_SIZE; } if (rightDirection) { - x[0] += DOT_SIZE; + dotXPos[0] += DOT_SIZE; } if (upDirection) { - y[0] -= DOT_SIZE; + dotYPos[0] -= DOT_SIZE; } if (downDirection) { - y[0] += DOT_SIZE; + dotYPos[0] += DOT_SIZE; } } private void checkCollision() { - for (int z = dots; z > 0; z--) { - - if ((z > 4) && (x[0] == x[z]) && (y[0] == y[z])) { - inGame = false; + /* + Loops through every part of the snake's body and for each piece checks to see if that piece is touching the + head. If it is, ends the game. Removed a check that was initially here which ignored collisions with early parts + of the body. With that check in place, it was super easy to pass straight through the body anytime you got trapped. + */ + for (int z = currentSnakeLength; z > 0; z--) { + if ((dotXPos[0] == dotXPos[z]) && (dotYPos[0] == dotYPos[z])) { + gameRunning = false; } } - if (y[0] >= B_HEIGHT) { - inGame = false; + //All of these checks are to ensure the snake is within the bounds of the game board. + if (dotYPos[0] >= BOARD_HEIGHT) { + gameRunning = false; } - if (y[0] < 0) { - inGame = false; + if (dotYPos[0] < 0) { + gameRunning = false; } - if (x[0] >= B_WIDTH) { - inGame = false; + if (dotXPos[0] >= BOARD_WIDTH) { + gameRunning = false; } - if (x[0] < 0) { - inGame = false; + if (dotXPos[0] < 0) { + gameRunning = false; } - - if (!inGame) { + + if (!gameRunning) { timer.stop(); } } + //Randomizes the x and y position of the apple. private void locateApple() { int r = (int) (Math.random() * RAND_POS); - apple_x = ((r * DOT_SIZE)); + appleXPos = ((r * DOT_SIZE)); r = (int) (Math.random() * RAND_POS); - apple_y = ((r * DOT_SIZE)); + appleYPos = ((r * DOT_SIZE)); } + //This is run every 'tick' of the game-loop. First everything is updated and refreshed, put in it's proper place, + //and then the screen is updated to reflect these changes. @Override public void actionPerformed(ActionEvent e) { - if (inGame) { - + if (gameRunning) { checkApple(); checkCollision(); move(); @@ -210,36 +216,44 @@ public void actionPerformed(ActionEvent e) { repaint(); } - private class TAdapter extends KeyAdapter { + //Class responsible for the handling of user-input and movement of the snake + private class InputHandler extends KeyAdapter { @Override public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); - if ((key == KeyEvent.VK_LEFT) && (!rightDirection)) { + //Added support for the 'w' 'a' 's' 'd' keys here + if ( ( (key == KeyEvent.VK_LEFT) || (key == KeyEvent.VK_A) ) && (!rightDirection)) { leftDirection = true; upDirection = false; downDirection = false; } - if ((key == KeyEvent.VK_RIGHT) && (!leftDirection)) { + if ( ( (key == KeyEvent.VK_RIGHT) || (key == KeyEvent.VK_D) ) && (!leftDirection)) { rightDirection = true; upDirection = false; downDirection = false; } - if ((key == KeyEvent.VK_UP) && (!downDirection)) { + if ( ( (key == KeyEvent.VK_UP) || (key == KeyEvent.VK_W) ) && (!downDirection)) { upDirection = true; rightDirection = false; leftDirection = false; } - if ((key == KeyEvent.VK_DOWN) && (!upDirection)) { + if ( ( (key == KeyEvent.VK_DOWN) || (key == KeyEvent.VK_S) ) && (!upDirection)) { downDirection = true; rightDirection = false; leftDirection = false; } + + if(key == KeyEvent.VK_R && !gameRunning){ + Snake.restartGame(); + } + } + } } diff --git a/src/com/zetcode/Snake.java b/src/com/zetcode/Snake.java index 52040ff..5420316 100644 --- a/src/com/zetcode/Snake.java +++ b/src/com/zetcode/Snake.java @@ -1,33 +1,45 @@ package com.zetcode; import java.awt.EventQueue; -import javax.swing.JFrame; +import javax.swing.*; public class Snake extends JFrame { + private static JFrame frame; + public Snake() { - initUI(); } - + private void initUI() { - + add(new Board()); - + setResizable(false); pack(); - + setTitle("Snake"); + setIconImage(new ImageIcon("src/resources/icon.png").getImage()); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } - + + //Basic method for restarting the game via disposing the current frame and initializing a new one. + public static void restartGame(){ + frame.dispose(); + + startGame(); + } public static void main(String[] args) { - + startGame(); + } + + private static void startGame(){ EventQueue.invokeLater(() -> { - JFrame ex = new Snake(); - ex.setVisible(true); + frame = new Snake(); + frame.setVisible(true); }); } + } diff --git a/src/resources/apple.png b/src/resources/apple.png index 90e70c82fb41b4ed099938f4917385cb1817a892..ee1c494c3751a3093153d05432728253cc7635bf 100644 GIT binary patch delta 2652 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 BOTz#F delta 183 zcmaFLc$sm6NM1MG8=my~NYkmHh^% zjIfDRt=~!kpiqLRi(`ny<*5^PayB?Hu()$?&=b`0dh?2fTR`WSx_t9zgO8TtX16@c z0s;lI5(Uf>#hWfHv2~pL!1*_a({HAw+c=WhBn-F`8MW?ho|dwVd2tDnm{r-UW|{r5lN diff --git a/src/resources/icon.png b/src/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f022f5644aff826e3dce8b3b622cf11d681a6c99 GIT binary patch literal 379 zcmeAS@N?(olHy`uVBq!ia0vp^9zbl*!2~4FtdPtEQjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`ISV`@iy0XB4ude`@%$AjKtZ7r*NBqf{Irtt#G+IN_oCF)yyA@flDJR~ zkd{xLE{-7?&Tp?8@-aG!9Q!Elx1q(!y)jGHp(Cinx$|&`1w*9xLmsb1PdiVhd_Lr3 zDgE>RC(*KfNzW8FgPa8d4nE0xizoZ!8SOm1V#2XQYwKBVMeQ{{vF+`-%jq-TmR!7= zS$f^5`qme-k74XDwx4^t((dJUy_f0PdT$rjewog>y*Ve}?bF3mxf8tVwy1wQ$v^QL zPx2!(kJOs2>fbJE-I?X-bBAr^pSKAM?Qa`ig78ejza6oAe_HuQ*so6k@%kmPJ=^xL z|6Lp!m-_4L^Hv}E_b=FMb-1@!HSC*sIym$ND=);Q3=Tp{`X|<@f0LE+_5=xey85}S Ib4q9e0KAx&;s5{u literal 0 HcmV?d00001