본문 바로가기
프로젝트/한글 게임

20. 난이도 분류

by 갱생angel 2024. 6. 14.

<추가/수정 사항>

※난이도를 상/중/하로 분류

※힌트 추가(글자 수, 연관 단어)

 

-백엔드-

 

model - <gameModel.js> 

const mongoose = require("mongoose");

const Schema = mongoose.Schema;

const gameSchema = new Schema({
  _id: { type: String, required: true }, 
  title: { type: String, require: true, unique: true },
  image: { type: String, require: true },
  level: { type: String, require: true }, //난이도
  length: { type: String, require: true }, //힌트
  hint: { type: String, require: true },
});

module.exports = mongoose.model("Game", gameSchema);

 

controller - <gameController.js>

//Get random Image, /image : 이미지 가져오기
let imageID = [];

const getImage = asynchHandler(async (req, res) => {
  const { level } = req.body; //프론트에서 level 값 받아오기
  if (imageID.length >= 10) {
    imageID = [];
    res.send({ message: "게임이 종료되었습니다." });
  } else {
    const game = await Game.aggregate([
      { $match: { _id: { $nin: imageID }, level: level } }, //레벨 지정
      { $sample: { size: 1 } },
    ]);
    imageID.push(game[0]._id);
    res.status(200).send({ game: game, count: imageID.length });
  }
  console.log(imageID);
});

//Post reset Image ID, /image/reset : 게임 초기화
const resetImageID = asynchHandler(async (req, res) => {
  imageID = [];
  res.status(200).send({ message: "게임 데이터가 초기화되었습니다." }); //오류 수정
});

//Post Image, /image : 이미지 데이터 등록
const postImage = asynchHandler(async (req, res) => {
  const { title, level, length, hint } = req.body;
  const existingTitle = await User.findOne({ title });
  if (existingTitle)
    return res.status(401).json({ message: "이미 저장된 데이터입니다." });
  const image = req.file.filename;
  await Game.create({ _id: title, title, image, level, length, hint }); //document 이름을 title로 지정
  res.status(201).send({ message: "등록되었습니다." });
});

 

route - <gameRoute.js>

const express = require("express");
const router = express.Router();
const {
  postCanvas,
  getImage,
  resetImageID,
  postImage,
  addImageScore,
  addCombineScore,
} = require("../controller/gameController");
const { upload } = require("../config/multer");
const { authUser } = require("../middleware/authMiddleware");

router.route("/canvas").post(postCanvas);
router.route("/game").post(upload.single("image"), postImage);
router.route("/gameData").post(getImage); //게임 데이터 클라이언트로 보내기
router.route("/game/reset").post(resetImageID);
router.route("/imageScore").post(authUser, addImageScore);
router.route("/combineScore").post(authUser, addCombineScore);

module.exports = router;

-프론트엔드-

 

page - Game - <ImageLevel.js>

import React, { useState } from "react";
import "../../css/game.css";
import ImageGame from "./ImageGame";
import axios from "axios";

function ImageLevel() {
  const [levelList, setLevelList] = useState(true); //레벨 버튼 state
  const [high, setHight] = useState(false); //상 state
  const [middle, setMiddle] = useState(false); //중 state
  const [low, setLow] = useState(false); //하 state

  const highGame = () => { //상 게임 함수
    axios.post("http://localhost:5000/game/reset"); //게임 초기화
    setHight(true); //상 게임 컴포넌트 활성화
    setLevelList(false); //난이도 버튼 비활성화
  };
  const middleGame = () => { //중 게임 함수 
    axios.post("http://localhost:5000/game/reset"); //게임 초기화
    setMiddle(true); //중 게임 컴포넌트 활성화
    setLevelList(false); //난이도 버튼 비활성화
  };
  const lowGame = () => { //하 게임 함수
    axios.post("http://localhost:5000/game/reset"); //게임 초기화
    setLow(true); //하 게임 컴포넌트 활성화
    setLevelList(false); //난이도 버튼 비활성화
  };

  return (
    <div className="imageGameContainer">
      <h1>이미지 게임</h1>
      {levelList && (
        <div className="levelBtn">
          <button onClick={highGame}>상</button>
          <button onClick={middleGame}>중</button>
          <button onClick={lowGame}>하</button>
        </div>
      )}
      {high && <ImageGame gameLevel={"상"} />}
      {middle && <ImageGame gameLevel={"중"} />}
      {low && <ImageGame gameLevel={"하"} />}
    </div>
  );
}

export default ImageLevel;

 

page - Game - <ImageGame.js>

import axios from "axios";
import React, { useCallback, useEffect, useState } from "react";
import "../../css/game.css";
import Canvas from "../../component/Canvas";
import Typing from "../../component/Typing";
import { useNavigate } from "react-router-dom";

function ImageGame({ gameLevel }) {
  const navigate = useNavigate();

  const winNum = 1; //서버로 보낼 점수 1점
  const [imageData, setImageData] = useState(""); //이미지, 텍스트 데이터
  const [quiz, setQuiz] = useState(""); //제시된 텍스트 퀴즈
  const [hint, setHint] = useState(""); //힌트
  const [length, setLength] = useState(""); //글자 수
  const [round, setRound] = useState(1); //라운드
  const [score, setScore] = useState(0); //점수
  const [gameOver, setGameOver] = useState(false); //게임 끝 여부
  const [checkQuiz, setCheckQuiz] = useState(false); //정답, 오답 확인
  const [moreChance, setMoreChance] = useState(0); //재도전 제공
  const [answerObj, setAnswerObj] = useState(false); //캔버스, 타이핑 변환
  const [answerObjName, setAnswerObjName] = useState("타이핑"); //캔버스, 타이핑 변환 버튼 이름
  const [answerObjButton, setAnswerObjButton] = useState(false); //캔버스, 타이핑 변환 버튼

  const checkTrue = () => {
    setCheckQuiz(true);
    setAnswerObjButton(true);
  };

  const resetGame = () => window.location.reload();

  const toggleAnswerObj = () => {
    setAnswerObj((answerObj) => !answerObj);
    setAnswerObjName((prev) => (prev === "타이핑" ? "캔버스" : "타이핑"));
  };

  const checkAnswer = (text) => {
    if (text === quiz) {
      alert("정답입니다.");
      checkTrue();
      setScore((score) => score + 10);
    } else {
      if (moreChance === 0) {
        alert("오답입니다. 한 번 더 시도해보세요.");
        setMoreChance((moreChance) => moreChance + 1);
      } else if (moreChance === 1) {
        alert("오답입니다. 다음 라운드로 넘어갑니다.");
        setMoreChance(0);
        checkTrue();
      }
    }
  };

  const fetchData = async () => {
    try {
      await axios
        .post("http://localhost:5000/gameData", { level: gameLevel }) //서버로 level 보내기
        .then((res) => {
          if (res.data.game && res.data.game.length > 0) {
            setImageData(res.data.game[0].image);
            setQuiz(res.data.game[0].title);
            setRound(res.data.count);
            setHint(res.data.game[0].hint);
            setLength(res.data.game[0].length);
            if (round >= 10) {
              setGameOver(true);
              alert(res.data.message);
            }
          } else {
            setGameOver(true);
            alert(res.data.message);
          }
        });
    } catch (err) {
      console.error(err);
    }
  };

  const updateScore = useCallback(async () => {
    const token = localStorage.getItem("token");
    const headerData = {
      headers: {
        Authorization: `Bearer ${token}`,
      },
      withCredentials: true,
    };
    try {
      await axios.post(
        "http://localhost:5000/imageScore",
        { imageScore: winNum },
        headerData
      );
    } catch (err) {
      if (err.response.status === 401) {
        try {
          const refreshRes = await axios.post(
            "http://localhost:5000/refresh",
            {},
            { withCredentials: true }
          );
          const newToken = refreshRes.data.token;
          localStorage.setItem("token", newToken);
          axios.defaults.headers.common["Authorization"] = `Bearer ${newToken}`;
          updateScore();
        } catch (err) {
          console.error(err);
          localStorage.removeItem("token");
        }
      } else {
        console.error(err);
        localStorage.removeItem("token");
      }
    }
  }, []);

  useEffect(() => {
    fetchData();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [winNum]);

  useEffect(() => {
    if (score >= 100 && localStorage.getItem("token")) updateScore();
  }, [score, updateScore]);

  return (
    <div className="imageGameContainer">
      <div className="imageDiv">
        {gameOver ? (
          <div>
            <h1>Game Over, 점수: {score} / 100</h1>
            <button onClick={resetGame}>난이도 선택</button>
            <button onClick={() => navigate("/")}>홈으로</button>
          </div>
        ) : (
          <div>
            <div className="roundDiv">
              <h2>Round: {round} / 10</h2>
              <button onClick={toggleAnswerObj} disabled={answerObjButton}>
                {answerObjName}
              </button>
            </div>
            {imageData ? ( //레이아웃 변경을 방지에서 CLS의 성능을 높임
              <img
                alt="이미지"
                src={`http://localhost:5000/file/${imageData}`}
              />
            ) : (
              <div style={{ width: "500px", height: "300px" }}></div>
            )}
          </div>
        )}
      </div>
      {!gameOver && (
        <div>
          <h2>
            글자 수: {length}, 힌트: {hint} <!--힌트 추가-->
          </h2>
          {answerObj ? (
            <Typing
              checkAnswer={checkAnswer}
              fetchData={fetchData}
              quiz={quiz}
              checkQuiz={checkQuiz}
              setCheckQuiz={setCheckQuiz}
              setAnswerObjButton={setAnswerObjButton}
            />
          ) : (
            <Canvas
              checkAnswer={checkAnswer}
              fetchData={fetchData}
              quiz={quiz}
              checkQuiz={checkQuiz}
              setCheckQuiz={setCheckQuiz}
              setAnswerObjButton={setAnswerObjButton}
            />
          )}
        </div>
      )}
    </div>
  );
}

export default ImageGame;

 

page - Game - <CombineLevel.js >

import React, { useState } from "react";
import "../../css/game.css";
import CombineGame from "./CombineGame";
import axios from "axios";

function CombineLevel() {
  const [levelList, setLevelList] = useState(true); //레벨 버튼 state
  const [high, setHight] = useState(false); //상 state
  const [middle, setMiddle] = useState(false); //중 state
  const [low, setLow] = useState(false); //하 state

  const highGame = () => { //상 게임 함수
    axios.post("http://localhost:5000/game/reset"); //게임 초기화
    setHight(true); //상 게임 컴포넌트 활성화
    setLevelList(false); //난이도 버튼 비활성화
  };
  const middleGame = () => { //중 게임 함수 
    axios.post("http://localhost:5000/game/reset"); //게임 초기화
    setMiddle(true); //중 게임 컴포넌트 활성화
    setLevelList(false); //난이도 버튼 비활성화
  };
  const lowGame = () => { //하 게임 함수
    axios.post("http://localhost:5000/game/reset"); //게임 초기화
    setLow(true); //하 게임 컴포넌트 활성화
    setLevelList(false); //난이도 버튼 비활성화
  };

  return (
    <div className="imageGameContainer">
      <h1>낱말 조합</h1>
      {levelList && (
        <div className="levelBtn">
          <button onClick={highGame}>상</button>
          <button onClick={middleGame}>중</button>
          <button onClick={lowGame}>하</button>
        </div>
      )}
      {high && <CombineGame gameLevel={"상"} />}
      {middle && <CombineGame gameLevel={"중"} />}
      {low && <CombineGame gameLevel={"하"} />}
    </div>
  );
}

export default CombineLevel;

 

page - Game - <CombineGame.js>

import axios from "axios";
import React, { useCallback, useEffect, useState } from "react";
import "../../css/game.css";
import Canvas from "../../component/Canvas";
import Typing from "../../component/Typing";
import { CHO, JUNG, JONG } from "../../component/Word";
import { useNavigate } from "react-router-dom";

function CombineGame({ gameLevel }) {
  const navigate = useNavigate();

  const winNum = 1; //서버로 보낼 점수 1점
  const [charArray, setCharArray] = useState([]); //랜덤 문자 배열
  const [quiz, setQuiz] = useState(""); //제시된 텍스트 퀴즈
  const [hint, setHint] = useState(""); //힌트
  const [length, setLength] = useState(""); //글자 수
  const [round, setRound] = useState(1); //라운드
  const [score, setScore] = useState(0); //점수
  const [gameOver, setGameOver] = useState(false); //게임 끝 여부
  const [checkQuiz, setCheckQuiz] = useState(false); //정답, 오답 확인
  const [moreChance, setMoreChance] = useState(0); //재도전 제공
  const [answerObj, setAnswerObj] = useState(false); //캔버스, 타이핑 변환
  const [answerObjName, setAnswerObjName] = useState("타이핑"); //캔버스, 타이핑 변환 버튼 이름
  const [answerObjButton, setAnswerObjButton] = useState(false); //캔버스, 타이핑 변환 버튼

  //초성, 중성, 종성 분리
  const separateText = () => {
    const result = [];
    for (let char of quiz) {
      const unicode = char.charCodeAt(0) - 44032;
      const choIndex = parseInt(unicode / 588);
      const jungIndex = parseInt((unicode - choIndex * 588) / 28);
      const jongIndex = parseInt(unicode % 28);
      const choChar = CHO[choIndex];
      const jungChar = JUNG[jungIndex];
      const jongChar = JONG[jongIndex];
      result.push(choChar, jungChar, jongChar);
    }
    return result;
  };

  const resetButton = () => window.location.reload();

  const checkTrue = () => {
    setCheckQuiz(true);
    setAnswerObjButton(true);
  };

  const toggleAnswerObj = () => {
    setAnswerObj((answerObj) => !answerObj);
    setAnswerObjName((prev) => (prev === "타이핑" ? "캔버스" : "타이핑"));
  };

  const checkAnswer = (text) => {
    if (text === quiz) {
      alert("정답입니다.");
      checkTrue();
      setScore((score) => score + 10);
    } else {
      if (moreChance === 0) {
        alert("오답입니다. 한 번 더 시도해보세요.");
        setMoreChance((moreChance) => moreChance + 1);
      } else if (moreChance === 1) {
        alert("오답입니다. 다음 라운드로 넘어갑니다.");
        setMoreChance(0);
        checkTrue();
      }
    }
  };

  const fetchData = async () => {
    try {
      await axios
        .post("http://localhost:5000/gameData", { level: gameLevel }) //서버로 level 보내기
        .then((res) => {
          if (res.data.game && res.data.game.length > 0) {
            setQuiz(res.data.game[0].title);
            setRound(res.data.count);
            setHint(res.data.game[0].hint);
            setLength(res.data.game[0].length);
            if (round >= 10) {
              setGameOver(true);
              alert(res.data.message);
            }
          } else {
            setGameOver(true);
            alert(res.data.message);
          }
        });
    } catch (err) {
      console.error(err);
    }
  };

  const updateScore = useCallback(async () => {
    const token = localStorage.getItem("token");
    const headerData = {
      headers: {
        Authorization: `Bearer ${token}`,
      },
      withCredentials: true,
    };
    try {
      await axios.post(
        "http://localhost:5000/combineScore",
        { combineScore: winNum },
        headerData
      );
    } catch (err) {
      if (err.response.status === 401) {
        try {
          const refreshRes = await axios.post(
            "http://localhost:5000/refresh",
            {},
            { withCredentials: true }
          );
          const newToken = refreshRes.data.token;
          localStorage.setItem("token", newToken);
          axios.defaults.headers.common["Authorization"] = `Bearer ${newToken}`;
          updateScore();
        } catch (err) {
          console.error(err);
          localStorage.removeItem("token");
        }
      } else {
        console.error(err);
        localStorage.removeItem("token");
      }
    }
  }, [winNum]);

  useEffect(() => {
    fetchData();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    setCharArray(
      separateText()
        .sort(() => Math.random() - 0.5)
        .filter((char) => char !== "")
    );
    //eslint-disable-next-line react-hooks/exhaustive-deps
  }, [quiz]);

  useEffect(() => {
    if (score >= 100 && localStorage.getItem("token")) updateScore();
  }, [score, updateScore]);

  return (
    <div className="combineGameContainer">
      <div className="combineDiv">
        {gameOver ? (
          <div>
            <h1>Game Over, 점수: {score} / 100</h1>
            <button onClick={resetButton}>다시하기</button>
            <button onClick={() => navigate("/")}>홈으로</button>
          </div>
        ) : (
          <div>
            <div className="roundDiv">
              <h2>Round: {round} / 10</h2>
              <button onClick={toggleAnswerObj} disabled={answerObjButton}>
                {answerObjName}
              </button>
            </div>
            <div className="textQuizDiv">
              <span>{charArray.join(" , ")}</span>
            </div>
          </div>
        )}
      </div>
      {!gameOver && (
        <div>
          <h2>
            글자 수: {length}, 힌트: {hint} <!--힌트 추가-->
          </h2>
          {answerObj ? (
            <Typing
              checkAnswer={checkAnswer}
              fetchData={fetchData}
              quiz={quiz}
              checkQuiz={checkQuiz}
              setCheckQuiz={setCheckQuiz}
              setAnswerObjButton={setAnswerObjButton}
            />
          ) : (
            <Canvas
              checkAnswer={checkAnswer}
              fetchData={fetchData}
              quiz={quiz}
              checkQuiz={checkQuiz}
              setCheckQuiz={setCheckQuiz}
              setAnswerObjButton={setAnswerObjButton}
            />
          )}
        </div>
      )}
    </div>
  );
}

export default CombineGame;

 

<App.js>

import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Nav from "./component/Nav";
import Login from "./page/User/Login";
import Register from "./page/User/Register";
import Home from "./page/Home";
import ImageRegist from "./page/Game/ImageRegist";
import IdFind from "./page/User/ID/IdFind";
import PwdFind from "./page/User/PWD/PwdFind";
import MyPage from "./page/User/MyPage";
import LearningPage from "./page/Learn/LearningPage";
import Footer from "./component/footer";
import "bootstrap/dist/css/bootstrap.min.css";
import Introduce from "./page/introduce";
import ImageLevel from "./page/Game/ImageLevel";
import CombineLevel from "./page/Game/CombineLevel";

function App() {
  return (
    <div>
      <BrowserRouter>
        <Nav />
        <Routes>
          <Route index element={<Home />} />
          <Route path="/login" element={<Login />} />
          <Route path="/find_id" element={<IdFind />} />
          <Route path="/find_pwd" element={<PwdFind />} />
          <Route path="/register" element={<Register />} />
          <Route path="/mypage" element={<MyPage />} />
          <Route path="/image/add" element={<ImageRegist />} />
          <Route path="/imageGame" element={<ImageLevel />} /> <!--이미지 게임 레벨 지정-->
          <Route path="/combineGame" element={<CombineLevel />} /> <!--낱말 조합 레벨 지정-->
          <Route path="/learn" element={<LearningPage />} />
          <Route path="/introduce" element={<Introduce />} />
        </Routes>
      </BrowserRouter>
      <Footer />
    </div>
  );
}

export default App;

 

css - <game.css>

.levelBtn button {
  background-color: #a0cbe7;
  color: white;
  border: none;
  border-radius: 4px;
  padding: 10px 30px;
  cursor: pointer;
  font-size: 16px;
  margin: 5px;
}
.levelBtn button:hover {
  background-color: #8cb4d6;
}

'프로젝트 > 한글 게임' 카테고리의 다른 글

19. 종성 ' ' 제외, 쉼표(,) 추가  (0) 2024.06.07
18. 초성/중성/종성 파일 모듈화  (0) 2024.05.20
17. 포인트 기능  (0) 2024.05.11
16. 무작위 나열  (0) 2024.04.29
15. 초성/중성/종성 분리  (0) 2024.04.29