├── .editorconfig ├── .gitignore ├── Archive ├── deprecated_BlockMap.h ├── old_ClientSession.cpp ├── old_ClientSession.h ├── old_Common.h ├── old_GameRoom.cpp ├── old_GameRoom.h ├── old_Server.cpp └── old_Server.h ├── Client ├── Block.cpp ├── Block.h ├── Cell.cpp ├── Cell.h ├── Client.cpp ├── Client.h ├── Client.vcxproj ├── Client.vcxproj.filters ├── ConsoleFrame.cpp ├── ConsoleFrame.h ├── ConsoleRenderer.cpp ├── ConsoleRenderer.h ├── Engine.cpp ├── Engine.h ├── GUI │ ├── Button.cpp │ ├── Button.h │ ├── InteractiveWidget.h │ └── Widget.h ├── GameModeType.h ├── Managers │ ├── ColorManager.cpp │ ├── ColorManager.h │ ├── ConsoleColor.h │ ├── EventManager.cpp │ ├── EventManager.h │ ├── InputManager.cpp │ ├── InputManager.h │ ├── SceneManager.cpp │ ├── SceneManager.h │ ├── UIManager.cpp │ └── UIManager.h ├── ModelPointer.cpp ├── ModelPointer.h ├── Scenes │ ├── GameOverScene.cpp │ ├── GameOverScene.h │ ├── GameScene.cpp │ ├── GameScene.h │ ├── MainMenuScene.cpp │ ├── MainMenuScene.h │ ├── Scene.cpp │ ├── Scene.h │ └── SceneType.h ├── TetrisBoard.cpp ├── TetrisBoard.h └── main.cpp ├── Common ├── Common.vcxproj ├── Common.vcxproj.filters └── Libs │ ├── Console │ ├── Console.cpp │ ├── Console.h │ ├── ConsoleTypes.h │ ├── ConsoleUnix.cpp │ ├── ConsoleUnix.h │ ├── ConsoleWin32.cpp │ ├── ConsoleWin32.h │ ├── Consoles.h │ ├── Panel │ │ ├── Panel.cpp │ │ └── Panel.h │ └── README.md │ └── Server │ ├── Command │ ├── ACommandAdaptorIntString.h │ ├── ACommandStandardDoubleString.cpp │ ├── ACommandStandardDoubleString.h │ ├── ACommandStandardMessage.cpp │ ├── ACommandStandardMessage.h │ ├── ACommandStandardNumericData.cpp │ ├── ACommandStandardNumericData.h │ ├── ACommandStandardSignal.cpp │ ├── ACommandStandardSignal.h │ ├── ACommandStandardStringList.cpp │ ├── ACommandStandardStringList.h │ ├── Authentication │ │ ├── C2S_RequestLogin.h │ │ └── S2C_Welcome.h │ ├── Chat │ │ ├── C2S_SendChatMessage.h │ │ └── S2C_SendChatMessage.h │ ├── Commands.h │ ├── Connection │ │ └── Common_NotifyDisconnect.h │ ├── ICommand.h │ └── Lobby │ │ ├── C2S_RequestLobbyInfo.cpp │ │ ├── C2S_RequestLobbyInfo.h │ │ ├── S2C_NotifyLobbyEntry.h │ │ ├── S2C_SendLobbyInfo.h │ │ └── S2C_SendLobbyUserList.h │ ├── Constants.h │ ├── Hub │ ├── ClientHub.cpp │ ├── ClientHub.h │ ├── GameRoom.cpp │ ├── GameRoom.h │ ├── Hub.cpp │ ├── Hub.h │ ├── IManager.h │ ├── Lobby.cpp │ ├── Lobby.h │ ├── TicketBooth.cpp │ └── TicketBooth.h │ ├── Protocol │ ├── Endian.h │ ├── Packet.cpp │ ├── Packet.h │ ├── SerDes.cpp │ └── SerDes.h │ ├── README.md │ ├── Translation.h │ └── User │ ├── Session.cpp │ ├── Session.h │ ├── User.cpp │ └── User.h ├── HongLabTetris.sln ├── README.md ├── Server ├── Server.cpp ├── Server.h ├── Server.vcxproj ├── Server.vcxproj.filters └── main.cpp ├── ServerTester ├── EventManager.cpp ├── EventManager.h ├── ServerTester.vcxproj ├── ServerTester.vcxproj.filters └── main.cpp ├── vcpkg-configuration.json └── vcpkg.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | 6 | [*.{c++,cc,cl,cpp,cppm,cu,cuh,cxx,glsl,h,h++,hh,hpp,hxx,inl,ipp,ixx,shdbg,tlh,tli}] 7 | 8 | # Visual C++ Code Style settings 9 | 10 | cpp_generate_documentation_comments = xml 11 | 12 | # Visual C++ Formatting settings 13 | 14 | cpp_indent_braces = false 15 | cpp_indent_multi_line_relative_to = innermost_parenthesis 16 | cpp_indent_within_parentheses = align_to_parenthesis 17 | cpp_indent_preserve_within_parentheses = true 18 | cpp_indent_case_contents = true 19 | cpp_indent_case_labels = true 20 | cpp_indent_case_contents_when_block = false 21 | cpp_indent_lambda_braces_when_parameter = true 22 | cpp_indent_goto_labels = leftmost_column 23 | cpp_indent_preprocessor = leftmost_column 24 | cpp_indent_access_specifiers = false 25 | cpp_indent_namespace_contents = false 26 | cpp_indent_preserve_comments = false 27 | cpp_new_line_before_open_brace_namespace = new_line 28 | cpp_new_line_before_open_brace_type = new_line 29 | cpp_new_line_before_open_brace_function = new_line 30 | cpp_new_line_before_open_brace_block = new_line 31 | cpp_new_line_before_open_brace_lambda = new_line 32 | cpp_new_line_scope_braces_on_separate_lines = true 33 | cpp_new_line_close_brace_same_line_empty_type = false 34 | cpp_new_line_close_brace_same_line_empty_function = false 35 | cpp_new_line_before_catch = true 36 | cpp_new_line_before_else = true 37 | cpp_new_line_before_while_in_do_while = false 38 | cpp_space_before_function_open_parenthesis = remove 39 | cpp_space_within_parameter_list_parentheses = false 40 | cpp_space_between_empty_parameter_list_parentheses = false 41 | cpp_space_after_keywords_in_control_flow_statements = true 42 | cpp_space_within_control_flow_statement_parentheses = false 43 | cpp_space_before_lambda_open_parenthesis = false 44 | cpp_space_within_cast_parentheses = false 45 | cpp_space_after_cast_close_parenthesis = false 46 | cpp_space_within_expression_parentheses = false 47 | cpp_space_before_block_open_brace = true 48 | cpp_space_between_empty_braces = false 49 | cpp_space_before_initializer_list_open_brace = false 50 | cpp_space_within_initializer_list_braces = false 51 | cpp_space_preserve_in_initializer_list = true 52 | cpp_space_before_open_square_bracket = false 53 | cpp_space_within_square_brackets = false 54 | cpp_space_before_empty_square_brackets = false 55 | cpp_space_between_empty_square_brackets = false 56 | cpp_space_group_square_brackets = true 57 | cpp_space_within_lambda_brackets = false 58 | cpp_space_between_empty_lambda_brackets = false 59 | cpp_space_before_comma = false 60 | cpp_space_after_comma = true 61 | cpp_space_remove_around_member_operators = true 62 | cpp_space_before_inheritance_colon = false 63 | cpp_space_before_constructor_colon = true 64 | cpp_space_remove_before_semicolon = true 65 | cpp_space_after_semicolon = true 66 | cpp_space_remove_around_unary_operator = true 67 | cpp_space_around_binary_operator = insert 68 | cpp_space_around_assignment_operator = insert 69 | cpp_space_pointer_reference_alignment = left 70 | cpp_space_around_ternary_operator = insert 71 | cpp_use_unreal_engine_macro_formatting = false 72 | cpp_wrap_preserve_blocks = never 73 | 74 | # Visual C++ Inlcude Cleanup settings 75 | 76 | cpp_include_cleanup_add_missing_error_tag_type = suggestion 77 | cpp_include_cleanup_remove_unused_error_tag_type = dimmed 78 | cpp_include_cleanup_optimize_unused_error_tag_type = suggestion 79 | cpp_include_cleanup_sort_after_edits = false 80 | cpp_sort_includes_error_tag_type = none 81 | cpp_sort_includes_priority_case_sensitive = false 82 | cpp_sort_includes_priority_style = quoted 83 | cpp_includes_style = default 84 | cpp_includes_use_forward_slash = false 85 | -------------------------------------------------------------------------------- /Archive/deprecated_BlockMap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //char blockModel[][4][4] = 4 | //{ 5 | // /* ㅁ 6 | // ㅁㅁㅁ */ 7 | // { 8 | // {0,0,0,0}, 9 | // {0,1,0,0}, 10 | // {0,1,1,1}, 11 | // {0,0,0,0} 12 | // }, 13 | // { 14 | // {0,0,1,0}, 15 | // {0,0,1,0}, 16 | // {0,1,1,0}, 17 | // {0,0,0,0} 18 | // }, 19 | // { 20 | // {0,0,0,0}, 21 | // {1,1,1,0}, 22 | // {0,0,1,0}, 23 | // {0,0,0,0} 24 | // }, 25 | // { 26 | // {0,0,0,0}, 27 | // {0,1,1,0}, 28 | // {0,1,0,0}, 29 | // {0,1,0,0} 30 | // }, 31 | // 32 | // /* ㅁ 33 | // ㅁㅁㅁ */ 34 | // { 35 | // {0,0,0,0}, 36 | // {0,0,1,0}, 37 | // {1,1,1,0}, 38 | // {0,0,0,0} 39 | // }, 40 | // { 41 | // {0,0,0,0}, 42 | // {0,1,1,0}, 43 | // {0,0,1,0}, 44 | // {0,0,1,0} 45 | // }, 46 | // { 47 | // {0,0,0,0}, 48 | // {0,1,1,1}, 49 | // {0,1,0,0}, 50 | // {0,0,0,0} 51 | // }, 52 | // { 53 | // {0,1,0,0}, 54 | // {0,1,0,0}, 55 | // {0,1,1,0}, 56 | // {0,0,0,0} 57 | // }, 58 | // 59 | // /* ㅁ 60 | // ㅁㅁㅁ */ 61 | // { 62 | // {0,0,0,0}, 63 | // {0,1,0,0}, 64 | // {1,1,1,0}, 65 | // {0,0,0,0} 66 | // }, 67 | // { 68 | // {0,1,0,0}, 69 | // {0,1,1,0}, 70 | // {0,1,0,0}, 71 | // {0,0,0,0} 72 | // }, 73 | // { 74 | // {0,0,0,0}, 75 | // {0,1,1,1}, 76 | // {0,0,1,0}, 77 | // {0,0,0,0} 78 | // }, 79 | // { 80 | // {0,0,0,0}, 81 | // {0,0,1,0}, 82 | // {0,1,1,0}, 83 | // {0,0,1,0} 84 | // }, 85 | // 86 | // /* ㅁ 87 | // ㅁ 88 | // ㅁ 89 | // ㅁ */ 90 | // { 91 | // {0,1,0,0}, 92 | // {0,1,0,0}, 93 | // {0,1,0,0}, 94 | // {0,1,0,0} 95 | // }, 96 | // { 97 | // {0,0,0,0}, 98 | // {1,1,1,1}, 99 | // {0,0,0,0}, 100 | // {0,0,0,0} 101 | // }, 102 | // { 103 | // {0,0,1,0}, 104 | // {0,0,1,0}, 105 | // {0,0,1,0}, 106 | // {0,0,1,0} 107 | // }, 108 | // { 109 | // {0,0,0,0}, 110 | // {0,0,0,0}, 111 | // {1,1,1,1}, 112 | // {0,0,0,0} 113 | // }, 114 | // 115 | // /* ㅁㅁ 116 | // ㅁㅁ */ 117 | // { 118 | // {0,0,0,0}, 119 | // {1,1,0,0}, 120 | // {1,1,0,0}, 121 | // {0,0,0,0} 122 | // }, 123 | // { 124 | // {0,0,0,0}, 125 | // {1,1,0,0}, 126 | // {1,1,0,0}, 127 | // {0,0,0,0} 128 | // }, 129 | // { 130 | // {0,0,0,0}, 131 | // {1,1,0,0}, 132 | // {1,1,0,0}, 133 | // {0,0,0,0} 134 | // }, 135 | // { 136 | // {0,0,0,0}, 137 | // {1,1,0,0}, 138 | // {1,1,0,0}, 139 | // {0,0,0,0} 140 | // }, 141 | // 142 | // /* ㅁㅁ 143 | // ㅁㅁ */ 144 | // { 145 | // {0,0,0,0}, 146 | // {0,1,1,0}, 147 | // {1,1,0,0}, 148 | // {0,0,0,0} 149 | // }, 150 | // { 151 | // {1,0,0,0}, 152 | // {1,1,0,0}, 153 | // {0,1,0,0}, 154 | // {0,0,0,0} 155 | // }, 156 | // { 157 | // {0,0,0,0}, 158 | // {0,1,1,0}, 159 | // {1,1,0,0}, 160 | // {0,0,0,0} 161 | // }, 162 | // { 163 | // {1,0,0,0}, 164 | // {1,1,0,0}, 165 | // {0,1,0,0}, 166 | // {0,0,0,0} 167 | // }, 168 | // 169 | // /* ㅁㅁ 170 | // ㅁㅁ */ 171 | // { 172 | // {0,0,0,0}, 173 | // {1,1,0,0}, 174 | // {0,1,1,0}, 175 | // {0,0,0,0} 176 | // }, 177 | // { 178 | // {0,1,0,0}, 179 | // {1,1,0,0}, 180 | // {1,0,0,0}, 181 | // {0,0,0,0} 182 | // }, 183 | // { 184 | // {0,0,0,0}, 185 | // {1,1,0,0}, 186 | // {0,1,1,0}, 187 | // {0,0,0,0} 188 | // }, 189 | // { 190 | // {0,1,0,0}, 191 | // {1,1,0,0}, 192 | // {1,0,0,0}, 193 | // {0,0,0,0} 194 | // } 195 | //}; 196 | -------------------------------------------------------------------------------- /Archive/old_ClientSession.cpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ClientSession.h" 4 | 5 | // 생성자: 내부 소켓 및 실행 스트랜드를 io_context를 통해 초기화 6 | 7 | ClientSession::ClientSession(boost::asio::io_context & ioContext) 8 | : socket_(ioContext), 9 | strand_(boost::asio::make_strand(ioContext)) 10 | {} 11 | 12 | // 세션 시작: 비동기 읽기 작업을 시작합니다. 13 | 14 | void ClientSession::start() { 15 | doRead(); 16 | } 17 | 18 | // 외부에서 전달받은 메시지를 클라이언트에 전송하기 위한 인터페이스 19 | 20 | void ClientSession::deliver(const std::string & message) { 21 | // writeQueue_에 메시지를 추가하고, 큐가 비어있으면 비동기 쓰기 시작 22 | auto self(shared_from_this()); 23 | boost::asio::post(strand_, 24 | [this, self, message]() { 25 | bool writeInProgress = !writeQueue_.empty(); 26 | writeQueue_.push_back(message); 27 | if (!writeInProgress) { 28 | doWrite(); 29 | } 30 | } 31 | ); 32 | } 33 | 34 | // 외부에서 메시지 처리 콜백을 설정하여, 읽은 데이터에 대해 별도의 비즈니스 로직을 처리할 수 있게 합니다. 35 | 36 | void ClientSession::setMessageHandler(MessageHandler handler) { 37 | messageHandler_ = handler; 38 | } 39 | 40 | // 소켓에 대한 참조를 반환 (연결 설정 등 외부에서 필요시 사용) 41 | 42 | boost::asio::ip::tcp::socket & ClientSession::socket() { 43 | return socket_; 44 | } 45 | 46 | // 현재 참가중인 방의 이름을 반환합니다. 47 | 48 | const std::string & ClientSession::getRoomName() const { 49 | return roomName_; 50 | } 51 | 52 | // 세션이 참가하는 방 이름을 설정합니다. 53 | 54 | void ClientSession::setRoomName(const std::string & roomName) { 55 | roomName_ = roomName; 56 | } 57 | 58 | // 비동기 읽기 작업: 소켓에서 데이터를 읽고, 완성된 메시지가 있으면 콜백을 호출합니다. 59 | 60 | void ClientSession::doRead() { 61 | auto self(shared_from_this()); 62 | boost::asio::async_read_until(socket_, readBuffer_, '\n', 63 | boost::asio::bind_executor(strand_, 64 | [this, self](const boost::system::error_code& ec, std::size_t bytesTransferred) { 65 | if (!ec) { 66 | std::istream is(&readBuffer_); 67 | std::string line; 68 | std::getline(is, line); 69 | if (messageHandler_) { 70 | messageHandler_(line, self); 71 | } 72 | doRead(); 73 | } 74 | // 에러 발생 시 세션 종료 처리는 외부에서 관리하도록 할 수 있음 75 | } 76 | ) 77 | ); 78 | } 79 | 80 | // 비동기 쓰기 작업: writeQueue_에 쌓인 메시지를 순차적으로 전송합니다. 81 | 82 | void ClientSession::doWrite() { 83 | auto self(shared_from_this()); 84 | boost::asio::async_write(socket_, 85 | boost::asio::buffer(writeQueue_.front()), 86 | boost::asio::bind_executor(strand_, 87 | [this, self](const boost::system::error_code& ec, std::size_t /*bytesTransferred*/) { 88 | if (!ec) { 89 | writeQueue_.pop_front(); 90 | if (!writeQueue_.empty()) { 91 | doWrite(); 92 | } 93 | } 94 | // 에러 발생 시 세션 종료 처리는 외부에서 관리하도록 할 수 있음 95 | } 96 | ) 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /Archive/old_ClientSession.h: -------------------------------------------------------------------------------- 1 | // ClientSession.h 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // ClientSession은 클라이언트와의 비동기 통신을 관리하는 역할에 집중합니다. 11 | // 메시지 처리 로직이나 방 관리 등 비즈니스 로직은 외부(예: 서버, 게임룸 클래스)에서 콜백을 통해 처리하도록 합니다. 12 | class ClientSession: public std::enable_shared_from_this { 13 | public: 14 | // 메시지 처리 콜백: 완성된 메시지와 현재 세션(shared_ptr)을 인자로 전달 15 | using MessageHandler = std::function)>; 16 | 17 | // 생성자: 내부 소켓 및 실행 스트랜드를 io_context를 통해 초기화 18 | explicit ClientSession(boost::asio::io_context& ioContext); 19 | 20 | ~ClientSession() = default; 21 | 22 | // 세션 시작: 비동기 읽기 작업을 시작합니다. 23 | void start(); 24 | 25 | // 외부에서 전달받은 메시지를 클라이언트에 전송하기 위한 인터페이스 26 | void deliver(const std::string& message); 27 | 28 | // 외부에서 메시지 처리 콜백을 설정하여, 읽은 데이터에 대해 별도의 비즈니스 로직을 처리할 수 있게 합니다. 29 | void setMessageHandler(MessageHandler handler); 30 | 31 | // 소켓에 대한 참조를 반환 (연결 설정 등 외부에서 필요시 사용) 32 | boost::asio::ip::tcp::socket& socket(); 33 | 34 | // 현재 참가중인 방의 이름을 반환합니다. 35 | const std::string& getRoomName() const; 36 | 37 | // 세션이 참가하는 방 이름을 설정합니다. 38 | void setRoomName(const std::string& roomName); 39 | 40 | private: 41 | // 비동기 읽기 작업: 소켓에서 데이터를 읽고, 완성된 메시지가 있으면 콜백을 호출합니다. 42 | void doRead(); 43 | 44 | // 비동기 쓰기 작업: writeQueue_에 쌓인 메시지를 순차적으로 전송합니다. 45 | void doWrite(); 46 | 47 | // 소켓과 관련된 비동기 작업을 순차적으로 실행하기 위한 실행 스트랜드 48 | boost::asio::ip::tcp::socket socket_; 49 | boost::asio::strand strand_; 50 | 51 | // 입력 버퍼: 클라이언트로부터 읽은 데이터 보관 52 | boost::asio::streambuf readBuffer_; 53 | // 출력 큐: 클라이언트로 보낼 메시지 저장 54 | std::deque writeQueue_; 55 | 56 | // 현재 세션이 속한 방의 이름 (비즈니스 로직에서 필요시만 사용) 57 | std::string roomName_; 58 | 59 | // 읽은 메시지에 대해 호출할 콜백 함수 (비즈니스 로직 분리) 60 | MessageHandler messageHandler_; 61 | }; 62 | -------------------------------------------------------------------------------- /Archive/old_Common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // 메시지 구분자 (양쪽 모두 동일하게 사용) 8 | constexpr char MESSAGE_DELIMITER = '\n'; 9 | 10 | // 서버와 클라이언트 모두가 사용할 수 있는 공통 콜백 타입들 11 | 12 | // 서버: 메시지와 해당 메시지를 보낸 소켓 포인터를 전달 13 | using ServerMessageReceivedCallback = std::function; 14 | 15 | // 클라이언트: 메시지(서버에서 전달된 내용)만 전달 16 | using ClientMessageReceivedCallback = std::function; 17 | 18 | // 메시지 끝에 구분자를 붙이는 유틸리티 함수 (선택 사항) 19 | inline std::string addDelimiter(const std::string& message) { 20 | return message + MESSAGE_DELIMITER; 21 | } -------------------------------------------------------------------------------- /Archive/old_GameRoom.cpp: -------------------------------------------------------------------------------- 1 | #include "GameRoom.h" 2 | 3 | GameRoom::GameRoom(const std::string &name) 4 | : roomName(name) {} 5 | 6 | // 클라이언트를 방에 추가합니다. 7 | void GameRoom::AddClient(std::shared_ptr session) { 8 | std::lock_guard lock(roomMutex); 9 | participants.push_back(session); 10 | } 11 | 12 | // 클라이언트를 방에서 제거합니다. 13 | void GameRoom::RemoveClient(std::shared_ptr session) { 14 | std::lock_guard lock(roomMutex); 15 | auto it = std::remove(participants.begin(), participants.end(), session); 16 | if (it != participants.end()) { 17 | participants.erase(it, participants.end()); 18 | } 19 | } 20 | 21 | // 방의 모든 참가자에게 메시지를 전송합니다. 22 | void GameRoom::Broadcast(const std::string &message) { 23 | std::lock_guard lock(roomMutex); 24 | for (auto &session : participants) { 25 | session->deliver(message); 26 | } 27 | } 28 | 29 | const std::string & GameRoom::GetName() const { 30 | return roomName; 31 | } 32 | -------------------------------------------------------------------------------- /Archive/old_GameRoom.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "ClientSession.h" 9 | 10 | class GameRoom { 11 | public: 12 | explicit GameRoom(const std::string &name); 13 | 14 | // 클라이언트 추가/제거 (Server에서 호출) 15 | void AddClient(std::shared_ptr session); 16 | void RemoveClient(std::shared_ptr session); 17 | 18 | // 방에 속한 모든 참가자에게 메시지 브로드캐스트 19 | void Broadcast(const std::string &message); 20 | 21 | // 방 이름 반환 22 | const std::string & GetName() const; 23 | 24 | private: 25 | std::string roomName; 26 | std::vector> participants; 27 | std::mutex roomMutex; 28 | }; 29 | -------------------------------------------------------------------------------- /Archive/old_Server.cpp: -------------------------------------------------------------------------------- 1 | #include "Server.h" 2 | #include "ClientSession.h" 3 | #include "GameRoom.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using boost::asio::ip::tcp; 16 | 17 | // 18 | // 생성자, 소멸자 및 기본 초기화 19 | // 20 | Server::Server() 21 | : acceptor(nullptr) 22 | {} 23 | 24 | Server::~Server() { 25 | Stop(); 26 | if (ioThread.joinable()) { 27 | ioThread.join(); 28 | } 29 | } 30 | 31 | bool Server::InitializeAsServer(unsigned short port) { 32 | try { 33 | tcp::endpoint endpoint(tcp::v4(), port); 34 | acceptor = std::make_unique(ioContext, endpoint); 35 | 36 | StartAccept(); 37 | 38 | // io_context를 별도 스레드에서 실행 39 | ioThread = std::thread([this]() { 40 | ioContext.run(); 41 | }); 42 | } catch (std::exception& e) { 43 | std::cerr << "InitializeAsServer 예외: " << e.what() << std::endl; 44 | return false; 45 | } 46 | return true; 47 | } 48 | 49 | void Server::Stop() { 50 | ioContext.stop(); 51 | if (acceptor) { 52 | boost::system::error_code ec; 53 | acceptor->close(ec); 54 | } 55 | CloseAllSessions(); 56 | } 57 | 58 | void Server::SetMessageReceivedCallback(MessageReceivedCallback callback) { 59 | messageReceivedCallback = callback; 60 | } 61 | 62 | // 63 | // 전체 클라이언트에게 메시지 브로드캐스트 (각 ClientSession::deliver() 호출) 64 | // 65 | void Server::Broadcast(const std::string& message) { 66 | std::lock_guard lock(sessionsMutex); 67 | for (auto& session : clientSessions) { 68 | session->deliver(message); 69 | } 70 | } 71 | 72 | // 73 | // 특정 소켓에 메시지 전송 (비동기 쓰기) 74 | // (ClientSession의 deliver()를 사용하는 것이 권장되지만, 기존 인터페이스를 위해 구현) 75 | // 76 | void Server::SendToSocket(tcp::socket* socketPtr, const std::string& message) { 77 | if (socketPtr && socketPtr->is_open()) { 78 | auto writeBuffer = std::make_shared(message); 79 | boost::asio::async_write(*socketPtr, 80 | boost::asio::buffer(*writeBuffer), 81 | [writeBuffer](const boost::system::error_code& ec, std::size_t /*bytesTransferred*/) { 82 | if (ec) { 83 | std::cerr << "SendToSocket async_write 오류: " << ec.message() << std::endl; 84 | } 85 | } 86 | ); 87 | } 88 | } 89 | 90 | // 91 | // 방 관리 관련 함수 92 | // 93 | std::shared_ptr Server::GetRoom(const std::string& roomName) { 94 | std::lock_guard lock(roomsMutex); 95 | auto it = rooms.find(roomName); 96 | if (it != rooms.end()) { 97 | return it->second; 98 | } 99 | return nullptr; 100 | } 101 | 102 | void Server::JoinRoom(std::shared_ptr session, const std::string& roomName) { 103 | std::shared_ptr room; 104 | { 105 | std::lock_guard lock(roomsMutex); 106 | auto it = rooms.find(roomName); 107 | if (it == rooms.end()) { 108 | room = std::make_shared(roomName); 109 | rooms[roomName] = room; 110 | } else { 111 | room = it->second; 112 | } 113 | } 114 | if (room) { 115 | // GameRoom은 세션을 추가하는 AddClient()를 제공한다고 가정 116 | room->AddClient(session); 117 | session->setRoomName(roomName); 118 | } 119 | } 120 | 121 | void Server::LeaveRoom(std::shared_ptr session) { 122 | std::lock_guard lock(roomsMutex); 123 | for (auto& pair : rooms) { 124 | // GameRoom은 세션 제거를 위한 RemoveClient()를 제공한다고 가정 125 | pair.second->RemoveClient(session); 126 | } 127 | session->setRoomName(""); 128 | } 129 | 130 | void Server::ListRooms(std::shared_ptr session) { 131 | std::string roomList = "Rooms: "; 132 | { 133 | std::lock_guard lock(roomsMutex); 134 | for (auto& pair : rooms) { 135 | roomList += pair.first + " "; 136 | } 137 | } 138 | session->deliver(roomList); 139 | } 140 | 141 | // 142 | // 연결 수락 관련 함수 143 | // 144 | void Server::StartAccept() { 145 | // 새 ClientSession 생성 (내부에 소켓 생성) 146 | auto session = std::make_shared(ioContext); 147 | std::cout << "New Client try to connection" << std::endl; 148 | acceptor->async_accept(session->socket(), 149 | [this, session](const boost::system::error_code& error) { 150 | HandleAccept(session, error); 151 | } 152 | ); 153 | } 154 | 155 | void Server::HandleAccept(std::shared_ptr session, const boost::system::error_code& error) { 156 | if (!error) { 157 | std::cout << "New Client is connected" << std::endl; 158 | { 159 | std::lock_guard lock(sessionsMutex); 160 | clientSessions.push_back(session); 161 | std::cout << "Total Clients: " << clientSessions.size() << std::endl; 162 | 163 | } 164 | // ClientSession의 메시지 처리 콜백을 서버의 messageReceivedCallback과 연결 165 | session->setMessageHandler([this](const std::string& message, std::shared_ptr session) { 166 | if (messageReceivedCallback) { 167 | messageReceivedCallback(message, &session->socket()); 168 | } 169 | }); 170 | // 세션 내에서 비동기 읽기를 시작 (내부적으로 doRead() 호출) 171 | session->start(); 172 | } else { 173 | std::cerr << "연결 수락 오류: " << error.message() << std::endl; 174 | } 175 | 176 | std::cout << "Wait Next client" << std::endl; 177 | StartAccept(); 178 | } 179 | 180 | // 181 | // 세션 제거 및 소켓 종료 182 | // 183 | void Server::RemoveSession(std::shared_ptr session) { 184 | { 185 | std::lock_guard lock(sessionsMutex); 186 | auto it = std::remove(clientSessions.begin(), clientSessions.end(), session); 187 | clientSessions.erase(it, clientSessions.end()); 188 | } 189 | boost::system::error_code ec; 190 | session->socket().shutdown(tcp::socket::shutdown_both, ec); 191 | session->socket().close(ec); 192 | } 193 | 194 | // 195 | // 모든 클라이언트 세션 종료 196 | // 197 | void Server::CloseAllSessions() { 198 | std::lock_guard lock(sessionsMutex); 199 | for (auto& session : clientSessions) { 200 | boost::system::error_code ec; 201 | session->socket().shutdown(tcp::socket::shutdown_both, ec); 202 | session->socket().close(ec); 203 | 204 | std::cout << "Session is closed" << std::endl; 205 | } 206 | clientSessions.clear(); 207 | } 208 | -------------------------------------------------------------------------------- /Archive/old_Server.h: -------------------------------------------------------------------------------- 1 | // Server.h 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // 전방 선언 (구현은 별도의 헤더(ClientSession.h, GameRoom.h)에서 관리) 14 | class ClientSession; 15 | class GameRoom; 16 | 17 | // 서버에서 메시지 수신 시 호출할 콜백 함수 타입 18 | // (전달인자로 메시지와 해당 메시지를 보낸 소켓 포인터를 전달) 19 | using MessageReceivedCallback = std::function; 20 | 21 | class Server { 22 | public: 23 | Server(); 24 | ~Server(); 25 | 26 | // 지정된 포트에서 서버 초기화 (포트 바인딩 및 수락 시작) 27 | bool InitializeAsServer(unsigned short port); 28 | 29 | // 서버 정지 (io_context 종료 및 모든 세션 닫기) 30 | void Stop(); 31 | 32 | // 메시지 수신 콜백 설정 33 | void SetMessageReceivedCallback(MessageReceivedCallback callback); 34 | 35 | // 전체 클라이언트에게 메시지 브로드캐스트 36 | void Broadcast(const std::string& message); 37 | 38 | // 특정 소켓에 메시지 전송 39 | void SendToSocket(boost::asio::ip::tcp::socket* socketPtr, const std::string& message); 40 | 41 | // [방 관리 기능] 42 | // 지정된 이름의 방을 가져옴 (없으면 nullptr 반환) 43 | std::shared_ptr GetRoom(const std::string& roomName); 44 | // 클라이언트 세션을 해당 방에 참가시킴 (방이 없으면 새로 생성) 45 | void JoinRoom(std::shared_ptr session, const std::string& roomName); 46 | // 클라이언트 세션이 참가 중인 방에서 퇴장 47 | void LeaveRoom(std::shared_ptr session); 48 | // 현재 서버에 존재하는 방 목록을 클라이언트에 전송 49 | void ListRooms(std::shared_ptr session); 50 | 51 | private: 52 | // 비동기 IO를 위한 io_context 객체 53 | boost::asio::io_context ioContext; 54 | // 클라이언트 연결 수락용 acceptor 55 | std::unique_ptr acceptor; 56 | // 현재 연결되어 있는 클라이언트 세션들 57 | std::vector> clientSessions; 58 | // 클라이언트 세션 목록 접근 동기화를 위한 mutex 59 | std::mutex sessionsMutex; 60 | // io_context 실행을 위한 스레드 61 | std::thread ioThread; 62 | // 메시지 수신 시 호출할 콜백 함수 63 | MessageReceivedCallback messageReceivedCallback; 64 | 65 | // 방 관리를 위한 컨테이너 (방 이름 -> GameRoom) 66 | std::map> rooms; 67 | std::mutex roomsMutex; 68 | 69 | // 연결 수락 및 데이터 처리를 위한 내부 함수들 70 | void StartAccept(); 71 | void HandleAccept(std::shared_ptr session, const boost::system::error_code& error); 72 | 73 | // 클라이언트 세션에서 데이터 읽기를 시작 74 | void StartRead(std::shared_ptr session); 75 | // 클라이언트로부터 메시지를 읽은 후 처리 76 | void HandleRead(std::shared_ptr session, const boost::system::error_code& error, std::size_t bytesTransferred); 77 | 78 | // 지정된 클라이언트 세션에 메시지 쓰기를 시작 79 | void StartWrite(std::shared_ptr session, const std::string& message); 80 | // 쓰기 완료 후 후속 처리 81 | void HandleWrite(std::shared_ptr session, const boost::system::error_code& error, std::size_t bytesTransferred); 82 | 83 | // 세션 제거 및 소켓 종료 84 | void RemoveSession(std::shared_ptr session); 85 | // 모든 클라이언트 세션 종료 86 | void CloseAllSessions(); 87 | }; 88 | 89 | -------------------------------------------------------------------------------- /Client/Block.cpp: -------------------------------------------------------------------------------- 1 | #include "Block.h" 2 | #include 3 | 4 | Block::Block(int posX, int posY, ConsoleColor bTexture) 5 | : x(posX) 6 | , y(posY) 7 | , texture(bTexture) 8 | { 9 | } 10 | 11 | int SetRandNum(int size) { 12 | 13 | std::random_device rd; 14 | std::mt19937 gen(rd()); 15 | 16 | std::uniform_int_distribution<> distr(0, size - 1); 17 | 18 | return distr(gen); 19 | } 20 | 21 | void Block::Initalize() { 22 | type = static_cast(SetRandNum(SHAPE_COUNT)); 23 | rotation = static_cast(SetRandNum(ROTATION_COUNT)); 24 | currentShape = &modelPointers[type][0]; 25 | } 26 | 27 | void Block::CopyFrom(const Block& other) 28 | { 29 | type = other.type; 30 | rotation = other.rotation; 31 | prevRotate= other.prevRotate; 32 | currentShape = other.currentShape; 33 | 34 | prevX = other.prevX; 35 | prevY = other.prevY; 36 | x = other.x; 37 | y = other.y; 38 | 39 | this->texture = other.texture; 40 | } 41 | 42 | void Block::Update() 43 | { 44 | y++; 45 | } 46 | 47 | void Block::MoveLeft() 48 | { 49 | x--; 50 | } 51 | void Block::MoveRight() 52 | { 53 | x++; 54 | } 55 | void Block::MoveDown() 56 | { 57 | y ++; 58 | } 59 | 60 | 61 | void Block::Rotate() { 62 | 63 | rotation = static_cast((rotation + 1) % ROTATION_COUNT); 64 | } 65 | 66 | void Block::rollback() 67 | { 68 | rotation = prevRotate; 69 | x = prevX; 70 | y = prevY; 71 | } 72 | 73 | void Block::UpdatePos() { 74 | 75 | prevRotate = rotation; 76 | prevX = x; 77 | prevY = y; 78 | } 79 | 80 | void Block::SetTexture(ConsoleColor tex) { 81 | texture = tex; 82 | } 83 | 84 | 85 | #pragma region Getter 86 | const ModelPointer* Block::GetShapeMatrix() const { 87 | 88 | return currentShape; 89 | } 90 | 91 | int Block::GetMatrixSize() const 92 | { 93 | if(type == SHAPE_O) 94 | return 2; 95 | else if(type == SHAPE_I) 96 | return 4; 97 | return 3; 98 | } 99 | 100 | char Block::GetValue(int i,int j) 101 | { 102 | currentShape[rotation].mat2[i][j]; 103 | if(type == SHAPE_O) { 104 | return (*currentShape[rotation].mat2)[i][j]; // mat2x2 접근 105 | } else if(type == SHAPE_I) { 106 | return (*currentShape[rotation].mat4)[i][j]; // mat4x4 접근 107 | } else { 108 | return (*currentShape[rotation].mat3)[i][j]; // mat3x3 접근 109 | } 110 | } 111 | 112 | void Block::SetX(int row) 113 | { 114 | x = row; 115 | } 116 | 117 | void Block::SetY(int column) 118 | { 119 | y = column; 120 | } 121 | 122 | int Block::GetX() const 123 | { 124 | return x; 125 | } 126 | int Block::GetY() const 127 | { 128 | return y; 129 | } 130 | ConsoleColor Block::GetTexture() const 131 | { 132 | return texture; 133 | } 134 | #pragma endregion 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /Client/Block.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Managers/ConsoleColor.h" 5 | #include "ModelPointer.h" 6 | 7 | class Block { 8 | 9 | ShapeType type; 10 | RotationState rotation; 11 | RotationState prevRotate; 12 | 13 | ConsoleColor texture; 14 | const ModelPointer* currentShape; 15 | 16 | int prevX,prevY; 17 | int x,y; 18 | 19 | 20 | public: 21 | Block(int posX = 0, int posY = 0, ConsoleColor bTexture = ConsoleColor::Black); 22 | ~Block() = default; 23 | 24 | void Initalize(); 25 | void CopyFrom(const Block& other); 26 | void Update(); 27 | 28 | 29 | 30 | void MoveLeft(); 31 | void MoveRight(); 32 | void MoveDown(); 33 | 34 | 35 | void Rotate(); 36 | void rollback(); 37 | 38 | void UpdatePos(); 39 | 40 | 41 | #pragma region Getter 42 | const ModelPointer* GetShapeMatrix() const; 43 | int GetMatrixSize() const; 44 | char GetValue(int i,int j); 45 | 46 | void SetX(int row); 47 | void SetY(int column); 48 | int GetX () const; 49 | int GetY () const; 50 | 51 | ConsoleColor GetTexture() const; 52 | #pragma endregion 53 | 54 | void SetTexture(ConsoleColor tex); 55 | 56 | 57 | }; 58 | 59 | int SetRandNum(); 60 | 61 | -------------------------------------------------------------------------------- /Client/Cell.cpp: -------------------------------------------------------------------------------- 1 | #include "Cell.h" 2 | 3 | const Cell Cell::emptyCell(Cell::Type::Empty, Cell::CHAR_EMPTY, static_cast(ConsoleColor::Black)); 4 | const Cell Cell::borderCell(Cell::Type::Border, Cell::CHAR_BLOCK, static_cast(ConsoleColor::White) << 4); 5 | const Cell Cell::blockCell(Cell::Type::Block, Cell::CHAR_BLOCK); 6 | 7 | Cell::Cell(Type type) 8 | : mType(type) {} 9 | 10 | Cell::Cell(Type type, WCHAR c) 11 | : mType(type) 12 | , mChar(c) {} 13 | 14 | Cell::Cell(Type type, WCHAR c, WORD colorAttr) 15 | : mType(type) 16 | , mChar(c) 17 | , mColorAttr(colorAttr) {} 18 | 19 | Cell::Cell(WORD colorAttr) 20 | : mColorAttr(colorAttr) {} 21 | 22 | CHAR_INFO Cell::ToCharInfo() const 23 | { 24 | CHAR_INFO info = {}; 25 | 26 | info.Char.UnicodeChar = mChar; 27 | info.Attributes = mColorAttr; 28 | 29 | return info; 30 | } 31 | 32 | 33 | Cell::Type Cell::GetType() const 34 | { 35 | return mType; 36 | } 37 | 38 | void Cell::SetType(Type type) 39 | { 40 | mType = type; 41 | } 42 | 43 | WCHAR Cell::GetChar() const 44 | { 45 | return mChar; 46 | } 47 | 48 | void Cell::SetChar(WCHAR c) 49 | { 50 | mChar = c; 51 | } 52 | 53 | WORD Cell::GetAttributes() const 54 | { 55 | return mColorAttr; 56 | } 57 | 58 | void Cell::SetAttributes(WORD ColorAttr) 59 | { 60 | mColorAttr = ColorAttr; 61 | } 62 | 63 | void Cell::SetForegroundColor(ConsoleColor color) 64 | { 65 | mColorAttr &= 0xFFF0; 66 | mColorAttr |= static_cast(color); 67 | } 68 | 69 | void Cell::SetBackgroundColor(ConsoleColor color) 70 | { 71 | mColorAttr &= 0xFF0F; 72 | mColorAttr |= static_cast(color) << 4; 73 | } 74 | -------------------------------------------------------------------------------- /Client/Cell.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Managers/ConsoleColor.h" 5 | 6 | class Cell 7 | { 8 | public: 9 | enum class Type 10 | { 11 | None = -1, 12 | Empty, 13 | Border, 14 | Block 15 | }; 16 | 17 | Cell() = default; 18 | Cell(Type type); 19 | Cell(Type type, WCHAR c); 20 | Cell(Type type, WCHAR c, WORD colorAttr); 21 | Cell(WORD colorAttr); 22 | ~Cell() = default; 23 | 24 | CHAR_INFO ToCharInfo() const; // WinAPI 호출에 필요한 CHAR_INFO로 변환 25 | 26 | Type GetType() const; 27 | void SetType(Type type); 28 | 29 | WCHAR GetChar() const; 30 | void SetChar(WCHAR c); 31 | 32 | WORD GetAttributes() const; 33 | void SetAttributes(WORD ColorAttr); 34 | void SetForegroundColor(ConsoleColor color); 35 | void SetBackgroundColor(ConsoleColor color); 36 | 37 | static const Cell emptyCell; 38 | static const Cell borderCell; 39 | static const Cell blockCell; 40 | 41 | static constexpr WCHAR CHAR_EMPTY = L' '; // 공백 42 | static constexpr WCHAR CHAR_BLOCK = L' '; // 현재는 BLOCK도 공백으로 사용중 43 | 44 | private: 45 | Type mType = Type::Block; //셀 타입(상태) 46 | WCHAR mChar = CHAR_BLOCK; // 표시문자 47 | WORD mColorAttr = static_cast(ConsoleColor::BrightWhite) << 4; // 셀 색상 48 | }; 49 | -------------------------------------------------------------------------------- /Client/Client.cpp: -------------------------------------------------------------------------------- 1 | #include "Client.h" 2 | #include 3 | 4 | Client::Client() 5 | : resolver(nullptr), session(nullptr), isConnected(false) 6 | {} 7 | 8 | Client::~Client() 9 | { 10 | Stop(); 11 | } 12 | 13 | bool Client::InitializeAsClient(const std::string& host, unsigned short port) 14 | { 15 | try { 16 | resolver = std::make_unique(ioContext); 17 | session = std::make_shared(ioContext); 18 | StartConnect(host, port); 19 | 20 | ioThread = std::thread([this]() { ioContext.run(); }); 21 | return true; 22 | } catch (std::exception& e) { 23 | std::cerr << "클라이언트 초기화 실패: " << e.what() << std::endl; 24 | return false; 25 | } 26 | } 27 | 28 | void Client::SetMessageReceivedCallback(MessageReceivedCallback callback) 29 | { 30 | messageReceivedCallback_ = callback; 31 | } 32 | 33 | void Client::Send(const std::string& message) 34 | { 35 | if (!isConnected) { 36 | std::cerr << "서버에 연결되어 있지 않습니다." << std::endl; 37 | return; 38 | } 39 | 40 | auto self = shared_from_this(); 41 | boost::asio::post(session->strand, [this, self, message]() { 42 | bool writeInProgress = !session->writeQueue.empty(); 43 | session->writeQueue.push_back(message); 44 | if (!writeInProgress) { 45 | StartWrite(); 46 | } 47 | }); 48 | } 49 | 50 | void Client::Stop() 51 | { 52 | if (isConnected) { 53 | CloseConnection(); 54 | } 55 | 56 | ioContext.stop(); 57 | if (ioThread.joinable()) { 58 | ioThread.join(); 59 | } 60 | } 61 | 62 | void Client::StartConnect(const std::string& host, unsigned short port) 63 | { 64 | auto endpoints = resolver->resolve(host, std::to_string(port)); 65 | boost::asio::async_connect(*session->socket, endpoints, 66 | [this, self = shared_from_this()](const boost::system::error_code& error, const boost::asio::ip::tcp::endpoint&) { 67 | HandleConnect(error); 68 | }); 69 | } 70 | 71 | void Client::HandleConnect(const boost::system::error_code& error) 72 | { 73 | if (!error) { 74 | isConnected = true; 75 | StartRead(); 76 | std::cout << "서버에 성공적으로 연결되었습니다." << std::endl; 77 | } else { 78 | std::cerr << "서버 연결 실패: " << error.message() << std::endl; 79 | } 80 | } 81 | 82 | void Client::StartRead() 83 | { 84 | boost::asio::async_read_until(*session->socket, session->readBuffer, '\n', 85 | [this, self = shared_from_this()](const boost::system::error_code& error, std::size_t bytesTransferred) { 86 | HandleRead(error, bytesTransferred); 87 | }); 88 | } 89 | 90 | void Client::HandleRead(const boost::system::error_code& error, std::size_t bytesTransferred) 91 | { 92 | if (!error) { 93 | std::istream is(&session->readBuffer); 94 | std::string message; 95 | std::getline(is, message); 96 | 97 | if (messageReceivedCallback_) { 98 | messageReceivedCallback_(message); 99 | } 100 | 101 | StartRead(); 102 | } else { 103 | std::cerr << "읽기 오류: " << error.message() << std::endl; 104 | CloseConnection(); 105 | } 106 | } 107 | 108 | void Client::StartWrite() 109 | { 110 | boost::asio::async_write(*session->socket, 111 | boost::asio::buffer(session->writeQueue.front() + "\n"), 112 | boost::asio::bind_executor(session->strand, 113 | [this, self = shared_from_this()](const boost::system::error_code& error, std::size_t bytesTransferred) { 114 | HandleWrite(error, bytesTransferred); 115 | })); 116 | } 117 | 118 | void Client::HandleWrite(const boost::system::error_code& error, std::size_t bytesTransferred) 119 | { 120 | if (!error) { 121 | session->writeQueue.pop_front(); 122 | if (!session->writeQueue.empty()) { 123 | StartWrite(); 124 | } 125 | } else { 126 | std::cerr << "쓰기 오류: " << error.message() << std::endl; 127 | CloseConnection(); 128 | } 129 | } 130 | 131 | void Client::CloseConnection() 132 | { 133 | if (session->socket->is_open()) { 134 | boost::system::error_code ec; 135 | session->socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); 136 | session->socket->close(ec); 137 | } 138 | isConnected = false; 139 | } 140 | -------------------------------------------------------------------------------- /Client/Client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | class Client: public std::enable_shared_from_this { 13 | public: 14 | using MessageReceivedCallback = std::function; 15 | 16 | Client(); 17 | ~Client(); 18 | 19 | bool InitializeAsClient(const std::string& host, unsigned short port); 20 | void SetMessageReceivedCallback(MessageReceivedCallback callback); 21 | void Send(const std::string& message); 22 | void Stop(); 23 | 24 | private: 25 | struct ServerSession: public std::enable_shared_from_this { 26 | std::shared_ptr socket; 27 | boost::asio::streambuf readBuffer; 28 | std::deque writeQueue; 29 | boost::asio::strand strand; 30 | 31 | ServerSession(boost::asio::io_context& ioContext) 32 | : socket(std::make_shared(ioContext)), 33 | strand(ioContext.get_executor()) {} 34 | }; 35 | 36 | void StartConnect(const std::string& host, unsigned short port); 37 | void HandleConnect(const boost::system::error_code& error); 38 | void StartRead(); 39 | void HandleRead(const boost::system::error_code& error, std::size_t bytesTransferred); 40 | void StartWrite(); 41 | void HandleWrite(const boost::system::error_code& error, std::size_t bytesTransferred); 42 | void CloseConnection(); 43 | 44 | boost::asio::io_context ioContext; 45 | std::unique_ptr resolver; 46 | std::shared_ptr session; 47 | std::thread ioThread; 48 | 49 | std::mutex writeMutex; 50 | MessageReceivedCallback messageReceivedCallback_; 51 | bool isConnected; 52 | }; 53 | -------------------------------------------------------------------------------- /Client/Client.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | {af5a5723-a3d6-4aed-aef0-e3ab7aa21ecf} 18 | 19 | 20 | {5ffccc06-9ad7-4ae7-9956-89fa86f92bf6} 21 | 22 | 23 | {94f7783e-278f-440b-913b-796f1b357ef0} 24 | 25 | 26 | {293a965c-8848-4330-91f0-b652e8e77d56} 27 | 28 | 29 | {e9d5d6c7-4dda-4f62-bf54-d3107057620f} 30 | 31 | 32 | {f276af80-2563-4c03-9b19-100572c72589} 33 | 34 | 35 | {19c599e0-34c9-4ff2-862b-f8f3ec84bbe0} 36 | 37 | 38 | 39 | 40 | Source Files 41 | 42 | 43 | Source Files 44 | 45 | 46 | Source Files 47 | 48 | 49 | Source Files 50 | 51 | 52 | Source Files 53 | 54 | 55 | Source Files 56 | 57 | 58 | Source Files 59 | 60 | 61 | Source Files 62 | 63 | 64 | Source Files 65 | 66 | 67 | Source Files 68 | 69 | 70 | Source Files\Managers 71 | 72 | 73 | Source Files\Managers 74 | 75 | 76 | Source Files\Scenes 77 | 78 | 79 | Source Files\Scenes 80 | 81 | 82 | Source Files\Scenes 83 | 84 | 85 | Source Files 86 | 87 | 88 | Source Files 89 | 90 | 91 | Source Files 92 | 93 | 94 | Source Files 95 | 96 | 97 | 98 | 99 | Header Files 100 | 101 | 102 | Header Files 103 | 104 | 105 | Header Files 106 | 107 | 108 | Header Files 109 | 110 | 111 | Header Files 112 | 113 | 114 | Header Files 115 | 116 | 117 | Header Files 118 | 119 | 120 | Header Files 121 | 122 | 123 | Header Files 124 | 125 | 126 | Header Files 127 | 128 | 129 | Header Files\Scenes 130 | 131 | 132 | Header Files\Scenes 133 | 134 | 135 | Header Files\Managers 136 | 137 | 138 | Header Files\Scenes 139 | 140 | 141 | Header Files\Type 142 | 143 | 144 | Header Files\Type 145 | 146 | 147 | Header Files\Scenes 148 | 149 | 150 | Header Files 151 | 152 | 153 | Header Files 154 | 155 | 156 | Header Files 157 | 158 | 159 | Header Files 160 | 161 | 162 | Header Files 163 | 164 | 165 | Header Files 166 | 167 | 168 | -------------------------------------------------------------------------------- /Client/ConsoleFrame.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ConsoleFrame.h" 3 | #include "ConsoleRenderer.h" 4 | #include "Managers/ConsoleColor.h" 5 | 6 | ConsoleFrame::ConsoleFrame(int x, int y, int width, int height) 7 | : mX(x) 8 | , mY(y) 9 | , mWidth(width) 10 | , mHeight(height) 11 | { 12 | mCells.resize(mWidth * mHeight, Cell::emptyCell); 13 | } 14 | 15 | void ConsoleFrame::Clear() 16 | { 17 | std::fill(mCells.begin(), mCells.end(), Cell::emptyCell); 18 | } 19 | 20 | const Cell& ConsoleFrame::GetCell(int x, int y) const 21 | { 22 | assert(0 <= x && x < mWidth && "x is out of range"); 23 | assert(0 <= y && y < mHeight && "y is out of range"); 24 | 25 | return mCells[y * mWidth + x]; 26 | } 27 | 28 | void ConsoleFrame::SetCell(int x, int y, const Cell& cell) 29 | { 30 | if(x < 0 || mWidth <= x || y < 0 || mHeight <= y) 31 | return; 32 | 33 | mCells[y * mWidth + x] = cell; 34 | } 35 | 36 | void ConsoleFrame::DrawRectangle(int x, int y, int width, int height, const Cell& borderCell) 37 | { 38 | if(x >= mWidth || y >= mHeight) 39 | return; 40 | if(x + width <= 0 || y + height <= 0) 41 | return; 42 | 43 | for (int i = 0; i < width; ++i) 44 | { 45 | SetCell(x + i, y, borderCell); 46 | SetCell(x + i, y + height - 1, borderCell); 47 | } 48 | for (int j = 0; j < height; ++j) 49 | { 50 | SetCell(x, y + j, borderCell); 51 | SetCell(x + width - 1, y + j, borderCell); 52 | } 53 | } 54 | 55 | void ConsoleFrame::FillRectangle(int x, int y, int width, int height, const Cell& fillCell) 56 | { 57 | if(x >= mWidth || y >= mHeight) 58 | return; 59 | if(x + width <= 0 || y + height <= 0) 60 | return; 61 | 62 | for (int j = 0; j < height; ++j) 63 | { 64 | for (int i = 0; i < width; ++i) 65 | { 66 | SetCell(x + i, y + j, fillCell); 67 | } 68 | } 69 | } 70 | 71 | void ConsoleFrame::SetText(int x, int y, const std::wstring & text, 72 | WORD CollorAttr) 73 | { 74 | if(x + text.length() <= 0 || mWidth <= x || y < 0 || mHeight <= y) 75 | return; 76 | 77 | Cell cell(Cell::Type::Empty, Cell::CHAR_EMPTY, CollorAttr); 78 | for (int i = 0; i < text.length(); ++i) 79 | { 80 | if (x + i < mWidth) 81 | { 82 | cell.SetChar(text[i]); 83 | SetCell(x + i, y, cell); 84 | } 85 | } 86 | } 87 | 88 | int ConsoleFrame::GetX() const 89 | { 90 | return mX; 91 | } 92 | 93 | int ConsoleFrame::GetY() const 94 | { 95 | return mY; 96 | } 97 | 98 | int ConsoleFrame::GetWidth() const 99 | { 100 | return mWidth; 101 | } 102 | 103 | int ConsoleFrame::GetHeight() const 104 | { 105 | return mHeight; 106 | } 107 | -------------------------------------------------------------------------------- /Client/ConsoleFrame.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "Cell.h" 5 | 6 | class ConsoleRenderer; 7 | 8 | class ConsoleFrame 9 | { 10 | public: 11 | ConsoleFrame(int x, int y, int width, int height); 12 | ~ConsoleFrame() = default; 13 | 14 | void Clear(); 15 | const Cell& GetCell(int x, int y) const; 16 | void SetCell(int x, int y, const Cell& cell); 17 | 18 | void DrawRectangle(int x, int y, int width, int height, const Cell& borderCell); 19 | void FillRectangle(int x, int y, int width, int height, const Cell& fillCell); 20 | void SetText(int x, int y, const std::wstring& text, 21 | WORD CollorAttr = static_cast(ConsoleColor::BrightWhite)); 22 | 23 | int GetX() const; 24 | int GetY() const; 25 | int GetWidth() const; 26 | int GetHeight() const; 27 | 28 | protected: 29 | int mX; // 콘솔 상에서 Frame의 시작 X 좌표 30 | int mY; // 콘솔 상에서 Frame의 시작 Y 좌표 31 | int mWidth; // Frame 너비 32 | int mHeight; // Frame 높이 33 | std::vector mCells; // Frame 내부의 Cell 배열 34 | }; 35 | -------------------------------------------------------------------------------- /Client/ConsoleRenderer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ConsoleRenderer.h" 3 | #include "ConsoleFrame.h" 4 | #include 5 | 6 | ConsoleRenderer::ConsoleRenderer(int width, int height, float frameRate) 7 | : mWidth(width) 8 | , mHeight(height) 9 | , mFrameRate(frameRate) 10 | , mFrameTime(1.0f / frameRate) 11 | { 12 | mActualWidth = mWidth * CELL_WIDTH; 13 | 14 | mConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE); 15 | COORD bufferSize = {(SHORT)mActualWidth, (SHORT)mHeight}; 16 | SetConsoleScreenBufferSize(mConsoleHandle, bufferSize); 17 | 18 | CONSOLE_CURSOR_INFO cursorInfo = {1, FALSE}; 19 | SetConsoleCursorInfo(mConsoleHandle, &cursorInfo); 20 | 21 | mBuffer = new CHAR_INFO[mActualWidth * mHeight]; 22 | mMainFrame = new ConsoleFrame(0, 0, width, height); 23 | } 24 | 25 | ConsoleRenderer::~ConsoleRenderer() 26 | { 27 | Clear(); 28 | delete mMainFrame; 29 | delete[] mBuffer; 30 | } 31 | 32 | void ConsoleRenderer::Clear() 33 | { 34 | for (auto frame : mFrames) 35 | { 36 | delete frame; 37 | } 38 | mFrames.clear(); 39 | 40 | assert(mMainFrame != nullptr && "MainFrame is nullptr"); 41 | mMainFrame->Clear(); 42 | } 43 | 44 | ConsoleFrame* ConsoleRenderer::AddFrame(int positionX, int positionY, int width, int height) 45 | { 46 | ConsoleFrame* output = new ConsoleFrame(positionX, positionY, width, height); 47 | mFrames.push_back(output); 48 | return output; 49 | } 50 | 51 | ConsoleFrame * ConsoleRenderer::GetMainFrame() 52 | { 53 | return mMainFrame; 54 | } 55 | 56 | float ConsoleRenderer::GetFrameRate() const 57 | { 58 | return mFrameRate; 59 | } 60 | 61 | float ConsoleRenderer::GetFrameTime() const 62 | { 63 | return mFrameTime; 64 | } 65 | 66 | void ConsoleRenderer::RemoveFrame(ConsoleFrame* frame) 67 | { 68 | if (frame == nullptr) 69 | return; 70 | 71 | auto it = std::find(mFrames.begin(), mFrames.end(), frame); 72 | 73 | if (it != mFrames.end()) 74 | { 75 | delete *it; 76 | mFrames.erase(it); 77 | } 78 | } 79 | 80 | void ConsoleRenderer::SetBuffer(int row, int column, CHAR_INFO charInfo) 81 | { 82 | assert(0 <= row && row < mHeight && "row is out of range"); 83 | assert(0 <= column && column < mActualWidth && "column is out of range"); 84 | 85 | mBuffer[row * mActualWidth + column] = charInfo; 86 | } 87 | 88 | void ConsoleRenderer::RenderFrame(ConsoleFrame* frame) 89 | { 90 | for (int row = 0; row < frame->GetHeight(); ++row) 91 | { 92 | for (int column = 0; column < frame->GetWidth(); ++column) 93 | { 94 | int globalRow = frame->GetY() + row; 95 | int globalColumn = (frame->GetX() + column) * CELL_WIDTH; 96 | 97 | const Cell& cell = frame->GetCell(column, row); 98 | 99 | if (LEFT_SPACE) 100 | { 101 | SetBuffer(globalRow, globalColumn, {L' ', cell.GetAttributes()}); 102 | ++globalColumn; 103 | } 104 | 105 | SetBuffer(globalRow, globalColumn, cell.ToCharInfo()); 106 | ++globalColumn; 107 | 108 | if (RIGHT_SPACE) 109 | { 110 | SetBuffer(globalRow, globalColumn, {L' ', cell.GetAttributes()}); 111 | } 112 | } 113 | } 114 | } 115 | 116 | void ConsoleRenderer::Render() 117 | { 118 | assert(mMainFrame != nullptr && "MainFrame is nullptr"); 119 | RenderFrame(mMainFrame); 120 | 121 | for (const auto& frame : mFrames) 122 | { 123 | RenderFrame(frame); 124 | } 125 | 126 | SMALL_RECT writeRegion = {0, 0, (SHORT)(mActualWidth - 1), (SHORT)(mHeight - 1)}; 127 | COORD bufferSize = {(SHORT)mActualWidth, (SHORT)mHeight}; 128 | COORD bufferCoord = {0, 0}; 129 | 130 | WriteConsoleOutputW(mConsoleHandle, mBuffer, bufferSize, bufferCoord, &writeRegion); 131 | } -------------------------------------------------------------------------------- /Client/ConsoleRenderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | class ConsoleFrame; 6 | 7 | class ConsoleRenderer 8 | { 9 | public: 10 | ConsoleRenderer(int width, int height, float frameRate); 11 | ~ConsoleRenderer(); 12 | 13 | void Clear(); 14 | 15 | // 나중에 추가된 Frame이 위쪽에 위치합니다. 16 | // Frame끼리 겹치는 부분이 있는 경우 나중에 추가된 Frame이 우선권을 가집니다. 17 | ConsoleFrame* AddFrame(int positionX, int positionY, int width, int height); 18 | void RemoveFrame(ConsoleFrame* frame); 19 | 20 | ConsoleFrame* GetMainFrame(); 21 | 22 | float GetFrameRate() const; 23 | float GetFrameTime() const; 24 | 25 | void SetBuffer(int row, int column, CHAR_INFO charInfo); 26 | void Render(); 27 | 28 | //출력되는 셀에 좌우 공백 여부 29 | static constexpr bool LEFT_SPACE = false; 30 | static constexpr bool RIGHT_SPACE = true; 31 | static constexpr int CELL_WIDTH = 1 + (LEFT_SPACE ? 1 : 0) + (RIGHT_SPACE ? 1 : 0); 32 | 33 | 34 | POINT GetWindowPosition() const { 35 | return mWindowPosition; 36 | } 37 | RECT GetClientRect() const { 38 | return mClientRect; 39 | } 40 | SIZE GetFontSize() const { 41 | return mFontSize; 42 | } 43 | RECT GetWindowRect() const { 44 | return mWindowRect; 45 | } 46 | 47 | private: 48 | void RenderFrame(ConsoleFrame* frame); 49 | 50 | int mWidth; 51 | int mHeight; 52 | int mActualWidth; //공백(LEFT, RIGHT SPACE)포함 실제 너비 53 | float mFrameRate; 54 | float mFrameTime; 55 | HANDLE mConsoleHandle; 56 | CHAR_INFO* mBuffer; 57 | 58 | ConsoleFrame* mMainFrame; // 바탕화면 59 | std::vector mFrames; 60 | 61 | // 창 위치 및 크기 정보 추가 62 | POINT mWindowPosition; // 창의 스크린 좌표 63 | RECT mClientRect; // 클라이언트 영역 64 | SIZE mFontSize; // 폰트 크기 65 | RECT mWindowRect; // 전체 창 영역 66 | }; 67 | -------------------------------------------------------------------------------- /Client/Engine.cpp: -------------------------------------------------------------------------------- 1 | #include "Engine.h" 2 | 3 | #include 4 | 5 | Engine::Engine(ConsoleRenderer& renderer) 6 | : consoleRenderer(renderer) 7 | , quit(false) 8 | { 9 | inputManager = std::make_unique(); 10 | uiManager = std::make_unique(); 11 | sceneManager = std::make_unique(renderer, inputManager.get(), uiManager.get()); 12 | eventManager = std::make_unique(); 13 | 14 | client = std::make_shared(); 15 | } 16 | 17 | Engine::~Engine() 18 | { 19 | if (client != nullptr) 20 | client->Stop(); 21 | 22 | } 23 | 24 | 25 | 26 | void Engine::Initailize() 27 | { 28 | InitializeModelPointers(); 29 | 30 | 31 | std::string host = "127.0.0.1"; // 서버 호스트 32 | unsigned short port = 12345; // 서버 포트 33 | 34 | if (!client->InitializeAsClient(host, port)) { 35 | std::cerr << "클라이언트 초기화 실패. 게임을 종료합니다.\n"; 36 | QuitGame(); 37 | return; 38 | } 39 | 40 | client->SetMessageReceivedCallback( 41 | [this](const std::string& message) { HandleNetworkMessage(message); } 42 | ); 43 | 44 | } 45 | 46 | void Engine::Run() 47 | { 48 | // 고해상도 카운터 49 | LARGE_INTEGER frequency; 50 | QueryPerformanceFrequency(&frequency); 51 | 52 | LARGE_INTEGER counter; 53 | QueryPerformanceCounter(&counter); 54 | 55 | int64_t currentTime = 0; 56 | int64_t previousTime = counter.QuadPart; 57 | 58 | float targetFrameRate = consoleRenderer.GetFrameRate(); 59 | float targetOneFrameTime = consoleRenderer.GetFrameTime(); 60 | 61 | constexpr int INPUT_FREQUENCY_MULTIPLIER = 5; 62 | float refreshTime = targetOneFrameTime / static_cast(INPUT_FREQUENCY_MULTIPLIER); 63 | 64 | int frameUntilRender = 1; 65 | // Main Game Loop 66 | while(true) 67 | { 68 | if(quit) 69 | { 70 | break; 71 | } 72 | 73 | // 프레임 시간 계산 74 | QueryPerformanceCounter(&counter); 75 | currentTime = counter.QuadPart; 76 | 77 | float deltaTime = static_cast(currentTime - previousTime) / 78 | static_cast(frequency.QuadPart); 79 | 80 | if(deltaTime >= refreshTime) 81 | { 82 | ProcessInput(); 83 | --frameUntilRender; 84 | 85 | if (frameUntilRender <= 0) 86 | { 87 | 88 | Update(deltaTime); 89 | Draw(); 90 | frameUntilRender = INPUT_FREQUENCY_MULTIPLIER; 91 | } 92 | 93 | previousTime = currentTime; 94 | } 95 | 96 | } 97 | 98 | 99 | // 게임 종료 시 클라이언트 정리 100 | if (client) { 101 | client->Stop(); 102 | } 103 | } 104 | 105 | void Engine::ProcessInput() 106 | { 107 | inputManager->Update(); 108 | } 109 | 110 | void Engine::Update(float deltaTime) 111 | { 112 | sceneManager->Update(deltaTime); 113 | 114 | //Sleep(500); 115 | } 116 | 117 | void Engine::Draw() 118 | { 119 | sceneManager->Draw(); 120 | } 121 | 122 | void Engine::QuitGame() 123 | { 124 | quit = true; 125 | } 126 | 127 | 128 | 129 | 130 | // Handle Network Message 131 | void Engine::HandleNetworkMessage(const std::string& message) 132 | { 133 | // 수신한 네트워크 메시지 처리 134 | std::cout << "네트워크 메시지 수신: " << message << std::endl; 135 | 136 | // 메시지에 따라 게임 상태 업데이트 137 | if (message == "PlayerJump") 138 | { 139 | 140 | } 141 | // 기타 메시지 처리... 142 | } 143 | 144 | void Engine::SendNetworkMessage(const std::string& message) 145 | { 146 | if (client) { 147 | client->Send(message); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Client/Engine.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | #include // 필요한 경우 6 | 7 | #include "Managers/ColorManager.h" 8 | #include "Managers/InputManager.h" 9 | #include "Managers/UIManager.h" 10 | #include "ConsoleRenderer.h" 11 | #include "ModelPointer.h" 12 | #include "Managers/EventManager.h" 13 | #include "Managers/SceneManager.h" 14 | #include "Client.h" 15 | 16 | class GameMode; 17 | class Engine 18 | { 19 | public: 20 | explicit Engine(ConsoleRenderer& renderer); 21 | ~Engine(); 22 | 23 | void Initailize(); 24 | void Run(); 25 | 26 | private: 27 | 28 | void ProcessInput(); 29 | void Update(float deltaTime); 30 | void Draw(); 31 | 32 | void QuitGame(); 33 | 34 | // Network Massage 처리 35 | void HandleNetworkMessage(const std::string& message); 36 | void SendNetworkMessage(const std::string& message); 37 | 38 | private: 39 | ConsoleRenderer& consoleRenderer; 40 | bool quit; 41 | 42 | std::unique_ptr inputManager; 43 | std::unique_ptr uiManager; 44 | std::unique_ptr sceneManager; 45 | std::unique_ptr eventManager; 46 | 47 | 48 | // 네트워크 관리 49 | std::shared_ptr client; 50 | }; 51 | -------------------------------------------------------------------------------- /Client/GUI/Button.cpp: -------------------------------------------------------------------------------- 1 | // Button.cpp 2 | #include "Button.h" 3 | #include "../Managers/ConsoleColor.h" 4 | #include "../Cell.h" 5 | 6 | Button::Button() 7 | : InteractiveWidget(nullptr, nullptr) 8 | , text(L"Button") 9 | , mIsHovered(false) 10 | , mIsPressed(false) 11 | {} 12 | 13 | Button::Button(InputManager* inputManager, ConsoleFrame* frame) 14 | : InteractiveWidget(inputManager, frame) 15 | , text(L"Button") 16 | , mIsHovered(false) 17 | , mIsPressed(false) 18 | , mListenerIndex(0) 19 | { 20 | if (inputManager) { 21 | mListenerIndex = inputManager->AddMouseListener( 22 | [this](const MouseEvent& event) { 23 | HandleMouseEvent(event); 24 | } 25 | ); 26 | } 27 | } 28 | 29 | Button::~Button() { 30 | 31 | if (mInputManager) { 32 | mInputManager->RemoveMouseListener(mListenerIndex); 33 | } 34 | } 35 | 36 | void Button::Update() { 37 | if (!mInputManager || !mFrame) return; 38 | } 39 | 40 | void Button::draw() { 41 | if (!mFrame) return; 42 | 43 | Cell buttonCell{}; 44 | buttonCell.SetBackgroundColor( 45 | mIsPressed ? ConsoleColor::BrightBlue : 46 | mIsHovered ? ConsoleColor::BrightCyan : 47 | ConsoleColor::BrightGreen 48 | ); 49 | 50 | mFrame->FillRectangle( 51 | posX - width, // x 좌표를 posX로 변경 52 | posY - height / 2 + (height % 2 == 0 ? 0 : 1), // y 좌표를 posY로 변경 53 | width, 54 | height, 55 | buttonCell 56 | ); 57 | 58 | 59 | // 텍스트 위치 조정 60 | int textX = posX + (width - static_cast(text.length())) / 2 - width; 61 | int textY = posY + height / 2; 62 | mFrame->SetText( 63 | textX, 64 | textY, 65 | text, 66 | static_cast( 67 | mIsPressed ? ConsoleColor::White : 68 | mIsHovered ? ConsoleColor::BrightYellow : 69 | ConsoleColor::BrightGreen 70 | ) 71 | ); 72 | 73 | if (mInputManager) { 74 | POINT mousePos = mInputManager->GetMousePosition(); 75 | POINT framePos = { 76 | mousePos.x - mFrame->GetX(), 77 | mousePos.y - mFrame->GetY() 78 | }; 79 | mFrame->SetText(0, 1, 80 | L"Mouse: screen(" + std::to_wstring(mousePos.x) + L"," + std::to_wstring(mousePos.y) + 81 | L") frame(" + std::to_wstring(framePos.x) + L"," + std::to_wstring(framePos.y) + L")", 82 | static_cast(ConsoleColor::White)); 83 | 84 | mFrame->SetText(0, 2, 85 | L"Button: pos(" + std::to_wstring(posX) + L"," + std::to_wstring(posY) + 86 | L") size(" + std::to_wstring(width) + L"," + std::to_wstring(height) + 87 | L") hover:" + (mIsHovered ? L"Y" : L"N"), 88 | static_cast(ConsoleColor::White)); 89 | 90 | mFrame->SetText(0, 3, 91 | L"Button: pos(" + std::to_wstring(posX) + L"," + std::to_wstring(posY) + 92 | L") size(" + std::to_wstring(width) + L"," + std::to_wstring(height) + 93 | L") pressed:" + (mIsPressed ? L"Y" : L"N"), 94 | static_cast(ConsoleColor::White)); 95 | 96 | 97 | 98 | } 99 | } 100 | 101 | 102 | 103 | bool Button::contains(int mouseX, int mouseY) const { 104 | // 절대 좌표 기준으로 계산 105 | return (mouseX >= posX - width && 106 | mouseX <= posX + width && 107 | mouseY >= posY - height / 2 && 108 | mouseY <= posY + height / 2); 109 | } 110 | 111 | void Button::setText(const std::wstring& newText) { 112 | text = newText; 113 | } 114 | 115 | std::wstring Button::getText() const { 116 | return text; 117 | } 118 | 119 | void Button::SetOnHoverEnter(std::function callback) { 120 | onHoverEnter = callback; 121 | } 122 | 123 | void Button::SetOnHoverExit(std::function callback) { 124 | onHoverExit = callback; 125 | } 126 | 127 | void Button::SetOnMouseDown(std::function callback) { 128 | onMouseDown = callback; 129 | } 130 | 131 | void Button::SetOnMouseUp(std::function callback) { 132 | onMouseUp = callback; 133 | } 134 | 135 | void Button::SetOnClick(std::function callback) { 136 | onClick = callback; 137 | } 138 | 139 | void Button::HandleMouseEvent(const MouseEvent& event) { 140 | if (!mFrame) return; 141 | 142 | POINT framePos = { 143 | event.position.x - mFrame->GetX(), 144 | event.position.y - mFrame->GetY() 145 | }; 146 | 147 | mFrame->SetText(0, 3, 148 | L"HandleEvent: pos(" + std::to_wstring(framePos.x) + L"," + std::to_wstring(framePos.y) + 149 | L") button:" + std::to_wstring(static_cast(event.button)) + 150 | L" pressed:" + (event.isPressed ? L"Y" : L"N"), 151 | static_cast(ConsoleColor::White)); 152 | 153 | bool isInside = contains(framePos.x, framePos.y); 154 | 155 | 156 | // 호버 상태 업데이트 157 | if (isInside != mIsHovered) { 158 | mIsHovered = isInside; 159 | if (mIsHovered && onHoverEnter) { 160 | onHoverEnter(); 161 | } else if (!mIsHovered && onHoverExit) { 162 | onHoverExit(); 163 | } 164 | } 165 | 166 | // 마우스 버튼 이벤트 처리 167 | if (event.button == MOUSE_LEFT) { 168 | if (isInside) { 169 | if (event.isPressed) { 170 | mIsPressed = true; 171 | if (onMouseDown) onMouseDown(); 172 | } 173 | else if (mIsPressed) { 174 | mIsPressed = false; 175 | if (onMouseUp) onMouseUp(); 176 | if (onClick) onClick(); 177 | } 178 | } else if (!event.isPressed) { 179 | mIsPressed = false; 180 | } 181 | } 182 | } -------------------------------------------------------------------------------- /Client/GUI/Button.h: -------------------------------------------------------------------------------- 1 | // Button.h 2 | #ifndef BUTTON_H 3 | #define BUTTON_H 4 | 5 | #include "InteractiveWidget.h" 6 | #include 7 | #include 8 | 9 | class Button: public InteractiveWidget { 10 | public: 11 | Button(); 12 | Button(InputManager* inputManager, ConsoleFrame* frame); 13 | virtual ~Button(); 14 | 15 | // Widget 인터페이스 구현 16 | virtual void Update() override; 17 | virtual void draw() override; 18 | virtual bool contains(int mouseX, int mouseY) const override; 19 | 20 | // 텍스트 관련 메서드 21 | void setText(const std::wstring& newText); 22 | std::wstring getText() const; 23 | 24 | // 이벤트 콜백 설정 25 | void SetOnHoverEnter(std::function callback); 26 | void SetOnHoverExit(std::function callback); 27 | void SetOnMouseDown(std::function callback); 28 | void SetOnMouseUp(std::function callback); 29 | void SetOnClick(std::function callback); 30 | 31 | private: 32 | void HandleMouseEvent(const MouseEvent& event); 33 | 34 | std::wstring text; 35 | size_t mListenerIndex; // 이벤트 리스너 인덱스 저장 36 | 37 | // 이벤트 콜백 38 | std::function onHoverEnter; 39 | std::function onHoverExit; 40 | std::function onMouseDown; 41 | std::function onMouseUp; 42 | std::function onClick; 43 | 44 | // 상태 플래그 45 | bool mIsHovered; 46 | bool mIsPressed; 47 | 48 | }; 49 | 50 | #endif // BUTTON_H -------------------------------------------------------------------------------- /Client/GUI/InteractiveWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef INTERACTIVEWIDGET_H 2 | #define INTERACTIVEWIDGET_H 3 | 4 | #include "Widget.h" 5 | 6 | class InteractiveWidget: public Widget { 7 | public: 8 | InteractiveWidget(InputManager* inputManager, ConsoleFrame* frame) 9 | : Widget(inputManager, frame) 10 | {} 11 | virtual ~InteractiveWidget() = default; 12 | }; 13 | 14 | #endif -------------------------------------------------------------------------------- /Client/GUI/Widget.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGET_H 2 | #define WIDGET_H 3 | 4 | #include 5 | #include "../Managers/InputManager.h" 6 | #include "../ConsoleFrame.h" 7 | #include "../ConsoleRenderer.h" 8 | 9 | // Widget은 GUI 컴포넌트의 기본 클래스 10 | class Widget { 11 | public: 12 | Widget() = default; 13 | Widget(InputManager* im, ConsoleFrame* frame) 14 | : mInputManager(im), mFrame(frame) {} 15 | virtual ~Widget() = default; 16 | virtual void Update() = 0; 17 | virtual void draw() = 0; 18 | 19 | // 기본 resize 함수: 위젯의 크기를 변경 20 | virtual void resize(int width, int height) { 21 | this->width = width; 22 | this->height = height; 23 | } 24 | 25 | virtual void SetPosition(int x, int y) { 26 | posX = x; 27 | posY = y; 28 | } 29 | 30 | // 위젯의 이름 설정 31 | void setName(const std::string& name) { 32 | this->name = name; 33 | } 34 | std::string getName() const { 35 | return name; 36 | } 37 | 38 | // 기본적으로 입력 이벤트 대상이 아니라면 false를 반환 39 | virtual bool contains(int mouseX, int mouseY) const { 40 | return false; 41 | } 42 | 43 | protected: 44 | std::string name; // 위젯 이름 45 | int width = 0; // 위젯 가로 크기 46 | int height = 0; // 위젯 세로 크기 47 | int posX = 0; // 위젯의 좌측 상단(혹은 중앙) X 좌표 48 | int posY = 0; // 위젯의 좌측 상단(혹은 중앙) Y 좌표 49 | 50 | InputManager* mInputManager = nullptr; 51 | ConsoleFrame* mFrame = nullptr; 52 | }; 53 | 54 | #endif // WIDGET_H -------------------------------------------------------------------------------- /Client/GameModeType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum class GameModeType 4 | { 5 | Single, 6 | Multiplayer 7 | }; 8 | 9 | enum class GameState 10 | { 11 | MainMenu, 12 | Playing, 13 | Paused, 14 | GameOver 15 | }; -------------------------------------------------------------------------------- /Client/Managers/ColorManager.cpp: -------------------------------------------------------------------------------- 1 | #include "ColorManager.h" 2 | 3 | ColorManager::ColorManager() 4 | : mRandomEngine(std::random_device{}()) 5 | { 6 | } 7 | 8 | void ColorManager::AddColorToTable(ConsoleColor color) 9 | { 10 | auto it = std::find(mColorTable.begin(), mColorTable.end(), color); 11 | 12 | if (it == mColorTable.end()) 13 | { 14 | mColorTable.push_back(color); 15 | } 16 | } 17 | 18 | void ColorManager::RemoveColorFromTable(ConsoleColor color) 19 | { 20 | auto it = std::find(mColorTable.begin(), mColorTable.end(), color); 21 | 22 | if (it != mColorTable.end()) 23 | { 24 | mColorTable.erase(it); 25 | } 26 | } 27 | 28 | void ColorManager::ClearColors() 29 | { 30 | mColorTable.clear(); 31 | } 32 | 33 | void ColorManager::AddAllColors() 34 | { 35 | for (int i = 0; i <= static_cast(ConsoleColor::BrightWhite); ++i) 36 | { 37 | AddColorToTable(static_cast(i)); 38 | } 39 | } 40 | 41 | void ColorManager::AddBrightColors() 42 | { 43 | AddColorToTable(ConsoleColor::BrightBlue); 44 | AddColorToTable(ConsoleColor::BrightGreen); 45 | AddColorToTable(ConsoleColor::BrightRed); 46 | AddColorToTable(ConsoleColor::BrightCyan); 47 | AddColorToTable(ConsoleColor::BrightMagenta); 48 | AddColorToTable(ConsoleColor::BrightYellow); 49 | AddColorToTable(ConsoleColor::BrightWhite); 50 | } 51 | 52 | void ColorManager::AddDarkColors() 53 | { 54 | AddColorToTable(ConsoleColor::Black); 55 | AddColorToTable(ConsoleColor::Blue); 56 | AddColorToTable(ConsoleColor::Green); 57 | AddColorToTable(ConsoleColor::Red); 58 | AddColorToTable(ConsoleColor::Cyan); 59 | AddColorToTable(ConsoleColor::Magenta); 60 | AddColorToTable(ConsoleColor::Yellow); 61 | AddColorToTable(ConsoleColor::White); 62 | } 63 | 64 | ConsoleColor ColorManager::GetRandomColor() const 65 | { 66 | if (mColorTable.empty()) 67 | { 68 | return ConsoleColor::BrightWhite; 69 | } 70 | std::uniform_int_distribution dist(0, mColorTable.size() - 1); 71 | return mColorTable[dist(mRandomEngine)]; 72 | } 73 | 74 | WORD ColorManager::ToWord(ConsoleColor fg, ConsoleColor bg) 75 | { 76 | return static_cast(fg) | (static_cast(bg) << 4); 77 | } 78 | -------------------------------------------------------------------------------- /Client/Managers/ColorManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ConsoleColor.h" 4 | #include 5 | #include 6 | 7 | class ColorManager 8 | { 9 | public: 10 | ColorManager(); 11 | ~ColorManager() = default; 12 | 13 | void AddColorToTable(ConsoleColor color); 14 | void RemoveColorFromTable(ConsoleColor color); 15 | void ClearColors(); 16 | void AddAllColors(); 17 | void AddBrightColors(); 18 | void AddDarkColors(); 19 | 20 | ConsoleColor GetRandomColor() const; 21 | 22 | static WORD ToWord(ConsoleColor fg, ConsoleColor bg = ConsoleColor::Black); 23 | 24 | private: 25 | std::vector mColorTable; 26 | mutable std::mt19937 mRandomEngine; 27 | }; 28 | -------------------------------------------------------------------------------- /Client/Managers/ConsoleColor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | enum class ConsoleColor : WORD 6 | { 7 | Black = 0, 8 | Blue = FOREGROUND_BLUE, 9 | Green = FOREGROUND_GREEN, 10 | Red = FOREGROUND_RED, 11 | Cyan = FOREGROUND_BLUE | FOREGROUND_GREEN, 12 | Magenta = FOREGROUND_RED | FOREGROUND_BLUE, 13 | Yellow = FOREGROUND_RED | FOREGROUND_GREEN, 14 | White = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, 15 | BrightBlue = FOREGROUND_BLUE | FOREGROUND_INTENSITY, 16 | BrightGreen = FOREGROUND_GREEN | FOREGROUND_INTENSITY, 17 | BrightRed = FOREGROUND_RED | FOREGROUND_INTENSITY, 18 | BrightCyan = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY, 19 | BrightMagenta = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY, 20 | BrightYellow = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, 21 | BrightWhite = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY 22 | }; 23 | -------------------------------------------------------------------------------- /Client/Managers/EventManager.cpp: -------------------------------------------------------------------------------- 1 | #include "EventManager.h" 2 | 3 | void EventManager::Subscribe(EventType eventType,std::function handler) { 4 | handlers[eventType].push_back(handler); 5 | } 6 | 7 | void EventManager::Publish(EventType eventType) { 8 | // 해당 이벤트 타입에 등록된 핸들러들 호출 9 | if(handlers.find(eventType) != handlers.end()) { 10 | for(auto& handler : handlers[eventType]) { 11 | handler(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Client/Managers/EventManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class EventManager { 8 | public: 9 | // 이벤트 타입 정의 (열거형 사용) 10 | enum class EventType { 11 | BlockSpawned, 12 | LineCleared, 13 | GameOver, 14 | }; 15 | 16 | // 이벤트 구독 17 | void Subscribe(EventType eventType,std::function handler); 18 | 19 | // 이벤트 발생 20 | void Publish(EventType eventType); 21 | 22 | private: 23 | // 이벤트 타입별 핸들러 목록 24 | std::map>> handlers; 25 | }; -------------------------------------------------------------------------------- /Client/Managers/InputManager.cpp: -------------------------------------------------------------------------------- 1 | #include "InputManager.h" 2 | #include 3 | 4 | #pragma region KeyBoard Method 5 | 6 | bool InputManager::IsKeyPressed(int key) 7 | { 8 | return (GetAsyncKeyState(key) & 0x8000) != 0; 9 | } 10 | bool InputManager::IsKeyDown(int key) 11 | { 12 | return false; 13 | } 14 | 15 | void InputManager::EnqueueInput(int key) { 16 | mInputQueue.push(key); 17 | } 18 | int InputManager::DequeueInput() { 19 | 20 | if(mInputQueue.empty()) 21 | return -1; 22 | 23 | int key = mInputQueue.front(); 24 | mInputQueue.pop(); 25 | return key; 26 | 27 | } 28 | void InputManager::AddPressedKeysToQueue() { 29 | 30 | using clock = std::chrono::steady_clock; 31 | auto now = clock::now(); 32 | 33 | int keysToCheck[] = {VK_LEFT,VK_RIGHT,VK_UP,VK_DOWN, VK_RETURN, VK_SPACE}; 34 | 35 | for(int key : keysToCheck) { 36 | if(IsKeyPressed(key)) { 37 | if(!prevKeyState[key]) { 38 | EnqueueInput(key); 39 | prevKeyState[key] = true; 40 | lastKeyEventTime[key] = now; 41 | } else { 42 | if(now - lastKeyEventTime[key] >= continuousInputInterval) { 43 | EnqueueInput(key); 44 | lastKeyEventTime[key] = now; // 시간 갱신 45 | } 46 | } 47 | } else { 48 | prevKeyState[key] = false; 49 | } 50 | } 51 | } 52 | 53 | #pragma endregion 54 | 55 | 56 | #pragma region Mouse Method 57 | 58 | bool InputManager::IsMousePressed(MouseButton button) { 59 | return m_mousePressed[button]; 60 | } 61 | 62 | bool InputManager::IsMouseDown(MouseButton button) { 63 | return m_mousePressed[button] && !prevMouseState[button]; 64 | } 65 | 66 | void InputManager::EnqueueMouseInput(int eventCode) { 67 | mMouseInputQueue.push(eventCode); 68 | } 69 | 70 | int InputManager::DequeueMouseInput() { 71 | if (mMouseInputQueue.empty()) return -1; 72 | int event = mMouseInputQueue.front(); 73 | mMouseInputQueue.pop(); 74 | return event; 75 | } 76 | 77 | POINT InputManager::GetMousePosition() const { 78 | return m_mousePosition; 79 | } 80 | 81 | void InputManager::SetMousePosition(int x, int y) { 82 | m_mousePosition = {x, y}; 83 | } 84 | 85 | // 새로 추가된 이벤트 관련 메서드들 86 | 87 | size_t InputManager::AddMouseListener(MouseEventCallback callback) { 88 | size_t newId = mNextListenerId++; 89 | mMouseListeners[newId] = callback; 90 | return newId; 91 | } 92 | 93 | void InputManager::RemoveMouseListener(size_t id) { 94 | mMouseListeners.erase(id); 95 | } 96 | 97 | void InputManager::ProcessMouseEvent(MouseButton button, bool isPressed) { 98 | MouseEvent event{ 99 | button, 100 | m_mousePosition, 101 | isPressed 102 | }; 103 | 104 | auto listenersCopy = mMouseListeners; 105 | for (const auto& [id, listener] : listenersCopy) { 106 | listener(event); 107 | } 108 | } 109 | 110 | void InputManager::UpdateMousePosition() { 111 | m_prevMousePosition = m_mousePosition; // 현재 위치를 이전 위치로 저장 112 | 113 | POINT screenPos; 114 | GetCursorPos(&screenPos); 115 | HWND terminalWindow = GetForegroundWindow(); 116 | RECT windowRect, clientRect; 117 | GetWindowRect(terminalWindow, &windowRect); 118 | GetClientRect(terminalWindow, &clientRect); 119 | 120 | // 타이틀 바와 경계선의 높이 계산 121 | int borderHeight = windowRect.bottom - windowRect.top - clientRect.bottom; 122 | int titleBarHeight = GetSystemMetrics(SM_CYCAPTION); 123 | 124 | // 윈도우 좌표로 변환 (타이틀 바와 경계선 직접 고려) 125 | screenPos.x -= windowRect.left; 126 | screenPos.y -= (windowRect.top + titleBarHeight); 127 | 128 | int CONSOLE_WIDTH = 80; 129 | int CONSOLE_HEIGHT = 29; 130 | 131 | // 콘솔 좌표로 변환 132 | float ratioX = static_cast(screenPos.x) / clientRect.right; 133 | float ratioY = static_cast(screenPos.y) / clientRect.bottom; 134 | 135 | m_mousePosition.x = static_cast(ratioX * CONSOLE_WIDTH); 136 | m_mousePosition.y = static_cast(ratioY * CONSOLE_HEIGHT); 137 | 138 | if (m_prevMousePosition.x != m_mousePosition.x || 139 | m_prevMousePosition.y != m_mousePosition.y) { 140 | ProcessMouseEvent(MOUSE_LEFT, false); // 마우스 이동 이벤트 141 | } 142 | } 143 | 144 | void InputManager::AddMouseEventsToQueue() { 145 | auto currentTime = std::chrono::steady_clock::now(); 146 | 147 | // 마우스 버튼 상태 업데이트 148 | bool newStates[MOUSE_BUTTON_COUNT] = { 149 | (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0, 150 | (GetAsyncKeyState(VK_RBUTTON) & 0x8000) != 0, 151 | (GetAsyncKeyState(VK_MBUTTON) & 0x8000) != 0 152 | }; 153 | 154 | for (int i = 0; i < MOUSE_BUTTON_COUNT; ++i) { 155 | m_mousePressed[i] = newStates[i]; 156 | 157 | if (m_mousePressed[i] != prevMouseState[i]) { 158 | ProcessMouseEvent(static_cast(i), m_mousePressed[i]); 159 | 160 | if (m_mousePressed[i]) { // 눌렸을 때만 큐에 추가 161 | EnqueueMouseInput(i); 162 | lastMouseEventTime[i] = currentTime; 163 | } 164 | } 165 | prevMouseState[i] = m_mousePressed[i]; 166 | } 167 | } 168 | 169 | void InputManager::Update() 170 | { 171 | UpdateMousePosition(); 172 | AddMouseEventsToQueue(); 173 | AddPressedKeysToQueue(); 174 | } 175 | 176 | #pragma endregion 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /Client/Managers/InputManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace ConsoleConstants { 10 | const int CONSOLE_WIDTH = 80; 11 | const int CONSOLE_HEIGHT = 25; 12 | const int CELL_WIDTH = 2; 13 | } 14 | 15 | enum MouseButton { 16 | MOUSE_LEFT = 0, 17 | MOUSE_RIGHT, 18 | MOUSE_MIDDLE, 19 | MOUSE_BUTTON_COUNT 20 | }; 21 | 22 | struct MouseEvent { 23 | MouseButton button; 24 | POINT position; 25 | bool isPressed; 26 | }; 27 | 28 | class InputManager { 29 | using MouseEventCallback = std::function; 30 | 31 | private: 32 | std::queue mInputQueue; // 키 입력 큐 33 | std::queue mMouseInputQueue; // 마우스 입력 큐 34 | 35 | // 키보드 상태 36 | bool m_keyPressed[256] = {false, }; 37 | bool prevKeyState[256] = {false, }; 38 | std::chrono::steady_clock::time_point lastKeyEventTime[256]; 39 | const std::chrono::milliseconds continuousInputInterval = std::chrono::milliseconds(166); 40 | 41 | // 마우스 상태 42 | bool m_mousePressed[MOUSE_BUTTON_COUNT] = {false, }; 43 | bool prevMouseState[MOUSE_BUTTON_COUNT] = {false, }; 44 | std::chrono::steady_clock::time_point lastMouseEventTime[MOUSE_BUTTON_COUNT]; 45 | POINT m_mousePosition = {0, 0}; 46 | POINT m_prevMousePosition = {0, 0}; 47 | 48 | // 이벤트 리스너 49 | size_t mNextListenerId = 0; 50 | std::unordered_map mMouseListeners; 51 | public: 52 | #pragma region KeyBoard Method 53 | bool IsKeyPressed(int key); // 특정 키가 눌렸는지 54 | bool IsKeyDown(int key); // 이번 프레임에 막 눌린 키인지 55 | void EnqueueInput(int key); 56 | int DequeueInput(); 57 | void AddPressedKeysToQueue(); 58 | #pragma endregion 59 | 60 | #pragma region Mouse Method 61 | // 기존 메서드들 62 | bool IsMousePressed(MouseButton button); 63 | 64 | bool IsMouseDown(MouseButton button); 65 | 66 | void EnqueueMouseInput(int eventCode); 67 | 68 | int DequeueMouseInput(); 69 | 70 | POINT GetMousePosition() const; 71 | 72 | void SetMousePosition(int x, int y); 73 | 74 | // 새로 추가된 이벤트 관련 메서드들 75 | size_t AddMouseListener(MouseEventCallback callback); 76 | 77 | void RemoveMouseListener(size_t index); 78 | 79 | void ProcessMouseEvent(MouseButton button, bool isPressed); 80 | 81 | void UpdateMousePosition(); 82 | 83 | void AddMouseEventsToQueue(); 84 | #pragma endregion 85 | 86 | void Update(); 87 | 88 | }; -------------------------------------------------------------------------------- /Client/Managers/SceneManager.cpp: -------------------------------------------------------------------------------- 1 | #include "SceneManager.h" 2 | #include "../Scenes/MainMenuScene.h" 3 | #include "../Scenes/GameScene.h" 4 | #include "../Scenes/GameOverScene.h" 5 | 6 | SceneManager::SceneManager(ConsoleRenderer& renderer, InputManager* inputManager, UIManager* uiManager) 7 | : mRenderer(renderer) 8 | , mInputManager(inputManager) 9 | , mUIManager(uiManager) 10 | , mCurrentScene(nullptr) 11 | { 12 | // 시작 시 MainMenu로 전환 (원하면 다른 SceneType도 가능) 13 | RequestSceneChange(SceneType::MainMenu); 14 | } 15 | 16 | void SceneManager::RequestSceneChange(SceneType type) 17 | { 18 | // 기존 Scene 제거 19 | mCurrentScene.reset(); 20 | 21 | // 새로운 Scene 생성 22 | switch (type) { 23 | case SceneType::MainMenu: 24 | mCurrentScene = std::make_unique(mRenderer, mInputManager, mUIManager, this); 25 | break; 26 | case SceneType::Playing: 27 | mCurrentScene = std::make_unique(mRenderer, mInputManager, mUIManager, this, GameModeType::Single); 28 | break; 29 | case SceneType::GameOver: 30 | mCurrentScene = std::make_unique(mRenderer, mInputManager, mUIManager, this); 31 | break; 32 | default: 33 | //throw std::runtime_error("Unknown SceneType requested"); 34 | break; 35 | } 36 | } 37 | 38 | void SceneManager::Update(float deltaTime) 39 | { 40 | // 현재 Scene 업데이트 41 | if (mCurrentScene) { 42 | mCurrentScene->Update(deltaTime); 43 | 44 | SceneType pendingChange = mCurrentScene->GetPendingSceneChange(); 45 | if (pendingChange != SceneType::None) { 46 | mCurrentScene->ClearPendingSceneChange(); 47 | RequestSceneChange(pendingChange); 48 | } 49 | } 50 | } 51 | 52 | void SceneManager::Draw() 53 | { 54 | // 현재 Scene 렌더링 55 | if (mCurrentScene) { 56 | mCurrentScene->Draw(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Client/Managers/SceneManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../Scenes/Scene.h" 5 | #include "../Scenes/SceneType.h" 6 | #include "../ConsoleRenderer.h" 7 | #include "InputManager.h" 8 | #include "UIManager.h" 9 | 10 | class Scene; 11 | 12 | // SceneManager: 현재 Scene을 보유하며, Scene 전환, 업데이트, 렌더링을 담당 13 | class SceneManager { 14 | public: 15 | SceneManager( 16 | ConsoleRenderer& renderer, 17 | InputManager* inputManager, 18 | UIManager* uiManager 19 | ); 20 | ~SceneManager() = default; 21 | 22 | // Scene 전환 요청 23 | void RequestSceneChange(SceneType type); 24 | 25 | // 현재 Scene 업데이트/렌더링 26 | void Update(float deltaTime); 27 | void Draw(); 28 | 29 | private: 30 | ConsoleRenderer& mRenderer; 31 | InputManager* mInputManager; 32 | UIManager* mUIManager; 33 | std::unique_ptr mCurrentScene; // 현재 활성화된 Scene 34 | }; -------------------------------------------------------------------------------- /Client/Managers/UIManager.cpp: -------------------------------------------------------------------------------- 1 | #include "UIManager.h" 2 | 3 | 4 | UIManager::~UIManager() { 5 | for (Widget* widget : mWidgets) { 6 | delete widget; 7 | } 8 | mWidgets.clear(); 9 | } 10 | 11 | // 위젯 추가 12 | void UIManager::AddWidget(Widget * widget) { 13 | if (widget) 14 | mWidgets.push_back(widget); 15 | } 16 | 17 | // 위젯 제거 18 | void UIManager::RemoveWidget(Widget * widget) { 19 | auto it = std::find(mWidgets.begin(), mWidgets.end(), widget); 20 | if (it != mWidgets.end()) { 21 | delete *it; 22 | mWidgets.erase(it); 23 | } 24 | } 25 | 26 | // 마우스 이벤트 처리: 입력 처리가 가능한 위젯(예: Button)만 대상으로 함 27 | 28 | void UIManager::ProcessMouseEvent(const MOUSE_EVENT_RECORD & mouseEvent) { 29 | int mouseX = mouseEvent.dwMousePosition.X; 30 | int mouseY = mouseEvent.dwMousePosition.Y; 31 | 32 | for (Widget* widget : mWidgets) { 33 | if (auto interactive = dynamic_cast(widget)) { 34 | if (interactive->contains(mouseX, mouseY)) { 35 | break; // 하나의 위젯만 처리하도록 한다면 break 36 | } 37 | } 38 | } 39 | } 40 | 41 | // 모든 위젯 업데이트 42 | void UIManager::Update() { 43 | for (Widget* widget : mWidgets) { 44 | widget->Update(); 45 | } 46 | } 47 | 48 | // 모든 위젯 그리기 49 | void UIManager::draw() { 50 | for (Widget* widget : mWidgets) { 51 | widget->draw(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Client/Managers/UIManager.h: -------------------------------------------------------------------------------- 1 | #ifndef UIMANAGER_H 2 | #define UIMANAGER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "../GUI/InteractiveWidget.h" 8 | 9 | class UIManager { 10 | public: 11 | UIManager() = default; 12 | ~UIManager(); 13 | 14 | // 위젯 추가 15 | void AddWidget(Widget* widget); 16 | 17 | // 위젯 제거 18 | void RemoveWidget(Widget* widget); 19 | 20 | // 마우스 이벤트 처리: 입력 처리가 가능한 위젯(예: Button)만 대상으로 함 21 | void ProcessMouseEvent(const MOUSE_EVENT_RECORD& mouseEvent); 22 | 23 | // 모든 위젯 업데이트 24 | void Update(); 25 | 26 | // 모든 위젯 그리기 27 | void draw(); 28 | 29 | private: 30 | std::vector mWidgets; 31 | }; 32 | 33 | #endif // UIMANAGER_H 34 | -------------------------------------------------------------------------------- /Client/ModelPointer.cpp: -------------------------------------------------------------------------------- 1 | #include "ModelPointer.h" 2 | 3 | mat2x2 blockModel2x2_[1][4] = { 4 | { // O 모양 5 | { // 회전 0도 6 | {1,1}, 7 | {1,1} 8 | }, 9 | { // 회전 90도 10 | {1,1}, 11 | {1,1} 12 | }, 13 | { // 회전 180도 14 | {1,1}, 15 | {1,1} 16 | }, 17 | { // 회전 270도 18 | {1,1}, 19 | {1,1} 20 | } 21 | } 22 | }; 23 | 24 | mat3x3 blockModel3x3_[5][4] = { 25 | { // 인덱스 0: J 모양 26 | { // 회전 0도 27 | {1,0,0}, 28 | {1,1,1}, 29 | {0,0,0} 30 | }, 31 | { // 회전 90도 32 | {0,1,1}, 33 | {0,1,0}, 34 | {0,1,0} 35 | }, 36 | { // 회전 180도 37 | {0,0,0}, 38 | {1,1,1}, 39 | {0,0,1} 40 | }, 41 | { // 회전 270도 42 | {0,1,0}, 43 | {0,1,0}, 44 | {1,1,0} 45 | } 46 | }, 47 | { // 인덱스 1: L 모양 48 | { // 회전 0도 49 | {0,0,1}, 50 | {1,1,1}, 51 | {0,0,0} 52 | }, 53 | { // 회전 90도 54 | {0,1,0}, 55 | {0,1,0}, 56 | {0,1,1} 57 | }, 58 | { // 회전 180도 59 | {0,0,0}, 60 | {1,1,1}, 61 | {1,0,0} 62 | }, 63 | { // 회전 270도 64 | {1,1,0}, 65 | {0,1,0}, 66 | {0,1,0} 67 | } 68 | }, 69 | { // 인덱스 2: S 모양 70 | { // 회전 0도 71 | {0,1,1}, 72 | {1,1,0}, 73 | {0,0,0} 74 | }, 75 | { // 회전 90도 76 | {0,1,0}, 77 | {0,1,1}, 78 | {0,0,1} 79 | }, 80 | { // 회전 180도 (0도와 동일) 81 | {0,1,1}, 82 | {1,1,0}, 83 | {0,0,0} 84 | }, 85 | { // 회전 270도 (90도와 동일) 86 | {0,1,0}, 87 | {0,1,1}, 88 | {0,0,1} 89 | } 90 | }, 91 | { // 인덱스 3: T 모양 92 | { // 회전 0도 93 | {0,1,0}, 94 | {1,1,1}, 95 | {0,0,0} 96 | }, 97 | { // 회전 90도 98 | {0,1,0}, 99 | {0,1,1}, 100 | {0,1,0} 101 | }, 102 | { // 회전 180도 103 | {0,0,0}, 104 | {1,1,1}, 105 | {0,1,0} 106 | }, 107 | { // 회전 270도 108 | {0,1,0}, 109 | {1,1,0}, 110 | {0,1,0} 111 | } 112 | }, 113 | { // 인덱스 4: Z 모양 114 | { // 회전 0도 115 | {1,1,0}, 116 | {0,1,1}, 117 | {0,0,0} 118 | }, 119 | { // 회전 90도 120 | {0,0,1}, 121 | {0,1,1}, 122 | {0,1,0} 123 | }, 124 | { // 회전 180도 (0도와 동일) 125 | {1,1,0}, 126 | {0,1,1}, 127 | {0,0,0} 128 | }, 129 | { // 회전 270도 (90도와 동일) 130 | {0,0,1}, 131 | {0,1,1}, 132 | {0,1,0} 133 | } 134 | } 135 | }; 136 | 137 | mat4x4 blockModel4x4_[1][4] = { 138 | { // 인덱스 0: I 모양 139 | { // 회전 0도 140 | {0,0,0,0}, 141 | {1,1,1,1}, 142 | {0,0,0,0}, 143 | {0,0,0,0} 144 | }, 145 | { // 회전 90도 146 | {0,0,1,0}, 147 | {0,0,1,0}, 148 | {0,0,1,0}, 149 | {0,0,1,0} 150 | }, 151 | { // 회전 180도 (I 모양은 0도와 동일) 152 | {0,0,0,0}, 153 | {1,1,1,1}, 154 | {0,0,0,0}, 155 | {0,0,0,0} 156 | }, 157 | { // 회전 270도 (I 모양은 90도와 동일) 158 | {0,0,1,0}, 159 | {0,0,1,0}, 160 | {0,0,1,0}, 161 | {0,0,1,0} 162 | } 163 | } 164 | }; 165 | 166 | ModelPointer modelPointers[SHAPE_COUNT][ROTATION_COUNT]; 167 | 168 | void InitializeModelPointers() { 169 | // 모든 블록 모양과 회전 상태에 대해 포인터 초기화 170 | for(int r = 0; r < ROTATION_COUNT; r++) { 171 | 172 | // O 모양 처리 (2x2) 173 | modelPointers[SHAPE_O][r].mat2 = &blockModel2x2_[0][r]; // O 모양 174 | 175 | // 3x3 블록 모양들 처리 176 | modelPointers[SHAPE_J][r].mat3 = &blockModel3x3_[0][r]; // T 모양 177 | modelPointers[SHAPE_L][r].mat3 = &blockModel3x3_[1][r]; // L 모양 178 | modelPointers[SHAPE_S][r].mat3 = &blockModel3x3_[2][r]; // J 모양 179 | modelPointers[SHAPE_T][r].mat3 = &blockModel3x3_[3][r]; // S 모양 180 | modelPointers[SHAPE_Z][r].mat3 = &blockModel3x3_[4][r]; // Z 모양 181 | 182 | // I 모양 처리 (4x4) 183 | modelPointers[SHAPE_I][r].mat4 = &blockModel4x4_[0][r]; 184 | } 185 | } -------------------------------------------------------------------------------- /Client/ModelPointer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define SHAPE_COUNT 7 4 | #define ROTATION_COUNT 4 5 | 6 | typedef enum { 7 | SHAPE_I, 8 | SHAPE_J, 9 | SHAPE_L, 10 | SHAPE_O, 11 | SHAPE_S, 12 | SHAPE_T, 13 | SHAPE_Z, 14 | } ShapeType; 15 | typedef enum { 16 | ROTATION_0, 17 | ROTATION_90, 18 | ROTATION_180, 19 | ROTATION_270, 20 | } RotationState; 21 | 22 | typedef char mat2x2[2][2]; 23 | typedef char mat3x3[3][3]; 24 | typedef char mat4x4[4][4]; 25 | 26 | extern mat2x2 blockModel2x2_[1][4]; 27 | extern mat3x3 blockModel3x3_[5][4]; 28 | extern mat4x4 blockModel4x4_[1][4]; 29 | 30 | typedef union { 31 | const mat2x2* mat2; 32 | const mat3x3* mat3; 33 | const mat4x4* mat4; 34 | } ModelPointer; 35 | 36 | 37 | extern ModelPointer modelPointers[SHAPE_COUNT][ROTATION_COUNT]; 38 | 39 | void InitializeModelPointers(); -------------------------------------------------------------------------------- /Client/Scenes/GameOverScene.cpp: -------------------------------------------------------------------------------- 1 | #include "GameOverScene.h" 2 | 3 | GameOverScene::GameOverScene( 4 | ConsoleRenderer & renderer, 5 | InputManager * im, 6 | UIManager* um, 7 | SceneManager * sm) 8 | :Scene(renderer, im, um, sm) 9 | { 10 | 11 | posY = 10; 12 | renderer.Clear(); 13 | mFrame = renderer.AddFrame(0, 0, 40, 30); 14 | } 15 | 16 | void GameOverScene::Update(float deltaTime) 17 | { 18 | mFrame->Clear(); 19 | mFrame->SetText(5, posY, L"Game Over", static_cast(ConsoleColor::BrightRed)); 20 | posY--; 21 | Sleep(100); 22 | } 23 | 24 | void GameOverScene::Draw() 25 | { 26 | mRenderer.Render(); 27 | } 28 | -------------------------------------------------------------------------------- /Client/Scenes/GameOverScene.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Scene.h" 3 | #include "../GameModeType.h" 4 | #include "../TetrisBoard.h" 5 | 6 | class GameOverScene: public Scene { 7 | 8 | constexpr static int END_WIDTH = 20; 9 | constexpr static int END_HEIGHT = 10; 10 | public: 11 | GameOverScene( 12 | ConsoleRenderer& renderer, 13 | InputManager* im, 14 | UIManager* um, 15 | SceneManager* sm); 16 | 17 | void Update(float deltaTime) override; 18 | 19 | void Draw() override; 20 | 21 | private: 22 | int posX, posY; 23 | 24 | }; -------------------------------------------------------------------------------- /Client/Scenes/GameScene.cpp: -------------------------------------------------------------------------------- 1 | #include "GameScene.h" 2 | 3 | PlayingScene::PlayingScene( 4 | ConsoleRenderer & renderer, 5 | InputManager * im, 6 | UIManager* um, 7 | SceneManager * sm, 8 | GameModeType mode) 9 | : Scene(renderer, im, um, sm), mCurrentMode(mode) { 10 | 11 | mRenderer.Clear(); 12 | mFrame = mRenderer.AddFrame(10, 5, 40, 30); 13 | switch (mCurrentMode) { 14 | case GameModeType::Single: SetupSingleMode(); break; 15 | case GameModeType::Multiplayer: SetupMultiplayerMode(); break; 16 | } 17 | } 18 | 19 | void PlayingScene::Update(float deltaTime) { 20 | // 보드 업데이트 21 | for (auto& board : mBoards) { 22 | board->Update(deltaTime); 23 | if (board->IsFull()) { 24 | // Game Over 25 | mSceneManager->RequestSceneChange(SceneType::GameOver); 26 | } 27 | } 28 | } 29 | 30 | void PlayingScene::Draw() { 31 | for (auto& board : mBoards) { 32 | board->Draw(); 33 | } 34 | mRenderer.Render(); 35 | } 36 | 37 | void PlayingScene::SetupSingleMode() { /*...*/ 38 | mBoards.emplace_back(std::make_shared(mRenderer, 3, 3, 12, 24, mInputManager)); 39 | } 40 | 41 | void PlayingScene::SetupMultiplayerMode() { /*...*/ 42 | } 43 | -------------------------------------------------------------------------------- /Client/Scenes/GameScene.h: -------------------------------------------------------------------------------- 1 | // PlayingScene.h 2 | #pragma once 3 | #include "Scene.h" 4 | #include "../GameModeType.h" 5 | #include "../TetrisBoard.h" 6 | 7 | class PlayingScene: public Scene { 8 | public: 9 | PlayingScene( 10 | ConsoleRenderer& renderer, 11 | InputManager* im, 12 | UIManager* um, 13 | SceneManager* sm, 14 | GameModeType mode); 15 | 16 | void Update(float deltaTime) override; 17 | 18 | void Draw() override; 19 | 20 | private: 21 | void SetupSingleMode(); 22 | void SetupMultiplayerMode(); 23 | 24 | std::vector> mBoards; 25 | GameModeType mCurrentMode; 26 | }; -------------------------------------------------------------------------------- /Client/Scenes/MainMenuScene.cpp: -------------------------------------------------------------------------------- 1 | #include "MainMenuScene.h" 2 | #include 3 | 4 | void MainMenuScene::InitializeButtons() 5 | { 6 | int screenWidth = mFrame->GetWidth(); 7 | int screenHeight = mFrame->GetHeight(); 8 | int centerX = screenWidth / 2; 9 | int centerY = screenHeight / 2; 10 | 11 | // Single 버튼 (위쪽) 12 | mSingleMode->SetPosition(centerX, centerY - 3); 13 | mSingleMode->resize(10, 3); 14 | mSingleMode->setText(L"Single"); 15 | 16 | 17 | // Multi 버튼 (아래쪽) 18 | mMultiMode->SetPosition(centerX, centerY + 3); // 중앙 기준 19 | mMultiMode->resize(10, 3); // 여백 포함 크기 20 | mMultiMode->setText(L"Multi"); 21 | 22 | // 싱글 모드 버튼 이벤트 설정 23 | mSingleMode->SetOnHoverEnter([this]() { 24 | // 호버 효과 (선택적) 25 | }); 26 | 27 | mSingleMode->SetOnClick([this]() { 28 | mPendingSceneChange = SceneType::Playing; 29 | }); 30 | 31 | 32 | // 멀티 모드 버튼 이벤트 설정 33 | mMultiMode->SetOnHoverEnter([this]() { 34 | // 호버 효과 (선택적) 35 | }); 36 | 37 | mMultiMode->SetOnClick([this]() { 38 | mPendingSceneChange = SceneType::Playing; 39 | }); 40 | 41 | 42 | 43 | } 44 | 45 | MainMenuScene::MainMenuScene( 46 | ConsoleRenderer& renderer, 47 | InputManager* inputManager, 48 | UIManager* uiManager, 49 | SceneManager* sceneManager) 50 | : Scene(renderer, inputManager, uiManager, sceneManager) 51 | { 52 | mRenderer.Clear(); 53 | mFrame = mRenderer.AddFrame(0, 0, 40, 30); 54 | mFrame->Clear(); 55 | 56 | try { 57 | mSingleMode = std::make_unique