diff --git a/.gitignore b/.gitignore index 3c4efe2..2606a7a 100644 --- a/.gitignore +++ b/.gitignore @@ -258,4 +258,5 @@ paket-files/ # Python Tools for Visual Studio (PTVS) __pycache__/ -*.pyc \ No newline at end of file +*.pyc +/Publish Win10.bat diff --git a/FramapadCopy.md b/FramapadCopy.md new file mode 100644 index 0000000..6e61dca --- /dev/null +++ b/FramapadCopy.md @@ -0,0 +1,81 @@ +Spiele ideen + + + (Text Adventure) + + Console + + Mit Karrrrrrrrrrrrrrrrrrrrrmarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr als Bösewicht errrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr will Magier sein + + Dungeon + + Karte manipulierbar + + 1h zeit zum gewinnen + + Multiplayer + + Charakter-Designer + + pen and paper style + + Highscore-Listen (online) + + modbar + + Kartenzufallsgenerierung (Procedural Generated) + + Inventar System (Gewichtsystem?) + + Fallen-System (sichtbare/unsichtbare Fallen, die dem Spieler schaden zufügen oder auf eine andere map teleportiert || Multiplayer können andere spieler fallen aufstellen) + + Zeit (Lebenszeit) wird bei wenig Health schneller abgezogen + + Eigenes Wiki + + Eigene Website + + Sammelbare Items (Z.b. um die Zeit zu erhöhen?) + + Karrmarr klaut zwischendurch Teile der Karte, um zu verhindern dass der Spieler zu ihm durch kommt :D + + Beep-Soundtrack (Amiga version) + + Storymodus + + Npcs + + Bossgegner + + eine Rüstung aus Gurken, so als Easteregg + + Cut-Scenes!hyp + + + + + +Is not going to be implemented + + hollywood voiceover + + 8bit Grafik + + Controller-Unterstützung + + Dass man regelmäßig Trinken und Essen muss, sonst stirbt man + + Rogue like + + Crafting-System + + KI Begleiter (Tiere) + + Spendenmöglichkeit für LiveEdu :P +5 + + Shopsystem + + dlc's + + + \ No newline at end of file diff --git a/Konzeption_TheRuleOfSilvester.xcf b/Konzeption_TheRuleOfSilvester.xcf new file mode 100644 index 0000000..aa683f1 Binary files /dev/null and b/Konzeption_TheRuleOfSilvester.xcf differ diff --git a/TheRuleOfSilvester.Core/ActionType.cs b/TheRuleOfSilvester.Core/ActionType.cs new file mode 100644 index 0000000..a3d5f9b --- /dev/null +++ b/TheRuleOfSilvester.Core/ActionType.cs @@ -0,0 +1,10 @@ +namespace TheRuleOfSilvester.Core +{ + public enum ActionType + { + None, + Moved, + ChangedMapCell, + CollectedItem + } +} diff --git a/TheRuleOfSilvester.Core/BasicMapGenerator.cs b/TheRuleOfSilvester.Core/BasicMapGenerator.cs new file mode 100644 index 0000000..506b826 --- /dev/null +++ b/TheRuleOfSilvester.Core/BasicMapGenerator.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using TheRuleOfSilvester.Core.Cells; + +namespace TheRuleOfSilvester.Core +{ + public abstract class BasicMapGenerator + { + protected Random random; + public List CellTypes { get; protected set; } + + public BasicMapGenerator() + { + random = new Random(); + } + + public BasicMapGenerator(Guid[] cellGUIDs) : this() + { + var types = Assembly.GetExecutingAssembly().GetTypes().Where(c => c.BaseType == typeof(MapCell)).ToList(); + + CellTypes = new List(); + + foreach (var item in cellGUIDs) + CellTypes.Add(types.FirstOrDefault(c => c.GUID == item)); + } + + public abstract Map Generate(int x, int y); + } +} \ No newline at end of file diff --git a/TheRuleOfSilvester.Core/Cell.cs b/TheRuleOfSilvester.Core/Cell.cs index 0066fe3..6eca6bb 100644 --- a/TheRuleOfSilvester.Core/Cell.cs +++ b/TheRuleOfSilvester.Core/Cell.cs @@ -1,22 +1,97 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.IO; +using System.Runtime.CompilerServices; using System.Text; +using TheRuleOfSilvester.Core.Cells; namespace TheRuleOfSilvester.Core { - public abstract class Cell + public abstract class Cell : IDisposable, INotifyPropertyChanged { - public const int HEIGHT = 3; - public const int WIDTH = 5; - public bool Invalid { get; set; } - public string[,] Lines; + public Point Position { get => position; set => SetValue(value, ref position); } + public bool Invalid { get => invalid; set => SetValue(value, ref invalid); } - public Cell() + public int Width => Lines.GetLength(0); + public int Height => Lines.GetLength(1); + public bool Movable { get; set; } + public Color Color { get; set; } + public Map Map { get; set; } + + public BaseElement[,] Lines { get; protected set; } + + public BaseElement[,] Layer { get; internal set; } + + public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangeEventHandler PropertyChange; + + protected bool disposed; + + private Point position; + private bool invalid; + + public Cell(int width, int height, Map map, bool movable = true) + { + Color = Color.White; + Lines = new BaseElement[width, height]; + Layer = new BaseElement[width, height]; + Invalid = true; + Map = map; + Movable = movable; + } + public Cell(Map map, bool movable = true) : this(5, 3, map, movable) + { + + } + + public void SetPosition(Point position) { - Lines = new string[HEIGHT, WIDTH]; + Position = position; Invalid = true; } - + + public virtual void Update(Game game) + { + + } + + public override string ToString() => $"{GetType()} | {Position.X} : {Position.Y}"; + + public virtual void Dispose() + { + if (disposed) + return; + + Position = new Point(0); + Invalid = false; + Movable = false; + Color = new Color(); + Map = null; + Lines = null; + + disposed = true; + + GC.SuppressFinalize(this); + } + + + private void SetValue(T value, ref T privateField, [CallerMemberName] string name = null) + { + if (value.Equals(privateField)) + return; + + PropertyChange?.Invoke(this, new PropertyChangeEventArgs(name, oldValue: privateField, newValue: value)); + + privateField = value; + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } + + public static bool IsOnPosition(Point pos, Cell x) => + x.Position.X * x.Width <= pos.X && (x.Position.X * x.Width + x.Width) > pos.X + && x.Position.Y * x.Height <= pos.Y && (x.Position.Y * x.Height + x.Height) > pos.Y; } } diff --git a/TheRuleOfSilvester.Core/Cells/BaseElement.cs b/TheRuleOfSilvester.Core/Cells/BaseElement.cs new file mode 100644 index 0000000..a75dd4d --- /dev/null +++ b/TheRuleOfSilvester.Core/Cells/BaseElement.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Core.Cells +{ + public class BaseElement + { + public int ElementID { get; set; } + public ConnectionPoints Connections { get; set; } + + + public BaseElement(int elementID, ConnectionPoints connections) + { + ElementID = elementID; + Connections = connections; + } + + public static implicit operator BaseElement(char value) + { + switch (value) + { + case '│': return new BaseElement(1, ConnectionPoints.Up | ConnectionPoints.Down); + case '║': return new BaseElement(2, ConnectionPoints.Up | ConnectionPoints.Down); + case '─': return new BaseElement(3, ConnectionPoints.Left | ConnectionPoints.Right); + case '═': return new BaseElement(4, ConnectionPoints.Left | ConnectionPoints.Right); + case '┌': return new BaseElement(5, ConnectionPoints.Down | ConnectionPoints.Right); + case '╔': return new BaseElement(6, ConnectionPoints.Down | ConnectionPoints.Right); + case '└': return new BaseElement(7, ConnectionPoints.Up | ConnectionPoints.Right); + case '╚': return new BaseElement(8, ConnectionPoints.Up | ConnectionPoints.Right); + case '┐': return new BaseElement(9, ConnectionPoints.Left | ConnectionPoints.Down); + case '╗': return new BaseElement(10, ConnectionPoints.Left | ConnectionPoints.Down); + case '┘': return new BaseElement(11, ConnectionPoints.Left | ConnectionPoints.Up); + case '╝': return new BaseElement(12, ConnectionPoints.Left | ConnectionPoints.Up); + case '┬': return new BaseElement(13, ConnectionPoints.Left | ConnectionPoints.Right | ConnectionPoints.Down); + case '╦': return new BaseElement(14, ConnectionPoints.Left | ConnectionPoints.Right | ConnectionPoints.Down); + case '┴': return new BaseElement(15, ConnectionPoints.Left | ConnectionPoints.Right | ConnectionPoints.Up); + case '╩': return new BaseElement(16, ConnectionPoints.Left | ConnectionPoints.Right | ConnectionPoints.Up); + case '├': return new BaseElement(17, ConnectionPoints.Down | ConnectionPoints.Up | ConnectionPoints.Right); + case '╠': return new BaseElement(18, ConnectionPoints.Down | ConnectionPoints.Up | ConnectionPoints.Right); + case '┤': return new BaseElement(19, ConnectionPoints.Down | ConnectionPoints.Up | ConnectionPoints.Left); + case '╣': return new BaseElement(20, ConnectionPoints.Down | ConnectionPoints.Up | ConnectionPoints.Left); + case '┼': return new BaseElement(21, ConnectionPoints.Down | ConnectionPoints.Up | ConnectionPoints.Left | ConnectionPoints.Right); + case '╬': return new BaseElement(22, ConnectionPoints.Down | ConnectionPoints.Up | ConnectionPoints.Left | ConnectionPoints.Right); + default: + return new BaseElement(value, ConnectionPoints.None); + } + } + + public static implicit operator BaseElement(ConnectionPoints connectionPoints) + { + switch (connectionPoints) + { + case ConnectionPoints.Up | ConnectionPoints.Down: return new BaseElement(1, connectionPoints); + case ConnectionPoints.Left | ConnectionPoints.Right: return new BaseElement(3, connectionPoints); + case ConnectionPoints.Down | ConnectionPoints.Right: return new BaseElement(5, connectionPoints); + case ConnectionPoints.Up | ConnectionPoints.Right: return new BaseElement(7, connectionPoints); + case ConnectionPoints.Left | ConnectionPoints.Down: return new BaseElement(9, connectionPoints); + case ConnectionPoints.Left | ConnectionPoints.Up: return new BaseElement(11, connectionPoints); + case ConnectionPoints.Left | ConnectionPoints.Right | ConnectionPoints.Down: return new BaseElement(13, connectionPoints); + case ConnectionPoints.Left | ConnectionPoints.Right | ConnectionPoints.Up: return new BaseElement(15, connectionPoints); + case ConnectionPoints.Down | ConnectionPoints.Up | ConnectionPoints.Right: return new BaseElement(17, connectionPoints); + case ConnectionPoints.Down | ConnectionPoints.Up | ConnectionPoints.Left: return new BaseElement(19, connectionPoints); + case ConnectionPoints.Down | ConnectionPoints.Up | ConnectionPoints.Left | ConnectionPoints.Right: return new BaseElement(21, connectionPoints); + default: + return new BaseElement(0, connectionPoints); + } + } + } +} diff --git a/TheRuleOfSilvester.Core/Cells/CellsEnum.cs b/TheRuleOfSilvester.Core/Cells/CellsEnum.cs new file mode 100644 index 0000000..dea9c1d --- /dev/null +++ b/TheRuleOfSilvester.Core/Cells/CellsEnum.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Core.Cells +{ + + public enum ConnectionPoints + { + None = 0, + Up = 1, + Down = 2, + Left = 4, + Right = 8, + } +} diff --git a/TheRuleOfSilvester.Core/Cells/CornerLeftDown.cs b/TheRuleOfSilvester.Core/Cells/CornerLeftDown.cs index e864df4..99b0d9d 100644 --- a/TheRuleOfSilvester.Core/Cells/CornerLeftDown.cs +++ b/TheRuleOfSilvester.Core/Cells/CornerLeftDown.cs @@ -1,19 +1,21 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Text; namespace TheRuleOfSilvester.Core.Cells { - public class CornerLeftDown : Cell + [Guid("C37255B3-2C41-431C-AB26-10B740837FA3")] + public class CornerLeftDown : MapCell { - public CornerLeftDown() + public CornerLeftDown(Map map, bool movable = true) : base(map, movable) { - Lines[2, 4] = "│"; - Lines[2, 0] = "┐"; - Lines[1, 4] = "│"; - Lines[0, 4] = "┐"; + Lines[4, 2] = Movable ? '│' : '║'; + Lines[0, 2] = Movable ? '┐' : '╗'; + Lines[4, 1] = Movable ? '│' : '║'; + Lines[4, 0] = Movable ? '┐' : '╗'; for (int i = 0; i < 4; i++) - Lines[0, i] = "─"; + Lines[i, 0] = Movable ? '─' : '═'; } } } diff --git a/TheRuleOfSilvester.Core/Cells/CornerLeftUp.cs b/TheRuleOfSilvester.Core/Cells/CornerLeftUp.cs index 9d6bb49..38586d3 100644 --- a/TheRuleOfSilvester.Core/Cells/CornerLeftUp.cs +++ b/TheRuleOfSilvester.Core/Cells/CornerLeftUp.cs @@ -1,19 +1,22 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Text; namespace TheRuleOfSilvester.Core.Cells { - public class CornerLeftUp : Cell + [Guid("726D1EE9-90C5-48F1-A206-2EAEB19AC24B")] + public class CornerLeftUp : MapCell { - public CornerLeftUp() + public CornerLeftUp(Map map, bool movable = true) : base(map, movable) { - Lines[0, 4] = "│"; - Lines[0, 0] = "┘"; - Lines[1, 4] = "│"; - Lines[2, 4] = "┘"; + + Lines[4, 0] = Movable ? '│' : '║'; + Lines[0, 0] = Movable ? '┘' : '╝'; + Lines[4, 1] = Movable ? '│' : '║'; + Lines[4, 2] = Movable ? '┘' : '╝'; for (int i = 0; i < 4; i++) - Lines[2, i] = "─"; + Lines[i, 2] = Movable ? '─' : '═'; } } } diff --git a/TheRuleOfSilvester.Core/Cells/CornerRightDown.cs b/TheRuleOfSilvester.Core/Cells/CornerRightDown.cs index 898b4cb..c961d50 100644 --- a/TheRuleOfSilvester.Core/Cells/CornerRightDown.cs +++ b/TheRuleOfSilvester.Core/Cells/CornerRightDown.cs @@ -1,19 +1,22 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Text; namespace TheRuleOfSilvester.Core.Cells { - public class CornerRightDown : Cell + [Guid("0D85862D-0B7D-4613-A513-F47EE3E6F8D7")] + public class CornerRightDown : MapCell { - public CornerRightDown() : base() + + public CornerRightDown(Map map, bool movable = true) : base(map, movable) { - Lines[2, 0] = "│"; - Lines[2, 4] = "┌"; - Lines[1, 0] = "│"; - Lines[0, 0] = "┌"; + Lines[0, 2] = Movable ? '│' : '║'; + Lines[4, 2] = Movable ? '┌' : '╔'; + Lines[0, 1] = Movable ? '│' : '║'; + Lines[0, 0] = Movable ? '┌' : '╔'; for (int i = 1; i < 5; i++) - Lines[0, i] = "─"; + Lines[i, 0] = Movable ? '─' : '═'; } } } diff --git a/TheRuleOfSilvester.Core/Cells/CornerRightUp.cs b/TheRuleOfSilvester.Core/Cells/CornerRightUp.cs index ec011e4..41d3852 100644 --- a/TheRuleOfSilvester.Core/Cells/CornerRightUp.cs +++ b/TheRuleOfSilvester.Core/Cells/CornerRightUp.cs @@ -1,20 +1,23 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Text; namespace TheRuleOfSilvester.Core.Cells { - public class CornerRightUp : Cell + [Guid("A0D9A44A-EF84-4E5A-929E-2664E51F481C")] + public class CornerRightUp : MapCell { - public CornerRightUp() : base() + + public CornerRightUp(Map map, bool movable = true) : base(map, movable) { - Lines[0, 0] = "│"; - Lines[0, 4] = "└"; - Lines[1, 0] = "│"; - Lines[2, 0] = "└"; + Lines[0, 0] = Movable ? '│' : '║'; + Lines[4, 0] = Movable ? '└' : '╚'; + Lines[0, 1] = Movable ? '│' : '║'; + Lines[0, 2] = Movable ? '└' : '╚'; for (int i = 1; i < 5; i++) - Lines[2, i] = "─"; + Lines[i, 2] = Movable ? '─' : '═'; } } } diff --git a/TheRuleOfSilvester.Core/Cells/Cross.cs b/TheRuleOfSilvester.Core/Cells/Cross.cs deleted file mode 100644 index 9943796..0000000 --- a/TheRuleOfSilvester.Core/Cells/Cross.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace TheRuleOfSilvester.Core.Cells -{ - public class Cross : Cell - { - public Cross() : base() - { - Lines[0, 0] = "┘"; - Lines[0, 4] = "└"; - Lines[2, 0] = "┐"; - Lines[2, 4] = "┌"; - } - } -} diff --git a/TheRuleOfSilvester.Core/Cells/CrossLeftRightUpDown.cs b/TheRuleOfSilvester.Core/Cells/CrossLeftRightUpDown.cs new file mode 100644 index 0000000..8ff5186 --- /dev/null +++ b/TheRuleOfSilvester.Core/Cells/CrossLeftRightUpDown.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace TheRuleOfSilvester.Core.Cells +{ + [Guid("315958D8-3618-4CA7-A212-FB1288D5D7A0")] + public class CrossLeftRightUpDown : MapCell + { + + public CrossLeftRightUpDown(Map map, bool movable = true) : base(map, movable) + { + Lines[0, 0] = Movable ? '┘' : '╝'; + Lines[4, 0] = Movable ? '└' : '╚'; + Lines[0, 2] = Movable ? '┐' : '╗'; + Lines[4, 2] = Movable ? '┌' : '╔'; + } + } +} diff --git a/TheRuleOfSilvester.Core/Cells/LeftDownRight.cs b/TheRuleOfSilvester.Core/Cells/LeftDownRight.cs index d2b8cc3..70dafc9 100644 --- a/TheRuleOfSilvester.Core/Cells/LeftDownRight.cs +++ b/TheRuleOfSilvester.Core/Cells/LeftDownRight.cs @@ -1,17 +1,20 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Text; namespace TheRuleOfSilvester.Core.Cells { - class LeftDownRight : Cell + [Guid("67861DA4-9D1C-42B0-9B11-85516B908F75")] + class LeftDownRight : MapCell { - public LeftDownRight() + + public LeftDownRight(Map map, bool movable = true) : base(map, movable) { - Lines[2, 0] = "┐"; - Lines[2, 4] = "┌"; + Lines[0, 2] = Movable ? '┐' : '╗'; + Lines[4, 2] = Movable ? '┌' : '╔'; for (int i = 0; i < 5; i++) - Lines[0, i] = "─"; + Lines[i, 0] = Movable ? '─' : '═'; } } } diff --git a/TheRuleOfSilvester.Core/Cells/LeftUpRight.cs b/TheRuleOfSilvester.Core/Cells/LeftUpRight.cs index 4e853bc..e844402 100644 --- a/TheRuleOfSilvester.Core/Cells/LeftUpRight.cs +++ b/TheRuleOfSilvester.Core/Cells/LeftUpRight.cs @@ -1,17 +1,20 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Text; namespace TheRuleOfSilvester.Core.Cells { - class LeftUpRight : Cell + [Guid("A107CCB2-3802-41A5-9E47-AB4D175EE89A")] + class LeftUpRight : MapCell { - public LeftUpRight() + + public LeftUpRight(Map map, bool movable = true) : base(map, movable) { - Lines[0, 0] = "┘"; - Lines[0, 4] = "└"; + Lines[0, 0] = Movable ? '┘' : '╝'; + Lines[4, 0] = Movable ? '└' : '╚'; for (int i = 0; i < 5; i++) - Lines[2, i] = "─"; + Lines[i, 2] = Movable ? '─' : '═'; } } } diff --git a/TheRuleOfSilvester.Core/Cells/MapCell.cs b/TheRuleOfSilvester.Core/Cells/MapCell.cs new file mode 100644 index 0000000..c50abf4 --- /dev/null +++ b/TheRuleOfSilvester.Core/Cells/MapCell.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using TheRuleOfSilvester.Core.Interfaces; + +namespace TheRuleOfSilvester.Core.Cells +{ + public abstract class MapCell : Cell, IByteSerializable + { + public Guid Guid { get; private set; } + + + public MapCell(Map map, bool movable = true) : base(5, 3, map, movable) + { + var guidVal = GetType().GetCustomAttribute()?.Value; + Guid = string.IsNullOrWhiteSpace(guidVal) ? Guid.NewGuid() : new Guid(guidVal); + } + + public void NormalizeLayering() + { + ClearLayer(); + var mapCells = Map.Cells.Where(x => typeof(MapCell).IsAssignableFrom(x.GetType())); + + var nTopCell = mapCells.FirstOrDefault(c => c.Position.X == Position.X && c.Position.Y == Position.Y - 1); + var nDownCell = mapCells.FirstOrDefault(c => c.Position.X == Position.X && c.Position.Y == Position.Y + 1); + var nLeftCell = mapCells.FirstOrDefault(c => c.Position.X == Position.X - 1 && c.Position.Y == Position.Y); + var nRightCell = mapCells.FirstOrDefault(c => c.Position.X == Position.X + 1 && c.Position.Y == Position.Y); + + if (nLeftCell != null) + { + Layer[0, 0] = (Layer[0, 0] == null ? ConnectionPoints.None : Layer[0, 0].Connections) | Lines[0, 0].Connections | ((nLeftCell.Lines[4, 0].Connections & ConnectionPoints.Right) == ConnectionPoints.Right ? ConnectionPoints.Left : ConnectionPoints.None); + Layer[0, 2] = (Layer[0, 2] == null ? ConnectionPoints.None : Layer[0, 2].Connections) | Lines[0, 2].Connections | ((nLeftCell.Lines[4, 2].Connections & ConnectionPoints.Right) == ConnectionPoints.Right ? ConnectionPoints.Left : ConnectionPoints.None); + } + if (nTopCell != null) + { + Layer[0, 0] = (Layer[0, 0] == null ? ConnectionPoints.None : Layer[0, 0].Connections) | Lines[0, 0].Connections | ((nTopCell.Lines[0, 2].Connections & ConnectionPoints.Down) == ConnectionPoints.Down ? ConnectionPoints.Up : ConnectionPoints.None); + Layer[4, 0] = (Layer[4, 0] == null ? ConnectionPoints.None : Layer[4, 0].Connections) | Lines[4, 0].Connections | ((nTopCell.Lines[4, 2].Connections & ConnectionPoints.Down) == ConnectionPoints.Down ? ConnectionPoints.Up : ConnectionPoints.None); + } + if (nDownCell != null) + { + Layer[0, 2] = (Layer[0, 2] == null ? ConnectionPoints.None : Layer[0, 2].Connections) | Lines[0, 2].Connections | ((nDownCell.Lines[0, 0].Connections & ConnectionPoints.Up) == ConnectionPoints.Up ? ConnectionPoints.Down : ConnectionPoints.None); + Layer[4, 2] = (Layer[4, 2] == null ? ConnectionPoints.None : Layer[4, 2].Connections) | Lines[4, 2].Connections | ((nDownCell.Lines[4, 0].Connections & ConnectionPoints.Up) == ConnectionPoints.Up ? ConnectionPoints.Down : ConnectionPoints.None); + } + if (nRightCell != null) + { + Layer[4, 0] = (Layer[4, 0] == null ? ConnectionPoints.None : Layer[4, 0].Connections) | Lines[4, 0].Connections | ((nRightCell.Lines[0, 0].Connections & ConnectionPoints.Left) == ConnectionPoints.Left ? ConnectionPoints.Right : ConnectionPoints.None); + Layer[4, 2] = (Layer[4, 2] == null ? ConnectionPoints.None : Layer[4, 2].Connections) | Lines[4, 2].Connections | ((nRightCell.Lines[0, 2].Connections & ConnectionPoints.Left) == ConnectionPoints.Left ? ConnectionPoints.Right : ConnectionPoints.None); + } + + if (!Movable) + foreach (var item in Layer) + if (item != null && item.ElementID % 2 == 1) + item.ElementID++; + + Invalid = true; + } + + public void ClearLayer() + { + Layer = new BaseElement[Layer.GetLength(0), Layer.GetLength(1)]; + } + + public void Serialize(BinaryWriter binaryWriter) + { + binaryWriter.Write(Guid.ToByteArray()); + binaryWriter.Write(Movable); + binaryWriter.Write(Position.X); + binaryWriter.Write(Position.Y); + binaryWriter.Write(Color.ToArgb()); + } + + public void Deserialize(BinaryReader binaryReader) + => throw new NotSupportedException("Use SerializeHelper.DeserializeMapCell instead ;)"); + } +} \ No newline at end of file diff --git a/TheRuleOfSilvester.Core/Cells/StraightLeftRight.cs b/TheRuleOfSilvester.Core/Cells/StraightLeftRight.cs index ca26c2e..141d55f 100644 --- a/TheRuleOfSilvester.Core/Cells/StraightLeftRight.cs +++ b/TheRuleOfSilvester.Core/Cells/StraightLeftRight.cs @@ -1,17 +1,20 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Text; namespace TheRuleOfSilvester.Core.Cells { - public class StraightLeftRight : Cell + [Guid("029E61FD-51E0-4B16-A1A1-3B6BBA534E3C")] + public class StraightLeftRight : MapCell { - public StraightLeftRight() : base() + + public StraightLeftRight(Map map, bool movable = true) : base(map, movable) { for (int i = 0; i < 5; i++) { - Lines[0, i] = "─"; - Lines[2, i] = "─"; + Lines[i, 0] = Movable ? '─' : '═'; + Lines[i, 2] = Movable ? '─' : '═'; } } } diff --git a/TheRuleOfSilvester.Core/Cells/StraightUpDown.cs b/TheRuleOfSilvester.Core/Cells/StraightUpDown.cs index 6c02b90..bed7417 100644 --- a/TheRuleOfSilvester.Core/Cells/StraightUpDown.cs +++ b/TheRuleOfSilvester.Core/Cells/StraightUpDown.cs @@ -1,17 +1,20 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Text; namespace TheRuleOfSilvester.Core.Cells { - public class StraightUpDown : Cell + [Guid("5729128C-5D6E-4ABA-BCC6-F6190D1F7FF0")] + public class StraightUpDown : MapCell { - public StraightUpDown() : base() + + public StraightUpDown(Map map, bool movable = true) : base(map, movable) { for (int i = 0; i < 3; i++) { - Lines[i, 0] = "│"; - Lines[i, 4] = "│"; + Lines[0, i] = Movable ? '│' : '║'; + Lines[4, i] = Movable ? '│' : '║'; } } } diff --git a/TheRuleOfSilvester.Core/Cells/UpDownLeft.cs b/TheRuleOfSilvester.Core/Cells/UpDownLeft.cs index 78f94d7..ec85aba 100644 --- a/TheRuleOfSilvester.Core/Cells/UpDownLeft.cs +++ b/TheRuleOfSilvester.Core/Cells/UpDownLeft.cs @@ -1,17 +1,20 @@ -using System; +using System.Runtime.InteropServices; +using System; using System.Collections.Generic; using System.Text; namespace TheRuleOfSilvester.Core.Cells { - class UpDownLeft : Cell + [Guid("22A7CB27-2AD3-491D-BD2B-4A172EC768B6")] + class UpDownLeft : MapCell { - public UpDownLeft() + + public UpDownLeft(Map map, bool movable = true) : base(map, movable) { - Lines[0, 0] = "┘"; - Lines[2, 0] = "┐"; + Lines[0, 0] = Movable ? '┘' : '╝'; + Lines[0, 2] = Movable ? '┐' : '╗'; for (int i = 0; i < 3; i++) - Lines[i, 4] = "│"; + Lines[4, i] = Movable ? '│' : '║'; } } } diff --git a/TheRuleOfSilvester.Core/Cells/UpDownRight.cs b/TheRuleOfSilvester.Core/Cells/UpDownRight.cs index 5e004f9..a5a2346 100644 --- a/TheRuleOfSilvester.Core/Cells/UpDownRight.cs +++ b/TheRuleOfSilvester.Core/Cells/UpDownRight.cs @@ -1,17 +1,20 @@ -using System; +using System.Runtime.InteropServices; +using System; using System.Collections.Generic; using System.Text; namespace TheRuleOfSilvester.Core.Cells { - class UpDownRight : Cell + [Guid("6971B8CC-9A66-45F4-98B4-6497A43F74E8")] + class UpDownRight : MapCell { - public UpDownRight() + + public UpDownRight(Map map, bool movable = true) : base(map, movable) { - Lines[2, 4] = "┌"; - Lines[0, 4] = "└"; + Lines[4, 2] = Movable ? '┌' : '╔'; + Lines[4, 0] = Movable ? '└' : '╚'; for (int i = 0; i < 3; i++) - Lines[i, 0] = "│"; + Lines[0, i] = Movable ? '│' : '║'; } } } diff --git a/TheRuleOfSilvester.Core/Conditions/ICondition.cs b/TheRuleOfSilvester.Core/Conditions/ICondition.cs new file mode 100644 index 0000000..4da0829 --- /dev/null +++ b/TheRuleOfSilvester.Core/Conditions/ICondition.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Core.Conditions +{ + public interface ICondition + { + bool Match(Player player); + } +} diff --git a/TheRuleOfSilvester.Core/Conditions/ItemHoldCondition.cs b/TheRuleOfSilvester.Core/Conditions/ItemHoldCondition.cs new file mode 100644 index 0000000..c15b127 --- /dev/null +++ b/TheRuleOfSilvester.Core/Conditions/ItemHoldCondition.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; +using TheRuleOfSilvester.Core.Items; + +namespace TheRuleOfSilvester.Core.Conditions +{ + public class ItemHoldCondition : ICondition + { + public Type ItemType { get; set; } + + public bool Match(Player player) + { + return player.ItemInventory.Any(i => ItemType.IsAssignableFrom(i?.GetType())); + } + } +} diff --git a/TheRuleOfSilvester.Core/DefaultRoundManagerComponent.cs b/TheRuleOfSilvester.Core/DefaultRoundManagerComponent.cs new file mode 100644 index 0000000..8a891f1 --- /dev/null +++ b/TheRuleOfSilvester.Core/DefaultRoundManagerComponent.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Reflection; +using System.Text; +using TheRuleOfSilvester.Core.Interfaces; +using TheRuleOfSilvester.Core.RoundComponents; + +namespace TheRuleOfSilvester.Core +{ + public class DefaultRoundManagerComponent : IRoundManagerComponent + { + public IRoundComponent Round { get; private set; } + public RoundMode RoundMode { get; set; } + + private readonly int maxRoundMode; + private TextCell roundModeCell; + private List rounds; + + private bool firstRun; + + public DefaultRoundManagerComponent(Map map) + { + rounds = Assembly.GetExecutingAssembly() + .GetTypes() + .Where(x => x.GetInterfaces().Contains(typeof(IRoundComponent))) + .Select(x => (IRoundComponent)Activator.CreateInstance(x)) + .ToList(); + + maxRoundMode = Enum.GetValues(typeof(RoundMode)).Cast().Max() + 1; + Round = rounds.FirstOrDefault(x => x.Round == RoundMode); + firstRun = true; + + roundModeCell = new TextCell(("RoundMode: " + RoundMode).PadRight(20, ' '), 20, map) + { Position = new Point(4, (map.Height * 3) + 3) }; + + map.TextCells.Add(roundModeCell); + } + + public void Update(Game game) + { + if (firstRun) + { + Round.Start(game); + firstRun = false; + } + + Round?.Update(game); + + if (game.InputAction?.Type != InputActionType.RoundButton && + Round != null && + !Round.RoundEnd) + return; + + Round?.Stop(game); + Round.RoundEnd = false; + + RoundMode += 1; + RoundMode = (RoundMode)((int)RoundMode % maxRoundMode); + + Round = rounds.FirstOrDefault(x => x.Round == RoundMode); + + Round?.Start(game); + + roundModeCell.Text = ("RoundMode: " + RoundMode).PadRight(20, ' '); + } + } +} diff --git a/TheRuleOfSilvester.Core/DefaultWaitingComponent.cs b/TheRuleOfSilvester.Core/DefaultWaitingComponent.cs new file mode 100644 index 0000000..62d5310 --- /dev/null +++ b/TheRuleOfSilvester.Core/DefaultWaitingComponent.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; +using TheRuleOfSilvester.Core.Interfaces; +using TheRuleOfSilvester.Network; + +namespace TheRuleOfSilvester.Core +{ + public class DefaultWaitingComponent : IWaitingComponent + { + + public void Update(Game game) + { + if (game.IsMutliplayer) + { + WaitForServer(game); + } + else + { + return; //TODO: Is there a Singleplayer waiting logic?????? + } + + } + + private void WaitForServer(Game game) + { + var status = game.MultiplayerComponent.CurrentServerStatus; + + if (status == ServerStatus.Waiting) + game.CurrentGameStatus = GameStatus.Waiting; + else if (status == ServerStatus.Started) + game.CurrentGameStatus = GameStatus.Running; + } + } +} diff --git a/TheRuleOfSilvester.Core/Game.cs b/TheRuleOfSilvester.Core/Game.cs index ac2d75b..ac9ecb1 100644 --- a/TheRuleOfSilvester.Core/Game.cs +++ b/TheRuleOfSilvester.Core/Game.cs @@ -1,40 +1,79 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Text; using System.Threading; using TheRuleOfSilvester.Core.Cells; +using TheRuleOfSilvester.Core.Interfaces; +using TheRuleOfSilvester.Core.Roles; namespace TheRuleOfSilvester.Core { - public class Game : IDisposable + public class Game : IDisposable, IGameStatus { + public ICollection CurrentUpdateSets { get; internal set; } public IDrawComponent DrawComponent { get; set; } + public IInputCompoment InputCompoment { get; set; } + public IMultiplayerComponent MultiplayerComponent { get; set; } + public IRoundManagerComponent RoundComponent { get; set; } + public IWaitingComponent WaitingComponent { get; set; } + public GameStatus CurrentGameStatus { get; set; } + + public int Frames { get; private set; } + + public Map Map { get; private set; } + + public bool IsRunning { get; private set; } + public bool IsMutliplayer { get; private set; } + + internal InputAction InputAction { get; private set; } + public List Winners { get; internal set; } - private int frame; private int ups; private Thread gameThread; - private Map map; - private List cells; - int c = 0; + private Player player; + private readonly ManualResetEventSlim manualResetEvent; + + public Game() => manualResetEvent = new ManualResetEventSlim(false); - public void Run(int frame, int ups) + public void Run(int frame, int ups, bool multiplayer, string playername = "") { - this.frame = frame; + IsMutliplayer = multiplayer; + + if (multiplayer) + MultiplayerComponent.Connect(); + + Frames = frame; this.ups = ups; - map = new Map(); - cells = new List { - new CornerLeftDown(), - new CornerLeftUp(), - new CornerRightDown(), - new CornerRightUp(), - new Cross(), - new LeftDownRight(), - new LeftUpRight(), - new StraightLeftRight(), - new StraightUpDown(), - new UpDownLeft(), - new UpDownRight(), - }; + + if (multiplayer) + { + Map = MultiplayerComponent.GetMap(); + player = MultiplayerComponent.ConnectPlayer(playername); + player.Map = Map; + player.IsLocal = true; + player.Color = Color.Red; + } + else + { + var generator = new MapGenerator(); + Map = generator.Generate(20, 10); + + player = new Player(Map, RoleManager.GetRandomRole()) + { + Color = Color.Red, + Position = new Point(7, 4) + }; + CurrentGameStatus = GameStatus.Running; + } + + + Map.Players.Add(player); + + if (RoundComponent == null) + RoundComponent = new DefaultRoundManagerComponent(Map); + + IsRunning = true; gameThread = new Thread(Loop) { @@ -43,35 +82,89 @@ public void Run(int frame, int ups) gameThread.Start(); } + public void Run(int frame, int ups) => Run(frame, ups, false); public void Stop() { + if (IsMutliplayer) + MultiplayerComponent.Disconnect(); + if (gameThread.IsAlive) - gameThread.Abort(); + IsRunning = false; + + CurrentGameStatus = GameStatus.Stopped; } public void Update() { - DrawComponent.Draw(map); - cells[c].Invalid = true; - map.Cells[3, 3] = cells[c++]; - c %= cells.Count; + BeforeUpdate(); + SystemUpdate(); + UiUpdate(); + AfterUpdate(); } - public void Dispose() + public void Wait() + => manualResetEvent.Wait(); + + private void BeforeUpdate() { - //Stop(); + if (InputCompoment.InputActions.TryDequeue(out var inputAction)) + { + InputAction = inputAction; + } + else + { + InputAction?.SetInvalid(); + InputAction = null; + } + } + + private void SystemUpdate() + { + if (IsMutliplayer) + MultiplayerComponent.Update(this); + + WaitingComponent?.Update(this); + + if (CurrentGameStatus == GameStatus.Running) + GameUpdate(); + } + + private void GameUpdate() + { + player.Update(this); + RoundComponent.Update(this); + } + + private void UiUpdate() + { + if (CurrentGameStatus == GameStatus.Running) + DrawComponent.Draw(Map); + else + DrawComponent.DrawCells(new List { new TextCell("NOT Running", Map) }); + } - //gameThread = null; + private void AfterUpdate() + { + InputAction?.SetInvalid(); + InputAction = null; } + public void Dispose() => + //Stop(); + + gameThread = null; + private void Loop() { - while (true) + while (IsRunning) { Update(); - Thread.Sleep(1000 / frame); + Thread.Sleep(1000 / Frames); } + + manualResetEvent.Set(); } + } } diff --git a/TheRuleOfSilvester.Core/GameStatus.cs b/TheRuleOfSilvester.Core/GameStatus.cs new file mode 100644 index 0000000..67c0b7f --- /dev/null +++ b/TheRuleOfSilvester.Core/GameStatus.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Core +{ + public enum GameStatus + { + Stopped, + Paused, + Running, + Waiting + } +} diff --git a/TheRuleOfSilvester.Core/GhostPlayer.cs b/TheRuleOfSilvester.Core/GhostPlayer.cs new file mode 100644 index 0000000..78fd7f8 --- /dev/null +++ b/TheRuleOfSilvester.Core/GhostPlayer.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using TheRuleOfSilvester.Core.Cells; + +namespace TheRuleOfSilvester.Core +{ + class GhostPlayer : Cell + { + public Cell SelectedCell => Map.GetTileAbsolutePos(Position); + + private Player player; + + private int moveSizeX; + private int moveSizeY; + private TextCell text; + + public GhostPlayer(Map map, Player original) : base(map) + { + Lines = new BaseElement[1, 1]; + player = original; + Color = Color.Green; + text = new TextCell("Ghostmode ACTIVATED", map) { Position = new Point(0, (Map.Height + 1) * 3)}; + + text.Color = Color.Red; + moveSizeX = 5; + moveSizeY = 3; + + Lines[0, 0] = original.Avatar; + Position = player.Position; + map.Cells.Add(this); + map.TextCells.Add(text); + } + + public void MoveUp() + { + if (Position.Y - moveSizeY <= 0) + return; + + MoveGeneral(new Point(Position.X, Position.Y - moveSizeY)); + } + + public void MoveDown() + { + if (Position.Y >= Map.Height * Map.Cells.FirstOrDefault().Height) + return; + + MoveGeneral(new Point(Position.X, Position.Y + moveSizeY)); + } + + public void MoveLeft() + { + if (Position.X - moveSizeX <= 0) + return; + + MoveGeneral(new Point(Position.X - moveSizeX, Position.Y)); + } + + public void MoveRight() + { + if (Position.X >= Map.Width * Map.Cells.FirstOrDefault().Width) + return; + + MoveGeneral(new Point(Position.X + moveSizeX, Position.Y)); + } + + private void MoveGeneral(Point move) + { + var cell = Map.Cells.FirstOrDefault(x => + x.Position.X * x.Width < Position.X && (x.Position.X * x.Width + x.Width) > Position.X + && x.Position.Y * x.Height < Position.Y && (x.Position.Y * x.Height + x.Height) > Position.Y); + SetPosition(move); + if (cell != null) + cell.Invalid = true; + } + + public override void Dispose() + { + if (disposed) + return; + + var cell = Map.GetTileAbsolutePos(Position); + cell.Invalid = true; + Map.Cells.Remove(this); + text.MakeBlank(); + text.PropertyChanged += (s, e) => + { + if (e.PropertyName == "Invalid" && !text.Invalid) + text.Map.TextCells.Remove(text); + text = null; + }; + + player = null; + + moveSizeX = 0; + moveSizeY = 0; + + base.Dispose(); + } + } +} diff --git a/TheRuleOfSilvester.Core/InputAction.cs b/TheRuleOfSilvester.Core/InputAction.cs new file mode 100644 index 0000000..2302f1a --- /dev/null +++ b/TheRuleOfSilvester.Core/InputAction.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Core +{ + public class InputAction + { + public InputActionType Type { get; private set; } + public bool Valid { get; private set; } + + public InputAction(InputActionType actionType) + { + Type = actionType; + Valid = true; + } + public InputAction(int actionType) : this((InputActionType)actionType) + { + } + + internal void SetInvalid() + { + Valid = false; + } + } + +} diff --git a/TheRuleOfSilvester.Core/InputActionType.cs b/TheRuleOfSilvester.Core/InputActionType.cs new file mode 100644 index 0000000..63e7508 --- /dev/null +++ b/TheRuleOfSilvester.Core/InputActionType.cs @@ -0,0 +1,13 @@ +namespace TheRuleOfSilvester.Core +{ + public enum InputActionType + { + Up, + Down, + Left, + Right, + StartAction, + RoundButton, + RoundActionButton + } +} diff --git a/TheRuleOfSilvester.Core/Interfaces/IByteSerializable.cs b/TheRuleOfSilvester.Core/Interfaces/IByteSerializable.cs new file mode 100644 index 0000000..714c59d --- /dev/null +++ b/TheRuleOfSilvester.Core/Interfaces/IByteSerializable.cs @@ -0,0 +1,10 @@ +using System.IO; + +namespace TheRuleOfSilvester.Core.Interfaces +{ + public interface IByteSerializable + { + void Serialize(BinaryWriter binaryWriter); + void Deserialize(BinaryReader binaryReader); + } +} \ No newline at end of file diff --git a/TheRuleOfSilvester.Core/Interfaces/IDrawComponent.cs b/TheRuleOfSilvester.Core/Interfaces/IDrawComponent.cs new file mode 100644 index 0000000..4eaaab9 --- /dev/null +++ b/TheRuleOfSilvester.Core/Interfaces/IDrawComponent.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Core.Interfaces +{ + public interface IDrawComponent + { + void Draw(Map map); + void DrawCells(List cells) where T : Cell; + } +} diff --git a/TheRuleOfSilvester.Core/Interfaces/IGameStatus.cs b/TheRuleOfSilvester.Core/Interfaces/IGameStatus.cs new file mode 100644 index 0000000..6d4e0bf --- /dev/null +++ b/TheRuleOfSilvester.Core/Interfaces/IGameStatus.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Core.Interfaces +{ + public interface IGameStatus + { + GameStatus CurrentGameStatus { get; } + } +} diff --git a/TheRuleOfSilvester.Core/Interfaces/IInputCompoment.cs b/TheRuleOfSilvester.Core/Interfaces/IInputCompoment.cs new file mode 100644 index 0000000..990f317 --- /dev/null +++ b/TheRuleOfSilvester.Core/Interfaces/IInputCompoment.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Core.Interfaces +{ + public interface IInputCompoment + { + ConcurrentQueue InputActions { get; } + + bool Active { get; set; } + } +} diff --git a/TheRuleOfSilvester.Core/Interfaces/IMultiplayerComponent.cs b/TheRuleOfSilvester.Core/Interfaces/IMultiplayerComponent.cs new file mode 100644 index 0000000..89db7fc --- /dev/null +++ b/TheRuleOfSilvester.Core/Interfaces/IMultiplayerComponent.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; +using TheRuleOfSilvester.Network; + +namespace TheRuleOfSilvester.Core.Interfaces +{ + public interface IMultiplayerComponent : IUpdateable + { + Client Client { get; } + int Port { get; } + string Host { get; } + ServerStatus CurrentServerStatus { get; } + + void Connect(); + + void Disconnect(); + + Map GetMap(); + + Player ConnectPlayer(string playername); + + List GetPlayers(); + + List GetWinners(); + + void TransmitActions(Stack actions, Player player); + + void EndRound(); + + bool GetUpdateSet(out ICollection updateSet); + + ServerStatus GetServerStatus(); + } +} diff --git a/TheRuleOfSilvester.Core/Interfaces/IRoundComponent.cs b/TheRuleOfSilvester.Core/Interfaces/IRoundComponent.cs new file mode 100644 index 0000000..34f1f7f --- /dev/null +++ b/TheRuleOfSilvester.Core/Interfaces/IRoundComponent.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; +using TheRuleOfSilvester.Core.RoundComponents; + +namespace TheRuleOfSilvester.Core.Interfaces +{ + public interface IRoundManagerComponent : IUpdateable + { + IRoundComponent Round { get; } + RoundMode RoundMode { get; } + + } +} diff --git a/TheRuleOfSilvester.Core/Interfaces/IUpdateable.cs b/TheRuleOfSilvester.Core/Interfaces/IUpdateable.cs new file mode 100644 index 0000000..9cbb36d --- /dev/null +++ b/TheRuleOfSilvester.Core/Interfaces/IUpdateable.cs @@ -0,0 +1,7 @@ +namespace TheRuleOfSilvester.Core.Interfaces +{ + public interface IUpdateable + { + void Update(Game game); + } +} \ No newline at end of file diff --git a/TheRuleOfSilvester.Core/Interfaces/IWaitingComponent.cs b/TheRuleOfSilvester.Core/Interfaces/IWaitingComponent.cs new file mode 100644 index 0000000..b888067 --- /dev/null +++ b/TheRuleOfSilvester.Core/Interfaces/IWaitingComponent.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Core.Interfaces +{ + public interface IWaitingComponent : IUpdateable + { + } +} diff --git a/TheRuleOfSilvester.Core/Items/BaseItemCell.cs b/TheRuleOfSilvester.Core/Items/BaseItemCell.cs new file mode 100644 index 0000000..bc7355b --- /dev/null +++ b/TheRuleOfSilvester.Core/Items/BaseItemCell.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using TheRuleOfSilvester.Core.Interfaces; + +namespace TheRuleOfSilvester.Core.Items +{ + public class BaseItemCell : Cell, IByteSerializable + { + public Guid Guid { get; private set; } + + public BaseItemCell(Map map, bool movable = true) : base(1, 1, map, movable) + { + var guidVal = GetType().GetCustomAttribute()?.Value; + Guid = string.IsNullOrWhiteSpace(guidVal) ? Guid.NewGuid() : new Guid(guidVal); + } + + public void Serialize(BinaryWriter binaryWriter) + { + binaryWriter.Write(Guid.ToByteArray()); + binaryWriter.Write(Movable); + binaryWriter.Write(Position.X); + binaryWriter.Write(Position.Y); + binaryWriter.Write(Color.ToArgb()); + } + + public void Deserialize(BinaryReader binaryReader) + => throw new NotSupportedException("Use SerializeHelper.DeserializeMapCell instead ;)"); + + } +} diff --git a/TheRuleOfSilvester.Core/Items/CoinStack.cs b/TheRuleOfSilvester.Core/Items/CoinStack.cs new file mode 100644 index 0000000..6bfda76 --- /dev/null +++ b/TheRuleOfSilvester.Core/Items/CoinStack.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using TheRuleOfSilvester.Core.Cells; + +namespace TheRuleOfSilvester.Core.Items +{ + [Guid("14165DEE-CE9D-4FE1-B73B-E3F29DEE2449")] + public class CoinStack : BaseItemCell + { + public CoinStack(Map map, bool movable = true) : base(map, movable) + { + Lines[0, 0] = '⛃'; + } + } +} diff --git a/TheRuleOfSilvester.Core/Map.cs b/TheRuleOfSilvester.Core/Map.cs index 89ed90e..bb86f6d 100644 --- a/TheRuleOfSilvester.Core/Map.cs +++ b/TheRuleOfSilvester.Core/Map.cs @@ -1,29 +1,123 @@ using System; using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; using System.Text; using TheRuleOfSilvester.Core.Cells; +using TheRuleOfSilvester.Core.Interfaces; +using TheRuleOfSilvester.Core.Items; namespace TheRuleOfSilvester.Core { - public class Map + public class Map : IByteSerializable { //┌┬┐└┴┘─│├┼┤ //╔╦╗╚╩╝═║╠╬╣ - public Cell[,] Cells { get; set; } - public Map() + public List Cells { get; set; } + public List TextCells { get; set; } + public List Players { get; set; } + public int Height { get; set; } + public int Width { get; set; } + public BasicMapGenerator MapGenerator { get; private set; } + + public Map(int width, int height, BasicMapGenerator mapGenerator) { - Cells = new Cell[10, 24]; - - - Cells[0, 0] = new CornerRightDown(); - Cells[0, 1] = new LeftDownRight(); - Cells[0, 2] = new CornerLeftDown(); - Cells[1, 0] = new UpDownRight(); - Cells[1, 1] = new Cross(); - Cells[1, 2] = new UpDownLeft(); - Cells[2, 0] = new CornerRightUp(); - Cells[2, 1] = new LeftUpRight(); - Cells[2, 2] = new CornerLeftUp(); + Players = new List(); + MapGenerator = mapGenerator; + Height = height; + Width = width; + Cells = new List(); + TextCells = new List(); + } + public Map() : this(0, 0, null) { } + + public bool IsTileOccupied(Point pos) + { + var cellList = Cells.Where(x => typeof(MapCell).IsAssignableFrom(x.GetType())).Where(x => Cell.IsOnPosition(pos, x)); + + foreach (var cell in cellList) + { + if (cell.Lines[pos.X % cell.Width, pos.Y % cell.Height] != null) + return true; + } + + return false; + } + public Cell GetTileAbsolutePos(Point pos) + { + return Cells.Where(x => typeof(MapCell).IsAssignableFrom(x.GetType())).FirstOrDefault(x => Cell.IsOnPosition(pos, x)); + + } + + public Cell SwapInventoryAndMapCell(Cell cell, Point position, int x = 5) + { + var mapCell = Cells.First(c => c.Position == position); + + cell.Position = position; + Cells.Remove(mapCell); + Cells.Add(cell); + + mapCell.Position = new Point(x, Height + 2); + cell.Invalid = true; + mapCell.Invalid = true; + + var cellsToNormalize = Cells.Where(c => + c.Position.X == cell.Position.X && c.Position.Y == cell.Position.Y - 1 + || c.Position.X == cell.Position.X && c.Position.Y == cell.Position.Y + 1 + || c.Position.X == cell.Position.X - 1 && c.Position.Y == cell.Position.Y + || c.Position.X == cell.Position.X + 1 && c.Position.Y == cell.Position.Y) + .Select(c => (MapCell)c).ToList(); + cellsToNormalize.ForEach(c => c.NormalizeLayering()); + + (cell as MapCell).NormalizeLayering(); + (mapCell as MapCell).NormalizeLayering(); + + return mapCell; } + + public void Serialize(BinaryWriter writer) + { + writer.Write(Height); + writer.Write(Width); + writer.Write(Cells.Count); + + foreach (IByteSerializable cell in Cells.Where(x => typeof(MapCell).IsAssignableFrom(x.GetType()) || + typeof(BaseItemCell).IsAssignableFrom(x.GetType()))) + { + cell.Serialize(writer); + } + + writer.Write(MapGenerator.CellTypes.Count); + + foreach (var item in MapGenerator.CellTypes) + writer.Write(item.GUID.ToByteArray()); + + } + + public void Deserialize(BinaryReader binaryReader) + { + Height = binaryReader.ReadInt32(); + Width = binaryReader.ReadInt32(); + + SerializeHelper.Map = this; + var length1 = binaryReader.ReadInt32(); + for (int i = 0; i < length1; i++) + Cells.Add(SerializeHelper.DeserializeMapCell(binaryReader)); + + var stringList = new List(); + var length = binaryReader.ReadInt32(); + for (int i = 0; i < length; i++) + stringList.Add(new Guid(binaryReader.ReadBytes(16))); + + MapGenerator = new MapGenerator(stringList.ToArray()); + + foreach (MapCell ourCell in Cells.Where(c => typeof(MapCell).IsAssignableFrom(c.GetType()))) + ourCell.NormalizeLayering(); + } + + } } diff --git a/TheRuleOfSilvester.Core/MapGenerator.cs b/TheRuleOfSilvester.Core/MapGenerator.cs new file mode 100644 index 0000000..e3260d9 --- /dev/null +++ b/TheRuleOfSilvester.Core/MapGenerator.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using TheRuleOfSilvester.Core.Cells; +using TheRuleOfSilvester.Core.Items; + +namespace TheRuleOfSilvester.Core +{ + public class MapGenerator : BasicMapGenerator + { + public MapGenerator() + { + CellTypes = new List(); + + CellTypes.AddRange(Assembly.GetExecutingAssembly() + .GetTypes() + .Where(c => c.BaseType == typeof(MapCell))); + } + + public MapGenerator(Guid[] cellGUIDs) : base(cellGUIDs) + { + } + + public override Map Generate(int x, int y) + { + var map = new Map(x, y, this); + map.Cells.Clear(); + + //var tileSet = BuildTileSet(); + + var topCells = CellTypes.Where(ct => !ct.Name.ToLower().Contains("up")).ToList(); + var downCells = CellTypes.Where(ct => !ct.Name.ToLower().Contains("down")).ToList(); + var leftCells = CellTypes.Where(ct => !ct.Name.ToLower().Contains("left")).ToList(); + var rightCells = CellTypes.Where(ct => !ct.Name.ToLower().Contains("right")).ToList(); + + map.Cells.Add(new CornerRightDown(map, false) { Position = new Point(0, 0) }); + map.Cells.Add(new CornerLeftDown(map, false) { Position = new Point(x, 0) }); + map.Cells.Add(new CornerRightUp(map, false) { Position = new Point(0, y) }); + map.Cells.Add(new CornerLeftUp(map, false) { Position = new Point(x, y) }); + + //Todo should be more generic + + for (int i = 1; i < x; i++) + { + var cell = (Cell)Activator.CreateInstance(topCells[random.Next(0, topCells.Count)], map, false); + cell.Position = new Point(i, 0); + map.Cells.Add(cell); + } + + for (int i = 1; i < y; i++) + { + var cell = (Cell)Activator.CreateInstance(leftCells[random.Next(0, topCells.Count)], map, false); + cell.Position = new Point(0, i); + cell.Movable = false; + map.Cells.Add(cell); + } + + for (int i = 1; i < x; i++) + { + var cell = (Cell)Activator.CreateInstance(downCells[random.Next(0, topCells.Count)], map, false); + cell.Position = new Point(i, y); + cell.Movable = false; + map.Cells.Add(cell); + } + + for (int i = 1; i < y; i++) + { + var cell = (Cell)Activator.CreateInstance(rightCells[random.Next(0, topCells.Count)], map, false); + cell.Position = new Point(x, i); + cell.Movable = false; + map.Cells.Add(cell); + } + //x + 1 bis x - 1 + //y + 1 bis y - 1 + + for (int tempX = 1; tempX < x; tempX++) + for (int tempY = 1; tempY < y; tempY++) + { + var nTopCell = map.Cells.FirstOrDefault(c => c.Position.X == tempX && c.Position.Y == tempY - 1); + var nDownCell = map.Cells.FirstOrDefault(c => c.Position.X == tempX && c.Position.Y == tempY + 1); + var nLeftCell = map.Cells.FirstOrDefault(c => c.Position.X == tempX - 1 && c.Position.Y == tempY); + var nRightCell = map.Cells.FirstOrDefault(c => c.Position.X == tempX + 1 && c.Position.Y == tempY); + + var possibleCells = CellTypes.Where(cellType => ((nTopCell != null + && nTopCell.GetType().Name.ToLower().Contains("down")) ? (cellType.Name.ToLower().Contains("up") ? true : false) : true) + && ((nDownCell != null && nDownCell.GetType().Name.ToLower().Contains("up")) ? (cellType.Name.ToLower().Contains("down") ? true : false) : true) + && ((nLeftCell != null && nLeftCell.GetType().Name.ToLower().Contains("right")) ? (cellType.Name.ToLower().Contains("left") ? true : false) : true) + && ((nRightCell != null && nLeftCell.GetType().Name.ToLower().Contains("left")) ? (cellType.Name.ToLower().Contains("right") ? true : false) : true)).ToArray(); + + var cell = (Cell)Activator.CreateInstance(possibleCells[random.Next(0, possibleCells.Length)], map, true); + cell.Position = new Point(tempX, tempY); + map.Cells.Add(cell); + } + + foreach (MapCell ourCell in map.Cells) + ourCell.NormalizeLayering(); + + //Default coin stack + var r = new Random(); + map.Cells.Add(new CoinStack(map) { Position = new Point(5 * r.Next(1, map.Width - 2) - 3, 3 * r.Next(1, map.Height - 2) - 2) }); + + return map; + } + } +} diff --git a/TheRuleOfSilvester.Core/PlanningComponent.cs b/TheRuleOfSilvester.Core/PlanningComponent.cs new file mode 100644 index 0000000..9e86a68 --- /dev/null +++ b/TheRuleOfSilvester.Core/PlanningComponent.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Core +{ + // TextCells mit gemachten Schritten + // Ähnlicht wie GhostPlayer, nur mit Kollision + // TextCell mit Actionskontigent + // Array mit "Actions" (Name WIP) + class PlanningComponent + { + } +} diff --git a/TheRuleOfSilvester.Core/Player.cs b/TheRuleOfSilvester.Core/Player.cs new file mode 100644 index 0000000..2d483e9 --- /dev/null +++ b/TheRuleOfSilvester.Core/Player.cs @@ -0,0 +1,361 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using TheRuleOfSilvester.Core.Cells; +using TheRuleOfSilvester.Core.Interfaces; +using TheRuleOfSilvester.Core.Items; +using TheRuleOfSilvester.Core.Roles; + +namespace TheRuleOfSilvester.Core +{ + public class Player : Cell, IByteSerializable + { + public string Name + { + get => name; set + { + if (value.Length > 20) + name = value.Substring(0, 20); + else + name = value; + } + } + public char Avatar { get; private set; } + public bool IsLocal { get; set; } + public BaseItemCell[] ItemInventory { get; set; } + public List CellInventory { get; set; } + public int Id { get; set; } + + public BaseRole Role { get; private set; } + + public event EventHandler PlayerChangedCell; + + private readonly int moveSizeX; + private readonly int moveSizeY; + + private bool ghostMode; + private GhostPlayer ghost; + private string name; + + public Player() : base(1, 1, null) + { + IsLocal = false; + Color = Color.Yellow; + Lines = new BaseElement[1, 1]; + Name = "Tim"; + Role = RoleManager.GetRandomRole(); + CellInventory = new List(); + ItemInventory = new BaseItemCell[10]; + moveSizeX = 5; + moveSizeY = 3; + } + public Player(Map map, BaseRole role) : base(1, 1, map) + { + ItemInventory = new BaseItemCell[10]; + CellInventory = new List(); + Lines = new BaseElement[1, 1]; + IsLocal = true; + moveSizeX = 5; + moveSizeY = 3; + Name = "Tim"; + ghostMode = false; + Role = role; + var random = new Random(); + + GenerateInventory(map, random); + + map.TextCells.Add(new TextCell("Inventory:", map) { Position = new Point(0, (map.Height + 1) * 3 + 1) }); + + SetAvatar(role.Avatar); + } + + public void SetMap(Map map) + { + if (Map == null) + Map = map; + } + + public void SetAvatar(char avatar) + { + Avatar = avatar; + Lines[0, 0] = Avatar; + } + + public void MoveUp(bool ghostMode) + { + if (ghostMode) + { + ghost.MoveUp(); + return; + } + + MoveGeneral(new Point(Position.X, Position.Y - moveSizeY)); + } + + public void MoveDown(bool ghostMode) + { + if (ghostMode) + { + ghost.MoveDown(); + return; + } + + MoveGeneral(new Point(Position.X, Position.Y + moveSizeY)); + } + + public void MoveLeft(bool ghostMode) + { + if (ghostMode) + { + ghost.MoveLeft(); + return; + } + + MoveGeneral(new Point(Position.X - moveSizeX, Position.Y)); + } + + public void MoveRight(bool ghostMode) + { + if (ghostMode) + { + ghost.MoveRight(); + return; + } + + MoveGeneral(new Point(Position.X + moveSizeX, Position.Y)); + } + + public void StartAction() + { + //TODO: more functions + MoveCell(); + } + + public override void Update(Game game) + { + var lastInput = game.InputAction; + + if (lastInput == null || !lastInput.Valid) + return; + + if (ghostMode) + Invalid = true; + + switch (lastInput.Type) + { + case InputActionType.Up: + MoveUp(ghostMode); + break; + case InputActionType.Down: + MoveDown(ghostMode); + break; + case InputActionType.Left: + MoveLeft(ghostMode); + break; + case InputActionType.Right: + MoveRight(ghostMode); + break; + case InputActionType.StartAction: + StartAction(); + break; + default: + break; + } + } + + private void GenerateInventory(Map map, Random random) + { + var cellTypes = map.MapGenerator?.CellTypes; + + for (int i = 0; i < 3; i++) + { + var cell = (Cell)Activator.CreateInstance(cellTypes[random.Next(0, cellTypes.Count)], map, true); + cell.Position = new Point(1 + i * 2, Map.Height + 2); + CellInventory.Add(cell); + } + } + + private void MoveCell() + { + if (!ghostMode) + { + ghostMode = true; + ghost = new GhostPlayer(Map, this); + + } + else + { + ghostMode = false; + + var changedCell = ghost.SelectedCell; + if (!changedCell.Movable) + { + changedCell.Invalid = true; + ghost.Dispose(); + ghost = null; + return; + } + Map.Cells.Remove(changedCell); + + var inventoryCell = CellInventory.FirstOrDefault(); + CellInventory.Remove(inventoryCell); + + inventoryCell.Position = changedCell.Position; + inventoryCell.Invalid = true; + + Map.Cells.Add(inventoryCell); + + changedCell.Position = new Point(5, Map.Height + 2); + changedCell.Invalid = true; + CellInventory.ForEach(x => { x.Position = new Point(x.Position.X - 2, x.Position.Y); x.Invalid = true; }); + CellInventory.Add(changedCell); + + + var cellsToNormalize = Map.Cells.Where(c => + c.Position.X == inventoryCell.Position.X && c.Position.Y == inventoryCell.Position.Y - 1 + || c.Position.X == inventoryCell.Position.X && c.Position.Y == inventoryCell.Position.Y + 1 + || c.Position.X == inventoryCell.Position.X - 1 && c.Position.Y == inventoryCell.Position.Y + || c.Position.X == inventoryCell.Position.X + 1 && c.Position.Y == inventoryCell.Position.Y) + .Select(x => (MapCell)x).ToList(); + cellsToNormalize.ForEach(x => x.NormalizeLayering()); + + (inventoryCell as MapCell).NormalizeLayering(); + (changedCell as MapCell).NormalizeLayering(); + + ghost.Dispose(); + ghost = null; + + PlayerChangedCell?.Invoke(this, inventoryCell); + } + } + + private bool MovementOccupied(int move, bool XDirection) + { + for (int i = move < 0 ? move : 0; i < (move < 0 ? 0 : move); i++) + { + if (XDirection) + { + if (Map.IsTileOccupied(new Point(Position.X + i, Position.Y))) + return true; + } + else + { + if (Map.IsTileOccupied(new Point(Position.X, Position.Y + i))) + return true; + } + } + + return false; + } + + public void MoveGeneral(Point move) + { + var mapCells = Map.Cells.OfType(); + + int m = move.X - Position.X + move.Y - Position.Y; + bool xDir = move.X - Position.X != 0; + if (MovementOccupied(m, xDir)) + return; + + var cell = mapCells.FirstOrDefault(x => IsOnPosition(Position, x)); + + SetPosition(move); + + if (cell != null) + cell.Invalid = true; + } + public void MoveGeneralRelative(Point move) + => MoveGeneral(Position + new Size(move)); + + public void Serialize(BinaryWriter binaryWriter) + { + binaryWriter.Write(Avatar); + binaryWriter.Write(Id); + binaryWriter.Write(Name); + + Role.Serialize(binaryWriter); + + binaryWriter.Write(Position.X); + binaryWriter.Write(Position.Y); + + binaryWriter.Write(CellInventory.Count); + + foreach (MapCell cell in CellInventory) + cell.Serialize(binaryWriter); + } + + public void Deserialize(BinaryReader binaryReader) + { + SetAvatar(binaryReader.ReadChar()); + Id = binaryReader.ReadInt32(); + Name = binaryReader.ReadString(); + + Role = (BaseRole)Activator.CreateInstance(Type.GetType(binaryReader.ReadString())); + + Position = new Point(binaryReader.ReadInt32(), binaryReader.ReadInt32()); + + var count = binaryReader.ReadInt32(); + + for (int i = 0; i < count; i++) + CellInventory.Add(SerializeHelper.DeserializeMapCell(binaryReader)); + + } + + public bool TryCollectItem() + { + var item = Map.Cells.FirstOrDefault( + c => c.Position == Position && typeof(BaseItemCell).IsAssignableFrom(c.GetType())); + + if (item == null) + return false; + + Map.Cells.Remove(item); + for (int i = 0; i < ItemInventory.Length; i++) + { + if (ItemInventory[i] == null) + { + ItemInventory[i] = item as BaseItemCell; + break; + } + } + + return true; + } + + public override bool Equals(object obj) + { + if (obj is Player player) + return player.Id == Id; + + return false; + } + + public override int GetHashCode() => base.GetHashCode(); + + public static bool Equals(Player player1, Player player2) + { + if (((object)player1) == null && ((object)player2) == null) + { + return true; + } + else if (((object)player1) == null && ((object)player2) != null || + ((object)player1) != null && ((object)player2) == null) + { + return false; + } + else + { + return player1.Equals(player2); + } + + } + + public static bool operator ==(Player player1, Player player2) + => Equals(player1, player2); + + public static bool operator !=(Player player1, Player player2) + => !Equals(player1, player2); + } +} diff --git a/TheRuleOfSilvester.Core/PlayerAction.cs b/TheRuleOfSilvester.Core/PlayerAction.cs new file mode 100644 index 0000000..2cd9322 --- /dev/null +++ b/TheRuleOfSilvester.Core/PlayerAction.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Text; +using TheRuleOfSilvester.Core.Interfaces; + +namespace TheRuleOfSilvester.Core +{ + public class PlayerAction : IByteSerializable + { + public Player Player { get; set; } + + public uint Order { get; set; } + + public ActionType ActionType { get; private set; } + public Point Point { get; private set; } + + public PlayerAction() + { + + } + + public PlayerAction(Player player, ActionType moveType, Point point) : this() + { + Player = player; + ActionType = moveType; + Point = point; + } + + public void Deserialize(BinaryReader binaryReader) + { + Player = new Player(); + Player.Deserialize(binaryReader); + Point = new Point(binaryReader.ReadInt32(), binaryReader.ReadInt32()); + ActionType = (ActionType)binaryReader.ReadInt32(); + Order = binaryReader.ReadUInt32(); + } + + public void Serialize(BinaryWriter binaryWriter) + { + Player.Serialize(binaryWriter); + binaryWriter.Write(Point.X); + binaryWriter.Write(Point.Y); + binaryWriter.Write((int)ActionType); + binaryWriter.Write(Order); + } + + public override string ToString() => ActionType.ToString() + " | " + Point.ToString(); + } +} diff --git a/TheRuleOfSilvester.Core/PropertyChangeEventHandler.cs b/TheRuleOfSilvester.Core/PropertyChangeEventHandler.cs new file mode 100644 index 0000000..5caf321 --- /dev/null +++ b/TheRuleOfSilvester.Core/PropertyChangeEventHandler.cs @@ -0,0 +1,20 @@ +using System; + +namespace TheRuleOfSilvester.Core +{ + public delegate void PropertyChangeEventHandler(object sender, PropertyChangeEventArgs e); + + public class PropertyChangeEventArgs : EventArgs + { + public string PropertyName { get; } + public object OldValue { get; } + public object NewValue { get; } + + public PropertyChangeEventArgs(string propertyName, object oldValue, object newValue) + { + PropertyName = propertyName; + OldValue = oldValue; + NewValue = newValue; + } + } +} \ No newline at end of file diff --git a/TheRuleOfSilvester.Core/Referee.cs b/TheRuleOfSilvester.Core/Referee.cs new file mode 100644 index 0000000..42d3671 --- /dev/null +++ b/TheRuleOfSilvester.Core/Referee.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Core +{ + public class Referee + { + public IEnumerable GetWinners(IEnumerable players) + { + foreach (var player in players) + { + var conditions = player.Role.Conditions; + if (conditions.TrueForAll(x => x.Match(player))) + yield return player; + } + } + } +} diff --git a/TheRuleOfSilvester.Core/Roles/Assassin.cs b/TheRuleOfSilvester.Core/Roles/Assassin.cs new file mode 100644 index 0000000..dd18141 --- /dev/null +++ b/TheRuleOfSilvester.Core/Roles/Assassin.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Core.Roles +{ + public class Assassin : BaseRole + { + public override char Avatar => '➳'; + + public Assassin() : base(nameof(Assassin)) + { + MaxHealthPoints = 50; + HealthPoints = MaxHealthPoints; + ActionsPoints = 10; + Attack = 30; + Defence = 0; + } + } +} diff --git a/TheRuleOfSilvester.Core/Roles/BaseRole.cs b/TheRuleOfSilvester.Core/Roles/BaseRole.cs new file mode 100644 index 0000000..6cd2bbf --- /dev/null +++ b/TheRuleOfSilvester.Core/Roles/BaseRole.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.IO; +using TheRuleOfSilvester.Core.Conditions; +using TheRuleOfSilvester.Core.Interfaces; +using TheRuleOfSilvester.Core.Items; + +namespace TheRuleOfSilvester.Core.Roles +{ + public abstract class BaseRole : IByteSerializable + { + public string Name { get; private set; } + + public List Conditions { get; set; } + + public int ActionsPoints { get; protected set; } + public int HealthPoints + { + get => healthPoints; + internal set => healthPoints = value > MaxHealthPoints ? MaxHealthPoints : value; + } + + public int MaxHealthPoints { get; protected set; } + public int Attack { get; protected set; } + public int Defence { get; protected set; } + public bool RedrawStats { get; set; } + public abstract char Avatar { get; } + public int RestActionPoints => ActionsPoints - currentActionPoints; + + private int healthPoints; + private int currentActionPoints; + + protected BaseRole(string name) + { + currentActionPoints = 0; + Name = name; + RedrawStats = true; + Conditions = new List() { new ItemHoldCondition() { ItemType = typeof(CoinStack) } }; + } + + public void Serialize(BinaryWriter binaryWriter) + => binaryWriter.Write(GetType().FullName); + + public void Deserialize(BinaryReader binaryReader) + => throw new NotSupportedException("see player"); + + public void ResetActionPoints() + { + currentActionPoints = 0; + } + + public void UseActionPoint(int actionPoints = 1) + { + currentActionPoints += actionPoints; + } + + public void SetUsedActionPoints(int actionPoints) + { + currentActionPoints = actionPoints; + RedrawStats = true; + } + } +} diff --git a/TheRuleOfSilvester.Core/Roles/Mage.cs b/TheRuleOfSilvester.Core/Roles/Mage.cs new file mode 100644 index 0000000..1fac441 --- /dev/null +++ b/TheRuleOfSilvester.Core/Roles/Mage.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Core.Roles +{ + public class Mage : BaseRole + { + public override char Avatar => '⛤'; + + public Mage() : base(nameof(Mage)) + { + MaxHealthPoints = 100; + HealthPoints = MaxHealthPoints; + ActionsPoints = 7; + Attack = 10; + Defence = 2; + } + } +} diff --git a/TheRuleOfSilvester.Core/Roles/RoleManager.cs b/TheRuleOfSilvester.Core/Roles/RoleManager.cs new file mode 100644 index 0000000..478ba6d --- /dev/null +++ b/TheRuleOfSilvester.Core/Roles/RoleManager.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace TheRuleOfSilvester.Core.Roles +{ + public static class RoleManager + { + private static List baseRoles; + + static RoleManager() + { + baseRoles = new List(); + + baseRoles.AddRange(Assembly.GetExecutingAssembly().GetTypes().Where(x => x.BaseType == typeof(BaseRole))); + } + + public static Queue GetAllRolesRandomized() + { + Random r = new Random(); + return new Queue(baseRoles.OrderBy(x => r.Next()).Select(x => (BaseRole)Activator.CreateInstance(x))); + } + + public static Queue GetRandomRoles(int amount) + { + Random r = new Random(); + var queue = new Queue(); + + for (int i = 0; i < amount; i++) + queue.Enqueue((BaseRole)Activator.CreateInstance(baseRoles[r.Next(0, baseRoles.Count)])); + + return queue; + } + + public static BaseRole GetRandomRole() + { + Random r = new Random(); + return (BaseRole)Activator.CreateInstance(baseRoles[r.Next(0, baseRoles.Count)]); + } + } +} diff --git a/TheRuleOfSilvester.Core/Roles/Thief.cs b/TheRuleOfSilvester.Core/Roles/Thief.cs new file mode 100644 index 0000000..512021a --- /dev/null +++ b/TheRuleOfSilvester.Core/Roles/Thief.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Core.Roles +{ + public class Thief : BaseRole + { + public override char Avatar => '✋'; + + public Thief() : base(nameof(Thief)) + { + MaxHealthPoints = 70; + HealthPoints = MaxHealthPoints; + ActionsPoints = 8; + Attack = 15; + Defence = 5; + } + } +} diff --git a/TheRuleOfSilvester.Core/Roles/Warrior.cs b/TheRuleOfSilvester.Core/Roles/Warrior.cs new file mode 100644 index 0000000..c5f187c --- /dev/null +++ b/TheRuleOfSilvester.Core/Roles/Warrior.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Core.Roles +{ + public class Warrior : BaseRole + { + public override char Avatar => '♞'; + + public Warrior() : base(nameof(Warrior)) + { + MaxHealthPoints = 120; + HealthPoints = MaxHealthPoints; + ActionsPoints = 5; + Attack = 20; + Defence = 10; + } + } +} diff --git a/TheRuleOfSilvester.Core/RoundComponents/ExecutingRoundComponent.cs b/TheRuleOfSilvester.Core/RoundComponents/ExecutingRoundComponent.cs new file mode 100644 index 0000000..da4eec8 --- /dev/null +++ b/TheRuleOfSilvester.Core/RoundComponents/ExecutingRoundComponent.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Drawing; +using System.Linq; + +namespace TheRuleOfSilvester.Core.RoundComponents +{ + internal class ExecutingRoundComponent : IRoundComponent + { + public RoundMode Round => RoundMode.Executing; + + public bool RoundEnd { get; set; } + public Queue CurrentUpdateSets { get; private set; } + + private int updateCount; + + public void Start(Game game) + { + CurrentUpdateSets = new Queue(game.CurrentUpdateSets); + } + + public void Stop(Game game) + { + game.MultiplayerComponent?.EndRound(); + if (game.MultiplayerComponent.CurrentServerStatus == Network.ServerStatus.Ended) + { + game.Winners = game.MultiplayerComponent.GetWinners(); + game.Stop(); + } + } + + public void Update(Game game) + { + updateCount++; + + if (game.Frames / 2 != updateCount) + return; + + if (CurrentUpdateSets.Count < 1) + return; + + PlayerAction action = CurrentUpdateSets.Dequeue(); + + var localUpdatePlayer = game.Map.Players.First(p => p == action.Player); + + switch (action.ActionType) + { + case ActionType.Moved: + localUpdatePlayer.MoveGeneralRelative(action.Point); + game.Map.Players./*Where(p => p.Position == action.Point).ToList().*/ForEach(x => x.Invalid = true); + break; + case ActionType.ChangedMapCell: + var inventoryCell = localUpdatePlayer.CellInventory.First(x => x.Position.X == 1); + localUpdatePlayer.CellInventory.Remove(inventoryCell); + + var mapCell = game.Map.SwapInventoryAndMapCell(inventoryCell, action.Point); + + localUpdatePlayer.CellInventory.ForEach(x => { x.Position = new Point(x.Position.X - 2, x.Position.Y); x.Invalid = true; }); + localUpdatePlayer.CellInventory.Add(mapCell); + localUpdatePlayer.Invalid = true; + break; + case ActionType.CollectedItem: + localUpdatePlayer.TryCollectItem(); + break; + case ActionType.None: + default: + break; + } + + updateCount = 0; + + if (CurrentUpdateSets.Count == 0) + RoundEnd = true; + } + } +} diff --git a/TheRuleOfSilvester.Core/RoundComponents/IRoundComponent.cs b/TheRuleOfSilvester.Core/RoundComponents/IRoundComponent.cs new file mode 100644 index 0000000..93e9d2b --- /dev/null +++ b/TheRuleOfSilvester.Core/RoundComponents/IRoundComponent.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Core.RoundComponents +{ + public interface IRoundComponent + { + RoundMode Round { get; } + bool RoundEnd { get; set; } + + void Update(Game game); + void Start(Game game); + void Stop(Game game); + } +} diff --git a/TheRuleOfSilvester.Core/RoundComponents/PlanningRoundComponent.cs b/TheRuleOfSilvester.Core/RoundComponents/PlanningRoundComponent.cs new file mode 100644 index 0000000..926c9fd --- /dev/null +++ b/TheRuleOfSilvester.Core/RoundComponents/PlanningRoundComponent.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Linq; +using System.Text; +using TheRuleOfSilvester.Core.Cells; +using TheRuleOfSilvester.Core.Items; + +namespace TheRuleOfSilvester.Core.RoundComponents +{ + internal class PlanningRoundComponent : IRoundComponent + { + public uint NextOrder => currentOrder++; + + private uint currentOrder; + + public RoundMode Round => RoundMode.Planning; + + public bool RoundEnd { get; set; } + + private Player player; + + private Stack actions; + + private bool propertyChangedRelevant = true; + + public PlanningRoundComponent() + { + currentOrder = 1; + } + + public void Update(Game game) + { + //Undo Button iplementation + if (game.InputAction == null) + return; + + if (game.InputAction.Type == InputActionType.RoundActionButton && game.InputAction.Valid) + { + propertyChangedRelevant = false; + UndoLastMovement(game.Map); + propertyChangedRelevant = true; + } + } + + public void Start(Game game) + { + game.InputCompoment.Active = true; + player = game.Map.Players.FirstOrDefault(x => x.IsLocal); + actions = new Stack(player.Role.ActionsPoints); + Subscribe(); + } + + public void Stop(Game game) + { + Desubscribe(); + + game.MultiplayerComponent?.TransmitActions(actions, player); + + int z = actions.Count; + for (int i = 0; i < z; i++) + UndoLastMovement(game.Map); + + game.MultiplayerComponent?.EndRound(); + } + + public void UndoLastMovement(Map map) + { + if (actions.Count == 0) + return; + + var move = actions.Pop(); + player.Role.SetUsedActionPoints(actions.Count); + switch (move.ActionType) + { + case ActionType.Moved: + player.MoveGeneral(new Point(player.Position.X - move.Point.X, player.Position.Y - move.Point.Y)); + map + .Cells + .Where(c => typeof(BaseItemCell).IsAssignableFrom(c.GetType())) + .ToList() + .ForEach(i => i.Invalid = true); + break; + case ActionType.ChangedMapCell: + + var inventoryCell = player.CellInventory.Last(); + player.CellInventory.Remove(inventoryCell); + + var mapCell = map.SwapInventoryAndMapCell(inventoryCell, move.Point, 1); + + //TODO Reduce duplicated code + player.CellInventory.ForEach(x => { x.Position = new Point(x.Position.X + 2, x.Position.Y); x.Invalid = true; }); + player.CellInventory.Insert(0, mapCell); + break; + case ActionType.CollectedItem: + var itemIndex = Array.FindLastIndex(player.ItemInventory, x => x != null); + var item = player.ItemInventory[itemIndex]; + item.Position = player.Position; + map.Cells.Add(item); + player.ItemInventory[itemIndex] = null; + item.Invalid = true; + player.Role.RedrawStats = true; + break; + } + } + + private void Subscribe() + { + player.PropertyChange += PlayerPropertyChange; + player.PropertyChanged += PlayerPropertyChanged; + + player.PlayerChangedCell += OnPlayerChangedCell; + } + + private void Desubscribe() + { + player.PropertyChange -= PlayerPropertyChange; + player.PropertyChanged -= PlayerPropertyChanged; + + player.PlayerChangedCell -= OnPlayerChangedCell; + } + + private void OnPlayerChangedCell(object sender, Cell e) + { + PlayerAction action; + + if (sender is Player senderPlayer) + action = new PlayerAction(senderPlayer, ActionType.ChangedMapCell, e.Position); + else + action = new PlayerAction(player, ActionType.ChangedMapCell, e.Position); + + action.Order = NextOrder; + actions.Push(action); + player.Role.SetUsedActionPoints(actions.Count); + } + + private void PlayerPropertyChanged(object sender, PropertyChangedEventArgs e) + { + propertyChangedRelevant = false; + //Temp maybe make it different + while (player.Role.RestActionPoints < 0) + UndoLastMovement(player.Map); + + propertyChangedRelevant = true; + + if (e.PropertyName == nameof(Player.Position) && player.TryCollectItem()) + { + PlayerAction action; + if (sender is Player senderPlayer) + action = new PlayerAction(senderPlayer, ActionType.CollectedItem, senderPlayer.Position); + else + action = new PlayerAction(player, ActionType.CollectedItem, player.Position); + + action.Order = NextOrder; + actions.Push(action); + player.Role.SetUsedActionPoints(actions.Count); + } + } + + private void PlayerPropertyChange(object sender, PropertyChangeEventArgs e) + { + if (e.PropertyName != nameof(Player.Position)) + return; + + var newPos = (Point)e.NewValue; + var oldPos = (Point)e.OldValue; + + if (propertyChangedRelevant) + { + PlayerAction action; + + if (sender is Player senderPlayer) + action = new PlayerAction(senderPlayer, ActionType.Moved, new Point(newPos.X - oldPos.X, newPos.Y - oldPos.Y)); + else + action = new PlayerAction(player, ActionType.Moved, new Point(newPos.X - oldPos.X, newPos.Y - oldPos.Y)); + + action.Order = NextOrder; + actions.Push(action); + player.Role.SetUsedActionPoints(actions.Count); + } + } + } +} diff --git a/TheRuleOfSilvester.Core/RoundComponents/WaitingRoundComponent.cs b/TheRuleOfSilvester.Core/RoundComponents/WaitingRoundComponent.cs new file mode 100644 index 0000000..b940fa3 --- /dev/null +++ b/TheRuleOfSilvester.Core/RoundComponents/WaitingRoundComponent.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TheRuleOfSilvester.Core.RoundComponents +{ + class WaitingRoundComponent : IRoundComponent + { + public RoundMode Round => RoundMode.Waiting; + + public bool RoundEnd { get; set; } + + public void Start(Game game) + { + game.InputCompoment.Active = false; + } + + public void Stop(Game game) + { + //game.MultiplayerComponent?.EndRound(); + } + + public void Update(Game game) + { + if (game.MultiplayerComponent == null) + { + RoundEnd = true; + return; + } + + if (game.MultiplayerComponent.GetUpdateSet(out ICollection updateSet)) + { + game.CurrentUpdateSets = updateSet.OrderBy(a => a.Order).ToList(); + RoundEnd = true; + }; + } + } +} diff --git a/TheRuleOfSilvester.Core/IDrawComponent.cs b/TheRuleOfSilvester.Core/RoundMode.cs similarity index 60% rename from TheRuleOfSilvester.Core/IDrawComponent.cs rename to TheRuleOfSilvester.Core/RoundMode.cs index 318ac3e..ec649c5 100644 --- a/TheRuleOfSilvester.Core/IDrawComponent.cs +++ b/TheRuleOfSilvester.Core/RoundMode.cs @@ -4,8 +4,10 @@ namespace TheRuleOfSilvester.Core { - public interface IDrawComponent + public enum RoundMode { - void Draw(Map map); + Planning, + Waiting, + Executing } } diff --git a/TheRuleOfSilvester.Core/SerializeHelper.cs b/TheRuleOfSilvester.Core/SerializeHelper.cs new file mode 100644 index 0000000..4d92a4a --- /dev/null +++ b/TheRuleOfSilvester.Core/SerializeHelper.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using TheRuleOfSilvester.Core.Cells; +using TheRuleOfSilvester.Core.Interfaces; +using TheRuleOfSilvester.Core.Items; + +namespace TheRuleOfSilvester.Core +{ + public static class SerializeHelper + { + public static Map Map { get; set; } + + private static Dictionary mapCells; + static SerializeHelper() + { + mapCells = Assembly.GetExecutingAssembly() + .GetTypes() + .Where(c => (typeof(MapCell).IsAssignableFrom(c) || typeof(BaseItemCell).IsAssignableFrom(c)) + && c.GetCustomAttribute() != null) + .ToDictionary(c => c.GetCustomAttribute().Value, + c => c); + } + + public static T Deserialize(byte[] value) where T : IByteSerializable, new() + { + var tmpObj = Activator.CreateInstance(); + + using (var memoryStream = new MemoryStream(value)) + { + using (var binaryReader = new BinaryReader(memoryStream)) + { + tmpObj.Deserialize(binaryReader); + } + + return tmpObj; + } + } + + public static ICollection DeserializeToList(byte[] value) where T : IByteSerializable, new() + { + var tmpList = Activator.CreateInstance>(); + + if (value.Length > 0) + { + using (var memoryStream = new MemoryStream(value)) + { + using (var binaryReader = new BinaryReader(memoryStream)) + { + var count = binaryReader.ReadInt32(); + for (int i = 0; i < count; i++) + { + var tmpObj = Activator.CreateInstance(); + tmpObj.Deserialize(binaryReader); + tmpList.Add(tmpObj); + } + } + + } + } + + return tmpList; + } + + public static Cell DeserializeMapCell(BinaryReader binaryReader) + { + var key = new Guid(binaryReader.ReadBytes(16)).ToString().ToUpper(); + var type = mapCells[key]; + + var cell = (Cell)Activator.CreateInstance(type, new object[] { Map, binaryReader.ReadBoolean() }); + cell.Position = new Point(binaryReader.ReadInt32(), binaryReader.ReadInt32()); + cell.Color = Color.FromArgb(binaryReader.ReadInt32()); + return cell; + } + + public static byte[] Serialize(T obj) where T : IByteSerializable + { + using (var memoryStream = new MemoryStream()) + { + using (var binaryWriter = new BinaryWriter(memoryStream)) + { + obj.Serialize(binaryWriter); + } + return memoryStream.ToArray(); + } + } + + public static byte[] SerializeList(ICollection list) where T : IByteSerializable + { + if (list == null) + return new byte[0]; + + using (var memoryStream = new MemoryStream()) + { + using (var binaryWriter = new BinaryWriter(memoryStream)) + { + binaryWriter.Write(list.Count); + + foreach (var obj in list.ToList()) + obj.Serialize(binaryWriter); + } + + return memoryStream.ToArray(); + } + } + } +} diff --git a/TheRuleOfSilvester.Core/TextCell.cs b/TheRuleOfSilvester.Core/TextCell.cs new file mode 100644 index 0000000..763a708 --- /dev/null +++ b/TheRuleOfSilvester.Core/TextCell.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; + +namespace TheRuleOfSilvester.Core +{ + public class TextCell : Cell + { + public string Text + { + get => text; set + { + if (value.Length != text?.Length) + Lines = new Cells.BaseElement[value.Length, 1]; + text = value; + for (int i = 0; i < text.Length; i++) + Lines[i, 0] = text[i]; + Invalid = true; + } + } + + private string text; + + public TextCell(string text, int length, Map map) : base(length, 1, map) + { + Text = text; + } + public TextCell(string text, Map map) : this(text, text.Length, map) + { + } + public TextCell(string text, Point pos, Map map) : this(text, map) + { + Position = pos; + } + + public void MakeBlank() + { + for (int i = 0; i < Text.Length; i++) + Lines[i, 0] = ' '; + Invalid = true; + } + + public void MakeNotBlank() + { + for (int i = 0; i < Text.Length; i++) + Lines[i, 0] = Text[i]; + Invalid = false; + } + } +} diff --git a/TheRuleOfSilvester.Core/TheRuleOfSilvester.Core.csproj b/TheRuleOfSilvester.Core/TheRuleOfSilvester.Core.csproj index 9f5c4f4..11a87f3 100644 --- a/TheRuleOfSilvester.Core/TheRuleOfSilvester.Core.csproj +++ b/TheRuleOfSilvester.Core/TheRuleOfSilvester.Core.csproj @@ -2,6 +2,12 @@ netstandard2.0 + 7.3 + 0.1.0 + + + + diff --git a/TheRuleOfSilvester.Network/BaseClient.cs b/TheRuleOfSilvester.Network/BaseClient.cs new file mode 100644 index 0000000..0a0fed0 --- /dev/null +++ b/TheRuleOfSilvester.Network/BaseClient.cs @@ -0,0 +1,166 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading; + +namespace TheRuleOfSilvester.Network +{ + public abstract class BaseClient + { + public event EventHandler<(byte[] Data, int Length)> OnMessageReceived; + + protected readonly Socket Socket; + protected readonly SocketAsyncEventArgs ReceiveArgs; + + private byte readSendQueueIndex; + private byte nextSendQueueWriteIndex; + private bool sending; + + private readonly SocketAsyncEventArgs sendArgs; + + private readonly (byte[] data, int len)[] sendQueue; + private readonly object sendLock; + + protected BaseClient(Socket socket) + { + sendQueue = new(byte[] data, int len)[256]; + sendLock = new object(); + + Socket = socket; + Socket.NoDelay = true; + + ReceiveArgs = new SocketAsyncEventArgs(); + ReceiveArgs.Completed += OnReceived; + ReceiveArgs.SetBuffer(ArrayPool.Shared.Rent(20480), 0, 20480); + + sendArgs = new SocketAsyncEventArgs(); + sendArgs.Completed += OnSent; + + } + + public void Start() + { + while (true) + { + if (Socket.ReceiveAsync(ReceiveArgs)) + return; + + ProcessInternal(ReceiveArgs.Buffer, ReceiveArgs.BytesTransferred); + } + } + + public void Disconnect() + { + Socket.Disconnect(false); + } + + public void Send(byte[] data, int len) + { + lock (sendLock) + { + if (sending) + { + sendQueue[nextSendQueueWriteIndex++] = (data, len); + return; + } + + sending = true; + } + + SendInternal(data, len); + + } + + public byte[] Send(byte[] data) + { + + var resetEvent = new ManualResetEvent(false); + (byte[] Data, int Length) localData = (new byte[0], 0); + + void messageReceived(object sender, (byte[] Data, int Length) args) + { + localData = args; + resetEvent.Set(); + } + + OnMessageReceived += messageReceived; + Send(data, data.Length); + resetEvent.WaitOne(); + OnMessageReceived -= messageReceived; + return localData.Data.Take(localData.Length).ToArray(); + } + + protected abstract void ProcessInternal(byte[] receiveArgsBuffer, int receiveArgsCount); + + private void SendInternal(byte[] data, int len) + { + while (true) + { + sendArgs.SetBuffer(data, 0, len); + + if (Socket.SendAsync(sendArgs)) + return; + + //ArrayPool.Shared.Return(data); + + lock (sendLock) + { + if (readSendQueueIndex < nextSendQueueWriteIndex) + { + (data, len) = sendQueue[readSendQueueIndex++]; + } + else + { + nextSendQueueWriteIndex = 0; + readSendQueueIndex = 0; + sending = false; + return; + } + } + } + } + + private void OnSent(object sender, SocketAsyncEventArgs e) + { + byte[] data; + int len; + + ArrayPool.Shared.Return(e.Buffer); + + lock (sendLock) + { + if (readSendQueueIndex < nextSendQueueWriteIndex) + { + (data, len) = sendQueue[readSendQueueIndex++]; + } + else + { + nextSendQueueWriteIndex = 0; + readSendQueueIndex = 0; + sending = false; + return; + } + } + + SendInternal(data, len); + } + + private void OnReceived(object sender, SocketAsyncEventArgs e) + { + ProcessInternal(e.Buffer, e.BytesTransferred); + + OnMessageReceived?.Invoke(this, (e.Buffer, e.BytesTransferred)); + + while (true) + { + if (Socket.ReceiveAsync(ReceiveArgs)) + return; + + ProcessInternal(ReceiveArgs.Buffer, ReceiveArgs.BytesTransferred); + } + } + } +} diff --git a/TheRuleOfSilvester.Network/Client.cs b/TheRuleOfSilvester.Network/Client.cs new file mode 100644 index 0000000..aaa98b8 --- /dev/null +++ b/TheRuleOfSilvester.Network/Client.cs @@ -0,0 +1,56 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; + +namespace TheRuleOfSilvester.Network +{ + public class Client : BaseClient + { + public bool IsClient { get; set; } + + private static readonly int clientReceived; + + public Client() : + base(new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + } + + public void SendPing() + { + var buffer = ArrayPool.Shared.Rent(4); + Encoding.UTF8.GetBytes("PING", 0, 4, buffer, 0); + Send(buffer, 4); + } + + public void Connect(string host, int port) + { + var address = Dns.GetHostAddresses(host).FirstOrDefault( + a => a.AddressFamily == Socket.AddressFamily); + + Socket.BeginConnect(new IPEndPoint(address, port), OnConnected, null); + } + + protected override void ProcessInternal(byte[] receiveArgsBuffer, int receiveArgsCount) + { + var tmpString = Encoding.UTF8.GetString(receiveArgsBuffer, 0, receiveArgsCount); + } + + private void OnConnected(IAsyncResult ar) + { + Socket.EndConnect(ar); + + while (true) + { + if (Socket.ReceiveAsync(ReceiveArgs)) + return; + + ProcessInternal(ReceiveArgs.Buffer, ReceiveArgs.BytesTransferred); + } + } + } +} diff --git a/TheRuleOfSilvester.Network/CommandName.cs b/TheRuleOfSilvester.Network/CommandName.cs new file mode 100644 index 0000000..1b5643c --- /dev/null +++ b/TheRuleOfSilvester.Network/CommandName.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Network +{ + public enum CommandName : short + { + GetMap, + GetMovableTiles, + NewPlayer, + GetPlayers, + UpdatePlayer, + TransmitActions, + EndRound, + Wait, + GetStatus, + GetWinners + } +} diff --git a/TheRuleOfSilvester.Network/ConnectedClient.cs b/TheRuleOfSilvester.Network/ConnectedClient.cs new file mode 100644 index 0000000..38a2181 --- /dev/null +++ b/TheRuleOfSilvester.Network/ConnectedClient.cs @@ -0,0 +1,38 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading; + +namespace TheRuleOfSilvester.Network +{ + public class ConnectedClient : BaseClient + { + private static int received; + + public int PlayerId { get; set; } + + public bool Registered => PlayerId > 0; + + public event EventHandler<(short Command, byte[] Data)> OnCommandReceived; + + public ConnectedClient(Socket socket) : base(socket) + { + PlayerId = -1; + } + + protected override void ProcessInternal(byte[] receiveArgsBuffer, int receiveArgsCount) + { + if (receiveArgsCount < 2) + receiveArgsCount = 2; //because of the substraction in the next lines + + (short Command, byte[] Data) = (0, new byte[receiveArgsCount - 2]); + Command = BitConverter.ToInt16(receiveArgsBuffer, 0); + Array.Copy(receiveArgsBuffer, 2, Data, 0, receiveArgsCount - 2); + OnCommandReceived?.Invoke(this, (Command, Data)); + } + + } +} diff --git a/TheRuleOfSilvester.Network/Server.cs b/TheRuleOfSilvester.Network/Server.cs new file mode 100644 index 0000000..8f6322b --- /dev/null +++ b/TheRuleOfSilvester.Network/Server.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace TheRuleOfSilvester.Network +{ + public class Server : IDisposable + { + public event EventHandler OnClientConnected; + public int ClientAmount => connectedClients.Count; + + private Socket socket; + private List connectedClients; + private readonly object lockObj; + + public Server() + { + socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + lockObj = new object(); + } + + public void Start(IPAddress address, int port) + { + connectedClients = new List(); + socket.Bind(new IPEndPoint(address, port)); + socket.Listen(1024); + socket.BeginAccept(OnClientAccepted, null); + } + public void Start(string host, int port) + { + var address = Dns.GetHostAddresses(host).FirstOrDefault( + a => a.AddressFamily == socket.AddressFamily); + + Start(address, port); + } + + public void Stop() + { + foreach (var client in connectedClients.ToArray()) + { + client.Disconnect(); + connectedClients.Remove(client); + } + + socket.Disconnect(true); + } + + public void Dispose() + { + Stop(); + connectedClients.Clear(); + connectedClients = null; + socket.Dispose(); + socket = null; + } + + private void OnClientAccepted(IAsyncResult ar) + { + var tmpSocket = socket.EndAccept(ar); + socket.BeginAccept(OnClientAccepted, null); + tmpSocket.NoDelay = true; + + var client = new ConnectedClient(tmpSocket); + OnClientConnected?.Invoke(this, client); + + lock (lockObj) + connectedClients.Add(client); + + client.Start(); + } + } +} diff --git a/TheRuleOfSilvester.Network/ServerStatus.cs b/TheRuleOfSilvester.Network/ServerStatus.cs new file mode 100644 index 0000000..fd45db0 --- /dev/null +++ b/TheRuleOfSilvester.Network/ServerStatus.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester.Network +{ + public enum ServerStatus : byte + { + //0 - 0 -> Undefined + Undefined, + //1 - 9 -> System Information + Ok, + Closed, + //10 - 19 -> Reserved for later usage + //20 - 29 -> Game Information + Started = 21, + Waiting = 22, + Stopped = 23, + Paused = 24, + Ended = 25, + //30 - 39 -> Player Information + Joined = 30, + Left = 31, + Kicked = 32, + Banned = 33, + ConnectionRefused = 34, + //40 - 49 -> Reserved for later usage + //50 - 59 -> Errors + Error = 50, + NetworkConnectionError = 51, + Timeout = 52, + } +} diff --git a/TheRuleOfSilvester.Network/TheRuleOfSilvester.Network.csproj b/TheRuleOfSilvester.Network/TheRuleOfSilvester.Network.csproj new file mode 100644 index 0000000..c3df6cf --- /dev/null +++ b/TheRuleOfSilvester.Network/TheRuleOfSilvester.Network.csproj @@ -0,0 +1,14 @@ + + + + netstandard2.0 + 0.1.0 + + + + + C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.netcore.app\2.0.0\ref\netcoreapp2.0\System.Buffers.dll + + + + diff --git a/TheRuleOfSilvester.Server/ActionCache.cs b/TheRuleOfSilvester.Server/ActionCache.cs new file mode 100644 index 0000000..7372943 --- /dev/null +++ b/TheRuleOfSilvester.Server/ActionCache.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Text; +using TheRuleOfSilvester.Core; + +namespace TheRuleOfSilvester.Server +{ + internal class ActionCache : IEnumerable> + { + private readonly ConcurrentDictionary> internalDictionary; + + public ActionCache() + { + internalDictionary = new ConcurrentDictionary>(); + } + + public bool TryAdd(int playerId, PlayerAction action) + { + if (!internalDictionary.TryGetValue(playerId, out Queue queue)) + { + if (internalDictionary.ContainsKey(playerId)) + { + return false; + } + else + { + queue = new Queue(); + if (!internalDictionary.TryAdd(playerId, queue)) + return false; + } + } + + queue.Enqueue(action); + + return true; + } + public bool TryGet(int playerId, out PlayerAction action) + { + action = null; + + if (!internalDictionary.TryGetValue(playerId, out Queue queue)) + return false; + + if (queue.TryPeek(out action)) + return false; + + return true; + } + + public bool TryNext(out List actions) + { + actions = new List(); + + foreach (var playerStack in internalDictionary) + { + if (playerStack.Value.TryDequeue(out PlayerAction playerAction)) + actions.Add(playerAction); + } + + return actions.Count > 0; + } + + internal void AddRange(IEnumerable playerActions) + { + foreach (var action in playerActions) + TryAdd(action.Player.Id, action); + } + + public IEnumerator> GetEnumerator() + => new CacheEnumerator(this); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public class CacheEnumerator : IEnumerator> + { + private readonly ActionCache actionCache; + + public CacheEnumerator(ActionCache actionCache) + { + this.actionCache = actionCache; + } + + public List Current { get; private set; } + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + var result = actionCache.TryNext(out List playerActions); + Current = playerActions; + return result; + } + + public void Reset() + { + Current = null; + } + + public void Dispose() + { + Reset(); + } + } + } +} diff --git a/TheRuleOfSilvester.Server/CommandArgs.cs b/TheRuleOfSilvester.Server/CommandArgs.cs new file mode 100644 index 0000000..6ac2501 --- /dev/null +++ b/TheRuleOfSilvester.Server/CommandArgs.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; +using TheRuleOfSilvester.Network; + +namespace TheRuleOfSilvester.Server +{ + public class CommandArgs + { + public NetworkPlayer NetworkPlayer { get; set; } + public byte[] Data { get; set; } + public ConnectedClient Client { get; } + + public bool HavePlayer => NetworkPlayer != null; + + public CommandArgs(NetworkPlayer player, ConnectedClient client, byte[] data) + { + NetworkPlayer = player; + Data = data; + Client = client; + } + } +} diff --git a/TheRuleOfSilvester.Server/Commands/GeneralCommands.cs b/TheRuleOfSilvester.Server/Commands/GeneralCommands.cs new file mode 100644 index 0000000..6b63d3e --- /dev/null +++ b/TheRuleOfSilvester.Server/Commands/GeneralCommands.cs @@ -0,0 +1,30 @@ +using CommandManagementSystem.Attributes; +using System; +using System.Collections.Generic; +using System.Text; +using TheRuleOfSilvester.Core; +using TheRuleOfSilvester.Network; + +namespace TheRuleOfSilvester.Server.Commands +{ + public static partial class GeneralCommands + { + [Command((short)CommandName.NewPlayer)] + public static byte[] NewPlayer(CommandArgs args) + { + var playerName = Encoding.UTF8.GetString(args.Data); + + Console.WriteLine($"{playerName} has a joint game"); + + return SerializeHelper.Serialize(GameManager.GetNewPlayer(args.Client, playerName)); + } + + [Command((short)CommandName.GetStatus)] + public static byte[] GetStatus(CommandArgs args) + => new byte[] { (byte)args.NetworkPlayer.CurrentServerStatus }; + + [Command((short)CommandName.GetWinners)] + public static byte[] GetWinners(CommandArgs args) + => SerializeHelper.SerializeList(GameManager.GetWinners()); + } +} diff --git a/TheRuleOfSilvester.Server/Commands/MapCommands.cs b/TheRuleOfSilvester.Server/Commands/MapCommands.cs new file mode 100644 index 0000000..60659e4 --- /dev/null +++ b/TheRuleOfSilvester.Server/Commands/MapCommands.cs @@ -0,0 +1,32 @@ +using CommandManagementSystem.Attributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using TheRuleOfSilvester.Core; +using TheRuleOfSilvester.Network; + +namespace TheRuleOfSilvester.Server.Commands +{ + public partial class MapCommands + { + [Command((short)CommandName.GetMap)] + public static byte[] GetMap(CommandArgs args) => SerializeHelper.Serialize(GameManager.Map); + + [Command((short)CommandName.GetPlayers)] + public static byte[] GetPlayers(CommandArgs args) + => SerializeHelper.SerializeList(GameManager.Map.Players.Where(p => p.Id != args.Client.PlayerId).ToList()); + + [Command((short)CommandName.UpdatePlayer)] + public static byte[] UpdatePlayer(CommandArgs args) + { + //if(!args.HavePlayer) + // return BitConverter.GetBytes((short)CommandNames.UpdatePlayer); + + var newPlayer = SerializeHelper.Deserialize(args.Data); + args.NetworkPlayer.Player.Position = newPlayer.Position; + return BitConverter.GetBytes((short)CommandName.UpdatePlayer); + } + + } +} diff --git a/TheRuleOfSilvester.Server/Commands/RoundCommands.cs b/TheRuleOfSilvester.Server/Commands/RoundCommands.cs new file mode 100644 index 0000000..94a1922 --- /dev/null +++ b/TheRuleOfSilvester.Server/Commands/RoundCommands.cs @@ -0,0 +1,40 @@ +using CommandManagementSystem.Attributes; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using TheRuleOfSilvester.Core; +using TheRuleOfSilvester.Network; + +namespace TheRuleOfSilvester.Server.Commands +{ + public partial class RoundCommands + { + [Command((short)CommandName.TransmitActions)] + public static byte[] TransmitActions(CommandArgs args) + { + var playerActions = SerializeHelper.DeserializeToList(args.Data.ToArray()).ToList(); + GameManager.AddRoundActions(args.NetworkPlayer.Player, playerActions.OrderBy(a => a.Order).ToList()); + + return BitConverter.GetBytes((short)CommandName.TransmitActions); + } + + [Command((short)CommandName.EndRound)] + public static byte[] EndRound(CommandArgs args) + { + GameManager.EndRound(args.NetworkPlayer); + return BitConverter.GetBytes((short)CommandName.EndRound); + } + + [Command((short)CommandName.Wait)] + public static byte[] Wait(CommandArgs args) + { + return BitConverter + .GetBytes(args.NetworkPlayer.RoundMode == RoundMode.Executing) + .Concat(SerializeHelper.SerializeList(args.NetworkPlayer.UpdateSets ?? new List())) + .ToArray(); + } + } +} diff --git a/TheRuleOfSilvester.Server/Executor.cs b/TheRuleOfSilvester.Server/Executor.cs new file mode 100644 index 0000000..7653e34 --- /dev/null +++ b/TheRuleOfSilvester.Server/Executor.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using TheRuleOfSilvester.Core; + +namespace TheRuleOfSilvester.Server +{ + internal class Executor + { + public List UpdateSets { get; } + + private readonly ActionCache actionCache; + + public Executor(ActionCache actionCache) + { + this.actionCache = actionCache; + UpdateSets = new List(); + } + + public void Execute(Map map) + { + UpdateSets.Clear(); + + foreach (var actions in actionCache) + { + foreach (var action in actions) + { + var player = action.Player; + + switch (action.ActionType) + { + case ActionType.Moved: + var point = new Point(player.Position.X + action.Point.X, player.Position.Y + action.Point.Y); + map.Players.First(p => p == player).MoveGeneral(point); + break; + case ActionType.ChangedMapCell: + var cell = map.Cells.First(c => c.Position == action.Point); + map.Cells.Remove(cell); + var inventoryCell = player.CellInventory.First(); + inventoryCell.Position = cell.Position; + inventoryCell.Invalid = true; + map.Cells.Add(inventoryCell); + player.CellInventory.Remove(inventoryCell); + player.CellInventory.Add(cell); + + cell.Position = new Point(5, map.Height + 2); + cell.Invalid = true; + player.CellInventory.ForEach(x => + { + x.Position = new Point(x.Position.X - 2, x.Position.Y); + x.Invalid = true; + }); + break; + case ActionType.CollectedItem: + player.TryCollectItem(); + break; + case ActionType.None: + default: + break; + + } + + UpdateSets.Add(action); + } + } + } + } +} \ No newline at end of file diff --git a/TheRuleOfSilvester.Server/GameManager.cs b/TheRuleOfSilvester.Server/GameManager.cs new file mode 100644 index 0000000..be83b47 --- /dev/null +++ b/TheRuleOfSilvester.Server/GameManager.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using TheRuleOfSilvester.Core; +using TheRuleOfSilvester.Core.Interfaces; +using TheRuleOfSilvester.Core.Roles; +using TheRuleOfSilvester.Network; + +namespace TheRuleOfSilvester.Server +{ + internal static class GameManager + { + public static Map Map { get; private set; } + public static Dictionary Players { get; private set; } + private static readonly Queue roles; + private static readonly ActionCache actionCache; + private static readonly Executor executor; + + private static SemaphoreSlim winnersSemaphore; + private static List listOfWinners; + + static GameManager() + { + Map = GenerateMap(); + + listOfWinners = new List(); + winnersSemaphore = new SemaphoreSlim(1, 1); + + actionCache = new ActionCache(); + executor = new Executor(actionCache); + Players = new Dictionary(); + roles = RoleManager.GetAllRolesRandomized(); + } + + internal static List GetWinners() + { + var tmpPlayer = new List(); + winnersSemaphore.Wait(); + tmpPlayer.AddRange(listOfWinners); + winnersSemaphore.Release(); + return tmpPlayer; + } + + internal static void AddRoundActions(Player player, List playerActions) + { + playerActions.ForEach(a => a.Player = player); + actionCache.AddRange(playerActions); + } + + internal static Player GetNewPlayer(ConnectedClient client, string playername) + { + int tmpId = Players.Count + 1; + + while (Players.ContainsKey(tmpId)) + tmpId++; + + var player = new Player(Map, roles.Dequeue()) + { + Name = playername, + Position = new Point(7, 4), + }; + Players.Add(tmpId, new NetworkPlayer(player) + { + Client = client + }); + Map.Players.Add(player); + player.Id = tmpId; + client.PlayerId = tmpId; + return player; + } + + internal static void StopGame() + { + + } + + internal static void StartGame() + { + foreach (var player in Players) + player.Value.CurrentServerStatus = ServerStatus.Started; + } + + internal static void EndRound(NetworkPlayer player) + { + player.RoundMode++; + + if (player.UpdateSets.Count > 0) + player.UpdateSets.Clear(); + + CheckAllPlayersAsync(); + } + + private static Map GenerateMap() + => new MapGenerator().Generate(20, 10); + + private static void CheckAllPlayersAsync() + { + Task.Run(() => + { + var referee = new Referee(); + var tmpPlayers = Players.Values.ToList(); + foreach (var player in tmpPlayers) + { + if (player.RoundMode != RoundMode.Waiting) + return; + } + + Execute(); + var winners = referee.GetWinners(tmpPlayers.Select(t => t.Player)); + if (winners.Count() > 0) + { + Console.Clear(); + foreach (var winner in winners) + { + Console.WriteLine($"{winner.Name} has won the match :)"); + } + + tmpPlayers.ForEach(p => p.CurrentServerStatus = ServerStatus.Ended); + + winnersSemaphore.Wait(); + listOfWinners.AddRange(winners); + winnersSemaphore.Release(); + } + }); + } + + + + private static void Execute() + { + executor.Execute(Map); + + foreach (var player in Players.Values) + { + player.UpdateSets = executor.UpdateSets; + player.RoundMode++; + } + } + + } +} diff --git a/TheRuleOfSilvester.Server/NetworkPlayer.cs b/TheRuleOfSilvester.Server/NetworkPlayer.cs new file mode 100644 index 0000000..eeb1b3a --- /dev/null +++ b/TheRuleOfSilvester.Server/NetworkPlayer.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using TheRuleOfSilvester.Core; +using TheRuleOfSilvester.Network; + +namespace TheRuleOfSilvester.Server +{ + public class NetworkPlayer + { + public Player Player { get; set; } + public ConnectedClient Client { get; internal set; } + public RoundMode RoundMode + { + get => roundMode; + internal set + { + roundMode = (RoundMode)((int)value % maxRoundMode); + OnRoundModeChange?.Invoke(this, roundMode); + } + } + public ServerStatus CurrentServerStatus + { + get + { + var tmp = currentServerStatus; + if (tmp == ServerStatus.Started) + currentServerStatus = ServerStatus.Ok; + return tmp; + } + set => currentServerStatus = value; + } + private ServerStatus currentServerStatus; + + public List UpdateSets { get; internal set; } + + public event EventHandler OnRoundModeChange; + + private RoundMode roundMode; + + private readonly int maxRoundMode; + + public NetworkPlayer(Player player) + { + Player = player; + maxRoundMode = Enum.GetValues(typeof(RoundMode)).Cast().Max() + 1; + UpdateSets = new List(); + CurrentServerStatus = ServerStatus.Waiting; + } + + } +} diff --git a/TheRuleOfSilvester.Server/Program.cs b/TheRuleOfSilvester.Server/Program.cs new file mode 100644 index 0000000..2a259bc --- /dev/null +++ b/TheRuleOfSilvester.Server/Program.cs @@ -0,0 +1,56 @@ +using CommandManagementSystem; +using System; +using System.Net; +using System.Threading; +using TheRuleOfSilvester.Network; + +namespace TheRuleOfSilvester.Server +{ + class Program + { + private static DefaultCommandManager manager; + static Network.Server server; + + static void Main(string[] args) + { + var mResetEvent = new ManualResetEvent(false); + manager = new DefaultCommandManager(typeof(Program).Namespace + ".Commands"); + + using (server = new Network.Server()) + { + server.Start(IPAddress.Any, 4400); + Console.CancelKeyPress += (s, e) => mResetEvent.Reset(); + server.OnClientConnected += ServerOnClientConnected; + Console.WriteLine("Server has started, waiting for clients"); + string command; + do + { + command = ""; + command = Console.ReadLine(); + } while (command.ToLower() != "!start"); + + Console.WriteLine("Game started."); + GameManager.StartGame(); + mResetEvent.WaitOne(); + GameManager.StopGame(); + } + } + + private static void ServerOnClientConnected(object sender, ConnectedClient e) + { + e.OnCommandReceived += (s, args) => + { + var connectedClient = (ConnectedClient)s; + NetworkPlayer player = null; + + if (connectedClient.Registered) + GameManager.Players.TryGetValue(connectedClient.PlayerId, out player); + + var answer = manager.Dispatch(command: (CommandName)args.Command, new CommandArgs(player, connectedClient, args.Data)); + e.Send(answer, answer.Length); + }; + + Console.WriteLine("New Client has connected. Current Amount: " + (server.ClientAmount + 1)); + } + } +} diff --git a/TheRuleOfSilvester.Server/TheRuleOfSilvester.Server.csproj b/TheRuleOfSilvester.Server/TheRuleOfSilvester.Server.csproj new file mode 100644 index 0000000..2ff3968 --- /dev/null +++ b/TheRuleOfSilvester.Server/TheRuleOfSilvester.Server.csproj @@ -0,0 +1,23 @@ + + + + Exe + netcoreapp2.0 + 7.2 + 0.1.0 + + + + 7.2 + + + + + + + + + + + + diff --git a/TheRuleOfSilvester.sln b/TheRuleOfSilvester.sln index 91601a9..f2cbc7f 100644 --- a/TheRuleOfSilvester.sln +++ b/TheRuleOfSilvester.sln @@ -1,11 +1,15 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27130.2010 +VisualStudioVersion = 15.0.27130.2036 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheRuleOfSilvester", "TheRuleOfSilvester\TheRuleOfSilvester.csproj", "{C68BA472-A503-47C4-B9CE-56C47D963044}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TheRuleOfSilvester", "TheRuleOfSilvester\TheRuleOfSilvester.csproj", "{C68BA472-A503-47C4-B9CE-56C47D963044}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheRuleOfSilvester.Core", "TheRuleOfSilvester.Core\TheRuleOfSilvester.Core.csproj", "{02EBCB6A-D3FB-4186-B2CB-4D59AC0A7010}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TheRuleOfSilvester.Core", "TheRuleOfSilvester.Core\TheRuleOfSilvester.Core.csproj", "{02EBCB6A-D3FB-4186-B2CB-4D59AC0A7010}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TheRuleOfSilvester.Network", "TheRuleOfSilvester.Network\TheRuleOfSilvester.Network.csproj", "{B398BF7B-2601-4FD0-99DA-F983A389524C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheRuleOfSilvester.Server", "TheRuleOfSilvester.Server\TheRuleOfSilvester.Server.csproj", "{09086C9C-0F90-436D-BC43-76D5376C2D35}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -21,6 +25,14 @@ Global {02EBCB6A-D3FB-4186-B2CB-4D59AC0A7010}.Debug|Any CPU.Build.0 = Debug|Any CPU {02EBCB6A-D3FB-4186-B2CB-4D59AC0A7010}.Release|Any CPU.ActiveCfg = Release|Any CPU {02EBCB6A-D3FB-4186-B2CB-4D59AC0A7010}.Release|Any CPU.Build.0 = Release|Any CPU + {B398BF7B-2601-4FD0-99DA-F983A389524C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B398BF7B-2601-4FD0-99DA-F983A389524C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B398BF7B-2601-4FD0-99DA-F983A389524C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B398BF7B-2601-4FD0-99DA-F983A389524C}.Release|Any CPU.Build.0 = Release|Any CPU + {09086C9C-0F90-436D-BC43-76D5376C2D35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09086C9C-0F90-436D-BC43-76D5376C2D35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09086C9C-0F90-436D-BC43-76D5376C2D35}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09086C9C-0F90-436D-BC43-76D5376C2D35}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TheRuleOfSilvester/DrawComponent.cs b/TheRuleOfSilvester/DrawComponent.cs index fbc8816..cb914ea 100644 --- a/TheRuleOfSilvester/DrawComponent.cs +++ b/TheRuleOfSilvester/DrawComponent.cs @@ -1,36 +1,172 @@ using System; using System.Collections.Generic; -using System.Text; +using System.Linq; using TheRuleOfSilvester.Core; +using TheRuleOfSilvester.Core.Cells; +using TheRuleOfSilvester.Core.Interfaces; +using TheRuleOfSilvester.Core.Items; +using TheRuleOfSilvester.Core.Roles; namespace TheRuleOfSilvester { internal class DrawComponent : IDrawComponent { + private BaseItemCell[] oldPlayerInventory; + public void Draw(Map map) { - for (int i = 0; i < map.Cells.GetLength(0); i++) + DrawCells(map.Cells); + + //TODO: Quick and Dirty, must be set to player pos later on + DrawCells(map.Players); + //TODO: Unschön, Spieler weiß wer er ist, vlt. anders schöner? + + var localPlayer = map.Players.FirstOrDefault(x => x.IsLocal); + DrawCells(localPlayer.CellInventory); + + DrawCells(map.TextCells); + DrawPlayerInfo(localPlayer); + DrawItemInventory(localPlayer); + + Console.SetCursorPosition(Console.WindowWidth - 2, Console.WindowHeight - 2); + } + + private void DrawPlayerHealth(BaseRole role) + { + string s = ""; + + var points = role.HealthPoints * 10d / role.MaxHealthPoints; + + for (int i = 1; i < points; points--) + s += "█"; + Console.Write(s);//U+2588 + + char c = '█'; + c += (char)Math.Ceiling(7 - points * 7); + Console.Write(c); + + for (int i = 0; i < 10 - role.HealthPoints * 10 / role.MaxHealthPoints; i++) + Console.Write(" "); + } + + public void DrawPlayerInfo(Player player) + { + if (!player.Role.RedrawStats) + return; + int topPos = 1; + void ResetCursor() { - for (int o = 0; o < map.Cells.GetLength(1); o++) + Console.CursorLeft = Console.WindowWidth - 26; + Console.CursorTop = topPos++; + } + ResetCursor(); + Console.Write(player.Name); + ResetCursor(); + Console.Write($"♥HP: {player.Role.HealthPoints} "); + ResetCursor(); + Console.ForegroundColor = ConsoleColor.Red; + DrawPlayerHealth(player.Role); + Console.ForegroundColor = ConsoleColor.White; + ResetCursor(); + Console.Write("⚔ATK: " + player.Role.Attack); + ResetCursor(); + Console.Write("⛨DEF: " + player.Role.Defence); + ResetCursor(); + Console.Write("▼AP: " + player.Role.RestActionPoints); + player.Role.RedrawStats = false; + } + + public void DrawItemInventory(Player player) + { + int topPos = 7; + void ResetCursor() + { + Console.CursorLeft = Console.WindowWidth - 26; + Console.CursorTop = topPos++; + } + + var itemInventory = player.ItemInventory; + + if (oldPlayerInventory == null) + oldPlayerInventory = new BaseItemCell[itemInventory.Length]; + + for (int i = 0; i < itemInventory.Length; i++) + { + ResetCursor(); + + if (itemInventory[i] != oldPlayerInventory[i]) + { + if (itemInventory[i] == null) + Console.Write(' '); + else + Console.Write((char)itemInventory[i].Lines[0, 0].ElementID); + } + } + + player.ItemInventory.CopyTo(oldPlayerInventory, 0); + } + + public void DrawCells(List cells) where T : Cell + { + foreach (var cell in cells.ToArray()) + { + if (cell.Invalid) { - var cell = map.Cells[i, o]; - if (cell != null && cell.Invalid) + Console.ForegroundColor = Enum.TryParse(cell.Color.Name, out ConsoleColor color) ? color : ConsoleColor.White; + + for (int l = 0; l < cell.Lines.GetLength(0); l++) { - for (int k = 0; k < cell.Lines.GetLength(0); k++) + for (int h = 0; h < cell.Lines.GetLength(1); h++) { - for (int l = 0; l < cell.Lines.GetLength(1); l++) - { - Console.SetCursorPosition(o * 5 + l, i * 3 + k); - if (cell.Lines[k, l] == null) - Console.Write(" "); - else - Console.Write(cell.Lines[k, l]); - cell.Invalid = false; - } + Console.SetCursorPosition((cell.Position.X * cell.Width) + l, (cell.Position.Y * cell.Height) + h); + + if (cell.Layer[l, h] != null) + Console.Write(BaseElementToChar(cell.Layer[l, h])); + else + Console.Write(BaseElementToChar(cell.Lines[l, h])); + + cell.Invalid = false; } } } } + } + + private char BaseElementToChar(BaseElement baseElement) + { + //TODO implement Overlayering + if (baseElement == null) + return ' '; + + switch (baseElement.ElementID) + { + case 1: return '│'; + case 2: return '║'; + case 3: return '─'; + case 4: return '═'; + case 5: return '┌'; + case 6: return '╔'; + case 7: return '└'; + case 8: return '╚'; + case 9: return '┐'; + case 10: return '╗'; + case 11: return '┘'; + case 12: return '╝'; + case 13: return '┬'; + case 14: return '╦'; + case 15: return '┴'; + case 16: return '╩'; + case 17: return '├'; + case 18: return '╠'; + case 19: return '┤'; + case 20: return '╣'; + case 21: return '┼'; + case 22: return '╬'; + default: + return (char)baseElement.ElementID; + } + } + } } diff --git a/TheRuleOfSilvester/GameMenu.cs b/TheRuleOfSilvester/GameMenu.cs new file mode 100644 index 0000000..0d34584 --- /dev/null +++ b/TheRuleOfSilvester/GameMenu.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TheRuleOfSilvester +{ + class GameMenu + { + public List MenueItems { get; private set; } + public bool IsRunning { get; private set; } + + public GameMenu(List items) + { + MenueItems = items; + } + + public MenuItem Run() + { + IsRunning = true; + + while (IsRunning) + { + DrawMenu(); + var menuItem = MenuAction(Console.ReadKey()); + if (menuItem != null) + return menuItem; + } + + return null; + } + + public void DrawMenu() + { + Console.Clear(); + + for (int i = 0; i < MenueItems.Count; i++) + { + if (MenueItems[i].Selected) + Console.WriteLine($">{i + 1}. {MenueItems[i].Title}"); + else + Console.WriteLine($"{i + 1}. {MenueItems[i].Title}"); + } + } + + private MenuItem MenuAction(ConsoleKeyInfo consoleKeyInfo) + { + int index; + MenuItem item; + + switch (consoleKeyInfo.Key) + { + case ConsoleKey.Tab: + case ConsoleKey.DownArrow: + index = MenueItems.FindIndex(i => i.Selected); + item = MenueItems[index]; + item.Selected = false; + item = MenueItems[(index + 1) % MenueItems.Count]; + item.Selected = true; + return null; + case ConsoleKey.Enter: + case ConsoleKey.Spacebar: + return MenueItems.First(m => m.Selected); + case ConsoleKey.UpArrow: + index = MenueItems.FindIndex(i => i.Selected); + item = MenueItems[index]; + item.Selected = false; + item = MenueItems[(index + MenueItems.Count - 1) % MenueItems.Count]; + item.Selected = true; + return null; + default: + return null; + } + + } + } +} diff --git a/TheRuleOfSilvester/InputComponent.cs b/TheRuleOfSilvester/InputComponent.cs new file mode 100644 index 0000000..33d0a4e --- /dev/null +++ b/TheRuleOfSilvester/InputComponent.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using TheRuleOfSilvester.Core; +using TheRuleOfSilvester.Core.Interfaces; + +namespace TheRuleOfSilvester +{ + public class InputComponent : IInputCompoment + { + public ConcurrentQueue InputActions { get; private set; } + + public bool Active { get; set; } + + private Thread inputThread; + private bool running; + + public InputComponent() + { + Active = true; + InputActions = new ConcurrentQueue(); + } + + internal void Start() + { + running = true; + inputThread = new Thread(InternalListen) + { + IsBackground = true, + Name = "Input Thread" + }; + + inputThread.Start(); + } + + internal void Stop() + { + running = false; + } + + private void InternalListen() + { + while (running) + { + var lastKey = Console.ReadKey(true).Key; + + if (!Active) + continue; + + switch (lastKey) + { + case ConsoleKey.UpArrow: + case ConsoleKey.W: + InputActions.Enqueue(new InputAction(InputActionType.Up)); + break; + case ConsoleKey.DownArrow: + case ConsoleKey.S: + InputActions.Enqueue(new InputAction(InputActionType.Down)); + break; + case ConsoleKey.LeftArrow: + case ConsoleKey.A: + InputActions.Enqueue(new InputAction(InputActionType.Left)); + break; + case ConsoleKey.RightArrow: + case ConsoleKey.D: + InputActions.Enqueue(new InputAction(InputActionType.Right)); + break; + case ConsoleKey.NumPad0: + case ConsoleKey.Q: + InputActions.Enqueue(new InputAction(InputActionType.StartAction)); + break; + case ConsoleKey.R: + InputActions.Enqueue(new InputAction(InputActionType.RoundButton)); + break; + case ConsoleKey.D1: + InputActions.Enqueue(new InputAction(InputActionType.RoundActionButton)); + break; + + case ConsoleKey.Escape: + break; + default: + break; + } + } + } + + } +} diff --git a/TheRuleOfSilvester/MenuItem.cs b/TheRuleOfSilvester/MenuItem.cs new file mode 100644 index 0000000..7677b2b --- /dev/null +++ b/TheRuleOfSilvester/MenuItem.cs @@ -0,0 +1,18 @@ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace TheRuleOfSilvester +{ + internal class MenuItem + { + public bool Selected { get; set; } + public string Title { get; set; } + public Action Action { get; set; } + + public MenuItem(string title) => Title = title; + public MenuItem(bool selected, string title) : this(title) => Selected = selected; + public MenuItem(bool selected, string title, Action action) : this(selected, title) => Action = action; + } +} diff --git a/TheRuleOfSilvester/MultiplayerComponent.cs b/TheRuleOfSilvester/MultiplayerComponent.cs new file mode 100644 index 0000000..cbfe4bd --- /dev/null +++ b/TheRuleOfSilvester/MultiplayerComponent.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using TheRuleOfSilvester.Core; +using TheRuleOfSilvester.Core.Interfaces; +using TheRuleOfSilvester.Network; + +namespace TheRuleOfSilvester +{ + public class MultiplayerComponent : IMultiplayerComponent + { + public Client Client { get; private set; } + public int Port { get; set; } + public string Host { get; set; } + public ServerStatus CurrentServerStatus { get; set; } + + public MultiplayerComponent() + => Client = new Client(); + + public void Connect() + => Client.Connect(Host, Port); + + public void Disconnect() + => Client.Disconnect(); + + public void Update(Game game) + { + //TODO: Implement waiting screen + CurrentServerStatus = GetServerStatus(); + + switch (CurrentServerStatus) + { + case ServerStatus.Started: + game.CurrentGameStatus = GameStatus.Running; + var players = GetPlayers(); + players.ForEach(x => x.Map = game.Map); + game.Map.Players.AddRange(players); + break; + default: + break; + } + + } + + public ServerStatus GetServerStatus() + => (ServerStatus)Send(CommandName.GetStatus)[0]; + + public Map GetMap() + => SerializeHelper.Deserialize(Send(CommandName.GetMap)); + + public Player ConnectPlayer(string playername) + => SerializeHelper.Deserialize(Send(CommandName.NewPlayer, Encoding.UTF8.GetBytes(playername))); + + public List GetPlayers() + => SerializeHelper.DeserializeToList(Send(CommandName.GetPlayers)).ToList(); + + public List GetWinners() + => SerializeHelper.DeserializeToList(Send(CommandName.GetWinners)).ToList(); + + public void UpdatePlayer(Player player) + => Send(CommandName.UpdatePlayer, SerializeHelper.Serialize(player)); + + public void TransmitActions(Stack actions, Player player) + => Send(CommandName.TransmitActions, + SerializeHelper.SerializeList(actions.ToList())); + + public void EndRound() + => Send(CommandName.EndRound); + + public bool GetUpdateSet(out ICollection updateSet) + { + var answer = Send(CommandName.Wait); + + updateSet = SerializeHelper.DeserializeToList(answer.Skip(sizeof(bool)).ToArray()); + return BitConverter.ToBoolean(answer, 0); + } + + private byte[] Send(CommandName command, params byte[] data) + => Client.Send(BitConverter.GetBytes((short)command).Concat(data).ToArray()); + } +} diff --git a/TheRuleOfSilvester/Program.cs b/TheRuleOfSilvester/Program.cs index 096ed59..aaf68c2 100644 --- a/TheRuleOfSilvester/Program.cs +++ b/TheRuleOfSilvester/Program.cs @@ -1,26 +1,135 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; using System.Text; using System.Threading; using TheRuleOfSilvester.Core; namespace TheRuleOfSilvester { - class Program + internal class Program { - static Game game; - static AutoResetEvent are; + private static Game game; + private static InputComponent inputComponent; + private static MultiplayerComponent multiplayerComponent; + private static GameMenu menu; + + public static bool Running { get; private set; } + + private static string playerName; + //┌┬┐└┴┘│├┼┤ - static void Main(string[] args) + private static void Main(string[] args) + { + do + { + Console.Clear(); + //are = new AutoResetEvent(false); + Console.OutputEncoding = Encoding.Unicode; + Console.CursorVisible = false; + inputComponent = new InputComponent(); + multiplayerComponent = new MultiplayerComponent(); + Running = true; + + menu = new GameMenu(new List() + { + new MenuItem(true, "New Game", SinglePlayer), + new MenuItem(false, "Multiplayer", MultiPlayer), + new MenuItem(false, "Exit", () => Running = false) + }); + + if (Running) + { + var menuItem = menu.Run(); + menuItem.Action(); + } + + } while (Running); + } + + private static void MultiPlayer() + { + Console.Clear(); + + do + { + Console.Write("Spielername: "); + + playerName = Console.ReadLine(); + Console.Clear(); + } + while (string.IsNullOrWhiteSpace(playerName)); + + Console.Write("IP Address of Server: "); + + bool GetAddress(string value, out IPAddress ipAddress) + { + //TODO: Port + if (value.ToLower() == "localhorst" || string.IsNullOrWhiteSpace(value) || value.ToLower() == "horst") + value = "localhost"; + + if (IPAddress.TryParse(value, out ipAddress)) + return true; + try + { + ipAddress = Dns.GetHostAddresses(value) + .FirstOrDefault(i => i.AddressFamily == AddressFamily.InterNetwork); + } + catch { } + + return ipAddress != null; + } + + IPAddress address; + while (!GetAddress(Console.ReadLine(), out address)) + { + Console.WriteLine("You've entered a wrong ip. Please try again! ☺"); + Console.Write("IP Address of Server: "); + } + Console.Clear(); + multiplayerComponent.Host = address.ToString(); + multiplayerComponent.Port = 4400; + + CreateGame(true); + } + + private static void SinglePlayer() + { + Console.Clear(); + CreateGame(false); + } + + private static void CreateGame(bool isMultiplayer) { - are = new AutoResetEvent(false); - Console.OutputEncoding = Encoding.Unicode; - Console.CursorVisible = false; using (game = new Game()) { game.DrawComponent = new DrawComponent(); - game.Run(60, 60); - are.WaitOne(); + game.InputCompoment = inputComponent; + game.MultiplayerComponent = multiplayerComponent; + game.Run(60, 60, isMultiplayer, playerName); + inputComponent.Start(); + + Console.CancelKeyPress += (s, e) => game.Stop(); + game.Wait(); + + Console.Clear(); + inputComponent.Stop(); + + if (game.Winners.Count > 0) + { + Console.WriteLine("The winners are: "); + Console.WriteLine(); + foreach (var winner in game.Winners) + Console.WriteLine(winner.Name); + } + + Console.WriteLine(); + Console.WriteLine("Please press any key"); + Console.ReadKey(); } + } } } diff --git a/TheRuleOfSilvester/TheRuleOfSilvester.csproj b/TheRuleOfSilvester/TheRuleOfSilvester.csproj index 6c29ef4..d836eec 100644 --- a/TheRuleOfSilvester/TheRuleOfSilvester.csproj +++ b/TheRuleOfSilvester/TheRuleOfSilvester.csproj @@ -3,10 +3,15 @@ Exe netcoreapp2.0 + 7.3 + 0.1.0.0 + 1.1.0.0 + 0.1.0 + diff --git a/TukTuk.md b/TukTuk.md index 52a86b1..81bce6c 100644 --- a/TukTuk.md +++ b/TukTuk.md @@ -1,6 +1,43 @@ +# ToDo +* ~~Player auf anfangsposition zurücksetzen~~ +* ~~Update aller Clients mit Server Action???~~ +* ~~UndoLastMovement(Game game = null): Reduce duplicated code~~ +* ~~Server sollte Player Inventar generieren~~ +* Items implementieren +* Timer implementieren + # Spiele ideen - (Text Adventure) - Console - Mit Karrmarr als Bösewicht er will Magier sein - Dungeon -- Karte manipulierbar \ No newline at end of file +- Karte manipulierbar + +# Symbole: +⌘⛥⛦⛧♰♱➳➴➵➶➷➸➹👁🗡⛀⛁⛂⛃🌀🔪 +⌖ ⯐ † +Krieger:♞ +Magier:⛤ +Dieb:✋ +Attentäter:➳ + +# Real Tuktuk +- + +https://annuel2.framapad.org/p/TheRuleOfSilvester + +picarto.tv/NekoChaan + +│ ║ +─ ═ + +┌ ╔ +└ ╚ +┐ ╗ +┘ ╝ +┬ ╦ +┴ ╩ +├ ╠ +┤ ╣ + +┼ ╬ \ No newline at end of file