C#お勉強物

アバター
TOMY
記事: 53
登録日時: 13年前
住所: 愛知県
連絡を取る:

C#お勉強物

投稿記事 by TOMY » 11年前

Unityでも使われるC#の為にちょっと勉強。
軽いコンソールじゃんけんプログラムを作ってみた。
初めての言語を触ると大体そうだが、とにかくエラーパラダイス&ソースが汚い。
特にC#は親切が故のエラーが多すぎて3時間位かかった。(予定だと1時間もあれば組めるかと思っていたのだが・・・)
C#はCと違ってエラー時のコードの種類を吐かない(c1234とかLNK1234というエラーの種類)のでエラーの原因を調べるのに苦労したり・・・
変数初期化せずに代入でエラー吐いたり・・・
staticやら、privateやら、public付けないと勝手にprivateと判断してエラー吐いたり・・・
まぁなれるまでの辛抱ですかな。
後、C#の教本に乗っていたモデリング技法と表記記法をつかってじゃんけんプログラムを設計したが、最終的に必要ないデータが出たりと波瀾万丈でした。
使ったモデリング技法はじゃんけんプログラムのような小規模なものですが、OOA(Object Oriented Approach)。
表記法はUML(Unified Modeling Language)です。
結構使い勝手が良かった。
添付ファイル

[拡張子 zip は無効化されているため、表示できません]


アバター
せんちゃ
記事: 50
登録日時: 14年前

Re: C#お勉強物

投稿記事 by せんちゃ » 11年前

クラスのメンバ変数がフルpublicなのは言語の知識以前の問題のような気がします。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 14年前

Re: C#お勉強物

投稿記事 by softya(ソフト屋) » 11年前

少なくとも就職作品を考えて目指されているのならカプセル化全面否定はまずいと思います。
テスト作品でもちゃんとやりましょう。
最後に編集したユーザー softya(ソフト屋) on 2014年6月01日(日) 20:41 [ 編集 1 回目 ]

YuO
記事: 947
登録日時: 14年前

RE: C#お勉強物

投稿記事 by YuO » 11年前

C#のコンパイラもちゃんとエラーコードを吐いていますし,VSの出力タブにもちゃんと表示されています。
エラー一覧ではもっと重要なエラー内容についてのみ書かれているのでエラーコードが書かれていませんが。


コードは……頑張りましょうレベルかなぁ,と。

例えば,CPUを1人から2人にしたら,Mainが大幅に書き換わります。
通常なら書き換わるのは勝敗を決める部分だけなのですが,そういうレベルでなくなってしまっています。

また,WinFormsでやるとなると,GameObject.csすら使い回せない,という残念な結果が待っています。
もちろん,このために入出力用にインターフェースを切って,とかやり出すと今回の目的には大がかりになりすぎるでしょうが,それすらTestable Codeという側面から見れば大がかりとも言い切れません。

あと,継承構造と無関係にあるGameData.Player.SetHandとGameData.Cpu.SetHandも何とかした方がよいですね。
GameData.PlayerとGameData.CpuはGameData.Duelistから継承しているのに,SetHandは独立して存在するのはコードを読んで最初に引っかかった点です。
ただし,現状においてGameData.Duelistにabstractなメソッドint SetHand (int)を用意されたら,余計に混乱しますが。

アバター
h2so5
副管理人
記事: 2212
登録日時: 14年前

Re: C#お勉強物

投稿記事 by h2so5 » 11年前

C#はほとんど書いたことないのですがリファクタリングしてみました。
3人以上の対戦にも対応していますが、IO関係を全部Mainに押し込めちゃったのであんまり綺麗じゃないですね...
► スポイラーを表示
最後に編集したユーザー h2so5 on 2014年6月02日(月) 10:58 [ 編集 3 回目 ]

アバター
TOMY
記事: 53
登録日時: 13年前
住所: 愛知県
連絡を取る:

Re: C#お勉強物

投稿記事 by TOMY » 11年前

せんちゃ さんが書きました:クラスのメンバ変数がフルpublicなのは言語の知識以前の問題のような気がします。
それくらいは知っています。ですが今回は動くこと前提で組みました。
private化するとエラーを吐きまくったのでとにかく動かすこと前提で直したらpublicまみれになりました。
なんでprivate化した変数をそのクラス内でしか使っていないはずなのにエラーで文句言われたんだろ・・・
ええ。自分のプログラミングスキルの低さがモロに出たいい例です。このままでは終われないので近いうちにリベンジしなくては・・・
なんかいつも使っている言語と微妙に使い勝手が違うと動かすだけで一苦労です・・・
最後に編集したユーザー TOMY on 2014年6月02日(月) 00:31 [ 編集 1 回目 ]

アバター
TOMY
記事: 53
登録日時: 13年前
住所: 愛知県
連絡を取る:

Re: C#お勉強物

投稿記事 by TOMY » 11年前

softya(ソフト屋) さんが書きました:少なくとも就職作品を考えて目指されているのならカプセル化全面否定はまずいと思います。
テスト作品でもちゃんとやりましょう。
カプセル化を全否定しているわけではないのですが、とにかくちょっとした事でエラー吐いてコンパイル通らないのは辛いです。
今までCでこういうふうに書いていたので

CODE:

class Hoge{
private:
    int m_a;
    int m_b;
public:
    void func1();
    int func2();
};
一度privateやpublic指定すると一々記載しなくて済むので、毎回全てにprivateやらpublicと記載する感覚が無いんです。
それにちょっとC#のエラーの条件がよくわからない。
まだ触って1日目なので何とかするしか無いですね・・・Unity触るためにC#の勉強を始めたんですし、最低限使い方とルールをシッカリ覚えておかないと・・・
最後に編集したユーザー TOMY on 2014年6月02日(月) 00:30 [ 編集 1 回目 ]

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 14年前

Re: C#お勉強物

投稿記事 by softya(ソフト屋) » 11年前

どっちかと言うとprivateやpublicのJava的な書き方なのですが、C++の書き方のほうがマイナーかと思います。

アバター
せんちゃ
記事: 50
登録日時: 14年前

Re: C#お勉強物

投稿記事 by せんちゃ » 11年前

今回のようにCPUとプレイヤーが存在するゲームではそれぞれを別々に作ってしまうのはよくやってしまいがちなのですが、じゃあどうやって作るんだよ!というとコントローラーという概念を抽象化します。

出す手を決める、勝敗はどうなのか、名前はなんなのか
ここまでは全員が共通して持ってても良いデータです。
違うのはプレイヤーにおこさせるアクションだけです。
なのでこの各コントローラのアクションをある共通のデータ形式に変換し、
そのデータに基づいてどのような動作をプレイヤーが行うのか、までを一本化してみると変更に強いプログラムになるのかなと思います。

ちょっと書いてみました

CODE:

	public enum Hand {
		Guu   ,
		Choki ,
		Paa   ,
	}
	public enum Result {
		DrawnGame ,
		Win       ,
		Lose      ,
	}

	public abstract class PlayerController {
		public enum COMMAND {
			DECIDE_GUU   ,
			DECIDE_CHOKI ,
			DECIDE_PAA   ,
		}
		public abstract COMMAND SelectCommand();
	}

	public class BattlePlayer {
		private Hand m_decideHand; 
		private string m_name;
		private Result m_battleResult;
		private PlayerController m_controller;
		public BattlePlayer( string name , PlayerController ctrl ){
			if( name == null ) throw new Exception( "名前を入力してください" );
			if( ctrl == null ) throw new Exception( "コントローラーが設定されていません" );
			m_decideHand = Hand.Guu;
			m_name = name;
			m_battleResult = Result.DrawnGame;
			m_controller = ctrl;
		}
		public void Decide(){
			m_decideHand = Hand.Guu;
			PlayerController.COMMAND selectedCommand = m_controller.SelectCommand();
			switch( selectedCommand ){
				case PlayerController.COMMAND.DECIDE_GUU   : m_decideHand = Hand.Guu;   break;
				case PlayerController.COMMAND.DECIDE_CHOKI : m_decideHand = Hand.Choki; break;
				case PlayerController.COMMAND.DECIDE_PAA   : m_decideHand = Hand.Paa;   break;
			}
		}
		public Result BattleResult {
			get{
				return m_battleResult;
			}
		}
		public string Name {
			get{
				return m_name;
			}
		}
		public Hand DecideHand {
			get{
				return m_decideHand;
			}
		}
		public void NotifyResult( Result result ){
			m_battleResult = result;
		}
	}

	public class CpuPlayer : PlayerController {
		private static Random g_Rand = new System.Random( Environment.TickCount );
		public override PlayerController.COMMAND SelectCommand(){
			COMMAND cmd = COMMAND.DECIDE_GUU;
			int rand = g_Rand.Next() % 3;
			switch( rand ){
				case 0 : cmd = COMMAND.DECIDE_GUU;   break;
				case 1 : cmd = COMMAND.DECIDE_CHOKI; break;
				case 2 : cmd = COMMAND.DECIDE_PAA;   break;
			}
			return cmd;
		}
	}

	public class HumanPlayer : PlayerController {
		public override PlayerController.COMMAND SelectCommand(){
			ConsoleKeyInfo info = Console.ReadKey(true);

			switch( info.Key ){
				case ConsoleKey.Z : return COMMAND.DECIDE_GUU;
				case ConsoleKey.X : return COMMAND.DECIDE_CHOKI;
				case ConsoleKey.C : return COMMAND.DECIDE_PAA;
				default : throw new Exception( "Z,X,C以外のキーは対応していません!" );
			}
		}
	}

	/*
	 * バトルの処理を行う
	 */
	public class BattleManager {
		private List m_guuPlayer;
		private List m_chokiPlayer;
		private List m_paaPlayer;
		private List m_playerList;

		public BattleManager(){
			m_guuPlayer = new List();
			m_chokiPlayer = new List();
			m_paaPlayer = new List();
			m_playerList = new List();
		}

		/*
		 * 対戦結果をプレイヤーに通知
		 */
		private static void NotifyBattleResult( List playerList , Result result ){
			foreach( BattlePlayer player in playerList ){
				player.NotifyResult( result );
			}
		}
		/*
		 * 全員が出した手で
		 * もっとも一番多くだされた手の数を返す
		 */
		private int GetMaxHandList(){
			int max = m_guuPlayer.Count;
			if( m_chokiPlayer.Count >= max ){
				max = m_chokiPlayer.Count;
			}
			if( m_paaPlayer.Count >= max ){
				max = m_paaPlayer.Count;
			}
			return max;
		}

		/*
		 * バトル開始
		 * 各プレイヤーは出す手を決定する
		 */
		public void BattleStart(){
			foreach( BattlePlayer player in m_playerList ){
				player.Decide();
			}
		}

		/*
		 * バトル判定
		 */
		public void JudgeBattle(){
			m_guuPlayer.Clear();
			m_chokiPlayer.Clear();
			m_paaPlayer.Clear();
			foreach( BattlePlayer player in m_playerList ){
				List resultHandList = null;
				Hand playerDecidedHand = player.DecideHand;
				switch( playerDecidedHand ){
					case Hand.Guu   : resultHandList = m_guuPlayer;   break;
					case Hand.Choki : resultHandList = m_chokiPlayer; break;
					case Hand.Paa   : resultHandList = m_paaPlayer;   break;
					default : throw new Exception( "不正な手です : " + playerDecidedHand );
				}
				resultHandList.Add( player );
			}
			/*
			 * グー・チョキ・パーが出た : 引き分け
			 */
			if( m_guuPlayer.Count > 0 && m_chokiPlayer.Count > 0 && m_paaPlayer.Count > 0 ){
				NotifyBattleResult( m_playerList , Result.DrawnGame );
				return;
			}
			/*
			 * 全員同じ手を出している : 引き分け
			 */
			if( m_playerList.Count == GetMaxHandList() ){
				NotifyBattleResult( m_playerList , Result.DrawnGame );
				return;
			}
			/*
			 * パーとチョキ
			 * 勝ち:チョキ
			 * 負け:パー
			 */
			if( m_guuPlayer.Count == 0 ){
				NotifyBattleResult( m_chokiPlayer , Result.Win );
				NotifyBattleResult( m_paaPlayer   , Result.Lose );
				return;
			}
			/*
			 * パーとグー
			 * 勝ち:パー
			 * 負け:グー
			 */
			if( m_chokiPlayer.Count == 0 ){
				NotifyBattleResult( m_paaPlayer   , Result.Win );
				NotifyBattleResult( m_guuPlayer   , Result.Lose );
				return;
			}
			/*
			 * グーとチョキ
			 * 勝ち:パー
			 * 負け:チョキ
			 */
			if( m_paaPlayer.Count == 0 ){
				NotifyBattleResult( m_guuPlayer   , Result.Win );
				NotifyBattleResult( m_chokiPlayer , Result.Lose );
				return;
			}
		}

		/*
		 * プレイヤー登録
		 */
		public void EntryPlayer( BattlePlayer player ){
			m_playerList.Add( player );
		}

		/*
		 * 対戦結果表示
		 */
		public void DumpResult(){
			foreach( BattlePlayer player in m_playerList ){
				Console.Write( player.Name + ":" );
				switch( player.BattleResult ){
					case Result.Win :
						Console.Write( "勝ち" );
						break;
					case Result.Lose :
						Console.Write( "負け" );
						break;
					case Result.DrawnGame :
						Console.Write( "あいこ" );
						break;
				}
				Console.WriteLine( "[出した手={0}]" , player.DecideHand );
			}
		}
	}


	class Program {
		static void Main( string[] args ){
			BattleManager battleMan = new BattleManager();
			battleMan.EntryPlayer( new BattlePlayer( "あなた" ,  new HumanPlayer() ) );
			battleMan.EntryPlayer( new BattlePlayer( "CPU:2"  ,  new CpuPlayer() ) );
			//battleMan.EntryPlayer( new BattlePlayer( "CPU:3"  ,  new HumanPlayer() ) );
			//battleMan.EntryPlayer( new BattlePlayer( "CPU:4"  ,  new CpuPlayer() ) );

			battleMan.BattleStart(); // 各プレイヤーは出す手を決める
			battleMan.JudgeBattle(); // 勝敗判定
			battleMan.DumpResult();  // 勝敗結果表示
		}
	}
対戦人数は特に気にする必要がありませんが
人間が2人になっても全員CPUでも問題なく動作します。
CPUの手を出すロジックを乱数ではない別のロジックにしたとしても修正は一箇所です。

この考え方は格闘ゲームやシューティングゲームでも同じことです。

アバター
TOMY
記事: 53
登録日時: 13年前
住所: 愛知県
連絡を取る:

Re: C#お勉強物

投稿記事 by TOMY » 11年前

softya(ソフト屋) さんが書きました:どっちかと言うとprivateやpublicのJava的な書き方なのですが、C++の書き方のほうがマイナーかと思います。
ずーっとCとC++ばっかりやってた自分にとってはこれが基準でした・・・
javaで組む機械なんて殆どなかったですからね・・・
そっかぁ・・・マイナーなのか・・・

アバター
TOMY
記事: 53
登録日時: 13年前
住所: 愛知県
連絡を取る:

Re: C#お勉強物

投稿記事 by TOMY » 11年前

せんちゃさん。わざわざソースコードの提示までしてレクチャーしてくれてありがとうございます。
今はちょっと時間ないのですぐには取り掛かれませんが時間ができ次第解読します。