Przebudowanie aplikacji, usprawnione AI, dodanie combo buildera
Some checks failed
Build Linux App / build (push) Failing after 1m18s
Some checks failed
Build Linux App / build (push) Failing after 1m18s
This commit is contained in:
204
lib/data/services/combo_service.dart
Normal file
204
lib/data/services/combo_service.dart
Normal file
@@ -0,0 +1,204 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
const _uuid = Uuid();
|
||||
|
||||
/// One technique node in a combo graph.
|
||||
class ComboNode {
|
||||
const ComboNode({
|
||||
required this.id,
|
||||
required this.technique,
|
||||
this.translation = '',
|
||||
required this.dx,
|
||||
required this.dy,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String technique;
|
||||
final String translation;
|
||||
final double dx;
|
||||
final double dy;
|
||||
|
||||
ComboNode copyWith({double? dx, double? dy}) => ComboNode(
|
||||
id: id,
|
||||
technique: technique,
|
||||
translation: translation,
|
||||
dx: dx ?? this.dx,
|
||||
dy: dy ?? this.dy,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'technique': technique,
|
||||
'translation': translation,
|
||||
'dx': dx,
|
||||
'dy': dy,
|
||||
};
|
||||
|
||||
factory ComboNode.fromJson(Map<String, dynamic> json) => ComboNode(
|
||||
id: json['id'] as String,
|
||||
technique: json['technique'] as String,
|
||||
translation: (json['translation'] ?? '') as String,
|
||||
dx: (json['dx'] as num).toDouble(),
|
||||
dy: (json['dy'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
/// A directed transition between two techniques.
|
||||
class ComboEdge {
|
||||
const ComboEdge(this.fromId, this.toId);
|
||||
|
||||
final String fromId;
|
||||
final String toId;
|
||||
|
||||
Map<String, dynamic> toJson() => {'from': fromId, 'to': toId};
|
||||
|
||||
factory ComboEdge.fromJson(Map<String, dynamic> json) =>
|
||||
ComboEdge(json['from'] as String, json['to'] as String);
|
||||
}
|
||||
|
||||
/// A named technique graph: nodes are techniques, edges are the transitions
|
||||
/// that make sense between them.
|
||||
class Combo {
|
||||
const Combo({
|
||||
required this.id,
|
||||
required this.name,
|
||||
this.nodes = const [],
|
||||
this.edges = const [],
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String name;
|
||||
final List<ComboNode> nodes;
|
||||
final List<ComboEdge> edges;
|
||||
|
||||
Combo copyWith({
|
||||
String? name,
|
||||
List<ComboNode>? nodes,
|
||||
List<ComboEdge>? edges,
|
||||
}) => Combo(
|
||||
id: id,
|
||||
name: name ?? this.name,
|
||||
nodes: nodes ?? this.nodes,
|
||||
edges: edges ?? this.edges,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'nodes': nodes.map((n) => n.toJson()).toList(),
|
||||
'edges': edges.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
|
||||
factory Combo.fromJson(Map<String, dynamic> json) => Combo(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
nodes: [
|
||||
for (final n in (json['nodes'] as List? ?? []))
|
||||
ComboNode.fromJson(n as Map<String, dynamic>),
|
||||
],
|
||||
edges: [
|
||||
for (final e in (json['edges'] as List? ?? []))
|
||||
ComboEdge.fromJson(e as Map<String, dynamic>),
|
||||
],
|
||||
);
|
||||
|
||||
/// Random walk over the graph — the generated combination to drill.
|
||||
/// Follows outgoing edges; if a node is a dead end the walk stops there.
|
||||
List<ComboNode> generateSequence({int length = 4, Random? random}) {
|
||||
if (nodes.isEmpty) return [];
|
||||
final rnd = random ?? Random();
|
||||
final byId = {for (final n in nodes) n.id: n};
|
||||
final outgoing = <String, List<String>>{};
|
||||
for (final e in edges) {
|
||||
outgoing.putIfAbsent(e.fromId, () => []).add(e.toId);
|
||||
}
|
||||
|
||||
// Prefer starting somewhere that actually leads on.
|
||||
final starts = nodes.where((n) => outgoing.containsKey(n.id)).toList();
|
||||
var current = (starts.isEmpty
|
||||
? nodes
|
||||
: starts)[rnd.nextInt((starts.isEmpty ? nodes : starts).length)];
|
||||
|
||||
final sequence = <ComboNode>[current];
|
||||
while (sequence.length < length) {
|
||||
final next = outgoing[current.id];
|
||||
if (next == null || next.isEmpty) break;
|
||||
current = byId[next[rnd.nextInt(next.length)]]!;
|
||||
sequence.add(current);
|
||||
}
|
||||
return sequence;
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads and persists combos as a JSON file in the app documents dir.
|
||||
class ComboService extends ChangeNotifier {
|
||||
static const _fileName = 'trainhub_combos.json';
|
||||
|
||||
List<Combo> _combos = [];
|
||||
bool _loaded = false;
|
||||
|
||||
List<Combo> get combos => _combos;
|
||||
|
||||
Future<File> _file() async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
return File(p.join(dir.path, _fileName));
|
||||
}
|
||||
|
||||
Future<void> ensureLoaded() async {
|
||||
if (_loaded) return;
|
||||
_loaded = true;
|
||||
try {
|
||||
final file = await _file();
|
||||
if (!file.existsSync()) return;
|
||||
final json = jsonDecode(await file.readAsString()) as List<dynamic>;
|
||||
_combos = [
|
||||
for (final c in json) Combo.fromJson(c as Map<String, dynamic>),
|
||||
];
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
if (kDebugMode) debugPrint('Failed to load combos: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Combo create(String name) {
|
||||
final combo = Combo(id: _uuid.v4(), name: name);
|
||||
_combos = [..._combos, combo];
|
||||
notifyListeners();
|
||||
_persist();
|
||||
return combo;
|
||||
}
|
||||
|
||||
void delete(String id) {
|
||||
_combos = _combos.where((c) => c.id != id).toList();
|
||||
notifyListeners();
|
||||
_persist();
|
||||
}
|
||||
|
||||
/// Replaces a combo. Set [persist] false for high-frequency updates
|
||||
/// (node dragging) and call [persistNow] once afterwards.
|
||||
void update(Combo combo, {bool persist = true}) {
|
||||
_combos = [for (final c in _combos) c.id == combo.id ? combo : c];
|
||||
notifyListeners();
|
||||
if (persist) _persist();
|
||||
}
|
||||
|
||||
void persistNow() => _persist();
|
||||
|
||||
Future<void> _persist() async {
|
||||
try {
|
||||
final file = await _file();
|
||||
await file.writeAsString(
|
||||
jsonEncode(_combos.map((c) => c.toJson()).toList()),
|
||||
);
|
||||
} catch (e) {
|
||||
if (kDebugMode) debugPrint('Failed to save combos: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user