[Vue3/Typescript] 가위 바위 보 게임 만들기 – 3. 게임만들기

이제 제목과 같은 게임을 만들어보자.

https://www.youtube.com/watch?v=rhZpF8H66RI&list=PLZzSdj89sCN38KeadQVtiha8gZ-KGpTOC

먼저 이번 소스는 위의 영상에서 소개하는 내용을 Vue3/TypeScript 환경에 맞게 컨버팅 한 것이다. 소스의 흐름은 위와 동일하니 한 번쯤 위의 영상을 보고 온다면 더 쉽게 이해가 될 것이다. 개인적으로 뷰 처음 접했을 때 해당 채널의 영상들이 많이 도움이 되어서 초심자들에게 추천하는 채널이다.

● main.vue

<script setup lang="ts">
import { reactive, computed, watch } from "vue";
// import type { Ref } from "vue";
import { Log } from "~/interfaces/Main";
import { storeToRefs } from "pinia";
import { useMainStore } from "~/store/main.ts";

const store = useMainStore();
const {
  myChoice,
  comChoice,
  count,
  winner,
  lifeOfMe,
  lifeOfCom,
  isSelectable,
  logs,
} = storeToRefs(store);
// 스토어 사용 안할시
// const myChoice: Ref<Nullable<string>> = ref(null);
// const comChoice: Ref<Nullable<string>> = ref(null);
// const count: Ref<number> = ref(3);
// const winner: Ref<Nullable<string>> = ref(null);
// const lifeOfMe: Ref<number> = ref(3);
// const lifeOfCom: Ref<number> = ref(3);
// const isSelectable: Ref<boolean> = ref(true);
// const logs: Log[] = reactive([]);

const selects = reactive([
  { name: "가위", value: "scissor" },
  { name: "바위", value: "rock" },
  { name: "보", value: "paper" },
]);

const myChoiceImg = computed(() => {
  return myChoice.value !== null
    ? `images/${myChoice.value}.jpg`
    : "images/question.jpg";
});
const comChoiceImg = computed(() => {
  return comChoice.value !== null
    ? `images/${comChoice.value}.jpg`
    : "images/question.jpg";
});
const leftLifeOfMe = computed(() => {
  return 3 - lifeOfMe.value;
});
const leftLifeOfCom = computed(() => {
  return 3 - lifeOfCom.value;
});

watch(
  () => count.value,
  (newVal) => {
    if (newVal === 0) {
      // 컴퓨터가 가위바위보를 선택하는
      selectCom();

      // 가위바위보 승패 결정 & 몫을 차감
      whoIsWin();

      // 게임 리셋
      count.value = 3;
      isSelectable.value = true;

      // 로그를 업데이트 하는 부분
      updateLogs();
    }
  }
);

watch(lifeOfMe, async (newVal) => {
  if (newVal === 0) {
    // 게임을 종료
    endGame("안타깝네요. 당신이 패배하였습니다.");
  }
});

watch(lifeOfCom, async (newVal) => {
  if (newVal === 0) {
    endGame("축하드립니다. 당신이 승리하였습니다.");
  }
});

function startGame() {
  // 버튼이 보이지 않음
  isSelectable.value = false;
  if (myChoice.value === null) {
    alert("가위 바위 보 중 하나를 선택해주세요.");
    isSelectable.value = true;
  } else {
    let countDown = setInterval(() => {
      count.value--;
      if (count.value === 0) {
        clearInterval(countDown);
      }
    }, 1000);
  }
}
function selectCom() {
  let number = Math.random();
  if (number < 0.33) {
    comChoice.value = "scissor";
  } else if (number < 0.66) {
    comChoice.value = "rock";
  } else {
    comChoice.value = "paper";
  }
}
function whoIsWin() {
  if (myChoice.value === comChoice.value) winner.value = "no one";
  else if (myChoice.value === "rock" && comChoice.value === "scissor")
    winner.value = "me";
  else if (myChoice.value === "scissor" && comChoice.value === "paper")
    winner.value = "me";
  else if (myChoice.value === "paper" && comChoice.value === "rock")
    winner.value = "me";
  else if (myChoice.value === "scissor" && comChoice.value === "rock")
    winner.value = "com";
  else if (myChoice.value === "paper" && comChoice.value === "scissor")
    winner.value = "com";
  else if (myChoice.value === "rock" && comChoice.value === "paper")
    winner.value = "com";
  else winner.value = "error";

  // 몫 차감
  if (winner.value === "me") {
    lifeOfCom.value--;
  } else if (winner.value === "com") {
    lifeOfMe.value--;
  }
}
function updateLogs() {
  let log: Log = {
    message: `You: ${myChoice.value}, Computer: ${comChoice.value}`,
    winner: winner.value,
  };

  store.addLog(log);
}

function endGame(msg: string) {
  setTimeout(() => {
    confirm(msg);
    lifeOfMe.value = 3;
    lifeOfCom.value = 3;
    myChoice.value = null;
    comChoice.value = null;
    winner.value = null;
    store.resetLog(); // logs 변수 초기화 []
  }, 500);
}
</script>
<template>
  <div class="row">
    <div class="small-5 columns text-center">
      <img :src="myChoiceImg" alt="" class="text-center" />
      <h1 class="text-center"><strong>YOU</strong></h1>
    </div>
    <div class="small-2 columns text-center">
      <h1 style="font-size: 100px">
        <strong>{{ count }}</strong>
      </h1>
    </div>
    <div class="small-5 columns text-center">
      <img :src="comChoiceImg" alt="" class="text-center" />
      <h1 class="text-center"><strong>Computer</strong></h1>
    </div>
  </div>

  <div class="row">
    <div class="small-6 columns text-center">
      <div class="battle-wrap">
        <img
          v-for="life in lifeOfMe"
          :key="'me_live_' + life"
          src="/images/heart.jpg"
          class="heart"
          alt=""
        />
        <img
          v-for="life in leftLifeOfMe"
          :key="'me_broken_' + life"
          src="/images/broken-heart.jpg"
          class="heart"
          alt=""
        />
      </div>
    </div>
    <div class="small-6 columns text-center">
      <div class="battle-wrap">
        <img
          v-for="life in lifeOfCom"
          :key="'come_live_' + life"
          src="/images/heart.jpg"
          class="heart"
          alt=""
        />
        <img
          v-for="life in leftLifeOfCom"
          :key="'com_broken_' + life"
          src="/images/broken-heart.jpg"
          class="heart"
          alt=""
        />
      </div>
    </div>
  </div>

  <div class="row">
    <div class="small-6 columns text-center">
      <div class="row">
        <div class="small-8 small-offset-2 columns text-center">
          <label v-for="select in selects" class="radio-label">
            <input type="radio" v-model="myChoice" :value="select.value" />
            {{ select.name }}
          </label>
        </div>
      </div>
      <div class="row">
        <div class="small-12 columns">
          <div class="text-center" v-if="isSelectable">
            <button class="start-btn" @click="startGame()">선택 완료!</button>
          </div>
          <div v-else class="loading">기다리는 중...</div>
        </div>
      </div>
    </div>
    <div class="small-6 columns text-center">
      <p>생각 중...</p>
    </div>
  </div>

  <div class="row">
    <div class="small-12 columns log">
      <ul>
        <li
          :class="{
            'win-log': log.winner === 'me',
            'defeat-log': log.winner === 'com',
            'draw-log': log.winner === 'no one',
          }"
          v-for="log in logs"
        >
          {{ log.message }}
        </li>
      </ul>
    </div>
  </div>
</template>

<style>
@import "~/assets/css/app.css";
@import "~/assets/css/foundation.min.css";
</style>

 

 

● main.ts

import { createApp } from "vue";
import App from "./App.vue";
import { setupStore } from "~/store";
import { router } from "~/router";
import axiosPlugin from "~/plugins/axios";

const app = createApp(App);

setupStore(app);

app.use(router);
app.use(axiosPlugin);

// env variable get
console.log(import.meta.env.VITE_APP_TEST_KEY);

app.mount("#app");

 

원본 소스와 조금 달라진 점은 store적용을 한 부분이다. 이제 새로 고침을 해도 게임 상태가 유지된다.


소스

https://github.com/junseongday/vue3-rock-paper-scissors

Leave A Reply

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다