Reputation: 47
I would like to make a game, if i press play button, random level (activity) will open. I got code for this: https://stackoverflow.com/a/29579373/13101103 This is working, but i would like to edit, example, all levels have 2 different answer, answer1 is fail, answer2 is pass the level, if user pass level1, and in level2 fail, than go back to mainactivity, and if start again, then the passed levels will not show again.
Example: There are 5 levels, user start random level, example level3, it passed, go to next random level, example level2, it pass, go to next... level4, it failed, go back to mainactivity, user start again, but the already passed levels will not show, only unpassed... example start level3... if passed then go to level1....
How can i edit this code for my solution? Can somebody give me some tips? Because in this if i go back to mainactivity and start again, then it start with all levels... I tried to edit, but i'm stucked and not works...
Plus i would like to save progress when user leave the app. In sharedpreferences how can i save the passed levels (arraylist)....?
MainActivity:
enter code here
Button level1Button = findViewById(R.id.level1Button);
level1Button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// We are creating a list, which will store the activities that haven't been opened yet
ArrayList<Class> activityList = new ArrayList<>();
activityList.add(Level1Activity.class);
activityList.add(Level2Activity.class);
activityList.add(Level3Activity.class);
activityList.add(Level4Activity.class);
activityList.add(Level5Activity.class);
Random generator = new Random();
int number = generator.nextInt(5) + 1;
Class activity = null;
// Here, we are checking to see what the output of the random was
switch(number) {
case 1:
activity = Level1Activity.class;
// We are adding the number of the activity to the list
activityList.remove(Level1Activity.class);
break;
case 2:
activity = Level2Activity.class;
activityList.remove(Level2Activity.class);
break;
case 3:
activity = Level3Activity.class;
activityList.remove(Level3Activity.class);
break;
case 4:
activity = Level4Activity.class;
activityList.remove(Level4Activity.class);
break;
default:
activity = Level5Activity.class;
activityList.remove(Level5Activity.class);
break;
}
// We use intents to start activities
Intent intent = new Intent(getBaseContext(), activity);
// `intent.putExtra(...)` is used to pass on extra information to the next activity
intent.putExtra("ACTIVITY_LIST", activityList);
startActivity(intent);
}
});
Level1Activity:
enter code here
failbutton1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v){
ArrayList<Class> activityList = new ArrayList<>();
activityList.add(Level1Activity.class);
Bundle extras = getIntent().getExtras();
activityList = (ArrayList<Class>) extras.get("ACTIVITY_LIST");
//Class activity = null;
Intent intent = new Intent(Level1Activity.this, Main2Activity.class);
intent.putExtra("ACTIVITY_LIST", activityList);
startActivity(intent);
}
});
buttonlevel1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ArrayList<Class> activityList = new ArrayList<>();
Bundle extras = getIntent().getExtras();
activityList = (ArrayList<Class>) extras.get("ACTIVITY_LIST");
if(activityList.size() == 0) {
// Do something when after all activities have been opened
//startActivity(new Intent(Level1Activity.this, Main2Activity.class));
//Intent intent = new Intent(Level1Activity.this, Main2Activity.class);
//intent.putExtra("ACTIVITY_LIST", activityList);
//startActivity(intent);
} else {
// Now, the random number is generated between 1 and however many
// activities we have remaining
Random generator = new Random();
int number = generator.nextInt(activityList.size()) + 1;
Class activity = null;
// Here, we are checking to see what the output of the random was
switch(number) {
case 1:
// We will open the first remaining activity of the list
activity = activityList.get(0);
// We will now remove that activity from the list
activityList.remove(0);
break;
case 2:
// We will open the second remaining activity of the list
activity = activityList.get(1);
activityList.remove(1);
break;
case 3:
// We will open the third remaining activity of the list
activity = activityList.get(2);
activityList.remove(2);
break;
case 4:
// We will open the fourth remaining activity of the list
activity = activityList.get(3);
activityList.remove(3);
break;
default:
// We will open the fifth remaining activity of the list
activity = activityList.get(4);
activityList.remove(4);
break;
}
// Note: in the above, we might not have 3 remaining activities, for example,
// but it doesn't matter because that case wouldn't be called anyway,
// as we have already decided that the number would be between 1 and the number of
// activities left.
// Starting the activity, and passing on the remaining number of activities
// to the next one that is opened
Intent intent = new Intent(getBaseContext(), activity);
intent.putExtra("ACTIVITY_LIST", activityList);
startActivity(intent);
}
}
});
level2, level3.... is same just different id-s
Upvotes: 0
Views: 183
Reputation: 319
I'd suggest using the Singleton pattern to handle passing data between activities.
You can pass the list by intent's putExtra()
or by SharedPreferences
but with a Singleton
class, it looks much better and easier to manipulate your data because they are encapsulated. So much so in your situation where you want to save your levels' states (e.g. when they are already completed).
However, if you really insist on using SharedPreferences
to save the list then I suggest converting it to Json
by using Gson
. (Check below my answer on how to implement this.)
As I said, I'd use the Singleton
pattern to avoid creating unnecessary boilerplate code and to encapsulate the levels' states.
LevelManager class (the singleton)
final class LevelManager {
// constants
private static final String LEVELS_SHARED_PREFERENCES_NAME = "app_name.LEVELS";
// variables
private static LevelManager instance;
private List<Class> levels;
private SharedPreferences sharedPreferences;
private LevelManager(Context context) {
sharedPreferences =
context.getSharedPreferences(LEVELS_SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
levels = new ArrayList<>();
initializeList();
}
private void initializeList() {
// Initialize levels, ie. add levels that are not yet completed/passed
// Check in SharedPreferences if level has already been completed
boolean alreadyPassed;
alreadyPassed = sharedPreferences.getBoolean(Level1Activity.class.getSimpleName(), false);
if (!alreadyPassed) levels.add(Level1Activity.class);
alreadyPassed = sharedPreferences.getBoolean(Level2Activity.class.getSimpleName(), false);
if (!alreadyPassed) levels.add(Level2Activity.class);
alreadyPassed = sharedPreferences.getBoolean(Level3Activity.class.getSimpleName(), false);
if (!alreadyPassed) levels.add(Level3Activity.class);
alreadyPassed = sharedPreferences.getBoolean(Level4Activity.class.getSimpleName(), false);
if (!alreadyPassed) levels.add(Level4Activity.class);
alreadyPassed = sharedPreferences.getBoolean(Level5Activity.class.getSimpleName(), false);
if (!alreadyPassed) levels.add(Level5Activity.class);
}
static LevelManager getInstance(Context context) {
if (instance == null) {
instance = new LevelManager(context);
}
return instance;
}
Class getRandomLevel() {
if (levels.isEmpty()) {
return null; // Return null if all levels are already completed
}
Collections.shuffle(levels);
return levels.get(0);
}
void saveLevelState(Class levelClass, boolean passed) {
sharedPreferences.edit().putBoolean(levelClass.getSimpleName(), passed).apply();
if (passed) {
// Remove level from list if user passed it so that it won't
// be included in next levels
levels.remove(levelClass);
}
}
void reset() {
// Clears all entries in SharedPreferences and re-initialize list
sharedPreferences.edit().clear().apply();
initializeList();
}
}
Inside onCreate in MainActivity
// Get LevelManager singleton instance
final LevelManager levelManager = LevelManager.getInstance(this);
Button startButton = findViewById(R.id.startButton);
startButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Get next random level
Class levelToStart = levelManager.getRandomLevel();
// If all levels are already completed
if (levelToStart == null) {
Toast.makeText(MainActivity.this, "All levels are completed!",
Toast.LENGTH_LONG).show();
return;
}
Intent intent = new Intent(MainActivity.this, levelToStart);
startActivity(intent);
}
});
// I added a new button to reset all levels
Button resetButton = findViewById(R.id.resetButton);
resetButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Use the method reset() from LevelManager to restart everything
levelManager.reset();
Toast.makeText(MainActivity.this, "All levels have been reset!",
Toast.LENGTH_LONG).show();
}
});
Inside of onCreate on each Level Activity
// Get LevelManager
final LevelManager levelManager = LevelManager.getInstance(this);
// I created two buttons to simulate pass and fail
Button pass = findViewById(R.id.passButton);
Button fail = findViewById(R.id.failButton);
pass.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Save state (Don't forget to change 'N' below)
levelManager.saveLevelState(LevelNActivity.class, true);
// Get next level
Class levelToStart = levelManager.getRandomLevel();
// Check if all are levels already completed
if (levelToStart == null) {
Toast.makeText(LevelNActivity.this, "Completed all levels",
Toast.LENGTH_LONG).show();
finish(); // Must implement to avoid going back to previous level (ie. Activity)
return;
}
Intent intent = new Intent(LevelNActivity.this, levelToStart);
startActivity(intent);
finish();
}
});
fail.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
As you can see, you can simply use the finish()
method if the user failed the level whereas you use the code below to proceed to the next level:
// Get LevelManager
LevelManager levelManager = LevelManager.getInstance(this);
// Set that the user passed this level (Change 'N' to the current level we are in)
levelManager.saveLevelState(LevelNActivity.class, true);
// Get next level
Class nextLevel = levelManager.getRandomLevel();
// If all levels are completed then 'nextLevel' will be null
if (nextLevel == null) {
// ...
}
// Start next level and finish current
Intent intent = new Intent(this, nextLevel);
startActivity(intent);
finish();
Note: To avoid calling finish()
explicitly when starting the next level, you can put android:noHistory="true"
in your levels' activity tag inside your manifest file.
How to save list to SharedPreferences by converting it to Json using Gson
To actually use Gson
, you'll have to add implementation 'com.google.code.gson:gson:2.8.6'
inside your app gradle dependencies.
Also, there's a problem on Gson
when parsing Class
objects to Json
: You need to create your own serializer and deserializer for these objects and register it to your GsonBuilder
.
ClassAdapter class (this is where we create our own custom serializer and deserializer for Class objects)
public class ClassAdapter implements JsonSerializer<Class>, JsonDeserializer<Class> {
@Override
public JsonElement serialize(Class src, Type typeOfSrc, JsonSerializationContext context) {
// Get our class 'src' name
return new JsonPrimitive(src.getName());
}
@Override
public Class deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try {
// Get class
return Class.forName(json.getAsString());
} catch (ClassNotFoundException e) {
// If class could not be found or did not exists, handle error here...
e.printStackTrace();
}
return null;
}
}
Here's a sample usage of saving a list to SharedPreferences
by Json
using Gson
:
// Create new GsonBuilder and register our adapter for Class objects
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Class.class, new ClassAdapter());
// Initialize our list of levels (ie. classes)
List<Class> classes = new ArrayList<>();
classes.add(Level1Activity.class);
classes.add(Level2Activity.class);
classes.add(Level3Activity.class);
classes.add(Level4Activity.class);
classes.add(Level5Activity.class);
// Create Gson from GsonBuilder and convert list to json
Gson gson = gsonBuilder.create();
String json = gson.toJson(classes);
// Save json to SharedPreferences
SharedPreferences sharedPreferences = getSharedPreferences("app_name", MODE_PRIVATE);
sharedPreferences.edit().putString("levels", json).apply();
And to retrieve the list back:
// Retrieve json from SharedPreferences
SharedPreferences sharedPreferences = getSharedPreferences("app_name", MODE_PRIVATE);
String json = sharedPreferences.getString("levels", null);
// Handle here if json doesn't exist yet
if (json == null) {
// ...
}
// Create new GsonBuilder and register our adapter for Class objects
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Class.class, new ClassAdapter());
// Create Gson from GsonBuilder and specify type of list
Gson gson = gsonBuilder.create();
Type type = new TypeToken<ArrayList<Class>>(){}.getType();
// Convert json to list
List<Class> classes = gson.fromJson(json, type);
I hope you gained valuable tips to tackle this problem! And as always, happy coding!
Upvotes: 1