|
1 | | -// |
2 | | -// Created by 김서윤 on 25. 5. 30. |
3 | | -// |
| 1 | +#include "GameLoop.hpp" |
| 2 | +#include "GameLogic.hpp" // isKingInCheck, findKing, isCheckmate, getPossibleMoves 사용 |
| 3 | + |
| 4 | +#include <iostream> // for std::cerr, std::cout |
| 5 | +#include <iomanip> // For std::setfill, std::setw |
| 6 | +#include <sstream> // For std::ostringstream |
| 7 | + |
| 8 | +// formatTime 함수 정의 |
| 9 | +std::string formatTime(sf::Time time) { |
| 10 | + int totalSeconds = static_cast<int>(time.asSeconds()); |
| 11 | + if (totalSeconds < 0) totalSeconds = 0; |
| 12 | + int minutes = totalSeconds / 60; |
| 13 | + int seconds = totalSeconds % 60; |
| 14 | + std::ostringstream oss; |
| 15 | + oss << std::setfill('0') << std::setw(2) << minutes << ":" << std::setfill('0') << std::setw(2) << seconds; |
| 16 | + return oss.str(); |
| 17 | +} |
| 18 | + |
| 19 | +// 게임 루프 함수 정의 |
| 20 | +void gameLoop( |
| 21 | + sf::RenderWindow& window, |
| 22 | + sf::Font& font, |
| 23 | + sf::RectangleShape& tile, |
| 24 | + sf::Color& lightColor, |
| 25 | + sf::Color& darkColor, |
| 26 | + sf::Color& checkedKingTileColor, |
| 27 | + sf::Text& chooseSidePromptText, |
| 28 | + sf::Text& messageText, |
| 29 | + sf::Text& whiteTimerText, |
| 30 | + sf::Text& blackTimerText, |
| 31 | + sf::RectangleShape& whiteStartButton, |
| 32 | + sf::Text& whiteStartText, |
| 33 | + sf::RectangleShape& blackStartButton, |
| 34 | + sf::Text& blackStartText, |
| 35 | + sf::RectangleShape& popupBackground, |
| 36 | + sf::Text& popupMessageText, |
| 37 | + sf::RectangleShape& homeButtonShape, |
| 38 | + sf::Text& homeButtonText, |
| 39 | + GameState& currentGameState, |
| 40 | + std::optional<sf::Vector2i>& selectedPiecePos, |
| 41 | + std::vector<sf::Vector2i>& possibleMoves, |
| 42 | + PieceColor& currentTurn, |
| 43 | + std::string& gameMessageStr, |
| 44 | + std::map<std::string, sf::Texture>& textures, // place_piece 람다 때문에 main에서 textures를 캡처하고, |
| 45 | + // actualResetGame이 textures를 사용하므로 gameLoop에는 직접 필요 없을 수 있으나, |
| 46 | + // 혹시 모를 확장성을 위해 전달 (현재는 actualResetGame이 main의 textures를 캡처) |
| 47 | + std::array<std::array<std::optional<Piece>, 8>, 8>& board_state, |
| 48 | + sf::Time& whiteTimeLeft, |
| 49 | + sf::Time& blackTimeLeft, |
| 50 | + sf::Clock& frameClock, |
| 51 | + std::function<void()> actualResetGame, |
| 52 | + float timerPadding |
| 53 | +) { |
| 54 | + // --- 메인 게임 루프 --- |
| 55 | + while (window.isOpen()) { |
| 56 | + sf::Time deltaTime = frameClock.restart(); |
| 57 | + bool kingIsCurrentlyChecked = false; |
| 58 | + sf::Vector2i checkedKingCurrentPos = {-1, -1}; |
| 59 | + |
| 60 | + // 게임 상태 업데이트 (시간, 체크/메이트, 메시지) |
| 61 | + if (currentGameState == GameState::Playing && currentTurn != PieceColor::None) { |
| 62 | + if (currentTurn == PieceColor::White) { |
| 63 | + if (whiteTimeLeft > sf::Time::Zero) whiteTimeLeft -= deltaTime; |
| 64 | + if (whiteTimeLeft <= sf::Time::Zero) { whiteTimeLeft = sf::Time::Zero; currentGameState = GameState::GameOver; gameMessageStr = "Black wins on time!"; } |
| 65 | + } else if (currentTurn == PieceColor::Black) { |
| 66 | + if (blackTimeLeft > sf::Time::Zero) blackTimeLeft -= deltaTime; |
| 67 | + if (blackTimeLeft <= sf::Time::Zero) { blackTimeLeft = sf::Time::Zero; currentGameState = GameState::GameOver; gameMessageStr = "White wins on time!"; } |
| 68 | + } |
| 69 | + if (currentGameState != GameState::GameOver) { |
| 70 | + kingIsCurrentlyChecked = isKingInCheck(board_state, currentTurn); |
| 71 | + if (kingIsCurrentlyChecked) { |
| 72 | + checkedKingCurrentPos = findKing(board_state, currentTurn); |
| 73 | + if (isCheckmate(board_state, currentTurn)) { |
| 74 | + currentGameState = GameState::GameOver; gameMessageStr = std::string((currentTurn == PieceColor::White) ? "Black" : "White") + " wins by Checkmate!"; |
| 75 | + } else { gameMessageStr = std::string((currentTurn == PieceColor::White) ? "White" : "Black") + " King is in Check!"; } |
| 76 | + } else { gameMessageStr = std::string((currentTurn == PieceColor::White) ? "White" : "Black") + " to move"; } |
| 77 | + } |
| 78 | + } else if (currentGameState == GameState::ChoosingPlayer) { |
| 79 | + gameMessageStr = ""; |
| 80 | + } |
| 81 | + |
| 82 | + // 타이머 텍스트 업데이트 |
| 83 | + whiteTimerText.setString("White: " + formatTime(whiteTimeLeft)); |
| 84 | + blackTimerText.setString("Black: " + formatTime(blackTimeLeft)); |
| 85 | + |
| 86 | + sf::FloatRect wt_bounds_loop = whiteTimerText.getLocalBounds(); |
| 87 | + whiteTimerText.setPosition({ BOARD_WIDTH + (BUTTON_PANEL_WIDTH - wt_bounds_loop.size.x) / 2.f - wt_bounds_loop.position.x, timerPadding - wt_bounds_loop.position.y}); |
| 88 | + sf::FloatRect bt_bounds_loop = blackTimerText.getLocalBounds(); |
| 89 | + blackTimerText.setPosition({ BOARD_WIDTH + (BUTTON_PANEL_WIDTH - bt_bounds_loop.size.x) / 2.f - bt_bounds_loop.position.x, whiteTimerText.getPosition().y + wt_bounds_loop.size.y + wt_bounds_loop.position.y + 5.f - bt_bounds_loop.position.y }); |
| 90 | + |
| 91 | + // 이벤트 처리 |
| 92 | + while (const auto event_opt = window.pollEvent()) { |
| 93 | + const sf::Event& event = *event_opt; |
| 94 | + if (event.is<sf::Event::Closed>()) window.close(); |
| 95 | + else if (const auto* keyPressed = event.getIf<sf::Event::KeyPressed>()) { |
| 96 | + if (keyPressed->scancode == sf::Keyboard::Scancode::Escape) window.close(); |
| 97 | + } else if (const auto* mouseButtonPressed = event.getIf<sf::Event::MouseButtonPressed>()) { |
| 98 | + if (mouseButtonPressed->button == sf::Mouse::Button::Left) { |
| 99 | + sf::Vector2i mousePos = mouseButtonPressed->position; |
| 100 | + |
| 101 | + if (currentGameState == GameState::ChoosingPlayer) { |
| 102 | + if (whiteStartButton.getGlobalBounds().contains(static_cast<sf::Vector2f>(mousePos))) { |
| 103 | + currentTurn = PieceColor::White; currentGameState = GameState::Playing; |
| 104 | + gameMessageStr = "White to move"; frameClock.restart(); |
| 105 | + } else if (blackStartButton.getGlobalBounds().contains(static_cast<sf::Vector2f>(mousePos))) { |
| 106 | + currentTurn = PieceColor::Black; currentGameState = GameState::Playing; |
| 107 | + gameMessageStr = "Black to move"; frameClock.restart(); |
| 108 | + } |
| 109 | + } else if (currentGameState == GameState::Playing) { |
| 110 | + int clickedCol = mousePos.x / TILE_SIZE; int clickedRow = mousePos.y / TILE_SIZE; |
| 111 | + if (clickedCol >=0 && clickedCol < 8 && clickedRow >=0 && clickedRow < 8) { |
| 112 | + bool moved = false; |
| 113 | + int fromR_local = -1, fromC_local = -1; |
| 114 | + |
| 115 | + if (selectedPiecePos.has_value()) { |
| 116 | + fromR_local = selectedPiecePos->y; |
| 117 | + fromC_local = selectedPiecePos->x; |
| 118 | + for (const auto& move_coord : possibleMoves) { |
| 119 | + if (move_coord.x == clickedCol && move_coord.y == clickedRow) { |
| 120 | + std::array<std::array<std::optional<Piece>, 8>, 8> tempBoard = board_state; |
| 121 | + std::optional<Piece> pieceToMoveOpt = tempBoard[fromR_local][fromC_local]; |
| 122 | + if (pieceToMoveOpt.has_value()){ |
| 123 | + tempBoard[clickedRow][clickedCol] = Piece(pieceToMoveOpt->type, pieceToMoveOpt->color, pieceToMoveOpt->sprite); |
| 124 | + tempBoard[fromR_local][fromC_local].reset(); |
| 125 | + } |
| 126 | + if (!isKingInCheck(tempBoard, currentTurn)) { |
| 127 | + std::optional<Piece> actualPieceToMoveOpt = board_state[fromR_local][fromC_local]; |
| 128 | + if(actualPieceToMoveOpt.has_value()){ |
| 129 | + board_state[clickedRow][clickedCol] = Piece(actualPieceToMoveOpt->type, actualPieceToMoveOpt->color, actualPieceToMoveOpt->sprite); |
| 130 | + board_state[fromR_local][fromC_local].reset(); |
| 131 | + } |
| 132 | + if (board_state[clickedRow][clickedCol].has_value()) { |
| 133 | + sf::Sprite& movedSprite = board_state[clickedRow][clickedCol]->sprite; |
| 134 | + sf::FloatRect spriteBounds = movedSprite.getGlobalBounds(); |
| 135 | + float x_offset = (static_cast<float>(TILE_SIZE) - spriteBounds.size.x) / 2.f; |
| 136 | + float y_offset = (static_cast<float>(TILE_SIZE) - spriteBounds.size.y) / 2.f; |
| 137 | + movedSprite.setPosition({clickedCol*static_cast<float>(TILE_SIZE)+x_offset, clickedRow*static_cast<float>(TILE_SIZE)+y_offset}); |
| 138 | + } |
| 139 | + moved = true; |
| 140 | + currentTurn = (currentTurn == PieceColor::White) ? PieceColor::Black : PieceColor::White; |
| 141 | + frameClock.restart(); |
| 142 | + break; |
| 143 | + } else gameMessageStr = "Invalid move: King would be in check!"; |
| 144 | + } |
| 145 | + } |
| 146 | + } |
| 147 | + if (moved) { |
| 148 | + selectedPiecePos.reset(); possibleMoves.clear(); |
| 149 | + } else { |
| 150 | + if (board_state[clickedRow][clickedCol].has_value() && board_state[clickedRow][clickedCol]->color == currentTurn) { |
| 151 | + if (selectedPiecePos.has_value() && selectedPiecePos->x == clickedCol && selectedPiecePos->y == clickedRow) { |
| 152 | + selectedPiecePos.reset(); possibleMoves.clear(); |
| 153 | + } else { |
| 154 | + selectedPiecePos = sf::Vector2i(clickedCol, clickedRow); |
| 155 | + possibleMoves = getPossibleMoves(board_state, clickedRow, clickedCol); |
| 156 | + |
| 157 | + std::vector<sf::Vector2i> valid_moves; |
| 158 | + for(const auto& p_move : possibleMoves){ |
| 159 | + std::array<std::array<std::optional<Piece>, 8>, 8> temp_board_check = board_state; |
| 160 | + std::optional<Piece> piece_to_sim = temp_board_check[clickedRow][clickedCol]; |
| 161 | + if(piece_to_sim.has_value()){ |
| 162 | + temp_board_check[p_move.y][p_move.x] = Piece(piece_to_sim->type, piece_to_sim->color, piece_to_sim->sprite); |
| 163 | + temp_board_check[clickedRow][clickedCol].reset(); |
| 164 | + if(!isKingInCheck(temp_board_check, currentTurn)){ |
| 165 | + valid_moves.push_back(p_move); |
| 166 | + } |
| 167 | + } |
| 168 | + } |
| 169 | + possibleMoves = valid_moves; |
| 170 | + } |
| 171 | + } else { |
| 172 | + selectedPiecePos.reset(); possibleMoves.clear(); |
| 173 | + } |
| 174 | + } |
| 175 | + } else { selectedPiecePos.reset(); possibleMoves.clear(); } |
| 176 | + } else if (currentGameState == GameState::GameOver) { |
| 177 | + if (homeButtonShape.getGlobalBounds().contains(static_cast<sf::Vector2f>(mousePos))) { |
| 178 | + actualResetGame(); |
| 179 | + } |
| 180 | + } |
| 181 | + } |
| 182 | + } |
| 183 | + } |
| 184 | + |
| 185 | + // 그리기 |
| 186 | + window.clear(sf::Color(240,240,240)); |
| 187 | + |
| 188 | + if (currentGameState == GameState::ChoosingPlayer) { |
| 189 | + window.draw(chooseSidePromptText); |
| 190 | + window.draw(whiteStartButton); window.draw(whiteStartText); |
| 191 | + window.draw(blackStartButton); window.draw(blackStartText); |
| 192 | + } else { // Playing or GameOver |
| 193 | + for (int r = 0; r < 8; ++r) for (int c = 0; c < 8; ++c) { |
| 194 | + bool isLight = (r + c) % 2 == 0; |
| 195 | + tile.setFillColor(isLight ? lightColor : darkColor); |
| 196 | + if (kingIsCurrentlyChecked && checkedKingCurrentPos.x == c && checkedKingCurrentPos.y == r) tile.setFillColor(checkedKingTileColor); |
| 197 | + else if (selectedPiecePos.has_value() && selectedPiecePos->x == c && selectedPiecePos->y == r) tile.setFillColor(sf::Color(255, 255, 0, 150)); |
| 198 | + else { |
| 199 | + for (const auto& move_coord : possibleMoves) { |
| 200 | + if (move_coord.x == c && move_coord.y == r) { |
| 201 | + if (board_state[r][c].has_value() && board_state[r][c]->color != currentTurn) tile.setFillColor(sf::Color(50, 150, 250, 150)); |
| 202 | + else tile.setFillColor(sf::Color(100, 250, 50, 150)); |
| 203 | + break; |
| 204 | + } |
| 205 | + } |
| 206 | + } |
| 207 | + tile.setPosition({c * static_cast<float>(TILE_SIZE), r * static_cast<float>(TILE_SIZE)}); |
| 208 | + window.draw(tile); |
| 209 | + } |
| 210 | + for (int r_idx = 0; r_idx < 8; ++r_idx) for (int c_idx = 0; c_idx < 8; ++c_idx) { |
| 211 | + if (board_state[r_idx][c_idx].has_value()) { |
| 212 | + // SFML 렌더링 시 const가 아닌 스프라이트가 필요할 수 있으므로, |
| 213 | + // board_state에서 Piece를 가져올 때 value()로 복사본을 사용하거나, |
| 214 | + // GameLogic에 const가 아닌 접근자를 만들거나, |
| 215 | + // UIManager가 board_state의 복사본을 가지도록 설계 변경 필요. |
| 216 | + // 여기서는 가장 간단하게 복사본을 사용합니다. |
| 217 | + Piece piece_to_draw = board_state[r_idx][c_idx].value(); |
| 218 | + bool isLosingKing = (currentGameState == GameState::GameOver && piece_to_draw.type == PieceType::King && piece_to_draw.color == currentTurn); |
| 219 | + if (isLosingKing) piece_to_draw.sprite.setColor(sf::Color::Red); |
| 220 | + else piece_to_draw.sprite.setColor(sf::Color::White); // 항상 기본 색상으로 리셋 |
| 221 | + window.draw(piece_to_draw.sprite); |
| 222 | + } |
| 223 | + } |
| 224 | + window.draw(whiteTimerText); window.draw(blackTimerText); |
| 225 | + if (!gameMessageStr.empty()) { |
| 226 | + messageText.setString(gameMessageStr); |
| 227 | + sf::FloatRect gameMsgLocalBounds = messageText.getLocalBounds(); |
| 228 | + messageText.setPosition({ BOARD_WIDTH + (BUTTON_PANEL_WIDTH - gameMsgLocalBounds.size.x) / 2.f - gameMsgLocalBounds.position.x, (WINDOW_HEIGHT - gameMsgLocalBounds.size.y) / 2.f - gameMsgLocalBounds.position.y }); |
| 229 | + messageText.setFillColor(sf::Color::Black); messageText.setStyle(sf::Text::Regular); |
| 230 | + if(currentGameState == GameState::GameOver || gameMessageStr.find("wins by") != std::string::npos ) { messageText.setFillColor(sf::Color::Red); messageText.setStyle(sf::Text::Bold); } |
| 231 | + else if (gameMessageStr.find("Check!") != std::string::npos ) { messageText.setFillColor(sf::Color(200,0,0)); messageText.setStyle(sf::Text::Bold); } |
| 232 | + window.draw(messageText); |
| 233 | + } |
| 234 | + |
| 235 | + if (currentGameState == GameState::GameOver) { |
| 236 | + window.draw(popupBackground); |
| 237 | + popupMessageText.setString(gameMessageStr); |
| 238 | + sf::FloatRect popupMsgBounds = popupMessageText.getLocalBounds(); |
| 239 | + popupMessageText.setPosition({ popupBackground.getPosition().x - popupMsgBounds.size.x / 2.f - popupMsgBounds.position.x, popupBackground.getPosition().y - popupBackground.getSize().y / 2.f + 30.f - popupMsgBounds.position.y }); |
| 240 | + window.draw(popupMessageText); |
| 241 | + homeButtonShape.setPosition({ popupBackground.getPosition().x, popupBackground.getPosition().y + popupBackground.getSize().y / 2.f - 40.f - homeButtonShape.getSize().y / 2.f }); |
| 242 | + window.draw(homeButtonShape); |
| 243 | + sf::FloatRect homeButtonTextBounds = homeButtonText.getLocalBounds(); // popupMessageText의 바운드를 사용하던 것을 수정 |
| 244 | + homeButtonText.setPosition({ homeButtonShape.getPosition().x - homeButtonTextBounds.size.x / 2.f - homeButtonTextBounds.position.x, homeButtonShape.getPosition().y - homeButtonTextBounds.size.y / 2.f - homeButtonTextBounds.position.y }); |
| 245 | + window.draw(homeButtonText); |
| 246 | + } |
| 247 | + } |
| 248 | + window.display(); |
| 249 | + } |
| 250 | +} |
0 commit comments