2013年6月19日水曜日

ニコ生とC言語

ペアプログラミング気分を味わいたいときはニコ生のプログラミング配信をよく見ています。

 ニコニコ生放送
 http://live.nicovideo.jp


「開発」や「プログラミング」で検索すると、意外と出てきます。
趣味でコードを書き始めたおじさん、Unity でバリバリゲームを作っているお兄さん、「やさしいC」と格闘する中学生まで様々です。
どんなことでも人に見せることを意識するとモチベーションが上がりますよね。

放送中に Stypi などのリアルタイムで共有できるエディタを使ってみんなでわいわいコードを書いたりすると結構楽しいものです。

 Stypi
 https://code.stypi.com/

そこで放送中に C 言語のコンソールゲーム書きました。
wasd で移動してアイテムを集めます。敵に当たったらゲームオーパーです。



C言語を触り始めた頃はよくこんなかんじのゲーム作りましたよね。
/**
 * ダンジョンRPG
 * C言語でダンジョンRPG的なものを作る
 *
 * - 画面の見方 -
 * @ : 主人公
 * X : 敵
 * * : アイテム
 *
 * - ルール -
 * 敵に当たらずにアイテムを集めろ
 *
 * - 操作方法 -
 * w: 上
 * a: 左
 * s: 下
 * d: 右
 */
#include <stdio.h>
#include <stdlib.h>

// プロトタイプ宣言
void showMap();
void showMessage(char message[]);
int getKey();
void clear();
void moveTo(int x, int y);
void setItems();
void initialize();
void gameClear();
void setEnemy();
void pickup();
void moveEnemies();
void gameOver();

// マップサイズ
const int WIDTH = 30;
const int HEIGHT = 10;

// アイテムの数
const int ITEMNUM = 10;

// キー操作
const char UP = 'w';
const char LEFT = 'a';
const char DOWN = 's';
const char RIGHT = 'd';

// 面倒くさいから関数間データ共有はグローバル
char map[HEIGHT][WIDTH];
int playerX = 5;
int playerY = 1;
int collected = 0; // 拾ったアイテムの数
int gameover = 0; // ゲームオーバーになったかどうか

int main(int argc, const char *argv[]) {
    int key;

    // 初期化
    initialize();

    // メインループ
    while(gameover == 0) {
        // 画面の更新
        clear();
        showMap();

     // アイテムを全部拾ったらゲームクリア
        if(collected >= ITEMNUM) {
            gameClear();
            break;
        }

        // メッセージ出力
        showMessage("  コマンド入力後に Enter \n  上:w  左:a  下:s 右:d");

        // プレイヤーの入力
        key = getKey();
        
        // データの更新
        switch(key) {
        case UP:
            moveTo(0, -1);
            break;
        case DOWN:
            moveTo(0, 1);
            break;
        case RIGHT:
            moveTo(1, 0);
            break;
        case LEFT:
            moveTo(-1, 0);
            break;
        default:
            // それ以外のキーが押されたら次の入力をまつ 
            continue;
        }

        // 敵を動かす
        moveEnemies();
    }
}

/**
 * ゲームの初期化
 * @function
 */
void initialize() {
    int i, j;

    // マップ初期化
    for(i = 0; i < HEIGHT; i++) {
        for(j = 0; j < WIDTH; j++) {
            map[i][j] = ' ';
        }
    }

    // 主人公の配置
    map[playerY][playerX] = '@';
    
    // アイテムの配置
    setItems();
}


/**
 * アイテムを拾う
 * ついでに敵が出現する
 * @function
 */
void pickup() {
    collected++;
    setEnemy();
}

/**
 * ゲームクリア
 * @function
 */
void gameClear() {
    showMessage("すべてのアイテムを集めた!\nゲームをクリアした!おめでとう!");
}

/**
 * 敵を1対配置する
 * @function
 */
void setEnemy() {
    int x, y;
    do {
        x = rand() % WIDTH;
        y = rand() % HEIGHT;
    } while(map[y][x] != ' ');
    map[y][x] = 'X';
}

/**
 * 敵を動かす
 * ランダム方向に移動しようとしてアイテムにt回阻まれたら動かない
 * @function
 */
void moveEnemies() {
    int t = 1;
    int i, j, k;
    int dx, dy;
    int count = 0;
    int enemyCount = 0;
    int nowEnemyNumber = 0;
    int duplicated;

    // 既に動いた敵の移動先座標リスト
    // 長さが ITEMNUM なのは敵がアイテム数以上出現しないため
    // 同じ敵が2回以上動くことを防止する
    int movedListX[ITEMNUM];
    int movedListY[ITEMNUM];
    
    // 敵を数える
    for(i = 0; i < HEIGHT; i++) {
        for(j = 0; j < WIDTH; j++) {
            if(map[i][j] == 'X') {
                enemyCount++;
            }
        }
    }

    // 既に動いた敵リストを初期化
    for(i = 0; i < ITEMNUM; i++) {
        movedListX[i] = -1;
        movedListY[i] = -1;
    }

    // 敵を順番に動かす
    for(i = 0; i < HEIGHT; i++) {
        for(j = 0; j < WIDTH; j++) {
            if(map[i][j] == 'X') {
                
                // 既に動かした敵リストに登録されているものは動かさない
                duplicated = 0;
                for(k = 0; k < nowEnemyNumber; k++) {
                    if(i == movedListY[k] && j == movedListX[k]) {
                        duplicated = 1;
                        break;
                    }
                }
                if(duplicated) {
                    continue;
                }
                
                do {
                    dx = (rand() % 2) == 0 ? 1 : -1;
                    dy = (rand() % 2) == 0 ? 1 : -1;

                    if(i + dy < 0 || HEIGHT < i + dy || j + dx < 0 || WIDTH < j + dx) {
                        continue;
                    }

                    count++;
                } while(map[i + dy][j + dx] != ' ' && map[i + dy][j + dx] != '@' && count < t);

                if(map[i + dy][j + dx] == '@') {
                    map[i][j] = ' ';
                    map[i + dy][j + dx] = 'X';
                    movedListX[nowEnemyNumber] = j + dx;
                    movedListY[nowEnemyNumber] = i + dy;
                    gameOver();
                } else if(map[i + dy][j + dx] == ' ') {
                    map[i][j] = ' ';
                    map[i + dy][j + dx] = 'X';
                    movedListX[nowEnemyNumber] = j + dx;
                    movedListY[nowEnemyNumber] = i + dy;
                } else {
                    // 動かない
                }

                // 動かした敵を数えておく
                nowEnemyNumber++;
            }
        }
    }
}

/**
 * 敵に触ってゲームオーバー
 * @function
 */
void gameOver() {
    clear();
    showMap();
    showMessage("敵に襲われてゲームオーバー!");
    gameover = 1;
}

/**
 * アイテムの配置
 * マップ内にランダムにアイテムを配置する
 * @function
 */ 
void setItems() {
    int x, y, i;
    for(i = 0; i < ITEMNUM; i++) {
        x = rand() % WIDTH;
        y = rand() % HEIGHT;
        if(map[y][x] == ' ') {
            map[y][x] = '*';
        } else {
            i--;
            continue;
        }
    }
}

/**
 * プレイヤーを現在地から移動させる
 * @function
 * @param {int} x X方向の移動距離
 * @param {int} y Y方向の移動距離
 */
void moveTo(int x, int y) {
    // 外の世界に出ないようにする
    if(playerX + x < 0 || WIDTH < playerX + x || playerY + y < 0 || HEIGHT < playerY + y) {
        return;
    }
    
    // 移動前の主人公を消す
    map[playerY][playerX] = ' ';
    
    // 座標の更新
    playerX += x;
    playerY += y;
    
 // 移動先にアイテムがあれば拾う
    if(map[playerY][playerX] == '*') {
        pickup();
    }

    // 移動後の主人公を追加
    map[playerY][playerX] = '@';
}

/**
 * プレイヤーからのキー入力を受け付ける
 * @function
 * @returns {int} プレイヤーが押したキー
 */
int getKey() {
    printf("コマンド? => ");
    return getchar();
}

/**
 * 画面を消す
 * @function
 */
void clear() {
    int i;
    for(i = 0; i < 30; i++) {
        putchar('\n');
    }
}

/**
 * 枠線を表示(メッセージ用)
 * @function
 */
void line() {
    int i;
    putchar('+');
    for(i = 0; i < WIDTH - 2; i++) {
        putchar('-');
    }
    putchar('+');
    putchar('\n');    
}

/**
 * 画面下部にメッセージ表示
 * @function
 * @param {char[]} 表示するメッセージ
 */
void showMessage(char message[]) {
    line();
    puts(message);
    printf("  アイテム( %d / %d )\n", collected, ITEMNUM);
    line();
}

/**
 * マップの出力
 * @function
 */
void showMap() {
 int i, j;
    for(i = 0; i < HEIGHT; i++) {
        for(j = 0; j < WIDTH; j++) {
            putchar(map[i][j]);
        }
        putchar('\n');
    }
    map[playerY][playerX] = '@';
}

0 件のコメント:

コメントを投稿