242 lines
6.5 KiB
Dart
242 lines
6.5 KiB
Dart
import 'dart:async';
|
|
import 'dart:math' as math;
|
|
import 'dart:ui' as ui;
|
|
import 'dart:typed_data';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:aesthetica_wallpaper/models/puzzle_game.dart';
|
|
|
|
class PuzzleProvider extends ChangeNotifier {
|
|
PuzzleGame? _currentGame;
|
|
Timer? _timer;
|
|
List<GameRecord> _records = [];
|
|
|
|
PuzzleGame? get currentGame => _currentGame;
|
|
List<GameRecord> get records => _records;
|
|
|
|
/// 创建新游戏
|
|
Future<void> createGame({
|
|
required String imagePath,
|
|
required GameDifficulty difficulty,
|
|
GameMode mode = GameMode.classic,
|
|
}) async {
|
|
// 停止之前的计时器
|
|
_timer?.cancel();
|
|
|
|
// 加载并分割图片
|
|
final pieces = await _splitImage(imagePath, difficulty.gridSize);
|
|
|
|
// 打乱拼图
|
|
_shufflePieces(pieces, difficulty.gridSize);
|
|
|
|
// 创建游戏
|
|
_currentGame = PuzzleGame(
|
|
pieces: pieces,
|
|
difficulty: difficulty,
|
|
mode: mode,
|
|
emptyPosition: mode == GameMode.classic ? pieces.length - 1 : null,
|
|
);
|
|
|
|
// 开始计时
|
|
_startTimer();
|
|
|
|
notifyListeners();
|
|
}
|
|
|
|
/// 分割图片为拼图块
|
|
Future<List<PuzzlePiece>> _splitImage(String imagePath, int gridSize) async {
|
|
final pieces = <PuzzlePiece>[];
|
|
|
|
// 加载图片
|
|
final ByteData data = await rootBundle.load(imagePath);
|
|
final codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
|
|
final frame = await codec.getNextFrame();
|
|
final image = frame.image;
|
|
|
|
final pieceWidth = image.width ~/ gridSize;
|
|
final pieceHeight = image.height ~/ gridSize;
|
|
|
|
// 分割图片
|
|
for (int row = 0; row < gridSize; row++) {
|
|
for (int col = 0; col < gridSize; col++) {
|
|
final position = row * gridSize + col;
|
|
|
|
// 创建图片块
|
|
final recorder = ui.PictureRecorder();
|
|
final canvas = Canvas(recorder);
|
|
|
|
canvas.drawImageRect(
|
|
image,
|
|
Rect.fromLTWH(
|
|
col * pieceWidth.toDouble(),
|
|
row * pieceHeight.toDouble(),
|
|
pieceWidth.toDouble(),
|
|
pieceHeight.toDouble(),
|
|
),
|
|
Rect.fromLTWH(0, 0, pieceWidth.toDouble(), pieceHeight.toDouble()),
|
|
Paint(),
|
|
);
|
|
|
|
final picture = recorder.endRecording();
|
|
final pieceImage = await picture.toImage(pieceWidth, pieceHeight);
|
|
|
|
pieces.add(
|
|
PuzzlePiece(
|
|
id: position,
|
|
correctPosition: position,
|
|
currentPosition: position,
|
|
image: MemoryImage(
|
|
(await pieceImage.toByteData(
|
|
format: ui.ImageByteFormat.png,
|
|
))!.buffer.asUint8List(),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
return pieces;
|
|
}
|
|
|
|
/// 打乱拼图
|
|
void _shufflePieces(List<PuzzlePiece> pieces, int gridSize) {
|
|
final random = math.Random();
|
|
|
|
// 执行多次随机交换
|
|
for (int i = 0; i < pieces.length * 10; i++) {
|
|
final index1 = random.nextInt(pieces.length);
|
|
final index2 = random.nextInt(pieces.length);
|
|
|
|
final temp = pieces[index1].currentPosition;
|
|
pieces[index1].currentPosition = pieces[index2].currentPosition;
|
|
pieces[index2].currentPosition = temp;
|
|
}
|
|
|
|
// 确保拼图是可解的(对于滑动模式)
|
|
// TODO: 实现可解性检查
|
|
}
|
|
|
|
/// 移动拼图块
|
|
void movePiece(int pieceIndex) {
|
|
if (_currentGame == null || _currentGame!.isComplete) return;
|
|
|
|
final piece = _currentGame!.pieces[pieceIndex];
|
|
final gridSize = _currentGame!.gridSize;
|
|
|
|
if (_currentGame!.mode == GameMode.classic) {
|
|
// 滑动模式:只能移动到空格
|
|
if (!_canMoveToEmpty(piece.currentPosition, gridSize)) return;
|
|
|
|
final emptyPos = _currentGame!.emptyPosition!;
|
|
piece.currentPosition = emptyPos;
|
|
_currentGame = _currentGame!.copyWith(emptyPosition: pieceIndex);
|
|
}
|
|
|
|
// 增加步数
|
|
_currentGame = _currentGame!.copyWith(moves: _currentGame!.moves + 1);
|
|
|
|
// 检查是否完成
|
|
if (_currentGame!.checkComplete()) {
|
|
_completeGame();
|
|
}
|
|
|
|
notifyListeners();
|
|
}
|
|
|
|
/// 交换两个拼图块(交换模式)
|
|
void swapPieces(int index1, int index2) {
|
|
if (_currentGame == null || _currentGame!.isComplete) return;
|
|
if (_currentGame!.mode == GameMode.classic) return;
|
|
|
|
final piece1 = _currentGame!.pieces[index1];
|
|
final piece2 = _currentGame!.pieces[index2];
|
|
|
|
final temp = piece1.currentPosition;
|
|
piece1.currentPosition = piece2.currentPosition;
|
|
piece2.currentPosition = temp;
|
|
|
|
_currentGame = _currentGame!.copyWith(moves: _currentGame!.moves + 1);
|
|
|
|
if (_currentGame!.checkComplete()) {
|
|
_completeGame();
|
|
}
|
|
|
|
notifyListeners();
|
|
}
|
|
|
|
/// 检查是否可以移动到空格
|
|
bool _canMoveToEmpty(int position, int gridSize) {
|
|
final emptyPos = _currentGame!.emptyPosition!;
|
|
final row = position ~/ gridSize;
|
|
final col = position % gridSize;
|
|
final emptyRow = emptyPos ~/ gridSize;
|
|
final emptyCol = emptyPos % gridSize;
|
|
|
|
// 检查是否相邻
|
|
return (row == emptyRow && (col - emptyCol).abs() == 1) ||
|
|
(col == emptyCol && (row - emptyRow).abs() == 1);
|
|
}
|
|
|
|
/// 开始计时
|
|
void _startTimer() {
|
|
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
if (_currentGame != null && !_currentGame!.isComplete) {
|
|
_currentGame = _currentGame!.copyWith(
|
|
elapsedTime: _currentGame!.elapsedTime + const Duration(seconds: 1),
|
|
);
|
|
notifyListeners();
|
|
}
|
|
});
|
|
}
|
|
|
|
/// 完成游戏
|
|
void _completeGame() {
|
|
_timer?.cancel();
|
|
_currentGame = _currentGame!.copyWith(isComplete: true);
|
|
|
|
// 保存记录
|
|
final record = GameRecord(
|
|
imageId: 'temp', // TODO: 使用实际图片ID
|
|
difficulty: _currentGame!.difficulty,
|
|
time: _currentGame!.elapsedTime,
|
|
moves: _currentGame!.moves,
|
|
stars: _currentGame!.getStarRating(),
|
|
completedAt: DateTime.now(),
|
|
);
|
|
|
|
_records.add(record);
|
|
// TODO: 持久化保存记录
|
|
}
|
|
|
|
/// 重新开始游戏
|
|
void restartGame() {
|
|
if (_currentGame == null) return;
|
|
|
|
_timer?.cancel();
|
|
_shufflePieces(_currentGame!.pieces, _currentGame!.gridSize);
|
|
|
|
_currentGame = _currentGame!.copyWith(
|
|
moves: 0,
|
|
elapsedTime: Duration.zero,
|
|
isComplete: false,
|
|
);
|
|
|
|
_startTimer();
|
|
notifyListeners();
|
|
}
|
|
|
|
/// 使用提示
|
|
void useHint() {
|
|
if (_currentGame == null || _currentGame!.isComplete) return;
|
|
|
|
// TODO: 实现提示逻辑
|
|
// 找到一个不在正确位置的块,并高亮显示其正确位置
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_timer?.cancel();
|
|
super.dispose();
|
|
}
|
|
}
|