Code Sketch
yadnesh shahare using gemeni
Category: Programming
// --- Kojo 3x3 Dynamic Rubik's Cube: Scramble & Auto-Solve ---
cleari()
setBackground(Color(15, 15, 25))
case class Vector3D(x: Double, y: Double, z: Double)
val size = canvasBounds.width * 0.4
val viewAngleHalf = Math.toRadians(60.0 / 2.0)
// 3D Rotations
def rotateX(p: Vector3D, angle: Double): Vector3D = {
val rad = Math.toRadians(angle)
Vector3D(p.x, p.y * Math.cos(rad) - p.z * Math.sin(rad), p.y * Math.sin(rad) + p.z * Math.cos(rad))
}
def rotateY(p: Vector3D, angle: Double): Vector3D = {
val rad = Math.toRadians(angle)
Vector3D(p.x * Math.cos(rad) + p.z * Math.sin(rad), p.y, -p.x * Math.sin(rad) + p.z * Math.cos(rad))
}
def rotateZ(p: Vector3D, angle: Double): Vector3D = {
val rad = Math.toRadians(angle)
Vector3D(p.x * Math.cos(rad) - p.y * Math.sin(rad), p.x * Math.sin(rad) + p.y * Math.cos(rad), p.z)
}
def project(p: Vector3D): (Double, Double, Double) = {
val distance = size + 350
val pz = p.z + distance
val factor = size / (pz * Math.tan(viewAngleHalf))
(p.x * factor, p.y * factor, pz)
}
// Rubik's Colors
val faceColors = Map(
"FRONT" -> Color(255, 50, 50), // Red
"BACK" -> Color(255, 140, 0), // Orange
"TOP" -> Color(255, 255, 255), // White
"BOTTOM" -> Color(255, 215, 0), // Yellow
"RIGHT" -> Color(50, 205, 50), // Green
"LEFT" -> Color(30, 144, 255) // Blue
)
case class Piece(var pos: Vector3D, id: Int) {
val s = 30.0
val h = s / 2.0
def baseVertices: Seq[Vector3D] = Seq(
Vector3D(-h, -h, -h), Vector3D(h, -h, -h),
Vector3D(h, h, -h), Vector3D(-h, h, -h),
Vector3D(-h, -h, h), Vector3D(h, -h, h),
Vector3D(h, h, h), Vector3D(-h, h, h)
)
var currentRot = (0.0, 0.0, 0.0)
val faces = Seq(
("FRONT", Seq(4, 5, 6, 7), Vector3D(0, 0, 1)),
("BACK", Seq(1, 0, 3, 2), Vector3D(0, 0, -1)),
("TOP", Seq(3, 2, 6, 7), Vector3D(0, 1, 0)),
("BOTTOM", Seq(4, 5, 1, 0), Vector3D(0, -1, 0)),
("RIGHT", Seq(5, 1, 2, 6), Vector3D(1, 0, 0)),
("LEFT", Seq(0, 4, 7, 3), Vector3D(-1, 0, 0))
)
}
val spacing = 34.0
var pieces = (for {
x <- -1 to 1
y <- -1 to 1
z <- -1 to 1
if !(x == 0 && y == 0 && z == 0)
} yield Piece(Vector3D(x * spacing, y * spacing, z * spacing), x*100 + y*10 + z)).toBuffer
// Move Definition: (Axis: 'X'/'Y'/'Z', LayerIndex: -1/0/1, Direction: 1/-1)
case class Move(axis: Char, layer: Int, dir: Int)
val scrambleSequence = Seq(
Move('Y', 1, 1), // Top Layer Right
Move('X', 1, -1), // Right Layer Up
Move('Z', -1, 1), // Front Layer Clockwise
Move('Y', -1, -1), // Bottom Layer Left
Move('X', -1, 1), // Left Layer Down
Move('Z', 1, -1) // Back Layer
)
// Generate Solve Sequence by Inverting Scramble Moves
val solveSequence = scrambleSequence.reverse.map(m => Move(m.axis, m.layer, -m.dir))
val fullMoves = scrambleSequence ++ solveSequence
var currentMoveIdx = 0
var moveProgress = 0.0 // 0 to 90 degrees
val moveSpeed = 6.0 // Animation speed
val viewRotX = 25.0
val viewRotY = -35.0
def snapPosition(v: Double): Double = Math.round(v / spacing) * spacing
// Main Animation Loop
animateWithState(0) { state =>
erasePictures()
val activeMove = if (currentMoveIdx < fullMoves.size) Some(fullMoves(currentMoveIdx)) else None
// Update animation angle for active move
if (activeMove.isDefined) {
moveProgress += moveSpeed
if (moveProgress >= 90.0) {
// Snap pieces to grid after 90 degree turn
val m = activeMove.get
pieces.foreach { p =>
val inLayer = m.axis match {
case 'X' => Math.abs(p.pos.x - m.layer * spacing) < 5
case 'Y' => Math.abs(p.pos.y - m.layer * spacing) < 5
case 'Z' => Math.abs(p.pos.z - m.layer * spacing) < 5
}
if (inLayer) {
val rad = Math.toRadians(90.0 * m.dir)
val newPos = m.axis match {
case 'X' => rotateX(p.pos, 90.0 * m.dir)
case 'Y' => rotateY(p.pos, 90.0 * m.dir)
case 'Z' => rotateZ(p.pos, 90.0 * m.dir)
}
p.pos = Vector3D(snapPosition(newPos.x), snapPosition(newPos.y), snapPosition(newPos.z))
}
}
moveProgress = 0.0
currentMoveIdx += 1
}
}
// Draw Dynamic Status Text
val statusText = if (currentMoveIdx < scrambleSequence.size) "Scrambling Cube... (????? ????? ???)"
else if (currentMoveIdx < fullMoves.size) "Auto-Solving Cube... (????? ??????? ??? ???)"
else "Cube Solved! (????? ???????? ??????? ????!)"
val statusPic = Picture.text(statusText, 16)
statusPic.setPenColor(if (currentMoveIdx >= fullMoves.size) Color(50, 255, 50) else Color(255, 215, 0))
statusPic.setPosition(-canvasBounds.width/2 + 30, canvasBounds.height/2 - 40)
draw(statusPic)
// Collect & Sort Faces for 3D Rendering
var renderFaces = Seq.empty[(Seq[(Double, Double)], Color, Double)]
pieces.foreach { p =>
val isMoving = activeMove.exists { m =>
m.axis match {
case 'X' => Math.abs(p.pos.x - m.layer * spacing) < 5
case 'Y' => Math.abs(p.pos.y - m.layer * spacing) < 5
case 'Z' => Math.abs(p.pos.z - m.layer * spacing) < 5
}
}
val currentAnimAngle = if (isMoving) moveProgress * activeMove.get.dir else 0.0
p.faces.foreach { case (faceName, idxs, normal) =>
val rotatedVerts = idxs.map { i =>
var v = p.baseVertices(i)
// 1. Piece Local Placement
v = Vector3D(v.x + p.pos.x, v.y + p.pos.y, v.z + p.pos.z)
// 2. Layer Rotation Animation
if (isMoving) {
val m = activeMove.get
v = m.axis match {
case 'X' => rotateX(v, currentAnimAngle)
case 'Y' => rotateY(v, currentAnimAngle)
case 'Z' => rotateZ(v, currentAnimAngle)
}
}
// 3. Fixed Viewport Perspective Angle
val finalWorld = rotateY(rotateX(v, viewRotX), viewRotY)
project(finalWorld)
}
// Calculate Normal for Back-Face Culling
var animatedNormal = normal
if (isMoving) {
val m = activeMove.get
animatedNormal = m.axis match {
case 'X' => rotateX(animatedNormal, currentAnimAngle)
case 'Y' => rotateY(animatedNormal, currentAnimAngle)
case 'Z' => rotateZ(animatedNormal, currentAnimAngle)
}
}
val viewNormal = rotateY(rotateX(animatedNormal, viewRotX), viewRotY)
if (viewNormal.z < 0) {
val pts2D = rotatedVerts.map { case (px, py, _) => (px, py) }
val avgZ = rotatedVerts.map(_._3).sum / 4.0
renderFaces = renderFaces :+ (pts2D, faceColors(faceName), avgZ)
}
}
}
// Render Depth Sorted Faces
renderFaces.sortBy(_._3).reverse.foreach { case (pts, color, _) =>
val facePic = Picture.fromVertexShape { p =>
p.beginShape()
pts.foreach { case (x, y) => p.vertex(x, y) }
p.endShape()
}
facePic.setFillColor(color)
facePic.setPenColor(Color(20, 20, 20))
facePic.setPenThickness(2)
draw(facePic)
}
state + 1
}