📜 ⬆️ ⬇️

We write solitaire "Klondike"

Nine years ago, I had the imprudence to purchase a PSP, which I was very pleased about. Only the lack of solitaire overshadowed the joy. Not that I was a lover of solitaire, but somehow I got used to laying out one of the options - “Klondike”. I had to write this solitaire myself. In the future, this is written for PSP solitaire, I ported under Windows and under QNX. In this article here I will tell you how to write such a game.

First of all, we need graphics. I am not able to draw, so I took all the graphics from the Internet. In the version for the PSP card, I deduced from the fragments (numbers and suit), and in the other versions, when porting, each map received a separate sprite. Next you need to think about the implementation of the algorithm of the solitaire itself.

Let's set the boxes where the cards can be found here with such a structure:

// 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     

We have 13 boxes in total. Each box consists of 52 compartments. Here they are in the picture:
')

Boxes on the playing field

The map visibility flag indicates that the map is open. Let us assume that if the value of the card is negative, then there are no more cards in the box.

In each box, you can place a maximum of 52 cards and another sign that there are no more cards - only 53 compartments-cells.

We need a function to move cards between boxes. Here she is:

 //---------------------------------------------------------------------------------------------------- //    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); } 

Here we are looking for the branch index from which we can take the branch index into which we can put. But this function does not check the rules for moving according to suit and card value. It’s just moving the bottom cards from one box to another.

We also need the function of moving cards from the zero box to the first. These boxes are a shop, so their contents move in a circle.

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

Here we move the card from the zero box to the first one, and if such a move fails, it means that the zero box is empty and all the cards need to be moved from the first one to the zero one.

Now we need to initialize the alignment. Let's do it like this:

 //---------------------------------------------------------------------------------------------------- //  //---------------------------------------------------------------------------------------------------- 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();//  } } 

Initially, all cards are placed in the zero box (store), then this box is scrolled to a random number, and then the card simply moves to the rest of the boxes with indices from 2 to 8. You can, of course, scatter cards so that solitaire is guaranteed to be collected, but I did not. And you can simply choose a card of 52 cards at random and put in the right box. So I also did not do it.

The above function uses another function:

 //---------------------------------------------------------------------------------------------------- //     //---------------------------------------------------------------------------------------------------- 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); } 

Well, here I think everything is clear. Of course, in order to optimize, you could always remember how much is to the card box, but there is no special point in this - the speed does not matter here, since these functions are rarely called.

In order not to keep track of which cards are visible and which are not, I have set such a function here:

 //---------------------------------------------------------------------------------------------------- //      //---------------------------------------------------------------------------------------------------- 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; } } 

It opens all the lower cards in the boxes from the second to the eighth.

The above function MoveCard, and so, in fact, it is practically not used in the game itself, as it is used only at the stage of solitaire initialization and when scrolling through the store. The thing is that in solitaire you need to transfer groups of cards, not individual cards. To move such groups there is a function ChangeBox, which requires the indication of the source box, the destination box and the index of the cell, starting with which we need to transfer maps.

 //---------------------------------------------------------------------------------------------------- //       //---------------------------------------------------------------------------------------------------- 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;//    } } 

But the complete movement of the maps, taking into account all the rules, is performed by another function using the 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; } } 

On the field assembly (boxes with indexes from 9 to 12) you can put only suited cards in order of increasing value, but the first must always be an ace. On the playing field, the colors of the suit must be opposite, the values ​​of the cards must increase, and only the king can be transferred to an empty field.

Solitaire is collected when there are exactly 13 cards in the box in the box:

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

For convenient work with boxes, there is an array with their coordinates:

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

This array is populated like this:

  //   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; } } 

In this array for each box are formed all the locations of all 52 cards of the deck. Using this array, you can easily determine what the player chose with the mouse:

 //   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); } 

Actually, this is where the writing of the logical part of solitaire ends. You can make the interface part to your liking. Since I transferred the program from the PSP (where it turns in while (1)), I personally tied the loops to the timer and gave each timer mode its own number and handler. Also asynchronously tied the OnPaint test from the timer. This was the easiest way to do it when porting.

In the archive program for Windows, for QNX and the original for PSP.

UPD. I apologize, while editing the text for macros, I made a mistake in the function MoveCard - the cycle should not be 52, but 53 should be. Archive perezalit.

Source: https://habr.com/ru/post/330470/


All Articles