"Tag" Java - kā izstrādāt pilnvērtīgu spēli

"Tag" Java - kā izstrādāt pilnvērtīgu spēli

"Piecpadsmit" vai "Piecpadsmit" ir lielisks piemērs vienkārÅ”ai loÄ£ikas spēlei, kas ir populāra visā pasaulē. Lai atrisinātu mÄ«klu, jums ir jāsakārto kvadrāti ar cipariem, sākot no mazākā lÄ«dz lielākajam. Tas nav viegli, bet ir interesanti.

Šodienas apmācībā mēs parādām, kā izstrādāt Fifteen Java 8, izmantojot Eclipse. Lai izstrādātu lietotāja interfeisu, mēs izmantosim Swing API.

Atgādinām: visiem "Habr" lasītājiem - atlaide 10 000 rubļu, reģistrējoties jebkurā Skillbox kursā, izmantojot "Habr" reklāmas kodu.

Skillbox iesaka: IzglītojoŔs tieŔsaistes kurss "Java izstrādātāja profesija".

Spēles dizains

Šajā posmā jums ir jādefinē rekvizīti:

  • Izmērs ā€” spēles laukuma lielums;
  • nbTiles ā€” tagu skaits laukā. nbTiles = izmērs*izmērs - 1;
  • FlÄ«zes ir tags, kas ir viendimensijas veselu skaitļu masÄ«vs. Katrs no tagiem saņems unikālu vērtÄ«bu diapazonā [0, nbTiles]. Nulle norāda tukÅ”u kvadrātu;
  • blankPos ā€” tukŔā kvadrāta pozÄ«cija.

Spēles loģika

Mums ir jādefinē atiestatÄ«Å”anas metode, ko izmanto, lai inicializētu jaunu spēles pozÄ«ciju. Tādā veidā mēs iestatām vērtÄ«bu katram tagu masÄ«va elementam. Tad mēs ievietojam blankPos masÄ«va pēdējā pozÄ«cijā.

Mums ir nepiecieÅ”ama arÄ« jaukÅ”anas metode, lai sajauktu tagu masÄ«vu. Mēs neiekļaujam tukÅ”o tagu jaukÅ”anas procesā, lai atstātu to tajā paŔā pozÄ«cijā.

Tā kā tikai pusei no iespējamām mÄ«klas sākuma pozÄ«cijām ir risinājums, jums ir jāpārbauda iegÅ«tais jaukÅ”anas rezultāts, lai pārliecinātos, ka paÅ”reizējais izkārtojums ir vienmērÄ«gi atrisināms. Lai to izdarÄ«tu, mēs definējam metodi isSolvaable.

Ja pirms noteikta taga ir tags ar lielāku vērtÄ«bu, tas tiek uzskatÄ«ts par inversiju. Kad tukŔā vieta ir vietā, inversiju skaitam jābÅ«t vienādam, lai mÄ«kla bÅ«tu atrisināma. Tātad mēs saskaitām inversiju skaitu un atgriežam patiesu, ja skaitlis ir pāra.

Pēc tam ir svarÄ«gi definēt isSolved metodi, lai pārbaudÄ«tu, vai mÅ«su piecpadsmito spēļu kombinācija ir atrisināta. Vispirms skatāmies, kur ir tukÅ”a vieta. Ja sākotnējā pozÄ«cijā, tad paÅ”reizējais izlÄ«dzinājums ir jauns, iepriekÅ” nav noteikts. Pēc tam mēs atkārtojam elementus apgrieztā secÄ«bā, un, ja atzÄ«mes vērtÄ«ba atŔķiras no atbilstoŔā indeksa +1, tiek atgriezta kļūda. Pretējā gadÄ«jumā metodes beigās ir pienācis laiks atgriezt patieso, jo mÄ«kla jau ir atrisināta.

Vēl viena metode, kas jādefinē, ir newGame. Ir nepiecieÅ”ams izveidot jaunu spēles gadÄ«jumu. Lai to izdarÄ«tu, mēs atiestatām spēles laukumu, pēc tam sajaucam to un turpinām, lÄ«dz spēles pozÄ«cija ir atrisināma.

Šeit ir koda piemērs ar taga atslēgas loģiku:

private void newGame() {
  do {
    reset(); // reset in initial state
    shuffle(); // shuffle
  } while(!isSolvable()); // make it until grid be solvable
 
  gameOver = false;
}
 
private void reset() {
  for (int i = 0; i < tiles.length; i++) {
    tiles[i] = (i + 1) % tiles.length;
  }
 
  // we set blank cell at the last
  blankPos = tiles.length - 1;
}
 
private void shuffle() {
  // don't include the blank tile in the shuffle, leave in the solved position
  int n = nbTiles;
 
  while (n > 1) {
    int r = RANDOM.nextInt(n--);
    int tmp = tiles[r];
    tiles[r] = tiles[n];
    tiles[n] = tmp;
  }
}
 
// Only half permutations of the puzzle are solvable/
// Whenever a tile is preceded by a tile with higher value it counts
// as an inversion. In our case, with the blank tile in the solved position,
// the number of inversions must be even for the puzzle to be solvable
private boolean isSolvable() {
  int countInversions = 0;
 
  for (int i = 0; i < nbTiles; i++) {
    for (int j = 0; j < i; j++) {
      if (tiles[j] > tiles[i])
        countInversions++;
    }
  }
 
  return countInversions % 2 == 0;
}
 
private boolean isSolved() {
  if (tiles[tiles.length - 1] != 0) // if blank tile is not in the solved position ==> not solved
    return false;
 
  for (int i = nbTiles - 1; i >= 0; i--) {
    if (tiles[i] != i + 1)
      return false;
  }
 
  return true;
}

Visbeidzot, jums ir jāieprogrammē tagu kustÄ«ba masÄ«vā. Å is kods tiks izsaukts vēlāk, izmantojot atzvanÄ«Å”anu, lai reaģētu uz kursora kustÄ«bu. MÅ«su spēle atbalstÄ«s vairākas flīžu kustÄ«bas vienlaikus. Tādējādi pēc tam, kad esam pārveidojuÅ”i nospiesto pozÄ«ciju uz ekrāna par tagu, mēs iegÅ«stam tukŔās atzÄ«mes pozÄ«ciju un meklējam kustÄ«bas virzienu, lai vienlaikus atbalstÄ«tu vairākas tās kustÄ«bas.

Šeit ir koda piemērs:

// get position of the click
int ex = e.getX() - margin;
int ey = e.getY() - margin;
 
// click in the grid ?
if (ex < 0 || ex > gridSize  || ey < 0  || ey > gridSize)
  return;
 
// get position in the grid
int c1 = ex / tileSize;
int r1 = ey / tileSize;
 
// get position of the blank cell
int c2 = blankPos % size;
int r2 = blankPos / size;
 
// we convert in the 1D coord
int clickPos = r1 * size + c1;
 
int dir = 0;
 
// we search direction for multiple tile moves at once
if (c1 == c2  &&  Math.abs(r1 - r2) > 0)
  dir = (r1 - r2) > 0 ? size : -size;
else if (r1 == r2 && Math.abs(c1 - c2) > 0)
  dir = (c1 - c2) > 0 ? 1 : -1;
 
if (dir != 0) {
  // we move tiles in the direction
  do {
    int newBlankPos = blankPos + dir;
    tiles[blankPos] = tiles[newBlankPos];
    blankPos = newBlankPos;
  } while(blankPos != clickPos);
 
tiles[blankPos] = 0;

Mēs izstrādājam lietotāja saskarni, izmantojot Swing API

Ir pienācis laiks strādāt pie interfeisa. Vispirms mēs ņemam Jpanel klasi. Pēc tam uz lauka uzzīmējam tagus - lai aprēķinātu katra izmērus, izmantosim spēles konstruktora parametrā norādītos datus:

gridSize = (dimā€Š - ā€Š2 * margin);
tileSize = gridSize / size;

Marža ir arī parametru komplekts spēles konstruktorā.

Tagad mums ir jādefinē drawGrid metode, lai zÄ«mētu režģi un plankumus uz ekrāna. Mēs analizējam tagu masÄ«vu un pārvērÅ”am koordinātas lietotāja interfeisa koordinātās. Pēc tam uzzÄ«mējiet katru vietu ar atbilstoÅ”o numuru centrā:

private void drawGrid(Graphics2D g) {
  for (int i = 0; i < tiles.length; i++) {
    // we convert 1D coords to 2D coords given the size of the 2D Array
    int r = i / size;
    int c = i % size;
    // we convert in coords on the UI
    int x = margin + c * tileSize;
    int y = margin + r * tileSize;
 
    // check special case for blank tile
    if(tiles[i] == 0) {
      if (gameOver) {
        g.setColor(FOREGROUND_COLOR);
        drawCenteredString(g, "u2713", x, y);
      }
 
      continue;
    }
 
    // for other tiles
    g.setColor(getForeground());
    g.fillRoundRect(x, y, tileSize, tileSize, 25, 25);
    g.setColor(Color.BLACK);
    g.drawRoundRect(x, y, tileSize, tileSize, 25, 25);
    g.setColor(Color.WHITE);
 
    drawCenteredString(g, String.valueOf(tiles[i]), x , y);
  }
}

Visbeidzot, ignorēsim metodi paintComponent, kas izriet no JPane klases. Pēc tam mēs izmantojam metodi drawGrid, kam seko metodi drawStartMessage, lai parādÄ«tu ziņojumu, kas aicina noklikŔķināt, lai sāktu spēli:

private void drawStartMessage(Graphics2D g) {
  if (gameOver) {
    g.setFont(getFont().deriveFont(Font.BOLD, 18));
    g.setColor(FOREGROUND_COLOR);
    String s = "Click to start new game";
    g.drawString(s, (getWidth() - g.getFontMetrics().stringWidth(s)) / 2,
        getHeight() - margin);
  }
}
 
private void drawCenteredString(Graphics2D g, String s, int x, int y) {
  // center string s for the given tile (x,y)
  FontMetrics fm = g.getFontMetrics();
  int asc = fm.getAscent();
  int desc = fm.getDescent();
  g.drawString(s,  x + (tileSize - fm.stringWidth(s)) / 2,
      y + (asc + (tileSize - (asc + desc)) / 2));
}
 
@Override
protected void paintComponent(Graphics g) {
  super.paintComponent(g);
  Graphics2D g2D = (Graphics2D) g;
  g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  drawGrid(g2D);
  drawStartMessage(g2D);
}

Reaģē uz lietotāja darbībām lietotāja saskarnē

Lai spēle darbotos savu gaitu, ir nepiecieÅ”ams apstrādāt lietotāja darbÄ«bas UI. Lai to izdarÄ«tu, Jpanel pievienojiet MouseListener implementāciju un vietu pārvietoÅ”anas kodu, kas jau parādÄ«ts iepriekÅ”:

addMouseListener(new MouseAdapter() {
  @Override
  public void mousePressed(MouseEvent e) {
    // used to let users to interact on the grid by clicking
    // it's time to implement interaction with users to move tiles to solve the game !
    if (gameOver) {
      newGame();
    } else {
      // get position of the click
      int ex = e.getX() - margin;
      int ey = e.getY() - margin;
 
      // click in the grid ?
      if (ex < 0 || ex > gridSize  || ey < 0  || ey > gridSize)
        return;
 
      // get position in the grid
      int c1 = ex / tileSize;
      int r1 = ey / tileSize;
 
      // get position of the blank cell
      int c2 = blankPos % size;
      int r2 = blankPos / size;
 
      // we convert in the 1D coord
      int clickPos = r1 * size + c1;
 
      int dir = 0;
 
      // we search direction for multiple tile moves at once
      if (c1 == c2  &&  Math.abs(r1 - r2) > 0)
        dir = (r1 - r2) > 0 ? size : -size;
      else if (r1 == r2 && Math.abs(c1 - c2) > 0)
        dir = (c1 - c2) > 0 ? 1 : -1;
 
      if (dir != 0) {
        // we move tiles in the direction
        do {
          int newBlankPos = blankPos + dir;
          tiles[blankPos] = tiles[newBlankPos];
          blankPos = newBlankPos;
        } while(blankPos != clickPos);
 
        tiles[blankPos] = 0;
      }
 
      // we check if game is solved
      gameOver = isSolved();
    }
 
    // we repaint panel
    repaint();
  }
});

Mēs ievietojam kodu GameOfFifteen klases konstruktorā. PaŔās beigās mēs izsaucam newGame metodi, lai sāktu jaunu spēli.

Pilns spēles kods

Pēdējais solis, pirms redzēt spēli darbÄ«bā, ir visu koda elementu salikÅ”ana. LÅ«k, kas notiek:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Random;
 
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
 
// We are going to create a Game of 15 Puzzle with Java 8 and Swing
// If you have some questions, feel free to read comments ;)
public class GameOfFifteen extends JPanel { // our grid will be drawn in a dedicated Panel
 
  // Size of our Game of Fifteen instance
  private int size;
  // Number of tiles
  private int nbTiles;
  // Grid UI Dimension
  private int dimension;
  // Foreground Color
  private static final Color FOREGROUND_COLOR = new Color(239, 83, 80); // we use arbitrary color
  // Random object to shuffle tiles
  private static final Random RANDOM = new Random();
  // Storing the tiles in a 1D Array of integers
  private int[] tiles;
  // Size of tile on UI
  private int tileSize;
  // Position of the blank tile
  private int blankPos;
  // Margin for the grid on the frame
  private int margin;
  // Grid UI Size
  private int gridSize;
  private boolean gameOver; // true if game over, false otherwise
 
  public GameOfFifteen(int size, int dim, int mar) {
    this.size = size;
    dimension = dim;
    margin = mar;
    
    // init tiles
    nbTiles = size * size - 1; // -1 because we don't count blank tile
    tiles = new int[size * size];
    
    // calculate grid size and tile size
    gridSize = (dim - 2 * margin);
    tileSize = gridSize / size;
    
    setPreferredSize(new Dimension(dimension, dimension + margin));
    setBackground(Color.WHITE);
    setForeground(FOREGROUND_COLOR);
    setFont(new Font("SansSerif", Font.BOLD, 60));
    
    gameOver = true;
    
    addMouseListener(new MouseAdapter() {
      @Override
      public void mousePressed(MouseEvent e) {
        // used to let users to interact on the grid by clicking
        // it's time to implement interaction with users to move tiles to solve the game !
        if (gameOver) {
          newGame();
        } else {
          // get position of the click
          int ex = e.getX() - margin;
          int ey = e.getY() - margin;
          
          // click in the grid ?
          if (ex < 0 || ex > gridSize  || ey < 0  || ey > gridSize)
            return;
          
          // get position in the grid
          int c1 = ex / tileSize;
          int r1 = ey / tileSize;
          
          // get position of the blank cell
          int c2 = blankPos % size;
          int r2 = blankPos / size;
          
          // we convert in the 1D coord
          int clickPos = r1 * size + c1;
          
          int dir = 0;
          
          // we search direction for multiple tile moves at once
          if (c1 == c2  &&  Math.abs(r1 - r2) > 0)
            dir = (r1 - r2) > 0 ? size : -size;
          else if (r1 == r2 && Math.abs(c1 - c2) > 0)
            dir = (c1 - c2) > 0 ? 1 : -1;
            
          if (dir != 0) {
            // we move tiles in the direction
            do {
              int newBlankPos = blankPos + dir;
              tiles[blankPos] = tiles[newBlankPos];
              blankPos = newBlankPos;
            } while(blankPos != clickPos);
            
            tiles[blankPos] = 0;
          }
          
          // we check if game is solved
          gameOver = isSolved();
        }
        
        // we repaint panel
        repaint();
      }
    });
    
    newGame();
  }
 
  private void newGame() {
    do {
      reset(); // reset in intial state
      shuffle(); // shuffle
    } while(!isSolvable()); // make it until grid be solvable
    
    gameOver = false;
  }
 
  private void reset() {
    for (int i = 0; i < tiles.length; i++) {
      tiles[i] = (i + 1) % tiles.length;
    }
    
    // we set blank cell at the last
    blankPos = tiles.length - 1;
  }
 
  private void shuffle() {
    // don't include the blank tile in the shuffle, leave in the solved position
    int n = nbTiles;
    
    while (n > 1) {
      int r = RANDOM.nextInt(n--);
      int tmp = tiles[r];
      tiles[r] = tiles[n];
      tiles[n] = tmp;
    }
  }
 
  // Only half permutations of the puzzle are solvable.
  // Whenever a tile is preceded by a tile with higher value it counts
  // as an inversion. In our case, with the blank tile in the solved position,
  // the number of inversions must be even for the puzzle to be solvable
  private boolean isSolvable() {
    int countInversions = 0;
    
    for (int i = 0; i < nbTiles; i++) {
      for (int j = 0; j < i; j++) {
        if (tiles[j] > tiles[i])
          countInversions++;
      }
    }
    
    return countInversions % 2 == 0;
  }
 
  private boolean isSolved() {
    if (tiles[tiles.length - 1] != 0) // if blank tile is not in the solved position ==> not solved
      return false;
    
    for (int i = nbTiles - 1; i >= 0; i--) {
      if (tiles[i] != i + 1)
        return false;      
    }
    
    return true;
  }
 
  private void drawGrid(Graphics2D g) {
    for (int i = 0; i < tiles.length; i++) {
      // we convert 1D coords to 2D coords given the size of the 2D Array
      int r = i / size;
      int c = i % size;
      // we convert in coords on the UI
      int x = margin + c * tileSize;
      int y = margin + r * tileSize;
      
      // check special case for blank tile
      if(tiles[i] == 0) {
        if (gameOver) {
          g.setColor(FOREGROUND_COLOR);
          drawCenteredString(g, "u2713", x, y);
        }
        
        continue;
      }
      
      // for other tiles
      g.setColor(getForeground());
      g.fillRoundRect(x, y, tileSize, tileSize, 25, 25);
      g.setColor(Color.BLACK);
      g.drawRoundRect(x, y, tileSize, tileSize, 25, 25);
      g.setColor(Color.WHITE);
      
      drawCenteredString(g, String.valueOf(tiles[i]), x , y);
    }
  }
 
  private void drawStartMessage(Graphics2D g) {
    if (gameOver) {
      g.setFont(getFont().deriveFont(Font.BOLD, 18));
      g.setColor(FOREGROUND_COLOR);
      String s = "Click to start new game";
      g.drawString(s, (getWidth() - g.getFontMetrics().stringWidth(s)) / 2,
          getHeight() - margin);
    }
  }
 
  private void drawCenteredString(Graphics2D g, String s, int x, int y) {
    // center string s for the given tile (x,y)
    FontMetrics fm = g.getFontMetrics();
    int asc = fm.getAscent();
    int desc = fm.getDescent();
    g.drawString(s,  x + (tileSize - fm.stringWidth(s)) / 2,
        y + (asc + (tileSize - (asc + desc)) / 2));
  }
 
  @Override
  protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2D = (Graphics2D) g;
    g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    drawGrid(g2D);
    drawStartMessage(g2D);
  }
 
  public static void main(String[] args) {
    SwingUtilities.invokeLater(() -> {
      JFrame frame = new JFrame();
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setTitle("Game of Fifteen");
      frame.setResizable(false);
      frame.add(new GameOfFifteen(4, 550, 30), BorderLayout.CENTER);
      frame.pack();
      // center on the screen
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
    });
  }
 
 
}

Beidzot spēlēsim!

Ir pienācis laiks palaist spēli un pārbaudÄ«t to darbÄ«bā. Laukam vajadzētu izskatÄ«ties Ŕādi:

"Tag" Java - kā izstrādāt pilnvērtīgu spēli

Mēģināsim atrisināt mÄ«klu. Ja viss noritēja labi, mēs iegÅ«stam Å”o:

"Tag" Java - kā izstrādāt pilnvērtīgu spēli

Tas ir viss. Vai jÅ«s gaidÄ«jāt vairāk? šŸ™‚

Skillbox iesaka:

Avots: www.habr.com

Pievieno komentāru