
주사위 게임 만들기🎲
📑문제
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;
}
}
'Backend > Spring & JPA' 카테고리의 다른 글
[Spring] Vue + JPA + Spring 연동 시 Spring 구조 이해하기 (0) | 2022.09.19 |
---|---|
[Spring] 220729 7차 과제 : 로또 번호 배분/추첨/당첨자 발표 시스템 🎰 (0) | 2022.08.01 |
[Spring] 220724 4차 과제 : 문제 은행[2],[4] 풀기 (0) | 2022.08.01 |
[Spring] 220721 3차 과제 : 학생들의 점수를 관리하고 평균 값을 내는 프로그램 (0) | 2022.07.24 |
[Spring] 220720 2차 과제 : 주사위 값을 화면에 출력하기! (0) | 2022.07.24 |