Pen Settings

JavaScript

Babel includes JSX processing.

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

Flutter

              
                import 'dart:async';
import 'dart:math' as math;
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/src/scheduler/ticker.dart';
import 'dart:html' as html;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Trex',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
          fontFamily: 'Courier New'),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
  TrexGame game;

  FocusNode focusNode;

  @override
  void initState() {
    super.initState();
    focusNode = FocusNode();
    game = TrexGame(this);
    focusNode.requestFocus();
  }

  @override
  void dispose() {
    game.dispose();
    focusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return RawKeyboardListener(
      focusNode: focusNode,
      onKey: (key) {
        if (key is RawKeyDownEvent &&
            key.logicalKey == LogicalKeyboardKey.space) {
          game.space();
        }
      },
      child: GestureDetector(
        behavior: HitTestBehavior.opaque,
        onTapDown: (_) => game.space(),
        // onTap: () => game.space(),
        child: Scaffold(
          body: AnimatedBuilder(
            animation: game,
            builder: (context, child) {
              game?.updateSize(MediaQuery.of(context).size.width);
              return Stack(
                children: [
                  CustomPaint(
                    painter: TrexPainter(game),
                    child: Container(),
                  ),
                  if (game.status == GameState.idle) StartMessage(),
                  if (game.status == GameState.gameOver) GameOver(),
                  Score(game: game),
                  ChangePlayer(game: game),
                  StartStop(game: game)
                ],
              );
            },
          ),
        ),
      ),
    );
  }
}

class ChangePlayer extends StatelessWidget {
  const ChangePlayer({
    Key key,
    @required this.game,
  }) : super(key: key);

  final TrexGame game;

  @override
  Widget build(BuildContext context) {
    return Positioned(
      top: 0,
      left: 0,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: RaisedButton(
          child: Text('Change player'),
          onPressed: () => game.togglePlayer(),
        ),
      ),
    );
  }
}

class Score extends StatelessWidget {
  const Score({
    Key key,
    @required this.game,
  }) : super(key: key);

  final TrexGame game;

  @override
  Widget build(BuildContext context) {
    return Positioned(
      top: 0,
      right: 0,
      child: Padding(
        padding: const EdgeInsets.all(26.0),
        child: Row(
          children: [
            if (game.hiScore != null)
              Text(
                'HI ${game.hiScore}',
                style: TextStyle(
                  fontSize: 22,
                  fontWeight: FontWeight.bold,
                  color: Colors.grey[700],
                ),
              ),
            SizedBox(width: 12),
            Text(
              '${game.result}',
              style: TextStyle(
                fontSize: 22,
                fontWeight: FontWeight.bold,
                color: Colors.grey[700],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class StartStop extends StatelessWidget {
  const StartStop({
    Key key,
    @required this.game,
  }) : super(key: key);

  final TrexGame game;

  @override
  Widget build(BuildContext context) {
    return Positioned(
      bottom: 0,
      left: 0,
      right: 0,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          RaisedButton(
            child: Text(
                game.status == GameState.playing ? 'Stop game' : 'Start game'),
            onPressed: () {
              game.status == GameState.playing
                  ? game.stopGame()
                  : game.startGame();
            },
          ),
          RaisedButton(
            child: Text('Made by Dominik Roszkowski'),
            onPressed: () {
              html.window.open('https://twitter.com/OrestesGaolin', 'Twitter');
            },
          ),
          SizedBox(height: 36),
        ],
      ),
    );
  }
}

class StartMessage extends StatelessWidget {
  const StartMessage({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Positioned(
      top: 0,
      left: 0,
      right: 0,
      child: Padding(
        padding: EdgeInsets.only(top: MediaQuery.of(context).size.width / 4),
        child: Text(
          'Press SPACE or tap to start',
          textAlign: TextAlign.center,
        ),
      ),
    );
  }
}

class GameOver extends StatelessWidget {
  const GameOver({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Positioned(
      top: 0,
      left: 0,
      right: 0,
      child: Padding(
        padding: EdgeInsets.only(top: MediaQuery.of(context).size.height / 4),
        child: Column(
          children: [
            Text(
              'G A M E  O V E R',
              textAlign: TextAlign.center,
              style: TextStyle(
                fontWeight: FontWeight.w600,
                color: Colors.grey[700],
              ),
            ),
            SizedBox(height: 6),
            Container(
              decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(6.0),
                  color: Colors.grey[700]),
              child: Padding(
                padding: const EdgeInsets.all(3.0),
                child: Icon(
                  Icons.autorenew,
                  color: Colors.white,
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}

class TrexPainter extends CustomPainter {
  final TrexGame game;
  final stonePaint = Paint()
    ..strokeWidth = 2.0
    ..color = Colors.grey
    ..style = PaintingStyle.fill
    ..strokeCap = StrokeCap.round;

  final horizonPaint = Paint()
    ..color = Colors.grey
    ..strokeWidth = 2.0
    ..style = PaintingStyle.stroke
    ..strokeCap = StrokeCap.round;

  final obstaclePaint = Paint()
    ..color = Colors.grey[700]
    ..style = PaintingStyle.fill;

  TrexPainter(this.game);

  @override
  void paint(Canvas canvas, Size size) {
    final yHor = size.height / 2;
    // _drawDebug(canvas, size);
    _drawHorizon(yHor, size, canvas);
    _drawObstacles(canvas, yHor, size);
    if (game.player == Player.dash) {
      _drawDash(canvas, size);
    } else {
      _drawTrex(canvas, size);
    }
  }

  void _drawDash(Canvas canvas, Size size) {
    canvas.save();
    canvas.translate(size.width / 5, size.height / 2 - 30);
    canvas.scale(0.3);
    game.trex.getDash(canvas);
    canvas.restore();
  }

  void _drawDebug(Canvas canvas, Size size) {
    final span = TextSpan(
        text: 'x: ${game.xPosition.toStringAsFixed(0)}, width: ${size.width}',
        style: TextStyle(
            color: Colors.grey[800], fontSize: 20, fontFamily: 'Courier New'));
    final painter = TextPainter(text: span, textDirection: TextDirection.ltr);
    painter.layout();
    painter.paint(canvas, Offset(20, 80));
    for (var i = 0; i < game.horizon.obstacles.length; i++) {
      final span = TextSpan(
          text: '${game.horizon.obstacles[i].position.toStringAsFixed(0)}',
          style: TextStyle(color: Colors.grey[800], fontSize: 15));
      final painter = TextPainter(text: span, textDirection: TextDirection.ltr);
      painter.layout();
      painter.paint(canvas, Offset(20, 15.0 * i + 120));
    }
  }

  void _drawObstacles(Canvas canvas, double yHor, Size size) {
    canvas.save();
    canvas.translate(-game.xPosition, 0);
    canvas.translate(0, yHor - 30);
    for (var obst in game.horizon.obstacles) {
      canvas.save();
      canvas.translate(obst.position, 0);
      canvas.scale(0.45);
      canvas.drawPath(obst.path, obstaclePaint);

      // _drawObstacleX(obst, canvas);
      canvas.restore();
    }

    canvas.restore();
  }

  void _drawObstacleX(Obstacle obst, Canvas canvas) {
    final span = TextSpan(
        text: '${obst.position.toStringAsFixed(0)}',
        style: TextStyle(color: Colors.grey[800], fontSize: 20));
    final painter = TextPainter(text: span, textDirection: TextDirection.ltr);
    painter.layout();
    painter.paint(canvas, Offset(20, 80));
  }

  void _drawHorizon(double yHor, Size size, Canvas canvas) {
    final horizonObs = size.width / 4;

    canvas.save();
    canvas.translate(-size.width, 0);
    canvas.translate(-game.xPosition % (size.width * 2), 0);

    final horizonPath = Path()
      ..moveTo(-size.width, yHor)
      ..lineTo(horizonObs - 16.0, yHor)
      ..lineTo(horizonObs - 10.0, yHor - 3.0)
      ..lineTo(horizonObs + 0.0, yHor - 3.0)
      ..lineTo(horizonObs + 6.0, yHor)
      ..lineTo(horizonObs + 10.0, yHor)
      ..lineTo(horizonObs + 12.0, yHor + 3.0)
      ..lineTo(horizonObs + 18.0, yHor + 3.0)
      ..lineTo(horizonObs + 22.0, yHor)
      ..lineTo(horizonObs + 286.0, yHor)
      ..lineTo(horizonObs + 290.0, yHor - 3.0)
      ..lineTo(horizonObs + 300.0, yHor - 3.0)
      ..lineTo(horizonObs + 306.0, yHor)
      ..lineTo(horizonObs + 310.0, yHor)
      ..lineTo(horizonObs + 312.0, yHor + 3.0)
      ..lineTo(horizonObs + 318.0, yHor + 3.0)
      ..lineTo(horizonObs + 322.0, yHor)
      ..lineTo(8 * horizonObs, yHor);

    canvas.drawPoints(
      PointMode.points,
      game.horizon.horizonStones
          .map(
            (e) => Offset(
              e.dx * size.width / 25 + horizonObs,
              e.dy + yHor,
            ),
          )
          .toList(),
      stonePaint,
    );
    canvas.drawPoints(
      PointMode.points,
      game.horizon.horizonStones
          .map(
            (e) => Offset(
              e.dx * size.width / 25 + horizonObs - size.width * 2,
              e.dy + yHor,
            ),
          )
          .toList(),
      stonePaint,
    );

    canvas.drawPath(horizonPath, horizonPaint);
    canvas.restore();
  }

  void _drawTrex(Canvas canvas, Size size) {
    canvas.save();
    canvas.translate(size.width / 5, size.height / 2 - 24);
    canvas.scale(0.1);
    final path = Path()..addPolygon(game.trex.bodyPoints(), true);
    canvas.drawPath(
      path,
      Paint()..color = Colors.grey[700],
    );
    final eye = Path()..addRect(game.trex.eye);
    canvas.drawPath(eye, Paint()..color = Colors.white);
    canvas.restore();
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => true;
}

enum GameState { idle, playing, gameOver }

class TrexGame extends ChangeNotifier {
  final TickerProvider tickerProvider;

  Ticker _ticker;
  Horizon horizon;
  Trex trex;
  Player player = Player.trex;

  double _time = 0.0;
  GameState _status = GameState.idle;
  GameState get status => _status;
  double _xPosition = 0.0;
  double get xPosition => _xPosition;
  String get result => _time.toStringAsFixed(0).padLeft(5, "0");

  double _screenSize = 500.0;

  String hiScore;

  TrexGame(this.tickerProvider) {
    trex = Trex();
    horizon = Horizon();
  }

  void updateSize(double size) {
    _screenSize = size;
  }

  void startGame() {
    _startGameLoop();
  }

  void stopGame({bool fail}) {
    _status = fail == true ? GameState.gameOver : GameState.idle;
    trex.stop();
    _ticker?.stop();
    _ticker?.dispose();
    hiScore = result;
    notifyListeners();
  }

  void togglePlayer() {
    if (player == Player.trex) {
      player = Player.dash;
    } else {
      player = Player.trex;
    }
  }

  void _startGameLoop() {
    print('Starting game');
    horizon = Horizon();
    _time = 0.0;
    _xPosition = 0.0;
    _ticker = tickerProvider.createTicker(_tick)..start();
    _status = GameState.playing;

    trex.start();
  }

  void _tick(Duration deltaTime) {
    final lastFrameTime = deltaTime.inMilliseconds.toDouble() / 1000.0;

    _time = lastFrameTime;
    _xPosition = lastFrameTime * 100 * ((_time + 50.0) / 50.0).clamp(1.0, 50.0);
    trex.update(_time);
    horizon.update(_time, xPosition, _screenSize);
    _checkCollisions();

    notifyListeners();
  }

  void _checkCollisions() {
    final margin = 7.0;
    final trexPosition = xPosition + _screenSize / 5;
    for (var obstacle in horizon.obstacles) {
      if (obstacle.position > trexPosition - margin &&
          obstacle.position < trexPosition + margin) {
        if (trex.yOffset > -margin) {
          stopGame(fail: true);
        }
      }
    }
  }

  void jump() {
    trex.jump();
  }

  void space() {
    if (status == GameState.playing) {
      jump();
    } else {
      startGame();
    }
  }

  @override
  void dispose() {
    _ticker.dispose();
    super.dispose();
  }
}

enum TrexState { idle, running, jump }

enum Player { trex, dash }

class Trex {
  final double startJumpVelocity = -2500.0;
  final double dropVelocity = 0.0;

  double time = 0.0;
  TrexState state = TrexState.idle;
  bool get legTicker => (time * 10).toInt().isEven;
  double yOffset = 0.0;
  double jumpStart;
  double jumpVelocity = 0.0;

  void jump() {
    if (state != TrexState.jump) {
      state = TrexState.jump;
      jumpStart = time;
      jumpVelocity = startJumpVelocity;
      yOffset = 0.0;
    }
  }

  void update(double time) {
    this.time = time;
    if (state == TrexState.jump) {
      final delta = time - jumpStart;
      yOffset = jumpVelocity * delta;
      jumpVelocity += 320 * delta;

      if (yOffset >= 0.0) {
        state = TrexState.running;
        yOffset = 0.0;
      }
    }
  }

  Rect get eye => Rect.fromLTWH(
        169.247839,
        20.5685714 + yOffset,
        16.9740634,
        16.9914286,
      );

  List<Offset> bodyPoints() {
    bool leftLeg =
        state == TrexState.idle || legTicker && state == TrexState.running;
    bool rightLeg =
        state == TrexState.idle || !legTicker && state == TrexState.running;

    return [
      Offset(256.351585, 91.6642857 + yOffset),
      Offset(213.916427, 91.6642857 + yOffset),
      Offset(213.916427, 80.0385714 + yOffset),
      Offset(284.492795, 80.0385714 + yOffset),
      Offset(284.492795, 14.3085714 + yOffset),
      Offset(270.198847, 14.3085714 + yOffset),
      Offset(270.198847, 0 + yOffset),
      Offset(155.400576, 0 + yOffset),
      Offset(155.400576, 14.3085714 + yOffset),
      Offset(141.553314, 14.3085714 + yOffset),
      Offset(141.553314, 105.972857 + yOffset),
      Offset(127.259366, 105.972857 + yOffset),
      Offset(127.259366, 119.834286 + yOffset),
      Offset(106.26513, 119.834286 + yOffset),
      Offset(106.26513, 134.142857 + yOffset),
      Offset(85.2708934, 134.142857 + yOffset),
      Offset(85.2708934, 148.451429 + yOffset),
      Offset(70.9769452, 148.451429 + yOffset),
      Offset(70.9769452, 162.312857 + yOffset),
      Offset(45.0691643, 162.312857 + yOffset),
      Offset(45.0691643, 148.004286 + yOffset),
      Offset(31.221902, 148.004286 + yOffset),
      Offset(31.221902, 134.142857 + yOffset),
      Offset(16.9279539, 134.142857 + yOffset),
      Offset(16.9279539, 105.972857 + yOffset),
      Offset(0.847262248, 105.972857 + yOffset),
      Offset(0.847262248, 192.718571 + yOffset),
      Offset(14.6945245, 192.718571 + yOffset),
      Offset(14.6945245, 207.027143 + yOffset),
      Offset(28.9884726, 207.027143 + yOffset),
      Offset(28.9884726, 220.888571 + yOffset),
      Offset(42.8357349, 220.888571 + yOffset),
      Offset(42.8357349, 235.197143 + yOffset),
      Offset(57.129683, 235.197143 + yOffset),
      Offset(57.129683, 249.058571 + yOffset),
      Offset(70.9769452, 249.058571 + yOffset),
      if (leftLeg) ...[
        Offset(70.9769452, 305.398571 + yOffset),
        Offset(101.351585, 305.398571 + yOffset),
        Offset(101.351585, 289.301429 + yOffset),
        Offset(87.5043228, 289.301429 + yOffset),
        Offset(87.5043228, 277.228571 + yOffset),
        Offset(101.351585, 277.228571 + yOffset),
      ],
      Offset(101.351585, 263.367143 + yOffset),
      Offset(115.645533, 263.367143 + yOffset),
      Offset(115.645533, 249.058571 + yOffset),
      Offset(127.259366, 249.058571 + yOffset),
      if (rightLeg) ...[
        Offset(127.259366, 263.367143 + yOffset),
        Offset(141.553314, 263.367143 + yOffset),
        Offset(141.553314, 305.398571 + yOffset),
        Offset(171.927954, 305.398571 + yOffset),
        Offset(171.927954, 289.301429 + yOffset),
        Offset(157.634006, 289.301429 + yOffset),
      ],
      Offset(157.634006, 235.197143 + yOffset),
      Offset(171.927954, 235.197143 + yOffset),
      Offset(171.927954, 220.888571 + yOffset),
      Offset(185.775216, 220.888571 + yOffset),
      Offset(185.775216, 199.872857 + yOffset),
      Offset(200.069164, 199.872857 + yOffset),
      Offset(200.069164, 150.687143 + yOffset),
      Offset(211.682997, 150.687143 + yOffset),
      Offset(211.682997, 164.548571 + yOffset),
      Offset(228.210375, 164.548571 + yOffset),
      Offset(228.210375, 134.142857 + yOffset),
      Offset(200.069164, 134.142857 + yOffset),
      Offset(200.069164, 108.208571 + yOffset),
      Offset(256.351585, 108.208571 + yOffset),
      Offset(256.351585, 91.6642857 + yOffset),
    ];
  }

  void getDash(Canvas canvas) {
    bool leftLeg =
        state == TrexState.idle || legTicker && state == TrexState.running;
    bool rightLeg =
        state == TrexState.idle || !legTicker && state == TrexState.running;

    final body = Path()
      ..addOval(Rect.fromLTWH(
        10,
        10,
        100,
        100,
      ));
    final tail = Path()
      ..moveTo(0, 20)
      ..lineTo(40, 40)
      ..lineTo(40, 80)
      ..lineTo(0, 50);
    final wing = Path()
      ..moveTo(0, 40)
      ..lineTo(45, 40)
      ..relativeArcToPoint(Offset(0, 40), radius: Radius.circular(20))
      ..lineTo(15, 80)
      ..close();
    final tip = Path()
      ..moveTo(90, 50)
      ..lineTo(140, 60)
      ..lineTo(90, 60);
    final eye = Path()..addOval(Rect.fromLTWH(80, 40, 10, 10));
    final eyeWhite = Path()..addOval(Rect.fromLTWH(82.5, 42.5, 3, 3));
    final top = Path()..addOval(Rect.fromLTWH(50, 6, 20, 10));
    final legL = Path()
      ..moveTo(50, 100)
      ..lineTo(50, 130)
      ..lineTo(52, 135)
      ..lineTo(63, 135)
      ..lineTo(58, 130)
      ..lineTo(58, 100);
    final legR = Path()
      ..moveTo(68, 100)
      ..lineTo(68, 130)
      ..lineTo(70, 135)
      ..lineTo(81, 135)
      ..lineTo(76, 130)
      ..lineTo(76, 100);
    final faceWhite = Path()
      ..moveTo(90, 50)
      ..relativeArcToPoint(Offset(-0, 50),
          radius: Radius.circular(15), clockwise: false)
      ..lineTo(100, 90)
      ..lineTo(110, 80);

    canvas.translate(0, yOffset);
    if (leftLeg) canvas.drawPath(legL, Paint()..color = Colors.brown[400]);
    if (rightLeg) canvas.drawPath(legR, Paint()..color = Colors.brown[400]);
    canvas.drawPath(body, Paint()..color = Colors.blue[300]);
    canvas.drawPath(tail, Paint()..color = Colors.teal[400]);
    canvas.drawPath(faceWhite, Paint()..color = Colors.white.withOpacity(0.8));
    canvas.drawPath(wing, Paint()..color = Colors.blue[500]);
    canvas.drawPath(tip, Paint()..color = Colors.brown[400]);
    canvas.drawPath(eye, Paint()..color = Colors.black);
    canvas.drawPath(eyeWhite, Paint()..color = Colors.white);
    canvas.drawPath(top, Paint()..color = Colors.blue[400]);
  }

  void start() {
    yOffset = 0.0;
    state = TrexState.running;
  }

  void stop() {
    state = TrexState.idle;
  }
}

class Horizon {
  /// x positions of obstacles on horizon
  final obstacles = <Obstacle>[];
  final horizonStones = <Offset>[];
  final double margin = 20.0;

  Horizon() {
    _generateHorizonStones();
  }

  _generateHorizonStones() {
    for (var i = 0; i < 50; i++) {
      final rand = math.Random().nextInt(12);
      final point = Offset(i.toDouble(), rand.toDouble());
      horizonStones.add(point);
    }
  }

  void update(double time, double xPosition, double margin) {
    if (obstacles.length == 0) {
      obstacles.add(Obstacle(xPosition + 500));
      obstacles.add(Obstacle(xPosition + 700));
      obstacles.add(Obstacle(xPosition + 900));
      obstacles.add(Obstacle(xPosition + 1050));
      obstacles.add(Obstacle(xPosition + 1150));
      obstacles.add(Obstacle(xPosition + 1300));
      obstacles.add(Obstacle(xPosition + 1500));
      obstacles.add(Obstacle(xPosition + 1700));
      obstacles.add(Obstacle(xPosition + 1870));
      obstacles.add(Obstacle(xPosition + 2000));
      obstacles.add(Obstacle(xPosition + 2250));
      obstacles.add(Obstacle(xPosition + 2330));
      obstacles.add(Obstacle(xPosition + 2450));
      obstacles.add(Obstacle(xPosition + 2650));
      obstacles.add(Obstacle(xPosition + 2850));
    }
    final newObstacles = obstacles.take(obstacles.length).toList();
    for (var obstacle in newObstacles) {
      if (obstacle.position < xPosition - margin) {
        final rand = math.Random().nextInt(8) + 2;
        print(
            'removing obstacle at ${obstacle.position}, current x $xPosition');
        obstacles.remove(obstacle);
        final newPos = obstacles.map((e) => e.position).reduce(math.max) +
            30 * rand +
            time;
        obstacles.add(Obstacle(newPos));
        print('added obstacle at ${newPos}, current x $xPosition');
      }
    }
  }
}

class Obstacle {
  final double position;
  final double width = 10.0;

  Obstacle(this.position);

  bool isVisible(double horizonPosition) {
    return position > horizonPosition;
  }

  Path get path => Path()
    ..moveTo(40, 80)
    ..lineTo(55, 80)
    ..lineTo(55, 60)
    ..lineTo(65, 60)
    ..lineTo(70, 55)
    ..lineTo(70, 30)
    ..lineTo(68, 27)
    ..lineTo(64, 27)
    ..lineTo(62, 30)
    ..lineTo(62, 50)
    ..lineTo(55, 50)
    ..lineTo(55, 10)
    ..lineTo(53, 7)
    ..lineTo(42, 7)
    ..lineTo(40, 10)
    ..lineTo(40, 52)
    ..lineTo(35, 52)
    ..lineTo(35, 28)
    ..lineTo(30, 25)
    ..lineTo(28, 25)
    ..lineTo(25, 28)
    ..lineTo(25, 52)
    ..lineTo(28, 55)
    ..lineTo(30, 58)
    ..lineTo(40, 58)
    ..close();
}

              
            
!
999px

Console