initial commit
This commit is contained in:
284
src/Board.cpp
Normal file
284
src/Board.cpp
Normal 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 ¤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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
38
src/Cell.cpp
Normal 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
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;
|
||||
}
|
||||
299
src/main.cpp
Normal file
299
src/main.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user