[英]Java Game of life wrap around
我有一个运行中的生活游戏,但是我不知道的一件事是如何环绕网格或木板,我想这与邻居数和网格有关,我需要一种方法来指示数组包装。
规则 :
生命游戏的宇宙是由正方形单元组成的无限二维正交网格,
每种状态都处于两种可能的状态之一,即生存或死亡。
每个单元格都与其八个邻居互动,这八个邻居是直接水平的单元格,
垂直或对角线相邻。 在每个时间步上,都会发生以下转换:
1,任何少于两个活邻居的活细胞都会死亡,好像是由于人口不足造成的。
2.具有三个以上活邻居的任何活细胞都会死亡,好像人满为患。
3.任何有两个或三个活邻居的活细胞都可以存活到下一代。
4,任何有三个活邻居的死细胞都变成活细胞。
初始模式构成了系统的种子。 第一代是通过将上述规则同时应用于种子中的每个细胞而创建的-生与死同时发生。
这是一些与网格/板有关的代码; 称为CellsGrid单元;
GameOfLife2(int nbRow, int nbCol) {
super(" New GameOfLife");
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// create the labels (2 more on each size) these wont be shown
// but will be used in calculating the cells alive around
Cells = new CellsGrid[nbRow+2][nbCol+2];
for(int r = 0; r < nbRow+2; r++) {
for(int c = 0; c < nbCol+2; c++) {
Cells[r][c] = new CellsGrid();
}
}
for(int r = 1; r < nbRow+1; r++) {
for(int c = 1; c < nbCol+1; c++) {
panel.add(Cells[r][c]);
Cells[r][c].addNeighbour(Cells[r-1][c]); // North
Cells[r][c].addNeighbour(Cells[r+1][c]); // South
Cells[r][c].addNeighbour(Cells[r][c-1]); // West
Cells[r][c].addNeighbour(Cells[r][c+1]); // East
Cells[r][c].addNeighbour(Cells[r-1][c-1]); // North West
Cells[r][c].addNeighbour(Cells[r-1][c+1]); // North East
Cells[r][c].addNeighbour(Cells[r+1][c-1]); // South West
Cells[r][c].addNeighbour(Cells[r+1][c+1]); // South East
}
}
if(!gameRunning)
return;
++generation;
CellsIteration.setText("Generation: " + generation);
for(int r = 0; r < Cells.length; r++) {
for(int c = 0; c < Cells[r].length; c++) {
Cells[r][c].checkState();
}
}
for(int r = 0; r < Cells.length; r++) {
for(int c = 0; c < Cells[r].length; c++) {
Cells[r][c].updateState();
}
}
}
void checkState() {
// number alive around
int NumNeighbours = 0; // number alive neighbours
// see the state of my neighbour
for(int i = 0; i < numNeighbours; i++)
NumNeighbours += neighbour[i].state;
// newState
if(state == 1) { // if alive
if(NumNeighbours < 2) // 1.Any live cell with fewer than two live neighbours dies
newState = 0;
if(NumNeighbours > 3) // 2.Any live cell with more than three live neighbours dies
newState = 0;
}
else {
if(NumNeighbours == 3) // 4.Any dead cell with exactly three live neighbours becomes a live cell
newState = 1;
}
}
完整代码:
package com.ggl.life;
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;
public class GameOfLife2 extends JFrame implements ActionListener {
/**
*
*/
public static Random random = new Random();
private static final long serialVersionUID = 1L;
static final Color[] color = {Color.YELLOW, Color.BLACK};
// size in pixel of every label
static final int size = 15;
static final Dimension dim = new Dimension(size, size);
static final int GenDelay = 200;
// the cells labels
private CellsGrid[][] Cells;
// timer that fires the next generation
private Timer timer;
// generation counter
private int generation = 0;
private JLabel CellsIteration = new JLabel("Generation: 0");
// the 3 buttons
private JButton clearBtn = new JButton("Clear"),
PauseBtn = new JButton("Pause"),
StartBtn = new JButton("Start");
// the slider for the speed
// state of the game (running or pause)
private boolean gameRunning = false;
// if the mouse is down or not
private boolean mouseDown = false;
GameOfLife2(int nbRow, int nbCol) {
super(" New GameOfLife");
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// create the labels (2 more on each size) these wont be shown
// but will be used in calculating the cells alive around
Cells = new CellsGrid[nbRow+2][nbCol+2];
for(int r = 0; r < nbRow+2; r++) {
for(int c = 0; c < nbCol+2; c++) {
Cells[r][c] = new CellsGrid();
}
}
// panel in the center with the labels
JPanel panel = new JPanel(new GridLayout(nbRow, nbCol, 1, 1));
panel.setBackground(Color.BLACK);
panel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
// add each label (not the one on the border) to the panel and add to each of them its neighbours
for(int r = 1; r < nbRow+1; r++) {
for(int c = 1; c < nbCol+1; c++) {
panel.add(Cells[r][c]);
Cells[r][c].addNeighbour(Cells[r-1][c]);
//Cells[r][c].addNeighbour(getCellSafe(r-1, c)); // North
Cells[r][c].addNeighbour(Cells[r+1][c]); // South
//Cells[r][c].addNeighbour(getCellSafe(r+1, c));
Cells[r][c].addNeighbour(Cells[r][c-1]); // West
//Cells[r][c].addNeighbour(getCellSafe(r, c-1));
Cells[r][c].addNeighbour(Cells[r][c+1]); // East
//Cells[r][c].addNeighbour(getCellSafe(r, c+1));
Cells[r][c].addNeighbour(Cells[r-1][c-1]); // North West
//Cells[r][c].addNeighbour(getCellSafe(r-1, c-1));
Cells[r][c].addNeighbour(Cells[r-1][c+1]); // North East
//Cells[r][c].addNeighbour(getCellSafe(r-1, c+1));
Cells[r][c].addNeighbour(Cells[r+1][c-1]); // South West
//Cells[r][c].addNeighbour(getCellSafe(r+1, c-1));
Cells[r][c].addNeighbour(Cells[r+1][c+1]); // South East
//Cells[r][c].addNeighbour(getCellSafe(r+1, +c));
}
}
// now the panel can be added
add(panel, BorderLayout.CENTER);
// the bottom panel with the buttons the generation label and the slider
// this panel is formed grid panels
panel = new JPanel(new GridLayout(1,3));
// another panel for the 3 buttons
JPanel buttonPanel = new JPanel(new GridLayout(1,3));
clearBtn.addActionListener(this);
buttonPanel.add(clearBtn);
PauseBtn.addActionListener(this);
PauseBtn.setEnabled(false); // game is pause the pause button is disabled
buttonPanel.add(PauseBtn);
StartBtn.addActionListener(this);
buttonPanel.add(StartBtn);
// add the 3 buttons to the panel
panel.add(buttonPanel);
// the generation label
CellsIteration.setHorizontalAlignment(SwingConstants.CENTER);
panel.add(CellsIteration);
// in the JFrame
add(panel, BorderLayout.NORTH);
// put the frame on
setLocation(20, 20);
pack(); // adjust to the window size
setVisible(true);
// start the thread that run the cycles of life
timer = new Timer(GenDelay , this);
}
private CellsGrid getCellSafe(int r0, int c0) {
int r = r0 % Cells.length; // Cells.length is effectively nbRow
if (r < 0) r += Cells.length; // deal with how % works for negatives
int c = c0 % Cells[0].length; // Cells[0].length is effectively nbCol
if (c < 0) c += Cells[0].length; // deal with how % works for negatives
return Cells[r][c];
}
//end of game of life
// called by the Timer and the JButtons
public synchronized void actionPerformed(ActionEvent e) {
// test the JButtons first
Object o = e.getSource();
// the clear button
if(o == clearBtn) {
timer.stop(); // stop timer
gameRunning = false; // flag gamme not running
PauseBtn.setEnabled(false); // disable pause button
StartBtn.setEnabled(true); // enable go button
// clear all cells
for(int r = 1; r < Cells.length ; r++) {
for(int c = 1; c < Cells[r].length ; c++) {
Cells[r][c].clear();
}
}
// reset generation number and its label
generation = 0;
CellsIteration.setText("Generation: 0");
return;
}
// the pause button
if(o == PauseBtn) {
timer.stop(); // stop timer
gameRunning = false; // flag not running
PauseBtn.setEnabled(false); // disable myself
StartBtn.setEnabled(true); // enable go button
return;
}
// the go button
if(o == StartBtn) {
PauseBtn.setEnabled(true); // enable pause button
StartBtn.setEnabled(false); // disable myself
gameRunning = true; // flag game is running
timer.setDelay(GenDelay);
timer.start();
return;
}
// not a JButton so it is the timer
// set the delay for the next time
timer.setDelay(GenDelay);
// if the game is not running wait for next time
if(!gameRunning)
return;
++generation;
CellsIteration.setText("Generation: " + generation);
for(int r = 0; r < Cells.length; r++) {
for(int c = 0; c < Cells[r].length; c++) {
Cells[r][c].checkState();
}
}
for(int r = 0; r < Cells.length; r++) {
for(int c = 0; c < Cells[r].length; c++) {
Cells[r][c].updateState();
}
}
}
//end of action
// to start the whole thing as a Java application
public static void main(String[] arg) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new GameOfLife2(50, 50);
}
});
}
// A class that extends JLabel but also check for the neigbour
// when asked to do so
class CellsGrid extends JLabel implements MouseListener {
/**
*
*/
private static final long serialVersionUID = 1L;
private int state, newState;
private int numNeighbours;
private CellsGrid[] neighbour = new CellsGrid[8]; // array of total neighbours with possibility of 8
CellsGrid() {
state = newState = 0; // Dead
setOpaque(true); // so color will be showed
setBackground(color[0]); //set colour of dead cell
addMouseListener(this); // to select new LIVE cells
this.setPreferredSize(dim); //set size a new cells
}
// to add a neighbour
void addNeighbour(CellsGrid n) {
neighbour[numNeighbours++] = n;
}
// to see if I should live or not
void checkState() {
// number alive around
int NumNeighbours = 0; // number alive neighbours
// see the state of my neighbour
for(int i = 0; i < numNeighbours; i++)
NumNeighbours += neighbour[i].state;
// newState
if(state == 1) { // if alive
if(NumNeighbours < 2) // 1.Any live cell with fewer than two live neighbours dies
newState = 0;
if(NumNeighbours > 3) // 2.Any live cell with more than three live neighbours dies
newState = 0;
}
else {
if(NumNeighbours == 3) // 4.Any dead cell with exactly three live neighbours becomes a live cell
newState = 1;
}
}
// after the run switch the state to new state
void updateState() {
if(state != newState) { // do the test to avoid re-setting same color for nothing
state = newState;
setBackground(color[state]);
}
}
// called when the game is reset/clear
void clear() {
if(state == 1 || newState == 1) {
state = newState = 0;
setBackground(color[state]);
}
}
@Override
public void mouseClicked(MouseEvent arg0) {
}
// if the mouse enter a cell and it is down we make the cell alive
public void mouseEntered(MouseEvent arg0) {
if(mouseDown) {
state = newState = 1;
setBackground(color[1]);
}
}
@Override
public void mouseExited(MouseEvent arg0) {
}
// if the mouse is pressed on a cell you register the fact that it is down
// and make that cell alive
public void mousePressed(MouseEvent arg0) {
mouseDown = true;
state = newState = 1;
setBackground(color[1]);
}
// turn off the fact that the cell is down
public void mouseReleased(MouseEvent arg0) {
mouseDown = false;
}
}
}
作为UnholySheep,您需要学习%
运算符。 %
是计算除法又称为模的提醒的方法。 参见https://en.wikipedia.org/wiki/Modulo_operation
在这里应用它的最简单方法是这样的。 首先介绍getCellSafe
方法
private CellsGrid getCellSafe(int r0, int c0) {
int r = r0 % Cells.length; // Cells.length is effectively nbRow
if (r < 0) r += Cells.length; // deal with how % works for negatives
int c = c0 % Cells[0].length; // Cells[0].length is effectively nbCol
if (c < 0) c += Cells[0].length; // deal with how % works for negatives
return Cells[r][c];
}
然后使用这种方法直接访问,例如
Cells[r][c].addNeighbour(getCellSafe(r-1, c); // North
并摆脱nbCol
和nbRow
中的+ 2
“ L形”的错误在“东南”行的副本中,其中您将c+1
转换为+c
Cells[r][c].addNeighbour(Cells[r+1][c+1]); // South East //Cells[r][c].addNeighbour(getCellSafe(r+1, +c));
通常,您的代码非常糟糕。 有很多逻辑重复(请参阅DRY原理 )和其他不良代码,例如庞大的ActionListener
。 这是我尝试清理一下的尝试:
public class GameOfLife2 extends JFrame { /** * */ public static Random random = new Random(); static final Color[] color = {Color.YELLOW, Color.BLACK}; // size in pixel of every label static final int cellSize = 15; static final Dimension cellDim = new Dimension(cellSize, cellSize); static final int GenDelay = 200; // the cells labels private CellsGrid[][] Cells; // that fires the next generation private Timer timer; // generation counter private int generation = 0; private JLabel CellsIteration = new JLabel("Generation: 0"); // the 3 buttons private JButton clearBtn = new JButton("Clear"); private JButton PauseBtn = new JButton("Pause"); private JButton StartBtn = new JButton("Start"); private JButton StepBtn = new JButton("Step"); // the slider for the speed // state of the game (running or pause) private boolean gameRunning = false; GameOfLife2(int nbRow, int nbCol) { super("New GameOfLife"); setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); Cells = new CellsGrid[nbRow][nbCol]; for (int r = 0; r < nbRow; r++) { for (int c = 0; c < nbCol; c++) { Cells[r][c] = new CellsGrid(); } } // panel in the center with the labels JPanel panel = new JPanel(new GridLayout(nbRow, nbCol, 1, 1)); panel.setBackground(Color.BLACK); panel.setBorder(BorderFactory.createLineBorder(Color.BLACK)); // add each label (not the one on the border) to the panel and add to each of them its neighbours for (int r = 0; r < nbRow; r++) { for (int c = 0; c < nbCol; c++) { CellsGrid curCell = Cells[r][c]; panel.add(curCell); for (int dr = -1; dr <= 1; dr++) { for (int dc = -1; dc <= 1; dc++) { CellsGrid neighbor = getCellSafe(r + dr, c + dc); if (neighbor != curCell) curCell.addNeighbour(neighbor); } } } } // now the panel can be added add(panel, BorderLayout.CENTER); // the bottom panel with the buttons the generation label and the slider Box headerPanel = Box.createHorizontalBox(); Box buttonPanel = Box.createHorizontalBox(); // old pre-Java 8 syntax //clearBtn.addActionListener(new ActionListener() //{ // @Override // public void actionPerformed(ActionEvent e) // { // clearGame(); // } //}); clearBtn.addActionListener((e) -> clearGame()); // holy Java 8 lambda syntax buttonPanel.add(clearBtn); PauseBtn.addActionListener((e) -> stopGame()); buttonPanel.add(PauseBtn); StartBtn.addActionListener((e) -> startGame()); buttonPanel.add(StartBtn); StepBtn.addActionListener((e) -> stepToNextGeneration()); buttonPanel.add(StepBtn); // the generation label CellsIteration.setHorizontalAlignment(SwingConstants.CENTER); headerPanel.add(Box.createHorizontalStrut(10)); headerPanel.add(buttonPanel); headerPanel.add(Box.createHorizontalStrut(10)); headerPanel.add(Box.createHorizontalGlue()); headerPanel.add(Box.createHorizontalStrut(10)); headerPanel.add(CellsIteration); headerPanel.add(Box.createHorizontalGlue()); // if you want "generation" label closer to center headerPanel.add(Box.createHorizontalStrut(10)); // in the JFrame add(headerPanel, BorderLayout.NORTH); // put the frame on setLocation(20, 20); gameRunning = false; generation = 0; updateGenerationTitle(); updateButtonsState(); pack(); // adjust to the window size setMinimumSize(new Dimension(600, 500)); setVisible(true); // start the thread that run the cycles of life timer = new Timer(GenDelay, (e) -> onTimerStep()); } private CellsGrid getCellSafe(int r0, int c0) { int r = r0 % Cells.length; // Cells.length is effectively nbRow if (r < 0) r += Cells.length; // deal with how % works for negatives int c = c0 % Cells[0].length; // Cells[0].length is effectively nbCol if (c < 0) c += Cells[0].length; // deal with how % works for negatives return Cells[r][c]; } private void updateButtonsState() { PauseBtn.setEnabled(gameRunning); StartBtn.setEnabled(!gameRunning); StepBtn.setEnabled(!gameRunning); } private void updateGenerationTitle() { CellsIteration.setText("Generation: " + generation); } private void startGame() { gameRunning = true; // flag game is running updateButtonsState(); timer.setDelay(GenDelay); timer.start(); } private void stopGame() { timer.stop(); // stop timer gameRunning = false; // flag not running updateButtonsState(); } private void clearGame() { stopGame(); // clear all cells for (int r = 0; r < Cells.length; r++) { for (int c = 0; c < Cells[r].length; c++) { Cells[r][c].clear(); } } // reset generation number and its label generation = 0; updateGenerationTitle(); } private void stepToNextGeneration() { ++generation; updateGenerationTitle(); for (int r = 0; r < Cells.length; r++) { for (int c = 0; c < Cells[r].length; c++) { Cells[r][c].checkState(); } } for (int r = 0; r < Cells.length; r++) { for (int c = 0; c < Cells[r].length; c++) { Cells[r][c].updateState(); } } } private void onTimerStep() { if (gameRunning) stepToNextGeneration(); } // to start the whole thing as a Java application public static void main(String[] arg) { SwingUtilities.invokeLater(new Runnable() { public void run() { new GameOfLife2(50, 50); } }); } // A class that extends JLabel but also check for the neigbour // when asked to do so static class CellsGrid extends JLabel implements MouseListener { private boolean alive = false; private boolean newAlive = false; private final ArrayList<CellsGrid> neighbours = new ArrayList<>(8); // array of total neighbours with possibility of 8 CellsGrid() { setOpaque(true); // so color will be showed addMouseListener(this); // to select new LIVE cells setPreferredSize(cellDim); //set size a new cells updateColor(); } // to add a neighbour void addNeighbour(CellsGrid n) { neighbours.add(n); } // to see if I should live or not void checkState() { // number alive around int NumNeighbours = 0; // number alive neighbours // see the state of my neighbour for (CellsGrid neighbour : neighbours) NumNeighbours += neighbour.alive ? 1 : 0; // 1. Any live cell with fewer than two live neighbours dies // 2. Any live cell with two or three live neighbours lives on to the next generation. // 3. Any live cell with more than three live neighbours dies // 4. Any dead cell with exactly three live neighbours becomes a live cell if (alive) { newAlive = (NumNeighbours == 2) || (NumNeighbours == 3); } else { newAlive = (NumNeighbours == 3); } } // after the run switch the state to new state void updateState() { alive = newAlive; updateColor(); } // called when the game is reset/clear void clear() { alive = newAlive = false; updateColor(); } private void updateColor() { setBackground(color[alive ? 1 : 0]); } @Override public void mouseClicked(MouseEvent arg0) { } // if the mouse enter a cell and it is down we make the cell alive @Override public void mouseEntered(MouseEvent e) { boolean mouseDown = (e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0; if (mouseDown) { alive = newAlive = true; updateColor(); } } @Override public void mouseExited(MouseEvent e) { } @Override public void mousePressed(MouseEvent arg0) { // state = newState = true; alive = newAlive = !newAlive; //invert state is more useful, so you can clear something updateColor(); } @Override public void mouseReleased(MouseEvent arg0) { } } }
该代码似乎可以正常工作,包括滑翔机沿对角线绕场飞行。
一些注意事项:
ActionListener
拆分为独立的对象,并使用Java 8语法使它们简短易注册 updateXyz
和stepToNextGeneration
方法来大大减少代码重复 MouseEvent
已经提供了有关按钮是否按下的信息。 JComponent
)类中定义serialVersionUID并没有多大意义,因为它们实际上无法序列化(尽管它们确实实现了Serializable
接口)。 另请参见Swing组件和序列化 如果我想有一些预制的初始状态,我做了一个简单的类(更像structute) Point
只有两个字段row
和column
,有一个List
或它们的阵列,然后做了clear
和遍历集合到使列表中的那些单元格活跃起来。 如果我想走得更远,我可能会再添加一个包装类(结构) SavedGame
或一些可以节省width
和height
东西,以及一些Point
。 并使整个UI动态化,而不是在构造函数中一次构建一次; 然后增加了从某些文件保存/读取此类配置的功能
至于随机,有一个java.util.Random类,它带有有用的int nextInt(int n)
方法。 因此,我想问用户应该使多少个随机单元变为活动状态,然后循环运行直到达到该数量,并使用Random通过生成行和列来生成新的活动单元。 请注意,如果要填充大量的单元格,则可能需要检查所选的下一个随机单元格是否还活着,如果是,请再次“掷骰子”。 如果要填充更多,则需要将算法更改为更复杂,或者填充90%的单元可能要花很多时间。 一种想法可能是生成范围为(0;行*高度-已经存活的细胞数)的单个细胞索引,而不是独立的行和列,然后仅从左上角开始到右下角的细胞进行计数,仅计算死细胞,使相应的细胞存活。 这样,您可以确保每个新的活细胞都花费相同的时间来生成。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.