绘制集录-井字棋绘制

This commit is contained in:
toly
2021-04-23 16:52:32 +08:00
parent 7b0e147cbf
commit 709fe248a7
6 changed files with 382 additions and 3 deletions

View File

@@ -0,0 +1,145 @@
import 'dart:math';
import 'package:flutter/material.dart';
class StampData extends ChangeNotifier {
final List<Stamp> stamps = [];
void push(Stamp stamp) {
stamps.add(stamp);
notifyListeners();
}
void removeLast() {
stamps.removeLast();
notifyListeners();
}
void activeLast({Color color = Colors.blue}) {
stamps.last.color = color;
notifyListeners();
}
void clear() {
stamps.clear();
notifyListeners();
}
void animateAt(int index, double radius) {
stamps[index].radius = radius;
stamps[index].rePath();
notifyListeners();
}
GameState checkWin(double length){
bool redWin = _checkWinByColor(length,Colors.red);
if(redWin) return GameState.redWin;
bool blueWin = _checkWinByColor(length,Colors.blue);
if(blueWin) return GameState.blueWin;
return GameState.doing;
}
bool _checkWinByColor(double length,Color color) {
List<Offset> red = stamps
.where((element) => element.color == color)
.map((e) => e.center)
.toList();
List<Point<int>> redPoints = red
.map<Point<int>>((e) => Point<int>(e.dx ~/ length, e.dy ~/ length))
.toList();
return _checkWinInline(redPoints, 3);
}
bool _checkWinInline(List<Point<int>> points, int max) {
if (points.length < max) return false;
for (int i = 0; i < points.length; i++) {
int x = points[i].x;
int y = points[i].y;
if (_check(x, y, points, CheckModel.horizontal,max)) {
return true;
} else if (_check(x, y, points, CheckModel.vertical,max)) {
return true;
} else if (_check(x, y, points, CheckModel.leftDiagonal,max)) {
return true;
} else if (_check(x, y, points, CheckModel.rightDiagonal,max)) {
return true;
}
}
return false;
}
bool _check(int x, int y, List<Point> points, CheckModel checkModel,int max) {
int count = 1;
Point checkPoint;
for (int i = 1; i < max; i++) {
switch (checkModel) {
case CheckModel.horizontal: checkPoint = Point(x - i, y); break;
case CheckModel.vertical: checkPoint = Point(x, y - i); break;
case CheckModel.leftDiagonal: checkPoint = Point(x - i, y + i);break;
case CheckModel.rightDiagonal: checkPoint = Point(x + i, y + i); break;
}
if (points.contains(checkPoint)) {count++;} else {break;}
}
if (count == max) return true;
return false;
}
}
enum CheckModel {
horizontal, // 横向判断
vertical, // 竖向判断
leftDiagonal, // 左斜判断
rightDiagonal // 右斜判断
}
enum GameState{
doing, // 进行中
redWin, // 红胜
blueWin // 蓝胜
}
class Stamp {
Color color;
Offset center;
double radius;
Stamp({this.color = Colors.blue, this.center, this.radius = 20});
Path _path;
Path get path {
if (_path == null) {
_path = Path();
double r = radius;
double rad = 30 / 180 * pi;
_path..moveTo(center.dx, center.dy);
_path.relativeMoveTo(r * cos(rad), -r * sin(rad));
_path.relativeLineTo(-2 * r * cos(rad), 0);
_path.relativeLineTo(r * cos(rad), r + r * sin(rad));
_path.relativeLineTo(r * cos(rad), -(r + r * sin(rad)));
_path..moveTo(center.dx, center.dy);
_path.relativeMoveTo(0, -r);
_path.relativeLineTo(-r * cos(rad), r + r * sin(rad));
_path.relativeLineTo(2 * r * cos(rad), 0);
_path.relativeLineTo(-r * cos(rad), -(r + r * sin(rad)));
return _path;
} else {
return _path;
}
}
set path(Path path) {
_path = path;
}
void rePath() {
_path = null;
_path = path;
}
}

View File

@@ -0,0 +1,227 @@
import 'dart:math';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'stamp_data.dart';
class StampPaper extends StatefulWidget {
@override
_StampPaperState createState() => _StampPaperState();
}
class _StampPaperState extends State<StampPaper>
with SingleTickerProviderStateMixin {
final StampData stamps = StampData();
int gridCount = 3;
double radius = 0;
double width = 0;
GameState gameState = GameState.doing;
bool get gameOver => gameState != GameState.doing;
// 定义动画器
AnimationController _controller;
final Duration animDuration = const Duration(milliseconds: 200);
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: animDuration)
..addListener(_listenAnim);
}
void _listenAnim() {
if (_controller.value == 1.0) {
_controller.reverse();
}
double rate = (0.9 - 1) * _controller.value + 1;
stamps.animateAt(containsIndex, rate * radius);
}
@override
Widget build(BuildContext context) {
width = MediaQuery.of(context).size.shortestSide * 0.8;
return GestureDetector(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
onDoubleTap: _clear,
onTapCancel: _removeLast,
child: CustomPaint(
foregroundPainter: StampPainter(stamps: stamps, count: gridCount),
painter: BackGroundPainter(count: gridCount),
size: Size(width, width),
),
);
}
bool get contains => containsIndex != -1;
void _onTapDown(TapDownDetails details) {
if (gameOver) return;
containsIndex = checkZone(details.localPosition);
if (contains) {
_controller.forward();
return;
}
radius = width / 2 / gridCount * 0.618;
stamps.push(Stamp(
radius: radius, center: details.localPosition, color: Colors.grey));
}
int containsIndex = -1;
int checkZone(Offset src) {
for (int i = 0; i < stamps.stamps.length; i++) {
Rect zone = Rect.fromCircle(
center: stamps.stamps[i].center, radius: width / gridCount / 2);
if (zone.contains(src)) {
return i;
}
}
return -1;
}
void _onTapUp(TapUpDetails details) {
if (contains || gameOver) return;
stamps.activeLast(
color: stamps.stamps.length % 2 == 0 ? Colors.red : Colors.blue);
gameState = stamps.checkWin(width / gridCount);
if (gameState == GameState.redWin) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("红棋获胜!"),
backgroundColor: Colors.red,
));
}
if (gameState == GameState.blueWin) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("蓝棋获胜!"), backgroundColor: Colors.blue));
}
}
void _clear() {
stamps.clear();
gameState=GameState.doing;
}
void _removeLast() {
if (contains || gameOver) return;
stamps.removeLast();
}
@override
void dispose() {
stamps.dispose();
super.dispose();
}
}
class BackGroundPainter extends CustomPainter {
BackGroundPainter({this.count = 3});
final int count;
final Paint _pathPaint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1;
static const List<Color> colors = [
Color(0xFFF60C0C),
Color(0xFFF3B913),
Color(0xFFE7F716),
Color(0xFF3DF30B),
Color(0xFF0DF6EF),
Color(0xFF0829FB),
Color(0xFFB709F4),
];
static const List<double> pos = [
1.0 / 7,
2.0 / 7,
3.0 / 7,
4.0 / 7,
5.0 / 7,
6.0 / 7,
1.0
];
@override
void paint(Canvas canvas, Size size) {
Rect zone = Offset.zero & size;
canvas.clipRect(zone);
_pathPaint.shader = ui.Gradient.sweep(
Offset(size.width / 2, size.height / 2),
colors,
pos,
TileMode.mirror,
pi / 2,
pi);
canvas.save();
for (int i = 0; i < count - 1; i++) {
canvas.translate(0, size.height / count);
canvas.drawLine(Offset.zero, Offset(size.width, 0), _pathPaint);
}
canvas.restore();
canvas.save();
for (int i = 0; i < count - 1; i++) {
canvas.translate(size.width / count, 0);
canvas.drawLine(Offset.zero, Offset(0, size.height), _pathPaint);
}
canvas.restore();
}
@override
bool shouldRepaint(covariant BackGroundPainter oldDelegate) {
return count != oldDelegate.count;
}
}
class StampPainter extends CustomPainter {
final StampData stamps;
final int count;
final Paint _paint = Paint();
final Paint _pathPaint = Paint()
..color = Colors.white
..style = PaintingStyle.stroke;
StampPainter({this.stamps, this.count = 3}) : super(repaint: stamps);
@override
void paint(Canvas canvas, Size size) {
Rect zone = Offset.zero & size;
canvas.clipRect(zone);
stamps.stamps.forEach((stamp) {
double length = size.width / count;
int x = stamp.center.dx ~/ (size.width / count);
int y = stamp.center.dy ~/ (size.width / count);
double strokeWidth = stamp.radius * 0.07;
Offset center = Offset(length * x + length / 2, length * y + length / 2);
stamp.center = center;
canvas.drawCircle(
stamp.center, stamp.radius, _paint..color = stamp.color);
canvas.drawPath(
stamp.path,
_pathPaint
..strokeWidth = strokeWidth
..color = Colors.white);
canvas.drawCircle(stamp.center, stamp.radius + strokeWidth * 1.5,
_pathPaint..color = stamp.color);
});
}
@override
bool shouldRepaint(covariant StampPainter oldDelegate) {
return this.stamps != oldDelegate.stamps || this.count != oldDelegate.count;
}
}

View File

@@ -7,7 +7,7 @@ import 'base/draw_picture.dart';
import 'art/hypnotic_squares.dart';
import 'art/joy_division.dart';
import 'art/piet_mondrian.dart';
import 'base/random_portrait.dart';
import 'fun/random_portrait.dart';
import 'art/tiled_lines.dart';
import 'art/triangular_mesh.dart';
import 'art/un_deux_trois.dart';

View File

@@ -14,7 +14,8 @@ import 'base/clock_widget.dart';
import 'base/draw_path_fun.dart';
import 'base/draw_grid_axis.dart';
import 'base/draw_picture.dart';
import 'base/random_portrait.dart';
import 'fun/random_portrait.dart';
import 'fun/stemp/stamp_paper.dart';
import 'gallery.dart';
/// create by 张风捷特烈 on 2020/12/5
@@ -82,6 +83,12 @@ class GalleryFactory {
info:
" 本样例介绍绘制矩形及随机数处理。通过点位集合确定矩形位置信息,将其绘制出来。其中对点的随机生成和对称处理能让你练习对数据的控制力。",
content: RandomPortrait()),
FrameShower(
title: "井字棋",
author: "张风捷特烈",
info:
" 本例通过井字棋的绘制与逻辑校验,集合了手势、绘制、动画、校验等重要的技能,是一个非常好的联系案例。",
content: StampPaper()),
];
case GalleryType.art:
return [

View File

@@ -7,7 +7,7 @@ import 'package:flutter_unit/painter_system/base/draw_picture.dart';
import 'package:flutter_unit/painter_system/art/hypnotic_squares.dart';
import 'package:flutter_unit/painter_system/art/joy_division.dart';
import 'package:flutter_unit/painter_system/art/piet_mondrian.dart';
import 'package:flutter_unit/painter_system/base/random_portrait.dart';
import 'package:flutter_unit/painter_system/fun/random_portrait.dart';
import 'package:flutter_unit/painter_system/art/tiled_lines.dart';
import 'package:flutter_unit/painter_system/art/triangular_mesh.dart';
import 'package:flutter_unit/painter_system/art/un_deux_trois.dart';