diff --git a/.gitignore b/.gitignore index 7bb0aa0..d0c6412 100644 --- a/.gitignore +++ b/.gitignore @@ -164,4 +164,4 @@ Thumbs.db ## You should delete or comment out the next line if you have configuration in a different resource-config.json . **/resource-config.json -assets/saves/ +#assets/saves/ diff --git a/assets/levels.json b/assets/levels.json index 40f82bd..f2c9759 100644 --- a/assets/levels.json +++ b/assets/levels.json @@ -1,5 +1,5 @@ { "levels": [ - "9" + "0" ] } diff --git a/assets/saves/oct26-72/best-calculator-36.json b/assets/saves/oct26-72/best-calculator-36.json new file mode 100644 index 0000000..a4c3c89 --- /dev/null +++ b/assets/saves/oct26-72/best-calculator-36.json @@ -0,0 +1 @@ +{"isAddBias":true,"inputs":[{"id":0,"x":0.1,"incoming":[]},{"id":1,"x":0.1,"incoming":[]},{"id":2,"x":0.1,"incoming":[]},{"id":3,"x":0.1,"incoming":[]},{"id":4,"x":0.1,"incoming":[]},{"id":5,"x":0.1,"incoming":[]},{"id":6,"x":0.1,"incoming":[]},{"id":7,"x":0.1,"incoming":[]},{"id":8,"x":0.1,"incoming":[]},{"id":9,"x":0.1,"incoming":[]}],"hidden":[{"id":50,"x":0.29999998,"incoming":[{"fromId":4,"weight":0.3260812,"enabled":true},{"fromId":0,"weight":-0.13214411,"enabled":true},{"fromId":5,"weight":-0.23394921,"enabled":true}]},{"id":65,"x":0.29999998,"incoming":[{"fromId":1,"weight":0.4682414,"enabled":true},{"fromId":0,"weight":-0.47455698,"enabled":true}]},{"id":95,"x":0.29999998,"incoming":[{"fromId":3,"weight":0.8860415,"enabled":true},{"fromId":0,"weight":0.5815747,"enabled":true}]},{"id":15,"x":0.49999997,"incoming":[{"fromId":4,"weight":0.104795486,"enabled":true},{"fromId":0,"weight":-0.98747563,"enabled":true},{"fromId":2,"weight":1.0192616,"enabled":true},{"fromId":50,"weight":0.20612434,"enabled":true},{"fromId":6,"weight":-0.91859585,"enabled":true},{"fromId":8,"weight":-1.532623,"enabled":true}]},{"id":18,"x":0.49999997,"incoming":[{"fromId":6,"weight":-0.31562048,"enabled":true},{"fromId":0,"weight":0.054992825,"enabled":true},{"fromId":2,"weight":0.061889596,"enabled":true}]},{"id":19,"x":0.49999997,"incoming":[{"fromId":6,"weight":-0.5483341,"enabled":true},{"fromId":0,"weight":-0.43364102,"enabled":true},{"fromId":1,"weight":0.09399427,"enabled":true},{"fromId":8,"weight":-0.56737363,"enabled":true},{"fromId":50,"weight":0.81796086,"enabled":true}]},{"id":21,"x":0.49999997,"incoming":[{"fromId":5,"weight":0.15442422,"enabled":true},{"fromId":0,"weight":0.11729443,"enabled":true},{"fromId":7,"weight":0.29076958,"enabled":true},{"fromId":9,"weight":0.869735,"enabled":true},{"fromId":2,"weight":-0.18688652,"enabled":true},{"fromId":1,"weight":-0.069211215,"enabled":true},{"fromId":50,"weight":-0.095420495,"enabled":true}]},{"id":22,"x":0.49999997,"incoming":[{"fromId":1,"weight":-0.34996057,"enabled":true},{"fromId":0,"weight":1.5778112,"enabled":true}]},{"id":23,"x":0.49999997,"incoming":[{"fromId":3,"weight":-0.097104095,"enabled":true},{"fromId":0,"weight":0.2671735,"enabled":true}]},{"id":24,"x":0.49999997,"incoming":[{"fromId":0,"weight":0.35363954,"enabled":true}]},{"id":25,"x":0.49999997,"incoming":[{"fromId":4,"weight":1.7080326,"enabled":true},{"fromId":0,"weight":-0.22062598,"enabled":true}]},{"id":26,"x":0.49999997,"incoming":[{"fromId":5,"weight":1.9095197,"enabled":true},{"fromId":0,"weight":-0.2787925,"enabled":true}]},{"id":27,"x":0.49999997,"incoming":[{"fromId":8,"weight":-0.3263183,"enabled":true},{"fromId":0,"weight":-0.41584808,"enabled":true}]},{"id":33,"x":0.49999997,"incoming":[{"fromId":7,"weight":0.9111565,"enabled":true},{"fromId":0,"weight":0.764455,"enabled":true},{"fromId":2,"weight":-0.17805265,"enabled":true},{"fromId":9,"weight":0.63011473,"enabled":true},{"fromId":65,"weight":-0.77902955,"enabled":true}]},{"id":34,"x":0.49999997,"incoming":[{"fromId":5,"weight":-0.5812924,"enabled":true},{"fromId":0,"weight":0.119829625,"enabled":true},{"fromId":3,"weight":0.94418555,"enabled":false},{"fromId":95,"weight":-0.17384759,"enabled":true}]},{"id":35,"x":0.49999997,"incoming":[{"fromId":9,"weight":0.50162625,"enabled":true},{"fromId":0,"weight":0.015230969,"enabled":true},{"fromId":2,"weight":0.6678108,"enabled":true},{"fromId":6,"weight":1.6147876,"enabled":true},{"fromId":1,"weight":0.84308577,"enabled":true}]},{"id":37,"x":0.49999997,"incoming":[{"fromId":9,"weight":0.5279875,"enabled":true},{"fromId":0,"weight":-0.57465184,"enabled":true}]},{"id":38,"x":0.49999997,"incoming":[{"fromId":9,"weight":1.2731506,"enabled":true},{"fromId":0,"weight":0.5932157,"enabled":true}]},{"id":41,"x":0.49999997,"incoming":[{"fromId":3,"weight":-0.85861194,"enabled":true},{"fromId":0,"weight":0.29206973,"enabled":true}]},{"id":44,"x":0.49999997,"incoming":[{"fromId":4,"weight":0.93435645,"enabled":true},{"fromId":0,"weight":-0.34713316,"enabled":true},{"fromId":9,"weight":0.36525097,"enabled":true}]},{"id":48,"x":0.49999997,"incoming":[{"fromId":1,"weight":0.209748,"enabled":true},{"fromId":0,"weight":0.06655161,"enabled":true},{"fromId":6,"weight":0.29044563,"enabled":true},{"fromId":65,"weight":-0.07227522,"enabled":true}]},{"id":53,"x":0.49999997,"incoming":[{"fromId":8,"weight":0.86038285,"enabled":true},{"fromId":0,"weight":0.30322415,"enabled":true}]},{"id":54,"x":0.49999997,"incoming":[{"fromId":1,"weight":0.8321307,"enabled":true},{"fromId":0,"weight":0.19514272,"enabled":true}]},{"id":45,"x":0.7,"incoming":[{"fromId":33,"weight":1.1273955,"enabled":true},{"fromId":0,"weight":-0.93755716,"enabled":true}]},{"id":49,"x":0.7,"incoming":[{"fromId":15,"weight":-0.13298008,"enabled":true},{"fromId":0,"weight":-0.28581312,"enabled":true},{"fromId":48,"weight":-0.24489906,"enabled":true},{"fromId":7,"weight":-0.49334025,"enabled":true},{"fromId":35,"weight":-0.29637682,"enabled":true}]},{"id":85,"x":0.7,"incoming":[{"fromId":41,"weight":0.028685674,"enabled":true},{"fromId":0,"weight":-1.0208642,"enabled":true}]}],"outputs":[{"id":10,"x":0.9,"incoming":[{"fromId":0,"weight":0.42874485,"enabled":true},{"fromId":1,"weight":1.5450724,"enabled":true},{"fromId":2,"weight":0.38866156,"enabled":true},{"fromId":3,"weight":-0.048597246,"enabled":true},{"fromId":4,"weight":-0.11140473,"enabled":true},{"fromId":5,"weight":-0.059036836,"enabled":true},{"fromId":6,"weight":1.029555,"enabled":true},{"fromId":7,"weight":0.41845065,"enabled":true},{"fromId":8,"weight":0.15941091,"enabled":true},{"fromId":9,"weight":-0.5737207,"enabled":true},{"fromId":19,"weight":-0.5511075,"enabled":true},{"fromId":23,"weight":0.5776908,"enabled":true},{"fromId":25,"weight":-0.37440747,"enabled":true},{"fromId":26,"weight":-0.34981215,"enabled":true},{"fromId":27,"weight":0.50026274,"enabled":true},{"fromId":37,"weight":-0.45316577,"enabled":true},{"fromId":48,"weight":-0.11845264,"enabled":true}]},{"id":11,"x":0.9,"incoming":[{"fromId":0,"weight":0.316352,"enabled":true},{"fromId":1,"weight":-1.1575654,"enabled":true},{"fromId":2,"weight":-0.64221,"enabled":true},{"fromId":3,"weight":0.09709452,"enabled":true},{"fromId":4,"weight":-0.099510014,"enabled":true},{"fromId":5,"weight":-0.5341325,"enabled":true},{"fromId":6,"weight":0.43441856,"enabled":true},{"fromId":7,"weight":-0.07322796,"enabled":true},{"fromId":8,"weight":-0.03106527,"enabled":true},{"fromId":9,"weight":0.358767,"enabled":true},{"fromId":18,"weight":-0.7623632,"enabled":true},{"fromId":22,"weight":-0.07749091,"enabled":true},{"fromId":38,"weight":0.98119074,"enabled":true},{"fromId":48,"weight":-0.65435505,"enabled":true},{"fromId":50,"weight":-0.25067723,"enabled":true}]},{"id":12,"x":0.9,"incoming":[{"fromId":0,"weight":0.21142665,"enabled":true},{"fromId":1,"weight":-0.61067617,"enabled":true},{"fromId":2,"weight":1.0964764,"enabled":true},{"fromId":3,"weight":0.34340036,"enabled":true},{"fromId":4,"weight":0.005664274,"enabled":true},{"fromId":5,"weight":-0.37394798,"enabled":true},{"fromId":6,"weight":0.26421446,"enabled":true},{"fromId":7,"weight":0.4770956,"enabled":true},{"fromId":8,"weight":0.29335403,"enabled":true},{"fromId":9,"weight":1.217599,"enabled":true},{"fromId":15,"weight":0.49869615,"enabled":true},{"fromId":24,"weight":0.4653223,"enabled":true},{"fromId":34,"weight":-0.073580146,"enabled":true},{"fromId":18,"weight":-0.7540115,"enabled":true},{"fromId":33,"weight":0.5142838,"enabled":true},{"fromId":49,"weight":0.3121156,"enabled":true},{"fromId":53,"weight":0.3028291,"enabled":true},{"fromId":50,"weight":-0.16403285,"enabled":true},{"fromId":21,"weight":-1.3699591,"enabled":true}]},{"id":13,"x":0.9,"incoming":[{"fromId":0,"weight":-0.002495844,"enabled":true},{"fromId":1,"weight":-0.2662651,"enabled":true},{"fromId":2,"weight":-0.10949284,"enabled":true},{"fromId":3,"weight":0.035460964,"enabled":true},{"fromId":4,"weight":-0.2954322,"enabled":true},{"fromId":5,"weight":-0.5018914,"enabled":true},{"fromId":6,"weight":0.42833543,"enabled":true},{"fromId":7,"weight":-0.2399191,"enabled":true},{"fromId":8,"weight":0.028984532,"enabled":true},{"fromId":9,"weight":0.402998,"enabled":true},{"fromId":21,"weight":-0.385299,"enabled":true},{"fromId":33,"weight":-0.25534916,"enabled":true},{"fromId":35,"weight":5.688518E-4,"enabled":true},{"fromId":41,"weight":-0.20653962,"enabled":true},{"fromId":44,"weight":0.27038646,"enabled":true},{"fromId":45,"weight":-0.16030513,"enabled":true},{"fromId":54,"weight":-0.87239826,"enabled":true},{"fromId":38,"weight":0.6673388,"enabled":true},{"fromId":85,"weight":-0.8848945,"enabled":true}]}]} \ No newline at end of file diff --git a/assets/saves/oct26-80/best-calculator-63.json b/assets/saves/oct26-80/best-calculator-63.json new file mode 100644 index 0000000..9a84641 --- /dev/null +++ b/assets/saves/oct26-80/best-calculator-63.json @@ -0,0 +1 @@ +{"isAddBias":true,"inputs":[{"id":0,"x":0.1,"incoming":[]},{"id":1,"x":0.1,"incoming":[]},{"id":2,"x":0.1,"incoming":[]},{"id":3,"x":0.1,"incoming":[]},{"id":4,"x":0.1,"incoming":[]},{"id":5,"x":0.1,"incoming":[]},{"id":6,"x":0.1,"incoming":[]},{"id":7,"x":0.1,"incoming":[]},{"id":8,"x":0.1,"incoming":[]}],"hidden":[{"id":50,"x":0.29999998,"incoming":[{"fromId":5,"weight":-0.73318624,"enabled":true},{"fromId":0,"weight":-0.09799453,"enabled":true}]},{"id":86,"x":0.29999998,"incoming":[{"fromId":5,"weight":0.9698178,"enabled":true},{"fromId":0,"weight":0.82551205,"enabled":true}]},{"id":14,"x":0.49999997,"incoming":[{"fromId":4,"weight":-0.25745088,"enabled":true},{"fromId":0,"weight":0.2020576,"enabled":true}]},{"id":17,"x":0.49999997,"incoming":[{"fromId":7,"weight":0.8697504,"enabled":true},{"fromId":0,"weight":-0.309209,"enabled":true},{"fromId":2,"weight":-1.008094,"enabled":true},{"fromId":1,"weight":0.45947468,"enabled":true}]},{"id":18,"x":0.49999997,"incoming":[{"fromId":6,"weight":0.04953271,"enabled":true},{"fromId":0,"weight":-0.55681276,"enabled":true}]},{"id":21,"x":0.49999997,"incoming":[{"fromId":4,"weight":0.04523669,"enabled":true},{"fromId":0,"weight":0.21809094,"enabled":true},{"fromId":3,"weight":0.08068417,"enabled":true},{"fromId":50,"weight":0.6940344,"enabled":true}]},{"id":24,"x":0.49999997,"incoming":[{"fromId":0,"weight":0.276672,"enabled":true},{"fromId":3,"weight":-0.35569942,"enabled":true},{"fromId":4,"weight":-0.56869346,"enabled":true}]},{"id":25,"x":0.49999997,"incoming":[{"fromId":8,"weight":-0.56451136,"enabled":true},{"fromId":0,"weight":-0.02433632,"enabled":true},{"fromId":3,"weight":0.11776473,"enabled":true}]},{"id":26,"x":0.49999997,"incoming":[{"fromId":2,"weight":0.1757319,"enabled":true},{"fromId":0,"weight":0.4079069,"enabled":true}]},{"id":27,"x":0.49999997,"incoming":[{"fromId":0,"weight":0.55360836,"enabled":true},{"fromId":6,"weight":-0.015474588,"enabled":true}]},{"id":29,"x":0.49999997,"incoming":[{"fromId":3,"weight":0.83256924,"enabled":true},{"fromId":0,"weight":-0.12941436,"enabled":true},{"fromId":50,"weight":0.32883006,"enabled":true}]},{"id":32,"x":0.49999997,"incoming":[{"fromId":1,"weight":-0.8327364,"enabled":true},{"fromId":0,"weight":0.37621683,"enabled":true},{"fromId":2,"weight":-0.4280192,"enabled":true}]},{"id":33,"x":0.49999997,"incoming":[{"fromId":2,"weight":0.20178892,"enabled":true},{"fromId":0,"weight":0.03770241,"enabled":true},{"fromId":50,"weight":0.91409963,"enabled":true}]},{"id":35,"x":0.49999997,"incoming":[{"fromId":5,"weight":-0.5423191,"enabled":true},{"fromId":0,"weight":0.39796975,"enabled":true},{"fromId":86,"weight":-0.3052985,"enabled":true}]},{"id":38,"x":0.49999997,"incoming":[{"fromId":4,"weight":0.28430638,"enabled":true},{"fromId":0,"weight":0.43830466,"enabled":true},{"fromId":5,"weight":0.4438919,"enabled":true}]},{"id":40,"x":0.49999997,"incoming":[{"fromId":1,"weight":-0.26690724,"enabled":true},{"fromId":0,"weight":-0.4035197,"enabled":true}]},{"id":43,"x":0.49999997,"incoming":[{"fromId":7,"weight":0.0696076,"enabled":true},{"fromId":0,"weight":0.67335284,"enabled":true},{"fromId":3,"weight":0.15251754,"enabled":true}]},{"id":46,"x":0.49999997,"incoming":[{"fromId":5,"weight":0.3982815,"enabled":true},{"fromId":0,"weight":0.051497206,"enabled":true},{"fromId":50,"weight":0.7691549,"enabled":true},{"fromId":6,"weight":0.13945112,"enabled":true},{"fromId":4,"weight":0.4318214,"enabled":true}]},{"id":56,"x":0.7,"incoming":[{"fromId":29,"weight":0.2432541,"enabled":true},{"fromId":0,"weight":0.32452047,"enabled":true}]},{"id":87,"x":0.7,"incoming":[{"fromId":27,"weight":1.0535529,"enabled":true},{"fromId":0,"weight":0.23740153,"enabled":true},{"fromId":8,"weight":0.27270532,"enabled":true},{"fromId":33,"weight":0.07614358,"enabled":true},{"fromId":17,"weight":0.5065347,"enabled":true}]}],"outputs":[{"id":9,"x":0.9,"incoming":[{"fromId":0,"weight":0.9560259,"enabled":true},{"fromId":1,"weight":2.7942674,"enabled":true},{"fromId":2,"weight":-0.012877494,"enabled":true},{"fromId":3,"weight":0.41387114,"enabled":true},{"fromId":4,"weight":-0.03746769,"enabled":true},{"fromId":5,"weight":-0.08882332,"enabled":true},{"fromId":6,"weight":-1.0062743,"enabled":true},{"fromId":7,"weight":-0.29130673,"enabled":true},{"fromId":8,"weight":-0.15766335,"enabled":true},{"fromId":18,"weight":-0.5196569,"enabled":true},{"fromId":33,"weight":-0.18276952,"enabled":true},{"fromId":38,"weight":-0.20709828,"enabled":true}]},{"id":10,"x":0.9,"incoming":[{"fromId":0,"weight":0.42232144,"enabled":true},{"fromId":1,"weight":-0.59807914,"enabled":true},{"fromId":2,"weight":-0.5933764,"enabled":true},{"fromId":3,"weight":0.41245896,"enabled":true},{"fromId":4,"weight":0.23198506,"enabled":true},{"fromId":5,"weight":-0.49270597,"enabled":true},{"fromId":6,"weight":0.10625306,"enabled":true},{"fromId":7,"weight":-0.27555874,"enabled":true},{"fromId":8,"weight":-0.6163728,"enabled":true},{"fromId":14,"weight":0.78394365,"enabled":true},{"fromId":17,"weight":-0.15328407,"enabled":true},{"fromId":24,"weight":0.22443217,"enabled":true},{"fromId":32,"weight":1.2688023,"enabled":true},{"fromId":25,"weight":0.20290792,"enabled":true},{"fromId":46,"weight":-0.5708654,"enabled":true}]},{"id":11,"x":0.9,"incoming":[{"fromId":0,"weight":0.2933759,"enabled":true},{"fromId":1,"weight":-0.264471,"enabled":true},{"fromId":2,"weight":1.3373718,"enabled":true},{"fromId":3,"weight":-0.3901256,"enabled":true},{"fromId":4,"weight":0.53775966,"enabled":true},{"fromId":5,"weight":1.0048475,"enabled":true},{"fromId":6,"weight":-0.68406343,"enabled":true},{"fromId":7,"weight":0.5846671,"enabled":true},{"fromId":8,"weight":0.16000403,"enabled":true},{"fromId":21,"weight":-0.02291429,"enabled":true},{"fromId":26,"weight":0.122866854,"enabled":true},{"fromId":29,"weight":0.31331462,"enabled":true},{"fromId":40,"weight":0.045506343,"enabled":true},{"fromId":27,"weight":-0.25766852,"enabled":true},{"fromId":46,"weight":0.95972085,"enabled":true}]},{"id":12,"x":0.9,"incoming":[{"fromId":0,"weight":-0.47128052,"enabled":true},{"fromId":1,"weight":-0.2914877,"enabled":true},{"fromId":2,"weight":0.588234,"enabled":true},{"fromId":3,"weight":-0.84448004,"enabled":true},{"fromId":4,"weight":0.23557794,"enabled":true},{"fromId":5,"weight":-0.28685936,"enabled":true},{"fromId":6,"weight":0.0016622841,"enabled":true},{"fromId":7,"weight":-0.70946217,"enabled":true},{"fromId":8,"weight":-0.11265129,"enabled":true},{"fromId":25,"weight":0.75856704,"enabled":true},{"fromId":27,"weight":-0.3125484,"enabled":true},{"fromId":35,"weight":0.01801601,"enabled":true},{"fromId":43,"weight":0.2998522,"enabled":true},{"fromId":29,"weight":0.5195759,"enabled":true},{"fromId":56,"weight":0.20899533,"enabled":true},{"fromId":87,"weight":0.12560014,"enabled":true}]}]} \ No newline at end of file diff --git a/assets/saves/oct26-83/best-calculator-87.json b/assets/saves/oct26-83/best-calculator-87.json new file mode 100644 index 0000000..df495dc --- /dev/null +++ b/assets/saves/oct26-83/best-calculator-87.json @@ -0,0 +1 @@ +{"isAddBias":true,"inputs":[{"id":0,"x":0.1,"incoming":[]},{"id":1,"x":0.1,"incoming":[]},{"id":2,"x":0.1,"incoming":[]},{"id":3,"x":0.1,"incoming":[]},{"id":4,"x":0.1,"incoming":[]},{"id":5,"x":0.1,"incoming":[]},{"id":6,"x":0.1,"incoming":[]},{"id":7,"x":0.1,"incoming":[]},{"id":8,"x":0.1,"incoming":[]}],"hidden":[{"id":103,"x":0.29999998,"incoming":[{"fromId":4,"weight":-0.75605005,"enabled":true},{"fromId":0,"weight":-0.395471,"enabled":true}]},{"id":147,"x":0.29999998,"incoming":[{"fromId":4,"weight":-1.1067865,"enabled":true},{"fromId":0,"weight":0.5648355,"enabled":true},{"fromId":8,"weight":0.20269266,"enabled":true},{"fromId":5,"weight":-2.3135078,"enabled":true}]},{"id":149,"x":0.29999998,"incoming":[{"fromId":2,"weight":0.45141992,"enabled":true},{"fromId":0,"weight":0.39749306,"enabled":true},{"fromId":1,"weight":0.6346309,"enabled":true},{"fromId":4,"weight":-0.6604395,"enabled":true}]},{"id":160,"x":0.29999998,"incoming":[{"fromId":4,"weight":-1.7306871,"enabled":true},{"fromId":0,"weight":0.5938391,"enabled":true}]},{"id":16,"x":0.49999997,"incoming":[{"fromId":6,"weight":-0.14614072,"enabled":true},{"fromId":0,"weight":0.2316266,"enabled":true}]},{"id":18,"x":0.49999997,"incoming":[{"fromId":6,"weight":0.46015835,"enabled":true},{"fromId":0,"weight":-0.38740024,"enabled":true},{"fromId":2,"weight":-0.14090341,"enabled":false},{"fromId":5,"weight":-0.59524506,"enabled":true},{"fromId":3,"weight":-0.10561895,"enabled":true},{"fromId":4,"weight":0.3116178,"enabled":true},{"fromId":7,"weight":0.060221955,"enabled":true},{"fromId":1,"weight":0.9951101,"enabled":true},{"fromId":8,"weight":-0.28201458,"enabled":true},{"fromId":149,"weight":1.4118636,"enabled":true},{"fromId":147,"weight":-0.20409444,"enabled":true}]},{"id":23,"x":0.49999997,"incoming":[{"fromId":8,"weight":-0.013752326,"enabled":true},{"fromId":0,"weight":-0.47912228,"enabled":true},{"fromId":6,"weight":-0.4030968,"enabled":true},{"fromId":4,"weight":-1.0460085,"enabled":true},{"fromId":3,"weight":0.5285127,"enabled":true},{"fromId":147,"weight":0.32999167,"enabled":true}]},{"id":25,"x":0.49999997,"incoming":[{"fromId":8,"weight":1.4021529,"enabled":true},{"fromId":0,"weight":-1.6461971,"enabled":true}]},{"id":26,"x":0.49999997,"incoming":[{"fromId":2,"weight":1.013501,"enabled":true},{"fromId":0,"weight":-1.2715883,"enabled":true}]},{"id":28,"x":0.49999997,"incoming":[{"fromId":1,"weight":1.5577924,"enabled":true},{"fromId":0,"weight":-0.2747485,"enabled":true}]},{"id":30,"x":0.49999997,"incoming":[{"fromId":2,"weight":-0.12490612,"enabled":true},{"fromId":0,"weight":-2.2526577,"enabled":true}]},{"id":31,"x":0.49999997,"incoming":[{"fromId":7,"weight":-0.7602389,"enabled":true},{"fromId":0,"weight":-0.8037754,"enabled":true}]},{"id":34,"x":0.49999997,"incoming":[{"fromId":0,"weight":1.1757369,"enabled":true},{"fromId":8,"weight":-0.36619347,"enabled":true},{"fromId":5,"weight":-0.8266476,"enabled":true},{"fromId":147,"weight":-0.38919455,"enabled":true}]},{"id":35,"x":0.49999997,"incoming":[{"fromId":5,"weight":0.40289527,"enabled":true},{"fromId":0,"weight":-0.6015227,"enabled":true},{"fromId":4,"weight":0.75680274,"enabled":true},{"fromId":8,"weight":-3.0960274,"enabled":true},{"fromId":7,"weight":1.3233759,"enabled":true},{"fromId":103,"weight":-0.5590126,"enabled":true},{"fromId":6,"weight":-1.6878694,"enabled":true}]},{"id":38,"x":0.49999997,"incoming":[{"fromId":4,"weight":0.022669524,"enabled":true},{"fromId":0,"weight":0.48031467,"enabled":true},{"fromId":7,"weight":-1.3196592,"enabled":true},{"fromId":1,"weight":0.23968288,"enabled":true}]},{"id":40,"x":0.49999997,"incoming":[{"fromId":1,"weight":-0.25357947,"enabled":true},{"fromId":0,"weight":-0.70107985,"enabled":true},{"fromId":7,"weight":0.67628485,"enabled":true},{"fromId":4,"weight":-1.2484431,"enabled":true},{"fromId":8,"weight":0.5711151,"enabled":true},{"fromId":147,"weight":-0.08900313,"enabled":true},{"fromId":160,"weight":-1.1897418,"enabled":true}]},{"id":46,"x":0.49999997,"incoming":[{"fromId":5,"weight":-1.5862174,"enabled":true},{"fromId":0,"weight":-0.608724,"enabled":true},{"fromId":6,"weight":-0.7941735,"enabled":true},{"fromId":2,"weight":-0.56962997,"enabled":true},{"fromId":8,"weight":-0.99704194,"enabled":true}]},{"id":52,"x":0.49999997,"incoming":[{"fromId":3,"weight":0.85444355,"enabled":true},{"fromId":0,"weight":0.15006885,"enabled":true},{"fromId":8,"weight":-1.6256567,"enabled":true}]},{"id":114,"x":0.7,"incoming":[{"fromId":18,"weight":0.8579911,"enabled":true},{"fromId":0,"weight":0.12111913,"enabled":true},{"fromId":8,"weight":0.5964794,"enabled":true},{"fromId":28,"weight":1.7859622,"enabled":true}]}],"outputs":[{"id":9,"x":0.9,"incoming":[{"fromId":0,"weight":0.031564653,"enabled":true},{"fromId":1,"weight":2.9249825,"enabled":true},{"fromId":2,"weight":0.064846426,"enabled":true},{"fromId":3,"weight":-0.27084315,"enabled":true},{"fromId":4,"weight":-0.68686,"enabled":true},{"fromId":5,"weight":-0.46270803,"enabled":true},{"fromId":6,"weight":-0.90852225,"enabled":true},{"fromId":7,"weight":-1.3909911,"enabled":true},{"fromId":8,"weight":-0.81490093,"enabled":true},{"fromId":18,"weight":-0.04064625,"enabled":true},{"fromId":34,"weight":-1.0095365,"enabled":true},{"fromId":38,"weight":-0.32835463,"enabled":true},{"fromId":23,"weight":0.5576244,"enabled":true},{"fromId":147,"weight":-0.57810694,"enabled":true},{"fromId":149,"weight":0.24368237,"enabled":true}]},{"id":10,"x":0.9,"incoming":[{"fromId":0,"weight":-0.6603104,"enabled":true},{"fromId":1,"weight":-0.74902034,"enabled":true},{"fromId":2,"weight":-2.0361514,"enabled":true},{"fromId":3,"weight":-0.8444284,"enabled":false},{"fromId":4,"weight":0.6633264,"enabled":true},{"fromId":5,"weight":0.119130395,"enabled":true},{"fromId":6,"weight":-1.1926677,"enabled":true},{"fromId":7,"weight":0.1384106,"enabled":true},{"fromId":8,"weight":0.18462567,"enabled":true},{"fromId":16,"weight":1.0407348,"enabled":true},{"fromId":52,"weight":-0.53094435,"enabled":true},{"fromId":18,"weight":-0.16371676,"enabled":true},{"fromId":46,"weight":0.4681051,"enabled":true},{"fromId":35,"weight":-0.41891068,"enabled":true},{"fromId":114,"weight":-0.37253943,"enabled":true}]},{"id":11,"x":0.9,"incoming":[{"fromId":0,"weight":-1.191597,"enabled":true},{"fromId":1,"weight":-0.7668504,"enabled":true},{"fromId":2,"weight":2.0141513,"enabled":true},{"fromId":3,"weight":-0.61426795,"enabled":true},{"fromId":4,"weight":-0.05082631,"enabled":true},{"fromId":5,"weight":0.39534637,"enabled":true},{"fromId":6,"weight":0.54902935,"enabled":true},{"fromId":7,"weight":-0.51054525,"enabled":false},{"fromId":8,"weight":-1.3694528,"enabled":true},{"fromId":23,"weight":-0.23818377,"enabled":true},{"fromId":26,"weight":0.84556675,"enabled":true},{"fromId":31,"weight":0.7206137,"enabled":true},{"fromId":40,"weight":-0.23447919,"enabled":true},{"fromId":46,"weight":-0.33683693,"enabled":true},{"fromId":16,"weight":0.25415665,"enabled":true}]},{"id":12,"x":0.9,"incoming":[{"fromId":0,"weight":-2.46104,"enabled":true},{"fromId":1,"weight":0.09756243,"enabled":true},{"fromId":2,"weight":-0.9753169,"enabled":true},{"fromId":3,"weight":-0.5540126,"enabled":true},{"fromId":4,"weight":0.97165066,"enabled":true},{"fromId":5,"weight":-0.8292797,"enabled":true},{"fromId":6,"weight":0.19022086,"enabled":true},{"fromId":7,"weight":-0.38471147,"enabled":true},{"fromId":8,"weight":-0.13585834,"enabled":true},{"fromId":25,"weight":-0.39404047,"enabled":true},{"fromId":28,"weight":-2.1526086,"enabled":true},{"fromId":30,"weight":-1.2365108,"enabled":true},{"fromId":35,"weight":0.6078776,"enabled":true},{"fromId":46,"weight":-0.68654394,"enabled":true},{"fromId":40,"weight":-0.60150987,"enabled":true},{"fromId":18,"weight":1.7129339,"enabled":true}]}]} \ No newline at end of file diff --git a/core/src/main/java/com/buaisociety/pacman/Main.java b/core/src/main/java/com/buaisociety/pacman/Main.java index 29113e7..d705307 100644 --- a/core/src/main/java/com/buaisociety/pacman/Main.java +++ b/core/src/main/java/com/buaisociety/pacman/Main.java @@ -51,13 +51,15 @@ public class Main extends ApplicationAdapter { private final @NotNull EventSystem events = new EventSystem(); private final @NotNull Vector2i visibleGames = new Vector2i(4, 2); private final @NotNull List managers = new ArrayList<>(); - private final int totalGames = 250; + private final int totalGames = NeatConfig.populationSize; private GameLoop secondLoop; // 1 update per second private boolean paused; private boolean showNetworks; private int frames; private int fps; + private boolean USE_TOURNAMENT_SETTINGS = NeatConfig.USE_TOURNAMENT_SETTINGS; + // deep learning private Neat neat; private NeatPrinter neatPrinter; @@ -75,7 +77,7 @@ public void create() { secondLoop = new GameLoop(1); int processors = Runtime.getRuntime().availableProcessors(); - threadPool = Executors.newFixedThreadPool(processors); + threadPool = Executors.newFixedThreadPool(Math.max(1, processors - 1)); System.out.println("Using " + processors + " threads"); // When all games have ended, reset @@ -103,9 +105,9 @@ public void create() { public @NotNull Neat createNeat() { // Change this to true/false as needed, if you want to load from file - if (false) { + if (NeatConfig.loadFromFile) { // TODO: Change this to the exact file you want to load - File exactFile = new File("saves" + File.separator + "oct26-4" + File.separator + "generation-51.json"); + File exactFile = new File("saves" + File.separator + NeatConfig.folder + File.separator + NeatConfig.file); // load exactFile contents to string String json; try { @@ -120,12 +122,12 @@ public void create() { return impl; } else { Parameters neatParameters = new Parameters(); - neatParameters.setMutateWeightChance(0.75f); - neatParameters.setWeightCoefficient(1.0f); // speciate on weight more often - neatParameters.setTargetClientsPerSpecies(12); // targeting ~12 clients per species - neatParameters.setStagnationLimit(10); // lower stagnation limit - neatParameters.setUseBiasNode(true); // use bias node - return new NeatImpl(4, 4, totalGames, neatParameters); + neatParameters.setMutateWeightChance(NeatConfig.mutateWeightChance); + neatParameters.setWeightCoefficient(NeatConfig.weightCoefficient); // speciate on weight more often + neatParameters.setTargetClientsPerSpecies(NeatConfig.targetClientsPerSpecies); // targeting ~12 clients per species + neatParameters.setStagnationLimit(NeatConfig.stagnationLimit); // lower stagnation limit + neatParameters.setUseBiasNode(NeatConfig.biasEnabled); // use bias node + return new NeatImpl(NeatConfig.neatInputNodes, NeatConfig.neatOutputNodes, totalGames, neatParameters); } } @@ -180,6 +182,11 @@ public void reset() { for (int i = 0; i < totalGames; i++) { GameManager.Config config = new GameManager.Config(); config.id = i; + if(USE_TOURNAMENT_SETTINGS) { + config.handicap = 8; + config.levelsPreset = "tournament_levels.json"; + } + GameManager gameManager = new GameManager(events, config); gameManager.nextLevel(); gameManager.setExtraLives(0); diff --git a/core/src/main/java/com/buaisociety/pacman/NeatConfig.java b/core/src/main/java/com/buaisociety/pacman/NeatConfig.java new file mode 100644 index 0000000..6dbbf52 --- /dev/null +++ b/core/src/main/java/com/buaisociety/pacman/NeatConfig.java @@ -0,0 +1,23 @@ +package com.buaisociety.pacman; + +public class NeatConfig { + // NEAT parameters + public static int populationSize = 300; + public static float mutateWeightChance = 0.75f; + public static float weightCoefficient = 0.3f; + public static int targetClientsPerSpecies = 12; + public static int stagnationLimit = 10; + + public static boolean biasEnabled = true; + + public static int neatInputNodes = 8; +// public static int neatInputNodes = 22; + public static int neatOutputNodes = 6; + + + public static boolean loadFromFile = false; + public static String folder = "oct26-80"; + public static String file = "generation-70.json"; + + public static boolean USE_TOURNAMENT_SETTINGS = false; +} diff --git a/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java b/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java index d9d5684..cff26fb 100644 --- a/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java +++ b/core/src/main/java/com/buaisociety/pacman/SpecialTrainingConditions.java @@ -21,7 +21,6 @@ * maze, or even change the behavior of the entities during training. */ public final class SpecialTrainingConditions { - // Prevent instantiation private SpecialTrainingConditions() { } @@ -30,7 +29,7 @@ private SpecialTrainingConditions() { return event -> { // Prevent ghosts from spawning during training if (event.getEntityType() == EntityType.GHOST) { - event.setCancelled(true); +// event.setCancelled(true); } }; } diff --git a/core/src/main/java/com/buaisociety/pacman/Tournament.java b/core/src/main/java/com/buaisociety/pacman/Tournament.java index 697fa74..9abbe00 100644 --- a/core/src/main/java/com/buaisociety/pacman/Tournament.java +++ b/core/src/main/java/com/buaisociety/pacman/Tournament.java @@ -47,7 +47,7 @@ public class Tournament extends ApplicationAdapter { */ public Behavior setupBehavior() { // TODO: Choose your best client here - File file = new File("saves" + File.separator + "oct20-4" + File.separator + "best-calculator-127.json"); + File file = new File("saves" + File.separator + "oct26-80" + File.separator + "best-calculator-63.json"); if (!file.exists()) { System.err.println("Could not find the file: " + file.getAbsolutePath()); return null; @@ -98,8 +98,8 @@ public void onEvent(@NotNull EntityPreSpawnEvent event) { gameManager = new GameManager(events, config); gameManager.nextLevel(); - gameLoop = new GameLoop(60); - secondLoop = new GameLoop(1); + gameLoop = new GameLoop(6000); + secondLoop = new GameLoop(1000); // Maximize window Graphics.DisplayMode displayMode = Gdx.graphics.getDisplayMode(); diff --git a/core/src/main/java/com/buaisociety/pacman/entity/behavior/NeatPacmanBehavior.java b/core/src/main/java/com/buaisociety/pacman/entity/behavior/NeatPacmanBehavior.java index d3fd62f..4173ecf 100644 --- a/core/src/main/java/com/buaisociety/pacman/entity/behavior/NeatPacmanBehavior.java +++ b/core/src/main/java/com/buaisociety/pacman/entity/behavior/NeatPacmanBehavior.java @@ -2,27 +2,63 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.buaisociety.pacman.NeatConfig; +import com.buaisociety.pacman.entity.GhostEntity; import com.buaisociety.pacman.maze.Maze; +import com.buaisociety.pacman.maze.Tile; +import com.buaisociety.pacman.maze.TileState; import com.buaisociety.pacman.sprite.DebugDrawing; import com.cjcrafter.neat.Client; import com.buaisociety.pacman.entity.Direction; import com.buaisociety.pacman.entity.Entity; import com.buaisociety.pacman.entity.PacmanEntity; +import com.cjcrafter.neat.Neat; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.joml.Vector2d; +import org.joml.Vector2i; +import org.joml.Vector2ic; + +import java.util.*; public class NeatPacmanBehavior implements Behavior { + public static float epsilon = 1.0f; private final @NotNull Client client; private @Nullable PacmanEntity pacman; + private Direction forward = Direction.UP; + private Direction left = Direction.LEFT; + private Direction right = Direction.RIGHT; + private Direction behind = Direction.DOWN; + // Score modifiers help us maintain "multiple pools" of points. // This is great for training, because we can take away points from // specific pools of points instead of subtracting from all. - private int scoreModifier = 0; + private float scoreModifier = 0; + + public final List moveHistory = new ArrayList<>(); + + private int lastScore = 0; + private int updatesSinceLastScore = 0; + + private int[][] distances; + + public int numGhosts = 0; + + private float[] lastOutputs = new float[NeatConfig.neatOutputNodes]; + + private final Random random; + List inputs = new ArrayList<>(); + + List highlightedTiles = new ArrayList<>(); + List highlightedPellets = new ArrayList<>(); + + public static boolean useRelative = true; public NeatPacmanBehavior(@NotNull Client client) { this.client = client; + this.random = new Random(); } /** @@ -34,62 +70,624 @@ public NeatPacmanBehavior(@NotNull Client client) { @NotNull @Override public Direction getDirection(@NotNull Entity entity) { + initializePacman(entity); + updateDirections(); + + highlightedTiles.clear(); + highlightedPellets.clear(); + distances = computeDistances(); + + handleSpecialTrainingConditions(); + + + float[] inputs = buildInputs(); + + float[] outputs = client.getCalculator().calculate(inputs).join(); + lastOutputs = outputs; + + Direction newDirection = selectDirectionFromOutputs(new float[]{outputs[0], outputs[1], outputs[2], outputs[3]}); + + updateScore(newDirection); + + moveHistory.add(newDirection); + return newDirection; + } + + @Override + public void render(@NotNull SpriteBatch batch) { + if (pacman != null) { + Tile nearestPellet = getNearestPellet(); + DebugDrawing.outlineTile(batch, nearestPellet, Color.GREEN); + // Additional debug rendering can be added here + } + + for(Tile tile : highlightedTiles){ + DebugDrawing.outlineTile(batch, tile, Color.RED); + } + + for (Tile tile : highlightedPellets) { + DebugDrawing.outlineTile(batch, tile, Color.BLUE); + } + } + + /** + * Initializes the pacman entity if it's not already set. + */ + private void initializePacman(@NotNull Entity entity) { if (pacman == null) { pacman = (PacmanEntity) entity; } + } - // SPECIAL TRAINING CONDITIONS - // TODO: Make changes here to help with your training... - // END OF SPECIAL TRAINING CONDITIONS + /** + * Updates the directional fields based on Pacman's current direction. + */ + private void updateDirections() { + assert pacman != null; + forward = pacman.getDirection(); + left = pacman.getDirection().left(); + right = pacman.getDirection().right(); + behind = pacman.getDirection().behind(); + } - // We are going to use these directions a lot for different inputs. Get them all once for clarity and brevity - Direction forward = pacman.getDirection(); - Direction left = pacman.getDirection().left(); - Direction right = pacman.getDirection().right(); - Direction behind = pacman.getDirection().behind(); + /** + * Handles special training conditions such as score updates and kill conditions. + */ + private void handleSpecialTrainingConditions() { + int currentScore = pacman.getMaze().getLevelManager().getScore(); + if (currentScore > lastScore) { + lastScore = currentScore; + updatesSinceLastScore = 0; + if (currentScore > 100_000) { + pacman.kill(); + return; + } + } else { + updatesSinceLastScore++; + } - // Input nodes 1, 2, 3, and 4 show if the pacman can move in the forward, left, right, and behind directions - boolean canMoveForward = pacman.canMove(forward); - boolean canMoveLeft = pacman.canMove(left); - boolean canMoveRight = pacman.canMove(right); - boolean canMoveBehind = pacman.canMove(behind); + int maxUpdates = 60 * 10; // 10 seconds - float[] outputs = client.getCalculator().calculate(new float[]{ - canMoveForward ? 1f : 0f, - canMoveLeft ? 1f : 0f, - canMoveRight ? 1f : 0f, - canMoveBehind ? 1f : 0f, - }).join(); + if(numGhosts > 0){ + maxUpdates = 60 * 60; // 1 minute + } - int index = 0; - float max = outputs[0]; - for (int i = 1; i < outputs.length; i++) { - if (outputs[i] > max) { - max = outputs[i]; - index = i; + if (updatesSinceLastScore > maxUpdates) { + pacman.kill(); + } + } + + /** + * Checks if the given position is within the maze boundaries. + * + * @param position the position to check + * @param dimensions the dimensions of the maze + * @return true if within bounds, false otherwise + */ + private boolean isWithinBounds(Vector2i position, Vector2ic dimensions) { + return position.x() >= 0 && position.x() < dimensions.x() && + position.y() >= 0 && position.y() < dimensions.y(); + } + + /** + * Gathers information about the ghosts in the maze. + * + * @return a GhostInfo object containing distances and directions of ghosts + */ + private GhostInfo gatherGhostInformation() { + List ghosts = getGhostEntities(); + numGhosts = ghosts.size(); + float[] ghostDistances = new float[4]; + Vector2d[] ghostDirections = new Vector2d[4]; + Arrays.fill(ghostDistances, 1); + + int ghostIndex = 0; + for (GhostEntity ghost : ghosts) { + if (ghostIndex >= 4) break; // Limit to 4 ghosts + float distance = distances[ghost.getTilePosition().x()][ghost.getTilePosition().y()]; + if (distance == -1) continue; + Tile ghostTile = pacman.getMaze().getTile(ghost.getTilePosition()); + Direction direction = getFirstStepToTile(ghostTile); + if (direction == null) continue; + + ghostDistances[ghostIndex] = distance / (pacman.getMaze().getDimensions().x() + pacman.getMaze().getDimensions().y()); + ghostDirections[ghostIndex] = rotateRelative(new Vector2d(direction.getDx(), direction.getDy())); + ghostIndex++; + } + + return new GhostInfo(ghostDistances, ghostDirections); + } + + /** + * Retrieves all ghost entities from the maze. + * + * @return a list of GhostEntity objects + */ + private List getGhostEntities() { + List entities = pacman.getMaze().getEntities(); + List ghosts = new ArrayList<>(); + for (Entity entity : entities) { + if (entity instanceof GhostEntity ghost) { + ghosts.add(ghost); + } + } + return ghosts; + } + + /** + * Builds the input array for the NEAT calculator. + * + * @return an array of input values + */ + private float[] buildInputs() { + Vector2ic dimensions = pacman.getMaze().getDimensions(); +// addMazeInfo(); + addRayCasts(); + // Disabled for debugging + // addHistory(); +// addSuggestedPellet(); + // addGhostInfo(); + // addSuggestedPowerPellet(); + + /** Disabled for inefficiency + addVision(ghostInfo); too many inputs + */ + // Convert List to float[] + float[] inputArray = new float[inputs.size()]; + for (int i = 0; i < inputs.size(); i++) { + inputArray[i] = inputs.get(i); + } + inputs.clear(); + return inputArray; + } + + private void addMazeInfo() { + Vector2ic dimensions = pacman.getMaze().getDimensions(); + float pelletPercentage = pacman.getMaze().getPelletsRemaining() / (float) (dimensions.x() * dimensions.y()); + inputs.add(pelletPercentage); + + } + + private void addGhostInfo(){ + GhostInfo ghostInfo = gatherGhostInformation(); + float nearestDistance = 1000.0f; + Vector2d nearestDirection = null; + + for (int i = 0; i < ghostInfo.ghostDistances.length; i++) { + if (ghostInfo.ghostDirections[i] != null && ghostInfo.ghostDistances[i] < nearestDistance) { + nearestDistance = ghostInfo.ghostDistances[i]; + nearestDirection = ghostInfo.ghostDirections[i]; } } - Direction newDirection = switch (index) { - case 0 -> pacman.getDirection(); - case 1 -> pacman.getDirection().left(); - case 2 -> pacman.getDirection().right(); - case 3 -> pacman.getDirection().behind(); - default -> throw new IllegalStateException("Unexpected value: " + index); + inputs.add(1.0f / (nearestDistance + epsilon)); + inputs.add(nearestDirection != null ? (float) nearestDirection.x() : 0f); + inputs.add(nearestDirection != null ? (float) nearestDirection.y() : 0f); + // timer + float timeLeft = pacman.getMaze().getFrightenedTimer() <= 3 ? 0 : (float) (pacman.getMaze().getFrightenedTimer() / 200f + 0.5f); + inputs.add(timeLeft); + } + + private void addRayCasts() { + Vector2ic dimensions = pacman.getMaze().getDimensions(); + float maxDistance = dimensions.x() + dimensions.y(); // Maximum possible distance + + Vector2i forwardVec = pacman.getDirection().asVector(); + Vector2i rightVec = pacman.getDirection().right().asVector(); + if(!useRelative){ + forwardVec = new Vector2i(0, 1); + rightVec = new Vector2i(1, 0); + } + + // Define the 8 directions for raycasting + Vector2i[] directions = new Vector2i[]{ + new Vector2i(forwardVec.x, forwardVec.y), + new Vector2i(forwardVec.x + rightVec.x, forwardVec.y + rightVec.y), + new Vector2i(rightVec.x, rightVec.y), + new Vector2i(-forwardVec.x + rightVec.x, -forwardVec.y + rightVec.y), +// new Vector2i(-forwardVec.x, -forwardVec.y), +// new Vector2i(-forwardVec.x - rightVec.x, -forwardVec.y - rightVec.y), +// new Vector2i(-rightVec.x, -rightVec.y), +// new Vector2i(forwardVec.x - rightVec.x, forwardVec.y - rightVec.y) }; - client.setScore(pacman.getMaze().getLevelManager().getScore() + scoreModifier); - return newDirection; + float[] rayCastWalls = new float[directions.length]; + float[] rayCastPellets = new float[directions.length]; + + for (int i = 0; i < directions.length; i++) { + Vector2i direction = directions[i]; + Vector2i position = new Vector2i(pacman.getTilePosition()); + + // Ray cast for walls + int wallDistance = 0; + while (isWithinBounds(position, dimensions) && + pacman.getMaze().getTile(position).getState() != TileState.WALL) { + position.add(direction); + wallDistance++; + } + + if (isWithinBounds(position, dimensions)) { + highlightedTiles.add(pacman.getMaze().getTile(position)); + // Normalize distance to [0,1] range - closer walls give higher values + rayCastWalls[i] = 1.0f - (wallDistance / maxDistance); + } + + // Reset position for pellet raycast + position.set(pacman.getTilePosition()); + int pelletDistance = 0; + + // Ray cast for pellets + while (isWithinBounds(position, dimensions) && + pacman.getMaze().getTile(position).getState() != TileState.PELLET && + pacman.getMaze().getTile(position).getState() != TileState.POWER_PELLET) { + position.add(direction); + pelletDistance++; + if (!isWithinBounds(position, dimensions)) { + pelletDistance = (int)maxDistance; // If no pellet found, set to max distance + break; + } + } + + if (isWithinBounds(position, dimensions)) { + highlightedPellets.add(pacman.getMaze().getTile(position)); + // Normalize distance to [0,1] range - closer pellets give higher values + rayCastPellets[i] = 1.0f - (pelletDistance / maxDistance); + } + } + + // Add normalized distances to inputs + for (float wallDist : rayCastWalls) { + inputs.add(wallDist); + } + for (float pelletDist : rayCastPellets) { + inputs.add(pelletDist); + } } - @Override - public void render(@NotNull SpriteBatch batch) { - // TODO: You can render debug information here - /* - if (pacman != null) { - DebugDrawing.outlineTile(batch, pacman.getMaze().getTile(pacman.getTilePosition()), Color.RED); - DebugDrawing.drawDirection(batch, pacman.getTilePosition().x() * Maze.TILE_SIZE, pacman.getTilePosition().y() * Maze.TILE_SIZE, pacman.getDirection(), Color.RED); + private void addHistory(){ + for (int i = 4; i < lastOutputs.length; i++) { + inputs.add(lastOutputs[i]); + } + } + + private void addVision(GhostInfo ghostInfo){ + int visionRange = 4; // n by n grid around pacman + Vector2ic dimensions = pacman.getMaze().getDimensions(); + Vector2i pacmanPos = pacman.getTilePosition(); + + for(int y = -visionRange; y <= visionRange; y++){ + for(int x = -visionRange; x <= visionRange; x++){ + Vector2i pos = new Vector2i(pacmanPos.x() + x, pacmanPos.y() + y); + if(isWithinBounds(pos, dimensions)){ + Tile tile = pacman.getMaze().getTile(pos); + float tileState = 0; + if(tile.getState() == TileState.WALL){ + tileState = 1; + } else if(tile.getState() == TileState.PELLET){ + tileState = 2; + } else if(tile.getState() == TileState.POWER_PELLET){ + tileState = 3; + } + else{ + tileState = 0; + } + inputs.add(tileState); + }else{ + inputs.add(-1f); + } + } + } + } + + private void addSuggestedPowerPellet(){ + Tile nearestPowerPellet = getNearestPowerPellet(); + Vector2d relativePowerPelletPos = translateRelative(nearestPowerPellet.getPosition()); + + Direction suggestedPowerDirection = getSuggestedDirection(nearestPowerPellet); + Vector2d suggestedPowerDirRelative = rotateRelative(new Vector2d( + suggestedPowerDirection.getDx(), + suggestedPowerDirection.getDy() + )); + + inputs.add((float) suggestedPowerDirRelative.x()); + inputs.add((float) suggestedPowerDirRelative.y()); + + } + private void addSuggestedPellet() { + Tile nearestPellet = getNearestPellet(); + Vector2d relativePelletPos = translateRelative(nearestPellet.getPosition()); + + Direction suggestedPelletDirection = getSuggestedDirection(nearestPellet); + Vector2d suggestedPelletDirRelative = rotateRelative(new Vector2d( + suggestedPelletDirection.getDx(), + suggestedPelletDirection.getDy() + )); + + inputs.add((float) suggestedPelletDirRelative.x()); + inputs.add((float) suggestedPelletDirRelative.y()); + } + + + /** + * Selects the direction based on the outputs from the NEAT calculator. + * + * @param outputs the output array from the NEAT calculator + * @return the selected Direction + */ + private Direction selectDirectionFromOutputs(float[] outputs) { + int selectedIndex = 0; + float maxOutput = outputs[0]; + + for (int i = 1; i < outputs.length; i++) { + if (outputs[i] > maxOutput) { + maxOutput = outputs[i]; + selectedIndex = i; + } + } + if (useRelative) { + return switch (selectedIndex) { + case 0 -> forward; + case 1 -> left; + case 2 -> right; + case 3 -> behind; + default -> throw new IllegalStateException("Unexpected output index: " + selectedIndex); + }; + } else{ + return switch (selectedIndex) { + case 0 -> Direction.UP; + case 1 -> Direction.LEFT; + case 2 -> Direction.RIGHT; + case 3 -> Direction.DOWN; + default -> throw new IllegalStateException("Unexpected output index: " + selectedIndex); + }; + } + + } + + /** + * Updates the score based on the new direction. + * + * @param newDirection the direction chosen + */ + private void updateScore(Direction newDirection) { + Vector2i newPosition = new Vector2i( + pacman.getTilePosition().x() + newDirection.getDx(), + pacman.getTilePosition().y() + newDirection.getDy() + ); + + + Tile newTile = pacman.getMaze().getTile(newPosition.x(), newPosition.y()); +// if (newTile.getState() == TileState.PELLET) { +// scoreModifier += 10; // Reward for collecting a pellet +// } else if (newTile.getState() == TileState.POWER_PELLET) { +// scoreModifier += 50; // Higher reward for collecting a power pellet +// } + + // Optional: Penalize for invalid moves +// if (!pacman.canMove(newDirection)) { +// scoreModifier -= 5; +// } + + // Optional: Small penalty to encourage faster completion +// scoreModifier += 0.01f; + + + client.setScore( + pacman.getMaze().getLevelManager().getScore() + scoreModifier + ); // TODO: Improve score heuristics + } + + /** + * Retrieves the first step direction towards the target tile using the pre-computed distances. + * + * @param targetTile the destination tile + * @return the Direction to move in, or null if no path exists + */ + @Nullable + public Direction getFirstStepToTile(@NotNull Tile targetTile) { + Vector2ic targetPos = targetTile.getPosition(); + Vector2ic currentPos = pacman.getTilePosition(); + Vector2ic dimensions = pacman.getMaze().getDimensions(); + + // If we're already at the target, no need to move + if (currentPos.equals(targetPos)) { + return null; + } + + // If target is unreachable (distance == -1), return null + if (distances[targetPos.x()][targetPos.y()] == -1) { + return null; + } + + // Start from the target and work backwards to find the first step + Vector2i backtrackPos = new Vector2i(targetPos); + int currentDistance = distances[targetPos.x()][targetPos.y()]; + + while (currentDistance > 1) { + boolean stepFound = false; + + for (Direction dir : Direction.values()) { + int nextX = backtrackPos.x() + dir.getDx(); + int nextY = backtrackPos.y() + dir.getDy(); + + if (!isWithinBounds(new Vector2i(nextX, nextY), dimensions)) { + continue; + } + + if (distances[nextX][nextY] == currentDistance - 1) { + backtrackPos.set(nextX, nextY); + currentDistance--; + stepFound = true; + break; + } + } + + if (!stepFound) { + return null; // Path is broken + } + } + + // Determine the direction from Pacman's current position to the backtrack position + for (Direction dir : Direction.values()) { + int stepX = currentPos.x() + dir.getDx(); + int stepY = currentPos.y() + dir.getDy(); + + if (stepX == backtrackPos.x() && stepY == backtrackPos.y()) { + return dir; + } + } + System.out.println("No direction found"); + return null; // Should not reach here + } + + /** + * Translates the given coordinates to be relative to Pacman's position and orientation. + * + * @param coordinates the coordinates to translate + * @return the translated coordinates + */ + private Vector2d translateRelative(Vector2ic coordinates) { + Vector2i pacmanPos = pacman.getTilePosition(); + Vector2d relative = new Vector2d( + coordinates.x() - pacmanPos.x(), + coordinates.y() - pacmanPos.y() + ); + return rotateRelative(relative); + } + + /** + * Rotates the given coordinates based on Pacman's current direction. + * + * @param coordinates the coordinates to rotate + * @return the rotated coordinates + */ + private Vector2d rotateRelative(Vector2d coordinates) { + // Assuming rotation based on forward and right directions + double rotatedX = coordinates.x() * forward.getDx() + coordinates.y() * right.getDx(); + double rotatedY = coordinates.x() * forward.getDy() + coordinates.y() * right.getDy(); + return new Vector2d(rotatedX, rotatedY); + } + + /** + * Finds the nearest power pellet in the maze. + * + * @return the nearest power pellet tile + */ + @NotNull + public Tile getNearestPowerPellet() { + Vector2ic dimensions = pacman.getMaze().getDimensions(); + Tile nearestPowerPellet = null; + + for (int y = 0; y < dimensions.y(); y++) { + for (int x = 0; x < dimensions.x(); x++) { + Tile tile = pacman.getMaze().getTile(x, y); + if (tile.getState() == TileState.POWER_PELLET) { + if (nearestPowerPellet == null || + distances[x][y] < distances[nearestPowerPellet.getPosition().x()][nearestPowerPellet.getPosition().y()]) { + nearestPowerPellet = tile; + } + } + } + } + + // Fallback to -1, -1 if no power pellets are found + if (nearestPowerPellet == null) { + nearestPowerPellet = pacman.getMaze().getTile(0, 0); + } + + return nearestPowerPellet; + } + + /** + * Finds the nearest pellet (regular or power) in the maze. + * + * @return the nearest pellet tile + */ + @NotNull + public Tile getNearestPellet() { + Vector2ic dimensions = pacman.getMaze().getDimensions(); + Tile nearestPellet = null; + + for (int y = 0; y < dimensions.y(); y++) { + for (int x = 0; x < dimensions.x(); x++) { + Tile tile = pacman.getMaze().getTile(x, y); + if (tile.getState() == TileState.PELLET || tile.getState() == TileState.POWER_PELLET) { + if (nearestPellet == null || + distances[x][y] < distances[nearestPellet.getPosition().x()][nearestPellet.getPosition().y()]) { + nearestPellet = tile; + } + } + } + } + + return nearestPellet; + } + + + + /** + * Computes the distances from Pacman's current position to all reachable tiles using BFS. + * + * @return a 2D array of distances + */ + public int[][] computeDistances() { + Vector2ic dimensions = pacman.getMaze().getDimensions(); + Vector2ic pacmanPos = pacman.getTilePosition(); + int[][] distance = new int[dimensions.x()][dimensions.y()]; + + for (int y = 0; y < dimensions.y(); y++) { + for (int x = 0; x < dimensions.x(); x++) { + distance[x][y] = -1; + } + } + + distance[pacmanPos.x()][pacmanPos.y()] = 0; + Queue queue = new LinkedList<>(); + queue.add(new Vector2i(pacmanPos)); + + while (!queue.isEmpty()) { + Vector2i current = queue.poll(); + for (Direction dir : Direction.values()) { + int nextX = current.x() + dir.getDx(); + int nextY = current.y() + dir.getDy(); + + if (!isWithinBounds(new Vector2i(nextX, nextY), dimensions)) { + continue; + } + + if (pacman.getMaze().getTile(nextX, nextY).getState() != TileState.WALL && + distance[nextX][nextY] == -1) { + distance[nextX][nextY] = distance[current.x()][current.y()] + 1; + queue.add(new Vector2i(nextX, nextY)); + } + } + } + + return distance; + } + + /** + * Retrieves the suggested direction towards the target tile. + * + * @param targetTile the target tile + * @return the suggested Direction + */ + @Nullable + private Direction getSuggestedDirection(Tile targetTile) { + Direction direction = getFirstStepToTile(targetTile); + return direction != null ? direction : pacman.getDirection(); + } + + /** + * Helper class to store ghost distances and directions. + */ + private static class GhostInfo { + float[] ghostDistances; + Vector2d[] ghostDirections; + + GhostInfo(float[] ghostDistances, Vector2d[] ghostDirections) { + this.ghostDistances = ghostDistances; + this.ghostDirections = ghostDirections; } - */ } } diff --git a/core/src/main/java/com/buaisociety/pacman/entity/behavior/TournamentBehavior.java b/core/src/main/java/com/buaisociety/pacman/entity/behavior/TournamentBehavior.java index 22cab50..b70f6e5 100644 --- a/core/src/main/java/com/buaisociety/pacman/entity/behavior/TournamentBehavior.java +++ b/core/src/main/java/com/buaisociety/pacman/entity/behavior/TournamentBehavior.java @@ -1,22 +1,64 @@ package com.buaisociety.pacman.entity.behavior; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.buaisociety.pacman.entity.Direction; import com.buaisociety.pacman.entity.Entity; +import com.buaisociety.pacman.entity.GhostEntity; import com.buaisociety.pacman.entity.PacmanEntity; +import com.buaisociety.pacman.maze.Maze; +import com.buaisociety.pacman.maze.Tile; +import com.buaisociety.pacman.maze.TileState; +import com.buaisociety.pacman.sprite.DebugDrawing; import com.cjcrafter.neat.compute.Calculator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.joml.Vector2d; +import org.joml.Vector2i; +import org.joml.Vector2ic; +import java.util.*; + +/** + * TournamentBehavior class migrated from NeatPacmanBehavior. + * Implements the Behavior interface and uses a neural network to decide Pacman's movement. + */ public class TournamentBehavior implements Behavior { private final Calculator calculator; private @Nullable PacmanEntity pacman; - private int previousScore = 0; - private int framesSinceScoreUpdate = 0; + private Direction forward = Direction.UP; + private Direction left = Direction.LEFT; + private Direction right = Direction.RIGHT; + private Direction behind = Direction.DOWN; + + // Score modifiers help us maintain "multiple pools" of points. + // This is great for training, because we can take away points from + // specific pools of points instead of subtracting from all. + private float scoreModifier = 0; + + public final List moveHistory = new ArrayList<>(); + + private int lastScore = 0; + private int updatesSinceLastScore = 0; + + private int[][] distances; + + public int numGhosts = 0; - public TournamentBehavior(Calculator calculator) { + public int visionRange = 2; + + private final Random random; + + int movesMade; + + ArrayList positions = new ArrayList(); + + public TournamentBehavior(@NotNull Calculator calculator) { this.calculator = calculator; + this.random = new Random(); + } /** @@ -30,47 +72,529 @@ public TournamentBehavior(Calculator calculator) { public Direction getDirection(@NotNull Entity entity) { // --- DO NOT REMOVE --- if (pacman == null) { - pacman = (PacmanEntity) entity; } + assert false; + pacman = (PacmanEntity) entity; int newScore = pacman.getMaze().getLevelManager().getScore(); - if (previousScore != newScore) { - previousScore = newScore; - framesSinceScoreUpdate = 0; + if (lastScore != newScore) { + lastScore = newScore; + updatesSinceLastScore = 0; } else { - framesSinceScoreUpdate++; + updatesSinceLastScore++; } - if (framesSinceScoreUpdate > 60 * 40) { + if (updatesSinceLastScore > 60 * 40) { // 40 seconds pacman.kill(); - framesSinceScoreUpdate = 0; + updatesSinceLastScore = 0; } // --- END OF DO NOT REMOVE --- - // TODO: Put all your code for info into the neural network here + // Initialize directions based on current direction + updateDirections(); - float[] inputs = new float[] { - // TODO: Add your inputs here - }; + // Compute distances using BFS + distances = computeDistances(); + + // Handle special training conditions (similar to original behavior) + handleSpecialTrainingConditions(); + + // Perform ray casting to detect walls + float[] rayCastDistances = performRayCasting(); + + // Get nearest pellets + Tile nearestPellet = getNearestPellet(); + Vector2d relativePelletPos = translateRelative(nearestPellet.getPosition()); + + Direction suggestedPelletDirection = getSuggestedDirection(nearestPellet); + Vector2d suggestedPelletDirRelative = rotateRelative(new Vector2d( + suggestedPelletDirection.getDx(), + suggestedPelletDirection.getDy() + )); + + // Get nearest power pellets + Tile nearestPowerPellet = getNearestPowerPellet(); + Vector2d relativePowerPelletPos = translateRelative(nearestPowerPellet.getPosition()); + + Direction suggestedPowerDirection = getSuggestedDirection(nearestPowerPellet); + Vector2d suggestedPowerDirRelative = rotateRelative(new Vector2d( + suggestedPowerDirection.getDx(), + suggestedPowerDirection.getDy() + )); + + // Gather ghost information + GhostInfo ghostInfo = gatherGhostInformation(); + + // Build inputs for the neural network + float[] inputs = buildInputs(rayCastDistances, relativePelletPos, suggestedPelletDirRelative, + nearestPellet, relativePowerPelletPos, suggestedPowerDirRelative, ghostInfo); + + // Calculate outputs from the neural network float[] outputs = calculator.calculate(inputs).join(); - // Chooses the maximum output as the direction to go... feel free to change this ofc! - // Adjust this to whatever you used in the NeatPacmanBehavior.class - int index = 0; - float max = outputs[0]; - for (int i = 1; i < outputs.length; i++) { - if (outputs[i] > max) { - max = outputs[i]; - index = i; + // Select the direction based on outputs + Direction newDirection = selectDirectionFromOutputs(outputs); + + // Add the new direction to move history + moveHistory.add(newDirection); + positions.add(new Vector2d(pacman.getTilePosition().x(), pacman.getTilePosition().y())); + if (positions.size() > 40 && updatesSinceLastScore > 30) { + positions.remove(0); + if(positions.get(0).equals(positions.get(39))) { + Random random = new Random(); + newDirection = Direction.values()[random.nextInt(4)]; } } + + return newDirection; + } + + /** + * Updates the directional fields based on Pacman's current direction. + */ + private void updateDirections() { + forward = pacman.getDirection(); + left = pacman.getDirection().left(); + right = pacman.getDirection().right(); + behind = pacman.getDirection().behind(); + } + + /** + * Handles special training conditions such as score updates and kill conditions. + */ + private void handleSpecialTrainingConditions() { + int currentScore = pacman.getMaze().getLevelManager().getScore(); + if (currentScore > lastScore) { + lastScore = currentScore; + updatesSinceLastScore = 0; + if (currentScore > 100_000) { +// pacman.kill(); + return; + } + } else { + updatesSinceLastScore++; + } + int maxUpdates = 60 * 10; // 10 seconds + if (lastScore > 5000) { + maxUpdates = 60 * 20; // 20 seconds + } + + if (lastScore > 10000) { + maxUpdates = 60 * 30; // 30 seconds + } + if (numGhosts > 0) { + if (lastScore > 200) { + maxUpdates = 60 * 120; // 2 minutes + } else { + maxUpdates = 60 * 3; // 3 seconds + } + } + if (updatesSinceLastScore > maxUpdates) { +// pacman.kill(); + } + } + + /** + * Performs ray casting in all four directions to detect distances to walls. + * + * @return an array containing distances in forward, left, right, and behind directions + */ + private float[] performRayCasting() { + float[] rayCast = new float[4]; + Vector2ic dimensions = pacman.getMaze().getDimensions(); + + for (int i = 0; i < 4; i++) { + Direction direction = getDirectionByIndex(i); + Vector2i position = new Vector2i(pacman.getTilePosition().x(), pacman.getTilePosition().y()); + + while (isWithinBounds(position, dimensions) && + pacman.getMaze().getTile(position).getState() != TileState.WALL) { + position.add(direction.getDx(), direction.getDy()); + if (!isWithinBounds(position, dimensions)) { + break; + } + rayCast[i] = (float) Math.sqrt( + Math.pow((position.x() - pacman.getTilePosition().x()) / (double) dimensions.x(), 2) + + Math.pow((position.y() - pacman.getTilePosition().y()) / (double) dimensions.y(), 2) + ); + } + } + + return rayCast; + } + + /** + * Retrieves the corresponding Direction based on the index. + * + * @param index the index (0: forward, 1: left, 2: right, 3: behind) + * @return the corresponding Direction + */ + private Direction getDirectionByIndex(int index) { return switch (index) { - case 0 -> pacman.getDirection(); - case 1 -> pacman.getDirection().left(); - case 2 -> pacman.getDirection().right(); - case 3 -> pacman.getDirection().behind(); + case 0 -> forward; + case 1 -> left; + case 2 -> right; + case 3 -> behind; default -> throw new IllegalStateException("Unexpected value: " + index); }; } + + /** + * Checks if the given position is within the maze boundaries. + * + * @param position the position to check + * @param dimensions the dimensions of the maze + * @return true if within bounds, false otherwise + */ + private boolean isWithinBounds(Vector2i position, Vector2ic dimensions) { + return position.x() >= 0 && position.x() < dimensions.x() && + position.y() >= 0 && position.y() < dimensions.y(); + } + + /** + * Gathers information about the ghosts in the maze. + * + * @return a GhostInfo object containing distances and directions of ghosts + */ + private GhostInfo gatherGhostInformation() { + List ghosts = getGhostEntities(); + numGhosts = ghosts.size(); + float[] ghostDistances = new float[4]; + Vector2d[] ghostDirections = new Vector2d[4]; + Arrays.fill(ghostDistances, 1); + + int ghostIndex = 0; + for (GhostEntity ghost : ghosts) { + if (ghostIndex >= 4) break; // Limit to 4 ghosts + float distance = distances[ghost.getTilePosition().x()][ghost.getTilePosition().y()]; + if (distance == -1) continue; + Tile ghostTile = pacman.getMaze().getTile(ghost.getTilePosition()); + Direction direction = getFirstStepToTile(ghostTile); + if (direction == null) continue; + + ghostDistances[ghostIndex] = distance / (pacman.getMaze().getDimensions().x() + pacman.getMaze().getDimensions().y()); + ghostDirections[ghostIndex] = rotateRelative(new Vector2d(direction.getDx(), direction.getDy())); + ghostIndex++; + } + + return new GhostInfo(ghostDistances, ghostDirections); + } + + /** + * Retrieves all ghost entities from the maze. + * + * @return a list of GhostEntity objects + */ + private List getGhostEntities() { + List entities = pacman.getMaze().getEntities(); + List ghosts = new ArrayList<>(); + for (Entity entity : entities) { + if (entity instanceof GhostEntity ghost) { + ghosts.add(ghost); + } + } + return ghosts; + } + + /** + * Builds the input array for the neural network. + * + * @param rayCastDistances distances from ray casting + * @param relativePelletPos relative position to the nearest pellet + * @param suggestedPelletDirRelative relative direction to the nearest pellet + * @param nearestPellet the nearest pellet tile + * @param relativePowerPelletPos relative position to the nearest power pellet + * @param suggestedPowerDirRelative relative direction to the nearest power pellet + * @param ghostInfo information about ghosts + * @return an array of input values + */ + private float[] buildInputs(float[] rayCastDistances, Vector2d relativePelletPos, + Vector2d suggestedPelletDirRelative, Tile nearestPellet, + Vector2d relativePowerPelletPos, Vector2d suggestedPowerDirRelative, + GhostInfo ghostInfo) { + Vector2ic dimensions = pacman.getMaze().getDimensions(); + return new float[]{ + // Suggested pellet direction (x, y) + (float) suggestedPelletDirRelative.x(), + (float) suggestedPelletDirRelative.y(), + + // Relative power pellet coordinates +// (float) relativePowerPelletPos.x() / dimensions.x(), +// (float) relativePowerPelletPos.y() / dimensions.y(), + + // Suggested power pellet direction (x, y) + (float) suggestedPowerDirRelative.x(), + (float) suggestedPowerDirRelative.y(), + + // Ghost distances + ghostInfo.ghostDistances[0], +// ghostInfo.ghostDistances[1], +// ghostInfo.ghostDistances[2], +// ghostInfo.ghostDistances[3], + + // Ghost directions (x, y) for each ghost + ghostInfo.ghostDirections[0] != null ? (float) ghostInfo.ghostDirections[0].x() : 0, + ghostInfo.ghostDirections[0] != null ? (float) ghostInfo.ghostDirections[0].y() : 0, +// ghostInfo.ghostDirections[1] != null ? (float) ghostInfo.ghostDirections[1].x() : 0, +// ghostInfo.ghostDirections[1] != null ? (float) ghostInfo.ghostDirections[1].y() : 0, +// ghostInfo.ghostDirections[2] != null ? (float) ghostInfo.ghostDirections[2].x() : 0, +// ghostInfo.ghostDirections[2] != null ? (float) ghostInfo.ghostDirections[2].y() : 0, +// ghostInfo.ghostDirections[3] != null ? (float) ghostInfo.ghostDirections[3].x() : 0, +// ghostInfo.ghostDirections[3] != null ? (float) ghostInfo.ghostDirections[3].y() : 0, + + // Frightened timer status + pacman.getMaze().getFrightenedTimer() <= 3 ? 0 : (float) (pacman.getMaze().getFrightenedTimer() / 200f + 0.5f), + + // Random input for variability +// random.nextFloat(), + }; + } + + /** + * Selects the direction based on the outputs from the neural network. + * + * @param outputs the output array from the neural network + * @return the selected Direction + */ + private Direction selectDirectionFromOutputs(float[] outputs) { + int selectedIndex = 0; + float maxOutput = outputs[0]; + + for (int i = 1; i < outputs.length; i++) { + if (outputs[i] > maxOutput) { + maxOutput = outputs[i]; + selectedIndex = i; + } + } + + return switch (selectedIndex) { + case 0 -> forward; + case 1 -> left; + case 2 -> right; + case 3 -> behind; + default -> throw new IllegalStateException("Unexpected output index: " + selectedIndex); + }; + } + + + + /** + * Retrieves the first step direction towards the target tile using the pre-computed distances. + * + * @param targetTile the destination tile + * @return the Direction to move in, or null if no path exists + */ + @Nullable + public Direction getFirstStepToTile(@NotNull Tile targetTile) { + Vector2ic targetPos = targetTile.getPosition(); + Vector2ic currentPos = pacman.getTilePosition(); + Vector2ic dimensions = pacman.getMaze().getDimensions(); + + // If we're already at the target, no need to move + if (currentPos.equals(targetPos)) { + return null; + } + + // If target is unreachable (distance == -1), return null + if (distances[targetPos.x()][targetPos.y()] == -1) { + return null; + } + + // Start from the target and work backwards to find the first step + Vector2i backtrackPos = new Vector2i(targetPos); + int currentDistance = distances[targetPos.x()][targetPos.y()]; + + while (currentDistance > 1) { + boolean stepFound = false; + + for (Direction dir : Direction.values()) { + int nextX = backtrackPos.x() + dir.getDx(); + int nextY = backtrackPos.y() + dir.getDy(); + + if (!isWithinBounds(new Vector2i(nextX, nextY), dimensions)) { + continue; + } + + if (distances[nextX][nextY] == currentDistance - 1) { + backtrackPos.set(nextX, nextY); + currentDistance--; + stepFound = true; + break; + } + } + + if (!stepFound) { + return null; // Path is broken + } + } + + // Determine the direction from Pacman's current position to the backtrack position + for (Direction dir : Direction.values()) { + int stepX = currentPos.x() + dir.getDx(); + int stepY = currentPos.y() + dir.getDy(); + + if (stepX == backtrackPos.x() && stepY == backtrackPos.y()) { + return dir; + } + } + + return null; // Should not reach here + } + + /** + * Translates the given coordinates to be relative to Pacman's position and orientation. + * + * @param coordinates the coordinates to translate + * @return the translated coordinates + */ + private Vector2d translateRelative(Vector2ic coordinates) { + Vector2i pacmanPos = pacman.getTilePosition(); + Vector2d relative = new Vector2d( + coordinates.x() - pacmanPos.x(), + coordinates.y() - pacmanPos.y() + ); + return rotateRelative(relative); + } + + /** + * Rotates the given coordinates based on Pacman's current direction. + * + * @param coordinates the coordinates to rotate + * @return the rotated coordinates + */ + private Vector2d rotateRelative(Vector2d coordinates) { + // Assuming rotation based on forward and right directions + double rotatedX = coordinates.x() * forward.getDx() + coordinates.y() * right.getDx(); + double rotatedY = coordinates.x() * forward.getDy() + coordinates.y() * right.getDy(); + return new Vector2d(rotatedX, rotatedY); + } + + /** + * Finds the nearest power pellet in the maze. + * + * @return the nearest power pellet tile + */ + @NotNull + public Tile getNearestPowerPellet() { + Vector2ic dimensions = pacman.getMaze().getDimensions(); + Tile nearestPowerPellet = null; + + for (int y = 0; y < dimensions.y(); y++) { + for (int x = 0; x < dimensions.x(); x++) { + Tile tile = pacman.getMaze().getTile(x, y); + if (tile.getState() == TileState.POWER_PELLET) { + if (nearestPowerPellet == null || + distances[x][y] < distances[nearestPowerPellet.getPosition().x()][nearestPowerPellet.getPosition().y()]) { + nearestPowerPellet = tile; + } + } + } + } + + // Fallback to (0,0) if no power pellets are found + if (nearestPowerPellet == null) { + nearestPowerPellet = pacman.getMaze().getTile(0, 0); + } + + System.out.println(nearestPowerPellet.getPosition()); + + return nearestPowerPellet; + } + + /** + * Finds the nearest pellet (regular or power) in the maze. + * + * @return the nearest pellet tile + */ + @NotNull + public Tile getNearestPellet() { + Vector2ic dimensions = pacman.getMaze().getDimensions(); + Tile nearestPellet = null; + + for (int y = 0; y < dimensions.y(); y++) { + for (int x = 0; x < dimensions.x(); x++) { + Tile tile = pacman.getMaze().getTile(x, y); + if (tile.getState() == TileState.PELLET || tile.getState() == TileState.POWER_PELLET) { + if (nearestPellet == null || + distances[x][y] < distances[nearestPellet.getPosition().x()][nearestPellet.getPosition().y()]) { + nearestPellet = tile; + } + } + } + } + + + if(nearestPellet == null) { + nearestPellet = pacman.getMaze().getTile(0, 0); + } + System.out.println(nearestPellet.getPosition()); + return nearestPellet; + } + + /** + * Computes the distances from Pacman's current position to all reachable tiles using BFS. + * + * @return a 2D array of distances + */ + public int[][] computeDistances() { + Vector2ic dimensions = pacman.getMaze().getDimensions(); + Vector2ic pacmanPos = pacman.getTilePosition(); + int[][] distance = new int[dimensions.x()][dimensions.y()]; + + for (int y = 0; y < dimensions.y(); y++) { + for (int x = 0; x < dimensions.x(); x++) { + distance[x][y] = -1; + } + } + + distance[pacmanPos.x()][pacmanPos.y()] = 0; + Queue queue = new LinkedList<>(); + queue.add(new Vector2i(pacmanPos)); + + while (!queue.isEmpty()) { + Vector2i current = queue.poll(); + for (Direction dir : Direction.values()) { + int nextX = current.x() + dir.getDx(); + int nextY = current.y() + dir.getDy(); + + if (!isWithinBounds(new Vector2i(nextX, nextY), dimensions)) { + continue; + } + + if (pacman.getMaze().getTile(nextX, nextY).getState() != TileState.WALL && + distance[nextX][nextY] == -1) { + distance[nextX][nextY] = distance[current.x()][current.y()] + 1; + queue.add(new Vector2i(nextX, nextY)); + } + } + } + + return distance; + } + + /** + * Retrieves the suggested direction towards the target tile. + * + * @param targetTile the target tile + * @return the suggested Direction + */ + @Nullable + private Direction getSuggestedDirection(Tile targetTile) { + Direction direction = getFirstStepToTile(targetTile); + return direction != null ? direction : pacman.getDirection(); + } + + /** + * Helper class to store ghost distances and directions. + */ + private static class GhostInfo { + float[] ghostDistances; + Vector2d[] ghostDirections; + + GhostInfo(float[] ghostDistances, Vector2d[] ghostDirections) { + this.ghostDistances = ghostDistances; + this.ghostDirections = ghostDirections; + } + } } diff --git a/gradle.properties b/gradle.properties index ad1758b..599e4de 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ -org.gradle.daemon=false -org.gradle.jvmargs=-Xms512M -Xmx1G -Dfile.encoding=UTF-8 -Dconsole.encoding=UTF-8 +org.gradle.daemon=true +org.gradle.jvmargs=-Xms4G -Xmx8G -Dfile.encoding=UTF-8 -Dconsole.encoding=UTF-8 org.gradle.configureondemand=false graalHelperVersion=2.0.1 enableGraalNative=false diff --git a/lwjgl3/build.gradle b/lwjgl3/build.gradle index e7676a9..59fdaa9 100644 --- a/lwjgl3/build.gradle +++ b/lwjgl3/build.gradle @@ -17,6 +17,8 @@ plugins { import io.github.fourlastor.construo.Target + + sourceSets.main.resources.srcDirs += [ rootProject.file('assets').path ] mainClassName = 'com.buaisociety.pacman.lwjgl3.Lwjgl3Launcher' application.setMainClass(mainClassName) @@ -110,6 +112,7 @@ tasks.register('dist') { dependsOn 'jar' } + distributions { main { contents { diff --git a/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java b/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java index 0e83fca..d3336ea 100644 --- a/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java +++ b/lwjgl3/src/main/java/com/buaisociety/pacman/lwjgl3/Lwjgl3Launcher.java @@ -18,15 +18,16 @@ private static Lwjgl3Application createApplication() { Lwjgl3ApplicationConfiguration config = getDefaultConfiguration(); // disable vsync to run the game as fast as possible config.useVsync(false); + config.setForegroundFPS(2000); + // hard limit on fps to see the game running at a reasonable speed - config.setForegroundFPS(400); - return new Lwjgl3Application(new Main(), config); + return new Lwjgl3Application(new Main(), config); } else { Lwjgl3ApplicationConfiguration config = getDefaultConfiguration(); // enable vsync to limit the fps to the monitor refresh rate - config.useVsync(true); + config.useVsync(false); // pacman runs at 60 updates per second - config.setForegroundFPS(60); + config.setForegroundFPS(240); return new Lwjgl3Application(new Tournament(), config); } }