Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make game loop back to first level after beating last level #44

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 39 additions & 17 deletions Assets/Scripts/Configuration.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.IO;
using static ShootAR.Spawner;
using UnityEngine;
using System.Xml;

namespace ShootAR {
/// <summary>
Expand All @@ -11,7 +12,10 @@ public class Configuration {
private static Configuration instance;
public static Configuration Instance {
get {
if (instance == null) instance = new Configuration();
if (instance == null) {
instance = new Configuration();
instance.Initialize();
}

return instance;
}
Expand All @@ -29,6 +33,21 @@ public int SpawnPatternSlot {
spawnPatternSlot = value;
UnsavedChanges = true;

// Get how many levels are in the spawn pattern:
int numberOfLevels = 0;
using (
XmlReader element = XmlReader.Create(Configuration.Instance.SpawnPatternFile)
) {
element.MoveToContent();
element.ReadToDescendant("level");
do {
numberOfLevels++;
} while (element.ReadToNextSibling("level"));

NumberOfLevels = numberOfLevels;
}
// ---

OnSlotChanged?.Invoke();
}
}
Expand All @@ -44,6 +63,9 @@ public string SpawnPatternFile {
get => $"{patternsDir.FullName}/{SpawnPattern}.xml";
}

/// <summary>The total number of levels defined in the selected pattern file.</summary>
public int NumberOfLevels { get; private set; }

private bool soundMuted = false;

public bool SoundMuted {
Expand Down Expand Up @@ -105,8 +127,11 @@ public float Volume {
/// <summary>File where high-scores are stored.</summary>
public FileInfo Highscores { get; private set; }

///<summary>Constructor that extracts values from config file</summary>
private Configuration() {
/* Putting the initialization code in the constructor causes it to fall into a
recursive loop of trying to create the singleton object every time a member of the
instance object is called. Moving the code out of the constructor and calling it
after the object has already been created solves this issue. */
private void Initialize() {
patternsDir = new DirectoryInfo(Path.Combine(
Application.persistentDataPath,
PATTERNS_DIR
Expand All @@ -127,19 +152,6 @@ private Configuration() {
HIGHSCORES_DIR
));

/* Read config file before calling CreateFile to avoid needlessly
* reading the same default values from the just-created config file. */
if (configFile.Exists) {
using (BinaryReader reader = new BinaryReader(configFile.OpenRead())) {
/* The order that the data are read must be the same as the
* the order they are stored. */
SoundMuted = reader.ReadBoolean();
BgmMuted = reader.ReadBoolean();
Volume = reader.ReadSingle();
SpawnPatternSlot = reader.ReadInt32();
}
}

CreateFiles();

// Read names of spawn patterns from file and fill up SpawnPatterns.
Expand Down Expand Up @@ -167,6 +179,16 @@ private Configuration() {
if (SpawnPatternSlot >= SpawnPatterns.Length)
SpawnPatternSlot = 0;

// Read config file
using (BinaryReader reader = new BinaryReader(configFile.OpenRead())) {
/* The order that the data are read must be the same as
* the order they are stored. */
SoundMuted = reader.ReadBoolean();
BgmMuted = reader.ReadBoolean();
Volume = reader.ReadSingle();
SpawnPatternSlot = reader.ReadInt32();
}


Highscores = new FileInfo(Path.Combine(
highscoresDir.FullName,
Expand Down Expand Up @@ -194,7 +216,7 @@ public void SaveSettings() {

using (BinaryWriter writer = new BinaryWriter(configFile.OpenWrite())) {
/* The order the data is written must be the same as
* the order the constructor reads them. */
* the order they are read. */
writer.Write(SoundMuted);
writer.Write(BgmMuted);
writer.Write(Volume);
Expand Down
21 changes: 13 additions & 8 deletions Assets/Scripts/Game/GameManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using System.Collections;
using System.Xml;

namespace ShootAR
{
Expand Down Expand Up @@ -221,11 +221,16 @@ private void AdvanceLevel() {
Debug.Log($"Advancing to level {gameState.Level}");
#endif

/* If the current level exceeds the total number of defined levels,
* translate the index to the equivalent level in pattern file. */
int l = gameState.Level % Configuration.Instance.NumberOfLevels;
int roundedLevel = l == 0 ? Configuration.Instance.NumberOfLevels : l;

// Configuring spawners
Stack<Spawner.SpawnConfig>[] patterns
= Spawner.ParseSpawnPattern(Configuration.Instance.SpawnPatternFile);
Dictionary<Type, Stack<Spawner.SpawnConfig>> patterns
= Spawner.ParseSpawnPattern(Configuration.Instance.SpawnPatternFile, roundedLevel);

Spawner.SpawnerFactory(patterns, 0, ref spawnerGroups, ref stashedSpawners);
Spawner.SpawnerFactory(patterns, ref spawnerGroups, ref stashedSpawners);

int totalEnemies = 0;
foreach (var group in spawnerGroups) {
Expand All @@ -242,18 +247,18 @@ private void AdvanceLevel() {
/* Player should always have enough ammo to play the next
* round. If they already have more than enough, they get
* points. */
ulong difference = (ulong)(player.Ammo - totalEnemies);
int difference = player.Ammo - totalEnemies;
if (difference > 0)
scoreManager.AddScore(difference * 10);
scoreManager.AddScore((ulong)difference * 10);
else if (difference < 0) {
/* If it is before the 1st round, give player more bullets
* so they are allowed to miss shots. */
const float bonusBullets = 0.55f;
if (gameState.Level == 1) {
difference *= (ulong)bonusBullets;
difference = (int)(difference * bonusBullets);
}

player.Ammo += (difference < int.MaxValue) ? -(int)difference : int.MaxValue;
player.Ammo += -difference;
}

gameState.RoundWon = false;
Expand Down
150 changes: 60 additions & 90 deletions Assets/Scripts/Game/Spawner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ public void StopSpawning() {
}


public static Stack<SpawnConfig>[] ParseSpawnPattern(string spawnPatternFilePath, int level = 1) {
public static Dictionary<Type, Stack<SpawnConfig>> ParseSpawnPattern(string spawnPatternFilePath, int level = 1) {
Type type = default;
int limit = default, multiplier = -1;
float rate = default, delay = default,
Expand All @@ -319,16 +319,18 @@ public static Stack<SpawnConfig>[] ParseSpawnPattern(string spawnPatternFilePath
* the same <spawnable> node. */

while (!doneParsingForCurrentLevel) {
if (!(xmlPattern?.Read() ?? false)) {
if (!(xmlPattern?.Read() ?? false)) { //< Moving to next element happens here.
xmlPattern = XmlReader.Create(spawnPatternFilePath);
xmlPattern.MoveToContent();
}

// skip to wanted level
if (level > 1) {
// skip to wanted level
xmlPattern.ReadToDescendant("level");
for (int i = 1; i < level; i++) {
xmlPattern.Skip();
for (int i = 1; level > i; i++) {
if (!xmlPattern.ReadToNextSibling("level")) {
throw new UnityException(
"Not enough levels in spawn pattern."
);
}
}
}

Expand Down Expand Up @@ -421,100 +423,83 @@ public static Stack<SpawnConfig>[] ParseSpawnPattern(string spawnPatternFilePath
}
}

xmlPattern.Close();
xmlPattern.Dispose();

Stack<SpawnConfig>[] extractedPatterns = new Stack<SpawnConfig>[groupsByType.Count];
groupsByType.Values.CopyTo(extractedPatterns, 0);
return extractedPatterns;
return groupsByType;
}

public static void SpawnerFactory(
Stack<SpawnConfig>[] spawnPatterns, int index,
Dictionary<Type, Stack<SpawnConfig>> spawnPatterns,
ref Dictionary<Type, List<Spawner>> spawners,
ref Stack<Spawner> stashedSpawners) {

/* A list to keep track of which types of spawners
* are in the pattern and should not be stashed away. */
List<Type> requiredSpawnerTypes = new List<Type>();
/* If spawners.Keys is used directly in foreach, the enumeration
stops because its values gets changed. A copy of the values is
used to avoid that. */
Type[] keys = new Type[spawners.Keys.Count];
spawners.Keys.CopyTo(keys, 0);

bool recursed = false;

for (int id = index; id < spawnPatterns.Length; id++) {
Stack<SpawnConfig> pattern = spawnPatterns[id];
Type type = pattern.Peek().type;
if (!requiredSpawnerTypes.Contains(type))
requiredSpawnerTypes.Add(type);
/* Stash entire group of spawners when that type is not used
* this round. */
foreach (Type type in keys) {
if (spawnPatterns.ContainsKey(type)) {
foreach (Spawner spawner in spawners[type]) {
stashedSpawners.Push(spawner);
}
spawners.Remove(type);
}
}

/* Skip indices that we don't care about.
* But even if we don't care about those skipped patterns, the
* type still needs to be tracked first so that the spawners
* that are actually needed don't end up stashed away. */
if (id < index) continue;
Dictionary<Type, int> spawnersRequired = new Dictionary<Type, int>();

/* Check how many spawners will be needed.
If spawners' list contains more spawners of a type, stash them away.
This step goes before parsing the actual patterns, to make sure that
all unused spawners are stashed and availiable to be reused when needed. */
foreach (Type type in spawnPatterns.Keys) {
if (!spawners.ContainsKey(type))
spawners.Add(type, new List<Spawner>(0));

int spawnersRequired = pattern.Count;
int spawnersAvailable = spawners[type].Count;
int required = spawnPatterns[type].Count - spawners[type].Count;

// If there are not enough spawners available take from stash
if (spawnersRequired > spawnersAvailable) {
// How many spawners will be taken from stash?
int spawnersReused;
if (spawnersRequired <= spawnersAvailable + stashedSpawners.Count)
spawnersReused = spawnersRequired - spawnersAvailable;
else
spawnersReused = stashedSpawners.Count;
if (required > 0)
spawnersRequired.Add(type, required);

for (int i = 0; i < spawnersReused; i++) {
spawners[type].Add(stashedSpawners.Pop());
spawnersRequired--;
}
else if (required < 0) /* Stash excess spawners */ {
for (int i = spawners[type].Count; i < spawners[type].Count + required; i--)
stashedSpawners.Push(spawners[type][i]);

/* If there are still not enough spawners, continue to the
* rest of the patterns hoping that more spawners will be
* stashed in the meantime. */
int recursionIndex = index + id + 1;
if (spawnersRequired > 0 && recursionIndex < spawnPatterns.Length) {
SpawnerFactory(spawnPatterns, recursionIndex,
ref spawners, ref stashedSpawners);

recursed = true;

// Take spawners from stash
for (
int i = stashedSpawners.Count;
i != 0 && spawnersRequired > 0;
i--
) {
spawners[type].Add(stashedSpawners.Pop());
spawnersRequired--;
}
}
spawners[type].RemoveRange(spawners[type].Count + required, required);
}
}

// If there are still not enough spawners, create new
while (spawnersRequired > 0) {
spawners[type].Add(Instantiate(
Resources.Load<Spawner>(Prefabs.SPAWNER)));
// Configure spawners
foreach (Type type in spawnPatterns.Keys) {
int spawnersReused; // How many spawners will be taken from stash

spawnersRequired--;
}
// Take spawners from stash
if (spawnersRequired[type] <= stashedSpawners.Count)
spawnersReused = spawnersRequired[type];
else
spawnersReused = stashedSpawners.Count;

for (int i = 0; i < spawnersReused; i++) {
spawners[type].Add(stashedSpawners.Pop());
}
else if (spawnersRequired < spawnersAvailable) {
// Stash leftover spawners.

int firstLeftover = spawnersRequired + 1,
leftoversCount = spawnersAvailable - spawnersRequired;
spawnersRequired[type] -= spawnersReused;

for (int i = firstLeftover; i < leftoversCount; i++)
stashedSpawners.Push(spawners[type][i]);
// If there are not enough stashed spawners, create new.
while (spawnersRequired[type] > 0) {
spawners[type].Add(Instantiate(
Resources.Load<Spawner>(Prefabs.SPAWNER)));

spawners[type].RemoveRange(firstLeftover, leftoversCount);
spawnersRequired[type]--;
}

// Configure spawner using the retrieved data.
foreach (Spawner spawner in spawners[type]) {
spawner.Configure(pattern.Pop());
spawner.Configure(spawnPatterns[type].Pop());
}

// Populating pools
Expand Down Expand Up @@ -544,21 +529,6 @@ public static void SpawnerFactory(
else if (type == typeof(PowerUpCapsule) && Spawnable.Pool<PowerUpCapsule>.Instance.Count == 0) {
Spawnable.Pool<PowerUpCapsule>.Instance.Populate();
}

/* If a recursion happened then the rest patterns have already
* been parsed, meaning that there is no need to continue the
* loop. */
if (recursed) return;
}

/* Stash entire group of spawners when that type is not used
* this round. */
foreach (var type in requiredSpawnerTypes) {
if (!spawners.ContainsKey(type)) {
for (int i = 0; i < spawners[type].Count; i++)
stashedSpawners.Push(spawners[type][i]);
spawners.Remove(type);
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Assets/Tests/PatternsFileTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public void CopyFileToPermDataPath() {
LocalFiles.CopyResourceToPersistentData(patternFileBasename, patternFile);

Assert.That(File.Exists(targetFile));
File.Delete(patternFile);
File.Delete(targetFile);
}

[Test]
Expand Down
Loading