Skip to content

Instantly share code, notes, and snippets.

@zonble
Created January 6, 2020 09:53
Show Gist options
  • Save zonble/303d28bce04b8e995a0174ef24b0174e to your computer and use it in GitHub Desktop.
Save zonble/303d28bce04b8e995a0174ef24b0174e to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'dart:ui';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: TurtleView(
child: Container(),
commands: [
PenDown(),
SetColor(() => Color(0xffff9933)),
Repeat(() => 20, [
Repeat(() => 180, [
Forward(() => 25.0),
Right(() => 20),
]),
Right(() => 18),
]),
PenUp(),
],
),
);
}
}
class _TurtlePainter extends CustomPainter {
final List<TurtleCommand> commands;
_TurtlePainter(this.commands);
@override
void paint(Canvas canvas, Size size) {
var turtle = TurtleState();
var paint = Paint()
..color = Colors.black
..strokeWidth = 2;
var center = Offset(size.width / 2, size.height / 2);
for (final command in commands) {
command.exec(turtle, canvas, paint, center);
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
class TurtleView extends StatelessWidget {
final List<TurtleCommand> commands;
final Widget child;
TurtleView({Key key, this.child, this.commands}) : super(key: key);
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: _TurtlePainter(commands),
child: child,
);
}
}
/// Represents the state of a turtle.
class TurtleState {
/// If the pen is down.
///
/// Or, if we should paint a line instead of merely moves the [position] of
/// the turtle while calling the [Forward] command.
bool isPenDown = false;
/// The position of the turtle.
///
/// The turtle is in the center of a canvas by default.
Offset position = Offset(0.0, 0.0);
/// The angle of the turtle.
///
/// It effect how the turtle moves while calling a [Forward] command.
double angle = 90.0;
}
/// An abstract interface for all commands.
abstract class TurtleCommand {
/// Runs the command.
void exec(TurtleState turtle, Canvas canvas, Paint paint, Offset center);
}
/// Puts the pen down.
@immutable
class PenDown implements TurtleCommand {
@override
void exec(TurtleState turtle, Canvas canvas, Paint paint, Offset center) =>
turtle.isPenDown = true;
}
/// Raises the pen up.
@immutable
class PenUp implements TurtleCommand {
@override
void exec(TurtleState turtle, Canvas canvas, Paint paint, Offset center) =>
turtle.isPenDown = false;
}
/// Turns left.
@immutable
class Left implements TurtleCommand {
/// the angle.
final double Function() angle;
/// Creates a new instance.
Left(this.angle);
@override
void exec(TurtleState turtle, Canvas canvas, Paint paint, Offset center) =>
turtle.angle += angle();
}
/// Turns right.
@immutable
class Right implements TurtleCommand {
/// the right.
final double Function() angle;
/// Creates a new instance.
Right(this.angle);
@override
void exec(TurtleState turtle, Canvas canvas, Paint paint, Offset center) =>
turtle.angle -= angle();
}
_angleToRadians(double angle) => angle / 180 * math.pi;
/// Moves forward.
@immutable
class Forward implements TurtleCommand {
/// The distance.
final double Function() distance;
/// Creates a new instance.
Forward(this.distance);
@override
void exec(TurtleState turtle, Canvas canvas, Paint paint, Offset center) {
final radians = _angleToRadians(turtle.angle);
final distance = this.distance();
final x = math.cos(radians) * distance;
final y = math.sin(radians) * distance;
final currentPosition = turtle.position;
turtle.position = currentPosition + Offset(x, y);
if (turtle.isPenDown) {
final drawingBegin = center + currentPosition;
final drawingEnd = center + turtle.position;
canvas.drawLine(drawingBegin, drawingEnd, paint);
}
}
}
/// Sets a new color.
@immutable
class SetColor implements TurtleCommand {
/// The new color.
final Color Function() color;
/// Creates a new color.
SetColor(this.color);
@override
void exec(TurtleState turtle, Canvas canvas, Paint paint, Offset center) =>
paint.color = color();
}
/// Moves the turtle to center.
@immutable
class ResetPosition implements TurtleCommand {
@override
void exec(TurtleState turtle, Canvas canvas, Paint paint, Offset center) =>
turtle.position = Offset(0.0, 0.0);
}
/// Makes the turtle to face to top.
@immutable
class ResetHeading implements TurtleCommand {
@override
void exec(TurtleState turtle, Canvas canvas, Paint paint, Offset center) =>
turtle.angle = 90;
}
/// Repeats commands
@immutable
class Repeat implements TurtleCommand {
/// How many times to repeat.
final int Function() times;
/// The commands to run.
final List<TurtleCommand> commands;
/// Creates a new instance.
Repeat(this.times, this.commands);
@override
void exec(TurtleState turtle, Canvas canvas, Paint paint, Offset center) {
for (var i = 0; i < times(); i++) {
commands.forEach((command) {
command.exec(turtle, canvas, paint, center);
});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment