"Clib" i Java - conas cluiche lán-chuimsitheach a fhorbairt

"Clib" i Java - conas cluiche lán-chuimsitheach a fhorbairt

"Cúig Déag" nó "Cúig Déag" Is sampla iontach é de chluiche loighic simplí a bhfuil an-tóir air ar fud an domhain. D'fhonn an bhfreagra a réiteach, ní mór duit na cearnóga a shocrú le huimhreacha in ord, ón gceann is lú go dtí an ceann is mó. Níl sé éasca, ach tá sé suimiúil.

I rang teagaisc an lae inniu léirímid conas Fifteen in Java 8 a fhorbairt le Eclipse. Chun an Chomhéadain a fhorbairt bainfimid úsáid as an Swing API.

Meabhraímid: do léitheoirí uile "Habr" - lascaine de 10 rúbal nuair a chláraíonn siad in aon chúrsa Scilbox ag baint úsáide as an gcód bolscaireachta "Habr".

Molann Skillbox: Cúrsa oideachais ar líne "Forbróir Java Gairm".

Dearadh Cluiche

Ag an gcéim seo ní mór duit na hairíonna a shainiú:

  • Méid — méid na páirce imeartha;
  • nbTiles — líon na gclibeanna sa réimse. nbTiles = méid * méid - 1;
  • Is clib é tíleanna atá ina sraith aontoiseach de shlánuimhreacha. Gheobhaidh gach ceann de na clibeanna luach uathúil sa raon [0, nbTiles]. Léiríonn nialais cearnóg fholamh;
  • blankPos — suíomh na cearnóige folamh.

Cluiche loighic

Ní mór dúinn modh athshocraithe a shainiú a úsáidtear chun suíomh cluiche nua a thosú. Ar an mbealach seo shocraímid luach do gach eilimint den eagar clibeanna. Bhuel, ansin cuirimid blankPos sa suíomh deireanach den eagar.

Teastaíonn modh suaite uainn freisin chun an raon clibeanna a shuffles. Ní chuirimid an chlib folamh san áireamh sa phróiseas shuffling chun é a fhágáil sa suíomh céanna.

Ós rud é nach bhfuil réiteach ach ag leath de na suíomhanna tosaigh féideartha den bhfreagra, ní mór duit an toradh suaitheadh ​​​​mar thoradh air a sheiceáil chun a chinntiú go bhfuil an leagan amach reatha fiú intuaslagtha. Chun seo a dhéanamh, sainímid an modh isSolvable.

Má thagann clib ar a bhfuil luach níos airde roimh chlib ar leith, meastar gur inbhéartú é. Nuair a bhíonn an láthair folamh i bhfeidhm, caithfidh líon na n-aisiompuithe a bheith cothrom le go mbeidh an bhfreagra intuaslagtha. Mar sin déanaimid líon na n-aisiompuithe a chomhaireamh agus fillimid fíor má tá an uimhir cothrom.

Tá sé tábhachtach ansin an modh isSolved a shainiú chun a sheiceáil an bhfuil ár leagan amach Game Of Fifteen réitithe. Ar dtús féachaimid ar an áit a bhfuil an láthair folamh. Más rud é sa suíomh tosaigh, ansin tá an t-ailíniú reatha nua, nár cinneadh roimhe seo. Déanaimid ite arís trí na tíleanna in ord droim ar ais, agus má tá luach an chlib difriúil ón innéacs comhfhreagrach +1, filleann muid bréagach. Seachas sin, ag deireadh an mhodha tá sé in am filleadh fíor toisc go bhfuil an bhfreagra réitithe cheana féin.

Modh eile nach mór a shainiú ná newGame. Tá sé ag teastáil chun sampla nua den chluiche a chruthú. Chun seo a dhéanamh, déanaimid an pháirc imeartha a athshocrú, ansin é a shuffle agus leanúint ar aghaidh go dtí go mbeidh an suíomh imeartha inréite.

Seo cód samplach leis an eochair-loighic chlib:

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

Ar deireadh, ní mór duit gluaiseacht na gclibeanna san eagar a ríomhchlárú. Glaofar an cód seo níos déanaí trí aisghlao chun freagairt do ghluaiseacht cúrsóra. Tacóidh ár gcluiche le gluaiseachtaí iomadúla tíl ag an am céanna. Mar sin, tar éis dúinn an suíomh brúite ar an scáileán a thiontú ina chlib, faighimid suíomh na clibe folamh agus lorgaimid treo gluaiseachta chun tacú le roinnt dá gluaiseachtaí ag an am céanna.

Seo cód samplach:

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

Forbraímid Chomhéadain ag baint úsáide as Swing API

Tá sé in am oibriú ar an gcomhéadan. Ar dtús glacaimid an rang Jpanel. Ansin tarraingímid clibeanna ar an réimse - chun méideanna gach ceann acu a ríomh, úsáidfimid na sonraí atá sonraithe i bparaiméadar cruthaitheoir an chluiche:

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

Is paraiméadar é an corrlach freisin atá leagtha síos sa chruthaitheoir cluiche.

Anois ní mór dúinn an modh drawGrid a shainiú chun an eangach agus na spotaí ar an scáileán a tharraingt. Déanaimid anailís ar raon na gclibeanna agus déanaimid na comhordanáidí a thiontú ina gcomhordanáidí comhéadan úsáideora. Ansin tarraing gach spota leis an uimhir chomhfhreagrach sa lár:

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

Ar deireadh, déanaimis an modh péint Comhpháirte a shárú, a eascraíonn ón rang JPane. Bainimid úsáid ansin as an modh drawGrid, agus an modh drawStartMessage ina dhiaidh sin chun teachtaireacht a thaispeáint a thugann leid dúinn cliceáil chun an cluiche a thosú:

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

Ag freagairt do ghníomhartha úsáideora san Chomhéadain

Chun gur féidir leis an gcluiche a chúrsa a rith, is gá gníomhartha úsáideora san Chomhéadain a phróiseáil. Chun seo a dhéanamh, cuir cur i bhfeidhm MouseListener ar Jpanel agus an cód le haghaidh spotaí gluaiseachta, a thaispeántar thuas cheana féin:

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

Cuirimid an cód i cruthaitheoir an ranga GameOfFifteen. Ag an deireadh, tugaimid an modh newGame chun cluiche nua a thosú.

Cód cluiche iomlán

Is é an chéim dheireanach sula bhfeictear an cluiche i ngníomh ná na heilimintí cód go léir a chur le chéile. Seo cad a tharlaíonn:

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

Ar deireadh, a ligean ar imirt!

Tá sé in am an cluiche a sheoladh agus é a thástáil i ngníomh. Ba chóir go mbeadh cuma mar seo ar an bpáirc:

"Clib" i Java - conas cluiche lán-chuimsitheach a fhorbairt

Déanaimis iarracht an bhfreagra a réiteach. Má chuaigh gach rud go maith, gheobhaidh muid seo:

"Clib" i Java - conas cluiche lán-chuimsitheach a fhorbairt

Sin é an méid. An raibh tú ag súil le níos mó? 🙂

Molann Skillbox:

Foinse: will.com

Add a comment