Source Code

;; title: braingame
;; version: 1.0.0
;; summary: A simple on-chain quiz smart contract for Stacks blockchain
;; description: Create quizzes, test knowledge, score answers, and compete on the leaderboard

;; traits
;;

;; token definitions
;;

;; constants
;;

;; Error codes
(define-constant ERR_UNAUTHORIZED (err u100))
(define-constant ERR_QUIZ_NOT_FOUND (err u101))
(define-constant ERR_ALREADY_TAKEN (err u102))
(define-constant ERR_INVALID_ANSWERS (err u103))
(define-constant ERR_INVALID_QUIZ_DATA (err u104))
(define-constant ERR_UNDERFLOW (err u105))

;; Maximum questions per quiz
(define-constant MAX_QUESTIONS u20)

;; ============================================
;; data vars
;;

;; Counter for general testing
(define-data-var counter uint u0)

;; Quiz ID counter
(define-data-var next-quiz-id uint u0)

;; ============================================
;; data maps
;;

;; Quiz storage: quiz-id -> quiz data
(define-map quizzes
  uint
  {
    title: (string-ascii 100),
    creator: principal,
    question-count: uint,
    questions: (list 20 (string-ascii 200)),
    correct-answers: (list 20 uint),
    total-attempts: uint,
    created-at: uint
  }
)

;; Quiz scores: {quiz-id, player} -> score data
(define-map quiz-scores
  {quiz-id: uint, player: principal}
  {
    score: uint,
    percentage: uint,
    submitted-at: uint
  }
)

;; Player statistics: player -> stats
(define-map player-stats
  principal
  {
    quizzes-taken: uint,
    total-score: uint,
    best-score: uint
  }
)

;; ============================================
;; public functions
;;

;; --- Counter Functions (for testing) ---

;; Increment the counter
(define-public (increment)
  (let
    ((new-value (+ (var-get counter) u1)))
    (begin
      (var-set counter new-value)
      (print {
        event: "counter-incremented",
        caller: tx-sender,
        new-value: new-value,
        block-height: stacks-block-height
      })
      (ok new-value)
    )
  )
)

;; Decrement the counter
(define-public (decrement)
  (let 
    ((current-value (var-get counter)))
    (begin
      ;; Prevent underflow
      (asserts! (> current-value u0) ERR_UNDERFLOW)
      (let
        ((new-value (- current-value u1)))
        (begin
          (var-set counter new-value)
          (print {
            event: "counter-decremented",
            caller: tx-sender,
            new-value: new-value,
            block-height: stacks-block-height
          })
          (ok new-value)
        )
      )
    )
  )
)

;; --- Quiz Core Functions ---

;; Create a new quiz
(define-public (create-quiz 
  (title (string-ascii 100))
  (questions (list 20 (string-ascii 200)))
  (correct-answers (list 20 uint))
)
  (let
    (
      (quiz-id (var-get next-quiz-id))
      (question-count (len questions))
      (answer-count (len correct-answers))
    )
    (begin
      ;; Validate inputs
      (asserts! (> question-count u0) ERR_INVALID_QUIZ_DATA)
      (asserts! (<= question-count MAX_QUESTIONS) ERR_INVALID_QUIZ_DATA)
      (asserts! (is-eq question-count answer-count) ERR_INVALID_QUIZ_DATA)
      
      ;; Store quiz data
      (map-set quizzes quiz-id
        {
          title: title,
          creator: tx-sender,
          question-count: question-count,
          questions: questions,
          correct-answers: correct-answers,
          total-attempts: u0,
          created-at: stacks-block-height
        }
      )
      
      ;; Increment quiz ID counter
      (var-set next-quiz-id (+ quiz-id u1))
      
      ;; Emit event
      (print {
        event: "quiz-created",
        quiz-id: quiz-id,
        title: title,
        creator: tx-sender,
        question-count: question-count,
        created-at: stacks-block-height
      })
      
      (ok quiz-id)
    )
  )
)

;; Submit answers to a quiz
(define-public (submit-answers
  (quiz-id uint)
  (answers (list 20 uint))
)
  (let
    (
      (quiz-data (unwrap! (map-get? quizzes quiz-id) ERR_QUIZ_NOT_FOUND))
      (player tx-sender)
      (answer-count (len answers))
      (question-count (get question-count quiz-data))
      (correct-answers (get correct-answers quiz-data))
    )
    (begin
      ;; Check if player already took this quiz
      (asserts! (is-none (map-get? quiz-scores {quiz-id: quiz-id, player: player})) ERR_ALREADY_TAKEN)
      
      ;; Validate answer count matches question count
      (asserts! (is-eq answer-count question-count) ERR_INVALID_ANSWERS)
      
      ;; Calculate score
      (let
        (
          (score (calculate-score answers correct-answers))
          (percentage (/ (* score u100) question-count))
        )
        (begin
          ;; Store score
          (map-set quiz-scores
            {quiz-id: quiz-id, player: player}
            {
              score: score,
              percentage: percentage,
              submitted-at: stacks-block-height
            }
          )
          
          ;; Update quiz attempt count
          (map-set quizzes quiz-id
            (merge quiz-data {total-attempts: (+ (get total-attempts quiz-data) u1)})
          )
          
          ;; Update player stats
          (update-player-stats player score percentage)
          
          ;; Emit event
          (print {
            event: "quiz-submitted",
            quiz-id: quiz-id,
            player: player,
            score: score,
            percentage: percentage,
            submitted-at: stacks-block-height
          })
          
          (ok {score: score, percentage: percentage})
        )
      )
    )
  )
)

;; ============================================
;; read only functions
;;

;; Get counter value
(define-read-only (get-counter)
  (ok (var-get counter))
)

;; Get current block height
(define-read-only (get-current-block)
  (ok stacks-block-height)
)

;; Get total number of quizzes
(define-read-only (get-total-quizzes)
  (ok (var-get next-quiz-id))
)

;; Get quiz information
(define-read-only (get-quiz-info (quiz-id uint))
  (match (map-get? quizzes quiz-id)
    quiz (ok {
      title: (get title quiz),
      creator: (get creator quiz),
      question-count: (get question-count quiz),
      total-attempts: (get total-attempts quiz),
      created-at: (get created-at quiz)
    })
    ERR_QUIZ_NOT_FOUND
  )
)

;; Get quiz questions
(define-read-only (get-quiz-questions (quiz-id uint))
  (match (map-get? quizzes quiz-id)
    quiz (ok (get questions quiz))
    ERR_QUIZ_NOT_FOUND
  )
)

;; Get player's score for a specific quiz
(define-read-only (get-quiz-score (quiz-id uint) (player principal))
  (match (map-get? quiz-scores {quiz-id: quiz-id, player: player})
    score-data (ok score-data)
    (ok {score: u0, percentage: u0, submitted-at: u0})
  )
)

;; Check if player has taken a quiz
(define-read-only (has-taken-quiz (quiz-id uint) (player principal))
  (ok (is-some (map-get? quiz-scores {quiz-id: quiz-id, player: player})))
)

;; Get player statistics
(define-read-only (get-player-stats (player principal))
  (match (map-get? player-stats player)
    stats (ok stats)
    (ok {quizzes-taken: u0, total-score: u0, best-score: u0})
  )
)

;; ============================================
;; private functions
;;

;; Calculate score by comparing answers
(define-private (calculate-score
  (answers (list 20 uint))
  (correct-answers (list 20 uint))
)
  (let
    (
      (comparison (map compare-answer answers correct-answers))
    )
    (fold + comparison u0)
  )
)

;; Compare a single answer (returns 1 if correct, 0 if wrong)
(define-private (compare-answer (answer uint) (correct uint))
  (if (is-eq answer correct) u1 u0)
)

;; Update player statistics
(define-private (update-player-stats (player principal) (score uint) (percentage uint))
  (let
    (
      (current-stats (default-to 
        {quizzes-taken: u0, total-score: u0, best-score: u0}
        (map-get? player-stats player)
      ))
      (new-quizzes-taken (+ (get quizzes-taken current-stats) u1))
      (new-total-score (+ (get total-score current-stats) percentage))
      (new-best-score (if (> percentage (get best-score current-stats))
        percentage
        (get best-score current-stats)
      ))
    )
    (map-set player-stats player
      {
        quizzes-taken: new-quizzes-taken,
        total-score: new-total-score,
        best-score: new-best-score
      }
    )
  )
)

Functions (15)

FunctionAccessArgs
incrementpublic
decrementpublic
create-quizpublictitle: (string-ascii 100
submit-answerspublicquiz-id: uint, answers: (list 20 uint
get-counterread-only
get-current-blockread-only
get-total-quizzesread-only
get-quiz-inforead-onlyquiz-id: uint
get-quiz-questionsread-onlyquiz-id: uint
get-quiz-scoreread-onlyquiz-id: uint, player: principal
has-taken-quizread-onlyquiz-id: uint, player: principal
get-player-statsread-onlyplayer: principal
calculate-scoreprivateanswers: (list 20 uint
compare-answerprivateanswer: uint, correct: uint
update-player-statsprivateplayer: principal, score: uint, percentage: uint