Minesweeper BombCounts
Tôi có một phương pháp kiểm tra tất cả các ô xung quanh nó và trả về số lượng bom xung quanh nó. Nhưng nó thực sự dài mã và xấu xí, vậy nó có thể được rút ngắn?
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;
}
}
}
}
Trả lời
Nó có thể được rút ngắn và "làm đẹp" bằng cách cấu trúc lại các giới hạn kiểm tra mạng và kiểm tra của tôi thành phương pháp nội bộ:
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;
}
Sau đó, số lượng mìn trở nên nhỏ (chúng ta có thể giả định rằng tâm của hình vuông 3x3 không có mìn, nếu không người chơi sẽ phát nổ và kết thúc trò chơi):
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++;
}
}
}
Những gì chúng tôi đã làm là chia mã thành các phương thức, mỗi phương thức thực hiện một chức năng nhỏ và được xác định rõ. Bởi vì mỗi phương pháp thực hiện chính xác một việc, chúng trở nên dễ hiểu, dễ bảo trì và kiểm tra hơn.
Bạn nên kiểm tra các quy ước đặt tên Java . Tên biến phải ở trong camelCase, startingWithSmallLetter.
Tên biến và phương thức phải mô tả lý do tại sao mã tồn tại. Ví dụ: mineSearchkhó hiểu vì biến không tìm kiếm mỏ, nó chỉ giữ số lượng của chúng. Vì vậy, mineCountlà một thay thế tốt hơn.
Countscũng gây nhầm lẫn vì nó chứa một giá trị được đặt tên MINErõ ràng là một điểm đánh dấu cho một ô chứa mỏ nhưng nó cũng chứa số lượng mỏ xung quanh. Tôi đã tạo một bản sao của máy quét mìn (hoặc thực sự là một người giải quyết máy quét mìn) một lần và tôi sử dụng một mảng có chứa các đối tượng Cell. Đối tượng Cell cung cấp các phương thức để truy vấn trạng thái của ô (bước lên, gắn cờ, không xác định) và số lượng mìn xung quanh nếu nó đã được bước vào.
Mặc dù Câu trả lời của @TorbenPutkonen là đúng, nó là một cách tiếp cận thủ tục cho vấn đề.
Không có gì sai với các phương pháp tiếp cận thủ tục như vậy, nhưng vì Java là một ngôn ngữ hướng đối tượng, chúng tôi có thể tìm kiếm các phương pháp tiếp cận OO để thay thế ...
Tôi sẽ trích xuất kiểm tra hàng xóm thành một enumnhư thế này:
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);
}
Lợi ích là enum này có thể sống trong tệp riêng của nó và có trách nhiệm rất hạn chế. Điều đó có nghĩa là nó rất dễ hiểu là gì, phải không?
Trong phương pháp tính toán của mình, bạn có thể chỉ cần lặp lại các enumhằng số như sau:
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++;
}
}
}
}
Bước tiếp theo, tôi sẽ áp dụng nguyên tắc "cho biết, không hỏi" bằng cách thay đổi chữ ký phương thức:
abstract int getBombValueOf(inx x, int y, boolean[] field);
việc triển khai trong enumsẽ thay đổi như thế này:
int getBombValueOf(inx x, int y, boolean[] field){
if(0 < x && BOMB == field(x-1, y))
return 1;
else
return 0;
},
Điều đó có thể được đơn giản hóa thành "toán tử elvis":
int getBombValueOf(inx x, int y, boolean[] field){
return (0 < x && BOMB == field(x-1, y))
? 1
: 0;
},
và cách sử dụng sẽ thay đổi thành:
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) );
}
}
}
Chúng ta có thể đạt được điều tương tự (chuyển phép tính hàng xóm sang tệp khác) bằng cách sử dụng một FunctionalInterfacevà một tập hợp đơn giản:
@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) );
}
}
}
Tất nhiên, chúng ta có thể thu được nhiều lợi ích hơn từ các nguyên tắc OO nếu trường thi đấu không phải là một mảng các nguyên thủy mà là một Tập hợp các Đối tượng . Nhưng đó có thể là công cụ cho một câu trả lời khác ...; o)