initial commit

This commit is contained in:
2025-06-21 22:11:30 +03:00
commit dbe6dc82cf
11 changed files with 987 additions and 0 deletions

284
src/Board.cpp Normal file
View File

@@ -0,0 +1,284 @@
#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;
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 < w && ny >= 0 && ny < h)
{
if (cells[ny][nx].getContent() == Cell::Content::Mine)
{
count++;
}
}
}
}
cell.setContent(static_cast<Cell::Content>(count));
}
}
}
}
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 &current = 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));
}
}
}
}
}
}
}
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;
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)
{
if (cells[ny][nx].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;
}

38
src/Cell.cpp Normal file
View File

@@ -0,0 +1,38 @@
#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;
}

58
src/Vector2.cpp Normal file
View 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;
}

299
src/main.cpp Normal file
View File

@@ -0,0 +1,299 @@
#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;
while (gameRunning)
{
usleep((1000 / MAX_TIME) * 1000);
if (board.isGameOver() || board.isGameWon())
{
gameRunning = false;
}
elapsedTime = difftime(time(NULL), startTime);
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));
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);
board.regenerateBoard();
break;
}
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;
}
}
}