Skip to content

Commit

Permalink
WIP - firebase
Browse files Browse the repository at this point in the history
  • Loading branch information
luanpotter committed Jul 3, 2023
1 parent 589e269 commit bfab1a3
Show file tree
Hide file tree
Showing 11 changed files with 265 additions and 34 deletions.
44 changes: 35 additions & 9 deletions lib/end_game/view/end_game_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,27 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:gamepads/gamepads.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:lightrunners/firebase/score_calculator.dart';
import 'package:lightrunners/firebase/scores.dart';
import 'package:lightrunners/game/game.dart';
import 'package:lightrunners/game/player.dart';
import 'package:lightrunners/title/view/title_page.dart';
import 'package:lightrunners/ui/ui.dart';
import 'package:lightrunners/utils/gamepad_navigator.dart';
import 'package:lightrunners/widgets/widgets.dart';

class EndGamePage extends StatefulWidget {
const EndGamePage({
required this.scores,
required this.points,
super.key,
});

final Map<Color, int> scores;
final Map<Player, int> points;

static Route<void> route(Map<Color, int> scores) {
static Route<void> route(Map<Player, int> points) {
return MaterialPageRoute<void>(
maintainState: false,
builder: (_) => EndGamePage(scores: scores),
builder: (_) => EndGamePage(points: points),
);
}

Expand All @@ -32,11 +35,20 @@ class _EndGamePageState extends State<EndGamePage> {
late StreamSubscription<GamepadEvent> _gamepadSubscription;
late GamepadNavigator _gamepadNavigator;

// TODO(any): display loading indicator on screen
bool updatingFirebase = true;

@override
void initState() {
super.initState();
_updateFirebase();
_gamepadNavigator = GamepadNavigator(
onAny: () => Navigator.of(context).pushReplacement(TitlePage.route()),
onAny: () {
if (updatingFirebase) {
return;
}
Navigator.of(context).pushReplacement(TitlePage.route());
},
);
_gamepadSubscription = Gamepads.events.listen(_gamepadNavigator.handle);
}
Expand All @@ -48,11 +60,25 @@ class _EndGamePageState extends State<EndGamePage> {
super.dispose();
}

Future<void> _updateFirebase() async {
final scores = ScoreCalculator.computeScores(widget.points);
final futures = scores.entries
.where((entry) => entry.key.playerId != null)
.map((entry) {
return Scores.updateScore(
playerId: entry.key.playerId!,
score: entry.value,
);
});
await Future.wait(futures);
setState(() => updatingFirebase = false);
}

@override
Widget build(BuildContext context) {
final fontFamily = GoogleFonts.bungee().fontFamily;

final scores = widget.scores.entries.toList()
final scores = widget.points.entries.toList()
..sort((a, b) => b.value.compareTo(a.value));

const baseSize = 64;
Expand Down Expand Up @@ -88,7 +114,7 @@ class _EndGamePageState extends State<EndGamePage> {
child: Column(
children: [
Image.asset(
'assets/images/ships/${shipSprites[GamePalette.shipValues.indexOf(scores[i].key)]}',
'assets/images/ships/${shipSprites[GamePalette.shipValues.indexOf(scores[i].key.color)]}',
width: baseSize.toDouble(),
),
const SizedBox(height: 16),
Expand All @@ -97,15 +123,15 @@ class _EndGamePageState extends State<EndGamePage> {
style: TextStyle(
fontFamily: fontFamily,
fontSize: 32,
color: scores[i].key,
color: scores[i].key.color,
),
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(16),
width: 180,
decoration: BoxDecoration(
color: scores[i].key,
color: scores[i].key.color,
backgroundBlendMode: BlendMode.colorBurn,
boxShadow: [
BoxShadow(
Expand Down
20 changes: 20 additions & 0 deletions lib/firebase/score.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:firedart/firestore/models.dart';

class Score {
final int playerId;
final String email;
final int score;

Score({
required this.playerId,
required this.email,
required this.score,
});

Score.fromDocument(Document document)
: this(
playerId: document.map['playerId'] as int,
email: document.map['email'] as String,
score: document.map['score'] as int,
);
}
47 changes: 47 additions & 0 deletions lib/firebase/score_calculator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'package:collection/collection.dart';
import 'package:lightrunners/game/player.dart';

class _PlayerScore {
final Player player;
int score = 0;

_PlayerScore(this.player);
}

class ScoreCalculator {
ScoreCalculator._();

static Map<Player, int> computeScores(Map<Player, int> points) {
final scores = points.entries
.toList()
.sortedBy<num>((entry) => entry.value)
.map((entry) => _PlayerScore(entry.key));

final numPlayers = points.length;
final totalScore = points.values.reduce((a, b) => a + b);

var jackpot = numPlayers;
if (jackpot == 0) {
return {};
}

scores.first.score = 1;
for (final playerScore in scores) {
final point = points[playerScore.player]!;
final ratio = point / totalScore;

final score = (ratio * jackpot).ceil().clamp(0, jackpot);
playerScore.score += score;
jackpot -= score;
if (jackpot == 0) {
break;
}
}

return Map.fromEntries(
scores
.map((score) => MapEntry(score.player, score.score))
.where((element) => element.value > 0),
);
}
}
37 changes: 37 additions & 0 deletions lib/firebase/scores.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:firedart/firedart.dart';
import 'package:lightrunners/firebase/score.dart';

class Scores {
static late Firestore firestore;

Scores._();

static Future<void> init() async {
// NOTE: This will not work. support was removed from the library
const projectId = 'lightrunners-e89a9';
Firestore.initialize(projectId);
firestore = Firestore.instance;
}

static Future<List<Score>> topScores() async {
final page = await firestore
.collection('scores')
.orderBy('score', descending: true)
.limit(10)
.get();
return page.map(Score.fromDocument).toList();
}

static Future<void> updateScore({
required int playerId,
required int score,
}) async {
final document =
firestore.collection('scores').document(playerId.toString());
if (await document.exists) {
await document.update({'score': score});
} else {
print('Error: Score not found for player id $playerId.');
}
}
}
7 changes: 6 additions & 1 deletion lib/game/components/ship.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:flame/geometry.dart';
import 'package:flutter/services.dart';
import 'package:gamepads/gamepads.dart';
import 'package:lightrunners/game/game.dart';
import 'package:lightrunners/game/player.dart';
import 'package:lightrunners/ui/ui.dart';
import 'package:lightrunners/utils/gamepad_map.dart';
import 'package:lightrunners/utils/input_handler_utils.dart';
Expand Down Expand Up @@ -86,12 +87,16 @@ class Ship extends SpriteComponent
static final _random = Random();

Ship(this.playerNumber, this.gamepadId)
: moveJoystick = _makeJoystick(gamepadId, leftXAxis, leftYAxis),
: player = Player(
color: _shipColors[playerNumber].color,
),
moveJoystick = _makeJoystick(gamepadId, leftXAxis, leftYAxis),
super(size: Vector2(40, 80), anchor: Anchor.center) {
paint = _shipColors[playerNumber];
spritePath = shipSprites[playerNumber];
}

final Player player;
final int playerNumber;
final String? gamepadId; // null means keyboard
int score = 0;
Expand Down
11 changes: 9 additions & 2 deletions lib/game/lightrunners_game.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:math';
import 'dart:ui';

import 'package:flame/camera.dart';
Expand All @@ -10,6 +11,7 @@ import 'package:lightrunners/game/components/game_border.dart';
import 'package:lightrunners/game/components/score_panel.dart';
import 'package:lightrunners/game/components/spotlight.dart';
import 'package:lightrunners/game/game.dart';
import 'package:lightrunners/game/player.dart';
import 'package:lightrunners/utils/constants.dart';
import 'package:lightrunners/utils/utils.dart';

Expand All @@ -22,7 +24,7 @@ class LightRunnersGame extends FlameGame
late final Map<String, Ship> ships;

StreamSubscription<GamepadEvent>? _subscription;
final void Function(Map<Color, int>) onEndGame;
final void Function(Map<Player, int>) onEndGame;

LightRunnersGame({required this.players, required this.onEndGame});

Expand Down Expand Up @@ -55,7 +57,7 @@ class LightRunnersGame extends FlameGame
onTimeUp: () {
countDown.removeFromParent();
onEndGame({
for (final ship in ships.values) ship.paint.color: ship.score,
for (final ship in ships.values) ship.player: ship.score,
});
},
),
Expand All @@ -73,6 +75,11 @@ class LightRunnersGame extends FlameGame
ships[''] = Ship(0, null);
}

// TODO(any): correctly configure player numbers from the app
for (final ship in ships.values) {
ship.player.playerId = Random().nextInt(999);
}

_subscription = Gamepads.events.listen((event) {
ships[event.gamepadId]?.onGamepadEvent(event);
});
Expand Down
10 changes: 10 additions & 0 deletions lib/game/player.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'package:flame/extensions.dart';

class Player {
int? playerId;
final Color color;

Player({
required this.color,
});
}
56 changes: 34 additions & 22 deletions lib/leaderboard/view/leaderboard_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:gamepads/gamepads.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:lightrunners/firebase/score.dart';
import 'package:lightrunners/firebase/scores.dart';
import 'package:lightrunners/title/title.dart';
import 'package:lightrunners/utils/gamepad_navigator.dart';
import 'package:lightrunners/widgets/widgets.dart';
Expand All @@ -13,13 +15,6 @@ const _maxCharactersScoreBoard = 26;
const _lightrunnersInfoBlob =
'''Light Runners was made with 💙 by Blue Fire and Invertase for Fluttercon 2023
''';
final _leaderboard = [
(name: 'Erick', score: 100),
(name: 'Luan', score: 90),
(name: 'Renan', score: 80),
(name: 'Spydon', score: 70),
(name: 'Wolfenrain', score: 60),
];

class LeaderboardPage extends StatefulWidget {
const LeaderboardPage({
Expand All @@ -41,13 +36,25 @@ class _LeaderboardPageState extends State<LeaderboardPage> {
late StreamSubscription<GamepadEvent> _gamepadSubscription;
late GamepadNavigator _gamepadNavigator;

bool loading = true;
List<Score> scores = [];

@override
void initState() {
super.initState();
_gamepadNavigator = GamepadNavigator(
onAny: () => Navigator.of(context).pushReplacement(TitlePage.route()),
);
_gamepadSubscription = Gamepads.events.listen(_gamepadNavigator.handle);
_updateScores();
}

Future<void> _updateScores() async {
final scores = await Scores.topScores();
setState(() {
loading = false;
this.scores = scores;
});
}

@override
Expand Down Expand Up @@ -92,16 +99,19 @@ class _LeaderboardPageState extends State<LeaderboardPage> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (final entry in _leaderboard)
Text(
_toScoreboardLine(entry),
style: TextStyle(
fontFamily: major,
fontWeight: FontWeight.bold,
fontSize: 38,
color: Colors.white,
),
)
if (loading)
const CircularProgressIndicator()
else
for (final score in scores)
Text(
_toScoreboardLine(score),
style: TextStyle(
fontFamily: major,
fontWeight: FontWeight.bold,
fontSize: 38,
color: Colors.white,
),
)
],
),
),
Expand All @@ -111,14 +121,16 @@ class _LeaderboardPageState extends State<LeaderboardPage> {
);
}

String _toScoreboardLine(({String name, int score}) entry) {
final name = entry.name.substring(
String _toScoreboardLine(Score record) {
final name = record.email.substring(
0,
min(entry.name.length, _maxCharactersScoreBoard - _maxScoreDigits - 1),
min(record.email.length, _maxCharactersScoreBoard - _maxScoreDigits - 1),
);
final maxScore = pow(10, _maxScoreDigits) - 1;
final score =
entry.score.clamp(0, maxScore).toString().padLeft(_maxScoreDigits, '0');
final score = record.score
.clamp(0, maxScore)
.toString()
.padLeft(_maxScoreDigits, '0');
final dotDotDot =
'.' * (_maxCharactersScoreBoard - name.length - score.length);

Expand Down
Loading

0 comments on commit bfab1a3

Please sign in to comment.