Compare commits

...

176 Commits

Author SHA1 Message Date
Ismail TAHA JANAN d95ee1ad2e Merge pull request 'doxygen' (#202) from doxygen into main
continuous-integration/drone/push Build is passing Details
2 years ago
mzjeeawody b73d88f983 change branch master yml
2 years ago
mzjeeawody c19f7398f6 output directory
continuous-integration/drone/push Build is passing Details
2 years ago
mzjeeawody 142f902ed9 correct yml
continuous-integration/drone/push Build is passing Details
2 years ago
mzjeeawody 4cf65f7bbd test local doxygen
continuous-integration/drone/push Build is passing Details
2 years ago
mzjeeawody 2e7974f29d doxygen
continuous-integration/drone/push Build is passing Details
2 years ago
Mohammad Zafir JEEAWODY 4b240195e9 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 89c98fef2c Merge pull request '🐛 Replace Equals with SequenceEqual for a collection' (#200) from apply-fixes-following-interview into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai c61ffeb783 🐛 Replace Equals with SequenceEqual
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 72ff04f845 Merge pull request '🚨 Encapsulate collections...' (#199) from apply-fixes-following-interview into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai d259884ba8 🚨 Encapsulate collections...
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 7d1f973153 Merge pull request ' Fix #176' (#195) from ut-turnextensions into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 9c0edea60b Finish unit testing TurnExtensions
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai a2d832a9f2 🚧 WIP
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 308ef1a5b1 ✏️
2 years ago
Alexis Drai c4c2a993c6 🔥 ♻️
2 years ago
Alexis Drai 42ab467bfb ✏️ Fix typo
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 7c6b3ee663 Merge pull request '🔊 Implement logger' (#192) from implement-logger into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 6de4e96803 🔧
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 932babdca6 🔥 📈 Get rid of ToString() methods
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 5c11e4b253 🔊 Use NLog in App.Program and Data.Players.PlayerDbManager
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai ef1a6966ea Merge pull request '🗃️ Add TurnEntity (many to many)' (#191) from add-turnentities into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 865947fa9c 📈
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 5694baf4cd Unit test Utils.Enumerables
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 2ef6b304dc 📈 Slither over the 80% line
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai cce587437e Test TurnEntity
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 1bf9d8394f 🚧 Redefine Equals and Hash in dice and faces
2 years ago
Alexis Drai 27e90bfbd5 🚚 Move Point to Tests root
2 years ago
Alexis Drai 32a58dd414 🐛 Initialize empty collections, debug die creation
2 years ago
Alexis Drai 0a01b402c0 🗃️ Add TurnEntity and implement many-to-many
2 years ago
Alexis Drai bf22c44740 ♻️ Rename Player to PlayerEntity in EF project for clarity
2 years ago
Alexis Drai 7fd6078c25 ♻️ Use inheritance, use properties for FK
2 years ago
Alexis Drai c7aac52f48 🔀 main --> this
2 years ago
Alexis Drai c3f7194a34 Merge pull request '🚑 Fix EF issue by using entities' (#190) from ef-hotfix into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai ca7c4a796f 🚑
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai e6ba5f150e 🚧 WIP
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 3a49fe0e88 Merge pull request '🐛 ♻️ Fix up dice and faces (EF)' (#189) from fixup-dice-entities into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 2c44df5e16 ♻️ Use extension features
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai b760b6f905 🐛
2 years ago
Alexis Drai f20adbad17 Merge pull request 'EF_Dice_Faces' (#188) from EF_Dice_Faces into main
continuous-integration/drone/push Build is passing Details
2 years ago
Ismail TAHA JANAN 6128f57dcc Merge branch 'main' into EF_Dice_Faces
continuous-integration/drone/push Build is passing Details
2 years ago
Ismail TAHA JANAN 48c130faf5 add entities and extentions to dice and faces
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai c623776e4e ✏️ Fix typo
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 84f52c3dce Merge pull request 'use-async-in-playerdb' (#186) from use-async-in-playerdb into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai e5fc62499f 📈 🔥 Remove unused and untestable code
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai cd2df0ba86 Update and correct UTs
2 years ago
Alexis Drai a319d7deb2 🚨 fix code smells
2 years ago
Alexis Drai a46d4b73a4 ⚗️ Play around with async-await
2 years ago
Alexis Drai 9ea198b9fa 📝 Update README
continuous-integration/drone/push Build is passing Details
2 years ago
Ismail TAHA JANAN 423965361b no sence code
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 4574e48203 Merge pull request ' dice and faces tests implementd' (#185) from TU_Dice_Faces into main
continuous-integration/drone/push Build is passing Details
2 years ago
Ismail TAHA JANAN 963ac99001 dice and faces tests implementd
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis DRAI 6c460efa63 ✏️ Fix typo in class name
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai ac5ebd7b87 ✏️ Fix comment
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai d4a90f917a 📝 Update README
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 0050836358 📝 Update README
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 6dc90b103c Merge pull request '🏗️ Fix #157' (#182) from add-future-classes into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai a0670fceac 🏗️ Fix #157
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 800c8dd8f8 Merge pull request '🐛 Prevent Die ctor from making dice with no face' (#140) from fix-#127 into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai ca3ec81ae6 🐛 Prevent Die ctor from making dice with no face
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 36005a73e3 Merge pull request ' Add a NumberFace test' (#128) from add-some-face-test into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 51d231a6e3 Add a NumberFace test
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai a80e52c7cd Merge pull request '♻️ Rename DieManager to DiceGroupManager' (#126) from rename-diemanager into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 1f2ca7483f ♻️ Rename DieManager to DiceGroupManager
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai a28149ec29 Merge pull request '♻️ Fix #120 add-gamemanager' (#125) from add-gamemanager into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai b21b78135e ♻️ Rename GameRunner to MasterOfCeremonies
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 08c1e415a9 ♻️ Refactor manager-y stuff into GameManager
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 52b91286b4 📝 Update README
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai d1c7f0674d Merge pull request '🐛 Throw appropriate exception when trying to update the Die collection itself' (#121) from debug-diemanager into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 0769b5116f 🐛 Throw appropriate exception when trying to update the Die collection itself
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 0a9ab6a39a Merge pull request 'finalize-db-ef-player' (#110) from finalize-db-ef-player into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 85a1b67dd6 📈 Shrink untested lines to make those metrics shine
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 5b4c2469c9 Test the DbStub a little bit too
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai d8fead1051 Fully test PlayerDbManager, and improve it
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 9a453da7bc 🔧 Disable nullable reference types in the Data project
2 years ago
Alexis Drai 1604fece2f :refactor: Use Guid.NewGuid() instead of hardcoding them
2 years ago
Alexis Drai c100c20a39 🔥 Improve some model UT
2 years ago
Alexis Drai 4632516d2c 🧐 Switch CLRF thing
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 912f6f6627 Add tests for GetOneById()
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 2fae748af9 👔 Implement PlayerDbManager
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 621d6c3bc2 ⚗️ Add features to prototype, test DB further
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 901a1128e9 🩹 Try-catch db connectionsin prototype
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai cfb495cd68 argh
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 722a4314fa ⚗️ Try things out, in accordance with wiki UML
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 90c1eba43b ✏️ Fix typo
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 6c03fc4586 ⚗️ Try things out
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai fe131e00a9 Merge pull request '🚚 Clean up our solution' (#107) from clean-up-solution into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 376c06c3cb 🚚 Clean up our solution
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 630ffa55f2 📝 Update README
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai b340fc7aab 📝 Update README
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai a987f36eac 🔥 Clean up imports
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai c28a36292a Merge pull request 'fix_die_and_faces_structure' (#106) from fix_die_and_faces_structure into main
continuous-integration/drone/push Build is passing Details
2 years ago
Ismail TAHA JANAN e49afa3b14 🩹 improve code
continuous-integration/drone/push Build is passing Details
2 years ago
Ismail TAHA JANAN 949521ff9b conflict solved
continuous-integration/drone/push Build is passing Details
2 years ago
Ismail TAHA JANAN f2eafe4ded fix merging conflict
continuous-integration/drone/push Build is passing Details
2 years ago
Ismail TAHA JANAN 3886fc7934 code with error
continuous-integration/drone/push Build is failing Details
2 years ago
Alexis Drai ebf8a9ac91 Merge pull request '🗃️ introduce-ef-persistence' (#104) from introduce-ef-persistence into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 3cb5b72041 Merge branch 'main' into introduce-ef-persistence
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai b8ea8a020e 🚚 Organize UTs in folders
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai ca6b62574e 🐛 Fix class mixup
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai fc6a71801f Get UTs done
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 937a57ec11 🔥 Shrink the debug program
2 years ago
Alexis Drai cb4851c0ca 🚨 Implement IManager
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 55436e15a9 🎨 🐛 fix bug in Remove, use affirmative statements, reformat
2 years ago
mohammad_zafir.jeeawody b84818610e validation method
2 years ago
Alexis Drai 23dbbbfb21 Update 'README.md'
2 years ago
Alexis Drai a0d3caf91c 💩 Include methods that throw NotSupportedExceptions for now
2 years ago
Alexis Drai 1683414231 🎨 🐛 fix bug in Remove, use affirmative statements, reformat
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 973b18b5d2 Merge pull request 'validation method' (#102) from die_manager_class into main
continuous-integration/drone/push Build is passing Details
2 years ago
mohammad_zafir.jeeawody 678d1f7de5 validation method
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai e14f5e01a6 🩹 Seal PlayerDBManager
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 2fa185829f ✏️ ♻️ Rename Faces and override GetRandomFace()
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 753789bcd4 🚧 Make data classes internal, start implementing DBManager
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai a65edca2fd 🩹 Make Program internal and static
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai c60935b793 Update 'README.md'
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 7c4c7bcfcb 🔧🗃️ Make Data/Program.cs executable, add Db stub, improve
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 8514606fa6 Juggle with dependencies to make it make sense
2 years ago
Alexis Drai 32d07e1e3c 🩹 Add default value for layer name
2 years ago
Alexis Drai f12997f1f8 ♻️ 🐛 🚨 Refactor URIs and handle URI exceptions in prototype
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai d4d587afb9 Simplify code and give more freedom
continuous-integration/drone/push Build is passing Details
2 years ago
Ismail TAHA JANAN 3a263b4dca simple commit
continuous-integration/drone/push Build is failing Details
2 years ago
Alexis Drai 6a47b33f44 🚧 WIP
continuous-integration/drone/push Build is passing Details
2 years ago
Ismail TAHA JANAN 1149c916c4 simple commit
continuous-integration/drone/push Build is failing Details
2 years ago
Alexis Drai e3687b206e 🚧 WIP on EF BP
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 6d418735d7 Merge pull request '💩 Include methods that throw NotSupportedExceptions for now' (#101) from fix-#89 into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai b0bb332f41 💩 Include methods that throw NotSupportedExceptions for now
continuous-integration/drone/push Build is passing Details
2 years ago
Ismail TAHA JANAN 6e6f999b09 simple commit
continuous-integration/drone/push Build is failing Details
2 years ago
Alexis Drai dad0302e56 Merge pull request '📝 Add comments and validation to DieManager' (#100) from add-comment-to-diemanager into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 55d1f95fd1 📝 Add comments and validation to DieManager
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 76ce25b986 Merge pull request 'implement-save-in-app' (#99) from implement-save-in-app into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai d5d659d30b ♻️ Improve stub by using GameRunner's methods where possible
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai ac6c6b0740 📝 Update comments a little bit
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 03bf800fe7 🐛 Use polymorphism
2 years ago
Alexis Drai 6019a50607 Yay
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai cc492550e6 Merge pull request 'fix-#28' (#98) from fix-#28 into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 5073793f92 ⚗️ Try without UT constructor
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 02abe1fbfd ⚗️ Hardcode the stub in UT class to test SonarQube
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai f99d4dd710 Merge pull request '♻️ Fix #28 Refactor UTs and hope that SonarQube updates coverage' (#96) from fix-#28 into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 006b35066d ♻️ Refactor UTs and hope that SonarQube updates coverage
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 0a99afd8c5 Merge pull request 'fix-#28' (#95) from fix-#28 into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 1bc1d96f33 Merge pull request '♻️ Remove extra methods, make Game.PlayerManager usable' (#94) from fix-game into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 455f1210ef Complete test suite, add checks to R and U
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 8d8c2f445a 💩
2 years ago
Alexis Drai 062c17b7f1 🚧 WIP
2 years ago
Alexis Drai 3c74fa4962 🚧 WIP
2 years ago
Alexis Drai c46d88029b ♻️ Remove extra methods, make Game.PlayerManager usable
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 0298360c6b Merge pull request 'fix-gamerunner-interface' (#92) from fix-gamerunner-interface into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 497534e722 📝 Update README
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 3c4df08f74 📝 Update README
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai c59eaceaff ♻️ Refactor CRUD on players and die into Manager properties
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 9d92f3b84f Make GameRunner implement the IManager interface
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 7e4f18f924 ✏️ Fix typo
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 787077f841 Fix #8
2 years ago
Alexis Drai 42a106c9b5 Merge pull request '⚗️ bang-out-dirty-prototype' (#91) from bang-out-dirty-prototype into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai b86cc38e1d Merge pull request '👔 fix-#16' (#90) from fix-#16 into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 796c599f3f 💚 Remove console prototype from the CI build
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai e0abed2590 🔥 Remove ption to use shorter hexcodes for colors
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 39ad4eadcd ⚗️ Bang out a quick and dirty console prototype
2 years ago
Alexis Drai 855d697572 🩹 Make method static, remove Load
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai daf468463c 👔 Finish GameRunner class for now
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai cccd21ca12 Merge pull request '🐛 fix-#87' (#88) from fix-#87 into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 2ee91862cf 🚧 WIP
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 8f42e7577e Add comments, break keyboard..?
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 295d43e3ce 🔥 Simplify adding turns
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 3631af4e8a 🚨 Seal Turn and implement IEquatable
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 9da428862f Override equals and hash, add UTs
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai a03c1f4526 Ensure unicity within Game.turns
2 years ago
Alexis Drai 2556b46192 Merge pull request '🎨 Improve die faces, expose Value, make use of inheritance' (#86) from improve-die-faces into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 82c36049f2 🎨 Improve die faces, expose Value, make use of inheritance
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai ccf8f222a0 Merge pull request ' Finish UT PlayerManager' (#84) from 100-percent-ut-playermanager into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 29251e01d4 Merge pull request 'fix-#30' (#83) from fix-#30 into main
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai a1590ede32 Finish UT PlayerManager
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai ff4d6eab9b Finish tests and fix #30
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai 2ea6feec0d Add UTs for Game, 🐛 debug nextIndex
continuous-integration/drone/push Build is passing Details
2 years ago
Alexis Drai f0e74ae517 🚧 WIP
continuous-integration/drone/push Build is failing Details
2 years ago
Alexis Drai 4d07c3ffc1 Set up instances for UTs
2 years ago

@ -9,6 +9,9 @@ trigger:
steps:
- name: build
image: mcr.microsoft.com/dotnet/sdk:6.0
volumes:
- name: docs
path: /docs
commands:
- cd Sources/
- dotnet restore DiceApp.sln
@ -41,4 +44,25 @@ steps:
# accessible en ligne de commande par $${PLUGIN_SONAR_TOKEN}
sonar_token:
from_secret: SECRET_SONAR_LOGIN
depends_on: [tests]
depends_on: [tests]
# La documentation Doxygen doit être dans le répertoire
# Documentation/doxygen
# avec le ficher
# Documentation/doxygen/Doxyfile contenant
# OUTPUT_DIRECTORY = /docs/doxygen
- name: generate-and-deploy-docs
image: hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-docdeployer
volumes:
- name: docs
path: /docs
commands:
- /entrypoint.sh
when:
branch:
- master
depends_on: [ build ]
volumes:
- name: docs
temp: {}

7
.gitignore vendored

@ -4,6 +4,13 @@
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# Migrations and DB files
# useful while the DB is still fluid, in the early stages
[Mm]igrations/
*.db
*.db-wal
*.db-shm
# User-specific files
*.rsuser
*.suo

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

File diff suppressed because it is too large Load Diff

@ -0,0 +1,8 @@
<html><body>
<p>
<hr size="1"/><address style="text-align: right;"><small>Generated on $datetime with &nbsp;
<img src="CodeFirst.png" alt="Code#0" align="middle" border="0" height="40px"/>
by Doxygen version $doxygenversion</small></address>
</p>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

@ -14,9 +14,47 @@
## To use the app
TLDR: you can't really
### Console prototype
Open the *DiceApp* solution and navigate to the *App* project. The *Program.cs* file has a `Main()` method that can be launched. It will soon be able to load a stub. Then you will be able to play a console prototype / perform functional tests. But it isn't ready yet.
Open the *DiceAppConsole.sln* solution and navigate to the *App* project. The *Program.cs* file has a `Main()` method that can be launched.
*If you simply load DiceApp.sln, Visual Studio will not load the App project...*
The console prototype loads a stub with a few small games that you can test, and you can create new everything (with a little patience).
### DiceApp DB context with stub
Still in *Program.cs*, we also now have a nacent data layer, using Entity Framework.
Open the *DiceAppConsole.sln* solution and navigate to the *App* project. The *Program.cs* file has a `Main()` method that can be launched.
The NuGet packages are managed in files that are versioned, so you shouldn't need to manage the dependencies yourself. *"The Line"* is taken care of too.
However, you do need to create the migrations and DB (and you probably should delete them everytime you want to reload).
First, in Visual Studio's terminal ("Developer PowerShell"), go to *DiceApp/Sources/Data*, and make sure Entity Framework is installed and / or updated.
```
cd Data
dotnet tool install --global dotnet-ef
dotnet tool update --global dotnet-ef
```
Now the migrations and DB. Since we have a `DiceAppDbContext` *and* and `DiceAppDbContextWithStub`, you will need to specify which one to use.
```
cd Data
dotnet ef migrations add dice_app_db --context DiceAppDbContextWithStub
dotnet ef database update --context DiceAppDbContextWithStub --startup-project ../App
```
Replace `DiceAppDbContextWithStub` with `DiceAppDbContext` if you want to launch an app with an empty DB.
You can now run the *App* program, and check out your local DB.
You may not want to read tables in the debug window -- in which case, just download [DB Brower for SQLite](https://sqlitebrowser.org/dl/) and open the *.db* file in it.
Ta-da.
#### Troubleshooting (VS vs .NET EF)
**If Visual Studio's embedded terminal refuses to recognize `dotnet ef`, try to fully close and reopen Visual Studio**
## To contribute (workflow)
@ -62,4 +100,9 @@ On [the repository's main page](https://codefirst.iut.uca.fr/git/alexis.drai/dic
It should then allow you to `merge into: ...:main` and `pull from: ...:new-feature`
Follow the platform's instructions, until you've made a "work in progress" (WIP) pull request. You can now assign reviewers among your colleagues. They will get familiar with your new code -- and will either accept the branch as it is, or help you arrange it.
Follow the platform's instructions, until you've made a "work in progress" (WIP) pull request. You can now assign reviewers among your colleagues. They will get familiar with your new code -- and will either accept the branch as it is, or help you arrange it.
## Known issues and limitations
### copies of games
As of now, this app does not allow making copies of a game. We're not trying to make a roguelike, it's just not considered to be a priority feature.

@ -1,13 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Data\Data.csproj" />
<ProjectReference Include="..\Model\Model.csproj" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<StartWorkingDirectory>$(MSBuildProjectDirectory)</StartWorkingDirectory>
</PropertyGroup>
<ItemGroup>
<Content Include="NLog.config">
<BuildAction>Content</BuildAction>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NLog" Version="5.0.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Data\Data.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
throwConfigExceptions="true">
<targets async="true">
<target name="logfile" xsi:type="File" fileName="log.txt" />
<target name="logdebug" xsi:type="Debug" />
</targets>
<rules>
<logger name="*" minlevel="Info" writeTo="logdebug"/>
<logger name="*" minlevel="Warn" writeTo="logfile"/>
</rules>
</nlog>

@ -1,16 +1,457 @@
using Data;
using Model.Games;
using System.Diagnostics;
namespace App
{
internal static class Program
{
static void Main(string[] args)
{
ILoader loader = new Stub();
GameRunner gameRunner = loader.LoadApp();
// use gameRunner to play
}
}
}
using Data;
using Data.EF;
using Data.EF.Players;
using Model.Dice;
using Model.Dice.Faces;
using Model.Games;
using Model.Players;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace App
{
internal static class Program
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
static async Task Main(string[] args)
{
// MODEL stuff
ILoader loader = new Stub();
MasterOfCeremonies masterOfCeremonies;
try
{
masterOfCeremonies = await loader.LoadApp();
}
catch (Exception ex)
{
logger.Warn(ex);
masterOfCeremonies = new(new PlayerManager(), new DiceGroupManager(), new GameManager());
}
try
{
// DB stuff when the app opens
using (DiceAppDbContext db = new())
{
// Later, we'll use the DiceAppDbContext to get a GameDbRunner
// get all the players from the DB
PlayerDbManager playerDbManager = new(db);
IEnumerable<PlayerEntity> entities = await playerDbManager.GetAll();
foreach (PlayerEntity entity in entities)
{
try
{
// persist them as models !
await masterOfCeremonies.GlobalPlayerManager.Add(entity.ToModel());
}
catch (Exception ex) { Debug.WriteLine($"{ex.Message}\n... Never mind"); }
}
}
}
catch (Exception ex) { Console.WriteLine($"{ex.Message}\n... Couldn't use the database"); }
string menuChoice = "nothing";
while (menuChoice != "q")
{
Console.WriteLine(
"l... load a game\n" +
"n... start new game\n" +
"d... delete a game\n" +
"i... see all dice\n" +
"c... create a group of dice\n" +
"p... see all players\n" +
"y... create players\n" +
"q... QUIT\n" +
">"
);
menuChoice = Console.ReadLine();
switch (menuChoice)
{
case "q":
break;
case "l":
string loadName = await ChooseGame(masterOfCeremonies);
if (masterOfCeremonies.GameManager.GetOneByName(loadName) != null)
{
await Play(masterOfCeremonies, loadName);
}
break;
case "n":
if (!(await masterOfCeremonies.DiceGroupManager.GetAll()).Any())
{
Console.WriteLine("make at least one dice group first, then try again");
break;
}
Console.WriteLine("add dice to the game");
IEnumerable<Die> newGameDice = await PrepareDice(masterOfCeremonies);
string newGameName;
Console.WriteLine("give this new game a name\n>");
newGameName = Console.ReadLine();
Console.WriteLine("add players to the game");
PlayerManager playerManager = await PreparePlayers(masterOfCeremonies);
await masterOfCeremonies.StartNewGame(newGameName, playerManager, newGameDice);
await Play(masterOfCeremonies, newGameName);
break;
case "d":
string deleteName = await ChooseGame(masterOfCeremonies);
masterOfCeremonies.GameManager.Remove(await masterOfCeremonies.GameManager.GetOneByName(deleteName));
break;
case "c":
string newGroupName;
Console.WriteLine("give this new dice group a name");
newGroupName = Console.ReadLine();
List<Die> newGroupDice = new();
string menuChoiceNewDice = "";
while (!(menuChoiceNewDice.Equals("ok") && newGroupDice.Any()))
{
Die die = null;
Console.WriteLine("create a die you want to add (at least one), or enter 'ok' if you're finished");
Console.WriteLine("what type of die ?\n" +
"n... number\n" +
"c... color\n" +
"i... image");
menuChoiceNewDice = Console.ReadLine();
switch (menuChoiceNewDice)
{
case "n":
die = MakeNumberDie();
break;
case "c":
die = MakeColorDie();
break;
case "i":
die = MakeImageDie();
break;
}
// almost no checks, this is temporary
if (die is not null)
{
newGroupDice.Add(die);
}
}
await masterOfCeremonies.DiceGroupManager.Add(new DiceGroup(newGroupName, newGroupDice));
break;
case "p":
await ShowPlayers(masterOfCeremonies);
break;
case "i":
await ShowDice(masterOfCeremonies);
break;
case "y":
await PreparePlayers(masterOfCeremonies);
break;
default:
Console.WriteLine("u wot m8?");
break;
}
}
try
{
// DB stuff when the app closes
using (DiceAppDbContext db = new())
{
// get all the players from the app's memory
IEnumerable<Player> models = await masterOfCeremonies.GlobalPlayerManager.GetAll();
// create a PlayerDbManager (and inject it with the DB)
PlayerDbManager playerDbManager = new(db);
foreach (Player model in models)
{
try // to persist them
{ // as entities !
PlayerEntity entity = model.ToEntity();
await playerDbManager.Add(entity);
}
// what if there's already a player with that name? Exception (see PlayerEntity's annotations)
catch (ArgumentException ex) { Debug.WriteLine($"{ex.Message}\n... Never mind"); }
}
}
// flushing and closing NLog before quitting completely
NLog.LogManager.Shutdown();
}
catch (Exception ex) { Console.WriteLine($"{ex.Message}\n... Couldn't use the database"); }
}
private static async Task Play(MasterOfCeremonies masterOfCeremonies, string name)
{
string menuChoicePlay = "";
while (menuChoicePlay != "q")
{
Game game = await masterOfCeremonies.GameManager.GetOneByName(name);
Console.WriteLine($"{PlayerToString(await game.GetWhoPlaysNow())}'s turn\n" +
"q... quit\n" +
"h... show history\n" +
"s... save\n" +
"any other... throw");
menuChoicePlay = Console.ReadLine();
switch (menuChoicePlay)
{
case "q":
break;
case "h":
foreach (Turn turn in game.GetHistory()) { Console.WriteLine(TurnToString(turn)); }
break;
case "s":
await masterOfCeremonies.GameManager.Add(game);
break;
default:
await MasterOfCeremonies.PlayGame(game);
Console.WriteLine(TurnToString(game.GetHistory().Last()));
break;
}
}
}
private static async Task<string> ChooseGame(MasterOfCeremonies masterOfCeremonies)
{
string name;
Console.WriteLine("which of these games?\n(choose by name)\n>");
foreach (Game game in await masterOfCeremonies.GameManager.GetAll())
{
Console.WriteLine(GameToString(game));
}
name = Console.ReadLine();
return name;
}
private static async Task ShowPlayers(MasterOfCeremonies masterOfCeremonies)
{
Console.WriteLine("Look at all them players!");
foreach (Player player in await masterOfCeremonies.GlobalPlayerManager.GetAll())
{
Console.WriteLine(PlayerToString(player));
}
}
private static async Task ShowDice(MasterOfCeremonies masterOfCeremonies)
{
foreach ((string name, ReadOnlyCollection<Die> dice) in await masterOfCeremonies.DiceGroupManager.GetAll())
{
Console.WriteLine($"{name} -- {dice}"); // maybe code a quick and dirty DieToString()
}
}
private static NumberDie MakeNumberDie()
{
NumberDie die;
List<NumberFace> faces = new();
string menuChoiceNewFaces = "";
while (menuChoiceNewFaces != "ok")
{
Console.WriteLine("create a face with a number, or enter 'ok' if you're finished");
menuChoiceNewFaces = Console.ReadLine();
PreventEmptyDieCreation(ref menuChoiceNewFaces, faces.Count);
if (!menuChoiceNewFaces.Equals("ok") && int.TryParse(menuChoiceNewFaces, out int num))
{
faces.Add(new(num));
}
}
NumberFace[] facesArr = faces.ToArray();
die = new NumberDie(facesArr[0], facesArr[1..]);
return die;
}
private static ColorDie MakeColorDie()
{
ColorDie die;
List<ColorFace> faces = new();
string menuChoiceNewFaces = "";
while (!menuChoiceNewFaces.Equals("ok"))
{
Console.WriteLine("create a face with an color name, or enter 'ok' if you're finished");
menuChoiceNewFaces = Console.ReadLine();
PreventEmptyDieCreation(ref menuChoiceNewFaces, faces.Count);
if (menuChoiceNewFaces != "ok") faces.Add(new(Color.FromName(menuChoiceNewFaces)));
}
ColorFace[] facesArr = faces.ToArray();
die = new ColorDie(facesArr[0], facesArr[1..]);
return die;
}
private static ImageDie MakeImageDie()
{
ImageDie die;
List<ImageFace> faces = new();
string menuChoiceNewFaces = "";
while (!menuChoiceNewFaces.Equals("ok"))
{
Console.WriteLine("create a face with an image uri, or enter 'ok' if you're finished");
menuChoiceNewFaces = Console.ReadLine();
PreventEmptyDieCreation(ref menuChoiceNewFaces, faces.Count);
if (menuChoiceNewFaces != "ok")
{
try
{
faces.Add(new(new Uri(menuChoiceNewFaces)));
}
catch (ArgumentNullException ex)
{
Console.WriteLine(ex.Message);
logger.Warn(ex);
}
catch (UriFormatException ex)
{
Console.WriteLine("that URI was not valid");
Console.WriteLine(ex.Message);
logger.Warn(ex);
}
}
}
ImageFace[] facesArr = faces.ToArray();
die = new ImageDie(facesArr[0], facesArr[1..]);
return die;
}
private static void PreventEmptyDieCreation(ref string menuChoice, int count)
{
if (menuChoice.Equals("ok") && count == 0)
{
Console.WriteLine("create at least one valid face");
menuChoice = ""; // persists outside the scope of this function
}
}
private async static Task<IEnumerable<Die>> PrepareDice(MasterOfCeremonies masterOfCeremonies)
{
List<Die> result = new();
Console.WriteLine("all known dice or groups of dice:");
await ShowDice(masterOfCeremonies);
string menuChoiceDice = "";
while (!(menuChoiceDice.Equals("ok") && result.Any()))
{
Console.WriteLine("write the name of a dice group you want to add (at least one), or 'ok' if you're finished");
menuChoiceDice = Console.ReadLine();
if (!menuChoiceDice.Equals("ok"))
{
IEnumerable<Die> chosenDice = (await masterOfCeremonies.DiceGroupManager.GetOneByName(menuChoiceDice)).Dice;
foreach (Die die in chosenDice)
{
result.Add(die);
}
}
}
return result.AsEnumerable();
}
private async static Task<PlayerManager> PreparePlayers(MasterOfCeremonies masterOfCeremonies)
{
PlayerManager result = new();
Console.WriteLine("all known players:");
await ShowPlayers(masterOfCeremonies);
string menuChoicePlayers = "";
while (!(menuChoicePlayers.Equals("ok") && (await result.GetAll()).Any()))
{
Console.WriteLine("write the name of a player you want to add (at least one), or 'ok' if you're finished");
menuChoicePlayers = Console.ReadLine();
if (!menuChoicePlayers.Equals("ok"))
{
Player player = new(menuChoicePlayers);
if (!(await masterOfCeremonies.GlobalPlayerManager.GetAll()).Contains(player))
{
// if the player didn't exist, now it does...
await masterOfCeremonies.GlobalPlayerManager.Add(player);
}
// almost no checks, this is temporary
try
{
await result.Add(player);
}
catch (ArgumentException ex) { Console.WriteLine($"{ex.Message}\n... Never mind"); }
}
}
return result;
}
private static string TurnToString(Turn turn)
{
string[] datetime = turn.When.ToString("s", System.Globalization.CultureInfo.InvariantCulture).Split("T");
string date = datetime[0];
string time = datetime[1];
StringBuilder sb = new();
sb.AppendFormat("{0} {1} -- {2} rolled:",
date,
time,
PlayerToString(turn.Player));
foreach (KeyValuePair<Die, Face> kvp in turn.DiceNFaces)
{
sb.Append(" " + kvp.Value.StringValue);
}
return sb.ToString();
}
private async static Task<string> GameToString(Game game)
{
StringBuilder sb = new();
sb.Append($"Game: {game.Name}");
sb.Append("\nPlayers:");
foreach (Player player in game.PlayerManager.GetAll()?.Result)
{
sb.Append($" {PlayerToString(player)}");
}
sb.Append($"\nNext: {PlayerToString(await game.GetWhoPlaysNow())}");
sb.Append("\nLog:\n");
foreach (Turn turn in game.GetHistory())
{
sb.Append($"\t{TurnToString(turn)}\n");
}
return sb.ToString();
}
private static string PlayerToString(Player player)
{
return player.Name;
}
}
}

@ -1,13 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<StartWorkingDirectory>$(MSBuildProjectDirectory)</StartWorkingDirectory>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Model\Model.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NLog" Version="5.0.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Model\Model.csproj" />
<ProjectReference Include="..\Utils\Utils\Utils.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,9 @@
using Data.EF.Dice.Faces;
namespace Data.EF.Dice
{
public class ColorDieEntity : DieEntity
{
public new ICollection<ColorFaceEntity> Faces { get; set; } = new List<ColorFaceEntity>();
}
}

@ -0,0 +1,35 @@
using Data.EF.Dice.Faces;
using Model.Dice;
using Model.Dice.Faces;
namespace Data.EF.Dice
{
public static class ColorDieExtensions
{
public static ColorDie ToModel(this ColorDieEntity dieEntity)
{
/*
* creating an array of faces model
*/
ColorFace[] faces = dieEntity.Faces.ToModels().ToArray();
/*
* creating the die
*/
ColorDie die = new(faces[0], faces[1..]);
return die;
}
public static IEnumerable<ColorDie> ToModels(this IEnumerable<ColorDieEntity> entities) => entities.Select(entity => entity.ToModel());
public static ColorDieEntity ToEntity(this ColorDie model)
{
var entity = new ColorDieEntity();
foreach (var face in model.Faces) { entity.Faces.Add(((ColorFace)face).ToEntity()); }
return entity;
}
public static IEnumerable<ColorDieEntity> ToEntities(this IEnumerable<ColorDie> models) => models.Select(model => model.ToEntity());
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Data.EF.Dice
{
public class DiceGroupDbManager
{
}
}

@ -0,0 +1,32 @@
using Data.EF.Dice.Faces;
using Data.EF.Games;
using Data.EF.Joins;
using System.Diagnostics.CodeAnalysis;
namespace Data.EF.Dice
{
/// <summary>
/// not designed to be instantiated, but not abstract in order to allow extensions
/// </summary>
///
public class DieEntity : IEqualityComparer<DieEntity>
{
public Guid ID { get; set; }
public ICollection<FaceEntity> Faces { get; set; } = new List<FaceEntity>(); // one to many
public ICollection<TurnEntity> Turns { get; set; } = new List<TurnEntity>(); // many to many
public List<DieTurn> DieTurns { get; set; } = new();
public bool Equals(DieEntity x, DieEntity y)
{
return x is not null
&& y is not null
&& x.ID.Equals(y.ID)
&& x.Faces.Equals(y.Faces);
}
public int GetHashCode([DisallowNull] DieEntity obj)
{
return HashCode.Combine(ID, Faces);
}
}
}

@ -0,0 +1,23 @@
using System.Drawing;
namespace Data.EF.Dice.Faces
{
public class ColorFaceEntity : FaceEntity
{
public byte A { get; set; }
public byte R { get; set; }
public byte G { get; set; }
public byte B { get; set; }
public Guid ColorDieEntityID { get; set; }
public ColorDieEntity ColorDieEntity { get; set; }
public void SetValue(Color c)
{
A = c.A;
R = c.R;
G = c.G;
B = c.B;
}
}
}

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model.Dice.Faces;
using System.Drawing;
namespace Data.EF.Dice.Faces
{
public static class ColorFaceExtensions
{
public static ColorFace ToModel(this ColorFaceEntity clrFaceEntity) => new(Color.FromArgb(clrFaceEntity.A, clrFaceEntity.R, clrFaceEntity.G, clrFaceEntity.B));
public static IEnumerable<ColorFace> ToModels(this IEnumerable<ColorFaceEntity> entities) => entities.Select(entity => entity.ToModel());
public static ColorFaceEntity ToEntity(this ColorFace model) => new() { A = model.Value.A, R = model.Value.R, G = model.Value.G, B = model.Value.B };
public static IEnumerable<ColorFaceEntity> ToEntities(this IEnumerable<ColorFace> models) => models.Select(model => model.ToEntity());
}
}

@ -0,0 +1,28 @@
using Data.EF.Games;
using Data.EF.Joins;
using System.Diagnostics.CodeAnalysis;
namespace Data.EF.Dice.Faces
{
/// <summary>
/// not designed to be instantiated, but not abstract in order to allow extensions
/// </summary>
public class FaceEntity : IEqualityComparer<FaceEntity>
{
public Guid ID { get; set; }
public ICollection<TurnEntity> Turns { get; set; } // many to many
public List<FaceTurn> FaceTurns { get; set; }
public bool Equals(FaceEntity x, FaceEntity y)
{
return x is not null
&& y is not null
&& x.ID.Equals(y.ID);
}
public int GetHashCode([DisallowNull] FaceEntity obj)
{
return ID.GetHashCode();
}
}
}

@ -0,0 +1,9 @@
namespace Data.EF.Dice.Faces
{
public class ImageFaceEntity : FaceEntity
{
public string Value { get; set; }
public Guid ImageDieEntityID { get; set; }
public ImageDieEntity ImageDieEntity { get; set; }
}
}

@ -0,0 +1,22 @@
using Data.EF.Players;
using Model.Dice.Faces;
using Model.Players;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Data.EF.Dice.Faces
{
public static class ImageFaceExtensions
{
public static ImageFace ToModel(this ImageFaceEntity entity) => new(new Uri(entity.Value));
public static IEnumerable<ImageFace> ToModels(this IEnumerable<ImageFaceEntity> entities) => entities.Select(entity => entity.ToModel());
public static ImageFaceEntity ToEntity(this ImageFace model) => new() { Value = model.StringValue };
public static IEnumerable<ImageFaceEntity> ToEntities(this IEnumerable<ImageFace> models) => models.Select(model => model.ToEntity());
}
}

@ -0,0 +1,9 @@
namespace Data.EF.Dice.Faces
{
public class NumberFaceEntity : FaceEntity
{
public int Value { get; set; }
public Guid NumberDieEntityID { get; set; }
public NumberDieEntity NumberDieEntity { get; set; }
}
}

@ -0,0 +1,20 @@
using Model.Dice.Faces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Data.EF.Dice.Faces
{
public static class NumberFaceExtensions
{
public static NumberFace ToModel(this NumberFaceEntity entity) => new(entity.Value);
public static IEnumerable<NumberFace> ToModels(this IEnumerable<NumberFaceEntity> entities) => entities.Select(entity => entity.ToModel());
public static NumberFaceEntity ToEntity(this NumberFace model) => new() { Value = model.Value };
public static IEnumerable<NumberFaceEntity> ToEntities(this IEnumerable<NumberFace> models) => models.Select(model => model.ToEntity());
}
}

@ -0,0 +1,9 @@
using Data.EF.Dice.Faces;
namespace Data.EF.Dice
{
public class ImageDieEntity : DieEntity
{
public new ICollection<ImageFaceEntity> Faces { get; set; } = new List<ImageFaceEntity>();
}
}

@ -0,0 +1,35 @@
using Data.EF.Dice.Faces;
using Model.Dice;
using Model.Dice.Faces;
namespace Data.EF.Dice
{
public static class ImageDieExtensions
{
public static ImageDie ToModel(this ImageDieEntity dieEntity)
{
/*
* creating an array of faces model
*/
ImageFace[] faces = dieEntity.Faces.ToModels().ToArray();
/*
* creating the die
*/
ImageDie die = new(faces[0], faces[1..]);
return die;
}
public static IEnumerable<ImageDie> ToModels(this IEnumerable<ImageDieEntity> entities) => entities.Select(entity => entity.ToModel());
public static ImageDieEntity ToEntity(this ImageDie model)
{
var entity = new ImageDieEntity();
foreach (var face in model.Faces) { entity.Faces.Add(((ImageFace)face).ToEntity()); }
return entity;
}
public static IEnumerable<ImageDieEntity> ToEntities(this IEnumerable<ImageDie> models) => models.Select(model => model.ToEntity());
}
}

@ -0,0 +1,9 @@
using Data.EF.Dice.Faces;
namespace Data.EF.Dice
{
public class NumberDieEntity : DieEntity
{
public new ICollection<NumberFaceEntity> Faces { get; set; } = new List<NumberFaceEntity>();
}
}

@ -0,0 +1,35 @@
using Data.EF.Dice.Faces;
using Model.Dice;
using Model.Dice.Faces;
namespace Data.EF.Dice
{
public static class NumberDieExtensions
{
public static NumberDie ToModel(this NumberDieEntity dieEntity)
{
/*
* creating an array of faces model
*/
NumberFace[] faces = dieEntity.Faces.ToModels().ToArray();
/*
* creating the die
*/
NumberDie die = new(faces[0], faces[1..]);
return die;
}
public static IEnumerable<NumberDie> ToModels(this IEnumerable<NumberDieEntity> entities) => entities.Select(entity => ToModel(entity));
public static NumberDieEntity ToEntity(this NumberDie model)
{
var entity = new NumberDieEntity();
foreach (var face in model.Faces) { entity.Faces.Add(((NumberFace)face).ToEntity()); }
return entity;
}
public static IEnumerable<NumberDieEntity> ToEntities(this IEnumerable<NumberDie> models) => models.Select(model => model.ToEntity());
}
}

@ -0,0 +1,82 @@
using Data.EF.Dice;
using Data.EF.Dice.Faces;
using Data.EF.Games;
using Data.EF.Joins;
using Data.EF.Players;
using Microsoft.EntityFrameworkCore;
using Model.Games;
namespace Data.EF
{
public class DiceAppDbContext : DbContext, ILoader
{
// will be async!
public virtual Task<MasterOfCeremonies> LoadApp() { throw new NotImplementedException(); }
public DbSet<PlayerEntity> PlayerEntity { get; set; }
public DbSet<TurnEntity> TurnEntity { get; set; }
public DbSet<NumberDieEntity> NumberDieEntity { get; set; }
public DbSet<NumberFaceEntity> NumberFaceEntity { get; set; }
public DbSet<ImageDieEntity> ImageDieEntity { get; set; }
public DbSet<ImageFaceEntity> ImageFaceEntity { get; set; }
public DbSet<ColorDieEntity> ColorDieEntity { get; set; }
public DbSet<ColorFaceEntity> ColorFaceEntity { get; set; }
public DiceAppDbContext() { }
public DiceAppDbContext(DbContextOptions<DiceAppDbContext> options)
: base(options) { }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured) optionsBuilder.UseSqlite("Data Source=EFDice.DiceApp.db").EnableSensitiveDataLogging();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// thanks to https://learn.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#join-entity-type-configuration
// many to many TurnEntity <-> FaceEntity
modelBuilder.Entity<FaceEntity>()
.HasMany(face => face.Turns)
.WithMany(turn => turn.Faces)
.UsingEntity<FaceTurn>(
join => join
// FaceTurn --> TurnEntity
.HasOne(faceturn => faceturn.TurnEntity)
.WithMany(turn => turn.FaceTurns)
.HasForeignKey(faceturn => faceturn.TurnEntityID),
join => join
// FaceTurn --> FaceEntity
.HasOne(faceturn => faceturn.FaceEntity)
.WithMany(face => face.FaceTurns)
.HasForeignKey(faceturn => faceturn.FaceEntityID),
// FaceTurn.PK = (ID1, ID2)
join => join.HasKey(faceturn => new { faceturn.FaceEntityID, faceturn.TurnEntityID })
);
// many to many TurnEntity <-> DieEntity
modelBuilder.Entity<DieEntity>()
.HasMany(die => die.Turns)
.WithMany(turn => turn.Dice)
.UsingEntity<DieTurn>(
join => join
// DieTurn --> TurnEntity
.HasOne(dieturn => dieturn.TurnEntity)
.WithMany(turn => turn.DieTurns)
.HasForeignKey(dieturn => dieturn.TurnEntityID),
join => join
// DieTurn --> DieEntity
.HasOne(dieturn => dieturn.DieEntity)
.WithMany(die => die.DieTurns)
.HasForeignKey(dieturn => dieturn.DieEntityID),
// DieTurn.PK = (ID1, ID2)
join => join.HasKey(dieturn => new { dieturn.DieEntityID, dieturn.TurnEntityID })
);
}
}
}

@ -0,0 +1,112 @@
using Data.EF.Dice;
using Data.EF.Dice.Faces;
using Data.EF.Games;
using Data.EF.Joins;
using Data.EF.Players;
using Microsoft.EntityFrameworkCore;
using Model.Games;
using System.Drawing;
using System.Linq.Expressions;
using System.Security.Cryptography.X509Certificates;
namespace Data.EF
{
public class DiceAppDbContextWithStub : DiceAppDbContext
{
// will be async
public override Task<MasterOfCeremonies> LoadApp() { throw new NotImplementedException(); }
public DiceAppDbContextWithStub() { }
public DiceAppDbContextWithStub(DbContextOptions<DiceAppDbContext> options)
: base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
Guid playerID_1 = Guid.NewGuid();
Guid playerID_2 = new("6e856818-92f1-4d7d-b35c-f9c6687ef8e1");
Guid playerID_3 = Guid.NewGuid();
Guid playerID_4 = Guid.NewGuid();
PlayerEntity player_1 = new() { ID = playerID_1, Name = "Alice" };
PlayerEntity player_2 = new() { ID = playerID_2, Name = "Bob" };
PlayerEntity player_3 = new() { ID = playerID_3, Name = "Clyde" };
PlayerEntity player_4 = new() { ID = playerID_4, Name = "Dahlia" };
Guid turnID_1 = Guid.NewGuid();
Guid turnID_2 = Guid.NewGuid();
DateTime datetime_1 = new(2017, 1, 6, 17, 30, 0, DateTimeKind.Utc);
TurnEntity turn_1 = new() { ID = turnID_1, When = datetime_1, PlayerEntityID = playerID_1 };
TurnEntity turn_2 = new() { ID = turnID_2, When = DateTime.UtcNow, PlayerEntityID = playerID_2 };
Guid dieID_1 = Guid.NewGuid();
Guid dieID_2 = Guid.NewGuid();
Guid dieID_3 = Guid.NewGuid();
NumberDieEntity die_1 = new() { ID = dieID_1 };
ImageDieEntity die_2 = new() { ID = dieID_2 };
ColorDieEntity die_3 = new() { ID = dieID_3 };
Guid faceID_1 = Guid.NewGuid();
Guid faceID_2 = Guid.NewGuid();
Guid faceID_3 = Guid.NewGuid();
Guid faceID_4 = Guid.NewGuid();
Guid faceID_5 = Guid.NewGuid();
Guid faceID_6 = Guid.NewGuid();
NumberFaceEntity face_1 = new() { ID = faceID_1, Value = 1, NumberDieEntityID = dieID_1 };
NumberFaceEntity face_2 = new() { ID = faceID_2, Value = 2, NumberDieEntityID = dieID_1 };
ImageFaceEntity face_3 = new() { ID = faceID_3, Value = "https://1", ImageDieEntityID = dieID_2 };
ImageFaceEntity face_4 = new() { ID = faceID_4, Value = "https://2", ImageDieEntityID = dieID_2 };
ColorFaceEntity face_5 = new() { ID = faceID_5, ColorDieEntityID = dieID_3 };
face_5.SetValue(Color.FromArgb(255, 255, 0, 0));
ColorFaceEntity face_6 = new() { ID = faceID_6, ColorDieEntityID = dieID_3 };
face_6.SetValue(Color.FromName("green"));
modelBuilder.Entity<PlayerEntity>().HasData(player_1, player_2, player_3, player_4);
modelBuilder.Entity<TurnEntity>().HasData(turn_1, turn_2);
modelBuilder.Entity<NumberDieEntity>().HasData(die_1);
modelBuilder.Entity<NumberFaceEntity>().HasData(face_1, face_2);
modelBuilder.Entity<ImageDieEntity>().HasData(die_2);
modelBuilder.Entity<ImageFaceEntity>().HasData(face_3, face_4);
modelBuilder.Entity<ColorDieEntity>().HasData(die_3);
modelBuilder.Entity<ColorFaceEntity>().HasData(face_5, face_6);
// die 1 die 2 die 3
// turn 1 : num->2, img->https://2, clr->red
// turn 2 : num->1, clr->green
modelBuilder
.Entity<TurnEntity>()
.HasMany(turn => turn.Faces)
.WithMany(face => face.Turns)
.UsingEntity<FaceTurn>(join => join.HasData(
new { TurnEntityID = turnID_1, FaceEntityID = faceID_2 },
new { TurnEntityID = turnID_1, FaceEntityID = faceID_4 },
new { TurnEntityID = turnID_1, FaceEntityID = faceID_5 },
new { TurnEntityID = turnID_2, FaceEntityID = faceID_1 },
new { TurnEntityID = turnID_2, FaceEntityID = faceID_6 }));
modelBuilder
.Entity<TurnEntity>()
.HasMany(turn => turn.Dice)
.WithMany(die => die.Turns)
.UsingEntity<DieTurn>(join => join.HasData(
new { TurnEntityID = turnID_1, DieEntityID = dieID_1 },
new { TurnEntityID = turnID_1, DieEntityID = dieID_2 },
new { TurnEntityID = turnID_1, DieEntityID = dieID_3 },
new { TurnEntityID = turnID_2, DieEntityID = dieID_1 },
new { TurnEntityID = turnID_2, DieEntityID = dieID_3 }));
}
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Data.EF.Games
{
public class GameDbManager
{
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Data.EF.Games
{
public class GameEntity
{
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Data.EF.Games
{
public static class GameExtensions
{
}
}

@ -0,0 +1,88 @@
using Data.EF.Dice;
using Data.EF.Dice.Faces;
using Data.EF.Joins;
using Data.EF.Players;
using Model.Players;
namespace Data.EF.Games
{
public sealed class TurnEntity : IEquatable<TurnEntity>
{
public Guid ID { get; set; }
public DateTime When { get; set; }
public PlayerEntity PlayerEntity { get; set; }
public Guid PlayerEntityID { get; set; }
public ICollection<DieEntity> Dice { get; set; } = new List<DieEntity>(); // many to many
public List<DieTurn> DieTurns { get; set; } = new();
public ICollection<FaceEntity> Faces { get; set; } = new List<FaceEntity>(); // many to many
public List<FaceTurn> FaceTurns { get; set; } = new();
public override bool Equals(object obj)
{
if (obj is not TurnEntity)
{
return false;
}
return Equals(obj as TurnEntity);
}
public bool Equals(TurnEntity other)
{
if (other is null
||
!(PlayerEntity.Equals(other.PlayerEntity)
&& When.Equals(other.When)
&& ID.Equals(other.ID)
&& Dice.Count == other.Dice.Count
&& Faces.Count == other.Faces.Count))
{
return false;
}
for (int i = 0; i < Faces.Count; i++)
{
if (Dice.ElementAt(i).Faces.Count
!= other.Dice.ElementAt(i).Faces.Count)
{
return false;
}
if (!other.Faces.ElementAt(i).ID
.Equals(Faces.ElementAt(i).ID))
{
return false;
}
for (int j = 0; j < Dice.ElementAt(i).Faces.Count; j++)
{
if (!other.Dice.ElementAt(i).Faces.ElementAt(j).ID
.Equals(Dice.ElementAt(i).Faces.ElementAt(j).ID))
{
return false;
}
}
}
return true;
}
public override int GetHashCode()
{
int result = HashCode.Combine(
ID,
When,
PlayerEntity);
foreach (DieEntity die in Dice)
{
result += die.GetHashCode();
}
foreach (FaceEntity face in Faces)
{
result += face.GetHashCode();
}
return result;
}
}
}

@ -0,0 +1,68 @@
using Data.EF.Dice;
using Data.EF.Dice.Faces;
using Data.EF.Players;
using Model.Dice;
using Model.Dice.Faces;
using Model.Games;
namespace Data.EF.Games
{
public static class TurnExtensions
{
private static (List<Die>, List<Face>) ToModelsByTypes(ICollection<DieEntity> diceEntities, ICollection<FaceEntity> faceEntities)
{
List<Die> dice = new();
List<Face> faces = new();
foreach (DieEntity dieEntity in diceEntities)
{
if (dieEntity.GetType() == typeof(NumberDieEntity)) { dice.Add((dieEntity as NumberDieEntity).ToModel()); }
if (dieEntity.GetType() == typeof(ColorDieEntity)) { dice.Add((dieEntity as ColorDieEntity).ToModel()); }
if (dieEntity.GetType() == typeof(ImageDieEntity)) { dice.Add((dieEntity as ImageDieEntity).ToModel()); }
}
foreach (FaceEntity faceEntity in faceEntities)
{
if (faceEntity.GetType() == typeof(NumberFaceEntity)) { faces.Add((faceEntity as NumberFaceEntity).ToModel()); }
if (faceEntity.GetType() == typeof(ColorFaceEntity)) { faces.Add((faceEntity as ColorFaceEntity).ToModel()); }
if (faceEntity.GetType() == typeof(ImageFaceEntity)) { faces.Add((faceEntity as ImageFaceEntity).ToModel()); }
}
return (dice, faces);
}
public static Turn ToModel(this TurnEntity entity)
{
Dictionary<Die, Face> DiceNFaces = new();
List<Die> keysList;
List<Face> valuesList;
(keysList, valuesList) = ToModelsByTypes(entity.Dice, entity.Faces);
DiceNFaces = Utils.Enumerables.FeedListsToDict(DiceNFaces, keysList, valuesList);
return Turn.CreateWithSpecifiedTime(when: entity.When, player: entity.PlayerEntity.ToModel(), diceNFaces: DiceNFaces);
}
public static IEnumerable<Turn> ToModels(this IEnumerable<TurnEntity> entities) => entities.Select(entity => entity.ToModel());
public static TurnEntity ToEntity(this Turn model)
{
List<DieEntity> DiceEntities = new();
List<FaceEntity> FaceEntities = new();
foreach (KeyValuePair<Die, Face> kvp in model.DiceNFaces)
{
if (kvp.Key.GetType() == typeof(NumberDie)) { DiceEntities.Add((kvp.Key as NumberDie).ToEntity()); FaceEntities.Add((kvp.Value as NumberFace).ToEntity()); }
if (kvp.Key.GetType() == typeof(ImageDie)) { DiceEntities.Add((kvp.Key as ImageDie).ToEntity()); FaceEntities.Add((kvp.Value as ImageFace).ToEntity()); }
if (kvp.Key.GetType() == typeof(ColorDie)) { DiceEntities.Add((kvp.Key as ColorDie).ToEntity()); FaceEntities.Add((kvp.Value as ColorFace).ToEntity()); }
}
return new TurnEntity() { When = model.When, PlayerEntity = model.Player.ToEntity(), Dice = DiceEntities, Faces = FaceEntities };
}
public static IEnumerable<TurnEntity> ToEntities(this IEnumerable<Turn> models) => models.Select(model => model.ToEntity());
}
}

@ -0,0 +1,15 @@
using Data.EF.Dice;
using Data.EF.Dice.Faces;
using Data.EF.Games;
namespace Data.EF.Joins
{
public class DieTurn
{
public Guid DieEntityID { get; set; }
public DieEntity DieEntity { get; set; }
public Guid TurnEntityID { get; set; }
public TurnEntity TurnEntity { get; set; }
}
}

@ -0,0 +1,19 @@
using Data.EF.Dice.Faces;
using Data.EF.Games;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Data.EF.Joins
{
public class FaceTurn
{
public Guid FaceEntityID { get; set; }
public FaceEntity FaceEntity { get; set; }
public Guid TurnEntityID { get; set; }
public TurnEntity TurnEntity { get; set; }
}
}

@ -0,0 +1,186 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Model;
using System.Collections.ObjectModel;
namespace Data.EF.Players
{
public sealed class PlayerDbManager : IManager<PlayerEntity>
{
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private readonly DiceAppDbContext db;
public PlayerDbManager(DiceAppDbContext db)
{
if (db is null)
{
ArgumentNullException ex = new(nameof(db), "param should not be null");
logger.Error(ex, "attempted to construct PlayerDbManager with a null context");
throw ex;
}
this.db = db;
}
/// <summary>
/// side effect: entity's name is trimmed.
/// </summary>
/// <param name="entity"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
private static void CleanPlayerEntity(PlayerEntity entity)
{
if (entity is null)
{
ArgumentNullException ex = new(nameof(entity), "param should not be null");
logger.Warn(ex);
throw ex;
}
if (string.IsNullOrWhiteSpace(entity.Name))
{
ArgumentException ex = new("Name property should not be null or whitespace", nameof(entity));
logger.Warn(ex);
throw ex;
}
entity.Name = entity.Name.Trim();
}
/// <summary>
/// adds a non-null PlayerEntity with a valid name to this mgr's context
/// </summary>
/// <param name="toAdd">the entity to add</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public Task<PlayerEntity> Add(PlayerEntity toAdd)
{
CleanPlayerEntity(toAdd);
if (db.PlayerEntity.Where(entity => entity.Name == toAdd.Name).Any())
{
ArgumentException ex = new("this username is already taken", nameof(toAdd));
logger.Warn(ex);
throw ex;
}
return InternalAdd(toAdd);
}
private async Task<PlayerEntity> InternalAdd(PlayerEntity toAdd)
{
EntityEntry ee = await db.PlayerEntity.AddAsync(toAdd);
await db.SaveChangesAsync();
logger.Info("Added {0}", ee.Entity.ToString());
return (PlayerEntity)ee.Entity;
}
public async Task<ReadOnlyCollection<PlayerEntity>> GetAll()
{
List<PlayerEntity> players = new();
await Task.Run(() => players.AddRange(db.PlayerEntity));
return new ReadOnlyCollection<PlayerEntity>(players);
}
/// <summary>
/// This will throw an exception if no player with such name exists.
/// If you want to know whether any player with that name exists, call IsPresentByName()
/// </summary>
/// <param name="name"></param>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="InvalidOperationException"></exception>
/// <returns></returns>
public Task<PlayerEntity> GetOneByName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("Name property should not be null or whitespace", nameof(name));
}
name = name.Trim();
return InternalGetOneByName(name);
}
private async Task<PlayerEntity> InternalGetOneByName(string name)
{
return await db.PlayerEntity.Where(p => p.Name == name).FirstAsync();
}
public async Task<bool> IsPresentByName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return false;
}
name = name.Trim();
return await db.PlayerEntity.Where(p => p.Name == name).FirstOrDefaultAsync() is not null;
}
/// <summary>
/// removes a non-null PlayerEntity with a valid name from this mgr's context
/// </summary>
/// <param name="toRemove">the entity to remove</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public void Remove(PlayerEntity toRemove)
{
CleanPlayerEntity(toRemove);
bool isPresent = IsPresentByID(toRemove.ID).Result;
if (isPresent)
{
db.PlayerEntity.Remove(toRemove);
logger.Info("Removed {0}", toRemove.ToString());
db.SaveChanges();
}
}
/// <summary>
/// updates a non-null PlayerEntity with a valid name in this mgr's context. This cannot update an ID
/// </summary>
/// <param name="before">the entity to update</param>
/// <param name="after">the entity to replace 'before'</param>
/// <returns>the updated entity</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public Task<PlayerEntity> Update(PlayerEntity before, PlayerEntity after)
{
PlayerEntity[] args = { before, after };
foreach (PlayerEntity entity in args)
{
CleanPlayerEntity(entity);
}
if (before.ID != after.ID)
{
ArgumentException ex = new("ID cannot be updated", nameof(after));
logger.Warn(ex);
throw ex;
}
return InternalUpdate(before, after);
}
private async Task<PlayerEntity> InternalUpdate(PlayerEntity before, PlayerEntity after)
{
Remove(before);
return await Add(after);
}
/// <summary>
/// This will throw an exception if no player with such ID exists.
/// If you want to know whether any player with that ID exists, call IsPresentByID()
/// </summary>
/// <param name="ID">the ID to look for</param>
/// <returns>PlayerEntity with that ID</returns>
/// <exception cref="InvalidOperationException"></exception>
public async Task<PlayerEntity> GetOneByID(Guid ID)
{
return await db.PlayerEntity.FirstAsync(p => p.ID == ID);
}
public async Task<bool> IsPresentByID(Guid ID)
{
return await db.PlayerEntity.FirstOrDefaultAsync(p => p.ID == ID) is not null;
}
}
}

@ -0,0 +1,186 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Model;
using System.Collections.ObjectModel;
namespace Data.EF.Players
{
public sealed class PlayerDbManager : IManager<PlayerEntity>
{
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private readonly DiceAppDbContext db;
public PlayerDbManager(DiceAppDbContext db)
{
if (db is null)
{
ArgumentNullException ex = new(nameof(db), "param should not be null");
logger.Error(ex, "attempted to construct PlayerDbManager with a null context");
throw ex;
}
this.db = db;
}
/// <summary>
/// side effect: entity's name is trimmed.
/// </summary>
/// <param name="entity"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
private static void CleanPlayerEntity(PlayerEntity entity)
{
if (entity is null)
{
ArgumentNullException ex = new(nameof(entity), "param should not be null");
logger.Warn(ex);
throw ex;
}
if (string.IsNullOrWhiteSpace(entity.Name))
{
ArgumentException ex = new("Name property should not be null or whitespace", nameof(entity));
logger.Warn(ex);
throw ex;
}
entity.Name = entity.Name.Trim();
}
/// <summary>
/// adds a non-null PlayerEntity with a valid name to this mgr's context
/// </summary>
/// <param name="toAdd">the entity to add</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public Task<PlayerEntity> Add(PlayerEntity toAdd)
{
CleanPlayerEntity(toAdd);
if (db.PlayerEntity.Where(entity => entity.Name == toAdd.Name).Any())
{
ArgumentException ex = new("this username is already taken", nameof(toAdd));
logger.Warn(ex);
throw ex;
}
return InternalAdd(toAdd);
}
private async Task<PlayerEntity> InternalAdd(PlayerEntity toAdd)
{
EntityEntry ee = await db.PlayerEntity.AddAsync(toAdd);
await db.SaveChangesAsync();
logger.Info("Added {0}", ee.Entity.ToString());
return (PlayerEntity)ee.Entity;
}
public async Task<ReadOnlyCollection<PlayerEntity>> GetAll()
{
List<PlayerEntity> players = new();
await Task.Run(() => players.AddRange(db.PlayerEntity));
return new ReadOnlyCollection<PlayerEntity>(players);
}
/// <summary>
/// This will throw an exception if no player with such name exists.
/// If you want to know whether any player with that name exists, call IsPresentByName()
/// </summary>
/// <param name="name"></param>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="InvalidOperationException"></exception>
/// <returns></returns>
public Task<PlayerEntity> GetOneByName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("Name property should not be null or whitespace", nameof(name));
}
name = name.Trim();
return InternalGetOneByName(name);
}
private async Task<PlayerEntity> InternalGetOneByName(string name)
{
return await db.PlayerEntity.Where(p => p.Name == name).FirstAsync();
}
public async Task<bool> IsPresentByName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return false;
}
name = name.Trim();
return await db.PlayerEntity.Where(p => p.Name == name).FirstOrDefaultAsync() is not null;
}
/// <summary>
/// removes a non-null PlayerEntity with a valid name from this mgr's context
/// </summary>
/// <param name="toRemove">the entity to remove</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public void Remove(PlayerEntity toRemove)
{
CleanPlayerEntity(toRemove);
bool isPresent = IsPresentByID(toRemove.ID).Result;
if (isPresent)
{
db.PlayerEntity.Remove(toRemove);
logger.Info("Removed {0}", toRemove.ToString());
db.SaveChanges();
}
}
/// <summary>
/// updates a non-null PlayerEntity with a valid name in this mgr's context. This cannot update an ID
/// </summary>
/// <param name="before">the entity to update</param>
/// <param name="after">the entity to replace 'before'</param>
/// <returns>the updated entity</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public Task<PlayerEntity> Update(PlayerEntity before, PlayerEntity after)
{
PlayerEntity[] args = { before, after };
foreach (PlayerEntity entity in args)
{
CleanPlayerEntity(entity);
}
if (before.ID != after.ID)
{
ArgumentException ex = new("ID cannot be updated", nameof(after));
logger.Warn(ex);
throw ex;
}
return InternalUpdate(before, after);
}
private async Task<PlayerEntity> InternalUpdate(PlayerEntity before, PlayerEntity after)
{
Remove(before);
return await Add(after);
}
/// <summary>
/// This will throw an exception if no player with such ID exists.
/// If you want to know whether any player with that ID exists, call IsPresentByID()
/// </summary>
/// <param name="ID">the ID to look for</param>
/// <returns>PlayerEntity with that ID</returns>
/// <exception cref="InvalidOperationException"></exception>
public async Task<PlayerEntity> GetOneByID(Guid ID)
{
return await db.PlayerEntity.FirstAsync(p => p.ID == ID);
}
public async Task<bool> IsPresentByID(Guid ID)
{
return await db.PlayerEntity.FirstOrDefaultAsync(p => p.ID == ID) is not null;
}
}
}

@ -0,0 +1,41 @@
using Data.EF.Games;
using Microsoft.EntityFrameworkCore;
namespace Data.EF.Players
{
[Index(nameof(Name), IsUnique = true)]
public sealed class PlayerEntity : IEquatable<PlayerEntity>
{
public Guid ID { get; set; }
public string Name { get; set; }
public ICollection<TurnEntity> Turns { get; set; } = new List<TurnEntity>();
public override bool Equals(object obj)
{
if (obj is not PlayerEntity)
{
return false;
}
return Equals(obj as PlayerEntity);
}
public bool Equals(PlayerEntity other)
{
return other is not null && this.ID.Equals(other.ID) && this.Name.Equals(other.Name);
}
public override int GetHashCode()
{
return HashCode.Combine(ID, Name);
}
public override string ToString()
{
return $"{ID.ToString().ToUpper()} -- {Name}";
}
}
}

@ -0,0 +1,15 @@
using Model.Players;
namespace Data.EF.Players
{
public static class PlayerExtensions
{
public static Player ToModel(this PlayerEntity entity) => new(name: entity.Name);
public static IEnumerable<Player> ToModels(this IEnumerable<PlayerEntity> entities) => entities.Select(entity => entity.ToModel());
public static PlayerEntity ToEntity(this Player model) => new() { Name = model.Name };
public static IEnumerable<PlayerEntity> ToEntities(this IEnumerable<Player> models) => models.Select(model => model.ToEntity());
}
}

@ -1,14 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model.Games;
using Model.Games;
namespace Data
{
public interface ILoader
{
public GameRunner LoadApp();
public Task<MasterOfCeremonies> LoadApp();
}
}

@ -1,129 +1,98 @@
using Model;
using Model.Dice;
using Model.Dice;
using Model.Dice.Faces;
using Model.Games;
using Model.Players;
using System.Collections.Generic;
using System.Drawing;
namespace Data
{
public class Stub : ILoader
{
// when the other classes are ready
// the Stub should just make and return a GameRunner, and the GameRunner should have
// a PlayerManager, a collection of Games, a FavGroupManager, etc. (see diagram)
public GameRunner LoadApp()
public async Task<MasterOfCeremonies> LoadApp()
{
string g1 = "game1", g2 = "game2", g3 = "game3";
MasterOfCeremonies mc = new(new PlayerManager(), new DiceGroupManager(), new GameManager());
Player player1 = new("Alice"), player2 = new("Bob"), player3 = new("Clyde");
Player player1 = new("Alice(Old Stub)"), player2 = new("Bob(Old Stub)"), player3 = new("Clyde(Old Stub)");
IManager<KeyValuePair<string, IEnumerable<AbstractDie<AbstractDieFace>>>> globalDieManager = new DieManager();
// create at least one group in there
// ...
await mc.GlobalPlayerManager.Add(player1);
await mc.GlobalPlayerManager.Add(player2);
await mc.GlobalPlayerManager.Add(player3);
IEnumerable<AbstractDie<AbstractDieFace>> dice1 = globalDieManager.GetAll().First().Value;
IEnumerable<AbstractDie<AbstractDieFace>> dice2 = globalDieManager.GetAll().Last().Value;
Game game1 = new(name: g1, playerManager: new PlayerManager(), dice: dice1);
Game game2 = new(name: g2, playerManager: new PlayerManager(), dice: dice2);
Game game3 = new(name: g3, playerManager: new PlayerManager(), dice: dice1);
List<Die> monopolyDice = new();
List<Die> dndDice = new();
List<Game> games = new() { game1, game2, game3 };
string monopolyName = "Monopoly", dndName = "DnD";
PlayerManager globalPlayerManager = new();
globalPlayerManager.Add(player1);
globalPlayerManager.Add(player2);
globalPlayerManager.Add(player3);
NumberFace[] d6Faces = new NumberFace[] { new(1), new(2), new(3), new(4), new(5), new(6) };
GameRunner gameRunner = new(globalPlayerManager, globalDieManager, games);
monopolyDice.Add(new NumberDie(new NumberFace(1), new NumberFace(1), new NumberFace(1), new NumberFace(1)));
monopolyDice.Add(new NumberDie(d6Faces[0], d6Faces[1..]));
monopolyDice.Add(new NumberDie(d6Faces[0], d6Faces[1..]));
game1.AddPlayerToGame(player1);
game1.AddPlayerToGame(player2);
ColorFace[] colorFaces = new ColorFace[]
{
new(Color.FromName("blue")),
new(Color.FromName("red")),
new(Color.FromName("yellow")),
new(Color.FromName("green")),
new(Color.FromName("black")),
new(Color.FromName("white"))
};
game2.AddPlayerToGame(player1);
game2.AddPlayerToGame(player2);
game2.AddPlayerToGame(player3);
monopolyDice.Add(new ColorDie(colorFaces[0], colorFaces[1..]));
game3.AddPlayerToGame(player1);
game3.AddPlayerToGame(player3);
string rootPath = "https://unsplash.com/photos/";
foreach (Game game in games)
ImageFace[] imageFaces = new ImageFace[]
{
for (int i = 0; i < 10; i++)
{
Player currentPlayer = game.GetWhoPlaysNow();
game.PerformTurn(currentPlayer);
game.PrepareNextPlayer(currentPlayer);
}
}
new(new Uri(rootPath + "TLD6iCOlyb0")),
new(new Uri(rootPath + "rTZW4f02zY8")),
new(new Uri(rootPath + "Hyu76loQLdk")),
new(new Uri(rootPath + "A_Ncbi-RH6s")),
};
return gameRunner;
}
monopolyDice.Add(new ImageDie(imageFaces[0], imageFaces[1..]));
public static List<Player> LoadPlayers()
{
List<Player> list = new()
{
new Player("name 1"),
new Player("name 2"),
new Player("name 3"),
new Player("name 4"),
new Player("name 5"),
new Player("name 6")
NumberFace[] d20Faces = new NumberFace[] {
new(1), new(2), new(3), new(4), new(5),
new(6), new(7), new(8), new(9), new(10),
new(11), new(12), new(13), new(14), new(15),
new(16), new(17), new(18), new(19), new(20)
};
return list;
}
public static List<NumberDieFace> LoadNumFaces()
{
List<NumberDieFace> list = new()
{
new NumberDieFace(1),
new NumberDieFace(2),
new NumberDieFace(3),
new NumberDieFace(4),
new NumberDieFace(5),
new NumberDieFace(6),
new NumberDieFace(7)
};
dndDice.Add(new NumberDie(d20Faces[0], d20Faces[1..]));
return list;
}
await mc.DiceGroupManager.Add(new DiceGroup(dndName, dndDice));
await mc.DiceGroupManager.Add(new DiceGroup(monopolyName, monopolyDice));
public static List<ColorDieFace> LoadClrFaces()
{
List<ColorDieFace> list = new()
{
new ColorDieFace("ffffff"),
new ColorDieFace("ffff66"),
new ColorDieFace("ffff11"),
new ColorDieFace("ffff22"),
new ColorDieFace("ffff33"),
new ColorDieFace("ffff44"),
new ColorDieFace("ffff55")
};
string game1 = "Forgotten Realms", game2 = "4e", game3 = "The Coopers";
return list;
}
await mc.GameManager.Add(new(game1, new PlayerManager(), dndDice.AsEnumerable()));
await mc.GameManager.Add(new(game2, new PlayerManager(), dndDice.AsEnumerable()));
await mc.GameManager.Add(new(game3, new PlayerManager(), monopolyDice.AsEnumerable()));
public static List<ImageDieFace> LoadImgFaces()
{
string urlBase = "baseUrl/img/";
List<ImageDieFace> list = new()
await (await mc.GameManager.GetOneByName(game1)).PlayerManager.Add(player1);
await (await mc.GameManager.GetOneByName(game1)).PlayerManager.Add(player2);
await (await mc.GameManager.GetOneByName(game2)).PlayerManager.Add(player1);
await (await mc.GameManager.GetOneByName(game2)).PlayerManager.Add(player2);
await (await mc.GameManager.GetOneByName(game2)).PlayerManager.Add(player3);
await (await mc.GameManager.GetOneByName(game3)).PlayerManager.Add(player1);
await (await mc.GameManager.GetOneByName(game3)).PlayerManager.Add(player3);
foreach (Game game in mc.GameManager.GetAll()?.Result)
{
new ImageDieFace( urlBase + 1),
new ImageDieFace( urlBase + 2),
new ImageDieFace( urlBase + 3),
new ImageDieFace( urlBase + 4),
new ImageDieFace( urlBase + 5),
new ImageDieFace( urlBase + 6),
new ImageDieFace( urlBase + 7),
};
for (int i = 0; i < 10; i++)
{
Player currentPlayer = await game.GetWhoPlaysNow();
game.PerformTurn(currentPlayer);
await game.PrepareNextPlayer(currentPlayer);
}
}
return list;
return mc;
}
}
}

@ -5,17 +5,14 @@ VisualStudioVersion = 17.3.32901.215
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Model", "Model\Model.csproj", "{4F3B0337-8019-4988-ADFE-FD7AE5BCDBF6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "App", "App\App.csproj", "{81443A61-4C32-4863-90B5-7548C7385059}"
ProjectSection(ProjectDependencies) = postProject
{4F3B0337-8019-4988-ADFE-FD7AE5BCDBF6} = {4F3B0337-8019-4988-ADFE-FD7AE5BCDBF6}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{11BDDDA8-CBED-46EE-A224-144C3CD545A7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Data", "Data\Data.csproj", "{3047BFD8-EF44-4095-9E54-45D47C7AB212}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{953D2D67-BCE7-412C-B7BB-7C63B5592359}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Data", "Data\Data.csproj", "{E9683741-E603-4ED3-8088-4099D67FCA6D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utils", "Utils\Utils\Utils.csproj", "{9300910D-9D32-4C79-8868-67D0ED56E2F3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -26,18 +23,18 @@ Global
{4F3B0337-8019-4988-ADFE-FD7AE5BCDBF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F3B0337-8019-4988-ADFE-FD7AE5BCDBF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4F3B0337-8019-4988-ADFE-FD7AE5BCDBF6}.Release|Any CPU.Build.0 = Release|Any CPU
{81443A61-4C32-4863-90B5-7548C7385059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81443A61-4C32-4863-90B5-7548C7385059}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81443A61-4C32-4863-90B5-7548C7385059}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81443A61-4C32-4863-90B5-7548C7385059}.Release|Any CPU.Build.0 = Release|Any CPU
{11BDDDA8-CBED-46EE-A224-144C3CD545A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{11BDDDA8-CBED-46EE-A224-144C3CD545A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11BDDDA8-CBED-46EE-A224-144C3CD545A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{11BDDDA8-CBED-46EE-A224-144C3CD545A7}.Release|Any CPU.Build.0 = Release|Any CPU
{3047BFD8-EF44-4095-9E54-45D47C7AB212}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3047BFD8-EF44-4095-9E54-45D47C7AB212}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3047BFD8-EF44-4095-9E54-45D47C7AB212}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3047BFD8-EF44-4095-9E54-45D47C7AB212}.Release|Any CPU.Build.0 = Release|Any CPU
{E9683741-E603-4ED3-8088-4099D67FCA6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E9683741-E603-4ED3-8088-4099D67FCA6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9683741-E603-4ED3-8088-4099D67FCA6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9683741-E603-4ED3-8088-4099D67FCA6D}.Release|Any CPU.Build.0 = Release|Any CPU
{9300910D-9D32-4C79-8868-67D0ED56E2F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9300910D-9D32-4C79-8868-67D0ED56E2F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9300910D-9D32-4C79-8868-67D0ED56E2F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9300910D-9D32-4C79-8868-67D0ED56E2F3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

@ -0,0 +1,54 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.3.32901.215
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Model", "Model\Model.csproj", "{4F3B0337-8019-4988-ADFE-FD7AE5BCDBF6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "App", "App\App.csproj", "{81443A61-4C32-4863-90B5-7548C7385059}"
ProjectSection(ProjectDependencies) = postProject
{4F3B0337-8019-4988-ADFE-FD7AE5BCDBF6} = {4F3B0337-8019-4988-ADFE-FD7AE5BCDBF6}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{11BDDDA8-CBED-46EE-A224-144C3CD545A7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{953D2D67-BCE7-412C-B7BB-7C63B5592359}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Data", "Data\Data.csproj", "{E9683741-E603-4ED3-8088-4099D67FCA6D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utils", "Utils\Utils\Utils.csproj", "{9300910D-9D32-4C79-8868-67D0ED56E2F3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4F3B0337-8019-4988-ADFE-FD7AE5BCDBF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4F3B0337-8019-4988-ADFE-FD7AE5BCDBF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F3B0337-8019-4988-ADFE-FD7AE5BCDBF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4F3B0337-8019-4988-ADFE-FD7AE5BCDBF6}.Release|Any CPU.Build.0 = Release|Any CPU
{81443A61-4C32-4863-90B5-7548C7385059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81443A61-4C32-4863-90B5-7548C7385059}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81443A61-4C32-4863-90B5-7548C7385059}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81443A61-4C32-4863-90B5-7548C7385059}.Release|Any CPU.Build.0 = Release|Any CPU
{11BDDDA8-CBED-46EE-A224-144C3CD545A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{11BDDDA8-CBED-46EE-A224-144C3CD545A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11BDDDA8-CBED-46EE-A224-144C3CD545A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{11BDDDA8-CBED-46EE-A224-144C3CD545A7}.Release|Any CPU.Build.0 = Release|Any CPU
{E9683741-E603-4ED3-8088-4099D67FCA6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E9683741-E603-4ED3-8088-4099D67FCA6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9683741-E603-4ED3-8088-4099D67FCA6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9683741-E603-4ED3-8088-4099D67FCA6D}.Release|Any CPU.Build.0 = Release|Any CPU
{9300910D-9D32-4C79-8868-67D0ED56E2F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9300910D-9D32-4C79-8868-67D0ED56E2F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9300910D-9D32-4C79-8868-67D0ED56E2F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9300910D-9D32-4C79-8868-67D0ED56E2F3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F1DFFC48-0814-4BD5-A8E6-9F757CEB8725}
EndGlobalSection
EndGlobal

@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Model.Dice.Faces;
namespace Model.Dice
{
public abstract class AbstractDie<T> : RandomnessHaver where T : AbstractDieFace
{
public IEnumerable<T> ListFaces => listFaces;
private readonly List<T> listFaces = new();
protected AbstractDie(params T[] faces)
{
listFaces.AddRange(faces);
}
public T GetRandomFace()
{
int faceIndex = rnd.Next(0, ListFaces.Count());
return ListFaces.ElementAt(faceIndex);
}
}
}

@ -1,17 +1,12 @@
using Model.Dice.Faces;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
namespace Model.Dice
{
public class ColorDie : AbstractDie<AbstractDieFace>
public class ColorDie : HomogeneousDie<Color>
{
public ColorDie(params ColorDieFace[] faces) : base(faces)
public ColorDie(ColorFace first, params ColorFace[] faces)
: base(first, faces)
{
}
}

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace Model.Dice
{
public sealed class DiceGroup : IEquatable<DiceGroup>
{
public string Name { get; private set; }
public ReadOnlyCollection<Die> Dice => new(dice);
private readonly List<Die> dice = new();
public DiceGroup(string name, IEnumerable<Die> dice)
{
Name = name;
this.dice.AddRange(dice);
}
public bool Equals(DiceGroup other)
{
return Name == other.Name && Dice.SequenceEqual(other.Dice);
}
public override bool Equals(object obj)
{
if (obj is null) return false; // is null
if (ReferenceEquals(obj, this)) return true; // is me
if (!obj.GetType().Equals(GetType())) return false; // is different type
return Equals(obj as DiceGroup); // is not me, is not null, is same type : send up
}
public override int GetHashCode()
{
return HashCode.Combine(Name, dice);
}
public void Deconstruct(out string name, out ReadOnlyCollection<Die> dice)
{
dice = Dice;
name = Name;
}
}
}

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace Model.Dice
{
public class DiceGroupManager : IManager<DiceGroup>
{
private readonly List<DiceGroup> diceGroups = new();
public Task<DiceGroup> Add(DiceGroup toAdd)
{
if (string.IsNullOrWhiteSpace(toAdd.Name))
{
throw new ArgumentNullException(nameof(toAdd), "param should not be null or empty");
}
if (diceGroups.Contains(toAdd))
{
throw new ArgumentException("this dice group already exists", nameof(toAdd));
}
diceGroups.Add(new(toAdd.Name.Trim(), toAdd.Dice));
return Task.FromResult(toAdd);
}
public Task<ReadOnlyCollection<DiceGroup>> GetAll()
{
return Task.FromResult(new ReadOnlyCollection<DiceGroup>(diceGroups));
}
public Task<DiceGroup> GetOneByID(Guid ID)
{
throw new NotImplementedException();
}
public Task<DiceGroup> GetOneByName(string name)
{
// les groupes de dés nommés :
// ils sont case-sensistive, mais "mon jeu" == "mon jeu " == " mon jeu"
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException(nameof(name), "param should not be null or empty");
}
return Task.FromResult(diceGroups.First(diceGroup => diceGroup.Name.Equals(name.Trim())));
}
public void Remove(DiceGroup toRemove)
{
if (toRemove.Name is null)
{
throw new ArgumentNullException(nameof(toRemove), "param should not be null");
}
else
{
diceGroups.Remove(toRemove);
}
}
/// <summary>
/// updates a (string, ReadOnlyCollection-of-Die) couple. only the name can be updated
/// </summary>
/// <param name="before">original couple</param>
/// <param name="after">new couple</param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="ArgumentNullException"></exception>
public Task<DiceGroup> Update(DiceGroup before, DiceGroup after)
{
// pas autorisé de changer les dés, juste le nom
if (!before.Dice.SequenceEqual(after.Dice))
{
throw new ArgumentException("the group of dice cannot be updated, only the name", nameof(before));
}
if (string.IsNullOrWhiteSpace(before.Name) || string.IsNullOrWhiteSpace(after.Name))
{
throw new ArgumentNullException(nameof(before), "dice group name should not be null or empty");
}
Remove(before);
Add(after);
return Task.FromResult(after);
}
}
}

@ -0,0 +1,28 @@
using Model.Dice.Faces;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace Model.Dice
{
public abstract class Die
{
public ReadOnlyCollection<Face> Faces => new(faces);
protected static readonly Random rnd = new();
private readonly List<Face> faces = new();
protected Die(Face first, params Face[] faces)
{
this.faces.AddRange(faces.Append(first));
}
public virtual Face GetRandomFace()
{
int faceIndex = rnd.Next(0, Faces.Count);
return Faces.ElementAt(faceIndex);
}
}
}

@ -1,41 +0,0 @@
using Model.Dice.Faces;
using System.Collections.Generic;
using System.Linq;
namespace Model.Dice
{
public class DieManager : IManager<KeyValuePair<string, IEnumerable<AbstractDie<AbstractDieFace>>>>
{
private readonly Dictionary<string, IEnumerable<AbstractDie<AbstractDieFace>>> diceGroups = new();
public KeyValuePair<string, IEnumerable<AbstractDie<AbstractDieFace>>> Add(KeyValuePair<string, IEnumerable<AbstractDie<AbstractDieFace>>> toAdd)
{
diceGroups.Add(toAdd.Key, toAdd.Value);
return toAdd;
}
public IEnumerable<KeyValuePair<string, IEnumerable<AbstractDie<AbstractDieFace>>>> GetAll()
{
return diceGroups.AsEnumerable();
}
public KeyValuePair<string, IEnumerable<AbstractDie<AbstractDieFace>>> GetOneByName(string name)
{
return new KeyValuePair<string, IEnumerable<AbstractDie<AbstractDieFace>>>(name, diceGroups[name]);
}
public void Remove(KeyValuePair<string, IEnumerable<AbstractDie<AbstractDieFace>>> toRemove)
{
diceGroups.Remove(toRemove.Key);
}
public KeyValuePair<string, IEnumerable<AbstractDie<AbstractDieFace>>> Update(KeyValuePair<string, IEnumerable<AbstractDie<AbstractDieFace>>> before, KeyValuePair<string, IEnumerable<AbstractDie<AbstractDieFace>>> after)
{
// check if key 1 exists
// check if both keys same
diceGroups.Remove(before.Key);
diceGroups.Add(after.Key, after.Value);
return after;
}
}
}

@ -1,26 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model.Dice.Faces
{
public abstract class AbstractDieFace
{
/// <summary>
/// every die face has a value, and they can all be represented by an int,
/// even if they're not litterally a decimal number
/// <br/>
/// USE GetPracticalValue for a Value specific to face type
/// </summary>
protected abstract int Value { get; }
public abstract object GetPracticalValue();
public override string ToString()
{
return GetPracticalValue().ToString();
}
}
}

@ -1,44 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model.Dice.Faces
{
public class ColorDieFace : AbstractDieFace
{
/// <summary>
/// a decimal representation of the hex (...representation of the color)
/// </summary>
protected override int Value { get; }
/// <summary>
/// accepts hex strings like "ffbb84" ([RRGGBB])
/// </summary>
/// <param name="hexValueString">hex string</param>
public ColorDieFace(string hexValueString)
{
// https://stackoverflow.com/questions/1139957/convert-integer-to-hexadecimal-and-back-again
// if style is ("f0b"), this constructor can develop it to "ff00bb" before doing the job
Value = int.Parse(hexValueString, System.Globalization.NumberStyles.HexNumber);
}
/// <summary>
/// accepts a decimal value that represents a color hex (0 is black, 65280 is green...)
/// </summary>
/// <param name="decimalValue"></param>
public ColorDieFace(int decimalValue)
{
Value = decimalValue;
}
public override object GetPracticalValue()
{
// https://stackoverflow.com/questions/1139957/convert-integer-to-hexadecimal-and-back-again
// maybe prepend it with a "#"...
return Value.ToString("X6").Insert(0, "#");
}
}
}

@ -0,0 +1,11 @@
using System.Drawing;
namespace Model.Dice.Faces
{
public class ColorFace : Face<Color>
{
public ColorFace(Color value) : base(value)
{
}
}
}

@ -0,0 +1,18 @@
namespace Model.Dice.Faces
{
public abstract class Face
{
public string StringValue { get; protected set; }
}
public abstract class Face<T> : Face
{
public T Value { get; protected set; }
protected Face(T value)
{
Value = value;
StringValue = value.ToString();
}
}
}

@ -1,35 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model.Dice.Faces
{
public class ImageDieFace : AbstractDieFace
{
/// <summary>
/// an image URL code, to find the image URL (to find the image)
/// </summary>
protected override int Value { get; }
public ImageDieFace(string uri)
{
/*parse an int after the last occurrence of "/" ? */
string resultString = uri[(uri.LastIndexOf('/') + 1)..];
/* !! here we should make sure to remove any ".jpg" etc, if there was one in the uri*/
int result = int.Parse(resultString);
Value = result;
}
public ImageDieFace(int code)
{
Value = code;
}
public override object GetPracticalValue()
{
return string.Format("Assets/images/{0}", Value);
}
}
}

@ -0,0 +1,11 @@
using System;
namespace Model.Dice.Faces
{
public class ImageFace : Face<Uri>
{
public ImageFace(Uri value) : base(value)
{
}
}
}

@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model.Dice.Faces
{
public class NumberDieFace : AbstractDieFace
{
protected override int Value { get; }
public NumberDieFace(int value)
{
Value = value;
}
public override object GetPracticalValue()
{
return Value;
}
}
}

@ -0,0 +1,9 @@
namespace Model.Dice.Faces
{
public class NumberFace : Face<int>
{
public NumberFace(int value) : base(value)
{
}
}
}

@ -0,0 +1,17 @@
using Model.Dice.Faces;
namespace Model.Dice
{
public abstract class HomogeneousDie<T> : Die
{
protected HomogeneousDie(Face<T> first, params Face<T>[] faces)
: base(first, faces)
{
}
public override Face<T> GetRandomFace()
{
return (Face<T>)base.GetRandomFace();
}
}
}

@ -1,15 +1,12 @@
using Model.Dice.Faces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model.Dice
{
public class ImageDie : AbstractDie<AbstractDieFace>
public class ImageDie : HomogeneousDie<Uri>
{
public ImageDie(params ImageDieFace[] faces) : base(faces)
public ImageDie(ImageFace first, params ImageFace[] faces)
: base(first, faces)
{
}
}

@ -1,15 +1,11 @@
using Model.Dice.Faces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model.Dice
{
public class NumberDie : AbstractDie<AbstractDieFace>
public class NumberDie : HomogeneousDie<int>
{
public NumberDie(params NumberDieFace[] faces) : base(faces)
public NumberDie(NumberFace first, params NumberFace[] faces)
: base(first, faces)
{
}
}

@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model.Dice
{
public class RandomnessHaver
{
protected RandomnessHaver()
{
}
protected static readonly Random rnd = new();
}
}

@ -1,206 +1,165 @@
using Model.Dice;
using Model.Dice.Faces;
using Model.Players;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
namespace Model.Games
{
public class Game
{
/// <summary>
/// the name of the game 😎
/// </summary>
public string Name
{
get
{
return name;
}
set // GameRunner will need to take care of forbidding
// (or allowing) having two Games with the same name etc.
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("param should not be null or blank", nameof(value));
}
name = value;
}
}
private string name;
/// <summary>
/// references the position in list of the current player, for a given game.
/// </summary>
private int nextIndex = 0;
/// <summary>
/// the turns that have been done so far
/// </summary>
private readonly List<Turn> turns;
/// </summary>
/// get a READ ONLY enumerable of all turns belonging to this game
/// </summary>
/// <returns>a readonly enumerable of all this game's turns</returns>
public IEnumerable<Turn> GetHistory() => turns.AsEnumerable();
/// <summary>
/// the game's player manager, doing CRUD on players and switching whose turn it is
/// </summary>
private readonly IManager<Player> playerManager;
/// <summary>
/// the group of dice used for this game
/// </summary>
private readonly IEnumerable<AbstractDie<AbstractDieFace>> dice;
/// <summary>
/// constructs a Game with its own history of Turns.
/// If <paramref name="turns"/> is null, starts a new history
/// </summary>
/// <param name="name">the name of the game 😎</param>
/// <param name="turns">the turns that have been done so far</param>
/// <param name="playerManager">the game's player manager, doing CRUD on players and switching whose turn it is</param>
/// <param name="favGroup">the group of dice used for this game</param>
public Game(string name, IManager<Player> playerManager, IEnumerable<AbstractDie<AbstractDieFace>> dice, IEnumerable<Turn> turns)
{
Name = name;
this.turns = turns.ToList() ?? new List<Turn>();
this.playerManager = playerManager;
this.dice = dice;
}
/// <summary>
/// constructs a Game with no history of turns.
/// </summary>
/// <param name="name">the name of the game 😎</param>
/// <param name="playerManager">the game's player manager, doing CRUD on players and switching whose turn it is</param>
/// <param name="favGroup">the group of dice used for this game</param>
public Game(string name, IManager<Player> playerManager, IEnumerable<AbstractDie<AbstractDieFace>> dice)
: this(name, playerManager, dice, null)
{ }
/// <summary>
/// performs a Turn, marks this Game as "started", and logs that Turn
/// </summary>
/// <param name="player">the player whose turn it is</param>
public void PerformTurn(Player player)
{
Turn turn = Turn.CreateWithDefaultTime(
new Player(player), //using a copy so that next lines can't "change history"
ThrowAll()
);
turns.Add(turn);
nextIndex++;
}
/// <summary>
/// finds and returns the player whose turn it is
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public Player GetWhoPlaysNow()
{
if (!playerManager.GetAll().Any())
{
throw new MemberAccessException("you are exploring an empty collection\nthis should not have happened");
}
return playerManager.GetAll().ElementAt(nextIndex);
}
/// <summary>
/// this feels very dirty
/// </summary>
/// <param name="current">the current player</param>
/// <exception cref="MemberAccessException"></exception>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public void PrepareNextPlayer(Player current)
{
if (!playerManager.GetAll().Any())
{
throw new MemberAccessException("you are exploring an empty collection\nthis should not have happened");
}
if (current == null)
{
throw new ArgumentNullException(nameof(current), "param should not be null");
}
if (!playerManager.GetAll().Contains(current))
{
throw new ArgumentException("param could not be found in this collection\n did you forget to add it?", nameof(current));
}
if (playerManager.GetAll().Last() == current)
{
// if we've reached the last player, we need the index to loop back around
nextIndex = 0;
}
else
{
// else we can just move up by one from the current index
nextIndex++;
}
}
/// <summary>
/// throws all the Dice in FavGroup and returns a list of their Faces
/// </summary>
/// <returns>list of AbstractDieFaces after a throw</returns>
private Dictionary<AbstractDie<AbstractDieFace>, AbstractDieFace> ThrowAll()
{
Dictionary<AbstractDie<AbstractDieFace>, AbstractDieFace> faces = new();
foreach (AbstractDie<AbstractDieFace> die in dice)
{
faces.Add(die, die.GetRandomFace());
}
return faces;
}
public Player AddPlayerToGame(Player player)
{
return playerManager.Add(player);
}
public IEnumerable<Player> GetPlayersFromGame()
{
return playerManager.GetAll();
}
public Player UpdatePlayerInGame(Player oldPlayer, Player newPlayer)
{
return playerManager.Update(oldPlayer, newPlayer);
}
public void RemovePlayerFromGame(Player player)
{
playerManager.Remove(player);
}
/// <summary>
/// represents a Game in string format
/// </summary>
/// <returns>a Game in string format</returns>
public override string ToString()
{
StringBuilder sb = new();
sb.AppendFormat("Game: {0}===========\n" +
"{1} are playing. {2} is next.\n" +
"Log:\n",
Name,
playerManager.GetAll().ToString(),
GetWhoPlaysNow());
foreach (Turn turn in this.turns)
{
sb.Append("\t" + turn.ToString());
}
return sb.ToString();
}
}
}
using Model.Dice;
using Model.Dice.Faces;
using Model.Players;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace Model.Games
{
public class Game
{
/// <summary>
/// the name of the game 😎
/// </summary>
public string Name
{
get
{
return name;
}
set // MasterOfCeremonies will need to take care of forbidding
// (or allowing) having two Games with the same name etc.
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("param should not be null or blank", nameof(value));
}
name = value;
}
}
private string name;
/// <summary>
/// references the position in list of the current player, for a given game.
/// </summary>
private int nextIndex = 0;
/// <summary>
/// the turns that have been done so far
/// </summary>
private readonly List<Turn> turns = new();
/// </summary>
/// get a READ ONLY enumerable of all turns belonging to this game
/// </summary>
/// <returns>a readonly enumerable of all this game's turns</returns>
public ReadOnlyCollection<Turn> GetHistory() => new(turns);
/// <summary>
/// the game's player manager, doing CRUD on players and switching whose turn it is
/// </summary>
public IManager<Player> PlayerManager { get; private set; }
/// <summary>
/// the group of dice used for this game
/// </summary>
public ReadOnlyCollection<Die> Dice => new(dice);
private readonly List<Die> dice = new();
/// <summary>
/// constructs a Game with its own history of Turns.
/// If <paramref name="turns"/> is null, starts a new history
/// </summary>
/// <param name="name">the name of the game 😎</param>
/// <param name="turns">the turns that have been done so far</param>
/// <param name="playerManager">the game's player manager, doing CRUD on players and switching whose turn it is</param>
/// <param name="favGroup">the group of dice used for this game</param>
public Game(string name, IManager<Player> playerManager, IEnumerable<Die> dice, IEnumerable<Turn> turns)
{
Name = name;
PlayerManager = playerManager;
this.dice.AddRange(dice);
this.turns.AddRange(turns);
}
/// <summary>
/// constructs a Game with no history of turns.
///
/// </summary>
/// <param name="name">the name of the game 😎</param>
/// <param name="playerManager">the game's player manager, doing CRUD on players and switching whose turn it is</param>
/// <param name="favGroup">the group of dice used for this game</param>
public Game(string name, IManager<Player> playerManager, IEnumerable<Die> dice)
: this(name, playerManager, dice, new List<Turn>())
{ }
/// <summary>
/// performs a Turn, marks this Game as "started", and logs that Turn
/// </summary>
/// <param name="player">the player whose turn it is</param>
public void PerformTurn(Player player)
{
Turn turn = Turn.CreateWithDefaultTime(
player,
ThrowAll()
);
AddTurn(turn);
}
private void AddTurn(Turn turn)
{
if (!(turns.Contains(turn)))
{
turns.Add(turn);
}
}
/// <summary>
/// finds and returns the player whose turn it is
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public async Task<Player> GetWhoPlaysNow()
{
if (!(await PlayerManager.GetAll()).Any())
{
throw new MemberAccessException("you are exploring an empty collection\nthis should not have happened");
}
return (await PlayerManager.GetAll()).ElementAt(nextIndex);
}
/// <summary>
/// this feels very dirty
/// </summary>
/// <param name="current">the current player</param>
/// <exception cref="MemberAccessException"></exception>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public async Task PrepareNextPlayer(Player current)
{
IEnumerable<Player> players = await PlayerManager.GetAll();
if (!players.Any())
{
throw new MemberAccessException("you are exploring an empty collection\nthis should not have happened");
}
if (current == null)
{
throw new ArgumentNullException(nameof(current), "param should not be null");
}
if (!players.Contains(current))
{
throw new ArgumentException("param could not be found in this collection\n did you forget to add it?", nameof(current));
}
nextIndex = (nextIndex + 1) % players.Count();
}
/// <summary>
/// throws all the Dice in FavGroup and returns a list of their Faces
/// </summary>
/// <returns>list of AbstractDieFaces after a throw</returns>
private Dictionary<Die, Face> ThrowAll()
{
Dictionary<Die, Face> faces = new();
foreach (Die die in dice)
{
faces.Add(die, die.GetRandomFace());
}
return faces;
}
}
}

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace Model.Games
{
public class GameManager : IManager<Game>
{
/// <summary>
/// the games managed by this instance
/// </summary>
private readonly List<Game> games = new();
/// <summary>
/// gets an unmodifiable collection of the games
/// </summary>
/// <returns>unmodifiable collection of the games</returns>
public Task<ReadOnlyCollection<Game>> GetAll() => Task.FromResult(new ReadOnlyCollection<Game>(games));
/// <summary>
/// finds the game with that name and returns it
/// <br/>
/// that copy does not belong to this manager's games, so it should not be modified
/// </summary>
/// <param name="name">a games's name</param>
/// <returns>game with said name, <em>or null</em> if no such game was found</returns>
public Task<Game> GetOneByName(string name)
{
if (!string.IsNullOrWhiteSpace(name))
{
return Task.FromResult(games.FirstOrDefault(g => g.Name == name)); // may return null
}
throw new ArgumentException("param should not be null or blank", nameof(name));
}
/// <summary>
/// not implemented in the model
/// </summary>
/// <param name="ID"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public Task<Game> GetOneByID(Guid ID)
{
throw new NotImplementedException();
}
/// <summary>
/// saves a given game -- does not allow copies yet: if a game with the same name exists, it is overwritten
/// </summary>
/// <param name="toAdd">a game to save</param>
public Task<Game> Add(Game toAdd)
{
if (toAdd is null)
{
throw new ArgumentNullException(nameof(toAdd), "param should not be null");
}
games.Remove(games.FirstOrDefault(g => g.Name == toAdd.Name));
// will often be an update: if game with that name exists, it is removed, else, nothing happens above
games.Add(toAdd);
return Task.FromResult(toAdd);
}
/// <summary>
/// removes a game. does nothing if the game doesn't exist
/// </summary>
/// <param name="toRemove">game to remove</param>
/// <exception cref="ArgumentNullException"></exception>
public void Remove(Game toRemove)
{
if (toRemove is null)
{
throw new ArgumentNullException(nameof(toRemove), "param should not be null");
}
games.Remove(toRemove);
}
/// <summary>
/// updates a game
/// </summary>
/// <param name="before">original game</param>
/// <param name="after">new game</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public Task<Game> Update(Game before, Game after)
{
Game[] args = { before, after };
foreach (Game game in args)
{
if (game is null)
{
throw new ArgumentNullException(nameof(after), "param should not be null");
// could also be because of before, but one param had to be chosen as an example
// and putting "player" there was raising a major code smell
}
}
Remove(before);
return (Add(after));
}
}
}

@ -1,64 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model.Dice;
using Model.Dice.Faces;
using Model.Players;
namespace Model.Games
{
public class GameRunner
{
private readonly IManager<Player> globalPlayerManager;
private readonly IManager<KeyValuePair<string, IEnumerable<AbstractDie<AbstractDieFace>>>> globalDieManager;
private readonly List<Game> games;
public GameRunner(IManager<Player> globalPlayerManager, IManager<KeyValuePair<string, IEnumerable<AbstractDie<AbstractDieFace>>>> globalDieManager, List<Game> games)
{
this.globalPlayerManager = globalPlayerManager;
this.globalDieManager = globalDieManager;
this.games = games ?? new();
}
public IEnumerable<Game> GetAllGames() => games.AsEnumerable();
/// <summary>
/// finds the game with that name and returns it
/// <br/>
/// that copy does not belong to this gamerunner's games, so it should not be modified
/// </summary>
/// <param name="name">a games's name</param>
/// <returns>game with said name, <em>or null</em> if no such game was found</returns>
public Game GetOneGameByName(string name)
{
if (!string.IsNullOrWhiteSpace(name))
{
Game result = games.FirstOrDefault(g => g.Name == name);
return result; // may return null
}
throw new ArgumentException("param should not be null or blank", nameof(name));
}
public void SaveGame(Game game)
{
throw new NotSupportedException();
}
public Game LoadGame(string name)
{
throw new NotSupportedException();
}
public void StartNewGame()
{
throw new NotSupportedException();
}
public void DeleteGame(Game game)
{
throw new NotSupportedException();
}
}
}

@ -0,0 +1,46 @@
using Model.Dice;
using Model.Players;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Model.Games
{
public class MasterOfCeremonies
{
public IManager<Player> GlobalPlayerManager { get; private set; }
public IManager<DiceGroup> DiceGroupManager { get; private set; }
public IManager<Game> GameManager { get; private set; }
public MasterOfCeremonies(IManager<Player> globalPlayerManager, IManager<DiceGroup> globalDiceGroupManager, IManager<Game> gameManager)
{
GlobalPlayerManager = globalPlayerManager;
DiceGroupManager = globalDiceGroupManager;
GameManager = gameManager;
}
/// <summary>
/// creates a new game
/// </summary>
/// <param name="name"></param>
/// <param name="playerManager"></param>
/// <param name="dice"></param>
/// <returns></returns>
public async Task<Game> StartNewGame(string name, IManager<Player> playerManager, IEnumerable<Die> dice)
{
Game game = new(name, playerManager, dice);
return await GameManager.Add(game);
}
/// <summary>
/// plays one turn of the game
/// </summary>
/// <param name="game">the game from which a turn will be played</param>
public static async Task PlayGame(Game game)
{
Player current = await game.GetWhoPlaysNow();
game.PerformTurn(current);
await game.PrepareNextPlayer(current);
}
}
}

@ -1,23 +1,18 @@
using System;
using System.Collections;
using Model.Dice;
using Model.Dice.Faces;
using Model.Players;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using Model.Dice;
using Model.Dice.Faces;
using Model.Players;
namespace Model.Games
{
/// <summary>
/// a Turn consists of a Player, a DateTime, and a IEnumerable of AbstractDieFace
/// Like a turn in some game.
/// <br/>
/// Two turns are equal if they are litterally the same instance in RAM
/// (default behaviors Equals() and GetHashCode())
/// </summary>
public class Turn
public sealed class Turn : IEquatable<Turn>
{
/// <summary>
@ -33,8 +28,8 @@ namespace Model.Games
/// <summary>
/// the collection of Face that were rolled
/// </summary>
public IEnumerable<KeyValuePair<AbstractDie<AbstractDieFace>, AbstractDieFace>> DiceNFaces => diceNFaces.AsEnumerable();
private readonly Dictionary<AbstractDie<AbstractDieFace>, AbstractDieFace> diceNFaces;
public ReadOnlyDictionary<Die, Face> DiceNFaces => new(diceNFaces);
private readonly Dictionary<Die, Face> diceNFaces;
/// <summary>
/// this private constructor is to be used only by factories
@ -42,24 +37,7 @@ namespace Model.Games
/// <param name="when">date and time of the turn</param>
/// <param name="player">player who played the turn</param>
/// <param name="faces">faces that were rolled</param>
private Turn(DateTime when, Player player, Dictionary<AbstractDie<AbstractDieFace>, AbstractDieFace> diceNFaces)
{
When = when;
Player = player;
this.diceNFaces = diceNFaces;
}
/// <summary>
/// creates a Turn with a specified time, passed as a parameter.
/// <br/>
/// whatever the DateTimeKind of <paramref name="when"/> might be,
/// it will become UTC during construction
/// </summary>
/// <param name="when">date and time of the turn</param>
/// <param name="player">player who played the turn</param>
/// <param name="faces">faces that were rolled</param>
/// <returns>a new Turn object</returns>
public static Turn CreateWithSpecifiedTime(DateTime when, Player player, Dictionary<AbstractDie<AbstractDieFace>, AbstractDieFace> diceNFaces)
private Turn(DateTime when, Player player, IEnumerable<KeyValuePair<Die, Face>> diceNFaces)
{
if (player is null)
{
@ -69,7 +47,7 @@ namespace Model.Games
{
throw new ArgumentNullException(nameof(diceNFaces), "param should not be null");
}
if (diceNFaces.Count == 0)
if (!diceNFaces.Any())
{
throw new ArgumentException("param should not be null", nameof(diceNFaces));
}
@ -78,6 +56,23 @@ namespace Model.Games
when = when.ToUniversalTime();
}
When = when;
Player = player;
this.diceNFaces = diceNFaces.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
/// <summary>
/// creates a Turn with a specified time, passed as a parameter.
/// <br/>
/// whatever the DateTimeKind of <paramref name="when"/> might be,
/// it will become UTC during construction
/// </summary>
/// <param name="when">date and time of the turn</param>
/// <param name="player">player who played the turn</param>
/// <param name="faces">faces that were rolled</param>
/// <returns>a new Turn object</returns>
public static Turn CreateWithSpecifiedTime(DateTime when, Player player, IEnumerable<KeyValuePair<Die, Face>> diceNFaces)
{
return new Turn(when, player, diceNFaces);
}
@ -87,33 +82,61 @@ namespace Model.Games
/// <param name="player">player who played the turn</param>
/// <param name="faces">faces that were rolled</param>
/// <returns>a new Turn object</returns>
public static Turn CreateWithDefaultTime(Player player, Dictionary<AbstractDie<AbstractDieFace>, AbstractDieFace> diceNFaces)
public static Turn CreateWithDefaultTime(Player player, IEnumerable<KeyValuePair<Die, Face>> diceNFaces)
{
return CreateWithSpecifiedTime(DateTime.UtcNow, player, diceNFaces);
}
/// <summary>
/// represents a turn in string format
/// </summary>
/// <returns>a turn in string format</returns>
public override string ToString()
public bool Equals(Turn other)
{
string[] datetime = When.ToString("s", System.Globalization.CultureInfo.InvariantCulture).Split("T");
string date = datetime[0];
string time = datetime[1];
if (other is null
||
!(Player.Equals(other.Player)
&& When.Equals(other.When)
&& DiceNFaces.Count == other.DiceNFaces.Count))
{
return false;
}
StringBuilder sb = new();
// 🤮
for (int i = 0; i < DiceNFaces.Count; i++)
{
if (DiceNFaces.ElementAt(i).Key.Faces.Count
!= other.DiceNFaces.ElementAt(i).Key.Faces.Count)
{
return false;
}
if (!other.DiceNFaces.ElementAt(i).Value.StringValue
.Equals(DiceNFaces.ElementAt(i).Value.StringValue))
{
return false;
}
sb.AppendFormat("{0} {1} -- {2} rolled:",
date,
time,
Player.ToString());
foreach (AbstractDieFace face in this.diceNFaces.Values)
for (int j = 0; j < DiceNFaces.ElementAt(i).Key.Faces.Count; j++)
{
if (!other.DiceNFaces.ElementAt(i).Key.Faces.ElementAt(j).StringValue
.Equals(DiceNFaces.ElementAt(i).Key.Faces.ElementAt(j).StringValue))
{
return false;
}
}
}
return true;
}
public override bool Equals(object obj)
{
if (obj is not Turn)
{
sb.Append(" " + face.ToString());
return false;
}
return Equals(obj as Turn);
}
return sb.ToString();
public override int GetHashCode()
{
return When.GetHashCode();
}
}
}

@ -1,12 +1,21 @@
using System.Collections.Generic;
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
namespace Model
{
public interface IManager<T>
{
public T Add(T toAdd);
public T GetOneByName(string name);
public IEnumerable<T> GetAll();
public T Update(T before, T after);
public Task<T> Add(T toAdd);
public Task<T> GetOneByName(string name);
public Task<T> GetOneByID(Guid ID);
public Task<ReadOnlyCollection<T>> GetAll();
public Task<T> Update(T before, T after);
public void Remove(T toRemove);
}
}

@ -4,4 +4,8 @@
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NLog" Version="5.0.4" />
</ItemGroup>
</Project>

@ -30,14 +30,9 @@ namespace Model.Players
: this(player?.Name) // passes the player's name if player exists, else null
{ }
public override string ToString()
{
return Name;
}
public bool Equals(Player other)
{
return Name.ToUpper() == other.Name.ToUpper(); // equality is case insensitive
return other is not null && Name.ToUpper() == other.Name.ToUpper(); // equality is case insensitive
}
public override bool Equals(object obj)

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace Model.Players
{
@ -9,18 +11,14 @@ namespace Model.Players
/// <summary>
/// a collection of the players that this manager is in charge of
/// </summary>
private readonly List<Player> players;
private readonly List<Player> players = new();
public PlayerManager()
{
players = new();
}
/// <summary>
/// add a new player
/// </summary>
/// <param name="toAdd">player to be added</param>
/// <returns>added player, or null if <paramref name="toAdd"/> was null</returns>
public Player Add(Player toAdd)
/// <returns>added player</returns>
public Task<Player> Add(Player toAdd)
{
if (toAdd is null)
{
@ -31,25 +29,27 @@ namespace Model.Players
throw new ArgumentException("this username is already taken", nameof(toAdd));
}
players.Add(toAdd);
return toAdd;
return Task.FromResult(toAdd);
}
/// <summary>
/// finds the player with that name and returns A COPY OF IT
/// <br/>
/// that copy does not belong to this manager's players, so it should not be modified
/// finds the player with that name and returns it
/// </summary>
/// <param name="name">a player's unique name</param>
/// <returns>player with said name, <em>or null</em> if no such player was found</returns>
public Player GetOneByName(string name)
public Task<Player> GetOneByName(string name)
{
if (!string.IsNullOrWhiteSpace(name))
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("param should not be null or blank", nameof(name));
}
Player result = players.FirstOrDefault(p => p.Name.ToUpper().Equals(name.ToUpper().Trim()));
if (result == null)
{
Player wanted = new(name);
Player result = players.FirstOrDefault(p => p.Equals(wanted));
return result is null ? null : new Player(result); // THIS IS A COPY (using a copy constructor)
return Task.FromResult<Player>(null);
}
throw new ArgumentException("param should not be null or blank", nameof(name));
return Task.FromResult(result);
}
/// </summary>
@ -57,7 +57,7 @@ namespace Model.Players
/// so that the only way to modify the collection of players is to use this class's methods
/// </summary>
/// <returns>a readonly enumerable of all this manager's players</returns>
public IEnumerable<Player> GetAll() => players.AsEnumerable();
public Task<ReadOnlyCollection<Player>> GetAll() => Task.FromResult(new ReadOnlyCollection<Player>(players));
/// <summary>
/// update a player from <paramref name="before"/> to <paramref name="after"/>
@ -65,7 +65,7 @@ namespace Model.Players
/// <param name="before">player to be updated</param>
/// <param name="after">player in the state that it needs to be in after the update</param>
/// <returns>updated player</returns>
public Player Update(Player before, Player after)
public Task<Player> Update(Player before, Player after)
{
Player[] args = { before, after };
@ -95,5 +95,10 @@ namespace Model.Players
// the built-in Remove() method will use our redefined Equals(), using Name only
players.Remove(toRemove);
}
public Task<Player> GetOneByID(Guid ID)
{
throw new NotImplementedException();
}
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Data_UTs.Dice
{
public class ColorDieEntityTest
{
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Data_UTs.Dice
{
public class ColorDieExtensionsTest
{
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Data_UTs.Dice
{
public class DiceGroupDbManagerTest
{
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Data_UTs.Dice.Faces
{
public class ColorFaceEntityTest
{
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Data_UTs.Dice.Faces
{
public class ColorFaceExtensionsTest
{
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Data_UTs.Dice.Faces
{
public class ImageFaceEntityTest
{
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Data_UTs.Dice.Faces
{
public class ImageFaceExtensionsTest
{
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Data_UTs.Dice.Faces
{
public class NumberFaceEntityTest
{
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Data_UTs.Dice.Faces
{
public class NumberFaceExtensionsTest
{
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Data_UTs.Dice
{
public class ImageDieEntityTest
{
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Data_UTs.Dice
{
public class ImageDieExtensionsTest
{
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Data_UTs.Dice
{
public class NumberDieEntityTest
{
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Data_UTs.Dice
{
public class NumberDieExtensionsTest
{
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Data_UTs
{
public class DiceAppDbContextTest
{
}
}

@ -0,0 +1,49 @@
using Data.EF;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Tests.Data_UTs
{
public class DiceAppDbContextWithStubTest
{
private readonly SqliteConnection connection = new("DataSource=:memory:");
private readonly DbContextOptions<DiceAppDbContext> options;
public DiceAppDbContextWithStubTest()
{
connection.Open();
options = new DbContextOptionsBuilder<DiceAppDbContext>()
.UseSqlite(connection)
.EnableSensitiveDataLogging()
.Options;
}
[Theory]
[InlineData("Alice")]
[InlineData("Bob")]
[InlineData("Clyde")]
[InlineData("Dahlia")]
public void TestDbStubContainsAll(string name)
{
// Arrange
using (DiceAppDbContextWithStub db = new(options))
{
db.Database.EnsureCreated();
// Assert
Assert.True(db.PlayerEntity.Where(p => p.Name.Equals(name)).Any());
}
}
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Data_UTs.Games
{
public class GameDbManagerTest
{
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Data_UTs.Games
{
public class GameEntityTest
{
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Data_UTs.Games
{
public class GameExtensionsTest
{
}
}

@ -0,0 +1,407 @@
using Data.EF.Dice;
using Data.EF.Dice.Faces;
using Data.EF.Games;
using Data.EF.Joins;
using Data.EF.Players;
using System;
using System.Collections.Generic;
using System.Drawing;
using Xunit;
namespace Tests.Data_UTs.Games
{
public class TurnEntityTest
{
private readonly DieEntity numDie;
private readonly FaceEntity numFace1 = new NumberFaceEntity() { ID = Guid.NewGuid(), Value = 7 };
private readonly FaceEntity numFace2 = new NumberFaceEntity() { ID = Guid.NewGuid(), Value = 8 };
private readonly DieEntity clrDie;
private readonly FaceEntity clrFace1 = new ColorFaceEntity() { ID = Guid.NewGuid(), A = 255, R = 255, G = 255, B = 255 };
private readonly FaceEntity clrFace2 = new ColorFaceEntity() { ID = Guid.NewGuid() };
private readonly DieEntity imgDie;
private readonly FaceEntity imgFace1 = new ImageFaceEntity() { ID = Guid.NewGuid(), Value = "https://a" };
private readonly FaceEntity imgFace2 = new ImageFaceEntity() { ID = Guid.NewGuid(), Value = "https://b" };
private readonly PlayerEntity player1 = new() { ID = Guid.NewGuid(), Name = "Marvin" };
private readonly PlayerEntity player2 = new() { ID = Guid.NewGuid(), Name = "Barbara" };
private readonly DateTime datetime1 = new(2020, 6, 15, 12, 15, 3, DateTimeKind.Utc);
private readonly DateTime datetime2 = new(2016, 12, 13, 14, 15, 16, DateTimeKind.Utc);
private readonly DieTurn dieTurn1;
private readonly DieTurn dieTurn2;
private readonly DieTurn dieTurn3;
private readonly DieTurn dieTurn4;
private readonly DieTurn dieTurn5;
private readonly FaceTurn faceTurn1;
private readonly FaceTurn faceTurn2;
private readonly FaceTurn faceTurn3;
private readonly FaceTurn faceTurn4;
private readonly FaceTurn faceTurn5;
private readonly TurnEntity turn1;
private readonly TurnEntity turn2;
private readonly TurnEntity turn3;
public TurnEntityTest()
{
numDie = new NumberDieEntity() { ID = Guid.NewGuid(), Faces = new List<NumberFaceEntity>() { numFace1 as NumberFaceEntity, numFace2 as NumberFaceEntity } };
(numFace1 as NumberFaceEntity).NumberDieEntity = (NumberDieEntity)numDie;
(numFace2 as NumberFaceEntity).NumberDieEntity = (NumberDieEntity)numDie;
(clrFace2 as ColorFaceEntity).SetValue(Color.FromName("blue"));
clrDie = new ColorDieEntity() { ID = Guid.NewGuid(), Faces = new List<ColorFaceEntity>() { clrFace1 as ColorFaceEntity, clrFace2 as ColorFaceEntity } };
(clrFace1 as ColorFaceEntity).ColorDieEntity = (ColorDieEntity)clrDie;
(clrFace2 as ColorFaceEntity).ColorDieEntity = (ColorDieEntity)clrDie;
imgDie = new ImageDieEntity() { ID = Guid.NewGuid(), Faces = new List<ImageFaceEntity>() { imgFace1 as ImageFaceEntity, imgFace2 as ImageFaceEntity } };
(imgFace1 as ImageFaceEntity).ImageDieEntity = (ImageDieEntity)imgDie;
(imgFace2 as ImageFaceEntity).ImageDieEntity = (ImageDieEntity)imgDie;
turn1 = new()
{
ID = Guid.NewGuid(),
When = datetime1,
PlayerEntity = player1,
PlayerEntityID = player1.ID,
Dice = new List<DieEntity>
{
numDie,
clrDie,
imgDie
},
Faces = new List<FaceEntity>
{
numFace1,
clrFace2,
imgFace2
},
};
dieTurn1 = new() { DieEntityID = numDie.ID, DieEntity = numDie, TurnEntityID = turn1.ID, TurnEntity = turn1 };
dieTurn2 = new() { DieEntityID = clrDie.ID, DieEntity = clrDie, TurnEntityID = turn1.ID, TurnEntity = turn1 };
dieTurn3 = new() { DieEntityID = imgDie.ID, DieEntity = imgDie, TurnEntityID = turn1.ID, TurnEntity = turn1 };
turn1.DieTurns = new() { dieTurn1, dieTurn2, dieTurn3 };
numDie.DieTurns = new() { dieTurn1 };
clrDie.DieTurns = new() { dieTurn2 };
imgDie.DieTurns = new() { dieTurn3 };
faceTurn1 = new() { FaceEntityID = numFace1.ID, FaceEntity = numFace1, TurnEntityID = turn1.ID, TurnEntity = turn1 };
faceTurn2 = new() { FaceEntityID = clrFace2.ID, FaceEntity = clrFace2, TurnEntityID = turn1.ID, TurnEntity = turn1 };
faceTurn3 = new() { FaceEntityID = imgFace2.ID, FaceEntity = imgFace2, TurnEntityID = turn1.ID, TurnEntity = turn1 };
turn1.FaceTurns = new() { faceTurn1, faceTurn2, faceTurn3 };
numFace1.FaceTurns = new() { faceTurn1 };
clrFace2.FaceTurns = new() { faceTurn2 };
imgFace2.FaceTurns = new() { faceTurn3 };
Guid turn2ID = Guid.NewGuid();
turn2 = new()
{
ID = turn2ID,
When = datetime2,
PlayerEntity = player2,
PlayerEntityID = player2.ID,
Dice = new List<DieEntity>
{
numDie,
clrDie
},
Faces = new List<FaceEntity>
{
numFace2,
clrFace2
},
};
dieTurn4 = new() { DieEntityID = numDie.ID, DieEntity = numDie, TurnEntityID = turn2.ID, TurnEntity = turn2 };
dieTurn5 = new() { DieEntityID = clrDie.ID, DieEntity = clrDie, TurnEntityID = turn2.ID, TurnEntity = turn2 };
turn2.DieTurns = new() { dieTurn4, dieTurn5 };
numDie.DieTurns = new() { dieTurn4 };
clrDie.DieTurns = new() { dieTurn5 };
faceTurn4 = new() { FaceEntityID = numFace2.ID, FaceEntity = numFace2, TurnEntityID = turn2.ID, TurnEntity = turn2 };
faceTurn5 = new() { FaceEntityID = clrFace2.ID, FaceEntity = clrFace2, TurnEntityID = turn2.ID, TurnEntity = turn2 };
turn2.FaceTurns = new() { faceTurn4, faceTurn5 };
numFace2.FaceTurns = new() { faceTurn4 };
clrFace2.FaceTurns = new() { faceTurn5 };
turn3 = new()
{
ID = turn2ID,
When = datetime2,
PlayerEntity = player2,
PlayerEntityID = player2.ID,
Dice = new List<DieEntity>
{
numDie,
clrDie
},
Faces = new List<FaceEntity>
{
numFace2,
clrFace2
},
};
}
[Fact]
public void TestGetSetID()
{
// Arrange
TurnEntity turn = new();
Guid expected = Guid.NewGuid();
// Act
turn.ID = expected;
Guid actual = turn.ID;
// Assert
Assert.Equal(expected, actual);
}
[Fact]
public void TestGetSetWhen()
{
// Arrange
TurnEntity turn = new();
DateTime expected = datetime1;
// Act
turn.When = expected;
DateTime actual = turn.When;
// Assert
Assert.Equal(expected, actual);
}
[Fact]
public void TestGetSetPlayer()
{
// Arrange
TurnEntity turn = new();
PlayerEntity expected = player1;
// Act
turn.PlayerEntity = expected;
PlayerEntity actual = turn.PlayerEntity;
// Assert
Assert.Equal(expected, actual);
}
[Fact]
public void TestGetSetPlayerID()
{
// Arrange
TurnEntity turn = new();
Guid expected = player1.ID;
// Act
turn.PlayerEntityID = expected;
Guid actual = turn.PlayerEntityID;
// Assert
Assert.Equal(expected, actual);
}
[Fact]
public void TestGetSetDice()
{
// Arrange
TurnEntity turn = new();
ICollection<DieEntity> expected = new List<DieEntity>
{
numDie,
clrDie,
imgDie
};
// Act
turn.Dice = expected;
ICollection<DieEntity> actual = turn.Dice;
// Assert
Assert.Equal(expected, actual);
}
[Fact]
public void TestGetSetDieTurns()
{
// Arrange
TurnEntity turn = new();
List<DieTurn> expected = new() { dieTurn1, dieTurn2, dieTurn3 };
// Act
turn.DieTurns = expected;
List<DieTurn> actual = turn.DieTurns;
// Assert
Assert.Equal(expected, actual);
}
[Fact]
public void TestGetSetFaces()
{
// Arrange
TurnEntity turn = new();
ICollection<FaceEntity> expected = new List<FaceEntity>
{
numFace1,
clrFace1,
imgFace1
};
// Act
turn.Faces = expected;
ICollection<FaceEntity> actual = turn.Faces;
// Assert
Assert.Equal(expected, actual);
}
[Fact]
public void TestGetSetFaceTurns()
{
// Arrange
TurnEntity turn = new();
List<FaceTurn> expected = new() { faceTurn1, faceTurn2 };
// Act
turn.FaceTurns = expected;
List<FaceTurn> actual = turn.FaceTurns;
// Assert
Assert.Equal(expected, actual);
}
[Fact]
public void TestEqualsWhenNotTurnEntityThenFalse()
{
// Arrange
Point point;
TurnEntity entity;
// Act
point = new(1, 2);
entity = turn1;
// Assert
Assert.False(point.Equals(entity));
Assert.False(entity.Equals(point));
}
[Fact]
public void TestEqualsWhenNullThenFalse()
{
// Arrange
TurnEntity entity;
// Act
entity = turn2;
// Assert
Assert.False(entity.Equals(null));
}
[Fact]
public void TestGoesThruToSecondMethodIfObjIsTypeTurnEntity()
{
// Arrange
object t1;
TurnEntity t2;
// Act
t1 = turn1;
t2 = turn2;
// Assert
Assert.False(t1.Equals(t2));
Assert.False(t2.Equals(t1));
}
[Fact]
public void TestEqualsFalseIfNotSame()
{
// Arrange
TurnEntity t1;
TurnEntity t2;
// Act
t1 = turn1;
t2 = turn2;
// Assert
Assert.False(t1.Equals(t2));
Assert.False(t2.Equals(t1));
}
[Fact]
public void TestEqualsTrueIfSame()
{
// Arrange
TurnEntity t1;
TurnEntity t2;
// Act
t1 = turn2;
t2 = turn3; // turns 2 and 3 should be same as far as Equals is concerned
// Assert
Assert.True(t1.Equals(t2));
Assert.True(t2.Equals(t1));
}
[Fact]
public void TestSameHashFalseIfNotSame()
{
// Arrange
TurnEntity t1;
TurnEntity t2;
// Act
t1 = turn1;
t2 = turn2;
// Assert
Assert.False(t1.GetHashCode().Equals(t2.GetHashCode()));
Assert.False(t2.GetHashCode().Equals(t1.GetHashCode()));
}
[Fact]
public void TestSameHashTrueIfSame()
{
// Arrange
TurnEntity t1;
TurnEntity t2;
// Act
t1 = turn2;
t2 = turn3;
// Assert
Assert.True(t1.GetHashCode().Equals(t2.GetHashCode()));
Assert.True(t2.GetHashCode().Equals(t1.GetHashCode()));
}
}
}

@ -0,0 +1,274 @@
using Data.EF.Dice.Faces;
using Data.EF.Dice;
using Data.EF.Players;
using Model.Dice;
using Model.Players;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using Data.EF.Games;
using System.Drawing;
using Model.Games;
using Newtonsoft.Json.Linq;
using Model.Dice.Faces;
using System.Diagnostics;
namespace Tests.Data_UTs.Games
{
public class TurnExtensionsTest
{
private readonly DateTime datetime1 = new(2013, 2, 1, 1, 19, 4, DateTimeKind.Utc);
private readonly DateTime datetime2 = new(2014, 8, 1, 23, 12, 4, DateTimeKind.Utc);
private readonly PlayerEntity playerEntity = new() { Name = "Paula" };
private readonly DieEntity numDieEntity;
private readonly FaceEntity numFace1Entity = new NumberFaceEntity() { Value = 7 };
private readonly FaceEntity numFace2Entity = new NumberFaceEntity() { Value = 8 };
private readonly DieEntity clrDieEntity;
private readonly FaceEntity clrFace1Entity = new ColorFaceEntity() { A = 255, R = 255, G = 255, B = 255 };
private readonly FaceEntity clrFace2Entity = new ColorFaceEntity() { A = 255, R = 0, G = 0, B = 128 };
private readonly DieEntity imgDieEntity;
private readonly FaceEntity imgFace1Entity = new ImageFaceEntity() { Value = "https://a" };
private readonly FaceEntity imgFace2Entity = new ImageFaceEntity() { Value = "https://b" };
public TurnExtensionsTest()
{
numDieEntity = new NumberDieEntity() { Faces = new List<NumberFaceEntity>() { numFace1Entity as NumberFaceEntity, numFace2Entity as NumberFaceEntity } };
(numFace1Entity as NumberFaceEntity).NumberDieEntity = (NumberDieEntity)numDieEntity;
(numFace2Entity as NumberFaceEntity).NumberDieEntity = (NumberDieEntity)numDieEntity;
clrDieEntity = new ColorDieEntity() { Faces = new List<ColorFaceEntity>() { clrFace1Entity as ColorFaceEntity, clrFace2Entity as ColorFaceEntity } };
(clrFace1Entity as ColorFaceEntity).ColorDieEntity = (ColorDieEntity)clrDieEntity;
(clrFace2Entity as ColorFaceEntity).ColorDieEntity = (ColorDieEntity)clrDieEntity;
imgDieEntity = new ImageDieEntity() { Faces = new List<ImageFaceEntity>() { imgFace1Entity as ImageFaceEntity, imgFace2Entity as ImageFaceEntity } };
(imgFace1Entity as ImageFaceEntity).ImageDieEntity = (ImageDieEntity)imgDieEntity;
(imgFace2Entity as ImageFaceEntity).ImageDieEntity = (ImageDieEntity)imgDieEntity;
}
[Fact]
public void TestToModel()
{
// Arrange
TurnEntity entity = new()
{
When = datetime1,
PlayerEntity = playerEntity,
Dice = new List<DieEntity>
{
numDieEntity,
clrDieEntity,
imgDieEntity
},
Faces = new List<FaceEntity>
{
numFace1Entity,
clrFace2Entity,
imgFace2Entity
}
};
Turn expected = Turn.CreateWithSpecifiedTime(
datetime1,
playerEntity.ToModel(),
new Dictionary<Die, Face>()
{
{(numDieEntity as NumberDieEntity).ToModel(), (numFace1Entity as NumberFaceEntity).ToModel() },
{(clrDieEntity as ColorDieEntity).ToModel(), (clrFace2Entity as ColorFaceEntity).ToModel() },
{(imgDieEntity as ImageDieEntity).ToModel(), (imgFace2Entity as ImageFaceEntity).ToModel() }
});
// Act
Turn actual = entity.ToModel();
// Assert
Assert.True(expected.Equals(actual));
}
[Fact]
public void TestToModels()
{
// Arrange
TurnEntity[] entities = new TurnEntity[]
{
new TurnEntity()
{
When = datetime1,
PlayerEntity = new PlayerEntity() {Name = "Aardvark"},
Dice = new List<DieEntity>
{
numDieEntity,
clrDieEntity
},
Faces = new List<FaceEntity>
{
numFace2Entity,
clrFace2Entity
}
},
new TurnEntity()
{
When = datetime2,
PlayerEntity = new PlayerEntity() {Name = "Chloe"},
Dice = new List<DieEntity>
{
clrDieEntity,
imgDieEntity
},
Faces = new List<FaceEntity>
{
clrFace1Entity,
imgFace1Entity
}
}
};
IEnumerable<Turn> expected = new Turn[]
{
Turn.CreateWithSpecifiedTime(
datetime1,
new("Aardvark"),
new Dictionary<Die, Face>()
{
{(numDieEntity as NumberDieEntity).ToModel(), (numFace2Entity as NumberFaceEntity).ToModel() },
{(clrDieEntity as ColorDieEntity).ToModel(), (clrFace2Entity as ColorFaceEntity).ToModel() },
}),
Turn.CreateWithSpecifiedTime(
datetime2,
new("Chloe"),
new Dictionary<Die, Face>()
{
{(clrDieEntity as ColorDieEntity).ToModel(), (clrFace1Entity as ColorFaceEntity).ToModel() },
{(imgDieEntity as ImageDieEntity).ToModel(), (imgFace1Entity as ImageFaceEntity).ToModel() }
})
}.AsEnumerable();
// Act
IEnumerable<Turn> actual = entities.ToModels();
// Assert
Assert.Equal(expected, actual);
}
[Fact]
public void TestToEntity()
{
// Arrange
Turn model = Turn.CreateWithSpecifiedTime(
datetime1,
playerEntity.ToModel(),
new Dictionary<Die, Face>()
{
{(numDieEntity as NumberDieEntity).ToModel(), (numFace2Entity as NumberFaceEntity).ToModel() },
{(clrDieEntity as ColorDieEntity).ToModel(), (clrFace2Entity as ColorFaceEntity).ToModel() },
{(imgDieEntity as ImageDieEntity).ToModel(), (imgFace1Entity as ImageFaceEntity).ToModel() }
});
TurnEntity expected = new()
{
When = datetime1,
PlayerEntity = playerEntity,
Dice = new List<DieEntity>
{
numDieEntity,
clrDieEntity,
imgDieEntity
},
Faces = new List<FaceEntity>
{
numFace2Entity,
clrFace2Entity,
imgFace1Entity
}
};
// Act
TurnEntity actual = model.ToEntity();
// Assert
Assert.True(expected.Equals(actual));
}
[Fact]
public void TestToEntities()
{
// Arrange
Turn[] models = new Turn[]
{
Turn.CreateWithSpecifiedTime(
datetime2,
new("Mimi"),
new Dictionary<Die, Face>()
{
{(numDieEntity as NumberDieEntity).ToModel(), (numFace2Entity as NumberFaceEntity).ToModel() },
{(clrDieEntity as ColorDieEntity).ToModel(), (clrFace2Entity as ColorFaceEntity).ToModel() },
}),
Turn.CreateWithSpecifiedTime(
datetime1,
new("blaaargh"),
new Dictionary<Die, Face>()
{
{(clrDieEntity as ColorDieEntity).ToModel(), (clrFace1Entity as ColorFaceEntity).ToModel() },
{(imgDieEntity as ImageDieEntity).ToModel(), (imgFace1Entity as ImageFaceEntity).ToModel() }
})
};
IEnumerable<TurnEntity> expected = new TurnEntity[]
{
new TurnEntity()
{
When = datetime2,
PlayerEntity = new PlayerEntity() {Name = "Mimi"},
Dice = new List<DieEntity>
{
numDieEntity,
clrDieEntity
},
Faces = new List<FaceEntity>
{
numFace2Entity,
clrFace2Entity
}
},
new TurnEntity()
{
When = datetime1,
PlayerEntity = new PlayerEntity() {Name = "blaaargh"},
Dice = new List<DieEntity>
{
clrDieEntity,
imgDieEntity
},
Faces = new List<FaceEntity>
{
clrFace1Entity,
imgFace1Entity
}
}
}.AsEnumerable();
// Act
IEnumerable<TurnEntity> actual = models.ToEntities();
// Assert
Assert.Equal(expected, actual);
}
}
}

@ -0,0 +1,688 @@
using Data.EF;
using Data.EF.Players;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
namespace Tests.Data_UTs.Players
{
public class PlayerDbManagerTest
{
private readonly SqliteConnection connection = new("DataSource=:memory:");
private readonly DbContextOptions<DiceAppDbContext> options;
public PlayerDbManagerTest()
{
connection.Open();
options = new DbContextOptionsBuilder<DiceAppDbContext>()
.UseSqlite(connection)
.EnableSensitiveDataLogging()
.Options;
}
[Fact]
public async Task TestConstructorWhenGivenContextThenConstructs()
{
// Arrange
PlayerDbManager mgr;
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
// Assert
Assert.Equal(new Collection<PlayerEntity>(), await mgr.GetAll());
}
}
[Fact]
public void TestConstructorWhenGivenNullThrowsException()
{
// Arrange
PlayerDbManager mgr;
// Act
void action() => mgr = new PlayerDbManager(null);
// Assert
Assert.Throws<ArgumentNullException>(action);
}
[Fact]
public async Task TestAddWhenValidThenValid()
{
// Arrange
string expectedName = "Jeff";
int expectedCount = 2;
PlayerDbManager mgr;
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
await mgr.Add(new PlayerEntity() { Name = expectedName });
await mgr.Add(new PlayerEntity() { Name = "whatever" });
// mgr takes care of the SaveChange() calls internally
// we might use Units of Work later, to optimize our calls to DB
}
// Assert
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
Assert.Equal(expectedName, (await mgr.GetOneByName(expectedName)).Name);
Assert.Equal(expectedCount, (await mgr.GetAll()).Count);
}
}
[Fact]
public async Task TestAddWhenPreExistentThenException()
{
// Arrange
string name = "Flynt";
PlayerDbManager mgr;
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
await mgr.Add(new PlayerEntity() { Name = name });
async Task actionAsync() => await mgr.Add(new PlayerEntity() { Name = name });
// Assert
await Assert.ThrowsAsync<ArgumentException>(actionAsync);
}
}
[Fact]
public void TestAddWhenNullThenException()
{
// Arrange
PlayerDbManager mgr;
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
async Task actionAsync() => await mgr.Add(null);
// Assert
Assert.ThrowsAsync<ArgumentNullException>(actionAsync);
}
}
[Theory]
[InlineData(" ")]
[InlineData(null)]
[InlineData("")]
public void TestAddWhenInvalidNameThenException(string name)
{
// Arrange
PlayerDbManager mgr;
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
async Task actionAsync() => await mgr.Add(new PlayerEntity() { Name = name });
// Assert
Assert.ThrowsAsync<ArgumentException>(actionAsync);
}
}
[Theory]
[InlineData(" ")]
[InlineData(null)]
[InlineData("")]
public async Task TestGetOneByNameWhenInvalidThenException(string name)
{
// Arrange
PlayerDbManager mgr;
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
await mgr.Add(new() { Name = "Ernesto" });
await mgr.Add(new() { Name = "Basil" });
async Task actionAsync() => await mgr.GetOneByName(name);
// Assert
await Assert.ThrowsAsync<ArgumentException>(actionAsync);
}
}
[Theory]
[InlineData("Caroline")]
[InlineData("Caroline ")]
[InlineData(" Caroline")]
public async Task TestGetOneByNameWhenValidAndExistsThenGetsIt(string name)
{
// Arrange
PlayerDbManager mgr;
PlayerEntity actual;
PlayerEntity expected = new() { Name = name.Trim() };
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
await mgr.Add(expected);
await mgr.Add(new() { Name = "Philip" });
}
// Assert
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
actual = await mgr.GetOneByName(name);
Assert.Equal(expected, actual);
}
}
[Fact]
public async Task TestGetOneByNameWhenValidAndNotExistsThenException()
{
// Arrange
PlayerDbManager mgr;
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
//mgr.Add(expected);
await mgr.Add(new() { Name = "Brett" });
await mgr.Add(new() { Name = "Noah" });
async Task actionAsync() => await mgr.GetOneByName("*r^a*éàru é^à");
// Assert
await Assert.ThrowsAsync<InvalidOperationException>(actionAsync);
}
}
[Fact]
public async Task TestIsPresentByNameWhenValidAndExistsThenTrue()
{
// Arrange
PlayerDbManager mgr;
string name = "Gerald";
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
await mgr.Add(new() { Name = "Philip" });
await mgr.Add(new() { Name = name });
}
// Assert
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
Assert.True(await mgr.IsPresentByName(name));
}
}
[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData(null)]
[InlineData("nowaythatthisnameisalreadyinourdatabase")]
public async Task TestIsPresentByNameWhenInvalidOrNonExistentThenFalse(string name)
{
// Arrange
PlayerDbManager mgr;
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
await mgr.Add(new() { Name = "Herman" });
await mgr.Add(new() { Name = "Paulo" });
}
// Assert
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
Assert.False(await mgr.IsPresentByName(name));
}
}
[Fact]
public void TestRemoveWhenNullThenException()
{
// Arrange
PlayerDbManager mgr;
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
void action() => mgr.Remove(null);
// Assert
Assert.Throws<ArgumentNullException>(action);
}
}
[Fact]
public async Task TestRemoveWhenPreExistentThenRemoves()
{
// Arrange
PlayerDbManager mgr;
PlayerEntity toRemove = new() { ID = Guid.NewGuid(), Name = "Please!" };
// Act
using (DiceAppDbContextWithStub db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
await mgr.Add(toRemove); // calls SaveChangesAsync()
mgr.Remove(toRemove);
}
// Assert
using (DiceAppDbContextWithStub db = new(options))
{
db.Database.EnsureCreated();
Assert.DoesNotContain(toRemove, db.PlayerEntity);
}
}
[Fact]
public async Task TestRemoveWhenNonExistentThenStillNonExistent()
{
// Arrange
PlayerDbManager mgr;
PlayerEntity toRemove = new() { ID = Guid.NewGuid(), Name = "Filibert" };
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
await mgr.Add(new() { ID = Guid.NewGuid(), Name = "Bert" });
mgr.Remove(toRemove);
}
// Assert
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
Assert.DoesNotContain(toRemove, await mgr.GetAll());
}
}
[Theory]
[InlineData("filiBert")]
[InlineData("Bertrand")]
public async Task TestUpdateWhenValidThenUpdates(string name)
{
// Arrange
PlayerDbManager mgr;
Guid idBefore = Guid.NewGuid();
PlayerEntity before = new() { ID = idBefore, Name = "Filibert" };
PlayerEntity after = new() { ID = idBefore, Name = name };
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
await mgr.Add(before);
await mgr.Update(before, after);
}
// Assert
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
Assert.DoesNotContain(before, await mgr.GetAll());
Assert.Contains(after, await mgr.GetAll());
}
}
[Theory]
[InlineData("Valerie")]
[InlineData("Valerie ")]
[InlineData(" Valerie")]
public async Task TestUpdateWhenSameThenKeepsAndWorks(string name)
{
// Arrange
PlayerDbManager mgr;
string nameBefore = "Valerie";
Guid idBefore = Guid.NewGuid();
PlayerEntity before = new() { ID = idBefore, Name = nameBefore };
PlayerEntity after = new() { ID = idBefore, Name = name };
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
await mgr.Add(before);
await mgr.Update(before, after);
}
// Assert
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
Assert.Contains(before, await mgr.GetAll());
Assert.Contains(after, await mgr.GetAll());
}
}
[Fact]
public async Task TestUpdateWhenNewIDThenException()
{
// Arrange
PlayerDbManager mgr;
PlayerEntity before = new() { ID = Guid.NewGuid(), Name = "Nova" };
PlayerEntity after = new() { ID = Guid.NewGuid(), Name = "Jacquie" };
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
await mgr.Add(before);
async Task actionAsync() => await mgr.Update(before, after);
// Assert
await Assert.ThrowsAsync<ArgumentException>(actionAsync);
}
}
[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData(null)]
public async Task TestUpdateWhenInvalidThenException(string name)
{
// Arrange
PlayerDbManager mgr;
Guid id = Guid.NewGuid();
PlayerEntity before = new() { ID = id, Name = "Llanfair­pwll­gwyn­gyll­go­gery­chwyrn­drobwll­llan­tysilio­gogo­goch" };
PlayerEntity after = new() { ID = id, Name = name };
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
await mgr.Add(before);
async Task actionAsync() => await mgr.Update(before, after);
// Assert
await Assert.ThrowsAsync<ArgumentException>(actionAsync);
}
}
[Fact]
public async Task TestUpdateWhenNullThenException()
{
// Arrange
PlayerDbManager mgr;
PlayerEntity before = new() { ID = Guid.NewGuid(), Name = "Dwight" };
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
await mgr.Add(before);
async Task actionAsync() => await mgr.Update(before, null);
// Assert
await Assert.ThrowsAsync<ArgumentNullException>(actionAsync);
}
}
[Fact]
public async Task TestGetOneByIDWhenExistsThenGetsIt()
{
// Arrange
PlayerDbManager mgr;
Guid id = Guid.NewGuid();
PlayerEntity actual;
PlayerEntity expected = new() { ID = id, Name = "Hugh" };
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
await mgr.Add(expected);
}
// Assert
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
actual = await mgr.GetOneByID(id);
Assert.Equal(expected, actual);
}
}
[Fact]
public async Task TestGetOneByIDWhenNotExistsThenExceptionAsync()
{
// Arrange
PlayerDbManager mgr;
Guid id = Guid.NewGuid();
PlayerEntity expected = new() { ID = id, Name = "Kyle" };
Guid otherId = Guid.NewGuid();
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
await mgr.Add(expected);
async Task actionAsync() => await mgr.GetOneByID(otherId);
// Assert
await Assert.ThrowsAsync<InvalidOperationException>(actionAsync);
}
}
[Fact]
public async Task TestIsPresentbyIdWhenExistsThenTrue()
{
// Arrange
PlayerDbManager mgr;
Guid id = Guid.NewGuid();
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
await mgr.Add(new() { ID = id, Name = "Bobby" });
}
// Assert
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
Assert.True(await mgr.IsPresentByID(id));
}
}
[Fact]
public async Task TestIsPresentbyIdWhenNotExistsThenFalse()
{
// Arrange
PlayerDbManager mgr;
Guid id = Guid.NewGuid();
Guid otherId;
PlayerEntity presentEntity;
// Act
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
presentEntity = new() { ID = id, Name = "Victor" };
await mgr.Add(presentEntity);
otherId = Guid.NewGuid();
// not added
}
// Assert
using (DiceAppDbContext db = new(options))
{
db.Database.EnsureCreated();
mgr = new(db);
Assert.False(await mgr.IsPresentByID(otherId));
}
}
}
}

@ -0,0 +1,193 @@
using Data.EF.Players;
using System;
using Xunit;
namespace Tests.Data_UTs.Players
{
public class PlayerEntityTest
{
[Fact]
public void TestGetSetName()
{
// Arrange
PlayerEntity player = new();
string expected = "Alice";
// Act
player.Name = expected;
string actual = player.Name;
// Assert
Assert.Equal(expected, actual);
}
[Fact]
public void TestGetSetID()
{
// Arrange
PlayerEntity player = new();
Guid expected = Guid.NewGuid();
// Act
player.ID = expected;
Guid actual = player.ID;
// Assert
Assert.Equal(expected, actual);
}
[Fact]
public void TestToString()
{
// Arrange
PlayerEntity player = new();
string IDString = "c8f60957-dd36-4e47-a7ce-1281f4f8bea4";
string nameString = "Bob";
player.ID = new Guid(IDString);
player.Name = nameString;
// Act
string expected = $"{IDString.ToUpper()} -- {nameString}";
// Assert
Assert.Equal(expected, player.ToString());
}
[Fact]
public void TestEqualsWhenNotPlayerEntityThenFalse()
{
// Arrange
Point point;
PlayerEntity entity;
// Act
point = new(1, 2);
entity = new() { Name = "Clyde" };
// Assert
Assert.False(point.Equals(entity));
Assert.False(entity.Equals(point));
}
[Fact]
public void TestEqualsWhenNullThenFalse()
{
// Arrange
PlayerEntity entity;
// Act
entity = new() { Name = "Clyde" };
// Assert
Assert.False(entity.Equals(null));
}
[Fact]
public void TestGoesThruToSecondMethodIfObjIsTypePlayerEntity()
{
// Arrange
object p1;
PlayerEntity p2;
// Act
p1 = new PlayerEntity() { Name = "Marvin" };
p2 = new() { Name = "Clyde" };
// Assert
Assert.False(p1.Equals(p2));
Assert.False(p2.Equals(p1));
}
[Fact]
public void TestEqualsFalseIfNotSameNameOrID()
{
// Arrange
PlayerEntity p1;
PlayerEntity p2;
PlayerEntity p3;
Guid id1 = Guid.NewGuid();
Guid id2 = Guid.NewGuid();
string name1 = "Panama";
string name2 = "Clyde";
// Act
p1 = new() { ID = id1, Name = name1 };
p2 = new() { ID = id1, Name = name2 };
p3 = new() { ID = id2, Name = name2 };
// Assert
Assert.False(p1.Equals(p2));
Assert.False(p1.Equals(p3));
Assert.False(p2.Equals(p1));
Assert.False(p2.Equals(p3));
Assert.False(p3.Equals(p1));
Assert.False(p3.Equals(p2));
}
[Fact]
public void TestEqualsTrueIfSameIDAndName()
{
// Arrange
PlayerEntity p1;
PlayerEntity p2;
Guid id = Guid.NewGuid();
string name = "Marley";
// Act
p1 = new() { ID = id, Name = name };
p2 = new() { ID = id, Name = name };
// Assert
Assert.True(p1.Equals(p2));
Assert.True(p2.Equals(p1));
}
[Fact]
public void TestSameHashFalseIfNotSameNameOrID()
{
// Arrange
PlayerEntity p1;
PlayerEntity p2;
PlayerEntity p3;
Guid id1 = Guid.NewGuid();
Guid id2 = Guid.NewGuid();
string name1 = "Panama";
string name2 = "Clyde";
// Act
p1 = new() { ID = id1, Name = name1 };
p2 = new() { ID = id1, Name = name2 };
p3 = new() { ID = id2, Name = name2 };
// Assert
Assert.False(p1.GetHashCode().Equals(p2.GetHashCode()));
Assert.False(p1.GetHashCode().Equals(p3.GetHashCode()));
Assert.False(p2.GetHashCode().Equals(p1.GetHashCode()));
Assert.False(p2.GetHashCode().Equals(p3.GetHashCode()));
Assert.False(p3.GetHashCode().Equals(p1.GetHashCode()));
Assert.False(p3.GetHashCode().Equals(p2.GetHashCode()));
}
[Fact]
public void TestSameHashTrueIfSame()
{
// Arrange
PlayerEntity p1;
PlayerEntity p2;
Guid id = Guid.NewGuid();
string name = "Marley";
// Act
p1 = new() { ID = id, Name = name };
p2 = new() { ID = id, Name = name };
// Assert
Assert.True(p1.GetHashCode().Equals(p2.GetHashCode()));
Assert.True(p2.GetHashCode().Equals(p1.GetHashCode()));
}
}
}

@ -0,0 +1,90 @@
using Data.EF.Players;
using Model.Players;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Tests.Data_UTs.Players
{
public class PlayerExtensionsTest
{
[Fact]
public void TestToModel()
{
// Arrange
string name = "Alice";
PlayerEntity entity = new() { Name = name };
Player expected = new(name);
// Act
Player actual = entity.ToModel();
// Assert
Assert.Equal(expected, actual);
}
[Fact]
public void TestToEntity()
{
// Arrange
string name = "Bob";
Player model = new(name);
PlayerEntity expected = new() { Name = name };
// Act
PlayerEntity actual = model.ToEntity();
// Assert
Assert.Equal(expected, actual);
}
[Fact]
public void TestToModels()
{
// Arrange
string n1 = "Alice", n2 = "Bob", n3 = "Clyde";
PlayerEntity[] entities = new PlayerEntity[] {
new() {Name = n1 },
new() {Name = n2 },
new() {Name = n3 },
};
IEnumerable<Player> expected = new Player[] {
new(n1),
new(n2),
new(n3)
}.AsEnumerable();
// Act
IEnumerable<Player> actual = entities.ToModels();
// Assert
Assert.Equal(expected, actual);
}
[Fact]
public void TestToEntities()
{
// Arrange
string n1 = "Alice", n2 = "Bob", n3 = "Clyde";
Player[] models = new Player[] {
new(n1),
new(n2),
new(n3)
};
IEnumerable<PlayerEntity> expected = new PlayerEntity[] {
new() {Name = n1 },
new() {Name = n2 },
new() {Name = n3 },
}.AsEnumerable();
// Act
IEnumerable<PlayerEntity> actual = models.ToEntities();
// Assert
Assert.Equal(expected, actual);
}
}
}

@ -0,0 +1,51 @@
using Model.Dice.Faces;
using Model.Dice;
using System.Collections.Generic;
using System.Drawing;
using Xunit;
namespace Tests.Model_UTs.Dice
{
public class ColorDieTest
{
public static IEnumerable<object[]> Data_Uri()
{
yield return new object[]
{
new ColorFace(Color.FromName("Chocolate")),
new ColorFace(Color.FromName("Aqua")),
new ColorFace(Color.FromName("Beige")),
new ColorFace(Color.FromName("Black")),
new ColorFace(Color.FromName("BurlyWood")),
};
}
[Theory]
[MemberData(nameof(Data_Uri))]
public void RndmFaceTest(ColorFace f1, ColorFace f2, ColorFace f3, ColorFace f4, ColorFace f5)
{
//Arrange
List<ColorFace> listFaces = new() {
f1,f2,f3,f4,f5
};
ColorDie die = new(
listFaces[1],
listFaces[2],
listFaces[3],
listFaces[4]
);
//Act
ColorFace actual = (ColorFace)die.GetRandomFace();
//Assert
Assert.Contains(listFaces, face => face == actual);
}
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tests.Model_UTs.Dice
{
public class DiceGroupManagerTest
{
}
}

@ -0,0 +1,70 @@
using Model.Dice.Faces;
using System.Collections.Generic;
using System.Drawing;
using Xunit;
namespace Tests.Model_UTs.Dice.Faces
{
public class ColorFaceTest
{
public static IEnumerable<object[]> Data_Colors()
{
yield return new object[]
{
Color.FromName("Chocolate"),
Color.FromArgb(144, 255, 78, 240),
};
yield return new object[]
{
Color.FromName("Chocolate"),
Color.FromArgb(144, 255, 78, 240),
};
}
[Theory]
[MemberData(nameof(Data_Colors))]
public void ColorFaceValueTest(Color clrA, Color clrB)
{
//Arrage
ColorFace face1 = new(clrA);
ColorFace face2 = new(clrB);
//Act
Color expected1 = clrA;
Color actual1 = face1.Value;
Color expected2 = clrB;
Color actual2 = face2.Value;
//Assert
Assert.Equal(expected1, actual1);
Assert.Equal(expected2, actual2);
}
[Theory]
[MemberData(nameof(Data_Colors))]
public void ColorFaceValueToStringTest(Color clrA, Color clrB)
{
//Arrage
ColorFace face1 = new(clrA);
ColorFace face2 = new(clrB);
//Act
string expected1 = clrA.ToString();
string actual1 = face1.StringValue;
string expected2 = clrB.ToString();
string actual2 = face2.StringValue;
//Assert
Assert.Equal(expected1, actual1);
Assert.Equal(expected2, actual2);
}
}
}

@ -0,0 +1,70 @@
using Model.Dice.Faces;
using System;
using System.Collections.Generic;
using Xunit;
namespace Tests.Model_UTs.Dice.Faces
{
public class ImageFaceTest
{
public static IEnumerable<object[]> Data_Colors()
{
yield return new object[]
{
new Uri("http://www.contoso.com/"),
new Uri("https://www.pedagojeux.fr/wp-content/uploads/2019/11/1280x720_LoL.jpg"),
};
yield return new object[]
{
new Uri("https://www.lacremedugaming.fr/wp-content/uploads/creme-gaming/2022/02/media-13411.jpg"),
new Uri("https://i1.moyens.net/io/images/2022/01/1642321015_Mises-a-jour-de-VALORANT-en-melee-a-venir-dans.jpg"),
};
}
[Theory]
[MemberData(nameof(Data_Colors))]
public void ImageFaceValueTest(Uri uriA, Uri uriB)
{
//Arrage
ImageFace face1 = new(uriA);
ImageFace face2 = new(uriB);
//Act
Uri expected1 = uriA;
Uri actual1 = face1.Value;
Uri expected2 = uriB;
Uri actual2 = face2.Value;
//Assert
Assert.Equal(expected1, actual1);
Assert.Equal(expected2, actual2);
}
[Theory]
[MemberData(nameof(Data_Colors))]
public void ImageFaceValueToStringTest(Uri uriA, Uri uriB)
{
//Arrage
ImageFace face1 = new(uriA);
ImageFace face2 = new(uriB);
//Act
string expected1 = uriA.ToString();
string actual1 = face1.StringValue;
string expected2 = uriB.ToString();
string actual2 = face2.StringValue;
//Assert
Assert.Equal(expected1, actual1);
Assert.Equal(expected2, actual2);
}
}
}

@ -0,0 +1,51 @@
using Model.Dice.Faces;
using Xunit;
namespace Tests.Model_UTs.Dice.Faces
{
public class NumberFaceTest
{
[Fact]
public void NumberFaceValueTest()
{
//Arrage
NumberFace face1 = new NumberFace(3);
NumberFace face2 = new NumberFace(5);
//Act
int expected1 = 3;
int actual1 = face1.Value;
int expected2 = 5;
int actual2 = face2.Value;
//Assert
Assert.Equal(expected1, actual1);
Assert.Equal(expected2, actual2);
}
[Fact]
public void NumberFaceValueToStringTest()
{
//Arrage
NumberFace face1 = new(3);
NumberFace face2 = new(5);
//Act
string expected1 = 3.ToString();
string actual1 = face1.StringValue;
string expected2 = 5.ToString();
string actual2 = face2.StringValue;
//Assert
Assert.Equal(expected1, actual1);
Assert.Equal(expected2, actual2);
}
}
}

@ -0,0 +1,53 @@
using Model.Dice.Faces;
using Model.Dice;
using System;
using System.Collections.Generic;
using Xunit;
namespace Tests.Model_UTs.Dice
{
public class ImageDieTest
{
public static IEnumerable<object[]> Data_Uri()
{
yield return new object[]
{
new ImageFace(new Uri("https://nothing1/")),
new ImageFace(new Uri("https://nothing2/")),
new ImageFace(new Uri("https://nothing3/")),
new ImageFace(new Uri("https://nothing4/")),
new ImageFace(new Uri("https://nothing5/")),
};
}
[Theory]
[MemberData(nameof(Data_Uri))]
public void RndmFaceTest(ImageFace f1, ImageFace f2, ImageFace f3, ImageFace f4, ImageFace f5)
{
//Arrange
List<ImageFace> listFaces = new() {
f1,f2,f3,f4,f5
};
ImageDie die = new(
listFaces[1],
listFaces[2],
listFaces[3],
listFaces[4]
);
//Act
ImageFace actual = (ImageFace)die.GetRandomFace();
//Assert
Assert.Contains(listFaces, face => face == actual);
}
}
}

@ -0,0 +1,41 @@
using Model.Dice;
using Model.Dice.Faces;
using System.Collections.Generic;
using Xunit;
namespace Tests.Model_UTs.Dice
{
public class NumberDieTest
{
[Fact]
public void RndmFaceTest()
{
//Arrange
List<NumberFace> listFaces = new() {
new NumberFace(1),
new NumberFace(2),
new NumberFace(3),
new NumberFace(4),
new NumberFace(5),
};
NumberDie die = new(
listFaces[1],
listFaces[2],
listFaces[3],
listFaces[4]
);
//Act
NumberFace actual = (NumberFace)die.GetRandomFace();
//Assert
Assert.Contains(listFaces, face => face == actual);
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save