Skip to content

Instantly share code, notes, and snippets.

@joncloud
Created December 8, 2019 22:57
Show Gist options
  • Save joncloud/fe7fbecf8441438d9318c08def14adcb to your computer and use it in GitHub Desktop.
Save joncloud/fe7fbecf8441438d9318c08def14adcb to your computer and use it in GitHub Desktop.
MonoGame Console
<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>
#----------------------------- 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
<?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>&#32;</Start>
<End>&#126;</End>
</CharacterRegion>
<CharacterRegion>
<Start>&#171;</Start>
<End>&#171;</End>
</CharacterRegion>
<CharacterRegion>
<Start>&#187;</Start>
<End>&#187;</End>
</CharacterRegion>
</CharacterRegions>
</Asset>
</XnaContent>
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