【CardTable】cardtable.dllのソースコードはいかがだったでしょうか?
その後にこのDLLを使ったアプリである「神経衰弱(Concentration.exe)」のソースを解説するつもりだったのですが、何気なく手を出した事にどっぷりと嵌り、現在そっちをやっつけています。
何かって?
ちょっとだけよー。
(仕様では最下段の配牌13枚も伏せ牌になりますよ。)
【CardTable】cardtable.dllのソースコードはいかがだったでしょうか?
その後にこのDLLを使ったアプリである「神経衰弱(Concentration.exe)」のソースを解説するつもりだったのですが、何気なく手を出した事にどっぷりと嵌り、現在そっちをやっつけています。
何かって?
ちょっとだけよー。
(仕様では最下段の配牌13枚も伏せ牌になりますよ。)
前回予告しましたように、今回からはソースコードの解説をしてゆきます。(未だにフリーのVisual Studioを使わない私の)開発環境はWindows 11に同梱のC# 5.0とBCCSkeltonで自作したMSCompAss、ResourceWriter、ResourceReaderです。
【CardTable】トランプゲーム用の汎用DLLを考える(1)と【CardTable】トランプゲーム用の汎用DLLを考える(2)で書いた大まかな仕様に基づき、テストプログラムで動作テストを行い、「神経衰弱(Concentration)」で更に修正を積み重ねたものが以下のコードです。いつものように、インラインで解説をしてゆきます。
【cardtable.cs】
///////////////////////////////////////////////////////////
// CardTable.cs
// Copyright (c) by Ysama September, 2024
// Free Images come from:
// https://www.civillink.netfsozaitramp.html (表面) and
// https://depositphotos.com/jp/vectorsトランプの裏面.html
///////////////////////////////////////////////////////////
using System;
using System.Collections; //解説:これと↓は配列等
using System.Collections.Generic; //を扱う時は必須ですよね。
using System.Drawing;
using System.Windows.Forms;
using System.Reflection; //Assemblyを使う為
using System.Resources; //リソース関係クラス等の使用の為
namespace cardtable
{
////////////////////////////////
//CardTable(トランプ台)クラス
////////////////////////////////
public class CardTable : Panel //ピクチャー(ボックス)の枠となるPanel(解説:Panelコントロールから派生させています。)
{
//メンバーフィールド(変数)
PictureBox picBox; //ピクチャーボックスコントロール(解説:Panelに貼り付ける描画用です。)
Bitmap pbCanvas; //貼り付ける描画用ビットマップ(解説:picBoxに貼り付ける描画用です。)
Graphics cvsGr; //pbCanvasの描画ハンドル(hDC)(解説:pbCanvasの描画に使います。)
//メンバープロパティ
public int cvsWidth {get; private set;} //pbCanvasの幅
public int cvsHeight {get; private set;} //pbCanvasの高さ
public int msX {get; private set;} //マウスが押された時のx座標
public int msY {get; private set;} //マウスが押された時のx座標
public MouseButtons msButton {get; private set;} //押されたマウスボタン
public bool smallCards {get; set;} //カードサイズ(初期値 false)
public Card[] Deck {get; private set;} //シャッフルされた後の「山」(Cardインスタンス配列)
public int DealtCards {get; private set;} //「山」から配られたカードの数
public CardTable(int W = 988, int H = 714, bool Joker = true) //コンストラクター
//解説:この988と714はカードイメージのビットマップから私の環境に合わせて設定しました。
//尚、Jokerはジョーカー使用フラグで、既定値は「使用する」です。
{
//メンバープロパティの初期化(自動実装はC# 6.0から)解説:従ってC# 5,0では宣言で値を与えられません。
cvsWidth = W; //pbCanvasの幅(初期値暫定-(幅72 + 4) x 13枚)
cvsHeight = H; //pbCanvasの高さ(初期値暫定-(高115 + 4) x 6段)
Deck = new Card[53]; //Card配列の添字配列の初期化(数札52枚 + Joker)
//CardTable(Panel)の初期化
this.Size = new Size(640, 480); //初期値は描画範囲よりも小さく設定
this.MinimumSize = new Size(640, 480); //初期値を最小値とする(暫定)
this.MaximumSize = new Size(cvsWidth + 8, cvsHeight + 8); //"+8"はスクロールバーマージン
this.BorderStyle = BorderStyle.Fixed3D; //解説:単なる趣味です。(プログラミングは枠幅が出るので面倒)
this.AutoScroll = true; //スクロールバー付
this.BackColor = Color.Green; //解説:トランプ台は緑でしょう?
//描画ビットマップとデバイスコンテキストの生成
pbCanvas = new Bitmap(cvsWidth, cvsHeight);
//参考:画面サイズ(System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width,
// System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height);
//解説:最初これに使用かと悩みました。
cvsGr = Graphics.FromImage(pbCanvas);
//ピクチャーボックスコントロール
picBox = new PictureBox();
picBox.Location = new Point(0, 0); //枠内の位置
picBox.SizeMode = PictureBoxSizeMode.AutoSize; //画像サイズでPictureBoxの大きさが変更される
picBox.Image = pbCanvas; //pbCanvasを貼る
picBox.Click += Panel_Click; //picBox(即ちそのコンテナーであるthis)のクリックイベント
this.Controls.Add(picBox); //picBoxを子にする
//Cradクラスインスタンスの初期化とシャッフル
Shuffle(Joker);
}
~CardTable() //デストラクター(解説:アンマネージドリソースの処理に必要ですね。)
{
cvsGr.Dispose(); //pbCanvasの描画ハンドル廃棄
pbCanvas.Dispose(); //描画用ビットマップ廃棄
}
//CardTable上のクリックイベント(picBox→Panelへ伝達)
private void Panel_Click(object sender, EventArgs e)
{
Point p = this.PointToClient(Cursor.Position);
msX = p.X; //クリックされた際のX座標を記録
msY = p.Y; //クリックされた際のY座標を記録
msButton = (e as MouseEventArgs).Button; //クリックされたボタンを記録
this.OnClick(e); //picBoxからPanelのClickイベントを呼び出す(解説:このように伝達します)
}
//Cardインスタンス配列の初期化
private void InitCards()
{
//数札
for(int i = 0; i < 4; i++)
{
for(int j = 0; j < 13; j++)
{
Deck[i * 13 + j] = new Card();
Deck[i * 13 + j].Suit = i; //"Clover" - 0, "Diamond" - 1, "Heart" - 2, "Spade" - 3
Deck[i * 13 + j].Number = j + 1; //1 - 13(BackとJokerでは0)
Deck[i * 13 + j].Owner = 0; //0は一人ゲームまたは未配布を意味する
Deck[i * 13 + j].Shown = false;
Deck[i * 13 + j].Discarded = false;
}
}
//Joker
Deck[52] = new Card();
Deck[52].Suit = 4; //"Joker" - 4
Deck[52].Number = 0;
Deck[52].Owner = 0;
Deck[52].Shown = false;
Deck[52].Discarded = false;
DealtCards = 0;
}
//Cardインスタンス配列のシャッフル
public void Shuffle(bool Joker)
{
int n = Joker ? 53 : 52; //Jokerを入れるなら53枚、入れないなら52枚でシャッフル
//カード配列の初期化
InitCards();
//乱数の初期化(発生パターンが時間により変化する)
Random rand = new Random((int)DateTime.Now.Ticks & 0x0000FFFF);
while(n > 1) //カードが2枚になった回が最後
{
n--; //'0'ベースの配列の最後を指す
int m = rand.Next(n + 1); //n + 1より小さい0 以上の整数
Card temp = Deck[m]; //解説:交換のための暫定Cardインスタンス
Deck[m] = Deck[n];
Deck[n] = temp;
}
}
//CardTable(picBox)を緑色でクリアする
public void Clear()
{
cvsGr.Clear(Color.Green);
}
//Cardインスタンスの表示面指定
public void TurnFace(int cardNo, bool face)
{
Deck[cardNo].Shown = face; //解説:trueがturn up(表)、falseがturn down(裏)
}
//デフォルトサイズ(cvsWidth = 76 x 13, cvsHeight = 119 x 6)のグリッド用
//コンストラクターでサイズを変更する場合には変更を要する
public int GeGridtNo(int x, int y)
{
int offset = smallCards? 11 : 2;
if((x - offset < 0 || (x - offset) > 76 * 13) //グリッドサイズ:W = 76dots (72 + 4)
|| (y - offset < 0 || (y - offset) > 119 * 6)) //グリッドサイズ:H = 119dots (115 + 4)
return -1; //エラー
else
return ((y - offset) / 119) * 13 + ((x - offset) / 76); //解説:0~51(13 x 4)の解列添字を返します。
}
//Cardインスタンスの指定位置表示
//位置指定はクライアント座標、または横13 x 縦6のグリッド座標
//グリッドサイズ:W = 76dots (72 + 4)、H = 119dots (115 + 4)
//offsetサイズ :大カード-2、小カード-11
public void ShowCard(int cardNo, int x, int y, bool grid = false)
{
//本プログラムの埋め込みリソースのリソースマネージャーを作成
Assembly asm = Assembly.GetExecutingAssembly();
ResourceManager rm = new ResourceManager("Cards", asm); //解説:Cards.resourcesのリソース
//ピクチャーボックス描画用ビットマップ
Bitmap bm;
//リソースID名の作成
string[] suit = new string[] {"Clover", "Diamond", "Heart", "Spade", "Joker"};
string name = "Joker";
if(Deck[cardNo].Suit != 4) //解説:Jokerでなければ「数札("(スート名)01~13")にします。
name = suit[Deck[cardNo].Suit] + String.Format("{0:00}", Deck[cardNo].Number);
if(Deck[cardNo].Shown)
//Cardインスタンスのビットマップ(解説:表面)を読み込む
bm = (Bitmap)rm.GetObject(name);
else
//裏面のビットマップを読み込む
bm = (Bitmap)rm.GetObject("Back");
bm.MakeTransparent(); //解説:カードイメージの背景色(緑)を透明化します。
//【参考】オリジナルサイズ:cvsGr.DrawImage(bm, x, y, bm.Width, bm.Height); //108 x 172
if(grid) //解説:グリッド表示の場合
{
int offset = smallCards? 11 : 2;
if(smallCards) //解説:イメージ小
cvsGr.DrawImage(bm, x * 76 + offset, y * 119 + offset, bm.Width / 2, bm.Height / 2); //54 x 86
else //解説:イメージ大
cvsGr.DrawImage(bm, x * 76 + offset, y * 119 + offset, bm.Width * 2 / 3, bm.Height * 2 / 3); //72 x 115(114.67)
}
else //解説:クライアント座標表示の場合
{
if(smallCards)
cvsGr.DrawImage(bm, x, y, bm.Width / 2, bm.Height / 2); //54 x 86
else
cvsGr.DrawImage(bm, x, y, bm.Width * 2 / 3, bm.Height * 2 / 3); //72 x 115(114.67)
}
//ビットマップを廃棄
bm.Dispose();
//pbCanvasを貼る
picBox.Image = pbCanvas;
}
//Cardインスタンスの消去
//位置指定はクライアント座標、または横13 x 縦6のグリッド座標
//グリッドサイズ:W = 76dots (72 + 4)、H = 119dots (115 + 4)
//offsetサイズ :大カード-2、小カード-11
public void RemoveCard(int cardNo, int x, int y, bool grid = false)
{
int Width = 108;
int Height = 172;
//smallCard - true (54 x 86), false (72 x 115(114.67))
//【参考】オリジナルサイズ:Width x Height = 108 x 172
if(grid) //解説:Showメソッドと足並みをそろえています。
{
int offset = smallCards? 11 : 2;
if(smallCards)
cvsGr.FillRectangle(Brushes.Green, x * 76 + offset, y * 119 + offset, Width / 2, Height / 2);
else
cvsGr.FillRectangle(Brushes.Green, x * 76 + offset, y * 119 + offset, Width * 2 / 3, Height * 2 / 3);
}
else
{
if(smallCards)
cvsGr.FillRectangle(Brushes.Green, x, y, Width / 2, Height / 2);
else
cvsGr.FillRectangle(Brushes.Green, x, y, Width * 2 / 3, Height * 2 / 3);
}
//Discardedサインを立てる
Deck[cardNo].Discarded = true;
//pbCanvasを貼る
picBox.Image = pbCanvas;
}
//Cardインスタンス配列からカードを配る
public void Deal(int no_of_players, int no_of_cards)
{
for(int i = 0; i < no_of_cards; i++)
for(int j = 0; j < no_of_players; j++)
Deck[i * no_of_players + j].Owner = j + 1; //解説:Ownerを指定
DealtCards += no_of_players * no_of_cards; //解説:Deck配列の添字を配布数進めます
}
//Cardインスタンス配列からカードを一枚引く
public int Draw(int player)
{
Deck[DealtCards++].Owner = player; //解説:DealtCards.Ownerを指定後、インクリメントします。
return (53 - DealtCards); //戻り値は残カード数(0ならゲーム終了)
}
}
/////////////////////////
//Card(トランプ)構造体
/////////////////////////
public struct Card //合計16バイト
{
public int Suit {get; set;} //"Clover" - 0, "Diamond" - 1, "Heart" - 2, "Spade" - 3, "Joker" - 4
public int Number {get; set;} //1 - 13(BackとJokerでは0)
public int Owner {get; set;} //1から始まるオーナー(プレーヤー)番号-"0"は一人ゲームまたは未配布を意味する
public bool Shown {get; set;} //表示を表にするか、裏にするかのフラグ(裏の場合は常にBackを表示)
public bool Discarded {get; set;} //捨てられた(以降はゲームで使われない)が否かのフラグ
}
}
如何だったでしょうか?自分としてはスッキリさせたという認識です。後はこのコードをコンパイルするだけですが、MSCompAssなら、
出力ファイルをLibrary(DLL)にし、リソース(ファイル)を指定してコンパイルするだけです。↓のようにoptフィルとDLLファイルが出来ます。
ps. なお、Vectorで配布しているBCCForm and BCCSkeltonパッケージのSampleBCCSkelton\MSCompAss\Debug\Samples\CardTableフォールダーにソースとリソースを入れておきましたので、それを使うことでDLLが出来ます。
ps of ps. 何故だか私の環境では冒頭の文字が大きくなっています。再度フォントを直して、編集画面で適切に表示されても治らないのでそのままにしました。
前回、トランプゲーム用の汎用DLL、CardTableのテストの為に、それを使ったアプリを作ってみようかな、と書きましたが、思いの他簡単に(注)できましたので、
ご紹介するとともに少し開発上の備忘を書いておきます。(CardTableとアプリ第1号となるConcentrationのコードは別途解説します。)
注:偉そうなことを言っていますが、テスト用アプリ第一号は(恐らくカードゲームで)最も簡単な「神経衰弱(英語ではConcentrationとか、Memoryというそうです)」にしました。
先ずCardTableについてですが、思い入れのある点や当初の仕様と最後の仕様が大きく異なった点等を思いつくままに記しておきます。
(1)私はC、C++で育ったので、フィールド(C++でいうメンバー変数)を多用してしまい、入出力を規制することが出来る(C#の優れた点である)プロパティを使うことが余り多くなかったので、今回は意識的に使いました。(とはいっても、現段階ではエラーチェック無しの"{set; get;}"だけですが。)
(2)最初は(現在は構造体にしている)"Card"をクラスにして、カードイメージ(ビットマップ)も持たせるつもりでいました。その為、Cardクラスのインスタンスをいじくると負荷が大きくなるので、例えばシャッフルする際等では(Suit、Numberで整然と並んでいる)Cardクラスインスタンス配列を先ず作り、それの添字の並び替えで見せかけ上シャッフル等並び替えを変える「添字(整数)配列」を使うつもりでいました。
しかしリソースで確保したメモリーと同じものをCard配列でも持つことがそもそもメモリーの浪費であると考え直して、リソースIDを文字列で持たせることにしました。
しかし、それ(リソースID文字列)もSuitとNumberから生成できるので廃止し、イメージ(リソースのビットマップ)も「必要な時だけ表示用メソッドでリソースIDを作り、リソースから呼び出して表示した後に廃棄し、メモリーを空ける(ガーベージコレクションが済むまでは、直ぐに解放できませんが...)」ことにしました。その結果、Cardクラスがint 3つとbool 2つとコンパクトになったので(今までC#で使ったことが無かった)構造体にしてみました。
(3)CardTableはPanelクラスから派生させ、PictureBoxを貼って作ることにしましたが、マウス操作はPictureBoxにイベント通知が行き、Panelは無反応なので、(私には初めての経験ですが)孫コントロールのイベントハンドラーで親コントロールに通知を転送し、その親である祖父フォームのプログラムから利用できるようにしました。
(4)Card構造体インスタンスの配列(Deck[])はアクセス可能なパブリック(但し、書き込みはユーザーアプリからはできないprivate set;)とし、生成手順としては①SuitとNumber順に生成し、Jokerを追加して53枚とし、その後(Jokerを含む、含まないのオプション付きで)②(コンパクトになったので)配列自体をシャッフルすることにしました。
(5)イメージの表示に関しては、トランプ台を一掃するClear、Shownフラグで裏表を指定するTurnFace、その指定に基づきクライアント座標、または13列X6段(迄)のグリッドで表示するShowCard、カードを廃棄する場合等表示したカードを消去するRemoveCard(追加しました)、ポーカーなどでカードを配るDeal、一枚づつカードを引くDrawというメソッドを実装しました。(「神経衰弱」ではTurnFace、ShowCard、RemoveCardくらいしか使いませんでしたが...何か?)
次に最初のアプリとなる「神経衰弱(Concentration)」ですが、
(1)CardTableのDLL化で、本体はすっきりと以下だけになりました。
using System;
using System.Windows.Forms;
using System.Drawing;
using cardtable; //CardTableクラスを利用する
(2)Concentrationというアプリ名(即ちnamespace)にしたので、クラス名はMemoryとし、アプリ用の固有のフィールド(面倒なので、今度はプロパティにしませんでした)を
//メンバーフィールド(変数)
CardTable myTable; //トランプ台
int trials; //試行回数
bool ontheway; //一枚開いた状態
int firstNo; //一枚目のカード番号
int secondNo; //二枚目のカード番号
int pairs; //対を開いた数
の様に設定しています。
(3)「神経衰弱」はシンプルにJokerを使用せず、数札だけ13枚4段の表示とし、サイズ決定は次のようなコードで調査して決めました。
// this.SizeChanged += OnSizeChanged; //調査用
・
・
・
//private void OnSizeChanged(object sender, EventArgs e)//調査用
//{
// this.Text = "Width: " + Size.Width.ToString() + ", Height: " + Size.Height.ToString();
//}
(4)一番大事なのは、DLLで提供されるCardTableコントロールの通知イベントで、これは
myTable.Click += OnMyCardClick;
・
・
・
private void OnMyCardClick(object sender, EventArgs e)
{
if(myTable.msButton == MouseButtons.Left)
{
}
else if(myTable.msButton == MouseButtons.Right) {
{
}
else //解説:MouseButtons.Middle、ですね。
{
}
}
が基本となります。
(5)これに初期設定と表示を行う private void Init() とマウスカーソル位置からカード番号を計算する private int GetNo(int x, int y) を追加するだけで、
あら不思議!もう出来上がっちゃいました。
というのが本日の状況です。
今後cardtable.csとConcentration.csのコードを解説して行こうと思います。
さて、前回から標題に取り組んでいます。
私のプログラミングスタイルは、
(1)プログラム仕様をガチガチに煮詰めず、アウトラインを決めてプロトタイプを作ってみる。
(2)プロトタイプのプログラミング途上で、「あーでもない」「こーでもない」という疑問やアイデアが浮かんでくるので、仕様の具体化、変更、廃止等を含めプロトタイプの修正を行ってゆく。
(3)実際にプロトタイプを使ってサンプルを動かし、想定とは異なる、または想定していなかった事態を発見、対処してゆく。
というようなものなので、まー別の言葉でいえば、所謂
「行き当たりばったり」
と言われても仕方ありませんが、アマチュア暇プログラマーにはこれが最良の「遊び方」なんです。
今回も動作テストサンプルを使ってDLL部分の煮詰めを行ってきましたが、どうも「大体ここ迄かな?」というところまで来ましたので、簡単にご紹介させていただきます。
(1)DLL名とクラス名は"CardTable"で、これはPanelコントロールにPictureBoxを貼ってUIのベースとしています。(ユーザーに見せるのは苦心して修正したフリーのトランプカードイメージです。)
////////////////////////////////
//CardTable(トランプ台)クラス
////////////////////////////////
public class CardTable : Panel //ピクチャー(ボックス)の枠となるPanel
(2)この"CardTable"クラスにはトランプカードが無くてはならないので、"Card"という構造体(丁度16バイトなので...使ってみたくて)を作ってジョーカー込み53枚のプロパティ配列をメンバーにしています。
/////////////////////////
//Card(トランプ)構造体
/////////////////////////
public struct Card //合計16バイト
{
public int Suit {get; set;} //"Clover" - 0, "Diamond" - 1, "Heart" - 2, "Spade" - 3, "Joker" - 4
public int Number {get; set;} //1 - 13(BackとJokerでは0)
public int Owner {get; set;} //1から始まるオーナー(プレーヤー)番号-"0"は一人ゲームまたは未配布を意味する
public bool Shown {get; set;} //表示を表にするか、裏にするかのフラグ(裏の場合は常にBackを表示)
public bool Discarded {get; set;} //捨てられた(以降はゲームで使われない)が否か
}
//メンバープロパティ(解説:CardTableクラスの、です。)
・
・
・
public Card[] Deck {get; private set;} //シャッフルされた後の「山」(Cardインスタンス配列)
(3)CardTableクラスには他にアクセス可能なこのようなプロパティがあります。
//メンバープロパティ
public int cvsWidth {get; private set;} //pbCanvas(解説:描画するPictureBoxです)の幅
public int cvsHeight {get; private set;} //pbCanvasの高さ
public int msX {get; private set;} //マウスが押された時のx座標
public int msY {get; private set;} //マウスが押された時のx座標
public MouseButtons msButton {get; private set;} //押されたマウスボタン(MouseButtonEventArgs.ChangedButton)
public bool smallCards {get; set;} //カードサイズ(初期値 false)
(4)CardTableクラスのメソッドとして、現在は以下を実装しています。
(a) private のマウスクリックイベント(Panel_Click)はピクチャーボックスが拾うので、それを親のPanelに伝えてその親(ユーザーForm)に伝えます。 その際のマウスX/Y座標やボタンはアクセス可能なプロパティに記録します。
(b) 同じく private でトランプの「山」(解説:英語ではDeckというそうです。)を順に初期化するInitCardsがありますが、これは初期化目的でシャッフルの際に使います。
(c) アクセス可能(public)なメソッドでは、カードをシャフルする Shuffle があります。これはJokerを使うか否かを指定できます。
(d) (このブログを書いていてハッと思いついて追加しましたが)CardTable上をきれいにする(初期色の緑で塗りつぶす)Clearがあります。
(e) カードは初期状態では「裏面」が表示されますが、TurnFaceを使うことで表にひっくり返すことが出来ます。また表のものを伏せることもできます。(解説:英語ではTurn up/downというそうです。)
(f) そしてカード(の裏面または表面)を表示するShowCardというメソッドもあります。これはCardTableのクライアント座標を(x, y)で、またはCardTableを横13列、縦6列のグリッドに分けてその座標で指定することもできます。なお、カードのオリジナルイメージは108 x 172(ドット)ですが、これでは大きすぎるので大(72 x 115)小(54 x 86)を用意して、smallCardsプロパティで指定できます。
(g) 「山」から複数プレーヤーに規定枚数を配る(解説:英語ではDealというそうです。)為のDealがあり、これでどのカードのOwnerがどのプレーヤーかが分かります。
(h) 最後に「山」から単独プレーヤーが一枚カードを引いてくる(解説:英語ではDrawまたはTaleというそうです。)為のDrawがあり、残枚数が戻り値で帰ってきます。
ということで、
(全てのカードイメージ(大)をCardTableにGridで並べてみました。)
(今度はシャッフルしたカード(小)をCardTableにGridで並べてみました。)
と、まぁ、ここまで来ました
ので、問題点の再発見を兼ねて何か実際に何かプログラムを組んでみようかな、と考えています。(その際にCardTable.dllのコードも紹介します。)
乞う、(気長に)ご期待!
どうやら創作意欲も復活してきたようなので、麻雀牌
の方は取り敢えず置いておき、先ずはリソースのイメージも整ったトランプカード
を先にやっつけましょうか?(注)
注:改めて書くと思いますが、現在上記54(4種 x 13枚 + Joker + 背景)のビットマップをファイル名のIDでCards.resourcesというC#用のリソースファイルに纏めています。
カードイメージが整ったので「いざ、出陣!」と張り切るのは良いのですが、今回は色々なトランプゲームに対応できるトランプカードゲーム用のを汎用インフラ(DLL)を考えているので、"OOP (Object Oriented Programing)"らしく、じっくりと構成(プログラム構造)を考える必要があります。
こういう場合は「現実のトランプゲームを行う場合の必要なもの、事項、条件をリビュー、分析」してみることが非常に重要だと思います。(注)
注:「私の経験上」の話ですが、紙のアルバムをシミュレートしたAlbumとか、麻雀放浪記をシミュレートしたChincirorinなどで有効でした。
取り敢えず、思いついたことを列挙してみましょう。
(1)先ずはトランプカードが必要ですね。(イメージは出来たので、ソフトウェア的にはこれから作ります。Card クラス?)
(2)トランプカードを使う(表示する)台が要りますね。(CardTable コントロール?-座布団でもよいのですが...)
(3)トランプカードは↑のイメージの様に、クラブ、ダイヤ、ハート、スペードという4つのスート(suit プロパティ?)が1~13(number プロパティ?)まであります。
(4)また、トランプカードは表と裏があり、初期的には隠された状態(裏)で、使われる時に表にされ(Used プロパティ?)、台の上の所定の位置に裏、また表で置かれ(表示(Shown プロパティ?)され)ます。
(5)トランプゲームを始める際にはカードの山(Card クラスのインスタンスの配列?)を「切る(シャッフル)-初期化(Initialization)メソッド?」する必要があります。これはスートと番号順に並んだ53枚のカードをランダム、無秩序に再度並び替えることを意味します。
(6)シャッフル(初期化)されたカードの山は一人ゲームの場合を含め、「誰か(Player or Owner プロパティ?)に配られる」ことからゲームが開始されます。配られただけの状態(Owner プロパティは非null)では、使われていますが(Used プロパティは非null)、まだ表示されず(Shown プロパティは false)、使い切ると捨てられる(Discard プロパティ or Used プロパティ?がfalse)ことになります。
(7)トランプゲーム自体は、固有のルールと勝敗判断で進みますが、それは今回のプログラムには関係ありません。今回のプログラムは「台の上で、トランプカードをシャッフルし、プレーヤーに裏の状態で配り、必要に応じて表を見せ、最後に捨てられる」までを簡単にまとめる(ライブラリー化する)ことが目的です。
以上から「Card クラスのメンバー配列を持つ、CardTableクラスのコントロール(PictureBoxクラスから派生?)を作り、シャッフル、配る、表に返す、捨てる等のメソッドを持たせる」イメージが湧いてきました。しかし、上で書いたプロパティはまだオーバーラップしていそうで、もう少し整理する必要がありそうですね。
早いもので(注)、明後日で義父が亡くなってから一か月になります。未だに神さんとお姉さんは95になる義母と役所の手続きを行っていますが、私は少しづつ日常を取り戻し始めています。
注:一か月という時間の長さは変わらず、月日の経つ(時の過ぎる)速度が「速い」という意味なので、「早い (early)」ではなく「速い (swiftly)」とも感じますが...まぁ、どうでもよいことですが。(2024年11月3日追補-忘れていました!)
そんな中で、未だに「【無駄話】何かを作る悦び」もあるのですが、新聞に和田秀樹さんの新刊本の広告が出ていて、そこに書かれていたボケチェック設問(二つ以上なら要注意!)に4つも該当してしまい、
アセアセ(汗;)
そういうことで、
前に一度は投げ出していた課題(【無駄話】結局、...)、に再挑戦し、
というように画像の背景色を統一(緑-但し、C#のColor.Greenとは微妙に違いますね...)、透明化し、黒点等の除去を行って、使える状態にしました。
又、「【無駄話】性懲りもなく....」で麻雀牌にも挑戦しましたが、同様の問題が起こり、これも少しづつ、少しづつ同様に修正、加えて位置合わせも行い、
これも使える状態にしました。
The only question yet left is how to handle them (後は、どう料理するか?)
いかんっ!
前回が10月9日、前々回が9月26日とネタ切れ症状は悪化するばかり。とはいえ、身の入らないテーマは飽きるばかりなので、
自然体で
衝動が生まれるのを待ちましょう。
って、何の衝動?
それは、
何かを作る悦び
ではないでしょうか?
よく「気力、知力、体力」という言葉が出ますが、矢張り齢を重ねると
体力が、次に知力が、そして最後に気力(欲)
が失われてゆきます。
私も若い頃は自身の欲望を持て余すこともありましたが、次第に制御され、着たいものがなくなり、行きたいところがなくなってゆき、終いには食べたいものもなくなってしまうのを経験して、「あー、これが老いか」と思うことが有ります。
また、
昔は何もなかったので、自分で作ることが必要でしたが、現在は物にあふれ、空腹になる前に食べるという飽食状態で、ソフト面でもほとんど必要なものはロハでダウンロードできます。
今は(なりふり構わず)猟をする必要が無く、(多少の不満を抱えたままで)飼いならされるだけの時代なのか?
と深刻に思います。そして依然
ネタ切れ
は続きます。
もう10月9日になるのですね...前回書いたのが9月26日だから、約2週間のご無沙汰でした。
実は神さんの親父が突然肺炎にかかり、大分頑張ったのですが、96歳という年齢もあって先日他界しました。その後のあわただしい、納棺式、通夜、告別式でPCに触ることもできませんでした。ご容赦ください。
(とか言って、ネタのないことの言い訳にしているとお考えになっても、遠からずでもあるのですが...とはいえ)
何か作らなきゃ、何か作りたいな...
と切実に思ってはいるのですが、矢張り
(1)家庭で作るソフト(アプリ)は限られていて、
(2)得意とするユーティリティツールについても、多くのツールは既にできあがったものがあるか、自作済で、
(3)絵心もなく、ゲームのネタもだんだん尽きてきて、
(4)前回のトランプゲームのインフラ作りも挫折してしまった(注)
注:「【無駄話】結局、...」参照。
ので、壁に頭を打ち付けているのも確かです。
そんな時に、
あ"~、そういえば、大学を5年やった原因のゲームがあったな!
ということで、性懲りもなく今度はトランプから麻雀牌に切り替えて、何かゲームインフラでもできないか考えています。
今度は前回のような画像の瑕疵はないと思うのですが、まぁ、期待しないでお待ちください。
大分ブログをさぼってしまいました。
実家の101歳の母に顔を見せたり、少し知恵がついて羞恥心を覚えた孫の面倒を見たり、33年ぶりにバスルームをリノベしたり等、身近な些事があったり、女房の父(96歳)が肺炎で3週間入院したりで、それを言い訳に更新をさぼっていました。
というかー、
ネタが無いことが一番の原因かも。とはいえ、
ずーーーーーーーーっと
ネタ探しは続けており、あれやこれやweb検索し、あれやこれやMicrosoftご本家をウロウロしたり、ChatGPTご本尊様にお伺いを立てたりもしましたが、
刺さるものが無いんです。
俺も愈々後期高齢者の仲間入りかな、という「正しい認識」を持ちつつ、未だ「まだまだいけるぜ」という幻想に身を浸(ヒタ)し(たいと思い)つつ、
いつもネタを考えているのに、
何も返ってこない。
何故?
と自問自答した時に、2011年の東日本大震災の時の金子みすゞの詩がよみがえります。
「遊ぼう」っていうと
「遊ぼう」っていう。
「馬鹿」っていうと
「馬鹿」っていう。
「もう遊ばない」っていうと
「もう遊ばない」っていう。
そして、あとで
さみしくなって、
「ごめんね」っていうと
「ごめんね」っていう。
こだまでしょうか、
いいえ、誰でも。
俺は
復活できるのでしょうか?
ps. ウィンドブレーカーを着こまなければならない早朝の寒気から、汗ばむ気温の晴天に変わり、今晩は大好きなDewer's 12年のハイボール で始め、気が変わってVodka(ウオッカ)ハイボールに切り替え4杯目。つらつらと思いを馳せながら一寸おセンチになってしまったのでしょうか?
さて、前回まででImageConverterの解説を終えましたが、フリーのトランプカードイメージを色々と物色し、数字と絵札とジョーカーとトランプ裏面を異なるフリー素材から取得してそれを同一サイズ、同一ファイル形式でまとめたことは既にお話ししました。
それでは愈々、
DLLでカードゲームインフラプログラムをチャレンジしようかと思い、クラスの有り様を色々と考えていました。例えば...
(1)カードは4スート(suits)、13枚づつの他、ジョーカーと裏面がいるが、これらをプログラミング的意味で合理的配列にするにはどうするか?→スートはenumで纏め、52枚のカードを配列として扱うが、裏面及びジョーカーは配列に入れる必要はない。しかし画像を扱う関係でImageListを使うなら最後の[52][53]('0'ベースなので53、54番目)に入れておくほうが良いかも。
(2)"PlayingCard"クラス(class)を作るといっても実はカードだけで良いわけではない。先ずゲームを行う盤面(boardは通常green色なんでしょうね)と不定数の参加者(players)をクラスプロパティに入れないと不味。
(3)「トランプカードをゲームで使う」とは「盤上で、(不定数の)参加者によりシャッフルされ、(不定数で)配られ、『一定のゲームルール』(これは個別性が高いのでDLLに入れない)によりで、配布済カードが捨てられ、その穴を未使用のカードが配られて埋める。又、未使用の残カード数は常に分からないとならない。」ということではないか?
(4)カードは配列として生成し(ジョーカーを必要とするゲームは『52番目カード』として入れる)、それを乱数で再配列しなおし(シャッフル)、必要な参加者数によりplayer配列を生成し、それらにカードを配る(カード側には『所有者』が分からないと不味)。そしてカードは「未使用→使用中→廃棄」の状態が分かるようにしないと不味。
等と考えていましたが、
その前に、先ずカードを表示しよう!
ということで、ダミーのApplicationウィンドウにPanelコントロールを'green board'として配置し、そこにリソース(*.resourcesの中に入れます)として組み込んだビットマップを読みだして表示するだけのプログラムを作ってみました。その結果
あ"~、そーなんだー。
ということが判りました。
何が分かったって?
(1)数字カードは単純な配色ですが、絵札は結構色数が多く、「白地」が実は単純な白地ではなく若干のアンジュレーションがあったところ、これをファイル形式変換したら目立ってしまいました。(Paintツールで拡大するとよくわかりました。)この為、絵札はファウンデーションを塗る(白色で消してゆく)必要があります。
(2)絵札は数字カードのサイズに合わせて切り取ったので余り目立ちませんでしたが、共に(特に数字カードが)「余白の統計化処理」がなされていない為、52枚のカードの枠線外の余白を総て「透明色→恐らくこれは緑色なんでしょう」で塗潰してゆく必要があります。
しかし、それってPaintツールしかないし、枠線も色が複数色になっていて、簡単にツールプログラムで一括処理にできない為、
手作業で52枚を修正してゆかなければなりません!
って、どんだけぇぇぇぇぇぇぇ~!!!
という訳で、一旦はフォールダーを作って始める気満々だったPlayingCardsプロジェクトは
無期延期のお蔵入り
に決定しました。m_(__)_m
ps. また、なんかネタを考えます。