Refactored and added base projects & code

This commit is contained in:
Brychan Dempsey 2021-08-29 15:04:28 +12:00
parent 8dfbba79ee
commit 9b5f08e86f
22 changed files with 1647 additions and 0 deletions

4
.editorconfig Normal file
View File

@ -0,0 +1,4 @@
[*.cs]
# IDE0011: Add braces
csharp_prefer_braces = when_multiline

3
.gitignore vendored
View File

@ -373,3 +373,6 @@ FodyWeavers.xsd
# Local History for Visual Studio Code
.history/
# Ignore testing board
TestingBoard/

6
PDGServer_WPF/App.xaml Normal file
View File

@ -0,0 +1,6 @@
<Application x:Class="RBG_Server.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RBG_Server"
StartupUri="MainWindow.xaml">
</Application>

17
PDGServer_WPF/App.xaml.cs Normal file
View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace RBG_Server
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

View File

@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -0,0 +1,56 @@
<Window x:Class="RBG_Server.WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="PDG Server" Height="600" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBox x:Name="ImageSourceTextBox" Text="Source" TextWrapping="Wrap" LostFocus="ImageSourceTextBox_LostFocus"/>
<Button x:Name="BrowseButton" Content="Browse" Height="20" Width="40" Click="BrowseButton_Click"/>
<TextBlock Text="Number of Rows" TextWrapping="Wrap" Padding="0,10,0,0"/>
<Slider x:Name="NumberOfRows" IsSnapToTickEnabled="True" Minimum="1" TickPlacement="BottomRight" AutoToolTipPlacement="BottomRight" ValueChanged="NumberOfRows_ValueChanged" Value="9"/>
<TextBlock Text="Number of Columns:" TextWrapping="Wrap" Padding="0,10,0,0"/>
<Slider x:Name="NumberOfColumns" IsSnapToTickEnabled="True" Minimum="1" TickPlacement="BottomRight" AutoToolTipPlacement="BottomRight" ValueChanged="NumberOfColumns_ValueChanged" Value="9"/>
<TextBlock Text="Starting Row" TextWrapping="Wrap" Padding="0,10,0,0"/>
<Slider x:Name="StartingRow" IsSnapToTickEnabled="True" Value="8" AutoToolTipPlacement="BottomRight" TickPlacement="BottomRight" ValueChanged="Sliders_ValueChanged" Maximum="9"/>
<TextBlock Text="Starting Column" TextWrapping="Wrap" Padding="0,10,0,0"/>
<Slider x:Name="StartingColumn" IsSnapToTickEnabled="True" AutoToolTipPlacement="BottomRight" TickPlacement="BottomRight" ValueChanged="Sliders_ValueChanged" Maximum="9"/>
<TextBlock Text="Zoom Box Starting Row" TextWrapping="Wrap" Padding="0,10,0,0"/>
<Slider x:Name="ZoomBoxStartRow" IsSnapToTickEnabled="True" AutoToolTipPlacement="BottomRight" TickPlacement="BottomRight" Value="3" ValueChanged="Sliders_ValueChanged" Maximum="9"/>
<TextBlock Text="Zoom Box Starting Column" TextWrapping="Wrap" Padding="0,10,0,0"/>
<Slider x:Name="ZoomBoxStartColumn" IsSnapToTickEnabled="True" AutoToolTipPlacement="BottomRight" TickPlacement="BottomRight" Value="3" ValueChanged="Sliders_ValueChanged" Maximum="9"/>
<TextBlock Text="Zoom Box Span" TextWrapping="Wrap" Padding="0,10,0,0"/>
<Slider x:Name="ZoomBoxSpan" IsSnapToTickEnabled="True" AutoToolTipPlacement="BottomRight" TickPlacement="BottomRight" Value="3" ValueChanged="Sliders_ValueChanged" Minimum="1" Maximum="6"/>
<Button x:Name="GridVisibilityToggleButton" Content="Show/Hide Grid" Click="GridVisibilityToggleButton_Click"/>
<Button x:Name="StartServerButton" Content="Start Server" HorizontalAlignment="Center" Margin="0,25,0,0" Click="StartServerButton_Click"/>
<Button x:Name="DrawRandomizedPlayers" Content="Draw Random Players" Margin="0,25,0,0" Click="DrawRandomizedPlayers_Click"/>
<WrapPanel Height="100">
</WrapPanel>
</StackPanel>
<Border Grid.Column="1" BorderThickness="1,1,1,1" Background="#54D3D3D3" BorderBrush="DarkGray">
<ListView x:Name="PlayersList">
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
</Border>
</Grid>
<Grid Grid.Column="1" VerticalAlignment="Center">
<Image x:Name="PreviewImage" MinHeight="150" Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
<Grid x:Name="PreviewImageOverlay"/>
</Grid>
</Grid>
</Window>

View File

@ -0,0 +1,290 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
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
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
GameServer gameServer;
MemoryStream gameBoard;
public MainWindow()
{
InitializeComponent();
//gameServer = new GameServer(8, 0, 9, 9, 3, 3, 3, 3, null);
}
/// <summary>
/// Browse for file button
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BrowseButton_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog ofd = new();
ofd.Filter = "Image Files (.png; .jpg)|*.png;*.jpg";
if (ofd.ShowDialog() == true)
{
ImageSourceTextBox.Text = ofd.FileName;
UpdateImage();
}
}
/// <summary>
/// Update the image when the text box looses focus
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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);
}
}
/// <summary>
/// Creates and starts the game server
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void StartServerButton_Click(object sender, RoutedEventArgs e)
{
gameServer = new((int)StartingRow.Value, (int)StartingColumn.Value, (int)NumberOfRows.Value, (int)NumberOfColumns.Value, (int)ZoomBoxStartRow.Value, (int)ZoomBoxStartColumn.Value, (int)ZoomBoxSpan.Value, (int)ZoomBoxSpan.Value, gameBoard);
}
private void NumberOfRows_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> 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<double> 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);
}
}
}
/// <summary>
/// Generates elements to go inside the game grid from the provided list of players
/// The grid should be appropriately sized before using this
/// </summary>
private void GenerateGameBoard(List<Player> players)
{
SolidColorBrush buttonBrush = new(Color.FromArgb(1, 255, 255, 255));
Grid[] cells = new Grid[PreviewImageOverlay.RowDefinitions.Count * PreviewImageOverlay.ColumnDefinitions.Count];
List<Player> 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<Player> 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<int, int>(i / PreviewImageOverlay.ColumnDefinitions.Count, i % PreviewImageOverlay.ColumnDefinitions.Count); // Tag is a Tuple<row, column>
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);
}
}
/// <summary>
/// Represents a cell on the board
/// </summary>
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<Player> removedPlayers = new();
Grid parentGrid;
public BoardCell(Grid parentGrid)
{
this.parentGrid = parentGrid;
}
}
private void GenerateNewGameBoard(List<Player> players)
{
SolidColorBrush buttonBrush = new(Color.FromArgb(1, 255, 255, 255));
Grid[] cells = new Grid[PreviewImageOverlay.RowDefinitions.Count * PreviewImageOverlay.ColumnDefinitions.Count];
List<Player> 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<int, int>)cellButton.Tag).Item2, ((Tuple<int, int>)cellButton.Tag).Item1);
}
private void Sliders_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> 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<Player> 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);
}
}
}

View File

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
<UseWPF>true</UseWPF>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RBG.Helpers\RBG.Helpers.csproj" />
<ProjectReference Include="..\RBG_Server.Core\RBG_Server.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,4 @@
[*.cs]
# IDE0011: Add braces
csharp_prefer_braces = when_multiline

View File

@ -0,0 +1,9 @@
<Application x:Class="Pokemon_Drinking_Game.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Pokemon_Drinking_Game"
StartupUri="MainWindow.xaml">
<Application.Resources>
<BitmapImage x:Key="GameBoardImage" UriSource="../pokemon_v3.png" />
</Application.Resources>
</Application>

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace Pokemon_Drinking_Game
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

View File

@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -0,0 +1,147 @@
<Window x:Class="Pokemon_Drinking_Game.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Pokemon_Drinking_Game"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" Background="Black">
<Grid>
<Grid x:Name="GameGrid" Background="Black" Visibility="Visible">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image x:Name="gameBoard" Source="/pokemon_v3.png" />
<Grid x:Name="boardLayout" Margin="1,1,1,1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<!-- First moves -->
<Button x:Name="palletTownButton" Content="" Grid.Row="8" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="rattataButton" Content="" Grid.Row="7" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="pidgeyButton" Content="" Grid.Row="6" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="caterpieButton" Content="" Grid.Row="5" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="pikachuButton" Content="" Grid.Row="4" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="beedrilButton" Content="" Grid.Row="3" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="pewterButton" Content="" Grid.Row="2" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="nidoranButton" Content="" Grid.Row="1" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="zubatButton" Content="" Grid.Row="0" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<!-- Second Moves -->
<Button x:Name="clefairyButton" Content="" Grid.Row="0" Grid.Column="1" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="jigglypuffButton" Content="" Grid.Row="0" Grid.Column="2" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="abraButton" Content="" Grid.Row="0" Grid.Column="3" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="garyButton" Content="" Grid.Row="0" Grid.Column="4" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="ceruleanButton" Content="" Grid.Row="0" Grid.Column="5" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="slowpokeButton" Content="" Grid.Row="0" Grid.Column="6" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="bellsproutButton" Content="" Grid.Row="0" Grid.Column="7" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="meowthButton" Content="" Grid.Row="0" Grid.Column="8" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<!-- Third Moves -->
<Button x:Name="diglettButton" Content="" Grid.Row="1" Grid.Column="8" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="ssanneButton" Content="" Grid.Row="2" Grid.Column="8" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="vermillionButton" Content="" Grid.Row="3" Grid.Column="8" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="bicycleButton" Content="" Grid.Row="4" Grid.Column="8" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="magikarpButton" Content="" Grid.Row="5" Grid.Column="8" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="sandshrewButton" Content="" Grid.Row="6" Grid.Column="8" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="pokemontowerButton" Content="" Grid.Row="7" Grid.Column="8" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="channelerButton" Content="" Grid.Row="8" Grid.Column="8" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<!-- fourth Moves -->
<Button x:Name="haunterButton" Content="" Grid.Row="8" Grid.Column="7" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="cuboneButton" Content="" Grid.Row="8" Grid.Column="6" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="ghostButton" Content="" Grid.Row="8" Grid.Column="5" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="abra2Button" Content="" Grid.Row="8" Grid.Column="4" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="snorlaxButton" Content="" Grid.Row="8" Grid.Column="3" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="gary2Button" Content="" Grid.Row="8" Grid.Column="2" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="eeveeButton" Content="" Grid.Row="8" Grid.Column="1" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<!-- fifth Moves -->
<Button x:Name="celadonButton" Content="" Grid.Row="7" Grid.Column="1" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="psyduckButton" Content="" Grid.Row="6" Grid.Column="1" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="evolutionButton" Content="" Grid.Row="5" Grid.Column="1" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="porygonButton" Content="" Grid.Row="4" Grid.Column="1" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="silphButton" Content="" Grid.Row="3" Grid.Column="1" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="scientistButton" Content="" Grid.Row="2" Grid.Column="1" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="laprasButton" Content="" Grid.Row="1" Grid.Column="1" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<!-- sixth Moves -->
<Button x:Name="rocketButton" Content="" Grid.Row="1" Grid.Column="2" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="giovanniButton" Content="" Grid.Row="1" Grid.Column="3" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="rarecandyButton" Content="" Grid.Row="1" Grid.Column="4" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="gary3Button" Content="" Grid.Row="1" Grid.Column="5" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="saffronButton" Content="" Grid.Row="1" Grid.Column="6" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="hitmonsButton" Content="" Grid.Row="1" Grid.Column="7" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<!-- seventh Moves -->
<Button x:Name="krabbyButton" Content="" Grid.Row="2" Grid.Column="7" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="dittoButton" Content="" Grid.Row="3" Grid.Column="7" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="doduoButton" Content="" Grid.Row="4" Grid.Column="7" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="safariButton" Content="" Grid.Row="5" Grid.Column="7" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="dratiniButton" Content="" Grid.Row="6" Grid.Column="7" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="taurosButton" Content="" Grid.Row="7" Grid.Column="7" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<!-- eigth Moves -->
<Button x:Name="chanseyButton" Content="" Grid.Row="7" Grid.Column="6" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="fuchsiaButton" Content="" Grid.Row="7" Grid.Column="5" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="electrodeButton" Content="" Grid.Row="7" Grid.Column="4" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="electabuzzButton" Content="" Grid.Row="7" Grid.Column="3" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="poliwagButton" Content="" Grid.Row="7" Grid.Column="2" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<!-- ninth Moves -->
<Button x:Name="seakingButton" Content="" Grid.Row="6" Grid.Column="2" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="missingnoButton" Content="" Grid.Row="5" Grid.Column="2" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="cinnabarButton" Content="" Grid.Row="4" Grid.Column="2" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="koffingButton" Content="" Grid.Row="3" Grid.Column="2" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="fossilButton" Content="" Grid.Row="2" Grid.Column="2" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<!-- tenth Moves -->
<Button x:Name="pokeballButton" Content="" Grid.Row="2" Grid.Column="3" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="persianButton" Content="" Grid.Row="2" Grid.Column="4" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="viridianButton" Content="" Grid.Row="2" Grid.Column="5" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="fearowButton" Content="" Grid.Row="2" Grid.Column="6" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<!-- tenth Moves -->
<Button x:Name="gravellerButton" Content="" Grid.Row="3" Grid.Column="6" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="gyradosButton" Content="" Grid.Row="4" Grid.Column="6" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="dragoniteButton" Content="" Grid.Row="5" Grid.Column="6" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="legendbirdButton" Content="" Grid.Row="6" Grid.Column="6" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<!-- 11th Moves -->
<Button x:Name="elitefourButton" Content="" Grid.Row="6" Grid.Column="5" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="championButton" Content="" Grid.Row="6" Grid.Column="4" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Button x:Name="masterButton" Content="" Grid.Row="6" Grid.Column="3" Background="#01000000" MouseEnter="buttons_MouseEnter" MouseLeave="buttons_MouseLeave" BorderBrush="{x:Null}"/>
<Image x:Name="zoomPieceImage" Grid.ColumnSpan="3" Grid.Column="3" Grid.RowSpan="3" Grid.Row="3" Source="/pokemon_v3.png" />
<!-- Holders -->
<StackPanel Grid.Row="8"/>
</Grid>
<StackPanel x:Name="PlayerList" Grid.Column="1"/>
</Grid>
<Grid x:Name="ConnectionGrid" Background="Black" Visibility="Visible" d:IsHidden="True">
<StackPanel Margin="0,20,0,10" HorizontalAlignment="Center">
<TextBlock Text="Connection:" TextWrapping="Wrap" Foreground="White" HorizontalAlignment="Center" Margin="0,0,0,10" FontSize="22"/>
<TextBlock Text="Server Address:" TextWrapping="Wrap" Foreground="White" HorizontalAlignment="Center"/>
<TextBox x:Name="AddressTextBox" Text="pdgserver.kauripeak.co.nz" TextWrapping="Wrap" MinWidth="250" Margin="0,5,0,0"/>
<TextBlock Text="Server Port:" TextWrapping="Wrap" Foreground="White" HorizontalAlignment="Center" Margin="0,10,0,0"/>
<TextBox x:Name="PortTextBox" Text="13000" TextWrapping="Wrap" MinWidth="50" HorizontalAlignment="Center" Margin="0,5,0,0"/>
<TextBlock Text="Player Name:" TextWrapping="Wrap" Foreground="White" HorizontalAlignment="Center" Margin="0,10,0,0"/>
<TextBox x:Name="PlayerNameTextBox" Text="Player" TextWrapping="Wrap" MinWidth="250" HorizontalAlignment="Center" Margin="0,5,0,0"/>
<Button x:Name="ConnectButton" Content="Connect" HorizontalAlignment="Center" Margin="0,20,0,0" Click="ConnectButton_Click"/>
</StackPanel>
</Grid>
</Grid>
</Window>

View File

@ -0,0 +1,338 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Pokemon_Drinking_Game
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
BitmapImage subImage;
List<Player> players;
int ourPlayerIndex;
int currentTurn;
Timer updateTimer;
TcpClient client;
public MainWindow()
{
InitializeComponent();
// Load the sub image just once
loadImage();
players = new List<Player>();
//TcpClient client = new TcpClient("pdgserver.kauripeak.co.nz", 13000);
}
void loadImage()
{
subImage = new(new Uri("pack://application:,,,/pokemon_v3.png"));
int i = 0;
}
private void buttons_MouseEnter(object sender, MouseEventArgs e)
{
// Replace center image
zoomPieceImage.Source = retrieveSubImage(subImage, (Button)sender);
zoomPieceImage.Visibility = Visibility.Visible;
}
private void buttons_MouseLeave(object sender, MouseEventArgs e)
{
// Hide center image
//zoomPieceImage.Source = new BitmapImage();
zoomPieceImage.Visibility = Visibility.Collapsed;
}
CroppedBitmap retrieveSubImage(BitmapImage source, Button button)
{
// Get various element heights & widths
int imageWidth = source.PixelWidth;
int imageHight = source.PixelHeight;
int pixPerTile = imageWidth / 9 -1;
double parentHeight = ((Grid)button.Parent).ActualHeight;
double parentWidth = ((Grid)button.Parent).ActualWidth;
int row = -1;
int col = -1;
// Hard coding cause its a bitch
if (button == palletTownButton)
{
col = 0;
row = 8;
}
else if (button == rattataButton) { col = 0; row = 7; }
else if (button == pidgeyButton) { col = 0; row = 6; }
else if (button == caterpieButton) { col = 0; row = 5; }
else if (button == pikachuButton) { col = 0; row = 4; }
else if (button == beedrilButton) { col = 0; row = 3; }
else if (button == pewterButton) { col = 0; row = 2; }
else if (button == nidoranButton) { col = 0; row = 1; }
else if (button == zubatButton) { col = 0; row = 0; }
//
else if (button == clefairyButton) { col = 1; row = 0; }
else if (button == jigglypuffButton) { col = 2; row = 0; }
else if (button == abraButton) { col = 3; row = 0; }
else if (button == garyButton) { col = 4; row = 0; }
else if (button == ceruleanButton) { col = 5; row = 0; }
else if (button == slowpokeButton) { col = 6; row = 0; }
else if (button == bellsproutButton) { col = 7; row = 0; }
else if (button == meowthButton) { col = 8; row = 0; }
//
else if (button == diglettButton) { col = 8; row = 1; }
else if (button == ssanneButton) { col = 8; row = 2; }
else if (button == vermillionButton) { col = 8; row = 3; }
else if (button == bicycleButton) { col = 8; row = 4; }
else if (button == magikarpButton) { col = 8; row = 5; }
else if (button == sandshrewButton) { col = 8; row = 6; }
else if (button == pokemontowerButton) { col = 8; row = 7; }
else if (button == channelerButton) { col = 8; row = 8; }
//
else if (button == haunterButton) { col = 7; row = 8; }
else if (button == cuboneButton) { col = 6; row = 8; }
else if (button == ghostButton) { col = 5; row = 8; }
else if (button == abra2Button) { col = 4; row = 8; }
else if (button == snorlaxButton) { col = 3; row = 8; }
else if (button == gary2Button) { col = 2; row = 8; }
else if (button == eeveeButton) { col = 1; row = 8; }
//
else if (button == celadonButton) { col = 1; row = 7; }
else if (button == psyduckButton) { col = 1; row = 6; }
else if (button == evolutionButton) { col = 1; row = 5; }
else if (button == porygonButton) { col = 1; row = 4; }
else if (button == silphButton) { col = 1; row = 3; }
else if (button == scientistButton) { col = 1; row = 2; }
else if (button == laprasButton) { col = 1; row = 1; }
//
else if (button == rocketButton) { col = 2; row = 1; }
else if (button == giovanniButton) { col = 3; row = 1; }
else if (button == rarecandyButton) { col = 4; row = 1; }
else if (button == gary3Button) { col = 5; row = 1; }
else if (button == saffronButton) { col = 6; row = 1; }
else if (button == hitmonsButton) { col = 7; row = 1; }
//
else if (button == krabbyButton) { col = 7; row = 2; }
else if (button == dittoButton) { col = 7; row = 3; }
else if (button == doduoButton) { col = 7; row = 4; }
else if (button == safariButton) { col = 7; row = 5; }
else if (button == dratiniButton) { col = 7; row = 6; }
else if (button == taurosButton) { col = 7; row = 7; }
//
else if (button == chanseyButton) { col = 6; row = 7; }
else if (button == fuchsiaButton) { col = 5; row = 7; }
else if (button == electrodeButton) { col = 4; row = 7; }
else if (button == electabuzzButton) { col = 3; row = 7; }
else if (button == poliwagButton) { col = 2; row = 7; }
//
else if (button == seakingButton) { col = 2; row = 6; }
else if (button == missingnoButton) { col = 2; row = 5; }
else if (button == cinnabarButton) { col = 2; row = 4; }
else if (button == koffingButton) { col = 2; row = 3; }
else if (button == fossilButton) { col = 2; row = 2; }
//
else if (button == pokeballButton) { col = 3; row = 2; }
else if (button == persianButton) { col = 4; row = 2; }
else if (button == viridianButton) { col = 5; row = 2; }
else if (button == fearowButton) { col = 6; row = 2; }
//
else if (button == gravellerButton) { col = 6; row = 3; }
else if (button == gyradosButton) { col = 6; row = 4; }
else if (button == dragoniteButton) { col = 6; row = 5; }
else if (button == legendbirdButton) { col = 6; row = 6; }
//
else if (button == elitefourButton) { col = 5; row = 6; }
else if (button == championButton) { col = 4; row = 6; }
else if (button == masterButton) { col = 3; row = 6; }
Int32Rect pixelRect = new Int32Rect(pixPerTile * col, pixPerTile * row , pixPerTile, pixPerTile);
CroppedBitmap cb = new CroppedBitmap(source, pixelRect);
return cb;
}
byte[] overflowBuffer;
/// <summary>
/// Server logic
/// Called periodically (e.g. 1/10 of a second), retrieves new data from the buffer, and
/// sends out any updates that have occured.
///
/// Data packets are 0xFF terminated (new command is the proceeding byte)
/// Data terminations are 0xFE (next data type is the proceeding byte; does not apply to deterministic primative sizes)
/// strings are expected to be sent in UTF-8 format
///
/// As this is all in TCP, data is guaranteed to be in the correct order
/// </summary>
void ClientUpdate()
{
// ***************************
// **** Ensure connected ***
// ***************************
if (!client.Connected)
{
}
// ***************************
// ****** Recieve Data *****
// ***************************
if (client.Available > 0)
{
int available = client.Available;
// Internal byte array to store all awaiting bytes
byte[] buffer = new byte[available];
// Get the underlying networkstream, to read and write
NetworkStream connectionStream = client.GetStream();
connectionStream.Read(buffer, 0, available);
int pos = 0;
while (pos < available)
{
int command = buffer[pos] >> 4; // Get the four command bits
ourPlayerIndex = buffer[pos++] & 0xF; // Ensure we are the correct indexed player
switch (command) // post-increment past the command byte
{
case 0: // Server queries a heartbeat response
connectionStream.Write(new byte[] { (byte)ourPlayerIndex, 1, 0xFF });
break;
case 1: // Server has a board (game) update
while (buffer[pos] != 0xFF)
{
// First four bits, after the command is the number of players
int playerCount = buffer[pos] >> 4;
// Next four are the number of players
currentTurn = buffer[pos++] | 0xF;
// Next
// If there is a different number of players, create a new list of players
if (players.Count != playerCount)
{
players = new List<Player>(playerCount);
}
// Next bit is a flag, denoting if the current player is a
}
break;
default:
break;
}
}
UpdateUI();
}
// ***************************
// ******* Send Data *******
// ***************************
}
void UpdateUI()
{
Brush labelBrush = new SolidColorBrush(Color.FromRgb(255, 255, 255));
Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Render, new Action(() =>
{
PlayerList.Children.Clear();
// do UI updates here
foreach (var item in players)
{
Label playerLabel = new Label()
{
Content = item.name,
Foreground = labelBrush
};
PlayerList.Children.Add(playerLabel);
}
}));
}
private void ConnectButton_Click(object sender, RoutedEventArgs e)
{
// Do connection using props
if (client != null)
{
client.Close();
}
bool success = false;
try
{
client = new TcpClient(AddressTextBox.Text, Convert.ToInt32(PortTextBox.Text));
success = true;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
if (success)
{
ConnectionGrid.Visibility = Visibility.Collapsed;
GameGrid.Visibility = Visibility.Visible;
ourPlayerIndex = 0;
currentTurn = 0;
List<byte> sendBytes = new();
sendBytes.Add(1 << 4);
sendBytes.AddRange(Encoding.UTF8.GetBytes(PlayerNameTextBox.Text));
sendBytes.Add(0xFF);
// Send through the player name
client.GetStream().Write(sendBytes.ToArray());
// Add listener for data
updateTimer = new Timer((t) =>
{
ClientUpdate();
});
updateTimer.Change(100, 33);
}
}
}
struct Player
{
public string name;
public int row;
public int column;
public long lastTime;
public Player(string name, int row, int column)
{
this.name = name;
this.row = row;
this.column = column;
this.lastTime = DateTime.Now.Ticks;
}
public override string ToString()
{
return name + " (" + column + ", " + row + ")";
}
}
}

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows</TargetFramework>
<RootNamespace>Pokemon_Drinking_Game</RootNamespace>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<None Remove="pokemon_v3.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="pokemon_v3.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Resource>
</ItemGroup>
</Project>

View File

@ -0,0 +1,42 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31515.178
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pokemon Drinking Game", "Pokemon Drinking Game.csproj", "{3BF04BFD-307C-4878-B420-47DFFAD97BE0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PDGServer", "..\PDGServer\PDGServer.csproj", "{FD6A1623-2695-4353-8B4E-7115BDCB34FD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PDGServer_WPF", "..\PDGServer_WPF\PDGServer_WPF.csproj", "{78CE20FA-E26B-4C12-9498-6EFB058586E5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F4C5002D-A70D-4B97-8BDD-F02008F4B79B}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3BF04BFD-307C-4878-B420-47DFFAD97BE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3BF04BFD-307C-4878-B420-47DFFAD97BE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3BF04BFD-307C-4878-B420-47DFFAD97BE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3BF04BFD-307C-4878-B420-47DFFAD97BE0}.Release|Any CPU.Build.0 = Release|Any CPU
{FD6A1623-2695-4353-8B4E-7115BDCB34FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FD6A1623-2695-4353-8B4E-7115BDCB34FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FD6A1623-2695-4353-8B4E-7115BDCB34FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FD6A1623-2695-4353-8B4E-7115BDCB34FD}.Release|Any CPU.Build.0 = Release|Any CPU
{78CE20FA-E26B-4C12-9498-6EFB058586E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{78CE20FA-E26B-4C12-9498-6EFB058586E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{78CE20FA-E26B-4C12-9498-6EFB058586E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{78CE20FA-E26B-4C12-9498-6EFB058586E5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {35DA9C3E-6A70-4E39-85A6-1843C79A07D4}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,86 @@
using System;
using System.Windows;
using System.Windows.Media.Imaging;
namespace RBG.Helpers
{
public class ImageProcessing
{
/// <summary>
/// Returns a copy of the original bitmap with a specified per-channel colour multiplier
/// Each channel expects a rate 0-1 (will handle other values, with unexpected results)
/// Defaults to 1.0 for each channel (no modification)
/// </summary>
/// <param name="sourceBitmap">The source bitmap</param>
/// <param name="redRate">The red-channel multiplier</param>
/// <param name="greenRate">The green-channel multiplier</param>
/// <param name="blueRate">The blue-channel multiplier</param>
/// <param name="alphaRate">The alpha-channel multiplier</param>
/// <returns></returns>
public static BitmapSource UpdatePixelColours(BitmapSource sourceBitmap, double redRate = 1.0, double greenRate = 1.0, double blueRate = 1.0, double alphaRate = 1.0)
{
// Create a writeable bitmap from the source bitmap
WriteableBitmap wbmp = new(sourceBitmap);
// Ensure 32 bpp
if (wbmp.Format.BitsPerPixel != 32) return null;
// Lock the bitmap so the buffer cannot change
wbmp.Lock();
// Enter unsafe code (used over marshalled pointer access so that it is more performant, but has a
// higher security risk as this is raw [but still bounded] memory access ( => data may be changed by a third party)
// As the buffer is of a known size, and not terminated by a specific symbol, modified data in the buffer is
// unlikely to cause a novel vulnerability in the software ( => vulnerability exists in BitmapSource, can be
// exploited with any bitmap image being loaded)
unsafe
{
// Retrieve the buffer pointer
int* arrayptr = (int*)wbmp.BackBuffer;
// Iterate through each pixel
for (int i = 0; i < wbmp.PixelWidth * wbmp.PixelHeight; i++)
{
// Dereference the exact pixel value
int pixel = *(arrayptr + i);
// decompose the colour channels
int a = (pixel >> 24) & 0xFF;
int r = (pixel >> 16) & 0xFF;
int g = (pixel >> 8) & 0xFF;
int b = pixel & 0xFF;
// Calc new values & clamp to a max of 0xFF (8 bits per channel)
int newB = (int)Math.Min((uint)(b * blueRate), 255);
int newG = (int)Math.Min((uint)(g * greenRate), 255);
int newR = (int)Math.Min((uint)(r * redRate), 255);
int newA = (int)Math.Min((uint)(a * alphaRate), 255);
// Set the pixel to the new value
*(arrayptr + i) = (newA << 24) | (newR << 16) | (newG << 8) | newB;
}
}
// Invalidate the full current image, so that the new buffer data is fully applied
wbmp.AddDirtyRect(new Int32Rect(0, 0, wbmp.PixelWidth, wbmp.PixelHeight));
// Unlock the image, as everything has finished changing
wbmp.Unlock();
return wbmp;
}
/// <summary>
/// Retrieves a cropped area of the provided bitmap
/// </summary>
/// <param name="source"></param>
/// <param name="row"></param>
/// <param name="col"></param>
/// <param name="rowSize"></param>
/// <param name="colSize"></param>
/// <param name="colsIncluded"></param>
/// <param name="rowsIncluded"></param>
/// <returns></returns>
public static BitmapSource RetrieveSubImage(BitmapSource source, int row, int col, int rowSize, int colSize, int colsIncluded = 1, int rowsIncluded = 1)
{
// Get number of pixels per segment
int rowPixelsPerTile = (source.PixelWidth / rowSize) - 1;
int colPixelsPerTile = (source.PixelHeight / colSize) - 1;
// Create a cropping bound
Int32Rect pixelRect = new(colPixelsPerTile * col, rowPixelsPerTile * row, colPixelsPerTile * colsIncluded, rowPixelsPerTile * rowsIncluded);
return new CroppedBitmap(source, pixelRect);
}
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WPF" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,428 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace RBG_Server
{
public class GameServer
{
private readonly byte startRow, startColumn, rowSize, columnSize, zoomRow, zoomCol, zoomRowSpan, zoomColSpan;
private readonly Dictionary<int, byte[]> boardImages;
/// <summary>
/// Initialise a new server, with the provided starting row & column
///
/// </summary>
/// <param name="startRow">The starting row of the board</param>
/// <param name="startColumn">The starting column of the board</param>
/// <param name="boardImage">The full-scale board image</param>
public GameServer(int startRow, int startColumn, int rowSize, int columnSize, int zoomRow, int zoomCol, int zoomRowSpan, int zoomColSpan, Stream boardImage)
{
this.startRow = (byte)startRow;
this.startColumn = (byte)startColumn;
this.rowSize = (byte)rowSize;
this.columnSize = (byte)columnSize;
this.zoomRow = (byte)zoomRow;
this.zoomCol = (byte)zoomCol;
this.zoomRowSpan = (byte)zoomRowSpan;
this.zoomColSpan = (byte)zoomColSpan;
BitmapImage gameBoardImage = new();
gameBoardImage.StreamSource = boardImage;
// Hold images
Dictionary<int, byte[]> sourceImages = new();
// Create a JPEG encoder
JpegBitmapEncoder encoder = new()
{
QualityLevel = 100,
};
// Add the base frame to the encoder and retrieve its byte array
encoder.Frames.Add(BitmapFrame.Create(gameBoardImage));
using (MemoryStream stream = new())
{
encoder.Save(stream);
byte[] frameData = stream.ToArray();
sourceImages.Add(-1, frameData);
encoder.Frames.Clear();
}
// Add a resized version for each "common" scale
if (gameBoardImage.PixelHeight > 2160)
{
double rate = 2160 / gameBoardImage.PixelHeight;
TransformedBitmap modified = new(gameBoardImage, new ScaleTransform(gameBoardImage.PixelWidth * rate, gameBoardImage.PixelHeight * rate));
encoder.Frames.Add(BitmapFrame.Create(modified));
using MemoryStream stream = new();
encoder.Save(stream);
byte[] frameData = stream.ToArray();
sourceImages.Add(2160, frameData);
encoder.Frames.Clear();
}
if (gameBoardImage.PixelHeight > 1440)
{
double rate = 1440 / gameBoardImage.PixelHeight;
TransformedBitmap modified = new(gameBoardImage, new ScaleTransform(gameBoardImage.PixelWidth * rate, gameBoardImage.PixelHeight * rate));
encoder.Frames.Add(BitmapFrame.Create(modified));
using MemoryStream stream = new();
encoder.Save(stream);
byte[] frameData = stream.ToArray();
sourceImages.Add(1440, frameData);
encoder.Frames.Clear();
}
if (gameBoardImage.PixelHeight > 1080)
{
double rate = 1080 / gameBoardImage.PixelHeight;
TransformedBitmap modified = new(gameBoardImage, new ScaleTransform(gameBoardImage.PixelWidth * rate, gameBoardImage.PixelHeight * rate));
encoder.Frames.Add(BitmapFrame.Create(modified));
using MemoryStream stream = new();
encoder.Save(stream);
byte[] frameData = stream.ToArray();
sourceImages.Add(1080, frameData);
encoder.Frames.Clear();
}
if (gameBoardImage.PixelHeight > 720)
{
double rate = 720 / gameBoardImage.PixelHeight;
TransformedBitmap modified = new(gameBoardImage, new ScaleTransform(gameBoardImage.PixelWidth * rate, gameBoardImage.PixelHeight * rate));
encoder.Frames.Add(BitmapFrame.Create(modified));
using MemoryStream stream = new();
encoder.Save(stream);
byte[] frameData = stream.ToArray();
sourceImages.Add(720, frameData);
encoder.Frames.Clear();
}
// Send a 240p thumbnail
{
double rate = 240 / gameBoardImage.PixelHeight;
TransformedBitmap modified = new(gameBoardImage, new ScaleTransform(gameBoardImage.PixelWidth * rate, gameBoardImage.PixelHeight * rate));
encoder.Frames.Add(BitmapFrame.Create(modified));
using MemoryStream stream = new();
encoder.Save(stream);
byte[] frameData = stream.ToArray();
sourceImages.Add(240, frameData);
encoder.Frames.Clear();
}
// add the board images to the
boardImages = sourceImages;
}
public List<Player> Players { get; } = new();
// Map players to connections/clients
Dictionary<TcpClient, Player> clients = new();
// Clients that have connected, but are not yet added to the list
ConcurrentQueue<TcpClient> awaitingAdds = new();
void Init(IPAddress localAddr, int port)
{
TcpListener listener = new(localAddr, port);
listener.Start();
while (true)
{
TcpClient client = listener.AcceptTcpClient();
// Delegate to another thread
_ = Task.Run(() =>
{
AcceptConnections(client);
});
}
}
/// <summary>
/// Accept the client, and do the preprocessing
/// </summary>
/// <param name="client"></param>
void AcceptConnections(TcpClient client)
{
// Ensure we send data immediately
client.NoDelay = true;
// In the case the client
NetworkStream stream = client.GetStream();
// Await the mode switch data from the client
int command;
do
{
command = stream.ReadByte();
break;
}
while (stream.DataAvailable);
if (command == 0xFD)
{
// Connect to game; send request
}
else if (command == 0xFC)
{
// Next byte contains the screen size
int screenSize = stream.ReadByte() << 8 | stream.ReadByte();
// Send three images, the low res, and the screen res, and the full res (in that order, so the client may display something
SendImage(240, stream);
// Send the customized screen res image
if (screenSize <= 720)
{
SendImage(720, stream);
}
else if (screenSize <= 1080)
{
SendImage(1080, stream);
}
else if (screenSize <= 1440)
{
SendImage(1440, stream);
}
else if (screenSize <= 2160)
{
SendImage(2160, stream);
}
// Finally, send the full-screen image
SendImage(-1, stream);
}
}
void SendImage(int index, NetworkStream stream)
{
byte[] dataSize = new byte[] { (byte)(boardImages[index].Length >> 24), (byte)(boardImages[index].Length >> 16), (byte)(boardImages[index].Length >> 8), (byte)(boardImages[index].Length & 0xFF) };
stream.Write(dataSize);
stream.Write(boardImages[index]);
}
static int currentTurn = 0;
void ServerUpdate()
{
// ***************************
// ** Accept new connections**
// ***************************
while (!awaitingAdds.IsEmpty)
{
// try to dequeue a client
if (awaitingAdds.TryDequeue(out TcpClient poppedClient))
{
// TODO:
// Check that the player isn't reconnecting
Player newPlayer = new("<loading>", startRow, startColumn);
Players.Add(newPlayer);
clients.Add(poppedClient, newPlayer);
Console.WriteLine("New player added.");
}
}
// Create a list of clients that are no longer connected, so they may be closed at the end of this
// loop
ConcurrentQueue<TcpClient> closedClients = new();
bool turnCompleted = false;
// ***************************
// ***** Recieve Data *****
// ***************************
// Data are recieved in parallel, so that it may be processed quickly
Parallel.ForEach(clients, (item) =>
{
Player player = item.Value;
if (!item.Key.Connected)
{
// Client no longer connected, add to the removed list
closedClients.Enqueue(item.Key);
return;
}
else
{
// Game logic
// If there are available bytes to be read, we can process them
if (item.Key.Available > 0)
{
// Get number of bytes
int availableBytes = item.Key.Available;
// Store read bytes in an array
byte[] receivedBytes = new byte[player.UnhandledBuffer.Length + availableBytes];
// Grab the stream to read from/write to
NetworkStream clientStream = item.Key.GetStream();
// read the buffer to the end
clientStream.Read(receivedBytes, player.UnhandledBuffer.Length, availableBytes);
// Copy the unhandled buffer to the recieved bytes
player.UnhandledBuffer.CopyTo(receivedBytes, 0);
// Reset the unhandled array
player.UnhandledBuffer = Array.Empty<byte>();
int pos = 0;
// While we still haven't reached the end of the buffer, read bytes
while (pos < receivedBytes.Length)
{
if (pos == receivedBytes.Length - 1)
{
player.UnhandledBuffer = new byte[] { receivedBytes[^1] };
break;
}
int command = receivedBytes[pos] >> 4; // Get the four command bits
if ((receivedBytes[pos++] & 0xF) != Players.IndexOf(item.Value))
{
Console.WriteLine("Unexpected client ID: {0}", (receivedBytes[pos++] & 0xF));
}
switch (command)
{
case 0: // Heartbeat from client {0x0, 0x1, 0xFF}
// In case of buffer overflow, take the last command and paste it into
// the unhandled buffer
if (pos + 2 > receivedBytes.Length)
{
player.UnhandledBuffer = new byte[receivedBytes.Length - pos + 1];
for (int i = 0; i < receivedBytes.Length - pos + 1; i++)
{
player.UnhandledBuffer[i] = receivedBytes[(pos - 1) + i];
}
// Finally, set the pos to the end of the buffer
pos = receivedBytes.Length;
}
else
{
// Buffer won't overflow, process normally
player.LastTime = DateTime.Now.Ticks;
pos += 2; // Move two bytes
}
break;
case 1: // Player sets name
List<byte> strBytes = new();
bool endReached = false;
while (pos < receivedBytes.Length && receivedBytes[pos] != 0xFF)
{
byte nextByte = receivedBytes[pos++];
if (pos != receivedBytes.Length - 1 && receivedBytes[pos + 1] == 0xFF)
{
endReached = true;
}
else
{
strBytes.Add(nextByte);
}
}
if (endReached)
{
// Buffer contained full string, move to the next command
player.PlayerName = Encoding.UTF8.GetString(strBytes.ToArray());
}
else
{
player.UnhandledBuffer = new byte[receivedBytes.Length - strBytes.Count + 1];
for (int i = 0; i < receivedBytes.Length - strBytes.Count + 1; i++)
{
player.UnhandledBuffer[i] = receivedBytes[(strBytes.Count - 1) + i];
}
// Pos is implicitely
}
break;
case 2: // update player position
if (Players[currentTurn].Equals(player))
{
// It is our turn, move
byte nextByte = receivedBytes[pos++];
int row = nextByte >> 4;
int column = nextByte & 0xF;
player.Column = column;
player.Row = row;
// Can't update turn until we are synchronised, else we would
// inadvertently accept turns from the next player
turnCompleted = true;
}
break;
default:
Console.WriteLine("Unknown client command");
break;
}
}
}
// If we haven't recieved data from this client for some time, send a packet that requests
// a response
else if (TimeSpan.FromTicks(DateTime.Now.Ticks - item.Value.LastTime) > TimeSpan.FromSeconds(5))
{
item.Key.GetStream().Write(new byte[] { (byte)(Players.IndexOf(item.Value)), 0xFF }); // Heartbeat is 0x0, 0x1
Player curr = item.Value;
curr.LastTime = DateTime.Now.Ticks;
}
}
});
// Synchronised, update player turn
if (turnCompleted)
{
currentTurn++;
currentTurn %= Players.Count;
}
// ***************************
// ** Remove old connections**
// ***************************
foreach (var item in closedClients)
{
_ = Players.Remove(clients[item]);
_ = clients.Remove(item);
}
// ***************************
// ******* Send Data *******
// ***************************
IReadOnlyCollection<byte> dataPacket;
{
List<byte> allData = new();
allData.Add(1); // command bits; gets modulated when sent
byte playersTurn = (byte)(Players.Count << 4 | currentTurn); // Player count & turn
allData.Add(playersTurn);
// Data about each player
for (int i = 0; i < Players.Count; i++)
{
// Data begins with the UTF-8 formatted player-name string (and termination char)
allData.AddRange(Encoding.UTF8.GetBytes(Players[i].PlayerName));
allData.Add(0xFE); // EoString
// Next data byte is the row & column the player is in
byte pos = (byte)(Players[i].Row << 4 | Players[i].Column);
allData.Add(pos);
// End of player is implicit
}
// Finally, terminate the command
allData.Add(0xFF);
dataPacket = allData.AsReadOnly();
}
// Create a personalised data packet for each client (tells the client its order in the game)
// Sent in parallel, to prevent a misbehaving connection from delaying everyone
_ = Parallel.ForEach(clients, (x) =>
{
Player item = x.Value;
// Alert any further ops that there is a pending data send
if (item.ObtainLock() == 0)
{
List<byte> clientPacket = new(dataPacket);
clientPacket[0] = (byte)(clientPacket[0] << 4 | Players.IndexOf(item));
long attemptStart = DateTime.Now.Ticks;
int attemptCount = 0;
while (attemptCount < 10)
{
try
{
x.Key.GetStream().Write(clientPacket.ToArray(), 0, clientPacket.Count);
break;
}
catch (IOException e)
{
Console.WriteLine(e);
_ = Task.Delay(100);
attemptCount++;
}
}
// Reset the lock;
item.ReleaseLock();
}
else
{
// client hasn't recieved old data; avoid sending new state updates
Console.WriteLine("Client is still recieving old data");
}
});
}
}
}

61
RBG_Server.Core/Player.cs Normal file
View File

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace RBG_Server
{
/// <summary>
/// Data class containing information about a player. Drawn directly to the screen, hence the inheritance
/// of Image, which allows this entire object to be
/// </summary>
public class Player : Image
{
// C# uses implicit field definitions; i.e. declaring a property creates an implicit private field
// Note that it is also possible to assign values, and specify access modifiers for the get & set independantly
public string PlayerName { get; set; }
public int Row { get; set; }
public int Column { get; set; }
public long LastTime { get; set; } = DateTime.Now.Ticks;
public bool Connected { get; private set; }
public byte[] UnhandledBuffer { get; set; }
private int processing;
public int ObtainLock()
{
return System.Threading.Interlocked.CompareExchange(ref processing, 1, 0);
}
public void ReleaseLock()
{
_ = System.Threading.Interlocked.Exchange(ref processing, 0);
}
// public Image sprite; // Sprite is now set as the implementation of this class
public Player(string name, int row, int column) : base() // Call the base constructor at the same time
{
PlayerName = name;
Row = row;
Column = column;
LastTime = DateTime.Now.Ticks;
Connected = true;
UnhandledBuffer = Array.Empty<byte>();
}
public new bool Equals(object obj)
{
return (obj as Player).GetHashCode() == GetHashCode();
}
public new int GetHashCode()
{
int res = (base.GetHashCode() + PlayerName.GetHashCode()) >> 8; // Right-shift the original hashcode by one byte & use our row & column
int rcByte = ((Row & 0xF) << 4) | (Column & 0xF);
return (rcByte << 24) | res;
}
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WPF" />
</ItemGroup>
</Project>

48
Remote Board Game.sln Normal file
View File

@ -0,0 +1,48 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31515.178
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RBG Server WPF", "PDGServer_WPF\RBG Server WPF.csproj", "{E6B5F89A-2D77-46CE-8DC2-C2F99AF9B74E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pokemon Drinking Game", "Pokemon Drinking Game\Pokemon Drinking Game.csproj", "{AE1C89D8-A6F6-425A-B4C1-AEBECE850198}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RBG_Server", "RBG_Server.Core\RBG_Server.csproj", "{0C203CD4-5139-4CB2-A55D-83405B72DAEF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4B56FDC3-E973-462F-BEEC-BB33A0E3F290}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RBG.Helpers", "RBG.Helpers\RBG.Helpers.csproj", "{B3366717-065E-4025-ABCD-5B8104FE2E0E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E6B5F89A-2D77-46CE-8DC2-C2F99AF9B74E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E6B5F89A-2D77-46CE-8DC2-C2F99AF9B74E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E6B5F89A-2D77-46CE-8DC2-C2F99AF9B74E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E6B5F89A-2D77-46CE-8DC2-C2F99AF9B74E}.Release|Any CPU.Build.0 = Release|Any CPU
{AE1C89D8-A6F6-425A-B4C1-AEBECE850198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AE1C89D8-A6F6-425A-B4C1-AEBECE850198}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AE1C89D8-A6F6-425A-B4C1-AEBECE850198}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AE1C89D8-A6F6-425A-B4C1-AEBECE850198}.Release|Any CPU.Build.0 = Release|Any CPU
{0C203CD4-5139-4CB2-A55D-83405B72DAEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0C203CD4-5139-4CB2-A55D-83405B72DAEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C203CD4-5139-4CB2-A55D-83405B72DAEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C203CD4-5139-4CB2-A55D-83405B72DAEF}.Release|Any CPU.Build.0 = Release|Any CPU
{B3366717-065E-4025-ABCD-5B8104FE2E0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B3366717-065E-4025-ABCD-5B8104FE2E0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B3366717-065E-4025-ABCD-5B8104FE2E0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B3366717-065E-4025-ABCD-5B8104FE2E0E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {07E43435-7C22-47A1-B830-5ECFAC6F6CD9}
EndGlobalSection
EndGlobal