blob: fb376d871ef9222debc792803e3680d14c756a87 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
library game_component;
import '../logic/card.dart' as logic_card;
import '../logic/game/game.dart' show Game, GameType;
import '../logic/hearts/hearts.dart' show HeartsGame, HeartsPhase, HeartsType;
import '../logic/solitaire/solitaire.dart' show SolitaireGame, SolitairePhase;
import 'board.dart' show HeartsBoard;
import 'card.dart' as component_card;
import 'card_collection.dart'
show CardCollectionComponent, DropType, CardCollectionOrientation, AcceptCb;
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
part 'hearts/hearts.part.dart';
part 'proto/proto.part.dart';
part 'solitaire/solitaire.part.dart';
typedef void NoArgCb();
abstract class GameComponent extends StatefulComponent {
final Game game;
final NoArgCb gameEndCallback;
final double width;
final double height;
GameComponent(this.game, this.gameEndCallback,
{this.width, this.height});
}
abstract class GameComponentState<T extends GameComponent> extends State<T> {
Map<logic_card.Card, CardAnimationData> cardLevelMap;
void initState() {
super.initState();
cardLevelMap = new Map<logic_card.Card, CardAnimationData>();
config.game.updateCallback = update;
}
void _reset() {
cardLevelMap.clear();
}
// This callback is used to force the UI to draw when state changes occur
// outside of the UIs control (e.g., synced data).
void update() {
setState(() {});
}
// A helper that most subclasses use in order to quit their respective games.
void _quitGameCallback() {
setState(() {
config.gameEndCallback();
});
}
// A helper that subclasses might override to create buttons.
Widget _makeButton(String text, NoArgCb callback) {
return new FlatButton(child: new Text(text), onPressed: callback);
}
@override
Widget build(BuildContext context); // still UNIMPLEMENTED
void _cardLevelMapProcessAllVisible(List<int> visibleCardCollections) {
Game game = config.game;
for (int i = 0; i < visibleCardCollections.length; i++) {
int index = visibleCardCollections[i];
for (int j = 0; j < game.cardCollections[index].length; j++) {
_cardLevelMapProcess(game.cardCollections[index][j]);
}
}
}
void _cardLevelMapProcess(logic_card.Card logicCard) {
component_card.GlobalCardKey key = new component_card.GlobalCardKey(logicCard, component_card.CardUIType.CARD);
component_card.CardState cardState = key.currentState;
if (cardState == null) {
return; // There's nothing we can really do about this card since it hasn't drawn yet.
}
Point p = cardState.getGlobalPosition();
double z = cardState.config.z;
component_card.Card c = key.currentWidget;
assert(c == cardState.config);
CardAnimationData cad = cardLevelMap[logicCard];
if (cad == null || cad.newPoint != p || cad.z != z) {
setState(() {
cardLevelMap[logicCard] = new CardAnimationData(c, cad?.newPoint, p, z);
});
}
}
// Helper to build the card animation layer.
// Note: This isn't a component because of its dependence on Widgets.
Widget buildCardAnimationLayer(List<int> visibleCardCollections) {
// It's possible that some cards need to be moved after this build.
// If so, we can catch it in the next frame.
scheduler.requestPostFrameCallback((Duration d) {
_cardLevelMapProcessAllVisible(visibleCardCollections);
});
List<Widget> positionedCards = new List<Widget>();
// Sort the cards by z-index.
List<logic_card.Card> orderedKeys = cardLevelMap.keys.toList()..sort((logic_card.Card a, logic_card.Card b) {
double diff = cardLevelMap[a].z - cardLevelMap[b].z;
return diff.sign.toInt();
});
orderedKeys.forEach((logic_card.Card c) {
// Don't show a card if it isn't part of a visible collection.
if (!visibleCardCollections.contains(config.game.findCard(c))) {
cardLevelMap.remove(c); // It is an old card, which we can clean up.
return;
}
CardAnimationData data = cardLevelMap[c];
RenderBox box = context.findRenderObject();
Point p = data.newPoint;
Point trueP = box.globalToLocal(p);
positionedCards.add(new Positioned(
key: new GlobalObjectKey(c.toString()), //needed, or else the Positioned wrapper may be traded out and animations fail.
top: trueP.y, // must pass x and y or else it expands to the maximum Stack size.
left: trueP.x, // must pass x and y or else it expands to the maximum Stack size.
child: new component_card.ZCard(data.comp_card, data.oldPoint, data.newPoint)));
});
return new IgnorePointer(
ignoring: true,
child: new Container(
width: config.width,
height: config.height,
child: new Stack(positionedCards)
)
);
}
}
GameComponent createGameComponent(
Game game, NoArgCb gameEndCallback,
{double width, double height}) {
switch (game.gameType) {
case GameType.Proto:
return new ProtoGameComponent(game, gameEndCallback,
width: width, height: height);
case GameType.Hearts:
return new HeartsGameComponent(game, gameEndCallback,
width: width, height: height);
case GameType.Solitaire:
return new SolitaireGameComponent(game, gameEndCallback,
width: width, height: height);
default:
// We're probably not ready to serve the other games yet.
assert(false);
return null;
}
}
/// CardAnimationData contains the relevant information for a ZCard to be built.
/// It uses the comp_card's properties, the oldPoint, newPoint, and z-index to
/// determine how it needs to animate.
class CardAnimationData {
component_card.Card comp_card;
Point oldPoint;
Point newPoint;
double z;
CardAnimationData(this.comp_card, this.oldPoint, this.newPoint, this.z);
}