From 4e2f56a70c372e6606d84e863303a8499248dafa Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Mon, 7 Mar 2022 12:48:24 +0100 Subject: [PATCH 01/27] feat: created maths package for calculate curves --- packages/maths/.gitignore | 7 +++ packages/maths/README.md | 11 ++++ packages/maths/analysis_options.yaml | 1 + packages/maths/lib/maths.dart | 3 + packages/maths/lib/src/maths.dart | 81 +++++++++++++++++++++++++ packages/maths/pubspec.yaml | 15 +++++ packages/maths/test/src/maths_test.dart | 11 ++++ 7 files changed, 129 insertions(+) create mode 100644 packages/maths/.gitignore create mode 100644 packages/maths/README.md create mode 100644 packages/maths/analysis_options.yaml create mode 100644 packages/maths/lib/maths.dart create mode 100644 packages/maths/lib/src/maths.dart create mode 100644 packages/maths/pubspec.yaml create mode 100644 packages/maths/test/src/maths_test.dart diff --git a/packages/maths/.gitignore b/packages/maths/.gitignore new file mode 100644 index 00000000..526da158 --- /dev/null +++ b/packages/maths/.gitignore @@ -0,0 +1,7 @@ +# See https://www.dartlang.org/guides/libraries/private-files + +# Files and directories created by pub +.dart_tool/ +.packages +build/ +pubspec.lock \ No newline at end of file diff --git a/packages/maths/README.md b/packages/maths/README.md new file mode 100644 index 00000000..cbc4cca1 --- /dev/null +++ b/packages/maths/README.md @@ -0,0 +1,11 @@ +# maths + +[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link] +[![License: MIT][license_badge]][license_link] + +A Very Good Project created by Very Good CLI. + +[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg +[license_link]: https://opensource.org/licenses/MIT +[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg +[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis \ No newline at end of file diff --git a/packages/maths/analysis_options.yaml b/packages/maths/analysis_options.yaml new file mode 100644 index 00000000..3742fc3d --- /dev/null +++ b/packages/maths/analysis_options.yaml @@ -0,0 +1 @@ +include: package:very_good_analysis/analysis_options.2.4.0.yaml \ No newline at end of file diff --git a/packages/maths/lib/maths.dart b/packages/maths/lib/maths.dart new file mode 100644 index 00000000..b340388b --- /dev/null +++ b/packages/maths/lib/maths.dart @@ -0,0 +1,3 @@ +library maths; + +export 'src/maths.dart'; diff --git a/packages/maths/lib/src/maths.dart b/packages/maths/lib/src/maths.dart new file mode 100644 index 00000000..ea447db5 --- /dev/null +++ b/packages/maths/lib/src/maths.dart @@ -0,0 +1,81 @@ +import 'dart:math' as math; +import 'package:flame/extensions.dart'; + +/// Method to calculate all points (with a required precision amount of them) +/// of a circumference based on angle, offsetAngle and radius +List calculateArc({ + required Vector2 center, + required double radius, + required double angle, + double offsetAngle = 0, + int precision = 100, +}) { + final stepAngle = radians(angle / precision); + final stepOffset = radians(offsetAngle); + + final points = []; + for (var i = 0; i <= precision; i++) { + final xCoord = center.x + radius * math.cos((stepAngle * i) + stepOffset); + final yCoord = center.y - radius * math.sin((stepAngle * i) + stepOffset); + + final point = Vector2(xCoord, yCoord); + points.add(point); + } + + return points; +} + +/// Method that calculates all points of a bezier curve of degree 'g' and +/// n=g-1 control points and range 0<=t<=1 +/// https://en.wikipedia.org/wiki/B%C3%A9zier_curve +List calculateBezierCurve({ + required List controlPoints, + double step = 0.001, +}) { + assert( + controlPoints.length >= 2, + 'At least 2 control points to create a bezier curve', + ); + var t = 0.0; + final n = controlPoints.length - 1; + final points = []; + + do { + var xCoord = 0.0; + var yCoord = 0.0; + for (var i = 0; i <= n; i++) { + final point = controlPoints[i]; + + xCoord += + _binomial(n, i) * math.pow(1 - t, n - i) * math.pow(t, i) * point.x; + yCoord += + _binomial(n, i) * math.pow(1 - t, n - i) * math.pow(t, i) * point.y; + } + points.add(Vector2(xCoord, yCoord)); + + t = t + step; + } while (t <= 1); + + return points; +} + +/// Method to calculate the binomial coefficient of 'n' and 'k' +/// https://en.wikipedia.org/wiki/Binomial_coefficient +num _binomial(num n, num k) { + assert(0 <= k && k <= n, 'Range 0<=k<=n'); + if (k == 0 || n == k) { + return 1; + } else { + return _factorial(n) / (_factorial(k) * _factorial(n - k)); + } +} + +/// Method to calculate the factorial of some number 'n' +/// https://en.wikipedia.org/wiki/Factorial +num _factorial(num n) { + if (n == 1) { + return 1; + } else { + return n * _factorial(n - 1); + } +} diff --git a/packages/maths/pubspec.yaml b/packages/maths/pubspec.yaml new file mode 100644 index 00000000..3b4b863c --- /dev/null +++ b/packages/maths/pubspec.yaml @@ -0,0 +1,15 @@ +name: maths +description: A Very Good Project created by Very Good CLI. +version: 1.0.0+1 +publish_to: none + +environment: + sdk: ">=2.16.0 <3.0.0" + +dev_dependencies: + coverage: ^1.1.0 + mocktail: ^0.2.0 + test: ^1.19.2 + very_good_analysis: ^2.4.0 +dependencies: + flame: ^1.0.0 diff --git a/packages/maths/test/src/maths_test.dart b/packages/maths/test/src/maths_test.dart new file mode 100644 index 00000000..dceedab8 --- /dev/null +++ b/packages/maths/test/src/maths_test.dart @@ -0,0 +1,11 @@ +// ignore_for_file: prefer_const_constructors +import 'package:maths/maths.dart'; +import 'package:test/test.dart'; + +void main() { + group('Maths', () { + test('can be instantiated', () { + expect(Maths(), isNotNull); + }); + }); +} From 09464347ef2af67d9676e95a67f04096e83227d1 Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Mon, 7 Mar 2022 12:49:16 +0100 Subject: [PATCH 02/27] feat: added maths local dependency --- pubspec.lock | 7 +++++++ pubspec.yaml | 2 ++ 2 files changed, 9 insertions(+) diff --git a/pubspec.lock b/pubspec.lock index e218776d..db3b239a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -261,6 +261,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.3" + maths: + dependency: "direct main" + description: + path: "/Users/ruialonso/dev/flutter/googleIO22/pinball/packages/maths" + relative: false + source: path + version: "1.0.0+1" meta: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5d708073..7b9c9c19 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,6 +18,8 @@ dependencies: flutter_localizations: sdk: flutter intl: ^0.17.0 + maths: + path: packages/maths dev_dependencies: bloc_test: ^9.0.2 From b0e8a3be143c27f1d022a65e14858e8c99206331 Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Mon, 7 Mar 2022 12:49:48 +0100 Subject: [PATCH 03/27] feat: created path component to paint pathways for the ball --- lib/game/components/components.dart | 1 + lib/game/components/path.dart | 141 ++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 lib/game/components/path.dart diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index 95134ec2..81e8cad2 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -1,5 +1,6 @@ export 'anchor.dart'; export 'ball.dart'; +export 'path.dart'; export 'plunger.dart'; export 'score_points.dart'; export 'wall.dart'; diff --git a/lib/game/components/path.dart b/lib/game/components/path.dart new file mode 100644 index 00000000..0d7105d7 --- /dev/null +++ b/lib/game/components/path.dart @@ -0,0 +1,141 @@ +import 'package:flame/extensions.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/material.dart'; +import 'package:maths/maths.dart'; + +class Path extends BodyComponent { + Path._({ + Color? color, + required Vector2 position, + required List> paths, + }) : _position = position, + _paths = paths { + if (color != null) { + paint = Paint() + ..color = color + ..style = PaintingStyle.stroke; + } + } + + factory Path.straight({ + Color? color, + required Vector2 position, + required Vector2 start, + required Vector2 end, + required double pathWidth, + double rotation = 0, + bool onlyOneWall = false, + }) { + final paths = >[]; + final wall1 = [ + start.clone(), + end.clone(), + ]; + paths.add(wall1.map((e) => e..rotate(radians(rotation))).toList()); + + if (!onlyOneWall) { + final wall2 = [ + start + Vector2(pathWidth, 0), + end + Vector2(pathWidth, 0), + ]; + paths.add(wall2.map((e) => e..rotate(radians(rotation))).toList()); + } + + return Path._( + color: color, + position: position, + paths: paths, + ); + } + + factory Path.arc({ + Color? color, + required Vector2 position, + required double pathWidth, + required double radius, + required double angle, + double rotation = 0, + bool onlyOneWall = false, + }) { + final paths = >[]; + + final wall1 = calculateArc( + center: position, + radius: radius, + angle: angle, + offsetAngle: rotation, + ); + paths.add(wall1); + + if (!onlyOneWall) { + final minRadius = radius - pathWidth; + + final wall2 = calculateArc( + center: position, + radius: minRadius, + angle: angle, + offsetAngle: rotation, + ); + paths.add(wall2); + } + + return Path._( + color: color, + position: position, + paths: paths, + ); + } + + factory Path.bezierCurve({ + Color? color, + required Vector2 position, + required List controlPoints, + required double pathWidth, + double rotation = 0, + bool onlyOneWall = false, + }) { + final paths = >[]; + + final wall1 = calculateBezierCurve(controlPoints: controlPoints); + paths.add(wall1.map((e) => e..rotate(radians(rotation))).toList()); + + var wall2 = []; + if (!onlyOneWall) { + wall2 = calculateBezierCurve( + controlPoints: controlPoints + .map((e) => e + Vector2(pathWidth, -pathWidth)) + .toList(), + ); + paths.add(wall2.map((e) => e..rotate(radians(rotation))).toList()); + } + + return Path._( + color: color, + position: position, + paths: paths, + ); + } + + final Vector2 _position; + final List> _paths; + + @override + Body createBody() { + final bodyDef = BodyDef() + ..type = BodyType.static + ..position = _position; + + final body = world.createBody(bodyDef); + + for (final path in _paths) { + final chain = ChainShape() + ..createChain( + path.map((e) => gameRef.screenToWorld(e)).toList(), + ); + final fixtureDef = FixtureDef(chain); + body.createFixture(fixtureDef); + } + + return body; + } +} From 53f24643b7a8c94dc82b725ff2d17bb511f11e0d Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Mon, 7 Mar 2022 12:50:12 +0100 Subject: [PATCH 04/27] test: tests for Path component --- test/game/components/path_test.dart | 249 ++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 test/game/components/path_test.dart diff --git a/test/game/components/path_test.dart b/test/game/components/path_test.dart new file mode 100644 index 00000000..440070b6 --- /dev/null +++ b/test/game/components/path_test.dart @@ -0,0 +1,249 @@ +// ignore_for_file: cascade_invocations + +import 'dart:math'; +import 'dart:ui'; + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball/game/game.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(PinballGame.new); + + group('Path', () { + const pathWidth = 50.0; + + group('straight', () { + group('color', () { + flameTester.test( + 'has white color by default if not specified', + (game) async { + final path = Path.straight( + position: Vector2.zero(), + start: Vector2(10, 10), + end: Vector2(20, 20), + pathWidth: pathWidth, + ); + await game.ensureAdd(path); + expect(game.contains(path), isTrue); + expect(path.paint, isNotNull); + expect(path.paint.color, equals(Colors.white)); + }, + ); + flameTester.test( + 'has a color if set', + (game) async { + const defaultColor = Colors.blue; + + final path = Path.straight( + color: defaultColor, + position: Vector2.zero(), + start: Vector2(10, 10), + end: Vector2(20, 20), + pathWidth: pathWidth, + ); + await game.ensureAdd(path); + expect(game.contains(path), isTrue); + expect(path.paint, isNotNull); + expect(path.paint.color.value, equals(defaultColor.value)); + }, + ); + }); + + flameTester.test( + 'loads correctly', + (game) async { + final path = Path.straight( + position: Vector2.zero(), + start: Vector2(10, 10), + end: Vector2(20, 20), + pathWidth: pathWidth, + ); + await game.ensureAdd(path); + expect(game.contains(path), isTrue); + }, + ); + + group('body', () { + flameTester.test( + 'positions correctly', + (game) async { + final position = Vector2.all(10); + final path = Path.straight( + position: position, + start: Vector2(10, 10), + end: Vector2(20, 20), + pathWidth: pathWidth, + ); + await game.ensureAdd(path); + game.contains(path); + + expect(path.body.position, position); + }, + ); + + flameTester.test( + 'is static', + (game) async { + final path = Path.straight( + position: Vector2.zero(), + start: Vector2(10, 10), + end: Vector2(20, 20), + pathWidth: pathWidth, + ); + await game.ensureAdd(path); + + expect(path.body.bodyType, equals(BodyType.static)); + }, + ); + }); + + group('fixtures', () { + flameTester.test( + 'exists only one ChainShape if just one wall', + (game) async { + final path = Path.straight( + position: Vector2.zero(), + start: Vector2(10, 10), + end: Vector2(20, 20), + pathWidth: pathWidth, + onlyOneWall: true, + ); + await game.ensureAdd(path); + + expect(path.body.fixtures.length, 1); + final fixture = path.body.fixtures[0]; + expect(fixture, isA()); + expect(fixture.shape.shapeType, equals(ShapeType.chain)); + }, + ); + + flameTester.test( + 'exists two ChainShape if there is by default two walls', + (game) async { + final path = Path.straight( + position: Vector2.zero(), + start: Vector2(10, 10), + end: Vector2(20, 20), + pathWidth: pathWidth, + ); + await game.ensureAdd(path); + + expect(path.body.fixtures.length, 2); + for (var fixture in path.body.fixtures) { + expect(fixture, isA()); + expect(fixture.shape.shapeType, equals(ShapeType.chain)); + } + }, + ); + }); + }); + + group('arc', () { + flameTester.test( + 'loads correctly', + (game) async { + final path = Path.arc( + position: Vector2.zero(), + pathWidth: pathWidth, + radius: 100, + angle: 90, + ); + await game.ensureAdd(path); + expect(game.contains(path), isTrue); + }, + ); + + group('body', () { + flameTester.test( + 'positions correctly', + (game) async { + final position = Vector2.all(10); + final path = Path.arc( + position: position, + pathWidth: pathWidth, + radius: 100, + angle: 90, + ); + await game.ensureAdd(path); + game.contains(path); + + expect(path.body.position, position); + }, + ); + + flameTester.test( + 'is static', + (game) async { + final path = Path.arc( + position: Vector2.zero(), + pathWidth: pathWidth, + radius: 100, + angle: 90, + ); + await game.ensureAdd(path); + + expect(path.body.bodyType, equals(BodyType.static)); + }, + ); + }); + }); + + group('bezier curve', () { + final controlPoints = [ + Vector2(0, 0), + Vector2(50, 0), + Vector2(0, 50), + Vector2(50, 50), + ]; + + flameTester.test( + 'loads correctly', + (game) async { + final path = Path.bezierCurve( + position: Vector2.zero(), + controlPoints: controlPoints, + pathWidth: pathWidth, + ); + await game.ensureAdd(path); + expect(game.contains(path), isTrue); + }, + ); + + group('body', () { + flameTester.test( + 'positions correctly', + (game) async { + final position = Vector2.all(10); + final path = Path.bezierCurve( + position: position, + controlPoints: controlPoints, + pathWidth: pathWidth, + ); + await game.ensureAdd(path); + game.contains(path); + + expect(path.body.position, position); + }, + ); + + flameTester.test( + 'is static', + (game) async { + final path = Path.bezierCurve( + position: Vector2.zero(), + controlPoints: controlPoints, + pathWidth: pathWidth, + ); + await game.ensureAdd(path); + + expect(path.body.bodyType, equals(BodyType.static)); + }, + ); + }); + }); + }); +} From c693e9a10910c0dc8151077406da3c1d2ce9ca07 Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Mon, 7 Mar 2022 12:52:58 +0100 Subject: [PATCH 05/27] chore: analysis errors fixed on tests --- test/game/components/path_test.dart | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/game/components/path_test.dart b/test/game/components/path_test.dart index 440070b6..a97ba866 100644 --- a/test/game/components/path_test.dart +++ b/test/game/components/path_test.dart @@ -1,8 +1,4 @@ // ignore_for_file: cascade_invocations - -import 'dart:math'; -import 'dart:ui'; - import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter/material.dart'; @@ -133,7 +129,7 @@ void main() { await game.ensureAdd(path); expect(path.body.fixtures.length, 2); - for (var fixture in path.body.fixtures) { + for (final fixture in path.body.fixtures) { expect(fixture, isA()); expect(fixture.shape.shapeType, equals(ShapeType.chain)); } From aeb289fce66e5d68dcb33033d3600e9f674b0575 Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Mon, 7 Mar 2022 13:04:28 +0100 Subject: [PATCH 06/27] fix: changed paths area default color to transparent --- lib/game/components/path.dart | 8 +++----- test/game/components/path_test.dart | 9 ++++++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/game/components/path.dart b/lib/game/components/path.dart index 0d7105d7..66dad004 100644 --- a/lib/game/components/path.dart +++ b/lib/game/components/path.dart @@ -10,11 +10,9 @@ class Path extends BodyComponent { required List> paths, }) : _position = position, _paths = paths { - if (color != null) { - paint = Paint() - ..color = color - ..style = PaintingStyle.stroke; - } + paint = Paint() + ..color = color ?? const Color.fromARGB(0, 0, 0, 0) + ..style = PaintingStyle.stroke; } factory Path.straight({ diff --git a/test/game/components/path_test.dart b/test/game/components/path_test.dart index a97ba866..9564c21c 100644 --- a/test/game/components/path_test.dart +++ b/test/game/components/path_test.dart @@ -1,4 +1,4 @@ -// ignore_for_file: cascade_invocations +// ignore_for_file: cascade_invocations, prefer_const_constructors import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter/material.dart'; @@ -15,7 +15,7 @@ void main() { group('straight', () { group('color', () { flameTester.test( - 'has white color by default if not specified', + 'has transparent color by default if not specified', (game) async { final path = Path.straight( position: Vector2.zero(), @@ -26,7 +26,10 @@ void main() { await game.ensureAdd(path); expect(game.contains(path), isTrue); expect(path.paint, isNotNull); - expect(path.paint.color, equals(Colors.white)); + expect( + path.paint.color, + equals(Color.fromARGB(0, 0, 0, 0)), + ); }, ); flameTester.test( From 9ccf85c2ef3bc060f4ac998c361a70807dabaace Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Mon, 7 Mar 2022 13:27:13 +0100 Subject: [PATCH 07/27] doc: documented Path component --- lib/game/components/path.dart | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/game/components/path.dart b/lib/game/components/path.dart index 66dad004..69b16b20 100644 --- a/lib/game/components/path.dart +++ b/lib/game/components/path.dart @@ -3,6 +3,10 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:maths/maths.dart'; +/// {@template path} +/// [Path] creates different shapes that sets the pathways that ball can follow +/// or collide to like walls. +/// {@endtemplate} class Path extends BodyComponent { Path._({ Color? color, @@ -15,6 +19,12 @@ class Path extends BodyComponent { ..style = PaintingStyle.stroke; } + /// {@macro path} + /// [Path.straight] creates a straight path for the ball given a [position] + /// for the body, between a [start] and [end] points. + /// It creates two [ChainShape] separated by a [pathWidth]. If [onlyOneWall] + /// is true, just one [ChainShape] is created (like a wall instead of a path) + /// The path could be rotated by [rotation] in degrees. factory Path.straight({ Color? color, required Vector2 position, @@ -46,6 +56,15 @@ class Path extends BodyComponent { ); } + /// {@macro path} + /// [Path.straight] creates an arc path for the ball given a [position] + /// for the body, a [radius] for the circumference and an [angle] to specify + /// the size of the semi circumference. + /// It creates two [ChainShape] separated by a [pathWidth], like a circular + /// crown. The specified [radius] is for the outer arc, the inner one will + /// have a radius of radius-pathWidth. + /// If [onlyOneWall] is true, just one [ChainShape] is created. + /// The path could be rotated by [rotation] in degrees. factory Path.arc({ Color? color, required Vector2 position, @@ -84,6 +103,14 @@ class Path extends BodyComponent { ); } + /// {@macro path} + /// [Path.straight] creates a bezier curve path for the ball given a + /// [position] for the body, with control point specified by [controlPoints]. + /// First and last points set the beginning and end of the curve, all the + /// inner points between them set the bezier curve final shape. + /// It creates two [ChainShape] separated by a [pathWidth]. If [onlyOneWall] + /// is true, just one [ChainShape] is created (like a wall instead of a path) + /// The path could be rotated by [rotation] in degrees. factory Path.bezierCurve({ Color? color, required Vector2 position, From 8f6bad7a6f7cfc73ba1cbc5bba7984cff4292903 Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Mon, 7 Mar 2022 14:12:33 +0100 Subject: [PATCH 08/27] test: tests for maths methods --- packages/maths/lib/src/maths.dart | 18 +++-- packages/maths/pubspec.yaml | 10 ++- packages/maths/test/src/maths_test.dart | 87 +++++++++++++++++++++++-- 3 files changed, 101 insertions(+), 14 deletions(-) diff --git a/packages/maths/lib/src/maths.dart b/packages/maths/lib/src/maths.dart index ea447db5..bdb31d43 100644 --- a/packages/maths/lib/src/maths.dart +++ b/packages/maths/lib/src/maths.dart @@ -3,6 +3,7 @@ import 'package:flame/extensions.dart'; /// Method to calculate all points (with a required precision amount of them) /// of a circumference based on angle, offsetAngle and radius +/// https://en.wikipedia.org/wiki/Trigonometric_functions List calculateArc({ required Vector2 center, required double radius, @@ -47,9 +48,9 @@ List calculateBezierCurve({ final point = controlPoints[i]; xCoord += - _binomial(n, i) * math.pow(1 - t, n - i) * math.pow(t, i) * point.x; + binomial(n, i) * math.pow(1 - t, n - i) * math.pow(t, i) * point.x; yCoord += - _binomial(n, i) * math.pow(1 - t, n - i) * math.pow(t, i) * point.y; + binomial(n, i) * math.pow(1 - t, n - i) * math.pow(t, i) * point.y; } points.add(Vector2(xCoord, yCoord)); @@ -61,21 +62,24 @@ List calculateBezierCurve({ /// Method to calculate the binomial coefficient of 'n' and 'k' /// https://en.wikipedia.org/wiki/Binomial_coefficient -num _binomial(num n, num k) { +num binomial(num n, num k) { assert(0 <= k && k <= n, 'Range 0<=k<=n'); if (k == 0 || n == k) { return 1; } else { - return _factorial(n) / (_factorial(k) * _factorial(n - k)); + return factorial(n) / (factorial(k) * factorial(n - k)); } } /// Method to calculate the factorial of some number 'n' /// https://en.wikipedia.org/wiki/Factorial -num _factorial(num n) { - if (n == 1) { +num factorial(num n) { + assert(0 <= n, 'Non negative n'); + if (n == 0) { + return 1; + } else if (n == 1) { return 1; } else { - return n * _factorial(n - 1); + return n * factorial(n - 1); } } diff --git a/packages/maths/pubspec.yaml b/packages/maths/pubspec.yaml index 3b4b863c..b037f4a4 100644 --- a/packages/maths/pubspec.yaml +++ b/packages/maths/pubspec.yaml @@ -6,10 +6,14 @@ publish_to: none environment: sdk: ">=2.16.0 <3.0.0" +dependencies: + flame: ^1.0.0 + flutter: + sdk: flutter + dev_dependencies: - coverage: ^1.1.0 + flutter_test: + sdk: flutter mocktail: ^0.2.0 test: ^1.19.2 very_good_analysis: ^2.4.0 -dependencies: - flame: ^1.0.0 diff --git a/packages/maths/test/src/maths_test.dart b/packages/maths/test/src/maths_test.dart index dceedab8..8faa57cf 100644 --- a/packages/maths/test/src/maths_test.dart +++ b/packages/maths/test/src/maths_test.dart @@ -1,11 +1,90 @@ -// ignore_for_file: prefer_const_constructors +// ignore_for_file: prefer_const_constructors, cascade_invocations +import 'package:flutter_test/flutter_test.dart'; import 'package:maths/maths.dart'; -import 'package:test/test.dart'; + +class Binomial { + Binomial({required this.n, required this.k}); + + final num n; + final num k; +} void main() { group('Maths', () { - test('can be instantiated', () { - expect(Maths(), isNotNull); + group('calculateArc', () {}); + group('calculateBezierCurve', () {}); + + group('binomial', () { + test('fails if k is negative', () { + expect(() => binomial(1, -1), throwsAssertionError); + }); + test('fails if n is negative', () { + expect(() => binomial(-1, 1), throwsAssertionError); + }); + test('fails if n < k', () { + expect(() => binomial(1, 2), throwsAssertionError); + }); + test('for a specific input gives a correct value', () { + final binomialInputsToExpected = { + Binomial(n: 0, k: 0): 1, + Binomial(n: 1, k: 0): 1, + Binomial(n: 1, k: 1): 1, + Binomial(n: 2, k: 0): 1, + Binomial(n: 2, k: 1): 2, + Binomial(n: 2, k: 2): 1, + Binomial(n: 3, k: 0): 1, + Binomial(n: 3, k: 1): 3, + Binomial(n: 3, k: 2): 3, + Binomial(n: 3, k: 3): 1, + Binomial(n: 4, k: 0): 1, + Binomial(n: 4, k: 1): 4, + Binomial(n: 4, k: 2): 6, + Binomial(n: 4, k: 3): 4, + Binomial(n: 4, k: 4): 1, + Binomial(n: 5, k: 0): 1, + Binomial(n: 5, k: 1): 5, + Binomial(n: 5, k: 2): 10, + Binomial(n: 5, k: 3): 10, + Binomial(n: 5, k: 4): 5, + Binomial(n: 5, k: 5): 1, + Binomial(n: 6, k: 0): 1, + Binomial(n: 6, k: 1): 6, + Binomial(n: 6, k: 2): 15, + Binomial(n: 6, k: 3): 20, + Binomial(n: 6, k: 4): 15, + Binomial(n: 6, k: 5): 6, + Binomial(n: 6, k: 6): 1, + }; + binomialInputsToExpected.forEach((input, value) { + expect(binomial(input.n, input.k), value); + }); + }); + }); + group('factorial', () { + test('fails if negative number', () { + expect(() => factorial(-1), throwsAssertionError); + }); + test('for a specific input gives a correct value', () { + final factorialInputsToExpected = { + 0: 1, + 1: 1, + 2: 2, + 3: 6, + 4: 24, + 5: 120, + 6: 720, + 7: 5040, + 8: 40320, + 9: 362880, + 10: 3628800, + 11: 39916800, + 12: 479001600, + 13: 6227020800, + }; + factorialInputsToExpected.forEach((input, expected) { + expect(factorial(input), expected); + }); + }); }); }); } From 71033266caa9817a1adc6df50279e494966d9093 Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Mon, 7 Mar 2022 14:25:21 +0100 Subject: [PATCH 09/27] fix: fixed precision value for calculateArc method --- packages/maths/lib/src/maths.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/maths/lib/src/maths.dart b/packages/maths/lib/src/maths.dart index bdb31d43..e37f8132 100644 --- a/packages/maths/lib/src/maths.dart +++ b/packages/maths/lib/src/maths.dart @@ -11,11 +11,11 @@ List calculateArc({ double offsetAngle = 0, int precision = 100, }) { - final stepAngle = radians(angle / precision); + final stepAngle = radians(angle / (precision - 1)); final stepOffset = radians(offsetAngle); final points = []; - for (var i = 0; i <= precision; i++) { + for (var i = 0; i < precision; i++) { final xCoord = center.x + radius * math.cos((stepAngle * i) + stepOffset); final yCoord = center.y - radius * math.sin((stepAngle * i) + stepOffset); From 53a22f1f1de320eb12ee014303f40a2cd4fc65b6 Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Mon, 7 Mar 2022 14:32:36 +0100 Subject: [PATCH 10/27] fix: added assert to maths --- packages/maths/lib/src/maths.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/maths/lib/src/maths.dart b/packages/maths/lib/src/maths.dart index e37f8132..d0912418 100644 --- a/packages/maths/lib/src/maths.dart +++ b/packages/maths/lib/src/maths.dart @@ -33,6 +33,7 @@ List calculateBezierCurve({ required List controlPoints, double step = 0.001, }) { + assert(0 <= step && step <= 1, 'Range 0<=step<=1'); assert( controlPoints.length >= 2, 'At least 2 control points to create a bezier curve', From ad8499d173783b8c7883b201ac7801cf7c336de2 Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Mon, 7 Mar 2022 14:33:01 +0100 Subject: [PATCH 11/27] test: coverage for maths methods --- packages/maths/test/src/maths_test.dart | 67 ++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/packages/maths/test/src/maths_test.dart b/packages/maths/test/src/maths_test.dart index 8faa57cf..2e164989 100644 --- a/packages/maths/test/src/maths_test.dart +++ b/packages/maths/test/src/maths_test.dart @@ -1,4 +1,5 @@ // ignore_for_file: prefer_const_constructors, cascade_invocations +import 'package:flame/extensions.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:maths/maths.dart'; @@ -11,8 +12,70 @@ class Binomial { void main() { group('Maths', () { - group('calculateArc', () {}); - group('calculateBezierCurve', () {}); + group('calculateArc', () { + test('it returns by default 100 points as indicated by precision', () { + final points = calculateArc( + center: Vector2.zero(), + radius: 100, + angle: 90, + ); + expect(points.length, 100); + }); + test('it returns as many points as indicated by precision', () { + final points = calculateArc( + center: Vector2.zero(), + radius: 100, + angle: 90, + precision: 50, + ); + expect(points.length, 50); + }); + }); + group('calculateBezierCurve', () { + test('fails if step not in range', () { + expect( + () => calculateBezierCurve( + controlPoints: [ + Vector2(0, 0), + Vector2(10, 10), + ], + step: 2, + ), + throwsAssertionError, + ); + }); + test('fails if not enough control points', () { + expect( + () => calculateBezierCurve(controlPoints: [Vector2.zero()]), + throwsAssertionError, + ); + expect( + () => calculateBezierCurve(controlPoints: []), + throwsAssertionError, + ); + }); + + test('it returns by default 1000 points as indicated by step', () { + final points = calculateBezierCurve( + controlPoints: [ + Vector2(0, 0), + Vector2(10, 10), + ], + ); + expect(points.length, 1000); + }); + + test('it returns as many points as indicated by step', () { + final points = calculateBezierCurve( + controlPoints: [ + Vector2(0, 0), + Vector2(10, 10), + ], + step: 0.01, + ); + expect(points.length, 100); + }); + }); group('binomial', () { test('fails if k is negative', () { From d6f7bb853d3b7a0c31d4f4fd509554805c199614 Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Mon, 7 Mar 2022 16:19:18 +0100 Subject: [PATCH 12/27] test: names for tests --- test/game/components/path_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/game/components/path_test.dart b/test/game/components/path_test.dart index 9564c21c..73a7bcfb 100644 --- a/test/game/components/path_test.dart +++ b/test/game/components/path_test.dart @@ -109,7 +109,7 @@ void main() { start: Vector2(10, 10), end: Vector2(20, 20), pathWidth: pathWidth, - onlyOneWall: true, + singleWall: true, ); await game.ensureAdd(path); From 8901bcec70cebd819fc11e0d6165108c402b218b Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Mon, 7 Mar 2022 16:20:01 +0100 Subject: [PATCH 13/27] chore: var names and lambda method for identity element --- lib/game/components/path.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/game/components/path.dart b/lib/game/components/path.dart index 69b16b20..d5263024 100644 --- a/lib/game/components/path.dart +++ b/lib/game/components/path.dart @@ -32,7 +32,7 @@ class Path extends BodyComponent { required Vector2 end, required double pathWidth, double rotation = 0, - bool onlyOneWall = false, + bool singleWall = false, }) { final paths = >[]; final wall1 = [ @@ -41,7 +41,7 @@ class Path extends BodyComponent { ]; paths.add(wall1.map((e) => e..rotate(radians(rotation))).toList()); - if (!onlyOneWall) { + if (!singleWall) { final wall2 = [ start + Vector2(pathWidth, 0), end + Vector2(pathWidth, 0), @@ -72,7 +72,7 @@ class Path extends BodyComponent { required double radius, required double angle, double rotation = 0, - bool onlyOneWall = false, + bool singleWall = false, }) { final paths = >[]; @@ -84,7 +84,7 @@ class Path extends BodyComponent { ); paths.add(wall1); - if (!onlyOneWall) { + if (!singleWall) { final minRadius = radius - pathWidth; final wall2 = calculateArc( @@ -117,7 +117,7 @@ class Path extends BodyComponent { required List controlPoints, required double pathWidth, double rotation = 0, - bool onlyOneWall = false, + bool singleWall = false, }) { final paths = >[]; @@ -125,7 +125,7 @@ class Path extends BodyComponent { paths.add(wall1.map((e) => e..rotate(radians(rotation))).toList()); var wall2 = []; - if (!onlyOneWall) { + if (!singleWall) { wall2 = calculateBezierCurve( controlPoints: controlPoints .map((e) => e + Vector2(pathWidth, -pathWidth)) @@ -155,7 +155,7 @@ class Path extends BodyComponent { for (final path in _paths) { final chain = ChainShape() ..createChain( - path.map((e) => gameRef.screenToWorld(e)).toList(), + path.map(gameRef.screenToWorld).toList(), ); final fixtureDef = FixtureDef(chain); body.createFixture(fixtureDef); From 20c81533638e1e8b2a833ca079fd6ca52fdd4bfa Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Mon, 7 Mar 2022 16:23:21 +0100 Subject: [PATCH 14/27] chore: var and test names not saved --- lib/game/components/path.dart | 6 +++--- packages/maths/test/src/maths_test.dart | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/game/components/path.dart b/lib/game/components/path.dart index d5263024..6f5c786e 100644 --- a/lib/game/components/path.dart +++ b/lib/game/components/path.dart @@ -22,7 +22,7 @@ class Path extends BodyComponent { /// {@macro path} /// [Path.straight] creates a straight path for the ball given a [position] /// for the body, between a [start] and [end] points. - /// It creates two [ChainShape] separated by a [pathWidth]. If [onlyOneWall] + /// It creates two [ChainShape] separated by a [pathWidth]. If [singleWall] /// is true, just one [ChainShape] is created (like a wall instead of a path) /// The path could be rotated by [rotation] in degrees. factory Path.straight({ @@ -63,7 +63,7 @@ class Path extends BodyComponent { /// It creates two [ChainShape] separated by a [pathWidth], like a circular /// crown. The specified [radius] is for the outer arc, the inner one will /// have a radius of radius-pathWidth. - /// If [onlyOneWall] is true, just one [ChainShape] is created. + /// If [singleWall] is true, just one [ChainShape] is created. /// The path could be rotated by [rotation] in degrees. factory Path.arc({ Color? color, @@ -108,7 +108,7 @@ class Path extends BodyComponent { /// [position] for the body, with control point specified by [controlPoints]. /// First and last points set the beginning and end of the curve, all the /// inner points between them set the bezier curve final shape. - /// It creates two [ChainShape] separated by a [pathWidth]. If [onlyOneWall] + /// It creates two [ChainShape] separated by a [pathWidth]. If [singleWall] /// is true, just one [ChainShape] is created (like a wall instead of a path) /// The path could be rotated by [rotation] in degrees. factory Path.bezierCurve({ diff --git a/packages/maths/test/src/maths_test.dart b/packages/maths/test/src/maths_test.dart index 2e164989..eaaf9367 100644 --- a/packages/maths/test/src/maths_test.dart +++ b/packages/maths/test/src/maths_test.dart @@ -13,7 +13,7 @@ class Binomial { void main() { group('Maths', () { group('calculateArc', () { - test('it returns by default 100 points as indicated by precision', () { + test('returns by default 100 points as indicated by precision', () { final points = calculateArc( center: Vector2.zero(), radius: 100, @@ -21,7 +21,7 @@ void main() { ); expect(points.length, 100); }); - test('it returns as many points as indicated by precision', () { + test('returns as many points as indicated by precision', () { final points = calculateArc( center: Vector2.zero(), radius: 100, @@ -55,7 +55,7 @@ void main() { ); }); - test('it returns by default 1000 points as indicated by step', () { + test('returns by default 1000 points as indicated by step', () { final points = calculateBezierCurve( controlPoints: [ Vector2(0, 0), @@ -65,7 +65,7 @@ void main() { expect(points.length, 1000); }); - test('it returns as many points as indicated by step', () { + test('returns as many points as indicated by step', () { final points = calculateBezierCurve( controlPoints: [ Vector2(0, 0), From 7e43e0042e748da82f85208084eb238693ebe40a Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Mon, 7 Mar 2022 16:40:01 +0100 Subject: [PATCH 15/27] refactor: changed name of Path to Pathway --- lib/game/components/components.dart | 2 +- .../components/{path.dart => pathway.dart} | 70 +++++----- test/game/components/path_test.dart | 126 +++++++++--------- 3 files changed, 100 insertions(+), 98 deletions(-) rename lib/game/components/{path.dart => pathway.dart} (66%) diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index 81e8cad2..a5989fd5 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -1,6 +1,6 @@ export 'anchor.dart'; export 'ball.dart'; -export 'path.dart'; +export 'pathway.dart'; export 'plunger.dart'; export 'score_points.dart'; export 'wall.dart'; diff --git a/lib/game/components/path.dart b/lib/game/components/pathway.dart similarity index 66% rename from lib/game/components/path.dart rename to lib/game/components/pathway.dart index 6f5c786e..75656e9e 100644 --- a/lib/game/components/path.dart +++ b/lib/game/components/pathway.dart @@ -3,12 +3,12 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:maths/maths.dart'; -/// {@template path} -/// [Path] creates different shapes that sets the pathways that ball can follow -/// or collide to like walls. +/// {@template pathway} +/// [Pathway] creates different shapes that sets the pathwayways that ball +/// can follow or collide to like walls. /// {@endtemplate} -class Path extends BodyComponent { - Path._({ +class Pathway extends BodyComponent { + Pathway._({ Color? color, required Vector2 position, required List> paths, @@ -19,18 +19,19 @@ class Path extends BodyComponent { ..style = PaintingStyle.stroke; } - /// {@macro path} - /// [Path.straight] creates a straight path for the ball given a [position] - /// for the body, between a [start] and [end] points. - /// It creates two [ChainShape] separated by a [pathWidth]. If [singleWall] - /// is true, just one [ChainShape] is created (like a wall instead of a path) - /// The path could be rotated by [rotation] in degrees. - factory Path.straight({ + /// {@macro pathway} + /// [Pathway.straight] creates a straight pathway for the ball given + /// a [position] for the body, between a [start] and [end] points. + /// It creates two [ChainShape] separated by a [pathwayWidth]. If [singleWall] + /// is true, just one [ChainShape] is created + /// (like a wall instead of a pathway) + /// The pathway could be rotated by [rotation] in degrees. + factory Pathway.straight({ Color? color, required Vector2 position, required Vector2 start, required Vector2 end, - required double pathWidth, + required double pathwayWidth, double rotation = 0, bool singleWall = false, }) { @@ -43,32 +44,32 @@ class Path extends BodyComponent { if (!singleWall) { final wall2 = [ - start + Vector2(pathWidth, 0), - end + Vector2(pathWidth, 0), + start + Vector2(pathwayWidth, 0), + end + Vector2(pathwayWidth, 0), ]; paths.add(wall2.map((e) => e..rotate(radians(rotation))).toList()); } - return Path._( + return Pathway._( color: color, position: position, paths: paths, ); } - /// {@macro path} - /// [Path.straight] creates an arc path for the ball given a [position] + /// {@macro pathway} + /// [Pathway.straight] creates an arc pathway for the ball given a [position] /// for the body, a [radius] for the circumference and an [angle] to specify /// the size of the semi circumference. - /// It creates two [ChainShape] separated by a [pathWidth], like a circular + /// It creates two [ChainShape] separated by a [pathwayWidth], like a circular /// crown. The specified [radius] is for the outer arc, the inner one will - /// have a radius of radius-pathWidth. + /// have a radius of radius-pathwayWidth. /// If [singleWall] is true, just one [ChainShape] is created. - /// The path could be rotated by [rotation] in degrees. - factory Path.arc({ + /// The pathway could be rotated by [rotation] in degrees. + factory Pathway.arc({ Color? color, required Vector2 position, - required double pathWidth, + required double pathwayWidth, required double radius, required double angle, double rotation = 0, @@ -85,7 +86,7 @@ class Path extends BodyComponent { paths.add(wall1); if (!singleWall) { - final minRadius = radius - pathWidth; + final minRadius = radius - pathwayWidth; final wall2 = calculateArc( center: position, @@ -96,26 +97,27 @@ class Path extends BodyComponent { paths.add(wall2); } - return Path._( + return Pathway._( color: color, position: position, paths: paths, ); } - /// {@macro path} - /// [Path.straight] creates a bezier curve path for the ball given a + /// {@macro pathway} + /// [Pathway.straight] creates a bezier curve pathway for the ball given a /// [position] for the body, with control point specified by [controlPoints]. /// First and last points set the beginning and end of the curve, all the /// inner points between them set the bezier curve final shape. - /// It creates two [ChainShape] separated by a [pathWidth]. If [singleWall] - /// is true, just one [ChainShape] is created (like a wall instead of a path) - /// The path could be rotated by [rotation] in degrees. - factory Path.bezierCurve({ + /// It creates two [ChainShape] separated by a [pathwayWidth]. If [singleWall] + /// is true, just one [ChainShape] is created + /// (like a wall instead of a pathway) + /// The pathway could be rotated by [rotation] in degrees. + factory Pathway.bezierCurve({ Color? color, required Vector2 position, required List controlPoints, - required double pathWidth, + required double pathwayWidth, double rotation = 0, bool singleWall = false, }) { @@ -128,13 +130,13 @@ class Path extends BodyComponent { if (!singleWall) { wall2 = calculateBezierCurve( controlPoints: controlPoints - .map((e) => e + Vector2(pathWidth, -pathWidth)) + .map((e) => e + Vector2(pathwayWidth, -pathwayWidth)) .toList(), ); paths.add(wall2.map((e) => e..rotate(radians(rotation))).toList()); } - return Path._( + return Pathway._( color: color, position: position, paths: paths, diff --git a/test/game/components/path_test.dart b/test/game/components/path_test.dart index 73a7bcfb..e1c10455 100644 --- a/test/game/components/path_test.dart +++ b/test/game/components/path_test.dart @@ -9,25 +9,25 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); final flameTester = FlameTester(PinballGame.new); - group('Path', () { - const pathWidth = 50.0; + group('Pathway', () { + const pathwayWidth = 50.0; group('straight', () { group('color', () { flameTester.test( 'has transparent color by default if not specified', (game) async { - final path = Path.straight( + final pathway = Pathway.straight( position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathWidth: pathWidth, + pathwayWidth: pathwayWidth, ); - await game.ensureAdd(path); - expect(game.contains(path), isTrue); - expect(path.paint, isNotNull); + await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); + expect(pathway.paint, isNotNull); expect( - path.paint.color, + pathway.paint.color, equals(Color.fromARGB(0, 0, 0, 0)), ); }, @@ -37,17 +37,17 @@ void main() { (game) async { const defaultColor = Colors.blue; - final path = Path.straight( + final pathway = Pathway.straight( color: defaultColor, position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathWidth: pathWidth, + pathwayWidth: pathwayWidth, ); - await game.ensureAdd(path); - expect(game.contains(path), isTrue); - expect(path.paint, isNotNull); - expect(path.paint.color.value, equals(defaultColor.value)); + await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); + expect(pathway.paint, isNotNull); + expect(pathway.paint.color.value, equals(defaultColor.value)); }, ); }); @@ -55,14 +55,14 @@ void main() { flameTester.test( 'loads correctly', (game) async { - final path = Path.straight( + final pathway = Pathway.straight( position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathWidth: pathWidth, + pathwayWidth: pathwayWidth, ); - await game.ensureAdd(path); - expect(game.contains(path), isTrue); + await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); }, ); @@ -71,31 +71,31 @@ void main() { 'positions correctly', (game) async { final position = Vector2.all(10); - final path = Path.straight( + final pathway = Pathway.straight( position: position, start: Vector2(10, 10), end: Vector2(20, 20), - pathWidth: pathWidth, + pathwayWidth: pathwayWidth, ); - await game.ensureAdd(path); - game.contains(path); + await game.ensureAdd(pathway); + game.contains(pathway); - expect(path.body.position, position); + expect(pathway.body.position, position); }, ); flameTester.test( 'is static', (game) async { - final path = Path.straight( + final pathway = Pathway.straight( position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathWidth: pathWidth, + pathwayWidth: pathwayWidth, ); - await game.ensureAdd(path); + await game.ensureAdd(pathway); - expect(path.body.bodyType, equals(BodyType.static)); + expect(pathway.body.bodyType, equals(BodyType.static)); }, ); }); @@ -104,17 +104,17 @@ void main() { flameTester.test( 'exists only one ChainShape if just one wall', (game) async { - final path = Path.straight( + final pathway = Pathway.straight( position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathWidth: pathWidth, + pathwayWidth: pathwayWidth, singleWall: true, ); - await game.ensureAdd(path); + await game.ensureAdd(pathway); - expect(path.body.fixtures.length, 1); - final fixture = path.body.fixtures[0]; + expect(pathway.body.fixtures.length, 1); + final fixture = pathway.body.fixtures[0]; expect(fixture, isA()); expect(fixture.shape.shapeType, equals(ShapeType.chain)); }, @@ -123,16 +123,16 @@ void main() { flameTester.test( 'exists two ChainShape if there is by default two walls', (game) async { - final path = Path.straight( + final pathway = Pathway.straight( position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathWidth: pathWidth, + pathwayWidth: pathwayWidth, ); - await game.ensureAdd(path); + await game.ensureAdd(pathway); - expect(path.body.fixtures.length, 2); - for (final fixture in path.body.fixtures) { + expect(pathway.body.fixtures.length, 2); + for (final fixture in pathway.body.fixtures) { expect(fixture, isA()); expect(fixture.shape.shapeType, equals(ShapeType.chain)); } @@ -145,14 +145,14 @@ void main() { flameTester.test( 'loads correctly', (game) async { - final path = Path.arc( + final pathway = Pathway.arc( position: Vector2.zero(), - pathWidth: pathWidth, + pathwayWidth: pathwayWidth, radius: 100, angle: 90, ); - await game.ensureAdd(path); - expect(game.contains(path), isTrue); + await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); }, ); @@ -161,31 +161,31 @@ void main() { 'positions correctly', (game) async { final position = Vector2.all(10); - final path = Path.arc( + final pathway = Pathway.arc( position: position, - pathWidth: pathWidth, + pathwayWidth: pathwayWidth, radius: 100, angle: 90, ); - await game.ensureAdd(path); - game.contains(path); + await game.ensureAdd(pathway); + game.contains(pathway); - expect(path.body.position, position); + expect(pathway.body.position, position); }, ); flameTester.test( 'is static', (game) async { - final path = Path.arc( + final pathway = Pathway.arc( position: Vector2.zero(), - pathWidth: pathWidth, + pathwayWidth: pathwayWidth, radius: 100, angle: 90, ); - await game.ensureAdd(path); + await game.ensureAdd(pathway); - expect(path.body.bodyType, equals(BodyType.static)); + expect(pathway.body.bodyType, equals(BodyType.static)); }, ); }); @@ -202,13 +202,13 @@ void main() { flameTester.test( 'loads correctly', (game) async { - final path = Path.bezierCurve( + final pathway = Pathway.bezierCurve( position: Vector2.zero(), controlPoints: controlPoints, - pathWidth: pathWidth, + pathwayWidth: pathwayWidth, ); - await game.ensureAdd(path); - expect(game.contains(path), isTrue); + await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); }, ); @@ -217,29 +217,29 @@ void main() { 'positions correctly', (game) async { final position = Vector2.all(10); - final path = Path.bezierCurve( + final pathway = Pathway.bezierCurve( position: position, controlPoints: controlPoints, - pathWidth: pathWidth, + pathwayWidth: pathwayWidth, ); - await game.ensureAdd(path); - game.contains(path); + await game.ensureAdd(pathway); + game.contains(pathway); - expect(path.body.position, position); + expect(pathway.body.position, position); }, ); flameTester.test( 'is static', (game) async { - final path = Path.bezierCurve( + final pathway = Pathway.bezierCurve( position: Vector2.zero(), controlPoints: controlPoints, - pathWidth: pathWidth, + pathwayWidth: pathwayWidth, ); - await game.ensureAdd(path); + await game.ensureAdd(pathway); - expect(path.body.bodyType, equals(BodyType.static)); + expect(pathway.body.bodyType, equals(BodyType.static)); }, ); }); From 631688f821998de9aa63d1f794dc06a8264e71dd Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Tue, 8 Mar 2022 11:26:57 +0100 Subject: [PATCH 16/27] refactor: path changed to pathway, as well as tests --- test/game/components/path_test.dart | 248 ---------------------------- 1 file changed, 248 deletions(-) delete mode 100644 test/game/components/path_test.dart diff --git a/test/game/components/path_test.dart b/test/game/components/path_test.dart deleted file mode 100644 index e1c10455..00000000 --- a/test/game/components/path_test.dart +++ /dev/null @@ -1,248 +0,0 @@ -// ignore_for_file: cascade_invocations, prefer_const_constructors -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:flame_test/flame_test.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball/game/game.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(PinballGame.new); - - group('Pathway', () { - const pathwayWidth = 50.0; - - group('straight', () { - group('color', () { - flameTester.test( - 'has transparent color by default if not specified', - (game) async { - final pathway = Pathway.straight( - position: Vector2.zero(), - start: Vector2(10, 10), - end: Vector2(20, 20), - pathwayWidth: pathwayWidth, - ); - await game.ensureAdd(pathway); - expect(game.contains(pathway), isTrue); - expect(pathway.paint, isNotNull); - expect( - pathway.paint.color, - equals(Color.fromARGB(0, 0, 0, 0)), - ); - }, - ); - flameTester.test( - 'has a color if set', - (game) async { - const defaultColor = Colors.blue; - - final pathway = Pathway.straight( - color: defaultColor, - position: Vector2.zero(), - start: Vector2(10, 10), - end: Vector2(20, 20), - pathwayWidth: pathwayWidth, - ); - await game.ensureAdd(pathway); - expect(game.contains(pathway), isTrue); - expect(pathway.paint, isNotNull); - expect(pathway.paint.color.value, equals(defaultColor.value)); - }, - ); - }); - - flameTester.test( - 'loads correctly', - (game) async { - final pathway = Pathway.straight( - position: Vector2.zero(), - start: Vector2(10, 10), - end: Vector2(20, 20), - pathwayWidth: pathwayWidth, - ); - await game.ensureAdd(pathway); - expect(game.contains(pathway), isTrue); - }, - ); - - group('body', () { - flameTester.test( - 'positions correctly', - (game) async { - final position = Vector2.all(10); - final pathway = Pathway.straight( - position: position, - start: Vector2(10, 10), - end: Vector2(20, 20), - pathwayWidth: pathwayWidth, - ); - await game.ensureAdd(pathway); - game.contains(pathway); - - expect(pathway.body.position, position); - }, - ); - - flameTester.test( - 'is static', - (game) async { - final pathway = Pathway.straight( - position: Vector2.zero(), - start: Vector2(10, 10), - end: Vector2(20, 20), - pathwayWidth: pathwayWidth, - ); - await game.ensureAdd(pathway); - - expect(pathway.body.bodyType, equals(BodyType.static)); - }, - ); - }); - - group('fixtures', () { - flameTester.test( - 'exists only one ChainShape if just one wall', - (game) async { - final pathway = Pathway.straight( - position: Vector2.zero(), - start: Vector2(10, 10), - end: Vector2(20, 20), - pathwayWidth: pathwayWidth, - singleWall: true, - ); - await game.ensureAdd(pathway); - - expect(pathway.body.fixtures.length, 1); - final fixture = pathway.body.fixtures[0]; - expect(fixture, isA()); - expect(fixture.shape.shapeType, equals(ShapeType.chain)); - }, - ); - - flameTester.test( - 'exists two ChainShape if there is by default two walls', - (game) async { - final pathway = Pathway.straight( - position: Vector2.zero(), - start: Vector2(10, 10), - end: Vector2(20, 20), - pathwayWidth: pathwayWidth, - ); - await game.ensureAdd(pathway); - - expect(pathway.body.fixtures.length, 2); - for (final fixture in pathway.body.fixtures) { - expect(fixture, isA()); - expect(fixture.shape.shapeType, equals(ShapeType.chain)); - } - }, - ); - }); - }); - - group('arc', () { - flameTester.test( - 'loads correctly', - (game) async { - final pathway = Pathway.arc( - position: Vector2.zero(), - pathwayWidth: pathwayWidth, - radius: 100, - angle: 90, - ); - await game.ensureAdd(pathway); - expect(game.contains(pathway), isTrue); - }, - ); - - group('body', () { - flameTester.test( - 'positions correctly', - (game) async { - final position = Vector2.all(10); - final pathway = Pathway.arc( - position: position, - pathwayWidth: pathwayWidth, - radius: 100, - angle: 90, - ); - await game.ensureAdd(pathway); - game.contains(pathway); - - expect(pathway.body.position, position); - }, - ); - - flameTester.test( - 'is static', - (game) async { - final pathway = Pathway.arc( - position: Vector2.zero(), - pathwayWidth: pathwayWidth, - radius: 100, - angle: 90, - ); - await game.ensureAdd(pathway); - - expect(pathway.body.bodyType, equals(BodyType.static)); - }, - ); - }); - }); - - group('bezier curve', () { - final controlPoints = [ - Vector2(0, 0), - Vector2(50, 0), - Vector2(0, 50), - Vector2(50, 50), - ]; - - flameTester.test( - 'loads correctly', - (game) async { - final pathway = Pathway.bezierCurve( - position: Vector2.zero(), - controlPoints: controlPoints, - pathwayWidth: pathwayWidth, - ); - await game.ensureAdd(pathway); - expect(game.contains(pathway), isTrue); - }, - ); - - group('body', () { - flameTester.test( - 'positions correctly', - (game) async { - final position = Vector2.all(10); - final pathway = Pathway.bezierCurve( - position: position, - controlPoints: controlPoints, - pathwayWidth: pathwayWidth, - ); - await game.ensureAdd(pathway); - game.contains(pathway); - - expect(pathway.body.position, position); - }, - ); - - flameTester.test( - 'is static', - (game) async { - final pathway = Pathway.bezierCurve( - position: Vector2.zero(), - controlPoints: controlPoints, - pathwayWidth: pathwayWidth, - ); - await game.ensureAdd(pathway); - - expect(pathway.body.bodyType, equals(BodyType.static)); - }, - ); - }); - }); - }); -} From 4faf703648317810914a2238d0f9404565cc4a41 Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Tue, 8 Mar 2022 11:28:18 +0100 Subject: [PATCH 17/27] chore: applied changes from GH comments, doc descriptions, var names, asserts, etc --- lib/game/components/pathway.dart | 59 +++--- test/game/components/pathway_test.dart | 248 +++++++++++++++++++++++++ 2 files changed, 280 insertions(+), 27 deletions(-) create mode 100644 test/game/components/pathway_test.dart diff --git a/lib/game/components/pathway.dart b/lib/game/components/pathway.dart index 75656e9e..13595cc9 100644 --- a/lib/game/components/pathway.dart +++ b/lib/game/components/pathway.dart @@ -4,11 +4,12 @@ import 'package:flutter/material.dart'; import 'package:maths/maths.dart'; /// {@template pathway} -/// [Pathway] creates different shapes that sets the pathwayways that ball -/// can follow or collide to like walls. +/// [Pathway] creates lines of various shapes that the [Ball] can collide +/// with and move along. /// {@endtemplate} class Pathway extends BodyComponent { Pathway._({ + // TODO(ruialonso): remove color when assets added. Color? color, required Vector2 position, required List> paths, @@ -20,10 +21,11 @@ class Pathway extends BodyComponent { } /// {@macro pathway} - /// [Pathway.straight] creates a straight pathway for the ball given - /// a [position] for the body, between a [start] and [end] points. - /// It creates two [ChainShape] separated by a [pathwayWidth]. If [singleWall] - /// is true, just one [ChainShape] is created + /// [Pathway.straight] creates a straight pathway for the ball. + /// + /// given a [position] for the body, between a [start] and [end] points. + /// It creates two [ChainShape] separated by a [pathwayWidth]. + /// If [singleWall] is true, just one [ChainShape] is created /// (like a wall instead of a pathway) /// The pathway could be rotated by [rotation] in degrees. factory Pathway.straight({ @@ -39,15 +41,15 @@ class Pathway extends BodyComponent { final wall1 = [ start.clone(), end.clone(), - ]; - paths.add(wall1.map((e) => e..rotate(radians(rotation))).toList()); + ].map((vector) => vector..rotate(radians(rotation))).toList(); + paths.add(wall1); if (!singleWall) { final wall2 = [ start + Vector2(pathwayWidth, 0), end + Vector2(pathwayWidth, 0), - ]; - paths.add(wall2.map((e) => e..rotate(radians(rotation))).toList()); + ].map((vector) => vector..rotate(radians(rotation))).toList(); + paths.add(wall2); } return Pathway._( @@ -58,9 +60,11 @@ class Pathway extends BodyComponent { } /// {@macro pathway} - /// [Pathway.straight] creates an arc pathway for the ball given a [position] - /// for the body, a [radius] for the circumference and an [angle] to specify - /// the size of the semi circumference. + /// [Pathway.arc] creates an arc pathway for the ball. + /// + /// The arc is created given a [position] for the body, a [radius] for the + /// circumference and an [angle] to specify the size of it (360 will return + /// a completed circumference and minor angles a semi circumference ). /// It creates two [ChainShape] separated by a [pathwayWidth], like a circular /// crown. The specified [radius] is for the outer arc, the inner one will /// have a radius of radius-pathwayWidth. @@ -86,11 +90,9 @@ class Pathway extends BodyComponent { paths.add(wall1); if (!singleWall) { - final minRadius = radius - pathwayWidth; - final wall2 = calculateArc( center: position, - radius: minRadius, + radius: radius - pathwayWidth, angle: angle, offsetAngle: rotation, ); @@ -105,12 +107,14 @@ class Pathway extends BodyComponent { } /// {@macro pathway} - /// [Pathway.straight] creates a bezier curve pathway for the ball given a - /// [position] for the body, with control point specified by [controlPoints]. + /// [Pathway.bezierCurve] creates a bezier curve pathway for the ball. + /// + /// The curve is created given a [position] for the body, and + /// with a list of control points specified by [controlPoints]. /// First and last points set the beginning and end of the curve, all the /// inner points between them set the bezier curve final shape. - /// It creates two [ChainShape] separated by a [pathwayWidth]. If [singleWall] - /// is true, just one [ChainShape] is created + /// It creates two [ChainShape] separated by a [pathwayWidth]. + /// If [singleWall] is true, just one [ChainShape] is created /// (like a wall instead of a pathway) /// The pathway could be rotated by [rotation] in degrees. factory Pathway.bezierCurve({ @@ -123,17 +127,18 @@ class Pathway extends BodyComponent { }) { final paths = >[]; - final wall1 = calculateBezierCurve(controlPoints: controlPoints); - paths.add(wall1.map((e) => e..rotate(radians(rotation))).toList()); + final wall1 = calculateBezierCurve(controlPoints: controlPoints) + .map((vector) => vector..rotate(radians(rotation))) + .toList(); + paths.add(wall1); - var wall2 = []; if (!singleWall) { - wall2 = calculateBezierCurve( + final wall2 = calculateBezierCurve( controlPoints: controlPoints - .map((e) => e + Vector2(pathwayWidth, -pathwayWidth)) + .map((vector) => vector + Vector2(pathwayWidth, -pathwayWidth)) .toList(), - ); - paths.add(wall2.map((e) => e..rotate(radians(rotation))).toList()); + ).map((vector) => vector..rotate(radians(rotation))).toList(); + paths.add(wall2); } return Pathway._( diff --git a/test/game/components/pathway_test.dart b/test/game/components/pathway_test.dart new file mode 100644 index 00000000..e1c10455 --- /dev/null +++ b/test/game/components/pathway_test.dart @@ -0,0 +1,248 @@ +// ignore_for_file: cascade_invocations, prefer_const_constructors +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball/game/game.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(PinballGame.new); + + group('Pathway', () { + const pathwayWidth = 50.0; + + group('straight', () { + group('color', () { + flameTester.test( + 'has transparent color by default if not specified', + (game) async { + final pathway = Pathway.straight( + position: Vector2.zero(), + start: Vector2(10, 10), + end: Vector2(20, 20), + pathwayWidth: pathwayWidth, + ); + await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); + expect(pathway.paint, isNotNull); + expect( + pathway.paint.color, + equals(Color.fromARGB(0, 0, 0, 0)), + ); + }, + ); + flameTester.test( + 'has a color if set', + (game) async { + const defaultColor = Colors.blue; + + final pathway = Pathway.straight( + color: defaultColor, + position: Vector2.zero(), + start: Vector2(10, 10), + end: Vector2(20, 20), + pathwayWidth: pathwayWidth, + ); + await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); + expect(pathway.paint, isNotNull); + expect(pathway.paint.color.value, equals(defaultColor.value)); + }, + ); + }); + + flameTester.test( + 'loads correctly', + (game) async { + final pathway = Pathway.straight( + position: Vector2.zero(), + start: Vector2(10, 10), + end: Vector2(20, 20), + pathwayWidth: pathwayWidth, + ); + await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); + }, + ); + + group('body', () { + flameTester.test( + 'positions correctly', + (game) async { + final position = Vector2.all(10); + final pathway = Pathway.straight( + position: position, + start: Vector2(10, 10), + end: Vector2(20, 20), + pathwayWidth: pathwayWidth, + ); + await game.ensureAdd(pathway); + game.contains(pathway); + + expect(pathway.body.position, position); + }, + ); + + flameTester.test( + 'is static', + (game) async { + final pathway = Pathway.straight( + position: Vector2.zero(), + start: Vector2(10, 10), + end: Vector2(20, 20), + pathwayWidth: pathwayWidth, + ); + await game.ensureAdd(pathway); + + expect(pathway.body.bodyType, equals(BodyType.static)); + }, + ); + }); + + group('fixtures', () { + flameTester.test( + 'exists only one ChainShape if just one wall', + (game) async { + final pathway = Pathway.straight( + position: Vector2.zero(), + start: Vector2(10, 10), + end: Vector2(20, 20), + pathwayWidth: pathwayWidth, + singleWall: true, + ); + await game.ensureAdd(pathway); + + expect(pathway.body.fixtures.length, 1); + final fixture = pathway.body.fixtures[0]; + expect(fixture, isA()); + expect(fixture.shape.shapeType, equals(ShapeType.chain)); + }, + ); + + flameTester.test( + 'exists two ChainShape if there is by default two walls', + (game) async { + final pathway = Pathway.straight( + position: Vector2.zero(), + start: Vector2(10, 10), + end: Vector2(20, 20), + pathwayWidth: pathwayWidth, + ); + await game.ensureAdd(pathway); + + expect(pathway.body.fixtures.length, 2); + for (final fixture in pathway.body.fixtures) { + expect(fixture, isA()); + expect(fixture.shape.shapeType, equals(ShapeType.chain)); + } + }, + ); + }); + }); + + group('arc', () { + flameTester.test( + 'loads correctly', + (game) async { + final pathway = Pathway.arc( + position: Vector2.zero(), + pathwayWidth: pathwayWidth, + radius: 100, + angle: 90, + ); + await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); + }, + ); + + group('body', () { + flameTester.test( + 'positions correctly', + (game) async { + final position = Vector2.all(10); + final pathway = Pathway.arc( + position: position, + pathwayWidth: pathwayWidth, + radius: 100, + angle: 90, + ); + await game.ensureAdd(pathway); + game.contains(pathway); + + expect(pathway.body.position, position); + }, + ); + + flameTester.test( + 'is static', + (game) async { + final pathway = Pathway.arc( + position: Vector2.zero(), + pathwayWidth: pathwayWidth, + radius: 100, + angle: 90, + ); + await game.ensureAdd(pathway); + + expect(pathway.body.bodyType, equals(BodyType.static)); + }, + ); + }); + }); + + group('bezier curve', () { + final controlPoints = [ + Vector2(0, 0), + Vector2(50, 0), + Vector2(0, 50), + Vector2(50, 50), + ]; + + flameTester.test( + 'loads correctly', + (game) async { + final pathway = Pathway.bezierCurve( + position: Vector2.zero(), + controlPoints: controlPoints, + pathwayWidth: pathwayWidth, + ); + await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); + }, + ); + + group('body', () { + flameTester.test( + 'positions correctly', + (game) async { + final position = Vector2.all(10); + final pathway = Pathway.bezierCurve( + position: position, + controlPoints: controlPoints, + pathwayWidth: pathwayWidth, + ); + await game.ensureAdd(pathway); + game.contains(pathway); + + expect(pathway.body.position, position); + }, + ); + + flameTester.test( + 'is static', + (game) async { + final pathway = Pathway.bezierCurve( + position: Vector2.zero(), + controlPoints: controlPoints, + pathwayWidth: pathwayWidth, + ); + await game.ensureAdd(pathway); + + expect(pathway.body.bodyType, equals(BodyType.static)); + }, + ); + }); + }); + }); +} From 4290cb5a8d517c9ecafb9f8d8c5fbf34f46c8b6c Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Tue, 8 Mar 2022 11:37:08 +0100 Subject: [PATCH 18/27] refactor: changed math package to geometry --- lib/game/components/pathway.dart | 8 ++-- packages/{maths => geometry}/.gitignore | 0 packages/{maths => geometry}/README.md | 0 .../{maths => geometry}/analysis_options.yaml | 0 packages/geometry/lib/geometry.dart | 3 ++ .../lib/src/geometry.dart} | 46 +++++++++++++------ packages/{maths => geometry}/pubspec.yaml | 2 +- .../test/src/geometry_test.dart} | 9 +++- packages/maths/lib/maths.dart | 3 -- pubspec.lock | 14 +++--- pubspec.yaml | 4 +- 11 files changed, 56 insertions(+), 33 deletions(-) rename packages/{maths => geometry}/.gitignore (100%) rename packages/{maths => geometry}/README.md (100%) rename packages/{maths => geometry}/analysis_options.yaml (100%) create mode 100644 packages/geometry/lib/geometry.dart rename packages/{maths/lib/src/maths.dart => geometry/lib/src/geometry.dart} (53%) rename packages/{maths => geometry}/pubspec.yaml (95%) rename packages/{maths/test/src/maths_test.dart => geometry/test/src/geometry_test.dart} (98%) delete mode 100644 packages/maths/lib/maths.dart diff --git a/lib/game/components/pathway.dart b/lib/game/components/pathway.dart index 13595cc9..1fc48af1 100644 --- a/lib/game/components/pathway.dart +++ b/lib/game/components/pathway.dart @@ -1,7 +1,7 @@ import 'package:flame/extensions.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; -import 'package:maths/maths.dart'; +import 'package:geometry/geometry.dart'; /// {@template pathway} /// [Pathway] creates lines of various shapes that the [Ball] can collide @@ -21,7 +21,7 @@ class Pathway extends BodyComponent { } /// {@macro pathway} - /// [Pathway.straight] creates a straight pathway for the ball. + /// [Pathway.straight] creates a straight pathway for the [Ball]. /// /// given a [position] for the body, between a [start] and [end] points. /// It creates two [ChainShape] separated by a [pathwayWidth]. @@ -60,7 +60,7 @@ class Pathway extends BodyComponent { } /// {@macro pathway} - /// [Pathway.arc] creates an arc pathway for the ball. + /// [Pathway.arc] creates an arc pathway for the [Ball]. /// /// The arc is created given a [position] for the body, a [radius] for the /// circumference and an [angle] to specify the size of it (360 will return @@ -107,7 +107,7 @@ class Pathway extends BodyComponent { } /// {@macro pathway} - /// [Pathway.bezierCurve] creates a bezier curve pathway for the ball. + /// [Pathway.bezierCurve] creates a bezier curve pathway for the [Ball]. /// /// The curve is created given a [position] for the body, and /// with a list of control points specified by [controlPoints]. diff --git a/packages/maths/.gitignore b/packages/geometry/.gitignore similarity index 100% rename from packages/maths/.gitignore rename to packages/geometry/.gitignore diff --git a/packages/maths/README.md b/packages/geometry/README.md similarity index 100% rename from packages/maths/README.md rename to packages/geometry/README.md diff --git a/packages/maths/analysis_options.yaml b/packages/geometry/analysis_options.yaml similarity index 100% rename from packages/maths/analysis_options.yaml rename to packages/geometry/analysis_options.yaml diff --git a/packages/geometry/lib/geometry.dart b/packages/geometry/lib/geometry.dart new file mode 100644 index 00000000..2453ed05 --- /dev/null +++ b/packages/geometry/lib/geometry.dart @@ -0,0 +1,3 @@ +library geometry; + +export 'src/geometry.dart'; diff --git a/packages/maths/lib/src/maths.dart b/packages/geometry/lib/src/geometry.dart similarity index 53% rename from packages/maths/lib/src/maths.dart rename to packages/geometry/lib/src/geometry.dart index d0912418..c992e5a2 100644 --- a/packages/maths/lib/src/maths.dart +++ b/packages/geometry/lib/src/geometry.dart @@ -1,9 +1,15 @@ import 'dart:math' as math; import 'package:flame/extensions.dart'; -/// Method to calculate all points (with a required precision amount of them) -/// of a circumference based on angle, offsetAngle and radius -/// https://en.wikipedia.org/wiki/Trigonometric_functions +/// Calculates all [Vector2]s of a circumference. +/// +/// Circumference is created from a [center] and a [radius] +/// Also semi circumference could be created, specifying its [angle] in degrees +/// and the offset start angle [offsetAngle] for this semi circumference. +/// The higher the [precision], the more [Vector2]s will be calculated, +/// achieving a more rounded arc. +/// +/// For more information read: https://en.wikipedia.org/wiki/Trigonometric_functions. List calculateArc({ required Vector2 center, required double radius, @@ -26,18 +32,28 @@ List calculateArc({ return points; } -/// Method that calculates all points of a bezier curve of degree 'g' and -/// n=g-1 control points and range 0<=t<=1 -/// https://en.wikipedia.org/wiki/B%C3%A9zier_curve +/// Calculates all [Vector2]s of a bezier curve. +/// +/// A bezier curve of [controlPoints] that say how to create this curve. +/// First and last points specify the beginning and the end respectively +/// of the curve. The inner points specify the shape of the curve and +/// its turning points. +/// The [step] must be in range 0<=step<=1 and indicates the precision to +/// calculate the curve. +/// For more information read: https://en.wikipedia.org/wiki/B%C3%A9zier_curve List calculateBezierCurve({ required List controlPoints, double step = 0.001, }) { - assert(0 <= step && step <= 1, 'Range 0<=step<=1'); + assert( + 0 <= step && step <= 1, + 'Step ($step) must be in range 0 <= step <= 1', + ); assert( controlPoints.length >= 2, - 'At least 2 control points to create a bezier curve', + 'At least 2 control points needed to create a bezier curve', ); + var t = 0.0; final n = controlPoints.length - 1; final points = []; @@ -62,9 +78,10 @@ List calculateBezierCurve({ } /// Method to calculate the binomial coefficient of 'n' and 'k' -/// https://en.wikipedia.org/wiki/Binomial_coefficient +/// For more information read: https://en.wikipedia.org/wiki/Binomial_coefficient num binomial(num n, num k) { - assert(0 <= k && k <= n, 'Range 0<=k<=n'); + assert(0 <= k && k <= n, 'k ($k) and n ($n) must be in range 0 <= k <= n'); + if (k == 0 || n == k) { return 1; } else { @@ -73,12 +90,11 @@ num binomial(num n, num k) { } /// Method to calculate the factorial of some number 'n' -/// https://en.wikipedia.org/wiki/Factorial +/// For more information read: https://en.wikipedia.org/wiki/Factorial num factorial(num n) { - assert(0 <= n, 'Non negative n'); - if (n == 0) { - return 1; - } else if (n == 1) { + assert(n >= 0, 'Factorial is not defined for negative number n ($n)'); + + if (n == 0 || n == 1) { return 1; } else { return n * factorial(n - 1); diff --git a/packages/maths/pubspec.yaml b/packages/geometry/pubspec.yaml similarity index 95% rename from packages/maths/pubspec.yaml rename to packages/geometry/pubspec.yaml index b037f4a4..99c09cd0 100644 --- a/packages/maths/pubspec.yaml +++ b/packages/geometry/pubspec.yaml @@ -1,4 +1,4 @@ -name: maths +name: geometry description: A Very Good Project created by Very Good CLI. version: 1.0.0+1 publish_to: none diff --git a/packages/maths/test/src/maths_test.dart b/packages/geometry/test/src/geometry_test.dart similarity index 98% rename from packages/maths/test/src/maths_test.dart rename to packages/geometry/test/src/geometry_test.dart index eaaf9367..6ecf66c8 100644 --- a/packages/maths/test/src/maths_test.dart +++ b/packages/geometry/test/src/geometry_test.dart @@ -1,7 +1,7 @@ // ignore_for_file: prefer_const_constructors, cascade_invocations import 'package:flame/extensions.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:maths/maths.dart'; +import 'package:geometry/geometry.dart'; class Binomial { Binomial({required this.n, required this.k}); @@ -31,6 +31,7 @@ void main() { expect(points.length, 50); }); }); + group('calculateBezierCurve', () { test('fails if step not in range', () { expect( @@ -44,6 +45,7 @@ void main() { throwsAssertionError, ); }); + test('fails if not enough control points', () { expect( () => calculateBezierCurve(controlPoints: [Vector2.zero()]), @@ -81,12 +83,15 @@ void main() { test('fails if k is negative', () { expect(() => binomial(1, -1), throwsAssertionError); }); + test('fails if n is negative', () { expect(() => binomial(-1, 1), throwsAssertionError); }); + test('fails if n < k', () { expect(() => binomial(1, 2), throwsAssertionError); }); + test('for a specific input gives a correct value', () { final binomialInputsToExpected = { Binomial(n: 0, k: 0): 1, @@ -123,10 +128,12 @@ void main() { }); }); }); + group('factorial', () { test('fails if negative number', () { expect(() => factorial(-1), throwsAssertionError); }); + test('for a specific input gives a correct value', () { final factorialInputsToExpected = { 0: 1, diff --git a/packages/maths/lib/maths.dart b/packages/maths/lib/maths.dart deleted file mode 100644 index b340388b..00000000 --- a/packages/maths/lib/maths.dart +++ /dev/null @@ -1,3 +0,0 @@ -library maths; - -export 'src/maths.dart'; diff --git a/pubspec.lock b/pubspec.lock index db3b239a..5dee0c71 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -198,6 +198,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.2" + geometry: + dependency: "direct main" + description: + path: "packages/geometry" + relative: true + source: path + version: "1.0.0+1" glob: dependency: transitive description: @@ -261,13 +268,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.3" - maths: - dependency: "direct main" - description: - path: "/Users/ruialonso/dev/flutter/googleIO22/pinball/packages/maths" - relative: false - source: path - version: "1.0.0+1" meta: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7b9c9c19..1d0c8ada 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,9 +17,9 @@ dependencies: flutter_bloc: ^8.0.1 flutter_localizations: sdk: flutter + geometry: + path: packages/geometry intl: ^0.17.0 - maths: - path: packages/maths dev_dependencies: bloc_test: ^9.0.2 From 178876b4fc8f5124f5370d8ca7a2844f311bbb44 Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Tue, 8 Mar 2022 11:40:07 +0100 Subject: [PATCH 19/27] refactor: changed geometry package description --- packages/geometry/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/geometry/pubspec.yaml b/packages/geometry/pubspec.yaml index 99c09cd0..2678cdef 100644 --- a/packages/geometry/pubspec.yaml +++ b/packages/geometry/pubspec.yaml @@ -1,5 +1,5 @@ name: geometry -description: A Very Good Project created by Very Good CLI. +description: Helper package to calculate points of lines, arcs and curves for the pathways of the ball version: 1.0.0+1 publish_to: none From 6b3158b019cff4e9426c8a0325638843d0b1a58b Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Tue, 8 Mar 2022 11:41:45 +0100 Subject: [PATCH 20/27] chore: removed unscope Ball refs in doc --- lib/game/components/pathway.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/game/components/pathway.dart b/lib/game/components/pathway.dart index 1fc48af1..d6582a91 100644 --- a/lib/game/components/pathway.dart +++ b/lib/game/components/pathway.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:geometry/geometry.dart'; /// {@template pathway} -/// [Pathway] creates lines of various shapes that the [Ball] can collide +/// [Pathway] creates lines of various shapes that the ball can collide /// with and move along. /// {@endtemplate} class Pathway extends BodyComponent { @@ -21,7 +21,7 @@ class Pathway extends BodyComponent { } /// {@macro pathway} - /// [Pathway.straight] creates a straight pathway for the [Ball]. + /// [Pathway.straight] creates a straight pathway for the ball. /// /// given a [position] for the body, between a [start] and [end] points. /// It creates two [ChainShape] separated by a [pathwayWidth]. @@ -60,7 +60,7 @@ class Pathway extends BodyComponent { } /// {@macro pathway} - /// [Pathway.arc] creates an arc pathway for the [Ball]. + /// [Pathway.arc] creates an arc pathway for the ball. /// /// The arc is created given a [position] for the body, a [radius] for the /// circumference and an [angle] to specify the size of it (360 will return @@ -107,7 +107,7 @@ class Pathway extends BodyComponent { } /// {@macro pathway} - /// [Pathway.bezierCurve] creates a bezier curve pathway for the [Ball]. + /// [Pathway.bezierCurve] creates a bezier curve pathway for the ball. /// /// The curve is created given a [position] for the body, and /// with a list of control points specified by [controlPoints]. From bbab659b85613f8a11519317dd11549bbcb50137 Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Wed, 9 Mar 2022 17:36:15 +0100 Subject: [PATCH 21/27] refactor: changed docs and use angles in radians instead of degrees --- lib/game/components/pathway.dart | 81 +++++++++++++------------ lib/game/pinball_game.dart | 1 - packages/geometry/lib/src/geometry.dart | 20 +++--- test/game/components/pathway_test.dart | 61 ++++++++++--------- 4 files changed, 86 insertions(+), 77 deletions(-) diff --git a/lib/game/components/pathway.dart b/lib/game/components/pathway.dart index d6582a91..3404f2d2 100644 --- a/lib/game/components/pathway.dart +++ b/lib/game/components/pathway.dart @@ -4,8 +4,9 @@ import 'package:flutter/material.dart'; import 'package:geometry/geometry.dart'; /// {@template pathway} -/// [Pathway] creates lines of various shapes that the ball can collide -/// with and move along. +/// [Pathway] creates lines of various shapes. +/// +/// [BodyComponent]s such as a Ball can collide and move along a [Pathway]. /// {@endtemplate} class Pathway extends BodyComponent { Pathway._({ @@ -20,20 +21,19 @@ class Pathway extends BodyComponent { ..style = PaintingStyle.stroke; } - /// {@macro pathway} - /// [Pathway.straight] creates a straight pathway for the ball. + /// Creates a uniform unidirectional (straight) [Pathway]. /// - /// given a [position] for the body, between a [start] and [end] points. - /// It creates two [ChainShape] separated by a [pathwayWidth]. - /// If [singleWall] is true, just one [ChainShape] is created - /// (like a wall instead of a pathway) - /// The pathway could be rotated by [rotation] in degrees. + /// Does so with two [ChainShape] separated by a [width]. Placed + /// at a [position] between [start] and [end] points. Can + /// be rotated by a given [rotation] in radians. + /// + /// If [singleWall] is true, just one [ChainShape] is created. factory Pathway.straight({ Color? color, required Vector2 position, required Vector2 start, required Vector2 end, - required double pathwayWidth, + required double width, double rotation = 0, bool singleWall = false, }) { @@ -41,14 +41,14 @@ class Pathway extends BodyComponent { final wall1 = [ start.clone(), end.clone(), - ].map((vector) => vector..rotate(radians(rotation))).toList(); + ].map((vector) => vector..rotate(rotation)).toList(); paths.add(wall1); if (!singleWall) { final wall2 = [ - start + Vector2(pathwayWidth, 0), - end + Vector2(pathwayWidth, 0), - ].map((vector) => vector..rotate(radians(rotation))).toList(); + start + Vector2(width, 0), + end + Vector2(width, 0), + ].map((vector) => vector..rotate(rotation)).toList(); paths.add(wall2); } @@ -59,21 +59,24 @@ class Pathway extends BodyComponent { ); } - /// {@macro pathway} - /// [Pathway.arc] creates an arc pathway for the ball. + /// Creates an arc [Pathway]. + /// + /// The [angle], in radians, specifies the size of the arc. For example, 2*pi + /// returns a complete circumference and minor angles a semi circumference. + /// + /// The center of the arc is placed at [position]. + /// + /// Does so with two [ChainShape] separated by a [width]. Which can be + /// rotated by a given [rotation] in radians. + /// + /// The outer radius is specified by [radius], whilst the inner one is + /// equivalent to [radius] - [width]. /// - /// The arc is created given a [position] for the body, a [radius] for the - /// circumference and an [angle] to specify the size of it (360 will return - /// a completed circumference and minor angles a semi circumference ). - /// It creates two [ChainShape] separated by a [pathwayWidth], like a circular - /// crown. The specified [radius] is for the outer arc, the inner one will - /// have a radius of radius-pathwayWidth. /// If [singleWall] is true, just one [ChainShape] is created. - /// The pathway could be rotated by [rotation] in degrees. factory Pathway.arc({ Color? color, required Vector2 position, - required double pathwayWidth, + required double width, required double radius, required double angle, double rotation = 0, @@ -92,7 +95,7 @@ class Pathway extends BodyComponent { if (!singleWall) { final wall2 = calculateArc( center: position, - radius: radius - pathwayWidth, + radius: radius - width, angle: angle, offsetAngle: rotation, ); @@ -106,38 +109,36 @@ class Pathway extends BodyComponent { ); } - /// {@macro pathway} - /// [Pathway.bezierCurve] creates a bezier curve pathway for the ball. + /// Creates a bezier curve [Pathway]. /// - /// The curve is created given a [position] for the body, and - /// with a list of control points specified by [controlPoints]. - /// First and last points set the beginning and end of the curve, all the - /// inner points between them set the bezier curve final shape. - /// It creates two [ChainShape] separated by a [pathwayWidth]. - /// If [singleWall] is true, just one [ChainShape] is created - /// (like a wall instead of a pathway) - /// The pathway could be rotated by [rotation] in degrees. + /// Does so with two [ChainShape] separated by a [width]. Which can be + /// rotated by a given [rotation] in radians. + /// + /// First and last [controlPoints] set the beginning and end of the curve, + /// inner points between them set its final shape. + /// + /// If [singleWall] is true, just one [ChainShape] is created. factory Pathway.bezierCurve({ Color? color, required Vector2 position, required List controlPoints, - required double pathwayWidth, + required double width, double rotation = 0, bool singleWall = false, }) { final paths = >[]; final wall1 = calculateBezierCurve(controlPoints: controlPoints) - .map((vector) => vector..rotate(radians(rotation))) + .map((vector) => vector..rotate(rotation)) .toList(); paths.add(wall1); if (!singleWall) { final wall2 = calculateBezierCurve( controlPoints: controlPoints - .map((vector) => vector + Vector2(pathwayWidth, -pathwayWidth)) + .map((vector) => vector + Vector2(width, -width)) .toList(), - ).map((vector) => vector..rotate(radians(rotation))).toList(); + ).map((vector) => vector..rotate(rotation)).toList(); paths.add(wall2); } @@ -158,13 +159,13 @@ class Pathway extends BodyComponent { ..position = _position; final body = world.createBody(bodyDef); - for (final path in _paths) { final chain = ChainShape() ..createChain( path.map(gameRef.screenToWorld).toList(), ); final fixtureDef = FixtureDef(chain); + body.createFixture(fixtureDef); } diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 7c701c09..6fe6e571 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -1,5 +1,4 @@ import 'dart:async'; - import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball/game/game.dart'; diff --git a/packages/geometry/lib/src/geometry.dart b/packages/geometry/lib/src/geometry.dart index c992e5a2..8851ed25 100644 --- a/packages/geometry/lib/src/geometry.dart +++ b/packages/geometry/lib/src/geometry.dart @@ -4,7 +4,7 @@ import 'package:flame/extensions.dart'; /// Calculates all [Vector2]s of a circumference. /// /// Circumference is created from a [center] and a [radius] -/// Also semi circumference could be created, specifying its [angle] in degrees +/// Also semi circumference could be created, specifying its [angle] in radians /// and the offset start angle [offsetAngle] for this semi circumference. /// The higher the [precision], the more [Vector2]s will be calculated, /// achieving a more rounded arc. @@ -17,13 +17,12 @@ List calculateArc({ double offsetAngle = 0, int precision = 100, }) { - final stepAngle = radians(angle / (precision - 1)); - final stepOffset = radians(offsetAngle); + final stepAngle = angle / (precision - 1); final points = []; for (var i = 0; i < precision; i++) { - final xCoord = center.x + radius * math.cos((stepAngle * i) + stepOffset); - final yCoord = center.y - radius * math.sin((stepAngle * i) + stepOffset); + final xCoord = center.x + radius * math.cos((stepAngle * i) + offsetAngle); + final yCoord = center.y - radius * math.sin((stepAngle * i) + offsetAngle); final point = Vector2(xCoord, yCoord); points.add(point); @@ -35,11 +34,14 @@ List calculateArc({ /// Calculates all [Vector2]s of a bezier curve. /// /// A bezier curve of [controlPoints] that say how to create this curve. +/// /// First and last points specify the beginning and the end respectively /// of the curve. The inner points specify the shape of the curve and /// its turning points. -/// The [step] must be in range 0<=step<=1 and indicates the precision to -/// calculate the curve. +/// +/// The [step] must be between zero and one (inclusive), indicating the +/// precision to calculate the curve. +/// /// For more information read: https://en.wikipedia.org/wiki/B%C3%A9zier_curve List calculateBezierCurve({ required List controlPoints, @@ -77,7 +79,7 @@ List calculateBezierCurve({ return points; } -/// Method to calculate the binomial coefficient of 'n' and 'k' +/// Calculates the binomial coefficient of 'n' and 'k'. /// For more information read: https://en.wikipedia.org/wiki/Binomial_coefficient num binomial(num n, num k) { assert(0 <= k && k <= n, 'k ($k) and n ($n) must be in range 0 <= k <= n'); @@ -89,7 +91,7 @@ num binomial(num n, num k) { } } -/// Method to calculate the factorial of some number 'n' +/// Calculate the factorial of 'n'. /// For more information read: https://en.wikipedia.org/wiki/Factorial num factorial(num n) { assert(n >= 0, 'Factorial is not defined for negative number n ($n)'); diff --git a/test/game/components/pathway_test.dart b/test/game/components/pathway_test.dart index e1c10455..7f8357d8 100644 --- a/test/game/components/pathway_test.dart +++ b/test/game/components/pathway_test.dart @@ -1,4 +1,5 @@ // ignore_for_file: cascade_invocations, prefer_const_constructors +import 'dart:math' as math; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter/material.dart'; @@ -10,20 +11,21 @@ void main() { final flameTester = FlameTester(PinballGame.new); group('Pathway', () { - const pathwayWidth = 50.0; + const width = 50.0; group('straight', () { group('color', () { flameTester.test( - 'has transparent color by default if not specified', + 'has transparent color by default when no color is specified', (game) async { final pathway = Pathway.straight( position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); expect(pathway.paint, isNotNull); expect( @@ -32,8 +34,9 @@ void main() { ); }, ); + flameTester.test( - 'has a color if set', + 'has a color when is specified', (game) async { const defaultColor = Colors.blue; @@ -42,9 +45,10 @@ void main() { position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); expect(pathway.paint, isNotNull); expect(pathway.paint.color.value, equals(defaultColor.value)); @@ -59,9 +63,10 @@ void main() { position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); }, ); @@ -75,11 +80,11 @@ void main() { position: position, start: Vector2(10, 10), end: Vector2(20, 20), - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway); - game.contains(pathway); + game.contains(pathway); expect(pathway.body.position, position); }, ); @@ -91,7 +96,7 @@ void main() { position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway); @@ -102,13 +107,13 @@ void main() { group('fixtures', () { flameTester.test( - 'exists only one ChainShape if just one wall', + 'has only one ChainShape when singleWall is true', (game) async { final pathway = Pathway.straight( position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathwayWidth: pathwayWidth, + width: width, singleWall: true, ); await game.ensureAdd(pathway); @@ -121,13 +126,13 @@ void main() { ); flameTester.test( - 'exists two ChainShape if there is by default two walls', + 'has two ChainShape when singleWall is false (default)', (game) async { final pathway = Pathway.straight( position: Vector2.zero(), start: Vector2(10, 10), end: Vector2(20, 20), - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway); @@ -147,11 +152,12 @@ void main() { (game) async { final pathway = Pathway.arc( position: Vector2.zero(), - pathwayWidth: pathwayWidth, - radius: 100, - angle: 90, + width: width, + radius: math.pi / 2, + angle: math.pi / 2, ); await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); }, ); @@ -163,13 +169,13 @@ void main() { final position = Vector2.all(10); final pathway = Pathway.arc( position: position, - pathwayWidth: pathwayWidth, - radius: 100, - angle: 90, + width: width, + radius: math.pi / 2, + angle: math.pi / 2, ); await game.ensureAdd(pathway); - game.contains(pathway); + game.contains(pathway); expect(pathway.body.position, position); }, ); @@ -179,9 +185,9 @@ void main() { (game) async { final pathway = Pathway.arc( position: Vector2.zero(), - pathwayWidth: pathwayWidth, - radius: 100, - angle: 90, + width: width, + radius: math.pi / 2, + angle: math.pi / 2, ); await game.ensureAdd(pathway); @@ -205,9 +211,10 @@ void main() { final pathway = Pathway.bezierCurve( position: Vector2.zero(), controlPoints: controlPoints, - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway); + expect(game.contains(pathway), isTrue); }, ); @@ -220,11 +227,11 @@ void main() { final pathway = Pathway.bezierCurve( position: position, controlPoints: controlPoints, - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway); - game.contains(pathway); + game.contains(pathway); expect(pathway.body.position, position); }, ); @@ -235,7 +242,7 @@ void main() { final pathway = Pathway.bezierCurve( position: Vector2.zero(), controlPoints: controlPoints, - pathwayWidth: pathwayWidth, + width: width, ); await game.ensureAdd(pathway); From 45dc583c86512ddf524d3157cffe1871f4dc19aa Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Wed, 9 Mar 2022 17:42:54 +0100 Subject: [PATCH 22/27] test: fixed bug with radius in radians :S --- test/game/components/pathway_test.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/game/components/pathway_test.dart b/test/game/components/pathway_test.dart index 7f8357d8..6b11a2b3 100644 --- a/test/game/components/pathway_test.dart +++ b/test/game/components/pathway_test.dart @@ -1,5 +1,6 @@ // ignore_for_file: cascade_invocations, prefer_const_constructors import 'dart:math' as math; +import 'package:flame/input.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter/material.dart'; @@ -153,7 +154,7 @@ void main() { final pathway = Pathway.arc( position: Vector2.zero(), width: width, - radius: math.pi / 2, + radius: 100, angle: math.pi / 2, ); await game.ensureAdd(pathway); @@ -170,7 +171,7 @@ void main() { final pathway = Pathway.arc( position: position, width: width, - radius: math.pi / 2, + radius: 100, angle: math.pi / 2, ); await game.ensureAdd(pathway); @@ -186,7 +187,7 @@ void main() { final pathway = Pathway.arc( position: Vector2.zero(), width: width, - radius: math.pi / 2, + radius: 100, angle: math.pi / 2, ); await game.ensureAdd(pathway); From 696ace8bfff9f632fd03f858667d5596dd26fdce Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Wed, 9 Mar 2022 18:44:20 +0100 Subject: [PATCH 23/27] chore: removed unused import --- test/game/components/pathway_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/test/game/components/pathway_test.dart b/test/game/components/pathway_test.dart index 6b11a2b3..d3b82e96 100644 --- a/test/game/components/pathway_test.dart +++ b/test/game/components/pathway_test.dart @@ -1,6 +1,5 @@ // ignore_for_file: cascade_invocations, prefer_const_constructors import 'dart:math' as math; -import 'package:flame/input.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter/material.dart'; From 85cbe42958a8c0f8902463f0c9e980036f93b3b4 Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Wed, 9 Mar 2022 18:52:25 +0100 Subject: [PATCH 24/27] refactor: renamed wall1 and wall2 vars --- lib/game/components/pathway.dart | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/game/components/pathway.dart b/lib/game/components/pathway.dart index 3404f2d2..d41ce3da 100644 --- a/lib/game/components/pathway.dart +++ b/lib/game/components/pathway.dart @@ -38,18 +38,20 @@ class Pathway extends BodyComponent { bool singleWall = false, }) { final paths = >[]; - final wall1 = [ + + // TODO(ruialonso): Refactor repetitive logic + final firstWall = [ start.clone(), end.clone(), ].map((vector) => vector..rotate(rotation)).toList(); - paths.add(wall1); + paths.add(firstWall); if (!singleWall) { - final wall2 = [ + final secondWall = [ start + Vector2(width, 0), end + Vector2(width, 0), ].map((vector) => vector..rotate(rotation)).toList(); - paths.add(wall2); + paths.add(secondWall); } return Pathway._( @@ -84,22 +86,23 @@ class Pathway extends BodyComponent { }) { final paths = >[]; - final wall1 = calculateArc( + // TODO(ruialonso): Refactor repetitive logic + final outerWall = calculateArc( center: position, radius: radius, angle: angle, offsetAngle: rotation, ); - paths.add(wall1); + paths.add(outerWall); if (!singleWall) { - final wall2 = calculateArc( + final innerWall = calculateArc( center: position, radius: radius - width, angle: angle, offsetAngle: rotation, ); - paths.add(wall2); + paths.add(innerWall); } return Pathway._( @@ -128,18 +131,19 @@ class Pathway extends BodyComponent { }) { final paths = >[]; - final wall1 = calculateBezierCurve(controlPoints: controlPoints) + // TODO(ruialonso): Refactor repetitive logic + final firstWall = calculateBezierCurve(controlPoints: controlPoints) .map((vector) => vector..rotate(rotation)) .toList(); - paths.add(wall1); + paths.add(firstWall); if (!singleWall) { - final wall2 = calculateBezierCurve( + final secondWall = calculateBezierCurve( controlPoints: controlPoints .map((vector) => vector + Vector2(width, -width)) .toList(), ).map((vector) => vector..rotate(rotation)).toList(); - paths.add(wall2); + paths.add(secondWall); } return Pathway._( From 3a092f8a1c33700081429d9d6dda8aa3d6912af1 Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Wed, 9 Mar 2022 18:55:30 +0100 Subject: [PATCH 25/27] test: removed old group --- packages/geometry/test/src/geometry_test.dart | 251 +++++++++--------- 1 file changed, 125 insertions(+), 126 deletions(-) diff --git a/packages/geometry/test/src/geometry_test.dart b/packages/geometry/test/src/geometry_test.dart index 6ecf66c8..a3040a9c 100644 --- a/packages/geometry/test/src/geometry_test.dart +++ b/packages/geometry/test/src/geometry_test.dart @@ -11,149 +11,148 @@ class Binomial { } void main() { - group('Maths', () { - group('calculateArc', () { - test('returns by default 100 points as indicated by precision', () { - final points = calculateArc( - center: Vector2.zero(), - radius: 100, - angle: 90, - ); - expect(points.length, 100); - }); - test('returns as many points as indicated by precision', () { - final points = calculateArc( - center: Vector2.zero(), - radius: 100, - angle: 90, - precision: 50, - ); - expect(points.length, 50); - }); + group('calculateArc', () { + test('returns by default 100 points as indicated by precision', () { + final points = calculateArc( + center: Vector2.zero(), + radius: 100, + angle: 90, + ); + expect(points.length, 100); }); - group('calculateBezierCurve', () { - test('fails if step not in range', () { - expect( - () => calculateBezierCurve( - controlPoints: [ - Vector2(0, 0), - Vector2(10, 10), - ], - step: 2, - ), - throwsAssertionError, - ); - }); - - test('fails if not enough control points', () { - expect( - () => calculateBezierCurve(controlPoints: [Vector2.zero()]), - throwsAssertionError, - ); - expect( - () => calculateBezierCurve(controlPoints: []), - throwsAssertionError, - ); - }); + test('returns as many points as indicated by precision', () { + final points = calculateArc( + center: Vector2.zero(), + radius: 100, + angle: 90, + precision: 50, + ); + expect(points.length, 50); + }); + }); - test('returns by default 1000 points as indicated by step', () { - final points = calculateBezierCurve( + group('calculateBezierCurve', () { + test('fails if step not in range', () { + expect( + () => calculateBezierCurve( controlPoints: [ Vector2(0, 0), Vector2(10, 10), ], - ); - expect(points.length, 1000); - }); + step: 2, + ), + throwsAssertionError, + ); + }); - test('returns as many points as indicated by step', () { - final points = calculateBezierCurve( - controlPoints: [ - Vector2(0, 0), - Vector2(10, 10), - ], - step: 0.01, - ); - expect(points.length, 100); - }); + test('fails if not enough control points', () { + expect( + () => calculateBezierCurve(controlPoints: [Vector2.zero()]), + throwsAssertionError, + ); + expect( + () => calculateBezierCurve(controlPoints: []), + throwsAssertionError, + ); }); - group('binomial', () { - test('fails if k is negative', () { - expect(() => binomial(1, -1), throwsAssertionError); - }); + test('returns by default 1000 points as indicated by step', () { + final points = calculateBezierCurve( + controlPoints: [ + Vector2(0, 0), + Vector2(10, 10), + ], + ); + expect(points.length, 1000); + }); - test('fails if n is negative', () { - expect(() => binomial(-1, 1), throwsAssertionError); - }); + test('returns as many points as indicated by step', () { + final points = calculateBezierCurve( + controlPoints: [ + Vector2(0, 0), + Vector2(10, 10), + ], + step: 0.01, + ); + expect(points.length, 100); + }); + }); - test('fails if n < k', () { - expect(() => binomial(1, 2), throwsAssertionError); - }); + group('binomial', () { + test('fails if k is negative', () { + expect(() => binomial(1, -1), throwsAssertionError); + }); - test('for a specific input gives a correct value', () { - final binomialInputsToExpected = { - Binomial(n: 0, k: 0): 1, - Binomial(n: 1, k: 0): 1, - Binomial(n: 1, k: 1): 1, - Binomial(n: 2, k: 0): 1, - Binomial(n: 2, k: 1): 2, - Binomial(n: 2, k: 2): 1, - Binomial(n: 3, k: 0): 1, - Binomial(n: 3, k: 1): 3, - Binomial(n: 3, k: 2): 3, - Binomial(n: 3, k: 3): 1, - Binomial(n: 4, k: 0): 1, - Binomial(n: 4, k: 1): 4, - Binomial(n: 4, k: 2): 6, - Binomial(n: 4, k: 3): 4, - Binomial(n: 4, k: 4): 1, - Binomial(n: 5, k: 0): 1, - Binomial(n: 5, k: 1): 5, - Binomial(n: 5, k: 2): 10, - Binomial(n: 5, k: 3): 10, - Binomial(n: 5, k: 4): 5, - Binomial(n: 5, k: 5): 1, - Binomial(n: 6, k: 0): 1, - Binomial(n: 6, k: 1): 6, - Binomial(n: 6, k: 2): 15, - Binomial(n: 6, k: 3): 20, - Binomial(n: 6, k: 4): 15, - Binomial(n: 6, k: 5): 6, - Binomial(n: 6, k: 6): 1, - }; - binomialInputsToExpected.forEach((input, value) { - expect(binomial(input.n, input.k), value); - }); - }); + test('fails if n is negative', () { + expect(() => binomial(-1, 1), throwsAssertionError); + }); + + test('fails if n < k', () { + expect(() => binomial(1, 2), throwsAssertionError); }); - group('factorial', () { - test('fails if negative number', () { - expect(() => factorial(-1), throwsAssertionError); + test('for a specific input gives a correct value', () { + final binomialInputsToExpected = { + Binomial(n: 0, k: 0): 1, + Binomial(n: 1, k: 0): 1, + Binomial(n: 1, k: 1): 1, + Binomial(n: 2, k: 0): 1, + Binomial(n: 2, k: 1): 2, + Binomial(n: 2, k: 2): 1, + Binomial(n: 3, k: 0): 1, + Binomial(n: 3, k: 1): 3, + Binomial(n: 3, k: 2): 3, + Binomial(n: 3, k: 3): 1, + Binomial(n: 4, k: 0): 1, + Binomial(n: 4, k: 1): 4, + Binomial(n: 4, k: 2): 6, + Binomial(n: 4, k: 3): 4, + Binomial(n: 4, k: 4): 1, + Binomial(n: 5, k: 0): 1, + Binomial(n: 5, k: 1): 5, + Binomial(n: 5, k: 2): 10, + Binomial(n: 5, k: 3): 10, + Binomial(n: 5, k: 4): 5, + Binomial(n: 5, k: 5): 1, + Binomial(n: 6, k: 0): 1, + Binomial(n: 6, k: 1): 6, + Binomial(n: 6, k: 2): 15, + Binomial(n: 6, k: 3): 20, + Binomial(n: 6, k: 4): 15, + Binomial(n: 6, k: 5): 6, + Binomial(n: 6, k: 6): 1, + }; + binomialInputsToExpected.forEach((input, value) { + expect(binomial(input.n, input.k), value); }); + }); + }); + + group('factorial', () { + test('fails if negative number', () { + expect(() => factorial(-1), throwsAssertionError); + }); - test('for a specific input gives a correct value', () { - final factorialInputsToExpected = { - 0: 1, - 1: 1, - 2: 2, - 3: 6, - 4: 24, - 5: 120, - 6: 720, - 7: 5040, - 8: 40320, - 9: 362880, - 10: 3628800, - 11: 39916800, - 12: 479001600, - 13: 6227020800, - }; - factorialInputsToExpected.forEach((input, expected) { - expect(factorial(input), expected); - }); + test('for a specific input gives a correct value', () { + final factorialInputsToExpected = { + 0: 1, + 1: 1, + 2: 2, + 3: 6, + 4: 24, + 5: 120, + 6: 720, + 7: 5040, + 8: 40320, + 9: 362880, + 10: 3628800, + 11: 39916800, + 12: 479001600, + 13: 6227020800, + }; + factorialInputsToExpected.forEach((input, expected) { + expect(factorial(input), expected); }); }); }); From 78eb18eb0e14d763fb6aea1e4ebd80e21a1e95a3 Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Wed, 9 Mar 2022 19:33:25 +0100 Subject: [PATCH 26/27] chore: updated package geometry readme file --- packages/geometry/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/geometry/README.md b/packages/geometry/README.md index cbc4cca1..f0841d82 100644 --- a/packages/geometry/README.md +++ b/packages/geometry/README.md @@ -1,9 +1,9 @@ -# maths +# geometry [![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link] [![License: MIT][license_badge]][license_link] -A Very Good Project created by Very Good CLI. +Helper package to calculate points of lines, arcs and curves for the pathways of the ball. [license_badge]: https://img.shields.io/badge/license-MIT-blue.svg [license_link]: https://opensource.org/licenses/MIT From f75d5514cc5cfba51735162c76933b6ec124e6ba Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Wed, 9 Mar 2022 19:47:23 +0100 Subject: [PATCH 27/27] doc: geometry doc refactored --- packages/geometry/lib/src/geometry.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/geometry/lib/src/geometry.dart b/packages/geometry/lib/src/geometry.dart index 8851ed25..dceb4e9e 100644 --- a/packages/geometry/lib/src/geometry.dart +++ b/packages/geometry/lib/src/geometry.dart @@ -3,10 +3,11 @@ import 'package:flame/extensions.dart'; /// Calculates all [Vector2]s of a circumference. /// -/// Circumference is created from a [center] and a [radius] -/// Also semi circumference could be created, specifying its [angle] in radians -/// and the offset start angle [offsetAngle] for this semi circumference. -/// The higher the [precision], the more [Vector2]s will be calculated, +/// A circumference can be achieved by specifying a [center] and a [radius]. +/// In addition, a semi-circle can be achieved by specifying its [angle] and an +/// [offsetAngle] (both in radians). +/// +/// The higher the [precision], the more [Vector2]s will be calculated; /// achieving a more rounded arc. /// /// For more information read: https://en.wikipedia.org/wiki/Trigonometric_functions. @@ -80,6 +81,7 @@ List calculateBezierCurve({ } /// Calculates the binomial coefficient of 'n' and 'k'. +/// /// For more information read: https://en.wikipedia.org/wiki/Binomial_coefficient num binomial(num n, num k) { assert(0 <= k && k <= n, 'k ($k) and n ($n) must be in range 0 <= k <= n'); @@ -92,6 +94,7 @@ num binomial(num n, num k) { } /// Calculate the factorial of 'n'. +/// /// For more information read: https://en.wikipedia.org/wiki/Factorial num factorial(num n) { assert(n >= 0, 'Factorial is not defined for negative number n ($n)');