This adds a few starting pages for the Hearts app.
- Welcome Screen
- Game Chooser
- We skip Arrange Players entirely, but can add that later.
- Then we can play "Hearts". Or "Proto" as I'm calling it now.
I also split apart Game into subclasses for ease of use.
ProtoGame and HeartsGame are subclasses that override its "move" method.
diff --git a/lib/components/croupier.dart b/lib/components/croupier.dart
new file mode 100644
index 0000000..38909a8
--- /dev/null
+++ b/lib/components/croupier.dart
@@ -0,0 +1,78 @@
+import '../logic/croupier.dart' as logic_croupier;
+import '../logic/game.dart' as logic_game;
+import 'package:sky/widgets.dart' show FlatButton;
+import 'package:sky/widgets/basic.dart';
+import 'game.dart' show GameComponent;
+import 'dart:sky' as sky;
+
+class CroupierComponent extends StatefulComponent {
+ logic_croupier.Croupier croupier;
+
+ CroupierComponent(this.croupier) : super();
+
+ void syncFields(CroupierComponent other) {
+ croupier = other.croupier;
+ }
+
+ Function setStateCallbackFactory(logic_croupier.CroupierState s, [var data = null]) {
+ return () => setState(() {
+ croupier.setState(s, data);
+ });
+ }
+
+ Widget build() {
+ switch (croupier.state) {
+ case logic_croupier.CroupierState.Welcome:
+ // in which we show them a UI to start a new game, join a game, or change some settings.
+ return new Container(
+ padding: new EdgeDims.only(top: sky.view.paddingTop),
+ child: new Flex([
+ new FlatButton(
+ child: new Text('Create Game'),
+ onPressed: setStateCallbackFactory(logic_croupier.CroupierState.ChooseGame)
+ ),
+ new FlatButton(
+ child: new Text('Join Game')
+ ),
+ new FlatButton(
+ child: new Text('Settings')
+ )
+ ], direction: FlexDirection.vertical
+ )
+ );
+ case logic_croupier.CroupierState.Settings:
+ return null; // in which we let them pick an avatar, name, and color. And return to the previous screen after (NOT IMPLEMENTED YET)
+ case logic_croupier.CroupierState.ChooseGame:
+ // in which we let them pick a game out of the many possible games... There aren't that many.
+ return new Container(
+ padding: new EdgeDims.only(top: sky.view.paddingTop),
+ child: new Flex([
+ new FlatButton(
+ child: new Text('Proto'),
+ onPressed: setStateCallbackFactory(logic_croupier.CroupierState.PlayGame, logic_game.GameType.Proto)
+ ),
+ new FlatButton(
+ child: new Text('Hearts'),
+ onPressed: setStateCallbackFactory(logic_croupier.CroupierState.PlayGame, logic_game.GameType.Hearts)
+ ),
+ new FlatButton(
+ child: new Text('Poker')
+ ),
+ new FlatButton(
+ child: new Text('Solitaire')
+ )
+ ], direction: FlexDirection.vertical
+ )
+ );
+ case logic_croupier.CroupierState.AwaitGame:
+ return null; // in which players wait for game invitations to arrive.
+ case logic_croupier.CroupierState.ArrangePlayers:
+ return null; // If needed, lists the players around and what devices they'd like to use.
+ case logic_croupier.CroupierState.PlayGame:
+ return new GameComponent(croupier.game); // Asks the game UI to draw itself.
+ default:
+ assert(false);
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/components/game.dart b/lib/components/game.dart
index defc49b..ab5eea5 100644
--- a/lib/components/game.dart
+++ b/lib/components/game.dart
@@ -23,10 +23,13 @@
Widget build() {
switch (game.gameType) {
+ case GameType.Proto:
+ return buildProto();
case GameType.Hearts:
return buildHearts();
- // Code to display board:
- //return new Board(1, [2,3,4], [1, 2, 3, 4]); Does NOT work in checked mode since it has a Stack of Positioned Stack with Positioned Widgets.
+ case GameType.Board:
+ // Does NOT work in checked mode since it has a Stack of Positioned Stack with Positioned Widgets.
+ return new Board(1, [2,3,4], [1, 2, 3, 4]);
default:
return null; // unsupported
}
@@ -44,7 +47,7 @@
});
}
- Widget buildHearts() {
+ Widget buildProto() {
List<Widget> cardCollections = new List<Widget>();
// debugString
@@ -100,4 +103,31 @@
child: new Flex(cardCollections, direction: FlexDirection.vertical)//new Stack(cardCollections)
);
}
+
+ Widget buildHearts() {
+ List<Widget> cardCollections = new List<Widget>();
+
+ cardCollections.add(new Text(game.debugString));
+
+ for (int i = 0; i < 4; i++) {
+ List<Card> cards = game.cardCollections[i];
+ CardCollectionComponent c = new CardCollectionComponent(cards, game.playerNumber == i, Orientation.horz, _updateGameCallback);
+ cardCollections.add(c); // flex
+ }
+
+ cardCollections.add(new Container(
+ decoration: new BoxDecoration(backgroundColor: colors.Green[500], borderRadius: 5.0),
+ child: new CardCollectionComponent(game.cardCollections[4], true, Orientation.show1, _updateGameCallback)
+ ));
+
+ cardCollections.add(new FlatButton(
+ child: new Text('Switch View'),
+ onPressed: _switchPlayersCallback
+ ));
+
+ return new Container(
+ decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
+ child: new Flex(cardCollections, direction: FlexDirection.vertical)
+ );
+ }
}
diff --git a/lib/logic/croupier.dart b/lib/logic/croupier.dart
new file mode 100644
index 0000000..9a548cc
--- /dev/null
+++ b/lib/logic/croupier.dart
@@ -0,0 +1,65 @@
+import 'game.dart' show Game, GameType;
+
+enum CroupierState {
+ Welcome, Settings, ChooseGame, AwaitGame, ArrangePlayers, PlayGame
+}
+
+class Croupier {
+ CroupierState state;
+ Settings settings;
+ Game game; // null until chosen
+
+ Croupier() {
+ state = CroupierState.Welcome;
+ // settings = new Settings.load(?); // Give it in the croupier constructor. The app itself should load this info.
+ }
+
+ // Sets the next part of croupier state.
+ // Depending on the originating state, data can contain extra information that we need.
+ void setState(CroupierState nextState, var data) {
+ switch (state) {
+ case CroupierState.Welcome:
+ // data should be empty.
+ assert(data == null);
+ break;
+ case CroupierState.Settings:
+ // data should be empty.
+ // All settings changes affect the croupier settings directly without changing app state.
+ assert(data == null);
+ break;
+ case CroupierState.ChooseGame:
+ // data should be the game id here.
+ GameType gt = data as GameType;
+ game = new Game(gt, 0); // Start as player 0 of whatever game type.
+ break;
+ case CroupierState.AwaitGame:
+ // data would probably be the game id again.
+ GameType gt = data as GameType;
+ game = new Game(gt, 0); // Start as player 0 of whatever game type.
+ break;
+ case CroupierState.ArrangePlayers:
+ // data should be empty.
+ // All rearrangements affect the Game's player number without changing app state.
+ break;
+ case CroupierState.PlayGame:
+ // data should be empty.
+ // The signal to start really isn't anything special.
+ break;
+ default:
+ assert(false);
+ }
+
+ state = nextState;
+ }
+}
+
+class Settings {
+ String avatar;
+ String name;
+ String color; // in hex?
+
+ Settings(this.avatar, this.name, this.color);
+
+ // Settings.load(String data) {}
+ // String save() { return null; }
+}
\ No newline at end of file
diff --git a/lib/logic/game.dart b/lib/logic/game.dart
index 08c1285..df02af9 100644
--- a/lib/logic/game.dart
+++ b/lib/logic/game.dart
@@ -1,8 +1,11 @@
import 'card.dart' show Card;
import 'dart:math' show Random;
+// Note: Proto and Board are "fake" games intended to demonstrate what we can do.
+// Proto is just a drag cards around "game".
+// Board is meant to show how one _could_ layout a game of Hearts. This one is not hooked up very well yet.
enum GameType {
- Hearts
+ Proto, Hearts, Poker, Solitaire, Board
}
/// A game consists of multiple decks and tracks a single deck of cards.
@@ -15,27 +18,28 @@
final Random random = new Random();
final GameLog gamelog = new GameLog();
int playerNumber;
- Function updateCallback;
String debugString = 'hello?';
- Game.hearts(this.playerNumber) : gameType = GameType.Hearts {
+ Function updateCallback; // Used to inform components of when a change has occurred. This is especially important when something non-UI related changes what should be drawn.
+
+ factory Game(GameType gt, int pn) {
+ switch (gt) {
+ case GameType.Proto:
+ return new ProtoGame(pn);
+ case GameType.Hearts:
+ return new HeartsGame(pn);
+ default:
+ assert(false);
+ return null;
+ }
+ }
+
+ // A super constructor, don't call this unless you're a subclass.
+ Game._create(this.gameType, this.playerNumber, int numCollections) {
gamelog.setGame(this);
-
- // playerNumber would be used in a real game, but I have to ignore it for debugging.
- // It would determine faceUp/faceDown status.
-
- deck.shuffle();
- cardCollections.add(new List<Card>()); // Player A
- cardCollections.add(new List<Card>()); // Player B
- cardCollections.add(new List<Card>()); // Player C
- cardCollections.add(new List<Card>()); // Player D
- cardCollections.add(new List<Card>()); // an empty pile
- cardCollections.add(new List<Card>()); // a hidden pile!
-
- /*deal(0, 8);
- deal(1, 5);
- deal(2, 4);
- deal(3, 1);*/
+ for (int i = 0; i < numCollections; i++) {
+ cardCollections.add(new List<Card>());
+ }
}
List<Card> deckPeek(int numCards) {
@@ -44,10 +48,42 @@
return cards;
}
+ // Which card collection has the card?
+ int findCard(Card card) {
+ for (int i = 0; i < cardCollections.length; i++) {
+ if (cardCollections[i].contains(card)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ // UNIMPLEMENTED: Let subclasses override this?
+ // Or is it improper to do so?
+ void move(Card card, List<Card> dest) {}
+}
+
+class ProtoGame extends Game {
+ ProtoGame(int playerNumber) : super._create(GameType.Proto, playerNumber, 6) {
+ // playerNumber would be used in a real game, but I have to ignore it for debugging.
+ // It would determine faceUp/faceDown status.faceDown
+
+ // TODO: Set the number of piles created to either 9 (1x per player, 1 discard, 4 play piles) or 12 (2x per player, 4 play piles)
+ // But for now, we will deal with 6. 1x per player, 1 discard, and 1 undrawn pile.
+
+ // We do some arbitrary things here... Just for setup.
+ deck.shuffle();
+ deal(0, 8);
+ deal(1, 5);
+ deal(2, 4);
+ deal(3, 1);
+ }
+
void deal(int playerId, int numCards) {
gamelog.add(new HeartsCommand.deal(playerId, this.deckPeek(numCards)));
}
+ // Overrides Game's move method with the "move" logic for the card dragging prototype.
void move(Card card, List<Card> dest) {
// The first step is to find the card. Where is it?
// then we can remove it and add to the dest.
@@ -61,23 +97,51 @@
gamelog.add(new HeartsCommand.pass(i, destId, <Card>[card]));
- /*cardCollections[i].remove(card);
- dest.add(card);*/
debugString = 'Move ${i} ${card.toString()}';
print(debugString);
}
+}
- // Which card collection has the card?
- int findCard(Card card) {
- for (int i = 0; i < cardCollections.length; i++) {
- if (cardCollections[i].contains(card)) {
- return i;
- }
+class HeartsGame extends Game {
+ HeartsGame(int playerNumber) : super._create(GameType.Hearts, playerNumber, 6) {
+ // playerNumber would be used in a real game, but I have to ignore it for debugging.
+ // It would determine faceUp/faceDown status.faceDown
+
+ // TODO: Set the number of piles created to either 9 (1x per player, 1 discard, 4 play piles) or 12 (2x per player, 4 play piles)
+ // But for now, we will deal with 6. 1x per player, 1 discard, and 1 undrawn pile.
+
+ // We do some arbitrary things here... Just for setup.
+ deck.shuffle();
+ deal(0, 8);
+ deal(1, 5);
+ deal(2, 4);
+ deal(3, 1);
+ }
+
+ void deal(int playerId, int numCards) {
+ gamelog.add(new HeartsCommand.deal(playerId, this.deckPeek(numCards)));
+ }
+
+ // Overrides Game's move method with the "move" logic for Hearts.
+ void move(Card card, List<Card> dest) {
+ // The first step is to find the card. Where is it?
+ // then we can remove it and add to the dest.
+ debugString = 'Moving... ${card.toString()}';
+ int i = findCard(card);
+ if (i == -1) {
+ debugString = 'NO... ${card.toString()}';
+ return;
}
- return -1;
+ int destId = cardCollections.indexOf(dest);
+
+ gamelog.add(new HeartsCommand.pass(i, destId, <Card>[card]));
+
+ debugString = 'Move ${i} ${card.toString()}';
+ print(debugString);
}
}
+
class GameLog {
Game game;
List<GameCommand> log = new List<GameCommand>();
diff --git a/lib/main.dart b/lib/main.dart
index 7a6ea14..ef523a3 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -7,22 +7,24 @@
//import 'package:sky/theme/colors.dart' as colors;
import 'package:sky/widgets.dart';
-import 'logic/game.dart' show Game, HeartsCommand;
-import 'components/game.dart' show GameComponent;
+//import 'logic/game.dart' show Game, HeartsCommand;
+//import 'components/game.dart' show GameComponent;
+import 'logic/croupier.dart' show Croupier;
+import 'components/croupier.dart' show CroupierComponent;
-import 'dart:io';
-import 'dart:convert';
-import 'dart:async';
+//import 'dart:io';
+//import 'dart:convert';
+//import 'dart:async';
class CroupierApp extends App {
- Game game;
+ Croupier croupier;
CroupierApp() : super() {
- this.game = new Game.hearts(0);
+ this.croupier = new Croupier();
}
Widget build() {
- return new GameComponent(this.game);
+ return new CroupierComponent(this.croupier);
}
}
@@ -31,7 +33,7 @@
// Had difficulty reading from a file, so I can use this to simulate it.
// Seems like only NetworkImage exists, but why not also have NetworkFile?
- List<String> commands = <String>[
+ /*List<String> commands = <String>[
"Deal:0:classic h1:classic h2:classic h3:classic h4:END",
"Deal:1:classic d1:classic d2:classic d3:classic d4:END",
"Deal:2:classic s1:classic s2:classic s3:classic s4:END",
@@ -49,10 +51,8 @@
app.game.gamelog.add(new HeartsCommand(commands[i]));
});
}
- });
+ });*/
runApp(app);
-
-
}
\ No newline at end of file