Compare commits
11 Commits
98837a6fc3
...
a65787b674
Author | SHA1 | Date | |
---|---|---|---|
a65787b674 | |||
429074dd79 | |||
3eb99d591f | |||
91c3a9daf2 | |||
41fb7478d0 | |||
e2f3dfb1b0 | |||
a3e80d0b43 | |||
bb41c6643e | |||
1e8a067b83 | |||
099354b523 | |||
dbe6dc82cf |
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
lib
|
||||
.vscode
|
||||
bin
|
||||
build
|
||||
Makefile
|
56
Makefile.unix
Normal file
56
Makefile.unix
Normal file
@ -0,0 +1,56 @@
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
all: debug
|
||||
|
||||
debug: $(DBG_DIR)/debug
|
||||
release: $(RLS_DIR)/release $(RLS_DIR)/release_stripped
|
||||
both: debug release
|
||||
|
||||
# linking
|
||||
$(DBG_DIR)/debug: $(DBG_OBJS)
|
||||
$(CXX) -o $@ $^ $(DBG_LDFLAGS)
|
||||
|
||||
$(RLS_DIR)/release_stripped: $(RLS_OBJS)
|
||||
$(CXX) -o $@ $^ $(RLS_LDFLAGS)
|
||||
|
||||
$(RLS_DIR)/release: $(RLS_OBJS)
|
||||
$(CXX) -o $@ $^ $(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)
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR) $(DBG_DIR) $(RLS_DIR)
|
69
Makefile.win
Normal file
69
Makefile.win
Normal file
@ -0,0 +1,69 @@
|
||||
# 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
|
||||
|
||||
# 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: debug
|
||||
|
||||
debug: $(DBG_DIR)/debug.exe libraries_debug
|
||||
release: $(RLS_DIR)/release_stripped.exe $(RLS_DIR)/release.exe libraries_release
|
||||
both: debug release
|
||||
|
||||
libraries_debug: $(LIBS_DBG)
|
||||
libraries_release: $(LIBS_RLS)
|
||||
|
||||
# linking
|
||||
$(DBG_DIR)/debug.exe: $(DBG_OBJS)
|
||||
$(CXX) -o $@ $^ $(DBG_LDFLAGS)
|
||||
|
||||
$(RLS_DIR)/release_stripped.exe: $(RLS_OBJS)
|
||||
$(CXX) -o $@ $^ $(RLS_STRP_LDFLAGS)
|
||||
|
||||
$(RLS_DIR)/release.exe: $(RLS_OBJS)
|
||||
$(CXX) -o $@ $^ $(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)
|
69
README.md
69
README.md
@ -1,3 +1,68 @@
|
||||
# minesweeper
|
||||
# Minesweeper... in the terminal!
|
||||
|
||||
ascii minesweeper
|
||||
Minesweeper is a classic puzzle game where the objective is to clear a rectangular board without detonating any hidden mines.
|
||||
|
||||
## How to Play
|
||||
|
||||
The board consists of hidden cells, some containing mines. Uncover a cell by selecting its coordinates. If it's a mine, you lose. If it's safe, a number appears indicating how many mines are adjacent to that cell.
|
||||
**Use logic to deduce mine locations and mark them with flags.**
|
||||
Marking all mines with flags isn't necessary, but helps.
|
||||
|
||||
### Clear all non-mine cells to win!
|
||||
|
||||
## Controls
|
||||
|
||||
Arrows to move.
|
||||
Z to reveal.
|
||||
X to flag.
|
||||
C to chord.
|
||||
R to restart.
|
||||
P to pause.
|
||||
Q to quit.
|
||||
|
||||
## Compilation
|
||||
|
||||
By default, the Makefile compiles in debug mode, so to compile both, use `make both`.
|
||||
|
||||
### 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**:
|
||||
|
||||
```sh
|
||||
sudo apt install libncurses-dev
|
||||
```
|
||||
|
||||
**Arch**:
|
||||
|
||||
```sh
|
||||
sudo pacman -S ncurses
|
||||
```
|
||||
|
||||
**Fedora**:
|
||||
|
||||
```sh
|
||||
sudo dnf install ncurses-devel
|
||||
```
|
||||
|
||||
**Other distros**:
|
||||
Search engines are your best friend.
|
||||
|
||||
Then rename `Makefile.unix`, press the make button, and a moment later, you got it.
|
||||
|
33
include/Board.h
Normal file
33
include/Board.h
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include "Cell.h"
|
||||
|
||||
class Board
|
||||
{
|
||||
public:
|
||||
Board(int w, int h, short mines);
|
||||
Cell revealCellAt(int x, int y);
|
||||
void gameOver();
|
||||
void flagCellAt(int x, int y);
|
||||
Vector2 getBoardSize() const;
|
||||
bool isGameOver();
|
||||
void revealEmptyCells(int x, int y);
|
||||
Cell::State getCellStateAt(int x, int y);
|
||||
int getMineCount() const;
|
||||
void regenerateBoard();
|
||||
bool isGameWon() const;
|
||||
std::vector<Cell> getNeighborsOf(int x, int y);
|
||||
|
||||
private:
|
||||
Vector2 size;
|
||||
short mines;
|
||||
bool b_gameOver = false;
|
||||
bool isFirstClick = true;
|
||||
std::vector<std::vector<Cell>> cells;
|
||||
|
||||
bool checkWinCondition() const;
|
||||
};
|
47
include/Cell.h
Normal file
47
include/Cell.h
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "Vector2.h"
|
||||
|
||||
class Cell
|
||||
{
|
||||
public:
|
||||
enum class Content
|
||||
{
|
||||
Empty = 0,
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
Five,
|
||||
Six,
|
||||
Seven,
|
||||
Eight,
|
||||
Mine
|
||||
};
|
||||
|
||||
enum class State
|
||||
{
|
||||
Hidden,
|
||||
Revealed,
|
||||
Flagged,
|
||||
NotAMine
|
||||
};
|
||||
|
||||
friend class Board;
|
||||
|
||||
Cell() = default;
|
||||
Cell(int x, int y);
|
||||
Cell(Vector2 &initPos);
|
||||
|
||||
State getState() const;
|
||||
void reveal();
|
||||
void toggleFlag();
|
||||
Content getContent() const;
|
||||
void setContent(Content newContent);
|
||||
Vector2 getPosition() const;
|
||||
|
||||
private:
|
||||
State state = State::Hidden;
|
||||
Content content = Content::Empty;
|
||||
Vector2 position;
|
||||
};
|
48
include/Vector2.h
Normal file
48
include/Vector2.h
Normal file
@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
struct Vector2
|
||||
{
|
||||
/**
|
||||
* The position vector.
|
||||
*/
|
||||
int x, y;
|
||||
|
||||
/**
|
||||
* The Vector2 initializer.
|
||||
*/
|
||||
Vector2(int x = 0, int y = 0);
|
||||
|
||||
/**
|
||||
* Addition.
|
||||
*/
|
||||
Vector2 operator+(const Vector2 &other) const;
|
||||
/**
|
||||
* Subtraction.
|
||||
*/
|
||||
Vector2 operator-(const Vector2 &other) const;
|
||||
/**
|
||||
* Multiplication (by scalar).
|
||||
*/
|
||||
Vector2 operator*(float scalar) const;
|
||||
/**
|
||||
* Division (by scalar).
|
||||
*/
|
||||
Vector2 operator/(float scalar) const;
|
||||
|
||||
/**
|
||||
* Addition.
|
||||
*/
|
||||
Vector2 &operator+=(const Vector2 &other);
|
||||
/**
|
||||
* Subtraction.
|
||||
*/
|
||||
Vector2 &operator-=(const Vector2 &other);
|
||||
/**
|
||||
* Multiplication (by scalar).
|
||||
*/
|
||||
Vector2 &operator*=(float scalar);
|
||||
/**
|
||||
* Division (by scalar).
|
||||
*/
|
||||
Vector2 &operator/=(float scalar);
|
||||
};
|
292
src/Board.cpp
Normal file
292
src/Board.cpp
Normal file
@ -0,0 +1,292 @@
|
||||
#include "Board.h"
|
||||
#include <iostream>
|
||||
|
||||
Board::Board(int w, int h, short mines) : size(w, h), mines(mines), isFirstClick(true), b_gameOver(false)
|
||||
{
|
||||
cells.resize(h);
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
cells[y].resize(w);
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
cells[y][x] = Cell(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Vector2> positions;
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
positions.push_back(Vector2(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 g(rd());
|
||||
std::shuffle(positions.begin(), positions.end(), g);
|
||||
|
||||
for (int i = 0; i < mines; i++)
|
||||
{
|
||||
Vector2 pos = positions[i];
|
||||
cells[pos.y][pos.x].setContent(Cell::Content::Mine);
|
||||
}
|
||||
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
Cell &cell = cells[y][x];
|
||||
if (cell.getContent() != Cell::Content::Mine)
|
||||
{
|
||||
int count = 0;
|
||||
std::vector<Cell> neighbors = getNeighborsOf(x, y);
|
||||
for (const Cell &neighbor : neighbors)
|
||||
{
|
||||
if (neighbor.getContent() == Cell::Content::Mine)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
cell.setContent(static_cast<Cell::Content>(count));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Cell> Board::getNeighborsOf(int x, int y)
|
||||
{
|
||||
std::vector<Cell> neighbors;
|
||||
for (int dx = -1; dx <= 1; dx++)
|
||||
{
|
||||
for (int dy = -1; dy <= 1; dy++)
|
||||
{
|
||||
if (dx == 0 && dy == 0)
|
||||
continue;
|
||||
int nx = x + dx;
|
||||
int ny = y + dy;
|
||||
if (nx >= 0 && nx < size.x && ny >= 0 && ny < size.y)
|
||||
neighbors.push_back(cells[ny][nx]);
|
||||
}
|
||||
}
|
||||
return neighbors;
|
||||
}
|
||||
|
||||
bool Board::isGameOver()
|
||||
{
|
||||
return b_gameOver;
|
||||
}
|
||||
|
||||
Cell Board::revealCellAt(int x, int y)
|
||||
{
|
||||
if (x < 0 || x >= size.x || y < 0 || y >= size.y)
|
||||
{
|
||||
throw std::out_of_range("Cell coordinates out of bounds");
|
||||
}
|
||||
|
||||
Cell &cell = cells[y][x];
|
||||
if (cell.getState() == Cell::State::Revealed || cell.getState() == Cell::State::Flagged)
|
||||
{
|
||||
return cell;
|
||||
}
|
||||
|
||||
if (isFirstClick)
|
||||
{
|
||||
if (cell.getContent() == Cell::Content::Mine)
|
||||
{
|
||||
regenerateBoard();
|
||||
return revealCellAt(x, y);
|
||||
}
|
||||
isFirstClick = false;
|
||||
}
|
||||
|
||||
if (cell.getContent() == Cell::Content::Mine)
|
||||
{
|
||||
cell.reveal();
|
||||
gameOver();
|
||||
return cell;
|
||||
}
|
||||
|
||||
if (cell.getContent() == Cell::Content::Empty)
|
||||
{
|
||||
revealEmptyCells(x, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.reveal();
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
void Board::revealEmptyCells(int x, int y)
|
||||
{
|
||||
std::queue<Vector2> q;
|
||||
q.push(Vector2(x, y));
|
||||
|
||||
while (!q.empty())
|
||||
{
|
||||
Vector2 pos = q.front();
|
||||
q.pop();
|
||||
|
||||
Cell ¤t = cells[pos.y][pos.x];
|
||||
if (current.getState() != Cell::State::Hidden)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
current.reveal();
|
||||
|
||||
if (current.getContent() == Cell::Content::Empty)
|
||||
{
|
||||
// for (int dx = -1; dx <= 1; dx++)
|
||||
// {
|
||||
// for (int dy = -1; dy <= 1; dy++)
|
||||
// {
|
||||
// if (dx == 0 && dy == 0)
|
||||
// continue;
|
||||
// int nx = pos.x + dx;
|
||||
// int ny = pos.y + dy;
|
||||
// if (nx >= 0 && nx < size.x && ny >= 0 && ny < size.y)
|
||||
// {
|
||||
// Cell &neighbor = cells[ny][nx];
|
||||
// if (neighbor.getState() == Cell::State::Hidden)
|
||||
// {
|
||||
// q.push(Vector2(nx, ny));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
std::vector<Cell> neighbors = getNeighborsOf(pos.x, pos.y);
|
||||
for (const Cell &neighbor : neighbors)
|
||||
{
|
||||
if (neighbor.getState() == Cell::State::Hidden)
|
||||
{
|
||||
q.push(Vector2(neighbor.position.x, neighbor.position.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Board::gameOver()
|
||||
{
|
||||
b_gameOver = true;
|
||||
for (int y = 0; y < size.y; y++)
|
||||
{
|
||||
for (int x = 0; x < size.x; x++)
|
||||
{
|
||||
Cell &cell = cells[y][x];
|
||||
if (cell.getContent() == Cell::Content::Mine &&
|
||||
cell.getState() != Cell::State::Flagged)
|
||||
{
|
||||
cell.reveal();
|
||||
}
|
||||
if (cell.getContent() != Cell::Content::Mine && cell.getState() == Cell::State::Flagged)
|
||||
{
|
||||
cell.state = Cell::State::NotAMine;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Board::flagCellAt(int x, int y)
|
||||
{
|
||||
if (x < 0 || x >= size.x || y < 0 || y >= size.y)
|
||||
{
|
||||
return;
|
||||
}
|
||||
cells[y][x].toggleFlag();
|
||||
}
|
||||
|
||||
Vector2 Board::getBoardSize() const
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
void Board::regenerateBoard()
|
||||
{
|
||||
cells.clear();
|
||||
isFirstClick = true;
|
||||
|
||||
cells.resize(size.y);
|
||||
for (int y = 0; y < size.y; y++)
|
||||
{
|
||||
cells[y].resize(size.x);
|
||||
for (int x = 0; x < size.x; x++)
|
||||
{
|
||||
cells[y][x] = Cell(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Vector2> positions;
|
||||
for (int y = 0; y < size.y; y++)
|
||||
{
|
||||
for (int x = 0; x < size.x; x++)
|
||||
{
|
||||
positions.push_back(Vector2(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 g(rd());
|
||||
std::shuffle(positions.begin(), positions.end(), g);
|
||||
|
||||
for (int i = 0; i < mines; i++)
|
||||
{
|
||||
Vector2 pos = positions[i];
|
||||
cells[pos.y][pos.x].setContent(Cell::Content::Mine);
|
||||
}
|
||||
|
||||
for (int y = 0; y < size.y; y++)
|
||||
{
|
||||
for (int x = 0; x < size.x; x++)
|
||||
{
|
||||
Cell &cell = cells[y][x];
|
||||
if (cell.getContent() != Cell::Content::Mine)
|
||||
{
|
||||
int count = 0;
|
||||
std::vector<Cell> neighbors = getNeighborsOf(x, y);
|
||||
for (const Cell &neighbor : neighbors)
|
||||
{
|
||||
if (neighbor.getContent() == Cell::Content::Mine)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
cell.setContent(static_cast<Cell::Content>(count));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Board::getMineCount() const
|
||||
{
|
||||
return mines;
|
||||
}
|
||||
|
||||
Cell::State Board::getCellStateAt(int x, int y)
|
||||
{
|
||||
return cells[y][x].getState();
|
||||
}
|
||||
|
||||
bool Board::isGameWon() const
|
||||
{
|
||||
return checkWinCondition();
|
||||
}
|
||||
|
||||
bool Board::checkWinCondition() const
|
||||
{
|
||||
for (int y = 0; y < size.y; y++)
|
||||
{
|
||||
for (int x = 0; x < size.x; x++)
|
||||
{
|
||||
const Cell &cell = cells[y][x];
|
||||
if (cell.getContent() != Cell::Content::Mine && cell.getState() != Cell::State::Revealed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
43
src/Cell.cpp
Normal file
43
src/Cell.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#include "Cell.h"
|
||||
|
||||
Cell::Cell(int x, int y)
|
||||
: position(x, y)
|
||||
{
|
||||
}
|
||||
|
||||
Cell::Cell(Vector2 &initPos)
|
||||
: position(initPos)
|
||||
{
|
||||
}
|
||||
|
||||
Cell::State Cell::getState() const
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
void Cell::reveal()
|
||||
{
|
||||
if (state != State::Flagged)
|
||||
state = State::Revealed;
|
||||
}
|
||||
|
||||
void Cell::toggleFlag()
|
||||
{
|
||||
state = (state == State::Flagged) ? State::Hidden : State::Flagged;
|
||||
}
|
||||
|
||||
Cell::Content Cell::getContent() const
|
||||
{
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
void Cell::setContent(Content newContent)
|
||||
{
|
||||
content = newContent;
|
||||
}
|
||||
|
||||
Vector2 Cell::getPosition() const
|
||||
{
|
||||
return position;
|
||||
}
|
58
src/Vector2.cpp
Normal file
58
src/Vector2.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#include "Vector2.h"
|
||||
|
||||
Vector2::Vector2(int x, int y) : x(x), y(y) {}
|
||||
|
||||
Vector2 Vector2::operator+(const Vector2 &other) const
|
||||
{
|
||||
return Vector2(x + other.x, y + other.y);
|
||||
}
|
||||
|
||||
Vector2 Vector2::operator-(const Vector2 &other) const
|
||||
{
|
||||
return Vector2(x - other.x, y - other.y);
|
||||
}
|
||||
|
||||
Vector2 Vector2::operator*(float scalar) const
|
||||
{
|
||||
return Vector2(x * scalar, y * scalar);
|
||||
}
|
||||
|
||||
Vector2 Vector2::operator/(float scalar) const
|
||||
{
|
||||
// In production code, you might want to handle division by zero
|
||||
return Vector2(x / scalar, y / scalar);
|
||||
}
|
||||
|
||||
Vector2 &Vector2::operator+=(const Vector2 &other)
|
||||
{
|
||||
x += other.x;
|
||||
y += other.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vector2 &Vector2::operator-=(const Vector2 &other)
|
||||
{
|
||||
x -= other.x;
|
||||
y -= other.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vector2 &Vector2::operator*=(float scalar)
|
||||
{
|
||||
x *= scalar;
|
||||
y *= scalar;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vector2 &Vector2::operator/=(float scalar)
|
||||
{
|
||||
x /= scalar;
|
||||
y /= scalar;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Non-member function for left-hand scalar multiplication
|
||||
Vector2 operator*(float scalar, const Vector2 &vector)
|
||||
{
|
||||
return vector * scalar;
|
||||
}
|
333
src/main.cpp
Normal file
333
src/main.cpp
Normal file
@ -0,0 +1,333 @@
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <ncursesw/ncurses.h>
|
||||
#include <ctime>
|
||||
#include <unistd.h>
|
||||
#include "Board.h"
|
||||
|
||||
#define MAX_TIME 60
|
||||
|
||||
void startGame(Board &board)
|
||||
{
|
||||
int minesLeft = board.getMineCount();
|
||||
|
||||
Vector2 boardSize = board.getBoardSize();
|
||||
int c;
|
||||
int cursorX = 0, cursorY = 0;
|
||||
|
||||
refresh();
|
||||
bool gameRunning = true;
|
||||
|
||||
time_t startTime = time(nullptr);
|
||||
int elapsedTime = 0;
|
||||
|
||||
bool isPaused = false;
|
||||
time_t startPauseTime = 0;
|
||||
time_t pauseTime = 0;
|
||||
time_t totalpausetime = 0;
|
||||
while (gameRunning)
|
||||
{
|
||||
|
||||
usleep((1000 / MAX_TIME) * 1000);
|
||||
if (board.isGameOver() || board.isGameWon())
|
||||
{
|
||||
gameRunning = false;
|
||||
}
|
||||
if (!isPaused)
|
||||
elapsedTime = difftime(time(NULL), startTime) - totalpausetime;
|
||||
char flags[5];
|
||||
char tim[5];
|
||||
sprintf(flags, "%03d", minesLeft);
|
||||
sprintf(tim, "%03d", elapsedTime);
|
||||
attron(COLOR_PAIR(3));
|
||||
mvprintw(1, 1, "%s", flags);
|
||||
attroff(COLOR_PAIR(3));
|
||||
if (!isPaused)
|
||||
mvprintw(1, 5, ":)");
|
||||
else
|
||||
mvprintw(1, 5, ":|");
|
||||
attron(COLOR_PAIR(3));
|
||||
mvprintw(1, 8, "%s", tim);
|
||||
attroff(COLOR_PAIR(3));
|
||||
refresh();
|
||||
|
||||
for (int y = 0; y < boardSize.y; y++)
|
||||
{
|
||||
for (int x = 0; x < boardSize.x; x++)
|
||||
{
|
||||
Cell::State state = board.getCellStateAt(x, y);
|
||||
|
||||
char displayChar = ' ';
|
||||
if (state == Cell::State::Flagged)
|
||||
{
|
||||
displayChar = 'F';
|
||||
}
|
||||
else if (state == Cell::State::Revealed)
|
||||
{
|
||||
Cell::Content content = board.revealCellAt(x, y).getContent();
|
||||
if (content == Cell::Content::Mine)
|
||||
{
|
||||
attron(COLOR_PAIR(7));
|
||||
displayChar = '+';
|
||||
}
|
||||
else if (content != Cell::Content::Empty)
|
||||
{
|
||||
displayChar = '0' + static_cast<int>(content);
|
||||
}
|
||||
}
|
||||
else if (state == Cell::State::Hidden)
|
||||
{
|
||||
displayChar = '#';
|
||||
}
|
||||
else if (state == Cell::State::NotAMine)
|
||||
{
|
||||
displayChar = 'X';
|
||||
}
|
||||
|
||||
if (x == cursorX && y == cursorY)
|
||||
{
|
||||
attron(A_REVERSE);
|
||||
}
|
||||
|
||||
if (displayChar == 'F')
|
||||
{
|
||||
attron(COLOR_PAIR(9));
|
||||
}
|
||||
|
||||
if (displayChar >= '1' && displayChar <= '8')
|
||||
{
|
||||
attron(COLOR_PAIR(displayChar - '0'));
|
||||
}
|
||||
if (displayChar == 'X')
|
||||
{
|
||||
attron(COLOR_PAIR(9));
|
||||
}
|
||||
|
||||
mvaddch(y + 2, x + 1, displayChar);
|
||||
|
||||
if (displayChar == 'X')
|
||||
{
|
||||
attroff(COLOR_PAIR(9));
|
||||
}
|
||||
|
||||
if (displayChar >= '1' && displayChar <= '8')
|
||||
{
|
||||
attroff(COLOR_PAIR(displayChar - '0'));
|
||||
}
|
||||
|
||||
if (displayChar == 'F')
|
||||
{
|
||||
attroff(COLOR_PAIR(9));
|
||||
}
|
||||
|
||||
if (displayChar == '+')
|
||||
{
|
||||
attroff(COLOR_PAIR(7));
|
||||
}
|
||||
|
||||
if (x == cursorX && y == cursorY)
|
||||
{
|
||||
attroff(A_REVERSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c = getch();
|
||||
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':
|
||||
return;
|
||||
break;
|
||||
case 'z':
|
||||
board.revealCellAt(cursorX, cursorY);
|
||||
break;
|
||||
case 'x':
|
||||
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();
|
||||
break;
|
||||
case 'p':
|
||||
if (isPaused)
|
||||
{
|
||||
pauseTime = difftime(time(NULL), startPauseTime);
|
||||
totalpausetime += pauseTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
startPauseTime = time(NULL);
|
||||
}
|
||||
isPaused = !isPaused;
|
||||
break;
|
||||
case 'c':
|
||||
auto neighbors = board.getNeighborsOf(cursorX, cursorY);
|
||||
for (const Cell &neighbor : neighbors)
|
||||
{
|
||||
if (neighbor.getState() == Cell::State::Flagged)
|
||||
continue;
|
||||
board.revealCellAt(neighbor.getPosition().x, neighbor.getPosition().y);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (isPaused)
|
||||
continue;
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
if (board.isGameOver())
|
||||
{
|
||||
char flags[3];
|
||||
char tim[3];
|
||||
sprintf(flags, "%03d", minesLeft);
|
||||
sprintf(tim, "%03d", elapsedTime);
|
||||
attron(COLOR_PAIR(3));
|
||||
mvprintw(1, 1, "%s", flags);
|
||||
attroff(COLOR_PAIR(3));
|
||||
mvprintw(1, 5, "X(");
|
||||
attron(COLOR_PAIR(3));
|
||||
mvprintw(1, 8, "%s", tim);
|
||||
attroff(COLOR_PAIR(3));
|
||||
refresh();
|
||||
while ((c = getch()) != 'q')
|
||||
{
|
||||
if (c == 'r')
|
||||
{
|
||||
Board newBoard(boardSize.x, boardSize.y, board.getMineCount());
|
||||
startGame(newBoard);
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (board.isGameWon())
|
||||
{
|
||||
char flags[3];
|
||||
char tim[3];
|
||||
sprintf(flags, "%03d", minesLeft);
|
||||
sprintf(tim, "%03d", elapsedTime);
|
||||
attron(COLOR_PAIR(3));
|
||||
mvprintw(1, 1, "%s", flags);
|
||||
attroff(COLOR_PAIR(3));
|
||||
mvprintw(1, 5, "B)");
|
||||
attron(COLOR_PAIR(3));
|
||||
mvprintw(1, 8, "%s", tim);
|
||||
attroff(COLOR_PAIR(3));
|
||||
refresh();
|
||||
int c;
|
||||
while ((c = getch()) != 'q')
|
||||
{
|
||||
if (c == 'r')
|
||||
{
|
||||
Board newBoard(boardSize.x, boardSize.y, board.getMineCount());
|
||||
startGame(newBoard);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
setlocale(LC_ALL, "");
|
||||
initscr();
|
||||
noecho();
|
||||
cbreak();
|
||||
keypad(stdscr, TRUE);
|
||||
nodelay(stdscr, TRUE);
|
||||
|
||||
start_color();
|
||||
init_pair(1, COLOR_BLUE, COLOR_BLACK);
|
||||
init_pair(2, COLOR_GREEN, COLOR_BLACK);
|
||||
init_pair(3, COLOR_RED, COLOR_BLACK);
|
||||
init_pair(4, COLOR_BLUE, COLOR_BLACK);
|
||||
init_pair(5, COLOR_RED, COLOR_BLACK);
|
||||
init_pair(6, COLOR_CYAN, COLOR_BLACK);
|
||||
init_pair(7, COLOR_BLACK, COLOR_WHITE);
|
||||
init_pair(8, COLOR_WHITE, COLOR_BLACK);
|
||||
init_pair(9, COLOR_RED, COLOR_WHITE);
|
||||
|
||||
int choice = 0;
|
||||
int max_choice = 2;
|
||||
curs_set(0);
|
||||
|
||||
while (true)
|
||||
{
|
||||
usleep((1000 / MAX_TIME) * 1000);
|
||||
|
||||
mvprintw(1, 1, "Select a difficulty:");
|
||||
attron(A_REVERSE);
|
||||
mvprintw(2, 2, choice == 0 ? "Beginner (9x9, 10 mines)" : "");
|
||||
attroff(A_REVERSE);
|
||||
mvprintw(2, 2, choice == 0 ? "" : "Beginner (9x9, 10 mines)");
|
||||
attron(A_REVERSE);
|
||||
mvprintw(3, 2, choice == 1 ? "Intermediate (16x16, 40 mines)" : "");
|
||||
attroff(A_REVERSE);
|
||||
mvprintw(3, 2, choice == 1 ? "" : "Intermediate (16x16, 40 mines)");
|
||||
attron(A_REVERSE);
|
||||
mvprintw(4, 2, choice == 2 ? "Expert (16x30, 99 mines)" : "");
|
||||
attroff(A_REVERSE);
|
||||
mvprintw(4, 2, choice == 2 ? "" : "Expert (16x30, 99 mines)");
|
||||
refresh();
|
||||
|
||||
int ch = getch();
|
||||
switch (ch)
|
||||
{
|
||||
case KEY_UP:
|
||||
choice = (choice > 0) ? choice - 1 : max_choice;
|
||||
break;
|
||||
case KEY_DOWN:
|
||||
choice = (choice < max_choice) ? choice + 1 : 0;
|
||||
break;
|
||||
case '\n':
|
||||
int w, h, mines;
|
||||
if (choice == 0)
|
||||
{
|
||||
w = 9;
|
||||
h = 9;
|
||||
mines = 10;
|
||||
}
|
||||
else if (choice == 1)
|
||||
{
|
||||
w = 16;
|
||||
h = 16;
|
||||
mines = 40;
|
||||
}
|
||||
else
|
||||
{
|
||||
w = 16;
|
||||
h = 30;
|
||||
mines = 99;
|
||||
}
|
||||
Board board(w, h, mines);
|
||||
clear();
|
||||
startGame(board);
|
||||
curs_set(2);
|
||||
endwin();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user