using Microsoft.Win32; 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 { GameServer gameServer; MemoryStream 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); gameBoard = new(); fs.CopyTo(gameBoard); // Load into memory, so the server can also utilise the stream BitmapImage gameBoardImage = new(); gameBoardImage.BeginInit(); gameBoardImage.StreamSource = gameBoard; //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(); } } private void RedrawGrid() { SolidColorBrush borderBrush = new(Color.FromRgb(255, 0, 0)); 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 elements to go inside the game grid from the provided list of players /// The grid should be appropriately sized before using this /// private void GenerateGameBoard(List players) { SolidColorBrush buttonBrush = new(Color.FromArgb(1, 255, 255, 255)); Grid[] cells = new Grid[PreviewImageOverlay.RowDefinitions.Count * PreviewImageOverlay.ColumnDefinitions.Count]; List unaddedPlayers = players; for (int i = 0; i < cells.Length; i++) { cells[i] = new Grid(); // Each cell has several components: // Stackpanel, which has each sprite in the cell UniformGrid cellStack = new(); cellStack.Columns = 4; //cellStack.ItemWidth = cellStack.Width / 4; 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); _ = cellStack.Children.Add(removed); } _ = cells[i].Children.Add(cellStack); // Button, whose content is empty Button cellButton = new() { Content = "" }; cellButton.Background = buttonBrush; cellButton.Tag = new Tuple(i / PreviewImageOverlay.ColumnDefinitions.Count, i % PreviewImageOverlay.ColumnDefinitions.Count); // Tag is a Tuple cellButton.Click += CellButton_Click; _ = cells[i].Children.Add(cellButton); _ = PreviewImageOverlay.Children.Add(cells[i]); Grid.SetColumn(cells[i], i % PreviewImageOverlay.ColumnDefinitions.Count); Grid.SetRow(cells[i], i / PreviewImageOverlay.ColumnDefinitions.Count); } } /// /// Represents a cell on the board /// class BoardCell { // brush the button should have static SolidColorBrush buttonBrush = new(Color.FromArgb(1, 255, 255, 255)); // Each cell has a uniform grid that UniformGrid cellStack = new() { Columns = 4, HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch }; Button cellButton = new() { Content = "" }; Queue removedPlayers = new(); Grid parentGrid; public BoardCell(Grid parentGrid) { this.parentGrid = parentGrid; } } private void GenerateNewGameBoard(List players) { SolidColorBrush buttonBrush = new(Color.FromArgb(1, 255, 255, 255)); Grid[] cells = new Grid[PreviewImageOverlay.RowDefinitions.Count * PreviewImageOverlay.ColumnDefinitions.Count]; List unaddedPlayers = players; for (int i = 0; i < cells.Length; i++) { } } private void CellButton_Click(object sender, RoutedEventArgs e) { Button cellButton = sender as Button; Console.WriteLine("Button at {0}, {1} was clicked", ((Tuple)cellButton.Tag).Item2, ((Tuple)cellButton.Tag).Item1); } 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 BitmapSource pSprite = new BitmapImage(new Uri("pack://application:,,,/Sprites/" + sprites[rand.Next(0, sprites.Length)])); // Recolour: if (rand.Next(1, 4) == 1) { pSprite = RBG.Helpers.ImageProcessing.UpdatePixelColours(pSprite, 0.0, 0.0, 1.0); } p.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"; 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); }); // 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) { BitmapImage image = new(); image.BeginInit(); image.StreamSource = item.Value; image.EndInit(); Image spriteImage = new() { Source = image }; _ = LoadedSprites.Children.Add(spriteImage); } })); }); } } } }