20
トランプソリテア シンプルサイモン
いよいよ本格的なトランプのゲームを作ります。 シンプルサイモンは,ウィンドウズのゲームパックに入っているトランプソリテア1のひとつ “スパイダーズ” の 1 パック版です。20.1
ホイル
20.1.1 レイアウト トランプ 1 パック 52 枚を表向きにして次のように 10 列に並べます。左の列から順に,1 枚,2 枚,3 枚,. . . ,7 枚,8 枚,8 枚,8 枚 で,各列のカードは下のカードが何かわかるように少しず らして重ねて置きます。これをテーブルと言います。 20.1.2 移動可能なカード群 各列のトップカード(一番上にあるカード)は移動可能です。その下にトップカードと同じスー トでランクが 1 ずつ小さいカードが重なっている場合は,それらをまとめて移動可能です。たとえ ば,スペードの 5 の下にスペードの 6,スペードの 7 の順に重なっていれば,それら 3 枚をまとめ て移動可能です。 20.1.3 移動先 トップカードの上に,スートに関係なくそれよりランクが 1 大きいカードを乗せることができま す。したがって,上の例の移動可能な 3 枚のカード群は,ランクが 8 のトップカードがあればその 上にまとめて乗せることができます。 また,空になった列があれば,そこには移動可能なカード群は何でも移すことができます。 20.1.4 上げる 同じスートの 13 枚のカードがランク順に重なると,それらをテーブルから取り除く(上げる) ことができます。 20.1.5 目的 4 つのスートをすべて上げてテーブルを空にしたら成功です。 1ソリテア (solitaire) とは一人遊びのことで,特定のゲームを指す言葉ではありません。20.2
プログラムの準備
20.2.1 アプリケーションを[ファイル|新規作成] フォルダ Simon (Trump の中に作る) ユニット SimonU.pas プロジェクト SimonP.dpr 20.2.2 フォームのプロパティ Name FormSimon Caption トランプソリテア シンプルサイモン 学生証番号 氏名 Position poDeskTopCenter 20.2.3 ユニットに追加 usesWindows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs ,TrumpU; type TFormSimon = class(TForm) private Trump : TTrump; public end; これだけで翻訳(コンパイル)するとエラーになります。 20.2.4 TrumpU を追加 [プロジェクト|プロジェクトに追加]を行い,TrumpU を追加する。 表示上は何も変化がありませんが,翻訳するとエラーが出なくなっています。 20.2.5 メインパネルと終了ボタン Panel をフォームの中に置く。 Align alBottom いつもと違って下に付ける Height 40 くらい Name PanelMain Caption Button をメインパネルの中,左端の方に置く。 Name ButtonClose Caption 終了 Anchors.akLeft False Anchors.akRight True
(********** Method **********) (********** Event Handler **********)
procedure TFormSimon.ButtonCloseClick(Sender: TObject); begin Close; end; {ButtonCloseClick} 20.2.6 テーブルイメージ Image をフォームの中,メインパネルの外に置く。 Align alClient Name ImageTable Width 880 (となるように Form を大きくする) Height 640 (となるように Form を大きくする) 20.2.7 作成タイマー
Example プログラムでは,FormCreate 時に Trump を生成し,作成ボタンを押すと各 Card を 作成しました。これを一箇所にまとめて自動的に行うようにしたいのですが,メインフォームの FormCreate 時には FormTrump ができてないので Card を作成することはできません。作成ボタ ンを押すのではなくタイマーを使って自動的に行うようにします。 Timer をフォームの中,任意の場所に置く。 Name TimerSakusei Interval 1 begin if Trump = NiL // 未作成なら作成する then begin // 最初に1回だけ実行 Trump := TTrump.Create; Trump.Sakusei(Self); Trump.MouseHandler(CardMouseDown,CardMouseUp,CardMouseMove); end; end; TimerSakuseiTimer 20.2.8 Card の Mouse ハンドラーを追加 前回やったように,CardMouseDown,CardMouseUp,CardMouseMove をメソッドとして追加し ます。とりあえず実現部はコメント行を書いておきます(何も書かないと自動的に消されることが あるので)。 20.2.9 実行 カードが作成されます。
20.2.10 カードをばらまく
どうせタイマーを使うなら,楽しみましょう。
procedure TFormSimon.TimerSakuseiTimer(Sender: TObject); var CardNo : TCardNo; RectSaki : TRect; RectMoto : TRect; X1,Y1 : Integer; begin if Trump = NiL // 未作成なら作成する then begin // 最初に1回だけ実行 Trump := TTrump.Create; Trump.Sakusei(Self); Trump.MouseHandler(CardMouseDown,CardMouseUp,CardMouseMove); end; //Trump.Hide; X1 := Random(ClientWidth)-36; Y1 := Random(ClientHeight)-48; CardNo := Random(52); with Trump.Cards[CardNo] do begin RectMoto := Canvas.ClipRect; RectSaki := Bounds(X1,Y1,72,96); ImageTable.Canvas.CopyRect(RectSaki,Canvas,RectMoto); end; end; {TimerSakuseiTimer} 20.2.11 実行 楽しめましたか。 最初に作成したカードがちらついて目障りなので //Trump.Hide; の // を消して 見えなくしましょう。 実行する前に,TrumpU にメソッドを追加する必要があります。 20.2.12 TrumpU にメソッドを追加 [表示|ユニットの表示]をして TrumpU を表示させて追加します。 procedure TTrump.Hide; (* 見えなくする *) var CardNo : TCardNo; begin for CardNo := 0 to 51 do Cards[CardNo].Hide; end; (* Hide *)
procedure TTrump.Show; (* 見えるようにする *) var CardNo : TCardNo; begin for CardNo := 0 to 51 do Cards[CardNo].Show; end; (* Show *) 20.2.13 実行 ちらつきがなくなるはずです。 もう TrumpU を変更することはないので,[ファイル|閉じる]で見えなくしてかまいません。
20.3
ゲーム開始準備
SimonU に追加します。 20.3.1 データ構造 type TCol = 1..10; TRow = 0..52; TColAndRow = record Col: TCol; Row: TRow; end;TTable = array [TCol,TRow] of TCardNo;
TMaisuu = array [TCol] of TRow;
TBasyo = array [TCardNo] of TColAndRow; TFormSimon = class(TForm) 途中省略 private Trump : TTrump; Table : TTable; Maisuu : TMaisuu; Basyo : TBasyo; SentakuChuu: Boolean; ErandaFuda : TCardNo; NokoriSuit : 0..4; public end; 20.3.2 メソッドを追加 (********** Method **********)
(* Col 列にCardNo番の札を追加する *) var Row : TRow; begin // 内部処理 Inc(Maisuu[Col]); // Col 列の札が1枚増える Row := Maisuu[Col]; // 枚数行目に
Table[Col,Row] := CardNo; // CrdNo 番の札を置く
Basyo[CardNo].Col := Col; // その場所を逆引きできるように Basyo[CardNo].Row := Row; // 表示 with Trump.Cards[CardNo] do begin Top := 30*Row; Left := 80*Col-40; BringToFront; Visible := True; end; end; {Kuwaeru} procedure TFormSimon.Layout; (* 開始状態に並べる *) (* Tableを作る *) var Col : TCol; Row : TRow; CardNo : TCardNo; begin for Col := 1 to 10 do // すべての列を空にする Maisuu[Col] := 0; Row := 1; // 1行目から始める Col := 1; // 1列目から始める for CardNo := 0 to 51 do begin Kuwaeru(Col,CardNo); // 1枚並べる if Col < 10 // 10列になるまで then Inc(Col) // 次の列にする else begin // 10列まで終わったら Inc(Row); // 次の行にする Col := Row; // 行と同じ番号の列から始める end; end; end; {Layout} procedure TFormSimon.Hyouji; (* Tableを表示する *) var Col : TCol; Row : TRow; begin with ImageTable.Canvas do begin Brush.Color := clTeal; FillRect(ClipRect); Brush.Color := clAqua; for Col := 1 to 10 do FillRect(Bounds(80*Col-40,30,72,96));
for Col := 1 to 10 do
for Row := 1 to Maisuu[Col] do with Trump.Cards[Table[Col,Row]] do begin Visible := True; PosiNiSuru; Top := 30*Row; Left := 80*Col-40; BringToFront; end; end; {Hyouji} procedure TFormSimon.Start; begin Layout; Hyouji; SentakuChuu := False; NokoriSuit := 4; TimerSakusei.Enabled := False; end; {Start} 20.3.3 開始ボタン Button をメインパネルの中,右の方に置く。 Name ButtonStart Caption ゲーム開始
procedure TFormSimon.ButtonStartClick(Sender: TObject); begin Start; end; {ButtonStartClick} 20.3.4 実行 カードが並べられます。まだゲームはできません。 20.3.5 新ゲームボタン Button をゲーム開始ボタンの右に置く。 Name ButtonNew Caption 新しいゲーム
procedure TFormSimon.ButtonNewClick(Sender: TObject); begin
Trump.Shuffle; Start;
20.3.6 実行 ゲーム開始ボタンは前と同じゲームに再挑戦するときに使うボタンで,新しいゲームボタンは名 前どおり新しいゲームに挑戦するとき使うボタンです。
20.4
移動可能カード群選択
移動可能カード群の一番下のカードをクリックすると,それらのカードをネガにして選択したこ とがわかるようにします。 20.4.1 メソッド追加procedure TFormSimon.Erabu(CardNo : TCardNo);
(* CardNo番の札から上の一群が選択可能ならば選択する *) (* 選択可能=同じスーツでランクが1ずつ減少している *) var Col : TCol; Row : TRow; begin with Trump do begin Col := Basyo[CardNo].Col;
Row := Basyo[CardNo].Row; // CardNo番の札がある行から始めて
while (Row < Maisuu[Col]) and
(Cards[Table[Col,Row+1]].Suit = Cards[Table[Col,Row]].Suit) and (Cards[Table[Col,Row+1]].Rank = Cards[Table[Col,Row]].Rank-1) do
Inc(Row); // 選択可能条件を満たしていたら次の行へ
SentakuChuu := Row = Maisuu[Col]; // 上の札がすべて満たしていたらOK
if SentakuChuu then begin
ErandaFuda := CardNo; // 選んだ群の先頭
for Row := Row downto Basyo[CardNo].Row do // 選んだ群を
Cards[Table[Col,Row]].NegaNiSuru; // ネガにする end; end; end; {Erabu} procedure TFormSimon.SentakuKaijo; (* 札が選ばれている状態を解除する *) var Col : TCol; Row : TRow; begin Col := Basyo[ErandaFuda].Col; // 群(の先頭)がある列
for Row := Basyo[ErandaFuda].Row to Maisuu[Col] do // 群の札を
Trump.Cards[Table[Col,Row]].PosiNiSuru; // ポジにする
SentakuChuu := False; // 選ばれてない
end; {SentakuKaijo}
Shift: TShiftState; X, Y: Integer); begin
with TCard(Sender) do begin
if not SentakuChuu
then Erabu(Tag) // Tag = CardNo
else SentakuKaijo; end; end; {CardMouseDown} 20.4.3 実行 最初のレイアウトでは,トップカードだけが選択できます。 新しいゲームボタンを押して,運良く 2 枚以上の移動可能カード群があるようなレイアウトがで きれば,カード群が選択できることもわかるのですが,なかなかそういうレイアウトができてはく れないでしょう。
20.5
移動
現在は,移動可能カードをクリックすると選択して(ネガにして),選択状態でクリックすると 選択状態を解除します(ポジに戻します)。 選択状態でどこかをクリックしたら,そこに移動させます(もちろん移動可能なとき)。 20.5.1 CardMouseDown 変更procedure TFormSimon.CardMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
with TCard(Sender) do begin
if not SentakuChuu
then Erabu(Tag) // Tag = CardNo
else Utsusu(Basyo[Tag].Col); end;
end; {CardMouseDown}
20.5.2 ImagaTable の OnMouseDown ハンドラーを追加
procedure TFormSimon.ImageTableMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin
if SentakuChuu and (ImageTable.Canvas.Pixels[X,Y] = clAqua) then Utsusu((X+42) div 80);
20.5.3 メソッド追加
procedure TFormSimon.Utsusu(ColTo : TCol);
(* 選んだ群をColTo列に移動可能ならば移動する *) (* 移動可能=移動先が空または一番上の札のランクが1大きい *) var RowFrom : TRow; ColFrom : TCol; begin if (Maisuu[ColTo] = 0) or (Trump.Cards[Table[ColTo,Maisuu[ColTo]]].Rank = Trump.Cards[ErandaFuda].Rank+1) then begin ColFrom := Basyo[ErandaFuda].Col;
for RowFrom := Basyo[ErandaFuda].Row to Maisuu[ColFrom] do begin Kuwaeru(ColTo,Table[ColFrom,RowFrom]); Dec(Maisuu[ColFrom]); end; end; SentakuKaijo; //SorottaraAgeru(ColTo); end; {Utsusu} 20.5.4 実行 最初のレイアウトで,次のことができることを確認しなさい。 (1) クラブの A をハートの 2 の上に移す。 (2) スペードの J を Q の上に移す。 (3) スペードの Q(とその上の J)を K の上に移す。 (4) スペードの K たちを左端の空の列に移す。 (5) その上にスペードのカードを次々に全部移す。 まだ 13 枚そろっても “上げる” ことをしません。
20.6
上げる
20.6.1 Utsusu を変更 実行部最後の //SorottaraAgeru(ColTo); の // を消す。20.6.2 メソッドを追加
procedure TFormSimon.SorottaraAgeru(Col : TCol);
(* 13 枚揃ったら取り除く *) begin if Maisuu[Col] >= 13 then begin Erabu(Table[Col,Maisuu[Col]-12]); if SentakuChuu then Ageru(Col); end; end; {SorottaraAgeru}
procedure TFormSimon.Ageru(Col : TCol);
(* 13枚揃った群を上げる(取り除く) *) var K : 1..13; begin SentakuKaijo; // 選択状態を解除する for K := 1 to 13 do with Trump.Cards[Table[Col,Maisuu[Col]]] do begin repeat Top := Top+1; Application.ProcessMessages; // 表示する
until Top+Height >= ImageTable.Height;
Visible := False; // 見えなくする Dec(Maisuu[Col]); // 1枚減らす end; Dec(NokoriSuit); //if NokoriSuit = 0 // then GameOver(True); end; {Ageru} 20.6.3 実行 さっきと同じようにして,スペードを 13 枚そろえると,揃ったカードを上げます。 どのように上げるでしょうか。 他のスートも上げられますよ。
20.7
ゲーム終了
全部上げたら,“おめでとう” と表示するようにします。また,ギブアップできるようにします。 20.7.1 Ageru 変更 最後の 2 行の // を消す。20.7.2 ギブアップボタン Button を 1 つ追加する。
Name ButtonGiveUp
Caption ギブアップ
procedure TFormSimon.ButtonClick(Sender: TObject); begin
GameOver(False); end; ButtonClick
20.7.3 メソッド追加
procedure TFormSimon.GameOver(Seikou : Boolean);
(* ゲーム終了 *) begin TimerSakusei.Enabled := True; if Seikou then ShowMessage(#13+#13+#13+’ おめでとう ’+#13+#13+#13) else ShowMessage(#13+#13+#13+’ 残念でした ’+#13+#13+#13); end; GameOveer 20.7.4 実行 これで完全なゲームになりました。