Code Sketch


yadnesh shahare using gemeni
By: Mhalsakant School
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
}