高専生は日本の宝？！〜高専生が授業の知識だけでC言語のゲーム作ってみた〜

#はじめに
RHEMS技研の平澤です！
IT企業でバイトしている高専生です。

今回の記事の目的は、
**1.実際、高専生のレベルはどれくらいなのか？**
**2.高専情報分野の3年生で習った知識で、どれくらいのものが作れるのか？**
というのをお伝えすることです。
先日のネットニュースでは、「[高専生は日本の宝](https://www.nikkei.com/article/DGXMZO37752760U8A111C1000000/)」などと言われていました。
その日本の宝の実情をさらけ出そうということです。

今回お見せするのは、週1時間で開講されているC言語の授業で習った知識で作ったゲームです。

#ゲームの概要
c言語のcursesライブラリを使ってのゲーム制作です。
ゲームは端末で動かします。
某監督の代表作品を参考にしています。
ゲームの内容は、敵に見つからないように、スタート地点からゴール地点に向かうというもの。
実際のゲーム画面はこんな感じ。

* タイトル画面
<img width="923" alt="スクリーンショット 2019-02-08 18.01.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/349288/f14e8933-af76-f04d-0b73-cecd68715820.png">



* メニュー画面
<img width="928" alt="スクリーンショット 2019-02-08 18.00.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/349288/c528ab8e-e1f9-6273-0ac4-0f8c5f9f7a0c.png">


* プレイしている様子
![stage1.gif](https://qiita-image-store.s3.amazonaws.com/0/349288/7cfddd2d-d103-0f12-4110-9eb4f664d056.gif)
Sはスタート位置、Gはゴール位置、＄は自分の操作キャラ、＠は敵キャラ
＠の周りにある青い部分は敵の視界を表しています。
敵の視界に入らずにゴールまで行くとステージクリア、敵の視界に入るとゲームオーバーとなります。

#どうやって動いている？
ゲームが置いてあるディレクトリの構成はこんな感じ。
<img width="682" alt="スクリーンショット 2019-02-08 14.49.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/349288/a884edb7-de1d-63d3-2067-1a60603efcda.png">

構造体を使っています。定義は以下のヘッダーファイルに書いています。
##structure.h
```c:structure.h
#ifndef STRUCTURE_H      //インクルードガード
#define STRUCTURE_H

typedef struct {
	int	x, y;		    //自キャラの座標
} Player;

typedef struct {
	double	x, y;		//敵キャラ1の座標
	char	move[256];	//敵キャラの動き
} Enemy;

typedef struct {
	int	w, h;		//端末の幅と高さ
	char	*pixel;		//端末に表示する文字列
} Img;

typedef struct {
	int	w, h;		//ステージの幅と高さ
	char	wall[256][256];		//ステージの壁
} Map;

#endif
```

これらの構造体はどこにでも登場してくるので、最初に示しました。
次は最も核となるmain.cです。
##main.c
```c:main.c
#include<ncurses.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include"load.h"
#include"ctrlshow.h"
#include"title.h"


int Game(FILE *stagemap, FILE *stagedata, FILE *enemydata1, FILE *enemydata2, FILE *enemydata3)
{
	Player	player;
	Enemy	enemy1, enemy2, enemy3;
	Img	img;
	int	key;
	int	*i1, *i2, *i3;
	int	j1=0, j2=0, j3=0;
	int	a=0;
	Map	map;

	i1 = &j1;
	i2 = &j2;
	i3 = &j3;

	timeout(0.1);
	clear();
	Loadstage(stagedata, stagemap, &player, &map);
	Loadenemy(enemydata1, &enemy1);
	Loadenemy(enemydata2, &enemy2);
	Loadenemy(enemydata3, &enemy3);

	getmaxyx(stdscr, img.h, img.w);
	img.pixel = (char *)malloc(sizeof(char)*img.w*img.h);
	if (img.pixel == NULL) goto ERROR;

	start_color();
	init_pair(1, COLOR_WHITE, COLOR_BLACK);		//デフォルトの色
	init_pair(2, COLOR_GREEN, COLOR_BLACK);		//ステージの色
	init_pair(3, COLOR_WHITE, COLOR_BLACK);		//自キャラの色

	while (1) {
		erase();
		attrset(COLOR_PAIR(2));
		DrawMap(&map);				        //ステージ読み込み
		attrset(COLOR_PAIR(3));
        ShowPlayer(&player);                //自キャラ読み込み
		ShowEnemy(&enemy1, &map, i1);		//敵1の表示
		ShowEnemy(&enemy2, &map, i2);		//敵2の表示
		ShowEnemy(&enemy3, &map, i3);		//敵2の表示
		refresh();
		if (a == 10) {
			a=0;
            j1++; j2++; j3++;               //カウンタアップ
            CtrlEnemy(&enemy1, i1);         //敵のコントロール
            CtrlEnemy(&enemy2, i2);         //敵のコントロール
            CtrlEnemy(&enemy3, i3);         //敵のコントロール
        }
        a++;
		if (map.wall[player.y][player.x] == 'G') goto SUCCESS;
		if (Discovery(&player) == 1) goto FAILURE;
		key = getch();
		CtrlPlayer(&player, &map, key);
		if (key == 'q') {
			attrset(COLOR_PAIR(1));		
			goto END;
		}
		usleep(20000);
	}

END:
	free(img.pixel);
	return(0);

SUCCESS:
	timeout(-1);
	attrset(COLOR_PAIR(1));
	key = Ocelot(&img);
	if (key == ' ') {
		return(0);
	} else {
		goto SUCCESS;
	}

FAILURE:
	timeout(-1);
	attrset(COLOR_PAIR(1));
	key = Warning(&img);
	if (key == ' ') {
		return(0);
	} else {
		goto FAILURE;
	}	

ERROR:
	return(1);
}



int main(void)
{
	FILE	*stagemap;
	FILE	*stagedata;
	FILE	*enemydata1;
	FILE	*enemydata2;
	FILE	*enemydata3;
	int	key;		//キーボードからの入力を受け取る
	Img	img;        //端末の画面サイズを受け取る

	initscr();
	noecho();
	cbreak();
	curs_set(0);
	getmaxyx(stdscr, img.h, img.w);

TITLE:
	key = Title1(&img);
	if (key == 'q') {
		goto END;
	} else if (key == ' ') {
		goto GAME;
	} else {
		goto TITLE;
	}
	
GAME:
	while (1) {
		timeout(-1);
		key = Title2(&img);

		switch (key) {
		case 'q':
			goto TITLE;
		case '1':
			if (
			stagemap = fopen("stage-1-map.txt", "r"));
			stagedata = fopen("stage-1-data.txt", "r"));
			enemydata1 = fopen("stage-1-enemy1.txt", "r"));
			enemydata2 = fopen("stage-1-enemy2.txt", "r"));
			enemydata3 = fopen("stage-1-enemy3.txt", "r"));
			break;
		case '2':
			stagemap = fopen("stage-2-map.txt", "r");
			stagedata = fopen("stage-2-data.txt", "r");
			enemydata1 = fopen("stage-2-enemy1.txt", "r");
			enemydata2 = fopen("stage-2-enemy2.txt", "r");
			enemydata3 = fopen("stage-2-enemy3.txt", "r");
			break;
		case 's':
			stagemap = fopen("stage-my1-map.txt", "r");
            stagedata = fopen("stage-my1-data.txt", "r");
            enemydata1 = fopen("stage-my1-enemy1.txt", "r");
            enemydata2 = fopen("stage-my1-enemy2.txt", "r");
            enemydata3 = fopen("stage-my1-enemy3.txt", "r");
			break;
		default:
			continue;
		}
		Game(stagemap, stagedata, enemydata1, enemydata2, enemydata3);
	}
	fclose(stagemap);
	fclose(stagedata);
	fclose(enemydata1);
	fclose(enemydata2);
	fclose(enemydata3);
	
END:
	endwin();
	return(0);
}
```
main.cには2つの関数、mainとgameがあります。

main関数の大まかな流れは、
１．外部ファイルtitle.cから、タイトル画面とメニュー画面を表示する
２．キーボード入力で選択されたステージを、関数gameで実行する

例えば、メニュー画面が表示されているとき、キーボードで「1」を入力すると、

|File|Description|
|---|---|
|stage-1-map.txt|ステージの壁を描写している|
|stage-1-data.txt|ステージのサイズ、自分のキャラの初期位置、ゴールの位置を記してある|
|stage-1-enemy1.txt|敵1の初期位置、動き方を記してある|
|stage-1-enemy2.txt|敵2の初期位置、動き方を記してある|
|stage-1-enemy3.txt|敵3の初期位置、動き方を記してある|

これらのテキストファイルを読み込み、引数として関数gameを実行しています。

```:stage-1-map.txt
                                      #################                                
                                      ##             ##                                
                                      ##             ##                                
                                      ##      G      ##                                
                             ###########             ##################################
                             ##                                                      ##
                             ##                                               #####  ##
      #########################                                               #####  ##
      ##                                                                      #####  ##
      ##                                                                             ##
      ##                                                                             ##
      ##         #########################         #########################         ##
      ##         #########################         #########################         ##
      #####      #########################         #########################         ##
      #####      #########################         #########################         ##
      ##         #########################         #########################         ##
      ##         #########################         #########################         ##
      ##         #########################         #########################         ##
      ##                                                                             ##
      ##                                                                             ##
      ##                                                                             ##
      ##         #########################         #########################         ##
      ##         #########################         #########################         ##
      ##         #########################         #########################         ##
      ##         #########################         #########################         ##
      ##         #########################         #########################         ##
      ##         #########################         #########################         ##
########         #########################         #########################         ##
##                                     ###                                           ##
##                                                                                   ##
##                                                                                   ##
##               #####################################################               ##
##           ######                                                 ##               ##
##      ###########                                                 ##     ######    ##
##      ######   ##                                                 ##     ############
##      ######   ##                                                 ##     ######    ##
##               ##                                                 ##               ##
##       S       ##                                                 ##               ##
##               ##                                                 ##               ##
###################                                                 ###################
```

##title.c

```c:title.c
#include<ncurses.h>
#include<string.h>
#include"title.h"

int Warning(Img *img)
{
	int	sx, sy;
	sx = img->w / 2;
        sy = img->h / 2;

	mvaddstr(sy, sx, "SHAKE IS DEAD");
	mvaddstr(sy+1, sx-9, "press space key to go back to menu");
	refresh();
	return(getch());
}	

int Ocelot(Img *img)
{
	int	sx1, sx2, sy;
	sx1 = img->w / 2 - 22;
	sx2 = img->w / 2 - 18;
	sy = img->h / 2 - 9;
	
	mvaddstr(sy, sx1, "| ##    ## ####  ##  ##  ##  #####  ######  |");
	mvaddstr(sy+1, sx1, "|  ##  ## ##  ## ##  ##  ##      ##         |");
	mvaddstr(sy+2, sx1, "|   ####  ##  ## ##  ##      #####  ######  |");
	mvaddstr(sy+3, sx1, "|    ##   ##  ## ##  ##      ## ##  ##      |");
	mvaddstr(sy+4, sx1, "|    ##    ####   ####       ##  ## ######  |");
	mvaddstr(sy+6, sx1, "|#####  #####  ###### ###### ###### ##    ##|");
	mvaddstr(sy+7, sx1, "|##  ##     ##          ##     ##    ##  ## |");
	mvaddstr(sy+8, sx1, "|#####  #####  ######   ##     ##     ####  |");
	mvaddstr(sy+9, sx1, "|##     ## ##  ##       ##     ##      ##   |");
	mvaddstr(sy+10, sx1, "|##     ##  ## ######   ##     ##      ##   |");
	mvaddstr(sy+12, sx1, "| ######  #####   #####  ######    ###  ### |");
	mvaddstr(sy+13, sx1, "|##      ##   ## ##   ## ##  ###   ###  ### |");
	mvaddstr(sy+14, sx1, "|## #### ##   ## ##   ## ##   ###  ###  ### |");
	mvaddstr(sy+15, sx1, "|##   ## ##   ## ##   ## ##  ###            |");
	mvaddstr(sy+16, sx1, "| ######  #####   #####  ######    ###  ### |");
	mvaddstr(sy+19, sx2, "Press space key to go to mission menu");
	refresh();
	start_color();
	init_pair(1, COLOR_WHITE, COLOR_BLACK);         //通常の色
	attrset(COLOR_PAIR(1));	
	return(getch());
}

int Title1(Img *img)
{
	int 	sx1, sx3, sy;
	
	clear();
	sx1 = (img->w / 2) - 36;		//タイトルの真ん中決め
	sx3 = (img->w / 2) - 5;
	sy = (img->h / 2) - 7;

	  mvaddstr(sy, sx1, "      ##    ## ###### ######   ##  ##     ######     ##  #####   ");
	mvaddstr(sy+1, sx1, "     ###   ###          ##    ###  ##               ###      ##  ");
	mvaddstr(sy+2, sx1, "    ## # ## ## ######   ##   ## ## ##     ######   ## ## #####   ");
	mvaddstr(sy+3, sx1, "   ##  ##   ## ##       ##  ##  ## ##     ##      ##  ## ## ##   ");
	mvaddstr(sy+4, sx1, "  ##   #    ## ######   ## ##   ## #####  ###### ##   ## ##  ##  ");
	mvaddstr(sy+5, sx1, " ##                                                           ## ");
	mvaddstr(sy+6, sx1, "##                                                             ##");
	mvaddstr(sy+8, sx3, "press space key");
	refresh();
	return(getch());
}

int Title2(Img *img)
{
	int	sx, sy;

	clear();
	sx = (img->w / 2) - 21;
	sy = (img->h / 2) - 7;

	mvaddstr(sy, sx, "                  [Missions]                   ");
	mvaddstr(sy+2, sx, "|   Your missions are to go to 'G' from 'S'   |");
	mvaddstr(sy+3, sx, "|          Must not be found by enemy         |");
	mvaddstr(sy+4, sx, "|        There is no support from here        |");
	mvaddstr(sy+5, sx, "|             Good luck with that             |");
	mvaddstr(sy+7, sx, "Press 'WASD' to move");
	mvaddstr(sy+8, sx, "Press '1' to go ahead 'LOADING DOCK' [MGS]");
	mvaddstr(sy+9, sx, "Press '2' to go ahead 'PUERTO DEL ALBA' [MGSPW]");
	mvaddstr(sy+10, sx, "Press 's' to go ahead 'SPECIAL STAGE'　[created by me]");
	mvaddstr(sy+11, sx, "Press 'q' to back to title");
	return(getch());
}
```

関数WarningとOcelotは、ステージクリア時とゲームオーバー時に表示されるものです。

次は関数gameについてです。流れは、

1. ステージを表示
2. 自分の操作キャラを表示
3. 敵を表示
4. 敵の位置を変更
5. キー入力で自分のキャラを移動
6. 1〜5を高速で繰り返す

という感じです。
上にあるGIFと照らし合わせて言うと、関数game内のwhile文は、敵キャラが1マス動くごとに10回繰り返しています。
LoadStage、LoadEnemy、DrawMap、ShowPlayerなどの外部関数は、
ctrlshow.cとload.cから読み込んでいます。

##ctrlshow.c
```c:ctrlshow.c
#include<ncurses.h>
#include<string.h>
#include"ctrlshow.h"

void CtrlPlayer(Player *player, Map *map, int key)
{
	switch (key) {
	case 'a': {
		if (map->wall[player->y][player->x-1] == '#') break;
		(player->x)--;	break;
	}
	case 's': {
		if (map->wall[player->y+1][player->x] == '#') break;
		(player->y)++;	break;
	}
	case 'w': {
		if (map->wall[player->y-1][player->x] == '#') break;
		(player->y)--;	break;
	}
	case 'd': {
		if (map->wall[player->y][player->x+1] == '#') break;
		(player->x)++;	break;
	}
	}	
}

void CtrlEnemy(Enemy *enemy, int *i)
{
	switch (enemy->move[*i]) {
	case 'a': {
		(enemy->x) = (enemy->x - 1);	break;
	}
	case 's': {
		(enemy->y) = (enemy->y + 1);	break;
	}	
	case 'w': {
		(enemy->y) = (enemy->y - 1);	break;
	}
	case 'd': {
		(enemy->x) = (enemy->x + 1);	break;
	}
	default:
		*i = 0;
	}
}

void ShowPlayer(Player *player)
{
	mvaddch(player->y, player->x, '$');
}

void ShowEnemy(Enemy *enemy, Map *map, int *i)
{
	int	a;

	start_color();
        init_pair(4, COLOR_RED, COLOR_BLACK);           //敵の色
        init_pair(5, COLOR_BLUE, COLOR_BLUE);           //敵の視界の色
	
	switch (enemy->move[*i]) {
        case 'a': {
		attrset(COLOR_PAIR(5));
		for (a=1; a<=6; a++) {
                        if (map->wall[(int)enemy->y-2][(int)enemy->x-(11-a)] == '#') continue;
                        mvaddch(enemy->y-2, enemy->x-(11-a), '*');
                }
		for (a=1; a<=8; a++) {
			if (map->wall[(int)enemy->y-1][(int)enemy->x-(11-a)] == '#') continue;
			mvaddch(enemy->y-1, enemy->x-(11-a), '*');
		}
		for (a=1; a<=10; a++) {
			if (map->wall[(int)enemy->y][(int)enemy->x-(11-a)] == '#') continue;
			mvaddch(enemy->y, enemy->x-(11-a), '*');
		}
		attrset(COLOR_PAIR(4));
		mvaddch(enemy->y, enemy->x, '@');
		attrset(COLOR_PAIR(5));
		for (a=1; a<=8; a++) {
			if (map->wall[(int)enemy->y+1][(int)enemy->x-(11-a)] == '#') continue;
			mvaddch(enemy->y+1, enemy->x-(11-a), '*');
		}
		for (a=1; a<=6; a++) {
                        if (map->wall[(int)enemy->y+2][(int)enemy->x-(11-a)] == '#') continue;
                        mvaddch(enemy->y+2, enemy->x-(11-a), '*');
                }
		break;
        }
        case 's': {
		attrset(COLOR_PAIR(4));
                mvaddch(enemy->y, enemy->x, '@');
		attrset(COLOR_PAIR(5));
		for (a=1; a<=3; a++) {
			if (map->wall[(int)enemy->y+1][(int)enemy->x-(2-a)] == '#') continue;
			mvaddch(enemy->y+1, enemy->x-(2-a), '*');
		}
		for (a=1; a<=5; a++) {
			if (map->wall[(int)enemy->y+2][(int)enemy->x-(3-a)] == '#') continue;
			mvaddch(enemy->y+2, enemy->x-(3-a), '*');
		}
		for (a=1; a<=7; a++) {
			if (map->wall[(int)enemy->y+3][(int)enemy->x-(4-a)] == '#') continue;
			mvaddch(enemy->y+3, enemy->x-(4-a), '*');
		}
		for (a=1; a<=9; a++) {
			if (map->wall[(int)enemy->y+4][(int)enemy->x-(5-a)] == '#') continue;
			mvaddch(enemy->y+4, enemy->x-(5-a), '*');
		}
		break;
        }
        case 'w': {
		attrset(COLOR_PAIR(5));
		for (a=1; a<=9; a++) {
			if (map->wall[(int)enemy->y-4][(int)enemy->x-(5-a)] == '#') continue;
			mvaddch(enemy->y-4, enemy->x-(5-a), '*');
		}
		for (a=1; a<=7; a++) {
			if (map->wall[(int)enemy->y-3][(int)enemy->x-(4-a)] == '#') continue;
			mvaddch(enemy->y-3, enemy->x-(4-a), '*');
		}
		for (a=1; a<=5; a++) {
			if (map->wall[(int)enemy->y-2][(int)enemy->x-(3-a)] == '#') continue;
			mvaddch(enemy->y-2, enemy->x-(3-a), '*');
		}
		for (a=1; a<=3; a++) {
			if (map->wall[(int)enemy->y-1][(int)enemy->x-(2-a)] == '#') continue;
			mvaddch(enemy->y-1, enemy->x-(2-a), '*');
		}
		attrset(COLOR_PAIR(4));
		mvaddch(enemy->y, enemy->x, '@');
		break;
        }
        case 'd': {
		attrset(COLOR_PAIR(5));
		for (a=1; a<=6; a++) {
			if (map->wall[(int)enemy->y-2][(int)enemy->x+(4+a)] == '#') continue;
			mvaddch(enemy->y-2, enemy->x+(4+a), '*');
		}
		for (a=1; a<=8; a++) {
			if (map->wall[(int)enemy->y-1][(int)enemy->x+(2+a)] == '#') continue;
			mvaddch(enemy->y-1, enemy->x+(2+a), '*');
		}
		attrset(COLOR_PAIR(4));
		mvaddch(enemy->y, enemy->x, '@');
		attrset(COLOR_PAIR(5));
		for (a=1; a<=10; a++) {
			if (map->wall[(int)enemy->y][(int)enemy->x+a] == '#') continue;
			mvaddch(enemy->y, enemy->x+a, '*');
		}
		for (a=1; a<=8; a++) {
			if (map->wall[(int)enemy->y+1][(int)enemy->x+(2+a)] == '#') continue;
			mvaddch(enemy->y+1, enemy->x+(2+a), '*');
		}
		for (a=1; a<=6; a++) {
                        if (map->wall[(int)enemy->y+2][(int)enemy->x+(4+a)] == '#') continue;
                        mvaddch(enemy->y+2, enemy->x+(4+a), '*');
		}
		break;
	}
	}
}

int DrawMap(Map *map)
{
	int	x, y;

	for (y = 0; y < map->h; y++) {
		for (x = 0; x < map->w; x++) {
			move(y, x);
			addch(map->wall[y][x]);
		}
	}
	return (0);
}

int Discovery(Player *player)
{
	char	c[1];
	int	a;

	move(player->y, player->x);
	innstr(c, 1);
	if (c[0] == '*') {
		a = 1;
		return(a);			//発見された
	} else {
		a = 0;
		return(a);			//発見されてない
	}
}
```

一番苦戦したのは、ShowEnemyで、「ステージの壁があったら視界を表示しない」という処理です。

##その他ヘッダーファイル
```c:ctrlshow.h
#include "structure.h"

extern void CtrlPlayer(Player *player, Map *map, int key);
extern void CtrlEnemy(Enemy *enemy, int *i);
extern void ShowPlayer(Player *player);
extern void ShowEnemy(Enemy *enemy, Map *map, int *i);
extern int DrawMap(Map *map);
extern int Discovery(Player *player);
```

```c:load.h
#include <stdio.h>
#include "structure.h"

extern void Loadstage(FILE *stagedata, FILE *stagemap, Player *player, Map *map);
extern void Loadenemy(FILE *enemydata, Enemy *enemy);
```

```c:title.h
#include "structure.h"

extern int Warning(Img *img);
extern int Ocelot(Img *img);
extern int Title1(Img *img);
extern int Title2(Img *img);
```
externしかありません

#最後に
どうだったでしょうか？
高専3年生が、週一回のc言語の講義で学んだことを生かして作ったゲームです。
「高専のレベルはこんなもんか」とか、「こんなこと教えてんのか」とか、色々思ってくれると嬉しいです。
汚いコードで見苦しいところも多いと思いますが、私のレベルはこんなものです。

以下のリンクから、コードをダウンロードして遊ぶこともできます。
https://github.com/shindex/MetalEar