جاوا ۾ "ٽيگ" - هڪ مڪمل راند ڪيئن ٺاهي

جاوا ۾ "ٽيگ" - هڪ مڪمل راند ڪيئن ٺاهي

"پندرهن" يا "پندرهن" هڪ سادي منطق جي راند جو هڪ بهترين مثال آهي جيڪا سڄي دنيا ۾ مشهور آهي. پہیلی کي حل ڪرڻ لاء، توھان کي نمبرن سان چوڪن کي ترتيب ڏيڻ جي ضرورت آھي، ننڍي کان وڏي تائين. اهو آسان ناهي، پر اهو دلچسپ آهي.

اڄ جي ٽيوٽوريل ۾ اسين توهان کي ڏيکارينداسين ته ڪيئن Fifteen کي جاوا 8 ۾ Eclipse سان ڊولپ ڪيو وڃي. UI کي ترقي ڪرڻ لاءِ اسان استعمال ڪنداسين Swing API.

اسان توهان کي ياد ڏياريون ٿا: "Habr" جي سڀني پڙهندڙن لاءِ - 10 روبل جي رعايت جڏهن "Habr" پروموشنل ڪوڊ استعمال ڪندي ڪنهن به اسڪل باڪس ڪورس ۾ داخلا.

Skillbox سفارش ڪري ٿو: تعليمي آن لائين ڪورس "پروفيشنل جاوا ڊولپر".

راند جي ڊيزائن

هن مرحلي تي توهان کي ملڪيت جي وضاحت ڪرڻ جي ضرورت آهي:

  • سائيز - راند جي ميدان جي ماپ؛
  • nbTiles - فيلڊ ۾ ٽيگ جو تعداد. nb ٽائلس = سائيز * سائيز - 1؛
  • ٽائلس هڪ ٽيگ آهي جيڪو هڪ طرفي صفن جي عددن جو آهي. هر هڪ ٽيگ رينج ۾ هڪ منفرد قدر حاصل ڪندو [0, nbTiles]. صفر هڪ خالي چورس اشارو ڪري ٿو؛
  • blankPos - خالي چورس جي پوزيشن.

راند جو منطق

اسان کي نئين راند جي پوزيشن کي شروع ڪرڻ لاء استعمال ٿيل ريٽيڪل طريقو بيان ڪرڻ جي ضرورت آهي. هن طريقي سان اسان ٽيگ جي صف جي هر عنصر لاء هڪ قدر مقرر ڪيو. خير، پوء اسان صف جي آخري پوزيشن ۾ blankPos رکون ٿا.

اسان کي ٽيگ جي صف کي شفل ڪرڻ لاءِ شفل جو طريقو پڻ گهرجي. اسان شفلنگ جي عمل ۾ خالي ٽيگ شامل نه ڪندا آهيون انهي کي ساڳي پوزيشن ۾ ڇڏڻ لاءِ.

جيئن ته پزل جي صرف اڌ ممڪن شروعاتي پوزيشن وٽ حل آهي، توهان کي پڪ ڪرڻ جي ضرورت آهي ته ڦيرڦار جي نتيجي جي نتيجي کي يقيني بڻائي ته موجوده ترتيب اڃا به حل ڪرڻ جي قابل آهي. هن کي ڪرڻ لاء، اسان وضاحت ڪريون ٿا isSolvable طريقو.

جيڪڏهن هڪ خاص ٽيگ هڪ اعلي قيمت سان هڪ ٽيگ کان اڳ آهي، اهو هڪ ڦيرو سمجهيو ويندو آهي. جڏهن خالي جاءِ جاءِ تي هجي، ته پزل کي حل ڪرڻ لاءِ ڦيرڦار جو تعداد به هجڻ گهرجي. تنهن ڪري اسان انوائرن جو تعداد ڳڻيو ۽ صحيح موٽايو جيڪڏهن انگ برابر آهي.

اهو پوءِ ضروري آهي ته isSolved طريقي جي وضاحت ڪرڻ لاءِ چيڪ ڪيو وڃي ته ڇا اسان جي گيم آف ففٽين لي آئوٽ حل ٿيل آهي. پهرين اسان ڏسون ٿا ته خالي جڳهه ڪٿي آهي. جيڪڏهن شروعاتي پوزيشن ۾، پوء موجوده ترتيب نئين آهي، اڳ ۾ فيصلو نه ڪيو ويو آهي. پوءِ اسان ٽائلس ذريعي ريورس آرڊر ۾ ورجائيندا آهيون، ۽ جيڪڏهن ٽيگ جي قيمت لاڳاپيل انڊيڪس +1 کان مختلف آهي، اسان غلط موٽائينداسين. ٻي صورت ۾، طريقي جي آخر ۾ اهو وقت صحيح موٽڻ جو وقت آهي ڇو ته پہیلی اڳ ۾ ئي حل ٿي چڪي آهي.

ٻيو طريقو جنهن جي وضاحت ڪرڻ جي ضرورت آهي نئين راند آهي. اهو ضروري آهي ته راند جو هڪ نئون مثال ٺاهي. هن کي ڪرڻ لاء، اسان راند جي ميدان کي ريٽ ڪيو، پوء ان کي ڦيرايو ۽ جاري رکو جيستائين راند جي پوزيشن حل ٿي وڃي.

هتي ٽيگ جي اهم منطق سان هڪ مثال ڪوڊ آهي:

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

آخرڪار، توهان کي سر ۾ ٽيگ جي حرڪت کي پروگرام ڪرڻ جي ضرورت آهي. هي ڪوڊ بعد ۾ ڪال بڪ ذريعي ڪريسر جي حرڪت جو جواب ڏيڻ لاءِ سڏيو ويندو. اسان جي راند هڪ ئي وقت ۾ ڪيترن ئي ٽائل تحريڪن جي حمايت ڪندي. اهڙيءَ طرح، اسان اسڪرين تي دٻايل پوزيشن کي ٽيگ ۾ تبديل ڪرڻ کان پوءِ، اسان خالي ٽيگ جي پوزيشن حاصل ڪريون ٿا ۽ هڪ ئي وقت ان جي ڪيترن ئي تحريڪن کي سپورٽ ڪرڻ لاءِ حرڪت جي هدايت ڳوليون ٿا.

هتي هڪ ڪوڊ مثال آهي:

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

اسان Swing API استعمال ڪندي UI ٺاهيندا آهيون

انٽرفيس تي ڪم ڪرڻ جو وقت آهي. پهرين اسان Jpanel ڪلاس وٺون ٿا. پوءِ اسان فيلڊ تي ٽيگ ٺاھيون ٿا - ھر ھڪڙي جي ماپ کي ڳڻڻ لاءِ، اسان استعمال ڪنداسين ڊيٽا کي بيان ڪيل راند ٺاھيندڙ پيٽرولر ۾:

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

مارجن پڻ ھڪڙو پيٽرولر سيٽ آھي راند ٺاھيندڙ ۾.

ھاڻي اسان کي اسڪرين تي گرڊ ۽ اسپاٽ ڪڍڻ لاءِ ڊرا گرڊ جو طريقو بيان ڪرڻو پوندو. اسان ٽيگ جي صف جو تجزيو ڪريون ٿا ۽ همراهن کي يوزر انٽرفيس ڪوآرڊينيٽس ۾ تبديل ڪريون ٿا. پوءِ هر جڳهه کي مرڪز ۾ لاڳاپيل نمبر سان ٺاھيو:

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

آخرڪار، اچو ته رنگ جي اجزاء واري طريقي کي ختم ڪريون، جيڪو JPane طبقي مان نڪتل آهي. اسان پوءِ استعمال ڪريون ٿا DrawGrid طريقو، جنهن جي پٺيان drawStartMessage طريقي سان هڪ پيغام ڏيکاري ٿو جيڪو اسان کي راند شروع ڪرڻ لاءِ ڪلڪ ڪرڻ لاءِ اشارو ڪري ٿو:

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

UI ۾ صارف جي عملن تي رد عمل

راند کي هلائڻ لاءِ ان جي ڪورس کي هلائڻ لاءِ، UI ۾ صارف جي عملن تي عمل ڪرڻ ضروري آهي. هن کي ڪرڻ لاءِ، جپانيل تي MouseListener جو نفاذ شامل ڪريو ۽ جڳهن جي حرڪت لاءِ ڪوڊ، اڳ ۾ ئي مٿي ڏيکاريل آهي:

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

اسان ڪوڊ کي GameOfFifteen ڪلاس جي تعمير ڪندڙ ۾ رکون ٿا. بلڪل آخر ۾، اسان نئين راند کي شروع ڪرڻ لاء نئين راند جو طريقو سڏين ٿا.

مڪمل راند جو ڪوڊ

راند کي عمل ۾ ڏسڻ کان اڳ آخري قدم سڀني ڪوڊ عناصر کي گڏ ڪرڻ آهي. هتي ڇا ٿئي ٿو:

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

آخرڪار، اچو ته راند ڪريون!

اهو وقت آهي راند کي شروع ڪرڻ ۽ ان کي عمل ۾ آزمايو. فيلڊ کي هن طرح ڏسڻ گهرجي:

جاوا ۾ "ٽيگ" - هڪ مڪمل راند ڪيئن ٺاهي

اچو ته پزل کي حل ڪرڻ جي ڪوشش ڪريون. جيڪڏهن سڀ ڪجهه ٺيڪ ٿي ويو، اسان هي حاصل ڪندا آهيون:

جاوا ۾ "ٽيگ" - هڪ مڪمل راند ڪيئن ٺاهي

اهو ئي سڀ ڪجهه آهي. ڇا توهان وڌيڪ توقع ڪئي؟ 🙂

Skillbox سفارش ڪري ٿو:

جو ذريعو: www.habr.com

تبصرو شامل ڪريو