본문 바로가기
프로젝트/숫자야구게임

[JS] 숫자야구게임 기능 구현하기 (2) - MVC 패턴 적용

by zldn 2025. 3. 22.

<MVC 패턴> - Model, View, Controller로 구성된 디자인패턴

- Model 

-> 게임 기능에 관한 로직을 처리한다.

-> 결과값을 Controller에게 전달한다.

-> view와 Controller에 대한 정보를 가지고 있지 않는다.

- View

-> UI를 보여주는 역할을 한다.

-> 이때 모델이 처리한 결과를 가지고 화면을 출력하며 이 데이터는 Controller에서 받아온다.

- Controller

-> 사용자의 입력에 반응한다.
-> Model과 View 사이에 존재하며 데이터를 전달한다

 

<MVC 패턴 적용한 코드>

- 이전 코드

우선 지난번에 MVC는 고려하지 않고 기능만 고려를 해서 더럽게 짰던 코드를 수정하여 바꿔보도록 하겠다. 아래 링크로 가면 이 전 코드를 확인할 수 있다.

https://zldn.tistory.com/86

 

[JS] 숫자야구게임 기능 구현하기 (1)

그동안 제대된 동아리나 협업활동을 해본적이 없었는데 올해 프론트엔드 분야로 공부를 해보고 싶어 새롭게 동아리를 들어갔다. 항상 혼자서 이리저리 조금씩 껄떡대며 공부를 하긴 했었지만

zldn.tistory.com

 

- 전반적인 수정 사항

> 함수의 이름이나 변수를 조금 더 기능이 명확하게 보이게 함수명을 바꾸고 또한 기능에 맞춰 세분화하여 변경

> 게임 진행관련과 이벤트리스너는 Controller(GameController.js)로 모두 이동

> 게임 로직과 관련된 것들은 Model(BaseballGame.js)로 이동

> 화면에 출력하는 것들은 View(GameView.js)로 이동
> Model에서 결과값을 Controller로 반환하고 Controller에서 그 결과값을 View로 보내도록 수정

 

- MVC를 적용하여 작성한 흐름

1. index.js에서는 Controller를 호출하는 역할만 한다.

//실행(index.js)
import { gameStart } from "./GameController.js"

gameStart();

 

2. Controller가 호출되면 게임을 초기화하고 Model과 View의 인스턴스를 생성한다.

//GameController.js

import BaseballGame from "./BaseballGame.js";
import GameView from "./GameView.js"

let game;
let view;

export function gameStart() {

    game = new BaseballGame();
    view = new GameView();

    view.resetGameUI();
    setEventListeners();
}

 

3. Controller에서 이벤트리스너를 이용해 사용자입력과 게임 재시작 이벤트를 처리한다.

//GameController.js

function setEventListeners() {
    const form = document.querySelector("form");
    form.addEventListener("submit", processUserInput);

    const replay = document.querySelector("#game-restart-button");
    replay.addEventListener("click", gameStart);
}

 

4. 사용자가 입력을 제출하면 Controller는 유저인풋 값을 Model에 전달한다. 

//GameController.js

function processUserInput(event) {
    event.preventDefault();
    let userInput = document.getElementById("user-input").value;
    let comparisionResult = game.handleUserInput(userInput);
    if (comparisionResult === "3스트라이크") {
        view.displaySuccessMessage();
    } else {
        view.displayResultMessage(comparisionResult);
    }
}

 

5. Model은 유저인풋값이 조건에 맞는지 확인하고 랜덤정수(정답)과의 비교를 통해 결과 메세지를 생성하여 Controller에 반환한다.

-> 이때 BaseballGame은 class로 묶어서 구성했다.

//Model
export default class BaseballGame {
    constructor() {
        this.computerNumbers = this.getRandomNumberString();
    }

    getRandomNumberString() {
        let randomNumber = [];
        while (randomNumber.length < 3) {
            let num = MissionUtils.Random.pickNumberInRange(1, 9);
            if (!randomNumber.includes(num)) {
                randomNumber.push(num);
            }
        }
        randomNumber = randomNumber.join("");
        return randomNumber;
    }

    handleUserInput(userInput) {

        if (this.checkUserInput(userInput)) {
            return;
        }

        const comparisionResult = this.compareInputWithAnswer(userInput);
        return comparisionResult;
    }

    compareInputWithAnswer(userInput) {
        let strikeCount = 0, ballCount = 0;

        for (let i = 0; i < 3; i++) {
            if (userInput[i] === this.computerNumbers[i]) {  //스트라이크
                strikeCount++;
            }
            else if (this.computerNumbers.includes(userInput[i])) {   //볼
                ballCount++;
            }
        }

        return this.play(strikeCount, ballCount);
    }

    play(strikeCount, ballCount) {
        let resultMessage = "";

        if (ballCount > 0) {
            resultMessage += `${ballCount}볼 `;
        }
        if (strikeCount > 0) {
            resultMessage += `${strikeCount}스트라이크`;
        }
        if (ballCount == 0 && strikeCount == 0) {
            resultMessage = "낫싱";
        }
        return resultMessage;
    }

    checkUserInput(userInput) {
        const isNotNumber = isNaN(userInput);
        if (isNotNumber) {
            alert("잘못된 값을 입력했습니다.\n숫자를 입력해주세요.");
            return true;
        }
        else if (userInput.length !== 3) {
            alert("잘못된 값을 입력했습니다.\n세자리 숫자를 입력해주세요");
            return true;
        }
        else if (new Set(userInput).size !== 3) {
            alert("잘못된 값을 입력했습니다.\n중복되지않는 세자리 숫자를 입력해주세요.");
            return true;
        }
        return false;
    }
}

 

6. Controller는 반환받은 결과 메세지를 View에 전달한다.

//Controller.js의 processUserInput(event)함수 내부의 일부

if (comparisionResult === "3스트라이크") {
        view.displaySuccessMessage();
    } else {
        view.displayResultMessage(comparisionResult);
}

 

7. View는 Controller로부터 Model의 결과값을 전달받아 화면을 출력한다.
-> 이때 View는 class로 묶어서 구성했다.

//GameView.js

export default class GameView {
    displaySuccessMessage() {
        document.querySelector("#result").style.display = "none";
        document.querySelector(".success").style.display = "block";

        const submitBtn = document.querySelector("#submit");
        submitBtn.disabled = true;
    }

    resetGameUI() {
        const submitBtn = document.querySelector("#submit");
        submitBtn.disabled = false;

        document.querySelector(".success").style.display = "none";
    }

    displayResultMessage(comparisionResult) {
        let gameResult = document.querySelector("#result");
        gameResult.style.display = "block";
        gameResult.textContent = comparisionResult;
    }
}

 

8. 정답을 맞추게 되면 Controller에서 이벤트리스너를 이용하여 재시작버튼에 대한 클릭이벤트를 감지하고 게임을 다시 실행하도록 한다.

//GameControlller.js에서 setEventListeneners() 함수 내의 일부

const replay = document.querySelector("#game-restart-button");
replay.addEventListener("click", gameStart);

 

'프로젝트 > 숫자야구게임' 카테고리의 다른 글

[JS] 숫자야구게임 기능 구현하기 (1)  (0) 2025.03.22