Skip to content

Commit

Permalink
fix: Klondike tutorial to use built-in world and camera (#2813)
Browse files Browse the repository at this point in the history
Firstly, (re Issue #2799) Flame 1.9.x broke the Klondike Tutorial, which was not set up to use the new built-in world and camera. So I modified the KlondikeGame's onLoad() code to use the built-in world and camera and no longer create its own World and CameraComponent. I also updated the step2.md documentation file, which instructs new users of Flame on how to use the world and the camera, how to set up a game-oriented co-ordinate system and how to fit it into the device screen.

Secondly, (re Issue #2798) Klondike was not adhering to the design stated in the Tutorial, that all 52 Card objects would be owned by the KlondikeGame. During the shuffle and deal sequence it was using removeLast() on the List\<Card\>cards, where the cards are kept, the result being that only 24 of the 52 cards were left in the list after the deal. I have fixed the bug and updated the Tutorial documentation (step4) which discusses how the deal is coded.

This bug does not affect the gameplay as it stands, but it does block the way to future features at the game level, such as an option to re-deal or a win-animation where all cards fly off the screen.
  • Loading branch information
IanWadham committed Oct 15, 2023
1 parent c2f3f04 commit eca7e41
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 38 deletions.
27 changes: 13 additions & 14 deletions doc/tutorials/klondike/app/lib/step4/klondike_game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,15 @@ class KlondikeGame extends FlameGame {
),
);

final world = World()
..add(stock)
..add(waste)
..addAll(foundations)
..addAll(piles);
add(world);
world.add(stock);
world.add(waste);
world.addAll(foundations);
world.addAll(piles);

final camera = CameraComponent(world: world)
..viewfinder.visibleGameSize =
Vector2(cardWidth * 7 + cardGap * 8, 4 * cardHeight + 3 * cardGap)
..viewfinder.position = Vector2(cardWidth * 3.5 + cardGap * 4, 0)
..viewfinder.anchor = Anchor.topCenter;
add(camera);
camera.viewfinder.visibleGameSize =
Vector2(cardWidth * 7 + cardGap * 8, 4 * cardHeight + 3 * cardGap);
camera.viewfinder.position = Vector2(cardWidth * 3.5 + cardGap * 4, 0);
camera.viewfinder.anchor = Anchor.topCenter;

final cards = [
for (var rank = 1; rank <= 13; rank++)
Expand All @@ -66,13 +62,16 @@ class KlondikeGame extends FlameGame {
cards.shuffle();
world.addAll(cards);

var cardToDeal = cards.length - 1;
for (var i = 0; i < 7; i++) {
for (var j = i; j < 7; j++) {
piles[j].acquireCard(cards.removeLast());
piles[j].acquireCard(cards[cardToDeal--]);
}
piles[i].flipTopCard();
}
cards.forEach(stock.acquireCard);
for (var n = 0; n <= cardToDeal; n++) {
stock.acquireCard(cards[n]);
}
}
}

Expand Down
35 changes: 16 additions & 19 deletions doc/tutorials/klondike/step2.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,16 +220,15 @@ are calculated using simple arithmetics. This should all happen inside the
);
```

Then we create the main `World` component, add to it all the components that
we just created, and finally add the `world` to the game.
Since Flame version 1.9.0, `FlameGame` sets up default `world` and `camera`
objects. `KlondikeGame` is an extension of `FlameGame`, so we can add to that
`world` all the components that we just created.

```dart
final world = World()
..add(stock)
..add(waste)
..addAll(foundations)
..addAll(piles);
add(world);
world.add(stock)
world.add(waste)
world.addAll(foundations)
world.addAll(piles);
```

```{note}
Expand All @@ -247,11 +246,11 @@ If you don't `await` the future from `.add()`, then the component will be added
to the game anyways, and in the same amount of time.
```

Lastly, we create a camera object to look at the `world`. Internally, the camera
consists of two parts: a **viewport** and a **viewfinder**. The default viewport
is `MaxViewport`, which takes up the entire available screen size -- this is
exactly what we need for our game, so no need to change anything. The
viewfinder, on the other hand, needs to be set up to properly take the
Lastly, we use FlameGame's `camera` object to look at the `world`. Internally,
the camera consists of two parts: a **viewport** and a **viewfinder**. The
default viewport is `MaxViewport`, which takes up the entire available screen
size -- this is exactly what we need for our game, so no need to change
anything. The viewfinder, on the other hand, needs to be set up to take the
dimensions of the underlying world into account.

We want the entire card layout to be visible on the screen without the need to
Expand Down Expand Up @@ -281,12 +280,10 @@ but if the screen is too tall, we want the content to be aligned at the
top.

```dart
final camera = CameraComponent(world: world)
..viewfinder.visibleGameSize =
Vector2(cardWidth * 7 + cardGap * 8, 4 * cardHeight + 3 * cardGap)
..viewfinder.position = Vector2(cardWidth * 3.5 + cardGap * 4, 0)
..viewfinder.anchor = Anchor.topCenter;
add(camera);
camera.viewfinder.visibleGameSize =
Vector2(cardWidth * 7 + cardGap * 8, 4 * cardHeight + 3 * cardGap);
camera.viewfinder.position = Vector2(cardWidth * 3.5 + cardGap * 4, 0);
camera.viewfinder.anchor = Anchor.topCenter;
```

If you run the game now, you should see the placeholders for where the various
Expand Down
23 changes: 18 additions & 5 deletions doc/tutorials/klondike/step4.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,19 +425,32 @@ method so that it looks like this:
cards.shuffle();
world.addAll(cards);
int cardToDeal = cards.length - 1;
for (var i = 0; i < 7; i++) {
for (var j = i; j < 7; j++) {
piles[j].acquireCard(cards.removeLast());
piles[j].acquireCard(cards[cardToDeal--]);
}
piles[i].flipTopCard();
}
cards.forEach(stock.acquireCard);
for(int n = 0; n <= cardToDeal; n++) {
stock.acquireCard(cards[n]);
}
}
```

Note how we remove the cards from the deck and place them into `TableauPile`s one by one, and only
after that we put the remaining cards into the stock. Also, the `flipTopCard` method in the
`TableauPile` class is as trivial as it sounds:
Note how we deal the cards from the deck and place them into `TableauPile`s one by one, and only
after that we put the remaining cards into the stock.

Recall that we decided earlier that all the cards would be owned by the `KlondikeGame` itself. So
they are put into a generated List structure called `cards`, shuffled and added to the `world`. This
List should always have 52 cards in it, so a descending index `cardToDeal` is used to deal 28 cards
one by one from the top of the deck into piles that acquire references to the cards in the deck. An
ascending index is used to deal the remaining 24 cards into the stock in correct shuffled order. At
the end of the deal there are still 52 `Card` objects in the `cards` list. In the card piles we
used `removeList()` to retrieve a card from a pile, but not here because it would remove cards
from `KlondikeGame`'s ownership.

The `flipTopCard` method in the `TableauPile` class is as trivial as it sounds:

```dart
void flipTopCard() {
Expand Down

0 comments on commit eca7e41

Please sign in to comment.