├── icon.png ├── assets ├── cross.png ├── mid-edge.png ├── top-corner.png ├── top-edges.png ├── top-corners.png ├── cross.png.import ├── mid-edge.png.import ├── top-edges.png.import ├── top-corner.png.import └── top-corners.png.import ├── media ├── color.png ├── cube1.png ├── blanked.png ├── color-map.png ├── icon-cube.png ├── sequence.png ├── solving.png ├── coordinates.png ├── screenshot.png ├── screenshot2.png ├── control-panel.png ├── cubic-screenshot.png ├── image-conversion.txt ├── color.png.import ├── cube1.png.import ├── blanked.png.import ├── solving.png.import ├── icon-cube.png.import ├── sequence.png.import ├── color-map.png.import ├── screenshot.png.import ├── coordinates.png.import ├── screenshot2.png.import ├── control-panel.png.import ├── cubic-screenshot.png.import ├── graphics.svg.import └── graphics.svg ├── scenes ├── face.material ├── label.stylebox ├── popup.stylebox ├── black-button-small.png ├── big_cube.tscn ├── face.gdshader ├── Support.gd ├── cube.gd ├── UI.gd ├── black-button-small.png.import ├── sequence.gd ├── sequence.tscn ├── orbiting_camera.gd ├── color_map.tscn ├── cube.tscn ├── color_map.gd ├── big_cube.gd ├── main.tscn └── main.gd ├── .gitattributes ├── .gitignore ├── project.godot ├── icon.png.import ├── icon.svg.import ├── LICENSE ├── README.md ├── licences.txt └── icon.svg /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/icon.png -------------------------------------------------------------------------------- /assets/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/assets/cross.png -------------------------------------------------------------------------------- /media/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/media/color.png -------------------------------------------------------------------------------- /media/cube1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/media/cube1.png -------------------------------------------------------------------------------- /assets/mid-edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/assets/mid-edge.png -------------------------------------------------------------------------------- /media/blanked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/media/blanked.png -------------------------------------------------------------------------------- /media/color-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/media/color-map.png -------------------------------------------------------------------------------- /media/icon-cube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/media/icon-cube.png -------------------------------------------------------------------------------- /media/sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/media/sequence.png -------------------------------------------------------------------------------- /media/solving.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/media/solving.png -------------------------------------------------------------------------------- /assets/top-corner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/assets/top-corner.png -------------------------------------------------------------------------------- /assets/top-edges.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/assets/top-edges.png -------------------------------------------------------------------------------- /media/coordinates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/media/coordinates.png -------------------------------------------------------------------------------- /media/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/media/screenshot.png -------------------------------------------------------------------------------- /media/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/media/screenshot2.png -------------------------------------------------------------------------------- /scenes/face.material: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/scenes/face.material -------------------------------------------------------------------------------- /scenes/label.stylebox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/scenes/label.stylebox -------------------------------------------------------------------------------- /scenes/popup.stylebox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/scenes/popup.stylebox -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /assets/top-corners.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/assets/top-corners.png -------------------------------------------------------------------------------- /media/control-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/media/control-panel.png -------------------------------------------------------------------------------- /media/cubic-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/media/cubic-screenshot.png -------------------------------------------------------------------------------- /scenes/black-button-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-wilkes/cubic/HEAD/scenes/black-button-small.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot-specific ignores 2 | .import/ 3 | export.cfg 4 | export_presets.cfg 5 | .godot 6 | 7 | # Mono-specific ignores 8 | .mono/ 9 | data_*/ 10 | 11 | builds/ 12 | -------------------------------------------------------------------------------- /scenes/big_cube.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://duux08b0j1brq"] 2 | 3 | [ext_resource type="Script" path="res://scenes/big_cube.gd" id="1_ne2f3"] 4 | 5 | [node name="BigCube" type="Node3D"] 6 | script = ExtResource("1_ne2f3") 7 | -------------------------------------------------------------------------------- /scenes/face.gdshader: -------------------------------------------------------------------------------- 1 | shader_type spatial; 2 | 3 | uniform vec3 color: source_color; 4 | uniform vec3 grey_color: source_color; 5 | uniform bool grey = false; 6 | 7 | void fragment() { 8 | if (grey) { 9 | ALBEDO = grey_color; 10 | } else { 11 | ALBEDO = color; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /media/image-conversion.txt: -------------------------------------------------------------------------------- 1 | convert media/icon-cube.png -resize 64 icon.png 2 | 3 | https://www.howtogeek.com/109369/how-to-quickly-resize-convert-modify-images-from-the-linux-terminal/ 4 | 5 | Creating an ICO file 6 | 7 | convert media/icon-cube.png -define icon:auto-resize=256,128,64,48,32,16 builds/windows/icon.ico -------------------------------------------------------------------------------- /scenes/Support.gd: -------------------------------------------------------------------------------- 1 | extends PopupPanel 2 | 3 | var button: Button 4 | 5 | func _ready(): 6 | button = get_parent().find_child("Coffee") 7 | if button: 8 | button.connect("pressed", show_popup) 9 | 10 | 11 | func show_popup(): 12 | button.release_focus() 13 | popup_centered() 14 | 15 | 16 | func _on_text_meta_clicked(_meta): 17 | var _e = OS.shell_open("https://buymeacoffee.com/gdscriptdude") 18 | -------------------------------------------------------------------------------- /scenes/cube.gd: -------------------------------------------------------------------------------- 1 | extends CSGBox3D 2 | 3 | 4 | func _ready(): 5 | # Make the shaders unique 6 | for face in get_children(): 7 | if face is CSGBox3D: 8 | var shader = face.get_material().duplicate() 9 | face.set_material(shader) 10 | 11 | 12 | func set_grey(make_grey): 13 | for face in get_children(): 14 | if face is CSGBox3D: 15 | face.get_material().set_shader_parameter("grey", make_grey) 16 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="CUBIC" 14 | run/main_scene="res://scenes/main.tscn" 15 | config/features=PackedStringArray("4.2", "Forward Plus") 16 | config/icon="res://icon.png" 17 | 18 | [display] 19 | 20 | window/size/viewport_width=900 21 | window/size/viewport_height=470 22 | window/size/resizable=false 23 | 24 | [rendering] 25 | 26 | environment/defaults/default_clear_color=Color(0.14902, 0.843137, 0.815686, 1) 27 | -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://001i6cl1qeb" 6 | path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.png" 14 | dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /media/color.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://7wlhmuiltuel" 6 | path="res://.godot/imported/color.png-98a5470e271a811e1fe7899fe645706b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://media/color.png" 14 | dest_files=["res://.godot/imported/color.png-98a5470e271a811e1fe7899fe645706b.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /media/cube1.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://buh6xhi2no8ub" 6 | path="res://.godot/imported/cube1.png-b530e023a555cac33a80eaf28d8e3e69.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://media/cube1.png" 14 | dest_files=["res://.godot/imported/cube1.png-b530e023a555cac33a80eaf28d8e3e69.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/cross.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bark3whhet5bm" 6 | path="res://.godot/imported/cross.png-02546c5657ca18f075c4c5271ba2b0d0.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/cross.png" 14 | dest_files=["res://.godot/imported/cross.png-02546c5657ca18f075c4c5271ba2b0d0.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /media/blanked.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bgbgprbb0sri8" 6 | path="res://.godot/imported/blanked.png-7f056f90252d02fcd18bd4211d876d33.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://media/blanked.png" 14 | dest_files=["res://.godot/imported/blanked.png-7f056f90252d02fcd18bd4211d876d33.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /media/solving.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cxy7fbx6b75or" 6 | path="res://.godot/imported/solving.png-89a20aa2cab0d3f505eb957e207cbc0b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://media/solving.png" 14 | dest_files=["res://.godot/imported/solving.png-89a20aa2cab0d3f505eb957e207cbc0b.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/mid-edge.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dqoxntu2lo6y6" 6 | path="res://.godot/imported/mid-edge.png-a12cfaf47b2ac0034f9f0244c0b25109.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/mid-edge.png" 14 | dest_files=["res://.godot/imported/mid-edge.png-a12cfaf47b2ac0034f9f0244c0b25109.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /media/icon-cube.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://77x7v0l41teh" 6 | path="res://.godot/imported/icon-cube.png-0526d8700d7ab0e4ded38b8b1696b6d2.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://media/icon-cube.png" 14 | dest_files=["res://.godot/imported/icon-cube.png-0526d8700d7ab0e4ded38b8b1696b6d2.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /media/sequence.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://owyexts8fmht" 6 | path="res://.godot/imported/sequence.png-f5d5b72bb22eef0519fedd3ebdf06998.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://media/sequence.png" 14 | dest_files=["res://.godot/imported/sequence.png-f5d5b72bb22eef0519fedd3ebdf06998.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/top-edges.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bwxs1ppsxt7hm" 6 | path="res://.godot/imported/top-edges.png-3b27be2924c233583f2fe327947db1c0.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/top-edges.png" 14 | dest_files=["res://.godot/imported/top-edges.png-3b27be2924c233583f2fe327947db1c0.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /media/color-map.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://diqfwpxawwr4y" 6 | path="res://.godot/imported/color-map.png-e56808a04f126ab7f5aee2b6bb444ca2.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://media/color-map.png" 14 | dest_files=["res://.godot/imported/color-map.png-e56808a04f126ab7f5aee2b6bb444ca2.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /media/screenshot.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://l0aarejsqnkp" 6 | path="res://.godot/imported/screenshot.png-383732b1ad1229e277ccf09f7ea21a80.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://media/screenshot.png" 14 | dest_files=["res://.godot/imported/screenshot.png-383732b1ad1229e277ccf09f7ea21a80.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/top-corner.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://b4e5cqklbbmvh" 6 | path="res://.godot/imported/top-corner.png-7ccdf09c6dad60ebc24409edbf743c6e.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/top-corner.png" 14 | dest_files=["res://.godot/imported/top-corner.png-7ccdf09c6dad60ebc24409edbf743c6e.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /assets/top-corners.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://vtgsknbn1yy" 6 | path="res://.godot/imported/top-corners.png-063a76b627c457308f4a22a89ba9353e.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/top-corners.png" 14 | dest_files=["res://.godot/imported/top-corners.png-063a76b627c457308f4a22a89ba9353e.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /media/coordinates.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dtqvb2vq15hxl" 6 | path="res://.godot/imported/coordinates.png-1d87ada43c38650a31d9bec9d6ddde9b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://media/coordinates.png" 14 | dest_files=["res://.godot/imported/coordinates.png-1d87ada43c38650a31d9bec9d6ddde9b.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /media/screenshot2.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://b51rsa5vmk1hp" 6 | path="res://.godot/imported/screenshot2.png-e4196b97580609344c8a02c6634c08fc.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://media/screenshot2.png" 14 | dest_files=["res://.godot/imported/screenshot2.png-e4196b97580609344c8a02c6634c08fc.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /media/control-panel.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dt1qp5vi8tpyq" 6 | path="res://.godot/imported/control-panel.png-f422ddfe0808ced3fe6e747f1f200273.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://media/control-panel.png" 14 | dest_files=["res://.godot/imported/control-panel.png-f422ddfe0808ced3fe6e747f1f200273.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /media/cubic-screenshot.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://j0a7a3sd7lnm" 6 | path="res://.godot/imported/cubic-screenshot.png-98a87ce847cfe68c73294941eec6e3cd.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://media/cubic-screenshot.png" 14 | dest_files=["res://.godot/imported/cubic-screenshot.png-98a87ce847cfe68c73294941eec6e3cd.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /scenes/UI.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | signal button_pressed(id, shift, ctrl) 4 | 5 | # Connect the signal to all of the buttons 6 | func _ready(): 7 | var nodes = $Grid.get_children() 8 | nodes.append_array(self.get_children()) 9 | nodes.append_array($Tools.get_children()) 10 | for node in nodes: 11 | if node is Button: 12 | node.button_down.connect(_on_button_down.bind(node.name)) 13 | 14 | 15 | func _on_button_down(bname: String): 16 | # Shift will be used to indicate a reverse rotation direction request 17 | var shift = Input.is_key_pressed(KEY_SHIFT) or Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT) 18 | # Ctrl is used to change the function of buttons to align the cube with a face 19 | var ctrl = Input.is_key_pressed(KEY_CTRL) or Input.is_mouse_button_pressed(MOUSE_BUTTON_MIDDLE) 20 | emit_signal("button_pressed", bname, shift, ctrl) 21 | 22 | 23 | func _unhandled_input(_event): 24 | if Input.is_key_pressed(KEY_ESCAPE): 25 | get_tree().quit() 26 | -------------------------------------------------------------------------------- /scenes/black-button-small.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bvpm6eq6ugoog" 6 | path="res://.godot/imported/black-button-small.png-26d3d6ebde0d727d6a22848e890f82b6.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://scenes/black-button-small.png" 14 | dest_files=["res://.godot/imported/black-button-small.png-26d3d6ebde0d727d6a22848e890f82b6.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=true 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://rr0edgufr7kt" 6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.svg" 14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /media/graphics.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cca4vuaomsgyj" 6 | path="res://.godot/imported/graphics.svg-729676a50632ecc9e2d42273d703f3ea.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://media/graphics.svg" 14 | dest_files=["res://.godot/imported/graphics.svg-729676a50632ecc9e2d42273d703f3ea.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Andrew Wilkes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scenes/sequence.gd: -------------------------------------------------------------------------------- 1 | extends PopupPanel 2 | 3 | signal play_sequence(seq, repeat) 4 | 5 | func _ready(): 6 | for node in $M/VB.get_children(): 7 | if node is HBoxContainer: 8 | var button: Button = node.get_child(1) 9 | button.connect("pressed", play.bind(node)) 10 | 11 | 12 | func play(node): 13 | hide() 14 | emit_signal("play_sequence", get_sequence(node), $M/VB/Repeat.button_pressed) 15 | 16 | 17 | func get_sequence(node): 18 | return parse_sequence(get_sequence_string(node)) 19 | 20 | 21 | func get_sequence_string(node): 22 | var seq = "" 23 | if node.get_children().size() > 1: 24 | var txt: String = node.get_child(0).text 25 | # Get last line of text 26 | seq = txt.split("\n")[-1].to_upper() 27 | return seq 28 | 29 | 30 | func parse_sequence(txt): 31 | var seq = [] 32 | var length = txt.length() 33 | var idx = 0 34 | while idx < length: 35 | var dir = 1 36 | var face = "ULFRDB".find(txt[idx]) 37 | idx += 1 38 | if face < 0: 39 | continue 40 | if idx < length: 41 | if txt[idx] == "2": 42 | seq.append([face, dir]) 43 | idx += 1 44 | elif txt[idx] == "'": 45 | dir = -1 46 | idx += 1 47 | seq.append([face, dir]) 48 | return seq 49 | 50 | 51 | func _on_close_pressed(): 52 | hide() 53 | -------------------------------------------------------------------------------- /scenes/sequence.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://0djlny363s4d"] 2 | 3 | [ext_resource type="Script" path="res://scenes/sequence.gd" id="1_yc8bs"] 4 | 5 | [node name="Sequence" type="PopupPanel"] 6 | size = Vector2i(300, 250) 7 | visible = true 8 | script = ExtResource("1_yc8bs") 9 | 10 | [node name="M" type="MarginContainer" parent="."] 11 | offset_left = 4.0 12 | offset_top = 4.0 13 | offset_right = 296.0 14 | offset_bottom = 246.0 15 | theme_override_constants/margin_left = 21 16 | theme_override_constants/margin_top = 21 17 | theme_override_constants/margin_right = 21 18 | theme_override_constants/margin_bottom = 21 19 | 20 | [node name="VB" type="VBoxContainer" parent="M"] 21 | layout_mode = 2 22 | theme_override_constants/separation = 10 23 | 24 | [node name="S1" type="HBoxContainer" parent="M/VB"] 25 | layout_mode = 2 26 | theme_override_constants/separation = 10 27 | 28 | [node name="Label" type="Label" parent="M/VB/S1"] 29 | layout_mode = 2 30 | size_flags_horizontal = 3 31 | text = "Order 1260 32 | RU2D'BD'" 33 | 34 | [node name="Play" type="Button" parent="M/VB/S1"] 35 | layout_mode = 2 36 | text = "Play" 37 | 38 | [node name="S2" type="HBoxContainer" parent="M/VB"] 39 | layout_mode = 2 40 | theme_override_constants/separation = 10 41 | 42 | [node name="Custom" type="LineEdit" parent="M/VB/S2"] 43 | layout_mode = 2 44 | size_flags_horizontal = 3 45 | placeholder_text = "Custom sequence" 46 | 47 | [node name="Play" type="Button" parent="M/VB/S2"] 48 | layout_mode = 2 49 | text = "Play" 50 | 51 | [node name="Spacer" type="Control" parent="M/VB"] 52 | layout_mode = 2 53 | 54 | [node name="Repeat" type="CheckBox" parent="M/VB"] 55 | layout_mode = 2 56 | text = "Repeat until solved" 57 | 58 | [node name="Close" type="Button" parent="M/VB"] 59 | layout_mode = 2 60 | text = "Close" 61 | 62 | [connection signal="pressed" from="M/VB/Close" to="." method="_on_close_pressed"] 63 | -------------------------------------------------------------------------------- /scenes/orbiting_camera.gd: -------------------------------------------------------------------------------- 1 | extends Marker3D 2 | 3 | signal rotation_complete 4 | 5 | @export var ROTATION_SPEED = 0.01 6 | @export var PANNING_SPEED = 0.05 7 | @export var ZOOMING_SPEED = 0.05 8 | 9 | enum { ROTATING, PANNING, ZOOMING } 10 | 11 | var moving = false 12 | var rotating = false 13 | var amount 14 | var from_q 15 | var to_q 16 | var speed = 3 17 | 18 | func _process(delta): 19 | delta *= (speed + 1) 20 | var b = transform.basis 21 | if (Input.is_key_pressed(KEY_DOWN)): 22 | rotate(b.x.normalized(), delta) 23 | if (Input.is_key_pressed(KEY_UP)): 24 | rotate(b.x.normalized(), -delta) 25 | if (Input.is_key_pressed(KEY_RIGHT)): 26 | rotate(b.y.normalized(), delta) 27 | if (Input.is_key_pressed(KEY_LEFT)): 28 | rotate(b.y.normalized(), -delta) 29 | if (Input.is_key_pressed(KEY_Z)): 30 | $Camera.position.z += delta 31 | if (Input.is_key_pressed(KEY_X)): 32 | $Camera.position.z -= delta 33 | if rotating: 34 | transform.basis = Basis(to_q.slerp(from_q, amount / 100.0)) 35 | amount -= delta * 30 36 | if amount < 0.1: 37 | transform.basis = Basis(to_q) 38 | rotating = false 39 | emit_signal("rotation_complete") 40 | 41 | 42 | func _unhandled_input(event): 43 | if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: 44 | if event.pressed: 45 | moving = true 46 | else: 47 | moving = false 48 | if event is InputEventMouseMotion and moving: 49 | var mode = ROTATING 50 | if Input.is_key_pressed(KEY_CTRL): 51 | mode = ZOOMING 52 | match mode: 53 | ROTATING: 54 | var b = transform.basis 55 | rotate(b.x.normalized(), event.relative.y * ROTATION_SPEED) 56 | rotate(b.y.normalized(), event.relative.x * ROTATION_SPEED) 57 | ZOOMING: 58 | $Camera.position.z += event.relative.y * ZOOMING_SPEED 59 | 60 | 61 | func rotate_to_face(vec: Vector2): 62 | rotating = true 63 | amount = 100.0 64 | from_q = Quaternion(transform.basis.orthonormalized()) 65 | var node = Node3D.new() 66 | node.rotate_x(PI/2 * vec.x) 67 | node.rotate_y(PI/2 * vec.y) 68 | to_q = Quaternion(node.transform.basis) 69 | -------------------------------------------------------------------------------- /scenes/color_map.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://dfknx5u3yhl8y"] 2 | 3 | [ext_resource type="Script" path="res://scenes/color_map.gd" id="1_ln6vs"] 4 | [ext_resource type="Texture2D" uid="uid://bvpm6eq6ugoog" path="res://scenes/black-button-small.png" id="2_s5l0w"] 5 | 6 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_s3ton"] 7 | bg_color = Color(0, 0, 0, 1) 8 | corner_radius_top_left = 5 9 | corner_radius_top_right = 5 10 | corner_radius_bottom_right = 5 11 | corner_radius_bottom_left = 5 12 | 13 | [node name="ColorMap" type="PanelContainer"] 14 | anchors_preset = 15 15 | anchor_right = 1.0 16 | anchor_bottom = 1.0 17 | grow_horizontal = 2 18 | grow_vertical = 2 19 | theme_override_styles/panel = SubResource("StyleBoxFlat_s3ton") 20 | script = ExtResource("1_ln6vs") 21 | 22 | [node name="Grid" type="GridContainer" parent="."] 23 | layout_mode = 2 24 | columns = 3 25 | 26 | [node name="Sp1" type="Control" parent="Grid"] 27 | layout_mode = 2 28 | 29 | [node name="U" type="GridContainer" parent="Grid"] 30 | layout_mode = 2 31 | theme_override_constants/h_separation = 2 32 | theme_override_constants/v_separation = 2 33 | columns = 3 34 | 35 | [node name="Sp2" type="Control" parent="Grid"] 36 | layout_mode = 2 37 | 38 | [node name="L" type="GridContainer" parent="Grid"] 39 | layout_mode = 2 40 | theme_override_constants/h_separation = 2 41 | theme_override_constants/v_separation = 2 42 | columns = 3 43 | 44 | [node name="F" type="GridContainer" parent="Grid"] 45 | layout_mode = 2 46 | theme_override_constants/h_separation = 2 47 | theme_override_constants/v_separation = 2 48 | columns = 3 49 | 50 | [node name="R" type="GridContainer" parent="Grid"] 51 | layout_mode = 2 52 | theme_override_constants/h_separation = 2 53 | theme_override_constants/v_separation = 2 54 | columns = 3 55 | 56 | [node name="Sp3" type="Control" parent="Grid"] 57 | layout_mode = 2 58 | 59 | [node name="D" type="GridContainer" parent="Grid"] 60 | layout_mode = 2 61 | theme_override_constants/h_separation = 2 62 | theme_override_constants/v_separation = 2 63 | columns = 3 64 | 65 | [node name="Sp4" type="Control" parent="Grid"] 66 | layout_mode = 2 67 | 68 | [node name="Coffee" type="Button" parent="Grid"] 69 | layout_mode = 2 70 | icon = ExtResource("2_s5l0w") 71 | 72 | [node name="B" type="GridContainer" parent="Grid"] 73 | layout_mode = 2 74 | theme_override_constants/h_separation = 2 75 | theme_override_constants/v_separation = 2 76 | columns = 3 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cubic 2 | Godot 4 Rubik Cube Simulator 3 | 4 | ![Cube](media/cube1.png) 5 | 6 | Downloads: [Cubic - Rubik Cube Simulator on Itch.io](https://andrew-wilkes.itch.io/cubic) 7 | 8 | ![Latest](media/screenshot.png) 9 | 10 | ## Camera Control 11 | 12 | Move around the cube by holding the left mouse button and moving the mouse. 13 | 14 | The camera is rotated around its x-axis followed by its y-axis. 15 | 16 | Hold down the CTRL key and left mouse button to zoom when moving the mouse. 17 | 18 | ## UI Control Panel 19 | 20 | ![Control Panel](media/control-panel.png) 21 | 22 | The various faces of the cube are related as per the forward view on the screen. So the side to the left is the left side of the cube whatever the rotation of the camera view. So the L button rotates the left side of the cube. 23 | 24 | Left-click a button to rotate the related face of the cube by 90 degrees in the clockwise direction. 25 | 26 | Shift-left-click to rotate the face in the counter-clockwise direction. Or right-click the button. 27 | 28 | Ctrl-click on a face button to make the camera look at that face. 29 | 30 | The *Reset* button restores the faces of the cube to the *solved* positions. 31 | 32 | The *Scramble* button causes the cube faces to be rotated randomly to scramble the puzzle. 33 | 34 | The *Solve* button initiates a solving animation that may be stepped through, played, paused or stopped to learn how to solve a cube puzzle. 35 | 36 | Various buttons are provided to play the moves associated with the *beginner* solving method by layers. 37 | 38 | The *Run Sequence* button opens a panel that lists some sequences of moves that may be played. 39 | 40 | The slider adjusts the playback speed of animations, and next to that is a button to open a background color selector panel. 41 | 42 | ## Color Map 43 | 44 | This will be used to edit and track the state of the faces of the cube. For example: to copy the state of a physical Rubik Cube in order to run the solver on it. Or to copy the image for reference. 45 | 46 | ![Color Map](media/color-map.png) 47 | 48 | Click on a cell (it is shaded darker), then click on another cell to try to move the first cell to the new position. If the move is valid, other cells will also be updated to reflect the changed positions of the puzzle pieces. Note that it's possible to set up impossible cube configurations in a similar manner to manually repositioning a corner in place. 49 | 50 | To toggle greying out a piece, shift-click on a face tile. 51 | 52 | ## Coordinate System 53 | 54 | This is used to assign index values to the various faces, edges, and corners for use when programming the code. 55 | 56 | ![coordinates](media/coordinates.png) 57 | -------------------------------------------------------------------------------- /licences.txt: -------------------------------------------------------------------------------- 1 | Licence Information 2 | 3 | This game uses Godot Engine, available under the following license: 4 | 5 | Copyright (c) 2014-present Godot Engine contributors. Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | 13 | -------- 14 | FreeType 15 | 16 | Portions of this software are copyright © 2021 The FreeType Project (www.freetype.org). All rights reserved. 17 | 18 | -------- 19 | ENet 20 | Godot includes the ENet library to handle high-level multiplayer. 21 | 22 | Copyright (c) 2002-2020 Lee Salzman 23 | 24 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | --------- 31 | MBedTLS 32 | 33 | For using SSL (usually through HTTP requests), the MBedTLS Apache license: 34 | 35 | Copyright The Mbed TLS Contributors 36 | 37 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 38 | 39 | http://www.apache.org/licenses/LICENSE-2.0 40 | 41 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 42 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /scenes/cube.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=15 format=3 uid="uid://3jtj0nkocrsc"] 2 | 3 | [ext_resource type="Script" path="res://scenes/cube.gd" id="1_b7lff"] 4 | 5 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_o04uo"] 6 | albedo_color = Color(0, 0, 0, 1) 7 | metallic_specular = 1.0 8 | roughness = 0.0 9 | 10 | [sub_resource type="Shader" id="Shader_kkp6o"] 11 | code = "shader_type spatial; 12 | 13 | uniform vec3 color: source_color; 14 | uniform vec3 grey_color: source_color; 15 | uniform bool grey = false; 16 | 17 | void fragment() { 18 | if (grey) { 19 | ALBEDO = grey_color; 20 | } else { 21 | ALBEDO = color; 22 | } 23 | } 24 | " 25 | 26 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_ch7rs"] 27 | resource_local_to_scene = true 28 | render_priority = 0 29 | shader = SubResource("Shader_kkp6o") 30 | shader_parameter/color = Color(1, 1, 0, 1) 31 | shader_parameter/grey_color = Color(0.152941, 0.152941, 0.152941, 1) 32 | shader_parameter/grey = false 33 | 34 | [sub_resource type="Shader" id="Shader_go3hg"] 35 | code = "shader_type spatial; 36 | 37 | uniform vec3 color: source_color; 38 | uniform vec3 grey_color: source_color; 39 | uniform bool grey = false; 40 | 41 | void fragment() { 42 | if (grey) { 43 | ALBEDO = grey_color; 44 | } else { 45 | ALBEDO = color; 46 | } 47 | } 48 | " 49 | 50 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_k5jdc"] 51 | render_priority = 0 52 | shader = SubResource("Shader_go3hg") 53 | shader_parameter/color = Color(1, 0, 0, 1) 54 | shader_parameter/grey_color = Color(0.152941, 0.152941, 0.152941, 1) 55 | shader_parameter/grey = false 56 | 57 | [sub_resource type="Shader" id="Shader_1a2nl"] 58 | code = "shader_type spatial; 59 | 60 | uniform vec3 color: source_color; 61 | uniform vec3 grey_color: source_color; 62 | uniform bool grey = false; 63 | 64 | void fragment() { 65 | if (grey) { 66 | ALBEDO = grey_color; 67 | } else { 68 | ALBEDO = color; 69 | } 70 | } 71 | " 72 | 73 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_87eps"] 74 | render_priority = 0 75 | shader = SubResource("Shader_1a2nl") 76 | shader_parameter/color = Color(0, 1, 0, 1) 77 | shader_parameter/grey_color = Color(0.152941, 0.152941, 0.152941, 1) 78 | shader_parameter/grey = false 79 | 80 | [sub_resource type="Shader" id="Shader_qvb0n"] 81 | code = "shader_type spatial; 82 | 83 | uniform vec3 color: source_color; 84 | uniform vec3 grey_color: source_color; 85 | uniform bool grey = false; 86 | 87 | void fragment() { 88 | if (grey) { 89 | ALBEDO = grey_color; 90 | } else { 91 | ALBEDO = color; 92 | } 93 | } 94 | " 95 | 96 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_ximad"] 97 | render_priority = 0 98 | shader = SubResource("Shader_qvb0n") 99 | shader_parameter/color = Color(1, 0.270588, 0, 1) 100 | shader_parameter/grey_color = Color(0.152941, 0.152941, 0.152941, 1) 101 | shader_parameter/grey = false 102 | 103 | [sub_resource type="Shader" id="Shader_seqtf"] 104 | code = "shader_type spatial; 105 | 106 | uniform vec3 color: source_color; 107 | uniform vec3 grey_color: source_color; 108 | uniform bool grey = false; 109 | 110 | void fragment() { 111 | if (grey) { 112 | ALBEDO = grey_color; 113 | } else { 114 | ALBEDO = color; 115 | } 116 | } 117 | " 118 | 119 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_3jo01"] 120 | render_priority = 0 121 | shader = SubResource("Shader_seqtf") 122 | shader_parameter/color = Color(1, 1, 1, 1) 123 | shader_parameter/grey_color = Color(0.152941, 0.152941, 0.152941, 1) 124 | shader_parameter/grey = false 125 | 126 | [sub_resource type="Shader" id="Shader_y40kv"] 127 | code = "shader_type spatial; 128 | 129 | uniform vec3 color: source_color; 130 | uniform vec3 grey_color: source_color; 131 | uniform bool grey = false; 132 | 133 | void fragment() { 134 | if (grey) { 135 | ALBEDO = grey_color; 136 | } else { 137 | ALBEDO = color; 138 | } 139 | } 140 | " 141 | 142 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_ooyp5"] 143 | render_priority = 0 144 | shader = SubResource("Shader_y40kv") 145 | shader_parameter/color = Color(0, 0, 1, 1) 146 | shader_parameter/grey_color = Color(0.152941, 0.152941, 0.152941, 1) 147 | shader_parameter/grey = false 148 | 149 | [node name="Cube" type="CSGBox3D"] 150 | rotation_edit_mode = 2 151 | material = SubResource("StandardMaterial3D_o04uo") 152 | script = ExtResource("1_b7lff") 153 | 154 | [node name="Up" type="CSGBox3D" parent="."] 155 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.53, 0) 156 | size = Vector3(0.8, 0.06, 0.8) 157 | material = SubResource("ShaderMaterial_ch7rs") 158 | 159 | [node name="Left" type="CSGBox3D" parent="."] 160 | transform = Transform3D(-4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, 0, 1, -0.53, 0, 0) 161 | size = Vector3(0.8, 0.06, 0.8) 162 | material = SubResource("ShaderMaterial_k5jdc") 163 | 164 | [node name="Front" type="CSGBox3D" parent="."] 165 | transform = Transform3D(1, 0, 0, 0, -0.00567235, 0.999984, 0, -0.999984, -0.00567235, 0, 0, 0.53) 166 | size = Vector3(0.8, 0.06, 0.8) 167 | material = SubResource("ShaderMaterial_87eps") 168 | 169 | [node name="Right" type="CSGBox3D" parent="."] 170 | transform = Transform3D(-4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, 0, 1, 0.53, 0, 0) 171 | size = Vector3(0.8, 0.06, 0.8) 172 | material = SubResource("ShaderMaterial_ximad") 173 | 174 | [node name="Down" type="CSGBox3D" parent="."] 175 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.53, 0) 176 | size = Vector3(0.8, 0.06, 0.8) 177 | material = SubResource("ShaderMaterial_3jo01") 178 | 179 | [node name="Back" type="CSGBox3D" parent="."] 180 | transform = Transform3D(1.91069e-15, 4.37114e-08, 1, 1, -4.37114e-08, 0, 4.37114e-08, 1, -4.37114e-08, 0, 0, -0.53) 181 | size = Vector3(0.8, 0.06, 0.8) 182 | material = SubResource("ShaderMaterial_ooyp5") 183 | 184 | [node name="Cubes" type="Node3D" parent="."] 185 | -------------------------------------------------------------------------------- /scenes/color_map.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | signal map_changed 4 | 5 | const COLORS = [Color.YELLOW, Color.RED, Color.GREEN, Color.ORANGE_RED, Color.WHITE, Color.BLUE, Color.DARK_GRAY] 6 | 7 | # See coordinates diagram for index relationships in media folder 8 | # The _FACE_MAP constants are indexing the faces 9 | # Copies of the FACE_MAP constants are indexing the changing colors on the faces of the pieces 10 | const CORNER_FACE_MAP = [[0,1,5],[0,5,3],[0,2,1],[0,3,2],[5,1,4],[1,2,4],[2,3,4],[3,5,4]] 11 | const EDGE_FACE_MAP = [[5,0],[1,0],[3,0],[2,0],[5,1],[1,2],[3,2],[3,5],[1,4],[2,4],[3,4],[5,4]] 12 | # FACE_ maps contain a list of edge/corner indices of parts on a face 13 | const FACE_CORNER_MAP = [[0,1,2,3],[0,2,4,5],[2,3,5,6],[3,1,6,7],[5,6,4,7],[4,7,0,1]] 14 | const FACE_EDGE_MAP = [[0,1,2,3],[1,4,5,8],[3,5,6,9],[2,6,7,10],[9,8,10,11],[11,4,7,0]] 15 | # This indexes the positions of faces under the Grid node 16 | const GRID_FACE_MAP = [1,3,4,5,7,10] 17 | const BLACK_IDX = 6 18 | 19 | var source_tile = null 20 | var edges = [] # 12 sets of 2 colors 21 | var corners = [] # 8 sets of 3 colors 22 | var center_visibility = [] 23 | var edge_visibility = [] 24 | var corner_visibility = [] 25 | var solving = false 26 | 27 | func _ready(): 28 | # Set up the tiles 29 | var tile = ColorRect.new() 30 | tile.custom_minimum_size = Vector2(32, 32) 31 | var idx = 0 32 | for node in $Grid.get_children(): 33 | if node is GridContainer: 34 | for n in 9: 35 | var new_tile = tile.duplicate() 36 | new_tile.set_meta("id", { "face": idx, "tile": n }) 37 | # immovable middle cell when n == 4 38 | new_tile.gui_input.connect(handle_click.bind(new_tile, n != 4)) 39 | node.add_child(new_tile) 40 | idx += 1 41 | reset() 42 | 43 | 44 | func reset(): 45 | # Need deep copy so that inner arrays may be modified 46 | edges = EDGE_FACE_MAP.duplicate(true) 47 | corners = CORNER_FACE_MAP.duplicate(true) 48 | center_visibility.resize(6) 49 | center_visibility.fill(true) 50 | edge_visibility.resize(12) 51 | edge_visibility.fill(true) 52 | corner_visibility.resize(8) 53 | corner_visibility.fill(true) 54 | var color_idx = 0 55 | for node in $Grid.get_children(): 56 | if node is GridContainer: 57 | for tile in node.get_children(): 58 | tile.color = COLORS[color_idx] 59 | color_idx += 1 60 | 61 | 62 | func solved(): 63 | return edges.hash() == EDGE_FACE_MAP.hash() && corners.hash() == CORNER_FACE_MAP.hash() 64 | 65 | 66 | func handle_click(ev: InputEvent, clicked_tile, moveable): 67 | if ev is InputEventMouseButton and ev.pressed and not solving: 68 | # If shift is pressed, toggle the visibility of the tile 69 | if Input.is_key_pressed(KEY_SHIFT) or Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT): 70 | var id = clicked_tile.get_meta("id") 71 | if is_edge(id.tile): 72 | var edge_idx = get_edge(id.face, id.tile) 73 | edge_visibility[edge_idx] = !edge_visibility[edge_idx] 74 | set_edge_color(edge_idx, 0) 75 | set_edge_color(edge_idx, 1) 76 | elif is_corner(id.tile): 77 | var corner_idx = get_corner(id.face, id.tile) 78 | corner_visibility[corner_idx] = !corner_visibility[corner_idx] 79 | for n in 3: 80 | set_corner_color(corner_idx, n) 81 | else: 82 | center_visibility[id.face] = !center_visibility[id.face] 83 | var color_idx = id.face if center_visibility[id.face] else BLACK_IDX 84 | set_tile_color(id.face, id.tile, color_idx) 85 | emit_signal("map_changed") 86 | elif moveable: 87 | if source_tile: 88 | source_tile.color = COLORS[source_tile.get_meta("id").face] 89 | try_move(source_tile, clicked_tile) 90 | source_tile = null 91 | else: 92 | source_tile = clicked_tile 93 | source_tile.color = clicked_tile.color.darkened(0.2) 94 | 95 | 96 | func try_move(from_tile, to_tile): 97 | var from = from_tile.get_meta("id") 98 | var to = to_tile.get_meta("id") 99 | if from == to: return 100 | # if moving in same group of cells then rotating the colors 101 | # corners move to corners 102 | # edges move to edges 103 | # middle cells can't be moved 104 | if is_edge(from.tile) and is_edge(to.tile): 105 | var edge1 = get_edge(from.face, from.tile) 106 | var edge2 = get_edge(to.face, to.tile) 107 | if edge1 == edge2: 108 | # swap colors 109 | edges[edge1].reverse() 110 | for face_idx in 2: 111 | set_edge_color(edge1, face_idx) 112 | else: 113 | # Want from.face tile to move to to.face tile for a natural feel 114 | if EDGE_FACE_MAP[edge1].find(from.face) != EDGE_FACE_MAP[edge2].find(to.face): 115 | # swap colors 116 | edges[edge1].reverse() 117 | # swap edges 118 | var edge1_colors = edges[edge1] 119 | edges[edge1] = edges[edge2] 120 | edges[edge2] = edge1_colors 121 | for face_idx in 2: 122 | set_edge_color(edge1, face_idx) 123 | set_edge_color(edge2, face_idx) 124 | if is_corner(from.tile) and is_corner(to.tile): 125 | var corner1 = get_corner(from.face, from.tile) 126 | var corner2 = get_corner(to.face, to.tile) 127 | if corner1 == corner2: 128 | # rotate colors 129 | corners[corner1] = get_rotated_faces(corner1, from.face, corner1, to.face) 130 | for face_idx in 3: 131 | set_corner_color(corner1, face_idx) 132 | else: 133 | # swap corners 134 | var rotated_corner1 = get_rotated_faces(corner1, from.face, corner2, to.face) 135 | corners[corner1] = corners[corner2] 136 | corners[corner2] = rotated_corner1 137 | for face_idx in 3: 138 | set_corner_color(corner1, face_idx) 139 | set_corner_color(corner2, face_idx) 140 | emit_signal("map_changed") 141 | 142 | 143 | func get_rotated_faces(corner1, from_idx, corner2, to_idx): 144 | var rot_step = wrapi(CORNER_FACE_MAP[corner1].find(from_idx) - CORNER_FACE_MAP[corner2].find(to_idx), -1, 2) 145 | return [corners[corner1][rot_step], corners[corner1][rot_step + 1], corners[corner1][wrapi(rot_step + 2, 0, 3)]] 146 | 147 | 148 | func is_edge(n): 149 | return n % 2 == 1 # Odd number 150 | 151 | 152 | func is_corner(n): 153 | return n in [0,2,6,8] 154 | 155 | 156 | func get_edge(face, n): 157 | return FACE_EDGE_MAP[face][n / 2] 158 | 159 | 160 | func get_corner(face, n): 161 | return FACE_CORNER_MAP[face][(n + 1) / 3] 162 | 163 | 164 | func set_edge_color(edge_idx, face_idx): 165 | # Get the face of the grid 166 | var face = EDGE_FACE_MAP[edge_idx][face_idx] 167 | # Get the tile idx within the face 168 | var tile = FACE_EDGE_MAP[face].find(edge_idx) * 2 + 1 169 | var color = edges[edge_idx][face_idx] if edge_visibility[edge_idx] else BLACK_IDX 170 | set_tile_color(face, tile, color) 171 | 172 | 173 | func set_corner_color(corner_idx, face_idx): 174 | var face = CORNER_FACE_MAP[corner_idx][face_idx] 175 | var tile = FACE_CORNER_MAP[face].find(corner_idx) * 3 / 2 * 2 176 | var color = corners[corner_idx][face_idx] if corner_visibility[corner_idx] else BLACK_IDX 177 | set_tile_color(face, tile, color) 178 | 179 | 180 | func set_tile_color(face_idx, tile_idx, color_idx): 181 | $Grid.get_child(GRID_FACE_MAP[face_idx]).get_child(tile_idx).color = COLORS[color_idx] 182 | 183 | 184 | # Input array of arrays of 6 cube face colors 185 | func set_edge_colors(edge_cube_colors, _edge_visibility): 186 | edge_visibility = _edge_visibility 187 | var idx = 0 188 | for ec in edge_cube_colors: 189 | # We want to extract the 2 face colors of the edge from the 6 faces of the cube 190 | edges[idx] = [] 191 | for n in 2: 192 | edges[idx].append(ec[EDGE_FACE_MAP[idx][n]]) 193 | set_edge_color(idx, n) 194 | idx += 1 195 | 196 | 197 | # Input array of arrays of 6 cube face colors 198 | func set_corner_colors(corner_cube_colors, _corner_visibility): 199 | corner_visibility = _corner_visibility 200 | var idx = 0 201 | for cc in corner_cube_colors: 202 | corners[idx] = [] 203 | # Extract 3 face colors for the corner 204 | for n in 3: 205 | corners[idx].append(cc[CORNER_FACE_MAP[idx][n]]) 206 | set_corner_color(idx, n) 207 | idx += 1 208 | 209 | 210 | # Search for the matching sets of colors to identify the edges 211 | # The index of the initial edge position is stored in the new position 212 | func get_edge_positions(): 213 | var positions = [] 214 | for n in 12: 215 | var cols = edges[n] 216 | positions.append(get_edge_position(cols)) 217 | return positions 218 | 219 | 220 | func get_edge_position(cols): 221 | if !EDGE_FACE_MAP.has(cols): 222 | cols = [cols[1], cols[0]] 223 | return EDGE_FACE_MAP.find(cols) 224 | 225 | 226 | func get_corner_positions(): 227 | var positions = [] 228 | for n in 8: 229 | var cols = corners[n] 230 | if !CORNER_FACE_MAP.has(cols): 231 | cols = [cols[2], cols[0], cols[1]] 232 | if !CORNER_FACE_MAP.has(cols): 233 | cols = [cols[2], cols[0], cols[1]] 234 | positions.append(CORNER_FACE_MAP.find(cols)) 235 | return positions 236 | 237 | 238 | func get_data(): 239 | return { 240 | "edge_positions": get_edge_positions(), 241 | "edge_colors": edges, 242 | "edge_face_map": EDGE_FACE_MAP, 243 | "corner_positions": get_corner_positions(), 244 | "corner_colors": corners, 245 | "corner_face_map": CORNER_FACE_MAP, 246 | "center_visibility": center_visibility, 247 | "edge_visibility": edge_visibility, 248 | "corner_visibility": corner_visibility 249 | } 250 | -------------------------------------------------------------------------------- /scenes/big_cube.gd: -------------------------------------------------------------------------------- 1 | extends Node3D 2 | 3 | class_name BigCube 4 | 5 | signal rotation_complete 6 | 7 | enum FACES { UP, LEFT, FRONT, RIGHT, DOWN, BACK } 8 | 9 | const PIVOT_POSITIONS = [ 10 | Vector3.UP, 11 | Vector3.LEFT, 12 | Vector3.BACK, 13 | Vector3.RIGHT, 14 | Vector3.DOWN, 15 | Vector3.FORWARD 16 | ] 17 | const INITIAL_FACE_MAP = [ 18 | FACES.UP, 19 | FACES.LEFT, 20 | FACES.FRONT, 21 | FACES.RIGHT, 22 | FACES.DOWN, 23 | FACES.BACK 24 | ] 25 | const CUBE_IDXS = [-1, 0, 1] 26 | const UPPERV = CUBE_IDXS[-1] 27 | const LOWERV = CUBE_IDXS[0] 28 | const X_AXIS_FACES = [FACES.UP, FACES.FRONT, FACES.DOWN, FACES.BACK] 29 | const Y_AXIS_FACES = [FACES.LEFT, FACES.FRONT, FACES.RIGHT, FACES.BACK] 30 | 31 | const EDGE_POSITIONS = [[0,1,-1],[-1,1,0],[1,1,0],[0,1,1],[-1,0,-1],[-1,0,1],[1,0,1],[1,0,-1],[-1,-1,0],[0,-1,1],[1,-1, 0],[0,-1,-1]] 32 | const CORNER_POSITIONS = [[-1,1,-1],[1,1,-1],[-1,1,1],[1,1,1],[-1,-1,-1],[-1,-1,1],[1,-1,1],[1,-1,-1]] 33 | const CENTER_POSITIONS = [[0,1,0],[-1,0,0],[0,0,1],[1,0,0],[0,-1,0],[0,0,-1]] 34 | 35 | const ROTATIONS = [0, 1, 2, -1] 36 | 37 | const PI2 = PI / 2 38 | 39 | @export var rotation_speed = 3.0 40 | 41 | var piece = preload("res://scenes/cube.tscn") 42 | var pivot = self 43 | 44 | # Keep a record of which of the 4 rotation positions each face is at 45 | var face_rotations = [0, 0, 0, 0, 0, 0] 46 | var face_rotating_idx := -1 47 | var face_rotation_angle = 0 48 | var face_rotation_direction := 1 49 | var rotation_dict = {} 50 | var faces_dict = {} 51 | var last_face_key = 0 52 | var edge_visibility = [] 53 | var corner_visibility = [] 54 | 55 | func _ready(): 56 | var cube_instance: Node3D = piece.instantiate() 57 | fill_rotation_dict(cube_instance) 58 | last_face_key = faces_dict.keys()[0] 59 | build_cube(cube_instance) 60 | edge_visibility.resize(12) 61 | corner_visibility.resize(8) 62 | reset() 63 | 64 | 65 | func build_cube(cube_instance): 66 | for x in CUBE_IDXS: 67 | for y in CUBE_IDXS: 68 | for z in CUBE_IDXS: 69 | var cube = cube_instance.duplicate() 70 | cube.position = Vector3(x, y, z) 71 | cube.set_meta("initial_position", cube.position) # Useful when resetting 72 | add_child(cube) 73 | # Set face visibility 74 | cube.get_child(FACES.UP).visible = y == UPPERV 75 | cube.get_child(FACES.DOWN).visible = y == LOWERV 76 | cube.get_child(FACES.RIGHT).visible = x == UPPERV 77 | cube.get_child(FACES.LEFT).visible = x == LOWERV 78 | cube.get_child(FACES.FRONT).visible = z == UPPERV 79 | cube.get_child(FACES.BACK).visible = z == LOWERV 80 | 81 | 82 | func reset(): 83 | face_rotations = [0, 0, 0, 0, 0, 0] 84 | for cube in get_children(): 85 | if cube is CSGBox3D: 86 | cube.transform.basis = Basis() 87 | cube.position = cube.get_meta("initial_position") 88 | cube.set_grey(false) 89 | 90 | 91 | # Animate the cube group rotation 92 | func _process(delta): 93 | if face_rotating_idx > -1: 94 | var offset = face_rotations[face_rotating_idx] 95 | face_rotation_angle = clamp(face_rotation_angle + delta * rotation_speed, 0, PI2) 96 | rotate_group(face_rotating_idx, face_rotation_angle * face_rotation_direction + offset * PI2) 97 | if face_rotation_angle >= PI2: 98 | # Rotation complete 99 | face_rotation_angle = 0 100 | face_rotations[face_rotating_idx] = (offset + face_rotation_direction) % 4 101 | face_rotating_idx = -1 102 | reparent_to_origin() 103 | emit_signal("rotation_complete") 104 | 105 | 106 | func rotate_faces_in_map(old_map, faces, offset): 107 | var new_map = old_map.duplicate() 108 | if offset > 0: 109 | for idx in faces.size(): 110 | new_map[faces[idx]] = old_map[faces[(idx + offset) % 4]] 111 | return new_map 112 | 113 | 114 | func rotate_face(idx, dir, bas): 115 | if face_rotating_idx < 0: 116 | var face_map = get_face_map_from_basis(bas) 117 | idx = face_map[idx] 118 | var group = get_group(idx) 119 | reparent_to_pivot(group) 120 | face_rotating_idx = idx 121 | face_rotation_direction = get_correct_dir(idx, dir) 122 | 123 | 124 | func get_correct_dir(idx, dir): 125 | # Correct the rotation direction according to the front face orientation 126 | return dir if idx in [FACES.BACK, FACES.DOWN, FACES.LEFT] else -dir 127 | 128 | 129 | func rotate_face_immediate(idx, dir): 130 | dir = get_correct_dir(idx, dir) 131 | var group = get_group(idx) 132 | reparent_to_pivot(group) 133 | var offset = dir + face_rotations[idx] 134 | rotate_group(idx, offset * PI2) 135 | reparent_to_origin() 136 | face_rotations[idx] = offset % 4 137 | 138 | 139 | func rotate_group(idx, rot_angle): 140 | pivot.transform.basis = Basis() # reset rotation 141 | match idx: 142 | FACES.LEFT, FACES.RIGHT: 143 | pivot.rotate_x(rot_angle) 144 | FACES.UP, FACES.DOWN: 145 | pivot.rotate_y(rot_angle) 146 | FACES.FRONT, FACES.BACK: 147 | pivot.rotate_z(rot_angle) 148 | 149 | 150 | func get_group(idx): 151 | var group = [] 152 | for cube in get_children(): 153 | if PIVOT_POSITIONS[idx] == cube.position: 154 | pivot = cube 155 | # Using the DOT product here to find cubes on the same face as the pivot cube 156 | if cube.position.dot(PIVOT_POSITIONS[idx]) > 0.5: 157 | group.append(cube) 158 | return group 159 | 160 | 161 | func reparent_to_pivot(group): 162 | var node = pivot.get_node("Cubes") 163 | for cube in group: 164 | if cube != pivot: 165 | cube.reparent(node) 166 | 167 | 168 | func reparent_to_origin(): 169 | if pivot != self: 170 | # Reparent previously rotated child cubes to self 171 | for cube in pivot.get_node("Cubes").get_children(): 172 | cube.reparent(self) 173 | 174 | ################################################### 175 | # Functions to use with mapping colors to/from cube 176 | 177 | func get_edge_colors(): 178 | var edge_colors = [] 179 | var idx = 0 180 | for node in get_edge_nodes(): 181 | edge_visibility[idx] = true if node.get_child(0).scale.x > 0.1 else false 182 | edge_colors.append(get_colors_of_cube(node)) 183 | idx += 1 184 | return edge_colors 185 | 186 | 187 | func get_corner_colors(): 188 | var corner_colors = [] 189 | var idx = 0 190 | for node in get_corner_nodes(): 191 | corner_visibility[idx] = true if node.get_child(0).scale.x > 0.1 else false 192 | corner_colors.append(get_colors_of_cube(node)) 193 | idx += 1 194 | return corner_colors 195 | 196 | 197 | func get_edge_nodes(): 198 | var edge_nodes = [] 199 | for pos in EDGE_POSITIONS: 200 | for node in get_children(): 201 | if (node.position - Vector3(pos[0], pos[1], pos[2])).length_squared() < 0.1: 202 | edge_nodes.append(node) 203 | return edge_nodes 204 | 205 | 206 | func get_corner_nodes(): 207 | var corner_nodes = [] 208 | for pos in CORNER_POSITIONS: 209 | for node in get_children(): 210 | if (node.position - Vector3(pos[0], pos[1], pos[2])).length_squared() < 0.1: 211 | corner_nodes.append(node) 212 | return corner_nodes 213 | 214 | 215 | func get_center_nodes(): 216 | var center_nodes = [] 217 | for pos in CENTER_POSITIONS: 218 | for node in get_children(): 219 | if (node.position - Vector3(pos[0], pos[1], pos[2])).length_squared() < 0.1: 220 | center_nodes.append(node) 221 | return center_nodes 222 | 223 | 224 | func get_colors_of_cube(node): 225 | var colors = [] 226 | for tile_idx in 6: 227 | var tile_pos = PIVOT_POSITIONS[tile_idx] 228 | for color_idx in 6: 229 | var pos = get_tile_position(node, color_idx) 230 | if pos.dot(tile_pos) > 0.5: 231 | colors.append(color_idx) 232 | return colors 233 | 234 | 235 | func get_tile_position(node, color_idx): 236 | return (node.get_child(color_idx).global_position - node.global_position).normalized() 237 | 238 | 239 | func set_edges(map_data): 240 | var idx = 0 241 | for node in get_edge_nodes(): 242 | # Find out where this node should be moved to 243 | var edge_idx = map_data.edge_positions.find(idx) 244 | var pos = EDGE_POSITIONS[edge_idx] 245 | node.position = Vector3(pos[0], pos[1], pos[2]) 246 | set_cube_face_visibility(node, map_data.edge_visibility[edge_idx]) 247 | # Rotate the cube to align the faces correctly 248 | rotate_cube(node, map_data.edge_face_map[edge_idx], map_data.edge_colors[edge_idx]) 249 | idx += 1 250 | 251 | 252 | func set_centers(map_data): 253 | var idx = 0 254 | for node in get_center_nodes(): 255 | set_cube_face_visibility(node, map_data.center_visibility[idx]) 256 | idx += 1 257 | 258 | 259 | func set_cube_face_visibility(cube, enable): 260 | cube.set_grey(not enable) 261 | 262 | 263 | func rotate_cube(node, face_map, tile_colors): 264 | var key = get_rotation_key(face_map, tile_colors) 265 | var xyz = rotation_dict[key] 266 | node.rotate_x(xyz[0] * PI2) 267 | node.rotate_y(xyz[1] * PI2) 268 | node.rotate_z(xyz[2] * PI2) 269 | 270 | 271 | func get_rotation_key(face_map, tile_colors): 272 | for key in rotation_dict.keys(): 273 | var found = true 274 | for idx in face_map.size(): 275 | if key[face_map[idx]] != tile_colors[idx]: 276 | found = false 277 | break # Try the next key 278 | if found: return key 279 | 280 | 281 | func set_corners(map_data): 282 | var idx = 0 283 | for node in get_corner_nodes(): 284 | var corner_idx = map_data.corner_positions.find(idx) 285 | var pos = CORNER_POSITIONS[corner_idx] 286 | node.position = Vector3(pos[0], pos[1], pos[2]) 287 | set_cube_face_visibility(node, map_data.corner_visibility[corner_idx]) 288 | rotate_cube(node, map_data.corner_face_map[corner_idx], map_data.corner_colors[corner_idx]) 289 | idx += 1 290 | 291 | 292 | func apply_map(map_data): 293 | reset() 294 | set_edges(map_data) 295 | set_corners(map_data) 296 | set_centers(map_data) 297 | 298 | 299 | func get_face_position(node, face_idx): 300 | var gp = node.get_child(face_idx).global_position 301 | var np = node.global_position 302 | var face_normal = (gp - np).normalized() 303 | for idx in 6: 304 | if face_normal.dot(PIVOT_POSITIONS[idx]) > 0.5: 305 | return idx 306 | 307 | 308 | # Make lookup dictionary of face orientations vs rotations (in XYZ order) 309 | # There are 24 possible permutations of cube faces 310 | func fill_rotation_dict(cube): 311 | add_child(cube) 312 | for z in ROTATIONS: 313 | for y in ROTATIONS: 314 | for x in ROTATIONS: 315 | var bcode = rotate_small_cube(cube, x, y, z) 316 | var faces = get_faces_of_rotated_cube(cube) 317 | if not rotation_dict.has(faces): 318 | rotation_dict[faces] = [x,y,z] 319 | faces_dict[bcode] = faces 320 | remove_child(cube) 321 | 322 | 323 | func rotate_small_cube(cube, x, y, z): 324 | cube.transform.basis = Basis() # reset rotation 325 | cube.rotate_x(x * PI2) 326 | cube.rotate_y(y * PI2) 327 | cube.rotate_z(z * PI2) 328 | return encode_basis(cube.transform.basis) 329 | 330 | 331 | func get_faces_of_rotated_cube(cube): 332 | var faces = [] 333 | faces.resize(6) 334 | for idx in 6: 335 | faces[get_face_position(cube, idx)] = idx 336 | return faces 337 | 338 | 339 | func encode_basis(b): 340 | return get_sector(b.x.normalized())\ 341 | + 13 * get_sector(b.y.normalized())\ 342 | + 169 * get_sector(b.z.normalized()) 343 | 344 | 345 | func get_sector(v): 346 | const th = 0.6 # This affects sensitivity around the edges/corners 347 | var n = 0 348 | if v.x < th: n += 1 349 | if v.x > th: n += 2 350 | if v.y < -th: n += 3 351 | if v.y > th: n += 4 352 | if v.z < -th: n += 5 353 | if v.z > th: n += 6 354 | return n 355 | 356 | 357 | func get_face_map_from_basis(b): 358 | var key = encode_basis(b.inverse()) 359 | if faces_dict.has(key): 360 | last_face_key = key 361 | return faces_dict[last_face_key] 362 | -------------------------------------------------------------------------------- /scenes/main.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=15 format=3 uid="uid://cs7xrk28v8act"] 2 | 3 | [ext_resource type="Script" path="res://scenes/main.gd" id="1_lbbsj"] 4 | [ext_resource type="PackedScene" uid="uid://duux08b0j1brq" path="res://scenes/big_cube.tscn" id="2_vrqpa"] 5 | [ext_resource type="Script" path="res://scenes/orbiting_camera.gd" id="3_imujc"] 6 | [ext_resource type="Script" path="res://scenes/UI.gd" id="4_g6qdy"] 7 | [ext_resource type="PackedScene" uid="uid://dfknx5u3yhl8y" path="res://scenes/color_map.tscn" id="5_3m2rh"] 8 | [ext_resource type="Texture2D" uid="uid://dqoxntu2lo6y6" path="res://assets/mid-edge.png" id="6_rgmga"] 9 | [ext_resource type="Script" path="res://scenes/Support.gd" id="6_yx4e8"] 10 | [ext_resource type="Texture2D" uid="uid://bark3whhet5bm" path="res://assets/cross.png" id="7_cufek"] 11 | [ext_resource type="StyleBox" uid="uid://c8yjwixda7sfl" path="res://scenes/label.stylebox" id="7_fv5dd"] 12 | [ext_resource type="PackedScene" uid="uid://0djlny363s4d" path="res://scenes/sequence.tscn" id="7_kilku"] 13 | [ext_resource type="Texture2D" uid="uid://bwxs1ppsxt7hm" path="res://assets/top-edges.png" id="8_40gr6"] 14 | [ext_resource type="Texture2D" uid="uid://vtgsknbn1yy" path="res://assets/top-corners.png" id="9_sr31l"] 15 | [ext_resource type="Texture2D" uid="uid://b4e5cqklbbmvh" path="res://assets/top-corner.png" id="10_n0pko"] 16 | 17 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_h34t6"] 18 | content_margin_left = 5.0 19 | content_margin_top = 5.0 20 | content_margin_right = 5.0 21 | content_margin_bottom = 5.0 22 | bg_color = Color(0.141176, 0.141176, 0.141176, 1) 23 | 24 | [node name="Main" type="MarginContainer"] 25 | theme_override_constants/margin_left = 20 26 | theme_override_constants/margin_top = 20 27 | theme_override_constants/margin_right = 20 28 | theme_override_constants/margin_bottom = 20 29 | script = ExtResource("1_lbbsj") 30 | 31 | [node name="C" type="HBoxContainer" parent="."] 32 | layout_mode = 2 33 | size_flags_horizontal = 3 34 | 35 | [node name="ColorMap" parent="C" instance=ExtResource("5_3m2rh")] 36 | layout_mode = 2 37 | size_flags_vertical = 4 38 | 39 | [node name="SVP" type="SubViewportContainer" parent="C"] 40 | unique_name_in_owner = true 41 | layout_mode = 2 42 | 43 | [node name="VP" type="SubViewport" parent="C/SVP"] 44 | transparent_bg = true 45 | handle_input_locally = false 46 | size = Vector2i(400, 400) 47 | render_target_update_mode = 4 48 | 49 | [node name="BigCube" parent="C/SVP/VP" instance=ExtResource("2_vrqpa")] 50 | 51 | [node name="Pivot" type="Marker3D" parent="C/SVP/VP/BigCube"] 52 | unique_name_in_owner = true 53 | rotation_edit_mode = 2 54 | script = ExtResource("3_imujc") 55 | 56 | [node name="Camera" type="Camera3D" parent="C/SVP/VP/BigCube/Pivot"] 57 | unique_name_in_owner = true 58 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 6) 59 | 60 | [node name="Light" type="DirectionalLight3D" parent="C/SVP/VP/BigCube/Pivot/Camera"] 61 | 62 | [node name="UI" type="VBoxContainer" parent="C"] 63 | layout_mode = 2 64 | size_flags_horizontal = 4 65 | theme_override_constants/separation = 10 66 | script = ExtResource("4_g6qdy") 67 | 68 | [node name="Grid" type="GridContainer" parent="C/UI"] 69 | layout_mode = 2 70 | theme_override_constants/h_separation = 5 71 | theme_override_constants/v_separation = 5 72 | columns = 3 73 | 74 | [node name="Sp" type="Control" parent="C/UI/Grid"] 75 | custom_minimum_size = Vector2(32, 32) 76 | layout_mode = 2 77 | size_flags_horizontal = 3 78 | 79 | [node name="U" type="Button" parent="C/UI/Grid"] 80 | custom_minimum_size = Vector2(32, 32) 81 | layout_mode = 2 82 | button_mask = 7 83 | text = "U" 84 | 85 | [node name="Sp2" type="Control" parent="C/UI/Grid"] 86 | custom_minimum_size = Vector2(32, 32) 87 | layout_mode = 2 88 | size_flags_horizontal = 3 89 | 90 | [node name="L" type="Button" parent="C/UI/Grid"] 91 | custom_minimum_size = Vector2(32, 32) 92 | layout_mode = 2 93 | size_flags_horizontal = 8 94 | button_mask = 7 95 | text = "L" 96 | 97 | [node name="F" type="Button" parent="C/UI/Grid"] 98 | custom_minimum_size = Vector2(32, 32) 99 | layout_mode = 2 100 | button_mask = 7 101 | text = "F" 102 | 103 | [node name="R" type="Button" parent="C/UI/Grid"] 104 | custom_minimum_size = Vector2(32, 32) 105 | layout_mode = 2 106 | size_flags_horizontal = 0 107 | button_mask = 7 108 | text = "R" 109 | 110 | [node name="Sp3" type="Control" parent="C/UI/Grid"] 111 | custom_minimum_size = Vector2(32, 32) 112 | layout_mode = 2 113 | 114 | [node name="D" type="Button" parent="C/UI/Grid"] 115 | custom_minimum_size = Vector2(32, 32) 116 | layout_mode = 2 117 | button_mask = 7 118 | text = "D" 119 | 120 | [node name="Sp4" type="Control" parent="C/UI/Grid"] 121 | custom_minimum_size = Vector2(32, 32) 122 | layout_mode = 2 123 | 124 | [node name="Sp5" type="Control" parent="C/UI/Grid"] 125 | custom_minimum_size = Vector2(32, 32) 126 | layout_mode = 2 127 | 128 | [node name="B" type="Button" parent="C/UI/Grid"] 129 | custom_minimum_size = Vector2(32, 32) 130 | layout_mode = 2 131 | button_mask = 7 132 | text = "B 133 | " 134 | 135 | [node name="Reset" type="Button" parent="C/UI"] 136 | layout_mode = 2 137 | text = "Reset" 138 | 139 | [node name="Scramble" type="Button" parent="C/UI"] 140 | layout_mode = 2 141 | text = "Scramble" 142 | 143 | [node name="Solve" type="Button" parent="C/UI"] 144 | layout_mode = 2 145 | text = "Solve" 146 | 147 | [node name="Tools" type="HBoxContainer" parent="C/UI"] 148 | layout_mode = 2 149 | 150 | [node name="MidEdges" type="Button" parent="C/UI/Tools"] 151 | layout_mode = 2 152 | tooltip_text = "2nd layer edges" 153 | icon = ExtResource("6_rgmga") 154 | 155 | [node name="Cross" type="Button" parent="C/UI/Tools"] 156 | layout_mode = 2 157 | tooltip_text = "Yellow cross" 158 | icon = ExtResource("7_cufek") 159 | 160 | [node name="TopEdges" type="Button" parent="C/UI/Tools"] 161 | layout_mode = 2 162 | tooltip_text = "Top edges" 163 | icon = ExtResource("8_40gr6") 164 | 165 | [node name="TopCorners" type="Button" parent="C/UI/Tools"] 166 | layout_mode = 2 167 | tooltip_text = "Top corner positions" 168 | icon = ExtResource("9_sr31l") 169 | 170 | [node name="CornerSpin" type="Button" parent="C/UI/Tools"] 171 | layout_mode = 2 172 | tooltip_text = "Top corner orientation" 173 | icon = ExtResource("10_n0pko") 174 | 175 | [node name="Sequence" type="Button" parent="C/UI"] 176 | layout_mode = 2 177 | text = "Run Sequence" 178 | 179 | [node name="Help" type="Button" parent="C/UI"] 180 | layout_mode = 2 181 | text = "Help" 182 | 183 | [node name="HB" type="HBoxContainer" parent="C/UI"] 184 | layout_mode = 2 185 | theme_override_constants/separation = 10 186 | 187 | [node name="SpeedSlider" type="HSlider" parent="C/UI/HB"] 188 | unique_name_in_owner = true 189 | layout_mode = 2 190 | size_flags_horizontal = 3 191 | tooltip_text = "Rotation Speed" 192 | min_value = 1.0 193 | max_value = 9.0 194 | value = 1.0 195 | 196 | [node name="ColorPicker" type="ColorPickerButton" parent="C/UI/HB"] 197 | unique_name_in_owner = true 198 | custom_minimum_size = Vector2(20, 0) 199 | layout_mode = 2 200 | color = Color(0, 0, 0, 0) 201 | edit_alpha = false 202 | 203 | [node name="Info" type="AcceptDialog" parent="."] 204 | title = "Instructions" 205 | size = Vector2i(552, 389) 206 | theme_override_styles/panel = SubResource("StyleBoxFlat_h34t6") 207 | dialog_text = "Color Map 208 | Click on source tile then destination tile to swap colors. 209 | Only valid moves are possible. 210 | 211 | Cube 212 | Use the mouse or arrow keys to rotate the cube. 213 | Ctrl + mouse button to zoom or ZX keys. 214 | 215 | Control Panel 216 | Click on face button to rotate face clockwise. 217 | Shift click or right mouse button for anti-clockwise rotation. 218 | Crtl click or middle mouse button to align the cube with face forward. 219 | Esc to quit." 220 | 221 | [node name="Support" type="PopupPanel" parent="."] 222 | title = "Extra Features" 223 | size = Vector2i(552, 300) 224 | theme_override_styles/panel = SubResource("StyleBoxFlat_h34t6") 225 | script = ExtResource("6_yx4e8") 226 | 227 | [node name="Text" type="RichTextLabel" parent="Support"] 228 | offset_left = 5.0 229 | offset_top = 5.0 230 | offset_right = 547.0 231 | offset_bottom = 295.0 232 | bbcode_enabled = true 233 | text = "Support My Work 234 | 235 | If you liked this project, how about [url]Buying me a Coffee[/url]? 236 | 237 | Best regards, 238 | 239 | Andrew Wilkes" 240 | 241 | [node name="Overlay" type="CanvasLayer" parent="."] 242 | 243 | [node name="SUI" type="VBoxContainer" parent="Overlay"] 244 | unique_name_in_owner = true 245 | offset_right = 400.0 246 | offset_bottom = 123.0 247 | mouse_filter = 2 248 | 249 | [node name="Gap" type="Control" parent="Overlay/SUI"] 250 | custom_minimum_size = Vector2(0, 20) 251 | layout_mode = 2 252 | 253 | [node name="C" type="CenterContainer" parent="Overlay/SUI"] 254 | custom_minimum_size = Vector2(400, 0) 255 | layout_mode = 2 256 | 257 | [node name="Note" type="Label" parent="Overlay/SUI/C"] 258 | unique_name_in_owner = true 259 | layout_mode = 2 260 | theme_override_styles/normal = ExtResource("7_fv5dd") 261 | text = "Text" 262 | 263 | [node name="SP" type="Control" parent="Overlay/SUI"] 264 | layout_mode = 2 265 | size_flags_vertical = 3 266 | mouse_filter = 2 267 | 268 | [node name="C2" type="CenterContainer" parent="Overlay/SUI"] 269 | custom_minimum_size = Vector2(400, 0) 270 | layout_mode = 2 271 | 272 | [node name="HB" type="HBoxContainer" parent="Overlay/SUI/C2"] 273 | layout_mode = 2 274 | theme_override_constants/separation = 10 275 | 276 | [node name="Step" type="Button" parent="Overlay/SUI/C2/HB"] 277 | unique_name_in_owner = true 278 | layout_mode = 2 279 | text = "Step" 280 | 281 | [node name="Play" type="Button" parent="Overlay/SUI/C2/HB"] 282 | unique_name_in_owner = true 283 | custom_minimum_size = Vector2(56, 0) 284 | layout_mode = 2 285 | text = "Play" 286 | 287 | [node name="Stop" type="Button" parent="Overlay/SUI/C2/HB"] 288 | unique_name_in_owner = true 289 | layout_mode = 2 290 | text = "Stop" 291 | 292 | [node name="CounterContainer" type="HBoxContainer" parent="Overlay"] 293 | unique_name_in_owner = true 294 | offset_left = 20.0 295 | offset_top = 20.0 296 | offset_right = 654.0 297 | offset_bottom = 46.0 298 | theme_override_constants/separation = 10 299 | 300 | [node name="Sp1" type="Control" parent="Overlay/CounterContainer"] 301 | layout_mode = 2 302 | size_flags_horizontal = 3 303 | 304 | [node name="Counter" type="Label" parent="Overlay/CounterContainer"] 305 | unique_name_in_owner = true 306 | layout_mode = 2 307 | theme_override_styles/normal = ExtResource("7_fv5dd") 308 | text = "Count 12" 309 | 310 | [node name="StopCounting" type="Button" parent="Overlay/CounterContainer"] 311 | unique_name_in_owner = true 312 | layout_mode = 2 313 | text = "Stop" 314 | 315 | [node name="Sp2" type="Control" parent="Overlay/CounterContainer"] 316 | layout_mode = 2 317 | size_flags_horizontal = 3 318 | 319 | [node name="Sequence" parent="." instance=ExtResource("7_kilku")] 320 | visible = false 321 | 322 | [connection signal="value_changed" from="C/UI/HB/SpeedSlider" to="." method="_on_speed_slider_value_changed"] 323 | [connection signal="color_changed" from="C/UI/HB/ColorPicker" to="." method="_on_color_picker_color_changed"] 324 | [connection signal="meta_clicked" from="Support/Text" to="Support" method="_on_text_meta_clicked"] 325 | [connection signal="pressed" from="Overlay/SUI/C2/HB/Step" to="." method="_on_step_pressed"] 326 | [connection signal="pressed" from="Overlay/SUI/C2/HB/Play" to="." method="_on_play_pressed"] 327 | [connection signal="pressed" from="Overlay/SUI/C2/HB/Stop" to="." method="_on_stop_pressed"] 328 | [connection signal="pressed" from="Overlay/CounterContainer/StopCounting" to="." method="_on_stop_counting_pressed"] 329 | -------------------------------------------------------------------------------- /scenes/main.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | const FACE_BUTTONS = ["U", "L", "F", "R", "D", "B"] 4 | const FACE_MAPS = [[0,1,2,3,4,5],[0,2,3,5,4,1],[0,3,5,1,4,2],[0,5,1,2,4,3]] # G,B,O,Y 5 | const EDGE_MAPS = [[0,1,2,3,4,5,6,7,8,9,10,11],[2,0,3,1,7,4,5,6,11,8,9,10],[3,2,1,0,6,7,4,5,10,11,8,9],[1,3,0,2,5,6,7,4,9,10,11,8]] 6 | const CORNER_MAPS = [[0,1,2,3,4,5,6,7],[1,3,0,2,7,4,5,6],[3,2,1,0,6,7,4,5],[2,0,3,1,5,6,7,4]] 7 | 8 | enum { STOPPED, PLAYING, STEPPING } 9 | 10 | var bc 11 | var cmap 12 | var solve_step = -1 13 | var move_step = -1 14 | var moves = [] 15 | var colors 16 | var face_map 17 | var edge_map 18 | var corner_map 19 | var rotation_completed = true 20 | var play_state = STOPPED 21 | var the_log = [] 22 | var repeat_sequence = [] 23 | var order_count = 0 24 | 25 | func _ready(): 26 | $C/UI.button_pressed.connect(_on_button_pressed) 27 | bc = $C/SVP/VP/BigCube 28 | cmap = $C/ColorMap 29 | cmap.connect("map_changed", copy_map_to_cube) 30 | %Pivot.connect("rotation_complete", rotation_done) 31 | bc.connect("rotation_complete", rotation_done) 32 | %SUI.hide() 33 | %CounterContainer.hide() 34 | $Sequence.connect("play_sequence", play_sequence) 35 | %SpeedSlider.value = bc.rotation_speed 36 | %Pivot.speed = bc.rotation_speed 37 | %ColorPicker.color = RenderingServer.get_default_clear_color() 38 | call_deferred("set_sui_transform") 39 | 40 | 41 | func play_sequence(seq, repeat): 42 | if seq.size() < 1: 43 | return 44 | if repeat: 45 | apply_reset() 46 | repeat_sequence = seq 47 | %CounterContainer.show() 48 | set_order_count(0) 49 | face_map = FACE_MAPS[0] 50 | set_sequence(seq) 51 | 52 | 53 | func set_sequence(seq): 54 | set_moves(seq) 55 | play_state = PLAYING 56 | solve_step = 100 57 | 58 | 59 | func set_order_count(n): 60 | order_count = n 61 | %Counter.text = "Order count: " + str(n) 62 | 63 | 64 | func set_sui_transform(): 65 | %SUI.size = %SVP.size 66 | %SUI.position = %SVP.position 67 | %CounterContainer.position = %SVP.position + Vector2(0, 20) 68 | %CounterContainer.size.x = %SVP.size.x 69 | 70 | 71 | func rotation_done(): 72 | rotation_completed = true 73 | copy_cube() 74 | if repeat_sequence.size() > 0 and move_step < 0: 75 | set_order_count(order_count + 1) 76 | if cmap.solved(): 77 | repeat_sequence.clear() 78 | else: 79 | set_moves(repeat_sequence) 80 | elif play_state == PLAYING: 81 | # solve() and set_moves() call apply_move() 82 | # so need to avoid calling apply_move() twice in this function 83 | solve() 84 | 85 | 86 | func _on_button_pressed(bname, shift, ctrl): 87 | if not rotation_completed: 88 | return 89 | %CounterContainer.hide() 90 | var bas = bc.get_node("Pivot").transform.basis 91 | face_map = bc.get_face_map_from_basis(bas) 92 | if FACE_BUTTONS.has(bname): 93 | var button_idx = FACE_BUTTONS.find(bname) 94 | if ctrl: 95 | var rotations = [Vector2(-1.0, 0.0), Vector2(0.0, -1.0), Vector2(0.0, 0.0), Vector2(0.0, 1.0), Vector2(1.0, 0.0), Vector2(0.0, 2.0)] 96 | bc.get_node("Pivot").rotate_to_face(rotations[button_idx]) 97 | else: 98 | var direction = -1 if shift else 1 99 | stop_solving() 100 | rotate_face(button_idx, direction, bas) 101 | match bname: 102 | "Reset": 103 | apply_reset() 104 | "Scramble": 105 | apply_reset() 106 | for n in randi_range(10, 15): 107 | bc.rotate_face_immediate(randi() % 6, 1 if randf() > 0.5 else -1) 108 | copy_cube() 109 | stop_solving() 110 | "Solve": 111 | if solve_step < 0: 112 | solve_step = 1 113 | play_state = STEPPING 114 | cmap.solving = true 115 | the_log.clear() 116 | %SUI.show() 117 | if play_state == PLAYING: 118 | stop_solving() 119 | else: 120 | solve() 121 | "Sequence": 122 | $Sequence.popup_centered() 123 | "Help": 124 | $Info.popup_centered() 125 | "MidEdges": 126 | var seq = [[0,-1],[1,-1],[0,1],[1,1],[0,1],[2,1],[0,-1],[2,-1]] if shift else \ 127 | [[0,1],[3,1],[0,-1],[3,-1],[0,-1],[2,-1],[0,1],[2,1]] 128 | set_sequence(seq) 129 | "Cross": 130 | var seq = [[2,1],[3,1],[0,1],[3,-1],[0,-1],[2,-1]] if shift else \ 131 | [[2,1],[0,1],[3,1],[0,-1],[3,-1],[2,-1]] 132 | set_sequence(seq) 133 | "TopEdges": 134 | var seq = [[3,1],[0,1],[3,-1],[0,1],[3,1],[0,1],[0,1],[3,-1]] 135 | set_sequence(seq) 136 | "TopCorners": 137 | var seq = [[1,-1],[0,1],[3,1],[0,-1],[1,1],[0,1],[3,-1],[0,-1]] if shift else \ 138 | [[0,1],[3,1],[0,-1],[1,-1],[0,1],[3,-1],[0,-1],[1,1]] 139 | set_sequence(seq) 140 | "CornerSpin": 141 | var seq = [[3,-1],[4,-1],[3,1],[4,1]] if shift else \ 142 | [[4,-1],[3,-1],[4,1],[3,1]] 143 | set_sequence(seq) 144 | 145 | 146 | func copy_map_to_cube(): 147 | if play_state != STOPPED: 148 | stop_solving() 149 | bc.apply_map($C/ColorMap.get_data()) 150 | 151 | 152 | func apply_reset(): 153 | bc.reset() 154 | copy_cube() 155 | stop_solving() 156 | repeat_sequence.clear() 157 | move_step = -1 158 | 159 | 160 | func rotate_face(face_idx, direction, bas = Basis()): 161 | bc.rotate_face(face_idx, direction, bas) 162 | rotation_completed = false 163 | 164 | 165 | func copy_cube(): 166 | $C/ColorMap.set_edge_colors(bc.get_edge_colors(), bc.edge_visibility) 167 | $C/ColorMap.set_corner_colors(bc.get_corner_colors(), bc.corner_visibility) 168 | 169 | 170 | func move_white_edge_to_face(): 171 | # Want to avoid disturbing already placed edges in the group: 8,9,10,11 172 | # Get the edge position where the piece is now 173 | var idx = get_edge_position(colors) 174 | # Move to front face 175 | # Map the idx depending on the front face 176 | idx = edge_map[idx] 177 | # set_moves will map the face index 178 | match idx: 179 | 0, 2: 180 | set_moves([[0,1]]) 181 | 1: 182 | set_moves([[0,-1]]) 183 | 4: 184 | set_moves([[1,1],[0,-1],[1,-1]]) 185 | 8: 186 | set_moves([[1,-1]]) 187 | 7: 188 | set_moves([[3,-1],[0,1],[3,1]]) 189 | 11: 190 | set_moves([[5,1],[5,1]]) 191 | 10: 192 | set_moves([[3,1]]) 193 | 3, 5, 6, 9: 194 | # Now on wanted face 195 | solve_step += 1 196 | solve() 197 | 198 | 199 | func move_white_edge_to_white_face(): 200 | var idx = get_edge_position(colors) 201 | var aligned = is_edge_aligned(colors, idx) 202 | idx = edge_map[idx] 203 | match idx: 204 | 3: 205 | set_moves([[2,1]]) 206 | 6: 207 | if aligned: 208 | set_moves([[2,1]]) 209 | else: 210 | set_moves([[4,1],[3,-1],[4,-1]]) 211 | 5: 212 | if aligned: 213 | set_moves([[2,-1]]) 214 | else: 215 | set_moves([[4,-1],[1,1],[4,1]]) 216 | 9: 217 | if aligned: 218 | solve_step += 1 219 | solve() 220 | else: 221 | set_moves([[2,-1]]) 222 | _: 223 | print("Illegal idx %d at step: %d" % [idx, solve_step]) 224 | save_log() 225 | breakpoint 226 | 227 | 228 | func move_corner_to_p2(): 229 | var idx = get_corner_position(colors) 230 | var aligned = is_corner_aligned(colors, idx) 231 | idx = corner_map[idx] 232 | if aligned and idx == 5: 233 | solve_step += 3 234 | solve() 235 | else: 236 | match idx: 237 | 4: 238 | set_moves([[1,1],[0,1],[1,-1]]) # 4 > 1 239 | 5: 240 | set_moves([[1,-1],[0,-1],[1,1]]) # 5 > 3 241 | 6: 242 | set_moves([[3,1],[0,1],[3,-1]]) # 6 > 2 243 | 7: 244 | set_moves([[3,-1],[0,-1],[3,1]]) # 7 > 0 245 | 0: 246 | set_moves([[0,-1]]) # 0 > 2 247 | 1: 248 | set_moves([[0,-1],[0,-1]]) # 1 > 2 249 | 3: 250 | set_moves([[0,1]]) # 3 > 2 251 | 2: 252 | solve_step += 1 253 | solve() 254 | 255 | 256 | func move_corner_to_white_face(): 257 | # On which face is the face color tile? 258 | match colors.find(cmap.corners[corner_map.find(2)][1]): 259 | 0: 260 | set_moves([[1,-1],[0,-1],[0,-1],[1,1],[0,1]]) 261 | 1: 262 | set_moves([[1,-1],[0,-1],[1,1]]) # Face color is forward 263 | solve_step += 1 264 | 2: 265 | set_moves([[2,1],[0,1],[2,-1]]) # White is forward 266 | solve_step += 1 267 | _: 268 | print("Illegal state at step: %d" % [solve_step]) 269 | save_log() 270 | breakpoint 271 | 272 | 273 | func move_mid_edge(): 274 | var idx = get_edge_position(colors) 275 | var aligned = is_edge_aligned(colors, idx) 276 | var eidx = [5,6,7,4].find(idx) 277 | idx = edge_map[idx] 278 | if aligned and idx == 5: 279 | solve_step += 1 280 | solve() 281 | return 282 | var moves1 = [[0,-1],[1,-1],[0,1],[1,1],[0,1],[2,1],[0,-1],[2,-1]] 283 | if eidx > -1: 284 | # Move to top layer (left side to top) 285 | face_map = FACE_MAPS[eidx] 286 | set_moves(moves1) 287 | else: 288 | match idx: 289 | 0, 1: 290 | set_moves([[0, -1]]) 291 | 2: 292 | set_moves([[0, 1]]) 293 | 3: 294 | if aligned: 295 | set_moves(moves1) 296 | else: 297 | set_moves([[0,1],[0,1],[2,1],[0,-1],[2,-1],[0,-1],[1,-1],[0,1],[1,1]]) 298 | 299 | 300 | func form_top_cross(): 301 | # Formed when edges 0,1,2,3 have face 0 as idx 1 of the edge 302 | var pattern = [0,0,0,0] 303 | for idx in 4: 304 | pattern[idx] = clampi(cmap.edges[idx][1], 0, 1) 305 | match pattern: 306 | [0,0,0,0]: 307 | solve_step += 1 308 | solve() 309 | [0,1,0,1], [0,1,1,0]: 310 | set_moves([[0,-1]]) 311 | [1,1,0,0]: 312 | set_moves([[0,1],[0,1]]) 313 | [1,0,1,0]: 314 | set_moves([[0,1]]) 315 | _: 316 | set_moves([[2,1],[3,1],[0,1],[3,-1],[0,-1],[2,-1]]) 317 | 318 | 319 | func align_top_edges(): 320 | var pattern = [0,0,0,0] 321 | for idx in 4: 322 | # Map idx to one of the top edges in order of traversal of the top rim 323 | pattern[idx] = cmap.edges[[0,1,3,2][idx]][0] 324 | # Check if all correct or correct but need to rotate top 325 | match pattern: 326 | [5,1,2,3]: 327 | solve_step += 1 328 | solve() 329 | [1,2,3,5]: 330 | set_moves([[0,-1]]) 331 | [2,3,5,1]: 332 | set_moves([[0,1],[0,1]]) 333 | [3,5,1,2]: 334 | set_moves([[0,1]]) 335 | _: 336 | # move correct adjacent pair to back right (pos idx 3) 337 | var pairs = [[0,1],[1,2],[2,3],[3,0]] 338 | var faces = [[5,1],[1,2],[2,3],[3,5]] 339 | var pos = -1 340 | var idx = 0 341 | # Find the position around the cube of the pair of faces 342 | for pair in pairs: 343 | if faces.find([pattern[pair[0]], pattern[pair[1]]]) > -1: 344 | pos = idx 345 | break 346 | idx += 1 347 | match pos: 348 | 0: 349 | set_moves([[0,1]]) 350 | 1: 351 | set_moves([[0,1],[0,1]]) 352 | 2: 353 | set_moves([[0,-1]]) 354 | _: 355 | # Apply moves even if there was no adjacent correct pair 356 | set_moves([[3,1],[0,1],[3,-1],[0,1],[3,1],[0,1],[0,1],[3,-1]]) 357 | 358 | 359 | func position_top_corners(): 360 | # 0, 1 or ALL the corner pieces will be in their correct positions 361 | var correct_corners = [] 362 | for idx in 4: 363 | if get_corner_position(cmap.CORNER_FACE_MAP[idx]) == idx: 364 | correct_corners.append(idx) 365 | if correct_corners.size() == 4: 366 | solve_step += 1 367 | solve() 368 | else: 369 | if correct_corners.size() > 0: 370 | # Pick face map for face where correct corner is in top right 371 | face_map = FACE_MAPS[[2,1,3,0][correct_corners[0]]] 372 | else: 373 | face_map = FACE_MAPS[2] 374 | set_moves([[0,1],[3,1],[0,-1],[1,-1],[0,1],[3,-1],[0,-1],[1,1]]) 375 | 376 | 377 | func rotate_top_right_corner(): 378 | if cmap.CORNER_FACE_MAP[3][0] == cmap.corners[3][0]: 379 | solve_step += 1 380 | solve() 381 | else: 382 | set_moves([[3,-1],[4,-1],[3,1],[4,1],[3,-1],[4,-1],[3,1],[4,1]]) 383 | 384 | 385 | func solve(): 386 | # https://www.speedcube.com.au/pages/how-to-solve-a-rubiks-cube 387 | if move_step > -1: 388 | # We are in a rotation sequence 389 | apply_move() 390 | return 391 | write_to_log() 392 | match solve_step: 393 | 1: 394 | %Pivot.rotate_to_face(Vector2(-0.3, 0.0)) 395 | solve_step += 1 396 | add_note("Solving") 397 | 2: 398 | add_note("Moving green/white edge to green face") 399 | if move_step < 0: 400 | face_map = FACE_MAPS[0] 401 | edge_map = EDGE_MAPS[0] 402 | colors = [2,4] 403 | # GREEN/ WHITE edge piece 404 | move_white_edge_to_face() 405 | 3: 406 | add_note("Making white cross\nMoving green/white edge to white face") 407 | %Pivot.rotate_to_face(Vector2(0.3, 0.0)) 408 | solve_step += 1 409 | 4: 410 | move_white_edge_to_white_face() 411 | 5: 412 | add_note("Moving orange/white edge to orange face") 413 | %Pivot.rotate_to_face(Vector2(-0.3, 1.0)) 414 | solve_step += 1 415 | 6: 416 | face_map = FACE_MAPS[1] 417 | edge_map = EDGE_MAPS[1] 418 | colors = [3,4] 419 | # ORANGE/ WHITE edge piece 420 | # Don't move edge 9 421 | move_white_edge_to_face() 422 | 7: 423 | %Pivot.rotate_to_face(Vector2(0.3, 1.0)) 424 | add_note("Moving orange/white edge to white face") 425 | solve_step += 1 426 | 8: 427 | move_white_edge_to_white_face() 428 | 9: 429 | add_note("Moving blue/white edge to blue face") 430 | %Pivot.rotate_to_face(Vector2(-0.3, 2.0)) 431 | solve_step += 1 432 | 10: 433 | face_map = FACE_MAPS[2] 434 | edge_map = EDGE_MAPS[2] 435 | colors = [5,4] 436 | # BLUE / WHITE edge piece 437 | # Avoid moving edges 9,10 438 | move_white_edge_to_face() 439 | 11: 440 | %Pivot.rotate_to_face(Vector2(0.3, 2.0)) 441 | add_note("Moving blue/white edge to white face") 442 | solve_step += 1 443 | 12: 444 | move_white_edge_to_white_face() 445 | 13: 446 | add_note("Moving red/white edge to red face") 447 | %Pivot.rotate_to_face(Vector2(-0.3, -1.0)) 448 | solve_step += 1 449 | 14: 450 | face_map = FACE_MAPS[3] 451 | edge_map = EDGE_MAPS[3] 452 | colors = [1,4] 453 | # RED / WHITE edge piece 454 | # Avoid moving edges 9,10,11 455 | move_white_edge_to_face() 456 | 15: 457 | %Pivot.rotate_to_face(Vector2(0.3, -1.0)) 458 | add_note("Moving red/white edge to white face") 459 | solve_step += 1 460 | 16: 461 | move_white_edge_to_white_face() 462 | 17: 463 | add_note("Placing white corners") 464 | await get_tree().create_timer(1.0).timeout 465 | add_note("Moving red/green/white corner\n to top/left corner") 466 | %Pivot.rotate_to_face(Vector2(-0.3, -0.3)) 467 | solve_step += 1 468 | 18: 469 | # Move the YELLOW/GREEN/WHITE corner to the top face 470 | colors = cmap.CORNER_FACE_MAP[5] 471 | corner_map = CORNER_MAPS[0] 472 | face_map = FACE_MAPS[0] 473 | move_corner_to_p2() 474 | 19: 475 | add_note("Moving red/green/white corner\n to bottom/left corner") 476 | %Pivot.rotate_to_face(Vector2(0.3, -0.3)) 477 | solve_step += 1 478 | 20: 479 | move_corner_to_white_face() 480 | 21: 481 | add_note("Moving green/orange/white corner\n to top/left corner") 482 | %Pivot.rotate_to_face(Vector2(-0.3, 0.7)) 483 | solve_step += 1 484 | 22: 485 | colors = cmap.CORNER_FACE_MAP[6] 486 | corner_map = CORNER_MAPS[1] 487 | face_map = FACE_MAPS[1] 488 | move_corner_to_p2() 489 | 23: 490 | add_note("Moving green/orange/white corner\n to bottom/left corner") 491 | %Pivot.rotate_to_face(Vector2(0.3, 0.7)) 492 | solve_step += 1 493 | 24: 494 | move_corner_to_white_face() 495 | 25: 496 | add_note("Moving orange/blue/white corner\n to top/left corner") 497 | %Pivot.rotate_to_face(Vector2(-0.3, 1.7)) 498 | solve_step += 1 499 | 26: 500 | colors = cmap.CORNER_FACE_MAP[7] 501 | corner_map = CORNER_MAPS[2] 502 | face_map = FACE_MAPS[2] 503 | move_corner_to_p2() 504 | 27: 505 | add_note("Moving orange/blue/white corner\n to bottom/left corner") 506 | %Pivot.rotate_to_face(Vector2(0.3, 1.7)) 507 | solve_step += 1 508 | 28: 509 | move_corner_to_white_face() 510 | 29: 511 | add_note("Moving blue/red/white corner\n to top/left corner") 512 | %Pivot.rotate_to_face(Vector2(-0.3, -1.3)) 513 | solve_step += 1 514 | 30: 515 | colors = cmap.CORNER_FACE_MAP[4] 516 | corner_map = CORNER_MAPS[3] 517 | face_map = FACE_MAPS[3] 518 | move_corner_to_p2() 519 | 31: 520 | add_note("Moving blue/red/white corner\n to bottom/left corner") 521 | %Pivot.rotate_to_face(Vector2(0.3, -1.3)) 522 | solve_step += 1 523 | 32: 524 | move_corner_to_white_face() 525 | 33: 526 | add_note("Complete middle layer") 527 | %Pivot.rotate_to_face(Vector2(0.0, -1.5)) 528 | solve_step += 1 529 | 34: 530 | add_note("Move blue/red edge") 531 | face_map = FACE_MAPS[3] 532 | edge_map = EDGE_MAPS[3] 533 | colors = cmap.EDGE_FACE_MAP[4] 534 | move_mid_edge() 535 | 35: 536 | add_note("Move red/green edge") 537 | %Pivot.rotate_to_face(Vector2(0.0, -0.5)) 538 | solve_step += 1 539 | 36: 540 | face_map = FACE_MAPS[0] 541 | edge_map = EDGE_MAPS[0] 542 | colors = cmap.EDGE_FACE_MAP[5] 543 | move_mid_edge() 544 | 37: 545 | add_note("Move green/orange edge") 546 | %Pivot.rotate_to_face(Vector2(0.0, 0.5)) 547 | solve_step += 1 548 | 38: 549 | face_map = FACE_MAPS[1] 550 | edge_map = EDGE_MAPS[1] 551 | colors = cmap.EDGE_FACE_MAP[6] 552 | move_mid_edge() 553 | 39: 554 | add_note("Move orange/blue edge") 555 | %Pivot.rotate_to_face(Vector2(0.0, 1.5)) 556 | solve_step += 1 557 | 40: 558 | face_map = FACE_MAPS[2] 559 | edge_map = EDGE_MAPS[2] 560 | colors = cmap.EDGE_FACE_MAP[7] 561 | move_mid_edge() 562 | 41: 563 | add_note("Form top cross") 564 | %Pivot.rotate_to_face(Vector2(-0.5, 0.0)) 565 | solve_step += 1 566 | 42: 567 | add_note("Now 2/4 edge pieces are\n in the correct position.\nAlign them.") 568 | face_map = FACE_MAPS[0] 569 | form_top_cross() 570 | 43: 571 | align_top_edges() 572 | 44: 573 | add_note("Position top corners") 574 | position_top_corners() 575 | 45: 576 | set_moves([[0,-1]]) 577 | solve_step += 1 578 | 46: 579 | face_map = FACE_MAPS[0] 580 | add_note("Rotate top-right corner 1") 581 | rotate_top_right_corner() 582 | 47: 583 | set_moves([[0,-1]]) 584 | solve_step += 1 585 | 48: 586 | add_note("Rotate top-right corner 2") 587 | rotate_top_right_corner() 588 | 49: 589 | set_moves([[0,-1]]) 590 | solve_step += 1 591 | 50: 592 | add_note("Rotate top-right corner 3") 593 | rotate_top_right_corner() 594 | 51: 595 | set_moves([[0,-1]]) 596 | solve_step += 1 597 | 52: 598 | add_note("Rotate top-right corner 4") 599 | rotate_top_right_corner() 600 | 53: 601 | %Pivot.rotate_to_face(Vector2(-0.5, -0.5)) 602 | solve_step += 1 603 | 54: 604 | add_note("Completed!") 605 | solve_step += 1 606 | await get_tree().create_timer(3.0).timeout 607 | stop_solving() 608 | 100: 609 | # Deal with end of sequence 610 | solve_step = -1 611 | play_state = STOPPED 612 | 613 | 614 | func get_edge_position(cols): 615 | var idx = 0 616 | for n in 12: 617 | if cmap.edges[idx].has(cols[0]) and cmap.edges[idx].has(cols[1]): 618 | break 619 | idx += 1 620 | return idx 621 | 622 | 623 | func is_edge_aligned(cols, idx): 624 | # The face color is the common color 625 | var fc = cols[0] if cmap.EDGE_FACE_MAP[idx].has(cols[0]) else cols[1] 626 | return cmap.EDGE_FACE_MAP[idx].find(fc) == cmap.edges[idx].find(fc) 627 | 628 | 629 | func get_corner_position(cols): 630 | var idx = 0 631 | for n in 8: 632 | if cmap.corners[idx].has(cols[0]) and cmap.corners[idx].has(cols[1]) and cmap.corners[idx].has(cols[2]): 633 | break 634 | idx += 1 635 | return idx 636 | 637 | 638 | func is_corner_aligned(cols, idx): 639 | # The face color is the common color 640 | var fc = cols[0] if cmap.CORNER_FACE_MAP[idx].has(cols[0]) else cols[1] if cmap.CORNER_FACE_MAP[idx].has(cols[1]) else cols[2] 641 | return cmap.CORNER_FACE_MAP[idx].find(fc) == cmap.corners[idx].find(fc) 642 | 643 | 644 | func set_moves(seq): 645 | moves = seq 646 | move_step = 0 647 | apply_move() 648 | 649 | 650 | func apply_move(): 651 | var move = moves[move_step] 652 | rotate_face(face_map[move[0]], move[1]) 653 | move_step += 1 654 | if move_step == moves.size(): 655 | move_step = -1 # Indicate end of move sequence 656 | 657 | 658 | func add_note(txt): 659 | %Note.text = txt 660 | %Note.show() 661 | 662 | 663 | func hide_note(): 664 | await get_tree().create_timer(0.5).timeout 665 | %Note.hide() 666 | 667 | 668 | func _on_step_pressed(): 669 | if rotation_completed: 670 | solve() 671 | 672 | 673 | func _on_play_pressed(): 674 | if play_state == PLAYING: 675 | %Play.text = "Play" 676 | %Step.disabled = false 677 | play_state = STEPPING 678 | else: 679 | %Play.text = "Pause" 680 | %Step.disabled = true 681 | play_state = PLAYING 682 | if rotation_completed: 683 | solve() 684 | 685 | 686 | func _on_stop_pressed(): 687 | %SUI.hide() 688 | play_state = STOPPED 689 | stop_solving() 690 | 691 | 692 | func stop_solving(): 693 | solve_step = -1 694 | play_state = STOPPED 695 | cmap.solving = false 696 | %SUI.hide() 697 | %Play.text = "Play" 698 | %Step.disabled = false 699 | 700 | 701 | func write_to_log(): 702 | the_log.append(solve_step) 703 | the_log.append(str(cmap.edges)) 704 | the_log.append(str(cmap.corners)) 705 | 706 | 707 | func save_log(): 708 | var file = FileAccess.open("res://log.txt", FileAccess.WRITE) 709 | file.store_string("\n".join(the_log)) 710 | 711 | 712 | func _on_stop_counting_pressed(): 713 | repeat_sequence.clear() 714 | move_step = -1 715 | 716 | 717 | func _on_speed_slider_value_changed(value): 718 | bc.rotation_speed = value 719 | %Pivot.speed = value 720 | 721 | 722 | func _on_color_picker_color_changed(color): 723 | RenderingServer.set_default_clear_color(color) 724 | -------------------------------------------------------------------------------- /media/graphics.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 42 | 45 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 61 | 72 | 82 | 89 | 96 | 103 | 110 | 117 | 124 | 0 134 | Face 144 | 1 154 | 2 164 | 3 174 | 4 184 | 5 194 | 0 204 | 0 214 | 0 224 | 1 234 | Corner 244 | 1 254 | 1 264 | 2 274 | 3 284 | 4 294 | 4 304 | 5 314 | 6 324 | 7 334 | 7 344 | 0 354 | 0 364 | 1 374 | 1 384 | 2 394 | 2 404 | Edge 414 | 9 424 | 5 434 | 8 444 | 8 454 | 10 464 | 10 474 | 11 484 | 4 494 | 4 504 | 7 514 | 7 524 | 3 534 | 6 544 | 552 | 560 | 568 | 576 | 584 | 592 | 600 | 608 | 616 | 627 | 635 | 643 | 651 | 659 | 667 | 675 | 683 | 691 | 699 | 710 | 718 | 726 | 734 | 742 | 750 | 758 | 766 | 774 | 782 | 793 | 801 | 809 | 817 | 825 | 833 | 841 | 849 | 857 | 865 | 876 | 884 | 892 | 900 | 908 | 916 | 924 | 932 | 940 | 948 | 949 | 950 | --------------------------------------------------------------------------------