diff --git a/Connector/Connector.v11.suo b/Connector/Connector.v11.suo new file mode 100644 index 0000000..c344e47 Binary files /dev/null and b/Connector/Connector.v11.suo differ diff --git a/Connector/Connector/Program.cs b/Connector/Connector/Program.cs new file mode 100644 index 0000000..b89be48 --- /dev/null +++ b/Connector/Connector/Program.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Connector +{ + class Program + { + static void Main(string[] args) + { + + } + } +} diff --git a/DefaultGui/DefaultGui/Backend.cs b/DefaultGui/DefaultGui/Backend.cs new file mode 100644 index 0000000..7a78acb --- /dev/null +++ b/DefaultGui/DefaultGui/Backend.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DefaultGui +{ + /// + /// All classes in the test-directory are just classes to illustrate the concept of the frontend and its interfaces. + /// They are undocumented and not very well written and are using dummy-data with no connection to the actual server. + /// On purpose. Because you should come up with your own implementation. :) + /// + public class Backend : IBackend + { + public void sendCommand(string command) + { + Console.WriteLine("received command " + command); + } + + public void sendChat(string message) + { + Console.WriteLine("received chatmessage " + message); + } + + public List getDragons() { + List dragons = new List(); + dragons.Add(new Entity(0,1)); + return dragons; + + } + public List getPlayers() { + List players = new List(); + players.Add(new Entity(1, 1)); + return players; + } + + public ITile[][] getMap() + { + int size = 10; + // init + ITile[][] map = new ITile[size][]; + for (int i = 0; i < size; i++) + { + map[i] = new ITile[size]; + } + Random r = new Random(); + for (int x = 0; x < size; x++) + { + for (int y = 0; y < size; y++) + { + List attr = new List(); + switch (r.Next(0, 5)) + { + case 0: + attr.Add(MapCellAttribute.WATER); + break; + case 1: + attr.Add(MapCellAttribute.HUNTABLE); + attr.Add(MapCellAttribute.FOREST); + break; + case 2: + attr.Add(MapCellAttribute.FOREST); + break; + case 3: + attr.Add(MapCellAttribute.UNWALKABLE); + break; + case 4: + break; + + } + map[x][y] = new MapCell(x, y, attr); + } + } + return map; + } + + } +} diff --git a/DefaultGui/DefaultGui/DefaultGui.Designer.cs b/DefaultGui/DefaultGui/DefaultGui.Designer.cs new file mode 100644 index 0000000..9ff6316 --- /dev/null +++ b/DefaultGui/DefaultGui/DefaultGui.Designer.cs @@ -0,0 +1,39 @@ +namespace DefaultGui +{ + partial class DefaultGui + { + /// + /// Erforderliche Designervariable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Verwendete Ressourcen bereinigen. + /// + /// True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Vom Windows Form-Designer generierter Code + + /// + /// Erforderliche Methode für die Designerunterstützung. + /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Text = "Form1"; + } + + #endregion + } +} + diff --git a/DefaultGui/DefaultGui/DefaultGui.cs b/DefaultGui/DefaultGui/DefaultGui.cs new file mode 100644 index 0000000..e920350 --- /dev/null +++ b/DefaultGui/DefaultGui/DefaultGui.cs @@ -0,0 +1,278 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Runtime.InteropServices; +using System.Diagnostics; + +namespace DefaultGui +{ + /// + /// This is a very, very basic GUI. It communicates with any IBackend, takes the map and existing entities and renders them in a generic fashion. + /// The space will be devided evenly among the ITiles of a map. That means: bigger maps will result in very tiny cells on the GUI. + /// No scrolling or any fancy mechanisms. + /// Entities will also be displayed in a generic way: players are rendered in yellow, dragons are red. No IDs, names or whatsoever. + /// It also offers basic functionality to communicate with the backend to input commands via the chat. So you can do some testing by manually entering commands like "/get:map". + /// The GUI also consists of a chat-window, featuring incoming chat-messages and a input-box, to send messages to the IBackend (which have to be handled there!). + /// Pressing ENTER sends the text in the input-box and also switches between chat-mode and walk-mode. In walk-mode, pressing WASD causes the frontend to send move-commands to the IBackend. + /// As you can see, this is just rudimentary functionality and rendering and you might wish (and are invited) to extend the class, modify the existing sourcecode or even write your very own implementation to have a more appealing GUI that fits your needs. + /// + /// To get this to work, you will have to let your backend implemented the provided IBackend-interface (and of course the other provided interfaces where needed). Then + /// + public partial class DefaultGui : Form + { + private IBackend backend; + public DefaultGui(IBackend backend) + : base() + { + if (backend == null) + { + throw new ArgumentNullException("invalid value for 'backend': null"); + } + this.backend = backend; + InitializeComponent(); + // we usually don't have a console in a form-project. This enables us to see debug-output anyway + AllocConsole(); + this.board.Paint += board_PaintMap; + this.board.Paint += board_PaintEntities; + this.chatInput.KeyPress += chat_KeyPress; + this.board.KeyPress += board_KeyPress; + } + + /// + /// Handles keypresses the chatInput-field receives. + /// Having ENTER pressed, causes the field to be emptied and have the trimmed text in the field sent to the backend. + /// If the input starts with "/", it will be handled as command (like ask:mv:up and such). + /// All other messages will be treated as chat-messages and have to be wrapped by the backend as such. + /// + /// + /// + private void chat_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e) + { + if (e.KeyChar == (char)Keys.Enter) + { + string input = this.chatInput.Text.Trim(); + this.chatInput.Text = ""; + // ignore empty input + if (input != "") + { + if (input.StartsWith("/")) + { + input = input.Substring(1, input.Length - 1); + this.backend.sendCommand(input); + } + else + { + this.backend.sendChat(input); + } + } + this.board.Focus(); + } + + } + + /// + /// Handles keypresses when the board is focused. This is whenever the chatinput is NOT focused. + /// Will handle WASD as triggers for the movement. + /// A: left + /// W: up + /// S: down + /// D: right + /// Pressing ENTER causes the chatinput to be focused again. + /// + /// + /// + private void board_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e) + { + // fall-through-cases for capital letters + switch (e.KeyChar) + { + case (char)Keys.Enter: + this.chatInput.Focus(); + break; + case 'a': + case 'A': + this.backend.sendCommand("ask:mv:lft"); + break; + case 'd': + case 'D': + this.backend.sendCommand("ask:mv:rgt"); + break; + case 'w': + case 'W': + this.backend.sendCommand("ask:mv:up"); + break; + case 's': + case 'S': + this.backend.sendCommand("ask:mv:dwn"); + break; + } + } + + /// + /// Handler to paint the tiles in the map + /// + /// + /// + private void board_PaintMap(object sender, System.Windows.Forms.PaintEventArgs e) + { + Size tileSize = this.getTileSize(); + ITile[][] cells = this.backend.getMap(); + // validity checked beforehand in getTileSize + Debug.Assert(cells != null); + BufferedGraphics buffer = BufferedGraphicsManager.Current.Allocate(this.board.CreateGraphics(), this.board.DisplayRectangle); + Graphics g = this.CreateGraphics(); + for (int x = 0; x < cells.Length; x++) + { + for (int y = 0; y < cells[x].Length; y++) + { + this.drawMapTile(buffer.Graphics, cells[x][y], x * tileSize.Width, y * tileSize.Height, tileSize.Width, tileSize.Height); + } + } + buffer.Render(); + } + + /// + /// Handler to paint all entities Dragons first, then the Players + /// + /// + /// + private void board_PaintEntities(object sender, System.Windows.Forms.PaintEventArgs e) + { + List dragons = this.backend.getDragons(); + foreach (IPositionable dragon in dragons) + { + this.drawDragon(e.Graphics, dragon); + } + List players = this.backend.getPlayers(); + foreach (IPositionable player in players) + { + this.drawPlayer(e.Graphics, player); + } + } + + /// + /// Draws a tile of the map on a graphics object. + /// By default, it will draw a rectangle (colour will be dependent of the attributes of the tile). + /// WATER: blue + /// FOREST: dark green + /// HUNTABLE: light green + /// UNWALKABLE: grey + /// else: peach + /// It will have a black 1px border. + /// + /// the graphics-object to draw on + /// the tile-object that implements ITile + /// absolute pixel x-position of the upper left corner of the tile, relative to the upper left corner of the graphics-object + /// absolute pixel y-position of the upper left corner of the tile, relative to the upper left corner of the graphics-object + /// width of the tile in pixels + /// height of the tile in pixels + protected void drawMapTile(Graphics g, ITile tile, int absX, int absY, int width, int height) + { + Color colour = Color.BurlyWood; + if (tile.isForest()) + { + if (tile.isHuntable()) + { + colour = Color.YellowGreen; + } + else + { + colour = Color.Green; + } + } + else if (tile.isWater()) + { + colour = Color.Blue; + } + else if (!tile.isWalkable()) + { + colour = Color.DimGray; + } + g.FillRectangle(new SolidBrush(colour), absX, absY, width, height); + g.DrawRectangle(new Pen(new SolidBrush(Color.Black)), new Rectangle(absX, absY, width, height)); + } + + /// + /// Draws a player on the graphics. + /// By default, players will be represented by a centered dark-yellow rectangle that takes up half of the cells size. + /// + /// the graphics-object to draw on + /// the player to draw + protected void drawPlayer(Graphics g, IPositionable player) + { + Size tileSize = this.getTileSize(); + g.FillRectangle(new SolidBrush(Color.DarkGoldenrod), + player.getXPosition() * tileSize.Width + tileSize.Width / 2 - tileSize.Width / 4, + player.getYPosition() * tileSize.Height + tileSize.Height / 2 - tileSize.Height / 4, + tileSize.Width / 2, + tileSize.Height / 2); + } + + /// + /// Draws a dragon on the graphics. + /// By default, dragons will be represented by a centered red rectangle that takes up half of the cells size. + /// + /// the graphics-object to draw on + /// the dragon to draw + protected void drawDragon(Graphics g, IPositionable dragon) + { + Size tileSize = this.getTileSize(); + g.FillRectangle(new SolidBrush(Color.DarkRed), + dragon.getXPosition() * tileSize.Width + tileSize.Width / 2 - tileSize.Width / 4, + dragon.getYPosition() * tileSize.Height + tileSize.Height / 2 - tileSize.Height / 4, + tileSize.Width / 2, + tileSize.Height / 2); + } + + /// + /// Devides the space of the board-panel equally among the tiles of the map. + /// For intance, if the board is 100px wide and the map consists of 5 cells in y-direction + /// each tile will have a width of 100px/5 = 20px. Same for the height. + /// + /// the size of one tile to fit the whole map on the board + protected Size getTileSize() + { + IPositionable[][] cells = this.backend.getMap(); + if (cells == null) + { + throw new ArgumentNullException("backend returned null as map"); + } + if (cells.Length == 0) + { + throw new IndexOutOfRangeException("outer dimension of the retrieved map has length 0"); + } + if (cells[0].Length == 0) + { + throw new IndexOutOfRangeException("inner dimension of the retrieved map has length 0"); + } + int cellWidth = this.board.Size.Width / cells.Length; + int cellHeight = this.board.Size.Height / cells[0].Length; + return new Size(cellWidth, cellHeight); + } + + /// + /// Appends a chat-message to the chat-window as a new line. Can be called from the backend or other participants to display incoming chat-messages. + /// Messages will always be displays in the fasion of: + /// sender: message + /// + /// the source of the message + /// the message itself + public void appendChatMessage(string sender, string message) + { + this.chatWindow.AppendText(sender + ": " + message + "\r\n"); + } + + + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool AllocConsole(); + + } +} diff --git a/DefaultGui/DefaultGui/Entity.cs b/DefaultGui/DefaultGui/Entity.cs new file mode 100644 index 0000000..20fe464 --- /dev/null +++ b/DefaultGui/DefaultGui/Entity.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DefaultGui +{ + public class Entity : IPositionable + { + int x, y; + public Entity(int x, int y) + { + this.x = x; + this.y = y; + } + + public int getXPosition() + { + return this.x; + } + + public int getYPosition() + { + return this.y; + } + } +} \ No newline at end of file diff --git a/DefaultGui/DefaultGui/IBackend.cs b/DefaultGui/DefaultGui/IBackend.cs new file mode 100644 index 0000000..22b61ee --- /dev/null +++ b/DefaultGui/DefaultGui/IBackend.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DefaultGui +{ + /// + /// Interface that should be implemented by the class that represents your backend. + /// + public interface IBackend + { + /// + /// Method to get an arbitrary collection of dragons + /// + /// a list of dragons that are currently on the map + List getDragons(); + /// + /// Method to get an arbitrary collection of players + /// + /// list of players that are currently on the map + List getPlayers(); + /// + /// Method to get a 2d-grid-representation of the map. The map doesn't actually has to be a 2d-array, but you should + /// somehow be able to convert it into one. + /// + /// a 2d-array of ITiles, representing the map + ITile[][] getMap(); + /// + /// Sends a command to the server (such as ask:mv:dwn) + /// + /// the command to send + void sendCommand(string command); + /// + /// Sends a chatmessage to broadcast. This is basically any text, wrapped in the chat-command. + /// So this method will ultimately call sendCommand() after forming a chat-command from the text. + /// + /// the text to send as chatmessage + void sendChat(string message); + } +} diff --git a/DefaultGui/DefaultGui/IPositionable.cs b/DefaultGui/DefaultGui/IPositionable.cs new file mode 100644 index 0000000..e670239 --- /dev/null +++ b/DefaultGui/DefaultGui/IPositionable.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DefaultGui +{ + /// + /// Interface your positionable objects should implement. Such as the dragons and players. + /// This enables the frontend to determine the current position of said objects to render them at the correct space. + /// + public interface IPositionable + { + /// + /// Getter for the x-position + /// + /// the x-position + int getXPosition(); + /// + /// Getter for the y-position + /// + /// the y-position + int getYPosition(); + } +} diff --git a/DefaultGui/DefaultGui/ITile.cs b/DefaultGui/DefaultGui/ITile.cs new file mode 100644 index 0000000..1670d06 --- /dev/null +++ b/DefaultGui/DefaultGui/ITile.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DefaultGui +{ + /// + /// Interface that should be implemented by your class that represents one field in the grid. + /// It has to provide methods to check for its properties, which are independed from the way you actually store those properties. + /// + public interface ITile : IPositionable + { + bool isWalkable(); + bool isForest(); + bool isHuntable(); + bool isWater(); + } +} \ No newline at end of file diff --git a/DefaultGui/DefaultGui/MapCell.cs b/DefaultGui/DefaultGui/MapCell.cs new file mode 100644 index 0000000..8562b7a --- /dev/null +++ b/DefaultGui/DefaultGui/MapCell.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DefaultGui +{ + public class MapCell : ITile + { + private int x, y; + private List attributes; + public MapCell(int x, int y, List attributes) + { + this.x = x; + this.y = y; + this.attributes = new List(); + this.attributes.AddRange(attributes); + } + + public int getXPosition() + { + return this.x; + } + + public int getYPosition() + { + return this.y; + } + + public bool isWalkable() + { + return !this.attributes.Contains(MapCellAttribute.UNWALKABLE); + } + + public bool isForest() + { + return this.attributes.Contains(MapCellAttribute.FOREST); + } + + public bool isHuntable() + { + return this.attributes.Contains(MapCellAttribute.HUNTABLE); + } + + public bool isWater() + { + return this.attributes.Contains(MapCellAttribute.WATER); + } + } + + public enum MapCellAttribute { UNWALKABLE, WATER, FOREST, HUNTABLE } +} \ No newline at end of file diff --git a/DefaultGui/DefaultGui/Program.cs b/DefaultGui/DefaultGui/Program.cs new file mode 100644 index 0000000..86d1c0a --- /dev/null +++ b/DefaultGui/DefaultGui/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace DefaultGui +{ + static class Program + { + /// + /// Der Haupteinstiegspunkt für die Anwendung. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new DefaultGui(new Backend())); + } + } +}