JAVA 숫자야구게임

SJ.CHO·2024년 9월 18일

Lv.1

필수기능

사용기술

  • Java
  • Git, Gtihub

진행과정

1. 정답숫자 생성하기.

public class NumberUtilManager {
    // 정답, 유저입력 숫자 저장 List
    private final Set<Integer> numSet = new HashSet<>();
    private final List<Integer> numList = new ArrayList<>();
    private final List<Integer> userNumList = new ArrayList<>();
    Scanner sc = new Scanner(System.in);

    // 게임 시작시 3개의 숫자를 생성. Set으로 중복을 제거하고 List로 변환시켜 셔플.
    public void addNum() {
        numSet.clear();
        numList.clear();
        while (numSet.size() < 3) {
            // random 메소드를 통해 1~9 숫자 생성
            numSet.add((int) (Math.random() * 9) + 1);
        }
        numList.addAll(numSet);
        Collections.shuffle(numList);
        System.out.println("< 게임을 시작합니다 >");
    }
  • 요구 조건 : 서로 다른 중복되지않는 3자리숫자 , 1~9 의 숫자
  • (Math.random() * 9) + 1) 메소드를 이용하여 1~9의 숫자를 랜덤으로 받아 Set의 저장한다. Set의 저장하는 이유는 중복을 허용하지 않기 때문.
  • Set의 경우 KeyIndex를 순차적으로 저장하기에 저장이 정렬이 되어있는거처럼 저장이 되기에 List에 저장하여 Collections.shuffle(numList) 을 통해 생성된 3자리의 숫자를 랜덤하게 섞어준다.

2. 정답 숫자 입력하기

// 유저에게 숫자를 연속된 문자열을 입력받아 List 저장
    public void inputNum() {
        // 외부에서 반복시 저장되어있던 값 삭제
        userNumList.clear();
        while (userNumList.size() < 3) {
            System.out.println("숫자를 3자리로 입력하세요.");
            userNumList.clear();
            String userNum = sc.nextLine();
            // 입력값 유효성 검사
            if (userNum.length() >= 4) {
                System.out.println("숫자를 3자리로 입력해주세요.");
            } else {
                // 문자열을 나누어서 각각의 int 타입 숫자로 일의 자릿수로 저장,
                for (int i = 0; i < userNum.length(); i++) {
                    char c = userNum.charAt(i);
                    // 입력값 유효성검사.
                    if (userNumList.contains(Character.getNumericValue(c)) || Character.getNumericValue(c) == 0 || !Character.isDigit(c)) {
                        System.out.println("올바르지 않은 입력값입니다.");
                        userNumList.clear();
                        break;
                    } else {
                        // 올바른 데이터일 경우 List add
                        userNumList.add(Character.getNumericValue(c));
                    }
                }
            }
        }
    }
  • 요구조건 : 중복되지않은 3자리숫자 , 숫자 외 입력 불가
  • while() 반복문을 통하여 유저에게 3자리 숫자를 한개의 문자열로 입력받아 4자리 미만인지 확인 후 문자열을 분해하여 1개의 문자를 유효성검사를 진행한다.
  • if (userNumList.contains(Character.getNumericValue(c)) || Character.getNumericValue(c) == 0 || !Character.isDigit(c)) 조건문을 통해 중복값이 있는지, 0이 포함되있는지, 숫자외 문자인지 검사 한 뒤 userNumList 에 저장한다.

3. 결과값 출력 및 게임로직 제작.

// Strike 와 Ball 의 갯수를 집계하는 메소드
    public int judgeStrikesAndBalls() {
        printAnswerLog();
        int stkcnt = 0;
        int ballcnt = 0;
        nm.inputNum();
        userList = nm.getUserNum();
        for (int i = 0; i < numList.size(); i++) {
            if (numList.get(i) == userList.get(i)) {
                stkcnt++;
            } else {
                for (int j = 0; j < numList.size(); j++) {
                    if (userList.get(i) == numList.get(j)) {
                        ballcnt++;
                    }
                }
            }
        }
        dm.printScoreBoard(stkcnt, ballcnt);
        return stkcnt;
    }
public class DisplayManger {
    // 게임의 상황을 출력해주는 메소드
    public void printScoreBoard(int stkcnt, int ballcnt) {
        if (stkcnt == 3) {
            System.out.println("정답입니다.");
        } else if (stkcnt >= 1 || ballcnt >= 1) {
            System.out.println(stkcnt + "스트라이크 " + ballcnt + " 볼");
        } else {
            System.out.println("아웃");
        }
    }
}
  • 요구조건 :

  • 앞에선 입력한 유저입력값이 정해져있는 List를 userList = nm.getUserNum(); 로 받아와서 반복문을 통해 정답값List와 유저값List를 전체순회하여 해당 숫자가 스트라이크, 볼, 아웃을 카운트 하여 DisplayManger 에게 전달하여 값을 콘솔로 출력함.

4. 게임 이어서하기

// 게임의 승패를 판정하는 메소드
    public void judge(List<Integer> nums) {
        judgeflag = !judgeflag;
        while (judgeflag) {
            numList = nums;
            // 3개이상의 strike 시 게임승리
            if (judgeStrikesAndBalls() == 3) {
                judgeflag = !judgeflag;
            }
        }
    }
  • 요구조건 : 정답을 맞출때까지 게임을 계속플레이
  • while() 반복문을 이용하여 유저가 정답을 전부 맞출때까지 계속해서 플레이하게 유도.

Lv.2

필수기능

1. 입력값 유효성 검사

// 유저에게 숫자를 연속된 문자열을 입력받아 List 저장
    public void inputNum() {
        // 외부에서 반복시 저장되어있던 값 삭제
        userNumList.clear();
        while (userNumList.size() < 3) {
            System.out.println("숫자를 3자리로 입력하세요.");
            userNumList.clear();
            String userNum = sc.nextLine();
            // 입력값 유효성 검사
            if (userNum.length() >= 4) {
                System.out.println("숫자를 3자리로 입력해주세요.");
            } else {
                // 문자열을 나누어서 각각의 int 타입 숫자로 일의 자릿수로 저장,
                for (int i = 0; i < userNum.length(); i++) {
                    char c = userNum.charAt(i);
                    // 입력값 유효성검사.
                    if (userNumList.contains(Character.getNumericValue(c)) || Character.getNumericValue(c) == 0 || !Character.isDigit(c)) {
                        System.out.println("올바르지 않은 입력값입니다.");
                        userNumList.clear();
                        break;
                    } else {
                        // 올바른 데이터일 경우 List add
                        userNumList.add(Character.getNumericValue(c));
                    }
                }
            }
        }
    }
  • 요구 조건 : 3자리수 , 중복불가 , 숫자 외 문자입력불가
  • if (userNumList.contains(Character.getNumericValue(c)) || Character.getNumericValue(c) == 0 || !Character.isDigit(c)) 조건문을 통해 입력값 유효성 검사.

2. 출력 개선

 // 게임 시작시 메인메뉴 출력
    public void printMainMenu() {
        System.out.println("환영합니다! 원하시는 번호를 입력해주세요.");
        System.out.println("1. 게임 시작하기 2. 게임 기록 보기 3. 종료하기");
    }
    // MainMenu 선택에 따른 행동흐름 제어
    private void controlMainMenuSelection() {
        switch (MainmenuType.find(sc.nextLine())) {
            case YES:
                nm.addNum();
                gm.judge(nm.getNumber());
                break;
            case HISTORY:
                break;
            case EXIT:
                System.out.println("게임을 종료합니다.");
                System.exit(0);
                break;
        }
    }
public enum MainmenuType {
    YES("1"),
    HISTORY("2"),
    EXIT("3");

    // 프로그램이 실행되면 Map을 캐싱해 찾고자 하는 키값과 필드값을 매칭 스트림과 람다식을 활용.
    private static final Map<String, MainmenuType> MAINMENU_TYPE_MAP_MAP =
            Collections.unmodifiableMap(Stream.of(values()).collect(Collectors.toMap(MainmenuType::getMainmenu, Function.identity())));
    private final String mainmenu;

    // 생성자를 통한 String 매칭
    MainmenuType(String mainmenu) {
        this.mainmenu = mainmenu;
    }

    // 입력된 연산자를 통해 Map 안에서 열거 객체 리턴
    public static MainmenuType find(String mainmenu) {
        if (MAINMENU_TYPE_MAP_MAP.containsKey(mainmenu)) {
            return MAINMENU_TYPE_MAP_MAP.get(mainmenu);
        } else {
            // 지정메뉴외 문자 입력시 예외발생
            throw new NullPointerException("메뉴중에서 입력해주세요.");
        }
    }

    public String getMainmenu() {
        return mainmenu;
    }
}
  • 요구 조건 : 1을 입력시 게임시작 , 2를 입력시 기록확인 (Lv.3 추가기능) , 3을 입력시 게임종료
  • enum MainmenuType Class 를 선언, switch (MainmenuType.find(sc.nextLine())) 조건문을 통해 유저가 입력한 필드값을 이용해 Eunm 열거상수를 받아 요구조건의 메뉴 기능 흐름을 따라간다.

Lv.3

필수기능

진행과정

1. 게임기록 통계

// 게임의 승패판정 및 게임시도횟수 집계 메소드
    public void judge(List<Integer> nums) {
        judgeflag = !judgeflag;
        while (judgeflag) {
            numList = nums;
            // 3개이상의 strike 시 게임승리
            if (judgeStrikesAndBalls() == 3) {
                gamecnt++;
                gameCountList.add(gamecnt);
                gamecnt = 0;
                judgeflag = !judgeflag;
            } else {
                gamecnt++;
            }
        }
    }
// MainMenu 선택에 따른 행동흐름 제어
    private void controlMainMenuSelection() {
        switch (MainMnuType.find(sc.nextLine())) {
            case YES:
                nm.addNum();
                gm.judge(nm.getNumber());
                break;
            case HISTORY:
                dm.printGameLog(gm.getGameCountList());
                break;
            case EXIT:
                System.out.println("< 숫자 야구 게임을 종료합니다 >");
                System.exit(0);
                break;
        }
    }
// 현재까지 진행한 게임 시도횟수
    public void printGameLog(List<Integer> gameLogList) {
        // 진행된 게임 기록이 있는지 유효성 검사
        if (gameLogList.isEmpty()) {
            System.out.println("진행된 게임 기록이 없습니다.");
        } else {
            int gameCnt = 0;
            System.out.println("<게임 기록 보기>");
            // 패싱 받은 List 를 전체반복하며 유저 게임횟수 출력
            for (int item : gameLogList) {
                System.out.println(++gameCnt + " 번째 게임 : 시도횟수 - " + item);
            }
        }
    }
  • 요구조건 : 현재 게임횟수 출력 , 사용자가 정답까지 도달한 횟수 출력
  • 게임이 실행되고 judge() 메소드 안에서 반복문이 실행되며 유저가 정답에 도달하기전과 도달직후 횟수를 증가 및 List 에 저장.
  • 현재 게임횟수는 List Index를 통해 확인이 가능하기에 별도로 선언하지 않음.
  • printGameLog() 메소드의 반복문을 통해 게임횟수 및 시도횟수를 출력. List가 비어있을시 예외처리.

Lv.4

필수기능

진행과정

1. 게임난이도 설정

// 유저 난이도 조절 메소드
    public void setDifficulty() {
        System.out.println("설정하고자 하는 자리수를 입력하세요.");
        System.out.println("기본 난이도는 3 자리수 입니다.");
        System.out.println("3, 4, 5 자리수 난이도만 설정이 가능합니다.");
        while (true) {
            String input = sc.nextLine();
            // 3,4,5 외 숫자,문자 검증
            if (input.matches("[3-5]+")) {
                difficulty = Integer.parseInt(input);
                System.out.println(difficulty + " 자리수 난이도로 설정되었습니다.");
                break;
            } else {
                System.out.println("3,4,5 난이도만 선택이 가능합니다.");
            }
        }
    }
// MainMenu 선택에 따른 행동흐름 제어
    private void controlMainMenuSelection() {
        switch (MainMnuType.find(sc.nextLine())) {
            // 유저 난이도선택 메뉴
            case SETDIFFT:
                nm.setDifficulty();
                nm.addNum();
                gm.judge(nm.getNumber());
                break;
            // 게임 실행 메뉴
            case YES:
                nm.addNum();
                gm.judge(nm.getNumber());
                break;
            // 유저 게임기록 출력 메뉴
            case HISTORY:
                dm.printGameLog(gm.getGameCountList());
                break;
            // 게임 종료 메뉴
            case EXIT:
                System.out.println("< 숫자 야구 게임을 종료합니다 >");
                System.exit(0);
                break;
        }
    }
private static int difficulty = 3;

// 현재 난이도 반환 메소드
    public int getDifficulty() {
        return difficulty;
    }
  • 요구 조건 : 자리수는 3,4,5 외엔 입력불가 , 자리수에 따른 게임 난이도 변화
  • setDifficulty() 메소드를 통해 difficulty 필드에 값을 조정. difficulty 값을 static으로 설정한 이유는 값을 공유하여 사용할일이 더 많기 때문에.
  • 게임로직 및 승리조건, 입력검산등의 유효성검사도 전부 difficulty의 기준하에 맞춰지도록 변경.

Lv. Custom

구현기능 :

찬스기능 추가 5자리 찬스점수 3점, 3~4자리 찬스점수 1점
1. 입력받은 자리 숫자 공개 - 찬스점수 2점 감소
2. 입력받은 자리 숫자 Up&Down - 찬스점수 1점 감소
3. 정답숫자 총합 공개 - 찬스점수 1점 감소

1. 입력받은 자리 숫자 공개

// 유저입력값을 받아 입력값 Index 에 해당하는 숫자 반환 메소드
    public void getIndexNumber(List<Integer> nums) {
        if (chance >= 2) {
            System.out.println("알고 싶은 자리수를 입력해주세요");
            while (true) {
                int chanceIndex = sc.nextInt();
                if (nums.size() > chanceIndex - 1) {
                    System.out.println(chanceIndex + " 번째 숫자는 " + nums.get(chanceIndex - 1) + " 입니다.");
                    chance -= 2;
                    break;
                } else {
                    System.out.println("자리수 입력을 확인해주세요.");
                    sc.nextLine();
                }
            }
        } else {
            System.out.println("가진 찬스횟수가 부족합니다.");
        }
    }
  • 요구 조건 : 찬스점수가 2점이상일것 , 자리인덱스를 정확히 입력할 것
  • List의 인덱스와 찬스필드의 점수가 일정이상인지의 유효성 검사를 진행 후 nums.get(chanceIndex - 1) 의 List 값을 가져와 값을 출력
  • 빈 List를 가져올 경우가 없기때문에 빈 List검사는 진행하지 않는다.

2. 입력받은 자리 숫자 Up&Down

// 유저입력값을 받아 입력값 Index 에 해당하는 숫자 가 큰지 작은지 반환하는 메소드
    public void getIndexNumberUpDown(List<Integer> nums) {
        if (chance >= 1) {
            System.out.println("알고 싶은 자리수를 입력해주세요");
            Loop1:
            while (true) {
                int chanceIndex = sc.nextInt();
                sc.nextLine();
                // 입력숫자와 Index 숫자를 비교하여 Up&Down 비교
                if (nums.size() > chanceIndex - 1) {
                    while (true) {
                        System.out.println("Up & Down 기준 숫자를 입력해주세요.");
                        String standardNum = sc.nextLine();
                        // 입력비교값이 1~9 사이일경우만 진행
                        if (!standardNum.matches("[1-9]+")) {
                            System.out.println("1~9의 숫자만 입력해주세요");
                        } else {
                            if (Integer.parseInt(standardNum) > nums.get(chanceIndex - 1)) {
                                System.out.println("DOWN");
                            } else if (Integer.parseInt(standardNum) < nums.get(chanceIndex - 1)) {
                                System.out.println("UP");
                            } else if (Integer.parseInt(standardNum) == nums.get(chanceIndex - 1)) {
                                System.out.println("Same");
                            }
                            chance--;
                            break Loop1;
                        }
                    }
                } else {
                    System.out.println("자리수 입력을 확인해주세요.");
                    sc.nextLine();
                }
            }
        } else {
            System.out.println("가진 찬스횟수가 부족합니다.");
        }
    }
  • 요구 조건 : 찬스점수가 1점이상일것 , 자리인덱스를 정확히 입력할 것 , 입력값이 1~9사이의 숫자일것
  • if (!standardNum.matches("[1-9]+")) 의 정규식 조건문을 통해 1~9 의 숫자만을 입력받을수 있다.
  • 입력받은 숫자와 가져온 Index 숫자와 비교 후 Up&Down 을 알려준다. 동일할경우 고민을 해봤는데 그냥 정답인것을 알려주는것으로 처리.

3. 정답숫자 총합 공개

// 모든 정답 숫자의 합 출력 메소드
    public void getSumAnswer(List<Integer> nums) {
        if (chance >= 1) {
            System.out.println("모든 번호의 합은");
            int sum = nums.stream().mapToInt(num -> num).sum();
            System.out.println(sum + " 입니다.");
            chance--;
        } else {
            System.out.println("가진 찬스횟수가 부족합니다.");
        }
    }
  • 요구 조건 : 찬스점수가 1점이상일것
  • int sum = nums.stream().mapToInt(num -> num).sum(); 의 람다 스트림을 이용하여 모든 정답숫자의 총합을 출력한다.
  • 애매한 찬스기능이지만 자리수와 총합을 가지고 대략적인 유추는 가능하다 생각하여 기능추가.

트러블슈팅

링크(클릭)

구현기능

프로그램 주요기능 플로우차트

Main Flow

Chance Flow

프로그램 동작화면

Stack

Architecture

📦 
├─ .gitignore
├─ .idea
│  ├─ .gitignore
│  ├─ material_theme_project_new.xml
│  ├─ misc.xml
│  ├─ modules.xml
│  ├─ uiDesigner.xml
│  └─ vcs.xml
├─ BullsandCowsProjects.iml
├─ README.md
└─ src
   └─ com
      └─ bullsandcows
         ├─ lv1 : Lv.1 필수기능구현 숫자야구
         │  ├─ Main.java : main 메소드 실행 Class
         │  └─ manager : 각 기능별 Manager Class 집합 패키지
         │     ├─ DisplayManger.java : 콘솔 출력 담당 Class
         │     ├─ GameJudgeManager.java : 게임 승패 및 게임 결과(Strike, Ball) 체크 Class
         │     ├─ NumberUtilManager.java : 정답숫자 생성, 유저 입력 숫자 유효성 검사 Class
         │     └─ ServiceManager.java : 게임 전체 흐름 담당 Class
         ├─ lv2 : Lv.2 필수기능구현 숫자야구
         │  ├─ Main.java
         │  ├─ MainmenuType.java : MainMenu Enum Class
         │  └─ manager
         │     ├─ DisplayManger.java
         │     ├─ GameJudgeManager.java
         │     ├─ NumberUtilManager.java
         │     └─ ServiceManager.java
         ├─ lv3 : Lv.3 필수기능구현 숫자야구
         │  ├─ Main.java
         │  ├─ MainMnuType.java
         │  └─ manager
         │     ├─ DisplayManger.java
         │     ├─ GameJudgeManager.java
         │     ├─ NumberUtilManager.java
         │     └─ ServiceManager.java
         ├─ lv4 : Lv.4 필수기능구현 숫자야구
         │  ├─ Main.java
         │  ├─ MainMnuType.java
         │  └─ manager
         │     ├─ DisplayManger.java
         │     ├─ GameJudgeManager.java
         │     ├─ NumberUtilManager.java
         │     └─ ServiceManager.java
         └─ lvcustom : 찬스 추가 기능구현 숫자야구
            ├─ ChanceMainMenuType.java : 찬스메뉴 Enum Class
            ├─ Main.java
            ├─ MainMenuType.java
            └─ manager
               ├─ ChanceManger.java : 찬스 추가 기능 담당 Class
               ├─ DisplayManger.java
               ├─ GameJudgeManager.java
               ├─ NumberUtilManager.java
               └─ ServiceManager.java

©generated by Project Tree Generator

Github 링크

링크(클릭)

profile
70살까지 개발하고싶은 개발자

0개의 댓글