├── .circleci
└── config.yml
├── .gitignore
├── .gitmodules
├── .metadata
├── LICENSE
├── README.md
├── analysis_options.yaml
├── android
├── app
│ ├── build.gradle
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ └── dev
│ │ │ └── flutter
│ │ │ └── devrpg
│ │ │ └── MainActivity.kt
│ │ └── res
│ │ ├── drawable
│ │ └── launch_background.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ └── values
│ │ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── artwork
└── speech bubble.pxm
├── assets
├── docs
│ ├── code_chomper_alpha.dart
│ ├── code_chomper_beta.dart
│ ├── dev-rpg-research-tree.graphml
│ └── research-tree.png
├── flare
│ ├── Chomper FUI Type.flr
│ ├── Chomper.flr
│ ├── CodeIcon.flr
│ ├── Coin.flr
│ ├── CoordinationIcon.flr
│ ├── CowboyCoder.flr
│ ├── Designer.flr
│ ├── EngineeringIcon.flr
│ ├── Joy.flr
│ ├── NotificationIcon.flr
│ ├── ProgramManager.flr
│ ├── SelectArrow.flr
│ ├── Sourcerer.flr
│ ├── Stars.flr
│ ├── TasksIcon.flr
│ ├── TeamIcon.flr
│ ├── Tester.flr
│ ├── TheArchitect.flr
│ ├── TheHacker.flr
│ ├── TheJack.flr
│ ├── TheRefactorer.flr
│ ├── UXResearcher.flr
│ ├── Users.flr
│ └── UxIcon.flr
├── fonts
│ ├── Gotham XNarrow Medium.otf
│ ├── Montserrat-Bold.otf
│ ├── Montserrat-Medium.otf
│ ├── Montserrat-Regular.otf
│ ├── Roboto-Regular.ttf
│ ├── RobotoCondensed-Bold.ttf
│ ├── SpaceMono-Bold.ttf
│ └── SpaceMono-Regular.ttf
├── images
│ ├── 2.0x
│ │ ├── 2dimensions.png
│ │ ├── 2dimensions_wide.png
│ │ └── flare_logo.png
│ ├── 2dimensions.png
│ ├── 2dimensions_wide.png
│ ├── 3.0x
│ │ ├── 2dimensions.png
│ │ ├── 2dimensions_wide.png
│ │ └── flare_logo.png
│ ├── flare_logo.png
│ └── flutter_logo.png
└── style_sphinx
│ ├── green_bed.png
│ ├── orange_cat.png
│ ├── pyramid.png
│ ├── red_bed.png
│ ├── sphinx.png
│ ├── sphinx_no_glasses.png
│ ├── start_background.png
│ ├── sunglasses.png
│ └── yellow_cat.png
├── ios
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── WorkspaceSettings.xcsettings
└── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon.png
│ │ ├── icon_20pt.png
│ │ ├── icon_20pt@2x.png
│ │ ├── icon_20pt@3x.png
│ │ ├── icon_29pt.png
│ │ ├── icon_29pt@2x.png
│ │ ├── icon_29pt@3x.png
│ │ ├── icon_40pt.png
│ │ ├── icon_40pt@2x.png
│ │ ├── icon_40pt@3x.png
│ │ ├── icon_60pt@2x.png
│ │ ├── icon_60pt@3x.png
│ │ ├── icon_76pt.png
│ │ ├── icon_76pt@2x.png
│ │ └── icon_83.5@2x.png
│ └── LaunchImage.imageset
│ │ ├── Contents.json
│ │ ├── LaunchImage.png
│ │ ├── LaunchImage@2x.png
│ │ ├── LaunchImage@3x.png
│ │ └── README.md
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ └── Runner-Bridging-Header.h
├── lib
├── main.dart
└── src
│ ├── about_screen.dart
│ ├── code_chomper
│ ├── code_chomper.dart
│ └── code_chomper_screen.dart
│ ├── game_screen.dart
│ ├── game_screen
│ ├── add_task_button.dart
│ ├── bug_picker_modal.dart
│ ├── character_modal.dart
│ ├── character_pool_page.dart
│ ├── character_style.dart
│ ├── project_picker_modal.dart
│ ├── task_pool_page.dart
│ └── team_picker_modal.dart
│ ├── game_screen_slim.dart
│ ├── game_screen_wide.dart
│ ├── rpg_layout_builder.dart
│ ├── shared_state
│ ├── game
│ │ ├── bug.dart
│ │ ├── character.dart
│ │ ├── character_pool.dart
│ │ ├── company.dart
│ │ ├── skill.dart
│ │ ├── src
│ │ │ ├── aspect.dart
│ │ │ ├── aspect_container.dart
│ │ │ └── child_aspect.dart
│ │ ├── task.dart
│ │ ├── task_blueprint.dart
│ │ ├── task_pool.dart
│ │ ├── task_prerequisite.dart
│ │ ├── task_tree
│ │ │ ├── animations.dart
│ │ │ ├── backend_infrastructure.dart
│ │ │ ├── beta.dart
│ │ │ ├── design.dart
│ │ │ ├── geolocation.dart
│ │ │ ├── image_messaging.dart
│ │ │ ├── launch.dart
│ │ │ ├── natural_language.dart
│ │ │ ├── pre_alpha.dart
│ │ │ ├── pre_launch.dart
│ │ │ ├── responsive_design.dart
│ │ │ ├── task_tree.dart
│ │ │ ├── theme.dart
│ │ │ ├── tree_hierarchy.dart
│ │ │ └── ux_testing.dart
│ │ ├── work_item.dart
│ │ └── world.dart
│ └── user.dart
│ ├── style.dart
│ ├── style_sphinx
│ ├── axis_questions.dart
│ ├── breathing_animations.dart
│ ├── flex_questions.dart
│ ├── kittens.dart
│ ├── question_arguments.dart
│ ├── question_scaffold.dart
│ ├── sphinx_buttton.dart
│ ├── sphinx_image.dart
│ ├── sphinx_screen.dart
│ ├── success_route.dart
│ └── text_bubble.dart
│ ├── welcome_screen.dart
│ └── widgets
│ ├── app_bar
│ ├── coin_badge.dart
│ ├── joy_badge.dart
│ ├── stat_badge.dart
│ ├── stat_separator.dart
│ └── users_badge.dart
│ ├── buttons
│ ├── welcome_button.dart
│ └── wide_button.dart
│ ├── flare
│ ├── desaturated_actor.dart
│ ├── hiring_bust.dart
│ ├── hiring_particles.dart
│ ├── skill_icon.dart
│ ├── start_screen_hero.dart
│ ├── warmup_flare.dart
│ └── work_team.dart
│ ├── game_over.dart
│ ├── keyboard.dart
│ ├── prowess_progress.dart
│ ├── screen_layout_builder.dart
│ ├── skill_badge.dart
│ ├── task_picker
│ ├── task_picker_header.dart
│ └── task_picker_item.dart
│ └── work_items
│ ├── bug_header.dart
│ ├── bug_list_item.dart
│ ├── skill_dot.dart
│ ├── task_header.dart
│ ├── task_list_item.dart
│ ├── tasks_button_header.dart
│ ├── tasks_section_header.dart
│ ├── work_list_item.dart
│ └── work_list_progress.dart
├── pubspec.yaml
├── test
├── character_test.dart
├── style_sphinx
│ ├── question_arguments_test.dart
│ └── sphinx_button_test.dart
├── widget_test.dart
└── world_test.dart
├── test_driver
├── .gitignore
├── durations.tsv
├── generate-graphs.R
├── parse_timeline.dart
├── perf_stats.tsv
├── performance.dart
└── performance_test.dart
└── tool
└── lock_android_scaling.sh
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | jobs:
3 | test:
4 |
5 | docker:
6 | - image: circleci/android:api-28
7 |
8 | environment:
9 | JVM_OPTS: -Xmx3200m
10 |
11 | steps:
12 | - checkout
13 | - run:
14 | name: "Update Submodules"
15 | command: |
16 | git submodule init
17 | git submodule update --recursive
18 | - run:
19 | name: Install Flutter
20 | command: |
21 | git clone https://github.com/flutter/flutter.git -b v1.5.4
22 | ./flutter/bin/flutter doctor
23 | ./flutter/bin/flutter packages get
24 |
25 | - run:
26 | name: Check formatting
27 | command: |
28 | ./flutter/bin/cache/dart-sdk/bin/dartfmt -n --set-exit-if-changed ./lib
29 |
30 | - run:
31 | name: Static Analysis
32 | command: |
33 | ./flutter/bin/cache/dart-sdk/bin/dartanalyzer --fatal-infos --fatal-warnings ./lib
34 |
35 | - run:
36 | name: Test App
37 | command: |
38 | ./flutter/bin/flutter test
39 |
40 | - run:
41 | name: Build App
42 | command: |
43 | ./flutter/bin/flutter build apk
44 |
45 | workflows:
46 | version: 2
47 | build:
48 | jobs:
49 | - test
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.lock
4 | *.log
5 | *.pyc
6 | *.swp
7 | .DS_Store
8 | .atom/
9 | .buildlog/
10 | .history
11 | .svn/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # Visual Studio Code related
20 | .vscode/
21 |
22 | # Flutter/Dart/Pub related
23 | **/doc/api/
24 | .dart_tool/
25 | .flutter-plugins
26 | .packages
27 | .pub-cache/
28 | .pub/
29 | build/
30 |
31 | # Android related
32 | **/android/**/gradle-wrapper.jar
33 | **/android/.gradle
34 | **/android/captures/
35 | **/android/gradlew
36 | **/android/gradlew.bat
37 | **/android/local.properties
38 | **/android/**/GeneratedPluginRegistrant.java
39 |
40 | # iOS/XCode related
41 | **/ios/**/*.mode1v3
42 | **/ios/**/*.mode2v3
43 | **/ios/**/*.moved-aside
44 | **/ios/**/*.pbxuser
45 | **/ios/**/*.perspectivev3
46 | **/ios/**/*sync/
47 | **/ios/**/.sconsign.dblite
48 | **/ios/**/.tags*
49 | **/ios/**/.vagrant/
50 | **/ios/**/DerivedData/
51 | **/ios/**/Icon?
52 | **/ios/**/Pods/
53 | **/ios/**/.symlinks/
54 | **/ios/**/profile
55 | **/ios/**/xcuserdata
56 | **/ios/.generated/
57 | **/ios/Flutter/App.framework
58 | **/ios/Flutter/Flutter.framework
59 | **/ios/Flutter/Generated.xcconfig
60 | **/ios/Flutter/app.flx
61 | **/ios/Flutter/app.zip
62 | **/ios/Flutter/flutter_assets/
63 | **/ios/ServiceDefinitions.json
64 | **/ios/Runner/GeneratedPluginRegistrant.*
65 |
66 | # Exceptions to above rules.
67 | !**/ios/**/default.mode1v3
68 | !**/ios/**/default.mode2v3
69 | !**/ios/**/default.pbxuser
70 | !**/ios/**/default.perspectivev3
71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
72 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/.gitmodules
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 5391447fae6209bb21a89e6a5a6583cac1af9b4b
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 2D, Inc
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Developer Quest
2 |
3 | Become a tech lead, slay bugs, and don't get fired.
4 |
5 | All in Flutter.
6 |
7 | ## Research tree
8 |
9 | The game progression is based on a "research tree" of tasks. The tree is defined in code
10 | in `lib/src/shared_state/task_tree` but for clarity it is also kept as a diagram
11 | in `assets/docs`. Here's the PNG.
12 |
13 | 
14 |
15 | ## Performance testing
16 |
17 | Attach a real device and run the following command from the root of the repo:
18 |
19 | ```sh
20 | flutter drive --target=test_driver/performance.dart --profile
21 | ```
22 |
23 | This will do an automated run-through of the app, and will save the output to files.
24 |
25 | * Look into to `build/walkthrough-*.json` files for detailed summaries of each run.
26 | * Look at `test_driver/perf_stats.tsv` to compare latest runs with historical data.
27 | * Run `Rscript test_driver/generate-graphs.R` (assuming you have R installed) to generate
28 | boxplots of the latest runs. This will show up as `test_driver/*.pdf` files.
29 | * Peruse the raw data file (used by R to generate the boxplots) by opening the
30 | `durations.tsv` file. These files contain build and rasterization times for each frame
31 | for every run.
32 |
33 | If you want to get several runs at once, you can use something like the following command:
34 |
35 | ```sh
36 | DESC="my change" bash -c 'for i in {1..5}; do flutter drive --target=test_driver/performance.dart --profile; sleep 1; done'
37 | ```
38 |
39 | Why run several times when we get so many data points on each walkthrough? With several identical
40 | walkthroughs it's possible to visually check variance between runs. Even with box plots,
41 | these nuances get lost in the summary stats, so it's hard to see whether a change actually
42 | brought any performance improvement or not. Running several times also eliminates
43 | the effect of extremely bad luck, like for example when Android decides to update some app while
44 | test is running.
45 |
46 | ### Lock CPU and GPU speed for your performance test device
47 |
48 | Run the following command when your performance test device is attached via USB.
49 |
50 | ```bash
51 | ./tool/lock_android_scaling.sh
52 | ```
53 |
54 | WARNING:
55 |
56 | * This only works for rooted devices.
57 | * This only works for Nexus 5. The specifics of scaling lock are different from device to device.
58 | You can modify the script to your needs, following
59 | [this template](https://github.com/google/skia/blob/master/infra/bots/recipe_modules/flavor/android.py)
60 | and
61 | [/sys/devices/system/cpu documentation](https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-devices-system-cpu).
62 |
63 | ### Where to store the profiling data
64 |
65 | You probably don't want to check the `*.tsv` output files into the repo. For that,
66 | run `git update-index --assume-unchanged test_driver/*.tsv` in the root dir. This is a one time
67 | command per machine.
68 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | analyzer:
2 | strong-mode:
3 | implicit-casts: false
4 | implicit-dynamic: false
5 |
6 | linter:
7 | rules:
8 | - always_put_required_named_parameters_first
9 | - always_require_non_null_named_parameters
10 | - annotate_overrides
11 | - avoid_annotating_with_dynamic
12 | - avoid_bool_literals_in_conditional_expressions
13 | - avoid_catches_without_on_clauses
14 | - avoid_catching_errors
15 | - avoid_classes_with_only_static_members
16 | - avoid_double_and_int_checks
17 | - avoid_empty_else
18 | - avoid_field_initializers_in_const_classes
19 | - avoid_implementing_value_types
20 | - avoid_init_to_null
21 | - avoid_js_rounded_ints
22 | - avoid_null_checks_in_equality_operators
23 | - avoid_relative_lib_imports
24 | - avoid_return_types_on_setters
25 | - avoid_returning_null
26 | - avoid_returning_null_for_future
27 | - avoid_returning_null_for_void
28 | - avoid_returning_this
29 | - avoid_setters_without_getters
30 | - avoid_shadowing_type_parameters
31 | - avoid_single_cascade_in_expression_statements
32 | - avoid_slow_async_io
33 | - avoid_types_as_parameter_names
34 | - avoid_unused_constructor_parameters
35 | - avoid_void_async
36 | - await_only_futures
37 | - camel_case_types
38 | - cancel_subscriptions
39 | - close_sinks
40 | - constant_identifier_names
41 | - control_flow_in_finally
42 | - curly_braces_in_flow_control_structures
43 | - directives_ordering
44 | - empty_catches
45 | - empty_constructor_bodies
46 | - empty_statements
47 | - file_names
48 | - hash_and_equals
49 | - implementation_imports
50 | - invariant_booleans
51 | - iterable_contains_unrelated_type
52 | - join_return_with_assignment
53 | - library_names
54 | - library_prefixes
55 | - lines_longer_than_80_chars
56 | - list_remove_unrelated_type
57 | - literal_only_boolean_expressions
58 | - no_adjacent_strings_in_list
59 | - no_duplicate_case_values
60 | - non_constant_identifier_names
61 | - null_closures
62 | - one_member_abstracts
63 | - only_throw_errors
64 | - overridden_fields
65 | - package_api_docs
66 | - package_names
67 | - package_prefixed_library_names
68 | - parameter_assignments
69 | - prefer_adjacent_string_concatenation
70 | - prefer_asserts_in_initializer_lists
71 | - prefer_collection_literals
72 | - prefer_conditional_assignment
73 | - prefer_const_constructors
74 | - prefer_const_constructors_in_immutables
75 | - prefer_const_declarations
76 | - prefer_const_literals_to_create_immutables
77 | - prefer_constructors_over_static_methods
78 | - prefer_contains
79 | - prefer_equal_for_default_values
80 | - prefer_final_fields
81 | - prefer_final_in_for_each
82 | - prefer_foreach
83 | - prefer_function_declarations_over_variables
84 | - prefer_initializing_formals
85 | - prefer_is_empty
86 | - prefer_is_not_empty
87 | - prefer_iterable_whereType
88 | - prefer_mixin
89 | - prefer_null_aware_operators
90 | - prefer_typing_uninitialized_variables
91 | - prefer_void_to_null
92 | - recursive_getters
93 | - slash_for_doc_comments
94 | - sort_pub_dependencies
95 | - sort_unnamed_constructors_first
96 | - test_types_in_equals
97 | - throw_in_finally
98 | - type_annotate_public_apis
99 | - type_init_formals
100 | - unawaited_futures
101 | - unnecessary_await_in_return
102 | - unnecessary_brace_in_string_interps
103 | - unnecessary_const
104 | - unnecessary_getters_setters
105 | - unnecessary_lambdas
106 | - unnecessary_new
107 | - unnecessary_null_aware_assignments
108 | - unnecessary_null_in_if_null_operators
109 | - unnecessary_overrides
110 | - unnecessary_parenthesis
111 | - unnecessary_statements
112 | - unnecessary_this
113 | - unrelated_type_equality_checks
114 | - use_full_hex_values_for_flutter_colors
115 | - use_rethrow_when_possible
116 | - use_setters_to_change_properties
117 | - use_string_buffers
118 | - use_to_and_as_if_applicable
119 | - valid_regexps
120 | - void_checks
121 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | def keystoreProperties = new Properties()
25 | if (System.getenv()["CI"] && System.getenv()["CIRCLECI"] == null) {
26 | def keystorePropertiesFile = rootProject.file("key.properties")
27 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
28 | }
29 |
30 | apply plugin: 'com.android.application'
31 | apply plugin: 'kotlin-android'
32 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
33 |
34 | android {
35 | compileSdkVersion 27
36 |
37 | sourceSets {
38 | main.java.srcDirs += 'src/main/kotlin'
39 | }
40 |
41 | lintOptions {
42 | disable 'InvalidPackage'
43 | }
44 |
45 | defaultConfig {
46 | applicationId "dev.flutter.devRpg"
47 | minSdkVersion 16
48 | targetSdkVersion 27
49 | versionCode flutterVersionCode.toInteger()
50 | versionName flutterVersionName
51 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
52 | }
53 | signingConfigs {
54 | release {
55 | if (System.getenv()["CI"] && System.getenv()["CIRCLECI"] == null) {
56 | keyAlias keystoreProperties['keyAlias']
57 | keyPassword keystoreProperties['keyPassword']
58 | storeFile file(keystoreProperties['storeFile'])
59 | storePassword keystoreProperties['storePassword']
60 | }
61 | }
62 | }
63 | buildTypes {
64 | release {
65 | // TODO: Add your own signing config for the release build.
66 | // Signing with the debug keys for now, so `flutter run --release` works.
67 | if (System.getenv()["CI"] && System.getenv()["CIRCLECI"] == null) {
68 | signingConfig signingConfigs.release
69 | }
70 | else {
71 | signingConfig signingConfigs.debug
72 | }
73 | }
74 | }
75 | }
76 |
77 | flutter {
78 | source '../..'
79 | }
80 |
81 | dependencies {
82 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
83 | testImplementation 'junit:junit:4.12'
84 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
85 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
86 | }
87 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
15 |
19 |
26 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/dev/flutter/devrpg/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package dev.flutter.devrpg
2 |
3 | import android.os.Bundle
4 |
5 | import io.flutter.app.FlutterActivity
6 | import io.flutter.plugins.GeneratedPluginRegistrant
7 |
8 | class MainActivity: FlutterActivity() {
9 | override fun onCreate(savedInstanceState: Bundle?) {
10 | super.onCreate(savedInstanceState)
11 | GeneratedPluginRegistrant.registerWith(this)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.2.71'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.2.1'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | jcenter()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
7 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4 |
5 | def plugins = new Properties()
6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7 | if (pluginsFile.exists()) {
8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9 | }
10 |
11 | plugins.each { name, path ->
12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13 | include ":$name"
14 | project(":$name").projectDir = pluginDirectory
15 | }
16 |
--------------------------------------------------------------------------------
/artwork/speech bubble.pxm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/artwork/speech bubble.pxm
--------------------------------------------------------------------------------
/assets/docs/code_chomper_alpha.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class RpgButton extends StatelessWidget {
4 | final VoidCallback onPressed;
5 | final Widget child;
6 |
7 | const RpgButton({
8 | @required this.onPressed,
9 | @required this.child,
10 | Key key,
11 | }) : super(key: key);
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | final radius = BorderRadius.circular(10);
16 |
17 | return Material(
18 | shape: RoundedRectangleBorder(borderRadius: radius),
19 | color: const Color.fromRGBO(242, 124, 78, 1),
20 | child: InkWell(
21 | borderRadius: radius,
22 | splashColor: const Color.fromRGBO(242, 124, 78, 1),
23 | child: Container(
24 | padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 16),
25 | child: DefaultTextStyle(
26 | child: child,
27 | style: const TextStyle(
28 | fontFamily: 'MontserratRegular',
29 | fontSize: 16,
30 | fontWeight: FontWeight.bold,
31 | color: Color.fromRGBO(85, 34, 34, 1)),
32 | ),
33 | ),
34 | onTap: onPressed,
35 | ),
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/assets/docs/code_chomper_beta.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | void main() => runApp(MyApp());
4 |
5 | class MyApp extends StatelessWidget {
6 | @override
7 | Widget build(BuildContext context) {
8 | return MaterialApp(
9 | title: 'Flutter Demo',
10 | theme: ThemeData(
11 | primarySwatch: Colors.blue,
12 | ),
13 | home: MyHomePage(title: 'Flutter Demo Home Page'),
14 | );
15 | }
16 | }
17 |
18 | class MyHomePage extends StatefulWidget {
19 | MyHomePage({Key key, this.title}) : super(key: key);
20 |
21 | final String title;
22 |
23 | @override
24 | _MyHomePageState createState() => _MyHomePageState();
25 | }
26 |
27 | class _MyHomePageState extends State {
28 | int _counter = 0;
29 |
30 | void _incrementCounter() {
31 | setState(() {
32 | _counter++;
33 | });
34 | }
35 |
36 | @override
37 | Widget build(BuildContext context) {
38 | return Scaffold(
39 | appBar: AppBar(
40 | title: Text(widget.title),
41 | ),
42 | body: Center(
43 | child: Column(
44 | mainAxisAlignment: MainAxisAlignment.center,
45 | children: [
46 | const Text(
47 | 'You have pushed the button this many times:',
48 | ),
49 | Text(
50 | '$_counter',
51 | style: Theme.of(context).textTheme.display1,
52 | ),
53 | ],
54 | ),
55 | ),
56 | floatingActionButton: FloatingActionButton(
57 | onPressed: _incrementCounter,
58 | tooltip: 'Increment',
59 | child: const Icon(Icons.add),
60 | ),
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/assets/docs/research-tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/docs/research-tree.png
--------------------------------------------------------------------------------
/assets/flare/Chomper FUI Type.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/Chomper FUI Type.flr
--------------------------------------------------------------------------------
/assets/flare/Chomper.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/Chomper.flr
--------------------------------------------------------------------------------
/assets/flare/CodeIcon.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/CodeIcon.flr
--------------------------------------------------------------------------------
/assets/flare/Coin.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/Coin.flr
--------------------------------------------------------------------------------
/assets/flare/CoordinationIcon.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/CoordinationIcon.flr
--------------------------------------------------------------------------------
/assets/flare/CowboyCoder.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/CowboyCoder.flr
--------------------------------------------------------------------------------
/assets/flare/Designer.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/Designer.flr
--------------------------------------------------------------------------------
/assets/flare/EngineeringIcon.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/EngineeringIcon.flr
--------------------------------------------------------------------------------
/assets/flare/Joy.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/Joy.flr
--------------------------------------------------------------------------------
/assets/flare/NotificationIcon.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/NotificationIcon.flr
--------------------------------------------------------------------------------
/assets/flare/ProgramManager.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/ProgramManager.flr
--------------------------------------------------------------------------------
/assets/flare/SelectArrow.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/SelectArrow.flr
--------------------------------------------------------------------------------
/assets/flare/Sourcerer.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/Sourcerer.flr
--------------------------------------------------------------------------------
/assets/flare/Stars.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/Stars.flr
--------------------------------------------------------------------------------
/assets/flare/TasksIcon.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/TasksIcon.flr
--------------------------------------------------------------------------------
/assets/flare/TeamIcon.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/TeamIcon.flr
--------------------------------------------------------------------------------
/assets/flare/Tester.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/Tester.flr
--------------------------------------------------------------------------------
/assets/flare/TheArchitect.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/TheArchitect.flr
--------------------------------------------------------------------------------
/assets/flare/TheHacker.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/TheHacker.flr
--------------------------------------------------------------------------------
/assets/flare/TheJack.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/TheJack.flr
--------------------------------------------------------------------------------
/assets/flare/TheRefactorer.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/TheRefactorer.flr
--------------------------------------------------------------------------------
/assets/flare/UXResearcher.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/UXResearcher.flr
--------------------------------------------------------------------------------
/assets/flare/Users.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/Users.flr
--------------------------------------------------------------------------------
/assets/flare/UxIcon.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/flare/UxIcon.flr
--------------------------------------------------------------------------------
/assets/fonts/Gotham XNarrow Medium.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/fonts/Gotham XNarrow Medium.otf
--------------------------------------------------------------------------------
/assets/fonts/Montserrat-Bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/fonts/Montserrat-Bold.otf
--------------------------------------------------------------------------------
/assets/fonts/Montserrat-Medium.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/fonts/Montserrat-Medium.otf
--------------------------------------------------------------------------------
/assets/fonts/Montserrat-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/fonts/Montserrat-Regular.otf
--------------------------------------------------------------------------------
/assets/fonts/Roboto-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/fonts/Roboto-Regular.ttf
--------------------------------------------------------------------------------
/assets/fonts/RobotoCondensed-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/fonts/RobotoCondensed-Bold.ttf
--------------------------------------------------------------------------------
/assets/fonts/SpaceMono-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/fonts/SpaceMono-Bold.ttf
--------------------------------------------------------------------------------
/assets/fonts/SpaceMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/fonts/SpaceMono-Regular.ttf
--------------------------------------------------------------------------------
/assets/images/2.0x/2dimensions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/images/2.0x/2dimensions.png
--------------------------------------------------------------------------------
/assets/images/2.0x/2dimensions_wide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/images/2.0x/2dimensions_wide.png
--------------------------------------------------------------------------------
/assets/images/2.0x/flare_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/images/2.0x/flare_logo.png
--------------------------------------------------------------------------------
/assets/images/2dimensions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/images/2dimensions.png
--------------------------------------------------------------------------------
/assets/images/2dimensions_wide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/images/2dimensions_wide.png
--------------------------------------------------------------------------------
/assets/images/3.0x/2dimensions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/images/3.0x/2dimensions.png
--------------------------------------------------------------------------------
/assets/images/3.0x/2dimensions_wide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/images/3.0x/2dimensions_wide.png
--------------------------------------------------------------------------------
/assets/images/3.0x/flare_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/images/3.0x/flare_logo.png
--------------------------------------------------------------------------------
/assets/images/flare_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/images/flare_logo.png
--------------------------------------------------------------------------------
/assets/images/flutter_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/images/flutter_logo.png
--------------------------------------------------------------------------------
/assets/style_sphinx/green_bed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/style_sphinx/green_bed.png
--------------------------------------------------------------------------------
/assets/style_sphinx/orange_cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/style_sphinx/orange_cat.png
--------------------------------------------------------------------------------
/assets/style_sphinx/pyramid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/style_sphinx/pyramid.png
--------------------------------------------------------------------------------
/assets/style_sphinx/red_bed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/style_sphinx/red_bed.png
--------------------------------------------------------------------------------
/assets/style_sphinx/sphinx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/style_sphinx/sphinx.png
--------------------------------------------------------------------------------
/assets/style_sphinx/sphinx_no_glasses.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/style_sphinx/sphinx_no_glasses.png
--------------------------------------------------------------------------------
/assets/style_sphinx/start_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/style_sphinx/start_background.png
--------------------------------------------------------------------------------
/assets/style_sphinx/sunglasses.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/style_sphinx/sunglasses.png
--------------------------------------------------------------------------------
/assets/style_sphinx/yellow_cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/assets/style_sphinx/yellow_cat.png
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
56 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
77 |
83 |
84 |
85 |
86 |
88 |
89 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildSystemType
6 | Original
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "icon_20pt@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "icon_20pt@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "icon_29pt.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "icon_29pt@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "icon_29pt@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "icon_40pt@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "icon_40pt@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "icon_60pt@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "icon_60pt@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "icon_20pt.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "icon_20pt@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "icon_29pt.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "icon_29pt@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "icon_40pt.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "icon_40pt@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "icon_76pt.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "icon_76pt@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "icon_83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20pt.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29pt.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40pt.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_76pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_76pt.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2d-inc/developer_quest/5574fd17c641207bfa898c4c3301334ca4650d38/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | Developer Quest
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | dev_rpg
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | UIViewControllerBasedStatusBarAppearance
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:dev_rpg/src/about_screen.dart';
4 | import 'package:dev_rpg/src/code_chomper/code_chomper.dart';
5 | import 'package:dev_rpg/src/game_screen.dart';
6 | import 'package:dev_rpg/src/shared_state/game/world.dart';
7 | import 'package:dev_rpg/src/shared_state/user.dart';
8 | import 'package:dev_rpg/src/style_sphinx/axis_questions.dart';
9 | import 'package:dev_rpg/src/style_sphinx/flex_questions.dart';
10 | import 'package:dev_rpg/src/style_sphinx/kittens.dart';
11 | import 'package:dev_rpg/src/style_sphinx/sphinx_image.dart';
12 | import 'package:dev_rpg/src/style_sphinx/sphinx_screen.dart';
13 | import 'package:dev_rpg/src/welcome_screen.dart';
14 | import 'package:flare_flutter/flare_cache.dart';
15 | import 'package:flutter/material.dart';
16 | import 'package:flutter/services.dart';
17 | import 'package:provider/provider.dart';
18 |
19 | void main() {
20 | // Don't prune the Flare cache, keep loaded Flare files warm and ready
21 | // to be re-displayed.
22 | FlareCache.doesPrune = false;
23 |
24 | runApp(MyApp());
25 | }
26 |
27 | class MyApp extends StatefulWidget {
28 | @override
29 | _MyAppState createState() => _MyAppState();
30 | }
31 |
32 | class _MyAppState extends State {
33 | final World world = World();
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | return MultiProvider(
38 | providers: [
39 | ChangeNotifierProvider(builder: (_) => User()),
40 | ChangeNotifierProvider.value(notifier: world),
41 | ChangeNotifierProvider.value(notifier: world.characterPool),
42 | ChangeNotifierProvider.value(notifier: world.taskPool),
43 | ChangeNotifierProvider.value(notifier: world.company),
44 | ChangeNotifierProvider.value(notifier: world.company.users),
45 | ChangeNotifierProvider.value(notifier: world.company.joy),
46 | ChangeNotifierProvider.value(notifier: world.company.coin),
47 | ],
48 | child: MaterialApp(
49 | title: 'Flutter Demo',
50 | theme: ThemeData(
51 | brightness: Brightness.dark,
52 | primarySwatch: Colors.orange,
53 | canvasColor: Colors.transparent),
54 | routes: {
55 | '/': (context) => WelcomeScreen(),
56 | '/gameloop': (context) => GameScreen(),
57 | '/about': (context) => AboutScreen(),
58 | CodeChomper.miniGameRouteName: (context) {
59 | String filename =
60 | ModalRoute.of(context).settings.arguments as String;
61 | return CodeChomper(filename);
62 | },
63 | SphinxScreen.miniGameRouteName: (context) => const SphinxScreen(),
64 | SphinxScreen.fullGameRouteName: (context) =>
65 | const SphinxScreen(fullGame: true),
66 | ColumnQuestion.routeName: (context) => const ColumnQuestion(),
67 | RowQuestion.routeName: (context) => const RowQuestion(),
68 | StackQuestion.routeName: (context) => const StackQuestion(),
69 | MainAxisCenterQuestion.routeName: (context) =>
70 | const MainAxisCenterQuestion(),
71 | MainAxisSpaceAroundQuestion.routeName: (context) =>
72 | const MainAxisSpaceAroundQuestion(),
73 | MainAxisSpaceBetweenQuestion.routeName: (context) =>
74 | const MainAxisSpaceBetweenQuestion(),
75 | MainAxisStartQuestion.routeName: (context) =>
76 | const MainAxisStartQuestion(),
77 | MainAxisEndQuestion.routeName: (context) =>
78 | const MainAxisEndQuestion(),
79 | MainAxisSpaceEvenlyQuestion.routeName: (context) =>
80 | const MainAxisSpaceEvenlyQuestion(),
81 | RowMainAxisEndQuestion.routeName: (context) =>
82 | const RowMainAxisEndQuestion(),
83 | RowMainAxisStartQuestion.routeName: (context) =>
84 | const RowMainAxisStartQuestion(),
85 | RowMainAxisSpaceBetween.routeName: (context) =>
86 | const RowMainAxisSpaceBetween(),
87 | },
88 | ));
89 | }
90 |
91 | @override
92 | void initState() {
93 | // Schedule a microtask that warms up the image cache with all of the style
94 | // sphinx images. This will run after the build method is executed, but
95 | // before the style sphinx is displayed.
96 | scheduleMicrotask(() {
97 | precacheImage(SphinxScreen.pyramid, context);
98 | precacheImage(SphinxScreen.background, context);
99 | precacheImage(SphinxImage.provider, context);
100 | precacheImage(SphinxWithoutGlassesImage.provider, context);
101 | precacheImage(SphinxGlassesImage.provider, context);
102 | precacheImage(KittyBed.redProvider, context);
103 | precacheImage(KittyBed.greenProvider, context);
104 | precacheImage(Kitty.orangeProvider, context);
105 | precacheImage(Kitty.yellowProvider, context);
106 | });
107 | super.initState();
108 | }
109 |
110 | @override
111 | void dispose() {
112 | world.dispose();
113 | super.dispose();
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/lib/src/game_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/rpg_layout_builder.dart';
2 | import 'package:flutter/material.dart';
3 | import 'game_screen_slim.dart';
4 | import 'game_screen_wide.dart';
5 |
6 | class GameScreen extends StatelessWidget {
7 | @override
8 | Widget build(BuildContext context) {
9 | return RpgLayoutBuilder(
10 | builder: (context, layout) =>
11 | layout == RpgLayout.slim ? GameScreenSlim() : GameScreenWide(),
12 | );
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/lib/src/game_screen/add_task_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/style.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | // A stylized button meant to be used for adding tasks to the task pool.
5 | class AddTaskButton extends StatefulWidget {
6 | final IconData icon;
7 | final Color color;
8 | final String label;
9 | final int count;
10 | final VoidCallback onPressed;
11 | final double scale;
12 |
13 | const AddTaskButton(
14 | this.label, {
15 | Key key,
16 | this.count = 0,
17 | this.icon,
18 | this.color,
19 | this.onPressed,
20 | this.scale = 1.0,
21 | }) : super(key: key);
22 |
23 | @override
24 | _AddTaskButtonState createState() => _AddTaskButtonState();
25 | }
26 |
27 | class _AddTaskButtonState extends State {
28 | bool _isPressed;
29 | @override
30 | void initState() {
31 | _isPressed = false;
32 | super.initState();
33 | }
34 |
35 | void onTapDown(TapDownDetails details) {
36 | setState(() {
37 | _isPressed = true;
38 | });
39 | }
40 |
41 | void onTapUp(TapUpDetails details) {
42 | setState(() {
43 | _isPressed = false;
44 | });
45 | }
46 |
47 | void onTap() {
48 | if (widget.onPressed != null) {
49 | widget.onPressed();
50 | }
51 | }
52 |
53 | bool get isDisabled => widget.count == 0;
54 |
55 | @override
56 | Widget build(BuildContext context) {
57 | return GestureDetector(
58 | onTapDown: isDisabled ? null : onTapDown,
59 | onTapUp: isDisabled ? null : onTapUp,
60 | onTap: isDisabled ? null : onTap,
61 | child: AnimatedContainer(
62 | duration: const Duration(milliseconds: 100),
63 | height: 40 * widget.scale,
64 | padding: const EdgeInsets.symmetric(horizontal: 8),
65 | decoration: BoxDecoration(
66 | boxShadow: isDisabled
67 | ? null
68 | : [
69 | BoxShadow(
70 | color: widget.color.withOpacity(0.3),
71 | offset: const Offset(0, 10),
72 | blurRadius: _isPressed ? 10 : 15,
73 | spreadRadius: 0),
74 | ],
75 | borderRadius: BorderRadius.all(Radius.circular(20 * widget.scale)),
76 | color: isDisabled
77 | ? disabledTaskColor.withOpacity(0.10)
78 | : _isPressed ? widget.color.withOpacity(0.8) : widget.color,
79 | ),
80 | child: Row(
81 | children: [
82 | Icon(widget.icon,
83 | color: isDisabled ? disabledTaskColor : Colors.white),
84 | const SizedBox(width: 4),
85 | Expanded(
86 | child: Text(
87 | widget.label,
88 | style: buttonTextStyle.apply(
89 | fontSizeDelta: -2,
90 | fontSizeFactor: widget.scale,
91 | color: isDisabled ? disabledTaskColor : Colors.white),
92 | ),
93 | ),
94 | isDisabled
95 | ? Container()
96 | : Container(
97 | constraints: BoxConstraints(
98 | minWidth: 26 * widget.scale,
99 | minHeight: 26 * widget.scale,
100 | maxHeight: 26 * widget.scale,
101 | ),
102 | decoration: const BoxDecoration(
103 | shape: BoxShape.circle,
104 | color: Colors.white,
105 | ),
106 | child: Center(
107 | child: Text(widget.count.toString(),
108 | style: buttonTextStyle.apply(
109 | fontSizeDelta: -2,
110 | fontSizeFactor: widget.scale,
111 | color: widget.color)),
112 | ),
113 | ),
114 | ],
115 | ),
116 | ),
117 | );
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/lib/src/game_screen/bug_picker_modal.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/bug.dart';
2 | import 'package:dev_rpg/src/shared_state/game/task_pool.dart';
3 | import 'package:dev_rpg/src/style.dart';
4 | import 'package:dev_rpg/src/widgets/work_items/bug_header.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:provider/provider.dart';
7 |
8 | /// Displays a list of the currently available [Bug]s.
9 | class BugPickerModal extends StatelessWidget {
10 | @override
11 | Widget build(BuildContext context) {
12 | var taskPool = Provider.of(context);
13 | var bugs = taskPool.availableBugs.toList(growable: false)
14 | ..sort((a, b) => -a.priority.drainOfJoy.compareTo(b.priority.drainOfJoy));
15 |
16 | return Center(
17 | child: ConstrainedBox(
18 | constraints: const BoxConstraints(maxWidth: modalMaxWidth),
19 | child: ClipRRect(
20 | borderRadius: const BorderRadius.only(
21 | topLeft: Radius.circular(10), topRight: Radius.circular(10)),
22 | child: Container(
23 | color: modalBackgroundColor,
24 | child: ListView.builder(
25 | padding: const EdgeInsets.only(top: 30, left: 15, right: 15),
26 | itemCount: bugs.length,
27 | itemBuilder: (context, index) {
28 | Bug bug = bugs[index];
29 |
30 | return Padding(
31 | padding: const EdgeInsets.only(bottom: 15),
32 | child: InkWell(
33 | onTap: () => Navigator.pop(context, bug),
34 | child: Container(
35 | padding: const EdgeInsets.all(15),
36 | decoration: const BoxDecoration(
37 | boxShadow: [
38 | BoxShadow(
39 | color: Color.fromRGBO(0, 0, 0, 0.03),
40 | offset: Offset(0, 10),
41 | blurRadius: 10,
42 | spreadRadius: 0),
43 | ],
44 | borderRadius: BorderRadius.all(Radius.circular(9)),
45 | color: Colors.white,
46 | ),
47 | child: Column(
48 | crossAxisAlignment: CrossAxisAlignment.start,
49 | children: [
50 | BugHeader(bug),
51 | const SizedBox(height: 12),
52 | Text(bug.name, style: contentStyle)
53 | ],
54 | ),
55 | ),
56 | ),
57 | );
58 | },
59 | ),
60 | ),
61 | ),
62 | ),
63 | );
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/lib/src/game_screen/character_style.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 | import 'dart:ui';
3 |
4 | import 'package:dev_rpg/src/shared_state/game/character.dart';
5 |
6 | /// UI style properties for [Character]s. [Character] to [CharacterStyle]
7 | /// mapping is done via [Character.id] values.
8 | class CharacterStyle {
9 | final String flare;
10 | final Color accent;
11 | final String name;
12 | final String description;
13 |
14 | static final Map _all = {
15 | 'jack': CharacterStyle(
16 | name: 'The Jack-of-All-Trades',
17 | flare: 'assets/flare/TheJack.flr',
18 | accent: const Color.fromRGBO(29, 202, 34, 1),
19 | description: 'Got a problem? Jack can help! Carries a snorkel '
20 | 'everywhere he goes since he\'s always prepared.'),
21 | 'sourcerer': CharacterStyle(
22 | name: 'The Sourcerer',
23 | flare: 'assets/flare/Sourcerer.flr',
24 | accent: const Color.fromRGBO(82, 183, 216, 1),
25 | description:
26 | 'Accomplished problem-solver and coder who is able to find the '
27 | 'answer to any and all problems by traversing codebases.'),
28 | 'refactorer': CharacterStyle(
29 | name: 'The Refactorer',
30 | flare: 'assets/flare/TheRefactorer.flr',
31 | accent: const Color.fromRGBO(75, 58, 185, 1),
32 | description:
33 | 'A Digital Druid. She has a sixth sense when it comes to code '
34 | 'health. Need to restructure your code? Is your code made up of a '
35 | 'bunch of copy-paste snippets? Send in The Refactorer to clean up '
36 | 'your codebase and make it shine!'),
37 | 'architect': CharacterStyle(
38 | name: 'The Architect',
39 | flare: 'assets/flare/TheArchitect.flr',
40 | accent: const Color.fromRGBO(236, 41, 117, 1),
41 | description:
42 | 'Helps provide structure in large codebases, which can improve '
43 | 'code health. Has a ton of books and a head full of ideas.'),
44 | 'pm': CharacterStyle(
45 | name: 'Program Manager',
46 | flare: 'assets/flare/ProgramManager.flr',
47 | accent: const Color.fromRGBO(84, 209, 88, 1),
48 | description:
49 | 'Promotes communication and group harmony. He has the superpower '
50 | 'of increasing everyone else\'s abilities if assigned to a task '
51 | 'with others.'),
52 | 'avant_garde_designer': CharacterStyle(
53 | name: 'Avant Garde Designer',
54 | flare: 'assets/flare/Designer.flr',
55 | accent: const Color.fromRGBO(236, 148, 0, 1),
56 | description:
57 | 'Improves team execution by inspiring them with great designs for '
58 | 'the app. Her designs win over more customers and spark joy when '
59 | 'users interact with the app.'),
60 | 'cowboy': CharacterStyle(
61 | name: 'The Cowboy Coder',
62 | flare: 'assets/flare/CowboyCoder.flr',
63 | accent: const Color.fromRGBO(75, 58, 185, 1),
64 | description:
65 | 'An extremely prolific coder who doesn\'t like structure. He can '
66 | 'write a whole lot of code very quickly... hopefully everyone else '
67 | 'can read it. Yeehaw!'),
68 | 'tester': CharacterStyle(
69 | name: 'The Test Engineer',
70 | flare: 'assets/flare/Tester.flr',
71 | accent: const Color.fromRGBO(75, 58, 185, 1),
72 | description:
73 | 'An excellent developer in their own right, the Test Engineer '
74 | 'creates invaluable frameworks for continuous integration testing '
75 | 'and fixes bugs at lightning speed.'),
76 | 'uxr': CharacterStyle(
77 | name: 'User Experience Researcher',
78 | flare: 'assets/flare/UXResearcher.flr',
79 | accent: const Color.fromRGBO(222, 165, 88, 1),
80 | description:
81 | 'They design enlightening experiments to better understand user '
82 | 'needs, resulting in a delightful user experience.'),
83 | 'hacker': CharacterStyle(
84 | name: 'The Hacker',
85 | flare: 'assets/flare/TheHacker.flr',
86 | accent: const Color.fromRGBO(236, 41, 117, 1),
87 | description:
88 | 'A strong coder on her own, but excels at finding and fixing '
89 | 'security flaws and also discovering unique solutions to problems. '
90 | 'She may have sniffed your email password while you read this '
91 | 'description.'),
92 | };
93 |
94 | CharacterStyle(
95 | {this.flare, this.accent, this.name, this.description = 'N/A'});
96 |
97 | static CharacterStyle from(Character character) {
98 | return _all[character.id];
99 | }
100 |
101 | static CharacterStyle random() {
102 | Random rand = Random();
103 | return _all.values.elementAt(rand.nextInt(_all.values.length));
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/lib/src/game_screen/task_pool_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/rpg_layout_builder.dart';
2 | import 'package:dev_rpg/src/shared_state/game/bug.dart';
3 | import 'package:dev_rpg/src/shared_state/game/task.dart';
4 | import 'package:dev_rpg/src/shared_state/game/task_pool.dart';
5 | import 'package:dev_rpg/src/shared_state/game/work_item.dart';
6 | import 'package:dev_rpg/src/widgets/work_items/bug_list_item.dart';
7 | import 'package:dev_rpg/src/widgets/work_items/task_list_item.dart';
8 | import 'package:dev_rpg/src/widgets/work_items/tasks_button_header.dart';
9 | import 'package:dev_rpg/src/widgets/work_items/tasks_section_header.dart';
10 | import 'package:flutter/material.dart';
11 | import 'package:provider/provider.dart';
12 |
13 | enum TaskPoolDisplay { all, inProgress, completed }
14 |
15 | /// Displays a list of the [Task]s the player has interacted with.
16 | /// These are [Task]s that have been added into the game, are being
17 | /// actively worked on, or have been completed and/or archived.
18 | class TaskPoolPage extends StatelessWidget {
19 | final TaskPoolDisplay display;
20 | const TaskPoolPage({this.display = TaskPoolDisplay.all});
21 |
22 | /// Builds a section of the task list with [title] and a list of [workItems].
23 | /// This returns slivers to be used in a [SliverList].
24 | void _buildSection(List slivers, String title, double scale,
25 | List workItems) {
26 | if (workItems.isNotEmpty) {
27 | slivers.add(SliverPersistentHeader(
28 | pinned: false,
29 | delegate: TasksSectionHeader(title, scale),
30 | ));
31 | slivers.add(SliverList(
32 | delegate: SliverChildBuilderDelegate((context, index) {
33 | WorkItem item = workItems[index];
34 | return ChangeNotifierProvider.value(
35 | notifier: item,
36 | key: ValueKey(item),
37 | child: item is Bug ? BugListItem() : TaskListItem(),
38 | );
39 | }, childCount: workItems.length),
40 | ));
41 | }
42 | }
43 |
44 | @override
45 | Widget build(BuildContext context) {
46 | return RpgLayoutBuilder(builder: (context, layout) {
47 | double scale = layout == RpgLayout.ultrawide ? 1.25 : 1;
48 | return Container(
49 | color: display == TaskPoolDisplay.completed
50 | ? const Color.fromRGBO(229, 229, 229, 1)
51 | : const Color.fromRGBO(241, 241, 241, 1),
52 | child: Consumer(
53 | builder: (context, taskPool, _) {
54 | var slivers = [];
55 |
56 | // Add the header only if we show the in progress tasks.
57 | if (display == TaskPoolDisplay.all ||
58 | display == TaskPoolDisplay.inProgress) {
59 | slivers.add(
60 | SliverPersistentHeader(
61 | pinned: false,
62 | delegate: TasksButtonHeader(taskPool: taskPool, scale: scale),
63 | ),
64 | );
65 | _buildSection(slivers, 'IN PROGRESS', scale, taskPool.workItems);
66 | }
67 |
68 | if (display == TaskPoolDisplay.all ||
69 | display == TaskPoolDisplay.completed) {
70 | _buildSection(
71 | slivers,
72 | 'COMPLETED',
73 | scale,
74 | taskPool.completedTasks
75 | .followedBy(taskPool.archivedTasks)
76 | .toList(growable: false));
77 | }
78 | return CustomScrollView(slivers: slivers);
79 | },
80 | ),
81 | );
82 | });
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/lib/src/game_screen_wide.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:dev_rpg/src/game_screen/character_pool_page.dart';
4 | import 'package:dev_rpg/src/rpg_layout_builder.dart';
5 | import 'package:dev_rpg/src/shared_state/game/company.dart';
6 | import 'package:dev_rpg/src/style.dart';
7 | import 'package:dev_rpg/src/widgets/app_bar/coin_badge.dart';
8 | import 'package:dev_rpg/src/widgets/app_bar/joy_badge.dart';
9 | import 'package:dev_rpg/src/widgets/app_bar/stat_separator.dart';
10 | import 'package:dev_rpg/src/widgets/app_bar/users_badge.dart';
11 | import 'package:flutter/material.dart';
12 | import 'package:provider/provider.dart';
13 |
14 | import 'game_screen/task_pool_page.dart';
15 |
16 | class GameScreenWide extends StatelessWidget {
17 | @override
18 | Widget build(BuildContext context) {
19 | var availableWidth = MediaQuery.of(context).size.width;
20 | var taskColumnWidth = min(modalMaxWidth, availableWidth / 3);
21 | var charactersWidth = availableWidth - taskColumnWidth * 2;
22 | var numCharacterColumns =
23 | (charactersWidth / idealCharacterWidth).round().clamp(2, 4).toInt();
24 |
25 | return RpgLayoutBuilder(
26 | builder: (context, layout) {
27 | double statsScale = layout == RpgLayout.ultrawide ? 1.25 : 1;
28 | double statsWidth = layout == RpgLayout.ultrawide ? 300 : 125;
29 | return Scaffold(
30 | backgroundColor: const Color.fromRGBO(59, 59, 73, 1),
31 | appBar: AppBar(
32 | automaticallyImplyLeading: false,
33 | titleSpacing: 0,
34 | title: Consumer(
35 | builder: (context, company, _) {
36 | // Using RepaintBoundary here because this part of the UI
37 | // changes frequently.
38 | return RepaintBoundary(
39 | child: Container(
40 | decoration: BoxDecoration(
41 | border: Border(
42 | top: const BorderSide(
43 | color: statsSeparatorColor,
44 | style: BorderStyle.solid,
45 | ),
46 | ),
47 | ),
48 | child: Row(
49 | children: [
50 | Container(
51 | width: statsWidth,
52 | child: UsersBadge(
53 | company.users,
54 | scale: statsScale,
55 | isWide: layout == RpgLayout.ultrawide,
56 | ),
57 | ),
58 | StatSeparator(),
59 | Container(
60 | width: statsWidth,
61 | child: JoyBadge(
62 | company.joy,
63 | scale: statsScale,
64 | isWide: layout == RpgLayout.ultrawide,
65 | ),
66 | ),
67 | StatSeparator(),
68 | layout == RpgLayout.ultrawide
69 | ? Container(
70 | width: statsWidth,
71 | child: CoinBadge(
72 | company.coin,
73 | scale: statsScale,
74 | isWide: layout == RpgLayout.ultrawide,
75 | ),
76 | )
77 | : Expanded(
78 | child:
79 | CoinBadge(company.coin, scale: statsScale)),
80 | ],
81 | ),
82 | ),
83 | );
84 | },
85 | ),
86 | ),
87 | body: Row(
88 | children: [
89 | SizedBox(
90 | width: charactersWidth,
91 | child:
92 | CharacterPoolPage(numColumns: numCharacterColumns.toInt()),
93 | ),
94 | SizedBox(
95 | width: taskColumnWidth,
96 | child: const TaskPoolPage(display: TaskPoolDisplay.inProgress),
97 | ),
98 | SizedBox(
99 | width: taskColumnWidth,
100 | child: const TaskPoolPage(display: TaskPoolDisplay.completed),
101 | ),
102 | ],
103 | ),
104 | );
105 | },
106 | );
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/lib/src/rpg_layout_builder.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/style.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | /// Layout for the dev_rpg game.
5 | enum RpgLayout { slim, wide, ultrawide }
6 |
7 | /// Signature for a function that builds a widget given an [RpgLayout].
8 | ///
9 | /// Used by [RpgLayoutBuilder.builder].
10 | typedef RpgLayoutWidgetBuilder = Widget Function(
11 | BuildContext context, RpgLayout layout);
12 |
13 | /// Builds a widget tree that can depend on the parent widget's width
14 | class RpgLayoutBuilder extends StatelessWidget {
15 | const RpgLayoutBuilder({
16 | @required this.builder,
17 | Key key,
18 | }) : assert(builder != null),
19 | super(key: key);
20 |
21 | /// Builds the widgets below this widget given this widget's layout width.
22 | final RpgLayoutWidgetBuilder builder;
23 |
24 | Widget _build(BuildContext context, BoxConstraints constraints) {
25 | var mediaWidth = MediaQuery.of(context).size.width;
26 | final RpgLayout layout = mediaWidth >= ultraWideLayoutThreshold
27 | ? RpgLayout.ultrawide
28 | : mediaWidth > wideLayoutThreshold ? RpgLayout.wide : RpgLayout.slim;
29 | return builder(context, layout);
30 | }
31 |
32 | @override
33 | Widget build(BuildContext context) {
34 | return LayoutBuilder(builder: _build);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/bug.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:dev_rpg/src/shared_state/game/skill.dart';
4 | import 'package:dev_rpg/src/shared_state/game/task_pool.dart';
5 | import 'package:dev_rpg/src/shared_state/game/work_item.dart';
6 |
7 | /// Build weighted list of priorities. Put each BugPriority type in the list
8 | /// n times, where n is the value in bugFrequency.
9 | List bugChances = bugFrequency.keys
10 | .expand((bug) => List.generate(bugFrequency[bug], (_) => bug))
11 | .toList();
12 |
13 | /// Map of Bug types to the frequency that they appear.
14 | Map bugFrequency = {
15 | BugPriority('P0', 0.9): 1,
16 | BugPriority('P1', 0.6): 2,
17 | BugPriority('P2', 0.3): 2,
18 | BugPriority('P3', 0.2): 3,
19 | BugPriority('P4', 0.1): 3,
20 | };
21 |
22 | /// A bug that randomly shows up in the work queue.
23 | class Bug extends WorkItem {
24 | static Random randomizer = Random();
25 | final BugPriority priority;
26 |
27 | Bug(this.priority, Map difficulty)
28 | : super(priority.name + ' Bug!!', difficulty);
29 |
30 | Bug.random(Set availableSkills)
31 | : this(bugChances[randomizer.nextInt(bugChances.length)],
32 | randomDifficulty(randomizer, availableSkills));
33 |
34 | @override
35 | void onCompleted() {
36 | get().squashBug(this);
37 | super.onCompleted();
38 | markDirty();
39 | }
40 | }
41 |
42 | class BugPriority {
43 | final double drainOfJoy;
44 | final String name;
45 |
46 | BugPriority(this.name, this.drainOfJoy);
47 |
48 | @override
49 | String toString() => 'BugPriority($drainOfJoy, $name)';
50 | }
51 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/character.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/company.dart';
2 | import 'package:dev_rpg/src/shared_state/game/skill.dart';
3 | import 'package:dev_rpg/src/shared_state/game/src/aspect.dart';
4 | import 'package:dev_rpg/src/shared_state/game/src/child_aspect.dart';
5 | import 'package:dev_rpg/src/shared_state/game/task_pool.dart';
6 | import 'package:dev_rpg/src/shared_state/game/world.dart';
7 |
8 | /// A single task for the player and her team to complete.
9 | ///
10 | /// The definition of the task is in [blueprint]. This class holds the runtime
11 | /// state (like [percentComplete]).
12 | class Character extends Aspect with ChildAspect {
13 | static const int maxSkillProwess = 5;
14 |
15 | final String id;
16 |
17 | final Map prowess;
18 |
19 | // basically a upgrade counter
20 | int _level = 1;
21 | int get level => _level;
22 |
23 | bool _isHired = false;
24 | bool get isHired => _isHired;
25 |
26 | final int customHiringCost;
27 | final int costMultiplier;
28 |
29 | /// This value will get summed with [TaskPool.featureBugChance]
30 | /// after completing work to compute the total bug chance.
31 | /// This value can be negative, meaning this character is so
32 | /// attentive that they will actually reduce the overall bug
33 | /// chance.
34 | final double bugChanceOffset;
35 |
36 | /// If this character produces bugs, the number of bugs that they often
37 | /// tend to produce at once.
38 | final int bugQuantity;
39 |
40 | bool _isBusy = false;
41 |
42 | Character(
43 | this.id,
44 | this.prowess, {
45 | this.customHiringCost,
46 | this.costMultiplier = 1,
47 | this.bugChanceOffset = 0,
48 | this.bugQuantity = TaskPool.defaultBugNumber,
49 | });
50 |
51 | bool get isBusy => _isBusy;
52 |
53 | set isBusy(bool value) {
54 | _isBusy = value;
55 | markDirty();
56 | }
57 |
58 | double getProwessProgress(Skill skill) =>
59 | (prowess[skill] ?? 0) / maxSkillProwess;
60 |
61 | bool contributes(List skills) {
62 | for (final Skill skill in skills) {
63 | if (prowess.containsKey(skill)) {
64 | return true;
65 | }
66 | }
67 | return false;
68 | }
69 |
70 | @override
71 | String toString() => id;
72 |
73 | int get upgradeCost => !_isHired && customHiringCost != null
74 | ? customHiringCost
75 | : prowess.values.fold(0, (int previous, int value) => previous + value) *
76 | (_isHired ? 110 : 220) *
77 | costMultiplier;
78 |
79 | bool get canUpgradeOrHire {
80 | Company company = get().company;
81 | // Make sure there's some skill that's below max (meaning we can bump
82 | // it up).
83 | if (prowess.values.firstWhere((value) => value < maxSkillProwess,
84 | orElse: () => maxSkillProwess) ==
85 | maxSkillProwess) {
86 | return false;
87 | }
88 | return company.coin.number >= upgradeCost;
89 | }
90 |
91 | bool upgradeOrHire() => _isHired ? upgrade() : hire();
92 |
93 | bool hire() {
94 | assert(!_isHired);
95 | Company company = get().company;
96 | if (!company.spend(upgradeCost)) {
97 | return false;
98 | }
99 | _isHired = true;
100 | markDirty();
101 | return true;
102 | }
103 |
104 | bool upgrade() {
105 | assert(_isHired);
106 | Company company = get().company;
107 | if (!company.spend(upgradeCost)) {
108 | return false;
109 | }
110 | for (final Skill skill in prowess.keys) {
111 | prowess[skill] += 1;
112 | }
113 | _level++;
114 | markDirty();
115 | return true;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/character_pool.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/character.dart';
2 | import 'package:dev_rpg/src/shared_state/game/skill.dart';
3 | import 'package:dev_rpg/src/shared_state/game/src/aspect_container.dart';
4 | import 'package:dev_rpg/src/shared_state/game/src/child_aspect.dart';
5 | import 'package:dev_rpg/src/shared_state/game/world.dart';
6 |
7 | /// A list of [Character]s.
8 | class CharacterPool extends AspectContainer with ChildAspect {
9 | CharacterPool() {
10 | initializeCharacters();
11 | }
12 |
13 | void initializeCharacters() => setAspects([
14 | Character('jack', {Skill.coding: 1, Skill.coordination: 1, Skill.ux: 1},
15 | customHiringCost: 220, costMultiplier: 4),
16 | Character('sourcerer',
17 | {Skill.coding: 1, Skill.coordination: 1, Skill.engineering: 1},
18 | customHiringCost: 220, costMultiplier: 4, bugChanceOffset: -0.02),
19 | Character('refactorer', {Skill.coding: 1, Skill.engineering: 3}),
20 | Character('architect', {Skill.coding: 1, Skill.engineering: 4},
21 | bugChanceOffset: -0.09),
22 | Character('hacker', {Skill.coding: 3, Skill.engineering: 1},
23 | bugChanceOffset: 0.03),
24 | Character('cowboy', {Skill.coding: 4, Skill.engineering: 1},
25 | bugChanceOffset: 0.6, bugQuantity: 6),
26 | Character('pm', {Skill.coordination: 3, Skill.ux: 1}),
27 | Character('uxr', {Skill.coordination: 1, Skill.ux: 3}),
28 | Character('avant_garde_designer', {Skill.ux: 4}),
29 | Character('tester', {Skill.coding: 2, Skill.coordination: 1},
30 | bugChanceOffset: -0.05),
31 | ]);
32 |
33 | /// Get all the characters that have been hired and are part of the
34 | /// player's team!
35 | List get fullTeam => children.where((c) => c.isHired).toList();
36 |
37 | // get the set of available skills available with the player's hired team
38 | Set get availableSkills => children
39 | .expand((character) => character.isHired
40 | ? character.prowess.keys
41 | : const Iterable.empty())
42 | .toSet();
43 |
44 | bool _isUpgradeAvailable = false;
45 | bool get isUpgradeAvailable => _isUpgradeAvailable;
46 |
47 | @override
48 | void update() {
49 | int coin = get().company.coin.number;
50 | bool upgradeAvailable =
51 | children.any((character) => character.upgradeCost <= coin);
52 | if (upgradeAvailable != _isUpgradeAvailable) {
53 | _isUpgradeAvailable = upgradeAvailable;
54 | markDirty();
55 | }
56 | super.update();
57 | }
58 |
59 | // Mark this Aspect dirty when any child is dirty.
60 | @override
61 | bool get inheritsDirt => true;
62 |
63 | void reset() => initializeCharacters();
64 | }
65 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/company.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:dev_rpg/src/shared_state/game/src/aspect.dart';
4 | import 'package:dev_rpg/src/shared_state/game/src/aspect_container.dart';
5 | import 'package:flutter/foundation.dart';
6 | import 'package:intl/intl.dart';
7 |
8 | /// The company the user is playing on behalf of.
9 | ///
10 | /// The company owns [coin], and generates [joy] for its [users].
11 | class Company extends AspectContainer {
12 | static const int _initialCoin = 540;
13 |
14 | final StatValue users;
15 |
16 | final StatValue joy;
17 |
18 | final StatValue coin;
19 |
20 | Company()
21 | : users = StatValue(0),
22 | joy = StatValue(0),
23 | coin = StatValue(_initialCoin) {
24 | addAspect(users);
25 | addAspect(joy);
26 | addAspect(coin);
27 | }
28 |
29 | double maxUsers = 0;
30 |
31 | /// Star rating out of 5. Based on users lost, which is a consequence of
32 | /// bugs not resolved in a timely manner.
33 | /// Since it's inevitable to lose some, we consider losing 5%
34 | /// exceptional.
35 | int get starRating =>
36 | ((users.number / maxUsers / 0.95).clamp(0, 1) * 4).round();
37 |
38 | void award(int newUsers, int coinReward) {
39 | users.number += newUsers;
40 | coin.number += coinReward;
41 | }
42 |
43 | bool spend(int cost) {
44 | if (cost > coin.number) {
45 | return false;
46 | }
47 | coin.number -= cost;
48 | markDirty();
49 | return true;
50 | }
51 |
52 | @override
53 | void update() {
54 | super.update();
55 |
56 | // Generous denorm.
57 | if (joy.number.abs() < 0.01) {
58 | joy.number = 0;
59 | }
60 |
61 | users.number = max(0, users.number + joy.number);
62 | maxUsers = max(maxUsers, users.number);
63 | }
64 |
65 | void reset() {
66 | joy.number = 0;
67 | users.number = 0;
68 | maxUsers = 0;
69 | coin.number = _initialCoin;
70 | }
71 | }
72 |
73 | /// A value that is shown to the user. It is backed by a [number], and for
74 | /// the purposes of UI repainting it is represented by a [String]
75 | /// (via [string]).
76 | class StatValue extends Aspect
77 | implements ValueListenable {
78 | /// The default formatter of stats. It uses `package:intl`'s
79 | /// [NumberFormat.compact].
80 | static final Formatter defaultFormatter = NumberFormat.compact().format;
81 |
82 | /// The current value.
83 | V _number;
84 |
85 | /// The value that listeners have been provided with most recently.
86 | ///
87 | /// This field is used to make sure we don't notify listeners when they're
88 | /// already showing the most recent value.
89 | String _shownValue;
90 |
91 | /// The formatter to be used to convert [number] into a String [string].
92 | final String Function(V) formatter;
93 |
94 | StatValue(this._number, {Formatter statFormatter})
95 | : assert(_number != null),
96 | formatter = statFormatter ?? StatValue.defaultFormatter {
97 | _shownValue = formatter(_number);
98 | }
99 |
100 | V get number => _number;
101 |
102 | set number(V newValue) {
103 | if ((newValue == 0 && _number != 0) || (newValue.sign != _number.sign)) {
104 | // Special case for significant value changes.
105 | _number = newValue;
106 | _shownValue = formatter(newValue);
107 | notifyListeners();
108 | return;
109 | }
110 |
111 | var newShownValue = formatter(newValue);
112 |
113 | if (newShownValue == _shownValue) {
114 | // Only update value, don't notify listeners.
115 | _number = newValue;
116 | return;
117 | }
118 |
119 | _number = newValue;
120 | _shownValue = newShownValue;
121 | notifyListeners();
122 | }
123 |
124 | @override
125 | String get value => _shownValue;
126 | }
127 |
128 | /// A function that takes a number and returns its string representation.
129 | typedef Formatter = String Function(T);
130 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/skill.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | enum Skill { coding, engineering, ux, coordination }
4 |
5 | // get a random set of skills and difficutly values
6 | Map randomDifficulty(
7 | Random randomizer, Set availableSkills,
8 | {double maxDifficulty = 100}) {
9 | Map difficulty = {};
10 |
11 | List availableSkillList = availableSkills.toList();
12 |
13 | // Add one or two skills.
14 | do {
15 | var skill =
16 | availableSkillList[randomizer.nextInt(availableSkillList.length)];
17 | difficulty[skill] = randomizer.nextDouble() * maxDifficulty;
18 | } while (difficulty.length < 2 && randomizer.nextBool());
19 |
20 | return difficulty;
21 | }
22 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/src/aspect.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 |
3 | /// An aspect of the game world that can be listened to.
4 | ///
5 | /// This is like a [ChangeNotifier], but it uses the concept of dirtiness.
6 | ///
7 | /// This is a very game-centric approach, geared toward state that is heavily
8 | /// interdependent (like in a game simulation) and that is changed all the time
9 | /// (again, like in a game simulation). You probably do not need this
10 | /// for a regular app. Use regular [ChangeNotifier], which is cleaner.
11 | abstract class Aspect extends ChangeNotifier {
12 | bool _dirty = false;
13 |
14 | /// The aspect has changed since the last time [update] was called.
15 | bool get isDirty => _dirty;
16 |
17 | /// Marks the aspect dirty (changed).
18 | ///
19 | /// This can be called from outside the class (unlike [notifyListeners], which
20 | /// is [protected]). Also unlike [notifyListeners], this does not immediately
21 | /// notify. Listeners will be called on next call of [update].
22 | void markDirty() {
23 | _dirty = true;
24 | }
25 |
26 | /// Called every "logical frame" (physical update in game parlance), to update
27 | /// the state of this aspect.
28 | ///
29 | /// Subclasses must call `super.update()`.
30 | @mustCallSuper
31 | void update() {
32 | if (_dirty) {
33 | notifyListeners();
34 | _dirty = false;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/src/aspect_container.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/src/aspect.dart';
2 | import 'package:dev_rpg/src/shared_state/game/src/child_aspect.dart';
3 |
4 | /// A general class for [Aspect]s that contain other [Aspect]s
5 | abstract class AspectContainer extends Aspect {
6 | final List children;
7 | AspectContainer() : children = [];
8 | bool _guardRemove = false;
9 | List _queuedRemoval;
10 |
11 | // Whether or not this container gets marked dirty when a child is dirty.
12 | bool get inheritsDirt => false;
13 |
14 | @override
15 | void update() {
16 | // guard against changing the list while iterating it
17 | _guardRemove = true;
18 | for (final T child in children) {
19 | if (inheritsDirt && child.isDirty) {
20 | markDirty();
21 | }
22 | child.update();
23 | }
24 | _guardRemove = false;
25 |
26 | // Process any queued removals.
27 | if (_queuedRemoval != null) {
28 | _queuedRemoval.forEach(children.remove);
29 | _queuedRemoval = null;
30 | markDirty();
31 | }
32 |
33 | super.update();
34 | }
35 |
36 | void addAspects(Iterable aspects) => aspects.forEach(addAspect);
37 | void clearAspects() {
38 | _queuedRemoval?.clear();
39 | children.clear();
40 | }
41 |
42 | void setAspects(Iterable aspects) {
43 | clearAspects();
44 | addAspects(aspects);
45 | }
46 |
47 | void addAspect(T aspect) {
48 | children.add(aspect);
49 | if (aspect is ChildAspect) {
50 | (aspect as ChildAspect).parent = this;
51 | }
52 | markDirty();
53 | }
54 |
55 | void removeAspect(T aspect) {
56 | // If we attempt to remove an aspect while the list is being iterated,
57 | // queue the removal and process it on our next update.
58 | if (_guardRemove) {
59 | _queuedRemoval ??= [];
60 | _queuedRemoval.add(aspect);
61 | return;
62 | }
63 | children.remove(aspect);
64 | markDirty();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/src/child_aspect.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/src/aspect.dart';
2 |
3 | /// A mixin that allows for hierarchical [Aspect]s
4 | /// and also allows searching for specific parent [Aspect]s
5 | mixin ChildAspect {
6 | Aspect parent;
7 |
8 | T get() {
9 | Aspect looking = parent;
10 | while (looking != null) {
11 | if (looking is T) {
12 | return looking as T;
13 | }
14 | looking = looking is ChildAspect ? (looking as ChildAspect).parent : null;
15 | }
16 |
17 | return null;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/task_blueprint.dart';
2 | import 'package:dev_rpg/src/shared_state/game/task_pool.dart';
3 | import 'package:dev_rpg/src/shared_state/game/work_item.dart';
4 | import 'package:dev_rpg/src/shared_state/game/world.dart';
5 |
6 | enum TaskState { working, completed, rewarded }
7 |
8 | /// A single task for the player and her team to complete.
9 | ///
10 | /// The definition of the task is in [blueprint]. This class holds the runtime
11 | /// state (like [percentComplete]).
12 | class Task extends WorkItem {
13 | final TaskBlueprint blueprint;
14 | TaskState _state = TaskState.working;
15 | TaskState get state => _state;
16 |
17 | Task(this.blueprint) : super(blueprint.name, blueprint.difficulty);
18 |
19 | @override
20 | void onCompleted() {
21 | _state = TaskState.completed;
22 |
23 | get().completeTask(this);
24 | super.onCompleted();
25 | }
26 |
27 | void shipFeature() {
28 | assert(_state == TaskState.completed);
29 | _state = TaskState.rewarded;
30 | markDirty();
31 |
32 | get().shipFeature(this);
33 | get().archiveTask(this);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task_blueprint.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/skill.dart';
2 | import 'package:dev_rpg/src/shared_state/game/task_prerequisite.dart';
3 | import 'package:meta/meta.dart';
4 |
5 | enum MiniGame { none, chomp, sphinx }
6 |
7 | /// A blueprint of a task.
8 | ///
9 | /// This class is immutable. Put runtime state into [Task].
10 | @immutable
11 | class TaskBlueprint implements Prerequisite {
12 | final String name;
13 |
14 | final Map difficulty;
15 |
16 | /// The tasks (and their combinations) that the player must have completed
17 | /// before this task is available.
18 | final Prerequisite requirements;
19 |
20 | final int userReward;
21 |
22 | final int coinReward;
23 |
24 | final MiniGame miniGame;
25 |
26 | /// When sorting several blueprints, the ones with higher priority
27 | /// come first. This is `0` by default.
28 | final int priority;
29 |
30 | /// List of names of tasks that, when started, immediately make this task
31 | /// not selectable. For example, starting a 'Red Design' disallows
32 | /// 'Blue Design'.
33 | final List mutuallyExclusive;
34 |
35 | const TaskBlueprint(this.name, this.difficulty,
36 | {@required this.requirements,
37 | this.userReward = 100,
38 | this.coinReward = 80,
39 | this.priority = 0,
40 | this.mutuallyExclusive = const [],
41 | this.miniGame = MiniGame.none})
42 | : assert(name != null),
43 | assert(difficulty != null),
44 | assert(requirements != null),
45 | assert(priority != null),
46 | assert(mutuallyExclusive != null);
47 |
48 | List get skillsNeeded => difficulty.keys.toList(growable: false);
49 |
50 | /// The task is satisfied if, and only if, it has already been done.
51 | @override
52 | bool isSatisfiedIn(Iterable done) => done.contains(this);
53 | }
54 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task_prerequisite.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/task_blueprint.dart';
2 |
3 | /// A prerequisite that is always satisfied.
4 | const Prerequisite none = _None();
5 |
6 | /// A prerequisite that is satisfied when all of the [prerequisites]
7 | /// are satisfied.
8 | class AllOf implements Prerequisite {
9 | final List prerequisites;
10 |
11 | const AllOf(this.prerequisites);
12 |
13 | @override
14 | bool isSatisfiedIn(Iterable done) {
15 | for (final prerequisite in prerequisites) {
16 | if (!prerequisite.isSatisfiedIn(done)) return false;
17 | }
18 | return true;
19 | }
20 | }
21 |
22 | /// A prerequisite that is satisfied when any of the [prerequisites]
23 | /// is satisfied.
24 | class AnyOf implements Prerequisite {
25 | final List prerequisites;
26 |
27 | const AnyOf(this.prerequisites);
28 |
29 | @override
30 | bool isSatisfiedIn(Iterable done) {
31 | for (final prerequisite in prerequisites) {
32 | if (prerequisite.isSatisfiedIn(done)) return true;
33 | }
34 | return false;
35 | }
36 | }
37 |
38 | /// An abstract class representing something that can be satisfied or not.
39 | ///
40 | /// Implemented by operators ([AnyOf], [AllOf]) and by [TaskBlueprint].
41 | // ignore: one_member_abstracts
42 | abstract class Prerequisite {
43 | bool isSatisfiedIn(Iterable done);
44 | }
45 |
46 | /// A prerequisite that is always satisfied. Only the first tasks in the tree
47 | /// should have this prerequisite.
48 | class _None implements Prerequisite {
49 | const _None();
50 |
51 | @override
52 | bool isSatisfiedIn(Iterable done) => true;
53 | }
54 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task_tree/animations.dart:
--------------------------------------------------------------------------------
1 | part of task_tree;
2 |
3 | const _basicAnimations = TaskBlueprint(
4 | 'Basic Animations',
5 | {Skill.ux: 100},
6 | coinReward: 200,
7 | requirements: AllOf([_alpha]),
8 | );
9 |
10 | const _advancedMotionDesign = TaskBlueprint(
11 | 'Advanced Motion Design',
12 | {Skill.ux: 200, Skill.coordination: 50},
13 | coinReward: 400,
14 | requirements: AllOf([_basicAnimations, _basicDesign, _uxTesting]),
15 | );
16 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task_tree/backend_infrastructure.dart:
--------------------------------------------------------------------------------
1 | part of task_tree;
2 |
3 | const _backendInfrastructure = TaskBlueprint(
4 | 'Backend Infrastructure',
5 | {Skill.engineering: 100, Skill.coding: 100},
6 | coinReward: 200,
7 | requirements: AllOf([_alpha]),
8 | );
9 |
10 | const _fastBackend = TaskBlueprint(
11 | 'Fast Backend',
12 | {Skill.coding: 100},
13 | coinReward: 250,
14 | requirements: AllOf([_backendInfrastructure]),
15 | );
16 |
17 | const _scalableBackend = TaskBlueprint(
18 | 'Scalable backend',
19 | {Skill.coding: 100},
20 | coinReward: 250,
21 | requirements: AllOf([_backendInfrastructure]),
22 | );
23 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task_tree/beta.dart:
--------------------------------------------------------------------------------
1 | part of task_tree;
2 |
3 | const _beta = TaskBlueprint('Beta', {Skill.coordination: 100},
4 | requirements: AllOf([
5 | _alpha,
6 | // Mascot & icon.
7 | AnyOf([_dinosaurMascot, _birdMascot, _catMascot]),
8 | // Design philosophy.
9 | AnyOf([_retroDesign, _scifiDesign, _mainstreamDesign]),
10 | // Color theme.
11 | AnyOf([_redTheme, _greenTheme, _blueTheme]),
12 | ]),
13 | priority: 100,
14 | coinReward: 800,
15 | miniGame: MiniGame.chomp);
16 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task_tree/design.dart:
--------------------------------------------------------------------------------
1 | part of task_tree;
2 |
3 | const _basicDesign = TaskBlueprint(
4 | 'Basic Design',
5 | {Skill.ux: 100, Skill.coordination: 50},
6 | requirements: AllOf([_alpha]),
7 | priority: 50,
8 | );
9 |
10 | const _dinosaurMascot = TaskBlueprint(
11 | 'Dinosaur Mascot & Icon',
12 | {Skill.coordination: 100, Skill.ux: 50},
13 | coinReward: 250,
14 | requirements: AllOf([_basicDesign]),
15 | mutuallyExclusive: ['Bird Mascot & Icon', 'Cat Mascot & Icon'],
16 | priority: 10,
17 | );
18 |
19 | const _birdMascot = TaskBlueprint(
20 | 'Bird Mascot & Icon',
21 | {Skill.coordination: 100, Skill.ux: 50},
22 | coinReward: 250,
23 | requirements: AllOf([_basicDesign]),
24 | mutuallyExclusive: ['Cat Mascot & Icon', 'Dinosaur Mascot & Icon'],
25 | priority: 10,
26 | );
27 |
28 | const _catMascot = TaskBlueprint(
29 | 'Cat Mascot & Icon',
30 | {Skill.coordination: 100, Skill.ux: 50},
31 | coinReward: 250,
32 | requirements: AllOf([_basicDesign]),
33 | mutuallyExclusive: ['Bird Mascot & Icon', 'Dinosaur Mascot & Icon'],
34 | priority: 10,
35 | );
36 |
37 | const _retroDesign = TaskBlueprint(
38 | 'Retro Design',
39 | {Skill.ux: 100},
40 | coinReward: 250,
41 | requirements: AllOf([_basicDesign]),
42 | mutuallyExclusive: ['Sci-Fi Design', 'Mainstream Design'],
43 | priority: 10,
44 | );
45 |
46 | const _scifiDesign = TaskBlueprint(
47 | 'Sci-Fi Design',
48 | {Skill.ux: 100},
49 | coinReward: 250,
50 | requirements: AllOf([_basicDesign]),
51 | mutuallyExclusive: ['Retro Design', 'Mainstream Design'],
52 | priority: 10,
53 | );
54 |
55 | const _mainstreamDesign = TaskBlueprint(
56 | 'Mainstream Design',
57 | {Skill.ux: 50},
58 | coinReward: 250,
59 | requirements: AllOf([_basicDesign]),
60 | mutuallyExclusive: ['Sci-Fi Design', 'Retro Design'],
61 | priority: 10,
62 | );
63 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task_tree/geolocation.dart:
--------------------------------------------------------------------------------
1 | part of task_tree;
2 |
3 | const _geolocation = TaskBlueprint(
4 | 'Geolocation',
5 | {Skill.engineering: 100, Skill.coding: 50},
6 | coinReward: 300,
7 | requirements: AllOf([_alpha]),
8 | );
9 |
10 | const _arMessages = TaskBlueprint(
11 | 'AR Messages',
12 | {Skill.engineering: 50, Skill.coding: 100},
13 | coinReward: 300,
14 | requirements: AllOf([_geolocation]),
15 | );
16 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task_tree/image_messaging.dart:
--------------------------------------------------------------------------------
1 | part of task_tree;
2 |
3 | const _simpleImageMessaging = TaskBlueprint(
4 | 'Simple Image Messaging',
5 | {Skill.engineering: 100, Skill.coding: 50, Skill.ux: 50},
6 | coinReward: 200,
7 | requirements: AllOf([_alpha]),
8 | );
9 |
10 | const _animatedGifSupport = TaskBlueprint(
11 | 'Animated GIF Support',
12 | {Skill.engineering: 50, Skill.coding: 100},
13 | coinReward: 200,
14 | requirements: AllOf([_simpleImageMessaging]),
15 | );
16 |
17 | const _backendImageProcessing = TaskBlueprint(
18 | 'Backend Image Processing',
19 | {Skill.engineering: 50, Skill.coding: 100},
20 | coinReward: 200,
21 | requirements: AllOf([_simpleImageMessaging, _backendInfrastructure]),
22 | );
23 |
24 | const _memeGenerator = TaskBlueprint(
25 | 'Meme Generator',
26 | {Skill.coding: 50, Skill.ux: 50, Skill.coordination: 50},
27 | coinReward: 200,
28 | requirements: AllOf([_simpleImageMessaging, _backendImageProcessing]),
29 | );
30 |
31 | const _imageFilters = TaskBlueprint(
32 | 'Image Filters',
33 | {Skill.coding: 50, Skill.ux: 50},
34 | coinReward: 200,
35 | requirements: AllOf([_backendImageProcessing, _backendInfrastructure]),
36 | );
37 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task_tree/launch.dart:
--------------------------------------------------------------------------------
1 | part of task_tree;
2 |
3 | const _launch = TaskBlueprint('1.0', {Skill.coordination: 100},
4 | requirements: AllOf([
5 | _beta,
6 | // At least one post-beta polish feature.
7 | AnyOf([
8 | _backendPerformanceOptimization,
9 | _backendScalabilityOptimization,
10 | _prelaunchMarketing,
11 | _backendHardening,
12 | _uiPerformanceOptimization,
13 | _uiPolish,
14 | ]),
15 | ]),
16 | priority: 100,
17 | coinReward: 5000,
18 | miniGame: MiniGame.sphinx);
19 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task_tree/natural_language.dart:
--------------------------------------------------------------------------------
1 | part of task_tree;
2 |
3 | const _naturalLanguageGeneration = TaskBlueprint(
4 | 'Natural Language Generation',
5 | {Skill.engineering: 100, Skill.coding: 100},
6 | coinReward: 350,
7 | requirements: AllOf([_alpha]),
8 | );
9 |
10 | const _naturalLanguageUnderstanding = TaskBlueprint(
11 | 'Natural Language Understanding',
12 | {Skill.engineering: 200, Skill.coding: 100},
13 | coinReward: 350,
14 | requirements: AllOf([_alpha]),
15 | );
16 |
17 | const _automatedBots = TaskBlueprint(
18 | 'Automated Bots',
19 | {Skill.coding: 100, Skill.ux: 50},
20 | coinReward: 350,
21 | requirements: AllOf([_naturalLanguageGeneration]),
22 | );
23 |
24 | const _conversationalChatbots = TaskBlueprint(
25 | 'Conversational Chatbots',
26 | {Skill.coding: 200, Skill.ux: 50},
27 | coinReward: 350,
28 | requirements:
29 | AllOf([_naturalLanguageGeneration, _naturalLanguageUnderstanding]),
30 | );
31 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task_tree/pre_alpha.dart:
--------------------------------------------------------------------------------
1 | part of task_tree;
2 |
3 | const _prototype = TaskBlueprint(
4 | 'Prototype',
5 | {Skill.coding: 30},
6 | requirements: none,
7 | );
8 |
9 | const _basicBackend = TaskBlueprint(
10 | 'Basic Backend',
11 | {Skill.coding: 100},
12 | coinReward: 100,
13 | requirements: AllOf([_prototype]),
14 | priority: 10,
15 | );
16 |
17 | const _basicUI = TaskBlueprint(
18 | 'Basic UI',
19 | {Skill.ux: 100},
20 | coinReward: 100,
21 | requirements: AllOf([_prototype]),
22 | mutuallyExclusive: ['Programmer Art UI'],
23 | );
24 |
25 | const _programmerArtUI = TaskBlueprint(
26 | 'Programmer Art UI',
27 | {Skill.coding: 100},
28 | requirements: AllOf([_prototype]),
29 | mutuallyExclusive: ['Basic UI'],
30 | );
31 |
32 | const _alpha =
33 | TaskBlueprint('Alpha release', {Skill.coordination: 100, Skill.coding: 100},
34 | requirements: AllOf([
35 | AnyOf([_programmerArtUI, _basicUI]),
36 | _basicBackend,
37 | ]),
38 | priority: 100,
39 | coinReward: 500,
40 | miniGame: MiniGame.chomp);
41 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task_tree/pre_launch.dart:
--------------------------------------------------------------------------------
1 | part of task_tree;
2 |
3 | const _backendPerformanceOptimization = TaskBlueprint(
4 | 'Backend Performance Optimization',
5 | {Skill.engineering: 100, Skill.coding: 100},
6 | requirements: AllOf([_beta, _fastBackend]),
7 | coinReward: 350,
8 | priority: 50,
9 | );
10 |
11 | const _backendScalabilityOptimization = TaskBlueprint(
12 | 'Backend Scalability Optimization',
13 | {Skill.engineering: 100, Skill.coding: 100},
14 | requirements: AllOf([_beta, _scalableBackend]),
15 | coinReward: 350,
16 | priority: 50,
17 | );
18 |
19 | const _prelaunchMarketing = TaskBlueprint(
20 | 'Pre-launch Marketing',
21 | {Skill.coordination: 100},
22 | requirements: AllOf([_beta]),
23 | coinReward: 350,
24 | priority: 50,
25 | );
26 |
27 | const _backendHardening = TaskBlueprint(
28 | 'Backend Hardening',
29 | {Skill.engineering: 100, Skill.coding: 100},
30 | requirements: AllOf([_beta]),
31 | coinReward: 350,
32 | priority: 50,
33 | );
34 |
35 | const _uiPerformanceOptimization = TaskBlueprint(
36 | 'UI Performance Optimization',
37 | {Skill.coding: 100, Skill.ux: 50},
38 | requirements: AllOf([_beta]),
39 | coinReward: 350,
40 | priority: 50,
41 | );
42 |
43 | const _uiPolish = TaskBlueprint(
44 | 'UI Polish',
45 | {Skill.coding: 100, Skill.ux: 100},
46 | requirements: AllOf([_beta]),
47 | coinReward: 350,
48 | priority: 50,
49 | );
50 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task_tree/responsive_design.dart:
--------------------------------------------------------------------------------
1 | part of task_tree;
2 |
3 | const _responsiveDesign = TaskBlueprint(
4 | 'Responsive Design',
5 | {Skill.ux: 100, Skill.coordination: 50, Skill.engineering: 50},
6 | requirements: AllOf([_basicDesign]),
7 | coinReward: 150,
8 | );
9 |
10 | const _tabletUI = TaskBlueprint(
11 | 'Tablet UI',
12 | {Skill.ux: 100, Skill.coding: 50},
13 | requirements: AllOf([_basicDesign]),
14 | coinReward: 150,
15 | );
16 |
17 | const _desktopUI = TaskBlueprint(
18 | 'Desktop UI',
19 | {Skill.ux: 100, Skill.coding: 50},
20 | requirements: AllOf([_basicDesign]),
21 | coinReward: 250,
22 | );
23 |
24 | const _iOSDesign = TaskBlueprint(
25 | 'Custom iOS Design',
26 | {Skill.ux: 100, Skill.coding: 50},
27 | requirements: AllOf([_basicDesign]),
28 | coinReward: 150,
29 | );
30 |
31 | const _webVersion = TaskBlueprint(
32 | 'Web Version',
33 | {Skill.coding: 100, Skill.ux: 50},
34 | requirements: AllOf([_desktopUI]),
35 | coinReward: 350,
36 | );
37 |
38 | const _desktopVersion = TaskBlueprint(
39 | 'Desktop Version',
40 | {Skill.coding: 100, Skill.ux: 50},
41 | requirements: AllOf([_desktopUI]),
42 | coinReward: 350,
43 | );
44 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task_tree/task_tree.dart:
--------------------------------------------------------------------------------
1 | library task_tree;
2 |
3 | import 'package:dev_rpg/src/shared_state/game/skill.dart';
4 | import 'package:dev_rpg/src/shared_state/game/task_blueprint.dart';
5 | import 'package:dev_rpg/src/shared_state/game/task_prerequisite.dart';
6 | import 'package:dev_rpg/src/shared_state/game/task_tree/tree_hierarchy.dart';
7 |
8 | part 'animations.dart';
9 | part 'backend_infrastructure.dart';
10 | part 'beta.dart';
11 | part 'design.dart';
12 | part 'geolocation.dart';
13 | part 'image_messaging.dart';
14 | part 'launch.dart';
15 | part 'natural_language.dart';
16 | part 'pre_alpha.dart';
17 | part 'pre_launch.dart';
18 | part 'responsive_design.dart';
19 | part 'theme.dart';
20 | part 'ux_testing.dart';
21 |
22 | /// The set of all tasks.
23 | const Set taskTree = {
24 | // animations.dart
25 | _basicAnimations,
26 | _advancedMotionDesign,
27 | // backend_infrastructure.dart
28 | _backendInfrastructure,
29 | _fastBackend,
30 | _scalableBackend,
31 | // beta.dart
32 | _beta,
33 | // design.dart
34 | _basicDesign,
35 | _dinosaurMascot,
36 | _birdMascot,
37 | _catMascot,
38 | _retroDesign,
39 | _scifiDesign,
40 | _mainstreamDesign,
41 | // geolocation.dart
42 | _geolocation,
43 | _arMessages,
44 | // image_messaging.dart
45 | _simpleImageMessaging,
46 | _animatedGifSupport,
47 | _backendImageProcessing,
48 | _memeGenerator,
49 | _imageFilters,
50 | // launch.dart
51 | _launch,
52 | // natural_language.dart
53 | _naturalLanguageGeneration,
54 | _naturalLanguageUnderstanding,
55 | _automatedBots,
56 | _conversationalChatbots,
57 | // pre_alpha.dart
58 | _prototype,
59 | _basicUI,
60 | _basicBackend,
61 | _programmerArtUI,
62 | _alpha,
63 | // pre_launch.dart
64 | _backendPerformanceOptimization,
65 | _backendScalabilityOptimization,
66 | _prelaunchMarketing,
67 | _backendHardening,
68 | _uiPerformanceOptimization,
69 | _uiPolish,
70 | // responsive_design.dart
71 | _responsiveDesign,
72 | _tabletUI,
73 | _desktopUI,
74 | _iOSDesign,
75 | _webVersion,
76 | _desktopVersion,
77 | // theme.dart
78 | _basicTheme,
79 | _greenTheme,
80 | _redTheme,
81 | _blueTheme,
82 | // ux_testing.dart
83 | _uxTesting,
84 | _foreignLanguageUx,
85 | _accessibilityUx,
86 | _internationalization,
87 | _accessibility,
88 | };
89 |
90 | // Store which tasks have been processed as we go to avoid finding
91 | // multiple dependencies which would cause a stack overflow.
92 | // Automatically exclude top level nodes from being found as dependencies
93 | // to other nodes.
94 | Set _processedTaskTree = {_prototype, _alpha, _beta, _launch};
95 |
96 | // Build the top down top level categories.
97 | class TaskNode implements TreeData {
98 | final TaskBlueprint blueprint;
99 | @override
100 | final List children = [];
101 |
102 | TaskNode(this.blueprint, [bool isTop = false]) {
103 | _processedTaskTree.add(blueprint);
104 | // Find tasks that are direct dependents of this task.
105 | for (final otherBlueprint in taskTree) {
106 | if (_processedTaskTree.contains(otherBlueprint) ||
107 | otherBlueprint == blueprint ||
108 | !otherBlueprint.requirements.isSatisfiedIn([blueprint])) {
109 | continue;
110 | }
111 | children.add(TaskNode(otherBlueprint));
112 | }
113 |
114 | if (isTop) {
115 | // Patch remaining items that are satisfied by this full set.
116 | // We have to do this because some items (like _advancedMotionDesign)
117 | // are only satisfied when multiple non-direct descendent items in the
118 | // tree are satisfied.
119 | //
120 | // ignore: literal_only_boolean_expressions
121 | while (true) {
122 | var patched = false;
123 |
124 | final allPrereqs = allPrerequisites;
125 | for (final otherBlueprint in taskTree) {
126 | if (_processedTaskTree.contains(otherBlueprint) ||
127 | otherBlueprint == blueprint ||
128 | !otherBlueprint.requirements.isSatisfiedIn(allPrereqs)) {
129 | continue;
130 | }
131 | // This changes the full list of prereqs, so patch again.
132 | children.add(TaskNode(otherBlueprint));
133 | patched = true;
134 | break;
135 | }
136 | if (!patched) {
137 | break;
138 | }
139 | }
140 | }
141 |
142 | children.sort((TaskNode a, TaskNode b) =>
143 | a.blueprint.priority.compareTo(b.blueprint.priority));
144 | }
145 |
146 | List get allPrerequisites => [blueprint]
147 | ..addAll(children.expand((child) => child.allPrerequisites).toList());
148 | }
149 |
150 | TaskNode prototypeTaskNode = TaskNode(_prototype, true);
151 | TaskNode alphaTaskNode = TaskNode(_alpha, true);
152 | TaskNode betaTaskNode = TaskNode(_beta, true);
153 | TaskNode launchTaskNode = TaskNode(_launch, true);
154 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task_tree/theme.dart:
--------------------------------------------------------------------------------
1 | part of task_tree;
2 |
3 | const _basicTheme = TaskBlueprint(
4 | 'Basic Theme',
5 | {Skill.ux: 100, Skill.coordination: 50},
6 | requirements: AllOf([_alpha]),
7 | coinReward: 250,
8 | priority: 50,
9 | );
10 |
11 | const _greenTheme = TaskBlueprint(
12 | 'Green Theme',
13 | {Skill.ux: 50},
14 | requirements: AllOf([_basicTheme]),
15 | mutuallyExclusive: ['Red Theme', 'Blue Theme'],
16 | coinReward: 250,
17 | priority: 10,
18 | );
19 |
20 | const _redTheme = TaskBlueprint(
21 | 'Red Theme',
22 | {Skill.ux: 50},
23 | requirements: AllOf([_basicTheme]),
24 | mutuallyExclusive: ['Green Theme', 'Blue Theme'],
25 | coinReward: 250,
26 | priority: 10,
27 | );
28 |
29 | const _blueTheme = TaskBlueprint(
30 | 'Blue Theme',
31 | {Skill.ux: 50},
32 | requirements: AllOf([_basicTheme]),
33 | mutuallyExclusive: ['Green Theme', 'Red Theme'],
34 | coinReward: 250,
35 | priority: 10,
36 | );
37 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task_tree/tree_hierarchy.dart:
--------------------------------------------------------------------------------
1 | /// An interface for hierarchical data.
2 | abstract class TreeData {
3 | List get children;
4 | }
5 |
6 | /// Flattened representation of the tree, making it possible
7 | /// to render the tree from virtualized (one dimensional) array.
8 | class FlattenedTreeData {
9 | /// Reference back to the hierarchical node represented by this
10 | /// flat structure.
11 | final TreeData data;
12 |
13 | /// Whether this node has a sibling right after it
14 | bool hasNextSibling = false;
15 |
16 | /// Whether this node contains a child and hence must draw an extra line
17 | /// under itself to that child.
18 | bool hasNextChild = false;
19 |
20 | /// The list of horizontal spaces (indentation) and whether or not they
21 | /// contain a line.
22 | List lines = [];
23 |
24 | FlattenedTreeData(this.data);
25 | }
26 |
27 | /// Flatten the hierarchical tree and store properties necessary to help
28 | /// define where connecting lines need to be drawn.
29 | List flattenTree(List list,
30 | [List flattened, List depth]) {
31 | flattened ??= [];
32 | depth ??= [true];
33 | for (int i = 0; i < list.length; i++) {
34 | final item = list[i];
35 | final flat = FlattenedTreeData(item);
36 | flat.lines = depth;
37 | flattened.add(flat);
38 | flat.hasNextSibling = i != list.length - 1;
39 | flat.hasNextChild = item.children.isNotEmpty;
40 | if (item.children.isNotEmpty) {
41 | List childDepth = List.from(depth);
42 | if (!flat.hasNextSibling && childDepth.length != 1) {
43 | childDepth[childDepth.length - 1] = false;
44 | }
45 | childDepth.add(true);
46 | flattenTree(item.children, flattened, childDepth);
47 | }
48 | }
49 | return flattened;
50 | }
51 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/task_tree/ux_testing.dart:
--------------------------------------------------------------------------------
1 | part of task_tree;
2 |
3 | const _uxTesting = TaskBlueprint(
4 | 'UX Testing',
5 | {Skill.ux: 100},
6 | coinReward: 120,
7 | requirements: AllOf([_alpha]),
8 | );
9 |
10 | const _foreignLanguageUx = TaskBlueprint(
11 | 'Foreign Language UX',
12 | {Skill.ux: 100},
13 | coinReward: 120,
14 | requirements: AllOf([_uxTesting]),
15 | );
16 |
17 | const _accessibilityUx = TaskBlueprint(
18 | 'Accessibility UX',
19 | {Skill.ux: 100},
20 | coinReward: 120,
21 | requirements: AllOf([_uxTesting]),
22 | );
23 |
24 | const _internationalization = TaskBlueprint(
25 | 'Internationalization',
26 | {Skill.coding: 100, Skill.engineering: 100, Skill.coordination: 50},
27 | requirements: AllOf([_foreignLanguageUx]),
28 | coinReward: 120,
29 | );
30 |
31 | const _accessibility = TaskBlueprint(
32 | 'Accessibility',
33 | {Skill.coding: 100, Skill.engineering: 10, Skill.coordination: 50},
34 | requirements: AllOf([_accessibilityUx]),
35 | coinReward: 120,
36 | );
37 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/work_item.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:dev_rpg/src/shared_state/game/character.dart';
4 | import 'package:dev_rpg/src/shared_state/game/skill.dart';
5 | import 'package:dev_rpg/src/shared_state/game/src/aspect.dart';
6 | import 'package:dev_rpg/src/shared_state/game/src/child_aspect.dart';
7 |
8 | /// An item that a set of [Character]s with specific [Skill]s can work on.
9 | abstract class WorkItem extends Aspect with ChildAspect {
10 | final String name;
11 | final Map difficulty;
12 | final Map completion;
13 |
14 | List _assignedTeam;
15 | List get assignedTeam => _assignedTeam;
16 |
17 | bool get isComplete => percentComplete == 1;
18 | List get skillsNeeded => difficulty.keys.toList(growable: false);
19 |
20 | bool get isBeingWorkedOn => _assignedTeam?.isNotEmpty ?? false;
21 |
22 | /// Reduce the length of time to complete a task with a boost.
23 | double _boost = 1;
24 |
25 | WorkItem(this.name, this.difficulty)
26 | : completion = difficulty.map((Skill s, _) => MapEntry(s, 0));
27 |
28 | void assignTeam(List team) {
29 | if (_assignedTeam != null) {
30 | // First, mark member who were unassigned as not busy.
31 | _assignedTeam.forEach((character) => character.isBusy = false);
32 | }
33 | _assignedTeam = team;
34 | _assignedTeam.forEach((character) => character.isBusy = true);
35 | markDirty();
36 | }
37 |
38 | void freeTeam() {
39 | if (_assignedTeam == null) {
40 | return;
41 | }
42 | for (final character in _assignedTeam) {
43 | character.isBusy = false;
44 | }
45 | _assignedTeam = null;
46 | }
47 |
48 | void onCompleted() {
49 | // Free up the workers if they are done!
50 | freeTeam();
51 | }
52 |
53 | @override
54 | void update() {
55 | if (isComplete || _assignedTeam == null) {
56 | super.update();
57 | return;
58 | }
59 |
60 | for (final character in _assignedTeam) {
61 | for (final skill in character.prowess.keys) {
62 | if (!skillsNeeded.contains(skill)) continue;
63 | var prowess = character.prowess[skill];
64 | completion[skill] += prowess * _boost;
65 | }
66 | }
67 | _boost = 1;
68 | if (percentComplete >= 1) {
69 | onCompleted();
70 | }
71 |
72 | markDirty();
73 | super.update();
74 | }
75 |
76 | bool addBoost() {
77 | if (!isBeingWorkedOn) {
78 | return false;
79 | }
80 | _boost += 2.5;
81 | return true;
82 | }
83 |
84 | /// Get progress of this task.
85 | double get percentComplete {
86 | double required = 0;
87 | double completed = 0;
88 |
89 | // accumulate the required skills and the amount completed for each one
90 | difficulty.forEach((Skill skill, double amount) {
91 | required += amount;
92 | // Make sure to not count one skill overworking as another :)
93 | completed += min(amount, completion[skill] ?? 0);
94 | });
95 |
96 | return completed / required;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/lib/src/shared_state/game/world.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:dev_rpg/src/shared_state/game/character_pool.dart';
4 | import 'package:dev_rpg/src/shared_state/game/company.dart';
5 | import 'package:dev_rpg/src/shared_state/game/src/aspect_container.dart';
6 | import 'package:dev_rpg/src/shared_state/game/task.dart';
7 | import 'package:dev_rpg/src/shared_state/game/task_pool.dart';
8 |
9 | /// The state of the game world.
10 | ///
11 | /// Widgets should subscribe to aspects of the world (such as [projectPool])
12 | /// instead of this whole world, unless they care about the most high-level
13 | /// stuff (like whether the simulation is running).
14 | class World extends AspectContainer {
15 | static const Duration newFeatureJoyDuration = Duration(seconds: 5);
16 |
17 | static Duration tickDuration = const Duration(milliseconds: 200);
18 |
19 | Timer _timer;
20 | double _joyAccumulation = 0;
21 | Timer _joyResetTimer;
22 |
23 | final CharacterPool characterPool;
24 |
25 | final TaskPool taskPool;
26 |
27 | final Company company;
28 |
29 | bool _isRunning = false;
30 |
31 | World()
32 | : characterPool = CharacterPool(),
33 | taskPool = TaskPool(),
34 | company = Company() {
35 | addAspect(characterPool);
36 | addAspect(taskPool);
37 | addAspect(company);
38 | }
39 |
40 | /// Returns `true` when the simulation is currently running.
41 | bool get isRunning => _isRunning;
42 |
43 | void pause() {
44 | if (_joyResetTimer?.isActive ?? false) {
45 | _joyResetTimer.cancel();
46 | _resetJoy();
47 | }
48 |
49 | _timer.cancel();
50 | _isRunning = false;
51 | markDirty();
52 | }
53 |
54 | void start() {
55 | _timer?.cancel();
56 | _timer = Timer.periodic(tickDuration, _performTick);
57 | _isRunning = true;
58 | markDirty();
59 | }
60 |
61 | void _performTick(Timer timer) {
62 | update();
63 | }
64 |
65 | /// TODO: Feature joy should probably depend on the feature
66 | /// (might be another stat for the feature/task).
67 | static const double featureJoy = 5;
68 |
69 | void _resetJoy() {
70 | company.joy.number -= _joyAccumulation;
71 | _joyResetTimer = null;
72 | _joyAccumulation = 0;
73 | }
74 |
75 | void shipFeature(Task task) {
76 | // Todo: modify these values by how quickly the user completed the task
77 | // some bonus system?
78 |
79 | // Give some joy for the new feature, at least for a while.
80 | company.joy.number += featureJoy;
81 |
82 | _joyAccumulation += featureJoy;
83 | _joyResetTimer?.cancel();
84 | _joyResetTimer = Timer(newFeatureJoyDuration, _resetJoy);
85 |
86 | company.award(task.blueprint.userReward, task.blueprint.coinReward);
87 | }
88 |
89 | void reset() {
90 | _joyResetTimer?.cancel();
91 | _timer?.cancel();
92 | _joyAccumulation = 0;
93 | company.reset();
94 | characterPool.reset();
95 | taskPool.reset();
96 | start();
97 | }
98 |
99 | @override
100 | void dispose() {
101 | _joyResetTimer?.cancel();
102 | if (_timer.isActive) {
103 | _timer.cancel();
104 | }
105 | super.dispose();
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/lib/src/shared_state/user.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 |
3 | class User extends ChangeNotifier {
4 | final String name = 'Daring Developer';
5 |
6 | @override
7 | String toString() => name;
8 | }
9 |
--------------------------------------------------------------------------------
/lib/src/style.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/skill.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | const Color contentColor = Color.fromRGBO(38, 38, 47, 1);
5 | const Color modalBackgroundColor = Color.fromRGBO(241, 241, 241, 1);
6 | const Color secondaryContentColor = Color.fromRGBO(111, 111, 118, 1);
7 | const Color skillTextColor = Color.fromRGBO(5, 59, 73, 1);
8 | const Color attentionColor = Color.fromRGBO(0, 152, 255, 1);
9 | const Color disabledColor = Color.fromRGBO(116, 116, 126, 1);
10 | const Color disabledTaskColor = Color.fromRGBO(38, 38, 47, 0.25);
11 | const Color treeLineColor = Color.fromRGBO(215, 215, 215, 1);
12 | const Color bugColor = Color.fromRGBO(236, 41, 117, 1);
13 | const Color statsSeparatorColor = Color.fromRGBO(57, 57, 71, 1);
14 |
15 | /// Maximum logical pixel width for a modal window.
16 | const double modalMaxWidth = 400;
17 |
18 | /// Once the logic screen pixel width exceeds this number, show the ultrawide
19 | /// layout.
20 | const double ultraWideLayoutThreshold = 1920;
21 |
22 | /// Once the logic screen pixel width exceeds this number, show the wide layout.
23 | const double wideLayoutThreshold = 800;
24 |
25 | /// Devices with a logical screen pixel width less than this value
26 | /// will not be permitted to rotate into landscape mode.
27 | const double blockLandscapeThreshold = 750;
28 |
29 | /// Ideal width of a character cell in the character hiring grid. This is used
30 | /// to compute the number of columns to display when viewing the character grid.
31 | const double idealCharacterWidth = 165;
32 |
33 | /// Ideal diameter of a particle in the hiring effect. The actual drawn particle
34 | /// size is computed based on a ratio of this diameter to the ideal character
35 | /// multiplied by the actual character width displayed.
36 | const double idealParticleSize = 10;
37 |
38 | const TextStyle contentSmallStyle = TextStyle(
39 | fontFamily: 'MontserratRegular',
40 | fontSize: 14,
41 | color: secondaryContentColor,
42 | );
43 |
44 | const TextStyle contentStyle = TextStyle(
45 | fontFamily: 'MontserratRegular',
46 | fontSize: 16,
47 | color: contentColor,
48 | );
49 |
50 | const TextStyle contentLargeStyle = TextStyle(
51 | fontFamily: 'MontserratRegular',
52 | fontSize: 24,
53 | color: contentColor,
54 | );
55 |
56 | const TextStyle buttonTextStyle = TextStyle(
57 | fontFamily: 'MontserratMedium',
58 | fontSize: 16,
59 | color: contentColor,
60 | );
61 |
62 | Map skillColor = {
63 | Skill.coding: const Color.fromRGBO(0, 179, 184, 1),
64 | Skill.engineering: const Color.fromRGBO(84, 114, 239, 1),
65 | Skill.ux: const Color.fromRGBO(184, 56, 72, 1),
66 | Skill.coordination: Colors.lightGreen
67 | };
68 |
69 | Map skillFlareIcon = {
70 | Skill.coding: 'assets/flare/CodeIcon.flr',
71 | Skill.engineering: 'assets/flare/EngineeringIcon.flr',
72 | Skill.ux: 'assets/flare/UxIcon.flr',
73 | Skill.coordination: 'assets/flare/CoordinationIcon.flr'
74 | };
75 |
--------------------------------------------------------------------------------
/lib/src/style_sphinx/breathing_animations.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 |
3 | // The base class for Breathing animations. Creates the necessary animation
4 | // controller that can be configured using a provided tween.
5 | class _BreathingBase extends StatefulWidget {
6 | final Widget Function(BuildContext, Animation) builder;
7 | final Tween tween;
8 |
9 | const _BreathingBase({
10 | @required this.builder,
11 | @required this.tween,
12 | Key key,
13 | }) : super(key: key);
14 |
15 | @override
16 | _BreathingBaseState createState() => _BreathingBaseState();
17 | }
18 |
19 | class _BreathingBaseState extends State<_BreathingBase>
20 | with SingleTickerProviderStateMixin {
21 | AnimationController _controller;
22 | Animation _animation;
23 |
24 | @override
25 | void initState() {
26 | // Create the animation controller to drive the offset animation
27 | _controller = AnimationController(
28 | vsync: this,
29 | duration: const Duration(milliseconds: 600),
30 | );
31 |
32 | // Create an animation using the provided tween
33 | _animation =
34 | widget.tween.chain(CurveTween(curve: Curves.ease)).animate(_controller);
35 |
36 | // Start the animation and ensure it repeats indefinitely
37 | _controller
38 | ..forward()
39 | ..repeat(reverse: true);
40 |
41 | super.initState();
42 | }
43 |
44 | @override
45 | void dispose() {
46 | _controller.dispose();
47 | super.dispose();
48 | }
49 |
50 | @override
51 | Widget build(BuildContext context) {
52 | return widget.builder(context, _animation);
53 | }
54 | }
55 |
56 | // Creates a Bouncing "Breathing Animation." It does so using an Offset Tween
57 | // and a SlideTransition.
58 | class Bouncy extends StatelessWidget {
59 | final Widget child;
60 |
61 | const Bouncy({@required this.child, Key key}) : super(key: key);
62 |
63 | @override
64 | Widget build(BuildContext context) {
65 | return _BreathingBase(
66 | tween: Tween(
67 | begin: const Offset(0, -0.05),
68 | end: const Offset(0, 0.05),
69 | ),
70 | builder: (context, animation) {
71 | return SlideTransition(
72 | position: animation,
73 | child: child,
74 | );
75 | },
76 | );
77 | }
78 | }
79 |
80 | // Creates a Breathing animation that fades a Widget in and out. It does so
81 | // using a Tween with a FadeTransition Widget
82 | class Faded extends StatelessWidget {
83 | final Widget child;
84 | final double begin;
85 | final double end;
86 |
87 | const Faded({
88 | @required this.child,
89 | this.begin = 1,
90 | this.end = 0.8,
91 | Key key,
92 | }) : super(key: key);
93 |
94 | @override
95 | Widget build(BuildContext context) {
96 | return _BreathingBase(
97 | tween: Tween(
98 | begin: begin,
99 | end: end,
100 | ),
101 | builder: (context, animation) {
102 | return FadeTransition(
103 | opacity: animation,
104 | child: child,
105 | );
106 | },
107 | );
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/lib/src/style_sphinx/kittens.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 |
3 | enum KittyType { orange, yellow }
4 |
5 | class KittyBed extends StatelessWidget {
6 | static const ImageProvider redProvider =
7 | AssetImage('assets/style_sphinx/red_bed.png');
8 | static const ImageProvider greenProvider =
9 | AssetImage('assets/style_sphinx/green_bed.png');
10 |
11 | final KittyType type;
12 |
13 | const KittyBed({@required this.type, Key key}) : super(key: key);
14 |
15 | @override
16 | Widget build(BuildContext context) =>
17 | _SizedItem(child: Image(image: _provider));
18 |
19 | ImageProvider get _provider {
20 | switch (type) {
21 | case KittyType.orange:
22 | return redProvider;
23 | case KittyType.yellow:
24 | default:
25 | return greenProvider;
26 | }
27 | }
28 | }
29 |
30 | class Kitty extends StatelessWidget {
31 | static const ImageProvider orangeProvider =
32 | AssetImage('assets/style_sphinx/orange_cat.png');
33 | static const ImageProvider yellowProvider =
34 | AssetImage('assets/style_sphinx/yellow_cat.png');
35 |
36 | final KittyType type;
37 |
38 | const Kitty({@required this.type, Key key}) : super(key: key);
39 |
40 | @override
41 | Widget build(BuildContext context) =>
42 | _SizedItem(child: Image(image: _provider));
43 |
44 | ImageProvider get _provider {
45 | switch (type) {
46 | case KittyType.orange:
47 | return orangeProvider;
48 | case KittyType.yellow:
49 | default:
50 | return yellowProvider;
51 | }
52 | }
53 | }
54 |
55 | class _SizedItem extends StatelessWidget {
56 | final Widget child;
57 |
58 | const _SizedItem({@required this.child, Key key}) : super(key: key);
59 |
60 | @override
61 | Widget build(BuildContext context) {
62 | final screenSize = MediaQuery.of(context).size;
63 | final width = screenSize.width;
64 | final height = screenSize.height;
65 |
66 | return ConstrainedBox(
67 | constraints: BoxConstraints(
68 | maxWidth: width < 600 ? 120 : width < 900 ? 160 : 200,
69 | maxHeight: height < 600 ? 120 : height < 900 ? 160 : 200,
70 | ),
71 | child: child,
72 | );
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/lib/src/style_sphinx/question_arguments.dart:
--------------------------------------------------------------------------------
1 | /// Arguments that drive the game from one screen to the next.
2 | class QuestionArguments {
3 | final List questionRoutes;
4 | final int currentIndex;
5 |
6 | QuestionArguments({
7 | this.questionRoutes = const [],
8 | this.currentIndex = 0,
9 | });
10 |
11 | bool get hasNextQuestion => currentIndex + 1 < questionRoutes.length;
12 |
13 | String get routeName => questionRoutes[currentIndex];
14 |
15 | QuestionArguments nextQuestion() {
16 | if (!hasNextQuestion) throw StateError('No next question available');
17 |
18 | return QuestionArguments(
19 | questionRoutes: questionRoutes,
20 | currentIndex: currentIndex + 1,
21 | );
22 | }
23 |
24 | @override
25 | String toString() =>
26 | '''QuestionArguments{questionRoutes: $questionRoutes, currentIndex: $currentIndex}''';
27 |
28 | @override
29 | bool operator ==(Object other) =>
30 | identical(this, other) ||
31 | other is QuestionArguments &&
32 | runtimeType == other.runtimeType &&
33 | questionRoutes == other.questionRoutes &&
34 | currentIndex == other.currentIndex;
35 |
36 | @override
37 | int get hashCode => questionRoutes.hashCode ^ currentIndex.hashCode;
38 | }
39 |
--------------------------------------------------------------------------------
/lib/src/style_sphinx/question_scaffold.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class QuestionScaffold extends StatelessWidget {
4 | final Widget question;
5 | final Widget expected;
6 | final Widget actual;
7 |
8 | const QuestionScaffold({
9 | Key key,
10 | this.question,
11 | this.expected,
12 | this.actual,
13 | }) : super(key: key);
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return Scaffold(
18 | backgroundColor: const Color.fromRGBO(246, 216, 204, 1),
19 | body: Column(
20 | crossAxisAlignment: CrossAxisAlignment.start,
21 | children: [
22 | SafeArea(
23 | child: Padding(
24 | padding: const EdgeInsets.only(
25 | left: 24,
26 | right: 24,
27 | bottom: 24,
28 | top: 8,
29 | ),
30 | child: question,
31 | ),
32 | ),
33 | Expanded(
34 | child: Stack(
35 | children: [
36 | Positioned.fill(
37 | child: Container(
38 | color: const Color.fromRGBO(252, 235, 227, 1),
39 | ),
40 | ),
41 | Positioned.fill(
42 | child: Padding(
43 | padding: const EdgeInsets.only(
44 | left: 24,
45 | right: 24,
46 | top: 0,
47 | bottom: 48,
48 | ),
49 | child: expected,
50 | ),
51 | ),
52 | Positioned.fill(
53 | child: Padding(
54 | padding: const EdgeInsets.only(
55 | left: 24,
56 | right: 24,
57 | top: 0,
58 | bottom: 48,
59 | ),
60 | child: actual,
61 | ),
62 | ),
63 | ],
64 | ),
65 | ),
66 | ],
67 | ),
68 | );
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/lib/src/style_sphinx/sphinx_buttton.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class SphinxButton extends StatelessWidget {
4 | final VoidCallback onPressed;
5 | final Widget child;
6 |
7 | const SphinxButton({
8 | @required this.onPressed,
9 | @required this.child,
10 | Key key,
11 | }) : super(key: key);
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | final radius = BorderRadius.circular(10);
16 |
17 | return Material(
18 | shape: RoundedRectangleBorder(borderRadius: radius),
19 | color: const Color.fromRGBO(242, 124, 78, 1),
20 | child: InkWell(
21 | borderRadius: radius,
22 | splashColor: const Color.fromRGBO(242, 124, 78, 1),
23 | child: Container(
24 | padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 16),
25 | child: DefaultTextStyle(
26 | child: child,
27 | style: const TextStyle(
28 | fontFamily: 'MontserratMedium',
29 | fontSize: 16,
30 | fontWeight: FontWeight.bold,
31 | color: Color.fromRGBO(85, 34, 34, 1)),
32 | ),
33 | ),
34 | onTap: onPressed,
35 | ),
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/src/style_sphinx/sphinx_image.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 |
3 | class SphinxImage extends StatelessWidget {
4 | static const ImageProvider provider =
5 | AssetImage('assets/style_sphinx/sphinx.png');
6 |
7 | const SphinxImage();
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Image(image: provider);
12 | }
13 | }
14 |
15 | class SphinxWithoutGlassesImage extends StatelessWidget {
16 | static const ImageProvider provider =
17 | AssetImage('assets/style_sphinx/sphinx_no_glasses.png');
18 |
19 | const SphinxWithoutGlassesImage();
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return Image(image: provider);
24 | }
25 | }
26 |
27 | class SphinxGlassesImage extends StatelessWidget {
28 | static const ImageProvider provider =
29 | AssetImage('assets/style_sphinx/sunglasses.png');
30 |
31 | const SphinxGlassesImage();
32 |
33 | @override
34 | Widget build(BuildContext context) {
35 | return Image(image: provider);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/src/style_sphinx/text_bubble.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/widgets.dart';
3 |
4 | /// Determines whether the indicator should point left or right
5 | enum TextBubbleDirection { left, right }
6 |
7 | /// Place content inside a Text Bubble
8 | class TextBubble extends StatelessWidget {
9 | final Widget child;
10 | final TextBubbleDirection direction;
11 | final Radius radius;
12 | final Size indicatorSize;
13 | final double shadowOffset;
14 | final EdgeInsets padding;
15 |
16 | const TextBubble({
17 | @required this.child,
18 | Key key,
19 | this.padding = const EdgeInsets.all(16),
20 | this.direction = TextBubbleDirection.left,
21 | this.radius = const Radius.circular(10),
22 | this.indicatorSize = const Size(35, 20),
23 | this.shadowOffset = 10,
24 | }) : super(key: key);
25 |
26 | @override
27 | Widget build(BuildContext context) {
28 | return Stack(
29 | children: [
30 | Positioned.fill(
31 | child: CustomPaint(
32 | painter: _TextBubbleBackgroundPainter(
33 | direction: direction,
34 | radius: radius,
35 | indicatorSize: indicatorSize,
36 | shadowOffset: shadowOffset,
37 | ),
38 | ),
39 | ),
40 | // Text
41 | Column(
42 | children: [
43 | Padding(
44 | padding: EdgeInsets.only(
45 | left: padding.left,
46 | right: padding.right,
47 | top: padding.top,
48 | bottom: padding.bottom + indicatorSize.height + shadowOffset,
49 | ),
50 | child: DefaultTextStyle(
51 | child: child,
52 | style: const TextStyle(
53 | color: Colors.black,
54 | fontFamily: 'MontserratMedium',
55 | fontSize: 16,
56 | ),
57 | ),
58 | ),
59 | ],
60 | ),
61 | ],
62 | );
63 | }
64 | }
65 |
66 | // Create a "Text Bubble" using a CustomPainter.
67 | class _TextBubbleBackgroundPainter extends CustomPainter {
68 | final TextBubbleDirection direction;
69 | final Radius radius;
70 | final Size indicatorSize;
71 | final double shadowOffset;
72 |
73 | _TextBubbleBackgroundPainter({
74 | @required this.direction,
75 | @required this.radius,
76 | @required this.indicatorSize,
77 | @required this.shadowOffset,
78 | });
79 |
80 | @override
81 | void paint(Canvas canvas, Size size) {
82 | final bubbleBottom = size.height - indicatorSize.height - shadowOffset;
83 |
84 | // Start the path and create the first two rounded corners
85 | final path = Path()
86 | ..moveTo(
87 | radius.x,
88 | 0,
89 | )
90 | ..lineTo(size.width - radius.x, 0)
91 | ..arcToPoint(
92 | Offset(size.width, radius.x),
93 | radius: radius,
94 | )
95 | ..lineTo(size.width, bubbleBottom - radius.y)
96 | ..arcToPoint(
97 | Offset(size.width - radius.x, bubbleBottom),
98 | radius: radius,
99 | );
100 |
101 | // After drawing the top and bottom-right corner, use the direction to
102 | // determine which indicator to draw: Facing left or right.
103 | if (direction == TextBubbleDirection.left) {
104 | final startX = size.width - radius.x - size.width / 6;
105 | final endX = startX - indicatorSize.width;
106 |
107 | path
108 | ..lineTo(startX, bubbleBottom)
109 | ..lineTo(endX, bubbleBottom + indicatorSize.height)
110 | ..lineTo(endX, bubbleBottom);
111 | } else {
112 | final startX = radius.x + indicatorSize.width + size.width / 6;
113 | final endX = startX - indicatorSize.width;
114 |
115 | path
116 | ..lineTo(startX, bubbleBottom)
117 | ..lineTo(startX, bubbleBottom + indicatorSize.height)
118 | ..lineTo(endX, bubbleBottom);
119 | }
120 |
121 | // Complete the path by creating the last two rounded corners
122 | path
123 | ..lineTo(radius.x, bubbleBottom)
124 | ..arcToPoint(
125 | Offset(0, bubbleBottom - radius.y),
126 | radius: radius,
127 | )
128 | ..lineTo(0, radius.y)
129 | ..arcToPoint(
130 | Offset(radius.x, 0),
131 | radius: radius,
132 | );
133 |
134 | // Paint the shadow
135 | canvas.save();
136 | canvas.translate(0, shadowOffset);
137 | canvas.drawPath(
138 | path,
139 | Paint()
140 | ..color = const Color.fromRGBO(85, 34, 34, 0.29)
141 | ..style = PaintingStyle.fill,
142 | );
143 |
144 | // Then, paint the white background
145 | canvas.restore();
146 | canvas.drawPath(
147 | path,
148 | Paint()
149 | ..color = Colors.white
150 | ..style = PaintingStyle.fill,
151 | );
152 | }
153 |
154 | @override
155 | bool shouldRepaint(_TextBubbleBackgroundPainter old) {
156 | return direction != old.direction;
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/lib/src/widgets/app_bar/coin_badge.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/company.dart';
2 | import 'package:dev_rpg/src/widgets/app_bar/stat_badge.dart';
3 |
4 | /// Visually indicates the amount of capital the [Company] has amassed for this
5 | /// game session.
6 | class CoinBadge extends StatBadge {
7 | CoinBadge(StatValue statValue, {double scale = 1, bool isWide = false})
8 | : super('Capital', statValue,
9 | flare: 'assets/flare/Coin.flr', scale: scale, isWide: isWide);
10 |
11 | /// Play the indicator animation after this value changes by at least 5 coins.
12 | @override
13 | int get celebrateAfter => 5;
14 | }
15 |
--------------------------------------------------------------------------------
/lib/src/widgets/app_bar/joy_badge.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/company.dart';
2 | import 'package:dev_rpg/src/widgets/app_bar/stat_badge.dart';
3 |
4 | /// Visually indicates the level of joy via a Flare animation with distinct
5 | /// sad, happy, neutral states and a call to attention animation whenever
6 | /// the numeric value changes.
7 | class JoyBadge extends StatBadge {
8 | JoyBadge(StatValue statValue, {double scale = 1, bool isWide = false})
9 | : super('Joy', statValue,
10 | flare: 'assets/flare/Joy.flr', scale: scale, isWide: isWide);
11 |
12 | /// Play the celebration animation whenever there's a change
13 | /// 0 is a flag for always in this case
14 | @override
15 | double get celebrateAfter => 0;
16 |
17 | @override
18 | JoyBadgeState createState() => JoyBadgeState();
19 | }
20 |
21 | /// The three emotions this widget can display
22 | enum _Emotion { sad, happy, neutral }
23 |
24 | class JoyBadgeState extends StatBadgeState {
25 | _Emotion _emotion;
26 |
27 | _Emotion get emotion => _emotion;
28 | set emotion(_Emotion value) {
29 | if (value == _emotion) {
30 | return;
31 | }
32 | _emotion = value;
33 | switch (_emotion) {
34 | case _Emotion.sad:
35 | controls.play('sad');
36 | break;
37 | case _Emotion.happy:
38 | controls.play('happy');
39 | break;
40 | case _Emotion.neutral:
41 | controls.play('neutral');
42 | break;
43 | }
44 | }
45 |
46 | @override
47 | void valueChanged() {
48 | double joy = widget.statValue.number;
49 | if (joy < 0) {
50 | emotion = _Emotion.sad;
51 | } else if (joy > 3) {
52 | emotion = _Emotion.happy;
53 | } else {
54 | emotion = _Emotion.neutral;
55 | }
56 | super.valueChanged();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/lib/src/widgets/app_bar/stat_separator.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import '../../style.dart';
4 |
5 | /// A widget to place between [StatBadge] widgets to show clear
6 | /// separation between them.
7 | class StatSeparator extends StatelessWidget {
8 | @override
9 | Widget build(BuildContext context) {
10 | return Container(
11 | width: 1,
12 | color: statsSeparatorColor,
13 | );
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lib/src/widgets/app_bar/users_badge.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/company.dart';
2 | import 'package:dev_rpg/src/widgets/app_bar/stat_badge.dart';
3 |
4 | /// Visually indicates the number of users the [Company] has amassed for this
5 | /// game session.
6 | class UsersBadge extends StatBadge {
7 | UsersBadge(StatValue statValue,
8 | {double scale = 1, bool isWide = false})
9 | : super('Users', statValue,
10 | flare: 'assets/flare/Users.flr', scale: scale, isWide: isWide);
11 |
12 | /// Play a celebration/call to attention animation after the value changes
13 | /// by 100 users.
14 | @override
15 | double get celebrateAfter => 100;
16 | }
17 |
--------------------------------------------------------------------------------
/lib/src/widgets/buttons/welcome_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/style.dart';
2 | import 'package:dev_rpg/src/widgets/buttons/wide_button.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | /// A styled button that animates its accent color.
6 | class WelcomeButton extends StatefulWidget {
7 | final Widget child;
8 | final Color background;
9 | final IconData icon;
10 | final String label;
11 | final double fontSize;
12 | @required
13 | final VoidCallback onPressed;
14 | const WelcomeButton({
15 | Key key,
16 | this.child,
17 | this.onPressed,
18 | this.background,
19 | this.icon,
20 | this.label,
21 | this.fontSize = 16,
22 | }) : super(key: key);
23 |
24 | @override
25 | _WelcomeButtonState createState() => _WelcomeButtonState();
26 | }
27 |
28 | class _WelcomeButtonState extends State
29 | with SingleTickerProviderStateMixin {
30 | AnimationController _animationController;
31 | Animation _colorTween;
32 |
33 | @override
34 | void initState() {
35 | _animationController = AnimationController(
36 | vsync: this,
37 | duration: const Duration(milliseconds: 3100),
38 | );
39 | _colorTween = ColorTween(begin: widget.background, end: widget.background)
40 | .animate(_animationController);
41 | super.initState();
42 | }
43 |
44 | @override
45 | void didUpdateWidget(WelcomeButton oldWidget) {
46 | setState(() {
47 | _colorTween = ColorTween(begin: _colorTween.value, end: widget.background)
48 | .animate(_animationController);
49 | _animationController.reset();
50 | _animationController.forward();
51 | });
52 | super.didUpdateWidget(oldWidget);
53 | }
54 |
55 | @override
56 | void dispose() {
57 | _animationController?.dispose();
58 | super.dispose();
59 | }
60 |
61 | @override
62 | Widget build(BuildContext context) {
63 | return AnimatedBuilder(
64 | animation: _animationController,
65 | builder: (context, child) => WideButton(
66 | onPressed: widget.onPressed,
67 | background: _colorTween.value,
68 | child: Align(
69 | alignment: Alignment.centerLeft,
70 | child: Row(
71 | children: [
72 | widget.icon == null
73 | ? Container()
74 | : Padding(
75 | padding: const EdgeInsets.only(right: 13),
76 | child: Icon(
77 | widget.icon,
78 | size: widget.fontSize,
79 | color: Colors.white.withOpacity(0.5),
80 | ),
81 | ),
82 | Text(widget.label.toUpperCase(),
83 | style: buttonTextStyle.apply(
84 | color: Colors.white,
85 | fontSizeDelta:
86 | widget.fontSize - buttonTextStyle.fontSize))
87 | ],
88 | ),
89 | ),
90 | ),
91 | );
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/lib/src/widgets/buttons/wide_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | /// A styled button that takes up all of the available horizontal space.
4 | class WideButton extends StatelessWidget {
5 | final Key buttonKey;
6 | final Widget child;
7 | final Color background;
8 | final Color shadowColor;
9 | final bool enabled;
10 | @required
11 | final VoidCallback onPressed;
12 |
13 | /// Use the padding tweak to allow negative adjustments to padding.
14 | final EdgeInsets paddingTweak;
15 |
16 | const WideButton({
17 | this.child,
18 | this.onPressed,
19 | this.background,
20 | this.paddingTweak = const EdgeInsets.all(0),
21 | this.buttonKey,
22 | this.shadowColor,
23 | this.enabled = true,
24 | });
25 |
26 | @override
27 | Widget build(BuildContext context) {
28 | return Container(
29 | constraints: const BoxConstraints(minWidth: double.infinity),
30 | decoration: shadowColor != null
31 | ? BoxDecoration(
32 | boxShadow: [
33 | BoxShadow(
34 | color: shadowColor,
35 | offset: const Offset(0, 10),
36 | blurRadius: 10),
37 | ],
38 | )
39 | : null,
40 | child: FlatButton(
41 | key: buttonKey,
42 | padding: EdgeInsets.only(
43 | left: 20 + paddingTweak.left,
44 | right: 20 + paddingTweak.right,
45 | top: 11 + paddingTweak.top,
46 | bottom: 11 + paddingTweak.bottom),
47 | shape: RoundedRectangleBorder(
48 | borderRadius: BorderRadius.circular(9),
49 | ),
50 | onPressed: enabled ? onPressed : null,
51 | color: background,
52 | child: child),
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/lib/src/widgets/flare/desaturated_actor.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui' as ui;
2 | import 'package:flare_dart/actor_flags.dart';
3 | import 'package:flare_dart/actor_image.dart';
4 | import 'package:flare_flutter/flare.dart';
5 | import 'package:vector_math/vector_math.dart';
6 |
7 | // This is the custom actor shape we create with custom painting
8 | class DesaturatedActor extends FlutterActor {
9 | bool _desaturate = true;
10 | bool get desaturate => _desaturate;
11 | set desaturate(bool value) {
12 | if (_desaturate == value) {
13 | return;
14 | }
15 | _desaturate = value;
16 | artboard.addDirt(artboard.root, DirtyFlags.paintDirty, true);
17 | }
18 |
19 | @override
20 | ActorImage makeImageNode() {
21 | return DesaturatedActorImage();
22 | }
23 | }
24 |
25 | /// Custom ActorImage to draw in place of regular Flare ActorImage
26 | /// We use this to override the paint and do custom color filtering.
27 | class DesaturatedActorImage extends FlutterActorImage {
28 | @override
29 | void onPaintUpdated(ui.Paint paint) {
30 | if (!(artboard.actor as DesaturatedActor).desaturate) {
31 | paint.colorFilter = null;
32 | return;
33 | }
34 | // Light blue tinge.
35 | ui.Color tint = const ui.Color.fromRGBO(222, 222, 255, 1);
36 | double rf = tint.red / 255;
37 | double gf = tint.green / 255;
38 | double bf = tint.blue / 255;
39 |
40 | Matrix3 greyMatrix = Matrix3.fromList([
41 | 0.21,
42 | 0.75,
43 | 0.077,
44 | 0.21,
45 | 0.75,
46 | 0.077,
47 | 0.21,
48 | 0.75,
49 | 0.077,
50 | ]);
51 |
52 | Matrix3 tintMatrix = Matrix3.fromList([
53 | rf,
54 | 0,
55 | 0,
56 | 0,
57 | gf,
58 | 0,
59 | 0,
60 | 0,
61 | bf,
62 | ]);
63 |
64 | greyMatrix.multiply(tintMatrix);
65 | paint.colorFilter = ui.ColorFilter.matrix([
66 | greyMatrix[0],
67 | greyMatrix[1],
68 | greyMatrix[2],
69 | 0,
70 | 0,
71 | greyMatrix[3],
72 | greyMatrix[4],
73 | greyMatrix[5],
74 | 0,
75 | 0,
76 | greyMatrix[6],
77 | greyMatrix[7],
78 | greyMatrix[8],
79 | 0,
80 | 0,
81 | 0,
82 | 0,
83 | 0,
84 | 1,
85 | 0
86 | ]);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/lib/src/widgets/flare/hiring_particles.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 | import 'dart:ui';
3 |
4 | /// Data for each particle in the [HiringParticles] system.
5 | /// [HiringParticles] will create and destroy as many [HiringParticle]
6 | /// instances as it needs to show the effect.
7 | class HiringParticle {
8 | /// The position of the particle.
9 | Offset offset;
10 |
11 | /// The brightness of the particle.
12 | double opacity;
13 |
14 | /// The scale in 0-1 range, gets multiplied by the desired particle display
15 | /// size by [HiringParticles] at draw time.
16 | double scale;
17 |
18 | /// The phase the particle is in for its horizontal motion which is driven
19 | /// by an LFO (simple sin wave).
20 | double phase;
21 | }
22 |
23 | /// This is the class that manages the list of particles that are displayed
24 | /// for the hiring bust. It's reponsible for instancing, destroying, moving, and
25 | /// painting the particles.
26 | class HiringParticles {
27 | final Color color;
28 | final List _particles = [];
29 | static const int particleCount = 20;
30 | double particleSize = 10;
31 | final Random _random = Random();
32 | double elapsedSinceEmission = 0;
33 |
34 | HiringParticles({this.color});
35 | void advance(double elapsedSeconds, Size size) {
36 | if (_particles.isEmpty) {
37 | while (_particles.length < particleCount) {
38 | _particles.add(HiringParticle()
39 | ..offset = Offset(_random.nextDouble() * size.width,
40 | _random.nextDouble() * size.height)
41 | ..opacity = 0
42 | ..phase = _random.nextDouble()
43 | ..scale = _random.nextDouble());
44 | }
45 | }
46 |
47 | List deadParticles = [];
48 | for (final HiringParticle particle in _particles) {
49 | particle.phase += elapsedSeconds;
50 | particle.offset = Offset(particle.offset.dx,
51 | particle.offset.dy - size.height * elapsedSeconds * 0.5);
52 | if (particle.offset.dy < size.height / 2) {
53 | particle.opacity -= elapsedSeconds;
54 | } else {
55 | particle.opacity += elapsedSeconds;
56 | }
57 | particle.scale -= min(1, elapsedSeconds / 5);
58 | if (particle.opacity < 0 || particle.scale < 0) {
59 | deadParticles.add(particle);
60 | }
61 | }
62 |
63 | elapsedSinceEmission += elapsedSeconds;
64 | // no more than two per advance
65 | if (elapsedSinceEmission > 0.1 && _particles.length < particleCount) {
66 | elapsedSinceEmission = 0;
67 | _particles.add(HiringParticle()
68 | ..offset = Offset(_random.nextDouble() * size.width, size.height)
69 | ..opacity = 0
70 | ..phase = _random.nextDouble()
71 | ..scale = 0.5 + 0.5 * _random.nextDouble());
72 | }
73 |
74 | deadParticles.forEach(_particles.remove);
75 | }
76 |
77 | void paint(Canvas canvas, Offset offset) {
78 | double fullRadius = particleSize / 2;
79 |
80 | for (final HiringParticle particle in _particles) {
81 | Offset po = offset + particle.offset;
82 | double radius = fullRadius * particle.scale;
83 | double size = radius * 2;
84 | double ox = sin(particle.phase * 2) * particleSize * particle.scale;
85 | canvas.drawOval(
86 | Rect.fromLTWH(
87 | ox + po.dx - radius, po.dy + fullRadius - radius, size, size),
88 | Paint()
89 | ..style = PaintingStyle.fill
90 | ..color =
91 | color.withOpacity(particle.opacity.clamp(0, 1).toDouble()));
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/lib/src/widgets/flare/skill_icon.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/skill.dart';
2 | import 'package:dev_rpg/src/style.dart';
3 | import 'package:flare_flutter/flare_actor.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | class SkillIcon extends StatelessWidget {
7 | final double width;
8 | final double height;
9 | final Skill skill;
10 | final double opacity;
11 | final Color color;
12 | const SkillIcon(this.skill,
13 | {this.width = 19,
14 | this.height = 16,
15 | this.opacity = 1,
16 | this.color = Colors.white});
17 | @override
18 | Widget build(BuildContext context) {
19 | return SizedBox(
20 | width: width,
21 | height: height,
22 | child: FlareActor(skillFlareIcon[skill],
23 | color: color.withOpacity(opacity),
24 | alignment: Alignment.topCenter,
25 | shouldClip: false,
26 | fit: BoxFit.contain,
27 | animation: 'idle'),
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/src/widgets/flare/warmup_flare.dart:
--------------------------------------------------------------------------------
1 | import 'package:flare_flutter/flare_cache.dart';
2 | import 'package:flutter/services.dart';
3 |
4 | const _filesToWarmup = [
5 | "assets/flare/CodeIcon.flr",
6 | "assets/flare/Coin.flr",
7 | "assets/flare/CoordinationIcon.flr",
8 | "assets/flare/CowboyCoder.flr",
9 | "assets/flare/Designer.flr",
10 | "assets/flare/EngineeringIcon.flr",
11 | "assets/flare/Joy.flr",
12 | "assets/flare/NotificationIcon.flr",
13 | "assets/flare/ProgramManager.flr",
14 | "assets/flare/SelectArrow.flr",
15 | "assets/flare/Sourcerer.flr",
16 | "assets/flare/Tester.flr",
17 | "assets/flare/TheArchitect.flr",
18 | "assets/flare/TheHacker.flr",
19 | "assets/flare/TheJack.flr",
20 | "assets/flare/TheRefactorer.flr",
21 | "assets/flare/Users.flr",
22 | "assets/flare/UxIcon.flr",
23 | "assets/flare/UXResearcher.flr",
24 | ];
25 |
26 | /// Ensure all Flare assets used by this app are cached and ready to
27 | /// be displayed as quickly as possible.
28 | Future warmupFlare() async {
29 | for (final filename in _filesToWarmup) {
30 | await cachedActor(rootBundle, filename);
31 | await Future.delayed(const Duration(milliseconds: 16));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/src/widgets/flare/work_team.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/game_screen/character_style.dart';
2 | import 'package:dev_rpg/src/shared_state/game/character.dart';
3 | import 'package:dev_rpg/src/shared_state/game/skill.dart';
4 | import 'package:dev_rpg/src/widgets/flare/hiring_bust.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter/rendering.dart';
7 |
8 | /// Widget that shows the team that is actively working on a work item.
9 | class WorkTeam extends StatefulWidget {
10 | final List team;
11 | final List skillsNeeded;
12 | final bool isComplete;
13 | const WorkTeam({this.skillsNeeded, this.team, this.isComplete});
14 | @override
15 | _WorkTeamState createState() => _WorkTeamState();
16 | }
17 |
18 | /// A helper to store the animation state to display for the Character
19 | /// in the [WorkTeam] widget.
20 | class _WorkTeamMember {
21 | final CharacterStyle style;
22 | HiringBustState state;
23 |
24 | _WorkTeamMember(this.style, this.state);
25 | }
26 |
27 | class _WorkTeamState extends State {
28 | final List<_WorkTeamMember> _workTeam = [];
29 | @override
30 | void didUpdateWidget(WorkTeam oldWidget) {
31 | setState(updateCharacterStyles);
32 | super.didUpdateWidget(oldWidget);
33 | }
34 |
35 | @override
36 | void initState() {
37 | updateCharacterStyles();
38 | super.initState();
39 | }
40 |
41 | void updateCharacterStyles() {
42 | if (widget?.team == null) {
43 | if (widget.isComplete) {
44 | for (final _WorkTeamMember member in _workTeam) {
45 | member.state = HiringBustState.success;
46 | }
47 | }
48 | return;
49 | }
50 | _workTeam.clear();
51 | for (final Character character in widget.team) {
52 | CharacterStyle style = CharacterStyle.from(character);
53 | if (style == null) {
54 | continue;
55 | }
56 |
57 | _workTeam.add(_WorkTeamMember(
58 | style,
59 | widget.isComplete
60 | ? HiringBustState.success
61 | : character.contributes(widget.skillsNeeded)
62 | ? HiringBustState.working
63 | : HiringBustState.hired));
64 | }
65 | }
66 |
67 | @override
68 | Widget build(BuildContext context) {
69 | return Wrap(
70 | spacing: 10,
71 | runSpacing: 10,
72 | alignment: WrapAlignment.end,
73 | crossAxisAlignment: WrapCrossAlignment.end,
74 | children: _workTeam.map((member) {
75 | return Container(
76 | width: 71,
77 | height: 71,
78 | decoration: BoxDecoration(
79 | borderRadius: BorderRadius.circular(10),
80 | color: const Color.fromRGBO(69, 69, 82, 1),
81 | ),
82 | child: HiringBust(
83 | filename: member.style.flare,
84 | fit: BoxFit.contain,
85 | alignment: Alignment.bottomCenter,
86 | hiringState: member.state,
87 | isPlaying: true,
88 | ));
89 | }).toList());
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/lib/src/widgets/keyboard.dart:
--------------------------------------------------------------------------------
1 | /// Convenience class for giving names to key identifiers.
2 | class KeyCode {
3 | static const int escape = 0x100070029;
4 | static const int backspace = 0x10007002a;
5 | }
6 |
--------------------------------------------------------------------------------
/lib/src/widgets/prowess_progress.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | /// A progress bar with rounded corners and custom colors.
4 | class ProwessProgress extends StatefulWidget {
5 | const ProwessProgress(
6 | {@required this.progress,
7 | Key key,
8 | this.color,
9 | this.background = const Color.fromRGBO(0, 0, 0, 0.06),
10 | this.height = 7,
11 | this.borderRadius,
12 | this.innerPadding = const EdgeInsets.all(0)})
13 | : super(key: key);
14 |
15 | final double progress;
16 | final Color background;
17 | final Color color;
18 | final double height;
19 | final EdgeInsets innerPadding;
20 | final BorderRadius borderRadius;
21 |
22 | @override
23 | _ProwessProgressState createState() => _ProwessProgressState();
24 | }
25 |
26 | class _ProwessProgressState extends State
27 | with SingleTickerProviderStateMixin {
28 | AnimationController _animationController;
29 | Animation _progressTween;
30 |
31 | @override
32 | void initState() {
33 | _animationController = AnimationController(
34 | vsync: this, duration: const Duration(milliseconds: 200));
35 | _progressTween = Tween(begin: widget.progress, end: widget.progress)
36 | .animate(_animationController);
37 | super.initState();
38 | }
39 |
40 | @override
41 | void dispose() {
42 | _animationController.dispose();
43 | super.dispose();
44 | }
45 |
46 | @override
47 | void didUpdateWidget(ProwessProgress oldWidget) {
48 | setState(() {
49 | _progressTween =
50 | Tween(begin: _progressTween.value, end: widget.progress)
51 | .animate(_animationController);
52 | _animationController.reset();
53 | _animationController.forward();
54 | });
55 | super.didUpdateWidget(oldWidget);
56 | }
57 |
58 | @override
59 | Widget build(BuildContext context) {
60 | return Container(
61 | height: widget.height,
62 | child: Stack(
63 | children: [
64 | ConstrainedBox(
65 | constraints: const BoxConstraints(minWidth: double.infinity),
66 | child: Container(
67 | decoration: BoxDecoration(
68 | color: widget.background, borderRadius: widget.borderRadius),
69 | ),
70 | ),
71 | AnimatedBuilder(
72 | animation: _animationController,
73 | builder: (context, child) => FractionallySizedBox(
74 | widthFactor: _progressTween.value,
75 | child: Padding(
76 | padding: widget.innerPadding,
77 | child: Container(
78 | height: widget.height,
79 | decoration: BoxDecoration(
80 | color: widget.color,
81 | borderRadius: widget.borderRadius),
82 | ),
83 | ),
84 | ),
85 | )
86 | ],
87 | ),
88 | );
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/lib/src/widgets/screen_layout_builder.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 |
3 | /// Create a mobile or tablet layout depending on the screen size.
4 | class ResponsiveLayoutBuilder extends StatelessWidget {
5 | final Widget Function(BuildContext) buildMobile;
6 | final Widget Function(BuildContext) buildTablet;
7 |
8 | const ResponsiveLayoutBuilder({Key key, this.buildMobile, this.buildTablet})
9 | : super(key: key);
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return MediaQuery.of(context).size.shortestSide < 600
14 | ? buildMobile(context)
15 | : buildTablet(context);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/lib/src/widgets/skill_badge.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/skill.dart';
2 | import 'package:dev_rpg/src/style.dart';
3 | import 'package:dev_rpg/src/widgets/flare/skill_icon.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | Map skillDisplayName = {
7 | Skill.coding: 'Coding',
8 | Skill.engineering: 'Engineering',
9 | Skill.ux: 'UX',
10 | Skill.coordination: 'Coordination'
11 | };
12 |
13 | /// Displays a skill in a nicely readable format along with
14 | /// the value if present.
15 | class SkillBadge extends StatelessWidget {
16 | final Skill skill;
17 |
18 | const SkillBadge(this.skill);
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | return Padding(
23 | padding: const EdgeInsets.only(right: 10),
24 | child: Container(
25 | padding: const EdgeInsets.all(8),
26 | decoration: BoxDecoration(
27 | color: skillColor[skill],
28 | borderRadius: const BorderRadius.all(
29 | Radius.circular(5),
30 | ),
31 | ),
32 | child: Row(
33 | children: [
34 | SkillIcon(skill),
35 | const SizedBox(width: 5),
36 | Text(
37 | skillDisplayName[skill].toUpperCase(),
38 | style:
39 | buttonTextStyle.apply(fontSizeDelta: -4, color: Colors.white),
40 | ),
41 | ],
42 | ),
43 | ),
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/src/widgets/task_picker/task_picker_header.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/style.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | /// This is a simple text header for the tasks list.
5 | class TaskPickerHeader extends SliverPersistentHeaderDelegate {
6 | final String title;
7 | final bool showLine;
8 | const TaskPickerHeader(this.title, {this.showLine = true});
9 |
10 | @override
11 | Widget build(
12 | BuildContext context, double shrinkOffset, bool overlapsContent) {
13 | return Stack(overflow: Overflow.visible, children: [
14 | showLine
15 | ? Positioned.fromRect(
16 | rect: Rect.fromLTWH(26, 25, 2, 35 - shrinkOffset),
17 | child: SizedOverflowBox(
18 | size: const Size.fromHeight(0),
19 | child: Container(color: treeLineColor),
20 | ),
21 | )
22 | : Container(),
23 | Row(
24 | children: [
25 | const SizedBox(width: 15),
26 | Container(
27 | width: 25,
28 | height: 25,
29 | decoration: const BoxDecoration(
30 | boxShadow: [
31 | BoxShadow(
32 | color: Color.fromRGBO(84, 114, 244, 0.25),
33 | offset: Offset(0, 10),
34 | blurRadius: 10,
35 | spreadRadius: 0),
36 | ],
37 | borderRadius: BorderRadius.all(Radius.circular(12.5)),
38 | color: Color.fromRGBO(84, 114, 239, 1),
39 | ),
40 | child: const Icon(Icons.keyboard_arrow_down)),
41 | const SizedBox(width: 15),
42 | Text(
43 | title,
44 | style: buttonTextStyle,
45 | ),
46 | ],
47 | ),
48 | ]);
49 | }
50 |
51 | @override
52 | double get maxExtent => 60;
53 |
54 | @override
55 | double get minExtent => 60;
56 |
57 | @override
58 | bool shouldRebuild(TaskPickerHeader oldDelegate) {
59 | return title != oldDelegate.title;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/src/widgets/work_items/bug_header.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/bug.dart';
2 | import 'package:dev_rpg/src/shared_state/game/skill.dart';
3 | import 'package:dev_rpg/src/style.dart';
4 | import 'package:dev_rpg/src/widgets/work_items/skill_dot.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | /// Indicator for bug list items. Shows skills necessary to fix the bug.
8 | class BugHeader extends StatelessWidget {
9 | final Bug bug;
10 |
11 | const BugHeader(this.bug);
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Row(
16 | children: [
17 | const Icon(Icons.bug_report, color: bugColor),
18 | Expanded(child: Container()),
19 | for (Skill skill in bug.skillsNeeded) SkillDot(skill)
20 | ],
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/src/widgets/work_items/bug_list_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/bug.dart';
2 | import 'package:dev_rpg/src/shared_state/game/work_item.dart';
3 | import 'package:dev_rpg/src/style.dart';
4 | import 'package:dev_rpg/src/widgets/work_items/bug_header.dart';
5 | import 'package:dev_rpg/src/widgets/work_items/work_list_item.dart';
6 | import 'package:flutter/material.dart';
7 | import 'package:provider/provider.dart';
8 |
9 | /// Displays a [Bug] that can be tapped on to assign it to a team.
10 | class BugListItem extends StatelessWidget {
11 | @override
12 | Widget build(BuildContext context) {
13 | var bug = Provider.of(context) as Bug;
14 |
15 | return WorkListItem(
16 | workItem: bug,
17 | isExpanded: bug.isBeingWorkedOn,
18 | progressColor: bugColor,
19 | heading: !bug.isComplete
20 | ? BugHeader(bug)
21 | : const Icon(Icons.bug_report, color: disabledColor),
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/src/widgets/work_items/skill_dot.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/skill.dart';
2 | import 'package:dev_rpg/src/style.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | /// Displays a skill as a colored dot.
6 | class SkillDot extends StatelessWidget {
7 | final Skill skill;
8 |
9 | const SkillDot(this.skill);
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return Padding(
14 | padding: const EdgeInsets.only(left: 10),
15 | child: Container(
16 | width: 10,
17 | height: 10,
18 | decoration: BoxDecoration(
19 | color: skillColor[skill],
20 | borderRadius: const BorderRadius.all(
21 | Radius.circular(5),
22 | ),
23 | ),
24 | ),
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/src/widgets/work_items/task_header.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/rpg_layout_builder.dart';
2 | import 'package:dev_rpg/src/shared_state/game/skill.dart';
3 | import 'package:dev_rpg/src/shared_state/game/task_blueprint.dart';
4 | import 'package:dev_rpg/src/style.dart';
5 | import 'package:dev_rpg/src/widgets/work_items/skill_dot.dart';
6 | import 'package:flare_flutter/flare_actor.dart';
7 | import 'package:flutter/material.dart';
8 | import 'package:dev_rpg/src/shared_state/game/task.dart';
9 |
10 | /// A header for [Task] indicating the rewarded coin and skills
11 | /// necessary to work on the task.
12 | class TaskHeader extends StatelessWidget {
13 | final TaskBlueprint blueprint;
14 | const TaskHeader(this.blueprint);
15 | @override
16 | Widget build(BuildContext context) {
17 | return RpgLayoutBuilder(builder: (context, layout) {
18 | double scale = layout == RpgLayout.ultrawide ? 1.25 : 1.0;
19 | return Row(
20 | children: [
21 | Container(
22 | width: 20 * scale,
23 | height: 20 * scale,
24 | child: const FlareActor('assets/flare/Coin.flr'),
25 | ),
26 | const SizedBox(width: 4),
27 | Text(blueprint.coinReward.toString(),
28 | style: contentSmallStyle.apply(fontSizeFactor: scale)),
29 | Expanded(child: Container()),
30 | for (Skill skill in blueprint.skillsNeeded) SkillDot(skill)
31 | ],
32 | );
33 | });
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/src/widgets/work_items/task_list_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/code_chomper/code_chomper.dart';
2 | import 'package:dev_rpg/src/shared_state/game/task.dart';
3 | import 'package:dev_rpg/src/shared_state/game/task_blueprint.dart';
4 | import 'package:dev_rpg/src/shared_state/game/work_item.dart';
5 | import 'package:dev_rpg/src/shared_state/game/world.dart';
6 | import 'package:dev_rpg/src/style.dart';
7 | import 'package:dev_rpg/src/style_sphinx/sphinx_screen.dart';
8 | import 'package:dev_rpg/src/widgets/work_items/task_header.dart';
9 | import 'package:dev_rpg/src/widgets/work_items/work_list_item.dart';
10 | import 'package:flutter/material.dart';
11 | import 'package:provider/provider.dart';
12 |
13 | import '../game_over.dart';
14 |
15 | /// Displays a [Task] that can be tapped on to assign it to a team.
16 | /// The task can also be tapped on to award points once it is completed.
17 | class TaskListItem extends StatelessWidget {
18 | bool _handleTap(Task task, BuildContext context) {
19 | if (task.state == TaskState.completed) {
20 | // N.B. we ship the feature only once a minigame has
21 | // completed (if there is one).
22 | // This ensures that the BuildContext is still valid after
23 | // the game completes.
24 | switch (task.blueprint.miniGame) {
25 | case MiniGame.none:
26 | task.shipFeature();
27 | break;
28 | case MiniGame.chomp:
29 | // Time to face chompy, temporarily pause the game.
30 | var world = Provider.of(context);
31 | world.pause();
32 | Navigator.of(context)
33 | .pushNamed(CodeChomper.miniGameRouteName,
34 | arguments: task.blueprint.name == 'Alpha release'
35 | ? 'assets/docs/code_chomper_alpha.dart'
36 | : 'assets/docs/code_chomper_beta.dart')
37 | .then((_) {
38 | world.start();
39 | task.shipFeature();
40 | });
41 | break;
42 | case MiniGame.sphinx:
43 | {
44 | // Time to face the Sphinx, game is effectively over.
45 | var world = Provider.of(context);
46 | world.pause();
47 |
48 | Navigator.of(context)
49 | .pushNamed(SphinxScreen.miniGameRouteName)
50 | .then((_) {
51 | // Escaped the Sphinx.
52 | task.shipFeature();
53 | showDialog(
54 | barrierDismissible: false,
55 | context: context,
56 | builder: (BuildContext context) {
57 | return GameOver(world);
58 | });
59 | });
60 | break;
61 | }
62 | }
63 |
64 | return true;
65 | }
66 | return false;
67 | }
68 |
69 | @override
70 | Widget build(BuildContext context) {
71 | var task = Provider.of(context) as Task;
72 |
73 | bool isExpanded = task.isBeingWorkedOn || task.state == TaskState.completed;
74 | return WorkListItem(
75 | workItem: task,
76 | isExpanded: isExpanded,
77 | handleTap: () => _handleTap(task, context),
78 | progressColor: const Color.fromRGBO(0, 152, 255, 1),
79 | heading: task.state != TaskState.rewarded
80 | ? TaskHeader(task.blueprint)
81 | : const Icon(Icons.check_circle, color: disabledColor),
82 | );
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/lib/src/widgets/work_items/tasks_button_header.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/game_screen/add_task_button.dart';
2 | import 'package:dev_rpg/src/game_screen/bug_picker_modal.dart';
3 | import 'package:dev_rpg/src/game_screen/project_picker_modal.dart';
4 | import 'package:dev_rpg/src/game_screen/team_picker_modal.dart';
5 | import 'package:dev_rpg/src/shared_state/game/bug.dart';
6 | import 'package:dev_rpg/src/shared_state/game/character.dart';
7 | import 'package:dev_rpg/src/shared_state/game/task_blueprint.dart';
8 | import 'package:dev_rpg/src/shared_state/game/task_pool.dart';
9 | import 'package:dev_rpg/src/shared_state/game/work_item.dart';
10 | import 'package:flutter/material.dart';
11 | import 'package:provider/provider.dart';
12 |
13 | /// This is a header providing two buttons, one for selecting tasks and another
14 | /// for selecting active bugs to add to the working list.
15 | class TasksButtonHeader extends SliverPersistentHeaderDelegate {
16 | final TaskPool taskPool;
17 | final double scale;
18 | const TasksButtonHeader({this.taskPool, this.scale});
19 |
20 | Future _pickTeam(BuildContext context, WorkItem item) async {
21 | // immediately show the character picker for this newly
22 | // created task.
23 | var characters = await showModalBottomSheet>(
24 | context: context,
25 | builder: (context) => TeamPickerModal(item),
26 | );
27 | if (characters != null && !item.isComplete) {
28 | item.assignTeam(characters.toList());
29 | }
30 | }
31 |
32 | @override
33 | Widget build(
34 | BuildContext context, double shrinkOffset, bool overlapsContent) {
35 | return Padding(
36 | padding: const EdgeInsets.only(top: 15, left: 15, right: 15),
37 | child: Row(
38 | children: [
39 | Expanded(
40 | child: AddTaskButton(
41 | 'Tasks',
42 | scale: scale,
43 | key: const Key('add_task'),
44 | count: taskPool.availableTasks.length,
45 | icon: Icons.add,
46 | color: const Color(0xff5472ee),
47 | onPressed: () async {
48 | var project = await showModalBottomSheet(
49 | context: context,
50 | builder: (context) => ProjectPickerModal(),
51 | );
52 | if (project != null) {
53 | var task = Provider.of(context, listen: false)
54 | .startTask(project);
55 | await _pickTeam(context, task);
56 | }
57 | },
58 | ),
59 | ),
60 | const SizedBox(width: 10),
61 | Expanded(
62 | child: AddTaskButton(
63 | 'Bugs',
64 | scale: scale,
65 | count: taskPool.availableBugs.length,
66 | icon: Icons.bug_report,
67 | color: const Color(0xffeb2875),
68 | onPressed: () async {
69 | var bug = await showModalBottomSheet(
70 | context: context,
71 | builder: (context) => BugPickerModal(),
72 | );
73 | if (bug != null) {
74 | Provider.of(context, listen: false)
75 | .addWorkItem(bug);
76 | await _pickTeam(context, bug);
77 | }
78 | },
79 | ),
80 | ),
81 | ],
82 | ),
83 | );
84 | }
85 |
86 | @override
87 | double get maxExtent => 55 * scale;
88 |
89 | @override
90 | double get minExtent => 55 * scale;
91 |
92 | @override
93 | bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
94 | return true;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/lib/src/widgets/work_items/tasks_section_header.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/style.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | /// This is a simple text header for the tasks list.
5 | class TasksSectionHeader extends SliverPersistentHeaderDelegate {
6 | final String title;
7 | final double scale;
8 | const TasksSectionHeader(this.title, this.scale);
9 |
10 | @override
11 | Widget build(
12 | BuildContext context, double shrinkOffset, bool overlapsContent) {
13 | return Padding(
14 | padding: const EdgeInsets.all(15),
15 | child: Text(
16 | title,
17 | style: buttonTextStyle.apply(
18 | fontSizeDelta: -4,
19 | color: secondaryContentColor,
20 | fontSizeFactor: scale),
21 | ),
22 | );
23 | }
24 |
25 | @override
26 | double get maxExtent => 45;
27 |
28 | @override
29 | double get minExtent => 45;
30 |
31 | @override
32 | bool shouldRebuild(TasksSectionHeader oldDelegate) {
33 | return title != oldDelegate.title || scale != oldDelegate.scale;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: dev_rpg
2 | description: Become a tech lead, slay bugs, and don't get fired.
3 |
4 | # The following defines the version and build number for your application.
5 | # A version number is three numbers separated by dots, like 1.2.43
6 | # followed by an optional build number separated by a +.
7 | # Both the version and the builder number may be overridden in flutter
8 | # build by specifying --build-name and --build-number, respectively.
9 | # Read more about versioning at semver.org.
10 | version: 1.0.0+1
11 |
12 | environment:
13 | # For Google I/O we are being very specific about pinning to specific branch revision.
14 | # This is flutter commit hash b593f5167bce84fb3cad5c258477bf3abc1b14eb, tagged
15 | # as Flutter version 1.5.4.
16 | sdk: ">=2.3.0-dev.0.1 <3.0.0"
17 |
18 | dependencies:
19 | flutter:
20 | sdk: flutter
21 | intl: any
22 | provider: ^2.0.0
23 |
24 | # The following adds the Cupertino Icons font to your application.
25 | # Use with the CupertinoIcons class for iOS style icons.
26 | cupertino_icons: ^0.1.2
27 | flare_flutter: ^1.8.3
28 | auto_size_text: ^1.1.2
29 |
30 | dev_dependencies:
31 | flutter_test:
32 | sdk: flutter
33 | flutter_driver:
34 | sdk: flutter
35 | logging: ^0.11.3+2
36 | git: ^0.5.1+1
37 | t_stats: ^2.0.0
38 | test: ^1.6.1
39 |
40 |
41 | # For information on the generic Dart part of this file, see the
42 | # following page: https://www.dartlang.org/tools/pub/pubspec
43 |
44 | # The following section is specific to Flutter.
45 | flutter:
46 |
47 | # The following line ensures that the Material Icons font is
48 | # included with your application, so that you can use the icons in
49 | # the material Icons class.
50 | uses-material-design: true
51 |
52 | # To add assets to your application, add an assets section, like this:
53 | assets:
54 | - assets/style_sphinx/
55 | - assets/images/
56 | - assets/images/2.0x/
57 | - assets/images/3.0x/
58 | - assets/flare/
59 | - assets/docs/code_chomper_alpha.dart
60 | - assets/docs/code_chomper_beta.dart
61 |
62 | fonts:
63 | - family: SpaceMonoRegular
64 | fonts:
65 | - asset: assets/fonts/SpaceMono-Regular.ttf
66 | - family: SpaceMonoBold
67 | fonts:
68 | - asset: assets/fonts/SpaceMono-Bold.ttf
69 | - family: RobotoCondensedBold
70 | fonts:
71 | - asset: assets/fonts/RobotoCondensed-Bold.ttf
72 | - family: RobotoRegular
73 | fonts:
74 | - asset: assets/fonts/Roboto-Regular.ttf
75 | - family: MontserratMedium
76 | fonts:
77 | - asset: assets/fonts/Montserrat-Medium.otf
78 | - family: MontserratRegular
79 | fonts:
80 | - asset: assets/fonts/Montserrat-Regular.otf
81 | - family: MontserratBold
82 | fonts:
83 | - asset: assets/fonts/Montserrat-Bold.otf
84 |
--------------------------------------------------------------------------------
/test/character_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/world.dart';
2 | import 'package:test/test.dart';
3 |
4 | void main() {
5 | test('cannot hire without money', () {
6 | var world = World()..company.coin.number = 0;
7 | var newHire = world.characterPool.children.firstWhere((ch) => !ch.isHired);
8 | expect(newHire.hire(), false);
9 | expect(newHire.isHired, false);
10 | });
11 |
12 | test('can hire', () {
13 | var world = World()..company.coin.number = 1000;
14 | var newHire = world.characterPool.children.firstWhere((ch) => !ch.isHired);
15 | expect(newHire.hire(), true);
16 | expect(newHire.isHired, true);
17 | });
18 |
19 | test('cannot be upgraded when not hired', () {
20 | var world = World();
21 | var unhired = world.characterPool.children.firstWhere((ch) => !ch.isHired);
22 | var previous = unhired.level;
23 | expect(() => unhired.upgrade(), throwsA(isA()));
24 | expect(unhired.level, previous);
25 | });
26 |
27 | test('level goes up when upgraded', () {
28 | var world = World()..company.coin.number = 10000;
29 | // Find the first available character that we can hire.
30 | var character = world.characterPool.children
31 | .firstWhere((ch) => !ch.isHired && ch.canUpgradeOrHire);
32 | character.hire();
33 | var previous = character.level;
34 | expect(character.upgrade(), true);
35 | expect(character.level, greaterThan(previous));
36 | });
37 |
38 | test('skill goes up when upgraded', () {
39 | var world = World()..company.coin.number = 10000;
40 | // Find the first available character that we can hire.
41 | var character = world.characterPool.children
42 | .firstWhere((ch) => !ch.isHired && ch.canUpgradeOrHire);
43 | character.hire();
44 | var previous = character.prowess.values.fold(0, (a, b) => a + b);
45 | expect(character.upgrade(), true);
46 | expect(character.prowess.values.fold(0, (a, b) => a + b),
47 | greaterThan(previous));
48 | });
49 | }
50 |
--------------------------------------------------------------------------------
/test/style_sphinx/question_arguments_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/style_sphinx/question_arguments.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 |
4 | /// This test suite demonstrates how you can test business logic independent of
5 | /// the Widget tree.
6 | void main() {
7 | group('QuestionArguments', () {
8 | test('should have a next question if one exists', () {
9 | expect(
10 | QuestionArguments(questionRoutes: ['a', 'b']).hasNextQuestion,
11 | isTrue,
12 | );
13 |
14 | expect(
15 | QuestionArguments(
16 | questionRoutes: ['a', 'b', 'c'],
17 | currentIndex: 1,
18 | ).hasNextQuestion,
19 | isTrue,
20 | );
21 | });
22 |
23 | test('should not have a next question if one does not exist', () {
24 | expect(QuestionArguments().hasNextQuestion, isFalse);
25 | expect(
26 | QuestionArguments(
27 | questionRoutes: ['a'],
28 | currentIndex: 0,
29 | ).hasNextQuestion,
30 | isFalse,
31 | );
32 | });
33 |
34 | test('should produce next arguments based on the current arguments', () {
35 | final routes = ['a', 'b'];
36 |
37 | expect(
38 | QuestionArguments(questionRoutes: routes).nextQuestion(),
39 | QuestionArguments(questionRoutes: routes, currentIndex: 1),
40 | );
41 | });
42 |
43 | test('should throw if the next question does not exist', () {
44 | expect(
45 | QuestionArguments().nextQuestion,
46 | throwsStateError,
47 | );
48 | });
49 | });
50 | }
51 |
--------------------------------------------------------------------------------
/test/style_sphinx/sphinx_button_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/style_sphinx/sphinx_buttton.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 |
5 | /// This test suite demonstrates how you can test your own custom Widgets
6 | void main() {
7 | group('SphinxButton', () {
8 | testWidgets('displays the child', (tester) async {
9 | await tester.pumpWidget(
10 | MaterialApp(
11 | home: SphinxButton(
12 | onPressed: () {},
13 | child: const Text('A'),
14 | ),
15 | ),
16 | );
17 |
18 | expect(find.text('A'), findsOneWidget);
19 | });
20 |
21 | testWidgets('executes a callback on press', (tester) async {
22 | int count = 0;
23 |
24 | await tester.pumpWidget(
25 | MaterialApp(
26 | home: SphinxButton(
27 | onPressed: () => count++,
28 | child: const Text('A'),
29 | ),
30 | ),
31 | );
32 |
33 | await tester.tap(find.text('A'));
34 |
35 | expect(count, 1);
36 | });
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility that Flutter provides. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:dev_rpg/main.dart';
9 | import 'package:flutter/material.dart';
10 | import 'package:flutter_test/flutter_test.dart';
11 |
12 | void main() {
13 | testWidgets(
14 | 'Start the game',
15 | (WidgetTester tester) async {
16 | final startFinder = find.text('START');
17 |
18 | // Build our app and trigger a frame.
19 | await tester.pumpWidget(MyApp());
20 |
21 | // Find the start text
22 | expect(startFinder, findsOneWidget);
23 |
24 | // Start the game.
25 | expect(find.byType(FlatButton), findsNWidgets(2));
26 | await tester.tap(startFinder);
27 | await tester.pumpAndSettle();
28 |
29 | expect(find.text('Tasks'), findsOneWidget);
30 | },
31 | // This currently fails with a hanging Future. Not because of code
32 | // in this app. Skipping until this is resolved. TODO.
33 | skip: true,
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/test/world_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:dev_rpg/src/shared_state/game/world.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 | import 'package:provider/provider.dart';
5 |
6 | void main() {
7 | testWidgets('world can be started', (WidgetTester tester) async {
8 | var buttonKey = const ValueKey('start');
9 |
10 | await tester.pumpWidget(
11 | ChangeNotifierProvider(
12 | builder: (_) => World(),
13 | child: MaterialApp(
14 | home: Consumer(
15 | builder: (context, world, child) => FlatButton(
16 | key: buttonKey,
17 | onPressed: () => world.start(),
18 | child: Text(world.isRunning ? 'Stop' : 'Start'),
19 | ),
20 | ),
21 | ),
22 | ),
23 | );
24 |
25 | expect(find.text('Stop'), findsNothing);
26 | await tester.tap(find.byKey(buttonKey));
27 | await tester.pumpAndSettle();
28 | expect(find.text('Stop'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/test_driver/.gitignore:
--------------------------------------------------------------------------------
1 | *.pdf
2 | *.tsv
3 | download_results.sh
4 |
--------------------------------------------------------------------------------
/test_driver/durations.tsv:
--------------------------------------------------------------------------------
1 | id build rasterizer frameRequest sha description
2 |
--------------------------------------------------------------------------------
/test_driver/generate-graphs.R:
--------------------------------------------------------------------------------
1 | # Load data from disk.
2 | durations <- read.csv("test_driver/durations.tsv", sep = "\t")
3 |
4 | # Uncomment and modify the following line to focus on just a few selected runs.
5 | # durations <- durations[durations$description == "baseline" | durations$description == "statvalue-always-notifies" ,]
6 |
7 | # Get just the IDs as a vector.
8 | ids <- aggregate(durations$id, by=list(durations$id), FUN=head)[1]$Group.1
9 | # Get just the date and time from the id.
10 | labels <- substr(ids, 18, 33)
11 |
12 | # Setting plot margins.
13 | old.par <- par(no.readonly = TRUE)
14 |
15 | # Percentiles
16 | build90 <- aggregate(build ~ description, durations, function(x) quantile(x, c(0.5,0.90)))
17 | build95 <- aggregate(build ~ description, durations, function(x) quantile(x, c(0.5,0.95)))
18 | build99 <- aggregate(build ~ description, durations, function(x) quantile(x, c(0.5,0.99)))
19 | len <- length(build95$description)
20 | labels <- gsub("-", "\n", build95$description)
21 |
22 | # Jank chart
23 | pdf("test_driver/builds_bad.pdf", width = 16, height = 9)
24 | par(mar = c(15,6,4,2)+0.1, las = 2)
25 | x <- barplot(t(build95$build), xlim = c(0, max(build99$build)), density = len:1 * 2, angle = len:1 * 90 + 45, names.arg = labels, horiz = TRUE, beside = TRUE)
26 | #arrows(x, build90$build, x, build99$build, length=0.05, angle=90, code=3)
27 | title("Build times worst case")
28 | dev.off()
29 |
30 | # CPU time chart
31 | pdf("test_driver/cpu_time.pdf", width = 4, height = 8)
32 | stats <- read.csv("test_driver/perf_stats.tsv", sep = "\t")
33 | mean_with_moe <- function(x) {
34 | m <- mean(x)
35 | std_dev <- sd(x)/sqrt(length(x))
36 | crit_val <- qt(0.975, df = length(x) - 1)
37 | moe <- std_dev * crit_val
38 | df <- data.frame(m, m - moe, m + moe)
39 | colnames(df) <- c("mean", "lower", "upper")
40 | return(df)
41 | }
42 | m <- aggregate(expiredTasksDuration ~ description, stats, function(x) mean_with_moe(x)$mean) / 1000
43 | low <- aggregate(expiredTasksDuration ~ description, stats, function(x) mean_with_moe(x)$lower) / 1000
44 | high <- aggregate(expiredTasksDuration ~ description, stats, function(x) mean_with_moe(x)$upper) / 1000
45 | x <- barplot(m$expiredTasksDuration, ylim = c(0, max(high$expiredTasksDuration) * 1.1), density = 5, names.arg = m$description, ylab = "CPU time (ms)")
46 | arrows(x, low$expiredTasksDuration, x, high$expiredTasksDuration, length=0.10, angle=90, code=3)
47 | title("CPU time (lower is better)")
48 | dev.off()
49 |
50 | # Build durations
51 | pdf("test_driver/builds.pdf", width = 10, height = 10)
52 | par(mar = c(15,6,4,2)+0.1, las = 2)
53 | boxplot(build ~ description, durations, notch = TRUE, boxwex = 0.8)
54 | #axis(1, at = 1:length(ids), labels = labels, las = 2, cex.axis=0.5)
55 | title("Build times")
56 | dev.off()
57 |
58 | pdf("test_driver/rasterizations.pdf", width = 10, height = 10)
59 | par(mar = c(15,6,4,2)+0.1, las = 2)
60 | boxplot(rasterizer ~ description, durations, notch = TRUE, boxwex = 0.8, las = 2)
61 | #axis(1, at = 1:length(ids), labels = labels, las = 2, cex.axis=0.5)
62 | title("Rasterizer times")
63 | dev.off()
64 |
65 | pdf("test_driver/frame_requests.pdf", width = 10, height = 10)
66 | par(mar = c(15,6,4,2)+0.1, las = 2)
67 | boxplot(frameRequest ~ description, durations, notch = TRUE, boxwex = 0.8, las = 2)
68 | #axis(1, at = 1:length(ids), labels = labels, las = 2, cex.axis=0.5)
69 | title("Frame request pending times")
70 | dev.off()
71 |
72 | par(old.par)
73 |
--------------------------------------------------------------------------------
/test_driver/perf_stats.tsv:
--------------------------------------------------------------------------------
1 | name mean lowerBound upperBound marginOfError stdDeviation stdError min max n description 90thPercentile 99thPercentile worstFrame missedFrames length frames fps frameRequestDurationMean dartPercentage dartPhaseEvents dartPhaseDuration expiredTasksEvents expiredTasksDuration timestamp
2 |
--------------------------------------------------------------------------------
/test_driver/performance.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:dev_rpg/main.dart' as app;
4 | import 'package:dev_rpg/src/shared_state/game/bug.dart';
5 | import 'package:dev_rpg/src/shared_state/game/task_pool.dart';
6 | import 'package:dev_rpg/src/shared_state/game/world.dart';
7 | import 'package:flutter_driver/driver_extension.dart';
8 |
9 | void main() {
10 | enableFlutterDriverExtension();
11 | // Make the simulation faster, about one update every 2 frames.
12 | World.tickDuration = const Duration(milliseconds: 1000 ~/ 30);
13 | Bug.randomizer = Random(42);
14 | TaskPool.bugRandom = Random(24);
15 | app.main();
16 | }
17 |
--------------------------------------------------------------------------------
/tool/lock_android_scaling.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # This is adapted from Skia's recipe at:
4 | # https://github.com/google/skia/blob/e25b4472cdd9f09cd393c9c34651218507c9847b/infra/bots/recipe_modules/flavor/android.py
5 | #
6 | # Use that recipe to modify this for your test device. This shell script assumes Nexus 5.
7 | # Every device will have slightly different numbers and processes.
8 |
9 | set -e
10 |
11 | # Remove whitespace characters from string.
12 | # From https://stackoverflow.com/a/3352015/1416886
13 | trim() {
14 | local var="$*"
15 | # remove leading whitespace characters
16 | var="${var#"${var%%[![:space:]]*}"}"
17 | # remove trailing whitespace characters
18 | var="${var%"${var##*[![:space:]]}"}"
19 | echo -n "$var"
20 | }
21 |
22 | TARGET_DEVICE="Nexus 5"
23 | echo "This assumes a rooted ${TARGET_DEVICE} attached via USB."
24 | ACTUAL_DEVICE=`adb shell getprop ro.product.model`
25 | ACTUAL_DEVICE=`trim ${ACTUAL_DEVICE}`
26 | echo "${ACTUAL_DEVICE} detected."
27 |
28 | if [[ "${TARGET_DEVICE}" != "${ACTUAL_DEVICE}" ]]; then
29 | echo "Error: the attached device ${ACTUAL_DEVICE} is not ${TARGET_DEVICE}."
30 | echo "Aborting. If you have another device attached, unplug it."
31 | exit 1
32 | fi
33 |
34 | # Nexus 5 has only one CPU to scale, cpu0.
35 | # https://github.com/google/skia/blob/e25b4472cdd9f09cd393c9c34651218507c9847b/infra/bots/recipe_modules/flavor/android.py#L53
36 | CPU_NO="0"
37 |
38 | # Root path to CPU scaling virtual files.
39 | # https://github.com/google/skia/blob/e25b4472cdd9f09cd393c9c34651218507c9847b/infra/bots/recipe_modules/flavor/android.py#L283
40 | ROOT="/sys/devices/system/cpu/cpu${CPU_NO}/cpufreq"
41 |
42 | echo
43 | ACTUAL_CPU_FREQ=`adb shell "cat ${ROOT}/scaling_cur_freq"`
44 | echo "Current CPU frequency: ${ACTUAL_CPU_FREQ}"
45 | ACTUAL_GOV=`adb shell "cat /sys/devices/system/cpu/cpu${CPU_NO}/cpufreq/scaling_governor"`
46 | echo "Current governor: ${ACTUAL_GOV}"
47 | echo
48 |
49 | echo "This script will set frequencies of your device's CPU and GPU."
50 | echo; read -n 1 -s -r -p "Press any key to continue, or Ctrl-C to cancel"; echo
51 |
52 | # https://github.com/google/skia/blob/e25b4472cdd9f09cd393c9c34651218507c9847b/infra/bots/recipe_modules/flavor/android.py#L151
53 | GPU_FREQ="320000000"
54 | IDLE_TIMER="10000"
55 |
56 | adb root
57 |
58 | # --- CPU ---
59 |
60 | # Set userspace governor for cpu0
61 | # https://github.com/google/skia/blob/e25b4472cdd9f09cd393c9c34651218507c9847b/infra/bots/recipe_modules/flavor/android.py#L205
62 | GOV="userspace"
63 | echo "Setting CPU governor to: ${GOV}"
64 | adb shell "echo ${GOV} > /sys/devices/system/cpu/cpu${CPU_NO}/cpufreq/scaling_governor"
65 | ACTUAL_GOV=`adb shell "cat /sys/devices/system/cpu/cpu${CPU_NO}/cpufreq/scaling_governor"`
66 | echo " - actual: ${ACTUAL_GOV}"
67 |
68 |
69 | # If you want to check available frequencies:
70 | # cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies
71 | #
72 | # Since we are hardcoding this script for Nexus 5, we can just select the frequency we want.
73 | #
74 | # Nexus 5 max frequency: 2,265,600 Hz
75 | # 60% : 1,359,360 Hz
76 | # closest available: 1,267,200 Hz
77 | CPU_FREQ="1267200"
78 | MAX_FREQ="2265600"
79 |
80 | echo "Setting CPU frequency to: ${CPU_FREQ}"
81 | # https://github.com/google/skia/blob/e25b4472cdd9f09cd393c9c34651218507c9847b/infra/bots/recipe_modules/flavor/android.py#L326
82 | # If scaling_max_freq is lower than our attempted setting, it won't take.
83 | # We must set min first, because if we try to set max to be less than min
84 | # (which sometimes happens after certain devices reboot) it returns a
85 | # perplexing permissions error.
86 | adb shell "echo 0 > ${ROOT}/scaling_min_freq"
87 | adb shell "echo ${CPU_FREQ} > ${ROOT}/scaling_max_freq"
88 | adb shell "echo ${CPU_FREQ} > ${ROOT}/scaling_setspeed"
89 | sleep 5
90 | ACTUAL_CPU_FREQ=`adb shell "cat ${ROOT}/scaling_cur_freq"`
91 | echo " - actual: ${ACTUAL_CPU_FREQ}"
92 |
93 | # According to Skia, no need to disable CPUs on a Nexus 5.
94 | # https://github.com/google/skia/blob/e25b4472cdd9f09cd393c9c34651218507c9847b/infra/bots/recipe_modules/flavor/android.py#L145
95 | # But actually, Nexus 5 does scale other CPUs.
96 |
97 | CPU_ONLINE=0
98 | for n in 1 2 3
99 | do
100 | echo "Turning CPU ${n} to: ${CPU_ONLINE}"
101 | adb shell "echo ${CPU_ONLINE} > /sys/devices/system/cpu/cpu${n}/online"
102 | ACTUAL=`adb shell "cat /sys/devices/system/cpu/cpu${n}/online"`
103 | echo " - actual: ${ACTUAL}"
104 | done
105 |
106 |
107 | # --- GPU ---
108 |
109 | # https://github.com/google/skia/blob/e25b4472cdd9f09cd393c9c34651218507c9847b/infra/bots/recipe_modules/flavor/android.py#L153
110 | echo "Stopping thermald"
111 | adb shell "stop thermald"
112 |
113 | echo "Setting GPU frequency to: ${GPU_FREQ}"
114 | adb shell "echo ${GPU_FREQ} > /sys/class/kgsl/kgsl-3d0/gpuclk"
115 | ACTUAL_GPU_FREQ=`adb shell "cat /sys/class/kgsl/kgsl-3d0/gpuclk"`
116 | echo " - actual: ${ACTUAL_GPU_FREQ}"
117 |
118 | echo "Setting GPU idle timer to: ${IDLE_TIMER}"
119 | adb shell "echo ${IDLE_TIMER} > /sys/class/kgsl/kgsl-3d0/idle_timer"
120 | ACTUAL_TIMER=`adb shell "cat /sys/class/kgsl/kgsl-3d0/idle_timer"`
121 | echo " - actual: ${ACTUAL_TIMER}"
122 |
123 | echo "Setting force_bus_on, force_rail_on, force_clk_on"
124 | adb shell "echo 1 > /sys/class/kgsl/kgsl-3d0/force_bus_on"
125 | adb shell "echo 1 > /sys/class/kgsl/kgsl-3d0/force_rail_on"
126 | adb shell "echo 1 > /sys/class/kgsl/kgsl-3d0/force_clk_on"
127 |
--------------------------------------------------------------------------------