Property definitions

rubix $ RubixCube :: defaultinit
# In-memory representation of a Rubix Cube
#
# Faces are represented in memory as if they were a flattened cube like:
#
# ~~~raw
#       B B B
#       B B B
#       B B B
#       U U U
#       U U U
#       U U U
# L L L F F F R R R
# L L L F F F R R R
# L L L F F F R R R
#       D D D
#       D D D
#       D D D
# ~~~
#
# All the commands detailed in the class respect the Singmaster notation
class RubixCube
	# Faces of the Cube
	#
	# 0 - Front
	# 1 - Left
	# 2 - Up
	# 3 - Right
	# 4 - Down
	# 5 - Back
	#
	# Each line is:
	#
	# 0 - Top line
	# 1 - Middle line
	# 2 - Down line
	var faces: Array[Array[Array[Int]]]

	# Build a new Rubix Cube with a solved layout
	init solved do
		var faces = new Array[Array[Array[Int]]]
		var colour = 0
		for i in [0 .. 6[ do
			var face = new Array[Array[Int]]
			faces.add face
			for j in [0 .. 3[ do
				var line = new Array[Int]
				for k in [0 .. 3[ do
					line.add colour
				end
				face.add line
			end
			colour += 1
		end
		init faces
	end

	# Build a new Rubix Cube with a scrambled layout
	#
	# NOTE: The layout is not random, but scrambled nonetheless
	init scrambled do
		var colours = once [0, 1, 2, 3, 4, 5]
		var colour_pos = 0
		var faces = new Array[Array[Array[Int]]]
		var increment = 1
		for i in [0 .. 6[ do
			var face = new Array[Array[Int]]
			faces.add face
			for j in [0 .. 3[ do
				var line = new Array[Int]
				for k in [0 .. 3[ do
					line.add colours[colour_pos]
					colour_pos += increment
					if colour_pos > 5 then
						increment = -1
						colour_pos = 5
					end
					if colour_pos < 0 then
						increment = 1
						colour_pos = 0
					end
				end
				face.add line
			end
		end
		init faces
	end

	# Reset the Rubix Cube to a solved position
	fun reset do
		for i in [0 .. 6[ do
			var face = faces[i]
			for j in [0 .. 3[ do
				var line = face[j]
				for k in [0 .. 3[ do
					line[k] = i
				end
			end
		end
	end

	# Checks if both objects are Rubix cubes and their content is equivalent
	#
	# NOTE: Rotationed versions are not yet considered equal
	redef fun ==(o) do
		if not o isa RubixCube then return false
		for mf in faces, tf in o.faces do
			for ml in mf, tl in tf do
				for mc in ml, tc in tl do if mc != tc then return false
			end
		end
		return true
	end

	# Is `self` a solved Rubix Cube ?
	fun is_solved: Bool do
		for face_id in [0 .. 6[ do
			var i = faces[face_id]
			var colour = i.first.first
			for j in [0 .. 3[ do
				var ln = i[j]
				for k in [0 .. 3[ do
					if ln[k] != colour then return false
				end
			end
		end
		return true
	end

	redef fun to_s do
		var buf = new Buffer
		buf.append(single_face(back_face))
		buf.append(single_face(top_face))
		buf.append(three_faces(left_face, front_face, right_face))
		buf.append(single_face(bottom_face))
		return buf.to_s
	end

	private fun single_face(face_id: Int): Text do
		var b = new Buffer
		var face = faces[face_id]
		for i in [0 .. 3[ do
			var ln = face[i]
			b.append("{" " * 6}{ln[0].rubix_colour} {ln[1].rubix_colour} {ln[2].rubix_colour}{" " * 6}\n")
		end
		return b
	end

	private fun three_faces(face1, face2, face3: Int): Text do
		var b = new Buffer
		var face_ids = [face1, face2, face3]
		for i in [0 .. 3[ do
			for j in face_ids do
				var face = faces[j]
				var ln = face[i]
				b.append("{ln[0].rubix_colour} {ln[1].rubix_colour} {ln[2].rubix_colour} ")
			end
			b.add '\n'
		end
		return b
	end

	private var rot_ln_buffer = new Array[Array[Int]].with_capacity(4)
	private fun rotate_line(ln_id: Int, direction: Int) do
		var line_data = rot_ln_buffer
		if line_data.is_empty then for i in [0 .. 4[ do line_data.add(new Array[Int])
		array1d_copy_to(faces[front_face][ln_id], line_data[0])
		array1d_copy_to(faces[left_face][ln_id], line_data[1])
		array1d_copy_to(faces[back_face][2 - ln_id], line_data[2])
		array1d_copy_to(faces[right_face][ln_id], line_data[3])
		if direction == counterclock then
			line_data[3].swap_at(0, 2)
			line_data[2].swap_at(0, 2)
			rot_ln_buffer.rotate_left
		else if direction == clock then
			line_data[1].swap_at(0, 2)
			line_data[2].swap_at(0, 2)
			rot_ln_buffer.rotate_right
		else
			abort
		end
		array1d_copy_to(line_data[0], faces[front_face][ln_id])
		array1d_copy_to(line_data[1], faces[left_face][ln_id])
		array1d_copy_to(line_data[2], faces[back_face][2 - ln_id])
		array1d_copy_to(line_data[3], faces[right_face][ln_id])
	end

	private var colbuf = new Array[Int].with_capacity(3)
	private fun coldata(face_id: Int, col_id: Int): Array[Int] do
		if colbuf.is_empty then for i in [0 .. 3[ do colbuf.add 0
		var face = faces[face_id]
		for i in [0 .. 3[ do colbuf[i] = face[i][col_id]
		return colbuf
	end

	private fun set_coldata(face_id, col_id: Int, coldata: Array[Int]) do
		var face = faces[face_id]
		for i in [0 .. 3[ do face[i][col_id] = coldata[i]
	end

	private var rot_col_buffer = new Array[Array[Int]].with_capacity(4)
	private fun rotate_column(col_id: Int, direction: Int) do
		var col_data = rot_col_buffer
		if col_data.is_empty then for i in [0 .. 4[ do col_data.add(new Array[Int])
		array1d_copy_to(coldata(front_face, col_id), rot_col_buffer[0])
		array1d_copy_to(coldata(top_face, col_id), rot_col_buffer[1])
		array1d_copy_to(coldata(back_face, col_id), rot_col_buffer[2])
		array1d_copy_to(coldata(bottom_face, col_id), rot_col_buffer[3])
		if direction == clock then
			rot_col_buffer.rotate_left
		else if direction == counterclock then
			rot_col_buffer.rotate_right
		else
			abort
		end
		set_coldata(front_face, col_id, rot_col_buffer[0])
		set_coldata(top_face, col_id, rot_col_buffer[1])
		set_coldata(back_face, col_id, rot_col_buffer[2])
		set_coldata(bottom_face, col_id, rot_col_buffer[3])
	end

	private var r90_cache = new Array[Array[Int]]
	private fun rotate_l90_face(face_id: Int) do
		var lines = r90_cache
		if lines.is_empty then for i in [0 .. 3[ do lines.add(new Array[Int])
		array1d_copy_to(faces[face_id][top_ln], lines[0])
		array1d_copy_to(faces[face_id][mid_ln], lines[1])
		array1d_copy_to(faces[face_id][bottom_ln], lines[2])
		for i in [0 .. 3[ do lines[i].swap_at(0, 2)
		set_coldata(face_id, left_col, lines[0])
		set_coldata(face_id, mid_col, lines[1])
		set_coldata(face_id, right_col, lines[2])
	end

	private fun rotate_r90_face(face_id: Int) do
		var lines = r90_cache
		if lines.is_empty then for i in [0 .. 3[ do lines.add(new Array[Int])
		array1d_copy_to(faces[face_id][top_ln], lines[0])
		array1d_copy_to(faces[face_id][mid_ln], lines[1])
		array1d_copy_to(faces[face_id][bottom_ln], lines[2])
		set_coldata(face_id, right_col, lines[0])
		set_coldata(face_id, mid_col, lines[1])
		set_coldata(face_id, left_col, lines[2])
	end

	# U command
	fun clock_U do
		rotate_line(top_ln, clock)
		rotate_r90_face(top_face)
	end

	# U' command
	fun cclock_U do
		rotate_line(top_ln, counterclock)
		rotate_l90_face(top_face)
	end

	# D command
	fun clock_D do
		rotate_line(bottom_ln, counterclock)
		rotate_r90_face(bottom_face)
	end

	# D' command
	fun cclock_D do
		rotate_line(bottom_ln, clock)
		rotate_l90_face(bottom_face)
	end

	# L command
	fun clock_L do
		rotate_column(left_col, clock)
		rotate_r90_face(left_face)
	end

	# L' command
	fun cclock_L do
		rotate_column(left_col, counterclock)
		rotate_l90_face(left_face)
	end

	# R command
	fun clock_R do
		rotate_column(right_col, counterclock)
		rotate_r90_face(right_face)
	end

	# R' command
	fun cclock_R do
		rotate_column(right_col, clock)
		rotate_l90_face(right_face)
	end

	# M command
	fun clock_M do rotate_column(mid_col, clock)

	# M' command
	fun cclock_M do rotate_column(mid_col, counterclock)

	# E command
	fun clock_E do rotate_line(mid_ln, counterclock)

	# E' command
	fun cclock_E do rotate_line(mid_ln, clock)

	# F command
	fun clock_F do
		cube_Y_rotation
		clock_L
		ccube_Y_rotation
	end

	# F' command
	fun cclock_F do
		cube_Y_rotation
		cclock_L
		ccube_Y_rotation
	end

	# B command
	fun clock_B do
		ccube_Y_rotation
		clock_L
		cube_Y_rotation
	end

	# B' command
	fun cclock_B do
		ccube_Y_rotation
		cclock_L
		cube_Y_rotation
	end

	# S command
	fun clock_S do
		cube_Y_rotation
		clock_M
		ccube_Y_rotation
	end

	# S' command
	fun cclock_S do
		cube_Y_rotation
		cclock_M
		ccube_Y_rotation
	end

	# u command
	fun clock_u do
		clock_U
		cclock_E
	end

	# u' command
	fun cclock_u do
		cclock_U
		clock_E
	end

	# l command
	fun clock_l do
		clock_L
		clock_M
	end

	# l' command
	fun cclock_l do
		cclock_L
		cclock_M
	end

	# f command
	fun clock_f do
		clock_F
		clock_S
	end

	# f' command
	fun cclock_f do
		cclock_F
		cclock_S
	end

	# r command
	fun clock_r do
		clock_R
		cclock_M
	end

	# r' command
	fun cclock_r do
		cclock_R
		clock_M
	end

	# b command
	fun clock_b do
		clock_B
		cclock_S
	end

	# b' command
	fun cclock_b do
		cclock_B
		clock_S
	end

	# d command
	fun clock_d do
		clock_D
		clock_E
	end

	# d' command
	fun cclock_d do
		cclock_D
		cclock_E
	end

	# Y command
	fun cube_Y_rotation do
		clock_U
		cclock_E
		cclock_D
	end

	# Y' command
	fun ccube_Y_rotation do
		cclock_U
		clock_E
		clock_D
	end

	# X command
	fun cube_X_rotation do
		cclock_L
		cclock_M
		clock_R
	end

	# X' command
	fun ccube_X_rotation do
		clock_L
		clock_M
		cclock_R
	end

	# Z command
	fun cube_Z_rotation do
		ccube_Y_rotation
		cube_X_rotation
		cube_Y_rotation
	end

	# Z' command
	fun ccube_Z_rotation do
		cube_Y_rotation
		cube_X_rotation
		ccube_Y_rotation
	end

	# Applies a command `cmd` to `self`, returns the number of operations performed during the command
	fun do_cmd(cmd: String): Int do
		if cmd == "" then return 0
		var iters = 1
		var cmdln = cmd.length
		if cmd[cmdln - 1] == '2' then
			iters = 2
		end
		var cmd1 = cmd[0]
		var cmd2 = '\0'
		if cmdln > 1 then
			cmd2 = cmd[1]
			if cmd2 == '2' then cmd2 = '\0'
		end
		for i in [1 .. iters] do
			if cmd1 == 'U' then
				if cmd2 == '\'' then
					cclock_U
				else
					clock_U
				end
			else if cmd1 == 'L' then
				if cmd2 == '\'' then
					cclock_L
				else
					clock_L
				end
			else if cmd1 == 'F' then
				if cmd2 == '\'' then
					cclock_F
				else
					clock_F
				end
			else if cmd1 == 'R' then
				if cmd2 == '\'' then
					cclock_R
				else
					clock_R
				end
			else if cmd1 == 'B' then
				if cmd2 == '\'' then
					cclock_B
				else
					clock_B
				end
			else if cmd1 == 'D' then
				if cmd2 == '\'' then
					cclock_D
				else
					clock_D
				end
			else if cmd1 == 'M' then
				if cmd2 == '\'' then
					cclock_M
				else
					clock_M
				end
			else if cmd1 == 'E' then
				if cmd2 == '\'' then
					cclock_E
				else
					clock_E
				end
			else if cmd1 == 'S' then
				if cmd2 == '\'' then
					cclock_S
				else
					clock_S
				end
			else if cmd1 == 'u' then
				if cmd2 == '\'' then
					cclock_u
				else
					clock_u
				end
			else if cmd1 == 'l' then
				if cmd2 == '\'' then
					cclock_l
				else
					clock_l
				end
			else if cmd1 == 'f' then
				if cmd2 == '\'' then
					cclock_f
				else
					clock_f
				end
			else if cmd1 == 'r' then
				if cmd2 == '\'' then
					cclock_r
				else
					clock_r
				end
			else if cmd1 == 'b' then
				if cmd2 == '\'' then
					cclock_b
				else
					clock_b
				end
			else if cmd1 == 'd' then
				if cmd2 == '\'' then
					cclock_d
				else
					clock_d
				end
			else if cmd1 == 'X' then
				if cmd2 == '\'' then
					ccube_X_rotation
				else
					cube_X_rotation
				end
			else if cmd1 == 'Y' then
				if cmd2 == '\'' then
					ccube_Y_rotation
				else
					cube_Y_rotation
				end
			else if cmd1 == 'Z' then
				if cmd2 == '\'' then
					ccube_Z_rotation
				else
					cube_Z_rotation
				end
			else
				abort
			end
		end
		return iters
	end
end
lib/rubix/rubix.nit:77,1--666,3