テキスト篇:第11章「構造体」
ゲーム開発演習:imageオブジェクトのプロパティ、画像の重ね合わせなど
参考:魔導士クラスの改良例(静的メンバの活用による引数付きコンストラクタの廃止・一本化)
//オブジェクト指向演習 RPG演習6・改
using System;
class Madoshi { //魔導士を表すクラス
//継承するデータメンバ
protected int mp; //MP(カプセル化(p.166)のためにpublicにしない)
protected int hp; //HP(〃)
protected static Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成
//継承しないデータメンバ
int id; //魔導士番号
static int num = 0; //魔導士の数
//デフォルトコンストラクタ
public Madoshi() {
mp = rnd.Next(10); //0~9の間のランダムな整数を得てMPの初期値とする
hp = 10 + rnd.Next(10); //10~19の間のランダムな整数を得てHPの初期値とする
num++; //【追加】魔導士の数をカウントアップ
id = num; //【変更】魔導士の数で魔導士番号を決める
}
//プロパティ
public int HP { //HPを返すプロパティ
get { return hp; }
}
//メソッド
public virtual void DispInfo() { //魔導士の情報を表示する仮想メソッド
Console.Write("魔導士{0}(HP={1} MP={2})", id, hp, mp); //情報表示(改行しない)
}
public int Fight() { //魔導士があなたを攻撃し、あなたのダメージ値を返す
if (hp <= 0) return 0; //死んでいる場合は0を返す
if (rnd.Next(2) == 1) { //確率1/2で魔法攻撃かどうかを選ぶ
int j = rnd.Next(5) + 1; //消費MPは1~5の乱数
DispInfo(); // 魔導士の情報を表示する
Console.WriteLine("は消費MP" + j + "の魔法を唱えた!");
if (mp < j) { // MP不足?
Console.WriteLine("MPが足りない!");
return 0; // あなたのダメージ=0を返す(攻撃終了)
} else { // MP充足?
Console.WriteLine("あなたは" + j * 4 + "のダメージを受けた!");
mp -= j; //MP消費
return j * 4; //あなたのダメージ=消費MPの4倍を返す(攻撃終了)
}
} else { //通常攻撃?
int k = rnd.Next(5) + 1; //攻撃力は1~5の乱数
DispInfo(); //魔導士の情報を表示する
Console.WriteLine("の攻撃!");
Console.WriteLine("あなたは" + k + "のダメージを受けた!");
return k; //あなたのダメージ=1~50を返す(攻撃終了)
}
}
public void Damage() { //魔導士があなたからダメージをくらう
int k = 5 + rnd.Next(5); //あなたの攻撃力は5~9の乱数
Console.WriteLine("あなたの攻撃!");
DispInfo(); //魔導士の情報を表示する(攻撃を受けた後の魔導士のHPを表示)
Console.WriteLine("に" + k + "のダメージを与えた!");
hp -= k; //魔導士のHPをダウン
}
}
class Daimado : Madoshi { //大魔導クラス(魔導士クラスの派生クラス)
public Daimado() { //コンストラクタ
mp = 40 + rnd.Next(10); //40~49の間のランダムな整数を得てMPの初期値とする
hp = 40 + rnd.Next(10); //40~49の間のランダムな整数を得てHPの初期値とする
}
public override void DispInfo() { //大魔導の情報を表示する
Console.Write("大魔導(HP={0} MP={1})", hp, mp); //情報表示(改行しない)
}
}
class minimadou { //ゲーム本体のクラス
public static void Main() { //ゲームを進行するメソッド
Random rnd = new Random(); //乱数用のRandomクラスのインスタンスを生成
string ans = null; //入力用
// 戦闘開始
int myhp = 40 + rnd.Next(21); //あなたのHPを40+0から40+20にする
Madoshi[] m = { new Madoshi(),new Madoshi(),new Madoshi() }; //【変更】全魔導士を生成
foreach (var w in m) { //全魔導士ついて繰返す
w.DispInfo(); //魔導士の情報を表示する
Console.WriteLine("が現れた。");
}
// 戦闘中(あなたが死ぬか,魔導士が死ぬまで繰り返す)
while (myhp > 0 && (m[0].HP > 0 || m[1].HP > 0 || m[2].HP > 0)) { //あなたとどれかの魔導士のHPがある間
Console.Write("残りHPは{0}です。どれを攻撃しますか?(1/2/3:魔導士1,2,3 0:やめる):", myhp);
ans = Console.ReadLine(); //回答を得る
int ansi = int.Parse(ans) - 1; //整数変換して-1し魔導士の添字0~2を得る
if (ansi == 0 || ansi == 1 || ansi == 2) { //魔導士を攻撃?
if (m[ansi].HP > 0) { //活きている魔導士?
m[ansi].Damage(); //魔導士を攻撃する
if(m[ansi].HP <= 0) { //魔導士[]を倒した?
Console.WriteLine("魔導士{0}を倒した!", ans);
}
} else {
Console.WriteLine("魔導士{0}はもういない!", ans);
}
for (int i = 0; i < 3 && myhp > 0; i++) { //全魔導士について繰返す
myhp -= m[i].Fight(); //魔導士の攻撃!あなたのHPをマイナス
}
} else { // やめる?
if (rnd.Next(2) == 0) { //確率50%で
Console.WriteLine("逃げることができた!");
break; // 繰り返し終了(p.125)
} else {
Console.WriteLine("逃げられない!");
for (int i = 0; i < 3 && myhp > 0; i++) { //全魔導士について繰返す
myhp -= m[i].Fight(); //魔導士の攻撃!あなたのHPをマイナス
}
}
}
if (myhp <= 0) { //あなたのHPがもうない?
Console.WriteLine("あなたは死にました。");
return; //ゲーム終了
}
}
if (ans != "0") { //逃げたのでなければ
Console.WriteLine("3人の魔導士を倒した!");
}
//ボス戦開始
Daimado d = new Daimado(); //大魔導を生成
d.DispInfo(); //大魔導の情報を表示する
Console.WriteLine("が現れた。");
int newhp = 40 + rnd.Next(10); //新しいHPを40から49で決める
if (myhp < newhp) { //回復する?
myhp = newhp; //HPを回復
Console.WriteLine("HPを回復!");
}
// 戦闘中(あなたが死ぬか,大魔導が死ぬまで繰り返す)
while (myhp > 0 && d.HP > 0) { //あなたと大魔導のHPがある間
Console.Write("残りHPは{0}です。攻撃しますか?(1:攻撃する 0:やめる):", myhp);
ans = Console.ReadLine(); //回答を得る
if (ans == "1") { //大魔導を攻撃?
d.Damage(); //大魔導を攻撃する
if(d.HP <= 0) { //大魔導を倒した?
Console.WriteLine("大魔導を倒した!");
break; //ゲーム終了
}
myhp -= d.Fight(); //大魔導の攻撃!あなたのHPをマイナス
} else { // やめる?
if (rnd.Next(2) == 0) { //確率50%で
Console.WriteLine("逃げることができた!");
break; //ゲーム終了
} else {
Console.WriteLine("逃げられない!");
myhp -= d.Fight(); //大魔導の攻撃!あなたのHPをマイナス
}
}
if (myhp <= 0) { //あなたのHPがもうない?
Console.WriteLine("あなたは死にました。");
break; //ゲーム終了
}
}
}
}
p.275 構造体とは
・C言語の構造体は複数の変数や配列を1まとめにしたデータ構造
・対して、C#の構造体は軽量クラス=クラスの仕様を一部除いて軽くしたもので、同時にC言語からの移植にも対応する
・よって「なにが除かれたか」に着目すると良い
・定義書式: struct 構造体名 {…}
・構造体のメンバはデータメンバ(クラスと同様)、メソッド、プロパティ、インデクサ等
・インターフェイスを実装できる
・実装の書式: struct 構造体名 : インターフェイス名,… {…}
・構造体の制限事項:
・他のクラスを継承できない
・データメンバの初期化ができない(利用する側で行うこと)
・引数のないコンストラクタは自動生成のみ(自前で記述は禁止)
・オブジェクトの扱いが参照型ではなく値型なので、代入するとコピーされる
p.276 struct01.cs
//p.276 struct01.cs
using System;
struct MyStruct { //構造体の定義
public int x; //宣言のみ可能で初期化はできない
public void show()
{
Console.WriteLine("x = {0}", x);
}
}
class struct01
{
public static void Main()
{
MyStruct ms; //構造体変数の宣言と構造体オブジェクトの生成(new不要)
ms.x = 10; //構造体変数.メンバ名で利用可
ms.show(); //構造体変数.メソッド名(…)で利用可
}
}
p.277 静的メンバを持つ構造体
・クラスの場合と全く同様 ・「構造体名.メンバ名」で利用できる ・また、静的メンバであれば初期化可能 ・よって、配列の静的メンバであれば要素の生成も可能
p.277 struct02.cs
//p.277 struct02.cs
using System;
struct MyStruct
{
public static int x = 10; //静的メンバであれば初期化可能
static int[] myarray = new int[10]; //静的メンバなので初期化(要素の生成)可能
public static void show() { //静的メソッド
Console.WriteLine("x = {0}", x);
}
}
class struct02
{
public static void Main()
{
MyStruct.show(); //構造体オブジェクトを生成せずに構造体名.メンバ名で利用可
MyStruct.x = 20; //同上
MyStruct.show(); //同上
}
}
アレンジ演習:p.277 struct02.cs
・利用していないmyarrayの要素をMainメソッドで利用してみよう ・その為にインデクサを追記しよう ・構造体に静的でない配列データメンバyourarrayを追加し、要素の生成はMainメソッドで行うようにしよう
作成例
//アレンジ演習:p.277 struct02.cs
using System;
struct MyStruct
{
public static int x = 10; //静的メンバであれば初期化可能
static int[] myarray = new int[10]; //静的メンバなので初期化(要素の生成)可能
public static void show() { //静的メソッド
Console.WriteLine("x = {0}", x);
}
public int this[int n] { //【追加】インデクサ
get { return myarray[n]; } set { myarray[n] = value; }
}
public int[] yourarray; //【追加】静的メンバではないので配列は宣言のみ
}
class struct02
{
public static void Main()
{
MyStruct.show(); //構造体オブジェクトを生成せずに構造体名.メンバ名で利用可
MyStruct.x = 20; //同上
MyStruct.show(); //同上
MyStruct a = new MyStruct(); //【追加】インデクサ用の構造体オブジェクトを生成
a[0] = 100; //【追加】インデクサ経由で代入
Console.WriteLine("a[0] = {0}", a[0]); //【追加】インデクサ経由で利用
a.yourarray = new int[3]; //【追加】要素の生成は利用側で行う
a.yourarray[0] = 200; //【追加】代入
Console.WriteLine("a.yourarray[0] = {0}", a.yourarray[0]); //【追加】利用
}
}
p.278 コンストラクタを持つ構造体
・引数のないコンストラクタは自動生成のみ(自前で記述は禁止) ・なお、オブジェクトの扱いが参照型ではなく値型なので、代入するとコピーされる
p.278 struct03.cs
//p.278 struct03.cs
using System;
struct MyStruct {
public int x;
//public MyStruct() { //引数のないコンストラクタは定義不可(自動生成のみ)
// x = 0;
//}
public MyStruct(int a) { //引数のあるコンストラクタは定義可能
x = a;
}
}
class struct03 {
public static void Main() {
MyStruct ms = new MyStruct(); //自動生成の引数のないコンストラクタを呼ぶ
Console.WriteLine("ms.x = {0}", ms.x); //ms.xは0で初期化されている
MyStruct ms2 = new MyStruct(100); //自前の引数のあるコンストラクタを呼ぶ
Console.WriteLine("ms2.x = {0}", ms2.x); //ms2.xは100で初期化されている
MyStruct ms3; //MyStruct型変数ms3を宣言(new不要)
ms3 = ms2; // ms2の値をms3に代入(値型なのでオブジェクトのコピーになる)
Console.WriteLine("ms3.x = {0}", ms3.x); // 当然ms3.xの値は100
ms3.x = 50; //ms3.xの値を50に変更
// ms3.xの値を50に変更してもms2.xは影響を受けない(コピーなので)
Console.WriteLine("ms2.x = {0}", ms2.x);
}
}
p.280 struct04.cs
//p.280 struct04.cs
using System;
struct MyStruct { //構造体の定義
public int x;
}
class MyClass { //クラスの定義
public int x;
}
class struct04 {
public static void Main() {
MyStruct ms1 = new MyStruct(); //構造体オブジェクトの生成
MyStruct ms2; //構造体変数の宣言
ms1.x = 20;
ms2 = ms1; //構造体オブジェクトをコピー(よってms1とms2は別のオブジェクトを指す)
Console.WriteLine("ms2.x = {0}", ms2.x); //コピーしたので20
ms2.x = 10; //コピー結果を書き換えても
Console.WriteLine("ms1.x = {0}", ms1.x); //コピーの元なので変わらず20
MyClass mc1 = new MyClass(); //クラスオブジェクトの生成
MyClass mc2; //参照変数の宣言
mc1.x = 20;
mc2 = mc1; //参照のコピーなのでmc1とmc2は同じオブジェクトを指す
Console.WriteLine("mc2.x = {0}", mc2.x); //同じオブジェクトなので20
mc2.x = 10; //参照経由で書き換えたので
Console.WriteLine("mc1.x = {0}", mc1.x); //元のオブジェクトの値が書き変わる
}
}