| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // ignore_for_file: public_member_api_docs |
| |
| import 'dart:async'; |
| import 'dart:math' as math; |
| |
| import 'package:flutter/material.dart'; |
| import 'package:sensors/sensors.dart'; |
| |
| class Snake extends StatefulWidget { |
| Snake({this.rows = 20, this.columns = 20, this.cellSize = 10.0}) { |
| assert(10 <= rows); |
| assert(10 <= columns); |
| assert(5.0 <= cellSize); |
| } |
| |
| final int rows; |
| final int columns; |
| final double cellSize; |
| |
| @override |
| State<StatefulWidget> createState() => SnakeState(rows, columns, cellSize); |
| } |
| |
| class SnakeBoardPainter extends CustomPainter { |
| SnakeBoardPainter(this.state, this.cellSize); |
| |
| GameState state; |
| double cellSize; |
| |
| @override |
| void paint(Canvas canvas, Size size) { |
| final Paint blackLine = Paint()..color = Colors.black; |
| final Paint blackFilled = Paint() |
| ..color = Colors.black |
| ..style = PaintingStyle.fill; |
| canvas.drawRect( |
| Rect.fromPoints(Offset.zero, size.bottomLeft(Offset.zero)), |
| blackLine, |
| ); |
| for (math.Point<int> p in state.body) { |
| final Offset a = Offset(cellSize * p.x, cellSize * p.y); |
| final Offset b = Offset(cellSize * (p.x + 1), cellSize * (p.y + 1)); |
| |
| canvas.drawRect(Rect.fromPoints(a, b), blackFilled); |
| } |
| } |
| |
| @override |
| bool shouldRepaint(CustomPainter oldDelegate) { |
| return true; |
| } |
| } |
| |
| class SnakeState extends State<Snake> { |
| SnakeState(int rows, int columns, this.cellSize) { |
| state = GameState(rows, columns); |
| } |
| |
| double cellSize; |
| GameState state; |
| AccelerometerEvent acceleration; |
| StreamSubscription<AccelerometerEvent> _streamSubscription; |
| Timer _timer; |
| |
| @override |
| Widget build(BuildContext context) { |
| return CustomPaint(painter: SnakeBoardPainter(state, cellSize)); |
| } |
| |
| @override |
| void dispose() { |
| super.dispose(); |
| _streamSubscription.cancel(); |
| _timer.cancel(); |
| } |
| |
| @override |
| void initState() { |
| super.initState(); |
| _streamSubscription = |
| accelerometerEvents.listen((AccelerometerEvent event) { |
| setState(() { |
| acceleration = event; |
| }); |
| }); |
| |
| _timer = Timer.periodic(const Duration(milliseconds: 200), (_) { |
| setState(() { |
| _step(); |
| }); |
| }); |
| } |
| |
| void _step() { |
| final math.Point<int> newDirection = acceleration == null |
| ? null |
| : acceleration.x.abs() < 1.0 && acceleration.y.abs() < 1.0 |
| ? null |
| : (acceleration.x.abs() < acceleration.y.abs()) |
| ? math.Point<int>(0, acceleration.y.sign.toInt()) |
| : math.Point<int>(-acceleration.x.sign.toInt(), 0); |
| state.step(newDirection); |
| } |
| } |
| |
| class GameState { |
| GameState(this.rows, this.columns) { |
| snakeLength = math.min(rows, columns) - 5; |
| } |
| |
| int rows; |
| int columns; |
| int snakeLength; |
| |
| List<math.Point<int>> body = <math.Point<int>>[const math.Point<int>(0, 0)]; |
| math.Point<int> direction = const math.Point<int>(1, 0); |
| |
| void step(math.Point<int> newDirection) { |
| math.Point<int> next = body.last + direction; |
| next = math.Point<int>(next.x % columns, next.y % rows); |
| |
| body.add(next); |
| if (body.length > snakeLength) body.removeAt(0); |
| direction = newDirection ?? direction; |
| } |
| } |