class Console als Basis Klasse?



  • Hallo,
    Ich schreib das diesmal nicht in das WinApi-Forum, weil es hier jetzt um eine abgeleitete Klasse geht. Ich habe meine statische Klasse Console aufgelöst und versucht zu einer Basis Klasse zu machen. Die abgeleitete Klasse wäre dann die Anwendung, die mit der Basis Klasse realisiert werden kann.

    Um zu zeigen, das es nicht nur ein Hirngespinst ist, habe ich mein MandelbrotDings dafür umgeschrieben. Die Klasse Mandelbrotselbst wurde dafür angenehm entschlackt.

    Es kommt jetzt viel Code, alleine Consolesind immer noch 600 Zeilen. Wer darauf keine Lust hat, kein Ding 😉
    Dazu kommt jetzt noch die abgeleitete Klasse.

    Oh ja, Fragen. Geht das so in Ordnung?

    Die (umfangreiche, vieles ist schon bekannt) Basis Klasse:

    #pragma once
    
    /* please use Multi-Byte Character Set in Compiler! */
    
    #define NOMINMAX
    #include <windows.h>
    #include <iostream>
    #include <string>
    #include <vector>
    #include <exception>
    #include <thread>
    #include <chrono>
    
    // some enums
    namespace console
    {
    	enum glyph
    	{
    		SPACE = 32,
    		LIGHT = 176,
    		MED = 177,
    		DARK = 178,
    		SOLID = 219,
    	};
    
    	enum color
    	{
    		BLACK = 0,
    		DGREY = 8,
    		GREY = 7,
    		WHITE = 15,
    
    		YELLOW = 14,
    		DYELLOW = 6,
    		RED = 12,
    		DRED = 4,
    		MAGENTA = 13,
    		DMAGENTA = 5,
    		DBLUE = 1,
    		BLUE = 9,
    		DCYAN = 3,
    		CYAN = 11,
    		GREEN = 10,
    		DGREEN = 2
    	};
    
    	enum background_mode
    	{
    		NONE = 0,
    		BGROUND = 1,
    		FGROUND = 2,
    		F_BGROUND = 3,
    		F_FGROUND = 4
    	};
    }
    
    // simple own CHAR_INFO
    class Dot
    {
    public:
    	TCHAR glyph = ' ';
    	int fColor = 0;
    	int bColor = 0;
    
    	Dot() = default;
    	Dot(const TCHAR ch, const int fgColor, const int bgColor = console::BLACK) :
    		glyph(ch), fColor(fgColor), bColor(bgColor) {}
    	Dot(const int c, const int fgColor, const int bgColor = console::BLACK) :
    		glyph(TCHAR(c)), fColor(fgColor), bColor(bgColor) {}
    
    	static Dot Default() { return Dot(console::SPACE, console::GREY, console::BLACK); }
    
    	CHAR_INFO toCHAR_INFO() const
    	{
    		CHAR_INFO ch;
    		ch.Char.UnicodeChar = static_cast<WCHAR>(glyph);
    		ch.Attributes = static_cast<WORD>(fColor + 16 * bColor);
    		return ch;
    	}
    
    	void print() const
    	{
    		std::cout << '\n' << glyph << " " << int(glyph) << " " << fColor << " " << bColor;
    	}
    };
    
    // some helpers for pausing console
    namespace
    {
    	void ready()
    	{
    		std::cerr << "\nready_\n";
    		std::cin.sync();
    		std::cin.get();
    	}
    
    	void _key()
    	{
    		std::cin.sync();
    		std::cin.get();
    	}
    
    	void sleep(const int milliSeconds)
    	{
    		std::this_thread::sleep_for(std::chrono::milliseconds(milliSeconds));
    	}
    }
    
    // class Console
    
    class Console
    {
    	typedef std::basic_string<TCHAR> tstring;
    
    	struct Info
    	{
    		struct Size
    		{
    			SHORT width = 0;
    			SHORT height = 0;
    		} size;
    
    		Dot windowDot;
    		SMALL_RECT windowRect = {};
    		tstring windowTitle;
    
    		CONSOLE_CURSOR_INFO cci;
    		CONSOLE_FONT_INFOEX cfi;
    		CONSOLE_SCREEN_BUFFER_INFO csbi;
    	};
    
    	struct KeyState
    	{
    		bool pressed = false;
    		bool released = false;
    		bool held = false;
    	};
    
    	struct ErrorState
    	{
    		bool state = false;
    		std::string type;
    	};
    
    	Info current, orig;
    	HANDLE outHandle;
    
    	std::vector<Dot> screenBuffer;
    	std::size_t keyBufferSize;
    	std::vector<KeyState> keys;
    	std::vector<SHORT> newState, oldState;
    
    	ErrorState err;
    	bool consoleCreated = false;
    	bool mainThreadRunning = false;
    	int frames = 0;
    
    	void mainThread()
    	{
    		// create user resources as part of this thread
    		if (!onUserCreate())
    			mainThreadRunning = false;
    
    		auto timePoint1 = std::chrono::system_clock::now();
    		auto timePoint2 = std::chrono::system_clock::now();
    
    		// run as fast as possible
    		while (mainThreadRunning)
    		{
    			// handle timing
    			timePoint2 = std::chrono::system_clock::now();
    			std::chrono::duration<float> elapsedTime_chrono = timePoint2 - timePoint1;
    			timePoint1 = timePoint2;
    			float elapsedTime = elapsedTime_chrono.count();
    
    			// handle keyboard input
    			handleKeyboardInput();
    
    			// handle frame update
    			if (!onUserUpdate(elapsedTime))
    				mainThreadRunning = false;
    
    			// update title & present screen buffer
    			frames = toInt(1.f / elapsedTime);
    			updateTitle();
    			writeBuffer();
    		}
    		restoreOriginal();
    	}
    
    protected:
    	std::string appName;
    
    public:
    	Console()
    	{
    		outHandle = GetStdHandle(STD_OUTPUT_HANDLE);
    		if (outHandle == INVALID_HANDLE_VALUE) throw std::exception();
    		if (!getCCI(orig.cci)) throw std::exception();
    		if (!getCFI(orig.cfi)) throw std::exception();
    		if (!getCSBI(orig.csbi)) throw std::exception();
    
    		orig.size = { orig.csbi.srWindow.Right + 1, orig.csbi.srWindow.Bottom + 1 };
    		orig.windowRect = orig.csbi.srWindow;
    		orig.windowDot = Dot::Default();
    		current = orig;
    
    		keyBufferSize = 256;
    		keys.resize(keyBufferSize);
    		newState.resize(keyBufferSize);
    		oldState.resize(keyBufferSize);
    		appName = "Console";
    	}
    
    	virtual ~Console() = default;
    
    	bool createConsole(const SHORT width, const SHORT height, SHORT fontWidth, SHORT fontHeight)
    	{
    		if (resizeConsole(width, height, fontWidth, fontHeight))
    		{
    			current.size = { width, height };
    			screenBuffer.resize(width * height, current.windowDot);
    			consoleCreated = true;
    			hideCursor();
    			return true;
    		}
    		else
    		{
    			restoreOriginal();
    			std::cerr << "console create error: " << err.type;
    			return false;
    		}
    	}
    
    	void start()
    	{
    		if (!consoleCreated) return;
    
    		// start the thread
    		mainThreadRunning = true;
    		// Some miracle occurs here
    		std::thread thread = std::thread(&Console::mainThread, this);
    		// wait for thread to be exited
    		thread.join();
    	}
    
    	int screenWidth() const { return static_cast<int>(current.size.width); }
    	int screenHeight() const { return static_cast<int>(current.size.height); }
    	int fontWidth() const { return static_cast<int>(current.cfi.dwFontSize.X); }
    	int fontHeight() const { return static_cast<int>(current.cfi.dwFontSize.Y); }
    	ErrorState error() const { return err; }
    
    protected:
    	Dot windowDot() const { return current.windowDot; }
    	KeyState getKey(const int keyCode) const { return keys[static_cast<std::size_t>(keyCode)]; }
    	// user MUST OVERRIDE THESE!!
    	virtual bool onUserCreate() = 0;
    	virtual bool onUserUpdate(float elapsedTime) = 0;
    	// can be override
    	virtual void plot(const int x, const int y, const Dot& ch, const int backgroundMode = console::NONE)
    	{
    		if (x >= 0 && x < current.size.width && y >= 0 && y < current.size.height)
    		{
    			const std::size_t idx = toLinearIndex(x, y);
    			Dot buf = screenBuffer[idx];
    			screenBuffer[idx] = combine(ch, buf, backgroundMode);
    		}
    	}
    
    	Dot getDot(const int x, const int y) const
    	{
    		Dot dot;
    		if (x >= 0 && x < current.size.width && y >= 0 && y < current.size.height)
    			dot = screenBuffer[toLinearIndex(x, y)];
    
    		return dot;
    	}
    
    	void printString(
    		int x,
    		const int y,
    		const std::string& text,
    		const int fColor = console::GREY,
    		const int bColor = console::BLACK,
    		const int backgroundMode = console::BGROUND)
    	{
    		for (const auto& ch : text)
    			plot(x++, y, Dot(ch, fColor, bColor), backgroundMode);
    	}
    
    	void fillBuffer()
    	{
    		std::fill(screenBuffer.begin(), screenBuffer.end(), current.windowDot);
    	}
    
    	void fill(const Dot& ch)
    	{
    		current.windowDot = ch;
    		fillBuffer();
    	}
    
    	void fill(const int fColor, const int bColor)
    	{
    		current.windowDot.fColor = fColor;
    		current.windowDot.bColor = bColor;
    		fillBuffer();
    	}
    
    	void clear()
    	{
    		fillBuffer();
    		writeBuffer();
    		setCursorPos(0, 0);
    	}
    
    	bool setCursorPos(const int x, const int y)
    	{
    		if (!SetConsoleCursorPosition(outHandle, { static_cast<SHORT>(x), static_cast<SHORT>(y) }))
    			return Error("SetConsoleCursorPosition");
    		else
    			return true;
    	}
    
    	bool hideCursor()
    	{
    		current.cci.bVisible = 0;
    		if (!SetConsoleCursorInfo(outHandle, &current.cci))
    			return Error("SetConsoleCursorInfo");
    		else
    			return true;
    	}
    
    	bool showCursor()
    	{
    		current.cci.bVisible = 1;
    		if (!SetConsoleCursorInfo(outHandle, &current.cci))
    			return Error("SetConsoleCursorInfo");
    		else
    			return true;
    	}
    
    	bool setTitle(const std::string& text)
    	{
    		current.windowTitle += toTstring(text);
    		if (!SetConsoleTitle(current.windowTitle.c_str()))
    			return Error("SetConsoleTitle");
    		else
    			return true;
    	}
    
    	bool updateTitle(const std::string& text = {})
    	{
    		const std::string str = text + " :: FPS: " + std::to_string(frames);
    		const tstring title = current.windowTitle + toTstring(str);
    		if (!SetConsoleTitle(title.c_str()))
    			return Error("SetConsoleTitle");
    		else
    			return true;
    	}
    
    	bool resizeConsole(const SHORT width, const SHORT height, SHORT fontWidth, SHORT fontHeight)
    	{
    		SMALL_RECT rect = { 0, 0, 1, 1 };
    		if (!SetConsoleWindowInfo(outHandle, TRUE, &rect)) throw std::exception();
    
    		// set the size of the screenbuffer
    		if (!SetConsoleScreenBufferSize(outHandle, { width, height }))
    			return Error("SetConsoleScreenBufferSize");
    
    		// assign screenbuffer to the console
    		if (!SetConsoleActiveScreenBuffer(outHandle))
    			return Error("SetConsoleActiveScreenBuffer");
    
    		// set the fontsize now that the screenbuffer has been assigned to the console
    		if (!fontWidth || !fontHeight)
    		{
    			fontWidth = orig.cfi.dwFontSize.X;
    			fontHeight = orig.cfi.dwFontSize.Y;
    		}
    		current.cfi.cbSize = sizeof(current.cfi);
    		current.cfi.nFont = 0;
    		current.cfi.dwFontSize.X = fontWidth;
    		current.cfi.dwFontSize.Y = fontHeight;
    		current.cfi.FontFamily = FF_DONTCARE;
    		current.cfi.FontWeight = FW_NORMAL;
    		wcscpy_s(current.cfi.FaceName, L"Consolas");
    		if (!SetCurrentConsoleFontEx(outHandle, FALSE, &current.cfi))
    			return Error("SetCurrentConsoleFontEx");
    
    		// set screenbuffer info and check the maximum allowed window size
    		if (!GetConsoleScreenBufferInfo(outHandle, &current.csbi))
    			return Error("GetConsoleScreenBufferSize");
    
    		if (width > current.csbi.dwMaximumWindowSize.X)
    			return Error("screen width / font width too big");
    		if (height > current.csbi.dwMaximumWindowSize.Y)
    			return Error("screen height / font height too big");
    
    		// set physical console window size
    		current.windowRect = { 0, 0, width - 1, height - 1 };
    		if (!SetConsoleWindowInfo(outHandle, TRUE, &current.windowRect))
    			return Error("SetConsoleWindowInfo");
    
    		// set console title
    		current.windowTitle = toTstring(
    			appName
    			+ " "
    			+ std::to_string(width)
    			+ "x"
    			+ std::to_string(height)
    			+ " "
    			+ std::to_string(fontWidth)
    			+ "x"
    			+ std::to_string(fontHeight)
    		    + " ");
    		if (!SetConsoleTitle(current.windowTitle.c_str()))
    			return Error("SetConsoleTitle");
    
    		return true;
    	}
    
    	void restoreOriginal()
    	{
    		SMALL_RECT rect = { 0, 0, 1, 1 };
    		if (!SetConsoleWindowInfo(outHandle, TRUE, &rect)) throw std::exception();
    		if (!SetConsoleScreenBufferSize(outHandle, { orig.size.width, orig.size.height })) throw std::exception();
    		if (!SetConsoleActiveScreenBuffer(outHandle)) throw std::exception();
    		if (!SetCurrentConsoleFontEx(outHandle, FALSE, &orig.cfi)) throw std::exception();
    		if (!SetConsoleWindowInfo(outHandle, TRUE, &orig.windowRect)) throw std::exception();
    		if (!SetConsoleTitle(orig.windowTitle.c_str())) throw std::exception();
    		if (!SetConsoleCursorInfo(outHandle, &orig.cci)) throw std::exception();
    		if (!SetConsoleCursorPosition(outHandle, { 0, 0 })) throw std::exception();
    
    		screenBuffer.resize(orig.size.width * orig.size.height, orig.windowDot);
    		current = orig;
    		appName.clear();
    		clear();
    	}
    
    private:
    	std::vector<CHAR_INFO> convertToCHAR_INFO() const
    	{
    		std::vector<CHAR_INFO> vec(std::size(screenBuffer));
    		std::size_t n = 0;
    		for (const auto& ch : screenBuffer)
    			vec[n++] = ch.toCHAR_INFO();
    
    		return vec;
    	}
    
    	bool writeBuffer()
    	{
    		std::vector<CHAR_INFO> outbuffer = convertToCHAR_INFO();
    		CHAR_INFO* output = outbuffer.data();
    		COORD coord = { current.size.width, current.size.height };
    		if (!WriteConsoleOutput(outHandle, output, coord, { 0, 0 }, &current.windowRect))
    			return Error("WriteConsoleOutput");
    		else
    			return true;
    	}
    
    	// must call before getting keys
    	void handleKeyboardInput()
    	{
    		for (std::size_t i = 0; i < keyBufferSize; ++i)
    		{
    			newState[i] = GetKeyState(static_cast<int>(i));
    			keys[i].pressed = false;
    			keys[i].released = false;
    
    			if (newState[i] != oldState[i])
    			{
    				if (newState[i] & 0x8000)
    				{
    					keys[i].pressed = !keys[i].held;
    					keys[i].held = true;
    				}
    				else
    				{
    					keys[i].released = true;
    					keys[i].held = false;
    				}
    			}
    			oldState[i] = newState[i];
    		}
    	}
    
    	std::size_t toLinearIndex(const int x, const int y) const 
    	{ 
    		return static_cast<std::size_t>(current.size.width * y + x); 
    	}
    
    	Dot combine(const Dot& lhs, const Dot& rhs, const int backgroundMode) const
    	{
    		Dot ch = lhs;
    		switch (backgroundMode)
    		{
    		case console::NONE:
    			break;
    		case console::BGROUND:
    			ch.bColor = rhs.bColor;
    			break;
    		case console::FGROUND:
    			ch.bColor = rhs.fColor;
    			break;
    		case console::F_BGROUND:
    			ch.fColor = rhs.bColor;
    			break;
    		case console::F_FGROUND:
    			ch.fColor = rhs.fColor;
    			break;
    		default:
    			break;
    		}
    		return ch;
    	}
    
    	bool Error(const std::string& errType)
    	{
    		err = { true, errType };
    		return false;
    	}
    
    	bool getCSBI(CONSOLE_SCREEN_BUFFER_INFO& csbi)
    	{
    		if (!GetConsoleScreenBufferInfo(outHandle, &csbi))
    			return Error("GetConsoleScreenBufferInfo");
    		else
    			return true;
    	}
    
    	bool getCFI(CONSOLE_FONT_INFOEX& cfi)
    	{
    		cfi.cbSize = sizeof(cfi);
    		if (!GetCurrentConsoleFontEx(outHandle, FALSE, &cfi))
    			return Error("GetCurrentConsoleFontEx");
    		else
    			return true;
    	}
    
    	bool getCCI(CONSOLE_CURSOR_INFO& cci)
    	{
    		if (!GetConsoleCursorInfo(outHandle, &cci))
    			return Error("GetConsoleCursorInfo");
    		else
    			return true;
    	}
    
    	tstring toTstring(const std::string& str) const
    	{
    		tstring tstr;
    		for (const auto ch : str)
    			tstr.push_back(static_cast<TCHAR>(ch));
    
    		return tstr;
    	}
    
    // drawing routines
    protected:
    	const float PI = std::acos(-1.f);
    	template <typename T> int toInt(const T v) const { return static_cast<int>(std::round(v)); }
    
    	struct Point
    	{
    		float x = 0;
    		float y = 0;
    	};
    
    	void plotLine(
    		int x0, int y0,
    		const int x, const int y,
    		const Dot& ch,
    		const int backgroundMode = console::NONE)
    	{
    		const int dx = std::abs(x - x0);
    		const int dy = -std::abs(y - y0);
    		const int sx = x0 < x ? 1 : -1;
    		const int sy = y0 < y ? 1 : -1;
    		int err = dx + dy; // error value e_xy 
    
    		for (;;)
    		{
    			plot(x0, y0, ch, backgroundMode);
    			if (x0 == x && y0 == y) break;
    
    			const int err2 = 2 * err;
    			if (err2 > dy) // e_xy + e_x > 0 
    			{
    				err += dy;
    				x0 += sx;
    			}
    			if (err2 < dx) // e_xy + e_y < 0 
    			{
    				err += dx;
    				y0 += sy;
    			}
    		}
    	}
    
    	void plotWireFrameModel(
    		const std::vector<Point>& modelCoordinates,
    		const float x, const float y,
    		const float angle, const float scale,
    		const Dot& ch)
    	{
    		// create translated model vector of coordinate pairs
    		const std::size_t vertices = std::size(modelCoordinates);
    		std::vector<Point> transformedCoordinates(vertices);
    
    		// angle
    		for (std::size_t i = 0; i < vertices; i++)
    		{
    			transformedCoordinates[i].x = modelCoordinates[i].x * std::cos(angle) - modelCoordinates[i].y * std::sin(angle);
    			transformedCoordinates[i].y = modelCoordinates[i].x * std::sin(angle) + modelCoordinates[i].y * std::cos(angle);
    		}
    
    		// scale
    		for (std::size_t i = 0; i < vertices; i++)
    		{
    			transformedCoordinates[i].x = transformedCoordinates[i].x * scale;
    			transformedCoordinates[i].y = transformedCoordinates[i].y * scale;
    		}
    
    		// translate
    		for (std::size_t i = 0; i < vertices; i++)
    		{
    			transformedCoordinates[i].x = transformedCoordinates[i].x + x;
    			transformedCoordinates[i].y = transformedCoordinates[i].y + y;
    		}
    
    		// draw closed polygon
    		for (std::size_t i = 0; i < vertices + 1; i++)
    		{
    			std::size_t j = (i + 1);
    			plotLine(
    				toInt<float>(transformedCoordinates[i % vertices].x), toInt<float>(transformedCoordinates[i % vertices].y),
    				toInt<float>(transformedCoordinates[j % vertices].x), toInt<float>(transformedCoordinates[j % vertices].y), ch);
    		}
    	}
    };
    

    Das Grundgerüst der abgeleiteten Klasse:

    #pragma once
    #include "Console.h"
    
    class Example : public Console
    {
    
    public:
    	Example()
    	{
    
    	}
    
    private:
    	bool onUserCreate() override
    	{
    		return true;
    	}
    
    	bool onUserUpdate(float elapsedTime) override
    	{
    		return true;
    	}
    };
    

    wie sie gestarted werden:

    #include "Example.h"
    
    int main()
    {
    	int width = 160;
    	int height = 100;
    	int pixelSize = 8;
    
    	Example demo;
    	if (demo.createConsole(width, height, pixelSize, pixelSize))
    		demo.start();
    }
    

    Und jetzt die Anwendung, wie das MandelbrotDings damit ausschaut:
    Mandelbrot selbst:

    #pragma once
    #include <vector>
    
    namespace mandelbrot
    {
    	struct Parameter
    	{
    		struct Size
    		{
    			int width = 0;
    			int height = 0;
    		} size;
    
    		struct Area
    		{
    			double x0 = 0;
    			double x = 0;
    			double y0 = 0;
    			double y = 0;
    		} area;
    
    		std::size_t maxIterations = 0;
    		std::size_t iterations = 0;
    	};
    }
    
    class Mandelbrot
    {
    	struct SizeFactor
    	{
    		double x = 0;
    		double y = 0;
    	};
    
    	struct Color
    	{
    		int x = 0;
    		int y = 0;
    		std::size_t index = 0;
    	};
    
    	mandelbrot::Parameter param;
    	SizeFactor sFactor;
    	std::vector<Color> mandelbrotSet;
    public:
    	Mandelbrot() {}
    
    	int width() const { return param.size.width; }
    	int height() const { return param.size.height; }
    	SizeFactor sizeFactor() const { return sFactor; }
    	std::size_t iterations() const { return param.iterations; }
    	std::size_t maxIterations() const { return param.maxIterations; }
    	std::vector<Color> mandelbrot() const { return mandelbrotSet; }
    	mandelbrot::Parameter parameter() const { return param; }
    
    	void setParameter(const mandelbrot::Parameter& pm)
    	{
    		setSize(pm);
    		setArea(pm);
    		param.maxIterations = pm.maxIterations;
    		setIterations(pm);
    	}
    
    	void setSize(const mandelbrot::Parameter& pm)
    	{
    		param.size.width = pm.size.width;
    		param.size.height = pm.size.height;
    		mandelbrotSet.resize(param.size.width * param.size.height);
    	}
    
    	void setArea(const mandelbrot::Parameter& pm)
    	{
    		param.area = pm.area;
    		sFactor.x = (param.area.x - param.area.x0) / (param.size.width - 1.);
    		sFactor.y = (param.area.y - param.area.y0) / (param.size.height - 1.);
    	}
    
    	void setIterations(const mandelbrot::Parameter& pm)
    	{
    		if (pm.iterations > param.maxIterations)
    			param.iterations = param.maxIterations;
    		else
    			param.iterations = pm.iterations;
    	}
    
    	void calc()
    	{
    		std::size_t n = 0;
    		for (int y = 0; y < param.size.height; y++)
    		{
    			for (int x = 0; x < param.size.width; x++)
    			{
    				const double real = param.area.x0 + x * sFactor.x; // current real value
    				const double imag = param.area.y0 + y * sFactor.y; // current imaginary value
    
    				mandelbrotSet[n++] = { x, y, getIndex(real, imag, param.iterations) };
    			}
    		}
    	}
    
    private:
    	std::size_t getIndex(const double real, const double imag, const std::size_t N) const
    	{
    		double zReal = real; // real value
    		double zImag = imag; // imaginary value
    
    		for (std::size_t n = 0; n < N; ++n)
    		{
    			const double r2 = zReal * zReal;
    			const double i2 = zImag * zImag;
    			const double v = r2 + i2;
    
    			if (v > 4.) return n;
    
    			zImag = 2. * zReal * zImag + imag;
    			zReal = r2 - i2 + real;
    		}
    		return N;
    	}
    };
    

    Die abgeleitete Klasse MandelbrotExplorer:

    #pragma once
    #include "Console.h"
    #include "Mandelbrot.h"
    
    using namespace console;
    
    namespace graphic
    {
    	class Cursor
    	{
    		struct Size
    		{
    			int width = 0;
    			int height = 0;
    			struct Center
    			{
    				int x = 0;
    				int y = 0;
    			} center;
    		};
    
    		struct Position
    		{
    			int x = 0;
    			int y = 0;
    			double fx = 0;
    			double fy = 0;
    		};
    
    	public:
    		Size size;
    		Position pos;
    		Dot dot;
    		std::vector<bool> image;
    
    		Cursor() :
    			dot{ console::SOLID, console::GREY },
    			size{ 7, 7, { 3, 3 } },
    			image{
    			0, 0, 0, 1, 0, 0, 0,
    			0, 0, 0, 1, 0, 0, 0,
    			0, 0, 0, 0, 0, 0, 0,
    			1, 1, 0, 0, 0, 1, 1,
    			0, 0, 0, 0, 0, 0, 0,
    			0, 0, 0, 1, 0, 0, 0,
    			0, 0, 0, 1, 0, 0, 0 } {}
    	};
    }
    
    class MandelbrotExplorer : public Console
    {
    	Mandelbrot mandelbrot;
    	mandelbrot::Parameter param;
    	std::vector<Dot> colors;
    
    	graphic::Cursor cursor;
    	bool singleKeyStep = false;
    
    public:
    	MandelbrotExplorer()
    	{
    		appName = "mandelbrot";
    	}
    
    private:
    	bool onUserCreate() override
    	{ 
    		// mandelbrot parameter
    		param.size.width = screenWidth();
    		param.size.height = screenHeight();
    		param.area = { -2., 1., -1., 1. };
    		param.maxIterations = 300;
    		param.iterations = 100;
    		mandelbrot.setParameter(param);
    		mandelbrot.calc();
    
    		// mandelbrot colors
    		std::vector<int> consoleColors = { BLUE, DBLUE, MAGENTA, DMAGENTA, DRED,  RED, DYELLOW, CYAN, DCYAN, BLUE, DBLUE, DGREY };
    		colors = convertToColors(consoleColors);
    		colors = fitColors(colors, mandelbrot.maxIterations());
    
    		// cursor position
    		setCursorToZero();
    
    		return true;
    	}
    
    	bool onUserUpdate(float elapsedTime) override
    	{
    
    		if (getKey(VK_ESCAPE).pressed)
    			return false;
    
    		if (getKey(VK_BACK).pressed)
    			resetParameter();
    
    		if (getKey(VK_SHIFT).pressed)
    		{
    			if (singleKeyStep)
    				singleKeyStep = false;
    			else
    				singleKeyStep = true;
    		}
    
    		if (key(VK_RIGHT, singleKeyStep))
    			cursorRight();
    
    		if (key(VK_LEFT, singleKeyStep))
    			cursorLeft();
    
    		if (key(VK_UP, singleKeyStep))
    			cursorUp();
    
    		if (key(VK_DOWN, singleKeyStep))
    			cursorDown();
    
    		if (key(VK_ADD, singleKeyStep))
    			addIterations();
    
    		if (key(VK_SUBTRACT, singleKeyStep))
    			subIterations();
    
    		if (key('A', singleKeyStep))
    			zoomIn();
    
    		if (key('Y', singleKeyStep))
    			zoomOut();
    
    		mandelbrot.calc();
    		plotMandelbrotSet();
    		plotCursor();
    		//updateWindowTitle();
    
    		return true;
    	}
    
    	bool key(const int keyCode, const bool singleStep)
    	{
    		bool get = false;
    		if (singleStep)
    			get = getKey(keyCode).pressed;
    		else
    			get = getKey(keyCode).held;
    
    		return get;
    	}
    
    	void resetParameter()
    	{
    		param.area = { -2., 1., -1., 1. };
    		param.iterations = 100;
    		mandelbrot.setParameter(param);
    		setCursorToZero();
    	}
    
    	void plotMandelbrotSet() 
    	{
    		for (const auto& dot : mandelbrot.mandelbrot())
    		{
    			Dot color;
    			if (dot.index == mandelbrot.iterations())
    				color = windowDot();
    			else
    				color = colors[dot.index];
    
    			plot(dot.x, dot.y, color);
    		}
    	}
    
    	void plotCursor()
    	{
    		std::size_t n = 0;
    		for (int i = 0; i < cursor.size.height; ++i)
    			for (int j = 0; j < cursor.size.width; ++j)
    			{
    				const int x = cursor.pos.x + j - cursor.size.center.x;
    				const int y = cursor.pos.y + i - cursor.size.center.y;
    				if (cursor.image[n++])
    					plot(x, y, cursor.dot);
    			}
    	}
    
    	void updateWindowTitle()
    	{
    		std::string x0 = " AREA: xMin: " + std::to_string(param.area.x0);
    		std::string x = "   xMax: " + std::to_string(param.area.x);
    		std::string y0 = "   yMin: " + std::to_string(param.area.y0);
    		std::string y = "   yMax: " + std::to_string(param.area.y);
    		std::string iter = "     ITERATIONS: " + std::to_string(param.iterations);
    		std::string cx = "     CURSOR: x: " + std::to_string(cursor.pos.fx);
    		std::string cy = "   y: " + std::to_string(cursor.pos.fy);
    
    		updateTitle(x0 + x + y0 + y + iter + cx + cy);
    	}
    
    	void moveCursor(const int x, const int y)
    	{
    		cursor.pos.x += x;
    		cursor.pos.y += y;
    		cursor.pos.fx = cursor.pos.x * mandelbrot.sizeFactor().x + mandelbrot.parameter().area.x0;
    		cursor.pos.fy = cursor.pos.y * mandelbrot.sizeFactor().y + mandelbrot.parameter().area.y0;
    	}
    
    	void setCursorToZero()
    	{
    		cursor.pos.x = 0;
    		cursor.pos.y = 0;
    		double fx0 = 1. / mandelbrot.sizeFactor().x * 2.;
    		double fy0 = 1. / mandelbrot.sizeFactor().y;
    		moveCursor(toInt<double>(std::round(fx0)), toInt<double>(std::round(fy0)));
    	}
    
    	void cursorLeft()
    	{
    		moveCursor(-1, 0);
    		if (cursor.pos.x < 0)
    		{
    			param.area = {
    				param.area.x0 - mandelbrot.sizeFactor().x,
    				param.area.x  - mandelbrot.sizeFactor().x,
    				param.area.y0,
    				param.area.y };
    
    			mandelbrot.setArea(param);
    			moveCursor(1, 0);
    		}
    	}
    
    	void cursorRight()
    	{
    		moveCursor(1, 0);
    		if (cursor.pos.x >= mandelbrot.width())
    		{
    			param.area = {
    				param.area.x0 + mandelbrot.sizeFactor().x,
    				param.area.x  + mandelbrot.sizeFactor().x,
    				param.area.y0,
    				param.area.y };
    
    			mandelbrot.setArea(param);
    			moveCursor(-1, 0);
    		}
    	}
    
    	void cursorUp()
    	{
    		moveCursor(0, -1);
    		if (cursor.pos.y < 0)
    		{
    			param.area = {
    				param.area.x0,
    				param.area.x,
    				param.area.y0 - mandelbrot.sizeFactor().y,
    				param.area.y  - mandelbrot.sizeFactor().y };
    
    			mandelbrot.setArea(param);
    			moveCursor(0, 1);
    		}
    	}
    
    	void cursorDown()
    	{
    		moveCursor(0, 1);
    		if (cursor.pos.y >= mandelbrot.height())
    		{
    			param.area = {
    				param.area.x0,
    				param.area.x,
    				param.area.y0 + mandelbrot.sizeFactor().y,
    				param.area.y  + mandelbrot.sizeFactor().y };
    
    			mandelbrot.setArea(param);
    			moveCursor(0, -1);
    		}
    	}
    
    	void addIterations()
    	{
    		param.iterations++;
    		if (param.iterations > mandelbrot.parameter().maxIterations)
    			param.iterations = 0;
    
    		mandelbrot.setIterations(param);
    	}
    
    	void subIterations()
    	{
    		param.iterations--;
    
    		if (param.iterations > mandelbrot.parameter().maxIterations)
    			param.iterations = mandelbrot.parameter().maxIterations;
    
    		mandelbrot.setIterations(param);
    	}
    
    	void zoomIn()
    	{
    		double dx0 = (param.area.x0 - cursor.pos.fx) / 100.;
    		double dx = (param.area.x - cursor.pos.fx) / 100.;
    		double dy0 = (param.area.y0 - cursor.pos.fy) / 100.;
    		double dy = (param.area.y - cursor.pos.fy) / 100.;
    	
    		param.area.x0 -= dx0;
    		param.area.x -= dx;
    		param.area.y0 -= dy0;
    		param.area.y -= dy;
    		mandelbrot.setArea(param);
    	}
    
    	void zoomOut()
    	{
    		double dx0 = (param.area.x0 - cursor.pos.fx) / 100.;
    		double dx = (param.area.x - cursor.pos.fx) / 100.;
    		double dy0 = (param.area.y0 - cursor.pos.fy) / 100.;
    		double dy = (param.area.y - cursor.pos.fy) / 100.;
    
    		param.area.x0 += dx0;
    		param.area.x += dx;
    		param.area.y0 += dy0;
    		param.area.y += dy;
    		mandelbrot.setArea(param);
    	}
    
    	std::vector<Dot> convertToColors(const std::vector<int>& vec)
    	{
    		std::vector<Dot> colors;
    		for (auto c : vec)
    			colors.push_back(Dot(SOLID, c));
    
    		return colors;
    	}
    
    	std::vector<Dot> fitColors(const std::vector<Dot>& givenColors, const std::size_t limit)
    	{
    		std::vector<Dot> colors;
    		if (std::size(givenColors) >= limit)
    		{
    			colors = givenColors;
    			colors.resize(limit);
    			return colors;
    		}
    
    		colors.resize(limit);
    		std::size_t n = 0;
    		std::size_t num_intervals = std::size(givenColors) - 1;
    		std::size_t sub_interval_length = limit / num_intervals;
    		std::size_t num_long_intervals = limit % num_intervals;
    		float hop_step = 1.f * num_intervals / num_long_intervals;
    
    		float hop_counter = 0;
    		for (std::size_t i = 0; i < num_intervals; ++i)
    		{
    			if (i >= hop_counter)
    			{
    				for (std::size_t j = 0; j < sub_interval_length + 1; ++j)
    					colors[n++] = givenColors[i];
    
    				hop_counter += hop_step;
    			}
    			else
    			{
    				for (std::size_t j = 0; j < sub_interval_length; ++j)
    					colors[n++] = givenColors[i];
    			}
    		}
    		return colors;
    	}
    };
    

    Puh, soviel verdammter Code. Aber ist ja alles für die Nachfahren... 🙃


  • Mod

    Ohne zu genau zu gucken: Vererbung setzt man nicht ein, bloß weil man es kann. Vererbung ist ein gefährliches Mittel, das man dann und nur dann einsetzt, wenn es vom Datenmodell her gerechtfertigt ist. In der Modellierung ist Vererbung als eine "ist ein" Beziehung zu verstehen. Bei dir also "Ein Example ist eine Console". Kommt dir das wie eine sinnvolle Beschreibung deines Modells vor? Wahrscheinlich nicht, denn es scheint dir nur um Codeweitergabe zwischen Console und Example zu gehen. Dafür gibt es aber andere Mechanismen, namentlich vor allem Komposition.

    https://www.c-plusplus.net/forum/topic/75672/kennt-ihr-schon-die-geschichte-des-mannes-der-regel-35-von-ecp-nicht-beachtet-hat



  • Finde ich jetzt die beste Lösung für "wie mache ich aus meiner statischen Console eine nicht-statische" 😉

    Ich finde gerade die Idee mit der abgeleiteten Klasse gut, weil ich kann damit Sachen machen, die vorher viel schwieriger waren.
    Aber ist natürlich ein Momentum, im Moment finde ich es richtig praktisch. In drei Jahren schäme ich mich wieder.

    Und so ganz die Prinzipien habe ich auch nicht richtig. Ich freue mich schon, wenn der Code leserlich ist... 🤓



  • Du solltest ersteinmal eine vernünftige Aufteilung in Header und Source machen (pure Header-only nutzt man nur für template).

    Und warum sind die Funktionen onUserCreate und onUserUpdate pure-virtual, denn so ist die Klasse Console ja ohne Ableitung gar nicht nutzbar.
    M.E. solltest du besser mehrere Klassen daraus erzeugen: eine technische (Basis-)Klasse und eine Userklasse.

    Inhaltlich finde ich auch einige Dinge eigenartig, z.B. die Thread-Benutzung in start()...



  • @zeropage sagte in class Console als Basis Klasse?:

    Die abgeleitete Klasse wäre dann die Anwendung, die mit der Basis Klasse realisiert werden kann.

    Damit beschreibst Du alle Vererbungen.

    Generell auf Konsolen/Terminals bezogen würde ich eher ein Template vorschlagen das die Möglichkeiten verschiedener Implementierungen nutzt. So kann es eine Klasse für die Win32-Konsole geben, eine für ein VT100, eine die ncurses nutzt, oder auch eine abgespeckte Version die nur auf Files arbeitet. Vielleicht sind dafür auch sowas wie terminal_traits nützlich, wer weiß 😉



  • Schonmal danke für die Antworten. Einige Nachfragen habe ich doch noch.

    Was ist nicht gut an header-only? Ich finde das eher belastend, immer zwei Dateien im Überblick zu haben. Ich schließe aber aus dem Hinweis, es muß einen guten Grund dagegen geben?



  • Zum einen kann das Kompilieren um Größenordnungen länger dauern, vor allem natürlich, wenn der Header mehrfach eingebunden wird.
    Zum anderen ist es eigentlich schon nicht schlecht, eine Übersicht in Form eines Headers zu haben. Klar, braucht man nicht unbedingt, haben die meisten anderen Sprachen ja auch nicht. Aber ich finde es oft tatsächlich praktisch.



  • Ich will "Kompilieren um Größenordnungen" nicht runterreden, aber überzeugt mich nicht mehr, als das man auf die Größe des Arbeitsspeicher achten soll.

    Klar gibt es Codes, die auch an heutige Hardware-Grenzen stoßen, und ich habe auch schon Beispiele gesehen, wo eine .cpp notwendig gewesen ist,
    aber bei solchen Beispielen wie hier, hat das doch alles keine Bewandnis. Ich meine, es macht schon einen Unterschied, wie groß der Code ist. Das kann man nicht 1:1 beziehen.



  • @zeropage sagte in class Console als Basis Klasse?:

    Ich will "Kompilieren um Größenordnungen" nicht runterreden, aber überzeugt mich nicht mehr, als das man auf die Größe des Arbeitsspeicher achten soll.

    Klar gibt es Codes, die auch an heutige Hardware-Grenzen stoßen, und ich habe auch schon Beispiele gesehen, wo eine .cpp notwendig gewesen ist,
    aber bei solchen Beispielen wie hier, hat das doch alles keine Bewandnis. Ich meine, es macht schon einen Unterschied, wie groß der Code ist. Das kann man nicht 1:1 beziehen.

    Das hat nichts mit dem RAM zu tun. In jeder Datei in die der Header eingebunden wird, muss halt der komplette Header wieder komplett kompiliert werden. Je mehr Dateien das betrifft, desto schlimmer. Aus diesem Grund wurden dann auch Precompiled Header empfunden.

    Wenn das Ganze für dich soo "belastend" ist, dann solltest du Module aus c++20 verfolgen.



  • @zeropage: Weißt du denn, was inline bedeutet, s.a. Inline Functions (bes. die letzten Abschnitte solltest du lesen)?



  • @zeropage sagte in class Console als Basis Klasse?:

    Ich will "Kompilieren um Größenordnungen" nicht runterreden, aber überzeugt mich nicht mehr, als das man auf die Größe des Arbeitsspeicher achten soll.

    Spätestens wenn das Kompilieren eine Stunde dauert, wird dich alles überzeugen, was die Compilezeit deutlich verkürzt 🙂
    Spätestens wenn du nicht mehr kompilieren kannst, weil der RAM voll ist und der Compiler (oder Linker) die Arbeit verweigert, wirst du auf den RAM-Verbrauch achten.



  • @zeropage sagte in class Console als Basis Klasse?:

    toTstring

    Deine Console.toTString Funktion sieht fehlerhaft aus. Wenn du nämlich das Projekt auf Unicode umstellst, kopiert deine Funktion einen std::string direkt in einen std::wstring. Und das darf man nicht.

    #include <cstdio>
    #include <string>
    #include <iostream>
    
    #include <Windows.h>
    
    #ifndef UNICODE
    #error "Projekt bitte auf Unicode umstellen";
    #endif
    
    typedef std::basic_string<TCHAR> tstring;
    
    
    
    tstring toTstring(const std::string& str)
    {
    	tstring tstr;
    	for (const auto ch : str)
    		tstr.push_back(static_cast<TCHAR>(ch));
    
    	return tstr;
    }
    
    int main(int argc, char** argv)
    {
    	std::wstring Text = toTstring("Schäferstündchen in der Tüpfelhyänenöhrchenstraße");
    	std::wcout << Text;
    	return 0;
    }
    

    Prüfe ob du nicht komplett auf TCHAR oder Unicode umstellen kannst, oder nutze MultiByteToWideChar().



  • Danke für die Anregungen. Und tschuldigung für die späte Antwort.


Anmelden zum Antworten