1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: Forty Thieves patience game
4 // Author: Chris Breeze
7 // Copyright: (c) 1993-1998 Chris Breeze
8 // Licence: wxWindows licence
9 //---------------------------------------------------------------------------
10 // Last modified: 22nd July 1998 - ported to wxWidgets 2.0
11 /////////////////////////////////////////////////////////////////////////////
13 // For compilers that support precompilation, includes "wx/wx.h".
14 #include "wx/wxprec.h"
31 Game::Game(int wins
, int games
, int score
) :
40 m_pack
= new Pack(2, 2 + 4 * (CardHeight
+ 2));
43 for (i
= 0; i
< 5; i
++) m_pack
->Shuffle();
45 m_discard
= new Discard(2, 2 + 5 * (CardHeight
+ 2));
47 for (i
= 0; i
< 8; i
++)
49 m_foundations
[i
] = new Foundation(2 + (i
/ 4) * (CardWidth
+ 2),
50 2 + (i
% 4) * (CardHeight
+ 2));
53 for (i
= 0; i
< 10; i
++)
55 m_bases
[i
] = new Base(8 + (i
+ 2) * (CardWidth
+ 2), 2);
61 // copy the input parameters for future reference
73 m_pack
->SetPos(2, 2 + 4 * (CardHeight
+ 2));
75 m_discard
->SetPos(2, 2 + 5 * (CardHeight
+ 2));
77 for (i
= 0; i
< 8; i
++)
79 m_foundations
[i
]->SetPos(2 + (i
/ 4) * (CardWidth
+ 2),
80 2 + (i
% 4) * (CardHeight
+ 2));
83 for (i
= 0; i
< 10; i
++)
85 m_bases
[i
]->SetPos(8 + (i
+ 2) * (CardWidth
+ 2), 2);
93 // Make sure we delete all objects created by the game object
100 for (i
= 0; i
< 8; i
++)
102 delete m_foundations
[i
];
104 for (i
= 0; i
< 10; i
++)
113 Set the score for a new player.
114 NB: call Deal() first if the new player is to start
117 void Game::NewPlayer(int wins
, int games
, int score
)
121 m_totalScore
= score
;
125 // Undo the last move
126 void Game::Undo(wxDC
& dc
)
131 Card
* card
= m_moves
[m_moveIndex
].dest
->RemoveTopCard(dc
);
132 m_moves
[m_moveIndex
].src
->AddCard(dc
, card
);
137 // Redo the last move
138 void Game::Redo(wxDC
& dc
)
140 if (m_moveIndex
< m_redoIndex
)
142 Card
* card
= m_moves
[m_moveIndex
].src
->RemoveTopCard(dc
);
143 if (m_moves
[m_moveIndex
].src
== m_pack
)
146 card
->TurnCard(faceup
);
148 m_moves
[m_moveIndex
].dest
->AddCard(dc
, card
);
154 void Game::DoMove(wxDC
& dc
, Pile
* src
, Pile
* dest
)
156 if (m_moveIndex
< MaxMoves
)
160 wxMessageBox(wxT("Game::DoMove() src == dest"), wxT("Debug message"),
161 wxOK
| wxICON_EXCLAMATION
);
163 m_moves
[m_moveIndex
].src
= src
;
164 m_moves
[m_moveIndex
].dest
= dest
;
167 // when we do a move any moves in redo buffer are discarded
168 m_redoIndex
= m_moveIndex
;
172 wxMessageBox(wxT("Game::DoMove() Undo buffer full"), wxT("Debug message"),
173 wxOK
| wxICON_EXCLAMATION
);
185 wxWindow
*frame
= wxTheApp
->GetTopWindow();
186 wxWindow
*canvas
= (wxWindow
*) NULL
;
190 wxWindowList::compatibility_iterator node
= frame
->GetChildren().GetFirst();
191 if (node
) canvas
= (wxWindow
*)node
->GetData();
197 // Redraw the score box to update games won
200 if (wxMessageBox(wxT("Do you wish to play again?"),
201 wxT("Well Done, You have won!"), wxYES_NO
| wxICON_QUESTION
) == wxYES
)
208 // user cancelled the dialog - exit the app
209 ((wxFrame
*)canvas
->GetParent())->Close(true);
215 void Game::DisplayScore(wxDC
& dc
)
217 wxColour bgColour
= FortyApp::BackgroundColour();
218 wxPen
* pen
= wxThePenList
->FindOrCreatePen(bgColour
, 1, wxSOLID
);
219 dc
.SetTextBackground(bgColour
);
220 dc
.SetTextForeground(FortyApp::TextColour());
221 dc
.SetBrush(FortyApp::BackgroundBrush());
224 // count the number of cards in foundations
226 for (int i
= 0; i
< 8; i
++)
228 m_currentScore
+= m_foundations
[i
]->GetNumCards();
232 m_pack
->GetTopCardPos(x
, y
);
233 x
+= 12 * CardWidth
- 105;
237 wxCoord width
, height
;
238 dc
.GetTextExtent(wxT("Average score:m_x"), &width
, &height
);
242 dc
.DrawRectangle(x
+ w
, y
, 20, 4 * h
);
245 str
.Printf(wxT("%d"), m_currentScore
);
246 dc
.DrawText(wxT("Score:"), x
, y
);
247 dc
.DrawText(str
, x
+ w
, y
);
250 str
.Printf(wxT("%d"), m_numGames
);
251 dc
.DrawText(wxT("Games played:"), x
, y
);
252 dc
.DrawText(str
, x
+ w
, y
);
255 str
.Printf(wxT("%d"), m_numWins
);
256 dc
.DrawText(wxT("Games won:"), x
, y
);
257 dc
.DrawText(str
, x
+ w
, y
);
263 average
= (2 * (m_currentScore
+ m_totalScore
) + m_numGames
) / (2 * m_numGames
);
265 str
.Printf(wxT("%d"), average
);
266 dc
.DrawText(wxT("Average score:"), x
, y
);
267 dc
.DrawText(str
, x
+ w
, y
);
271 // Shuffle the m_pack and deal the cards
277 // Reset all the piles, the undo buffer and shuffle the m_pack
280 for (i
= 0; i
< 5; i
++)
284 m_discard
->ResetPile();
285 for (i
= 0; i
< 10; i
++)
287 m_bases
[i
]->ResetPile();
289 for (i
= 0; i
< 8; i
++)
291 m_foundations
[i
]->ResetPile();
294 // Deal the initial 40 cards onto the bases
295 for (i
= 0; i
< 10; i
++)
297 for (j
= 1; j
<= 4; j
++)
299 card
= m_pack
->RemoveTopCard();
300 card
->TurnCard(faceup
);
301 m_bases
[i
]->AddCard(card
);
307 // player has started the game and then redealt
308 // and so we must add the score for this game to the total score
309 m_totalScore
+= m_currentScore
;
316 // Redraw the m_pack, discard pile, the bases and the foundations
317 void Game::Redraw(wxDC
& dc
)
321 m_discard
->Redraw(dc
);
322 for (i
= 0; i
< 8; i
++)
324 m_foundations
[i
]->Redraw(dc
);
326 for (i
= 0; i
< 10; i
++)
328 m_bases
[i
]->Redraw(dc
);
334 m_bmap
= new wxBitmap(CardWidth
, CardHeight
);
335 m_bmapCard
= new wxBitmap(CardWidth
, CardHeight
);
337 // Initialise the card bitmap to the background colour
339 memoryDC
.SelectObject(*m_bmapCard
);
340 memoryDC
.SetPen( *wxTRANSPARENT_PEN
);
341 memoryDC
.SetBrush(FortyApp::BackgroundBrush());
342 memoryDC
.DrawRectangle(0, 0, CardWidth
, CardHeight
);
343 memoryDC
.SelectObject(*m_bmap
);
344 memoryDC
.DrawRectangle(0, 0, CardWidth
, CardHeight
);
345 memoryDC
.SelectObject(wxNullBitmap
);
350 // Test to see if the point (x, y) is over the top card of one of the piles
351 // Returns pointer to the pile, or 0 if (x, y) is not over a pile
352 // or the pile is empty
353 Pile
* Game::WhichPile(int x
, int y
)
355 if (m_pack
->GetCard(x
, y
) &&
356 m_pack
->GetCard(x
, y
) == m_pack
->GetTopCard())
361 if (m_discard
->GetCard(x
, y
) &&
362 m_discard
->GetCard(x
, y
) == m_discard
->GetTopCard())
368 for (i
= 0; i
< 8; i
++)
370 if (m_foundations
[i
]->GetCard(x
, y
) &&
371 m_foundations
[i
]->GetCard(x
, y
) == m_foundations
[i
]->GetTopCard())
373 return m_foundations
[i
];
377 for (i
= 0; i
< 10; i
++)
379 if (m_bases
[i
]->GetCard(x
, y
) &&
380 m_bases
[i
]->GetCard(x
, y
) == m_bases
[i
]->GetTopCard())
389 // Left button is pressed - if cursor is over the m_pack then deal a card
390 // otherwise if it is over a card pick it up ready to be dragged - see MouseMove()
391 bool Game::LButtonDown(wxDC
& dc
, int x
, int y
)
393 m_srcPile
= WhichPile(x
, y
);
394 if (m_srcPile
== m_pack
)
396 Card
* card
= m_pack
->RemoveTopCard();
400 card
->TurnCard(faceup
);
401 m_discard
->AddCard(dc
, card
);
402 DoMove(dc
, m_pack
, m_discard
);
408 m_srcPile
->GetTopCardPos(m_xPos
, m_yPos
);
409 m_xOffset
= m_xPos
- x
;
410 m_yOffset
= m_yPos
- y
;
412 // Copy the area under the card
413 // Initialise the card bitmap to the background colour
416 memoryDC
.SelectObject(*m_bmap
);
417 m_liftedCard
= m_srcPile
->RemoveTopCard(memoryDC
, m_xPos
, m_yPos
);
420 // Draw the card in card bitmap ready for blitting onto
424 memoryDC
.SelectObject(*m_bmapCard
);
425 m_liftedCard
->Draw(memoryDC
, 0, 0);
428 return m_srcPile
!= 0;
431 // Called when the left button is double clicked
432 // If a card is under the pointer and it can move elsewhere then move it.
433 // Move onto a foundation as first choice, a populated base as second and
434 // an empty base as third choice.
435 // NB Cards in the m_pack cannot be moved in this way - they aren't in play
437 void Game::LButtonDblClk(wxDC
& dc
, int x
, int y
)
439 Pile
* pile
= WhichPile(x
, y
);
442 // Double click on m_pack is the same as left button down
445 LButtonDown(dc
, x
, y
);
449 Card
* card
= pile
->GetTopCard();
455 // if the card is an ace then try to place it next
456 // to an ace of the same suit
457 if (card
->GetPipValue() == 1)
459 for(i
= 0; i
< 4; i
++)
461 Card
* m_topCard
= m_foundations
[i
]->GetTopCard();
464 if (m_topCard
->GetSuit() == card
->GetSuit() &&
465 m_foundations
[i
+ 4] != pile
&&
466 m_foundations
[i
+ 4]->GetTopCard() == 0)
468 pile
->RemoveTopCard(dc
);
469 m_foundations
[i
+ 4]->AddCard(dc
, card
);
470 DoMove(dc
, pile
, m_foundations
[i
+ 4]);
477 // try to place the card on a foundation
478 for(i
= 0; i
< 8; i
++)
480 if (m_foundations
[i
]->AcceptCard(card
) && m_foundations
[i
] != pile
)
482 pile
->RemoveTopCard(dc
);
483 m_foundations
[i
]->AddCard(dc
, card
);
484 DoMove(dc
, pile
, m_foundations
[i
]);
488 // try to place the card on a populated base
489 for(i
= 0; i
< 10; i
++)
491 if (m_bases
[i
]->AcceptCard(card
) &&
492 m_bases
[i
] != pile
&&
493 m_bases
[i
]->GetTopCard())
495 pile
->RemoveTopCard(dc
);
496 m_bases
[i
]->AddCard(dc
, card
);
497 DoMove(dc
, pile
, m_bases
[i
]);
501 // try to place the card on any base
502 for(i
= 0; i
< 10; i
++)
504 if (m_bases
[i
]->AcceptCard(card
) && m_bases
[i
] != pile
)
506 pile
->RemoveTopCard(dc
);
507 m_bases
[i
]->AddCard(dc
, card
);
508 DoMove(dc
, pile
, m_bases
[i
]);
517 // Test to see whether the game has been won:
518 // i.e. m_pack, discard and bases are empty
519 bool Game::HaveYouWon()
521 if (m_pack
->GetTopCard()) return false;
522 if (m_discard
->GetTopCard()) return false;
523 for(int i
= 0; i
< 10; i
++)
525 if (m_bases
[i
]->GetTopCard()) return false;
528 m_totalScore
+= m_currentScore
;
534 // See whether the card under the cursor can be moved somewhere else
535 // Returns 'true' if it can be moved, 'false' otherwise
536 bool Game::CanYouGo(int x
, int y
)
538 Pile
* pile
= WhichPile(x
, y
);
539 if (pile
&& pile
!= m_pack
)
541 Card
* card
= pile
->GetTopCard();
546 for(i
= 0; i
< 8; i
++)
548 if (m_foundations
[i
]->AcceptCard(card
) && m_foundations
[i
] != pile
)
553 for(i
= 0; i
< 10; i
++)
555 if (m_bases
[i
]->GetTopCard() &&
556 m_bases
[i
]->AcceptCard(card
) &&
568 // Called when the left button is released after dragging a card
569 // Scan the piles to see if this card overlaps a pile and can be added
570 // to the pile. If the card overlaps more than one pile on which it can be placed
571 // then put it on the nearest pile.
572 void Game::LButtonUp(wxDC
& dc
, int x
, int y
)
576 // work out the position of the dragged card
580 Pile
* nearestPile
= 0;
581 int distance
= (CardHeight
+ CardWidth
) * (CardHeight
+ CardWidth
);
583 // find the nearest pile which will accept the card
585 for (i
= 0; i
< 8; i
++)
587 if (DropCard(x
, y
, m_foundations
[i
], m_liftedCard
))
589 if (m_foundations
[i
]->CalcDistance(x
, y
) < distance
)
591 nearestPile
= m_foundations
[i
];
592 distance
= nearestPile
->CalcDistance(x
, y
);
596 for (i
= 0; i
< 10; i
++)
598 if (DropCard(x
, y
, m_bases
[i
], m_liftedCard
))
600 if (m_bases
[i
]->CalcDistance(x
, y
) < distance
)
602 nearestPile
= m_bases
[i
];
603 distance
= nearestPile
->CalcDistance(x
, y
);
608 // Restore the area under the card
610 memoryDC
.SelectObject(*m_bmap
);
611 dc
.Blit(m_xPos
, m_yPos
, CardWidth
, CardHeight
,
612 &memoryDC
, 0, 0, wxCOPY
);
614 // Draw the card in its new position
618 nearestPile
->AddCard(dc
, m_liftedCard
);
619 if (nearestPile
!= m_srcPile
)
621 DoMove(dc
, m_srcPile
, nearestPile
);
626 // Return card to src pile
627 m_srcPile
->AddCard(dc
, m_liftedCard
);
637 bool Game::DropCard(int x
, int y
, Pile
* pile
, Card
* card
)
640 if (pile
->Overlap(x
, y
))
642 if (pile
->AcceptCard(card
))
651 void Game::MouseMove(wxDC
& dc
, int mx
, int my
)
656 memoryDC
.SelectObject(*m_bmap
);
658 int dx
= mx
+ m_xOffset
- m_xPos
;
659 int dy
= my
+ m_yOffset
- m_yPos
;
661 if (abs(dx
) >= CardWidth
|| abs(dy
) >= CardHeight
)
663 // Restore the area under the card
664 dc
.Blit(m_xPos
, m_yPos
, CardWidth
, CardHeight
,
665 &memoryDC
, 0, 0, wxCOPY
);
667 // Copy the area under the card in the new position
668 memoryDC
.Blit(0, 0, CardWidth
, CardHeight
,
669 &dc
, m_xPos
+ dx
, m_yPos
+ dy
, wxCOPY
);
674 dc
.Blit(m_xPos
, m_yPos
, dx
, CardHeight
, &memoryDC
, 0, 0, wxCOPY
);
678 dc
.Blit(m_xPos
+ dx
, m_yPos
, CardWidth
- dx
, dy
, &memoryDC
, dx
, 0, wxCOPY
);
679 memoryDC
.Blit(0, 0, CardWidth
- dx
, CardHeight
- dy
,
680 &memoryDC
, dx
, dy
, wxCOPY
);
681 memoryDC
.Blit(0, CardHeight
- dy
, CardWidth
- dx
, dy
,
682 &dc
, m_xPos
+ dx
, m_yPos
+ CardHeight
, wxCOPY
);
687 dc
.Blit(m_xPos
+ dx
, m_yPos
+ dy
+ CardHeight
, CardWidth
- dx
, -dy
,
688 &memoryDC
, dx
, CardHeight
+ dy
, wxCOPY
);
689 memoryDC
.Blit(0, -dy
, CardWidth
- dx
, CardHeight
+ dy
,
690 &memoryDC
, dx
, 0, wxCOPY
);
691 memoryDC
.Blit(0, 0, CardWidth
- dx
, -dy
,
692 &dc
, m_xPos
+ dx
, m_yPos
+ dy
, wxCOPY
);
694 memoryDC
.Blit(CardWidth
- dx
, 0, dx
, CardHeight
,
695 &dc
, m_xPos
+ CardWidth
, m_yPos
+ dy
, wxCOPY
);
700 dc
.Blit(m_xPos
+ CardWidth
+ dx
, m_yPos
, -dx
, CardHeight
,
701 &memoryDC
, CardWidth
+ dx
, 0, wxCOPY
);
704 dc
.Blit(m_xPos
, m_yPos
, CardWidth
+ dx
, dy
, &memoryDC
, 0, 0, wxCOPY
);
705 memoryDC
.Blit(-dx
, 0, CardWidth
+ dx
, CardHeight
- dy
,
706 &memoryDC
, 0, dy
, wxCOPY
);
707 memoryDC
.Blit(-dx
, CardHeight
- dy
, CardWidth
+ dx
, dy
,
708 &dc
, m_xPos
, m_yPos
+ CardHeight
, wxCOPY
);
713 dc
.Blit(m_xPos
, m_yPos
+ CardHeight
+ dy
, CardWidth
+ dx
, -dy
,
714 &memoryDC
, 0, CardHeight
+ dy
, wxCOPY
);
715 memoryDC
.Blit(-dx
, -dy
, CardWidth
+ dx
, CardHeight
+ dy
,
716 &memoryDC
, 0, 0, wxCOPY
);
717 memoryDC
.Blit(-dx
, 0, CardWidth
+ dx
, -dy
,
718 &dc
, m_xPos
, m_yPos
+ dy
, wxCOPY
);
720 memoryDC
.Blit(0, 0, -dx
, CardHeight
,
721 &dc
, m_xPos
+ dx
, m_yPos
+ dy
, wxCOPY
);
726 // draw the card in its new position
727 memoryDC
.SelectObject(*m_bmapCard
);
728 dc
.Blit(m_xPos
, m_yPos
, CardWidth
, CardHeight
,
729 &memoryDC
, 0, 0, wxCOPY
);
735 //----------------------------------------------//
736 // The Pack class: holds the two decks of cards //
737 //----------------------------------------------//
738 Pack::Pack(int x
, int y
) : Pile(x
, y
, 0, 0)
740 for (m_topCard
= 0; m_topCard
< NumCards
; m_topCard
++)
742 m_cards
[m_topCard
] = new Card(1 + m_topCard
/ 2, facedown
);
744 m_topCard
= NumCards
- 1;
750 Card
* temp
[NumCards
];
753 // Don't try to shuffle an empty m_pack!
754 if (m_topCard
< 0) return;
756 // Copy the cards into a temporary array. Start by clearing
757 // the array and then copy the card into a random position.
758 // If the position is occupied then find the next lower position.
759 for (i
= 0; i
<= m_topCard
; i
++)
763 for (i
= 0; i
<= m_topCard
; i
++)
765 int pos
= rand() % (m_topCard
+ 1);
769 if (pos
< 0) pos
= m_topCard
;
771 m_cards
[i
]->TurnCard(facedown
);
772 temp
[pos
] = m_cards
[i
];
776 // Copy each card back into the m_pack in a random
777 // position. If position is occupied then find nearest
778 // unoccupied position after the random position.
779 for (i
= 0; i
<= m_topCard
; i
++)
781 int pos
= rand() % (m_topCard
+ 1);
785 if (pos
> m_topCard
) pos
= 0;
787 m_cards
[pos
] = temp
[i
];
791 void Pack::Redraw(wxDC
& dc
)
796 str
.Printf(wxT("%d "), m_topCard
+ 1);
798 dc
.SetBackgroundMode( wxSOLID
);
799 dc
.SetTextBackground(FortyApp::BackgroundColour());
800 dc
.SetTextForeground(FortyApp::TextColour());
801 dc
.DrawText(str
, m_x
+ CardWidth
+ 5, m_y
+ CardHeight
/ 2);
805 void Pack::AddCard(Card
* card
)
807 if (card
== m_cards
[m_topCard
+ 1])
813 wxMessageBox(wxT("Pack::AddCard() Undo error"), wxT("Forty Thieves: Warning"),
814 wxOK
| wxICON_EXCLAMATION
);
816 card
->TurnCard(facedown
);
822 for (m_topCard
= 0; m_topCard
< NumCards
; m_topCard
++)
824 delete m_cards
[m_topCard
];
829 //------------------------------------------------------//
830 // The Base class: holds the initial pile of four cards //
831 //------------------------------------------------------//
832 Base::Base(int x
, int y
) : Pile(x
, y
, 0, 12)
838 bool Base::AcceptCard(Card
* card
)
844 if (m_cards
[m_topCard
]->GetSuit() == card
->GetSuit() &&
845 m_cards
[m_topCard
]->GetPipValue() - 1 == card
->GetPipValue())
852 // pile is empty - ACCEPT
859 //----------------------------------------------------------------//
860 // The Foundation class: holds the cards built up from the ace... //
861 //----------------------------------------------------------------//
862 Foundation::Foundation(int x
, int y
) : Pile(x
, y
, 0, 0)
867 bool Foundation::AcceptCard(Card
* card
)
873 if (m_cards
[m_topCard
]->GetSuit() == card
->GetSuit() &&
874 m_cards
[m_topCard
]->GetPipValue() + 1 == card
->GetPipValue())
879 else if (card
->GetPipValue() == 1)
881 // It's an ace and the pile is empty - ACCEPT
888 //----------------------------------------------------//
889 // The Discard class: holds cards dealt from the m_pack //
890 //----------------------------------------------------//
891 Discard::Discard(int x
, int y
) : Pile(x
, y
, 19, 0)
896 void Discard::Redraw(wxDC
& dc
)
900 if (m_dx
== 0 && m_dy
== 0)
902 m_cards
[m_topCard
]->Draw(dc
, m_x
, m_y
);
908 for (int i
= 0; i
<= m_topCard
; i
++)
910 m_cards
[i
]->Draw(dc
, x
, y
);
916 y
= m_y
+ CardHeight
/ 3;
923 Card::DrawNullCard(dc
, m_x
, m_y
);
928 void Discard::GetTopCardPos(int& x
, int& y
)
935 else if (m_topCard
> 31)
937 x
= m_x
+ m_dx
* (m_topCard
- 32);
938 y
= m_y
+ CardHeight
/ 3;
942 x
= m_x
+ m_dx
* m_topCard
;
948 Card
* Discard::RemoveTopCard(wxDC
& dc
, int m_xOffset
, int m_yOffset
)
954 card
= Pile::RemoveTopCard(dc
, m_xOffset
, m_yOffset
);
958 int topX
, topY
, x
, y
;
959 GetTopCardPos(topX
, topY
);
960 card
= Pile::RemoveTopCard();
961 card
->Erase(dc
, topX
- m_xOffset
, topY
- m_yOffset
);
963 dc
.SetClippingRegion(topX
- m_xOffset
, topY
- m_yOffset
,
964 CardWidth
, CardHeight
);
966 for (int i
= m_topCard
- 31; i
<= m_topCard
- 31 + CardWidth
/ m_dx
; i
++)
968 m_cards
[i
]->Draw(dc
, m_x
- m_xOffset
+ i
* m_dx
, m_y
- m_yOffset
);
972 m_cards
[m_topCard
]->Draw(dc
, topX
- m_xOffset
- m_dx
, topY
- m_yOffset
);
974 dc
.DestroyClippingRegion();