vol.12-1 ファイルの分割、リスト構造

目次

はじめに

メニュー画面の作成等の前に、ソースコードが肥大化してきたのでファイルの分割を行いたいと思います。これは理解しなくても今後問題はないので変更点のみ押さえておいてくださいな。
そして、vol.12-2で用いるリスト構造についての簡単な解説の引用を貼っておきます。活用しましょう。

ヘッダファイルとCファイル、extern宣言

ヘッダファイルと言うとstdio.hやtime.hなどがありますが、自作することができます。main関数を記述する以外に新たにCファイルを作成することも可能です。今回のゲーム作成ではグローバル変数を多用しているので、ファイル間でグローバル変数を使えるようにしないといけません。そもそもグローバル変数は1つのファイル内のみ有効な変数になるので、それを拡張するために新たに作成したCファイルで「extern宣言」というものをしてやります。

書式

extern データ型 データ名;

extern int x;

ここではint型のxを「宣言」するだけなので、定義(初期化)は別のファイルで行います。(今回の場合ならmain関数で初期化してやる。)定義では、

int x=0;

のように記述してあげればよいです。extern宣言は各変数に対して一度だけするようにしましょう。
自作ヘッダ(今回はgame.hとした)では、まず最初に#pragma onceと入力してやります。その後に#include...とすることによって二重includeによるエラーを起こらないようにしてくれます。今回はincludeするものを全てgame.hで管理するようにするので、game.hに使用する全てのヘッダを記述してやります。
game.hに記述する内容は、include指令マクロ(#define)指令列挙体定数(enum)構造体(typedef struct)関数のプロトタイプ宣言です。ヘッダファイルはとにかく宣言です。細かいことは別のところに書くので放置!
もう一つのCファイル(今回はfunc_def.cとした)には、冒頭に

#include "game.h"

と記述するのを忘れないようにしましょう。ここで関数の定義をしていくので必ず必要になります。また上述のように、グローバル変数の宣言はここで行ってやります。
main関数では、#include "game.h"としてinclude指令を一度で済むようにしました。グローバル変数の定義はここで行うので必ず行いましょう。初期化する必要のないと思っているグローバル変数も、裏側で適当な値が代入されて初期化が行われているので、例えばint areaCount;とするだけでもこれは定義になります。あくまでもfunc_def.cではextern「宣言」をしているので、いつものようにint型で宣言するのは「定義」することにあたります。

以下、ファイル毎のソースコード(一部省略)

---game.h---

#pragma once

#include...

#define...

enum {...};

//ルームの座標・横幅・高さをひとまとめに管理するために構造体ROOM_tで定義
typedef struct {...}ROOM_t;

/*
エリアの座標・横幅・高さをひとまとめに管理するために構造体AREA_tで定義
また、エリア内にルームを作成するのでルームもここで管理します。
orderRoom関数用にチェック項目を追加
*/

typedef struct {...}AREA_t;

typedef struct {...}PERSON_t;

typedef struct {...}ITEM_t;

int random(int, int);
void spritArea(int);
void generateField(void);
void orderRoom(int);
void setRandomIcon(int, int *, int *);
---func_def.c---

#include "game.h"

extern AREA_t areas[AREA_MAX]; //各エリアの座標・横幅・高さの情報をしまっておける
extern int areaCount; //エリアの個数
extern int field[FIELD_HEIGHT][FIELD_WIDTH]; //座標毎のフィールドの状態
extern int areaNumber[FIELD_HEIGHT][FIELD_WIDTH]; //座標に対応するエリア番号をしまっておける
extern char cellSymbol[CELL_COUNT][3];
extern int order[AREA_MAX]; //ルームをつなげる順番
extern int orderIndex; //order[]の添え字
extern int connectErrorCount; //エラー発見用

int random(int start, int end){...}

//エリアを分割して、エリア毎に座標・横幅・高さを取得する関数
//oldAreaIndex : 分割前のエリア番号
//newAreaIndex : 分割後のエリア番号
void spritArea(int oldAreaIndex){...}

void generateField(void){...}

void orderRoom(int areaIndex){...}

void setRandomIcon(int CELL_TYPE, int *x, int *y){...}

---main.c---

#include "game.h"

AREA_t areas[AREA_MAX]; //各エリアの座標・横幅・高さの情報をしまっておける
int areaCount; //エリアの個数
int field[FIELD_HEIGHT][FIELD_WIDTH]; //座標毎のフィールドの状態
int areaNumber[FIELD_HEIGHT][FIELD_WIDTH]; //座標に対応するエリア番号をしまっておける
char cellSymbol[CELL_COUNT][3] = {
	"・", //CELL_TYPE_NONE(0)番目の配列
	"■", //CELL_TYPE_WALL(1)番目の配列
	"@", //CELL_TYPE_PLAYER(2)番目の配列
	"巛", //CELL_TYPE_STAIRS(3)番目の配列
	"ア"  //4番目の配列にアイテムアイコン
};

int order[AREA_MAX]; //ルームをつなげる順番
int orderIndex = 0; //order[]の添え字
int connectErrorCount = 0; //エラー発見用

int main(void){...}

リスト構造

~データ構造(リストについて)~
データ系列の管理
①データの参照
②データの追加
③データの削除

【配列】
参照:添え字を用いて簡単に実現できる
追加、削除:1つずつずらす作業が必要

【リスト】
参照:先頭からたどっていく必要がある
追加、削除:ずらす必要がない

―リストとはー
データの追加や削除を低コストで実現できるデータ構造
セルをポインタで繋げて実現する

セル:要素とポインタ(次の要素)を格納している箱→一つのセルで一つのデータを実現する

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<conio.h>

//セルを表す構造体
struct element{
 int data; //要素
 struct element *next; //次の要素
};
//セルの作成
struct element *create(int dat) {
 struct element *p;

 //セルの領域を確保
 p = (struct element *)malloc(sizeof(struct element));

 p->data = dat;
 p->next = NULL;
 return p;
};
//(リストlは0番目)k番目の要素を返す
int access(struct element *l, int k)
{
 if (k > 1)
  return access(l->next, k - 1);
 else {
  return (l->next->data);
 }
}

//(リストlを0番目とする)k番目に要素datを追加
void insert(struct element *l, int k, int dat)
{
 struct element *p;

 if (k > 1)
  insert(l->next, k - 1, dat);
 else {
  p = create(dat);
  p->next = l->next;
  l->next = p;
 }
}
//(リストは0番目)k番目のリストを削除
void delete(struct element *l, int k)
{
 struct element *p;
 if (k > 1)
  delete(l->next, k - 1);
 else {
  p = l->next;
  l->next = l->next->next;
  free(p);
 }
}
//リストlから全て表示する
void print(struct element *l)
{
 while (l != NULL) {
  printf("%d ", l->data);
  l = l->next;
 }
 printf("\n\n");
}
//リストにデータを入力
void Input_element(struct element *l, int n)
{
 int dat;
 for (int i = 1; i < n; i++) {
  printf("%d番目:", i);
  scanf("%d", &dat);
  insert(l, i, dat);
 }
}

int main(void)
{
 int n,a;
 struct element *head;
 head = create(-1); //ダミーのセル
    
 Input_element(head, 6);
 print(head->next);

 insert(head, 2, 100);
 printf("2番目に100を追加してみた\n");
 print(head->next);

 delete(head, 3);
 printf("3番目を削除してみた\n");
 print(head->next);

 printf("3番目だけ表示してみた\n");
 a = access(head, 3);
 printf("%d\n", a);
 
 
 _getch();
 return 0;
}

―ダミーのセルとはー
データなしを表す
・探そうとする要素、追加しようとする空間、削除しようとする要素に直前の要素があることが保証される。

データの参照:access
・リストをたどって目的の一つ前のセルまでたどる
・次のセルのデータが参照すべきデータ

追加と削除については図がないと説明できない(語彙力がない)ので省略
[引用:LINEグループ「ゲームプログラミング」内 LINEノート 2019/7/17 14:56]

上述にある追加と削除についての解説図も追加しておきます。
データの追加:insert
f:id:sion2000114:20190907152344p:plain
データの削除:delete
f:id:sion2000114:20190907152508p:plain

おわりに

ここは準備段階になるので、次のvol.12-2からが本文になります。
では、お疲れ様でした。