import 'package:flutter/material.dart'; import 'package:gobang/models/chess.dart'; import 'package:gobang/models/position.dart'; import 'package:gobang/themes/app_colors.dart'; import 'package:gobang/utils/constants.dart'; /// 棋盘 + 棋子绘制;最后一手支持缩放入场与高亮光圈。 class BoardPainter extends CustomPainter { BoardPainter({ required this.pieces, required this.version, this.lastMoveScale = 1, this.lastMovePulse = 0, }); final List pieces; final int version; final double lastMoveScale; final double lastMovePulse; @override void paint(Canvas canvas, Size size) { final cell = size.width / (kBoardSize - 1); final bg = Paint() ..shader = const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ AppColors.woodLight, AppColors.woodMid, AppColors.woodDark, ], ).createShader(Offset.zero & size); final rrect = RRect.fromRectAndRadius( Offset.zero & size, const Radius.circular(12), ); canvas.drawRRect(rrect, bg); final line = Paint() ..style = PaintingStyle.stroke ..color = const Color(0x66FFFFFF) ..strokeWidth = 1; for (var i = 0; i < kBoardSize; i++) { final o = cell * i; canvas.drawLine(Offset(0, o), Offset(size.width, o), line); canvas.drawLine(Offset(o, 0), Offset(o, size.height), line); } // 星位 final star = Paint()..color = const Color(0xFF3D2B1F); for (final p in const [ Offset(3, 3), Offset(3, 11), Offset(7, 7), Offset(11, 3), Offset(11, 11), ]) { canvas.drawCircle( Offset(p.dx * cell, p.dy * cell), 3, star, ); } final radius = cell / 2 - 2; final fill = Paint()..style = PaintingStyle.fill; final lastIndex = pieces.length - 1; for (var i = 0; i < pieces.length; i++) { final p = pieces[i]; final isLast = i == lastIndex; final center = Offset(p.dx, p.dy); final scale = isLast ? lastMoveScale.clamp(0.0, 1.2) : 1.0; final r = radius * scale; if (isLast && lastMovePulse > 0) { final ring = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 2.5 ..color = AppColors.lastMark.withValues( alpha: 0.85 * lastMovePulse, ); canvas.drawCircle(center, radius * (1.15 + 0.45 * lastMovePulse), ring); } // 棋子阴影 canvas.drawCircle( center.translate(1.2, 1.8), r, Paint()..color = const Color(0x44000000), ); final isWhite = p.chess is WhiteChess; fill.shader = RadialGradient( center: const Alignment(-0.35, -0.4), radius: 0.95, colors: isWhite ? const [Color(0xFFFFFFFF), Color(0xFFD8D8D8)] : const [Color(0xFF4A4A4A), Color(0xFF111111)], ).createShader(Rect.fromCircle(center: center, radius: r)); if (p.chessShape.shape == 1) { canvas.drawCircle(center, r, fill); } else { canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromCircle(center: center, radius: r), Radius.circular(r * 0.2), ), fill, ); } fill.shader = null; if (isLast && scale > 0.85) { final mark = Paint() ..style = PaintingStyle.fill ..color = AppColors.lastMark; final s = radius * 0.28; final path = Path() ..moveTo(center.dx, center.dy - s) ..lineTo(center.dx - s * 0.9, center.dy + s * 0.7) ..lineTo(center.dx + s * 0.9, center.dy + s * 0.7) ..close(); canvas.drawPath(path, mark); } } } @override bool shouldRepaint(covariant BoardPainter oldDelegate) { return oldDelegate.version != version || oldDelegate.lastMoveScale != lastMoveScale || oldDelegate.lastMovePulse != lastMovePulse; } }