teaching machines

CS 1: Lecture 37 – Lights Out

Dear students,

It’s the last week! Given that we’re all stressed and ready to be done, let’s play a game today. Just kidding, let’s make one instead. We’re going to implement Lights Out, which started off as a handheld game from Tiger Electronics:

The game is played on a 5×5 grid of lights, some of which are on and some of which are off. Your task is turn all of them off. But here’s the thing: when you press a button, you toggle it and its 4-neighbors. We’ll write a graphical application that listens to mouse clicks, detects a win state, and saves and loads board configurations. Here’s our checklist:

  • Create a window.
  • Plot a 5×5 board of lights.
  • Add a gap between lights.
  • Toggle individual lights.
  • Toggle 4-neighbors as well.
  • Detect win condition.
  • Add a menu.
  • Save a board configuration to a file.
  • Load a previously saved level.
  • Export application to a runnable JAR file.

Here’s your TODO to complete before we meet again:

  • CS 145, tomorrow is our last lab. In case it hasn’t been made clear, tomorrow is your only opportunity to complete the lab 12 checkpoints. I will not have much time to help you during lab, as I will be running your peers’ robots.

See you next class!

Sincerely,

P.S. It’s time for a haiku!

I used to play games
Making them’s even harder
It’s that final boss

P.P.S. Here’s the code we wrote together…

LightsOutFrame.java

package lecture1211;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class LightsOutFrame extends JFrame implements MouseListener {
  private static final int PIXELS = 100;
  private static final int GAP = 2;

  private LightsOutPanel panel;
  private boolean[][] board;

  public LightsOutFrame() {
    board = new boolean[5][5];
//    board[2][3] = true;

    panel = new LightsOutPanel();
    panel.setPreferredSize(new Dimension(PIXELS * 5, PIXELS * 5));
    panel.addMouseListener(this);

    add(panel);
    
    JMenuItem saveLevel = new JMenuItem("Save level");
    JMenu menu = new JMenu("Level");
    JMenuBar menuBar = new JMenuBar();
    
    menu.add(saveLevel);
    menuBar.add(menu);
    setJMenuBar(menuBar);
    
    saveLevel.addActionListener(e -> {
      saveLevel();
    });

    pack();
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setVisible(true);
  }
  
  private void saveLevel() {
    try {
      PrintWriter out = new PrintWriter(new File("/Users/johnch/Desktop/level"));
      for (int r = 0; r < 5; ++r) {
        for (int c = 0; c < 5; ++c) {
          out.print(board[r][c] + " ");
        }
        out.println();
      }
      out.close();
    } catch (IOException e) {
      System.out.println(e);
    }
  }

  class LightsOutPanel extends JPanel {
    public void paintComponent(Graphics g) {
      for (int r = 0; r < 5; ++r) {
        for (int c = 0; c < 5; ++c) {
          // (c, r)
          if (board[r][c]) {
            g.setColor(Color.YELLOW);
          } else {
            g.setColor(Color.BLACK);
          }
          g.fillRect(c * PIXELS + GAP, r * PIXELS + GAP, PIXELS - 2 * GAP, PIXELS - 2 * GAP);
        }
      }
    }
  }

  @Override
  public void mouseClicked(MouseEvent e) {
  }

  @Override
  public void mousePressed(MouseEvent e) {
  }

  @Override
  public void mouseReleased(MouseEvent e) {
//    System.out.println(e);
    int r = e.getY() / PIXELS;
    int c = e.getX() / PIXELS;
    flop(c, r);
    flop(c + 1, r);
    flop(c - 1, r);
    flop(c, r - 1);
    flop(c, r + 1);
    repaint();
    
    if (isWin()) {
      JOptionPane.showMessageDialog(this, "A winner is you!");
    }
  }

  private void flop(int c,
                    int r) {
    if (r >= 0 && r < 5 && c >= 0 && c < 5) {
      board[r][c] = !board[r][c];
    }
  }
  
  private boolean isWin() {
    for (int r = 0; r < 5; ++r) {
      for (int c = 0; c < 5; ++c) {
        if (board[r][c]) {
          return false;
        }
      }
    }
    return true;
  }

  @Override
  public void mouseEntered(MouseEvent e) {
  }

  @Override
  public void mouseExited(MouseEvent e) {
  }

}

LightsOut.java

package lecture1211;

public class LightsOut {
  public static void main(String[] args) {
    new LightsOutFrame();
  }
}

LightsOutFrame.java

package lecture1211;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class LightsOutFrame extends JFrame implements MouseListener {
  private static final int PIXELS = 50;
  private static final int BORDER = 3;

  private boolean[][] board;

  public LightsOutFrame() {
    board = new boolean[5][5];
//    board[2][4] = true;

    LightsOutPanel panel = new LightsOutPanel();
    add(panel);

    panel.addMouseListener(this);

    panel.setPreferredSize(new Dimension(5 * PIXELS, 5 * PIXELS));
    pack();
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setVisible(true);
  }

  private class LightsOutPanel extends JPanel {
    public void paintComponent(Graphics g) {
      for (int r = 0; r < 5; ++r) {
        for (int c = 0; c < 5; ++c) {
          if (board[r][c]) {
            g.setColor(Color.GREEN);
          } else {
            g.setColor(Color.RED);
          }
          g.fillRect(c * PIXELS + BORDER, r * PIXELS + BORDER, PIXELS - BORDER * 2, PIXELS - BORDER * 2);
        }
      }
    }
  }

  @Override
  public void mouseClicked(MouseEvent e) {
  }

  @Override
  public void mousePressed(MouseEvent e) {
  }

  @Override
  public void mouseReleased(MouseEvent e) {
    int r = e.getY() / PIXELS;
    int c = e.getX() / PIXELS;
    flip(c, r);
    flip(c + 1, r);
    flip(c - 1, r);
    flip(c, r - 1);
    flip(c, r + 1);
    repaint();
    
    if (isWin()) {
      JOptionPane.showMessageDialog(this, "You winned!");
    }
  }
  
  private boolean isWin() {
    for (int r = 0; r < 5; ++r) {
      for (int c = 0; c < 5; ++c) {
        if (board[r][c]) {
          return false;
        }
      }
    }
    return true;
  }

  private void flip(int c,
                    int r) {
    if (c >= 0 && c < 5 && r >= 0 && r < 5) {
      board[r][c] = !board[r][c];
    }
  }

  @Override
  public void mouseEntered(MouseEvent e) {
  }

  @Override
  public void mouseExited(MouseEvent e) {
  }
}

LightsOut.java

package lecture1211;

public class LightsOut {
  public static void main(String[] args) {
    new LightsOutFrame();
  }
}

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *