Created
December 8, 2019 22:57
-
-
Save joncloud/fe7fbecf8441438d9318c08def14adcb to your computer and use it in GitHub Desktop.
MonoGame Console
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Project Sdk="Microsoft.NET.Sdk"> | |
<PropertyGroup> | |
<OutputType>WinExe</OutputType> | |
<TargetFramework>netcoreapp3.1</TargetFramework> | |
<MonoGamePlatform>DesktopGL</MonoGamePlatform> | |
<LangVersion>latest</LangVersion> | |
</PropertyGroup> | |
<ItemGroup> | |
<PackageReference Include="MonoGame.Content.Builder" Version="3.7.0.9" /> | |
<PackageReference Include="MonoGame.Framework.DesktopGL.Core" Version="3.7.0.7" /> | |
<MonoGameContentReference Include="**\*.mgcb" /> | |
</ItemGroup> | |
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#----------------------------- Global Properties ----------------------------# | |
/outputDir:bin/$(Platform) | |
/intermediateDir:obj/$(Platform) | |
/platform:DesktopGL | |
/config: | |
/profile:Reach | |
/compress:False | |
#-------------------------------- References --------------------------------# | |
#---------------------------------- Content ---------------------------------# | |
#begin Fonts/Text.spritefont | |
/importer:FontDescriptionImporter | |
/processor:FontDescriptionProcessor | |
/processorParam:PremultiplyAlpha=True | |
/processorParam:TextureFormat=Compressed | |
/build:Fonts/Text.spritefont | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics"> | |
<Asset Type="Graphics:FontDescription"> | |
<FontName>Segoe UI</FontName> | |
<Size>16</Size> | |
<Spacing>0</Spacing> | |
<UseKerning>true</UseKerning> | |
<Style>Regular</Style> | |
<DefaultCharacter>*</DefaultCharacter> | |
<CharacterRegions> | |
<CharacterRegion> | |
<Start> </Start> | |
<End>~</End> | |
</CharacterRegion> | |
<CharacterRegion> | |
<Start>«</Start> | |
<End>«</End> | |
</CharacterRegion> | |
<CharacterRegion> | |
<Start>»</Start> | |
<End>»</End> | |
</CharacterRegion> | |
</CharacterRegions> | |
</Asset> | |
</XnaContent> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.Xna.Framework; | |
using Microsoft.Xna.Framework.Graphics; | |
using Microsoft.Xna.Framework.Input; | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
namespace ConsoleApp30 | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
using var game = new Game1(); | |
game.Run(); | |
} | |
} | |
public abstract class Cache<T> : IEnumerable<T> | |
{ | |
public abstract int Count { get; } | |
public abstract T Add(T item); | |
public static Cache<T> LRU(int capacity) => | |
new LRUCache(capacity); | |
public abstract IEnumerator<T> GetEnumerator(); | |
IEnumerator IEnumerable.GetEnumerator() => | |
GetEnumerator(); | |
class LRUCache : Cache<T> | |
{ | |
readonly Queue<T> queue; | |
readonly int capacity; | |
public override int Count => queue.Count; | |
public LRUCache(int capacity) | |
{ | |
this.queue = new Queue<T>(capacity); | |
this.capacity = capacity; | |
} | |
public override T Add(T item) | |
{ | |
queue.Enqueue(item); | |
if (queue.Count > capacity) | |
{ | |
return queue.Dequeue(); | |
} | |
return default; | |
} | |
public override IEnumerator<T> GetEnumerator() => | |
queue.GetEnumerator(); | |
} | |
class MRUCache : Cache<T> | |
{ | |
readonly Stack<T> stack; | |
readonly int capacity; | |
public override int Count => stack.Count; | |
public MRUCache(int capacity) | |
{ | |
this.stack = new Stack<T>(capacity); | |
this.capacity = capacity; | |
} | |
public override T Add(T item) | |
{ | |
if (stack.Count == capacity) | |
{ | |
var mru = stack.Pop(); | |
stack.Push(item); | |
return mru; | |
} | |
stack.Push(item); | |
return default; | |
} | |
public override IEnumerator<T> GetEnumerator() => | |
stack.GetEnumerator(); | |
} | |
} | |
enum MessageType | |
{ | |
Input, | |
Error, | |
Output | |
} | |
interface IConsoleService | |
{ | |
IEnumerable<string> Messages { get; } | |
event Action<MessageType, string> MessageAdded; | |
void Add(MessageType type, string text); | |
} | |
class ConsoleGameComponent : DrawableGameComponent, IConsoleService | |
{ | |
public IEnumerable<string> Messages => _messages.Select(tuple => tuple.Item2); | |
public event Action<MessageType, string> MessageAdded; | |
public void Add(MessageType type, string text) | |
{ | |
_messages.Add((type, text)); | |
MessageAdded?.Invoke(type, text); | |
} | |
public ConsoleGameComponent(Game game) | |
: base(game) | |
{ | |
} | |
public static ConsoleGameComponent AddTo(Game game) | |
{ | |
var component = new ConsoleGameComponent(game); | |
game.Services.AddService<IConsoleService>(component); | |
game.Components.Add(component); | |
return component; | |
} | |
SpriteBatch _spriteBatch; | |
SpriteFont _font; | |
protected override void LoadContent() | |
{ | |
_spriteBatch = new SpriteBatch(GraphicsDevice); | |
_font = Game.Content.Load<SpriteFont>("Fonts/Text"); | |
base.LoadContent(); | |
} | |
const int MAX_MESSAGES_COUNT = 10; | |
readonly Dictionary<MessageType, Color> _colors = | |
new Dictionary<MessageType, Color> | |
{ | |
[MessageType.Input] = Color.White, | |
[MessageType.Error] = Color.Red, | |
[MessageType.Output] = Color.Yellow | |
}; | |
readonly Cache<(MessageType, string)> _messages = Cache<(MessageType, string) >.LRU(MAX_MESSAGES_COUNT); | |
readonly StringBuilder _current = new StringBuilder(); | |
public override void Draw(GameTime gameTime) | |
{ | |
GraphicsDevice.Clear(Color.Black); | |
var tx = Matrix.Identity; // Matrix.CreateScale(4, 4, 1); | |
_spriteBatch.Begin(transformMatrix: tx); | |
var offset = new Vector2(8, 8); | |
const int CHAR_OFFSET = 20; | |
var i = 0; | |
foreach (var (type, text) in _messages) | |
{ | |
var color = _colors[type]; | |
_spriteBatch.DrawString( | |
_font, | |
text, | |
new Vector2(0, (i + MAX_MESSAGES_COUNT - _messages.Count) * CHAR_OFFSET) | |
+ offset, | |
color | |
); | |
i++; | |
} | |
i = MAX_MESSAGES_COUNT; | |
_spriteBatch.DrawString( | |
_font, | |
">", | |
new Vector2(0, i * CHAR_OFFSET) + offset, | |
_colors[MessageType.Input] | |
); | |
_spriteBatch.DrawString( | |
_font, | |
_current, | |
new Vector2(CHAR_OFFSET, i * CHAR_OFFSET) + offset, | |
_colors[MessageType.Input] | |
); | |
_spriteBatch.End(); | |
} | |
Keys[] _last = new Keys[0]; | |
readonly Dictionary<Keys, char> _lower = | |
new Dictionary<Keys, char> | |
{ | |
[Keys.A] = 'a', | |
[Keys.B] = 'b', | |
[Keys.C] = 'c', | |
[Keys.D] = 'd', | |
[Keys.E] = 'e', | |
[Keys.F] = 'f', | |
[Keys.G] = 'g', | |
[Keys.H] = 'h', | |
[Keys.I] = 'i', | |
[Keys.J] = 'j', | |
[Keys.K] = 'k', | |
[Keys.L] = 'l', | |
[Keys.M] = 'm', | |
[Keys.N] = 'n', | |
[Keys.O] = 'o', | |
[Keys.P] = 'p', | |
[Keys.Q] = 'q', | |
[Keys.R] = 'r', | |
[Keys.S] = 's', | |
[Keys.T] = 't', | |
[Keys.U] = 'u', | |
[Keys.V] = 'v', | |
[Keys.W] = 'w', | |
[Keys.X] = 'x', | |
[Keys.Y] = 'y', | |
[Keys.Z] = 'z', | |
[Keys.D0] = '0', | |
[Keys.D1] = '1', | |
[Keys.D2] = '2', | |
[Keys.D3] = '3', | |
[Keys.D4] = '4', | |
[Keys.D5] = '5', | |
[Keys.D6] = '6', | |
[Keys.D7] = '7', | |
[Keys.D8] = '8', | |
[Keys.D9] = '9', | |
[Keys.NumPad0] = '0', | |
[Keys.NumPad1] = '1', | |
[Keys.NumPad2] = '2', | |
[Keys.NumPad3] = '3', | |
[Keys.NumPad4] = '4', | |
[Keys.NumPad5] = '5', | |
[Keys.NumPad6] = '6', | |
[Keys.NumPad7] = '7', | |
[Keys.NumPad8] = '8', | |
[Keys.NumPad9] = '9', | |
[Keys.Space] = ' ', | |
[Keys.OemMinus] = '-' | |
}; | |
readonly Dictionary<Keys, char> _upper = | |
new Dictionary<Keys, char> | |
{ | |
[Keys.A] = 'A', | |
[Keys.B] = 'B', | |
[Keys.C] = 'C', | |
[Keys.D] = 'D', | |
[Keys.E] = 'E', | |
[Keys.F] = 'F', | |
[Keys.G] = 'G', | |
[Keys.H] = 'H', | |
[Keys.I] = 'I', | |
[Keys.J] = 'J', | |
[Keys.K] = 'K', | |
[Keys.L] = 'L', | |
[Keys.M] = 'M', | |
[Keys.N] = 'N', | |
[Keys.O] = 'O', | |
[Keys.P] = 'P', | |
[Keys.Q] = 'Q', | |
[Keys.R] = 'R', | |
[Keys.S] = 'S', | |
[Keys.T] = 'T', | |
[Keys.U] = 'U', | |
[Keys.V] = 'V', | |
[Keys.W] = 'W', | |
[Keys.X] = 'X', | |
[Keys.Y] = 'Y', | |
[Keys.Z] = 'Z', | |
[Keys.D0] = ')', | |
[Keys.D1] = '!', | |
[Keys.D2] = '@', | |
[Keys.D3] = '#', | |
[Keys.D4] = '$', | |
[Keys.D5] = '%', | |
[Keys.D6] = '^', | |
[Keys.D7] = '&', | |
[Keys.D8] = '*', | |
[Keys.D9] = '(', | |
[Keys.Space] = ' ' | |
}; | |
public override void Update(GameTime gameTime) | |
{ | |
var current = Keyboard.GetState().GetPressedKeys(); | |
var map = current.Contains(Keys.LeftShift) || | |
current.Contains(Keys.RightShift) | |
? _upper | |
: _lower; | |
foreach (var k in _last) | |
{ | |
if (current.Contains(k)) | |
{ | |
continue; | |
} | |
if (map.TryGetValue(k, out var c)) | |
{ | |
_current.Append(c); | |
} | |
} | |
if (_last.Contains(Keys.Back) && !current.Contains(Keys.Back) && _current.Length > 0) | |
{ | |
_current.Length--; | |
} | |
if (_last.Contains(Keys.Enter) && !current.Contains(Keys.Enter) && _current.Length > 0) | |
{ | |
Add(MessageType.Input, _current.ToString()); | |
_current.Clear(); | |
} | |
_last = current; | |
} | |
} | |
class Game1 : Game | |
{ | |
readonly GraphicsDeviceManager _manager; | |
public Game1() | |
{ | |
_manager = new GraphicsDeviceManager(this); | |
Content.RootDirectory = "Content"; | |
} | |
IConsoleService _console; | |
protected override void Initialize() | |
{ | |
var pp = GraphicsDevice.PresentationParameters; | |
pp.BackBufferWidth = 1280; | |
pp.BackBufferHeight = 800; | |
pp.IsFullScreen = false; | |
GraphicsDevice.Present(); | |
_console = ConsoleGameComponent.AddTo(this); | |
_console.MessageAdded += Console_MessageAdded; | |
base.Initialize(); | |
} | |
void Console_MessageAdded(MessageType type, string text) | |
{ | |
if (type != MessageType.Input) return; | |
if (text.StartsWith("echo") && text.Length > 5 && | |
text[4] == ' ') | |
{ | |
_console.Add(MessageType.Output, "-- " + text.Substring(5)); | |
return; | |
} | |
switch (text) | |
{ | |
case "exit": | |
case "quit": | |
Exit(); | |
break; | |
default: | |
_console.Add(MessageType.Error, "Invalid message"); | |
break; | |
} | |
} | |
protected override void Draw(GameTime gameTime) | |
{ | |
GraphicsDevice.Clear(Color.Black); | |
base.Draw(gameTime); | |
} | |
protected override void Update(GameTime gameTime) | |
{ | |
if (Keyboard.GetState().IsKeyDown(Keys.Escape)) { Exit(); } | |
base.Update(gameTime); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment