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:
committed by
Andrew Brogdon
parent
42f2dce01b
commit
3fe927cb29
113
web/slide_puzzle/lib/src/core/body.dart
Normal file
113
web/slide_puzzle/lib/src/core/body.dart
Normal 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;
|
||||
}
|
||||
12
web/slide_puzzle/lib/src/core/point_int.dart
Normal file
12
web/slide_puzzle/lib/src/core/point_int.dart
Normal 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);
|
||||
}
|
||||
275
web/slide_puzzle/lib/src/core/puzzle.dart
Normal file
275
web/slide_puzzle/lib/src/core/puzzle.dart
Normal 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.');
|
||||
}
|
||||
}
|
||||
205
web/slide_puzzle/lib/src/core/puzzle_animator.dart
Normal file
205
web/slide_puzzle/lib/src/core/puzzle_animator.dart
Normal 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());
|
||||
}
|
||||
}
|
||||
63
web/slide_puzzle/lib/src/core/puzzle_simple.dart
Normal file
63
web/slide_puzzle/lib/src/core/puzzle_simple.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
188
web/slide_puzzle/lib/src/core/puzzle_smart.dart
Normal file
188
web/slide_puzzle/lib/src/core/puzzle_smart.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
58
web/slide_puzzle/lib/src/core/util.dart
Normal file
58
web/slide_puzzle/lib/src/core/util.dart
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user