Sign in to follow this  

ASCII-graphic: using Console vs. API

This topic is 1188 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello!

 

I'm writing my game with C# using ASCII-graphic.

Firtsly, I used a Console with kernel32.dll fast buffer and graphic output.

Here is imported function:

 [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool WriteConsoleOutput(
          SafeFileHandle hConsoleOutput,
          CharInfo[] lpBuffer,
          Coord dwBufferSize,
          Coord dwBufferCoord,
          ref SmallRect lpWriteRegion);

But the color range for console is poor and I have endless problems with proper encoding settings.

 

Can you help me to find the best way of ASCII-graphic implementation?

Should I use a Console with proper settings (which I need to find) or using Graphic API as XNA, for example.

If anybody knows some libraries (with Console or XNA graphic) for ASCII-graphic, please, share with me!

 

Also, I'm waiting for wise advices.

 

Thanks in advance.

Edited by Barrett

Share this post


Link to post
Share on other sites

The Windows console has always had only a 16-colors palette, and true console games have always worked with that.

 

If you don't really want to write  a console game, but just a game that uses ASCII characters, then you can do this even in HTML and JavaScript, where you can specify any color for characters.

 

If you still want to do it in C# (though I don't see the point), you'll have to draw your own "console", which is basically just a grid of characters (a 2D C# array, where each array element holds a character and a color). As for drawing this grid onto the screen, IMHO XNA is overkill. You could just use the System.Drawing class. If you're looking for reasons to learn XNA, you should find better ideas.

 

And what problems are you having with encoding console characters, exactly?

Share this post


Link to post
Share on other sites

The Windows console has always had only a 16-colors palette, and true console games have always worked with that.

 

If you don't really want to write  a console game, but just a game that uses ASCII characters, then you can do this even in HTML and JavaScript, where you can specify any color for characters.

 

If you still want to do it in C# (though I don't see the point), you'll have to draw your own "console", which is basically just a grid of characters (a 2D C# array, where each array element holds a character and a color). As for drawing this grid onto the screen, IMHO XNA is overkill. You could just use the System.Drawing class. If you're looking for reasons to learn XNA, you should find better ideas.

 

And what problems are you having with encoding console characters, exactly?

 

I'm trying to print some special characters (triangles, arrows and etc.). And on different computers I get different results - characters are printed, another characters printed or symbols "?" printed. 

 

I guess it is problem with encoding and fonts.

I changed the font to Consolas, then to Lucida Console, but nothing changed (also, I changed Console.OutputEncoding to UTF8, then to unicode) and combine all of this settings.

 

The way using System.Drawning is usefull, but very simple for me - I want to use a Console....

The main problem is incorrect char printing.

Edited by Barrett

Share this post


Link to post
Share on other sites
I use the TextOut function to do this for ASCII rendering in my hexeditor.

Pros:
- You can print all characters, even ones unprintable in the Console.
- You can use any colors you want.
- Ridiculously fast.

Cons:
- Requires a lot of P/Invoke and therefore will likely only run on Windows.
 
public class ASCIIPalette
{
	uint[] fg; // foreground color table
	uint[] bg; // background color table
	byte[] xl; // character translation table
		
	public uint[] FG { get { return fg;} }
	public uint[] BG { get { return bg;} }
	public byte[] XL { get { return xl;} }
		
	public ASCIIPalette()
	{
		fg = new uint[256];
		bg = new uint[256];
		xl = new byte[256];
			
		uint black  = GDI.RGB(0,0,0);
		uint lgrey  = GDI.RGB(160,160,160);
		uint dgrey  = GDI.RGB(112,112,112);
		uint yellow = GDI.RGB(255,255,0);
			
		// Set default colors
		for (int i=0; i<256; ++i)
		{
			bg[i] = black;
				
			switch (i)
			{
				case 0:
					fg[i] = dgrey;
					xl[i] = 177;
					break;
					
				case 255:
					fg[i] = yellow;
					xl[i] = 177;
					break;
				
				default:
					fg[i] = lgrey;
					xl[i] = (byte)i;
					break;
			}
		}
	}
}
	
[SuppressUnmanagedCodeSecurity]
public class ASCIIUtil
{
	static byte[] buffer = new byte[256];
		
	public static unsafe void Paint(IntPtr hdc, ASCIIPalette palette, byte[] data, int x, int y, int count)
	{
		// 1 = some random value that shouldn't be used.
		uint fgOld=1, fgNew=1;
		uint bgOld=1, bgNew=1;
			
		GDI.TextMetric textmetric = new GDI.TextMetric();
			
		GDI.SelectObject(hdc, GDI.GetStockObject(GDI.StockObject.OemFixedFont));
		GDI.GetTextMetrics(hdc, ref textmetric);
		int fontwidth = textmetric.AveCharWidth;
			
		int i=0;  // buffer insertion index
		int bX=x; // batch X coordinate
			
		fixed (byte *fixedData = data)
		fixed (byte *fixedBuffer = buffer)
		fixed (uint *paletteFG = palette.FG)
		fixed (uint *paletteBG = palette.BG)
		fixed (byte *paletteXL = palette.XL)
		{
			for (int a=0; a<count; ++a)
			{
				bool flush = false;
				byte current = fixedData[a];
					
				fgNew = paletteFG[current];
				bgNew = paletteBG[current];
					
				// Flush conditions
                if (i > 255)
                {
                    GDI.SetTextColor(hdc, fgNew);
                    GDI.SetBkColor  (hdc, bgNew);
                    flush = true;
                }
                else if (fgNew != fgOld || bgNew != bgOld)
                {
                    GDI.SetTextColor(hdc, fgOld);
                    GDI.SetBkColor  (hdc, bgOld);

                    fgOld = fgNew;
                    bgOld = bgNew;

                    flush = true;
                }
					
				if (flush && i > 0)
				{
					GDI.TextOutA(hdc, bX, y, fixedBuffer, i);
					i=0;
					bX = x + a*fontwidth;
				}
					
				fixedBuffer[i++] = paletteXL[current];
			}
				
			// Write out the final batch
			GDI.SetTextColor(hdc, fgNew);
			GDI.SetBkColor  (hdc, bgNew);

			GDI.TextOutA(hdc, bX, y, fixedBuffer, i);
		}
	}
		
	public static int GetTextWidth(IntPtr hdc)
	{
		GDI.TextMetric tm = new GDI.TextMetric();
		GDI.SelectObject(hdc, GDI.GetStockObject(GDI.StockObject.OemFixedFont));
		GDI.GetTextMetrics(hdc, ref tm);
		return tm.AveCharWidth;
	}
	public static int GetTextHeight(IntPtr hdc)
	{
		GDI.TextMetric tm = new GDI.TextMetric();
		GDI.SelectObject(hdc, GDI.GetStockObject(GDI.StockObject.OemFixedFont));
		GDI.GetTextMetrics(hdc, ref tm);
		return tm.Height;
	}
}

public static class GDI
{
	public enum StockObject : int
	{
		OemFixedFont = 10
	}
	
	public enum BmpUsage : uint
	{
		RGB = 0,
		PAL = 1
	}
		
	public enum RasterOp : uint
	{
			// Binary raster ops
			Black          = 1,  //  0
			NotMergePen    = 2,  // DPon
			MaskNotPen     = 3,  // DPna
			NotCopyPen     = 4,  // PN
			MaskPenNot     = 5,  // PDna
			Not            = 6,  // Dn
			XorPen         = 7,  // DPx
			NotMaskPen     = 8,  // DPan
			MaskPen        = 9,  // DPa
			NotXorPen      = 10, // DPxn
			Nop            = 11, // D
			MergeNotPen    = 12, // DPno
			CopyPen        = 13, // P
			MergePenNot    = 14, // PDno
			MergePen       = 15, // DPo
			White          = 16, //  1
			Last           = 16,
			/// <summary>
			/// Dest = Source
			/// </summary>
			SrcCopy        = 0x00CC0020,
			/// <summary>
			/// Dest = Source | Dest
			/// </summary>
			SrcPaint       = 0x00EE0086,
			/// <summary>
			/// Dest = Source & Dest
			/// </summary>
			SrcAnd         = 0x008800C6,
			/// <summary>
			/// Dest = Source ^ Dest
			/// </summary>
			SrcInvert      = 0x00660046,
			/// <summary>
			/// Dest = Source & ~Dest
			/// </summary>
			SrcErase       = 0x00440328,
			/// <summary>
			/// Dest = ~Source
			/// </summary>
			NotSrcCopy     = 0x00330008,
			/// <summary>
			/// Dest = ~Source & ~Dest
			/// </summary>
			NotSrcErase    = 0x001100A6,
			/// <summary>
			/// Dest = Source & Pattern
			/// </summary>
			MergeCopy      = 0x00C000CA,
			/// <summary>
			/// Dest = ~Source | Dest
			/// </summary>
			MergePaint     = 0x00BB0226,
			/// <summary>
			/// Dest = Pattern
			/// </summary>
			PatCopy        = 0x00F00021,
			/// <summary>
			/// Dest = DPSnoo
			/// </summary>
			PatPaint       = 0x00FB0A09,
			/// <summary>
			/// Dest = Pattern ^ Dest
			/// </summary>
			PatInvert      = 0x005A0049,
			/// <summary>
			/// Dest = ~Dest
			/// </summary>
			DstInvert      = 0x00550009,
			/// <summary>
			/// Dest = Black
			/// </summary>
			Blackness      = 0x00000042,
			/// <summary>
			/// Dest = White
			/// </summary>
			Whiteness      = 0x00FF0062,
			/// <summary>
			/// Do not mirror the bitmap in this call
			/// </summary>
			NoMirrorBitmap = 0x80000000,
			/// <summary>
			/// Include layered windows
			/// </summary>
			CaptureBlt     = 0x40000000
	}
		
	/// <summary>
	/// Edge Types for DrawEdge.
	/// </summary>
	public enum Edge : int
	{
		RaisedOuter = 0x0001,
		SunkenOuter = 0x0002,
		RaisedInner = 0x0004,
		SunkenInner = 0x0008,
		Raised      = RaisedOuter | RaisedInner,
		Sunken      = SunkenOuter | SunkenInner,
		Etched      = SunkenOuter | RaisedInner,
		Bump        = RaisedOuter | SunkenInner,
	}
		
	/// <summary>
	/// Border Flags for DrawEdge.
	/// </summary>
	public enum BorderFlags : int
	{
		Left                   = 0x0001,
		Top                    = 0x0002,
		Right                  = 0x0004,
		Bottom                 = 0x0008,
		TopLeft                = (Top | Left),
		TopRight               = (Top | Right),
		BottomLeft             = (Bottom | Left),
		BottomRight            = (Bottom | Right),
		EntireRect             = (Top | Bottom | Left | Right),
		Diagonal               = 0x0010,
		DiagonalEndTopRight    = (Diagonal | Top | Right),
		DiagonalEndTopLeft     = (Diagonal | Top | Left),
		DiagonalEndBottomLeft  = (Diagonal | Bottom | Left),
		DiagonalEndBottomRight = (Diagonal | Bottom | Right),
		Middle                 = 0x0800,  // Fill in the middle
		Soft                   = 0x1000,  // For softer buttons
		Adjust                 = 0x2000,  // Calculate the space left over
		Flat                   = 0x4000,  // For flat rather than 3D borders
		Mono                   = 0x8000,  // For monochrome borders
	}

		
	[StructLayout(LayoutKind.Sequential)]
	public struct TextMetric
	{
		public int Height;
		public int Ascent;
		public int Descent;
		public int InternalLeading;
		public int ExternalLeading;
		public int AveCharWidth;
		public int MaxCharWidth;
		public int Weight;
		public int Overhang;
		public int DigitizedAspectX;
		public int DigitizedAspectY;
		public char FirstChar;
		public char LastChar;
		public char DefaultChar;
		public char BreakChar;
		public byte Italic;
		public byte Underlined;
		public byte StruckOut;
		public byte PitchAndFamily;
		public byte CharSet;
	}
		
		
	[StructLayout(LayoutKind.Sequential)]
	public struct Rect
	{
		public Rect(int left, int top, int width, int height)
		{
			this.left = left;
			this.top = top;
			this.right = left+width-1;
			this.bottom = top+height-1;
		}
			
		public int left;
		public int top;
		public int right;
		public int bottom;
	}
		
	public enum BitmapInfoCompression : uint
	{
		RGB       = 0,
		RLE8      = 1,
		RLE4      = 2,
		Bitfields = 3,
		JPEG      = 4,
		PNG       = 5
	}
		
	[StructLayout(LayoutKind.Sequential)]
	public struct BitmapInfo
	{
		public BitmapInfo(int width, int height)
		{
			Size=40;
			Width=width;
			Height=height;
			Planes=1;
			BitCount=32;
			Compression=0;
			SizeImage=0;
			XPelsPerMeter=0;
			YPelsPerMeter=0;
			ClrUsed=0;
			ClrImportant=0;
		}
		public uint Size;
		/// <summary>
		/// Width in pixels.
		/// </summary>
		public int Width;
		/// <summary>
		/// Height in pixels.
		/// </summary>
		public int Height;
		/// <summary>
		/// Planes.  Must be set to one.
		/// </summary>
		public ushort Planes;
		/// <summary>
		/// Bits per pixel.
		/// </summary>
		public ushort BitCount;
		/// <summary>
		/// Compression type.
		/// </summary>
		public BitmapInfoCompression Compression;
		/// <summary>
		/// Size, in bytes of the image.  If Compression is set to BitmapInfoCompression.RGB, use 0.
		/// </summary>
		public uint SizeImage;
		/// <summary>
		/// Horizontal pixels-per-meter
		/// </summary>
		public int XPelsPerMeter;
		/// <summary>
		/// Vertical pixels-per-meter
		/// </summary>
		public int YPelsPerMeter;
		/// <summary>
		/// Amount of colors used in the color table (use zero).
		/// </summary>
		public uint ClrUsed;
		/// <summary>
		/// Amount of colors requires (use zero).
		/// </summary>
		public uint ClrImportant;
	}
		
	
	/// <summary>
	/// Select a GDI Object into the specified DC
	/// </summary>
	/// <param name="hdc">Handle to the DC to select into</param>
	/// <param name="obj">Handle to the object to select</param>
	/// <returns>Handle to the old selected object</returns>
	[SuppressUnmanagedCodeSecurity]
	[DllImport("gdi32.dll")]
	public static extern IntPtr SelectObject(IntPtr hdc, IntPtr obj);
		
	/// <summary>
	/// Gets an object handle for a stock (system-provided) object.
	/// </summary>
	/// <param name="stockobject">The stock object identifier</param>
	/// <returns>Handle to the specified object</returns>
	[SuppressUnmanagedCodeSecurity]
	[DllImport("gdi32.dll")]
	public static extern IntPtr GetStockObject(StockObject stockobject);
		
	/// <summary>
	/// Get the text metrics for the currently selected font object on the specified DC
	/// </summary>
	/// <param name="hdc">Handle to the DC to use</param>
	/// <param name="tm">The TextMetric struct to fill with info</param>
	/// <returns>True if the function succeeds, False if it fails</returns>
	[SuppressUnmanagedCodeSecurity]
	[DllImport("gdi32.dll")]
	public static extern bool GetTextMetrics(IntPtr hdc, ref TextMetric tm);
		
	/// <summary>
	/// Writes text to the specified DC using the pre-selected font
	/// </summary>
	/// <param name="hdc">Handle to the DC to write on</param>
	/// <param name="X">Initial X position</param>
	/// <param name="Y">Initial Y Position</param>
	/// <param name="chars">A byte ASCII string pointer</param>
	/// <param name="length">The number of ASCII chars to write</param>
	/// <returns>True on success, False otherwise</returns>
	[SuppressUnmanagedCodeSecurity]
	[DllImport("gdi32.dll")]
	public static unsafe extern bool TextOutA(IntPtr hdc, int X, int Y, byte *chars, int length);
		
	/// <summary>
	/// Sets the text foreground color for the specified DC
	/// </summary>
	/// <param name="hdc">Handle to the DC to use</param>
	/// <param name="color">The new color to use</param>
	/// <returns>The previous selected foreground color</returns>
	[SuppressUnmanagedCodeSecurity]
	[DllImport("gdi32.dll")]
	public static extern uint SetTextColor(IntPtr hdc, uint color);
		
	/// <summary>
	/// Sets the text background color for the specified DC
	/// </summary>
	/// <param name="hdc">Handle to the DC to use</param>
	/// <param name="color">The new color to use</param>
	/// <returns>The previous selected background color</returns>
	[SuppressUnmanagedCodeSecurity]
	[DllImport("gdi32.dll")]
	public static extern uint SetBkColor(IntPtr hdc, uint color);
		
	/// <summary>
	/// Gets the system dialog base units.
	/// </summary>
	/// <returns>Loword: Horizontal, Hiword: Vertical</returns>
	[SuppressUnmanagedCodeSecurity]
	[DllImport("user32.dll")]
	public static extern uint GetDialogBaseUnits();
		
	/// <summary>
	/// Converts dialog units to screen units.
	/// </summary>
	/// <param name="hwnd">Handle to a dialog box (other windows are invalid).</param>
	/// <param name="rect">The Rect to convert.</param>
	/// <returns>True: Success, False: Failure</returns>
	[SuppressUnmanagedCodeSecurity]
	[DllImport("user32.dll")]
	public static extern bool MapDialogRect(IntPtr hwnd, ref Rect rect);
		
	/// <summary>
	/// Creates a DC that is compatible with an existing DC.
	/// </summary>
	/// <param name="hdc">The existing DC to use.</param>
	/// <returns>The compatible DC.</returns>
	[SuppressUnmanagedCodeSecurity]
	[DllImport("gdi32.dll")]
	public static extern IntPtr CreateCompatibleDC(IntPtr hdc);

	#if UNSAFE
	// TODO: finalize DIB Section help
	/// <summary>
	/// Creates a DeviceIndependateBitmap Section.
	/// </summary>
	/// <param name="hdc">DC to use.</param>
	/// <param name="bitmapinfo">Information used to create the DIB.</param>
	/// <param name="usage">Specifies whether to use RGB or palette info.</param>
	/// <param name="data">Receives a pointer to the actual bitmap data.</param>
	/// <param name="mappingsection">Handle to a mapped file to use to create the DIB (or null).</param>
	/// <param name="offset">Offset in the mapped file to start (ignored if mappingsection is null).</param>
	/// <returns>The DIB Section.</returns>
	[SuppressUnmanagedCodeSecurity]
	[DllImport("gdi32.dll")]
	public static unsafe extern IntPtr CreateDIBSection(IntPtr hdc, BitmapInfo bitmapinfo, BmpUsage usage, ref byte* data, IntPtr mappingsection, uint offset);
	#endif
		
	/// <summary>
	/// Copies bitmap data from one DC to another.
	/// </summary>
	/// <param name="hdcdest">Destination DC.</param>
	/// <param name="xdest">X coordinate of the upper left corner of the destination.</param>
	/// <param name="ydest">Y coordinate of the upper left corner of the destination.</param>
	/// <param name="width">Width of data to copy.</param>
	/// <param name="height">Height of data to copy.</param>
	/// <param name="hdcsrc">Source DC.</param>
	/// <param name="xsrc">X coordinate of the upper left corner of the source.</param>
	/// <param name="ysrc">Y coordinate of the upper left cornet of the source.</param>
	/// <param name="ROP">Raster operation used when writing to the destination.</param>
	/// <returns>True: Success, False: Failure</returns>
	[SuppressUnmanagedCodeSecurity]
	[DllImport("gdi32.dll")]
	public static extern bool BitBlt(IntPtr hdcdest, int xdest, int ydest, int width, int height, IntPtr hdcsrc, int xsrc, int ysrc, RasterOp ROP);
		
	/// <summary>
	/// Gets the DC for a window.
	/// </summary>
	/// <param name="hwnd">Window handle.</param>
	/// <returns>The DC.</returns>
	[SuppressUnmanagedCodeSecurity]
	[DllImport("user32.dll")]
	public static extern IntPtr GetDC(IntPtr hwnd);
		
	/// <summary>
	/// Releases a DC previously retrieved from GetDC.
	/// </summary>
	/// <param name="hwnd">The Window that owns the DC.</param>
	/// <param name="hdc">The DC to release.</param>
	/// <returns>True: Success, False: Failure</returns>
	[SuppressUnmanagedCodeSecurity]
	[DllImport("user32.dll")]
	public static extern bool ReleaseDC(IntPtr hwnd, IntPtr hdc);
		
	/// <summary>
	/// Deletes a DC.
	/// </summary>
	/// <param name="hdc">The DC to delete.</param>
	/// <returns>True: Success, False: Failure</returns>
	[SuppressUnmanagedCodeSecurity]
	[DllImport("gdi32.dll")]
	public static extern bool DeleteDC(IntPtr hdc);
		
	/// <summary>
	/// Deletes a GDI Object.
	/// </summary>
	/// <param name="hobj">The Object to delete.</param>
	/// <returns>True: Success, False: Failure</returns>
	[SuppressUnmanagedCodeSecurity]
	[DllImport("gdi32.dll")]
	public static extern bool DeleteObject(IntPtr hobj);
		
	/// <summary>
	/// Draws one or more edges of a rectangle.
	/// </summary>
	/// <param name="hdc">The DC to draw to.</param>
	/// <param name="qrc">Rectangle defining the box to draw.</param>
	/// <param name="edge">Type of edge.</param>
	/// <param name="grfFlags">Type of border.</param>
	/// <returns>True: Success, False: Failure</returns>
	[SuppressUnmanagedCodeSecurity]
	[DllImport("user32.dll")]
	public static extern bool DrawEdge(IntPtr hdc, ref Rect qrc, Edge edge, BorderFlags grfFlags);
		
	/// <summary>
	/// Create a uint (COLORREF compatible) from red, green, blue components
	/// </summary>
	/// <param name="r">Red amount (0-255)</param>
	/// <param name="g">Green amount (0-255)</param>
	/// <param name="b">Blue amount (0-255)</param>
	/// <returns>The packed color value</returns>
	public static uint RGB(int r, int g, int b)
	{
		return (uint)(r + (g<<8) + (b<<16));
	}
}
IntPtr hdc comes from this in a WinForms-style OnPaint method:
 
protected override void OnPaint(PaintEventArgs e)
{
    IntPtr hdc = e.Graphics.GetHdc();

    // Calls to ASCIIUtil.Paint here
}
In my ASCIIPalette above, characters 0 and 0xFF are altered slightly to make them render differently from 0x20 (the space character). Normally characters 0, 0x20 and 0xFF all render as an empty glyph (in the OEM font anyway), and in my hexeditor it's important to be able to visually differentiate all bytes.

Also keep in mind that my code is not thread-safe due to the use of that static buffer. Edited by Nypyren

Share this post


Link to post
Share on other sites

This topic is 1188 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this