"Maka" ku-Java - indlela yokuthuthukisa umdlalo ogcwele

"Maka" ku-Java - indlela yokuthuthukisa umdlalo ogcwele

"Ishumi nanhlanu" noma "ishumi nanhlanu" iyisibonelo esihle kakhulu somdlalo olula we-logic odumile emhlabeni wonke. Ukuze uxazulule iphazili, udinga ukuhlela izikwele ngezinombolo ngokulandelana, kusukela kwencane kakhulu kuye kwenkulu. Akulula, kodwa kuyathakazelisa.

Esifundweni sanamuhla sizokukhombisa ukuthi ungathuthukisa kanjani i-Fifteen ku-Java 8 nge-Eclipse. Ukuze sithuthukise i-UI sizosebenzisa i-Swing API.

Siyakukhumbuza: kubo bonke abafundi be-"Habr" - isaphulelo sama-ruble angu-10 lapho ubhalisa kunoma yisiphi isifundo se-Skillbox usebenzisa ikhodi yephromoshini ethi "Habr".

I-Skillbox iyancoma: Isifundo se-inthanethi semfundo "Umthuthukisi we-Java ochwepheshe".

Umdlalo Design

Kulesi sigaba udinga ukuchaza izakhiwo:

  • Ubukhulu — ubukhulu benkundla yokudlala;
  • nbTiles — inani lamathegi ensimini. nbTiles = usayizi*usayizi - 1;
  • Amathayela amathegi anguhlelo olunohlangothi olulodwa lwamanani aphelele. Ithegi ngayinye izothola inani eliyingqayizivele kububanzi [0, nbTiles]. Uziro ukhombisa isikwele esingenalutho;
  • blankPos — indawo yesikwele esingenalutho.

I-logic yomdlalo

Kudingeka sichaze indlela yokusetha kabusha esetshenziswa ukuqalisa indawo entsha yegeyimu. Ngale ndlela sibeka inani le-elementi ngayinye yohlu lwamathegi. Hhayi-ke, bese sibeka ama-blankPos endaweni yokugcina yamalungu afanayo.

Sidinga futhi indlela yokushova ukuze sishove uhlu lwamathegi. Asifaki umaka ongenalutho enqubweni yokushova ukuze siyishiye isesimweni esifanayo.

Njengoba uhhafu kuphela wezindawo zokuqala zendida ezinesixazululo, udinga ukuhlola umphumela wokushova ukuze uqiniseke ukuthi isakhiwo samanje siyakwazi ukuxazululeka. Ukuze wenze lokhu, sichaza indlela ye-isSolvable.

Uma umaka othile wandulelwa umaka onenani eliphezulu, kuthathwa njengokuguquliwe. Uma indawo engenalutho isendaweni, inani lama-inversion kumele libe ngisho ukuze iphazili ixazululeke. Ngakho-ke sibala inani lama-inversions bese sibuyisela iqiniso uma inombolo ilingana.

Kubalulekile-ke ukuchaza indlela ye-isSolved ukuze uhlole ukuthi isakhiwo sethu se-Game Of Fifteen sixazululiwe. Okokuqala sibheka lapho okungenalutho khona. Uma endaweni yokuqala, ukuqondanisa kwamanje kusha, akunqunywanga ngaphambili. Bese siphindaphinda amathayela ngokulandelana, futhi uma inani likamaki lihlukile kunkomba ehambisanayo ethi +1, sibuyisela okungamanga. Uma kungenjalo, ekupheleni kwendlela sekuyisikhathi sokubuyela eqinisweni ngoba indida isivele ixazululiwe.

Enye indlela okufanele ichazwe i-newGame. Kudingeka ukudala isenzakalo esisha somdlalo. Ukuze senze lokhu, sisetha kabusha inkundla yokudlala, bese siyishova futhi siqhubeke kuze kube yilapho indawo yokudlala isixazululeka.

Nasi isibonelo sekhodi enokhiye wokhiye wethegi:

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

Ekugcineni, udinga ukuhlela ukunyakaza kwamathegi ohlwini. Le khodi izobizwa kamuva nge-callback ukuphendula ukunyakaza kwekhesa. Umdlalo wethu uzosekela ukunyakaza kwama-tile amaningi ngesikhathi esisodwa. Ngakho, ngemva kokuba sesiguqule isikhundla esicindezelwe esikrinini sibe umaka, sithola indawo yethegi engenalutho futhi sibheke isiqondiso sokunyakaza ukuze sisekele ukunyakaza kwayo okuningana ngesikhathi esisodwa.

Nasi isibonelo sekhodi:

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

Sithuthukisa i-UI sisebenzisa i-Swing API

Isikhathi sokusebenza ku-interface. Okokuqala sithatha isigaba se-JPanel. Bese sidweba omaka enkundleni - ukubala amasayizi ngamunye, sizosebenzisa idatha ecaciswe kupharamitha yomakhi womdlalo:

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

I-Margin nayo iyipharamitha esethwe kumakhi womdlalo.

Manje sidinga ukuchaza indlela ye-drawGrid ukudweba igridi namabala esikrinini. Sihlaziya uhlu lwamathegi futhi siguqule izixhumanisi zibe izixhumanisi zokusetshenziswa kubonwa. Bese udweba indawo ngayinye ngenombolo ehambisanayo phakathi nendawo:

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

Ekugcineni, masibhale ngaphezulu indlela ye-paintComponent, ephuma esigabeni se-JPane. Sibe sesisebenzisa indlela ye-drawGrid, elandelwa indlela ye-drawStartMessage ukuze sibonise umlayezo osikhuthaza ukuthi sichofoze ukuze siqale umdlalo:

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

Isabela ezenzweni zomsebenzisi ku-UI

Ukuze igeyimu isebenze, kuyadingeka ukucubungula izenzo zabasebenzisi ku-UI. Ukuze wenze lokhu, engeza ukusetshenziswa kwe-MouseListener ku-JPanel kanye nekhodi yezindawo ezihambayo, esivele ikhonjisiwe ngenhla:

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

Sibeka ikhodi kumakhi wekilasi le-GameOfFifteen. Ekugcineni, sibiza indlela entsha Yomdlalo ukuqala umdlalo omusha.

Ikhodi yomdlalo ogcwele

Isinyathelo sokugcina ngaphambi kokubona igeyimu isebenza ukuhlanganisa zonke izici zekhodi. Nakhu okwenzekayo:

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

Ekugcineni, asidlale!

Isikhathi sokuqalisa igeyimu futhi uyihlole isebenza. Inkambu kufanele ibukeke kanje:

"Maka" ku-Java - indlela yokuthuthukisa umdlalo ogcwele

Ake sizame ukuxazulula indida. Uma konke kuhambe kahle, sithola lokhu:

"Maka" ku-Java - indlela yokuthuthukisa umdlalo ogcwele

Yilokho kuphela. Ubulindele okwengeziwe? 🙂

I-Skillbox iyancoma:

Source: www.habr.com

Engeza amazwana