Skip to content

brann0n/StendenClicker

Repository files navigation

StendenClicker

In opdracht van NHL Stenden is er vanuit de vakken Design Patterns en C# Threading een eindopdracht gemaakt door de projectgroep genaamd “Stenden Clicker”. Er is besloten de eindopdrachten van de vakken te combineren welke samen 6EC waard zijn. Het project is gebaseerd op een clicker game. Er zal een mogelijkheid zijn om met meerdere spelers tegelijk te spelen.

Dit project is opgezet en is ontwikkeld door:

  • Brandon Abbenhuis
  • Sjihdazi Hellingman
  • Mark van der Hart
  • Sytze van der Gaag
  • Jarno Hilverts
  • Jurrian Tanke

Design Patterns

De volgende Design Patterns zijn verwerkt in het project:

  • Abstract Factory

De abstract factory maakt scenes, platforms en monsters aan. Deze zijn te vinden onder: StendenClicker.Library. De code voor de factory ziet er als volgt uit (voorbeeld: IAbstractScene.cs):

public interface IAbstractScene
{
	public int CurrentMonster { get; set; }
	public int MonsterCount { get; set; }
	public string Background { get; set; }
	public string Name { get; set; }
}

Hier is te zien hoe een scene wordt gemaakt aan de hand van een aantal fields die worden opgehaald en geset. Afhankelijk van wat er wordt opgehaald, komt de scene er op een bepaalde manier uit te zien.

  • Object Pool Design

De StendenClicker maakt gebruik van een object pool design voor het aanmaken en opslaan van coins. Deze kunnen vervolgens worden hergebruikt om recources te besparen. In StendenClicker.Library/CurrencyObjects staat ReusableCurrencyPool.cs. Hier is te zien hoe coins worden aangemaakt wanneer er niet genoeg van zijn en worden opgeslagen voor hergebruik.

protected static ReusableCurrencyPool Instance { get { return instance.Value; } }
private readonly List<Currency> Reusables;
private int PoolSizeSC { get { return Reusables.Where(owo => owo is SparkCoin).Count(); } }
private int PoolSizeEC { get { return Reusables.Where(uwu => uwu is EuropeanCredit).Count(); } }

Om ervoor te zorgen dat de pool niet oneindig groot wordt is er een limiet op gezet.

public const int PoolSizeSC_MAX = 50;
public const int PoolSizeEC_MAX = 3;
  • Memento

Het “Player” object wordt omgezet in JSON-data. Deze data worden vervolgens als String opgeslagen in de database door de ApiPlayerHandler. De ViewModel weet wanneer de data moet worden opgeslagen. Wanneer er op een fysieke knop wordt geklikt of wanneer de game wordt afgesloten zal de data worden opgeslagen. Via de getPlayerState van de ApiPlayerHandler kan de actie worden teruggedraaid. Dit is waar de design pattern memento van toepassing is. Memento zorgt ervoor dat een object weer terug kan veranderen naar zijn eerdere staat. Wanneer de speler het spel weer opstart kunnende opgeslagen gegevens gemakkelijk weer worden teruggezet.

In de onderstaande code wordt de huidige player state opgeslagen na elke monster die verslagen wordt. Deze is te vinden in StendenClicker.Library onder Playercontrols in de file ApiPlayerHandler.cs

public async Task SetPlayerStateAsync(Player player)
{
	state = player;
	Models.DatabaseModels.Player dbPlayer = player;
	var response = await RestHelper.PostRequestAsync("api/player/set", dbPlayer);
	if (response.StatusCode == HttpStatusCode.OK)
	{
		await LocalPlayerData.SaveLocalPlayerData(state);
	}
	else
	{
		await LocalPlayerData.SaveLocalPlayerData(state);
		throw new Exception($"Couldn't set the player state... Api error: [{response.StatusCode}] {response.ErrorMessage}");
	}
}
  • Observer

Binnen de StendenClicker game zal er een optie zijn om samen met andere spelers de strijd aan te gaan met monsters die de studenten door de jaren heen mentaal te lijf zijn gegaan. Om alle gebruikers dezelfde informatie te tonen over de status van hun online game, zal er gebruik worden gemaakt van het observer design pattern. De observer design pattern zorgt ervoor dat als er een object van status veranderd, alle afhankelijke objecten hiervan op de hoogte worden gebracht en automatisch worden bijgewerkt. Doormiddel van de methode ‘INotifyPropertyChanged’ van de ViewModel worden de eigendommen van de objecten bijgewerkt. Voor de communicatie tussen Client en de Server wordt gebruik gemaakt van SignalR.

In de onderstaande code is de broadcaster van een boss sessie te zien. Deze vrijwel hetzelfde als een normal sessie, echter moeten deze worden gescheiden zodat er twee pipelines kunnen worden gegenereerd. De code is te vinden in StendenClickerApi onder Hubs in het bestand: MultiplayerHub.cs.

[HubMethodName("broadcastSessionBoss")]
public async Task<bool> broadcastSession(string key, List<PlayerObject> sessionPlayers, BossGamePlatform a)
{
	if (key != UserGuid) throw new Exception("Session doesnt match the current userguid");

	bool SessionIsValid = SessionExtensions.ContainsKey(key);

	if (SessionIsValid)
	{
		List<string> PlayersInSession = sessionPlayers.Select(n => n.UserId.ToString()).ToList();

		SessionExtensions.UpdatePlayers(key, sessionPlayers);
		SessionExtensions.UpdateLevel(key, a);

		await Clients.Groups(PlayersInSession).receiveBossMonsterBroadcast(sessionPlayers, a);
	}
	return SessionIsValid;
}

Hierna kan de observer in StendenClickerGame onder HubProxy in bestand MultiplayerHubProxy.cs, de broadcast observen. Dat gebeurt in de onderstaande code.

MultiPlayerHub.On<MultiPlayerSession>("updateSession", sessionObject => updateSession(sessionObject));
MultiPlayerHub.On<List<Player>, NormalGamePlatform>("receiveNormalMonsterBroadcast", receiveNormalMonsterBroadcast);
MultiPlayerHub.On<List<Player>, BossGamePlatform>("receiveBossMonsterBroadcast", receiveBossMonsterBroadcast);
MultiPlayerHub.On("requestClickBatch", requestClickBatches);
MultiPlayerHub.On<InviteModel>("receiveInvite", receiveInvite);
  • Singleton

Voor de connectie met de server wordt een singleton gebruik. Dit is gedaan zodat er niet meerdere connecties kunnen worden gemaakt. Bij de StendenClicker is dat de MultiplayerHubProxy.cs, te vinden onder StendenClickerGame in HubProxy.

private static readonly Lazy<MultiplayerHubProxy> instance = new Lazy<MultiplayerHubProxy>(() =>
{
	MultiplayerHubProxy proxy = new MultiplayerHubProxy();
	proxy.InitProxyAsync(ServerURL);
	return proxy;
});

C# Threading

De volgende onderdelen van de code gebruiken threading:

  • Async and Await

Door de gehele applicatie wordt Async en Await gebruikt wanneer nodig. Het beste voorbeeld van het gebruik hiervan is bij de abilities. Deze zijn te vinden in StendenClickerGame, ViewModels in KoffieMachineViewModel.cs. Elke ability krijgt een cooldown die wordt await totdat deze weer geactiveerd mag worden.

private async void MartijnSportAbilityClick(Abilities SelfContext)
{
	ContextSetAbilityEnabled(SelfContext);

	CurrencyTrayViewModel.OnClickAbilityProcess += MartijnSportAbility;

	await ContextDelayProgressbarEmpty(SelfContext, 15000);
	CurrencyTrayViewModel.OnClickAbilityProcess -= MartijnSportAbility;
	await ContextDelayProgressbarFill(SelfContext, 285000);

	ContextSetAbilityDisabled(SelfContext);
}
  • Task en Multitasking

Binnen de applicatie en WebAPI wordt gebruik gemaakt van Tasks in een async patroon. Dit komt omdat het SignalR framework geïmplementeerd is, dit framework werkt goed samen met Tasks. SignalR kan doormiddel van Tasks garanderen dat een functie uitgevoerd is aan de server of client kant. Als bepaalde functies niet het await keyword bevatten dan is het niet mogelijk om te weten of die functie een Exception gegooid heeft. De Tasks zullen automatisch gebruik gaan maken van de Thread Pool in .NET. Mochten er functies uitgevoerd moeten worden waar geen Tasks voor beschikbaar zijn kunnen deze met de ThreadPool.QueueUserWorkItem functie alsnog op de ThreadPool uitgevoerd worden.

In de UI van de applicatie zal voor de OnHover events gebruik gemaakt worden van delegates, deze zullen taken uitvoeren die los staan van de UI Thread.

Server side moet er worden gewacht totdat alle clickbatches van de players ontvangen zijn. Hiervoor wordt een await gebruikt. Wanneer alle batches binnen zijn worden deze uitgevoerd en zullen de kliks worden verwerkt op de monsters.

  • Locking

Wanneer een sessie wordt gestart is het belangrijk dat er maar één sessie runt op een thead. Om ervoor te zorgen dat dit gebeurt wordt er gebruik gemaakt van thread locking.

public static MultiPlayerSession GetSessionByAnyClientId(string key)
{
	lock (AccessLock)
	{
		var session = Sessions.Values.Where(n => n.CurrentPlayerList.FirstOrDefault(m => m.UserId.ToString().Equals(key)) != null);
		if (session.Count() == 1)
		{
			return session.First();
		}
		else return null;
	}
}
  • Delegates

Delegates worden gebruikt voor het handelen van de coins wanneer de coins droppen. Er is een custom UI met coin grid welke deze gebruikt.

var location = coin.dropCoordinates(new StendenClicker.Library.Point {X = 15, Y = 3 });
	Grid.SetColumn(NewCoinButton, location.X);
	Grid.SetRow(NewCoinButton, location.Y);Add((Currency)sender);
}

Database Structuur

Alt Text

Werking van de Stenden Clicker Game

Om het spel op te starten moet de CPU op x86 worden gezet. Voor het gebruik van de game moet er een account aan worden gemaakt. Wanneer er een account is aangemaakt wordt het spel opgestart op de plek waar de speler voor het laatst is gebleven. Vanuit hier kan de speler, zoals de naam al suggereert, op de monsters en bosses klikken om levels te verslaan. Het doel van het spel is om zo veel mogelijk levels te verslaan. Op den duur worden monsters en bosses steeds moeilijker om te verslaan, maar hier kunnen upgrades en hero's je helpen. Deze zorgen ervoor dat de speler meer schade toe dient.

About

Clicker game developed in UWP, the game uses a backend server to provide a co-op experience

Resources

Stars

Watchers

Forks

Languages