From c8c02b6bf0d0fcb3b9a9e8bb3e725651081975f7 Mon Sep 17 00:00:00 2001 From: Felix Jung Date: Wed, 12 Jul 2023 15:31:14 +0200 Subject: [PATCH] Refactor: Improve code readability in various scripts Some scripts had inconsistencies and lacked sufficient code segregation which made the readability less intuitive. The changes involve: 1. Addition of a #region directive to encapsulate the specific namespaces for clarity. 2. Breaking down and reformatting complex lines of codes into simpler and more intuitive formats. 3. Improved the arrangement of the uniform variables for better readability. 4. Applied better spacing throughout the program to ensure consistency. 5. Included some small refactoring changes in the scripts to improve the overall code quality. Importantly, no functional changes have been made to the original design. --- Assets/ML-Agents/Timers/AI_timers.json | 68 +++- ...nitTestScene638247266395951172_timers.json | 30 ++ ...stScene638247266395951172_timers.json.meta | 7 + ...nitTestScene638247267290958812_timers.json | 30 ++ ...stScene638247267290958812_timers.json.meta | 7 + ...nitTestScene638247268610573678_timers.json | 30 ++ ...stScene638247268610573678_timers.json.meta | 7 + ...nitTestScene638247268662157737_timers.json | 30 ++ ...stScene638247268662157737_timers.json.meta | 7 + ...nitTestScene638247268719317398_timers.json | 30 ++ ...stScene638247268719317398_timers.json.meta | 7 + ...nitTestScene638247269934663094_timers.json | 30 ++ ...stScene638247269934663094_timers.json.meta | 7 + ...nitTestScene638247271174642528_timers.json | 30 ++ ...stScene638247271174642528_timers.json.meta | 7 + ...nitTestScene638247272406491140_timers.json | 30 ++ ...stScene638247272406491140_timers.json.meta | 7 + ...nitTestScene638247272671641522_timers.json | 30 ++ ...stScene638247272671641522_timers.json.meta | 7 + ...nitTestScene638247272775555030_timers.json | 30 ++ ...stScene638247272775555030_timers.json.meta | 7 + ...nitTestScene638247274319628902_timers.json | 76 +++++ ...stScene638247274319628902_timers.json.meta | 7 + .../Plugins/TextMesh Pro/Shaders/TMPro.cginc | 37 +-- .../TextMesh Pro/Shaders/TMPro_Mobile.cginc | 109 +++--- .../Shaders/TMPro_Properties.cginc | 124 +++---- .../TextMesh Pro/Shaders/TMPro_Surface.cginc | 12 +- Assets/SnakeAi.onnx | Bin 89510 -> 0 bytes Assets/SnakeAi.onnx.meta | 16 - Assets/SnakeGame.mlagents.settings.asset | 16 + Assets/SnakeGame.mlagents.settings.asset.meta | 8 + Assets/SnakeGame/Prefabs/SnakeAgent.prefab | 8 +- Assets/SnakeGame/Scripts/Board.cs | 59 ++-- Assets/SnakeGame/Scripts/BoardDisplay.cs | 6 +- Assets/SnakeGame/Scripts/ColorChanger.cs | 6 +- Assets/SnakeGame/Scripts/GameManager.cs | 10 +- Assets/SnakeGame/Scripts/InputController.cs | 19 +- Assets/SnakeGame/Scripts/InputSchemer.cs | 27 +- Assets/SnakeGame/Scripts/Scripts.asmdef | 21 ++ Assets/SnakeGame/Scripts/Scripts.asmdef.meta | 7 + Assets/SnakeGame/Scripts/Snake.cs | 19 +- Assets/SnakeGame/Scripts/SnakeAgent.cs | 309 +++++++++++------- Assets/SnakeGame/Scripts/SnakeController.cs | 16 +- .../SnakeGame/Scripts/SpriteBoardDisplay.cs | 29 +- Assets/SnakeGame/Scripts/TileDisplay.cs | 6 +- Assets/SnakeGame/_Levels/AI.unity | 40 ++- Assets/Tests.meta | 8 + Assets/Tests/EditMode.meta | 8 + Assets/Tests/EditMode/BoardTests.cs | 46 +++ Assets/Tests/EditMode/BoardTests.cs.meta | 11 + Assets/Tests/EditMode/EditMode.asmdef | 24 ++ Assets/Tests/EditMode/EditMode.asmdef.meta | 7 + Assets/Tests/EditMode/SnakeTest.cs | 70 ++++ Assets/Tests/EditMode/SnakeTest.cs.meta | 3 + Assets/Tests/PlayMode.meta | 8 + Assets/Tests/PlayMode/PlayMode.asmdef | 6 + Assets/Tests/PlayMode/PlayMode.asmdef.meta | 7 + Packages/manifest.json | 2 +- Packages/packages-lock.json | 2 +- ProjectSettings/EditorBuildSettings.asset | 3 +- ProjectSettings/ProjectSettings.asset | 2 +- 61 files changed, 1290 insertions(+), 342 deletions(-) create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247266395951172_timers.json create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247266395951172_timers.json.meta create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247267290958812_timers.json create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247267290958812_timers.json.meta create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247268610573678_timers.json create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247268610573678_timers.json.meta create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247268662157737_timers.json create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247268662157737_timers.json.meta create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247268719317398_timers.json create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247268719317398_timers.json.meta create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247269934663094_timers.json create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247269934663094_timers.json.meta create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247271174642528_timers.json create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247271174642528_timers.json.meta create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247272406491140_timers.json create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247272406491140_timers.json.meta create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247272671641522_timers.json create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247272671641522_timers.json.meta create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247272775555030_timers.json create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247272775555030_timers.json.meta create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247274319628902_timers.json create mode 100644 Assets/ML-Agents/Timers/InitTestScene638247274319628902_timers.json.meta delete mode 100644 Assets/SnakeAi.onnx delete mode 100644 Assets/SnakeAi.onnx.meta create mode 100644 Assets/SnakeGame.mlagents.settings.asset create mode 100644 Assets/SnakeGame.mlagents.settings.asset.meta create mode 100644 Assets/SnakeGame/Scripts/Scripts.asmdef create mode 100644 Assets/SnakeGame/Scripts/Scripts.asmdef.meta create mode 100644 Assets/Tests.meta create mode 100644 Assets/Tests/EditMode.meta create mode 100644 Assets/Tests/EditMode/BoardTests.cs create mode 100644 Assets/Tests/EditMode/BoardTests.cs.meta create mode 100644 Assets/Tests/EditMode/EditMode.asmdef create mode 100644 Assets/Tests/EditMode/EditMode.asmdef.meta create mode 100644 Assets/Tests/EditMode/SnakeTest.cs create mode 100644 Assets/Tests/EditMode/SnakeTest.cs.meta create mode 100644 Assets/Tests/PlayMode.meta create mode 100644 Assets/Tests/PlayMode/PlayMode.asmdef create mode 100644 Assets/Tests/PlayMode/PlayMode.asmdef.meta diff --git a/Assets/ML-Agents/Timers/AI_timers.json b/Assets/ML-Agents/Timers/AI_timers.json index 2331464..3265019 100644 --- a/Assets/ML-Agents/Timers/AI_timers.json +++ b/Assets/ML-Agents/Timers/AI_timers.json @@ -1 +1,67 @@ -{"count":1,"self":1070.6020352,"total":3714.9059334,"children":{"InitializeActuators":{"count":24,"self":0.0015083,"total":0.0015083,"children":null},"InitializeSensors":{"count":24,"self":0.0015055,"total":0.0015055,"children":null},"AgentSendState":{"count":461466,"self":7.9821912,"total":43.912659399999995,"children":{"CollectObservations":{"count":3691728,"self":11.673304,"total":11.6733041,"children":null},"WriteActionMask":{"count":3691728,"self":1.7134748,"total":1.7134748,"children":null},"RequestDecision":{"count":3691728,"self":5.8726204,"total":22.5436892,"children":{"AgentInfo.ToProto":{"count":3691728,"self":5.2082856,"total":16.671068899999998,"children":{"GenerateSensorData":{"count":3691728,"self":11.462783199999999,"total":11.4627831,"children":null}}}}}}},"DecideAction":{"count":461466,"self":2493.6136704,"total":2493.6136833,"children":null},"AgentAct":{"count":461466,"self":106.6151168,"total":106.77455959999999,"children":{"AgentInfo.ToProto":{"count":20861,"self":0.0722235,"total":0.1594411,"children":{"GenerateSensorData":{"count":20861,"self":0.087217599999999992,"total":0.087217599999999992,"children":null}}}}}},"gauges":{"SnakeAi.CumulativeReward":{"count":20861,"max":281.898163,"min":-137.800354,"runningAverage":50.4565544,"value":35.8000336,"weightedAverage":68.77597}},"metadata":{"timer_format_version":"0.1.0","start_time_seconds":"1689110079","unity_version":"2023.1.3f1","command_line_arguments":"C:\\Program Files\\Unity\\Hub\\Editor\\2023.1.3f1\\Editor\\Unity.exe -projectpath C:\\Users\\felix\\SnakeGame -useHub -hubIPC -cloudEnvironment production","communication_protocol_version":"1.5.0","com.unity.ml-agents_version":"2.3.0-exp.3","scene_name":"AI","end_time_seconds":"1689113793"}} \ No newline at end of file +{ + "count": 1, + "self": 9.3403792, + "total": 9.3681278, + "children": { + "InitializeActuators": { + "count": 8, + "self": 0.0010034, + "total": 0.0010034, + "children": null + }, + "InitializeSensors": { + "count": 8, + "self": 0.0009994, + "total": 0.0009994, + "children": null + }, + "AgentSendState": { + "count": 1, + "self": 0.0010061, + "total": 0.010084899999999999, + "children": { + "CollectObservations": { + "count": 8, + "self": 0.0080716, + "total": 0.0080716, + "children": null + }, + "WriteActionMask": { + "count": 8, + "self": 0, + "total": 0, + "children": null + }, + "RequestDecision": { + "count": 8, + "self": 0.0010072, + "total": 0.0010072, + "children": null + } + } + }, + "DecideAction": { + "count": 1, + "self": 0.0015562999999999998, + "total": 0.0015562999999999998, + "children": null + }, + "AgentAct": { + "count": 1, + "self": 0.014104799999999999, + "total": 0.014104799999999999, + "children": null + } + }, + "gauges": {}, + "metadata": { + "timer_format_version": "0.1.0", + "start_time_seconds": "1689167416", + "unity_version": "2023.1.3f1", + "command_line_arguments": "C:\\Program Files\\Unity\\Hub\\Editor\\2023.1.3f1\\Editor\\Unity.exe -projectpath C:\\Users\\felix\\SnakeGame -useHub -hubIPC -cloudEnvironment production", + "communication_protocol_version": "1.5.0", + "com.unity.ml-agents_version": "2.3.0-exp.3", + "scene_name": "AI", + "end_time_seconds": "1689167425" + } +} \ No newline at end of file diff --git a/Assets/ML-Agents/Timers/InitTestScene638247266395951172_timers.json b/Assets/ML-Agents/Timers/InitTestScene638247266395951172_timers.json new file mode 100644 index 0000000..8160bfa --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247266395951172_timers.json @@ -0,0 +1,30 @@ +{ + "count": 1, + "self": 4.2739704, + "total": 4.276006, + "children": { + "InitializeActuators": { + "count": 1, + "self": 0.00050389999999999994, + "total": 0.00050389999999999994, + "children": null + }, + "InitializeSensors": { + "count": 1, + "self": 0.0010049, + "total": 0.0010049, + "children": null + } + }, + "gauges": {}, + "metadata": { + "timer_format_version": "0.1.0", + "start_time_seconds": "1689122640", + "unity_version": "2023.1.3f1", + "command_line_arguments": "C:\\Program Files\\Unity\\Hub\\Editor\\2023.1.3f1\\Editor\\Unity.exe -projectpath C:\\Users\\felix\\SnakeGame -useHub -hubIPC -cloudEnvironment production", + "communication_protocol_version": "1.5.0", + "com.unity.ml-agents_version": "2.3.0-exp.3", + "scene_name": "InitTestScene638247266395951172", + "end_time_seconds": "1689122645" + } +} \ No newline at end of file diff --git a/Assets/ML-Agents/Timers/InitTestScene638247266395951172_timers.json.meta b/Assets/ML-Agents/Timers/InitTestScene638247266395951172_timers.json.meta new file mode 100644 index 0000000..04ef9f2 --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247266395951172_timers.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e432f49811ac5404c9e9db0dac6b2db4 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ML-Agents/Timers/InitTestScene638247267290958812_timers.json b/Assets/ML-Agents/Timers/InitTestScene638247267290958812_timers.json new file mode 100644 index 0000000..0da48b9 --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247267290958812_timers.json @@ -0,0 +1,30 @@ +{ + "count": 1, + "self": 89.8113408, + "total": 89.812848699999989, + "children": { + "InitializeActuators": { + "count": 2, + "self": 0.00050389999999999994, + "total": 0.00050389999999999994, + "children": null + }, + "InitializeSensors": { + "count": 2, + "self": 0.0010049, + "total": 0.0010049, + "children": null + } + }, + "gauges": {}, + "metadata": { + "timer_format_version": "0.1.0", + "start_time_seconds": "1689122640", + "unity_version": "2023.1.3f1", + "command_line_arguments": "C:\\Program Files\\Unity\\Hub\\Editor\\2023.1.3f1\\Editor\\Unity.exe -projectpath C:\\Users\\felix\\SnakeGame -useHub -hubIPC -cloudEnvironment production", + "communication_protocol_version": "1.5.0", + "com.unity.ml-agents_version": "2.3.0-exp.3", + "scene_name": "InitTestScene638247267290958812", + "end_time_seconds": "1689122730" + } +} \ No newline at end of file diff --git a/Assets/ML-Agents/Timers/InitTestScene638247267290958812_timers.json.meta b/Assets/ML-Agents/Timers/InitTestScene638247267290958812_timers.json.meta new file mode 100644 index 0000000..4ea0a3f --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247267290958812_timers.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e57847d71b1e4b442970be319eacde77 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ML-Agents/Timers/InitTestScene638247268610573678_timers.json b/Assets/ML-Agents/Timers/InitTestScene638247268610573678_timers.json new file mode 100644 index 0000000..90516bb --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247268610573678_timers.json @@ -0,0 +1,30 @@ +{ + "count": 1, + "self": 0.15764409999999998, + "total": 0.1611676, + "children": { + "InitializeActuators": { + "count": 1, + "self": 0.0010065999999999999, + "total": 0.0010065999999999999, + "children": null + }, + "InitializeSensors": { + "count": 1, + "self": 0.0010134, + "total": 0.0010134, + "children": null + } + }, + "gauges": {}, + "metadata": { + "timer_format_version": "0.1.0", + "start_time_seconds": "1689122862", + "unity_version": "2023.1.3f1", + "command_line_arguments": "C:\\Program Files\\Unity\\Hub\\Editor\\2023.1.3f1\\Editor\\Unity.exe -projectpath C:\\Users\\felix\\SnakeGame -useHub -hubIPC -cloudEnvironment production", + "communication_protocol_version": "1.5.0", + "com.unity.ml-agents_version": "2.3.0-exp.3", + "scene_name": "InitTestScene638247268610573678", + "end_time_seconds": "1689122862" + } +} \ No newline at end of file diff --git a/Assets/ML-Agents/Timers/InitTestScene638247268610573678_timers.json.meta b/Assets/ML-Agents/Timers/InitTestScene638247268610573678_timers.json.meta new file mode 100644 index 0000000..3d0a5a6 --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247268610573678_timers.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6e1a237da4090814dbb987604b3eb85f +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ML-Agents/Timers/InitTestScene638247268662157737_timers.json b/Assets/ML-Agents/Timers/InitTestScene638247268662157737_timers.json new file mode 100644 index 0000000..136892f --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247268662157737_timers.json @@ -0,0 +1,30 @@ +{ + "count": 1, + "self": 5.3269, + "total": 5.3289199, + "children": { + "InitializeActuators": { + "count": 2, + "self": 0.0010065999999999999, + "total": 0.0010065999999999999, + "children": null + }, + "InitializeSensors": { + "count": 2, + "self": 0.0010134, + "total": 0.0010134, + "children": null + } + }, + "gauges": {}, + "metadata": { + "timer_format_version": "0.1.0", + "start_time_seconds": "1689122862", + "unity_version": "2023.1.3f1", + "command_line_arguments": "C:\\Program Files\\Unity\\Hub\\Editor\\2023.1.3f1\\Editor\\Unity.exe -projectpath C:\\Users\\felix\\SnakeGame -useHub -hubIPC -cloudEnvironment production", + "communication_protocol_version": "1.5.0", + "com.unity.ml-agents_version": "2.3.0-exp.3", + "scene_name": "InitTestScene638247268662157737", + "end_time_seconds": "1689122867" + } +} \ No newline at end of file diff --git a/Assets/ML-Agents/Timers/InitTestScene638247268662157737_timers.json.meta b/Assets/ML-Agents/Timers/InitTestScene638247268662157737_timers.json.meta new file mode 100644 index 0000000..a920aa7 --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247268662157737_timers.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 081e53bfb87295742b6d30e528704765 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ML-Agents/Timers/InitTestScene638247268719317398_timers.json b/Assets/ML-Agents/Timers/InitTestScene638247268719317398_timers.json new file mode 100644 index 0000000..97923a3 --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247268719317398_timers.json @@ -0,0 +1,30 @@ +{ + "count": 1, + "self": 29.1070912, + "total": 29.109111199999997, + "children": { + "InitializeActuators": { + "count": 3, + "self": 0.0010065999999999999, + "total": 0.0010065999999999999, + "children": null + }, + "InitializeSensors": { + "count": 3, + "self": 0.0010134, + "total": 0.0010134, + "children": null + } + }, + "gauges": {}, + "metadata": { + "timer_format_version": "0.1.0", + "start_time_seconds": "1689122862", + "unity_version": "2023.1.3f1", + "command_line_arguments": "C:\\Program Files\\Unity\\Hub\\Editor\\2023.1.3f1\\Editor\\Unity.exe -projectpath C:\\Users\\felix\\SnakeGame -useHub -hubIPC -cloudEnvironment production", + "communication_protocol_version": "1.5.0", + "com.unity.ml-agents_version": "2.3.0-exp.3", + "scene_name": "InitTestScene638247268719317398", + "end_time_seconds": "1689122891" + } +} \ No newline at end of file diff --git a/Assets/ML-Agents/Timers/InitTestScene638247268719317398_timers.json.meta b/Assets/ML-Agents/Timers/InitTestScene638247268719317398_timers.json.meta new file mode 100644 index 0000000..5d5ca39 --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247268719317398_timers.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 33da8b9dbb6c7004c93f226e7019851d +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ML-Agents/Timers/InitTestScene638247269934663094_timers.json b/Assets/ML-Agents/Timers/InitTestScene638247269934663094_timers.json new file mode 100644 index 0000000..ddbbc40 --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247269934663094_timers.json @@ -0,0 +1,30 @@ +{ + "count": 1, + "self": 0.1554462, + "total": 0.157462, + "children": { + "InitializeActuators": { + "count": 1, + "self": 0, + "total": 0, + "children": null + }, + "InitializeSensors": { + "count": 1, + "self": 0.0010096, + "total": 0.0010096, + "children": null + } + }, + "gauges": {}, + "metadata": { + "timer_format_version": "0.1.0", + "start_time_seconds": "1689122994", + "unity_version": "2023.1.3f1", + "command_line_arguments": "C:\\Program Files\\Unity\\Hub\\Editor\\2023.1.3f1\\Editor\\Unity.exe -projectpath C:\\Users\\felix\\SnakeGame -useHub -hubIPC -cloudEnvironment production", + "communication_protocol_version": "1.5.0", + "com.unity.ml-agents_version": "2.3.0-exp.3", + "scene_name": "InitTestScene638247269934663094", + "end_time_seconds": "1689122994" + } +} \ No newline at end of file diff --git a/Assets/ML-Agents/Timers/InitTestScene638247269934663094_timers.json.meta b/Assets/ML-Agents/Timers/InitTestScene638247269934663094_timers.json.meta new file mode 100644 index 0000000..ce9380f --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247269934663094_timers.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e10cbc83bf49d7b4aa56bb4a2770e9c0 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ML-Agents/Timers/InitTestScene638247271174642528_timers.json b/Assets/ML-Agents/Timers/InitTestScene638247271174642528_timers.json new file mode 100644 index 0000000..58b6831 --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247271174642528_timers.json @@ -0,0 +1,30 @@ +{ + "count": 1, + "self": 0.19104, + "total": 0.19357549999999998, + "children": { + "InitializeActuators": { + "count": 1, + "self": 0.0005186, + "total": 0.0005186, + "children": null + }, + "InitializeSensors": { + "count": 1, + "self": 0.0005045, + "total": 0.0005045, + "children": null + } + }, + "gauges": {}, + "metadata": { + "timer_format_version": "0.1.0", + "start_time_seconds": "1689123118", + "unity_version": "2023.1.3f1", + "command_line_arguments": "C:\\Program Files\\Unity\\Hub\\Editor\\2023.1.3f1\\Editor\\Unity.exe -projectpath C:\\Users\\felix\\SnakeGame -useHub -hubIPC -cloudEnvironment production", + "communication_protocol_version": "1.5.0", + "com.unity.ml-agents_version": "2.3.0-exp.3", + "scene_name": "InitTestScene638247271174642528", + "end_time_seconds": "1689123119" + } +} \ No newline at end of file diff --git a/Assets/ML-Agents/Timers/InitTestScene638247271174642528_timers.json.meta b/Assets/ML-Agents/Timers/InitTestScene638247271174642528_timers.json.meta new file mode 100644 index 0000000..81930bf --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247271174642528_timers.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3a1538c02ef7cf048bd473b1d144c1b3 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ML-Agents/Timers/InitTestScene638247272406491140_timers.json b/Assets/ML-Agents/Timers/InitTestScene638247272406491140_timers.json new file mode 100644 index 0000000..327aabc --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247272406491140_timers.json @@ -0,0 +1,30 @@ +{ + "count": 1, + "self": 127.58368, + "total": 127.58469849999999, + "children": { + "InitializeActuators": { + "count": 2, + "self": 0.0005186, + "total": 0.0005186, + "children": null + }, + "InitializeSensors": { + "count": 2, + "self": 0.0005045, + "total": 0.0005045, + "children": null + } + }, + "gauges": {}, + "metadata": { + "timer_format_version": "0.1.0", + "start_time_seconds": "1689123118", + "unity_version": "2023.1.3f1", + "command_line_arguments": "C:\\Program Files\\Unity\\Hub\\Editor\\2023.1.3f1\\Editor\\Unity.exe -projectpath C:\\Users\\felix\\SnakeGame -useHub -hubIPC -cloudEnvironment production", + "communication_protocol_version": "1.5.0", + "com.unity.ml-agents_version": "2.3.0-exp.3", + "scene_name": "InitTestScene638247272406491140", + "end_time_seconds": "1689123246" + } +} \ No newline at end of file diff --git a/Assets/ML-Agents/Timers/InitTestScene638247272406491140_timers.json.meta b/Assets/ML-Agents/Timers/InitTestScene638247272406491140_timers.json.meta new file mode 100644 index 0000000..a2fb2a3 --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247272406491140_timers.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 73da7976e6e285d4e8380ba34b0a111b +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ML-Agents/Timers/InitTestScene638247272671641522_timers.json b/Assets/ML-Agents/Timers/InitTestScene638247272671641522_timers.json new file mode 100644 index 0000000..8be8dfe --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247272671641522_timers.json @@ -0,0 +1,30 @@ +{ + "count": 1, + "self": 153.79644159999998, + "total": 153.79746179999998, + "children": { + "InitializeActuators": { + "count": 3, + "self": 0.0005186, + "total": 0.0005186, + "children": null + }, + "InitializeSensors": { + "count": 3, + "self": 0.0005045, + "total": 0.0005045, + "children": null + } + }, + "gauges": {}, + "metadata": { + "timer_format_version": "0.1.0", + "start_time_seconds": "1689123118", + "unity_version": "2023.1.3f1", + "command_line_arguments": "C:\\Program Files\\Unity\\Hub\\Editor\\2023.1.3f1\\Editor\\Unity.exe -projectpath C:\\Users\\felix\\SnakeGame -useHub -hubIPC -cloudEnvironment production", + "communication_protocol_version": "1.5.0", + "com.unity.ml-agents_version": "2.3.0-exp.3", + "scene_name": "InitTestScene638247272671641522", + "end_time_seconds": "1689123272" + } +} \ No newline at end of file diff --git a/Assets/ML-Agents/Timers/InitTestScene638247272671641522_timers.json.meta b/Assets/ML-Agents/Timers/InitTestScene638247272671641522_timers.json.meta new file mode 100644 index 0000000..ba1eb68 --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247272671641522_timers.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9c07ba43d8c72344dae77a80c1db4101 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ML-Agents/Timers/InitTestScene638247272775555030_timers.json b/Assets/ML-Agents/Timers/InitTestScene638247272775555030_timers.json new file mode 100644 index 0000000..8ee1aad --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247272775555030_timers.json @@ -0,0 +1,30 @@ +{ + "count": 1, + "self": 226.90188799999999, + "total": 226.9029033, + "children": { + "InitializeActuators": { + "count": 4, + "self": 0.0005186, + "total": 0.0005186, + "children": null + }, + "InitializeSensors": { + "count": 4, + "self": 0.0005045, + "total": 0.0005045, + "children": null + } + }, + "gauges": {}, + "metadata": { + "timer_format_version": "0.1.0", + "start_time_seconds": "1689123118", + "unity_version": "2023.1.3f1", + "command_line_arguments": "C:\\Program Files\\Unity\\Hub\\Editor\\2023.1.3f1\\Editor\\Unity.exe -projectpath C:\\Users\\felix\\SnakeGame -useHub -hubIPC -cloudEnvironment production", + "communication_protocol_version": "1.5.0", + "com.unity.ml-agents_version": "2.3.0-exp.3", + "scene_name": "InitTestScene638247272775555030", + "end_time_seconds": "1689123345" + } +} \ No newline at end of file diff --git a/Assets/ML-Agents/Timers/InitTestScene638247272775555030_timers.json.meta b/Assets/ML-Agents/Timers/InitTestScene638247272775555030_timers.json.meta new file mode 100644 index 0000000..d291ddd --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247272775555030_timers.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2de93f7bcbc190e408bf3d3d12912f87 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ML-Agents/Timers/InitTestScene638247274319628902_timers.json b/Assets/ML-Agents/Timers/InitTestScene638247274319628902_timers.json new file mode 100644 index 0000000..325f488 --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247274319628902_timers.json @@ -0,0 +1,76 @@ +{ + "count": 1, + "self": 318.5292544, + "total": 318.5669903, + "children": { + "InitializeActuators": { + "count": 6, + "self": 0.0005186, + "total": 0.0005186, + "children": null + }, + "InitializeSensors": { + "count": 6, + "self": 0.0005045, + "total": 0.0005045, + "children": null + }, + "AgentSendState": { + "count": 35, + "self": 0.0025195, + "total": 0.0196018, + "children": { + "CollectObservations": { + "count": 35, + "self": 0.014570699999999999, + "total": 0.014570699999999999, + "children": null + }, + "WriteActionMask": { + "count": 35, + "self": 0.0015046999999999999, + "total": 0.0015046999999999999, + "children": null + }, + "RequestDecision": { + "count": 35, + "self": 0.0010069, + "total": 0.0010069, + "children": null + } + } + }, + "DecideAction": { + "count": 35, + "self": 0.0015523, + "total": 0.0015523, + "children": null + }, + "AgentAct": { + "count": 35, + "self": 0.015566, + "total": 0.015566, + "children": null + } + }, + "gauges": { + "SnakeAi.CumulativeReward": { + "count": 8, + "max": -11.1, + "min": -17.6999989, + "runningAverage": -13.85, + "value": -14.4, + "weightedAverage": -14.289423 + } + }, + "metadata": { + "timer_format_version": "0.1.0", + "start_time_seconds": "1689123118", + "unity_version": "2023.1.3f1", + "command_line_arguments": "C:\\Program Files\\Unity\\Hub\\Editor\\2023.1.3f1\\Editor\\Unity.exe -projectpath C:\\Users\\felix\\SnakeGame -useHub -hubIPC -cloudEnvironment production", + "communication_protocol_version": "1.5.0", + "com.unity.ml-agents_version": "2.3.0-exp.3", + "scene_name": "InitTestScene638247274319628902", + "end_time_seconds": "1689123437" + } +} \ No newline at end of file diff --git a/Assets/ML-Agents/Timers/InitTestScene638247274319628902_timers.json.meta b/Assets/ML-Agents/Timers/InitTestScene638247274319628902_timers.json.meta new file mode 100644 index 0000000..52070c3 --- /dev/null +++ b/Assets/ML-Agents/Timers/InitTestScene638247274319628902_timers.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4b4c70aae4355c345803a719cc48118f +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/TextMesh Pro/Shaders/TMPro.cginc b/Assets/Plugins/TextMesh Pro/Shaders/TMPro.cginc index 5898130..b495705 100644 --- a/Assets/Plugins/TextMesh Pro/Shaders/TMPro.cginc +++ b/Assets/Plugins/TextMesh Pro/Shaders/TMPro.cginc @@ -1,5 +1,5 @@ float2 UnpackUV(float uv) -{ +{ float2 output; output.x = floor(uv / 4096); output.y = uv - 4096 * output.x; @@ -9,7 +9,7 @@ float2 UnpackUV(float uv) fixed4 GetColor(half d, fixed4 faceColor, fixed4 outlineColor, half outline, half softness) { - half faceAlpha = 1-saturate((d - outline * 0.5 + softness * 0.5) / (1.0 + softness)); + half faceAlpha = 1 - saturate((d - outline * 0.5 + softness * 0.5) / (1.0 + softness)); half outlineAlpha = saturate((d + outline * 0.5)) * sqrt(min(1.0, outline)); faceColor.rgb *= faceColor.a; @@ -26,18 +26,18 @@ float3 GetSurfaceNormal(float4 h, float bias) { bool raisedBevel = step(1, fmod(_ShaderFlags, 2)); - h += bias+_BevelOffset; + h += bias + _BevelOffset; - float bevelWidth = max(.01, _OutlineWidth+_BevelWidth); + float bevelWidth = max(.01, _OutlineWidth + _BevelWidth); - // Track outline + // Track outline h -= .5; h /= bevelWidth; - h = saturate(h+.5); + h = saturate(h + .5); - if(raisedBevel) h = 1 - abs(h*2.0 - 1.0); - h = lerp(h, sin(h*3.141592/2.0), _BevelRoundness); - h = min(h, 1.0-_BevelClamp); + if(raisedBevel) h = 1 - abs(h * 2.0 - 1.0); + h = lerp(h, sin(h * 3.141592 / 2.0), _BevelRoundness); + h = min(h, 1.0 - _BevelClamp); h *= _Bevel * bevelWidth * _GradientScale * -2.0; float3 va = normalize(float3(1.0, 0.0, h.y - h.x)); @@ -49,10 +49,10 @@ float3 GetSurfaceNormal(float4 h, float bias) float3 GetSurfaceNormal(float2 uv, float bias, float3 delta) { // Read "height field" - float4 h = {tex2D(_MainTex, uv - delta.xz).a, - tex2D(_MainTex, uv + delta.xz).a, - tex2D(_MainTex, uv - delta.zy).a, - tex2D(_MainTex, uv + delta.zy).a}; + float4 h = {tex2D(_MainTex, uv - delta.xz).a, + tex2D(_MainTex, uv + delta.xz).a, + tex2D(_MainTex, uv - delta.zy).a, + tex2D(_MainTex, uv + delta.zy).a}; return GetSurfaceNormal(h, bias); } @@ -65,10 +65,10 @@ float3 GetSpecular(float3 n, float3 l) float4 GetGlowColor(float d, float scale) { - float glow = d - (_GlowOffset*_ScaleRatioB) * 0.5 * scale; + float glow = d - (_GlowOffset * _ScaleRatioB) * 0.5 * scale; float t = lerp(_GlowInner, (_GlowOuter * _ScaleRatioB), step(0.0, glow)) * 0.5 * scale; - glow = saturate(abs(glow/(1.0 + t))); - glow = 1.0-pow(glow, _GlowPower); + glow = saturate(abs(glow / (1.0 + t))); + glow = 1.0 - pow(glow, _GlowPower); glow *= sqrt(min(1.0, t)); // Fade off glow thinner than 1 screen pixel return float4(_GlowColor.rgb, saturate(_GlowColor.a * glow * 2)); } @@ -77,8 +77,7 @@ float4 BlendARGB(float4 overlying, float4 underlying) { overlying.rgb *= overlying.a; underlying.rgb *= underlying.a; - float3 blended = overlying.rgb + ((1-overlying.a)*underlying.rgb); - float alpha = underlying.a + (1-underlying.a)*overlying.a; + float3 blended = overlying.rgb + ((1 - overlying.a) * underlying.rgb); + float alpha = underlying.a + (1 - underlying.a) * overlying.a; return float4(blended, alpha); } - diff --git a/Assets/Plugins/TextMesh Pro/Shaders/TMPro_Mobile.cginc b/Assets/Plugins/TextMesh Pro/Shaders/TMPro_Mobile.cginc index 5969fec..d4bf5ff 100644 --- a/Assets/Plugins/TextMesh Pro/Shaders/TMPro_Mobile.cginc +++ b/Assets/Plugins/TextMesh Pro/Shaders/TMPro_Mobile.cginc @@ -1,83 +1,84 @@ struct vertex_t { - UNITY_VERTEX_INPUT_INSTANCE_ID - float4 position : POSITION; - float3 normal : NORMAL; - float4 color : COLOR; - float2 texcoord0 : TEXCOORD0; - float2 texcoord1 : TEXCOORD1; + UNITY_VERTEX_INPUT_INSTANCE_ID + float4 position : POSITION; + float3 normal : NORMAL; + float4 color : COLOR; + float2 texcoord0 : TEXCOORD0; + float2 texcoord1 : TEXCOORD1; }; struct pixel_t { - UNITY_VERTEX_INPUT_INSTANCE_ID - UNITY_VERTEX_OUTPUT_STEREO - float4 position : SV_POSITION; - float4 faceColor : COLOR; - float4 outlineColor : COLOR1; - float4 texcoord0 : TEXCOORD0; - float4 param : TEXCOORD1; // weight, scaleRatio - float2 mask : TEXCOORD2; + UNITY_VERTEX_INPUT_INSTANCE_ID + UNITY_VERTEX_OUTPUT_STEREO + float4 position : SV_POSITION; + float4 faceColor : COLOR; + float4 outlineColor : COLOR1; + float4 texcoord0 : TEXCOORD0; + float4 param : TEXCOORD1; // weight, scaleRatio + float2 mask : TEXCOORD2; #if (UNDERLAY_ON || UNDERLAY_INNER) float4 texcoord2 : TEXCOORD3; float4 underlayColor : COLOR2; #endif }; -float4 SRGBToLinear(float4 rgba) { - return float4(lerp(rgba.rgb / 12.92f, pow((rgba.rgb + 0.055f) / 1.055f, 2.4f), step(0.04045f, rgba.rgb)), rgba.a); +float4 SRGBToLinear(float4 rgba) +{ + return float4(lerp(rgba.rgb / 12.92f, pow((rgba.rgb + 0.055f) / 1.055f, 2.4f), step(0.04045f, rgba.rgb)), rgba.a); } pixel_t VertShader(vertex_t input) { - pixel_t output; + pixel_t output; - UNITY_INITIALIZE_OUTPUT(pixel_t, output); - UNITY_SETUP_INSTANCE_ID(input); - UNITY_TRANSFER_INSTANCE_ID(input, output); - UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); + UNITY_INITIALIZE_OUTPUT(pixel_t, output); + UNITY_SETUP_INSTANCE_ID(input); + UNITY_TRANSFER_INSTANCE_ID(input, output); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); - float bold = step(input.texcoord1.y, 0); + float bold = step(input.texcoord1.y, 0); - float4 vert = input.position; - vert.x += _VertexOffsetX; - vert.y += _VertexOffsetY; + float4 vert = input.position; + vert.x += _VertexOffsetX; + vert.y += _VertexOffsetY; - float4 vPosition = UnityObjectToClipPos(vert); + float4 vPosition = UnityObjectToClipPos(vert); - float weight = lerp(_WeightNormal, _WeightBold, bold) / 4.0; - weight = (weight + _FaceDilate) * _ScaleRatioA * 0.5; + float weight = lerp(_WeightNormal, _WeightBold, bold) / 4.0; + weight = (weight + _FaceDilate) * _ScaleRatioA * 0.5; - // Generate UV for the Masking Texture - float4 clampedRect = clamp(_ClipRect, -2e10, 2e10); - float2 maskUV = (vert.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy); + // Generate UV for the Masking Texture + float4 clampedRect = clamp(_ClipRect, -2e10, 2e10); + float2 maskUV = (vert.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy); - float4 color = input.color; + float4 color = input.color; #if (FORCE_LINEAR && !UNITY_COLORSPACE_GAMMA) color = SRGBToLinear(input.color); #endif - float opacity = color.a; + float opacity = color.a; #if (UNDERLAY_ON | UNDERLAY_INNER) opacity = 1.0; #endif - float4 faceColor = float4(color.rgb, opacity) * _FaceColor; - faceColor.rgb *= faceColor.a; + float4 faceColor = float4(color.rgb, opacity) * _FaceColor; + faceColor.rgb *= faceColor.a; - float4 outlineColor = _OutlineColor; - outlineColor.a *= opacity; - outlineColor.rgb *= outlineColor.a; + float4 outlineColor = _OutlineColor; + outlineColor.a *= opacity; + outlineColor.rgb *= outlineColor.a; - output.position = vPosition; - output.faceColor = faceColor; - output.outlineColor = outlineColor; - output.texcoord0 = float4(input.texcoord0.xy, maskUV.xy); - output.param = float4(0.5 - weight, 1.3333 * _GradientScale * (_Sharpness + 1) / _TextureWidth, _OutlineWidth * _ScaleRatioA * 0.5, 0); + output.position = vPosition; + output.faceColor = faceColor; + output.outlineColor = outlineColor; + output.texcoord0 = float4(input.texcoord0.xy, maskUV.xy); + output.param = float4(0.5 - weight, 1.3333 * _GradientScale * (_Sharpness + 1) / _TextureWidth, _OutlineWidth * _ScaleRatioA * 0.5, 0); - float2 mask = float2(0, 0); + float2 mask = float2(0, 0); #if UNITY_UI_CLIP_RECT mask = vert.xy * 2 - clampedRect.xy - clampedRect.zw; #endif - output.mask = mask; + output.mask = mask; #if (UNDERLAY_ON || UNDERLAY_INNER) float4 underlayColor = _UnderlayColor; @@ -90,17 +91,17 @@ pixel_t VertShader(vertex_t input) output.underlayColor = underlayColor; #endif - return output; + return output; } float4 PixShader(pixel_t input) : SV_Target { - UNITY_SETUP_INSTANCE_ID(input); + UNITY_SETUP_INSTANCE_ID(input); - float d = tex2D(_MainTex, input.texcoord0.xy).a; + float d = tex2D(_MainTex, input.texcoord0.xy).a; - float2 UV = input.texcoord0.xy; - float scale = rsqrt(abs(ddx(UV.x) * ddy(UV.y) - ddy(UV.x) * ddx(UV.y))) * input.param.y; + float2 UV = input.texcoord0.xy; + float scale = rsqrt(abs(ddx(UV.x) * ddy(UV.y) - ddy(UV.x) * ddx(UV.y))) * input.param.y; #if (UNDERLAY_ON | UNDERLAY_INNER) float layerScale = scale; @@ -108,9 +109,9 @@ float4 PixShader(pixel_t input) : SV_Target float layerBias = input.param.x * layerScale - .5 - ((_UnderlayDilate * _ScaleRatioC) * .5 * layerScale); #endif - scale /= 1 + (_OutlineSoftness * _ScaleRatioA * scale); + scale /= 1 + (_OutlineSoftness * _ScaleRatioA * scale); - float4 faceColor = input.faceColor * saturate((d - input.param.x) * scale + 0.5); + float4 faceColor = input.faceColor * saturate((d - input.param.x) * scale + 0.5); #ifdef OUTLINE_ON float4 outlineColor = lerp(input.faceColor, input.outlineColor, sqrt(min(1.0, input.param.z * scale * 2))); @@ -138,7 +139,7 @@ float4 PixShader(pixel_t input) : SV_Target faceColor *= a; #endif - // Alternative implementation to UnityGet2DClipping with support for softness + // Alternative implementation to UnityGet2DClipping with support for softness #if UNITY_UI_CLIP_RECT float2 maskZW = 0.25 / (0.25 * half2(_MaskSoftnessX, _MaskSoftnessY) + (1 / scale)); float2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(input.mask.xy)) * maskZW); @@ -153,5 +154,5 @@ float4 PixShader(pixel_t input) : SV_Target clip(faceColor.a - 0.001); #endif - return faceColor; + return faceColor; } diff --git a/Assets/Plugins/TextMesh Pro/Shaders/TMPro_Properties.cginc b/Assets/Plugins/TextMesh Pro/Shaders/TMPro_Properties.cginc index 2e96258..3723c8c 100644 --- a/Assets/Plugins/TextMesh Pro/Shaders/TMPro_Properties.cginc +++ b/Assets/Plugins/TextMesh Pro/Shaders/TMPro_Properties.cginc @@ -1,85 +1,85 @@ // UI Editable properties -uniform sampler2D _FaceTex; // Alpha : Signed Distance -uniform float _FaceUVSpeedX; -uniform float _FaceUVSpeedY; -uniform fixed4 _FaceColor; // RGBA : Color + Opacity -uniform float _FaceDilate; // v[ 0, 1] -uniform float _OutlineSoftness; // v[ 0, 1] +uniform sampler2D _FaceTex; // Alpha : Signed Distance +uniform float _FaceUVSpeedX; +uniform float _FaceUVSpeedY; +uniform fixed4 _FaceColor; // RGBA : Color + Opacity +uniform float _FaceDilate; // v[ 0, 1] +uniform float _OutlineSoftness; // v[ 0, 1] -uniform sampler2D _OutlineTex; // RGBA : Color + Opacity -uniform float _OutlineUVSpeedX; -uniform float _OutlineUVSpeedY; -uniform fixed4 _OutlineColor; // RGBA : Color + Opacity -uniform float _OutlineWidth; // v[ 0, 1] +uniform sampler2D _OutlineTex; // RGBA : Color + Opacity +uniform float _OutlineUVSpeedX; +uniform float _OutlineUVSpeedY; +uniform fixed4 _OutlineColor; // RGBA : Color + Opacity +uniform float _OutlineWidth; // v[ 0, 1] -uniform float _Bevel; // v[ 0, 1] -uniform float _BevelOffset; // v[-1, 1] -uniform float _BevelWidth; // v[-1, 1] -uniform float _BevelClamp; // v[ 0, 1] -uniform float _BevelRoundness; // v[ 0, 1] +uniform float _Bevel; // v[ 0, 1] +uniform float _BevelOffset; // v[-1, 1] +uniform float _BevelWidth; // v[-1, 1] +uniform float _BevelClamp; // v[ 0, 1] +uniform float _BevelRoundness; // v[ 0, 1] -uniform sampler2D _BumpMap; // Normal map -uniform float _BumpOutline; // v[ 0, 1] -uniform float _BumpFace; // v[ 0, 1] +uniform sampler2D _BumpMap; // Normal map +uniform float _BumpOutline; // v[ 0, 1] +uniform float _BumpFace; // v[ 0, 1] -uniform samplerCUBE _Cube; // Cube / sphere map -uniform fixed4 _ReflectFaceColor; // RGB intensity -uniform fixed4 _ReflectOutlineColor; +uniform samplerCUBE _Cube; // Cube / sphere map +uniform fixed4 _ReflectFaceColor; // RGB intensity +uniform fixed4 _ReflectOutlineColor; //uniform float _EnvTiltX; // v[-1, 1] //uniform float _EnvTiltY; // v[-1, 1] -uniform float3 _EnvMatrixRotation; -uniform float4x4 _EnvMatrix; +uniform float3 _EnvMatrixRotation; +uniform float4x4 _EnvMatrix; -uniform fixed4 _SpecularColor; // RGB intensity -uniform float _LightAngle; // v[ 0,Tau] -uniform float _SpecularPower; // v[ 0, 1] -uniform float _Reflectivity; // v[ 5, 15] -uniform float _Diffuse; // v[ 0, 1] -uniform float _Ambient; // v[ 0, 1] +uniform fixed4 _SpecularColor; // RGB intensity +uniform float _LightAngle; // v[ 0,Tau] +uniform float _SpecularPower; // v[ 0, 1] +uniform float _Reflectivity; // v[ 5, 15] +uniform float _Diffuse; // v[ 0, 1] +uniform float _Ambient; // v[ 0, 1] -uniform fixed4 _UnderlayColor; // RGBA : Color + Opacity -uniform float _UnderlayOffsetX; // v[-1, 1] -uniform float _UnderlayOffsetY; // v[-1, 1] -uniform float _UnderlayDilate; // v[-1, 1] -uniform float _UnderlaySoftness; // v[ 0, 1] +uniform fixed4 _UnderlayColor; // RGBA : Color + Opacity +uniform float _UnderlayOffsetX; // v[-1, 1] +uniform float _UnderlayOffsetY; // v[-1, 1] +uniform float _UnderlayDilate; // v[-1, 1] +uniform float _UnderlaySoftness; // v[ 0, 1] -uniform fixed4 _GlowColor; // RGBA : Color + Intesity -uniform float _GlowOffset; // v[-1, 1] -uniform float _GlowOuter; // v[ 0, 1] -uniform float _GlowInner; // v[ 0, 1] -uniform float _GlowPower; // v[ 1, 1/(1+4*4)] +uniform fixed4 _GlowColor; // RGBA : Color + Intesity +uniform float _GlowOffset; // v[-1, 1] +uniform float _GlowOuter; // v[ 0, 1] +uniform float _GlowInner; // v[ 0, 1] +uniform float _GlowPower; // v[ 1, 1/(1+4*4)] // API Editable properties -uniform float _ShaderFlags; -uniform float _WeightNormal; -uniform float _WeightBold; +uniform float _ShaderFlags; +uniform float _WeightNormal; +uniform float _WeightBold; -uniform float _ScaleRatioA; -uniform float _ScaleRatioB; -uniform float _ScaleRatioC; +uniform float _ScaleRatioA; +uniform float _ScaleRatioB; +uniform float _ScaleRatioC; -uniform float _VertexOffsetX; -uniform float _VertexOffsetY; +uniform float _VertexOffsetX; +uniform float _VertexOffsetY; //uniform float _UseClipRect; -uniform float _MaskID; -uniform sampler2D _MaskTex; -uniform float4 _MaskCoord; -uniform float4 _ClipRect; // bottom left(x,y) : top right(z,w) +uniform float _MaskID; +uniform sampler2D _MaskTex; +uniform float4 _MaskCoord; +uniform float4 _ClipRect; // bottom left(x,y) : top right(z,w) //uniform float _MaskWipeControl; //uniform float _MaskEdgeSoftness; //uniform fixed4 _MaskEdgeColor; //uniform bool _MaskInverse; -uniform float _MaskSoftnessX; -uniform float _MaskSoftnessY; +uniform float _MaskSoftnessX; +uniform float _MaskSoftnessY; // Font Atlas properties -uniform sampler2D _MainTex; -uniform float _TextureWidth; -uniform float _TextureHeight; -uniform float _GradientScale; -uniform float _ScaleX; -uniform float _ScaleY; -uniform float _PerspectiveFilter; -uniform float _Sharpness; +uniform sampler2D _MainTex; +uniform float _TextureWidth; +uniform float _TextureHeight; +uniform float _GradientScale; +uniform float _ScaleX; +uniform float _ScaleY; +uniform float _PerspectiveFilter; +uniform float _Sharpness; diff --git a/Assets/Plugins/TextMesh Pro/Shaders/TMPro_Surface.cginc b/Assets/Plugins/TextMesh Pro/Shaders/TMPro_Surface.cginc index 622ae87..7e3e801 100644 --- a/Assets/Plugins/TextMesh Pro/Shaders/TMPro_Surface.cginc +++ b/Assets/Plugins/TextMesh Pro/Shaders/TMPro_Surface.cginc @@ -45,8 +45,8 @@ void PixShader(Input input, inout SurfaceOutput o) // Signed distance float c = tex2D(_MainTex, input.uv_MainTex).a; float sd = (.5 - c - input.param.x) * scale + .5; - float outline = _OutlineWidth*_ScaleRatioA * scale; - float softness = _OutlineSoftness*_ScaleRatioA * scale; + float outline = _OutlineWidth * _ScaleRatioA * scale; + float softness = _OutlineSoftness * _ScaleRatioA * scale; // Color & Alpha float4 faceColor = _FaceColor; @@ -62,9 +62,9 @@ void PixShader(Input input, inout SurfaceOutput o) float3 delta = float3(1.0 / _TextureWidth, 1.0 / _TextureHeight, 0.0); float4 smp4x = {tex2D(_MainTex, input.uv_MainTex - delta.xz).a, - tex2D(_MainTex, input.uv_MainTex + delta.xz).a, - tex2D(_MainTex, input.uv_MainTex - delta.zy).a, - tex2D(_MainTex, input.uv_MainTex + delta.zy).a }; + tex2D(_MainTex, input.uv_MainTex + delta.xz).a, + tex2D(_MainTex, input.uv_MainTex - delta.zy).a, + tex2D(_MainTex, input.uv_MainTex + delta.zy).a}; // Face Normal float3 n = GetSurfaceNormal(smp4x, input.param.x); @@ -82,7 +82,7 @@ void PixShader(Input input, inout SurfaceOutput o) float3 n = float3(0, 0, -1); float3 emission = float3(0, 0, 0); #endif - + #if GLOW_ON float4 glowColor = GetGlowColor(sd, scale); glowColor.a *= input.color.a; diff --git a/Assets/SnakeAi.onnx b/Assets/SnakeAi.onnx deleted file mode 100644 index b6a6a96651fede435cd40473afa6bb27d8eac936..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89510 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 z 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 Y%Q-(UIk5!4dY}Qh@dhBXotOkT0jwKmzyJUM diff --git a/Assets/SnakeAi.onnx.meta b/Assets/SnakeAi.onnx.meta deleted file mode 100644 index fa74e7e..0000000 --- a/Assets/SnakeAi.onnx.meta +++ /dev/null @@ -1,16 +0,0 @@ -fileFormatVersion: 2 -guid: c4ba55f0222bd404697c6c2743553016 -ScriptedImporter: - internalIDToNameTable: [] - externalObjects: {} - serializedVersion: 2 - userData: - assetBundleName: - assetBundleVariant: - script: {fileID: 11500000, guid: 683b6cb6d0a474744822c888b46772c9, type: 3} - optimizeModel: 1 - forceArbitraryBatchSize: 1 - treatErrorsAsWarnings: 0 - importMode: 1 - weightsTypeMode: 0 - activationTypeMode: 0 diff --git a/Assets/SnakeGame.mlagents.settings.asset b/Assets/SnakeGame.mlagents.settings.asset new file mode 100644 index 0000000..c85c854 --- /dev/null +++ b/Assets/SnakeGame.mlagents.settings.asset @@ -0,0 +1,16 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 71515ce028aaa4b4cb6bee13e96ef6f3, type: 3} + m_Name: SnakeGame.mlagents.settings + m_EditorClassIdentifier: + m_ConnectTrainer: 1 + m_EditorPort: 5004 diff --git a/Assets/SnakeGame.mlagents.settings.asset.meta b/Assets/SnakeGame.mlagents.settings.asset.meta new file mode 100644 index 0000000..7df308b --- /dev/null +++ b/Assets/SnakeGame.mlagents.settings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fe255ffce795bc04384b74d57c2c56e4 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/SnakeGame/Prefabs/SnakeAgent.prefab b/Assets/SnakeGame/Prefabs/SnakeAgent.prefab index 30aabc0..b450942 100644 --- a/Assets/SnakeGame/Prefabs/SnakeAgent.prefab +++ b/Assets/SnakeGame/Prefabs/SnakeAgent.prefab @@ -49,7 +49,7 @@ MonoBehaviour: agentParameters: maxStep: 0 hasUpgradedFromAgentParameters: 1 - MaxStep: 0 + MaxStep: 5000 boardDisplay: {fileID: 0} width: 8 height: 8 @@ -65,6 +65,10 @@ MonoBehaviour: numberOfSnakes: 1 startSize: 1 isDisplayOn: 1 + maxPathLength: 5 + showPaths: 0 + blockedColor: {r: 1, g: 0, b: 0, a: 1} + openColor: {r: 0, g: 1, b: 0, a: 1} --- !u!114 &6952289727027405687 MonoBehaviour: m_ObjectHideFlags: 0 @@ -78,7 +82,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_BrainParameters: - VectorObservationSize: 30 + VectorObservationSize: 44 NumStackedVectorObservations: 1 m_ActionSpec: m_NumContinuousActions: 0 diff --git a/Assets/SnakeGame/Scripts/Board.cs b/Assets/SnakeGame/Scripts/Board.cs index bafbde6..65d7038 100644 --- a/Assets/SnakeGame/Scripts/Board.cs +++ b/Assets/SnakeGame/Scripts/Board.cs @@ -1,8 +1,12 @@ +#region + using System; using System.Collections.Generic; using UnityEngine; using Random = UnityEngine.Random; +#endregion + namespace SnakeGame.Scripts { [Serializable] public class Board { @@ -16,35 +20,46 @@ public class Board { public Board(int width, int height, params Snake[] snakes) { - _tiles = new Tile[width, height]; + Tiles = new Tile[width, height]; Snakes = snakes; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - _tiles[x, y] = new Tile(TileType.None); + Tiles[x, y] = new Tile(TileType.None); } } } - public int Width => _tiles.GetLength(0); - public int Height => _tiles.GetLength(1); + public int Width + { + get => Tiles.GetLength(0); + } + public int Height + { + get => Tiles.GetLength(1); + } public List FoodPositions { get; set; } = new(); public Snake[] Snakes { get => snakes; set => snakes = value; } + public Tile[,] Tiles + { + get => _tiles; + set => _tiles = value; + } public Tile GetTile(int x, int y) { - return _tiles[x, y]; + return Tiles[x, y]; } public Vector2Int SpawnFood() { int x = Random.Range(0, Width); int y = Random.Range(0, Height); - if (_tiles[x, y].Type == TileType.None) { - _tiles[x, y].Type = TileType.Food; + if (Tiles[x, y].Type == TileType.None) { + Tiles[x, y].Type = TileType.Food; } return new Vector2Int(x, y); @@ -56,7 +71,7 @@ public Vector2Int SpawnFood() { // Process all tiles for (int x = 0; x < Width; x++) { for (int y = 0; y < Height; y++) { - switch (_tiles[x, y].Type) { + switch (Tiles[x, y].Type) { case TileType.None: matrix[x, y] = 0; break; @@ -79,7 +94,7 @@ public int[] GetBoardAsArray() { for (int x = 0; x < Width; x++) { for (int y = 0; y < Height; y++) { - switch (_tiles[x, y].Type) { + switch (Tiles[x, y].Type) { case TileType.None: array[index] = 0; break; @@ -105,8 +120,9 @@ public void DrawSnake(Snake snake) { // Check boundaries before updating the board - if (bodyPart.x >= 0 && bodyPart.x < Width && bodyPart.y >= 0 && bodyPart.y < Height) { - var currentTile = _tiles[bodyPart.x, bodyPart.y]; + if (bodyPart.x >= 0 && bodyPart.x < Width && bodyPart.y >= 0 && + bodyPart.y < Height) { + var currentTile = Tiles[bodyPart.x, bodyPart.y]; currentTile.Type = TileType.Snake; currentTile.Snake = snake; } else { @@ -119,23 +135,24 @@ public void DrawSnake(Snake snake) { public void ClearBoard() { for (int y = 0; y < Height; y++) { for (int x = 0; x < Width; x++) { - _tiles[x, y].Type = TileType.None; + Tiles[x, y].Type = TileType.None; } } } public bool IsOutOfBounds(Vector2Int nextPosition) { - return nextPosition.x < 0 || nextPosition.x >= Width || nextPosition.y < 0 || nextPosition.y >= Height; + return nextPosition.x < 0 || nextPosition.x >= Width || nextPosition.y < 0 || + nextPosition.y >= Height; } public void DrawFood(List foodPositions) { foreach (var pos in foodPositions) { - _tiles[pos.x, pos.y].Type = TileType.Food; + Tiles[pos.x, pos.y].Type = TileType.Food; } } public bool IsOccupied(Vector2Int vector2Int) { - var tile = _tiles[vector2Int.x, vector2Int.y]; + var tile = Tiles[vector2Int.x, vector2Int.y]; return tile != null && tile.Type != TileType.Snake; } @@ -144,17 +161,17 @@ public Snake GetSnake(int i) { } public void Reset(Snake[] snakes, int width, int height) { - if (_tiles == null || _tiles.GetLength(0) != width || _tiles.GetLength(1) != height) { - _tiles = new Tile[width, height]; + if (Tiles == null || Tiles.GetLength(0) != width || Tiles.GetLength(1) != height) { + Tiles = new Tile[width, height]; } for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - if (_tiles[x, y] == null) { - _tiles[x, y] = new Tile(TileType.None); + if (Tiles[x, y] == null) { + Tiles[x, y] = new Tile(TileType.None); } else { - _tiles[x, y].Type = TileType.None; - _tiles[x, y].Snake = null; + Tiles[x, y].Type = TileType.None; + Tiles[x, y].Snake = null; } } } diff --git a/Assets/SnakeGame/Scripts/BoardDisplay.cs b/Assets/SnakeGame/Scripts/BoardDisplay.cs index c6ac330..1664343 100644 --- a/Assets/SnakeGame/Scripts/BoardDisplay.cs +++ b/Assets/SnakeGame/Scripts/BoardDisplay.cs @@ -1,4 +1,8 @@ -using UnityEngine; +#region + +using UnityEngine; + +#endregion namespace SnakeGame.Scripts { public abstract class BoardDisplay : MonoBehaviour { diff --git a/Assets/SnakeGame/Scripts/ColorChanger.cs b/Assets/SnakeGame/Scripts/ColorChanger.cs index 0203d6c..d4c4fb5 100644 --- a/Assets/SnakeGame/Scripts/ColorChanger.cs +++ b/Assets/SnakeGame/Scripts/ColorChanger.cs @@ -1,4 +1,8 @@ -using UnityEngine; +#region + +using UnityEngine; + +#endregion namespace SnakeGame.Scripts { public class ColorChanger : MonoBehaviour { diff --git a/Assets/SnakeGame/Scripts/GameManager.cs b/Assets/SnakeGame/Scripts/GameManager.cs index f604e66..26c5362 100644 --- a/Assets/SnakeGame/Scripts/GameManager.cs +++ b/Assets/SnakeGame/Scripts/GameManager.cs @@ -1,3 +1,5 @@ +#region + using System; using System.Collections.Generic; using Cinemachine; @@ -5,6 +7,8 @@ using UnityEngine; using UnityEngine.SceneManagement; +#endregion + namespace SnakeGame.Scripts { public class GameManager : MonoBehaviour { #region Serialized Fields @@ -68,7 +72,8 @@ private void Update() { var snake = _board.Snakes[0]; - var inputDirection = InputController.HandleInput(snake.Direction, player.inputSchemer); + var inputDirection = + InputController.HandleInput(snake.Direction, player.inputSchemer); if (inputDirection != Vector2Int.zero) snake.NextDirection = inputDirection; @@ -121,7 +126,8 @@ private void CreateSnakeControllers(int number) { startSpawnPosition += Vector2Int.right * i * 10; snakes[i] = snakeController; - SnakeController.InitializeSnakeBody(_board.Snakes[0], startSpawnPosition, Vector2Int.up, 5); + SnakeController.InitializeSnakeBody(_board.Snakes[0], startSpawnPosition, + Vector2Int.up, 5); } } diff --git a/Assets/SnakeGame/Scripts/InputController.cs b/Assets/SnakeGame/Scripts/InputController.cs index 875eb34..3dfe5b3 100644 --- a/Assets/SnakeGame/Scripts/InputController.cs +++ b/Assets/SnakeGame/Scripts/InputController.cs @@ -1,6 +1,10 @@ +#region + using UnityEngine; using UnityEngine.InputSystem; +#endregion + namespace SnakeGame.Scripts { /// /// This class gives the direction a player wants to move to @@ -8,7 +12,8 @@ namespace SnakeGame.Scripts { /// without the opposite direction /// public static class InputController { - public static Vector2Int HandleInput(Vector2Int currentDirection, InputSchemer inputSchemer) { + public static Vector2Int + HandleInput(Vector2Int currentDirection, InputSchemer inputSchemer) { var up = Vector2Int.up; var down = Vector2Int.down; var left = Vector2Int.left; @@ -18,13 +23,17 @@ public static Vector2Int HandleInput(Vector2Int currentDirection, InputSchemer i Vector2Int nextDirection = default; - if (Keyboard.current[inputSchemer.UpKey].wasPressedThisFrame && currentDirection != down) { + if (Keyboard.current[inputSchemer.UpKey].wasPressedThisFrame && + currentDirection != down) { nextDirection = up; - } else if (Keyboard.current[inputSchemer.LeftKey].wasPressedThisFrame && currentDirection != right) { + } else if (Keyboard.current[inputSchemer.LeftKey].wasPressedThisFrame && + currentDirection != right) { nextDirection = left; - } else if (Keyboard.current[inputSchemer.DownKey].wasPressedThisFrame && currentDirection != up) { + } else if (Keyboard.current[inputSchemer.DownKey].wasPressedThisFrame && + currentDirection != up) { nextDirection = down; - } else if (Keyboard.current[inputSchemer.RightKey].wasPressedThisFrame && currentDirection != left) { + } else if (Keyboard.current[inputSchemer.RightKey].wasPressedThisFrame && + currentDirection != left) { nextDirection = right; } diff --git a/Assets/SnakeGame/Scripts/InputSchemer.cs b/Assets/SnakeGame/Scripts/InputSchemer.cs index 9cedd11..47143e2 100644 --- a/Assets/SnakeGame/Scripts/InputSchemer.cs +++ b/Assets/SnakeGame/Scripts/InputSchemer.cs @@ -1,8 +1,13 @@ +#region + using UnityEngine; using UnityEngine.InputSystem; +#endregion + namespace SnakeGame.Scripts { - [CreateAssetMenu(fileName = "InputSchemer", menuName = "ScriptableObjects/InputSchemer", order = 1)] + [CreateAssetMenu(fileName = "InputSchemer", menuName = "ScriptableObjects/InputSchemer", + order = 1)] public class InputSchemer : ScriptableObject { #region Serialized Fields @@ -14,9 +19,21 @@ public class InputSchemer : ScriptableObject { #endregion - public Key UpKey => upKey; - public Key DownKey => downKey; - public Key RightKey => rightKey; - public Key LeftKey => leftKey; + public Key UpKey + { + get => upKey; + } + public Key DownKey + { + get => downKey; + } + public Key RightKey + { + get => rightKey; + } + public Key LeftKey + { + get => leftKey; + } } } \ No newline at end of file diff --git a/Assets/SnakeGame/Scripts/Scripts.asmdef b/Assets/SnakeGame/Scripts/Scripts.asmdef new file mode 100644 index 0000000..cacd691 --- /dev/null +++ b/Assets/SnakeGame/Scripts/Scripts.asmdef @@ -0,0 +1,21 @@ +{ + "name": "Scripts", + "rootNamespace": "", + "references": [ + "GUID:75469ad4d38634e559750d17036d5f7c", + "GUID:6055be8ebefd69e48b49212b09b47b2f", + "GUID:85e0054f8e64b47309646d35f8851f81", + "GUID:5c2b5ba89f9e74e418232e154bc5cc7a", + "", + "GUID:4307f53044263cf4b835bd812fc161a4" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/SnakeGame/Scripts/Scripts.asmdef.meta b/Assets/SnakeGame/Scripts/Scripts.asmdef.meta new file mode 100644 index 0000000..f40c361 --- /dev/null +++ b/Assets/SnakeGame/Scripts/Scripts.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c2c7bb5dbbb148a4a6e69e2d2b193c4a +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/SnakeGame/Scripts/Snake.cs b/Assets/SnakeGame/Scripts/Snake.cs index 40d9e41..6a83a9c 100644 --- a/Assets/SnakeGame/Scripts/Snake.cs +++ b/Assets/SnakeGame/Scripts/Snake.cs @@ -1,6 +1,10 @@ -using System; +#region + +using System; using UnityEngine; +#endregion + namespace SnakeGame.Scripts { [Serializable] public class Snake { @@ -17,7 +21,8 @@ public class Snake { #endregion - public Snake(Vector2Int position, Vector2Int direction, int length, int id, Vector2Int[] body, + public Snake(Vector2Int position, Vector2Int direction, int length, int id, + Vector2Int[] body, SnakeColor color) { Position = position; Direction = direction; @@ -82,9 +87,15 @@ public int Id public int Score { get; set; } public bool IsAlive { get; set; } = true; - public SnakeColor Color => color; + public SnakeColor Color + { + get => color; + } public bool AteFood { get; set; } - public Vector3 Head => new(Position.x, Position.y, 0); + public Vector3 Head + { + get => new(Position.x, Position.y, 0); + } public Vector2Int NextDirection { get => nextDirection; diff --git a/Assets/SnakeGame/Scripts/SnakeAgent.cs b/Assets/SnakeGame/Scripts/SnakeAgent.cs index c6979bb..a19b010 100644 --- a/Assets/SnakeGame/Scripts/SnakeAgent.cs +++ b/Assets/SnakeGame/Scripts/SnakeAgent.cs @@ -19,45 +19,44 @@ public class SnakeAgent : Agent { [SerializeField] private BoardDisplay boardDisplay; [Range(5, 100)] [SerializeField] private int width = 10; [Range(5, 100)] [SerializeField] private int height = 10; - [SerializeField] private List players; [SerializeField] private TMP_Text scoreText; [SerializeField] private TMP_Text highScoreText; - [SerializeField] private bool wrapIsEnabled; [SerializeField] private int foodCount = 1; [SerializeField] private Board board; [SerializeField] private int numberOfSnakes = 1; [SerializeField] private int startSize = 1; [SerializeField] private bool isDisplayOn; - #endregion - - private float currentReward; + [Range(1, 10)] [SerializeField] private int maxPathLength = 5; + #endregion - private readonly Vector2Int[] _actionDirections = { + private readonly Vector2Int[] _directions = { new(0, 1), new(1, 0), new(0, -1), new(-1, 0), }; - private Vector2Int _inputDirection = Vector2Int.up; // keeps track of the last input direction + private int[] _actionLongestPathArray; + + private float _currentReward; private float _currentTimer; private int _highScore; - private float _previousDistance; + private Vector2Int + _inputDirection = Vector2Int.up; // keeps track of the last input direction + private int _longestPath; + private bool _shouldRecalculatePaths = true; private SnakeController _snakeController; - public float PreviousDistance - { - get => _previousDistance; - set => _previousDistance = value; - } - - + + private float PreviousDistance { get; set; } #region Event Functions private void Start() { InitializeGame(); + _actionLongestPathArray = new int[3]; + _shouldRecalculatePaths = true; } private void Update() { @@ -74,49 +73,15 @@ private void Update() { } if (tempDirection != Vector2Int.zero) _inputDirection = tempDirection; - } - - #endregion - private int ActionIndexForDirection(Vector2Int direction) { - int index = Array.IndexOf(_actionDirections, direction); + if (_shouldRecalculatePaths) { + CalculateLongestPathsForAllDirections(); - - if (index == -1) { - Debug.LogError("Invalid direction"); - return 0; // This should be reasonable default value. + _shouldRecalculatePaths = false; } - - return index; } - private Vector2Int GetDirectionFromAction(int actionIndex) { - return _actionDirections[actionIndex]; - } - - private void InitializeGame() { - _snakeController = new SnakeController(); - var snakes = _snakeController.CreateSnakes(width, height, numberOfSnakes, startSize); - - if (board != null) { - board.Snakes = snakes; - board.Reset(snakes, width, height); - - if (isDisplayOn) { - boardDisplay.Reset(); - } - } else { - board = new Board(width, height, snakes); - } - - PreviousDistance = float.PositiveInfinity; - - - // food - for (int i = 0; i < foodCount; i++) { - board.FoodPositions.Add(board.SpawnFood()); - } - } + #endregion public override void OnEpisodeBegin() { @@ -130,31 +95,41 @@ private static bool IsSnakeAlive(Snake snake) { public override void CollectObservations(VectorSensor sensor) { - const int view = 5; // Define view size as per your needs + Debug.Log("Collecting Observations"); + + const int view = 6; // Define view size as per your needs // Assuming that there is always at least one snake and one food in the game var snake = board.Snakes[0]; var food = board.FoodPositions[0]; // Calculate direction to the food relative to the snake's heading - Vector2 directionToFood = (food - snake.Position); + Vector2 directionToFood = food - snake.Position; // Normalize directionToFood directionToFood = directionToFood.normalized; // Add the direction to the food (2 floats) - sensor.AddObservation(food); + sensor.AddObservation(directionToFood); // Check for immediate dangers: front, left, right relative to the snake's current direction var forwardPosition = snake.Position + snake.Direction; var leftPosition = snake.Position + RotateCounterClockwise(snake.Direction); var rightPosition = snake.Position + RotateClockwise(snake.Direction); - // Check if next positions are inside the board and not occupied by the snake's body sensor.AddObservation(IsPositionSafe(forwardPosition)); sensor.AddObservation(IsPositionSafe(leftPosition)); sensor.AddObservation(IsPositionSafe(rightPosition)); + + CalculateLongestPathsForAllDirections(); + + sensor.AddObservation(_actionLongestPathArray[1]); + sensor.AddObservation(_actionLongestPathArray[0]); + sensor.AddObservation(_actionLongestPathArray[2]); + + + // Assuming that there is always at least one snake in the game var snakeHeadPosition = board.Snakes[0].Position; @@ -169,7 +144,8 @@ public override void CollectObservations(VectorSensor sensor) { int cellValue; - if (cellPos.x < 0 || cellPos.y < 0 || cellPos.x >= board.Width || cellPos.y >= board.Height) { + if (cellPos.x < 0 || cellPos.y < 0 || cellPos.x >= board.Width || + cellPos.y >= board.Height) { // Cell is out of bounds cellValue = -1; } else if (tile.Type == TileType.Snake) { @@ -188,6 +164,36 @@ public override void CollectObservations(VectorSensor sensor) { } } + private int FindLongestPath(Vector2Int position, HashSet visitedPositions, + int maxLength, + int currentLength = 0) { + visitedPositions.Add(position); + + // Exit if the path length equals maxPathLength + if (currentLength == maxLength) { + return currentLength; + } + + int longestPath = 0; + + + + foreach (var dir in _directions) { + var nextPosition = position + dir; + + if (IsPositionSafe(nextPosition) && !visitedPositions.Contains(nextPosition)) { + longestPath = Math.Max(longestPath, + FindLongestPath(nextPosition, visitedPositions, + maxLength, + currentLength + 1)); + } + } + + visitedPositions.Remove(position); + + return longestPath; + } + private bool IsPositionSafe(Vector2Int position) { // Define the condition for the position to be safe. The following is just a basic example. // You might need to add more conditions or modify it according to your game rules. @@ -223,29 +229,11 @@ public override void OnActionReceived(ActionBuffers actions) { } var snake = board.Snakes[0]; - Vector2 FoodPosition, previousFoodPosition = Vector2.zero; - - if (board.FoodPositions.Count > 0) { - FoodPosition = board.FoodPositions[0]; - previousFoodPosition = FoodPosition; - } - switch (action) { - case 0: //turn left - snake.NextDirection = RotateCounterClockwise(snake.Direction); - break; - case 1: //go straight, no need to change the direction - snake.NextDirection = snake.Direction; - break; - case 2: //turn right - snake.NextDirection = RotateClockwise(snake.Direction); - break; - } - _snakeController.FinalizeDirection(snake); - _snakeController.CheckCollisions(board); + HandleSnakeDirection(action, snake); - _snakeController.Move(board, board.Snakes[0], false); + ProcessSnakeMovement(snake); board.ClearBoard(); board.DrawFood(board.FoodPositions); @@ -261,66 +249,129 @@ public override void OnActionReceived(ActionBuffers actions) { boardDisplay.DrawBoard(board); } - // update high score if current score is higher - if (board.Snakes[0].Score > _highScore) { - _highScore = snake.Score; - } - - highScoreText.text = "High Score: " + _highScore; - - - scoreText.text = "Score: " + board.Snakes[0].Score; + CheckForNewHighScore(snake); // Distance Reward const int rewardRadius = 4; + // Get action's longest path from the array + int actionLongestPath = _actionLongestPathArray[action]; + + // If the longest path is shorter than the maximum, it's a bad action (blocked), apply punishment + if (actionLongestPath < maxPathLength) { + Debug.Log("Bad action"); + AddReward(-1f); + } + // Good action, apply reward + else { + Debug.Log("Good action"); + AddReward(0.1f); + } + if (board.FoodPositions.Count > 0) { var currentTilePos = snake.Position; - FoodPosition = board.FoodPositions[0]; - - //Euclidean distance to food from current position - float currentDistance = Vector2.Distance(currentTilePos, FoodPosition); - // Normalize the current distance, max distance will be the sqrt of (height^2 + width^2) - float normalizedCurrentDistance = currentDistance / - Mathf.Sqrt((board.Width * board.Width) + - (board.Height * board.Height)); + Vector2 foodPosition = board.FoodPositions[0]; + + //Euclidean distance to food from current position + float currentDistance = Vector2.Distance(currentTilePos, foodPosition); - if (normalizedCurrentDistance <= rewardRadius) { - // Compute the reward based on the Normalized current distance - // float reward = Mathf.Log((snake.Length + PreviousDistance) / (snake.Length + currentDistance)); - - - + // Calculate the actual distance to the food + // If the current distance is within the reward radius, apply the reward + if (currentDistance <= rewardRadius) { + float normalizedCurrentDistance = currentDistance / + Mathf.Sqrt( + board.Width * board.Width + + board.Height * board.Height); + float normalizedPreviousDistance = PreviousDistance / + Mathf.Sqrt(board.Width * board.Width + + board.Height * board.Height); + // Compute the reward based on the current distance + float reward = Mathf.Log((snake.Length + normalizedPreviousDistance) / + (snake.Length + normalizedCurrentDistance)); - // Define the reward - float reward = Mathf.Log((snake.Length + PreviousDistance) - / (snake.Length + normalizedCurrentDistance)); + // normalize the reward between -1 and 1 - // Debug.Log("Distance Reward:" + reward); // Apply the reward - // AddReward(reward); - PreviousDistance = currentDistance; + AddReward(reward); } + PreviousDistance = currentDistance; + + if (currentDistance < PreviousDistance) { AddReward(0.01f); Debug.Log("Closer :" + 0.01f); - } else if (currentDistance > PreviousDistance) { AddReward(-0.01f); Debug.Log("Further :" + -0.01f); } + PreviousDistance = currentDistance; } - if (board.Snakes[0].AteFood) { - Debug.Log("Eat : " + 10f); + CheckSnakeStatus(snake); + + _shouldRecalculatePaths = true; + } + private void CalculateLongestPathsForAllDirections() { + var snake = board.Snakes[0]; + + for (int action = 0; action < 3; ++action) { + var actionDirection = action switch { + 0 => RotateCounterClockwise(snake.Direction), + 1 => snake.Direction, + 2 => RotateClockwise(snake.Direction), + _ => snake.Direction, + }; + + int longestPath = FindLongestPath(snake.Position + actionDirection, + new HashSet(), + maxPathLength); + string directionText = action switch { + 0 => "Left", + 1 => "Forward", + 2 => "Right", + _ => "Forward", + }; + Debug.Log($"Longest path for action {directionText} is {longestPath}"); + _actionLongestPathArray[action] = longestPath; + } + } + + + private void InitializeGame() { + _snakeController = new SnakeController(); + var snakes = _snakeController.CreateSnakes(width, height, numberOfSnakes, startSize); + + if (board != null) { + board.Snakes = snakes; + board.Reset(snakes, width, height); + + if (isDisplayOn) { + boardDisplay.Reset(); + } + } else { + board = new Board(width, height, snakes); + } + + PreviousDistance = float.PositiveInfinity; + + + // food + for (int i = 0; i < foodCount; i++) { + board.FoodPositions.Add(board.SpawnFood()); + } + } + + private void CheckSnakeStatus(Snake snake) { + if (snake.AteFood) { + Debug.Log("Eat : " + 20); AddReward(10f); - board.Snakes[0].AteFood = false; + snake.AteFood = false; } AddReward(-0.1f); @@ -332,10 +383,42 @@ public override void OnActionReceived(ActionBuffers actions) { } } + private void CheckForNewHighScore(Snake snake) { + // update high score if current score is higher + if (board.Snakes[0].Score > _highScore) { + _highScore = snake.Score; + } + + highScoreText.text = "High Score: " + _highScore; + + + scoreText.text = "Score: " + board.Snakes[0].Score; + } + + private void ProcessSnakeMovement(Snake snake) { + _snakeController.FinalizeDirection(snake); + _snakeController.CheckCollisions(board); + + _snakeController.Move(board, board.Snakes[0], false); + } + + private void HandleSnakeDirection(int action, Snake snake) { + switch (action) { + case 0: //turn left + snake.NextDirection = RotateCounterClockwise(snake.Direction); + break; + case 1: //go straight, no need to change the direction + snake.NextDirection = snake.Direction; + break; + case 2: //turn right + snake.NextDirection = RotateClockwise(snake.Direction); + break; + } + } + public override void Heuristic(in ActionBuffers actionsOut) { - // Debug.Log("Heuristic"); - Vector2Int currentDirection = board.Snakes[0].Direction; + var currentDirection = board.Snakes[0].Direction; int relativeDirection = GetRelativeDirection(currentDirection, _inputDirection); var discreteActionsOut = actionsOut.DiscreteActions; discreteActionsOut[0] = relativeDirection; diff --git a/Assets/SnakeGame/Scripts/SnakeController.cs b/Assets/SnakeGame/Scripts/SnakeController.cs index 4744d08..3dd68b9 100644 --- a/Assets/SnakeGame/Scripts/SnakeController.cs +++ b/Assets/SnakeGame/Scripts/SnakeController.cs @@ -1,4 +1,8 @@ -using UnityEngine; +#region + +using UnityEngine; + +#endregion namespace SnakeGame.Scripts { public class SnakeController { @@ -22,14 +26,16 @@ public Vector2Int NextDirection set => nextDirection = value; }*/ - public static void InitializeSnakeBody(Snake snake, Vector2Int position, Vector2Int direction, int length) { + public static void InitializeSnakeBody(Snake snake, Vector2Int position, + Vector2Int direction, int length) { for (int i = 0; i < length; i++) { snake.Body[i] = position - direction * i; } } public Snake CreateSnake(Vector2Int position, Vector2Int direction, int length, int id) { - var snake = new Snake(position, direction, length, id, new Vector2Int[length], SnakeColor.Player1); + var snake = new Snake(position, direction, length, id, new Vector2Int[length], + SnakeColor.Player1); InitializeSnakeBody(snake, position, direction, length); return snake; @@ -103,8 +109,8 @@ public Snake[] CreateSnakes(int width, int height, int numberOfSnakes, int start var snakeArray = new Snake[numberOfSnakes]; for (int i = 0; i < numberOfSnakes; i++) { - var startSpawnPosition = new Vector2Int(Random.Range(0, width), Random.Range(0, height)); - // var startSpawnPosition = new Vector2Int(0, 0); + var startSpawnPosition = + new Vector2Int(Random.Range(0, width), Random.Range(0, height)); var startDirection = Vector2Int.up; snakeArray[i] = CreateSnake(startSpawnPosition, startDirection, startSize, i); } diff --git a/Assets/SnakeGame/Scripts/SpriteBoardDisplay.cs b/Assets/SnakeGame/Scripts/SpriteBoardDisplay.cs index 8e847c0..75f6ace 100644 --- a/Assets/SnakeGame/Scripts/SpriteBoardDisplay.cs +++ b/Assets/SnakeGame/Scripts/SpriteBoardDisplay.cs @@ -1,8 +1,11 @@ -using System; +#region + +using System; using System.Collections.Generic; -using System.Linq; using UnityEngine; +#endregion + namespace SnakeGame.Scripts { internal class SpriteBoardDisplay : BoardDisplay { #region Serialized Fields @@ -14,7 +17,7 @@ internal class SpriteBoardDisplay : BoardDisplay { [SerializeField] private Material noneMaterial; [SerializeField] private Material foodMaterial; [SerializeField] private Material snakeMaterial; - [SerializeField] Material foodDistanceMaterial; + [SerializeField] private Material foodDistanceMaterial; [SerializeField] private Gradient rewardGradient; @@ -31,6 +34,7 @@ internal class SpriteBoardDisplay : BoardDisplay { [SerializeField] private Material player10Material; [Header("Visuals")] [SerializeField] private GameObject snakeDirectionPrefab; + [SerializeField] private bool isShowingReward; #endregion @@ -38,7 +42,6 @@ internal class SpriteBoardDisplay : BoardDisplay { private readonly List _snakeHeads = new(); private TileDisplay[,] _tileDisplays; - [SerializeField] private bool isShowingReward = false; #region Event Functions @@ -68,7 +71,8 @@ private void CreateTileDisplays(int width, int height) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - var tilePos = new Vector3(x, y, 0) - new Vector3(width, height, 0) / 2f + Vector3.one / 2f; + var tilePos = new Vector3(x, y, 0) - new Vector3(width, height, 0) / 2f + + Vector3.one / 2f; _tileDisplays[x, y] = CreateTileDisplay(tilePos); } } @@ -114,7 +118,8 @@ public override void DrawBoard(Board board) { } if (board.Snakes != null) { - var snakePos = new Vector2Int(board.Snakes[0].Position.x, board.Snakes[0].Position.y); + var snakePos = + new Vector2Int(board.Snakes[0].Position.x, board.Snakes[0].Position.y); var foodPos = new Vector2Int(board.FoodPositions[0].x, board.FoodPositions[0].y); int rewardRadius = 10; @@ -127,12 +132,15 @@ public override void DrawBoard(Board board) { case TileType.None: if (isShowingReward) { var currentTilePos = new Vector2Int(x, y); - float distanceToSnake = Vector2Int.Distance(currentTilePos, snakePos); - float distanceToFood = Vector2Int.Distance(currentTilePos, foodPos); + float distanceToSnake = + Vector2Int.Distance(currentTilePos, snakePos); + float distanceToFood = + Vector2Int.Distance(currentTilePos, foodPos); if (distanceToSnake <= rewardRadius) { var rewardMaterial = new Material(foodDistanceMaterial) { - color = rewardGradient.Evaluate(1f - (distanceToFood / rewardRadius)) + color = rewardGradient.Evaluate( + 1f - distanceToFood / rewardRadius), }; tileDisplay.ChangeMaterial(rewardMaterial); @@ -187,7 +195,8 @@ private void SnakeDirectionUpdate() { rotation = Quaternion.Euler(0, 0, -90); break; default: - throw new ArgumentOutOfRangeException("SnakeDirection is not valid", innerException: null); + throw new ArgumentOutOfRangeException( + "SnakeDirection is not valid", innerException: null); } entity.Value.transform.localRotation = rotation; diff --git a/Assets/SnakeGame/Scripts/TileDisplay.cs b/Assets/SnakeGame/Scripts/TileDisplay.cs index 1a2922e..1677582 100644 --- a/Assets/SnakeGame/Scripts/TileDisplay.cs +++ b/Assets/SnakeGame/Scripts/TileDisplay.cs @@ -1,4 +1,8 @@ -using UnityEngine; +#region + +using UnityEngine; + +#endregion namespace SnakeGame.Scripts { [RequireComponent(typeof(SpriteRenderer))] diff --git a/Assets/SnakeGame/_Levels/AI.unity b/Assets/SnakeGame/_Levels/AI.unity index 51dbf21..bfd5e5b 100644 --- a/Assets/SnakeGame/_Levels/AI.unity +++ b/Assets/SnakeGame/_Levels/AI.unity @@ -190,7 +190,7 @@ Transform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 12 + m_RootOrder: 11 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!4 &247828178 stripped Transform: @@ -202,6 +202,11 @@ Transform: m_CorrespondingSourceObject: {fileID: 5044792707948238565, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} m_PrefabInstance: {fileID: 7051632490841417575} m_PrefabAsset: {fileID: 0} +--- !u!224 &351170469 stripped +RectTransform: + m_CorrespondingSourceObject: {fileID: 7261023630039980208, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} + m_PrefabInstance: {fileID: 1705506837} + m_PrefabAsset: {fileID: 0} --- !u!4 &372270328 stripped Transform: m_CorrespondingSourceObject: {fileID: 5044792707948238565, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} @@ -218,7 +223,7 @@ PrefabInstance: serializedVersion: 2 m_Modification: serializedVersion: 3 - m_TransformParent: {fileID: 0} + m_TransformParent: {fileID: 351170469} m_Modifications: - target: {fileID: 1096062850392511375, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} propertyPath: m_IsActive @@ -232,6 +237,10 @@ PrefabInstance: propertyPath: m_BehaviorType value: 0 objectReference: {fileID: 0} + - target: {fileID: 1655411129354736655, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} + propertyPath: m_BrainParameters.NumStackedVectorObservations + value: 1 + objectReference: {fileID: 0} - target: {fileID: 3017644220238255728, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} propertyPath: m_Name value: Env_6 @@ -274,15 +283,15 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 5044792707948238565, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} propertyPath: m_RootOrder - value: 10 + value: -1 objectReference: {fileID: 0} - target: {fileID: 5044792707948238565, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} propertyPath: m_LocalPosition.x - value: -5 + value: 20.2 objectReference: {fileID: 0} - target: {fileID: 5044792707948238565, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} propertyPath: m_LocalPosition.y - value: 35 + value: 15 objectReference: {fileID: 0} - target: {fileID: 5044792707948238565, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} propertyPath: m_LocalPosition.z @@ -294,15 +303,15 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 5044792707948238565, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} propertyPath: m_LocalRotation.x - value: 0 + value: -0 objectReference: {fileID: 0} - target: {fileID: 5044792707948238565, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} propertyPath: m_LocalRotation.y - value: 0 + value: -0 objectReference: {fileID: 0} - target: {fileID: 5044792707948238565, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} propertyPath: m_LocalRotation.z - value: 0 + value: -0 objectReference: {fileID: 0} - target: {fileID: 5044792707948238565, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} propertyPath: m_LocalEulerAnglesHint.x @@ -692,7 +701,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 5044792707948238565, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} propertyPath: m_RootOrder - value: 11 + value: 10 objectReference: {fileID: 0} - target: {fileID: 5044792707948238565, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} propertyPath: m_LocalPosition.x @@ -1109,7 +1118,10 @@ PrefabInstance: m_RemovedComponents: [] m_RemovedGameObjects: - {fileID: 6541531631288212839, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} - m_AddedGameObjects: [] + m_AddedGameObjects: + - targetCorrespondingSourceObject: {fileID: 7261023630039980208, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} + insertIndex: -1 + addedObject: {fileID: 1367699698} m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} --- !u!4 &1816695672 stripped @@ -1548,10 +1560,18 @@ PrefabInstance: propertyPath: m_LocalScale.z value: 1 objectReference: {fileID: 0} + - target: {fileID: 7152727188512011903, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} + propertyPath: isShowingPaths + value: 1 + objectReference: {fileID: 0} - target: {fileID: 7152727188512011903, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} propertyPath: isShowingReward value: 0 objectReference: {fileID: 0} + - target: {fileID: 7538769358573959649, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} + propertyPath: showPaths + value: 0 + objectReference: {fileID: 0} m_RemovedComponents: [] m_RemovedGameObjects: - {fileID: 6541531631288212839, guid: 5aadfc29e3233f24f8980e6f948b18eb, type: 3} diff --git a/Assets/Tests.meta b/Assets/Tests.meta new file mode 100644 index 0000000..6f880ad --- /dev/null +++ b/Assets/Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 65fe64fe22774a22ac0313a6b54a96ae +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/EditMode.meta b/Assets/Tests/EditMode.meta new file mode 100644 index 0000000..6be2d23 --- /dev/null +++ b/Assets/Tests/EditMode.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 31cbdf841a65ceb43979a633954821a4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/EditMode/BoardTests.cs b/Assets/Tests/EditMode/BoardTests.cs new file mode 100644 index 0000000..27ab6aa --- /dev/null +++ b/Assets/Tests/EditMode/BoardTests.cs @@ -0,0 +1,46 @@ +#region + +using NUnit.Framework; +using SnakeGame.Scripts; + +#endregion + +namespace Tests.EditMode { + public class BoardTests { + private Board _board; + private readonly int _height = 10; + private readonly int _width = 10; + + [SetUp] + public void Setup() { + _board = new Board(_width, _height); + } + + [Test] + public void TestBoardCreation() { + Assert.IsNotNull(_board); + Assert.IsNotNull(_board.Tiles); + Assert.IsNotNull(_board.FoodPositions); + Assert.AreEqual(_board.Width, _width); + Assert.AreEqual(_board.Height, _height); + + for (int x = 0; x < _width; x++) { + for (int y = 0; y < _height; y++) { + Assert.IsNotNull(_board.GetTile(x, y)); + Assert.AreEqual(TileType.None, _board.GetTile(x, y).Type); + } + } + } + + [Test] + public void TestFoodSpawn() { + var foodPosition = _board.SpawnFood(); + + Assert.IsTrue(foodPosition.x >= 0); + Assert.IsTrue(foodPosition.x < _board.Width); + Assert.IsTrue(foodPosition.y >= 0); + Assert.IsTrue(foodPosition.y < _board.Height); + Assert.AreEqual(TileType.Food, _board.GetTile(foodPosition.x, foodPosition.y).Type); + } + } +} \ No newline at end of file diff --git a/Assets/Tests/EditMode/BoardTests.cs.meta b/Assets/Tests/EditMode/BoardTests.cs.meta new file mode 100644 index 0000000..bccb781 --- /dev/null +++ b/Assets/Tests/EditMode/BoardTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4233b5e82608d814aab020721b560482 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/EditMode/EditMode.asmdef b/Assets/Tests/EditMode/EditMode.asmdef new file mode 100644 index 0000000..2b4dba3 --- /dev/null +++ b/Assets/Tests/EditMode/EditMode.asmdef @@ -0,0 +1,24 @@ +{ + "name": "EditMode", + "rootNamespace": "", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "Scripts" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/Tests/EditMode/EditMode.asmdef.meta b/Assets/Tests/EditMode/EditMode.asmdef.meta new file mode 100644 index 0000000..d1decd4 --- /dev/null +++ b/Assets/Tests/EditMode/EditMode.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 35b7e44e447e90d4f98a4ff953010c96 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/EditMode/SnakeTest.cs b/Assets/Tests/EditMode/SnakeTest.cs new file mode 100644 index 0000000..a8038a2 --- /dev/null +++ b/Assets/Tests/EditMode/SnakeTest.cs @@ -0,0 +1,70 @@ +#region + +using NUnit.Framework; +using SnakeGame.Scripts; +using UnityEngine; + +#endregion + +namespace Tests.EditMode { + [TestFixture] + public class SnakeTests { + #region Setup/Teardown + + [SetUp] + public void SetUp() { + var position = new Vector2Int(0, 0); + var direction = new Vector2Int(0, 1); + var body = new[] {position}; + + _snake = new Snake(position, direction, 1, 1, body, SnakeColor.Player1); + } + + #endregion + + private Snake _snake; + + [Test] + public void ContainsPosition_ReturnsExpectedResult_WhenPositionIsInSnakeBody() { + var positionInBody = _snake.Body[0]; + + bool result = _snake.ContainsPosition(positionInBody); + + Assert.IsTrue(result); + } + + [Test] + public void ContainsPosition_ReturnsExpectedResult_WhenPositionIsNotInSnakeBody() { + var positionNotInBody = new Vector2Int(5, 5); + + bool result = _snake.ContainsPosition(positionNotInBody); + + Assert.IsFalse(result); + } + + [Test] + public void Die_MakesSnakeDead() { + _snake.Die(); + + Assert.IsFalse(_snake.IsAlive); + } + + [Test] + public void Grow_IncreasesSnakeLengthAndScore_AdjestsBodyAndAteFoodState() { + int initialLength = _snake.Length; + int initialScore = _snake.Score; + + _snake.Grow(); + + Assert.AreEqual(initialLength + 1, _snake.Length); + Assert.AreEqual(initialScore + 1, _snake.Score); + Assert.AreEqual(_snake.Body.Length, _snake.Length); + Assert.IsTrue(_snake.AteFood); + } + + [Test] + public void SnakeConstructor_CreatesSnakeInstance() { + Assert.IsNotNull(_snake); + } + } +} \ No newline at end of file diff --git a/Assets/Tests/EditMode/SnakeTest.cs.meta b/Assets/Tests/EditMode/SnakeTest.cs.meta new file mode 100644 index 0000000..b58c047 --- /dev/null +++ b/Assets/Tests/EditMode/SnakeTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5f4e81b7139c493d8ce803b25c70fc89 +timeCreated: 1689125111 \ No newline at end of file diff --git a/Assets/Tests/PlayMode.meta b/Assets/Tests/PlayMode.meta new file mode 100644 index 0000000..ef19337 --- /dev/null +++ b/Assets/Tests/PlayMode.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e05c84bcb6743ff4992535c6b4fb3ffb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/PlayMode/PlayMode.asmdef b/Assets/Tests/PlayMode/PlayMode.asmdef new file mode 100644 index 0000000..2abc975 --- /dev/null +++ b/Assets/Tests/PlayMode/PlayMode.asmdef @@ -0,0 +1,6 @@ +{ + "name": "PlayMode", + "optionalUnityReferences": [ + "TestAssemblies" + ] +} diff --git a/Assets/Tests/PlayMode/PlayMode.asmdef.meta b/Assets/Tests/PlayMode/PlayMode.asmdef.meta new file mode 100644 index 0000000..e9b67c4 --- /dev/null +++ b/Assets/Tests/PlayMode/PlayMode.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 102e5fabd8ecf4241b97ca8361d2284d +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/manifest.json b/Packages/manifest.json index 3bb16a7..765baf4 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -9,7 +9,7 @@ "com.unity.memoryprofiler": "1.0.0", "com.unity.ml-agents": "file:../ml-agents/com.unity.ml-agents", "com.unity.ml-agents.extensions": "file:../ml-agents/com.unity.ml-agents.extensions", - "com.unity.test-framework": "1.3.7", + "com.unity.test-framework": "1.3.8", "com.unity.textmeshpro": "3.0.6", "com.unity.timeline": "1.8.2", "com.unity.ugui": "1.0.0", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index eb1265c..e093ca6 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -231,7 +231,7 @@ } }, "com.unity.test-framework": { - "version": "1.3.7", + "version": "1.3.8", "depth": 0, "source": "registry", "dependencies": { diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset index 8d520b8..96d86f5 100644 --- a/ProjectSettings/EditorBuildSettings.asset +++ b/ProjectSettings/EditorBuildSettings.asset @@ -8,5 +8,6 @@ EditorBuildSettings: - enabled: 1 path: Assets/SnakeGame/_Levels/AI.unity guid: a063a73148572484591e47fdbcea95de - m_configObjects: {} + m_configObjects: + com.unity.ml-agents.settings: {fileID: 11400000, guid: fe255ffce795bc04384b74d57c2c56e4, type: 2} m_UseUCBPForAssetBundles: 0 diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 00f1710..55780e1 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -78,7 +78,7 @@ PlayerSettings: androidApplicationEntry: 1 defaultIsNativeResolution: 1 macRetinaSupport: 1 - runInBackground: 0 + runInBackground: 1 captureSingleScreen: 0 muteOtherAudioSources: 0 Prepare IOS For Recording: 0