Skip to content

Commit

Permalink
docs(example): add pickleball score tracking example (#88)
Browse files Browse the repository at this point in the history
  • Loading branch information
GregoryConrad committed Jan 31, 2024
1 parent 246e9ac commit 7ab1ed6
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .github/dependabot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,7 @@ updates:
directory: "/examples/presentation"
schedule:
interval: weekly
- package-ecosystem: pub
directory: "/examples/scorus"
schedule:
interval: weekly
63 changes: 63 additions & 0 deletions examples/scorus/lib/game_management.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import 'package:rearch/rearch.dart';
import 'package:scorus/score.dart';
import 'package:scorus/serving_player.dart';

/// Represents a team.
enum Team {
/// The first team.
team1,

/// The second team.
team2,
}

/// Provides [next].
extension NextTeam on Team {
/// Returns the next [Team] up for possesion.
Team get next => switch (this) {
Team.team1 => Team.team2,
Team.team2 => Team.team1,
};
}

/// Manages the current team with possesion.
({
Team teamWithPossesion,
void Function() givePossesionToNextTeam,
void Function() resetTeamWithPossesion,
}) teamWithPossesionManager(CapsuleHandle use) {
const startingTeam = Team.team1;
final (team, setTeam) = use.state(startingTeam);
return (
teamWithPossesion: team,
givePossesionToNextTeam: () => setTeam(team.next),
resetTeamWithPossesion: () => setTeam(startingTeam),
);
}

/// Returns which team and which player on that team is serving.
(Team, ServingPlayer) currServingTeamAndPlayerCapsule(CapsuleHandle use) {
final teamWithPossesion = use(teamWithPossesionManager).teamWithPossesion;
final servingPlayerOnTeamWithPossesion = switch (teamWithPossesion) {
Team.team1 => use(team1ServingPlayerManager).servingPlayer,
Team.team2 => use(team2ServingPlayerManager).servingPlayer,
};
return (teamWithPossesion, servingPlayerOnTeamWithPossesion);
}

/// Action capsule that returns a function to reset the game.
void Function() resetGameAction(CapsuleHandle use) {
final resets = [
use(team1ScoreManager).resetScore,
use(team2ScoreManager).resetScore,
use(team1ServingPlayerManager).resetServingPlayer,
use(team2ServingPlayerManager).resetServingPlayer,
use(teamWithPossesionManager).resetTeamWithPossesion,
];
final runTxn = use.transactionRunner();
return () => runTxn(() {
for (final reset in resets) {
reset();
}
});
}
131 changes: 131 additions & 0 deletions examples/scorus/lib/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// ignore_for_file: public_member_api_docs
import 'package:flutter/material.dart';
import 'package:flutter_rearch/flutter_rearch.dart';
import 'package:scorus/game_management.dart';
import 'package:scorus/score.dart';
import 'package:scorus/serving_player.dart';
import 'package:scorus/volley.dart';

void main() {
runApp(const ScorusApp());
}

class ScorusApp extends StatelessWidget {
const ScorusApp({super.key});

@override
Widget build(BuildContext context) {
return const RearchBootstrapper(
child: MaterialApp(home: ScorusBody()),
);
}
}

class ScorusBody extends RearchConsumer {
const ScorusBody({super.key});

@override
Widget build(BuildContext context, WidgetHandle use) {
final scoreTextStyle = Theme.of(context).textTheme.displayLarge;
final (servingTeam, servingPlayer) = use(currServingTeamAndPlayerCapsule);

return Scaffold(
appBar: AppBar(
title: const Text('Scorus'),
actions: [
IconButton(
icon: const Icon(Icons.restart_alt_rounded),
onPressed: use(resetGameAction),
),
],
),
body: Column(
children: [
Expanded(
child: InkWell(
onTap: use(team1WonVolleyAction),
child: Stack(
children: [
ColoredBox(
color: Colors.red,
child: Center(
child: Text(
'${use(team1ScoreManager).score}',
style: scoreTextStyle,
),
),
),
if (servingTeam == Team.team1)
ServingPlayerCard(
servingPlayer: servingPlayer,
anchorOnTop: true,
),
],
),
),
),
Expanded(
child: InkWell(
onTap: use(team2WonVolleyAction),
child: Stack(
children: [
ColoredBox(
color: Colors.blue,
child: Center(
child: Text(
'${use(team2ScoreManager).score}',
style: scoreTextStyle,
),
),
),
if (servingTeam == Team.team2)
ServingPlayerCard(
servingPlayer: servingPlayer,
anchorOnTop: false,
),
],
),
),
),
],
),
);
}
}

class ServingPlayerCard extends StatelessWidget {
const ServingPlayerCard({
required this.servingPlayer,
required this.anchorOnTop,
super.key,
});

final ServingPlayer servingPlayer;
final bool anchorOnTop;

@override
Widget build(BuildContext context) {
final servingPlayerNumber = switch (servingPlayer) {
ServingPlayer.player1 => 1,
ServingPlayer.player2 => 2,
};
return Positioned(
left: 0,
right: 0,
top: anchorOnTop ? 32 : null,
bottom: anchorOnTop ? null : 32,
child: Center(
child: Card(
color: Colors.white.withOpacity(0.1),
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
'Player $servingPlayerNumber serving',
style: Theme.of(context).textTheme.displaySmall,
),
),
),
),
);
}
}
26 changes: 26 additions & 0 deletions examples/scorus/lib/score.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'package:rearch/rearch.dart';

/// Defines what a score management [Capsule] can do.
typedef ScoreManager = ({
int score,
void Function() incrementScore,
void Function() resetScore,
});

/// Manages the score for the first team.
ScoreManager team1ScoreManager(CapsuleHandle use) => use.teamScore();

/// Manages the score for the second team.
ScoreManager team2ScoreManager(CapsuleHandle use) => use.teamScore();

extension on SideEffectRegistrar {
SideEffectRegistrar get use => this;
ScoreManager teamScore() {
final (score, setScore) = use.state(0);
return (
score: score,
incrementScore: () => setScore(score + 1),
resetScore: () => setScore(0)
);
}
}
46 changes: 46 additions & 0 deletions examples/scorus/lib/serving_player.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'package:rearch/rearch.dart';

/// Represents the currently serving player.
enum ServingPlayer {
/// Player #1.
player1,

/// Player #2.
player2,
}

/// Provides [next].
extension NextPlayer on ServingPlayer {
/// Returns the next [ServingPlayer] in the serving order.
ServingPlayer get next => switch (this) {
ServingPlayer.player1 => ServingPlayer.player2,
ServingPlayer.player2 => ServingPlayer.player1,
};
}

/// Represents what a [ServingPlayer] manager should be able to do.
typedef ServingPlayerManager = ({
ServingPlayer servingPlayer,
void Function() giveServeToNextPlayer,
void Function() resetServingPlayer,
});

/// Manages the [ServingPlayer] for the first team.
ServingPlayerManager team1ServingPlayerManager(CapsuleHandle use) =>
use.servingPlayer(startingPlayer: ServingPlayer.player2);

/// Manages the [ServingPlayer] for the second team.
ServingPlayerManager team2ServingPlayerManager(CapsuleHandle use) =>
use.servingPlayer(startingPlayer: ServingPlayer.player1);

extension on SideEffectRegistrar {
SideEffectRegistrar get use => this;
ServingPlayerManager servingPlayer({required ServingPlayer startingPlayer}) {
final (servingPlayer, setServingPlayer) = use.state(startingPlayer);
return (
servingPlayer: servingPlayer,
giveServeToNextPlayer: () => setServingPlayer(servingPlayer.next),
resetServingPlayer: () => setServingPlayer(startingPlayer),
);
}
}
52 changes: 52 additions & 0 deletions examples/scorus/lib/volley.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import 'package:rearch/rearch.dart';
import 'package:scorus/game_management.dart';
import 'package:scorus/score.dart';
import 'package:scorus/serving_player.dart';

/// An action capsule that when invoked indicates team 1 won a volley.
void Function() team1WonVolleyAction(CapsuleHandle use) => use.volleyWinner(
thisTeam: Team.team1,
teamWithPossesion: use(teamWithPossesionManager).teamWithPossesion,
incrementThisTeamScore: use(team1ScoreManager).incrementScore,
giveServeToOtherTeamNextPlayer:
use(team2ServingPlayerManager).giveServeToNextPlayer,
otherTeamServingPlayer: use(team2ServingPlayerManager).servingPlayer,
givePossesionToNextTeam:
use(teamWithPossesionManager).givePossesionToNextTeam,
);

/// An action capsule that when invoked indicates team 2 won a volley.
void Function() team2WonVolleyAction(CapsuleHandle use) => use.volleyWinner(
thisTeam: Team.team2,
teamWithPossesion: use(teamWithPossesionManager).teamWithPossesion,
incrementThisTeamScore: use(team2ScoreManager).incrementScore,
giveServeToOtherTeamNextPlayer:
use(team1ServingPlayerManager).giveServeToNextPlayer,
otherTeamServingPlayer: use(team1ServingPlayerManager).servingPlayer,
givePossesionToNextTeam:
use(teamWithPossesionManager).givePossesionToNextTeam,
);

extension on SideEffectRegistrar {
SideEffectRegistrar get use => this;
void Function() volleyWinner({
required Team thisTeam,
required Team teamWithPossesion,
required void Function() incrementThisTeamScore,
required void Function() giveServeToOtherTeamNextPlayer,
required ServingPlayer otherTeamServingPlayer,
required void Function() givePossesionToNextTeam,
}) {
final runTxn = use.transactionRunner();
return () => runTxn(() {
if (teamWithPossesion == thisTeam) {
incrementThisTeamScore();
} else {
giveServeToOtherTeamNextPlayer();
if (otherTeamServingPlayer == ServingPlayer.player2) {
givePossesionToNextTeam();
}
}
});
}
}
20 changes: 20 additions & 0 deletions examples/scorus/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: scorus
description: "Pickleball score tracker"
publish_to: none

environment:
sdk: '>=3.2.5 <4.0.0'
flutter: '>=3.16.9 <4.0.0'

dependencies:
flutter:
sdk: flutter
flutter_rearch: ^1.4.0
rearch: ^1.5.0

dev_dependencies:
flutter_test:
sdk: flutter

flutter:
uses-material-design: true

0 comments on commit 7ab1ed6

Please sign in to comment.