From 6739115f07b39da3ed59572faf393f9846bd4784 Mon Sep 17 00:00:00 2001 From: Daniel Jones Date: Fri, 12 Jun 2020 00:03:37 +0930 Subject: basic game working --- Board.cpp | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- Board.h | 15 +++++- MainWindow.cpp | 151 ++++++++++++++++++++++++++++++++++++++++++++++-------- MainWindow.h | 4 ++ Tile.cpp | 20 ++++++-- Tile.h | 17 +++++-- icons.h | 31 ++++++++--- icons/bomb.gif | Bin 96 -> 176 bytes 8 files changed, 358 insertions(+), 38 deletions(-) diff --git a/Board.cpp b/Board.cpp index 83117d7..39caacf 100644 --- a/Board.cpp +++ b/Board.cpp @@ -15,11 +15,14 @@ #include "Board.h" -Board:: Board(int width, int height) +Board:: Board(int width, int height, int minecount) { this->width = width; this->height = height; this->tilecount = width*height; + this->minecount = minecount; + this->game_running = true; + this->game_won = false; /* create tiles */ for (int x = 0; x < height; x++) @@ -30,6 +33,8 @@ Board:: Board(int width, int height) tiles.push_back(t); } } + generate_mines(); + retrieve_neighbors(); } Board::~Board() @@ -45,6 +50,24 @@ Board::reveal_tile_at(int x, int y) { Tile *tile = get_tile_at(x, y); printf("revealing x: %d y: %d\n", tile->get_x(), tile->get_y()); + if (tile->is_mine()) + { + /* lose */ + reveal_all_mines(); + game_running = false; + game_won = false; + return false; + } + tile->reveal(); + reveal_neighbor_tiles(tile->get_x(), tile->get_y()); + if (check_win()) + { + /* win */ + reveal_all_mines(); + game_won = true; + game_running = false; + } + return true; } @@ -53,4 +76,137 @@ Board::new_game(int x, int y) { } +void +Board::reveal_all_mines() +{ + Tile *tile; + for(auto t = tiles.begin(); t != tiles.end(); ++t) + { + tile = (*t).get(); + /* explicitly unflag tile */ + tile->clear_flag(Tile::FLAGGED); // FIXME: doesnt work as intended, it should unflag all tiles + if (tile->is_mine()) + { + tile->set_flag(Tile::REVEALED); + } + } +} +void +Board::retrieve_neighbors() +{ + Tile *tile; + for(auto t = tiles.begin(); t != tiles.end(); ++t) + { + tile = (*t).get(); + int badup = 0, baddown = 0, badleft = 0, badright = 0; + if (tile->get_x()-1<0) badleft = 1; + if (tile->get_x()+1>width-1) badright = 1; + + if (tile->get_y()-1<0) badup = 1; + if (tile->get_y()+1>height-1) baddown = 1; + + if (!badleft && !badup) tile->neighbors[0] = get_tile_at(tile->get_x()-1, tile->get_y()-1); + if (!badup) tile->neighbors[1] = get_tile_at(tile->get_x(), tile->get_y()-1); + if (!badright && !badup) tile->neighbors[2] = get_tile_at(tile->get_x()+1, tile->get_y()-1); + + if (!badleft) tile->neighbors[3] = get_tile_at(tile->get_x()-1, tile->get_y()); + if (!badright) tile->neighbors[4] = get_tile_at(tile->get_x()+1, tile->get_y()); + + if (!badleft && !baddown) tile->neighbors[5] = get_tile_at(tile->get_x()-1, tile->get_y()+1); + if (!baddown) tile->neighbors[6] = get_tile_at(tile->get_x(), tile->get_y()+1); + if (!badright && !baddown) tile->neighbors[7] = get_tile_at(tile->get_x()+1, tile->get_y()+1); + count_neighbor_mines(tile); + } +} + +void +Board::count_neighbor_mines(Tile *tile) +{ + Tile *neighbor; + int mines = 0; + for (int i = 0; i < 8; i++) + { + neighbor = tile->get_neighbor(i); + if (neighbor) + { + if (neighbor->is_mine()) + { + mines++; + } + } + } + tile->set_neighbor_mine_count(mines); + printf("init tile: x: %d, y: %d, neighbor mines: %d\n", tile->get_x(), tile->get_y(), tile->get_neighbor_mine_count()); +} + +void +Board::generate_mines() +{ + srand(time(NULL)); + int mx, my; + Tile *tile; + for (int i = 0; i < minecount; i++) + { +replace_mine: + mx = rand() % width; + my = rand() % height; + tile = get_tile_at(mx, my); + if (tile->is_mine()) /* don't overwrite mines */ + goto replace_mine; + tile->set_flag(Tile::MINE); + } +} + +bool +Board::reveal_neighbor_tiles(int x, int y) +{ + /* + * reveal all neighbor tiles that are empty, recursively + */ + + Tile *tile, *neighbor; + tile = get_tile_at(x, y); + + /* always reveal the first tile even if it has a neighbor mines */ + tile->set_flag(Tile::REVEALED); + if (tile->is_mine()) + return false; + if (tile->get_neighbor_mine_count() == 0) + { + /* if our tile is empty we want to reveal its immediate neighbors */ + for (int nc = 0; nc < 8; nc++) + { + neighbor = tile->get_neighbor(nc); + if (neighbor != nullptr && !neighbor->is_mine() && !neighbor->is_revealed()) + { + reveal_neighbor_tiles(neighbor->get_x(), neighbor->get_y()); + } + } + } + return true; +} + +bool +Board::check_win() +{ + int allowedmines = minecount; + int safetiles = (height * width) - minecount; + int correctflags = 0; + int correcttiles = 0; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + Tile *tile = get_tile_at(x, y); + if (tile->is_mine() && tile->is_flagged()) + correctflags++; + else if (!(tile->is_mine()) && (tile->is_revealed())) + correcttiles++; + } + } + + return (correctflags == allowedmines) || (correcttiles == safetiles); + +} diff --git a/Board.h b/Board.h index 64a3571..55e1f09 100644 --- a/Board.h +++ b/Board.h @@ -18,21 +18,34 @@ #include #include #include +#include +#include #include "Tile.h" class Board { public: - Board(int width, int height); + Board(int width, int height, int minecount); ~Board(); int get_tile_count(){ return tilecount; }; Tile *get_tile_at(int x, int y); bool reveal_tile_at(int x, int y); void new_game(int x, int y); + bool is_game_running() { return this->game_running; }; + bool is_game_won() { return this->game_won; }; + bool check_win(); private: std::vector> tiles; + void generate_mines(); + void retrieve_neighbors(); + void count_neighbor_mines(Tile *tile); + bool reveal_neighbor_tiles(int x, int y); + bool game_won; + void reveal_all_mines(); int tilecount; + int minecount; int width; int height; + bool game_running; }; #endif diff --git a/MainWindow.cpp b/MainWindow.cpp index 5e3f300..bdb0a84 100644 --- a/MainWindow.cpp +++ b/MainWindow.cpp @@ -14,6 +14,7 @@ */ #include "MainWindow.h" +#include "fxdefs.h" #include // rand #include #include @@ -23,19 +24,22 @@ FXDEFMAP(MainWindow) MainWindow_Map[]= //________Message_Type____________ID____________Message_Handler_______ FXMAPFUNC(SEL_COMMAND, MainWindow::UI_Tile, MainWindow::on_Tile_Click), FXMAPFUNC(SEL_COMMAND, MainWindow::UI_New, MainWindow::on_New_Click), + FXMAPFUNC(SEL_RIGHTBUTTONPRESS, MainWindow::UI_Tile, MainWindow::on_Tile_Right_Click), }; FXIMPLEMENT(MainWindow, FXMainWindow, MainWindow_Map, ARRAYNUMBER(MainWindow_Map)) MainWindow::MainWindow(FXApp *a) - : FXMainWindow(a, "MyProgram 0.1", nullptr, nullptr, DECOR_ALL, 0,0,200,150) + : FXMainWindow(a, "foxminesweeper", nullptr, nullptr, DECOR_ALL, 0,0,200,150) { board = nullptr; matrix = nullptr; app = a; create_ui(); - int width = 9; - int height = 9; - new_game(width, height, 10); + int width = 15; + int height = 15; + int minecount = 33; + game_over = false; + new_game(width, height, minecount); } MainWindow::~MainWindow() @@ -47,6 +51,17 @@ void MainWindow::create() { FXMainWindow::create(); + bomb_icon->create(); + flag_icon->create(); + empty_icon->create(); + tile_1_icon->create(); + tile_2_icon->create(); + tile_3_icon->create(); + tile_4_icon->create(); + tile_5_icon->create(); + tile_6_icon->create(); + tile_7_icon->create(); + tile_8_icon->create(); show(PLACEMENT_SCREEN); } @@ -73,14 +88,13 @@ MainWindow::create_ui() // Horizontal divider line new FXHorizontalSeparator(buttonFrame, SEPARATOR_RIDGE|LAYOUT_FILL_X); - FXText *gametxt = new FXText(buttonFrame); - gametxt->setText("test"); // Exit button new FXButton(buttonFrame, "&Exit", nullptr, app, FXApp::ID_QUIT, BUTTON_NORMAL|LAYOUT_FILL_X); new FXButton(buttonFrame, "&New Game", nullptr, this, MainWindow::UI_New, BUTTON_NORMAL|LAYOUT_FILL_X); /* create icons */ - bomb_icon = new FXGIFIcon(app, tile_1, IMAGE_KEEP); + bomb_icon = new FXGIFIcon(app, bomb, IMAGE_KEEP); + flag_icon = new FXGIFIcon(app, flag, IMAGE_KEEP); empty_icon = new FXGIFIcon(app, empty, IMAGE_KEEP); tile_1_icon = new FXGIFIcon(app, tile_1, IMAGE_KEEP); tile_2_icon = new FXGIFIcon(app, tile_2, IMAGE_KEEP); @@ -90,16 +104,6 @@ MainWindow::create_ui() tile_6_icon = new FXGIFIcon(app, tile_6, IMAGE_KEEP); tile_7_icon = new FXGIFIcon(app, tile_7, IMAGE_KEEP); tile_8_icon = new FXGIFIcon(app, tile_8, IMAGE_KEEP); - bomb_icon->create(); - empty_icon->create(); - tile_1_icon->create(); - tile_2_icon->create(); - tile_3_icon->create(); - tile_4_icon->create(); - tile_5_icon->create(); - tile_6_icon->create(); - tile_7_icon->create(); - tile_8_icon->create(); } void @@ -110,13 +114,13 @@ MainWindow::new_game(int width, int height, int minecount) puts("starting new game.."); int tilecount = width*height; tile_buttons.clear(); - board = new Board(width, height); + board = new Board(width, height, minecount); matrix = new FXMatrix(scroll_area, width, MATRIX_BY_COLUMNS|LAYOUT_CENTER_Y|LAYOUT_CENTER_X); for (int i = 0; i < tilecount; i++) { - std::shared_ptr b(new FXButton(matrix, "ok", nullptr, this, UI_Tile)); + std::shared_ptr b(new FXButton(matrix, "", nullptr, this, UI_Tile)); tile_buttons.push_back(b); - b->setIcon(tile_1_icon); + b->setIcon(empty_icon); } printf("rows: %d columns: %d\n", matrix->getNumRows(), matrix->getNumColumns()); draw_buttons(); @@ -125,21 +129,124 @@ MainWindow::new_game(int width, int height, int minecount) void MainWindow::draw_buttons() { - + int x, y; + FXButton *button; + Tile *tile; + for(auto b = tile_buttons.begin(); b != tile_buttons.end(); ++b) + { + button = (*b).get(); + x = matrix->colOfChild(button); + y = matrix->rowOfChild(button); + tile = board->get_tile_at(x, y); + if (tile->is_flagged()) + { + printf("flagged at: %d, %d\n", tile->get_x(), tile->get_y()); + button->setIcon(flag_icon); + button->setFrameStyle(0); + continue; + } + else if (!tile->is_revealed()) + continue; + else if (tile->is_mine()) + { + button->setIcon(bomb_icon); + button->setFrameStyle(0); + } + else + { + switch (tile->get_neighbor_mine_count()) + { + case 1: + button->setIcon(tile_1_icon); + break; + case 2: + button->setIcon(tile_2_icon); + break; + case 3: + button->setIcon(tile_3_icon); + break; + case 4: + button->setIcon(tile_4_icon); + break; + case 5: + button->setIcon(tile_5_icon); + break; + case 6: + button->setIcon(tile_6_icon); + break; + case 7: + button->setIcon(tile_7_icon); + break; + case 8: + button->setIcon(tile_8_icon); + break; + default: + button->setIcon(empty_icon); + break; + } + button->setFrameStyle(0); + } + } } long MainWindow::on_Tile_Click(FXObject *sender, FXSelector sel, void *data) { + if (game_over == true) + return 1; int x = 0, y = 0; + Tile *tile; FXButton *button = dynamic_cast(sender); if (!button) return 0; - +// button->killFocus(); // let user control with keyboard x = matrix->colOfChild(button); y = matrix->rowOfChild(button); + tile = board->get_tile_at(x, y); + if (tile->is_revealed()) + return 1; + if (tile->is_flagged()) + return 1; printf("button pressed: x=%d, y=%d\n", x, y); board->reveal_tile_at(x, y); + if (!board->is_game_running() && !board->is_game_won()) + { + /* lost */ + puts("you lose the game"); + FXMessageBox *msgbox = new FXMessageBox(app, "Game Over", "You lost", nullptr, FX::MBOX_OK); + msgbox->create(); + msgbox->show(); + game_over = true; + } + else if (!board->is_game_running() && board->is_game_won()) + { + /* won */ + puts("you won the game"); + FXMessageBox *msgbox = new FXMessageBox(app, "Game Over", "You win", nullptr, FX::MBOX_OK); + msgbox->create(); + msgbox->show(); + game_over = true; + } + button->setFrameStyle(0); + draw_buttons(); + return 1; +} + +long +MainWindow::on_Tile_Right_Click(FXObject *sender, FXSelector sel, void *data) +{ + if (game_over == true) + return 1; + int x = 0, y = 0; + Tile *tile; + FXButton *button = dynamic_cast(sender); + if (!button) + return 0; + x = matrix->colOfChild(button); + y = matrix->rowOfChild(button); + tile = board->get_tile_at(x, y); + printf("right click on: %d, %d\n", tile->get_x(), tile->get_y()); + tile->toggle_flag(Tile::FLAGGED); draw_buttons(); return 1; } diff --git a/MainWindow.h b/MainWindow.h index 1eb3514..0cc0841 100644 --- a/MainWindow.h +++ b/MainWindow.h @@ -18,6 +18,7 @@ #include #include +#include #include #include "Board.h" #include "icons.h" @@ -41,6 +42,7 @@ class MainWindow : public FXMainWindow /* Event handlers */ long on_Tile_Click(FXObject *sender, FXSelector sel, void *data); long on_New_Click(FXObject *sender, FXSelector sel, void *data); + long on_Tile_Right_Click(FXObject *sender, FXSelector sel, void *data); FXApp *get_app(){ return app; }; @@ -52,6 +54,7 @@ class MainWindow : public FXMainWindow void create_ui(); void new_game(int width, int height, int minecount); void draw_buttons(); + bool game_over; FXHorizontalFrame *contents; // Content frame FXVerticalFrame *canvasFrame; // Canvas frame FXVerticalFrame *buttonFrame; // Button frame @@ -62,6 +65,7 @@ class MainWindow : public FXMainWindow std::vector> tile_buttons; /* icons */ FXIcon *bomb_icon; + FXIcon *flag_icon; FXIcon *empty_icon; FXIcon *tile_1_icon; FXIcon *tile_2_icon; diff --git a/Tile.cpp b/Tile.cpp index a2c1b3e..dfad786 100644 --- a/Tile.cpp +++ b/Tile.cpp @@ -19,16 +19,17 @@ Tile::Tile(int x, int y) { this->x = x; this->y = y; - //bomb_icon = new FXGIFIcon(app, bomb, IMAGE_KEEP); - //empty_icon = new FXGIFIcon(app, empty, IMAGE_KEEP); - //this->setIcon(bomb_icon); + this->neighbor_mine_count = 0; + // + // better way? + for (int x = 0; x < 8; x++) + neighbors[x] = 0; + this->flags = HIDDEN; /* make sure we init the flags */ } Tile::~Tile() { - //delete bomb_icon; - //delete empty_icon; } void @@ -41,3 +42,12 @@ Tile::reveal() { this->set_flag(REVEALED); } + +Tile * +Tile::get_neighbor(int i) +{ + if (i < 0 || i > 8) + return nullptr; + return neighbors[i]; +} + diff --git a/Tile.h b/Tile.h index 176893f..371e121 100644 --- a/Tile.h +++ b/Tile.h @@ -33,12 +33,23 @@ class Tile FLAGGED = 1 << 2, REVEALED = 1 << 3, }; - enum STATE get_flags() { return flags; } - void set_flag(enum STATE flag) { this->flags = (STATE) (this->flags | flag); } - void set_flags(enum STATE flags) { this->flags = flags; } + enum STATE get_flags() { return flags; }; + void set_flag(enum STATE flag) { this->flags = (STATE) (this->flags | flag); }; + void clear_flag(enum STATE flag) { this->flags = (STATE) (this->flags & ~flag); }; + void toggle_flag(enum STATE flag) { this->flags = (STATE) (this->flags ^ flag); }; + bool is_mine() { return this->flags & MINE; }; + bool is_flagged() { return this->flags & FLAGGED; }; + bool is_revealed() { return this->flags & REVEALED; }; + int get_neighbor_mine_count() { return this->neighbor_mine_count; }; + void set_neighbor_mine_count(int count) { this->neighbor_mine_count = count; }; + Tile *get_neighbor(int i); + + Tile *neighbors[8]; // one day make private with getters/setters private: + void set_flags(enum STATE flags) { this->flags = flags; }; int x; int y; + int neighbor_mine_count; enum STATE flags; }; diff --git a/icons.h b/icons.h index 12cd247..76dae72 100644 --- a/icons.h +++ b/icons.h @@ -2,12 +2,17 @@ /* created by reswrap from file icons/bomb.gif */ const unsigned char bomb[]={ - 0x47,0x49,0x46,0x38,0x39,0x61,0x10,0x00,0x10,0x00,0xc2,0x05,0x00,0x00,0x00,0x00, - 0x1a,0x1a,0x1a,0x1c,0x1c,0x1c,0x28,0x28,0x28,0x2b,0x2b,0x2b,0xf5,0xe3,0x2c,0xf5, - 0xe3,0x2c,0xf5,0xe3,0x2c,0x21,0xf9,0x04,0x01,0x0a,0x00,0x07,0x00,0x2c,0x00,0x00, - 0x00,0x00,0x10,0x00,0x10,0x00,0x00,0x03,0x25,0x78,0xba,0xdc,0x7e,0x20,0x3e,0x16, - 0xeb,0x54,0xd5,0x5e,0xb0,0xe4,0xa5,0xdc,0xd7,0x85,0x22,0x44,0x8a,0x5e,0x99,0xa2, - 0xe7,0xb7,0xba,0x6d,0x43,0x64,0xd9,0xe0,0x08,0x74,0x15,0x94,0x4f,0x02,0x00,0x3b + 0x47,0x49,0x46,0x38,0x39,0x61,0x10,0x00,0x10,0x00,0xe3,0x00,0x00,0x00,0x00,0x00, + 0x73,0x72,0x72,0x87,0x85,0x85,0x8e,0x8c,0x8c,0x90,0x8e,0x8e,0x9e,0x9c,0x9c,0xbe, + 0xbc,0xbc,0xc8,0xc6,0xc6,0xcf,0xcc,0xcc,0xd8,0xd5,0xd5,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0xfe,0x28, + 0x6f,0x6e,0x69,0x69,0x69,0x69,0x2d,0x63,0x68,0x61,0x61,0x61,0x61,0x61,0x61,0x61, + 0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x6e,0x20,0x6e,0x6f,0x6f,0x6f,0x20, + 0x77,0x65,0x20,0x63,0x61,0x6e,0x27,0x74,0x00,0x21,0xf9,0x04,0x01,0x0a,0x00,0x0f, + 0x00,0x2c,0x00,0x00,0x00,0x00,0x10,0x00,0x10,0x00,0x00,0x04,0x31,0xf0,0xc9,0x49, + 0xab,0xbd,0xf8,0x81,0xcd,0x33,0xff,0x80,0x05,0x8e,0xe1,0x44,0x82,0x66,0x51,0x9c, + 0x9d,0x66,0x18,0xc9,0xc0,0x4a,0x40,0x10,0x1c,0xc2,0xac,0x6d,0x08,0xa2,0x73,0x04, + 0x82,0x6e,0xc7,0x2a,0xd1,0x8a,0x46,0x13,0xc9,0x83,0xca,0x38,0x1f,0x11,0x00,0x3b }; /* created by reswrap from file icons/empty.gif */ @@ -19,6 +24,20 @@ const unsigned char empty[]={ 0xed,0x0f,0xa3,0x9c,0xb4,0xda,0x8b,0xb3,0x3e,0x05,0x00,0x3b }; +/* created by reswrap from file icons/flag.gif */ +const unsigned char flag[]={ + 0x47,0x49,0x46,0x38,0x39,0x61,0x10,0x00,0x10,0x00,0xc2,0x04,0x00,0x00,0x00,0x00, + 0xc4,0x00,0x00,0xe4,0x00,0x00,0xff,0x00,0x00,0xd8,0xd5,0xd5,0xd8,0xd5,0xd5,0xd8, + 0xd5,0xd5,0xd8,0xd5,0xd5,0x21,0xfe,0x28,0x6f,0x6e,0x69,0x69,0x69,0x69,0x2d,0x63, + 0x68,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61, + 0x61,0x6e,0x20,0x6e,0x6f,0x6f,0x6f,0x20,0x77,0x65,0x20,0x63,0x61,0x6e,0x27,0x74, + 0x00,0x21,0xf9,0x04,0x01,0x0a,0x00,0x04,0x00,0x2c,0x00,0x00,0x00,0x00,0x10,0x00, + 0x10,0x00,0x00,0x03,0x2a,0x48,0xba,0xdc,0xfe,0x70,0x8d,0x11,0xe5,0x0c,0x75,0x4e, + 0x01,0x00,0xd4,0x53,0xe7,0x39,0x60,0xd8,0x91,0xe5,0x20,0x7e,0xe0,0x1a,0x69,0x6e, + 0x25,0x8e,0x72,0x0c,0xcd,0xa7,0x83,0xef,0x34,0xcf,0x2f,0xbe,0x99,0x22,0x01,0x00, + 0x3b + }; + /* created by reswrap from file icons/tile_1.gif */ const unsigned char tile_1[]={ 0x47,0x49,0x46,0x38,0x39,0x61,0x10,0x00,0x10,0x00,0xc2,0x04,0x00,0x01,0x00,0xfb, diff --git a/icons/bomb.gif b/icons/bomb.gif index e3f06fe..b335de6 100644 Binary files a/icons/bomb.gif and b/icons/bomb.gif differ -- cgit v1.2.3