이제 제목과 같은 게임을 만들어보자.
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적용을 한 부분이다. 이제 새로 고침을 해도 게임 상태가 유지된다.
소스