ソリティアを書くソリティア

9年前、私はPSPを購入することに無謀でした。 ソリティアの欠如だけが喜びによって曇っています。 私がソリティア愛好家だったわけではありませんが、どういうわけかオプションの1つである「スカーフ」のレイアウトに慣れました。 私はこのソリティアを自分で書かなければなりませんでした。 その後、PSP用に書かれたこのソリティアをWindowsおよびQNXで移植しました。 この記事では、このようなゲームの作成方法を説明します。



まず最初に、グラフィックが必要です。 描画方法がわからないので、すべてのグラフィックをインターネットから取得しました。 PSPカードのバージョンでは、フラグメント(数と色)から推測しましたが、他のバージョンでは、移植時に各カードが個別のスプライトを受け取りました。 次に、ソリティア自体のアルゴリズムの実装について考える必要があります。



この構造でカードを配置できるボックスを設定しましょう:



// enum CARD_SUIT { // CARD_SUIT_SPADES, // CARD_SUIT_HEARTS, // CARD_SUIT_CLUBS, // CARD_SUIT_DIAMONDS }; struct SCard { CARD_SUIT Suit;// long Value;//      bool Visible;//true-  } sCard_Box[13][53];//   52    
      
      





合計で13個の箱があります。 各ボックスは52の区画で構成されています。 これらは図にあります:





競技場の箱



マップ可視性フラグは、マップが開いていることを意味します。 カードの値が負の場合、ボックスにこれ以上カードがないと仮定します。



最大52枚のカードを各引き出しに入れることができ、カードがもうないことを示す別の印-合計53個のセルコンパートメント。



カードを引き出し間で移動する機能が必要です。 ここにあります:



 //---------------------------------------------------------------------------------------------------- //    s   d //---------------------------------------------------------------------------------------------------- bool CWnd_Main::MoveCard(long s,long d) { long n; long s_end=0; long d_end=0; //      for(n=0;n<53;n++) { s_end=n; if (sCard_Box[s][n].Value<0) break; } for(n=0;n<53;n++) { d_end=n; if (sCard_Box[d][n].Value<0) break; } if (s_end==0) return(false);//   //   sCard_Box[d][d_end]=sCard_Box[s][s_end-1]; sCard_Box[s][s_end-1].Value=-1;//    return(true); }
      
      





ここでは、取得するブランチインデックスと配置できるブランチインデックスを探しています。 しかし、この機能はカードのスートと価値を考慮した移動のルールをチェックしません。 一番下のカードをある引き出しから別の引き出しに移動するだけです。



また、ゼロボックスから最初のボックスにカードを移動する機能も必要です。 これらのボックスはストアであるため、その内容は円を描いて移動します。



 //---------------------------------------------------------------------------------------------------- //    //---------------------------------------------------------------------------------------------------- void CWnd_Main::RotatePool(void) { bool r=MoveCard(0,1);//       if (r==false)//  { //  while(MoveCard(1,0)==true); } }
      
      





ここで、カードをゼロボックスから最初のボックスに移動します。そのような移動が失敗した場合、ゼロボックスは空なので、最初のカードからすべてのカードをゼロに戻す必要があります。



次に、レイアウトを初期化する必要があります。 このようにしましょう:



 //---------------------------------------------------------------------------------------------------- //  //---------------------------------------------------------------------------------------------------- void CWnd_Main::InitGame(void) { TimerMode=TIMER_MODE_NONE; long value=sCursor.Number[0]+10*sCursor.Number[1]+100*sCursor.Number[2]+1000*sCursor.Number[3]+10000*sCursor.Number[4]; srand(value); long n,m,s; //       for(s=0;s<13;s++) for(n=0;n<53;n++) sCard_Box[s][n].Value=-1; //     long index=0; CARD_SUIT suit[4]={CARD_SUIT_SPADES,CARD_SUIT_HEARTS,CARD_SUIT_CLUBS,CARD_SUIT_DIAMONDS}; for(s=0;s<4;s++) { for(n=0;n<13;n++,index++) { sCard_Box[0][index].Value=n;//  sCard_Box[0][index].Suit=suit[s]; sCard_Box[0][index].Visible=true; } } //     for(n=0;n<7;n++) { for(m=0;m<=n;m++) { long change=RND(100); for(s=0;s<=change;s++) RotatePool();//  //  if (MoveCard(0,n+2)==false)//    0 -   { m--; continue; } long amount=GetCardInBox(n+2); if (amount>0) sCard_Box[n+2][amount-1].Visible=false;//  } } //     while(1) { if (GetCardInBox(1)==0) break;//    1 RotatePool();//  } }
      
      





最初は、すべてのカードがゼロボックス(ストア)に配置され、このボックスがランダムな数までスクロールし、2〜8のインデックスを持つ他のボックスに移動します。もちろん、ソリティアが確実に収集されるようにカードを投げることができますが、私はそうですしませんでした。 また、52枚のカードからランダムにカードを選択し、目的のボックスに入れるだけです。 それで私もしませんでした



上記の関数は別の関数を使用します:



 //---------------------------------------------------------------------------------------------------- //     //---------------------------------------------------------------------------------------------------- long CWnd_Main::GetCardInBox(long box) { long n; long amount=0; for(n=0;n<53;n++) { if (sCard_Box[box][n].Value<0) break; amount++; } return(amount); }
      
      





さて、ここではすべてが明確だと思います。 もちろん、最適化のために、カードの数はいつでも覚えることができますが、あまり意味はありません。これらの関数はほとんど呼び出されないため、ここではパフォーマンスは重要ではありません。



どのカードが表示され、どのカードが表示されないかを追跡しないために、ここでそのような機能を設定します。



 //---------------------------------------------------------------------------------------------------- //      //---------------------------------------------------------------------------------------------------- void CWnd_Main::OnVisibleCard(void) { long n; for(n=2;n<9;n++) { long amount=GetCardInBox(n); if (amount>0) sCard_Box[n][amount-1].Visible=true; } }
      
      





彼女は、引き出し2〜8の一番下のカードをすべて開きます。



MoveCard関数は上記のとおりです。実際、ソリティアの初期化段階とストアをスクロールするときにのみ使用されるため、実際にはゲーム自体では使用されません。 問題は、ソリティアでは、個々のカードではなく、カードのグループを転送する必要があるということです。 このようなグループを移動するには、ChangeBox関数があります。これには、ソースボックス、宛先ボックス、およびセルインデックスを指定する必要があり、そこからカードを転送する必要があります。



 //---------------------------------------------------------------------------------------------------- //       //---------------------------------------------------------------------------------------------------- void CWnd_Main::ChangeBox(long s_box,long s_index,long d_box) { long n; long d_end=0; //       for(n=0;n<52;n++) { d_end=n; if (sCard_Box[d_box][n].Value<0) break; } //      for(n=s_index;n<52;n++,d_end++) { if (sCard_Box[s_box][n].Value<0) break; sCard_Box[d_box][d_end]=sCard_Box[s_box][n]; sCard_Box[s_box][n].Value=-1;//    } }
      
      





ただし、すべてのルールを考慮したカードの完全な移動は、ChangeBoxを使用する別の関数によって実行されます。



 //---------------------------------------------------------------------------------------------------- //     //---------------------------------------------------------------------------------------------------- void CWnd_Main::ChangeCard(long s_box,long s_index,long d_box,long d_index) { if (d_box>=2 && d_box<9)//     { //  ,       if (d_index<0) { if (sCard_Box[s_box][s_index].Value==12) ChangeBox(s_box,s_index,d_box);//  - ,   return; } //,          if (sCard_Box[d_box][d_index].Value<=sCard_Box[s_box][s_index].Value) return;//  ,  ,      if (sCard_Box[d_box][d_index].Value>sCard_Box[s_box][s_index].Value+1) return;//   ,     1 CARD_SUIT md=sCard_Box[d_box][d_index].Suit; CARD_SUIT ms=sCard_Box[s_box][s_index].Suit; if ((md==CARD_SUIT_SPADES || md==CARD_SUIT_CLUBS) && (ms==CARD_SUIT_SPADES || ms==CARD_SUIT_CLUBS)) return;//   if ((md==CARD_SUIT_HEARTS || md==CARD_SUIT_DIAMONDS) && (ms==CARD_SUIT_HEARTS || ms==CARD_SUIT_DIAMONDS)) return;//   ChangeBox(s_box,s_index,d_box);//  return; } if (d_box>=9 && d_box<13)//     { //   ,      -    if (GetCardInBox(s_box)>s_index+1) return; //  ,       if (d_index<0) { if (sCard_Box[s_box][s_index].Value==0)//  - ,   { DrawMoveCard(s_box,s_index,d_box); } return; } //,          if (sCard_Box[d_box][d_index].Value>sCard_Box[s_box][s_index].Value) return;//  ,  ,      if (sCard_Box[d_box][d_index].Value+1<sCard_Box[s_box][s_index].Value) return;//   ,     1 CARD_SUIT md=sCard_Box[d_box][d_index].Suit; CARD_SUIT ms=sCard_Box[s_box][s_index].Suit; if (ms!=md) return;//   DrawMoveCard(s_box,s_index,d_box); return; } }
      
      





アセンブリフィールド(インデックスが9から12のボックス)では、値の大きい順に適したカードのみを配置できますが、最初のカードは常にエースである必要があります。 競技場では、スーツの色が反対になり、カードの値が増加し、空のフィールドに移動できるのは王だけです。



ソリティアは、各ボックスのアセンブリフィールドに正確に13枚のカードがある場合に収集されます。



 //---------------------------------------------------------------------------------------------------- //    //---------------------------------------------------------------------------------------------------- bool CWnd_Main::CheckFinish(void) { long n; for(n=9;n<13;n++) { if (GetCardInBox(n)!=13) return(false); } return(true); }
      
      





ボックスの便利な作業のために、座標を持つ配列があります:



  //    long BoxXPos[13][53]; long BoxYPos[13][53];
      
      





この配列は次のように入力されます。



  //   X #define BOX_WIDTH 30 //  0  2  X  Y #define BOX_0_1_OFFSET_X 5 #define BOX_0_1_OFFSET_Y 5 //   2  8  X  Y #define BOX_2_8_OFFSET_X 5 #define BOX_2_8_OFFSET_Y 45 //   9  12  X  Y #define BOX_9_12_OFFSET_X 95 #define BOX_9_12_OFFSET_Y 5 //     #define CARD_DX_OFFSET 10 //      PSP #define SIZE_SCALE 2 for(n=0;n<13;n++) { long xl=0; long yl=0; long dx=0; long dy=0; if (n<2) { xl=BOX_0_1_OFFSET_X+BOX_WIDTH*n; yl=BOX_0_1_OFFSET_Y; xl*=SIZE_SCALE; yl*=SIZE_SCALE; dx=0; dy=0; } if (n>=2 && n<9) { xl=BOX_2_8_OFFSET_X+BOX_WIDTH*(n-2); yl=BOX_2_8_OFFSET_Y; xl*=SIZE_SCALE; yl*=SIZE_SCALE; dx=0; dy=CARD_DX_OFFSET*SIZE_SCALE; } if (n>=9 && n<13) { xl=BOX_9_12_OFFSET_X+(n-9)*BOX_WIDTH; yl=BOX_9_12_OFFSET_Y; xl*=SIZE_SCALE; yl*=SIZE_SCALE; dx=0; dy=0; } for(m=0;m<53;m++) { BoxXPos[n][m]=xl+dx*m; BoxYPos[n][m]=yl+dy*m; } }
      
      





この配列では、各ボックスに対して、52枚すべてのデッキカードのすべての場所が形成されます。 この配列を使用すると、プレーヤーがマウスで選択したものを簡単に判断できます。



 //   X #define CARD_WIDTH 27 //   Y #define CARD_HEIGHT 37 //---------------------------------------------------------------------------------------------------- //           //---------------------------------------------------------------------------------------------------- bool CWnd_Main::GetSelectBoxParam(long x,long y,long *box,long *index) { *box=-1; *index=-1; long n,m; //   "" for(n=0;n<13;n++) { long amount; amount=GetCardInBox(n); for(m=0;m<=amount;m++)// m<=amount  53-  (    ) { long xl=BoxXPos[n][m]; long yl=BoxYPos[n][m]; long xr=xl+CARD_WIDTH*SIZE_SCALE; long yr=yl+CARD_HEIGHT*SIZE_SCALE; if (x>=xl && x<=xr && y>=yl && y<=yr) { *box=n; if (m<amount) *index=m; } } } if (*box<0) return(false); return(true); }
      
      





実際、ここでソリティアの論理部分の記述が終了します。 好みに合わせてインターフェイスを作成できます。 PSP(while(1)に回転する場所)からプログラムを転送したため、ループをタイマーに個人的に接続し、各タイマーモードに独自の番号とハンドラーを割り当てました。 また、タイマーから実行されるOnPaintを非同期的にバインドします。 これは、移植時にこれを行う最も簡単な方法でした。



QNX用およびPSP用のオリジナルのWindows用アーカイブプログラム。



GitHubでプログラムを修正: github.com/da-nie/Patience



MoveCard-サイクルは最大52でなく、最大53でなければなりません。 アーカイブが再充填されます。



All Articles