"Tag" sa Java - kung giunsa paghimo ang usa ka hingpit nga dula
"Kinse" o "Kinse" mao ang usa ka maayo kaayo nga panig-ingnan sa usa ka yano nga lohika dula nga popular sa tibuok kalibutan. Aron masulbad ang puzzle, kinahanglan nimo nga gihan-ay ang mga kwadro nga adunay mga numero sa pagkasunod-sunod, gikan sa labing gamay hangtod sa pinakadako. Dili kini sayon, apan kini makapaikag.
Sa karon nga panudlo gipakita namon kung giunsa ang paghimo sa Kinse sa Java 8 nga adunay Eclipse. Aron mapalambo ang UI atong gamiton ang Swing API.
Gipahinumduman namon ikaw:alang sa tanan nga mga magbabasa sa "Habr" - usa ka diskwento sa 10 nga mga rubles kung nagpalista sa bisan unsang kurso sa Skillbox gamit ang code sa promosyon nga "Habr".
Niini nga yugto kinahanglan nimo nga ipasabut ang mga kabtangan:
Gidak-on — gidak-on sa dulaanan;
nbTiles — gidaghanon sa mga tag sa uma. nbTiles = gidak-on * gidak-on - 1;
Ang mga tile usa ka tag nga usa ka dimensyon nga han-ay sa mga integer. Ang matag usa sa mga tag makadawat usa ka talagsaon nga kantidad sa range [0, nbTiles]. Ang zero nagpaila sa usa ka walay sulod nga kuwadrado;
blankPos — posisyon sa walay sulod nga square.
lohika sa dula
Kinahanglan namon nga ipasabut ang usa ka pamaagi sa pag-reset nga gigamit sa pagsugod sa usa ka bag-ong posisyon sa dula. Niining paagiha nagbutang kami usa ka kantidad alang sa matag elemento sa laray sa mga tag. Aw, unya gibutang namon ang blankPos sa katapusang posisyon sa array.
Nagkinahanglan usab kami og usa ka paagi sa pag-shuffle aron sa pag-shuffle sa han-ay sa mga tag. Wala namo iapil ang walay sulod nga tag sa proseso sa pag-shuffling aron ibilin kini sa samang posisyon.
Tungod kay katunga ra sa posible nga pagsugod nga mga posisyon sa puzzle ang adunay solusyon, kinahanglan nimo nga susihon ang resulta nga shuffle nga resulta aron masiguro nga ang karon nga layout masulbad pa. Aron mahimo kini, among gihubit ang isSolvable nga pamaagi.
Kung ang usa ka partikular nga tag giunhan sa usa ka tag nga adunay mas taas nga kantidad, kini giisip nga usa ka inversion. Kung ang walay sulod nga lugar naa sa lugar, ang gidaghanon sa mga inversion kinahanglan nga bisan alang sa puzzle nga masulbad. Mao nga giihap namon ang gidaghanon sa mga inversion ug ibalik ang tinuod kung parehas ang numero.
Mahinungdanon nga ipasabut ang isSolved nga pamaagi aron masusi kung ang among Game Of Fifteen nga layout nasulbad. Una atong tan-awon kung asa ang bakanteng lugar. Kung sa inisyal nga posisyon, nan ang kasamtangan nga paglinya bag-o, dili kaniadto nakahukom. Gibalikbalik namon ang mga tile sa reverse order, ug kung ang kantidad sa tag lahi sa katugbang nga indeks +1, gibalik namon ang sayup. Kung dili, sa katapusan sa pamaagi panahon na nga mobalik nga tinuod tungod kay ang puzzle nasulbad na.
Ang laing pamaagi nga kinahanglang ipasabot mao ang newGame. Kinahanglan nga maghimo usa ka bag-ong pananglitan sa dula. Aron mahimo kini, among i-reset ang dulaanan, dayon i-shuffle kini ug ipadayon hangtod masulbad ang posisyon sa pagdula.
Ania ang usa ka pananglitan nga code nga adunay yawe nga lohika sa tag:
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;
}
Sa katapusan, kinahanglan nimo nga iprograma ang paglihok sa mga tag sa laray. Kini nga code tawgon sa ulahi pinaagi sa usa ka callback aron pagtubag sa paglihok sa cursor. Ang among dula mosuporta sa daghang mga paglihok sa tile sa parehas nga oras. Busa, human nato mabag-o ang gipiit nga posisyon sa screen ngadto sa usa ka tag, atong makuha ang posisyon sa walay sulod nga tag ug mangita og direksyon sa paglihok aron suportahan ang daghang mga lihok niini sa samang higayon.
Ania ang usa ka pananglitan sa code:
// 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;
Gipalambo namo ang UI gamit ang Swing API
Panahon na aron magtrabaho sa interface. Una mi sa Jpanel nga klase. Dayon magdrowing kami og mga tag sa field - aron makalkulo ang gidak-on sa matag usa, gamiton namo ang datos nga gipiho sa parameter sa game constructor:
Ang margin usa usab ka parameter nga gitakda sa tigtukod sa dula.
Karon kinahanglan namong ipasabut ang pamaagi sa drawGrid aron madrowing ang grid ug mga spots sa screen. Among analisa ang han-ay sa mga tag ug i-convert ang mga coordinate ngadto sa user interface coordinates. Dayon idrowing ang matag dapit nga adunay katumbas nga numero sa tunga:
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);
}
}
Sa katapusan, atong i-override ang paintComponent nga pamaagi, nga gikan sa JPane nga klase. Gigamit dayon namo ang drawGrid nga pamaagi, gisundan sa drawStartMessage nga pamaagi aron ipakita ang mensahe nga nag-aghat kanamo sa pag-klik aron masugdan ang dula:
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);
}
Pagtubag sa mga aksyon sa tiggamit sa UI
Aron ang dula modagan sa kurso niini, gikinahanglan ang pagproseso sa mga aksyon sa user sa UI. Aron mahimo kini, idugang ang pagpatuman sa MouseListener sa Jpanel ug ang code alang sa pagbalhin sa mga spots, gipakita na sa ibabaw:
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();
}
});
Atong ibutang ang code sa constructor sa GameOfFifteen nga klase. Sa katapusan, gitawag namon ang bag-ong pamaagi sa Game aron magsugod usa ka bag-ong dula.
Bug-os nga code sa dula
Ang katapusang lakang sa dili pa makita ang dula sa aksyon mao ang pagbutang sa tanan nga mga elemento sa code. Ania ang mahitabo:
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);
});
}
}
Sa kataposan, magdula ta!
Panahon na aron ilunsad ang dula ug sulayan kini sa aksyon. Ang field kinahanglan nga tan-awon sama niini:
Atong sulayan pagsulbad ang puzzle. Kung maayo ang tanan, makuha namon kini: