diff --git a/INSTRUCTIONS.md b/INSTRUCTIONS.md new file mode 100644 index 0000000000000000000000000000000000000000..34ff60b3910f8cc74ff6967c2cfa36afda551700 --- /dev/null +++ b/INSTRUCTIONS.md @@ -0,0 +1,24 @@ +INSTALL INSTRUCTIONS + +In order to run 8-Bit Invaders, you must first have Android Studio installed [[install](https://developer.android.com/studio)] on your device. +After downloading, you are ready to create a new project in Android Studio [Do not open the repo for 8-Bit Invaders – you will need to download more software before opening the 8-Bit Invaders project code]. To create a new project, open android studio, create a new project. From templates choose Phone and Tablets -> No Activity -> next. Then pick a name (does not matter because it is temporary), choose java as a language, and then press finish. + +After you finish creating the project, please download an Android Emulator [[install](https://developer.android.com/studio/run/emulator)]. + +In order to run our project you will also need to download Android NDK and CMake. To do this please follow these instructions [[install](https://developer.android.com/studio/projects/install-ndk)]. + +After installing these components, you will need to clone the project repository to your local device. To do this, please use your computer’s terminal and use the following instructions: + +1. Make sure you have git installed on your computer [[install](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)] +2. Then create a directory [through your file explorer or terminal -- NOT IN ANDROID STUDIO] in which you would like to clone the project, and subsequently navigate to that directory in your terminal. +3. Then you will need to clone the App branch of this project. To do this use the command: + +git clone --branch App https://agile.bu.edu/gitlab/ec327_projects/group5project.git + +NOTE: The https link is the HTTPS link when you navigate to the project repo on Gitlab, and use the blue drop down on the right hand side of the screen [the link provided may be out of date upon your cloning]. + +After downloading all of the above, you are ready to open the 8-Bit Invaders code. To do this navigate to File->Open. Then find the directory in which you cloned the project, and open the project. You may need to wait a few minutes for the project to configure everything. You will then be ready to run the project. + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..749daa9b65be2cfc18b11f9b73f895832c444e53 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# Group5Project + +# Title +8-bit Invaders + +# Category: +Animated Game + +# Description: +We are building a Space Shooter-like game in an 8-bit style. + +# Roles +Lead: AnishSinha [14.29%], KrishShah [14.29%], NathanStrahs [14.29%], JamesKnee [14.29%], ThomasPoimenidis [14.29%], DerekXu [14.29%], JiaweiXiang [14.29%] + +Front: AnishSinha [14.29%], KrishShah [14.29%], NathanStrahs [14.29%], JamesKnee [14.29%], ThomasPoimenidis [14.29%], DerekXu [14.29%], JiaweiXiang [14.29%] + +Back: AnishSinha [14.29%], KrishShah [14.29%], NathanStrahs [14.29%], JamesKnee [14.29%], ThomasPoimenidis [14.29%], DerekXu [14.29%], JiaweiXiang [14.29%] + +Documenter: AnishSinha [14.29%], KrishShah [14.29%], NathanStrahs [14.29%], JamesKnee [14.29%], ThomasPoimenidis [14.29%], DerekXu [14.29%], JiaweiXiang [14.29%] + +Tester: AnishSinha [14.29%], KrishShah [14.29%], NathanStrahs [14.29%], JamesKnee [14.29%], ThomasPoimenidis [14.29%], DerekXu [14.29%], JiaweiXiang [14.29%] + +# Member Names: +Anish Sinha, Thomas Poimenidis, Jiawei Xiang(DavidXiang), Krish Shah, Nathan Strahs, Derek Xu, James Knee + +# Personalize Gitlab Task: +Completed diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..42afabfd2abebf31384ca7797186a27a4b7dbee8 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..9e5654e49edb3e57d35a17218d8a4ea1e325b6f4 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,64 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'com.example.a8_bitinvader' + compileSdk 33 + + defaultConfig { + applicationId "com.example.a8_bitinvader" + minSdk 21 + targetSdk 33 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + +// Added this line + externalNativeBuild{ + cmake{ + cppFlags "" + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +// Added this line + externalNativeBuild{ + cmake{ + path "src/main/cpp/CMakeLists.txt" + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation 'androidx.viewpager:viewpager:1.1.0-alpha01' + testImplementation 'junit:junit:4.13.2' + + + androidTestImplementation "androidx.test:runner:1.5.2" + androidTestImplementation "androidx.test:rules:1.5.0" + // Optional -- UI testing with Espresso + androidTestImplementation "androidx.test.espresso:espresso-core:3.5.1" + // To use the JUnit Extension APIs + androidTestImplementation "androidx.test.ext:junit:1.1.5" + // Kotlin extensions for androidx.test.ext.junit + androidTestImplementation "androidx.test.ext:junit-ktx:1.1.5" + +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..481bb434814107eb79d7a30b676d344b0df2f8ce --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedBackgroundTest.java b/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedBackgroundTest.java new file mode 100644 index 0000000000000000000000000000000000000000..773943635323f3871c5781c4811d532832e226ca --- /dev/null +++ b/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedBackgroundTest.java @@ -0,0 +1,41 @@ +package com.example.a8_bitinvader; + +import android.content.Context; +import android.content.res.Resources; +import android.media.AudioManager; +import android.view.View; + +import androidx.test.espresso.Espresso; +import androidx.test.espresso.ViewAssertion; +import androidx.test.espresso.action.ViewActions; +import androidx.test.espresso.assertion.PositionAssertions; +import androidx.test.espresso.assertion.ViewAssertions; +import androidx.test.espresso.matcher.ViewMatchers; +import androidx.test.platform.app.InstrumentationRegistry; +//import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; +import androidx.test.ext.junit.rules.ActivityScenarioRule; + +@RunWith(AndroidJUnit4.class) +public class InstrumentedBackgroundTest { + + // test creation of the background + @Test + public void makeBackground() { + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + Background back = new Background(500, 500, appContext.getResources()); + //Background back = null; + assertNotNull(back); + } + + + + + +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedGameOverTest.java b/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedGameOverTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c47f92c9e15b68d00b35b13d044f85be959f334e --- /dev/null +++ b/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedGameOverTest.java @@ -0,0 +1,54 @@ +package com.example.a8_bitinvader; + +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.media.AudioManager; +import android.view.View; + +import androidx.test.espresso.Espresso; +import androidx.test.espresso.ViewAssertion; +import androidx.test.espresso.action.ViewActions; +import androidx.test.espresso.assertion.PositionAssertions; +import androidx.test.espresso.assertion.ViewAssertions; +import androidx.test.espresso.matcher.ViewMatchers; +import androidx.test.platform.app.InstrumentationRegistry; +//import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.rule.ActivityTestRule; +import androidx.test.rule.ActivityTestRule$$ExternalSyntheticLambda0; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; +import androidx.test.ext.junit.rules.ActivityScenarioRule; + +@RunWith(AndroidJUnit4.class) +public class InstrumentedGameOverTest { + @Rule + public ActivityTestRule gameOver = new ActivityTestRule<>(GameOverActivity.class); + + /** + * Test the creation of the game over activity by first launching the game over activity and then + * checking whether the replay_button exists on the screen and is clickable. + * All of the UI checks utilize {@link Espresso} in order to automate interactions + */ + @Test + public void gameOver() { + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + Intent intent = new Intent(appContext, GameOverActivity.class); + gameOver.launchActivity(intent); + + Espresso.onView(ViewMatchers.withId(R.id.replay_button)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + Espresso.onView(ViewMatchers.withId(R.id.replay_button)) + .check(ViewAssertions.matches(ViewMatchers.isClickable())); + } + + + + + +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedGameTests.java b/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedGameTests.java new file mode 100644 index 0000000000000000000000000000000000000000..3ddb4ee4322ef6bebdb0a816eeb61f07dd87bd15 --- /dev/null +++ b/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedGameTests.java @@ -0,0 +1,71 @@ +package com.example.a8_bitinvader; + +import android.content.Context; +import android.content.res.Resources; +import android.media.AudioManager; +import android.view.View; + +import androidx.test.espresso.Espresso; +import androidx.test.espresso.ViewAssertion; +import androidx.test.espresso.action.ViewActions; +import androidx.test.espresso.assertion.PositionAssertions; +import androidx.test.espresso.assertion.ViewAssertions; +import androidx.test.espresso.matcher.ViewMatchers; +import androidx.test.platform.app.InstrumentationRegistry; +//import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; +import androidx.test.ext.junit.rules.ActivityScenarioRule; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class InstrumentedGameTests { + + + @Rule + public ActivityScenarioRule activityRule = + new ActivityScenarioRule<>(MainActivity.class); + + private void play() { + Espresso.onView(ViewMatchers.withId(R.id.play_button)).perform(ViewActions.click()); + } + + // test that the player can shoot + @Test + public void testShoot() { + play(); + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + Player play = new Player(5,5,0,0,100,100, appContext.getResources()); + play.shootNewBullet(); + assertEquals(play.bulletList.size(), 1); + + } + + /** + * Test if a bullet correctly collides with an enemy + * Creates a player and enemy that are in line vertically, the player then shoots, then checks if the bullet is collided + */ + @Test + public void testCollision() { + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + Player play = new Player(10, 10, 0, 0, 100, 100, appContext.getResources()); + Enemy enemy = new Enemy(10, 10, 0, 0, 100, 100, appContext.getResources()); + + play.shootNewBullet(); + assertTrue(play.bulletList.get(0).checkForCollision(enemy)); + } + + + + + +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedLevelOneTest.java b/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedLevelOneTest.java new file mode 100644 index 0000000000000000000000000000000000000000..790bee9ad6672259878081da040daf48f3d65109 --- /dev/null +++ b/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedLevelOneTest.java @@ -0,0 +1,39 @@ +package com.example.a8_bitinvader; + +import android.content.Context; +import android.content.res.Resources; +import android.media.AudioManager; +import android.view.MotionEvent; +import android.view.View; + +import androidx.test.espresso.Espresso; +import androidx.test.espresso.ViewAssertion; +import androidx.test.espresso.action.ViewActions; +import androidx.test.espresso.assertion.PositionAssertions; +import androidx.test.espresso.assertion.ViewAssertions; +import androidx.test.espresso.matcher.ViewMatchers; +import androidx.test.platform.app.InstrumentationRegistry; +//import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; +import androidx.test.ext.junit.rules.ActivityScenarioRule; + +@RunWith(AndroidJUnit4.class) +public class InstrumentedLevelOneTest { + + // test LevelOne Constructor that it creates a non-null LevelOne + @Test + public void LevelOne() + { + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + LevelOne levelOne = new LevelOne(appContext, 100, 100, 3); + assertNotNull(levelOne); + assertTrue(levelOne.enemyArrayList != null); + } + +} diff --git a/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedLevelTest.java b/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedLevelTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d9e2e19b09d816c431b1b893897f9e89265dd9ab --- /dev/null +++ b/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedLevelTest.java @@ -0,0 +1,47 @@ +package com.example.a8_bitinvader; + +import android.content.Context; +import android.content.res.Resources; +import android.media.AudioManager; +import android.view.MotionEvent; +import android.view.View; + +import androidx.test.espresso.Espresso; +import androidx.test.espresso.ViewAssertion; +import androidx.test.espresso.action.ViewActions; +import androidx.test.espresso.assertion.PositionAssertions; +import androidx.test.espresso.assertion.ViewAssertions; +import androidx.test.espresso.matcher.ViewMatchers; +import androidx.test.platform.app.InstrumentationRegistry; +//import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; +import androidx.test.ext.junit.rules.ActivityScenarioRule; + +@RunWith(AndroidJUnit4.class) +public class InstrumentedLevelTest { + + + // tests Level, tests construct, run(), resume(), and onTouchEvent() + @Test + public void LevelTest() { + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + Level level = new Level(appContext, 100, 100, 4); + assertNotNull(level); + assertNotNull(level.joystick); + assertNotNull(level.player); + + //tests resume() + level.resume(); + assertTrue(level.isRunning == true); + + boolean bool = level.onTouchEvent(MotionEvent.obtain(500, 0, 5, 5, 5, 5)); + assertTrue(level.joystick.getIsPressed() == false); + + } +} diff --git a/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedMenuTest.java b/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedMenuTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4fd9a9ad2f1cc94be46ea75767969f031d622a62 --- /dev/null +++ b/app/src/androidTest/java/com/example/a8_bitinvader/InstrumentedMenuTest.java @@ -0,0 +1,76 @@ +package com.example.a8_bitinvader; + +import android.content.Context; +import android.media.AudioManager; +import android.view.View; + +import androidx.test.espresso.Espresso; +import androidx.test.espresso.ViewAssertion; +import androidx.test.espresso.action.ViewActions; +import androidx.test.espresso.assertion.PositionAssertions; +import androidx.test.espresso.assertion.ViewAssertions; +import androidx.test.espresso.matcher.ViewMatchers; +import androidx.test.platform.app.InstrumentationRegistry; +//import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.runner.AndroidJUnit4; + +import org.hamcrest.Matcher; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; +import androidx.test.ext.junit.rules.ActivityScenarioRule; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class InstrumentedMenuTest { + + /** + * Wraps the class to be in the MainActivity Scenario + */ + @Rule + public ActivityScenarioRule activityRule = + new ActivityScenarioRule<>(MainActivity.class); + + + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.example.a8_bitinvader", appContext.getPackageName()); + } + + // test the play button goes away once in game + @Test + public void testPlay() { + Espresso.onView(ViewMatchers.withId(R.id.play_button)).perform(ViewActions.click()) + .check(ViewAssertions.doesNotExist()); + } + + @Test + public void testSound() { + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + AudioManager audio = (AudioManager) appContext.getSystemService(Context.AUDIO_SERVICE); + Espresso.onView(ViewMatchers.withId(R.id.mute_button)).perform(ViewActions.click()); + + int currentVolume = audio.getStreamVolume(AudioManager.STREAM_MUSIC); + assertEquals(currentVolume, 0); + } + +// @Rule +// public ActivityScenarioRule popUpActivity= new ActivityScenarioRule<>(PopUpWindow.class); + @Test + public void testLeaderBoard() { + Espresso.onView(ViewMatchers.withId(R.id.leaderboard_button)).perform(ViewActions.click()); + Espresso.onView(ViewMatchers.withId(R.id.popup_text)) + .check(ViewAssertions + .matches(ViewMatchers.isDisplayed())); + } + + +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..839e1dc07a3b62c769a773900716b98e74cf266d --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/cpp/Bullet.h b/app/src/main/cpp/Bullet.h new file mode 100644 index 0000000000000000000000000000000000000000..86d554d24e50592496f09e501f70e2b57bfa33c0 --- /dev/null +++ b/app/src/main/cpp/Bullet.h @@ -0,0 +1,55 @@ +// +// Created by Nathan Strahs on 4/24/23. +// + +#ifndef GROUP5PROJECT_BULLET_H +#define GROUP5PROJECT_BULLET_H + +#include "Sprite.h" + + +class Bullet: Sprite { +/** + * Creates an instance of a bullet with an initial position and velocity. + * @param xPos Starting x position + * @param yPos Starting y position + * @param xVel X velocity of the bullet + * @param yVel Y velocity of the bullet + * @param screenWidth The screen width + * @param screenHeight The screen height + * @param res {@link Resources} object + */ +public: +Bullet(int xPos, int yPos, int xVel, int yVel, int screenWidth, int screenHeight): Sprite(xPos, yPos, xVel, yVel, screenWidth, screenHeight) { + + spriteHeight = 64; + spriteWidth = 64; + } + +/** + * Checks if the bullet is still on screen + * @return True if the bullet is off the view screen + */ +bool isOffScreen() { + return (yPos + spriteHeight < 0); +} + +/** + * Checks for collision between this bullet and sprite + * @param sprite any sprite on the screen + * @return true if this bullet collides with sprite + */ +bool checkForCollision(Sprite sprite) { + int xPosMid = xPos + spriteWidth / 2; + if( (xPosMid >= sprite.xPos + && xPosMid <= sprite.xPos + sprite.spriteWidth + && yPos <= sprite.yPos + sprite.spriteHeight + && yPos + spriteHeight >= sprite.yPos) ) { + return true; + } + return false; + } +}; + + +#endif //GROUP5PROJECT_BULLET_H diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..4dd30d0992c0cd45a498675796fe1171a409dd3d --- /dev/null +++ b/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,30 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +# Sets the minimum version of CMake required to build the native library. + +cmake_minimum_required(VERSION 3.22.1) + +add_library( + native-lib + SHARED + native-lib.cpp +) + +add_library( + app-glue + STATIC + ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c ) + +find_library( + native-lib + log +) + + + +target_link_libraries( + native-lib + app-glue + ${log.lib} +) \ No newline at end of file diff --git a/app/src/main/cpp/Sprite.h b/app/src/main/cpp/Sprite.h new file mode 100644 index 0000000000000000000000000000000000000000..561abd56f6713eb7b9b6ae07db465e4902b0b3c7 --- /dev/null +++ b/app/src/main/cpp/Sprite.h @@ -0,0 +1,77 @@ +// +// Created by Nathan Strahs on 4/19/23. +// + +#ifndef GROUP5PROJECT_SPRITE_H +#define GROUP5PROJECT_SPRITE_H +class Sprite { +public: + /** X and Y position of the sprite */ + int xPos, yPos; + + /** X and Y velocity of the sprite */ + int xVel, yVel; + + /** Screen size */ + int screenWidth, screenHeight; + + /** + * @param xPos initial x position of Sprite + * @param yPos initial y position of Sprite + * @param xVel initial x velocity of Sprite + * @param yVel initial y velocity of Sprite + */ + Sprite(int xPos, int yPos, int xVel, int yVel, int screenWidth, int screenHeight) + { + this->xPos = xPos; + this->yPos = yPos; + this->xVel = xVel; + this->yVel = yVel; + + this->screenWidth = screenWidth; + this->screenHeight = screenHeight; + } + /** + * @return height of the sprite + */ + int getSpriteHeight() + { + return spriteHeight; + } +/** + * @return width of the sprite + */ +int getSpriteWidth() + { + return spriteWidth; + } + /** + * + * @param xx + * @param yy + * @return True if xx and yy are in the current sprite + */ + // IMPLEMENT THIS LATER TO CHECK FOR COLLISION WITH PASSING BULLET OBJECTS + bool checkForCollision(int xx, int yy) + { + //implement + if((xx < (xPos+spriteWidth) && xx > xPos) && (yy < (yPos+spriteHeight) && yy > yPos)) + { + return true; + } + return false; + } + /** + * Updates the sprite to move according to a given velocity + */ +void update() { + xPos += xVel; + yPos += yVel; + } + int spriteHeight = 128, spriteWidth = 128; + + +}; + + +#endif //GROUP5PROJECT_SPRITE_H diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp new file mode 100644 index 0000000000000000000000000000000000000000..abb6a22f047a113966772427860769b2c984a84e --- /dev/null +++ b/app/src/main/cpp/native-lib.cpp @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include "Sprite.h" +#include "Bullet.h" +static std::vector scores; +static int totalScore; + + +extern "C" +JNIEXPORT void JNICALL +/** + * keeps a local copy of the player scores (will not save after closing the file) + * @param env + * @param thiz + * @param value + * + */ +Java_com_example_a8_1bitinvader_Level_pushScore(JNIEnv *env, jobject thiz, jint value) { + if(value >= scores[4]){ + scores.pop_back(); + scores.push_back(value); + } + std::sort(scores.begin(),scores.end()); +} + +extern "C" +JNIEXPORT jint JNICALL +/** + * retrieves a copy of the local score at some specified index + * @param env + * @param thiz + * @param index + * @return the value of that particular run + */ +Java_com_example_a8_1bitinvader_Level_getScore(JNIEnv *env, jobject thiz, jint index) { + if (index >= scores.size() && index <= 0) + return 0; + else + return scores[index]; +} + +//when you pull, please delete your .gradle folder, and build folder(this one is in app folder), and rebuild the app, there is some issue with the NDK and gradle not able to include all the c++ libraries correctly. +/** + * uses the bullet class to calculate the collision + * @param bullet + * @param sprite + * @return a boolean for the collision + */ +bool checkForCollision(Bullet bullet, Sprite sprite) { + return bullet.checkForCollision(sprite); +} + + + +/** + * Calcuates whether the bullet (what this is used for) is offscreen + * @param yPos + * @param screenHeight + * @return a boolean representing whether the bullet is offscreen + */ +extern "C" +JNIEXPORT jboolean JNICALL +Java_com_example_a8_1bitinvader_MainActivity_isOffScreen(JNIEnv *env, jobject thiz, jint yPos, jint screenHeight) { + return (yPos + screenHeight < 0); +} + + +/** Creates a sprite and bullet from all of its attributes and uses them to use the bullet class' claculates for collisions + * + * @param env + * @param thiz + * @param b_x_pos + * @param b_y_pos + * @param b_x_vel + * @param b_y_vel + * @param b_screen_width + * @param b_screen_height + * @param s_x_pos + * @param s_y_pos + * @param s_x_vel + * @param s_y_vel + * @param s_screen_width + * @param s_screen_height + * @return boolean for the event of a collision + */ + extern "C" +JNIEXPORT jboolean JNICALL +Java_com_example_a8_1bitinvader_Level_checkForCollision(JNIEnv *env, jobject thiz, jint b_x_pos, + jint b_y_pos, jint b_x_vel, jint b_y_vel, + jint b_screen_width, jint b_screen_height, + jint s_x_pos, jint s_y_pos, jint s_x_vel, + jint s_y_vel, jint s_screen_width, + jint s_screen_height) { + Bullet bullet = Bullet(b_x_pos, b_y_pos, b_x_vel, b_y_vel, b_screen_width, b_screen_height); + Sprite sprite = Sprite(s_x_pos, s_y_pos, s_x_vel, s_y_vel, s_screen_width, s_screen_height); + return checkForCollision(bullet, sprite); +} +extern "C" +JNIEXPORT jint JNICALL +/** + * keeps track of the total number of player kills + * @param env + * @param thiz + * @param points + * @return number of player kills + */ +Java_com_example_a8_1bitinvader_Level_playerKills(JNIEnv *env, jobject thiz, jint points = 1) { + totalScore+=points; + return totalScore; +} diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..412686339e01537531374fe0e304a8d9e1f42170 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/example/a8_bitinvader/Background.java b/app/src/main/java/com/example/a8_bitinvader/Background.java new file mode 100644 index 0000000000000000000000000000000000000000..a43bd390f7bc2625f901c0df7b09ecf0ae17e284 --- /dev/null +++ b/app/src/main/java/com/example/a8_bitinvader/Background.java @@ -0,0 +1,23 @@ +package com.example.a8_bitinvader; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +public class Background { + + int xPos = 0, yPos = 0; + Bitmap background; + + /** + * Creates a background + * @param screenWidth Width of screen in pixels + * @param screenHeight Height of screen in pixels + * @param res Resource object + */ + Background(int screenWidth, int screenHeight, Resources res) { + background = BitmapFactory.decodeResource(res, R.drawable.background); + background = Bitmap.createScaledBitmap(background, screenWidth, screenHeight, false); + } + +} diff --git a/app/src/main/java/com/example/a8_bitinvader/Bullet.java b/app/src/main/java/com/example/a8_bitinvader/Bullet.java new file mode 100644 index 0000000000000000000000000000000000000000..c01a613404e24762bf66921e9f605105fe87ea90 --- /dev/null +++ b/app/src/main/java/com/example/a8_bitinvader/Bullet.java @@ -0,0 +1,67 @@ +package com.example.a8_bitinvader; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.media.MediaPlayer; + +public class Bullet extends Sprite { + /** + * Creates an instance of a bullet with an initial position and velocity. + * @param xPos Starting x position + * @param yPos Starting y position + * @param xVel X velocity of the bullet + * @param yVel Y velocity of the bullet + * @param screenWidth The screen width + * @param screenHeight The screen height + * @param res {@link Resources} object + */ + public Bullet(int xPos, int yPos, int xVel, int yVel, int screenWidth, int screenHeight, Resources res) { + super(xPos, yPos, xVel, yVel, screenWidth, screenHeight); + + spriteHeight = 64; + spriteWidth = 64; + + spriteImage = BitmapFactory.decodeResource(res, R.drawable.bullet); + spriteImage = Bitmap.createScaledBitmap(spriteImage, spriteWidth, spriteHeight, false); + } + + /** + * CONSTRUCTOR FOR TESTING Creates an instance of a bullet with an initial position and velocity. + * @param xPos Starting x position + * @param yPos Starting y position + * @param xVel X velocity of the bullet + * @param yVel Y velocity of the bullet + * @param screenWidth The screen width + * @param screenHeight The screen height + */ + public Bullet(int xPos, int yPos, int xVel, int yVel, int screenWidth, int screenHeight) { + super(xPos, yPos, xVel, yVel, screenWidth, screenHeight); + spriteHeight = 64; + spriteWidth = 64; + } + /** + * Checks if the bullet is still on screen + * @return True if the bullet is off the view screen + */ + public boolean isOffScreen() { + return (yPos + spriteHeight < 0); + } + + /** + * Checks for collision between this bullet and sprite + * @param sprite any sprite on the screen + * @return true if this bullet collides with sprite + */ + + public boolean checkForCollision(Sprite sprite) { + int xPosMid = xPos + spriteWidth / 2; + if( (xPosMid >= sprite.xPos + && xPosMid <= sprite.xPos + sprite.spriteWidth + && yPos <= sprite.yPos + sprite.spriteHeight + && yPos + spriteHeight >= sprite.yPos) ) { + return true; + } + return false; + } +} diff --git a/app/src/main/java/com/example/a8_bitinvader/Enemy.java b/app/src/main/java/com/example/a8_bitinvader/Enemy.java new file mode 100644 index 0000000000000000000000000000000000000000..3d4f15bfdc4555246e35a75302502f414b90366e --- /dev/null +++ b/app/src/main/java/com/example/a8_bitinvader/Enemy.java @@ -0,0 +1,57 @@ +package com.example.a8_bitinvader; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +import java.util.ArrayList; +import java.util.Random; + +public class Enemy extends Sprite{ + + /** Health of player */ + private int health = 1; + + /** Possible enemy sprites */ + private final int[] SpriteImages = + { + R.drawable.enemy1, + R.drawable.enemy5, + R.drawable.enemy3, + R.drawable.enemy4, + }; + + /** + * Initialize an enemy with the given coordinates and initial velocity + * @param xPos X point + * @param yPos Y point + * @param xVel X velocity + * @param yVel Y velocity + * @param res Resource object + */ + public Enemy(int xPos, int yPos, int xVel, int yVel, int screenWidth, int screenHeight, Resources res) { + super(xPos, yPos, xVel, yVel, screenWidth, screenHeight); + this.xPos = xPos; + this.yPos = yPos; + + Random rand = new Random(); + int randInt = rand.nextInt(SpriteImages.length); + spriteImage = BitmapFactory.decodeResource(res, SpriteImages[randInt]); + spriteImage = Bitmap.createScaledBitmap(spriteImage, spriteWidth, spriteHeight, false); + } + + /** + * Constructor for testing + * @param xPos + * @param yPos + * @param xVel + * @param yVel + * @param screenWidth + * @param screenHeight + */ + public Enemy(int xPos, int yPos, int xVel, int yVel, int screenWidth, int screenHeight) { + super(xPos, yPos, xVel, yVel, screenWidth, screenWidth); + this.xPos = xPos; + this.yPos = yPos; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/a8_bitinvader/GameActivity.java b/app/src/main/java/com/example/a8_bitinvader/GameActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..132fa25e428a460dcc4a4668c68362e83b7285f0 --- /dev/null +++ b/app/src/main/java/com/example/a8_bitinvader/GameActivity.java @@ -0,0 +1,50 @@ +package com.example.a8_bitinvader; + +import androidx.appcompat.app.AppCompatActivity; +import android.os.Bundle; +import android.content.Intent; +import android.util.Log; +import android.graphics.Point; // necessary to get ScreenWidth and ScreenHeight +import android.view.WindowManager; +public class GameActivity extends AppCompatActivity { + private Level currLevel; + + /** + * Create an instance of the activity based on the saved state. Then initialize the gameview and display the gameview onto the screen + * @param savedInstanceState The cached game state + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + /** puts app into fullscreen when GameActivity is running */ + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + + Point point = new Point(); + getWindowManager().getDefaultDisplay().getSize(point); + Intent startIntent = getIntent(); + + /** get number of player lives from MainActivity */ + int inputLives = startIntent.getIntExtra("inputLives", 3); + currLevel = new LevelOne(this, point.x, point.y, inputLives); + setContentView(currLevel); + } + + /** + * Pause the game + */ + @Override + protected void onPause() { + super.onPause(); + currLevel.pause(); + } + + /** + * Resume the game + */ + @Override + protected void onResume() { + super.onResume(); + currLevel.resume(); + } +} diff --git a/app/src/main/java/com/example/a8_bitinvader/GameOverActivity.java b/app/src/main/java/com/example/a8_bitinvader/GameOverActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..714045cd79f9ea756ceb6fb5e760b8c02dfe7ee6 --- /dev/null +++ b/app/src/main/java/com/example/a8_bitinvader/GameOverActivity.java @@ -0,0 +1,80 @@ +package com.example.a8_bitinvader; + +import androidx.appcompat.app.AppCompatActivity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.ImageView; +import android.widget.Button; +import android.content.Context; +import android.media.AudioManager; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.media.MediaPlayer; +import android.content.Context; +import android.widget. TextView; + + +public class GameOverActivity extends AppCompatActivity { + + Animation animation; + + private ScoreFileWriter scoreFW; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.game_finish); + Context context = getApplicationContext(); + scoreFW = new ScoreFileWriter(context); + + /** the if statement below hides the action bar + * https://www.geeksforgeeks.org/different-ways-to-hide-action-bar-in-android-with-examples/ + */ + if (getSupportActionBar() != null) { + getSupportActionBar().hide(); + } + + /** Goes back to GameActivity once replay button is pressed */ + findViewById(R.id.replay_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + startActivity(new Intent(GameOverActivity.this, GameActivity.class)); + } + }); + + /** Goes back to MainActivity Page when home button is pressed */ + findViewById(R.id.home_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + startActivity(new Intent(GameOverActivity.this, MainActivity.class)); + } + }); + + /** Display Player's Score (number of kills) from previous round */ + Intent intent = getIntent(); + TextView textView = (TextView) findViewById(R.id.player_score); + String score = intent.getStringExtra("score"); + textView.setText(score); + + + //code to add the highscore on final page + LinearLayout linearLayout = findViewById(R.id.linearLayoutScore); + View view; + + + String[] highScores = scoreFW.getTopScores(); + + for(int i = 0; i < 10; i ++){ + view = linearLayout.getChildAt(i); + + if(view instanceof TextView) { + TextView scoreText = (TextView) view; + scoreText.setText(highScores[i]); + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/a8_bitinvader/Joystick.java b/app/src/main/java/com/example/a8_bitinvader/Joystick.java new file mode 100644 index 0000000000000000000000000000000000000000..79bddb2f5aa64ca12673b3298e5aa139d3c645b9 --- /dev/null +++ b/app/src/main/java/com/example/a8_bitinvader/Joystick.java @@ -0,0 +1,180 @@ +package com.example.a8_bitinvader; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; + +public class Joystick { + private static final double MAX_SPEED = 3; + private int outerCircleCenterX; + private int outerCircleCenterY; + private int innerCircleCenterX; + private int innerCircleCenterY; + private int outerCircleRadii; + private int innerCircleRadii; + private Paint innerCirclePaint; + private Paint outerCirclePaint; + + private double joystickCenterToTouchDistance; + private boolean isPressed; + private double actuatorX; + private double actuatorY; + + public double XVelocity; + public double YVelocity; + + /** + * Constructs a Joystick instance + * @param CenterPositionX X position of the joystick + * @param CenterPositionY Y position of the joystick + * @param outerCircleRad Radius of outer circle of joystick + * @param innerCircleRad Radius of inner circle of joystick + */ + public Joystick(int CenterPositionX, int CenterPositionY, int outerCircleRad, int innerCircleRad){ + outerCircleCenterX = CenterPositionX; + outerCircleCenterY = CenterPositionY; + innerCircleCenterX = CenterPositionX; + innerCircleCenterY = CenterPositionY; + outerCircleRadii = outerCircleRad; + innerCircleRadii = innerCircleRad; + + outerCirclePaint= new Paint(); + outerCirclePaint.setColor(Color.CYAN); + outerCirclePaint.setStyle(Paint.Style.STROKE); + outerCirclePaint.setStrokeWidth(5); + + innerCirclePaint= new Paint(); + innerCirclePaint.setColor(Color.WHITE); + innerCirclePaint.setStyle(Paint.Style.FILL_AND_STROKE); + } + + /** + * Constructor of Joystick instance FOR TESTING + * @param CenterPositionX X position of the joystick + * @param CenterPositionY Y position of the joystick + * @param outerCircleRad Radius of outer circle of joystick + * @param innerCircleRad Radius of inner circle of joystick + */ + public Joystick(int CenterPositionX, int CenterPositionY, int outerCircleRad, int innerCircleRad, boolean test) { + outerCircleCenterX = CenterPositionX; + outerCircleCenterY = CenterPositionY; + innerCircleCenterX = CenterPositionX; + innerCircleCenterY = CenterPositionY; + outerCircleRadii = outerCircleRad; + innerCircleRadii = innerCircleRad; + } + + /** + * Draws the joystick onto the given canvas + * @param canvas The canvas object to draw on + */ + public void draw(Canvas canvas) { + //outer + canvas.drawCircle( + outerCircleCenterX, + outerCircleCenterY, + outerCircleRadii, + outerCirclePaint + ); + //inner + canvas.drawCircle( + innerCircleCenterX, + innerCircleCenterY, + innerCircleRadii, + innerCirclePaint + ); + } + + /** + * Updates the given joystick and updates the x and y velocities that are outputted from user input + * @param joystick Joystick instance to update + */ + public void update(Joystick joystick) { + XVelocity = joystick.getActuatorX() * MAX_SPEED; + YVelocity = joystick.getActuatorY() * MAX_SPEED; + updateInnerCirclePosition(); + } + + /** + * Updates the inner circle of the joystick + */ + private void updateInnerCirclePosition() { + innerCircleCenterX = (int) (outerCircleCenterX + actuatorX * outerCircleRadii); + innerCircleCenterY = (int) (outerCircleCenterY + actuatorY * outerCircleRadii); + } + + /** + * Checks whether the joystick has been touched by the user + * @param TouchPosX X position of the user's touch + * @param TouchPosY Y position of the user's touch + * @return True if the joystick has been pressed + */ + public boolean isPressed(double TouchPosX,double TouchPosY) { + joystickCenterToTouchDistance = Math.sqrt(Math.pow(outerCircleCenterX-TouchPosX,2) + + Math.pow(outerCircleCenterY-TouchPosY,2) + ); + return joystickCenterToTouchDistance < outerCircleRadii; + } + + /** + * Set the joystick to be pressed or not pressed + * @param isPressed Boolean for whether the joystick is pressed + */ + public void setIsPressed(boolean isPressed) { + this.isPressed = isPressed; + } + + /** + * + * @return True if the joystick is pressed + */ + public boolean getIsPressed() { + return isPressed; + } + + /** + * Sets the value of actuation depending on how far away the user touches from the center + * of the joystick. + * @param TouchPosX X position of the user touch + * @param TouchPosY Y position of the user touch + */ + public void setActuator(double TouchPosX,double TouchPosY) { + double deltaX = TouchPosX - outerCircleCenterX; + double deltaY = TouchPosY - outerCircleCenterY; + double deltaDistance = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY,2)); + + if(deltaDistance < outerCircleRadii) { + actuatorX = deltaX/outerCircleRadii; + actuatorY = deltaY/outerCircleRadii; + }else { + actuatorX = deltaX/deltaDistance; + actuatorY = deltaY/deltaDistance; + } + + } + + /** + * Resets the value of actuation to 0 + */ + public void resetActuator() { + actuatorX = 0.0; + actuatorY = 0.0; + } + + /** + * + * @return The x value of actuation + */ + public double getActuatorX() { + return actuatorX; + } + + /** + * + * @return The Y value of actuation + */ + public double getActuatorY() { + return actuatorY; + } + +} diff --git a/app/src/main/java/com/example/a8_bitinvader/Level.java b/app/src/main/java/com/example/a8_bitinvader/Level.java new file mode 100644 index 0000000000000000000000000000000000000000..accd4e11d772f0ab8ea01932b27d0319849c0a74 --- /dev/null +++ b/app/src/main/java/com/example/a8_bitinvader/Level.java @@ -0,0 +1,340 @@ +package com.example.a8_bitinvader; + +import android.content.Context; +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.graphics.Bitmap; +import android.media.MediaPlayer; +import android.util.Log; +import android.view.MotionEvent; +import android.view.SurfaceView; +import android.graphics.Canvas; +import android.graphics.Paint; // necessary when drawing Bitmaps with Canvas objects +import java.util.ArrayList; +import java.util.Random; + +public class Level extends SurfaceView implements Runnable { + private Thread thread; + private ScoreFileWriter scoreFW = new ScoreFileWriter(getContext()); + private Context context; + public boolean isRunning; + public int screenWidth, screenHeight; + private float screenRatioWidth, screenRatioHeight; + private Background background1, background2; + private Paint paint; + public int inputLives; + public int minEnemyVelocity = 6; + public int maxEnemyVelocity = 11; + + private MediaPlayer explosionSound; + + /** The {@link Joystick} */ + Joystick joystick; + + /** The {@link Player} */ + Player player; + + /** The {@link Enemy} */ + ArrayList enemyArrayList; + + int bulletCycleDelay = 0; + + /** + * Create a {@link Level} scaled based on the device size which includes the background, player, joystick, and enemies + * + * @param context Context of the device + * @param screenWidth Screen width in pixels + * @param screenHeight Screen height in pixels + */ + public Level(Context context, int screenWidth, int screenHeight, int inputLives) { + super(context); + this.context = context; + this.screenWidth = screenWidth; + this.screenHeight = screenHeight; + this.inputLives = inputLives; + + screenRatioWidth = 1080f / screenWidth; + screenRatioHeight = 1920f / screenHeight; + + background1 = new Background(screenWidth, screenHeight, getResources()); + background2 = new Background(screenWidth, screenHeight, getResources()); + explosionSound = MediaPlayer.create(context, R.raw.explosion); + + background2.yPos = -screenHeight; + + paint = new Paint(); + + /** Create Joystick and Player Objects */ + joystick = new Joystick(screenWidth / 2, screenHeight * 9 / 10, 130, 40); + player = new Player(screenWidth / 2, screenHeight * 7 / 10, 0, 0, screenWidth, screenHeight, getResources()); + player.playerLives = inputLives; + } + + /** + * Runs the app by continuously calling private methods {@link Void update()} and {@link Void draw()} based on whether the app {@link Boolean isRunning} + */ + @Override + public void run() { + while (isRunning) { + update(); + draw(); + + /** creates 30 millisecond delay in between draw() and update() */ + try { + Thread.sleep(30); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + /** + * Updates all animated parts necessary for GameActivity + */ + private void update() { + backgroundUpdate(); + + System.loadLibrary("native-lib"); + + /** + * Updates player position based on Joystick input + */ + player.update((int) joystick.XVelocity, (int) joystick.YVelocity); + joystick.update(joystick); + + for (Enemy enemy : enemyArrayList) { + enemy.update(); + + /** + * If player collides with enemy, game over + */ + int playerMiddleX = player.xPos + (player.spriteWidth / 2); + int playerMiddleY = player.yPos + (player.spriteHeight / 2); + if(playerMiddleX > enemy.xPos && + playerMiddleX < enemy.xPos + enemy.spriteWidth && + playerMiddleY > enemy.yPos && + playerMiddleY < enemy.yPos + enemy.spriteHeight){ + explosionSound.start(); + player.playerLives = 0; + } + + /** + * Once an enemy exits the screen, the player loses a life, + * and a new enemy is drawn from the top + * Once player has 0 lives, game over + */ + if (enemy.yPos > screenHeight) { + player.playerLives--; + + Random rand = new Random(); + Enemy replacement = new Enemy(rand.nextInt(3 * screenWidth / 4), 0, 0, minEnemyVelocity + rand.nextInt(maxEnemyVelocity), screenWidth, screenHeight, getResources()); + enemyArrayList.set(enemyArrayList.indexOf(enemy), replacement); + } + + if (player.playerLives <= 0) { + /** switches to the MainActivity page */ + Intent intent = new Intent(getContext(), GameOverActivity.class); + int calcScore = player.playerKills/inputLives; + /** Sends Player's Score to GameOverActivity and Records it to Scores file */ + scoreFW.writePtsToFile(calcScore, "name"); + String score = Integer.toString(calcScore); + intent.putExtra("score", score); + context.startActivity(intent); + } + } + boolean ndkCollision; + /** + * Checks for collision between player bullets and enemies + */ + for (int ii = 0; ii < player.bulletList.size(); ii++) { + if (player.bulletList.get(ii).isOffScreen()) { + player.bulletList.remove(player.bulletList.get(ii)); + ii -= 1; + } else { + player.bulletList.get(ii).update(); + for (int jj = 0; jj < enemyArrayList.size(); jj++) { + ndkCollision = false; + if (ii > -1) { + ndkCollision = checkForCollision(player.bulletList.get(ii).xPos, player.bulletList.get(ii).yPos, player.bulletList.get(ii).xVel, player.bulletList.get(ii).yVel, player.bulletList.get(ii).screenWidth, player.bulletList.get(ii).screenHeight, enemyArrayList.get(jj).xPos, enemyArrayList.get(jj).yPos, enemyArrayList.get(jj).xVel, enemyArrayList.get(jj).yVel, enemyArrayList.get(jj).screenWidth, enemyArrayList.get(jj).screenHeight); + } + /** old condition implemented with new NDK collision + * player.bulletList.get(ii).checkForCollision(enemyArrayList.get(jj)) + */ + if (ii > -1 && ndkCollision) { + player.bulletList.remove(player.bulletList.get(ii)); + + /** Replace dead enemy with new enemy */ + Random rand = new Random(); + Enemy replacement = new Enemy(rand.nextInt(3 * screenWidth / 4), 0, 0, minEnemyVelocity + rand.nextInt(maxEnemyVelocity), screenWidth, screenHeight, getResources()); + enemyArrayList.set(jj, replacement); + ii -= 1; + + /** Add to player's kill count */ + player.playerKills++; + playerKills(1); + explosionSound.start(); + } + } + } + } + } + + /** + * Updates the position of the backgrounds to give the illusion of an infinitely moving background. + * Cycles the background within a treadmill fashion, placing one of the + * two backgrounds behind each other while slowly moving both upward. + */ + void backgroundUpdate() { + background1.yPos += (int) (5 * screenRatioHeight); + background2.yPos += (int) (5 * screenRatioHeight); + + if (background1.yPos > screenHeight) { + background1.yPos = -screenHeight; + } + + if (background2.yPos > screenHeight) { + background2.yPos = -screenHeight; + } + } + + /** + * Initializes and draws the backgrounds at a given x position and y position + */ + private void draw() { + if (getHolder().getSurface().isValid()) { + Canvas canvas = getHolder().lockCanvas(); + + /** draw background */ + canvas.drawBitmap(background1.background, background1.xPos, background1.yPos, paint); + canvas.drawBitmap(background2.background, background2.xPos, background2.yPos, paint); + + /** draw player and enemies */ + canvas.drawBitmap(player.spriteImage, player.xPos, player.yPos, paint); + for (Enemy enemy : enemyArrayList) { + canvas.drawBitmap(enemy.spriteImage, enemy.xPos, enemy.yPos, paint); + } + + /** draw bullets */ + for (Bullet bullet : player.bulletList) { + canvas.drawBitmap(bullet.spriteImage, bullet.xPos, bullet.yPos, paint); + } + joystick.draw(canvas); + + + /** + * Only fires a new bullet every 20 loops of run() + * 20 * 30ms delay = 0.6 seconds + */ + bulletCycleDelay++; + if (bulletCycleDelay == 20) { + bulletCycleDelay = 0; + player.shootNewBullet(); + } + + /** draw player lives */ + Bitmap heartImage = BitmapFactory.decodeResource(getResources(), R.drawable.heart); + heartImage = Bitmap.createScaledBitmap(heartImage, 128, 128, false); + for (int i = 0; i < player.playerLives; i++) { + canvas.drawBitmap(heartImage, 50 + i * 128, 100, paint); + } + + getHolder().unlockCanvasAndPost(canvas); + } + } + + /** + * https://developer.android.com/reference/java/lang/Thread#start() + * initializes a thread and starts it, automatically calling the overridden run() method above. + */ + public void resume() { + isRunning = true; + thread = new Thread(this); + thread.start(); + } + + /** + * https://developer.android.com/reference/java/lang/Thread#join() + * waits for thread to die if game is paused + */ + public void pause() { + try { + isRunning = false; + thread.join(); + explosionSound.release(); + explosionSound = null; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + /** + * Keeps track of user touch events to trigger joystick movement and thus trigger player movement. + * This function calls the {@link Joystick} object class to change player movement as the user must touch + * the joystick specifically for movement. + * + * @param event The {@link MotionEvent} that the user triggers + * @return True if there is an action + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + if (joystick.isPressed((double) event.getX(), (double) event.getY())) { + joystick.setIsPressed(true); + } + return true; + case MotionEvent.ACTION_MOVE: + if (joystick.getIsPressed()) { + joystick.setActuator((double) event.getX(), (double) event.getY()); + } + return true; + case MotionEvent.ACTION_UP: + joystick.setIsPressed(false); + joystick.resetActuator(); + return true; + } + return super.onTouchEvent(event); + } + + /** + * Calculates whether a bullet has collided with an enemy sprite, and it passes in all the attributes + * of the sprite and bullet to allow the calculations to be done in c++ + * + * @param b_xPos + * @param b_yPos + * @param b_xVel + * @param b_yVel + * @param b_screenWidth + * @param b_screenHeight + * @param s_xPos + * @param s_yPos + * @param s_xVel + * @param s_yVel + * @param s_screenWidth + * @param s_screenHeight + * @return a boolean to tell whether the collision has happened + */ + public native boolean checkForCollision(int b_xPos, int b_yPos, int b_xVel, int b_yVel, int b_screenWidth, int b_screenHeight, int s_xPos, int s_yPos, int s_xVel, int s_yVel, int s_screenWidth, int s_screenHeight); + + /** + * Function to keep track of the players kills in c++ + * + * @param points represents the number of points the enemy counts as (1 per enemy) + * @return the total number of points + */ + public native int playerKills(int points); + + /** + * adds a score to a local copy of the best runs (does not save) + * @param value + */ + + private native void pushScore(int value); + + /** + * returns the index-th best score on teh local copy (does not save) + * @param index + * @return that best score + */ + private native int getScore(int index); +} \ No newline at end of file diff --git a/app/src/main/java/com/example/a8_bitinvader/LevelOne.java b/app/src/main/java/com/example/a8_bitinvader/LevelOne.java new file mode 100644 index 0000000000000000000000000000000000000000..0a7f757898c4b99a02ced89cb8ee1a2210e35ce2 --- /dev/null +++ b/app/src/main/java/com/example/a8_bitinvader/LevelOne.java @@ -0,0 +1,27 @@ +package com.example.a8_bitinvader; + +import android.content.Context; + +import java.util.ArrayList; +import java.util.Random; + +public class LevelOne extends Level { + + public int enemiesKilled; + + public LevelOne(Context context, int screenWidth, int screenHeight, int inputLives){ + super(context, screenWidth, screenHeight, inputLives); + initEnemies(); + enemiesKilled = 0; + } + + void initEnemies(){ + Random rand = new Random(); + + /** spawn enemies with random x position and random velocity */ + enemyArrayList = new ArrayList(4); + for(int ii = 0; ii < 4; ii++){ + enemyArrayList.add( new Enemy(rand.nextInt(7*screenWidth/8), -10, 0, minEnemyVelocity + rand.nextInt(maxEnemyVelocity), screenWidth, screenHeight, getResources())); + } + } +} diff --git a/app/src/main/java/com/example/a8_bitinvader/MainActivity.java b/app/src/main/java/com/example/a8_bitinvader/MainActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..d5de195f268448061d8e5615524be3e1601334f9 --- /dev/null +++ b/app/src/main/java/com/example/a8_bitinvader/MainActivity.java @@ -0,0 +1,315 @@ +package com.example.a8_bitinvader; + +import androidx.appcompat.app.AppCompatActivity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.ImageView; +import android.widget.Button; +import android.content.Context; +import android.media.AudioManager; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.media.MediaPlayer; +import android.content.Context; +import android.app.Dialog; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.text.SpannableStringBuilder; +import android.text.style.BulletSpan; +import android.text.Spanned; +public class MainActivity extends AppCompatActivity { + Animation animation; + int currentCharacter; + int inputLives = 3; + private MediaPlayer themeSound; + private CharacterPagerAdapter characterPagerAdapter; + private ViewPager characterViewPager; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + /** initializes information about NDK */ + System.loadLibrary("native-lib"); + + setContentView(R.layout.activity_main); + + /** Initialize the CharacterPagerAdapter */ + characterPagerAdapter = new CharacterPagerAdapter(); + characterViewPager = findViewById(R.id.characterViewPager); + characterViewPager.setAdapter(characterPagerAdapter); + + /** Playing Background Music + * Music Download Link: https://hypeddit.com/track/njf8op + */ + themeSound = MediaPlayer.create(this, R.raw.homeaudio); + themeSound.start(); + + /** the if statement below hides the action bar + * https://www.geeksforgeeks.org/different-ways-to-hide-action-bar-in-android-with-examples/ + */ + if (getSupportActionBar() != null) { + getSupportActionBar().hide(); + } + + /** Initializes 8-bit Invaders Logo */ + ImageView logo = (ImageView) findViewById(R.id.image_logo); + logo.setImageResource(R.drawable.invaderslogo); + + /** Creates new Intent and starts GameActivity if 'play' is pressed on MainActivity screen. */ + findViewById(R.id.play_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + /** pass number of player lives to gameActivity */ + Intent intentStart = new Intent(MainActivity.this, GameActivity.class); + intentStart.putExtra("inputLives", inputLives); + Player.setSelectedCharacterIndex(characterViewPager.getCurrentItem()); + startActivity(intentStart); + } + }); + + findViewById(R.id.help_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showHelpDialog(); + } + }); + + /** Creates new Intent and Mutes Device Audio if mute button is pressed on MainActivity screen + * Reference: https://www.youtube.com/watch?v=_Klq62-me8s&ab_channel=AppleCoders + * Reference: https://developer.android.com/reference/android/media/MediaPlayer */ + findViewById(R.id.mute_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + /** Declare an audio manager */ + AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); + /** + *A DJUST_MUTE => mutes the device + * FLAG_SHOW_UI => Show changes made to the volume bar + */ + audioManager.adjustVolume (AudioManager.ADJUST_MUTE, AudioManager.FLAG_SHOW_UI); + } + }); + + + /** Creates new Intent and stars LeaderboardView if the leaderboard button is pressed on the MainActivity Screen */ + findViewById(R.id.leaderboard_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + showLeaderboardDialog(); + } + }); + + findViewById(R.id.lives1).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + inputLives = 1; + } + }); + findViewById(R.id.lives3).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + inputLives = 3; + } + }); + findViewById(R.id.lives5).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + inputLives = 5; + } + }); + + ImageView leftButton = findViewById(R.id.left_button); + leftButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + int currentPosition = characterViewPager.getCurrentItem(); + int maxPosition = characterPagerAdapter.getCount() - 1; + if (currentPosition > 0) { + characterViewPager.setCurrentItem(currentPosition - 1); + } + else { + characterViewPager.setCurrentItem(maxPosition); + } + } + }); + + ImageView rightButton = findViewById(R.id.right_button); + rightButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + int currentPosition = characterViewPager.getCurrentItem(); + int maxPosition = characterPagerAdapter.getCount() - 1; + if (currentPosition < maxPosition) { + characterViewPager.setCurrentItem(currentPosition + 1); + } + else { + characterViewPager.setCurrentItem(0); + } + } + }); + + } + + private class CharacterPagerAdapter extends PagerAdapter { + private int[] characterImages = {R.drawable.player, R.drawable.player2, R.drawable.player3, R.drawable.player4}; + private int selectedCharacterIndex = 0; + + @NonNull + @Override + public Object instantiateItem(@NonNull ViewGroup container, int position) { + ImageView characterImageView = new ImageView(MainActivity.this); + characterImageView.setImageResource(characterImages[position]); + characterImageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + container.addView(characterImageView); + return characterImageView; + } + + @Override + public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { + container.removeView((ImageView) object); + } + + @Override + public int getCount() { + return characterImages.length; + } + + @Override + public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { + return view == object; + } + + /** Add a getter method to retrieve the selected character index */ + public int getSelectedCharacterIndex() { + return selectedCharacterIndex; + } + } + + /** + * Open up the popup window + */ + private void showLeaderboardDialog() { + + Context context = getApplicationContext(); + ScoreFileWriter scoreFw = new ScoreFileWriter(context); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Leaderboard"); + String[] scores = scoreFw.getTopScores(); + + StringBuilder messageBuilder = new StringBuilder(); + for (String score : scores) { + messageBuilder.append("\u2022 ").append(score).append("\n"); + } + + builder.setMessage(messageBuilder.toString()); + + /** Add an "OK" button to close the dialog */ + builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); /** Close the dialog */ + } + }); + + /** Create and show the dialog */ + AlertDialog leaderboardDialog = builder.create(); + leaderboardDialog.show(); + } + + /** + * Pop Up Window for Help Button + */ + private void showHelpDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Game Instructions"); + String[] instructions = { + "Choose your character using the left and right arrows on the Main Screen.", + "Select the number of lives you want. The lower the lives, the higher the score multiplier!", + "Use the joystick to move your character. Shoot down as many enemies as possible!", + "Do not crash your spaceship and do not let enemies leave the screen!", + "HAVE FUN!!" + }; + + StringBuilder messageBuilder = new StringBuilder(); + for (String instruction : instructions) { + messageBuilder.append("\u2022 ").append(instruction).append("\n"); + } + + builder.setMessage(messageBuilder.toString()); + + /** Add an "OK" button to close the dialog */ + builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); /** Close the dialog */ + } + }); + + /** Create and show the dialog */ + AlertDialog helpDialog = builder.create(); + helpDialog.show(); + } + + /** This function calls the android NDK to use c++ code */ + + public native boolean isOffScreen(int yPos, int screenHeight); + + @Override + protected void onStart() { + super.onStart(); + } + + /** + * Resume the game + */ + @Override + protected void onResume() { + super.onResume(); + } + + /** + * Pause the game + */ + @Override + protected void onPause() { + super.onPause(); + themeSound.release(); + themeSound = null; + } + + /** + * Stop the game + */ + @Override + protected void onStop() { + super.onStop(); + } + + /** + * Destroy the sprite + */ + @Override + protected void onDestroy() { + super.onDestroy(); + } + + /** + * Restart the game + */ + @Override + protected void onRestart() { + super.onRestart(); + } + + @Override + public void onBackPressed() { + /** Call super.onBackPressed() to perform default back button behavior */ + super.onBackPressed(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/a8_bitinvader/Player.java b/app/src/main/java/com/example/a8_bitinvader/Player.java new file mode 100644 index 0000000000000000000000000000000000000000..8d551c0f97f299a0d6b742fdaaf29b5aa0de1be0 --- /dev/null +++ b/app/src/main/java/com/example/a8_bitinvader/Player.java @@ -0,0 +1,100 @@ +package com.example.a8_bitinvader; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +import java.util.ArrayList; + +public class Player extends Sprite{ + + /** Lives of player */ + public int playerLives = 3; + + /** Player Kills */ + public int playerKills = 0; + + /** Speed */ + public final int SPEED_MULTIPLIER = 10; + + /** Bullet List */ + ArrayList bulletList; + + /** Resources */ + Resources res; + + private static int selectedCharacterIndex; + + /** + * Constructs a new player at given coordinates and + * @param xPos X position of player + * @param yPos Y position of player + * @param xVel X velocity of player + * @param yVel Y velocity of player + * @param res Resource object + */ + public Player(int xPos, int yPos, int xVel, int yVel, int screenWidth, int screenHeight, Resources res) { + super(xPos,yPos,xVel,yVel, screenWidth, screenHeight); + this.xPos = xPos; + this.yPos = yPos; + this.res = res; + + int[] characterImages = {R.drawable.player, R.drawable.player2, R.drawable.player3, R.drawable.player4}; + int characterImageResource = characterImages[selectedCharacterIndex]; + + spriteImage = BitmapFactory.decodeResource(res, characterImageResource); + spriteImage = Bitmap.createScaledBitmap(spriteImage, spriteWidth, spriteHeight, false); + + bulletList = new ArrayList(10); + } + + public static void setSelectedCharacterIndex(int index) { + selectedCharacterIndex = index; + } + + /** + * CONSTRUCTOR FOR TESTINGConstructs a new player at given coordinates and + * @param xPos X position of player + * @param yPos Y position of player + * @param xVel X velocity of player + * @param yVel Y velocity of player + */ + public Player(int xPos, int yPos, int xVel, int yVel, int screenWidth, int screenHeight, boolean test, Resources res, int selectedCharacterIndex) { + super(xPos,yPos,xVel,yVel, screenWidth, screenHeight); + this.xPos = xPos; + this.yPos = yPos; + + spriteImage = Bitmap.createScaledBitmap(spriteImage, spriteWidth, spriteHeight, false); + bulletList = new ArrayList(10); + } + + /** + * Update the position of the player based on joystick offset + * @param joyStickInputX X input from joystick + * @param joyStickInputY Y input from joystick + */ + public void update(int joyStickInputX, int joyStickInputY){ + xVel = joyStickInputX * SPEED_MULTIPLIER; + yVel = joyStickInputY * SPEED_MULTIPLIER; + + /** calls Sprite update(). Updates xPos and yPos */ + update(); + + /** checks if Player is offscreen. If so, repositions them within the screen */ + if(xPos < 0){ + xPos = 5; + } else if(xPos + spriteWidth > screenWidth){ + xPos = screenWidth - spriteWidth - 5; + } + + if(yPos < 0){ + yPos = 5; + } else if(yPos + spriteHeight > screenHeight){ + yPos = screenHeight - spriteHeight - 5; + } + } + + public void shootNewBullet(){ + bulletList.add(new Bullet(this.xPos + spriteWidth/2, this.yPos, 0, -25, screenWidth, screenWidth, res)); + } +} diff --git a/app/src/main/java/com/example/a8_bitinvader/PopUpWindow.java b/app/src/main/java/com/example/a8_bitinvader/PopUpWindow.java new file mode 100644 index 0000000000000000000000000000000000000000..546789dbeae3786cf1402773ec1f4a6d1b9bc79a --- /dev/null +++ b/app/src/main/java/com/example/a8_bitinvader/PopUpWindow.java @@ -0,0 +1,43 @@ +package com.example.a8_bitinvader; + +import androidx.appcompat.app.AppCompatActivity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.Gravity; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.Button; +import android.content.Context; +import android.media.AudioManager; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.util.DisplayMetrics; +public class PopUpWindow extends AppCompatActivity{ + + /** + * Handles popup windows and sets the game window to scale appropiately + * @param savedInstanceState {@link Bundle} object representing the saved state of the game + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.popup_window); + + DisplayMetrics dm = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics (dm); + + int width = dm.widthPixels; + int height = dm.heightPixels; + + getWindow().setLayout((int)(width*.7), (int)(height*.5)); + + WindowManager.LayoutParams params = getWindow().getAttributes(); + params.gravity = Gravity.CENTER; + params.x = 0; + params.y = -20; + + getWindow().setAttributes(params); + } + +} diff --git a/app/src/main/java/com/example/a8_bitinvader/ReverseInterpolator.java b/app/src/main/java/com/example/a8_bitinvader/ReverseInterpolator.java new file mode 100644 index 0000000000000000000000000000000000000000..490f10f0ff4bd8ecdb926a5d4549d125786dfc9a --- /dev/null +++ b/app/src/main/java/com/example/a8_bitinvader/ReverseInterpolator.java @@ -0,0 +1,10 @@ +package com.example.a8_bitinvader; + +import android.view.animation.Interpolator; + +public class ReverseInterpolator implements Interpolator { + @Override + public float getInterpolation(float paramFloat) { + return Math.abs(paramFloat -1f); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/a8_bitinvader/ScoreFileWriter.java b/app/src/main/java/com/example/a8_bitinvader/ScoreFileWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..963b72291815e3d9b0891f2eb740d244c99cba5f --- /dev/null +++ b/app/src/main/java/com/example/a8_bitinvader/ScoreFileWriter.java @@ -0,0 +1,130 @@ +package com.example.a8_bitinvader; + +import android.content.Context; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.Scanner; +import java.util.TreeMap; + +public class ScoreFileWriter implements Writer{ + + private File scoreLog; + + /** + * Creates ScoreFileWriter object and also creates a files named scoreLog.txt if possible + */ + public ScoreFileWriter(Context context) { + scoreLog = new File(context.getFilesDir(), "scoreLog.txt"); + try { + scoreLog.createNewFile(); + String directory = scoreLog.getParent(); + System.out.println("Directory: " + directory); + } catch(IOException e) { + e.printStackTrace(); + } + } + + /** + * function that takes current entriel of the file, if any and then adds on the new one + * Code also makes sure added entry in in order of list + * @param points point value from end game + * @param name name of the player that just set the score + */ + public void writePtsToFile(int points, String name) { + //Creates Treemap to relate relation between points and the player + TreeMap> sorted = new TreeMap>(Collections.reverseOrder()); + String[] fileLines = readFromFile(); + + //adds all the current enteries in the file to the treemap + for(String line : fileLines) { + int space = line.indexOf(" "); + int pts = Integer.valueOf(line.substring(0, space)); + String nm = line.substring(space + 1); + + if(!sorted.containsKey(pts)) { + sorted.put(pts, new ArrayList()); + } + sorted.get(pts).add(nm); + } + + //puts the new points value to be added and puts in the correct position + if(!sorted.containsKey(points)) { + sorted.put(points, new ArrayList()); + } + //adds the name to the points value just added + sorted.get(points).add(name); + + //Takes the treemap and turns it all to a string formatted in a way that we store it in the file + String sortedStr = ""; + for(Map.Entry> entry : sorted.entrySet()) { + for(String nameWPts : entry.getValue()) { + // sortedStr += entry.getKey() + " " + nameWPts + "\n"; + sortedStr += entry.getKey() + "\n"; + } + } + + writeToFile(sortedStr); + } + + /** + * Function to get the top 10 scores stored on the file + * @return Array of strings that each index has a value of Top 10 scores and player name that holds the score + */ + public String[] getTopScores() { + String[] out = new String[10]; + try { + Scanner scan = new Scanner(scoreLog); + + for(int i = 0; i < 10; i++) { + if(!scan.hasNextLine()) + break; + out[i] = scan.nextLine(); + } + + scan.close(); + } catch(IOException e) { + e.printStackTrace(); + } + return out; + } + + /** + * Writes code to the file + * @param str str of what is to be written on the file + */ + @Override + public void writeToFile(String str) { + try { + FileWriter fw = new FileWriter(scoreLog, false); + fw.write(str); + fw.close(); + } catch(IOException e) { + e.printStackTrace(); + } + } + + /** + * Reads code from the file + * @return Array of Strings that each entry is a player and their highscore + */ + @Override + public String[] readFromFile() { + ArrayList fileContents = new ArrayList(); + try { + Scanner scan = new Scanner(scoreLog); + while(scan.hasNextLine()) { + fileContents.add(scan.nextLine()); + } + scan.close(); + } catch(FileNotFoundException e) { + e.printStackTrace(); + } + return Arrays.copyOf(fileContents.toArray(), fileContents.size(), String[].class); + } +} diff --git a/app/src/main/java/com/example/a8_bitinvader/Sprite.java b/app/src/main/java/com/example/a8_bitinvader/Sprite.java new file mode 100644 index 0000000000000000000000000000000000000000..b1f14bbeb6989fab1c73a620e2763362c39bba1d --- /dev/null +++ b/app/src/main/java/com/example/a8_bitinvader/Sprite.java @@ -0,0 +1,46 @@ +package com.example.a8_bitinvader; + +import android.graphics.Bitmap; + +public class Sprite { + + /** X and Y position of the sprite */ + int xPos, yPos; + + /** X and Y velocity of the sprite */ + int xVel, yVel; + + /** Screen size */ + int screenWidth, screenHeight; + + /** Image of the sprite */ + Bitmap spriteImage; + + /** Scalable value for enemies and player */ + public int spriteHeight = 128, spriteWidth = 128; + + /** + * @param xPos initial x position of Sprite + * @param yPos initial y position of Sprite + * @param xVel initial x velocity of Sprite + * @param yVel initial y velocity of Sprite + */ + public Sprite(int xPos, int yPos, int xVel, int yVel, int screenWidth, int screenHeight) + { + this.xPos = xPos; + this.yPos = yPos; + this.xVel = xVel; + this.yVel = yVel; + + this.screenWidth = screenWidth; + this.screenHeight = screenHeight; + } + + /** + * Updates the sprite to move according to a given velocity + */ + public void update() { + xPos += xVel; + yPos += yVel; + } +} diff --git a/app/src/main/java/com/example/a8_bitinvader/Writer.java b/app/src/main/java/com/example/a8_bitinvader/Writer.java new file mode 100644 index 0000000000000000000000000000000000000000..9225e71cf9de835bf570424071277ed08715e9ef --- /dev/null +++ b/app/src/main/java/com/example/a8_bitinvader/Writer.java @@ -0,0 +1,7 @@ +package com.example.a8_bitinvader; + +/** Creates interface writer that can write and read files */ +public interface Writer { + void writeToFile(String str); + String[] readFromFile(); +} diff --git a/app/src/main/res/anim/blink_animation.xml b/app/src/main/res/anim/blink_animation.xml new file mode 100644 index 0000000000000000000000000000000000000000..884eb964b764db4d23fd52914d4d71cba56423ef --- /dev/null +++ b/app/src/main/res/anim/blink_animation.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/anim/fade_animation.xml b/app/src/main/res/anim/fade_animation.xml new file mode 100644 index 0000000000000000000000000000000000000000..5231ee876d8a1b5f545c1802e744d91f750d9b75 --- /dev/null +++ b/app/src/main/res/anim/fade_animation.xml @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/app/src/main/res/anim/move_animation.xml b/app/src/main/res/anim/move_animation.xml new file mode 100644 index 0000000000000000000000000000000000000000..31bb2edf255283e866302f012598a9f6f2cb2eea --- /dev/null +++ b/app/src/main/res/anim/move_animation.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/anim/slide_animation.xml b/app/src/main/res/anim/slide_animation.xml new file mode 100644 index 0000000000000000000000000000000000000000..8aa7d185fee29733588b8d282f592442620a3738 --- /dev/null +++ b/app/src/main/res/anim/slide_animation.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/anim/zoom_animation.xml b/app/src/main/res/anim/zoom_animation.xml new file mode 100644 index 0000000000000000000000000000000000000000..1c46ddabd1ce6994c962bd28dbfe17c5b66508d1 --- /dev/null +++ b/app/src/main/res/anim/zoom_animation.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000000000000000000000000000000000000..fde1368fc173af0045df850fc4b65501c7536765 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/leaderboard.png b/app/src/main/res/drawable-v24/leaderboard.png new file mode 100644 index 0000000000000000000000000000000000000000..b1829f5b71228b8b96c70c3969c11a75921cf045 Binary files /dev/null and b/app/src/main/res/drawable-v24/leaderboard.png differ diff --git a/app/src/main/res/drawable/axolotel.png b/app/src/main/res/drawable/axolotel.png new file mode 100644 index 0000000000000000000000000000000000000000..a95c70d87b88cfb741ea358759da6c7c2e16cb10 Binary files /dev/null and b/app/src/main/res/drawable/axolotel.png differ diff --git a/app/src/main/res/drawable/background.jpeg b/app/src/main/res/drawable/background.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..62b70aa2b3d9f59132c499b61d33c69292e0c4df Binary files /dev/null and b/app/src/main/res/drawable/background.jpeg differ diff --git a/app/src/main/res/drawable/bullet.png b/app/src/main/res/drawable/bullet.png new file mode 100644 index 0000000000000000000000000000000000000000..ee172dc46e184a1a99b566a2dbd156a9b8872495 Binary files /dev/null and b/app/src/main/res/drawable/bullet.png differ diff --git a/app/src/main/res/drawable/enemy1.png b/app/src/main/res/drawable/enemy1.png new file mode 100644 index 0000000000000000000000000000000000000000..145dbb3fc9fa179929f4be206c8213f37b006431 Binary files /dev/null and b/app/src/main/res/drawable/enemy1.png differ diff --git a/app/src/main/res/drawable/enemy3.png b/app/src/main/res/drawable/enemy3.png new file mode 100644 index 0000000000000000000000000000000000000000..a2c0ad367e6b37c51fca9146327290b8ef6bb8a2 Binary files /dev/null and b/app/src/main/res/drawable/enemy3.png differ diff --git a/app/src/main/res/drawable/enemy3_damage.png b/app/src/main/res/drawable/enemy3_damage.png new file mode 100644 index 0000000000000000000000000000000000000000..e57525e2c7043c5f44642ac436f7e3f6b4e6c5c6 Binary files /dev/null and b/app/src/main/res/drawable/enemy3_damage.png differ diff --git a/app/src/main/res/drawable/enemy4.png b/app/src/main/res/drawable/enemy4.png new file mode 100644 index 0000000000000000000000000000000000000000..b38b06cf3689b638c893b9b497f94f3d4bddf912 Binary files /dev/null and b/app/src/main/res/drawable/enemy4.png differ diff --git a/app/src/main/res/drawable/enemy4_damage.png b/app/src/main/res/drawable/enemy4_damage.png new file mode 100644 index 0000000000000000000000000000000000000000..5cbfab33f4d635a81d3a7112423016f797d183ec Binary files /dev/null and b/app/src/main/res/drawable/enemy4_damage.png differ diff --git a/app/src/main/res/drawable/enemy5.png b/app/src/main/res/drawable/enemy5.png new file mode 100644 index 0000000000000000000000000000000000000000..7725d74fb7d3ee7a2b300d7fd4515287668910d3 Binary files /dev/null and b/app/src/main/res/drawable/enemy5.png differ diff --git a/app/src/main/res/drawable/enemy5_damage.png b/app/src/main/res/drawable/enemy5_damage.png new file mode 100644 index 0000000000000000000000000000000000000000..52d541197f6ad5bc84e9458b88dc639de3ff3d51 Binary files /dev/null and b/app/src/main/res/drawable/enemy5_damage.png differ diff --git a/app/src/main/res/drawable/enemy6.png b/app/src/main/res/drawable/enemy6.png new file mode 100644 index 0000000000000000000000000000000000000000..e503fb4ad5cd45d698480f92ac1865dce574e7a2 Binary files /dev/null and b/app/src/main/res/drawable/enemy6.png differ diff --git a/app/src/main/res/drawable/enemy6_damage.png b/app/src/main/res/drawable/enemy6_damage.png new file mode 100644 index 0000000000000000000000000000000000000000..9c7b591be8028090a8bde25b0caf0102796a2cdb Binary files /dev/null and b/app/src/main/res/drawable/enemy6_damage.png differ diff --git a/app/src/main/res/drawable/explosion_frame_1.png b/app/src/main/res/drawable/explosion_frame_1.png new file mode 100644 index 0000000000000000000000000000000000000000..dcf2a44d4ea274d65232c988e2033606fd9ba44b Binary files /dev/null and b/app/src/main/res/drawable/explosion_frame_1.png differ diff --git a/app/src/main/res/drawable/explosion_frame_2.png b/app/src/main/res/drawable/explosion_frame_2.png new file mode 100644 index 0000000000000000000000000000000000000000..dcf2a44d4ea274d65232c988e2033606fd9ba44b Binary files /dev/null and b/app/src/main/res/drawable/explosion_frame_2.png differ diff --git a/app/src/main/res/drawable/explosion_frame_3.png b/app/src/main/res/drawable/explosion_frame_3.png new file mode 100644 index 0000000000000000000000000000000000000000..1d060500bf34caf08afaa0d520b6688abbcea4b5 Binary files /dev/null and b/app/src/main/res/drawable/explosion_frame_3.png differ diff --git a/app/src/main/res/drawable/explosion_frame_4.png b/app/src/main/res/drawable/explosion_frame_4.png new file mode 100644 index 0000000000000000000000000000000000000000..2135e9ef842072dc6b0635d8b5cad32067360e54 Binary files /dev/null and b/app/src/main/res/drawable/explosion_frame_4.png differ diff --git a/app/src/main/res/drawable/explosion_frame_5.png b/app/src/main/res/drawable/explosion_frame_5.png new file mode 100644 index 0000000000000000000000000000000000000000..28aabe3aaaf0bb52f05d92b036badee192c9e127 Binary files /dev/null and b/app/src/main/res/drawable/explosion_frame_5.png differ diff --git a/app/src/main/res/drawable/explosion_frame_6.png b/app/src/main/res/drawable/explosion_frame_6.png new file mode 100644 index 0000000000000000000000000000000000000000..28aabe3aaaf0bb52f05d92b036badee192c9e127 Binary files /dev/null and b/app/src/main/res/drawable/explosion_frame_6.png differ diff --git a/app/src/main/res/drawable/finalscore.png b/app/src/main/res/drawable/finalscore.png new file mode 100644 index 0000000000000000000000000000000000000000..cf33d227b4dbf5f4d4d335e62b6e30010d5f6969 Binary files /dev/null and b/app/src/main/res/drawable/finalscore.png differ diff --git a/app/src/main/res/drawable/green.png b/app/src/main/res/drawable/green.png new file mode 100644 index 0000000000000000000000000000000000000000..414c0ac8bb3e3f9702d4454bc942d0fcf6c0d296 Binary files /dev/null and b/app/src/main/res/drawable/green.png differ diff --git a/app/src/main/res/drawable/green32.png b/app/src/main/res/drawable/green32.png new file mode 100644 index 0000000000000000000000000000000000000000..0479416ef11fff1fd705841e12cf6ca428e4bc60 Binary files /dev/null and b/app/src/main/res/drawable/green32.png differ diff --git a/app/src/main/res/drawable/green64.png b/app/src/main/res/drawable/green64.png new file mode 100644 index 0000000000000000000000000000000000000000..d261c8e9adc67b238ba8370776e3019f0f125951 Binary files /dev/null and b/app/src/main/res/drawable/green64.png differ diff --git a/app/src/main/res/drawable/heart.png b/app/src/main/res/drawable/heart.png new file mode 100644 index 0000000000000000000000000000000000000000..66c4fc7cdfe5533560672949457b47e1f95bd943 Binary files /dev/null and b/app/src/main/res/drawable/heart.png differ diff --git a/app/src/main/res/drawable/help.png b/app/src/main/res/drawable/help.png new file mode 100644 index 0000000000000000000000000000000000000000..b6b09a1f0fa15d67b043e8171d6949c9954abc13 Binary files /dev/null and b/app/src/main/res/drawable/help.png differ diff --git a/app/src/main/res/drawable/home.png b/app/src/main/res/drawable/home.png new file mode 100644 index 0000000000000000000000000000000000000000..5192b93cb47bfb7be78cc28423d641b547eeb9d2 Binary files /dev/null and b/app/src/main/res/drawable/home.png differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..1e4408cae4a05b8fb4b1be8fa4521edd7e25735b --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/invaderslogo.png b/app/src/main/res/drawable/invaderslogo.png new file mode 100644 index 0000000000000000000000000000000000000000..80dd2a5282248ee487d18986f2848388971ca357 Binary files /dev/null and b/app/src/main/res/drawable/invaderslogo.png differ diff --git a/app/src/main/res/drawable/leftbutton.png b/app/src/main/res/drawable/leftbutton.png new file mode 100644 index 0000000000000000000000000000000000000000..f88e017ee49b3cb279eefb11fee6153b7d1b86bf Binary files /dev/null and b/app/src/main/res/drawable/leftbutton.png differ diff --git a/app/src/main/res/drawable/logobit.png b/app/src/main/res/drawable/logobit.png new file mode 100644 index 0000000000000000000000000000000000000000..5116028f641d62cf52ab06ae37eed0314f8897b6 Binary files /dev/null and b/app/src/main/res/drawable/logobit.png differ diff --git a/app/src/main/res/drawable/mute.png b/app/src/main/res/drawable/mute.png new file mode 100644 index 0000000000000000000000000000000000000000..9d994fd893064837580f127f93a2f0faba432e29 Binary files /dev/null and b/app/src/main/res/drawable/mute.png differ diff --git a/app/src/main/res/drawable/play.png b/app/src/main/res/drawable/play.png new file mode 100644 index 0000000000000000000000000000000000000000..c99d3867f7eb1e36e13bc9aa84bb26a0ecf8f5ac Binary files /dev/null and b/app/src/main/res/drawable/play.png differ diff --git a/app/src/main/res/drawable/playbutton.png b/app/src/main/res/drawable/playbutton.png new file mode 100644 index 0000000000000000000000000000000000000000..0631d9f75b1e9f5cff0ed09060179aef414fdd9e Binary files /dev/null and b/app/src/main/res/drawable/playbutton.png differ diff --git a/app/src/main/res/drawable/player.png b/app/src/main/res/drawable/player.png new file mode 100644 index 0000000000000000000000000000000000000000..ebdabc35d885fa1878aa873172a11c36af75faac Binary files /dev/null and b/app/src/main/res/drawable/player.png differ diff --git a/app/src/main/res/drawable/player2.png b/app/src/main/res/drawable/player2.png new file mode 100644 index 0000000000000000000000000000000000000000..2c0a12f991c7accdf92e0aa66daff0f318136283 Binary files /dev/null and b/app/src/main/res/drawable/player2.png differ diff --git a/app/src/main/res/drawable/player3.png b/app/src/main/res/drawable/player3.png new file mode 100644 index 0000000000000000000000000000000000000000..c18bbab8f6d87231effe0208e3dc9179ed80d1e3 Binary files /dev/null and b/app/src/main/res/drawable/player3.png differ diff --git a/app/src/main/res/drawable/player4.png b/app/src/main/res/drawable/player4.png new file mode 100644 index 0000000000000000000000000000000000000000..63c6f0944137ef045080fa3851b4dc105e544eac Binary files /dev/null and b/app/src/main/res/drawable/player4.png differ diff --git a/app/src/main/res/drawable/red.png b/app/src/main/res/drawable/red.png new file mode 100644 index 0000000000000000000000000000000000000000..9646866cb79a5e4e655fc83bd43e3653dfe834e3 Binary files /dev/null and b/app/src/main/res/drawable/red.png differ diff --git a/app/src/main/res/drawable/red32.png b/app/src/main/res/drawable/red32.png new file mode 100644 index 0000000000000000000000000000000000000000..adb1b83103294a098670f0e0da8fe1f0b0c2102b Binary files /dev/null and b/app/src/main/res/drawable/red32.png differ diff --git a/app/src/main/res/drawable/red64.png b/app/src/main/res/drawable/red64.png new file mode 100644 index 0000000000000000000000000000000000000000..3d02a77800a875066e5eb5f710c14314bb403c3b Binary files /dev/null and b/app/src/main/res/drawable/red64.png differ diff --git a/app/src/main/res/drawable/replay.png b/app/src/main/res/drawable/replay.png new file mode 100644 index 0000000000000000000000000000000000000000..05a0bbd5c47c2f200404ce1ecb3bc1c0bdf372b4 Binary files /dev/null and b/app/src/main/res/drawable/replay.png differ diff --git a/app/src/main/res/drawable/rightbutton.png b/app/src/main/res/drawable/rightbutton.png new file mode 100644 index 0000000000000000000000000000000000000000..bda9f3b3648ceab0edffeb5b8d4436000a7614e3 Binary files /dev/null and b/app/src/main/res/drawable/rightbutton.png differ diff --git a/app/src/main/res/drawable/unmute.png b/app/src/main/res/drawable/unmute.png new file mode 100644 index 0000000000000000000000000000000000000000..d7baac03d01696df64c60f1281358e01bf6bacf4 Binary files /dev/null and b/app/src/main/res/drawable/unmute.png differ diff --git a/app/src/main/res/font/ballsontherampage.ttf b/app/src/main/res/font/ballsontherampage.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f28ba6c6a54f617065e989506ea5713e4c710854 Binary files /dev/null and b/app/src/main/res/font/ballsontherampage.ttf differ diff --git a/app/src/main/res/font/rampage.xml b/app/src/main/res/font/rampage.xml new file mode 100644 index 0000000000000000000000000000000000000000..7df067c301818dbc8d2bd51e6a4651bc91d81392 --- /dev/null +++ b/app/src/main/res/font/rampage.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000000000000000000000000000000000..2e4af1083ff844fc1d00ef4e9689186c986694c8 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +