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

13. 캔버스/타이핑 변환

by 갱생angel 2024. 4. 26.

답 제출 수단을 캔버스 또는 타이핑으로 선택할 수 있도록 수정

  -<Typing.js> 파일 추가

  -CSS로 페이지 구성

  -정답 맞췄을 시 캔버스-타이핑 변환 버튼 비활성화

  -다음 레벨로 넘어갈 시 캔버스-타이핑 변환 버튼 활성화

 

page - Game - <Typing.js> : 타이핑 기능

import React, { useState } from "react";

function Typing({ checkAnswer, quiz, newFetch, setAnswerObjButton }) {
  const [typing, setTyping] = useState("");
  const [checkQuiz, setCheckQuiz] = useState(false);

  const changeTyping = (e) => setTyping(e.target.value);

  const sumbitAnswer = () => {
    checkAnswer(typing);
  };

  const nextLevel = () => {
    newFetch();
    setCheckQuiz(false);
    setTyping("");
    setAnswerObjButton(false); //캔버스-타이핑 변환 버튼 활성화
  };

  return (
    <div className="typingContainer">
      {checkQuiz ? (
        <div>
          <h3>
            정답: [{quiz}], 제출한 답: [{typing}]
          </h3>
          <button onClick={nextLevel}>다음 레벨</button>
        </div>
      ) : (
        <div>
          <input value={typing} onChange={changeTyping} />
          <button onClick={sumbitAnswer}>제출</button>
        </div>
      )}
    </div>
  );
}

export default Typing;

 

page - Game - <Canvas.js> : 타이핑 기능

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

function Canvas({ checkAnswer, newFetch, quiz, setAnswerObjButton }) {
  const canvasRef = useRef(null);

  const [isDrawing, setIsDrawing] = useState(false);
  const [checkQuiz, setCheckQuiz] = useState(false);

  const [lastX, setLastX] = useState(0);
  const [lastY, setLastY] = useState(0);

  const [outputImageSrc, setOutputImageSrc] = useState(null);

  const [path, setPath] = useState([]);
  const [paths, setPaths] = useState([]);

  const [imgText, setImgText] = useState("");

  useEffect(() => {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext("2d");

    const drawing = (e) => {
      if (!isDrawing) return;

      const rect = canvas.getBoundingClientRect();
      const offsetX = e.clientX - rect.left;
      const offsetY = e.clientY - rect.top;

      ctx.beginPath();
      ctx.moveTo(lastX, lastY);
      ctx.lineTo(offsetX, offsetY);
      ctx.strokeStyle = "blue";
      ctx.lineWidth = 10;
      ctx.stroke();

      setLastX(offsetX);
      setLastY(offsetY);

      setPath((prevPath) => [...prevPath, { x: offsetX, y: offsetY }]);
    };

    canvas.addEventListener("mousemove", drawing);

    return () => {
      canvas.removeEventListener("mousemove", drawing);
    };
  }, [isDrawing, lastX, lastY]);

  const drawingCanvas = (e) => {
    setIsDrawing(true);
    const rect = e.target.getBoundingClientRect();
    setLastX(e.clientX - rect.left);
    setLastY(e.clientY - rect.top);
    setPath([{ x: e.clientX - rect.left, y: e.clientY - rect.top }]);
  };

  const stopDrawing = () => {
    if (isDrawing) {
      setPaths((prevPaths) => [...prevPaths, path]);
      setPath([]);
    }
    setIsDrawing(false);
  };

  const canvasOut = () => {
    setIsDrawing(false);
  };

  const outputCanvasImage = async () => {
    const canvas = canvasRef.current;
    setOutputImageSrc(canvas.toDataURL());
    const dataURL = canvas.toDataURL("image/png");
    await axios
      .post("http://localhost:5000/canvas", { dataURL: dataURL })
      .then((res) => {
        setImgText(res.data.text); 
        checkAnswer(res.data.text); 
      });
    setCheckQuiz(true);
  };

  const clearCanvas = () => {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext("2d");
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    setOutputImageSrc(null);
    setPaths([]);
    setImgText("");
  };

  const returnCurrentLine = () => {
    setPaths((prevPaths) => prevPaths.slice(0, -1));
  };

  const redrawCanvas = () => {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext("2d");
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    paths.forEach((p) => {
      ctx.beginPath();
      ctx.moveTo(p[0].x, p[0].y);
      for (let i = 1; i < p.length; i++) {
        ctx.lineTo(p[i].x, p[i].y);
      }
      ctx.stroke();
    });
  };

  useEffect(redrawCanvas, [paths]);

  const nextLevel = () => { 
    newFetch(); 
    clearCanvas();
    setCheckQuiz(false);
    setAnswerObjButton(false) //캔버스-타이핑 변환 버튼 활성화
  };

  return (
    <div className="canvasContainer">
      <canvas
        ref={canvasRef}
        width={500}
        height={200}
        style={{ border: "1px solid black" }}
        onMouseDown={drawingCanvas}
        onMouseUp={stopDrawing}
        onMouseOut={canvasOut}
      />
      <div className="canvasButtonDiv">
        {checkQuiz ? (
          <div>
            <h3>
              정답: [{quiz}], 제출한 답: [{imgText}]
            </h3>
            <button onClick={nextLevel}>다음 레벨</button>
          </div>
        ) : (
          <div>
            <button onClick={outputCanvasImage}>제출</button>
            <button onClick={clearCanvas}>다시 쓰기</button>
            <button onClick={returnCurrentLine}>한 획 지우기</button>
          </div>
        )}
      </div>
    </div>
  );
}

export default Canvas;

 

page - Game - <ImageGame.js>

import axios from "axios";
import React, { useEffect, useState } from "react";
import "../../css/game.css";
import Canvas from "./Canvas";
import Typing from "./Typing";

function ImageGame() {
  const [imageData, setImagaData] = useState([]);

  const [quiz, setQuiz] = useState("");
  const [count, setCount] = useState(1);

  const [gameOver, setGameOver] = useState(false);
  const [checkQuiz, setCheckQuiz] = useState(false);

  const [answerObj, setAnswerObj] = useState(false); //false: 캔버스, true: 타이핑
  const [answerObjName, setAnswerObjName] = useState("타이핑"); //변환 버튼 이름
  const [answerObjButton, setAnswerObjButton] = useState(false); //버튼 활성화 유무

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

  const changeAnswerObj = () => { //캔버스-타이핑 변환
    setAnswerObj((answerObj) => !answerObj);
    if (answerObjName === "타이핑") {
      setAnswerObjName("캔버스");
    } else {
      setAnswerObjName("타이핑");
    }
  };

  const checkAnswer = (text) => {
    if (text === quiz) {
      alert("정답입니다.");
      setAnswerObjButton(true); //정답 일시 변환 버튼 비활성화
    } else {
      alert("오답입니다.");
    }
  };

  const newFetch = () => {
    fetchData();
  };

  const fetchData = () => {
    axios.get("http://localhost:5000/image").then((res) => {
      if (res.data.game && res.data.game.length > 0) {
        setImagaData(res.data.game[0].image);
        setQuiz(res.data.game[0].title);
        setCount(res.data.count);
        if (count >= 5) {
          setGameOver(true);
          alert(res.data.message);
        }
      } else {
        setGameOver(true);
        alert(res.data.message);
      }
    });
  };

  useEffect(() => {
    fetchData();
  }, []);

  return (
    <div className="imageGameContainer">
      <div className="imageDiv">
        {gameOver ? (
          <div>
            <h1>Game Over</h1>
            <button onClick={resetButton}>다시하기</button>
          </div>
        ) : (
          <div>
            <div className="roundDiv">
              <h2>Round: {count} / 5</h2>
              <button onClick={changeAnswerObj} disabled={answerObjButton}>
              	{answerObjName}
              </button>
            </div>
            <img alt="이미지" src={`http://localhost:5000/file/${imageData}`} />
          </div>
        )}
      </div>
      {!gameOver && (
        <div>
          {answerObj ? (
            <div>
              <Typing
                checkAnswer={checkAnswer}
                newFetch={newFetch}
                quiz={quiz}
                setAnswerObjButton={setAnswerObjButton}
              />
            </div>
          ) : (
            <div>
              <Canvas
                checkAnswer={checkAnswer}
                newFetch={newFetch}
                quiz={quiz}
                setAnswerObjButton={setAnswerObjButton}
              />
            </div>
          )}
        </div>
      )}
    </div>
  );
}

export default ImageGame;

 

css - <game.css>

//이미지 게임 전체 구성
.imageGameContainer {
  width: 500px;
  margin: 0 auto;
}
.imageDiv {
  text-align: center;
  margin: 0 auto;
}
.imageDiv h2 {
  margin-left: 100px;
  margin-right: 50px;
}
.imageDiv img {
  border: 1px solid black;
  width: 500px;
  height: 350px;
  object-fit: fill;
}
.imageDiv button {
  width: 100px;
  height: 30px;
  font-size: 15px;
  font-weight: bold;
  border: 1px solid black;
  background-color: white;
  margin-right: 10px;
}
.imageDiv button:hover {
  color: white;
  background-color: black;
}
.roundDiv {
  display: flex;
  align-items: center;
}

//캔버스 구성
.canvasContainer button {
  width: 100px;
  height: 30px;
  font-size: 15px;
  font-weight: bold;
  border: 1px solid black;
  background-color: white;
  margin-right: 10px;
}
.canvasContainer button:hover {
  color: white;
  background-color: black;
}
.canvasButtonDiv {
  width: 100%;
  text-align: center;
  margin: 0 auto;
}

//타이핑 구성
.typingContainer {
  width: 100%;
  text-align: center;
  margin-top: 10px;
  margin-left: 5px;
}
.typingContainer input {
  width: 370px;
  height: 28px;
  margin-right: 10px;
}
.typingContainer button {
  width: 100px;
  height: 30px;
  font-size: 15px;
  font-weight: bold;
  border: 1px solid black;
  background-color: white;
  margin-right: 10px;
}
.typingContainer button:hover {
  color: white;
  background-color: black;
}