From b7bc028944696b5dc8da25022cbc9fbf8fcbe531 Mon Sep 17 00:00:00 2001 From: donghyun Date: Thu, 22 Feb 2024 13:20:24 +0900 Subject: [PATCH 01/27] =?UTF-8?q?fix:=20=EC=98=A4=ED=83=80=20=EA=B3=A0?= =?UTF-8?q?=EC=B3=90=EC=A4=8C=20lvs1=20->=201vs1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/custom-game-list/page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/custom-game-list/page.js b/frontend/src/pages/custom-game-list/page.js index de8b5d2f..de699a9e 100644 --- a/frontend/src/pages/custom-game-list/page.js +++ b/frontend/src/pages/custom-game-list/page.js @@ -268,7 +268,7 @@ export default function CustomGameList($container) { mode: $tournamentModeBtn.style.opacity === "1" ? "casual_tournament" - : "casual_lvs1", + : "casual_1vs1", }), }).then((res) => { console.log(res); From 3594f43110e9909a14b5525df943e28d28bab84d Mon Sep 17 00:00:00 2001 From: donghyun Date: Thu, 22 Feb 2024 17:18:05 +0900 Subject: [PATCH 02/27] =?UTF-8?q?feat:=20=EB=B9=88=20=EC=82=AC=EB=9E=8C=20?= =?UTF-8?q?ui=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/waiting-room/userBox.js | 2 +- frontend/src/pages/waiting-room/userUnit.js | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/waiting-room/userBox.js b/frontend/src/pages/waiting-room/userBox.js index 01ced9b3..6caa6b29 100644 --- a/frontend/src/pages/waiting-room/userBox.js +++ b/frontend/src/pages/waiting-room/userBox.js @@ -28,7 +28,7 @@ export default function userBox(gameMode, props) {
user
- ${userUnit(props[3])} + ${userUnit(null)}
`; } diff --git a/frontend/src/pages/waiting-room/userUnit.js b/frontend/src/pages/waiting-room/userUnit.js index c6292c18..987c85cb 100644 --- a/frontend/src/pages/waiting-room/userUnit.js +++ b/frontend/src/pages/waiting-room/userUnit.js @@ -2,7 +2,16 @@ * * @param {img, nickname, rating, status} props */ -export default function userUnit({ img, nickname, rating, status, color }) { +export default function userUnit(props) { + // 빈 유저 칸 + if (props === null) { + return ` +
+ user + ??? +
`; + } + let { img, nickname, rating, status, color } = props; return `
user From 004cc76dc4737e1cb7eab2a971bda86c3b96f8a2 Mon Sep 17 00:00:00 2001 From: donghyun Date: Thu, 22 Feb 2024 17:19:03 +0900 Subject: [PATCH 03/27] =?UTF-8?q?chore:=20=EC=A3=BC=EC=84=9D=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/router.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/router.js b/frontend/src/router.js index 4d8ebe7d..f73fdc30 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -15,6 +15,7 @@ export default function Router($container) { const route = (info) => { // 현재 페이지에서 Unmount 함수가 있다면 호출합니다. if (currentPage !== undefined && currentPage.unmount) currentPage.unmount(); + // ToDO: 헤더 이동 시 unmount? 비스무리한거 실행해야함 const TargetPage = findMatchedRoute()?.page || ErrorPage; // 현재 경로에 따라 렌더링할 컴포넌트를 정의합니다. const TargetHeader = findMatchedRoute()?.header || MainHeader; // 헤더 컴포넌트도 정의합니다. From 53384282e5b63773abb6d9be7fea734ea98505fd Mon Sep 17 00:00:00 2001 From: donghyun Date: Thu, 22 Feb 2024 17:22:44 +0900 Subject: [PATCH 04/27] =?UTF-8?q?fix:=20fetch=20header=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/custom-game-list/page.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/src/pages/custom-game-list/page.js b/frontend/src/pages/custom-game-list/page.js index de699a9e..43eda422 100644 --- a/frontend/src/pages/custom-game-list/page.js +++ b/frontend/src/pages/custom-game-list/page.js @@ -262,6 +262,9 @@ export default function CustomGameList($container) { console.log($tournamentModeBtn.style.opacity === "1"); fetch(`${BACKEND}/games/`, { method: "POST", + headers: { + "Content-Type": "application/json", + }, body: JSON.stringify({ title: $roomNameInput.value, password: $passwordInput.value, From fae24b2a43eb0342880277263f01b81a272376d6 Mon Sep 17 00:00:00 2001 From: donghyun Date: Thu, 22 Feb 2024 19:21:26 +0900 Subject: [PATCH 05/27] =?UTF-8?q?feat:=20api=EC=99=84=EC=84=B1=20=EC=8B=9C?= =?UTF-8?q?=20=EC=9D=B4=EC=8B=9D=20=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20?= =?UTF-8?q?=EC=84=B8=ED=8C=85=20=EC=99=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/custom-game-list/page.js | 24 ++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/custom-game-list/page.js b/frontend/src/pages/custom-game-list/page.js index 43eda422..a5092521 100644 --- a/frontend/src/pages/custom-game-list/page.js +++ b/frontend/src/pages/custom-game-list/page.js @@ -87,15 +87,26 @@ export default function CustomGameList($container) { document.getElementById("password-modal-wrapper").style.display = "block"; // navigate("/waiting-room", { id: data.id }); - // TODO: api 이식 대기 + // TODO: 비번 맞으면 enterRoom 호출 } else { - navigate("/waiting-room", { id: data.id }); + enterRoom(data.id); } }); }); } }; + const enterRoom = (id) => { + fetch(`${BACKEND}/games/${id}/`, { + method: "POST", + }) + .then((res) => res.json()) + .then((res) => { + res.id = id; + navigate("/waiting-room", res); + }); + }; + const addEventListenersToLayout = () => { // querySelectorAll로 룸 리스트 각각의 요소들을 담은 변수 // const $roomContents = document.querySelectorAll( @@ -273,9 +284,12 @@ export default function CustomGameList($container) { ? "casual_tournament" : "casual_1vs1", }), - }).then((res) => { - console.log(res); - }); + }) + .then((res) => res.json()) + .then((res) => { + console.log(res); + // TODO: 방 만들기 성공시 waiting-room으로 이동 + }); }); }; From f5c818d853c6c64d46423b5ed7ad79db15e43056 Mon Sep 17 00:00:00 2001 From: donghyun Date: Fri, 23 Feb 2024 15:58:19 +0900 Subject: [PATCH 06/27] =?UTF-8?q?feat:=20=EC=86=8C=EC=BC=93=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=9D=B4=EC=8B=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/custom-game-list/page.js | 55 ++++++++++++++----- .../pages/custom-game-list/password-modal.js | 10 ++-- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/frontend/src/pages/custom-game-list/page.js b/frontend/src/pages/custom-game-list/page.js index a5092521..d10471cb 100644 --- a/frontend/src/pages/custom-game-list/page.js +++ b/frontend/src/pages/custom-game-list/page.js @@ -86,8 +86,14 @@ export default function CustomGameList($container) { if (data.is_secret === true) { document.getElementById("password-modal-wrapper").style.display = "block"; - // navigate("/waiting-room", { id: data.id }); - // TODO: 비번 맞으면 enterRoom 호출 + document + .getElementById("pwd-input") + .addEventListener("keydown", (e) => { + console.log(e.key); + if (e.key === "Enter") { + enterRoom(data.id, e.target.value); + } + }); } else { enterRoom(data.id); } @@ -96,15 +102,26 @@ export default function CustomGameList($container) { } }; - const enterRoom = (id) => { - fetch(`${BACKEND}/games/${id}/`, { - method: "POST", - }) - .then((res) => res.json()) - .then((res) => { - res.id = id; - navigate("/waiting-room", res); - }); + const enterRoom = (id, password = null) => { + let wss; + + if (password != null) + wss = new WebSocket( + `wss://localhost:443/ws/games/${id}/?password=${password}`, + ); + else wss = new WebSocket(`wss://localhost:443/ws/games/${id}/`); + + wss.onmessage = (event) => { + const res = JSON.parse(event.data); + if (res.error) { + alert(res.error); + return; + } + res.socket = wss; + // console.log(res); + wss.onmessage = null; + navigate("/waiting-room", res); + }; }; const addEventListenersToLayout = () => { @@ -225,6 +242,7 @@ export default function CustomGameList($container) { } else { return; } + pagination = 1; updateGameRoomList(); }); @@ -238,12 +256,13 @@ export default function CustomGameList($container) { } else { return; } + pagination = 1; updateGameRoomList(); }); // 신속히 입장 click(document.getElementById("quick-join"), () => { - navigate("/waiting-room", { id: 0 }); + enterRoom(0); }); // 게임 방 만들기 모달 @@ -271,6 +290,7 @@ export default function CustomGameList($container) { console.log($roomNameInput.value); console.log($passwordInput.value); console.log($tournamentModeBtn.style.opacity === "1"); + // TODO: title '' 일때 처리 fetch(`${BACKEND}/games/`, { method: "POST", headers: { @@ -285,10 +305,17 @@ export default function CustomGameList($container) { : "casual_1vs1", }), }) - .then((res) => res.json()) + .then((res) => { + if (res.status === 201) { + return res.json(); + } else { + console.log(res); + throw new Error("방 만들기 실패"); + } + }) .then((res) => { console.log(res); - // TODO: 방 만들기 성공시 waiting-room으로 이동 + enterRoom(res.id, $passwordInput.value); }); }); }; diff --git a/frontend/src/pages/custom-game-list/password-modal.js b/frontend/src/pages/custom-game-list/password-modal.js index 8090bc43..61f74f62 100644 --- a/frontend/src/pages/custom-game-list/password-modal.js +++ b/frontend/src/pages/custom-game-list/password-modal.js @@ -1,13 +1,13 @@ export default function passwordModal(isVisible) { - let display = "none"; - if (isVisible) display = "flex"; - return ` + let display = "none"; + if (isVisible) display = "flex"; + return `
암호를 입력해라
setting
- +
`; -} \ No newline at end of file +} From 9303b284daf4b2ab7e51954a7588648630173396 Mon Sep 17 00:00:00 2001 From: donghyun Date: Fri, 23 Feb 2024 16:29:41 +0900 Subject: [PATCH 07/27] =?UTF-8?q?feat:=20custom-game-room=20=EC=99=84?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/custom-game-list/page.js | 26 ++++++++++++------- .../custom-game-list/room-create-modal.js | 4 +-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/frontend/src/pages/custom-game-list/page.js b/frontend/src/pages/custom-game-list/page.js index d10471cb..f95e86b4 100644 --- a/frontend/src/pages/custom-game-list/page.js +++ b/frontend/src/pages/custom-game-list/page.js @@ -60,7 +60,9 @@ export default function CustomGameList($container) { */ this.renderGameRoomList = () => { let $listWrapper = $container.querySelector("#list-wrapper"); - $listWrapper.innerHTML = ""; + // $listWrapper.innerHTML = ""; + if ($listWrapper == null) return; + $listWrapper.textContent = ""; let curGameRoomList = getGameRoomList().data; if (curGameRoomList == null) { $listWrapper.insertAdjacentHTML( @@ -83,6 +85,18 @@ export default function CustomGameList($container) {
`, ); click($listWrapper.querySelector(`.room-id-${data.id}`), () => { + if (data.started === true) { + alert("게임이 이미 시작되었습니다."); + return; + } + if ( + (data.player_num === 4 && data.mode === 1) || + (data.player_num === 2 && data.mode === 0) + ) { + alert("방이 꽉 찼습니다."); + return; + } + if (data.is_secret === true) { document.getElementById("password-modal-wrapper").style.display = "block"; @@ -118,7 +132,6 @@ export default function CustomGameList($container) { return; } res.socket = wss; - // console.log(res); wss.onmessage = null; navigate("/waiting-room", res); }; @@ -220,7 +233,7 @@ export default function CustomGameList($container) { }); click($paginationAfter, () => { - if (pagination <= maxPage) { + if (pagination < maxPage) { pagination += 1; updateGameRoomList(); } @@ -283,13 +296,9 @@ export default function CustomGameList($container) { }); click($makeRoomBtn, () => { - console.log("make room"); const $roomNameInput = document.getElementById("room-name-input"); const $passwordInput = document.getElementById("password-input"); - console.log($roomNameInput.value); - console.log($passwordInput.value); - console.log($tournamentModeBtn.style.opacity === "1"); // TODO: title '' 일때 처리 fetch(`${BACKEND}/games/`, { method: "POST", @@ -350,7 +359,6 @@ export default function CustomGameList($container) { res.json().then((data) => { maxPage = data.total_pages; setGameRoomList(data); - // console.log(data); }); }); }; @@ -358,5 +366,3 @@ export default function CustomGameList($container) { addEventListenersToLayout(); const intervalId = setInterval(updateGameRoomList, 1000); } - -//TODO: 패스워드 모달 창 기능 추가, 방 만들기 기능 추가 diff --git a/frontend/src/pages/custom-game-list/room-create-modal.js b/frontend/src/pages/custom-game-list/room-create-modal.js index c6557e00..45ca61df 100644 --- a/frontend/src/pages/custom-game-list/room-create-modal.js +++ b/frontend/src/pages/custom-game-list/room-create-modal.js @@ -7,7 +7,7 @@ export default function roomCreateModal(isVisible) { setting
방 이름
- +
토너먼트
@@ -16,7 +16,7 @@ export default function roomCreateModal(isVisible) {
암호
- +
From cc763171400190800b7e76e29c118366e405cdc8 Mon Sep 17 00:00:00 2001 From: donghyun Date: Fri, 23 Feb 2024 16:51:03 +0900 Subject: [PATCH 08/27] =?UTF-8?q?feat:=20input-focus=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/custom-game-list/page.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/custom-game-list/page.js b/frontend/src/pages/custom-game-list/page.js index f95e86b4..b95f2c8c 100644 --- a/frontend/src/pages/custom-game-list/page.js +++ b/frontend/src/pages/custom-game-list/page.js @@ -100,6 +100,7 @@ export default function CustomGameList($container) { if (data.is_secret === true) { document.getElementById("password-modal-wrapper").style.display = "block"; + document.getElementById("pwd-input").focus(); document .getElementById("pwd-input") .addEventListener("keydown", (e) => { @@ -128,7 +129,9 @@ export default function CustomGameList($container) { wss.onmessage = (event) => { const res = JSON.parse(event.data); if (res.error) { - alert(res.error); + console.log(res.error); + if (res.error === "[PermissionDenied] can't access") + alert("비밀번호가 틀렸습니다."); return; } res.socket = wss; From 5c872ffe187f477a1f4f00fa5d7a3bc4d2682fd4 Mon Sep 17 00:00:00 2001 From: sejoonkimmm Date: Fri, 23 Feb 2024 17:52:11 +0900 Subject: [PATCH 09/27] =?UTF-8?q?fix:=20messages=EB=A5=BC=20message?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95=20/=20cancle=20=EC=98=A4?= =?UTF-8?q?=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/friends/consumers.py | 2 +- backend/friends/views.py | 14 ++++----- docker-compose.yml | 56 +++++++++++++++++++---------------- nginx/Dockerfile | 24 +++++++-------- nginx/config/modsecurity.conf | 2 +- 5 files changed, 52 insertions(+), 46 deletions(-) diff --git a/backend/friends/consumers.py b/backend/friends/consumers.py index 87b0ecfb..656dbe09 100644 --- a/backend/friends/consumers.py +++ b/backend/friends/consumers.py @@ -40,7 +40,7 @@ async def disconnect(self, close_code): await set_user_online(self.user.id, online=False) logger.info(f"Websocket connection closed for user {self.user.id}") if hasattr(self, 'send_update_task') and self.send_update_task: - self.send_update_task.cancle() + self.send_update_task.cancel() try: await self.send_update_task except asyncio.CancelledError: diff --git a/backend/friends/views.py b/backend/friends/views.py index 1775f1b8..37f92107 100644 --- a/backend/friends/views.py +++ b/backend/friends/views.py @@ -40,7 +40,7 @@ def get(self, request): response_data = {'friends': friend_list} return Response(response_data, status=status.HTTP_200_OK) except AuthenticationException as e: - return Response({'error': e.messages}, status=status.HTTP_401_UNAUTHORIZED) + return Response({'error': e.message}, status=status.HTTP_401_UNAUTHORIZED) except User.DoesNotExist: return Response({'error': 'User not found'}, status=status.HTTP_404_NOT_FOUND) except Exception as e: @@ -75,7 +75,7 @@ def post(self, request): Friend.objects.create(user_id=current_user, friend_id=requested_friend, status=0) return Response({'message': 'Friend request sent'}, status=status.HTTP_200_OK) except AuthenticationException as e: - return Response({'error': e.messages}, status=status.HTTP_401_UNAUTHORIZED) + return Response({'error': e.message}, status=status.HTTP_401_UNAUTHORIZED) except User.DoesNotExist: return Response({'error': 'User not found'}, status=status.HTTP_404_NOT_FOUND) except Exception as e: @@ -108,7 +108,7 @@ def delete(self, request): Friend.objects.filter(user_id=requested_friend, friend_id=current_user, status=1).delete() return Response({'message': 'Friend deleted'}, status=status.HTTP_200_OK) except AuthenticationException as e: - return Response({'error': e.messages}, status=status.HTTP_401_UNAUTHORIZED) + return Response({'error': e.message}, status=status.HTTP_401_UNAUTHORIZED) except User.DoesNotExist: return Response({'error': 'User not found'}, status=status.HTTP_404_NOT_FOUND) except Exception as e: @@ -146,7 +146,7 @@ def post(self, request): return Response({'message': 'Friend request Accepted'}, status=status.HTTP_200_OK) except AuthenticationException as e: - return Response({'error': e.messages}, status=status.HTTP_401_UNAUTHORIZED) + return Response({'error': e.message}, status=status.HTTP_401_UNAUTHORIZED) except User.DoesNotExist: return Response({'error': 'User not found'}, status=status.HTTP_404_NOT_FOUND) except Exception as e: @@ -181,7 +181,7 @@ def post(self, request): return Response({'message': 'Friend request rejected'}, status=status.HTTP_200_OK) except AuthenticationException as e: - return Response({'error': e.messages}, status=status.HTTP_401_UNAUTHORIZED) + return Response({'error': e.message}, status=status.HTTP_401_UNAUTHORIZED) except User.DoesNotExist: return Response({'error': 'User not found'}, status=status.HTTP_404_NOT_FOUND) except Exception as e: @@ -202,7 +202,7 @@ def get(self, request): return Response({'searchedUserList': users_data}, status=status.HTTP_200_OK) except AuthenticationException as e: - return Response({'error': e.messages}, status=status.HTTP_401_UNAUTHORIZED) + return Response({'error': e.message}, status=status.HTTP_401_UNAUTHORIZED) except Exception as e: return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) @@ -223,6 +223,6 @@ def get(self, request): response_data = {'friendRequestList': friend_request_list} return Response(response_data, status=status.HTTP_200_OK) except AuthenticationException as e: - return Response({'error': e.messages}, status=status.HTTP_401_UNAUTHORIZED) + return Response({'error': e.message}, status=status.HTTP_401_UNAUTHORIZED) except Exception as e: return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/docker-compose.yml b/docker-compose.yml index f1602745..a08ba42d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -66,32 +66,34 @@ services: depends_on: - hashicorp_vault - nginx: - build: ./nginx - container_name: nginx_container - volumes: - - "./frontend:/usr/share/nginx/html" - - static-volume:/usr/share/nginx/html/static - - "./volumes/certs:/etc/nginx/certs" - # - "/Users/sejokim/desktop/volumes/nginx/logs:/var/log/nginx" - logging: - options: - max-size: "10m" - max-file: "3" - ports: - - 443:443 # https 사용 - depends_on: - - django_node_app - networks: - - app_network + # nginx: + # build: ./nginx + # container_name: nginx_container + # volumes: + # - "./frontend:/usr/share/nginx/html" + # - static-volume:/usr/share/nginx/html/static + # - "./volumes/certs:/etc/nginx/certs" + # # - "/Users/sejokim/desktop/volumes/nginx/logs:/var/log/nginx" + # logging: + # options: + # max-size: "10m" + # max-file: "3" + # ports: + # - 443:443 # https 사용 + # depends_on: + # - django_node_app + # networks: + # - app_network # elasticsearch: # build: # context: ./elk/elasticsearch # dockerfile: Dockerfile # volumes: - # - "/Users/sejokim/desktop/volumes/elasticsearch/data:/usr/share/elasticsearch/data" - # - "/Users/sejokim/desktop/volumes/elasticsearch/certs:/usr/share/elasticsearch/config/certs" + # # - "/Users/sejokim/desktop/volumes/elasticsearch/data:/usr/share/elasticsearch/data" + # # - "/Users/sejokim/desktop/volumes/elasticsearch/certs:/usr/share/elasticsearch/config/certs" + # - "./volumes/elasticsearch/data:/usr/share/elasticsearch/data" + # - "./volumes/elasticsearch/certs:/usr/share/elasticsearch/config/certs" # environment: # discovery.type : single-node # ELASTIC_PW : ${ELASTIC_PW} @@ -103,7 +105,7 @@ services: # networks: # - app_network - # # elasticsearch-setup-passwords interactive --url "https://localhost:9200" -E xpack.security.http.ssl.verification_mode=none + # elasticsearch-setup-passwords interactive --url "https://localhost:9200" -E xpack.security.http.ssl.verification_mode=none # logstash: # build: ./elk/logstash @@ -113,8 +115,10 @@ services: # ELASTIC_PW: ${ELASTIC_PW} # ES_CERTS_PW: ${ES_CERTS_PW} # volumes: - # - "/Users/sejokim/desktop/volumes/elasticsearch/certs:/usr/share/logstash/config/certs" - # - "/Users/sejokim/desktop/volumes/nginx/logs:/usr/share/logstash/logs" + # # - "/Users/sejokim/desktop/volumes/elasticsearch/certs:/usr/share/logstash/config/certs" + # # - "/Users/sejokim/desktop/volumes/nginx/logs:/usr/share/logstash/logs" + # - "./volumes/elasticsearch/certs:/usr/share/logstash/config/certs" + # - "./volumes/nginx/logs:/usr/share/logstash/logs" # ports: # - 5333:5333 # depends_on: @@ -126,8 +130,10 @@ services: # build: ./elk/kibana # container_name: kibana_container # volumes: - # - "/Users/sejokim/desktop/volumes/kibana/data:/usr/share/kibana/data" - # - "/Users/sejokim/desktop/volumes/elasticsearch/certs:/usr/share/kibana/config/certs" + # # - "/Users/sejokim/desktop/volumes/kibana/data:/usr/share/kibana/data" + # # - "/Users/sejokim/desktop/volumes/elasticsearch/certs:/usr/share/kibana/config/certs" + # - "./volumes/kibana/data:/usr/share/kibana/data" + # - "./volumes/elasticsearch/certs:/usr/share/kibana/config/certs" # environment: # ELASTICSEARCH_URL: https://elasticsearch:9200 # ELASTIC_PW: ${ELASTIC_PW} diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 521c90d9..3a1454bf 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -3,7 +3,7 @@ FROM nginx:1.21.6-alpine # 필요한 패키지 설치 RUN apk add --no-cache git build-base libtool automake autoconf zlib-dev pcre-dev openssl-dev linux-headers openssl -# # ModSecurity 다운로드 및 컴파일 +# # # ModSecurity 다운로드 및 컴파일 # RUN git clone --depth 1 https://github.com/SpiderLabs/ModSecurity.git /usr/local/src/modsecurity \ # && cd /usr/local/src/modsecurity \ # && git submodule init \ @@ -13,21 +13,21 @@ RUN apk add --no-cache git build-base libtool automake autoconf zlib-dev pcre-de # && make \ # && make install -# ModSecurity-nginx 커넥터 다운로드 +# # ModSecurity-nginx 커넥터 다운로드 # RUN git clone --depth 1 https://github.com/SpiderLabs/ModSecurity-nginx.git /usr/local/src/modsecurity-nginx -# Nginx 컴파일을 위한 준비 및 ModSecurity 모듈 빌드 +# # Nginx 컴파일을 위한 준비 및 ModSecurity 모듈 빌드 # ARG NGINX_VERSION=1.21.6 # RUN wget https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz \ - # && tar zxvf nginx-${NGINX_VERSION}.tar.gz \ - # && cd nginx-${NGINX_VERSION} \ - # && ./configure --with-compat --add-dynamic-module=/usr/local/src/modsecurity-nginx \ - # && make modules \ - # && cp objs/ngx_http_modsecurity_module.so /usr/lib/nginx/modules +# && tar zxvf nginx-${NGINX_VERSION}.tar.gz \ +# && cd nginx-${NGINX_VERSION} \ +# && ./configure --with-compat --add-dynamic-module=/usr/local/src/modsecurity-nginx \ +# && make modules \ +# && cp objs/ngx_http_modsecurity_module.so /usr/lib/nginx/modules -# ModSecurity 설정 디렉토리 생성 및 파일 복사 +# # ModSecurity 설정 디렉토리 생성 및 파일 복사 # RUN mkdir -p /etc/nginx/modsecurity # COPY ./config/modsecurity.conf /etc/nginx/modsecurity/ # COPY ./config/owasp-crs /etc/nginx/modsecurity/owasp-crs @@ -44,8 +44,8 @@ RUN mkcert -key-file /etc/nginx/certs/server.key -cert-file /etc/nginx/certs/ser COPY ./config/nginx.conf /etc/nginx/nginx.conf # RUN mkdir -p /etc/nginx/modules-load.d/ \ - # && echo 'load_module /usr/lib/nginx/modules/ngx_http_modsecurity_module.so;' > /etc/nginx/modules-load.d/00-modsecurity.conf \ - # && cat /etc/nginx/nginx.conf > /etc/nginx/nginx.conf.bak \ - # && cat /etc/nginx/modules-load.d/00-modsecurity.conf /etc/nginx/nginx.conf.bak > /etc/nginx/nginx.conf +# && echo 'load_module /usr/lib/nginx/modules/ngx_http_modsecurity_module.so;' > /etc/nginx/modules-load.d/00-modsecurity.conf \ +# && cat /etc/nginx/nginx.conf > /etc/nginx/nginx.conf.bak \ +# && cat /etc/nginx/modules-load.d/00-modsecurity.conf /etc/nginx/nginx.conf.bak > /etc/nginx/nginx.conf CMD ["nginx", "-g", "daemon off;"] diff --git a/nginx/config/modsecurity.conf b/nginx/config/modsecurity.conf index 72e4ee07..ec436ac4 100644 --- a/nginx/config/modsecurity.conf +++ b/nginx/config/modsecurity.conf @@ -2,7 +2,7 @@ Include /etc/nginx/modsecurity/owasp-crs/crs-setup.conf Include /etc/nginx/modsecurity/owasp-crs/rules/*.conf -SecRuleEngine DetectionOnly +SecRuleEngine On SecRequestBodyAccess On SecResponseBodyAccess On SecResponseBodyMimeType text/plain text/html text/xml From c092bfa5db2e4418a0d57c77d444dd68a9709819 Mon Sep 17 00:00:00 2001 From: Sejoon Kim <117820621+sejoonkimmm@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:57:40 +0900 Subject: [PATCH 10/27] fix: Update docker-compose.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 임원정 <90092181+LWJ0513@users.noreply.github.com> --- docker-compose.yml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a08ba42d..374b3089 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -66,24 +66,24 @@ services: depends_on: - hashicorp_vault - # nginx: - # build: ./nginx - # container_name: nginx_container - # volumes: - # - "./frontend:/usr/share/nginx/html" - # - static-volume:/usr/share/nginx/html/static - # - "./volumes/certs:/etc/nginx/certs" - # # - "/Users/sejokim/desktop/volumes/nginx/logs:/var/log/nginx" - # logging: - # options: - # max-size: "10m" - # max-file: "3" - # ports: - # - 443:443 # https 사용 - # depends_on: - # - django_node_app - # networks: - # - app_network + nginx: + build: ./nginx + container_name: nginx_container + volumes: + - "./frontend:/usr/share/nginx/html" + - static-volume:/usr/share/nginx/html/static + - "./volumes/certs:/etc/nginx/certs" + # - "/Users/sejokim/desktop/volumes/nginx/logs:/var/log/nginx" + logging: + options: + max-size: "10m" + max-file: "3" + ports: + - 443:443 # https 사용 + depends_on: + - django_node_app + networks: + - app_network # elasticsearch: # build: From e1f8dec2ed1fd741ca0609c1702b03422f50e1e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EC=9B=90=EC=A0=95?= Date: Sun, 25 Feb 2024 19:13:42 +0900 Subject: [PATCH 11/27] =?UTF-8?q?chore:=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/games/consumers.py | 4 ++-- backend/games/routing.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/games/consumers.py b/backend/games/consumers.py index e57c97aa..e3c8c337 100644 --- a/backend/games/consumers.py +++ b/backend/games/consumers.py @@ -15,7 +15,7 @@ logger = logging.getLogger(__name__) -class GameConsumer(AsyncWebsocketConsumer): +class GameRoomConsumer(AsyncWebsocketConsumer): def __init__(self, *args, **kwargs): super().__init__(args, kwargs) self.user = None @@ -196,7 +196,7 @@ async def game_info(self, event): await self.send(text_data=json.dumps({"data": data})) -class RankGameConsumer(AsyncWebsocketConsumer): +class RankGameRoomConsumer(AsyncWebsocketConsumer): game_queue = [] diff --git a/backend/games/routing.py b/backend/games/routing.py index e875bad3..335fcd46 100644 --- a/backend/games/routing.py +++ b/backend/games/routing.py @@ -4,5 +4,6 @@ websocket_urlpatterns = [ re_path(r"ws/games/(?P\w+)/", consumers.GameConsumer.as_asgi()), - path('ws/rankgames/', consumers.RankGameConsumer.as_asgi()), -] \ No newline at end of file + re_path(r"ws/games/(?P\w+)/", consumers.GameRoomConsumer.as_asgi()), + path('ws/rankgames/', consumers.RankGameRoomConsumer.as_asgi()), +] From dfb6384d0f52005a12e2a1c7d3934ae1ae49150d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EC=9B=90=EC=A0=95?= Date: Sun, 25 Feb 2024 19:14:18 +0900 Subject: [PATCH 12/27] feat: add game socket url #312 --- backend/games/routing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/games/routing.py b/backend/games/routing.py index 335fcd46..066222b8 100644 --- a/backend/games/routing.py +++ b/backend/games/routing.py @@ -3,7 +3,7 @@ from . import consumers websocket_urlpatterns = [ - re_path(r"ws/games/(?P\w+)/", consumers.GameConsumer.as_asgi()), + re_path(r"ws/games/start/(?P\w+)", consumers.GameConsumer.as_asgi()), re_path(r"ws/games/(?P\w+)/", consumers.GameRoomConsumer.as_asgi()), path('ws/rankgames/', consumers.RankGameRoomConsumer.as_asgi()), ] From 305921a2a09d3f444850051b8bc56f78a353edaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EC=9B=90=EC=A0=95?= Date: Sun, 25 Feb 2024 19:15:14 +0900 Subject: [PATCH 13/27] =?UTF-8?q?chore:=20=EC=A3=BC=EC=84=9D=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/asgi.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/backend/src/asgi.py b/backend/src/asgi.py index 012b0e17..964be3ba 100644 --- a/backend/src/asgi.py +++ b/backend/src/asgi.py @@ -22,14 +22,3 @@ ) } ) - -# application = ProtocolTypeRouter( -# { -# "http": django_asgi_app, -# "websocket": AllowedHostsOriginValidator( -# URLRouter( -# games.routing.websocket_urlpatterns + friends.routing.websocket_urlpatterns -# ) -# ) -# } -# ) \ No newline at end of file From 798ed91f09b1ea5f7ee8af90a69c99d048297b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EC=9B=90=EC=A0=95?= Date: Sun, 25 Feb 2024 19:15:44 +0900 Subject: [PATCH 14/27] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20serializer=20#312?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/games/serializers.py | 39 +++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/backend/games/serializers.py b/backend/games/serializers.py index 9eae5b90..29c7ca70 100644 --- a/backend/games/serializers.py +++ b/backend/games/serializers.py @@ -276,4 +276,41 @@ def get_match3(self, game): data = MatchSerializer(game.match3).data data['winner'] = game.match3.winner.nickname data['date'] = game.started_at - return data + return + + +class PvPMatchSerializer(serializers.ModelSerializer): + + match1 = serializers.SerializerMethodField() + + class Meta: + model = Game + fields = ['match1'] + + def get_match1(self, game): + players_data = [] + players_data.append(UserSerializer(game.match1.player1).data) + players_data.append(UserSerializer(game.match1.player2).data) + return players_data + + +class TournamentMatchSerializer(serializers.ModelSerializer): + + match1 = serializers.SerializerMethodField() + match2 = serializers.SerializerMethodField() + + class Meta: + model = Game + fields = ['match1', 'match2'] + + def get_match1(self, game): + players_data = [] + players_data.append(UserSerializer(game.match1.player1).data) + players_data.append(UserSerializer(game.match1.player2).data) + return players_data + + def get_match2(self, game): + players_data = [] + players_data.append(UserSerializer(game.match2.player1).data) + players_data.append(UserSerializer(game.match2.player2).data) + return players_data From 1b35a63c937ce572c92a20296bf6705aa9c6dc8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EC=9B=90=EC=A0=95?= Date: Sun, 25 Feb 2024 19:22:21 +0900 Subject: [PATCH 15/27] =?UTF-8?q?feat:=20game=20=EC=9E=85=EC=9E=A5,=20?= =?UTF-8?q?=EB=A7=A4=EC=B9=AD,=20=EA=B2=8C=EC=9E=84=20=EC=8B=9C=EC=9E=91?= =?UTF-8?q?=20#312?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/games/consumers.py | 317 ++++++++++++++++++++++++++++++++++++- 1 file changed, 312 insertions(+), 5 deletions(-) diff --git a/backend/games/consumers.py b/backend/games/consumers.py index e3c8c337..8c053891 100644 --- a/backend/games/consumers.py +++ b/backend/games/consumers.py @@ -1,16 +1,18 @@ import json import random +import logging import urllib.parse -from channels.generic.websocket import AsyncWebsocketConsumer -from games.models import Game, CasualGameView -from games.serializers import GameRoomSerializer +import asyncio +from django.contrib.auth.models import AnonymousUser from django.db.models import Min, Count from django.core.exceptions import ValidationError, PermissionDenied +from channels.generic.websocket import AsyncWebsocketConsumer from channels.db import database_sync_to_async from asgiref.sync import sync_to_async -import logging -from django.contrib.auth.models import AnonymousUser +from games.models import Game, CasualGameView, PingPongGame, Ball, Racket, Player, PingPongMap, Result +from games.serializers import GameRoomSerializer, PvPMatchSerializer, TournamentMatchSerializer from users.models import User +from datetime import datetime logger = logging.getLogger(__name__) @@ -260,3 +262,308 @@ async def game_message(self, event): await self.send(text_data=json.dumps({ 'message': event['message'] })) + + +class GameConsumer(AsyncWebsocketConsumer): + users = [] + match1_users = [] + match2_users = [] + match3_users = [] + + ping_pong_map = PingPongMap(0, 0) + match1 = PingPongGame( + Player(None, 0, Racket(0, 0, 0, 0, 0)), + Player(None, 0, Racket(0, 0, 0, 0, 0)), + ping_pong_map, + Ball(0, 0, 0) + ) + match2 = PingPongGame( + Player(None, 0, Racket(0, 0, 0, 0, 0)), + Player(None, 0, Racket(0, 0, 0, 0, 0)), + ping_pong_map, + Ball(0, 0, 0) + ) + match3 = PingPongGame( + Player(None, 0, Racket(0, 0, 0, 0, 0)), + Player(None, 0, Racket(0, 0, 0, 0, 0)), + ping_pong_map, + Ball(0, 0, 0) + ) + + def __init__(self, *args, **kwargs): + super().__init__(args, kwargs) + self.user = None + self.game = None + self.game_id = None + self.room_group_name = None + self.my_match = None + self.match1_group_name = None + self.match2_group_name = None + self.match3_group_name = None + + async def connect(self): + logger.info("[인게임] connect") + if await self.is_invalid_user(): + await self.reject_invalid_user() + else: + await self.process_valid_user() + + async def disconnect(self, close_code): + logger.info('[인게임] disconnect') + if self.user in self.users: + self.users.remove(self.user) + if self.user in self.match1_users: + self.match1_users.remove(self.user) + elif self.user in self.match2_users: + self.match2_users.remove(self.user) + if self.user in self.match1_users: + self.match3_users.remove(self.user) + # todo 게임중이면 패배 처리 + # await self.channel_layer.group_discard(self.room_group_name, self.channel_name) + + async def is_invalid_user(self): + self.user = self.scope['user'] + return isinstance(self.user, AnonymousUser) + + async def reject_invalid_user(self): + await self.accept() + await self.send(text_data=json.dumps({"error": "Invalid user"})) + logger.info("[인게임] Invalid user") + await self.close() + + async def process_valid_user(self): + try: + await self.accept() + game_id = self.scope["url_route"]["kwargs"]["game_id"] + self.game_id = int(game_id) + await self.save_game_object_by_id() + await self.validate_user(self.user.nickname) + self.users.append(self.user) + + self.room_group_name = f"ingame_{self.game_id}" + self.match1_group_name = f"match1_{self.game_id}" + self.match2_group_name = f"match2_{self.game_id}" + self.match3_group_name = f"match3_{self.game_id}" + await self.channel_layer.group_add(self.room_group_name, self.channel_name) + + if self.game.status == 1: + await self.set_game_status() + if self.game.mode == 0: # 1e1 + self.my_match = 1 + await self.save_match() + await self.channel_layer.group_add(self.match1_group_name, self.channel_name) + else: # tournament + if (len(self.users) + 1) % 2 == 0: + self.my_match = 1 + await self.save_match() + await self.channel_layer.group_add(self.match1_group_name, self.channel_name) + else: + self.my_match = 2 + await self.save_match() + await self.channel_layer.group_add(self.match2_group_name, self.channel_name) + + logger.info(f"users[] = {self.users}") + if (self.game.mode == 0 and len(self.users) == 2) or (self.game.mode != 0 and len(self.users) == 4): + serializer_data = await self.get_serializer_data() + await self.channel_layer.group_send( + self.room_group_name, + { + 'type': 'game_info', + 'data': serializer_data + } + ) + except Exception as e: + await self.send(text_data=json.dumps({"error": "[" + e.__class__.__name__ + "] " + str(e)})) + await self.close() + + @database_sync_to_async + def save_game_object_by_id(self): + self.game = Game.objects.get(id=self.game_id) + # todo 게임방이 꽉찬 방이 아닐 때.. 같은 이런 예외처리를 해야 하나 + + @database_sync_to_async + def validate_user(self, user): + if self.game.manager.nickname == user: + return + elif self.game.player1.nickname == user: + return + elif self.game.mode == 1 and (self.game.player2.nickname == user or self.game.player3.nickname == user): + return + else: + raise Exception("게임 방에 속한 유저가 아닙니다.") + + @database_sync_to_async + def save_match(self): + if self.my_match == 1: + if self.game.match1 is None: + match = Result.objects.create(player1=self.user) + self.game.match1 = match + self.game.save() + else: + self.game.match1.player2 = self.user + self.game.match1.save() + else: + if self.game.match2 is None: + match = Result.objects.create(player1=self.user) + self.game.match2 = match + self.game.save() + else: + self.game.match2.player2 = self.user + self.game.match2.save() + + @database_sync_to_async + def set_game_status(self): + self.game.status = 2 + self.game.save() + + @database_sync_to_async + def get_serializer_data(self): + serializer = None + if self.game.mode == 0: + serializer = PvPMatchSerializer(self.game) + else: + serializer = TournamentMatchSerializer(self.game) + return serializer.data + + async def receive(self, text_data): + data = json.loads(text_data) + message_type = data.get('type') + message_data = data.get('data', {}) + await self.save_game_object_by_id() + if message_type == 'start': + if self.ping_pong_map.width == 0: + await self.set_values(message_data) + if self.my_match == 1: + self.match1_users.append(self.user) + elif self.my_match == 2: + self.match2_users.append(self.user) + + # 게임 시작 + if self.game.mode == 0 and len(self.match1_users) == 2: + await self.channel_layer.group_send( + self.match1_group_name, + { + 'type': 'gamestart', + 'data': '게임 스탓토' + } + ) + await self.gamestart({ + 'type': 'gamestart', + 'data': '게임 스탓토' + }) + await asyncio.sleep(2) + self.match1.started_at = datetime.now() + while True: + # todo 겜 로직 + # if 겜 끝나면 탈출 + await self.channel_layer.group_send( + self.match1_group_name, + { + 'type': 'in_game', + 'data': '화면 그릴 때 필요한 정보 serializer' + } + ) + await self.in_game({ + 'type': 'in_game', + 'data': '화면 그릴 때 필요한 정보 serializer' + }) + await asyncio.sleep(1 / 24) + + elif self.game.mode != 0 and len(self.match1_users) == 2 and len(self.match2_users) == 2: + await self.channel_layer.group_send( + self.match1_group_name, + { + 'type': 'gamestart', + 'data': '게임 스탓토 serializer serializer' + } + ) + await self.channel_layer.group_send( + self.match2_group_name, + { + 'type': 'gamestart', + 'data': '게임 스탓토 serializer serializer' + } + ) + await self.gamestart({ + 'type': 'gamestart', + 'data': '게임 스탓토 serializer' + }) + await asyncio.sleep(2) + self.match1.started_at = datetime.now() + self.match2.started_at = datetime.now() + while True: + # todo 겜 로직 + await self.channel_layer.group_send( + self.match1_group_name, + { + 'type': 'in_game', + 'data': 'match1 info' + } + ) + await self.channel_layer.group_send( + self.match2_group_name, + { + 'type': 'in_game', + 'data': 'match2 info' + } + ) + if self.my_match == 1: + await self.in_game({ + 'type': 'in_game', + 'data': 'match1 info' + }) + else: + await self.in_game({ + 'type': 'in_game', + 'data': 'match2 info' + }) + await asyncio.sleep(1 / 24) + + async def set_values(self, message_data): + map_width = message_data['map_width'] + map_height = message_data['map_height'] + + self.ping_pong_map.width = map_width + self.ping_pong_map.height = map_height + + await self.set_match(self.match1, message_data, 1) + if self.game.mode != 0: + await self.set_match(self.match2, message_data, 2) + await self.set_match(self.match3, message_data, 3) + + @database_sync_to_async + def set_match(self, match, data, match_num): + if match_num == 1: + match.left_side_player.user = self.game.match1.player1 + match.right_side_player.user = self.game.match1.player2 + elif match_num == 2: + match.left_side_player.user = self.game.match2.player1 + match.right_side_player.user = self.game.match2.player2 + + match.left_side_player.racket.width = data['racket_width'] + match.left_side_player.racket.height = data['racket_height'] + match.left_side_player.racket.x = data['left_racket_x'] + match.left_side_player.racket.y = data['left_racket_y'] + match.left_side_player.racket.speed = data['racket_speed'] + + match.right_side_player.racket.width = data['racket_width'] + match.right_side_player.racket.height = data['racket_height'] + match.right_side_player.racket.x = data['right_racket_x'] + match.right_side_player.racket.y = data['right_racket_y'] + match.right_side_player.racket.speed = data['racket_speed'] + + match.ball.radius = data['ball_radius'] + match.ball.x = data['ball_x'] + match.ball.y = data['ball_y'] + + async def game_info(self, event): + data = event["data"] + await self.send(text_data=json.dumps({"data": data})) + + async def gamestart(self, event): + data = event["data"] + await self.send(text_data=json.dumps({"data": data})) + + async def in_game(self, event): + data = event["data"] + await self.send(text_data=json.dumps({"data": data})) From 81aa1382b4a07b75b8b56cf84f2ae7b99f9d9c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EC=9B=90=EC=A0=95?= Date: Sun, 25 Feb 2024 19:22:40 +0900 Subject: [PATCH 16/27] =?UTF-8?q?fix:=20=EB=A1=A4=EB=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/requirements.txt | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 2c269165..3084b4e3 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,43 +1,30 @@ asgiref==3.7.2 -attrs==23.2.0 -autobahn==23.6.2 -Automat==22.10.0 certifi==2023.11.17 cffi==1.16.0 channels==4.0.0 charset-normalizer==3.3.2 -click==8.1.7 -constantly==23.10.4 -cryptography==41.0.7 +cryptography==42.0.0 daphne==4.0.0 defusedxml==0.8.0rc2 dj-rest-auth==5.0.2 -Django==4.2.8 +Django==4.2.10 django-allauth==0.60.1 django-cors-headers==4.3.1 django-debug-toolbar==4.2.0 django-environ==0.11.2 -django-otp==1.3.0 djangorestframework==3.14.0 djangorestframework-simplejwt==5.3.1 drf-yasg==1.21.7 -gunicorn==21.2.0 -h11==0.14.0 hvac==2.1.0 -hyperlink==21.0.0 idna==3.6 -incremental==22.10.0 inflection==0.5.1 oauthlib==3.2.2 packaging==23.2 pillow==10.2.0 psycopg==3.1.16 psycopg2-binary==2.9.9 -pyasn1==0.5.1 -pyasn1-modules==0.3.0 pycparser==2.21 PyJWT==2.8.0 -pyOpenSSL==24.0.0 pypng==0.20220715.0 python-decouple==3.8 python-environ==0.4.54 @@ -49,17 +36,9 @@ PyYAML==6.0.1 qrcode==7.4.2 requests==2.31.0 requests-oauthlib==1.3.1 -service-identity==24.1.0 -setuptools==69.0.3 -six==1.16.0 social-auth-app-django==5.4.0 social-auth-core==4.5.1 sqlparse==0.4.4 -supervisor==4.2.5 -Twisted==23.10.0 -txaio==23.1.1 typing_extensions==4.9.0 uritemplate==4.1.1 -urllib3==2.1.0 -uvicorn==0.27.0.post1 -zope.interface==6.1 +urllib3==2.1.0 \ No newline at end of file From be31fd05a8548a047f40517e03f178a3f2e83fdd Mon Sep 17 00:00:00 2001 From: donghyun kim Date: Mon, 26 Feb 2024 13:25:22 +0900 Subject: [PATCH 17/27] =?UTF-8?q?feat:=20waiting=20room=EC=97=90=20usestat?= =?UTF-8?q?e=20=EC=86=8C=EC=BC=93=20=EC=9D=B4=EC=8B=9D=20(#324)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 새로고침누르면 /game-mode로 보내버림 이후에 다 * feat: 방 이름 누락 시 alert 뜨게 * chore: console.log 다 제거 * feat: waiting-room 초기렌더링 완료 * feat: onmessage로 재렌더링 등록 * refactor: wss는 websocketserver의 약자라고 하네요 ws로변경 * feat: waiting-room에서 unmount시 ws.close --- frontend/src/pages/custom-game-list/page.js | 24 +++-- frontend/src/pages/waiting-room/page.js | 103 +++++--------------- frontend/src/pages/waiting-room/userBox.js | 14 +-- frontend/src/pages/waiting-room/userUnit.js | 10 +- 4 files changed, 45 insertions(+), 106 deletions(-) diff --git a/frontend/src/pages/custom-game-list/page.js b/frontend/src/pages/custom-game-list/page.js index b95f2c8c..6576282a 100644 --- a/frontend/src/pages/custom-game-list/page.js +++ b/frontend/src/pages/custom-game-list/page.js @@ -104,7 +104,6 @@ export default function CustomGameList($container) { document .getElementById("pwd-input") .addEventListener("keydown", (e) => { - console.log(e.key); if (e.key === "Enter") { enterRoom(data.id, e.target.value); } @@ -118,24 +117,24 @@ export default function CustomGameList($container) { }; const enterRoom = (id, password = null) => { - let wss; + let ws; if (password != null) - wss = new WebSocket( + ws = new WebSocket( `wss://localhost:443/ws/games/${id}/?password=${password}`, ); - else wss = new WebSocket(`wss://localhost:443/ws/games/${id}/`); + else ws = new WebSocket(`wss://localhost:443/ws/games/${id}/`); - wss.onmessage = (event) => { + ws.onmessage = (event) => { const res = JSON.parse(event.data); if (res.error) { - console.log(res.error); if (res.error === "[PermissionDenied] can't access") alert("비밀번호가 틀렸습니다."); return; } - res.socket = wss; - wss.onmessage = null; + res.socket = ws; + if (password != null) res.password = password; + ws.onmessage = null; navigate("/waiting-room", res); }; }; @@ -287,7 +286,6 @@ export default function CustomGameList($container) { const $makeRoomBtn = document.getElementById("make-room-btn"); click($1vs1ModeBtn, () => { - console.log("1vs1"); $tournamentModeBtn.style.opacity = "0.5"; $1vs1ModeBtn.style.opacity = "1"; }); @@ -295,14 +293,16 @@ export default function CustomGameList($container) { click($tournamentModeBtn, () => { $1vs1ModeBtn.style.opacity = "0.5"; $tournamentModeBtn.style.opacity = "1"; - console.log("tournament"); }); click($makeRoomBtn, () => { const $roomNameInput = document.getElementById("room-name-input"); const $passwordInput = document.getElementById("password-input"); - // TODO: title '' 일때 처리 + if ($roomNameInput.value === "") { + alert("방 이름을 입력해주세요."); + return; + } fetch(`${BACKEND}/games/`, { method: "POST", headers: { @@ -321,12 +321,10 @@ export default function CustomGameList($container) { if (res.status === 201) { return res.json(); } else { - console.log(res); throw new Error("방 만들기 실패"); } }) .then((res) => { - console.log(res); enterRoom(res.id, $passwordInput.value); }); }); diff --git a/frontend/src/pages/waiting-room/page.js b/frontend/src/pages/waiting-room/page.js index 6124b8ec..6bdafe5c 100644 --- a/frontend/src/pages/waiting-room/page.js +++ b/frontend/src/pages/waiting-room/page.js @@ -3,89 +3,26 @@ import { hover } from "../../utils/hoverEvent.js"; import userBox from "./userBox.js"; import countdownModal from "./countdownModal.js"; import useState from "../../utils/useState.js"; -import { click } from "../../utils/clickEvent.js"; +import { navigate } from "../../utils/navigate.js"; /** * @param {HTMLElement} $container * @param {object} info */ -export default function WaitingRoom($container, info) { - let roomTitle = "방 제목"; - let gameMode = "게임 모드"; - let password = "password"; - let gameModeNum = 4; +export default function WaitingRoom($container, info = null) { + const gameModeNum = info.data.mode; + // 새로고침 누르면 game-mode로 이동 + if (info !== null) { + navigate("/game-mode"); + } + const ws = info.socket; console.log(info); - let props = [ - { - img: "../../../assets/images/avatar/red_bust.png", - nickname: "donghyk2", - rating: 2400, - status: "오우-너", - color: "yellow", - }, - { - img: "../../../assets/images/avatar/blue_bust.png", - nickname: "john", - rating: 2200, - status: "준비 중", - color: "white", - }, - { - img: "../../../assets/images/avatar/yellow_bust.png", - nickname: "bob", - rating: 2300, - status: "준비 중", - color: "white", - }, - { - img: "../../../assets/images/avatar/green_bust.png", - nickname: "garry", - rating: 2300, - status: "준비 중", - color: "white", - }, - ]; + let props = info.data.players; - let props2 = [ - { - img: "../../../assets/images/avatar/red_bust.png", - nickname: "업데이트이름", - rating: 240, - status: "오우-너", - color: "yellow", - }, - { - img: "../../../assets/images/avatar/blue_bust.png", - nickname: "업데이트이름", - rating: 2200, - status: "비 중", - color: "white", - }, - { - img: "../../../assets/images/avatar/yellow_bust.png", - nickname: "업데이트이름", - rating: 2300, - status: "준비 중", - color: "white", - }, - { - img: "../../../assets/images/avatar/green_bust.png", - nickname: "업데이트이름", - rating: 2300, - status: "준비 중", - color: "white", - }, - ]; - - // let connectWebSocket = () => { - // this.ws = new WebSocket('wss://' + - // window.location.host + - // '/ws/games/' + - // roomName); - // }; const init = () => { - // connectWebSocket(); render(); + if (info.password) + $container.querySelector(".room-lock").style.display = "block"; hover( $container.querySelector(".room-lock"), () => { @@ -96,22 +33,26 @@ export default function WaitingRoom($container, info) { $container.querySelector(".room-password-modal").style.display = "none"; }, ); - click($container.querySelector(".start-btn"), () => { - setUserState(props2); - }); + ws.onmessage = (msg) => { + let data = JSON.parse(msg.data); + setUserState(data.data.players); + }; }; + this.unmount = () => { + ws.close(); + }; const render = () => { importCss("../../../assets/css/waiting-room.css"); $container.innerHTML = ` ${countdownModal(false)}
-
- lock -
${roomTitle} | ${gameMode}
+
+ +
${info.data.title} [${info.data.mode === 1 ? "토너먼트" : "1vs1"}]
${userBox(gameModeNum, props)} diff --git a/frontend/src/pages/waiting-room/userBox.js b/frontend/src/pages/waiting-room/userBox.js index 6caa6b29..bbf0b75e 100644 --- a/frontend/src/pages/waiting-room/userBox.js +++ b/frontend/src/pages/waiting-room/userBox.js @@ -5,30 +5,30 @@ import userUnit from "./userUnit.js"; * @param {array} props 유저 정보 */ export default function userBox(gameMode, props) { - if (gameMode === 2) { + if (gameMode === 0) { return `
- ${userUnit(props[0])} + ${userUnit(props[0] || null)}
user
- ${userUnit(props[1])} + ${userUnit(props[1] || null)}
`; } else { return `
- ${userUnit(props[0])} + ${userUnit(props[0] || null)}
user
- ${userUnit(props[1])} + ${userUnit(props[1] || null)}
- ${userUnit(props[2])} + ${userUnit(props[2] || null)}
user
- ${userUnit(null)} + ${userUnit(props[3] || null)}
`; } diff --git a/frontend/src/pages/waiting-room/userUnit.js b/frontend/src/pages/waiting-room/userUnit.js index 987c85cb..75ea39ee 100644 --- a/frontend/src/pages/waiting-room/userUnit.js +++ b/frontend/src/pages/waiting-room/userUnit.js @@ -1,6 +1,6 @@ /** * - * @param {img, nickname, rating, status} props + * @param {nickname, avatar, rating, is_manager} props */ export default function userUnit(props) { // 빈 유저 칸 @@ -11,14 +11,14 @@ export default function userUnit(props) { ???
`; } - let { img, nickname, rating, status, color } = props; + let { nickname, avatar, rating, is_manager } = props; return `
- user + user
${nickname}
Rating: ${rating}
-
-
${status}
+
+
`; } From c726de51e65bf2d707640eeed03480a810c1f08b Mon Sep 17 00:00:00 2001 From: sejoonkimmm Date: Mon, 26 Feb 2024 14:20:25 +0900 Subject: [PATCH 18/27] =?UTF-8?q?build=20:=20=EB=B3=BC=EB=A5=A8=20path?= =?UTF-8?q?=EB=93=A4=20=EC=98=88=EC=81=98=EA=B2=8C=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=ED=95=A8=20/=20init.sh=EC=97=90=20=ED=99=98=EA=B2=BD=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=ED=95=98=EB=82=98=20=EC=88=98=EC=A0=95=20/=20?= =?UTF-8?q?=ED=94=84=EB=A1=A0=ED=8A=B8=EC=97=94=EB=93=9C=20=EC=A0=84?= =?UTF-8?q?=EC=97=AD=EB=B3=80=EC=88=98=20=EB=B6=84=EA=B8=B0=EB=B3=84?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95=ED=95=98=EC=97=AC=20=EC=9B=B9?= =?UTF-8?q?=EC=86=8C=EC=BC=93=20/=20API=20=EB=82=98=EB=89=98=EA=B2=8C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 42 +++++++++------------ frontend/src/global.js | 15 +++++--- frontend/src/header/mainHeader/header.js | 2 +- frontend/src/pages/custom-game-list/page.js | 4 +- frontend/src/pages/game-mode/page.js | 2 +- init.sh | 2 + 6 files changed, 32 insertions(+), 35 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 374b3089..bdac2c87 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,14 +7,13 @@ services: dockerfile: Dockerfile ports: - 8200:8200 + env_file: + - .env volumes: - # - "/Users/sejokim/desktop/volumes/vault/file:/vault/file" - # - "/Users/sejokim/desktop/volumes/vault/logs:/vault/logs" - # - "/Users/sejokim/desktop/volumes/vault/config:/vault/config" - - "./volumes/vault/file:/vault/file" - - "./volumes/vault/logs:/vault/logs" - - "./volumes/vault/config:/vault/config" - - "./volumes/certs:/vault/certs" + - ${VAULT_FILE_PATH}:/vault/file + - "${VAULT_LOG_PATH}:/vault/logs" + - "${VAULT_TOKEN_PATH}:/vault/config" + - "${CERTS_PATH}:/vault/certs" environment: VAULT_ADDR: "https://hashicorp_vault:8200" @@ -32,9 +31,9 @@ services: - app_network volumes: - static-volume:/backend/static - - "./volumes/certs:/backend/certs" + - "${CERTS_PATH}:/backend/certs" env_file: - - ./.env + - .env environment: # DB DB_HOST: ${DB_HOST} @@ -60,7 +59,6 @@ services: INTRA42_CLIENT_ID: ${INTRA42_CLIENT_ID} INTRA42_CLIENT_SECRET: ${INTRA42_CLIENT_SECRET} INTRA42_USERINFO_API: ${INTRA42_USERINFO_API} - DJANGO_SETTING_MODULE: "src.settings" BASE_URL: ${BASE_URL} depends_on: @@ -70,10 +68,10 @@ services: build: ./nginx container_name: nginx_container volumes: - - "./frontend:/usr/share/nginx/html" + - "${FRONTEND_CODE}:/usr/share/nginx/html" - static-volume:/usr/share/nginx/html/static - - "./volumes/certs:/etc/nginx/certs" - # - "/Users/sejokim/desktop/volumes/nginx/logs:/var/log/nginx" + - "${CERTS_PATH}:/etc/nginx/certs" + - "${NGINX_LOGS}:/var/log/nginx" logging: options: max-size: "10m" @@ -90,10 +88,8 @@ services: # context: ./elk/elasticsearch # dockerfile: Dockerfile # volumes: - # # - "/Users/sejokim/desktop/volumes/elasticsearch/data:/usr/share/elasticsearch/data" - # # - "/Users/sejokim/desktop/volumes/elasticsearch/certs:/usr/share/elasticsearch/config/certs" - # - "./volumes/elasticsearch/data:/usr/share/elasticsearch/data" - # - "./volumes/elasticsearch/certs:/usr/share/elasticsearch/config/certs" + # - "${ELASTIC_DATA}:/usr/share/elasticsearch/data" + # - "${ELASTIC_CERTS}:/usr/share/elasticsearch/config/certs" # environment: # discovery.type : single-node # ELASTIC_PW : ${ELASTIC_PW} @@ -115,10 +111,8 @@ services: # ELASTIC_PW: ${ELASTIC_PW} # ES_CERTS_PW: ${ES_CERTS_PW} # volumes: - # # - "/Users/sejokim/desktop/volumes/elasticsearch/certs:/usr/share/logstash/config/certs" - # # - "/Users/sejokim/desktop/volumes/nginx/logs:/usr/share/logstash/logs" - # - "./volumes/elasticsearch/certs:/usr/share/logstash/config/certs" - # - "./volumes/nginx/logs:/usr/share/logstash/logs" + # - "${ELASTIC_CERTS}:/usr/share/logstash/config/certs" + # - "${NGINX_LOGS}:/usr/share/logstash/logs" # ports: # - 5333:5333 # depends_on: @@ -130,10 +124,8 @@ services: # build: ./elk/kibana # container_name: kibana_container # volumes: - # # - "/Users/sejokim/desktop/volumes/kibana/data:/usr/share/kibana/data" - # # - "/Users/sejokim/desktop/volumes/elasticsearch/certs:/usr/share/kibana/config/certs" - # - "./volumes/kibana/data:/usr/share/kibana/data" - # - "./volumes/elasticsearch/certs:/usr/share/kibana/config/certs" + # - "${KIBANA_DATA}:/usr/share/kibana/data" + # - "${ELASTIC_CERTS}:/usr/share/kibana/config/certs" # environment: # ELASTICSEARCH_URL: https://elasticsearch:9200 # ELASTIC_PW: ${ELASTIC_PW} diff --git a/frontend/src/global.js b/frontend/src/global.js index d6bfce59..be98409c 100644 --- a/frontend/src/global.js +++ b/frontend/src/global.js @@ -1,15 +1,18 @@ const devBackend = "http://localhost:8000/api"; -const prodBackend = "https://localhost/api"; +const devWebSocket = "ws://localhost:8000/ws" +const prodBackend = "https://localhost:443/api"; +const prodWebSocket = "wss://localhost:443/ws"; const getAPIUrl = () => { - if (window.mode === "dev") { - return devBackend; - } else { - return prodBackend; - } + return window.mode === "dev" ? devBackend : prodBackend; +}; + +const getWebsocketUrl = () => { + return window.mode === "dev" ? devWebSocket : prodWebSocket; }; export const BACKEND = getAPIUrl(); +export const WEBSOCKET = getWebsocketUrl(); export const HISTORIES_IMAGE_PATH = "../../../assets/images"; export const MODE = { diff --git a/frontend/src/header/mainHeader/header.js b/frontend/src/header/mainHeader/header.js index bddb74d5..5ad1f097 100644 --- a/frontend/src/header/mainHeader/header.js +++ b/frontend/src/header/mainHeader/header.js @@ -27,7 +27,7 @@ export default function MainHeader($container) { setUserInfo(data); }); // alert("웹소켓 연결!"); - new WebSocket("wss://localhost/ws/friend_status/"); + new WebSocket("{WEBSOCKET}/ws/friend_status/"); } else { // TODO => 에러 페이지로 이동 navigate("/"); diff --git a/frontend/src/pages/custom-game-list/page.js b/frontend/src/pages/custom-game-list/page.js index b95f2c8c..f80b6d77 100644 --- a/frontend/src/pages/custom-game-list/page.js +++ b/frontend/src/pages/custom-game-list/page.js @@ -122,9 +122,9 @@ export default function CustomGameList($container) { if (password != null) wss = new WebSocket( - `wss://localhost:443/ws/games/${id}/?password=${password}`, + `${WEBSOCKET}/ws/games/${id}/?password=${password}`, ); - else wss = new WebSocket(`wss://localhost:443/ws/games/${id}/`); + else wss = new WebSocket(`${WEBSOCKET}:443/ws/games/${id}/`); wss.onmessage = (event) => { const res = JSON.parse(event.data); diff --git a/frontend/src/pages/game-mode/page.js b/frontend/src/pages/game-mode/page.js index 856431e1..2f4f492d 100644 --- a/frontend/src/pages/game-mode/page.js +++ b/frontend/src/pages/game-mode/page.js @@ -14,7 +14,7 @@ export default function GameMode($container) { $container.querySelector(".queue-modal").style.display = "flex"; $container.querySelector(".modal-backdrop").style.display = "block"; console.log("clicked"); - this.ws = new WebSocket("wss://localhost/ws/rankgames/"); + this.ws = new WebSocket("${WEBSOCKET}/ws/rankgames/"); console.log("ws ok"); this.ws.onmessage = (event) => { console.log(event.data); diff --git a/init.sh b/init.sh index 51693114..7ff261bb 100644 --- a/init.sh +++ b/init.sh @@ -1,5 +1,7 @@ #!/bin/sh +export DJANGO_SETTINGS_MODULE=src.settings + python manage.py collectstatic --noinput daphne -b 0.0.0.0 -e ssl:8443:privateKey=/backend/certs/server.key:certKey=/backend/certs/server.crt src.asgi:application \ No newline at end of file From 69a999051709ee0a23814940eda28a55c7d06be3 Mon Sep 17 00:00:00 2001 From: sejoonkimmm Date: Mon, 26 Feb 2024 16:47:17 +0900 Subject: [PATCH 19/27] =?UTF-8?q?hotfix:=20/ws=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/header/mainHeader/header.js | 2 +- frontend/src/pages/custom-game-list/page.js | 4 ++-- frontend/src/pages/game-mode/page.js | 2 +- nginx/config/nginx.conf | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/header/mainHeader/header.js b/frontend/src/header/mainHeader/header.js index 5ad1f097..52d8a1e0 100644 --- a/frontend/src/header/mainHeader/header.js +++ b/frontend/src/header/mainHeader/header.js @@ -27,7 +27,7 @@ export default function MainHeader($container) { setUserInfo(data); }); // alert("웹소켓 연결!"); - new WebSocket("{WEBSOCKET}/ws/friend_status/"); + new WebSocket("{WEBSOCKET}/friend_status/"); } else { // TODO => 에러 페이지로 이동 navigate("/"); diff --git a/frontend/src/pages/custom-game-list/page.js b/frontend/src/pages/custom-game-list/page.js index 9d25af42..a0cc7a4f 100644 --- a/frontend/src/pages/custom-game-list/page.js +++ b/frontend/src/pages/custom-game-list/page.js @@ -121,9 +121,9 @@ export default function CustomGameList($container) { if (password != null) wss = new WebSocket( - `${WEBSOCKET}/ws/games/${id}/?password=${password}`, + `${WEBSOCKET}/games/${id}/?password=${password}`, ); - else ws = new WebSocket(`${WEBSOCKET}:443/ws/games/${id}/`); + else ws = new WebSocket(`${WEBSOCKET}/games/${id}/`); ws.onmessage = (event) => { diff --git a/frontend/src/pages/game-mode/page.js b/frontend/src/pages/game-mode/page.js index 2f4f492d..859d9d68 100644 --- a/frontend/src/pages/game-mode/page.js +++ b/frontend/src/pages/game-mode/page.js @@ -14,7 +14,7 @@ export default function GameMode($container) { $container.querySelector(".queue-modal").style.display = "flex"; $container.querySelector(".modal-backdrop").style.display = "block"; console.log("clicked"); - this.ws = new WebSocket("${WEBSOCKET}/ws/rankgames/"); + this.ws = new WebSocket("${WEBSOCKET}/rankgames/"); console.log("ws ok"); this.ws.onmessage = (event) => { console.log(event.data); diff --git a/nginx/config/nginx.conf b/nginx/config/nginx.conf index bfbfcd49..33f75f35 100644 --- a/nginx/config/nginx.conf +++ b/nginx/config/nginx.conf @@ -26,7 +26,7 @@ http { '}'; # access_log /var/log/nginx/access.log json_combined buffer=32k; - access_log /dev/stdout; + # access_log /dev/stdout; sendfile on; From 37c5546b3fa62ef44c43823c029366945e518e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EC=9B=90=EC=A0=95?= Date: Mon, 26 Feb 2024 14:39:55 +0900 Subject: [PATCH 20/27] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91=20=EB=B2=84=ED=8A=BC=20=EB=88=8C=EB=A0=80=EC=9D=84=20?= =?UTF-8?q?=EB=95=8C=20=EC=86=8C=EC=BC=93=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/games/consumers.py | 11 +++++++++++ frontend/src/pages/waiting-room/page.js | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/backend/games/consumers.py b/backend/games/consumers.py index 8c053891..7de982d9 100644 --- a/backend/games/consumers.py +++ b/backend/games/consumers.py @@ -118,6 +118,17 @@ async def process_valid_user(self): await self.send(text_data=json.dumps({"error": "[" + e.__class__.__name__ + "] " + str(e)})) await self.close() + async def receive(self, text_data): + data = json.loads(text_data) + message_type = data.get('type') + message_data = data.get('data', {}) + if message_type == 'game_start' and message_data == 'true': + await self.send_url({'url': f"/ws/games/start/{self.game_id}/"}) + + async def send_url(self, event): + data = event["url"] + await self.send(text_data=json.dumps({"url": data})) + @database_sync_to_async def count_casual_games(self): return CasualGameView.objects.aggregate(Count('game_id'))['game_id__count'] diff --git a/frontend/src/pages/waiting-room/page.js b/frontend/src/pages/waiting-room/page.js index 6bdafe5c..c61d0678 100644 --- a/frontend/src/pages/waiting-room/page.js +++ b/frontend/src/pages/waiting-room/page.js @@ -4,6 +4,7 @@ import userBox from "./userBox.js"; import countdownModal from "./countdownModal.js"; import useState from "../../utils/useState.js"; import { navigate } from "../../utils/navigate.js"; +import {click} from "../../utils/clickEvent.js"; /** * @param {HTMLElement} $container * @param {object} info @@ -37,6 +38,12 @@ export default function WaitingRoom($container, info = null) { let data = JSON.parse(msg.data); setUserState(data.data.players); }; + click($container.querySelector(".start-btn"), () => { + ws.send(JSON.stringify({ + 'type' : 'game_start', + 'data' : 'true' + })); + }); }; this.unmount = () => { From 2cd629f745d292f8886b57286966452c3f3e6471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EC=9B=90=EC=A0=95?= Date: Mon, 26 Feb 2024 17:00:03 +0900 Subject: [PATCH 21/27] =?UTF-8?q?fix:=20response=20=EC=A3=BC=EC=86=8C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/games/consumers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/games/consumers.py b/backend/games/consumers.py index 7de982d9..4aa273cc 100644 --- a/backend/games/consumers.py +++ b/backend/games/consumers.py @@ -123,7 +123,7 @@ async def receive(self, text_data): message_type = data.get('type') message_data = data.get('data', {}) if message_type == 'game_start' and message_data == 'true': - await self.send_url({'url': f"/ws/games/start/{self.game_id}/"}) + await self.send_url({'url': f"/games/start/{self.game_id}/"}) async def send_url(self, event): data = event["url"] From 36e9e5aa312c4d0118f962bf36b15014f95ba644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EC=9B=90=EC=A0=95?= Date: Mon, 26 Feb 2024 17:03:09 +0900 Subject: [PATCH 22/27] =?UTF-8?q?fix:=20rankgame=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/games/consumers.py | 43 +++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/backend/games/consumers.py b/backend/games/consumers.py index 4aa273cc..a44d4b87 100644 --- a/backend/games/consumers.py +++ b/backend/games/consumers.py @@ -215,18 +215,22 @@ class RankGameRoomConsumer(AsyncWebsocketConsumer): async def connect(self): user = self.scope['user'] + self.room_group_name = 'game_queue' + await self.channel_layer.group_add( + self.room_group_name, + self.channel_name, + ) + await self.accept() if isinstance(user, AnonymousUser): await self.close(code=4001) return - await self.accept() self.game_queue.append(user.id) logging.info(f'[RANK] User {user.nickname} 연결되었어요') - + if len(self.game_queue) >= 4: try: await self.create_game() self.game_queue.clear() - logging.info('[RANK] 게임이 생성되고 대기열이 모두 초기화되었어요.') except Exception as e: logging.error(e) @@ -234,26 +238,25 @@ async def disconnect(self, close_code): user = self.scope['user'] if user.id in self.game_queue: self.game_queue.remove(user.id) - logging.info(f'[RANK] User {user.nickname} 이 연결을 끊었어요.') - + async def create_game(self): + logging.info(f"[RANK] Creating game with users: {self.game_queue[:4]}") users = await sync_to_async(self.get_users)(self.game_queue[:4]) game = await sync_to_async(self.create_game_instance)(users) - for user_id in self.game_queue[:4]: - channel = f'user_{user_id}' - await self.channel_layer.group_add(channel, self.channel_name) - await self.channel_layer.group_send( - channel, - { - 'type': 'game_message', - 'message': {"game_id": game.id} - } - ) + game_url = f"/games/{game.id}/" + + await self.channel_layer.group_send( + self.room_group_name, + { + 'type': 'game_start', + 'url': game_url, + } + ) @staticmethod def get_users(user_ids): return [User.objects.get(id=user_id) for user_id in user_ids] - + @staticmethod def create_game_instance(users): try: @@ -268,10 +271,12 @@ def create_game_instance(users): return game except Exception as e: raise e - - async def game_message(self, event): + + async def game_start(self, event): + url = event['url'] await self.send(text_data=json.dumps({ - 'message': event['message'] + 'type': 'game_start', + 'url': url, })) From f20245c0593bd280b86df450c96c9835024ea85c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EC=9B=90=EC=A0=95?= Date: Mon, 26 Feb 2024 17:03:35 +0900 Subject: [PATCH 23/27] =?UTF-8?q?feat:=20=EB=AA=A8=EB=8D=B8=EC=97=90=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/games/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/games/models.py b/backend/games/models.py index f9b6fcd8..5562c1ad 100644 --- a/backend/games/models.py +++ b/backend/games/models.py @@ -274,6 +274,7 @@ class PingPongGame: ping_pong_map: PingPongMap ball: Ball started_at: datetime + finished: False def __init__(self, left_side_player: Player, From 234507514751d10105b97d6ed4fdb745e18403e4 Mon Sep 17 00:00:00 2001 From: sejoonkimmm Date: Mon, 26 Feb 2024 17:36:14 +0900 Subject: [PATCH 24/27] =?UTF-8?q?hotfix:=20=EC=9B=B9=EC=86=8C=EC=BC=93=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=EB=A1=9C=20=EB=93=9C?= =?UTF-8?q?=EB=94=94=EC=96=B4=20=EA=B3=A0=EC=B3=A4=EC=8A=B5=EB=8B=88?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/header/mainHeader/header.js | 4 ++-- frontend/src/pages/custom-game-list/page.js | 2 +- frontend/src/pages/game-mode/page.js | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/src/header/mainHeader/header.js b/frontend/src/header/mainHeader/header.js index 52d8a1e0..031482b5 100644 --- a/frontend/src/header/mainHeader/header.js +++ b/frontend/src/header/mainHeader/header.js @@ -1,7 +1,7 @@ import { importCss } from "../../utils/importCss.js"; import { navigate } from "../../utils/navigate.js"; import { click } from "../../utils/clickEvent.js"; -import { BACKEND, HISTORIES_IMAGE_PATH } from "../../global.js"; +import { BACKEND, WEBSOCKET, HISTORIES_IMAGE_PATH } from "../../global.js"; import { getCookie, deleteCookie } from "../../utils/cookie.js"; import useState from "../../utils/useState.js"; import friendsInfoModal from "./friends-info-modal.js"; @@ -27,7 +27,7 @@ export default function MainHeader($container) { setUserInfo(data); }); // alert("웹소켓 연결!"); - new WebSocket("{WEBSOCKET}/friend_status/"); + new WebSocket(`${WEBSOCKET}/friend_status/`); } else { // TODO => 에러 페이지로 이동 navigate("/"); diff --git a/frontend/src/pages/custom-game-list/page.js b/frontend/src/pages/custom-game-list/page.js index a0cc7a4f..fddd434b 100644 --- a/frontend/src/pages/custom-game-list/page.js +++ b/frontend/src/pages/custom-game-list/page.js @@ -5,7 +5,7 @@ import { hoverToggle } from "../../utils/hoverEvent.js"; import passwordModal from "./password-modal.js"; import useState from "../../utils/useState.js"; import gameRoomList from "./gameRoomlist.js"; -import { BACKEND } from "../../global.js"; +import { BACKEND, WEBSOCKET } from "../../global.js"; import { navigate } from "../../utils/navigate.js"; export default function CustomGameList($container) { diff --git a/frontend/src/pages/game-mode/page.js b/frontend/src/pages/game-mode/page.js index 859d9d68..7a7716b3 100644 --- a/frontend/src/pages/game-mode/page.js +++ b/frontend/src/pages/game-mode/page.js @@ -2,6 +2,8 @@ import { importCss } from "../../utils/importCss.js"; import gameModeUnit from "./gameModeUnit.js"; import { click } from "../../utils/clickEvent.js"; import { navigate } from "../../utils/navigate.js"; +import { WEBSOCKET } from "../../global.js"; + /** * @param {HTMLElement} $container */ @@ -14,7 +16,7 @@ export default function GameMode($container) { $container.querySelector(".queue-modal").style.display = "flex"; $container.querySelector(".modal-backdrop").style.display = "block"; console.log("clicked"); - this.ws = new WebSocket("${WEBSOCKET}/rankgames/"); + this.ws = new WebSocket(`${WEBSOCKET}/rankgames/`); console.log("ws ok"); this.ws.onmessage = (event) => { console.log(event.data); From 8e80e7a322ba262aaba1d1837e10763264d80fa5 Mon Sep 17 00:00:00 2001 From: yena <50291995+nyj001012@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:59:18 +0900 Subject: [PATCH 25/27] =?UTF-8?q?feat:=20=EB=A1=9C=EC=BB=AC=202P=20?= =?UTF-8?q?=EA=B2=8C=EC=9E=84=20=EA=B5=AC=ED=98=84=20(#307)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(in-game): draw 함수에서 바 두 개와 공의 정보를 인자로 받아오게 함 * feat(in-game): 게임 정보 초기화하는 모듈 추가 * feat(in-game): bar1, 2의 속도와 공의 속도 방향 추가 * feat(runGame): moveBall() 함수 프로토타입 추가 * feat(runGame): 공이 벽에 부딪혔는지 확인하는 함수 구현 * feat(runGame): 공이 바에 부딪혔는지 확인하는 함수 구현 * refactor(in-game): runGame.js 삭제 * chore(global): BACKEND 상수값 변경 * feat(in-game): 점수 업데이트하는 함수 추가 * feat(in-game): 공의 위치와 방향, 속도를 초기화하는 함수 구현 * feat(in-game): 30fps로 렌더링하도록 조정 * feat(in-game): 공 속도 조정 * feat(in-game): 게임 용 setInterval unmount 함수에서 clear하도록 추가 * feat(in-game): 공 방향의 보정치 추가 * feat(in-game): 공 보정치 모두 제거한 플레인 버전 구현 * feat(in-game): 벽 속에 공이 들어갔을 경우 고려 * fix(in-game): 접수 업데이트시 undefined로 들어가는 현상 수정 * refactor(in-game): setInterval 대신 requestAnimationFrame으로 게임 렌더링 * feat(in-game): 공이 바에 닿았을 때 판단하는 로직 수정 * feat(in-game): 프레임 마다 속도 1%씩 증가 * feat(in-game): 바 움직임 부드럽게 하면서 두 개를 동시에 움직일 수 있게 함 * chore(in-game): 불필요한 코드 제거 * fix((in-game)): 점수 집계 반대로 되던 것 수정 * refactor(in-game): 공 튀길 때 방향 바꾸는 부분 bounce 함수로 분리 * feat(in-game): 득점 후 잠깐 멈추는 pause 함수 구현 * feat(in-game): 게임 초기화 시, 공의 방향벡터 정규화 적용 --- frontend/src/pages/in-game/page.js | 270 ++++++++++++++++++++++++++--- 1 file changed, 246 insertions(+), 24 deletions(-) diff --git a/frontend/src/pages/in-game/page.js b/frontend/src/pages/in-game/page.js index 6bd8d9fd..236638b6 100644 --- a/frontend/src/pages/in-game/page.js +++ b/frontend/src/pages/in-game/page.js @@ -9,20 +9,26 @@ import scoreBar from "./scoreBar.js"; */ export default function InGame($container, info) { console.log(info.mode); - const scoreInput = { player1: 0, player2: 0 }; + let scoreInput = { player1: 0, player2: 0 }; let [getScore, setScore] = useState(scoreInput, this, "renderScoreBoard"); let [getTime, setTime] = useState(0, this, "renderTime"); + const BALL_SPEED = 7; const init = () => { hideHeader(); this.render(); this.renderScoreBoard(); - this.intervalId = setInterval(() => { + this.timeIntervalId = setInterval(() => { setTime(getTime() + 1); }, 1000); + this.gameAnimationId = window.requestAnimationFrame(() => + runGame(canvas, bar1, bar2, ball, draw), + ); }; this.unmount = () => { - clearInterval(this.intervalId); + clearInterval(this.timeIntervalId); + cancelAnimationFrame(this.gameAnimationId); + cancelAnimationFrame(this.barAnimationId); document.querySelector("#header").style.display = "block"; }; @@ -30,7 +36,7 @@ export default function InGame($container, info) { $container.innerHTML = ` ${scoreBar()}
- + `; }; this.renderScoreBoard = () => { @@ -49,7 +55,13 @@ export default function InGame($container, info) { document.querySelector("#header").style.display = "none"; }; - function draw() { + /** + * 바 두 개와 공의 데이터를 받아서 화면에 그리는 함수. + * @param bar1 {object} x, y, width, height + * @param bar2 {object} x, y, width, height + * @param ball {object} x, y, radius + */ + function draw(bar1, bar2, ball) { // 화면 클리어 ctx.clearRect(0, 0, canvas.width, canvas.height); @@ -71,36 +83,246 @@ export default function InGame($container, info) { init(); const canvas = $container.querySelector("#gameCanvas"); const ctx = canvas.getContext("2d"); - canvas.width = 800; - canvas.height = 400; + canvas.width = document.body.clientWidth; + canvas.height = document.body.clientHeight * 0.88; // header의 height가 12vh이므로 88%만큼의 height를 가짐 // 초기 위치 설정 - let bar1 = { x: 10, y: canvas.height / 2 - 50, width: 20, height: 100 }; + let commonBarInfo = { + width: canvas.width * 0.02, + height: canvas.height * 0.3, + speed: BALL_SPEED, + }; + let bar1 = { + x: 0, + y: canvas.height / 2 - 50, + ...commonBarInfo, + }; let bar2 = { - x: canvas.width - 30, + x: canvas.width - commonBarInfo.width, y: canvas.height / 2 - 50, - width: 20, - height: 100, + ...commonBarInfo, }; - let ball = { x: canvas.width / 2, y: canvas.height / 2, radius: 10 }; + let ball = { + x: canvas.width / 2, + y: canvas.height / 2, + radius: canvas.width * 0.02, + direction: { + x: Math.random() * 2 - 1, + y: Math.random() * 2 - 1, + }, + speed: BALL_SPEED, + }; + + let keyDown = {}; + this.barAnimationId = null; - window.addEventListener("keydown", (e) => { + const moveBar = () => { // bar1 이동 - if (e.key === "w" || e.key === "W" || e.key === "ㅈ") { - bar1.y = Math.max(bar1.y - 10, 0); - } else if (e.key === "s" || e.key === "S" || e.key === "ㄴ") { - bar1.y = Math.min(bar1.y + 10, canvas.height - bar1.height); + if (keyDown["w"]) { + bar1.y = Math.max(bar1.y - bar1.speed, 0); + } else if (keyDown["s"]) { + bar1.y = Math.min(bar1.y + bar1.speed, canvas.height - bar1.height); } // bar2 이동 - if (e.key === "ArrowUp") { - bar2.y = Math.max(bar2.y - 10, 0); - } else if (e.key === "ArrowDown") { - bar2.y = Math.min(bar2.y + 10, canvas.height - bar2.height); + if (keyDown["ArrowUp"]) { + bar2.y = Math.max(bar2.y - bar2.speed, 0); + } else if (keyDown["ArrowDown"]) { + bar2.y = Math.min(bar2.y + bar2.speed, canvas.height - bar2.height); } + this.barAnimationId = requestAnimationFrame(moveBar); + }; + + const getKeyDown = (e) => { + if (e.key === "w" || e.key === "W" || e.key === "ㅈ") keyDown["w"] = true; + else if (e.key === "s" || e.key === "S" || e.key === "ㄴ") + keyDown["s"] = true; + else if (e.key === "ArrowUp") keyDown["ArrowUp"] = true; + else if (e.key === "ArrowDown") keyDown["ArrowDown"] = true; + }; + + const getKeyUp = (e) => { + if (e.key === "w" || e.key === "W" || e.key === "ㅈ") keyDown["w"] = false; + else if (e.key === "s" || e.key === "S" || e.key === "ㄴ") + keyDown["s"] = false; + else if (e.key === "ArrowUp") keyDown["ArrowUp"] = false; + else if (e.key === "ArrowDown") keyDown["ArrowDown"] = false; + }; + + window.addEventListener("keydown", getKeyDown); + window.addEventListener("keyup", getKeyUp); + + /** + * 게임 실행 함수 + * @param canvas {HTMLElement} 게임이 실행될 캔버스 + * @param bar1 {object} 바의 x, y, width, height, speed + * @param bar2 {object} 바의 x, y, width, height, speed + * @param ball {object} 공의 x, y, radius, direction, speed + * @param drawFunction {function} 캔버스를 그리는 함수 + */ + const runGame = (canvas, bar1, bar2, ball, drawFunction) => { + const moveBall = () => { + if (isBallInsideBar(bar1, ball) || isBallInsideBar(bar2, ball)) { + bounce(true, false); + } else if (isBallHitWall(canvas, ball)) { + bounce(false, true); + } + ball.x = ball.x + ball.direction.x * ball.speed; + ball.y = ball.y + ball.direction.y * ball.speed; + ball.speed += 0.001; + let whetherScoreAGoal = isBallHitGoal(canvas, ball); + if (whetherScoreAGoal[0] || whetherScoreAGoal[1]) { + updateScore(whetherScoreAGoal); + reset(ball, canvas); + pause(1000); + } + drawFunction(bar1, bar2, ball); + let moveBallEventId = window.requestAnimationFrame(moveBall); + if (getScore().player1 + getScore().player2 >= 5) { + cancelAnimationFrame(moveBallEventId); + } + }; - draw(); // 키보드 이벤트 후 상태를 반영하여 다시 그림 - }); + /** + * 공이 벽에 부딪혔는지 확인하는 함수 + * @param canvas {HTMLCanvasElement} 게임이 실행될 캔버스 + * @param ball {object} 공의 x, y, radius, direction, speed + */ + const isBallHitWall = (canvas, ball) => { + let topPoint = ball.y - ball.radius; + let bottomPoint = ball.y + ball.radius; - draw(); // 초기 상태 그리기 + if (topPoint <= 0) { + ball.y += Math.abs(topPoint); + return true; + } else if (bottomPoint >= canvas.height) { + ball.y -= bottomPoint - canvas.height; + return true; + } + return false; + }; + + /** + * 공이 바의 x축 내부에 있는지 확인하는 함수 + * @param bar {object} 바의 x, y, width, height, speed + * @param ball {object} 공의 x, y, radius, direction, speed + * @returns {boolean} 바의 x축 내부에 있으면 true, 아니면 false + */ + const isBallInsideBarX = (bar, ball) => { + return ( + ball.x + ball.radius >= bar.x && + ball.x - ball.radius <= bar.x + bar.width + ); + }; + + /** + * 공이 바의 y축 내부에 있는지 확인하는 함수 + * @param bar {object} 바의 x, y, width, height, speed + * @param ball {object} 공의 x, y, radius, direction, speed + * @returns {boolean} 바의 y축 내부에 있으면 true, 아니면 false + */ + const isBallInsideBarY = (bar, ball) => { + return ( + ball.y + ball.radius >= bar.y && + ball.y - ball.radius <= bar.y + bar.height + ); + }; + + /** + * 공이 바에 부딪혔는지 확인하는 함수. 공의 충돌은 공이 바의 내부에 있는지를 기준으로 판단한다. + * @param bar {object} 바의 x, y, width, height, speed + * @param ball {object} 공의 x, y, radius, direction, speed + * @returns {boolean} 바에 부딪혔으면 true, 아니면 false + */ + const isBallInsideBar = (bar, ball) => { + return isBallInsideBarX(bar, ball) && isBallInsideBarY(bar, ball); + }; + + const isBallHitGoal = (canvas, ball) => { + if (ball.x <= 0) { + return [true, false]; + } else if (ball.x >= canvas.width) { + return [false, true]; + } + return [false, false]; + }; + + /** + * 공이 벽에 부딪혔을 때 방향을 바꾸는 함수 + * @param bounceX {boolean} x축으로 부딪혔는지 여부 + * @param bounceY {boolean} y축으로 부딪혔는지 여부 + */ + const bounce = (bounceX, bounceY) => { + if (bounceX) { + [ball.direction.x, ball.direction.y] = normalizeVector( + ball.direction.x * -1 * getRandomCoefficient(0.9, 1.1), + ball.direction.y, + ); + } else if (bounceY) { + [ball.direction.x, ball.direction.y] = normalizeVector( + ball.direction.x, + ball.direction.y * -1 * getRandomCoefficient(0.9, 1.1), + ); + } + }; + + const updateScore = (whetherScoreAGoal) => { + if (whetherScoreAGoal[0]) { + setScore({ + player1: getScore().player1, + player2: getScore().player2 + 1, + }); + } else if (whetherScoreAGoal[1]) { + setScore({ + player1: getScore().player1 + 1, + player2: getScore().player2, + }); + } + }; + + /** + * 공의 위치를 초기화하는 함수 + * @param ball {object} 공의 x, y, radius, direction, speed + * @param canvas {HTMLCanvasElement} 게임이 실행될 캔버스 + */ + const reset = (ball, canvas) => { + ball.x = canvas.width / 2; + ball.y = canvas.height / 2; + ball.direction = { + x: Math.random() * 2 - 1, + y: Math.random() * 2 - 1, + }; + normalizeVector(ball.direction.x, ball.direction.y); + ball.speed = BALL_SPEED; + }; + + /** + * 최소값과 최대값 사이의 랜덤한 수를 반환하는 함수 + */ + const getRandomCoefficient = (min, max) => { + return Math.floor(Math.random() * (max - min + 1)) + min; + }; + + /** + * 벡터를 정규화하는 함수 + * @param x {number} x축 방향 벡터 + * @param y {number} y축 방향 벡터 + * @returns {number[]} 정규화된 벡터 + */ + const normalizeVector = (x, y) => { + let vectorLength = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); + let normalizedX = x / vectorLength; + let normalizedY = y / vectorLength; + return [normalizedX, normalizedY]; + }; + + const pause = (ms) => { + const end = Date.now() + ms; + while (Date.now() < end) {} + }; + + normalizeVector(ball.direction.x, ball.direction.y); + moveBar(); + moveBall(); + }; } From f33aff70736a2e13d8be0ebc41988317273af102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EC=9B=90=EC=A0=95?= Date: Mon, 26 Feb 2024 18:14:28 +0900 Subject: [PATCH 26/27] =?UTF-8?q?refactor:=20=ED=95=A8=EC=88=98=EB=A1=9C?= =?UTF-8?q?=20=EB=B6=84=ED=95=A0=20#312?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/games/consumers.py | 160 ++++++++++++++++++++++--------------- 1 file changed, 96 insertions(+), 64 deletions(-) diff --git a/backend/games/consumers.py b/backend/games/consumers.py index a44d4b87..d66083fd 100644 --- a/backend/games/consumers.py +++ b/backend/games/consumers.py @@ -456,83 +456,35 @@ async def receive(self, text_data): # 게임 시작 if self.game.mode == 0 and len(self.match1_users) == 2: - await self.channel_layer.group_send( - self.match1_group_name, - { - 'type': 'gamestart', - 'data': '게임 스탓토' - } - ) - await self.gamestart({ - 'type': 'gamestart', - 'data': '게임 스탓토' - }) + await self.send_pvp_start_message() await asyncio.sleep(2) self.match1.started_at = datetime.now() while True: # todo 겜 로직 - # if 겜 끝나면 탈출 - await self.channel_layer.group_send( - self.match1_group_name, - { - 'type': 'in_game', - 'data': '화면 그릴 때 필요한 정보 serializer' - } - ) - await self.in_game({ - 'type': 'in_game', - 'data': '화면 그릴 때 필요한 정보 serializer' - }) + if self.match1.left_side_player.score + self.match1.right_side_player.score == 5: + self.match1.finished = True + # todo 게임 돌아가고 있는 거 끝내기 + break + await self.send_pvp_in_game_message() await asyncio.sleep(1 / 24) elif self.game.mode != 0 and len(self.match1_users) == 2 and len(self.match2_users) == 2: - await self.channel_layer.group_send( - self.match1_group_name, - { - 'type': 'gamestart', - 'data': '게임 스탓토 serializer serializer' - } - ) - await self.channel_layer.group_send( - self.match2_group_name, - { - 'type': 'gamestart', - 'data': '게임 스탓토 serializer serializer' - } - ) - await self.gamestart({ - 'type': 'gamestart', - 'data': '게임 스탓토 serializer' - }) + await self.send_tournament_start_message() await asyncio.sleep(2) self.match1.started_at = datetime.now() self.match2.started_at = datetime.now() while True: # todo 겜 로직 - await self.channel_layer.group_send( - self.match1_group_name, - { - 'type': 'in_game', - 'data': 'match1 info' - } - ) - await self.channel_layer.group_send( - self.match2_group_name, - { - 'type': 'in_game', + if self.match1.left_side_player.score + self.match1.right_side_player.score == 5: + self.match1.finished = True + # todo 게임 돌아가고 있는 거 끝내기 + if self.match2.left_side_player.score + self.match2.right_side_player.score == 5: + self.match2.finished = True + # todo 게임 돌아가고 있는 거 끝내기 + if self.match1.finished and self.match2.finished: + break + await self.send_tournament_in_game_message() 'data': 'match2 info' - } - ) - if self.my_match == 1: - await self.in_game({ - 'type': 'in_game', - 'data': 'match1 info' - }) - else: - await self.in_game({ - 'type': 'in_game', - 'data': 'match2 info' - }) await asyncio.sleep(1 / 24) async def set_values(self, message_data): @@ -572,6 +524,86 @@ def set_match(self, match, data, match_num): match.ball.x = data['ball_x'] match.ball.y = data['ball_y'] + async def send_pvp_start_message(self): + data = '게임 시작 데이터' + await self.channel_layer.group_send( + self.match1_group_name, + { + 'type': 'gamestart', + 'data': data + } + ) + await self.gamestart({ + 'type': 'gamestart', + 'data': data + }) + + async def send_pvp_in_game_message(self): + data = 'match1 정보' + await self.channel_layer.group_send( + self.match1_group_name, + { + 'type': 'in_game', + 'data': data + } + ) + await self.in_game({ + 'type': 'in_game', + 'data': data + }) + + async def send_tournament_start_message(self): + data = '겜 시작 정보' + await self.channel_layer.group_send( + self.match1_group_name, + { + 'type': 'gamestart', + 'data': data + } + ) + await self.channel_layer.group_send( + self.match2_group_name, + { + 'type': 'gamestart', + 'data': data + } + ) + await self.gamestart({ + 'type': 'gamestart', + 'data': data + }) + + async def send_tournament_in_game_message(self): + match1_data = 'match1 data' + match2_data = 'match2 data' + + if not self.match1.finished: + if self.my_match == 1: + await self.in_game({ + 'type': 'in_game', + 'data': match1_data + }) + await self.channel_layer.group_send( + self.match1_group_name, + { + 'type': 'in_game', + 'data': match1_data + } + ) + if not self.match2.finished: + if self.my_match == 2: + await self.in_game({ + 'type': 'in_game', + 'data': match2_data + }) + await self.channel_layer.group_send( + self.match2_group_name, + { + 'type': 'in_game', + 'data': match2_data + } + ) + async def game_info(self, event): data = event["data"] await self.send(text_data=json.dumps({"data": data})) From 8984a3e2705d4954ca514bc5d2b9ba52144a7dcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EC=9B=90=EC=A0=95?= Date: Mon, 26 Feb 2024 18:14:43 +0900 Subject: [PATCH 27/27] =?UTF-8?q?feat:=20=ED=82=A4=EB=B3=B4=EB=93=9C=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20#312?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/games/consumers.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/backend/games/consumers.py b/backend/games/consumers.py index d66083fd..50e6f129 100644 --- a/backend/games/consumers.py +++ b/backend/games/consumers.py @@ -442,6 +442,7 @@ def get_serializer_data(self): return serializer.data async def receive(self, text_data): + # todo try catch data = json.loads(text_data) message_type = data.get('type') message_data = data.get('data', {}) @@ -484,9 +485,16 @@ async def receive(self, text_data): if self.match1.finished and self.match2.finished: break await self.send_tournament_in_game_message() - 'data': 'match2 info' await asyncio.sleep(1 / 24) + elif message_type == 'keyboard': + if message_data == 'up': + # todo ? + pass + elif message_data == 'down': + # todo ? + pass + async def set_values(self, message_data): map_width = message_data['map_width'] map_height = message_data['map_height']