/* The following Minesweeper game uses GTK#. The code for the game is extremely similar to the Caml version, however the implementation of the GUI is somewhat different and has been restructured to work with GTK# and to take advantage of the GTK# graphics package. */ using Gtk; using GtkSharp; using System; using System.Collections; // Minesweeper board configuration class Config { // Number of columns on board private int nb_cols; public int Nb_cols{ get { return nb_cols; } } // Number of rows on board private int nb_rows; public int Nb_rows{ get { return nb_rows; } } // Number of mines on game board private int nb_mines; public int Nb_mines{ get { return nb_mines; } } // Default game settings public Config() { nb_cols = 5; nb_rows = 5; nb_mines = 5; } } // Minesweeper board cell struct Cell { // Whether the cell has a mine on it or not private bool mined; public bool Mined { get { return mined; } set { mined = value; } } // Whether the cell has been seen by the player or not private bool seen; public bool Seen { get { return seen; } set { seen = value; } } // Whether the player has placed a flag on the cell or not private bool flag; public bool Flag{ get { return flag; } set { flag = value; } } // The number of mines contained in the cells neighbouring this one private int nbm; public int Nbm { get { return nbm; } set { nbm = value; } } } // Minesweeper board of cells unsafe class Board { // Grid of cells for the minefield public Cell[,] grid; // Creates a new board of a specific size public Board(int* width, int* height) { grid = new Cell[*width, *height]; } } // Utility class for representing positions class Point { public int x; public int y; public Point(int x, int y) { this.x = x; this.y = y; } } // Minesweeper game with GTK# interface class Game { // Board configuration private static Config cf; // The game board private static Board b; // Random number generator private static Random r; // Used by cells_to_see function private static bool[,] visited; // Used for the GUI buttons for each board cell private static Button[,] gui_grid; // Whether we are in flagging mode or not private static bool flag_mode = false; // GUI label for the number of unopened/unflagged cells remaining private static Label cells_remaining; // GUI label for the number of unopened/unflagged cells remaining private static int nb_cells_remaining; // Layout table for game GUI private static Table game_table; // Whether the game is over or not private static bool bool_game_over = false; // Status GUI label to print messages for the player private static Label game_status; // GUI label for the number of flags remaining private static Label flags_remaining; // Number of flags remaining private static int nb_flags_remaining; // GUI button for turning flagging on/off. private static Button flag_button; // Sets the random number generator seed public static void generate_seed() { // Use line this for random mine placement each time r = new Random((int)(System.DateTime.Now.Ticks % System.Int32.MaxValue)); // Use line this for the same mine placement each time //r = new Random(1); } // Randomly lay a certain number of mines on the grid public static unsafe void lay_mines(int no_of_cells, int no_of_mines) { generate_seed(); // Positions to lay mines later int* minePositions = stackalloc int[no_of_mines]; int* current = minePositions; while(current < minePositions + no_of_mines) { // Pick a position to place a mine at int pos = r.Next(no_of_cells); // Check if this position has been chosen already int* search = minePositions; while (search < current) { if (*search == pos) { // Already chosen, so pick another position goto Found; } ++search; } // Store this position *current = pos; ++current; Found: ; } // Lay each mine for (int i = 0; i < no_of_mines; ++i) { int* position = (minePositions + i); b.grid[*position / cf.Nb_rows, *position % cf.Nb_rows].Mined = true; } } // Returns true if a position is within the bounds of the game board public static bool valid(Point p) { return (p.x >= 0 && p.x < cf.Nb_cols && p.y >= 0 && p.y < cf.Nb_rows); } // Returns the list of neighbours for a given board cell public static ArrayList neighbors(int x, int y) { ArrayList l = new ArrayList(); Point n1 = new Point(x - 1, y - 1); if (valid(n1)) { l.Insert(0, n1); } Point n2 = new Point(x - 1, y); if (valid(n2)) { l.Insert(0, n2); } Point n3 = new Point(x - 1, y + 1); if (valid(n3)) { l.Insert(0, n3); } Point n4 = new Point(x, y - 1); if (valid(n4)) { l.Insert(0, n4); } Point n5 = new Point(x, y + 1); if (valid(n5)) { l.Insert(0, n5); } Point n6 = new Point(x + 1, y - 1); if (valid(n6)) { l.Insert(0, n6); } Point n7 = new Point(x + 1, y); if (valid(n7)) { l.Insert(0, n7); } Point n8 = new Point(x + 1, y + 1); if (valid(n8)) { l.Insert(0, n8); } return l; } // Counts the number of mines in the neighbouring cells of a board position public static unsafe int* count_mined_adj(int i, int j) { int z = 0; foreach (Point p in neighbors(i,j)) { if (b.grid[p.x,p.y].Mined) { ++z; } } return &z; } // Sets the counter on a cell for the number of neighbouring mines public static unsafe void set_count(Cell* c, int i, int j) { if (!c->Mined) { c->Nbm = *count_mined_adj(i,j); } } // Initialises the game board public static void initialize_board() { cf = new Config(); unsafe { int width = cf.Nb_cols; int height = cf.Nb_rows; b = new Board(&width, &height); visited = new bool[cf.Nb_cols, cf.Nb_rows]; } lay_mines(cf.Nb_cols * cf.Nb_rows, cf.Nb_mines); // Set the counters for the number of mines in each cell for(int i = 0; i < cf.Nb_cols; ++i) { for(int j = 0; j < cf.Nb_rows; ++j) { unsafe { fixed (Cell* c = &b.grid[i, j]) { set_count(c, i, j); } } } } nb_cells_remaining = cf.Nb_cols * cf.Nb_rows - cf.Nb_mines; nb_flags_remaining = cf.Nb_mines; } // Returns a list of the cells that should be seen when a non-mined cell is opened, // using standard Minesweeper rules public static ArrayList cells_to_see(int i, int j) { // This cell has now been visited when looking for cells to see visited[i, j] = true; ArrayList z = new ArrayList(); z.Insert(0, new Point(i, j)); return cells_to_see(z); } // Returns the list of cells that need to be seen, given the list of cells that are going // to be seen public static unsafe ArrayList cells_to_see(ArrayList l) { // If no cells to open if (l.Count == 0) { return l; } // Split the list into a head and tail Point head = (Point)l[0]; l.RemoveAt(0); if (b.grid[head.x, head.y].Nbm != 0) { // Add the head to the list of cells to see for the tail and return it ArrayList z = cells_to_see(l); z.Insert(0, head); return z; } else { // Collect a list of empty neighbours and non-empty neighbours for the head cell ArrayList emptyNeighbors = new ArrayList(); ArrayList nonEmptyNeighbors = new ArrayList(); foreach (Point p in neighbors(head.x, head.y)) { // Calculate pointer offset into grid/visited array that the p refers to int offset = p.x * cf.Nb_rows + p.y; fixed (Cell* cp = b.grid) { // Calculate pointer to grid cell, remembering to take Cell memory size into account Cell c = cp[offset * (sizeof(Cell) / sizeof(Int32))]; fixed (bool* vp = visited) { // Pointer to visited array for point p bool* v = vp + offset; // Consider only if cell is not mined, not flagged, not seen already and has not been // visited by the cells_to_see function yet if (!(c.Mined || c.Flag || c.Seen || *v)) { // Cell has now been visited by the function *v = true; // Put into either the empty or non-empty neighbours list if (c.Nbm == 0) { emptyNeighbors.Insert(0, p); } else { nonEmptyNeighbors.Insert(0, p); } } } } } // Get the cells to see for the empty neighbours cells and the tail, then return // these with the head and the non-empty neighbours cells emptyNeighbors.AddRange(l); ArrayList z = cells_to_see(emptyNeighbors); z.AddRange(nonEmptyNeighbors); z.Insert(0, head); return z; } } // Construct the GUI public static void build_gui() { Application.Init(); Window window = new Window("Minesweeper"); window.DeleteEvent += new DeleteEventHandler(delete_event); window.BorderWidth = 5; gui_grid = new Button[cf.Nb_cols, cf.Nb_rows]; Table outerTable = new Table(4,1, false); outerTable.ColumnSpacing = 5; outerTable.RowSpacing = 5; window.Add(outerTable); game_status = new Label("Minesweeper"); outerTable.Attach(game_status, 0,1,0,1); Table topTable = new Table (1,3, true); outerTable.Attach(topTable, 0,1,1,2); cells_remaining = new Label("["+nb_cells_remaining+"]"); topTable.Attach(cells_remaining, 0,1,0,1); flag_button = new Button("Off"); topTable.Attach(flag_button, 1,2,0,1); flag_button.Clicked += new EventHandler(flag_button_clicked); flags_remaining = new Label("["+nb_flags_remaining+"]"); topTable.Attach(flags_remaining, 2,3,0,1); HSeparator horizLine = new HSeparator(); outerTable.Attach(horizLine, 0,1,2,3); game_table = new Table((uint)cf.Nb_rows, (uint)cf.Nb_cols, true); outerTable.Attach(game_table, 0, 1, 3, 4); game_table.BorderWidth=1; for (uint i = 0; i < cf.Nb_cols; ++i) { for (uint j = 0; j < cf.Nb_rows; ++j) { gui_grid[i, j] = new Button(" "); game_table.Attach(gui_grid[i, j], i, i + 1, j, j + 1); gui_grid[i, j].Clicked += new EventHandler(cellClick); } } window.ShowAll(); Application.Run(); } // Handle the delete event static void delete_event(object obj, DeleteEventArgs args) { Application.Quit(); } // Handle when board cells are clicked on static void cellClick(object obj, EventArgs args) { Button bu = (Button)obj; int x = 0, y = 0; if (!bool_game_over) { // Find which button was clicked on for (int i = 0; i < cf.Nb_cols; i++) { for (int j = 0; j < cf.Nb_rows; j++) { if (bu == gui_grid[i, j]) { x = i; y = j; } } } if (flag_mode) { flag_cell(x,y); } else { if (b.grid[x, y].Mined) { game_over(false); } else { // Open up a cell foreach (Point p in cells_to_see(x, y)) { open_cell(p.x, p.y); } } } } } // Handle when the flag button is clicked public static void flag_button_clicked(object obj, EventArgs args) { if (bool_game_over) { Application.Quit(); } if (flag_mode) { flag_mode = false; ((Button)obj).Label = "Off"; } else { flag_mode = true; ((Button)obj).Label = "On"; } } // Opens up a cell public static unsafe void open_cell(int i, int j) { fixed (Cell* c = &b.grid[i, j]) { c->Seen=true; cells_remaining.Text = "[" + --nb_cells_remaining + "]"; // If the cell has neighbouring mines, we replace the button with a label if (c->Nbm != 0) { game_table.Remove(gui_grid[i, j]); Label l = new Label("" + c->Nbm); game_table.Attach(l, (uint)i, (uint)i + 1, (uint)j, (uint)j + 1); l.Show(); } else { gui_grid[i, j].Hide(); } if (nb_cells_remaining == 0) { game_over(true); } } } // Flip the flagged status of a cell public static void flag_cell(int i, int j) { if (b.grid[i, j].Flag) { b.grid[i, j].Flag = false; gui_grid[i, j].Label = ""; flags_remaining.Text = "" + (++nb_flags_remaining); } else { b.grid[i, j].Flag = true; gui_grid[i, j].Label = "!"; flags_remaining.Text = "" + (--nb_flags_remaining); } } // Called when the game is over public static unsafe void game_over(bool hasWon) { // Show the unopened cells for(int i = 0; i < cf.Nb_cols; i++) { for(int j = 0; j < cf.Nb_rows; j++) { fixed (Cell* current = &b.grid[i, j]) { if (!current->Seen) { if (current->Mined) { gui_grid[i, j].Label = "*"; } else { if (current->Flag) { gui_grid[i, j].Label = "X"; } else if ((*current).Nbm != 0) { game_table.Remove(gui_grid[i, j]); Label l = new Label("" + current->Nbm); game_table.Attach(l, (uint)i, (uint)i + 1, (uint)j, (uint)j + 1); l.Show(); } else { gui_grid[i, j].Hide(); } } } } } } // Tell the user if they have won or lost game_status.Text = (hasWon ? "You Won!" : "Game over!"); // Flag button becomes the quit button flag_button.Label = "Quit"; bool_game_over = true; } public static void Main(string[] args) { initialize_board(); // Uncomment the line below for the mine positions to be written to the terminal print_board(); build_gui(); } // Prints out the mine locations to the terminal for debugging public static void print_board() { Console.Write("Minefield layout (for debugging):\n\n "); for (int j = 0; j < cf.Nb_cols; j++) { Console.Write("" + j + " "); } Console.Write("\n--"); for (int j = 0; j < cf.Nb_cols; j++) { Console.Write("---"); } Console.Write("\n"); for (int i = 0; i < cf.Nb_rows; i++) { Console.Write(i + " |"); for (int j = 0; j < cf.Nb_cols; j++) { if (b.grid[j, i].Mined) { Console.Write(" m "); } else { Console.Write(" " + b.grid[j, i].Nbm + " "); } } Console.Write("\n"); } } }