C言語を使ったコンソール画面で動くゲームです。
「Pythonでつくる ゲーム開発 入門講座」という書籍で紹介されていたPythonのtkinter(GUIライブラリ)を使ったプログラムをC言語に移植したものです。
2次元配列を使った勉強にもいいのでは?と思いアップしてみました。
ゲームのイメージ
元のイメージ(Pythonで作ったGUI版。今回のC言語版はこれではないですよ!)

ルールは簡単で、迷路をキーボードの上下左右で動き回り、床を全て塗ればゲームクリアです。ただし、元のGUI画面をC言語を使ったCUI画面に置き換えているので見た目は以下の通りとなります。(わたしはこうしたものが結構好きです)
C言語版のイメージ(Windowsコマンドプロンプト上で実行)
C言語版のルール
人 ・・・ プレイヤー
■ ・・・ 壁
× ・・・ 塗った床
矢印キーの上下左右でプレイヤーの移動。
プレイヤーが動けなくなってしまったら ESC キーで最初の状態に戻ります。
床を全て塗ったらゲームクリアです。
動作環境:Windows
コンパイラ:Borland C++ Compiler 5.5
動作上の注意点
動作環境をWindowsとした理由は2点あります。
1つ目は、Windows独自のシステムコマンドを使っているからです。
system ( "cls" ); |
clsはコマンドプロンプト上の命令なのでLinux上では動作しません。
2つ目は、今回プログラムでキー入力にkbhit()とgetch()関数を利用していますが、これらの関数が定義されているヘッダーファイルconio.hは、DebianなどのLinuxディストリビューションに最初から入っているgccコンパイラには存在しません。したがってLinux環境ではコンパイルエラーとなります。
コンパイルエラー:C4996: ‘getch’: The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: _getch.がでる場合
Visual Studioの開発環境でコンパイル時に
C4996: 'getch': The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: _getch.
などの警告がでる場合はソースコード82行目のkbhit()を_kbhit()に、83行目のgetch()を_getch()に置き換えてみてください。それぞれ_アンダーバーが関数名の頭につきます。(マイクロソフトの開発環境ではkbhit()とgetch()関数は非推奨の関数なので_kbhit()と_getch()をそれぞれ利用しないと警告がでる仕様になっているようです。どちらも#include <conio.h>で定義されている関数です)
ソースコード全体
/* | |
meiro_hitofude.c | |
迷路を一筆書きで抜けるゲーム | |
Created by dianxnao.com on 2020/08/17. | |
*/ | |
#include <stdio.h> | |
#include <conio.h> | |
#include <stdlib.h> | |
#define GYO 10 /* 迷路の行数 */ | |
#define RETU 10 /* 迷路の列数 */ | |
/* 迷路データ */ | |
int meiro[GYO][RETU] = { | |
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, | |
{1, 0, 0, 0, 0, 0, 1, 0, 0, 1}, | |
{1, 1, 1, 1, 1, 0, 1, 0, 0, 1}, | |
{1, 0, 0, 0, 0, 0, 1, 0, 0, 1}, | |
{1, 0, 0, 0, 1, 1, 1, 0, 0, 1}, | |
{1, 0, 0, 0, 0, 0, 0, 0, 0, 1}, | |
{1, 1, 1, 1, 1, 1, 1, 1, 0, 1}, | |
{1, 1, 0, 0, 0, 0, 0, 0, 0, 1}, | |
{1, 1, 0, 0, 0, 0, 0, 0, 0, 1}, | |
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1} | |
}; | |
int px, py; /* プレイヤーのxy座標 */ | |
int goal_count; /* 塗りつぶすべき床の数 */ | |
int count; /* 塗りつぶした床の数 */ | |
/* 最初の状態に戻る */ | |
void play_start(void) | |
{ | |
int x, y; | |
count = 0; | |
px = 1; | |
py = 1; | |
for(y=0; y<GYO; y++) | |
for(x=0; x<RETU; x++) | |
if(meiro[y][x] == 2) meiro[y][x] = 0; /* 塗りつぶし部分をもとに戻す */ | |
} | |
/* 塗りつぶすべき床の数をカウントする */ | |
void goal_count_check(void) | |
{ | |
int x, y; | |
goal_count = 0; | |
for(y=0; y<GYO; y++) | |
for(x=0; x<RETU; x++) | |
if(meiro[y][x] == 0) goal_count++; /* 移動可能な床の数をカウント */ | |
} | |
/* 迷路を描く */ | |
void draw_meiro(void) | |
{ | |
int x, y; | |
for(y=0; y<GYO; y++){ | |
for(x=0; x<RETU; x++){ | |
if(x == px && y == py){ /* プレイヤーの位置のとき */ | |
meiro[y][x] = 2; /* 塗りつぶし済みにする */ | |
count ++; /* 塗りつぶした数をカウント */ | |
printf("人"); /* プレイヤー */ | |
} | |
else if(meiro[y][x] == 0) /* 移動可能な床 */ | |
printf(" "); /* ← 注)全角スペース */ | |
else if(meiro[y][x] == 1) /* 壁 */ | |
printf("■"); | |
else if(meiro[y][x] == 2) /* 塗った床 */ | |
printf("×"); | |
} | |
printf("\n"); | |
} | |
printf("move: ←↑→↓ restart: ESC\n"); /* 操作説明 */ | |
} | |
/* キー入力判定 */ | |
void key_input(void) | |
{ | |
int key; | |
while (1) { /* キーが押されるまで待つ */ | |
if ( kbhit() ){ | |
key = getch(); /* 入力されたキー番号 */ | |
break ; | |
} | |
} | |
if(key == 72 && meiro[py-1][px] == 0) /* ↑キー */ | |
py --; /* 上に移動 */ | |
else if(key == 80 && meiro[py+1][px] == 0) /* ↓キー */ | |
py ++; /* 下に移動 */ | |
else if(key == 75 && meiro[py][px-1] == 0) /* ←キー */ | |
px --; /* 左に移動 */ | |
else if(key == 77 && meiro[py][px+1] == 0) /* →キー */ | |
px ++; /* 右に移動 */ | |
else if(key == 27) /* ESCキー */ | |
play_start(); /* 最初の状態に戻る */ | |
else /* 上記以外のキーの場合は */ | |
key_input(); /* 再度キー入力受付 */ | |
} | |
int main(void) | |
{ | |
px = 1; /* プレイヤーのx座標 */ | |
py = 1; /* プレイヤーのy座標 */ | |
count = 0; /* 塗りつぶした床の数 */ | |
goal_count_check(); /* 塗りつぶすべき床の数をカウントする */ | |
/* ゲームループ */ | |
while(1){ | |
system("cls"); /* コンソール画面をクリア */ | |
draw_meiro(); /* 迷路を表示 */ | |
if(count == goal_count){ /* 床を全て塗りつぶしたかのチェック */ | |
printf("全て塗りました!\n"); | |
break; | |
} | |
key_input(); /* キー入力受付 */ | |
} | |
return 0; | |
} |
プログラム解説
迷路データは単純な2次元配列で宣言します。1が壁で、0が床(動き回れる場所)です。
1 2 3 4 5 6 7 8 9 10 11 12 13 | /* 迷路データ */ int meiro[GYO][RETU] = { {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 0, 0, 0, 0, 0, 1, 0, 0, 1}, {1, 1, 1, 1, 1, 0, 1, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 1, 0, 0, 1}, {1, 0, 0, 0, 1, 1, 1, 0, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 0, 1}, {1, 1, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 1, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1} }; |
迷路の初期状態では、0と1の数値しか入っていません。
これを以下のような関数を作って画面に表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /* 迷路を描く */ void draw_meiro( void ) { int x, y; for (y=0; y<GYO; y++){ for (x=0; x<RETU; x++){ if (meiro[y][x] == 0) /* 移動可能な床 */ printf ( " " ); /* ← 注)全角スペース */ else if (meiro[y][x] == 1) /* 壁 */ printf ( "■" ); else if (meiro[y][x] == 2) /* 塗った床 */ printf ( "×" ); } printf ( "\n" ); } } |
実行するとこんな風に表示されます。(ただし上記のプログラムではプレイヤーが表示されないなどの問題がありますので、最終的にはもっと長くなります)

プレイヤーは配列の左上方(meiroの添え字でいうとmeiro[1][1]からスタートします。
1 2 | px = 1; py = 1; |
ゲーム開始前に塗るべき床の数をカウントしておきます。これは変数 goal_count に代入しています。
1 2 3 4 5 6 7 8 9 | /* 塗りつぶすべき床の数をカウントする */ void goal_count_check( void ) { int x, y; goal_count = 0; for (y=0; y<GYO; y++) for (x=0; x<RETU; x++) if (meiro[y][x] == 0) goal_count++; /* 移動可能な床の数をカウント */ } |
ゲーム中はゲームループと呼ばれる無限ループです。
無限ループを抜けたときがゲームクリアとなります。if(count == goal_count){ … } の部分。
1 2 3 4 5 6 7 8 9 10 11 12 | /* ゲームループ */ while (1){ system ( "cls" ); /* コンソール画面をクリア */ draw_meiro(); /* 迷路を表示 */ if (count == goal_count){ /* 床を全て塗りつぶしたかのチェック */ printf ( "全て塗りました!\n" ); break ; } key_input(); /* キー入力受付 */ } |
コンソール画面でスクロールしてしまうとどうしても見た目が損なわれます。ですから迷路を表示する前にコンソール画面のクリアをします。(Windowsでのみ動作)
1 | system ( "cls" ); /* コンソール画面をクリア */ |
迷路表示は関数化していますが、2重のfor文を使って行ごとに表示させます。内側ループが横方向(行ごとのすべての列)の表示。外側ループが縦方向(行を上から下に)の表示です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /* 迷路を描く */ void draw_meiro( void ) { int x, y; for (y=0; y<GYO; y++){ for (x=0; x<RETU; x++){ if (x == px && y == py){ /* プレイヤーの位置のとき */ meiro[y][x] = 2; /* 塗りつぶし済みにする */ count ++; /* 塗りつぶした数をカウント */ printf ( "人" ); /* プレイヤー */ } else if (meiro[y][x] == 0) /* 移動可能な床 */ printf ( " " ); /* ← 注)全角スペース */ else if (meiro[y][x] == 1) /* 壁 */ printf ( "■" ); else if (meiro[y][x] == 2) /* 塗った床 */ printf ( "×" ); } printf ( "\n" ); } printf ( "move: ←↑→↓ restart: ESC\n" ); /* 操作説明 */ } |
ポイントは、if文の最初にプレイヤーの表示をしている部分です。
迷路を塗りつぶし済みにして
1 | meiro[y][x] = 2; /* 塗りつぶし済みにする */ |
塗りつぶした床の数をカウントし
1 | count ++; /* 塗りつぶした数をカウント */ |
プレイヤーを表示させます。
1 | printf ( "人" ); /* プレイヤー */ |
もし、このプレイヤーの「人」の表示箇所を塗りつぶしの床表示の後ろに持ってきてしまうと「人」の位置に「×」が表示されてしまうのでif文の記述順序は大事です。
迷路表示の後、ユーザからのキー入力を受け付けます。
scanfをつかってしまうといちいちEnterキーを押すことになり面倒です。今回は押した時にリアルタイムなキー入力判定が可能なkbhit関数を利用しています。(kbhit関数の利用にはconio.hのインクルードが必要です)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /* キー入力判定 */ void key_input( void ) { int key; while (1) { /* キーが押されるまで待つ */ if ( kbhit() ){ key = getch(); /* 入力されたキー番号 */ break ; } } if (key == 72 && meiro[py-1][px] == 0) /* ↑キー */ py --; /* 上に移動 */ else if (key == 80 && meiro[py+1][px] == 0) /* ↓キー */ py ++; /* 下に移動 */ else if (key == 75 && meiro[py][px-1] == 0) /* ←キー */ px --; /* 左に移動 */ else if (key == 77 && meiro[py][px+1] == 0) /* →キー */ px ++; /* 右に移動 */ else if (key == 27) /* ESCキー */ play_start(); /* 最初の状態に戻る */ else /* 上記以外のキーの場合は */ key_input(); /* 再度キー入力受付 */ } |
kbhit関数は、キー入力されたときに真となる関数です。実際にどのキーが押されたかを知るにはgetch関数を使います。
1 2 3 4 | if ( kbhit() ){ key = getch(); /* 入力されたキー番号 */ break ; } |
キー番号はint型で返されるのでキー番号を知りたい場合は、以下のようにすれば表示できます。(ただし、その際は system(“cls”); 命令は消してください)
1 2 3 4 5 | if ( kbhit() ){ key = getch(); /* 入力されたキー番号 */ printf ( "key = %d\n" , key); break ; } |
コンソールプログラムでもアイデア次第でGUIっぽいゲームが出来るいい例だと思います。
コメント
82行目と83行目が
C4996 ‘getch’: The POSIX name for this item is deprecated. Instead, use the ISO C and C++ conformant name: _getch. See online help for details.
‘kbhit’:(以下同文)とエラーを吐いて実行できないのですが原因は何でしょうか。
匿名さんこんにちは。管理人です。
お使いの開発環境はVisual Studioでしょうか?
たぶんVisual StudioのCで使うマイクロソフトのコンパイラではgetchとkbhitは推奨されていない関数となっているかと思います。
エラー内容から推測するにgetch()とhbhit()の部分は、それぞれ_getch()と_kbhit()に置き換えてコンパイルするとすんなり通るかと思います。に両方とも定義されいてる関数です。
Visual StudioのCの環境では#include
参考
_kbhit – MSDN
https://learn.microsoft.com/ja-jp/cpp/c-runtime-library/reference/kbhit?view=msvc-170
ビジュアルスタジオです。SDLチェックをいいえにしたら正常に動きました。
それは良かったです。
getch()とkbhit()はマイクロソフトの環境で非推奨のようで、セキュリティを強化した_getch()と_kbhit()を使うように推奨されているみたいです。(記事に追記しておきました。ありがとうございます)