LoginSignup
1

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-02-08

はじめに

RHEMS技研の平澤です!
IT企業でバイトしている高専生です。

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

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

ゲームの概要

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

  • タイトル画面
    スクリーンショット 2019-02-08 18.01.03.png

  • メニュー画面
    スクリーンショット 2019-02-08 18.00.27.png

  • プレイしている様子
    stage1.gif
    Sはスタート位置、Gはゴール位置、$は自分の操作キャラ、@は敵キャラ
    @の周りにある青い部分は敵の視界を表しています。
    敵の視界に入らずにゴールまで行くとステージクリア、敵の視界に入るとゲームオーバーとなります。

どうやって動いている?

ゲームが置いてあるディレクトリの構成はこんな感じ。
スクリーンショット 2019-02-08 14.49.24.png

構造体を使っています。定義は以下のヘッダーファイルに書いています。

structure.h

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

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を実行しています。

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

title.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

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で、「ステージの壁があったら視界を表示しない」という処理です。

その他ヘッダーファイル

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);
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);
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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1