コンソールでダブルバッファみたいなことをしてみる

#ダブルバッファとは
　画面やグラフィックを描画する際に、描画領域と同じサイズのバッファ領域をメモリ上に用意してこれに描画処理を行なう手法。
　描画過程が見えてしまったり、再描画に伴って画面がちらつくのを防ぐ手法で、バッファ領域に描画が完成したらいっぺんに画面に転送する。画面には常に描画が完了した画像が表示されるため、変化が滑らかに表される。

##イメージ
![double_buffer_image_0.png](https://qiita-image-store.s3.amazonaws.com/0/62873/1fc81dce-6191-fb29-7cd9-e00551aca767.png)
表を表示している間、裏の方に書き込み、書き込みが終わると表と裏を入れ替える。
それを繰り返す感じです。

#コード
多分、動きます。

```c:ConsoleHandle.h
#ifndef _CONSOLE_HANDLE_H_
#define _CONSOLE_HANDLE_H_
#include <Windows.h>
#include<string.h>
#include<stdio.h>

typedef struct __CONSOLE {
	void(*swapConsoleHandle)();	//入れ替え
	void(*createConsoleHandle)();	//初期化
	void(*deleteConsoleHandle)();	//後始末
	void(*print)(const char str[]);	//文字を出力
	void(*clearScreen)();	        //画面をクリア
} *CONSOLE;	

CONSOLE getInstance();

#endif //_CONSOLE_HANDLE_H_
```
```c:ConsoleHandle.cpp 
#include "ConsoleHandle.h"


const SHORT BLACK = 0;
const SHORT WHITE = 15;
const int COLORS = 16;

static CONSOLE Console;
static HANDLE consoleHandle1;
static HANDLE consoleHandle2;
static bool isSwap;
static CONSOLE_CURSOR_INFO cursorInfo;
static CONSOLE_SCREEN_BUFFER_INFO screenInfo;
static CHAR_INFO* buffer;

static void _swapConsoleHandle();
static void _createConsoleHandle();
static void _deleteConsoleHandle();
static void _print(const char str[]);
static void _clearScreen();
static HANDLE getHandle();


CONSOLE getInstance() {	
	if (!Console) {
		Console = (CONSOLE)malloc(sizeof(*Console));
		Console->swapConsoleHandle = _swapConsoleHandle;
		Console->createConsoleHandle = _createConsoleHandle;
		Console->deleteConsoleHandle = _deleteConsoleHandle;
		Console->print = _print;
		Console->clearScreen = _clearScreen;
	}
	return Console;
}
static void _swapConsoleHandle() {
	::SetConsoleActiveScreenBuffer(getHandle());
	isSwap = !isSwap;
}
static void _createConsoleHandle() {
	//コンソールハンドルを作る
	consoleHandle1 = CreateConsoleScreenBuffer(
		GENERIC_READ | GENERIC_WRITE,
		0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
	consoleHandle2 = CreateConsoleScreenBuffer(
		GENERIC_READ | GENERIC_WRITE,
		0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
	
	cursorInfo.dwSize = 1;
	cursorInfo.bVisible = FALSE;
	::SetConsoleCursorInfo(consoleHandle1, &cursorInfo);
	::SetConsoleCursorInfo(consoleHandle2, &cursorInfo);

	::GetConsoleScreenBufferInfo(getHandle(), &screenInfo);

	isSwap = false;			//取り敢えずfalseで初期化

	
	buffer = (CHAR_INFO*)malloc(sizeof(CHAR_INFO) * screenInfo.dwSize.Y * screenInfo.dwSize.X); //バッファーを確保
	//バッファーを初期化
	for (int y = 0; y < screenInfo.dwSize.Y;++y) {
		for (int x = 0; x < screenInfo.dwSize.X;++x) {
			buffer[y * (int)screenInfo.dwSize.X + x].Attributes = WHITE + (BLACK << 4);
			buffer[y * (int)screenInfo.dwSize.X + x].Char.UnicodeChar = ' ';
		}
	}
}
static void _deleteConsoleHandle() {

	free(buffer);
	::CloseHandle(consoleHandle2);
	::CloseHandle(consoleHandle1);
	free(Console);
}
static HANDLE getHandle() {

	return isSwap ? consoleHandle1 : consoleHandle2;
}

static void _print(const char str[]) {
	
	COORD coord = { 0, 0 };																//書き込みを開始する位置 x:0y:0に設定
	COORD size = { screenInfo.dwSize.X,screenInfo.dwSize.Y };							//サイズ
	SMALL_RECT rect = { coord.X, coord.Y, screenInfo.dwSize.X, screenInfo.dwSize.Y };	//書き込む箇所を矩形で指定
	int length = strlen(str);															//文字の長さ
	for (int y = 0; y < screenInfo.dwSize.Y;++y) {
		for (int x = 0; x < screenInfo.dwSize.X;++x) {
			buffer[y * (int)screenInfo.dwSize.X + x].Char.UnicodeChar = str[x % (rand() % length + 1)];
			buffer[y * (int)screenInfo.dwSize.X + x].Attributes = rand() % COLORS + (rand()% COLORS << 4);
		}
	}

	::WriteConsoleOutputA(getHandle(), buffer,size, coord, &rect);
}	

void _clearScreen() {
	DWORD	dwNumberOfCharsWritten;	// 書き込まれたセル数
	COORD	coord = { 0, 0 };		//書き込みを開始する位置 x:0y:0に設定
	::GetConsoleScreenBufferInfo(getHandle(), &screenInfo);
	// バッファ内の指定した座標から指定した数の文字セル分だけ、前景色と背景色を設定
	::FillConsoleOutputAttribute(getHandle(), 
		WHITE + (BLACK << 4),
		screenInfo.dwSize.X * screenInfo.dwSize.Y,
		coord,
		&dwNumberOfCharsWritten
		);
	// バッファ内の指定した座標から、指定した文字を指定した数だけ書き込む
	::FillConsoleOutputCharacter(
		getHandle(), 
		' ',
		screenInfo.dwSize.X * screenInfo.dwSize.Y,
		coord,
		&dwNumberOfCharsWritten
		);
}

```
```c:main.cpp
#include "ConsoleHandle.h"

int main() {
	getInstance()->createConsoleHandle(); //初期化処理
	while (1) {
                getInstance()->clearScreen(); //画面のクリア
		getInstance()->print("abcdefghijk"); //文字を出力
		getInstance()->swapConsoleHandle(); //コンソールハンドルを入れ替え
	}
	getInstance()->deleteConsoleHandle();     //解放
	return 0;
}
```
シングルトンで実装しろと**金髪のお友達に脅された**のでそれっぽく実装しました。
ConsoleHandle.cppの中のConsoleHandle1やConsoleHandle2などは他のファイルから直接アクセスすることはできないようにカプセル化しています。アクセスするときはgetInstance()からアクセスすることができます。
 
system("cls"),printfしてた時よりはましだと思う。

~~コンソールでゲーム作りたくないですね~~

#参考サイト
- [MSコンソールリファレンス](https://msdn.microsoft.com/ja-jp/library/cc429123.aspx)
- [ダブルバッファリングとは｜double buffering - IT用語辞典](http://e-words.jp/w/%E3%83%80%E3%83%96%E3%83%AB%E3%83%90%E3%83%83%E3%83%95%E3%82%A1%E3%83%AA%E3%83%B3%E3%82%B0.html)
- [ダブルバッファ](http://vivi.dyndns.org/tech/cpp/DblBuffer.html)