diff --git a/android/app/build.gradle b/android/app/build.gradle index c8ef79082898053c7143502554fda40840b0eb5c..c49f6d91e764c06d2c70dacc143444d661a0bbd4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -37,7 +37,7 @@ if (keystorePropertiesFile.exists()) { } android { - compileSdkVersion 33 + compileSdkVersion 34 namespace "org.benoitharrault.sudoku" defaultConfig { diff --git a/android/gradle.properties b/android/gradle.properties index 142ae7bb643e141eab9405dc204c1c1bd653816f..5a0756cd02457428e3b8367e76eb2061b39f8a81 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true -app.versionName=0.1.18 -app.versionCode=67 +app.versionName=0.1.19 +app.versionCode=68 diff --git a/assets/files/templates.json b/assets/files/templates.json deleted file mode 100644 index 3a37ebd141958e36c6ccbb97e146eacae2f75497..0000000000000000000000000000000000000000 --- a/assets/files/templates.json +++ /dev/null @@ -1,355 +0,0 @@ -{ - "templates": { - "2x2": { - "easy": [ - "4020204332141032", - "0302043100133124", - "3240412303000432", - "4231014200230304", - "0310012332001032", - "2300143041200201", - "2401310042130020", - "3040142020044302", - "4031304020131004", - "4231030401400402", - "4302120004210103", - "0000421324010102", - "0010310042301320", - "0302420021340400", - "2400132002030102", - "3204002023004032", - "4013310010420001", - "4132030102001003", - "0000302110404210", - "0001300002131302" - ], - "medium": [ - "2403000000201000", - "0000001004323000", - "0000103000203004", - "0000201010030040", - "0001403000130000", - "0002030101030000", - "0003004041002000", - "0003204030000400", - "0004003003022000", - "0004100040200200", - "0004402010000200", - "0021000040002030", - "0024000030000403", - "0024000030004100", - "0030010000000314", - "0032000003404000", - "0102001000204000", - "0140000003041000", - "0203000101004000", - "0300120000020040" - ], - "hard": [ - "1000040300200000", - "1000200000020003", - "1000300200040000", - "1000400000200010", - "1000400100000020", - "1400000030200000", - "2000001000403000", - "2000400100400000", - "2000400300000040", - "2030002000000100", - "2100000000300200", - "3000002000001040", - "3002000000140000", - "3002040000030000", - "3100000000000204", - "4000000000302010", - "4000000020010003", - "4000000200300300", - "4000300200000030", - "4000300400000020" - ], - "nightmare": [ - "1000200300040000", - "1002000300004000", - "1004000200003000", - "1400000000002030", - "2000100200000030", - "2000104000300000", - "2001030000020000", - "2010000000000403", - "2010000043000000", - "2030000000004001", - "2300000010000002", - "3000002000040100", - "3001000000002400", - "3002040000000003", - "3020000000000403", - "4000003000101000", - "4000031000300000", - "4000204000000003", - "4002000000000103", - "4300000000200400" - ] - }, - "3x2": { - "easy": [ - "425361136520241053003142350016610230", - "123405506012002530354126230641461050", - "215460640001050612160350501246426035", - "143560526041410023362054201030034010", - "314065056314461023500106030051005632", - "620000305106504362263014136005052631", - "015204064315400051106003523040041532", - "020306346152050010614200061403432061", - "065400342000534201620304206143403605", - "305010421635106503530401650002204006", - "342016106003403650020301064100531064", - "413625265300120403050010002164600502", - "512406300512200054030620154200603140", - "000040540231461050230164154020600015", - "040203032045050026261534600052420001", - "160052020060536010240035050320312540", - "210036630050542603063024300145400060", - "241530500004050312002405400253025601", - "301500050040432600610230560123123400", - "614205003016040301136542400003060054" - ], - "medium": [ - "050000000251001300040102410500300004", - "460001005042000153000000530004620000", - "000000500063040256060000423005100002", - "004600000000102300506420050060021500", - "005000041000500120210503400005003002", - "005000300004000006003020002015150243", - "120000540000250064600100000020002015", - "200010400600001520500160000040040206", - "450300300240020000040502200006600020", - "504000310400043050100000030506060001", - "002006000502325400140300050000200004", - "600013000050000100025060060230201500", - "000040100523020000600050002100301002", - "000560005040000230600015530000410000", - "040035000106520000300004062000405000", - "050010000003104300020060005100001230", - "056002200005002001400050000600023010", - "060000010062026310050006005400000003", - "461000000001010520602000003260000100", - "500040146035450000001000005000030100" - ], - "hard": [ - "060210050300003450000000020100001000", - "104020002000000200013050500301000000", - "210000004300020540000001060000001400", - "300260500000002006000504000001010040", - "400000001640000002500034100000003010", - "400610200340004001000200050000300000", - "500100000260000004052000000030100450", - "502000000000004360000005030240006050", - "600001042006000000060003000500001604", - "602000000004000163010000501400000001", - "000000602000000005401300300621000030", - "000006100004020030000061030102005000", - "000020000615150000003000046003000400", - "000046000000206004003000100032000601", - "000400000003000006410500003650065000", - "000500050601003006040100004000300400", - "003000000602000001000250300005420003", - "006401000500000300100000020050001603", - "010000006000302400000000403100160003", - "010403000001040002000500300000065200" - ], - "nightmare": [ - "000006020500050003100002300000500600", - "000060600500000040031000025000060003", - "001024000300630000000003000000065001", - "003000000005060010100402014000000001", - "004000063000600002000050001030200004", - "004001000600200050041030020000100000", - "006000040100002003600002000536000000", - "006100000030000600000040054300030050", - "030045020000006000003010500002000006", - "060000000503604000000002105030000001", - "060000102050000042000010005000000034", - "305004000010000200050000046050000002", - "400000001032000020000500030040500001", - "406300300100000000000513004006000000", - "502010000004000000010020030000100540", - "600000050000006004200005000150010060", - "600010003000065000000000200106000520", - "000000005002000000431000002040000305", - "003001020000400005060400600000001000", - "200016000000006000000002000500604300" - ] - }, - "3x3": { - "easy": [ - "751283946482769351936451827629074138045398672378610095217046583803025764564837210", - "560810042803764150010320876438671025256400731791502684379006518645187093182953467", - "054283906007915032239674001390167008745020169601459327923846715406501203518732694", - "840257069026039478000846302362510907700362540154098603235681794017920836680473215", - "281600947960271508070894012010946375496730081530128496629017853140082009750069124", - "710290485490517602260384970047650298829470100651928743986045307170809504504702809", - "070081395814035620953276814200158743045369081138704069060847152521000070487512030", - "401975026060128309289306175305009714790512083816030592143000968972063451650400237", - "100398465305104800890002371632700598009603710471985206253410907746039102918057643", - "548009210910426005007581394401963782826174950379850460105007600694218537000695108", - "010428009920653800480017623863079245594206730271534960700860300008042190102795486", - "459203017387415290000798054062307900731984600040020030674139082198572403523800179", - "700009816980016720651872409460208351025347608308165004006924187814050002279081043", - "400057210728031459500402670000389026800165934963720085317900560284006301609203847", - "050980043197643285048572196589307402000000010710068359435120900971835020820700031", - "410000050926570108350410729030045960895067402004032810271308604083094271049720583", - "597631004128907305600508179002059046480763002906200730735492618204806053000010427", - "020359174035187060791406800000534006600802401300761598268910347510600082073048610", - "028569470043180650507204018800400720090608031200703006480070195015942387079805264", - "095007641640589020073006859104203590307095006580614702950060070418730960736001084" - ], - "medium": [ - "043700080007085321000360700500890070080547263024610058405130890208074036369058010", - "060300010549000026071900548654130709093460050102570604425603870800050200907201405", - "304008765859000300627453000793800002000100430146325007275030640038060001401580973", - "475631002016940300903870014080106740040003006702584139004009078090008401608417900", - "020196030790080206306702100070430028060020301083671050807300060012057093935264010", - "280315009360070500540869320070000031026138975138050200700500692650001700093720004", - "574023098239080064106947003860204031000000605340006280412309050000062010093805470", - "602503010030740080417086003854012006096070501020605000279008164040160278008427005", - "703004058090065047840070190671509800000406725254300619026908401010040563000600902", - "920451030045026079061080045170208400294500600008040912412070308050830001703160090", - "014095070020140050635280140700514900000903867093608500002439785300050002070860430", - "207006158891574620000000700028030471053420900900081205002040569039100047486700300", - "304027095501043200200000010892070140615400720000281956006010472708002501123050800", - "320608047700420010060197000573001008000530071416089020008302000297815364630970002", - "905624083000038070320001406457092001209500748100070009092147360736000910510360000", - "147080526030070000980254071300460907004013602008705010462897005859000004700046200", - "470308605950260304003050000200005070005603142300192506530000000104586709086034251", - "804250931090830046170040582020080400000702059300094020010520094508610003002408165", - "843106057126050400700004060580600721001205094279010036300001005000902018918503600", - "946010350001000002730040186250481093090700020604030875028000500105608234060023708" - ], - "hard": [ - "000050000020190070036740000060400800503008704004009100601900230209007600008020917", - "504700003810093200023061800701026009002300008380000501000030000049100050208000090", - "006300809780590023003008670172849365000050000050000007000080730000205010000060090", - "050040107900817605071600900000060700000080003300002500020401006607200409090036800", - "070408000001000900836010020007002830100500762000607100760000210508701003003000079", - "083009001651000008700518000007986100860000429019000006500040000906053010000007300", - "200568703009173004003200000907000512000005400032700800080000001300600240720410000", - "370280106009700020000000073068070000590430060700010490850090000930108507046000000", - "509040078401600020700005600070000460180070500056412300042000800890000003005008700", - "635000010017030040804000000963100200000042090040000871001053960000007400006429030", - "470350009000008247809470500000000726000926850000700000708000105305810902000000070", - "600905030200067800097400020024050001000080950809010003000096002480300019030040700", - "140000083027005140600000900700236051403050069000014000000700002000090610094000530", - "700520400001708000060000100050000678000480210608005000000130894300040000149057002", - "907000060620000501043069008070004000090057000400908070350000010060091052100006904", - "513740600020160005000080300700000000458900026000054097004000060082006500000400738", - "000000200000300081200080396071020038090400107000003509000034000807209000023750810", - "000782030214305090000109500058201000439600000007000600300410007900000403700003002", - "003900000047080000956002378000065014800730500005200000000129000090070420021300060", - "005093100690080740128060000000008000061400050709600010904007020080002007050000481" - ], - "nightmare": [ - "058001093200030004000200071905420000000007050000100000080304060703900005006810700", - "060140000720000000040875060070000493092007800004050000000090740000060025058004001", - "068070000003004000000180370900407601000901007000030040009043108057000000030602090", - "005208010000000809128690004000020000006900430009040208080014000004002700000009600", - "009007400030600000000485000200030107000070020170204600546700092800000040010000050", - "020500603000300004009024750050200006008000200012603000034000000507000001280900070", - "090304005000200490050086302300009250900000060070050130063000710020000500010000000", - "390007801508360020040950607000000000800003910700040005000030700010000086000200040", - "012034050000009640030600000784001300003080060000300078000002030005006000068000907", - "000000000050078200000590870600200100007004500004003008500000723040000006276030401", - "002000008070040031500890007009200000000503040040000150610000000000368700780150003", - "800000650300000080005007002108790400600050100052040060003000200000019000500003748", - "800004305000000790001000000010805000090100000200600184700200041402006070000037026", - "000000012240601009000300050600100020000700003320040605460008001900000008000036900", - "000409003750060040000000206000031079000000832500702600400007000000314060006000001", - "000000500009106430060090000008000050004000982300980007000035610017000090000000270", - "073002600200105008000000000729306100040000900000008004000003000007560001098200006", - "800003000050001079006079003000200001900700060000010305032090506000002000087000040", - "000800000039605400008000603005390000700006000380170004002900100004020306010000000", - "200000090500206700816700003400100000000050030900084520000000807000620050000000062" - ] - }, - "4x4": { - "easy": [ - "00B6483AE20FDC91GA8F0E659D3172B42D9CGF71406B538E03E42B9D8C57GFA6C2G06184DFEA9B7596105GE724C3FAD8A45DF32B6709E1CGEF78A9DCB01G64327860BC423GAE19FD30AED51F7040CG2BBGF13000C9D20507DC02E7G9F1B508634EC9825613FDB7GA81D79AFG00263E4CFB3G1D00A0042659650A74B30E9C8D1F", - "G8D13540972EFB0A3BC980EDA61457G2AF5721B683CGED0404267AG9DBF53018C7381BAFEGD92645BGA40EC7056018F95DF098647CA1GE3B916E532008BFC07D4EGA091BC20D0F8385B00732G906D4C16C1D0G00B473A92E297300DEF18060B0F38CG09A5047B1E602E5B6831A9C40DF064BED050FG093ACDA9G40F16E3B8257", - "48F0071693BC5DEA613CD5E80G0AB42029EA3C4B8051F0600D5B9F0G0426831C0C057634129E0B0G9218GB05F0A0E6430060E82C4530D9F10E431AF9D86GC75213865907GFE24ACD0000C1G36A842590C000FD6A5003700E059F4E82CB7D0106E40D625107G0003B3B790GCD014F6EA05601B47F3EC9G2D88FG2A09EB0D51C74", - "0381D0GA9FB06C20G0600842E03CFD50EFC5003BD42A1070A24D0F05800G3B9E00B30CFG19AE24D6800C041070F0G9EB4E0F00B902G8C531091G2DE35C4B708F0030A28CG617DEF0DC780B5F2E94AG130006E0D70A83024C2GAE439005D006B71D0230C40BE690A894F7G16EA852B0CDC6EAB9203D714FG5380BF7AD0G09E162", - "1DEF0A5C47209B865B7948D20E003AG000000763D0005E4F6003BE0085C0120D01026500E3D480C0030D841EC0F9002B8F9C020BAG15E364E64G093002B8AD51B0D8A3460157FC9GCGF19D853406200A40362FC7BA9GD01E92A0EGB1FC8D6430D80B7194GF3EC5A2GAC406285D700003F9253BEG680041D730105CAD9B42G608", - "G9D05382CEA06BF445EC19D0G06F0780F08070A0451D009EA700G4FE9B830C0D6C159B40AFG0D8E783B020107000FA0020FDA00059C4B163740AED6008300905C0A0F694D0EG851BE64080201C5B73G95130C0G7FA904D20DG9831B52046CEAF1D700208E40A960C382E00C96D75AFB1BFC407E031295GD89A56DF018GBCE472", - "0DC63B47F058G20AF0102E06DBAGC30032A009FD06E048500GE8005A3490DB6F2AFCE0005G01B0085047AGCFB230169E1E905003008A0700B63G1D78E9F45A2CAF736000C5198GDB600298DGA740E0F300B9FA3008DE05160C8E7500036F29A44900D3A00EB561C78705BFE90A0600G2E3008C600DG790B0C00147009F23A08D", - "AE3G6B980120DF0C0C007040900D06EA97185D0F00642BG3460523CAE0B0098004500F120EC798A6F06AD00E0G980254C829B076F403GD0E70E100A96052C3B0D04007GC50E60A910F8796B14A3CE5D2EA0600000710B4000193EA8020DF6C702DGC0057864EF13B15B0CEF0327A8G09037F0820D9G100C589AE106305F04720", - "08030C4B0D102A096F940E0580A203071A000F023G4B8C000B000A300F0E0645EC7B24509068F03DA08GB7CF03D402E0D26918A3G0EFB40000F00D9EB00C08GA0105C3D968FAEG727EAC06B020034D9036D2A5F4E9700B0189GFE20004BDA0635GC600EAD2893714FD3A518C4E07692B900E4G20A631500804180076F0C50E0G", - "0900FGE8071A24606G502D9B00CF73A10F17A0408D23G0B02400510000008E0FGBFAE2C0D1050843C8040AF630001000769ED5310080F2GAD03187040F60C09EA10G30020507D084534D0E0G20009AF682CF04DA0E00050097B6C85F030DE12GFCG9400E7AD856024A63GC2DF901B7E80D726F000C4E0G00BE8519A06230400D", - "D0B09E8G203610C4716GF024B590380D00980D0CE0FG0075ECA205B7400060G9A00EB0060GD00407002D005FC6478B30G45602030EB0C0D0073B48ED02A9F05G0G000C0ED47093868D07003A91EFG50B02437098GC5BE0AF0EF5G4D0380A712C56E103A90F0007B000D080016B205G933000D06279C5A0F12B79EFG5A3104C68", - "0E000648F0C1D352000F02GD00B507016000F000D340G0BE0000C105A090F846930C671B2AGD0F850A8004F2EC03961B00D1G8AC0564E200702459030F18CGA0G4530EB0C0D7092F8DB0430051290AG00F090C2G00AB5DE727CA0D0680FGB000E9060F81GB30250AD1A030C9420F7E680G7025640DE03BF935F2AB0E79801400", - "2BD7G0F0C0010590A419E67B50F3G2CD8305D00C7400BE10CF0641020B00A3780030AF0G490DE020E904B706F0008GD0FD205000G0081B07B68G300D007A49050548706F3E0BCDG0DEBA180527GC3F46706FC0E3A004918B90C000D0168F57EA0A0BFD006C1970303C0295B080470A0E007080000F3E2C59089E6037D2A0F4B1", - "032E0B061AC89DG4G0400C012B5930006B80290GE04D51C090C5084D0G0F002BADE090020613C8054G685FAE02BC07D300000DG4F50E0B000259601008D4000018A4G07FC936E20DE60214905D07GCAF5F0G0203A400867909D0E0680F2G4531000C008A07050002207601398EG0D4FAD0GB462501FA79008A0100D70C62B050", - "FGE427ACB09056D15C72BDE9G416A00000B6F301720EG490091064000C0D27EB010CA00240GB3079089A0070053162G06E07810B0D29000F20FGE50307001CBD8B20000006D4931A03A19000EB007D0540C01E0A39780G629760003520AG0EC0E54072B09A60D13GC600G91F8302E05770G930CD1F05002000835000DGB7C0F6", - "008A3259016FG0D4CD5070063E9A028134960G0807D00FE00F20D0BE0G08960061EFA54G0372D8C9G3ABFC20608901705040E890AF1D030G0970B163ECG0000006C809021030FE0A0010C03A042G7050F0G0500189AEC040A0B0GE8F7DC6291300D003AC48000590903C00E5G60184AD000590G400E31C0B0A0E801B905C00F0", - "16G20E8C50A00F490FD03190G70405007E390D450C00A2G8C040A6F028091D7E008G07C9A0406B53B0564GE0D00098000A20001B69750G004C006250EGB0D100A20FC9G6850BE431G8C12B7D009006FA000005AEF0G07082596EF400721000DBDG0000600E210A0C9500GFB70386001D60089A21CD50F0B0031BE0049AFG8705", - "247301050B000FDCCD800GBE450F10066F0007C908GD003EG90E00400C360080AC541E9B00D708FG90G27A0061080BC0D0300C800A5G00E701F7G600E40B30000B08092GF000CD64106FEB0A8D4C000052ED0413090AF0704309F006G200AEB1FA90000C57B4830DE8DC05A03G92704F302140F0C0AE0009B04693G7DF81EC2A", - "A064E087DFC3B29190080A0CEB106400G00093D460700F0AF00E006000A0D85758BC64F0A0E900G004G7B1A3F856C92E13900075B40D06AFE6FA800D312C754BB056A010C00G3D742A0G30BF0E009C6881E90020000F00B50703D590060000E2C00D263A40075B0039050B4800DEA0F0700F0D01906B00800G400FC01A852ED3", - "00B0070800E605FD800000G1039D0C0200D5FB49000C600130G9C05DF0B0870E0286B09FE5G731C40701AC649D8300009DC4G870100006500F0B1205006008975G02D31C04FA097019FC0EBA27DG5308030009F0B6C01DEG7B0D048G0E19CA2F0C0G70AEDB302F10059E41320000GB8CB0170006G20000D30A2380DB00407060" - ], - "medium": [ - "00B6483AE20FDC91GA8F0E659D3172B42D9CGF71406B538E03E42B9D8C57GFA6C2G06184DFEA9B7596105GE724C3FAD8A45DF32B6709E1CGEF78A9DCB01G64327860BC423GAE19FD30AED51F7040CG2BBGF13000C9D20507DC02E7G9F1B508634EC9825613FDB7GA81D79AFG00263E4CFB3G1D00A0042659650A74B30E9C8D1F", - "G8D13540972EFB0A3BC980EDA61457G2AF5721B683CGED0404267AG9DBF53018C7381BAFEGD92645BGA40EC7056018F95DF098647CA1GE3B916E532008BFC07D4EGA091BC20D0F8385B00732G906D4C16C1D0G00B473A92E297300DEF18060B0F38CG09A5047B1E602E5B6831A9C40DF064BED050FG093ACDA9G40F16E3B8257", - "48F0071693BC5DEA613CD5E80G0AB42029EA3C4B8051F0600D5B9F0G0426831C0C057634129E0B0G9218GB05F0A0E6430060E82C4530D9F10E431AF9D86GC75213865907GFE24ACD0000C1G36A842590C000FD6A5003700E059F4E82CB7D0106E40D625107G0003B3B790GCD014F6EA05601B47F3EC9G2D88FG2A09EB0D51C74", - "0381D0GA9FB06C20G0600842E03CFD50EFC5003BD42A1070A24D0F05800G3B9E00B30CFG19AE24D6800C041070F0G9EB4E0F00B902G8C531091G2DE35C4B708F0030A28CG617DEF0DC780B5F2E94AG130006E0D70A83024C2GAE439005D006B71D0230C40BE690A894F7G16EA852B0CDC6EAB9203D714FG5380BF7AD0G09E162", - "1DEF0A5C47209B865B7948D20E003AG000000763D0005E4F6003BE0085C0120D01026500E3D480C0030D841EC0F9002B8F9C020BAG15E364E64G093002B8AD51B0D8A3460157FC9GCGF19D853406200A40362FC7BA9GD01E92A0EGB1FC8D6430D80B7194GF3EC5A2GAC406285D700003F9253BEG680041D730105CAD9B42G608", - "G9D05382CEA06BF445EC19D0G06F0780F08070A0451D009EA700G4FE9B830C0D6C159B40AFG0D8E783B020107000FA0020FDA00059C4B163740AED6008300905C0A0F694D0EG851BE64080201C5B73G95130C0G7FA904D20DG9831B52046CEAF1D700208E40A960C382E00C96D75AFB1BFC407E031295GD89A56DF018GBCE472", - "0DC63B47F058G20AF0102E06DBAGC30032A009FD06E048500GE8005A3490DB6F2AFCE0005G01B0085047AGCFB230169E1E905003008A0700B63G1D78E9F45A2CAF736000C5198GDB600298DGA740E0F300B9FA3008DE05160C8E7500036F29A44900D3A00EB561C78705BFE90A0600G2E3008C600DG790B0C00147009F23A08D", - "AE3G6B980120DF0C0C007040900D06EA97185D0F00642BG3460523CAE0B0098004500F120EC798A6F06AD00E0G980254C829B076F403GD0E70E100A96052C3B0D04007GC50E60A910F8796B14A3CE5D2EA0600000710B4000193EA8020DF6C702DGC0057864EF13B15B0CEF0327A8G09037F0820D9G100C589AE106305F04720", - "08030C4B0D102A096F940E0580A203071A000F023G4B8C000B000A300F0E0645EC7B24509068F03DA08GB7CF03D402E0D26918A3G0EFB40000F00D9EB00C08GA0105C3D968FAEG727EAC06B020034D9036D2A5F4E9700B0189GFE20004BDA0635GC600EAD2893714FD3A518C4E07692B900E4G20A631500804180076F0C50E0G", - "0900FGE8071A24606G502D9B00CF73A10F17A0408D23G0B02400510000008E0FGBFAE2C0D1050843C8040AF630001000769ED5310080F2GAD03187040F60C09EA10G30020507D084534D0E0G20009AF682CF04DA0E00050097B6C85F030DE12GFCG9400E7AD856024A63GC2DF901B7E80D726F000C4E0G00BE8519A06230400D", - "D0B09E8G203610C4716GF024B590380D00980D0CE0FG0075ECA205B7400060G9A00EB0060GD00407002D005FC6478B30G45602030EB0C0D0073B48ED02A9F05G0G000C0ED47093868D07003A91EFG50B02437098GC5BE0AF0EF5G4D0380A712C56E103A90F0007B000D080016B205G933000D06279C5A0F12B79EFG5A3104C68" - ], - "hard": [ - "B329800F0140D0A0G700E0290500B6316058G01D2F00C079010CB005G00928FE00BG000E00230D073DF009G00C08001480E002F107G0A39C0A1034DB695F00G27000A08C90010000000B0E325G871006AF010090D000GCE820GED1B60AF07903F0CD96E0701405B000801D50020G074004302008FD9561CG0G754FC030E09000", - "G000070A9008C00BD040C02B603GE9050200G3091004D8A000690105E0A0F0326002A0D050EC00FG8FA06017300D5C0E0050F9B3000A20063G905E000600B08D1030BC0D20F0GE602E00958FD00700C4900F060EBA8372015086027004009BDF00F0ED9000C1602046D01AF070928000B92G705CF30610EA01E02036A8D54FG0", - "00G05A0E300000009C0A0004G201705EF07E1000A6B50G40010870DF904CB00A2D6430G510CE000FAE0G0180042FC0D3CF05AB42000D6E017010E0FC000024B50000C00025E40D86G9C10F0B006045E250ED2038B1G7900C0A0200E0FCD93B1700A3G000CF0B0804000C80B0E00000908B90F0C100A0023D1G200300D856EC7B", - "08B007E90F03042D0040830G00D2E015950DAC0B08013600E0F750D09C04B08G8000007102009E6CD000BE300706080A00E000A639087001700A28900G4E003B60C0028E0590A0D03A8E95CF4D17600204090B1D0A3G8C0EF1000A632000570400600050G02000E04E920FG0C65D1BA0C0031628B4EA0F0010A5ED04007002C6", - "10F3000C0A600502A0C8F9B320106D70B0G0052D0EF70000D2004600C09B00E000B0000G9C00A6585C6A3840ED009B07009F6B00A15402D33GD2AF50B780E40C8F30009460BA100EG500B3000901082600A9G00000CEF00B000001F0G340D9A003007EDF14028A60900714365F0C2EBGF42G0A8B000057016A002CG08070000D", - "CF004000906020107428000900010600D0050000230E70409310800B7C4DEFG5514003BC00800G6F000079F0G4250A000ED76A8G00FC35B0G8A0E4000B03927039040502000AG0010DF10G98300467A06A0GD743000B080080020F0EC09G04D0F5BD30E040G9A000420C9BGFE0108D50A08E521607CF0B30179608040500FCEG", - "30400000070005000FC84050B000D970E0D003A04000F6GB0050E9B8CF0G010000B005E4800300909E36B100F02D075A000400000000E30170A090236E00C400276E001B5G349F8054002E3FD006G000ADFC000609B70E148003D479A10E52600C8G300596721A00F31A0B00G0E57C4606E010GC30AF2800D07260FE0840B035", - "0B0G017A00C080390390D240700BC0G01420000CGA900F5000CF0GB00328040ADA30000000G7000FB1560AF08D009740G040CD07250F00A079F0G004301A00D8C074B0D86E300190060BA0C900D150000010F70G00A548B6008A0E01B042000C80A1E06000B00DC702000C005000G084476520GD0C03F001FCBD48A31G0E2965", - "193580400A6DF200AEG010B0040758D68B0DF50C129G4007020706005008100AF60C7D2A050304E0750B010F4009030800025C90670E0D0F409AEB800C0F200568002A7000F1350GD009B3F000C46080B3040G00A002E00DG001000836D007C05C700010D9008A0E0006A802000C0F549AF80EC700500G0000DG30560E8A00B1", - "8076FGE9500DB1C0B2A934100000807004050B0C900A0306F000006A38BG940537600C82DB04G009090B0000760C0003D08049300E02670C0G04E6700980DA02000008G000091F5790070001BF650D48100000B0200700A00A0E00FD83C12B9G08900FC0G00B0260A650GD080700EC0B41B06200C50EA8DFC0DFBA53004800G1", - "C4D0FB83050200060G105000D009A70B89050070310600CDE000D0028CA0300001C2070D089A6400400000A90D0EB0006009004B00F38C000D0E6F0804C19G2A0800CD07900B56020F000805001D003C060DB020CE05F8099000E3GA4680D07105A4700F100C0EB8FC080254E0D7136G070G81BEA3040F95000B309C0G580DA0", - "0000AE000B0264080960000070C01A00B3080074E1A0050D10F0DG006895B0E70G0BC13D2F007605000086EB045AC2008C0300451009FD0E00E5F9A70C0301000B4920C600000G73F60000095738000AE8GD070002460C907030E41809GC0B6F0F709360400BE0D004B0700E0A00300G000G1C020360A00B310C0A0F9GED0006", - "0G0BC01D72000405000860320GD0BE7C0100G500AF00093D407D0EF830C01200F0070009GD010080004A80E0900F2D510200D0B0003ACG0F1C00020G8600009A001638705E0DG0B0083FE00C6B9G5A10A00G0B00F002360427B50DG6000C980E0A049F0E01G30500700E006A00004BC380920G00B0F0D1E6CD00730B040EA0G9", - "95AE0008GF7D000B80005200B4EC0FA323C00FD090600408B004GE1C30280D00600020CE008795F0020C0050003F00D0F0E70B0020153A400A9304076CDB80020008E104079A560G3B0FD0028146EC00A149000650BED83FE050B80F00000214C0F2000510A00G0E403A002G00000007G06089E07BF000C07E1B0003C0G20900", - "B1C047820003056900000903060B0E0C08030065D29C1GA79000FACD18G00043870CA5160E304FBGG20F74300A00C800A03BD0F864C090020001200B7G80A03000G000B05002000D09BD630A01E07080000050700CD6GA0B00E090D083B0005F0D4000A00000F0GE2F003D57G90EB000E0700000ADF139003GA68F9EC0205701", - "G0DA30004010085004080000305000E0002085E060A94BG00390704B8000A26DEB0900F003006A08A8F50B040090D301207DC090000850BE063C005D1GE09F720500B20AE14D398619034EC0062A000BD00EG300B07F010507A6D00F003CE00G7064000002B0000A0C1264AEF08G05070G00003750000C0F900F2G0C7ED08004", - "00700300A0DC000F960C85EF04G230A03A0109D200070000800GC00A0F530ED00036700190F00040008F040G75EA9D36000E0B39002D7C00270008F043C051BE00000000F000C0250D03027C0005B010C002010BE064807G14005080C2BG00E0F0C00000D00E6791794A0DB5001F00C0E2001F6750A000G36310EC940G78F05B", - "0F0B206D000085CA0170B00000280G00G80400EF05007000A60000G907D0130E40F0GC000D000E600C080BF6G04100720B0009000006040G63G07D40020A0005C0409287D0E00B13300654D0B002E0G0090560007384D0FC00B0FE10A90C5847000900002A3FG0088G0DC1240B0E0006FE3205900C1DA7B475613F0A080G20ED", - "4C1A0F090GBE3D570GD35000C08A000F0020CEBA041D8906E900000D05F0C00A0E897GF40200000024C009A800D50EF01BF0035097000602D0060B000E000894060430G0F0095A0080GE4A90DC00BF01000000E048A6237052010076B00004DC0100B00F5A0360E0700290DGE00000A500EF07612DGB40000D6B05030F741G00", - "G60B9F0DEC0A4508F10040200000DA004AC2580B0G000F900E0000C35F00020BE2B306A000975400000000004083EC2A1F003500006E000754002E00DA00B1366000009FBE02AD040DG06705C0000900380ED14A907G2BC0AB090200801D0050B5600C37F2001009D340B0G160E000020701E456A039F00000EAFD0000CB6305" - ], - "nightmare": [ - "0000AE000B0264080960000070C01A00B3080074E1A0050D10F0DG006895B0E70G0BC13D2F007605000086EB045AC2008C0300451009FD0E00E5F9A70C0301000B4920C600000G73F60000095738000AE8GD070002460C907030E41809GC0B6F0F709360400BE0D004B0700E0A00300G000G1C020360A00B310C0A0F9GED0006", - "0G0BC01D72000405000860320GD0BE7C0100G500AF00093D407D0EF830C01200F0070009GD010080004A80E0900F2D510200D0B0003ACG0F1C00020G8600009A001638705E0DG0B0083FE00C6B9G5A10A00G0B00F002360427B50DG6000C980E0A049F0E01G30500700E006A00004BC380920G00B0F0D1E6CD00730B040EA0G9", - "95AE0008GF7D000B80005200B4EC0FA323C00FD090600408B004GE1C30280D00600020CE008795F0020C0050003F00D0F0E70B0020153A400A9304076CDB80020008E104079A560G3B0FD0028146EC00A149000650BED83FE050B80F00000214C0F2000510A00G0E403A002G00000007G06089E07BF000C07E1B0003C0G20900", - "B1C047820003056900000903060B0E0C08030065D29C1GA79000FACD18G00043870CA5160E304FBGG20F74300A00C800A03BD0F864C090020001200B7G80A03000G000B05002000D09BD630A01E07080000050700CD6GA0B00E090D083B0005F0D4000A00000F0GE2F003D57G90EB000E0700000ADF139003GA68F9EC0205701", - "G0DA30004010085004080000305000E0002085E060A94BG00390704B8000A26DEB0900F003006A08A8F50B040090D301207DC090000850BE063C005D1GE09F720500B20AE14D398619034EC0062A000BD00EG300B07F010507A6D00F003CE00G7064000002B0000A0C1264AEF08G05070G00003750000C0F900F2G0C7ED08004", - "00700300A0DC000F960C85EF04G230A03A0109D200070000800GC00A0F530ED00036700190F00040008F040G75EA9D36000E0B39002D7C00270008F043C051BE00000000F000C0250D03027C0005B010C002010BE064807G14005080C2BG00E0F0C00000D00E6791794A0DB5001F00C0E2001F6750A000G36310EC940G78F05B", - "0F0B206D000085CA0170B00000280G00G80400EF05007000A60000G907D0130E40F0GC000D000E600C080BF6G04100720B0009000006040G63G07D40020A0005C0409287D0E00B13300654D0B002E0G0090560007384D0FC00B0FE10A90C5847000900002A3FG0088G0DC1240B0E0006FE3205900C1DA7B475613F0A080G20ED", - "4C1A0F090GBE3D570GD35000C08A000F0020CEBA041D8906E900000D05F0C00A0E897GF40200000024C009A800D50EF01BF0035097000602D0060B000E000894060430G0F0095A0080GE4A90DC00BF01000000E048A6237052010076B00004DC0100B00F5A0360E0700290DGE00000A500EF07612DGB40000D6B05030F741G00", - "G60B9F0DEC0A4508F10040200000DA004AC2580B0G000F900E0000C35F00020BE2B306A000975400000000004083EC2A1F003500006E000754002E00DA00B1366000009FBE02AD040DG06705C0000900380ED14A907G2BC0AB090200801D0050B5600C37F2001009D340B0G160E000020701E456A039F00000EAFD0000CB6305", - "GB824DAC100300500400B2938050000E0951G700A20D000C00300F150G90B0020019000070600F30CFA7004BD00000003605A02E0008007040G0F6075000010D25EB046030090A0F0CF6E5B0GD80042790007AG0E00F6D01D07A03800B2000E000040C70B80EG610012G0EF003AC790B000D0B3109G5A2C08A0C00062071DEF3", - "G003C00820015006F00C5D04G09010A712003A70050C8BD90A0021E000B003FG30000007A00F008E05B0F02090863G7098A0B05310D00020D60E80003400F15BBD0906A0583EG000AG30004000000508000098004F0D07B20F00D0G00A100C9300F0E000B24A00CD70500201DG00083FE08D40B0FC702AG1000A070F806304E0", - "00ABFE5006009081FD7496G3000805206C528710F000D4E3918EAC0200306F0700DGB3600501F00C09B00F0100G00700163000000B0AG050450700C00080B60080C0E500091F00A0G01AC0300E6B400FDF0502B0G3A0C0987069G00F004C1B05040100000000000D000071284G0E500907004B0502D08100BA000D9010567200", - "70200805ECG1490D840G0920070D00000F51E00DB00602370D00C7A00040B5GE03A00D680B2E105990DE503081AF00CBB0F000000470E8D0008500000D000G0AE26FD07C000090B0000403E6A009C7200B70800006F030E5G093A0127000008409G841C06E0A53F02EB7F053081G040000C0GB000F020000F0002AD000000B10", - "00900DEF6210005A60CAB9420D5700E0000E0007000C40080075AC000F9E60001E80FG60040A507090GB20000300140CF0070AC498B1G020C020010DF0E0806020AF0E96B70G30D0760410D50AF9E000E300C0FG000800000G1C7B3000D290800900E000G53B0007B0000351700400000030G708200DC940G000D0A0108F2B0E", - "0105D082GCA0E007000063CB70D04008DEGC7F0430B850090700000509000F0D007810F02E0AG0CB00CDA70008G01062F00600BECD50800090008G000400DE0000000801E000000006B10CGD90800A040CD00003A671B09000FE9060B0CD71530002E03F87940CG100E00A0800FC90D00093000G5A00F076000F4597D0000200", - "2000DG601B097000F7000AB100050090DCE84925G30A000005007CF86E0D0G30G000135D0008C94F000620809FG000500A8F04000000E1670009060701E302AG0053C74G80960DF0000A0109005008000G028500BC0E9070B89D00A60030540C000008G00010F60006BE00040980G00082F0A000740010D040010F7CE5023A00", - "89506B0000700E0F00E003G861CF5DB00C1B570E4820G36A06G00001005A09470035BE00000G700CE8670G35B0000094B2D00AF000E5600G0004000276A000E00GA000860E4000730B00A00F0037400603080D002060905E5D46E070F90B00080481009G0700CB35000904B0C000E001250071E00004A08D000D00000208F000", - "ED0CG300820B01AF0000000065040C3003G1F0400D90607E608BA7C0FE0G0200A040000508F7006B3B0600F20409GDC0C52FD07000GA000801E00CBG53000700D8043600000F9G01003750G000A2E00C09C20080006E3A00G060000A3100740000000D082G060097000E40309F81A0G0970G2060D000800446100000E700D003", - "000003G0F040E56010E60A0D7800F00400009F0C010E003A0DF07000C60A08120E0C00BG5A0160496000000000008A000934500062EB1000G21BA0E60F9D7000D0G000030B02A170B0A008250G06040D00C0D10704F50EG05800B00000D00000F00A1002D3604B07030700DB1EA00086EB00G57F09C003A081000E00B0270D0C", - "1G05B289ADE0000608070500000300A003E04D00620G0B8062C0G0A3589B400000000E0G00450000047000003A6C0G00AD8003CB000204000C037000DB89A5008769DGB0C3A1FE05D02GF007800000C100140030B90D7AG8003C80250070B09000G008000CB00F00719004E62500G8DA0F0800G0760000000000105000D8C069" - ] - } - } -} diff --git a/assets/fonts/Nunito-Bold.ttf b/assets/fonts/Nunito-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..6519feb781449ebe0015cbc74dfd9e13110fbba9 Binary files /dev/null and b/assets/fonts/Nunito-Bold.ttf differ diff --git a/assets/fonts/Nunito-Light.ttf b/assets/fonts/Nunito-Light.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8a0736c41cd6c2a1225d356bf274de1d0afc3497 Binary files /dev/null and b/assets/fonts/Nunito-Light.ttf differ diff --git a/assets/fonts/Nunito-Medium.ttf b/assets/fonts/Nunito-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..88fccdc0638b6f5d6ac49d9d269dc3d518618ad1 Binary files /dev/null and b/assets/fonts/Nunito-Medium.ttf differ diff --git a/assets/fonts/Nunito-Regular.ttf b/assets/fonts/Nunito-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e7b8375a896ef0cd8e06730a78c84532b377e784 Binary files /dev/null and b/assets/fonts/Nunito-Regular.ttf differ diff --git a/assets/icons/button_delete_saved_game.png b/assets/icons/button_delete_saved_game.png deleted file mode 100644 index 5e4f217689b11e444b7163557d7e5d68f3bbfe7d..0000000000000000000000000000000000000000 Binary files a/assets/icons/button_delete_saved_game.png and /dev/null differ diff --git a/assets/icons/button_resume_game.png b/assets/icons/button_resume_game.png deleted file mode 100644 index b2ea0a02d05e42377eb551a4b51428b511a32f5d..0000000000000000000000000000000000000000 Binary files a/assets/icons/button_resume_game.png and /dev/null differ diff --git a/assets/icons/level_easy.png b/assets/icons/level_easy.png deleted file mode 100644 index aa9e2485272c12fd99d9e1faae5d71d027ee5431..0000000000000000000000000000000000000000 Binary files a/assets/icons/level_easy.png and /dev/null differ diff --git a/assets/icons/level_hard.png b/assets/icons/level_hard.png deleted file mode 100644 index 433c686b984fd22c4966def3dedcaf7384951810..0000000000000000000000000000000000000000 Binary files a/assets/icons/level_hard.png and /dev/null differ diff --git a/assets/icons/level_medium.png b/assets/icons/level_medium.png deleted file mode 100644 index 213f2ca844afb0cea47cc4bdd1b865433b068b59..0000000000000000000000000000000000000000 Binary files a/assets/icons/level_medium.png and /dev/null differ diff --git a/assets/icons/level_nightmare.png b/assets/icons/level_nightmare.png deleted file mode 100644 index df4182c1e228bbb33c5546d2df38d16ca1edd4f2..0000000000000000000000000000000000000000 Binary files a/assets/icons/level_nightmare.png and /dev/null differ diff --git a/assets/icons/size_2x2.png b/assets/icons/size_2x2.png deleted file mode 100644 index a8ac1675f003cf6815af46243f6a1cabaaef39af..0000000000000000000000000000000000000000 Binary files a/assets/icons/size_2x2.png and /dev/null differ diff --git a/assets/icons/size_3x2.png b/assets/icons/size_3x2.png deleted file mode 100644 index 9b4aa9a8f2a5e4d81c6029749ba52202973e69f3..0000000000000000000000000000000000000000 Binary files a/assets/icons/size_3x2.png and /dev/null differ diff --git a/assets/icons/size_3x3.png b/assets/icons/size_3x3.png deleted file mode 100644 index 77d6fb098cb8fb69f3632afcf2bef65c2d15ef4d..0000000000000000000000000000000000000000 Binary files a/assets/icons/size_3x3.png and /dev/null differ diff --git a/assets/icons/size_4x4.png b/assets/icons/size_4x4.png deleted file mode 100644 index 9ed5ea90e13d2eb476c430a7a01108b207dcf0b0..0000000000000000000000000000000000000000 Binary files a/assets/icons/size_4x4.png and /dev/null differ diff --git a/assets/icons/skin_default.png b/assets/icons/skin_digits.png similarity index 100% rename from assets/icons/skin_default.png rename to assets/icons/skin_digits.png diff --git a/assets/skins/default_1.png b/assets/skins/digits_1.png similarity index 100% rename from assets/skins/default_1.png rename to assets/skins/digits_1.png diff --git a/assets/skins/default_10.png b/assets/skins/digits_10.png similarity index 100% rename from assets/skins/default_10.png rename to assets/skins/digits_10.png diff --git a/assets/skins/default_11.png b/assets/skins/digits_11.png similarity index 100% rename from assets/skins/default_11.png rename to assets/skins/digits_11.png diff --git a/assets/skins/default_12.png b/assets/skins/digits_12.png similarity index 100% rename from assets/skins/default_12.png rename to assets/skins/digits_12.png diff --git a/assets/skins/default_13.png b/assets/skins/digits_13.png similarity index 100% rename from assets/skins/default_13.png rename to assets/skins/digits_13.png diff --git a/assets/skins/default_14.png b/assets/skins/digits_14.png similarity index 100% rename from assets/skins/default_14.png rename to assets/skins/digits_14.png diff --git a/assets/skins/default_15.png b/assets/skins/digits_15.png similarity index 100% rename from assets/skins/default_15.png rename to assets/skins/digits_15.png diff --git a/assets/skins/default_16.png b/assets/skins/digits_16.png similarity index 100% rename from assets/skins/default_16.png rename to assets/skins/digits_16.png diff --git a/assets/skins/default_2.png b/assets/skins/digits_2.png similarity index 100% rename from assets/skins/default_2.png rename to assets/skins/digits_2.png diff --git a/assets/skins/default_3.png b/assets/skins/digits_3.png similarity index 100% rename from assets/skins/default_3.png rename to assets/skins/digits_3.png diff --git a/assets/skins/default_4.png b/assets/skins/digits_4.png similarity index 100% rename from assets/skins/default_4.png rename to assets/skins/digits_4.png diff --git a/assets/skins/default_5.png b/assets/skins/digits_5.png similarity index 100% rename from assets/skins/default_5.png rename to assets/skins/digits_5.png diff --git a/assets/skins/default_6.png b/assets/skins/digits_6.png similarity index 100% rename from assets/skins/default_6.png rename to assets/skins/digits_6.png diff --git a/assets/skins/default_7.png b/assets/skins/digits_7.png similarity index 100% rename from assets/skins/default_7.png rename to assets/skins/digits_7.png diff --git a/assets/skins/default_8.png b/assets/skins/digits_8.png similarity index 100% rename from assets/skins/default_8.png rename to assets/skins/digits_8.png diff --git a/assets/skins/default_9.png b/assets/skins/digits_9.png similarity index 100% rename from assets/skins/default_9.png rename to assets/skins/digits_9.png diff --git a/assets/translations/en.json b/assets/translations/en.json new file mode 100644 index 0000000000000000000000000000000000000000..a815023fbd03632f2baeabd9374552df05b6d5a1 --- /dev/null +++ b/assets/translations/en.json @@ -0,0 +1,3 @@ +{ + "app_name": "Sudoku" +} diff --git a/assets/translations/fr.json b/assets/translations/fr.json new file mode 100644 index 0000000000000000000000000000000000000000..a815023fbd03632f2baeabd9374552df05b6d5a1 --- /dev/null +++ b/assets/translations/fr.json @@ -0,0 +1,3 @@ +{ + "app_name": "Sudoku" +} diff --git a/fastlane/metadata/android/en-US/changelogs/68.txt b/fastlane/metadata/android/en-US/changelogs/68.txt new file mode 100644 index 0000000000000000000000000000000000000000..d0bff939e5ed989737fa03356b1a04b9996d46b8 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/68.txt @@ -0,0 +1 @@ +Global improve application design. diff --git a/fastlane/metadata/android/fr-FR/changelogs/68.txt b/fastlane/metadata/android/fr-FR/changelogs/68.txt new file mode 100644 index 0000000000000000000000000000000000000000..b11bb3eba14ffff1b9e74b0d019957f5c8e6ce6f --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/68.txt @@ -0,0 +1 @@ +Amélioration globale de la conception de l'application. diff --git a/icons/build_game_icons.sh b/icons/build_game_icons.sh index 4f0f02f7db8b7dc424ca226e225d7e7ef8899b2a..03cd74fc54841271f1cb1f54dfa5c00e75a2f8f6 100755 --- a/icons/build_game_icons.sh +++ b/icons/build_game_icons.sh @@ -18,8 +18,6 @@ ICON_SIZE=192 AVAILABLE_GAME_IMAGES=" button_back button_start - button_resume_game - button_delete_saved_game button_help button_show_conflicts game_win @@ -27,15 +25,9 @@ AVAILABLE_GAME_IMAGES=" cell_empty " -# Settings images -AVAILABLES_GAME_SETTINGS=" - level:easy,medium,hard,nightmare - size:2x2,3x2,3x3,4x4 -" - # Skins AVAILABLE_SKINS=" - default + digits food monsters nature @@ -103,19 +95,6 @@ function build_icon() { optipng ${OPTIPNG_OPTIONS} ${TARGET} } -function build_settings_icons() { - INPUT_STRING="$1" - - SETTING_NAME="$(echo "${INPUT_STRING}" | cut -d":" -f1)" - SETTING_VALUES="$(echo "${INPUT_STRING}" | cut -d":" -f2 | tr "," " ")" - - for SETTING_VALUE in ${SETTING_VALUES} - do - SETTING_CODE="${SETTING_NAME}_${SETTING_VALUE}" - build_icon ${CURRENT_DIR}/${SETTING_CODE}.svg ${ASSETS_DIR}/icons/${SETTING_CODE}.png - done -} - function build_icon_for_skin() { SKIN_CODE="$1" @@ -145,12 +124,6 @@ do build_icon ${CURRENT_DIR}/${GAME_IMAGE}.svg ${ASSETS_DIR}/icons/${GAME_IMAGE}.png done -# build settings images -for GAME_SETTING in ${AVAILABLES_GAME_SETTINGS} -do - build_settings_icons "${GAME_SETTING}" -done - # build skins images for SKIN in ${AVAILABLE_SKINS} do diff --git a/icons/button_delete_saved_game.svg b/icons/button_delete_saved_game.svg deleted file mode 100644 index ac7eefef476f761903fe781b8c86d0c94323550a..0000000000000000000000000000000000000000 --- a/icons/button_delete_saved_game.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#ee7d49" stroke="#fff" stroke-width=".238"/><path d="m61.07 35.601-1.7399 27.837c-0.13442 2.1535-1.9205 3.8312-4.0781 3.8312h-16.84c-2.1576 0-3.9437-1.6777-4.0781-3.8312l-1.7399-27.837h-2.6176c-0.84621 0-1.5323-0.68613-1.5323-1.5323 0-0.84655 0.68613-1.5323 1.5323-1.5323h33.711c0.84621 0 1.5323 0.68578 1.5323 1.5323 0 0.84621-0.68613 1.5323-1.5323 1.5323zm-3.2617 0h-21.953l1.4715 26.674c0.05985 1.0829 0.95531 1.9305 2.0403 1.9305h14.929c1.085 0 1.9804-0.84757 2.0403-1.9305zm-10.977 3.0647c0.78977 0 1.4301 0.6403 1.4301 1.4301v19.614c0 0.78977-0.6403 1.4301-1.4301 1.4301s-1.4301-0.6403-1.4301-1.4301v-19.614c0-0.78977 0.6403-1.4301 1.4301-1.4301zm-6.1293 0c0.80004 0 1.4588 0.62935 1.495 1.4286l0.89647 19.719c0.03182 0.70016-0.50998 1.2933-1.2101 1.3255-0.01915 7.02e-4 -0.03831 1e-3 -0.05781 1e-3 -0.74462 0-1.3596-0.58215-1.4003-1.3261l-1.0757-19.719c-0.0407-0.74701 0.53188-1.3852 1.2786-1.4259 0.02462-0.0014 0.04926-2e-3 0.07388-2e-3zm12.259 0c0.74804 0 1.3541 0.60609 1.3541 1.3541 0 0.02462-3.28e-4 0.04926-0.0017 0.07388l-1.0703 19.618c-0.04379 0.80106-0.70597 1.4281-1.5081 1.4281-0.74804 0-1.3541-0.60609-1.3541-1.3541 0-0.02462 3.49e-4 -0.04925 0.0017-0.07388l1.0703-19.618c0.04379-0.80106 0.70597-1.4281 1.5081-1.4281zm-10.216-12.259h8.1728c2.2567 0 4.086 1.8293 4.086 4.086v2.0433h-16.344v-2.0433c0-2.2567 1.8293-4.086 4.086-4.086zm0.20453 3.0647c-0.67725 0-1.2259 0.54863-1.2259 1.2259v1.8388h10.215v-1.8388c0-0.67725-0.54863-1.2259-1.2259-1.2259z" fill="#fff" fill-rule="evenodd" stroke="#bd4812" stroke-width=".75383"/></svg> diff --git a/icons/button_resume_game.svg b/icons/button_resume_game.svg deleted file mode 100644 index 6ad8b64202d0e70f898c16c520e756fe8a934add..0000000000000000000000000000000000000000 --- a/icons/button_resume_game.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#49a1ee" stroke="#fff" stroke-width=".238"/><path d="m39.211 31.236c-0.84086-0.84489-2.9911-0.84489-2.9911 0v34.329c0 0.84594 2.1554 0.84594 2.9993 0l28.178-15.637c0.84392-0.84086 0.85812-2.2091 0.01623-3.053z" fill="#fefeff" stroke="#105ca1" stroke-linecap="round" stroke-linejoin="round" stroke-width="6.1726"/><path d="m40.355 33.714c-0.71948-0.72294-2.5594-0.72294-2.5594 0v29.373c0 0.72383 1.8442 0.72383 2.5663 0l24.11-13.38c0.7221-0.71948 0.73426-1.8902 0.01389-2.6124z" fill="#fefeff" stroke="#feffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.225"/><path d="m28.369 66.919v-37.591" fill="#105ca2" stroke="#105ca2" stroke-linecap="round" stroke-width="4.0337"/></svg> diff --git a/icons/level_easy.svg b/icons/level_easy.svg deleted file mode 100644 index 30048ce976f10fba1ad4233115035786a65d35de..0000000000000000000000000000000000000000 --- a/icons/level_easy.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#97c05c" stroke="#000" stroke-width="2"/><path d="m50.952 32.393c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133s-9.3059 8.2444-9.7225 9.5414c-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434c-0.42533-1.2941-10.203-8.1785-9.7868-9.4754 0.41657-1.297 12.375-1.2 13.474-2.0044s4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#030303" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/></svg> diff --git a/icons/level_hard.svg b/icons/level_hard.svg deleted file mode 100644 index 976249e8b0d2274b791d00e128be518d29a03731..0000000000000000000000000000000000000000 --- a/icons/level_hard.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#cd5542" stroke="#000" stroke-width="2"/><path d="m28.065 11.952c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133s-9.3059 8.2444-9.7225 9.5414c-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434c-0.42533-1.2941-10.203-8.1785-9.7868-9.4754 0.41657-1.297 12.375-1.2 13.474-2.0044s4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#010101" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m73.839 11.952c1.3622-0.0046 4.9652 11.398 6.07 12.195s13.062 0.61914 13.487 1.9133c0.42533 1.2941-9.3059 8.2444-9.7225 9.5414-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434c-0.42533-1.2941-10.203-8.1785-9.7868-9.4754 0.41657-1.297 12.375-1.2 13.474-2.0044 1.0993-0.80441 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#010101" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m50.952 52.835c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133 0.42533 1.2941-9.3059 8.2444-9.7225 9.5414-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 5e-3 -10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434-10.203-8.1785-9.7868-9.4754c0.41657-1.297 12.375-1.2 13.474-2.0044 1.0993-0.80442 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#010101" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/></svg> diff --git a/icons/level_medium.svg b/icons/level_medium.svg deleted file mode 100644 index e70fd60d179b05a0db5701e4b7858c214887c6be..0000000000000000000000000000000000000000 --- a/icons/level_medium.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#f29c38" stroke="#000" stroke-width="2"/><path d="m27.72 32.393c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133s-9.3059 8.2444-9.7225 9.5414c-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434c-0.42533-1.2941-10.203-8.1785-9.7868-9.4754 0.41657-1.297 12.375-1.2 13.474-2.0044s4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m74.183 32.393c1.3622-0.0046 4.9652 11.398 6.07 12.195s13.062 0.61914 13.487 1.9133-9.3059 8.2444-9.7225 9.5414c-0.41656 1.297 3.4475 12.614 2.3482 13.418-1.0994 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797-1.1048-0.79696 2.6827-12.14 2.2574-13.434s-10.203-8.1785-9.7868-9.4754c0.41657-1.297 12.375-1.2 13.474-2.0044 1.0993-0.80441 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/></svg> diff --git a/icons/level_nightmare.svg b/icons/level_nightmare.svg deleted file mode 100644 index 87f28a3defc23c6fe95980cb376efbf56d6885cc..0000000000000000000000000000000000000000 --- a/icons/level_nightmare.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="1" y="1" width="100" height="100" ry="0" fill="#6041b0" stroke="#000" stroke-width="2"/><path d="m28.929 11.793c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133s-9.3059 8.2444-9.7225 9.5414c-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80442-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797-1.1048-0.79696 2.6827-12.14 2.2574-13.434-0.42533-1.2941-10.203-8.1785-9.7868-9.4754 0.41657-1.297 12.375-1.2 13.474-2.0044 1.0993-0.80441 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m73.125 11.861c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133 0.42533 1.2941-9.3059 8.2444-9.7225 9.5414-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80441-10.717-6.3028-12.079-6.2982-1.3622 0.0046-10.931 7.1767-12.036 6.3797s2.6827-12.14 2.2574-13.434-10.203-8.1785-9.7868-9.4754c0.41657-1.297 12.375-1.2 13.474-2.0044 1.0993-0.80442 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m28.778 52.923c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133 0.42533 1.2941-9.3059 8.2444-9.7225 9.5414s3.4475 12.614 2.3481 13.418c-1.0993 0.80442-10.717-6.3028-12.079-6.2982-1.3622 5e-3 -10.931 7.1767-12.036 6.3797-1.1048-0.79697 2.6827-12.14 2.2574-13.434-0.42533-1.2941-10.203-8.1785-9.7868-9.4754s12.375-1.2 13.474-2.0044c1.0993-0.80441 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/><path d="m73.104 52.992c1.3622-0.0046 4.9652 11.398 6.07 12.195 1.1048 0.79696 13.062 0.61914 13.487 1.9133s-9.3059 8.2444-9.7225 9.5414c-0.41657 1.297 3.4475 12.614 2.3481 13.418-1.0993 0.80442-10.717-6.3028-12.079-6.2982-1.3622 5e-3 -10.931 7.1767-12.036 6.3797-1.1048-0.79697 2.6827-12.14 2.2574-13.434-0.42533-1.2941-10.203-8.1785-9.7868-9.4754s12.375-1.2 13.474-2.0044c1.0993-0.80441 4.6252-12.231 5.9874-12.236z" fill="#fff" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="6" stroke-width="3.3"/></svg> diff --git a/icons/size_2x2.svg b/icons/size_2x2.svg deleted file mode 100644 index 1959de3b39b5df72eb27801268ffc22f6fd271ae..0000000000000000000000000000000000000000 --- a/icons/size_2x2.svg +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="0" fill="#97c05c" stroke="#000" stroke-width="2"/> - <g transform="translate(-5e-4 -17.966)"><rect x="27.579" y="45.549" width="43.928" height="43.924" fill="#fff"/><path d="m22.09 40.056v55.821h55.821v-55.821zm49.417 49.417h-19.218v-19.218h19.218zm-0.0022-24.706h-19.217v-19.218h19.218zm-43.926-19.218h19.218v19.218h-19.218zm0 24.71h19.218v19.218h-19.218z" stroke-width="2.7453"/></g></svg> diff --git a/icons/size_3x2.svg b/icons/size_3x2.svg deleted file mode 100644 index fb37d7647416ad4bfff7cc6b0084a6c57f87ca93..0000000000000000000000000000000000000000 --- a/icons/size_3x2.svg +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="0" fill="#f29c38" stroke="#000" stroke-width="2"/> - <g transform="translate(0 -18.024)"><rect x="19.174" y="49.114" width="60.385" height="36.983" fill="#fff"/><path d="m14.151 44.442v47.163h71.698v-47.163zm65.408 4.6759v15.58h-16.268v-15.58zm0 21.399v15.58h-16.268v-15.58zm-22.057-21.4v15.58h-16.268v-15.58zm0 21.399v15.58h-16.268v-15.58zm-38.328-21.402h16.268v15.58h-16.268zm0 21.399h16.268v15.58h-16.268z" stroke-width="2.422"/></g></svg> diff --git a/icons/size_3x3.svg b/icons/size_3x3.svg deleted file mode 100644 index 6abc920ef7ea59ddfda5956cfc784fb395952926..0000000000000000000000000000000000000000 --- a/icons/size_3x3.svg +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="0" fill="#cd5542" stroke="#000" stroke-width="2"/> - <g transform="translate(0 -19.83)"><rect x="25.631" y="45.406" width="48.427" height="48.427" fill="#fefdfd"/><path d="m21.909 41.68v56.301h56.182v-56.301zm35.388 20.489v14.901h-14.901v-14.901zm-14.901-3.7252v-13.038h14.901v13.038zm31.662 35.389h-13.038v-13.038h13.038zm0-16.762h-13.038v-14.901h13.038zm-16.762 3.7252v13.038h-14.901v-13.038zm16.762-22.352h-13.038v-13.038h13.038zm-48.427-13.038h13.038v13.038h-13.038zm0 16.762h13.038v14.901h-13.038zm0 18.627h13.038v13.038h-13.038z" stroke-width="1.8627"/></g></svg> diff --git a/icons/size_4x4.svg b/icons/size_4x4.svg deleted file mode 100644 index 39c8e92ebdfd0f972c1204485df68cbb89a9e14a..0000000000000000000000000000000000000000 --- a/icons/size_4x4.svg +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100" ry="0" fill="#6041b0" stroke="#000" stroke-width="2"/> - <g transform="translate(0 -18.82)"><rect x="25.882" y="43.926" width="48" height="49.184" fill="#fff"/><path d="m23.09 41.131v55.377h53.819v-55.377zm25.359 15.368v11.176h-9.9925v-11.176zm-9.9925-2.794v-9.7789h9.9925v9.7789zm35.426 39.405h-9.7789v-9.7789h9.7789zm0-12.862h-9.7789v-9.7789h9.7789zm-12.862 0h-9.7789v-9.7789h9.7789zm0 3.0824v9.7789h-9.7789v-9.7789zm12.862-15.655h-9.7789v-11.176h9.7789zm-12.862 0h-9.7789v-11.176h9.7789zm-12.572 2.794v9.7789h-9.9925v-9.7789zm0 12.862v9.7789h-9.9925v-9.7789zm25.434-39.405v9.7789h-9.7789v-9.7789zm-12.862 9.7789h-9.7789v-9.7789h9.7789zm-35.138-9.7789h9.7789v9.7789h-9.7789zm0 12.572h9.7789v11.176h-9.7789zm0 13.971h9.7789v9.7789h-9.7789zm0 12.862h9.7789v9.7789h-9.7789z" stroke-width="1.3971"/></g></svg> diff --git a/icons/skin_default.svg b/icons/skin_digits.svg similarity index 100% rename from icons/skin_default.svg rename to icons/skin_digits.svg diff --git a/icons/skins/default/1.svg b/icons/skins/digits/1.svg similarity index 100% rename from icons/skins/default/1.svg rename to icons/skins/digits/1.svg diff --git a/icons/skins/default/10.svg b/icons/skins/digits/10.svg similarity index 100% rename from icons/skins/default/10.svg rename to icons/skins/digits/10.svg diff --git a/icons/skins/default/11.svg b/icons/skins/digits/11.svg similarity index 100% rename from icons/skins/default/11.svg rename to icons/skins/digits/11.svg diff --git a/icons/skins/default/12.svg b/icons/skins/digits/12.svg similarity index 100% rename from icons/skins/default/12.svg rename to icons/skins/digits/12.svg diff --git a/icons/skins/default/13.svg b/icons/skins/digits/13.svg similarity index 100% rename from icons/skins/default/13.svg rename to icons/skins/digits/13.svg diff --git a/icons/skins/default/14.svg b/icons/skins/digits/14.svg similarity index 100% rename from icons/skins/default/14.svg rename to icons/skins/digits/14.svg diff --git a/icons/skins/default/15.svg b/icons/skins/digits/15.svg similarity index 100% rename from icons/skins/default/15.svg rename to icons/skins/digits/15.svg diff --git a/icons/skins/default/16.svg b/icons/skins/digits/16.svg similarity index 100% rename from icons/skins/default/16.svg rename to icons/skins/digits/16.svg diff --git a/icons/skins/default/2.svg b/icons/skins/digits/2.svg similarity index 100% rename from icons/skins/default/2.svg rename to icons/skins/digits/2.svg diff --git a/icons/skins/default/3.svg b/icons/skins/digits/3.svg similarity index 100% rename from icons/skins/default/3.svg rename to icons/skins/digits/3.svg diff --git a/icons/skins/default/4.svg b/icons/skins/digits/4.svg similarity index 100% rename from icons/skins/default/4.svg rename to icons/skins/digits/4.svg diff --git a/icons/skins/default/5.svg b/icons/skins/digits/5.svg similarity index 100% rename from icons/skins/default/5.svg rename to icons/skins/digits/5.svg diff --git a/icons/skins/default/6.svg b/icons/skins/digits/6.svg similarity index 100% rename from icons/skins/default/6.svg rename to icons/skins/digits/6.svg diff --git a/icons/skins/default/7.svg b/icons/skins/digits/7.svg similarity index 100% rename from icons/skins/default/7.svg rename to icons/skins/digits/7.svg diff --git a/icons/skins/default/8.svg b/icons/skins/digits/8.svg similarity index 100% rename from icons/skins/default/8.svg rename to icons/skins/digits/8.svg diff --git a/icons/skins/default/9.svg b/icons/skins/digits/9.svg similarity index 100% rename from icons/skins/default/9.svg rename to icons/skins/digits/9.svg diff --git a/lib/assets/grids.dart b/lib/assets/grids.dart new file mode 100644 index 0000000000000000000000000000000000000000..c3cea19cbf5228a857f8031578c49d9d7beea3af --- /dev/null +++ b/lib/assets/grids.dart @@ -0,0 +1,355 @@ +class SudokuGrids { + static const Map<String, Map<String, List<String>>> templates = { + '2x2': { + 'easy': [ + '4020204332141032', + '0302043100133124', + '3240412303000432', + '4231014200230304', + '0310012332001032', + '2300143041200201', + '2401310042130020', + '3040142020044302', + '4031304020131004', + '4231030401400402', + '4302120004210103', + '0000421324010102', + '0010310042301320', + '0302420021340400', + '2400132002030102', + '3204002023004032', + '4013310010420001', + '4132030102001003', + '0000302110404210', + '0001300002131302' + ], + 'medium': [ + '2403000000201000', + '0000001004323000', + '0000103000203004', + '0000201010030040', + '0001403000130000', + '0002030101030000', + '0003004041002000', + '0003204030000400', + '0004003003022000', + '0004100040200200', + '0004402010000200', + '0021000040002030', + '0024000030000403', + '0024000030004100', + '0030010000000314', + '0032000003404000', + '0102001000204000', + '0140000003041000', + '0203000101004000', + '0300120000020040' + ], + 'hard': [ + '1000040300200000', + '1000200000020003', + '1000300200040000', + '1000400000200010', + '1000400100000020', + '1400000030200000', + '2000001000403000', + '2000400100400000', + '2000400300000040', + '2030002000000100', + '2100000000300200', + '3000002000001040', + '3002000000140000', + '3002040000030000', + '3100000000000204', + '4000000000302010', + '4000000020010003', + '4000000200300300', + '4000300200000030', + '4000300400000020' + ], + 'nightmare': [ + '1000200300040000', + '1002000300004000', + '1004000200003000', + '1400000000002030', + '2000100200000030', + '2000104000300000', + '2001030000020000', + '2010000000000403', + '2010000043000000', + '2030000000004001', + '2300000010000002', + '3000002000040100', + '3001000000002400', + '3002040000000003', + '3020000000000403', + '4000003000101000', + '4000031000300000', + '4000204000000003', + '4002000000000103', + '4300000000200400' + ] + }, + '3x2': { + 'easy': [ + '425361136520241053003142350016610230', + '123405506012002530354126230641461050', + '215460640001050612160350501246426035', + '143560526041410023362054201030034010', + '314065056314461023500106030051005632', + '620000305106504362263014136005052631', + '015204064315400051106003523040041532', + '020306346152050010614200061403432061', + '065400342000534201620304206143403605', + '305010421635106503530401650002204006', + '342016106003403650020301064100531064', + '413625265300120403050010002164600502', + '512406300512200054030620154200603140', + '000040540231461050230164154020600015', + '040203032045050026261534600052420001', + '160052020060536010240035050320312540', + '210036630050542603063024300145400060', + '241530500004050312002405400253025601', + '301500050040432600610230560123123400', + '614205003016040301136542400003060054' + ], + 'medium': [ + '050000000251001300040102410500300004', + '460001005042000153000000530004620000', + '000000500063040256060000423005100002', + '004600000000102300506420050060021500', + '005000041000500120210503400005003002', + '005000300004000006003020002015150243', + '120000540000250064600100000020002015', + '200010400600001520500160000040040206', + '450300300240020000040502200006600020', + '504000310400043050100000030506060001', + '002006000502325400140300050000200004', + '600013000050000100025060060230201500', + '000040100523020000600050002100301002', + '000560005040000230600015530000410000', + '040035000106520000300004062000405000', + '050010000003104300020060005100001230', + '056002200005002001400050000600023010', + '060000010062026310050006005400000003', + '461000000001010520602000003260000100', + '500040146035450000001000005000030100' + ], + 'hard': [ + '060210050300003450000000020100001000', + '104020002000000200013050500301000000', + '210000004300020540000001060000001400', + '300260500000002006000504000001010040', + '400000001640000002500034100000003010', + '400610200340004001000200050000300000', + '500100000260000004052000000030100450', + '502000000000004360000005030240006050', + '600001042006000000060003000500001604', + '602000000004000163010000501400000001', + '000000602000000005401300300621000030', + '000006100004020030000061030102005000', + '000020000615150000003000046003000400', + '000046000000206004003000100032000601', + '000400000003000006410500003650065000', + '000500050601003006040100004000300400', + '003000000602000001000250300005420003', + '006401000500000300100000020050001603', + '010000006000302400000000403100160003', + '010403000001040002000500300000065200' + ], + 'nightmare': [ + '000006020500050003100002300000500600', + '000060600500000040031000025000060003', + '001024000300630000000003000000065001', + '003000000005060010100402014000000001', + '004000063000600002000050001030200004', + '004001000600200050041030020000100000', + '006000040100002003600002000536000000', + '006100000030000600000040054300030050', + '030045020000006000003010500002000006', + '060000000503604000000002105030000001', + '060000102050000042000010005000000034', + '305004000010000200050000046050000002', + '400000001032000020000500030040500001', + '406300300100000000000513004006000000', + '502010000004000000010020030000100540', + '600000050000006004200005000150010060', + '600010003000065000000000200106000520', + '000000005002000000431000002040000305', + '003001020000400005060400600000001000', + '200016000000006000000002000500604300' + ] + }, + '3x3': { + 'easy': [ + '751283946482769351936451827629074138045398672378610095217046583803025764564837210', + '560810042803764150010320876438671025256400731791502684379006518645187093182953467', + '054283906007915032239674001390167008745020169601459327923846715406501203518732694', + '840257069026039478000846302362510907700362540154098603235681794017920836680473215', + '281600947960271508070894012010946375496730081530128496629017853140082009750069124', + '710290485490517602260384970047650298829470100651928743986045307170809504504702809', + '070081395814035620953276814200158743045369081138704069060847152521000070487512030', + '401975026060128309289306175305009714790512083816030592143000968972063451650400237', + '100398465305104800890002371632700598009603710471985206253410907746039102918057643', + '548009210910426005007581394401963782826174950379850460105007600694218537000695108', + '010428009920653800480017623863079245594206730271534960700860300008042190102795486', + '459203017387415290000798054062307900731984600040020030674139082198572403523800179', + '700009816980016720651872409460208351025347608308165004006924187814050002279081043', + '400057210728031459500402670000389026800165934963720085317900560284006301609203847', + '050980043197643285048572196589307402000000010710068359435120900971835020820700031', + '410000050926570108350410729030045960895067402004032810271308604083094271049720583', + '597631004128907305600508179002059046480763002906200730735492618204806053000010427', + '020359174035187060791406800000534006600802401300761598268910347510600082073048610', + '028569470043180650507204018800400720090608031200703006480070195015942387079805264', + '095007641640589020073006859104203590307095006580614702950060070418730960736001084' + ], + 'medium': [ + '043700080007085321000360700500890070080547263024610058405130890208074036369058010', + '060300010549000026071900548654130709093460050102570604425603870800050200907201405', + '304008765859000300627453000793800002000100430146325007275030640038060001401580973', + '475631002016940300903870014080106740040003006702584139004009078090008401608417900', + '020196030790080206306702100070430028060020301083671050807300060012057093935264010', + '280315009360070500540869320070000031026138975138050200700500692650001700093720004', + '574023098239080064106947003860204031000000605340006280412309050000062010093805470', + '602503010030740080417086003854012006096070501020605000279008164040160278008427005', + '703004058090065047840070190671509800000406725254300619026908401010040563000600902', + '920451030045026079061080045170208400294500600008040912412070308050830001703160090', + '014095070020140050635280140700514900000903867093608500002439785300050002070860430', + '207006158891574620000000700028030471053420900900081205002040569039100047486700300', + '304027095501043200200000010892070140615400720000281956006010472708002501123050800', + '320608047700420010060197000573001008000530071416089020008302000297815364630970002', + '905624083000038070320001406457092001209500748100070009092147360736000910510360000', + '147080526030070000980254071300460907004013602008705010462897005859000004700046200', + '470308605950260304003050000200005070005603142300192506530000000104586709086034251', + '804250931090830046170040582020080400000702059300094020010520094508610003002408165', + '843106057126050400700004060580600721001205094279010036300001005000902018918503600', + '946010350001000002730040186250481093090700020604030875028000500105608234060023708' + ], + 'hard': [ + '000050000020190070036740000060400800503008704004009100601900230209007600008020917', + '504700003810093200023061800701026009002300008380000501000030000049100050208000090', + '006300809780590023003008670172849365000050000050000007000080730000205010000060090', + '050040107900817605071600900000060700000080003300002500020401006607200409090036800', + '070408000001000900836010020007002830100500762000607100760000210508701003003000079', + '083009001651000008700518000007986100860000429019000006500040000906053010000007300', + '200568703009173004003200000907000512000005400032700800080000001300600240720410000', + '370280106009700020000000073068070000590430060700010490850090000930108507046000000', + '509040078401600020700005600070000460180070500056412300042000800890000003005008700', + '635000010017030040804000000963100200000042090040000871001053960000007400006429030', + '470350009000008247809470500000000726000926850000700000708000105305810902000000070', + '600905030200067800097400020024050001000080950809010003000096002480300019030040700', + '140000083027005140600000900700236051403050069000014000000700002000090610094000530', + '700520400001708000060000100050000678000480210608005000000130894300040000149057002', + '907000060620000501043069008070004000090057000400908070350000010060091052100006904', + '513740600020160005000080300700000000458900026000054097004000060082006500000400738', + '000000200000300081200080396071020038090400107000003509000034000807209000023750810', + '000782030214305090000109500058201000439600000007000600300410007900000403700003002', + '003900000047080000956002378000065014800730500005200000000129000090070420021300060', + '005093100690080740128060000000008000061400050709600010904007020080002007050000481' + ], + 'nightmare': [ + '058001093200030004000200071905420000000007050000100000080304060703900005006810700', + '060140000720000000040875060070000493092007800004050000000090740000060025058004001', + '068070000003004000000180370900407601000901007000030040009043108057000000030602090', + '005208010000000809128690004000020000006900430009040208080014000004002700000009600', + '009007400030600000000485000200030107000070020170204600546700092800000040010000050', + '020500603000300004009024750050200006008000200012603000034000000507000001280900070', + '090304005000200490050086302300009250900000060070050130063000710020000500010000000', + '390007801508360020040950607000000000800003910700040005000030700010000086000200040', + '012034050000009640030600000784001300003080060000300078000002030005006000068000907', + '000000000050078200000590870600200100007004500004003008500000723040000006276030401', + '002000008070040031500890007009200000000503040040000150610000000000368700780150003', + '800000650300000080005007002108790400600050100052040060003000200000019000500003748', + '800004305000000790001000000010805000090100000200600184700200041402006070000037026', + '000000012240601009000300050600100020000700003320040605460008001900000008000036900', + '000409003750060040000000206000031079000000832500702600400007000000314060006000001', + '000000500009106430060090000008000050004000982300980007000035610017000090000000270', + '073002600200105008000000000729306100040000900000008004000003000007560001098200006', + '800003000050001079006079003000200001900700060000010305032090506000002000087000040', + '000800000039605400008000603005390000700006000380170004002900100004020306010000000', + '200000090500206700816700003400100000000050030900084520000000807000620050000000062' + ] + }, + '4x4': { + 'easy': [ + '00B6483AE20FDC91GA8F0E659D3172B42D9CGF71406B538E03E42B9D8C57GFA6C2G06184DFEA9B7596105GE724C3FAD8A45DF32B6709E1CGEF78A9DCB01G64327860BC423GAE19FD30AED51F7040CG2BBGF13000C9D20507DC02E7G9F1B508634EC9825613FDB7GA81D79AFG00263E4CFB3G1D00A0042659650A74B30E9C8D1F', + 'G8D13540972EFB0A3BC980EDA61457G2AF5721B683CGED0404267AG9DBF53018C7381BAFEGD92645BGA40EC7056018F95DF098647CA1GE3B916E532008BFC07D4EGA091BC20D0F8385B00732G906D4C16C1D0G00B473A92E297300DEF18060B0F38CG09A5047B1E602E5B6831A9C40DF064BED050FG093ACDA9G40F16E3B8257', + '48F0071693BC5DEA613CD5E80G0AB42029EA3C4B8051F0600D5B9F0G0426831C0C057634129E0B0G9218GB05F0A0E6430060E82C4530D9F10E431AF9D86GC75213865907GFE24ACD0000C1G36A842590C000FD6A5003700E059F4E82CB7D0106E40D625107G0003B3B790GCD014F6EA05601B47F3EC9G2D88FG2A09EB0D51C74', + '0381D0GA9FB06C20G0600842E03CFD50EFC5003BD42A1070A24D0F05800G3B9E00B30CFG19AE24D6800C041070F0G9EB4E0F00B902G8C531091G2DE35C4B708F0030A28CG617DEF0DC780B5F2E94AG130006E0D70A83024C2GAE439005D006B71D0230C40BE690A894F7G16EA852B0CDC6EAB9203D714FG5380BF7AD0G09E162', + '1DEF0A5C47209B865B7948D20E003AG000000763D0005E4F6003BE0085C0120D01026500E3D480C0030D841EC0F9002B8F9C020BAG15E364E64G093002B8AD51B0D8A3460157FC9GCGF19D853406200A40362FC7BA9GD01E92A0EGB1FC8D6430D80B7194GF3EC5A2GAC406285D700003F9253BEG680041D730105CAD9B42G608', + 'G9D05382CEA06BF445EC19D0G06F0780F08070A0451D009EA700G4FE9B830C0D6C159B40AFG0D8E783B020107000FA0020FDA00059C4B163740AED6008300905C0A0F694D0EG851BE64080201C5B73G95130C0G7FA904D20DG9831B52046CEAF1D700208E40A960C382E00C96D75AFB1BFC407E031295GD89A56DF018GBCE472', + '0DC63B47F058G20AF0102E06DBAGC30032A009FD06E048500GE8005A3490DB6F2AFCE0005G01B0085047AGCFB230169E1E905003008A0700B63G1D78E9F45A2CAF736000C5198GDB600298DGA740E0F300B9FA3008DE05160C8E7500036F29A44900D3A00EB561C78705BFE90A0600G2E3008C600DG790B0C00147009F23A08D', + 'AE3G6B980120DF0C0C007040900D06EA97185D0F00642BG3460523CAE0B0098004500F120EC798A6F06AD00E0G980254C829B076F403GD0E70E100A96052C3B0D04007GC50E60A910F8796B14A3CE5D2EA0600000710B4000193EA8020DF6C702DGC0057864EF13B15B0CEF0327A8G09037F0820D9G100C589AE106305F04720', + '08030C4B0D102A096F940E0580A203071A000F023G4B8C000B000A300F0E0645EC7B24509068F03DA08GB7CF03D402E0D26918A3G0EFB40000F00D9EB00C08GA0105C3D968FAEG727EAC06B020034D9036D2A5F4E9700B0189GFE20004BDA0635GC600EAD2893714FD3A518C4E07692B900E4G20A631500804180076F0C50E0G', + '0900FGE8071A24606G502D9B00CF73A10F17A0408D23G0B02400510000008E0FGBFAE2C0D1050843C8040AF630001000769ED5310080F2GAD03187040F60C09EA10G30020507D084534D0E0G20009AF682CF04DA0E00050097B6C85F030DE12GFCG9400E7AD856024A63GC2DF901B7E80D726F000C4E0G00BE8519A06230400D', + 'D0B09E8G203610C4716GF024B590380D00980D0CE0FG0075ECA205B7400060G9A00EB0060GD00407002D005FC6478B30G45602030EB0C0D0073B48ED02A9F05G0G000C0ED47093868D07003A91EFG50B02437098GC5BE0AF0EF5G4D0380A712C56E103A90F0007B000D080016B205G933000D06279C5A0F12B79EFG5A3104C68', + '0E000648F0C1D352000F02GD00B507016000F000D340G0BE0000C105A090F846930C671B2AGD0F850A8004F2EC03961B00D1G8AC0564E200702459030F18CGA0G4530EB0C0D7092F8DB0430051290AG00F090C2G00AB5DE727CA0D0680FGB000E9060F81GB30250AD1A030C9420F7E680G7025640DE03BF935F2AB0E79801400', + '2BD7G0F0C0010590A419E67B50F3G2CD8305D00C7400BE10CF0641020B00A3780030AF0G490DE020E904B706F0008GD0FD205000G0081B07B68G300D007A49050548706F3E0BCDG0DEBA180527GC3F46706FC0E3A004918B90C000D0168F57EA0A0BFD006C1970303C0295B080470A0E007080000F3E2C59089E6037D2A0F4B1', + '032E0B061AC89DG4G0400C012B5930006B80290GE04D51C090C5084D0G0F002BADE090020613C8054G685FAE02BC07D300000DG4F50E0B000259601008D4000018A4G07FC936E20DE60214905D07GCAF5F0G0203A400867909D0E0680F2G4531000C008A07050002207601398EG0D4FAD0GB462501FA79008A0100D70C62B050', + 'FGE427ACB09056D15C72BDE9G416A00000B6F301720EG490091064000C0D27EB010CA00240GB3079089A0070053162G06E07810B0D29000F20FGE50307001CBD8B20000006D4931A03A19000EB007D0540C01E0A39780G629760003520AG0EC0E54072B09A60D13GC600G91F8302E05770G930CD1F05002000835000DGB7C0F6', + '008A3259016FG0D4CD5070063E9A028134960G0807D00FE00F20D0BE0G08960061EFA54G0372D8C9G3ABFC20608901705040E890AF1D030G0970B163ECG0000006C809021030FE0A0010C03A042G7050F0G0500189AEC040A0B0GE8F7DC6291300D003AC48000590903C00E5G60184AD000590G400E31C0B0A0E801B905C00F0', + '16G20E8C50A00F490FD03190G70405007E390D450C00A2G8C040A6F028091D7E008G07C9A0406B53B0564GE0D00098000A20001B69750G004C006250EGB0D100A20FC9G6850BE431G8C12B7D009006FA000005AEF0G07082596EF400721000DBDG0000600E210A0C9500GFB70386001D60089A21CD50F0B0031BE0049AFG8705', + '247301050B000FDCCD800GBE450F10066F0007C908GD003EG90E00400C360080AC541E9B00D708FG90G27A0061080BC0D0300C800A5G00E701F7G600E40B30000B08092GF000CD64106FEB0A8D4C000052ED0413090AF0704309F006G200AEB1FA90000C57B4830DE8DC05A03G92704F302140F0C0AE0009B04693G7DF81EC2A', + 'A064E087DFC3B29190080A0CEB106400G00093D460700F0AF00E006000A0D85758BC64F0A0E900G004G7B1A3F856C92E13900075B40D06AFE6FA800D312C754BB056A010C00G3D742A0G30BF0E009C6881E90020000F00B50703D590060000E2C00D263A40075B0039050B4800DEA0F0700F0D01906B00800G400FC01A852ED3', + '00B0070800E605FD800000G1039D0C0200D5FB49000C600130G9C05DF0B0870E0286B09FE5G731C40701AC649D8300009DC4G870100006500F0B1205006008975G02D31C04FA097019FC0EBA27DG5308030009F0B6C01DEG7B0D048G0E19CA2F0C0G70AEDB302F10059E41320000GB8CB0170006G20000D30A2380DB00407060' + ], + 'medium': [ + '00B6483AE20FDC91GA8F0E659D3172B42D9CGF71406B538E03E42B9D8C57GFA6C2G06184DFEA9B7596105GE724C3FAD8A45DF32B6709E1CGEF78A9DCB01G64327860BC423GAE19FD30AED51F7040CG2BBGF13000C9D20507DC02E7G9F1B508634EC9825613FDB7GA81D79AFG00263E4CFB3G1D00A0042659650A74B30E9C8D1F', + 'G8D13540972EFB0A3BC980EDA61457G2AF5721B683CGED0404267AG9DBF53018C7381BAFEGD92645BGA40EC7056018F95DF098647CA1GE3B916E532008BFC07D4EGA091BC20D0F8385B00732G906D4C16C1D0G00B473A92E297300DEF18060B0F38CG09A5047B1E602E5B6831A9C40DF064BED050FG093ACDA9G40F16E3B8257', + '48F0071693BC5DEA613CD5E80G0AB42029EA3C4B8051F0600D5B9F0G0426831C0C057634129E0B0G9218GB05F0A0E6430060E82C4530D9F10E431AF9D86GC75213865907GFE24ACD0000C1G36A842590C000FD6A5003700E059F4E82CB7D0106E40D625107G0003B3B790GCD014F6EA05601B47F3EC9G2D88FG2A09EB0D51C74', + '0381D0GA9FB06C20G0600842E03CFD50EFC5003BD42A1070A24D0F05800G3B9E00B30CFG19AE24D6800C041070F0G9EB4E0F00B902G8C531091G2DE35C4B708F0030A28CG617DEF0DC780B5F2E94AG130006E0D70A83024C2GAE439005D006B71D0230C40BE690A894F7G16EA852B0CDC6EAB9203D714FG5380BF7AD0G09E162', + '1DEF0A5C47209B865B7948D20E003AG000000763D0005E4F6003BE0085C0120D01026500E3D480C0030D841EC0F9002B8F9C020BAG15E364E64G093002B8AD51B0D8A3460157FC9GCGF19D853406200A40362FC7BA9GD01E92A0EGB1FC8D6430D80B7194GF3EC5A2GAC406285D700003F9253BEG680041D730105CAD9B42G608', + 'G9D05382CEA06BF445EC19D0G06F0780F08070A0451D009EA700G4FE9B830C0D6C159B40AFG0D8E783B020107000FA0020FDA00059C4B163740AED6008300905C0A0F694D0EG851BE64080201C5B73G95130C0G7FA904D20DG9831B52046CEAF1D700208E40A960C382E00C96D75AFB1BFC407E031295GD89A56DF018GBCE472', + '0DC63B47F058G20AF0102E06DBAGC30032A009FD06E048500GE8005A3490DB6F2AFCE0005G01B0085047AGCFB230169E1E905003008A0700B63G1D78E9F45A2CAF736000C5198GDB600298DGA740E0F300B9FA3008DE05160C8E7500036F29A44900D3A00EB561C78705BFE90A0600G2E3008C600DG790B0C00147009F23A08D', + 'AE3G6B980120DF0C0C007040900D06EA97185D0F00642BG3460523CAE0B0098004500F120EC798A6F06AD00E0G980254C829B076F403GD0E70E100A96052C3B0D04007GC50E60A910F8796B14A3CE5D2EA0600000710B4000193EA8020DF6C702DGC0057864EF13B15B0CEF0327A8G09037F0820D9G100C589AE106305F04720', + '08030C4B0D102A096F940E0580A203071A000F023G4B8C000B000A300F0E0645EC7B24509068F03DA08GB7CF03D402E0D26918A3G0EFB40000F00D9EB00C08GA0105C3D968FAEG727EAC06B020034D9036D2A5F4E9700B0189GFE20004BDA0635GC600EAD2893714FD3A518C4E07692B900E4G20A631500804180076F0C50E0G', + '0900FGE8071A24606G502D9B00CF73A10F17A0408D23G0B02400510000008E0FGBFAE2C0D1050843C8040AF630001000769ED5310080F2GAD03187040F60C09EA10G30020507D084534D0E0G20009AF682CF04DA0E00050097B6C85F030DE12GFCG9400E7AD856024A63GC2DF901B7E80D726F000C4E0G00BE8519A06230400D', + 'D0B09E8G203610C4716GF024B590380D00980D0CE0FG0075ECA205B7400060G9A00EB0060GD00407002D005FC6478B30G45602030EB0C0D0073B48ED02A9F05G0G000C0ED47093868D07003A91EFG50B02437098GC5BE0AF0EF5G4D0380A712C56E103A90F0007B000D080016B205G933000D06279C5A0F12B79EFG5A3104C68' + ], + 'hard': [ + 'B329800F0140D0A0G700E0290500B6316058G01D2F00C079010CB005G00928FE00BG000E00230D073DF009G00C08001480E002F107G0A39C0A1034DB695F00G27000A08C90010000000B0E325G871006AF010090D000GCE820GED1B60AF07903F0CD96E0701405B000801D50020G074004302008FD9561CG0G754FC030E09000', + 'G000070A9008C00BD040C02B603GE9050200G3091004D8A000690105E0A0F0326002A0D050EC00FG8FA06017300D5C0E0050F9B3000A20063G905E000600B08D1030BC0D20F0GE602E00958FD00700C4900F060EBA8372015086027004009BDF00F0ED9000C1602046D01AF070928000B92G705CF30610EA01E02036A8D54FG0', + '00G05A0E300000009C0A0004G201705EF07E1000A6B50G40010870DF904CB00A2D6430G510CE000FAE0G0180042FC0D3CF05AB42000D6E017010E0FC000024B50000C00025E40D86G9C10F0B006045E250ED2038B1G7900C0A0200E0FCD93B1700A3G000CF0B0804000C80B0E00000908B90F0C100A0023D1G200300D856EC7B', + '08B007E90F03042D0040830G00D2E015950DAC0B08013600E0F750D09C04B08G8000007102009E6CD000BE300706080A00E000A639087001700A28900G4E003B60C0028E0590A0D03A8E95CF4D17600204090B1D0A3G8C0EF1000A632000570400600050G02000E04E920FG0C65D1BA0C0031628B4EA0F0010A5ED04007002C6', + '10F3000C0A600502A0C8F9B320106D70B0G0052D0EF70000D2004600C09B00E000B0000G9C00A6585C6A3840ED009B07009F6B00A15402D33GD2AF50B780E40C8F30009460BA100EG500B3000901082600A9G00000CEF00B000001F0G340D9A003007EDF14028A60900714365F0C2EBGF42G0A8B000057016A002CG08070000D', + 'CF004000906020107428000900010600D0050000230E70409310800B7C4DEFG5514003BC00800G6F000079F0G4250A000ED76A8G00FC35B0G8A0E4000B03927039040502000AG0010DF10G98300467A06A0GD743000B080080020F0EC09G04D0F5BD30E040G9A000420C9BGFE0108D50A08E521607CF0B30179608040500FCEG', + '30400000070005000FC84050B000D970E0D003A04000F6GB0050E9B8CF0G010000B005E4800300909E36B100F02D075A000400000000E30170A090236E00C400276E001B5G349F8054002E3FD006G000ADFC000609B70E148003D479A10E52600C8G300596721A00F31A0B00G0E57C4606E010GC30AF2800D07260FE0840B035', + '0B0G017A00C080390390D240700BC0G01420000CGA900F5000CF0GB00328040ADA30000000G7000FB1560AF08D009740G040CD07250F00A079F0G004301A00D8C074B0D86E300190060BA0C900D150000010F70G00A548B6008A0E01B042000C80A1E06000B00DC702000C005000G084476520GD0C03F001FCBD48A31G0E2965', + '193580400A6DF200AEG010B0040758D68B0DF50C129G4007020706005008100AF60C7D2A050304E0750B010F4009030800025C90670E0D0F409AEB800C0F200568002A7000F1350GD009B3F000C46080B3040G00A002E00DG001000836D007C05C700010D9008A0E0006A802000C0F549AF80EC700500G0000DG30560E8A00B1', + '8076FGE9500DB1C0B2A934100000807004050B0C900A0306F000006A38BG940537600C82DB04G009090B0000760C0003D08049300E02670C0G04E6700980DA02000008G000091F5790070001BF650D48100000B0200700A00A0E00FD83C12B9G08900FC0G00B0260A650GD080700EC0B41B06200C50EA8DFC0DFBA53004800G1', + 'C4D0FB83050200060G105000D009A70B89050070310600CDE000D0028CA0300001C2070D089A6400400000A90D0EB0006009004B00F38C000D0E6F0804C19G2A0800CD07900B56020F000805001D003C060DB020CE05F8099000E3GA4680D07105A4700F100C0EB8FC080254E0D7136G070G81BEA3040F95000B309C0G580DA0', + '0000AE000B0264080960000070C01A00B3080074E1A0050D10F0DG006895B0E70G0BC13D2F007605000086EB045AC2008C0300451009FD0E00E5F9A70C0301000B4920C600000G73F60000095738000AE8GD070002460C907030E41809GC0B6F0F709360400BE0D004B0700E0A00300G000G1C020360A00B310C0A0F9GED0006', + '0G0BC01D72000405000860320GD0BE7C0100G500AF00093D407D0EF830C01200F0070009GD010080004A80E0900F2D510200D0B0003ACG0F1C00020G8600009A001638705E0DG0B0083FE00C6B9G5A10A00G0B00F002360427B50DG6000C980E0A049F0E01G30500700E006A00004BC380920G00B0F0D1E6CD00730B040EA0G9', + '95AE0008GF7D000B80005200B4EC0FA323C00FD090600408B004GE1C30280D00600020CE008795F0020C0050003F00D0F0E70B0020153A400A9304076CDB80020008E104079A560G3B0FD0028146EC00A149000650BED83FE050B80F00000214C0F2000510A00G0E403A002G00000007G06089E07BF000C07E1B0003C0G20900', + 'B1C047820003056900000903060B0E0C08030065D29C1GA79000FACD18G00043870CA5160E304FBGG20F74300A00C800A03BD0F864C090020001200B7G80A03000G000B05002000D09BD630A01E07080000050700CD6GA0B00E090D083B0005F0D4000A00000F0GE2F003D57G90EB000E0700000ADF139003GA68F9EC0205701', + 'G0DA30004010085004080000305000E0002085E060A94BG00390704B8000A26DEB0900F003006A08A8F50B040090D301207DC090000850BE063C005D1GE09F720500B20AE14D398619034EC0062A000BD00EG300B07F010507A6D00F003CE00G7064000002B0000A0C1264AEF08G05070G00003750000C0F900F2G0C7ED08004', + '00700300A0DC000F960C85EF04G230A03A0109D200070000800GC00A0F530ED00036700190F00040008F040G75EA9D36000E0B39002D7C00270008F043C051BE00000000F000C0250D03027C0005B010C002010BE064807G14005080C2BG00E0F0C00000D00E6791794A0DB5001F00C0E2001F6750A000G36310EC940G78F05B', + '0F0B206D000085CA0170B00000280G00G80400EF05007000A60000G907D0130E40F0GC000D000E600C080BF6G04100720B0009000006040G63G07D40020A0005C0409287D0E00B13300654D0B002E0G0090560007384D0FC00B0FE10A90C5847000900002A3FG0088G0DC1240B0E0006FE3205900C1DA7B475613F0A080G20ED', + '4C1A0F090GBE3D570GD35000C08A000F0020CEBA041D8906E900000D05F0C00A0E897GF40200000024C009A800D50EF01BF0035097000602D0060B000E000894060430G0F0095A0080GE4A90DC00BF01000000E048A6237052010076B00004DC0100B00F5A0360E0700290DGE00000A500EF07612DGB40000D6B05030F741G00', + 'G60B9F0DEC0A4508F10040200000DA004AC2580B0G000F900E0000C35F00020BE2B306A000975400000000004083EC2A1F003500006E000754002E00DA00B1366000009FBE02AD040DG06705C0000900380ED14A907G2BC0AB090200801D0050B5600C37F2001009D340B0G160E000020701E456A039F00000EAFD0000CB6305' + ], + 'nightmare': [ + '0000AE000B0264080960000070C01A00B3080074E1A0050D10F0DG006895B0E70G0BC13D2F007605000086EB045AC2008C0300451009FD0E00E5F9A70C0301000B4920C600000G73F60000095738000AE8GD070002460C907030E41809GC0B6F0F709360400BE0D004B0700E0A00300G000G1C020360A00B310C0A0F9GED0006', + '0G0BC01D72000405000860320GD0BE7C0100G500AF00093D407D0EF830C01200F0070009GD010080004A80E0900F2D510200D0B0003ACG0F1C00020G8600009A001638705E0DG0B0083FE00C6B9G5A10A00G0B00F002360427B50DG6000C980E0A049F0E01G30500700E006A00004BC380920G00B0F0D1E6CD00730B040EA0G9', + '95AE0008GF7D000B80005200B4EC0FA323C00FD090600408B004GE1C30280D00600020CE008795F0020C0050003F00D0F0E70B0020153A400A9304076CDB80020008E104079A560G3B0FD0028146EC00A149000650BED83FE050B80F00000214C0F2000510A00G0E403A002G00000007G06089E07BF000C07E1B0003C0G20900', + 'B1C047820003056900000903060B0E0C08030065D29C1GA79000FACD18G00043870CA5160E304FBGG20F74300A00C800A03BD0F864C090020001200B7G80A03000G000B05002000D09BD630A01E07080000050700CD6GA0B00E090D083B0005F0D4000A00000F0GE2F003D57G90EB000E0700000ADF139003GA68F9EC0205701', + 'G0DA30004010085004080000305000E0002085E060A94BG00390704B8000A26DEB0900F003006A08A8F50B040090D301207DC090000850BE063C005D1GE09F720500B20AE14D398619034EC0062A000BD00EG300B07F010507A6D00F003CE00G7064000002B0000A0C1264AEF08G05070G00003750000C0F900F2G0C7ED08004', + '00700300A0DC000F960C85EF04G230A03A0109D200070000800GC00A0F530ED00036700190F00040008F040G75EA9D36000E0B39002D7C00270008F043C051BE00000000F000C0250D03027C0005B010C002010BE064807G14005080C2BG00E0F0C00000D00E6791794A0DB5001F00C0E2001F6750A000G36310EC940G78F05B', + '0F0B206D000085CA0170B00000280G00G80400EF05007000A60000G907D0130E40F0GC000D000E600C080BF6G04100720B0009000006040G63G07D40020A0005C0409287D0E00B13300654D0B002E0G0090560007384D0FC00B0FE10A90C5847000900002A3FG0088G0DC1240B0E0006FE3205900C1DA7B475613F0A080G20ED', + '4C1A0F090GBE3D570GD35000C08A000F0020CEBA041D8906E900000D05F0C00A0E897GF40200000024C009A800D50EF01BF0035097000602D0060B000E000894060430G0F0095A0080GE4A90DC00BF01000000E048A6237052010076B00004DC0100B00F5A0360E0700290DGE00000A500EF07612DGB40000D6B05030F741G00', + 'G60B9F0DEC0A4508F10040200000DA004AC2580B0G000F900E0000C35F00020BE2B306A000975400000000004083EC2A1F003500006E000754002E00DA00B1366000009FBE02AD040DG06705C0000900380ED14A907G2BC0AB090200801D0050B5600C37F2001009D340B0G160E000020701E456A039F00000EAFD0000CB6305', + 'GB824DAC100300500400B2938050000E0951G700A20D000C00300F150G90B0020019000070600F30CFA7004BD00000003605A02E0008007040G0F6075000010D25EB046030090A0F0CF6E5B0GD80042790007AG0E00F6D01D07A03800B2000E000040C70B80EG610012G0EF003AC790B000D0B3109G5A2C08A0C00062071DEF3', + 'G003C00820015006F00C5D04G09010A712003A70050C8BD90A0021E000B003FG30000007A00F008E05B0F02090863G7098A0B05310D00020D60E80003400F15BBD0906A0583EG000AG30004000000508000098004F0D07B20F00D0G00A100C9300F0E000B24A00CD70500201DG00083FE08D40B0FC702AG1000A070F806304E0', + '00ABFE5006009081FD7496G3000805206C528710F000D4E3918EAC0200306F0700DGB3600501F00C09B00F0100G00700163000000B0AG050450700C00080B60080C0E500091F00A0G01AC0300E6B400FDF0502B0G3A0C0987069G00F004C1B05040100000000000D000071284G0E500907004B0502D08100BA000D9010567200', + '70200805ECG1490D840G0920070D00000F51E00DB00602370D00C7A00040B5GE03A00D680B2E105990DE503081AF00CBB0F000000470E8D0008500000D000G0AE26FD07C000090B0000403E6A009C7200B70800006F030E5G093A0127000008409G841C06E0A53F02EB7F053081G040000C0GB000F020000F0002AD000000B10', + '00900DEF6210005A60CAB9420D5700E0000E0007000C40080075AC000F9E60001E80FG60040A507090GB20000300140CF0070AC498B1G020C020010DF0E0806020AF0E96B70G30D0760410D50AF9E000E300C0FG000800000G1C7B3000D290800900E000G53B0007B0000351700400000030G708200DC940G000D0A0108F2B0E', + '0105D082GCA0E007000063CB70D04008DEGC7F0430B850090700000509000F0D007810F02E0AG0CB00CDA70008G01062F00600BECD50800090008G000400DE0000000801E000000006B10CGD90800A040CD00003A671B09000FE9060B0CD71530002E03F87940CG100E00A0800FC90D00093000G5A00F076000F4597D0000200', + '2000DG601B097000F7000AB100050090DCE84925G30A000005007CF86E0D0G30G000135D0008C94F000620809FG000500A8F04000000E1670009060701E302AG0053C74G80960DF0000A0109005008000G028500BC0E9070B89D00A60030540C000008G00010F60006BE00040980G00082F0A000740010D040010F7CE5023A00', + '89506B0000700E0F00E003G861CF5DB00C1B570E4820G36A06G00001005A09470035BE00000G700CE8670G35B0000094B2D00AF000E5600G0004000276A000E00GA000860E4000730B00A00F0037400603080D002060905E5D46E070F90B00080481009G0700CB35000904B0C000E001250071E00004A08D000D00000208F000', + 'ED0CG300820B01AF0000000065040C3003G1F0400D90607E608BA7C0FE0G0200A040000508F7006B3B0600F20409GDC0C52FD07000GA000801E00CBG53000700D8043600000F9G01003750G000A2E00C09C20080006E3A00G060000A3100740000000D082G060097000E40309F81A0G0970G2060D000800446100000E700D003', + '000003G0F040E56010E60A0D7800F00400009F0C010E003A0DF07000C60A08120E0C00BG5A0160496000000000008A000934500062EB1000G21BA0E60F9D7000D0G000030B02A170B0A008250G06040D00C0D10704F50EG05800B00000D00000F00A1002D3604B07030700DB1EA00086EB00G57F09C003A081000E00B0270D0C', + '1G05B289ADE0000608070500000300A003E04D00620G0B8062C0G0A3589B400000000E0G00450000047000003A6C0G00AD8003CB000204000C037000DB89A5008769DGB0C3A1FE05D02GF007800000C100140030B90D7AG8003C80250070B09000G008000CB00F00719004E62500G8DA0F0800G0760000000000105000D8C069' + ] + } + }; +} diff --git a/lib/config/default_game_settings.dart b/lib/config/default_game_settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..2f9ae753d2cd5ef02f1feafda79db96a84a9315b --- /dev/null +++ b/lib/config/default_game_settings.dart @@ -0,0 +1,49 @@ +import 'package:sudoku/utils/tools.dart'; + +class DefaultGameSettings { + static const String parameterCodeLevel = 'level'; + static const String parameterCodeSize = 'size'; + + static const List<String> availableParameters = [ + parameterCodeLevel, + parameterCodeSize, + ]; + + static const String levelValueEasy = 'easy'; + static const String levelValueMedium = 'medium'; + static const String levelValueHard = 'hard'; + static const String levelValueNightmare = 'nightmare'; + + static const String defaultLevelValue = levelValueMedium; + static const List<String> allowedLevelValues = [ + levelValueEasy, + levelValueMedium, + levelValueHard, + levelValueNightmare, + ]; + + static const String sizeValueTiny = '2x2'; + static const String sizeValueSmall = '3x2'; + static const String sizeValueStandard = '3x3'; + static const String sizeValueLarge = '4x4'; + + static const String defaultSizeValue = sizeValueStandard; + static const List<String> allowedSizeValues = [ + sizeValueTiny, + sizeValueSmall, + sizeValueStandard, + sizeValueLarge, + ]; + + static List<String> getAvailableValues(String parameterCode) { + switch (parameterCode) { + case parameterCodeLevel: + return DefaultGameSettings.allowedLevelValues; + case parameterCodeSize: + return DefaultGameSettings.allowedSizeValues; + } + + printlog('Did not find any available value for game parameter "$parameterCode".'); + return []; + } +} diff --git a/lib/config/default_global_settings.dart b/lib/config/default_global_settings.dart new file mode 100644 index 0000000000000000000000000000000000000000..401e3efd5333c35a48da480c6c4443ebb23d3277 --- /dev/null +++ b/lib/config/default_global_settings.dart @@ -0,0 +1,39 @@ +import 'package:sudoku/utils/tools.dart'; + +class DefaultGlobalSettings { + static const String parameterCodeSkin = 'skin'; + + static const List<String> availableParameters = [ + parameterCodeSkin, + ]; + + static const String skinValueDigits = 'digits'; + static const String skinValueFood = 'food'; + static const String skinValueNature = 'nature'; + static const String skinValueMonsters = 'monsters'; + + static const String defaultSkinValue = skinValueDigits; + static const List<String> allowedSkinValues = [ + skinValueDigits, + skinValueFood, + skinValueNature, + skinValueMonsters, + ]; + static const List<String> shufflableSkins = [ + skinValueFood, + skinValueNature, + skinValueMonsters, + ]; + + static List<String> getAvailableValues(String parameterCode) { + switch (parameterCode) { + case parameterCodeSkin: + return DefaultGlobalSettings.allowedSkinValues; + } + + printlog('Did not find any available value for global parameter "$parameterCode".'); + return []; + } + + static const int defaultTipCountDownValueInSeconds = 20; +} diff --git a/lib/config/theme.dart b/lib/config/theme.dart new file mode 100644 index 0000000000000000000000000000000000000000..1732b790ab4f411f084a987bc5e50be2ab408c24 --- /dev/null +++ b/lib/config/theme.dart @@ -0,0 +1,196 @@ +import 'package:flutter/material.dart'; + +/// Colors from Tailwind CSS (v3.0) - June 2022 +/// +/// https://tailwindcss.com/docs/customizing-colors + +const int _primaryColor = 0xFF6366F1; +const MaterialColor primarySwatch = MaterialColor(_primaryColor, <int, Color>{ + 50: Color(0xFFEEF2FF), // indigo-50 + 100: Color(0xFFE0E7FF), // indigo-100 + 200: Color(0xFFC7D2FE), // indigo-200 + 300: Color(0xFFA5B4FC), // indigo-300 + 400: Color(0xFF818CF8), // indigo-400 + 500: Color(_primaryColor), // indigo-500 + 600: Color(0xFF4F46E5), // indigo-600 + 700: Color(0xFF4338CA), // indigo-700 + 800: Color(0xFF3730A3), // indigo-800 + 900: Color(0xFF312E81), // indigo-900 +}); + +const int _textColor = 0xFF64748B; +const MaterialColor textSwatch = MaterialColor(_textColor, <int, Color>{ + 50: Color(0xFFF8FAFC), // slate-50 + 100: Color(0xFFF1F5F9), // slate-100 + 200: Color(0xFFE2E8F0), // slate-200 + 300: Color(0xFFCBD5E1), // slate-300 + 400: Color(0xFF94A3B8), // slate-400 + 500: Color(_textColor), // slate-500 + 600: Color(0xFF475569), // slate-600 + 700: Color(0xFF334155), // slate-700 + 800: Color(0xFF1E293B), // slate-800 + 900: Color(0xFF0F172A), // slate-900 +}); + +const Color errorColor = Color(0xFFDC2626); // red-600 + +final ColorScheme lightColorScheme = ColorScheme.light( + primary: primarySwatch.shade500, + secondary: primarySwatch.shade500, + onSecondary: Colors.white, + error: errorColor, + background: textSwatch.shade200, + onBackground: textSwatch.shade500, + onSurface: textSwatch.shade500, + surface: textSwatch.shade50, + surfaceVariant: Colors.white, + shadow: textSwatch.shade900.withOpacity(.1), +); + +final ColorScheme darkColorScheme = ColorScheme.dark( + primary: primarySwatch.shade500, + secondary: primarySwatch.shade500, + onSecondary: Colors.white, + error: errorColor, + background: const Color(0xFF171724), + onBackground: textSwatch.shade400, + onSurface: textSwatch.shade300, + surface: const Color(0xFF262630), + surfaceVariant: const Color(0xFF282832), + shadow: textSwatch.shade900.withOpacity(.2), +); + +final ThemeData lightTheme = ThemeData( + colorScheme: lightColorScheme, + fontFamily: 'Nunito', + textTheme: TextTheme( + displayLarge: TextStyle( + color: textSwatch.shade700, + fontFamily: 'Nunito', + ), + displayMedium: TextStyle( + color: textSwatch.shade600, + fontFamily: 'Nunito', + ), + displaySmall: TextStyle( + color: textSwatch.shade500, + fontFamily: 'Nunito', + ), + headlineLarge: TextStyle( + color: textSwatch.shade700, + fontFamily: 'Nunito', + ), + headlineMedium: TextStyle( + color: textSwatch.shade600, + fontFamily: 'Nunito', + ), + headlineSmall: TextStyle( + color: textSwatch.shade500, + fontFamily: 'Nunito', + ), + titleLarge: TextStyle( + color: textSwatch.shade700, + fontFamily: 'Nunito', + ), + titleMedium: TextStyle( + color: textSwatch.shade600, + fontFamily: 'Nunito', + ), + titleSmall: TextStyle( + color: textSwatch.shade500, + fontFamily: 'Nunito', + ), + bodyLarge: TextStyle( + color: textSwatch.shade700, + fontFamily: 'Nunito', + ), + bodyMedium: TextStyle( + color: textSwatch.shade600, + fontFamily: 'Nunito', + ), + bodySmall: TextStyle( + color: textSwatch.shade500, + fontFamily: 'Nunito', + ), + labelLarge: TextStyle( + color: textSwatch.shade700, + fontFamily: 'Nunito', + ), + labelMedium: TextStyle( + color: textSwatch.shade600, + fontFamily: 'Nunito', + ), + labelSmall: TextStyle( + color: textSwatch.shade500, + fontFamily: 'Nunito', + ), + ), +); + +final ThemeData darkTheme = lightTheme.copyWith( + colorScheme: darkColorScheme, + textTheme: TextTheme( + displayLarge: TextStyle( + color: textSwatch.shade200, + fontFamily: 'Nunito', + ), + displayMedium: TextStyle( + color: textSwatch.shade300, + fontFamily: 'Nunito', + ), + displaySmall: TextStyle( + color: textSwatch.shade400, + fontFamily: 'Nunito', + ), + headlineLarge: TextStyle( + color: textSwatch.shade200, + fontFamily: 'Nunito', + ), + headlineMedium: TextStyle( + color: textSwatch.shade300, + fontFamily: 'Nunito', + ), + headlineSmall: TextStyle( + color: textSwatch.shade400, + fontFamily: 'Nunito', + ), + titleLarge: TextStyle( + color: textSwatch.shade200, + fontFamily: 'Nunito', + ), + titleMedium: TextStyle( + color: textSwatch.shade300, + fontFamily: 'Nunito', + ), + titleSmall: TextStyle( + color: textSwatch.shade400, + fontFamily: 'Nunito', + ), + bodyLarge: TextStyle( + color: textSwatch.shade200, + fontFamily: 'Nunito', + ), + bodyMedium: TextStyle( + color: textSwatch.shade300, + fontFamily: 'Nunito', + ), + bodySmall: TextStyle( + color: textSwatch.shade400, + fontFamily: 'Nunito', + ), + labelLarge: TextStyle( + color: textSwatch.shade200, + fontFamily: 'Nunito', + ), + labelMedium: TextStyle( + color: textSwatch.shade300, + fontFamily: 'Nunito', + ), + labelSmall: TextStyle( + color: textSwatch.shade400, + fontFamily: 'Nunito', + ), + ), +); + +final ThemeData appTheme = lightTheme; diff --git a/lib/cubit/game_cubit.dart b/lib/cubit/game_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..3f29efd13c70c8999233870ff0f126f53f242461 --- /dev/null +++ b/lib/cubit/game_cubit.dart @@ -0,0 +1,166 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:sudoku/config/default_global_settings.dart'; +import 'package:sudoku/models/cell.dart'; + +import 'package:sudoku/models/cell_location.dart'; +import 'package:sudoku/models/game.dart'; +import 'package:sudoku/models/settings_game.dart'; +import 'package:sudoku/models/settings_global.dart'; +import 'package:sudoku/utils/board_animate.dart'; + +part 'game_state.dart'; + +class GameCubit extends HydratedCubit<GameState> { + GameCubit() + : super(GameState( + game: Game.createNull(), + )); + + void updateState(Game game) { + emit(GameState( + game: game, + )); + } + + void refresh() { + updateState(Game( + gameSettings: state.game.gameSettings, + globalSettings: state.game.globalSettings, + board: state.game.board, + solvedBoard: state.game.solvedBoard, + isRunning: state.game.isRunning, + isFinished: state.game.isFinished, + blockSizeHorizontal: state.game.blockSizeHorizontal, + blockSizeVertical: state.game.blockSizeVertical, + boardSize: state.game.boardSize, + shuffledCellValues: state.game.shuffledCellValues, + boardConflicts: state.game.boardConflicts, + selectedCell: state.game.selectedCell, + showConflicts: state.game.showConflicts, + givenTipsCount: state.game.givenTipsCount, + buttonTipsCountdown: state.game.buttonTipsCountdown, + animationInProgress: state.game.animationInProgress, + boardAnimated: state.game.boardAnimated, + )); + } + + void startNewGame({ + required GameSettings gameSettings, + required GlobalSettings globalSettings, + }) { + final Game newGame = Game.createNew( + gameSettings: gameSettings, + globalSettings: globalSettings, + ); + + newGame.dump(); + + updateState(newGame); + refresh(); + + BoardAnimate.startAnimation(this, 'start'); + } + + void selectCell(CellLocation location) { + state.game.selectedCell = state.game.board.get(location); + refresh(); + } + + void unselectCell() { + state.game.selectedCell = null; + refresh(); + } + + void updateCellValue(CellLocation location, int value) { + if (state.game.board.get(location).isFixed == false) { + state.game.board.set( + location, + Cell( + location: location, + value: value, + isFixed: false, + ), + ); + refresh(); + } + + if (state.game.checkBoardIsSolved()) { + BoardAnimate.startAnimation(this, 'win'); + state.game.isFinished = true; + refresh(); + } + } + + void toggleShowConflicts() { + state.game.showConflicts = !state.game.showConflicts; + refresh(); + } + + void increaseGivenTipsCount() { + state.game.givenTipsCount++; + state.game.buttonTipsCountdown = DefaultGlobalSettings.defaultTipCountDownValueInSeconds; + refresh(); + + const Duration interval = Duration(milliseconds: 500); + Timer.periodic( + interval, + (Timer timer) { + if (state.game.buttonTipsCountdown == 0) { + timer.cancel(); + } else { + state.game.buttonTipsCountdown = max(state.game.buttonTipsCountdown - 1, 0); + } + refresh(); + }, + ); + } + + void quitGame() { + state.game.isRunning = false; + refresh(); + } + + void updateAnimationInProgress(bool animationInProgress) { + state.game.animationInProgress = animationInProgress; + refresh(); + } + + void setAnimatedBackground(List animatedCellsPattern) { + for (int row = 0; row < state.game.boardSize; row++) { + for (int col = 0; col < state.game.boardSize; col++) { + state.game.boardAnimated[row][col] = animatedCellsPattern[row][col]; + } + } + refresh(); + } + + void resetAnimatedBackground() { + for (int row = 0; row < state.game.boardSize; row++) { + for (int col = 0; col < state.game.boardSize; col++) { + state.game.boardAnimated[row][col] = false; + } + } + refresh(); + } + + @override + GameState? fromJson(Map<String, dynamic> json) { + Game game = json['game'] as Game; + + return GameState( + game: game, + ); + } + + @override + Map<String, dynamic>? toJson(GameState state) { + return <String, dynamic>{ + 'game': state.game.toJson(), + }; + } +} diff --git a/lib/cubit/game_state.dart b/lib/cubit/game_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..8d8f70fc138bd99263f889070f3c929c6fb04e31 --- /dev/null +++ b/lib/cubit/game_state.dart @@ -0,0 +1,19 @@ +part of 'game_cubit.dart'; + +@immutable +class GameState extends Equatable { + const GameState({ + required this.game, + }); + + final Game game; + + @override + List<dynamic> get props => <dynamic>[ + game, + ]; + + Map<String, dynamic> get values => <String, dynamic>{ + 'game': game, + }; +} diff --git a/lib/cubit/settings_game_cubit.dart b/lib/cubit/settings_game_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..fc2840d804f35ae11e58873e7d2194a4747aab3f --- /dev/null +++ b/lib/cubit/settings_game_cubit.dart @@ -0,0 +1,71 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:sudoku/config/default_game_settings.dart'; + +import 'package:sudoku/models/settings_game.dart'; + +part 'settings_game_state.dart'; + +class GameSettingsCubit extends HydratedCubit<GameSettingsState> { + GameSettingsCubit() : super(GameSettingsState(settings: GameSettings.createDefault())); + + void setValues({ + String? level, + String? size, + }) { + emit( + GameSettingsState( + settings: GameSettings( + level: level ?? state.settings.level, + size: size ?? state.settings.size, + ), + ), + ); + } + + String getParameterValue(String code) { + switch (code) { + case DefaultGameSettings.parameterCodeLevel: + return GameSettings.getLevelValueFromUnsafe(state.settings.level); + case DefaultGameSettings.parameterCodeSize: + return GameSettings.getSizeValueFromUnsafe(state.settings.size); + } + return ''; + } + + void setParameterValue(String code, String value) { + final String level = (code == DefaultGameSettings.parameterCodeLevel) + ? value + : getParameterValue(DefaultGameSettings.parameterCodeLevel); + final String size = (code == DefaultGameSettings.parameterCodeSize) + ? value + : getParameterValue(DefaultGameSettings.parameterCodeSize); + + setValues( + level: level, + size: size, + ); + } + + @override + GameSettingsState? fromJson(Map<String, dynamic> json) { + final String level = json[DefaultGameSettings.parameterCodeLevel] as String; + final String size = json[DefaultGameSettings.parameterCodeSize] as String; + + return GameSettingsState( + settings: GameSettings( + level: level, + size: size, + ), + ); + } + + @override + Map<String, dynamic>? toJson(GameSettingsState state) { + return <String, dynamic>{ + DefaultGameSettings.parameterCodeLevel: state.settings.level, + DefaultGameSettings.parameterCodeSize: state.settings.size, + }; + } +} diff --git a/lib/cubit/settings_game_state.dart b/lib/cubit/settings_game_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..b773dc69be12673b158e880e2d7e6e7bec465506 --- /dev/null +++ b/lib/cubit/settings_game_state.dart @@ -0,0 +1,19 @@ +part of 'settings_game_cubit.dart'; + +@immutable +class GameSettingsState extends Equatable { + const GameSettingsState({ + required this.settings, + }); + + final GameSettings settings; + + @override + List<dynamic> get props => <dynamic>[ + settings, + ]; + + Map<String, dynamic> get values => <String, dynamic>{ + 'settings': settings, + }; +} diff --git a/lib/cubit/settings_global_cubit.dart b/lib/cubit/settings_global_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..9dceaa067477ac8ccf58b86afee7f7c4b7b0977f --- /dev/null +++ b/lib/cubit/settings_global_cubit.dart @@ -0,0 +1,60 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:sudoku/config/default_global_settings.dart'; + +import 'package:sudoku/models/settings_global.dart'; + +part 'settings_global_state.dart'; + +class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> { + GlobalSettingsCubit() : super(GlobalSettingsState(settings: GlobalSettings.createDefault())); + + void setValues({ + String? skin, + }) { + emit( + GlobalSettingsState( + settings: GlobalSettings( + skin: skin ?? state.settings.skin, + ), + ), + ); + } + + String getParameterValue(String code) { + switch (code) { + case DefaultGlobalSettings.parameterCodeSkin: + return GlobalSettings.getSkinValueFromUnsafe(state.settings.skin); + } + return ''; + } + + void setParameterValue(String code, String value) { + final String skin = (code == DefaultGlobalSettings.parameterCodeSkin) + ? value + : getParameterValue(DefaultGlobalSettings.parameterCodeSkin); + + setValues( + skin: skin, + ); + } + + @override + GlobalSettingsState? fromJson(Map<String, dynamic> json) { + final String skin = json[DefaultGlobalSettings.parameterCodeSkin] as String; + + return GlobalSettingsState( + settings: GlobalSettings( + skin: skin, + ), + ); + } + + @override + Map<String, dynamic>? toJson(GlobalSettingsState state) { + return <String, dynamic>{ + DefaultGlobalSettings.parameterCodeSkin: state.settings.skin, + }; + } +} diff --git a/lib/cubit/settings_global_state.dart b/lib/cubit/settings_global_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..4e4fbdf707b4e805f2092d0ca6a68a2de1c957c6 --- /dev/null +++ b/lib/cubit/settings_global_state.dart @@ -0,0 +1,19 @@ +part of 'settings_global_cubit.dart'; + +@immutable +class GlobalSettingsState extends Equatable { + const GlobalSettingsState({ + required this.settings, + }); + + final GlobalSettings settings; + + @override + List<dynamic> get props => <dynamic>[ + settings, + ]; + + Map<String, dynamic> get values => <String, dynamic>{ + 'settings': settings, + }; +} diff --git a/lib/entities/cell.dart b/lib/entities/cell.dart deleted file mode 100644 index 8767c741b7b3edf0380b48e12a9bbe2b1becf009..0000000000000000000000000000000000000000 --- a/lib/entities/cell.dart +++ /dev/null @@ -1,222 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:sudoku/provider/data.dart'; -import 'package:sudoku/utils/board_animate.dart'; -import 'package:sudoku/utils/board_utils.dart'; - -class Cell { - const Cell({ - required this.row, - required this.col, - required this.value, - required this.isFixed, - }); - - final int row; - final int col; - final int value; - final bool isFixed; - - static const Cell none = Cell(row: 0, col: 0, value: 0, isFixed: true); - - /* - * Build widget for board cell, with interactions - */ - Widget widget(Data myProvider) { - final String imageAsset = getImageAssetName(myProvider); - - return Container( - decoration: BoxDecoration( - color: getBackgroundColor(myProvider), - border: getCellBorders(myProvider), - ), - child: GestureDetector( - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 100), - transitionBuilder: (Widget child, Animation<double> animation) { - return ScaleTransition(scale: animation, child: child); - }, - child: Image( - image: AssetImage(imageAsset), - fit: BoxFit.fill, - key: ValueKey<int>(imageAsset.hashCode), - ), - ), - onTap: () { - if (col != myProvider.selectedCellCol || row != myProvider.selectedCellRow) { - myProvider.selectCell(col, row); - } else { - myProvider.selectCell(null, null); - } - }, - ), - ); - } - - /* - * Build widget for select/update value cell, with interactions - */ - Container widgetUpdateValue(Data myProvider) { - if (value < 0) { - return Container(); - } - - final String imageAsset = getImageAssetName(myProvider); - Color backgroundColor = Colors.grey.shade200; - - if (myProvider.showConflicts && - myProvider.selectedCellCol != null && - myProvider.selectedCellRow != null) { - final Board cells = myProvider.board; - final int blockSizeHorizontal = myProvider.blockSizeHorizontal; - final int blockSizeVertical = myProvider.blockSizeVertical; - - if (!BoardUtils.isValueAllowed(cells, blockSizeHorizontal, blockSizeVertical, - myProvider.selectedCellCol, myProvider.selectedCellRow, value)) { - backgroundColor = Colors.pink.shade100; - } - } - - return Container( - decoration: BoxDecoration( - color: backgroundColor, - border: Border.all( - color: Colors.black, - width: 2, - ), - ), - child: GestureDetector( - child: Image(image: AssetImage(imageAsset), fit: BoxFit.fill), - onTap: () { - if (myProvider.selectedCellCol != null && myProvider.selectedCellRow != null) { - myProvider.updateCellValue( - myProvider.selectedCellCol, myProvider.selectedCellRow, value); - } - myProvider.selectCell(null, null); - if (BoardUtils.checkBoardIsSolved(myProvider)) { - BoardAnimate.startAnimation(myProvider, 'win'); - } - }, - )); - } - - /* - * Compute image asset name, from skin and cell value/state - */ - String getImageAssetName(Data myProvider) { - if (value > 0) { - int cellValue = myProvider.getTranslatedValueForDisplay(value); - return 'assets/skins/${myProvider.parameterSkin}_$cellValue.png'; - } - - return 'assets/icons/cell_empty.png'; - } - - // Compute cell background color, from cell state - Color getBackgroundColor(Data myProvider) { - final Color editableCellColor = Colors.grey.shade100; - final Color editableCellColorConflict = Colors.pink.shade100; - final Color fixedCellColor = Colors.grey.shade300; - final Color fixedCellColorConflict = Colors.pink.shade200; - final Color editableSelectedValueColor = Colors.green.shade100; - final Color fixedSelectedValueColor = Colors.green.shade300; - final Color editableAnimated = Colors.green.shade200; - final Color fixedAnimated = Colors.green.shade300; - - Color backgroundColor = editableCellColor; - - if (isFixed) { - backgroundColor = fixedCellColor; - } - - final int conflictsCount = myProvider.boardConflicts[row][col]; - - if (myProvider.showConflicts && (conflictsCount != 0)) { - if (isFixed) { - backgroundColor = fixedCellColorConflict; - } else { - backgroundColor = editableCellColorConflict; - } - } - - if (myProvider.showConflicts && (value == myProvider.selectedCellValue)) { - if (isFixed) { - backgroundColor = fixedSelectedValueColor; - } else { - backgroundColor = editableSelectedValueColor; - } - } - - final bool isAnimated = myProvider.boardAnimated[row][col]; - - if (isAnimated) { - if (isFixed) { - backgroundColor = fixedAnimated; - } else { - backgroundColor = editableAnimated; - } - } - - return backgroundColor; - } - - // Compute cell borders, from board size and cell state - Border getCellBorders(Data myProvider) { - // final int row = , int col; - - final int blockSizeHorizontal = myProvider.blockSizeHorizontal; - final int blockSizeVertical = myProvider.blockSizeVertical; - - const Color cellBorderDarkColor = Colors.black; - const Color cellBorderLightColor = Colors.grey; - const Color cellBorderSelectedColor = Colors.red; - - Color cellBorderColor = cellBorderSelectedColor; - double cellBorderWidth = 4; - - // Reduce cell border width on big boards - final int boardSize = blockSizeVertical * blockSizeHorizontal; - if (boardSize > 8) { - cellBorderWidth = 2; - if (boardSize > 10) { - cellBorderWidth = 1; - } - } - - if (!myProvider.gameIsRunning) { - cellBorderColor = Colors.green.shade700; - } - - Border borders = Border.all( - color: cellBorderColor, - width: cellBorderWidth, - ); - - // Update cell borders if not currently selected cell - if (col != myProvider.selectedCellCol || row != myProvider.selectedCellRow) { - borders = Border( - top: BorderSide( - width: cellBorderWidth, - color: - ((row % blockSizeVertical) == 0) ? cellBorderDarkColor : cellBorderLightColor), - left: BorderSide( - width: cellBorderWidth, - color: ((col % blockSizeHorizontal) == 0) - ? cellBorderDarkColor - : cellBorderLightColor), - right: BorderSide( - width: cellBorderWidth, - color: (((col + 1) % blockSizeHorizontal) == 0) - ? cellBorderDarkColor - : cellBorderLightColor), - bottom: BorderSide( - width: cellBorderWidth, - color: (((row + 1) % blockSizeVertical) == 0) - ? cellBorderDarkColor - : cellBorderLightColor), - ); - } - - return borders; - } -} diff --git a/lib/main.dart b/lib/main.dart index a1fc31fe9c887e17017e8450e8740e2dd2b7c8b7..a5c7e892c19d4286c9379750b21d9c14c2305ff7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,15 +1,42 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hive/hive.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:overlay_support/overlay_support.dart'; -import 'package:sudoku/provider/data.dart'; -import 'package:sudoku/ui/screens/home.dart'; +import 'package:sudoku/config/default_global_settings.dart'; +import 'package:sudoku/config/theme.dart'; +import 'package:sudoku/cubit/game_cubit.dart'; +import 'package:sudoku/cubit/settings_game_cubit.dart'; +import 'package:sudoku/cubit/settings_global_cubit.dart'; +import 'package:sudoku/ui/skeleton.dart'; -void main() { +void main() async { + // Initialize packages WidgetsFlutterBinding.ensureInitialized(); - SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]) - .then((value) => runApp(const MyApp())); + await EasyLocalization.ensureInitialized(); + final Directory tmpDir = await getTemporaryDirectory(); + Hive.init(tmpDir.toString()); + HydratedBloc.storage = await HydratedStorage.build( + storageDirectory: tmpDir, + ); + + runApp( + EasyLocalization( + path: 'assets/translations', + supportedLocales: const <Locale>[ + Locale('en'), + Locale('fr'), + ], + fallbackLocale: const Locale('en'), + useFallbackTranslations: true, + child: const MyApp(), + ), + ); } class MyApp extends StatelessWidget { @@ -17,19 +44,62 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (BuildContext context) => Data(), - child: Consumer<Data>(builder: (context, data, child) { - return OverlaySupport( - child: MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData( - visualDensity: VisualDensity.adaptivePlatformDensity, - ), - home: const Home(), - ), - ); - }), + final List<String> assets = getImagesAssets(); + for (String asset in assets) { + precacheImage(AssetImage(asset), context); + } + + return MultiBlocProvider( + providers: [ + BlocProvider<GameCubit>(create: (context) => GameCubit()), + BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()), + BlocProvider<GameSettingsCubit>(create: (context) => GameSettingsCubit()), + ], + child: OverlaySupport( + child: MaterialApp( + title: 'Sudoku', + theme: appTheme, + home: const SkeletonScreen(), + + // Localization stuff + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + debugShowCheckedModeBanner: false, + ), + ), ); } + + List<String> getImagesAssets() { + final List<String> assets = []; + + final List<String> gameImages = [ + 'button_back', + 'button_help', + 'button_show_conflicts', + 'button_start', + 'game_win', + 'placeholder', + 'cell_empty' + ]; + + for (String image in gameImages) { + assets.add('${'assets/icons/$image'}.png'); + } + + List<String> skinImages = []; + for (int value = 1; value <= 16; value++) { + skinImages.add(value.toString()); + } + + for (String skin in DefaultGlobalSettings.allowedSkinValues) { + assets.add('assets/icons/skin_$skin.png'); + for (String image in skinImages) { + assets.add('${'${'assets/skins/$skin'}_$image'}.png'); + } + } + + return assets; + } } diff --git a/lib/models/board.dart b/lib/models/board.dart new file mode 100644 index 0000000000000000000000000000000000000000..c83989ed1f70bec670d6fe6a565279ff294fdab6 --- /dev/null +++ b/lib/models/board.dart @@ -0,0 +1,206 @@ +import 'dart:math'; + +import 'package:sudoku/models/cell.dart'; +import 'package:sudoku/models/cell_location.dart'; +import 'package:sudoku/models/types.dart'; +import 'package:sudoku/utils/tools.dart'; + +class Board { + Board({ + required this.cells, + }); + + BoardCells cells = const []; + + factory Board.createEmpty() { + return Board( + cells: [], + ); + } + + factory Board.createNew({ + required BoardCells cells, + }) { + return Board( + cells: cells, + ); + } + + Cell get(CellLocation location) { + if (location.row < cells.length) { + if (location.col < cells[location.row].length) { + return cells[location.row][location.col]; + } + } + + return Cell.none; + } + + void set(CellLocation location, Cell cell) { + cells[location.row][location.col] = cell; + } + + BoardCells copyCells() { + final BoardCells copiedGrid = []; + for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) { + final List<Cell> row = []; + for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) { + row.add(Cell( + location: CellLocation.go(rowIndex, colIndex), + value: cells[rowIndex][colIndex].value, + isFixed: false, + )); + } + copiedGrid.add(row); + } + + return copiedGrid; + } + + BoardCells getSolvedGrid() { + final Board tmpBoard = Board(cells: copyCells()); + + do { + final List<List<int>> cellsWithUniqueAvailableValue = + tmpBoard.getEmptyCellsWithUniqueAvailableValue(); + if (cellsWithUniqueAvailableValue.isEmpty) { + break; + } + + for (int i = 0; i < cellsWithUniqueAvailableValue.length; i++) { + final int row = cellsWithUniqueAvailableValue[i][0]; + final int col = cellsWithUniqueAvailableValue[i][1]; + final int value = cellsWithUniqueAvailableValue[i][2]; + + tmpBoard.cells[row][col] = Cell( + location: CellLocation.go(row, col), + value: value, + isFixed: tmpBoard.cells[row][col].isFixed, + ); + } + } while (true); + + return tmpBoard.cells; + } + + List<List<int>> getEmptyCellsWithUniqueAvailableValue() { + List<List<int>> candidateCells = []; + + final int boardSize = cells.length; + + for (int row = 0; row < boardSize; row++) { + for (int col = 0; col < boardSize; col++) { + if (cells[row][col].value == 0) { + int allowedValuesCount = 0; + int candidateValue = 0; + for (int value = 1; value <= boardSize; value++) { + if (isValueAllowed(CellLocation.go(row, col), value)) { + candidateValue = value; + allowedValuesCount++; + } + } + if (allowedValuesCount == 1) { + candidateCells.add([row, col, candidateValue]); + } + } + } + } + + return candidateCells; + } + + bool isValueAllowed(CellLocation? candidateLocation, int candidateValue) { + if ((candidateLocation == null) || (candidateValue == 0)) { + return true; + } + + final int boardSize = cells.length; + + // check lines does not contains a value twice + for (int row = 0; row < boardSize; row++) { + final List<int> values = []; + for (int col = 0; col < boardSize; col++) { + int value = cells[row][col].value; + if (row == candidateLocation.row && col == candidateLocation.col) { + value = candidateValue; + } + if (value != 0) { + values.add(value); + } + } + final List<int> distinctValues = values.toSet().toList(); + if (values.length != distinctValues.length) { + return false; + } + } + + // check columns does not contains a value twice + for (int col = 0; col < boardSize; col++) { + final List<int> values = []; + for (int row = 0; row < boardSize; row++) { + int value = cells[row][col].value; + if (row == candidateLocation.row && col == candidateLocation.col) { + value = candidateValue; + } + if (value != 0) { + values.add(value); + } + } + final List<int> distinctValues = values.toSet().toList(); + if (values.length != distinctValues.length) { + return false; + } + } + + // check blocks does not contains a value twice + final int blockSizeVertical = sqrt(cells.length).toInt(); + final int blockSizeHorizontal = cells.length ~/ blockSizeVertical; + + final int horizontalBlocksCount = blockSizeVertical; + final int verticalBlocksCount = blockSizeHorizontal; + for (int blockRow = 0; blockRow < verticalBlocksCount; blockRow++) { + for (int blockCol = 0; blockCol < horizontalBlocksCount; blockCol++) { + final List<int> values = []; + + for (int rowInBlock = 0; rowInBlock < blockSizeVertical; rowInBlock++) { + for (int colInBlock = 0; colInBlock < blockSizeHorizontal; colInBlock++) { + final int row = (blockRow * blockSizeVertical) + rowInBlock; + final int col = (blockCol * blockSizeHorizontal) + colInBlock; + int value = cells[row][col].value; + if (row == candidateLocation.row && col == candidateLocation.col) { + value = candidateValue; + } + if (value != 0) { + values.add(value); + } + } + } + + final List<int> distinctValues = values.toSet().toList(); + if (values.length != distinctValues.length) { + return false; + } + } + } + + return true; + } + + void dump() { + printlog(''); + printlog('$Board:'); + printlog(' cells: $cells'); + printlog(''); + } + + @override + String toString() { + return '$Board(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'cells': cells, + }; + } +} diff --git a/lib/models/cell.dart b/lib/models/cell.dart new file mode 100644 index 0000000000000000000000000000000000000000..c8e07172493e6857ef70558dce37c4071950f88d --- /dev/null +++ b/lib/models/cell.dart @@ -0,0 +1,41 @@ +import 'package:sudoku/models/cell_location.dart'; +import 'package:sudoku/utils/tools.dart'; + +class Cell { + const Cell({ + required this.location, + required this.value, + required this.isFixed, + }); + + final CellLocation location; + final int value; + final bool isFixed; + + static Cell none = Cell( + location: CellLocation.go(0, 0), + value: 0, + isFixed: true, + ); + + void dump() { + printlog('$Cell:'); + printlog(' location: $location'); + printlog(' value: $value'); + printlog(' isFixed: $isFixed'); + printlog(''); + } + + @override + String toString() { + return '$Cell(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'location': location.toJson(), + 'value': value, + 'isFixed': isFixed, + }; + } +} diff --git a/lib/models/cell_location.dart b/lib/models/cell_location.dart new file mode 100644 index 0000000000000000000000000000000000000000..e3c14a8c27cc1368afb9fdd30d4683206c9df987 --- /dev/null +++ b/lib/models/cell_location.dart @@ -0,0 +1,34 @@ +import 'package:sudoku/utils/tools.dart'; + +class CellLocation { + final int col; + final int row; + + CellLocation({ + required this.col, + required this.row, + }); + + factory CellLocation.go(int row, int col) { + return CellLocation(col: col, row: row); + } + + void dump() { + printlog('$CellLocation:'); + printlog(' row: $row'); + printlog(' col: $col'); + printlog(''); + } + + @override + String toString() { + return '$CellLocation(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'row': row, + 'col': col, + }; + } +} diff --git a/lib/models/game.dart b/lib/models/game.dart new file mode 100644 index 0000000000000000000000000000000000000000..3ae9cc9a96192c9e9d4b5d9444c7df3c434c5f5c --- /dev/null +++ b/lib/models/game.dart @@ -0,0 +1,424 @@ +import 'dart:math'; + +import 'package:sudoku/assets/grids.dart'; +import 'package:sudoku/config/default_global_settings.dart'; +import 'package:sudoku/cubit/game_cubit.dart'; +import 'package:sudoku/models/board.dart'; +import 'package:sudoku/models/cell.dart'; +import 'package:sudoku/models/cell_location.dart'; +import 'package:sudoku/models/settings_game.dart'; +import 'package:sudoku/models/settings_global.dart'; +import 'package:sudoku/models/types.dart'; +import 'package:sudoku/utils/board_utils.dart'; +import 'package:sudoku/utils/tools.dart'; + +class Game { + Game({ + required this.gameSettings, + required this.globalSettings, + required this.board, + required this.solvedBoard, + required this.isRunning, + required this.isFinished, + this.shuffledCellValues = const [], + this.boardConflicts = const [], + this.selectedCell, + this.showConflicts = false, + this.givenTipsCount = 0, + this.buttonTipsCountdown = 0, + this.animationInProgress = false, + this.boardAnimated = const [], + required this.blockSizeHorizontal, + required this.blockSizeVertical, + required this.boardSize, + }); + + final GameSettings gameSettings; + final GlobalSettings globalSettings; + + bool isRunning = false; + bool isFinished = false; + + int blockSizeVertical = 0; + int blockSizeHorizontal = 0; + int boardSize = 0; + + Board board; + Board solvedBoard; + + List<int> shuffledCellValues = []; + Cell? selectedCell; + + int givenTipsCount = 0; + int buttonTipsCountdown = 0; + bool showConflicts = false; + ConflictsCount boardConflicts = []; + + bool animationInProgress = false; + AnimatedBoard boardAnimated = []; + + factory Game.createNull() { + return Game( + gameSettings: GameSettings.createDefault(), + globalSettings: GlobalSettings.createDefault(), + board: Board.createEmpty(), + solvedBoard: Board.createEmpty(), + shuffledCellValues: [], + isRunning: false, + isFinished: false, + givenTipsCount: 0, + blockSizeHorizontal: 0, + blockSizeVertical: 0, + boardSize: 0, + ); + } + + factory Game.createNew({ + GameSettings? gameSettings, + GlobalSettings? globalSettings, + }) { + final GameSettings newGameSettings = gameSettings ?? GameSettings.createDefault(); + final GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault(); + + final int blockSizeHorizontal = int.parse(newGameSettings.size.split('x')[0]); + final int blockSizeVertical = int.parse(newGameSettings.size.split('x')[1]); + final int boardSize = blockSizeHorizontal * blockSizeVertical; + + const int maxCellValue = 16; + final List<int> shuffledCellValues = List<int>.generate(maxCellValue, (i) => i + 1); + + if (DefaultGlobalSettings.shufflableSkins.contains(newGlobalSettings.skin)) { + shuffledCellValues.shuffle(); + printlog('Shuffled tiles values: $shuffledCellValues'); + } + + ConflictsCount nonConflictedBoard = []; + + for (int row = 0; row < boardSize; row++) { + List<int> line = []; + for (int col = 0; col < boardSize; col++) { + line.add(0); + } + nonConflictedBoard.add(line); + } + + final List<String> templates = + SudokuGrids.templates[newGameSettings.size]?[newGameSettings.level] ?? []; + final String template = templates.elementAt(Random().nextInt(templates.length)).toString(); + + if (template.length != pow(blockSizeHorizontal * blockSizeVertical, 2)) { + printlog('Failed to get grid template...'); + return Game.createNull(); + } + + final Board board = BoardUtils.createBoardFromTemplate( + template: template, + isSymetric: (blockSizeHorizontal == blockSizeVertical), + ); + final Board solvedBoard = Board.createNew(cells: board.getSolvedGrid()); + + // Animated background + AnimatedBoard notAnimatedBoard = []; + for (int row = 0; row < boardSize; row++) { + List<bool> line = []; + for (int col = 0; col < boardSize; col++) { + line.add(false); + } + notAnimatedBoard.add(line); + } + + return Game( + gameSettings: newGameSettings, + globalSettings: newGlobalSettings, + board: board, + solvedBoard: solvedBoard, + isRunning: true, + isFinished: false, + boardConflicts: nonConflictedBoard, + shuffledCellValues: shuffledCellValues, + selectedCell: null, + blockSizeHorizontal: blockSizeHorizontal, + blockSizeVertical: blockSizeVertical, + boardSize: boardSize, + boardAnimated: notAnimatedBoard, + ); + } + + bool canGiveTip() { + return (buttonTipsCountdown == 0); + } + + int getTranslatedValueForDisplay(int originalValue) { + return shuffledCellValues[originalValue - 1]; + } + + bool checkBoardIsSolved() { + // (re)compute conflicts + boardConflicts = computeConflictsInBoard(); + + // check grid is fully completed and does not contain conflict + for (int row = 0; row < boardSize; row++) { + for (int col = 0; col < boardSize; col++) { + if (board.cells[row][col].value == 0 || boardConflicts[row][col] != 0) { + return false; + } + } + } + + printlog('-> ok sudoku solved!'); + + return true; + } + + ConflictsCount computeConflictsInBoard() { + final BoardCells cells = board.cells; + final ConflictsCount conflicts = boardConflicts; + + // reset conflict states + for (int row = 0; row < boardSize; row++) { + for (int col = 0; col < boardSize; col++) { + conflicts[row][col] = 0; + } + } + + // check lines does not contains a value twice + for (int row = 0; row < boardSize; row++) { + final List<int> values = []; + for (int col = 0; col < boardSize; col++) { + int value = cells[row][col].value; + if (value != 0) { + values.add(value); + } + } + final List<int> distinctValues = values.toSet().toList(); + if (values.length != distinctValues.length) { + printlog('line $row contains duplicates'); + // Add line to cells in conflict + for (int col = 0; col < boardSize; col++) { + conflicts[row][col]++; + } + } + } + + // check columns does not contains a value twice + for (int col = 0; col < boardSize; col++) { + final List<int> values = []; + for (int row = 0; row < boardSize; row++) { + int value = cells[row][col].value; + if (value != 0) { + values.add(value); + } + } + final List<int> distinctValues = values.toSet().toList(); + if (values.length != distinctValues.length) { + printlog('column $col contains duplicates'); + // Add column to cells in conflict + for (int row = 0; row < boardSize; row++) { + conflicts[row][col]++; + } + } + } + + // check blocks does not contains a value twice + final int horizontalBlocksCount = blockSizeVertical; + final int verticalBlocksCount = blockSizeHorizontal; + for (int blockRow = 0; blockRow < verticalBlocksCount; blockRow++) { + for (int blockCol = 0; blockCol < horizontalBlocksCount; blockCol++) { + List<int> values = []; + + for (int rowInBlock = 0; rowInBlock < blockSizeVertical; rowInBlock++) { + for (int colInBlock = 0; colInBlock < blockSizeHorizontal; colInBlock++) { + int row = (blockRow * blockSizeVertical) + rowInBlock; + int col = (blockCol * blockSizeHorizontal) + colInBlock; + int value = cells[row][col].value; + if (value != 0) { + values.add(value); + } + } + } + + List<int> distinctValues = values.toSet().toList(); + if (values.length != distinctValues.length) { + printlog('block [$blockCol,$blockRow] contains duplicates'); + // Add blocks to cells in conflict + for (int rowInBlock = 0; rowInBlock < blockSizeVertical; rowInBlock++) { + for (int colInBlock = 0; colInBlock < blockSizeHorizontal; colInBlock++) { + int row = (blockRow * blockSizeVertical) + rowInBlock; + int col = (blockCol * blockSizeHorizontal) + colInBlock; + conflicts[row][col]++; + } + } + } + } + } + + return conflicts; + } + + void showTip(GameCubit gameCubit) { + if (selectedCell == null) { + // no selected cell -> pick one + helpSelectCell(gameCubit); + } else { + // currently selected cell -> set value + helpFillCell(gameCubit); + } + gameCubit.increaseGivenTipsCount(); + } + + void helpSelectCell(GameCubit gameCubit) { + // pick one of wrong value cells, if found + final List<List<int>> wrongValueCells = getCellsWithWrongValue(); + if (wrongValueCells.isNotEmpty) { + printlog('will pick from wrongValueCells'); + pickRandomFromList(gameCubit, wrongValueCells); + return; + } + + // pick one of conflicting cells, if found + final List<List<int>> conflictingCells = getCellsWithConflicts(); + if (conflictingCells.isNotEmpty) { + printlog('will pick from conflictingCells'); + pickRandomFromList(gameCubit, conflictingCells); + return; + } + + // pick one form cells with unique non-conflicting candidate value + final List<List<int>> candidateCells = board.getEmptyCellsWithUniqueAvailableValue(); + if (candidateCells.isNotEmpty) { + printlog('will pick from candidateCells'); + pickRandomFromList(gameCubit, candidateCells); + return; + } + } + + List<List<int>> getCellsWithWrongValue() { + final BoardCells cells = board.cells; + final BoardCells cellsSolved = solvedBoard.cells; + + List<List<int>> cellsWithWrongValue = []; + + for (int row = 0; row < boardSize; row++) { + for (int col = 0; col < boardSize; col++) { + if (cells[row][col].value != 0 && + cells[row][col].value != cellsSolved[row][col].value) { + cellsWithWrongValue.add([row, col]); + } + } + } + + return cellsWithWrongValue; + } + + List<List<int>> getCellsWithConflicts() { + List<List<int>> cellsWithConflict = []; + + for (int row = 0; row < boardSize; row++) { + for (int col = 0; col < boardSize; col++) { + if (boardConflicts[row][col] != 0) { + cellsWithConflict.add([row, col]); + } + } + } + + return cellsWithConflict; + } + + void pickRandomFromList(GameCubit gameCubit, List<List<int>> cellsCoordinates) { + if (cellsCoordinates.isNotEmpty) { + cellsCoordinates.shuffle(); + final List<int> cell = cellsCoordinates[0]; + gameCubit.selectCell(CellLocation.go(cell[0], cell[1])); + } + } + + void helpFillCell(GameCubit gameCubit) { + // Will clean cell if no eligible value found + int eligibleValue = 0; + + // Ensure there is only one eligible value for this cell + int allowedValuesCount = 0; + for (int value = 1; value <= boardSize; value++) { + if (board.isValueAllowed(selectedCell?.location, value)) { + allowedValuesCount++; + eligibleValue = value; + } + } + + gameCubit.updateCellValue( + selectedCell!.location, allowedValuesCount == 1 ? eligibleValue : 0); + gameCubit.unselectCell(); + } + + printGrid() { + final BoardCells cells = board.cells; + final BoardCells solvedCells = solvedBoard.cells; + + const String stringValues = '0123456789ABCDEFG'; + printlog(''); + printlog('-------'); + for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) { + String row = ''; + String rowSolved = ''; + for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) { + row += stringValues[cells[rowIndex][colIndex].value]; + rowSolved += stringValues[solvedCells[rowIndex][colIndex].value]; + } + printlog('$row | $rowSolved'); + } + printlog('-------'); + printlog(''); + } + + void dump() { + printlog(''); + printlog('## Current game dump:'); + printlog(''); + gameSettings.dump(); + globalSettings.dump(); + printlog(''); + printlog('$Game:'); + printlog(' blockSizeHorizontal: $blockSizeHorizontal'); + printlog(' blockSizeVertical: $blockSizeVertical'); + printlog(' boardSize: $boardSize'); + printlog(' shuffledCellValues: $shuffledCellValues'); + printlog(''); + printlog(' isRunning: $isRunning'); + printlog(' isFinished: $isFinished'); + printlog(' selectedCell: ${selectedCell?.toString() ?? ''}'); + printlog(' showConflicts: $showConflicts'); + printlog(' givenTipsCount: $givenTipsCount'); + printlog(' givenTipsCountEnableCountdown: $buttonTipsCountdown'); + printlog(' animationInProgress: $animationInProgress'); + printlog(''); + + printGrid(); + printlog(''); + } + + @override + String toString() { + return '$Game(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'gameSettings': gameSettings.toJson(), + 'globalSettings': globalSettings.toJson(), + 'isRunning': isRunning, + 'isFinished': isFinished, + 'board': board.toJson(), + 'solvedBoard': solvedBoard.toJson(), + 'shuffledCellValues': shuffledCellValues, + 'boardConflicts': boardConflicts, + 'selectedCell': selectedCell?.toJson(), + 'showConflicts': showConflicts, + 'givenTipsCount': givenTipsCount, + 'givenTipsCountEnableCountdown': buttonTipsCountdown, + 'animationInProgress': animationInProgress, + 'boardAnimated': boardAnimated, + 'blockSizeHorizontal': blockSizeHorizontal, + 'blockSizeVertical': blockSizeVertical, + }; + } +} diff --git a/lib/models/settings_game.dart b/lib/models/settings_game.dart new file mode 100644 index 0000000000000000000000000000000000000000..8786fc9279dc6f3c1893ff606105999b21d501c8 --- /dev/null +++ b/lib/models/settings_game.dart @@ -0,0 +1,54 @@ +import 'package:sudoku/config/default_game_settings.dart'; +import 'package:sudoku/utils/tools.dart'; + +class GameSettings { + String level; + String size; + + GameSettings({ + required this.level, + required this.size, + }); + + static String getLevelValueFromUnsafe(String level) { + if (DefaultGameSettings.allowedLevelValues.contains(level)) { + return level; + } + + return DefaultGameSettings.defaultLevelValue; + } + + static String getSizeValueFromUnsafe(String size) { + if (DefaultGameSettings.allowedSizeValues.contains(size)) { + return size; + } + + return DefaultGameSettings.defaultSizeValue; + } + + factory GameSettings.createDefault() { + return GameSettings( + level: DefaultGameSettings.defaultLevelValue, + size: DefaultGameSettings.defaultSizeValue, + ); + } + + void dump() { + printlog('$GameSettings:'); + printlog(' ${DefaultGameSettings.parameterCodeLevel}: $level'); + printlog(' ${DefaultGameSettings.parameterCodeSize}: $size'); + printlog(''); + } + + @override + String toString() { + return '$GameSettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + DefaultGameSettings.parameterCodeLevel: level, + DefaultGameSettings.parameterCodeSize: size, + }; + } +} diff --git a/lib/models/settings_global.dart b/lib/models/settings_global.dart new file mode 100644 index 0000000000000000000000000000000000000000..b96912020563dd4ace4494be96626d80f8da65d6 --- /dev/null +++ b/lib/models/settings_global.dart @@ -0,0 +1,41 @@ +import 'package:sudoku/config/default_global_settings.dart'; +import 'package:sudoku/utils/tools.dart'; + +class GlobalSettings { + String skin; + + GlobalSettings({ + required this.skin, + }); + + static String getSkinValueFromUnsafe(String skin) { + if (DefaultGlobalSettings.allowedSkinValues.contains(skin)) { + return skin; + } + + return DefaultGlobalSettings.defaultSkinValue; + } + + factory GlobalSettings.createDefault() { + return GlobalSettings( + skin: DefaultGlobalSettings.defaultSkinValue, + ); + } + + void dump() { + printlog('$GlobalSettings: '); + printlog(' ${DefaultGlobalSettings.parameterCodeSkin}: $skin'); + printlog(''); + } + + @override + String toString() { + return '$GlobalSettings(${toJson()})'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + DefaultGlobalSettings.parameterCodeSkin: skin, + }; + } +} diff --git a/lib/models/types.dart b/lib/models/types.dart new file mode 100644 index 0000000000000000000000000000000000000000..9f75508834cf3590dbc3ed2f1401b6d04bff3d6b --- /dev/null +++ b/lib/models/types.dart @@ -0,0 +1,6 @@ +import 'package:sudoku/models/cell.dart'; + +typedef BoardCells = List<List<Cell>>; +typedef ConflictsCount = List<List<int>>; +typedef AnimatedBoard = List<List<bool>>; +typedef AnimatedBoardSequence = List<AnimatedBoard>; diff --git a/lib/provider/data.dart b/lib/provider/data.dart deleted file mode 100644 index 0ad2253bac79978469fa31c7f717283cfddf0f89..0000000000000000000000000000000000000000 --- a/lib/provider/data.dart +++ /dev/null @@ -1,382 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:flutter/foundation.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -import 'package:sudoku/entities/cell.dart'; -import 'package:sudoku/utils/tools.dart'; - -typedef Board = List<List<Cell>>; -typedef ConflictsCount = List<List<int>>; -typedef AnimatedBoard = List<List<bool>>; -typedef AnimatedBoardSequence = List<AnimatedBoard>; - -class Data extends ChangeNotifier { - // Configuration available values - final List<String> _availableParameters = ['level', 'size', 'skin']; - - final List<String> _availableLevelValues = ['easy', 'medium', 'hard', 'nightmare']; - final List<String> _availableSizeValues = ['2x2', '3x2', '3x3', '4x4']; - final List<String> _availableSkinValues = ['default', 'food', 'nature', 'monsters']; - final List<String> _shufflableSkins = ['food', 'nature', 'monsters']; - - List<String> get availableParameters => _availableParameters; - List<String> get availableLevelValues => _availableLevelValues; - List<String> get availableSizeValues => _availableSizeValues; - List<String> get availableSkinValues => _availableSkinValues; - - // Application default configuration - String _parameterLevel = ''; - final String _parameterLevelDefault = 'medium'; - String _parameterSize = ''; - final String _parameterSizeDefault = '3x3'; - String _parameterSkin = ''; - final String _parameterSkinDefault = 'default'; - - // Application current configuration - String get parameterLevel => _parameterLevel; - String get parameterSize => _parameterSize; - String get parameterSkin => _parameterSkin; - - // Game data - bool _assetsPreloaded = false; - bool _gameIsRunning = false; - bool _animationInProgress = false; - int _blockSizeVertical = 0; - int _blockSizeHorizontal = 0; - Board _board = []; - Board _boardSolved = []; - ConflictsCount _boardConflicts = []; - AnimatedBoard _boardAnimated = []; - List<int> _shuffledCellValues = []; - int? _selectedCellCol; - int? _selectedCellRow; - int? _selectedCellValue; - bool _showConflicts = false; - int _givenTipsCount = 0; - int _givenTipsCountEnableCountdown = 0; - String _currentSavedState = ''; - - void updateParameterLevel(String parameterLevel) { - _parameterLevel = parameterLevel; - notifyListeners(); - } - - int get blockSizeVertical => _blockSizeVertical; - int get blockSizeHorizontal => _blockSizeHorizontal; - void updateParameterSize(String parameterSize) { - _parameterSize = parameterSize; - _blockSizeHorizontal = int.parse(_parameterSize.split('x')[0]); - _blockSizeVertical = int.parse(_parameterSize.split('x')[1]); - notifyListeners(); - } - - void updateParameterSkin(String parameterSkin) { - _parameterSkin = parameterSkin; - notifyListeners(); - } - - String getParameterValue(String parameterCode) { - switch (parameterCode) { - case 'level': - return _parameterLevel; - case 'size': - return _parameterSize; - case 'skin': - return _parameterSkin; - } - return ''; - } - - List<String> getParameterAvailableValues(String parameterCode) { - switch (parameterCode) { - case 'level': - return _availableLevelValues; - case 'size': - return _availableSizeValues; - case 'skin': - return _availableSkinValues; - } - return []; - } - - void setParameterValue(String parameterCode, String parameterValue) async { - switch (parameterCode) { - case 'level': - updateParameterLevel(parameterValue); - break; - case 'size': - updateParameterSize(parameterValue); - break; - case 'skin': - updateParameterSkin(parameterValue); - break; - } - final prefs = await SharedPreferences.getInstance(); - prefs.setString(parameterCode, parameterValue); - } - - void initParametersValues() async { - final prefs = await SharedPreferences.getInstance(); - setParameterValue('level', prefs.getString('level') ?? _parameterLevelDefault); - setParameterValue('size', prefs.getString('size') ?? _parameterSizeDefault); - setParameterValue('skin', prefs.getString('skin') ?? _parameterSkinDefault); - } - - String get currentSavedState => _currentSavedState; - - String computeCurrentGameState() { - String cellsValues = ''; - const String stringValues = '0123456789ABCDEFG'; - for (int rowIndex = 0; rowIndex < _board.length; rowIndex++) { - for (int colIndex = 0; colIndex < _board[rowIndex].length; colIndex++) { - cellsValues += stringValues[_board[rowIndex][colIndex].value]; - cellsValues += _board[rowIndex][colIndex].isFixed ? 'x' : ' '; - } - } - - return json.encode({ - 'level': _parameterLevel, - 'size': _parameterSize, - 'skin': _parameterSkin, - 'tipsCount': _givenTipsCount, - 'showConflicts': _showConflicts, - 'boardValues': cellsValues, - 'shuffledCellValues': _shuffledCellValues, - }); - } - - void saveCurrentGameState() async { - if (_gameIsRunning) { - _currentSavedState = computeCurrentGameState(); - - final prefs = await SharedPreferences.getInstance(); - prefs.setString('savedState', _currentSavedState); - } else { - resetCurrentSavedState(); - } - } - - void resetCurrentSavedState() async { - _currentSavedState = ''; - - final prefs = await SharedPreferences.getInstance(); - prefs.setString('savedState', _currentSavedState); - notifyListeners(); - } - - void loadCurrentSavedState() async { - final prefs = await SharedPreferences.getInstance(); - _currentSavedState = prefs.getString('savedState') ?? ''; - } - - bool hasCurrentSavedState() { - return (_currentSavedState != ''); - } - - Map<String, dynamic> getCurrentSavedState() { - if (_currentSavedState != '') { - Map<String, dynamic> savedState = json.decode(_currentSavedState); - if (savedState.isNotEmpty) { - return savedState; - } - } - return {}; - } - - bool get gameIsRunning => _gameIsRunning; - void updateGameIsRunning(bool gameIsRunning) { - _gameIsRunning = gameIsRunning; - notifyListeners(); - } - - bool get assetsPreloaded => _assetsPreloaded; - void updateAssetsPreloaded(bool assetsPreloaded) { - _assetsPreloaded = assetsPreloaded; - } - - Board get board => _board; - void updateCells(Board board) { - _board = board; - notifyListeners(); - } - - Board get boardSolved => _boardSolved; - void updateCellsSolved(Board boardSolved) { - _boardSolved = boardSolved; - } - - AnimatedBoard get boardAnimated => _boardAnimated; - ConflictsCount get boardConflicts => _boardConflicts; - - void shuffleCellValues() { - const int maxCellValue = 16; - final List<int> values = List<int>.generate(maxCellValue, (i) => i + 1); - - if (_shufflableSkins.contains(_parameterSkin)) { - values.shuffle(); - printlog('Shuffled tiles values: $values'); - } - - _shuffledCellValues = values; - } - - void setShuffleCellValues(List<int> values) { - _shuffledCellValues = values; - } - - int getTranslatedValueForDisplay(int originalValue) { - return _shuffledCellValues[originalValue - 1]; - } - - int? get selectedCellCol => _selectedCellCol; - set updateCelectedCellCol(int? selectedCellCol) { - _selectedCellCol = selectedCellCol; - notifyListeners(); - } - - int? get selectedCellRow => _selectedCellRow; - set updateSelectedCellRow(int? selectedCellRow) { - _selectedCellRow = selectedCellRow; - notifyListeners(); - } - - int? get selectedCellValue => _selectedCellValue; - set updateSelectedCellValue(int? selectedCellValue) { - _selectedCellValue = selectedCellValue; - notifyListeners(); - } - - int get givenTipsCountEnableCountdown => _givenTipsCountEnableCountdown; - - int get givenTipsCount => _givenTipsCount; - void increaseGivenTipsCount() { - _givenTipsCount = _givenTipsCount + 1; - _givenTipsCountEnableCountdown = 60; - const Duration interval = Duration(milliseconds: 500); - Timer.periodic( - interval, - (Timer timer) { - if (_givenTipsCountEnableCountdown == 0) { - timer.cancel(); - notifyListeners(); - } else { - _givenTipsCountEnableCountdown--; - notifyListeners(); - } - }, - ); - - saveCurrentGameState(); - notifyListeners(); - } - - void setGivenTipsCount(int value) { - _givenTipsCount = value; - _givenTipsCountEnableCountdown = 0; - notifyListeners(); - } - - bool canGiveTip() { - return (_givenTipsCountEnableCountdown == 0); - } - - void resetGivenTipsCount() { - setGivenTipsCount(0); - } - - void selectCell(int? col, int? row) { - _selectedCellCol = col; - _selectedCellRow = row; - _selectedCellValue = null; - if (row != null && col != null) { - if (_board[row][col].value != 0) { - _selectedCellValue = _board[row][col].value; - } - } - notifyListeners(); - } - - void initConflictsBoard() { - ConflictsCount nonConflictedBoard = []; - final int boardSideLength = _blockSizeHorizontal * _blockSizeVertical; - for (int row = 0; row < boardSideLength; row++) { - List<int> line = []; - for (int col = 0; col < boardSideLength; col++) { - line.add(0); - } - nonConflictedBoard.add(line); - } - _boardConflicts = nonConflictedBoard; - } - - void updateConflicts(ConflictsCount conflictsCount) { - _boardConflicts = conflictsCount; - } - - void updateCellValue(int? col, int? row, int value) { - if ((col != null) && (row != null)) { - if (!_board[row][col].isFixed) { - _board[row][col] = Cell( - row: row, - col: col, - value: value, - isFixed: false, - ); - - saveCurrentGameState(); - notifyListeners(); - } - } - } - - bool get showConflicts => _showConflicts; - void updateShowConflicts(bool showConflicts) { - _showConflicts = showConflicts; - notifyListeners(); - } - - void toggleShowConflicts() { - updateShowConflicts(!showConflicts); - saveCurrentGameState(); - } - - bool get animationInProgress => _animationInProgress; - void updateAnimationInProgress(bool animationInProgress) { - _animationInProgress = animationInProgress; - notifyListeners(); - } - - void initAnimatedBackground() { - AnimatedBoard staticBoard = []; - final int boardSideLength = _blockSizeHorizontal * _blockSizeVertical; - for (int row = 0; row < boardSideLength; row++) { - List<bool> line = []; - for (int col = 0; col < boardSideLength; col++) { - line.add(false); - } - staticBoard.add(line); - } - _boardAnimated = staticBoard; - } - - void setAnimatedBackground(List animatedCellsPattern) { - final int boardSideLength = _blockSizeHorizontal * _blockSizeVertical; - for (int row = 0; row < boardSideLength; row++) { - for (int col = 0; col < boardSideLength; col++) { - _boardAnimated[row][col] = animatedCellsPattern[row][col]; - } - } - notifyListeners(); - } - - void resetAnimatedBackground() { - final int boardSideLength = _blockSizeHorizontal * _blockSizeVertical; - for (int row = 0; row < boardSideLength; row++) { - for (int col = 0; col < boardSideLength; col++) { - _boardAnimated[row][col] = false; - } - } - } -} diff --git a/lib/ui/layout/board.dart b/lib/ui/layout/board.dart index 069557ef249bea8deb463c029133df17f038fd13..3d44197e0c38ad2dea02d8ef0c93fe79f285b52a 100644 --- a/lib/ui/layout/board.dart +++ b/lib/ui/layout/board.dart @@ -1,48 +1,55 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:sudoku/provider/data.dart'; +import 'package:sudoku/cubit/game_cubit.dart'; +import 'package:sudoku/models/cell_location.dart'; +import 'package:sudoku/models/game.dart'; +import 'package:sudoku/ui/widgets/cell_widget.dart'; class BoardLayout extends StatelessWidget { - const BoardLayout({super.key, required this.myProvider}); - - final Data myProvider; + const BoardLayout({super.key}); @override - Container build(BuildContext context) { - const Color borderColor = Colors.black; - final int boardSize = myProvider.blockSizeHorizontal * myProvider.blockSizeVertical; + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game game = gameState.game; + + const Color borderColor = Colors.black; - final Widget gameTileset = Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - for (int row = 0; row < boardSize; row++) - TableRow( + return Container( + margin: const EdgeInsets.all(2), + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: borderColor, + borderRadius: BorderRadius.circular(2), + border: Border.all( + color: borderColor, + width: 2, + ), + ), + child: Column( children: [ - for (int col = 0; col < boardSize; col++) - Column( - children: [myProvider.board[row][col].widget(myProvider)], - ), + Table( + defaultColumnWidth: const IntrinsicColumnWidth(), + children: [ + for (int row = 0; row < game.boardSize; row++) + TableRow( + children: [ + for (int col = 0; col < game.boardSize; col++) + Column( + children: [ + CellWidget(cell: game.board.get(CellLocation.go(row, col))) + ], + ), + ], + ), + ], + ), ], ), - ], - ); - - return Container( - margin: const EdgeInsets.all(2), - padding: const EdgeInsets.all(2), - decoration: BoxDecoration( - color: borderColor, - borderRadius: BorderRadius.circular(2), - border: Border.all( - color: borderColor, - width: 2, - ), - ), - child: Column( - children: [ - gameTileset, - ], - ), + ); + }, ); } } diff --git a/lib/ui/layout/game.dart b/lib/ui/layout/game.dart deleted file mode 100644 index e6cc4ee582875b63e60d5ed2b8951580277a9af0..0000000000000000000000000000000000000000 --- a/lib/ui/layout/game.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:sudoku/ui/layout/board.dart'; -import 'package:sudoku/ui/widgets/message_game_end.dart'; -import 'package:sudoku/ui/widgets/bar_select_cell_value.dart'; -import 'package:sudoku/provider/data.dart'; -import 'package:sudoku/utils/board_utils.dart'; - -class Game extends StatelessWidget { - const Game({super.key, required this.myProvider}); - - final Data myProvider; - - @override - Widget build(BuildContext context) { - final bool gameIsFinished = BoardUtils.checkBoardIsSolved(myProvider); - - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - BoardLayout(myProvider: myProvider), - const SizedBox(height: 2), - gameIsFinished - ? EndGameMessage(myProvider: myProvider) - : SelectCellValueBar(myProvider: myProvider), - ], - ); - } -} diff --git a/lib/ui/layout/parameters.dart b/lib/ui/layout/parameters.dart deleted file mode 100644 index abd8b9c25df646b7e7c4e75bb53aeacc7585d3de..0000000000000000000000000000000000000000 --- a/lib/ui/layout/parameters.dart +++ /dev/null @@ -1,129 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:sudoku/provider/data.dart'; -import 'package:sudoku/ui/widgets/button_game_resume.dart'; -import 'package:sudoku/ui/widgets/button_game_start_new.dart'; - -class Parameters extends StatelessWidget { - const Parameters({super.key, required this.myProvider}); - - final Data myProvider; - - static const double separatorHeight = 2.0; - static const double blockMargin = 3.0; - static const double blockPadding = 2.0; - static const Color buttonBackgroundColor = Colors.white; - static const Color buttonBorderColorActive = Colors.blue; - static const Color buttonBorderColorInactive = Colors.white; - static const double buttonBorderWidth = 10.0; - static const double buttonBorderRadius = 8.0; - static const double buttonPadding = 0.0; - static const double buttonMargin = 0.0; - - @override - Widget build(BuildContext context) { - List<Widget> lines = []; - - List<String> parameters = myProvider.availableParameters; - for (int index = 0; index < parameters.length; index++) { - lines.add(buildParameterSelector(myProvider, parameters[index])); - lines.add(const SizedBox(height: separatorHeight)); - } - - myProvider.loadCurrentSavedState(); - Widget buttonsBlock = myProvider.hasCurrentSavedState() - ? ResumeGameButton(myProvider: myProvider) - : StartNewGameButton(myProvider: myProvider); - - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: separatorHeight), - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: lines, - ), - ), - const SizedBox(height: separatorHeight), - Container( - child: buttonsBlock, - ), - ], - ); - } - - static Image buildImageWidget(String imageAssetCode) { - return Image( - image: AssetImage('assets/icons/$imageAssetCode.png'), - fit: BoxFit.fill, - ); - } - - static Container buildImageContainerWidget(String imageAssetCode) { - return Container( - child: buildImageWidget(imageAssetCode), - ); - } - - static Column buildDecorationImageWidget() { - return Column( - children: [ - TextButton( - child: buildImageContainerWidget('placeholder'), - onPressed: () {}, - ), - ], - ); - } - - Widget buildParameterSelector(Data myProvider, String parameterCode) { - List<String> availableValues = myProvider.getParameterAvailableValues(parameterCode); - - if (availableValues.length == 1) { - return const SizedBox(height: 0.0); - } - - return Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - for (int index = 0; index < availableValues.length; index++) - Column( - children: [ - _buildParameterButton(myProvider, parameterCode, availableValues[index]) - ], - ), - ], - ), - ], - ); - } - - Widget _buildParameterButton(Data myProvider, String parameterCode, String parameterValue) { - String currentValue = myProvider.getParameterValue(parameterCode).toString(); - - bool isActive = (parameterValue == currentValue); - String imageAsset = '${parameterCode}_$parameterValue'; - - return TextButton( - child: Container( - margin: const EdgeInsets.all(buttonMargin), - padding: const EdgeInsets.all(buttonPadding), - decoration: BoxDecoration( - color: buttonBackgroundColor, - borderRadius: BorderRadius.circular(buttonBorderRadius), - border: Border.all( - color: isActive ? buttonBorderColorActive : buttonBorderColorInactive, - width: buttonBorderWidth, - ), - ), - child: buildImageWidget(imageAsset), - ), - onPressed: () => myProvider.setParameterValue(parameterCode, parameterValue), - ); - } -} diff --git a/lib/ui/painters/parameter_painter.dart b/lib/ui/painters/parameter_painter.dart new file mode 100644 index 0000000000000000000000000000000000000000..a409d1173f4b93fe5de5b2474d6ea2c7825a012c --- /dev/null +++ b/lib/ui/painters/parameter_painter.dart @@ -0,0 +1,233 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import 'package:sudoku/config/default_game_settings.dart'; +import 'package:sudoku/models/settings_game.dart'; +import 'package:sudoku/models/settings_global.dart'; +import 'package:sudoku/utils/tools.dart'; + +class ParameterPainter extends CustomPainter { + const ParameterPainter({ + required this.code, + required this.value, + required this.isSelected, + required this.gameSettings, + required this.globalSettings, + }); + + final String code; + final String value; + final bool isSelected; + final GameSettings gameSettings; + final GlobalSettings globalSettings; + + @override + void paint(Canvas canvas, Size size) { + // force square + final double canvasSize = min(size.width, size.height); + + const Color borderColorEnabled = Colors.blue; + const Color borderColorDisabled = Colors.white; + + // "enabled/disabled" border + final paint = Paint(); + paint.style = PaintingStyle.stroke; + paint.color = isSelected ? borderColorEnabled : borderColorDisabled; + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 20 / 100 * canvasSize; + canvas.drawRect( + Rect.fromPoints(const Offset(0, 0), Offset(canvasSize, canvasSize)), paint); + + // content + switch (code) { + case DefaultGameSettings.parameterCodeLevel: + paintLevelParameterItem(value, canvas, canvasSize); + break; + case DefaultGameSettings.parameterCodeSize: + paintSizeParameterItem(value, canvas, canvasSize); + break; + default: + printlog('Unknown parameter: $code/$value'); + paintUnknownParameterItem(value, canvas, canvasSize); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return false; + } + + // "unknown" parameter -> simple block with text + void paintUnknownParameterItem( + final String value, + final Canvas canvas, + final double size, + ) { + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3 / 100 * size; + + paint.color = Colors.grey; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + final textSpan = TextSpan( + text: '?\n$value', + style: const TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + ); + textPainter.layout(); + textPainter.paint( + canvas, + Offset( + (size - textPainter.width) * 0.5, + (size - textPainter.height) * 0.5, + ), + ); + } + + void paintLevelParameterItem( + final String value, + final Canvas canvas, + final double size, + ) { + Color backgroundColor = Colors.grey; + + final List<dynamic> stars = []; + + switch (value) { + case DefaultGameSettings.levelValueEasy: + backgroundColor = Colors.green; + stars.add([0.5, 0.5]); + break; + case DefaultGameSettings.levelValueMedium: + backgroundColor = Colors.orange; + stars.add([0.3, 0.5]); + stars.add([0.7, 0.5]); + break; + case DefaultGameSettings.levelValueHard: + backgroundColor = Colors.red; + stars.add([0.3, 0.3]); + stars.add([0.7, 0.3]); + stars.add([0.5, 0.7]); + break; + case DefaultGameSettings.levelValueNightmare: + backgroundColor = Colors.purple; + stars.add([0.3, 0.3]); + stars.add([0.7, 0.3]); + stars.add([0.3, 0.7]); + stars.add([0.7, 0.7]); + break; + default: + printlog('Wrong value for level parameter value: $value'); + } + + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 3 / 100 * size; + + // Colored background + paint.color = backgroundColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + // Stars + final textSpan = TextSpan( + text: 'â', + style: TextStyle( + color: Colors.black, + fontSize: size / 3, + fontWeight: FontWeight.bold, + ), + ); + final textPainter = TextPainter( + text: textSpan, + textDirection: TextDirection.ltr, + ); + textPainter.layout(); + + for (var center in stars) { + textPainter.paint( + canvas, + Offset( + size * center[0] - textPainter.width * 0.5, + size * center[1] - textPainter.height * 0.5, + ), + ); + } + } + + void paintSizeParameterItem( + final String value, + final Canvas canvas, + final double size, + ) { + Color backgroundColor = Colors.grey; + int gridWidth = 1; + int gridHeight = 1; + + switch (value) { + case DefaultGameSettings.sizeValueTiny: + backgroundColor = Colors.green; + gridWidth = 2; + gridHeight = 2; + break; + case DefaultGameSettings.sizeValueSmall: + backgroundColor = Colors.orange; + gridWidth = 3; + gridHeight = 2; + break; + case DefaultGameSettings.sizeValueStandard: + backgroundColor = Colors.red; + gridWidth = 3; + gridHeight = 3; + break; + case DefaultGameSettings.sizeValueLarge: + backgroundColor = Colors.purple; + gridWidth = 4; + gridHeight = 4; + break; + default: + printlog('Wrong value for size parameter value: $value'); + } + + final paint = Paint(); + paint.strokeJoin = StrokeJoin.round; + paint.strokeWidth = 5 / 100 * size; + + // Colored background + paint.color = backgroundColor; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint); + + // Mini grid + final borderColor = Colors.grey.shade800; + + final double cellSize = size / 6; + final double originX = (size - gridWidth * cellSize) / 2; + final double originY = (size - gridHeight * cellSize) / 2; + + for (int row = 0; row < gridHeight; row++) { + for (int col = 0; col < gridWidth; col++) { + final Offset topLeft = Offset(originX + col * cellSize, originY + row * cellSize); + final Offset bottomRight = topLeft + Offset(cellSize, cellSize); + + paint.color = Colors.white; + paint.style = PaintingStyle.fill; + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + + paint.color = borderColor; + paint.style = PaintingStyle.stroke; + canvas.drawRect(Rect.fromPoints(topLeft, bottomRight), paint); + } + } + } +} diff --git a/lib/ui/screens/home.dart b/lib/ui/screens/home.dart deleted file mode 100644 index 3b86e93a33d36f4f319655a41c9eeac189abeba1..0000000000000000000000000000000000000000 --- a/lib/ui/screens/home.dart +++ /dev/null @@ -1,165 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:badges/badges.dart' as badges; -import 'package:overlay_support/overlay_support.dart'; - -import 'package:sudoku/ui/layout/game.dart'; -import 'package:sudoku/ui/layout/parameters.dart'; -import 'package:sudoku/provider/data.dart'; -import 'package:sudoku/utils/game_utils.dart'; - -class Home extends StatefulWidget { - const Home({super.key}); - - @override - HomeState createState() => HomeState(); -} - -class HomeState extends State<Home> { - @override - void initState() { - super.initState(); - - Data myProvider = Provider.of<Data>(context, listen: false); - myProvider.initParametersValues(); - myProvider.loadCurrentSavedState(); - } - - List<String> getImagesAssets(Data myProvider) { - final List<String> assets = []; - - final List<String> gameImages = [ - 'button_back', - 'button_help', - 'button_show_conflicts', - 'button_start', - 'game_win', - 'placeholder', - 'cell_empty' - ]; - for (String level in myProvider.availableLevelValues) { - gameImages.add('level_$level'); - } - for (String size in myProvider.availableSizeValues) { - gameImages.add('size_$size'); - } - - for (String image in gameImages) { - assets.add('${'assets/icons/$image'}.png'); - } - - List<String> skinImages = []; - for (int value = 1; value <= 16; value++) { - skinImages.add(value.toString()); - } - - for (String skin in myProvider.availableSkinValues) { - for (String image in skinImages) { - assets.add('${'${'assets/skins/$skin'}_$image'}.png'); - } - } - - return assets; - } - - @override - Widget build(BuildContext context) { - final Data myProvider = Provider.of<Data>(context); - - if (!myProvider.assetsPreloaded) { - final List<String> assets = getImagesAssets(myProvider); - for (String asset in assets) { - precacheImage(AssetImage(asset), context); - } - myProvider.updateAssetsPreloaded(true); - } - - final List<Widget> menuActions = []; - - if (myProvider.gameIsRunning) { - menuActions.add(TextButton( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - border: Border.all( - color: Colors.white, - width: 3, - ), - ), - child: const Image( - image: AssetImage('assets/icons/button_back.png'), - fit: BoxFit.fill, - ), - ), - onPressed: () => toast('Long press to quit game...'), - onLongPress: () => GameUtils.quitGame(myProvider), - )); - menuActions.add(const Spacer(flex: 6)); - menuActions.add(TextButton( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - border: Border.all( - color: Colors.white, - width: 3, - ), - ), - child: badges.Badge( - showBadge: myProvider.givenTipsCount == 0 ? false : true, - badgeStyle: badges.BadgeStyle( - badgeColor: myProvider.givenTipsCount < 10 - ? Colors.green - : myProvider.givenTipsCount < 20 - ? Colors.orange - : Colors.red, - ), - badgeContent: Text( - myProvider.givenTipsCount == 0 ? '' : myProvider.givenTipsCount.toString(), - style: const TextStyle(color: Colors.white), - ), - child: Container( - padding: EdgeInsets.all(myProvider.givenTipsCountEnableCountdown / 4), - child: const Image( - image: AssetImage('assets/icons/button_help.png'), - fit: BoxFit.fill, - ), - ), - ), - ), - onPressed: () => myProvider.canGiveTip() ? GameUtils.showTip(myProvider) : null, - )); - menuActions.add(const Spacer()); - menuActions.add(TextButton( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - border: Border.all( - color: myProvider.showConflicts ? Colors.blue : Colors.white, - width: 3, - ), - ), - child: const Image( - image: AssetImage('assets/icons/button_show_conflicts.png'), - fit: BoxFit.fill, - ), - ), - onPressed: () { - myProvider.toggleShowConflicts(); - }, - )); - } - - return Scaffold( - appBar: AppBar( - actions: menuActions, - ), - body: SafeArea( - child: Center( - child: myProvider.gameIsRunning - ? Game(myProvider: myProvider) - : Parameters(myProvider: myProvider), - ), - ), - ); - } -} diff --git a/lib/ui/screens/screen_game.dart b/lib/ui/screens/screen_game.dart new file mode 100644 index 0000000000000000000000000000000000000000..0992122a5e2a5e1ddf71f476b9cc3f508ea0771d --- /dev/null +++ b/lib/ui/screens/screen_game.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:sudoku/cubit/game_cubit.dart'; +import 'package:sudoku/models/game.dart'; +import 'package:sudoku/ui/layout/board.dart'; +import 'package:sudoku/ui/widgets/message_game_end.dart'; +import 'package:sudoku/ui/widgets/bar_select_cell_value.dart'; + +class ScreenGame extends StatelessWidget { + const ScreenGame({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game game = gameState.game; + + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 8), + const BoardLayout(), + const SizedBox(height: 8), + game.isFinished ? const SizedBox.shrink() : const SelectCellValueBar(), + const Expanded(child: SizedBox.shrink()), + game.isFinished ? const EndGameMessage() : const SizedBox.shrink(), + ], + ); + }, + ); + } +} diff --git a/lib/ui/screens/screen_parameters.dart b/lib/ui/screens/screen_parameters.dart new file mode 100644 index 0000000000000000000000000000000000000000..fe20130eefec4ef76849c171ac5f18420c208826 --- /dev/null +++ b/lib/ui/screens/screen_parameters.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:sudoku/config/default_game_settings.dart'; +import 'package:sudoku/config/default_global_settings.dart'; +import 'package:sudoku/cubit/settings_game_cubit.dart'; +import 'package:sudoku/cubit/settings_global_cubit.dart'; +import 'package:sudoku/ui/painters/parameter_painter.dart'; +import 'package:sudoku/ui/widgets/button_game_start_new.dart'; +import 'package:sudoku/ui/widgets/parameter_image.dart'; + +class ScreenParameters extends StatelessWidget { + const ScreenParameters({super.key}); + + final double separatorHeight = 8.0; + + @override + Widget build(BuildContext context) { + final List<Widget> lines = []; + + // Game settings + for (String code in DefaultGameSettings.availableParameters) { + lines.add(Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: buildParametersLine( + code: code, + isGlobal: false, + ), + )); + + lines.add(SizedBox(height: separatorHeight)); + } + + lines.add(SizedBox(height: separatorHeight)); + lines.add(const Expanded(child: StartNewGameButton())); + lines.add(SizedBox(height: separatorHeight)); + + // Global settings + for (String code in DefaultGlobalSettings.availableParameters) { + lines.add( + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: buildParametersLine( + code: code, + isGlobal: true, + ), + ), + ); + + lines.add(SizedBox(height: separatorHeight)); + } + + return Column( + children: lines, + ); + } + + List<Widget> buildParametersLine({ + required String code, + required bool isGlobal, + }) { + final List<Widget> parameterButtons = []; + + final List<String> availableValues = isGlobal + ? DefaultGlobalSettings.getAvailableValues(code) + : DefaultGameSettings.getAvailableValues(code); + + for (String value in availableValues) { + final Widget parameterButton = BlocBuilder<GameSettingsCubit, GameSettingsState>( + builder: (BuildContext context, GameSettingsState gameSettingsState) { + return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>( + builder: (BuildContext context, GlobalSettingsState globalSettingsState) { + final GameSettingsCubit gameSettingsCubit = + BlocProvider.of<GameSettingsCubit>(context); + final GlobalSettingsCubit globalSettingsCubit = + BlocProvider.of<GlobalSettingsCubit>(context); + + final String currentValue = isGlobal + ? globalSettingsCubit.getParameterValue(code) + : gameSettingsCubit.getParameterValue(code); + + final bool isActive = (value == currentValue); + + final double displayWidth = MediaQuery.of(context).size.width; + final double itemWidth = displayWidth / availableValues.length - 25; + + final bool isPainterOrAsset = (code != DefaultGlobalSettings.parameterCodeSkin); + + return TextButton( + child: Container( + child: isPainterOrAsset + ? CustomPaint( + size: Size(itemWidth, itemWidth), + willChange: false, + painter: ParameterPainter( + code: code, + value: value, + isSelected: isActive, + gameSettings: gameSettingsState.settings, + globalSettings: globalSettingsState.settings, + ), + isComplex: true, + ) + : SizedBox.square( + dimension: itemWidth, + child: ParameterImage( + code: code, + value: value, + isSelected: isActive, + ), + ), + ), + onPressed: () => isGlobal + ? globalSettingsCubit.setParameterValue(code, value) + : gameSettingsCubit.setParameterValue(code, value), + ); + }, + ); + }, + ); + + parameterButtons.add(parameterButton); + } + + return parameterButtons; + } +} diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart new file mode 100644 index 0000000000000000000000000000000000000000..2b450cac224517da2565ca260b95b66744106e37 --- /dev/null +++ b/lib/ui/skeleton.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:sudoku/cubit/game_cubit.dart'; +import 'package:sudoku/ui/screens/screen_game.dart'; +import 'package:sudoku/ui/screens/screen_parameters.dart'; +import 'package:sudoku/ui/widgets/global_app_bar.dart'; + +class SkeletonScreen extends StatelessWidget { + const SkeletonScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: const GlobalAppBar(), + extendBodyBehindAppBar: false, + body: Material( + color: Theme.of(context).colorScheme.background, + child: Container( + margin: const EdgeInsets.only( + top: 8.0, + ), + child: BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + return gameState.game.isRunning ? const ScreenGame() : const ScreenParameters(); + }, + ), + ), + ), + backgroundColor: Theme.of(context).colorScheme.background, + ); + } +} diff --git a/lib/ui/widgets/bar_select_cell_value.dart b/lib/ui/widgets/bar_select_cell_value.dart index 581b36491edb1d2ce4d8f9be313ccdfd2df51e08..7ace4cb54c741ca357ac147567333fe755b6998a 100644 --- a/lib/ui/widgets/bar_select_cell_value.dart +++ b/lib/ui/widgets/bar_select_cell_value.dart @@ -1,56 +1,62 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:sudoku/entities/cell.dart'; -import 'package:sudoku/provider/data.dart'; +import 'package:sudoku/cubit/game_cubit.dart'; +import 'package:sudoku/models/cell.dart'; +import 'package:sudoku/models/cell_location.dart'; +import 'package:sudoku/models/game.dart'; +import 'package:sudoku/ui/widgets/cell_widget_update.dart'; class SelectCellValueBar extends StatelessWidget { - const SelectCellValueBar({super.key, required this.myProvider}); - - final Data myProvider; + const SelectCellValueBar({super.key}); @override Widget build(BuildContext context) { - final Board cells = myProvider.board; - - final bool isCellSelected = - (myProvider.selectedCellCol != null && myProvider.selectedCellRow != null); - final bool isUpdatableCellSelected = isCellSelected - ? !cells[myProvider.selectedCellRow ?? 0][myProvider.selectedCellCol ?? 0].isFixed - : false; - final int maxValue = myProvider.blockSizeHorizontal * myProvider.blockSizeVertical; - - const int maxItemsPerLine = 10; - final int linesCount = (maxValue / maxItemsPerLine).floor() + 1; - final int itemsCountPerLine = min(maxItemsPerLine, maxValue + 1); - - return Container( - margin: const EdgeInsets.all(2), - padding: const EdgeInsets.all(2), - child: Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - for (int lineIndex = 0; lineIndex < linesCount; lineIndex++) - TableRow( - children: [ - for (int value = lineIndex * itemsCountPerLine; - value < (lineIndex + 1) * itemsCountPerLine; - value++) - Column( - children: [ - Cell( - row: 1, - col: value, - value: isUpdatableCellSelected ? (value <= maxValue ? value : -1) : -1, - isFixed: false, - ).widgetUpdateValue(myProvider) - ], - ), - ], - ), - ], - ), + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game game = gameState.game; + + final bool isUpdatableCellSelected = + (game.selectedCell != null) ? !game.selectedCell!.isFixed : false; + final int maxValue = game.boardSize; + + const int maxItemsPerLine = 10; + final int linesCount = (maxValue / maxItemsPerLine).floor() + 1; + final int itemsCountPerLine = min(maxItemsPerLine, maxValue + 1); + + return Container( + margin: const EdgeInsets.all(2), + padding: const EdgeInsets.all(2), + child: Table( + defaultColumnWidth: const IntrinsicColumnWidth(), + children: [ + for (int lineIndex = 0; lineIndex < linesCount; lineIndex++) + TableRow( + children: [ + for (int value = lineIndex * itemsCountPerLine; + value < (lineIndex + 1) * itemsCountPerLine; + value++) + Column( + children: [ + CellWidgetUpdate( + cell: Cell( + location: CellLocation.go(1, value), + value: isUpdatableCellSelected + ? (value <= maxValue ? value : -1) + : -1, + isFixed: false, + ), + ), + ], + ), + ], + ), + ], + ), + ); + }, ); } } diff --git a/lib/ui/widgets/button_game_restart.dart b/lib/ui/widgets/button_game_restart.dart index a2e8b670a6cb3ebe8753baf5a7a331a53b3a5286..1da7f3934aa4b1e8184e925cbdf207d719f0d9e5 100644 --- a/lib/ui/widgets/button_game_restart.dart +++ b/lib/ui/widgets/button_game_restart.dart @@ -1,21 +1,27 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:sudoku/provider/data.dart'; -import 'package:sudoku/utils/game_utils.dart'; +import 'package:sudoku/cubit/game_cubit.dart'; class RestartGameButton extends StatelessWidget { - const RestartGameButton({super.key, required this.myProvider}); - - final Data myProvider; + const RestartGameButton({super.key}); @override Widget build(BuildContext context) { - return TextButton( - child: const Image( - image: AssetImage('assets/icons/button_back.png'), - fit: BoxFit.fill, - ), - onPressed: () => GameUtils.quitGame(myProvider), + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + + return TextButton( + child: const Image( + image: AssetImage('assets/icons/button_back.png'), + fit: BoxFit.fill, + ), + onPressed: () { + gameCubit.quitGame(); + }, + ); + }, ); } } diff --git a/lib/ui/widgets/button_game_resume.dart b/lib/ui/widgets/button_game_resume.dart deleted file mode 100644 index 41cb5688656aecce2faa0238717b9bb5d653eb9d..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/button_game_resume.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:sudoku/ui/layout/parameters.dart'; -import 'package:sudoku/utils/game_utils.dart'; - -class ResumeGameButton extends Parameters { - const ResumeGameButton({super.key, required super.myProvider}); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.all(Parameters.blockMargin), - padding: const EdgeInsets.all(Parameters.blockPadding), - child: Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - Column( - children: [ - TextButton( - child: Parameters.buildImageContainerWidget('button_delete_saved_game'), - onPressed: () => GameUtils.deleteSavedGame(myProvider), - ), - ], - ), - Column( - children: [ - TextButton( - child: Parameters.buildImageContainerWidget('button_resume_game'), - onPressed: () => GameUtils.resumeSavedGame(myProvider), - ), - ], - ), - Parameters.buildDecorationImageWidget(), - ], - ), - ], - ), - ); - } -} diff --git a/lib/ui/widgets/button_game_start_new.dart b/lib/ui/widgets/button_game_start_new.dart index 88e004d9a4bd18f317c22f774762916b635ac568..6cd2d639331d1a8da0f8a77806faa0d02de97379 100644 --- a/lib/ui/widgets/button_game_start_new.dart +++ b/lib/ui/widgets/button_game_start_new.dart @@ -1,38 +1,34 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:sudoku/provider/data.dart'; -import 'package:sudoku/ui/layout/parameters.dart'; -import 'package:sudoku/utils/game_utils.dart'; +import 'package:sudoku/cubit/game_cubit.dart'; +import 'package:sudoku/cubit/settings_game_cubit.dart'; +import 'package:sudoku/cubit/settings_global_cubit.dart'; class StartNewGameButton extends StatelessWidget { - const StartNewGameButton({super.key, required this.myProvider}); - - final Data myProvider; + const StartNewGameButton({super.key}); @override Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.all(Parameters.blockMargin), - padding: const EdgeInsets.all(Parameters.blockPadding), - child: Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( - children: [ - Parameters.buildDecorationImageWidget(), - Column( - children: [ - TextButton( - child: Parameters.buildImageContainerWidget('button_start'), - onPressed: () => GameUtils.startNewGame(myProvider), - ), - ], + return BlocBuilder<GameSettingsCubit, GameSettingsState>( + builder: (BuildContext context, GameSettingsState gameSettingsState) { + return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>( + builder: (BuildContext context, GlobalSettingsState globalSettingsState) { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + + return TextButton( + child: const Image( + image: AssetImage('assets/icons/button_start.png'), + fit: BoxFit.fill, + ), + onPressed: () => gameCubit.startNewGame( + gameSettings: gameSettingsState.settings, + globalSettings: globalSettingsState.settings, ), - Parameters.buildDecorationImageWidget(), - ], - ), - ], - ), + ); + }, + ); + }, ); } } diff --git a/lib/ui/widgets/cell_widget.dart b/lib/ui/widgets/cell_widget.dart new file mode 100644 index 0000000000000000000000000000000000000000..eb697a0fc709e1b5c777b7e7bbda2bb314c91805 --- /dev/null +++ b/lib/ui/widgets/cell_widget.dart @@ -0,0 +1,171 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:sudoku/cubit/game_cubit.dart'; +import 'package:sudoku/models/cell.dart'; +import 'package:sudoku/models/game.dart'; + +class CellWidget extends StatelessWidget { + const CellWidget({super.key, required this.cell}); + + final Cell cell; + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game game = gameState.game; + + final String imageAsset = getImageAssetName(game); + + return Container( + decoration: BoxDecoration( + color: getBackgroundColor(game), + border: getCellBorders(game), + ), + child: GestureDetector( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 100), + transitionBuilder: (Widget child, Animation<double> animation) { + return ScaleTransition(scale: animation, child: child); + }, + child: Image( + image: AssetImage(imageAsset), + fit: BoxFit.fill, + key: ValueKey<int>(imageAsset.hashCode), + ), + ), + onTap: () { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + + if (cell.location.col != game.selectedCell?.location.col || + cell.location.row != game.selectedCell?.location.row) { + gameCubit.selectCell(cell.location); + } else { + gameCubit.unselectCell(); + } + }, + ), + ); + }, + ); + } + + /* + * Compute image asset name, from skin and cell value/state + */ + String getImageAssetName(Game game) { + if ((cell.value) > 0) { + final int cellValue = game.getTranslatedValueForDisplay(cell.value); + return 'assets/skins/${game.globalSettings.skin}_$cellValue.png'; + } + + return 'assets/icons/cell_empty.png'; + } + + // Compute cell background color, from cell state + Color getBackgroundColor(Game game) { + final Color editableCellColor = Colors.grey.shade100; + final Color editableCellColorConflict = Colors.pink.shade100; + final Color fixedCellColor = Colors.grey.shade300; + final Color fixedCellColorConflict = Colors.pink.shade200; + final Color editableSelectedValueColor = Colors.green.shade100; + final Color fixedSelectedValueColor = Colors.green.shade300; + final Color editableAnimated = Colors.green.shade200; + final Color fixedAnimated = Colors.green.shade300; + + Color backgroundColor = editableCellColor; + + if (cell.isFixed == true) { + backgroundColor = fixedCellColor; + } + + final int conflictsCount = game.boardConflicts[cell.location.row][cell.location.col]; + + if (game.showConflicts == true) { + if (conflictsCount != 0) { + if (cell.isFixed == true) { + backgroundColor = fixedCellColorConflict; + } else { + backgroundColor = editableCellColorConflict; + } + } + + if ((cell.value != 0) && (cell.value == game.selectedCell?.value)) { + if (cell.isFixed == true) { + backgroundColor = fixedSelectedValueColor; + } else { + backgroundColor = editableSelectedValueColor; + } + } + } + + final bool isAnimated = game.boardAnimated[cell.location.row][cell.location.col]; + + if (isAnimated) { + if (cell.isFixed == true) { + backgroundColor = fixedAnimated; + } else { + backgroundColor = editableAnimated; + } + } + + return backgroundColor; + } + + // Compute cell borders, from board size and cell state + Border getCellBorders(Game game) { + const Color cellBorderDarkColor = Colors.black; + const Color cellBorderLightColor = Colors.grey; + const Color cellBorderSelectedColor = Colors.red; + + Color cellBorderColor = cellBorderSelectedColor; + double cellBorderWidth = 4; + + // Reduce cell border width on big boards + if (game.boardSize > 8) { + cellBorderWidth = 2; + if (game.boardSize > 10) { + cellBorderWidth = 1; + } + } + + if (!game.isRunning) { + cellBorderColor = Colors.green.shade700; + } + + Border borders = Border.all( + color: cellBorderColor, + width: cellBorderWidth, + ); + + // Update cell borders if not currently selected cell + if (cell.location.col != game.selectedCell?.location.col || + cell.location.row != game.selectedCell?.location.row) { + borders = Border( + top: BorderSide( + width: cellBorderWidth, + color: (((cell.location.row) % game.blockSizeVertical) == 0) + ? cellBorderDarkColor + : cellBorderLightColor), + left: BorderSide( + width: cellBorderWidth, + color: (((cell.location.col) % game.blockSizeHorizontal) == 0) + ? cellBorderDarkColor + : cellBorderLightColor), + right: BorderSide( + width: cellBorderWidth, + color: ((((cell.location.col) + 1) % game.blockSizeHorizontal) == 0) + ? cellBorderDarkColor + : cellBorderLightColor), + bottom: BorderSide( + width: cellBorderWidth, + color: ((((cell.location.row) + 1) % game.blockSizeVertical) == 0) + ? cellBorderDarkColor + : cellBorderLightColor), + ); + } + + return borders; + } +} diff --git a/lib/ui/widgets/cell_widget_update.dart b/lib/ui/widgets/cell_widget_update.dart new file mode 100644 index 0000000000000000000000000000000000000000..a8bb005faf9b6d684a23af474cb90ab85c93c5c6 --- /dev/null +++ b/lib/ui/widgets/cell_widget_update.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:sudoku/cubit/game_cubit.dart'; +import 'package:sudoku/models/cell.dart'; +import 'package:sudoku/models/game.dart'; + +class CellWidgetUpdate extends StatelessWidget { + const CellWidgetUpdate({super.key, this.cell}); + + final Cell? cell; + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game game = gameState.game; + + if ((cell?.value ?? 0) < 0) { + return Container(); + } + + final String imageAsset = getImageAssetName(game); + + Color backgroundColor = Colors.grey.shade200; + + if (game.showConflicts == true && + game.selectedCell?.location.col != null && + game.selectedCell?.location.row != null) { + if (!game.board.isValueAllowed(game.selectedCell?.location, cell?.value ?? 0)) { + backgroundColor = Colors.pink.shade100; + } + } + + return Container( + decoration: BoxDecoration( + color: backgroundColor, + border: Border.all( + color: Colors.black, + width: 2, + ), + ), + child: GestureDetector( + child: Image(image: AssetImage(imageAsset), fit: BoxFit.fill), + onTap: () { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + + if (game.selectedCell != null) { + gameCubit.updateCellValue(game.selectedCell!.location, cell?.value ?? 0); + } + + gameCubit.unselectCell(); + }, + ), + ); + }, + ); + } + + /* + * Compute image asset name, from skin and cell value/state + */ + String getImageAssetName(Game game) { + if ((cell?.value ?? 0) > 0) { + final int cellValue = game.getTranslatedValueForDisplay(cell?.value ?? 0); + return 'assets/skins/${game.globalSettings.skin}_$cellValue.png'; + } + + return 'assets/icons/cell_empty.png'; + } +} diff --git a/lib/ui/widgets/global_app_bar.dart b/lib/ui/widgets/global_app_bar.dart new file mode 100644 index 0000000000000000000000000000000000000000..558340540c5791c5123732dd8a46dda008bac102 --- /dev/null +++ b/lib/ui/widgets/global_app_bar.dart @@ -0,0 +1,113 @@ +import 'package:badges/badges.dart' as badges; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:overlay_support/overlay_support.dart'; + +import 'package:sudoku/config/default_global_settings.dart'; +import 'package:sudoku/cubit/game_cubit.dart'; +import 'package:sudoku/models/game.dart'; + +class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget { + const GlobalAppBar({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game game = gameState.game; + + final List<Widget> menuActions = []; + + if (game.isRunning && !game.isFinished) { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + + menuActions.add(TextButton( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: Colors.white, + width: 3, + ), + ), + child: const Image( + image: AssetImage('assets/icons/button_back.png'), + fit: BoxFit.fill, + ), + ), + onPressed: () => toast('Long press to quit game...'), + onLongPress: () { + gameCubit.quitGame(); + }, + )); + menuActions.add(const Spacer(flex: 6)); + menuActions.add(TextButton( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: Colors.white, + width: 3, + ), + ), + child: badges.Badge( + showBadge: game.givenTipsCount == 0 ? false : true, + badgeStyle: badges.BadgeStyle( + badgeColor: game.givenTipsCount < 10 + ? Colors.green + : game.givenTipsCount < 20 + ? Colors.orange + : Colors.red, + ), + badgeContent: Text( + game.givenTipsCount == 0 ? '' : game.givenTipsCount.toString(), + style: const TextStyle(color: Colors.white), + ), + child: Container( + padding: EdgeInsets.all(15 * + game.buttonTipsCountdown / + DefaultGlobalSettings.defaultTipCountDownValueInSeconds), + child: const Image( + image: AssetImage('assets/icons/button_help.png'), + fit: BoxFit.fill, + ), + ), + ), + ), + onPressed: () { + final GameCubit gameCubit = BlocProvider.of<GameCubit>(context); + game.canGiveTip() ? game.showTip(gameCubit) : null; + }, + )); + menuActions.add(const Spacer()); + menuActions.add(TextButton( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: game.showConflicts == true ? Colors.blue : Colors.white, + width: 3, + ), + ), + child: const Image( + image: AssetImage('assets/icons/button_show_conflicts.png'), + fit: BoxFit.fill, + ), + ), + onPressed: () { + gameCubit.toggleShowConflicts(); + }, + )); + } + + return AppBar( + title: const SizedBox.shrink(), + actions: menuActions, + ); + }, + ); + } + + @override + Size get preferredSize => const Size.fromHeight(50); +} diff --git a/lib/ui/widgets/message_game_end.dart b/lib/ui/widgets/message_game_end.dart index 168e055e50074f24ad953566e5c52d9df8633e5d..8716d31d8ea27bd364cfe71b8c504e6484525e7a 100644 --- a/lib/ui/widgets/message_game_end.dart +++ b/lib/ui/widgets/message_game_end.dart @@ -1,45 +1,51 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:sudoku/cubit/game_cubit.dart'; +import 'package:sudoku/models/game.dart'; import 'package:sudoku/ui/widgets/button_game_restart.dart'; -import 'package:sudoku/provider/data.dart'; class EndGameMessage extends StatelessWidget { - const EndGameMessage({super.key, required this.myProvider}); - - final Data myProvider; + const EndGameMessage({super.key}); @override Widget build(BuildContext context) { - const Image decorationImage = Image( - image: AssetImage('assets/icons/game_win.png'), - fit: BoxFit.fill, - ); + return BlocBuilder<GameCubit, GameState>( + builder: (BuildContext context, GameState gameState) { + final Game game = gameState.game; + + const Image decorationImage = Image( + image: AssetImage('assets/icons/game_win.png'), + fit: BoxFit.fill, + ); - return Container( - margin: const EdgeInsets.all(2), - padding: const EdgeInsets.all(2), - child: Table( - defaultColumnWidth: const IntrinsicColumnWidth(), - children: [ - TableRow( + return Container( + margin: const EdgeInsets.all(2), + padding: const EdgeInsets.all(2), + child: Table( + defaultColumnWidth: const IntrinsicColumnWidth(), children: [ - const Column( - children: [decorationImage], - ), - Column( + TableRow( children: [ - myProvider.animationInProgress - ? decorationImage - : RestartGameButton(myProvider: myProvider) + const Column( + children: [decorationImage], + ), + Column( + children: [ + game.animationInProgress == true + ? decorationImage + : const RestartGameButton() + ], + ), + const Column( + children: [decorationImage], + ), ], ), - const Column( - children: [decorationImage], - ), ], ), - ], - ), + ); + }, ); } } diff --git a/lib/ui/widgets/parameter_image.dart b/lib/ui/widgets/parameter_image.dart new file mode 100644 index 0000000000000000000000000000000000000000..54787e2c0a15c8f84c218181e7f06b9624a5d88d --- /dev/null +++ b/lib/ui/widgets/parameter_image.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; + +class ParameterImage extends StatelessWidget { + const ParameterImage({ + super.key, + required this.code, + required this.value, + required this.isSelected, + }); + + final String code; + final String value; + final bool isSelected; + + static const Color buttonBackgroundColor = Colors.white; + static const Color buttonBorderColorActive = Colors.blue; + static const Color buttonBorderColorInactive = Colors.white; + static const double buttonBorderWidth = 8.0; + static const double buttonBorderRadius = 8.0; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: buttonBackgroundColor, + borderRadius: BorderRadius.circular(buttonBorderRadius), + border: Border.all( + color: isSelected ? buttonBorderColorActive : buttonBorderColorInactive, + width: buttonBorderWidth, + ), + ), + child: Image( + image: AssetImage('assets/icons/${code}_$value.png'), + fit: BoxFit.fill, + ), + ); + } +} diff --git a/lib/utils/board_animate.dart b/lib/utils/board_animate.dart index dd28298a282b09ec9ce4b7d1ce82903616b46dee..40adc186efb3ecb677b26ea1986d628aad06bb3b 100644 --- a/lib/utils/board_animate.dart +++ b/lib/utils/board_animate.dart @@ -1,20 +1,21 @@ import 'dart:async'; -import 'package:sudoku/provider/data.dart'; +import 'package:sudoku/cubit/game_cubit.dart'; +import 'package:sudoku/models/game.dart'; +import 'package:sudoku/models/types.dart'; class BoardAnimate { // Start game animation: blinking tiles - static AnimatedBoardSequence createStartGameAnimationPatterns(Data myProvider) { + static AnimatedBoardSequence createStartGameAnimationPatterns(Game game) { AnimatedBoardSequence patterns = []; int patternsCount = 3; - int boardSideLength = myProvider.blockSizeHorizontal * myProvider.blockSizeVertical; for (int patternIndex = 0; patternIndex < patternsCount; patternIndex++) { AnimatedBoard pattern = []; - for (int row = 0; row < boardSideLength; row++) { + for (int row = 0; row < game.boardSize; row++) { List<bool> patternRow = []; - for (int col = 0; col < boardSideLength; col++) { + for (int col = 0; col < game.boardSize; col++) { patternRow.add(((patternIndex + row + col) % 2 == 0)); } pattern.add(patternRow); @@ -26,17 +27,16 @@ class BoardAnimate { } // Win game animation: fill board with colored rows, from bottom to top - static AnimatedBoardSequence createWinGameAnimationPatterns(Data myProvider) { + static AnimatedBoardSequence createWinGameAnimationPatterns(Game game) { AnimatedBoardSequence patterns = []; - int boardSideLength = myProvider.blockSizeHorizontal * myProvider.blockSizeVertical; - int patternsCount = boardSideLength + 6; + int patternsCount = game.boardSize + 6; for (int patternIndex = 0; patternIndex < patternsCount; patternIndex++) { AnimatedBoard pattern = []; - for (int row = 0; row < boardSideLength; row++) { + for (int row = 0; row < game.boardSize; row++) { List<bool> patternRow = []; - for (int col = 0; col < boardSideLength; col++) { + for (int col = 0; col < game.boardSize; col++) { patternRow.add(row > (patternIndex - 4)); } pattern.add(patternRow); @@ -48,17 +48,17 @@ class BoardAnimate { } // Default multi-purpose animation: sliding stripes, from top left to right bottom - static AnimatedBoardSequence createDefaultAnimationPatterns(Data myProvider) { + static AnimatedBoardSequence createDefaultAnimationPatterns(Game game) { AnimatedBoardSequence patterns = []; - int boardSideLength = myProvider.blockSizeHorizontal * myProvider.blockSizeVertical; + int boardSideLength = game.boardSize; int patternsCount = boardSideLength; for (int patternIndex = 0; patternIndex < patternsCount; patternIndex++) { AnimatedBoard pattern = []; - for (int row = 0; row < boardSideLength; row++) { + for (int row = 0; row < game.boardSize; row++) { List<bool> patternRow = []; - for (int col = 0; col < boardSideLength; col++) { + for (int col = 0; col < game.boardSize; col++) { patternRow.add(((patternIndex + row + col) % 4 == 0)); } pattern.add(patternRow); @@ -69,23 +69,25 @@ class BoardAnimate { return patterns; } - static void startAnimation(Data myProvider, String animationType) { + static void startAnimation(GameCubit gameCubit, String animationType) { + final Game game = gameCubit.state.game; + AnimatedBoardSequence patterns = []; switch (animationType) { case 'start': - patterns = createStartGameAnimationPatterns(myProvider); + patterns = createStartGameAnimationPatterns(game); break; case 'win': - patterns = createWinGameAnimationPatterns(myProvider); + patterns = createWinGameAnimationPatterns(game); break; default: - patterns = createDefaultAnimationPatterns(myProvider); + patterns = createDefaultAnimationPatterns(game); } int patternIndex = patterns.length; - myProvider.updateAnimationInProgress(true); + gameCubit.updateAnimationInProgress(true); const interval = Duration(milliseconds: 200); Timer.periodic( @@ -93,11 +95,11 @@ class BoardAnimate { (Timer timer) { if (patternIndex == 0) { timer.cancel(); - myProvider.resetAnimatedBackground(); - myProvider.updateAnimationInProgress(false); + gameCubit.resetAnimatedBackground(); + gameCubit.updateAnimationInProgress(false); } else { patternIndex--; - myProvider.setAnimatedBackground(patterns[patternIndex]); + gameCubit.setAnimatedBackground(patterns[patternIndex]); } }, ); diff --git a/lib/utils/board_utils.dart b/lib/utils/board_utils.dart index 8f0a835da2297b5fafebf68e5ba164ba718a8bee..491c3d1a07e3677aad8aad21b3998ee60ba86690 100644 --- a/lib/utils/board_utils.dart +++ b/lib/utils/board_utils.dart @@ -1,59 +1,19 @@ import 'dart:math'; -import 'package:sudoku/entities/cell.dart'; -import 'package:sudoku/provider/data.dart'; -import 'package:sudoku/utils/random_pick_grid.dart'; +import 'package:sudoku/models/board.dart'; +import 'package:sudoku/models/cell.dart'; +import 'package:sudoku/models/cell_location.dart'; +import 'package:sudoku/models/types.dart'; import 'package:sudoku/utils/tools.dart'; class BoardUtils { - static printGrid(Board cells, Board solvedCells) { - String stringValues = '0123456789ABCDEFG'; - printlog(''); - printlog('-------'); - for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) { - String row = ''; - String rowSolved = ''; - for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) { - row += stringValues[cells[rowIndex][colIndex].value]; - rowSolved += stringValues[solvedCells[rowIndex][colIndex].value]; - } - printlog('$row | $rowSolved'); - } - printlog('-------'); - printlog(''); - } - - static Future<void> pickGrid(Data myProvider) async { - String grid = ''; - RandomPickGrid randomPickGrid; - - randomPickGrid = RandomPickGrid(); - await randomPickGrid.init(myProvider.parameterLevel, myProvider.parameterSize); - - grid = randomPickGrid.grid; - - final int blockSizeHorizontal = myProvider.blockSizeHorizontal; - final int blockSizeVertical = myProvider.blockSizeVertical; - - if (grid.length == pow(blockSizeHorizontal * blockSizeVertical, 2)) { - printlog('Picked grid from template: $grid'); - bool isSymetric = (blockSizeHorizontal == blockSizeVertical); - myProvider.updateCells(BoardUtils.createBoardFromTemplate(grid, isSymetric)); - myProvider.updateCellsSolved(BoardUtils.getSolvedGrid(myProvider)); - myProvider.selectCell(null, null); - - printGrid(myProvider.board, myProvider.boardSolved); - } - } - - static Board createEmptyBoard(final int boardSize) { - final Board cells = []; + static BoardCells createEmptyBoard(final int boardSize) { + final BoardCells cells = []; for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { final List<Cell> row = []; for (int colIndex = 0; colIndex < boardSize; colIndex++) { row.add(Cell( - row: rowIndex, - col: colIndex, + location: CellLocation.go(rowIndex, colIndex), value: 0, isFixed: false, )); @@ -64,56 +24,12 @@ class BoardUtils { return cells; } - static Board createBoardFromSavedState(Data myProvider, String savedBoard) { - final Board cells = []; - final int boardSize = int.parse(pow((savedBoard.length / 2), 1 / 2).toStringAsFixed(0)); - - const String stringValues = '0123456789ABCDEFG'; - - int index = 0; - for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { - final List<Cell> row = []; - for (int colIndex = 0; colIndex < boardSize; colIndex++) { - final String stringValue = savedBoard[index++]; - final int value = stringValues.indexOf(stringValue); - - final String isFixedString = savedBoard[index++]; - final bool isFixed = (isFixedString != ' '); - - row.add(Cell( - row: rowIndex, - col: colIndex, - value: value, - isFixed: isFixed, - )); - } - cells.add(row); - } - - return cells; - } - - static Board copyBoard(List cells) { - final Board copiedGrid = []; - for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) { - final List<Cell> row = []; - for (int colIndex = 0; colIndex < cells[rowIndex].length; colIndex++) { - row.add(Cell( - row: rowIndex, - col: colIndex, - value: cells[rowIndex][colIndex].value, - isFixed: false, - )); - } - copiedGrid.add(row); - } - - return copiedGrid; - } - - static Board createBoardFromTemplate(String grid, bool isSymetric) { - Board cells = []; - final int boardSize = int.parse(pow(grid.length, 1 / 2).toStringAsFixed(0)); + static Board createBoardFromTemplate({ + required String template, + required bool isSymetric, + }) { + BoardCells cells = []; + final int boardSize = int.parse(pow(template.length, 1 / 2).toStringAsFixed(0)); const String stringValues = '0123456789ABCDEFG'; @@ -121,11 +37,10 @@ class BoardUtils { for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { final List<Cell> row = []; for (int colIndex = 0; colIndex < boardSize; colIndex++) { - final String stringValue = grid[index++]; + final String stringValue = template[index++]; final int value = stringValues.indexOf(stringValue); row.add(Cell( - row: rowIndex, - col: colIndex, + location: CellLocation.go(rowIndex, colIndex), value: value, isFixed: (value != 0), )); @@ -151,12 +66,11 @@ class BoardUtils { switch (flip) { case 'horizontal': { - final Board transformedBoard = createEmptyBoard(boardSize); + final BoardCells transformedBoard = createEmptyBoard(boardSize); for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { for (int colIndex = 0; colIndex < boardSize; colIndex++) { transformedBoard[rowIndex][colIndex] = Cell( - row: rowIndex, - col: colIndex, + location: CellLocation.go(rowIndex, colIndex), value: cells[boardSize - rowIndex - 1][colIndex].value, isFixed: false, ); @@ -167,12 +81,11 @@ class BoardUtils { break; case 'vertical': { - final Board transformedBoard = createEmptyBoard(boardSize); + final BoardCells transformedBoard = createEmptyBoard(boardSize); for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { for (int colIndex = 0; colIndex < boardSize; colIndex++) { transformedBoard[rowIndex][colIndex] = Cell( - row: rowIndex, - col: colIndex, + location: CellLocation.go(rowIndex, colIndex), value: cells[rowIndex][boardSize - colIndex - 1].value, isFixed: false, ); @@ -186,12 +99,11 @@ class BoardUtils { switch (rotate) { case 'left': { - final Board transformedBoard = createEmptyBoard(boardSize); + final BoardCells transformedBoard = createEmptyBoard(boardSize); for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { for (int colIndex = 0; colIndex < boardSize; colIndex++) { transformedBoard[rowIndex][colIndex] = Cell( - row: rowIndex, - col: colIndex, + location: CellLocation.go(rowIndex, colIndex), value: cells[colIndex][boardSize - rowIndex - 1].value, isFixed: false, ); @@ -202,12 +114,11 @@ class BoardUtils { break; case 'right': { - final Board transformedBoard = createEmptyBoard(boardSize); + final BoardCells transformedBoard = createEmptyBoard(boardSize); for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { for (int colIndex = 0; colIndex < boardSize; colIndex++) { transformedBoard[rowIndex][colIndex] = Cell( - row: rowIndex, - col: colIndex, + location: CellLocation.go(rowIndex, colIndex), value: cells[boardSize - colIndex - 1][rowIndex].value, isFixed: false, ); @@ -222,293 +133,15 @@ class BoardUtils { for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { for (int colIndex = 0; colIndex < boardSize; colIndex++) { cells[rowIndex][colIndex] = Cell( - row: rowIndex, - col: colIndex, + location: CellLocation.go(rowIndex, colIndex), value: cells[rowIndex][colIndex].value, isFixed: (cells[rowIndex][colIndex].value != 0) ? true : false, ); } } - return cells; - } - - static bool checkBoardIsSolved(Data myProvider) { - final Board cells = myProvider.board; - final ConflictsCount conflicts = myProvider.boardConflicts; - - final int blockSizeHorizontal = myProvider.blockSizeHorizontal; - final int blockSizeVertical = myProvider.blockSizeVertical; - - final int boardSize = blockSizeHorizontal * blockSizeVertical; - - // (re)compute conflicts - BoardUtils.computeConflictsInBoard(myProvider); - - // check grid is fully completed and does not contain conflict - for (int row = 0; row < boardSize; row++) { - for (int col = 0; col < boardSize; col++) { - if (cells[row][col].value == 0 || conflicts[row][col] != 0) { - return false; - } - } - } - - printlog('-> ok sudoku solved!'); - - return true; - } - - static bool isValueAllowed( - Board cells, - int blockSizeHorizontal, - int blockSizeVertical, - int? candidateCol, - int? candidateRow, - int candidateValue, - ) { - if ((candidateCol == null) || (candidateRow == null) || (candidateValue == 0)) { - return true; - } - - final int boardSize = blockSizeHorizontal * blockSizeVertical; - - // check lines does not contains a value twice - for (int row = 0; row < boardSize; row++) { - final List<int> values = []; - for (int col = 0; col < boardSize; col++) { - int value = cells[row][col].value; - if (row == candidateRow && col == candidateCol) { - value = candidateValue; - } - if (value != 0) { - values.add(value); - } - } - final List<int> distinctValues = values.toSet().toList(); - if (values.length != distinctValues.length) { - return false; - } - } - - // check columns does not contains a value twice - for (int col = 0; col < boardSize; col++) { - final List<int> values = []; - for (int row = 0; row < boardSize; row++) { - int value = cells[row][col].value; - if (row == candidateRow && col == candidateCol) { - value = candidateValue; - } - if (value != 0) { - values.add(value); - } - } - final List<int> distinctValues = values.toSet().toList(); - if (values.length != distinctValues.length) { - return false; - } - } - - // check blocks does not contains a value twice - final int horizontalBlocksCount = blockSizeVertical; - final int verticalBlocksCount = blockSizeHorizontal; - for (int blockRow = 0; blockRow < verticalBlocksCount; blockRow++) { - for (int blockCol = 0; blockCol < horizontalBlocksCount; blockCol++) { - final List<int> values = []; - - for (int rowInBlock = 0; rowInBlock < blockSizeVertical; rowInBlock++) { - for (int colInBlock = 0; colInBlock < blockSizeHorizontal; colInBlock++) { - final int row = (blockRow * blockSizeVertical) + rowInBlock; - final int col = (blockCol * blockSizeHorizontal) + colInBlock; - int value = cells[row][col].value; - if (row == candidateRow && col == candidateCol) { - value = candidateValue; - } - if (value != 0) { - values.add(value); - } - } - } - - final List<int> distinctValues = values.toSet().toList(); - if (values.length != distinctValues.length) { - return false; - } - } - } - - return true; - } - - static void computeConflictsInBoard(Data myProvider) { - final Board cells = myProvider.board; - final ConflictsCount conflicts = myProvider.boardConflicts; - - final int blockSizeHorizontal = myProvider.blockSizeHorizontal; - final int blockSizeVertical = myProvider.blockSizeVertical; - - final int boardSize = blockSizeHorizontal * blockSizeVertical; - - // reset conflict states - for (int row = 0; row < boardSize; row++) { - for (int col = 0; col < boardSize; col++) { - conflicts[row][col] = 0; - } - } - - // check lines does not contains a value twice - for (int row = 0; row < boardSize; row++) { - final List<int> values = []; - for (int col = 0; col < boardSize; col++) { - int value = cells[row][col].value; - if (value != 0) { - values.add(value); - } - } - final List<int> distinctValues = values.toSet().toList(); - if (values.length != distinctValues.length) { - printlog('line $row contains duplicates'); - // Add line to cells in conflict - for (int col = 0; col < boardSize; col++) { - conflicts[row][col]++; - } - } - } - - // check columns does not contains a value twice - for (int col = 0; col < boardSize; col++) { - final List<int> values = []; - for (int row = 0; row < boardSize; row++) { - int value = cells[row][col].value; - if (value != 0) { - values.add(value); - } - } - final List<int> distinctValues = values.toSet().toList(); - if (values.length != distinctValues.length) { - printlog('column $col contains duplicates'); - // Add column to cells in conflict - for (int row = 0; row < boardSize; row++) { - conflicts[row][col]++; - } - } - } - - // check blocks does not contains a value twice - final int horizontalBlocksCount = blockSizeVertical; - final int verticalBlocksCount = blockSizeHorizontal; - for (int blockRow = 0; blockRow < verticalBlocksCount; blockRow++) { - for (int blockCol = 0; blockCol < horizontalBlocksCount; blockCol++) { - List<int> values = []; - - for (int rowInBlock = 0; rowInBlock < blockSizeVertical; rowInBlock++) { - for (int colInBlock = 0; colInBlock < blockSizeHorizontal; colInBlock++) { - int row = (blockRow * blockSizeVertical) + rowInBlock; - int col = (blockCol * blockSizeHorizontal) + colInBlock; - int value = cells[row][col].value; - if (value != 0) { - values.add(value); - } - } - } - - List<int> distinctValues = values.toSet().toList(); - if (values.length != distinctValues.length) { - printlog('block [$blockCol,$blockRow] contains duplicates'); - // Add blocks to cells in conflict - for (int rowInBlock = 0; rowInBlock < blockSizeVertical; rowInBlock++) { - for (int colInBlock = 0; colInBlock < blockSizeHorizontal; colInBlock++) { - int row = (blockRow * blockSizeVertical) + rowInBlock; - int col = (blockCol * blockSizeHorizontal) + colInBlock; - conflicts[row][col]++; - } - } - } - } - } - - myProvider.updateConflicts(conflicts); - } - - static List<List<int>> getCellsWithWrongValue( - final Board cells, - final Board cellsSolved, - final int blockSizeHorizontal, - final int blockSizeVertical, - ) { - final List<List<int>> cellsWithWrongValue = []; - final int boardSize = blockSizeHorizontal * blockSizeVertical; - - for (int row = 0; row < boardSize; row++) { - for (int col = 0; col < boardSize; col++) { - if (cells[row][col].value != 0 && - cells[row][col].value != cellsSolved[row][col].value) { - cellsWithWrongValue.add([col, row]); - } - } - } - - return cellsWithWrongValue; - } - - static List<List<int>> getCellsWithUniqueAvailableValue( - Board cells, - final int blockSizeHorizontal, - final int blockSizeVertical, - ) { - final List<List<int>> candidateCells = []; - final int boardSize = blockSizeHorizontal * blockSizeVertical; - - for (int row = 0; row < boardSize; row++) { - for (int col = 0; col < boardSize; col++) { - if (cells[row][col].value == 0) { - int allowedValuesCount = 0; - int candidateValue = 0; - for (int value = 1; value <= boardSize; value++) { - if (BoardUtils.isValueAllowed( - cells, blockSizeHorizontal, blockSizeVertical, col, row, value)) { - candidateValue = value; - allowedValuesCount++; - } - } - if (allowedValuesCount == 1) { - candidateCells.add([col, row, candidateValue]); - } - } - } - } - - return candidateCells; - } - - static Board getSolvedGrid(Data myProvider) { - final Board cells = copyBoard(myProvider.board); - final int blockSizeHorizontal = myProvider.blockSizeHorizontal; - int blockSizeVertical = myProvider.blockSizeVertical; - - do { - List<List<int>> cellsWithUniqueAvailableValue = - BoardUtils.getCellsWithUniqueAvailableValue( - cells, - blockSizeHorizontal, - blockSizeVertical, - ); - if (cellsWithUniqueAvailableValue.isEmpty) { - break; - } - for (int i = 0; i < cellsWithUniqueAvailableValue.length; i++) { - int col = cellsWithUniqueAvailableValue[i][0]; - int row = cellsWithUniqueAvailableValue[i][1]; - int value = cellsWithUniqueAvailableValue[i][2]; - cells[row][col] = Cell( - row: row, - col: col, - value: value, - isFixed: cells[row][col].isFixed, - ); - } - } while (true); - - return cells; + return Board.createNew( + cells: cells, + ); } } diff --git a/lib/utils/game_utils.dart b/lib/utils/game_utils.dart deleted file mode 100644 index 4de2454cd2d382bc54cbe327f308ed9a7546a278..0000000000000000000000000000000000000000 --- a/lib/utils/game_utils.dart +++ /dev/null @@ -1,147 +0,0 @@ -import 'package:sudoku/provider/data.dart'; -import 'package:sudoku/utils/board_animate.dart'; -import 'package:sudoku/utils/board_utils.dart'; -import 'package:sudoku/utils/tools.dart'; - -class GameUtils { - static Future<void> quitGame(Data myProvider) async { - myProvider.updateGameIsRunning(false); - if (BoardUtils.checkBoardIsSolved(myProvider)) { - myProvider.resetCurrentSavedState(); - } - } - - static Future<void> startNewGame(Data myProvider) async { - myProvider.updateParameterSize(myProvider.parameterSize); - myProvider.updateGameIsRunning(true); - myProvider.resetGivenTipsCount(); - myProvider.shuffleCellValues(); - myProvider.updateCells(BoardUtils.createEmptyBoard( - myProvider.blockSizeHorizontal * myProvider.blockSizeVertical)); - myProvider.initAnimatedBackground(); - myProvider.initConflictsBoard(); - BoardUtils.pickGrid(myProvider); - BoardAnimate.startAnimation(myProvider, 'start'); - } - - static void deleteSavedGame(Data myProvider) { - myProvider.resetCurrentSavedState(); - } - - static void resumeSavedGame(Data myProvider) { - final Map<String, dynamic> savedState = myProvider.getCurrentSavedState(); - if (savedState.isNotEmpty) { - try { - myProvider.setParameterValue('level', savedState['level']); - myProvider.setParameterValue('size', savedState['size']); - myProvider.setParameterValue('skin', savedState['skin']); - - myProvider.setGivenTipsCount(savedState['tipsCount']); - myProvider.updateShowConflicts(savedState['showConflicts']); - - myProvider.setShuffleCellValues(savedState['shuffledCellValues']); - - myProvider.updateCells( - BoardUtils.createBoardFromSavedState(myProvider, savedState['boardValues'])); - myProvider.updateGameIsRunning(true); - } catch (e) { - printlog('Failed to resume game. Will start new one instead.'); - myProvider.resetCurrentSavedState(); - myProvider.initParametersValues(); - startNewGame(myProvider); - } - } else { - myProvider.resetCurrentSavedState(); - myProvider.initParametersValues(); - startNewGame(myProvider); - } - } - - static void showTip(Data myProvider) { - if (myProvider.selectedCellCol == null || myProvider.selectedCellRow == null) { - // no selected cell -> pick one - GameUtils.helpSelectCell(myProvider); - } else { - // currently selected cell -> set value - GameUtils.helpFillCell(myProvider); - } - myProvider.increaseGivenTipsCount(); - } - - static void helpSelectCell(Data myProvider) { - final Board cells = myProvider.board; - final int blockSizeHorizontal = myProvider.blockSizeHorizontal; - final int blockSizeVertical = myProvider.blockSizeVertical; - - // pick one of wrong value cells, if found - final List<List<int>> wrongValueCells = BoardUtils.getCellsWithWrongValue( - cells, - myProvider.boardSolved, - blockSizeHorizontal, - blockSizeVertical, - ); - if (wrongValueCells.isNotEmpty) { - GameUtils.pickRandomFromList(myProvider, wrongValueCells); - return; - } - - // pick one of conflicting cells, if found - final List<List<int>> conflictingCells = BoardUtils.getCellsWithUniqueAvailableValue( - cells, - blockSizeHorizontal, - blockSizeVertical, - ); - if (conflictingCells.isNotEmpty) { - GameUtils.pickRandomFromList(myProvider, conflictingCells); - return; - } - - // pick one form cells with unique non-conflicting candidate value - final List<List<int>> candidateCells = BoardUtils.getCellsWithUniqueAvailableValue( - cells, - blockSizeHorizontal, - blockSizeVertical, - ); - if (candidateCells.isNotEmpty) { - GameUtils.pickRandomFromList(myProvider, candidateCells); - return; - } - } - - static void pickRandomFromList(Data myProvider, List<List<int>> cellsCoordinates) { - if (cellsCoordinates.isNotEmpty) { - cellsCoordinates.shuffle(); - List<int> cell = cellsCoordinates[0]; - myProvider.selectCell(cell[0], cell[1]); - } - } - - static void helpFillCell(Data myProvider) { - final Board cells = myProvider.board; - final int blockSizeHorizontal = myProvider.blockSizeHorizontal; - final int blockSizeVertical = myProvider.blockSizeVertical; - - final int boardSize = blockSizeHorizontal * blockSizeVertical; - - // Will clean cell if no eligible value found - int eligibleValue = 0; - - // ensure there is only one eligible value for this cell - int allowedValuesCount = 0; - for (int value = 1; value <= boardSize; value++) { - if (BoardUtils.isValueAllowed(cells, blockSizeHorizontal, blockSizeVertical, - myProvider.selectedCellCol, myProvider.selectedCellRow, value)) { - allowedValuesCount++; - eligibleValue = value; - } - } - - myProvider.updateCellValue(myProvider.selectedCellCol, myProvider.selectedCellRow, - allowedValuesCount == 1 ? eligibleValue : 0); - myProvider.selectCell(null, null); - if (BoardUtils.checkBoardIsSolved(myProvider)) { - myProvider.resetCurrentSavedState(); - BoardAnimate.startAnimation(myProvider, 'win'); - } - } -} diff --git a/lib/utils/random_pick_grid.dart b/lib/utils/random_pick_grid.dart deleted file mode 100644 index 795988460a9bcfbeb2f489a79d1e876f91c8726a..0000000000000000000000000000000000000000 --- a/lib/utils/random_pick_grid.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/services.dart'; - -import 'package:sudoku/utils/tools.dart'; - -class RandomPickGrid { - RandomPickGrid(); - - String _grid = ''; - - init(String level, String size) async { - _grid = ''; - await gridFromLocalFile(level, size); - } - - Future<void> gridFromLocalFile(String level, String size) async { - // Get global grids list - List grids = []; - try { - String jsonString = await rootBundle.loadString('assets/files/templates.json'); - final jsonResponse = await json.decode(jsonString); - grids = jsonResponse['templates'][size][level]; - } catch (e) { - printlog("$e"); - } - - // Check we have enough grids - if (grids.isEmpty) { - printlog('Not enough grids [$size, $level] in templates.'); - } - - // Randomize grids list - grids.shuffle(); - - // Pick first grids from shuffled list - _grid = grids.take(1).toList()[0]; - } - - String get grid => _grid; -} diff --git a/pubspec.lock b/pubspec.lock index cc569cd7a5e93d27e7349cd166dfaf1efd0d63dd..4991f4dceb056cec471bfc8547563946a71e98ea 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" async: dependency: transitive description: @@ -17,6 +25,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + bloc: + dependency: transitive + description: + name: bloc + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + url: "https://pub.dev" + source: hosted + version: "8.1.4" characters: dependency: transitive description: @@ -25,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" collection: dependency: transitive description: @@ -33,6 +57,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + easy_localization: + dependency: "direct main" + description: + name: easy_localization + sha256: "432698c31a488dd64c56d4759f20d04844baba5e9e4f2cb1abb9676257918b17" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + easy_logger: + dependency: transitive + description: + name: easy_logger + sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7 + url: "https://pub.dev" + source: hosted + version: "0.0.2" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" ffi: dependency: transitive description: @@ -54,19 +110,56 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2 + url: "https://pub.dev" + source: hosted + version: "8.1.5" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_web_plugins: dependency: transitive description: flutter source: sdk version: "0.0.0" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hydrated_bloc: + dependency: "direct main" + description: + name: hydrated_bloc + sha256: af35b357739fe41728df10bec03aad422cdc725a1e702e03af9d2a41ea05160c + url: "https://pub.dev" + source: hosted + version: "9.1.5" + intl: + dependency: transitive + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" lints: dependency: transitive description: @@ -115,6 +208,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + url: "https://pub.dev" + source: hosted + version: "2.2.4" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + url: "https://pub.dev" + source: hosted + version: "2.3.2" path_provider_linux: dependency: transitive description: @@ -156,7 +273,7 @@ packages: source: hosted version: "2.1.8" provider: - dependency: "direct main" + dependency: transitive description: name: provider sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c @@ -164,21 +281,21 @@ packages: source: hosted version: "6.1.2" shared_preferences: - dependency: "direct main" + dependency: transitive description: name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_foundation: dependency: transitive description: @@ -224,6 +341,22 @@ packages: description: flutter source: sdk version: "0.0.99" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" vector_math: dependency: transitive description: @@ -236,18 +369,18 @@ packages: dependency: transitive description: name: web - sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad" + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.5.1" win32: dependency: transitive description: name: win32 - sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" + sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" url: "https://pub.dev" source: hosted - version: "5.2.0" + version: "5.5.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9c7e81f30e99d0d95ee2f2e8439789911cf4e51a..6c79d6b72d4e8694622522f0345e41bab3e78cca 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,9 @@ name: sudoku description: A sudoku game application. + publish_to: 'none' -version: 0.1.18+67 + +version: 0.1.19+68 environment: sdk: '^3.0.0' @@ -9,10 +11,14 @@ environment: dependencies: flutter: sdk: flutter - provider: ^6.0.5 badges: ^3.1.2 - shared_preferences: ^2.2.1 + easy_localization: ^3.0.1 + equatable: ^2.0.5 + flutter_bloc: ^8.1.1 + hive: ^2.2.3 + hydrated_bloc: ^9.0.0 overlay_support: ^2.1.0 + path_provider: ^2.0.11 dev_dependencies: flutter_lints: ^3.0.1 @@ -20,6 +26,18 @@ dev_dependencies: flutter: uses-material-design: true assets: - - assets/files/ - assets/icons/ - assets/skins/ + - assets/translations/ + + fonts: + - family: Nunito + fonts: + - asset: assets/fonts/Nunito-Bold.ttf + weight: 700 + - asset: assets/fonts/Nunito-Medium.ttf + weight: 500 + - asset: assets/fonts/Nunito-Regular.ttf + weight: 400 + - asset: assets/fonts/Nunito-Light.ttf + weight: 300