diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..0a768b9b7 Binary files /dev/null and b/.DS_Store differ diff --git a/src/main/java/cleancode/minesweeper/tobe/BoardIndexConverter.java b/src/main/java/cleancode/minesweeper/tobe/BoardIndexConverter.java new file mode 100644 index 000000000..b7144491e --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/BoardIndexConverter.java @@ -0,0 +1,34 @@ +package cleancode.minesweeper.tobe; + +public class BoardIndexConverter { + + private static final char BASE_CHAR_FOR_COL = 'a'; + + public int getSelectedRowIndex(String cellInput) { + String cellInputRow = cellInput.substring(1); + return convertRowFrom(cellInputRow); + } + + public int getSelectedColIndex(String cellInput) { + char cellInputCol = cellInput.charAt(0); + return convertColFrom(cellInputCol); + } + + private int convertRowFrom(String cellInputRow) { + int rowIndex = Integer.parseInt(cellInputRow) - 1; + if (rowIndex < 0) { + throw new GameException("잘못된 입력입니다."); + } + + return rowIndex; + } + + private int convertColFrom(char cellInputCol) { + int colIndex = cellInputCol - BASE_CHAR_FOR_COL; + if (colIndex < 0) { + throw new GameException("잘못된 입력입니다."); + } + + return colIndex; + } +} diff --git a/src/main/java/cleancode/minesweeper/tobe/GameApplication.java b/src/main/java/cleancode/minesweeper/tobe/GameApplication.java new file mode 100644 index 000000000..9ad5102db --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/GameApplication.java @@ -0,0 +1,24 @@ +package cleancode.minesweeper.tobe; + +import cleancode.minesweeper.tobe.gamelevel.Advanced; +import cleancode.minesweeper.tobe.gamelevel.GameLevel; +import cleancode.minesweeper.tobe.gamelevel.Middle; +import cleancode.minesweeper.tobe.io.ConsoleInputHandler; +import cleancode.minesweeper.tobe.io.ConsoleOutputHandler; +import cleancode.minesweeper.tobe.io.InputHandler; + +public class GameApplication { + + // 이 클래스는 딱 프로그램 실행에 진입점만 가지게 된다. + // 이름도 MinesweeperGame 에서 GameApplication 으로 변경한다. -> 이렇게 변경하면 지뢰찾기게임(Minesweeper 뿐만이 아닌 다른 게임도 실행할 수 있게 된다.) + // 게임 실행에 대한 책임과 지뢰찾기 도메인 자체, 지뢰찾기 게임을 담당하는 역할을 분리했다. + public static void main(String[] args) { + GameLevel gameLevel = new Middle(); + InputHandler inputHandler = new ConsoleInputHandler(); + ConsoleOutputHandler outputHandler = new ConsoleOutputHandler(); + + Minesweeper minesweeper = new Minesweeper(gameLevel, inputHandler, outputHandler); + minesweeper.initialize(); + minesweeper.run(); + } +} diff --git a/src/main/java/cleancode/minesweeper/tobe/GameBoard.java b/src/main/java/cleancode/minesweeper/tobe/GameBoard.java new file mode 100644 index 000000000..a021cfcdc --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/GameBoard.java @@ -0,0 +1,158 @@ +package cleancode.minesweeper.tobe; + +import cleancode.minesweeper.tobe.cell.Cell; +import cleancode.minesweeper.tobe.cell.Cells; +import cleancode.minesweeper.tobe.cell.EmptyCell; +import cleancode.minesweeper.tobe.cell.LandMineCell; +import cleancode.minesweeper.tobe.cell.NumberCell; +import cleancode.minesweeper.tobe.gamelevel.GameLevel; +import cleancode.minesweeper.tobe.positoion.CellPosition; +import cleancode.minesweeper.tobe.positoion.CellPositions; +import cleancode.minesweeper.tobe.positoion.RelativePosition; + +import java.util.List; +public class GameBoard { + + private final Cell[][] board; + private final int landMineCount; + + public GameBoard(GameLevel gameLevel) { + int rowSize = gameLevel.getRowSize(); + int colSize = gameLevel.getColSize(); + board = new Cell[rowSize][colSize]; + + landMineCount = gameLevel.getLandMineCount(); + } + + public void flagAt(CellPosition cellPosition) { + Cell cell = findCell(cellPosition); + cell.flag(); + } + + public void openAt(CellPosition cellPosition) { + Cell cell = findCell(cellPosition); + cell.open(); + } + + public void openSurroundedCells(CellPosition cellPosition) { + if (isOpenedCell(cellPosition)) { + return; + } + if (isLandMineCellAt(cellPosition)) { + return; + } + + openAt(cellPosition); + + if (doesCellHaveLandMineCount(cellPosition)) { + return; + } + + List surroundedPositions = calculateSurroundedPositions(cellPosition, getRowSize(), getColSize()); + surroundedPositions.forEach(this::openSurroundedCells); + } + + private boolean doesCellHaveLandMineCount(CellPosition cellPosition) { + Cell cell = findCell(cellPosition); + return cell.hasLandMineCount(); + } + + private boolean isOpenedCell(CellPosition cellPosition) { + Cell cell = findCell(cellPosition); + return cell.isOpened(); + } + + public boolean isLandMineCellAt(CellPosition cellPosition) { + Cell cell = findCell(cellPosition); + return cell.isLandMine(); + } + + public boolean isAllCellChecked() { + Cells cells = Cells.from(board); + return cells.isAllChecked(); + } + + public boolean isInvalidCellPosition(CellPosition cellPosition) { + int rowSize = getRowSize(); + int colSize = getColSize(); + + return cellPosition.isRowIndexMoreThanOrEqual(rowSize) + || cellPosition.isColIndexMoreThanOrEqual(colSize); + } + + public void initializeGame() { + CellPositions cellPositions = CellPositions.from(board); + + initializeEmptyCells(cellPositions); + + List landMinePositions = cellPositions.extractRandomPositions(landMineCount); + initializeLandMineCells(landMinePositions); + + List numberPositionCandidates = cellPositions.subtract(landMinePositions); + initializeNumberCells(numberPositionCandidates); + } + + private void initializeEmptyCells(CellPositions cellPositions) { + List allPositions = cellPositions.getPositions(); + for (CellPosition position : allPositions) { + updateCellAt(position, new EmptyCell()); + } + } + + private void initializeLandMineCells(List landMinePositions) { + for (CellPosition position : landMinePositions) { + updateCellAt(position, new LandMineCell()); + } + } + + private void initializeNumberCells(List numberPositionCandidates) { + for (CellPosition candidatePosition : numberPositionCandidates) { + int count = countNearbyLandMines(candidatePosition); + if (count != 0) { + updateCellAt(candidatePosition, new NumberCell(count)); + } + } + } + + private void updateCellAt(CellPosition position, Cell cell) { + board[position.getRowIndex()][position.getColIndex()] = cell; + } + + public String getSign(CellPosition cellPosition) { + Cell cell = findCell(cellPosition); + return cell.getSign(); + } + + private Cell findCell(CellPosition cellPosition) { + return board[cellPosition.getRowIndex()][cellPosition.getColIndex()]; + } + + public int getRowSize() { + return board.length; + } + + public int getColSize() { + return board[0].length; + } + + private int countNearbyLandMines(CellPosition cellPosition) { + int rowSize = getRowSize(); + int colSize = getColSize(); + + long count = calculateSurroundedPositions(cellPosition, rowSize, colSize).stream() + .filter(this::isLandMineCellAt) + .count(); + + return (int) count; + } + + private List calculateSurroundedPositions(CellPosition cellPosition, int rowSize, int colSize) { + return RelativePosition.SURROUNDED_POSITIONS.stream() + .filter(cellPosition::canCalculatePositionBy) + .map(cellPosition::calculatePositionBy) + .filter(position -> position.isRowIndexLessThan(rowSize)) + .filter(position -> position.isColIndexLessThan(colSize)) + .toList(); + } + +} diff --git a/src/main/java/cleancode/minesweeper/tobe/GameException.java b/src/main/java/cleancode/minesweeper/tobe/GameException.java new file mode 100644 index 000000000..2b9bae5b8 --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/GameException.java @@ -0,0 +1,7 @@ +package cleancode.minesweeper.tobe; + +public class GameException extends RuntimeException { + public GameException(String message) { + super(message); + } +} diff --git a/src/main/java/cleancode/minesweeper/tobe/Minesweeper.java b/src/main/java/cleancode/minesweeper/tobe/Minesweeper.java new file mode 100644 index 000000000..f649a6e18 --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/Minesweeper.java @@ -0,0 +1,130 @@ +package cleancode.minesweeper.tobe; + +import cleancode.minesweeper.tobe.game.GameInitializable; +import cleancode.minesweeper.tobe.game.GameRunnable; +import cleancode.minesweeper.tobe.gamelevel.GameLevel; +import cleancode.minesweeper.tobe.io.InputHandler; +import cleancode.minesweeper.tobe.io.OutputHandler; +import cleancode.minesweeper.tobe.positoion.CellPosition; + +public class Minesweeper implements GameInitializable, GameRunnable { + + // BOARD 도 하는 일이 너무 많고 중요하기 때문에 Minesweeper 클래스 내부에 상수로 두기에는 너무 책임이 과도하다. + // 이렇게 GameBoard 클래스를 두면 Minesweeper 입장에서는 Cell[][] 이중배열에 대해서는 모른다. + // 객체로 추상화가 되었고, 데이터 구조에 대한 것은 캐슐화가 되었기 때문이다. + private final GameBoard gameBoard; + // SRP: cellInput 이라는 사용자의 입력을 받아서 rowIndex, colIndex 로 변환하는 역할을 하는 또 하나의 클래스로 볼 수 있지 않을까? + + // 게임이 진행되는 핵심 로직들과 사용자 입출력에 대한 로직 책임을 분리한다. + // DIP: InputHandler, OutputHandler 는 이제 Console 에 관한 것은 모른다. 인터페이스만 의존하고 있다. + // 구현체가 변경되어도 Minesweeper 클래스는 영향을 받지 않는다. + private final InputHandler inputHandler; + private final OutputHandler outputHandler; + private int gameStatus = 0; // 0: 게임 중, 1: 승리, -1: 패배 + + public Minesweeper(GameLevel gameLevel, InputHandler inputHandler, OutputHandler outputHandler) { + gameBoard = new GameBoard(gameLevel); + this.inputHandler = inputHandler; + this.outputHandler = outputHandler; + } + + @Override + public void initialize() { + gameBoard.initializeGame(); + } + + @Override + public void run() { + outputHandler.showGameStartComments(); + + while (true) { + try { + outputHandler.showBoard(gameBoard); + + if (doesUserWinTheGame()) { + outputHandler.showGameWinningComment(); + break; + } + if (doesUserLoseTheGame()) { + outputHandler.showGameLosingComment(); + break; + } + + CellPosition cellPosition = getCellInputFromUser(); + String userActionInput = getUserActionInputFromUser(); + actOnCell(cellPosition, userActionInput); + } catch (GameException e) { + outputHandler.showExceptionMessage(e); + } catch (Exception e) { + outputHandler.showSimpleMessage("프로그램에 문제가 생겼습니다."); + } + } + } + + private void actOnCell(CellPosition cellPosition, String userActionInput) { + if (doesUserChooseToPlantFlag(userActionInput)) { + gameBoard.flagAt(cellPosition); + checkIfGameIsOver(); + return; + } + + if (doesUserChooseToOpenCell(userActionInput)) { + if (gameBoard.isLandMineCellAt(cellPosition)) { + gameBoard.openAt(cellPosition); + changeGameStatusToLose(); + return; + } + + gameBoard.openSurroundedCells(cellPosition); + checkIfGameIsOver(); + return; + } + throw new GameException("잘못된 번호를 선택하셨습니다."); + } + + private void changeGameStatusToLose() { + gameStatus = -1; + } + + private boolean doesUserChooseToOpenCell(String userActionInput) { + return userActionInput.equals("1"); + } + + private boolean doesUserChooseToPlantFlag(String userActionInput) { + return userActionInput.equals("2"); + } + + private String getUserActionInputFromUser() { + outputHandler.showCommentForUserAction(); + return inputHandler.getUserInput(); + } + + private CellPosition getCellInputFromUser() { + outputHandler.showCommentForSelectingCell(); + CellPosition cellPosition = inputHandler.getCellPositionFromUser(); + if (gameBoard.isInvalidCellPosition(cellPosition)) { + throw new GameException("잘못된 좌표를 선택하셨습니다."); + } + + return cellPosition; + } + + private boolean doesUserLoseTheGame() { + return gameStatus == -1; + } + + private boolean doesUserWinTheGame() { + return gameStatus == 1; + } + + private void checkIfGameIsOver() { + if (gameBoard.isAllCellChecked()) { + changeGameStatusToWin(); + } + } + + private void changeGameStatusToWin() { + gameStatus = 1; + } + +} diff --git a/src/main/java/cleancode/minesweeper/tobe/MinesweeperGame.java b/src/main/java/cleancode/minesweeper/tobe/MinesweeperGame.java deleted file mode 100644 index dd85c3ce0..000000000 --- a/src/main/java/cleancode/minesweeper/tobe/MinesweeperGame.java +++ /dev/null @@ -1,187 +0,0 @@ -package cleancode.minesweeper.tobe; - -import java.util.Random; -import java.util.Scanner; - -public class MinesweeperGame { - - private static String[][] board = new String[8][10]; - private static Integer[][] landMineCounts = new Integer[8][10]; - private static boolean[][] landMines = new boolean[8][10]; - private static int gameStatus = 0; // 0: 게임 중, 1: 승리, -1: 패배 - - public static void main(String[] args) { - System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); - System.out.println("지뢰찾기 게임 시작!"); - System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); - Scanner scanner = new Scanner(System.in); - for (int i = 0; i < 8; i++) { - for (int j = 0; j < 10; j++) { - board[i][j] = "□"; - } - } - for (int i = 0; i < 10; i++) { - int col = new Random().nextInt(10); - int row = new Random().nextInt(8); - landMines[row][col] = true; - } - for (int i = 0; i < 8; i++) { - for (int j = 0; j < 10; j++) { - int count = 0; - if (!landMines[i][j]) { - if (i - 1 >= 0 && j - 1 >= 0 && landMines[i - 1][j - 1]) { - count++; - } - if (i - 1 >= 0 && landMines[i - 1][j]) { - count++; - } - if (i - 1 >= 0 && j + 1 < 10 && landMines[i - 1][j + 1]) { - count++; - } - if (j - 1 >= 0 && landMines[i][j - 1]) { - count++; - } - if (j + 1 < 10 && landMines[i][j + 1]) { - count++; - } - if (i + 1 < 8 && j - 1 >= 0 && landMines[i + 1][j - 1]) { - count++; - } - if (i + 1 < 8 && landMines[i + 1][j]) { - count++; - } - if (i + 1 < 8 && j + 1 < 10 && landMines[i + 1][j + 1]) { - count++; - } - landMineCounts[i][j] = count; - continue; - } - landMineCounts[i][j] = 0; - } - } - while (true) { - System.out.println(" a b c d e f g h i j"); - for (int i = 0; i < 8; i++) { - System.out.printf("%d ", i + 1); - for (int j = 0; j < 10; j++) { - System.out.print(board[i][j] + " "); - } - System.out.println(); - } - if (gameStatus == 1) { - System.out.println("지뢰를 모두 찾았습니다. GAME CLEAR!"); - break; - } - if (gameStatus == -1) { - System.out.println("지뢰를 밟았습니다. GAME OVER!"); - break; - } - System.out.println(); - System.out.println("선택할 좌표를 입력하세요. (예: a1)"); - String input = scanner.nextLine(); - System.out.println("선택한 셀에 대한 행위를 선택하세요. (1: 오픈, 2: 깃발 꽂기)"); - String input2 = scanner.nextLine(); - char c = input.charAt(0); - char r = input.charAt(1); - int col; - switch (c) { - case 'a': - col = 0; - break; - case 'b': - col = 1; - break; - case 'c': - col = 2; - break; - case 'd': - col = 3; - break; - case 'e': - col = 4; - break; - case 'f': - col = 5; - break; - case 'g': - col = 6; - break; - case 'h': - col = 7; - break; - case 'i': - col = 8; - break; - case 'j': - col = 9; - break; - default: - col = -1; - break; - } - int row = Character.getNumericValue(r) - 1; - if (input2.equals("2")) { - board[row][col] = "⚑"; - boolean open = true; - for (int i = 0; i < 8; i++) { - for (int j = 0; j < 10; j++) { - if (board[i][j].equals("□")) { - open = false; - } - } - } - if (open) { - gameStatus = 1; - } - } else if (input2.equals("1")) { - if (landMines[row][col]) { - board[row][col] = "☼"; - gameStatus = -1; - continue; - } else { - open(row, col); - } - boolean open = true; - for (int i = 0; i < 8; i++) { - for (int j = 0; j < 10; j++) { - if (board[i][j].equals("□")) { - open = false; - } - } - } - if (open) { - gameStatus = 1; - } - } else { - System.out.println("잘못된 번호를 선택하셨습니다."); - } - } - } - - private static void open(int row, int col) { - if (row < 0 || row >= 8 || col < 0 || col >= 10) { - return; - } - if (!board[row][col].equals("□")) { - return; - } - if (landMines[row][col]) { - return; - } - if (landMineCounts[row][col] != 0) { - board[row][col] = String.valueOf(landMineCounts[row][col]); - return; - } else { - board[row][col] = "■"; - } - 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); - } - -} diff --git a/src/main/java/cleancode/minesweeper/tobe/cell/Cell.java b/src/main/java/cleancode/minesweeper/tobe/cell/Cell.java new file mode 100644 index 000000000..7b8d9bcbb --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/cell/Cell.java @@ -0,0 +1,21 @@ +package cleancode.minesweeper.tobe.cell; + +public interface Cell { + + // 하위 클래스에서도 사용할 수 있기 때문에 protected 로 변경 + String FLAG_SIGN = "⚑"; + String UNCHECKED_SIGN = "□"; + + boolean isLandMine(); + boolean hasLandMineCount(); + String getSign(); + + // isOpened, isFlagged 는 Cell 의 공통 기능이므로 그대로 둔다. + void flag(); + + void open(); + + boolean isChecked(); + + boolean isOpened(); +} diff --git a/src/main/java/cleancode/minesweeper/tobe/cell/CellState.java b/src/main/java/cleancode/minesweeper/tobe/cell/CellState.java new file mode 100644 index 000000000..98de7fe51 --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/cell/CellState.java @@ -0,0 +1,37 @@ +package cleancode.minesweeper.tobe.cell; + +public class CellState { + private boolean isFlagged; + private boolean isOpened; + + private CellState(boolean isFlagged, boolean isOpened) { + this.isFlagged = isFlagged; + this.isOpened = isOpened; + } + + public static CellState initialize() { + return new CellState(false, false); + } + + // isOpened, isFlagged 는 Cell 의 공통 기능이므로 그대로 둔다. + public void flag() { + this.isFlagged = true; + } + + public void open() { + this.isOpened = true; + } + + public boolean isChecked() { + return isFlagged || isOpened; + } + + public boolean isOpened() { + return isOpened; + } + + + public boolean isFlagged() { + return isFlagged; + } +} diff --git a/src/main/java/cleancode/minesweeper/tobe/cell/Cells.java b/src/main/java/cleancode/minesweeper/tobe/cell/Cells.java new file mode 100644 index 000000000..252fe78c2 --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/cell/Cells.java @@ -0,0 +1,28 @@ +package cleancode.minesweeper.tobe.cell; + +import java.util.Arrays; +import java.util.List; + +public class Cells { + private final List cells; + + private Cells(List cells) { + this.cells = cells; + } + + public static Cells of(List cells) { + return new Cells(cells); + } + + public static Cells from(Cell[][] cells) { + List cellList = Arrays.stream(cells) + .flatMap(Arrays::stream) + .toList(); + return of(cellList); + } + + public boolean isAllChecked() { + return cells.stream() + .allMatch(Cell::isChecked); + } +} diff --git a/src/main/java/cleancode/minesweeper/tobe/cell/EmptyCell.java b/src/main/java/cleancode/minesweeper/tobe/cell/EmptyCell.java new file mode 100644 index 000000000..0021c03e1 --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/cell/EmptyCell.java @@ -0,0 +1,49 @@ +package cleancode.minesweeper.tobe.cell; + +public class EmptyCell implements Cell { + + private static final String EMPTY_SIGN = "■"; + + private final CellState cellState = CellState.initialize(); + + @Override + public boolean isLandMine() { + return false; + } + + @Override + public boolean hasLandMineCount() { + return false; + } + + @Override + public String getSign() { + if (cellState.isOpened()) { + return EMPTY_SIGN; + } + if (cellState.isFlagged()) { + return FLAG_SIGN; + } + return UNCHECKED_SIGN; + } + + @Override + public void flag() { + cellState.flag(); + } + + @Override + public void open() { + cellState.open(); + } + + @Override + public boolean isChecked() { + return cellState.isChecked(); + } + + @Override + public boolean isOpened() { + return cellState.isOpened(); + } +} diff --git a/src/main/java/cleancode/minesweeper/tobe/cell/LandMineCell.java b/src/main/java/cleancode/minesweeper/tobe/cell/LandMineCell.java new file mode 100644 index 000000000..e79b94774 --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/cell/LandMineCell.java @@ -0,0 +1,49 @@ +package cleancode.minesweeper.tobe.cell; + +public class LandMineCell implements Cell { + + private static final String LAND_MINE_SIGN = "☼"; + private final CellState cellState = CellState.initialize(); + + @Override + public boolean isLandMine() { + return true; + } + + @Override + public boolean hasLandMineCount() { + return false; + } + + @Override + public String getSign() { + if (cellState.isOpened()) { + return LAND_MINE_SIGN; + } + if (cellState.isFlagged()) { + return FLAG_SIGN; + } + + return UNCHECKED_SIGN; + } + + @Override + public void flag() { + cellState.flag(); + } + + @Override + public void open() { + cellState.open(); + } + + @Override + public boolean isChecked() { + return cellState.isChecked(); + } + + @Override + public boolean isOpened() { + return cellState.isOpened(); + } +} diff --git a/src/main/java/cleancode/minesweeper/tobe/cell/NumberCell.java b/src/main/java/cleancode/minesweeper/tobe/cell/NumberCell.java new file mode 100644 index 000000000..4c2431d1f --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/cell/NumberCell.java @@ -0,0 +1,54 @@ +package cleancode.minesweeper.tobe.cell; + +public class NumberCell implements Cell { + + private final int nearbyLandMineCount; + private final CellState cellState = CellState.initialize(); + + + public NumberCell(int nearbyLandMineCount) { + this.nearbyLandMineCount = nearbyLandMineCount; + } + + @Override + public boolean isLandMine() { + return false; + } + + @Override + public boolean hasLandMineCount() { + return true; + } + + @Override + public String getSign() { + if (cellState.isOpened()) { + return String.valueOf(nearbyLandMineCount); + } + if (cellState.isFlagged()) { + return FLAG_SIGN; + } + + return UNCHECKED_SIGN; + } + + @Override + public void flag() { + cellState.flag(); + } + + @Override + public void open() { + cellState.open(); + } + + @Override + public boolean isChecked() { + return cellState.isChecked(); + } + + @Override + public boolean isOpened() { + return cellState.isOpened(); + } +} diff --git a/src/main/java/cleancode/minesweeper/tobe/game/GameInitializable.java b/src/main/java/cleancode/minesweeper/tobe/game/GameInitializable.java new file mode 100644 index 000000000..4acf16e46 --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/game/GameInitializable.java @@ -0,0 +1,5 @@ +package cleancode.minesweeper.tobe.game; + +public interface GameInitializable { + void initialize(); +} diff --git a/src/main/java/cleancode/minesweeper/tobe/game/GameRunnable.java b/src/main/java/cleancode/minesweeper/tobe/game/GameRunnable.java new file mode 100644 index 000000000..1d6c157bc --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/game/GameRunnable.java @@ -0,0 +1,5 @@ +package cleancode.minesweeper.tobe.game; + +public interface GameRunnable { + void run(); +} diff --git a/src/main/java/cleancode/minesweeper/tobe/gamelevel/Advanced.java b/src/main/java/cleancode/minesweeper/tobe/gamelevel/Advanced.java new file mode 100644 index 000000000..ed8c30569 --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/gamelevel/Advanced.java @@ -0,0 +1,18 @@ +package cleancode.minesweeper.tobe.gamelevel; + +public class Advanced implements GameLevel{ + @Override + public int getRowSize() { + return 20; + } + + @Override + public int getColSize() { + return 24; + } + + @Override + public int getLandMineCount() { + return 99; + } +} diff --git a/src/main/java/cleancode/minesweeper/tobe/gamelevel/Beginner.java b/src/main/java/cleancode/minesweeper/tobe/gamelevel/Beginner.java new file mode 100644 index 000000000..4acb00b42 --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/gamelevel/Beginner.java @@ -0,0 +1,18 @@ +package cleancode.minesweeper.tobe.gamelevel; + +public class Beginner implements GameLevel{ + @Override + public int getRowSize() { + return 8; + } + + @Override + public int getColSize() { + return 10; + } + + @Override + public int getLandMineCount() { + return 10; + } +} diff --git a/src/main/java/cleancode/minesweeper/tobe/gamelevel/GameLevel.java b/src/main/java/cleancode/minesweeper/tobe/gamelevel/GameLevel.java new file mode 100644 index 000000000..82781ea48 --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/gamelevel/GameLevel.java @@ -0,0 +1,11 @@ +package cleancode.minesweeper.tobe.gamelevel; + +// 추상화를 정말 바로 보여주는 구조이다. 인터페이스가 갖고 있는 스펙들 즉, 선언된 메서드 선언부들이 이 객체가 어떠한 역할을 갖는지 설명을 해준다. +// 이 GameLevel 인터페이스를 MineSweeper 안에 넣어줄 것이다. +// Minesweeper 객체는 GameLevel 을 받을 것이지만, 인터페이스여서 런타임 시점에 어떤 GameLevel 구현체가 들어오는지는 모른다. 하지만 GameLevel 인터페이스의 스펙은 알고 있다. +// Minesweeper 는 GameLevel 의 스펙을 통해 구현하면 된다. +public interface GameLevel { + int getRowSize(); + int getColSize(); + int getLandMineCount(); +} diff --git a/src/main/java/cleancode/minesweeper/tobe/gamelevel/Middle.java b/src/main/java/cleancode/minesweeper/tobe/gamelevel/Middle.java new file mode 100644 index 000000000..7f6892f76 --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/gamelevel/Middle.java @@ -0,0 +1,18 @@ +package cleancode.minesweeper.tobe.gamelevel; + +public class Middle implements GameLevel{ + @Override + public int getRowSize() { + return 14; + } + + @Override + public int getColSize() { + return 18; + } + + @Override + public int getLandMineCount() { + return 40; + } +} diff --git a/src/main/java/cleancode/minesweeper/tobe/gamelevel/VeryBeginner.java b/src/main/java/cleancode/minesweeper/tobe/gamelevel/VeryBeginner.java new file mode 100644 index 000000000..2cae9dd3b --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/gamelevel/VeryBeginner.java @@ -0,0 +1,20 @@ +package cleancode.minesweeper.tobe.gamelevel; + +public class VeryBeginner implements GameLevel{ + + + @Override + public int getRowSize() { + return 4; + } + + @Override + public int getColSize() { + return 5; + } + + @Override + public int getLandMineCount() { + return 2; + } +} diff --git a/src/main/java/cleancode/minesweeper/tobe/io/ConsoleInputHandler.java b/src/main/java/cleancode/minesweeper/tobe/io/ConsoleInputHandler.java new file mode 100644 index 000000000..23e5b4455 --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/io/ConsoleInputHandler.java @@ -0,0 +1,25 @@ +package cleancode.minesweeper.tobe.io; + +import cleancode.minesweeper.tobe.BoardIndexConverter; +import cleancode.minesweeper.tobe.positoion.CellPosition; + +import java.util.Scanner; + +public class ConsoleInputHandler implements InputHandler { + public static final Scanner SCANNER = new Scanner(System.in); + private final BoardIndexConverter boardIndexConverter = new BoardIndexConverter(); + + @Override + public String getUserInput() { + return SCANNER.nextLine(); + } + + @Override + public CellPosition getCellPositionFromUser() { + String userInput = SCANNER.nextLine(); + + int rowIndex = boardIndexConverter.getSelectedColIndex(userInput); + int colIndex = boardIndexConverter.getSelectedColIndex(userInput); + return CellPosition.of(rowIndex, colIndex); + } +} diff --git a/src/main/java/cleancode/minesweeper/tobe/io/ConsoleOutputHandler.java b/src/main/java/cleancode/minesweeper/tobe/io/ConsoleOutputHandler.java new file mode 100644 index 000000000..b0074d8ed --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/io/ConsoleOutputHandler.java @@ -0,0 +1,71 @@ +package cleancode.minesweeper.tobe.io; + +import cleancode.minesweeper.tobe.GameBoard; +import cleancode.minesweeper.tobe.GameException; +import cleancode.minesweeper.tobe.positoion.CellPosition; + +import java.util.List; +import java.util.stream.IntStream; + +public class ConsoleOutputHandler implements OutputHandler { + + @Override + public void showGameStartComments() { + System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); + System.out.println("지뢰찾기 게임 시작!"); + System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); + } + + @Override + public void showBoard(GameBoard board) { + String alphabets = generateColAlphabets(board); + + System.out.println(" " + alphabets); + for (int row = 0; row < board.getRowSize(); row++) { + System.out.printf("%2d ", row + 1); + for (int col = 0; col < board.getColSize(); col++) { + CellPosition cellPosition = CellPosition.of(row, col); + System.out.print(board.getSign(cellPosition) + " "); + } + System.out.println(); + } + } + + private String generateColAlphabets(GameBoard board) { + List alphabets = IntStream.range(0, board.getColSize()) + .mapToObj(index -> (char) (index + 'a')) + .map(String::valueOf) + .toList(); + return String.join(" ", alphabets); + } + + @Override + public void showGameWinningComment() { + System.out.println("지뢰를 모두 찾았습니다. GAME CLEAR!"); + } + + @Override + public void showGameLosingComment() { + System.out.println("지뢰를 밟았습니다. GAME OVER!"); + } + + @Override + public void showCommentForSelectingCell() { + System.out.println("선택할 좌표를 입력하세요. (예: a1)"); + } + + @Override + public void showCommentForUserAction() { + System.out.println("선택한 셀에 대한 행위를 선택하세요. (1: 오픈, 2: 깃발 꽂기)"); + } + + @Override + public void showExceptionMessage(GameException e) { + System.out.println(e.getMessage()); + } + + @Override + public void showSimpleMessage(String message) { + System.out.println(message); + } +} diff --git a/src/main/java/cleancode/minesweeper/tobe/io/InputHandler.java b/src/main/java/cleancode/minesweeper/tobe/io/InputHandler.java new file mode 100644 index 000000000..cc464f241 --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/io/InputHandler.java @@ -0,0 +1,9 @@ +package cleancode.minesweeper.tobe.io; + +import cleancode.minesweeper.tobe.positoion.CellPosition; + +public interface InputHandler { + String getUserInput(); + + CellPosition getCellPositionFromUser(); +} diff --git a/src/main/java/cleancode/minesweeper/tobe/io/OutputHandler.java b/src/main/java/cleancode/minesweeper/tobe/io/OutputHandler.java new file mode 100644 index 000000000..80bd7f7c8 --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/io/OutputHandler.java @@ -0,0 +1,41 @@ +package cleancode.minesweeper.tobe.io; + +import cleancode.minesweeper.tobe.GameBoard; +import cleancode.minesweeper.tobe.GameException; + +public interface OutputHandler { + + // show, print 중에 show 는 추상적인 느낌이고, print 는 구체적인 느낌이 강하다. + // print 는 console 에 print 한다는 느낌이 강하다. + // 높은 추상화 레벨인 OutputHandler 입장에서는 print 보다는 show 가 더 낫겠다. + + void showGameStartComments(); + + void showBoard(GameBoard board); + + + void showGameWinningComment(); + + void showGameLosingComment(); + + void showCommentForSelectingCell(); + + void showCommentForUserAction(); + + void showExceptionMessage(GameException e); + + void showSimpleMessage(String message); + + +// void printGameWinningComment(); + +// void printGameLosingComment(); + +// void printCommentForSelectingCell(); + +// void printCommentForUserAction(); + +// void printExceptionMessage(GameException e); +// +// void printSimpleMessage(String message); +} diff --git a/src/main/java/cleancode/minesweeper/tobe/positoion/CellPosition.java b/src/main/java/cleancode/minesweeper/tobe/positoion/CellPosition.java new file mode 100644 index 000000000..86b1332dc --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/positoion/CellPosition.java @@ -0,0 +1,73 @@ +package cleancode.minesweeper.tobe.positoion; + +import java.util.Objects; + +public class CellPosition { + private final int rowIndex; + private final int colIndex; + + private CellPosition(int rowIndex, int colIndex) { + if (rowIndex < 0 || colIndex < 0) { + throw new IllegalArgumentException("올바르지 않은 좌표입니다."); + } + this.rowIndex = rowIndex; + this.colIndex = colIndex; + } + + public static CellPosition of(int rowIndex, int colIndex) { + return new CellPosition(rowIndex, colIndex); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CellPosition that = (CellPosition) o; + return rowIndex == that.rowIndex && colIndex == that.colIndex; + } + + @Override + public int hashCode() { + return Objects.hash(rowIndex, colIndex); + } + + public boolean isRowIndexMoreThanOrEqual(int rowIndex) { + return this.rowIndex >= rowIndex; + } + + public boolean isColIndexMoreThanOrEqual(int colIndex) { + return this.colIndex >= colIndex; + } + + public int getRowIndex() { + return rowIndex; + } + + public int getColIndex() { + return colIndex; + } + + public boolean canCalculatePositionBy(RelativePosition relativePosition) { + return this.rowIndex + relativePosition.getDeltaRow() >= 0 + && this.colIndex + relativePosition.getDeltaCol() >= 0; + } + + public CellPosition calculatePositionBy(RelativePosition relativePosition) { + if (this.canCalculatePositionBy(relativePosition)) { + return CellPosition.of( + this.rowIndex + relativePosition.getDeltaRow(), + this.colIndex + relativePosition.getDeltaCol() + ); + } + throw new IllegalArgumentException("움직일 수 있는 좌표가 아닙니다."); + } + + public boolean isRowIndexLessThan(int rowIndex) { + return this.rowIndex < rowIndex; + } + + public boolean isColIndexLessThan(int colIndex) { + return this.colIndex < colIndex; + } + +} diff --git a/src/main/java/cleancode/minesweeper/tobe/positoion/CellPositions.java b/src/main/java/cleancode/minesweeper/tobe/positoion/CellPositions.java new file mode 100644 index 000000000..c2c454be1 --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/positoion/CellPositions.java @@ -0,0 +1,57 @@ +package cleancode.minesweeper.tobe.positoion; + +import cleancode.minesweeper.tobe.cell.Cell; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class CellPositions { + private final List positions; + + private CellPositions(List positions) { + this.positions = positions; + } + + public static CellPositions of(List positions) { + return new CellPositions(positions); + } + + + public static CellPositions from(Cell[][] board) { + List cellPositions = new ArrayList<>(); + + for (int row = 0; row < board.length; row++) { + for (int col = 0; col < board[0].length; col++) { + CellPosition cellPosition = CellPosition.of(row, col); + cellPositions.add(cellPosition); + } + } + + return of(cellPositions); + } + + public List extractRandomPositions(int count) { + List cellPositions = new ArrayList<>(positions); + + Collections.shuffle(cellPositions); + return cellPositions.subList(0, count); + } + + public List subtract(List positionListToSubtract) { + List cellPositions = new ArrayList<>(positions); + CellPositions positionsToSubtract = CellPositions.of(positionListToSubtract); + + return cellPositions.stream() + .filter(positionsToSubtract::doesNotContain) + .toList(); + } + + private boolean doesNotContain(CellPosition position) { + return !positions.contains(position); + } + + public List getPositions() { + return new ArrayList<>(positions); + } +} diff --git a/src/main/java/cleancode/minesweeper/tobe/positoion/RelativePosition.java b/src/main/java/cleancode/minesweeper/tobe/positoion/RelativePosition.java new file mode 100644 index 000000000..93831a35d --- /dev/null +++ b/src/main/java/cleancode/minesweeper/tobe/positoion/RelativePosition.java @@ -0,0 +1,52 @@ +package cleancode.minesweeper.tobe.positoion; + +import java.util.List; +import java.util.Objects; + +public class RelativePosition { + + public static final List SURROUNDED_POSITIONS = List.of( + RelativePosition.of(-1, -1), + RelativePosition.of(-1, 0), + RelativePosition.of(-1, 1), + RelativePosition.of(0, -1), + RelativePosition.of(0, 1), + RelativePosition.of(1, -1), + RelativePosition.of(1, 0), + RelativePosition.of(1, 1) + ); + + private final int deltaRow; + private final int deltaCol; + + private RelativePosition(int deltaRow, int deltaCol) { + this.deltaRow = deltaRow; + this.deltaCol = deltaCol; + } + + public static RelativePosition of(int deltaRow, int deltaCol) { + return new RelativePosition(deltaRow, deltaCol); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RelativePosition that = (RelativePosition) o; + return deltaRow == that.deltaRow && deltaCol == that.deltaCol; + } + + @Override + public int hashCode() { + return Objects.hash(deltaRow, deltaCol); + } + + public int getDeltaRow() { + return deltaRow; + } + + public int getDeltaCol() { + return deltaCol; + } + +} diff --git a/src/main/java/cleancode/studycafe/tobe/StudyCafeApplication.java b/src/main/java/cleancode/studycafe/tobe/StudyCafeApplication.java deleted file mode 100644 index a60afb3f3..000000000 --- a/src/main/java/cleancode/studycafe/tobe/StudyCafeApplication.java +++ /dev/null @@ -1,10 +0,0 @@ -package cleancode.studycafe.tobe; - -public class StudyCafeApplication { - - public static void main(String[] args) { - StudyCafePassMachine studyCafePassMachine = new StudyCafePassMachine(); - studyCafePassMachine.run(); - } - -} diff --git a/src/main/java/cleancode/studycafe/tobe/StudyCafePassMachine.java b/src/main/java/cleancode/studycafe/tobe/StudyCafePassMachine.java deleted file mode 100644 index 3d34d9eef..000000000 --- a/src/main/java/cleancode/studycafe/tobe/StudyCafePassMachine.java +++ /dev/null @@ -1,81 +0,0 @@ -package cleancode.studycafe.tobe; - -import cleancode.studycafe.tobe.exception.AppException; -import cleancode.studycafe.tobe.io.InputHandler; -import cleancode.studycafe.tobe.io.OutputHandler; -import cleancode.studycafe.tobe.io.StudyCafeFileHandler; -import cleancode.studycafe.tobe.model.StudyCafeLockerPass; -import cleancode.studycafe.tobe.model.StudyCafePass; -import cleancode.studycafe.tobe.model.StudyCafePassType; - -import java.util.List; - -public class StudyCafePassMachine { - - private final InputHandler inputHandler = new InputHandler(); - private final OutputHandler outputHandler = new OutputHandler(); - - public void run() { - try { - outputHandler.showWelcomeMessage(); - outputHandler.showAnnouncement(); - - outputHandler.askPassTypeSelection(); - StudyCafePassType studyCafePassType = inputHandler.getPassTypeSelectingUserAction(); - - if (studyCafePassType == StudyCafePassType.HOURLY) { - StudyCafeFileHandler studyCafeFileHandler = new StudyCafeFileHandler(); - List studyCafePasses = studyCafeFileHandler.readStudyCafePasses(); - List hourlyPasses = studyCafePasses.stream() - .filter(studyCafePass -> studyCafePass.getPassType() == StudyCafePassType.HOURLY) - .toList(); - outputHandler.showPassListForSelection(hourlyPasses); - StudyCafePass selectedPass = inputHandler.getSelectPass(hourlyPasses); - outputHandler.showPassOrderSummary(selectedPass, null); - } else if (studyCafePassType == StudyCafePassType.WEEKLY) { - StudyCafeFileHandler studyCafeFileHandler = new StudyCafeFileHandler(); - List studyCafePasses = studyCafeFileHandler.readStudyCafePasses(); - List weeklyPasses = studyCafePasses.stream() - .filter(studyCafePass -> studyCafePass.getPassType() == StudyCafePassType.WEEKLY) - .toList(); - outputHandler.showPassListForSelection(weeklyPasses); - StudyCafePass selectedPass = inputHandler.getSelectPass(weeklyPasses); - outputHandler.showPassOrderSummary(selectedPass, null); - } else if (studyCafePassType == StudyCafePassType.FIXED) { - StudyCafeFileHandler studyCafeFileHandler = new StudyCafeFileHandler(); - List studyCafePasses = studyCafeFileHandler.readStudyCafePasses(); - List fixedPasses = studyCafePasses.stream() - .filter(studyCafePass -> studyCafePass.getPassType() == StudyCafePassType.FIXED) - .toList(); - outputHandler.showPassListForSelection(fixedPasses); - StudyCafePass selectedPass = inputHandler.getSelectPass(fixedPasses); - - List lockerPasses = studyCafeFileHandler.readLockerPasses(); - StudyCafeLockerPass lockerPass = lockerPasses.stream() - .filter(option -> - option.getPassType() == selectedPass.getPassType() - && option.getDuration() == selectedPass.getDuration() - ) - .findFirst() - .orElse(null); - - boolean lockerSelection = false; - if (lockerPass != null) { - outputHandler.askLockerPass(lockerPass); - lockerSelection = inputHandler.getLockerSelection(); - } - - if (lockerSelection) { - outputHandler.showPassOrderSummary(selectedPass, lockerPass); - } else { - outputHandler.showPassOrderSummary(selectedPass, null); - } - } - } catch (AppException e) { - outputHandler.showSimpleMessage(e.getMessage()); - } catch (Exception e) { - outputHandler.showSimpleMessage("알 수 없는 오류가 발생했습니다."); - } - } - -} diff --git a/src/main/java/cleancode/studycafe/tobe/io/InputHandler.java b/src/main/java/cleancode/studycafe/tobe/io/InputHandler.java deleted file mode 100644 index 2f1ceb64d..000000000 --- a/src/main/java/cleancode/studycafe/tobe/io/InputHandler.java +++ /dev/null @@ -1,40 +0,0 @@ -package cleancode.studycafe.tobe.io; - -import cleancode.studycafe.tobe.exception.AppException; -import cleancode.studycafe.tobe.model.StudyCafePass; -import cleancode.studycafe.tobe.model.StudyCafePassType; - -import java.util.List; -import java.util.Scanner; - -public class InputHandler { - - private static final Scanner SCANNER = new Scanner(System.in); - - public StudyCafePassType getPassTypeSelectingUserAction() { - String userInput = SCANNER.nextLine(); - - if ("1".equals(userInput)) { - return StudyCafePassType.HOURLY; - } - if ("2".equals(userInput)) { - return StudyCafePassType.WEEKLY; - } - if ("3".equals(userInput)) { - return StudyCafePassType.FIXED; - } - throw new AppException("잘못된 입력입니다."); - } - - public StudyCafePass getSelectPass(List passes) { - String userInput = SCANNER.nextLine(); - int selectedIndex = Integer.parseInt(userInput) - 1; - return passes.get(selectedIndex); - } - - public boolean getLockerSelection() { - String userInput = SCANNER.nextLine(); - return "1".equals(userInput); - } - -} diff --git a/src/main/java/cleancode/studycafe/tobe/io/OutputHandler.java b/src/main/java/cleancode/studycafe/tobe/io/OutputHandler.java deleted file mode 100644 index d8e0181a4..000000000 --- a/src/main/java/cleancode/studycafe/tobe/io/OutputHandler.java +++ /dev/null @@ -1,68 +0,0 @@ -package cleancode.studycafe.tobe.io; - -import cleancode.studycafe.tobe.model.StudyCafeLockerPass; -import cleancode.studycafe.tobe.model.StudyCafePass; - -import java.util.List; - -public class OutputHandler { - - public void showWelcomeMessage() { - System.out.println("*** 프리미엄 스터디카페 ***"); - } - - public void showAnnouncement() { - System.out.println("* 사물함은 고정석 선택 시 이용 가능합니다. (추가 결제)"); - System.out.println("* !오픈 이벤트! 2주권 이상 결제 시 10% 할인, 12주권 결제 시 15% 할인! (결제 시 적용)"); - System.out.println(); - } - - public void askPassTypeSelection() { - System.out.println("사용하실 이용권을 선택해 주세요."); - System.out.println("1. 시간 이용권(자유석) | 2. 주단위 이용권(자유석) | 3. 1인 고정석"); - } - - public void showPassListForSelection(List passes) { - System.out.println(); - System.out.println("이용권 목록"); - for (int index = 0; index < passes.size(); index++) { - StudyCafePass pass = passes.get(index); - System.out.println(String.format("%s. ", index + 1) + pass.display()); - } - } - - public void askLockerPass(StudyCafeLockerPass lockerPass) { - System.out.println(); - String askMessage = String.format( - "사물함을 이용하시겠습니까? (%s)", - lockerPass.display() - ); - - System.out.println(askMessage); - System.out.println("1. 예 | 2. 아니오"); - } - - public void showPassOrderSummary(StudyCafePass selectedPass, StudyCafeLockerPass lockerPass) { - System.out.println(); - System.out.println("이용 내역"); - System.out.println("이용권: " + selectedPass.display()); - if (lockerPass != null) { - System.out.println("사물함: " + lockerPass.display()); - } - - double discountRate = selectedPass.getDiscountRate(); - int discountPrice = (int) (selectedPass.getPrice() * discountRate); - if (discountPrice > 0) { - System.out.println("이벤트 할인 금액: " + discountPrice + "원"); - } - - int totalPrice = selectedPass.getPrice() - discountPrice + (lockerPass != null ? lockerPass.getPrice() : 0); - System.out.println("총 결제 금액: " + totalPrice + "원"); - System.out.println(); - } - - public void showSimpleMessage(String message) { - System.out.println(message); - } - -} diff --git a/src/main/java/cleancode/studycafe/tobe/model/StudyCafePassType.java b/src/main/java/cleancode/studycafe/tobe/model/StudyCafePassType.java deleted file mode 100644 index bac959b4f..000000000 --- a/src/main/java/cleancode/studycafe/tobe/model/StudyCafePassType.java +++ /dev/null @@ -1,15 +0,0 @@ -package cleancode.studycafe.tobe.model; - -public enum StudyCafePassType { - - HOURLY("시간 단위 이용권"), - WEEKLY("주 단위 이용권"), - FIXED("1인 고정석"); - - private final String description; - - StudyCafePassType(String description) { - this.description = description; - } - -} diff --git a/src/main/java/cleancode/studycafe/tobe2/StudyCafeApplication.java b/src/main/java/cleancode/studycafe/tobe2/StudyCafeApplication.java new file mode 100644 index 000000000..769dfb813 --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/StudyCafeApplication.java @@ -0,0 +1,18 @@ +package cleancode.studycafe.tobe2; + +import cleancode.studycafe.tobe2.io.ConsoleInputHandler; +import cleancode.studycafe.tobe2.io.ConsoleOutputHandler; +import cleancode.studycafe.tobe2.model.LocalStudyCafeProcessorFactory; + +public class StudyCafeApplication { + + public static void main(String[] args) { + StudyCafePassMachine studyCafePassMachine = new StudyCafePassMachine( + new ConsoleInputHandler(), + new ConsoleOutputHandler(), + new LocalStudyCafeProcessorFactory() + ); + studyCafePassMachine.run(); + } + +} diff --git a/src/main/java/cleancode/studycafe/tobe2/StudyCafePassMachine.java b/src/main/java/cleancode/studycafe/tobe2/StudyCafePassMachine.java new file mode 100644 index 000000000..a1ae228b0 --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/StudyCafePassMachine.java @@ -0,0 +1,43 @@ +package cleancode.studycafe.tobe2; + +import cleancode.studycafe.tobe2.exception.AppException; +import cleancode.studycafe.tobe2.io.InputHandler; +import cleancode.studycafe.tobe2.io.OutputHandler; +import cleancode.studycafe.tobe2.io.StudyCafeFileHandler; +import cleancode.studycafe.tobe2.model.StudyCafeLockerPass; +import cleancode.studycafe.tobe2.model.StudyCafePass; +import cleancode.studycafe.tobe2.model.StudyCafePassType; +import cleancode.studycafe.tobe2.model.StudyCafeProcessor; +import cleancode.studycafe.tobe2.model.StudyCafeProcessorFactory; + +import java.util.List; + +public class StudyCafePassMachine { + + private final InputHandler inputHandler; + private final OutputHandler outputHandler; + private final StudyCafeProcessorFactory studyCafeProcessorFactory; + + public StudyCafePassMachine(InputHandler inputHandler, OutputHandler outputHandler, StudyCafeProcessorFactory studyCafeProcessorFactory) { + this.inputHandler = inputHandler; + this.outputHandler = outputHandler; + this.studyCafeProcessorFactory = studyCafeProcessorFactory; + } + + public void run() { + try { + outputHandler.showWelcomeMessage(); + outputHandler.showAnnouncement(); + + outputHandler.askPassTypeSelection(); + StudyCafePassType studyCafePassType = inputHandler.getPassTypeSelectingUserAction(); + StudyCafeProcessor studyCafeProcessor = studyCafeProcessorFactory.createProcessor(studyCafePassType); + studyCafeProcessor.process(); + } catch (AppException e) { + outputHandler.showSimpleMessage(e.getMessage()); + } catch (Exception e) { + outputHandler.showSimpleMessage("알 수 없는 오류가 발생했습니다."); + } + } + +} diff --git a/src/main/java/cleancode/studycafe/tobe/exception/AppException.java b/src/main/java/cleancode/studycafe/tobe2/exception/AppException.java similarity index 74% rename from src/main/java/cleancode/studycafe/tobe/exception/AppException.java rename to src/main/java/cleancode/studycafe/tobe2/exception/AppException.java index 4920bcba4..2d282a453 100644 --- a/src/main/java/cleancode/studycafe/tobe/exception/AppException.java +++ b/src/main/java/cleancode/studycafe/tobe2/exception/AppException.java @@ -1,4 +1,4 @@ -package cleancode.studycafe.tobe.exception; +package cleancode.studycafe.tobe2.exception; public class AppException extends RuntimeException { diff --git a/src/main/java/cleancode/studycafe/tobe2/io/ConsoleInputHandler.java b/src/main/java/cleancode/studycafe/tobe2/io/ConsoleInputHandler.java new file mode 100644 index 000000000..83739e1cb --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/io/ConsoleInputHandler.java @@ -0,0 +1,28 @@ +package cleancode.studycafe.tobe2.io; + +import cleancode.minesweeper.tobe.io.InputHandler; +import cleancode.studycafe.tobe2.model.StudyCafePass; +import cleancode.studycafe.tobe2.model.StudyCafePassType; +import cleancode.studycafe.tobe2.model.StudyCafePasses; + +import java.util.Scanner; + +public class ConsoleInputHandler implements InputHandler { + private static final Scanner SCANNER = new Scanner(System.in); + + public StudyCafePassType getPassTypeSelectingUserAction() { + String userInput = SCANNER.nextLine(); + return StudyCafePassType.findByUserAction(userInput); + } + + public StudyCafePass getSelectPass(StudyCafePasses studyCafePasses) { + String userInput = SCANNER.nextLine(); + int selectedIndex = Integer.parseInt(userInput) - 1; + return studyCafePasses.get(selectedIndex); + } + + public boolean getLockerSelection() { + String userInput = SCANNER.nextLine(); + return "1".equals(userInput); + } +} diff --git a/src/main/java/cleancode/studycafe/tobe2/io/ConsoleOutputHandler.java b/src/main/java/cleancode/studycafe/tobe2/io/ConsoleOutputHandler.java new file mode 100644 index 000000000..7c527ff3a --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/io/ConsoleOutputHandler.java @@ -0,0 +1,67 @@ +package cleancode.studycafe.tobe2.io; + +import cleancode.minesweeper.tobe.io.OutputHandler; +import cleancode.studycafe.tobe2.model.StudyCafeLockerPass; +import cleancode.studycafe.tobe2.model.StudyCafePass; +import cleancode.studycafe.tobe2.model.StudyCafePasses; + +public class ConsoleOutputHandler implements OutputHandler { + + public void showWelcomeMessage() { + System.out.println("*** 프리미엄 스터디카페 ***"); + } + + public void showAnnouncement() { + System.out.println("* 사물함은 고정석 선택 시 이용 가능합니다. (추가 결제)"); + System.out.println("* !오픈 이벤트! 2주권 이상 결제 시 10% 할인, 12주권 결제 시 15% 할인! (결제 시 적용)"); + System.out.println(); + } + + public void askPassTypeSelection() { + System.out.println("사용하실 이용권을 선택해 주세요."); + System.out.println("1. 시간 이용권(자유석) | 2. 주단위 이용권(자유석) | 3. 1인 고정석"); + } + + public void showPassListForSelection(StudyCafePasses passes) { + System.out.println(); + System.out.println("이용권 목록"); + for (int index = 0; index < passes.getSize(); index++) { + StudyCafePass pass = passes.get(index); + System.out.println(String.format("%s. ", index + 1) + pass.display()); + } + } + + public void askLockerPass(StudyCafeLockerPass lockerPass) { + System.out.println(); + String askMessage = String.format( + "사물함을 이용하시겠습니까? (%s)", + lockerPass.display() + ); + + System.out.println(askMessage); + System.out.println("1. 예 | 2. 아니오"); + } + + public void showPassOrderSummary(StudyCafePass selectedPass, StudyCafeLockerPass lockerPass) { + System.out.println(); + System.out.println("이용 내역"); + System.out.println("이용권: " + selectedPass.display()); + if (lockerPass != null) { + System.out.println("사물함: " + lockerPass.display()); + } + + double discountRate = selectedPass.getDiscountRate(); + int discountPrice = (int) (selectedPass.getPrice() * discountRate); + if (discountPrice > 0) { + System.out.println("이벤트 할인 금액: " + discountPrice + "원"); + } + + int totalPrice = selectedPass.getPrice() - discountPrice + (lockerPass != null ? lockerPass.getPrice() : 0); + System.out.println("총 결제 금액: " + totalPrice + "원"); + System.out.println(); + } + + public void showSimpleMessage(String message) { + System.out.println(message); + } +} diff --git a/src/main/java/cleancode/studycafe/tobe2/io/InputHandler.java b/src/main/java/cleancode/studycafe/tobe2/io/InputHandler.java new file mode 100644 index 000000000..d1b13146c --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/io/InputHandler.java @@ -0,0 +1,13 @@ +package cleancode.studycafe.tobe2.io; + +import cleancode.studycafe.tobe2.model.StudyCafePass; +import cleancode.studycafe.tobe2.model.StudyCafePassType; +import cleancode.studycafe.tobe2.model.StudyCafePasses; + +public interface InputHandler { + StudyCafePassType getPassTypeSelectingUserAction(); + + StudyCafePass getSelectPass(StudyCafePasses studyCafePasses); + + boolean getLockerSelection(); +} diff --git a/src/main/java/cleancode/studycafe/tobe2/io/OutputHandler.java b/src/main/java/cleancode/studycafe/tobe2/io/OutputHandler.java new file mode 100644 index 000000000..b494d032d --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/io/OutputHandler.java @@ -0,0 +1,23 @@ +package cleancode.studycafe.tobe2.io; + +import cleancode.studycafe.tobe2.model.StudyCafeLockerPass; +import cleancode.studycafe.tobe2.model.StudyCafePass; +import cleancode.studycafe.tobe2.model.StudyCafePasses; + +import java.util.List; + +public interface OutputHandler { + void showWelcomeMessage(); + + void showAnnouncement(); + + void askPassTypeSelection(); + + void showPassListForSelection(StudyCafePasses studyCafePasses); + + void askLockerPass(StudyCafeLockerPass lockerPass); + + void showPassOrderSummary(StudyCafePass selectedPass, StudyCafeLockerPass lockerPass); + + void showSimpleMessage(String message); +} diff --git a/src/main/java/cleancode/studycafe/tobe/io/StudyCafeFileHandler.java b/src/main/java/cleancode/studycafe/tobe2/io/StudyCafeFileHandler.java similarity index 90% rename from src/main/java/cleancode/studycafe/tobe/io/StudyCafeFileHandler.java rename to src/main/java/cleancode/studycafe/tobe2/io/StudyCafeFileHandler.java index 920a27e59..48cfd02e5 100644 --- a/src/main/java/cleancode/studycafe/tobe/io/StudyCafeFileHandler.java +++ b/src/main/java/cleancode/studycafe/tobe2/io/StudyCafeFileHandler.java @@ -1,8 +1,8 @@ -package cleancode.studycafe.tobe.io; +package cleancode.studycafe.tobe2.io; -import cleancode.studycafe.tobe.model.StudyCafeLockerPass; -import cleancode.studycafe.tobe.model.StudyCafePass; -import cleancode.studycafe.tobe.model.StudyCafePassType; +import cleancode.studycafe.tobe2.model.StudyCafeLockerPass; +import cleancode.studycafe.tobe2.model.StudyCafePass; +import cleancode.studycafe.tobe2.model.StudyCafePassType; import java.io.IOException; import java.nio.file.Files; diff --git a/src/main/java/cleancode/studycafe/tobe2/io/StudyCafeLockerPassReader.java b/src/main/java/cleancode/studycafe/tobe2/io/StudyCafeLockerPassReader.java new file mode 100644 index 000000000..d1b927656 --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/io/StudyCafeLockerPassReader.java @@ -0,0 +1,7 @@ +package cleancode.studycafe.tobe2.io; + +import cleancode.studycafe.tobe2.model.StudyCafeLockerPasses; + +public interface StudyCafeLockerPassReader { + StudyCafeLockerPasses readLockerPasses(); +} diff --git a/src/main/java/cleancode/studycafe/tobe2/io/StudyCafePassReader.java b/src/main/java/cleancode/studycafe/tobe2/io/StudyCafePassReader.java new file mode 100644 index 000000000..bb606485b --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/io/StudyCafePassReader.java @@ -0,0 +1,7 @@ +package cleancode.studycafe.tobe2.io; + +import cleancode.studycafe.tobe2.model.StudyCafePasses; + +public interface StudyCafePassReader { + StudyCafePasses readStudyCafePasses(); +} diff --git a/src/main/java/cleancode/studycafe/tobe2/io/file/StudyCafeLockerPassFileReader.java b/src/main/java/cleancode/studycafe/tobe2/io/file/StudyCafeLockerPassFileReader.java new file mode 100644 index 000000000..aba8e0d1b --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/io/file/StudyCafeLockerPassFileReader.java @@ -0,0 +1,35 @@ +package cleancode.studycafe.tobe2.io.file; + +import cleancode.studycafe.tobe2.io.StudyCafeLockerPassReader; +import cleancode.studycafe.tobe2.model.StudyCafeLockerPass; +import cleancode.studycafe.tobe2.model.StudyCafeLockerPasses; +import cleancode.studycafe.tobe2.model.StudyCafePassType; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +public class StudyCafeLockerPassFileReader implements StudyCafeLockerPassReader { + @Override + public StudyCafeLockerPasses readLockerPasses() { + try { + List lines = Files.readAllLines(Paths.get("src/main/resources/cleancode/studycafe/locker.csv")); + List lockerPasses = new ArrayList<>(); + for (String line : lines) { + String[] values = line.split(","); + StudyCafePassType studyCafePassType = StudyCafePassType.valueOf(values[0]); + int duration = Integer.parseInt(values[1]); + int price = Integer.parseInt(values[2]); + + StudyCafeLockerPass lockerPass = StudyCafeLockerPass.of(studyCafePassType, duration, price); + lockerPasses.add(lockerPass); + } + + return StudyCafeLockerPasses.of(lockerPasses); + } catch (IOException e) { + throw new RuntimeException("파일을 읽는데 실패했습니다.", e); + } + } +} diff --git a/src/main/java/cleancode/studycafe/tobe2/io/file/StudyCafePassFileReader.java b/src/main/java/cleancode/studycafe/tobe2/io/file/StudyCafePassFileReader.java new file mode 100644 index 000000000..eb4482abc --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/io/file/StudyCafePassFileReader.java @@ -0,0 +1,36 @@ +package cleancode.studycafe.tobe2.io.file; + +import cleancode.studycafe.tobe2.io.StudyCafePassReader; +import cleancode.studycafe.tobe2.model.StudyCafePass; +import cleancode.studycafe.tobe2.model.StudyCafePassType; +import cleancode.studycafe.tobe2.model.StudyCafePasses; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +public class StudyCafePassFileReader implements StudyCafePassReader { + @Override + public StudyCafePasses readStudyCafePasses() { + try { + List lines = Files.readAllLines(Paths.get("src/main/resources/cleancode/studycafe/pass-list.csv")); + List studyCafePasses = new ArrayList<>(); + for (String line : lines) { + String[] values = line.split(","); + StudyCafePassType studyCafePassType = StudyCafePassType.valueOf(values[0]); + int duration = Integer.parseInt(values[1]); + int price = Integer.parseInt(values[2]); + double discountRate = Double.parseDouble(values[3]); + + StudyCafePass studyCafePass = StudyCafePass.of(studyCafePassType, duration, price, discountRate); + studyCafePasses.add(studyCafePass); + } + + return StudyCafePasses.of(studyCafePasses); + } catch (IOException e) { + throw new RuntimeException("파일을 읽는데 실패했습니다.", e); + } + } +} diff --git a/src/main/java/cleancode/studycafe/tobe2/model/FixedStudyCafeProcessor.java b/src/main/java/cleancode/studycafe/tobe2/model/FixedStudyCafeProcessor.java new file mode 100644 index 000000000..3dae8b497 --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/model/FixedStudyCafeProcessor.java @@ -0,0 +1,53 @@ +package cleancode.studycafe.tobe2.model; + +import cleancode.studycafe.tobe2.io.InputHandler; +import cleancode.studycafe.tobe2.io.OutputHandler; +import cleancode.studycafe.tobe2.io.StudyCafeLockerPassReader; +import cleancode.studycafe.tobe2.io.StudyCafePassReader; + +public class FixedStudyCafeProcessor implements StudyCafeProcessor { + private final StudyCafePassReader studyCafePassReader; + private final StudyCafeLockerPassReader studyCafeLockerPassReader; + private final InputHandler inputHandler; + private final OutputHandler outputHandler; + + public FixedStudyCafeProcessor(StudyCafePassReader studyCafePassReader, StudyCafeLockerPassReader studyCafeLockerPassReader, InputHandler inputHandler, OutputHandler outputHandler) { + this.studyCafePassReader = studyCafePassReader; + this.studyCafeLockerPassReader = studyCafeLockerPassReader; + this.inputHandler = inputHandler; + this.outputHandler = outputHandler; + } + + private static boolean isExists(StudyCafeLockerPass lockerPass) { + return lockerPass != null; + } + + @Override + public boolean satisfiedBy(StudyCafePassType passType) { + return passType == StudyCafePassType.FIXED; + } + + @Override + public void process() { + StudyCafePasses studyCafePasses = studyCafePassReader.readStudyCafePasses(); + StudyCafePasses fixedStudyCafePasses = studyCafePasses.findByType(StudyCafePassType.FIXED); + + outputHandler.showPassListForSelection(fixedStudyCafePasses); + StudyCafePass selectedPass = inputHandler.getSelectPass(fixedStudyCafePasses); + + StudyCafeLockerPasses lockerPasses = studyCafeLockerPassReader.readLockerPasses(); + StudyCafeLockerPass lockerPass = lockerPasses.getLockerPassByTypeAndDuration(selectedPass); + + boolean lockerSelection = false; + if (isExists(lockerPass)) { + outputHandler.askLockerPass(lockerPass); + lockerSelection = inputHandler.getLockerSelection(); + } + + if (lockerSelection) { + outputHandler.showPassOrderSummary(selectedPass, lockerPass); + } else { + outputHandler.showPassOrderSummary(selectedPass, null); + } + } +} diff --git a/src/main/java/cleancode/studycafe/tobe2/model/HourlyStudyCafeProcessor.java b/src/main/java/cleancode/studycafe/tobe2/model/HourlyStudyCafeProcessor.java new file mode 100644 index 000000000..134be9c25 --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/model/HourlyStudyCafeProcessor.java @@ -0,0 +1,31 @@ +package cleancode.studycafe.tobe2.model; + +import cleancode.studycafe.tobe2.io.InputHandler; +import cleancode.studycafe.tobe2.io.OutputHandler; +import cleancode.studycafe.tobe2.io.StudyCafePassReader; + +public class HourlyStudyCafeProcessor implements StudyCafeProcessor{ + private final StudyCafePassReader studyCafePassReader; + private final InputHandler inputHandler; + private final OutputHandler outputHandler; + + public HourlyStudyCafeProcessor(StudyCafePassReader studyCafePassReader, InputHandler inputHandler, OutputHandler outputHandler) { + this.studyCafePassReader = studyCafePassReader; + this.inputHandler = inputHandler; + this.outputHandler = outputHandler; + } + + @Override + public boolean satisfiedBy(StudyCafePassType passType) { + return passType == StudyCafePassType.HOURLY; + } + + @Override + public void process() { + StudyCafePasses studyCafePasses = studyCafePassReader.readStudyCafePasses(); + StudyCafePasses hourlyPasses = studyCafePasses.findByType(StudyCafePassType.HOURLY); + outputHandler.showPassListForSelection(hourlyPasses); + StudyCafePass selectedPass = inputHandler.getSelectPass(hourlyPasses); + outputHandler.showPassOrderSummary(selectedPass, null); + } +} diff --git a/src/main/java/cleancode/studycafe/tobe2/model/LocalStudyCafeProcessorFactory.java b/src/main/java/cleancode/studycafe/tobe2/model/LocalStudyCafeProcessorFactory.java new file mode 100644 index 000000000..3d89aaec0 --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/model/LocalStudyCafeProcessorFactory.java @@ -0,0 +1,38 @@ +package cleancode.studycafe.tobe2.model; + +import cleancode.minesweeper.tobe.io.OutputHandler; +import cleancode.studycafe.tobe2.exception.AppException; +import cleancode.studycafe.tobe2.io.ConsoleInputHandler; +import cleancode.studycafe.tobe2.io.ConsoleOutputHandler; +import cleancode.studycafe.tobe2.io.InputHandler; +import cleancode.studycafe.tobe2.io.StudyCafeLockerPassReader; +import cleancode.studycafe.tobe2.io.StudyCafePassReader; +import cleancode.studycafe.tobe2.io.file.StudyCafeLockerPassFileReader; +import cleancode.studycafe.tobe2.io.file.StudyCafePassFileReader; + +import java.util.List; + +public class LocalStudyCafeProcessorFactory implements StudyCafeProcessorFactory{ + private static final StudyCafePassReader studyCafePassReader = new StudyCafePassFileReader(); + private static final StudyCafeLockerPassReader studyCafeLockerPassReader = new StudyCafeLockerPassFileReader(); + private static final InputHandler inputHandler = new ConsoleInputHandler(); + private static final OutputHandler outputHandler = new ConsoleOutputHandler(); + + private static final List processors = List.of( + new HourlyStudyCafeProcessor(studyCafePassReader, inputHandler, outputHandler), + new WeeklyStudyCafeProcessor(studyCafePassReader, inputHandler, outputHandler), + new FixedStudyCafeProcessor(studyCafePassReader, studyCafeLockerPassReader, inputHandler, outputHandler) + ); + + public LocalStudyCafeProcessorFactory() { + + } + + @Override + public StudyCafeProcessor createProcessor(StudyCafePassType passType) { + return processors.stream() + .filter(processor -> processor.satisfiedBy(passType)) + .findFirst() + .orElseThrow(() -> new AppException("해당 이용권을 처리할 수 있는 프로세서가 없습니다.")); + } +} diff --git a/src/main/java/cleancode/studycafe/tobe/model/StudyCafeLockerPass.java b/src/main/java/cleancode/studycafe/tobe2/model/StudyCafeLockerPass.java similarity index 96% rename from src/main/java/cleancode/studycafe/tobe/model/StudyCafeLockerPass.java rename to src/main/java/cleancode/studycafe/tobe2/model/StudyCafeLockerPass.java index 6512ec0a8..0a8b6fafe 100644 --- a/src/main/java/cleancode/studycafe/tobe/model/StudyCafeLockerPass.java +++ b/src/main/java/cleancode/studycafe/tobe2/model/StudyCafeLockerPass.java @@ -1,4 +1,4 @@ -package cleancode.studycafe.tobe.model; +package cleancode.studycafe.tobe2.model; public class StudyCafeLockerPass { diff --git a/src/main/java/cleancode/studycafe/tobe2/model/StudyCafeLockerPasses.java b/src/main/java/cleancode/studycafe/tobe2/model/StudyCafeLockerPasses.java new file mode 100644 index 000000000..45df7490d --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/model/StudyCafeLockerPasses.java @@ -0,0 +1,25 @@ +package cleancode.studycafe.tobe2.model; + +import java.util.List; + +public class StudyCafeLockerPasses { + private final List studyCafeLockerPasses; + + private StudyCafeLockerPasses(List studyCafeLockerPasses) { + this.studyCafeLockerPasses = studyCafeLockerPasses; + } + + public static StudyCafeLockerPasses of(List studyCafeLockerPasses) { + return new StudyCafeLockerPasses(studyCafeLockerPasses); + } + + public StudyCafeLockerPass getLockerPassByTypeAndDuration(StudyCafePass studyCafePass) { + return studyCafeLockerPasses.stream() + .filter(option -> + option.getPassType() == studyCafePass.getPassType() + && option.getDuration() == studyCafePass.getDuration() + ) + .findFirst() + .orElse(null); + } +} diff --git a/src/main/java/cleancode/studycafe/tobe/model/StudyCafePass.java b/src/main/java/cleancode/studycafe/tobe2/model/StudyCafePass.java similarity index 97% rename from src/main/java/cleancode/studycafe/tobe/model/StudyCafePass.java rename to src/main/java/cleancode/studycafe/tobe2/model/StudyCafePass.java index 0749f41b8..01cc19f48 100644 --- a/src/main/java/cleancode/studycafe/tobe/model/StudyCafePass.java +++ b/src/main/java/cleancode/studycafe/tobe2/model/StudyCafePass.java @@ -1,4 +1,4 @@ -package cleancode.studycafe.tobe.model; +package cleancode.studycafe.tobe2.model; public class StudyCafePass { diff --git a/src/main/java/cleancode/studycafe/tobe2/model/StudyCafePassType.java b/src/main/java/cleancode/studycafe/tobe2/model/StudyCafePassType.java new file mode 100644 index 000000000..501632389 --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/model/StudyCafePassType.java @@ -0,0 +1,31 @@ +package cleancode.studycafe.tobe2.model; + +import cleancode.studycafe.tobe2.exception.AppException; + +public enum StudyCafePassType { + + + HOURLY("1", "시간 단위 이용권"), + WEEKLY("2", "주 단위 이용권"), + FIXED("3", "1인 고정석"); + + + private final String userAction; + private final String description; + + + StudyCafePassType(String userAction, String description) { + this.userAction = userAction; + this.description = description; + } + + + public static StudyCafePassType findByUserAction(String userAction) { + for (StudyCafePassType passType : values()) { + if (passType.userAction.equals(userAction)) { + return passType; + } + } + throw new AppException("잘못된 입력입니다."); + } +} diff --git a/src/main/java/cleancode/studycafe/tobe2/model/StudyCafePasses.java b/src/main/java/cleancode/studycafe/tobe2/model/StudyCafePasses.java new file mode 100644 index 000000000..93ee08a4a --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/model/StudyCafePasses.java @@ -0,0 +1,30 @@ +package cleancode.studycafe.tobe2.model; + +import java.util.List; + +public class StudyCafePasses { + + private final List studyCafePasses; + + private StudyCafePasses(List studyCafePasses) { + this.studyCafePasses = studyCafePasses; + } + + public static StudyCafePasses of(List studyCafePasses) { + return new StudyCafePasses(studyCafePasses); + } + + public StudyCafePasses findByType(StudyCafePassType passType) { + return StudyCafePasses.of(studyCafePasses.stream() + .filter(studyCafePass -> studyCafePass.getPassType() == passType) + .toList()); + } + + public int getSize() { + return studyCafePasses.size(); + } + + public StudyCafePass get(int index) { + return studyCafePasses.get(index); + } +} diff --git a/src/main/java/cleancode/studycafe/tobe2/model/StudyCafeProcessor.java b/src/main/java/cleancode/studycafe/tobe2/model/StudyCafeProcessor.java new file mode 100644 index 000000000..19dc5ee37 --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/model/StudyCafeProcessor.java @@ -0,0 +1,6 @@ +package cleancode.studycafe.tobe2.model; + +public interface StudyCafeProcessor { + boolean satisfiedBy(StudyCafePassType passType); + void process(); +} diff --git a/src/main/java/cleancode/studycafe/tobe2/model/StudyCafeProcessorFactory.java b/src/main/java/cleancode/studycafe/tobe2/model/StudyCafeProcessorFactory.java new file mode 100644 index 000000000..d9b5a6bc2 --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/model/StudyCafeProcessorFactory.java @@ -0,0 +1,5 @@ +package cleancode.studycafe.tobe2.model; + +public interface StudyCafeProcessorFactory { + +} diff --git a/src/main/java/cleancode/studycafe/tobe2/model/WeeklyStudyCafeProcessor.java b/src/main/java/cleancode/studycafe/tobe2/model/WeeklyStudyCafeProcessor.java new file mode 100644 index 000000000..6aeb1919f --- /dev/null +++ b/src/main/java/cleancode/studycafe/tobe2/model/WeeklyStudyCafeProcessor.java @@ -0,0 +1,31 @@ +package cleancode.studycafe.tobe2.model; + +import cleancode.studycafe.tobe2.io.InputHandler; +import cleancode.studycafe.tobe2.io.OutputHandler; +import cleancode.studycafe.tobe2.io.StudyCafePassReader; + +public class WeeklyStudyCafeProcessor implements StudyCafeProcessor{ + private final StudyCafePassReader studyCafePassReader; + private final InputHandler inputHandler; + private final OutputHandler outputHandler; + + public WeeklyStudyCafeProcessor(StudyCafePassReader studyCafePassReader, InputHandler inputHandler, OutputHandler outputHandler) { + this.studyCafePassReader = studyCafePassReader; + this.inputHandler = inputHandler; + this.outputHandler = outputHandler; + } + + @Override + public boolean satisfiedBy(StudyCafePassType passType) { + return passType == StudyCafePassType.WEEKLY; + } + + @Override + public void process() { + StudyCafePasses studyCafePasses = studyCafePassReader.readStudyCafePasses(); + StudyCafePasses weeklyPasses = studyCafePasses.findByType(StudyCafePassType.WEEKLY); + outputHandler.showPassListForSelection(weeklyPasses); + StudyCafePass selectedPass = inputHandler.getSelectPass(weeklyPasses); + outputHandler.showPassOrderSummary(selectedPass, null); + } +}