middlemoon

[우아한테크코스 프리코스]3주차를 마무리하면서 본문

우테코

[우아한테크코스 프리코스]3주차를 마무리하면서

중대경 2022. 12. 10. 17:18

 

 

벌써 4주중에 절반이 지나온 시점이다.  마찬가지로 전 시간에 리뷰했던 숫자야구게임에 대한 공통 피드백이 왔다

2주차에서도 기능구현을 하는 부분에 초점을 맞추어 진행하였다. 이번 3주차에서는 입력값(예외사항), 당첨통계에 대한 내용, 수익률 등 라이브러리를 이용해 쓰일만한 문제들이 나오곤 하였다.

2주차 피드백

사실 한 블로그 안에 많은 내용을 담아내기가 꽤나 어려웠다고 생각한다. 이번 3주차는 예외사항을 신경을 많이 쓰면서 코딩을 한 기억이 난다.

 

 

기능구현리스트


 

#로또 3주차 

## 기능 요구 사항 
로또 게임 기능을 구현해야 한다. 로또 게임은 아래와 같은 규칙으로 진행된다.

- 로또 번호의 숫자 범위는 1~45까지이다.
- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. 
- 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다.
- 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다.
    - 1등: 6개 번호 일치 / 2,000,000,000원
    - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
    - 3등: 5개 번호 일치 / 1,500,000원
    - 4등: 4개 번호 일치 / 50,000원
    - 5등: 3개 번호 일치 / 5,000원
    
  
      로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다.
      로또 1장의 가격은 1,000원이다.
      당첨 번호와 보너스 번호를 입력받는다.
      사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다.
      사용자가 잘못된 값을 입력할 경우 IllegalArgumentException를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 종료한다.

## 기능 요구 구현 리스트

- [X] 로또 금액 입력 메소드 model#RandomNumber()
  - [X] 로또 금액이 입력 된 후 로또를 자동발행하는 메소드 - 1부터 45까지
  - [X] 예외사항 -
  

- [X] 로또 금액 가격 메소드 view#LottoPrice()
  - [X] 로또 금액 1,000원 단위로 입력받을수 있도록 구현하는 메소드 
  - [X] 예외사항 - 


- [X] 당첨 번호 메소드 view#LottoSixNumber()
  - [X] 당첨번호를 (,)로 받을 수 있는 메소드
  - [X] 예외사항 - 


- [X] 보너스 번호 메소드 Match#BonusCheck()
  - [X] 예외사항 - 


- [X] 사용자 구매한 로또, 당첨 번호 비교 model#LottoCompare()
- [X] 당첨 내역  model#RankList()
- [X] 수익률 출력 Game#LottoReturn()



- [X] 전체 예외처리 Validator()
- [X] 전체 Enum 적용 view()

 

 

2주차 피드백을 최대한 반영하여 첫번째에 명시된 README.md에 대한 내용들을 정확히 기술하려 노력하였다. 사실 어떻게 구현하던간에 정답은 없다생각하지만 프로그램을 짜다보면 겹치는 부분도 있을뿐더러, 아 이런식으로도 구현할수 있겠구나 라고 배우기도 한다.

문제 당 접근하는 스타일이 다르기 때문에, 그에 상응하는 요구사항도 다를수 있다는 것을 깨달았다.

 

 

요구사항은 다음과 같다. 우리가 흔히아는 로또형식인데, 1~45까지의 임의의 수 로또 자동생성기 그리고 유저가 직접 입력하게 되는 부분이 한가지씩 있다. 일반 로또 규칙과 별반다를것이 없다. 요구 사항의 기능들을 차례대로 구현을 하면된다.

방향성


이번 3주차 미션은 MVC패턴을 바탕으로 만드는 것을 첫번째 기준으로 잡았다. MVC패턴은 Model, View, Controller의 약자로 서버와 클라이언트간의 통신 관계에서 하나의 개념이라 생각하면 될것이다.

흔히, MVC패턴을 이용해 웹 어플리케이션을 설계한다. 라는 말을 들어보았듯이 3주차 과제에 대한 프로젝트도 일종의 홈페이지를 만든다 생각하고 진행을 했다 생각하면 된다. 그러다보니 패키지들과 클래스들을 각각 해당하는 역할에 맞춰 넣어야 하다보니 2주차에 비해 까다로웠던것도 사실이다. 하지만 로직분리가 정확하게 됨으로써 메서드들이 우왕좌왕 하지 않고 제 역할만 할 수 있다는것이 아마 장점이지 않을까 싶다.

 

 

 

 

코드구현 


 

Lotto.java (Model Logic)

package lotto.model;

import lotto.view.ErrorMessage;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Lotto {
    private final List<Integer> numbers;

    public Lotto(List<Integer> numbers) {
        Validate(numbers);
        ValidatorRange(numbers);
        ValidatorDuplicate(numbers);
        this.numbers = numbers;
    }

    @Override
    public String toString(){
        return numbers.toString();
    }

    private void Validate(List<Integer> numbers) {
        if (numbers.size() != 6) {
            throw new IllegalArgumentException(ErrorMessage.LOTTO_SIX_NUMBER_POSSBLITY.getMessage());
        }
    }
    // TODO: 추가 기능 구현

    public boolean LottoPrice(int LottoPrice){
        if(LottoPrice % 1000 == 0){
            return true;
        }
        return false;

    }


    public void ValidatorRange(List<Integer> number){
        for(Integer one : number){
            if(one < 1 || one > 45){
                throw new IllegalArgumentException(ErrorMessage.LOTTO_RANDOM_NUMBER_RANGE.getMessage());
            }
        }
    }

    public void ValidatorDuplicate(List<Integer> number){
        Set<Integer> hash = new HashSet<>(number);
        if(hash.size() != number.size()){
            throw new IllegalArgumentException(ErrorMessage.LOTTO_OVERLAP_INPUT.getMessage());
        }
    }

}

 

 

 

 

Match.java (Model Logic)

package lotto.model;

import lotto.view.InputView;

import java.util.*;
import java.util.stream.Collectors;

public class Match {
    private static int matchCount = 0;
    private static int countCheck;

    InputView inputView = new InputView();
    public int compareResult(List<Integer> WinningNumber, List<Integer> RandomsNumber){

        int result = 0;
        List<Integer> Winning = WinningNumber.stream().collect(Collectors.toList());
        List<Integer> Randoms = RandomsNumber.stream().collect(Collectors.toList());

        int count = 0;
        for(Integer sr : RandomsNumber){
            if(RandomsNumber.contains(WinningNumber)){
                result = count++;
            }
        }
        return result;
    }

    public static List<Integer> matchingCorrector(List<List<Integer>> lottoNumbers, List<Integer> WinningNumbers){
        Map<List<Integer>, Integer> map = new HashMap<>();
        for(List<Integer> lotto: lottoNumbers){
            countCheck(map, lotto, WinningNumbers);
            countCheck = 0;
        }
        setMatchCount(map);
        List<Integer> MatchingLotto = new ArrayList<>();
        for (Map.Entry<List<Integer>, Integer> entry : map.entrySet()){
            if(entry.getValue().equals(matchCount)){
                MatchingLotto = entry.getKey();
                break;
            }
        }
        return MatchingLotto;
    }

    public static int getMatchCount(){
        return matchCount;
    }

    private static void setMatchCount(Map<List<Integer>, Integer> map){
        countCheck = Collections.max(map.values());
        matchCount = countCheck;
    }


    // 3개일치 --> 1개 추가
    public static void countCheck(Map<List<Integer>, Integer> map, List<Integer> lotto, List<Integer> winningNumbers) {
            for(Integer match : winningNumbers){
                if(lotto.contains(match)){
                    countCheck++;
                    map.put(lotto, countCheck);
                }
            }
    }


    public static boolean BonusCheck(List<Integer> MatchingLotto, int BonuseNumber){
        if(MatchingLotto.contains(BonuseNumber)){
            return true;
        }
        return false;
    }
}

 

RankList(Model Logic) - enum 구현

package lotto.model;

public enum RankList {

    MATCH_NOTTHING(0, " 당첨번호가 없습니다." , 0),
    MATCH_THREE(3, "3개 일치 (5,000원) - ", 5_000),
    MATCH_FOUR(4, "4개 일치 (50,000원) - ", 50_000),
    MATCH_FIVE(5, "5개 일치 (1,500,000원) - ", 1_500),
    MATCH_FIVE_AND_BONUS(5, "5개 일치, 보너스 볼 일치 (30,000,000원) - ", 30_000_000),
    MATCH_SIX(6, "6개 일치 (2,000,000,000원) - ", 2_000_000_000);


    private final int match;
    private final String rank;
    private final int money;

    RankList(int match, String rank, int money){
        this.match = match;
        this.rank = rank;
        this.money = money;
    }

    public static RankList getRankResult(int matchCount, boolean bonus){
        if (MATCH_FIVE_AND_BONUS.match == matchCount && bonus){
            return MATCH_FIVE_AND_BONUS;
        }

        for(RankList rank : values()){
            if(rank.match == matchCount && rank != MATCH_FIVE_AND_BONUS){
                return rank;
            }
        }
        return MATCH_NOTTHING;
    }

    public String getRank(){return rank;}

    public int getMoney(){return money;}

}

 

 

OutputView.java

package lotto.view;

import lotto.model.RankList;

import java.text.DecimalFormat;
import java.util.List;
import java.util.Map;

public class OutputView {
    private  static final int unit = 1000;

    public static void resultCount(int number){
//        int count = number / 1000;
        System.out.println(number + GameMessage.SECOND_BUY_MESSAGE.getgameMessage());
    }

    public static int divideCount(int price){
        int Amount = price / unit;
        return Amount;
    }

    public static void printLotto(List<List<Integer>> lottos){
        for(List<Integer> list : lottos){
            System.out.println(lottos);
        }
        System.out.println();
    }

    public static void Overlap(List<Integer> winningNum, int bonus){
        if(winningNum.contains(bonus)){
            throw new IllegalArgumentException(ErrorMessage.LOTTO_OVERLAP_INPUT.getMessage());
        }
    }

    public static void countProcess(Map<RankList, Integer> result){
        System.out.println(GameMessage.CORRECT_LIST.getgameMessage());
        for(RankList rank : RankList.values()){
            if(rank.getMoney() == 0){
                continue;
            }
            System.out.println(rank.getRank() + result.get(rank) + "개");
        }
    }

    public static void countResult(Map<RankList, Integer> result, int purchase){
        for( RankList rank : result.keySet()){
            if(result.get(rank) == 1){
                Double dou = ((double) rank.getMoney() / purchase) * 100;
                DecimalFormat decimalFormat = new DecimalFormat("###,###.#");
                System.out.println(GameMessage.RATE.getgameMessage() + decimalFormat.format(dou) + GameMessage.PERCENT.getgameMessage());
            }
        }
    }
}

MVC패턴 중 View에 해당하는 화면이다. match에 해당하는 0개일치 3개일치 4개일치 5개일치 6개일치는 model부분에서

matchingCorrector를 이용하여 구현하였고, 해당하는 금액도 동시에 넣어주었다. 물론 화면에 보이고자 하는 부분 enum에서

로직분리를 어떤식으로 해줘야하는지 고민을 했었던 것 같다. 

 

 

 

 

View안에 넣으려했던 것들은 다음과 같다. 게임이 진행될때 안내되는 메세지, 사용자가 예외로 다른 경우를 입력했을 때의 모든 예외 메세지 모두 enum형식으로 넣으려고 하였다. 요구사항에서도 enum을 활용하여 값들이 하드코딩되는것을 방지하기 위해 만든 규칙 같아보였다.

package lotto.view;

public enum GameMessage {
    FIRST_START_MESSAGE("구입금액을 입력해 주세요."),
    SECOND_BUY_MESSAGE("개를 구매했습니다."),
    THIRD_INPUT_WINNINGNUMBER("당첨 번호를 입력해 주세요."),
    FOUR_INPUT_BONUSNUMBER("보너스 번호를 입력해 주세요."),
    CORRECT_LIST("당첨 통계\n---"),
    RATE("총 수익률은 "),
    PERCENT("%입니다.");

    private final String gameMessage;


    GameMessage(String gameMessage){
        this.gameMessage = gameMessage;
    }



    public String getgameMessage(){
        return gameMessage;
    }



}

 

 

 

3주차 회고


엊그제 프리코스를 시작했었던 기억이 나는데 벌써 절반이 넘게 끝나버렸다. 처음 개발자가 되기 위해 임했던 각오들이 어떠했는지

다시 한번 기억해본다. 어떤 개발자로 살아남아야하는지에 대한 질문과 무엇을 만드는 사람이 되어야하는지 다시한번 생각해본다.

이번 요구사항도 더불어 제공된 클래스가 있었다. 클래스의 역할은 함수를 만들기 위해 큰 틀밖에 되진 않았었구나 라고 생각했었지만

생성자를 통해 만드는 경우에도 동일하게 함수로 만들수 있구나 라는 것을 알게되었다.

추가된 요구사항을 진행하면서 15라인을 넘어가지 않도록, Java Enum을 사용하도록, 핵심 로직을 구현하는 코드와 UI를 분리하는 로직을 구현하도록 나오는 것이 3주차 추가요구사항인데, 이번 리펙토링과 더불어 다시풀어보는 시간을 가지면서 충분히 고민해보고 필요한 시간이었다고 생각한다.

 

 

Comments