Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: C++: Sudoku tekoäly

Sivun loppuun

Touho [18.01.2006 18:33:16]

#

Tämä ohjelma ratkaisee sudoku-pelin.

Sudokun säännöt:
Täytä kaikki ruudut numeroilla 1-9 siten, että jokaisella pystyrivillä sekä vaakarivillä on numerot 1-9. myös yhdeksän 3*3 ruudun pitää sisältää kaikki numerot 1-9.,

Esimerkkikuva: http://www.hindu.com/thehindu/sudoku/sudokusamq.jpg


HUOM! Muistakaa ottaa pois tuo "*** sudoku.txt" kohta. ohjelma lukee kaikki tarvittavat tiedot, joten se lukee vaan tuon yhden kentän, siispä sen alapuolelle voi laittaa useampia kenttiä muistiin.

sudoku.txt

5 3 0 0 7 0 0 0 0
6 0 0 1 9 5 0 0 0
0 9 8 0 0 0 0 6 0
8 0 0 0 6 0 0 0 3
4 0 0 8 0 3 0 0 1
7 0 0 0 2 0 0 0 6
0 6 0 0 0 0 2 8 0
0 0 0 4 1 9 0 0 5
0 0 0 0 8 0 0 7 9

tästä eteenpäin ohjelma ei enää lue.


Tässä on esimerkkikuvan alkutilanne.

8 1 0 0 0 0 7 0 3
0 0 0 6 0 7 0 0 8
9 0 2 3 1 0 6 0 0
0 4 0 0 7 0 5 6 0
0 0 7 9 0 1 2 0 0
0 6 3 0 4 0 0 9 0
0 0 4 0 9 2 1 0 6
6 0 0 5 0 4 0 0 0
7 0 8 0 0 0 0 5 9
#include <iostream> // output
#include <fstream> //lukemista varten
#include <time.h>
int start_time;

using namespace std;

int i, ii, x, y, x2, y2, i2, var1, var2;
int guesses=0;
int advanced=0;
int loops=0;

class RUUTU // luokka yhdelle pienelle ruudulle
{
public:
	bool vaih[10];
	short int num;
	bool is;

	int OnePoss() // jos on vain yksi numerovaihtoehto, OnePoss() palauttaa numeron 1-9
	{			  // jos on monta, OnePoss() palauttaa 0.
		short int thenum=0;

		for(i=1;i<10;i++) // montako vaihtoehtoa?
		{
			if(vaih[i])
				thenum++;
		}

		if(thenum==1) // jos yksi vaihtoehto, niin katsotaan mikä
		{
			for(i=1;i<10;i++)
			{
				if(vaih[i])
				{
					thenum=i;
					break;
				}
			}
		}
		else thenum=0;

		return thenum; // palautetaan numero, joka tulee olemaan tässä ruudussa.
	}
};

class SUDOKU // luokka sudoku kentälle
{
public:

	RUUTU original[9][9], numero[9][9], numero2[9][9]; // kentät


	SUDOKU(short int taulukko[9][9]) // on init..
	{
		for(x=0;x<9;x++)
		for(y=0;y<9;y++)
		{
			numero[x][y].num=taulukko[x][y]; // annetaan tiedot kenttään
			original[x][y].num=numero[x][y].num;

			for(i=0;i<10;i++)                // laitetaan kaikki numerot mahdolliseksi
				numero[x][y].vaih[i]=true;

			if(numero[x][y].num==0)          // jos numero valmiina, is=true
				numero[x][y].is=false;
			else
				numero[x][y].is=true;
		}
	}

	bool See(short int x, short int y, short int i) // jos 3*3 ruudussa tai pysty tai vaaka viivalla on
	{												// numero i, niin palautetaan true
		short int x2, y2;
		short int place_x, place_y;

		y2=y;
		for(x2=0;x2<9;x2++) // vaakaviiva
		{
			if(numero[x2][y2].num==i)
				return true;
		}

		x2=x;
		for(y2=0;y2<9;y2++) // pystyviiva
		{
			if(numero[x2][y2].num==i)
				return true;
		}

		place_x=(short int)((double) x / 3.0);
		place_y=(short int)((double) y / 3.0);

		for(x2=0;x2<3;x2++) // ruudukko
		for(y2=0;y2<3;y2++)
		{
			if(numero[place_x*3+x2][place_y*3+y2].num==i)
				return true;
		}

		return false;
	}
	int SeeTwoChanges(short int x, short int y, short int *x_v, short int *y_v, short int *x2_v, short int *y2_v)
	{
		short int x2, y2;
		short int counter;
		short int i;
		bool one=false;

		short int vaihs[10];
		for(i=0;i<10;i++)
			vaihs[i]=0;

		NoGuessing();

		for(i=1;i<10;i++)
		{
			for(y2=0;y2<3;y2++)
			for(x2=0;x2<3;x2++) // ruudukko
			{
				if(numero[x*3+x2][y*3+y2].is==false && numero[x*3+x2][y*3+y2].vaih[i]==true)
				{
					vaihs[i]+=numero[x*3+x2][y*3+y2].vaih[i];
				}
			}
		}


		for(i=1;i<10;i++)
		{
			if(vaihs[i]==2)
			{
				for(x2=0;x2<3;x2++) // ruudukko
				for(y2=0;y2<3;y2++)
				{
					if(numero[x*3+x2][y*3+y2].vaih[i]==true &&
						numero[x*3+x2][y*3+y2].is==false)
					{
						if(one)
						{
							*x2_v=x*3+x2;
							*y2_v=y*3+y2;

							return i;
						}
						else
						{
							*x_v=x*3+x2;
							*y_v=y*3+y2;

							one=true;
						}

					}
				}
			}
		}


		return 0;
	}

	bool OnlyPlace(short int o_x, short int o_y, short int o_i)
	{
		if(numero[o_x][o_y].vaih[o_i]==false)
			return false;

		short int place_x, place_y;

		place_x=(short int)((double) o_x / 3.0);
		place_y=(short int)((double) o_y / 3.0);

		short int counter=0;

		for(x2=0;x2<3;x2++)
		for(y2=0;y2<3;y2++)
		{
			if(numero[place_x*3+x2][place_y*3+y2].vaih[o_i]==true &&
				numero[place_x*3+x2][place_y*3+y2].is==false)
			{
				counter++;
			}
		}

		if(counter==1)
			return true;
		else
			return false;

	}

	bool AdvancedClear(int vaaka, int pysty, int num)
	{

		bool changed=false;

		short int place_x=(short int)((double) vaaka / 3.0);
		short int place_y=(short int)((double) pysty / 3.0);


		for(x=0;x<3;x++)
		for(y=0;y<3;y++)
		{
			if(numero[place_x*3+x][place_y*3+y].vaih[num]==true &&
				numero[place_x*3+x][place_y*3+y].is==false &&
				!(place_x*3+x==vaaka && place_y*3+y==pysty))
			{
				numero[place_x*3+x][place_y*3+y].vaih[num]=false;
				changed=true;
			}
		}
		if(changed)
			return true;
		return false;

	}
	bool AdvancedSolving()
	{
		int num, vaaka, pysty, counter;
		int mnum, mvaaka, mpysty;
		bool changes=false;
		for(num=1;num<10;num++)
		{
			for(vaaka=0;vaaka<9;vaaka++) //PYSTY
			{
				counter=0;
				for(pysty=0;pysty<9;pysty++)
				{
					if(See(vaaka, pysty, num) || numero[vaaka][pysty].is)
						continue;
					counter++;
					mpysty=pysty;
				}
				if(counter==1)
				{
					advanced++;
					if(AdvancedClear(vaaka, mpysty, num))
						changes=true;
				}
			}


			for(pysty=0;pysty<9;pysty++) //VAAKA
			{
				counter=0;
				for(vaaka=0;vaaka<9;vaaka++)
				{
					if(See(vaaka, pysty, num) || numero[vaaka][pysty].is)
						continue;
					counter++;
					mvaaka=vaaka;
				}
				if(counter==1)
				{
					if(AdvancedClear(mvaaka, pysty, num))
						changes=true;
				}
			}

		}

		if(changes)
			return true;
		return false;
	}

	void NoGuessing()
	{
		short int x, x2, y, y2;

		bool changes;

		while(changes) // looppi
		{
			loops++;
			changes=false;

			for(x=0;x<9;x++) // käydään kaikki ruudut läpi
			for(y=0;y<9;y++)
			{
				if(numero[x][y].is==false) // jos ruudussa ei ole numeroa vielä
				{
					if(numero[x][y].OnePoss()>0) // jos on vain yksi vaihtoehto ruutuun
					{
						numero[x][y].num=numero[x][y].OnePoss(); // laitetaan varmasti oikea numero ruutuun
						numero[x][y].is=true; // ja kerrotaan, että ruudussa on numero

						changes=true; // jotain on tapahtunut
					}

					for(i=1;i<10;i++) // ainoa paikka tälle numerolle
					{
						if(OnlyPlace(x, y, i)==true && See(x, y, i)==false)
						{
							numero[x][y].num=i;
							numero[x][y].is=true;

							changes=true;
							break;
						}
					}

					for(i=1;i<10;i++) // käydään kaikki vaihtoehtoiset numerot läpi
					{
						if(numero[x][y].vaih[i]==true) // jos tämä numero i on mahdollista laittaa kohtaan x, y
						{
							if(See(x, y, i)) // onko samassa 3*3 ruudussa tai vaaka tai pysty rivissä numeroa i
							{
								numero[x][y].vaih[i]=false; // oletetaan tämä numero mahdottomaksi tähän ruutuun
								changes=true; // jotain on tapahtunut
							}
						}
					}
				}
			}

			if(!changes)
			{
				if(AdvancedSolving())
					changes=true;
			}
		}
	}

	bool SearchPair(short int *x, short int *x2, short int *y, short int *y2, short int *i)
	{
		short int fx, fy, fi;
		short int x3, y3, x4, y4;


		for(fx=0;fx<3;fx++)
		for(fy=0;fy<3;fy++)
		{

			// for 3*3, palauttaa 2 x ja y:n arvoa. uus systeemi STC:ssä...

			if(numero[fx][fy].is==false)
			{

				fi=SeeTwoChanges(fx, fy, &x3, &y3, &x4, &y4);


				*x=x3;
				*y=y3;
				*i=fi;
				*x2=x4;
				*y2=y4;

				if(fi>0 && fi<10)return true;
			}

		}


		return false;
	}

	bool GuessSolveLoop()
	{
		short int x, x2, y, y2, i;
		short int forx, fory, fori;

		short int random = rand()%2;

		if(SearchPair(&x, &x2, &y, &y2, &i))
		{

			if(random==0)
			{
				numero[x][y].num=i;
				numero[x][y].is=true;
			}
			else
			{
				numero[x2][y2].num=i;
				numero[x2][y2].is=true;
			}
			guesses++;

			NoGuessing();

			if(Check())
				return 1;

			if(FakeCheck())
			{
				if(GuessSolveLoop())
					return 1;
			}

			for(forx=0;forx<9;forx++)
			for(fory=0;fory<9;fory++)
			{
				numero[forx][fory].num=numero2[forx][fory].num;
				numero[forx][fory].is=numero2[forx][fory].is;
				for(fori=0;fori<10;fori++)
					numero[forx][fory].vaih[fori]=numero2[forx][fory].vaih[fori];
			}
		}

		return 0;
	}

	void Guessing(int ms)
	{

		short int x, x2, y, y2, i;
		short int forx, fory, fori;
		if(SearchPair(&x, &x2, &y, &y2, &i))
		{
			for(forx=0;forx<9;forx++)
			for(fory=0;fory<9;fory++)
			{
				numero2[forx][fory].num=numero[forx][fory].num;
				numero2[forx][fory].is=numero[forx][fory].is;
				for(fori=0;fori<10;fori++)
					numero2[forx][fory].vaih[fori]=numero[forx][fory].vaih[fori];
			}

			if(FakeCheck())
			{
				while(clock()<start_time+ms)
				{
					guesses=0;
					if(GuessSolveLoop())
						return;
				}
			}
		}

		return;
	}

	void Solve() // ratkaistaan sudoku
	{
		short int x, x2, y, y2;

		bool changes=false;

		if(!Check())
		{
			printf("Solving...
");
			NoGuessing();
		}

		if(!Check())
		{
			printf("Guessing numbers...
");
			Guessing(3000);
		}

		printf("
");
		return;
	}

	bool Check() // katsotaan, onko sudoku ratkaistu
	{
		int check[10];

		int var;

		for(x=0;x<9;x++) // jos ei ole kaikkia laitettu, ei sudoku ole ratkaistu
		for(y=0;y<9;y++)
			if(numero[x][y].is==false)return false;

		for(y=0;y<9;y++) // vaakatarkistus
		{
			for(i=0;i<9;i++)
				check[i]=0;

			for(x=0;x<9;x++)
			{
				check[numero[x][y].num]=true;
			}

			for(i=1;i<10;i++)
			{
				if(check[i]==false)
					return false;
			}
		}

		for(x=0;x<9;x++) // pystytarkistus
		{
			for(i=0;i<9;i++)
				check[i]=0;

			for(y=0;y<9;y++)
			{
				check[numero[y][x].num]=true;
			}

			for(i=1;i<10;i++)
			{
				if(check[i]==false)
					return false;
			}
		}

		for(i=0;i<3;i++)      //3*3 ruututarkistus
		for(ii=0;ii<3;ii++)
		{
			for(x=0;x<9;x++)
				check[x]=0;

			for(y=0;y<3;y++)
			for(x=0;x<3;x++)
			{
				check[numero[x+i*3][y+ii*3].num]=true;
			}

			for(x=1;x<10;x++)
			{
				if(check[x]==false)
					return false;
			}

		}
		return true;
	}


	bool FakeCheck() // katsotaan, onko sudoku ratkaistu
	{
		int x, y, i;
		int check[10];

		int var;

		for(y=0;y<9;y++) // vaakatarkistus
		{
			for(i=0;i<10;i++)
				check[i]=0;

			for(x=0;x<9;x++)
			{
				check[numero[x][y].num]++;
			}

			for(i=1;i<10;i++)
			{
				if(check[i]>1)
					return false;
			}
		}

		for(x=0;x<9;x++) // pystytarkistus
		{
			for(i=0;i<10;i++)
				check[i]=0;

			for(y=0;y<9;y++)
			{
				check[numero[x][y].num]++;
			}

			for(i=1;i<10;i++)
			{
				if(check[i]>1)
					return false;
			}
		}

		for(i=0;i<3;i++)      //3*3 ruututarkistus
		for(ii=0;ii<3;ii++)
		{
			for(x=0;x<10;x++)
				check[x]=0;

			for(x=0;x<3;x++)
			for(y=0;y<3;y++)
			{
				check[numero[x+i*3][y+ii*3].num]++;
			}

			for(x=1;x<10;x++)
			{
				if(check[x]>2)
					return false;
			}

		}
		return true;
	}



	void Print() // tuloksen tulostus
	{
		for(ii=0;ii<9;ii++) //rivit
		{
			for(i=0;i<9;i++) //ratkaistu
			{
				if(numero[i][ii].num!=0)
					cout << numero[i][ii].num << " ";
				else
					cout << "- ";

				if(i%3==2)cout << " ";
			}

			cout << "  ";

			for(i=0;i<9;i++) //ratkaisematon
			{
				if(original[i][ii].num!=0)
					cout << original[i][ii].num << " ";
				else
					cout << "- ";

				if(i%3==2)cout << " ";
			}
			cout << endl;
			if(ii%3==2)cout << endl;
		}
		return;
	}

	void PrintVaih() // tuloksen tulostus
	{

		for(ii=0;ii<9;ii++) //rivit
		{
			for(i=0;i<9;i++) //ratkaistu
			{
				for(x=1;x<10;x++)
				{
					cout << numero[i][ii].vaih[x];
				}

				if(i%3==2)cout << "   ";
				else
					cout << " ";
			}
			cout << endl;
			if(ii%3==2)cout << endl;
		}
		return;
	}

};

void Read(short int taulukko[9][9]) // luetaan kenttä tiedostosta sudoku.txt
{
	ifstream read;
	read.open("sudoku.txt");

	for(y=0;y<9;y++)
	for(x=0;x<9;x++)
	{
		read >> taulukko[x][y];
	}
	read.close();
}

int main()
{

	srand(time(NULL));

	start_time=clock();

	short int taulukko[9][9]; //tehdään taulukko, jolla annetaan alkutilanne

	Read(taulukko); //luetaan alkutilanne

	SUDOKU Sudoku(taulukko); //tehdään sudokukenttä

	Sudoku.Solve(); //ratkaistaan kenttä

	Sudoku.Print(); //tulostetaan ratkaistu ja ratkaisematon kenttä

	if(Sudoku.Check())
	{
		printf("
Solved!

");
		printf("Info:
-Time: %dms
-Guesses: %d
-Advanced moves: %d
-Solving loops: %d
",
			clock(), guesses, advanced, loops);

		int taso=0;
		taso+=guesses*30;
		taso+=advanced*15;
		taso+=loops;


		if(taso>100)
			printf("-Difficulty: Impossible (%d)

", taso);
		else if(taso>30)
			printf("-Difficulty: Hard (%d)

", taso);
		else if(taso>10)
			printf("-Difficulty: Medium (%d)

", taso);
		else
			printf("-Difficulty: Easy (%d)

", taso);


		ofstream write("solution.txt");

		write << "Original:

";

		for(ii=0;ii<9;ii++) //rivit
		{
			for(i=0;i<9;i++) //ratkaistu
			{
				if(Sudoku.original[i][ii].num!=0)
					write << Sudoku.original[i][ii].num << " ";
				else
					write << "0 ";

				if(i%3==2)write << " ";
			}
			write << endl;
			if(ii%3==2)write << endl;
		}

		write << "

Solved:

";

		for(ii=0;ii<9;ii++) //rivit
		{
			for(i=0;i<9;i++) //ratkaistu
			{
				if(Sudoku.numero[i][ii].num!=0)
					write << Sudoku.numero[i][ii].num << " ";
				else
					write << "- ";

				if(i%3==2)write << " ";
			}
			write << endl;
			if(ii%3==2)write << endl;
		}

	}
	else
		printf("
Unsolved!

");

	system("PAUSE");


	return 0;
}

T.M. [25.01.2006 21:44:51]

#

Oletko kokeillut osaako tuo ratkaista kaikki vaikeimmat sudoku tehtävät joita löytyy esim lehdistä tms?

Deewiant [12.02.2006 14:29:47]

#

Ei osaa ratkaista läheskään kaikkia, se käyttää ratkaisuun vain kaikkein simppeleintä metodia: jos ruutuun voi laittaa vain yhden luvun, se laittaa sen luvun ruutuun. Harvat Sudokut ratkeavat pelkästään tällä metodilla.

Esimerkiksi tästä tämä solveri ei sano mitään:

3 0 7 0 4 0 0 0 0
0 0 0 0 0 0 0 9 1
8 0 0 0 0 0 0 0 0
4 0 0 0 0 0 7 0 0
0 0 0 1 6 0 0 0 0
0 0 0 2 5 0 0 0 0
0 0 0 0 0 0 3 8 0
0 9 0 0 0 0 5 0 0
0 2 0 6 0 0 0 0 0

Vaikka tälle löytyy uniikki ratkaisu, jonka esimerkiksi oma solverini löytää:

317|849|265
245|736|891
869|512|473
---+---+---
456|398|712
732|164|958
981|257|634
---+---+---
174|925|386
693|481|527
528|673|149

Koodia voisi muuttaa sen verran, että vaihtaa alun fstream.h:n fstream:ksi, heittää perään using namespace std;, ja antaa Read- ja Print-funktioille palautusarvoksi void. Kääntyisi siten ehkä useammallakin kääntäjällä ilman muokkauksia.

Baglair [12.02.2006 14:53:53]

#

Ihan hieno. Deewiant mainitsikin virheet.

Touho [14.02.2006 18:07:47]

#

Okei. Nyt laitoin uusimman version ohjelmasta. tämä on ratkaissut websudoku.comin kaikki evil tasot mitkä olen kokeillut. Myös deewiantin mainitseman.

Tämä myöskin tekee tiedoston, josta on helppo kopioida tuloksen.

EDIT: Uusimpia koodeja en ole kommentoinut.

Deewiant [15.02.2006 21:35:21]

#

Rivinvaihdot hajonneet? Johtunee 'Putkasta, ellei kääntäjäsi syö tuollaisia katkonaisia rivejä. Jos kyse on siitä, kannattaa suosiolla vaihtaa vaikka endl-vakioon, kun kerran iostreamia käytät.

Iostreamista puheen ollen voisi printf-kutsutkin ottaa pois, turha sitä on kahta eri IO-tapaa käyttää.

Ja tuo ei muuten ratkaise tuota mainitsemaani Sudokua. Johtuu siitä, että kommentoit FakeCheck()-funktion pois. Se ulos, niin johan skulaa. Tässä eräs, jota tämä ei ratkaise edes maksimioptimoinneilla omalla koneellani tuossa 3000 ms rajassa - vähän typerää, että onnistuminen riippuu koneen nopeudesta tuon aikarajoituksen takia:

0 0 0 0 7 0 9 4 0
0 7 0 0 9 0 0 0 5
3 0 0 0 0 5 0 7 0
0 8 7 4 0 0 1 0 0
4 6 3 0 0 0 0 0 0
0 0 0 0 0 7 0 8 0
8 0 0 7 0 0 0 0 0
7 0 0 0 0 0 0 2 8
0 5 0 2 6 8 0 0 0

Ei tätä tosin omanikaan ratkaise arvaamatta. Arvaamistahan ei tosin tueta, kyllähän sillä ratkaisee minkä tahansa Sudokun. Logiikalla ne pitää tehdä ;-)

Touho [15.02.2006 22:54:57]

#

Noi rivin vaihdot taitaa olla putkan bugi, koska omalla kääntäjälläni ne ovat tavallisia välilyöntejä. En keksinyt muita tapoja ratkasta mahdollisimman monta sudokua, kuin tuo arvaaminen. Joissain paikoissa kommentit taitaa olla päin metsää, koska olen koodia kopioinut paikasta toiseen... Suokaa anteeksi.

EDIT: Missä siis olen kommentoinut traagisesti FakeCheckin pois?

Deewiant [16.02.2006 18:20:19]

#

Touho kirjoitti:

EDIT: Missä siis olen kommentoinut traagisesti FakeCheckin pois?

//printf("FakeCheck: %d

", (int)Sudoku.FakeCheck());

Täällä Putkassa tuo ei tietenkään näy uloskommentoituna, mutta jos nuo rivinvaihdot korjataan, saadaan tällainen rivi, jossa FakeCheck():ä ei siis kutsuta:

//printf("FakeCheck: %d\n\n", (int)Sudoku.FakeCheck());

lainaus:

En keksinyt muita tapoja ratkasta mahdollisimman monta sudokua, kuin tuo arvaaminen.

Jos jaksaa kiinnostaa, paras (lue: nopein - laskee kaikki mahdolliset ratkaisut kaikille Sudokuille millisekunneissa) tunnettu metodi on Donald Knuthin kehittämä Dancing Links. Nopeutensa ansiosta sitä käytetäänkin monissa Sudokugenerattoreissa, jotka tekevät Sudokuja generoimalla ruudukoita ja sitten testaavat tuolla algoritmilla, voiko sen ratkaista ja kuinka monta ratkaisua löytyy.

Touho [19.02.2006 13:06:29]

#

Ei tuo rivi vaikuta sudokun ratkaisemiseen milläänlailla. Virhe tulee luultavasti siitä, että se täällä putkassa ei kommentoi koko lauseketta vaan pelkästään puolet siitä. Koodipätkä oli vain tuon funktion testausta vasten, joten otan sen nyt pois.

Deewiant [20.02.2006 18:43:51]

#

Touho kirjoitti:

Ei tuo rivi vaikuta sudokun ratkaisemiseen milläänlailla.

Tarkistin koodia, ja siltä vaikuttaakin. Syy sille, miksi tämä ei välillä tee mitään, johtuu ilmeisesti kääntäjästäni (MinGW:n GCC 3.4.2). Kokeilin toista ja johan skulasi. Kummaa.

moptim [31.10.2006 09:43:46]

#

Onko se C++ muka niin hauska... Teenpä oman versioni (ups, meinasin sanoa jo)


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta