[
  {
    "path": ".flutter-plugins-dependencies",
    "content": "{\"info\":\"This is a generated file; do not edit or check into version control.\",\"plugins\":{\"ios\":[{\"name\":\"audio_session\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/audio_session-0.1.6+1/\",\"dependencies\":[]},{\"name\":\"just_audio\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/just_audio-0.9.20/\",\"dependencies\":[\"audio_session\"]},{\"name\":\"path_provider_ios\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_ios-2.0.8/\",\"dependencies\":[]},{\"name\":\"shared_preferences_ios\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_ios-2.1.0/\",\"dependencies\":[]}],\"android\":[{\"name\":\"audio_session\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/audio_session-0.1.6+1/\",\"dependencies\":[]},{\"name\":\"just_audio\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/just_audio-0.9.20/\",\"dependencies\":[\"audio_session\"]},{\"name\":\"path_provider_android\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_android-2.0.12/\",\"dependencies\":[]},{\"name\":\"shared_preferences_android\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_android-2.0.11/\",\"dependencies\":[]}],\"macos\":[{\"name\":\"audio_session\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/audio_session-0.1.6+1/\",\"dependencies\":[]},{\"name\":\"just_audio\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/just_audio-0.9.20/\",\"dependencies\":[\"audio_session\"]},{\"name\":\"path_provider_macos\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-2.0.5/\",\"dependencies\":[]},{\"name\":\"shared_preferences_macos\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_macos-2.0.3/\",\"dependencies\":[]}],\"linux\":[{\"name\":\"path_provider_linux\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-2.1.5/\",\"dependencies\":[]},{\"name\":\"shared_preferences_linux\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_linux-2.1.0/\",\"dependencies\":[\"path_provider_linux\"]}],\"windows\":[{\"name\":\"path_provider_windows\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-2.0.5/\",\"dependencies\":[]},{\"name\":\"shared_preferences_windows\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_windows-2.1.0/\",\"dependencies\":[\"path_provider_windows\"]}],\"web\":[{\"name\":\"audio_session\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/audio_session-0.1.6+1/\",\"dependencies\":[]},{\"name\":\"just_audio_web\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/just_audio_web-0.4.7/\",\"dependencies\":[]},{\"name\":\"shared_preferences_web\",\"path\":\"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_web-2.0.3/\",\"dependencies\":[]}]},\"dependencyGraph\":[{\"name\":\"audio_session\",\"dependencies\":[]},{\"name\":\"just_audio\",\"dependencies\":[\"just_audio_web\",\"audio_session\",\"path_provider\"]},{\"name\":\"just_audio_web\",\"dependencies\":[]},{\"name\":\"path_provider\",\"dependencies\":[\"path_provider_android\",\"path_provider_ios\",\"path_provider_linux\",\"path_provider_macos\",\"path_provider_windows\"]},{\"name\":\"path_provider_android\",\"dependencies\":[]},{\"name\":\"path_provider_ios\",\"dependencies\":[]},{\"name\":\"path_provider_linux\",\"dependencies\":[]},{\"name\":\"path_provider_macos\",\"dependencies\":[]},{\"name\":\"path_provider_windows\",\"dependencies\":[]},{\"name\":\"shared_preferences\",\"dependencies\":[\"shared_preferences_android\",\"shared_preferences_ios\",\"shared_preferences_linux\",\"shared_preferences_macos\",\"shared_preferences_web\",\"shared_preferences_windows\"]},{\"name\":\"shared_preferences_android\",\"dependencies\":[]},{\"name\":\"shared_preferences_ios\",\"dependencies\":[]},{\"name\":\"shared_preferences_linux\",\"dependencies\":[\"path_provider_linux\"]},{\"name\":\"shared_preferences_macos\",\"dependencies\":[]},{\"name\":\"shared_preferences_web\",\"dependencies\":[]},{\"name\":\"shared_preferences_windows\",\"dependencies\":[\"path_provider_windows\"]}],\"date_created\":\"2022-04-24 14:11:37.796311\",\"version\":\"2.10.4\"}"
  },
  {
    "path": ".gitignore",
    "content": "# Miscellaneous\n*.class\n*.lock\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.buildlog/\n.history\n.svn/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# Visual Studio Code related\n.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n.dart_tool/\n.flutter-plugins\n.packages\n.pub-cache/\n.pub/\nbuild/\n\n# Android related\n**/android/**/gradle-wrapper.jar\n**/android/.gradle\n**/android/captures/\n**/android/gradlew\n**/android/gradlew.bat\n**/android/local.properties\n**/android/**/GeneratedPluginRegistrant.java\n\n# iOS/XCode related\n**/ios/**/*.mode1v3\n**/ios/**/*.mode2v3\n**/ios/**/*.moved-aside\n**/ios/**/*.pbxuser\n**/ios/**/*.perspectivev3\n**/ios/**/*sync/\n**/ios/**/.sconsign.dblite\n**/ios/**/.tags*\n**/ios/**/.vagrant/\n**/ios/**/DerivedData/\n**/ios/**/Icon?\n**/ios/**/Pods/\n**/ios/**/.symlinks/\n**/ios/**/profile\n**/ios/**/xcuserdata\n**/ios/.generated/\n**/ios/Flutter/App.framework\n**/ios/Flutter/Flutter.framework\n**/ios/Flutter/Generated.xcconfig\n**/ios/Flutter/app.flx\n**/ios/Flutter/app.zip\n**/ios/Flutter/flutter_assets/\n**/ios/ServiceDefinitions.json\n**/ios/Runner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!**/ios/**/default.mode1v3\n!**/ios/**/default.mode2v3\n!**/ios/**/default.pbxuser\n!**/ios/**/default.perspectivev3\n!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages\n"
  },
  {
    "path": ".metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled and should not be manually edited.\n\nversion:\n  revision: 5391447fae6209bb21a89e6a5a6583cac1af9b4b\n  channel: stable\n\nproject_type: app\n"
  },
  {
    "path": "LICENSE",
    "content": "// Copyright 2022 The SpriteWidget Authors. All rights reserved.\n// Copyright 2014 The Chromium Authors. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//    * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//    * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//    * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# SpaceBlast\n\nA demo game for [SpriteWidget](https://spritewidget.com). Showcases most features of SpriteWidget.\n\n## Getting Started\n\nTry it out online [here](https://spritewidget.com/spaceblast) or clone it and run it like you run any Flutter app:\n\n    flutter create .\n    flutter run\n"
  },
  {
    "path": "analysis_options.yaml",
    "content": "# This file configures the analyzer, which statically analyzes Dart code to\n# check for errors, warnings, and lints.\n#\n# The issues identified by the analyzer are surfaced in the UI of Dart-enabled\n# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be\n# invoked from the command line by running `flutter analyze`.\n\n# The following line activates a set of recommended lints for Flutter apps,\n# packages, and plugins designed to encourage good coding practices.\ninclude: package:flutter_lints/flutter.yaml\n\nlinter:\n  # The lint rules applied to this project can be customized in the\n  # section below to disable rules from the `package:flutter_lints/flutter.yaml`\n  # included above or to enable additional rules. A list of all available lints\n  # and their documentation is published at\n  # https://dart-lang.github.io/linter/lints/index.html.\n  #\n  # Instead of disabling a lint rule for the entire project in the\n  # section below, it can also be suppressed for a single line of code\n  # or a specific dart file by using the `// ignore: name_of_lint` and\n  # `// ignore_for_file: name_of_lint` syntax on the line or in the file\n  # producing the lint.\n  rules:\n    # avoid_print: false  # Uncomment to disable the `avoid_print` rule\n    # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule\n\n# Additional information about this file can be found at\n# https://dart.dev/guides/language/analysis-options\n"
  },
  {
    "path": "assets/game_ui.json",
    "content": "{\"frames\": [\n\n{\n\t\"filename\": \"badge.png\",\n\t\"frame\": {\"x\":1948,\"y\":619,\"w\":62,\"h\":62},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":62,\"h\":62},\n\t\"sourceSize\": {\"w\":62,\"h\":62},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"bar_shield.png\",\n\t\"frame\": {\"x\":769,\"y\":2,\"w\":412,\"h\":100},\n\t\"rotated\": true,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":412,\"h\":100},\n\t\"sourceSize\": {\"w\":412,\"h\":100},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"bar_shield_fill.png\",\n\t\"frame\": {\"x\":1965,\"y\":297,\"w\":320,\"h\":72},\n\t\"rotated\": true,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":320,\"h\":72},\n\t\"sourceSize\": {\"w\":320,\"h\":72},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"btn_close.png\",\n\t\"frame\": {\"x\":1788,\"y\":297,\"w\":175,\"h\":175},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":175,\"h\":175},\n\t\"sourceSize\": {\"w\":175,\"h\":175},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"btn_laser_upgrade.png\",\n\t\"frame\": {\"x\":2,\"y\":2,\"w\":652,\"h\":290},\n\t\"rotated\": true,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":652,\"h\":290},\n\t\"sourceSize\": {\"w\":652,\"h\":290},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"btn_level_down.png\",\n\t\"frame\": {\"x\":1032,\"y\":150,\"w\":145,\"h\":145},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":145,\"h\":145},\n\t\"sourceSize\": {\"w\":145,\"h\":145},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"btn_level_up.png\",\n\t\"frame\": {\"x\":1032,\"y\":2,\"w\":146,\"h\":145},\n\t\"rotated\": true,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":146,\"h\":145},\n\t\"sourceSize\": {\"w\":146,\"h\":145},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"btn_play.png\",\n\t\"frame\": {\"x\":1179,\"y\":2,\"w\":860,\"h\":293},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":860,\"h\":293},\n\t\"sourceSize\": {\"w\":860,\"h\":293},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"btn_powerup_0.png\",\n\t\"frame\": {\"x\":360,\"y\":520,\"w\":259,\"h\":271},\n\t\"rotated\": true,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":259,\"h\":271},\n\t\"sourceSize\": {\"w\":259,\"h\":271},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"btn_powerup_1.png\",\n\t\"frame\": {\"x\":633,\"y\":520,\"w\":259,\"h\":271},\n\t\"rotated\": true,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":259,\"h\":271},\n\t\"sourceSize\": {\"w\":259,\"h\":271},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"btn_powerup_2.png\",\n\t\"frame\": {\"x\":906,\"y\":518,\"w\":259,\"h\":271},\n\t\"rotated\": true,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":259,\"h\":271},\n\t\"sourceSize\": {\"w\":259,\"h\":271},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"btn_powerup_3.png\",\n\t\"frame\": {\"x\":1179,\"y\":516,\"w\":259,\"h\":271},\n\t\"rotated\": true,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":259,\"h\":271},\n\t\"sourceSize\": {\"w\":259,\"h\":271},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"coinboard.png\",\n\t\"frame\": {\"x\":769,\"y\":416,\"w\":261,\"h\":100},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":261,\"h\":100},\n\t\"sourceSize\": {\"w\":261,\"h\":100},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"icn_crystal.png\",\n\t\"frame\": {\"x\":1452,\"y\":516,\"w\":48,\"h\":81},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":48,\"h\":81},\n\t\"sourceSize\": {\"w\":48,\"h\":81},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"icn_medal.png\",\n\t\"frame\": {\"x\":294,\"y\":2,\"w\":473,\"h\":516},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":473,\"h\":516},\n\t\"sourceSize\": {\"w\":473,\"h\":516},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"level_display.png\",\n\t\"frame\": {\"x\":1505,\"y\":476,\"w\":294,\"h\":293},\n\t\"rotated\": true,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":294,\"h\":293},\n\t\"sourceSize\": {\"w\":294,\"h\":293},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"level_display_0.png\",\n\t\"frame\": {\"x\":2,\"y\":656,\"w\":107,\"h\":177},\n\t\"rotated\": true,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":94,\"y\":60,\"w\":107,\"h\":177},\n\t\"sourceSize\": {\"w\":294,\"h\":293},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"level_display_1.png\",\n\t\"frame\": {\"x\":1800,\"y\":619,\"w\":45,\"h\":160},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":156,\"y\":71,\"w\":45,\"h\":160},\n\t\"sourceSize\": {\"w\":294,\"h\":293},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"level_display_2.png\",\n\t\"frame\": {\"x\":181,\"y\":656,\"w\":107,\"h\":177},\n\t\"rotated\": true,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":94,\"y\":60,\"w\":107,\"h\":177},\n\t\"sourceSize\": {\"w\":294,\"h\":293},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"level_display_3.png\",\n\t\"frame\": {\"x\":1687,\"y\":297,\"w\":99,\"h\":177},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":102,\"y\":60,\"w\":99,\"h\":177},\n\t\"sourceSize\": {\"w\":294,\"h\":293},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"level_display_4.png\",\n\t\"frame\": {\"x\":1800,\"y\":474,\"w\":107,\"h\":161},\n\t\"rotated\": true,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":94,\"y\":68,\"w\":107,\"h\":161},\n\t\"sourceSize\": {\"w\":294,\"h\":293},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"level_display_5.png\",\n\t\"frame\": {\"x\":1251,\"y\":297,\"w\":107,\"h\":177},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":94,\"y\":60,\"w\":107,\"h\":177},\n\t\"sourceSize\": {\"w\":294,\"h\":293},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"level_display_6.png\",\n\t\"frame\": {\"x\":1360,\"y\":297,\"w\":107,\"h\":177},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":94,\"y\":60,\"w\":107,\"h\":177},\n\t\"sourceSize\": {\"w\":294,\"h\":293},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"level_display_7.png\",\n\t\"frame\": {\"x\":1847,\"y\":583,\"w\":99,\"h\":169},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":102,\"y\":60,\"w\":99,\"h\":169},\n\t\"sourceSize\": {\"w\":294,\"h\":293},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"level_display_8.png\",\n\t\"frame\": {\"x\":1469,\"y\":297,\"w\":107,\"h\":177},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":94,\"y\":60,\"w\":107,\"h\":177},\n\t\"sourceSize\": {\"w\":294,\"h\":293},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"level_display_9.png\",\n\t\"frame\": {\"x\":1578,\"y\":297,\"w\":107,\"h\":177},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":94,\"y\":60,\"w\":107,\"h\":177},\n\t\"sourceSize\": {\"w\":294,\"h\":293},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"number_0.png\",\n\t\"frame\": {\"x\":294,\"y\":520,\"w\":38,\"h\":63},\n\t\"rotated\": true,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":23,\"y\":22,\"w\":38,\"h\":63},\n\t\"sourceSize\": {\"w\":84,\"h\":107},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"number_1.png\",\n\t\"frame\": {\"x\":1847,\"y\":754,\"w\":16,\"h\":57},\n\t\"rotated\": true,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":45,\"y\":25,\"w\":16,\"h\":57},\n\t\"sourceSize\": {\"w\":84,\"h\":107},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"number_2.png\",\n\t\"frame\": {\"x\":294,\"y\":560,\"w\":38,\"h\":63},\n\t\"rotated\": true,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":23,\"y\":22,\"w\":38,\"h\":63},\n\t\"sourceSize\": {\"w\":84,\"h\":107},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"number_3.png\",\n\t\"frame\": {\"x\":1452,\"y\":599,\"w\":35,\"h\":63},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":26,\"y\":22,\"w\":35,\"h\":63},\n\t\"sourceSize\": {\"w\":84,\"h\":107},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"number_4.png\",\n\t\"frame\": {\"x\":1446,\"y\":476,\"w\":38,\"h\":57},\n\t\"rotated\": true,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":23,\"y\":25,\"w\":38,\"h\":57},\n\t\"sourceSize\": {\"w\":84,\"h\":107},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"number_5.png\",\n\t\"frame\": {\"x\":294,\"y\":600,\"w\":38,\"h\":63},\n\t\"rotated\": true,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":23,\"y\":22,\"w\":38,\"h\":63},\n\t\"sourceSize\": {\"w\":84,\"h\":107},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"number_6.png\",\n\t\"frame\": {\"x\":1251,\"y\":476,\"w\":38,\"h\":63},\n\t\"rotated\": true,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":23,\"y\":22,\"w\":38,\"h\":63},\n\t\"sourceSize\": {\"w\":84,\"h\":107},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"number_7.png\",\n\t\"frame\": {\"x\":1452,\"y\":664,\"w\":35,\"h\":60},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":26,\"y\":22,\"w\":35,\"h\":60},\n\t\"sourceSize\": {\"w\":84,\"h\":107},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"number_8.png\",\n\t\"frame\": {\"x\":1316,\"y\":476,\"w\":38,\"h\":63},\n\t\"rotated\": true,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":23,\"y\":22,\"w\":38,\"h\":63},\n\t\"sourceSize\": {\"w\":84,\"h\":107},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"number_9.png\",\n\t\"frame\": {\"x\":1381,\"y\":476,\"w\":38,\"h\":63},\n\t\"rotated\": true,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":23,\"y\":22,\"w\":38,\"h\":63},\n\t\"sourceSize\": {\"w\":84,\"h\":107},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"player_icon.png\",\n\t\"frame\": {\"x\":1032,\"y\":297,\"w\":217,\"h\":217},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":217,\"h\":217},\n\t\"sourceSize\": {\"w\":217,\"h\":217},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"scoreboard.png\",\n\t\"frame\": {\"x\":871,\"y\":2,\"w\":362,\"h\":98},\n\t\"rotated\": true,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":362,\"h\":98},\n\t\"sourceSize\": {\"w\":362,\"h\":98},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n}],\n\"meta\": {\n\t\"app\": \"http://www.codeandweb.com/texturepacker\",\n\t\"version\": \"1.0\",\n\t\"image\": \"game_ui.png\",\n\t\"format\": \"RGBA8888\",\n\t\"size\": {\"w\":2041,\"h\":781},\n\t\"scale\": \"1\",\n\t\"smartupdate\": \"$TexturePacker:SmartUpdate:8cb2693ee295d1eeae391efd7de6f156:d356cc9506480f7ffb79175a765b156f:10ac111e32c27e51f4e8444dbb39eff6$\"\n}\n}\n"
  },
  {
    "path": "assets/sprites.json",
    "content": "{\"frames\": [\n\n{\n\t\"filename\": \"asteroid_big_0.nrm.png\",\n\t\"frame\": {\"x\":245,\"y\":799,\"w\":200,\"h\":188},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":200,\"h\":188},\n\t\"sourceSize\": {\"w\":200,\"h\":188},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"asteroid_big_0.png\",\n\t\"frame\": {\"x\":706,\"y\":402,\"w\":200,\"h\":167},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":21,\"w\":200,\"h\":167},\n\t\"sourceSize\": {\"w\":200,\"h\":188},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"asteroid_big_1.nrm.png\",\n\t\"frame\": {\"x\":305,\"y\":631,\"w\":204,\"h\":166},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":204,\"h\":166},\n\t\"sourceSize\": {\"w\":204,\"h\":166},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"asteroid_big_1.png\",\n\t\"frame\": {\"x\":706,\"y\":571,\"w\":204,\"h\":166},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":204,\"h\":166},\n\t\"sourceSize\": {\"w\":204,\"h\":166},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"asteroid_big_2.nrm.png\",\n\t\"frame\": {\"x\":912,\"y\":571,\"w\":194,\"h\":165},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":194,\"h\":165},\n\t\"sourceSize\": {\"w\":194,\"h\":165},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"asteroid_big_2.png\",\n\t\"frame\": {\"x\":912,\"y\":738,\"w\":194,\"h\":165},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":2,\"w\":194,\"h\":165},\n\t\"sourceSize\": {\"w\":194,\"h\":167},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"asteroid_small_0.nrm.png\",\n\t\"frame\": {\"x\":701,\"y\":881,\"w\":102,\"h\":84},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":102,\"h\":84},\n\t\"sourceSize\": {\"w\":102,\"h\":84},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"asteroid_small_0.png\",\n\t\"frame\": {\"x\":805,\"y\":867,\"w\":102,\"h\":84},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":102,\"h\":84},\n\t\"sourceSize\": {\"w\":102,\"h\":84},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"asteroid_small_1.nrm.png\",\n\t\"frame\": {\"x\":1108,\"y\":555,\"w\":96,\"h\":102},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":96,\"h\":102},\n\t\"sourceSize\": {\"w\":96,\"h\":102},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"asteroid_small_1.png\",\n\t\"frame\": {\"x\":1108,\"y\":659,\"w\":96,\"h\":102},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":96,\"h\":102},\n\t\"sourceSize\": {\"w\":96,\"h\":106},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"asteroid_small_2.nrm.png\",\n\t\"frame\": {\"x\":909,\"y\":905,\"w\":109,\"h\":84},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":109,\"h\":84},\n\t\"sourceSize\": {\"w\":109,\"h\":84},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"asteroid_small_2.png\",\n\t\"frame\": {\"x\":1020,\"y\":905,\"w\":109,\"h\":84},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":109,\"h\":84},\n\t\"sourceSize\": {\"w\":109,\"h\":84},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"coin.png\",\n\t\"frame\": {\"x\":805,\"y\":953,\"w\":42,\"h\":47},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":6,\"y\":4,\"w\":42,\"h\":47},\n\t\"sourceSize\": {\"w\":54,\"h\":56},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"crystal_0.png\",\n\t\"frame\": {\"x\":886,\"y\":2,\"w\":229,\"h\":239},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":4,\"y\":3,\"w\":229,\"h\":239},\n\t\"sourceSize\": {\"w\":256,\"h\":256},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"crystal_1.png\",\n\t\"frame\": {\"x\":2,\"y\":769,\"w\":241,\"h\":232},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":241,\"h\":232},\n\t\"sourceSize\": {\"w\":256,\"h\":256},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"crystal_2.png\",\n\t\"frame\": {\"x\":305,\"y\":413,\"w\":208,\"h\":216},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":26,\"y\":8,\"w\":208,\"h\":216},\n\t\"sourceSize\": {\"w\":256,\"h\":256},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"enemy_boss_0.png\",\n\t\"frame\": {\"x\":2,\"y\":2,\"w\":365,\"h\":389},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":45,\"y\":34,\"w\":365,\"h\":389},\n\t\"sourceSize\": {\"w\":456,\"h\":457},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"enemy_boss_1.png\",\n\t\"frame\": {\"x\":2,\"y\":393,\"w\":301,\"h\":374},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":77,\"y\":42,\"w\":301,\"h\":374},\n\t\"sourceSize\": {\"w\":456,\"h\":457},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"enemy_boss_2.png\",\n\t\"frame\": {\"x\":369,\"y\":2,\"w\":257,\"h\":409},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":125,\"y\":23,\"w\":257,\"h\":409},\n\t\"sourceSize\": {\"w\":456,\"h\":457},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"enemy_destroyer_0.png\",\n\t\"frame\": {\"x\":855,\"y\":260,\"w\":167,\"h\":140},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":10,\"y\":31,\"w\":167,\"h\":140},\n\t\"sourceSize\": {\"w\":188,\"h\":188},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"enemy_destroyer_1.png\",\n\t\"frame\": {\"x\":586,\"y\":739,\"w\":196,\"h\":140},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":24,\"y\":52,\"w\":196,\"h\":140},\n\t\"sourceSize\": {\"w\":244,\"h\":244},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"enemy_destroyer_2.png\",\n\t\"frame\": {\"x\":628,\"y\":260,\"w\":225,\"h\":140},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":9,\"y\":52,\"w\":225,\"h\":140},\n\t\"sourceSize\": {\"w\":244,\"h\":244},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"enemy_scout_0.png\",\n\t\"frame\": {\"x\":784,\"y\":739,\"w\":107,\"h\":126},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":11,\"y\":1,\"w\":107,\"h\":126},\n\t\"sourceSize\": {\"w\":129,\"h\":129},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"enemy_scout_1.png\",\n\t\"frame\": {\"x\":1024,\"y\":243,\"w\":156,\"h\":154},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":9,\"y\":15,\"w\":156,\"h\":154},\n\t\"sourceSize\": {\"w\":185,\"h\":185},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"enemy_scout_2.png\",\n\t\"frame\": {\"x\":1047,\"y\":399,\"w\":157,\"h\":154},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":5,\"y\":15,\"w\":157,\"h\":154},\n\t\"sourceSize\": {\"w\":185,\"h\":185},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"explosion_flare.png\",\n\t\"frame\": {\"x\":1117,\"y\":2,\"w\":56,\"h\":209},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":4,\"y\":0,\"w\":56,\"h\":209},\n\t\"sourceSize\": {\"w\":64,\"h\":256},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"explosion_particle.png\",\n\t\"frame\": {\"x\":511,\"y\":737,\"w\":36,\"h\":60},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":14,\"y\":1,\"w\":36,\"h\":60},\n\t\"sourceSize\": {\"w\":64,\"h\":64},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"explosion_ring.png\",\n\t\"frame\": {\"x\":628,\"y\":2,\"w\":256,\"h\":256},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":256,\"h\":256},\n\t\"sourceSize\": {\"w\":256,\"h\":256},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"fire_particle.png\",\n\t\"frame\": {\"x\":630,\"y\":668,\"w\":55,\"h\":55},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":4,\"y\":4,\"w\":55,\"h\":55},\n\t\"sourceSize\": {\"w\":64,\"h\":64},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"laser.png\",\n\t\"frame\": {\"x\":1131,\"y\":901,\"w\":37,\"h\":76},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":37,\"h\":76},\n\t\"sourceSize\": {\"w\":37,\"h\":76},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"powerup.png\",\n\t\"frame\": {\"x\":586,\"y\":881,\"w\":113,\"h\":113},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":71,\"y\":72,\"w\":113,\"h\":113},\n\t\"sourceSize\": {\"w\":256,\"h\":256},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"powerup_0.png\",\n\t\"frame\": {\"x\":1108,\"y\":763,\"w\":64,\"h\":68},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":96,\"y\":95,\"w\":64,\"h\":68},\n\t\"sourceSize\": {\"w\":256,\"h\":256},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"powerup_1.png\",\n\t\"frame\": {\"x\":575,\"y\":668,\"w\":53,\"h\":67},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":101,\"y\":93,\"w\":53,\"h\":67},\n\t\"sourceSize\": {\"w\":256,\"h\":256},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"powerup_2.png\",\n\t\"frame\": {\"x\":1108,\"y\":833,\"w\":63,\"h\":66},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":96,\"y\":96,\"w\":63,\"h\":66},\n\t\"sourceSize\": {\"w\":256,\"h\":256},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"powerup_3.png\",\n\t\"frame\": {\"x\":643,\"y\":604,\"w\":57,\"h\":62},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":100,\"y\":95,\"w\":57,\"h\":62},\n\t\"sourceSize\": {\"w\":256,\"h\":256},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"shield.png\",\n\t\"frame\": {\"x\":515,\"y\":413,\"w\":189,\"h\":189},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":189,\"h\":189},\n\t\"sourceSize\": {\"w\":189,\"h\":189},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"ship.nrm.png\",\n\t\"frame\": {\"x\":447,\"y\":799,\"w\":137,\"h\":167},\n\t\"rotated\": false,\n\t\"trimmed\": false,\n\t\"spriteSourceSize\": {\"x\":0,\"y\":0,\"w\":137,\"h\":167},\n\t\"sourceSize\": {\"w\":137,\"h\":167},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"ship.png\",\n\t\"frame\": {\"x\":908,\"y\":402,\"w\":137,\"h\":167},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":25,\"y\":10,\"w\":137,\"h\":167},\n\t\"sourceSize\": {\"w\":188,\"h\":188},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"star_0.png\",\n\t\"frame\": {\"x\":515,\"y\":604,\"w\":62,\"h\":62},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":1,\"y\":1,\"w\":62,\"h\":62},\n\t\"sourceSize\": {\"w\":64,\"h\":64},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"star_1.png\",\n\t\"frame\": {\"x\":579,\"y\":604,\"w\":62,\"h\":62},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":1,\"y\":1,\"w\":62,\"h\":62},\n\t\"sourceSize\": {\"w\":64,\"h\":64},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n},\n{\n\t\"filename\": \"star_2.png\",\n\t\"frame\": {\"x\":511,\"y\":668,\"w\":62,\"h\":62},\n\t\"rotated\": false,\n\t\"trimmed\": true,\n\t\"spriteSourceSize\": {\"x\":1,\"y\":1,\"w\":62,\"h\":62},\n\t\"sourceSize\": {\"w\":64,\"h\":64},\n\t\"pivot\": {\"x\":0.5,\"y\":0.5}\n}],\n\"meta\": {\n\t\"app\": \"http://www.codeandweb.com/texturepacker\",\n\t\"version\": \"1.0\",\n\t\"image\": \"sprites.png\",\n\t\"format\": \"RGBA8888\",\n\t\"size\": {\"w\":1206,\"h\":1003},\n\t\"scale\": \"1\",\n\t\"smartupdate\": \"$TexturePacker:SmartUpdate:b5fb5d5db8ad960a7927226c3156595b:fd6909c3527bc05911568aa5fd053d0d:1eabdf11f75e3a4fe3147baf7b5be24b$\"\n}\n}\n"
  },
  {
    "path": "build_web",
    "content": "#!/bin/sh\n\nflutter build web --web-renderer canvaskit --base-href \"/spaceblast/\"\ncp -r build/web/ ../spritewidget_web/docs/spaceblast/\n"
  },
  {
    "path": "fonts/OFL.txt",
    "content": "Copyright (c) 2009, Matt McInerney (matt@pixelspread.com),\r\nwith Reserved Font Name Orbitron.\r\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\r\nThis license is copied below, and is also available with a FAQ at:\r\nhttp://scripts.sil.org/OFL\r\n\r\n\r\n-----------------------------------------------------------\r\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\r\n-----------------------------------------------------------\r\n\r\nPREAMBLE\r\nThe goals of the Open Font License (OFL) are to stimulate worldwide\r\ndevelopment of collaborative font projects, to support the font creation\r\nefforts of academic and linguistic communities, and to provide a free and\r\nopen framework in which fonts may be shared and improved in partnership\r\nwith others.\r\n\r\nThe OFL allows the licensed fonts to be used, studied, modified and\r\nredistributed freely as long as they are not sold by themselves. The\r\nfonts, including any derivative works, can be bundled, embedded, \r\nredistributed and/or sold with any software provided that any reserved\r\nnames are not used by derivative works. The fonts and derivatives,\r\nhowever, cannot be released under any other type of license. The\r\nrequirement for fonts to remain under this license does not apply\r\nto any document created using the fonts or their derivatives.\r\n\r\nDEFINITIONS\r\n\"Font Software\" refers to the set of files released by the Copyright\r\nHolder(s) under this license and clearly marked as such. This may\r\ninclude source files, build scripts and documentation.\r\n\r\n\"Reserved Font Name\" refers to any names specified as such after the\r\ncopyright statement(s).\r\n\r\n\"Original Version\" refers to the collection of Font Software components as\r\ndistributed by the Copyright Holder(s).\r\n\r\n\"Modified Version\" refers to any derivative made by adding to, deleting,\r\nor substituting -- in part or in whole -- any of the components of the\r\nOriginal Version, by changing formats or by porting the Font Software to a\r\nnew environment.\r\n\r\n\"Author\" refers to any designer, engineer, programmer, technical\r\nwriter or other person who contributed to the Font Software.\r\n\r\nPERMISSION & CONDITIONS\r\nPermission is hereby granted, free of charge, to any person obtaining\r\na copy of the Font Software, to use, study, copy, merge, embed, modify,\r\nredistribute, and sell modified and unmodified copies of the Font\r\nSoftware, subject to the following conditions:\r\n\r\n1) Neither the Font Software nor any of its individual components,\r\nin Original or Modified Versions, may be sold by itself.\r\n\r\n2) Original or Modified Versions of the Font Software may be bundled,\r\nredistributed and/or sold with any software, provided that each copy\r\ncontains the above copyright notice and this license. These can be\r\nincluded either as stand-alone text files, human-readable headers or\r\nin the appropriate machine-readable metadata fields within text or\r\nbinary files as long as those fields can be easily viewed by the user.\r\n\r\n3) No Modified Version of the Font Software may use the Reserved Font\r\nName(s) unless explicit written permission is granted by the corresponding\r\nCopyright Holder. This restriction only applies to the primary font name as\r\npresented to the users.\r\n\r\n4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\r\nSoftware shall not be used to promote, endorse or advertise any\r\nModified Version, except to acknowledge the contribution(s) of the\r\nCopyright Holder(s) and the Author(s) or with their explicit written\r\npermission.\r\n\r\n5) The Font Software, modified or unmodified, in part or in whole,\r\nmust be distributed entirely under this license, and must not be\r\ndistributed under any other license. The requirement for fonts to\r\nremain under this license does not apply to any document created\r\nusing the Font Software.\r\n\r\nTERMINATION\r\nThis license becomes null and void if any of the above conditions are\r\nnot met.\r\n\r\nDISCLAIMER\r\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\r\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\r\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\r\nOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\r\nCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\r\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\r\nOTHER DEALINGS IN THE FONT SOFTWARE.\r\n"
  },
  {
    "path": "lib/coordinate_system.dart",
    "content": "part of game;\n\nclass CoordinateSystem extends SingleChildRenderObjectWidget {\n  const CoordinateSystem({\n    Key? key,\n    required this.systemSize,\n    this.systemType = CoordinateSystemType.fixedWidth,\n    required Widget child,\n  }) : super(key: key, child: child);\n\n  final Size systemSize;\n  final CoordinateSystemType systemType;\n\n  @override\n  RenderCoordinateSystem createRenderObject(BuildContext context) {\n    return RenderCoordinateSystem(\n      systemSize: systemSize,\n      systemType: systemType,\n    );\n  }\n\n  @override\n  void updateRenderObject(\n    BuildContext context,\n    RenderCoordinateSystem renderObject,\n  ) {\n    renderObject.systemSize = systemSize;\n    renderObject.systemType = systemType;\n  }\n}\n"
  },
  {
    "path": "lib/custom_actions.dart",
    "content": "part of game;\n\ntypedef PointSetterCallback = void Function(Offset value);\n\nclass ActionCircularMove extends MotionInterval {\n  ActionCircularMove(\n    this.setter,\n    this.center,\n    this.radius,\n    this.startAngle,\n    this.clockWise,\n    double duration,\n  ) : super(duration);\n\n  final PointSetterCallback setter;\n  final Offset center;\n  final double radius;\n  final double startAngle;\n  final bool clockWise;\n\n  @override\n  void update(double t) {\n    if (!clockWise) t = -t;\n    double rad = radians(startAngle + t * 360.0);\n    Offset offset = Offset(math.cos(rad) * radius, math.sin(rad) * radius);\n    Offset pos = center + offset;\n    setter(pos);\n  }\n}\n\nclass ActionOscillate extends MotionInterval {\n  ActionOscillate(this.setter, this.center, this.radius, double duration)\n      : super(duration);\n\n  final PointSetterCallback setter;\n  final Offset center;\n  final double radius;\n\n  @override\n  void update(double t) {\n    double rad = radians(t * 360.0);\n    Offset offset = Offset(math.sin(rad) * radius, 0.0);\n    setter(center + offset);\n  }\n}\n"
  },
  {
    "path": "lib/explosions.dart",
    "content": "part of game;\n\nclass Explosion extends Node {\n  Explosion() {\n    zPosition = 10.0;\n  }\n}\n\nclass ExplosionBig extends Explosion {\n  ExplosionBig(SpriteSheet sheet) {\n    // Add particles\n    ParticleSystem particlesDebris = ParticleSystem(\n      texture: sheet[\"explosion_particle.png\"]!,\n      rotateToMovement: true,\n      startRotation: 90.0,\n      startRotationVar: 0.0,\n      endRotation: 90.0,\n      startSize: 0.3,\n      startSizeVar: 0.1,\n      endSize: 0.3,\n      endSizeVar: 0.1,\n      numParticlesToEmit: 25,\n      emissionRate: 1000.0,\n      greenVar: 127,\n      redVar: 127,\n      life: 0.75,\n      lifeVar: 0.5,\n    );\n    particlesDebris.zPosition = 1010.0;\n    addChild(particlesDebris);\n\n    ParticleSystem particlesFire = ParticleSystem(\n      texture: sheet[\"fire_particle.png\"]!,\n      colorSequence: ColorSequence(\n        colors: [\n          const Color(0xffffff33),\n          const Color(0xffff3333),\n          const Color(0x00ff3333)\n        ],\n        stops: [\n          0.0,\n          0.5,\n          1.0,\n        ],\n      ),\n      numParticlesToEmit: 25,\n      emissionRate: 1000.0,\n      startSize: 0.5,\n      startSizeVar: 0.1,\n      endSize: 0.5,\n      endSizeVar: 0.1,\n      posVar: const Offset(10.0, 10.0),\n      speed: 10.0,\n      speedVar: 5.0,\n      life: 0.75,\n      lifeVar: 0.5,\n    );\n    particlesFire.zPosition = 1011.0;\n    addChild(particlesFire);\n\n    // Add ring\n    Sprite spriteRing = Sprite(texture: sheet[\"explosion_ring.png\"]!);\n    spriteRing.blendMode = ui.BlendMode.plus;\n    addChild(spriteRing);\n\n    Motion scale = MotionTween<double>(\n      setter: (a) => spriteRing.scale = a,\n      start: 0.2,\n      end: 1.0,\n      duration: 0.75,\n    );\n    Motion scaleAndRemove = MotionSequence(\n      motions: <Motion>[scale, MotionRemoveNode(node: spriteRing)],\n    );\n    Motion fade = MotionTween<double>(\n      setter: (a) => spriteRing.opacity = a,\n      start: 1.0,\n      end: 0.0,\n      duration: 0.75,\n    );\n    motions.run(scaleAndRemove);\n    motions.run(fade);\n\n    // Add streaks\n    for (int i = 0; i < 5; i++) {\n      Sprite spriteFlare = Sprite(texture: sheet[\"explosion_flare.png\"]!);\n      spriteFlare.pivot = const Offset(0.3, 1.0);\n      spriteFlare.scaleX = 0.3;\n      spriteFlare.blendMode = ui.BlendMode.plus;\n      spriteFlare.rotation = randomDouble() * 360.0;\n      addChild(spriteFlare);\n\n      double multiplier = randomDouble() * 0.3 + 1.0;\n\n      Motion scale = MotionTween<double>(\n        setter: (a) => spriteFlare.scaleY = a,\n        start: 0.3 * multiplier,\n        end: 0.8,\n        duration: 0.75 * multiplier,\n      );\n      Motion scaleAndRemove = MotionSequence(\n        motions: [\n          scale,\n          MotionRemoveNode(node: spriteFlare),\n        ],\n      );\n      Motion fadeIn = MotionTween<double>(\n        setter: (a) => spriteFlare.opacity = a,\n        start: 0.0,\n        end: 1.0,\n        duration: 0.25 * multiplier,\n      );\n      Motion fadeOut = MotionTween<double>(\n        setter: (a) => spriteFlare.opacity = a,\n        start: 1.0,\n        end: 0.0,\n        duration: 0.5 * multiplier,\n      );\n      Motion fadeInOut = MotionSequence(motions: [fadeIn, fadeOut]);\n      motions.run(scaleAndRemove);\n      motions.run(fadeInOut);\n    }\n  }\n}\n\nclass ExplosionMini extends Explosion {\n  ExplosionMini(SpriteSheet sheet) {\n    for (int i = 0; i < 2; i++) {\n      Sprite star = Sprite(texture: sheet[\"star_0.png\"]!);\n      star.scale = 0.5;\n      star.colorOverlay = const Color(0xff95f4fb);\n      star.blendMode = ui.BlendMode.plus;\n      addChild(star);\n\n      double rotationStart = randomDouble() * 90.0;\n      double rotationEnd = 180.0 + randomDouble() * 90.0;\n      if (i == 0) {\n        rotationStart = -rotationStart;\n        rotationEnd = -rotationEnd;\n      }\n\n      MotionTween rotate = MotionTween<double>(\n        setter: (a) => star.rotation = a,\n        start: rotationStart,\n        end: rotationEnd,\n        duration: 0.2,\n      );\n      motions.run(rotate);\n\n      MotionTween fade = MotionTween<double>(\n        setter: (a) => star.opacity = a,\n        start: 1.0,\n        end: 0.0,\n        duration: 0.2,\n      );\n      motions.run(fade);\n    }\n\n    MotionSequence seq = MotionSequence(\n      motions: [\n        MotionDelay(delay: 0.2),\n        MotionRemoveNode(node: this),\n      ],\n    );\n    motions.run(seq);\n  }\n}\n"
  },
  {
    "path": "lib/flash.dart",
    "content": "part of game;\n\nclass Flash extends NodeWithSize {\n  Flash(Size size, this.duration) : super(size) {\n    MotionTween fade = MotionTween<double>(\n      setter: (a) => _opacity = a,\n      start: 1.0,\n      end: 0.0,\n      duration: duration,\n    );\n    MotionSequence seq = MotionSequence(\n      motions: <Motion>[\n        fade,\n        MotionRemoveNode(node: this),\n      ],\n    );\n    motions.run(seq);\n  }\n\n  double duration;\n  double _opacity = 1.0;\n  final Paint _cachedPaint = Paint();\n\n  @override\n  void paint(Canvas canvas) {\n    // Update the color\n    _cachedPaint.color =\n        Color.fromARGB((255.0 * _opacity).toInt(), 255, 255, 255);\n    // Fill the area\n    applyTransformForPivot(canvas);\n    canvas.drawRect(\n        Rect.fromLTRB(0.0, 0.0, size.width, size.height), _cachedPaint);\n  }\n}\n"
  },
  {
    "path": "lib/game_demo.dart",
    "content": "library game;\n\nimport 'dart:async';\nimport 'dart:convert';\nimport 'dart:math' as math;\nimport 'dart:ui' as ui;\n\nimport 'package:flutter/rendering.dart';\nimport 'package:flutter/widgets.dart';\nimport 'package:just_audio/just_audio.dart';\nimport 'package:shared_preferences/shared_preferences.dart';\nimport 'package:spritewidget/spritewidget.dart';\nimport 'package:vector_math/vector_math_64.dart';\n\npart 'coordinate_system.dart';\npart 'custom_actions.dart';\npart 'explosions.dart';\npart 'flash.dart';\npart 'game_demo_node.dart';\npart 'game_object_factory.dart';\npart 'game_objects.dart';\npart 'persistant_game_state.dart';\npart 'player_state.dart';\npart 'power_bar.dart';\npart 'render_coordinate_system.dart';\npart 'repeated_image.dart';\npart 'sound_assets.dart';\npart 'star_field.dart';\npart 'widgets.dart';\n"
  },
  {
    "path": "lib/game_demo_node.dart",
    "content": "part of game;\n\nvar _gameSizeHeight = 320.0;\n\nconst _chunkSpacing = 640.0;\nconst int _chunksPerLevel = 9;\n\nconst bool _drawDebug = false;\n\ntypedef GameOverCallback = void Function(\n    int score, int coins, int levelReached);\n\nclass GameDemoNode extends NodeWithSize {\n  GameDemoNode(this._images, this._spritesGame, this._spritesUI, this._sounds,\n      this._gameState, this._gameOverCallback)\n      : super(const Size(320.0, 320.0)) {\n    // Add background\n    _background = RepeatedImage(_images[\"assets/starfield.png\"]!);\n    addChild(_background);\n\n    // Create starfield\n    _starField = StarField(_spritesGame, 200);\n    addChild(_starField);\n\n    // Add nebula\n    _nebula = RepeatedImage(_images[\"assets/nebula.png\"]!, ui.BlendMode.plus);\n    addChild(_nebula);\n\n    // Setup game screen, it will always be anchored to the bottom of the screen\n    _gameScreen = Node();\n    addChild(_gameScreen);\n\n    // Setup the level and add it to the screen, the level is the node where\n    // all our game objects live. It is moved to scroll the game\n    _level = Level();\n    _gameScreen.addChild(_level);\n\n    // Add heads up display\n    _playerState = PlayerState(_spritesUI, _spritesGame, _gameState);\n    _playerState.position = const Offset(0.0, 20.0);\n    addChild(_playerState);\n\n    _objectFactory =\n        GameObjectFactory(_spritesGame, _sounds, _level, _playerState);\n\n    _level.ship = Ship(_objectFactory);\n    _level.ship.setupActions();\n    _level.addChild(_level.ship);\n\n    // Add the joystick\n    _joystick = VirtualJoystick();\n    _gameScreen.addChild(_joystick);\n\n    // Add initial game objects\n    addObjects();\n  }\n\n  final PersistantGameState _gameState;\n\n  // Resources\n  final ImageMap _images;\n  final SoundAssets _sounds;\n  final SpriteSheet _spritesGame;\n  final SpriteSheet _spritesUI;\n\n  // Callback\n  final GameOverCallback _gameOverCallback;\n\n  // Game screen nodes\n  late Node _gameScreen;\n  late VirtualJoystick _joystick;\n\n  late GameObjectFactory _objectFactory;\n  late Level _level;\n  int _topLevelReached = 0;\n  late StarField _starField;\n  late RepeatedImage _background;\n  late RepeatedImage _nebula;\n  late PlayerState _playerState;\n\n  // Game properties\n  double _scroll = 0.0;\n\n  int _framesToFire = 0;\n  final int _framesBetweenShots = 20;\n\n  bool _gameOver = false;\n\n  @override\n  void spriteBoxPerformedLayout() {\n    _gameSizeHeight = spriteBox!.visibleArea!.height;\n    _gameScreen.position = Offset(0.0, _gameSizeHeight);\n  }\n\n  @override\n  void update(double dt) {\n    // Scroll the level\n    _scroll = _level.scroll(_playerState.scrollSpeed);\n    _starField.move(0.0, _playerState.scrollSpeed);\n\n    _background.move(_playerState.scrollSpeed * 0.1);\n    _nebula.move(_playerState.scrollSpeed);\n\n    // Add objects\n    addObjects();\n\n    // Move the ship\n    if (!_gameOver) {\n      _level.ship.applyThrust(_joystick.value, _scroll);\n    }\n\n    // Add shots\n    if (_framesToFire == 0 && _joystick.isDown && !_gameOver) {\n      fire();\n      _framesToFire = (_playerState.speedLaserActive)\n          ? _framesBetweenShots ~/ 2\n          : _framesBetweenShots;\n    }\n    if (_framesToFire > 0) _framesToFire--;\n\n    // Move game objects\n    for (Node node in _level.children) {\n      if (node is GameObject) {\n        node.move();\n      }\n    }\n\n    // Remove offscreen game objects\n    for (int i = _level.children.length - 1; i >= 0; i--) {\n      Node node = _level.children[i];\n      if (node is GameObject) {\n        node.removeIfOffscreen(_scroll);\n      }\n    }\n\n    if (_gameOver) return;\n\n    // Check for collisions between lasers and objects that can take damage\n    List<Laser> lasers = <Laser>[];\n    for (Node node in _level.children) {\n      if (node is Laser) lasers.add(node);\n    }\n\n    List<GameObject> damageables = <GameObject>[];\n    for (Node node in _level.children) {\n      if (node is GameObject && node.canBeDamaged) damageables.add(node);\n    }\n\n    for (Laser laser in lasers) {\n      for (GameObject damageable in damageables) {\n        if (laser.collidingWith(damageable)) {\n          // Hit something that can take damage\n          damageable.addDamage(laser.impact);\n          laser.destroy();\n        }\n      }\n    }\n\n    // Check for collsions between ship and objects that can damage the ship\n    List<Node> nodes = List<Node>.from(_level.children);\n    for (Node node in nodes) {\n      if (node is GameObject && node.canDamageShip) {\n        if (node.collidingWith(_level.ship)) {\n          if (_playerState.shieldActive) {\n            // Hit, but saved by the shield!\n            if (node is! EnemyBoss) node.destroy();\n          } else {\n            // The ship was hit :(\n            killShip();\n          }\n        }\n      } else if (node is GameObject && node.canBeCollected) {\n        if (node.collidingWith(_level.ship)) {\n          // The ship ran over something collectable\n          node.collect();\n        }\n      }\n    }\n  }\n\n  int _chunk = 0;\n\n  void addObjects() {\n    while (_scroll + _chunkSpacing >= _chunk * _chunkSpacing) {\n      addLevelChunk(_chunk, -_chunk * _chunkSpacing - _chunkSpacing);\n\n      _chunk += 1;\n    }\n  }\n\n  void addLevelChunk(int chunk, double yPos) {\n    int level = chunk ~/ _chunksPerLevel + _gameState.currentStartingLevel;\n    int part = chunk % _chunksPerLevel;\n\n    if (part == 0) {\n      LevelLabel lbl = LevelLabel(_objectFactory, level + 1);\n      lbl.position = Offset(0.0, yPos + _chunkSpacing / 2.0 - 150.0);\n\n      _topLevelReached = level;\n      _level.addChild(lbl);\n    } else if (part == 1) {\n      _objectFactory.addAsteroids(level, yPos);\n    } else if (part == 2) {\n      _objectFactory.addEnemyScoutSwarm(level, yPos);\n    } else if (part == 3) {\n      _objectFactory.addAsteroids(level, yPos);\n    } else if (part == 4) {\n      _objectFactory.addEnemyDestroyerSwarm(level, yPos);\n    } else if (part == 5) {\n      _objectFactory.addAsteroids(level, yPos);\n    } else if (part == 6) {\n      _objectFactory.addEnemyScoutSwarm(level, yPos);\n    } else if (part == 7) {\n      _objectFactory.addAsteroids(level, yPos);\n    } else if (part == 8) {\n      _objectFactory.addBossFight(level, yPos);\n    }\n  }\n\n  void fire() {\n    int laserLevel = _objectFactory.playerState.laserLevel;\n\n    Laser shot0 = Laser(_objectFactory, laserLevel, -90.0);\n    shot0.position = _level.ship.position + const Offset(17.0, -10.0);\n    _level.addChild(shot0);\n\n    Laser shot1 = Laser(_objectFactory, laserLevel, -90.0);\n    shot1.position = _level.ship.position + const Offset(-17.0, -10.0);\n    _level.addChild(shot1);\n\n    if (_playerState.sideLaserActive) {\n      Laser shot2 = Laser(_objectFactory, laserLevel, -45.0);\n      shot2.position = _level.ship.position + const Offset(17.0, -10.0);\n      _level.addChild(shot2);\n\n      Laser shot3 = Laser(_objectFactory, laserLevel, -135.0);\n      shot3.position = _level.ship.position + const Offset(-17.0, -10.0);\n      _level.addChild(shot3);\n    }\n  }\n\n  void killShip() {\n    // Hide ship\n    _level.ship.visible = false;\n\n    _sounds.playEffect(\"explosion_player\");\n\n    // Add explosion\n    ExplosionBig explo = ExplosionBig(_spritesGame);\n    explo.scale = 1.5;\n    explo.position = _level.ship.position;\n    _level.addChild(explo);\n\n    // Add flash\n    Flash flash = Flash(size, 1.0);\n    addChild(flash);\n\n    // Set the state to game over\n    _gameOver = true;\n\n    // Return to main scene and report the score back in 2 seconds\n    Timer(const Duration(seconds: 2), () {\n      _gameOverCallback(\n          _playerState.score, _playerState.coins, _topLevelReached);\n    });\n  }\n}\n\nclass Level extends Node {\n  Level() {\n    position = const Offset(160.0, 0.0);\n  }\n\n  late Ship ship;\n\n  double scroll(double scrollSpeed) {\n    position += Offset(0.0, scrollSpeed);\n    return position.dy;\n  }\n}\n"
  },
  {
    "path": "lib/game_object_factory.dart",
    "content": "part of game;\n\nconst int _maxLevel = 9;\n\nclass GameObjectFactory {\n  GameObjectFactory(this.sheet, this.sounds, this.level, this.playerState);\n\n  SpriteSheet sheet;\n  SoundAssets sounds;\n  Level level;\n  PlayerState playerState;\n\n  void addAsteroids(int level, double yPos) {\n    int numAsteroids = 10 + level * 4;\n    double distribution = (level * 0.2).clamp(0.0, 0.8);\n\n    for (int i = 0; i < numAsteroids; i++) {\n      GameObject obj;\n      if (i == 0) {\n        obj = AsteroidPowerUp(this);\n      } else if (randomDouble() < distribution) {\n        obj = AsteroidBig(this);\n      } else {\n        obj = AsteroidSmall(this);\n      }\n\n      Offset pos = Offset(\n          randomSignedDouble() * 160.0, yPos + _chunkSpacing * randomDouble());\n      addGameObject(obj, pos);\n    }\n  }\n\n  void addEnemyScoutSwarm(int level, double yPos) {\n    int numEnemies = (3 + level * 3).clamp(0, 12);\n    late List<int> types;\n    int swarmLevel = level % _maxLevel;\n\n    if (swarmLevel == 0) {\n      types = [0, 0, 0];\n    } else if (swarmLevel == 1) {\n      types = [0, 1, 0];\n    } else if (swarmLevel == 2) {\n      types = [1, 0, 1];\n    } else if (swarmLevel == 3) {\n      types = [1, 1, 1];\n    } else if (swarmLevel == 4) {\n      types = [0, 1, 2];\n    } else if (swarmLevel == 5) {\n      types = [1, 2, 1];\n    } else if (swarmLevel == 6) {\n      types = [2, 1, 2];\n    } else if (swarmLevel == 7) {\n      types = [2, 1, 2];\n    } else if (swarmLevel == 8) {\n      types = [2, 2, 2];\n    }\n\n    for (int i = 0; i < numEnemies; i++) {\n      int type = types[i % 3];\n      double spacing = math.max(_chunkSpacing / (numEnemies + 1.0), 80.0);\n      double y = yPos +\n          _chunkSpacing / 2.0 -\n          (numEnemies - 1) * spacing / 2.0 +\n          i * spacing;\n      addGameObject(EnemyScout(this, type), Offset(0.0, y));\n    }\n  }\n\n  void addEnemyDestroyerSwarm(int level, double yPos) {\n    int numEnemies = (2 + level).clamp(2, 10);\n    late List<int> types;\n    int swarmLevel = level % _maxLevel;\n\n    if (swarmLevel == 0) {\n      types = [0, 0, 0];\n    } else if (swarmLevel == 1) {\n      types = [0, 1, 0];\n    } else if (swarmLevel == 2) {\n      types = [1, 0, 1];\n    } else if (swarmLevel == 3) {\n      types = [1, 1, 1];\n    } else if (swarmLevel == 4) {\n      types = [0, 1, 2];\n    } else if (swarmLevel == 5) {\n      types = [1, 2, 1];\n    } else if (swarmLevel == 6) {\n      types = [2, 1, 2];\n    } else if (swarmLevel == 7) {\n      types = [2, 1, 2];\n    } else if (swarmLevel == 8) {\n      types = [2, 2, 2];\n    }\n\n    for (int i = 0; i < numEnemies; i++) {\n      int type = types[i % 3];\n      addGameObject(\n          EnemyDestroyer(this, type),\n          Offset(randomSignedDouble() * 120.0,\n              yPos + _chunkSpacing * randomDouble()));\n    }\n  }\n\n  void addGameObject(GameObject obj, Offset pos) {\n    obj.position = pos;\n    obj.setupActions();\n\n    level.addChild(obj);\n  }\n\n  void addBossFight(int level, double yPos) {\n    // Add boss\n    EnemyBoss boss = EnemyBoss(this, level);\n    Offset pos = Offset(0.0, yPos + _chunkSpacing / 2.0);\n\n    addGameObject(boss, pos);\n\n    playerState.boss = boss;\n\n    int destroyerLevel = (level - 1 ~/ 3).clamp(0, 2);\n\n    // Add boss's helpers\n    if (level >= 1) {\n      EnemyDestroyer destroyer0 = EnemyDestroyer(this, destroyerLevel);\n      addGameObject(\n          destroyer0, Offset(-80.0, yPos + _chunkSpacing / 2.0 + 70.0));\n\n      EnemyDestroyer destroyer1 = EnemyDestroyer(this, destroyerLevel);\n      addGameObject(\n          destroyer1, Offset(80.0, yPos + _chunkSpacing / 2.0 + 70.0));\n\n      if (level >= 2) {\n        EnemyDestroyer destroyer0 = EnemyDestroyer(this, destroyerLevel);\n        addGameObject(\n            destroyer0, Offset(-80.0, yPos + _chunkSpacing / 2.0 - 70.0));\n\n        EnemyDestroyer destroyer1 = EnemyDestroyer(this, destroyerLevel);\n        addGameObject(\n            destroyer1, Offset(80.0, yPos + _chunkSpacing / 2.0 - 70.0));\n      }\n    }\n  }\n}\n\nconst List<Color> laserColors = [\n  Color(0xff95f4fb),\n  Color(0xff5bff35),\n  Color(0xffff886c),\n  Color(0xffffd012),\n  Color(0xfffd7fff),\n];\n\nvoid addLaserSprites(Node node, int level, double r, SpriteSheet sheet) {\n  int numLasers = level % 3 + 1;\n  Color laserColor = laserColors[(level ~/ 3) % laserColors.length];\n\n  // Add sprites\n  List<Sprite> sprites = <Sprite>[];\n  for (int i = 0; i < numLasers; i++) {\n    Sprite sprite = Sprite(texture: sheet[\"explosion_particle.png\"]!);\n    sprite.scale = 0.5;\n    sprite.colorOverlay = laserColor;\n    sprite.blendMode = ui.BlendMode.plus;\n    node.addChild(sprite);\n    sprites.add(sprite);\n  }\n\n  // Position the individual sprites\n  if (numLasers == 2) {\n    sprites[0].position = const Offset(-3.0, 0.0);\n    sprites[1].position = const Offset(3.0, 0.0);\n  } else if (numLasers == 3) {\n    sprites[0].position = const Offset(-4.0, 0.0);\n    sprites[1].position = const Offset(4.0, 0.0);\n    sprites[2].position = const Offset(0.0, -2.0);\n  }\n}\n"
  },
  {
    "path": "lib/game_objects.dart",
    "content": "part of game;\n\nabstract class GameObject extends Node {\n  GameObject(this.f);\n\n  double radius = 0.0;\n  double removeLimit = 1280.0;\n  bool canDamageShip = true;\n  bool canBeDamaged = true;\n  bool canBeCollected = false;\n  double maxDamage = 3.0;\n  double damage = 0.0;\n\n  final GameObjectFactory f;\n\n  final Paint _paintDebug = Paint()\n    ..color = const Color(0xffff0000)\n    ..strokeWidth = 1.0\n    ..style = ui.PaintingStyle.stroke;\n\n  bool collidingWith(GameObject obj) {\n    return (GameMath.distanceBetweenPoints(position, obj.position) <\n        radius + obj.radius);\n  }\n\n  void move() {}\n\n  void removeIfOffscreen(double scroll) {\n    if (-position.dy > scroll + removeLimit || -position.dy < scroll - 50.0) {\n      removeFromParent();\n    }\n  }\n\n  void destroy() {\n    if (parent != null) {\n      Explosion? explo = createExplosion();\n      if (explo != null) {\n        explo.position = position;\n        parent!.addChild(explo);\n      }\n\n      Collectable? powerUp = createPowerUp();\n      if (powerUp != null) {\n        f.addGameObject(powerUp, position);\n      }\n\n      removeFromParent();\n    }\n  }\n\n  void collect() {\n    removeFromParent();\n  }\n\n  void addDamage(double d) {\n    if (!canBeDamaged) return;\n\n    damage += d;\n    if (damage >= maxDamage) {\n      destroy();\n      f.playerState.score += (maxDamage * 10).ceil();\n    } else {\n      f.sounds.playEffect(\"hit\");\n    }\n  }\n\n  Explosion? createExplosion() {\n    return null;\n  }\n\n  Collectable? createPowerUp() {\n    return null;\n  }\n\n  @override\n  void paint(Canvas canvas) {\n    if (_drawDebug) {\n      canvas.drawCircle(Offset.zero, radius, _paintDebug);\n    }\n    super.paint(canvas);\n  }\n\n  void setupActions() {}\n}\n\nclass LevelLabel extends GameObject {\n  LevelLabel(GameObjectFactory f, int level) : super(f) {\n    canDamageShip = false;\n    canBeDamaged = false;\n\n    Label lbl = Label(\"LEVEL $level\",\n        textAlign: TextAlign.center,\n        textStyle: const TextStyle(\n            fontFamily: \"Orbitron\",\n            letterSpacing: 10.0,\n            color: Color(0xffffffff),\n            fontSize: 24.0,\n            fontWeight: FontWeight.w600));\n    addChild(lbl);\n  }\n}\n\nclass Ship extends GameObject {\n  Ship(GameObjectFactory f) : super(f) {\n    // Add main ship sprite\n    _sprite = Sprite(texture: f.sheet[\"ship.png\"]!);\n    _sprite.scale = 0.3;\n    _sprite.rotation = -90.0;\n    addChild(_sprite);\n\n    _spriteShield = Sprite(texture: f.sheet[\"shield.png\"]!);\n    _spriteShield.scale = 0.35;\n    _spriteShield.blendMode = ui.BlendMode.plus;\n    addChild(_spriteShield);\n\n    radius = 20.0;\n    canBeDamaged = false;\n    canDamageShip = false;\n\n    // Set start position\n    position = const Offset(0.0, 50.0);\n  }\n\n  late Sprite _sprite;\n  late Sprite _spriteShield;\n\n  void applyThrust(Offset joystickValue, double scroll) {\n    Offset oldPos = position;\n    Offset target = Offset(\n        joystickValue.dx * 160.0, joystickValue.dy * 220.0 - 250.0 - scroll);\n    double filterFactor = 0.2;\n\n    position = Offset(GameMath.filter(oldPos.dx, target.dx, filterFactor),\n        GameMath.filter(oldPos.dy, target.dy, filterFactor));\n  }\n\n  @override\n  void setupActions() {\n    MotionTween rotate = MotionTween<double>(\n      setter: (a) => _spriteShield.rotation = a,\n      start: 0.0,\n      end: 360.0,\n      duration: 1.0,\n    );\n    _spriteShield.motions.run(MotionRepeatForever(motion: rotate));\n  }\n\n  @override\n  void update(double dt) {\n    // Update shield\n    if (f.playerState.shieldActive) {\n      if (f.playerState.shieldDeactivating) {\n        _spriteShield.visible = !_spriteShield.visible;\n      } else {\n        _spriteShield.visible = true;\n      }\n    } else {\n      _spriteShield.visible = false;\n    }\n  }\n}\n\nclass Laser extends GameObject {\n  double impact = 0.0;\n\n  Laser(GameObjectFactory f, int level, double r) : super(f) {\n    // Game object properties\n    radius = 10.0;\n    removeLimit = _gameSizeHeight + radius;\n    canDamageShip = false;\n    canBeDamaged = false;\n    impact = 1.0 + level * 0.5;\n\n    // Offset for movement\n    _offset = Offset(math.cos(radians(r)) * 8.0,\n        math.sin(radians(r)) * 8.0 - f.playerState.scrollSpeed);\n\n    // Drawing properties\n    rotation = r + 90.0;\n\n    addLaserSprites(this, level, r, f.sheet);\n  }\n\n  late Offset _offset;\n\n  @override\n  void move() {\n    position += _offset;\n  }\n\n  @override\n  Explosion createExplosion() {\n    return ExplosionMini(f.sheet);\n  }\n}\n\nColor colorForDamage(double damage, double maxDamage, [Color? toColor]) {\n  int r, g, b;\n  if (toColor == null) {\n    r = 255;\n    g = 3;\n    b = 86;\n  } else {\n    r = toColor.red;\n    g = toColor.green;\n    b = toColor.blue;\n  }\n\n  int alpha = ((200.0 * damage) ~/ maxDamage).clamp(0, 200);\n  return Color.fromARGB(alpha, r, g, b);\n}\n\nabstract class Obstacle extends GameObject {\n  Obstacle(GameObjectFactory f) : super(f);\n\n  double explosionScale = 1.0;\n\n  @override\n  Explosion createExplosion() {\n    f.sounds.playEffect(\"explosion_${randomInt(3)}\");\n    Explosion explo = ExplosionBig(f.sheet);\n    explo.scale = explosionScale;\n    return explo;\n  }\n}\n\nabstract class Asteroid extends Obstacle {\n  Asteroid(GameObjectFactory f) : super(f);\n\n  late Sprite _sprite;\n\n  @override\n  void setupActions() {\n    // Rotate obstacle\n    int direction = 1;\n    if (randomBool()) direction = -1;\n    MotionTween rotate = MotionTween<double>(\n      setter: (a) => _sprite.rotation = a,\n      start: 0.0,\n      end: 360.0 * direction,\n      duration: 5.0 + 5.0 * randomDouble(),\n    );\n    _sprite.motions.run(MotionRepeatForever(motion: rotate));\n  }\n\n  @override\n  set damage(double d) {\n    super.damage = d;\n    _sprite.colorOverlay = colorForDamage(d, maxDamage);\n  }\n\n  @override\n  Collectable createPowerUp() {\n    return Coin(f);\n  }\n}\n\nclass AsteroidBig extends Asteroid {\n  AsteroidBig(GameObjectFactory f) : super(f) {\n    _sprite = Sprite(texture: f.sheet[\"asteroid_big_${randomInt(3)}.png\"]!);\n    _sprite.scale = 0.3;\n    radius = 25.0;\n    maxDamage = 5.0;\n    addChild(_sprite);\n  }\n}\n\nclass AsteroidSmall extends Asteroid {\n  AsteroidSmall(GameObjectFactory f) : super(f) {\n    _sprite = Sprite(texture: f.sheet[\"asteroid_small_${randomInt(3)}.png\"]!);\n    _sprite.scale = 0.3;\n    radius = 12.0;\n    maxDamage = 3.0;\n    addChild(_sprite);\n  }\n}\n\nclass AsteroidPowerUp extends AsteroidBig {\n  late PowerUpType _powerUpType;\n\n  AsteroidPowerUp(GameObjectFactory f) : super(f) {\n    _powerUpType = nextPowerUpType();\n\n    removeAllChildren();\n\n    Sprite powerUpBg = Sprite(\n      texture: f.sheet[\"powerup.png\"]!,\n    );\n    powerUpBg.scale = 0.3;\n    addChild(powerUpBg);\n\n    Sprite powerUpIcon = Sprite(\n      texture: f.sheet[\"powerup_${_powerUpType.index}.png\"]!,\n    );\n    powerUpIcon.scale = 0.3;\n    addChild(powerUpIcon);\n\n    _sprite = Sprite(\n      texture: f.sheet[\"crystal_${randomInt(2)}.png\"]!,\n    );\n    _sprite.scale = 0.3;\n    addChild(_sprite);\n  }\n\n  @override\n  void setupActions() {}\n\n  @override\n  Collectable createPowerUp() {\n    return PowerUp(f, _powerUpType);\n  }\n\n  @override\n  set damage(double d) {\n    super.damage = d;\n    _sprite.colorOverlay =\n        colorForDamage(d, maxDamage, const Color.fromARGB(255, 200, 200, 255));\n  }\n}\n\nclass EnemyScout extends Obstacle {\n  EnemyScout(GameObjectFactory f, int level) : super(f) {\n    _sprite = Sprite(texture: f.sheet[\"enemy_scout_$level.png\"]!);\n    _sprite.scale = 0.32;\n\n    radius = 12.0 + level * 2.0;\n\n    if (level == 0) {\n      maxDamage = 1.0;\n    } else if (level == 1) {\n      maxDamage = 4.0;\n    } else if (level == 2) {\n      maxDamage = 8.0;\n    }\n\n    addChild(_sprite);\n\n    constraints = <Constraint>[ConstraintRotationToMovement(dampening: 0.5)];\n  }\n\n  final double _swirlSpacing = 80.0;\n\n  _addRandomSquare(List<Offset> offsets, double x, double y) {\n    double xMove = (randomBool()) ? _swirlSpacing : -_swirlSpacing;\n    double yMove = (randomBool()) ? _swirlSpacing : -_swirlSpacing;\n\n    if (randomBool()) {\n      offsets.addAll(<Offset>[\n        Offset(x, y),\n        Offset(xMove + x, y),\n        Offset(xMove + x, yMove + y),\n        Offset(x, yMove + y),\n        Offset(x, y)\n      ]);\n    } else {\n      offsets.addAll(<Offset>[\n        Offset(x, y),\n        Offset(x, y + yMove),\n        Offset(xMove + x, yMove + y),\n        Offset(xMove + x, y),\n        Offset(x, y)\n      ]);\n    }\n  }\n\n  @override\n  void setupActions() {\n    List<Offset> offsets = <Offset>[];\n    _addRandomSquare(offsets, -_swirlSpacing, 0.0);\n    _addRandomSquare(offsets, _swirlSpacing, 0.0);\n    offsets.add(Offset(-_swirlSpacing, 0.0));\n\n    List<Offset> points = <Offset>[];\n    for (Offset offset in offsets) {\n      points.add(position + offset);\n    }\n\n    MotionSpline spline = MotionSpline(\n      setter: (Offset a) => position = a,\n      points: points,\n      duration: 6.0,\n    );\n    spline.tension = 0.7;\n    motions.run(MotionRepeatForever(motion: spline));\n  }\n\n  @override\n  Collectable createPowerUp() {\n    return Coin(f);\n  }\n\n  @override\n  set damage(double d) {\n    super.damage = d;\n    _sprite.colorOverlay = colorForDamage(d, maxDamage);\n  }\n\n  late Sprite _sprite;\n}\n\nclass EnemyDestroyer extends Obstacle {\n  EnemyDestroyer(GameObjectFactory f, int level) : super(f) {\n    _sprite = Sprite(texture: f.sheet[\"enemy_destroyer_$level.png\"]!);\n    _sprite.scale = 0.32;\n\n    radius = 24.0 + level * 2;\n\n    if (level == 0) {\n      maxDamage = 4.0;\n    } else if (level == 1) {\n      maxDamage = 8.0;\n    } else if (level == 2) {\n      maxDamage = 16.0;\n    }\n\n    addChild(_sprite);\n\n    constraints = <Constraint>[\n      ConstraintRotationToNode(targetNode: f.level.ship, dampening: 0.05)\n    ];\n  }\n\n  int _countDown = randomInt(120) + 240;\n\n  @override\n  void setupActions() {\n    ActionCircularMove circle = ActionCircularMove((Offset a) {\n      position = a;\n    }, position, 40.0, 360.0 * randomDouble(), randomBool(), 3.0);\n    motions.run(MotionRepeatForever(motion: circle));\n  }\n\n  @override\n  Collectable createPowerUp() {\n    return Coin(f);\n  }\n\n  @override\n  void update(double dt) {\n    _countDown -= 1;\n    if (_countDown <= 0) {\n      // Shoot at player\n      f.sounds.playEffect(\"laser\");\n\n      EnemyLaser laser = EnemyLaser(f, rotation, 5.0, const Color(0xffffe38e));\n      laser.position = position;\n      f.level.addChild(laser);\n\n      _countDown = 60 + randomInt(120);\n    }\n  }\n\n  @override\n  set damage(double d) {\n    super.damage = d;\n    _sprite.colorOverlay = colorForDamage(d, maxDamage);\n  }\n\n  late Sprite _sprite;\n}\n\nclass EnemyLaser extends Obstacle {\n  EnemyLaser(GameObjectFactory f, double rotation, double speed, Color color)\n      : super(f) {\n    _sprite = Sprite(texture: f.sheet[\"explosion_particle.png\"]!);\n    _sprite.scale = 0.5;\n    _sprite.rotation = rotation + 90;\n    _sprite.colorOverlay = color;\n    addChild(_sprite);\n\n    canDamageShip = true;\n    canBeDamaged = false;\n\n    double rad = radians(rotation);\n    _movement = Offset(math.cos(rad) * speed, math.sin(rad) * speed);\n  }\n\n  late Sprite _sprite;\n  late Offset _movement;\n\n  @override\n  void move() {\n    position += _movement;\n  }\n}\n\nclass EnemyBoss extends Obstacle {\n  EnemyBoss(GameObjectFactory f, int level) : super(f) {\n    radius = 48.0;\n    _sprite = Sprite(texture: f.sheet[\"enemy_boss_${level % 3}.png\"]!);\n    _sprite.scale = 0.32;\n    addChild(_sprite);\n    maxDamage = 40.0 + 20.0 * level;\n\n    constraints = <Constraint>[\n      ConstraintRotationToNode(targetNode: f.level.ship, dampening: 0.05)\n    ];\n\n    _powerBar = PowerBar(const Size(60.0, 10.0));\n    _powerBar.pivot = const Offset(0.5, 0.5);\n    f.level.addChild(_powerBar);\n    _powerBar.constraints = <Constraint>[\n      ConstraintPositionToNode(\n        targetNode: this,\n        dampening: 0.5,\n        offset: const Offset(0.0, -70.0),\n      )\n    ];\n  }\n\n  late Sprite _sprite;\n  late PowerBar _powerBar;\n\n  int _countDown = randomInt(120) + 240;\n\n  @override\n  void update(double dt) {\n    _countDown -= 1;\n    if (_countDown <= 0) {\n      // Shoot at player\n      f.sounds.playEffect(\"laser\");\n\n      fire(10.0);\n      fire(0.0);\n      fire(-10.0);\n\n      _countDown = 60 + randomInt(120);\n    }\n  }\n\n  void fire(double r) {\n    r += rotation;\n    EnemyLaser laser = EnemyLaser(f, r, 5.0, const Color(0xffffe38e));\n\n    double rad = radians(r);\n    Offset startOffset = Offset(math.cos(rad) * 30.0, math.sin(rad) * 30.0);\n\n    laser.position = position + startOffset;\n    f.level.addChild(laser);\n  }\n\n  @override\n  void setupActions() {\n    ActionOscillate oscillate = ActionOscillate((Offset a) {\n      position = a;\n    }, position, 120.0, 3.0);\n    motions.run(MotionRepeatForever(motion: oscillate));\n  }\n\n  @override\n  void destroy() {\n    f.playerState.boss = null;\n    if (_powerBar.parent != null) _powerBar.removeFromParent();\n\n    // Flash the screen\n    NodeWithSize screen = f.playerState.parent as NodeWithSize;\n    screen.addChild(Flash(screen.size, 1.0));\n    super.destroy();\n\n    // Add coins\n    for (int i = 0; i < 20; i++) {\n      Coin coin = Coin(f);\n      Offset pos = Offset(randomSignedDouble() * 160,\n          position.dy + randomSignedDouble() * 160.0);\n      f.addGameObject(coin, pos);\n    }\n  }\n\n  @override\n  Explosion createExplosion() {\n    f.sounds.playEffect(\"explosion_boss\");\n    ExplosionBig explo = ExplosionBig(f.sheet);\n    explo.scale = 1.5;\n    return explo;\n  }\n\n  @override\n  set damage(double d) {\n    super.damage = d;\n    _sprite.motions.stopAll();\n    _sprite.motions.run(\n      MotionTween<Color>(\n        setter: (a) => _sprite.colorOverlay = a,\n        start: const Color.fromARGB(180, 255, 3, 86),\n        end: const Color(0x00000000),\n        duration: 0.3,\n      ),\n    );\n\n    _powerBar.power = (1.0 - (damage / maxDamage)).clamp(0.0, 1.0);\n  }\n}\n\nclass Collectable extends GameObject {\n  Collectable(GameObjectFactory f) : super(f) {\n    canDamageShip = false;\n    canBeDamaged = false;\n    canBeCollected = true;\n\n    zPosition = 20.0;\n  }\n}\n\nclass Coin extends Collectable {\n  Coin(GameObjectFactory f) : super(f) {\n    _sprite = Sprite(texture: f.sheet[\"coin.png\"]!);\n    _sprite.scale = 0.7;\n    addChild(_sprite);\n\n    radius = 7.5;\n  }\n\n  @override\n  void setupActions() {\n    // Rotate\n    MotionTween rotate = MotionTween<double>(\n      setter: (a) => _sprite.rotation = a,\n      start: 0.0,\n      end: 360.0,\n      duration: 1.0,\n    );\n    motions.run(MotionRepeatForever(motion: rotate));\n\n    // Fade in\n    MotionTween fadeIn = MotionTween<double>(\n      setter: (a) => _sprite.opacity = a,\n      start: 0.0,\n      end: 1.0,\n      duration: 0.6,\n    );\n    motions.run(fadeIn);\n  }\n\n  late Sprite _sprite;\n\n  @override\n  void collect() {\n    f.sounds.playEffect(\"pickup_0\");\n    f.playerState.addCoin(this);\n    super.collect();\n  }\n}\n\nenum PowerUpType {\n  shield,\n  speedLaser,\n  sideLaser,\n  speedBoost,\n}\n\nList<PowerUpType> _powerUpTypes = List<PowerUpType>.from(PowerUpType.values);\nint _lastPowerUp = _powerUpTypes.length;\n\nPowerUpType nextPowerUpType() {\n  if (_lastPowerUp >= _powerUpTypes.length) {\n    _powerUpTypes.shuffle();\n    _lastPowerUp = 0;\n  }\n\n  PowerUpType type = _powerUpTypes[_lastPowerUp];\n  _lastPowerUp++;\n\n  return type;\n}\n\nclass PowerUp extends Collectable {\n  PowerUp(GameObjectFactory f, this.type) : super(f) {\n    _sprite = Sprite(texture: f.sheet[\"powerup.png\"]!);\n    _sprite.scale = 0.3;\n    addChild(_sprite);\n\n    Sprite powerUpIcon = Sprite(texture: f.sheet[\"powerup_${type.index}.png\"]!);\n    powerUpIcon.scale = 0.3;\n    addChild(powerUpIcon);\n\n    radius = 10.0;\n  }\n\n  late Sprite _sprite;\n  PowerUpType type;\n\n  @override\n  void setupActions() {\n    MotionTween rotate = MotionTween<double>(\n      setter: (a) => _sprite.rotation = a,\n      start: 0.0,\n      end: 360.0,\n      duration: 1.0,\n    );\n    motions.run(MotionRepeatForever(motion: rotate));\n\n    // Fade in\n    MotionTween fadeIn = MotionTween<double>(\n      setter: (a) => _sprite.opacity = a,\n      start: 0.0,\n      end: 1.0,\n      duration: 0.6,\n    );\n    motions.run(fadeIn);\n  }\n\n  @override\n  void collect() {\n    f.sounds.playEffect(\"buy_upgrade\");\n    f.playerState.activatePowerUp(type);\n    super.collect();\n  }\n}\n"
  },
  {
    "path": "lib/generated_plugin_registrant.dart",
    "content": "//\n// Generated file. Do not edit.\n//\n\n// ignore_for_file: directives_ordering\n// ignore_for_file: lines_longer_than_80_chars\n\nimport 'package:audio_session/audio_session_web.dart';\nimport 'package:just_audio_web/just_audio_web.dart';\nimport 'package:shared_preferences_web/shared_preferences_web.dart';\n\nimport 'package:flutter_web_plugins/flutter_web_plugins.dart';\n\n// ignore: public_member_api_docs\nvoid registerPlugins(Registrar registrar) {\n  AudioSessionWeb.registerWith(registrar);\n  JustAudioPlugin.registerWith(registrar);\n  SharedPreferencesPlugin.registerWith(registrar);\n  registrar.registerMessageHandler();\n}\n"
  },
  {
    "path": "lib/main.dart",
    "content": "// Copyright 2015 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\nimport 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nimport 'package:spritewidget/spritewidget.dart';\n\nimport 'game_demo.dart';\n\nlate PersistantGameState _gameState;\n\nconst Color _darkTextColor = Color(0xff3c3f4a);\n\ntypedef SelectTabCallback = void Function(int index);\ntypedef UpgradePowerUpCallback = void Function(PowerUpType type);\n\nlate ImageMap _imageMap;\nlate SpriteSheet _spriteSheet;\nlate SpriteSheet _spriteSheetUI;\n\nlate SoundAssets _sounds;\n\nmain() async {\n  // We need to call ensureInitialized if we are loading images before runApp\n  // is called.\n  // TODO: This should be refactored to use a loading screen\n  WidgetsFlutterBinding.ensureInitialized();\n\n  // Hide all menu bars\n  SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);\n\n  // Load game state\n  _gameState = PersistantGameState();\n  await _gameState.load();\n\n  // Load images\n  _imageMap = ImageMap();\n\n  await _imageMap.load(<String>[\n    'assets/nebula.png',\n    'assets/sprites.png',\n    'assets/starfield.png',\n    'assets/game_ui.png',\n    'assets/ui_bg_top.png',\n    'assets/ui_bg_bottom.png',\n    'assets/ui_popup.png',\n  ]);\n\n  _sounds = SoundAssets(rootBundle);\n  final loads = <Future>[];\n  loads.addAll([\n    _sounds.loadEffect('explosion_0'),\n    _sounds.loadEffect('explosion_1'),\n    _sounds.loadEffect('explosion_2'),\n    _sounds.loadEffect('explosion_boss'),\n    _sounds.loadEffect('explosion_player'),\n    _sounds.loadEffect('laser'),\n    _sounds.loadEffect('hit'),\n    _sounds.loadEffect('levelup'),\n    _sounds.loadEffect('pickup_0'),\n    _sounds.loadEffect('pickup_1'),\n    _sounds.loadEffect('pickup_2'),\n    _sounds.loadEffect('pickup_powerup'),\n    _sounds.loadEffect('click'),\n    _sounds.loadEffect('buy_upgrade'),\n    _sounds.loadMusic('music_intro'),\n    _sounds.loadMusic('music_game'),\n  ]);\n\n  await Future.wait(loads);\n\n  // Load sprite sheets\n  String json = await rootBundle.loadString('assets/sprites.json');\n  _spriteSheet = SpriteSheet(\n    image: _imageMap['assets/sprites.png']!,\n    jsonDefinition: json,\n  );\n\n  json = await rootBundle.loadString('assets/game_ui.json');\n  _spriteSheetUI = SpriteSheet(\n    image: _imageMap['assets/game_ui.png']!,\n    jsonDefinition: json,\n  );\n\n  // All game assets are loaded - we are good to go!\n  runApp(const GameDemo());\n}\n\nclass GameDemo extends StatefulWidget {\n  const GameDemo({Key? key}) : super(key: key);\n\n  @override\n  GameDemoState createState() => GameDemoState();\n}\n\nclass GameDemoState extends State<GameDemo> {\n  final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      home: Title(\n        title: 'Space Blast',\n        color: const Color(0xFF9900FF),\n        child: AppFrame(\n          child: Navigator(\n            key: _navigatorKey,\n            onGenerateRoute: (RouteSettings settings) {\n              switch (settings.name) {\n                case '/game':\n                  return _buildGameSceneRoute();\n                default:\n                  return _buildMainSceneRoute();\n              }\n            },\n          ),\n        ),\n      ),\n    );\n  }\n\n  PageRoute _buildGameSceneRoute() {\n    return MaterialPageRoute(builder: (BuildContext context) {\n      return GameScene(\n          onGameOver: (int lastScore, int coins, int levelReached) {\n            setState(() {\n              _gameState.lastScore = lastScore;\n              _gameState.coins += coins;\n              _gameState.reachedLevel(levelReached);\n            });\n          },\n          gameState: _gameState);\n    });\n  }\n\n  PageRoute _buildMainSceneRoute() {\n    return MaterialPageRoute(builder: (BuildContext context) {\n      return MainScene(\n        gameState: _gameState,\n        onUpgradePowerUp: (PowerUpType type) {\n          setState(() {\n            if (_gameState.upgradePowerUp(type)) {\n              _sounds.playEffect('buy_upgrade');\n            } else {\n              _sounds.playEffect('click');\n            }\n          });\n        },\n        onUpgradeLaser: () {\n          setState(() {\n            if (_gameState.upgradeLaser()) {\n              _sounds.playEffect('buy_upgrade');\n            } else {\n              _sounds.playEffect('click');\n            }\n          });\n        },\n        onStartLevelUp: () {\n          setState(() {\n            _gameState.currentStartingLevel++;\n            _sounds.playEffect('click');\n          });\n        },\n        onStartLevelDown: () {\n          setState(() {\n            _gameState.currentStartingLevel--;\n            _sounds.playEffect('click');\n          });\n        },\n      );\n    });\n  }\n}\n\nclass GameScene extends StatefulWidget {\n  const GameScene({\n    this.onGameOver,\n    this.gameState,\n    Key? key,\n  }) : super(key: key);\n\n  final GameOverCallback? onGameOver;\n  final PersistantGameState? gameState;\n\n  @override\n  State<GameScene> createState() => GameSceneState();\n}\n\nclass GameSceneState extends State<GameScene> {\n  late NodeWithSize _game;\n\n  @override\n  void initState() {\n    super.initState();\n\n    _game = GameDemoNode(\n      _imageMap,\n      _spriteSheet,\n      _spriteSheetUI,\n      _sounds,\n      widget.gameState!,\n      (\n        int score,\n        int coins,\n        int levelReached,\n      ) {\n        Navigator.pop(context);\n        widget.onGameOver!(score, coins, levelReached);\n        _sounds.playMusic('music_intro');\n      },\n    );\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return SpriteWidget(\n      _game,\n      transformMode: SpriteBoxTransformMode.fixedWidth,\n    );\n  }\n}\n\nclass MainScene extends StatefulWidget {\n  const MainScene({\n    Key? key,\n    required this.gameState,\n    required this.onUpgradePowerUp,\n    required this.onUpgradeLaser,\n    required this.onStartLevelUp,\n    required this.onStartLevelDown,\n  }) : super(key: key);\n\n  final PersistantGameState gameState;\n  final UpgradePowerUpCallback onUpgradePowerUp;\n  final VoidCallback onUpgradeLaser;\n  final VoidCallback onStartLevelUp;\n  final VoidCallback onStartLevelDown;\n\n  @override\n  State<MainScene> createState() => MainSceneState();\n}\n\nclass MainSceneState extends State<MainScene> {\n  @override\n  void initState() {\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    var notchOffset = MediaQuery.of(context).padding.top;\n\n    return Column(\n      crossAxisAlignment: CrossAxisAlignment.stretch,\n      mainAxisSize: MainAxisSize.max,\n      children: <Widget>[\n        Container(\n          height: notchOffset,\n        ),\n        Expanded(\n          child: CoordinateSystem(\n            systemSize: const Size(320.0, 320.0),\n            child: DefaultTextStyle(\n              style: const TextStyle(\n                fontFamily: \"Orbitron\",\n                fontSize: 20.0,\n                color: Color(0xffffffff),\n              ),\n              child: Stack(\n                children: <Widget>[\n                  const MainSceneBackground(),\n                  Column(\n                    children: <Widget>[\n                      SizedBox(\n                        width: 320.0,\n                        height: 98.0,\n                        child: TopBar(\n                          gameState: widget.gameState,\n                        ),\n                      ),\n                      Expanded(\n                        child: CenterArea(\n                          onUpgradeLaser: widget.onUpgradeLaser,\n                          onUpgradePowerUp: widget.onUpgradePowerUp,\n                          gameState: widget.gameState,\n                        ),\n                      ),\n                      SizedBox(\n                        width: 320.0,\n                        height: 93.0,\n                        child: BottomBar(\n                          onPlay: () {\n                            Navigator.pushNamed(context, '/game');\n                            _sounds.playMusic('music_game');\n                          },\n                          onStartLevelUp: widget.onStartLevelUp,\n                          onStartLevelDown: widget.onStartLevelDown,\n                          gameState: widget.gameState,\n                        ),\n                      ),\n                    ],\n                  ),\n                ],\n              ),\n            ),\n          ),\n        ),\n      ],\n    );\n  }\n}\n\nclass TopBar extends StatelessWidget {\n  const TopBar({\n    required this.gameState,\n    Key? key,\n  }) : super(key: key);\n\n  final PersistantGameState gameState;\n\n  @override\n  Widget build(BuildContext context) {\n    TextStyle scoreLabelStyle = const TextStyle(\n        fontFamily: \"Orbitron\",\n        fontSize: 20.0,\n        fontWeight: FontWeight.w500,\n        color: _darkTextColor);\n\n    return Stack(\n      children: <Widget>[\n        Positioned(\n          left: 18.0,\n          top: 13.0,\n          child: Text(\"Last Score\", style: scoreLabelStyle),\n        ),\n        Positioned(\n          left: 18.0,\n          top: 39.0,\n          child: Text(\"Weekly Best\", style: scoreLabelStyle),\n        ),\n        Positioned(\n          right: 18.0,\n          top: 13.0,\n          child: Text(\"${gameState.lastScore}\", style: scoreLabelStyle),\n        ),\n        Positioned(\n          right: 18.0,\n          top: 39.0,\n          child: Text(\"${gameState.weeklyBestScore}\", style: scoreLabelStyle),\n        ),\n        Positioned(\n          left: 18.0,\n          top: 80.0,\n          child: TextureImage(\n              texture: _spriteSheetUI['icn_crystal.png']!,\n              width: 12.0,\n              height: 18.0),\n        ),\n        Positioned(\n          left: 36.0,\n          top: 82.5,\n          child: Text(\n            \"${gameState.coins}\",\n            style: const TextStyle(\n              fontSize: 16.0,\n              fontWeight: FontWeight.w500,\n              color: _darkTextColor,\n            ),\n          ),\n        ),\n      ],\n    );\n  }\n}\n\nclass CenterArea extends StatelessWidget {\n  const CenterArea({\n    // required this.selection,\n    required this.onUpgradeLaser,\n    required this.gameState,\n    required this.onUpgradePowerUp,\n    Key? key,\n  }) : super(key: key);\n\n  // final int selection;\n  final VoidCallback onUpgradeLaser;\n  final UpgradePowerUpCallback onUpgradePowerUp;\n  final PersistantGameState gameState;\n\n  @override\n  Widget build(BuildContext context) {\n    return _buildCenterArea();\n  }\n\n  Widget _buildCenterArea() {\n    return _buildUpgradePanel();\n  }\n\n  Widget _buildUpgradePanel() {\n    return Column(\n      children: <Widget>[\n        const Text(\"Upgrade Laser\"),\n        _buildLaserUpgradeButton(),\n        const Text(\"Upgrade Power-Ups\"),\n        Row(children: <Widget>[\n          _buildPowerUpButton(PowerUpType.shield),\n          _buildPowerUpButton(PowerUpType.sideLaser),\n          _buildPowerUpButton(PowerUpType.speedBoost),\n          _buildPowerUpButton(PowerUpType.speedLaser),\n        ], mainAxisAlignment: MainAxisAlignment.center)\n      ],\n      mainAxisAlignment: MainAxisAlignment.center,\n      key: const Key(\"upgradePanel\"),\n    );\n  }\n\n  Widget _buildPowerUpButton(PowerUpType type) {\n    return Padding(\n      padding: const EdgeInsets.all(8.0),\n      child: Column(\n        children: <Widget>[\n          TextureButton(\n            texture: _spriteSheetUI['btn_powerup_${type.index}.png']!,\n            width: 57.0,\n            height: 57.0,\n            label: \"${gameState.powerUpUpgradePrice(type)}\",\n            labelOffset: const Offset(3.0, 20.5),\n            textStyle: const TextStyle(\n                fontFamily: \"Orbitron\", fontSize: 11.0, color: _darkTextColor),\n            textAlign: TextAlign.center,\n            onPressed: () => onUpgradePowerUp(type),\n          ),\n          Padding(\n              padding: const EdgeInsets.all(5.0),\n              child: Text(\"Lvl ${gameState.powerupLevel(type) + 1}\",\n                  style: const TextStyle(fontSize: 12.0)))\n        ],\n      ),\n    );\n  }\n\n  Widget _buildLaserUpgradeButton() {\n    return Padding(\n      padding: const EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 18.0),\n      child: Stack(\n        children: <Widget>[\n          TextureButton(\n            texture: _spriteSheetUI['btn_laser_upgrade.png']!,\n            width: 137.0,\n            height: 63.0,\n            label: \"${gameState.laserUpgradePrice()}\",\n            labelOffset: const Offset(2.0, 20.0),\n            textStyle: const TextStyle(\n                fontFamily: \"Orbitron\", fontSize: 12.0, color: _darkTextColor),\n            textAlign: TextAlign.center,\n            onPressed: onUpgradeLaser,\n          ),\n          Positioned(\n            child: LaserDisplay(level: gameState.laserLevel),\n            left: 19.5,\n            top: 14.0,\n          ),\n          Positioned(\n            child: LaserDisplay(level: gameState.laserLevel + 1),\n            right: 19.5,\n            top: 14.0,\n          )\n        ],\n      ),\n    );\n  }\n}\n\nclass BottomBar extends StatelessWidget {\n  const BottomBar({\n    required this.onPlay,\n    required this.gameState,\n    required this.onStartLevelUp,\n    required this.onStartLevelDown,\n    Key? key,\n  }) : super(key: key);\n\n  final VoidCallback onPlay;\n  final VoidCallback onStartLevelUp;\n  final VoidCallback onStartLevelDown;\n  final PersistantGameState gameState;\n\n  @override\n  Widget build(BuildContext context) {\n    return Stack(\n      children: <Widget>[\n        Positioned(\n          left: 18.0,\n          top: 14.0,\n          child: TextureImage(\n            texture: _spriteSheetUI['level_display.png']!,\n            width: 62.0,\n            height: 62.0,\n          ),\n        ),\n        Positioned(\n          left: 18.0,\n          top: 14.0,\n          child: TextureImage(\n            texture: _spriteSheetUI[\n                'level_display_${gameState.currentStartingLevel + 1}.png']!,\n            width: 62.0,\n            height: 62.0,\n          ),\n        ),\n        Positioned(\n          left: 85.0,\n          top: 14.0,\n          child: TextureButton(\n            texture: _spriteSheetUI['btn_level_up.png']!,\n            width: 30.0,\n            height: 30.0,\n            onPressed: onStartLevelUp,\n          ),\n        ),\n        Positioned(\n          left: 85.0,\n          top: 46.0,\n          child: TextureButton(\n            texture: _spriteSheetUI['btn_level_down.png']!,\n            width: 30.0,\n            height: 30.0,\n            onPressed: onStartLevelDown,\n          ),\n        ),\n        Positioned(\n          left: 120.0,\n          top: 14.0,\n          child: TextureButton(\n            onPressed: onPlay,\n            texture: _spriteSheetUI['btn_play.png']!,\n            label: \"PLAY\",\n            textStyle: const TextStyle(\n              fontFamily: \"Orbitron\",\n              fontSize: 28.0,\n              letterSpacing: 3.0,\n              color: Color(0xffffffff),\n            ),\n            textAlign: TextAlign.center,\n            width: 181.0,\n            height: 62.0,\n          ),\n        ),\n      ],\n    );\n  }\n}\n\nclass MainSceneBackground extends StatefulWidget {\n  const MainSceneBackground({Key? key}) : super(key: key);\n\n  @override\n  MainSceneBackgroundState createState() => MainSceneBackgroundState();\n}\n\nclass MainSceneBackgroundState extends State<MainSceneBackground> {\n  late MainSceneBackgroundNode _backgroundNode;\n\n  @override\n  void initState() {\n    super.initState();\n    _backgroundNode = MainSceneBackgroundNode();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return SpriteWidget(\n      _backgroundNode,\n      transformMode: SpriteBoxTransformMode.fixedWidth,\n    );\n  }\n}\n\nclass MainSceneBackgroundNode extends NodeWithSize {\n  late Sprite _bgTop;\n  late Sprite _bgBottom;\n  late RepeatedImage _background;\n  late RepeatedImage _nebula;\n\n  MainSceneBackgroundNode() : super(const Size(320.0, 320.0)) {\n    // Add background\n    _background = RepeatedImage(_imageMap[\"assets/starfield.png\"]!);\n    addChild(_background);\n\n    StarField starField = StarField(_spriteSheet, 200, true);\n    addChild(starField);\n\n    // Add nebula\n    _nebula = RepeatedImage(_imageMap[\"assets/nebula.png\"]!, BlendMode.plus);\n    addChild(_nebula);\n\n    _bgTop = Sprite.fromImage(_imageMap[\"assets/ui_bg_top.png\"]!);\n    _bgTop.pivot = Offset.zero;\n    _bgTop.size = const Size(320.0, 108.0);\n    addChild(_bgTop);\n\n    _bgBottom = Sprite.fromImage(_imageMap[\"assets/ui_bg_bottom.png\"]!);\n    _bgBottom.pivot = const Offset(0.0, 1.0);\n    _bgBottom.size = const Size(320.0, 97.0);\n    addChild(_bgBottom);\n  }\n\n  @override\n  void paint(Canvas canvas) {\n    canvas.drawRect(const Rect.fromLTWH(0.0, 0.0, 320.0, 320.0),\n        Paint()..color = const Color(0xff000000));\n    super.paint(canvas);\n  }\n\n  @override\n  void spriteBoxPerformedLayout() {\n    _bgBottom.position = Offset(0.0, spriteBox!.visibleArea!.size.height);\n  }\n\n  @override\n  void update(double dt) {\n    _background.move(10.0 * dt);\n    _nebula.move(100.0 * dt);\n  }\n}\n\nclass LaserDisplay extends StatelessWidget {\n  const LaserDisplay({\n    required this.level,\n    Key? key,\n  }) : super(key: key);\n\n  final int level;\n\n  @override\n  Widget build(BuildContext context) {\n    return IgnorePointer(\n      child: SizedBox(\n        child: SpriteWidget(LaserDisplayNode(level)),\n        width: 26.0,\n        height: 26.0,\n      ),\n    );\n  }\n}\n\nclass LaserDisplayNode extends NodeWithSize {\n  LaserDisplayNode(int level) : super(const Size(16.0, 16.0)) {\n    Node placementNode = Node();\n    placementNode.position = const Offset(8.0, 8.0);\n    placementNode.scale = 0.7;\n    addChild(placementNode);\n    addLaserSprites(placementNode, level, 0.0, _spriteSheet);\n  }\n}\n"
  },
  {
    "path": "lib/persistant_game_state.dart",
    "content": "part of game;\n\nclass PersistantGameState {\n  Future load() async {\n    final prefs = await SharedPreferences.getInstance();\n    final json = prefs.getString('game_prefs');\n    if (json == null) return;\n\n    JsonDecoder decoder = const JsonDecoder();\n    Map data = decoder.convert(json);\n\n    coins = data['coins'];\n    _powerupLevels = data['powerUpLevels'].cast<int>();\n    _currentStartingLevel = data['currentStartingLevel'];\n    maxStartingLevel = data['maxStartingLevel'];\n    laserLevel = data['laserLevel'];\n    _lastScore = data['lastScore'];\n    weeklyBestScore = data['bestScore'];\n  }\n\n  Future store() async {\n    final prefs = await SharedPreferences.getInstance();\n\n    Map data = {\n      'coins': coins,\n      'powerUpLevels': _powerupLevels,\n      'currentStartingLevel': _currentStartingLevel,\n      'maxStartingLevel': maxStartingLevel,\n      'laserLevel': laserLevel,\n      'lastScore': _lastScore,\n      'bestScore': weeklyBestScore\n    };\n    JsonEncoder encoder = const JsonEncoder();\n    String json = encoder.convert(data);\n    prefs.setString('game_prefs', json);\n  }\n\n  int coins = 0;\n\n  List<int> _powerupLevels = <int>[0, 0, 0, 0];\n\n  int powerupLevel(PowerUpType type) {\n    return _powerupLevels[type.index];\n  }\n\n  int maxPowerUpLevel = 8;\n\n  int _currentStartingLevel = 0;\n\n  int get currentStartingLevel => _currentStartingLevel;\n\n  set currentStartingLevel(int currentStartingLevel) {\n    if (currentStartingLevel >= 0 && currentStartingLevel <= maxStartingLevel) {\n      _currentStartingLevel = currentStartingLevel;\n    }\n  }\n\n  int maxStartingLevel = 0;\n\n  int laserLevel = 0;\n\n  int maxLaserLevel = 11;\n\n  int _lastScore = 0;\n\n  int get lastScore => _lastScore;\n\n  set lastScore(int lastScore) {\n    _lastScore = lastScore;\n    if (lastScore > weeklyBestScore) weeklyBestScore = lastScore;\n  }\n\n  int weeklyBestScore = 0;\n\n  int powerUpUpgradePrice(PowerUpType type) {\n    int level = powerupLevel(type) + 1;\n    return level * 50 + 50;\n  }\n\n  int powerUpFrames(PowerUpType type) {\n    int level = powerupLevel(type);\n\n    if (type == PowerUpType.speedBoost) {\n      return 150 + 25 * level;\n    } else {\n      return 300 + 50 * level;\n    }\n  }\n\n  bool upgradePowerUp(PowerUpType type) {\n    int price = powerUpUpgradePrice(type);\n\n    if (coins >= price && _powerupLevels[type.index] < maxPowerUpLevel) {\n      coins -= price;\n      _powerupLevels[type.index] += 1;\n      store();\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n  int laserUpgradePrice() {\n    return laserLevel * 100 + 200;\n  }\n\n  bool upgradeLaser() {\n    if (coins >= laserUpgradePrice() && laserLevel < maxLaserLevel) {\n      coins -= laserUpgradePrice();\n      laserLevel++;\n      store();\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n  void reachedLevel(int level) {\n    if (level > maxStartingLevel && level < 9) {\n      maxStartingLevel = level;\n      _currentStartingLevel = level;\n    }\n    store();\n  }\n}\n"
  },
  {
    "path": "lib/player_state.dart",
    "content": "part of game;\n\nclass PlayerState extends Node {\n  PlayerState(this._sheetUI, this._sheetGame, this._gameState) {\n    // Score display\n    _spriteBackgroundScore = Sprite(texture: _sheetUI[\"scoreboard.png\"]!);\n    _spriteBackgroundScore.pivot = const Offset(1.0, 0.0);\n    _spriteBackgroundScore.scale = 0.35;\n    _spriteBackgroundScore.position = const Offset(240.0, 10.0);\n    addChild(_spriteBackgroundScore);\n\n    _scoreDisplay = ScoreDisplay(_sheetUI);\n    _scoreDisplay.position = const Offset(349.0, 49.0);\n    _spriteBackgroundScore.addChild(_scoreDisplay);\n\n    // Coin display\n    _spriteBackgroundCoins = Sprite(texture: _sheetUI[\"coinboard.png\"]!);\n    _spriteBackgroundCoins.pivot = const Offset(1.0, 0.0);\n    _spriteBackgroundCoins.scale = 0.35;\n    _spriteBackgroundCoins.position = const Offset(105.0, 10.0);\n    addChild(_spriteBackgroundCoins);\n\n    _coinDisplay = ScoreDisplay(_sheetUI);\n    _coinDisplay.position = const Offset(252.0, 49.0);\n    _spriteBackgroundCoins.addChild(_coinDisplay);\n\n    laserLevel = _gameState.laserLevel;\n  }\n\n  final SpriteSheet _sheetUI;\n  final SpriteSheet _sheetGame;\n  final PersistantGameState _gameState;\n\n  int laserLevel = 0;\n\n  static const double normalScrollSpeed = 2.0;\n\n  double scrollSpeed = normalScrollSpeed;\n\n  double _scrollSpeedTarget = normalScrollSpeed;\n\n  EnemyBoss? boss;\n\n  late Sprite _spriteBackgroundScore;\n  late ScoreDisplay _scoreDisplay;\n  late Sprite _spriteBackgroundCoins;\n  late ScoreDisplay _coinDisplay;\n\n  int get score => _scoreDisplay.score;\n\n  set score(int score) {\n    _scoreDisplay.score = score;\n    flashBackgroundSprite(_spriteBackgroundScore);\n  }\n\n  int get coins => _coinDisplay.score;\n\n  void addCoin(Coin c) {\n    // Animate coin to the top of the screen\n    Offset startPos = convertPointFromNode(Offset.zero, c);\n    Offset finalPos = const Offset(30.0, 30.0);\n    Offset middlePos = Offset((startPos.dx + finalPos.dx) / 2.0 + 50.0,\n        (startPos.dy + finalPos.dy) / 2.0);\n\n    List<Offset> path = <Offset>[startPos, middlePos, finalPos];\n\n    Sprite sprite = Sprite(texture: _sheetGame[\"coin.png\"]!);\n    sprite.scale = 0.7;\n\n    MotionSpline spline = MotionSpline(\n      setter: (Offset a) => sprite.position = a,\n      points: path,\n      duration: 0.5,\n    );\n    spline.tension = 0.25;\n    MotionTween rotate = MotionTween<double>(\n      setter: (a) => sprite.rotation = a,\n      start: 0.0,\n      end: 360.0,\n      duration: 0.5,\n    );\n    MotionTween scale = MotionTween<double>(\n      setter: (a) => sprite.scale = a,\n      start: 0.7,\n      end: 1.2,\n      duration: 0.5,\n    );\n    MotionGroup group = MotionGroup(motions: [spline, rotate, scale]);\n    sprite.motions.run(\n      MotionSequence(\n        motions: [\n          group,\n          MotionRemoveNode(node: sprite),\n          MotionCallFunction(\n            callback: () {\n              _coinDisplay.score += 1;\n              flashBackgroundSprite(_spriteBackgroundCoins);\n            },\n          ),\n        ],\n      ),\n    );\n\n    addChild(sprite);\n  }\n\n  void activatePowerUp(PowerUpType type) {\n    if (type == PowerUpType.shield) {\n      _shieldFrames += _gameState.powerUpFrames(type);\n    } else if (type == PowerUpType.sideLaser) {\n      _sideLaserFrames += _gameState.powerUpFrames(type);\n    } else if (type == PowerUpType.speedLaser) {\n      _speedLaserFrames += _gameState.powerUpFrames(type);\n    } else if (type == PowerUpType.speedBoost) {\n      _speedBoostFrames += _gameState.powerUpFrames(type);\n      _shieldFrames += _gameState.powerUpFrames(type) + 60;\n    }\n  }\n\n  int _shieldFrames = 0;\n  bool get shieldActive => _shieldFrames > 0 || _speedBoostFrames > 0;\n  bool get shieldDeactivating =>\n      math.max(_shieldFrames, _speedBoostFrames) > 0 &&\n      math.max(_shieldFrames, _speedBoostFrames) < 60;\n\n  int _sideLaserFrames = 0;\n  bool get sideLaserActive => _sideLaserFrames > 0;\n\n  int _speedLaserFrames = 0;\n  bool get speedLaserActive => _speedLaserFrames > 0;\n\n  int _speedBoostFrames = 0;\n  bool get speedBoostActive => _speedBoostFrames > 0;\n\n  void flashBackgroundSprite(Sprite sprite) {\n    sprite.motions.stopAll();\n    MotionTween flash = MotionTween<Color>(\n      setter: (a) => sprite.colorOverlay = a,\n      start: const Color(0x66ccfff0),\n      end: const Color(0x00ccfff0),\n      duration: 0.3,\n    );\n    sprite.motions.run(flash);\n  }\n\n  @override\n  void update(double dt) {\n    if (_shieldFrames > 0) {\n      _shieldFrames--;\n    }\n    if (_sideLaserFrames > 0) {\n      _sideLaserFrames--;\n    }\n    if (_speedLaserFrames > 0) {\n      _speedLaserFrames--;\n    }\n    if (_speedBoostFrames > 0) {\n      _speedBoostFrames--;\n    }\n\n    // Update speed\n    if (boss != null) {\n      Offset globalBossPos = boss!.convertPointToBoxSpace(Offset.zero);\n      if (globalBossPos.dy > (_gameSizeHeight - 400.0)) {\n        _scrollSpeedTarget = 0.0;\n      } else {\n        _scrollSpeedTarget = normalScrollSpeed;\n      }\n    } else {\n      if (speedBoostActive) {\n        _scrollSpeedTarget = normalScrollSpeed * 6.0;\n      } else {\n        _scrollSpeedTarget = normalScrollSpeed;\n      }\n    }\n\n    scrollSpeed = GameMath.filter(scrollSpeed, _scrollSpeedTarget, 0.1);\n  }\n}\n\nclass ScoreDisplay extends Node {\n  ScoreDisplay(this._sheetUI);\n\n  int _score = 0;\n\n  int get score => _score;\n\n  set score(int score) {\n    _score = score;\n    _dirtyScore = true;\n  }\n\n  final SpriteSheet _sheetUI;\n\n  bool _dirtyScore = true;\n\n  @override\n  void update(double dt) {\n    if (_dirtyScore) {\n      removeAllChildren();\n\n      String scoreStr = _score.toString();\n      double xPos = -37.0;\n      for (int i = scoreStr.length - 1; i >= 0; i--) {\n        String numStr = scoreStr.substring(i, i + 1);\n        Sprite numSprite = Sprite(texture: _sheetUI[\"number_$numStr.png\"]!);\n        numSprite.position = Offset(xPos, 0.0);\n        addChild(numSprite);\n        xPos -= 37.0;\n      }\n      _dirtyScore = false;\n    }\n  }\n}\n"
  },
  {
    "path": "lib/power_bar.dart",
    "content": "part of game;\n\nclass PowerBar extends NodeWithSize {\n  PowerBar(Size size, [this.power = 1.0]) : super(size);\n\n  double power;\n\n  final Paint _paintFill = Paint()..color = const Color(0xffffffff);\n  final Paint _paintOutline = Paint()\n    ..color = const Color(0xffffffff)\n    ..strokeWidth = 1.0\n    ..style = ui.PaintingStyle.stroke;\n\n  @override\n  void paint(Canvas canvas) {\n    applyTransformForPivot(canvas);\n\n    canvas.drawRect(\n      Rect.fromLTRB(0.0, 0.0, size.width - 0.0, size.height - 0.0),\n      _paintOutline,\n    );\n    canvas.drawRect(\n      Rect.fromLTRB(2.0, 2.0, (size.width - 2.0) * power, size.height - 2.0),\n      _paintFill,\n    );\n  }\n}\n"
  },
  {
    "path": "lib/render_coordinate_system.dart",
    "content": "part of game;\n\nenum CoordinateSystemType {\n  fixedWidth,\n  fixedHeight,\n  stretch,\n}\n\nclass RenderCoordinateSystem extends RenderProxyBox {\n  RenderCoordinateSystem({\n    required Size systemSize,\n    required CoordinateSystemType systemType,\n    RenderBox? child,\n  }) : super(child) {\n    _systemSize = systemSize;\n    _systemType = systemType;\n  }\n\n  Size get systemSize => _systemSize;\n  late Size _systemSize;\n  set systemSize(Size systemSize) {\n    if (_systemSize == systemSize) return;\n    _systemSize = systemSize;\n    markNeedsPaint();\n  }\n\n  CoordinateSystemType get systemType => _systemType;\n  late CoordinateSystemType _systemType;\n  set systemType(CoordinateSystemType systemType) {\n    if (_systemType == systemType) return;\n    _systemType = systemType;\n    markNeedsPaint();\n  }\n\n  Matrix4 get _effectiveTransform {\n    double scaleX = 1.0;\n    double scaleY = 1.0;\n\n    switch (systemType) {\n      case CoordinateSystemType.stretch:\n        scaleX = size.width / systemSize.width;\n        scaleY = size.height / systemSize.height;\n        break;\n      case CoordinateSystemType.fixedWidth:\n        scaleX = size.width / systemSize.width;\n        scaleY = scaleX;\n        break;\n      case CoordinateSystemType.fixedHeight:\n        scaleY = size.height / systemSize.height;\n        scaleX = scaleY;\n        break;\n      default:\n        assert(false);\n    }\n\n    Matrix4 transformMatrix = Matrix4.identity();\n    transformMatrix.scale(scaleX, scaleY);\n\n    return transformMatrix;\n  }\n\n  @override\n  bool hitTest(HitTestResult result, {required Offset position}) {\n    Matrix4 inverse = Matrix4.zero();\n    // TODO(abarth): Check the determinant for degeneracy.\n    inverse.copyInverse(_effectiveTransform);\n\n    Vector3 position3 = Vector3(position.dx, position.dy, 0.0);\n    Vector3 transformed3 = inverse.transform3(position3);\n    Offset transformed = Offset(transformed3.x, transformed3.y);\n    return super.hitTest(result as BoxHitTestResult, position: transformed);\n  }\n\n  @override\n  void paint(PaintingContext context, Offset offset) {\n    if (child != null) {\n      Matrix4 transform = _effectiveTransform;\n      Offset? childOffset = MatrixUtils.getAsTranslation(transform);\n      if (childOffset == null) {\n        context.pushTransform(needsCompositing, offset, transform, super.paint);\n      } else {\n        super.paint(context, offset + childOffset);\n      }\n    }\n  }\n\n  @override\n  void applyPaintTransform(RenderObject child, Matrix4 transform) {\n    transform.multiply(_effectiveTransform);\n    super.applyPaintTransform(child, transform);\n  }\n\n//  void debugDescribeChildren(List<String> settings) {\n//    super.debugDescribeChildren(settings);\n//    settings.add('systemSize: $systemSize');\n//    settings.add('systemType: $systemType');\n//  }\n\n  @override\n  bool get sizedByParent => true;\n\n  @override\n  void performResize() {\n    size = constraints.biggest;\n  }\n\n  // Perform layout\n  @override\n  void performLayout() {\n    double xScale = _effectiveTransform[0];\n    double yScale = _effectiveTransform[5];\n\n    if (child != null) {\n      child!.layout(BoxConstraints.tightFor(\n          width: size.width / xScale, height: size.height / yScale));\n    }\n  }\n}\n"
  },
  {
    "path": "lib/repeated_image.dart",
    "content": "part of game;\n\nclass RepeatedImage extends Node {\n  late Sprite _sprite0;\n  late Sprite _sprite1;\n\n  RepeatedImage(ui.Image image, [ui.BlendMode? mode]) {\n    _sprite0 = Sprite.fromImage(image);\n    _sprite0.size = const Size(1024.0, 1024.0);\n    _sprite0.pivot = Offset.zero;\n    _sprite1 = Sprite.fromImage(image);\n    _sprite1.size = const Size(1024.0, 1024.0);\n    _sprite1.pivot = Offset.zero;\n    _sprite1.position = const Offset(0.0, -1024.0);\n\n    if (mode != null) {\n      _sprite0.blendMode = mode;\n      _sprite1.blendMode = mode;\n    }\n\n    addChild(_sprite0);\n    addChild(_sprite1);\n  }\n\n  void move(double dy) {\n    double yPos = (position.dy + dy) % 1024.0;\n    position = Offset(0.0, yPos);\n  }\n}\n"
  },
  {
    "path": "lib/sound_assets.dart",
    "content": "part of game;\n\nclass SoundAssets {\n  final AudioPlayer _musicPlayer = AudioPlayer();\n  final Map<String, AudioPlayer> _effectPlayers = {};\n  SoundAssets(this.bundle);\n\n  final AssetBundle bundle;\n\n  Future<void> loadEffect(String name) async {\n    final player = AudioPlayer();\n    player.setAsset(_effectPathForName(name));\n    await player.load();\n    _effectPlayers[name] = player;\n  }\n\n  Future<void> loadMusic(String name) async {\n    final player = AudioPlayer();\n    player.setAsset(_musicPathForName(name));\n    await player.load();\n  }\n\n  void playEffect(String name) {\n    _effectPlayers[name]?.setAsset(_effectPathForName(name));\n    _effectPlayers[name]?.play();\n  }\n\n  void playMusic(String name) {\n    _musicPlayer.setAsset(_musicPathForName(name));\n    _musicPlayer.setLoopMode(LoopMode.all);\n    _musicPlayer.play();\n  }\n\n  String _effectPathForName(String name) => 'assets/$name.wav';\n\n  String _musicPathForName(String name) => 'assets/$name.mp3';\n}\n"
  },
  {
    "path": "lib/star_field.dart",
    "content": "part of game;\n\nclass StarField extends NodeWithSize {\n  late ui.Image _image;\n  final SpriteSheet _spriteSheet;\n  final int _numStars;\n  final bool _autoScroll;\n\n  List<Offset> _starPositions = [];\n  List<double> _starScales = [];\n  List<Rect> _rects = [];\n  List<Color>? _colors;\n\n  final double _padding = 50.0;\n  Size _paddedSize = Size.zero;\n\n  final Paint _paint = Paint()\n    ..filterQuality = ui.FilterQuality.low\n    ..isAntiAlias = false\n    ..blendMode = ui.BlendMode.plus;\n\n  StarField(this._spriteSheet, this._numStars, [this._autoScroll = false])\n      : super(Size.zero) {\n    _image = _spriteSheet.image;\n    addStars();\n  }\n\n  void addStars() {\n    _starPositions = [];\n    _starScales = [];\n    _colors = [];\n    _rects = [];\n\n    if (spriteBox == null) {\n      size = const Size(2048, 2048);\n    } else {\n      size = spriteBox!.visibleArea!.size;\n    }\n    _paddedSize =\n        Size(size.width + _padding * 2.0, size.height + _padding * 2.0);\n\n    for (int i = 0; i < _numStars; i++) {\n      _starPositions.add(\n        Offset(\n          randomDouble() * _paddedSize.width,\n          randomDouble() * _paddedSize.height,\n        ),\n      );\n      _starScales.add(randomDouble() * 0.4);\n      _colors!.add(\n        Color.fromARGB(\n          (255.0 * (randomDouble() * 0.5 + 0.5)).toInt(),\n          255,\n          255,\n          255,\n        ),\n      );\n      _rects.add(_spriteSheet[\"star_${randomInt(2)}.png\"]!.frame);\n    }\n  }\n\n  @override\n  void spriteBoxPerformedLayout() {\n    addStars();\n  }\n\n  @override\n  void paint(Canvas canvas) {\n    // Create a transform for each star\n    List<ui.RSTransform> transforms = <ui.RSTransform>[];\n    for (int i = 0; i < _numStars; i++) {\n      ui.RSTransform transform = ui.RSTransform(_starScales[i], 0.0,\n          _starPositions[i].dx - _padding, _starPositions[i].dy - _padding);\n\n      transforms.add(transform);\n    }\n\n    // Draw the stars\n    canvas.drawAtlas(_image, transforms, _rects, _colors, ui.BlendMode.modulate,\n        null, _paint);\n  }\n\n  void move(double dx, double dy) {\n    for (int i = 0; i < _numStars; i++) {\n      double xPos = _starPositions[i].dx;\n      double yPos = _starPositions[i].dy;\n      double scale = _starScales[i];\n\n      xPos += dx * scale;\n      yPos += dy * scale;\n\n      if (xPos >= _paddedSize.width) xPos -= _paddedSize.width;\n      if (xPos < 0) xPos += _paddedSize.width;\n      if (yPos >= _paddedSize.height) yPos -= _paddedSize.height;\n      if (yPos < 0) yPos += _paddedSize.height;\n\n      _starPositions[i] = Offset(xPos, yPos);\n    }\n  }\n\n  @override\n  void update(double dt) {\n    if (_autoScroll) {\n      move(0.0, dt * 100.0);\n    }\n  }\n}\n"
  },
  {
    "path": "lib/widgets.dart",
    "content": "part of game;\n\nclass TextureImage extends StatelessWidget {\n  const TextureImage({\n    Key? key,\n    required this.texture,\n    this.width = 128.0,\n    this.height = 128.0,\n  }) : super(key: key);\n\n  final SpriteTexture texture;\n  final double width;\n  final double height;\n\n  @override\n  Widget build(BuildContext context) {\n    return SizedBox(\n      width: width,\n      height: height,\n      child: CustomPaint(\n        painter: TextureImagePainter(texture, width, height),\n      ),\n    );\n  }\n}\n\nclass TextureImagePainter extends CustomPainter {\n  TextureImagePainter(this.texture, this.width, this.height);\n\n  final SpriteTexture texture;\n  final double width;\n  final double height;\n\n  @override\n  void paint(Canvas canvas, Size size) {\n    canvas.save();\n    canvas.scale(\n      size.width / texture.size.width,\n      size.height / texture.size.height,\n    );\n    texture.drawTexture(canvas, Offset.zero, Paint());\n    canvas.restore();\n  }\n\n  @override\n  bool shouldRepaint(TextureImagePainter oldDelegate) {\n    return oldDelegate.texture != texture ||\n        oldDelegate.width != width ||\n        oldDelegate.height != height;\n  }\n}\n\nclass TextureButton extends StatefulWidget {\n  const TextureButton(\n      {Key? key,\n      required this.onPressed,\n      required this.texture,\n      this.textureDown,\n      this.width = 128.0,\n      this.height = 128.0,\n      this.label,\n      this.textStyle,\n      this.textAlign = TextAlign.center,\n      this.labelOffset = Offset.zero})\n      : super(key: key);\n\n  final VoidCallback onPressed;\n  final SpriteTexture texture;\n  final SpriteTexture? textureDown;\n  final TextStyle? textStyle;\n  final TextAlign textAlign;\n  final String? label;\n  final double width;\n  final double height;\n  final Offset labelOffset;\n\n  @override\n  TextureButtonState createState() => TextureButtonState();\n}\n\nclass TextureButtonState extends State<TextureButton> {\n  bool _highlight = false;\n\n  @override\n  Widget build(BuildContext context) {\n    return GestureDetector(\n        child: SizedBox(\n            width: widget.width,\n            height: widget.height,\n            child:\n                CustomPaint(painter: TextureButtonPainter(widget, _highlight))),\n        onTapDown: (_) {\n          setState(() {\n            _highlight = true;\n          });\n        },\n        onTap: () {\n          setState(() {\n            _highlight = false;\n          });\n          widget.onPressed();\n        },\n        onTapCancel: () {\n          setState(() {\n            _highlight = false;\n          });\n        });\n  }\n}\n\nclass TextureButtonPainter extends CustomPainter {\n  TextureButtonPainter(this.config, this.highlight);\n\n  final TextureButton config;\n  final bool highlight;\n\n  @override\n  void paint(Canvas canvas, Size size) {\n    canvas.save();\n    if (highlight) {\n      // Draw down state\n      if (config.textureDown != null) {\n        canvas.scale(\n          size.width / config.textureDown!.size.width,\n          size.height / config.textureDown!.size.height,\n        );\n        config.textureDown!.drawTexture(canvas, Offset.zero, Paint());\n      } else {\n        canvas.scale(\n          size.width / config.texture.size.width,\n          size.height / config.texture.size.height,\n        );\n        config.texture.drawTexture(\n          canvas,\n          Offset.zero,\n          Paint()\n            ..colorFilter = const ColorFilter.mode(\n              Color(0x66000000),\n              BlendMode.srcATop,\n            ),\n        );\n      }\n    } else {\n      // Draw up state\n      canvas.scale(size.width / config.texture.size.width,\n          size.height / config.texture.size.height);\n      config.texture.drawTexture(canvas, Offset.zero, Paint());\n    }\n    canvas.restore();\n\n    if (config.label != null) {\n      TextStyle style;\n      if (config.textStyle == null) {\n        style = const TextStyle(fontSize: 24.0, fontWeight: FontWeight.w700);\n      } else {\n        style = config.textStyle!;\n      }\n\n      TextSpan textSpan = TextSpan(style: style, text: config.label);\n      TextPainter painter = TextPainter(\n        text: textSpan,\n        textAlign: config.textAlign,\n        textDirection: TextDirection.ltr,\n      );\n\n      painter.layout(minWidth: size.width, maxWidth: size.width);\n      painter.paint(\n          canvas,\n          Offset(0.0, size.height / 2.0 - painter.height / 2.0) +\n              config.labelOffset);\n    }\n  }\n\n  @override\n  bool shouldRepaint(TextureButtonPainter oldDelegate) {\n    return oldDelegate.highlight != highlight ||\n        oldDelegate.config.texture != config.texture ||\n        oldDelegate.config.textureDown != config.textureDown ||\n        oldDelegate.config.textStyle != config.textStyle ||\n        oldDelegate.config.label != config.label ||\n        oldDelegate.config.width != config.width ||\n        oldDelegate.config.height != config.height;\n  }\n}\n\nclass AppFrame extends StatelessWidget {\n  const AppFrame({Key? key, required this.child}) : super(key: key);\n\n  final Widget child;\n\n  static const _minRatio = 1.5;\n\n  @override\n  Widget build(BuildContext context) {\n    return LayoutBuilder(builder: (context, constraints) {\n      final ratio = constraints.maxHeight / constraints.maxWidth;\n\n      if (ratio > _minRatio) {\n        return child;\n      } else {\n        final width = constraints.maxHeight / _minRatio;\n        final horizontalInset = (constraints.maxWidth - width) / 2;\n\n        return Stack(\n          children: [\n            Container(\n              color: const Color(0xFF222244),\n            ),\n            Positioned(\n              child: ClipRect(\n                child: child,\n              ),\n              top: 0,\n              bottom: 0,\n              left: horizontalInset,\n              right: horizontalInset,\n            ),\n          ],\n        );\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "pubspec.yaml",
    "content": "name: spaceblast\npublish_to: 'none'\ndescription: A SpriteWidget demo.\nversion: 0.9.0\n\nenvironment:\n  sdk: '>=2.12.0 <3.0.0'\n\ndependencies:\n  flutter:\n    sdk: flutter\n  spritewidget:\n    path: ../spritewidget\n  cupertino_icons: ^1.0.4\n  shared_preferences: ^2.0.13\n  just_audio: ^0.9.20\n\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n  flutter_lints: 1.0.0\n\nflutter:\n  uses-material-design: true\n\n  assets:\n    - assets/nebula.png\n    - assets/sprites.json\n    - assets/sprites.png\n    - assets/starfield.png\n    - assets/game_ui.png\n    - assets/game_ui.json\n    - assets/ui_bg_top.png\n    - assets/ui_bg_bottom.png\n    - assets/ui_popup.png\n    - assets/music_game.mp3\n    - assets/music_boss.mp3\n    - assets/music_intro.mp3\n    - assets/explosion_0.wav\n    - assets/explosion_1.wav\n    - assets/explosion_2.wav\n    - assets/explosion_boss.wav\n    - assets/explosion_player.wav\n    - assets/laser.wav\n    - assets/hit.wav\n    - assets/levelup.wav\n    - assets/pickup_0.wav\n    - assets/pickup_1.wav\n    - assets/pickup_2.wav\n    - assets/pickup_powerup.wav\n    - assets/click.wav\n    - assets/buy_upgrade.wav\n    - fonts/Orbitron-Medium.ttf\n  fonts:\n    - family: Orbitron\n      fonts:\n        - asset: fonts/Orbitron-Medium.ttf\n"
  },
  {
    "path": "web/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <!--\n    If you are serving your web app in a path other than the root, change the\n    href value below to reflect the base path you are serving from.\n\n    The path provided below has to start and end with a slash \"/\" in order for\n    it to work correctly.\n\n    For more details:\n    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base\n\n    This is a placeholder for base href that will be replaced by the value of\n    the `--base-href` argument provided to `flutter build`.\n  -->\n  <base href=\"$FLUTTER_BASE_HREF\">\n\n  <meta charset=\"UTF-8\">\n  <meta content=\"IE=Edge\" http-equiv=\"X-UA-Compatible\">\n  <meta name=\"description\" content=\"A new Flutter project.\">\n\n  <!-- iOS meta tags & icons -->\n  <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n  <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n  <meta name=\"apple-mobile-web-app-title\" content=\"spaceblast\">\n  <link rel=\"apple-touch-icon\" href=\"icons/Icon-192.png\">\n\n  <!-- Favicon -->\n  <link rel=\"icon\" type=\"image/png\" href=\"favicon.png\"/>\n\n  <title>SpaceBlast - SpriteWidget demo</title>\n  <link rel=\"manifest\" href=\"manifest.json\">\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"styles.css\">\n</head>\n<body>\n  <!-- This script installs service_worker.js to provide PWA functionality to\n       application. For more information, see:\n       https://developers.google.com/web/fundamentals/primers/service-workers -->\n  <script>\n    var serviceWorkerVersion = null;\n    var scriptLoaded = false;\n    function loadMainDartJs() {\n      if (scriptLoaded) {\n        return;\n      }\n      scriptLoaded = true;\n      var scriptTag = document.createElement('script');\n      scriptTag.src = 'main.dart.js';\n      scriptTag.type = 'application/javascript';\n      document.body.append(scriptTag);\n    }\n\n    if ('serviceWorker' in navigator) {\n      // Service workers are supported. Use them.\n      window.addEventListener('load', function () {\n        // Wait for registration to finish before dropping the <script> tag.\n        // Otherwise, the browser will load the script multiple times,\n        // potentially different versions.\n        var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;\n        navigator.serviceWorker.register(serviceWorkerUrl)\n          .then((reg) => {\n            function waitForActivation(serviceWorker) {\n              serviceWorker.addEventListener('statechange', () => {\n                if (serviceWorker.state == 'activated') {\n                  console.log('Installed new service worker.');\n                  loadMainDartJs();\n                }\n              });\n            }\n            if (!reg.active && (reg.installing || reg.waiting)) {\n              // No active web worker and we have installed or are installing\n              // one for the first time. Simply wait for it to activate.\n              waitForActivation(reg.installing || reg.waiting);\n            } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {\n              // When the app updates the serviceWorkerVersion changes, so we\n              // need to ask the service worker to update.\n              console.log('New service worker available.');\n              reg.update();\n              waitForActivation(reg.installing);\n            } else {\n              // Existing service worker is still good.\n              console.log('Loading app from service worker.');\n              loadMainDartJs();\n            }\n          });\n\n        // If service worker doesn't succeed in a reasonable amount of time,\n        // fallback to plaint <script> tag.\n        setTimeout(() => {\n          if (!scriptLoaded) {\n            console.warn(\n              'Failed to load app from service worker. Falling back to plain <script> tag.',\n            );\n            loadMainDartJs();\n          }\n        }, 4000);\n      });\n    } else {\n      // Service workers not supported. Just drop the <script> tag.\n      loadMainDartJs();\n    }\n  </script>\n  <div class=\"center\">\n    <img src=\"img/spinner.gif\" width=\"64\" height=\"64\" />\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "web/manifest.json",
    "content": "{\n    \"name\": \"spaceblast\",\n    \"short_name\": \"spaceblast\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#0175C2\",\n    \"theme_color\": \"#0175C2\",\n    \"description\": \"A new Flutter project.\",\n    \"orientation\": \"portrait-primary\",\n    \"prefer_related_applications\": false,\n    \"icons\": [\n        {\n            \"src\": \"icons/Icon-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        },\n        {\n            \"src\": \"icons/Icon-maskable-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"maskable\"\n        }\n    ]\n}\n"
  },
  {
    "path": "web/styles.css",
    "content": "html,\nbody {\n    background-color: #333333\n}\n\n.center {\n    margin: 0;\n    position: absolute;\n    top: 50%;\n    left: 50%;\n    margin-right: -50%;\n    transform: translate(-50%, -50%)\n}"
  }
]