🎉 énoncé et modèle

master
Marc CHEVALDONNE 2 years ago
parent 1b6bdf4715
commit 4c04b61530

@ -0,0 +1,51 @@
kind: pipeline
type: docker
name: CI
trigger:
event:
- push
steps:
# Les fichiers swagger.[yml|json] doivent être dans le répertoire /docs
- name: build
image: hub.codefirst.iut.uca.fr/marc.chevaldonne/codefirst-dotnet7-maui:latest
commands:
- cd Sources/
- dotnet restore LeagueOfLegends.sln
- dotnet build LeagueOfLegends.sln -c Release --no-restore /p:AndroidSdkDirectory=$ANDROID_SDK_ROOT -property:Aapt2ToolPath=$ANDROID_SDK_ROOT/build-tools/33.0.0
- dotnet publish LolApp/LolApp.csproj -c Release --no-restore -o $CI_PROJECT_DIR/build/release -f:net7.0-android /p:AndroidSdkDirectory=/usr/lib/android-sdk
- name: tests
image: hub.codefirst.iut.uca.fr/marc.chevaldonne/codefirst-dotnet7-maui:latest
commands:
- cd Sources/
- dotnet restore LeagueOfLegends.sln
- dotnet test LeagueOfLegends.sln --no-restore /p:AndroidSdkDirectory=$ANDROID_SDK_ROOT -property:Aapt2ToolPath=$ANDROID_SDK_ROOT/build-tools/33.0.0
depends_on: [build]
- name: code-analysis
image: hub.codefirst.iut.uca.fr/marc.chevaldonne/codefirst-dronesonarplugin-dotnet7-maui
commands:
- cd Sources/
- dotnet restore LeagueOfLegends.sln
- dotnet sonarscanner begin /k:$${project_key} /d:sonar.host.url=$${sonar_host} /d:sonar.coverageReportPaths="coveragereport/SonarQube.xml" /d:sonar.coverage.exclusions=$${coverage_exclusions} /d:sonar.login=$${sonar_token}
- dotnet build LeagueOfLegends.sln -c Release --no-restore /p:AndroidSdkDirectory=$ANDROID_SDK_ROOT -property:Aapt2ToolPath=$ANDROID_SDK_ROOT/build-tools/33.0.0
- dotnet test LeagueOfLegends.sln --logger trx --no-restore /p:AndroidSdkDirectory=$ANDROID_SDK_ROOT -property:Aapt2ToolPath=$ANDROID_SDK_ROOT/build-tools/33.0.0 /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura --collect "XPlat Code Coverage"
- reportgenerator -reports:"**/coverage.cobertura.xml" -reporttypes:SonarQube -targetdir:"coveragereport"
- dotnet publish LolApp/LolApp.csproj -c Release --no-restore -o $CI_PROJECT_DIR/build/release -f:net7.0-android /p:AndroidSdkDirectory=/usr/lib/android-sdk
- dotnet sonarscanner end /d:sonar.login=$${sonar_token}
secrets: [ SECRET_SONAR_LOGIN ]
environment:
sonar_host: https://codefirst.iut.uca.fr/sonar/
sonar_token:
from_secret: SECRET_SONAR_LOGIN
project_key: efLol
coverage_exclusions: "Tests/**"
# when:
# branch:
# - master
# event:
# - push
# - pull_request
depends_on: [tests]

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

@ -0,0 +1,200 @@
# prepaLoL
## Enoncé
Réaliser une application MAUI avec un MVVM "maison".
Je vous fournis le modèle, et peut-être quelques vues au fur et à mesure.
J'attends de vous :
- la réalisation d'un toolkit MVVM (bibliothèque de classes),
- le _wrapping_ des classes du modèle par des VM (à chaque fois que c'est nécessaire),
- l'utilisation de commandes pour les différentes fonctionnalités,
- l'utilisation d'une VM _applicative_ (navigation, index, sélection...).
Faites ce que vous pouvez avec, dans l'ordre :
1. l'affichage de la collection de Champions. La possibilité de naviguer de n en n champions (5 champions par page, ou 10, etc.) et la pagination doivent être gérées.
2. Permettez la sélection d'un champion pour le voir dans une page (on n'utilisera que ses propriétés simples (```Name```, ```Bio```, ```Icon```) puis ```LargeImage```).
3. Ajoutez la gestion des caractéristiques (```Characteristics```).
4. Ajoutez la gestion de la classe du champion.
5. Permettez la modification d'un champion existant (depuis la page du champion, et depuis un swipe sur l'item sélectionné dans la collection).
6. Permettez l'ajout d'un nouveau champion.
7. Ajoutez la gestion des skills.
8. Ajoutez la gestion des skins.
## Captures d'écrans
Quelques captures d'écrans comme attendus :
<img src="./Documentation/IMG_5744.PNG" width=200/>
<img src="./Documentation/IMG_5745.PNG" width=200/>
<img src="./Documentation/IMG_5746.PNG" width=200/>
<img src="./Documentation/IMG_5747.PNG" width=200/>
<img src="./Documentation/IMG_5748.PNG" width=200/>
<img src="./Documentation/IMG_5749.PNG" width=200/>
<img src="./Documentation/IMG_5750.PNG" width=200/>
<img src="./Documentation/IMG_5751.PNG" width=200/>
<img src="./Documentation/IMG_5752.PNG" width=200/>
## Diagramme de classes du modèle
```mermaid
classDiagram
class LargeImage{
+/Base64 : string
}
class Champion{
+/Name : string
+/Bio : string
+/Icon : string
+/Characteristics : Dictionary~string, int~
~ AddSkin(skin : Skin) bool
~ RemoveSkin(skin: Skin) bool
+ AddSkill(skill: Skill) bool
+ RemoveSkill(skill: Skill) bool
+ AddCharacteristics(someCharacteristics : params Tuple~string, int~[])
+ RemoveCharacteristics(label : string) bool
+ this~label : string~ : int?
}
Champion --> "1" LargeImage : Image
class ChampionClass{
<<enumeration>>
Unknown,
Assassin,
Fighter,
Mage,
Marksman,
Support,
Tank,
}
Champion --> "1" ChampionClass : Class
class Skin{
+/Name : string
+/Description : string
+/Icon : string
+/Price : float
}
Skin --> "1" LargeImage : Image
Champion "1" -- "*" Skin
class Skill{
+/Name : string
+/Description : string
}
class SkillType{
<<enumeration>>
Unknown,
Basic,
Passive,
Ultimate,
}
Skill --> "1" SkillType : Type
Champion --> "*" Skill
class Rune{
+/Name : string
+/Description : string
}
Rune --> "1" LargeImage : Image
class RuneFamily{
<<enumeration>>
Unknown,
Precision,
Domination
}
Rune --> "1" RuneFamily : Family
class Category{
<<enumeration>>
Major,
Minor1,
Minor2,
Minor3,
OtherMinor1,
OtherMinor2
}
class RunePage{
+/Name : string
+/this[category : Category] : Rune?
- CheckRunes(newRuneCategory : Category)
- CheckFamilies(cat1 : Category, cat2 : Category) bool?
- UpdateMajorFamily(minor : Category, expectedValue : bool)
}
RunePage --> "*" Rune : Dictionary~Category,Rune~
```
## Diagramme de classes des interfaces de gestion de l'accès aux données
```mermaid
classDiagram
direction LR;
class IGenericDataManager~T~{
<<interface>>
GetNbItems() Task~int~
GetItems(index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~T~~
GetNbItemsByName(substring : string)
GetItemsByName(substring : string, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~T~~
UpdateItem(oldItem : T, newItem : T) Task~T~~
AddItem(item : T) Task~T~
DeleteItem(item : T) Task~bool~
}
class IChampionsManager{
<<interface>>
GetNbItemsByCharacteristic(charName : string)
GetItemsByCharacteristic(charName : string, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~Champion?~~
GetNbItemsByClass(championClass : ChampionClass)
GetItemsByClass(championClass : ChampionClass, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~Champion?~~
GetNbItemsBySkill(skill : Skill?)
GetItemsBySkill(skill : Skill?, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~Champion?~~
GetNbItemsBySkill(skill : string)
GetItemsBySkill(skill : string, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~Champion?~~
GetNbItemsByRunePage(runePage : RunePage?)
GetItemsByRunePage(runePage : RunePage?, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~Champion?~~
}
class ISkinsManager{
<<interface>>
GetNbItemsByChampion(champion : Champion?)
GetItemsByChampion(champion : Champion?, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~Skin?~~
}
class IRunesManager{
<<interface>>
GetNbItemsByFamily(family : RuneFamily)
GetItemsByFamily(family : RuneFamily, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~Rune?~~
}
class IRunePagesManager{
<<interface>>
GetNbItemsByRune(rune : Rune?)
GetItemsByRune(rune : Rune?, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~RunePage?~~
GetNbItemsByChampion(champion : Champion?)
GetItemsByChampion(champion : Champion?, index : int, count : int, orderingPropertyName : string?, descending : bool) Task~IEnumerable~RunePage?~~
}
IGenericDataManager~Champion?~ <|.. IChampionsManager : T--Champion?
IGenericDataManager~Skin?~ <|.. ISkinsManager : T--Skin?
IGenericDataManager~Rune?~ <|.. IRunesManager : T--Rune?
IGenericDataManager~RunePage?~ <|.. IRunePagesManager : T--RunePage?
class IDataManager{
<<interface>>
}
IChampionsManager <-- IDataManager : ChampionsMgr
ISkinsManager <-- IDataManager : SkinsMgr
IRunesManager <-- IDataManager : RunesMgr
IRunePagesManager <-- IDataManager : RunePagesMgr
```
## Diagramme de classes simplifié du Stub
```mermaid
classDiagram
direction TB;
IDataManager <|.. StubData
ChampionsManager ..|> IChampionsManager
StubData --> ChampionsManager
RunesManager ..|> IRunesManager
StubData --> RunesManager
RunePagesManager ..|> IRunePagesManager
StubData --> RunePagesManager
SkinsManager ..|> ISkinsManager
StubData --> SkinsManager
StubData --> RunesManager
StubData --> "*" Champion
StubData --> "*" Rune
StubData --> "*" RunePages
StubData --> "*" Skins
```

@ -0,0 +1,51 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33110.190
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Model", "Model\Model.csproj", "{2960F9BA-49DE-494D-92E3-CE5A794BA1A9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C76D0C23-1FFA-4963-93CD-E12BD643F030}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleTests", "Tests\ConsoleTests\ConsoleTests.csproj", "{1889FA6E-B7C6-416E-8628-9449FB9070B9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csproj", "{3B720C0C-53FE-4642-A2DB-87FD8634CD74}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Stub", "Stub", "{2C607793-B163-4731-A4D1-AFE8A7C4C170}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StubLib", "StubLib\StubLib.csproj", "{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2960F9BA-49DE-494D-92E3-CE5A794BA1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2960F9BA-49DE-494D-92E3-CE5A794BA1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2960F9BA-49DE-494D-92E3-CE5A794BA1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2960F9BA-49DE-494D-92E3-CE5A794BA1A9}.Release|Any CPU.Build.0 = Release|Any CPU
{1889FA6E-B7C6-416E-8628-9449FB9070B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1889FA6E-B7C6-416E-8628-9449FB9070B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1889FA6E-B7C6-416E-8628-9449FB9070B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1889FA6E-B7C6-416E-8628-9449FB9070B9}.Release|Any CPU.Build.0 = Release|Any CPU
{3B720C0C-53FE-4642-A2DB-87FD8634CD74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B720C0C-53FE-4642-A2DB-87FD8634CD74}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B720C0C-53FE-4642-A2DB-87FD8634CD74}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B720C0C-53FE-4642-A2DB-87FD8634CD74}.Release|Any CPU.Build.0 = Release|Any CPU
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{1889FA6E-B7C6-416E-8628-9449FB9070B9} = {C76D0C23-1FFA-4963-93CD-E12BD643F030}
{B01D7EF2-2D64-409A-A29A-61FB7BB7A9DB} = {2C607793-B163-4731-A4D1-AFE8A7C4C170}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {92F3083D-793F-4552-8A9A-0AD6534159C9}
EndGlobalSection
EndGlobal

@ -0,0 +1,151 @@
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Numerics;
using System.Text;
namespace Model;
public class Champion : IEquatable<Champion>
{
public string Name
{
get => name;
private init
{
if(string.IsNullOrWhiteSpace(value))
{
name = "Unknown";
return;
}
name = value;
}
}
private readonly string name = null!;
public string Bio
{
get => bio;
set
{
if(value == null)
{
bio = "";
return;
}
bio = value;
}
}
private string bio = "";
public ChampionClass Class { get; set; }
public string Icon { get; set; }
public LargeImage Image { get; set; }
public Champion(string name, ChampionClass champClass = ChampionClass.Unknown, string icon = "", string image = "", string bio = "")
{
Name = name;
Class = champClass;
Icon = icon;
Image = new LargeImage(image);
Bio = bio;
Characteristics = new ReadOnlyDictionary<string, int>(characteristics);
Skins = new ReadOnlyCollection<Skin>(skins);
}
public ReadOnlyCollection<Skin> Skins { get; private set; }
private List<Skin> skins = new ();
public ReadOnlyDictionary<string, int> Characteristics { get; private set; }
private readonly Dictionary<string, int> characteristics = new Dictionary<string, int>();
public ImmutableHashSet<Skill> Skills => skills.ToImmutableHashSet();
private HashSet<Skill> skills = new HashSet<Skill>();
internal bool AddSkin(Skin skin)
{
if (skins.Contains(skin))
return false;
skins.Add(skin);
return true;
}
internal bool RemoveSkin(Skin skin)
=> skins.Remove(skin);
public bool AddSkill(Skill skill)
=> skills.Add(skill);
public bool RemoveSkill(Skill skill)
=> skills.Remove(skill);
public void AddCharacteristics(params Tuple<string, int>[] someCharacteristics)
{
foreach(var c in someCharacteristics)
{
characteristics[c.Item1] = c.Item2;
}
}
public bool RemoveCharacteristics(string label)
=> characteristics.Remove(label);
public int? this[string label]
{
get
{
if(!characteristics.TryGetValue(label, out int value)) return null;
else return value;
}
set
{
if(!value.HasValue)
{
RemoveCharacteristics(label);
return;
}
characteristics[label] = value.Value;
}
}
public override bool Equals(object? obj)
{
if(ReferenceEquals(obj, null)) return false;
if(ReferenceEquals(obj, this)) return true;
if(GetType() != obj.GetType()) return false;
return Equals(obj as Champion);
}
public override int GetHashCode()
=> Name.GetHashCode() % 997;
public bool Equals(Champion? other)
=> Name.Equals(other?.Name);
public override string ToString()
{
StringBuilder sb = new StringBuilder($"{Name} ({Class})");
if(!string.IsNullOrWhiteSpace(bio))
{
sb.AppendLine($"\t{bio}");
}
if(characteristics.Any())
{
sb.AppendLine("\tCharacteristics:");
foreach(var characteristic in characteristics)
{
sb.AppendLine($"\t\t{characteristic.Key} - {characteristic.Value}");
}
}
if(skills.Any())
{
sb.AppendLine("\tSkills:");
foreach(var skill in Skills)
{
sb.AppendLine($"\t\t{skill.Name} - {skill.Description}");
}
}
return sb.ToString();
}
}

@ -0,0 +1,53 @@
using System;
using Shared;
namespace Model
{
public interface IDataManager
{
IChampionsManager ChampionsMgr { get; }
ISkinsManager SkinsMgr { get; }
IRunesManager RunesMgr { get; }
IRunePagesManager RunePagesMgr { get; }
}
public interface IChampionsManager : IGenericDataManager<Champion?>
{
Task<int> GetNbItemsByCharacteristic(string charName);
Task<IEnumerable<Champion?>> GetItemsByCharacteristic(string charName, int index, int count, string? orderingPropertyName = null, bool descending = false);
Task<int> GetNbItemsByClass(ChampionClass championClass);
Task<IEnumerable<Champion?>> GetItemsByClass(ChampionClass championClass, int index, int count, string? orderingPropertyName = null, bool descending = false);
Task<int> GetNbItemsBySkill(Skill? skill);
Task<IEnumerable<Champion?>> GetItemsBySkill(Skill? skill, int index, int count, string? orderingPropertyName = null, bool descending = false);
Task<int> GetNbItemsByRunePage(RunePage? runePage);
Task<IEnumerable<Champion?>> GetItemsByRunePage(RunePage? runePage, int index, int count, string? orderingPropertyName = null, bool descending = false);
Task<int> GetNbItemsBySkill(string skill);
Task<IEnumerable<Champion?>> GetItemsBySkill(string skill, int index, int count, string? orderingPropertyName = null, bool descending = false);
}
public interface ISkinsManager : IGenericDataManager<Skin?>
{
Task<int> GetNbItemsByChampion(Champion? champion);
Task<IEnumerable<Skin?>> GetItemsByChampion(Champion? champion, int index, int count, string? orderingPropertyName = null, bool descending = false);
}
public interface IRunesManager : IGenericDataManager<Rune?>
{
Task<int> GetNbItemsByFamily(RuneFamily family);
Task<IEnumerable<Rune?>> GetItemsByFamily(RuneFamily family, int index, int count, string? orderingPropertyName = null, bool descending = false);
}
public interface IRunePagesManager : IGenericDataManager<RunePage?>
{
Task<int> GetNbItemsByRune(Rune? rune);
Task<IEnumerable<RunePage?>> GetItemsByRune(Rune? rune, int index, int count, string? orderingPropertyName = null, bool descending = false);
Task<int> GetNbItemsByChampion(Champion? champion);
Task<IEnumerable<RunePage?>> GetItemsByChampion(Champion? champion, int index, int count, string? orderingPropertyName = null, bool descending = false);
}
}

@ -0,0 +1,28 @@
using System;
namespace Model
{
public class LargeImage : IEquatable<LargeImage>
{
public string Base64 { get; set; }
public LargeImage(string base64)
{
Base64 = base64;
}
public bool Equals(LargeImage? other)
=> other != null && other.Base64.Equals(Base64);
public override bool Equals(object? obj)
{
if(ReferenceEquals(obj, null)) return false;
if(ReferenceEquals(obj!, this)) return true;
if(GetType() != obj!.GetType()) return false;
return Equals(obj! as LargeImage);
}
public override int GetHashCode()
=> Base64.Substring(0, 10).GetHashCode();
}
}

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="enums\" />
</ItemGroup>
<ItemGroup>
<Folder Include="enums\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,69 @@
using System;
namespace Model
{
public class Rune : IEquatable<Rune>
{
public string Name
{
get => name;
private init
{
if(string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("a Rune must have a name");
}
name = value;
}
}
private readonly string name = null!;
public string Description
{
get => description;
set
{
if(string.IsNullOrWhiteSpace(value))
{
description = "";
return;
}
description = value;
}
}
private string description = "";
public RuneFamily Family { get; set; }
public string Icon { get; set; }
public LargeImage Image { get; set; }
public Rune(string name, RuneFamily family, string icon = "", string image = "", string description = "")
{
Name = name;
Family = family;
Icon = icon;
Image = new LargeImage(image);
Description = description;
}
public override bool Equals(object? obj)
{
if(ReferenceEquals(obj, null)) return false;
if(ReferenceEquals(obj, this)) return true;
if(GetType() != obj.GetType()) return false;
return Equals(obj as Rune);
}
public bool Equals(Rune? other)
=> Name.Equals(other?.Name);
public override int GetHashCode()
=> Name.GetHashCode() % 281;
public override string ToString()
=> $"{Name} ({Family})";
}
}

@ -0,0 +1,17 @@
using System;
namespace Model
{
public partial class RunePage
{
public enum Category
{
Major,
Minor1,
Minor2,
Minor3,
OtherMinor1,
OtherMinor2
}
}
}

@ -0,0 +1,96 @@
using System;
using System.Collections.ObjectModel;
namespace Model
{
public partial class RunePage
{
public string Name
{
get => name;
private init
{
if(string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("a Rune Page must have a name");
}
name = value;
}
}
private readonly string name = null!;
public ReadOnlyDictionary<Category, Rune> Runes { get; private set; }
private Dictionary<Category, Rune> runes = new Dictionary<Category, Rune>();
public RunePage(string name)
{
Name = name;
Runes = new ReadOnlyDictionary<Category, Rune>(runes);
}
public Rune? this[Category category]
{
get
{
if(runes.TryGetValue(category, out Rune? rune))
{
return rune;
}
return null;
}
set
{
if(value == null)
{
runes.Remove(category);
return;
}
runes[category] = value!;
CheckRunes(category);
}
}
private void CheckRunes(Category newRuneCategory)
{
switch(newRuneCategory)
{
case Category.Major:
UpdateMajorFamily(Category.Minor1, true);
UpdateMajorFamily(Category.Minor2, true);
UpdateMajorFamily(Category.Minor3, true);
UpdateMajorFamily(Category.OtherMinor1, false);
UpdateMajorFamily(Category.OtherMinor2, false);
break;
case Category.Minor1:
case Category.Minor2:
case Category.Minor3:
UpdateMajorFamily(newRuneCategory, true);
break;
case Category.OtherMinor1:
case Category.OtherMinor2:
UpdateMajorFamily(newRuneCategory, false);
break;
}
}
private bool? CheckFamilies(Category cat1, Category cat2)
{
runes.TryGetValue(cat1, out Rune? rune1);
runes.TryGetValue(cat2, out Rune? rune2);
if(rune1 == null || rune2 == null)
{
return null;
}
return rune1.Family == rune2.Family;
}
private void UpdateMajorFamily(Category cat, bool expectedValue)
{
if(CheckFamilies(Category.Major, cat).GetValueOrDefault(expectedValue) != expectedValue)
{
runes.Remove(cat);
}
}
}
}

@ -0,0 +1,63 @@
using System;
namespace Model
{
public class Skill : IEquatable<Skill>
{
public SkillType Type { get; private set; }
public string Name
{
get => name;
private init
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("a Skill needs a name");
}
name = value;
}
}
private readonly string name = null!;
public string Description
{
get => description;
set
{
if(string.IsNullOrWhiteSpace(value))
{
description = "";
return;
}
description = value;
}
}
private string description = "";
public Skill(string name, SkillType type, string description = "")
{
Name = name;
Type = type;
Description = description ?? "";
}
public override bool Equals(object? obj)
{
if(ReferenceEquals(obj, null)) return false;
if(ReferenceEquals(obj, this)) return true;
if(GetType() != obj.GetType()) return false;
return Equals(obj as Skill);
}
public bool Equals(Skill? other)
=> Name.Equals(other?.Name) && Type == other.Type;
public override int GetHashCode()
=> Name.GetHashCode() % 281;
public override string ToString()
=> $"{Name} ({Type})";
}
}

@ -0,0 +1,83 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Model
{
public class Skin : IEquatable<Skin>
{
public string Name
{
get => name;
private init
{
if(string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("A skin must have a name");
}
name = value;
}
}
private readonly string name = null!;
public string Description
{
get => description;
set
{
if (string.IsNullOrWhiteSpace(value))
{
description = "";
return;
}
description = value;
}
}
private string description = "";
public string Icon { get; set; }
public LargeImage Image { get; set; }
public float Price { get; set; }
public Champion Champion
{
get => champion;
private init
{
if (value == null)
throw new ArgumentNullException("A skill can't have a null champion");
champion = value;
}
}
private readonly Champion champion = null!;
public Skin(string name, Champion champion, float price = 0.0f, string icon = "", string image = "", string description = "")
{
Name = name;
Champion = champion;
Champion.AddSkin(this);
Price = price;
Icon = icon;
Image = new LargeImage(image);
Description = description;
}
public override bool Equals(object? obj)
{
if(ReferenceEquals(obj, null)) return false;
if(ReferenceEquals(obj, this)) return true;
if(GetType() != obj.GetType()) return false;
return Equals(obj as Skin);
}
public bool Equals(Skin? other)
=> Name.Equals(other?.Name);
public override int GetHashCode()
=> Name.GetHashCode() % 997;
public override string ToString()
=> $"{Name}";
}
}

@ -0,0 +1,15 @@
using System;
namespace Model
{
public enum ChampionClass
{
Unknown,
Assassin,
Fighter,
Mage,
Marksman,
Support,
Tank,
}
}

@ -0,0 +1,11 @@
using System;
namespace Model
{
public enum RuneFamily
{
Unknown,
Precision,
Domination
}
}

@ -0,0 +1,12 @@
using System;
namespace Model
{
public enum SkillType
{
Unknown,
Basic,
Passive,
Ultimate
}
}

@ -0,0 +1,11 @@
namespace Shared;
public interface IGenericDataManager<T>
{
Task<int> GetNbItems();
Task<IEnumerable<T>> GetItems(int index, int count, string? orderingPropertyName = null, bool descending = false);
Task<int> GetNbItemsByName(string substring);
Task<IEnumerable<T>> GetItemsByName(string substring, int index, int count, string? orderingPropertyName = null, bool descending = false);
Task<T> UpdateItem(T oldItem, T newItem);
Task<T> AddItem(T item);
Task<bool> DeleteItem(T item);
}

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

@ -0,0 +1,65 @@
using System;
using Model;
namespace StubLib
{
static class Extensions
{
internal static Task<IEnumerable<T?>> GetItemsWithFilterAndOrdering<T>(this IEnumerable<T> collection,
Func<T, bool> filter, int index, int count, string? orderingPropertyName = null, bool descending = false)
{
IEnumerable<T> temp = collection;
temp = temp.Where(item => filter(item));
if(orderingPropertyName != null)
{
var prop = typeof(T).GetProperty(orderingPropertyName!);
if (prop != null)
{
temp = descending ? temp.OrderByDescending(item => prop.GetValue(item))
: temp.OrderBy(item => prop.GetValue(item));
}
}
return Task.FromResult<IEnumerable<T?>>(temp.Skip(index*count).Take(count));
}
internal static Task<int> GetNbItemsWithFilter<T>(this IEnumerable<T> collection, Func<T, bool> filter)
{
return Task.FromResult(collection.Count(item => filter(item)));
}
internal static Task<T?> AddItem<T>(this IList<T> collection, T? item)
{
if(item == null || collection.Contains(item))
{
return Task.FromResult<T?>(default(T));
}
collection.Add(item);
return Task.FromResult<T?>(item);
}
internal static Task<bool> DeleteItem<T>(this IList<T> collection, T? item)
{
if(item == null)
{
return Task.FromResult(false);
}
bool result = collection.Remove(item!);
return Task.FromResult(result);
}
internal static Task<T?> UpdateItem<T>(this IList<T> collection, T? oldItem, T? newItem)
{
if(oldItem == null || newItem == null) return Task.FromResult<T?>(default(T));
if(!collection.Contains(oldItem))
{
return Task.FromResult<T?>(default(T));
}
collection.Remove(oldItem!);
collection.Add(newItem!);
return Task.FromResult<T?>(newItem);
}
}
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,83 @@
using System;
using Model;
namespace StubLib
{
public partial class StubData
{
private readonly List<RunePage> runePages = new();
private void InitRunePages()
{
var runePage1 = new RunePage("rune page 1");
runePage1[RunePage.Category.Major] = runes[0];
runePage1[RunePage.Category.Minor1] = runes[1];
runePage1[RunePage.Category.Minor2] = runes[2];
runePage1[RunePage.Category.Minor3] = runes[3];
runePage1[RunePage.Category.OtherMinor1] = runes[4];
runePage1[RunePage.Category.OtherMinor2] = runes[5];
runePages.Add(runePage1);
}
public class RunePagesManager : IRunePagesManager
{
private readonly StubData parent;
public RunePagesManager(StubData parent)
=> this.parent = parent;
private static Func<RunePage, string, bool> filterByName
= (rp, substring) => rp.Name.Contains(substring, StringComparison.InvariantCultureIgnoreCase);
private static Func<RunePage, Rune?, bool> filterByRune
= (rp, rune) => rune != null && rp.Runes.Values.Contains(rune!);
public Task<RunePage?> AddItem(RunePage? item)
=> parent.runePages.AddItem(item);
public Task<bool> DeleteItem(RunePage? item)
=> parent.runePages.DeleteItem(item);
public Task<IEnumerable<RunePage?>> GetItems(int index, int count, string? orderingPropertyName = null, bool descending = false)
=> parent.runePages.GetItemsWithFilterAndOrdering(
rp => true,
index, count, orderingPropertyName, descending);
public Task<IEnumerable<RunePage?>> GetItemsByChampion(Champion? champion, int index, int count, string? orderingPropertyName = null, bool descending = false)
=> Task.FromResult<IEnumerable<RunePage?>>(
parent.championsAndRunePages
.Where(tuple => tuple.Item1.Equals(champion))
.Select(tuple => tuple.Item2)
.Skip(index*count).Take(count));
public Task<IEnumerable<RunePage?>> GetItemsByName(string substring, int index, int count, string? orderingPropertyName = null, bool descending = false)
=> parent.runePages.GetItemsWithFilterAndOrdering(
rp => filterByName(rp, substring),
index, count, orderingPropertyName, descending);
public Task<IEnumerable<RunePage?>> GetItemsByRune(Rune? rune, int index, int count, string? orderingPropertyName = null, bool descending = false)
=> parent.runePages.GetItemsWithFilterAndOrdering(
rp => filterByRune(rp, rune),
index, count, orderingPropertyName, descending);
public Task<int> GetNbItems()
=> parent.runePages.GetNbItemsWithFilter(
rp => true);
public Task<int> GetNbItemsByChampion(Champion? champion)
=> Task.FromResult(parent.championsAndRunePages.Count(tuple => tuple.Item1.Equals(champion)));
public Task<int> GetNbItemsByName(string substring)
=> parent.runePages.GetNbItemsWithFilter(
rp => filterByName(rp, substring));
public Task<int> GetNbItemsByRune(Rune? rune)
=> parent.runePages.GetNbItemsWithFilter(
rp => filterByRune(rp, rune));
public Task<RunePage?> UpdateItem(RunePage? oldItem, RunePage? newItem)
=> parent.runePages.UpdateItem(oldItem, newItem);
}
}
}

@ -0,0 +1,69 @@
using System;
using Model;
namespace StubLib
{
public partial class StubData
{
private readonly List<Rune> runes = new()
{
new Rune("Conqueror", RuneFamily.Precision),
new Rune("Triumph", RuneFamily.Precision),
new Rune("Legend: Alacrity", RuneFamily.Precision),
new Rune("Legend: Tenacity", RuneFamily.Precision),
new Rune("last stand", RuneFamily.Domination),
new Rune("last stand 2", RuneFamily.Domination),
};
public class RunesManager : IRunesManager
{
private readonly StubData parent;
public RunesManager(StubData parent)
=> this.parent = parent;
public Task<Rune?> AddItem(Rune? item)
=> parent.runes.AddItem(item);
public Task<bool> DeleteItem(Rune? item)
=> parent.runes.DeleteItem(item);
public Task<IEnumerable<Rune?>> GetItems(int index, int count, string? orderingPropertyName = null, bool descending = false)
=> parent.runes.GetItemsWithFilterAndOrdering(
r => true,
index, count, orderingPropertyName, descending);
private static Func<Rune, RuneFamily, bool> filterByRuneFamily
= (rune, family) => rune.Family == family;
private static Func<Rune, string, bool> filterByName
= (rune, substring) => rune.Name.Contains(substring, StringComparison.InvariantCultureIgnoreCase);
public Task<IEnumerable<Rune?>> GetItemsByFamily(RuneFamily family, int index, int count, string? orderingPropertyName = null, bool descending = false)
=> parent.runes.GetItemsWithFilterAndOrdering(
rune => filterByRuneFamily(rune, family),
index, count, orderingPropertyName, descending);
public Task<IEnumerable<Rune?>> GetItemsByName(string substring, int index, int count, string? orderingPropertyName = null, bool descending = false)
=> parent.runes.GetItemsWithFilterAndOrdering(
rune => filterByName(rune, substring),
index, count, orderingPropertyName, descending);
public Task<int> GetNbItems()
=> parent.runes.GetNbItemsWithFilter(
rune => true);
public Task<int> GetNbItemsByFamily(RuneFamily family)
=> parent.runes.GetNbItemsWithFilter(
rune => filterByRuneFamily(rune, family));
public Task<int> GetNbItemsByName(string substring)
=> parent.runes.GetNbItemsWithFilter(
rune => filterByName(rune, substring));
public Task<Rune?> UpdateItem(Rune? oldItem, Rune? newItem)
=> parent.runes.UpdateItem(oldItem, newItem);
}
}
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,35 @@
using Model;
namespace StubLib;
public partial class StubData : IDataManager
{
public StubData()
{
ChampionsMgr = new ChampionsManager(this);
SkinsMgr = new SkinsManager(this);
RunesMgr = new RunesManager(this);
RunePagesMgr = new RunePagesManager(this);
InitSkins();
InitRunePages();
}
public IChampionsManager ChampionsMgr { get; }
public ISkinsManager SkinsMgr { get; }
public IRunesManager RunesMgr { get; }
public IRunePagesManager RunePagesMgr { get; }
private List<Tuple<Champion, RunePage>> championsAndRunePages = new();
private void InitChampionsAndRunePages()
{
championsAndRunePages.Add(Tuple.Create(champions[0], runePages[0]));
}
}

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Model\Model.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Model\Model.csproj" />
<ProjectReference Include="..\..\StubLib\StubLib.csproj" />
</ItemGroup>
<ItemGroup>
<None Remove="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
</ItemGroup>
</Project>

@ -0,0 +1,338 @@
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Model;
using StubLib;
using static System.Console;
namespace ConsoleTests
{
static class Program
{
static IDataManager dataManager = null!;
static async Task Main(string[] args)
{
try
{
using var servicesProvider = new ServiceCollection()
.AddSingleton<IDataManager, StubData>()
.BuildServiceProvider();
dataManager = servicesProvider.GetRequiredService<IDataManager>();
await DisplayMainMenu();
Console.ReadLine();
}
catch (Exception ex)
{
Debug.WriteLine(ex, "Stopped program because of exception");
throw;
}
}
public static async Task DisplayMainMenu()
{
Dictionary<int, string> choices = new Dictionary<int, string>()
{
[1] = "1- Manage Champions",
[2] = "2- Manage Skins",
[3] = "3- Manage Runes",
[4] = "4- Manage Rune Pages",
[99] = "99- Quit"
};
while(true)
{
int input = DisplayAMenu(choices);
switch(input)
{
case 1:
await DisplayChampionsMenu();
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 99:
WriteLine("Bye bye!");
return;
default:
break;
}
}
}
private static int DisplayAMenu(Dictionary<int, string> choices)
{
int input=-1;
while(true)
{
WriteLine("What is your choice?");
WriteLine("--------------------");
foreach(var choice in choices.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value))
{
WriteLine(choice);
}
if(!int.TryParse(ReadLine(), out input) || input == -1)
{
WriteLine("I do not understand what your choice is. Please try again.");
continue;
}
break;
}
WriteLine($"You have chosen: {choices[input]}");
WriteLine();
return input;
}
public static async Task DisplayChampionsMenu()
{
Dictionary<int, string> choices = new Dictionary<int, string>()
{
[0] = "0- Get number of champions",
[1] = "1- Get champions",
[2] = "2- Find champions by name",
[3] = "3- Find champions by characteristic",
[4] = "4- Find champions by class",
[5] = "5- Find champions by skill",
[6] = "6- Add new champion",
[7] = "7- Delete a champion",
[8] = "8- Update a champion",
};
int input = DisplayAMenu(choices);
switch(input)
{
case 0:
int nb = await dataManager.ChampionsMgr.GetNbItems();
WriteLine($"There are {nb} champions");
WriteLine("**********************");
break;
case 1:
{
int index = ReadAnInt("Please enter the page index");
int count = ReadAnInt("Please enter the number of elements to display");
WriteLine($"{count} champions of page {index+1}");
var champions = await dataManager.ChampionsMgr.GetItems(index, count, nameof(Champion.Name));
foreach(var champion in champions)
{
WriteLine($"\t{champion}");
}
WriteLine("**********************");
}
break;
case 2:
{
string substring = ReadAString("Please enter the substring to look for in the name of a champion");
int index = ReadAnInt("Please enter the page index");
int count = ReadAnInt("Please enter the number of elements to display");
var champions = await dataManager.ChampionsMgr.GetItemsByName(substring, index, count, nameof(Champion.Name));
foreach(var champion in champions)
{
WriteLine($"\t{champion}");
}
WriteLine("**********************");
}
break;
case 3:
{
string substring = ReadAString("Please enter the substring to look for in the characteristics of champions");
int index = ReadAnInt("Please enter the page index");
int count = ReadAnInt("Please enter the number of elements to display");
var champions = await dataManager.ChampionsMgr.GetItemsByCharacteristic(substring, index, count, nameof(Champion.Name));
foreach(var champion in champions)
{
WriteLine($"\t{champion}");
}
WriteLine("**********************");
}
break;
case 4:
{
ChampionClass championClass = ReadAnEnum<ChampionClass>($"Please enter the champion class (possible values are: {Enum.GetNames<ChampionClass>().Aggregate("", (name, chaine) => $"{chaine} {name}")}):");
int index = ReadAnInt("Please enter the page index");
int count = ReadAnInt("Please enter the number of elements to display");
var champions = await dataManager.ChampionsMgr.GetItemsByClass(championClass, index, count, nameof(Champion.Name));
foreach(var champion in champions)
{
WriteLine($"\t{champion}");
}
WriteLine("**********************");
}
break;
case 5:
{
string substring = ReadAString("Please enter the substring to look for in the skills of champions");
int index = ReadAnInt("Please enter the page index");
int count = ReadAnInt("Please enter the number of elements to display");
var champions = await dataManager.ChampionsMgr.GetItemsBySkill(substring, index, count, nameof(Champion.Name));
foreach(var champion in champions)
{
WriteLine($"\t{champion}");
}
WriteLine("**********************");
}
break;
case 6:
{
WriteLine("You are going to create a new champion.");
string name = ReadAString("Please enter the champion name:");
ChampionClass championClass = ReadAnEnum<ChampionClass>($"Please enter the champion class (possible values are: {Enum.GetNames<ChampionClass>().Aggregate("", (name, chaine) => $"{chaine} {name}")}):");
string bio = ReadAString("Please enter the champion bio:");
Champion champion = new Champion(name, championClass, bio: bio);
DisplayCreationChampionMenu(champion);
_ = await dataManager.ChampionsMgr.AddItem(champion);
}
break;
case 7:
{
WriteLine("You are going to delete a champion.");
string name = ReadAString("Please enter the champion name:");
var somechampions = await dataManager.ChampionsMgr.GetItemsByName(name, 0, 10, nameof(Champion.Name));
var someChampionNames = somechampions.Select(c => c!.Name);
var someChampionNamesAsOneString = someChampionNames.Aggregate("", (name, chaine) => $"{chaine} {name}");
string champName = ReadAStringAmongPossibleValues($"Who do you want to delete among these champions? (type \"Cancel\" to ... cancel) {someChampionNamesAsOneString}",
someChampionNames.ToArray());
if(champName != "Cancel")
{
await dataManager.ChampionsMgr.DeleteItem(somechampions.Single(c => c!.Name == champName));
}
}
break;
case 8:
{
WriteLine("You are going to update a champion.");
string name = ReadAString("Please enter the champion name:");
var somechampions = await dataManager.ChampionsMgr.GetItemsByName(name, 0, 10, nameof(Champion.Name));
var someChampionNames = somechampions.Select(c => c!.Name);
var someChampionNamesAsOneString = someChampionNames.Aggregate("", (name, chaine) => $"{chaine} {name}");
string champName = ReadAStringAmongPossibleValues($"Who do you want to update among these champions? (type \"Cancel\" to ... cancel) {someChampionNamesAsOneString}",
someChampionNames.ToArray());
if(champName == "Cancel") break;
ChampionClass championClass = ReadAnEnum<ChampionClass>($"Please enter the champion class (possible values are: {Enum.GetNames<ChampionClass>().Aggregate("", (name, chaine) => $"{chaine} {name}")}):");
string bio = ReadAString("Please enter the champion bio:");
Champion champion = new Champion(champName, championClass, bio: bio);
DisplayCreationChampionMenu(champion);
await dataManager.ChampionsMgr.UpdateItem(somechampions.Single(c => c!.Name == champName), champion);
}
break;
default:
break;
}
}
public static void DisplayCreationChampionMenu(Champion champion)
{
Dictionary<int, string> choices = new Dictionary<int, string>()
{
[1] = "1- Add a skill",
[2] = "2- Add a skin",
[3] = "3- Add a characteristic",
[99] = "99- Finish"
};
while(true)
{
int input = DisplayAMenu(choices);
switch(input)
{
case 1:
string skillName = ReadAString("Please enter the skill name:");
SkillType skillType = ReadAnEnum<SkillType>($"Please enter the skill type (possible values are: {Enum.GetNames<SkillType>().Aggregate("", (name, chaine) => $"{chaine} {name}")}):");
string skillDescription = ReadAString("Please enter the skill description:");
Skill skill = new Skill(skillName, skillType, skillDescription);
champion.AddSkill(skill);
break;
case 2:
string skinName = ReadAString("Please enter the skin name:");
string skinDescription = ReadAString("Please enter the skin description:");
float skinPrice = ReadAFloat("Please enter the price of this skin:");
Skin skin = new Skin(skinName, champion, skinPrice, description: skinDescription);
break;
case 3:
string characteristic = ReadAString("Please enter the characteristic:");
int value = ReadAnInt("Please enter the value associated to this characteristic:");
champion.AddCharacteristics(Tuple.Create(characteristic, value));
break;
case 99:
return;
default:
break;
}
}
}
private static int ReadAnInt(string message)
{
while(true)
{
WriteLine(message);
if(!int.TryParse(ReadLine(), out int result))
{
continue;
}
return result;
}
}
private static float ReadAFloat(string message)
{
while(true)
{
WriteLine(message);
if(!float.TryParse(ReadLine(), out float result))
{
continue;
}
return result;
}
}
private static string ReadAString(string message)
{
while(true)
{
WriteLine(message);
string? line = ReadLine();
if(line == null)
{
continue;
}
return line!;
}
}
private static TEnum ReadAnEnum<TEnum>(string message) where TEnum :struct
{
while(true)
{
WriteLine(message);
if(!Enum.TryParse<TEnum>(ReadLine(), out TEnum result))
{
continue;
}
return result;
}
}
private static string ReadAStringAmongPossibleValues(string message, params string[] possibleValues)
{
while(true)
{
WriteLine(message);
string? result = ReadLine();
if(result == null) continue;
if(result != "Cancel" && !possibleValues.Contains(result!)) continue;
return result!;
}
}
}
}
Loading…
Cancel
Save