Compare commits

..

No commits in common. "main" and "vaults" have entirely different histories.
main ... vaults

21 changed files with 81 additions and 701 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,72 +0,0 @@
import 'package:collection/collection.dart';
class Edge<T> {
final double cost;
final T destination;
Edge(this.cost, this.destination);
}
class Result<T> {
final double cost;
final T? predecessor;
final T item;
Result(this.cost, this.predecessor, this.item);
}
Iterable<Result<T>> dijkstra<T>(
T source, Iterable<Edge<T>> Function(T) neighbors) sync* {
var queue = PriorityQueue<Result<T>>((i0, i1) => i0.cost.compareTo(i1.cost));
Set<T> seen = {source};
queue.add(Result(0.0, null, source));
while (queue.isNotEmpty) {
var u = queue.removeFirst();
yield u;
for (var v in neighbors(u.item)) {
if (seen.contains(v.destination)) {
continue;
}
seen.add(v.destination);
queue.add(Result(u.cost + v.cost, u.item, v.destination));
}
}
}
List<T>? dijkstraPath<T>(
T source, T destination, Iterable<Edge<T>> Function(T) neighbors,
{double? maxCost}) {
if (source == destination) {
return [];
}
Map<T, T?> predecessor = {};
for (var r in dijkstra(source, neighbors)) {
predecessor[r.item] = r.predecessor;
if (maxCost != null && r.cost >= maxCost) {
return null;
}
if (r.item == destination) {
break;
}
}
var revPath = [destination];
while (true) {
var pred = predecessor[revPath.last];
if (pred == source) {
revPath.reverseRange(0, revPath.length);
return revPath;
} else if (pred == null) {
throw Exception(
"predecessor should not be null -- that would mean we missed source");
} else {
revPath.add(pred);
}
}
}

View File

@ -17,13 +17,6 @@ class Size {
String toString() {
return "$dx x $dy";
}
@override
bool operator ==(Object other) =>
other is Size && other.dx == dx && other.dy == dy;
@override
int get hashCode => (dx, dy).hashCode;
}
class Offset {
@ -36,13 +29,6 @@ class Offset {
String toString() {
return "@($x, $y)";
}
@override
bool operator ==(Object other) =>
other is Offset && other.x == x && other.y == y;
@override
int get hashCode => (x, y).hashCode;
}
class Rect {
@ -77,15 +63,4 @@ class Rect {
String toString() {
return "@($x0, $y0) $size";
}
@override
bool operator ==(Object other) =>
other is Rect &&
other.x0 == x0 &&
other.y0 == y0 &&
other.dx == dx &&
other.dy == dy;
@override
int get hashCode => (x0, y0, dx, dy).hashCode;
}

View File

@ -1,141 +0,0 @@
// Port of https://www.albertford.com/shadowcasting/
import 'package:dartterm/algorithms/geometry.dart' as geo;
void shadowcast(geo.Offset origin, bool Function(geo.Offset) isBlocking,
Function(geo.Offset) markVisible) {
markVisible(origin);
for (var i = 0; i < 4; i++) {
var quadrant = Quadrant(i, origin.x, origin.y);
void reveal(geo.Offset tile) {
markVisible(quadrant.transform(tile));
}
bool isWall(geo.Offset? tile) {
if (tile == null) {
return false;
}
return isBlocking(quadrant.transform(tile));
}
bool isFloor(geo.Offset? tile) {
if (tile == null) {
return false;
}
return !isBlocking(quadrant.transform(tile));
}
void scan(Row row) {
geo.Offset? prevTile;
for (var tile in row.tiles()) {
if (isWall(tile) || isSymmetric(row, tile)) {
reveal(tile);
}
if (isWall(prevTile) && isFloor(tile)) {
row.startSlope = slope(tile);
}
if (isFloor(prevTile) && isWall(tile)) {
Row nextRow = row.next();
nextRow.endSlope = slope(tile);
scan(nextRow);
}
prevTile = tile;
}
if (isFloor(prevTile)) {
scan(row.next());
}
}
Row firstRow = Row(1, Fraction(-1, 1), Fraction(1, 1));
scan(firstRow);
}
}
class Quadrant {
static const int north = 0;
static const int east = 1;
static const int south = 2;
static const int west = 3;
final int cardinal;
final int ox, oy;
Quadrant(this.cardinal, this.ox, this.oy);
geo.Offset transform(geo.Offset tile) {
var geo.Offset(x: row, y: col) = tile;
switch (cardinal) {
case north:
return geo.Offset(ox + col, oy - row);
case south:
return geo.Offset(ox + col, oy + row);
case east:
return geo.Offset(ox + row, oy + col);
case west:
return geo.Offset(ox - row, oy + col);
default:
throw Exception("Quadrant must be initialized with a real cardinal");
}
}
}
class Row {
int depth;
Fraction startSlope;
Fraction endSlope;
Row(this.depth, this.startSlope, this.endSlope);
Iterable<geo.Offset> tiles() sync* {
var minCol = roundTiesUp(startSlope.scale(depth));
var maxCol = roundTiesDown(endSlope.scale(depth));
for (int col = minCol; col <= maxCol; col++) {
yield geo.Offset(depth, col);
}
}
Row next() {
return Row(depth + 1, startSlope, endSlope);
}
}
class Fraction {
final int numerator;
final int denominator;
Fraction(this.numerator, this.denominator);
Fraction scale(int n) {
return Fraction(numerator * n, denominator);
}
// We're often comparing this to an int or a double, so it's OK
// to have precision loss _so long as we do all divides after all multiplies_
double toDouble() {
return numerator.toDouble() / denominator.toDouble();
}
}
Fraction slope(geo.Offset tile) {
var geo.Offset(x: rowDepth, y: col) = tile;
return Fraction(2 * col - 1, 2 * rowDepth);
}
bool isSymmetric(Row row, geo.Offset tile) {
var geo.Offset(x: rowDepth, y: col) = tile;
return (col >= row.startSlope.scale(rowDepth).toDouble() &&
col <= (row.endSlope.scale(row.depth)).toDouble());
}
int roundTiesUp(Fraction n) {
return (n.toDouble() + 0.5).floor();
}
int roundTiesDown(Fraction n) {
return (n.toDouble() - 0.5).ceil();
}

View File

@ -1,26 +1,11 @@
import 'package:flutter/material.dart';
class Palette {
static const defaultBg = Color(0xFF272D1B);
static const uiBg = Color(0xFF232308);
static const defaultFg = Color(0xFFEEE9D1);
static const demoPlayer = Color(0xFFFEFEF2);
static const defaultBg = Colors.black;
static const defaultFg = Colors.white;
static const demoDoor = Color(0xFF847A4B);
static const demoExit = Color(0xFF847A4B);
static const subtitle = Colors.red;
static const sitemodePlayer = demoPlayer;
static const sitemodeSeenDoor = Color(0xFFFEFD4B);
static const sitemodeUnseenDoor = demoExit;
static const sitemodeSeenWall = defaultFg;
static const sitemodeUnseenWall = demoExit;
static const sitemodeSeenExit = defaultFg;
static const sitemodeUnseenExit = demoExit;
static const sitemodeSeenFloor = uiBg;
static const demoFloorHighlight = Color(0xFF863B6F);
static const demoDoor = Colors.red;
static const demoExit = Colors.red;
}

View File

@ -260,7 +260,6 @@ final List<String> _fromCp437 = [
"\u00a0"
];
final Map<String, Cp437> _toCp437 = {};
final Map<int, Cp437> _toCp437I = {};
void _init() {
if (initialized) {
@ -269,7 +268,6 @@ void _init() {
for (final (i, c) in _fromCp437.indexed) {
_toCp437[c] = i;
_toCp437I[c.runes.first] = i;
}
initialized = true;
@ -293,7 +291,6 @@ Cp437 toCp437Char(String c) {
}
String fromCp437String(List<Cp437> s) {
_init();
var out = "";
for (final c in s) {
out += fromCp437Char(c);
@ -302,10 +299,9 @@ String fromCp437String(List<Cp437> s) {
}
List<Cp437> toCp437String(String s) {
_init();
List<Cp437> out = [];
for (final c in s.runes) {
out.add(_toCp437I[c] ?? toCp437Char("?"));
out.add(c);
}
return out;
}

View File

@ -8,7 +8,7 @@ import 'package:dartterm/skreek.dart';
import 'package:dartterm/terminal.dart';
import 'package:dartterm/world/level.dart';
void generator_test_program() async {
void main() async {
Vaults vaults;
while (true) {
skreek("about to load template");
@ -63,10 +63,6 @@ void generator_test_program() async {
}
}
}
at(output.spawn.x * 2, output.spawn.y * 2)
.fg(Palette.demoPlayer)
.big()
.puts("\u00ff");
inpLoop:
await for (var inp in rawInput()) {
skreek("$inp $seed");
@ -82,3 +78,37 @@ void generator_test_program() async {
}
}
}
/*
void main() async {
var descriptor = "generic";
while (true) {
at(0, 0).clear();
at(0, 0).puts("Hello, bats!");
at(0, 2).fg(Palette.subtitle).small().puts("Beware of the bat!");
at(4, 4)
.bg(Palette.subtitle)
.fg(Palette.defaultBg)
.big()
.highlight()
.act(Act(
isDefault: true,
label: "Strong!",
callback: () async {
log("strong!");
descriptor = "strong";
}))
.act(Act(
label: "Nocturnal!",
callback: () async {
log("nocturnal!");
descriptor = "nocturnal";
}))
.puts("ALTER BAT");
at(4, 8).normal().puts("A $descriptor bat!");
// await zzz(1.0);
await waitMenu();
}
}
*/

View File

@ -1,72 +0,0 @@
import 'dart:math' as math;
import 'package:dartterm/assets.dart';
import 'package:dartterm/game/sitemode/sitemode.dart';
import 'package:dartterm/gen/generator.dart';
import 'package:dartterm/skreek.dart';
import 'package:dartterm/terminal.dart';
import 'package:dartterm/world/level.dart';
void main() async {
var level = await getLevel();
await sitemode(level);
}
/*
void main() async {
var descriptor = "generic";
while (true) {
at(0, 0).clear();
at(0, 0).puts("Hello, bats!");
at(0, 2).fg(Palette.subtitle).small().puts("Beware of the bat!");
at(4, 4)
.bg(Palette.subtitle)
.fg(Palette.defaultBg)
.big()
.highlight()
.act(Act(
isDefault: true,
label: "Strong!",
callback: () async {
log("strong!");
descriptor = "strong";
}))
.act(Act(
label: "Nocturnal!",
callback: () async {
log("nocturnal!");
descriptor = "nocturnal";
}))
.puts("ALTER BAT");
at(4, 8).normal().puts("A $descriptor bat!");
// await zzz(1.0);
await waitMenu();
}
}
*/
Future<Level> getLevel() async {
Vaults vaults;
while (true) {
Vaults? maybeVaults =
getVaultsIfAvailable("assets/images/vaults/house1.png");
if (maybeVaults != null) {
vaults = maybeVaults;
break;
}
await zzz(0.1);
}
return Generator(math.Random(0), vaults).generateLevel(Requirement(
16,
32,
16,
18,
DirectionSet({
Direction.up,
Direction.down,
Direction.left,
Direction.right,
})));
}

View File

@ -1,127 +0,0 @@
part of 'sitemode.dart';
// We render each thing as a 2x2 block.
// We want the player's cell to be
// _actually centered_, and the terminal is an even number of cells across
// So, shifting the camera an extra cell to the north and then
// drawing one extra tile offscreen? That should accomplish it!
const cameraViewWidth = Terminal.width ~/ 2 + 1;
const cameraViewHeight = Terminal.height ~/ 2 + 1;
const cameraMargin =
4; // how far the camera is from the ideal position before it pans
const cameraTileOffset = geo.Offset(-1, -1);
extension CameraParts on SiteMode {
void cameraInit() {
camera = _cameraIdeal();
}
void cameraMaintain() {
var ideal = _cameraIdeal();
while (camera.x < ideal.x - cameraMargin) {
camera = geo.Offset(camera.x + 1, camera.y);
}
while (camera.x > ideal.x + cameraMargin) {
camera = geo.Offset(camera.x - 1, camera.y);
}
while (camera.y < ideal.y - cameraMargin) {
camera = geo.Offset(camera.x, camera.y + 1);
}
while (camera.y > ideal.y + cameraMargin) {
camera = geo.Offset(camera.x, camera.y - 1);
}
}
geo.Offset _cameraIdeal() {
return geo.Offset(playerPosition.x - cameraViewWidth ~/ 2 - 1,
playerPosition.y - cameraViewHeight ~/ 2 - 1);
}
void cameraDraw() {
// Draw the world!
// Work in columns, top to bottom, which should facilitate our fake 3D effects
for (var cx = 0; cx < cameraViewWidth; cx++) {
for (var cy = 0; cy < cameraViewHeight; cy++) {
var tx = cameraTileOffset.x + cx * 2;
var ty = cameraTileOffset.y + cy * 2;
cameraDrawFloor(tx, ty, camera.x + cx, camera.y + cy);
}
}
for (var cx = 0; cx < cameraViewWidth; cx++) {
for (var cy = 0; cy < cameraViewHeight; cy++) {
var tx = cameraTileOffset.x + cx * 2;
var ty = cameraTileOffset.y + cy * 2;
cameraDrawCell(tx, ty, camera.x + cx, camera.y + cy);
}
}
}
void cameraDrawFloor(int tx, int ty, int cx, int cy) {
var cxy = geo.Offset(cx, cy);
if (!fovVisible.contains(cxy)) {
return;
}
var tile = level.tiles.get(cx, cy);
at(tx, ty).bg(Palette.sitemodeSeenFloor).puts(" ");
}
void cameraDrawCell(int tx, int ty, int cx, int cy) {
var cursorAbove = at(tx, ty);
var cursorBelow = at(tx, ty + 2);
LevelTile? tile;
// TODO: Fade tiles when loaded from memory
// TODO: Darken live floors
bool seenLive;
var cxy = geo.Offset(cx, cy);
if (fovVisible.contains(cxy)) {
tile = level.tiles.get(cx, cy);
seenLive = true;
} else if (fovMemory.contains(cxy)) {
tile = level.tiles.get(cx, cy);
seenLive = false;
} else {
return;
}
if (fovMovable.contains(geo.Offset(cx, cy))) {
cursorAbove
.highlight()
.act(Act(label: "Move", callback: () => playerMoveTo(cx, cy)));
}
var colorWall = Palette.sitemodeSeenWall;
var colorWallInner = Palette.sitemodeSeenWall;
var colorExit = Palette.sitemodeSeenExit;
var colorDoor = Palette.sitemodeSeenDoor;
if (cy >= playerPosition.y) {
colorWallInner = Palette.sitemodeUnseenWall; // we wouldn't see it anyway
}
if (!seenLive) {
colorWallInner = colorWall = Palette.sitemodeUnseenWall;
colorExit = Palette.sitemodeUnseenExit;
colorDoor = Palette.sitemodeUnseenDoor;
}
switch (tile) {
case null:
case LevelTile.floor:
case LevelTile.openDoor:
cursorAbove.touch(2);
case LevelTile.exit:
cursorAbove.big().fg(colorExit).puts("X");
case LevelTile.wall:
cursorAbove.fg(colorWall).puts("██");
cursorBelow.small().fg(colorWallInner).puts("░░");
case LevelTile.closedDoor:
cursorAbove.big().fg(colorDoor).puts("+");
}
var cursorContent = at(tx, ty);
if (geo.Offset(cx, cy) == playerPosition) {
cursorContent.fg(Palette.sitemodePlayer).big().putc(0xff);
}
}
}

View File

@ -1,81 +0,0 @@
part of 'sitemode.dart';
const double fovMaxMovementCost = 10.0;
const double fovMaxMemoryDistance = 80.0;
extension FOVParts on SiteMode {
void fovMaintain() {
fovVisible = {};
shadowcast(playerPosition, _fovIsBlocking, (xy) => fovVisible.add(xy));
var oldFovMemory = fovMemory;
fovMemory = {};
oldFovMemory.addAll(fovVisible);
for (var xy in oldFovMemory) {
if (_fovMemorablyClose(xy)) {
fovMemory.add(xy);
}
}
fovMovable = {};
for (var r in dijkstra<geo.Offset>(playerPosition, _fovDijkstraNeighbors)) {
if (r.cost > fovMaxMovementCost) {
break;
}
fovMovable.add(r.item);
}
}
bool _fovMemorablyClose(geo.Offset other) {
var dx = (other.x - playerPosition.x).toDouble();
var dy = (other.y - playerPosition.y).toDouble();
return math.sqrt(dx * dx + dy * dy) < fovMaxMemoryDistance;
}
bool _fovKnown(geo.Offset other) {
return fovVisible.contains(other) || fovMemory.contains(other);
}
bool _fovIsBlocking(geo.Offset xy) {
switch (level.tiles.get(xy.x, xy.y)) {
case null:
return true;
case LevelTile.exit:
return true;
case LevelTile.openDoor:
case LevelTile.floor:
return false;
case LevelTile.wall:
return true;
case LevelTile.closedDoor:
return true;
}
}
Iterable<Edge<geo.Offset>> _fovDijkstraNeighbors(geo.Offset xy) sync* {
var tile = level.tiles.get(xy.x, xy.y);
if (tile == LevelTile.exit || tile == LevelTile.closedDoor) {
// can't go anywhere after this
return;
}
for (var (dx, dy) in [(-1, 0), (1, 0), (0, -1), (0, 1)]) {
var xy2 = geo.Offset(xy.x + dx, xy.y + dy);
// Only return visible or remembered tiles
if (!_fovKnown(xy2)) {
continue;
}
var tile = level.tiles.get(xy2.x, xy2.y);
if (tile == LevelTile.wall) {
continue;
}
// for now, because exiting isn't implemented
if (tile == LevelTile.exit) {
continue;
}
yield Edge(1.0, xy2);
}
}
}

View File

@ -1,33 +0,0 @@
part of 'sitemode.dart';
extension PlayerParts on SiteMode {
void playerMaintain() {
playerTookAutomatedAction = false;
var geo.Offset(:x, :y) = playerPosition;
var tile = level.tiles.get(x, y);
if (tile == LevelTile.closedDoor) {
level.tiles.set(x, y, LevelTile.openDoor);
}
if (playerIntendedPath.isNotEmpty) {
var nextPosition = playerIntendedPath.removeAt(0);
playerPosition = nextPosition;
playerTookAutomatedAction = true;
}
}
// support the UI command here
Future<void> playerMoveTo(int cx, int cy) async {
var ipath = dijkstraPath(
geo.Offset(playerPosition.x, playerPosition.y),
geo.Offset(cx, cy),
_fovDijkstraNeighbors,
);
if (ipath == null) {
return; // don't move, I guess
}
playerIntendedPath.addAll(ipath);
}
}

View File

@ -1,65 +0,0 @@
import 'package:dartterm/algorithms/dijkstra.dart';
import 'package:dartterm/algorithms/geometry.dart' as geo;
import 'package:dartterm/algorithms/shadowcasting.dart';
import 'package:dartterm/colors.dart';
import 'package:dartterm/skreek.dart';
import 'package:dartterm/terminal.dart';
import 'package:dartterm/world/level.dart';
import 'dart:math' as math;
part 'camera.dart';
part 'player.dart';
part 'fov.dart';
Future<void> sitemode(Level level) async {
await SiteMode(level).start();
}
class SiteMode {
Level level;
late geo.Offset playerPosition;
bool playerTookAutomatedAction = false;
List<geo.Offset> playerIntendedPath = [];
late geo.Offset camera;
late Set<geo.Offset> fovVisible;
late Set<geo.Offset> fovMovable;
Set<geo.Offset> fovMemory = {};
SiteMode(this.level) {
playerPosition = level.spawn;
init();
maintain();
}
void init() {
cameraInit();
}
void maintain() {
playerMaintain();
fovMaintain();
cameraMaintain();
}
void draw() {
clear();
cameraDraw();
}
Future<void> start() async {
while (true) {
maintain();
draw();
// take automated actions, otherwise receive input
if (playerTookAutomatedAction) {
await zzz(0.1);
} else {
await waitMenu();
}
}
}
}

View File

@ -1,34 +0,0 @@
/*
void main() async {
var descriptor = "generic";
while (true) {
at(0, 0).clear();
at(0, 0).puts("Hello, bats!");
at(0, 2).fg(Palette.subtitle).small().puts("Beware of the bat!");
at(4, 4)
.bg(Palette.subtitle)
.fg(Palette.defaultBg)
.big()
.highlight()
.act(Act(
isDefault: true,
label: "Strong!",
callback: () async {
log("strong!");
descriptor = "strong";
}))
.act(Act(
label: "Nocturnal!",
callback: () async {
log("nocturnal!");
descriptor = "nocturnal";
}))
.puts("ALTER BAT");
at(4, 8).normal().puts("A $descriptor bat!");
// await zzz(1.0);
await waitMenu();
}
}
*/

View File

@ -12,7 +12,6 @@ part 'direction_set.dart';
part 'orientation.dart';
part 'requirement.dart';
part 'vault.dart';
part 'vault_tile.dart';
part 'vaults.dart';
const vaultTries = 30;

View File

@ -39,3 +39,37 @@ class Vault {
return Vault(tiles.rotateLeft(), smooth.rotateLeft());
}
}
VaultTile mergeVaultTile(VaultTile bottom, VaultTile top) {
if (bottom == VaultTile.wall && top == VaultTile.archpronewall) {
return VaultTile.wall;
}
if (bottom == VaultTile.wall && top == VaultTile.archwall) {
return VaultTile.wall;
}
if (bottom == VaultTile.archwall && top == VaultTile.archpronewall) {
return VaultTile.archwall;
}
return top;
}
LevelTile flattenVaultTile(VaultTile vt) {
switch (vt) {
case VaultTile.meta0:
case VaultTile.defaultwall:
case VaultTile.archpronewall:
case VaultTile.archwall:
case VaultTile.wall:
return LevelTile.wall;
case VaultTile.exit:
return LevelTile.exit;
case VaultTile.door:
return LevelTile.closedDoor;
case VaultTile.doorpronefloor:
case VaultTile.bspfloor:
case VaultTile.floor:
return LevelTile.floor;
}
}

View File

@ -28,6 +28,7 @@ class Vaults {
}
static Vault loadVault(Region r, Bitmap<VaultTile?> b) {
skreek("Loading vault: $r");
var tiles = [
for (var y = r.rect.y0; y < r.rect.y1; y++)
for (var x = r.rect.x0; x < r.rect.x1; x++)

View File

@ -1,6 +1,6 @@
import 'package:dartterm/colors.dart';
import 'package:dartterm/input.dart' as input;
import 'package:dartterm/game/game.dart' as game;
import 'package:dartterm/game.dart' as game;
import 'package:dartterm/terminal.dart' as terminal;
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
@ -17,7 +17,6 @@ class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'DARTTERM',
theme: ThemeData(
useMaterial3: true, scaffoldBackgroundColor: Palette.defaultBg),

View File

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:developer';
import 'dart:math' as math;
import 'dart:ui' as ui;
@ -71,6 +72,7 @@ class Terminal {
}
void _notifyInput(Input i) {
skreek("Input: $i $_lastSeenMouse");
_inputSink.add(i);
}

View File

@ -18,20 +18,12 @@ class Cursor {
t._tiles = [for (var i = 0; i < nTiles; i += 1) Tile()];
}
void putc(int? c) {
void putc(int c) {
for (var dx = 0; dx < font.cellWidth; dx += 1) {
for (var dy = 0; dy < font.cellHeight; dy += 1) {
var i = t._fromXY(x + dx, y + dy);
if (i != null) {
// you can pass null and in that case a character-sized block of
// screen is automatically UI-affected, but not drawn
if (c == null) {
t._tiles[i].highlightGroup = highlightGroup;
t._tiles[i].actions.addAll(_acts);
continue;
}
final (sourceCx, sourceCy) = (
(c % font.nCellsW) * font.cellWidth + dx,
(c ~/ font.nCellsW) * font.cellHeight + dy
@ -56,13 +48,6 @@ class Cursor {
}
}
void touch(int n) {
for (var i = 0; i < n; i++) {
putc(null);
x += font.cellWidth;
}
}
void puts(String s) {
var startX = x;
for (final c in toCp437String(s)) {

View File

@ -34,7 +34,7 @@ packages:
source: hosted
version: "1.1.1"
collection:
dependency: "direct main"
dependency: transitive
description:
name: collection
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687

View File

@ -28,7 +28,6 @@ environment:
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
collection: ^1.17.2
flutter:
sdk: flutter