Skip to content
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2c7ac0b
bridgeInfo 생성
Oct 28, 2025
601989e
feat : application
Oct 30, 2025
45188f7
feat : controller
Oct 30, 2025
6bc5102
feat : Bridge
Oct 30, 2025
25114d9
feat : BridgeStep
Oct 30, 2025
2d98c95
feat : 사다리 하강 로직
Oct 30, 2025
4ae4ed7
feat : 플레이어 관리 클래스
Oct 30, 2025
e2f073b
feat : 도착지에 있는 보상 클래스
Oct 30, 2025
e46be8a
feat : 문자열 분리 메소드
Oct 30, 2025
c29c860
feat : view
Oct 30, 2025
5aeab91
refactor : 스트림 이용
Oct 30, 2025
d6ac423
refactor : 들여쓰기 적용 및 라인 정리
Oct 30, 2025
c90102f
feat : READ.ME 작성
Oct 30, 2025
53b8d01
feat : READ.ME 작성
Oct 30, 2025
00f8644
feat : 테스트 코드 작성
Oct 30, 2025
35bc4d1
refactor(controller) : 한 가지 목적을 가진 코드들 메소드로 분리
Nov 2, 2025
9eff889
refactor(controller) : isAll과 while문의 break문
Nov 2, 2025
4b32197
refactor(controller) : split 메소드 적용
Nov 2, 2025
1007500
refactor(controller) : split 메소드 적용2
Nov 2, 2025
e6710c5
refactor(outputView) : 메소드 이름 수정
Nov 2, 2025
af2e6a8
refactor(outputView) : printAll과 printSpecificPlayer 리팩토링
Nov 2, 2025
7ed8f0a
refactor(outputView) : Bridge와 BridgeRow로 리팩토링
Nov 2, 2025
04b80c8
refactor(outputView) : Bridge와 BridgeRow로 리팩토링2
Nov 2, 2025
4db07ab
refactor : Rewards 수정
Nov 2, 2025
4fe7fca
refactor : 종속성 수정
Nov 2, 2025
cf2700c
feat : LadderResult 와 테스트 코드
Nov 3, 2025
82afadc
refactor(controller) : height 선언 시점 수정
Nov 5, 2025
88d3fc3
refactor(OutputView,controller) : getWidthAndHeight 리팩토링
Nov 5, 2025
86db6b1
refactor(Bridge) : 정적 팩토리 메소드 적용
Nov 5, 2025
c60e922
refactor(Test) : 테스트 코드 클래스 별 분리
Nov 5, 2025
cee5e54
refactor(Test) : 람다식을 이용한 soft assertion 적용
Nov 5, 2025
3c3f2e2
refactor(Test) : 람다식을 이용한 soft assertion 적용
Nov 5, 2025
12dcf0d
refactor : 전략패턴을 이용한 다리 생성 로직 수정
Nov 5, 2025
3dac03c
refactor : formatter를 이용한 뷰 클래스 리팩토링
Nov 5, 2025
7b421a9
refactor : formatter를 이용한 controller 리팩토링
Nov 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# 자바 사다리 게임

## 주요 기능

* 쉼표로 구분하여 여러 플레이어 이름 입력
* 사다리 끝에 위치할 보상 입력
* 사다리 높이 설정
* 가로 라인이 무작위로 생성되는 사다리
* 최종 생성된 사다리 모양 출력
* 각 플레이어의 최종 결과(보상) 표시
* 전체 플레이어 또는 특정 플레이어의 결과 조회 기능

## 코드 설명
* **`Application.java`**: 프로그램의 시작점입니다. 전체 게임의 흐름을 제어합니다.

* **`controller` 패키지**:
* `Controller.java`: 사용자의 입력을 받아 Model을 업데이트하고, 결과를 View에 전달하는 등 게임의 핵심 로직을 관리합니다.

* **`model` 패키지**: 게임의 데이터와 비즈니스 로직을 담당합니다.
* `Player.java`: 게임에 참여하는 플레이어들의 정보를 관리합니다.
* `Rewards.java`: 사다리 게임의 결과로 주어지는 보상 정보를 관리합니다.
* `Bridge.java`: 전체 사다리의 구조를 나타냅니다. 가로 줄(`BridgeStep`)들의 리스트로 구성됩니다.
* `BridgeStep.java`: 사다리의 한 칸이 가로 줄을 가지고 있는지 여부(`EXIST` 또는 `EMPTY`)를 나타내는 Enum입니다.
* `LadderDescentService.java`: 플레이어가 사다리를 내려가는 과정을 처리하는 서비스 클래스입니다.
* `SplitMethod.java`: 사용자 입력을 쉼표 기준으로 분리하는 유틸리티 클래스입니다.

* **`view` 패키지**: 사용자 인터페이스(입력/출력)를 담당합니다.
* `InputView.java`: 사용자로부터 플레이어 이름, 보상, 사다리 높이 등의 입력을 받습니다.
* `OutputView.java`: 게임 진행 상황, 사다리 모양, 최종 결과 등을 콘솔에 출력합니다.
8 changes: 8 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Controller.Controller;

public class Application {
public static void main(String[] args) {
Controller controller = new Controller();
controller.startLadder();
}
}
78 changes: 78 additions & 0 deletions src/main/java/Controller/Controller.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package Controller;

import Model.Bridge;
import Model.Player;
import Model.Rewards;
import view.InputView;
import view.OutputView;

import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import Model.LadderDescentService;
import Model.LadderResult;

public class Controller {
InputView inputView = new InputView();
OutputView outputView = new OutputView();

public void startLadder() {
int height;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

height와 isAll이 가장 위에 있는데 먼저 선언한 이유가 있나요??

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

따로 의미를 두고 필드를 위에 선언 한 이유는 없습니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 보통 변수는 선언과 동시에 초기화하는게 좋아요


Player players = getPlayer();
Rewards rewards = getRewards();

outputView.askLadderHeight();
height = inputView.getWidthAndHeight();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맨위에 있는 height는 여기랑 합치면 좋아보여요~

Suggested change
height = inputView.getWidthAndHeight();
int height = inputView.getWidthAndHeight();

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수정하겠습니다!


outputView.printPlayersAndRewards(players.getPlayers());
Bridge bridge = new Bridge(height, players.getPlayersNumber() - 1);
outputView.printBridge(bridge);

outputView.printPlayersAndRewards(rewards.getRewards());

LadderDescentService descentService = new LadderDescentService(bridge);
LadderResult ladderResult = descentService.calculateAllResults(players.getPlayersNumber());

inputView.getScanner().nextLine();

while (true) {
outputView.askResults();
String string = inputView.getScanner().nextLine();

Comment on lines 41 to 52
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while문 밖에 inputView.getScanner().nextLine(); 코드가 있어서 확인해보니 outputView.askResults();가 출력되기도 전에 먼저 줄바꿈을 입력 받게 되는 케이스가 있네요

찾아보니 해결방법이 inputView getWidthAndHeight 내부 로직을 nextInt() 대신 nextLine()으로 String으로 값을 받아 int 형으로 변환을 해주면 while문 밖의 코드도 사라질 것 같아요

이게 대표적으로 사연 있는 코드인데, 최대한 사연 없는 코드로 만들어주시는게 좋습니다 😄
원래 코드에서는 없어야만하는 코드인데, 다른 방법으로 해결하지 못하는 내용이라면 먼저 코멘트로 알려주는게 좋아요

if (string.trim().equals("all")) {
outputView.printAllResults(players, rewards, ladderResult);
break;
}
outputView.printSpecificResults(players, rewards, ladderResult, string);
}
}

private Rewards getRewards() {
outputView.askRewards();
String rewardsInputs = inputView.getScanner().nextLine();
return new Rewards(split(rewardsInputs));
}

private Player getPlayer() {
outputView.askPlayers();
String playerInputs = inputView.getScanner().nextLine();
return new Player(split(playerInputs));
}

private static List<String> split(String input) {

final Pattern DELIMITER = Pattern.compile(",");

if (input == null || input.isEmpty()) {
return Collections.emptyList();
}

return DELIMITER.splitAsStream(input)
.map(String::trim) // 양쪽 공백 제거
.filter(name -> !name.isEmpty()) // 빈 값 제거 (예: "neo,,brie")
.collect(Collectors.toList());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헙 저는 SplitMethod.java는 그대로 두고 Controller에서 SplitMehtod.split(...)을 사용하는걸 생각했는데 Controller 안에 메서드를 넣으셨군요. 요것도 괜찮아보여요

}
32 changes: 32 additions & 0 deletions src/main/java/Model/Bridge.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package Model;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Collections;

public class Bridge {

private final List<BridgeRow> rows;
private final Random random = new Random();

public Bridge(int height, int width) {
this.rows = new ArrayList<>();
initializeBridgeRows(height, width);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거는 생성자 방식으로 만들기보다 정적 팩토리 메서드 방식으로 만드는게 좋아보여요~

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 수정하겠습니다!!


public Bridge(List<BridgeRow> rows) {
this.rows = rows;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 생성자는 테스트 코드만을 위한 생성자인데, 테스트 코드만을 위한 코드를 만드는게 좋은가?에 대해서는 다른 분들이랑 이야기 해볼만한 주제인 것 같아요~



private void initializeBridgeRows(int height, int width) {
for (int i = 0; i < height; i++) {
this.rows.add(new BridgeRow(width, this.random));
}
}

public List<BridgeRow> getRows() {
return Collections.unmodifiableList(this.rows);
}
}
57 changes: 57 additions & 0 deletions src/main/java/Model/BridgeRow.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package Model;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Collections;

public class BridgeRow {

private final List<BridgeStep> steps;

public BridgeRow(int width, Random random) {
this.steps = createOneRow(width, random);
}

private List<BridgeStep> createOneRow(int width, Random random) {
List<BridgeStep> row = generateRandomRow(width, random);
ensureNoSideBySideSteps(row, random);
return row;
}

private List<BridgeStep> generateRandomRow(int width, Random random) {
List<BridgeStep> row = new ArrayList<>();
for (int i = 0; i < width; i++) {
row.add(BridgeStep.createRandomStep(random));
}
return row;
}

private void ensureNoSideBySideSteps(List<BridgeStep> row, Random random) {
for (int i = 0; i < row.size() - 1; i++) {
fixSideBySideStepIfNeeded(row, i, random);
}
}

private void fixSideBySideStepIfNeeded(List<BridgeStep> row, int index, Random random) {
if (areBothStepsExisting(row, index)) {
setOneToNoneRandomly(row, index, random);
}
}

private boolean areBothStepsExisting(List<BridgeStep> row, int index) {
return row.get(index).isExist() && row.get(index + 1).isExist();
}

private void setOneToNoneRandomly(List<BridgeStep> row, int index, Random random) {
if (random.nextBoolean()) {
row.set(index, BridgeStep.NONE);
return;
}
row.set(index + 1, BridgeStep.NONE);
}

public List<BridgeStep> getSteps() {
return Collections.unmodifiableList(this.steps);
}
}
26 changes: 26 additions & 0 deletions src/main/java/Model/BridgeStep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// BridgeStep.java
package Model;

import java.util.Random;

public enum BridgeStep {
NONE(0), // 다리 없음
EXIST(1); // 다리 있음

private final int value;

BridgeStep(int value) {
this.value = value;
}

public static BridgeStep createRandomStep(Random random) {
if (random.nextBoolean()) {
return EXIST;
}
return NONE;
}

public boolean isExist() {
return this == EXIST;
}
}
55 changes: 55 additions & 0 deletions src/main/java/Model/LadderDescentService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package Model;

import java.util.List;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class LadderDescentService {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로직 깔끔해서 좋네요 👍👍

private final Bridge bridge;

public LadderDescentService(Bridge bridge) {
this.bridge = bridge;
}

public LadderResult calculateAllResults(int numberOfPlayers) {
Map<Integer, Integer> resultMap = IntStream.range(0, numberOfPlayers)
.boxed()
.collect(Collectors.toMap(i -> i, this::descent));
return new LadderResult(resultMap);
}

private int descent(int curPos){
int pos = curPos;
for (BridgeRow row : bridge.getRows()) {
List<BridgeStep> steps = row.getSteps();
pos += checkRightBridge(steps,pos) + checkLeftBridge(steps,pos);
}
return pos;
}


private int checkRightBridge(List<BridgeStep> row, int pos) {
if (pos >= row.size()) {
return 0;
}
if (row.get(pos) == BridgeStep.EXIST) {
return 1;
}
return 0;
}

private int checkLeftBridge(List<BridgeStep> row, int pos) {
if (pos == 0) {
return 0;
}
if (row.get(pos - 1) == BridgeStep.EXIST) {
return -1;
}
return 0;
}


}
20 changes: 20 additions & 0 deletions src/main/java/Model/LadderResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package Model;

import java.util.Collections;
import java.util.Map;

public class LadderResult {
private final Map<Integer, Integer> resultMap;

public LadderResult(Map<Integer, Integer> resultMap) {
this.resultMap = resultMap;
}

public int getDestinationIndex(int startIndex) {
return resultMap.get(startIndex);
}

public Map<Integer, Integer> getResultMap() {
return Collections.unmodifiableMap(resultMap);
}
}
29 changes: 29 additions & 0 deletions src/main/java/Model/Player.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package Model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Player {
List<String> players;
Comment on lines +7 to +8
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

객체명은 플레이어라는 단수지만, 내부는 플레이어들이라는 복수형을 사용하고 있네요

요런 내용은 일급 컬렉션으로 따로 단수 객체는 단수 객체로, 복수 객체는 일급 컬렉션으로 나눠주면 좋습니다~

이렇게 나누게 되면 Players가 할 수 있는 내용이랑 Player만 할 수 있는 메서드로 나뉘게 되는데요

코드 반영하기엔 시간이 오래 걸릴 것 같아 상상 코딩을 하자면 아래 메서드를 각각 어느 클래스에 둘 수 있을까요??
Controller에서는 지금처럼 무조건 Players만 호출이 가능하다고 가정해봅시다

  • getPlayers
  • getPlayersNumber
  • getPlayerName
  • getPlayerIndex

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getPlayers()
getPlayersNumber()
위 두 개의 메소드는 Players가 가지고 있어야 된다고 생가합니다.
getPlayerName()
getPlayerIndex()
나머지 두 개의 메소드는 Player가 가지고 있어야 한다고 생각합니다.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getPlayerIndex는 몇 번째 플레이어인지 확인하는 용도이므로 List인 Players가 더 적절할 것 같아요
이거는 어떻게 만들어질지 하나하나 상상으로 코딩해보면 코딩 작성할 때 큰 도움이 될거라고 생각합니다


public Player(List<String> players) {
this.players = players;
}

public List<String> getPlayers() {
return Collections.unmodifiableList(players);
}

public int getPlayersNumber() {
return players.size();
}

public String getPlayerName(int index) {
return players.get(index);
}

public int getPlayerIndex(String name) {
return players.indexOf(name);
}
}
20 changes: 20 additions & 0 deletions src/main/java/Model/Rewards.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package Model;

import java.util.Collections;
import java.util.List;

public class Rewards {
private final List<String> rewards;

public Rewards(List<String> rewards) {
this.rewards = rewards;
}

public String getReward(int index) {
return rewards.get(index);
}

public List<String> getRewards() {
return Collections.unmodifiableList(rewards);
}
}
15 changes: 15 additions & 0 deletions src/main/java/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package view;

import java.util.Scanner;

public class InputView {
Scanner scanner = new Scanner(System.in);

public int getWidthAndHeight() {
return scanner.nextInt();
}

public Scanner getScanner() {
return scanner;
}
}
Loading