주사위 게임 만들기🎲 

📑문제

https://cafe.naver.com/eddicorp/861

 

Java 문제 은행 [ 1 ]

1. 주사위 게임을 만들어보자! 총 2개의 주사위를 굴릴 수 있다. 첫 번째 주사위를 굴려서 짝수가 나왔다면 주사위를 한 번 더 굴릴 수 있다. 새롭게 한 번 더 굴리는 주...

cafe.naver.com

 

 

🔍풀이

com.example.demo.controller.basic.sixth

 

SixthCleanDiceGameController

 

package com.example.demo.controller.basic.sixth;

import com.example.demo.entity.basic.sixth.Player;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

@Slf4j
@RestController
@RequestMapping("/sixth")
public class SixthCleanDiceGameController {
    private final int DEFAULT_PLAYER_NUM = 2;
    private final int DEATH_SCORE = -4444;
    private List<Player> players;
    private int playerNum;

    @GetMapping("/dice-game")
    public String diceGameStarter() {

        createPlayer();
        gameStart();

        return checkWinner();
    }

    // Collection을 사용한다는 것은 결국 특정 집합체를 다루는 것을 의미함
    // 특정 집합체를 다루는 가장 기본형태는 배열이다.
    // 근데 배열은 단점은 ?
    // 1. 길이가 고정됨
    // 2. 한 가지 종류만 저장된다 ? (Object 타입 배열)
    // 3. 중간에 값을 끼워넣을 수 있을까 ???
    //    가능은 하지만 코드가 매우 쓰잘데기 없이 드러워짐
    //    기존 내용들을 전부 다 뒤로 빼고 해당 위치에 새로운것을 배치해야함
    //    근데 데이터가 1억개면 ?
    //    이렇게 만들면 모니터랑 머리랑 결합됩니다.

    //    만약 배열 형태가 아니라 특정 정보를 끼워넣고 참조하는 위치만 변경할 수 있다면 어떨까요 ?

    // * 배열 vs 연결리스트
    //            1  1  1  1  1  1  1  1  1  1
    // 배열의 형태 [0][1][2][3][4][5][6][7][8][9]  <--- 구조 자체가 순차적임 (하드웨어적으로도)
    //
    // 리스트의 형태 { 1, 참조 } ---> { 1, 참조 } ---> { 1, 참조 } ---> ...
    //
    // 위 케이스에서 1번 인덱스에 숫자 7을 넣는 작업을 생각해봅시다.
    // 일단 1번 인덱스의 위치로 이동
    // 1번 ~ 9번 인덱스의 내용을 외부 공간에 저장 (별도의 변수 생성하던지 등등)
    // 인덱스 1에 숫자 7 배치
    // 인덱스 2번부터 별도의 공간에 생성한 정보 가져와서 재배치
    // (이때 사실은 기존 메모리를 날리고 새롭게 증가된 배열을 할당 받아야함)
    // new int[origin_arr.length + 1] 형태로
    // 보기만 해도 코드 구조 자체가 매우 더러울 것이 자명함
    //
    // 리스트 케이스
    // { 1, 참조 } ---> { 1, 참조 } ---> { 1, 참조 } ---> ...
    // 1번 인덱스를 조정할 것이니 아래와 같이 연결을 일시적으로 끊음.
    // { 1, 참조 }      { 1, 참조 } ---> { 1, 참조 } ---> ...
    // 새로 배치할 정보를 생성함
    // { 1, 참조 }      { 1, 참조 } ---> { 1, 참조 } ---> ...
    //         { 7, 참조 }
    // 참조 연결을 다시 맵핑
    // { 1, 참조 }      { 1, 참조 } ---> { 1, 참조 } ---> ...
    //     |                 ^
    //     |                 |
    //     ---> { 7, 참조 } ---
    // 뭐가 더 좋은가요 ?
    // 이게 Collection 쓰는 이유의 시작이자 마지막이고 모든것임.
    // 물론 HashSet 등등 Red Black 트리 쓰는 기법이 있는데 일단 여기까지 신경쓰진 말자!
    public void createPlayer() {
        // 기존에 플레이어 정보를 배열로 관리하고 있었음
        // 만약 네트워크 대전 게임이라고 가정한다면 ?
        // 보편적인 네트워크 대전 게임엔 어떤 기능이 있나요 ? 난입 <<<
        // 사람 숫자 제한이 없다고 가정한다면 ??? 접속자가 몇명이 될지 알 수 없으니 배열로 커버가 안됨.
        // 그렇다면 여기에서 뭘 써야 할까요 ? Collection 내의 정보 집합체들

        players = new ArrayList<>();
        this.playerNum = DEFAULT_PLAYER_NUM;

        createPlayerObj(players, DEFAULT_PLAYER_NUM);
    }

    public void createPlayerObj(List<Player> players, int playerObjNum) {
        for (int i = 0; i < playerObjNum; i++) {
            players.add(new Player());
        }
    }

    // 사용자가 게임을 시작해서
    // 주사위를 굴리고
    // 굴린 주사위가 짝수라면
    // 앞서 만든 유틸리티 사용하고
    // 유틸리티를 사용해서 각 플레이어들에게 옵션 적용
    public void gameStart() {
        for (int i = 0; i < playerNum; i++) {
            players.get(i).gameStart(players);
        }
    }

    // 최종 승자 판정
    // 현재 이 부분은 일반화하지 않았고 2명이라 가정하고 진행함
    // 실제로는 Collection에 넣고 sort() 시키면 정렬이 됨
    // 여기선 그냥 두명 가정하고 만듭시다.
    // 이거까지 또 일반화하면 ... 머리 터질것 같으니까 ㅋㅋㅋ
    public String checkWinner() {
        // 기본적으로 Java는 String에 대한 처리를 서포트하지만
        // 사용자 커스텀 자료타입인 class에 대해서는 부가적 작업을 해줘야함
        // Collections가 기본적으로 Comparable을 extends 하고 있으며
        // 실제 Comparable은 interface임을 볼 수 있다.
        // 해당 인터페이스는 compareTo 프로토타입을 가지고 있으므로
        // 우리가 정렬하고자 하는 Player 클래스에 이에 대한 구현체가 필요하다.
        log.info("정렬전: " + String.valueOf(players));

        Collections.sort(players);

        log.info("정렬후: " + String.valueOf(players));

        int maxPlayerScore = acquirePlayerTotalScore(playerNum - 1);

        if (maxPlayerScore == DEATH_SCORE) { return "전원 탈락!!!"; }

        List<Integer> sameScoreIdxList = new ArrayList<>();

        for (int i = playerNum - 2; i >= 0; i--) {
            int tmpScore = acquirePlayerTotalScore(i);

            if (maxPlayerScore != tmpScore) { break; }

            sameScoreIdxList.add(i);
        }

        if (sameScoreIdxList.size() > 0) { return "무승부입니다!"; }

        return "플레이어: " + players.get(playerNum - 1).getNickName() + " 님이 승리하였습니다!";
    }

    public int acquirePlayerTotalScore(int idx) {
        return players.get(idx).getScore().getTotalScore();
    }
}

 

 

om.example.demo.entity.basic.sixth

Player

package com.example.demo.entity.basic.sixth;

import com.example.demo.utillity.basic.sixth.Dice;
import lombok.Getter;
import lombok.ToString;

import java.util.List;

@Getter
@ToString
public class Player implements Comparable<Player> {
    private Score score;
    private String nickName;

    private final String DEFAULT = "anonymous";

    public Player() {
        nickName = DEFAULT;
        score = new Score();
    }

    public void gameStart(List<Player> players) {
        for (int i = 0; i < players.size(); i++) {
            int diceNum = Dice.rollDice();
            score.addScore(diceNum);

            if (diceNum % 2 == 0) {
                diceNum = Dice.rollDice();
                Dice specialDice = new Dice();
                score.addScore(diceNum);

                specialDice.checkSpecialDice(players, diceNum, i);
            }
        }
    }

    // 코드 단위로 분석하면 대가리 폭발하므로
    // 쉽게 생각한다면 아래와 같은 메커니즘으로 보도록 한다.
    // 1. 내가 비교하고자 하는 정보가 무엇인가 ? 현재 우리케이스는 totalScore
    //    그래서 비교 대상에 this.getScore().getTotalScore(),
    //    player.getScore().getTotalScore()이 사용됨
    // 2. 다음으로 한쪽이 크면 양수, 작으면 음수, 같으면 0으로 배치하면
    //    내가 비교하고자 하는 값으로 언제든지 정렬이됨
    // 3. 내부 코드 볼 필요 없음 (추상화)
    @Override
    public int compareTo(Player player) {
        int srcScore = this.getScore().getTotalScore();
        int dstScore = player.getScore().getTotalScore();

        if (srcScore > dstScore) {
            return 1;
        } else if (srcScore < dstScore) {
            return -1;
        } else {
            return 0;
        }
    }
}

 

Score

package com.example.demo.entity.basic.sixth;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class Score {
    private int totalScore;

    public void addScore(int score) {
        totalScore += score;

        if (totalScore < 0) {
            totalScore = 0;
        }
    }
}

 

 

 

com.example.demo.utillity.basic.sixth

Dice

package com.example.demo.utillity.basic.sixth;

import com.example.demo.entity.basic.sixth.Player;
import com.example.demo.utillity.basic.third.CustomRandom;

import java.util.List;

// 인터페이스는 상속하지 않고 구현한다고 한다.
// 왜냐하면 매서드가 작성되어 있지 않기 때문에
// implements 하는 클래스에서 매서드를 구현해줘야 한다.
public class Dice implements SpecialDiceOption {
    private static final int MIN = 1;
    private static final int MAX = 6;

    public static int rollDice() {
        return CustomRandom.makeIntCustomRandom(MIN, MAX);
    }

    public static void steelPlayerScore(List<Player> players, int playerIdx, int steelScore) {
        for (int i = 0; i < players.size(); i++) {
            if (i == playerIdx) { continue; }

            players.get(i).getScore().addScore(-steelScore);
            players.get(playerIdx).getScore().addScore(steelScore);
        }
    }

    public static void donatePlayerScore(List<Player> players, int playerIdx, int donateScore) {
        for (int i = 0; i < players.size(); i++) {
            if (i == playerIdx) { continue; }

            players.get(playerIdx).getScore().addScore(-donateScore);
            players.get(i).getScore().addScore(donateScore);
        }
    }
    // rollSpecialDice는 플레이어 객체 정보가 필요함
    // 나를 제외하고 적용하는 경우도 존재하니까
    @Override
    public void checkSpecialDice(List<Player> players, int diceNum, int playerIdx) {
        final int SOB = 1;
        final int LUCKY = 3;
        final int DEATH = 4;
        final int BULLSHIT = 5;

        final int SOB_SCORE = 2;
        final int LUCKY_SCORE = 3;
        final int DEATH_SCORE = -4444;
        final int BULLSHIT_SCORE = 2;

        for (int i = 0; i < players.size(); i++) {
            switch (diceNum) {
                case SOB:
                    players.get(i).getScore().addScore(-SOB_SCORE);
                    break;

                case LUCKY:
                    // 플레이어에 구현하는 것이 좋을까 ?
                    // 점수에 구현하는 것이 좋을까 ?
                    // 아니면 별개로 구현하는 것이 좋을까 ?
                    steelPlayerScore(players, playerIdx, LUCKY_SCORE);
                    break;

                case DEATH:
                    players.get(i).getScore().setTotalScore(DEATH_SCORE);
                    break;

                case BULLSHIT:
                    donatePlayerScore(players, playerIdx, BULLSHIT_SCORE);
                    break;
            }
        }
    }
}

 

 

 

com.example.demo.utillity.basic.sixth

SpecialDiceOption

package com.example.demo.utillity.basic.sixth;

// 주사위를 굴릴 것임
// 근데 주사위 숫자에 규칙은 있을 수도 없을 수도 있고
// 특정 규칙이 존재한다면 해당 규칙은 상황에 따라 바뀔 수도 있을 것이다.
// A 게임사, B 게임사, C 게임사, D 카지노, E 카지노 등등

// 이렇게 규칙이 바뀌는 상황에서도 일관된 처리를 유지하고자 하는 경우
// 사용하는 방식이 바로 interface(인터페이스)에 해당한다.

import com.example.demo.entity.basic.sixth.Player;

import java.util.List;

// interface는 매서드 프로토타입만 작성한다.
// 프로토타입: 리턴타입, 매서드 이름, 파라메터
public interface SpecialDiceOption {
    public void checkSpecialDice(List<Player> players, int diceNum, int playerIdx);
}

 

 

 

com.example.demo.utillity.basic.third

CustomRandom

public class CustomRandom {

    public static int makeIntCustomRandom(int min, int max){
        Random random = new Random();
        return random.nextInt(max - min) + min;
    }
}
복사했습니다!