diff --git a/handtris/src/app/globals.css b/handtris/src/app/globals.css index 97d0fb9..4a47bf0 100644 --- a/handtris/src/app/globals.css +++ b/handtris/src/app/globals.css @@ -388,6 +388,7 @@ canvas { border-radius: 8px !important; font-size: 1.125rem !important; } +/* NOTE ATTACKS*/ @keyframes flipCanvas { 0% { @@ -486,3 +487,26 @@ canvas { .animate-pulse { animation: handWarning 1s infinite; } + +.flip-text { + animation: flipText 3s ease-in; +} + +@keyframes flipText { + 0% { + transform: rotateX(180deg); + } + 100% { + transform: rotateX(0deg); + } +} + + +@keyframes warning-flash { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +.animate-warning { + animation: warning-flash 0.5s ease-in-out infinite; +} diff --git a/handtris/src/components/TetrisPlay.tsx b/handtris/src/components/TetrisPlay.tsx index 10c870e..385710e 100644 --- a/handtris/src/components/TetrisPlay.tsx +++ b/handtris/src/components/TetrisPlay.tsx @@ -66,7 +66,9 @@ const Home: React.FC = () => { const prevIsDangerousRef = useRef(false); const [showFirstAttack, setShowFirstAttack] = useState(false); const [showFirstAttacked, setShowFirstAttacked] = useState(false); - + const [isFlipping, setIsFlipping] = useState(false); + const [isNextBlockDonut, setIsNextBlockDonut] = useState(false); + const [showDonutWarning, setShowDonutWarning] = useState(false); const fetchRoomPlayers = useCallback(async () => { setIsLoading(true); try { @@ -110,68 +112,43 @@ const Home: React.FC = () => { const canvas = nextBlockRef.current; if (canvas && nextBlock) { const context = canvas.getContext("2d"); - if (context) { + if (context && tetrisGameRef.current) { context.clearRect(0, 0, canvas.width, canvas.height); + + const drawBlock = (x: number, y: number) => { + const offsetX = + { + orange: 1.3, + blue: 0.5, + green: 1.0, + red: 1.0, + yellow: 1.0, + pink: 1.05, + }[nextBlock.color] || 0.5; + + const offsetY = + { + orange: 0.5, + blue: -0.1, + green: 0.9, + red: 1.0, + yellow: 0.8, + pink: 0.45, + }[nextBlock.color] || 0.5; + + tetrisGameRef.current?.drawSquareCanvas( + context, + x + offsetX, + y + offsetY, + nextBlock.color, + false, + ); + }; + nextBlock.activeTetromino.forEach((row, y) => { row.forEach((value, x) => { - if (value && tetrisGameRef.current) { - if (nextBlock.color === "orange") { - tetrisGameRef.current.drawSquareCanvas( - context, - x + 1.3, - y + 0.5, - nextBlock.color, - false, - ); - } else if (nextBlock.color === "blue") { - tetrisGameRef.current.drawSquareCanvas( - context, - x + 0.5, - y - 0.1, - nextBlock.color, - false, - ); - } else if (nextBlock.color === "green") { - tetrisGameRef.current.drawSquareCanvas( - context, - x + 1.0, - y + 0.9, - nextBlock.color, - false, - ); - } else if (nextBlock.color === "red") { - tetrisGameRef.current.drawSquareCanvas( - context, - x + 1.0, - y + 1.0, - nextBlock.color, - false, - ); - } else if (nextBlock.color === "yellow") { - tetrisGameRef.current.drawSquareCanvas( - context, - x + 1.0, - y + 0.8, - nextBlock.color, - false, - ); - } else if (nextBlock.color === "pink") { - tetrisGameRef.current.drawSquareCanvas( - context, - x + 1.05, - y + 0.45, - nextBlock.color, - false, - ); - } else { - tetrisGameRef.current.drawSquareCanvas( - context, - x + 0.5, - y + 0.5, - nextBlock.color, - false, - ); - } + if (value) { + drawBlock(x, y); } }); }); @@ -574,7 +551,7 @@ const Home: React.FC = () => { // tetrisGameRef.current.addBlockRow(); //NOTE - 실시간 공격 적용 시 이 부분 수정 필요 tetrisGameRef.current.isAddAttacked = true; } else if (message.isFlipAttack) { - // tetrisGameRef.current.toggleAttackedEffect = true; + setIsFlipping(true); const playOppTetrisElement = document.getElementById("tetris-container"); if ( @@ -583,14 +560,19 @@ const Home: React.FC = () => { ) { playOppTetrisElement.classList.add("flipped-canvas"); setTimeout(() => { - playOppTetrisElement.classList.add("unflipped-canvas"); + setIsFlipping(false); setTimeout(() => { - playOppTetrisElement.classList.remove("flipped-canvas"); - playOppTetrisElement.classList.remove( - "unflipped-canvas", - ); - }, 500); - }, 3000); + playOppTetrisElement.classList.add("unflipped-canvas"); + setTimeout(() => { + playOppTetrisElement.classList.remove( + "flipped-canvas", + ); + playOppTetrisElement.classList.remove( + "unflipped-canvas", + ); + }, 500); + }, 100); + }, 2900); } } else if (message.isDonutAttack) { tetrisGameRef.current.isDonutAttacked = true; @@ -878,6 +860,19 @@ const Home: React.FC = () => { }); } }, []); + useEffect(() => { + if (tetrisGameRef.current) { + const nextBlock = tetrisGameRef.current.getNextBlock(); + const isDonut = nextBlock.color === "pink"; + setIsNextBlockDonut(isDonut); + if (isDonut) { + setShowDonutWarning(true); + // NOTE 효과추가 + setTimeout(() => setShowDonutWarning(false), 3000); + } + drawNextBlock(nextBlock); + } + }, [tetrisGameRef.current?.getNextBlock()]); useEffect(() => { if (showFirstAttack) { @@ -1026,11 +1021,22 @@ const Home: React.FC = () => { className={`flex flex-col justify-between relative ${isDangerous ? "danger-state" : ""}`} > {isDangerous && ( -