using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; using System.IO; using System.Xml; using System.Xml.Serialization; using sharpallegro; using sharpupdater; namespace sharpmines { public class Program : Allegro { private enum Status { Stop, Playing, GameOver, Won, Highscore, Exit } #region Text // Letters + Numbers + Symbols private const int CHARACTERS_NUMBER = 26 + 10 + 8; private const int SMALL_TEXT_WIDTH = 12; private const int MEDIUM_TEXT_WIDTH = 14; private const int BIG_TEXT_WIDTH = 14; #endregion private const int TILE_WIDTH = 23; private const int ACTION_TRESHOLD = 300; private const int FONT_WIDTH = 12; private const int FONT_HEIGHT = 16; private const int LEFT_PANEL_WIDTH = 97; private static Difficulty difficulty; private static int flags = 0; private static int time = 0; private static int elapsedTime = 0; private static Status status = Status.Stop; private static Tile[,] minefield; private static Point[] stars; private static bool leftClick = false; private static bool rightClick = false; private static bool middleClick = false; private static bool showCredits = false; private static Highscore score; static void Main() { SharpUpdater updater = new SharpUpdater("http://elvenprogrammer.themanaworld.org/updates/sharpmines.xml"); updater.UpdateProduct("sharpmines", "0.0.2"); DifficultyDialog dialog = new DifficultyDialog(); if (dialog.ShowDialog() != DialogResult.OK) { return; } difficulty = new Difficulty(dialog.DifficultyLevel); // Load highscores if (File.Exists("highscores.xml")) { FileStream stream = new FileStream("highscores.xml", FileMode.Open); try { XmlSerializer serializer = new XmlSerializer(typeof(Highscore[])); if (stream.Length > 0) { XmlReader reader = new XmlTextReader(stream); Highscore[] highscores = (Highscore[])serializer.Deserialize(reader); reader.Close(); Highscore.Highscores.AddRange(highscores); } } catch (Exception ex) { MessageBox.Show("Unable to load highscore\n" + ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { stream.Close(); } } if (!InitializeAllegro()) { return; } AllegroCycle(); } private static bool InitializeAllegro() { allegro_init(); install_timer(); install_keyboard(); install_mouse(); set_window_title("sharpmines"); int width = difficulty.Width * TILE_WIDTH; if (width % 2 == 1) width++; if (width < 232) width = 232; int height = difficulty.Height * TILE_WIDTH + 51; if (height % 2 == 1) height++; if (difficulty.DifficultyLevel == Difficulty.Difficulties.Advanced) { width = 720; height = 480; } set_color_depth(32); if (set_gfx_mode(GFX_AUTODETECT_WINDOWED, width, height, 0, 0) != 0) { allegro_message(string.Format("Unable to install Allegro: {0}", allegro_error)); return false; } return true; } private static void AllegroCycle() { NewGame(); BITMAP buffer = create_bitmap(SCREEN_W, SCREEN_H); BITMAP panel = load_bitmap("images/panel.bmp", NULL); BITMAP baseTile = load_bitmap("images/base.bmp", NULL); BITMAP coverTile = load_bitmap("images/cover.bmp", NULL); BITMAP flagTile = load_bitmap("images/flag.bmp", NULL); BITMAP[] mineTiles = new BITMAP[2]; mineTiles[0] = load_bitmap("images/mine.bmp", NULL); mineTiles[1] = load_bitmap("images/mine2.bmp", NULL); BITMAP text = load_bitmap("images/mediumtext.bmp", NULL); BITMAP smallText = load_bitmap("images/smalltext.bmp", NULL); BITMAP highscores = load_bitmap("images/highscores.bmp", NULL); BITMAP credits = load_bitmap("images/credits.bmp", NULL); stars = new Point[200]; Random random = new Random(DateTime.Now.Millisecond); for (int i = 0; i < stars.Length; i++) { stars[i] = new Point(random.Next(SCREEN_W), random.Next(SCREEN_H)); } int lastAction = Timer.Instance.Time; int white = makecol(255, 255, 255); while (!key[KEY_ESC] && status != Status.Exit) { if (status == Status.Playing && time != 0) { elapsedTime = Timer.Instance.elapsedTime(time) / 1000; if (elapsedTime > 999) elapsedTime = 999; } if (status != Status.Won && status != Status.Highscore && status != Status.GameOver) { bool won = true; for (int j = 0; j < difficulty.Height; j++) { for (int i = 0; i < difficulty.Width; i++) { won = (!minefield[i, j].Mine && minefield[i, j].Status) || (minefield[i, j].Mine && minefield[i, j].Flag); if (!won) break; } if (!won) break; } if (won) { status = Status.Won; score = new Highscore(string.Empty, elapsedTime); if (Highscore.IsHighscore(score)) { status = Status.Highscore; Highscore.Add(score); } } } if (Timer.Instance.elapsedTime(lastAction) > ACTION_TRESHOLD) { // Reset game if (key[KEY_F2]) { lastAction = Timer.Instance.Time; NewGame(); } if (key[KEY_F1]) { lastAction = Timer.Instance.Time; showCredits = !showCredits; } if (status == Status.Highscore) { if (string.IsNullOrEmpty(score.Name) || score.Name.Length < 3) { for (int i = KEY_A; i < KEY_Z; i++) { if (key[i]) { char c = (char)('A' + (char)(i - KEY_A)); score.Name += c; lastAction = Timer.Instance.Time; } } if (key[KEY_BACKSPACE] && score.Name.Length > 0) { score.Name = score.Name.Substring(0, score.Name.Length - 1); lastAction = Timer.Instance.Time; } } if (score.Name.Length == 3) { score = null; status = Status.Won; } } } int fieldX = (SCREEN_W - difficulty.Width * TILE_WIDTH) / 2; #region Game logic if (status == Status.Stop || status == Status.Playing) { if ((mouse_b & 1) > 0) { if (!leftClick) leftClick = true; } else { if (leftClick) { int x = (mouse_x - fieldX) / (TILE_WIDTH); int y = mouse_y / (TILE_WIDTH); LeftClick(x, y); if (x < difficulty.Width && y < difficulty.Height) { if (minefield[x, y].Status == false && !minefield[x, y].Flag) { if (minefield[x, y].Mine) status = Status.GameOver; else { FloodFill8(x, y); } if (status == Status.Stop) { status = Status.Playing; time = Timer.Instance.Time; } } } leftClick = false; } } if ((mouse_b & 2) > 0) { if (!rightClick) rightClick = true; } else { if (rightClick) { int x = (mouse_x - fieldX) / (TILE_WIDTH); int y = mouse_y / (TILE_WIDTH); if (x < difficulty.Width && y < difficulty.Height) { if (!minefield[x, y].Status) { if (minefield[x, y].Flag) { minefield[x, y].Flag = false; flags--; } else if (!minefield[x, y].Flag && flags < difficulty.Mines) { minefield[x, y].Flag = true; flags++; } } } rightClick = false; } } if ((mouse_b & 4) > 0) { if (!middleClick) middleClick = true; } else { if (middleClick) { int x = (mouse_x - fieldX) / (TILE_WIDTH); int y = mouse_y / (TILE_WIDTH); if (x < difficulty.Width && y < difficulty.Height) { LeftClickFill(x, y); } middleClick = false; } } } #endregion #region Rendering show_mouse(NULL); clear_bitmap(buffer); for (int i = 0; i < stars.Length; i++) { putpixel(buffer, stars[i].X, stars[i].Y, makecol(255, 255, 255)); } for (int j = 0; j < difficulty.Height; j++) { for (int i = 0; i < difficulty.Width; i++) { int x = i * TILE_WIDTH + fieldX; int y = j * TILE_WIDTH; if (minefield[i, j].Status) { draw_sprite(buffer, baseTile, x, y); } else { switch (minefield[i, j].Rotation) { case 1: draw_sprite_h_flip(buffer, coverTile, x, y); break; case 2: draw_sprite_v_flip(buffer, coverTile, x, y); break; case 3: draw_sprite_vh_flip(buffer, coverTile, x, y); break; default: draw_sprite(buffer, coverTile, x, y); break; } } if ( status == Status.GameOver && minefield[i, j].Mine ) { draw_sprite(buffer, mineTiles[(Timer.Instance.Time / 1000) % 2], x, y); } if (minefield[i, j].Flag) { draw_sprite(buffer, flagTile, x, y); } if (minefield[i, j].Status && !minefield[i, j].Mine ) { int boundingMines = BoundingMines(i, j); if (boundingMines > 0) { PrintCenteredString(buffer, text, boundingMines.ToString(), x + TILE_WIDTH / 2, j * TILE_WIDTH + TILE_WIDTH / 2, 14); } } } } draw_sprite(buffer, panel, (SCREEN_W - panel.w) / 2, TILE_WIDTH * difficulty.Height); string minesText = string.Format("{0:000}", difficulty.Mines - flags); PrintString(buffer, text, minesText, 58 + ((SCREEN_W - panel.w) / 2), TILE_WIDTH * difficulty.Height + 7, 12); string timeText = string.Format("{0:000}", elapsedTime); PrintString(buffer, text, timeText, 58 + ((SCREEN_W - panel.w) / 2), TILE_WIDTH * difficulty.Height + 30, 12); int textX = // Left space ((SCREEN_W - panel.w) / 2) + // Left panel width LEFT_PANEL_WIDTH + // Right panel center ((panel.w - LEFT_PANEL_WIDTH) / 2); int textY = // Game field height (TILE_WIDTH * difficulty.Height) + // Right panel center (panel.h / 2); if (status == Status.GameOver) { PrintCenteredString(buffer, smallText, "GAME OVER", textX, textY, -1); } else if (status == Status.Won || status == Status.Highscore) { PrintCenteredString(buffer, smallText, "GAME WON!", textX, textY, -1); int highscoreX = (SCREEN_W - highscores.w) / 2; int highscoreY = (SCREEN_H - highscores.h) / 2; draw_sprite(buffer, highscores, highscoreX, highscoreY); for (int i = 0; i < Highscore.Highscores.Count; i++) { Highscore highscore = Highscore.Highscores[i]; string highscoreText = string.Format("{0}: {1:000}", highscore.Name.PadRight(3, '-'), highscore.Time); if (highscore.Name.Length == 3 || (Timer.Instance.Time / 500) % 2 == 0) { PrintString(buffer, smallText, highscoreText, highscoreX + 38, highscoreY + 7 + i * 25, -1); } } } if (showCredits) { int creditsX = (SCREEN_W - credits.w) / 2; int creditsY = (SCREEN_H - credits.h) / 2; draw_sprite(buffer, credits, creditsX, creditsY); PrintString(buffer, smallText, "CREDITS:", creditsX + 38, creditsY + 7, -1); PrintString(buffer, smallText, "Code by\nEugenio Favalli".ToUpper(), creditsX, creditsY + 2 + 1 * 25, 11); PrintString(buffer, smallText, "Iron Plague art\nby Daniel Cook\n(Lostgarden.com)".ToUpper(), creditsX, creditsY + 14 + 2 * 25, 11); } show_mouse(buffer); blit(buffer, screen, 0, 0, 1, 0, SCREEN_W, SCREEN_H); #endregion } } private static void NewGame() { Random random = new Random(); minefield = new Tile[difficulty.Width, difficulty.Height]; for (int j = 0; j < difficulty.Height; j++) { for (int i = 0; i < difficulty.Width; i++) { minefield[i, j] = new Tile(i, j); minefield[i, j].Rotation = random.Next(4); } } for (int i = 0; i < difficulty.Mines; i++) { int x = random.Next(difficulty.Width); int y = random.Next(difficulty.Height); if (minefield[x, y].Mine) i--; minefield[x, y].Mine = true; } status = Status.Stop; time = 1; elapsedTime = 0; flags = 0; } private static void FloodFill8(int x, int y) { if (x >= 0 && x < difficulty.Width && y >= 0 && y < difficulty.Height && !minefield[x, y].Status && BoundingMines(x, y) == 0) { minefield[x, y].Status = true; FloodFill8(x + 1, y); FloodFill8(x - 1, y); FloodFill8(x, y + 1); FloodFill8(x, y - 1); FloodFill8(x - 1, y - 1); FloodFill8(x - 1, y + 1); FloodFill8(x + 1, y - 1); FloodFill8(x + 1, y + 1); } else if (x >= 0 && x < difficulty.Width && y >= 0 && y < difficulty.Height && !minefield[x, y].Status && BoundingMines(x, y) != 0) { minefield[x, y].Status = true; } } private static int BoundingMines(int x, int y) { int boundingMines = 0; for (int j = -1; j < 2; j++) { for (int i = -1; i < 2; i++) { int temp_x = x + i; int temp_y = y + j; if (temp_x >= 0 && temp_y >= 0 && temp_x < difficulty.Width && temp_y < difficulty.Height && !(temp_x == x && temp_y == y)) { if (minefield[temp_x, temp_y].Mine) boundingMines++; } } } return boundingMines; } private static int BoundingFlaggedMines(int x, int y) { int flaggedMines = 0; for (int j = -1; j < 2; j++) { for (int i = -1; i < 2; i++) { int temp_x = x + i; int temp_y = y + j; if (temp_x >= 0 && temp_y >= 0 && temp_x < difficulty.Width && temp_y < difficulty.Height && !(temp_x == x && temp_y == y)) { if (minefield[temp_x, temp_y].Flag) flaggedMines++; } } } return flaggedMines; } private static int StringLength(IntPtr font, string text, int overrideWidth) { int fontWidth = ((BITMAP)font).w / CHARACTERS_NUMBER; return text.Length * (overrideWidth > 0 ? overrideWidth : fontWidth); } private static void PrintCenteredString(IntPtr bmp, IntPtr font, string text, int x, int y, int overrideWidth) { int fontHeight = ((BITMAP)font).h; PrintString(bmp, font, text, x - StringLength(font, text, overrideWidth) / 2, y - (fontHeight / 2), overrideWidth); } private static void PrintString(IntPtr bmp, IntPtr font, string text, int x, int y, int overrideWidth) { // overrideWidth specifies wheter you want to override font widht and let letters overlap int left = 0; int top = 0; int fontWidth = ((BITMAP)font).w / CHARACTERS_NUMBER; int fontHeight = ((BITMAP)font).h; for (int i = 0; i < text.Length; i++) { char c = text[i]; int character = 39; // Question mark if (c >= 'A' && c <= 'Z') { character = c - 'A'; } else if (c >= '0' && c <= '9') { character = (c - '0') + 26; } else if (c == '.') { character = 36; } else if (c == ':') { character = 37; } else if (c == '!') { character = 38; // Exclamation mark } else if (c == '-') { character = 41; } else if (c == '(') { character = 45; } else if (c == ')') { character = 46; } else if (c == '\n') { top++; left = -1; } if (c != ' ' && c != '\n') { masked_blit( font, bmp, character * fontWidth, 0, x + left * (overrideWidth > 0 ? overrideWidth : fontWidth), y + top * fontHeight, fontWidth, fontHeight); } left++; } } private static void LeftClick(int x, int y) { if (x >= 0 && y >= 0 && x < difficulty.Width && y < difficulty.Height) { if (minefield[x, y].Status == false && !minefield[x, y].Flag) { if (minefield[x, y].Mine) status = Status.GameOver; else { FloodFill8(x, y); } if (status == Status.Stop) { status = Status.Playing; time = Timer.Instance.Time; } } } } private static void LeftClickFill(int x, int y) { if (BoundingFlaggedMines(x, y) == BoundingMines(x, y)) { LeftClick(x + 1, y); LeftClick(x - 1, y); LeftClick(x, y + 1); LeftClick(x, y - 1); LeftClick(x - 1, y - 1); LeftClick(x - 1, y + 1); LeftClick(x + 1, y - 1); LeftClick(x + 1, y + 1); } } } }