Jumlah Bom Minesweeper
Saya memiliki metode yang memeriksa semua kotak di sekitarnya dan mengembalikan jumlah bom di sekitarnya. Tapi itu kode yang sangat panjang dan jelek, jadi bisakah itu dipersingkat?
final int MINE =10
for (int x = 0; x < counts.length; x++) {
for (int y = 0; y < counts[0].length; y++) {
if (counts[x][y] != MINE) {
int Minesearch = 0;
if (x > 0 && y > 0 && counts[x-1][y-1] == MINE) {//up left
Minesearch++;
}
if (y > 0 && counts[x][y-1] == MINE) {//up
Minesearch++;
}
if (x < counts.length - 1 && y > 0 && counts[x+1][y-1] == MINE) {//up right
Minesearch++;
}
if (x > 0 && counts[x-1][y] == MINE) {//left
Minesearch++;
}
if (x < counts.length - 1 && counts[x+1][y] == MINE) {//right
Minesearch++;
}
if (x > 0 && y < counts[0].length - 1 && counts[x-1][y+1] == MINE) {//down left
Minesearch++;
}
if (y < counts[0].length - 1 && counts[x][y+1] == MINE) {//down
Minesearch++;
}
if (x < counts.length - 1 && y < counts[0].length - 1 && counts[x+1][y+1] == MINE) {//down right
Minesearch++;
}
counts[x][y] = Minesearch;
}
}
}
}
Jawaban
Ini dapat dipersingkat dan "dipercantik" dengan refactoring cyhecking batas dan pemeriksaan tambang menjadi metode internal:
private boolean isWithinBounds(int x, int y) {
return x >= 0 && y >= 0 && x < width && y < height;
}
private boolean isMine(int x, int y) {
return field[x][y] == MINE;
}
Kemudian hitungan ranjau menjadi sepele (kita dapat mengasumsikan bahwa pusat dari kotak 3x3 tidak memiliki ranjau, jika tidak pemain akan meledak dan mengakhiri permainan):
for (int x1 = x - 1; x1 <= x + 1; x1++) {
for (int y1 = y - 1; y1 <= y + 1; y1++) {
if (isWithinBounds(x1, y1) && isMine(x1, y1) {
mineCount++;
}
}
}
Apa yang telah kita lakukan adalah memecah kode menjadi beberapa metode, yang masing-masing menerapkan fungsi kecil dan terdefinisi dengan baik. Karena setiap metode melakukan tepat satu hal, metode ini menjadi lebih mudah untuk dipahami, dipelihara, dan diuji.
Anda harus memeriksa konvensi penamaan Java . Nama variabel harus dalam camelCase, startingWithSmallLetter.
Nama variabel dan metode harus menjelaskan alasan mengapa kode tersebut ada. Misalnya mineSearchmembingungkan karena variabel tidak mencari tambang, hanya menghitungnya. Jadi mineCountalternatif yang lebih baik.
Countsjuga membingungkan karena mengandung nilai bernama MINEyang jelas merupakan penanda untuk sel yang berisi tambang tetapi juga berisi jumlah tambang di sekitarnya. Saya melakukan klon kapal penyapu ranjau (atau sebenarnya pemecah penyapu ranjau) sekali dan saya menggunakan array yang berisi objek-sel. Objek Cell menyediakan metode untuk menanyakan status sel (diinjak, ditandai, tidak diketahui) dan jumlah ranjau di sekitarnya jika telah diinjak.
Meskipun Jawaban @TorbenPutkonen benar, ini adalah pendekatan prosedural untuk masalah tersebut.
Tidak ada yang salah dengan pendekatan prosedural seperti itu, tetapi karena Java adalah bahasa berorientasi objek, kita mungkin mencari pendekatan-OO sebagai gantinya ...
Saya akan mengekstrak cek tetangga menjadi enumseperti ini:
enum Direction {
NORTH{
boolean isBomb(inx x, int y, boolean[] field){
if(0 < x)
return BOMB == field(x-1, y);
else
return false;
}
},
NORTH_WEST{
boolean isBomb(inx x, int y, boolean[] field){
if(0 < x && 0 < y)
return BOMB == field(x-1, y-1);
else
return false;
}
},
SOUTH{
boolean isBomb(inx x, int y, boolean[] field){
if(field.length-1 > x)
return BOMB == field(x+1, y);
else
return false;
}
},
SOUTH_EAST{
boolean isBomb(inx x, int y, boolean[] field){
if(field.length-1 > x && field[0].length-1>y)
return BOMB == field(x+1, y+1);
else
return false;
}
}
// other directions following same pattern
abstract boolean isBomb(inx x, int y, boolean[] field);
}
Manfaatnya adalah enum ini dapat hidup di filenya sendiri dan memiliki tanggung jawab yang sangat terbatas. Artinya mudah untuk memahami apa itu, bukan?
Dalam metode kalkulasi Anda, Anda cukup mengulang enumkonstanta seperti ini:
for (int x = 0; x < counts.length; x++) {
for (int y = 0; y < counts[0].length; y++) {
int mineCount =0;
for(Direction direction : Direction.values()) {
if (direction.isBomb(x, y, counts) ) {
mineCount++;
}
}
}
}
Sebagai langkah berikutnya, saya akan menerapkan prinsip "beri tahu, jangan tanya" dengan mengubah tanda tangan metode:
abstract int getBombValueOf(inx x, int y, boolean[] field);
implementasi di enumakan berubah seperti ini:
int getBombValueOf(inx x, int y, boolean[] field){
if(0 < x && BOMB == field(x-1, y))
return 1;
else
return 0;
},
Itu bisa disederhanakan menjadi "operator elvis":
int getBombValueOf(inx x, int y, boolean[] field){
return (0 < x && BOMB == field(x-1, y))
? 1
: 0;
},
dan penggunaannya akan berubah menjadi ini:
for (int x = 0; x < counts.length; x++) {
for (int y = 0; y < counts[0].length; y++) {
int mineCount =0;
for(Direction direction : Direction.values()) {
mineCount +=
direction.getBombValueOf(x, y, counts) );
}
}
}
Kita bisa mencapai hal yang sama (kecuali memindahkan kalkulasi tetangga ke file lain) dengan menggunakan FunctionalInterfacedan koleksi sederhana:
@FunctionalInterface
interface Direction{
int getBombValueOf(inx x, int y, boolean[] field);
}
private final Collection<Direction> directions = new HashSet<>();
// in constructor
directions.add(new Direction() { // old style anonymous inner class
int getBombValueOf(inx x, int y, boolean[] field){
return (0 < x && BOMB == field(x-1, y))
? 1
: 0;
}
};
directions.add((x, y, field)-> { // Java8 Lambda
return (0 < x && 0 < y &&BOMB == field(x-1, y-1))
? 1
: 0;
};
// more following same pattern
// in your method
for (int x = 0; x < counts.length; x++) {
for (int y = 0; y < counts[0].length; y++) {
int mineCount =0;
for(Direction direction : directions) {
mineCount +=
direction.getBombValueOf(x, y, counts) );
}
}
}
Karena itu, kami bisa mendapatkan lebih banyak manfaat dari prinsip-prinsip OO jika bidang permainan tidak akan menjadi array primitif tetapi Koleksi Objek . Tapi itu mungkin barang untuk jawaban lain ...; o)