-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
33292f4
commit 8f7a877
Showing
449 changed files
with
125,831 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
*.iml | ||
.gradle | ||
/local.properties | ||
**/build | ||
.idea | ||
/captures | ||
app/out* | ||
/keystores/release.properties |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
# Candy Crush Solver | ||
|
||
This is an Android project that solves the game Candy Crush. | ||
This service scans the grid to find sweets, find the best move for the player and displays it on the screen, on top of Candy Crush. For more information, read here: https://applidium.com/news/candy_crush_solver/. | ||
|
||
## Setup | ||
|
||
This project uses the Android version of OpenCV libraries. | ||
If our setup doesn't work on your computer, we advise you to follow this [tutorial][]. | ||
|
||
In order to run the tests, you might want to add `-Djava.library.path=src/test` to your VM options if you want to run your tests outside of gradle. | ||
(For VM options in Android Studio, follow this path : Run -> Edit Configuration -> JUnit -> VM Options.) | ||
|
||
Finally, if you want to use analytics or crash reporting, everything is ready in the code : just add an HockeyApp key to `/keystore/release.properties` (variable `keyAlias`). To get your own key, go to [HockeyApp website][] and create a new project. | ||
|
||
[tutorial]: https://www.youtube.com/watch?v=OTw_GIQNbD8 | ||
[HockeyApp website]: https://hockeyapp.net | ||
|
||
## Project architecture | ||
|
||
### Launch | ||
|
||
`MainActivity` starts when the user launches the app, with its `SettingsFragment`. Several permissions are needed before beginning : overlay permission, accessibility access. Another button is here to start taking screenshots. When these three permissions are unlocked, our `HeadService starts`. | ||
|
||
Besides, `TutoActivity`, and other tuto fragments compose a little tutorial. | ||
The `PermissionChecker` helps to know when overlay permission is on. | ||
|
||
### Screenshot | ||
|
||
Most of the code lays in the same named class. Once started in `SettingsFragment`, a new capture is saved each time the `ImageReader.OnImageAvailableListener` detects a change in the screen. The image is saved in the internal storage of the phone. | ||
|
||
### Accessibility | ||
|
||
The `HeadService` checks if the user is on Candy Crush or not. It won't start any action before that. We also pay attention that screenshots are actually taken (by checking its modification time). Moreover, we stop our service when the user quits Candy Crush. | ||
|
||
```java | ||
if (event.getPackageName().toString().equals("com.king.candycrushsaga") && Math.abs(lastModDate.getTime() - d.getTime()) < TIME_LIMIT) { | ||
launchBusinessService(); | ||
} else { | ||
stopBusinessService(); | ||
} | ||
} | ||
``` | ||
|
||
Then the app launches the `BusinessService` at regular intervals. | ||
|
||
### Grid recognition | ||
|
||
The `BusinessService calls the `FeaturesExtractor` and let the engine work. Several methods can be used here : | ||
|
||
1. Call `private List<Sweet> extractSweetsForFeature(Mat img, int feature, int i, int orientation)` if you prefer a quick recognition. This function uses the pixel colors to find Candy Crush sweets. The integer `feature` represents its color code. But this methods won't be efficient for a black and white pattern. We chose this one in our solver, because the the algorithm performances are far better. | ||
(Note : this recognition is not as precise as the next one, so don't forget to center your final display) | ||
|
||
2. Call `public List<Sweet> extractSweetsForFeatureWithOpenCV(Mat img, Mat feature, int i)` if you prefer a really precise recognition. However, it is really slower (but depends of the size of your screenshot). | ||
|
||
The engine also contains a `FeaturePainter` that can help you see the results of this grid recognition. | ||
|
||
### Find a move | ||
|
||
Then the class `MoveFinder` works to find every move on the screen, and gives at the end the best one. The numerous booleans present aim to find special moves (will 4 sweets be aligned ? Or 5 ? Or can I make a sweet bomb with this move ?). If the algorithm seems a bit confusing, find explanation on our [blogpost][]. | ||
|
||
[blogpost]: https://www.youtube.com/watch?v=OTw_GIQNbD8 | ||
|
||
### Solution display | ||
|
||
Finally, the object HeadLayer represents the overlay on which we can draw. Here is the global setup : | ||
|
||
```java | ||
private WindowManager.LayoutParams params = new WindowManager.LayoutParams( | ||
WindowManager.LayoutParams.WRAP_CONTENT, | ||
WindowManager.LayoutParams.WRAP_CONTENT, | ||
WindowManager.LayoutParams.TYPE_PHONE, | ||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, | ||
PixelFormat.TRANSLUCENT); | ||
``` | ||
|
||
And then you can add whatever you wan on the layout : | ||
|
||
```java | ||
private void addToWindowManager() { | ||
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); | ||
windowManager.addView(frameLayout, params); | ||
|
||
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); | ||
|
||
layoutInflater.inflate(R.layout.head, frameLayout); | ||
image = (ImageView) frameLayout.findViewById(R.id.solution); | ||
} | ||
} | ||
``` | ||
|
||
And don't forget to add this view in your `head.xml` file. | ||
In our case, we chose to display the solution as a black transparent bitmap, with a white hole on the move we want to show. | ||
|
||
## Make your own game solver ! | ||
|
||
This project aims to be as general as possible, in order to be reused for other logic game resolution. We encourage you to create your own solver, based on this project. For example, feel free to develop a sudoku solver ! | ||
|
||
## Known bugs | ||
|
||
We are still working to fix the following bugs : | ||
|
||
1. Orange sweets are mixed up with yellow ones on some devices, which leads to displaying wrong moves. | ||
|
||
2. There is an offset on solution display on Samsung Galaxy S6. | ||
|
||
Don't hesitate to contact us if you have any clues or if you find other errors. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
*.iml | ||
.gradle | ||
/local.properties | ||
/.idea/workspace.xml | ||
/.idea/libraries | ||
.DS_Store | ||
/build | ||
/captures |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
apply plugin: 'android-sdk-manager' | ||
apply plugin: 'com.android.application' | ||
apply plugin: 'com.neenbedankt.android-apt' | ||
apply plugin: 'com.tmiyamon.config' | ||
apply plugin: 'me.leolin.gradle-android-aspectj' | ||
|
||
android { | ||
compileSdkVersion 24 | ||
buildToolsVersion "23.0.3" | ||
|
||
defaultConfig { | ||
applicationId "com.applidium.candycrushsolver" | ||
//the class "android.media.projection.MediaProjectionManager" doesn't exist on API lower than 21 and is compulsory for our screenshots | ||
minSdkVersion 21 | ||
targetSdkVersion 24 | ||
versionCode getVersionCode(1) | ||
versionName getVersionName("0.1") | ||
} | ||
|
||
productFlavors { | ||
stubs | ||
preprod | ||
prod | ||
} | ||
|
||
variantFilter { variant -> associateProdWithRelease(variant) } | ||
|
||
signingConfigs { | ||
release { | ||
Properties props = new Properties() | ||
props.load(new FileInputStream(file("../keystores/release.properties"))) | ||
storeFile file(props['storeFile']) | ||
storePassword props['storePassword'] | ||
keyAlias props['keyAlias'] | ||
keyPassword props['keyPassword'] | ||
} | ||
} | ||
|
||
buildTypes { | ||
distrib.initWith(buildTypes.debug) | ||
release.setSigningConfig(signingConfigs.release) | ||
|
||
buildTypes.each { | ||
Properties props = new Properties() | ||
props.load(new FileInputStream(file("../keystores/release.properties"))) | ||
it.buildConfigField 'String', 'APP_KEY', props['keyAlias'] as String | ||
} | ||
} | ||
|
||
decorateFlavors() | ||
|
||
|
||
sourceSets.main { | ||
jniLibs.srcDir '../libs/opencv/jniLibs' | ||
} | ||
|
||
testOptions { | ||
unitTests.all { | ||
jvmArgs '-Djava.library.path=src/test' | ||
} | ||
} | ||
|
||
packagingOptions { | ||
exclude 'META-INF/maven/commons-io/commons-io/pom.xml' | ||
exclude 'META-INF/maven/commons-io/commons-io/pom.properties' | ||
} | ||
|
||
// Open finder on the apks when doing release builds | ||
applicationVariants.all { variant -> | ||
variant.assemble.doLast { | ||
//If this is a 'release' build, reveal the compiled apk in finder/explorer | ||
if (variant.buildType.name.contains('release')) { | ||
|
||
def path = null; | ||
variant.outputs.each { output -> | ||
path = output.outputFile | ||
} | ||
|
||
exec { | ||
if (System.properties['os.name'].toLowerCase().contains('mac os x')) { | ||
['open', '-R', path].execute() | ||
} else if (System.properties['os.name'].toLowerCase().contains('windows')) { | ||
['explorer', '/select,', path].execute() | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
repositories { | ||
flatDir { | ||
dirs "libs" | ||
} | ||
} | ||
|
||
dependencies { | ||
compile fileTree(dir: 'libs', include: ['*.jar']) | ||
compile 'com.android.support:appcompat-v7:24.1.1' | ||
compile 'com.android.support:palette-v7:24.1.1' | ||
compile 'org.apache.commons:commons-io:1.3.2' | ||
compile project(':libs:opencv') | ||
compile 'com.viewpagerindicator:library:2.4.1@aar' | ||
compile 'net.hockeyapp.android:HockeySDK:4.1.0-beta.2' | ||
|
||
testCompile 'junit:junit:4.12' | ||
|
||
compile "com.google.dagger:dagger:2.5" | ||
apt "com.google.dagger:dagger-compiler:2.5" | ||
provided "javax.annotation:javax.annotation-api:1.2" | ||
} | ||
|
||
task copyResDirectoryToClasses(type: Copy){ | ||
from "${projectDir}/src/main/res/raw" | ||
into "${buildDir}/intermediates/classes/test/debug/assets" | ||
from "${projectDir}/src/test/res" | ||
into "${buildDir}/intermediates/classes/test/debug/assets" | ||
} | ||
|
||
afterEvaluate { | ||
preBuild.dependsOn(copyResDirectoryToClasses) | ||
} | ||
|
||
private decorateFlavors() { | ||
android.buildTypes.all { type -> | ||
|
||
if (!type.debuggable) { | ||
return | ||
} | ||
|
||
def version = android.defaultConfig.versionCode | ||
def sha1 = 'git rev-parse --short HEAD'.execute().text.trim() | ||
|
||
applicationIdSuffix = ".${type.name}" | ||
versionNameSuffix = "-${version}-${sha1}-${type.name}" | ||
} | ||
android.productFlavors.all { flavor -> | ||
def String name = flavor.name | ||
|
||
if ("prod".equals(name)) { | ||
return | ||
} | ||
|
||
applicationId = appendedApplicationId(name) | ||
versionName = appendedVersionName(name) | ||
} | ||
} | ||
|
||
private String appendedVersionName(String name) { | ||
android.defaultConfig.versionName + toAppend(name, "-") | ||
} | ||
|
||
private String appendedApplicationId(String name) { | ||
android.defaultConfig.applicationId + toAppend(name, ".") | ||
} | ||
|
||
private String toAppend(String name, String sep) { | ||
name.isEmpty() ? "" : sep + name | ||
} | ||
|
||
Closure associateProdWithRelease(variant) { | ||
def isRelease = variant.buildType.name.equals('release') | ||
def isProd = variant.getFlavors().get(0).name.equals('prod') | ||
|
||
if (isRelease) { | ||
variant.setIgnore(!isProd); | ||
} | ||
} | ||
|
||
def getVersionName(defaultVName) { | ||
if (project.hasProperty('vName') && project.vName) { | ||
return project.vName | ||
} | ||
return defaultVName | ||
} | ||
|
||
def getVersionCode(defaultVCode) { | ||
if (project.hasProperty('vCode') && project.vCode) { | ||
return project.vCode.toInteger() | ||
} | ||
return defaultVCode | ||
} | ||
|
||
|
||
task('getVariantInfo') << { | ||
println "Variants:\n" | ||
android.applicationVariants.all { v -> | ||
println v.name | ||
println v.buildType.name | ||
println v.buildType.applicationIdSuffix | ||
println v.buildType.versionNameSuffix | ||
println v.mergedFlavor.versionName | ||
println v.mergedFlavor.applicationId | ||
println "" | ||
} | ||
} | ||
|
||
def installAll = tasks.create('installAll') | ||
installAll.description = 'Install all applications.' | ||
android.applicationVariants.all { variant -> | ||
installAll.dependsOn(variant.install) | ||
// Ensure we end up in the same group as the other install tasks. | ||
installAll.group = variant.install.group | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
logging: | ||
enabled: true | ||
show_hashs: false | ||
performance: | ||
leak_canary: true | ||
dev_metrics: true | ||
stetho: true | ||
errors: | ||
rethrow: false | ||
crashes: | ||
enabled: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
logging: | ||
enabled: false | ||
show_hashs: false | ||
tracing: | ||
enabled: true | ||
performance: | ||
leak_canary: false | ||
dev_metrics: false | ||
stetho: false | ||
errors: | ||
rethrow: true | ||
crashes: | ||
enabled: true | ||
additional_data: false | ||
max_network: 10 | ||
max_trace: 10 | ||
max_component: 10 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
logging: | ||
enabled: false | ||
show_hashs: false | ||
performance: | ||
leak_canary: false | ||
dev_metrics: false | ||
stetho: false | ||
errors: | ||
rethrow: false | ||
crashes: | ||
enabled: true | ||
additional_data: true | ||
max_network: 100 | ||
max_trace: 100 | ||
max_component: 100 |
Oops, something went wrong.