답 제출 수단을 캔버스 또는 타이핑으로 선택할 수 있도록 수정
-<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;
}
'프로젝트 > 한글 게임' 카테고리의 다른 글
15. 초성/중성/종성 분리 (0) | 2024.04.29 |
---|---|
14. 재도전 기회 (0) | 2024.04.26 |
12. 캔버스, 이미지 데이터 병합 (0) | 2024.04.22 |
11. 이미지 중복 방지, 횟수 제한 (0) | 2024.04.20 |
10. 이미지 데이터 가져오기 (0) | 2024.04.18 |