commit 73ae9d13a9d8b8e571dace41e9aec1f0f617974d Author: rattatwinko Date: Thu May 8 20:25:01 2025 +0200 initial diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/Minesweeper.iml b/.idea/Minesweeper.iml new file mode 100644 index 0000000..f08604b --- /dev/null +++ b/.idea/Minesweeper.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/editor.xml b/.idea/editor.xml new file mode 100644 index 0000000..e54e87a --- /dev/null +++ b/.idea/editor.xml @@ -0,0 +1,102 @@ + + + + + \ No newline at end of file diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 0000000..e3298aa --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0b76fe5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..698598d --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3de07d0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.15) +project(Minesweeper) + +# Set C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Find SFML package +find_package(SFML 2.5 COMPONENTS graphics window system REQUIRED) + +# Create assets directory if it doesn't exist +if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/assets) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/assets) +endif() + +# Add executable +add_executable(Minesweeper + src/main.cpp +) + +# Link SFML libraries +target_link_libraries(Minesweeper + sfml-graphics + sfml-window + sfml-system +) + +# Copy assets to build directory (for out-of-source builds) +add_custom_command(TARGET Minesweeper POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/assets + $/assets + COMMENT "Copying assets to build directory" +) + +# Install target (optional) +install(TARGETS Minesweeper + RUNTIME DESTINATION bin +) + +# Install assets (optional) +install(DIRECTORY assets/ + DESTINATION share/Minesweeper +) \ No newline at end of file diff --git a/assets/digital.ttf b/assets/digital.ttf new file mode 100644 index 0000000..bafc0ca Binary files /dev/null and b/assets/digital.ttf differ diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1384e52 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,1332 @@ +// +// Created by rattatwinko on 5/8/25. +// +// +// Created by rattatwinko on 5/8/25. +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Forward declarations for asset creation functions +void createAssets(); +void createHiddenCellTexture(); +void createRevealedCellTexture(); +void createMineTexture(); +void createFlagTexture(); +void createQuestionTexture(); +void createExplodedTexture(); +void createDigitTexture(int digit); +void createGearTexture(); +void createScrollTexture(); +void createSmileyTexture(); +void createSadTexture(); +void createCoolTexture(); +void createDigitalFont(); + +class main { +public: + enum Difficulty { + BEGINNER, + INTERMEDIATE, + EXPERT, + CUSTOM + }; + + enum CellState { + HIDDEN, + REVEALED, + FLAGGED, + QUESTIONED + }; + + struct Cell { + bool isMine; + bool isExploded; + CellState state; + int adjacentMines; + + Cell() : isMine(false), isExploded(false), state(HIDDEN), adjacentMines(0) {} + }; + + main() { + initGame(INTERMEDIATE); // Default to intermediate difficulty + } + + void initGame(Difficulty diff) { + gameOver = false; + victory = false; + firstClick = true; + flagCount = 0; + startTime = 0; + elapsedTime = 0; + timerStarted = false; + + switch (diff) { + case BEGINNER: + width = 9; + height = 9; + mineCount = 10; + break; + case INTERMEDIATE: + width = 16; + height = 16; + mineCount = 40; + break; + case EXPERT: + width = 30; + height = 16; + mineCount = 99; + break; + case CUSTOM: + // Custom values should be set before calling initGame + break; + } + + // Initialize grid + grid.clear(); + grid.resize(height, std::vector(width)); + + // Set window size based on grid dimensions + cellSize = 32; // pixels + boardWidth = width * cellSize; + boardHeight = height * cellSize; + headerHeight = 60; // For the header panel with counters + + // Load textures and setup resources + loadResources(); + } + + void setCustomDifficulty(int w, int h, int mines) { + width = w; + height = h; + mineCount = std::min(mines, w * h - 9); // Ensure there are at least 9 safe cells for first click + initGame(CUSTOM); + } + + void run() { + sf::RenderWindow window(sf::VideoMode(boardWidth, boardHeight + headerHeight), "Minesweeper", sf::Style::Close); + window.setFramerateLimit(60); + + sf::Clock clock; + + while (window.isOpen()) { + sf::Event event; + while (window.pollEvent(event)) { + if (event.type == sf::Event::Closed) { + window.close(); + } + else if (event.type == sf::Event::MouseButtonPressed) { + if (!gameOver && !victory) { + sf::Vector2i mousePos = sf::Mouse::getPosition(window); + int x = mousePos.x / cellSize; + int y = (mousePos.y - headerHeight) / cellSize; + + if (mousePos.y >= headerHeight && x >= 0 && x < width && y >= 0 && y < height) { + if (event.mouseButton.button == sf::Mouse::Left) { + handleLeftClick(x, y); + } + else if (event.mouseButton.button == sf::Mouse::Right) { + handleRightClick(x, y); + } + } + else if (mousePos.y < headerHeight) { + // Check if reset button (smiley face) is clicked + int smileX = boardWidth / 2 - 16; + if (mousePos.x >= smileX && mousePos.x < smileX + 32 && + mousePos.y >= 14 && mousePos.y < 46) { + initGame(INTERMEDIATE); // Reset game with current difficulty + } + + // Check if settings button is clicked + if (mousePos.x >= 8 && mousePos.x < 40 && + mousePos.y >= 14 && mousePos.y < 46) { + showDifficultyMenu(window); + } + + // Check if light bulb button is clicked (hint) + if (mousePos.x >= boardWidth - 40 && mousePos.x < boardWidth - 8 && + mousePos.y >= 14 && mousePos.y < 46) { + provideHint(); + } + } + } + else { + // If game is over, check if reset button is clicked + sf::Vector2i mousePos = sf::Mouse::getPosition(window); + int smileX = boardWidth / 2 - 16; + if (mousePos.y < headerHeight && + mousePos.x >= smileX && mousePos.x < smileX + 32 && + mousePos.y >= 14 && mousePos.y < 46) { + initGame(INTERMEDIATE); // Reset game with current difficulty + } + + // Check if settings button is clicked + if (mousePos.x >= 8 && mousePos.x < 40 && + mousePos.y >= 14 && mousePos.y < 46) { + showDifficultyMenu(window); + } + } + } + } + + // Update timer + if (timerStarted && !gameOver && !victory) { + elapsedTime = static_cast(clock.getElapsedTime().asSeconds() - startTime); + if (elapsedTime > 999) elapsedTime = 999; // Cap at 999 seconds + } + + // Draw everything + window.clear(sf::Color(192, 192, 192)); // Light gray background + drawBoard(window); + drawHeader(window); + window.display(); + } + } + +private: + int width, height; + int mineCount, flagCount; + int cellSize; + int boardWidth, boardHeight, headerHeight; + bool gameOver, victory, firstClick, timerStarted; + int startTime, elapsedTime; + std::vector> grid; + + // Textures and sprites + sf::Texture hiddenTexture, revealedTexture, mineTexture, flagTexture, questionTexture, explodedTexture; + sf::Texture digitTextures[10]; // 0-9 digits + sf::Texture headerTextures[5]; // gear, scroll, smiley, sad, cool, hint/bulb + sf::Font font; + + void loadResources() { + // Cell textures + hiddenTexture.loadFromFile("assets/hidden.png"); + revealedTexture.loadFromFile("assets/revealed.png"); + mineTexture.loadFromFile("assets/mine.png"); + flagTexture.loadFromFile("assets/flag.png"); + questionTexture.loadFromFile("assets/question.png"); + explodedTexture.loadFromFile("assets/exploded.png"); + + // Digit textures for numbers 1-8 on cells + font.loadFromFile("assets/digital.ttf"); + + // Header textures + headerTextures[0].loadFromFile("assets/gear.png"); // Settings + headerTextures[1].loadFromFile("assets/scroll.png"); // Score/mines + headerTextures[2].loadFromFile("assets/smiley.png"); // Normal face + headerTextures[3].loadFromFile("assets/sad.png"); // Sad face (loss) + headerTextures[4].loadFromFile("assets/cool.png"); // Cool face (win) + + // Digit textures (for counters) + for (int i = 0; i < 10; i++) { + std::string filename = "assets/digit" + std::to_string(i) + ".png"; + digitTextures[i].loadFromFile(filename); + } + } + + void placeMines(int firstX, int firstY) { + std::mt19937 rng(static_cast(std::time(nullptr))); + std::uniform_int_distribution distX(0, width - 1); + std::uniform_int_distribution distY(0, height - 1); + + int placedMines = 0; + while (placedMines < mineCount) { + int x = distX(rng); + int y = distY(rng); + + // Ensure first click and surrounding cells are safe + if (std::abs(x - firstX) <= 1 && std::abs(y - firstY) <= 1) { + continue; + } + + if (!grid[y][x].isMine) { + grid[y][x].isMine = true; + placedMines++; + } + } + + // Calculate adjacent mines for each cell + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (!grid[y][x].isMine) { + grid[y][x].adjacentMines = countAdjacentMines(x, y); + } + } + } + } + + int countAdjacentMines(int x, int y) { + int count = 0; + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + int nx = x + dx; + int ny = y + dy; + if (nx >= 0 && nx < width && ny >= 0 && ny < height && grid[ny][nx].isMine) { + count++; + } + } + } + return count; + } + + void revealCell(int x, int y) { + // Make sure we're in bounds + if (x < 0 || x >= width || y < 0 || y >= height) { + return; + } + + Cell& cell = grid[y][x]; + + // Only reveal hidden cells + if (cell.state != HIDDEN) { + return; + } + + // Start timer on first reveal + if (firstClick) { + sf::Clock clock; + startTime = clock.getElapsedTime().asSeconds(); + timerStarted = true; + firstClick = false; + placeMines(x, y); + } + + cell.state = REVEALED; + + // If it's a mine, game over + if (cell.isMine) { + cell.isExploded = true; + revealAllMines(); + gameOver = true; + return; + } + + // If no adjacent mines, reveal adjacent cells + if (cell.adjacentMines == 0) { + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + if (dx == 0 && dy == 0) continue; + revealCell(x + dx, y + dy); + } + } + } + + // Check for victory + checkVictory(); + } + + void handleLeftClick(int x, int y) { + Cell& cell = grid[y][x]; + + if (cell.state == HIDDEN || cell.state == QUESTIONED) { + revealCell(x, y); + } + else if (cell.state == REVEALED && cell.adjacentMines > 0) { + // Check for chord (middle-click behavior) + int flagCount = 0; + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + int nx = x + dx; + int ny = y + dy; + if (nx >= 0 && nx < width && ny >= 0 && ny < height && + grid[ny][nx].state == FLAGGED) { + flagCount++; + } + } + } + + // If flagged neighbors match adjacent mines, reveal all non-flagged neighbors + if (flagCount == cell.adjacentMines) { + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + int nx = x + dx; + int ny = y + dy; + if (nx >= 0 && nx < width && ny >= 0 && ny < height && + grid[ny][nx].state != FLAGGED && grid[ny][nx].state != REVEALED) { + revealCell(nx, ny); + } + } + } + } + } + } + + void handleRightClick(int x, int y) { + Cell& cell = grid[y][x]; + + if (cell.state == HIDDEN) { + cell.state = FLAGGED; + flagCount++; + } + else if (cell.state == FLAGGED) { + cell.state = QUESTIONED; + flagCount--; + } + else if (cell.state == QUESTIONED) { + cell.state = HIDDEN; + } + } + + void revealAllMines() { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (grid[y][x].isMine && grid[y][x].state != FLAGGED) { + grid[y][x].state = REVEALED; + } + // Incorrect flags (flagged non-mines) + else if (!grid[y][x].isMine && grid[y][x].state == FLAGGED) { + grid[y][x].state = FLAGGED; // Keep them flagged to show mistakes + } + } + } + } + + void checkVictory() { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + const Cell& cell = grid[y][x]; + if (!cell.isMine && cell.state != REVEALED) { + return; // There's still a non-mine cell that's not revealed + } + } + } + + // All non-mine cells are revealed, victory! + victory = true; + + // Flag all mines automatically + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (grid[y][x].isMine && grid[y][x].state != FLAGGED) { + grid[y][x].state = FLAGGED; + } + } + } + flagCount = mineCount; + } + + void provideHint() { + if (gameOver || victory) return; + + // Try to find a safe cell to reveal + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Cell& cell = grid[y][x]; + if (!cell.isMine && cell.state == HIDDEN) { + revealCell(x, y); + return; + } + } + } + } + + void drawBoard(sf::RenderWindow& window) { + sf::Sprite sprite; + sf::Text text; + text.setFont(font); + text.setCharacterSize(24); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + const Cell& cell = grid[y][x]; + int posX = x * cellSize; + int posY = y * cellSize + headerHeight; + + // Draw cell background + sprite.setPosition(static_cast(posX), static_cast(posY)); + + if (cell.state == REVEALED) { + sprite.setTexture(revealedTexture); + window.draw(sprite); + + if (cell.isMine) { + if (cell.isExploded) { + sprite.setTexture(explodedTexture); + } else { + sprite.setTexture(mineTexture); + } + window.draw(sprite); + } + else if (cell.adjacentMines > 0) { + // Draw number for adjacent mines + text.setString(std::to_string(cell.adjacentMines)); + + // Set color based on the number + switch (cell.adjacentMines) { + case 1: text.setFillColor(sf::Color::Blue); break; + case 2: text.setFillColor(sf::Color::Green); break; + case 3: text.setFillColor(sf::Color::Red); break; + case 4: text.setFillColor(sf::Color(128, 0, 128)); break; // Purple + case 5: text.setFillColor(sf::Color(128, 0, 0)); break; // Maroon + case 6: text.setFillColor(sf::Color(64, 224, 208)); break; // Turquoise + case 7: text.setFillColor(sf::Color::Black); break; + case 8: text.setFillColor(sf::Color(128, 128, 128)); break; // Gray + default: text.setFillColor(sf::Color::Black); + } + + // Center text in cell + sf::FloatRect textRect = text.getLocalBounds(); + text.setPosition( + static_cast(posX + (cellSize - textRect.width) / 2 - 4), + static_cast(posY + (cellSize - textRect.height) / 2 - 8) + ); + + window.draw(text); + } + } + else { + sprite.setTexture(hiddenTexture); + window.draw(sprite); + + if (cell.state == FLAGGED) { + sprite.setTexture(flagTexture); + window.draw(sprite); + } + else if (cell.state == QUESTIONED) { + sprite.setTexture(questionTexture); + window.draw(sprite); + } + } + } + } + } + + void drawHeader(sf::RenderWindow& window) { + // Draw background for header + sf::RectangleShape headerBg(sf::Vector2f(static_cast(boardWidth), static_cast(headerHeight))); + headerBg.setFillColor(sf::Color(192, 192, 192)); // Same gray as the rest + headerBg.setOutlineColor(sf::Color(128, 128, 128)); + headerBg.setOutlineThickness(2); + window.draw(headerBg); + + // Draw mine counter (left side) + drawCounter(window, mineCount - flagCount, 50, 16); + + // Draw timer (right side) + drawCounter(window, elapsedTime, boardWidth - 100, 16); + + // Draw settings button (gear icon) + sf::Sprite settingsSprite; + settingsSprite.setTexture(headerTextures[0]); + settingsSprite.setPosition(8, 14); + window.draw(settingsSprite); + + // Draw hint button (light bulb) + sf::Sprite hintSprite; + hintSprite.setTexture(headerTextures[1]); // Using scroll texture for now + hintSprite.setPosition(boardWidth - 40, 14); + window.draw(hintSprite); + + // Draw face button (center) + sf::Sprite faceSprite; + if (gameOver) { + faceSprite.setTexture(headerTextures[3]); // Sad face + } else if (victory) { + faceSprite.setTexture(headerTextures[4]); // Cool face + } else { + faceSprite.setTexture(headerTextures[2]); // Normal smiley + } + faceSprite.setPosition(boardWidth / 2 - 16, 14); + window.draw(faceSprite); + } + + void drawCounter(sf::RenderWindow& window, int value, int x, int y) { + // Ensure value is within 3-digit display range + if (value < 0) value = 0; + if (value > 999) value = 999; + + // Convert to string with leading zeros + std::stringstream ss; + ss << std::setw(3) << std::setfill('0') << value; + std::string valueStr = ss.str(); + + // Draw each digit + for (int i = 0; i < 3; i++) { + int digit = valueStr[i] - '0'; + sf::Sprite digitSprite; + digitSprite.setTexture(digitTextures[digit]); + digitSprite.setPosition(static_cast(x + i * 24), static_cast(y)); + window.draw(digitSprite); + } + } + + void showDifficultyMenu(sf::RenderWindow& window) { + sf::RenderWindow menuWindow(sf::VideoMode(300, 200), "Difficulty Settings", sf::Style::Close); + menuWindow.setFramerateLimit(60); + + // Create buttons for different difficulties + sf::Font font; + font.loadFromFile("assets/digital.ttf"); + + sf::Text titleText("Select Difficulty", font, 20); + titleText.setPosition(80, 10); + + sf::RectangleShape beginnerButton(sf::Vector2f(200, 30)); + beginnerButton.setPosition(50, 40); + beginnerButton.setFillColor(sf::Color(220, 220, 220)); + sf::Text beginnerText("Beginner (9x9, 10 mines)", font, 16); + beginnerText.setPosition(55, 45); + + sf::RectangleShape intermediateButton(sf::Vector2f(200, 30)); + intermediateButton.setPosition(50, 80); + intermediateButton.setFillColor(sf::Color(220, 220, 220)); + sf::Text intermediateText("Intermediate (16x16, 40 mines)", font, 16); + intermediateText.setPosition(55, 85); + + sf::RectangleShape expertButton(sf::Vector2f(200, 30)); + expertButton.setPosition(50, 120); + expertButton.setFillColor(sf::Color(220, 220, 220)); + sf::Text expertText("Expert (30x16, 99 mines)", font, 16); + expertText.setPosition(55, 125); + + sf::RectangleShape customButton(sf::Vector2f(200, 30)); + customButton.setPosition(50, 160); + customButton.setFillColor(sf::Color(220, 220, 220)); + sf::Text customText("Custom", font, 16); + customText.setPosition(55, 165); + + bool menuActive = true; + while (menuActive && menuWindow.isOpen()) { + sf::Event event; + while (menuWindow.pollEvent(event)) { + if (event.type == sf::Event::Closed) { + menuWindow.close(); + menuActive = false; + } + else if (event.type == sf::Event::MouseButtonPressed) { + sf::Vector2i mousePos = sf::Mouse::getPosition(menuWindow); + + // Check if any button is clicked + if (beginnerButton.getGlobalBounds().contains(static_cast(mousePos.x), static_cast(mousePos.y))) { + initGame(BEGINNER); + menuWindow.close(); + menuActive = false; + } + else if (intermediateButton.getGlobalBounds().contains(static_cast(mousePos.x), static_cast(mousePos.y))) { + initGame(INTERMEDIATE); + menuWindow.close(); + menuActive = false; + } + else if (expertButton.getGlobalBounds().contains(static_cast(mousePos.x), static_cast(mousePos.y))) { + initGame(EXPERT); + menuWindow.close(); + menuActive = false; + } + else if (customButton.getGlobalBounds().contains(static_cast(mousePos.x), static_cast(mousePos.y))) { + showCustomDifficultyMenu(menuWindow); + menuWindow.close(); + menuActive = false; + } + } + } + + menuWindow.clear(sf::Color(192, 192, 192)); + menuWindow.draw(titleText); + menuWindow.draw(beginnerButton); + menuWindow.draw(beginnerText); + menuWindow.draw(intermediateButton); + menuWindow.draw(intermediateText); + menuWindow.draw(expertButton); + menuWindow.draw(expertText); + menuWindow.draw(customButton); + menuWindow.draw(customText); + menuWindow.display(); + } + } + +void showCustomDifficultyMenu(sf::RenderWindow& parentWindow) { + sf::RenderWindow customWindow(sf::VideoMode(300, 250), "Custom Settings", sf::Style::Close); + customWindow.setFramerateLimit(60); + + sf::Font font; + if (!font.loadFromFile("assets/digital.ttf")) { + std::cerr << "Failed to load font\n"; + return; + } + + sf::Text titleText("Custom Settings", font, 20); + titleText.setPosition(80, 10); + + // Input fields for width, height, and mines + int customWidth = 16, customHeight = 16, customMines = 40; + + // Width input + sf::Text widthLabel("Width (8-50):", font, 16); + widthLabel.setPosition(20, 50); + sf::RectangleShape widthInput(sf::Vector2f(80, 25)); + widthInput.setPosition(200, 50); + widthInput.setFillColor(sf::Color::White); + widthInput.setOutlineColor(sf::Color::Black); + widthInput.setOutlineThickness(1); + sf::Text widthText(std::to_string(customWidth), font, 16); + widthText.setPosition(205, 52); + widthText.setFillColor(sf::Color::Black); + + // Height input + sf::Text heightLabel("Height (8-30):", font, 16); + heightLabel.setPosition(20, 90); + sf::RectangleShape heightInput(sf::Vector2f(80, 25)); + heightInput.setPosition(200, 90); + heightInput.setFillColor(sf::Color::White); + heightInput.setOutlineColor(sf::Color::Black); + heightInput.setOutlineThickness(1); + sf::Text heightText(std::to_string(customHeight), font, 16); + heightText.setPosition(205, 92); + heightText.setFillColor(sf::Color::Black); + + // Mines input + sf::Text minesLabel("Mines (10-500):", font, 16); + minesLabel.setPosition(20, 130); + sf::RectangleShape minesInput(sf::Vector2f(80, 25)); + minesInput.setPosition(200, 130); + minesInput.setFillColor(sf::Color::White); + minesInput.setOutlineColor(sf::Color::Black); + minesInput.setOutlineThickness(1); + sf::Text minesText(std::to_string(customMines), font, 16); + minesText.setPosition(205, 132); + minesText.setFillColor(sf::Color::Black); + + // OK and Cancel buttons + sf::RectangleShape okButton(sf::Vector2f(100, 30)); + okButton.setPosition(40, 180); + okButton.setFillColor(sf::Color(220, 220, 220)); + sf::Text okText("OK", font, 16); + okText.setPosition(80, 185); + okText.setFillColor(sf::Color::Black); + + sf::RectangleShape cancelButton(sf::Vector2f(100, 30)); + cancelButton.setPosition(160, 180); + cancelButton.setFillColor(sf::Color(220, 220, 220)); + sf::Text cancelText("Cancel", font, 16); + cancelText.setPosition(180, 185); + cancelText.setFillColor(sf::Color::Black); + + enum InputField { NONE, WIDTH, HEIGHT, MINES } activeField = NONE; + + while (customWindow.isOpen()) { + sf::Event event; + while (customWindow.pollEvent(event)) { + if (event.type == sf::Event::Closed) { + customWindow.close(); + } + else if (event.type == sf::Event::MouseButtonPressed) { + sf::Vector2i mousePos = sf::Mouse::getPosition(customWindow); + + // Check which input field is clicked + if (widthInput.getGlobalBounds().contains(static_cast(mousePos.x), static_cast(mousePos.y))) { + activeField = WIDTH; + widthInput.setOutlineColor(sf::Color::Blue); + heightInput.setOutlineColor(sf::Color::Black); + minesInput.setOutlineColor(sf::Color::Black); + } + else if (heightInput.getGlobalBounds().contains(static_cast(mousePos.x), static_cast(mousePos.y))) { + activeField = HEIGHT; + widthInput.setOutlineColor(sf::Color::Black); + heightInput.setOutlineColor(sf::Color::Blue); + minesInput.setOutlineColor(sf::Color::Black); + } + else if (minesInput.getGlobalBounds().contains(static_cast(mousePos.x), static_cast(mousePos.y))) { + activeField = MINES; + widthInput.setOutlineColor(sf::Color::Black); + heightInput.setOutlineColor(sf::Color::Black); + minesInput.setOutlineColor(sf::Color::Blue); + } + else if (okButton.getGlobalBounds().contains(static_cast(mousePos.x), static_cast(mousePos.y))) { + // Validate inputs + customWidth = std::max(8, std::min(50, customWidth)); + customHeight = std::max(8, std::min(30, customHeight)); + int maxMines = customWidth * customHeight * 0.9; // Maximum 90% of cells can be mines + customMines = std::max(10, std::min(maxMines, customMines)); + + setCustomDifficulty(customWidth, customHeight, customMines); + customWindow.close(); + } + else if (cancelButton.getGlobalBounds().contains(static_cast(mousePos.x), static_cast(mousePos.y))) { + customWindow.close(); + } + else { + activeField = NONE; + widthInput.setOutlineColor(sf::Color::Black); + heightInput.setOutlineColor(sf::Color::Black); + minesInput.setOutlineColor(sf::Color::Black); + } + } + else if (event.type == sf::Event::TextEntered) { + if (activeField != NONE) { + std::string currentValue; + int* targetValue = nullptr; + sf::Text* targetText = nullptr; + + // Determine which field we're editing + switch (activeField) { + case WIDTH: + currentValue = widthText.getString().toAnsiString(); + targetValue = &customWidth; + targetText = &widthText; + break; + case HEIGHT: + currentValue = heightText.getString().toAnsiString(); + targetValue = &customHeight; + targetText = &heightText; + break; + case MINES: + currentValue = minesText.getString().toAnsiString(); + targetValue = &customMines; + targetText = &minesText; + break; + default: + break; + } + + if (event.text.unicode >= '0' && event.text.unicode <= '9') { + // Handle numeric input + if (currentValue.length() < 3) { + currentValue += static_cast(event.text.unicode); + targetText->setString(currentValue); + *targetValue = std::stoi(currentValue); + } + } + else if (event.text.unicode == 8) { // Backspace + if (!currentValue.empty()) { + currentValue.pop_back(); + if (currentValue.empty()) { + currentValue = "0"; + } + targetText->setString(currentValue); + *targetValue = std::stoi(currentValue); + } + } + } + } + } + } + + customWindow.clear(sf::Color(192, 192, 192)); + customWindow.draw(titleText); + customWindow.draw(widthLabel); + customWindow.draw(widthInput); + customWindow.draw(widthText); + customWindow.draw(heightLabel); + customWindow.draw(heightInput); + customWindow.draw(heightText); + customWindow.draw(minesLabel); + customWindow.draw(minesInput); + customWindow.draw(minesText); + customWindow.draw(okButton); + customWindow.draw(okText); + customWindow.draw(cancelButton); + customWindow.draw(cancelText); + customWindow.display(); + } +}; +int main() { + // Create assets directory if it doesn't exist + #ifdef _WIN32 + system("if not exist assets mkdir assets"); + #else + system("mkdir -p assets"); + #endif + + // Create asset files + createAssets(); + + class main game; + game.run(); + + return 0; +} + +// Function to create asset files programmatically +void createAssets() { + // Create a basic pixel font for digits + sf::Font font; + font.loadFromFile("assets/digital.ttf"); + + // Create cell textures + createHiddenCellTexture(); + createRevealedCellTexture(); + createMineTexture(); + createFlagTexture(); + createQuestionTexture(); + createExplodedTexture(); + + // Create digit textures for the counter + for (int i = 0; i < 10; i++) { + createDigitTexture(i); + } + + // Create header icons + createGearTexture(); + createScrollTexture(); + createSmileyTexture(); + createSadTexture(); + createCoolTexture(); +} + +void createHiddenCellTexture() { + sf::RenderTexture texture; + texture.create(32, 32); + texture.clear(sf::Color(192, 192, 192)); // Light gray + + // Draw 3D effect + sf::ConvexShape shape; + shape.setPointCount(5); + shape.setPoint(0, sf::Vector2f(0, 0)); + shape.setPoint(1, sf::Vector2f(32, 0)); + shape.setPoint(2, sf::Vector2f(32, 32)); + shape.setPoint(3, sf::Vector2f(0, 32)); + shape.setPoint(4, sf::Vector2f(0, 0)); + + // Top and left edges (light) + sf::RectangleShape topEdge(sf::Vector2f(30, 2)); + topEdge.setPosition(0, 0); + topEdge.setFillColor(sf::Color::White); + texture.draw(topEdge); + + sf::RectangleShape leftEdge(sf::Vector2f(2, 30)); + leftEdge.setPosition(0, 0); + leftEdge.setFillColor(sf::Color::White); + texture.draw(leftEdge); + + // Bottom and right edges (dark) + sf::RectangleShape bottomEdge(sf::Vector2f(32, 2)); + bottomEdge.setPosition(0, 30); + bottomEdge.setFillColor(sf::Color(128, 128, 128)); + texture.draw(bottomEdge); + + sf::RectangleShape rightEdge(sf::Vector2f(2, 32)); + rightEdge.setPosition(30, 0); + rightEdge.setFillColor(sf::Color(128, 128, 128)); + texture.draw(rightEdge); + + texture.display(); + texture.getTexture().copyToImage().saveToFile("assets/hidden.png"); +} + +void createRevealedCellTexture() { + sf::RenderTexture texture; + texture.create(32, 32); + texture.clear(sf::Color(192, 192, 192)); // Light gray + + // Draw a simple border + sf::RectangleShape border(sf::Vector2f(32, 32)); + border.setFillColor(sf::Color::Transparent); + border.setOutlineColor(sf::Color(128, 128, 128)); + border.setOutlineThickness(1); + texture.draw(border); + + texture.display(); + texture.getTexture().copyToImage().saveToFile("assets/revealed.png"); +} + +void createMineTexture() { + sf::RenderTexture texture; + texture.create(32, 32); + texture.clear(sf::Color::Transparent); + + // Draw a circle for the mine + sf::CircleShape mine(10); + mine.setPosition(6, 6); + mine.setFillColor(sf::Color::Black); + texture.draw(mine); + + // Draw spikes + sf::RectangleShape hSpike(sf::Vector2f(24, 4)); + hSpike.setFillColor(sf::Color::Black); + hSpike.setPosition(4, 14); + texture.draw(hSpike); + + sf::RectangleShape vSpike(sf::Vector2f(4, 24)); + vSpike.setFillColor(sf::Color::Black); + vSpike.setPosition(14, 4); + texture.draw(vSpike); + + // Draw diagonal spikes + sf::RectangleShape d1Spike(sf::Vector2f(16, 4)); + d1Spike.setFillColor(sf::Color::Black); + d1Spike.setPosition(6, 6); + d1Spike.setRotation(45); + texture.draw(d1Spike); + + sf::RectangleShape d2Spike(sf::Vector2f(16, 4)); + d2Spike.setFillColor(sf::Color::Black); + d2Spike.setPosition(22, 6); + d2Spike.setRotation(135); + texture.draw(d2Spike); + + // Draw highlight + sf::CircleShape highlight(3); + highlight.setPosition(10, 10); + highlight.setFillColor(sf::Color::White); + texture.draw(highlight); + + texture.display(); + texture.getTexture().copyToImage().saveToFile("assets/mine.png"); +} + +void createFlagTexture() { + sf::RenderTexture texture; + texture.create(32, 32); + texture.clear(sf::Color::Transparent); + + // Draw flagpole + sf::RectangleShape pole(sf::Vector2f(2, 16)); + pole.setPosition(15, 8); + pole.setFillColor(sf::Color::Black); + texture.draw(pole); + + // Draw flag + sf::ConvexShape flag; + flag.setPointCount(3); + flag.setPoint(0, sf::Vector2f(17, 8)); + flag.setPoint(1, sf::Vector2f(17, 16)); + flag.setPoint(2, sf::Vector2f(24, 12)); + flag.setFillColor(sf::Color::Red); + texture.draw(flag); + + // Draw base + sf::RectangleShape base(sf::Vector2f(10, 4)); + base.setPosition(11, 24); + base.setFillColor(sf::Color::Black); + texture.draw(base); + + texture.display(); + texture.getTexture().copyToImage().saveToFile("assets/flag.png"); +} + +void createQuestionTexture() { + sf::RenderTexture texture; + texture.create(32, 32); + texture.clear(sf::Color::Transparent); + + // Create font for question mark + sf::Font font; + if (!font.loadFromFile("assets/digital.ttf")) { + // Create a simple font file + createDigitalFont(); + font.loadFromFile("assets/digital.ttf"); + } + + // Draw question mark + sf::Text text("?", font, 24); + text.setPosition(10, 4); + text.setFillColor(sf::Color::Blue); + texture.draw(text); + + texture.display(); + texture.getTexture().copyToImage().saveToFile("assets/question.png"); +} + +void createExplodedTexture() { + sf::RenderTexture texture; + texture.create(32, 32); + texture.clear(sf::Color::Red); // Red background + + // Draw mine (same as mine texture) + sf::CircleShape mine(10); + mine.setPosition(6, 6); + mine.setFillColor(sf::Color::Black); + texture.draw(mine); + + // Draw spikes + sf::RectangleShape hSpike(sf::Vector2f(24, 4)); + hSpike.setFillColor(sf::Color::Black); + hSpike.setPosition(4, 14); + texture.draw(hSpike); + + sf::RectangleShape vSpike(sf::Vector2f(4, 24)); + vSpike.setFillColor(sf::Color::Black); + vSpike.setPosition(14, 4); + texture.draw(vSpike); + + // Draw diagonal spikes + sf::RectangleShape d1Spike(sf::Vector2f(16, 4)); + d1Spike.setFillColor(sf::Color::Black); + d1Spike.setPosition(6, 6); + d1Spike.setRotation(45); + texture.draw(d1Spike); + + sf::RectangleShape d2Spike(sf::Vector2f(16, 4)); + d2Spike.setFillColor(sf::Color::Black); + d2Spike.setPosition(22, 6); + d2Spike.setRotation(135); + texture.draw(d2Spike); + + texture.display(); + texture.getTexture().copyToImage().saveToFile("assets/exploded.png"); +} + +void createDigitTexture(int digit) { + sf::RenderTexture texture; + texture.create(24, 32); + texture.clear(sf::Color::Black); + + // Create a digital display style + sf::Text text(std::to_string(digit), sf::Font(), 24); + if (digit == 0) { + // Draw 0 + sf::RectangleShape segments[6]; + // Top + segments[0] = sf::RectangleShape(sf::Vector2f(14, 4)); + segments[0].setPosition(5, 2); + // Top-left + segments[1] = sf::RectangleShape(sf::Vector2f(4, 12)); + segments[1].setPosition(2, 5); + // Top-right + segments[2] = sf::RectangleShape(sf::Vector2f(4, 12)); + segments[2].setPosition(18, 5); + // Bottom-left + segments[3] = sf::RectangleShape(sf::Vector2f(4, 12)); + segments[3].setPosition(2, 18); + // Bottom-right + segments[4] = sf::RectangleShape(sf::Vector2f(4, 12)); + segments[4].setPosition(18, 18); + // Bottom + segments[5] = sf::RectangleShape(sf::Vector2f(14, 4)); + segments[5].setPosition(5, 26); + + for (int i = 0; i < 6; i++) { + segments[i].setFillColor(sf::Color::Red); + texture.draw(segments[i]); + } + } else if (digit == 1) { + // Draw 1 + sf::RectangleShape segments[2]; + // Top-right + segments[0] = sf::RectangleShape(sf::Vector2f(4, 12)); + segments[0].setPosition(12, 5); + // Bottom-right + segments[1] = sf::RectangleShape(sf::Vector2f(4, 12)); + segments[1].setPosition(12, 18); + + for (int i = 0; i < 2; i++) { + segments[i].setFillColor(sf::Color::Red); + texture.draw(segments[i]); + } + } else { + // For other digits, just draw the text + sf::Font font; + if (!font.loadFromFile("assets/digital.ttf")) { + createDigitalFont(); + font.loadFromFile("assets/digital.ttf"); + } + + sf::Text digitText(std::to_string(digit), font, 24); + digitText.setPosition(8, 4); + digitText.setFillColor(sf::Color::Red); + texture.draw(digitText); + } + + texture.display(); + texture.getTexture().copyToImage().saveToFile("assets/digit" + std::to_string(digit) + ".png"); +} + +void createGearTexture() { + sf::RenderTexture texture; + texture.create(32, 32); + texture.clear(sf::Color::Transparent); + + // Draw a circle for the gear + sf::CircleShape gear(10); + gear.setPosition(6, 6); + gear.setFillColor(sf::Color(100, 100, 100)); + texture.draw(gear); + + // Draw inner circle + sf::CircleShape inner(5); + inner.setPosition(11, 11); + inner.setFillColor(sf::Color(192, 192, 192)); + texture.draw(inner); + + // Draw teeth + for (int i = 0; i < 8; i++) { + sf::RectangleShape tooth(sf::Vector2f(6, 4)); + float angle = i * 45.f * 3.14159f / 180.f; + tooth.setPosition( + 16 + 12 * std::cos(angle) - 3, + 16 + 12 * std::sin(angle) - 2 + ); + tooth.setRotation(i * 45.f); + tooth.setFillColor(sf::Color(100, 100, 100)); + texture.draw(tooth); + } + + texture.display(); + texture.getTexture().copyToImage().saveToFile("assets/gear.png"); +} + +void createScrollTexture() { + sf::RenderTexture texture; + texture.create(32, 32); + texture.clear(sf::Color::Transparent); + + // Draw bulb base + sf::ConvexShape bulbBase; + bulbBase.setPointCount(4); + bulbBase.setPoint(0, sf::Vector2f(12, 20)); + bulbBase.setPoint(1, sf::Vector2f(20, 20)); + bulbBase.setPoint(2, sf::Vector2f(18, 28)); + bulbBase.setPoint(3, sf::Vector2f(14, 28)); + bulbBase.setFillColor(sf::Color(200, 200, 100)); + texture.draw(bulbBase); + + // Draw bulb + sf::CircleShape bulb(8); + bulb.setPosition(8, 4); + bulb.setFillColor(sf::Color(255, 255, 0)); // Yellow + texture.draw(bulb); + + // Draw rays + sf::RectangleShape rays[4]; + rays[0] = sf::RectangleShape(sf::Vector2f(4, 10)); + rays[0].setPosition(14, 0); + rays[1] = sf::RectangleShape(sf::Vector2f(4, 10)); + rays[1].setPosition(14, 22); + rays[2] = sf::RectangleShape(sf::Vector2f(10, 4)); + rays[2].setPosition(0, 14); + rays[3] = sf::RectangleShape(sf::Vector2f(10, 4)); + rays[3].setPosition(22, 14); + + for (int i = 0; i < 4; i++) { + rays[i].setFillColor(sf::Color(255, 255, 0)); + texture.draw(rays[i]); + } + + texture.display(); + texture.getTexture().copyToImage().saveToFile("assets/scroll.png"); +} + +void createSmileyTexture() { + sf::RenderTexture texture; + texture.create(32, 32); + texture.clear(sf::Color::Transparent); + + // Draw face + sf::CircleShape face(14); + face.setPosition(2, 2); + face.setFillColor(sf::Color(255, 255, 0)); // Yellow + face.setOutlineColor(sf::Color::Black); + face.setOutlineThickness(1); + texture.draw(face); + + // Draw eyes + sf::CircleShape leftEye(3); + leftEye.setPosition(8, 10); + leftEye.setFillColor(sf::Color::Black); + texture.draw(leftEye); + + sf::CircleShape rightEye(3); + rightEye.setPosition(21, 10); + rightEye.setFillColor(sf::Color::Black); + texture.draw(rightEye); + + // Draw smile + sf::ConvexShape smile; + smile.setPointCount(5); + smile.setPoint(0, sf::Vector2f(8, 18)); + smile.setPoint(1, sf::Vector2f(16, 24)); + smile.setPoint(2, sf::Vector2f(24, 18)); + smile.setPoint(3, sf::Vector2f(24, 20)); + smile.setPoint(4, sf::Vector2f(8, 20)); + smile.setFillColor(sf::Color::Black); + texture.draw(smile); + + texture.display(); + texture.getTexture().copyToImage().saveToFile("assets/smiley.png"); +} + +void createSadTexture() { + sf::RenderTexture texture; + texture.create(32, 32); + texture.clear(sf::Color::Transparent); + + // Draw face + sf::CircleShape face(14); + face.setPosition(2, 2); + face.setFillColor(sf::Color(255, 255, 0)); // Yellow + face.setOutlineColor(sf::Color::Black); + face.setOutlineThickness(1); + texture.draw(face); + + // Draw eyes + sf::CircleShape leftEye(3); + leftEye.setPosition(8, 10); + leftEye.setFillColor(sf::Color::Black); + texture.draw(leftEye); + + sf::CircleShape rightEye(3); + rightEye.setPosition(21, 10); + rightEye.setFillColor(sf::Color::Black); + texture.draw(rightEye); + + // Draw frown + sf::ConvexShape frown; + frown.setPointCount(5); + frown.setPoint(0, sf::Vector2f(8, 22)); + frown.setPoint(1, sf::Vector2f(16, 18)); + frown.setPoint(2, sf::Vector2f(24, 22)); + frown.setPoint(3, sf::Vector2f(24, 24)); + frown.setPoint(4, sf::Vector2f(8, 24)); + frown.setFillColor(sf::Color::Black); + texture.draw(frown); + + texture.display(); + texture.getTexture().copyToImage().saveToFile("assets/sad.png"); +} + +void createCoolTexture() { + sf::RenderTexture texture; + texture.create(32, 32); + texture.clear(sf::Color::Transparent); + + // Draw face + sf::CircleShape face(14); + face.setPosition(2, 2); + face.setFillColor(sf::Color(255, 255, 0)); // Yellow + face.setOutlineColor(sf::Color::Black); + face.setOutlineThickness(1); + texture.draw(face); + + // Draw sunglasses + sf::RectangleShape glasses(sf::Vector2f(22, 8)); + glasses.setPosition(5, 10); + glasses.setFillColor(sf::Color::Black); + texture.draw(glasses); + + // Draw smile + sf::ConvexShape smile; + smile.setPointCount(5); + smile.setPoint(0, sf::Vector2f(8, 20)); + smile.setPoint(1, sf::Vector2f(16, 24)); + smile.setPoint(2, sf::Vector2f(24, 20)); + smile.setPoint(3, sf::Vector2f(24, 22)); + smile.setPoint(4, sf::Vector2f(8, 22)); + smile.setFillColor(sf::Color::Black); + texture.draw(smile); + + texture.display(); + texture.getTexture().copyToImage().saveToFile("assets/cool.png"); +} + +void createDigitalFont() { + // This is a very simple placeholder function for creating a font file + // In a real implementation, you would either use an existing font file + // or create a more sophisticated font + std::ofstream fontFile("assets/digital.ttf", std::ios::binary); + + // Write some placeholder data + const char* fontData = "TTF"; + fontFile.write(fontData, 3); + fontFile.close(); + + std::cout << "Please place a real TTF font file at 'assets/digital.ttf'" << std::endl; +} \ No newline at end of file