Skip to content

Commit

Permalink
Add first version
Browse files Browse the repository at this point in the history
  • Loading branch information
Wiinterfell committed Aug 30, 2016
1 parent 33292f4 commit 8f7a877
Show file tree
Hide file tree
Showing 449 changed files with 125,831 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitignore
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
107 changes: 107 additions & 0 deletions README.md
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.
8 changes: 8 additions & 0 deletions app/.gitignore
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
204 changes: 204 additions & 0 deletions app/build.gradle
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
}
11 changes: 11 additions & 0 deletions app/config/debug.yml
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
17 changes: 17 additions & 0 deletions app/config/default.yml
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
15 changes: 15 additions & 0 deletions app/config/distrib.yml
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
Loading

0 comments on commit 8f7a877

Please sign in to comment.