Code Sketch


kojo
By: Mhalsakant School
Category: Programming
import javax.swing._
import javax.swing.border._
import java.awt._
import java.awt.event._
import java.awt.geom._

// ============================================================
// EASY KOJO SCALA QUESTION PAPER
// 30 MINUTES / 20 QUESTIONS / EASY LEVEL
// ============================================================

val frame = new JFrame("Easy Kojo Scala - 30 Minute Easy Level Question Paper")
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
frame.setSize(1240, 820)
frame.setMinimumSize(new Dimension(1040, 700))
frame.setLocationRelativeTo(null)

val BG = new Color(20, 26, 38)
val PANEL = new Color(30, 39, 55)
val PANEL2 = new Color(39, 50, 69)
val TEXT = new Color(242, 245, 250)
val MUTED = new Color(180, 192, 208)
val ACCENT = new Color(88, 166, 255)
val GOOD = new Color(78, 190, 120)
val BAD = new Color(235, 95, 95)
val GOLD = new Color(245, 190, 70)

val mainPanel = new JPanel(new BorderLayout(10, 10))
mainPanel.setBackground(BG)
mainPanel.setBorder(new EmptyBorder(10, 10, 10, 10))

// ============================================================
// QUESTION BANK
// ============================================================

val questionText = Array[String](
  "Complete the Kojo command: forward(______)",
  "Complete the turn command so the turtle makes a right angle: right(______)",
  "A square has how many sides?",
  "Complete the code to draw a square using repeat: ______(4) { forward(60); right(90) }",
  "The shown square has equal sides. What is the value of each angle in a square?",
  "Complete the triangle code: forward(80); right(______); forward(80)",
  "How many sides does the shown triangle have?",
  "Which command draws a circle with radius 50?",
  "Complete: val side = ______",
  "Which command prints a message in Scala?",
  "Complete the condition: if (score ______ 10) { println(\"Great\") }",
  "Which operator means NOT EQUAL in Scala?",
  "Complete the pen command that lifts the pen: ______()",
  "Complete the pen command that puts the pen down: ______()",
  "Which command clears the Kojo drawing?",
  "Complete the position command: ______(100, 50)",
  "For a loop from 1 to 5, complete: for (i <- 1 ______ 5) { println(i) }",
  "Complete the function name: def ______(): Unit = { println(\"Hi\") }",
  "The shown circle has how many straight sides?",
  "Complete the update: var score = 0; score ______ 1"
)

val questionType = Array[Int](
  0, 0, 1, 0, 1,
  0, 2, 1, 0, 1,
  0, 1, 0, 0, 1,
  0, 0, 0, 2, 0
)

val answerKey = Array[String](
  "100|100.0",
  "90|90.0",
  "4",
  "repeat",
  "90|90.0",
  "120|120.0",
  "3",
  "circle(50)",
  "50|50.0",
  "println|println(\"Hello\")",
  ">",
  "!=",
  "penUp|penup",
  "penDown|pendown",
  "cleari|cleari()",
  "setPosition(100,50)|setPosition(100, 50)",
  "to",
  "greet",
  "0|zero",
  "+="
)

val codeTemplate = Array[String](
  "forward(______)\n",
  "right(______)\n",
  "Answer the number only.\n",
  "______(4) {\n  forward(60)\n  right(90)\n}\n",
  "Square angle = ______ degrees\n",
  "forward(80)\nright(______)\nforward(80)\n",
  "Look at the triangle diagram and answer the number.\n",
  "______(50)\n",
  "val side = ______\n",
  "______(\"Hello\")\n",
  "if (score ______ 10) {\n  println(\"Great\")\n}\n",
  "x ______ y\n",
  "______()\nforward(80)\n",
  "______()\nforward(80)\n",
  "______()\n",
  "______(100, 50)\n",
  "for (i <- 1 ______ 5) {\n  println(i)\n}\n",
  "def ______(): Unit = {\n  println(\"Hi\")\n}\n",
  "Look at the circle diagram and answer the number.\n",
  "var score = 0\nscore ______ 1\n"
)

val optionBank = Array[Array[String]](
  Array[String](),
  Array[String](),
  Array[String]("3", "4", "5", "6"),
  Array[String](),
  Array[String]("45", "60", "90", "120"),
  Array[String](),
  Array[String](),
  Array[String]("circle(50)", "square(50)", "drawCircle(50)", "ring(50)"),
  Array[String](),
  Array[String]("print", "println", "show", "echo"),
  Array[String](),
  Array[String]("!=", "==", "=>", "=<"),
  Array[String](),
  Array[String](),
  Array[String]("cleari", "clearAll", "eraseAll", "clear"),
  Array[String](),
  Array[String](),
  Array[String](),
  Array[String](),
  Array[String]()
)

val diagramType = Array[Int](
  0, 0, 0, 0, 1,
  0, 2, 0, 0, 0,
  0, 0, 0, 0, 0,
  0, 0, 0, 3, 0
)

// ============================================================
// STATE
// ============================================================

var currentQuestion = 0
var submitted = false
var paperFinished = false
var resultVisible = false
var secondsLeft = 30 * 60

val userAnswers = Array.fill[String](questionText.length)("")
val visited = Array.fill[Boolean](questionText.length)(false)

// ============================================================
// HEADER
// ============================================================

val titleLabel = new JLabel(
  "EASY KOJO SCALA QUESTION PAPER",
  SwingConstants.CENTER
)
titleLabel.setForeground(TEXT)
titleLabel.setFont(new Font("SansSerif", Font.BOLD, 28))


val subtitleLabel = new JLabel(
  "Easy Level | 20 Questions | Fill in the Blanks | Simple Code | Shapes | Basic Logic",
  SwingConstants.CENTER
)
subtitleLabel.setForeground(MUTED)
subtitleLabel.setFont(new Font("SansSerif", Font.PLAIN, 14))

val headerCenter = new JPanel(new GridLayout(2, 1, 2, 2))
headerCenter.setBackground(BG)
headerCenter.add(titleLabel)
headerCenter.add(subtitleLabel)

val timerLabel = new JLabel("TIME 30:00", SwingConstants.CENTER)
timerLabel.setForeground(GOLD)
timerLabel.setFont(new Font("Monospaced", Font.BOLD, 25))
timerLabel.setBorder(BorderFactory.createLineBorder(GOLD, 2))


val header = new JPanel(new BorderLayout(12, 4))
header.setBackground(BG)
header.add(headerCenter, BorderLayout.CENTER)
header.add(timerLabel, BorderLayout.EAST)
mainPanel.add(header, BorderLayout.NORTH)

// ============================================================
// INFO STRIP
// ============================================================

val infoLabel = new JLabel("Question 1 of 20 | Answer every question. The paper stops automatically at 30:00.")
infoLabel.setForeground(TEXT)
infoLabel.setFont(new Font("SansSerif", Font.BOLD, 15))
infoLabel.setBorder(BorderFactory.createEmptyBorder(8, 10, 8, 10))
infoLabel.setBackground(PANEL2)
infoLabel.setOpaque(true)

val progressLabel = new JLabel("Answered: 0 / 20", SwingConstants.RIGHT)
progressLabel.setForeground(GOOD)
progressLabel.setFont(new Font("SansSerif", Font.BOLD, 14))

val infoStrip = new JPanel(new BorderLayout())
infoStrip.setBackground(PANEL2)
infoStrip.add(infoLabel, BorderLayout.CENTER)
infoStrip.add(progressLabel, BorderLayout.EAST)

// ============================================================
// QUESTION AREA
// ============================================================

val questionTitle = new JLabel("QUESTION 1", SwingConstants.CENTER)
questionTitle.setForeground(ACCENT)
questionTitle.setFont(new Font("SansSerif", Font.BOLD, 24))

val questionTextArea = new JTextArea()
questionTextArea.setEditable(false)
questionTextArea.setLineWrap(true)
questionTextArea.setWrapStyleWord(true)
questionTextArea.setFont(new Font("SansSerif", Font.BOLD, 18))
questionTextArea.setForeground(TEXT)
questionTextArea.setBackground(PANEL)
questionTextArea.setBorder(new EmptyBorder(12, 12, 12, 12))

val codeArea = new JTextArea()
codeArea.setEditable(false)
codeArea.setFont(new Font("Monospaced", Font.PLAIN, 16))
codeArea.setForeground(new Color(190, 235, 255))
codeArea.setBackground(new Color(16, 22, 32))
codeArea.setBorder(BorderFactory.createTitledBorder(
  BorderFactory.createLineBorder(new Color(70, 110, 150), 1),
  "CODE / QUESTION WORK AREA"
))

val answerField = new JTextField()
answerField.setFont(new Font("SansSerif", Font.PLAIN, 19))
answerField.setBackground(new Color(248, 250, 252))
answerField.setForeground(new Color(20, 26, 38))
answerField.setToolTipText("Type your answer here")

val answerLabel = new JLabel("Your Answer:")
answerLabel.setForeground(TEXT)
answerLabel.setFont(new Font("SansSerif", Font.BOLD, 16))

val optionBox = new JComboBox[String]()
optionBox.setFont(new Font("SansSerif", Font.PLAIN, 18))
optionBox.setBackground(new Color(248, 250, 252))
optionBox.setForeground(new Color(20, 26, 38))
optionBox.setSelectedIndex(-1)

val workPanel = new JPanel(new BorderLayout(8, 8))
workPanel.setBackground(PANEL)
workPanel.setBorder(new EmptyBorder(10, 10, 10, 10))

val answerPanel = new JPanel(new BorderLayout(8, 8))
answerPanel.setBackground(PANEL)
answerPanel.add(answerLabel, BorderLayout.WEST)
answerPanel.add(answerField, BorderLayout.CENTER)
answerPanel.add(optionBox, BorderLayout.SOUTH)

workPanel.add(new JScrollPane(codeArea), BorderLayout.CENTER)
workPanel.add(answerPanel, BorderLayout.SOUTH)

val leftTop = new JPanel(new BorderLayout(8, 8))
leftTop.setBackground(PANEL)
leftTop.add(questionTitle, BorderLayout.NORTH)
leftTop.add(new JScrollPane(questionTextArea), BorderLayout.CENTER)

val leftPanel = new JPanel(new BorderLayout(8, 8))
leftPanel.setBackground(BG)
leftPanel.add(leftTop, BorderLayout.NORTH)
leftPanel.add(workPanel, BorderLayout.CENTER)

// ============================================================
// DIAGRAM PANEL
// ============================================================

val diagramPanel = new JPanel() {
  override def paintComponent(g0: Graphics): Unit = {
    super.paintComponent(g0)
    val g = g0.asInstanceOf[Graphics2D]
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)

    g.setColor(PANEL)
    g.fillRect(0, 0, getWidth, getHeight)

    g.setColor(TEXT)
    g.setFont(new Font("SansSerif", Font.BOLD, 21))
    g.drawString("SHAPE / DIAGRAM", 26, 35)

    val kind = diagramType(currentQuestion)
    val cx = getWidth / 2
    val cy = getHeight / 2 + 15

    if (kind == 1) {
      g.setColor(new Color(88, 166, 255))
      g.fillRoundRect(cx - 110, cy - 110, 220, 220, 14, 14)
      g.setColor(TEXT)
      g.setStroke(new BasicStroke(3.0f))
      g.drawRoundRect(cx - 110, cy - 110, 220, 220, 14, 14)
      g.setFont(new Font("SansSerif", Font.BOLD, 18))
      g.drawString("SQUARE", cx - 48, cy + 145)
      g.drawString("4 equal sides", cx - 65, cy + 175)
    } else if (kind == 2) {
      val tri = new Polygon()
      tri.addPoint(cx, cy - 125)
      tri.addPoint(cx - 130, cy + 105)
      tri.addPoint(cx + 130, cy + 105)
      g.setColor(new Color(255, 148, 82))
      g.fillPolygon(tri)
      g.setColor(TEXT)
      g.setStroke(new BasicStroke(3.0f))
      g.drawPolygon(tri)
      g.setFont(new Font("SansSerif", Font.BOLD, 18))
      g.drawString("TRIANGLE", cx - 57, cy + 145)
      g.drawString("3 sides", cx - 35, cy + 175)
    } else if (kind == 3 || kind == 4) {
      g.setColor(new Color(110, 210, 150))
      g.fillOval(cx - 115, cy - 115, 230, 230)
      g.setColor(TEXT)
      g.setStroke(new BasicStroke(3.0f))
      g.drawOval(cx - 115, cy - 115, 230, 230)
      g.setFont(new Font("SansSerif", Font.BOLD, 18))
      g.drawString("CIRCLE", cx - 38, cy + 145)
      g.drawString("0 straight sides", cx - 70, cy + 175)
    } else {
      g.setColor(MUTED)
      g.setFont(new Font("SansSerif", Font.PLAIN, 18))
      g.drawString("Diagram appears for shape questions.", 45, cy - 10)
      g.drawString("Read the question and choose/fill the answer.", 45, cy + 25)
    }
  }
}
diagramPanel.setPreferredSize(new Dimension(360, 520))
diagramPanel.setBorder(BorderFactory.createLineBorder(new Color(65, 80, 105), 2))

val progressBar = new JProgressBar(0, questionText.length)
progressBar.setValue(0)
progressBar.setStringPainted(true)
progressBar.setForeground(ACCENT)
progressBar.setBackground(PANEL2)

val rightPanel = new JPanel(new BorderLayout(8, 8))
rightPanel.setBackground(BG)
rightPanel.add(diagramPanel, BorderLayout.CENTER)
rightPanel.add(progressBar, BorderLayout.SOUTH)

val split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel)
split.setResizeWeight(0.73)
split.setBorder(null)
split.setDividerSize(7)

val centerPanel = new JPanel(new BorderLayout(8, 8))
centerPanel.setBackground(BG)
centerPanel.add(infoStrip, BorderLayout.NORTH)
centerPanel.add(split, BorderLayout.CENTER)
mainPanel.add(centerPanel, BorderLayout.CENTER)

// ============================================================
// BOTTOM CONTROLS
// ============================================================

val previousButton = new JButton("< Previous")
val nextButton = new JButton("Next >")
val finishButton = new JButton("FINISH PAPER")
val resultButton = new JButton("SEE YOUR RESULT")
val resetButton = new JButton("NEW PAPER")

val questionBox = new JComboBox[String]()
var qi = 1
while (qi <= questionText.length) {
  questionBox.addItem("Question " + qi)
  qi += 1
}
questionBox.setSelectedIndex(0)
questionBox.setFont(new Font("SansSerif", Font.BOLD, 14))

val bottomPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 8, 8))
bottomPanel.setBackground(BG)
bottomPanel.add(previousButton)
bottomPanel.add(questionBox)
bottomPanel.add(nextButton)
bottomPanel.add(finishButton)
bottomPanel.add(resultButton)
bottomPanel.add(resetButton)

mainPanel.add(bottomPanel, BorderLayout.SOUTH)

// ============================================================
// RESULT VIEW
// ============================================================

val resultArea = new JTextArea()
resultArea.setEditable(false)
resultArea.setLineWrap(true)
resultArea.setWrapStyleWord(true)
resultArea.setFont(new Font("Monospaced", Font.PLAIN, 16))
resultArea.setForeground(TEXT)
resultArea.setBackground(PANEL)
resultArea.setBorder(new EmptyBorder(16, 16, 16, 16))

val resultPanel = new JPanel(new BorderLayout(10, 10))
resultPanel.setBackground(BG)
resultPanel.setBorder(new EmptyBorder(20, 20, 20, 20))

val resultTitle = new JLabel("YOUR RESULT", SwingConstants.CENTER)
resultTitle.setForeground(GOLD)
resultTitle.setFont(new Font("SansSerif", Font.BOLD, 30))
resultPanel.add(resultTitle, BorderLayout.NORTH)
resultPanel.add(new JScrollPane(resultArea), BorderLayout.CENTER)

// ============================================================
// HELPERS
// ============================================================

def cleanAnswer(s: String): String = {
  s.toLowerCase
    .trim
    .replace("`", "")
    .replace(" ", "")
    .replace("\t", "")
    .replace("\r", "")
    .replace("\n", "")
}

def isCorrect(index: Int): Boolean = {
  val got = cleanAnswer(userAnswers(index))
  val accepted = answerKey(index).split("\\|")
  var i = 0
  var ok = false
  while (i < accepted.length) {
    if (got == cleanAnswer(accepted(i))) {
      ok = true
    }
    i += 1
  }
  ok
}

def answeredCount(): Int = {
  var count = 0
  var i = 0
  while (i < userAnswers.length) {
    if (userAnswers(i).trim.nonEmpty) count += 1
    i += 1
  }
  count
}

def formatTime(sec: Int): String = {
  val minutes = sec / 60
  val seconds = sec % 60
  val mm = if (minutes < 10) "0" + minutes else minutes.toString
  val ss = if (seconds < 10) "0" + seconds else seconds.toString
  mm + ":" + ss
}

def updateTimerLabel(): Unit = {
  timerLabel.setText("TIME " + formatTime(secondsLeft))
  if (secondsLeft <= 60) {
    timerLabel.setForeground(BAD)
  } else {
    timerLabel.setForeground(GOLD)
  }
}

def saveCurrentAnswer(): Unit = {
  if (!paperFinished) {
    if (optionBox.isVisible) {
      val selected = optionBox.getSelectedItem
      if (selected != null) {
        userAnswers(currentQuestion) = selected.toString
      }
    } else {
      userAnswers(currentQuestion) = answerField.getText
    }
    visited(currentQuestion) = true
  }
}

def updateProgress(): Unit = {
  val count = answeredCount()
  progressLabel.setText("Answered: " + count + " / " + questionText.length)
  progressBar.setValue(count)
  progressBar.setString(count + " answered")
}

def showQuestion(index: Int): Unit = {
  if (index < 0 || index >= questionText.length) return

  saveCurrentAnswer()
  currentQuestion = index
  questionTitle.setText("QUESTION " + (currentQuestion + 1))
  questionTextArea.setText(questionText(currentQuestion))
  codeArea.setText(codeTemplate(currentQuestion))
  infoLabel.setText("Question " + (currentQuestion + 1) + " of " + questionText.length + " | EASY LEVEL | Read the code and diagram carefully.")

  if (optionBank(currentQuestion).length > 0) {
    optionBox.removeAllItems()
    var j = 0
    while (j < optionBank(currentQuestion).length) {
      optionBox.addItem(optionBank(currentQuestion)(j))
      j += 1
    }
    optionBox.setVisible(true)
    answerField.setVisible(false)
    answerLabel.setText("Choose:")
    val old = userAnswers(currentQuestion)
    if (old.nonEmpty) {
      var k = 0
      while (k < optionBank(currentQuestion).length) {
        if (optionBank(currentQuestion)(k) == old) optionBox.setSelectedIndex(k)
        k += 1
      }
    } else {
      optionBox.setSelectedIndex(-1)
    }
  } else {
    optionBox.setVisible(false)
    answerField.setVisible(true)
    answerLabel.setText("Your Answer:")
    answerField.setText(userAnswers(currentQuestion))
    SwingUtilities.invokeLater(new Runnable {
      def run(): Unit = {
        if (!paperFinished && !resultVisible) answerField.requestFocusInWindow()
      }
    })
  }

  diagramPanel.repaint()
  updateProgress()
  workPanel.revalidate()
  workPanel.repaint()
}

def buildResultText(): String = {
  var correct = 0
  var wrong = 0
  var unanswered = 0
  var i = 0
  val sb = new StringBuilder()

  while (i < questionText.length) {
    if (userAnswers(i).trim.isEmpty) {
      unanswered += 1
    } else if (isCorrect(i)) {
      correct += 1
    } else {
      wrong += 1
    }
    i += 1
  }

  val total = questionText.length
  val percent = (correct.toDouble * 100.0) / total.toDouble

  sb.append("==============================================\n")
  sb.append("             EASY KOJO SCALA RESULT\n")
  sb.append("==============================================\n\n")
  sb.append("Total Questions : " + total + "\n")
  sb.append("Correct         : " + correct + "\n")
  sb.append("Wrong           : " + wrong + "\n")
  sb.append("Unanswered      : " + unanswered + "\n")
  sb.append("Score           : " + correct + " / " + total + "\n")
  sb.append("Percentage      : " + (Math.round(percent * 100.0) / 100.0) + "%\n")
  sb.append("Time Left       : " + formatTime(secondsLeft) + "\n")
  sb.append("\n----------------------------------------------\n")
  sb.append("WRONG / UNANSWERED REVIEW\n")
  sb.append("----------------------------------------------\n")

  i = 0
  while (i < questionText.length) {
    if (!isCorrect(i)) {
      sb.append("\nQuestion " + (i + 1) + ": " + questionText(i) + "\n")
      sb.append("Your Answer : " + (if (userAnswers(i).trim.isEmpty) "Not answered" else userAnswers(i)) + "\n")
      sb.append("Correct Ans : " + answerKey(i).replace("|", " / ") + "\n")
    }
    i += 1
  }

  sb.append("\n----------------------------------------------\n")
  sb.append("NOTE: This is a beginner practice paper based on simple Kojo/Scala concepts.\n")
  sb.append("Keep practicing commands, shapes, variables, conditions, loops and functions.\n")
  sb.append("==============================================\n")
  sb.toString
}

def showResult(): Unit = {
  if (!paperFinished) return
  resultVisible = true
  resultArea.setText(buildResultText())
  centerPanel.removeAll()
  centerPanel.add(resultPanel, BorderLayout.CENTER)
  centerPanel.revalidate()
  centerPanel.repaint()
  infoLabel.setText("RESULT READY - You can start a new paper from the NEW PAPER button.")
  previousButton.setEnabled(false)
  nextButton.setEnabled(false)
  questionBox.setEnabled(false)
  finishButton.setEnabled(false)
  resultButton.setEnabled(false)
  answerField.setEnabled(false)
  optionBox.setEnabled(false)
}

def finishPaper(): Unit = {
  if (paperFinished) return
  saveCurrentAnswer()
  paperFinished = true
  submitted = true
  secondsLeft = Math.max(0, secondsLeft)
  updateTimerLabel()
  answerField.setEnabled(false)
  optionBox.setEnabled(false)
  previousButton.setEnabled(false)
  nextButton.setEnabled(false)
  questionBox.setEnabled(false)
  finishButton.setEnabled(false)
  resultButton.setEnabled(true)
  infoLabel.setText("PAPER STOPPED. Press SEE YOUR RESULT to open the full result report.")
}

def resetPaper(): Unit = {
  var i = 0
  while (i < userAnswers.length) {
    userAnswers(i) = ""
    visited(i) = false
    i += 1
  }
  currentQuestion = 0
  submitted = false
  paperFinished = false
  resultVisible = false
  secondsLeft = 30 * 60
  answerField.setEnabled(true)
  optionBox.setEnabled(true)
  previousButton.setEnabled(true)
  nextButton.setEnabled(true)
  questionBox.setEnabled(true)
  finishButton.setEnabled(true)
  resultButton.setEnabled(false)

  centerPanel.removeAll()
  centerPanel.add(infoStrip, BorderLayout.NORTH)
  centerPanel.add(split, BorderLayout.CENTER)
  centerPanel.revalidate()
  centerPanel.repaint()
  showQuestion(0)
  updateTimerLabel()
  updateProgress()
}

// ============================================================
// LISTENERS
// ============================================================

previousButton.addActionListener(new ActionListener {
  override def actionPerformed(e: ActionEvent): Unit = {
    if (!paperFinished && currentQuestion > 0) {
      showQuestion(currentQuestion - 1)
    }
  }
})

nextButton.addActionListener(new ActionListener {
  override def actionPerformed(e: ActionEvent): Unit = {
    if (!paperFinished) {
      saveCurrentAnswer()
      if (currentQuestion < questionText.length - 1) {
        showQuestion(currentQuestion + 1)
      }
    }
  }
})

questionBox.addActionListener(new ActionListener {
  override def actionPerformed(e: ActionEvent): Unit = {
    if (!paperFinished) {
      val selected = questionBox.getSelectedIndex
      if (selected >= 0 && selected < questionText.length && selected != currentQuestion) {
        showQuestion(selected)
      }
    }
  }
})

finishButton.addActionListener(new ActionListener {
  override def actionPerformed(e: ActionEvent): Unit = {
    val confirm = JOptionPane.showConfirmDialog(
      frame,
      "Finish the paper now? You can then see your result.",
      "Finish Paper",
      JOptionPane.YES_NO_OPTION
    )
    if (confirm == JOptionPane.YES_OPTION) {
      finishPaper()
    }
  }
})

resultButton.addActionListener(new ActionListener {
  override def actionPerformed(e: ActionEvent): Unit = {
    showResult()
  }
})

resetButton.addActionListener(new ActionListener {
  override def actionPerformed(e: ActionEvent): Unit = {
    resetPaper()
  }
})

answerField.addKeyListener(new KeyAdapter {
  override def keyReleased(e: KeyEvent): Unit = {
    if (!paperFinished) {
      userAnswers(currentQuestion) = answerField.getText
      visited(currentQuestion) = true
      updateProgress()
    }
  }
})

optionBox.addActionListener(new ActionListener {
  override def actionPerformed(e: ActionEvent): Unit = {
    if (!paperFinished) {
      val selected = optionBox.getSelectedItem
      if (selected != null) {
        userAnswers(currentQuestion) = selected.toString
        visited(currentQuestion) = true
        updateProgress()
      }
    }
  }
})

// ============================================================
// KEYBOARD SHORTCUTS
// ============================================================

mainPanel.setFocusable(true)
mainPanel.addKeyListener(new KeyAdapter {
  override def keyPressed(e: KeyEvent): Unit = {
    if (!paperFinished) {
      if (e.getKeyCode == KeyEvent.VK_RIGHT && currentQuestion < questionText.length - 1) {
        saveCurrentAnswer()
        showQuestion(currentQuestion + 1)
      } else if (e.getKeyCode == KeyEvent.VK_LEFT && currentQuestion > 0) {
        saveCurrentAnswer()
        showQuestion(currentQuestion - 1)
      }
    }
  }
})

// ============================================================
// TIMER - EXACTLY 30 MINUTES
// ============================================================

val paperTimer = new javax.swing.Timer(1000, new ActionListener {
  override def actionPerformed(e: ActionEvent): Unit = {
    if (!paperFinished) {
      secondsLeft -= 1
      if (secondsLeft <= 0) {
        secondsLeft = 0
        updateTimerLabel()
        e.getSource.asInstanceOf[javax.swing.Timer].stop()
        JOptionPane.showMessageDialog(
          frame,
          "30 minutes are over. The paper is now closed.\nPress SEE YOUR RESULT.",
          "TIME UP",
          JOptionPane.INFORMATION_MESSAGE
        )
        finishPaper()
      } else {
        updateTimerLabel()
      }
    }
  }
})

// ============================================================
// INITIAL STATE / STARTUP
// ============================================================

resultButton.setEnabled(false)
showQuestion(0)
updateTimerLabel()
updateProgress()

frame.setContentPane(mainPanel)
frame.setVisible(true)
frame.toFront()
frame.requestFocus()
mainPanel.requestFocusInWindow()
paperTimer.start()