├── 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 | 
5 |
6 | Downloads: [Cubic - Rubik Cube Simulator on Itch.io](https://andrew-wilkes.itch.io/cubic)
7 |
8 | 
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 | 
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 | 
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 | 
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 |
950 |
--------------------------------------------------------------------------------