"Pazdeh" li Java - meriv çawa lîstikek bêkêmasî pêşve dike

"Pazdeh" li Java - meriv çawa lîstikek bêkêmasî pêşve dike

"Pazdeh", an "Pazdeh" mînakek mezin a lîstikek mantiqî ya hêsan e ku li seranserê cîhanê populer e. Ji bo ku hûn puzzle çareser bikin, hûn hewce ne ku çargoşeyan bi jimareyan bi rêz, ji piçûktir berbi herî mezin, rêz bikin. Ew ne hêsan e, lê balkêş e.

Di dersa îro de, em nîşanî we didin ka meriv çawa di Java 8-ê de Fifteen-ê bi Eclipse-ê pêşve dibe. Ji bo pêşxistina UI, em ê Swing API bikar bînin.

Em bînin bîra xwe: ji bo hemî xwendevanên "Habr" - dema ku hûn beşdarî qursek Skillbox-ê bi karanîna koda danasînê ya "Habr" têne qeyd kirin 10 rubleyan dakêşin.

Skillbox pêşniyar dike: Kursa perwerdehiya serhêl "Pîşesaziya Java Developer".

Sêwirana lîstikê

Di vê qonaxê de, hûn hewce ne ku taybetmendiyên xwe diyar bikin:

  • Mezinahî - mezinahiya qada lîstikê;
  • nbTiles - hejmara tagên li qadê. nbTiles = mezinahî * mezinahî - 1;
  • Tile tagek e, ku rêzek yek-alî ya jimaran e. Her yek ji nîşanan dê di rêza [0, nbTiles] de nirxek bêhempa bistîne. Sifir çargoşeya vala nîşan dide;
  • blankPos - pozîsyona meydana vala.

Mantiqa lîstikê

Pêdivî ye ku em rêbazek vesazkirinê ya ku ji bo destpêkirina pozîsyonek lîstikek nû tê bikar anîn diyar bikin. Bi vî rengî em ji bo her hêmanek rêzika tagê nirxek destnîşan dikin. Welê, wê hingê em blankPos di pozîsyona paşîn a rêzê de danîn.

Di heman demê de ji me re rêbazek tevlihevkirinê jî hewce ye ku rêzika nîşanan bişewitîne. Em etîketek vala di pêvajoya şilkirinê de nagirin da ku wê di pozîsyona xweya orjînal de bihêlin.

Ji ber ku tenê nîvê pozîsyonên destpêkê yên muhtemel ên puzzle xwedan çareseriyek e, divê encama tevlihevkirinê were kontrol kirin da ku pê ewle bibin ku nexşeya heyî bi gelemperî çareser dibe. Ji bo vê yekê, em rêbaza isSolvable diyar dikin.

Heke pişkek taybetî li pêşiya pişkek bi nirxek bilindtir be, ew wekî berevajî tê hesibandin. Dema ku etîketa vala li cihê xwe be, ji bo ku puzzle çareser bibe divê hejmara vegeran jî be. Ji ber vê yekê em hejmara veguheztinan dihejmêrin û heke hejmar zewac be em rast vedigerînin.

Dûv re, girîng e ku meriv rêbazek isSolved destnîşan bike da ku kontrol bike ka destê me ya Game Of Panzdeh çareser bûye. Pêşî em mêze dikin ku taga vala li ku ye. Ger di pozîsyona destpêkê de ye, wê hingê nexşeya heyî nû ye, berê nehatiye çareser kirin. Dûv re em di rêza berevajî de li ser pêkanan digerînin, û heke nirxa tagê ji nîşana +1 ya têkildar cûda be, em xelet vedigerînin. Wekî din, ew dem e ku di dawiya rêbazê de rast vegere ji ber ku puzzle jixwe çareser bûye.

Rêbazek din a ku divê were destnîşankirin newGame ye. Pêdivî ye ku meriv mînakek nû ya lîstikê biafirîne. Ji bo vê yekê, em panela lîstikê ji nû ve saz dikin, dûv re wê dihejînin û heya ku pozîsyona lîstikê çareser bibe berdewam dikin.

Li vir mînakek kodek bi mantiqa sereke ya nîşanan heye:

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;
}

Di dawiyê de, hûn hewce ne ku tevgera tagên di rêzê de bername bikin. Dê ev kod paşê bi navgîniyek vegerê were gazî kirin da ku bersivê bide tevgera kursor. Lîstika me dê di heman demê de gelek tevgerên tilê piştgirî bike. Bi vî rengî, piştî ku me pozîsyona pêçandî ya li ser ekranê veguherand nîşanek, em cîhê nîşanek vala digirin û li rêgezek tevgerê digerin ku di heman demê de piştgirî bide çend tevgerên wê.

Li vir kodek nimûne ye:

// 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;

Pêşxistina UI bi Swing API

Wextê navberê ye. Pêşî em dersa Jpanel digirin. Dûv re em etîketan li ser zeviyê xêz dikin - ji bo ku mezinahiya her yekê hesab bikin, em ê daneyên ku di parametreya çêkerê lîstikê de hatine destnîşan kirin bikar bînin:

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

Margin jî di çêkera lîstikê de parametreyek e.

Naha pêdivî ye ku em rêbaza drawGrid-ê diyar bikin da ku tevn û hûrikên li ser ekranê xêz bikin. Em rêzika nîşanan analîz dikin û hevrêzan vediguhezînin hevrêzên navbeynkariya bikarhêner. Dûv re em her etîketê bi jimareya têkildar li navendê xêz dikin:

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);
  }
}

Di dawiyê de, em ê rêbaza paintComponent, ku ji çîna JPane-yê hatî derxistin, bişopînin. Dûv re em rêbaza drawGrid û dûv re jî rêbaza drawStartMessage bikar tînin da ku peyamek nîşan bidin ku ji me tê xwestin ku em bikirtînin da ku lîstikê dest pê bikin:

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);
}

Bersiva kiryarên bikarhêner di UI de

Ji bo ku lîstik qursa xwe bimeşîne, pêdivî ye ku meriv kiryarên bikarhêner di UI-yê de birêve bibe. Ji bo vê yekê, em pêkanîna MouseListener li ser Jpanel û koda ji bo veguheztina nîşanan, ku berê li jor hatî destnîşan kirin, zêde dikin:

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();
  }
});

Kod di çêkera çîna GameOfFifteen de tê danîn. Di dawiyê de, em gazî rêbaza newGame dikin ku lîstikek nû dest pê bike.

Koda lîstikê ya tevahî

Pêngava paşîn berî dîtina lîstikê di çalakiyê de ev e ku meriv hemî perçeyên kodê li hev bicivîne. Li vir çi dibe:

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);
    });
  }
 
 
}

Di dawiyê de, em bilîzin!

Wext e ku hûn lîstikê bidin destpêkirin û wê di çalakiyê de ceribandin. Zeviyek divê bi vî rengî xuya bike:

"Pazdeh" li Java - meriv çawa lîstikek bêkêmasî pêşve dike

Werin em hewl bidin ku puzzle çareser bikin. Ger her tişt baş bû, em vê yekê digirin:

"Pazdeh" li Java - meriv çawa lîstikek bêkêmasî pêşve dike

Navê pêger. Ma we bêtir hêvî dikir? 🙂

Skillbox pêşniyar dike:

Source: www.habr.com

Add a comment