C言語+Win32API:ドット絵の魚が30分ごとに教えてくれるプログラム

C言語

パソコンに熱中すると数時間過ぎている事が多い。
目を休めるために30分ごとにウインドウが最大化して知らせてくれるWindowsアプリを作った。

動作イメージ

機能

機能はほぼ無いに等しいが、一応説明する。

実行は、ダブルクリックするだけ。(わたしはWindowsのスタートアップフォルダに入れています)

30分経つと…
画面いっぱいにメッセージが表示される。(英語が変かもしれないのでプログラムで適当に変えてください)
Rested! ボタンを押すとまた30分待機して再実行となる。(ちなみに30分間はバックグラウンドプロセスとして実行される)

ソースコード

表示文字やフォントサイズ、タイマーの時間等はソースコードのハイライト部分を適当に変えてみてください。
コンパイル方法は、先頭のコメントに記述してある通りです。

rest_your_eyes.c

/*
	rest_your_eyes.c: 30分後に目を休めるメッセージを知らせるWindowAPIアプリ
	
		v1.0 正式版
		v0.9 開発用:ウインドウサイズ等をビットマップに表示している

		コンパイル方法
			Borland C++ Compiler5.5
				bcc32 -W FILENAME.c
			GCC	コンパイラ
				gcc -o APPNAME FILENAME.c -mwindows
*/
#include <windows.h>
#include <strsafe.h>
#include <time.h>
#include <stdlib.h>

#define DRAW_STRING		TEXT("Rest your eyes. If don't...")	/* 表示メッセージ */

#define ID_TIMER			1		/* タイマーID */
#define ID_ANIM_TIMER		2			/* アニメ切り替え用タイマーID */
#define TIMER_MINUTES		30			/* 目を休める時間(分) */
#define TIMER				TIMER_MINUTES * 60
#define ANIM_TIMER			0.2		/* アニメの切り替わり間隔(秒) */
#define SPEED				10		/* 魚の速度(ピクセル単位) */
#define ID_BUTTON			1		/* ボタンID */
#define PIXEL_SIZE			20		/* 1ドットのサイズ */
#define FONT_SIZE			32		/* フォントサイズ */

RECT rcDisplay;				/* デスクトップサイズ取得用 */
HFONT hFont1;				/* フォント作成用 */
int px, py;					/* 魚のxy座標 */
int speed;					/* 魚の向きを加えた速度 */
int flipFlop;					/* アニメ切り替え用変数(1->0 0->1) */
HBRUSH brasi_color1, brasi_color2;		/* ドット絵の色(2色のみ使用) */
int fontSize;					/* 表示文字のサイズ */
int centerX, centerY;			/* ウインドウの中央座標(ボタンや文字のレイアウト用) */

/* 魚のドット絵1 */
int dot1[][8] = {
	{ 0, 0, 0, 0, 0, 0, 0, 0},
	{ 0, 0, 0, 1, 0, 0, 0, 0},
	{ 0, 0, 0, 1, 1, 1, 0, 0},
	{ 1, 0, 1, 1, 1, 1, 1, 0},
	{ 0, 1, 1, 1, 1, 0, 1, 1},
	{ 0, 1, 1, 1, 1, 1, 1, 0},
	{ 1, 0, 1, 1, 1, 1, 0, 0},
	{ 0, 0, 0, 0, 1, 0, 0, 0}
};

/* 魚のドット絵2 */
int dot2[][8] = {
	{ 0, 0, 0, 0, 0, 0, 0, 0},
	{ 0, 0, 0, 1, 0, 0, 0, 0},
	{ 0, 0, 0, 1, 1, 1, 0, 0},
	{ 1, 0, 1, 1, 1, 1, 1, 0},
	{ 0, 1, 1, 1, 1, 0, 1, 1},
	{ 0, 1, 1, 1, 1, 1, 1, 0},
	{ 1, 0, 1, 1, 1, 1, 0, 0},
	{ 0, 0, 0, 2, 1, 0, 0, 0}
};

/* 1ドットを描画する関数 */
void DrawPoint(HDC hdc, int x, int y, int color){
	if(color == 1){
		SelectObject(hdc, brasi_color1); /* 配列dot1, dot2の値が 1 のときの色 */
	}
	else if(color == 2){
		SelectObject(hdc, brasi_color2); /* 配列dot1, dot2の値が 2 のときの色 */
	}
	Rectangle(hdc, x, y, x+PIXEL_SIZE, y+PIXEL_SIZE);	/* 四角形を描画 */
}

/* ドット絵全体を描画する関数 */
void DotDraw(HDC hdc, int dot[][8]){
	for(int i=0; i<8; i++){		/* 縦方向8ドット */
		for(int j=0; j<8; j++){	/* 横方向8ドット */
			if(dot[i][j]){	/* 値が0以外のとき描画 */
				DrawPoint(hdc, px+j*PIXEL_SIZE, py+i*PIXEL_SIZE, dot[i][j]);
			}
		}
	}
}

/* ドット絵配列の左右を反転する関数 */
void DotReverse(int dot[][8]){
	int work;
	for(int i=0; i<8; i++){
		for(int j=0; j<4; j++){
			work = dot[i][j];
			dot[i][j] = dot[i][7-j];
			dot[i][7-j] = work;
		}
	}
}

/* ウインドウ作成関連(Windows API) */
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
	static TCHAR appName[] = DRAW_STRING;
	HWND hWnd, hDeskTopWnd;
	MSG msg;
	WNDCLASS wndclass;
	HWND hWnd_button;		/* ボタン作成用 */

	/* デスクトップのサイズを取得 */
	hDeskTopWnd = GetDesktopWindow();
	GetWindowRect(hDeskTopWnd, &rcDisplay);
	
	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = WndProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hInstance = hInstance;
	wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = (HBRUSH) CreateSolidBrush(RGB(0x00, 0x00, 0x00));		/* 背景色設定 */
	wndclass.lpszMenuName = NULL;
	wndclass.lpszClassName = appName;
	
	if(!RegisterClass(&wndclass)){
		MessageBox(NULL, TEXT("ウインドウクラスの作成に失敗しました"),
					NULL, MB_OK);
		return 0;
	}
	/* ウインドウ作成 */
	hWnd = CreateWindow(appName, DRAW_STRING,
						WS_OVERLAPPEDWINDOW | WS_VISIBLE,
						CW_USEDEFAULT, CW_USEDEFAULT,
						CW_USEDEFAULT, CW_USEDEFAULT,
						NULL, NULL, hInstance, NULL);
	/* ボタンコントロール作成 */
	hWnd_button = CreateWindowA("button", "Rested !", WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
		rcDisplay.right / 2-50, rcDisplay.bottom -150, 100, 50, hWnd, (HMENU)ID_BUTTON, hInstance, NULL);
	
	if(hWnd == NULL || hWnd_button == NULL){
		MessageBox(NULL, TEXT("ウインドウの作成に失敗しました"), NULL, MB_OK);
		return 0;
	}
	/* 初期設定 */
	srand((unsigned) time(NULL));		/* 乱数系列初期化 */
	speed = SPEED;					/* 魚の速度設定 */
	px = 0; py = rand() % (rcDisplay.bottom - PIXEL_SIZE * 8);	/* 魚のxy座標(y座標はランダム) */
	flipFlop = 1;						/* アニメ切り替え用 */
	centerX = rcDisplay.right / 2;		/* ウインドウの中心 x座標 */
	centerY = rcDisplay.bottom / 2;		/* ウインドウの中心 y座標 */

	ShowWindow(hWnd, 0);	/* ウィンドウを隠したまま開く */
	UpdateWindow(hWnd);
	
	/* 起動待機 */
	while(GetMessage(&msg, NULL, 0, 0)>0){
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	
	return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HBITMAP	hb;		/* ビットマップハンドル */
	PAINTSTRUCT ps;
	static HDC mhdc;			/* メモリデバイスコンテキスト */
	HDC hdc;				/* デバイスコンテキストハンドル取得用 */
	
	switch(message){
		case WM_DESTROY:
			/* タイマーを停止 */
			KillTimer(hwnd, ID_TIMER);
			KillTimer(hwnd, ID_ANIM_TIMER);

			DeleteObject(hFont1);	/* HFONTオブジェクトを破棄 */
			DeleteObject(hb);		/* ビットマップハンドルを削除 */
			DeleteDC(mhdc);		/* メモリデバイスコンテキストを破棄 */

			PostQuitMessage(0);
			return 0;
		case WM_COMMAND:
			switch(LOWORD(wParam)){
				case ID_BUTTON:	/* 「Rested!」ボタンを押したときの処理 */
					KillTimer(hwnd, ID_ANIM_TIMER);					/* アニメ停止 */
					SetTimer(hwnd, ID_TIMER, TIMER * 1000, NULL);		/* タイマー再開 */
					ShowWindow(hwnd, 0);	/* ウィンドウを隠す */
			}
			break;
		case WM_CREATE:
			hdc = GetDC(hwnd);		/* デバイスコンテキストハンドルを取得 */
			mhdc = CreateCompatibleDC(hdc);
			ReleaseDC(hwnd, hdc);
			/* 使用するブラシカラーを作成 */
			brasi_color1 = CreateSolidBrush(RGB(118, 151, 222)); /* ブラシカラー1 */
			brasi_color2 = CreateSolidBrush(RGB(170, 192, 255)); /* ブラシカラー2 */

			/* フォント作成 */
			fontSize = FONT_SIZE * rcDisplay.right / 1280;
			hFont1 = CreateFont(fontSize, 0,	/* 高さ, 幅 */
				0, 0, FW_BOLD, 			/* 傾き(反時計回り), 文字の向き, 太字(0にするとデフォルト) */
				FALSE, FALSE, FALSE,		/* Italic,  下線,  打消し線 */
				SHIFTJIS_CHARSET, OUT_DEFAULT_PRECIS,	/* キャラクタセット, 物理フォントの検索方法 */
				CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,		/* はみ出した場合の処理,  属性 */
				VARIABLE_PITCH | FF_ROMAN, NULL);			/* ピッチ、ファミリ,  タイプフェイスのポインタ */

			/* タイマー開始 */
			SetTimer(hwnd, ID_TIMER, TIMER * 1000, NULL);
			/* アニメ開始 */
			SetTimer(hwnd, ID_ANIM_TIMER, ANIM_TIMER * 1000, NULL);

			return 0;
		case WM_TIMER:
			if(wParam == 1){		/* タイマー終了(30分経ったら) */
				MessageBeep(-1);				/* システムビープ音 */
				KillTimer(hwnd, ID_TIMER);	/* タイマー停止 */
				SetTimer(hwnd, ID_ANIM_TIMER, ANIM_TIMER * 1000, NULL);	/* アニメ再開 */
				InvalidateRect(hwnd, NULL, FALSE);		/* ウインドウ背景そのままで WM_PAINT 呼び出し */
				ShowWindow(hwnd, 3);	/* ウインドウ最大化 */
			}
			else if(wParam == 2){
				px+=speed;		/* 魚のx座標移動 */
				flipFlop ^= 1;	/* 0->1 1->0 */
				
				/* 魚の向きを変える */
				if(px > rcDisplay.right || px < 0 - PIXEL_SIZE*8){
					speed = -speed;
					py = rand() % (rcDisplay.bottom - PIXEL_SIZE * 8);	/* y座標をランダムで再設定 */
					DotReverse(dot1);	/* 配列の左右反転 */
					DotReverse(dot2);
				}
				
				InvalidateRect(hwnd, NULL, TRUE);	/* ウインドウ背景を消去して WM_PAINT 呼び出し */
			}
			return 0;
		case WM_PAINT:
			hdc = BeginPaint(hwnd, &ps);

			/* ドット絵描画 */
			if(flipFlop) DotDraw(hdc, dot1);
			else DotDraw(hdc, dot2);

			/* 文字表示 */
			SelectObject(hdc, hFont1);		/* フォントをセット */
			SetTextColor(hdc, RGB(255, 255, 255));			/* 文字色 */
			SetBkColor(hdc, TRANSPARENT);			/* 背景色は透明 */
			SetBkMode(hdc, TRANSPARENT);			/* 背景を塗りつぶさない */
			TextOut(hdc, centerX-lstrlen(DRAW_STRING)/2*(fontSize/2)+fontSize, centerY-fontSize, DRAW_STRING, lstrlen(DRAW_STRING));
			
			EndPaint(hwnd, &ps);
			return 0;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}

プログラムについて

ドット絵の魚は、2つの2次元配列dot1とdot2を交互に表示させている。

/* 魚のドット絵1 */
int dot1[][8] = {
	{ 0, 0, 0, 0, 0, 0, 0, 0},
	{ 0, 0, 0, 1, 0, 0, 0, 0},
	{ 0, 0, 0, 1, 1, 1, 0, 0},
	{ 1, 0, 1, 1, 1, 1, 1, 0},
	{ 0, 1, 1, 1, 1, 0, 1, 1},
	{ 0, 1, 1, 1, 1, 1, 1, 0},
	{ 1, 0, 1, 1, 1, 1, 0, 0},
	{ 0, 0, 0, 0, 1, 0, 0, 0}
};

/* 魚のドット絵2 */
int dot2[][8] = {
	{ 0, 0, 0, 0, 0, 0, 0, 0},
	{ 0, 0, 0, 1, 0, 0, 0, 0},
	{ 0, 0, 0, 1, 1, 1, 0, 0},
	{ 1, 0, 1, 1, 1, 1, 1, 0},
	{ 0, 1, 1, 1, 1, 0, 1, 1},
	{ 0, 1, 1, 1, 1, 1, 1, 0},
	{ 1, 0, 1, 1, 1, 1, 0, 0},
	{ 0, 0, 0, 2, 1, 0, 0, 0}
};

2つの配列の違いはほとんど無い。(笑)dot2の一部が2となっているだけ。
しかも配列データの1と2の違いは、描画色が微妙に違うのみ。(ただこうすることで魚のひれが動いているような気がしたので…)

たぶんWindowsAPIのBitBlt関数などを使えばドット絵の表示部分はもっと簡略化できるはずだが、あえて毎回四角形(Rectangle関数で描画)を8×8個(計64個)描画する面倒な仕様になっている。

ドット絵の魚が向きを変える部分も、2次元配列を左右反転して小面倒なことをしている。

/* ドット絵配列の左右を反転する関数 */
void DotReverse(int dot[][8]){
	int work;
	for(int i=0; i<8; i++){
		for(int j=0; j<4; j++){
			work = dot[i][j];
			dot[i][j] = dot[i][7-j];
			dot[i][7-j] = work;
		}
	}
}

コメント

タイトルとURLをコピーしました