using Microsoft.Win32; using RBG.Helpers; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Media; using System.Windows.Media.Effects; using System.Windows.Media.Imaging; using System.Windows.Shapes; namespace RBG_Server.WPF { /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow : Window { // Global Styles etc private static readonly SolidColorBrush borderBrush = new(Color.FromRgb(255, 0, 0)); GameServer gameServer; // Game board bytes byte[] gameBoard; public MainWindow() { InitializeComponent(); } /// /// Browse for file button /// /// /// private void BrowseButton_Click(object sender, RoutedEventArgs e) { OpenFileDialog ofd = new(); ofd.Filter = "Image Files|*.png;*.jpg"; if (ofd.ShowDialog() == true) { ImageSourceTextBox.Text = ofd.FileName; UpdateImage(); } } /// /// Update the image when the text box looses focus /// /// /// private void ImageSourceTextBox_LostFocus(object sender, RoutedEventArgs e) { UpdateImage(); } private void UpdateImage() { try { using FileStream fs = File.OpenRead(ImageSourceTextBox.Text); using MemoryStream ms = new(); fs.CopyTo(ms); // Load into memory, so the server can also utilise the stream gameBoard = ms.ToArray(); BitmapImage gameBoardImage = new(); gameBoardImage.BeginInit(); gameBoardImage.StreamSource = new MemoryStream(gameBoard, false); //gameBoardImage.CacheOption = BitmapCacheOption.OnLoad; // Must preload the image into memory, before it is unloaded gameBoardImage.EndInit(); PreviewImage.Source = gameBoardImage; RedrawGrid(); } catch (Exception e) { Console.WriteLine(e); } } /// /// Creates and starts the game server /// /// /// private void StartServerButton_Click(object sender, RoutedEventArgs e) { gameServer = new((byte)StartingRow.Value, (byte)StartingColumn.Value, (byte)NumberOfRows.Value, (byte)NumberOfColumns.Value, (byte)ZoomBoxStartRow.Value, (byte)ZoomBoxStartColumn.Value, (byte)ZoomBoxSpan.Value, (byte)ZoomBoxSpan.Value, gameBoard); } private void NumberOfRows_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { if (IsInitialized) { StartingRow.Maximum = NumberOfRows.Value; ZoomBoxStartRow.Maximum = NumberOfRows.Value; ZoomBoxSpan.Maximum = Math.Min(NumberOfRows.Value - ZoomBoxStartRow.Value, NumberOfColumns.Value - ZoomBoxStartColumn.Value); RedrawGrid(); } } private void NumberOfColumns_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { if (IsInitialized) { StartingColumn.Maximum = NumberOfColumns.Value; ZoomBoxStartColumn.Maximum = NumberOfColumns.Value; ZoomBoxSpan.Maximum = Math.Min(NumberOfRows.Value - ZoomBoxStartRow.Value, NumberOfColumns.Value - ZoomBoxStartColumn.Value); RedrawGrid(); } } /// /// Draws the overlay grid, clearing existing elements if necessary /// private void RedrawGrid() { PreviewImageOverlay.RowDefinitions.Clear(); PreviewImageOverlay.ColumnDefinitions.Clear(); PreviewImageOverlay.Children.Clear(); for (int i = 0; i < NumberOfColumns.Value; i++) { PreviewImageOverlay.ColumnDefinitions.Add(new ColumnDefinition()); } for (int i = 0; i < NumberOfRows.Value; i++) { PreviewImageOverlay.RowDefinitions.Add(new RowDefinition()); } // Draw the overlay Rectangle overlay = new(); overlay.Fill = new SolidColorBrush(Color.FromArgb(128, 0, 128, 255)); _ = PreviewImageOverlay.Children.Add(overlay); Grid.SetRow(overlay, (int)ZoomBoxStartRow.Value); Grid.SetColumn(overlay, (int)ZoomBoxStartColumn.Value); Grid.SetRowSpan(overlay, (int)ZoomBoxSpan.Value); Grid.SetColumnSpan(overlay, (int)ZoomBoxSpan.Value); // Draw the starting position overlay = new(); overlay.Fill = new SolidColorBrush(Color.FromArgb(64, 0, 255, 128)); _ = PreviewImageOverlay.Children.Add(overlay); Grid.SetRow(overlay, (int)StartingRow.Value); Grid.SetColumn(overlay, (int)StartingColumn.Value); // Draw grids onto the preview image for (int v = 0; v < PreviewImageOverlay.RowDefinitions.Count; v++) { for (int u = 0; u < PreviewImageOverlay.ColumnDefinitions.Count; u++) { Border border = new(); border.BorderBrush = borderBrush; border.BorderThickness = new Thickness(2); _ = PreviewImageOverlay.Children.Add(border); Grid.SetRow(border, v); Grid.SetColumn(border, u); } } } /// /// Generates the game board overlay grid /// The grid should be appropriately sized before using this /// private void GenerateGameBoard(List players) { // Create an array of BoardCells Rows*Columns in size BoardCell[] cells = new BoardCell[PreviewImageOverlay.RowDefinitions.Count * PreviewImageOverlay.ColumnDefinitions.Count]; List unaddedPlayers = players; for (int i = 0; i < cells.Length; i++) { cells[i] = new BoardCell(); Queue removedPlayers = new(); foreach (Player item in unaddedPlayers) { if ((item.Row * PreviewImageOverlay.ColumnDefinitions.Count) + item.Column == i) { removedPlayers.Enqueue(item); } } while (removedPlayers.Count > 0) { Player removed = removedPlayers.Dequeue(); _ = unaddedPlayers.Remove(removed); _ = cells[i].CellStack.Children.Add(removed); } _ = PreviewImageOverlay.Children.Add(cells[i]); cells[i].CellButton.Tag = new Tuple(i % PreviewImageOverlay.ColumnDefinitions.Count, i / PreviewImageOverlay.ColumnDefinitions.Count); Grid.SetColumn(cells[i], i % PreviewImageOverlay.ColumnDefinitions.Count); Grid.SetRow(cells[i], i / PreviewImageOverlay.ColumnDefinitions.Count); } } /// /// Represents a cell on the board; as a grid /// class BoardCell : Grid { // brush the button should have static SolidColorBrush buttonBrush = new(Color.FromArgb(1, 255, 255, 255)); // Each cell has a uniform grid that public UniformGrid CellStack { get; } = new() { Columns = 4, HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch }; public Button CellButton { get; } = new() { Content = "", }; Queue removedPlayers = new(); // Grid parentGrid; // Parent grid is now this itself public BoardCell() : base() { CellButton.MouseEnter += CellButton_MouseEnter; CellButton.MouseLeave += CellButton_MouseLeave; CellButton.Click += CellButton_Click; CellButton.Background = buttonBrush; CellStack.Columns = 2; CellStack.VerticalAlignment = VerticalAlignment.Top; // Add our grid and button _ = Children.Add(CellStack); _ = Children.Add(CellButton); } private void CellButton_Click(object sender, RoutedEventArgs e) { Button cellButton = sender as Button; System.Diagnostics.Debug.WriteLine("Button at {0}, {1} was clicked", ((Tuple)cellButton.Tag).Item2, ((Tuple)cellButton.Tag).Item1); } private void CellButton_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e) { //throw new NotImplementedException(); } private void CellButton_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e) { //throw new NotImplementedException(); } } private void Sliders_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { if (IsInitialized) { RedrawGrid(); } } private void GridVisibilityToggleButton_Click(object sender, RoutedEventArgs e) { PreviewImageOverlay.Visibility = PreviewImageOverlay.Visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible; } private static readonly string[] sprites = new string[] { "01.png", "02.png", "03.png", "04.png", }; private void DrawRandomizedPlayers_Click(object sender, RoutedEventArgs e) { List randPlayers = new(); Random rand = new(); int count = rand.Next(1, 10); for (int i = 0; i < count; i++) { // Create a new player at a random location Player p = new("Player " + i, rand.Next(0, PreviewImageOverlay.RowDefinitions.Count), rand.Next(PreviewImageOverlay.ColumnDefinitions.Count)); // Load the sprite string[] keys = new string[loadedSprites.Count]; loadedSprites.Keys.CopyTo(keys, 0); string key = keys[rand.Next(keys.Length)]; if (key.ToLower().EndsWith(".gif")) { try { AnimatedBitmapImage animatedBitmap = new(new MemoryStream(loadedSprites[key], false), p.PlayerSprite, Application.Current.Dispatcher); } catch { BitmapSource pSprite; { // Scope & create the base data for pSprite BitmapImage src = new(); src.BeginInit(); src.StreamSource = new MemoryStream(loadedSprites[key],false); src.EndInit(); pSprite = src; } // Recolour: if (rand.Next(1, 4) == 1) { pSprite = ImageProcessing.UpdatePixelColours(pSprite, 0.0, 0.0, 1.0); } p.PlayerSprite.Source = pSprite; } } else { BitmapSource pSprite; { // Scope & create the base data for pSprite BitmapImage src = new(); src.BeginInit(); src.StreamSource = new MemoryStream(loadedSprites[key], false); src.EndInit(); pSprite = src; } // Recolour: if (rand.Next(1, 4) == 1) { pSprite = ImageProcessing.UpdatePixelColours(pSprite, 0.0, 0.0, 1.0); } p.PlayerSprite.Source = pSprite; } randPlayers.Add(p); } GenerateGameBoard(randPlayers); } ConcurrentDictionary loadedSprites = new(); Task loadingFiles; private void GameSpritesBrowser_Click(object sender, RoutedEventArgs e) { OpenFileDialog ofd = new(); ofd.Filter = "Image Files|*.png;*.jpg;*.gif"; ofd.Multiselect = true; if (ofd.ShowDialog() == true) { string[] resultFiles = ofd.FileNames; loadedSprites.Clear(); // Run the following work in a separate thread loadingFiles = Task.Run(() => { // Continue in parallel _ = Parallel.ForEach(resultFiles, (file) => { MemoryStream ms = new(); using FileStream fs = File.OpenRead(file); if (new FileInfo(file).Length > 512e6) // greater than 512 kB; resize the sprite to something more reasonable { BitmapImage gameBoardImage = new(); gameBoardImage.BeginInit(); gameBoardImage.StreamSource = fs; gameBoardImage.CacheOption = BitmapCacheOption.Default; gameBoardImage.EndInit(); double vRate = 128 / (double)gameBoardImage.PixelHeight; // Resize so the largest dimension is 128 px double hRate = 128 / (double)gameBoardImage.PixelWidth; TransformedBitmap modified = new(gameBoardImage, new ScaleTransform(gameBoardImage.PixelWidth * Math.Min(vRate, hRate), gameBoardImage.PixelHeight * Math.Min(vRate, hRate))); PngBitmapEncoder encoder = new(); encoder.Frames.Add(BitmapFrame.Create(modified)); encoder.Save(ms); } else { fs.CopyTo(ms); } _ = loadedSprites.TryAdd(file, ms.ToArray()); }); // Invoke the dispatcher to add all of the sprites to the loaded sprites list _ = Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Render, new Action(() => { LoadedSprites.Children.Clear(); foreach (KeyValuePair item in loadedSprites) { if (item.Key.ToLower().EndsWith(".gif")) { try { /* // Must write-out the stream to MediaElement gifMedia = new(); gifMedia.LoadedBehavior = MediaState.Play; gifMedia.Source = new Uri(item.Key); _ = LoadedSprites.Children.Add(gifMedia); */ Image spriteImage = new(); RBG.Helpers.AnimatedBitmapImage animatedBitmap = new(new MemoryStream(item.Value, false), spriteImage, Application.Current.Dispatcher); _ = LoadedSprites.Children.Add(spriteImage); } catch { // On failed, use the cached data BitmapImage image = new(); image.BeginInit(); image.StreamSource = new MemoryStream(item.Value, false); image.EndInit(); Image spriteImage = new() { Source = image }; _ = LoadedSprites.Children.Add(spriteImage); } } else { BitmapImage image = new(); image.BeginInit(); image.StreamSource = new MemoryStream(item.Value, false); image.EndInit(); Image spriteImage = new() { Source = image }; _ = LoadedSprites.Children.Add(spriteImage); } } })); }); } } } }