1
0
mirror of https://github.com/flutter/samples.git synced 2026-03-30 16:23:23 +00:00

Add flutter_web samples (#75)

This commit is contained in:
Kevin Moore
2019-05-07 13:32:08 -07:00
committed by Andrew Brogdon
parent 42f2dce01b
commit 3fe927cb29
697 changed files with 241026 additions and 0 deletions

View File

@@ -0,0 +1,113 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' show Point;
const zeroPoint = Point<double>(0, 0);
const _epsilon = 0.0001;
/// Represents a point object with a location and velocity.
class Body {
Point<double> _velocity;
Point<double> _location;
Point<double> get velocity => _velocity;
Point<double> get location => _location;
Body({Point<double> location = zeroPoint, Point<double> velocity = zeroPoint})
: assert(location.magnitude.isFinite),
_location = location,
assert(velocity.magnitude.isFinite),
_velocity = velocity;
factory Body.raw(double x, double y, double vx, double vy) =>
Body(location: Point(x, y), velocity: Point(vx, vy));
Body clone() => Body(location: _location, velocity: _velocity);
/// Add the velocity specified in [delta] to `this`.
void kick(Point<double> delta) {
assert(delta.magnitude.isFinite);
_velocity = delta;
}
/// [drag] must be greater than or equal to zero. It defines the percent of
/// the previous velocity that is lost every second.
bool animate(double seconds,
{Point<double> force = zeroPoint,
double drag = 0,
double maxVelocity,
Point<double> snapTo}) {
assert(seconds.isFinite && seconds > 0,
'milliseconds must be finite and > 0 (was $seconds)');
force ??= zeroPoint;
assert(force.x.isFinite && force.y.isFinite, 'force must be finite');
drag ??= 0;
assert(drag.isFinite && drag >= 0, 'drag must be finiate and >= 0');
maxVelocity ??= double.infinity;
assert(maxVelocity > 0, 'maxVelocity must be null or > 0');
final dragVelocity = _velocity * (1 - drag * seconds);
if (_sameDirection(_velocity, dragVelocity)) {
assert(dragVelocity.magnitude <= _velocity.magnitude,
'Huh? $dragVelocity $_velocity');
_velocity = dragVelocity;
} else {
_velocity = zeroPoint;
}
// apply force to velocity
_velocity += force * seconds;
// apply terminal velocity
if (_velocity.magnitude > maxVelocity) {
_velocity = _unitPoint(_velocity) * maxVelocity;
}
// update location
final locationDelta = _velocity * seconds;
if (locationDelta.magnitude > _epsilon ||
(force.magnitude * seconds) > _epsilon) {
_location += locationDelta;
return true;
} else {
if (snapTo != null && (_location.distanceTo(snapTo) < _epsilon * 2)) {
_location = snapTo;
}
_velocity = zeroPoint;
return false;
}
}
@override
String toString() =>
'Body @(${_location.x},${_location.y}) ↕(${_velocity.x},${_velocity.y})';
@override
bool operator ==(Object other) =>
other is Body &&
other._location == _location &&
other._velocity == _velocity;
// Since this is a mutable class, a constant value is returned for `hashCode`
// This ensures values don't get lost in a Hashing data structure.
// Note: this means you shouldn't use this type in most Map/Set impls.
@override
int get hashCode => 199;
}
Point<double> _unitPoint(Point<double> source) {
final result = source * (1 / source.magnitude);
return Point(result.x.isNaN ? 0 : result.x, result.y.isNaN ? 0 : result.y);
}
bool _sameDirection(Point a, Point b) {
return a.x.sign == b.x.sign && a.y.sign == b.y.sign;
}

View File

@@ -0,0 +1,12 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
class Point extends math.Point<int> {
Point(int x, int y) : super(x, y);
@override
Point operator +(math.Point<int> other) => Point(x + other.x, y + other.y);
}

View File

@@ -0,0 +1,275 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:collection';
import 'dart:convert';
import 'dart:math' show Random, max;
import 'dart:typed_data';
import 'point_int.dart';
import 'util.dart';
part 'puzzle_simple.dart';
part 'puzzle_smart.dart';
final _rnd = Random();
final _spacesRegexp = RegExp(' +');
abstract class Puzzle {
int get width;
int get length;
int operator [](int index);
int indexOf(int value);
List<int> get _intView;
List<int> _copyData();
Puzzle _newWithValues(List<int> values);
Puzzle clone();
int get height => length ~/ width;
Puzzle._();
factory Puzzle._raw(int width, List<int> source) {
if (source.length <= 16) {
return _PuzzleSmart(width, source);
}
return _PuzzleSimple(width, source);
}
factory Puzzle.raw(int width, List<int> source) {
requireArgument(width >= 3, 'width', 'Must be at least 3.');
requireArgument(source.length >= 6, 'source', 'Must be at least 6 items');
_validate(source);
return Puzzle._raw(width, source);
}
factory Puzzle(int width, int height) =>
Puzzle.raw(width, _randomList(width, height));
factory Puzzle.parse(String input) {
final rows = LineSplitter.split(input).map((line) {
final splits = line.trim().split(_spacesRegexp);
return splits.map(int.parse).toList();
}).toList();
return Puzzle.raw(rows.first.length, rows.expand((row) => row).toList());
}
int valueAt(int x, int y) {
final i = _getIndex(x, y);
return this[i];
}
int get tileCount => length - 1;
bool isCorrectPosition(int cellValue) => cellValue == this[cellValue];
bool get solvable => isSolvable(width, _intView);
Puzzle reset({List<int> source}) {
final data = (source == null)
? _randomizeList(width, _intView)
: Uint8List.fromList(source);
if (data.length != length) {
throw ArgumentError.value(source, 'source', 'Cannot change the size!');
}
_validate(data);
if (!isSolvable(width, data)) {
throw ArgumentError.value(source, 'source', 'Not a solvable puzzle.');
}
return _newWithValues(data);
}
int get incorrectTiles {
var count = tileCount;
for (var i = 0; i < tileCount; i++) {
if (isCorrectPosition(i)) {
count--;
}
}
return count;
}
Point openPosition() => coordinatesOf(tileCount);
/// A measure of how close the puzzle is to being solved.
///
/// The sum of all of the distances squared `(x + y)^2 ` each tile has to move
/// to be in the correct position.
///
/// `0` - you've won!
int get fitness {
var value = 0;
for (var i = 0; i < tileCount; i++) {
if (!isCorrectPosition(i)) {
final correctColumn = i % width;
final correctRow = i ~/ width;
final index = indexOf(i);
final x = index % width;
final y = index ~/ width;
final delta = (correctColumn - x).abs() + (correctRow - y).abs();
value += delta * delta;
}
}
return value * incorrectTiles;
}
Puzzle clickRandom({bool vertical}) {
final clickable = clickableValues(vertical: vertical).toList();
return clickValue(clickable[_rnd.nextInt(clickable.length)]);
}
Iterable<Puzzle> allMovable() =>
(clickableValues()..shuffle(_rnd)).map(_clickValue);
List<int> clickableValues({bool vertical}) {
final open = openPosition();
final doRow = vertical == null || vertical == false;
final doColumn = vertical == null || vertical;
final values =
Uint8List((doRow ? (width - 1) : 0) + (doColumn ? (height - 1) : 0));
var index = 0;
if (doRow) {
for (var x = 0; x < width; x++) {
if (x != open.x) {
values[index++] = valueAt(x, open.y);
}
}
}
if (doColumn) {
for (var y = 0; y < height; y++) {
if (y != open.y) {
values[index++] = valueAt(open.x, y);
}
}
}
return values;
}
bool _movable(int tileValue) {
if (tileValue == tileCount) {
return false;
}
final target = coordinatesOf(tileValue);
final lastCoord = openPosition();
if (lastCoord.x != target.x && lastCoord.y != target.y) {
return false;
}
return true;
}
Puzzle clickValue(int tileValue) {
if (!_movable(tileValue)) {
return null;
}
return _clickValue(tileValue);
}
Puzzle _clickValue(int tileValue) {
assert(_movable(tileValue));
final target = coordinatesOf(tileValue);
final newStore = _copyData();
_shift(newStore, target.x, target.y);
return _newWithValues(newStore);
}
void _shift(List<int> source, int targetX, int targetY) {
final lastCoord = openPosition();
final deltaX = lastCoord.x - targetX;
final deltaY = lastCoord.y - targetY;
if ((deltaX.abs() + deltaY.abs()) > 1) {
final shiftPointX = targetX + deltaX.sign;
final shiftPointY = targetY + deltaY.sign;
_shift(source, shiftPointX, shiftPointY);
_staticSwap(source, targetX, targetY, shiftPointX, shiftPointY);
} else {
_staticSwap(source, lastCoord.x, lastCoord.y, targetX, targetY);
}
}
void _staticSwap(List<int> source, int ax, int ay, int bx, int by) {
final aIndex = ax + ay * width;
final aValue = source[aIndex];
final bIndex = bx + by * width;
source[aIndex] = source[bIndex];
source[bIndex] = aValue;
}
Point coordinatesOf(int value) {
final index = indexOf(value);
final x = index % width;
final y = index ~/ width;
assert(_getIndex(x, y) == index);
return Point(x, y);
}
int _getIndex(int x, int y) {
assert(x >= 0 && x < width);
assert(y >= 0 && y < height);
return x + y * width;
}
@override
String toString() => _toString();
String _toString() {
final grid = List<List<String>>.generate(
height,
(row) => List<String>.generate(
width, (col) => valueAt(col, row).toString()));
final longestLength =
grid.expand((r) => r).fold(0, (int l, cell) => max(l, cell.length));
return grid
.map((r) => r.map((v) => v.padLeft(longestLength)).join(' '))
.join('\n');
}
}
Uint8List _randomList(int width, int height) => _randomizeList(
width, List<int>.generate(width * height, (i) => i, growable: false));
Uint8List _randomizeList(int width, List<int> existing) {
final copy = Uint8List.fromList(existing);
do {
copy.shuffle(_rnd);
} while (!isSolvable(width, copy) ||
copy.any((v) => copy[v] == v || copy[v] == existing[v]));
return copy;
}
void _validate(List<int> source) {
for (var i = 0; i < source.length; i++) {
requireArgument(
source.contains(i),
'source',
'Must contain each number from 0 to `length - 1` '
'once and only once.');
}
}

View File

@@ -0,0 +1,205 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:math' show Point, Random;
import 'body.dart';
import 'puzzle.dart';
enum PuzzleEvent { click, reset, noop }
abstract class PuzzleProxy {
int get width;
int get height;
int get length;
bool get solved;
void reset();
void clickOrShake(int tileValue);
int get tileCount;
int get clickCount;
int get incorrectTiles;
Point<double> location(int index);
bool isCorrectPosition(int value);
}
class PuzzleAnimator implements PuzzleProxy {
final _rnd = Random();
final List<Body> _locations;
final _controller = StreamController<PuzzleEvent>();
bool _nextRandomVertical = true;
Puzzle _puzzle;
int _clickCount = 0;
bool _stable;
bool get stable => _stable;
@override
bool get solved => _puzzle.incorrectTiles == 0;
@override
int get width => _puzzle.width;
@override
int get height => _puzzle.height;
@override
int get length => _puzzle.length;
@override
int get tileCount => _puzzle.tileCount;
@override
int get incorrectTiles => _puzzle.incorrectTiles;
@override
int get clickCount => _clickCount;
@override
void reset() => _resetCore();
Stream<PuzzleEvent> get onEvent => _controller.stream;
@override
bool isCorrectPosition(int value) => _puzzle.isCorrectPosition(value);
@override
Point<double> location(int index) => _locations[index].location;
int _lastBadClick;
int _badClickCount = 0;
PuzzleAnimator(int width, int height) : this._(Puzzle(width, height));
PuzzleAnimator._(this._puzzle)
: _locations = List.generate(_puzzle.length, (i) {
return Body.raw(
(_puzzle.width - 1.0) / 2, (_puzzle.height - 1.0) / 2, 0, 0);
});
void playRandom() {
if (_puzzle.fitness == 0) {
return;
}
_puzzle = _puzzle.clickRandom(vertical: _nextRandomVertical);
_nextRandomVertical = !_nextRandomVertical;
_clickCount++;
_controller.add(PuzzleEvent.click);
}
@override
void clickOrShake(int tileValue) {
if (solved) {
_controller.add(PuzzleEvent.noop);
_shake(tileValue);
_lastBadClick = null;
_badClickCount = 0;
return;
}
_controller.add(PuzzleEvent.click);
if (!_clickValue(tileValue)) {
_shake(tileValue);
// This is logic to allow a user to skip to the end useful for testing
// click on 5 un-movable tiles in a row, but not the same tile twice
// in a row
if (tileValue != _lastBadClick) {
_badClickCount++;
if (_badClickCount >= 5) {
// Do the reset!
final newValues = List.generate(_puzzle.length, (i) {
if (i == _puzzle.tileCount) {
return _puzzle.tileCount - 1;
} else if (i == (_puzzle.tileCount - 1)) {
return _puzzle.tileCount;
}
return i;
});
_resetCore(source: newValues);
_clickCount = 999;
}
} else {
_badClickCount = 0;
}
_lastBadClick = tileValue;
} else {
_lastBadClick = null;
_badClickCount = 0;
}
}
void _resetCore({List<int> source}) {
_puzzle = _puzzle.reset(source: source);
_clickCount = 0;
_lastBadClick = null;
_badClickCount = 0;
_controller.add(PuzzleEvent.reset);
}
bool _clickValue(int value) {
final newPuzzle = _puzzle.clickValue(value);
if (newPuzzle == null) {
return false;
} else {
_clickCount++;
_puzzle = newPuzzle;
return true;
}
}
void _shake(int tileValue) {
Point<double> deltaDouble;
if (solved) {
deltaDouble = Point(_rnd.nextDouble() - 0.5, _rnd.nextDouble() - 0.5);
} else {
final delta = _puzzle.openPosition() - _puzzle.coordinatesOf(tileValue);
deltaDouble = Point(delta.x.toDouble(), delta.y.toDouble());
}
deltaDouble *= 0.5 / deltaDouble.magnitude;
_locations[tileValue].kick(deltaDouble);
}
void update(Duration timeDelta) {
assert(!timeDelta.isNegative);
assert(timeDelta != Duration.zero);
var animationSeconds = timeDelta.inMilliseconds / 60.0;
if (animationSeconds == 0) {
animationSeconds = 0.1;
}
assert(animationSeconds > 0);
_stable = true;
for (var i = 0; i < _puzzle.length; i++) {
final target = _target(i);
final body = _locations[i];
_stable = !body.animate(animationSeconds,
force: target - body.location,
drag: .9,
maxVelocity: 1.0,
snapTo: target) &&
_stable;
}
}
Point<double> _target(int item) {
final target = _puzzle.coordinatesOf(item);
return Point(target.x.toDouble(), target.y.toDouble());
}
}

View File

@@ -0,0 +1,63 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of 'puzzle.dart';
class _PuzzleSimple extends Puzzle {
@override
final int width;
final Uint8List _source;
_PuzzleSimple(this.width, List<int> source)
: _source = UnmodifiableUint8ListView(Uint8List.fromList(source)),
super._();
@override
int indexOf(int value) => _source.indexOf(value);
@override
List<int> get _intView => _source;
@override
List<int> _copyData() => Uint8List.fromList(_source);
@override
Puzzle _newWithValues(List<int> values) => _PuzzleSimple(width, values);
@override
int operator [](int index) => _source[index];
@override
Puzzle clone() => _PuzzleSimple(width, _source);
@override
int get length => _source.length;
@override
bool operator ==(other) {
if (other is Puzzle &&
other.width == width &&
other.length == _source.length) {
for (var i = 0; i < _source.length; i++) {
if (other[i] != _source[i]) {
return false;
}
}
return true;
}
return false;
}
@override
int get hashCode {
var v = 0;
for (var i = 0; i < _source.length; i++) {
v = (v << 2) + _source[i];
}
v += v << 3;
v ^= v >> 11;
v += v << 15;
return v;
}
}

View File

@@ -0,0 +1,188 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of 'puzzle.dart';
mixin _SliceListMixin on ListMixin<int> {
static const _bitsPerValue = 4;
static const _maxShift = _valuesPerCell - 1;
static const _bitsPerCell = 32;
static const _valuesPerCell = _bitsPerCell ~/ _bitsPerValue;
static const _valueMask = (1 << _bitsPerValue) - 1;
Uint32List get _data;
int _cellValue(int index) => _data[index ~/ _valuesPerCell];
@override
int operator [](int index) {
/*
final bigValue = _data[index ~/ _valuesPerCell];
final shiftedValue =
bigValue >> (_maxShift - (index % _valuesPerCell)) * _bitsPerValue;
final flattenedValue = shiftedValue & _valueMask;
return flattenedValue;
*/
return (_cellValue(index) >>
(_maxShift - (index % _valuesPerCell)) * _bitsPerValue) &
_valueMask;
}
@override
int indexOf(Object value, [int start]) {
for (var i = 0; i < _data.length; i++) {
final cellValue = _data[i];
for (var j = 0; j < _valuesPerCell; j++) {
final option =
(cellValue >> (_maxShift - j) * _bitsPerValue) & _valueMask;
if (value == option) {
final k = i * _valuesPerCell + j;
if (k < length && (start == null || k >= start)) {
return k;
}
}
}
}
return -1;
}
@override
set length(int value) => throw UnsupportedError('immutable, yo!');
}
class _SliceList with ListMixin<int>, _SliceListMixin {
@override
final Uint32List _data;
@override
final int length;
_SliceList(this.length, this._data);
@override
void operator []=(int index, int value) {
var cellValue = _cellValue(index);
// clear out the target bits in `cellValue`
final sharedShift =
(_SliceListMixin._maxShift - (index % _SliceListMixin._valuesPerCell)) *
_SliceListMixin._bitsPerValue;
final wipeout = _SliceListMixin._valueMask << sharedShift;
cellValue &= ~wipeout;
final newShiftedValue = value << sharedShift;
cellValue |= newShiftedValue;
_data[index ~/ _SliceListMixin._valuesPerCell] = cellValue;
}
}
class _PuzzleSmart extends Puzzle with ListMixin<int>, _SliceListMixin {
static const _bitsPerValue = 4;
static const _maxShift = _valuesPerCell - 1;
static const _bitsPerCell = 32;
static const _valuesPerCell = _bitsPerCell ~/ _bitsPerValue;
static const _valueMask = (1 << _bitsPerValue) - 1;
@override
final Uint32List _data;
@override
final int width;
@override
final int length;
int _fitnessCache;
@override
int get fitness => _fitnessCache ??= super.fitness;
_PuzzleSmart(this.width, List<int> source)
: length = source.length,
_data = _create(source),
super._();
@override
void operator []=(int index, int value) =>
throw UnsupportedError('immutable, yo!');
@override
List<int> get _intView => this;
@override
List<int> _copyData() => _SliceList(length, Uint32List.fromList(_data));
@override
Puzzle _newWithValues(List<int> values) => _PuzzleSmart(width, values);
@override
Puzzle clone() => _PuzzleSmart(width, this);
@override
String toString() => _toString();
@override
bool operator ==(other) {
if (other is _PuzzleSmart &&
other.width == width &&
other._data.length == _data.length) {
for (var i = 0; i < _data.length; i++) {
if (other._data[i] != _data[i]) {
return false;
}
}
return true;
}
if (other is Puzzle && other.width == width && other.length == length) {
for (var i = 0; i < length; i++) {
if (other[i] != this[i]) {
return false;
}
}
return true;
}
return false;
}
@override
int get hashCode {
var v = 0;
for (var i = 0; i < _data.length; i++) {
v = (v << 2) + _data[i];
}
v += v << 3;
v ^= v >> 11;
v += v << 15;
return v;
}
static Uint32List _create(List<int> source) {
if (source is _SliceList) {
return source._data;
}
final data = Uint32List((source.length / _valuesPerCell).ceil());
for (var i = 0; i < data.length; i++) {
var value = 0;
for (var j = 0; j < _valuesPerCell; j++) {
final k = i * _valuesPerCell + j;
if (k < source.length) {
// shift the value over 4 bits for item 0, 3 for item 2, etc
final sourceValue = source[k] << ((_maxShift - j) * _bitsPerValue);
value |= sourceValue;
}
}
data[i] = value;
}
return data;
}
}

View File

@@ -0,0 +1,58 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
void requireArgument(bool truth, String argName, [String message]) {
if (!truth) {
if (message == null || message.isEmpty) {
message = 'value was invalid';
}
throw ArgumentError('`$argName` - $message');
}
}
void requireArgumentNotNull(argument, String argName) {
if (argument == null) {
throw ArgumentError.notNull(argName);
}
}
// Logic from
// https://www.cs.bham.ac.uk/~mdr/teaching/modules04/java2/TilesSolvability.html
// Used with gratitude!
bool isSolvable(int width, List<int> list) {
final height = list.length ~/ width;
assert(width * height == list.length);
final inversions = _countInversions(list);
if (width.isOdd) {
return inversions.isEven;
}
final blankRow = list.indexOf(list.length - 1) ~/ width;
if ((height - blankRow).isEven) {
return inversions.isOdd;
} else {
return inversions.isEven;
}
}
int _countInversions(List<int> items) {
final tileCount = items.length - 1;
var score = 0;
for (var i = 0; i < items.length; i++) {
final value = items[i];
if (value == tileCount) {
continue;
}
for (var j = i + 1; j < items.length; j++) {
final v = items[j];
if (v != tileCount && v < value) {
score++;
}
}
}
return score;
}