Add References to git
63
info/Minesweeper2/.gitattributes
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
340
info/Minesweeper2/.gitignore
vendored
Normal file
@ -0,0 +1,340 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- Backup*.rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
6
info/Minesweeper2/Bulk Runner/App.config
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
|
||||
</startup>
|
||||
</configuration>
|
84
info/Minesweeper2/Bulk Runner/Bulk Runner.csproj
Normal file
@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{B19C6960-EC27-4425-BFB5-E7AA65D69C28}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>Bulk_Runner</RootNamespace>
|
||||
<AssemblyName>Bulk Runner</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
<InstallFrom>Disk</InstallFrom>
|
||||
<UpdateEnabled>false</UpdateEnabled>
|
||||
<UpdateMode>Foreground</UpdateMode>
|
||||
<UpdateInterval>7</UpdateInterval>
|
||||
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||
<UpdatePeriodically>false</UpdatePeriodically>
|
||||
<UpdateRequired>false</UpdateRequired>
|
||||
<MapFileExtensions>true</MapFileExtensions>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="BulkRunner.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include=".NETFramework,Version=v4.7.2">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>Microsoft .NET Framework 4.7.2 %28x86 and x64%29</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<Import Project="..\MinesweeperGame\MinesweeperGame.projitems" Label="Shared" />
|
||||
<Import Project="..\MinesweeperSolver\MinesweeperSolver.projitems" Label="Shared" />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
158
info/Minesweeper2/Bulk Runner/BulkRunner.cs
Normal file
@ -0,0 +1,158 @@
|
||||
using MinesweeperControl;
|
||||
using MinesweeperSolver;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using static MinesweeperControl.GameDescription;
|
||||
using static MinesweeperControl.MinesweeperGame;
|
||||
|
||||
namespace Bulk_Runner {
|
||||
class BulkRunner {
|
||||
|
||||
private static readonly bool pauseOnWin = false;
|
||||
private static readonly bool playUntilWin = false;
|
||||
private static GameAction[] firstPlay;
|
||||
|
||||
private static int[] deathTable = new int[50];
|
||||
|
||||
static void Main(string[] args) {
|
||||
|
||||
GameDescription description = GameDescription.EXPERT_SAFE;
|
||||
//GameDescription description = new GameDescription(30, 24, 225, GameType.Safe);
|
||||
//GameDescription description = new GameDescription(100, 100, 1500, GameType.Zero);
|
||||
//GameDescription description = new GameDescription(50, 50, 500, GameType.Safe);
|
||||
//GameDescription description = new GameDescription(8, 8, 34, GameType.Safe);
|
||||
|
||||
if (description.gameType == GameType.Zero) {
|
||||
firstPlay = new GameAction[] { new GameAction(3, 3, ActionType.Clear) };
|
||||
} else {
|
||||
firstPlay = new GameAction[] { new GameAction(0, 0, ActionType.Clear) };
|
||||
}
|
||||
|
||||
int seedGen = new Random().Next();
|
||||
//int seedGen = 1104105816;
|
||||
|
||||
Random rng = new Random(seedGen);
|
||||
|
||||
int run = 100000;
|
||||
int steps = 100;
|
||||
|
||||
int won = 0;
|
||||
int lost = 0;
|
||||
int deaths = 0;
|
||||
|
||||
SolverMain.Initialise();
|
||||
|
||||
Write("using generation seed " + seedGen + " to run " + run + " games of minesweeper " + description.AsText());
|
||||
Write("--- Press Enter to start ---");
|
||||
|
||||
Console.ReadLine();
|
||||
|
||||
long start = DateTime.Now.Ticks;
|
||||
|
||||
for (int i=0; i < run; i++) {
|
||||
|
||||
int seed = rng.Next();
|
||||
|
||||
MinesweeperGame game = new MinesweeperGame(description, seed, !playUntilWin);
|
||||
|
||||
//Write("Seed " + game.seed + " starting");
|
||||
|
||||
GameStatus status = AutoPlayRunner(game);
|
||||
|
||||
int died = game.GetDeaths();
|
||||
if (died < deathTable.Length) {
|
||||
deathTable[died]++;
|
||||
} else {
|
||||
deathTable[deathTable.Length - 1]++;
|
||||
}
|
||||
|
||||
|
||||
deaths = deaths + died;
|
||||
|
||||
if (status == GameStatus.Lost) {
|
||||
lost++;
|
||||
} else if (status == GameStatus.Won) {
|
||||
won++;
|
||||
if (pauseOnWin) {
|
||||
Write("Seed " + game.seed + " won");
|
||||
Write("--- Press Enter to continue ---");
|
||||
Console.ReadLine();
|
||||
}
|
||||
} else {
|
||||
Write("Seed " + game.seed + " : Unexpected game status from autoplay " + status);
|
||||
}
|
||||
|
||||
if ((i + 1) % steps == 0) {
|
||||
Write("Seed " + game.seed + " finished. Games won " + won + " out of " + (i + 1));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
long duration = (DateTime.Now.Ticks - start ) / 10000;
|
||||
|
||||
|
||||
Write("Games won " + won + ", lost " + lost + " out of " + run + " in " + duration + " milliseconds.");
|
||||
|
||||
double winRate = (won * 10000 / run) / 100d;
|
||||
|
||||
Write("Win rate " + winRate + "%");
|
||||
|
||||
Write("Deaths " + deaths + " average deaths per game " + (deaths * 1000 / run) / 1000d);
|
||||
|
||||
for (int i=0; i < deathTable.Length - 1; i++) {
|
||||
Write("Died " + i + " times in " + deathTable[i] + " games");
|
||||
}
|
||||
Write("Died >=" + (deathTable.Length - 1) + " times in " + deathTable[deathTable.Length - 1] + " games");
|
||||
|
||||
Console.ReadLine();
|
||||
|
||||
|
||||
}
|
||||
|
||||
// this method runs on a different thread. gameNumber can be changed by clicking chosing a new game from the UI thread.
|
||||
private static GameStatus AutoPlayRunner(MinesweeperGame game) {
|
||||
|
||||
//long start = DateTime.Now.Ticks;
|
||||
|
||||
SolverActionHeader solverActions;
|
||||
|
||||
GameResult result = game.ProcessActions(firstPlay);
|
||||
|
||||
SolverInfo solverInfo = new SolverInfo(game.description);
|
||||
|
||||
while (result.status == GameStatus.InPlay) {
|
||||
|
||||
solverInfo.AddInformation(result); // let the solver know what has happened
|
||||
|
||||
solverActions = SolverMain.FindActions(solverInfo); // pass the solver info into the solver and see what it comes up with
|
||||
|
||||
if (solverActions.solverActions.Count == 0) {
|
||||
Write("No actions returned by the solver!!");
|
||||
break;
|
||||
}
|
||||
|
||||
result = game.ProcessActions(solverActions.solverActions);
|
||||
|
||||
//Write("Game controller returned " + result.actionResults.Count + " results from this action");
|
||||
|
||||
//foreach (SolverAction action in solverActions) {
|
||||
// Write("Playing " + action.AsText() + " probability " + action.safeProbability);
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
//long end = DateTime.Now.Ticks;
|
||||
|
||||
//Write("game took " + (end - start) + " ticks");
|
||||
|
||||
|
||||
return result.status;
|
||||
}
|
||||
|
||||
private static void Write(String text) {
|
||||
Console.WriteLine(text);
|
||||
}
|
||||
}
|
||||
}
|
36
info/Minesweeper2/Bulk Runner/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Bulk Runner")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Bulk Runner")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("b19c6960-ec27-4425-bfb5-e7aa65d69c28")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
41
info/Minesweeper2/Minesweeper.net.sln
Normal file
@ -0,0 +1,41 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29123.89
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MinesweeperGui", "MinesweeperGui\MinesweeperGui.csproj", "{C981193D-B2EF-40D2-A0E1-99071CA2936F}"
|
||||
EndProject
|
||||
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "MinesweeperGame", "MinesweeperGame\MinesweeperGame.shproj", "{DBCA9D17-B7A4-477E-A515-6E82B39840F1}"
|
||||
EndProject
|
||||
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "MinesweeperSolver", "MinesweeperSolver\MinesweeperSolver.shproj", "{60591E9A-F066-4AF3-B1E3-B20B2B2B0B1D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bulk Runner", "Bulk Runner\Bulk Runner.csproj", "{B19C6960-EC27-4425-BFB5-E7AA65D69C28}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SharedMSBuildProjectFiles) = preSolution
|
||||
MinesweeperSolver\MinesweeperSolver.projitems*{60591e9a-f066-4af3-b1e3-b20b2b2b0b1d}*SharedItemsImports = 13
|
||||
MinesweeperGame\MinesweeperGame.projitems*{b19c6960-ec27-4425-bfb5-e7aa65d69c28}*SharedItemsImports = 4
|
||||
MinesweeperSolver\MinesweeperSolver.projitems*{b19c6960-ec27-4425-bfb5-e7aa65d69c28}*SharedItemsImports = 4
|
||||
MinesweeperGame\MinesweeperGame.projitems*{dbca9d17-b7a4-477e-a515-6e82b39840f1}*SharedItemsImports = 13
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{C981193D-B2EF-40D2-A0E1-99071CA2936F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C981193D-B2EF-40D2-A0E1-99071CA2936F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C981193D-B2EF-40D2-A0E1-99071CA2936F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C981193D-B2EF-40D2-A0E1-99071CA2936F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B19C6960-EC27-4425-BFB5-E7AA65D69C28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B19C6960-EC27-4425-BFB5-E7AA65D69C28}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B19C6960-EC27-4425-BFB5-E7AA65D69C28}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B19C6960-EC27-4425-BFB5-E7AA65D69C28}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {22747E34-ADCA-4C34-9FA8-EA30B962C694}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
39
info/Minesweeper2/MinesweeperGame/GameDescription.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MinesweeperControl {
|
||||
|
||||
public class GameDescription {
|
||||
|
||||
public enum GameType { Safe, Zero };
|
||||
|
||||
public static readonly GameDescription BEGINNER_SAFE = new GameDescription(8, 8, 10, GameType.Safe);
|
||||
public static readonly GameDescription INTERMEDIATE_SAFE = new GameDescription(16, 16, 40, GameType.Safe);
|
||||
public static readonly GameDescription EXPERT_SAFE = new GameDescription(30, 16, 99, GameType.Safe);
|
||||
|
||||
public static readonly GameDescription BEGINNER_ZERO = new GameDescription(9, 9, 10, GameType.Zero);
|
||||
public static readonly GameDescription INTERMEDIATE_ZERO = new GameDescription(16, 16, 40, GameType.Zero);
|
||||
public static readonly GameDescription EXPERT_ZERO = new GameDescription(30, 16, 99, GameType.Zero);
|
||||
|
||||
public readonly int width;
|
||||
public readonly int height;
|
||||
public readonly int mines;
|
||||
public readonly GameType gameType;
|
||||
|
||||
public GameDescription(int width, int height, int mines, GameType gameType) {
|
||||
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.mines = mines;
|
||||
this.gameType = gameType;
|
||||
|
||||
}
|
||||
|
||||
public string AsText() {
|
||||
|
||||
return width + "x" + height + "x" + mines + " " + gameType + " start";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
506
info/Minesweeper2/MinesweeperGame/MinesweeperGame.cs
Normal file
@ -0,0 +1,506 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static MinesweeperControl.GameDescription;
|
||||
|
||||
namespace MinesweeperControl {
|
||||
/*
|
||||
* This class describes a game of minesweeper
|
||||
*/
|
||||
public class MinesweeperGame {
|
||||
|
||||
public enum ActionType { Clear, Flag, Chord, Dead };
|
||||
public enum GameStatus { NotStarted, InPlay, Won, Lost };
|
||||
public enum ResultType { Cleared, Flagged, Hidden, Exploded, Mine, FlaggedWrong };
|
||||
|
||||
// describes the actions being perform in the gane
|
||||
public class GameAction {
|
||||
|
||||
public readonly int x;
|
||||
public readonly int y;
|
||||
public readonly ActionType action;
|
||||
|
||||
public GameAction(int x, int y, ActionType action) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String AsText() {
|
||||
return "(" + x + "," + y + ") " + action;
|
||||
}
|
||||
}
|
||||
|
||||
// describes the result of the actions on the game
|
||||
public class GameResult {
|
||||
|
||||
public readonly GameStatus status;
|
||||
public readonly List<ActionResult> actionResults;
|
||||
|
||||
public GameResult(GameStatus status, List<ActionResult> actionResults) {
|
||||
this.status = status;
|
||||
this.actionResults = actionResults;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// describes the result of actions being perform in the gane
|
||||
public class ActionResult {
|
||||
|
||||
public readonly int x;
|
||||
public readonly int y;
|
||||
public readonly ResultType resultType;
|
||||
public readonly int value;
|
||||
|
||||
internal ActionResult(int x, int y, ResultType resultType) : this(x, y, resultType, 0) {
|
||||
}
|
||||
|
||||
internal ActionResult(int x, int y, ResultType resultType, int value) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.resultType = resultType;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// game variables
|
||||
public readonly GameDescription description;
|
||||
public readonly int seed; // seed to use to build the game
|
||||
|
||||
private readonly int[] adjacentOffset = new int[8];
|
||||
|
||||
private GameStatus gameStatus = GameStatus.NotStarted;
|
||||
private int tilesLeft;
|
||||
private MinesweeperTile[] tiles;
|
||||
private int minesLeft;
|
||||
|
||||
private readonly bool hardcore;
|
||||
private int deaths = 0;
|
||||
|
||||
public MinesweeperGame(GameDescription description, int seed) : this(description, seed, true) {
|
||||
}
|
||||
|
||||
public MinesweeperGame(GameDescription description, int seed, bool hardcore) {
|
||||
|
||||
if (seed == 0) {
|
||||
this.seed = new Random().Next(1, Int32.MaxValue);
|
||||
} else {
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
this.description = description;
|
||||
this.hardcore = hardcore;
|
||||
|
||||
// create adjacent offsets
|
||||
adjacentOffset[0] = -description.width - 1;
|
||||
adjacentOffset[1] = -description.width;
|
||||
adjacentOffset[2] = -description.width + 1;
|
||||
adjacentOffset[3] = -1;
|
||||
adjacentOffset[4] = 1;
|
||||
adjacentOffset[5] = +description.width - 1;
|
||||
adjacentOffset[6] = +description.width;
|
||||
adjacentOffset[7] = +description.width + 1;
|
||||
|
||||
tilesLeft = this.description.height * this.description.width - this.description.mines;
|
||||
minesLeft = description.mines;
|
||||
|
||||
// create all the tiles. the mines get placed at the first clear.
|
||||
CreateTiles();
|
||||
|
||||
}
|
||||
|
||||
public int GetMinesLeft() {
|
||||
return minesLeft;
|
||||
}
|
||||
|
||||
public int GetDeaths() {
|
||||
return deaths;
|
||||
}
|
||||
|
||||
public GameStatus GetGameStatus() {
|
||||
return this.gameStatus;
|
||||
}
|
||||
|
||||
public GameResult ProcessActions<T>(IList<T> actions) where T : GameAction { // accept any Array of classes extending GameAction
|
||||
|
||||
//long start = DateTime.Now.Ticks;
|
||||
|
||||
List<ActionResult> actionResults = new List<ActionResult>();
|
||||
|
||||
foreach (GameAction action in actions) {
|
||||
|
||||
if (gameStatus != GameStatus.Lost && gameStatus != GameStatus.Won) { // only process actions while the game is in play
|
||||
if (action.action == ActionType.Clear) {
|
||||
actionResults.AddRange(ClearTile(action));
|
||||
} else if (action.action == ActionType.Flag) {
|
||||
actionResults.AddRange(FlagTile(action));
|
||||
} else if (action.action == ActionType.Chord) {
|
||||
actionResults.AddRange(ChordTile(action));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GameStatus finalStatus = gameStatus; // take the game status for the game
|
||||
|
||||
if (gameStatus == GameStatus.Lost) {
|
||||
|
||||
for (var i = 0; i < this.tiles.Length; i++) {
|
||||
MinesweeperTile tile = this.tiles[i];
|
||||
if (tile.IsMine() && !tile.IsFlagged() && !tile.GetExploded()) {
|
||||
actionResults.Add(new ActionResult(tile.GetX(), tile.GetY(), ResultType.Mine)); // show remaining mines
|
||||
}
|
||||
if (!tile.IsMine() && tile.IsFlagged()) {
|
||||
actionResults.Add(new ActionResult(tile.GetX(), tile.GetY(), ResultType.FlaggedWrong)); // show flags placed wrong
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Write("Game processing actions took " + (DateTime.Now.Ticks - start) + " ticks");
|
||||
|
||||
return new GameResult(finalStatus, actionResults);
|
||||
|
||||
}
|
||||
|
||||
private List<ActionResult> FlagTile(GameAction action) {
|
||||
|
||||
List<ActionResult> actionResults = new List<ActionResult>();
|
||||
|
||||
int index = GetIndex(action.x, action.y);
|
||||
|
||||
MinesweeperTile tile = this.tiles[index];
|
||||
|
||||
// if the tile is covered then toggle the flag and return it's new state
|
||||
if (tile.IsCovered()) {
|
||||
tile.ToggleFlagged();
|
||||
if (tile.IsFlagged()) {
|
||||
actionResults.Add(new ActionResult(action.x, action.y, ResultType.Flagged));
|
||||
minesLeft--;
|
||||
} else {
|
||||
if (tile.GetExploded()) {
|
||||
actionResults.Add(new ActionResult(action.x, action.y, ResultType.Exploded));
|
||||
} else {
|
||||
actionResults.Add(new ActionResult(action.x, action.y, ResultType.Hidden));
|
||||
}
|
||||
|
||||
minesLeft++;
|
||||
}
|
||||
}
|
||||
|
||||
return actionResults;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// clicks the assigned tile and returns an object containing a list of tiles cleared
|
||||
private List<ActionResult> ClearTile(GameAction action) {
|
||||
|
||||
List<ActionResult> actionResults = new List<ActionResult>();
|
||||
|
||||
int index = GetIndex(action.x, action.y);
|
||||
|
||||
// if this is the first click then create the tiles and place the mines
|
||||
if (this.gameStatus == GameStatus.NotStarted) {
|
||||
PlaceMines(index);
|
||||
}
|
||||
|
||||
MinesweeperTile tile = this.tiles[index];
|
||||
|
||||
// are we clicking on a flag
|
||||
if (tile.IsFlagged()) {
|
||||
Write("Unable to Clear: Clicked on a Flag");
|
||||
|
||||
} else if (tile.GetExploded()) {
|
||||
Write("Unable to Clear: Clicked on an exploded Mine");
|
||||
|
||||
} else if (tile.IsMine()) {
|
||||
//Write("Gameover: Clicked on a mine");
|
||||
|
||||
deaths++;
|
||||
if (hardcore) {
|
||||
this.gameStatus = GameStatus.Lost;
|
||||
}
|
||||
tile.SetExploded(true);
|
||||
actionResults.Add(new ActionResult(action.x, action.y, ResultType.Exploded));
|
||||
|
||||
} else {
|
||||
if (tile.IsCovered()) { // make sure the tile is clickable
|
||||
|
||||
List<MinesweeperTile> tilesToReveal = new List<MinesweeperTile>();
|
||||
tilesToReveal.Add(tile);
|
||||
|
||||
actionResults.AddRange(Reveal(tilesToReveal));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return actionResults;
|
||||
|
||||
}
|
||||
|
||||
// chord the assigned tile and returns an object containing a list of tiles cleared
|
||||
private List<ActionResult> ChordTile(GameAction action) {
|
||||
|
||||
List<ActionResult> actionResults = new List<ActionResult>();
|
||||
|
||||
int index = GetIndex(action.x, action.y);
|
||||
|
||||
// if this is the first click then create the tiles and place the mines
|
||||
if (this.gameStatus == GameStatus.NotStarted) {
|
||||
PlaceMines(index);
|
||||
}
|
||||
|
||||
MinesweeperTile tile = this.tiles[index];
|
||||
|
||||
int flagCount = 0;
|
||||
int hiddenCount = 0;
|
||||
foreach (int adjIndex in GetAdjacentIndex(index)) {
|
||||
if (tiles[adjIndex].IsFlagged()) {
|
||||
flagCount++;
|
||||
} else if (tiles[adjIndex].IsCovered()) {
|
||||
hiddenCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// If the hidden count is zero then there is nothing to do
|
||||
if (hiddenCount == 0) {
|
||||
Write("Unable to Chord: Nothing to clear");
|
||||
return actionResults;
|
||||
}
|
||||
|
||||
// nothing to do if the tile is not yet surrounded by the correct number of flags
|
||||
if (tile.GetValue() != flagCount) {
|
||||
Write("Unable to Chord: value=" + tile.GetValue() + " flags=" + flagCount);
|
||||
return actionResults;
|
||||
}
|
||||
|
||||
// see if there are any unflagged bombs in the area to be chorded - this loses the game
|
||||
var bombCount = 0;
|
||||
foreach (int adjIndex in GetAdjacentIndex(index)) {
|
||||
MinesweeperTile adjTile = tiles[adjIndex];
|
||||
if (adjTile.IsMine() && !adjTile.IsFlagged()) {
|
||||
adjTile.SetExploded(true);
|
||||
actionResults.Add(new ActionResult(adjTile.GetX(), adjTile.GetY(), ResultType.Exploded)); // mark the tile as exploded
|
||||
bombCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// if we have triggered a bomb then return
|
||||
if (bombCount != 0) {
|
||||
deaths = deaths + bombCount;
|
||||
if (hardcore) {
|
||||
this.gameStatus = GameStatus.Lost;
|
||||
}
|
||||
return actionResults;
|
||||
}
|
||||
|
||||
// seems okay, so do the chording
|
||||
List<MinesweeperTile> tilesToReveal = new List<MinesweeperTile>();
|
||||
|
||||
// determine which tiles need revealing
|
||||
foreach (int adjIndex in this.GetAdjacentIndex(index)) {
|
||||
MinesweeperTile adjTile = tiles[adjIndex];
|
||||
if (adjTile.IsCovered() && !adjTile.IsFlagged()) { // covered and not flagged
|
||||
tilesToReveal.Add(adjTile);
|
||||
}
|
||||
}
|
||||
|
||||
actionResults.AddRange(Reveal(tilesToReveal));
|
||||
|
||||
return actionResults;
|
||||
|
||||
}
|
||||
|
||||
// takes a list of tiles and reveals them and expands any zeros
|
||||
private List<ActionResult> Reveal(List<MinesweeperTile> firstTiles) {
|
||||
|
||||
List<ActionResult> actionResults = new List<ActionResult>();
|
||||
|
||||
var soFar = 0;
|
||||
|
||||
foreach (MinesweeperTile firstTile in firstTiles) {
|
||||
firstTile.SetCovered(false);
|
||||
}
|
||||
|
||||
int safety = 1000000;
|
||||
|
||||
while (soFar < firstTiles.Count) {
|
||||
|
||||
MinesweeperTile tile = firstTiles[soFar];
|
||||
|
||||
actionResults.Add(new ActionResult(tile.GetX(), tile.GetY(), ResultType.Cleared, tile.GetValue()));
|
||||
|
||||
this.tilesLeft--;
|
||||
|
||||
// if the value is zero then for each adjacent tile not yet revealed add it to the list
|
||||
if (tile.GetValue() == 0) {
|
||||
|
||||
foreach (int adjIndex in GetAdjacentIndex(tile.GetIndex())) {
|
||||
|
||||
MinesweeperTile adjTile = this.tiles[adjIndex];
|
||||
|
||||
if (adjTile.IsCovered() && !adjTile.IsFlagged()) { // if not covered and not a flag
|
||||
adjTile.SetCovered(false); // it will be uncovered in a bit
|
||||
firstTiles.Add(adjTile);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
soFar++;
|
||||
if (safety-- < 0) {
|
||||
Write("MinesweeperGame: Reveal Safety limit reached !!");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if there are no tiles left to find then set the remaining tiles to flagged and we've won
|
||||
if (this.tilesLeft == 0) {
|
||||
for (var i = 0; i < this.tiles.Length; i++) {
|
||||
MinesweeperTile tile = this.tiles[i];
|
||||
if (tile.IsMine() && !tile.IsFlagged()) {
|
||||
|
||||
minesLeft--;
|
||||
tile.ToggleFlagged();
|
||||
actionResults.Add(new ActionResult(tile.GetX(), tile.GetY(), ResultType.Flagged)); // auto set remaining flags
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
this.gameStatus = GameStatus.Won;
|
||||
|
||||
}
|
||||
|
||||
return actionResults;
|
||||
}
|
||||
|
||||
// converts X and Y position to an Index
|
||||
private int GetIndex(int x, int y) {
|
||||
return y * this.description.width + x;
|
||||
}
|
||||
|
||||
// create the tiles
|
||||
private void CreateTiles() {
|
||||
|
||||
//long start = DateTime.Now.Ticks;
|
||||
|
||||
tiles = new MinesweeperTile[this.description.width * this.description.height];
|
||||
|
||||
// create the tiles and store non-excluded indices into a list
|
||||
for (int y = 0; y < this.description.height; y++) {
|
||||
for (int x = 0; x < this.description.width; x++) {
|
||||
|
||||
int i = GetIndex(x, y);
|
||||
tiles[i] = new MinesweeperTile(i, x, y);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//Write("Ticks to create MinesweeperTiles " + (DateTime.Now.Ticks - start));
|
||||
}
|
||||
|
||||
// builds all the tiles and assigns bombs to them
|
||||
private void PlaceMines(int firstIndex) {
|
||||
|
||||
//long start = DateTime.Now.Ticks;
|
||||
|
||||
// hold the tiles to exclude from being a mine
|
||||
HashSet<int> excludedIndices = new HashSet<int>();
|
||||
excludedIndices.Add(firstIndex);
|
||||
|
||||
// for a zero start game all the adjacent tile can't be mines either
|
||||
if (this.description.gameType == GameType.Zero) {
|
||||
foreach (int adjIndex in GetAdjacentIndex(firstIndex)) {
|
||||
excludedIndices.Add(adjIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// create a list of all included indices
|
||||
List<int> indices = new List<int>();
|
||||
for (int y = 0; y < this.description.height; y++) {
|
||||
for (int x = 0; x < this.description.width; x++) {
|
||||
|
||||
int i = GetIndex(x, y);
|
||||
if (!excludedIndices.Contains(i)) {
|
||||
indices.Add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// shuffle the indices using a seed
|
||||
indices.Shuffle(seed);
|
||||
|
||||
// allocate the mines and calculate the values
|
||||
for (int i = 0; i < description.mines; i++) {
|
||||
int index = indices[i];
|
||||
MinesweeperTile tile = tiles[index];
|
||||
|
||||
//Utility.Write("Setting " + tile.AsText() + " to be a mine");
|
||||
tile.SetMine(true); // this is set to be a mine
|
||||
|
||||
// set each affected tile to have an increased 'value'
|
||||
foreach (int adjIndex in GetAdjacentIndex(tile.GetIndex())) {
|
||||
tiles[adjIndex].IncrementValue();
|
||||
}
|
||||
}
|
||||
|
||||
this.gameStatus = GameStatus.InPlay;
|
||||
|
||||
//Write("Ticks to place mines on MinesweeperTiles " + (DateTime.Now.Ticks - start));
|
||||
}
|
||||
|
||||
// returns all the indices adjacent to this index
|
||||
private List<int> GetAdjacentIndex(int index) {
|
||||
|
||||
int col = index % description.width;
|
||||
int row = index / description.width;
|
||||
|
||||
int first_row = Math.Max(0, row - 1);
|
||||
int last_row = Math.Min(description.height - 1, row + 1);
|
||||
|
||||
int first_col = Math.Max(0, col - 1);
|
||||
int last_col = Math.Min(description.width - 1, col + 1);
|
||||
|
||||
List<int> result = new List<int>();
|
||||
|
||||
for (int r = first_row; r <= last_row; r++) {
|
||||
for (int c = first_col; c <= last_col; c++) {
|
||||
int i = description.width * r + c;
|
||||
if (i != index) {
|
||||
result.Add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void Write(String text) {
|
||||
Console.WriteLine(text);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Shuffler {
|
||||
|
||||
// shuffle a given array
|
||||
public static void Shuffle<T>(this IList<T> list, int seed) {
|
||||
|
||||
Random rng = new Random(seed);
|
||||
|
||||
int n = list.Count;
|
||||
while (n > 1) {
|
||||
n--;
|
||||
int k = rng.Next(n + 1);
|
||||
T value = list[k];
|
||||
list[k] = list[n];
|
||||
list[n] = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
16
info/Minesweeper2/MinesweeperGame/MinesweeperGame.projitems
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
<HasSharedItems>true</HasSharedItems>
|
||||
<SharedGUID>dbca9d17-b7a4-477e-a515-6e82b39840f1</SharedGUID>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration">
|
||||
<Import_RootNamespace>MinesweeperControl</Import_RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)GameDescription.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)MinesweeperGame.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)MinesweeperTile.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
13
info/Minesweeper2/MinesweeperGame/MinesweeperGame.shproj
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>dbca9d17-b7a4-477e-a515-6e82b39840f1</ProjectGuid>
|
||||
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
|
||||
<PropertyGroup />
|
||||
<Import Project="MinesweeperGame.projitems" Label="Shared" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
|
||||
</Project>
|
82
info/Minesweeper2/MinesweeperGame/MinesweeperTile.cs
Normal file
@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MinesweeperControl {
|
||||
|
||||
internal class MinesweeperTile {
|
||||
|
||||
private readonly int index;
|
||||
private readonly int x;
|
||||
private readonly int y;
|
||||
private bool covered = true;
|
||||
private int value = 0;
|
||||
private bool flagged = false;
|
||||
private bool exploded = false;
|
||||
private bool mine;
|
||||
|
||||
internal MinesweeperTile(int index, int x, int y) {
|
||||
|
||||
this.index = index;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
|
||||
}
|
||||
|
||||
internal int GetIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
internal int GetX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
internal int GetY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
internal bool IsCovered() {
|
||||
return covered;
|
||||
}
|
||||
|
||||
internal void SetCovered(bool value) {
|
||||
covered = value;
|
||||
}
|
||||
|
||||
internal int GetValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
internal void IncrementValue() {
|
||||
this.value++;
|
||||
}
|
||||
|
||||
internal bool IsFlagged() {
|
||||
return flagged;
|
||||
}
|
||||
|
||||
internal void ToggleFlagged() {
|
||||
flagged = !flagged;
|
||||
}
|
||||
|
||||
internal bool GetExploded() {
|
||||
return exploded;
|
||||
}
|
||||
|
||||
internal void SetExploded(bool value) {
|
||||
exploded = value;
|
||||
}
|
||||
|
||||
internal bool IsMine() {
|
||||
return mine;
|
||||
}
|
||||
|
||||
internal void SetMine(bool value) {
|
||||
mine = value;
|
||||
}
|
||||
|
||||
internal String AsText() {
|
||||
return "(" + x + "," + y + ")";
|
||||
}
|
||||
}
|
||||
}
|
61
info/Minesweeper2/MinesweeperGui/App.xaml
Normal file
@ -0,0 +1,61 @@
|
||||
<Application x:Class="MinesweeperGui.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:MinesweeperGui"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
||||
<Style TargetType="Button">
|
||||
<!--Set to true to not get any properties from the themes.-->
|
||||
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="border" Background="Red" BorderBrush="Black" BorderThickness="3" CornerRadius="5" >
|
||||
<Grid Height="40" Width="120">
|
||||
<Image x:Name="buttonImage" Source="resources/images/facingDown.png" Stretch="Fill" />
|
||||
<ContentPresenter Margin="{TemplateBinding Padding}" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="true">
|
||||
<Setter TargetName="border" Property="BorderBrush" Value="Blue" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="true">
|
||||
<Setter TargetName="buttonImage" Property="Source" Value="resources/images/0.png" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
|
||||
<Style x:Key="RaisedButton" TargetType="Button">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<ImageBrush ImageSource="resources/images/facingDown.png"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<!--
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<ImageBrush ImageSource="resources/images/0.png"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
-->
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Foreground" Value="Red" />
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<ImageBrush ImageSource="resources/images/0.png"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Application.Resources>
|
||||
</Application>
|
25
info/Minesweeper2/MinesweeperGui/App.xaml.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace MinesweeperGui
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
Utility.Write("In OnStartup");
|
||||
base.OnStartup(e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
52
info/Minesweeper2/MinesweeperGui/MainWindow.xaml
Normal file
@ -0,0 +1,52 @@
|
||||
<Window x:Class="MinesweeperGui.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:MinesweeperGui"
|
||||
mc:Ignorable="d"
|
||||
Title="Minesweeper solver" Height="720" Width="1250">
|
||||
<Canvas x:Name="mainWindow" Background="#FFC7BFBF" SizeChanged="RedrawWindow">
|
||||
<Canvas MaxWidth="180" Margin="6,9,6,10" Width="180" Height="542" Canvas.Left="16" Canvas.Top="97">
|
||||
<Button Content="Beginner" Canvas.Left="27" Canvas.Top="140" Click="BeginnerClick"/>
|
||||
<Button Content="Intermediate" Canvas.Left="27" Canvas.Top="191" Click="IntermediateClick"/>
|
||||
<Button Content="Expert" Canvas.Left="27" Canvas.Top="244" Click="ExpertClick"/>
|
||||
<Button Content="Custom" Canvas.Left="27" Canvas.Top="400" Click="CustomClick"/>
|
||||
<Label Content="Width:" Height="32" Canvas.Left="10" Canvas.Top="301" Width="55" RenderTransformOrigin="0.178,0.47"/>
|
||||
<TextBox x:Name="CustomWidth" Height="22" Canvas.Left="70" Canvas.Top="302" Text="TextBox" TextWrapping="Wrap" Width="95"/>
|
||||
<Label Content="Height:" Height="32" Canvas.Left="10" Canvas.Top="329" Width="55" RenderTransformOrigin="0.178,0.47"/>
|
||||
<TextBox x:Name="CustomHeight" Height="22" Canvas.Left="70" Canvas.Top="334" Text="TextBox" TextWrapping="Wrap" Width="95" RenderTransformOrigin="0.491,0.502"/>
|
||||
<Label Content="Mines:" Height="32" Canvas.Left="10" Canvas.Top="359" Width="55" RenderTransformOrigin="0.178,0.47"/>
|
||||
<TextBox x:Name="CustomMines" Height="22" Canvas.Left="70" Canvas.Top="365" Text="TextBox" TextWrapping="Wrap" Width="95" RenderTransformOrigin="0.491,0.502"/>
|
||||
<CheckBox x:Name="useSeed" Content="Use seed" Height="18" Width="134" Canvas.Left="36" Canvas.Top="10"/>
|
||||
<TextBox x:Name="SeedTextBox" Height="22" Canvas.Left="27" Canvas.Top="33" TextWrapping="Wrap" Width="138"/>
|
||||
<CheckBox x:Name="showHInts" Content="Show hints" Height="18" Width="160" Canvas.Left="10" Canvas.Top="454" Checked="SetSolverDetails" Unchecked="SetSolverDetails"/>
|
||||
<CheckBox x:Name="autoPlay" Content="Auto play" Height="18" Width="160" Canvas.Left="10" Canvas.Top="477" Checked="SetSolverDetails" Unchecked="SetSolverDetails"/>
|
||||
<CheckBox x:Name="acceptGuesses" Content="Accept guesses" Height="18" Width="160" Canvas.Left="10" Canvas.Top="500" Checked="SetSolverDetails" Unchecked="SetSolverDetails"/>
|
||||
<RadioButton x:Name="safeStart" Content="Safe start" Height="16" Canvas.Left="44" Canvas.Top="66" Width="92" GroupName="startType" IsChecked="True"/>
|
||||
<RadioButton x:Name="zeroStart" Content="Zero start" Height="20" Canvas.Left="44" Canvas.Top="89" Width="92" GroupName="startType"/>
|
||||
<CheckBox x:Name="hardcore" Content="Hard core" Height="18" Width="110" Canvas.Left="43" Canvas.Top="114" IsChecked="True"/>
|
||||
</Canvas>
|
||||
<Canvas>
|
||||
<Label Content="Minesweeper" Grid.ColumnSpan="2" FontSize="30" FontWeight="Bold" Height="50" Margin="10,10,10,0" Canvas.Top="-9" Width="205" Canvas.Left="-9"/>
|
||||
<Border x:Name="MinesLeftHolder" BorderBrush="#7F000000" BorderThickness="4,4,0,0" Margin="10,58,754,0" Grid.Column="1" Width="104" Height="48" Canvas.Left="211" Canvas.Top="-42">
|
||||
<Border BorderThickness="0,0,4,4" BorderBrush="#7FFFFFFF">
|
||||
<Canvas x:Name="MinesLeft" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
|
||||
</Border>
|
||||
</Border>
|
||||
<Label Content="Solver" FontSize="30" FontWeight="Bold" Height="50" Canvas.Top="35" Width="104" RenderTransformOrigin="0.519,0.521" Canvas.Left="42"/>
|
||||
</Canvas>
|
||||
<Border x:Name="BoardHolder" BorderBrush="#7F000000" BorderThickness="4,4,0,0" Height="562" Width="960" Canvas.Left="220" Canvas.Top="70">
|
||||
<Border BorderThickness="0,0,4,4" BorderBrush="#7FFFFFFF">
|
||||
<Canvas x:Name="gameCanvas" Background="#FF58F037" MouseDown="CanvasMouseDown" MouseMove="MouseMoveOnBoard" MouseLeave="MouseLeftBoard" MouseEnter="MouseEnteredBoard" MouseWheel="MouseWheelOnBoard"/>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Border x:Name="MessageHolder" BorderThickness="2" BorderBrush="Black" MaxHeight="35" Height="35" Background="#FFC7BFBF" Canvas.Bottom="5" Canvas.Left="220" Width="1000">
|
||||
<Label x:Name="MessageLine" Content="Message line" FontWeight="Bold" FontSize="14" MouseDown="ClickMessageLine"/>
|
||||
</Border>
|
||||
<ScrollBar x:Name="horizontalScrollbar" Maximum="8000" ViewportSize="2000" Orientation="Horizontal" Height="20" Width="1019" Canvas.Left="221" Canvas.Bottom="50" Scroll="HorizontalScroll" SmallChange="1"/>
|
||||
<ScrollBar x:Name="verticalScrollbar" Maximum="8000" ViewportSize="2000" Orientation="Vertical" Height="500" Width="20" Canvas.Right="10" Canvas.Top="70" Scroll="VerticalScroll" SmallChange="1"/>
|
||||
|
||||
</Canvas>
|
||||
</Window>
|
955
info/Minesweeper2/MinesweeperGui/MainWindow.xaml.cs
Normal file
@ -0,0 +1,955 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using MinesweeperControl;
|
||||
using MinesweeperSolver;
|
||||
using static MinesweeperControl.GameDescription;
|
||||
using static MinesweeperControl.MinesweeperGame;
|
||||
|
||||
namespace MinesweeperGui {
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
|
||||
private readonly bool verbose = true; // determines whether diagnostic info gets written to the console
|
||||
|
||||
private readonly ImageSource Hidden;
|
||||
private readonly ImageSource[] MineValue = new ImageSource[9];
|
||||
private readonly ImageSource Flagged;
|
||||
private readonly ImageSource FlaggedWrong;
|
||||
private readonly ImageSource Mine;
|
||||
private readonly ImageSource MineWrong;
|
||||
|
||||
private readonly ImageSource[] LedValue = new ImageSource[10];
|
||||
|
||||
private int tileSize = 32;
|
||||
|
||||
private GameDescription gameDescription = GameDescription.EXPERT_SAFE;
|
||||
|
||||
// details about the solver
|
||||
private SolverInfo solverInfo;
|
||||
private SolverActionHeader solverActions = new SolverActionHeader();
|
||||
|
||||
// graphical components
|
||||
private Image hintImage; // overlay of hints from the solver
|
||||
private Image totalImage; // composite image of imageDrawings
|
||||
private DrawingGroup imageDrawings; // contains all the tiles
|
||||
private ImageDrawing[,] images; // all the tiles
|
||||
private readonly Image[] digits = new Image[8];
|
||||
|
||||
// tool tip elements
|
||||
private Popup gameToolTip = new Popup();
|
||||
private readonly TextBox popupText = new TextBox();
|
||||
|
||||
// details about the visible board
|
||||
private int topX = 0;
|
||||
private int topY = 0;
|
||||
private int boardWidth = 0;
|
||||
private int boardHeight = 0;
|
||||
private ActionResult[,] tiles;
|
||||
private MinesweeperGame game;
|
||||
private int minesLeftSize = 6;
|
||||
|
||||
private bool useSolver = false;
|
||||
private bool autoRunning = false;
|
||||
private bool useGuesses = false;
|
||||
private bool logicalLock = false;
|
||||
private int gameNumber = 1;
|
||||
private int autoPlayMinDelay = 250;
|
||||
private SolverAction jumpTo;
|
||||
|
||||
public object MinesweeperMain { get; private set; }
|
||||
|
||||
public MainWindow() {
|
||||
Utility.Write("At MainWindow constructor");
|
||||
InitializeComponent();
|
||||
|
||||
// load the images we need
|
||||
Hidden = Utility.BuildImageSource("facingDown.png", tileSize);
|
||||
Flagged = Utility.BuildImageSource("flagged.png", tileSize);
|
||||
FlaggedWrong = Utility.BuildImageSource("flaggedWrong.png", tileSize);
|
||||
Mine = Utility.BuildImageSource("mine.png", tileSize);
|
||||
MineWrong = Utility.BuildImageSource("exploded.png", tileSize);
|
||||
|
||||
for (int i = 0; i < 9; i++) {
|
||||
MineValue[i] = Utility.BuildImageSource(i + ".png", tileSize);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
LedValue[i] = Utility.BuildImageSource("led" + i + ".png", 24, 40);
|
||||
}
|
||||
|
||||
CustomWidth.Text = "100";
|
||||
CustomHeight.Text = "100";
|
||||
CustomMines.Text = "2000";
|
||||
|
||||
for (int i = 0; i < digits.Length; i++) {
|
||||
digits[i] = new Image() {
|
||||
Source = LedValue[0]
|
||||
};
|
||||
Canvas.SetTop(digits[i], 0);
|
||||
Canvas.SetLeft(digits[i], i * LedValue[0].Width);
|
||||
MinesLeft.Children.Add(digits[i]);
|
||||
}
|
||||
|
||||
// complete creating the popup
|
||||
popupText.Text = "";
|
||||
popupText.Background = new SolidColorBrush( Color.FromArgb(128, 227, 227, 227));
|
||||
popupText.Foreground = Brushes.Black;
|
||||
popupText.FontSize = 20;
|
||||
popupText.FontWeight = FontWeights.Bold;
|
||||
|
||||
gameToolTip.Child = popupText;
|
||||
|
||||
gameToolTip.AllowsTransparency = true;
|
||||
gameToolTip.Placement = PlacementMode.Relative;
|
||||
gameToolTip.PlacementTarget = gameCanvas;
|
||||
gameToolTip.IsOpen = true;
|
||||
|
||||
gameCanvas.Children.Add(gameToolTip);
|
||||
|
||||
// start up a game
|
||||
InitializeTiles1();
|
||||
|
||||
Utility.Write("Graphics rendering tier = " + (RenderCapability.Tier >> 16));
|
||||
|
||||
SolverMain.Initialise();
|
||||
}
|
||||
|
||||
private void AutoPlay() {
|
||||
|
||||
Thread autoplay = new Thread(AutoPlayRunner);
|
||||
autoplay.Start();
|
||||
|
||||
}
|
||||
|
||||
|
||||
// this method runs on a different thread. gameNumber is changed by clicking chosing a new game from the UI thread.
|
||||
private void AutoPlayRunner() {
|
||||
|
||||
Dispatcher.Invoke(() => gameCanvas.Cursor = Cursors.Wait);
|
||||
logicalLock = true;
|
||||
|
||||
int runningGameNumber = gameNumber; // remember the game number we are solving
|
||||
|
||||
//Utility.Write("AutoRunning thread starting");
|
||||
|
||||
solverActions = SolverMain.FindActions(solverInfo); // pass the solver info into the solver and see what it comes up with
|
||||
Dispatcher.Invoke(() => RenderHints());
|
||||
|
||||
while (IsAutoPlayValid() && gameNumber == runningGameNumber) {
|
||||
|
||||
long start = DateTime.Now.Ticks;
|
||||
|
||||
GameResult result = game.ProcessActions(solverActions.solverActions);
|
||||
|
||||
//Utility.Write("Game controller returned " + result.actionResults.Count + " results from this action");
|
||||
|
||||
long end1 = DateTime.Now.Ticks;
|
||||
// do the rendering while we are stil playing the same game
|
||||
if (gameNumber == runningGameNumber) {
|
||||
gameCanvas.Dispatcher.Invoke(() => RenderResults(result));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
//Utility.Write("After RenderResults took " + (DateTime.Now.Ticks - start) + " ticks");
|
||||
|
||||
solverInfo.AddInformation(result); // let the solver know what has happened
|
||||
|
||||
// nothing more to do if we have won or lost
|
||||
if (result.status == GameStatus.Lost || result.status == GameStatus.Won) {
|
||||
Dispatcher.Invoke(() => DisplayFinalOutcome(result));
|
||||
break;
|
||||
}
|
||||
|
||||
solverActions = SolverMain.FindActions(solverInfo); // pass the solver info into the solver and see what it comes up with
|
||||
|
||||
//foreach (SolverAction action in solverActions) {
|
||||
// Utility.Write("Playing " + action.AsText() + " probability " + action.safeProbability);
|
||||
//}
|
||||
|
||||
//long end2 = DateTime.Now.Ticks;
|
||||
// do the rendering while we are stil playing the same game
|
||||
if (gameNumber == runningGameNumber) {
|
||||
Dispatcher.Invoke(() => RenderHints());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
//Utility.Write("After RenderHints took " + (DateTime.Now.Ticks - start) + " ticks");
|
||||
Utility.Write("Tiles remaining " + solverInfo.GetTilesLeft());
|
||||
|
||||
long end = DateTime.Now.Ticks;
|
||||
|
||||
int milliseconds = (int) ((end - start) / 10000);
|
||||
|
||||
int wait = Math.Max(0, autoPlayMinDelay - milliseconds);
|
||||
|
||||
//Utility.Write("Autoplay processing took " + milliseconds + " milliseconds (" + (end - start) + " ticks)");
|
||||
//Console.WriteLine("Autoplay processing took " + milliseconds + " milliseconds");
|
||||
|
||||
if (!IsAutoPlayValid()) {
|
||||
break;
|
||||
}
|
||||
|
||||
Thread.Sleep(wait); // wait until all the time is used up
|
||||
|
||||
}
|
||||
|
||||
logicalLock = false;
|
||||
Dispatcher.Invoke(() => gameCanvas.Cursor = Cursors.Arrow);
|
||||
|
||||
//Utility.Write("AutoRunning thread ending");
|
||||
|
||||
}
|
||||
|
||||
// create the non-graphical parts of the game
|
||||
private void InitializeTiles1() {
|
||||
|
||||
gameNumber++;
|
||||
logicalLock = false;
|
||||
|
||||
minesLeftSize = Math.Max(3, (int) Math.Log10(gameDescription.mines) + 1);
|
||||
|
||||
MinesLeftHolder.Width = 24 * minesLeftSize + 8;
|
||||
for (int i= 0; i < digits.Length; i++) {
|
||||
if (i < minesLeftSize) {
|
||||
digits[i].Visibility = Visibility.Visible;
|
||||
} else {
|
||||
digits[i].Visibility = Visibility.Hidden;
|
||||
}
|
||||
}
|
||||
|
||||
int seed = 0;
|
||||
if (useSeed.IsChecked == true) {
|
||||
try {
|
||||
seed = int.Parse(SeedTextBox.Text);
|
||||
} catch (Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
//game = new MinesweeperGame(gameDescription, seed);
|
||||
game = new MinesweeperGame(gameDescription, seed, (hardcore.IsChecked == true));
|
||||
solverInfo = new SolverInfo(gameDescription, verbose);
|
||||
|
||||
RenderMinesLeft();
|
||||
|
||||
long start = DateTime.Now.Ticks;
|
||||
|
||||
tiles = new ActionResult[gameDescription.width, gameDescription.height];
|
||||
solverActions = new SolverActionHeader(); // remove any hints from the previous game
|
||||
|
||||
Utility.Write("Ticks to build screen " + (DateTime.Now.Ticks - start));
|
||||
|
||||
}
|
||||
|
||||
// build the graphical part of the board
|
||||
private void BuildBoard() {
|
||||
|
||||
//long start = DateTime.Now.Ticks;
|
||||
|
||||
images = new ImageDrawing[boardWidth, boardHeight];
|
||||
|
||||
// remove existing tiles from the canvas
|
||||
gameCanvas.Children.Clear(); // clear game board
|
||||
|
||||
imageDrawings = new DrawingGroup();
|
||||
|
||||
for (int y = 0; y < boardHeight; y++) {
|
||||
for (int x = 0; x < boardWidth; x++) {
|
||||
|
||||
ActionResult tile = tiles[topX + x, topY + y];
|
||||
ImageSource imageSource;
|
||||
if (tile == null) {
|
||||
imageSource = Hidden;
|
||||
|
||||
} else if (tile.resultType == ResultType.Cleared) {
|
||||
imageSource = MineValue[tile.value];
|
||||
|
||||
} else if (tile.resultType == ResultType.Flagged) {
|
||||
imageSource = Flagged;
|
||||
|
||||
} else if (tile.resultType == ResultType.Hidden) {
|
||||
imageSource = Hidden;
|
||||
|
||||
} else if (tile.resultType == ResultType.Exploded) {
|
||||
imageSource = MineWrong;
|
||||
|
||||
} else if (tile.resultType == ResultType.FlaggedWrong) {
|
||||
imageSource = FlaggedWrong;
|
||||
|
||||
} else if (tile.resultType == ResultType.Mine) {
|
||||
imageSource = Mine;
|
||||
} else {
|
||||
imageSource = Hidden;
|
||||
}
|
||||
|
||||
|
||||
ImageDrawing image = new ImageDrawing() {
|
||||
ImageSource = imageSource,
|
||||
Rect = new Rect(x * tileSize, y * tileSize, tileSize, tileSize)
|
||||
};
|
||||
|
||||
imageDrawings.Children.Add(image);
|
||||
|
||||
images[x, y] = image;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
totalImage = new Image() {
|
||||
Source = new DrawingImage(imageDrawings)
|
||||
};
|
||||
|
||||
Canvas.SetLeft(totalImage, 0);
|
||||
Canvas.SetTop(totalImage, 0);
|
||||
gameCanvas.Children.Add(totalImage);
|
||||
|
||||
// create an empty hints image which we'll use later
|
||||
hintImage = new Image();
|
||||
|
||||
Canvas.SetLeft(hintImage, 0);
|
||||
Canvas.SetTop(hintImage, 0);
|
||||
gameCanvas.Children.Add(hintImage);
|
||||
|
||||
//Utility.Write("Ticks to build screen " + (DateTime.Now.Ticks - start));
|
||||
|
||||
}
|
||||
|
||||
private void DoAction(int x, int y, ActionType action) {
|
||||
|
||||
//int index = gameDescription.width * y + x;
|
||||
|
||||
GameResult result = game.ProcessActions(new GameAction[] { new GameAction(x, y, action) });
|
||||
|
||||
Utility.Write("Game controller returned " + result.actionResults.Count + " results from this action");
|
||||
|
||||
RenderResults(result); // draw the new board
|
||||
|
||||
solverInfo.AddInformation(result); // let the solver know what has happened
|
||||
|
||||
if (result.status == GameStatus.Lost) {
|
||||
MessageLine.Content = "The game is lost!";
|
||||
} else if (result.status == GameStatus.Won) {
|
||||
if (game.GetDeaths() != 0) {
|
||||
MessageLine.Content = "The game is won with " + game.GetDeaths() + " deaths";
|
||||
} else {
|
||||
MessageLine.Content = "The game is won";
|
||||
}
|
||||
|
||||
} else {
|
||||
// if we are showing hints or auto playing then get the hints
|
||||
if (useSolver || autoRunning) {
|
||||
AutoPlay(); // let the solver playout the moves
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void DisplayFinalOutcome(GameResult result) {
|
||||
|
||||
if (result.status == GameStatus.Lost) {
|
||||
MessageLine.Content = "The game is lost!";
|
||||
} else if (result.status == GameStatus.Won) {
|
||||
if (this.game.GetDeaths() != 0) {
|
||||
MessageLine.Content = "The game is won with " + game.GetDeaths() + " deaths";
|
||||
} else {
|
||||
MessageLine.Content = "The game is won";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private bool IsAutoPlayValid() {
|
||||
|
||||
if (!autoRunning) {
|
||||
//Utility.Write("Autoplay not valid - AutoPlay is not checked");
|
||||
return false;
|
||||
}
|
||||
|
||||
// if we have no actions then we can't autoplay
|
||||
if (solverActions.solverActions.Count == 0) {
|
||||
//Utility.Write("Autoplay not valid - no actions found by the solver");
|
||||
return false;
|
||||
}
|
||||
|
||||
// if we are accepting guesses then everything goes
|
||||
if (useGuesses) {
|
||||
//Utility.Write("Autoplay valid - accepting guesses");
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((solverActions.solverActions[0].safeProbability > 0 && solverActions.solverActions[0].safeProbability < 1)) {
|
||||
//Utility.Write("Autoplay not valid - no certain actions found by the solver and we aren't accepting guesses");
|
||||
return false;
|
||||
}
|
||||
|
||||
//Utility.Write("Autoplay valid - certain actions found by the solver");
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// draw the outputs from the Game onto the visible board
|
||||
private void RenderResults(GameResult result) {
|
||||
|
||||
//long start = DateTime.Now.Ticks;
|
||||
|
||||
SeedTextBox.Text = game.seed.ToString();
|
||||
|
||||
hintImage.Visibility = Visibility.Hidden; // hide the previous hints
|
||||
|
||||
foreach (ActionResult ar in result.actionResults) {
|
||||
|
||||
// remember what happen here last
|
||||
tiles[ar.x, ar.y] = ar;
|
||||
|
||||
int boardX = ar.x - topX;
|
||||
int boardY = ar.y - topY;
|
||||
|
||||
// if the action is taking place in the part of the board we are looking at
|
||||
if (boardX >= 0 && boardX < boardWidth && boardY >= 0 && boardY < boardHeight) {
|
||||
|
||||
ImageDrawing image = images[boardX, boardY];
|
||||
|
||||
if (ar.resultType == ResultType.Cleared) {
|
||||
image.ImageSource = MineValue[ar.value];
|
||||
|
||||
} else if (ar.resultType == ResultType.Flagged) {
|
||||
image.ImageSource = Flagged;
|
||||
|
||||
} else if (ar.resultType == ResultType.Hidden) {
|
||||
image.ImageSource = Hidden;
|
||||
|
||||
} else if (ar.resultType == ResultType.Exploded) {
|
||||
image.ImageSource = MineWrong;
|
||||
|
||||
} else if (ar.resultType == ResultType.FlaggedWrong) {
|
||||
image.ImageSource = FlaggedWrong;
|
||||
|
||||
} else if (ar.resultType == ResultType.Mine) {
|
||||
image.ImageSource = Mine;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RenderMinesLeft();
|
||||
|
||||
//Utility.Write("Ticks to render results " + (DateTime.Now.Ticks - start));
|
||||
|
||||
}
|
||||
|
||||
// draw the outputs from the Solver onto the visible board
|
||||
private void RenderHints() {
|
||||
|
||||
//long start = DateTime.Now.Ticks;
|
||||
|
||||
DrawingGroup hintDrawings = new DrawingGroup();
|
||||
|
||||
int offSetLeft = tileSize * (boardWidth + 1);
|
||||
int offSetTop = tileSize * (boardHeight + 1);
|
||||
|
||||
jumpTo = null;
|
||||
if (this.game.GetGameStatus() == GameStatus.InPlay) {
|
||||
if (solverActions.solverActions.Count == 0) {
|
||||
MessageLine.Content = "The solver has no suggestions!";
|
||||
} else if (solverActions.solverActions.Count > 1) {
|
||||
MessageLine.Content = "The solver has found " + solverActions.solverActions.Count + " moves.";
|
||||
} else if (solverActions.solverActions[0].safeProbability > 0 && solverActions.solverActions[0].safeProbability < 1) {
|
||||
MessageLine.Content = "The solver suggests guessing " + solverActions.solverActions[0].AsText();
|
||||
jumpTo = solverActions.solverActions[0];
|
||||
} else {
|
||||
MessageLine.Content = "The solver has found 1 move.";
|
||||
}
|
||||
}
|
||||
|
||||
foreach (SolverAction ar in solverActions.solverActions) {
|
||||
|
||||
int boardX = ar.x - topX;
|
||||
int boardY = ar.y - topY;
|
||||
|
||||
// if the action is taking place in the part of the board we are looking at
|
||||
if (boardX >= 0 && boardX < boardWidth && boardY >= 0 && boardY < boardHeight) {
|
||||
|
||||
int x = boardX * tileSize;
|
||||
int y = boardY * tileSize;
|
||||
|
||||
if (x < offSetLeft) {
|
||||
offSetLeft = x;
|
||||
}
|
||||
if (y < offSetTop) {
|
||||
offSetTop = y;
|
||||
}
|
||||
|
||||
Color fill;
|
||||
if (ar.action == ActionType.Clear) {
|
||||
if (ar.safeProbability == 1) {
|
||||
fill = Color.FromArgb(128, 0, 255, 0); // green if safe
|
||||
} else if (ar.isDead) {
|
||||
fill = Color.FromArgb(128, 0, 0, 0); // black if excluded
|
||||
} else {
|
||||
fill = Color.FromArgb(128, 255, 165, 0); // orange if not safe
|
||||
}
|
||||
|
||||
} else if (ar.action == ActionType.Flag) {
|
||||
fill = Color.FromArgb(128, 255, 0, 0); // red if a mine
|
||||
}
|
||||
|
||||
GeometryDrawing square =
|
||||
new GeometryDrawing(
|
||||
new SolidColorBrush(fill),
|
||||
new Pen(Brushes.Black, 0),
|
||||
new RectangleGeometry(new Rect(x, y, tileSize, tileSize))
|
||||
);
|
||||
|
||||
hintDrawings.Children.Add(square);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// render dead tiles
|
||||
foreach (SolverAction ar in solverActions.deadActions) {
|
||||
|
||||
int boardX = ar.x - topX;
|
||||
int boardY = ar.y - topY;
|
||||
|
||||
// if the action is taking place in the part of the board we are looking at
|
||||
if (boardX >= 0 && boardX < boardWidth && boardY >= 0 && boardY < boardHeight) {
|
||||
|
||||
int x = boardX * tileSize;
|
||||
int y = boardY * tileSize;
|
||||
|
||||
if (x < offSetLeft) {
|
||||
offSetLeft = x;
|
||||
}
|
||||
if (y < offSetTop) {
|
||||
offSetTop = y;
|
||||
}
|
||||
|
||||
Color fill = Color.FromArgb(128, 0, 0, 0); // black if dead
|
||||
|
||||
GeometryDrawing square =
|
||||
new GeometryDrawing(
|
||||
new SolidColorBrush(fill),
|
||||
new Pen(Brushes.Black, 0),
|
||||
new RectangleGeometry(new Rect(x, y, tileSize, tileSize))
|
||||
);
|
||||
|
||||
hintDrawings.Children.Add(square);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// replace the old composite hints image with the new
|
||||
hintImage.Source = new DrawingImage(hintDrawings);
|
||||
|
||||
// and place it in the write position
|
||||
Canvas.SetLeft(hintImage, offSetLeft);
|
||||
Canvas.SetTop(hintImage, offSetTop);
|
||||
hintImage.Visibility = Visibility.Visible;
|
||||
|
||||
//Utility.Write("Ticks to render hints " + (DateTime.Now.Ticks - start));
|
||||
}
|
||||
|
||||
private void RebuildBoard(int topX, int topY, int boardWidth, int boardHeight) {
|
||||
|
||||
this.topX = topX;
|
||||
this.topY = topY;
|
||||
this.boardWidth = boardWidth;
|
||||
this.boardHeight = boardHeight;
|
||||
|
||||
// make sure the top position allows enough space to fill the board
|
||||
if (this.topX + this.boardWidth > gameDescription.width) {
|
||||
this.topX = gameDescription.width - this.boardWidth;
|
||||
}
|
||||
|
||||
if (this.topY + this.boardHeight > gameDescription.height) {
|
||||
this.topY = gameDescription.height - this.boardHeight;
|
||||
}
|
||||
|
||||
|
||||
if (boardWidth > 0 && boardHeight > 0) {
|
||||
BuildBoard();
|
||||
RenderHints();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void RenderMinesLeft() {
|
||||
|
||||
int i = minesLeftSize - 1;
|
||||
int mines = game.GetMinesLeft();
|
||||
|
||||
while (i >= 0) {
|
||||
|
||||
int d0 = mines % 10;
|
||||
mines = (mines - d0) / 10;
|
||||
|
||||
digits[i].Source = LedValue[d0];
|
||||
|
||||
i--;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void CanvasMouseDown(object sender, MouseButtonEventArgs e) {
|
||||
|
||||
|
||||
if (logicalLock) { // this means the solver is playing the game and the user input is ignored
|
||||
return;
|
||||
}
|
||||
|
||||
int x = (int) e.GetPosition(gameCanvas).X;
|
||||
int y = (int) e.GetPosition(gameCanvas).Y;
|
||||
|
||||
//Utility.Write("Mouse clicked at X=" + x + ", Y=" + y);
|
||||
|
||||
int col = x / tileSize;
|
||||
int row = y / tileSize;
|
||||
|
||||
if (col < 0 || col > boardWidth || row < 0 || row > boardHeight) {
|
||||
Utility.Write("Clicked outside of game boundary col=" + col + ", row=" + row);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//Utility.Write("col=" + col + ", row=" + row);
|
||||
ActionType actionType;
|
||||
|
||||
ActionResult tile = tiles[col + topX, row + topY];
|
||||
|
||||
if (e.ChangedButton == MouseButton.Left) {
|
||||
if (tile == null || tile.resultType == ResultType.Hidden) {
|
||||
actionType = ActionType.Clear;
|
||||
} else if (tile.resultType == ResultType.Cleared) {
|
||||
actionType = ActionType.Chord;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (e.ChangedButton == MouseButton.Right) {
|
||||
actionType = ActionType.Flag;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
DoAction(col + topX, row + topY, actionType);
|
||||
|
||||
}
|
||||
|
||||
private void NewGame() {
|
||||
|
||||
InitializeTiles1();
|
||||
|
||||
// reposition the display to the top left
|
||||
//topX = 0;
|
||||
//topY = 0;
|
||||
|
||||
// move the scroll bars back to the start
|
||||
horizontalScrollbar.Value = 0;
|
||||
verticalScrollbar.Value = 0;
|
||||
|
||||
// work out if scroll bars are need and how big the thumb is
|
||||
//boardWidth = DoWidth(mainWindow.ActualWidth);
|
||||
//boardHeight = DoHeight(mainWindow.ActualHeight);
|
||||
|
||||
RebuildBoard(0, 0, DoWidth(mainWindow.ActualWidth), DoHeight(mainWindow.ActualHeight));
|
||||
|
||||
}
|
||||
|
||||
private void BeginnerClick(object sender, RoutedEventArgs e) {
|
||||
|
||||
if (zeroStart.IsChecked == true) {
|
||||
gameDescription = GameDescription.BEGINNER_ZERO;
|
||||
} else {
|
||||
gameDescription = GameDescription.BEGINNER_SAFE;
|
||||
}
|
||||
|
||||
NewGame();
|
||||
|
||||
}
|
||||
|
||||
private void IntermediateClick(object sender, RoutedEventArgs e) {
|
||||
|
||||
if (zeroStart.IsChecked == true) {
|
||||
gameDescription = GameDescription.INTERMEDIATE_ZERO;
|
||||
} else {
|
||||
gameDescription = GameDescription.INTERMEDIATE_SAFE;
|
||||
}
|
||||
|
||||
NewGame();
|
||||
|
||||
}
|
||||
|
||||
private void ExpertClick(object sender, RoutedEventArgs e) {
|
||||
|
||||
|
||||
if (zeroStart.IsChecked == true) {
|
||||
gameDescription = GameDescription.EXPERT_ZERO;
|
||||
} else {
|
||||
gameDescription = GameDescription.EXPERT_SAFE;
|
||||
}
|
||||
|
||||
NewGame();
|
||||
|
||||
}
|
||||
|
||||
private void CustomClick(object sender, RoutedEventArgs e) {
|
||||
|
||||
int gameWidth;
|
||||
int gameHeight;
|
||||
int gameMines;
|
||||
|
||||
try {
|
||||
gameWidth = int.Parse(CustomWidth.Text);
|
||||
} catch (System.Exception) {
|
||||
gameWidth = 30;
|
||||
CustomWidth.Text = gameWidth.ToString();
|
||||
}
|
||||
|
||||
try {
|
||||
gameHeight = int.Parse(CustomHeight.Text);
|
||||
} catch (System.Exception) {
|
||||
gameHeight = 16;
|
||||
CustomHeight.Text = gameHeight.ToString();
|
||||
}
|
||||
|
||||
try {
|
||||
gameMines = int.Parse(CustomMines.Text);
|
||||
} catch (System.Exception) {
|
||||
gameMines = gameWidth * gameHeight / 5;
|
||||
CustomMines.Text = gameMines.ToString();
|
||||
}
|
||||
|
||||
if (zeroStart.IsChecked == true) {
|
||||
gameDescription = new GameDescription(gameWidth, gameHeight, gameMines, GameType.Zero);
|
||||
} else {
|
||||
gameDescription = new GameDescription(gameWidth, gameHeight, gameMines, GameType.Safe);
|
||||
}
|
||||
|
||||
NewGame();
|
||||
|
||||
}
|
||||
|
||||
// redraws the controls based on the new width and returns the number of columns we are now able to show
|
||||
private int DoWidth(double width) {
|
||||
|
||||
MessageHolder.Width = Math.Max(0, width - 260);
|
||||
|
||||
double boardMax = gameDescription.width * tileSize + 8;
|
||||
|
||||
double actualWidth = Math.Max(8, Math.Min(boardMax, width - 260));
|
||||
|
||||
int showTilesWidth = (int)Math.Floor((actualWidth - 8) / tileSize);
|
||||
|
||||
double boardWidthPixels = showTilesWidth * tileSize + 8; // board is in Whole tiles
|
||||
|
||||
BoardHolder.Width = boardWidthPixels;
|
||||
|
||||
if (showTilesWidth == gameDescription.width) {
|
||||
horizontalScrollbar.Visibility = Visibility.Hidden;
|
||||
} else {
|
||||
horizontalScrollbar.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
horizontalScrollbar.Width = boardWidthPixels;
|
||||
horizontalScrollbar.Maximum = (gameDescription.width - showTilesWidth);
|
||||
horizontalScrollbar.ViewportSize = showTilesWidth;
|
||||
horizontalScrollbar.LargeChange = showTilesWidth; // scroll bar large scroll is a whole screen
|
||||
|
||||
return showTilesWidth;
|
||||
|
||||
}
|
||||
|
||||
// redraws the controls based on the new height and returns the number of columns we are now able to show
|
||||
private int DoHeight(double height) {
|
||||
|
||||
double boardMax = gameDescription.height * tileSize + 8;
|
||||
|
||||
double actualHeight = Math.Max(8, Math.Min(boardMax, height - 140));
|
||||
|
||||
int showTilesHeight = (int) Math.Floor((actualHeight - 8) / tileSize);
|
||||
|
||||
double boardHeightPixels = showTilesHeight * tileSize + 8; // board is in Whole tiles
|
||||
|
||||
BoardHolder.Height = boardHeightPixels;
|
||||
|
||||
if (showTilesHeight == gameDescription.height) {
|
||||
verticalScrollbar.Visibility = Visibility.Hidden;
|
||||
} else {
|
||||
verticalScrollbar.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
verticalScrollbar.Height = boardHeightPixels;
|
||||
verticalScrollbar.Maximum = (gameDescription.height - showTilesHeight);
|
||||
verticalScrollbar.ViewportSize = showTilesHeight;
|
||||
verticalScrollbar.LargeChange = showTilesHeight; // scroll bar large scroll is a whole screen
|
||||
|
||||
return showTilesHeight;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void RedrawWindow(object sender, SizeChangedEventArgs e) {
|
||||
|
||||
//Utility.Write("New width:" + e.NewSize.Width + " height:" + e.NewSize.Height);
|
||||
|
||||
int newBoardWidth = boardWidth;
|
||||
int newBoardHeight = boardHeight;
|
||||
|
||||
if (e.WidthChanged == true) {
|
||||
newBoardWidth = DoWidth(e.NewSize.Width);
|
||||
}
|
||||
|
||||
if (e.HeightChanged == true) {
|
||||
newBoardHeight = DoHeight(e.NewSize.Height);
|
||||
}
|
||||
|
||||
if (newBoardWidth != this.boardWidth || newBoardHeight != this.boardHeight) {
|
||||
RebuildBoard(topX, topY, newBoardWidth, newBoardHeight);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void VerticalScroll(object sender, System.Windows.Controls.Primitives.ScrollEventArgs e) {
|
||||
|
||||
//Utility.Write("Vertical scroll : " + e.NewValue);
|
||||
|
||||
int newTopY = (int) Math.Floor(e.NewValue + 0.5d);
|
||||
|
||||
if (newTopY != topY) {
|
||||
RebuildBoard(topX, newTopY, boardWidth, boardHeight);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void HorizontalScroll(object sender, System.Windows.Controls.Primitives.ScrollEventArgs e) {
|
||||
|
||||
//Utility.Write("Horizontal scroll : " + e.NewValue);
|
||||
|
||||
int newTopX = (int)Math.Floor(e.NewValue + 0.5d);
|
||||
|
||||
if (newTopX != topX) {
|
||||
RebuildBoard(newTopX, topY, boardWidth, boardHeight);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void MouseMoveOnBoard(object sender, MouseEventArgs e) {
|
||||
|
||||
int x = (int)e.GetPosition(gameCanvas).X;
|
||||
int y = (int)e.GetPosition(gameCanvas).Y;
|
||||
|
||||
int col = x / tileSize;
|
||||
int row = y / tileSize;
|
||||
|
||||
if (col < 0 || col > boardWidth || row < 0 || row > boardHeight) {
|
||||
Utility.Write("Mouse outside of game boundary col=" + col + ", row=" + row);
|
||||
return;
|
||||
}
|
||||
|
||||
int realCol = col + topX;
|
||||
int realRow = row + topY;
|
||||
|
||||
double prob = solverInfo.GetProbability(realCol, realRow);
|
||||
string probText;
|
||||
if (prob == -1) {
|
||||
probText = "";
|
||||
} else if (prob == 0) {
|
||||
probText = "Mine";
|
||||
} else if (prob == 1) {
|
||||
probText = "Clear";
|
||||
} else {
|
||||
//probText = String.Format("0.00", prob) + " Safe";
|
||||
probText = (prob * 100).ToString("N2") + "% Safe";
|
||||
}
|
||||
|
||||
popupText.Text = "(" + realCol + "," + realRow + ") " + probText;
|
||||
|
||||
gameToolTip.HorizontalOffset = x;
|
||||
gameToolTip.VerticalOffset = y + 10;
|
||||
|
||||
}
|
||||
|
||||
private void MouseLeftBoard(object sender, MouseEventArgs e) {
|
||||
|
||||
gameToolTip.IsOpen = false;
|
||||
|
||||
}
|
||||
|
||||
private void MouseEnteredBoard(object sender, MouseEventArgs e) {
|
||||
|
||||
gameToolTip.IsOpen = true;
|
||||
|
||||
}
|
||||
|
||||
private void ClickMessageLine(object sender, MouseButtonEventArgs e) {
|
||||
|
||||
if (jumpTo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// set the scroll bars
|
||||
verticalScrollbar.Value = jumpTo.y - this.boardHeight / 2;
|
||||
horizontalScrollbar.Value = jumpTo.x - this.boardWidth / 2;
|
||||
|
||||
// now see where the scrollbars are
|
||||
int newTopY = (int)Math.Floor(verticalScrollbar.Value + 0.5d);
|
||||
int newTopX = (int)Math.Floor(horizontalScrollbar.Value + 0.5d);
|
||||
|
||||
// redraw board if we need to
|
||||
if (newTopY != topY || newTopX != topX) {
|
||||
RebuildBoard(newTopX, newTopY, boardWidth, boardHeight);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void MouseWheelOnBoard(object sender, MouseWheelEventArgs e) {
|
||||
|
||||
// if shift is pressed scroll left and right
|
||||
if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) {
|
||||
horizontalScrollbar.Value -= e.Delta / 40;
|
||||
|
||||
int newTopX = (int)Math.Floor(horizontalScrollbar.Value + 0.5d);
|
||||
|
||||
if (newTopX != topX) {
|
||||
RebuildBoard(newTopX, topY, boardWidth, boardHeight);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control) {
|
||||
return;
|
||||
}
|
||||
|
||||
verticalScrollbar.Value -= e.Delta / 40;
|
||||
|
||||
int newTopY = (int)Math.Floor(verticalScrollbar.Value + 0.5d);
|
||||
|
||||
if (newTopY != topY) {
|
||||
RebuildBoard(topX, newTopY, boardWidth, boardHeight);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void SetSolverDetails(object sender, RoutedEventArgs e) {
|
||||
|
||||
useSolver = (showHInts.IsChecked == true);
|
||||
autoRunning = (autoPlay.IsChecked == true);
|
||||
useGuesses = (acceptGuesses.IsChecked == true);
|
||||
|
||||
//Console.WriteLine(useSolver + " " + autoRunning + " " + useGuesses);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
93
info/Minesweeper2/MinesweeperGui/MinesweeperGui.csproj
Normal file
@ -0,0 +1,93 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<StartupObject>MinesweeperGui.App</StartupObject>
|
||||
<ApplicationIcon />
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="resources\images\0.png" />
|
||||
<None Remove="resources\images\1.png" />
|
||||
<None Remove="resources\images\2.png" />
|
||||
<None Remove="resources\images\3.png" />
|
||||
<None Remove="resources\images\4.png" />
|
||||
<None Remove="resources\images\5.png" />
|
||||
<None Remove="resources\images\6.png" />
|
||||
<None Remove="resources\images\7.png" />
|
||||
<None Remove="resources\images\8.png" />
|
||||
<None Remove="resources\images\bombTransparent.png" />
|
||||
<None Remove="resources\images\exploded.png" />
|
||||
<None Remove="resources\images\facingDown.png" />
|
||||
<None Remove="resources\images\flagged.png" />
|
||||
<None Remove="resources\images\flaggedWrong.png" />
|
||||
<None Remove="resources\images\led0.png" />
|
||||
<None Remove="resources\images\led1.png" />
|
||||
<None Remove="resources\images\led2.png" />
|
||||
<None Remove="resources\images\led3.png" />
|
||||
<None Remove="resources\images\led4.png" />
|
||||
<None Remove="resources\images\led5.png" />
|
||||
<None Remove="resources\images\led6.png" />
|
||||
<None Remove="resources\images\led7.png" />
|
||||
<None Remove="resources\images\led8.png" />
|
||||
<None Remove="resources\images\led9.png" />
|
||||
<None Remove="resources\images\led_blank.png" />
|
||||
<None Remove="resources\images\mine.png" />
|
||||
<None Remove="resources\images\raised.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="resources\images\0.png" />
|
||||
<Resource Include="resources\images\1.png" />
|
||||
<Resource Include="resources\images\2.png" />
|
||||
<Resource Include="resources\images\3.png" />
|
||||
<Resource Include="resources\images\4.png" />
|
||||
<Resource Include="resources\images\5.png" />
|
||||
<Resource Include="resources\images\6.png" />
|
||||
<Resource Include="resources\images\7.png" />
|
||||
<Resource Include="resources\images\8.png" />
|
||||
<Resource Include="resources\images\bombTransparent.png" />
|
||||
<Resource Include="resources\images\exploded.png" />
|
||||
<Resource Include="resources\images\facingDown.png" />
|
||||
<Resource Include="resources\images\flagged.png" />
|
||||
<Resource Include="resources\images\flaggedWrong.png" />
|
||||
<Resource Include="resources\images\led0.png" />
|
||||
<Resource Include="resources\images\led1.png" />
|
||||
<Resource Include="resources\images\led2.png" />
|
||||
<Resource Include="resources\images\led3.png" />
|
||||
<Resource Include="resources\images\led4.png" />
|
||||
<Resource Include="resources\images\led5.png" />
|
||||
<Resource Include="resources\images\led6.png" />
|
||||
<Resource Include="resources\images\led7.png" />
|
||||
<Resource Include="resources\images\led8.png" />
|
||||
<Resource Include="resources\images\led9.png" />
|
||||
<Resource Include="resources\images\led_blank.png" />
|
||||
<Resource Include="resources\images\mine.png" />
|
||||
<Resource Include="resources\images\raised.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="App.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="MainWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\MinesweeperGame\MinesweeperGame.projitems" Label="Shared" />
|
||||
|
||||
<Import Project="..\MinesweeperSolver\MinesweeperSolver.projitems" Label="Shared" />
|
||||
|
||||
</Project>
|
49
info/Minesweeper2/MinesweeperGui/Utility.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace MinesweeperGui
|
||||
{
|
||||
/*
|
||||
* This class holds some utility functions
|
||||
*/
|
||||
public static class Utility {
|
||||
|
||||
public static ImageSource BuildImageSource(string filename, int tileSize) {
|
||||
return BuildImageSource(filename, tileSize, tileSize);
|
||||
}
|
||||
|
||||
public static ImageSource BuildImageSource(string filename, int tileWidth, int tileHeight) {
|
||||
|
||||
Uri uri = new Uri("pack://application:,,,/resources/images/" + filename);
|
||||
|
||||
// Create source.
|
||||
BitmapImage bi = new BitmapImage();
|
||||
// BitmapImage.UriSource must be in a BeginInit/EndInit block.
|
||||
bi.BeginInit();
|
||||
bi.UriSource = uri;
|
||||
bi.DecodePixelHeight = tileHeight;
|
||||
bi.DecodePixelWidth = tileWidth;
|
||||
bi.EndInit();
|
||||
|
||||
bi.Freeze();
|
||||
|
||||
return bi;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static void Write(string text) {
|
||||
//System.Diagnostics.Debug.Print(text);
|
||||
Console.WriteLine(text);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
BIN
info/Minesweeper2/MinesweeperGui/resources/images/0.png
Normal file
After Width: | Height: | Size: 697 B |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/1.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/2.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/3.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/4.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/5.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/6.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/7.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/8.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.3 KiB |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/exploded.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/facingDown.png
Normal file
After Width: | Height: | Size: 871 B |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/flagged.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 4.0 KiB |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/led0.png
Normal file
After Width: | Height: | Size: 916 B |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/led1.png
Normal file
After Width: | Height: | Size: 977 B |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/led2.png
Normal file
After Width: | Height: | Size: 973 B |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/led3.png
Normal file
After Width: | Height: | Size: 879 B |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/led4.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/led5.png
Normal file
After Width: | Height: | Size: 990 B |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/led6.png
Normal file
After Width: | Height: | Size: 948 B |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/led7.png
Normal file
After Width: | Height: | Size: 991 B |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/led8.png
Normal file
After Width: | Height: | Size: 749 B |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/led9.png
Normal file
After Width: | Height: | Size: 929 B |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/led_blank.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/mine.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
info/Minesweeper2/MinesweeperGui/resources/images/raised.png
Normal file
After Width: | Height: | Size: 327 B |
606
info/Minesweeper2/MinesweeperSolver/BruteForce.cs
Normal file
@ -0,0 +1,606 @@
|
||||
using System;
|
||||
using System.CodeDom;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
/**
|
||||
* Performs a brute force search on the provided squares using the iterator
|
||||
*
|
||||
*/
|
||||
public class Cruncher {
|
||||
|
||||
public const sbyte BOMB = -10;
|
||||
|
||||
readonly private SolverInfo information;
|
||||
private readonly WitnessWebIterator iterator;
|
||||
readonly private List<SolverTile> tiles;
|
||||
readonly private List<BoxWitness> witnesses;
|
||||
readonly private sbyte[] currentFlagsTiles;
|
||||
readonly private sbyte[] currentFlagsWitnesses;
|
||||
|
||||
private int candidates = 0; // number of samples which satisfy the current board state
|
||||
private readonly BruteForceAnalysis bfa;
|
||||
|
||||
public Cruncher(SolverInfo information, WitnessWebIterator iterator, List<BoxWitness> witnesses, BruteForceAnalysis bfa) {
|
||||
|
||||
this.information = information;
|
||||
this.iterator = iterator; // the iterator
|
||||
this.tiles = iterator.getTiles(); // the tiles the iterator is iterating over
|
||||
this.witnesses = witnesses; // the dependent witnesses (class BoxWitness) which need to be checked to see if they are satisfied
|
||||
|
||||
this.bfa = bfa;
|
||||
|
||||
// determine how many found mines are currently next to each tile
|
||||
this.currentFlagsTiles = new sbyte[this.tiles.Count];
|
||||
for (int i = 0; i < this.tiles.Count; i++) {
|
||||
this.currentFlagsTiles[i] = (sbyte) this.information.AdjacentTileInfo(this.tiles[i]).mines;
|
||||
}
|
||||
|
||||
|
||||
// determine how many found mines are currently next to each witness
|
||||
this.currentFlagsWitnesses = new sbyte[this.witnesses.Count];
|
||||
for (int i = 0; i < this.witnesses.Count; i++) {
|
||||
this.currentFlagsWitnesses[i] = (sbyte) this.information.AdjacentTileInfo(this.witnesses[i].GetTile()).mines;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static BruteForceAnalysis PerformBruteForce(SolverInfo information, WitnessWebIterator[] iterators, List<BoxWitness> witnesses) {
|
||||
|
||||
BruteForceAnalysis bfa = new BruteForceAnalysis(information, iterators[0].getTiles(), SolverMain.MAX_BFDA_SOLUTIONS, null);
|
||||
|
||||
Cruncher[] crunchers = new Cruncher[iterators.Length];
|
||||
Task[] tasks = new Task[iterators.Length];
|
||||
|
||||
for (int i=0; i < iterators.Length; i++) {
|
||||
|
||||
crunchers[i] = new Cruncher(information, iterators[i], witnesses, bfa);
|
||||
Cruncher cruncher = crunchers[i];
|
||||
|
||||
tasks[i] = Task.Factory.StartNew(() => { cruncher.Crunch(); });
|
||||
}
|
||||
Task.WaitAll(tasks);
|
||||
|
||||
int solutions = 0;
|
||||
int iterations = 0;
|
||||
for (int i = 0; i < iterators.Length; i++) {
|
||||
solutions = solutions + crunchers[i].GetSolutionsFound();
|
||||
iterations = iterations + iterators[i].GetIterations();
|
||||
}
|
||||
|
||||
information.Write("Solutions found by brute force " + solutions + " after " + iterations + " iterations");
|
||||
|
||||
return bfa;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void Crunch() {
|
||||
|
||||
int[] sample = this.iterator.GetSample();
|
||||
|
||||
while (sample != null) {
|
||||
|
||||
if (this.CheckSample(sample)) {
|
||||
candidates++;
|
||||
}
|
||||
|
||||
sample = this.iterator.GetSample();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<SolverTile> getTiles() {
|
||||
return iterator.getTiles();
|
||||
}
|
||||
|
||||
// this checks whether the positions of the mines are a valid candidate solution
|
||||
private bool CheckSample(int[] sample) {
|
||||
|
||||
// get the tiles which are mines in this sample
|
||||
SolverTile[] mine = new SolverTile[sample.Length];
|
||||
for (int i = 0; i < sample.Length; i++) {
|
||||
mine[i] = this.tiles[sample[i]];
|
||||
}
|
||||
|
||||
for (int i = 0; i < this.witnesses.Count; i++) {
|
||||
|
||||
int flags1 = this.currentFlagsWitnesses[i];
|
||||
int flags2 = 0;
|
||||
|
||||
// count how many candidate mines are next to this witness
|
||||
for (int j = 0; j < mine.Length; j++) {
|
||||
if (mine[j].IsAdjacent(this.witnesses[i].GetTile())) {
|
||||
flags2++;
|
||||
}
|
||||
}
|
||||
|
||||
int flags3 = this.witnesses[i].GetTile().GetValue(); // number of flags indicated on the tile
|
||||
|
||||
if (flags3 != flags1 + flags2) {
|
||||
//Console.WriteLine("Failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//if it is a good solution then calculate the distribution if required
|
||||
|
||||
//Console.WriteLine("Solution found");
|
||||
|
||||
sbyte[] solution = new sbyte[this.tiles.Count];
|
||||
|
||||
for (int i = 0; i < this.tiles.Count; i++) {
|
||||
|
||||
bool isMine = false;
|
||||
for (int j = 0; j < sample.Length; j++) {
|
||||
if (i == sample[j]) {
|
||||
isMine = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if we are a mine then it doesn't matter how many mines surround us
|
||||
if (!isMine) {
|
||||
sbyte flags2 = this.currentFlagsTiles[i];
|
||||
// count how many candidate mines are next to this square
|
||||
for (int j = 0; j < mine.Length; j++) {
|
||||
if (mine[j].IsAdjacent(this.tiles[i])) {
|
||||
flags2++;
|
||||
}
|
||||
}
|
||||
solution[i] = flags2;
|
||||
} else {
|
||||
solution[i] = BOMB;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bfa.AddSolution(solution);
|
||||
|
||||
/*
|
||||
string output = "";
|
||||
for (int i = 0; i < mine.length; i++) {
|
||||
output = output + mine[i].asText();
|
||||
}
|
||||
console.log(output);
|
||||
*/
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public BruteForceAnalysis GetBruteForceAnalysis() {
|
||||
return this.bfa;
|
||||
}
|
||||
|
||||
public int GetSolutionsFound() {
|
||||
return this.candidates;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// create an iterator which is like a set of rotating wheels
|
||||
public class WitnessWebIterator {
|
||||
|
||||
private int[] sample;
|
||||
private SequentialIterator[] cogs;
|
||||
private int[] squareOffset;
|
||||
private int[] mineOffset;
|
||||
private List<SolverTile> tiles;
|
||||
|
||||
private int iterationsDone = 0;
|
||||
|
||||
readonly private int top;
|
||||
readonly private int bottom;
|
||||
|
||||
private bool done = false;
|
||||
|
||||
/*
|
||||
// if rotation is -1 then this does all the possible iterations
|
||||
// if rotation is not - 1 then this locks the first 'cog' in that position and iterates the remaining cogs. This allows parallel processing based on the position of the first 'cog'
|
||||
public WitnessWebIterator(ProbabilityEngine pe, List<SolverTile> allCoveredTiles, int rotation) {
|
||||
|
||||
this.tiles = new List<SolverTile>(); // list of tiles being iterated over
|
||||
|
||||
this.cogs = new SequentialIterator[pe.GetIndependentWitnesses().Count + 1]; // array of cogs
|
||||
this.squareOffset = new int[pe.GetIndependentWitnesses().Count + 1]; // int array
|
||||
this.mineOffset = new int[pe.GetIndependentWitnesses().Count + 1]; // int array
|
||||
|
||||
this.iterationsDone = 0;
|
||||
|
||||
this.done = false;
|
||||
|
||||
//this.probabilityEngine = pe;
|
||||
|
||||
// if we are setting the position of the top cog then it can't ever change
|
||||
if (rotation == -1) {
|
||||
this.bottom = 0;
|
||||
} else {
|
||||
this.bottom = 1;
|
||||
}
|
||||
|
||||
List<SolverTile> loc = new List<SolverTile>(); // array of locations
|
||||
|
||||
List<BoxWitness> indWitnesses = pe.GetIndependentWitnesses();
|
||||
|
||||
int cogi = 0;
|
||||
int indSquares = 0;
|
||||
int indMines = 0;
|
||||
|
||||
// create an array of locations in the order of independent witnesses
|
||||
foreach (BoxWitness w in indWitnesses) {
|
||||
|
||||
this.squareOffset[cogi] = indSquares;
|
||||
this.mineOffset[cogi] = indMines;
|
||||
this.cogs[cogi] = new SequentialIterator(w.GetMinesToFind(), w.GetAdjacentTiles().Count);
|
||||
cogi++;
|
||||
|
||||
indSquares = indSquares + w.GetAdjacentTiles().Count;
|
||||
indMines = indMines + w.GetMinesToFind();
|
||||
|
||||
loc.AddRange(w.GetAdjacentTiles());
|
||||
|
||||
}
|
||||
|
||||
//System.out.println("Mines left = " + (mines - indMines));
|
||||
//System.out.println("Squrs left = " + (web.getSquares().length - indSquares));
|
||||
|
||||
// the last cog has the remaining squares and mines
|
||||
|
||||
//add the rest of the locations
|
||||
for (int i = 0; i < allCoveredTiles.Count; i++) {
|
||||
|
||||
SolverTile l = allCoveredTiles[i];
|
||||
bool skip = false;
|
||||
for (int j = 0; j < loc.Count; j++) {
|
||||
|
||||
SolverTile m = loc[j];
|
||||
|
||||
if (l.IsEqual(m)) {
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!skip) {
|
||||
loc.Add(l);
|
||||
}
|
||||
}
|
||||
|
||||
this.tiles = loc;
|
||||
|
||||
SolverInfo information = pe.GetSolverInfo();
|
||||
|
||||
int minesLeft = information.GetMinesLeft() - information.GetExcludedMineCount();
|
||||
int tilesLeft = information.GetTilesLeft() - information.GetExcludedTiles().Count;
|
||||
|
||||
information.Write("Mines left " + minesLeft);
|
||||
information.Write("Independent Mines " + indMines);
|
||||
information.Write("Tiles left " + tilesLeft);
|
||||
information.Write("Independent tiles " + indSquares);
|
||||
|
||||
|
||||
// if there are more mines left then squares then no solution is possible
|
||||
// if there are not enough mines to satisfy the minimum we know are needed
|
||||
if (minesLeft - indMines > tilesLeft - indSquares
|
||||
|| indMines > minesLeft) {
|
||||
this.done = true;
|
||||
this.top = 0;
|
||||
Console.WriteLine("Nothing to do in this iterator");
|
||||
return;
|
||||
}
|
||||
|
||||
// if there are no mines left then no need for a cog
|
||||
if (minesLeft > indMines) {
|
||||
this.squareOffset[cogi] = indSquares;
|
||||
this.mineOffset[cogi] = indMines;
|
||||
this.cogs[cogi] = new SequentialIterator(minesLeft - indMines, tilesLeft - indSquares);
|
||||
this.top = cogi;
|
||||
} else {
|
||||
top = cogi - 1;
|
||||
}
|
||||
|
||||
//this.top = this.cogs.Length - 1;
|
||||
|
||||
this.sample = new int[minesLeft]; // make the sample array the size of the number of mines
|
||||
|
||||
// if we are locking and rotating the top cog then do it
|
||||
if (rotation != -1) {
|
||||
for (var i = 0; i < rotation; i++) {
|
||||
this.cogs[0].GetNextSample();
|
||||
}
|
||||
}
|
||||
|
||||
// now set up the initial sample position
|
||||
for (int i = 0; i < this.top; i++) {
|
||||
int[] s = this.cogs[i].GetNextSample();
|
||||
for (int j = 0; j < s.Length; j++) {
|
||||
this.sample[this.mineOffset[i] + j] = this.squareOffset[i] + s[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// if rotation is -1 then this does all the possible iterations
|
||||
// if rotation is not - 1 then this locks the first 'cog' in that position and iterates the remaining cogs. This allows parallel processing based on the position of the first 'cog'
|
||||
public WitnessWebIterator(SolverInfo information, List<BoxWitness> independentWitnesses, List<BoxWitness> depdendentWitnesses
|
||||
, List<SolverTile> allCoveredTiles, int minesLeft, int tilesLeft, int rotation) {
|
||||
|
||||
this.tiles = new List<SolverTile>(); // list of tiles being iterated over
|
||||
|
||||
int cogs;
|
||||
if (independentWitnesses == null) {
|
||||
cogs = 1;
|
||||
} else {
|
||||
cogs = independentWitnesses.Count + 1;
|
||||
}
|
||||
|
||||
this.cogs = new SequentialIterator[cogs]; // array of cogs
|
||||
this.squareOffset = new int[cogs]; // int array
|
||||
this.mineOffset = new int[cogs]; // int array
|
||||
|
||||
this.iterationsDone = 0;
|
||||
|
||||
this.done = false;
|
||||
|
||||
//this.probabilityEngine = pe;
|
||||
|
||||
// if we are setting the position of the top cog then it can't ever change
|
||||
if (rotation == -1) {
|
||||
this.bottom = 0;
|
||||
} else {
|
||||
this.bottom = 1;
|
||||
}
|
||||
|
||||
List<SolverTile> loc = new List<SolverTile>(); // array of locations
|
||||
|
||||
int cogi = 0;
|
||||
int indSquares = 0;
|
||||
int indMines = 0;
|
||||
|
||||
// create an array of locations in the order of independent witnesses
|
||||
if (independentWitnesses != null) {
|
||||
foreach (BoxWitness w in independentWitnesses) {
|
||||
|
||||
this.squareOffset[cogi] = indSquares;
|
||||
this.mineOffset[cogi] = indMines;
|
||||
this.cogs[cogi] = new SequentialIterator(w.GetMinesToFind(), w.GetAdjacentTiles().Count);
|
||||
cogi++;
|
||||
|
||||
indSquares = indSquares + w.GetAdjacentTiles().Count;
|
||||
indMines = indMines + w.GetMinesToFind();
|
||||
|
||||
loc.AddRange(w.GetAdjacentTiles());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//System.out.println("Mines left = " + (mines - indMines));
|
||||
//System.out.println("Squrs left = " + (web.getSquares().length - indSquares));
|
||||
|
||||
// the last cog has the remaining squares and mines
|
||||
|
||||
//add the rest of the locations
|
||||
for (int i = 0; i < allCoveredTiles.Count; i++) {
|
||||
|
||||
SolverTile l = allCoveredTiles[i];
|
||||
bool skip = false;
|
||||
for (int j = 0; j < loc.Count; j++) {
|
||||
|
||||
SolverTile m = loc[j];
|
||||
|
||||
if (l.IsEqual(m)) {
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!skip) {
|
||||
loc.Add(l);
|
||||
}
|
||||
}
|
||||
|
||||
this.tiles = loc;
|
||||
|
||||
information.Write("Mines left " + minesLeft);
|
||||
information.Write("Independent Mines " + indMines);
|
||||
information.Write("Tiles left " + tilesLeft);
|
||||
information.Write("Independent tiles " + indSquares);
|
||||
|
||||
|
||||
// if there are more mines left then squares then no solution is possible
|
||||
// if there are not enough mines to satisfy the minimum we know are needed
|
||||
if (minesLeft - indMines > tilesLeft - indSquares
|
||||
|| indMines > minesLeft) {
|
||||
this.done = true;
|
||||
this.top = 0;
|
||||
Console.WriteLine("Nothing to do in this iterator");
|
||||
return;
|
||||
}
|
||||
|
||||
// if there are no mines left then no need for a cog
|
||||
if (minesLeft > indMines) {
|
||||
this.squareOffset[cogi] = indSquares;
|
||||
this.mineOffset[cogi] = indMines;
|
||||
this.cogs[cogi] = new SequentialIterator(minesLeft - indMines, tilesLeft - indSquares);
|
||||
this.top = cogi;
|
||||
} else {
|
||||
top = cogi - 1;
|
||||
}
|
||||
|
||||
//this.top = this.cogs.Length - 1;
|
||||
|
||||
this.sample = new int[minesLeft]; // make the sample array the size of the number of mines
|
||||
|
||||
// if we are locking and rotating the top cog then do it
|
||||
if (rotation != -1) {
|
||||
for (var i = 0; i < rotation; i++) {
|
||||
this.cogs[0].GetNextSample();
|
||||
}
|
||||
}
|
||||
|
||||
// now set up the initial sample position
|
||||
for (int i = 0; i < this.top; i++) {
|
||||
int[] s = this.cogs[i].GetNextSample();
|
||||
for (int j = 0; j < s.Length; j++) {
|
||||
this.sample[this.mineOffset[i] + j] = this.squareOffset[i] + s[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int[] GetSample() {
|
||||
|
||||
|
||||
if (this.done) {
|
||||
Console.WriteLine("**** attempting to iterator when already completed ****");
|
||||
return null;
|
||||
}
|
||||
int index = this.top;
|
||||
|
||||
int[] s = this.cogs[index].GetNextSample();
|
||||
|
||||
while (s == null && index != this.bottom) {
|
||||
index--;
|
||||
s = this.cogs[index].GetNextSample();
|
||||
}
|
||||
|
||||
if (index == this.bottom && s == null) {
|
||||
this.done = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int j = 0; j < s.Length; j++) {
|
||||
this.sample[this.mineOffset[index] + j] = this.squareOffset[index] + s[j];
|
||||
}
|
||||
index++;
|
||||
while (index <= this.top) {
|
||||
this.cogs[index] = new SequentialIterator(this.cogs[index].GetNumberBalls(), this.cogs[index].GetNumberHoles());
|
||||
s = this.cogs[index].GetNextSample();
|
||||
for (int j = 0; j < s.Length; j++) {
|
||||
this.sample[this.mineOffset[index] + j] = this.squareOffset[index] + s[j];
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
/*
|
||||
String output = "";
|
||||
for (int j = 0; j < sample.Length; j++) {
|
||||
output = output + this.sample[j] + " ";
|
||||
}
|
||||
Console.WriteLine(output);
|
||||
*/
|
||||
|
||||
this.iterationsDone++;
|
||||
|
||||
return this.sample;
|
||||
|
||||
}
|
||||
|
||||
public List<SolverTile> getTiles() {
|
||||
return this.tiles;
|
||||
}
|
||||
|
||||
|
||||
public int GetIterations() {
|
||||
return iterationsDone;
|
||||
}
|
||||
|
||||
/*
|
||||
// if the location is a Independent witness then we know it will always
|
||||
// have exactly the correct amount of mines around it since that is what
|
||||
// this iterator does
|
||||
public bool WitnessAlwaysSatisfied(SolverTile location) {
|
||||
|
||||
for (var i = 0; i < this.probabilityEngine.independentWitness.length; i++) {
|
||||
if (this.probabilityEngine.independentWitness[i].equals(location)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class SequentialIterator {
|
||||
|
||||
readonly private int[] sample;
|
||||
readonly private int numberHoles;
|
||||
readonly private int numberBalls;
|
||||
|
||||
private bool more;
|
||||
private int index;
|
||||
|
||||
// a sequential iterator that puts n-balls in m-holes once in each possible way
|
||||
public SequentialIterator(int n, int m) {
|
||||
|
||||
this.numberHoles = m;
|
||||
this.numberBalls = n;
|
||||
|
||||
this.sample = new int[n];
|
||||
|
||||
this.more = true;
|
||||
|
||||
this.index = n - 1;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
this.sample[i] = i;
|
||||
}
|
||||
|
||||
// reduce the iterator by 1, since the first getSample() will increase it
|
||||
// by 1 again
|
||||
this.sample[this.index]--;
|
||||
|
||||
//Console.WriteLine("Sequential Iterator has " + this.numberBalls + " mines and " + this.numberHoles + " squares");
|
||||
|
||||
}
|
||||
|
||||
public int[] GetNextSample() {
|
||||
|
||||
if (!this.more) {
|
||||
Console.WriteLine("**** Trying to iterate after the end ****");
|
||||
return null;
|
||||
}
|
||||
|
||||
this.index = this.numberBalls - 1;
|
||||
|
||||
// add on one to the iterator
|
||||
this.sample[this.index]++;
|
||||
|
||||
// if we have rolled off the end then move backwards until we can fit
|
||||
// the next iteration
|
||||
while (this.sample[this.index] >= this.numberHoles - this.numberBalls + 1 + this.index) {
|
||||
if (this.index == 0) {
|
||||
this.more = false;
|
||||
return null;
|
||||
} else {
|
||||
this.index--;
|
||||
this.sample[this.index]++;
|
||||
}
|
||||
}
|
||||
|
||||
// roll forward
|
||||
while (this.index != this.numberBalls - 1) {
|
||||
this.index++;
|
||||
this.sample[this.index] = this.sample[this.index - 1] + 1;
|
||||
}
|
||||
|
||||
return this.sample;
|
||||
|
||||
}
|
||||
|
||||
public int GetNumberBalls() {
|
||||
return this.numberBalls;
|
||||
}
|
||||
|
||||
public int GetNumberHoles() {
|
||||
return this.numberHoles;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
837
info/Minesweeper2/MinesweeperSolver/BruteForceAnalysis.cs
Normal file
@ -0,0 +1,837 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using static MinesweeperControl.MinesweeperGame;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
|
||||
public class BruteForceAnalysis {
|
||||
|
||||
// used to hold all the solutions left in the game
|
||||
public class SolutionTable {
|
||||
|
||||
private readonly object locker = new object();
|
||||
|
||||
private readonly BruteForceAnalysis bfa;
|
||||
private readonly sbyte[][] solutions;
|
||||
private int size = 0;
|
||||
|
||||
public SolutionTable(BruteForceAnalysis bfa, int maxSize) {
|
||||
this.bfa = bfa;
|
||||
solutions = new sbyte[maxSize][];
|
||||
}
|
||||
|
||||
public void AddSolution(sbyte[] solution) {
|
||||
lock(locker) {
|
||||
solutions[size] = solution;
|
||||
size++;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public sbyte[] Get(int index) {
|
||||
return solutions[index];
|
||||
}
|
||||
|
||||
public void SortSolutions(int start, int end, int index) {
|
||||
|
||||
Array.Sort(solutions, start, end - start, bfa.sorters[index]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This sorts solutions by the value of a position
|
||||
*/
|
||||
public class SortSolutions : IComparer<sbyte[]> {
|
||||
|
||||
private readonly int sortIndex;
|
||||
|
||||
public SortSolutions(int index) {
|
||||
sortIndex = index;
|
||||
}
|
||||
|
||||
public int Compare(sbyte[] o1, sbyte[] o2) {
|
||||
return o1[sortIndex] - o2[sortIndex];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A key to uniquely identify a position
|
||||
*/
|
||||
public class Position {
|
||||
|
||||
private readonly byte[] position;
|
||||
private int hash;
|
||||
|
||||
public Position(int size) {
|
||||
position = new byte[size];
|
||||
for (int i = 0; i < position.Length; i++) {
|
||||
position[i] = 15;
|
||||
}
|
||||
}
|
||||
|
||||
public Position(Position p, int index, int value) {
|
||||
// copy and update to reflect the new position
|
||||
position = new byte[p.position.Length];
|
||||
Array.Copy(p.position, position, p.position.Length);
|
||||
position[index] = (byte)(value + 50);
|
||||
}
|
||||
|
||||
// copied from String hash
|
||||
public override int GetHashCode() {
|
||||
int h = hash;
|
||||
if (h == 0 && position.Length > 0) {
|
||||
for (int i = 0; i < position.Length; i++) {
|
||||
h = 31 * h + position[i];
|
||||
}
|
||||
hash = h;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
public override bool Equals(Object o) {
|
||||
if (o is Position) {
|
||||
for (int i = 0; i < position.Length; i++) {
|
||||
if (this.position[i] != ((Position)o).position[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Positions on the board which can still reveal information about the game.
|
||||
*/
|
||||
public class LivingLocation : IComparable<LivingLocation> {
|
||||
|
||||
//private int winningLines = 0;
|
||||
public bool pruned = false;
|
||||
public readonly short index;
|
||||
public int mineCount = 0; // number of remaining solutions which have a mine in this position
|
||||
public int maxSolutions = 0; // the maximum number of solutions that can be remaining after clicking here
|
||||
public int zeroSolutions = 0; // the number of solutions that have a '0' value here
|
||||
public sbyte maxValue = -1;
|
||||
public sbyte minValue = -1;
|
||||
public byte count; // number of possible values at this location
|
||||
|
||||
public Node[] children;
|
||||
private readonly BruteForceAnalysis bfa;
|
||||
|
||||
public LivingLocation(BruteForceAnalysis bfa, short index) {
|
||||
this.index = index;
|
||||
this.bfa = bfa;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the Nodes which are created if we play this move. Up to 9 positions where this locations reveals a value [0-8].
|
||||
* @param location
|
||||
* @return
|
||||
*/
|
||||
public void BuildChildNodes(Node parent) {
|
||||
|
||||
// sort the solutions by possible values
|
||||
bfa.allSolutions.SortSolutions(parent.startLocation, parent.endLocation, this.index);
|
||||
int index = parent.startLocation;
|
||||
|
||||
// skip over the mines
|
||||
while (index < parent.endLocation && bfa.allSolutions.Get(index)[this.index] == Cruncher.BOMB) {
|
||||
index++;
|
||||
}
|
||||
|
||||
Node[] work = new Node[9];
|
||||
for (int i = this.minValue; i < this.maxValue + 1; i++) {
|
||||
|
||||
// if the node is in the cache then use it
|
||||
Position pos = new Position(parent.position, this.index, i);
|
||||
|
||||
if (!bfa.cache.TryGetValue(pos, out Node temp1)) { // if value not in cache
|
||||
|
||||
Node temp = new Node(bfa, pos);
|
||||
|
||||
temp.startLocation = index;
|
||||
// find all solutions for this values at this location
|
||||
while (index < parent.endLocation && bfa.allSolutions.Get(index)[this.index] == i) {
|
||||
index++;
|
||||
}
|
||||
temp.endLocation = index;
|
||||
|
||||
work[i] = temp;
|
||||
|
||||
} else { // value in cache
|
||||
|
||||
//System.out.println("In cache " + temp.position.key + " " + temp1.position.key);
|
||||
//if (!temp.equals(temp1)) {
|
||||
// System.out.println("Cache not equal!!");
|
||||
//}
|
||||
//temp1.fromCache = true;
|
||||
work[i] = temp1;
|
||||
bfa.cacheHit++;
|
||||
bfa.cacheWinningLines = bfa.cacheWinningLines + temp1.winningLines;
|
||||
// skip past these details in the array
|
||||
while (index < parent.endLocation && bfa.allSolutions.Get(index)[this.index] <= i) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (index != parent.endLocation) {
|
||||
Console.WriteLine("Didn't read all the elements in the array; index = " + index + " end = " + parent.endLocation);
|
||||
}
|
||||
|
||||
|
||||
for (int i = this.minValue; i <= this.maxValue; i++) {
|
||||
if (work[i].GetSolutionSize() > 0) {
|
||||
//if (!work[i].fromCache) {
|
||||
// work[i].determineLivingLocations(this.livingLocations, living.index);
|
||||
//}
|
||||
} else {
|
||||
work[i] = null; // if no solutions then don't hold on to the details
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.children = work;
|
||||
|
||||
}
|
||||
|
||||
public int CompareTo(LivingLocation o) {
|
||||
|
||||
// return location most likely to be clear - this has to be first, the logic depends upon it
|
||||
int test = this.mineCount - o.mineCount;
|
||||
if (test != 0) {
|
||||
return test;
|
||||
}
|
||||
|
||||
// then the location most likely to have a zero
|
||||
test = o.zeroSolutions - this.zeroSolutions;
|
||||
if (test != 0) {
|
||||
return test;
|
||||
}
|
||||
|
||||
// then by most number of different possible values
|
||||
test = o.count - this.count;
|
||||
if (test != 0) {
|
||||
return test;
|
||||
}
|
||||
|
||||
// then by the maxSolutions - ascending
|
||||
return this.maxSolutions - o.maxSolutions;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A representation of a possible state of the game
|
||||
*/
|
||||
public class Node {
|
||||
|
||||
public Position position; // representation of the position we are analysing / have reached
|
||||
|
||||
public int winningLines = 0; // this is the number of winning lines below this position in the tree
|
||||
public int work = 0; // this is a measure of how much work was needed to calculate WinningLines value
|
||||
private bool fromCache = false; // indicates whether this position came from the cache
|
||||
|
||||
public int startLocation; // the first solution in the solution array that applies to this position
|
||||
public int endLocation; // the last + 1 solution in the solution array that applies to this position
|
||||
|
||||
public List<LivingLocation> livingLocations; // these are the locations which need to be analysed
|
||||
|
||||
public LivingLocation bestLiving; // after analysis this is the location that represents best play
|
||||
|
||||
private readonly BruteForceAnalysis bfa;
|
||||
|
||||
public Node(BruteForceAnalysis bfa, int size) {
|
||||
position = new Position(size);
|
||||
this.bfa = bfa;
|
||||
}
|
||||
|
||||
public Node(BruteForceAnalysis bfa, Position position) {
|
||||
this.position = position;
|
||||
this.bfa = bfa;
|
||||
}
|
||||
|
||||
public List<LivingLocation> GetLivingLocations() {
|
||||
return livingLocations;
|
||||
}
|
||||
|
||||
public int GetSolutionSize() {
|
||||
return endLocation - startLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the probability of winning the game from the position this node represents (winningLines / solution size)
|
||||
* @return
|
||||
*/
|
||||
public double GetProbability() {
|
||||
|
||||
return ((double)winningLines) / GetSolutionSize();
|
||||
//return BigDecimal.valueOf(winningLines).divide(BigDecimal.valueOf(getSolutionSize()), Solver.DP, RoundingMode.HALF_UP);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of winning lines if this move is played at this position
|
||||
* Used at top of the game tree
|
||||
*/
|
||||
public int GetWinningLines(LivingLocation move) {
|
||||
|
||||
//if we can never exceed the cutoff then no point continuing
|
||||
if (SolverMain.PRUNE_BF_ANALYSIS && this.GetSolutionSize() - move.mineCount <= this.winningLines) {
|
||||
move.pruned = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int winningLines = GetWinningLines(1, move, this.winningLines);
|
||||
|
||||
if (winningLines > this.winningLines) {
|
||||
this.winningLines = winningLines;
|
||||
}
|
||||
|
||||
return winningLines;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the number of winning lines if this move is played at this position
|
||||
* Used when exploring the game tree
|
||||
*/
|
||||
public int GetWinningLines(int depth, LivingLocation move, int cutoff) {
|
||||
|
||||
int result = 0;
|
||||
|
||||
bfa.processCount++;
|
||||
if (bfa.processCount > SolverMain.BRUTE_FORCE_ANALYSIS_MAX_NODES) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int notMines = this.GetSolutionSize() - move.mineCount;
|
||||
|
||||
move.BuildChildNodes(this);
|
||||
|
||||
foreach (Node child in move.children) {
|
||||
|
||||
if (child == null) {
|
||||
continue; // continue the loop but ignore this entry
|
||||
}
|
||||
|
||||
int maxWinningLines = result + notMines;
|
||||
|
||||
// if the max possible winning lines is less than the current cutoff then no point doing the analysis
|
||||
if (SolverMain.PRUNE_BF_ANALYSIS && maxWinningLines <= cutoff) {
|
||||
move.pruned = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if (child.fromCache) { // nothing more to do, since we did it before
|
||||
this.work++;
|
||||
} else {
|
||||
|
||||
child.DetermineLivingLocations(this.livingLocations, move.index);
|
||||
this.work++;
|
||||
|
||||
if (child.GetLivingLocations().Count == 0) { // no further information ==> all solution indistinguishable ==> 1 winning line
|
||||
|
||||
child.winningLines = 1;
|
||||
|
||||
} else { // not cached and not terminal node, so we need to do the recursion
|
||||
|
||||
foreach (LivingLocation childMove in child.GetLivingLocations()) {
|
||||
|
||||
// if the number of safe solutions <= the best winning lines then we can't do any better, so skip the rest
|
||||
if (child.GetSolutionSize() - childMove.mineCount <= child.winningLines) {
|
||||
break;
|
||||
}
|
||||
|
||||
// now calculate the winning lines for each of these children
|
||||
int winningLines = child.GetWinningLines(depth + 1, childMove, child.winningLines);
|
||||
if (child.winningLines < winningLines || (child.bestLiving != null && child.winningLines == winningLines && child.bestLiving.mineCount < childMove.mineCount)) {
|
||||
child.winningLines = winningLines;
|
||||
child.bestLiving = childMove;
|
||||
}
|
||||
|
||||
// if there are no mines then this is a 100% safe move, so skip any further analysis since it can't be any better
|
||||
if (childMove.mineCount == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// no need to hold onto the living location once we have determined the best of them
|
||||
child.livingLocations = null;
|
||||
|
||||
//if (depth > solver.preferences.BRUTE_FORCE_ANALYSIS_TREE_DEPTH) { // stop holding the tree beyond this depth
|
||||
// child.bestLiving = null;
|
||||
//}
|
||||
|
||||
// add the child to the cache if it didn't come from there and it is carrying sufficient winning lines
|
||||
if (child.work > 30) {
|
||||
child.work = 0;
|
||||
child.fromCache = true;
|
||||
bfa.cacheSize++;
|
||||
bfa.cache.Add(child.position, child);
|
||||
} else {
|
||||
this.work = this.work + child.work;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (depth > SolverMain.BRUTE_FORCE_ANALYSIS_TREE_DEPTH) { // stop holding the tree beyond this depth
|
||||
child.bestLiving = null;
|
||||
}
|
||||
|
||||
// store the aggregate winning lines
|
||||
result = result + child.winningLines;
|
||||
|
||||
notMines = notMines - child.GetSolutionSize(); // reduce the number of not mines
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* this generates a list of Location that are still alive, (i.e. have more than one possible value) from a list of previously living locations
|
||||
* Index is the move which has just been played (in terms of the off-set to the position[] array)
|
||||
*/
|
||||
public void DetermineLivingLocations(List<LivingLocation> liveLocs, int index) {
|
||||
|
||||
List<LivingLocation> living = new List<LivingLocation>(liveLocs.Count);
|
||||
|
||||
foreach (LivingLocation live in liveLocs) {
|
||||
|
||||
if (live.index == index) { // if this is the same move we just played then no need to analyse it - definitely now non-living.
|
||||
continue;
|
||||
}
|
||||
|
||||
int value;
|
||||
|
||||
int[] valueCount = bfa.ResetValues();
|
||||
int mines = 0;
|
||||
int maxSolutions = 0;
|
||||
byte count = 0;
|
||||
sbyte minValue = 0;
|
||||
sbyte maxValue = 0;
|
||||
|
||||
for (int j = startLocation; j < endLocation; j++) {
|
||||
value = bfa.allSolutions.Get(j)[live.index];
|
||||
if (value != Cruncher.BOMB) {
|
||||
//values[value] = true;
|
||||
valueCount[value]++;
|
||||
} else {
|
||||
mines++;
|
||||
}
|
||||
}
|
||||
|
||||
// find the new minimum value and maximum value for this location (can't be wider than the previous min and max)
|
||||
for (sbyte j = live.minValue; j <= live.maxValue; j++) {
|
||||
if (valueCount[j] > 0) {
|
||||
if (count == 0) {
|
||||
minValue = j;
|
||||
}
|
||||
maxValue = j;
|
||||
count++;
|
||||
if (maxSolutions < valueCount[j]) {
|
||||
maxSolutions = valueCount[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count > 1) {
|
||||
LivingLocation alive = new LivingLocation(bfa, live.index);
|
||||
alive.mineCount = mines;
|
||||
alive.count = count;
|
||||
alive.minValue = minValue;
|
||||
alive.maxValue = maxValue;
|
||||
alive.maxSolutions = maxSolutions;
|
||||
alive.zeroSolutions = valueCount[0];
|
||||
living.Add(alive);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
living.Sort();
|
||||
//Collections.sort(living);
|
||||
|
||||
this.livingLocations = living;
|
||||
|
||||
}
|
||||
|
||||
public override int GetHashCode() {
|
||||
return position.GetHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(Object o) {
|
||||
if (o is Node) {
|
||||
return position.Equals(((Node)o).position);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// start of main class
|
||||
|
||||
private static readonly String INDENT = "................................................................................";
|
||||
|
||||
//private static readonly BigDecimal ONE_HUNDRED = BigDecimal.valueOf(100);
|
||||
|
||||
public int processCount = 0;
|
||||
|
||||
private readonly SolverInfo solver;
|
||||
private readonly int maxSolutionSize;
|
||||
|
||||
//private Node top;
|
||||
|
||||
private readonly List<SolverTile> locations; // the positions being analysed
|
||||
private readonly List<SolverTile> startLocations; // the positions which will be considered for the first move
|
||||
|
||||
private readonly SolutionTable allSolutions;
|
||||
|
||||
//private readonly String scope;
|
||||
|
||||
private Node currentNode;
|
||||
private SolverTile expectedMove;
|
||||
|
||||
private readonly SortSolutions[] sorters;
|
||||
|
||||
private int cacheHit = 0;
|
||||
public int cacheSize = 0;
|
||||
private int cacheWinningLines = 0;
|
||||
private bool allDead = false; // this is true if all the locations are dead
|
||||
private bool tooMany = false;
|
||||
private bool completed = false;
|
||||
|
||||
// some work areas to prevent having to instantiate many 1000's of copies of them
|
||||
//private final boolean[] values = new boolean[9];
|
||||
private readonly int[] valueCount = new int[9];
|
||||
|
||||
private Dictionary<Position, Node> cache = new Dictionary<Position, Node>(5000);
|
||||
|
||||
public BruteForceAnalysis(SolverInfo solver, List<SolverTile> locations, int size, List<SolverTile> startLocations) {
|
||||
|
||||
this.solver = solver;
|
||||
this.locations = locations;
|
||||
this.maxSolutionSize = size;
|
||||
|
||||
//this.top = new Node();
|
||||
sorters = new SortSolutions[locations.Count];
|
||||
for (int i = 0; i < sorters.Length; i++) {
|
||||
sorters[i] = new SortSolutions(i);
|
||||
}
|
||||
|
||||
this.allSolutions = new SolutionTable(this, size);
|
||||
|
||||
this.startLocations = startLocations;
|
||||
|
||||
}
|
||||
|
||||
public void AddSolution(sbyte[] solution) {
|
||||
|
||||
if (solution.Length != locations.Count) {
|
||||
throw new Exception("Solution does not have the correct number of locations");
|
||||
}
|
||||
|
||||
if (allSolutions.GetSize() >= maxSolutionSize) {
|
||||
tooMany = true;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
String text = "";
|
||||
for (int i=0; i < solution.length; i++) {
|
||||
text = text + solution[i] + " ";
|
||||
}
|
||||
solver.display(text);
|
||||
*/
|
||||
|
||||
allSolutions.AddSolution(solution);
|
||||
|
||||
}
|
||||
|
||||
public void process() {
|
||||
|
||||
long start = DateTime.Now.Ticks;
|
||||
|
||||
solver.Write("----- Brute Force Deep Analysis starting ----");
|
||||
solver.Write(allSolutions.GetSize() + " solutions in BruteForceAnalysis");
|
||||
|
||||
// create the top node
|
||||
Node top = buildTopNode(allSolutions);
|
||||
|
||||
if (top.GetLivingLocations().Count == 0) {
|
||||
allDead = true;
|
||||
}
|
||||
|
||||
int best = 0;
|
||||
|
||||
foreach (LivingLocation move in top.GetLivingLocations()) {
|
||||
|
||||
// check that the move is in the startLocation list
|
||||
if (startLocations != null) {
|
||||
bool found = false;
|
||||
foreach (SolverTile l in startLocations) {
|
||||
if (locations[move.index].Equals(l)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) { // if not then skip this move
|
||||
solver.Write(move.index + " " + locations[move.index].AsText() + " is not a starting location");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int winningLines = top.GetWinningLines(move); // calculate the number of winning lines if this move is played
|
||||
|
||||
if (best < winningLines || (top.bestLiving != null && best == winningLines && top.bestLiving.mineCount < move.mineCount)) {
|
||||
best = winningLines;
|
||||
top.bestLiving = move;
|
||||
}
|
||||
|
||||
double singleProb = (allSolutions.GetSize() - move.mineCount) / allSolutions.GetSize();
|
||||
|
||||
if (move.pruned) {
|
||||
solver.Write(move.index + " " + locations[move.index].AsText() + " is living with " + move.count + " possible values and probability " + singleProb + ", this location was pruned");
|
||||
} else {
|
||||
solver.Write(move.index + " " + locations[move.index].AsText() + " is living with " + move.count + " possible values and probability " + singleProb + ", winning lines " + winningLines);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
top.winningLines = best;
|
||||
|
||||
currentNode = top;
|
||||
|
||||
if (processCount < SolverMain.BRUTE_FORCE_ANALYSIS_MAX_NODES) {
|
||||
this.completed = true;
|
||||
//if (solver.isShowProbabilityTree()) {
|
||||
// solver.newLine("--------- Probability Tree dump start ---------");
|
||||
// showTree(0, 0, top);
|
||||
// solver.newLine("---------- Probability Tree dump end ----------");
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
// clear down the cache
|
||||
cache.Clear();
|
||||
|
||||
long end = DateTime.Now.Ticks;
|
||||
|
||||
solver.Write("Total nodes in cache = " + cacheSize + ", total cache hits = " + cacheHit + ", total winning lines saved = " + this.cacheWinningLines);
|
||||
solver.Write("process took " + (end - start) + " milliseconds and explored " + processCount + " nodes");
|
||||
solver.Write("----- Brute Force Deep Analysis finished ----");
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a top of tree node based on the solutions provided
|
||||
*/
|
||||
private Node buildTopNode(SolutionTable solutionTable) {
|
||||
|
||||
Node result = new Node(this, locations.Count);
|
||||
|
||||
result.startLocation = 0;
|
||||
result.endLocation = solutionTable.GetSize();
|
||||
|
||||
List<LivingLocation> living = new List<LivingLocation>();
|
||||
|
||||
for (short i = 0; i < locations.Count; i++) {
|
||||
int value;
|
||||
|
||||
int[] valueCount = ResetValues();
|
||||
int mines = 0;
|
||||
int maxSolutions = 0;
|
||||
byte count = 0;
|
||||
sbyte minValue = 0;
|
||||
sbyte maxValue = 0;
|
||||
|
||||
for (int j = 0; j < result.GetSolutionSize(); j++) {
|
||||
if (solutionTable.Get(j)[i] != Cruncher.BOMB) {
|
||||
value = solutionTable.Get(j)[i];
|
||||
//values[value] = true;
|
||||
valueCount[value]++;
|
||||
} else {
|
||||
mines++;
|
||||
}
|
||||
}
|
||||
|
||||
for (sbyte j = 0; j < valueCount.Length; j++) {
|
||||
if (valueCount[j] > 0) {
|
||||
if (count == 0) {
|
||||
minValue = j;
|
||||
}
|
||||
maxValue = j;
|
||||
count++;
|
||||
if (maxSolutions < valueCount[j]) {
|
||||
maxSolutions = valueCount[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count > 1) {
|
||||
LivingLocation alive = new LivingLocation(this, i);
|
||||
alive.mineCount = mines;
|
||||
alive.count = count;
|
||||
alive.minValue = minValue;
|
||||
alive.maxValue = maxValue;
|
||||
alive.maxSolutions = maxSolutions;
|
||||
alive.zeroSolutions = valueCount[0];
|
||||
living.Add(alive);
|
||||
} else {
|
||||
solver.Write(locations[i].AsText() + " is dead with value " + minValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
living.Sort();
|
||||
//Collections.sort(living);
|
||||
|
||||
result.livingLocations = living;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public int[] ResetValues() {
|
||||
for (int i = 0; i < valueCount.Length; i++) {
|
||||
valueCount[i] = 0;
|
||||
}
|
||||
return valueCount;
|
||||
}
|
||||
|
||||
public int GetSolutionCount() {
|
||||
return allSolutions.GetSize();
|
||||
}
|
||||
|
||||
|
||||
public int GetNodeCount() {
|
||||
return processCount;
|
||||
}
|
||||
|
||||
public SolverAction GetNextMove() {
|
||||
|
||||
LivingLocation bestLiving = getBestLocation(currentNode);
|
||||
|
||||
if (bestLiving == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SolverTile loc = this.locations[bestLiving.index];
|
||||
|
||||
//solver.display("first best move is " + loc.display());
|
||||
double prob = 1 - bestLiving.mineCount / currentNode.GetSolutionSize();
|
||||
|
||||
while (!loc.IsHidden()) {
|
||||
int value = loc.GetValue();
|
||||
|
||||
currentNode = bestLiving.children[value];
|
||||
bestLiving = getBestLocation(currentNode);
|
||||
if (bestLiving == null) {
|
||||
return null;
|
||||
}
|
||||
prob = 1 - ((double) bestLiving.mineCount) / currentNode.GetSolutionSize();
|
||||
|
||||
loc = this.locations[bestLiving.index];
|
||||
|
||||
}
|
||||
|
||||
solver.Write("mines = " + bestLiving.mineCount + " solutions = " + currentNode.GetSolutionSize());
|
||||
for (int i = 0; i < bestLiving.children.Length; i++) {
|
||||
if (bestLiving.children[i] == null) {
|
||||
//solver.display("Value of " + i + " is not possible");
|
||||
continue; //ignore this node but continue the loop
|
||||
}
|
||||
|
||||
String probText;
|
||||
if (bestLiving.children[i].bestLiving == null) {
|
||||
probText = (100 / (bestLiving.children[i].GetSolutionSize())) + "%";
|
||||
} else {
|
||||
probText = bestLiving.children[i].GetProbability() * 100 + "%";
|
||||
}
|
||||
solver.Write("Value of " + i + " leaves " + bestLiving.children[i].GetSolutionSize() + " solutions and winning probability " + probText + " (work size " + bestLiving.children[i].work + ")");
|
||||
}
|
||||
|
||||
//String text = " (solve " + (currentNode.GetProbability() * 100) + "%)";
|
||||
SolverAction action = new SolverAction(loc, ActionType.Clear, 0.5);
|
||||
|
||||
expectedMove = loc;
|
||||
|
||||
return action;
|
||||
|
||||
}
|
||||
|
||||
private LivingLocation getBestLocation(Node node) {
|
||||
return node.bestLiving;
|
||||
}
|
||||
|
||||
private void ShowTree(int depth, int value, Node node) {
|
||||
|
||||
String condition;
|
||||
if (depth == 0) {
|
||||
condition = node.GetSolutionSize() + " solutions remain";
|
||||
} else {
|
||||
condition = "When '" + value + "' ==> " + node.GetSolutionSize() + " solutions remain";
|
||||
}
|
||||
|
||||
if (node.bestLiving == null) {
|
||||
String line1 = INDENT.Substring(0, depth * 3) + condition + " Solve chance " + node.GetProbability() * 100 + "%";
|
||||
Console.WriteLine(line1);
|
||||
return;
|
||||
}
|
||||
|
||||
SolverTile loc = this.locations[node.bestLiving.index];
|
||||
|
||||
double prob = 1 - node.bestLiving.mineCount / node.GetSolutionSize();
|
||||
|
||||
|
||||
String line = INDENT.Substring(0, depth * 3) + condition + " play " + loc.AsText() + " Survival chance " + prob * 100 + "%, Solve chance " + node.GetProbability() * 100 + "%";
|
||||
|
||||
Console.WriteLine(line);
|
||||
|
||||
//for (Node nextNode: node.bestLiving.children) {
|
||||
for (int val = 0; val < node.bestLiving.children.Length; val++) {
|
||||
Node nextNode = node.bestLiving.children[val];
|
||||
if (nextNode != null) {
|
||||
ShowTree(depth + 1, val, nextNode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public bool IsComplete() {
|
||||
return this.completed;
|
||||
}
|
||||
|
||||
public SolverTile GetExpectedMove() {
|
||||
return expectedMove;
|
||||
}
|
||||
|
||||
//private String percentage(double prob) {
|
||||
// return Action.FORMAT_2DP.format(prob.multiply(ONE_HUNDRED));
|
||||
//}
|
||||
|
||||
public bool GetAllDead() {
|
||||
return allDead;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
45
info/Minesweeper2/MinesweeperSolver/Combination.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
public class Combination {
|
||||
|
||||
public static BigInteger Calculate(int mines, int squares) {
|
||||
|
||||
long start = DateTime.Now.Ticks;
|
||||
|
||||
BigInteger top = 1;
|
||||
BigInteger bot = 1;
|
||||
|
||||
var range = Math.Min(mines, squares - mines);
|
||||
|
||||
// calculate the combination.
|
||||
for (int i = 0; i < range; i++) {
|
||||
top = top * (squares - i);
|
||||
bot = bot * (i + 1);
|
||||
}
|
||||
|
||||
BigInteger result = top / bot;
|
||||
|
||||
//SolverMain.Write(squares + " pick " + mines + " in " + result + " ways");
|
||||
//SolverMain.Write("Combination duration " + (DateTime.Now.Ticks - start) + " ticks");
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private static readonly BigInteger[] power10n = { BigInteger.One, new BigInteger(10), new BigInteger(100), new BigInteger(1000), new BigInteger(10000), new BigInteger(100000), new BigInteger(1000000) };
|
||||
private static readonly int[] power10 = { 1, 10, 100, 1000, 10000, 100000, 1000000 };
|
||||
|
||||
public static double DivideBigIntegerToDouble(BigInteger numerator, BigInteger denominator, int dp) {
|
||||
|
||||
var work = numerator * power10n[dp] / denominator;
|
||||
|
||||
var result = (double) work / power10[dp];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
<HasSharedItems>true</HasSharedItems>
|
||||
<SharedGUID>60591e9a-f066-4af3-b1e3-b20b2b2b0b1d</SharedGUID>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration">
|
||||
<Import_RootNamespace>MinesweeperSolver</Import_RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Binomial.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)BruteForce.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)BruteForceAnalysis.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Combination.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)PrimeSieve.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)SolutionCounter.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ProbabilityEngine.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)SolverAction.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)SolverActionHeader.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)SolverInfo.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)SolverMain.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)SolverTile.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
13
info/Minesweeper2/MinesweeperSolver/MinesweeperSolver.shproj
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>60591e9a-f066-4af3-b1e3-b20b2b2b0b1d</ProjectGuid>
|
||||
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
|
||||
<PropertyGroup />
|
||||
<Import Project="MinesweeperSolver.projitems" Label="Shared" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
|
||||
</Project>
|
129
info/Minesweeper2/MinesweeperSolver/PrimeSieve.cs
Normal file
@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
|
||||
public class PrimeSieve {
|
||||
|
||||
// iterator for prime numbers
|
||||
public class Primes : IEnumerable<int>, IEnumerator<int> {
|
||||
|
||||
private int index = 0;
|
||||
private readonly int stop;
|
||||
private int nextPrime;
|
||||
private readonly bool[] composite;
|
||||
|
||||
|
||||
public int Current => Next();
|
||||
|
||||
object IEnumerator.Current => Next();
|
||||
|
||||
public Primes(bool[] composite, int start, int stop) {
|
||||
this.index = start;
|
||||
this.stop = stop;
|
||||
this.composite = composite;
|
||||
this.nextPrime = findNext();
|
||||
}
|
||||
|
||||
public int Next() {
|
||||
int result = nextPrime;
|
||||
nextPrime = findNext();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private int findNext() {
|
||||
|
||||
int next = -1;
|
||||
while (index <= stop && next == -1) {
|
||||
if (!composite[index]) {
|
||||
next = index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
return next;
|
||||
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
|
||||
}
|
||||
|
||||
public bool MoveNext() {
|
||||
return (nextPrime != -1);
|
||||
}
|
||||
|
||||
public void Reset() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IEnumerator<int> GetEnumerator() {
|
||||
return this;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private readonly bool[] composite;
|
||||
private readonly int max;
|
||||
|
||||
public PrimeSieve(int n) {
|
||||
|
||||
if (n < 2) {
|
||||
max = 2;
|
||||
} else {
|
||||
max = n;
|
||||
}
|
||||
|
||||
composite = new bool[max + 1];
|
||||
|
||||
int rootN = (int)Math.Floor(Math.Sqrt(n));
|
||||
|
||||
for (int i = 2; i < rootN; i++) {
|
||||
|
||||
// if this is a prime number (not composite) then sieve the array
|
||||
if (!composite[i]) {
|
||||
int index = i + i;
|
||||
while (index <= max) {
|
||||
composite[index] = true;
|
||||
index = index + i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public bool IsPrime(int n) {
|
||||
if (n <= 1 || n > max) {
|
||||
throw new Exception("Test value " + n + " is out of range 2 - " + max);
|
||||
}
|
||||
|
||||
return !composite [n];
|
||||
}
|
||||
|
||||
public IEnumerable<int> getPrimesIterable(int start, int stop) {
|
||||
|
||||
if (start > stop) {
|
||||
throw new Exception("start " + start + " must be <= to stop " + stop);
|
||||
}
|
||||
if (start <= 1 || start > max) {
|
||||
throw new Exception("Start value " + start + " is out of range 2 - " + max);
|
||||
}
|
||||
if (stop <= 1 || stop > max) {
|
||||
throw new Exception("Stop value " + stop + " is out of range 2 - " + max);
|
||||
}
|
||||
|
||||
return new Primes(composite, start, stop);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
2055
info/Minesweeper2/MinesweeperSolver/ProbabilityEngine.cs
Normal file
859
info/Minesweeper2/MinesweeperSolver/SolutionCounter.cs
Normal file
@ -0,0 +1,859 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using static MinesweeperControl.MinesweeperGame;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
public class SolutionCounter {
|
||||
|
||||
private readonly int[][] SMALL_COMBINATIONS = new int[][] { new int[] { 1 }, new int[] { 1, 1 }, new int[] { 1, 2, 1 }, new int[] { 1, 3, 3, 1 }, new int[] { 1, 4, 6, 4, 1 }, new int[] { 1, 5, 10, 10, 5, 1 }, new int[] { 1, 6, 15, 20, 15, 6, 1 }, new int[] { 1, 7, 21, 35, 35, 21, 7, 1 }, new int[] { 1, 8, 28, 56, 70, 56, 28, 8, 1 } };
|
||||
|
||||
private readonly List<SolverTile> witnessed;
|
||||
private readonly List<BoxWitness> prunedWitnesses = new List<BoxWitness>(); // a subset of allWitnesses with equivalent witnesses removed
|
||||
private readonly List<Box> boxes = new List<Box>();
|
||||
private readonly List<BoxWitness> boxWitnesses = new List<BoxWitness>();
|
||||
private bool[] mask;
|
||||
|
||||
private readonly Dictionary<SolverTile, Box> boxLookup = new Dictionary<SolverTile, Box>(); // a lookup which finds which box a tile belongs to (an efficiency enhancement)
|
||||
//private readonly List<DeadCandidate> deadCandidates = new List<DeadCandidate>();
|
||||
|
||||
private List<ProbabilityLine> workingProbs = new List<ProbabilityLine>();
|
||||
private readonly List<ProbabilityLine> heldProbs = new List<ProbabilityLine>();
|
||||
private readonly List<EdgeStore> edgeStore = new List<EdgeStore>(); // stores independent edge analysis until we know we have to merge them
|
||||
|
||||
|
||||
private readonly int minesLeft;
|
||||
private readonly int tilesLeft;
|
||||
private readonly int tilesOffEdge;
|
||||
private readonly int minTotalMines;
|
||||
private readonly int maxTotalMines;
|
||||
private int recursions;
|
||||
|
||||
// used to find the range of mine counts which offer the 2.5% - 97.5% weighted average
|
||||
private int edgeMinesMin;
|
||||
private int edgeMinesMax;
|
||||
private int edgeMinesMinLeft;
|
||||
private int edgeMinesMaxLeft;
|
||||
private int mineCountUpperCutoff;
|
||||
private int mineCountLowerCutoff;
|
||||
private bool truncatedProbs = false; // this gets set when the number of held probabilies exceeds the permitted threshold
|
||||
|
||||
private BigInteger finalSolutionCount = 0;
|
||||
private BigInteger solutionCountMultiplier = 1;
|
||||
|
||||
private int clearCount = 0;
|
||||
|
||||
private readonly SolverInfo information;
|
||||
|
||||
public SolutionCounter(SolverInfo information, List<SolverTile> allWitnesses, List<SolverTile> allWitnessed, int squaresLeft, int minesLeft) {
|
||||
|
||||
this.information = information;
|
||||
|
||||
this.witnessed = allWitnessed;
|
||||
|
||||
// constraints in the game
|
||||
this.minesLeft = minesLeft;
|
||||
this.tilesLeft = squaresLeft;
|
||||
this.tilesOffEdge = squaresLeft - allWitnessed.Count; // squares left off the edge and unrevealed
|
||||
this.minTotalMines = minesLeft - this.tilesOffEdge; //we can't use so few mines that we can't fit the remainder elsewhere on the board
|
||||
this.maxTotalMines = minesLeft;
|
||||
|
||||
this.mineCountUpperCutoff = minesLeft;
|
||||
this.mineCountLowerCutoff = minTotalMines;
|
||||
|
||||
information.Write("Tiles off edge " + tilesOffEdge);
|
||||
|
||||
//this.boxProb = []; // the probabilities end up here
|
||||
|
||||
// generate a BoxWitness for each witness tile and also create a list of pruned witnesses for the brute force search
|
||||
int pruned = 0;
|
||||
foreach (SolverTile wit in allWitnesses) {
|
||||
|
||||
BoxWitness boxWit = new BoxWitness(information, wit);
|
||||
|
||||
// if the witness is a duplicate then don't store it
|
||||
bool duplicate = false;
|
||||
foreach (BoxWitness w in this.boxWitnesses) {
|
||||
|
||||
if (w.Equivalent(boxWit)) {
|
||||
//if (boardState.getWitnessValue(w) - boardState.countAdjacentConfirmedFlags(w) != boardState.getWitnessValue(wit) - boardState.countAdjacentConfirmedFlags(wit)) {
|
||||
// boardState.display(w.display() + " and " + wit.display() + " share unrevealed squares but have different mine totals!");
|
||||
// validWeb = false;
|
||||
//}
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!duplicate) {
|
||||
this.prunedWitnesses.Add(boxWit);
|
||||
} else {
|
||||
pruned++;
|
||||
}
|
||||
this.boxWitnesses.Add(boxWit); // all witnesses are needed for the probability engine
|
||||
}
|
||||
information.Write("Pruned " + pruned + " witnesses as duplicates");
|
||||
information.Write("There are " + this.boxWitnesses.Count + " Box witnesses");
|
||||
|
||||
// allocate each of the witnessed squares to a box
|
||||
int uid = 0;
|
||||
foreach (SolverTile tile in this.witnessed) {
|
||||
|
||||
// for each adjacent tile see if it is a witness
|
||||
int count = 0;
|
||||
//foreach (SolverTile adjTile in information.GetAdjacentTiles(tile)) {
|
||||
// if (information.GetWitnesses().Contains(adjTile)) {
|
||||
// count++;
|
||||
// }
|
||||
//}
|
||||
|
||||
// count how many adjacent witnesses the tile has
|
||||
foreach (SolverTile tile1 in allWitnesses) {
|
||||
if (tile.IsAdjacent(tile1)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
// see if the witnessed tile fits any existing boxes
|
||||
bool found = false;
|
||||
foreach (Box box in this.boxes) {
|
||||
|
||||
if (box.Fits(tile, count)) {
|
||||
box.Add(tile);
|
||||
boxLookup.Add(tile, box); // add this to the lookup
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if not found create a new box and store it
|
||||
if (!found) {
|
||||
Box box = new Box(this.boxWitnesses, tile, uid++);
|
||||
this.boxes.Add(box);
|
||||
boxLookup.Add(tile, box); // add this to the lookup
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// calculate the min and max mines for each box
|
||||
foreach (Box box in this.boxes) {
|
||||
box.Calculate(this.minesLeft);
|
||||
//console.log("Box " + box.tiles[0].asText() + " has min mines = " + box.minMines + " and max mines = " + box.maxMines);
|
||||
}
|
||||
|
||||
// Report how many boxes each witness is adjacent to
|
||||
foreach (BoxWitness boxWit in this.boxWitnesses) {
|
||||
information.Write("Witness " + boxWit.GetTile().AsText() + " is adjacent to " + boxWit.GetBoxes().Count + " boxes and has " + boxWit.GetMinesToFind() + " mines to find");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// calculate a probability for each un-revealed tile on the board
|
||||
public void Process() {
|
||||
|
||||
this.mask = new bool[this.boxes.Count];
|
||||
|
||||
// create an initial solution of no mines anywhere
|
||||
ProbabilityLine held = new ProbabilityLine(this.boxes.Count);
|
||||
held.SetSolutionCount(1);
|
||||
this.heldProbs.Add(held);
|
||||
|
||||
// add an empty probability line to get us started
|
||||
this.workingProbs.Add(new ProbabilityLine(this.boxes.Count));
|
||||
|
||||
NextWitness nextWitness = FindFirstWitness();
|
||||
|
||||
while (nextWitness != null) {
|
||||
|
||||
// mark the new boxes as processed - which they will be soon
|
||||
foreach (Box box in nextWitness.GetNewBoxes()) {
|
||||
this.mask[box.GetUID()] = true;
|
||||
}
|
||||
|
||||
this.workingProbs = MergeProbabilities(nextWitness);
|
||||
|
||||
nextWitness = FindNextWitness(nextWitness);
|
||||
|
||||
}
|
||||
|
||||
CalculateBoxProbabilities();
|
||||
|
||||
}
|
||||
|
||||
|
||||
// take the next witness details and merge them into the currently held details
|
||||
private List<ProbabilityLine> MergeProbabilities(NextWitness nw) {
|
||||
|
||||
List<ProbabilityLine> newProbs = new List<ProbabilityLine>();
|
||||
|
||||
foreach (ProbabilityLine pl in this.workingProbs) {
|
||||
|
||||
int missingMines = nw.GetBoxWitness().GetMinesToFind() - (int)CountPlacedMines(pl, nw);
|
||||
|
||||
if (missingMines < 0) {
|
||||
//console.log("Missing mines < 0 ==> ignoring line");
|
||||
// too many mines placed around this witness previously, so this probability can't be valid
|
||||
} else if (missingMines == 0) {
|
||||
//console.log("Missing mines = 0 ==> keeping line as is");
|
||||
newProbs.Add(pl); // witness already exactly satisfied, so nothing to do
|
||||
} else if (nw.GetNewBoxes().Count == 0) {
|
||||
//console.log("new boxes = 0 ==> ignoring line since nowhere for mines to go");
|
||||
// nowhere to put the new mines, so this probability can't be valid
|
||||
} else {
|
||||
|
||||
List<ProbabilityLine> result = DistributeMissingMines(pl, nw, missingMines, 0);
|
||||
newProbs.AddRange(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//if (newProbs.length == 0) {
|
||||
// console.log("Returning no lines from merge probability !!");
|
||||
//}
|
||||
|
||||
return newProbs;
|
||||
|
||||
}
|
||||
|
||||
// counts the number of mines already placed
|
||||
private BigInteger CountPlacedMines(ProbabilityLine pl, NextWitness nw) {
|
||||
|
||||
BigInteger result = 0;
|
||||
|
||||
foreach (Box b in nw.GetOldBoxes()) {
|
||||
|
||||
result = result + pl.GetMineBoxCount(b.GetUID());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// this is used to recursively place the missing Mines into the available boxes for the probability line
|
||||
private List<ProbabilityLine> DistributeMissingMines(ProbabilityLine pl, NextWitness nw, int missingMines, int index) {
|
||||
|
||||
//console.log("Distributing " + missingMines + " missing mines to box " + nw.newBoxes[index].uid);
|
||||
|
||||
this.recursions++;
|
||||
if (this.recursions % 100000 == 0) {
|
||||
information.Write("Solution Counter recursision = " + recursions);
|
||||
}
|
||||
|
||||
List<ProbabilityLine> result = new List<ProbabilityLine>();
|
||||
|
||||
// if there is only one box left to put the missing mines we have reach the end of this branch of recursion
|
||||
if (nw.GetNewBoxes().Count - index == 1) {
|
||||
// if there are too many for this box then the probability can't be valid
|
||||
if (nw.GetNewBoxes()[index].GetMaxMines() < missingMines) {
|
||||
//console.log("Abandon (1)");
|
||||
return result;
|
||||
}
|
||||
// if there are too few for this box then the probability can't be valid
|
||||
if (nw.GetNewBoxes()[index].GetMinMines() > missingMines) {
|
||||
//console.log("Abandon (2)");
|
||||
return result;
|
||||
}
|
||||
// if there are too many for this game then the probability can't be valid
|
||||
if (pl.GetMineCount() + missingMines > this.maxTotalMines) {
|
||||
//console.log("Abandon (3)");
|
||||
return result;
|
||||
}
|
||||
|
||||
// otherwise place the mines in the probability line
|
||||
pl.SetMineBoxCount(nw.GetNewBoxes()[index].GetUID(), new BigInteger(missingMines));
|
||||
|
||||
pl.SetMineCount(pl.GetMineCount() + missingMines);
|
||||
result.Add(pl);
|
||||
//console.log("Distribute missing mines line after " + pl.mineBoxCount);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// this is the recursion
|
||||
int maxToPlace = Math.Min(nw.GetNewBoxes()[index].GetMaxMines(), missingMines);
|
||||
|
||||
for (int i = nw.GetNewBoxes()[index].GetMinMines(); i <= maxToPlace; i++) {
|
||||
|
||||
ProbabilityLine npl = ExtendProbabilityLine(pl, nw.GetNewBoxes()[index], i);
|
||||
|
||||
List<ProbabilityLine> r1 = DistributeMissingMines(npl, nw, missingMines - i, index + 1);
|
||||
result.AddRange(r1);
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
// create a new probability line by taking the old and adding the mines to the new Box
|
||||
private ProbabilityLine ExtendProbabilityLine(ProbabilityLine pl, Box newBox, int mines) {
|
||||
|
||||
//console.log("Extended probability line: Adding " + mines + " mines to box " + newBox.uid);
|
||||
//console.log("Extended probability line before" + pl.mineBoxCount);
|
||||
|
||||
ProbabilityLine result = new ProbabilityLine(this.boxes.Count);
|
||||
|
||||
result.SetMineCount(pl.GetMineCount() + mines);
|
||||
|
||||
result.CopyMineBoxCount(pl);
|
||||
|
||||
result.SetMineBoxCount(newBox.GetUID(), new BigInteger(mines));
|
||||
|
||||
//console.log("Extended probability line after " + result.mineBoxCount);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// here we store the information we have found about this independent edge for later use
|
||||
private void StoreProbabilities() {
|
||||
|
||||
List<ProbabilityLine> crunched = CrunchByMineCount(this.workingProbs);
|
||||
|
||||
//Console.WriteLine("Edge has " + crunched.Count + " probability lines after consolidation");
|
||||
|
||||
edgeStore.Add(new EdgeStore(crunched, this.mask)); // store the existing data
|
||||
|
||||
workingProbs.Clear(); // and get a new list for the next independent edge
|
||||
|
||||
this.workingProbs.Add(new ProbabilityLine(this.boxes.Count)); // add a new starter probability line
|
||||
|
||||
this.mask = new bool[boxes.Count];
|
||||
|
||||
}
|
||||
|
||||
// this combines newly generated probabilities with ones we have already stored from other independent sets of witnesses
|
||||
private void CombineProbabilities() {
|
||||
|
||||
List<ProbabilityLine> result = new List<ProbabilityLine>();
|
||||
|
||||
if (this.workingProbs.Count == 0) {
|
||||
information.Write("working probabilites list is empty!!");
|
||||
return;
|
||||
}
|
||||
|
||||
// see if we can find a common divisor
|
||||
BigInteger hcd = workingProbs[0].GetSolutionCount();
|
||||
foreach (ProbabilityLine pl in workingProbs) {
|
||||
hcd = BigInteger.GreatestCommonDivisor(hcd, pl.GetSolutionCount());
|
||||
}
|
||||
foreach (ProbabilityLine pl in heldProbs) {
|
||||
hcd = BigInteger.GreatestCommonDivisor(hcd, pl.GetSolutionCount());
|
||||
}
|
||||
information.Write("Greatest Common Divisor is " + hcd);
|
||||
|
||||
solutionCountMultiplier = solutionCountMultiplier * hcd;
|
||||
|
||||
int mineCountMin = workingProbs[0].GetMineCount() + heldProbs[0].GetMineCount();
|
||||
|
||||
// shrink the window
|
||||
edgeMinesMinLeft = edgeMinesMinLeft - workingProbs[0].GetMineCount();
|
||||
edgeMinesMaxLeft = edgeMinesMaxLeft - workingProbs[workingProbs.Count - 1].GetMineCount();
|
||||
|
||||
foreach (ProbabilityLine pl in workingProbs) {
|
||||
|
||||
BigInteger plSolCount = pl.GetSolutionCount() / hcd;
|
||||
|
||||
foreach (ProbabilityLine epl in heldProbs) {
|
||||
|
||||
// if the mine count can never reach the lower cuttoff then ignore it
|
||||
if (pl.GetMineCount() + epl.GetMineCount() + edgeMinesMaxLeft < this.mineCountLowerCutoff) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if the mine count will always be pushed beyonf the upper cuttoff then ignore it
|
||||
if (pl.GetMineCount() + epl.GetMineCount() + edgeMinesMinLeft > this.mineCountUpperCutoff) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ProbabilityLine newpl = new ProbabilityLine(this.boxes.Count);
|
||||
|
||||
newpl.SetMineCount(pl.GetMineCount() + epl.GetMineCount());
|
||||
|
||||
BigInteger eplSolCount = epl.GetSolutionCount() / hcd;
|
||||
|
||||
newpl.SetSolutionCount(pl.GetSolutionCount() * eplSolCount);
|
||||
|
||||
for (int k = 0; k < this.boxes.Count; k++) {
|
||||
|
||||
BigInteger w1 = pl.GetMineBoxCount(k) * eplSolCount;
|
||||
BigInteger w2 = epl.GetMineBoxCount(k) * plSolCount;
|
||||
newpl.SetMineBoxCount(k, w1 + w2);
|
||||
|
||||
}
|
||||
|
||||
result.Add(newpl);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//Console.WriteLine("Solution multiplier is " + solutionCountMultiplier);
|
||||
|
||||
this.heldProbs.Clear();
|
||||
|
||||
// if result is empty this is an impossible position
|
||||
if (result.Count == 0) {
|
||||
Console.WriteLine("Impossible position encountered");
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.Count == 1) {
|
||||
heldProbs.AddRange(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// sort into mine order
|
||||
result.Sort();
|
||||
|
||||
// and combine them into a single probability line for each mine count
|
||||
int mc = result[0].GetMineCount();
|
||||
ProbabilityLine npl = new ProbabilityLine(this.boxes.Count);
|
||||
npl.SetMineCount(mc);
|
||||
|
||||
int startMC = mc;
|
||||
|
||||
foreach (ProbabilityLine pl in result) {
|
||||
|
||||
if (pl.GetMineCount() != mc) {
|
||||
|
||||
this.heldProbs.Add(npl);
|
||||
mc = pl.GetMineCount();
|
||||
npl = new ProbabilityLine(this.boxes.Count);
|
||||
npl.SetMineCount(mc);
|
||||
}
|
||||
npl.SetSolutionCount(npl.GetSolutionCount() + pl.GetSolutionCount());
|
||||
|
||||
for (int j = 0; j < this.boxes.Count; j++) {
|
||||
npl.SetMineBoxCount(j, npl.GetMineBoxCount(j) + pl.GetMineBoxCount(j));
|
||||
}
|
||||
}
|
||||
|
||||
this.heldProbs.Add(npl);
|
||||
|
||||
}
|
||||
|
||||
private List<ProbabilityLine> CrunchByMineCount(List<ProbabilityLine> target) {
|
||||
|
||||
//if (target.Count == 0) {
|
||||
// return target;
|
||||
//}
|
||||
|
||||
// sort the solutions by number of mines
|
||||
target.Sort();
|
||||
|
||||
List<ProbabilityLine> result = new List<ProbabilityLine>();
|
||||
|
||||
int mc = target[0].GetMineCount();
|
||||
ProbabilityLine npl = new ProbabilityLine(this.boxes.Count);
|
||||
npl.SetMineCount(mc);
|
||||
|
||||
foreach (ProbabilityLine pl in target) {
|
||||
|
||||
if (pl.GetMineCount() != mc) {
|
||||
result.Add(npl);
|
||||
mc = pl.GetMineCount();
|
||||
npl = new ProbabilityLine(this.boxes.Count);
|
||||
npl.SetMineCount(mc);
|
||||
}
|
||||
MergeLineProbabilities(npl, pl);
|
||||
}
|
||||
|
||||
//if (npl.GetMineCount() >= minTotalMines) {
|
||||
result.Add(npl);
|
||||
//}
|
||||
//information.Write("Probability line has " + npl.GetMineCount() + " mines");
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
// calculate how many ways this solution can be generated and roll them into one
|
||||
private void MergeLineProbabilities(ProbabilityLine npl, ProbabilityLine pl) {
|
||||
|
||||
BigInteger solutions = 1;
|
||||
for (int i = 0; i < this.boxes.Count; i++) {
|
||||
solutions = solutions * (BigInteger)SMALL_COMBINATIONS[this.boxes[i].GetTiles().Count][(int)pl.GetMineBoxCount(i)];
|
||||
}
|
||||
|
||||
npl.SetSolutionCount(npl.GetSolutionCount() + solutions);
|
||||
|
||||
for (int i = 0; i < this.boxes.Count; i++) {
|
||||
if (this.mask[i]) { // if this box has been involved in this solution - if we don't do this the hash gets corrupted by boxes = 0 mines because they weren't part of this edge
|
||||
npl.SetMineBoxCount(i, npl.GetMineBoxCount(i) + pl.GetMineBoxCount(i) * solutions);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// return any witness which hasn't been processed
|
||||
private NextWitness FindFirstWitness() {
|
||||
|
||||
BoxWitness excluded = null;
|
||||
foreach (BoxWitness boxWit in this.boxWitnesses) {
|
||||
if (!boxWit.IsProcessed()) {
|
||||
return new NextWitness(boxWit);
|
||||
} else if (!boxWit.IsProcessed()) {
|
||||
excluded = boxWit;
|
||||
}
|
||||
}
|
||||
if (excluded != null) {
|
||||
return new NextWitness(excluded);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// look for the next witness to process
|
||||
private NextWitness FindNextWitness(NextWitness prevWitness) {
|
||||
|
||||
// flag the last set of details as processed
|
||||
prevWitness.GetBoxWitness().SetProcessed(true);
|
||||
|
||||
foreach (Box newBox in prevWitness.GetNewBoxes()) {
|
||||
newBox.SetProcessed(true);
|
||||
}
|
||||
|
||||
int bestTodo = 99999;
|
||||
BoxWitness bestWitness = null;
|
||||
|
||||
// and find a witness which is on the boundary of what has already been processed
|
||||
foreach (Box b in this.boxes) {
|
||||
if (b.IsProcessed()) {
|
||||
foreach (BoxWitness w in b.GetBoxWitnesses()) {
|
||||
if (!w.IsProcessed()) {
|
||||
int todo = 0;
|
||||
foreach (Box b1 in w.GetBoxes()) {
|
||||
if (!b1.IsProcessed()) {
|
||||
todo++;
|
||||
}
|
||||
}
|
||||
if (todo == 0) { // prioritise the witnesses which have the least boxes left to process
|
||||
return new NextWitness(w);
|
||||
} else if (todo < bestTodo) {
|
||||
bestTodo = todo;
|
||||
bestWitness = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestWitness != null) {
|
||||
return new NextWitness(bestWitness);
|
||||
} else {
|
||||
information.Write("Ending independent edge");
|
||||
}
|
||||
|
||||
//independentGroups++;
|
||||
|
||||
// since we have calculated all the mines in an independent set of witnesses we can crunch them down and store them for later
|
||||
|
||||
// store the details for this edge
|
||||
StoreProbabilities();
|
||||
|
||||
|
||||
// get an unprocessed witness
|
||||
NextWitness nw = FindFirstWitness();
|
||||
if (nw != null) {
|
||||
information.Write("Starting a new independent edge");
|
||||
}
|
||||
|
||||
// If there is nothing else to process then either do the local clears or calculate the probabilities
|
||||
if (nw == null) {
|
||||
|
||||
edgeStore.Sort(EdgeStore.SortByLineCount);
|
||||
|
||||
AnalyseAllEdges();
|
||||
|
||||
long start = DateTime.Now.Ticks;
|
||||
foreach (EdgeStore edgeDetails in edgeStore) {
|
||||
workingProbs = edgeDetails.data;
|
||||
CombineProbabilities();
|
||||
}
|
||||
information.Write("Combined all edges in " + (DateTime.Now.Ticks - start) + " ticks");
|
||||
|
||||
}
|
||||
|
||||
// return the next witness to process
|
||||
return nw;
|
||||
|
||||
}
|
||||
|
||||
// take a look at what edges we have and show some information - trying to get some ideas on how we can get faster good guesses
|
||||
private void AnalyseAllEdges() {
|
||||
|
||||
//Console.WriteLine("Number of tiles off the edge " + tilesOffEdge);
|
||||
//Console.WriteLine("Number of mines to find " + minesLeft);
|
||||
|
||||
this.edgeMinesMin = 0;
|
||||
this.edgeMinesMax = 0;
|
||||
|
||||
foreach (EdgeStore edge in edgeStore) {
|
||||
int edgeMinMines = edge.data[0].GetMineCount();
|
||||
int edgeMaxMines = edge.data[edge.data.Count - 1].GetMineCount();
|
||||
|
||||
edgeMinesMin = edgeMinesMin + edgeMinMines;
|
||||
edgeMinesMax = edgeMinesMax + edgeMaxMines;
|
||||
}
|
||||
|
||||
information.Write("Min mines on all edges " + edgeMinesMin + ", max " + edgeMinesMax);
|
||||
this.edgeMinesMaxLeft = this.edgeMinesMax; // these values are used in the merge logic to reduce the number of lines need to keep
|
||||
this.edgeMinesMinLeft = this.edgeMinesMin;
|
||||
|
||||
this.mineCountLowerCutoff = this.edgeMinesMin;
|
||||
this.mineCountUpperCutoff = Math.Min(this.edgeMinesMax, this.minesLeft); // can't have more mines than are left
|
||||
|
||||
// comment this out when doing large board analysis
|
||||
return;
|
||||
|
||||
// the code below reduces the range of mine count values to just be the 'significant' range
|
||||
|
||||
List<ProbabilityLine> store = new List<ProbabilityLine>();
|
||||
List<ProbabilityLine> store1 = new List<ProbabilityLine>();
|
||||
|
||||
ProbabilityLine init = new ProbabilityLine(0);
|
||||
init.SetSolutionCount(1);
|
||||
|
||||
store.Add(init);
|
||||
|
||||
// combine all the edges to determine the relative weights of the mine count
|
||||
foreach (EdgeStore edgeDetails in edgeStore) {
|
||||
|
||||
foreach (ProbabilityLine pl in edgeDetails.data) {
|
||||
|
||||
BigInteger plSolCount = pl.GetSolutionCount();
|
||||
|
||||
foreach (ProbabilityLine epl in store) {
|
||||
|
||||
if (pl.GetMineCount() + epl.GetMineCount() <= this.maxTotalMines) {
|
||||
|
||||
ProbabilityLine newpl = new ProbabilityLine(0);
|
||||
|
||||
newpl.SetMineCount(pl.GetMineCount() + epl.GetMineCount());
|
||||
|
||||
BigInteger eplSolCount = epl.GetSolutionCount();
|
||||
|
||||
newpl.SetSolutionCount(pl.GetSolutionCount() * eplSolCount);
|
||||
|
||||
store1.Add(newpl);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store.Clear();
|
||||
|
||||
// sort into mine order
|
||||
store1.Sort();
|
||||
|
||||
int mc = store1[0].GetMineCount();
|
||||
ProbabilityLine npl = new ProbabilityLine(0);
|
||||
npl.SetMineCount(mc);
|
||||
|
||||
foreach (ProbabilityLine pl in store1) {
|
||||
|
||||
if (pl.GetMineCount() != mc) {
|
||||
|
||||
store.Add(npl);
|
||||
mc = pl.GetMineCount();
|
||||
npl = new ProbabilityLine(0);
|
||||
npl.SetMineCount(mc);
|
||||
}
|
||||
npl.SetSolutionCount(npl.GetSolutionCount() + pl.GetSolutionCount());
|
||||
|
||||
}
|
||||
|
||||
store.Add(npl);
|
||||
store1.Clear();
|
||||
}
|
||||
|
||||
BigInteger total = 0;
|
||||
int mineValues = 0;
|
||||
foreach (ProbabilityLine pl in store) {
|
||||
if (pl.GetMineCount() >= this.minTotalMines) { // if the mine count for this solution is less than the minimum it can't be valid
|
||||
BigInteger mult = SolverMain.Calculate(this.minesLeft - pl.GetMineCount(), this.tilesOffEdge); //# of ways the rest of the board can be formed
|
||||
total = total + mult * pl.GetSolutionCount();
|
||||
mineValues++;
|
||||
}
|
||||
}
|
||||
|
||||
//this.mineCountLowerCutoff = this.edgeMinesMin;
|
||||
//this.mineCountUpperCutoff = Math.Min(this.edgeMinesMax, this.minesLeft); // can't have more mines than are left
|
||||
BigInteger soFar = 0;
|
||||
foreach (ProbabilityLine pl in store) {
|
||||
if (pl.GetMineCount() >= this.minTotalMines) { // if the mine count for this solution is less than the minimum it can't be valid
|
||||
BigInteger mult = SolverMain.Calculate(this.minesLeft - pl.GetMineCount(), this.tilesOffEdge); //# of ways the rest of the board can be formed
|
||||
soFar = soFar + mult * pl.GetSolutionCount();
|
||||
double perc = Combination.DivideBigIntegerToDouble(soFar, total, 6) * 100;
|
||||
//Console.WriteLine("Mine count " + pl.GetMineCount() + " has solution count " + pl.GetSolutionCount() + " multiplier " + mult + " running % " + perc);
|
||||
//Console.WriteLine("Mine count " + pl.GetMineCount() + " has solution count " + pl.GetSolutionCount() + " has running % " + perc);
|
||||
|
||||
if (mineValues > 30 && perc < 2.5) {
|
||||
this.mineCountLowerCutoff = pl.GetMineCount();
|
||||
}
|
||||
|
||||
if (mineValues > 30 && perc > 97.5) {
|
||||
this.mineCountUpperCutoff = pl.GetMineCount();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
information.Write("Significant range " + this.mineCountLowerCutoff + " - " + this.mineCountUpperCutoff);
|
||||
//this.edgeMinesMaxLeft = this.edgeMinesMax;
|
||||
//this.edgeMinesMinLeft = this.edgeMinesMin;
|
||||
|
||||
|
||||
return;
|
||||
|
||||
// below here are experimental ideas on getting a good guess on very large boards
|
||||
|
||||
int midRangeAllMines = (this.mineCountLowerCutoff + this.mineCountUpperCutoff) / 2;
|
||||
BigInteger[] tally = new BigInteger[boxes.Count];
|
||||
double[] probability = new double[boxes.Count];
|
||||
|
||||
|
||||
foreach (EdgeStore edgeDetails in edgeStore) {
|
||||
|
||||
int sizeRangeEdgeMines = (edgeDetails.data[edgeDetails.data.Count - 1].GetMineCount() - edgeDetails.data[0].GetMineCount()) / 2;
|
||||
|
||||
int start = (this.mineCountLowerCutoff - edgeDetails.data[0].GetMineCount() + this.mineCountUpperCutoff - edgeDetails.data[edgeDetails.data.Count - 1].GetMineCount()) / 2;
|
||||
//int start = midRangeAllMines - sizeRangeEdgeMines;
|
||||
|
||||
//BigInteger mult = Combination.Calculate(this.minesLeft - start, this.tilesOffEdge);
|
||||
BigInteger totalTally = 0;
|
||||
|
||||
foreach (ProbabilityLine pl in edgeDetails.data) {
|
||||
|
||||
BigInteger mult = Combination.Calculate(this.minesLeft - start - pl.GetMineCount(), this.tilesOffEdge);
|
||||
totalTally += mult * pl.GetSolutionCount();
|
||||
for (int i = 0; i < boxes.Count; i++) {
|
||||
if (edgeDetails.mask[i]) {
|
||||
BigInteger work = pl.GetMineBoxCount(i) * mult;
|
||||
tally[i] += work;
|
||||
}
|
||||
}
|
||||
|
||||
//mult = mult * (this.tilesOffEdge - start) / (start + 1);
|
||||
//start++;
|
||||
|
||||
}
|
||||
|
||||
for (int i = 0; i < boxes.Count; i++) {
|
||||
if (edgeDetails.mask[i]) {
|
||||
probability[i] = Combination.DivideBigIntegerToDouble(tally[i], totalTally, 6) / boxes[i].GetTiles().Count;
|
||||
}
|
||||
}
|
||||
|
||||
int minIndex = -1;
|
||||
for (int i = 0; i < boxes.Count; i++) {
|
||||
if (edgeDetails.mask[i]) {
|
||||
if (minIndex == -1 || probability[i] < probability[minIndex]) {
|
||||
minIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (minIndex != -1) {
|
||||
information.Write("Best guess is " + boxes[minIndex].GetTiles()[0].AsText() + " with " + (1 - probability[minIndex]));
|
||||
} else {
|
||||
information.Write("No Guess found");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// here we expand the localised solution to one across the whole board and
|
||||
// sum them together to create a definitive probability for each box
|
||||
private void CalculateBoxProbabilities() {
|
||||
|
||||
//long start = DateTime.Now.Ticks;
|
||||
|
||||
if (truncatedProbs) {
|
||||
Console.WriteLine("probability line combining was truncated");
|
||||
}
|
||||
|
||||
information.Write("Solution count multiplier is " + this.solutionCountMultiplier);
|
||||
|
||||
BigInteger[] tally = new BigInteger[this.boxes.Count];
|
||||
|
||||
// total game tally
|
||||
BigInteger totalTally = 0;
|
||||
|
||||
// outside a box tally
|
||||
BigInteger outsideTally = 0;
|
||||
|
||||
//console.log("There are " + this.heldProbs.length + " different mine counts on the edge");
|
||||
|
||||
bool[] emptyBox = new bool[boxes.Count];
|
||||
for (int i = 0; i < emptyBox.Length; i++) {
|
||||
emptyBox[i] = true;
|
||||
}
|
||||
|
||||
int linesProcessed = 0;
|
||||
// calculate how many mines
|
||||
foreach (ProbabilityLine pl in this.heldProbs) {
|
||||
|
||||
//console.log("Mine count is " + pl.mineCount + " with solution count " + pl.solutionCount + " mineBoxCount = " + pl.mineBoxCount);
|
||||
|
||||
if (pl.GetMineCount() >= this.minTotalMines) { // if the mine count for this solution is less than the minimum it can't be valid
|
||||
|
||||
linesProcessed++;
|
||||
|
||||
//console.log("Mines left " + this.minesLeft + " mines on PL " + pl.mineCount + " squares left = " + this.squaresLeft);
|
||||
BigInteger mult = SolverMain.Calculate(this.minesLeft - pl.GetMineCount(), this.tilesOffEdge); //# of ways the rest of the board can be formed
|
||||
|
||||
information.Write("Mines in solution " + pl.GetMineCount() + " solution count " + pl.GetSolutionCount() + " multiplier " + mult);
|
||||
|
||||
outsideTally = outsideTally + mult * new BigInteger(this.minesLeft - pl.GetMineCount()) * (pl.GetSolutionCount());
|
||||
|
||||
// this is all the possible ways the mines can be placed across the whole game
|
||||
totalTally = totalTally + mult * (pl.GetSolutionCount());
|
||||
|
||||
for (int i = 0; i < emptyBox.Length; i++) {
|
||||
if (pl.GetMineBoxCount(i) != 0) {
|
||||
emptyBox[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// determine how many clear squares there are
|
||||
if (totalTally > 0) {
|
||||
for (int i = 0; i < emptyBox.Length; i++) {
|
||||
if (emptyBox[i]) {
|
||||
clearCount = clearCount + boxes[i].GetTiles().Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.finalSolutionCount = totalTally * solutionCountMultiplier;
|
||||
|
||||
information.Write("Game has " + this.finalSolutionCount + " candidate solutions");
|
||||
|
||||
}
|
||||
|
||||
public BigInteger GetSolutionCount() {
|
||||
return this.finalSolutionCount;
|
||||
}
|
||||
|
||||
public int getClearCount() {
|
||||
return this.clearCount;
|
||||
}
|
||||
|
||||
public int GetSolutionCountMagnitude() {
|
||||
return (int)Math.Floor(BigInteger.Log10(this.finalSolutionCount));
|
||||
}
|
||||
|
||||
public SolverInfo GetSolverInfo() {
|
||||
return this.information;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
23
info/Minesweeper2/MinesweeperSolver/SolverAction.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using static MinesweeperControl.MinesweeperGame;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
public class SolverAction : GameAction, IComparable<SolverAction> {
|
||||
|
||||
public readonly double safeProbability;
|
||||
|
||||
public readonly bool isDead;
|
||||
//public readonly bool isExcluded;
|
||||
|
||||
public SolverAction(SolverTile tile, ActionType action, double safeprob) : base(tile.x, tile.y, action) {
|
||||
|
||||
this.safeProbability = safeprob;
|
||||
this.isDead = tile.IsDead();
|
||||
//this.isExcluded = tile.IsExcluded();
|
||||
}
|
||||
|
||||
public int CompareTo(SolverAction other) {
|
||||
return safeProbability.CompareTo(other.safeProbability);
|
||||
}
|
||||
}
|
||||
}
|
22
info/Minesweeper2/MinesweeperSolver/SolverActionHeader.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
|
||||
class SolverActionHeader {
|
||||
|
||||
public readonly IList<SolverAction> solverActions;
|
||||
public readonly IList<SolverAction> deadActions;
|
||||
|
||||
public SolverActionHeader() {
|
||||
List<SolverAction> empty = new List<SolverAction>();
|
||||
this.solverActions = empty.AsReadOnly();
|
||||
this.deadActions = empty.AsReadOnly();
|
||||
}
|
||||
|
||||
public SolverActionHeader(List<SolverAction> solverActions, List<SolverAction> deadActions) {
|
||||
this.solverActions = solverActions.AsReadOnly();
|
||||
this.deadActions = deadActions.AsReadOnly();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
377
info/Minesweeper2/MinesweeperSolver/SolverInfo.cs
Normal file
@ -0,0 +1,377 @@
|
||||
using MinesweeperControl;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static MinesweeperControl.MinesweeperGame;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
|
||||
public class SolverInfo {
|
||||
|
||||
private static readonly bool EXCLUDE_ON = false;
|
||||
|
||||
// contains information about what surrounds a tile
|
||||
public class AdjacentInfo {
|
||||
|
||||
public readonly int mines;
|
||||
public readonly int hidden;
|
||||
public readonly int excluded;
|
||||
|
||||
public AdjacentInfo(int mines, int hidden, int excluded) {
|
||||
this.mines = mines;
|
||||
this.hidden = hidden;
|
||||
this.excluded = excluded;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private readonly int key = new Random().Next();
|
||||
|
||||
public readonly GameDescription description;
|
||||
public readonly bool verbose;
|
||||
|
||||
private GameStatus gameStatus = GameStatus.NotStarted;
|
||||
private readonly SolverTile[,] tiles;
|
||||
private int tilesLeft;
|
||||
|
||||
private ProbabilityEngine probabilityEngine = null; // this is the probability engine from the last solver run
|
||||
private BruteForceAnalysis bfa = null; // this is the brute force analysis from the last solver run
|
||||
|
||||
private readonly HashSet<SolverTile> livingWitnesses = new HashSet<SolverTile>(20); // this is a set of tiles which are clear and have hidden neighbours.
|
||||
private readonly HashSet<SolverTile> knownMines; // this is a set of tiles which are known to be mines.
|
||||
private readonly HashSet<SolverTile> pendingClears = new HashSet<SolverTile>(); // this is a set of tiles which are clear and haven't yet been clicked.
|
||||
|
||||
|
||||
private readonly HashSet<SolverTile> deadTiles = new HashSet<SolverTile>(); // this is a set of tiles which are known to be dead.
|
||||
//private readonly HashSet<SolverTile> excludedWitnesses = new HashSet<SolverTile>(); // this is a set of witnesses which no longer influence the solving of the game.
|
||||
//private readonly HashSet<SolverTile> excludedTiles = new HashSet<SolverTile>(); // this is a set of tiles which no longer influence the solving of the game.
|
||||
//private int excludedMinesCount = 0; // number of mines which are with the exluded tiles area
|
||||
|
||||
|
||||
private readonly List<SolverTile> newClears = new List<SolverTile>();
|
||||
|
||||
public SolverInfo(GameDescription description) : this(description, false) {
|
||||
}
|
||||
|
||||
public SolverInfo(GameDescription description, bool verbose) {
|
||||
|
||||
this.description = description;
|
||||
this.verbose = verbose;
|
||||
|
||||
tiles = new SolverTile[description.width, description.height];
|
||||
|
||||
// populate the grid with solver tiles showing what we know so far ... which is nothing
|
||||
for (int x = 0; x < description.width; x++) {
|
||||
for (int y = 0; y < description.height; y++) {
|
||||
tiles[x, y] = new SolverTile(key, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
tilesLeft = description.height * description.width;
|
||||
|
||||
knownMines = new HashSet<SolverTile>(description.mines);
|
||||
|
||||
}
|
||||
|
||||
public void AddInformation(GameResult information) {
|
||||
|
||||
//long start = DateTime.Now.Ticks;
|
||||
|
||||
gameStatus = information.status;
|
||||
|
||||
this.probabilityEngine = null; // previous probability engine invalidated
|
||||
|
||||
newClears.Clear();
|
||||
|
||||
// the game results tell us when a tile is cleared, flagged or unflagged
|
||||
foreach (ActionResult ar in information.actionResults) {
|
||||
|
||||
if (ar.resultType == ResultType.Cleared) {
|
||||
tilesLeft--;
|
||||
tiles[ar.x, ar.y].SetValue(ar.value);
|
||||
|
||||
SolverTile tile = tiles[ar.x, ar.y];
|
||||
newClears.Add(tile);
|
||||
if (tile.IsDead()) {
|
||||
deadTiles.Remove(tile);
|
||||
}
|
||||
//if (tile.IsExcluded()) {
|
||||
// excludedTiles.Remove(tile);
|
||||
//}
|
||||
//pendingClears.Remove(tile);
|
||||
|
||||
} else if (ar.resultType == ResultType.Flagged) {
|
||||
tiles[ar.x, ar.y].SetFlagged(true);
|
||||
} else if (ar.resultType == ResultType.Hidden) {
|
||||
tiles[ar.x, ar.y].SetFlagged(false);
|
||||
} else if (ar.resultType == ResultType.Exploded) {
|
||||
SolverTile tile = tiles[ar.x, ar.y];
|
||||
tile.SetFlagged(false);
|
||||
MineFound(tile);
|
||||
this.bfa = null; // can't walk the bfa tree if we've trodden on a mine
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// find and mark tiles as exhausted
|
||||
//int removed = 0;
|
||||
//if (newClears.Count > 0) {
|
||||
|
||||
foreach (SolverTile tile in livingWitnesses) {
|
||||
|
||||
if (AdjacentTileInfo(tile).hidden == 0) {
|
||||
tile.SetExhausted();
|
||||
}
|
||||
}
|
||||
// remove all exhausted tiles from the list of witnesses
|
||||
livingWitnesses.RemoveWhere(x => x.IsExhausted());
|
||||
|
||||
//}
|
||||
|
||||
// add new witnesses which aren't exhausted
|
||||
foreach (SolverTile newTile in newClears) {
|
||||
AdjacentInfo adjInfo = AdjacentTileInfo(newTile);
|
||||
if (adjInfo.hidden != 0) {
|
||||
if (adjInfo.excluded != adjInfo.hidden) {
|
||||
livingWitnesses.Add(newTile);
|
||||
} else {
|
||||
//excludedWitnesses.Add(newTile);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//Write("There are " + livingWitnesses.Count + " living witnesses (" + removed + " deleted)");
|
||||
//Write("Adding Information to Solver took " + (DateTime.Now.Ticks - start) + " ticks");
|
||||
}
|
||||
|
||||
public int GetMinesLeft() {
|
||||
return description.mines - knownMines.Count; // mines left to find = mines in game - mines found
|
||||
}
|
||||
|
||||
public int GetTilesLeft() {
|
||||
return this.tilesLeft;
|
||||
}
|
||||
|
||||
// sets the tile as a mine, stores it for future use and returns true if it is currently flagged
|
||||
public bool MineFound(SolverTile tile) {
|
||||
|
||||
// if this is already known to be mine then nothing to do
|
||||
if (tile.IsMine()) {
|
||||
return tile.IsFlagged();
|
||||
}
|
||||
|
||||
tilesLeft--;
|
||||
|
||||
tile.SetAsMine(key);
|
||||
|
||||
knownMines.Add(tile);
|
||||
|
||||
if (tile.IsDead()) {
|
||||
deadTiles.Remove(tile); // remove the tile if it was on the dead list
|
||||
}
|
||||
//if (tile.IsExcluded()) {
|
||||
// excludedTiles.Remove(tile); // remove the tile if it was excluded
|
||||
// excludedMinesCount--;
|
||||
//}
|
||||
|
||||
return tile.IsFlagged();
|
||||
|
||||
}
|
||||
|
||||
public void SetTileToDead(SolverTile tile) {
|
||||
|
||||
if (tile.IsMine()) {
|
||||
Write("ERROR: Trying to set a mine tile to dead " + tile.AsText());
|
||||
return;
|
||||
}
|
||||
|
||||
tile.SetDead(key); // mark it
|
||||
deadTiles.Add(tile); // and add to the set
|
||||
|
||||
}
|
||||
|
||||
public HashSet<SolverTile> GetDeadTiles() {
|
||||
return deadTiles;
|
||||
}
|
||||
|
||||
/*
|
||||
public void ExcludeMines(int n) {
|
||||
|
||||
if (!EXCLUDE_ON) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.excludedMinesCount = this.excludedMinesCount + n;
|
||||
}
|
||||
|
||||
public int GetExcludedMineCount() {
|
||||
return this.excludedMinesCount;
|
||||
}
|
||||
|
||||
public void ExcludeTile(SolverTile tile) {
|
||||
|
||||
if (!EXCLUDE_ON) {
|
||||
return;
|
||||
}
|
||||
|
||||
tile.SetExcluded();
|
||||
excludedTiles.Add(tile);
|
||||
}
|
||||
|
||||
public HashSet<SolverTile> GetExcludedTiles() {
|
||||
return this.excludedTiles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move a tile from the list of witnesses to the list of excluded witnesses
|
||||
/// </summary>
|
||||
public void ExcludeWitness(SolverTile tile) {
|
||||
|
||||
if (!EXCLUDE_ON) {
|
||||
return;
|
||||
}
|
||||
|
||||
tile.SetExcluded();
|
||||
if (livingWitnesses.Remove(tile)) {
|
||||
excludedWitnesses.Add(tile);
|
||||
}
|
||||
}
|
||||
|
||||
public HashSet<SolverTile> GetExcludedWitnesses() {
|
||||
return this.excludedWitnesses;
|
||||
}
|
||||
*/
|
||||
|
||||
public void SetBruteForceAnalysis(BruteForceAnalysis bfa) {
|
||||
this.bfa = bfa;
|
||||
}
|
||||
|
||||
public BruteForceAnalysis GetBruteForceAnalysis() {
|
||||
return this.bfa;
|
||||
}
|
||||
|
||||
public void SetProbabilityEngine(ProbabilityEngine pe) {
|
||||
this.probabilityEngine = pe;
|
||||
}
|
||||
|
||||
// returns the probability the tile is safe if known. Or -1 otherwise.
|
||||
public double GetProbability(int x, int y) {
|
||||
|
||||
// if we are out of bounds then nothing to say
|
||||
if (x < 0 || x >= description.width || y < 0 || y >= description.height) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
SolverTile tile = tiles[x, y];
|
||||
|
||||
// an unflagged mine
|
||||
if (tile.IsMine() && !tile.IsFlagged()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// otherwise if revealed then nothing to say
|
||||
if (!tile.IsHidden()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
//if (tile.IsExcluded()) {
|
||||
// return -1;
|
||||
//}
|
||||
|
||||
if (probabilityEngine != null) {
|
||||
return probabilityEngine.GetProbability(tile);
|
||||
} else if (pendingClears.Contains(tile)) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//public bool IsTileDead(SolverTile tile) {
|
||||
// return deadTiles.Contains(tile);
|
||||
//}
|
||||
|
||||
// allow the gui to see if a tile is dead
|
||||
//public bool IsTileDead(int x, int y) {
|
||||
// return IsTileDead(tiles[x, y]);
|
||||
//}
|
||||
|
||||
// Adds the tile to a list of known clears for future use
|
||||
public void ClearFound(SolverTile tile) {
|
||||
pendingClears.Add(tile);
|
||||
}
|
||||
|
||||
// returns all the indices adjacent to this index
|
||||
public List<SolverTile> GetAdjacentTiles(SolverTile tile) {
|
||||
|
||||
// have we already calculated the adjacent tiles
|
||||
List<SolverTile> adjTiles = tile.GetAdjacentTiles();
|
||||
if (adjTiles != null) {
|
||||
return adjTiles;
|
||||
}
|
||||
|
||||
adjTiles = new List<SolverTile>();
|
||||
|
||||
int first_row = Math.Max(0, tile.y - 1);
|
||||
int last_row = Math.Min(description.height - 1, tile.y + 1);
|
||||
|
||||
int first_col = Math.Max(0, tile.x - 1);
|
||||
int last_col = Math.Min(description.width - 1, tile.x + 1);
|
||||
|
||||
for (int r = first_row; r <= last_row; r++) {
|
||||
for (int c = first_col; c <= last_col; c++) {
|
||||
if (r != tile.y || c != tile.x) {
|
||||
adjTiles.Add(tiles[c, r]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remember them for next time
|
||||
tile.SetAdjacentTiles(adjTiles);
|
||||
|
||||
return adjTiles;
|
||||
}
|
||||
|
||||
public AdjacentInfo AdjacentTileInfo(SolverTile tile) {
|
||||
|
||||
int hidden = 0;
|
||||
int mines = 0;
|
||||
int excluded = 0;
|
||||
foreach (SolverTile adjTile in GetAdjacentTiles(tile)) {
|
||||
if (adjTile.IsMine()) {
|
||||
mines++;
|
||||
} else if (adjTile.IsHidden() ) {
|
||||
hidden++;
|
||||
//if (adjTile.IsExcluded()) {
|
||||
// excluded++;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
return new AdjacentInfo(mines, hidden, excluded);
|
||||
}
|
||||
|
||||
public GameStatus GetGameStatus() {
|
||||
return gameStatus;
|
||||
}
|
||||
|
||||
public SolverTile GetTile(int x, int y) {
|
||||
return tiles[x, y];
|
||||
}
|
||||
|
||||
public HashSet<SolverTile> GetWitnesses() {
|
||||
return livingWitnesses;
|
||||
}
|
||||
|
||||
public HashSet<SolverTile> GetKnownMines() {
|
||||
return knownMines;
|
||||
}
|
||||
|
||||
public void Write(string text) {
|
||||
if (verbose) {
|
||||
Console.WriteLine(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
614
info/Minesweeper2/MinesweeperSolver/SolverMain.cs
Normal file
@ -0,0 +1,614 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using static MinesweeperControl.MinesweeperGame;
|
||||
using static MinesweeperSolver.SolverInfo;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
|
||||
|
||||
|
||||
class SolverMain {
|
||||
|
||||
public static readonly BigInteger PARALLEL_MINIMUM = new BigInteger(10000);
|
||||
public static readonly BigInteger MAX_BRUTE_FORCE_ITERATIONS = new BigInteger(10000000);
|
||||
|
||||
public const int BRUTE_FORCE_ANALYSIS_TREE_DEPTH = 20;
|
||||
public const int MAX_BFDA_SOLUTIONS = 400;
|
||||
public const int BRUTE_FORCE_ANALYSIS_MAX_NODES = 150000;
|
||||
public const bool PRUNE_BF_ANALYSIS = true;
|
||||
|
||||
private static readonly Binomial binomial = new Binomial(500000, 500);
|
||||
|
||||
public static SolverActionHeader FindActions(SolverInfo information) {
|
||||
|
||||
long time1 = DateTime.Now.Ticks;
|
||||
|
||||
List<SolverAction> actions ;
|
||||
|
||||
if (information.GetGameStatus() == GameStatus.Lost) {
|
||||
information.Write("Game has been lost already - no valid moves");
|
||||
return new SolverActionHeader();
|
||||
} else if (information.GetGameStatus() == GameStatus.Won) {
|
||||
information.Write("Game has been won already - no valid moves");
|
||||
return new SolverActionHeader();
|
||||
} else if (information.GetGameStatus() == GameStatus.NotStarted) {
|
||||
information.Write("Game has not yet started - currently unable to provide help");
|
||||
return new SolverActionHeader();
|
||||
}
|
||||
|
||||
// are we walking down a brute force deep analysis tree?
|
||||
BruteForceAnalysis lastBfa = information.GetBruteForceAnalysis();
|
||||
if (lastBfa != null) { // yes
|
||||
SolverTile expectedMove = lastBfa.GetExpectedMove();
|
||||
if (expectedMove != null && expectedMove.IsHidden()) { // the expected move wasn't played !
|
||||
information.Write("The expected Brute Force Analysis move " + expectedMove.AsText() + " wasn't played");
|
||||
information.SetBruteForceAnalysis(null);
|
||||
} else {
|
||||
SolverAction move = lastBfa.GetNextMove();
|
||||
if (move != null) {
|
||||
information.Write("Next Brute Force Deep Analysis move is " + move.AsText());
|
||||
actions = new List<SolverAction>(1);
|
||||
actions.Add(move);
|
||||
return BuildActionHeader(information, actions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
actions = FindTrivialActions(information);
|
||||
|
||||
long time2 = DateTime.Now.Ticks;
|
||||
|
||||
information.Write("Finding Trivial Actions took " + (time2 - time1) + " ticks");
|
||||
|
||||
// if we have some actions from the trivial search use them
|
||||
if (actions.Count > 0) {
|
||||
return BuildActionHeader(information, actions);
|
||||
}
|
||||
|
||||
|
||||
if (information.GetTilesLeft() == information.GetDeadTiles().Count) {
|
||||
information.Write("All tiles remaining are dead");
|
||||
|
||||
// when all the tiles are dead return the first one (they are all equally good or bad)
|
||||
foreach (SolverTile guess in information.GetDeadTiles()) {
|
||||
actions.Add(new SolverAction(guess, ActionType.Clear, 0.5)); // not all 0.5 safe though !!
|
||||
return BuildActionHeader(information, actions);
|
||||
}
|
||||
}
|
||||
|
||||
// get all the witnessed tiles
|
||||
|
||||
List<SolverTile> witnesses = new List<SolverTile>(information.GetWitnesses());
|
||||
HashSet<SolverTile> witnessedSet = new HashSet<SolverTile>();
|
||||
|
||||
foreach (SolverTile witness in witnesses) {
|
||||
|
||||
foreach(SolverTile adjTile in information.GetAdjacentTiles(witness)) {
|
||||
|
||||
if (adjTile.IsHidden()) {
|
||||
witnessedSet.Add(adjTile);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
List<SolverTile> witnessed = new List<SolverTile>(witnessedSet);
|
||||
int livingTilesLeft = information.GetTilesLeft();
|
||||
int livingMinesLeft = information.GetMinesLeft();
|
||||
int offEdgeTilesLeft = information.GetTilesLeft() - witnessed.Count;
|
||||
|
||||
//information.Write("Excluded tiles " + information.GetExcludedTiles().Count + " out of " + information.GetTilesLeft());
|
||||
//information.Write("Excluded witnesses " + information.GetExcludedWitnesses().Count);
|
||||
//information.Write("Excluded mines " + information.GetExcludedMineCount() + " out of " + information.GetMinesLeft());
|
||||
|
||||
// if there are no living mines but some living tiles then the living tiles can be cleared
|
||||
if (livingMinesLeft == 0 && livingTilesLeft > 0) {
|
||||
information.Write("There are no living mines left - all living tiles must be clearable");
|
||||
|
||||
for (int x = 0; x < information.description.width; x++) {
|
||||
for (int y = 0; y < information.description.height; y++) {
|
||||
SolverTile tile = information.GetTile(x, y);
|
||||
if (tile.IsHidden() && !tile.IsMine()) {
|
||||
actions.Add(new SolverAction(tile, ActionType.Clear, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
return BuildActionHeader(information, actions);
|
||||
}
|
||||
|
||||
|
||||
SolutionCounter solutionCounter = new SolutionCounter(information, witnesses, witnessed, livingTilesLeft, livingMinesLeft);
|
||||
solutionCounter.Process();
|
||||
information.Write("Solution counter says " + solutionCounter.GetSolutionCount() + " solutions and " + solutionCounter.getClearCount() + " clears");
|
||||
|
||||
|
||||
//ProbabilityEngine pe = new ProbabilityEngine(information, witnesses, witnessed, information.GetTilesLeft(), information.GetMinesLeft());
|
||||
ProbabilityEngine pe = new ProbabilityEngine(information, witnesses, witnessed, livingTilesLeft, livingMinesLeft);
|
||||
|
||||
pe.Process();
|
||||
|
||||
long time3 = DateTime.Now.Ticks;
|
||||
|
||||
information.Write("Probability Engine took " + (time3 - time2) + " ticks");
|
||||
|
||||
// have we found any local clears which we can use
|
||||
List<SolverTile> localClears = pe.GetLocalClears();
|
||||
if (localClears.Count > 0) {
|
||||
foreach (SolverTile tile in localClears) { // place each local clear into an action
|
||||
actions.Add(new SolverAction(tile, ActionType.Clear, 1));
|
||||
}
|
||||
information.Write("The probability engine has found " + localClears.Count + " safe Local Clears");
|
||||
|
||||
// add any mines to it
|
||||
List<SolverTile> minesFound = pe.GetMinesFound();
|
||||
foreach (SolverTile tile in minesFound) { // place each mine found into an action
|
||||
information.MineFound(tile);
|
||||
actions.Add(new SolverAction(tile, ActionType.Flag, 0));
|
||||
}
|
||||
information.Write("The probability engine has found " + minesFound.Count + " mines");
|
||||
|
||||
|
||||
return BuildActionHeader(information, actions);
|
||||
}
|
||||
|
||||
if (pe.GetBestEdgeProbability() == 1) {
|
||||
actions = pe.GetBestCandidates(1);
|
||||
information.Write("The probability engine has found " + actions.Count + " safe Clears");
|
||||
|
||||
// add any mines to it
|
||||
List<SolverTile> minesFound = pe.GetMinesFound();
|
||||
foreach (SolverTile tile in minesFound) { // place each mine found into an action
|
||||
information.MineFound(tile);
|
||||
actions.Add(new SolverAction(tile, ActionType.Flag, 0));
|
||||
}
|
||||
information.Write("The probability engine has found " + minesFound.Count + " mines");
|
||||
|
||||
return BuildActionHeader(information, actions);
|
||||
}
|
||||
|
||||
// dead edge (all tiles on the edge are dead and there is only one mine count value)
|
||||
if (pe.GetDeadEdge().Count != 0) {
|
||||
SolverTile tile = pe.GetDeadEdge()[0];
|
||||
information.Write("Probability engine has found a dead area, guessing at " + tile.AsText());
|
||||
double probability = Combination.DivideBigIntegerToDouble(BigInteger.One, pe.GetDeadEdgeSolutionCount(), 6);
|
||||
actions.Add(new SolverAction(tile, ActionType.Clear, probability));
|
||||
return BuildActionHeader(information, actions);
|
||||
}
|
||||
|
||||
// Isolated edges found (all adjacent tiles are also on the edge and there is only one mine count value)
|
||||
if (pe.GetOutcome() == ProbabilityEngine.Outcome.ISOLATED_EDGE) {
|
||||
information.Write("Probability engine has found an isolated area");
|
||||
|
||||
Cruncher cruncher = pe.getIsolatedEdgeCruncher();
|
||||
|
||||
// determine all possible solutions
|
||||
cruncher.Crunch();
|
||||
|
||||
// determine best way to solver them
|
||||
BruteForceAnalysis bfa = cruncher.GetBruteForceAnalysis();
|
||||
bfa.process();
|
||||
|
||||
// if after trying to process the data we can't complete then abandon it
|
||||
if (!bfa.IsComplete()) {
|
||||
information.Write("Abandoned the Brute Force Analysis after " + bfa.GetNodeCount() + " steps");
|
||||
bfa = null;
|
||||
|
||||
} else { // otherwise try and get the best long term move
|
||||
information.Write("Built probability tree from " + bfa.GetSolutionCount() + " solutions in " + bfa.GetNodeCount() + " steps");
|
||||
SolverAction move = bfa.GetNextMove();
|
||||
if (move != null) {
|
||||
information.SetBruteForceAnalysis(bfa); // save the details so we can walk the tree
|
||||
information.Write("Brute Force Analysis: " + move.AsText());
|
||||
actions.Add(move);
|
||||
return BuildActionHeader(information, actions);
|
||||
} else if (bfa.GetAllDead()) {
|
||||
|
||||
SolverTile tile = cruncher.getTiles()[0];
|
||||
information.Write("Brute Force Analysis has decided all tiles are dead on the Isolated Edge, guessing at " + tile.AsText());
|
||||
double probability = Combination.DivideBigIntegerToDouble(BigInteger.One, bfa.GetSolutionCount(), 6);
|
||||
actions.Add(new SolverAction(tile, ActionType.Clear, probability));
|
||||
return BuildActionHeader(information, actions);
|
||||
|
||||
} else {
|
||||
information.Write("Brute Force Analysis: no move found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// after this point we know the probability engine didn't return any certain clears. But there are still some special cases when everything off edge is either clear or a mine
|
||||
|
||||
// If there are tiles off the edge and they are definitely safe then clear them all, or mines then flag them
|
||||
if (offEdgeTilesLeft > 0 && (pe.GetOffEdgeProbability() == 1 || pe.GetOffEdgeProbability() == 0)) {
|
||||
information.Write("Looking for the certain moves off the edge found by the probability engine");
|
||||
bool clear;
|
||||
if (pe.GetOffEdgeProbability() == 1) {
|
||||
information.Write("All off edge tiles are clear");
|
||||
clear = true;
|
||||
} else {
|
||||
information.Write("All off edge tiles are mines");
|
||||
clear = false;
|
||||
}
|
||||
|
||||
for (int x=0; x < information.description.width; x++) {
|
||||
for (int y = 0; y < information.description.height; y++) {
|
||||
|
||||
SolverTile tile = information.GetTile(x, y);
|
||||
|
||||
if (tile.IsHidden() && !witnessedSet.Contains(tile)) {
|
||||
if (clear) {
|
||||
information.Write(tile.AsText() + " is clear");
|
||||
actions.Add(new SolverAction(tile, ActionType.Clear, 1));
|
||||
} else {
|
||||
information.Write(tile.AsText() + " is mine");
|
||||
information.MineFound(tile);
|
||||
actions.Add(new SolverAction(tile, ActionType.Flag, 0));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (actions.Count > 0) {
|
||||
|
||||
// add any mines to it
|
||||
List<SolverTile> minesFound = pe.GetMinesFound();
|
||||
foreach (SolverTile tile in minesFound) { // place each mine found into an action
|
||||
information.MineFound(tile);
|
||||
actions.Add(new SolverAction(tile, ActionType.Flag, 0));
|
||||
}
|
||||
information.Write("The probability engine has found " + minesFound.Count + " mines");
|
||||
|
||||
return BuildActionHeader(information, actions);
|
||||
} else {
|
||||
Console.WriteLine("No Actions found!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// these are guesses
|
||||
List<SolverAction> guesses = pe.GetBestCandidates(1);
|
||||
|
||||
// we know the Probability Engine completed so hold onto the information for the gui
|
||||
information.SetProbabilityEngine(pe);
|
||||
|
||||
// if there aren't many possible solutions then do a brute force search
|
||||
if (pe.GetSolutionCount() <= MAX_BFDA_SOLUTIONS) {
|
||||
|
||||
//if (minesFound.Count > 0) {
|
||||
// information.Write("Not doing a brute force analysis because we found some mines using the probability engine");
|
||||
// return BuildActionHeader(information, actions);
|
||||
//}
|
||||
|
||||
// find a set of independent witnesses we can use as the base of the iteration
|
||||
pe.GenerateIndependentWitnesses();
|
||||
|
||||
BigInteger expectedIterations = pe.GetIndependentIterations() * SolverMain.Calculate(livingMinesLeft - pe.GetIndependentMines(), livingTilesLeft - pe.GetIndependentTiles());
|
||||
|
||||
information.Write("Expected Brute Force iterations " + expectedIterations);
|
||||
|
||||
// do the brute force if there are not too many iterations
|
||||
if (expectedIterations < MAX_BRUTE_FORCE_ITERATIONS) {
|
||||
|
||||
List<SolverTile> allCoveredTiles = new List<SolverTile>();
|
||||
|
||||
for (int x = 0; x < information.description.width; x++) {
|
||||
for (int y = 0; y < information.description.height; y++) {
|
||||
SolverTile tile = information.GetTile(x, y);
|
||||
if (tile.IsHidden() && !tile.IsMine()) {
|
||||
allCoveredTiles.Add(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WitnessWebIterator[] iterators = BuildParallelIterators(information, pe, allCoveredTiles, expectedIterations);
|
||||
BruteForceAnalysis bfa = Cruncher.PerformBruteForce(information, iterators, pe.GetDependentWitnesses());
|
||||
|
||||
bfa.process();
|
||||
|
||||
// if after trying to process the data we can't complete then abandon it
|
||||
if (!bfa.IsComplete()) {
|
||||
information.Write("Abandoned the Brute Force Analysis after " + bfa.GetNodeCount() + " steps");
|
||||
bfa = null;
|
||||
|
||||
} else { // otherwise try and get the best long term move
|
||||
information.Write("Built probability tree from " + bfa.GetSolutionCount() + " solutions in " + bfa.GetNodeCount() + " steps");
|
||||
SolverAction move = bfa.GetNextMove();
|
||||
if (move != null) {
|
||||
information.SetBruteForceAnalysis(bfa); // save the details so we can walk the tree
|
||||
information.Write("Brute Force Analysis: " + move.AsText());
|
||||
actions.Add(move);
|
||||
return BuildActionHeader(information, actions);
|
||||
} else {
|
||||
information.Write("Brute Force Analysis: no move found!");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
information.Write("Too many iterations, Brute Force not atempted");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (guesses.Count == 0) { // find an off edge guess
|
||||
if (offEdgeTilesLeft > 0) {
|
||||
information.Write("getting an off edge guess");
|
||||
SolverTile tile = OffEdgeGuess(information, witnessedSet);
|
||||
SolverAction action = new SolverAction(tile, ActionType.Clear, pe.GetOffEdgeProbability());
|
||||
information.Write(action.AsText());
|
||||
actions.Add(action);
|
||||
} else {
|
||||
if (information.GetDeadTiles().Count > 0) {
|
||||
information.Write("Finding a dead tile to guess");
|
||||
SolverTile tile = null;
|
||||
foreach (SolverTile deadTile in information.GetDeadTiles()) { // get the first dead tile
|
||||
tile = deadTile;
|
||||
break;
|
||||
}
|
||||
SolverAction action = new SolverAction(tile, ActionType.Clear, 0.5); // probability may not be 0.5
|
||||
actions.Add(action);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else if (guesses.Count > 1) { // if we have more than 1 guess then do some tie break logic
|
||||
information.Write("Doing a tie break for " + guesses.Count + " actions");
|
||||
actions = DoTieBreak(guesses);
|
||||
|
||||
} else {
|
||||
actions = guesses;
|
||||
}
|
||||
|
||||
return BuildActionHeader(information, actions);
|
||||
|
||||
}
|
||||
|
||||
private static SolverActionHeader BuildActionHeader(SolverInfo information, List<SolverAction> actions) {
|
||||
|
||||
HashSet<SolverTile> dead = information.GetDeadTiles();
|
||||
|
||||
// add any dead tiles to the list of actions
|
||||
List<SolverAction> deadList = new List<SolverAction>();
|
||||
foreach (SolverTile dt in dead) {
|
||||
deadList.Add(new SolverAction(dt, ActionType.Dead, 1));
|
||||
}
|
||||
|
||||
return new SolverActionHeader(actions, deadList);
|
||||
|
||||
}
|
||||
|
||||
private static List<SolverAction> FindTrivialActions(SolverInfo information) {
|
||||
|
||||
List<SolverAction> actions = new List<SolverAction>();
|
||||
|
||||
// provide actions for known mines which aren't flagged
|
||||
foreach (SolverTile tile in information.GetKnownMines()) {
|
||||
if (!tile.IsFlagged()) {
|
||||
actions.Add(new SolverAction(tile, ActionType.Flag, 0)); // request it is flagged
|
||||
}
|
||||
}
|
||||
|
||||
actions.AddRange(ScanForTrivialActions(information, information.GetWitnesses()));
|
||||
|
||||
//actions.AddRange(ScanForTrivialActions(information, information.GetExcludedWitnesses()));
|
||||
|
||||
information.Write("Found " + actions.Count + " trivial actions");
|
||||
|
||||
return actions;
|
||||
|
||||
}
|
||||
|
||||
private static List<SolverAction> ScanForTrivialActions(SolverInfo information, HashSet<SolverTile> set) {
|
||||
|
||||
List<SolverAction> actions = new List<SolverAction>(); ;
|
||||
|
||||
HashSet<SolverTile> alreadyProcessed = new HashSet<SolverTile>();
|
||||
|
||||
foreach (SolverTile tile in set) {
|
||||
|
||||
AdjacentInfo adjInfo = information.AdjacentTileInfo(tile);
|
||||
|
||||
if (tile.GetValue() == adjInfo.mines) { // if we have the correct number of mines then the remaining hidden tiles can be cleared
|
||||
foreach (SolverTile adjTile in information.GetAdjacentTiles(tile)) {
|
||||
if (!alreadyProcessed.Contains(adjTile) && adjTile.IsHidden() && !adjTile.IsMine()) {
|
||||
alreadyProcessed.Add(adjTile); // avoid marking as cleared more than once
|
||||
//Utility.Write(adjTile.AsText() + " can be cleared");
|
||||
actions.Add(new SolverAction(adjTile, ActionType.Clear, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tile.GetValue() == adjInfo.mines + adjInfo.hidden) { // If Hidden + Mines we already know about = tile value then the rest must be mines
|
||||
foreach (SolverTile adjTile in information.GetAdjacentTiles(tile)) {
|
||||
if (!alreadyProcessed.Contains(adjTile) && adjTile.IsHidden() && !adjTile.IsMine()) {
|
||||
alreadyProcessed.Add(adjTile); // avoid marking as a mine more than once
|
||||
//Utility.Write(adjTile.AsText() + " is a mine");
|
||||
if (!information.MineFound(adjTile)) {
|
||||
actions.Add(new SolverAction(adjTile, ActionType.Flag, 0)); // and request it is flagged
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2 mines to find and only 3 tiles to put them
|
||||
if (tile.GetValue() - adjInfo.mines == 2 && adjInfo.hidden == 3) {
|
||||
foreach (SolverTile adjTile in information.GetAdjacentTiles(tile)) {
|
||||
if (!adjTile.IsHidden() && !adjTile.IsMine() && !adjTile.IsExhausted()) {
|
||||
|
||||
AdjacentInfo tileAdjInfo = information.AdjacentTileInfo(adjTile);
|
||||
|
||||
if (adjTile.GetValue() - tileAdjInfo.mines == 1) { // an adjacent tile only needs to find one mine
|
||||
|
||||
int notAdjacentCount = 0;
|
||||
SolverTile notAdjTile = null;
|
||||
foreach (SolverTile adjTile2 in information.GetAdjacentTiles(tile)) {
|
||||
if (adjTile2.IsHidden() && !adjTile2.IsAdjacent(adjTile)) {
|
||||
notAdjacentCount++;
|
||||
notAdjTile = adjTile2;
|
||||
}
|
||||
}
|
||||
|
||||
if (notAdjacentCount == 1 && !alreadyProcessed.Contains(notAdjTile)) { // we share all but one tile, so that one tile must contain the extra mine
|
||||
alreadyProcessed.Add(notAdjTile); // avoid marking as a mine more than once
|
||||
if (!information.MineFound(notAdjTile)) {
|
||||
actions.Add(new SolverAction(notAdjTile, ActionType.Flag, 0)); // and request it is flagged
|
||||
break; // restrict to finding one mine at a time (the action of marking the mine throws any further analysis out)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Subtraction method
|
||||
//continue; // skip this bit
|
||||
foreach (SolverTile adjTile in information.GetAdjacentTiles(tile)) {
|
||||
|
||||
if (!adjTile.IsHidden() && !adjTile.IsMine() && !adjTile.IsExhausted()) {
|
||||
|
||||
AdjacentInfo tileAdjInfo = information.AdjacentTileInfo(adjTile);
|
||||
|
||||
if (adjTile.GetValue() - tileAdjInfo.mines == tile.GetValue() - adjInfo.mines) { // if the adjacent tile is revealed and shares the same number of mines to find
|
||||
// If all the adjacent tiles adjacent tiles are also adjacent to the original tile
|
||||
bool allAdjacent = true;
|
||||
foreach (SolverTile adjTile2 in information.GetAdjacentTiles(adjTile)) {
|
||||
if (adjTile2.IsHidden() && !adjTile2.IsAdjacent(tile)) {
|
||||
allAdjacent = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allAdjacent) {
|
||||
//information.Write(tile.AsText() + " can be subtracted by " + adjTile.AsText());
|
||||
foreach (SolverTile adjTile2 in information.GetAdjacentTiles(tile)) {
|
||||
if (adjTile2.IsHidden() && !adjTile2.IsAdjacent(adjTile) && !alreadyProcessed.Contains(adjTile2)) {
|
||||
alreadyProcessed.Add(adjTile2); // avoid marking as a mine more than once
|
||||
actions.Add(new SolverAction(adjTile2, ActionType.Clear, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private static SolverTile OffEdgeGuess(SolverInfo information, HashSet<SolverTile> witnessedSet) {
|
||||
|
||||
// see if the corners are available
|
||||
SolverTile bestGuess = information.GetTile(0, 0);
|
||||
if (bestGuess.IsHidden() && !witnessedSet.Contains(bestGuess)) {
|
||||
return bestGuess;
|
||||
}
|
||||
bestGuess = information.GetTile(0, information.description.height - 1);
|
||||
if (bestGuess.IsHidden() && !witnessedSet.Contains(bestGuess)) {
|
||||
return bestGuess;
|
||||
}
|
||||
bestGuess = information.GetTile(information.description.width - 1, 0);
|
||||
if (bestGuess.IsHidden() && !witnessedSet.Contains(bestGuess)) {
|
||||
return bestGuess;
|
||||
}
|
||||
bestGuess = information.GetTile(information.description.width - 1, information.description.height - 1);
|
||||
if (bestGuess.IsHidden() && !witnessedSet.Contains(bestGuess)) {
|
||||
return bestGuess;
|
||||
}
|
||||
|
||||
bestGuess = null;
|
||||
int bestGuessCount = 9;
|
||||
|
||||
for (int x = 0; x < information.description.width; x++) {
|
||||
for (int y = 0; y < information.description.height; y++) {
|
||||
|
||||
SolverTile tile = information.GetTile(x, y);
|
||||
|
||||
if (tile.IsHidden() && !witnessedSet.Contains(tile)) {
|
||||
AdjacentInfo adjInfo = information.AdjacentTileInfo(tile);
|
||||
if (adjInfo.hidden < bestGuessCount) {
|
||||
bestGuess = tile;
|
||||
bestGuessCount = adjInfo.hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestGuess;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// do tie break logic
|
||||
private static List<SolverAction> DoTieBreak(List<SolverAction> actions) {
|
||||
|
||||
List<SolverAction> result = new List<SolverAction>();
|
||||
|
||||
// find and return a not dead tile
|
||||
foreach (SolverAction sa in actions) {
|
||||
if (!sa.isDead) {
|
||||
result.Add(sa);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise return the first one
|
||||
result.Add(actions[0]);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
// break a witness web search into a number of non-overlapping iterators
|
||||
private static WitnessWebIterator[] BuildParallelIterators(SolverInfo information, ProbabilityEngine pe, List<SolverTile> allCovered, BigInteger expectedIterations) {
|
||||
|
||||
information.Write("Building parallel iterators");
|
||||
|
||||
//information.Write("Non independent iterations = " + pe.GetNonIndependentIterations(mines));
|
||||
|
||||
int minesLeft = information.GetMinesLeft();
|
||||
|
||||
//the number of witnesses available
|
||||
int totalWitnesses = pe.GetIndependentWitnesses().Count + pe.GetDependentWitnesses().Count;
|
||||
|
||||
// if there is only one cog then we can't lock it,so send back a single iterator
|
||||
if (pe.GetIndependentWitnesses().Count == 1 && pe.GetIndependentMines() >= minesLeft || expectedIterations.CompareTo(PARALLEL_MINIMUM) < 0 || totalWitnesses == 0) {
|
||||
information.Write("Only a single iterator will be used");
|
||||
WitnessWebIterator[] one = new WitnessWebIterator[1];
|
||||
one[0] = new WitnessWebIterator(information, pe.GetIndependentWitnesses(), pe.GetDependentWitnesses(), allCovered, minesLeft, information.GetTilesLeft(), -1);
|
||||
return one;
|
||||
}
|
||||
|
||||
int witMines = pe.GetIndependentWitnesses()[0].GetMinesToFind();
|
||||
int squares = pe.GetIndependentWitnesses()[0].GetAdjacentTiles().Count;
|
||||
|
||||
BigInteger bigIterations = Calculate(witMines, squares);
|
||||
|
||||
int iter = (int) bigIterations;
|
||||
|
||||
information.Write("The first cog has " + iter + " iterations, so parallel processing is possible");
|
||||
|
||||
WitnessWebIterator[] result = new WitnessWebIterator[iter];
|
||||
|
||||
for (int i = 0; i < iter; i++) {
|
||||
// create a iterator with a lock first got at position i
|
||||
result[i] = new WitnessWebIterator(information, pe.GetIndependentWitnesses(), pe.GetDependentWitnesses(), allCovered, minesLeft, information.GetTilesLeft(), i);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static BigInteger Calculate(int mines, int squares) {
|
||||
return binomial.Generate(mines, squares);
|
||||
}
|
||||
|
||||
public static void Initialise() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
189
info/Minesweeper2/MinesweeperSolver/SolverTile.cs
Normal file
@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using static MinesweeperSolver.SolverInfo;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
|
||||
public class SolverTile {
|
||||
|
||||
public readonly int x;
|
||||
public readonly int y;
|
||||
private readonly long key; // the key identifies which SolverInfo created these tiles and locks down some methods.
|
||||
// details about me
|
||||
private bool isMine = false;
|
||||
private bool isFlagged = false;
|
||||
private bool isHidden = true;
|
||||
private int value = 0;
|
||||
private bool isDead = false; // a dead tile is one which provides no information if clicked. Should be avoided unless all tiles are dead.
|
||||
|
||||
private bool exhausted = false; // an exhausted tile is where it is cleared and all its neighbours are either cleared or a mine
|
||||
|
||||
//private bool newGrowth = true; // a tile is new growth the first time it is revealed. Once it has been analysed by the Probability Engine it stops being new growth.
|
||||
|
||||
//private bool excluded = false; // an excluded tile is one which no longer needs to be included in the propability engine since it no longer affects other tiles
|
||||
|
||||
private List<SolverTile> adjacentTiles = null;
|
||||
|
||||
public SolverTile(long key, int x, int y) {
|
||||
|
||||
this.key = key;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
|
||||
}
|
||||
|
||||
public bool IsMine() {
|
||||
return isMine;
|
||||
}
|
||||
|
||||
// this should only be called from the SolverInfo class
|
||||
public void SetAsMine(long key) {
|
||||
|
||||
if (key != this.key) {
|
||||
throw new Exception("Unauthorised access to the SetAsMine method");
|
||||
}
|
||||
|
||||
this.isMine = true;
|
||||
this.isHidden = false; // no longer hidden we know it is a mine
|
||||
}
|
||||
|
||||
public bool IsFlagged() {
|
||||
return this.isFlagged;
|
||||
}
|
||||
|
||||
public void SetFlagged(bool flagged) {
|
||||
isFlagged = flagged;
|
||||
}
|
||||
|
||||
public bool IsHidden() {
|
||||
return this.isHidden;
|
||||
}
|
||||
|
||||
public void SetValue(int value) {
|
||||
this.isHidden = false;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int GetValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public bool IsExhausted() {
|
||||
return this.exhausted;
|
||||
}
|
||||
|
||||
public void SetExhausted() {
|
||||
this.exhausted = true;
|
||||
}
|
||||
|
||||
// this should only be called from from the SolverInfo class
|
||||
public void SetDead(long key) {
|
||||
if (key != this.key) {
|
||||
throw new Exception("Unauthorised access to the SetDead method");
|
||||
}
|
||||
this.isDead = true;
|
||||
}
|
||||
|
||||
//public bool IsNewGrowth() {
|
||||
// return newGrowth;
|
||||
//}
|
||||
|
||||
//public void SetExamined() {
|
||||
// this.newGrowth = false;
|
||||
//}
|
||||
|
||||
public bool IsDead() {
|
||||
|
||||
return isDead;
|
||||
}
|
||||
|
||||
public List<SolverTile> GetAdjacentTiles() {
|
||||
return this.adjacentTiles;
|
||||
}
|
||||
|
||||
public void SetAdjacentTiles(List<SolverTile> adjTiles) {
|
||||
this.adjacentTiles = adjTiles;
|
||||
}
|
||||
|
||||
/*
|
||||
public void SetExcluded() {
|
||||
this.excluded = true;
|
||||
}
|
||||
|
||||
public bool IsExcluded() {
|
||||
return this.excluded;
|
||||
}
|
||||
*/
|
||||
|
||||
public bool IsEqual(SolverTile tile) {
|
||||
if (this.x == tile.x && this.y == tile.y) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if the tile provided is adjacent to this tile
|
||||
public bool IsAdjacent(SolverTile tile) {
|
||||
|
||||
var dx = Math.Abs(this.x - tile.x);
|
||||
if (dx > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var dy = Math.Abs(this.y - tile.y);
|
||||
if (dy > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dx == 0 && dy == 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
// adjacent and not equal
|
||||
if (dx < 2 && dy < 2 && !(dx == 0 && dy == 0)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
//<summary> return the number of hidden tiles shared by these tiles
|
||||
public int NumberOfSharedHiddenTiles(SolverTile tile) {
|
||||
|
||||
// if the locations are too far apart they can't share any of the same squares
|
||||
if (Math.Abs(tile.x - this.x) > 2 || Math.Abs(tile.y - this.y) > 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
foreach (SolverTile tile1 in this.GetAdjacentTiles()) {
|
||||
if (!tile1.IsHidden()) {
|
||||
continue;
|
||||
}
|
||||
foreach (SolverTile tile2 in tile.GetAdjacentTiles()) {
|
||||
if (!tile2.IsHidden()) {
|
||||
continue;
|
||||
}
|
||||
if (tile1.IsEqual(tile2)) { // if they share a tile then return true
|
||||
count ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no shared tile found
|
||||
return count;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public string AsText() {
|
||||
return "(" + x + "," + y + ")";
|
||||
}
|
||||
}
|
||||
}
|
177
info/Minesweeper2/MinesweeperSolver/binomial.cs
Normal file
@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
namespace MinesweeperSolver {
|
||||
|
||||
|
||||
public class Binomial {
|
||||
|
||||
private static readonly double LOG10 = Math.Log(10);
|
||||
|
||||
private readonly int max;
|
||||
private readonly PrimeSieve ps;
|
||||
|
||||
private readonly BigInteger[,] binomialLookup;
|
||||
private readonly int lookupLimit;
|
||||
|
||||
public Binomial(int max, int lookup) {
|
||||
|
||||
this.max = max;
|
||||
|
||||
ps = new PrimeSieve(this.max);
|
||||
|
||||
if (lookup < 10) {
|
||||
lookup = 10;
|
||||
}
|
||||
this.lookupLimit = lookup;
|
||||
|
||||
int lookup2 = lookup / 2;
|
||||
|
||||
binomialLookup = new BigInteger[lookup + 1, lookup2 + 1];
|
||||
|
||||
for (int total = 1; total <= lookup; total++) {
|
||||
for (int choose = 0; choose <= total / 2; choose++) {
|
||||
//try {
|
||||
binomialLookup[total, choose] = Generate(choose, total);
|
||||
//Console.WriteLine("Binomial " + total + " choose " + choose + " is " + binomialLookup[total, choose]);
|
||||
//} catch (Exception e) {
|
||||
// throw e;
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public BigInteger Generate(int k, int n) {
|
||||
|
||||
if (n == 0 && k == 0) {
|
||||
return BigInteger.One;
|
||||
}
|
||||
|
||||
if (n < 1) {
|
||||
throw new Exception("Binomial: 1 <= n required, but n was " + n);
|
||||
}
|
||||
|
||||
if (0 > k || k > n) {
|
||||
throw new Exception("Binomial: 0 <= k and k <= n required, but n was " + n + " and k was " + k);
|
||||
}
|
||||
|
||||
int choose = Math.Min(k, n - k);
|
||||
|
||||
if (n <= lookupLimit && binomialLookup[n, choose] != 0) { // it is zero when it hasn't been built yet
|
||||
return binomialLookup[n, choose];
|
||||
} else if (choose < 125) {
|
||||
return Combination(choose, n);
|
||||
} else if (n <= max) {
|
||||
return CombinationLarge(choose, n);
|
||||
} else {
|
||||
return CombinationApprox(choose, n);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static BigInteger Combination(int mines, int squares) {
|
||||
|
||||
BigInteger top = BigInteger.One;
|
||||
BigInteger bot = BigInteger.One;
|
||||
|
||||
int range = Math.Min(mines, squares - mines);
|
||||
|
||||
// calculate the combination.
|
||||
for (int i = 0; i < range; i++) {
|
||||
top = top * new BigInteger(squares - i);
|
||||
bot = bot * new BigInteger(i + 1);
|
||||
}
|
||||
|
||||
BigInteger result = top / bot;
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private BigInteger CombinationLarge(int k, int n) {
|
||||
|
||||
if ((k == 0) || (k == n)) return BigInteger.One;
|
||||
|
||||
int n2 = n / 2;
|
||||
|
||||
if (k > n2) {
|
||||
k = n - k;
|
||||
}
|
||||
|
||||
int nk = n - k;
|
||||
|
||||
int rootN = (int)Math.Floor(Math.Sqrt(n));
|
||||
|
||||
BigInteger result = BigInteger.One;
|
||||
|
||||
foreach (int prime in ps.getPrimesIterable(2, n)) {
|
||||
|
||||
if (prime > nk) {
|
||||
result = result * new BigInteger(prime);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prime > n2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prime > rootN) {
|
||||
if (n % prime < k % prime) {
|
||||
result = result * new BigInteger(prime);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
int r = 0, N = n, K = k, p = 1;
|
||||
|
||||
while (N > 0) {
|
||||
r = (N % prime) < (K % prime + r) ? 1 : 0;
|
||||
if (r == 1) {
|
||||
p *= prime;
|
||||
}
|
||||
N /= prime;
|
||||
K /= prime;
|
||||
}
|
||||
if (p > 1) {
|
||||
result = result * new BigInteger(p);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// use the stirling approximation for factorials to create an approximate combinatorial
|
||||
public BigInteger CombinationApprox(int k, int n) {
|
||||
|
||||
double logComb = (LogFactorialApprox(n) - LogFactorialApprox(k) - LogFactorialApprox(n - k));
|
||||
|
||||
int power = (int) Math.Floor(logComb);
|
||||
|
||||
int dp = Math.Min(6, power);
|
||||
|
||||
//double value = Math.Exp((logComb - power + dp) * LOG10);
|
||||
//BigInteger result = new BigInteger(sigDigit) * BigInteger.Pow(new BigInteger(10), power - dp);
|
||||
|
||||
// find the significant digits
|
||||
int sigDigits = (int) Math.Round(Math.Pow(10, logComb - power + dp));
|
||||
|
||||
String scientific = "" + sigDigits + "E" + (power - dp);
|
||||
|
||||
BigInteger result = BigInteger.Parse(scientific, NumberStyles.AllowExponent);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// returns an approximation for Log(n!)
|
||||
private double LogFactorialApprox(int n) {
|
||||
return n * Math.Log10(n) + 0.5d * Math.Log10(2 * Math.PI * n);
|
||||
}
|
||||
}
|
||||
}
|