blob: cb634d4fc609913db5ad26ff082b09c0893269fa [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.
import '../logic/card.dart' as logic_card;
import '../logic/game.dart'
show Game, GameType, Viewer, HeartsGame, HeartsPhase;
import '../src/syncbase/syncbase_echo_impl.dart' show SyncbaseEchoImpl;
//import 'board.dart' show Board;
import 'card_collection.dart'
show CardCollectionComponent, DropType, Orientation;
import 'draggable.dart' show Draggable;
import 'package:sky/widgets.dart';
import 'package:sky/theme/colors.dart' as colors;
abstract class GameComponent extends StatefulComponent {
Game game;
Function gameEndCallback;
GameComponent(this.game, this.gameEndCallback) {
game.updateCallback = update;
}
void update() {
setState(() {});
}
void syncConstructorArguments(GameComponent other) {
this.game = other.game;
}
// A helper that most subclasses use in order to quit their respective games.
void _quitGameCallback() {
setState(() {
this.gameEndCallback();
});
}
Widget _makeButton(String text, Function callback) {
return new FlatButton(child: new Text(text), onPressed: callback);
}
Widget build();
}
GameComponent createGameComponent(Game game, Function gameEndCallback) {
switch (game.gameType) {
case GameType.Proto:
return new ProtoGameComponent(game, gameEndCallback);
case GameType.Hearts:
return new HeartsGameComponent(game, gameEndCallback);
case GameType.SyncbaseEcho:
return new SyncbaseEchoGameComponent(game, gameEndCallback);
default:
// We're probably not ready to serve the other games yet.
assert(false);
return null;
}
}
class ProtoGameComponent extends GameComponent {
ProtoGameComponent(Game game, Function cb) : super(game, cb);
Widget build() {
List<Widget> cardCollections = new List<Widget>();
cardCollections.add(new Text(game.debugString));
for (int i = 0; i < 4; i++) {
List<logic_card.Card> cards = game.cardCollections[i];
CardCollectionComponent c = new CardCollectionComponent(cards,
game.playerNumber == i, Orientation.horz, _makeGameMoveCallback,
dragChildren: true, acceptType: DropType.card);
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, _makeGameMoveCallback,
dragChildren: true, acceptType: DropType.card)));
cardCollections.add(_makeDebugButtons());
return new Container(
decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
child: new Flex(cardCollections, direction: FlexDirection.vertical));
}
void _makeGameMoveCallback(logic_card.Card card, List<logic_card.Card> dest) {
setState(() {
try {
game.move(card, dest);
} catch (e) {
print("You can't do that! ${e.toString()}");
game.debugString = e.toString();
}
});
}
Widget _makeDebugButtons() => new Flex([
new Text('P${game.playerNumber}'),
_makeButton('Switch View', _switchPlayersCallback),
_makeButton('Quit', _quitGameCallback)
]);
void _switchPlayersCallback() {
setState(() {
game.playerNumber = (game.playerNumber + 1) % 4;
});
}
}
class SyncbaseEchoGameComponent extends GameComponent {
SyncbaseEchoImpl s;
SyncbaseEchoGameComponent(Game game, Function cb) : super(game, cb);
Widget build() {
if (s == null) {
s = new SyncbaseEchoImpl(game);
}
return buildSyncbaseEcho();
}
Widget buildSyncbaseEcho() {
return new Container(
decoration:
const BoxDecoration(backgroundColor: const Color(0xFF00ACC1)),
child: new Flex([
new RaisedButton(child: new Text('doEcho'), onPressed: s.doEcho),
new Text('sendMsg: ${s.sendMsg}'),
new Text('recvMsg: ${s.recvMsg}'),
new RaisedButton(child: new Text('doPutGet'), onPressed: s.doPutGet),
new Text('putStr: ${s.putStr}'),
new Text('getStr: ${s.getStr}'),
_makeButton('Quit', _quitGameCallback)
], direction: FlexDirection.vertical));
}
}
class HeartsGameComponent extends GameComponent {
List<logic_card.Card> passingCards = new List<logic_card.Card>();
HeartsGameComponent(Game game, Function cb) : super(game, cb);
Widget build() {
return buildHearts();
// Does NOT work in checked mode since it has a Stack of Positioned Stack with Positioned Widgets.
// Issue and possible workaround? https://github.com/domokit/sky_engine/issues/732
// return new Board(1, [2, 3, 4], [1, 2, 3, 4]);
// For GameType.Board
}
// Passing between the temporary pass list and the player's hand.
// Does not actually move anything in game logic terms.
void _uiPassCardCallback(logic_card.Card card, List<logic_card.Card> dest) {
setState(() {
if (dest == passingCards &&
!passingCards.contains(card) &&
passingCards.length < 3) {
passingCards.add(card);
} else if (dest != passingCards && passingCards.contains(card)) {
passingCards.remove(card);
}
});
}
// This shouldn't always be here, but for now, we have little choice.
void _switchPlayersCallback() {
setState(() {
game.playerNumber = (game.playerNumber + 1) % 4;
passingCards.clear(); // Just for sanity.
});
}
void _makeGamePassCallback(
List<logic_card.Card> cards, List<logic_card.Card> dest) {
setState(() {
try {
HeartsGame game = this.game as HeartsGame;
game.passCards(cards);
passingCards.clear();
} catch (e) {
print("You can't do that! ${e.toString()}");
game.debugString = e.toString();
}
});
}
void _makeGameTakeCallback(
List<logic_card.Card> cards, List<logic_card.Card> dest) {
setState(() {
try {
HeartsGame game = this.game as HeartsGame;
game.takeCards();
} catch (e) {
print("You can't do that! ${e.toString()}");
game.debugString = e.toString();
}
});
}
void _makeGameMoveCallback(logic_card.Card card, List<logic_card.Card> dest) {
setState(() {
try {
game.move(card, dest);
} catch (e) {
print("You can't do that! ${e.toString()}");
game.debugString = e.toString();
}
});
}
void _endRoundDebugCallback() {
setState(() {
HeartsGame game = this.game as HeartsGame;
game.jumpToScorePhaseDebug();
});
}
Widget _makeDebugButtons() => new Flex([
new Flexible(flex: 1, child: new Text('P${game.playerNumber}')),
new Flexible(
flex: 5, child: _makeButton('Switch View', _switchPlayersCallback)),
new Flexible(
flex: 5,
child: _makeButton('Dump Log', () => print(this.game.gamelog))),
new Flexible(
flex: 5, child: _makeButton('End Round', _endRoundDebugCallback)),
new Flexible(flex: 4, child: _makeButton('Quit', _quitGameCallback))
]);
Widget _makeButton(String text, Function callback) {
return new FlatButton(child: new Text(text), onPressed: callback);
}
Widget buildHearts() {
HeartsGame game = this.game as HeartsGame;
switch (game.phase) {
case HeartsPhase.Deal:
return showDeal();
case HeartsPhase.Pass:
return showPass();
case HeartsPhase.Take:
return showTake();
case HeartsPhase.Play:
return showPlay();
case HeartsPhase.Score:
return showScore();
default:
assert(false);
return null;
}
}
Widget showPlay() {
HeartsGame game = this.game as HeartsGame;
List<Widget> cardCollections = new List<Widget>();
cardCollections.add(new Text(game.debugString));
for (int i = 0; i < 4; i++) {
List<logic_card.Card> cards = game.cardCollections[i];
CardCollectionComponent c = new CardCollectionComponent(cards,
game.playerNumber == i, Orientation.horz, _makeGameMoveCallback,
dragChildren: game.whoseTurn == i);
cardCollections.add(c); // flex
}
List<Widget> plays = new List<Widget>();
for (int i = 0; i < 4; i++) {
DropType t = DropType.none;
if (game.playerNumber == i) {
t = DropType.card;
}
plays.add(new Container(
decoration: new BoxDecoration(
backgroundColor:
game.whoseTurn == i ? colors.Blue[500] : colors.Green[500],
borderRadius: 5.0),
child: new CardCollectionComponent(
game.cardCollections[i + HeartsGame.OFFSET_PLAY],
true,
Orientation.show1,
_makeGameMoveCallback,
acceptType: t)));
}
cardCollections.add(new Flex(plays));
cardCollections.add(_makeDebugButtons());
return new Container(
decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
child: new Flex(cardCollections, direction: FlexDirection.vertical));
}
Widget showScore() {
HeartsGame game = this.game as HeartsGame;
Widget w;
if (game.hasGameEnded) {
w = new Text("Game Over!");
} else if (game.ready[game.playerNumber]) {
w = new Text("Waiting for other players...");
} else {
w = _makeButton('Ready?', game.setReadyUI);
}
return new Container(
decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
child: new Flex([
new Text('Player ${game.playerNumber}'),
// TODO(alexfandrianto): we want to show round by round, deltas too, don't we?
new Text('${game.scores}'),
w,
_makeButton("Return to Lobby", _quitGameCallback),
_makeDebugButtons()
], direction: FlexDirection.vertical));
}
Widget showDeal() {
HeartsGame game = this.game as HeartsGame;
return new Container(
decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
child: new Flex([
new Text('Player ${game.playerNumber}'),
_makeButton('Deal', game.dealCards),
_makeDebugButtons()
], direction: FlexDirection.vertical));
}
Widget showPass() {
HeartsGame game = this.game as HeartsGame;
List<logic_card.Card> passCards =
game.cardCollections[game.playerNumber + HeartsGame.OFFSET_PASS];
List<logic_card.Card> playerCards = game.cardCollections[game.playerNumber];
List<logic_card.Card> remainingCards = new List<logic_card.Card>();
playerCards.forEach((logic_card.Card c) {
if (!passingCards.contains(c)) {
remainingCards.add(c);
}
});
bool hasPassed = passCards.length != 0;
// TODO(alexfandrianto): You can pass as many times as you want... which is silly.
// Luckily, later passes shouldn't do anything.
return new Container(
decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
child: new Flex(<Widget>[
new Text(game.debugString),
new CardCollectionComponent(
passCards, true, Orientation.horz, _makeGamePassCallback,
acceptType: DropType.card_collection),
new Draggable<CardCollectionComponent>(new CardCollectionComponent(
passingCards, true, Orientation.horz, _uiPassCardCallback,
dragChildren: !hasPassed, acceptType: DropType.card)),
new CardCollectionComponent(
remainingCards, true, Orientation.horz, _uiPassCardCallback,
dragChildren: !hasPassed, acceptType: DropType.card),
_makeDebugButtons()
], direction: FlexDirection.vertical));
}
Widget showTake() {
HeartsGame game = this.game as HeartsGame;
List<logic_card.Card> playerCards = game.cardCollections[game.playerNumber];
List<logic_card.Card> takeCards =
game.cardCollections[game.takeTarget + HeartsGame.OFFSET_PASS];
bool hasTaken = takeCards.length == 0;
Widget take = new CardCollectionComponent(
takeCards, true, Orientation.horz, _makeGameTakeCallback);
if (!hasTaken) {
take = new Draggable<CardCollectionComponent>(take);
}
return new Container(
decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
child: new Flex(<Widget>[
new Text(game.debugString),
take,
new CardCollectionComponent(
playerCards, true, Orientation.horz, _makeGameTakeCallback,
dragChildren: true, acceptType: DropType.card_collection),
_makeDebugButtons()
], direction: FlexDirection.vertical));
}
}