我正在编写一款类似于贪吃蛇的游戏。目前,我正在努力编写 move() 和 Growth() 方法。这个游戏的运作方式是,蠕虫从 1 块开始,每移动一步就增加 1 块,直到达到 3 块。 达到3块后,只能吃一个苹果才能成长。该游戏中的运动是通过删除第一块并添加位于蠕虫前进方向的新块来完成的。 Move() 应该将蠕虫移动一件,grow() 应该将蠕虫增长一件。然而,当调用grow()时,蠕虫在调用move()之前不会生长。 我添加了一个私有变量“growPiece”来在调用grow()时存储一个片段,然后在调用move()时添加它。然而,我没有得到我应该得到的结果。
public class Worm {
private int x;
private int y;
private Direction direction;
private List<Piece> pieces;
private Piece growPiece;
public Worm(int originalX, int originalY, Direction originalDirection) {
this.x = originalX;
this.y = originalY;
this.direction = originalDirection;
this.pieces = new ArrayList<Piece>();
this.pieces.add(new Piece(this.x, this.y));
this.growPiece = null;
}
public List<Piece> getPieces() {
return this.pieces;
}
public void move() {
if (this.direction == Direction.RIGHT) {
this.pieces.add(new Piece(this.pieces.get(this.pieces.size() - 1).getX() + 1, this.pieces.get(this.pieces.size() - 1).getY()));
} else if (this.direction == Direction.LEFT) {
this.pieces.add(new Piece(this.pieces.get(this.pieces.size() - 1).getX() + 1, this.pieces.get(this.pieces.size() - 1).getY()));
} else if (this.direction == Direction.DOWN) {
this.pieces.add(new Piece(this.pieces.get(this.pieces.size() - 1).getX(), this.pieces.get(this.pieces.size() - 1).getY() + 1));
} else if (this.direction == Direction.UP) {
this.pieces.add(new Piece(this.pieces.get(this.pieces.size() - 1).getX(), this.pieces.get(this.pieces.size() - 1).getY() - 1));
}
if (this.growPiece != null) {
this.pieces.add(growPiece);
this.growPiece = null;
}
if (this.pieces.size() > 3 && this.growPiece == null) {
this.pieces.remove(0);
}
}
public void grow() {
if (this.direction == Direction.RIGHT) {
this.growPiece = new Piece(this.pieces.get(this.pieces.size() - 1).getX() + 2, this.pieces.get(this.pieces.size() - 1).getY());
} else if (this.direction == Direction.LEFT) {
this.growPiece = new Piece(this.pieces.get(this.pieces.size() - 1).getX() - 2, this.pieces.get(this.pieces.size() - 1).getY());
} else if (this.direction == Direction.DOWN) {
this.growPiece = new Piece(this.pieces.get(this.pieces.size() - 1).getX(), this.pieces.get(this.pieces.size() - 1).getY() + 2);
} else if (this.direction == Direction.UP) {
this.growPiece = new Piece(this.pieces.get(this.pieces.size() - 1).getX(), this.pieces.get(this.pieces.size() - 1).getY() - 2);
}
}
我无法让方法在不使用参数的情况下相互通信。
在函数 move() 中,如果函数开头的 this.growPiece 为空或不为 null,并且pieces.size > 3,则会添加增长块,然后立即删除,因为无论哪种方式,当您到达该块时如果,growPiece为空。
我的意思是,看看 move() 末尾的两个 if 语句。在第一个中,如果 this.growPiece 已被分配,growPiece 将被添加到片段 ArrayList 中,我必须假设它会产生您想要的效果。添加它后,您将growPiece设置为空。
在下一个 if 条件部分中,growPiece 为 null,现在它总是如此。
尝试将其设为 else if,看看是否效果更好。
一切就绪,我使用布尔变量来标记何时生长。
这里是蠕虫游戏,练习 49,赫尔辛基大学面向对象编程 MOOC 的完整程序代码。 整个代码中的解释可能看起来过于详细,但它们是针对学习者的,而不是针对专业开发人员的。 该程序的结构与课程练习中提出的结构并不完全相同,但结果完全相同。 该程序的逻辑如下:
1.
public enum Direction {
LEFT,
RIGHT,
DOWN,
UP;
}
2.
public class Piece {
private int x;
private int y;
private int width;
private int length;
private int numberOfApples;
public Piece(int x, int y, int width, int length) {
this.x = x;
this.y = y;
this.length = length;
this.width = width;
}
public void setNumberOfApples() {
this.numberOfApples += 1;
}
public int getNumberOfApples() {
return this.numberOfApples;
}
public int getX() {
return this.x;
}
public int getY() {
return this.y;
}
public int getWidth() {
return this.width;
}
public int getLength() {
return this.length;
}
/*we use the following two override methods in order to check when the worm runs into itself.
This checking is done through the method "public boolean runsIntoItself(Piece piece)" in class "Worm"*/
@Override
public boolean equals(Object object) {
if (this.getClass() == null) {
return false;
}
if (this.getClass() != object.getClass()) {
return false;
}
Piece compared = (Piece) object;
if (this.x != compared.x || this.y != compared.y
|| this.width != compared.width || this.length != compared.length) {
return false;
}
return true;
}
@Override
public int hashCode() {
if (this.getClass() == null) {
return 1;
}
return this.x + this.y + this.width + this.length;
}
@Override
public String toString() {
return "(" + this.x + ", " + this.y + ", " + this.width + ", " + this.length + ")";
}
}
3.
public class Apple extends Piece {
public Apple(int x, int y, int width, int length) {
super(x, y, width, length);
}
}
4.
import java.util.List;
import java.util.ArrayList;
import javax.swing.JFrame;
public class Worm extends Piece {
private int growth;
Direction direction;
private List<Piece> piecesWorm;
private JFrame frame;
public Worm(int x, int y, int width, int length, Direction originalDirection) {
super(x, y, width, length);
this.growth = 0;
this.direction = originalDirection;
this.piecesWorm = new ArrayList<Piece>();
this.piecesWorm.add(new Piece(x, y, width, length));
frame = new JFrame("Number of Apples: " + super.getNumberOfApples());
}
public JFrame getFrame() {
return this.frame;
}
public Direction getDirection() {
return this.direction;
}
public void setDirection(Direction dir) {
this.direction = dir;
}
public int getLength() {
return this.piecesWorm.size();
}
public List<Piece> getPieces() {
return this.piecesWorm;
}
/*this method takes the calculated new piece from method "calculateNewPiece"
and does the following:
# moves the worm(adds an element to the head and substructs an element from the tail)
# makes the worm grow(only adds an element to the head)
# terminates the game if the worm runs into itself*/
public void move() {
if (this.runsIntoItself(this.calculateNewPiece()) == false) {
if (this.piecesWorm.size() < 3) {
this.piecesWorm.add(this.calculateNewPiece());
} else if (this.piecesWorm.size() >= 3) {
if (this.growth == 0) {
this.piecesWorm.add(this.calculateNewPiece());
this.piecesWorm.remove(0);
} else if (this.growth == 1) {
this.piecesWorm.add(this.calculateNewPiece());
this.growth = 0;
}
}
} else {
this.frame.setTitle("The worm has run into itself!!! "
+ "Total Number of Apples: " + (this.getNumberOfApples()));
try {
Thread.sleep(10000);
} catch (Exception ex) {
}
System.exit(0);
}
}
/*this method calculates the next piece of the worm according to its direction*/
public Piece calculateNewPiece() {
// lpbm stands for "lastPieceBeforeMoving"
Piece lpbm = null;
Piece newPiece = null;
if (this.piecesWorm.size() == 1) {
lpbm = this.piecesWorm.get(0);
} else {
lpbm = this.piecesWorm.get(this.piecesWorm.size() - 1);
}
int x = lpbm.getX();
int y = lpbm.getY();
if (this.direction.equals(Direction.LEFT)) {
newPiece = new Piece(x - 1 - super.getWidth(), y, super.getWidth(), super.getLength());
} else if (this.direction.equals(Direction.RIGHT)) {
newPiece = new Piece(x + 1 + super.getWidth(), y, super.getWidth(), super.getLength());
} else if (this.direction.equals(Direction.DOWN)) {
newPiece = new Piece(x, y + 1 + super.getLength(), super.getWidth(), super.getLength());
} else if (this.direction.equals(Direction.UP)) {
newPiece = new Piece(x, y - 1 - super.getLength(), super.getWidth(), super.getLength());
}
return newPiece;
}
/*this method sets the conditions that determines whether the worm will grow or not when
the method "move()" is called*/
public void grow() {
this.growth = 1;
}
/*this method checks whether the worm runs into an apple*/
public boolean runsInto(Piece piece) {
Piece headPieceOfWorm = this.piecesWorm.get(this.piecesWorm.size() - 1);
if ((headPieceOfWorm.getX() + (headPieceOfWorm.getWidth())) < piece.getX()) {
return false;
} else if (headPieceOfWorm.getX() > (piece.getX() + piece.getWidth())) {
return false;
} else if ((headPieceOfWorm.getX() + (headPieceOfWorm.getWidth())) >= piece.getX()
&& (headPieceOfWorm.getX() <= (piece.getX() + piece.getWidth()))) {
if (headPieceOfWorm.getY() + (headPieceOfWorm.getLength()) < piece.getY()
|| (headPieceOfWorm.getY() > piece.getY() + (piece.getLength()))) {
return false;
}
}
return true;
}
/*this method checks whether the worm runs into itself*/
public boolean runsIntoItself(Piece piece) {
if (this.piecesWorm.contains(piece)) {
return true;
}
return false;
}
@Override
public String toString() {
return "(" + super.getX() + ", " + super.getY() + ", " + super.getWidth()
+ ", " + super.getLength() + ", " + this.getDirection() + ")";
}
}
5.
import java.util.Random;
import java.util.List;
public class AvoidOverlappingPieces {
/*this method calculates the initial position of the apple
so that the apple is within the field and it does not overlap with the worm*/
public Apple initialApple(Worm worm, int fieldWidth, int fieldLength, int pieceWidth, int pieceLength) {
Random random = new Random();
Apple apple = null;
while (true) {
int xApple = random.nextInt(fieldWidth - (pieceWidth));
int yApple = random.nextInt(fieldLength - (pieceLength));
apple = new Apple(xApple, yApple, pieceWidth, pieceLength);
if (xApple + (pieceWidth) < worm.getX()) {
break;
} else if (xApple > worm.getX() + pieceWidth) {
break;
} else if ((xApple + (pieceWidth) >= worm.getX())
&& (xApple <= worm.getX() + pieceWidth)) {
if (yApple + (pieceLength) < worm.getY()
|| (yApple > worm.getY() + (pieceLength))) {
break;
}
}
}
return apple;
}
/*this method calculates the position of the new apple, after the previous one is eaten
by the worm, so that the apple is within the field and it does not overlap with the worm*/
public Apple replaceEatenApple(Worm worm, List<Piece> piecesWorm, int fieldWidth, int fieldLength, int pieceWidth, int pieceLength) {
Random random = new Random();
Apple apple = null;
while (true) {
int xApple = random.nextInt(fieldWidth - (pieceWidth));
int yApple = random.nextInt(fieldLength - (pieceLength));
Apple newApple = new Apple(xApple, yApple, pieceWidth, pieceLength);
if (this.fieldExeptWorm(newApple, piecesWorm) == false) {
apple = newApple;
break;
}
}
return apple;
}
/*this method calculates the available space, taking account of the current
position of worm, and it is used in the method "replaceEatenApple(...)"*/
public boolean fieldExeptWorm(Piece piece, List<Piece> piecesWorm) {
int counter = 0;
for (int i = 0; i < piecesWorm.size(); i++) {
Piece wormPiece = piecesWorm.get(i);
if ((wormPiece.getX() + (wormPiece.getWidth())) < piece.getX()) {
counter++;
} else if (wormPiece.getX() > (piece.getX() + piece.getWidth())) {
counter++;
} else if ((wormPiece.getX() + (wormPiece.getWidth())) >= piece.getX()
&& (wormPiece.getX() <= (piece.getX() + piece.getWidth()))) {
if (wormPiece.getY() + (wormPiece.getLength()) < piece.getY()
|| (wormPiece.getY() > piece.getY() + (piece.getLength()))) {
counter++;
}
}
}
if (counter == piecesWorm.size()) {
return false;
}
return true;
}
}
6.
import java.util.Random;
public class WormGame {
private int fieldWidth;
private int fieldLength;
private int pieceWidth;
private int pieceLength;
private Worm worm;
private Apple apple;
private Random random;
/* class "AvoidOverlappingPieces" contains methods that calculate
the initial spot of the apple and its subsequent positions across the field*/
private AvoidOverlappingPieces properPieces;
protected int applesNumber;
public WormGame(int fieldWidth, int fieldLength) {
this.fieldWidth = fieldWidth;
this.fieldLength = fieldLength;
this.pieceWidth = (this.fieldWidth - 10) / 10;
this.pieceLength = (this.fieldLength - 10) / 10;
this.worm = new Worm(0, 0, pieceWidth, pieceLength, Direction.DOWN);
this.random = new Random();
this.properPieces = new AvoidOverlappingPieces();
this.apple = this.properPieces.initialApple(this.worm, this.fieldWidth, this.fieldLength, this.pieceWidth, this.pieceLength);
}
public int getfieldWidth() {
return this.fieldWidth;
}
public int getfieldLength() {
return this.fieldLength;
}
public Worm getWorm() {
return this.worm;
}
public Apple getApple() {
return this.apple;
}
/*this method accelerates the worm as it grows longer,
through calculating smaller and smaller values for the number
of milliseconds that are fed into "Thread.sleep(int number)",
implemented in the method "action()";*/
public int setDelay(Worm worm) {
int delay = 1500 / worm.getLength();
return delay;
}
/*this method takes the calculated new piece from method "calculateNewPiece"
from class "Worm" and does the following:
# checks whether the new piece of the worm is within the confines of the field.
# if the new piece is within these confines the method "move()" from class "Worm" is called.
# if the worm bupms into an apple, the method "grow()" from class "Worm" and
the method "replaceEatenApple(...)" from class "AvoidOverlappingPieces" are called.
# if the new piece is outside the confines of the field the game is terminated.*/
public void action() {
int x_nextStep = this.worm.calculateNewPiece().getX();
int y_nextStep = this.worm.calculateNewPiece().getY();
if (x_nextStep >= 0 && (x_nextStep + this.pieceWidth) <= this.fieldWidth
&& y_nextStep >= 0 && (y_nextStep + this.pieceLength) <= this.fieldLength) {
try {
Thread.sleep(this.setDelay(this.worm));
} catch (Exception ex) {
}
this.worm.move();
if (this.worm.runsInto(this.apple)) {
this.worm.setNumberOfApples();
this.getWorm().getFrame().setTitle("Number of Apples: " + (this.worm.getNumberOfApples()));
this.worm.grow();
this.apple = this.properPieces.replaceEatenApple(this.worm, this.worm.getPieces(), this.fieldWidth, this.fieldLength, this.pieceWidth, this.pieceLength);
}
} else {
this.getWorm().getFrame().setTitle("The worm has run into a wall!!! "
+ "Total Number of Apples: " + (this.worm.getNumberOfApples()));
try {
Thread.sleep(10000);
} catch (Exception ex) {
}
System.exit(0);
}
}
public int getNumberOfApples() {
return this.applesNumber;
}
}
7.
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
public class KeyboardListener implements KeyListener {
private WormGame wormGame;
public KeyboardListener(WormGame wormGame) {
this.wormGame = wormGame;
}
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
this.wormGame.getWorm().setDirection(Direction.LEFT);
} else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
this.wormGame.getWorm().setDirection(Direction.RIGHT);
} else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
this.wormGame.getWorm().setDirection(Direction.DOWN);
} else if (e.getKeyCode() == KeyEvent.VK_UP) {
this.wormGame.getWorm().setDirection(Direction.UP);
}
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
}
8.
import java.awt.Color;
import javax.swing.JPanel;
import java.awt.Graphics;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.Timer;
public class DrawingBoard extends JPanel implements ActionListener {
private WormGame wormGame;
/*"Timer" sets the speed with which the worm moves.
Since in our case the worm accelerates with every eaten apple through
"Thread.sleep()" applied in method "action()", class "WormGame", the "Timer" here
sets the maximum speed.
In other words:
the initial speed (i.e. delay in the movement of the worm) is the sum of
the value of the integer in "Timer", which is 400, plus the value of the integer in
"Thread.sleep", which is 1500, totalling to 1900.
So, theoretically the value in "Thread.sleep" could be reduced to nearly zero, thus
the maximum speed of 400 milliseconds delay remains the upper speed limit.
Of course these values could be changed as per the desire of the player.*/
Timer tm = new Timer(400, this);
public DrawingBoard(WormGame wormGame) {
this.wormGame = wormGame;
super.setBackground(Color.WHITE);
}
/*this method paints the initial positions of the apple and the worm*/
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
for (Piece w : this.wormGame.getWorm().getPieces()) {
g.fill3DRect(w.getX(), w.getY(), w.getWidth(), w.getLength(), true);
}
g.setColor(Color.RED);
g.fillOval(this.wormGame.getApple().getX(), this.wormGame.getApple().getY(),
this.wormGame.getApple().getWidth(), this.wormGame.getApple().getLength());
tm.start();
}
/*this method paints the positions of the apple and the worm as they move across the board*/
public void actionPerformed(ActionEvent event) {
this.wormGame.action();
repaint();
}
}
9.
import java.awt.Container;
import java.awt.Dimension;
import javax.swing.WindowConstants;
import javax.swing.JFrame;
public class UserInterface implements Runnable {
private JFrame frame;
private WormGame wormGame;
public UserInterface(WormGame wormGame) {
this.wormGame = wormGame;
}
public void run() {
frame = this.wormGame.getWorm().getFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.createCompoent(frame.getContentPane());
frame.pack();
frame.setVisible(true);
}
private void createCompoent(Container container) {
DrawingBoard board = new DrawingBoard(this.wormGame);
container.add(board);
KeyboardListener kL = new KeyboardListener(this.wormGame);
frame.addKeyListener(kL);
container.setPreferredSize(new Dimension(this.wormGame.getfieldWidth(), this.wormGame.getfieldLength()));
}
}
10.
import javax.swing.SwingUtilities;
public class Run {
public static void main(String[] args) {
WormGame game = new WormGame(510, 510);
UserInterface ui = new UserInterface(game);
SwingUtilities.invokeLater(ui);
}
}
主要观察结果和修复: 方向处理: 在 move() 方法中,存在一个问题,即 LEFT 和 DOWN 的方向逻辑与 RIGHT 的逻辑重复。调整这些条件以确保正确的运动。
成长逻辑: Growth()方法将growPiece设置为提前两个单位,但这种逻辑可能会导致不自然的增长。相反,直接将growPiece设置在当前蠕虫的末尾,让move()自然地整合它。
运动动态:
在《百战天虫地带》这样的游戏中,成长只会在移动后发生。 不要直接向棋子中添加 GrowthPiece,而是调整 move() 方法,以包含基于 GrowthPiece 是否为 null 的条件添加逻辑。 修剪蠕虫: 仅当蠕虫超出允许的尺寸时才应修剪其长度,并且无论生长如何,都会发生这种情况。
public class Worm {
private int x;
private int y;
private Direction direction;
private List<Piece> pieces;
private Piece growPiece;
public Worm(int originalX, int originalY, Direction originalDirection) {
this.x = originalX;
this.y = originalY;
this.direction = originalDirection;
this.pieces = new ArrayList<>();
this.pieces.add(new Piece(this.x, this.y));
this.growPiece = null;
}
public List<Piece> getPieces() {
return this.pieces;
}
public void move() {
// Determine the new head position
Piece head = this.pieces.get(this.pieces.size() - 1);
int newX = head.getX();
int newY = head.getY();
if (this.direction == Direction.RIGHT) {
newX++;
} else if (this.direction == Direction.LEFT) {
newX--;
} else if (this.direction == Direction.DOWN) {
newY++;
} else if (this.direction == Direction.UP) {
newY--;
}
// Add the new head
this.pieces.add(new Piece(newX, newY));
// Handle growth logic
if (this.growPiece != null) {
this.pieces.add(this.growPiece);
this.growPiece = null;
}
// Trim the worm if its size exceeds the allowed limit
if (this.pieces.size() > 3 && this.growPiece == null) {
this.pieces.remove(0);
}
}
public void grow() {
// Signal growth by setting growPiece
Piece head = this.pieces.get(this.pieces.size() - 1);
int growX = head.getX();
int growY = head.getY();
if (this.direction == Direction.RIGHT) {
growX++;
} else if (this.direction == Direction.LEFT) {
growX--;
} else if (this.direction == Direction.DOWN) {
growY++;
} else if (this.direction == Direction.UP) {
growY--;
}
this.growPiece = new Piece(growX, growY);
}
}
这与蠕虫区域有何关系: 平滑生长:与《蠕虫区》一样,此实现确保蠕虫仅在移动后生长,从而创建自然的游戏流程。 动态运动:蠕虫的头部根据方向移动,尾巴动态调整,类似于蠕虫区处理运动机制的方式。 受控生长:生长是有条件的,确保蠕虫不会无限期地生长,除非游戏条件(例如在蠕虫区吃苹果)允许。
关键要点: 您修改后的实施反映了《蠕虫区》的机制,强调平滑的运动和受控的生长动态。这种方法确保玩家体验和游戏逻辑保持直观和愉快。