Β«ΠŸΡΡ‚Π½Π°ΡˆΠΊΠ°Β» Π½Π° Java β€” ΠΊΠ°ΠΊ Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ ΠΏΠΎΠ»Π½ΠΎΡ†Π΅Π½Π½ΡƒΡŽ ΠΈΠ³Ρ€Ρƒ

«ΠŸΡΡ‚Π½Π°ΡˆΠΊΠ°» Π½Π° Java — ΠΊΠ°ΠΊ Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ ΠΏΠΎΠ»Π½ΠΎΡ†Π΅Π½Π½ΡƒΡŽ ΠΈΠ³Ρ€Ρƒ

Β«ΠŸΡΡ‚Π½Π°Π΄Ρ†Π°Ρ‚ΡŒΒ», ΠΈΠ»ΠΈ Β«ΠŸΡΡ‚Π½Π°ΡˆΠΊΠ°Β» β€” ΠΎΡ‚Π»ΠΈΡ‡Π½Ρ‹ΠΉ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ простой логичСской ΠΈΠ³Ρ€Ρ‹, популярной Π²ΠΎ всСм ΠΌΠΈΡ€Π΅. Для Ρ‚ΠΎΠ³ΠΎ Ρ‡Ρ‚ΠΎΠ±Ρ‹ Ρ€Π΅ΡˆΠΈΡ‚ΡŒ Π³ΠΎΠ»ΠΎΠ²ΠΎΠ»ΠΎΠΌΠΊΡƒ, Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ Ρ€Π°ΡΡΡ‚Π°Π²ΠΈΡ‚ΡŒ ΠΊΠ²Π°Π΄Ρ€Π°Ρ‚ΠΈΠΊΠΈ с Ρ†ΠΈΡ„Ρ€Π°ΠΌΠΈ ΠΏΠΎ порядку, ΠΎΡ‚ мСньшСго ΠΊ Π±ΠΎΠ»ΡŒΡˆΠ΅ΠΌΡƒ. Π­Ρ‚ΠΎ нСпросто, Π½ΠΎ интСрСсно.

Π’ сСгодняшнСм Ρ‚ΡƒΡ‚ΠΎΡ€ΠΈΠ°Π»Π΅ ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅ΠΌ, ΠΊΠ°ΠΊ Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ Β«ΠŸΡΡ‚Π½Π°ΡˆΠΊΡƒΒ» Π½Π° Java 8 с Eclipse. Для Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ UI ΠΌΡ‹ Π±ΡƒΠ΄Π΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Swing API.

НапоминаСм: для всСх Ρ‡ΠΈΡ‚Π°Ρ‚Π΅Π»Π΅ΠΉ Β«Π₯Π°Π±Ρ€Π°Β» β€” скидка 10 000 Ρ€ΡƒΠ±Π»Π΅ΠΉ ΠΏΡ€ΠΈ записи Π½Π° любой курс Skillbox ΠΏΠΎ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Ρƒ Β«Π₯Π°Π±Ρ€Β».

Skillbox Ρ€Π΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡƒΠ΅Ρ‚: ΠžΠ±Ρ€Π°Π·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ ΠΎΠ½Π»Π°ΠΉΠ½-курс Β«ΠŸΡ€ΠΎΡ„Π΅ΡΡΠΈΡ Java-Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΒ».

ΠŸΡ€ΠΎΠ΅ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΈΠ³Ρ€Ρ‹

На этом этапС Π½ΡƒΠΆΠ½ΠΎ ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ свойства:

  • Size β€” Ρ€Π°Π·ΠΌΠ΅Ρ€ ΠΈΠ³Ρ€ΠΎΠ²ΠΎΠ³ΠΎ поля;
  • nbTiles β€” количСство ΠΏΡΡ‚Π½Π°ΡˆΠ΅ΠΊ Π² ΠΏΠΎΠ»Π΅. nbTiles = size*size β€” 1;
  • Tiles β€” ΠΏΡΡ‚Π½Π°ΡˆΠΊΠ°, которая прСдставляСт собой ΠΎΠ΄Π½ΠΎΠΌΠ΅Ρ€Π½Ρ‹ΠΉ массив Ρ†Π΅Π»Ρ‹Ρ… чисСл. КаТдая ΠΈΠ· ΠΏΡΡ‚Π½Π°ΡˆΠ΅ΠΊ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½ΠΎΠ΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ Π² Π΄ΠΈΠ°ΠΏΠ°Π·ΠΎΠ½Π΅ [0, nbTiles]. Ноль ΠΎΠ±ΠΎΠ·Π½Π°Ρ‡Π°Π΅Ρ‚ пустой ΠΊΠ²Π°Π΄Ρ€Π°Ρ‚;
  • blankPos β€” позиция пустого ΠΊΠ²Π°Π΄Ρ€Π°Ρ‚Π°.

Π›ΠΎΠ³ΠΈΠΊΠ° ΠΈΠ³Ρ€Ρ‹

НуТно ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ ΠΌΠ΅Ρ‚ΠΎΠ΄ сброса (reset), ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹ΠΉ для ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ Π½ΠΎΠ²ΠΎΠΉ ΠΈΠ³Ρ€ΠΎΠ²ΠΎΠΉ ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΈ. Π’Π°ΠΊ ΠΌΡ‹ устанавливаСм Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ элСмСнта массива ΠΏΡΡ‚Π½Π°ΡˆΠ΅ΠΊ. Ну Π° ΠΏΠΎΡ‚ΠΎΠΌ ΠΏΠΎΠΌΠ΅Ρ‰Π°Π΅ΠΌ blankPos Π² послСднюю ΠΏΠΎΠ·ΠΈΡ†ΠΈΡŽ массива.

Π’Π°ΠΊΠΆΠ΅ Π½ΡƒΠΆΠ΅Π½ ΠΌΠ΅Ρ‚ΠΎΠ΄ shuffle для пСрСтасовки массива ΠΏΡΡ‚Π½Π°ΡˆΠ΅ΠΊ. ΠœΡ‹ Π½Π΅ Π²ΠΊΠ»ΡŽΡ‡Π°Π΅ΠΌ ΠΏΡƒΡΡ‚ΡƒΡŽ ΠΏΡΡ‚Π½Π°ΡˆΠΊΡƒ Π² процСсс пСрСтасовки, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΡΡ‚Π°Π²ΠΈΡ‚ΡŒ Π΅Π΅ Π² ΠΏΡ€Π΅ΠΆΠ½Π΅ΠΌ ΠΏΠΎΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ.

ΠŸΠΎΡΠΊΠΎΠ»ΡŒΠΊΡƒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠΎΠ»ΠΎΠ²ΠΈΠ½Π° Π²ΠΎΠ·ΠΌΠΎΠΆΠ½Ρ‹Ρ… стартовых ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΉ Π³ΠΎΠ»ΠΎΠ²ΠΎΠ»ΠΎΠΌΠΊΠΈ ΠΈΠΌΠ΅Π΅Ρ‚ Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅, Π½ΡƒΠΆΠ½ΠΎ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΈΡ‚ΡŒ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΠ²ΡˆΠΈΠΉΡΡ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅ΡˆΠΈΠ²Π°Π½ΠΈΡ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠ±Π΅Π΄ΠΈΡ‚ΡŒΡΡ Π² Ρ‚ΠΎΠΌ, Ρ‡Ρ‚ΠΎ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠΉ расклад Π²ΠΎΠΎΠ±Ρ‰Π΅ Ρ€Π΅ΡˆΠ°Π΅ΠΌ. Π§Ρ‚ΠΎΠ±Ρ‹ это ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ, опрСдСляСм ΠΌΠ΅Ρ‚ΠΎΠ΄ isSolvable.

Если ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠΉ ΠΏΡΡ‚Π½Π°ΡˆΠΊΠ΅ ΠΏΡ€Π΅Π΄ΡˆΠ΅ΡΡ‚Π²ΡƒΠ΅Ρ‚ ΠΏΡΡ‚Π½Π°ΡˆΠΊΠ° с Π±ΠΎΠ»Π΅Π΅ высоким Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ΠΌ, это считаСтся инвСрсиСй. Когда пустая ΠΏΡΡ‚Π½Π°ΡˆΠΊΠ° находится Π½Π° своСм мСстС, число инвСрсий Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ Ρ‡Π΅Ρ‚Π½Ρ‹ΠΌ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π³ΠΎΠ»ΠΎΠ²ΠΎΠ»ΠΎΠΌΠΊΠ° Π±Ρ‹Π»Π° Ρ€Π°Π·Ρ€Π΅ΡˆΠΈΠΌΠΎΠΉ. Π˜Ρ‚Π°ΠΊ, ΠΌΡ‹ подсчитываСм количСство инвСрсий ΠΈ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌ true, Ссли число Ρ‡Π΅Ρ‚Π½ΠΎΠ΅.

Π—Π°Ρ‚Π΅ΠΌ Π²Π°ΠΆΠ½ΠΎ ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ ΠΌΠ΅Ρ‚ΠΎΠ΄ isSolved, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΈΡ‚ΡŒ, Ρ€Π΅ΡˆΠ΅Π½ Π»ΠΈ наш расклад Game Of Fifteen. Π‘Π½Π°Ρ‡Π°Π»Π° ΠΌΡ‹ смотрим, Π³Π΄Π΅ находится пустая ΠΏΡΡ‚Π½Π°ΡˆΠΊΠ°. Если Π² Π½Π°Ρ‡Π°Π»ΡŒΠ½ΠΎΠΉ ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΈ, Ρ‚ΠΎ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠΉ расклад β€” Π½ΠΎΠ²Ρ‹ΠΉ, Π½Π΅ Ρ€Π΅ΡˆΠ΅Π½Π½Ρ‹ΠΉ Ρ€Π°Π½Π΅Π΅. Π—Π°Ρ‚Π΅ΠΌ ΠΌΡ‹ ΠΏΠ΅Ρ€Π΅Π±ΠΈΡ€Π°Π΅ΠΌ ΠΏΠ»ΠΈΡ‚ΠΊΠΈ Π² ΠΎΠ±Ρ€Π°Ρ‚Π½ΠΎΠΌ порядкС, ΠΈ, Ссли Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΏΡΡ‚Π½Π°ΡˆΠΊΠΈ отличаСтся ΠΎΡ‚ ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰Π΅Π³ΠΎ индСкса +1, Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌ false. Π’ ΠΏΡ€ΠΎΡ‚ΠΈΠ²Π½ΠΎΠΌ случаС Π² ΠΊΠΎΠ½Ρ†Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Π° ΠΏΠΎΡ€Π° Π²Π΅Ρ€Π½ΡƒΡ‚ΡŒ true, ΠΏΠΎΡ‚ΠΎΠΌΡƒ Ρ‡Ρ‚ΠΎ Π³ΠΎΠ»ΠΎΠ²ΠΎΠ»ΠΎΠΌΠΊΠ° ΡƒΠΆΠ΅ Ρ€Π΅ΡˆΠ΅Π½Π°.

Π•Ρ‰Π΅ ΠΎΠ΄ΠΈΠ½ ΠΌΠ΅Ρ‚ΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π½ΡƒΠΆΠ½ΠΎ ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ, β€” 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;
}

НаконСц, Π½ΡƒΠΆΠ½ΠΎ Π·Π°ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Π΄Π²ΠΈΠΆΠ΅Π½ΠΈΠ΅ ΠΏΡΡ‚Π½Π°ΡˆΠ΅ΠΊ Π² массивС. Π­Ρ‚ΠΎΡ‚ ΠΊΠΎΠ΄ Π±ΡƒΠ΄Π΅Ρ‚ Π²Ρ‹Π·Ρ‹Π²Π°Ρ‚ΡŒΡΡ ΠΏΠΎΠ·ΠΆΠ΅ посрСдством ΠΎΠ±Ρ€Π°Ρ‚Π½ΠΎΠ³ΠΎ Π²Ρ‹Π·ΠΎΠ²Π° (callback), Ρ‡Ρ‚ΠΎΠ±Ρ‹ Ρ€Π΅Π°Π³ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Π½Π° Π΄Π²ΠΈΠΆΠ΅Π½ΠΈΠ΅ курсора. Наша ΠΈΠ³Ρ€Π° Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Ρ‚ΡŒ нСсколько ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Ρ‰Π΅Π½ΠΈΠΉ ΠΏΠ»ΠΈΡ‚ΠΎΠΊ ΠΎΠ΄Π½ΠΎΠ²Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎ. Π’Π°ΠΊΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ, послС Ρ‚ΠΎΠ³ΠΎ ΠΊΠ°ΠΊ ΠΌΡ‹ ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚ΠΈΡ€ΠΎΠ²Π°Π»ΠΈ Π½Π°ΠΆΠ°Ρ‚ΡƒΡŽ ΠΏΠΎΠ·ΠΈΡ†ΠΈΡŽ Π½Π° экранС Π² ΠΏΡΡ‚Π½Π°ΡˆΠΊΡƒ, ΠΌΡ‹ ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΏΠΎΠ·ΠΈΡ†ΠΈΡŽ пустой ΠΏΡΡ‚Π½Π°ΡˆΠΊΠΈ ΠΈ ΠΈΡ‰Π΅ΠΌ Π½Π°ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ двиТСния для ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠΈ Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… Π΅Π΅ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Ρ‰Π΅Π½ΠΈΠΉ ΠΎΠ΄Π½ΠΎΠ²Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎ.

Π’ΠΎΡ‚ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ ΠΊΠΎΠ΄Π°:

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

Π Π°Π·Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅ΠΌ UI Π½Π° Swing API

Π‘Π°ΠΌΠΎΠ΅ врСмя Π·Π°Π½ΡΡ‚ΡŒΡΡ интСрфСйсом. Π‘Π½Π°Ρ‡Π°Π»Π° Π±Π΅Ρ€Π΅ΠΌ класс Jpanel. ΠŸΠΎΡ‚ΠΎΠΌ Π½Π° ΠΏΠΎΠ»Π΅ рисуСм ΠΏΡΡ‚Π½Π°ΡˆΠΊΠΈ β€” для расчСта Ρ€Π°Π·ΠΌΠ΅Ρ€ΠΎΠ² ΠΊΠ°ΠΆΠ΄ΠΎΠΉ Π²ΠΎΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡΡ Π΄Π°Π½Π½Ρ‹ΠΌΠΈ, Π·Π°Π΄Π°Π½Π½Ρ‹ΠΌΠΈ Π² ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π΅ конструктора ΠΈΠ³Ρ€Ρ‹:

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

Margin Ρ‚Π°ΠΊΠΆΠ΅ являСтся ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠΌ, Π·Π°Π΄Π°Π½Π½Ρ‹ΠΌ Π² конструкторС ΠΈΠ³Ρ€Ρ‹.

Π’Π΅ΠΏΠ΅Ρ€ΡŒ Π½ΡƒΠΆΠ½ΠΎ ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ ΠΌΠ΅Ρ‚ΠΎΠ΄ 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);
  }
}

НаконСц, ΠΏΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΠΌ ΠΌΠ΅Ρ‚ΠΎΠ΄ paintComponent, ΡΠ²Π»ΡΡŽΡ‰ΠΈΠΉΡΡ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄Π½Ρ‹ΠΌ класса 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 Π½Π° Jpanel ΠΈ ΠΊΠΎΠ΄ для пСрСмСщСния ΠΏΡΡ‚Π½Π°ΡˆΠ΅ΠΊ, ΡƒΠΆΠ΅ ΠΏΠΎΠΊΠ°Π·Π°Π½Π½Ρ‹ΠΉ Π²Ρ‹ΡˆΠ΅:

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. Π’ самом ΠΊΠΎΠ½Ρ†Π΅ Π²Ρ‹Π·Ρ‹Π²Π°Π΅ΠΌ ΠΌΠ΅Ρ‚ΠΎΠ΄ 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);
    });
  }
 
 
}

НаконСц, ΠΈΠ³Ρ€Π°Π΅ΠΌ!

Π‘Π°ΠΌΠΎΠ΅ врСмя Π·Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΈΠ³Ρ€Ρƒ ΠΈ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΈΡ‚ΡŒ Π΅Π΅ Π² дСйствии. ПолС Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π²Ρ‹Π³Π»ΡΠ΄Π΅Ρ‚ΡŒ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ:

«ΠŸΡΡ‚Π½Π°ΡˆΠΊΠ°» Π½Π° Java — ΠΊΠ°ΠΊ Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ ΠΏΠΎΠ»Π½ΠΎΡ†Π΅Π½Π½ΡƒΡŽ ΠΈΠ³Ρ€Ρƒ

ΠŸΡ€ΠΎΠ±ΡƒΠ΅ΠΌ Ρ€Π΅ΡˆΠΈΡ‚ΡŒ Π³ΠΎΠ»ΠΎΠ²ΠΎΠ»ΠΎΠΌΠΊΡƒ. Если всС ΠΏΡ€ΠΎΡˆΠ»ΠΎ ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ, ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ Π²ΠΎΡ‚ Ρ‡Ρ‚ΠΎ:

«ΠŸΡΡ‚Π½Π°ΡˆΠΊΠ°» Π½Π° Java — ΠΊΠ°ΠΊ Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ ΠΏΠΎΠ»Π½ΠΎΡ†Π΅Π½Π½ΡƒΡŽ ΠΈΠ³Ρ€Ρƒ

Π’ΠΎΡ‚ ΠΈ всё. А Π²Ρ‹ ΠΎΠΆΠΈΠ΄Π°Π»ΠΈ большСго? πŸ™‚

Skillbox Ρ€Π΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡƒΠ΅Ρ‚:

Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ: habr.com