Package Tetris

Source Code of Tetris.Game

package Tetris;

import Tetris.Forms.MainFrame;
import Tetris.Forms.Options;
import Tetris.Structures.Block;
import Tetris.Structures.Grid;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;

public class Game extends JPanel implements ActionListener {
  public enum KEY_COMMAND { LEFT, RIGHT, ROTATE, HOLD, FALL, DROP, PAUSE }
 
  private static final int FALL_RATE = 1000, QUEUE_SIZE = 5;
 
  // boolean flag "holdUsed" in case the user tries to stall by switching between hold back and forth
  private boolean holdUsed, paused;
  private Timer fallTimer;
 
  private Options.Difficulty difficulty;
  private Boolean multipleLives;
  private Integer lives;
 
  private JButton resumeButton;
 
  private Block holdBlock, gameBlock;
  private Block[] inQueueBlock;
 
  private Grid gameGrid, holdGrid;
  private Grid[] inQueueGrid;

  private static HashMap<Boolean, HashMap<Options.Difficulty, Integer>> highScore;
  static {
    highScore = new HashMap<Boolean, HashMap<Options.Difficulty, Integer>>();
    highScore.put(true, new HashMap<Options.Difficulty, Integer>());
    for (Options.Difficulty d : Options.Difficulty.values()) {
      highScore.get(true).put(d, 0);
    }
    highScore.put(false, new HashMap<Options.Difficulty, Integer>());
    for (Options.Difficulty d : Options.Difficulty.values()) {
      highScore.get(false).put(d, 0);
    }
  }
 
  private Integer level, linesToLevel, score, consecutive;
  private JLabel livesCountLabel, levelCountLabel, linesCountLabel, scoreCountLabel, highScoreCountLabel;
 
  // This exists because there will only be a single instance that can be tracked statically
  private static Game game;
  public static boolean isActive() { return game != null; }
 
  public Game() {
    if (game != null) { game.fallTimer.stop(); }
   
    game = this;
    this.setLayout(null);
    this.setOpaque(false);
   
    difficulty = Options.difficulty;
    multipleLives = Options.multipleLives;
    lives = multipleLives ? 3 : 1;
   
    fallTimer = new Timer(FALL_RATE, this);
    holdUsed = false;
   
    resumeButton = new JButton("Click to Resume");
    resumeButton.setFont(Program.displayFont);
    resumeButton.setBounds(215, 290, 175 ,30); //gameGrid.setBounds(150, 10, 305, 600);
    resumeButton.addActionListener(this);
   
    JLabel
    livesLabel = new JLabel("Lives:");
    livesLabel.setForeground(Program.foreground);
    livesLabel.setHorizontalAlignment(SwingConstants.CENTER);
    livesLabel.setFont(Program.displayFont(Font.BOLD, 5));
    livesLabel.setBounds(25, 225, 100, 20);
   
    livesCountLabel = new JLabel("");
    livesCountLabel.setForeground(Color.YELLOW);
    livesCountLabel.setHorizontalAlignment(SwingConstants.CENTER);
    livesCountLabel.setFont(Program.displayFont(Font.BOLD, 10));
    livesCountLabel.setBounds(25, 250, 100, 30);
   
    if (multipleLives) {
      this.add(livesLabel);
      this.add(livesCountLabel);
    }
   
    JLabel
    levelLabel = new JLabel("Level:");
    levelLabel.setForeground(Program.foreground);
    levelLabel.setHorizontalAlignment(SwingConstants.CENTER);
    levelLabel.setFont(Program.displayFont(Font.BOLD, 5));
    levelLabel.setBounds(25, 300, 100, 20);
    this.add(levelLabel);
   
    levelCountLabel = new JLabel("");
    levelCountLabel.setForeground(Color.decode("#6ED3FF"));
    levelCountLabel.setHorizontalAlignment(SwingConstants.CENTER);
    levelCountLabel.setFont(Program.displayFont(Font.BOLD, 10));
    levelCountLabel.setBounds(25, 325, 100, 30);
    this.add(levelCountLabel);
   
    JLabel
    linesLabel = new JLabel("Goal:");
    linesLabel.setForeground(Program.foreground);
    linesLabel.setHorizontalAlignment(SwingConstants.CENTER);
    linesLabel.setFont(Program.displayFont(Font.BOLD, 5));
    linesLabel.setBounds(25, 375, 100, 20);
    this.add(linesLabel);
   
    linesCountLabel = new JLabel("");
    linesCountLabel.setForeground(Color.RED);
    linesCountLabel.setHorizontalAlignment(SwingConstants.CENTER);
    linesCountLabel.setFont(Program.displayFont(Font.BOLD, 10));
    linesCountLabel.setBounds(25, 400, 100, 30);
    this.add(linesCountLabel);
   
    JLabel
    scoreLabel = new JLabel("Score:");
    scoreLabel.setForeground(Program.foreground);
    scoreLabel.setHorizontalAlignment(SwingConstants.CENTER);
    scoreLabel.setFont(Program.displayFont(Font.BOLD, 5));
    scoreLabel.setBounds(25, 450, 100, 20);
    this.add(scoreLabel);
   
    scoreCountLabel = new JLabel("");
    scoreCountLabel.setForeground(Color.decode("#6ED3FF"));
    scoreCountLabel.setHorizontalAlignment(SwingConstants.CENTER);
    scoreCountLabel.setFont(Program.displayFont(Font.BOLD, 10));
    scoreCountLabel.setBounds(25, 475, 100, 30);
    this.add(scoreCountLabel);
   
    JLabel
    highScoreLabel = new JLabel("Score:");
    highScoreLabel.setForeground(Program.foreground);
    highScoreLabel.setHorizontalAlignment(SwingConstants.CENTER);
    highScoreLabel.setFont(Program.displayFont(Font.BOLD, 5));
    highScoreLabel.setBounds(25, 525, 100, 20);
    this.add(highScoreLabel);
   
    highScoreCountLabel = new JLabel("");
    highScoreCountLabel.setForeground(Color.RED);
    highScoreCountLabel.setHorizontalAlignment(SwingConstants.CENTER);
    highScoreCountLabel.setFont(Program.displayFont(Font.BOLD, 10));
    highScoreCountLabel.setBounds(25, 550, 100, 30);
    this.add(highScoreCountLabel);
   
    consecutive = 0;
    score = 0;
    level = 0;
    linesToLevel = 0;
    updateGameLevel();
   
    JLabel
    holdLabel = new JLabel("Hold");
    holdLabel.setForeground(Program.foreground);
    holdLabel.setHorizontalAlignment(SwingConstants.CENTER);
    holdLabel.setFont(Program.displayFont(Font.BOLD, 15));
    holdLabel.setBounds(25, 20, 100, 40);
    this.add(holdLabel);
   
    holdGrid = new Grid(true);
    holdGrid.setBounds(25, 70, 100, 100);
    this.add(holdGrid);
   
    holdBlock = null;
   
    gameGrid = new Grid(false);
    gameGrid.setBounds(150, 10, 305, 600);
    this.add(gameGrid);
   
    gameBlock = new Block(gameGrid);
   
    inQueueGrid = new Grid[QUEUE_SIZE];
    inQueueBlock = new Block[QUEUE_SIZE];
   
    JLabel
    nextLabel = new JLabel("Next");
    nextLabel.setForeground(Program.foreground);
    nextLabel.setHorizontalAlignment(SwingConstants.CENTER);
    nextLabel.setFont(Program.displayFont(Font.BOLD, 15));
    nextLabel.setBounds(480, 20, 100, 40);
    this.add(nextLabel);
   
    for (int i = 0; i < QUEUE_SIZE; ++i) {
      inQueueGrid[i] = new Grid(true);
      inQueueGrid[i].setBounds(480, 70 + 110 * i, 100, 100);
      this.add(inQueueGrid[i]);
     
      inQueueBlock[i] = new Block((inQueueGrid[i]));
    }
   
    MainFrame.setGamePanel(this);
    fallTimer.start();
  }
 
  // updateGameLevel - Determine if the number of lines to level up has been reached, if so update the game
  private boolean updateGameLevel() {
    if (linesToLevel <= 0) {
      int delay;
      switch (difficulty) {
        case Normal:
          ++level;
          linesToLevel += (int)(level * 1.25) + 5;
         
          // Level 1 fall rate is 1 second, ever level thereafter decrease by 50 ms
          delay = FALL_RATE - (75 * level);
          break;
       
        case Hard:
          ++level;
          linesToLevel += level * 2 + 5;
         
          // Level 1 fall rate is 1 second, ever level thereafter is 50% faster
          delay = (int)(FALL_RATE * Math.pow(1.5, 1 - level));
          break;
       
        default:
          delay = FALL_RATE;
      }
     
      // Let's not allow the timer to get too low...
      if (delay <= 10) {
        gameOver(true);
        return false;
      }
      fallTimer.setDelay(delay);
    }
    updateLabels();
   
    return true;
  }
 
  private void updateLabels() {
    livesCountLabel.setText(lives.toString());
    levelCountLabel.setText(level.toString());
    linesCountLabel.setText(linesToLevel.toString());
    highScoreCountLabel.setText(highScore.get(multipleLives).get(difficulty).toString());
    scoreCountLabel.setText(score.toString());
  }
 
  private boolean getNext() {
    holdUsed = false;
   
    // Dequeue the head element
    gameBlock = inQueueBlock[0];
    if (!insertBlock(gameBlock)) { return false; }
   
    // Shuffle the queue forward
    // There's only four elements in this queue so it's not a noticeable performance hit
    // The benefit to this is it keeps the indices for each block lined up with the panels they're drawn in
    for (int i = 1; i < QUEUE_SIZE; ++i) {
      inQueueBlock[i - 1] = inQueueBlock[i];
      inQueueBlock[i - 1].insert(inQueueGrid[i - 1]);
    }
   
    // Insert a new block at the tail of the queue
    inQueueBlock[QUEUE_SIZE - 1] = new Block(inQueueGrid[QUEUE_SIZE - 1]);
    return true;
  }

  private boolean insertBlock(Block gameBlock) {
    if (!gameBlock.insert(gameGrid)) {
      --lives;
      if (lives != 0) {
        gameGrid.wipe();
        return gameBlock.insert(gameGrid);
      }
      return false;
    }
    return true;
  }

  // Called when a block "sinks into place"
  public static void sink(int row) {
    game.fallTimer.stop();
   
    int lines = 0;
    try {
      lines = game.gameGrid.collapseAbove(row);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
   
    // Give bonus points for consecutive line removals
    if (lines == 0) {
      game.consecutive = 1;
    }
    else {
      ++game.consecutive;
     
      // Score weighs number of lines removed twice as heavily but still gives a noticeable bonus for consecutive completions
      game.score += (lines * game.level * 10) + (game.consecutive * game.level * 5);
     
      if (game.gameGrid.checkIfAllEmpty()) {
        // This is a relatively rare occurrence, anyone deserves big points for it :)
        game.score += (250 * game.level);
      }
    }
   
    game.linesToLevel -= lines;
   
    if (game.updateGameLevel()) {
      if (game.getNext()) {
        game.fallTimer.start();
      } else {
        game.gameOver(false);
      }
    }
  }
 
  private void hold() {
    if (holdUsed) {
      // TODO: Alert the user they can't switch again with perhaps a sound
      return;
    }
   
    game.fallTimer.stop();
   
    if (holdBlock != null) {
      Block temp = gameBlock;
     
      gameBlock = holdBlock;
      holdBlock = temp;
     
      gameBlock.freeGrid();
      holdBlock.insert(holdGrid);
      if (!insertBlock(gameBlock)) {
        gameOver(false);
        return;
      }
    }
    else {
      holdBlock = gameBlock;
      holdBlock.insert(holdGrid);
     
      if (!getNext()) {
        gameOver(false);
        return;
      }
    }
   
    holdUsed = true;
    fallTimer.start();
  }
 
  private void gameOver(boolean completed) {
    boolean beatRecord = score > highScore.get(multipleLives).get(difficulty);
    if (beatRecord) {
      highScore.get(multipleLives).put(difficulty, score);
    }
    JOptionPane.showMessageDialog(MainFrame.getThis(),
      "Congratulations!\n" +
        (!completed
          ?
          "You made it to:\n" +
          "Level " + level
          :
          "You completed all available levels!\n" +
          "You must be superman or something!"
        ) +
        (beatRecord
          ?
          "\n\nYou even beat the high score!\n" +
          "Most Impressive!"
          :
          ""
        ),
      "Game Over",
      JOptionPane.INFORMATION_MESSAGE
    );
   
    // Stop the game simply by setting it to null
    game = null;
  }
 
  public static void repaintGrid() {
    if (game != null) {
      game.gameGrid.repaintGrid();
      game.holdGrid.repaintGrid();
      for (int i = 0; i < QUEUE_SIZE; ++i) {
        game.inQueueGrid[i].repaintGrid();
      }
    }
    MainFrame.getThis().repaint();
    MainFrame.getThis().revalidate();
  }
 
  // Let block objects handle the key commands
  public static void executeKey(KEY_COMMAND k) {
    if (game.paused) {
      if (k == KEY_COMMAND.PAUSE) {
        game.resume();
      }
      return;
    }
    switch (k) {
      case LEFT:
        game.gameBlock.shiftLeft();
        break;
     
      case RIGHT:
        game.gameBlock.shiftRight();
        break;
     
      case ROTATE:
        game.gameBlock.rotate();
        break;
     
      case FALL:
        game.gameBlock.fall();
        break;
     
      case DROP:
        game.gameBlock.drop();
        break;
     
      case HOLD:
        game.hold();
        break;
     
      case PAUSE:
        game.pause();
        break;
    }
  }
 
  public static void pauseGame() { if (game != null) { game.pause(); } }
  private void pause() {
    fallTimer.stop();
    paused = true;
   
    this.add(resumeButton);
   
    this.remove(gameGrid);
    this.remove(holdGrid);
    for (int i = 0; i < QUEUE_SIZE; ++i) {
      this.remove(inQueueGrid[i]);
    }
   
    repaintGrid();
    this.add(resumeButton);
  }
  private void resume() {
    this.remove(resumeButton);
   
    this.add(gameGrid);
    this.add(holdGrid);
    for (int i = 0; i < QUEUE_SIZE; ++i) {
      this.add(inQueueGrid[i]);
    }
   
    MainFrame.getThis().requestFocus();
   
    repaintGrid();
    paused = false;
    fallTimer.start();
  }
 
  // ActionListener
  public synchronized void actionPerformed(ActionEvent e) {
    if (e.getSource() == fallTimer) {
      game.gameBlock.fall();
    }
    else if (e.getSource() == resumeButton) {
      game.resume();
    }
  }
}
TOP

Related Classes of Tetris.Game

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.