A simple Game of Life implementation in Java
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

363 lines
12 KiB

  1. package edu.stuy.goldfish;
  2. import java.util.Random;
  3. import java.util.concurrent.atomic.AtomicBoolean;
  4. import java.util.concurrent.atomic.AtomicInteger;
  5. import java.awt.*;
  6. import java.awt.event.*;
  7. import java.awt.image.*;
  8. import javax.swing.*;
  9. import javax.swing.event.*;
  10. public class Render extends Canvas implements Runnable, MouseListener,
  11. MouseMotionListener, ActionListener {
  12. private static final long serialVersionUID = 1L;
  13. public static String title;
  14. public static int width;
  15. public static int height;
  16. public static int scale;
  17. public int fps_now;
  18. public boolean paused;
  19. public String rule;
  20. public boolean reset;
  21. private AtomicBoolean[] _flags;
  22. private AtomicInteger _turn;
  23. private Grid _grid;
  24. private int[] _pixels;
  25. private BufferedImage _image;
  26. private long _lastTick;
  27. private String[] _rules;
  28. private JFrame _frame;
  29. private JButton pauseButton;
  30. private Random random = new Random();
  31. private int _drawState;
  32. public Render(int width, int height, Grid g, String[] rules) {
  33. addMouseListener(this);
  34. addMouseMotionListener(this);
  35. Render.title = "Goldfish: " + rules[0];
  36. Render.width = width;
  37. Render.height = height;
  38. setScale();
  39. paused = false;
  40. reset = false;
  41. rule = rules[0];
  42. _flags = new AtomicBoolean[2];
  43. for (int i = 0; i < _flags.length; i++)
  44. _flags[i] = new AtomicBoolean();
  45. _turn = new AtomicInteger();
  46. _grid = g;
  47. _image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
  48. _pixels = ((DataBufferInt) _image.getRaster().getDataBuffer()).getData();
  49. _lastTick = 0;
  50. _rules = rules;
  51. _drawState = -1;
  52. fps_now = 15;
  53. setFrame();
  54. }
  55. public Render(int width, int height, Grid g) {
  56. this(width, height, g, new String[0]);
  57. }
  58. public Render(int width, int height) {
  59. this(width, height, new Grid(width, height));
  60. }
  61. public Render() {
  62. this(256, 256);
  63. }
  64. /* Set the grid's scale factor so smaller grids appear larger. */
  65. private void setScale() {
  66. if (height <= 128 || width <= 128) {
  67. Render.scale = 4;
  68. } else if (height <= 256 || width <= 256) {
  69. Render.scale = 2;
  70. } else {
  71. Render.scale = 1;
  72. }
  73. }
  74. /* Set up the window frame with various buttons. */
  75. private void setFrame() {
  76. JMenuBar menuBar = new JMenuBar();
  77. JMenu menuAlgo = new JMenu("Algorithms");
  78. menuAlgo.setFont(new Font("Arial", 1, 12));
  79. menuAlgo.setPreferredSize(new Dimension(75, 0));
  80. for (String rule : _rules) {
  81. JMenuItem menuAlgoItem = new JMenuItem(rule);
  82. menuAlgo.add(menuAlgoItem);
  83. menuAlgoItem.addActionListener(this);
  84. }
  85. menuBar.add(menuAlgo);
  86. pauseButton = new JButton("Pause");
  87. pauseButton.setActionCommand("pause");
  88. pauseButton.setFont(new Font("Arial", 0, 12));
  89. pauseButton.setPreferredSize(new Dimension(90, 0));
  90. menuBar.add(pauseButton);
  91. pauseButton.addActionListener(this);
  92. JButton resetButton = new JButton("Reset");
  93. resetButton.setActionCommand("reset");
  94. resetButton.setFont(new Font("Arial", 0, 12));
  95. menuBar.add(resetButton);
  96. resetButton.addActionListener(this);
  97. JButton randomButton = new JButton("Random");
  98. randomButton.setActionCommand("random");
  99. randomButton.setFont(new Font("Arial", 0, 12));
  100. menuBar.add(randomButton);
  101. randomButton.addActionListener(this);
  102. JButton clearButton = new JButton("Clear");
  103. clearButton.setActionCommand("clear");
  104. clearButton.setFont(new Font("Arial", 0, 12));
  105. menuBar.add(clearButton);
  106. clearButton.addActionListener(this);
  107. JSlider framesPerSecond = new JSlider(JSlider.HORIZONTAL, 0, 30, 15);
  108. ChangeListener fpschange = new ChangeListener() {
  109. @Override
  110. public void stateChanged(ChangeEvent event) {
  111. JSlider source = (JSlider) event.getSource();
  112. if (!source.getValueIsAdjusting()) {
  113. int fps = (int) source.getValue();
  114. if (fps == 0) {
  115. paused = true;
  116. pauseButton.setText("Unpause");
  117. } else {
  118. paused = false;
  119. fps_now = fps;
  120. pauseButton.setText("Pause");
  121. }
  122. }
  123. }
  124. };
  125. framesPerSecond.addChangeListener(fpschange);
  126. framesPerSecond.setMajorTickSpacing(10);
  127. framesPerSecond.setMinorTickSpacing(1);
  128. framesPerSecond.setPaintTicks(true);
  129. framesPerSecond.setPaintLabels(true);
  130. framesPerSecond.setFont(new Font("Arial", 0, 12));
  131. framesPerSecond.setPreferredSize(new Dimension(100, 0));
  132. menuBar.add(framesPerSecond);
  133. menuBar.setPreferredSize(new Dimension(width * scale, 40));
  134. setPreferredSize(new Dimension(width * scale, height * scale));
  135. _frame = new JFrame();
  136. _frame.setJMenuBar(menuBar);
  137. _frame.setResizable(false);
  138. _frame.setTitle(title);
  139. _frame.add(this);
  140. _frame.pack();
  141. _frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  142. _frame.setLocationRelativeTo(null);
  143. _frame.setVisible(true);
  144. }
  145. /* Actually draw the grid to the screen. */
  146. private void update() {
  147. int state;
  148. int states = Goldfish.getMaxStates(rule);
  149. for (int i = 0; i < width; i++) {
  150. for (int j = 0; j < height; j++) {
  151. state = _grid.getPatch(i, j).getState();
  152. draw(i, j, (int) ((state / ((double) states - 1)) * 0xffffff));
  153. }
  154. }
  155. }
  156. /* Acquire a lock so two threads don't modify the grid at the same time. */
  157. public void acquireLock(int thread) {
  158. int other = (thread == 0 ? 1 : 0);
  159. _flags[thread].set(true);
  160. while (_flags[other].get() == true) {
  161. if (_turn.get() != thread) {
  162. _flags[thread].set(false);
  163. while (_turn.get() != thread) {}
  164. _flags[thread].set(true);
  165. }
  166. }
  167. }
  168. /* Release the lock when a thread is finished modifying the grid. */
  169. public void releaseLock(int thread) {
  170. int other = (thread == 0 ? 1 : 0);
  171. _turn.set(other);
  172. _flags[thread].set(false);
  173. }
  174. /* Main method to render the grid at its current state. */
  175. public void run() {
  176. BufferStrategy bs;
  177. Graphics g;
  178. bs = getBufferStrategy();
  179. if (bs == null) {
  180. createBufferStrategy(1);
  181. return;
  182. }
  183. update();
  184. g = bs.getDrawGraphics();
  185. g.drawImage(_image, 0, 0, getWidth(), getHeight(), null);
  186. g.dispose();
  187. bs.show();
  188. }
  189. /* Maintain a maximum FPS. */
  190. public void sleep() {
  191. long since = System.currentTimeMillis() - _lastTick;
  192. if (since < 1000 / fps_now) {
  193. try {
  194. Thread.sleep(1000 / fps_now - since);
  195. } catch (InterruptedException e) {
  196. return;
  197. }
  198. }
  199. _lastTick = System.currentTimeMillis();
  200. }
  201. /* Set the grid to be rendered. */
  202. public void setGrid(Grid g) {
  203. _grid = g;
  204. }
  205. /* Draw a certain color at (x, y) to the screen. */
  206. public void draw(int x, int y, int color) {
  207. if (_pixels[x + y * width] != color)
  208. _pixels[x + y * width] = color;
  209. }
  210. /* Set the state of all patches in the grid to zero. */
  211. public void clear() {
  212. acquireLock(1);
  213. for (int i = 0; i < _grid.getWidth(); i++) {
  214. for (int j = 0; j < _grid.getHeight(); j++) {
  215. _grid.getPatch(i, j).setState(0);
  216. }
  217. }
  218. releaseLock(1);
  219. }
  220. /* Set each patch in the grid to a random state. */
  221. public void randomize() {
  222. acquireLock(1);
  223. for (int i = 0; i < _grid.getWidth(); i++) {
  224. for (int j = 0; j < _grid.getHeight(); j++) {
  225. _grid.getPatch(i, j).setState(random.nextInt(Goldfish.getMaxStates(rule)));
  226. }
  227. }
  228. releaseLock(1);
  229. }
  230. /* Handle a mouse event by modifying the patch the mouse is over. */
  231. private void mouseDraw(MouseEvent e) {
  232. int states = Goldfish.getMaxStates(rule);
  233. if (e.getX() < 0 || e.getY() < 0 || e.getX() / scale >= width || e.getY() / scale >= height)
  234. return;
  235. Patch p = _grid.getPatch(e.getX() / scale, e.getY() / scale);
  236. if (_drawState == -1) {
  237. if (p.getState() == 0) {
  238. if (SwingUtilities.isLeftMouseButton(e))
  239. _drawState = states - 1;
  240. else if (SwingUtilities.isRightMouseButton(e))
  241. _drawState = 1;
  242. } else {
  243. _drawState = 0;
  244. }
  245. }
  246. p.setState(_drawState);
  247. e.consume();
  248. }
  249. /* Called when the mouse is dragged over a pixel. */
  250. @Override
  251. public void mouseDragged(MouseEvent e) {
  252. mouseDraw(e);
  253. }
  254. /* Called whenever the mouse is clicked. */
  255. @Override
  256. public void mouseClicked(MouseEvent e) {
  257. }
  258. /* Called whenever the mouse enters the window. */
  259. @Override
  260. public void mouseEntered(MouseEvent e) {
  261. }
  262. /* Called whenever the mouse exits the window. */
  263. @Override
  264. public void mouseExited(MouseEvent e) {
  265. }
  266. /* Called whenever the mouse is pressed. */
  267. @Override
  268. public void mousePressed(MouseEvent e) {
  269. mouseDraw(e);
  270. }
  271. /* Called whenever the mouse is released from being pressed. */
  272. @Override
  273. public void mouseReleased(MouseEvent e) {
  274. _drawState = -1;
  275. }
  276. /* Called whenever the mouse is moved, pressed or not. */
  277. @Override
  278. public void mouseMoved(MouseEvent e) {
  279. }
  280. /* Hook to handle button presses and menu choices. */
  281. @Override
  282. public void actionPerformed(ActionEvent event) {
  283. if ("pause".equals(event.getActionCommand())) {
  284. if (paused) {
  285. paused = false;
  286. pauseButton.setText("Pause");
  287. } else {
  288. paused = true;
  289. pauseButton.setText("Unpause");
  290. }
  291. } else if ("reset".equals(event.getActionCommand())) {
  292. clear();
  293. reset = true;
  294. } else if ("random".equals(event.getActionCommand())) {
  295. randomize();
  296. } else if ("clear".equals(event.getActionCommand())) {
  297. clear();
  298. } else {
  299. String oldRule = rule;
  300. rule = event.getActionCommand();
  301. if (oldRule.equals("Brian's Brain") && !rule.equals("Brian's Brain")) {
  302. for (int i = 0; i < _grid.getWidth(); i++) {
  303. for (int j = 0; j < _grid.getHeight(); j++) {
  304. if (_grid.getPatch(i,j).getState() == 2)
  305. _grid.getPatch(i,j).setState(1);
  306. }
  307. }
  308. } else if (!oldRule.equals("Brian's Brain") && rule.equals("Brian's Brain")) {
  309. for (int i = 0; i < _grid.getWidth(); i++) {
  310. for (int j = 0; j < _grid.getHeight(); j++) {
  311. if (_grid.getPatch(i,j).getState() == 1)
  312. _grid.getPatch(i,j).setState(2);
  313. }
  314. }
  315. }
  316. title = "Goldfish: " + rule;
  317. _frame.setTitle(title);
  318. }
  319. }
  320. }