рдЬрд╛рд╡рд╛ рдордзреАрд▓ "рдЯреЕрдЧ" - рд╕рдВрдкреВрд░реНрдг рдЧреЗрдо рдХрд╕рд╛ рд╡рд┐рдХрд╕рд┐рдд рдХрд░рд╛рдпрдЪрд╛

рдЬрд╛рд╡рд╛ рдордзреАрд▓ "рдЯреЕрдЧ" - рд╕рдВрдкреВрд░реНрдг рдЧреЗрдо рдХрд╕рд╛ рд╡рд┐рдХрд╕рд┐рдд рдХрд░рд╛рдпрдЪрд╛

"рдкрдВрдзрд░рд╛" рдХрд┐рдВрд╡рд╛ "рдкрдВрдзрд░рд╛" рдЬрдЧрднрд░рд╛рдд рд▓реЛрдХрдкреНрд░рд┐рдп рдЕрд╕рд▓реЗрд▓реНрдпрд╛ рд╕рд╛рдзреНрдпрд╛ рд▓реЙрдЬрд┐рдХ рдЧреЗрдордЪреЗ рдПрдХ рдЙрддреНрдХреГрд╖реНрдЯ рдЙрджрд╛рд╣рд░рдг рдЖрд╣реЗ. рдХреЛрдбреЗ рд╕реЛрдбрд╡рдгреНтАНрдпрд╛рд╕рд╛рдареА, рддреБрдореНтАНрд╣рд╛рд▓рд╛ рд╕рд░реНрд╡рд╛рдд рд▓рд╣рд╛рди рддреЗ рд╕рд░реНрд╡рд╛рдд рдореЛрдареЗ рдЕрд╕реЗ рдХреНрд░рдорд╛рдиреЗ рдЪреМрд░рд╕ рд▓рд╛рд╡рд╛рд╡реЗ рд▓рд╛рдЧрддреАрд▓. рд╣реЗ рд╕реЛрдкреЗ рдирд╛рд╣реА, рдкрд░рдВрддреБ рддреЗ рдордиреЛрд░рдВрдЬрдХ рдЖрд╣реЗ.

рдЖрдЬрдЪреНрдпрд╛ рдЯреНрдпреБрдЯреЛрд░рд┐рдпрд▓рдордзреНрдпреЗ рдЖрдореНрд╣реА рддреБрдореНрд╣рд╛рд▓рд╛ Eclipse рд╕рд╣ Java 8 рдордзреНрдпреЗ Fifteen рдХрд╕реЗ рд╡рд┐рдХрд╕рд┐рдд рдХрд░рд╛рдпрдЪреЗ рддреЗ рджрд╛рдЦрд╡реВ. UI рд╡рд┐рдХрд╕рд┐рдд рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рдЖрдореНрд╣реА рд╕реНрд╡рд┐рдВрдЧ API рд╡рд╛рдкрд░реВ.

рдЖрдореНрд╣реА рдЖрдард╡рдг рдХрд░реВрди рджреЗрддреЛ: рд╕рд░реНрд╡ Habr рд╡рд╛рдЪрдХрд╛рдВрд╕рд╛рдареА - Habr рдкреНрд░реЛрдореЛ рдХреЛрдб рд╡рд╛рдкрд░реВрди рдХреЛрдгрддреНрдпрд╛рд╣реА рд╕реНрдХрд┐рд▓рдмреЙрдХреНрд╕ рдХреЛрд░реНрд╕рдордзреНрдпреЗ рдирд╛рд╡рдиреЛрдВрджрдгреА рдХрд░рддрд╛рдирд╛ 10 рд░реВрдмрд▓ рд╕рд╡рд▓рдд.

рд╕реНрдХрд┐рд▓рдмреЙрдХреНрд╕ рд╢рд┐рдлрд╛рд░рд╕ рдХрд░рддреЛ: рд╢реИрдХреНрд╖рдгрд┐рдХ рдСрдирд▓рд╛рдЗрди рдЕрднреНрдпрд╛рд╕рдХреНрд░рдо "рдкреНрд░реЛрдлреЗрд╢рди рдЬрд╛рд╡рд╛ рдбреЗрд╡реНрд╣рд▓рдкрд░".

рдЧреЗрдо рдбрд┐рдЭрд╛рдЗрди

рдпрд╛ рдЯрдкреНрдкреНрдпрд╛рд╡рд░ рдЖрдкрд▓реНрдпрд╛рд▓рд╛ рдЧреБрдгрдзрд░реНрдо рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░рдгреЗ рдЖрд╡рд╢реНрдпрдХ рдЖрд╣реЗ:

  • рдЖрдХрд╛рд░ - рдЦреЗрд│рдгреНрдпрд╛рдЪреНрдпрд╛ рдореИрджрд╛рдирд╛рдЪрд╛ рдЖрдХрд╛рд░;
  • nbTiles тАФ рдлреАрд▓реНрдбрдордзреАрд▓ рдЯреЕрдЧрдЪреА рд╕рдВрдЦреНрдпрд╛. nbTiles = рдЖрдХрд╛рд░* рдЖрдХрд╛рд░ - 1;
  • рдЯрд╛рдЗрд▓реНрд╕ рд╣рд╛ рдПрдХ рдЯреЕрдЧ рдЖрд╣реЗ рдЬреЛ рдкреВрд░реНрдгрд╛рдВрдХрд╛рдВрдЪрд╛ рдПрдХ-рдЖрдпрд╛рдореА рдЕреЕрд░реЗ рдЖрд╣реЗ. рдкреНрд░рддреНрдпреЗрдХ рдЯреЕрдЧрд▓рд╛ рд╢реНрд░реЗрдгреА [0, nbTiles] рдордзреНрдпреЗ рдПрдХ рдЕрджреНрд╡рд┐рддреАрдп рдореВрд▓реНрдп рдкреНрд░рд╛рдкреНрдд рд╣реЛрдИрд▓. рд╢реВрдиреНрдп рд░рд┐рдХреНрдд рдЪреМрдХреЛрди рджрд░реНрд╢рд╡рддреЛ;
  • blankPos тАФ рд░рд┐рдХрд╛рдореНрдпрд╛ рдЪреМрд░рд╕рд╛рдЪреА рд╕реНрдерд┐рддреА.

рдЦреЗрд│ рддрд░реНрдХ

рдЖрдореНрд╣рд╛рд▓рд╛ рдирд╡реАрди рдЧреЗрдо рд╕реНрдерд┐рддреА рд╕реБрд░реВ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рд╡рд╛рдкрд░рдгреНрдпрд╛рдд рдпреЗрдгрд╛рд░реА рд░реАрд╕реЗрдЯ рдкрджреНрдзрдд рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░рдгреНрдпрд╛рдЪреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдЖрд╣реЗ. рдЕрд╢рд╛ рдкреНрд░рдХрд╛рд░реЗ рдЖрдкрдг рдЯреЕрдЧ рдЕреЕрд░реЗрдЪреНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ рдШрдЯрдХрд╛рд╕рд╛рдареА рдореВрд▓реНрдп рд╕реЗрдЯ рдХрд░реВ. рдмрд░рдВ, рдордЧ рдЖрдкрдг рдЕреЕрд░реЗрдЪреНрдпрд╛ рд╢реЗрд╡рдЯрдЪреНрдпрд╛ рд╕реНрдерд┐рддреАрдд blankPos рдареЗрд╡рддреЛ.

рдЯреЕрдЧрдЪреНрдпрд╛ рдЕреЕрд░реЗрдордзреНрдпреЗ рдлреЗрд░рдмрджрд▓ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рдЖрдореНрд╣рд╛рд▓рд╛ рд╢рдлрд▓ рдкрджреНрдзрдд рджреЗрдЦреАрд▓ рдЖрд╡рд╢реНрдпрдХ рдЖрд╣реЗ. рд░рд┐рдХрд╛рдореНрдпрд╛ рдЯреЕрдЧрд▓рд╛ рддреНрдпрд╛рдЪ рд╕реНрдерд┐рддреАрдд рдареЗрд╡рдгреНрдпрд╛рд╕рд╛рдареА рдЖрдореНрд╣реА рд╢рдлрд▓ рдкреНрд░рдХреНрд░рд┐рдпреЗрдордзреНрдпреЗ рд╕рдорд╛рд╡рд┐рд╖реНрдЯ рдХрд░рдд рдирд╛рд╣реА.

рдХреЛрдбреНрдпрд╛рдЪреНрдпрд╛ рдХреЗрд╡рд│ рдЕрд░реНрдзреНрдпрд╛ рд╕рдВрднрд╛рд╡реНрдп рд╕реБрд░реБрд╡рд╛рддреАрдЪреНрдпрд╛ рдкреЛрдЭрд┐рд╢рдиреНрд╕рдордзреНрдпреЗ рдПрдХ рдЙрдкрд╛рдп рдЕрд╕рд▓реНрдпрд╛рдиреЗ, рд╡рд░реНрддрдорд╛рди рд▓реЗрдЖрдЙрдЯ рдЕрдЧрджреА рд╕реЛрдбрд╡рдгреНрдпрд╛рдпреЛрдЧреНрдп рдЖрд╣реЗ рдпрд╛рдЪреА рдЦрд╛рддреНрд░реА рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рддреБрдореНрд╣рд╛рд▓рд╛ рдкрд░рд┐рдгрд╛рдореА рд╢рдлрд▓ рдкрд░рд┐рдгрд╛рдо рддрдкрд╛рд╕рдгреНрдпрд╛рдЪреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдЖрд╣реЗ. рд╣реЗ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА, рдЖрдореНрд╣реА isSolvable рдкрджреНрдзрдд рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░рддреЛ.

рдПрдЦрд╛рджреНрдпрд╛ рд╡рд┐рд╢рд┐рд╖реНрдЯ рдЯреЕрдЧрдЪреНрдпрд╛ рдЕрдЧреЛрджрд░ рдЙрдЪреНрдЪ рдореВрд▓реНрдп рдЕрд╕рд▓реЗрд▓реНрдпрд╛ рдЯреЕрдЧ рдЕрд╕рд▓реНрдпрд╛рд╕, рддреЗ рдЙрд▓рдЯ рдорд╛рдирд▓реЗ рдЬрд╛рддреЗ. рдЬреЗрд╡реНрд╣рд╛ рд░рд┐рдХрд╛рдореА рдЬрд╛рдЧрд╛ рдЕрд╕рддреЗ, рддреЗрд╡реНрд╣рд╛ рдХреЛрдбреЗ рд╕реЛрдбрд╡рддрд╛ рдпреЗрдгреНрдпрд╛рд╕рд╛рдареА рдЙрд▓рдЯреНрдпрд╛рдВрдЪреА рд╕рдВрдЦреНрдпрд╛ рд╕рдо рдЕрд╕рд╛рдпрд▓рд╛ рд╣рд╡реА. рдореНрд╣рдгреВрди рдЖрдореНрд╣реА рд╡реНрдпреБрддреНрдХреНрд░рдорд╛рдВрдЪреА рд╕рдВрдЦреНрдпрд╛ рдореЛрдЬрддреЛ рдЖрдгрд┐ рдЬрд░ рд╕рдВрдЦреНрдпрд╛ рд╕рдо рдЕрд╕реЗрд▓ рддрд░ рд╕рддреНрдп рдорд┐рд│рд╡рддреЛ.

рдордЧ рдЖрдордЪрд╛ рдЧреЗрдо рдСрдл рдлрд┐рдлреНрдЯреАрди рд▓реЗрдЖрдЙрдЯ рд╕реЛрдбрд╡рд▓рд╛ рдЖрд╣реЗ рдХреА рдирд╛рд╣реА рд╣реЗ рддрдкрд╛рд╕рдгреНрдпрд╛рд╕рд╛рдареА isSolved рдкрджреНрдзрдд рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░рдгреЗ рдорд╣рддреНрд╡рд╛рдЪреЗ рдЖрд╣реЗ. рдкреНрд░рдердо рдЖрдкрдг рд░рд┐рдХрд╛рдореА рдЬрд╛рдЧрд╛ рдХреБрдареЗ рдЖрд╣реЗ рддреЗ рдкрд╛рд╣реВ. рдЬрд░ рдкреНрд░рд╛рд░рдВрднрд┐рдХ рд╕реНрдерд┐рддреАрдд рдЕрд╕реЗрд▓, рддрд░ рд╡рд░реНрддрдорд╛рди рд╕рдВрд░реЗрдЦрди рдирд╡реАрди рдЖрд╣реЗ, рдкреВрд░реНрд╡реА рдард░рд╡рд▓реЗрд▓реЗ рдирд╛рд╣реА. рдЖрдореНрд╣реА рдирдВрддрд░ рдЙрд▓рдЯ рдХреНрд░рдорд╛рдиреЗ рдЯрд╛рдЗрд▓реНрд╕рджреНрд╡рд╛рд░реЗ рдкреБрдирд░рд╛рд╡реГрддреНрддреА рдХрд░рддреЛ рдЖрдгрд┐ рдЬрд░ рдЯреЕрдЧрдЪреЗ рдореВрд▓реНрдп рд╕рдВрдмрдВрдзрд┐рдд рдирд┐рд░реНрджреЗрд╢рд╛рдВрдХ +1 рдкреЗрдХреНрд╖рд╛ рд╡реЗрдЧрд│реЗ рдЕрд╕реЗрд▓ рддрд░ рдЖрдореНрд╣реА рдЪреБрдХреАрдЪреЗ рдкрд░рдд рдХрд░рддреЛ. рдЕрдиреНрдпрдерд╛, рдкрджреНрдзрддреАрдЪреНрдпрд╛ рд╢реЗрд╡рдЯреА, рдХреЛрдбреЗ рдЖрдзреАрдЪ рд╕реЛрдбрд╡рд▓реЗ рдЧреЗрд▓реЗ рдЕрд╕рд▓реНрдпрд╛рдиреЗ рддреЗ рдЦрд░реЗ рдард░рдгреНрдпрд╛рдЪреА рд╡реЗрд│ рдЖрд▓реА рдЖрд╣реЗ.

рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░рдгреЗ рдЖрд╡рд╢реНрдпрдХ рдЕрд╕рд▓реЗрд▓реА рджреБрд╕рд░реА рдкрджреНрдзрдд рдореНрд╣рдгрдЬреЗ newGame. рдЧреЗрдордЪреЗ рдирд╡реАрди рдЙрджрд╛рд╣рд░рдг рддрдпрд╛рд░ рдХрд░рдгреЗ рдЖрд╡рд╢реНрдпрдХ рдЖрд╣реЗ. рд╣реЗ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА, рдЖрдореНрд╣реА рдЦреЗрд│рдгреНрдпрд╛рдЪреЗ рдХреНрд╖реЗрддреНрд░ рд░реАрд╕реЗрдЯ рдХрд░рддреЛ, рдирдВрддрд░ рддреЗ рд╢рдлрд▓ рдХрд░рддреЛ рдЖрдгрд┐ рдЦреЗрд│рдгреНрдпрд╛рдЪреА рд╕реНрдерд┐рддреА рдирд┐рд░рд╛рдХрд░рдг рдХрд░рдгреНрдпрд╛рдпреЛрдЧреНрдп рд╣реЛрдИрдкрд░реНрдпрдВрдд рд╕реБрд░реВ рдареЗрд╡рддреЛ.

рдпреЗрдереЗ рдЯреЕрдЧрдЪреНрдпрд╛ рдореБрдЦреНрдп рддрд░реНрдХрд╛рд╕рд╣ рдПрдХ рдЙрджрд╛рд╣рд░рдг рдХреЛрдб рдЖрд╣реЗ:

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;

рдЖрдореНрд╣реА рд╕реНрд╡рд┐рдВрдЧ API рд╡рд╛рдкрд░реВрди UI рд╡рд┐рдХрд╕рд┐рдд рдХрд░рддреЛ

рдЗрдВрдЯрд░рдлреЗрд╕рд╡рд░ рдХрд╛рдо рдХрд░рдгреНрдпрд╛рдЪреА рд╡реЗрд│ рдЖрд▓реА рдЖрд╣реЗ. рдкреНрд░рдердо рдЖрдкрдг Jpanel рдХреНрд▓рд╛рд╕ рдШреЗрдК. рдордЧ рдЖрдореНрд╣реА рдлреАрд▓реНрдбрд╡рд░ рдЯреЕрдЧ рдХрд╛рдврддреЛ - рдкреНрд░рддреНрдпреЗрдХрд╛рдЪреНрдпрд╛ рдЖрдХрд╛рд░рд╛рдВрдЪреА рдЧрдгрдирд╛ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА, рдЖрдореНрд╣реА рдЧреЗрдо рдХрдиреНрд╕реНрдЯреНрд░рдХреНрдЯрд░ рдкреЕрд░рд╛рдореАрдЯрд░рдордзреНрдпреЗ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХреЗрд▓реЗрд▓рд╛ рдбреЗрдЯрд╛ рд╡рд╛рдкрд░реВ:

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

рдорд╛рд░реНрдЬрд┐рди рд╣рд╛ рдЧреЗрдо рдХрдиреНрд╕реНрдЯреНрд░рдХреНрдЯрд░рдордзреНрдпреЗ рд╕реЗрдЯ рдХреЗрд▓реЗрд▓рд╛ рдкреЕрд░рд╛рдореАрдЯрд░ рджреЗрдЦреАрд▓ рдЖрд╣реЗ.

рдЖрддрд╛ рдЖрдкрд▓реНрдпрд╛рд▓рд╛ рд╕реНрдХреНрд░реАрдирд╡рд░ рдЧреНрд░рд┐рдб рдЖрдгрд┐ рд╕реНрдкреЙрдЯреНрд╕ рдХрд╛рдврдгреНрдпрд╛рд╕рд╛рдареА drawGrid рдкрджреНрдзрдд рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░рд╛рдпрдЪреА рдЖрд╣реЗ. рдЖрдореНрд╣реА рдЯреЕрдЧрдЪреНрдпрд╛ рдЕреЕрд░реЗрдЪреЗ рд╡рд┐рд╢реНрд▓реЗрд╖рдг рдХрд░рддреЛ рдЖрдгрд┐ рдирд┐рд░реНрджреЗрд╢рд╛рдВрдХрд╛рдВрдирд╛ рд╡рд╛рдкрд░рдХрд░реНрддрд╛ рдЗрдВрдЯрд░рдлреЗрд╕ рдирд┐рд░реНрджреЗрд╢рд╛рдВрдХрд╛рдВрдордзреНрдпреЗ рд░реВрдкрд╛рдВрддрд░рд┐рдд рдХрд░рддреЛ. рдирдВрддрд░ рдордзреНрдпрднрд╛рдЧреА рд╕рдВрдмрдВрдзрд┐рдд рдХреНрд░рдорд╛рдВрдХрд╛рд╕рд╣ рдкреНрд░рддреНрдпреЗрдХ рд╕реНрдкреЙрдЯ рдХрд╛рдврд╛:

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

UI рдордзреАрд▓ рд╡рд╛рдкрд░рдХрд░реНрддреНрдпрд╛рдЪреНрдпрд╛ рдХреНрд░рд┐рдпрд╛рдВрд╡рд░ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рджреЗрдгреЗ

рдЧреЗрдордЪрд╛ рдХреЛрд░реНрд╕ рдЪрд╛рд▓рд╡рдгреНрдпрд╛рд╕рд╛рдареА, UI рдордзреНрдпреЗ рд╡рд╛рдкрд░рдХрд░реНрддреНрдпрд╛рдЪреНрдпрд╛ рдХреНрд░рд┐рдпрд╛рдВрд╡рд░ рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдХрд░рдгреЗ рдЖрд╡рд╢реНрдпрдХ рдЖрд╣реЗ. рд╣реЗ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА, Jpanel рд╡рд░ 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();
  }
});

рдЖрдореНрд╣реА рдЧреЗрдордСрдлрдлрд┐рдлреНрдЯреАрди рд╡рд░реНрдЧрд╛рдЪреНрдпрд╛ рдХрдиреНрд╕реНрдЯреНрд░рдХреНрдЯрд░рдордзреНрдпреЗ рдХреЛрдб рдареЗрд╡рддреЛ. рдЕрдЧрджреА рд╢реЗрд╡рдЯреА, рдЖрдореНрд╣реА рдирд╡реАрди рдЧреЗрдо рд╕реБрд░реВ рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА newGame рдкрджреНрдзрдд рдореНрд╣рдгрддреЛ.

рдкреВрд░реНрдг рдЧреЗрдо рдХреЛрдб

рдЧреЗрдо рдХреГрддреАрдд рдкрд╛рд╣рдгреНрдпрд╛рдкреВрд░реНрд╡реА рд╢реЗрд╡рдЯрдЪреА рдкрд╛рдпрд░реА рдореНрд╣рдгрдЬреЗ рд╕рд░реНрд╡ рдХреЛрдб рдШрдЯрдХ рдПрдХрддреНрд░ рдареЗрд╡рдгреЗ. рдХрд╛рдп рд╣реЛрддреЗ рддреЗ рдпреЗрдереЗ рдЖрд╣реЗ:

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

рд╢реЗрд╡рдЯреА, рдЪрд▓рд╛ рдЦреЗрд│реВрдпрд╛!

рдЧреЗрдо рд▓реЙрдиреНрдЪ рдХрд░рдгреНрдпрд╛рдЪреА рдЖрдгрд┐ рдХреГрддреАрдд рдЪрд╛рдЪрдгреА рдШреЗрдгреНрдпрд╛рдЪреА рд╣реА рд╡реЗрд│ рдЖрд╣реЗ. рдлреАрд▓реНрдб рдпрд╛рд╕рд╛рд░рдЦреЗ рджрд┐рд╕рд▓реЗ рдкрд╛рд╣рд┐рдЬреЗ:

рдЬрд╛рд╡рд╛ рдордзреАрд▓ "рдЯреЕрдЧ" - рд╕рдВрдкреВрд░реНрдг рдЧреЗрдо рдХрд╕рд╛ рд╡рд┐рдХрд╕рд┐рдд рдХрд░рд╛рдпрдЪрд╛

рдЪрд▓рд╛ рдХреЛрдбреЗ рд╕реЛрдбрд╡рдгреНрдпрд╛рдЪрд╛ рдкреНрд░рдпрддреНрди рдХрд░реВрдпрд╛. рдЬрд░ рд╕рд░реНрд╡ рдХрд╛рд╣реА рдареАрдХ рдЭрд╛рд▓реЗ рддрд░ рдЖрдореНрд╣рд╛рд▓рд╛ рд╣реЗ рдорд┐рд│реЗрд▓:

рдЬрд╛рд╡рд╛ рдордзреАрд▓ "рдЯреЕрдЧ" - рд╕рдВрдкреВрд░реНрдг рдЧреЗрдо рдХрд╕рд╛ рд╡рд┐рдХрд╕рд┐рдд рдХрд░рд╛рдпрдЪрд╛

рдЗрддрдХрдВрдЪ. рддреБрдореНрд╣рд╛рд▓рд╛ рдЕрдзрд┐рдХ рдЕрдкреЗрдХреНрд╖рд╛ рд╣реЛрддреА рдХрд╛? ЁЯЩВ

рд╕реНрдХрд┐рд▓рдмреЙрдХреНрд╕ рд╢рд┐рдлрд╛рд░рд╕ рдХрд░рддреЛ:

рд╕реНрддреНрд░реЛрдд: www.habr.com

рдПрдХ рдЯрд┐рдкреНрдкрдгреА рдЬреЛрдбрд╛