はじめに
RHEMS技研の平澤です!
IT企業でバイトしている高専生です。
今回の記事の目的は、
1.実際、高専生のレベルはどれくらいなのか?
2.高専情報分野の3年生で習った知識で、どれくらいのものが作れるのか?
というのをお伝えすることです。
先日のネットニュースでは、「高専生は日本の宝」などと言われていました。
その日本の宝の実情をさらけ出そうということです。
今回お見せするのは、週1時間で開講されているC言語の授業で習った知識で作ったゲームです。
ゲームの概要
c言語のcursesライブラリを使ってのゲーム制作です。
ゲームは端末で動かします。
某監督の代表作品を参考にしています。
ゲームの内容は、敵に見つからないように、スタート地点からゴール地点に向かうというもの。
実際のゲーム画面はこんな感じ。
プレイしている様子
Sはスタート位置、Gはゴール位置、$は自分の操作キャラ、@は敵キャラ
@の周りにある青い部分は敵の視界を表しています。
敵の視界に入らずにゴールまで行くとステージクリア、敵の視界に入るとゲームオーバーとなります。
どうやって動いている?
構造体を使っています。定義は以下のヘッダーファイルに書いています。
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
#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関数の大まかな流れは、
1.外部ファイルtitle.cから、タイトル画面とメニュー画面を表示する
2.キーボード入力で選択されたステージを、関数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を実行しています。
#################
## ##
## ##
## G ##
########### ##################################
## ##
## ##### ##
######################### ##### ##
## ##### ##
## ##
## ##
## ######################### ######################### ##
## ######################### ######################### ##
##### ######################### ######################### ##
##### ######################### ######################### ##
## ######################### ######################### ##
## ######################### ######################### ##
## ######################### ######################### ##
## ##
## ##
## ##
## ######################### ######################### ##
## ######################### ######################### ##
## ######################### ######################### ##
## ######################### ######################### ##
## ######################### ######################### ##
## ######################### ######################### ##
######## ######################### ######################### ##
## ### ##
## ##
## ##
## ##################################################### ##
## ###### ## ##
## ########### ## ###### ##
## ###### ## ## ############
## ###### ## ## ###### ##
## ## ## ##
## S ## ## ##
## ## ## ##
################### ###################
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〜5を高速で繰り返す
という感じです。
上にあるGIFと照らし合わせて言うと、関数game内のwhile文は、敵キャラが1マス動くごとに10回繰り返しています。
LoadStage、LoadEnemy、DrawMap、ShowPlayerなどの外部関数は、
ctrlshow.cとload.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で、「ステージの壁があったら視界を表示しない」という処理です。
その他ヘッダーファイル
#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);
#include <stdio.h>
#include "structure.h"
extern void Loadstage(FILE *stagedata, FILE *stagemap, Player *player, Map *map);
extern void Loadenemy(FILE *enemydata, Enemy *enemy);
#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