
Money 내부에서 검증을 맡기기에 응집도가 높아진다.
+getter도 마찬가지로 사용 자제 추천.

(1)이 아닌 (2)를 사용하자.
객체의 캡슐화를 존중해주어야 한다.
private static boolean isAllCellOpened() {
return Arrays.stream(BOARD2)
.flatMap(Arrays::stream)
.noneMatch(cell->cell.equalsSign(CLOSED_CELL_SIGN));
//getSign을 쓸수도 있겠지만, 최대한 안 쓰는 방향으로 Cell에 물어본다.
}
물론 때에 따라 get이 있는 것이 나을 때도 있다.
private static void showBoard() {
System.out.println(" a b c d e f g h i j");
for (int row = 0; row < BOARD_ROW_SIZE; row++) {
System.out.printf("%d ", row + 1);
for (int col = 0; col < BOARD_COL_SIZE; col++) {
//print를 여기서 하는 게 나으니까, get을 씀
System.out.print(BOARD2[row][col].getSign() + " ");
}
System.out.println();
}
System.out.println();
}


헷갈리지 않게 이렇게 코드를 바꾸자.
Example Code:
// public static final String FLAG_SIGN = "⚑";
// public static final String CLOSED_CELL_SIGN = "□";
// public static final String OPENED_CELL_SIGN = "■";
// public static final String LAND_MINE_SIGN = "☼";
public static final String FLAG_SIGN = "⚑";
public static final String UNCHECKED_SIGN = "□";
public static final String EMPTY_SIGN = "■";
public static final String LAND_MINE_SIGN = "☼";
정리된 Code
package cleancode.minesweeper.tobe;
import java.util.Arrays;
import java.util.Random;
import java.util.Scanner;
public class MinesweeperGame {
public static final int BOARD_ROW_SIZE = 8;
public static final int BOARD_COL_SIZE = 10;
public static final Scanner SCANNER = new Scanner(System.in);
private static final Cell[][] BOARD = new Cell[BOARD_ROW_SIZE][BOARD_COL_SIZE];
private static final int LAND_MINE_COUNT = 10;
private static int gameStatus = 0; // 0: 게임 중, 1: 승리, -1: 패배
public static void main(String[] args) {
showGameStartComments();
initializeGame();
while (true) {
try {
showBoard();
if (doesUserWinTheGame()) {
System.out.println("지뢰를 모두 찾았습니다. GAME CLEAR!");
break;
}
if (doesUserLooseTheGame()) {
System.out.println("지뢰를 밟았습니다. GAME OVER!");
break;
}
String cellInput = getCellInputFromUser();
String userActionInput = getUserActionInputFromUser();
actOnCell(cellInput, userActionInput);
} catch (AppException e) {
System.out.println(e.getMessage());
}catch(Exception e) {
System.out.println("프로그램에 문제가 생겼습니다.");
}
}
}
private static void actOnCell(String cellInput, String userActionInput) {
int selectedColIndex = getSelectedColIndex(cellInput);
int selectedRowIndex = getSelectedRowIndex(cellInput);
if (doesUserChooseFlag(userActionInput)) {
BOARD[selectedRowIndex][selectedColIndex].flag();
checkIfGameIsOver();
return;
}
if (doesUserChooseToOpenCell(userActionInput)) {
if (isLandMineCell(selectedRowIndex, selectedColIndex)) {
BOARD[selectedRowIndex][selectedColIndex].open();
changeGameStatusToLoose();
return;
}
open(selectedRowIndex, selectedColIndex);
checkIfGameIsOver();
return;
}
throw new AppException("잘못된 번호를 선택하셨습니다.");
}
private static void changeGameStatusToLoose() {
gameStatus = -1;
}
private static boolean isLandMineCell(int selectedRowIndex, int selectedColIndex) {
return BOARD[selectedRowIndex][selectedColIndex].isLandMine();
}
private static boolean doesUserChooseToOpenCell(String userActionInput) {
return userActionInput.equals("1");
}
private static boolean doesUserChooseFlag(String userActionInput) {
return userActionInput.equals("2");
}
private static int getSelectedRowIndex(String cellInput) {
char cellInputRow = cellInput.charAt(1);
return convertRowFrom(cellInputRow);
}
private static int getSelectedColIndex(String cellInput) {
char cellInputCol = cellInput.charAt(0);
return convertColFrom(cellInputCol);
}
private static String getUserActionInputFromUser() {
System.out.println("선택한 셀에 대한 행위를 선택하세요. (1: 오픈, 2: 깃발 꽂기)");
return SCANNER.nextLine();
}
private static String getCellInputFromUser() {
System.out.println("선택할 좌표를 입력하세요. (예: a1)");
return SCANNER.nextLine();
}
private static boolean doesUserLooseTheGame() {
return gameStatus == -1;
}
private static boolean doesUserWinTheGame() {
return gameStatus == 1;
}
private static void checkIfGameIsOver() {
boolean isAllChecked = isAllCellChecked();
if (isAllChecked) {
changeGameStatusToWin();
}
}
private static void changeGameStatusToWin() {
gameStatus = 1;
}
private static boolean isAllCellChecked() {
return Arrays.stream(BOARD)
.flatMap(Arrays::stream)
.allMatch(Cell::isChecked);
//getSign을 쓸수도 있겠지만, 최대한 안 쓰는 방향으로 Cell에 물어본다.
}
private static int convertRowFrom(char cellInputRow) {
int rowIndex = Character.getNumericValue(cellInputRow) - 1;
if (rowIndex > BOARD_ROW_SIZE) {
throw new AppException("잘못된 입력입니다.");
}
return rowIndex;
}
private static int convertColFrom(char cellInputCol) {
switch (cellInputCol) {
case 'a':
// selectedColIndex = 0;
// break;
return 0;
case 'b':
return 1;
case 'c':
return 2;
case 'd':
return 3;
case 'e':
return 4;
case 'f':
return 5;
case 'g':
return 6;
case 'h':
return 7;
case 'i':
return 8;
case 'j':
return 9;
default:
throw new AppException("잘못된 입력입니다.");
}
}
private static void showBoard() {
System.out.println(" a b c d e f g h i j");
for (int row = 0; row < BOARD_ROW_SIZE; row++) {
System.out.printf("%d ", row + 1);
for (int col = 0; col < BOARD_COL_SIZE; col++) {
//print를 여기서 하는 게 나으니까, get을 씀
System.out.print(BOARD[row][col].getSign() + " ");
}
System.out.println();
}
System.out.println();
}
private static void initializeGame() {
for (int row = 0; row < BOARD_ROW_SIZE; row++) {
for (int col = 0; col < BOARD_COL_SIZE; col++) {
BOARD[row][col] = Cell.create();
}
}
for (int i = 0; i < LAND_MINE_COUNT; i++) {
int col = new Random().nextInt(BOARD_COL_SIZE);
int row = new Random().nextInt(BOARD_ROW_SIZE);
BOARD[row][col].turnOnLandMine();
}
for (int row = 0; row < BOARD_ROW_SIZE; row++) {
for (int col = 0; col < BOARD_COL_SIZE; col++) {
if (isLandMineCell(row, col)) {
continue;
}
int count = countNearbyLandMines(row, col);
BOARD[row][col].updateNearbyLandMineCount(count);
}
}
}
private static int countNearbyLandMines(int row, int col) {
int count = 0;
if (row - 1 >= 0 && col - 1 >= 0 && isLandMineCell(row - 1, col - 1)) {
count++;
}
if (row - 1 >= 0 && isLandMineCell(row - 1, col)) {
count++;
}
if (row - 1 >= 0 && col + 1 < 10 && isLandMineCell(row - 1, col + 1)) {
count++;
}
if (col - 1 >= 0 && isLandMineCell(row, col - 1)) {
count++;
}
if (col + 1 < 10 && isLandMineCell(row, col + 1)) {
count++;
}
if (row + 1 < 8 && col - 1 >= 0 && isLandMineCell(row + 1, col - 1)) {
count++;
}
if (row + 1 < 8 && isLandMineCell(row + 1, col)) {
count++;
}
if (row + 1 < 8 && col + 1 < 10 && isLandMineCell(row + 1, col + 1)) {
count++;
}
return count;
}
private static void showGameStartComments() {
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
System.out.println("지뢰찾기 게임 시작!");
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
}
private static void open(int row, int col) {
if (row < 0 || row >= BOARD_ROW_SIZE || col < 0 || col >= BOARD_COL_SIZE) {
return;
}
if (BOARD[row][col].isOpened()) {
return;
}
if (isLandMineCell(row, col)) {
return;
}
BOARD[row][col].open();
if (BOARD[row][col].hasLandMineCount()) {
// BOARD[row][col] = Cell.ofNearbyLandMineCount(NEARBY_LAND_MINE_COUNTS[row][col]);
return;
}
open(row - 1, col - 1);
open(row - 1, col);
open(row - 1, col + 1);
open(row, col - 1);
open(row, col + 1);
open(row + 1, col - 1);
open(row + 1, col);
open(row + 1, col + 1);
}
}
package cleancode.minesweeper.tobe;
public class Cell {
public static final String FLAG_SIGN = "⚑";
public static final String UNCHECKED_SIGN = "□";
public static final String EMPTY_SIGN = "■";
public static final String LAND_MINE_SIGN = "☼";
private int nearbyLandMineCount;
private boolean isLandMine;
private boolean isFlagged;
private boolean isOpened;
// Cell이 가진 속성: 근처 지뢰 숫자, 지뢰 여부
// Cell의 상태 : 깃발 유무, 열리다/닫히다 개념, 사용자가 확인함
private Cell(int nearbyLandMineCount, boolean isLandMine, boolean isFlagged, boolean isOpened) {
this.nearbyLandMineCount = nearbyLandMineCount;
this.isLandMine = isLandMine;
this.isFlagged = isFlagged;
this.isOpened = isOpened;
}
public static Cell of(int nearbyLandMineCount, boolean isLandMine, boolean isFlagged, boolean isOpened) {
return new Cell(nearbyLandMineCount,isLandMine, isFlagged, isOpened);
}
public static Cell create() {
return of(0,false, false, false);
}
public void open() {
this.isOpened = true;
}
public boolean isOpened() {
return isOpened;
}
public void turnOnLandMine(){
this.isLandMine = true;
}
public String getSign() {
if(isOpened){
if(isLandMine){
return LAND_MINE_SIGN;
}
if(hasLandMineCount()){
return String.valueOf(nearbyLandMineCount);
}
return EMPTY_SIGN;
}
if(isFlagged){
return FLAG_SIGN;
}
return UNCHECKED_SIGN;
}
public void updateNearbyLandMineCount(int count) {
this.nearbyLandMineCount = count;
}
public void flag() {
this.isFlagged = true;
}
public boolean isChecked() {
return isFlagged || isOpened;
}
public boolean isLandMine() {
return isLandMine;
}
public boolean hasLandMineCount() {
return this.nearbyLandMineCount != 0;
}
}