diff --git a/Assets/Script/Common/Constants.cs b/Assets/Script/Common/Constants.cs index 83d9046..eb717cc 100644 --- a/Assets/Script/Common/Constants.cs +++ b/Assets/Script/Common/Constants.cs @@ -31,6 +31,7 @@ RevengeConfirmed, // 재대결 수락 전송 확인 RevengeRejected, // 재대결 거절 수신 RevengeRejectionConfirmed, // 재대결 거절 전송 확인 - ReceiveTimeout // 상대방이 타임 아웃일 때 + ReceiveTimeout, // 상대방이 타임 아웃일 때 + OpponentDisconnected }; } \ No newline at end of file diff --git a/Assets/Script/Game/GameLogic.cs b/Assets/Script/Game/GameLogic.cs index a795fef..1397fd2 100644 --- a/Assets/Script/Game/GameLogic.cs +++ b/Assets/Script/Game/GameLogic.cs @@ -34,6 +34,7 @@ public partial class GameLogic : IDisposable public int SelectedRow { get; private set; } public int SelectedCol { get; private set; } public FioTimer FioTimer { get; private set; } + public bool GameInProgress { get; private set; } // 게임 진행 중인지 확인 #endregion @@ -113,12 +114,18 @@ public partial class GameLogic : IDisposable StartGameOnMainThread(); break; case Constants.MultiplayManagerState.ExitRoom: - Debug.Log("## Exit Room"); + Debug.Log("## Exit Room"); // TODO: Exit Room 처리 break; case Constants.MultiplayManagerState.EndGame: Debug.Log("## End Game"); - // TODO: End Room 처리 + ExecuteOnMainThread(() => + { + GameManager.Instance.panelManager.OpenConfirmPanel("상대방이 방을 나갔습니다.", () => + { + // TODO: 무승부/항복 등 버튼 동작 안하도록 처리 + }); + }); break; case Constants.MultiplayManagerState.DoSurrender: Debug.Log("상대방의 항복 요청 들어옴"); @@ -141,6 +148,7 @@ public partial class GameLogic : IDisposable TimerPause(); ExecuteOnMainThread(() => { + GameManager.Instance.panelManager.OpenLoadingPanel(true, true, false, false); GameManager.Instance.panelManager.OpenDrawConfirmPanel("무승부 요청을 승낙하시겠습니까?", () => { GameManager.Instance.panelManager.OpenEffectPanel(Enums.GameResult.Draw); @@ -153,32 +161,52 @@ public partial class GameLogic : IDisposable }); break; case Constants.MultiplayManagerState.DrawRequestSent: + { Debug.Log("무승부 요청 전송 완료"); + ExecuteOnMainThread(() => + { + GameManager.Instance.panelManager.OpenLoadingPanel(true, true, false, false); + }); TimerPause(); break; + } case Constants.MultiplayManagerState.DrawAccepted: Debug.Log("무승부 요청이 승낙이 들어옴"); ExecuteOnMainThread(() => { + GameManager.Instance.panelManager.CloseLoadingPanel(); GameManager.Instance.panelManager.OpenEffectPanel(Enums.GameResult.Draw); EndGame(Enums.GameResult.Draw); }); break; case Constants.MultiplayManagerState.DrawConfirmed: + { Debug.Log("무승부 요청 승낙 완료"); + ExecuteOnMainThread(() => + { + GameManager.Instance.panelManager.CloseLoadingPanel(); + }); break; + } case Constants.MultiplayManagerState.DrawRejected: Debug.Log("무승부 요청이 거부가 들어옴"); TimerUnpause(); ExecuteOnMainThread(() => { + GameManager.Instance.panelManager.CloseLoadingPanel(); GameManager.Instance.panelManager.OpenConfirmPanel("무승부 요청을 거부하였습니다.", () => { }); }); break; case Constants.MultiplayManagerState.DrawRejectionConfirmed: + { Debug.Log("무승부 요청 거부 완료"); + ExecuteOnMainThread(() => + { + GameManager.Instance.panelManager.OpenLoadingPanel(true, true, false, false); + }); TimerUnpause(); break; + } case Constants.MultiplayManagerState.ReceiveTimeout: Debug.Log("상대방이 타임 아웃 됨"); ExecuteOnMainThread(() => @@ -192,6 +220,7 @@ public partial class GameLogic : IDisposable break; case Constants.MultiplayManagerState.ReceiveRevengeRequest: Debug.Log("상대방의 재대결 요청이 들어옴"); + ChangeGameInProgress(true); ExecuteOnMainThread(() => { GameManager.Instance.panelManager.OpenDrawConfirmPanel("상대방의 재대결 요청을\n승낙하시겠습니까?", () => @@ -267,7 +296,18 @@ public partial class GameLogic : IDisposable }); }); break; - } + case Constants.MultiplayManagerState.OpponentDisconnected: + Debug.Log("상대방 강제 종료"); + ExecuteOnMainThread(() => + { + GameManager.Instance.panelManager.OpenConfirmPanel("연결이 끊어졌습니다.", () => + { + GameManager.Instance.panelManager.OpenEffectPanel(Enums.GameResult.Win); + EndGame(Enums.GameResult.Win); + }); + }); + break; + } ReplayManager.Instance.InitReplayData(UserManager.Instance.Nickname,"nicknameB"); }); @@ -328,6 +368,9 @@ public partial class GameLogic : IDisposable // 메인스레드에서 게임 시작 private void StartGameOnMainThread() { + ChangeGameInProgress(true); + Debug.Log("GameInProgress 변경 true"); + ExecuteOnMainThread(() => { // 로딩 패널 열려있으면 닫기 @@ -476,6 +519,7 @@ public partial class GameLogic : IDisposable // 기존 멀티플레이 상태 초기화 MultiPlayManager = null; _roomId = null; + GameType = Enums.GameType.SinglePlay; // 싱글 플레이 상태로 변경 @@ -510,6 +554,12 @@ public partial class GameLogic : IDisposable } } + // 타이머 일시정지 + private void TimerPause() => FioTimer.PauseTimer(); + + // 타이머 일시정지 해제 + private void TimerUnpause() => FioTimer.StartTimer(); + // 이전에 표시된 금수 마크 제거 private void ClearForbiddenMarks() { @@ -533,17 +583,25 @@ public partial class GameLogic : IDisposable return AI_NAMIES[index]; } - // 타이머 일시정지 - private void TimerPause() => FioTimer.PauseTimer(); - - // 타이머 일시정지 해제 - private void TimerUnpause() => FioTimer.StartTimer(); - #endregion + + public void ChangeGameInProgress(bool inProgress) + { + if (GameInProgress == inProgress) + return; + + GameInProgress = inProgress; + } public void Dispose() { MultiPlayManager?.LeaveRoom(_roomId); MultiPlayManager?.Dispose(); } + + public void ForceQuit() + { + MultiPlayManager?.ForceQuit(_roomId); + MultiPlayManager?.Dispose(); + } } diff --git a/Assets/Script/Game/GameManager.cs b/Assets/Script/Game/GameManager.cs index f34e129..10bc6fc 100644 --- a/Assets/Script/Game/GameManager.cs +++ b/Assets/Script/Game/GameManager.cs @@ -152,4 +152,15 @@ public class GameManager : Singleton _gameLogic.EndGame(Enums.GameResult.Draw); } + + private void OnApplicationQuit() + { + Debug.Log("앱 종료 감지: 소켓 연결 정리 중..."); + + if(_gameLogic.GameInProgress) + _gameLogic?.ForceQuit(); + else + _gameLogic?.Dispose(); + + } } \ No newline at end of file diff --git a/Assets/Script/Game/GameUtility/GameRoutine.cs b/Assets/Script/Game/GameUtility/GameRoutine.cs index 18c131f..fd79021 100644 --- a/Assets/Script/Game/GameUtility/GameRoutine.cs +++ b/Assets/Script/Game/GameUtility/GameRoutine.cs @@ -1,4 +1,6 @@ -public partial class GameLogic +using UnityEngine; + +public partial class GameLogic { // 돌 카운터 증가 함수 public void CountStoneCounter() => _totalStoneCounter++; @@ -85,5 +87,13 @@ SetState(null); ReplayManager.Instance.SaveReplayDataResult(result); //TODO: 게임 종료 후 행동 구현 + ChangeGameInProgress(false); + Debug.Log("GameInProgress 변경 false"); + } + + public void SetLastPositioned(int row, int col) + { + _lastRow = row; + _lastCol = col; } } \ No newline at end of file diff --git a/Assets/Script/Game/MultiplayManager.cs b/Assets/Script/Game/MultiplayManager.cs index 62a1a29..1e8c232 100644 --- a/Assets/Script/Game/MultiplayManager.cs +++ b/Assets/Script/Game/MultiplayManager.cs @@ -113,7 +113,9 @@ public class MultiplayManager : IDisposable _socket.On("revengeConfirmed", RevengeConfirmed); _socket.On("revengeRejected", RevengeRejected); _socket.On("revengeRejectionConfirmed", RevengeRejectionConfirmed); - + // 강제 종료 처리 + _socket.On("opponentDisconnected", OpponentDisconnected); + _socket.Connect(); } catch (Exception e) @@ -216,6 +218,18 @@ public class MultiplayManager : IDisposable _roomId = null; // 방 나가면 roomId 초기화 } + public void ForceQuit(string roomId) + { + if (string.IsNullOrEmpty(_roomId)) + { + Debug.LogError("Disconnect 호출 실패: _roomId가 설정되지 않음"); + return; + } + + _socket.Emit("disconnect", new { roomId = _roomId }); + _roomId = null; // 방 나가면 roomId 초기화 + } + public void RequestSurrender() { if (string.IsNullOrEmpty(_roomId)) @@ -337,6 +351,13 @@ public class MultiplayManager : IDisposable _onMultiplayStateChanged?.Invoke(Constants.MultiplayManagerState.DrawRejectionConfirmed, data.message); } + + private void DerawRejectionConfirmed(SocketIOResponse response) + { + var data = response.GetValue(); + + _onMultiplayStateChanged?.Invoke(Constants.MultiplayManagerState.DrawRejectionConfirmed, data.message); + } #endregion @@ -414,6 +435,13 @@ public class MultiplayManager : IDisposable _onMultiplayStateChanged?.Invoke(Constants.MultiplayManagerState.RevengeRejectionConfirmed, data.message); } + private void OpponentDisconnected(SocketIOResponse response) + { + var data = response.GetValue(); + + _onMultiplayStateChanged?.Invoke(Constants.MultiplayManagerState.OpponentDisconnected, data.message); + } + #endregion public void Dispose() diff --git a/Assets/Script/Replay/ReplayManager.cs b/Assets/Script/Replay/ReplayManager.cs index 8b5bbc7..d716375 100644 --- a/Assets/Script/Replay/ReplayManager.cs +++ b/Assets/Script/Replay/ReplayManager.cs @@ -215,6 +215,16 @@ public class ReplayManager : Singleton { ReplayManager.Instance.PushUndoMove(targetMove); _gameLogic.RemoveStone(targetMove.columnIndex, targetMove.rowIndex); + if (_placedStoneStack.Count > 0) + { + var undoLastMove = _placedStoneStack.Peek(); + _gameLogic.StoneController.SetStoneState(Enums.StoneState.LastPositioned, undoLastMove.columnIndex, undoLastMove.rowIndex); + _gameLogic.SetLastPositioned(undoLastMove.columnIndex, undoLastMove.rowIndex); + } + else + { + _gameLogic.SetLastPositioned(-1, -1); + } } public void ReplayFirst()