├── .github
└── workflows
│ └── azure-swa.yml
├── .gitignore
├── Directory.Build.props
├── NuGet.Config
├── README.md
├── Wordle.Android
├── MainActivity.cs
├── Properties
│ └── AndroidManifest.xml
├── Resources
│ ├── AboutResources.txt
│ ├── drawable-night-v31
│ │ └── avalonia_anim.xml
│ ├── drawable-v31
│ │ └── avalonia_anim.xml
│ ├── drawable
│ │ └── splash_screen.xml
│ ├── values-night
│ │ └── colors.xml
│ ├── values-v31
│ │ └── styles.xml
│ └── values
│ │ ├── colors.xml
│ │ └── styles.xml
└── Wordle.Android.csproj
├── Wordle.Browser
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Wordle.Browser.csproj
├── runtimeconfig.template.json
└── wwwroot
│ ├── Logo.svg
│ ├── app.css
│ ├── favicon.ico
│ ├── index.html
│ └── main.js
├── Wordle.Desktop
├── Program.cs
├── Wordle.Desktop.csproj
└── app.manifest
├── Wordle.iOS
├── AppDelegate.cs
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ ├── 100.png
│ │ ├── 1024.png
│ │ ├── 114.png
│ │ ├── 120.png
│ │ ├── 128.png
│ │ ├── 144.png
│ │ ├── 152.png
│ │ ├── 16.png
│ │ ├── 167.png
│ │ ├── 172.png
│ │ ├── 180.png
│ │ ├── 196.png
│ │ ├── 20.png
│ │ ├── 216.png
│ │ ├── 256.png
│ │ ├── 29.png
│ │ ├── 32.png
│ │ ├── 40.png
│ │ ├── 48.png
│ │ ├── 50.png
│ │ ├── 512.png
│ │ ├── 55.png
│ │ ├── 57.png
│ │ ├── 58.png
│ │ ├── 60.png
│ │ ├── 64.png
│ │ ├── 72.png
│ │ ├── 76.png
│ │ ├── 80.png
│ │ ├── 87.png
│ │ ├── 88.png
│ │ └── Contents.json
├── Entitlements.plist
├── Info.plist
├── Main.cs
├── Resources
│ └── LaunchScreen.xib
└── Wordle.iOS.csproj
├── Wordle.sln
├── Wordle.sln.DotSettings
└── Wordle
├── App.axaml
├── App.axaml.cs
├── Assets
└── Icon.png
├── Messages
├── KeyPressedMessage.cs
├── LetterGuessedMessage.cs
└── PushNotificationMessage.cs
├── Models
├── Constants.cs
├── Enums
│ └── LetterState.cs
├── GameModel.cs
├── GuessResult.cs
├── LetterModel.cs
├── ObservableModel.cs
└── WordModel.cs
├── Services
├── IGuessValidationService.cs
├── IThemeService.cs
├── IWordProvider.cs
└── Impl
│ ├── GuessValidationServiceImpl.cs
│ ├── ThemeServiceImpl.cs
│ └── WordProviderImpl.cs
├── ViewModels
├── GameViewModel.cs
├── KeyboardViewModel.cs
├── LetterViewModel.cs
├── MainViewModel.cs
├── NotificationsViewModel.cs
├── ObservableViewModel.cs
├── SettingsViewModel.cs
├── StatisticsViewModel.cs
├── ViewModelBase.cs
└── WordViewModel.cs
├── Views
├── Controls
│ ├── KeyboardLetterButton.axaml
│ ├── KeyboardLetterButton.axaml.cs
│ ├── Letter.axaml
│ └── Letter.axaml.cs
├── GameView.axaml
├── GameView.axaml.cs
├── KeyboardView.axaml
├── KeyboardView.axaml.cs
├── MainView.axaml
├── MainView.axaml.cs
├── MainWindow.axaml
└── MainWindow.axaml.cs
└── Wordle.csproj
/.github/workflows/azure-swa.yml:
--------------------------------------------------------------------------------
1 | name: Azure Static Web Apps CI/CD
2 |
3 | on:
4 | push:
5 | branches:
6 | - release/*
7 |
8 | jobs:
9 | build_and_deploy_job:
10 | runs-on: ubuntu-latest
11 | name: Build and Deploy Job
12 | steps:
13 | - uses: actions/checkout@v2
14 | with:
15 | submodules: recursive
16 |
17 | - name: Setup .NET 8
18 | uses: actions/setup-dotnet@v3
19 | with:
20 | dotnet-version: 8.0.203
21 |
22 | - name: Install wasm-tools
23 | run: dotnet workload install wasm-tools
24 |
25 | - name: Publish .NET Project
26 | run: dotnet publish Wordle.Browser/Wordle.Browser.csproj -c Release
27 |
28 | - name: Build And Deploy
29 | id: builddeploy
30 | uses: Azure/static-web-apps-deploy@v1
31 | with:
32 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APP_DEPLOY_KEY }}
33 | repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
34 | action: "upload"
35 | ###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
36 | # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
37 | app_location: "Wordle.Browser/bin/Release/net8.0-browser/publish/wwwroot/" # App source code path
38 | api_location: "" # Api source code path - optional
39 | output_location: "" # Modify this to where your SSG places the built HTML - could be `dist`, `build`... check your config
40 | skip_app_build: true
41 | ###### End of Repository/Build Configurations ######
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # Tye
66 | .tye/
67 |
68 | # ASP.NET Scaffolding
69 | ScaffoldingReadMe.txt
70 |
71 | # StyleCop
72 | StyleCopReport.xml
73 |
74 | # Files built by Visual Studio
75 | *_i.c
76 | *_p.c
77 | *_h.h
78 | *.ilk
79 | *.meta
80 | *.obj
81 | *.iobj
82 | *.pch
83 | *.pdb
84 | *.ipdb
85 | *.pgc
86 | *.pgd
87 | *.rsp
88 | *.sbr
89 | *.tlb
90 | *.tli
91 | *.tlh
92 | *.tmp
93 | *.tmp_proj
94 | *_wpftmp.csproj
95 | *.log
96 | *.vspscc
97 | *.vssscc
98 | .builds
99 | *.pidb
100 | *.svclog
101 | *.scc
102 |
103 | # Chutzpah Test files
104 | _Chutzpah*
105 |
106 | # Visual C++ cache files
107 | ipch/
108 | *.aps
109 | *.ncb
110 | *.opendb
111 | *.opensdf
112 | *.sdf
113 | *.cachefile
114 | *.VC.db
115 | *.VC.VC.opendb
116 |
117 | # Visual Studio profiler
118 | *.psess
119 | *.vsp
120 | *.vspx
121 | *.sap
122 |
123 | # Visual Studio Trace Files
124 | *.e2e
125 |
126 | # TFS 2012 Local Workspace
127 | $tf/
128 |
129 | # Guidance Automation Toolkit
130 | *.gpState
131 |
132 | # ReSharper is a .NET coding add-in
133 | _ReSharper*/
134 | *.[Rr]e[Ss]harper
135 | *.DotSettings.user
136 |
137 | # TeamCity is a build add-in
138 | _TeamCity*
139 |
140 | # DotCover is a Code Coverage Tool
141 | *.dotCover
142 |
143 | # AxoCover is a Code Coverage Tool
144 | .axoCover/*
145 | !.axoCover/settings.json
146 |
147 | # Coverlet is a free, cross platform Code Coverage Tool
148 | coverage*.json
149 | coverage*.xml
150 | coverage*.info
151 |
152 | # Visual Studio code coverage results
153 | *.coverage
154 | *.coveragexml
155 |
156 | # NCrunch
157 | _NCrunch_*
158 | .*crunch*.local.xml
159 | nCrunchTemp_*
160 |
161 | # MightyMoose
162 | *.mm.*
163 | AutoTest.Net/
164 |
165 | # Web workbench (sass)
166 | .sass-cache/
167 |
168 | # Installshield output folder
169 | [Ee]xpress/
170 |
171 | # DocProject is a documentation generator add-in
172 | DocProject/buildhelp/
173 | DocProject/Help/*.HxT
174 | DocProject/Help/*.HxC
175 | DocProject/Help/*.hhc
176 | DocProject/Help/*.hhk
177 | DocProject/Help/*.hhp
178 | DocProject/Help/Html2
179 | DocProject/Help/html
180 |
181 | # Click-Once directory
182 | publish/
183 |
184 | # Publish Web Output
185 | *.[Pp]ublish.xml
186 | *.azurePubxml
187 | # Note: Comment the next line if you want to checkin your web deploy settings,
188 | # but database connection strings (with potential passwords) will be unencrypted
189 | *.pubxml
190 | *.publishproj
191 |
192 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
193 | # checkin your Azure Web App publish settings, but sensitive information contained
194 | # in these scripts will be unencrypted
195 | PublishScripts/
196 |
197 | # NuGet Packages
198 | *.nupkg
199 | # NuGet Symbol Packages
200 | *.snupkg
201 | # The packages folder can be ignored because of Package Restore
202 | **/[Pp]ackages/*
203 | # except build/, which is used as an MSBuild target.
204 | !**/[Pp]ackages/build/
205 | # Uncomment if necessary however generally it will be regenerated when needed
206 | #!**/[Pp]ackages/repositories.config
207 | # NuGet v3's project.json files produces more ignorable files
208 | *.nuget.props
209 | *.nuget.targets
210 |
211 | # Microsoft Azure Build Output
212 | csx/
213 | *.build.csdef
214 |
215 | # Microsoft Azure Emulator
216 | ecf/
217 | rcf/
218 |
219 | # Windows Store app package directories and files
220 | AppPackages/
221 | BundleArtifacts/
222 | Package.StoreAssociation.xml
223 | _pkginfo.txt
224 | *.appx
225 | *.appxbundle
226 | *.appxupload
227 |
228 | # Visual Studio cache files
229 | # files ending in .cache can be ignored
230 | *.[Cc]ache
231 | # but keep track of directories ending in .cache
232 | !?*.[Cc]ache/
233 |
234 | # Others
235 | ClientBin/
236 | ~$*
237 | *~
238 | *.dbmdl
239 | *.dbproj.schemaview
240 | *.jfm
241 | *.pfx
242 | *.publishsettings
243 | orleans.codegen.cs
244 |
245 | # Including strong name files can present a security risk
246 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
247 | #*.snk
248 |
249 | # Since there are multiple workflows, uncomment next line to ignore bower_components
250 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
251 | #bower_components/
252 |
253 | # RIA/Silverlight projects
254 | Generated_Code/
255 |
256 | # Backup & report files from converting an old project file
257 | # to a newer Visual Studio version. Backup files are not needed,
258 | # because we have git ;-)
259 | _UpgradeReport_Files/
260 | Backup*/
261 | UpgradeLog*.XML
262 | UpgradeLog*.htm
263 | ServiceFabricBackup/
264 | *.rptproj.bak
265 |
266 | # SQL Server files
267 | *.mdf
268 | *.ldf
269 | *.ndf
270 |
271 | # Business Intelligence projects
272 | *.rdl.data
273 | *.bim.layout
274 | *.bim_*.settings
275 | *.rptproj.rsuser
276 | *- [Bb]ackup.rdl
277 | *- [Bb]ackup ([0-9]).rdl
278 | *- [Bb]ackup ([0-9][0-9]).rdl
279 |
280 | # Microsoft Fakes
281 | FakesAssemblies/
282 |
283 | # GhostDoc plugin setting file
284 | *.GhostDoc.xml
285 |
286 | # Node.js Tools for Visual Studio
287 | .ntvs_analysis.dat
288 | node_modules/
289 |
290 | # Visual Studio 6 build log
291 | *.plg
292 |
293 | # Visual Studio 6 workspace options file
294 | *.opt
295 |
296 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
297 | *.vbw
298 |
299 | # Visual Studio LightSwitch build output
300 | **/*.HTMLClient/GeneratedArtifacts
301 | **/*.DesktopClient/GeneratedArtifacts
302 | **/*.DesktopClient/ModelManifest.xml
303 | **/*.Server/GeneratedArtifacts
304 | **/*.Server/ModelManifest.xml
305 | _Pvt_Extensions
306 |
307 | # Paket dependency manager
308 | .paket/paket.exe
309 | paket-files/
310 |
311 | # FAKE - F# Make
312 | .fake/
313 |
314 | # CodeRush personal settings
315 | .cr/personal
316 |
317 | # Python Tools for Visual Studio (PTVS)
318 | __pycache__/
319 | *.pyc
320 |
321 | # Cake - Uncomment if you are using it
322 | # tools/**
323 | # !tools/packages.config
324 |
325 | # Tabs Studio
326 | *.tss
327 |
328 | # Telerik's JustMock configuration file
329 | *.jmconfig
330 |
331 | # BizTalk build output
332 | *.btp.cs
333 | *.btm.cs
334 | *.odx.cs
335 | *.xsd.cs
336 |
337 | # OpenCover UI analysis results
338 | OpenCover/
339 |
340 | # Azure Stream Analytics local run output
341 | ASALocalRun/
342 |
343 | # MSBuild Binary and Structured Log
344 | *.binlog
345 |
346 | # NVidia Nsight GPU debugger configuration file
347 | *.nvuser
348 |
349 | # MFractors (Xamarin productivity tool) working folder
350 | .mfractor/
351 |
352 | # Local History for Visual Studio
353 | .localhistory/
354 |
355 | # BeatPulse healthcheck temp database
356 | healthchecksdb
357 |
358 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
359 | MigrationBackup/
360 |
361 | # Ionide (cross platform F# VS Code tools) working folder
362 | .ionide/
363 |
364 | # Fody - auto-generated XML schema
365 | FodyWeavers.xsd
366 |
367 | ##
368 | ## Visual studio for Mac
369 | ##
370 |
371 |
372 | # globs
373 | Makefile.in
374 | *.userprefs
375 | *.usertasks
376 | config.make
377 | config.status
378 | aclocal.m4
379 | install-sh
380 | autom4te.cache/
381 | *.tar.gz
382 | tarballs/
383 | test-results/
384 |
385 | # Mac bundle stuff
386 | *.dmg
387 | *.app
388 |
389 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
390 | # General
391 | .DS_Store
392 | .AppleDouble
393 | .LSOverride
394 |
395 | # Icon must end with two \r
396 | Icon
397 |
398 |
399 | # Thumbnails
400 | ._*
401 |
402 | # Files that might appear in the root of a volume
403 | .DocumentRevisions-V100
404 | .fseventsd
405 | .Spotlight-V100
406 | .TemporaryItems
407 | .Trashes
408 | .VolumeIcon.icns
409 | .com.apple.timemachine.donotpresent
410 |
411 | # Directories potentially created on remote AFP share
412 | .AppleDB
413 | .AppleDesktop
414 | Network Trash Folder
415 | Temporary Items
416 | .apdisk
417 |
418 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
419 | # Windows thumbnail cache files
420 | Thumbs.db
421 | ehthumbs.db
422 | ehthumbs_vista.db
423 |
424 | # Dump file
425 | *.stackdump
426 |
427 | # Folder config file
428 | [Dd]esktop.ini
429 |
430 | # Recycle Bin used on file shares
431 | $RECYCLE.BIN/
432 |
433 | # Windows Installer files
434 | *.cab
435 | *.msi
436 | *.msix
437 | *.msm
438 | *.msp
439 |
440 | # Windows shortcuts
441 | *.lnk
442 |
443 | # JetBrains Rider
444 | .idea/
445 | *.sln.iml
446 |
447 | ##
448 | ## Visual Studio Code
449 | ##
450 | .vscode/*
451 | !.vscode/settings.json
452 | !.vscode/tasks.json
453 | !.vscode/launch.json
454 | !.vscode/extensions.json
455 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | enable
4 | 11.1.0-beta1
5 | latest
6 | true
7 |
8 |
9 |
--------------------------------------------------------------------------------
/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Wordle-onia
2 |
3 | Wordle-onia is a port of Wordle to Avalonia.
4 |
5 | Much of the code is based on https://github.com/xEsteem/WPFordle
6 |
7 | Wordle-onia is a simple application that shows Avalonia running on Desktop, Mobile and Web.
8 |
9 | For instructions on how to prepare your system to build for mobile follow the documentation at:
10 | [Android docs](https://docs.avaloniaui.net/docs/guides/platforms/android/) and [iOS docs](https://docs.avaloniaui.net/docs/guides/platforms/ios/)
11 |
12 | 
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Wordle.Android/MainActivity.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Content.PM;
3 | using Avalonia;
4 | using Avalonia.Android;
5 |
6 | namespace Wordle.Android;
7 |
8 | [Activity(
9 | Label = "Wordle.Android",
10 | Theme = "@style/MyTheme.NoActionBar",
11 | Icon = "@drawable/icon",
12 | MainLauncher = true,
13 | ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
14 | public class MainActivity : AvaloniaMainActivity
15 | {
16 | protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
17 | {
18 | return base.CustomizeAppBuilder(builder);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Wordle.Android/Properties/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Wordle.Android/Resources/AboutResources.txt:
--------------------------------------------------------------------------------
1 | Images, layout descriptions, binary blobs and string dictionaries can be included
2 | in your application as resource files. Various Android APIs are designed to
3 | operate on the resource IDs instead of dealing with images, strings or binary blobs
4 | directly.
5 |
6 | For example, a sample Android app that contains a user interface layout (main.axml),
7 | an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
8 | would keep its resources in the "Resources" directory of the application:
9 |
10 | Resources/
11 | drawable/
12 | icon.png
13 |
14 | layout/
15 | main.axml
16 |
17 | values/
18 | strings.xml
19 |
20 | In order to get the build system to recognize Android resources, set the build action to
21 | "AndroidResource". The native Android APIs do not operate directly with filenames, but
22 | instead operate on resource IDs. When you compile an Android application that uses resources,
23 | the build system will package the resources for distribution and generate a class called "R"
24 | (this is an Android convention) that contains the tokens for each one of the resources
25 | included. For example, for the above Resources layout, this is what the R class would expose:
26 |
27 | public class R {
28 | public class drawable {
29 | public const int icon = 0x123;
30 | }
31 |
32 | public class layout {
33 | public const int main = 0x456;
34 | }
35 |
36 | public class strings {
37 | public const int first_string = 0xabc;
38 | public const int second_string = 0xbcd;
39 | }
40 | }
41 |
42 | You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main
43 | to reference the layout/main.axml file, or R.strings.first_string to reference the first
44 | string in the dictionary file values/strings.xml.
--------------------------------------------------------------------------------
/Wordle.Android/Resources/drawable-night-v31/avalonia_anim.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
11 |
15 |
16 |
20 |
25 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
42 |
43 |
44 |
45 |
46 |
53 |
54 |
55 |
56 |
57 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/Wordle.Android/Resources/drawable-v31/avalonia_anim.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
11 |
15 |
16 |
21 |
27 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
46 |
47 |
48 |
49 |
50 |
57 |
58 |
59 |
60 |
61 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/Wordle.Android/Resources/drawable/splash_screen.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Wordle.Android/Resources/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #212121
4 |
5 |
--------------------------------------------------------------------------------
/Wordle.Android/Resources/values-v31/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
17 |
21 |
22 |
--------------------------------------------------------------------------------
/Wordle.Android/Resources/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
5 |
--------------------------------------------------------------------------------
/Wordle.Android/Resources/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/Wordle.Android/Wordle.Android.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net8.0-android
5 | 21
6 | enable
7 | com.Avalonia.Wordle
8 | 1
9 | 1.0
10 | apk
11 | false
12 |
13 |
14 |
15 |
16 | Resources\drawable\Icon.png
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Wordle.Browser/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Versioning;
2 | using System.Threading.Tasks;
3 | using Avalonia;
4 | using Avalonia.Browser;
5 | using Wordle;
6 |
7 | [assembly: SupportedOSPlatform("browser")]
8 |
9 | internal sealed partial class Program
10 | {
11 | private static Task Main(string[] args) => BuildAvaloniaApp()
12 | .StartBrowserAppAsync("out");
13 |
14 | public static AppBuilder BuildAvaloniaApp()
15 | => AppBuilder.Configure();
16 | }
--------------------------------------------------------------------------------
/Wordle.Browser/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Wordle.Browser": {
4 | "commandName": "Project",
5 | "launchBrowser": true,
6 | "environmentVariables": {
7 | "ASPNETCORE_ENVIRONMENT": "Development"
8 | },
9 | "applicationUrl": "https://localhost:5001;http://localhost:5000",
10 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/debug?browser={browserInspectUri}"
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/Wordle.Browser/Wordle.Browser.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net8.0-browser
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Wordle.Browser/runtimeconfig.template.json:
--------------------------------------------------------------------------------
1 | {
2 | "wasmHostProperties": {
3 | "perHostConfig": [
4 | {
5 | "name": "browser",
6 | "html-path": "index.html",
7 | "Host": "browser"
8 | }
9 | ]
10 | }
11 | }
--------------------------------------------------------------------------------
/Wordle.Browser/wwwroot/Logo.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/Wordle.Browser/wwwroot/app.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --sat: env(safe-area-inset-top);
3 | --sar: env(safe-area-inset-right);
4 | --sab: env(safe-area-inset-bottom);
5 | --sal: env(safe-area-inset-left);
6 | }
7 |
8 | /* HTML styles for the splash screen */
9 |
10 | .highlight {
11 | color: white;
12 | font-size: 2.5rem;
13 | display: block;
14 | }
15 |
16 | .purple {
17 | color: #8b44ac;
18 | }
19 |
20 | .icon {
21 | opacity: 0.05;
22 | height: 35%;
23 | width: 35%;
24 | position: absolute;
25 | background-repeat: no-repeat;
26 | right: 0px;
27 | bottom: 0px;
28 | margin-right: 3%;
29 | margin-bottom: 5%;
30 | z-index: 5000;
31 | background-position: right bottom;
32 | pointer-events: none;
33 | }
34 |
35 | #avalonia-splash a {
36 | color: whitesmoke;
37 | text-decoration: none;
38 | }
39 |
40 | .center {
41 | display: flex;
42 | justify-content: center;
43 | align-items: center;
44 | height: 100vh;
45 | }
46 |
47 | #avalonia-splash {
48 | position: relative;
49 | height: 100%;
50 | width: 100%;
51 | color: whitesmoke;
52 | background: #1b2a4e;
53 | font-family: 'Nunito', sans-serif;
54 | background-position: center;
55 | background-size: cover;
56 | background-repeat: no-repeat;
57 | justify-content: center;
58 | align-items: center;
59 | }
60 |
61 | .splash-close {
62 | animation: fadeout 0.25s linear forwards;
63 | }
64 |
65 | @keyframes fadeout {
66 | 0% {
67 | opacity: 100%;
68 | }
69 |
70 | 100% {
71 | opacity: 0;
72 | visibility: collapse;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Wordle.Browser/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.Browser/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/Wordle.Browser/wwwroot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Wordle.Browser
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
24 |

25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Wordle.Browser/wwwroot/main.js:
--------------------------------------------------------------------------------
1 | import { dotnet } from './_framework/dotnet.js'
2 |
3 | const is_browser = typeof window != "undefined";
4 | if (!is_browser) throw new Error(`Expected to be running in a browser`);
5 |
6 | const dotnetRuntime = await dotnet
7 | .withDiagnosticTracing(false)
8 | .withApplicationArgumentsFromQuery()
9 | .create();
10 |
11 | const config = dotnetRuntime.getConfig();
12 |
13 | await dotnetRuntime.runMain(config.mainAssemblyName, [window.location.search]);
14 |
--------------------------------------------------------------------------------
/Wordle.Desktop/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Avalonia;
3 |
4 | namespace Wordle.Desktop;
5 |
6 | sealed class Program
7 | {
8 | // Initialization code. Don't use any Avalonia, third-party APIs or any
9 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
10 | // yet and stuff might break.
11 | [STAThread]
12 | public static void Main(string[] args) => BuildAvaloniaApp()
13 | .StartWithClassicDesktopLifetime(args);
14 |
15 | // Avalonia configuration, don't remove; also used by visual designer.
16 | public static AppBuilder BuildAvaloniaApp()
17 | => AppBuilder.Configure()
18 | .UsePlatformDetect()
19 | .LogToTrace();
20 | }
21 |
--------------------------------------------------------------------------------
/Wordle.Desktop/Wordle.Desktop.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 |
6 | net8.0
7 | enable
8 | true
9 |
10 |
11 |
12 | app.manifest
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Wordle.Desktop/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Wordle.iOS/AppDelegate.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 | using Avalonia;
3 | using Avalonia.iOS;
4 |
5 | namespace Wordle.iOS;
6 |
7 | // The UIApplicationDelegate for the application. This class is responsible for launching the
8 | // User Interface of the application, as well as listening (and optionally responding) to
9 | // application events from iOS.
10 | [Register("AppDelegate")]
11 | #pragma warning disable CA1711 // Identifiers should not have incorrect suffix
12 | public partial class AppDelegate : AvaloniaAppDelegate
13 | #pragma warning restore CA1711 // Identifiers should not have incorrect suffix
14 | {
15 | protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
16 | {
17 | return base.CustomizeAppBuilder(builder);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/100.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/1024.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/114.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/120.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/128.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/144.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/152.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/16.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/167.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/172.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/172.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/180.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/196.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/196.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/20.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/216.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/216.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/256.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/29.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/32.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/40.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/48.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/50.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/512.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/55.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/55.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/57.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/58.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/60.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/64.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/72.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/76.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/80.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/87.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/88.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/88.png
--------------------------------------------------------------------------------
/Wordle.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "40.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "60.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "58.png",
17 | "idiom" : "iphone",
18 | "scale" : "2x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "87.png",
23 | "idiom" : "iphone",
24 | "scale" : "3x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "80.png",
29 | "idiom" : "iphone",
30 | "scale" : "2x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "filename" : "120.png",
35 | "idiom" : "iphone",
36 | "scale" : "3x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "120.png",
41 | "idiom" : "iphone",
42 | "scale" : "2x",
43 | "size" : "60x60"
44 | },
45 | {
46 | "filename" : "180.png",
47 | "idiom" : "iphone",
48 | "scale" : "3x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "20.png",
53 | "idiom" : "ipad",
54 | "scale" : "1x",
55 | "size" : "20x20"
56 | },
57 | {
58 | "filename" : "40.png",
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "20x20"
62 | },
63 | {
64 | "filename" : "29.png",
65 | "idiom" : "ipad",
66 | "scale" : "1x",
67 | "size" : "29x29"
68 | },
69 | {
70 | "filename" : "58.png",
71 | "idiom" : "ipad",
72 | "scale" : "2x",
73 | "size" : "29x29"
74 | },
75 | {
76 | "filename" : "40.png",
77 | "idiom" : "ipad",
78 | "scale" : "1x",
79 | "size" : "40x40"
80 | },
81 | {
82 | "filename" : "80.png",
83 | "idiom" : "ipad",
84 | "scale" : "2x",
85 | "size" : "40x40"
86 | },
87 | {
88 | "filename" : "76.png",
89 | "idiom" : "ipad",
90 | "scale" : "1x",
91 | "size" : "76x76"
92 | },
93 | {
94 | "filename" : "152.png",
95 | "idiom" : "ipad",
96 | "scale" : "2x",
97 | "size" : "76x76"
98 | },
99 | {
100 | "filename" : "167.png",
101 | "idiom" : "ipad",
102 | "scale" : "2x",
103 | "size" : "83.5x83.5"
104 | },
105 | {
106 | "filename" : "1024.png",
107 | "idiom" : "ios-marketing",
108 | "scale" : "1x",
109 | "size" : "1024x1024"
110 | },
111 | {
112 | "idiom" : "car",
113 | "scale" : "2x",
114 | "size" : "60x60"
115 | },
116 | {
117 | "idiom" : "car",
118 | "scale" : "3x",
119 | "size" : "60x60"
120 | },
121 | {
122 | "filename" : "48.png",
123 | "idiom" : "watch",
124 | "role" : "notificationCenter",
125 | "scale" : "2x",
126 | "size" : "24x24",
127 | "subtype" : "38mm"
128 | },
129 | {
130 | "filename" : "55.png",
131 | "idiom" : "watch",
132 | "role" : "notificationCenter",
133 | "scale" : "2x",
134 | "size" : "27.5x27.5",
135 | "subtype" : "42mm"
136 | },
137 | {
138 | "filename" : "58.png",
139 | "idiom" : "watch",
140 | "role" : "companionSettings",
141 | "scale" : "2x",
142 | "size" : "29x29"
143 | },
144 | {
145 | "filename" : "87.png",
146 | "idiom" : "watch",
147 | "role" : "companionSettings",
148 | "scale" : "3x",
149 | "size" : "29x29"
150 | },
151 | {
152 | "idiom" : "watch",
153 | "role" : "notificationCenter",
154 | "scale" : "2x",
155 | "size" : "33x33",
156 | "subtype" : "45mm"
157 | },
158 | {
159 | "filename" : "80.png",
160 | "idiom" : "watch",
161 | "role" : "appLauncher",
162 | "scale" : "2x",
163 | "size" : "40x40",
164 | "subtype" : "38mm"
165 | },
166 | {
167 | "filename" : "88.png",
168 | "idiom" : "watch",
169 | "role" : "appLauncher",
170 | "scale" : "2x",
171 | "size" : "44x44",
172 | "subtype" : "40mm"
173 | },
174 | {
175 | "idiom" : "watch",
176 | "role" : "appLauncher",
177 | "scale" : "2x",
178 | "size" : "46x46",
179 | "subtype" : "41mm"
180 | },
181 | {
182 | "filename" : "100.png",
183 | "idiom" : "watch",
184 | "role" : "appLauncher",
185 | "scale" : "2x",
186 | "size" : "50x50",
187 | "subtype" : "44mm"
188 | },
189 | {
190 | "idiom" : "watch",
191 | "role" : "appLauncher",
192 | "scale" : "2x",
193 | "size" : "51x51",
194 | "subtype" : "45mm"
195 | },
196 | {
197 | "filename" : "172.png",
198 | "idiom" : "watch",
199 | "role" : "quickLook",
200 | "scale" : "2x",
201 | "size" : "86x86",
202 | "subtype" : "38mm"
203 | },
204 | {
205 | "filename" : "196.png",
206 | "idiom" : "watch",
207 | "role" : "quickLook",
208 | "scale" : "2x",
209 | "size" : "98x98",
210 | "subtype" : "42mm"
211 | },
212 | {
213 | "filename" : "216.png",
214 | "idiom" : "watch",
215 | "role" : "quickLook",
216 | "scale" : "2x",
217 | "size" : "108x108",
218 | "subtype" : "44mm"
219 | },
220 | {
221 | "idiom" : "watch",
222 | "role" : "quickLook",
223 | "scale" : "2x",
224 | "size" : "117x117",
225 | "subtype" : "45mm"
226 | },
227 | {
228 | "filename" : "1024.png",
229 | "idiom" : "watch-marketing",
230 | "scale" : "1x",
231 | "size" : "1024x1024"
232 | },
233 | {
234 | "filename" : "16.png",
235 | "idiom" : "mac",
236 | "scale" : "1x",
237 | "size" : "16x16"
238 | },
239 | {
240 | "filename" : "32.png",
241 | "idiom" : "mac",
242 | "scale" : "2x",
243 | "size" : "16x16"
244 | },
245 | {
246 | "filename" : "32.png",
247 | "idiom" : "mac",
248 | "scale" : "1x",
249 | "size" : "32x32"
250 | },
251 | {
252 | "filename" : "64.png",
253 | "idiom" : "mac",
254 | "scale" : "2x",
255 | "size" : "32x32"
256 | },
257 | {
258 | "filename" : "128.png",
259 | "idiom" : "mac",
260 | "scale" : "1x",
261 | "size" : "128x128"
262 | },
263 | {
264 | "filename" : "256.png",
265 | "idiom" : "mac",
266 | "scale" : "2x",
267 | "size" : "128x128"
268 | },
269 | {
270 | "filename" : "256.png",
271 | "idiom" : "mac",
272 | "scale" : "1x",
273 | "size" : "256x256"
274 | },
275 | {
276 | "filename" : "512.png",
277 | "idiom" : "mac",
278 | "scale" : "2x",
279 | "size" : "256x256"
280 | },
281 | {
282 | "filename" : "512.png",
283 | "idiom" : "mac",
284 | "scale" : "1x",
285 | "size" : "512x512"
286 | },
287 | {
288 | "filename" : "1024.png",
289 | "idiom" : "mac",
290 | "scale" : "2x",
291 | "size" : "512x512"
292 | }
293 | ],
294 | "info" : {
295 | "author" : "xcode",
296 | "version" : 1
297 | }
298 | }
299 |
--------------------------------------------------------------------------------
/Wordle.iOS/Entitlements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Wordle.iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDisplayName
6 | Wordle-onia
7 | CFBundleIcons
8 |
9 | CFBundleIcons~ipad
10 |
11 | CFBundleIdentifier
12 | com.avaloniaui.net.wordleonia
13 | CFBundleShortVersionString
14 | 1.0
15 | CFBundleVersion
16 | 1.0
17 | XSAppIconAssets
18 | Assets.xcassets/AppIcon.appiconset
19 | LSRequiresIPhoneOS
20 |
21 | MinimumOSVersion
22 | 10.0
23 | UIDeviceFamily
24 |
25 | 1
26 | 2
27 |
28 | UILaunchStoryboardName
29 | LaunchScreen
30 | UIRequiredDeviceCapabilities
31 |
32 | armv7
33 |
34 | UISupportedInterfaceOrientations
35 |
36 | UIInterfaceOrientationPortrait
37 | UIInterfaceOrientationPortraitUpsideDown
38 | UIInterfaceOrientationLandscapeLeft
39 | UIInterfaceOrientationLandscapeRight
40 |
41 | UISupportedInterfaceOrientations~ipad
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationPortraitUpsideDown
45 | UIInterfaceOrientationLandscapeLeft
46 | UIInterfaceOrientationLandscapeRight
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/Wordle.iOS/Main.cs:
--------------------------------------------------------------------------------
1 | using UIKit;
2 |
3 | namespace Wordle.iOS;
4 |
5 | public class Application
6 | {
7 | // This is the main entry point of the application.
8 | static void Main(string[] args)
9 | {
10 | // if you want to use a different Application Delegate class from "AppDelegate"
11 | // you can specify it here.
12 | UIApplication.Main(args, null, typeof(AppDelegate));
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Wordle.iOS/Resources/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
21 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Wordle.iOS/Wordle.iOS.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net8.0-ios
5 | 13.0
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Wordle.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.2.32317.152
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wordle", "Wordle\Wordle.csproj", "{EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wordle.Desktop", "Wordle.Desktop\Wordle.Desktop.csproj", "{ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wordle.iOS", "Wordle.iOS\Wordle.iOS.csproj", "{EBD9022F-BC83-4846-9A11-6F7F3772DC64}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wordle.Android", "Wordle.Android\Wordle.Android.csproj", "{F932FA2F-E854-43BA-B8B2-8DDFF16E03C8}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wordle.Browser", "Wordle.Browser\Wordle.Browser.csproj", "{010CEA68-D69F-4ADE-AC42-3BFF0472F533}"
15 | EndProject
16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{366944AC-9055-49D2-9F43-D68BC6FB9125}"
17 | ProjectSection(SolutionItems) = preProject
18 | Directory.Build.props = Directory.Build.props
19 | .github\workflows\azure-swa.yml = .github\workflows\azure-swa.yml
20 | .github\workflows\dotnet.yml = .github\workflows\dotnet.yml
21 | NuGet.Config = NuGet.Config
22 | EndProjectSection
23 | EndProject
24 | Global
25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
26 | Debug|Any CPU = Debug|Any CPU
27 | Release|Any CPU = Release|Any CPU
28 | EndGlobalSection
29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
30 | {EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {EBFA8512-1EA5-4D8C-B4AC-AB5B48A6D568}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {ABC31E74-02FF-46EB-B3B2-4E6AE43B456C}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {EBD9022F-BC83-4846-9A11-6F7F3772DC64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {EBD9022F-BC83-4846-9A11-6F7F3772DC64}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {EBD9022F-BC83-4846-9A11-6F7F3772DC64}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {EBD9022F-BC83-4846-9A11-6F7F3772DC64}.Release|Any CPU.Build.0 = Release|Any CPU
42 | {F932FA2F-E854-43BA-B8B2-8DDFF16E03C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43 | {F932FA2F-E854-43BA-B8B2-8DDFF16E03C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
44 | {F932FA2F-E854-43BA-B8B2-8DDFF16E03C8}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
45 | {F932FA2F-E854-43BA-B8B2-8DDFF16E03C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
46 | {F932FA2F-E854-43BA-B8B2-8DDFF16E03C8}.Release|Any CPU.Build.0 = Release|Any CPU
47 | {F932FA2F-E854-43BA-B8B2-8DDFF16E03C8}.Release|Any CPU.Deploy.0 = Release|Any CPU
48 | {010CEA68-D69F-4ADE-AC42-3BFF0472F533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49 | {010CEA68-D69F-4ADE-AC42-3BFF0472F533}.Debug|Any CPU.Build.0 = Debug|Any CPU
50 | {010CEA68-D69F-4ADE-AC42-3BFF0472F533}.Release|Any CPU.ActiveCfg = Release|Any CPU
51 | {010CEA68-D69F-4ADE-AC42-3BFF0472F533}.Release|Any CPU.Build.0 = Release|Any CPU
52 | EndGlobalSection
53 | GlobalSection(SolutionProperties) = preSolution
54 | HideSolutionNode = FALSE
55 | EndGlobalSection
56 | GlobalSection(ExtensibilityGlobals) = postSolution
57 | SolutionGuid = {65D50C0C-26A7-4A56-85C5-DC8957323326}
58 | EndGlobalSection
59 | EndGlobal
60 |
--------------------------------------------------------------------------------
/Wordle.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | True
--------------------------------------------------------------------------------
/Wordle/App.axaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
31 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Wordle/App.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls.ApplicationLifetimes;
3 | using Avalonia.Markup.Xaml;
4 | using CommunityToolkit.Mvvm.Messaging;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using Wordle.Services;
7 | using Wordle.Services.Impl;
8 | using Wordle.ViewModels;
9 | using Wordle.Views;
10 |
11 | namespace Wordle;
12 |
13 | public partial class App : Application
14 | {
15 | public static ServiceProvider? ServiceProvider { get; private set; }
16 |
17 | public override void Initialize()
18 | {
19 | AvaloniaXamlLoader.Load(this);
20 | }
21 |
22 | public override void OnFrameworkInitializationCompleted()
23 | {
24 | ServiceProvider = BuildServices();
25 |
26 | var viewModel = ServiceProvider.GetRequiredService();
27 |
28 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
29 | {
30 | desktop.MainWindow = new MainWindow
31 | {
32 | DataContext = viewModel
33 | };
34 | }
35 | else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
36 | {
37 | singleViewPlatform.MainView = new MainView
38 | {
39 | DataContext = viewModel
40 | };
41 | }
42 |
43 | base.OnFrameworkInitializationCompleted();
44 | }
45 |
46 | private static ServiceProvider BuildServices()
47 | {
48 | var sc = new ServiceCollection();
49 | sc.AddSingleton(WeakReferenceMessenger.Default);
50 | sc.AddTransient();
51 | sc.AddTransient();
52 | sc.AddTransient();
53 | return sc.BuildServiceProvider();
54 | }
55 | }
--------------------------------------------------------------------------------
/Wordle/Assets/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AvaloniaUI/Wordle-onia/5b33695f4ca8999e5deb1eddd651199e55606a09/Wordle/Assets/Icon.png
--------------------------------------------------------------------------------
/Wordle/Messages/KeyPressedMessage.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.Messages;
2 |
3 | using Avalonia.Input;
4 | using CommunityToolkit.Mvvm.Messaging.Messages;
5 |
6 | public class KeyPressedMessage(Key value) : ValueChangedMessage(value);
--------------------------------------------------------------------------------
/Wordle/Messages/LetterGuessedMessage.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.Messages;
2 |
3 | using CommunityToolkit.Mvvm.Messaging.Messages;
4 | using Models.Enums;
5 |
6 | public class LetterGuessedMessage(char value, LetterState letterState) : ValueChangedMessage(value)
7 | {
8 | public LetterState LetterState { get; } = letterState;
9 | }
--------------------------------------------------------------------------------
/Wordle/Messages/PushNotificationMessage.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.Messages;
2 |
3 | using CommunityToolkit.Mvvm.Messaging.Messages;
4 |
5 | public class PushNotificationMessage(string message) : ValueChangedMessage(message);
6 |
7 | public class GameWonMessage() : ValueChangedMessage(true);
--------------------------------------------------------------------------------
/Wordle/Models/Constants.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.Models;
2 |
3 | public static class Constants
4 | {
5 | public const int MaximumGuesses = 6;
6 |
7 | public const int TimeBetweenReveals = 300;
8 | }
--------------------------------------------------------------------------------
/Wordle/Models/Enums/LetterState.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.Models.Enums;
2 |
3 | public enum LetterState
4 | {
5 | None,
6 | WrongLetter,
7 | RightLetterWrongPlace,
8 | RightLetterRightPlace
9 | }
--------------------------------------------------------------------------------
/Wordle/Models/GameModel.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.Models;
2 |
3 | using Enums;
4 | using Services;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Threading.Tasks;
9 |
10 | public class GameModel : ObservableModel
11 | {
12 | private readonly IGuessValidationService _guessValidationService;
13 | private readonly IWordProvider _wordProvider;
14 | private int _currentGuessIndex;
15 | private int _currentLetterIndex;
16 | private bool _gameOver;
17 | private bool _isCommittingGuess;
18 |
19 | public GameModel(IGuessValidationService guessValidationService, IWordProvider wordProvider)
20 | {
21 | ArgumentNullException.ThrowIfNull(guessValidationService);
22 | ArgumentNullException.ThrowIfNull(wordProvider);
23 |
24 | _guessValidationService = guessValidationService;
25 | _wordProvider = wordProvider;
26 | TargetWord = new WordModel(wordProvider.GetDailyWord());
27 | Guesses = Enumerable.Range(0, Constants.MaximumGuesses)
28 | .Select(_ => new WordModel(TargetWord.Letters.Count))
29 | .ToList();
30 | }
31 |
32 | public IReadOnlyCollection Guesses { get; }
33 |
34 | public WordModel TargetWord { get; }
35 |
36 | public async Task CommitGuess()
37 | {
38 | if (_gameOver)
39 | {
40 | return null;
41 | }
42 |
43 | if (_isCommittingGuess)
44 | {
45 | return null;
46 | }
47 |
48 | _isCommittingGuess = true;
49 |
50 | try
51 | {
52 | if (_currentLetterIndex != TargetWord.Letters.Count)
53 | {
54 | return new GuessResult
55 | {
56 | Success = false,
57 | Validated = false,
58 | Message = "Not enough letters"
59 | };
60 | }
61 |
62 | var currentGuess = Guesses.ElementAt(_currentGuessIndex);
63 | var currentWord = string.Join(null, currentGuess.Letters.Select(x => x.Character));
64 | var isRecognizedWord = _wordProvider.IsRecognizedWord(currentWord);
65 | if (!isRecognizedWord)
66 | {
67 | return new GuessResult
68 | {
69 | Success = false,
70 | Validated = false,
71 | Message = "Not in word list",
72 | GuessedWord = currentGuess
73 | };
74 | }
75 |
76 | await _guessValidationService.ValidateGuessAsync(currentGuess, TargetWord);
77 | _currentGuessIndex++;
78 | _currentLetterIndex = 0;
79 |
80 | var success = currentGuess.Letters.All(x => x.State == LetterState.RightLetterRightPlace);
81 | GuessResult guessResult = new()
82 | {
83 | Success = success,
84 | Validated = true,
85 | Message = success ? "Genius" : null,
86 | GuessedWord = currentGuess
87 | };
88 |
89 | if (guessResult.Success || _currentGuessIndex == Constants.MaximumGuesses)
90 | {
91 | _gameOver = true;
92 | }
93 |
94 | return guessResult;
95 | }
96 | finally
97 | {
98 | _isCommittingGuess = false;
99 | }
100 | }
101 |
102 | public bool DeleteLastCharacter()
103 | {
104 | if (_isCommittingGuess)
105 | {
106 | return false;
107 | }
108 |
109 | if (_gameOver)
110 | {
111 | return false;
112 | }
113 |
114 | if (_currentLetterIndex == 0)
115 | {
116 | return false;
117 | }
118 |
119 | _currentLetterIndex--;
120 | var currentGuess = Guesses.ElementAt(_currentGuessIndex);
121 | var currentLetterGuess = currentGuess.Letters.ElementAt(_currentLetterIndex);
122 | currentLetterGuess.Character = null;
123 |
124 | return true;
125 | }
126 |
127 | public bool GuessLetter(char letter)
128 | {
129 | if (_isCommittingGuess)
130 | {
131 | return false;
132 | }
133 |
134 | if (_gameOver)
135 | {
136 | return false;
137 | }
138 |
139 | if (_currentLetterIndex == TargetWord.Letters.Count)
140 | {
141 | return false;
142 | }
143 |
144 | var currentGuess = Guesses.ElementAt(_currentGuessIndex);
145 | var currentLetterGuess = currentGuess.Letters.ElementAt(_currentLetterIndex);
146 | currentLetterGuess.Character = letter;
147 | _currentLetterIndex++;
148 |
149 | return true;
150 | }
151 | }
--------------------------------------------------------------------------------
/Wordle/Models/GuessResult.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.Models;
2 |
3 | public class GuessResult
4 | {
5 | public WordModel? GuessedWord { get; set; }
6 |
7 | public string? Message { get; set; }
8 |
9 | public bool Success { get; set; }
10 |
11 | public bool Validated { get; set; }
12 | }
--------------------------------------------------------------------------------
/Wordle/Models/LetterModel.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.Models;
2 |
3 | using CommunityToolkit.Mvvm.ComponentModel;
4 | using Enums;
5 |
6 | public partial class LetterModel : ObservableModel
7 | {
8 | [ObservableProperty]
9 | private char? _character;
10 |
11 | [ObservableProperty]
12 | private LetterState _state;
13 |
14 | public LetterModel()
15 | {
16 | }
17 |
18 | public LetterModel(char character)
19 | {
20 | _character = character;
21 | }
22 | }
--------------------------------------------------------------------------------
/Wordle/Models/ObservableModel.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.Models;
2 |
3 | using CommunityToolkit.Mvvm.ComponentModel;
4 |
5 | public abstract class ObservableModel : ObservableObject
6 | {
7 | }
--------------------------------------------------------------------------------
/Wordle/Models/WordModel.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.Models;
2 |
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | public class WordModel : ObservableModel
7 | {
8 | public WordModel(int wordLength)
9 | {
10 | Letters = Enumerable.Range(0, wordLength).Select(x => new LetterModel()).ToList();
11 | }
12 |
13 | public WordModel(string word)
14 | {
15 | Letters = word.ToCharArray().Select(x => new LetterModel(x)).ToList();
16 | }
17 |
18 | public IReadOnlyCollection Letters { get; }
19 | }
--------------------------------------------------------------------------------
/Wordle/Services/IGuessValidationService.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.Services;
2 |
3 | using Models;
4 | using System.Threading.Tasks;
5 |
6 | public interface IGuessValidationService
7 | {
8 | Task ValidateGuessAsync(WordModel guess, WordModel target);
9 | }
--------------------------------------------------------------------------------
/Wordle/Services/IThemeService.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.Services;
2 |
3 | using System;
4 |
5 | public interface IThemeService
6 | {
7 | public enum Theme
8 | {
9 | Dark,
10 | DarkHighContrast,
11 | Light,
12 | LightHighContrast
13 | }
14 |
15 | event EventHandler<(Theme? OldTheme, Theme NewTheme)>? ThemeChanged;
16 |
17 | Theme GetCurrentTheme();
18 |
19 | void SetThemeSettings(bool dark, bool highContrast);
20 | }
--------------------------------------------------------------------------------
/Wordle/Services/IWordProvider.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.Services;
2 |
3 | using System.Threading.Tasks;
4 |
5 | public interface IWordProvider
6 | {
7 | public string GetDailyWord();
8 |
9 | public bool IsRecognizedWord(string word);
10 |
11 | public Task LoadAsync();
12 | }
--------------------------------------------------------------------------------
/Wordle/Services/Impl/GuessValidationServiceImpl.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.Services.Impl;
2 |
3 | using Models;
4 | using Models.Enums;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 |
9 | public class GuessValidationServiceImpl : IGuessValidationService
10 | {
11 | public async Task ValidateGuessAsync(WordModel guess, WordModel target)
12 | {
13 | // TODO: This really need reworking totally, ive had to temp hack some new logic in here as i found a bug
14 | (LetterModel, LetterState)[] letterResults = new (LetterModel, LetterState)[guess.Letters.Count];
15 |
16 | List matchedGuessLetters = [];
17 | List matchedTargetLetters = [];
18 |
19 | // Get all correct letters first
20 | for (var i = 0; i < guess.Letters.Count; i++)
21 | {
22 | var letter = guess.Letters.ElementAt(i);
23 | var targetLetter = target.Letters.ElementAt(i);
24 |
25 | if (letter.Character == targetLetter.Character)
26 | {
27 | letterResults[i] = (letter, LetterState.RightLetterRightPlace);
28 | matchedGuessLetters.Add(letter);
29 | matchedTargetLetters.Add(targetLetter);
30 | }
31 | }
32 |
33 | // Now get partially correct
34 | for (var i = 0; i < guess.Letters.Count; i++)
35 | {
36 | var letter = guess.Letters.ElementAt(i);
37 | if (matchedGuessLetters.Contains(letter))
38 | {
39 | continue;
40 | }
41 |
42 | var partialMatch = target.Letters.Except(matchedTargetLetters)
43 | .FirstOrDefault(x => x.Character == letter.Character);
44 | if (partialMatch != null)
45 | {
46 | letterResults[i] = (letter, LetterState.RightLetterWrongPlace);
47 | matchedGuessLetters.Add(letter);
48 | matchedTargetLetters.Add(partialMatch);
49 | }
50 | else
51 | {
52 | letterResults[i] = (letter, LetterState.WrongLetter);
53 | }
54 | }
55 |
56 | foreach ((LetterModel, LetterState) letterResult in letterResults)
57 | {
58 | letterResult.Item1.State = letterResult.Item2;
59 | await Task.Delay(Constants.TimeBetweenReveals);
60 | }
61 |
62 | //for (int i = 0; i < guess.Letters.Count; i++)
63 | //{
64 | // LetterModel letter = guess.Letters.ElementAt(i);
65 |
66 | // LetterState letterState = LetterState.WrongLetter;
67 | // for (int j = 0; j < target.Letters.Count; j++)
68 | // {
69 | // LetterModel targetLetter = target.Letters.ElementAt(j);
70 | // if (letter.Label == targetLetter.Label)
71 | // {
72 | // if (i == j)
73 | // {
74 | // letterState = LetterState.RightLetterRightPlace;
75 | // break;
76 | // }
77 |
78 | // letterState = LetterState.RightLetterWrongPlace;
79 | // }
80 | // }
81 |
82 | // letter.State = letterState;
83 |
84 | // if (i < guess.Letters.Count - 1)
85 | // {
86 | // await Task.Delay(Constants.TimeBetweenReveals);
87 | // }
88 | //}
89 | }
90 | }
--------------------------------------------------------------------------------
/Wordle/Services/Impl/ThemeServiceImpl.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.Services.Impl;
2 |
3 | using System;
4 |
5 | public class ThemeServiceImpl : IThemeService
6 | {
7 | private IThemeService.Theme _currentTheme = IThemeService.Theme.Light;
8 |
9 | public event EventHandler<(IThemeService.Theme? OldTheme, IThemeService.Theme NewTheme)>? ThemeChanged;
10 |
11 | public IThemeService.Theme GetCurrentTheme()
12 | {
13 | return _currentTheme;
14 | }
15 |
16 | public void SetThemeSettings(bool dark, bool highContrast)
17 | {
18 | var oldTheme = _currentTheme;
19 | IThemeService.Theme newTheme;
20 | if (dark)
21 | {
22 | newTheme = highContrast
23 | ? IThemeService.Theme.DarkHighContrast
24 | : IThemeService.Theme.Dark;
25 | }
26 | else
27 | {
28 | newTheme = highContrast
29 | ? IThemeService.Theme.LightHighContrast
30 | : IThemeService.Theme.Light;
31 | }
32 |
33 | _currentTheme = newTheme;
34 | ThemeChanged?.Invoke(this, (oldTheme, newTheme));
35 | }
36 | }
--------------------------------------------------------------------------------
/Wordle/Services/Impl/WordProviderImpl.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.Services.Impl;
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Net.Http;
7 | using System.Threading.Tasks;
8 |
9 | public class WordProviderImpl : IWordProvider
10 | {
11 | private static readonly Uri AnswersListUri = new(
12 | "https://gist.githubusercontent.com/cfreshman/a03ef2cba789d8cf00c08f767e0fad7b/raw/5d752e5f0702da315298a6bb5a771586d6ff445c/wordle-answers-alphabetical.txt");
13 |
14 | private static readonly Uri ValidGuessesListUri = new(
15 | "https://gist.githubusercontent.com/cfreshman/cdcdf777450c5b5301e439061d29694c/raw/de1df631b45492e0974f7affe266ec36fed736eb/wordle-allowed-guesses.txt");
16 |
17 | private IReadOnlyCollection? _answersList;
18 |
19 | private IReadOnlyCollection? _validGuessesList;
20 |
21 | public string GetDailyWord()
22 | {
23 | if (_answersList == null || _validGuessesList == null)
24 | {
25 | throw new WordsNotLoadedException();
26 | }
27 |
28 | var today = DateOnly.FromDateTime(DateTime.Now);
29 | var hash = today.GetHashCode();
30 | Random random = new(hash);
31 | var index = random.Next(0, _answersList.Count);
32 |
33 | return _answersList.ElementAt(index);
34 | }
35 |
36 | public bool IsRecognizedWord(string word)
37 | {
38 | if (_answersList == null || _validGuessesList == null)
39 | {
40 | throw new WordsNotLoadedException();
41 | }
42 |
43 | var key = word.ToUpper();
44 | return _answersList.Contains(key) || _validGuessesList.Contains(key);
45 | }
46 |
47 | public async Task LoadAsync()
48 | {
49 | using HttpClient httpClient = new();
50 |
51 | var answersString = await httpClient.GetStringAsync(AnswersListUri);
52 | _answersList = answersString.Split('\n', '\r')
53 | .Select(x => x.Trim().ToUpper())
54 | .Where(x => !string.IsNullOrWhiteSpace(x))
55 | .ToHashSet();
56 |
57 | var validGuessesString = await httpClient.GetStringAsync(ValidGuessesListUri);
58 | _validGuessesList = validGuessesString.Split('\n', '\r')
59 | .Select(x => x.Trim().ToUpper())
60 | .Select(x => x.Trim())
61 | .Where(x => !string.IsNullOrWhiteSpace(x))
62 | .ToHashSet();
63 | }
64 |
65 | private class WordsNotLoadedException()
66 | : Exception("Words list not loaded. Call .LoadAsync prior to trying to get word related information.");
67 | }
--------------------------------------------------------------------------------
/Wordle/ViewModels/GameViewModel.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Input;
2 |
3 | namespace Wordle.ViewModels;
4 |
5 | using CommunityToolkit.Mvvm.Messaging;
6 | using Messages;
7 | using Models;
8 | using System.Collections.Generic;
9 | using System.Linq;
10 |
11 | public class GameViewModel : ObservableViewModel
12 | {
13 | private readonly IMessenger _messenger;
14 |
15 | public GameViewModel(GameModel model, IMessenger messenger)
16 | : base(model)
17 | {
18 | _messenger = messenger;
19 | Guesses = model.Guesses.Select(x => new WordViewModel(x)).ToList();
20 |
21 | _messenger.Register(this, (_, m) => OnKeyPressed(m.Value));
22 | }
23 |
24 | public IReadOnlyCollection Guesses { get; }
25 |
26 | private async void CommitGuess()
27 | {
28 | var guess = await Model.CommitGuess();
29 | if (guess == null)
30 | {
31 | return;
32 | }
33 |
34 | if (!string.IsNullOrWhiteSpace(guess.Message))
35 | {
36 | _messenger.Send(new PushNotificationMessage(guess.Message));
37 | }
38 |
39 | if (guess.Success)
40 | {
41 | _messenger.Send(new GameWonMessage());
42 | }
43 |
44 | if (guess.Validated && guess.GuessedWord != null)
45 | {
46 | foreach (var guessedWordLetter in guess.GuessedWord.Letters)
47 | {
48 | _messenger.Send(
49 | new LetterGuessedMessage(
50 | guessedWordLetter.Character!.Value,
51 | guessedWordLetter.State));
52 | }
53 | }
54 | }
55 |
56 | private void DeleteLastLetter()
57 | {
58 | Model.DeleteLastCharacter();
59 | }
60 |
61 | private void GuessLetter(char character)
62 | {
63 | Model.GuessLetter(character);
64 | }
65 |
66 | private void OnKeyPressed(Key key)
67 | {
68 | switch (key)
69 | {
70 | case Key.Enter:
71 | CommitGuess();
72 | break;
73 | case Key.Back:
74 | DeleteLastLetter();
75 | break;
76 | case Key.A:
77 | case Key.B:
78 | case Key.C:
79 | case Key.D:
80 | case Key.E:
81 | case Key.F:
82 | case Key.G:
83 | case Key.H:
84 | case Key.I:
85 | case Key.J:
86 | case Key.K:
87 | case Key.L:
88 | case Key.M:
89 | case Key.N:
90 | case Key.O:
91 | case Key.P:
92 | case Key.Q:
93 | case Key.R:
94 | case Key.S:
95 | case Key.T:
96 | case Key.U:
97 | case Key.V:
98 | case Key.W:
99 | case Key.X:
100 | case Key.Y:
101 | case Key.Z:
102 | GuessLetter(key.ToString().ToUpper().ToCharArray()[0]);
103 | break;
104 | }
105 | }
106 | }
--------------------------------------------------------------------------------
/Wordle/ViewModels/KeyboardViewModel.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Input;
2 |
3 | namespace Wordle.ViewModels;
4 |
5 | using CommunityToolkit.Mvvm.ComponentModel;
6 | using CommunityToolkit.Mvvm.Messaging;
7 | using Messages;
8 | using Models.Enums;
9 | using System.Collections.Generic;
10 | using System.Linq;
11 |
12 | public class KeyboardViewModel : ObservableObject
13 | {
14 | private static readonly Key[] KeyboardRow1 =
15 | [
16 | Key.Q,
17 | Key.W,
18 | Key.E,
19 | Key.R,
20 | Key.T,
21 | Key.Y,
22 | Key.U,
23 | Key.I,
24 | Key.O,
25 | Key.P
26 | ];
27 |
28 | private static readonly Key[] KeyboardRow2 =
29 | [
30 | Key.A,
31 | Key.S,
32 | Key.D,
33 | Key.F,
34 | Key.G,
35 | Key.H,
36 | Key.J,
37 | Key.K,
38 | Key.L
39 | ];
40 |
41 | private static readonly Key[] KeyboardRow3 =
42 | [
43 | Key.Z,
44 | Key.X,
45 | Key.C,
46 | Key.V,
47 | Key.B,
48 | Key.N,
49 | Key.M
50 | ];
51 |
52 | public KeyboardViewModel(IMessenger messenger)
53 | {
54 | IReadOnlyCollection rowOneKeys =
55 | KeyboardRow1.Select(x => new LetterKeyViewModel(x, messenger)).ToList();
56 |
57 | IReadOnlyCollection rowTwoKeys =
58 | KeyboardRow2.Select(x => new LetterKeyViewModel(x, messenger)).ToList();
59 |
60 | var rowThreeKeys =
61 | KeyboardRow3.Select(x => new LetterKeyViewModel(x, messenger)).ToList();
62 | rowThreeKeys.Insert(0, new EnterKeyViewModel(messenger));
63 | rowThreeKeys.Add(new DeleteKeyViewModel(messenger));
64 |
65 | Keys = new List>
66 | {
67 | rowOneKeys,
68 | rowTwoKeys,
69 | rowThreeKeys
70 | };
71 |
72 | messenger.Register(
73 | this,
74 | (_, m) => OnLetterGuessed(m.Value, m.LetterState));
75 | }
76 |
77 | public IReadOnlyCollection> Keys { get; }
78 |
79 | private void OnLetterGuessed(char character, LetterState letterState)
80 | {
81 | var match = Keys.SelectMany(x => x)
82 | .OfType()
83 | .FirstOrDefault(x => x.Label == character);
84 |
85 | match?.UpdateLetterResult(letterState);
86 | }
87 | }
88 |
89 | public partial class KeyViewModelBase : ObservableObject
90 | {
91 | private readonly Key _key;
92 | private readonly IMessenger _messenger;
93 |
94 | [ObservableProperty]
95 | private LetterState _letterState;
96 |
97 | protected KeyViewModelBase(Key key, IMessenger messenger)
98 | {
99 | _key = key;
100 | _messenger = messenger;
101 | }
102 |
103 | public void KeyPressed()
104 | {
105 | _messenger.Send(new KeyPressedMessage(_key));
106 | }
107 | }
108 |
109 | public class EnterKeyViewModel(IMessenger messenger) : KeyViewModelBase(Key.Enter, messenger);
110 |
111 | public class DeleteKeyViewModel(IMessenger messenger) : KeyViewModelBase(Key.Back, messenger);
112 |
113 | public class LetterKeyViewModel(Key key, IMessenger messenger) : KeyViewModelBase(key, messenger)
114 | {
115 | public char Label { get; } = key.ToString().ToUpper().ToCharArray()[0];
116 |
117 | public void UpdateLetterResult(LetterState letterState)
118 | {
119 | if (letterState > LetterState)
120 | {
121 | LetterState = letterState;
122 | }
123 | }
124 | }
--------------------------------------------------------------------------------
/Wordle/ViewModels/LetterViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.ViewModels;
2 |
3 | using Models;
4 | using Models.Enums;
5 |
6 | public class LetterViewModel(LetterModel model) : ObservableViewModel(model)
7 | {
8 | public char? Character => Model.Character;
9 |
10 | public LetterState State => Model.State;
11 | }
--------------------------------------------------------------------------------
/Wordle/ViewModels/MainViewModel.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Threading;
2 | using CommunityToolkit.Mvvm.ComponentModel;
3 | using CommunityToolkit.Mvvm.Messaging;
4 | using Wordle.Messages;
5 | using Wordle.Models;
6 | using Wordle.Services;
7 |
8 | namespace Wordle.ViewModels;
9 |
10 | public partial class MainViewModel : ViewModelBase
11 | {
12 | [ObservableProperty]
13 | private GameViewModel? _game;
14 |
15 | [ObservableProperty]
16 | private bool _showWonDialog;
17 |
18 | public MainViewModel(
19 | IMessenger messenger,
20 | IWordProvider wordProvider,
21 | IGuessValidationService validationService)
22 | {
23 | _ = Dispatcher.UIThread.InvokeAsync(async () =>
24 | {
25 | await wordProvider.LoadAsync();
26 |
27 | var gameModel = new GameModel(validationService, wordProvider);
28 |
29 | Game = new GameViewModel(gameModel, messenger);
30 | });
31 |
32 | Keyboard = new KeyboardViewModel(messenger);
33 |
34 | messenger.Register(this, (recipient, message) =>
35 | {
36 | ShowWonDialog = true;
37 | });
38 | }
39 |
40 | public KeyboardViewModel Keyboard { get; }
41 | }
--------------------------------------------------------------------------------
/Wordle/ViewModels/NotificationsViewModel.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Threading;
2 |
3 | namespace Wordle.ViewModels;
4 |
5 | using CommunityToolkit.Mvvm.ComponentModel;
6 | using CommunityToolkit.Mvvm.Messaging;
7 | using Messages;
8 | using System;
9 | using System.Collections.ObjectModel;
10 |
11 | public class NotificationsViewModel : ObservableObject
12 | {
13 | private static readonly TimeSpan NotificationLifespan = TimeSpan.FromSeconds(1);
14 |
15 | public NotificationsViewModel(IMessenger messenger)
16 | {
17 | Notifications = [];
18 |
19 | messenger.Register(this, (_, message) => PushNotification(message.Value));
20 | }
21 |
22 | public ObservableCollection Notifications { get; }
23 |
24 | private void OnCardExpired(object? sender, EventArgs e)
25 | {
26 | if (sender is DispatcherTimer timer)
27 | {
28 | timer.Stop();
29 | timer.Tick -= OnCardExpired;
30 | }
31 |
32 | Notifications.RemoveAt(Notifications.Count - 1);
33 | }
34 |
35 | private void PushNotification(string message)
36 | {
37 | Notifications.Insert(0, message);
38 | DispatcherTimer dispatcherTimer = new()
39 | {
40 | Interval = NotificationLifespan
41 | };
42 | dispatcherTimer.Tick += OnCardExpired;
43 | dispatcherTimer.Start();
44 | }
45 | }
--------------------------------------------------------------------------------
/Wordle/ViewModels/ObservableViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.ViewModels;
2 |
3 | using CommunityToolkit.Mvvm.ComponentModel;
4 | using Models;
5 | using System;
6 | using System.ComponentModel;
7 |
8 | public abstract class ObservableViewModel : ObservableObject, IDisposable
9 | where T : ObservableModel
10 | {
11 | private readonly bool _forwardModelPropertyChanges;
12 | private bool _disposed;
13 |
14 | protected ObservableViewModel(T model, bool forwardPropertyChanged = true)
15 | {
16 | ArgumentNullException.ThrowIfNull(model);
17 |
18 | Model = model;
19 | _forwardModelPropertyChanges = forwardPropertyChanged;
20 |
21 | model.PropertyChanged += OnModelPropertyChanged;
22 | }
23 |
24 | public T Model { get; }
25 |
26 | public void Dispose()
27 | {
28 | if (_disposed)
29 | {
30 | throw new ObjectDisposedException("Object already disposed.");
31 | }
32 |
33 | _disposed = true;
34 | Model.PropertyChanged -= OnModelPropertyChanged;
35 | }
36 |
37 | protected virtual void OnModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
38 | {
39 | if (_forwardModelPropertyChanges)
40 | {
41 | OnPropertyChanged(e);
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/Wordle/ViewModels/SettingsViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.ViewModels;
2 |
3 | using CommunityToolkit.Mvvm.ComponentModel;
4 | using Services;
5 | using System;
6 |
7 | public class SettingsViewModel : ObservableObject
8 | {
9 | private readonly IThemeService _themeService;
10 | private bool _darkTheme;
11 | private bool _highContrast;
12 |
13 | public SettingsViewModel(IThemeService themeService)
14 | {
15 | ArgumentNullException.ThrowIfNull(themeService);
16 |
17 | _themeService = themeService;
18 | var currentTheme = themeService.GetCurrentTheme();
19 |
20 | switch (currentTheme)
21 | {
22 | case IThemeService.Theme.Dark:
23 | _darkTheme = true;
24 | _highContrast = false;
25 | break;
26 | case IThemeService.Theme.DarkHighContrast:
27 | _darkTheme = true;
28 | _highContrast = true;
29 | break;
30 | case IThemeService.Theme.Light:
31 | _darkTheme = false;
32 | _highContrast = false;
33 | break;
34 | case IThemeService.Theme.LightHighContrast:
35 | _darkTheme = false;
36 | _highContrast = true;
37 | break;
38 | default:
39 | throw new ArgumentOutOfRangeException();
40 | }
41 | }
42 |
43 | public bool DarkTheme
44 | {
45 | get => _darkTheme;
46 | set
47 | {
48 | _darkTheme = value;
49 | OnPropertyChanged();
50 | _themeService.SetThemeSettings(value, HighContrast);
51 | }
52 | }
53 |
54 | public bool HighContrast
55 | {
56 | get => _highContrast;
57 | set
58 | {
59 | _highContrast = value;
60 | OnPropertyChanged();
61 | _themeService.SetThemeSettings(DarkTheme, value);
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/Wordle/ViewModels/StatisticsViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.ViewModels;
2 |
3 | using CommunityToolkit.Mvvm.ComponentModel;
4 |
5 | public partial class StatisticsViewModel : ObservableObject
6 | {
7 | [ObservableProperty]
8 | private int _currentStreak;
9 |
10 | [ObservableProperty]
11 | private int _maxStreak;
12 |
13 | [ObservableProperty]
14 | private int _played;
15 |
16 | [ObservableProperty]
17 | [NotifyPropertyChangedFor(nameof(WinPercentage))]
18 | private int _won;
19 |
20 | public int WinPercentage => Won / Played * 100;
21 | }
--------------------------------------------------------------------------------
/Wordle/ViewModels/ViewModelBase.cs:
--------------------------------------------------------------------------------
1 | using CommunityToolkit.Mvvm.ComponentModel;
2 |
3 | namespace Wordle.ViewModels;
4 |
5 | public partial class ViewModelBase : ObservableObject
6 | {
7 | }
--------------------------------------------------------------------------------
/Wordle/ViewModels/WordViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace Wordle.ViewModels;
2 |
3 | using Models;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 |
7 | public class WordViewModel : ObservableViewModel
8 | {
9 | public WordViewModel(WordModel model)
10 | : base(model)
11 | {
12 | Letters = model.Letters.Select(x => new LetterViewModel(x)).ToList();
13 | }
14 |
15 | public IReadOnlyCollection Letters { get; }
16 | }
--------------------------------------------------------------------------------
/Wordle/Views/Controls/KeyboardLetterButton.axaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
30 |
31 |
35 |
36 |
40 |
41 |
45 |
46 |
--------------------------------------------------------------------------------
/Wordle/Views/Controls/KeyboardLetterButton.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls;
3 | using Wordle.Models.Enums;
4 |
5 | namespace Wordle.Views.Controls;
6 |
7 | public class KeyboardLetterButton : Button
8 | {
9 | public static readonly StyledProperty StateProperty =
10 | AvaloniaProperty.Register(nameof(State), LetterState.None);
11 |
12 | public LetterState State
13 | {
14 | get => GetValue(StateProperty);
15 | set => SetValue(StateProperty, value);
16 | }
17 |
18 | private void UpdatePsuedoClasses(LetterState newValue)
19 | {
20 | PseudoClasses.Set(":wrong-letter", newValue == LetterState.WrongLetter);
21 | PseudoClasses.Set(":right-letter", newValue == LetterState.RightLetterWrongPlace);
22 | PseudoClasses.Set(":right-letter-placement", newValue == LetterState.RightLetterRightPlace);
23 | }
24 |
25 | protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
26 | {
27 | base.OnPropertyChanged(change);
28 |
29 | if (change.Property == StateProperty)
30 | {
31 | UpdatePsuedoClasses(change.GetNewValue());
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/Wordle/Views/Controls/Letter.axaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
25 |
26 |
27 |
28 |
36 |
37 |
38 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
50 |
51 |
54 |
55 |
72 |
73 |
77 |
78 |
82 |
83 |
87 |
88 |
--------------------------------------------------------------------------------
/Wordle/Views/Controls/Letter.axaml.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Avalonia;
3 | using Avalonia.Controls;
4 | using Avalonia.Controls.Primitives;
5 | using Avalonia.Threading;
6 | using Wordle.Models.Enums;
7 |
8 | namespace Wordle.Views.Controls;
9 |
10 | public class Letter : TemplatedControl
11 | {
12 | public static readonly StyledProperty CharacterProperty =
13 | AvaloniaProperty.Register(nameof(Character));
14 |
15 | public static readonly StyledProperty StateProperty =
16 | AvaloniaProperty.Register(nameof(State));
17 |
18 | private bool _hasLetter;
19 |
20 | public string Character
21 | {
22 | get => GetValue(CharacterProperty);
23 | set => SetValue(CharacterProperty, value);
24 | }
25 |
26 | public LetterState State
27 | {
28 | get => GetValue(StateProperty);
29 | set => SetValue(StateProperty, value);
30 | }
31 |
32 | protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
33 | {
34 | base.OnPropertyChanged(change);
35 |
36 | if (change.Property == StateProperty)
37 | {
38 | // PlayRevealAnimation();
39 | UpdatePsuedoClasses(State);
40 | }
41 |
42 | if (change.Property == CharacterProperty)
43 | {
44 | if (change.OldValue is null &&
45 | change.NewValue is string newChar && char.IsLetter(newChar[0]))
46 | {
47 | _hasLetter = true;
48 | }
49 |
50 | if (change.NewValue is null &&
51 | change.OldValue is string oldChar && char.IsLetter(oldChar[0]))
52 | {
53 | _hasLetter = false;
54 | }
55 |
56 | UpdatePsuedoClasses(State);
57 | }
58 | }
59 |
60 | private void UpdatePsuedoClasses(LetterState newValue)
61 | {
62 | PseudoClasses.Set(":has-letter", _hasLetter);
63 | PseudoClasses.Set(":wrong-letter", newValue == LetterState.WrongLetter);
64 | PseudoClasses.Set(":right-letter", newValue == LetterState.RightLetterWrongPlace);
65 | PseudoClasses.Set(":right-letter-placement", newValue == LetterState.RightLetterRightPlace);
66 |
67 | if (newValue != LetterState.None)
68 | {
69 | Dispatcher.UIThread.InvokeAsync(async () =>
70 | {
71 | PseudoClasses.Set(":flip", true);
72 | await Task.Delay(1000);
73 | PseudoClasses.Set(":flip", false);
74 | }, DispatcherPriority.ApplicationIdle);
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/Wordle/Views/GameView.axaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Wordle/Views/GameView.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 |
3 | namespace Wordle.Views;
4 |
5 | internal partial class GameView : UserControl
6 | {
7 | public GameView()
8 | {
9 | InitializeComponent();
10 | }
11 | }
--------------------------------------------------------------------------------
/Wordle/Views/KeyboardView.axaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
25 |
26 |
27 |
33 |
34 |
35 |
40 |
44 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/Wordle/Views/KeyboardView.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 |
3 | namespace Wordle.Views;
4 |
5 | internal partial class KeyboardView : UserControl
6 | {
7 | public KeyboardView()
8 | {
9 | InitializeComponent();
10 | }
11 | }
--------------------------------------------------------------------------------
/Wordle/Views/MainView.axaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
49 |
71 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/Wordle/Views/MainView.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.Input;
3 | using Avalonia.Threading;
4 | using CommunityToolkit.Mvvm.Messaging;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using Wordle.Messages;
7 |
8 | namespace Wordle.Views;
9 |
10 | internal partial class MainView : UserControl
11 | {
12 | private readonly IMessenger _messenger;
13 |
14 | public MainView()
15 | {
16 | InitializeComponent();
17 |
18 | _messenger = App.ServiceProvider!.GetRequiredService();
19 |
20 | Dispatcher.UIThread.Post(()=>Focus(), DispatcherPriority.Loaded);
21 | }
22 |
23 | protected override void OnKeyDown(KeyEventArgs e)
24 | {
25 | base.OnKeyDown(e);
26 |
27 | _messenger.Send(new KeyPressedMessage(e.Key));
28 |
29 | e.Handled = true;
30 | }
31 | }
--------------------------------------------------------------------------------
/Wordle/Views/MainWindow.axaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Wordle/Views/MainWindow.axaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 |
3 | namespace Wordle.Views;
4 |
5 | internal partial class MainWindow : Window
6 | {
7 | public MainWindow()
8 | {
9 | InitializeComponent();
10 | }
11 | }
--------------------------------------------------------------------------------
/Wordle/Wordle.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------