From 1590df482a1053e061e609591f37aca0eed78d59 Mon Sep 17 00:00:00 2001 From: fiore Date: Thu, 27 Mar 2025 21:00:57 +0900 Subject: [PATCH] =?UTF-8?q?DO-74=20[Refactor]=20=EA=B2=8C=EC=9E=84=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Script/Game/GameLogic.cs | 1082 ++++++----------- Assets/Script/Game/GameManager.cs | 6 +- Assets/Script/Game/GameStates.meta | 3 + Assets/Script/Game/GameStates/AIState.cs | 27 + Assets/Script/Game/GameStates/AIState.cs.meta | 3 + .../Script/Game/GameStates/BasePlayerState.cs | 58 + .../Game/GameStates/BasePlayerState.cs.meta | 3 + .../Game/GameStates/MultiPlayerState.cs | 62 + .../Game/GameStates/MultiPlayerState.cs.meta | 3 + Assets/Script/Game/GameStates/PlayerState.cs | 63 + .../Game/GameStates/PlayerState.cs.meta | 3 + Assets/Script/Game/GameUtility.meta | 3 + Assets/Script/Game/GameUtility/GameRoutine.cs | 89 ++ .../Game/GameUtility/GameRoutine.cs.meta | 3 + .../Script/Game/GameUtility/GameWinCheck.cs | 96 ++ .../Game/GameUtility/GameWinCheck.cs.meta | 3 + 16 files changed, 763 insertions(+), 744 deletions(-) create mode 100644 Assets/Script/Game/GameStates.meta create mode 100644 Assets/Script/Game/GameStates/AIState.cs create mode 100644 Assets/Script/Game/GameStates/AIState.cs.meta create mode 100644 Assets/Script/Game/GameStates/BasePlayerState.cs create mode 100644 Assets/Script/Game/GameStates/BasePlayerState.cs.meta create mode 100644 Assets/Script/Game/GameStates/MultiPlayerState.cs create mode 100644 Assets/Script/Game/GameStates/MultiPlayerState.cs.meta create mode 100644 Assets/Script/Game/GameStates/PlayerState.cs create mode 100644 Assets/Script/Game/GameStates/PlayerState.cs.meta create mode 100644 Assets/Script/Game/GameUtility.meta create mode 100644 Assets/Script/Game/GameUtility/GameRoutine.cs create mode 100644 Assets/Script/Game/GameUtility/GameRoutine.cs.meta create mode 100644 Assets/Script/Game/GameUtility/GameWinCheck.cs create mode 100644 Assets/Script/Game/GameUtility/GameWinCheck.cs.meta diff --git a/Assets/Script/Game/GameLogic.cs b/Assets/Script/Game/GameLogic.cs index d2d0037..e4b1dbf 100644 --- a/Assets/Script/Game/GameLogic.cs +++ b/Assets/Script/Game/GameLogic.cs @@ -1,300 +1,335 @@ using System; -using System.Collections; using System.Collections.Generic; using UnityEngine; -using UnityEngine.SceneManagement; using PimDeWitte.UnityMainThreadDispatcher; using Random = UnityEngine.Random; -public abstract class BasePlayerState +public partial class GameLogic : IDisposable { - public abstract void OnEnter(GameLogic gameLogic); - public abstract void OnExit(GameLogic gameLogic); - public abstract void HandleMove(GameLogic gameLogic, int row, int col); - public abstract void HandleNextTurn(GameLogic gameLogic); - - protected string _roomId; - protected bool _isMultiplay; - protected MultiplayManager _multiplayManager; + #region Fields - public void ProcessMove(GameLogic gameLogic, Enums.PlayerType playerType, int row, int col) - { - gameLogic.fioTimer.PauseTimer(); - gameLogic.SetNewBoardValue(playerType, row, col); - gameLogic.CountStoneCounter(); - - if (_isMultiplay) - { - _multiplayManager.SendPlayerMove(_roomId, new Vector2Int(row, col)); - } - - if (gameLogic.CheckGameWin(playerType, row, col)) - { - var gameResult = playerType == Enums.PlayerType.PlayerA? Enums.GameResult.Win:Enums.GameResult.Lose; - if (gameLogic.gameType == Enums.GameType.MultiPlay) - { - if (gameLogic.firstPlayerState.GetType() != typeof(PlayerState)) - { - gameResult = gameResult == Enums.GameResult.Win ? Enums.GameResult.Lose : Enums.GameResult.Win; - } - } - GameManager.Instance.panelManager.OpenEffectPanel(gameResult); - gameLogic.EndGame(gameResult); - } - else - { - if (gameLogic.TotalStoneCounter >= Constants.MinCountForDrawCheck) - { - if (gameLogic.CheckGameDraw()) - { - GameManager.Instance.panelManager.OpenEffectPanel(Enums.GameResult.Draw); - gameLogic.EndGame(Enums.GameResult.Draw); - } - else - { - HandleNextTurn(gameLogic); - } - } - else - { - HandleNextTurn(gameLogic); - } - } - } -} - -public class PlayerState : BasePlayerState -{ - private Enums.PlayerType _playerType; - private bool _isFirstPlayer; - - public PlayerState(bool isFirstPlayer) - { - _isFirstPlayer = isFirstPlayer; - _playerType = isFirstPlayer ? Enums.PlayerType.PlayerA : Enums.PlayerType.PlayerB; - _isMultiplay = false; - } - - public PlayerState(bool isFirstPlayer, MultiplayManager multiplayManager, string roomId) - : this(isFirstPlayer) - { - _isFirstPlayer = isFirstPlayer; - _multiplayManager = multiplayManager; - _roomId = roomId; - _isMultiplay = true; - } - - public override void OnEnter(GameLogic gameLogic) - { - gameLogic.fioTimer.StartTimer(); - - //TODO: 첫번째 플레이어면 렌주 룰 확인 - #region Renju Turn Set - // 턴이 변경될 때마다 금수 위치 업데이트 - gameLogic.UpdateForbiddenMoves(); - #endregion - - gameLogic.currentTurn = _playerType; - gameLogic.stoneController.OnStoneClickedDelegate = (row, col) => - { - HandleMove(gameLogic, row, col); - }; - } - - public override void OnExit(GameLogic gameLogic) - { - //TODO: 렌주 룰 금수자리 초기화 - - gameLogic.fioTimer.InitTimer(); - gameLogic.stoneController.OnStoneClickedDelegate = null; - } - - public override void HandleMove(GameLogic gameLogic, int row, int col) - { - gameLogic.SetStoneSelectedState(row, col); - } - - public override void HandleNextTurn(GameLogic gameLogic) - { - if (_isFirstPlayer) - { - gameLogic.SetState(gameLogic.secondPlayerState); - } - else - { - gameLogic.SetState(gameLogic.firstPlayerState); - } - } -} - -public class AIState: BasePlayerState -{ - public override void OnEnter(GameLogic gameLogic) - { - gameLogic.fioTimer.StartTimer(); - OmokAI.Instance.StartBestMoveSearch(gameLogic.GetBoard(), (bestMove) => - { - if(bestMove.HasValue) - HandleMove(gameLogic, bestMove.Value.Item1, bestMove.Value.Item2); - }); - } - - public override void OnExit(GameLogic gameLogic) - { - gameLogic.fioTimer.InitTimer(); - } - - public override void HandleMove(GameLogic gameLogic, int row, int col) - { - ProcessMove(gameLogic, Enums.PlayerType.PlayerB,row, col); - } - - public override void HandleNextTurn(GameLogic gameLogic) - { - gameLogic.SetState(gameLogic.firstPlayerState); - } -} -public class MultiPlayerState: BasePlayerState -{ - private Enums.PlayerType _playerType; - private bool _isFirstPlayer; - - private MultiplayManager _multiplayManager; - - public MultiPlayerState(bool isFirstPlayer, MultiplayManager multiplayManager) - { - _isFirstPlayer = isFirstPlayer; - _playerType = isFirstPlayer ? Enums.PlayerType.PlayerA : Enums.PlayerType.PlayerB; - _multiplayManager = multiplayManager; - } - - public override void OnEnter(GameLogic gameLogic) - { - gameLogic.fioTimer.StartTimer(); - //TODO: 첫번째 플레이어면 렌주 룰 확인 - #region Renju Turn Set - // 턴이 변경될 때마다 금수 위치 업데이트 - gameLogic.UpdateForbiddenMoves(); - #endregion - - gameLogic.currentTurn = _playerType; - // gameLogic.stoneController.OnStoneClickedDelegate = (row, col) => - // { - // HandleMove(gameLogic, row, col); - // }; - _multiplayManager.OnOpponentMove = moveData => - { - var row = moveData.position.x; - var col = moveData.position.y; - UnityThread.executeInUpdate(() => - { - HandleMove(gameLogic, row, col); - }); - }; - } - - public override void OnExit(GameLogic gameLogic) - { - gameLogic.fioTimer.InitTimer(); - _multiplayManager.OnOpponentMove = null; - } - - public override void HandleMove(GameLogic gameLogic, int row, int col) - { - ProcessMove(gameLogic, _playerType, row, col); - } - - public override void HandleNextTurn(GameLogic gameLogic) - { - if (_isFirstPlayer) - { - gameLogic.SetState(gameLogic.secondPlayerState); - } - else - { - gameLogic.SetState(gameLogic.firstPlayerState); - } - } -} - -public class GameLogic : IDisposable -{ private Enums.PlayerType[,] _board; - public StoneController stoneController; - public Enums.PlayerType currentTurn; - public Enums.GameType gameType; - //총 착수된 돌 카운터 - public int _totalStoneCounter; - public int TotalStoneCounter{get{return _totalStoneCounter;}} - //무승부 요청 가능 여부 - private bool _requestDrawChance; - public bool RequestDrawChance{ - get { return _requestDrawChance;} - set { _requestDrawChance = value;} - } - - public BasePlayerState firstPlayerState; - public BasePlayerState secondPlayerState; - private BasePlayerState _currentPlayerState; - - //타이머 - public FioTimer fioTimer; - - //선택된 좌표 - public int selectedRow; - public int selectedCol; - //마지막 배치된 좌표 - private int _lastRow; - private int _lastCol; - - public MultiplayManager _multiplayManager; + private int _totalStoneCounter; // 총 착수된 돌 카운터 + private int _lastRow, _lastCol; // 마지막 배치된 좌표 + private RenjuForbiddenMoveDetector _forbiddenDetector; // 렌주룰 금수 검사기 + private List _forbiddenMoves = new (); // 현재 금수 위치 목록 private string _roomId; - - -#region Renju Members - // 렌주룰 금수 검사기 - private RenjuForbiddenMoveDetector _forbiddenDetector; - // 현재 금수 위치 목록 - private List _forbiddenMoves = new List(); -#endregion + #endregion + + #region Properties + + public int TotalStoneCounter => _totalStoneCounter; + public bool RequestDrawChance { get; set; } // 무승부 요청 가능 여부 + public MultiplayManager MultiPlayManager { get; private set; } + public Enums.PlayerType CurrentTurn { get; set; } + public Enums.GameType GameType { get; set; } + public StoneController StoneController { get; set; } + public BasePlayerState CurrentPlayerState { get; private set; } + public BasePlayerState FirstPlayerState { get; private set; } + public BasePlayerState SecondPlayerState { get; private set; } + public int SelectedRow { get; private set; } + public int SelectedCol { get; private set; } + public FioTimer FioTimer { get; private set; } + + #endregion + + #region Constructor and Initialization public GameLogic(StoneController stoneController, Enums.GameType gameType, FioTimer fioTimer = null) { - //보드 초기화 + _forbiddenDetector = new RenjuForbiddenMoveDetector(); // 금수 감지기 초기화 + InitializeBoard(stoneController, gameType); // 보드 초기화 + InitializeFioTimer(fioTimer); // timer 초기화 + GameModeSetter(gameType); // 게임 모드 설정 + } + + // 게임 모드 분기 처리 + private void GameModeSetter(Enums.GameType gameType) + { + switch (gameType) + { + case Enums.GameType.MultiPlay: + InitializeMultiplayerMode(); + break; + case Enums.GameType.Replay: + //TODO: 리플레이 구현 + break; + // 현재 싱글 플레이로 바로 넘어가지 않기 때문에 미사용 + // case Enums.GameType.SinglePlay: + // InitializeSinglePlayMode(); + // break; + } + } + + private void InitializeMultiplayerMode() + { + // 메인 스레드에서 실행 - UI 업데이트는 메인 스레드에서 실행 필요 + ExecuteOnMainThread(() => + { + GameManager.Instance.panelManager.OpenLoadingPanel(true, true); + }); + + MultiPlayManager = new MultiplayManager((state, data) => + { + Debug.Log($"## {state}"); + + switch (state) + { + case Constants.MultiplayManagerState.CreateRoom: + Debug.Log("## Create Room"); + _roomId = data as string; + break; + case Constants.MultiplayManagerState.JoinRoom: + Debug.Log("## Join Room"); + var joinRoomData = data as JoinRoomData; + + // TODO: 응답값 없을 때 서버에서 다시 받아오기 or AI 플레이로 넘기는 처리 필요 + if (!ValidateRoomData(joinRoomData, "Join Room")) return; + + // 플레이어 셋업 + SetupPlayer(joinRoomData.isBlack, joinRoomData.roomId, joinRoomData.opponentNickname, joinRoomData.opponentImageIndex); + + // 메인 스레드에서 실행 - UI 업데이트는 메인 스레드에서 실행 필요 + StartGameOnMainThread(); + break; + case Constants.MultiplayManagerState.SwitchAI: + Debug.Log("## Switching to AI Mode"); + SwitchToSinglePlayer(); + break; + case Constants.MultiplayManagerState.StartGame: + Debug.Log("## Start Game"); + var startGameData = data as StartGameData; + + // TODO: 응답값 없을 때 서버에서 다시 받아오기 or AI 플레이로 넘기는 처리 필요 + if (!ValidateRoomData(startGameData, "Start Game")) return; + + // 플레이어 셋업 + SetupPlayer(startGameData.isBlack, _roomId, startGameData.opponentNickname, startGameData.opponentImageIndex); + + // 메인 스레드에서 실행 - UI 업데이트는 메인 스레드에서 실행 필요 + StartGameOnMainThread(); + break; + case Constants.MultiplayManagerState.ExitRoom: + Debug.Log("## Exit Room"); + // TODO: Exit Room 처리 + break; + case Constants.MultiplayManagerState.EndGame: + Debug.Log("## End Game"); + // TODO: End Room 처리 + break; + case Constants.MultiplayManagerState.DoSurrender: + Debug.Log("상대방의 항복 요청 들어옴"); + ExecuteOnMainThread(() => + { + GameManager.Instance.panelManager.OpenEffectPanel(Enums.GameResult.Win); + EndGame(Enums.GameResult.Win); + }); + break; + case Constants.MultiplayManagerState.SurrenderConfirmed: + Debug.Log("항복 요청 전송 완료"); + ExecuteOnMainThread(() => + { + GameManager.Instance.panelManager.OpenEffectPanel(Enums.GameResult.Lose); + EndGame(Enums.GameResult.Lose); + }); + break; + case Constants.MultiplayManagerState.ReceiveDrawRequest: + Debug.Log("상대방의 무승부 요청 들어옴"); + ExecuteOnMainThread(() => + { + GameManager.Instance.panelManager.OpenDrawConfirmPanel("무승부 요청을 승락하시겠습니까?", () => + { + GameManager.Instance.panelManager.OpenEffectPanel(Enums.GameResult.Draw); + EndGame(Enums.GameResult.Draw); + MultiPlayManager.AcceptDraw(); + }, () => + { + MultiPlayManager.RejectDraw(); + }); + }); + break; + case Constants.MultiplayManagerState.DrawRequestSent: + Debug.Log("무승부 요청 전송 완료"); + break; + case Constants.MultiplayManagerState.DrawAccepted: + Debug.Log("무승부 요청이 승락이 들어옴"); + ExecuteOnMainThread(() => + { + GameManager.Instance.panelManager.OpenEffectPanel(Enums.GameResult.Draw); + EndGame(Enums.GameResult.Draw); + }); + break; + case Constants.MultiplayManagerState.DrawConfirmed: + Debug.Log("무승부 요청 승락 완료"); + break; + case Constants.MultiplayManagerState.DrawRejected: + Debug.Log("무승부 요청이 거부가 들어옴"); + ExecuteOnMainThread(() => + { + GameManager.Instance.panelManager.OpenConfirmPanel("무승부 요청을 거부하였습니다.", () => { }); + }); + break; + case Constants.MultiplayManagerState.DrawRejectionConfirmed: + Debug.Log("무승부 요청 거부 완료"); + + break; + case Constants.MultiplayManagerState.ReceiveTimeout: + Debug.Log("상대방이 타임 아웃 됨"); + ExecuteOnMainThread(() => + { + GameManager.Instance.panelManager.OpenEffectPanel(Enums.GameResult.Win); + EndGame(Enums.GameResult.Win); + }); + break; + } + ReplayManager.Instance.InitReplayData(UserManager.Instance.Nickname,"nicknameB"); + }); + + MultiPlayManager.RegisterPlayer(UserManager.Instance.Nickname, UserManager.Instance.Rating, UserManager.Instance.imageIndex); + } + + private void SetupPlayer(bool isBlack, string roomId, string opponentNickname, int opponentImageIndex) + { + // 선공, 후공 처리 + var isFirstPlayer = isBlack; + + if (isFirstPlayer) + { + Debug.Log("해당 플레이어가 선공 입니다"); + FirstPlayerState = new PlayerState(true, MultiPlayManager, roomId); + SecondPlayerState = new MultiPlayerState(false, MultiPlayManager); + + UpdateUIForFirstPlayer(opponentNickname, opponentImageIndex); + } + else + { + Debug.Log("해당 플레이어가 후공 입니다"); + FirstPlayerState = new MultiPlayerState(true, MultiPlayManager); + SecondPlayerState = new PlayerState(false, MultiPlayManager, roomId); + + UpdateUIForSecondPlayer(opponentNickname, opponentImageIndex); + } + } + + private void UpdateUIForFirstPlayer(string opponentNickname, int opponentImageIndex) + { + ExecuteOnMainThread(() => + { + GameManager.Instance.InitPlayersName(UserManager.Instance.Nickname, opponentNickname); + GameManager.Instance.InitProfileImages(UserManager.Instance.imageIndex, opponentImageIndex); + + // 리플레이 데이터 업데이트 + ReplayManager.Instance.InitReplayData(UserManager.Instance.Nickname, opponentNickname, UserManager.Instance.imageIndex, opponentImageIndex); + }); + } + + private void UpdateUIForSecondPlayer(string opponentNickname, int opponentImageIndex) + { + ExecuteOnMainThread(() => + { + GameManager.Instance.InitPlayersName(opponentNickname, UserManager.Instance.Nickname); + GameManager.Instance.InitProfileImages(opponentImageIndex, UserManager.Instance.imageIndex); + + // 리플레이 데이터 업데이트 + ReplayManager.Instance.InitReplayData(opponentNickname, UserManager.Instance.Nickname, opponentImageIndex, UserManager.Instance.imageIndex); + }); + } + + + // 메인스레드에서 게임 시작 + private void StartGameOnMainThread() + { + ExecuteOnMainThread(() => + { + // 로딩 패널 열려있으면 닫기 + GameManager.Instance.panelManager.CloseLoadingPanel(); + + // 게임 시작 + SetState(FirstPlayerState); + }); + } + + // 방 데이터 유효성 검사 헬퍼 함수 + private bool ValidateRoomData(object roomData, string operationName) + { + if (roomData == null) + { + Debug.Log($"{operationName} 응답값이 null 입니다"); + return false; + } + return true; + } + + // 메인 스레드에서 실행하는 헬퍼 함수 + private void ExecuteOnMainThread(Action action) + { + UnityMainThreadDispatcher.Instance().Enqueue(action); + } + + private void InitializeSinglePlayMode() + { + FirstPlayerState = new PlayerState(true); + SecondPlayerState = new AIState(); + // AI 난이도 설정(급수 설정) + OmokAI.Instance.SetRating(UserManager.Instance.Rating); + + // 메인 스레드에서 실행 - UI 업데이트는 메인 스레드에서 실행 필요 + UnityMainThreadDispatcher.Instance().Enqueue(() => + { + // 스레드 확인 로그: 추후 디버깅 시 필요할 수 있을 것 같아 남겨둡니다 + // Debug.Log($"[UnityMainThreadDispatcher] 실행 스레드: {System.Threading.Thread.CurrentThread.ManagedThreadId}"); + + //AI닉네임 랜덤생성 + var aiName = RandomAINickname(); + var imageIndex = Random.Range(0, 2); + + //유저 이름 사진 초기화 + GameManager.Instance.InitPlayersName(UserManager.Instance.Nickname, aiName); + GameManager.Instance.InitProfileImages(UserManager.Instance.imageIndex, imageIndex); + // 리플레이 데이터 업데이트 + ReplayManager.Instance.InitReplayData(UserManager.Instance.Nickname,aiName, UserManager.Instance.imageIndex, imageIndex); + + // 로딩 패널 열려있으면 닫기 + GameManager.Instance.panelManager.CloseLoadingPanel(); + + // 첫 번째 플레이어(유저)부터 시작 + SetState(FirstPlayerState); + }); + } + + private void InitializeBoard(StoneController stoneController, Enums.GameType gameType) + { _board = new Enums.PlayerType[15, 15]; - this.stoneController = stoneController; - this.gameType = gameType; + StoneController = stoneController; + GameType = gameType; _totalStoneCounter = 0; RequestDrawChance = true; - - selectedRow = -1; - selectedCol = -1; - -#region Renju Init - // 금수 감지기 초기화 - _forbiddenDetector = new RenjuForbiddenMoveDetector(); -#endregion + SelectedRow = -1; + SelectedCol = -1; _lastRow = -1; _lastCol = -1; - //timer 초기화 + } + + private void InitializeFioTimer(FioTimer fioTimer) + { if (fioTimer != null) { - this.fioTimer = fioTimer; - this.fioTimer.InitTimer(); + FioTimer = fioTimer; + FioTimer.InitTimer(); //timer 시간초과시 진행 함수 - this.fioTimer.OnTimeout = () => + FioTimer.OnTimeout = () => { // 현재 턴의 플레이어가 로컬(유저)인지 확인 - bool isCurrentPlayerLocal = (currentTurn == Enums.PlayerType.PlayerA && firstPlayerState is PlayerState) || - (currentTurn == Enums.PlayerType.PlayerB && secondPlayerState is PlayerState); + bool isCurrentPlayerLocal = (CurrentTurn == Enums.PlayerType.PlayerA && FirstPlayerState is PlayerState) || + (CurrentTurn == Enums.PlayerType.PlayerB && SecondPlayerState is PlayerState); if (isCurrentPlayerLocal) // 내가 타임 오버일 때 { - if (this.gameType == Enums.GameType.MultiPlay) // 멀티플레이인 경우 + if (this.GameType == Enums.GameType.MultiPlay) // 멀티플레이인 경우 { - _multiplayManager?.SendTimeout(); + MultiPlayManager?.SendTimeout(); } GameManager.Instance.panelManager.OpenEffectPanel(Enums.GameResult.Lose); EndGame(Enums.GameResult.Lose); @@ -307,497 +342,45 @@ public class GameLogic : IDisposable } }; } - - switch (gameType) - { - // TODO: 현재 싱글 플레이로 바로 넘어가지 않기 때문에 미사용 중 - // case Enums.GameType.SinglePlay: - // firstPlayerState = new PlayerState(true); - // secondPlayerState = new AIState(); - // // AI 난이도 설정(급수 설정) - // OmokAI.Instance.SetRating(UserManager.Instance.Rating); - // - // //AI닉네임 랜덤생성 - // var aiName = RandomAINickname(); - // var imageIndex = UnityEngine.Random.Range(0, 2); - // - // //유저 이름 사진 초기화 - // GameManager.Instance.InitPlayersName(UserManager.Instance.Nickname, aiName); - // GameManager.Instance.InitProfileImages(UserManager.Instance.imageIndex, imageIndex); - // - // ReplayManager.Instance.InitReplayData(UserManager.Instance.Nickname,aiName, UserManager.Instance.imageIndex, imageIndex); - // - // SetState(firstPlayerState); - // break; - case Enums.GameType.MultiPlay: - // 메인 스레드에서 실행 - UI 업데이트는 메인 스레드에서 실행 필요 - UnityMainThreadDispatcher.Instance().Enqueue(() => - { - GameManager.Instance.panelManager.OpenLoadingPanel(true, true); - }); - _multiplayManager = new MultiplayManager((state, data) => - { - switch (state) - { - case Constants.MultiplayManagerState.CreateRoom: - Debug.Log("## Create Room"); - _roomId = data as string; - break; - case Constants.MultiplayManagerState.JoinRoom: - Debug.Log("## Join Room"); - var joinRoomData = data as JoinRoomData; - - // TODO: 응답값 없을 때 서버에서 다시 받아오기 or AI 플레이로 넘기는 처리 필요 - if (joinRoomData == null) - { - Debug.Log("Join Room 응답값이 null 입니다"); - return; - } - - // 선공, 후공 처리 - bool isFirstPlayer = joinRoomData.isBlack; - - if (isFirstPlayer) - { - Debug.Log("해당 플레이어가 선공 입니다"); - firstPlayerState = new PlayerState(true, _multiplayManager, joinRoomData.roomId); - secondPlayerState = new MultiPlayerState(false, _multiplayManager); - UnityMainThreadDispatcher.Instance().Enqueue(() => - { - GameManager.Instance.InitPlayersName(UserManager.Instance.Nickname, joinRoomData.opponentNickname); - GameManager.Instance.InitProfileImages(UserManager.Instance.imageIndex, joinRoomData.opponentImageIndex); - - // 리플레이 데이터 업데이트 - ReplayManager.Instance.InitReplayData(UserManager.Instance.Nickname, joinRoomData.opponentNickname, UserManager.Instance.imageIndex, joinRoomData.opponentImageIndex); - }); - } - else - { - Debug.Log("해당 플레이어가 후공 입니다"); - firstPlayerState = new MultiPlayerState(true, _multiplayManager); - secondPlayerState = new PlayerState(false, _multiplayManager, joinRoomData.roomId); - - UnityMainThreadDispatcher.Instance().Enqueue(() => - { - GameManager.Instance.InitPlayersName(joinRoomData.opponentNickname, UserManager.Instance.Nickname); - GameManager.Instance.InitProfileImages(joinRoomData.opponentImageIndex, UserManager.Instance.imageIndex); - - // 리플레이 데이터 업데이트 - ReplayManager.Instance.InitReplayData(joinRoomData.opponentNickname, UserManager.Instance.Nickname, joinRoomData.opponentImageIndex, UserManager.Instance.imageIndex); - }); - } - - // 메인 스레드에서 실행 - UI 업데이트는 메인 스레드에서 실행 필요 - UnityMainThreadDispatcher.Instance().Enqueue(() => - { - // 로딩 패널 열려있으면 닫기 - GameManager.Instance.panelManager.CloseLoadingPanel(); - - // 게임 시작 - SetState(firstPlayerState); - }); - break; - case Constants.MultiplayManagerState.SwitchAI: - Debug.Log("## Switching to AI Mode"); - SwitchToSinglePlayer(); - break; - case Constants.MultiplayManagerState.StartGame: - Debug.Log("## Start Game"); - var startGameData = data as StartGameData; - - // TODO: 응답값 없을 때 서버에서 다시 받아오기 or AI 플레이로 넘기는 처리 필요 - if (startGameData == null) - { - Debug.Log("Start Game 응답값이 null 입니다"); - return; - } - // 선공, 후공 처리 - isFirstPlayer = startGameData.isBlack; - - if (isFirstPlayer) - { - Debug.Log("해당 플레이어가 선공 입니다"); - firstPlayerState = new PlayerState(true, _multiplayManager, _roomId); - secondPlayerState = new MultiPlayerState(false, _multiplayManager); - UnityMainThreadDispatcher.Instance().Enqueue(() => - { - GameManager.Instance.InitPlayersName(UserManager.Instance.Nickname, startGameData.opponentNickname); - GameManager.Instance.InitProfileImages(UserManager.Instance.imageIndex, startGameData.opponentImageIndex); - - // 리플레이 데이터 업데이트 - ReplayManager.Instance.InitReplayData(UserManager.Instance.Nickname, startGameData.opponentNickname, UserManager.Instance.imageIndex, startGameData.opponentImageIndex); - }); - } - else - { - Debug.Log("해당 플레이어가 후공 입니다"); - firstPlayerState = new MultiPlayerState(true, _multiplayManager); - secondPlayerState = new PlayerState(false, _multiplayManager, _roomId); - UnityMainThreadDispatcher.Instance().Enqueue(() => - { - GameManager.Instance.InitPlayersName(startGameData.opponentNickname, UserManager.Instance.Nickname); - GameManager.Instance.InitProfileImages(startGameData.opponentImageIndex, UserManager.Instance.imageIndex); - - // 리플레이 데이터 업데이트 - ReplayManager.Instance.InitReplayData(startGameData.opponentNickname, UserManager.Instance.Nickname, startGameData.opponentImageIndex, UserManager.Instance.imageIndex); - }); - } - - // 메인 스레드에서 실행 - UI 업데이트는 메인 스레드에서 실행 필요 - UnityMainThreadDispatcher.Instance().Enqueue(() => - { - // 로딩 패널 열려있으면 닫기 - GameManager.Instance.panelManager.CloseLoadingPanel(); - - // 게임 시작 - SetState(firstPlayerState); - }); - break; - case Constants.MultiplayManagerState.ExitRoom: - Debug.Log("## Exit Room"); - // TODO: Exit Room 처리 - break; - case Constants.MultiplayManagerState.EndGame: - Debug.Log("## End Game"); - // TODO: End Room 처리 - break; - case Constants.MultiplayManagerState.DoSurrender: - Debug.Log("상대방의 항복 요청 들어옴"); - UnityMainThreadDispatcher.Instance().Enqueue(() => - { - GameManager.Instance.panelManager.OpenEffectPanel(Enums.GameResult.Win); - EndGame(Enums.GameResult.Win); - }); - break; - case Constants.MultiplayManagerState.SurrenderConfirmed: - Debug.Log("항복 요청 전송 완료"); - UnityMainThreadDispatcher.Instance().Enqueue(() => - { - GameManager.Instance.panelManager.OpenEffectPanel(Enums.GameResult.Lose); - EndGame(Enums.GameResult.Lose); - }); - break; - case Constants.MultiplayManagerState.ReceiveDrawRequest: - Debug.Log("상대방의 무승부 요청 들어옴"); - UnityMainThreadDispatcher.Instance().Enqueue(() => - { - GameManager.Instance.panelManager.OpenDrawConfirmPanel("무승부 요청을 승락하시겠습니까?", () => - { - GameManager.Instance.panelManager.OpenEffectPanel(Enums.GameResult.Draw); - EndGame(Enums.GameResult.Draw); - _multiplayManager.AcceptDraw(); - }, () => - { - _multiplayManager.RejectDraw(); - }); - }); - break; - case Constants.MultiplayManagerState.DrawRequestSent: - Debug.Log("무승부 요청 전송 완료"); - break; - case Constants.MultiplayManagerState.DrawAccepted: - Debug.Log("무승부 요청이 승락이 들어옴"); - UnityMainThreadDispatcher.Instance().Enqueue(() => - { - GameManager.Instance.panelManager.OpenEffectPanel(Enums.GameResult.Draw); - EndGame(Enums.GameResult.Draw); - }); - break; - case Constants.MultiplayManagerState.DrawConfirmed: - Debug.Log("무승부 요청 승락 완료"); - break; - case Constants.MultiplayManagerState.DrawRejected: - Debug.Log("무승부 요청이 거부가 들어옴"); - UnityMainThreadDispatcher.Instance().Enqueue(() => - { - GameManager.Instance.panelManager.OpenConfirmPanel("무승부 요청을 거부하였습니다.", () => { }); - }); - break; - case Constants.MultiplayManagerState.DrawRejectionConfirmed: - Debug.Log("무승부 요청 거부 완료"); - - break; - case Constants.MultiplayManagerState.ReceiveTimeout: - Debug.Log("상대방이 타임 아웃 됨"); - UnityMainThreadDispatcher.Instance().Enqueue(() => - { - GameManager.Instance.panelManager.OpenEffectPanel(Enums.GameResult.Win); - EndGame(Enums.GameResult.Win); - }); - break; - } - ReplayManager.Instance.InitReplayData(UserManager.Instance.Nickname,"nicknameB"); - - }); - _multiplayManager.RegisterPlayer(UserManager.Instance.Nickname, UserManager.Instance.Rating, UserManager.Instance.imageIndex); - break; - case Enums.GameType.Replay: - //TODO: 리플레이 구현 - break; - } } - - //AI닉네임 랜덤 생성 - private string RandomAINickname() + + #endregion + + public Enums.PlayerType[,] GetBoard() => _board; + + // 보드 초기화 + public void ResetBoard() => Array.Clear(_board, 0, _board.Length); + + // 상대가 매칭되지 않을 경우 AI로 전환하는 함수 + private void SwitchToSinglePlayer() { - string[] AI_NAMIES = { "이세돌", "신사동호랭이","진짜인간임","종로3가짱돌","마스터김춘배","62세황순자","고준일 강사님"}; - - var index = UnityEngine.Random.Range(0, AI_NAMIES.Length); - - return AI_NAMIES[index]; - } - - public void SwitchToSinglePlayer() - { - _multiplayManager?.Dispose(); + MultiPlayManager?.Dispose(); // 기존 멀티플레이 상태 초기화 - _multiplayManager = null; + MultiPlayManager = null; _roomId = null; // 싱글 플레이 상태로 변경 - firstPlayerState = new PlayerState(true); - secondPlayerState = new AIState(); - // AI 난이도 설정(급수 설정) - OmokAI.Instance.SetRating(UserManager.Instance.Rating); - - // 메인 스레드에서 실행 - UI 업데이트는 메인 스레드에서 실행 필요 - UnityMainThreadDispatcher.Instance().Enqueue(() => - { - // 스레드 확인 로그: 추후 디버깅 시 필요할 수 있을 것 같아 남겨둡니다 - // Debug.Log($"[UnityMainThreadDispatcher] 실행 스레드: {System.Threading.Thread.CurrentThread.ManagedThreadId}"); - - //AI닉네임 랜덤생성 - var aiName = RandomAINickname(); - var imageIndex = UnityEngine.Random.Range(0, 2); - - //유저 이름 사진 초기화 - GameManager.Instance.InitPlayersName(UserManager.Instance.Nickname, aiName); - GameManager.Instance.InitProfileImages(UserManager.Instance.imageIndex, imageIndex); - // 리플레이 데이터 업데이트 - ReplayManager.Instance.InitReplayData(UserManager.Instance.Nickname,aiName, UserManager.Instance.imageIndex, imageIndex); - - // 로딩 패널 열려있으면 닫기 - GameManager.Instance.panelManager.CloseLoadingPanel(); - - // 첫 번째 플레이어(유저)부터 시작 - SetState(firstPlayerState); - }); - } - - public void Dispose() - { - _multiplayManager?.LeaveRoom(_roomId); - _multiplayManager?.Dispose(); - } - - //돌 카운터 증가 함수 - public void CountStoneCounter() - { - _totalStoneCounter++; - } - - //착수 버튼 클릭시 호출되는 함수 - public void OnConfirm() - { - _currentPlayerState.ProcessMove(this, currentTurn, selectedRow, selectedCol); - } - //보드 초기화 - public void ResetBoard() - { - Array.Clear(_board, 0, _board.Length); + InitializeSinglePlayMode(); } public void SetState(BasePlayerState state) { - _currentPlayerState?.OnExit(this); - _currentPlayerState = state; - _currentPlayerState?.OnEnter(this); - //턴 표시 - GameManager.Instance.SetTurnIndicator(_currentPlayerState == firstPlayerState); - } - - //스톤의 상태변경 명령함수 - public void SetStoneNewState(Enums.StoneState state, int row, int col) - { - stoneController.SetStoneState(state, row, col); + CurrentPlayerState?.OnExit(this); + CurrentPlayerState = state; + CurrentPlayerState?.OnEnter(this); + // 턴 표시 + GameManager.Instance.SetTurnIndicator(CurrentPlayerState == FirstPlayerState); } - public void SetStoneSelectedState(int row, int col) - { - if (_board[row, col] != Enums.PlayerType.None) return; - - if (stoneController.GetStoneState(row, col) != Enums.StoneState.None && currentTurn == Enums.PlayerType.PlayerA) return; - //첫수 및 중복 확인 - if ((selectedRow != row || selectedCol != col) && (selectedRow != -1 && selectedCol != -1)) - { - stoneController.SetStoneState(Enums.StoneState.None,selectedRow, selectedCol); - } - selectedRow = row; - selectedCol = col; - stoneController.SetStoneState(Enums.StoneState.Selected, row, col); - } - - //보드에 돌추가 함수 - public void SetNewBoardValue(Enums.PlayerType playerType, int row, int col) - { - if (_board[row, col] != Enums.PlayerType.None) return; + #region Utility - switch (playerType) - { - case Enums.PlayerType.PlayerA: - stoneController.SetStoneType(Enums.StoneType.Black, row, col); - stoneController.SetStoneState(Enums.StoneState.LastPositioned, row, col); - _board[row, col] = Enums.PlayerType.PlayerA; - LastNSelectedSetting(row, col); - - ReplayManager.Instance.RecordStonePlaced(Enums.StoneType.Black, row, col); //기보 데이터 저장 - break; - case Enums.PlayerType.PlayerB: - stoneController.SetStoneType(Enums.StoneType.White, row, col); - stoneController.SetStoneState(Enums.StoneState.LastPositioned, row, col); - _board[row, col] = Enums.PlayerType.PlayerB; - LastNSelectedSetting(row, col); - - ReplayManager.Instance.RecordStonePlaced(Enums.StoneType.White, row, col); - - break; - } - } - - //돌 지우는 함수 - public void RemoveStone(int row, int col) - { - _board[row, col] = Enums.PlayerType.None; - stoneController.SetStoneType(Enums.StoneType.None, row, col); - stoneController.SetStoneState(Enums.StoneState.None, row, col); - } - - //마지막 좌표와 선택 좌표 세팅 - private void LastNSelectedSetting(int row, int col) - { - //첫수 확인 - if (_lastRow != -1 || _lastCol != -1) - { - stoneController.SetStoneState(Enums.StoneState.None, _lastRow, _lastCol); - } - //마지막 좌표 저장 - _lastRow = row; - _lastCol = col; - //선택 좌표 초기화 - selectedRow = -1; - selectedCol = -1; - } - //게임 끝 - public void EndGame(Enums.GameResult result) - { - SetState(null); - ReplayManager.Instance.SaveReplayDataResult(result); - //TODO: 게임 종료 후 행동 구현 - } - - //승리 확인 함수 - public bool CheckGameWin(Enums.PlayerType player, int row, int col) - { - return OmokAI.Instance.CheckGameWin(player, _board, row, col); - } - - // 특정 방향으로 같은 돌 개수와 열린 끝 개수를 계산하는 함수 - private (int count, int openEnds) CountStones( - Enums.PlayerType[,] board, int row, int col, int[] direction, Enums.PlayerType player) - { - int size = board.GetLength(0); - int count = 0; - int openEnds = 0; - - // 정방향 탐색 - int r = row + direction[0], c = col + direction[1]; - while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == player) - { - count++; - r += direction[0]; // row값 옮기기 - c += direction[1]; // col값 옮기기 - } - - if (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.None) - { - openEnds++; - } - - // 역방향 탐색 - r = row - direction[0]; - c = col - direction[1]; - while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == player) - { - count++; - r -= direction[0]; - c -= direction[1]; - } - - if (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.None) - { - openEnds++; - } - - return (count, openEnds); - } - - public Enums.PlayerType[,] GetBoard() - { - return _board; - } - //무승부 확인 - public bool CheckGameDraw() - { - if (CheckIsFull(_board)) return true; // 빈 칸이 없으면 무승부 - bool playerAHasChance = CheckFiveChance(_board, Enums.PlayerType.PlayerA); - bool playerBHasChance = CheckFiveChance(_board, Enums.PlayerType.PlayerB); - return !(playerAHasChance || playerBHasChance); // 둘 다 기회가 없으면 무승부 - } - - //연속되는 5개가 만들어질 기회가 있는지 판단 - private bool CheckFiveChance(Enums.PlayerType[,] board, Enums.PlayerType player) - { - var tempBoard = (Enums.PlayerType[,])board.Clone(); - int size = board.GetLength(0); - for (int row = 0; row < size; row++) - { - for (int col = 0; col < size; col++) - { - if (tempBoard[row, col] != Enums.PlayerType.None) continue; - tempBoard[row, col] = player; - foreach (var dir in AIConstants.Directions) - { - var (count, _) = CountStones(tempBoard, row, col, dir, player); - - // 자기 자신 포함하여 5개 이상일 시 true 반환 - if (count + 1 >= Constants.WIN_COUNT) return true; - } - } - } - return false; - } - //보드가 꽉 찼는지 확인 - private static bool CheckIsFull(Enums.PlayerType[,] board) - { - int size = board.GetLength(0); - for (int row = 0; row < size; row++) - { - for (int col = 0; col < size; col++) - { - if (board[row, col] == Enums.PlayerType.None) return false; - } - } - return true; - } - - #region Renju Rule Detector // 금수 위치 업데이트 및 표시 public void UpdateForbiddenMoves() { ClearForbiddenMarks(); - if (currentTurn == Enums.PlayerType.PlayerA) + if (CurrentTurn == Enums.PlayerType.PlayerA) { var cloneBoard = (Enums.PlayerType[,])_board.Clone(); _forbiddenMoves = _forbiddenDetector.RenjuForbiddenMove(cloneBoard); @@ -821,5 +404,22 @@ public class GameLogic : IDisposable } } } -#endregion + + // AI닉네임 랜덤 생성 + private string RandomAINickname() + { + string[] AI_NAMIES = { "이세돌", "신사동호랭이","진짜인간임","종로3가짱돌","마스터김춘배","62세황순자","고준일 강사님"}; + + var index = Random.Range(0, AI_NAMIES.Length); + + return AI_NAMIES[index]; + } + + #endregion + + public void Dispose() + { + MultiPlayManager?.LeaveRoom(_roomId); + MultiPlayManager?.Dispose(); + } } diff --git a/Assets/Script/Game/GameManager.cs b/Assets/Script/Game/GameManager.cs index b2dca0b..ef55b5b 100644 --- a/Assets/Script/Game/GameManager.cs +++ b/Assets/Script/Game/GameManager.cs @@ -30,7 +30,7 @@ public class GameManager : Singleton public MultiplayManager GetMultiplayManager() { - _multiplayManager = _gameLogic._multiplayManager; + _multiplayManager = _gameLogic.MultiPlayManager; if (_multiplayManager == null) Debug.Log("MultiplayManager가 null입니다"); return _multiplayManager; } @@ -49,7 +49,7 @@ public class GameManager : Singleton public void OnClickConfirmButton() { - if (_gameLogic.selectedRow != -1 && _gameLogic.selectedCol != -1) + if (_gameLogic.SelectedRow != -1 && _gameLogic.SelectedCol != -1) { _gameLogic.OnConfirm(); } @@ -99,7 +99,7 @@ public class GameManager : Singleton if (_gameLogic == null) return; _gameLogic.ResetBoard(); _stoneController.InitStones(); - _gameLogic.SetState(_gameLogic.firstPlayerState); + _gameLogic.SetState(_gameLogic.FirstPlayerState); } //유저 이름 Game UI에 초기화 public void InitPlayersName(string playerNameA, string playerNameB) diff --git a/Assets/Script/Game/GameStates.meta b/Assets/Script/Game/GameStates.meta new file mode 100644 index 0000000..7abcde1 --- /dev/null +++ b/Assets/Script/Game/GameStates.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b15e90e5f55e4d338705bfffd558d056 +timeCreated: 1743068734 \ No newline at end of file diff --git a/Assets/Script/Game/GameStates/AIState.cs b/Assets/Script/Game/GameStates/AIState.cs new file mode 100644 index 0000000..29de06a --- /dev/null +++ b/Assets/Script/Game/GameStates/AIState.cs @@ -0,0 +1,27 @@ +public class AIState: BasePlayerState +{ + public override void OnEnter(GameLogic gameLogic) + { + gameLogic.FioTimer.StartTimer(); + OmokAI.Instance.StartBestMoveSearch(gameLogic.GetBoard(), (bestMove) => + { + if(bestMove.HasValue) + HandleMove(gameLogic, bestMove.Value.Item1, bestMove.Value.Item2); + }); + } + + public override void OnExit(GameLogic gameLogic) + { + gameLogic.FioTimer.InitTimer(); + } + + public override void HandleMove(GameLogic gameLogic, int row, int col) + { + ProcessMove(gameLogic, Enums.PlayerType.PlayerB,row, col); + } + + public override void HandleNextTurn(GameLogic gameLogic) + { + gameLogic.SetState(gameLogic.FirstPlayerState); + } +} \ No newline at end of file diff --git a/Assets/Script/Game/GameStates/AIState.cs.meta b/Assets/Script/Game/GameStates/AIState.cs.meta new file mode 100644 index 0000000..877d37d --- /dev/null +++ b/Assets/Script/Game/GameStates/AIState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fbd2216a641845bc9892444713d3497f +timeCreated: 1743068785 \ No newline at end of file diff --git a/Assets/Script/Game/GameStates/BasePlayerState.cs b/Assets/Script/Game/GameStates/BasePlayerState.cs new file mode 100644 index 0000000..7a73f37 --- /dev/null +++ b/Assets/Script/Game/GameStates/BasePlayerState.cs @@ -0,0 +1,58 @@ +using UnityEngine; + +public abstract class BasePlayerState +{ + public abstract void OnEnter(GameLogic gameLogic); + public abstract void OnExit(GameLogic gameLogic); + public abstract void HandleMove(GameLogic gameLogic, int row, int col); + public abstract void HandleNextTurn(GameLogic gameLogic); + + protected string _roomId; + protected bool _isMultiplay; + protected MultiplayManager _multiplayManager; + + public void ProcessMove(GameLogic gameLogic, Enums.PlayerType playerType, int row, int col) + { + gameLogic.FioTimer.PauseTimer(); + gameLogic.SetNewBoardValue(playerType, row, col); + gameLogic.CountStoneCounter(); + + if (_isMultiplay) + { + _multiplayManager.SendPlayerMove(_roomId, new Vector2Int(row, col)); + } + + if (gameLogic.CheckGameWin(playerType, row, col)) + { + var gameResult = playerType == Enums.PlayerType.PlayerA? Enums.GameResult.Win:Enums.GameResult.Lose; + if (gameLogic.GameType == Enums.GameType.MultiPlay) + { + if (gameLogic.FirstPlayerState.GetType() != typeof(PlayerState)) + { + gameResult = gameResult == Enums.GameResult.Win ? Enums.GameResult.Lose : Enums.GameResult.Win; + } + } + GameManager.Instance.panelManager.OpenEffectPanel(gameResult); + gameLogic.EndGame(gameResult); + } + else + { + if (gameLogic.TotalStoneCounter >= Constants.MinCountForDrawCheck) + { + if (gameLogic.CheckGameDraw()) + { + GameManager.Instance.panelManager.OpenEffectPanel(Enums.GameResult.Draw); + gameLogic.EndGame(Enums.GameResult.Draw); + } + else + { + HandleNextTurn(gameLogic); + } + } + else + { + HandleNextTurn(gameLogic); + } + } + } +} \ No newline at end of file diff --git a/Assets/Script/Game/GameStates/BasePlayerState.cs.meta b/Assets/Script/Game/GameStates/BasePlayerState.cs.meta new file mode 100644 index 0000000..7d599d0 --- /dev/null +++ b/Assets/Script/Game/GameStates/BasePlayerState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 28db9c0415a04bd288598b502b43066f +timeCreated: 1743068714 \ No newline at end of file diff --git a/Assets/Script/Game/GameStates/MultiPlayerState.cs b/Assets/Script/Game/GameStates/MultiPlayerState.cs new file mode 100644 index 0000000..bb0390b --- /dev/null +++ b/Assets/Script/Game/GameStates/MultiPlayerState.cs @@ -0,0 +1,62 @@ +public class MultiPlayerState: BasePlayerState +{ + private Enums.PlayerType _playerType; + private bool _isFirstPlayer; + + private MultiplayManager _multiplayManager; + + public MultiPlayerState(bool isFirstPlayer, MultiplayManager multiplayManager) + { + _isFirstPlayer = isFirstPlayer; + _playerType = isFirstPlayer ? Enums.PlayerType.PlayerA : Enums.PlayerType.PlayerB; + _multiplayManager = multiplayManager; + } + + public override void OnEnter(GameLogic gameLogic) + { + gameLogic.FioTimer.StartTimer(); + //TODO: 첫번째 플레이어면 렌주 룰 확인 + #region Renju Turn Set + // 턴이 변경될 때마다 금수 위치 업데이트 + gameLogic.UpdateForbiddenMoves(); + #endregion + + gameLogic.CurrentTurn = _playerType; + // gameLogic.stoneController.OnStoneClickedDelegate = (row, col) => + // { + // HandleMove(gameLogic, row, col); + // }; + _multiplayManager.OnOpponentMove = moveData => + { + var row = moveData.position.x; + var col = moveData.position.y; + UnityThread.executeInUpdate(() => + { + HandleMove(gameLogic, row, col); + }); + }; + } + + public override void OnExit(GameLogic gameLogic) + { + gameLogic.FioTimer.InitTimer(); + _multiplayManager.OnOpponentMove = null; + } + + public override void HandleMove(GameLogic gameLogic, int row, int col) + { + ProcessMove(gameLogic, _playerType, row, col); + } + + public override void HandleNextTurn(GameLogic gameLogic) + { + if (_isFirstPlayer) + { + gameLogic.SetState(gameLogic.SecondPlayerState); + } + else + { + gameLogic.SetState(gameLogic.FirstPlayerState); + } + } +} \ No newline at end of file diff --git a/Assets/Script/Game/GameStates/MultiPlayerState.cs.meta b/Assets/Script/Game/GameStates/MultiPlayerState.cs.meta new file mode 100644 index 0000000..a97769a --- /dev/null +++ b/Assets/Script/Game/GameStates/MultiPlayerState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 20e292a1c2064962ab8eaaa5a80bc3b9 +timeCreated: 1743068802 \ No newline at end of file diff --git a/Assets/Script/Game/GameStates/PlayerState.cs b/Assets/Script/Game/GameStates/PlayerState.cs new file mode 100644 index 0000000..c5d38a9 --- /dev/null +++ b/Assets/Script/Game/GameStates/PlayerState.cs @@ -0,0 +1,63 @@ +public class PlayerState : BasePlayerState +{ + private Enums.PlayerType _playerType; + private bool _isFirstPlayer; + + public PlayerState(bool isFirstPlayer) + { + _isFirstPlayer = isFirstPlayer; + _playerType = isFirstPlayer ? Enums.PlayerType.PlayerA : Enums.PlayerType.PlayerB; + _isMultiplay = false; + } + + public PlayerState(bool isFirstPlayer, MultiplayManager multiplayManager, string roomId) + : this(isFirstPlayer) + { + _isFirstPlayer = isFirstPlayer; + _multiplayManager = multiplayManager; + _roomId = roomId; + _isMultiplay = true; + } + + public override void OnEnter(GameLogic gameLogic) + { + gameLogic.FioTimer.StartTimer(); + + //TODO: 첫번째 플레이어면 렌주 룰 확인 + #region Renju Turn Set + // 턴이 변경될 때마다 금수 위치 업데이트 + gameLogic.UpdateForbiddenMoves(); + #endregion + + gameLogic.CurrentTurn = _playerType; + gameLogic.StoneController.OnStoneClickedDelegate = (row, col) => + { + HandleMove(gameLogic, row, col); + }; + } + + public override void OnExit(GameLogic gameLogic) + { + //TODO: 렌주 룰 금수자리 초기화 + + gameLogic.FioTimer.InitTimer(); + gameLogic.StoneController.OnStoneClickedDelegate = null; + } + + public override void HandleMove(GameLogic gameLogic, int row, int col) + { + gameLogic.SetStoneSelectedState(row, col); + } + + public override void HandleNextTurn(GameLogic gameLogic) + { + if (_isFirstPlayer) + { + gameLogic.SetState(gameLogic.SecondPlayerState); + } + else + { + gameLogic.SetState(gameLogic.FirstPlayerState); + } + } +} \ No newline at end of file diff --git a/Assets/Script/Game/GameStates/PlayerState.cs.meta b/Assets/Script/Game/GameStates/PlayerState.cs.meta new file mode 100644 index 0000000..5f9c74f --- /dev/null +++ b/Assets/Script/Game/GameStates/PlayerState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 951fc71a9c154522b3562053350536b8 +timeCreated: 1743068763 \ No newline at end of file diff --git a/Assets/Script/Game/GameUtility.meta b/Assets/Script/Game/GameUtility.meta new file mode 100644 index 0000000..cc52462 --- /dev/null +++ b/Assets/Script/Game/GameUtility.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 79bd83540d994967a0e3b7ee6d5bc835 +timeCreated: 1743076291 \ No newline at end of file diff --git a/Assets/Script/Game/GameUtility/GameRoutine.cs b/Assets/Script/Game/GameUtility/GameRoutine.cs new file mode 100644 index 0000000..18c131f --- /dev/null +++ b/Assets/Script/Game/GameUtility/GameRoutine.cs @@ -0,0 +1,89 @@ +public partial class GameLogic +{ + // 돌 카운터 증가 함수 + public void CountStoneCounter() => _totalStoneCounter++; + + // 착수 버튼 클릭시 호출되는 함수 + public void OnConfirm() => CurrentPlayerState.ProcessMove(this, CurrentTurn, SelectedRow, SelectedCol); + + // 스톤의 상태변경 명령함수 + private void SetStoneNewState(Enums.StoneState state, int row, int col) + { + StoneController.SetStoneState(state, row, col); + } + + public void SetStoneSelectedState(int row, int col) + { + if (_board[row, col] != Enums.PlayerType.None) return; + + if (StoneController.GetStoneState(row, col) != Enums.StoneState.None && + CurrentTurn == Enums.PlayerType.PlayerA) return; + // 첫수 및 중복 확인 + if ((SelectedRow != row || SelectedCol != col) && (SelectedRow != -1 && SelectedCol != -1)) + { + StoneController.SetStoneState(Enums.StoneState.None, SelectedRow, SelectedCol); + } + + (SelectedRow, SelectedCol) = (row, col); + + StoneController.SetStoneState(Enums.StoneState.Selected, row, col); + } + + // 보드에 돌추가 함수 + public void SetNewBoardValue(Enums.PlayerType playerType, int row, int col) + { + if (_board[row, col] != Enums.PlayerType.None) return; + + switch (playerType) + { + case Enums.PlayerType.PlayerA: + StoneController.SetStoneType(Enums.StoneType.Black, row, col); + StoneController.SetStoneState(Enums.StoneState.LastPositioned, row, col); + _board[row, col] = Enums.PlayerType.PlayerA; + LastNSelectedSetting(row, col); + + ReplayManager.Instance.RecordStonePlaced(Enums.StoneType.Black, row, col); //기보 데이터 저장 + break; + case Enums.PlayerType.PlayerB: + StoneController.SetStoneType(Enums.StoneType.White, row, col); + StoneController.SetStoneState(Enums.StoneState.LastPositioned, row, col); + _board[row, col] = Enums.PlayerType.PlayerB; + LastNSelectedSetting(row, col); + + ReplayManager.Instance.RecordStonePlaced(Enums.StoneType.White, row, col); + break; + } + } + + // 돌 지우는 함수 + public void RemoveStone(int row, int col) + { + _board[row, col] = Enums.PlayerType.None; + StoneController.SetStoneType(Enums.StoneType.None, row, col); + StoneController.SetStoneState(Enums.StoneState.None, row, col); + } + + // 마지막 좌표와 선택 좌표 세팅 + private void LastNSelectedSetting(int row, int col) + { + //첫수 확인 + if (_lastRow != -1 || _lastCol != -1) + { + StoneController.SetStoneState(Enums.StoneState.None, _lastRow, _lastCol); + } + + //마지막 좌표 저장 + (_lastRow, _lastCol) = (row, col); + + //선택 좌표 초기화 + (SelectedRow, SelectedCol) = (-1, -1); + } + + // 게임 끝 + public void EndGame(Enums.GameResult result) + { + SetState(null); + ReplayManager.Instance.SaveReplayDataResult(result); + //TODO: 게임 종료 후 행동 구현 + } +} \ No newline at end of file diff --git a/Assets/Script/Game/GameUtility/GameRoutine.cs.meta b/Assets/Script/Game/GameUtility/GameRoutine.cs.meta new file mode 100644 index 0000000..80cebb3 --- /dev/null +++ b/Assets/Script/Game/GameUtility/GameRoutine.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d911ca14696a433ebc112577b60de664 +timeCreated: 1743076571 \ No newline at end of file diff --git a/Assets/Script/Game/GameUtility/GameWinCheck.cs b/Assets/Script/Game/GameUtility/GameWinCheck.cs new file mode 100644 index 0000000..2487c7a --- /dev/null +++ b/Assets/Script/Game/GameUtility/GameWinCheck.cs @@ -0,0 +1,96 @@ +public partial class GameLogic +{ + //승리 확인 함수 + public bool CheckGameWin(Enums.PlayerType player, int row, int col) + { + return OmokAI.Instance.CheckGameWin(player, _board, row, col); + } + + // 특정 방향으로 같은 돌 개수와 열린 끝 개수를 계산하는 함수 + private (int count, int openEnds) CountStones( + Enums.PlayerType[,] board, int row, int col, int[] direction, Enums.PlayerType player) + { + int size = board.GetLength(0); + int count = 0; + int openEnds = 0; + + // 정방향 탐색 + int r = row + direction[0], c = col + direction[1]; + while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == player) + { + count++; + r += direction[0]; // row값 옮기기 + c += direction[1]; // col값 옮기기 + } + + if (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.None) + { + openEnds++; + } + + // 역방향 탐색 + r = row - direction[0]; + c = col - direction[1]; + while (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == player) + { + count++; + r -= direction[0]; + c -= direction[1]; + } + + if (r >= 0 && r < size && c >= 0 && c < size && board[r, c] == Enums.PlayerType.None) + { + openEnds++; + } + + return (count, openEnds); + } + + //무승부 확인 + public bool CheckGameDraw() + { + if (CheckIsFull(_board)) return true; // 빈 칸이 없으면 무승부 + bool playerAHasChance = CheckFiveChance(_board, Enums.PlayerType.PlayerA); + bool playerBHasChance = CheckFiveChance(_board, Enums.PlayerType.PlayerB); + return !(playerAHasChance || playerBHasChance); // 둘 다 기회가 없으면 무승부 + } + + //연속되는 5개가 만들어질 기회가 있는지 판단 + private bool CheckFiveChance(Enums.PlayerType[,] board, Enums.PlayerType player) + { + var tempBoard = (Enums.PlayerType[,])board.Clone(); + int size = board.GetLength(0); + for (int row = 0; row < size; row++) + { + for (int col = 0; col < size; col++) + { + if (tempBoard[row, col] != Enums.PlayerType.None) continue; + tempBoard[row, col] = player; + foreach (var dir in AIConstants.Directions) + { + var (count, _) = CountStones(tempBoard, row, col, dir, player); + + // 자기 자신 포함하여 5개 이상일 시 true 반환 + if (count + 1 >= Constants.WIN_COUNT) return true; + } + } + } + + return false; + } + + //보드가 꽉 찼는지 확인 + private static bool CheckIsFull(Enums.PlayerType[,] board) + { + int size = board.GetLength(0); + for (int row = 0; row < size; row++) + { + for (int col = 0; col < size; col++) + { + if (board[row, col] == Enums.PlayerType.None) return false; + } + } + + return true; + } +} \ No newline at end of file diff --git a/Assets/Script/Game/GameUtility/GameWinCheck.cs.meta b/Assets/Script/Game/GameUtility/GameWinCheck.cs.meta new file mode 100644 index 0000000..f6d41b4 --- /dev/null +++ b/Assets/Script/Game/GameUtility/GameWinCheck.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 367e9d42ab0f4a879d00bac722922970 +timeCreated: 1743076291 \ No newline at end of file