Skip to content

Commit

Permalink
v0.5.0
Browse files Browse the repository at this point in the history
 - Firebase integrated for community scores
 - Personal best scores updated in firestore
  • Loading branch information
ashishbeck committed Feb 25, 2022
1 parent f34d0f7 commit 2c00bef
Show file tree
Hide file tree
Showing 12 changed files with 305 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
/build/
# Web related
lib/generated_plugin_registrant.dart
lib/firebase_options.dart

# Symbolication related
app.*.symbols
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.5.0

- Firebase integrated for community scores
- Personal best scores updated in firestore

## 0.4.4

- Refined game state mechanic
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The live build is available to try [here](https://n-puzzle-solver-1.web.app/)
- The solving function used here incorporates IDA* algorithm (with pattern database heuristic) written in python (code modified from [Michael Schrandt](https://github.com/mschrandt/NPuzzle)) which is executed in Google Cloud Run and is accessed via http requests. After trying to implement the algorithm in dart and realising that it could result in potential UI freezes in web along with many other technical problems, I decided to outsource the computational task. For a puzzle of grid size 3, it is able to solve the puzzle within seconds. Grid size of 4 takes quite some time and 5 is beyond the scope of the algorithm which is why I had to disable the solve button for it. The solution is definitely not optimal (usually 50+ moves) and I am not even trying to go for it because it will take a lot more time (sometimes over 2 minutes to solve with manhattan distance heuristic). Moreover, the player wouldn't even understand the moves the AI makes so it is not ideal to go for the optimal solutions anyway. It is a pure aesthetic feature that just looks "cool" and is extremely satisfying to watch.

## 🛠️ Building and Compiling
This project was created in Flutter 2.8.0 but the final build is produced with the latest version of 2.10.1. Please follow the official [documentation](https://docs.flutter.dev/get-started/install) to set up flutter in your system before proceeding. Clone the repository and open it in terminal/cmd/powershell. Run the following commands to get the app running:
This project was created in Flutter 2.8.0 but the final build is produced with the latest version of 2.10.1. Please follow the official [documentation](https://docs.flutter.dev/get-started/install) to set up flutter in your system before proceeding. It also uses Firebase to access the community scores. Please setup a firebase project for your app by following the [FlutterFire documentations](https://firebase.flutter.dev/docs/overview/#installation). Clone the repository and open it in terminal/cmd/powershell. Run the following commands to get the app running:
`flutter pub get`
`flutter run -d chrome`
### Important!
Expand Down
58 changes: 58 additions & 0 deletions lib/code/auth.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:slide_puzzle/code/models.dart';

class AuthService {
// static final AuthService instance = AuthService._init();
// AuthService._init();
final FirebaseAuth _auth = FirebaseAuth.instance;

Stream<User?>? user;

AuthService() {
user = _auth.authStateChanges();
}

signInAnonymously() async {
print("signing in");
UserCredential userCredential = await _auth.signInAnonymously();
if (userCredential.additionalUserInfo!.isNewUser) {
DatabaseService.instance.createUser(userCredential.user!.uid);
} else {
DatabaseService.instance.updateLastSeen(userCredential.user!.uid);
}
return userCredential;
}
}

class DatabaseService {
static final DatabaseService instance = DatabaseService._init();
DatabaseService._init();
final FirebaseFirestore _firestore = FirebaseFirestore.instance;

createUser(String uid) {
print("creating user");
final userData = UserData.newUser(uid);
// UserData(uid: uid, move3: 0, time3: 0, lastSeen: Timestamp.now());
_firestore.collection("users").doc(uid).set(userData.toMap());
}

updateUserData(UserData userData) {
_firestore.collection("users").doc(userData.uid).set(userData.toMap());
}

updateLastSeen(String uid) {
_firestore.collection("users").doc(uid).set({
"uid": uid,
"lastSeen": Timestamp.now(),
}, SetOptions(merge: true));
}

Stream<UserData?> currentUser(String uid) {
return _firestore
.collection("users")
.doc(uid)
.snapshots()
.map((event) => UserData.fromMap((event.data()!)));
}
}
68 changes: 68 additions & 0 deletions lib/code/models.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import 'dart:convert';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';

class TilesModel {
Expand Down Expand Up @@ -36,3 +39,68 @@ class TweenModel {
}

enum Direction { left, right, up, down }

class UserData {
String uid;
Map<String, int> moves;
Map<String, int> times;
Timestamp lastSeen;
UserData({
required this.uid,
required this.moves,
required this.times,
required this.lastSeen,
});

Map<String, dynamic> toMap() {
return {
'uid': uid,
'moves': moves,
'times': times,
'lastSeen': lastSeen,
};
}

factory UserData.newUser(String uid) {
return UserData(
uid: uid,
moves: {
"three": 0,
"four": 0,
},
times: {
"three": 0,
"four": 0,
},
lastSeen: Timestamp.now(),
);
}

factory UserData.fromMap(Map<String, dynamic> map) {
return UserData(
uid: map['uid'] ?? '',
moves: Map<String, int>.from(map['moves']),
times: Map<String, int>.from(map['times']),
lastSeen: (map['lastSeen']) ?? Timestamp.now(),
);
}

String toJson() => json.encode(toMap());

factory UserData.fromJson(String source) =>
UserData.fromMap(json.decode(source));

UserData copyWith({
String? uid,
Map<String, int>? moves,
Map<String, int>? times,
Timestamp? lastSeen,
}) {
return UserData(
uid: uid ?? this.uid,
moves: moves ?? this.moves,
times: times ?? this.times,
lastSeen: lastSeen ?? this.lastSeen,
);
}
}
25 changes: 24 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import 'dart:math';

import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:slide_puzzle/code/auth.dart';
import 'package:slide_puzzle/code/constants.dart';
import 'package:slide_puzzle/code/models.dart';
import 'package:slide_puzzle/code/providers.dart';
import 'package:slide_puzzle/firebase_options.dart';
import 'package:slide_puzzle/screen/app.dart';
import 'package:provider/provider.dart';
import 'package:slide_puzzle/screen/landing.dart';
import 'package:rxdart/rxdart.dart';

void main() {
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}

Expand All @@ -35,6 +44,20 @@ class MyApp extends StatelessWidget {
// Provider<List<TilesModel>>.value(
// value: list,
// ),
StreamProvider<User?>.value(
initialData: null, value: AuthService().user),
StreamProvider<UserData?>.value(
initialData: null,
catchError: (_, __) {
print("error at $__");
},
value: AuthService().user!.transform(
FlatMapStreamTransformer<User?, UserData?>(
(firebaseUser) =>
DatabaseService.instance.currentUser(firebaseUser!.uid),
),
),
),
ChangeNotifierProvider(create: (context) => TileProvider()),
ChangeNotifierProvider(create: (context) => TweenProvider()),
ChangeNotifierProvider(create: (context) => ConfigProvider()),
Expand Down
6 changes: 5 additions & 1 deletion lib/screen/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ class _LayoutPageState extends State<LayoutPage> {
if (shuffle) scoreProvider.beginTimer();
var duration = Duration(milliseconds: isChangingGrid ? 0 : 500);
configProvider.setDuration(duration, curve: Curves.easeInOutBack);
if (shuffle) configProvider.start();
if (shuffle) {
configProvider.start();
} else {
configProvider.wait();
}
Future.delayed(isChangingGrid ? Duration(milliseconds: 10) : duration)
.then((value) => configProvider.resetDuration());
}
Expand Down
15 changes: 15 additions & 0 deletions lib/screen/landing.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart';
import 'package:slide_puzzle/code/auth.dart';
import 'package:slide_puzzle/code/constants.dart';
import 'package:slide_puzzle/code/models.dart';
import 'package:slide_puzzle/screen/app.dart';
import 'package:url_launcher/url_launcher.dart';

Expand Down Expand Up @@ -39,6 +43,8 @@ class _LandingPageState extends State<LandingPage>
}
}

_authenticateUser() async {}

@override
void initState() {
super.initState();
Expand All @@ -49,10 +55,19 @@ class _LandingPageState extends State<LandingPage>
value: 0,
lowerBound: 0,
upperBound: 1);

User? user = context.read<User?>();
if (user == null) AuthService().signInAnonymously();
}

@override
Widget build(BuildContext context) {
UserData? userData = context.watch<UserData?>();
if (userData != null) {
print("user found: ${userData.toMap()}");
} else {
print("no user found");
}
return Scaffold(
backgroundColor: secondaryColor,
body: ScaleTransition(
Expand Down
39 changes: 39 additions & 0 deletions lib/screen/puzzle.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:math';
import 'package:provider/provider.dart';
import 'package:slide_puzzle/code/audio.dart';
import 'package:slide_puzzle/code/auth.dart';
import 'package:slide_puzzle/code/constants.dart';
import 'package:slide_puzzle/code/models.dart';
import 'package:slide_puzzle/code/providers.dart';
Expand Down Expand Up @@ -111,6 +113,7 @@ class _PuzzleState extends State<Puzzle> {

_checkIfSolved(List<TilesModel> tileList, ScoreProvider scoreProvider,
ConfigProvider configProvider) {
UserData? userData = context.read<UserData?>();
isSolved = Service().isSolved(tileList);
bool aiSolved = configProvider.gamestate == GameState.aiSolving;
if (isSolved &&
Expand All @@ -119,10 +122,46 @@ class _PuzzleState extends State<Puzzle> {
print("Solved!!");
scoreProvider.stopTimer();
configProvider.finish();
print(gridSize);
if (!aiSolved) {
_calculateAndSubmitScore(gridSize, scoreProvider);
}
_launchScoreBoard(aiSolved, scoreProvider);
}
}

_calculateAndSubmitScore(
int gridSize,
ScoreProvider scoreProvider,
) {
UserData? userData = context.read<UserData?>();
// Map<String, int> score = userData!.moves;
String grid = gridSize == 3 ? "three" : "four";
Map<String, int> allMoves = userData!.moves;
Map<String, int> allTimes = userData.times;
int bestMove = allMoves[grid]!;
int bestTime = allTimes[grid]!;
int currentMove = scoreProvider.moves;
int currentTime = scoreProvider.seconds;
if (currentMove < bestMove ||
currentTime < bestTime ||
bestMove == 0 ||
bestTime == 0) {
allMoves[grid] = bestMove == 0 ? currentMove : min(bestMove, currentMove);
allTimes[grid] = bestTime == 0 ? currentTime : min(bestTime, currentTime);
print(allTimes);
print(allMoves);
final newData = userData.copyWith(
uid: userData.uid,
moves: allMoves,
times: allTimes,
lastSeen: Timestamp.now(),
);
// print(newData.toString());
DatabaseService.instance.updateUserData(newData);
}
}

_launchScoreBoard(bool aiSolved, ScoreProvider scoreProvider) async {
await Future.delayed(Duration(milliseconds: 100));
showDialog(
Expand Down
15 changes: 15 additions & 0 deletions lib/ui/Scoreboard.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';

class ScoreBoard extends StatefulWidget {
const ScoreBoard({Key? key}) : super(key: key);

@override
_ScoreBoardState createState() => _ScoreBoardState();
}

class _ScoreBoardState extends State<ScoreBoard> {
@override
Widget build(BuildContext context) {
return Container();
}
}
Loading

0 comments on commit 2c00bef

Please sign in to comment.