using Snake.Model; using Snake.Persistance; namespace view { public partial class Form1 : Form { #region Fields const int line_width = 2; // Width of the table lines SnakeLevel? CurrentLevel; // Current game state SnakeLevelLoader level_loader; // Level loader #endregion #region Constructor public Form1() { InitializeComponent(); level_loader = new SnakeLevelLoader(Path.Combine(Directory.GetCurrentDirectory(), "levels.json")); } #endregion #region Draw Funciton /// /// Draws the current map to the canvas /// private void Draw() { if (CurrentLevel == null) return; using Graphics canvas_graphics = Canvas.CreateGraphics(); using Bitmap bmp = new Bitmap(Canvas.Width, Canvas.Height, canvas_graphics); using Graphics graphics = Graphics.FromImage(bmp); graphics.Clear(Color.White); var canvas_size = Math.Min(Canvas.Width, Canvas.Height); double cell_size = (double)(canvas_size - (CurrentLevel.Size + 1) * line_width) / CurrentLevel.Size; // vertical lines for (int i = 0; i <= CurrentLevel.Size; i++) { int x = (int)(i * (cell_size + line_width)); graphics.FillRectangle(Brushes.Black, x, 0, line_width, canvas_size); } // horizontal lines for (int i = 0; i <= CurrentLevel.Size; i++) { int y = (int)(i * (cell_size + line_width)); graphics.FillRectangle(Brushes.Black, 0, y, canvas_size, line_width); } // Ssnake foreach (var pos in CurrentLevel.Snake) { int x = (int)(cell_size * pos.X + line_width * (pos.X + 1)); int y = (int)(cell_size * pos.Y + line_width * (pos.Y + 1)); graphics.FillRectangle(Brushes.Green, x, y, (int)cell_size, (int)cell_size); } // snake head int snake_head_x = (int)(cell_size * CurrentLevel.SnakeHead.X + line_width * (CurrentLevel.SnakeHead.X + 1)); int snake_head_y = (int)(cell_size * CurrentLevel.SnakeHead.Y + line_width * (CurrentLevel.SnakeHead.Y + 1)); graphics.FillRectangle(Brushes.Red, snake_head_x, snake_head_y, (int)cell_size, (int)cell_size); switch (CurrentLevel.SnakeHeadDirection) { case SnakeLevel.SnakeDirection.Up: graphics.DrawLine(Pens.Black, snake_head_x + (int)cell_size / 2, snake_head_y, snake_head_x + (int)cell_size / 2, snake_head_y + (int)cell_size); graphics.DrawLine(Pens.Black, snake_head_x + (int)cell_size / 2, snake_head_y, snake_head_x, snake_head_y + (int)cell_size / 2); graphics.DrawLine(Pens.Black, snake_head_x + (int)cell_size / 2, snake_head_y, snake_head_x + (int)cell_size, snake_head_y + (int)cell_size / 2); break; case SnakeLevel.SnakeDirection.Down: graphics.DrawLine(Pens.Black, snake_head_x + (int)cell_size / 2, snake_head_y, snake_head_x + (int)cell_size / 2, snake_head_y + (int)cell_size); graphics.DrawLine(Pens.Black, snake_head_x + (int)cell_size / 2, snake_head_y + (int)cell_size, snake_head_x, snake_head_y + (int)cell_size / 2); graphics.DrawLine(Pens.Black, snake_head_x + (int)cell_size / 2, snake_head_y + (int)cell_size, snake_head_x + (int)cell_size, snake_head_y + (int)cell_size / 2); break; case SnakeLevel.SnakeDirection.Left: graphics.DrawLine(Pens.Black, snake_head_x, snake_head_y + (int)cell_size / 2, snake_head_x + (int)cell_size, snake_head_y + (int)cell_size / 2); graphics.DrawLine(Pens.Black, snake_head_x, snake_head_y + (int)(cell_size / 2), snake_head_x + (int)(cell_size / 2), snake_head_y); graphics.DrawLine(Pens.Black, snake_head_x, snake_head_y + (int)(cell_size / 2), snake_head_x + (int)(cell_size / 2), snake_head_y + (int)cell_size); break; case SnakeLevel.SnakeDirection.Right: graphics.DrawLine(Pens.Black, snake_head_x, snake_head_y + (int)cell_size / 2, snake_head_x + (int)cell_size, snake_head_y + (int)cell_size / 2); graphics.DrawLine(Pens.Black, snake_head_x + (int)cell_size, snake_head_y + (int)(cell_size / 2), snake_head_x + (int)(cell_size / 2), snake_head_y); graphics.DrawLine(Pens.Black, snake_head_x + (int)cell_size, snake_head_y + (int)(cell_size / 2), snake_head_x + (int)(cell_size / 2), snake_head_y + (int)cell_size); break; } // obstacles foreach (var pos in CurrentLevel.Obstacles) { int x = (int)(cell_size * pos.X + line_width * (pos.X + 1)); int y = (int)(cell_size * pos.Y + line_width * (pos.Y + 1)); graphics.FillRectangle(Brushes.Black, x, y, (int)cell_size, (int)cell_size); } //eggs foreach (var pos in CurrentLevel.Eggs) { int x = (int)(cell_size * pos.X + line_width * (pos.X + 1)); int y = (int)(cell_size * pos.Y + line_width * (pos.Y + 1)); graphics.FillRectangle(Brushes.Pink, x, y, (int)cell_size, (int)cell_size); } canvas_graphics.DrawImage(bmp, 0, 0); } #endregion #region Form event handlers private void Form1_KeyDown(object sender, KeyEventArgs e) { if (CurrentLevel == null) return; switch (e.KeyCode) { case Keys.Up: CurrentLevel.SnakeHeadDirection = SnakeLevel.SnakeDirection.Up; break; case Keys.Down: CurrentLevel.SnakeHeadDirection = SnakeLevel.SnakeDirection.Down; break; case Keys.Left: CurrentLevel.SnakeHeadDirection = SnakeLevel.SnakeDirection.Left; break; case Keys.Right: CurrentLevel.SnakeHeadDirection = SnakeLevel.SnakeDirection.Right; break; case Keys.Escape: if (CurrentLevel.State == SnakeLevel.GameState.Running) CurrentLevel.Pause(); break; } } private void btnStart_Click(object sender, EventArgs e) { if (CurrentLevel is null || CurrentLevel.State == SnakeLevel.GameState.Dead) { var MapChooser = new MapChooser(this.level_loader); MapChooser.ShowDialog(); if (MapChooser.SelectedLevel != null) { CurrentLevel = level_loader.LoadLevel(MapChooser.SelectedLevel); CurrentLevel.GameStateUpdate += CurrentLevel_GameStateUpdate; CurrentLevel.GameUpdate += CurrentLevel_GameUpdate; } else { CurrentLevel = null; } } if (CurrentLevel != null) { CurrentLevel.Start(); this.Focus(); } } private void Form1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e) { if (CurrentLevel?.State is SnakeLevel.GameState.Running) { e.IsInputKey = true; } } private void Form1_Shown(object sender, EventArgs e) { btnEndGame.Visible = false; // It will trigger a whole repaint the first time it is shown unless this is done level_loader.ReadAllDataAsync().ContinueWith((task) => { if (!this.IsDisposed) // The form can be closed before the file is loaded { this.Invoke(() => { if (task.IsCompletedSuccessfully) { lblLoading.Visible = false; btnStart.Enabled = true; btnStart.Focus(); } else { MessageBox.Show("Failed to read data"); } }); } }); } private void button1_Click(object sender, EventArgs e) { CurrentLevel = null; btnEndGame.Visible = false; Canvas.Refresh(); txtScore.Text = ""; } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { if (CurrentLevel != null) { if (CurrentLevel.State == SnakeLevel.GameState.Running) { CurrentLevel.Pause(); } CurrentLevel = null; } } #endregion #region Game event handlers private void CurrentLevel_GameUpdate(object? sender, EventArgs e) { if (CurrentLevel == null) return; this.Invoke(() => { Draw(); txtScore.Text = CurrentLevel.Score.ToString(); }); } private void CurrentLevel_GameStateUpdate(object? sender, SnakeLevel.GameState e) { if (CurrentLevel == null) return; this.Invoke(() => { switch (e) { case SnakeLevel.GameState.Running: btnStart.Visible = false; btnEndGame.Visible = false; Draw(); break; case SnakeLevel.GameState.Paused: btnStart.Visible = true; btnEndGame.Visible = true; break; case SnakeLevel.GameState.Dead: btnStart.Visible = true; MessageBox.Show($"Skill issue.\nScore: {CurrentLevel.Score}", "Snake", MessageBoxButtons.OK, MessageBoxIcon.Information); break; } }); } #endregion } }