17 Commits

Author SHA1 Message Date
theredbluecube2@gmail.com
df951db538 add sdl2 directive checks 2025-12-07 13:54:01 +03:00
theredbluecube2@gmail.com
92a3b6e20a bbbb 2025-12-06 18:44:08 +03:00
theredbluecube2@gmail.com
7ea34913fe change build system to CMake 2025-12-06 18:41:35 +03:00
theredbluecube2@gmail.com
e9814dea3e add controller support 2025-08-07 17:48:03 +03:00
2855b64f03 fix for incorrect phony rule declaration 2025-06-30 12:27:57 +03:00
9c45259962 fixed unusual terminal state after quitting 2025-06-30 12:27:57 +03:00
b1256014e3 forgot the preprocessor keywords 2025-06-29 21:01:19 +03:00
b1af7b4f41 Merge branch 'main' of https://gitea.codersquack.nl/thorium1256/minesweeper 2025-06-29 20:57:19 +03:00
2b0f20d65a Merge branch 'main' of https://gitea.codersquack.nl/thorium1256/minesweeper 2025-06-25 21:12:32 +03:00
e394d0986e relicense and make it REUSE-compliant 2025-06-25 20:49:03 +03:00
d8eb655dd7 fixed expert board size 2025-06-25 20:49:03 +03:00
b878c2ddb3 added install script 2025-06-25 20:49:03 +03:00
a0e5850e68 fix for compiler screams
forgotten others
2025-06-25 20:49:01 +03:00
84972ed9bb silly makefile stuff
makefile realization

makefile insanity

makefile madness

readme update to fit in with actual makefile
2025-06-25 20:48:44 +03:00
08f6ee7161 fixed a bug that would need you to press q a lot when there were a lot of restarts 2025-06-25 20:48:40 +03:00
f7d2f138ea minor changes to the rules 2025-06-25 20:48:39 +03:00
1d07cee01a the timer only starts when an action is taken 2025-06-25 20:48:36 +03:00
6 changed files with 337 additions and 275 deletions

3
.gitignore vendored
View File

@@ -5,5 +5,4 @@
lib
.vscode
bin
build
Makefile
build

72
CMakeLists.txt Normal file
View File

@@ -0,0 +1,72 @@
# SPDX-FileCopyrightText: 2025 thorium1256
#
# SPDX-License-Identifier: GPL-3.0-or-later
cmake_minimum_required(VERSION 3.11)
project(tuimine LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Compiler flags
add_compile_options(-Wall -Wextra)
# Find ncursesw (wide character version)
# Use the CURSES module but specifically look for ncursesw
set(CURSES_NEED_NCURSES TRUE)
set(CURSES_NEED_WIDE TRUE) # This is key for wide character support
find_package(Curses REQUIRED)
message(STATUS "Found Curses include path: ${CURSES_INCLUDE_PATH}")
message(STATUS "Found Curses libraries: ${CURSES_LIBRARIES}")
message(STATUS "Curses include dir: ${CURSES_INCLUDE_DIRS}")
# Optional SDL2
option(WITH_SDL2 "Enable SDL2 controller support" ON)
if(WITH_SDL2)
find_package(SDL2 QUIET)
if(SDL2_FOUND)
message(STATUS "SDL2 found - controller support enabled")
add_definitions(-DWITH_SDL2)
else()
message(STATUS "SDL2 not found - controller support disabled")
endif()
endif()
# Build type specific settings
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_compile_options(-g)
else()
add_compile_options(-O2)
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -s")
endif()
# Create executable
file(GLOB SOURCES "src/*.cpp")
add_executable(tuimine ${SOURCES})
target_include_directories(tuimine PRIVATE include)
# Link ncursesw - use the variables found by find_package(Curses)
if(CURSES_LIBRARIES)
target_link_libraries(tuimine PRIVATE ${CURSES_LIBRARIES})
else()
# Fallback to linking ncursesw directly
target_link_libraries(tuimine PRIVATE ncursesw)
endif()
if(WITH_SDL2 AND SDL2_FOUND)
target_link_libraries(tuimine PRIVATE SDL2::SDL2)
endif()
# Output configuration
set_target_properties(tuimine PROPERTIES
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/bin/debug"
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin/release"
OUTPUT_NAME_DEBUG "debug"
OUTPUT_NAME_RELEASE "tuimine"
)
# Install
install(TARGETS tuimine RUNTIME DESTINATION bin)

View File

@@ -1,73 +0,0 @@
# SPDX-FileCopyrightText: 2025 thorium1256
#
# SPDX-License-Identifier: GPL-3.0-or-later
# compiler stuff
CXX := c++
CXXFLAGS := -I include -std=c++11 -Wall -Wextra
DBG_CXXFLAGS := $(CXXFLAGS) -g
RLS_CXXFLAGS := $(CXXFLAGS) -O2
# linker stuff
LDFLAGS := -lncursesw
DBG_LDFLAGS := $(LDFLAGS)
RLS_LDFLAGS := $(LDFLAGS) -s
# directories
BUILD_DIR := build
SRC_DIR := src
BIN_DIR := bin
DBG_DIR := $(BIN_DIR)/debug
RLS_DIR := $(BIN_DIR)/release
DBG_EXEC := $(DBG_DIR)/debug
RLS_STRIPPED_EXEC := $(RLS_DIR)/tuimine
# install stuff
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
DATADIR ?= $(PREFIX)/share
APPLICATION ?= tuimine
# sources and objects
SRCS := $(wildcard $(SRC_DIR)/*.cpp)
DBG_OBJS := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/debug_%.o,$(SRCS))
RLS_OBJS := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/release_%.o,$(SRCS))
$(shell mkdir -p $(BIN_DIR))
$(shell mkdir -p $(DBG_DIR))
$(shell mkdir -p $(RLS_DIR))
$(shell mkdir -p $(BUILD_DIR))
# phony rules
.PHONY := all debug release clean install uninstall
all: release
debug: $(DBG_EXEC)
release: $(RLS_STRIPPED_EXEC)
both: debug release
# linking
$(DBG_EXEC): $(DBG_OBJS)
$(CXX) -o $@ $^ $(DBG_LDFLAGS)
$(RLS_STRIPPED_EXEC): $(RLS_OBJS)
$(CXX) -o $@ $^ $(RLS_LDFLAGS)
# compiling
$(BUILD_DIR)/debug_%.o: $(SRC_DIR)/%.cpp
$(CXX) -c -o $@ $< $(DBG_CXXFLAGS)
$(BUILD_DIR)/release_%.o: $(SRC_DIR)/%.cpp
$(CXX) -c -o $@ $< $(RLS_CXXFLAGS)
install: release
install -d $(DESTDIR)$(BINDIR)
install -m 755 $(RLS_STRIPPED_EXEC) $(DESTDIR)$(BINDIR)/$(APPLICATION)
uninstall:
rm -f $(DESTDIR)$(BINDIR)/$(APPLICATION)
clean:
rm -rf $(BUILD_DIR) $(DBG_DIR) $(RLS_DIR)

View File

@@ -1,72 +0,0 @@
# SPDX-FileCopyrightText: 2025 thorium1256
#
# SPDX-License-Identifier: GPL-3.0-or-later
# compiler stuff
CXX := c++
CXXFLAGS := -I include -std=c++11 -Wall -Wextra
DBG_CXXFLAGS := $(CXXFLAGS) -g
RLS_CXXFLAGS := $(CXXFLAGS) -O2
# linker stuff
LDFLAGS := -L C:\msys64\mingw64\bin -lncursesw6
DBG_LDFLAGS := $(LDFLAGS)
RLS_STRP_LDFLAGS := $(LDFLAGS) -s
# directories
BUILD_DIR := build
SRC_DIR := src
BIN_DIR := bin
DBG_DIR := $(BIN_DIR)\debug
RLS_DIR := $(BIN_DIR)\release
LIB_DIR := lib
DBG_EXEC := $(DBG_DIR)\debug.exe
RLS_STRIPPED_EXEC := $(RLS_DIR)\tuimine.exe
# sources and objects
LIBS := $(wildcard $(LIB_DIR)/*.dll)
LIBS_DBG := $(patsubst $(LIB_DIR)/%.dll, $(DBG_DIR)\\%.dll, $(LIBS))
LIBS_RLS := $(patsubst $(LIB_DIR)/%.dll, $(RLS_DIR)\\%.dll, $(LIBS))
SRCS := $(wildcard $(SRC_DIR)/*.cpp)
DBG_OBJS := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/debug_%.o,$(SRCS))
RLS_OBJS := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/release_%.o,$(SRCS))
$(shell if not exist $(BIN_DIR) mkdir $(BIN_DIR))
$(shell if not exist $(DBG_DIR) mkdir $(DBG_DIR))
$(shell if not exist $(RLS_DIR) mkdir $(RLS_DIR))
$(shell if not exist $(BUILD_DIR) mkdir $(BUILD_DIR))
# phony rules
.PHONY := all debug release clean libraries_debug libraries_release both
all: release
debug: $(DBG_EXEC) libraries_debug
release: $(RLS_STRIPPED_EXEC) libraries_release
both: debug release
libraries_debug: $(LIBS_DBG)
libraries_release: $(LIBS_RLS)
# linking
$(DBG_EXEC): $(DBG_OBJS)
$(CXX) -o $@ $^ $(DBG_LDFLAGS)
$(RLS_STRIPPED_EXEC): $(RLS_OBJS)
$(CXX) -o $@ $^ $(RLS_STRP_LDFLAGS)
# compiling
$(BUILD_DIR)/debug_%.o: $(SRC_DIR)/%.cpp
$(CXX) -c -o $@ $< $(DBG_CXXFLAGS)
$(BUILD_DIR)/release_%.o: $(SRC_DIR)/%.cpp
$(CXX) -c -o $@ $< $(RLS_CXXFLAGS)
$(DBG_DIR)\\%.dll: $(LIB_DIR)\\%.dll
xcopy $< $(DBG_DIR)\ >nul
$(RLS_DIR)\\%.dll: $(LIB_DIR)\\%.dll
xcopy $< $(RLS_DIR)\ >nul
clean:
rmdir /s /q $(BUILD_DIR) $(DBG_DIR) $(RLS_DIR)

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
# Minesweeper... in the terminal!
Minesweeper is a classic puzzle game where the objective is to clear a rectangular board without detonating any hidden mines.
Minesweeper is a puzzle game where the objective is to clear a rectangular board without detonating any hidden mines.
## How to Play
@@ -32,45 +32,22 @@ You can't flag already revealed cells.
## Compilation
### Windows
For compiling on Windows, you need MSYS2 and MinGW-w64.
If you want, you can compile on MSVC (if you get it to work), I don't care.
The prerequisites are _ncurses_. Only that.
The DLLs are not included in this repository, you should go get 'em yourself, and put them in the `lib` folder.
Rename the `Makefile.win` file, make, and you got Minesweeper.
But I have to mention something if it says something about no libraries found. MSYS2, for some reason, has been including dynamic libraries really weirdly in their packages. They only add the static libraries and don't get the dynamic ones. And that makes me angry. I don't know why they did this, but it's confusing. Please just put the DLLs in mingw64/lib and not in mingw64/bin.
Anyway, if it says something about that, just patch the Makefile with **normal** LDFLAGS and try again.
### Unix/Linux
Every normal Unix/Linux system has ncurses, but not always the development headers needed for compilation.
Installation for these:
**Debian/Ubuntu**:
Make a `build` directory and enter it:
```sh
sudo apt install libncurses-dev
mkdir build
cd build
```
**Arch**:
Make build files:
```sh
sudo pacman -S ncurses
cmake ..
```
**Fedora**:
Compile and install:
```sh
sudo dnf install ncurses-devel
make -j${nproc}
make install
```
**Other distros**:
Search engines are your best friend.
Then rename `Makefile.unix`, press the make button, and a moment later, you got it.

View File

@@ -4,9 +4,12 @@
#include <iostream>
#include <stdexcept>
#include <ncursesw/ncurses.h>
#include <ncurses.h>
#include <ctime>
#include <unistd.h>
#ifdef WITH_SDL2
#include <SDL2/SDL.h>
#endif
#include "Board.h"
#ifndef _WIN32
#include <signal.h>
@@ -20,6 +23,10 @@ void handleSIGWINCH(int sig)
#define MAX_TIME 60
#ifdef WITH_SDL2
SDL_GameController *controller;
#endif
void startGame(Board &board)
{
int minesLeft = board.getMineCount();
@@ -44,10 +51,11 @@ void startGame(Board &board)
{
usleep((1000 / MAX_TIME) * 1000);
#ifndef _WIN32
if((w.ws_row < boardSize.x + 3 )||( w.ws_col < boardSize.y + 2)) {
mvprintw(w.ws_col/2, w.ws_row/2, "Your terminal is too small:");
mvprintw(w.ws_col / 2 + 1, w.ws_row/2, "Current size: %dx%d", w.ws_row, w.ws_col);
mvprintw(w.ws_col / 2 + 2, w.ws_row/2, "Min size: %dx%d", boardSize.x + 3, boardSize.y + 2);
if ((w.ws_row < boardSize.x + 3) || (w.ws_col < boardSize.y + 2))
{
mvprintw(w.ws_col / 2, w.ws_row / 2, "Your terminal is too small:");
mvprintw(w.ws_col / 2 + 1, w.ws_row / 2, "Current size: %dx%d", w.ws_row, w.ws_col);
mvprintw(w.ws_col / 2 + 2, w.ws_row / 2, "Min size: %dx%d", boardSize.x + 3, boardSize.y + 2);
refresh();
continue;
}
@@ -159,100 +167,210 @@ void startGame(Board &board)
}
c = getch();
#ifdef WITH_SDL2
if (c == ERR)
continue;
switch (c)
{
case KEY_UP:
cursorY = (cursorY > 0) ? cursorY - 1 : boardSize.y - 1;
break;
case KEY_DOWN:
cursorY = (cursorY < boardSize.y - 1) ? cursorY + 1 : 0;
break;
case KEY_LEFT:
cursorX = (cursorX > 0) ? cursorX - 1 : boardSize.x - 1;
break;
case KEY_RIGHT:
cursorX = (cursorX < boardSize.x - 1) ? cursorX + 1 : 0;
break;
case 'q':
exit(0);
echo();
cbreak();
endwin();
break;
case 'z':
if (!somethingHasBeenDone)
if (controller != nullptr)
{
startTime = time(nullptr);
somethingHasBeenDone = true;
SDL_Event event;
while (SDL_PollEvent(&event))
{
if (event.type == SDL_CONTROLLERBUTTONDOWN)
{
switch (event.cbutton.button)
{
case SDL_CONTROLLER_BUTTON_DPAD_UP:
cursorY = (cursorY > 0) ? cursorY - 1 : boardSize.y - 1;
break;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
cursorY = (cursorY < boardSize.y - 1) ? cursorY + 1 : 0;
break;
case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
cursorX = (cursorX > 0) ? cursorX - 1 : boardSize.x - 1;
break;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
cursorX = (cursorX < boardSize.x - 1) ? cursorX + 1 : 0;
break;
case SDL_CONTROLLER_BUTTON_START:
if (isPaused)
{
pauseTime = difftime(time(NULL), startPauseTime);
totalpausetime += pauseTime;
}
else
{
startPauseTime = time(NULL);
}
isPaused = !isPaused;
break;
case SDL_CONTROLLER_BUTTON_A:
if (!somethingHasBeenDone)
{
startTime = time(nullptr);
somethingHasBeenDone = true;
}
if (board.getCellStateAt(cursorX, cursorY) == Cell::State::Revealed)
{
if (!somethingHasBeenDone)
{
startTime = time(nullptr);
somethingHasBeenDone = true;
}
auto neighbors = board.getNeighborsOf(cursorX, cursorY);
int flagCount = 0;
for (const Cell &neighbor : neighbors)
{
if (neighbor.getState() == Cell::State::Flagged)
flagCount++;
}
if (static_cast<int>(board.revealCellAt(cursorX, cursorY).getContent()) > flagCount)
break;
for (const Cell &neighbor : neighbors)
{
if (neighbor.getState() == Cell::State::Flagged)
continue;
board.revealCellAt(neighbor.getPosition().x, neighbor.getPosition().y);
}
break;
}
board.revealCellAt(cursorX, cursorY);
break;
case SDL_CONTROLLER_BUTTON_B:
if (board.getCellStateAt(cursorX, cursorY) == Cell::State::Revealed)
break;
if (!somethingHasBeenDone)
{
startTime = time(nullptr);
somethingHasBeenDone = true;
}
if (board.getCellStateAt(cursorX, cursorY) == Cell::State::Flagged)
{
minesLeft++;
}
else
{
minesLeft--;
}
board.flagCellAt(cursorX, cursorY);
break;
case SDL_CONTROLLER_BUTTON_MISC1:
startTime = time(NULL);
minesLeft = board.getMineCount();
board.regenerateBoard();
somethingHasBeenDone = false;
elapsedTime = 0;
break;
case SDL_CONTROLLER_BUTTON_BACK:
echo();
cbreak();
endwin();
exit(0);
break;
}
}
else
{
break;
}
}
}
board.revealCellAt(cursorX, cursorY);
break;
case 'x':
if (board.getCellStateAt(cursorX, cursorY) == Cell::State::Revealed)
}
#endif
if (!(c == ERR))
{
switch (c)
{
case KEY_UP:
cursorY = (cursorY > 0) ? cursorY - 1 : boardSize.y - 1;
break;
if (!somethingHasBeenDone)
{
startTime = time(nullptr);
somethingHasBeenDone = true;
}
if (board.getCellStateAt(cursorX, cursorY) == Cell::State::Flagged)
{
minesLeft++;
}
else
{
minesLeft--;
}
board.flagCellAt(cursorX, cursorY);
break;
case 'r':
startTime = time(NULL);
minesLeft = board.getMineCount();
board.regenerateBoard();
somethingHasBeenDone = false;
elapsedTime = 0;
break;
case 'p':
if (isPaused)
{
pauseTime = difftime(time(NULL), startPauseTime);
totalpausetime += pauseTime;
}
else
{
startPauseTime = time(NULL);
}
isPaused = !isPaused;
break;
case 'c':
if (board.getCellStateAt(cursorX, cursorY) != Cell::State::Revealed)
{
case KEY_DOWN:
cursorY = (cursorY < boardSize.y - 1) ? cursorY + 1 : 0;
break;
case KEY_LEFT:
cursorX = (cursorX > 0) ? cursorX - 1 : boardSize.x - 1;
break;
case KEY_RIGHT:
cursorX = (cursorX < boardSize.x - 1) ? cursorX + 1 : 0;
break;
case 'q':
echo();
cbreak();
endwin();
exit(0);
break;
case 'z':
if (!somethingHasBeenDone)
{
startTime = time(nullptr);
somethingHasBeenDone = true;
}
board.revealCellAt(cursorX, cursorY);
break;
case 'x':
if (board.getCellStateAt(cursorX, cursorY) == Cell::State::Revealed)
break;
if (!somethingHasBeenDone)
{
startTime = time(nullptr);
somethingHasBeenDone = true;
}
if (board.getCellStateAt(cursorX, cursorY) == Cell::State::Flagged)
{
minesLeft++;
}
else
{
minesLeft--;
}
board.flagCellAt(cursorX, cursorY);
break;
case 'r':
startTime = time(NULL);
minesLeft = board.getMineCount();
board.regenerateBoard();
somethingHasBeenDone = false;
elapsedTime = 0;
break;
case 'p':
if (isPaused)
{
pauseTime = difftime(time(NULL), startPauseTime);
totalpausetime += pauseTime;
}
else
{
startPauseTime = time(NULL);
}
isPaused = !isPaused;
break;
case 'c':
if (board.getCellStateAt(cursorX, cursorY) != Cell::State::Revealed)
{
break;
}
if (!somethingHasBeenDone)
{
startTime = time(nullptr);
somethingHasBeenDone = true;
}
auto neighbors = board.getNeighborsOf(cursorX, cursorY);
int flagCount = 0;
for (const Cell &neighbor : neighbors)
{
if (neighbor.getState() == Cell::State::Flagged)
flagCount++;
}
if (static_cast<int>(board.revealCellAt(cursorX, cursorY).getContent()) > flagCount)
break;
for (const Cell &neighbor : neighbors)
{
if (neighbor.getState() == Cell::State::Flagged)
continue;
board.revealCellAt(neighbor.getPosition().x, neighbor.getPosition().y);
}
break;
}
if (!somethingHasBeenDone)
{
startTime = time(nullptr);
somethingHasBeenDone = true;
}
auto neighbors = board.getNeighborsOf(cursorX, cursorY);
int flagCount = 0;
for (const Cell &neighbor : neighbors)
{
if (neighbor.getState() == Cell::State::Flagged)
flagCount++;
}
if (static_cast<int>(board.revealCellAt(cursorX, cursorY).getContent()) > flagCount)
break;
for (const Cell &neighbor : neighbors)
{
if (neighbor.getState() == Cell::State::Flagged)
continue;
board.revealCellAt(neighbor.getPosition().x, neighbor.getPosition().y);
}
break;
}
if (isPaused)
@@ -282,11 +400,13 @@ void startGame(Board &board)
{
Board newBoard(boardSize.x, boardSize.y, board.getMineCount());
startGame(newBoard);
} else if(c == 'q') {
exit(0);
}
else if (c == 'q')
{
echo();
cbreak();
endwin();
exit(0);
}
};
}
@@ -312,26 +432,65 @@ void startGame(Board &board)
{
Board newBoard(boardSize.x, boardSize.y, board.getMineCount());
startGame(newBoard);
} else if(c == 'q') {
exit(0);
}
else if (c == 'q')
{
echo();
cbreak();
endwin();
exit(0);
}
};
}
}
#ifdef WITH_SDL2
SDL_GameController *findController()
{
for (int i = 0; i < SDL_NumJoysticks(); i++)
{
if (SDL_IsGameController(i))
{
return SDL_GameControllerOpen(i);
}
}
return nullptr;
}
#endif
int main()
{
#ifndef _WIN32
signal(SIGWINCH, handleSIGWINCH);
#endif
setlocale(LC_ALL, "");
initscr();
noecho();
cbreak();
keypad(stdscr, TRUE);
nodelay(stdscr, TRUE);
#ifndef _WIN32
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
#endif
#ifdef WITH_SDL2
bool controllerSupport;
if (SDL_Init(SDL_INIT_GAMECONTROLLER) < 0)
{
std::cerr << "SDL failed to initialize GameController API! Controller support will not be available!" << std::endl;
controllerSupport = false;
}
else
{
controllerSupport = true;
}
controller = findController();
if (controller != nullptr)
controllerSupport = false; // no controller for you
#endif
start_color();
init_pair(1, COLOR_BLUE, COLOR_BLACK);