Builds for
1 pipeline
passed
in
7 minutes 51 seconds
Merge branch 'feature/ingame' into develop
Showing
14 changed files
with
443 additions
and
35 deletions
... | @@ -25,6 +25,7 @@ | ... | @@ -25,6 +25,7 @@ |
25 | "scripts": { | 25 | "scripts": { |
26 | "start": "npm run twcss && set PORT=3001 && react-scripts start", | 26 | "start": "npm run twcss && set PORT=3001 && react-scripts start", |
27 | "build": "npm run twcss && react-scripts build", | 27 | "build": "npm run twcss && react-scripts build", |
28 | + "onlybuild": "react-scripts build", | ||
28 | "test": "react-scripts test", | 29 | "test": "react-scripts test", |
29 | "eject": "react-scripts eject", | 30 | "eject": "react-scripts eject", |
30 | "twcss": "tailwind build src/tailwind.css -c tailwind.config.js -o src/index.css" | 31 | "twcss": "tailwind build src/tailwind.css -c tailwind.config.js -o src/index.css" | ... | ... |
... | @@ -18,5 +18,15 @@ export const MessageType = { | ... | @@ -18,5 +18,15 @@ export const MessageType = { |
18 | ROOM_USER_UPDATE: "updateRoomUser", | 18 | ROOM_USER_UPDATE: "updateRoomUser", |
19 | ROOM_CHAT: "chat", | 19 | ROOM_CHAT: "chat", |
20 | ROOM_READY: "ready", | 20 | ROOM_READY: "ready", |
21 | - ROOM_START: "startGame" | 21 | + ROOM_START: "startGame", |
22 | + GAME_START: "startRound", | ||
23 | + GAME_WORDSET: "wordSet", | ||
24 | + GAME_CHOOSE: "chooseWord", | ||
25 | + GAME_WORD: "wordChosen", | ||
26 | + GAME_TIMER: "timer", | ||
27 | + GAME_ACCEPT: "answerAccepted", | ||
28 | + GAME_FINISH_ROUND: "finishRound", | ||
29 | + GAME_FINISH_GAME: "finishGame", | ||
30 | + DRAW_SET: "setBrush", | ||
31 | + DRAW_MOVE: "moveBrush", | ||
22 | } as const | 32 | } as const |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
1 | -import React, { useCallback, useEffect, useRef, useState } from 'react'; | 1 | +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; |
2 | -import { Vector } from './types'; | 2 | +import SocketContext from '../../contexts/SocketContext'; |
3 | +import { MessageType, RawMessage } from '../common/types'; | ||
4 | +import { BrushData, Vector } from './types'; | ||
3 | 5 | ||
4 | // 참고 : https://basketdeveloper.tistory.com/79 | 6 | // 참고 : https://basketdeveloper.tistory.com/79 |
5 | 7 | ||
6 | -export const Canvas: React.FC = () => { | 8 | +interface CanvasProps { |
9 | + isDrawer: boolean; | ||
10 | +} | ||
11 | + | ||
12 | +export const Canvas: React.FC<CanvasProps> = ({ isDrawer }) => { | ||
13 | + const socket = useContext(SocketContext); | ||
7 | const canvasRef = useRef<HTMLCanvasElement>(null); | 14 | const canvasRef = useRef<HTMLCanvasElement>(null); |
8 | 15 | ||
9 | const [mousePosition, setMousePosition] = useState<Vector>({ x:0, y:0 }); | 16 | const [mousePosition, setMousePosition] = useState<Vector>({ x:0, y:0 }); |
... | @@ -52,6 +59,22 @@ export const Canvas: React.FC = () => { | ... | @@ -52,6 +59,22 @@ export const Canvas: React.FC = () => { |
52 | if (coordinates) { | 59 | if (coordinates) { |
53 | setIsPainting(true); | 60 | setIsPainting(true); |
54 | setMousePosition(coordinates); | 61 | setMousePosition(coordinates); |
62 | + | ||
63 | + const rawMessage: RawMessage = { | ||
64 | + type: MessageType.DRAW_MOVE, | ||
65 | + message: coordinates | ||
66 | + }; | ||
67 | + socket.emit('msg', rawMessage, () => {}); | ||
68 | + | ||
69 | + const nextRawMessage: RawMessage = { | ||
70 | + type: MessageType.DRAW_SET, | ||
71 | + message: { | ||
72 | + size: 5, | ||
73 | + color: '000000', | ||
74 | + drawing: true | ||
75 | + } as BrushData | ||
76 | + }; | ||
77 | + socket.emit('msg', nextRawMessage, () => {}); | ||
55 | } | 78 | } |
56 | }, []); | 79 | }, []); |
57 | 80 | ||
... | @@ -65,6 +88,13 @@ export const Canvas: React.FC = () => { | ... | @@ -65,6 +88,13 @@ export const Canvas: React.FC = () => { |
65 | const newMousePosition = getCoordinates(event); | 88 | const newMousePosition = getCoordinates(event); |
66 | if (mousePosition && newMousePosition) { | 89 | if (mousePosition && newMousePosition) { |
67 | drawLine(mousePosition, newMousePosition); | 90 | drawLine(mousePosition, newMousePosition); |
91 | + | ||
92 | + const rawMessage: RawMessage = { | ||
93 | + type: MessageType.DRAW_MOVE, | ||
94 | + message: newMousePosition | ||
95 | + }; | ||
96 | + socket.emit('msg', rawMessage, () => {}); | ||
97 | + | ||
68 | setMousePosition(newMousePosition); | 98 | setMousePosition(newMousePosition); |
69 | } | 99 | } |
70 | } | 100 | } |
... | @@ -73,30 +103,86 @@ export const Canvas: React.FC = () => { | ... | @@ -73,30 +103,86 @@ export const Canvas: React.FC = () => { |
73 | ); | 103 | ); |
74 | 104 | ||
75 | const exitPaint = useCallback(() => { | 105 | const exitPaint = useCallback(() => { |
106 | + const rawMessage: RawMessage = { | ||
107 | + type: MessageType.DRAW_SET, | ||
108 | + message: { | ||
109 | + size: 5, | ||
110 | + color: '000000', | ||
111 | + drawing: false | ||
112 | + } as BrushData | ||
113 | + }; | ||
114 | + socket.emit('msg', rawMessage, () => {}); | ||
115 | + | ||
76 | setIsPainting(false); | 116 | setIsPainting(false); |
77 | }, []); | 117 | }, []); |
78 | 118 | ||
119 | + const handleDrawSet = useCallback((rawMessage: RawMessage) => { | ||
120 | + if (rawMessage.type === MessageType.DRAW_SET) { | ||
121 | + const data = rawMessage.message as BrushData; | ||
122 | + setIsPainting(data.drawing); | ||
123 | + } | ||
124 | + }, []); | ||
125 | + | ||
126 | + const handleDrawMove = useCallback((rawMessage: RawMessage) => { | ||
127 | + if (rawMessage.type === MessageType.DRAW_MOVE) { | ||
128 | + const data = rawMessage.message as Vector; | ||
129 | + if (isPainting) { | ||
130 | + drawLine(mousePosition, data); | ||
131 | + } | ||
132 | + setMousePosition(data); | ||
133 | + } | ||
134 | + }, [isPainting, mousePosition]) | ||
135 | + | ||
79 | useEffect(() => { | 136 | useEffect(() => { |
80 | if (canvasRef.current) { | 137 | if (canvasRef.current) { |
81 | - const canvas: HTMLCanvasElement = canvasRef.current; | 138 | + if (isDrawer) { |
139 | + const canvas: HTMLCanvasElement = canvasRef.current; | ||
140 | + | ||
141 | + canvas.addEventListener('mousedown', startPaint); | ||
142 | + canvas.addEventListener('mousemove', paint); | ||
143 | + canvas.addEventListener('mouseup', exitPaint); | ||
144 | + canvas.addEventListener('mouseleave', exitPaint); | ||
145 | + | ||
146 | + return () => { | ||
147 | + canvas.removeEventListener('mousedown', startPaint); | ||
148 | + canvas.removeEventListener('mousemove', paint); | ||
149 | + canvas.removeEventListener('mouseup', exitPaint); | ||
150 | + canvas.removeEventListener('mouseleave', exitPaint); | ||
151 | + }; | ||
152 | + } | ||
153 | + } | ||
154 | + }, [isDrawer, startPaint, paint, exitPaint]); | ||
155 | + | ||
156 | + useEffect(() => { | ||
157 | + if (!isDrawer) { | ||
158 | + socket.on('msg', handleDrawSet); | ||
159 | + socket.on('msg', handleDrawMove); | ||
82 | 160 | ||
83 | - canvas.addEventListener('mousedown', startPaint); | ||
84 | - canvas.addEventListener('mousemove', paint); | ||
85 | - canvas.addEventListener('mouseup', exitPaint); | ||
86 | - canvas.addEventListener('mouseleave', exitPaint); | ||
87 | - | ||
88 | return () => { | 161 | return () => { |
89 | - canvas.removeEventListener('mousedown', startPaint); | 162 | + socket.off('msg', handleDrawSet); |
90 | - canvas.removeEventListener('mousemove', paint); | 163 | + socket.off('msg', handleDrawMove); |
91 | - canvas.removeEventListener('mouseup', exitPaint); | 164 | + } |
92 | - canvas.removeEventListener('mouseleave', exitPaint); | ||
93 | - }; | ||
94 | } | 165 | } |
95 | - }, [startPaint, paint, exitPaint]); | 166 | + }, [isDrawer, handleDrawMove]); |
167 | + | ||
168 | + const handleClearWhenStart = useCallback((rawMessage: RawMessage) => { | ||
169 | + if (rawMessage.type === MessageType.GAME_START) { | ||
170 | + clearCanvas(); | ||
171 | + setIsPainting(false); | ||
172 | + // TODO: 펜 굵기, 색 설정하게 되면 여기에 초기화 넣기 | ||
173 | + } | ||
174 | + }, []); | ||
175 | + | ||
176 | + useEffect(() => { | ||
177 | + socket.on('msg', handleClearWhenStart); | ||
178 | + return () => { | ||
179 | + socket.off('msg', handleClearWhenStart); | ||
180 | + } | ||
181 | + }, []); | ||
96 | 182 | ||
97 | return ( | 183 | return ( |
98 | <div className='mx-3 px-2 py-1 rounded shadow'> | 184 | <div className='mx-3 px-2 py-1 rounded shadow'> |
99 | - <canvas ref={canvasRef} width='512' height='384' /> | 185 | + <canvas ref={canvasRef} width='640' height='480' /> |
100 | </div> | 186 | </div> |
101 | ); | 187 | ); |
102 | } | 188 | } |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
... | @@ -23,9 +23,13 @@ export const Chat: React.FC<ChatProps> = (props) => { | ... | @@ -23,9 +23,13 @@ export const Chat: React.FC<ChatProps> = (props) => { |
23 | } | 23 | } |
24 | 24 | ||
25 | socket.on('msg', handleChatData); | 25 | socket.on('msg', handleChatData); |
26 | + socket.on('msg', handleAcceptMessage); | ||
27 | + socket.on('msg', handleFinishMessage); | ||
26 | 28 | ||
27 | return () => { | 29 | return () => { |
28 | socket.off('msg', handleChatData); | 30 | socket.off('msg', handleChatData); |
31 | + socket.off('msg', handleAcceptMessage); | ||
32 | + socket.on('msg', handleFinishMessage); | ||
29 | } | 33 | } |
30 | }, []); | 34 | }, []); |
31 | 35 | ||
... | @@ -47,9 +51,29 @@ export const Chat: React.FC<ChatProps> = (props) => { | ... | @@ -47,9 +51,29 @@ export const Chat: React.FC<ChatProps> = (props) => { |
47 | } | 51 | } |
48 | }, [input]); | 52 | }, [input]); |
49 | 53 | ||
54 | + const handleAcceptMessage = useCallback((rawMessage: RawMessage) => { | ||
55 | + if (rawMessage.type === MessageType.GAME_ACCEPT) { | ||
56 | + const message: ChatData = { | ||
57 | + sender: 'SYSTEM', | ||
58 | + message: 'That\'s correct!' | ||
59 | + }; | ||
60 | + setChatLines(oldChatLines => [...oldChatLines, message]); | ||
61 | + } | ||
62 | + }, []); | ||
63 | + | ||
64 | + const handleFinishMessage = useCallback((rawMessage: RawMessage) => { | ||
65 | + if (rawMessage.type === MessageType.GAME_FINISH_ROUND) { | ||
66 | + const message: ChatData = { | ||
67 | + sender: 'SYSTEM', | ||
68 | + message: 'The round is over!' | ||
69 | + }; | ||
70 | + setChatLines(oldChatLines => [...oldChatLines, message]); | ||
71 | + } | ||
72 | + }, []); | ||
73 | + | ||
50 | return ( | 74 | return ( |
51 | <div className={props.w}> | 75 | <div className={props.w}> |
52 | - <div className={`${props.h} w-full rounded shadow flex flex-col overflow-y-scroll`}> | 76 | + <div className={`${props.h} w-full py-2 rounded shadow flex flex-col overflow-y-scroll`}> |
53 | {chatLines.map((line, i) => (<ChatLine key={16383+i} chatData={line}/>))} | 77 | {chatLines.map((line, i) => (<ChatLine key={16383+i} chatData={line}/>))} |
54 | <div ref={messageEndRef} /> | 78 | <div ref={messageEndRef} /> |
55 | </div> | 79 | </div> | ... | ... |
web/src/components/room/GameBoard.tsx
0 → 100644
1 | +import React, { useCallback, useContext, useEffect, useState } from 'react'; | ||
2 | +import { useLocation } from 'react-router'; | ||
3 | +import SocketContext from '../../contexts/SocketContext'; | ||
4 | +import { MessageType, RawMessage } from '../common/types'; | ||
5 | +import { Canvas } from './Canvas'; | ||
6 | +import { RoundInfo } from './RoundInfo'; | ||
7 | +import { Role, RoundData } from './types'; | ||
8 | +import { Word } from './Word'; | ||
9 | + | ||
10 | +interface GameBoardLocation { | ||
11 | + state: { username: string } | ||
12 | +} | ||
13 | + | ||
14 | +interface GameBoardProps { | ||
15 | + isInGame: boolean | ||
16 | +} | ||
17 | + | ||
18 | +export const GameBoard: React.FC<GameBoardProps> = ({ isInGame }) => { | ||
19 | + const socket = useContext(SocketContext); | ||
20 | + const location: GameBoardLocation = useLocation(); | ||
21 | + | ||
22 | + const [ isDrawer, setIsDrawer ] = useState(false); | ||
23 | + const [ words, setWords ] = useState<string[]>([]); | ||
24 | + const [ wordChosen, setWordChosen ] = useState(''); | ||
25 | + const [ round, setRound ] = useState(0); | ||
26 | + | ||
27 | + const handleWordSet = useCallback((rawMessage: RawMessage) => { | ||
28 | + if (rawMessage.type === MessageType.GAME_WORDSET) { | ||
29 | + console.log('단어 도착'); | ||
30 | + const { words } = rawMessage.message as { words: string[] }; | ||
31 | + setWords(words); | ||
32 | + } | ||
33 | + }, []); | ||
34 | + | ||
35 | + const handleStart = useCallback((rawMessage: RawMessage) => { | ||
36 | + if (rawMessage.type === MessageType.GAME_START) { | ||
37 | + setWords([]); | ||
38 | + | ||
39 | + const data = rawMessage.message as RoundData; | ||
40 | + console.log('테스트 location ', location.state.username); | ||
41 | + console.log('테스트 rolse ', data.roles); | ||
42 | + const index = data.roles.findIndex(x => x.username === location.state.username); | ||
43 | + setIsDrawer(data.roles[index].role === 'drawer'); | ||
44 | + setWordChosen(''); | ||
45 | + setRound(data.round); | ||
46 | + } | ||
47 | + }, []); | ||
48 | + | ||
49 | + const handleGetWordLength = useCallback((rawMessage: RawMessage) => { | ||
50 | + if (rawMessage.type === MessageType.GAME_WORD) { | ||
51 | + if (wordChosen === '') { | ||
52 | + const { length } = rawMessage.message as { length: number }; | ||
53 | + setWordChosen('_' + ' _'.repeat(length-1)); | ||
54 | + } | ||
55 | + } | ||
56 | + }, [wordChosen]); | ||
57 | + | ||
58 | + const handleAnswer = useCallback((rawMessage: RawMessage) => { | ||
59 | + if (rawMessage.type === MessageType.GAME_ACCEPT || rawMessage.type === MessageType.GAME_FINISH_ROUND) { | ||
60 | + const { answer } = rawMessage.message as { answer: string }; | ||
61 | + setWordChosen(answer); | ||
62 | + } | ||
63 | + }, []); | ||
64 | + | ||
65 | + useEffect(() => { | ||
66 | + socket.on('msg', handleStart); | ||
67 | + socket.on('msg', handleGetWordLength); | ||
68 | + socket.on('msg', handleWordSet); | ||
69 | + socket.on('msg', handleAnswer); | ||
70 | + | ||
71 | + return () => { | ||
72 | + socket.off('msg', handleStart); | ||
73 | + socket.off('msg', handleGetWordLength); | ||
74 | + socket.off('msg', handleWordSet); | ||
75 | + socket.off('msg', handleAnswer); | ||
76 | + } | ||
77 | + }, []); | ||
78 | + | ||
79 | + return ( | ||
80 | + <div className={`w-auto ${isInGame ? '' : 'hidden'}`}> | ||
81 | + <div className='w-full flex flex-col justify-center items-center'> | ||
82 | + {words.map((word, i) => (<Word key={word} index={i} word={word} setWordChosen={setWordChosen} setWords={setWords} />))} | ||
83 | + </div> | ||
84 | + <Canvas isDrawer={isDrawer && wordChosen !== ''} /> | ||
85 | + <RoundInfo round={round} wordChosen={wordChosen} /> | ||
86 | + </div> | ||
87 | + ); | ||
88 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
... | @@ -33,7 +33,7 @@ export const Ready: React.FC<ReadyProps> = ({ users }) => { | ... | @@ -33,7 +33,7 @@ export const Ready: React.FC<ReadyProps> = ({ users }) => { |
33 | const handleReady = useCallback(() => { | 33 | const handleReady = useCallback(() => { |
34 | if (isAdmin && isAllReady) { | 34 | if (isAdmin && isAllReady) { |
35 | const rawMessage: RawMessage = { | 35 | const rawMessage: RawMessage = { |
36 | - type: MessageType.ROOM_READY, | 36 | + type: MessageType.ROOM_START, |
37 | message: {} | 37 | message: {} |
38 | } | 38 | } |
39 | socket.emit('msg', rawMessage, () => {}); | 39 | socket.emit('msg', rawMessage, () => {}); | ... | ... |
... | @@ -10,7 +10,7 @@ interface RoomInfoProps { | ... | @@ -10,7 +10,7 @@ interface RoomInfoProps { |
10 | 10 | ||
11 | export const RoomInfo: React.FC<RoomInfoProps> = ({ roomData }) => { | 11 | export const RoomInfo: React.FC<RoomInfoProps> = ({ roomData }) => { |
12 | return ( | 12 | return ( |
13 | - <div className='m-3 mb-8 w-5/6 flex items-center place-content-between'> | 13 | + <div className='m-3 my-8 w-5/6 flex items-center place-content-between'> |
14 | <div>{roomData.name}</div> | 14 | <div>{roomData.name}</div> |
15 | <div>{roomData.users.length}/{roomData.maxUsers}</div> | 15 | <div>{roomData.users.length}/{roomData.maxUsers}</div> |
16 | </div> | 16 | </div> | ... | ... |
web/src/components/room/RoundInfo.tsx
0 → 100644
1 | +import React from 'react'; | ||
2 | +import SocketContext from '../../contexts/SocketContext'; | ||
3 | +import { Timer } from './Timer'; | ||
4 | +import { RoundData } from './types'; | ||
5 | + | ||
6 | +interface RoundInfoProps { | ||
7 | + round: number; | ||
8 | + wordChosen: string; | ||
9 | +} | ||
10 | + | ||
11 | +export const RoundInfo: React.FC<RoundInfoProps> = ({ round, wordChosen }) => { | ||
12 | + return ( | ||
13 | + <div className='p-3 m-3 h-14 rounded shadow flex items-center place-content-between'> | ||
14 | + <Timer /> | ||
15 | + <div>{wordChosen}</div> | ||
16 | + <div>Round {round}/5</div> | ||
17 | + </div> | ||
18 | + ); | ||
19 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
web/src/components/room/Timer.tsx
0 → 100644
1 | +import React, { useCallback, useContext, useEffect, useState } from 'react'; | ||
2 | +import SocketContext from '../../contexts/SocketContext'; | ||
3 | +import { MessageType, RawMessage } from '../common/types'; | ||
4 | + | ||
5 | +interface timer { | ||
6 | + state: "started" | "stopped"; | ||
7 | + time: number; | ||
8 | +}; | ||
9 | + | ||
10 | +export const Timer: React.FC = () => { | ||
11 | + const socket = useContext(SocketContext); | ||
12 | + | ||
13 | + const [ time, setTime ] = useState(0); | ||
14 | + const [ isStop, setIsStop ] = useState(true); | ||
15 | + | ||
16 | + const handleTimeSet = useCallback((rawMessage: RawMessage) => { | ||
17 | + if (rawMessage.type === MessageType.GAME_TIMER) { | ||
18 | + const data = rawMessage.message as timer; | ||
19 | + | ||
20 | + console.log(data); | ||
21 | + if (data.state === 'started') { | ||
22 | + setIsStop(false); | ||
23 | + } else { | ||
24 | + setIsStop(true); | ||
25 | + } | ||
26 | + | ||
27 | + setTime(Math.floor(data.time)); | ||
28 | + } | ||
29 | + }, []); | ||
30 | + | ||
31 | + useEffect(() => { | ||
32 | + if (!isStop) { | ||
33 | + const go = setInterval(() => { | ||
34 | + setTime(time-1); | ||
35 | + }, 1000); | ||
36 | + | ||
37 | + return () => clearInterval(go); | ||
38 | + } else { | ||
39 | + setTime(0); | ||
40 | + } | ||
41 | + }, [time, isStop]); | ||
42 | + | ||
43 | + useEffect(() => { | ||
44 | + socket.on('msg', handleTimeSet); | ||
45 | + return () => { | ||
46 | + socket.off('msg', handleTimeSet); | ||
47 | + } | ||
48 | + }, []); | ||
49 | + | ||
50 | + return ( | ||
51 | + <div className={time < 10 ? 'text-red-500' : 'text-black'}> | ||
52 | + 🕒 {time} | ||
53 | + </div> | ||
54 | + ); | ||
55 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
web/src/components/room/UserRole.tsx
0 → 100644
1 | +import React, { useCallback, useContext, useEffect, useState } from 'react'; | ||
2 | +import SocketContext from '../../contexts/SocketContext'; | ||
3 | +import { MessageType, RawMessage } from '../common/types'; | ||
4 | +import { RoleData, RoundData } from './types'; | ||
5 | + | ||
6 | +interface UserRoleProps { | ||
7 | + isInGame: boolean; | ||
8 | +} | ||
9 | + | ||
10 | +export const UserRole: React.FC<UserRoleProps> = ({ isInGame }) => { | ||
11 | + const socket = useContext(SocketContext); | ||
12 | + const [ roles, setRoles ] = useState<RoleData[]>([]); | ||
13 | + | ||
14 | + const handleRole = useCallback((rawMessage: RawMessage) => { | ||
15 | + if (rawMessage.type === MessageType.GAME_START) { | ||
16 | + const { roles } = rawMessage.message as RoundData; | ||
17 | + setRoles(roles); | ||
18 | + } | ||
19 | + }, []); | ||
20 | + | ||
21 | + useEffect(() => { | ||
22 | + socket.on('msg', handleRole); | ||
23 | + return () => { | ||
24 | + socket.off('msg', handleRole); | ||
25 | + } | ||
26 | + }, []); | ||
27 | + | ||
28 | + return ( | ||
29 | + <div className={`w-40 h-140 rounded shadow flex flex-col items-center ${isInGame ? '' : 'hidden'}`}> | ||
30 | + <div className='mt-3' /> | ||
31 | + {roles.map(x => ( | ||
32 | + <div key={x.username} className={`my-5 ease-linear transition-all duration-100 | ||
33 | + ${x.role === 'drawer' ? 'text-blue-500' | ||
34 | + : x.role === 'winner' ? 'text-green-500' | ||
35 | + : 'text-black'}`}> | ||
36 | + {x.nickname} {x.role === 'drawer' ? '🖌️' : x.role === 'spectator' ? '👻' : ''} | ||
37 | + </div> | ||
38 | + ))} | ||
39 | + </div> | ||
40 | + ); | ||
41 | +} |
web/src/components/room/Word.tsx
0 → 100644
1 | +import React, { useCallback, useContext } from 'react'; | ||
2 | +import { IndexType } from 'typescript'; | ||
3 | +import SocketContext from '../../contexts/SocketContext'; | ||
4 | +import { MessageResponse, MessageType, RawMessage } from '../common/types'; | ||
5 | + | ||
6 | +interface WordProps { | ||
7 | + index: number; | ||
8 | + word: string; | ||
9 | + setWordChosen: (value: React.SetStateAction<string>) => void; | ||
10 | + setWords: (value: React.SetStateAction<string[]>) => void; | ||
11 | +} | ||
12 | + | ||
13 | +export const Word: React.FC<WordProps> = (props) => { | ||
14 | + const socket = useContext(SocketContext); | ||
15 | + | ||
16 | + const handleChoose = useCallback(() => { | ||
17 | + const rawMessage: RawMessage = { | ||
18 | + type: MessageType.GAME_CHOOSE, | ||
19 | + message: { word: props.word } | ||
20 | + }; | ||
21 | + socket.emit('msg', rawMessage, (response: MessageResponse<undefined>) => { | ||
22 | + if (response.ok) { | ||
23 | + props.setWords([]); | ||
24 | + props.setWordChosen(props.word); | ||
25 | + } | ||
26 | + }); | ||
27 | + }, [props.setWordChosen]); | ||
28 | + | ||
29 | + return ( | ||
30 | + <button className={`bg-green-500 active:bg-green-600 fixed | ||
31 | + text-white font-bold ${'mt-' + 40*(props.index+2)} mt-40 | ||
32 | + px-5 py-2 rounded shadow | ||
33 | + outline-none focus:outline-none hover:shadow-md | ||
34 | + ease-linear transition-all duration-100`} | ||
35 | + type="button" | ||
36 | + onClick={() => handleChoose()}>{props.word}</button> | ||
37 | + ); | ||
38 | +} |
... | @@ -26,3 +26,23 @@ export interface Vector { | ... | @@ -26,3 +26,23 @@ export interface Vector { |
26 | x: number; | 26 | x: number; |
27 | y: number; | 27 | y: number; |
28 | } | 28 | } |
29 | + | ||
30 | +export type Role = "drawer" | "guesser" | "winner" | "spectator"; | ||
31 | + | ||
32 | +export interface RoleData { | ||
33 | + username: string; | ||
34 | + nickname: string; | ||
35 | + role: Role; | ||
36 | +} | ||
37 | + | ||
38 | +export interface RoundData { | ||
39 | + round: number; | ||
40 | + duration: number; | ||
41 | + roles: RoleData[]; | ||
42 | +}; | ||
43 | + | ||
44 | +export interface BrushData { | ||
45 | + size: number; | ||
46 | + color: string; | ||
47 | + drawing: boolean; | ||
48 | +} | ... | ... |
... | @@ -4,10 +4,12 @@ import { Main } from '../components/common/Main'; | ... | @@ -4,10 +4,12 @@ import { Main } from '../components/common/Main'; |
4 | import { MessageResponse, MessageType, RawMessage } from '../components/common/types'; | 4 | import { MessageResponse, MessageType, RawMessage } from '../components/common/types'; |
5 | import { Canvas } from '../components/room/Canvas'; | 5 | import { Canvas } from '../components/room/Canvas'; |
6 | import { Chat } from '../components/room/Chat'; | 6 | import { Chat } from '../components/room/Chat'; |
7 | +import { GameBoard } from '../components/room/GameBoard'; | ||
7 | import { Ready } from '../components/room/Ready'; | 8 | import { Ready } from '../components/room/Ready'; |
8 | import { RoomInfo } from '../components/room/RoomInfo'; | 9 | import { RoomInfo } from '../components/room/RoomInfo'; |
9 | import { RoomData, UpdateRoomUser } from '../components/room/types'; | 10 | import { RoomData, UpdateRoomUser } from '../components/room/types'; |
10 | import { UserInfo } from '../components/room/UserInfo'; | 11 | import { UserInfo } from '../components/room/UserInfo'; |
12 | +import { UserRole } from '../components/room/UserRole'; | ||
11 | import { UserStatus } from '../components/room/UserStatus'; | 13 | import { UserStatus } from '../components/room/UserStatus'; |
12 | import SocketContext from '../contexts/SocketContext'; | 14 | import SocketContext from '../contexts/SocketContext'; |
13 | 15 | ||
... | @@ -29,6 +31,14 @@ export const Room: React.FC = () => { | ... | @@ -29,6 +31,14 @@ export const Room: React.FC = () => { |
29 | }); | 31 | }); |
30 | const [ isInGame, setIsInGame ] = useState(false); | 32 | const [ isInGame, setIsInGame ] = useState(false); |
31 | 33 | ||
34 | + const handleInGame = useCallback((rawMessage: RawMessage) => { | ||
35 | + if (rawMessage.type === MessageType.GAME_START) { | ||
36 | + setIsInGame(true); | ||
37 | + } else if (rawMessage.type === MessageType.GAME_FINISH_GAME) { | ||
38 | + setIsInGame(false); | ||
39 | + } | ||
40 | + }, []); | ||
41 | + | ||
32 | const handleUpdateRoomUser = useCallback((rawMessage: RawMessage) => { | 42 | const handleUpdateRoomUser = useCallback((rawMessage: RawMessage) => { |
33 | if (rawMessage.type == MessageType.ROOM_USER_UPDATE) { | 43 | if (rawMessage.type == MessageType.ROOM_USER_UPDATE) { |
34 | const data = rawMessage.message as UpdateRoomUser; | 44 | const data = rawMessage.message as UpdateRoomUser; |
... | @@ -81,8 +91,11 @@ export const Room: React.FC = () => { | ... | @@ -81,8 +91,11 @@ export const Room: React.FC = () => { |
81 | } | 91 | } |
82 | 92 | ||
83 | setRoomData(location.state.roomData); | 93 | setRoomData(location.state.roomData); |
94 | + socket.on('msg', handleInGame); | ||
84 | 95 | ||
85 | return () => { | 96 | return () => { |
97 | + socket.off('msg', handleInGame); | ||
98 | + | ||
86 | const rawMessage: RawMessage = { | 99 | const rawMessage: RawMessage = { |
87 | type: MessageType.ROOM_LEAVE, | 100 | type: MessageType.ROOM_LEAVE, |
88 | message: '' | 101 | message: '' |
... | @@ -94,20 +107,22 @@ export const Room: React.FC = () => { | ... | @@ -94,20 +107,22 @@ export const Room: React.FC = () => { |
94 | return ( | 107 | return ( |
95 | <Main> | 108 | <Main> |
96 | <RoomInfo roomData={roomData}/> | 109 | <RoomInfo roomData={roomData}/> |
97 | - { | 110 | + <div className='w-full flex justify-center'> |
98 | - isInGame ? ( | 111 | + {/* 게임보드와 유저롤을 계속 살려둬서 리스너를 항상 열어놓도록 하자 */} |
99 | - <div className='w-full flex'> | 112 | + <UserRole isInGame={isInGame} /> |
100 | - <Canvas /> | 113 | + <GameBoard isInGame={isInGame} /> |
101 | - <Chat w='w-4/12' h='h-80' /> | 114 | + { |
102 | - </div> | 115 | + isInGame ? ( |
103 | - ) : ( | 116 | + <Chat w='w-4/12' h='h-132' /> |
104 | - <div className='w-full flex flex-col justify-center items-center'> | 117 | + ) : ( |
105 | - <UserInfo users={roomData.users}/> | 118 | + <div className='w-full flex flex-col justify-center items-center'> |
106 | - <Ready users={roomData.users} /> | 119 | + <UserInfo users={roomData.users}/> |
107 | - <Chat w='w-7/12' h='h-96' /> | 120 | + <Ready users={roomData.users} /> |
108 | - </div> | 121 | + <Chat w='w-7/12' h='h-96' /> |
109 | - ) | 122 | + </div> |
110 | - } | 123 | + ) |
124 | + } | ||
125 | + </div> | ||
111 | </Main> | 126 | </Main> |
112 | ); | 127 | ); |
113 | } | 128 | } |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
... | @@ -2,7 +2,18 @@ module.exports = { | ... | @@ -2,7 +2,18 @@ module.exports = { |
2 | purge: [], | 2 | purge: [], |
3 | darkMode: false, // or 'media' or 'class' | 3 | darkMode: false, // or 'media' or 'class' |
4 | theme: { | 4 | theme: { |
5 | - extend: {}, | 5 | + extend: { |
6 | + spacing: { | ||
7 | + 120: '30rem', | ||
8 | + 124: '31rem', | ||
9 | + 128: '32rem', | ||
10 | + 132: '33rem', | ||
11 | + 136: '34rem', | ||
12 | + 140: '35rem', | ||
13 | + 160: '40rem', | ||
14 | + 200: '50rem', | ||
15 | + }, | ||
16 | + }, | ||
6 | }, | 17 | }, |
7 | variants: { | 18 | variants: { |
8 | extend: { | 19 | extend: { | ... | ... |
-
Please register or login to post a comment