├── .gitignore ├── Droid ├── Assets │ └── AboutAssets.txt ├── MainActivity.fs ├── Properties │ ├── AndroidManifest.xml │ └── AssemblyInfo.fs ├── Resources │ ├── AboutResources.txt │ ├── Resource.designer.cs │ ├── drawable-hdpi │ │ ├── Cross.png │ │ ├── Nought.png │ │ └── icon.png │ ├── drawable-xhdpi │ │ ├── Cross.png │ │ ├── Nought.png │ │ └── icon.png │ ├── drawable-xxhdpi │ │ ├── Cross.png │ │ ├── Nought.png │ │ └── icon.png │ ├── drawable │ │ ├── Cross.png │ │ ├── Nought.png │ │ └── icon.png │ ├── layout │ │ ├── Tabbar.axml │ │ └── Toolbar.axml │ └── values │ │ └── styles.xml ├── TicTacToe.Droid.fsproj └── packages.config ├── Elmish └── Elmish.XamarinForms.dll ├── LICENSE ├── README.md ├── TicTacToe.sln ├── TicTacToe ├── App.fs ├── AssemblyInfo.fs ├── Converters.fs ├── TicTacToe.fsproj ├── TicTacToePage.xaml ├── TicTacToePage.xaml.fs └── packages.config └── iOS ├── AppDelegate.fs ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json ├── Cross.imageset │ ├── Contents.json │ ├── Cross.png │ ├── Cross@2x.png │ └── Cross@3x.png └── Nought.imageset │ ├── Contents.json │ ├── Nought.png │ ├── Nought@2x.png │ └── Nought@3x.png ├── Entitlements.plist ├── Info.plist ├── LaunchScreen.storyboard ├── TicTacToe.iOS.fsproj └── packages.config /.gitignore: -------------------------------------------------------------------------------- 1 | # Autosave files 2 | *~ 3 | 4 | # build 5 | [Oo]bj/ 6 | [Bb]in/ 7 | packages/ 8 | TestResults/ 9 | 10 | # globs 11 | Makefile.in 12 | *.DS_Store 13 | *.sln.cache 14 | *.suo 15 | *.cache 16 | *.pidb 17 | *.userprefs 18 | *.usertasks 19 | config.log 20 | config.make 21 | config.status 22 | aclocal.m4 23 | install-sh 24 | autom4te.cache/ 25 | *.user 26 | *.tar.gz 27 | tarballs/ 28 | test-results/ 29 | Thumbs.db 30 | 31 | # Mac bundle stuff 32 | *.dmg 33 | *.app 34 | 35 | # resharper 36 | *_Resharper.* 37 | *.Resharper 38 | 39 | # dotCover 40 | *.dotCover 41 | .droidres/.toyidentifier.droidres.db 42 | *.db 43 | .vs/ToyIdentifier/xs/UserPrefs.xml 44 | .vs/TicTacToe/xs/UserPrefs.xml 45 | -------------------------------------------------------------------------------- /Droid/Assets/AboutAssets.txt: -------------------------------------------------------------------------------- 1 | Any raw assets you want to be deployed with your application can be placed in 2 | this directory (and child directories) and given a Build Action of "AndroidAsset". 3 | 4 | These files will be deployed with your package and will be accessible using Android's 5 | AssetManager, like this: 6 | 7 | public class ReadAsset : Activity 8 | { 9 | protected override void OnCreate (Bundle bundle) 10 | { 11 | base.OnCreate (bundle); 12 | 13 | InputStream input = Assets.Open ("my_asset.txt"); 14 | } 15 | } 16 | 17 | Additionally, some Android functions will automatically load asset files: 18 | 19 | Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); 20 | -------------------------------------------------------------------------------- /Droid/MainActivity.fs: -------------------------------------------------------------------------------- 1 | namespace TicTacToe.Droid 2 | open System 3 | 4 | open Android.App 5 | open Android.Content 6 | open Android.Content.PM 7 | open Android.Runtime 8 | open Android.Views 9 | open Android.Widget 10 | open Android.OS 11 | open Xamarin.Forms.Platform.Android 12 | 13 | [] 14 | type MainActivity() = 15 | inherit FormsAppCompatActivity() 16 | override this.OnCreate (bundle: Bundle) = 17 | FormsAppCompatActivity.TabLayoutResource <- Resources.Layout.Tabbar 18 | FormsAppCompatActivity.ToolbarResource <- Resources.Layout.Toolbar 19 | base.OnCreate (bundle) 20 | 21 | Xamarin.Forms.Forms.Init (this, bundle) 22 | 23 | this.LoadApplication (new TicTacToe.App ()) 24 | -------------------------------------------------------------------------------- /Droid/Properties/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Droid/Properties/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace TicTacToe.Droid 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | 6 | // the name of the type here needs to match the name inside the ResourceDesigner attribute 7 | type Resources = TicTacToe.Droid.Resource 8 | [] 9 | 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | 18 | // The assembly version has the format {Major}.{Minor}.{Build}.{Revision} 19 | 20 | [] 21 | 22 | //[] 23 | //[] 24 | 25 | () 26 | -------------------------------------------------------------------------------- /Droid/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. 45 | -------------------------------------------------------------------------------- /Droid/Resources/drawable-hdpi/Cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbobbennett/TicTacToe/c4524537bfe934e6ee686e4b2805bb034ec7656c/Droid/Resources/drawable-hdpi/Cross.png -------------------------------------------------------------------------------- /Droid/Resources/drawable-hdpi/Nought.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbobbennett/TicTacToe/c4524537bfe934e6ee686e4b2805bb034ec7656c/Droid/Resources/drawable-hdpi/Nought.png -------------------------------------------------------------------------------- /Droid/Resources/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbobbennett/TicTacToe/c4524537bfe934e6ee686e4b2805bb034ec7656c/Droid/Resources/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /Droid/Resources/drawable-xhdpi/Cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbobbennett/TicTacToe/c4524537bfe934e6ee686e4b2805bb034ec7656c/Droid/Resources/drawable-xhdpi/Cross.png -------------------------------------------------------------------------------- /Droid/Resources/drawable-xhdpi/Nought.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbobbennett/TicTacToe/c4524537bfe934e6ee686e4b2805bb034ec7656c/Droid/Resources/drawable-xhdpi/Nought.png -------------------------------------------------------------------------------- /Droid/Resources/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbobbennett/TicTacToe/c4524537bfe934e6ee686e4b2805bb034ec7656c/Droid/Resources/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /Droid/Resources/drawable-xxhdpi/Cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbobbennett/TicTacToe/c4524537bfe934e6ee686e4b2805bb034ec7656c/Droid/Resources/drawable-xxhdpi/Cross.png -------------------------------------------------------------------------------- /Droid/Resources/drawable-xxhdpi/Nought.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbobbennett/TicTacToe/c4524537bfe934e6ee686e4b2805bb034ec7656c/Droid/Resources/drawable-xxhdpi/Nought.png -------------------------------------------------------------------------------- /Droid/Resources/drawable-xxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbobbennett/TicTacToe/c4524537bfe934e6ee686e4b2805bb034ec7656c/Droid/Resources/drawable-xxhdpi/icon.png -------------------------------------------------------------------------------- /Droid/Resources/drawable/Cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbobbennett/TicTacToe/c4524537bfe934e6ee686e4b2805bb034ec7656c/Droid/Resources/drawable/Cross.png -------------------------------------------------------------------------------- /Droid/Resources/drawable/Nought.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbobbennett/TicTacToe/c4524537bfe934e6ee686e4b2805bb034ec7656c/Droid/Resources/drawable/Nought.png -------------------------------------------------------------------------------- /Droid/Resources/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbobbennett/TicTacToe/c4524537bfe934e6ee686e4b2805bb034ec7656c/Droid/Resources/drawable/icon.png -------------------------------------------------------------------------------- /Droid/Resources/layout/Tabbar.axml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /Droid/Resources/layout/Toolbar.axml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /Droid/Resources/values/styles.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 5 | 6 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /Droid/TicTacToe.Droid.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {8E482415-A728-4B35-9C9A-31F4D3AE1D54} 8 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{F2A71F9B-5D33-465A-A702-920D77279786} 9 | true 10 | Library 11 | TicTacToe.Droid 12 | TicTacToe.Droid 13 | v8.1 14 | True 15 | Resources\Resource.designer.cs 16 | Resource 17 | Properties\AndroidManifest.xml 18 | Resources 19 | Assets 20 | true 21 | 22 | 23 | true 24 | false 25 | bin\Debug 26 | DEBUG 27 | prompt 28 | None 29 | 30 | 31 | 32 | true 33 | true 34 | bin\Release 35 | 36 | prompt 37 | true 38 | false 39 | true 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ..\packages\FSharp.Core.4.3.4\lib\netstandard1.6\FSharp.Core.dll 50 | 51 | 52 | ..\packages\Xamarin.Android.FSharp.ResourceProvider.1.0.0.22\lib\Xamarin.Android.FSharp.ResourceProvider.Runtime.dll 53 | 54 | 55 | ..\packages\Xamarin.Android.Support.Annotations.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Annotations.dll 56 | 57 | 58 | ..\packages\Xamarin.Android.Arch.Core.Common.1.0.0\lib\MonoAndroid80\Xamarin.Android.Arch.Core.Common.dll 59 | 60 | 61 | ..\packages\Xamarin.Android.Arch.Lifecycle.Common.1.0.3\lib\MonoAndroid80\Xamarin.Android.Arch.Lifecycle.Common.dll 62 | 63 | 64 | ..\packages\Xamarin.Android.Arch.Lifecycle.Runtime.1.0.3\lib\MonoAndroid80\Xamarin.Android.Arch.Lifecycle.Runtime.dll 65 | 66 | 67 | ..\packages\Xamarin.Android.Support.Compat.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Compat.dll 68 | 69 | 70 | ..\packages\Xamarin.Android.Support.Core.UI.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Core.UI.dll 71 | 72 | 73 | ..\packages\Xamarin.Android.Support.Core.Utils.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Core.Utils.dll 74 | 75 | 76 | ..\packages\Xamarin.Android.Support.Fragment.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Fragment.dll 77 | 78 | 79 | ..\packages\Xamarin.Android.Support.Media.Compat.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Media.Compat.dll 80 | 81 | 82 | ..\packages\Xamarin.Android.Support.Transition.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Transition.dll 83 | 84 | 85 | ..\packages\Xamarin.Android.Support.v4.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.v4.dll 86 | 87 | 88 | ..\packages\Xamarin.Android.Support.v7.CardView.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.v7.CardView.dll 89 | 90 | 91 | ..\packages\Xamarin.Android.Support.v7.Palette.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.v7.Palette.dll 92 | 93 | 94 | ..\packages\Xamarin.Android.Support.v7.RecyclerView.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.v7.RecyclerView.dll 95 | 96 | 97 | ..\packages\Xamarin.Android.Support.Vector.Drawable.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Vector.Drawable.dll 98 | 99 | 100 | ..\packages\Xamarin.Android.Support.Animated.Vector.Drawable.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Animated.Vector.Drawable.dll 101 | 102 | 103 | ..\packages\Xamarin.Android.Support.v7.AppCompat.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.v7.AppCompat.dll 104 | 105 | 106 | ..\packages\Xamarin.Android.Support.Design.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Design.dll 107 | 108 | 109 | ..\packages\Xamarin.Android.Support.v7.MediaRouter.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.v7.MediaRouter.dll 110 | 111 | 112 | ..\packages\Xamarin.Forms.2.5.1.444934\lib\MonoAndroid10\FormsViewGroup.dll 113 | 114 | 115 | ..\packages\Xamarin.Forms.2.5.1.444934\lib\MonoAndroid10\Xamarin.Forms.Core.dll 116 | 117 | 118 | ..\packages\Xamarin.Forms.2.5.1.444934\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll 119 | 120 | 121 | ..\packages\Xamarin.Forms.2.5.1.444934\lib\MonoAndroid10\Xamarin.Forms.Platform.dll 122 | 123 | 124 | ..\packages\Xamarin.Forms.2.5.1.444934\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll 125 | 126 | 127 | 128 | ..\packages\FSharp.Control.AsyncSeq.2.0.21\lib\netstandard2.0\FSharp.Control.AsyncSeq.dll 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | {BE9B434E-D5CF-4B21-9C3A-58B79398B086} 159 | TicTacToe 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /Droid/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Elmish/Elmish.XamarinForms.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimbobbennett/TicTacToe/c4524537bfe934e6ee686e4b2805bb034ec7656c/Elmish/Elmish.XamarinForms.dll -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jim Bennett 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TicTacToe 2 | 3 | This is an implementation of a mobile app version of TicTacToe in F#. The app is build using Xamarin.Forms, using the Elmish arcitecture from [here](https://github.com/fsprojects/Elmish.XamarinForms). 4 | -------------------------------------------------------------------------------- /TicTacToe.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{f2a71f9b-5d33-465a-a702-920d77279786}") = "TicTacToe", "TicTacToe\TicTacToe.fsproj", "{BE9B434E-D5CF-4B21-9C3A-58B79398B086}" 5 | EndProject 6 | Project("{f2a71f9b-5d33-465a-a702-920d77279786}") = "TicTacToe.iOS", "iOS\TicTacToe.iOS.fsproj", "{E43D02E6-04E7-4E4E-A527-3C5D00D9D826}" 7 | EndProject 8 | Project("{f2a71f9b-5d33-465a-a702-920d77279786}") = "TicTacToe.Droid", "Droid\TicTacToe.Droid.fsproj", "{8E482415-A728-4B35-9C9A-31F4D3AE1D54}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | Debug|iPhoneSimulator = Debug|iPhoneSimulator 15 | Release|iPhone = Release|iPhone 16 | Release|iPhoneSimulator = Release|iPhoneSimulator 17 | Debug|iPhone = Debug|iPhone 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {BE9B434E-D5CF-4B21-9C3A-58B79398B086}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {BE9B434E-D5CF-4B21-9C3A-58B79398B086}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {BE9B434E-D5CF-4B21-9C3A-58B79398B086}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {BE9B434E-D5CF-4B21-9C3A-58B79398B086}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {BE9B434E-D5CF-4B21-9C3A-58B79398B086}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 25 | {BE9B434E-D5CF-4B21-9C3A-58B79398B086}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 26 | {BE9B434E-D5CF-4B21-9C3A-58B79398B086}.Release|iPhone.ActiveCfg = Release|Any CPU 27 | {BE9B434E-D5CF-4B21-9C3A-58B79398B086}.Release|iPhone.Build.0 = Release|Any CPU 28 | {BE9B434E-D5CF-4B21-9C3A-58B79398B086}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 29 | {BE9B434E-D5CF-4B21-9C3A-58B79398B086}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 30 | {BE9B434E-D5CF-4B21-9C3A-58B79398B086}.Debug|iPhone.ActiveCfg = Debug|Any CPU 31 | {BE9B434E-D5CF-4B21-9C3A-58B79398B086}.Debug|iPhone.Build.0 = Debug|Any CPU 32 | {E43D02E6-04E7-4E4E-A527-3C5D00D9D826}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator 33 | {E43D02E6-04E7-4E4E-A527-3C5D00D9D826}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator 34 | {E43D02E6-04E7-4E4E-A527-3C5D00D9D826}.Release|Any CPU.ActiveCfg = Release|iPhone 35 | {E43D02E6-04E7-4E4E-A527-3C5D00D9D826}.Release|Any CPU.Build.0 = Release|iPhone 36 | {E43D02E6-04E7-4E4E-A527-3C5D00D9D826}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator 37 | {E43D02E6-04E7-4E4E-A527-3C5D00D9D826}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator 38 | {E43D02E6-04E7-4E4E-A527-3C5D00D9D826}.Release|iPhone.ActiveCfg = Release|iPhone 39 | {E43D02E6-04E7-4E4E-A527-3C5D00D9D826}.Release|iPhone.Build.0 = Release|iPhone 40 | {E43D02E6-04E7-4E4E-A527-3C5D00D9D826}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator 41 | {E43D02E6-04E7-4E4E-A527-3C5D00D9D826}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator 42 | {E43D02E6-04E7-4E4E-A527-3C5D00D9D826}.Debug|iPhone.ActiveCfg = Debug|iPhone 43 | {E43D02E6-04E7-4E4E-A527-3C5D00D9D826}.Debug|iPhone.Build.0 = Debug|iPhone 44 | {8E482415-A728-4B35-9C9A-31F4D3AE1D54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {8E482415-A728-4B35-9C9A-31F4D3AE1D54}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {8E482415-A728-4B35-9C9A-31F4D3AE1D54}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {8E482415-A728-4B35-9C9A-31F4D3AE1D54}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {8E482415-A728-4B35-9C9A-31F4D3AE1D54}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 49 | {8E482415-A728-4B35-9C9A-31F4D3AE1D54}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 50 | {8E482415-A728-4B35-9C9A-31F4D3AE1D54}.Release|iPhone.ActiveCfg = Release|Any CPU 51 | {8E482415-A728-4B35-9C9A-31F4D3AE1D54}.Release|iPhone.Build.0 = Release|Any CPU 52 | {8E482415-A728-4B35-9C9A-31F4D3AE1D54}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 53 | {8E482415-A728-4B35-9C9A-31F4D3AE1D54}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 54 | {8E482415-A728-4B35-9C9A-31F4D3AE1D54}.Debug|iPhone.ActiveCfg = Debug|Any CPU 55 | {8E482415-A728-4B35-9C9A-31F4D3AE1D54}.Debug|iPhone.Build.0 = Debug|Any CPU 56 | EndGlobalSection 57 | EndGlobal 58 | -------------------------------------------------------------------------------- /TicTacToe/App.fs: -------------------------------------------------------------------------------- 1 | namespace TicTacToe 2 | 3 | open Elmish 4 | open Elmish.XamarinForms 5 | open Xamarin.Forms 6 | 7 | /// Represents a player and a player's move 8 | type Player = 9 | | X 10 | | O 11 | member p.Swap = match p with X -> O | O -> X 12 | 13 | /// Represents the game state contents of a single cell 14 | type GameCell = 15 | | Empty 16 | | Full of Player 17 | member x.CanPlay = (x = Empty) 18 | 19 | /// Represents the result of a game 20 | type GameResult = 21 | | StillPlaying 22 | | XWins 23 | | OWins 24 | | Draw 25 | 26 | /// Represents an update to the game 27 | type Msg = 28 | | PlayTL 29 | | PlayTC 30 | | PlayTR 31 | | PlayML 32 | | PlayMC 33 | | PlayMR 34 | | PlayBL 35 | | PlayBC 36 | | PlayBR 37 | | Restart 38 | 39 | /// Represents the state of the game board 40 | type Board = 41 | { 42 | TopLeft: GameCell 43 | TopCenter: GameCell 44 | TopRight: GameCell 45 | MiddleLeft: GameCell 46 | MiddleCenter: GameCell 47 | MiddleRight: GameCell 48 | BottomLeft: GameCell 49 | BottomCenter: GameCell 50 | BottomRight: GameCell 51 | } 52 | 53 | /// Represents the elements of a possibly-winning row 54 | type Row = 55 | { 56 | First: GameCell 57 | Second: GameCell 58 | Third: GameCell 59 | } 60 | 61 | /// Represents the state of the game 62 | type Model = 63 | { 64 | NextUp: Player 65 | Board: Board 66 | } 67 | 68 | /// The model, update and view content of the app. This is placed in an 69 | /// independent model to facilitate unit testing. 70 | module App = 71 | 72 | let initialBoard = 73 | { TopLeft = Empty; TopCenter = Empty; TopRight = Empty 74 | MiddleLeft = Empty; MiddleCenter = Empty; MiddleRight = Empty 75 | BottomLeft = Empty; BottomCenter = Empty; BottomRight = Empty } 76 | 77 | let init () = 78 | { NextUp = X 79 | Board = initialBoard } 80 | 81 | /// Check if there are any more moves available in the game 82 | let anyMoreMoves board = 83 | board.TopLeft.CanPlay || board.TopCenter.CanPlay || board.TopRight.CanPlay || 84 | board.MiddleLeft.CanPlay || board.MiddleCenter.CanPlay || board.MiddleRight.CanPlay || 85 | board.BottomLeft.CanPlay || board.BottomCenter.CanPlay || board.BottomRight.CanPlay 86 | 87 | let getWinLines m = 88 | [ // rows 89 | { First = m.TopLeft; Second = m.TopCenter; Third = m.TopRight } 90 | { First = m.MiddleLeft; Second = m.MiddleCenter; Third = m.MiddleRight } 91 | { First = m.BottomLeft; Second = m.BottomCenter; Third = m.BottomRight } 92 | 93 | // columns 94 | { First = m.TopLeft; Second = m.MiddleLeft; Third = m.BottomLeft } 95 | { First = m.TopCenter; Second = m.MiddleCenter; Third = m.BottomCenter } 96 | { First = m.TopRight; Second = m.MiddleRight; Third = m.BottomRight } 97 | 98 | // diagonals 99 | { First = m.TopLeft; Second = m.MiddleCenter; Third = m.BottomRight } 100 | { First = m.TopRight; Second = m.MiddleCenter; Third = m.BottomLeft } 101 | ] 102 | 103 | /// Determine if a line is a winning line. 104 | let getLineWinner l = 105 | match l.First, l.Second, l.Third with 106 | | Full X, Full X, Full X -> XWins 107 | | Full O, Full O, Full O -> OWins 108 | | _ -> StillPlaying 109 | 110 | /// Determine the game result, if any. 111 | let getGameResult m = 112 | let winLines = getWinLines m.Board |> Seq.map getLineWinner 113 | 114 | let xWins = winLines |> Seq.tryFind (fun r -> r = XWins) 115 | match xWins with 116 | | Some p -> p 117 | | _ -> 118 | 119 | let oWins = winLines |> Seq.tryFind (fun r -> r = OWins) 120 | match oWins with 121 | | Some p -> p 122 | | _ -> 123 | 124 | match anyMoreMoves m.Board with 125 | | true -> StillPlaying 126 | | false -> Draw 127 | 128 | /// Get a message to show the current game result 129 | let getMessage model = 130 | match getGameResult model with 131 | | StillPlaying -> sprintf "%O's turn" model.NextUp 132 | | XWins -> "X wins!" 133 | | OWins -> "O Wins!" 134 | | Draw -> "It is a draw!" 135 | 136 | /// The 'update' function to update the model 137 | let update gameOver msg model = 138 | let newModel = 139 | match msg with 140 | | PlayTL -> { model with Board = { model.Board with TopLeft = Full model.NextUp }; NextUp = model.NextUp.Swap } 141 | | PlayTC -> { model with Board = { model.Board with TopCenter = Full model.NextUp }; NextUp = model.NextUp.Swap } 142 | | PlayTR -> { model with Board = { model.Board with TopRight = Full model.NextUp }; NextUp = model.NextUp.Swap } 143 | | PlayML -> { model with Board = { model.Board with MiddleLeft = Full model.NextUp }; NextUp = model.NextUp.Swap } 144 | | PlayMC -> { model with Board = { model.Board with MiddleCenter = Full model.NextUp }; NextUp = model.NextUp.Swap } 145 | | PlayMR -> { model with Board = { model.Board with MiddleRight = Full model.NextUp }; NextUp = model.NextUp.Swap } 146 | | PlayBL -> { model with Board = { model.Board with BottomLeft = Full model.NextUp }; NextUp = model.NextUp.Swap } 147 | | PlayBC -> { model with Board = { model.Board with BottomCenter = Full model.NextUp }; NextUp = model.NextUp.Swap } 148 | | PlayBR -> { model with Board = { model.Board with BottomRight = Full model.NextUp }; NextUp = model.NextUp.Swap } 149 | | Restart -> init() 150 | 151 | // Make an announcement in the middle of the game. 152 | let result = getGameResult newModel 153 | if result <> StillPlaying then gameOver (getMessage newModel) 154 | 155 | // Return the new model. 156 | newModel 157 | 158 | /// A helper used in the 'view' function to get the name 159 | /// of the Xaml resource for the image for a player 160 | let imageForPlayer player = 161 | match player with 162 | | X -> "Cross" 163 | | O -> "Nought" 164 | 165 | /// A condition used in the 'view' function to check if we can play in a cell. 166 | /// The visual contents of a cell depends on this condition. 167 | let canPlay model cell = 168 | match cell with 169 | | Full _ -> false 170 | | Empty -> 171 | match getGameResult model with 172 | | StillPlaying -> true 173 | | _ -> false 174 | 175 | /// The 'view' function giving the Xaml bindings from the model to the view 176 | let view () = 177 | TicTacToePage (), 178 | [ "TurnMessage" |> Binding.oneWay (fun m -> getMessage m) 179 | "Restart" |> Binding.msg Restart 180 | "PlayTL" |> Binding.msg PlayTL 181 | "PlayTC" |> Binding.msg PlayTC 182 | "PlayTR" |> Binding.msg PlayTR 183 | "PlayML" |> Binding.msg PlayML 184 | "PlayMC" |> Binding.msg PlayMC 185 | "PlayMR" |> Binding.msg PlayMR 186 | "PlayBL" |> Binding.msg PlayBL 187 | "PlayBC" |> Binding.msg PlayBC 188 | "PlayBR" |> Binding.msg PlayBR 189 | "CanPlayTL" |> Binding.oneWay (fun m -> canPlay m m.Board.TopLeft ) 190 | "CanPlayTC" |> Binding.oneWay (fun m -> canPlay m m.Board.TopCenter ) 191 | "CanPlayTR" |> Binding.oneWay (fun m -> canPlay m m.Board.TopRight) 192 | "CanPlayML" |> Binding.oneWay (fun m -> canPlay m m.Board.MiddleLeft ) 193 | "CanPlayMC" |> Binding.oneWay (fun m -> canPlay m m.Board.MiddleCenter ) 194 | "CanPlayMR" |> Binding.oneWay (fun m -> canPlay m m.Board.MiddleRight ) 195 | "CanPlayBL" |> Binding.oneWay (fun m -> canPlay m m.Board.BottomLeft ) 196 | "CanPlayBC" |> Binding.oneWay (fun m -> canPlay m m.Board.BottomCenter ) 197 | "CanPlayBR" |> Binding.oneWay (fun m -> canPlay m m.Board.BottomRight ) 198 | "ImageTL" |> Binding.oneWay (fun m -> match m.Board.TopLeft with Empty -> "" | Full p -> imageForPlayer p ) 199 | "ImageTC" |> Binding.oneWay (fun m -> match m.Board.TopCenter with Empty -> "" | Full p -> imageForPlayer p ) 200 | "ImageTR" |> Binding.oneWay (fun m -> match m.Board.TopRight with Empty -> "" | Full p -> imageForPlayer p ) 201 | "ImageML" |> Binding.oneWay (fun m -> match m.Board.MiddleLeft with Empty -> "" | Full p -> imageForPlayer p ) 202 | "ImageMC" |> Binding.oneWay (fun m -> match m.Board.MiddleCenter with Empty -> "" | Full p -> imageForPlayer p ) 203 | "ImageMR" |> Binding.oneWay (fun m -> match m.Board.MiddleRight with Empty -> "" | Full p -> imageForPlayer p ) 204 | "ImageBL" |> Binding.oneWay (fun m -> match m.Board.BottomLeft with Empty -> "" | Full p -> imageForPlayer p ) 205 | "ImageBC" |> Binding.oneWay (fun m -> match m.Board.BottomCenter with Empty -> "" | Full p -> imageForPlayer p ) 206 | "ImageBR" |> Binding.oneWay (fun m -> match m.Board.BottomRight with Empty -> "" | Full p -> imageForPlayer p ) 207 | ] 208 | 209 | 210 | /// Stitch the model, update and view content into a single app. 211 | type App() = 212 | inherit Application() 213 | 214 | // Display a modal message giving the game result. This is doing a UI 215 | // action in the model update, which is ok for modal messages. We factor 216 | // this dependency out to allow unit testing of the 'update' function. 217 | let gameOver msg = 218 | Application.Current.MainPage.DisplayAlert("Game over", msg, "OK") |> ignore 219 | 220 | let page = 221 | Program.mkSimple App.init (App.update gameOver) (fun _ _ -> App.view()) 222 | |> Program.withConsoleTrace 223 | |> Program.run 224 | 225 | do base.MainPage <- new NavigationPage(page, BarBackgroundColor = Color.LightBlue, BarTextColor = Color.Black) 226 | 227 | -------------------------------------------------------------------------------- /TicTacToe/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace TicTacToe 2 | open System.Reflection 3 | open System.Runtime.CompilerServices 4 | 5 | [] 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | 13 | // The assembly version has the format {Major}.{Minor}.{Build}.{Revision} 14 | 15 | [] 16 | 17 | //[] 18 | //[] 19 | [] 20 | () 21 | -------------------------------------------------------------------------------- /TicTacToe/Converters.fs: -------------------------------------------------------------------------------- 1 | namespace TicTacToe 2 | 3 | open Xamarin.Forms 4 | 5 | type InvertedBooleanConverter () = 6 | interface IValueConverter with 7 | member this.Convert(value,targetType,parameter,culture) = 8 | let b = value :?> bool 9 | not b :> obj 10 | 11 | member this.ConvertBack(value,targetType,parameter,culture) = 12 | let b = value :?> bool 13 | not b :> obj -------------------------------------------------------------------------------- /TicTacToe/TicTacToe.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | false 5 | 6 | 7 | 8 | 9 | MSBuild:UpdateDesignTimeXaml 10 | 11 | 12 | TicTacToePage.xaml 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ..\Elmish\Elmish.XamarinForms.dll 25 | 26 | 27 | -------------------------------------------------------------------------------- /TicTacToe/TicTacToePage.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 43 | 46 | 49 | 52 | 53 | 56 | 59 | 62 | 63 | 66 | 69 | 72 | 73 | 76 | 79 | 82 | 83 |