지뢰 찾기 폭탄

Aug 28 2020

주변의 모든 사각형을 확인하고 주변의 폭탄 수를 반환하는 메서드가 있습니다. 하지만 정말 길고보기 흉한 코드인데 짧게 할 수 있을까요?

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

답변

12 TorbenPutkonen Aug 28 2020 at 12:28

경계를 cyhecking하고 내부 메서드로 검사하여 리팩토링하여 단축하고 "아름답게"만들 수 있습니다.

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

그런 다음 지뢰 수는 사소 해집니다 (3x3 사각형의 중심에 지뢰가 없다고 가정 할 수 있습니다. 그렇지 않으면 플레이어가 폭발하여 게임을 종료했을 것입니다) :

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

우리가 한 일은 코드를 메서드로 나누는 것입니다. 각각의 메서드는 작고 잘 정의 된 함수를 구현합니다. 각 방법은 정확히 한 가지를 수행하기 때문에 이해, 유지 관리 및 테스트가 더 쉬워집니다.

Java 이름 지정 규칙을 확인해야합니다 . 변수 이름은 camelCase, startingWithSmallLetter.

변수 및 메소드 이름은 코드가 존재하는 이유를 설명해야합니다. 예 mineSearch를 들어 변수가 광산을 검색하지 않기 때문에 혼란 스럽습니다. 따라서 mineCount더 나은 대안입니다.

CountsMINE분명히 광산을 포함하는 셀에 대한 표식이지만 주변 광산 수를 포함하는 값이 포함되어 있기 때문에 혼란 스럽습니다 . 지뢰 찾기 클론 (또는 실제로 지뢰 찾기 솔버)을 한 번 수행했고 Cell-objects를 포함하는 배열을 사용했습니다. Cell 객체는 셀의 상태 (스텝 온, 플래그 지정, 알 수 없음) 및 밟은 주변 광산 수를 쿼리하는 메서드를 제공했습니다.

1 TimothyTruckle Aug 29 2020 at 20:29

@TorbenPutkonen의 답변은 정확하지만 문제에 대한 절차 적 접근 방식입니다.

절차 적 접근 방식에는 문제가 없지만 Java는 객체 지향 언어 이기 때문에 대신 OO 접근 방식을 찾아 볼 수 있습니다.

이웃 수표를 다음 enum과 같이 추출합니다 .

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

이점은이 열거 형이 자체 파일에있을 수 있고 매우 제한된 책임을 가질 수 있다는 것입니다. 그것은 무엇을 하는지를 이해하기 쉽다는 것을 의미합니다.

계산 방법에서 다음 enum과 같이 상수를 간단히 반복 할 수 있습니다 .

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

다음 단계로 메서드 서명을 변경하여 "말하고 묻지 마십시오"원칙을 적용합니다.

abstract int getBombValueOf(inx x, int y, boolean[] field);

의 구현 enum은 다음과 같이 변경됩니다.

     int getBombValueOf(inx x, int y, boolean[] field){
       if(0 < x && BOMB == field(x-1, y))
         return 1;
       else
         return 0;
     },

"elvis 연산자"로 단순화 할 수 있습니다.

     int getBombValueOf(inx x, int y, boolean[] field){
       return (0 < x && BOMB == field(x-1, y))
         ? 1 
         : 0;
     },

사용법은 다음과 같이 변경됩니다.

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

a FunctionalInterface및 간단한 컬렉션 을 사용하여 동일한 작업을 수행 할 수 있습니다 (이웃 계산을 다른 파일로 이동하는 경우 제외) .

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

게임 필드가 프리미티브 의 배열이 아니라 Objects 의 컬렉션 이면 OO 원칙에서 훨씬 더 많은 이점을 얻을 수 있습니다 . 그러나 그것은 다른 대답에 대한 물건 일 수 있습니다 ...; o)