Skip to content

Commit

Permalink
Merge pull request #172 from ijun17/feature-fe#16#20
Browse files Browse the repository at this point in the history
[FE] feat#16#20
  • Loading branch information
ijun17 authored Nov 7, 2024
2 parents a321df3 + bae6e6a commit df1202c
Show file tree
Hide file tree
Showing 14 changed files with 393 additions and 97 deletions.
4 changes: 2 additions & 2 deletions FE/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"dev": "vite --host",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
Expand Down Expand Up @@ -40,4 +40,4 @@
"typescript-eslint": "^8.11.0",
"vite": "^5.4.10"
}
}
}
2 changes: 1 addition & 1 deletion FE/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function App() {
<Routes>
<Route path="/" element={<MainPage />} />
<Route path="/game/setup" element={<GameSetupPage />} />
<Route path="/game/:id" element={<GamePage />} />
<Route path="/game/:gameId" element={<GamePage />} />
<Route path="*" element={<div>not found</div>} />
</Routes>
</Router>
Expand Down
76 changes: 32 additions & 44 deletions FE/src/api/socket.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,66 @@
import { io, Socket } from 'socket.io-client';
import SocketEvents from '../constants/socketEvents';
import { SocketDataMap } from './socketEventTypes';

// type SocketEvent = (typeof SocketEvents)[keyof typeof SocketEvents];

type ChatMessage = {
userId: string;
message: string;
};

type CreateRoomPayload = {
title: string;
maxPlayerCount: number;
gameMode: string;
isPublic: boolean;
};

// 이벀트의 데이터 νƒ€μž…μ„ μ •μ˜
// type SocketDataMap = {
// [SocketEvents.CHAT_MESSAGE]: ChatMessage;
// [SocketEvents.CREATE_ROOM]: CreateRoomPayload;
// // λ‹€λ₯Έ 이벀트의 데이터 νƒ€μž…μ„ μΆ”κ°€
// };
type SocketEvent = keyof SocketDataMap;

class SocketService {
private socket: Socket;
private url: string;
private handlers: (() => void)[];

constructor(url: string) {
this.socket = io();
this.url = url;
this.handlers = [];
}

connect() {
async connect() {
if (this.isActive()) return;
this.socket = io(this.url);
return new Promise((resolve, reject) => {
this.socket.on('connect', () => resolve(null));
await new Promise<void>((resolve, reject) => {
this.socket.on('connect', () => resolve());
this.socket.on('error', () => reject());
});
this.handlers.forEach((h) => h());
return;
}

disconnect() {
this.socket.disconnect();
}

isActive() {
return this.socket && this.socket.active;
return this.socket && this.socket.connected;
}

// 이벀트 μˆ˜μ‹  λ©”μ„œλ“œ
// on<T extends SocketEvent>(event: T, callback: (data: SocketDataMap[T]) => void) {
// this.socket.on(event, (data: SocketDataMap[T]) => {
// callback(data);
// });
// }
on<T extends SocketEvent>(event: T, callback: (data: SocketDataMap[T]['response']) => void) {
const handler = () => this.socket.on(event as string, callback);
this.handlers.push(handler);
if (this.isActive()) handler();
}

// λ©”μ‹œμ§€ 전솑 λ©”μ„œλ“œ
sendChatMessage(message: ChatMessage) {
this.socket.emit(SocketEvents.CHAT_MESSAGE, message);
emit<T extends SocketEvent>(event: T, data: SocketDataMap[T]['request']) {
this.socket.emit(event, data);
}

// λ°© 생성 λ©”μ„œλ“œ
async createRoom(payload: CreateRoomPayload) {
await this.connect();
this.socket.emit(SocketEvents.CREATE_ROOM, payload);
sendChatMessage(message: SocketDataMap[typeof SocketEvents.CHAT_MESSAGE]['request']) {
this.emit(SocketEvents.CHAT_MESSAGE, message);
}

// μ—°κ²° μ’…λ£Œ λ©”μ„œλ“œ
disconnect() {
this.socket.disconnect();
async createRoom(payload: SocketDataMap['createRoom']['request']) {
await this.connect();
this.socket.emit(SocketEvents.CREATE_ROOM, payload);
}

joinRoom(gameId: string, playerName: string) {
this.socket.send(SocketEvents.JOIN_ROOM, { gameId, playerName });
async joinRoom(gameId: string, playerName: string) {
if (!this.isActive()) await this.connect();
this.socket.emit(SocketEvents.JOIN_ROOM, { gameId, playerName });
}

chatMessage(gameId: string, message: string) {
this.socket.send(SocketEvents.CHAT_MESSAGE, { gameId, message });
this.socket.emit(SocketEvents.CHAT_MESSAGE, { gameId, message });
}
}

export const socketService = new SocketService('http://quizground.duckdns.org:3000/game');
export const socketService = new SocketService('http://' + window.location.hostname + ':3000/game');
165 changes: 165 additions & 0 deletions FE/src/api/socketEventTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// μ±„νŒ… λ©”μ‹œμ§€ 전달 νƒ€μž…
type ChatMessageRequest = {
gameId: string; // PIN
message: string;
};

type ChatMessageResponse = {
playerId: string; // socketId
playerName: string;
message: string;
timestamp: Date;
};

// ν”Œλ ˆμ΄μ–΄ μœ„μΉ˜ μ—…λ°μ΄νŠΈ νƒ€μž…
type UpdatePositionRequest = {
newPosition: [number, number];
};

type UpdatePositionResponse = {
playerId: string; // socketId
playerPosition: [number, number];
};

// κ²Œμž„λ°© 생성 νƒ€μž…
type CreateRoomRequest = {
title: string;
gameMode: 'RANKING' | 'SURVIVAL';
maxPlayerCount: number;
isPublic: boolean;
};

type CreateRoomResponse = {
gameId: string; // PIN
};

// κ²Œμž„λ°© μ˜΅μ…˜ μˆ˜μ • νƒ€μž…
type UpdateRoomOptionRequest = {
gameId: string;
title: string;
gameMode: 'RANKING' | 'SURVIVAL';
maxPlayerCount: number;
isPublic: boolean;
};

type UpdateRoomOptionResponse = {
title: string;
gameMode: 'RANKING' | 'SURVIVAL';
maxPlayerCount: number;
isPublic: boolean;
};

// κ²Œμž„λ°© ν€΄μ¦ˆμ…‹ μˆ˜μ • νƒ€μž…
type UpdateRoomQuizsetRequest = {
quizsetId: number;
quizCount: number;
};

type UpdateRoomQuizsetResponse = {
quizsetId: number;
quizCount: number;
};

// κ²Œμž„λ°© μž…μž₯ νƒ€μž…
type JoinRoomRequest = {
gameId: string;
playerName: string;
};

type JoinRoomResponse = {
players: Array<{
playerId: string; // socketId
playerName: string;
playerPosition: [number, number];
}>;
};

// κ²Œμž„ μ‹œμž‘ νƒ€μž…
type StartGameRequest = {
gameId: string;
};

type StartGameResponse = Record<string, never>; // 빈 객체

// κ²Œμž„ 정지 νƒ€μž…
type StopGameRequest = {
gameId: string;
};

type StopGameResponse = {
status: string;
};

// ν€΄μ¦ˆ μ‹œκ°„ μ’…λ£Œ νƒ€μž…
type EndQuizTimeEvent = {
gameId: string;
};

// ν€΄μ¦ˆ μ‹œμž‘ νƒ€μž…
type StartQuizTimeEvent = {
quiz: string;
options: string[];
quizEndTime: Date;
};

// κ²Œμž„ 점수 μ—…λ°μ΄νŠΈ νƒ€μž…
type UpdateScoreEvent = {
scores: Map<string, number>; // Map<playerId, score>
};

// κ²Œμž„λ°© 퇴μž₯ νƒ€μž…
type ExitRoomEvent = {
playerId: string;
};

// 전체 μ†ŒμΌ“ 이벀트 νƒ€μž… 맡
export type SocketDataMap = {
chatMessage: {
request: ChatMessageRequest;
response: ChatMessageResponse;
};
updatePosition: {
request: UpdatePositionRequest;
response: UpdatePositionResponse;
};
createRoom: {
request: CreateRoomRequest;
response: CreateRoomResponse;
};
updateRoomOption: {
request: UpdateRoomOptionRequest;
response: UpdateRoomOptionResponse;
};
updateRoomQuizset: {
request: UpdateRoomQuizsetRequest;
response: UpdateRoomQuizsetResponse;
};
joinRoom: {
request: JoinRoomRequest;
response: JoinRoomResponse;
};
startGame: {
request: StartGameRequest;
response: StartGameResponse;
};
stopGame: {
request: StopGameRequest;
response: StopGameResponse;
};
endQuizTime: {
request: null;
response: EndQuizTimeEvent;
};
startQuizTime: {
request: null;
response: StartQuizTimeEvent;
};
updateScore: {
request: null;
response: UpdateScoreEvent;
};
exitRoom: {
request: null;
response: ExitRoomEvent;
};
};
29 changes: 9 additions & 20 deletions FE/src/components/Chat.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,33 @@
import { socketService } from '@/api/socket';
import { useEffect, useState } from 'react';

const sampleChat = Array(100)
.fill(null)
.map((_, i) => ({ name: 'user' + i, message: 'messagemessagemessagemessagemessagemessage' }));
import { useChatStore } from '@/store/useChatStore';
import { useRoomStore } from '@/store/useRoomStore';
import { useState } from 'react';

const Chat = () => {
const [messages, setMessages] = useState<Array<{ name: string; message: string }>>([]);
const gameId = useRoomStore((state) => state.gameId);
const messages = useChatStore((state) => state.messages);
const [inputValue, setInputValue] = useState('');

useEffect(() => {
setMessages(sampleChat); //TODO λ‚˜μ€‘μ— 고쳐야 함
// μ„œλ²„μ—μ„œ λ©”μ‹œμ§€λ₯Ό 받을 λ•Œ
// socket.on('chat message', (message) => {
// setMessages((prevMessages) => [...prevMessages, message]);
// });
// return () => {
// socket.off('chat message');
// };
}, []);

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
};

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();

if (inputValue.trim()) {
socketService.chatMessage('1234', inputValue);
if (inputValue.trim() && gameId) {
socketService.chatMessage(gameId, inputValue);
setInputValue('');
}
};

return (
<div className="component-default h-[100%]">
<div className="border-b border-default center h-[2.5rem]">λ©”μ‹œμ§€</div>
<div className="p-2 h-[calc(100%-6rem)] overflow-y-scroll">
{messages.map((e, i) => (
<div className="break-words leading-5 mt-3" key={i}>
<span className="font-bold mr-2">{e.name}</span>
<span className="font-bold mr-2">{e.playerName}</span>
<span>{e.message}</span>
</div>
))}
Expand Down
9 changes: 5 additions & 4 deletions FE/src/components/GameHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { ClipboardCopy } from './ClipboardCopy';
import Card from '@mui/material/Card';
import { QuizPreview } from './QuizView';
import { useParams } from 'react-router-dom';

export const GameHeader = () => {
// μž„μ‹œκ°’
const pinNum = '123456';
const linkURL = 'naver.com';
const { gameId } = useParams<{ gameId: string }>();
const pinNum = String(gameId);
const linkURL = window.location.hostname + `/game/${gameId}`;
return (
<Card className="p-4 border border-blue-600 shadow-xl rounded-md h-[280px] w-[1000px] bg-gradient-to-b from-blue-500 to-blue-700 text-white">
<div className="flex justify-center mb-4">
<ClipboardCopy valueToCopy={pinNum} message={`PIN: ${pinNum} 볡사`} />
<ClipboardCopy valueToCopy={linkURL} message="곡유 링크 볡사" />
</div>
<div className="flex flex-col items-center justify-center text-center space-y-2">
<span className="text-xl font-semibold">ν€΄μ¦ˆμ΄λ¦„22</span>
<span className="text-xl font-semibold">ν€΄μ¦ˆμ΄λ¦„</span>
</div>
<QuizPreview title="title" description="ν€΄μ¦ˆν€΄μ¦ˆν€΄γ…£μ¦ˆ" />
<div className="flex space-x-4 justify-center">
Expand Down
Loading

0 comments on commit df1202c

Please sign in to comment.