Code Sketch


yoii
By: Mhalsakant School
Category: Programming
// ============================================================
//  HAUNTED HOUSE ESCAPE 3D  -  Kojo (Scala) first-person horror escape
//  Goal: find 3 golden keys, then reach the green exit door (bottom right)
//  Avoid the Warden! He hunts you through the maze.
//  Controls: UP/DOWN = walk, LEFT/RIGHT = turn, ENTER = restart
// ============================================================
cleari()
drawStage(black)
setRefreshRate(30)

val W = 640.0; val H = 400.0; val N = 128
val CW = W / N
val FOV = math.Pi / 3
val MAP = Array(
  "1111111111111111",
  "1000000100000001",
  "1011110101111101",
  "1010000000000101",
  "1010111111110101",
  "1000100000010001",
  "1110101111010111",
  "1000001001000001",
  "1011111001111101",
  "1000000000000101",
  "1011110111100101",
  "1010000100000101",
  "1010111101111101",
  "1010000001000001",
  "1000111101011101",
  "1111111111111121")

def cell(x: Int, y: Int): Char =
    if (x < 0 || y < 0 || x >= 16 || y >= 16) '1' else MAP(y)(x)

val rnd = new scala.util.Random()
var px = 1.5; var py = 1.5; var ang = 0.0
var ex = 7.5; var ey = 9.5
var keys = List[(Double, Double)]()
var got = 0
var turnL = false; var turnR = false; var fwd = false; var back = false
var gameOver = false; var won = false
var frame = List[Picture](); var tick = 0
var shake = 0.0

// ---------- BFS distance map from a cell ----------
def distMap(tx: Int, ty: Int): Array[Array[Int]] = {
    val d = Array.fill(16, 16)(-1)
    var q = List((tx, ty)); d(ty)(tx) = 0
    while (q.nonEmpty) {
        val (cx, cy) = q.head; q = q.tail
        for ((dx, dy) <- List((1, 0), (-1, 0), (0, 1), (0, -1))) {
            val nx = cx + dx; val ny = cy + dy
            if (cell(nx, ny) == '0' && d(ny)(nx) == -1) { d(ny)(nx) = d(cy)(cx) + 1; q = q :+ ((nx, ny)) }
        }
    }
    d
}

def reset() {
    px = 1.5; py = 1.5; ang = 0.0; ex = 7.5; ey = 9.5
    got = 0; gameOver = false; won = false; tick = 0
    val d = distMap(1, 1)
    val far = (for (y <- 0 until 16; x <- 0 until 16 if d(y)(x) > 9 && !(x == 7 && y == 9)) yield (x + 0.5, y + 0.5)).toList
    keys = rnd.shuffle(far).take(3)
}

// ---------- raycaster (DDA) ----------
def cast(a: Double): (Double, Int, Char) = {
    val dx = math.cos(a); val dy = math.sin(a)
    var mx = px.toInt; var my = py.toInt
    val ddx = if (dx == 0) 1e30 else math.abs(1 / dx)
    val ddy = if (dy == 0) 1e30 else math.abs(1 / dy)
    val stepX = if (dx < 0) -1 else 1
    val stepY = if (dy < 0) -1 else 1
    var sdx = if (dx < 0) (px - mx) * ddx else (mx + 1 - px) * ddx
    var sdy = if (dy < 0) (py - my) * ddy else (my + 1 - py) * ddy
    var side = 0; var hit = false; var c = '1'; var guard = 0
    while (!hit && guard < 40) {
        guard += 1
        if (sdx < sdy) { sdx += ddx; mx += stepX; side = 0 } else { sdy += ddy; my += stepY; side = 1 }
        val ch = cell(mx, my)
        if (ch != '0') { hit = true; c = ch }
    }
    (if (side == 0) sdx - ddx else sdy - ddy, side, c)
}

def walkable(x: Double, y: Double) = cell(x.toInt, y.toInt) == '0'

def shade(r: Int, g: Int, b: Int, dist: Double): Color = {
    val k = math.max(0.03, 1.0 - dist / 7.5)       // flashlight / darkness
    Color((r * k).toInt, (g * k).toInt, (b * k).toInt)
}

def add(p: Picture) { draw(p); frame = p :: frame }

// ---------- sprite projection: returns (screenX, size, dist) if visible ----------
def project(wx: Double, wy: Double, depth: Array[Double]): Option[(Double, Double, Double)] = {
    val dx = wx - px; val dy = wy - py
    val dist = math.sqrt(dx * dx + dy * dy)
    var rel = math.atan2(dy, dx) - ang
    while (rel > math.Pi) rel -= 2 * math.Pi
    while (rel < -math.Pi) rel += 2 * math.Pi
    if (math.abs(rel) > FOV / 2 + 0.1 || dist < 0.2) None
    else {
        val col = ((rel / FOV + 0.5) * N).toInt
        val ok = col >= 0 && col < N && depth(col) > dist * math.cos(rel)
        if (ok) Some(((rel / FOV + 0.5) * W - W / 2, H / dist * 0.9, dist)) else None
    }
}

def update() {
    tick += 1
    if (gameOver || won) return
    if (turnL) ang -= 0.075
    if (turnR) ang += 0.075
    val sp = 0.085
    var mvx = 0.0; var mvy = 0.0
    if (fwd) { mvx = math.cos(ang) * sp; mvy = math.sin(ang) * sp }
    if (back) { mvx = -math.cos(ang) * sp * 0.7; mvy = -math.sin(ang) * sp * 0.7 }
    if (walkable(px + mvx * 3, py)) px += mvx
    if (walkable(px, py + mvy * 3)) py += mvy

    // keys pickup
    keys.foreach { k => if (math.hypot(k._1 - px, k._2 - py) < 0.55) got += 1 }
    keys = keys.filter(k => math.hypot(k._1 - px, k._2 - py) >= 0.55)

    // win
    if (px.toInt == 14 && py.toInt == 14 && got >= 3) won = true

    // Warden AI: BFS chase, gets faster with every key
    val d = distMap(px.toInt, py.toInt)
    val cx = ex.toInt; val cy = ey.toInt
    var best = (cx, cy); var bd = d(cy)(cx)
    for ((dx, dy) <- List((1, 0), (-1, 0), (0, 1), (0, -1))) {
        val nx = cx + dx; val ny = cy + dy
        if (cell(nx, ny) == '0' && d(ny)(nx) != -1 && (bd == -1 || d(ny)(nx) < bd)) { bd = d(ny)(nx); best = (nx, ny) }
    }
    val tx = best._1 + 0.5; val ty = best._2 + 0.5
    val es = 0.032 + 0.012 * got
    val len = math.hypot(tx - ex, ty - ey)
    if (len > 0.02) { ex += (tx - ex) / len * es; ey += (ty - ey) / len * es }
    val dist = math.hypot(ex - px, ey - py)
    shake = if (dist < 4) (4 - dist) * 2.5 else 0
    if (dist < 0.6) gameOver = true
}

def render() {
    frame.foreach(_.erase()); frame = Nil
    val depth = new Array[Double](N)
    val sx = if (shake > 0) (rnd.nextDouble - 0.5) * shake else 0.0
    val sy = if (shake > 0) (rnd.nextDouble - 0.5) * shake else 0.0

    // walls
    for (i <- 0 until N) {
        val a = ang - FOV / 2 + i * FOV / N
        val (dRaw, side, c) = cast(a)
        val dist = math.max(0.05, dRaw * math.cos(a - ang))
        depth(i) = dist
        val lineH = math.min(H * 1.4, H / dist)
        val flick = if (tick % 47 < 2) 0.6 else 1.0       // flickering light
        val base = if (c == '2') (if (got >= 3) (40, 255, 80) else (180, 40, 40))
                   else if (side == 0) (150, 110, 80) else (110, 80, 60)
        val col = shade((base._1 * flick).toInt, (base._2 * flick).toInt, (base._3 * flick).toInt, dist)
        val r = Picture.rectangle(CW + 1, lineH)
        r.setFillColor(col); r.setPenColor(noColor)
        r.setPosition(-W / 2 + i * CW + sx, -lineH / 2 + sy)
        add(r)
    }

    // keys (golden diamonds)
    keys.sortBy(k => -math.hypot(k._1 - px, k._2 - py)).foreach { k =>
        project(k._1, k._2, depth).foreach { case (x, size, dist) =>
            val s = size * 0.12
            val bob = math.sin(tick * 0.15) * s * 0.4
            val key = Picture.circle(s)
            key.setFillColor(shade(255, 215, 0, dist * 0.5)); key.setPenColor(Color(255, 255, 150))
            key.setPosition(x + sx, -size * 0.2 + bob + sy); add(key)
        }
    }

    // the Warden (original character: hooded pale figure with glowing eyes)
    project(ex, ey, depth).foreach { case (x, size, dist) =>
        val bw = size * 0.38; val bh = size * 0.75
        val body = Picture.rectangle(bw, bh)
        body.setFillColor(shade(70, 70, 90, dist * 0.6)); body.setPenColor(noColor)
        body.setPosition(x - bw / 2 + sx, -size * 0.45 + sy); add(body)
        val head = Picture.circle(bw * 0.38)
        head.setFillColor(shade(220, 215, 200, dist * 0.6)); head.setPenColor(noColor)
        head.setPosition(x + sx, -size * 0.45 + bh + sy); add(head)
        for (ox <- Seq(-0.12, 0.12)) {
            val eye = Picture.circle(bw * 0.07)
            eye.setFillColor(Color(255, 20, 20)); eye.setPenColor(noColor)
            eye.setPosition(x + ox * bw + sx, -size * 0.45 + bh + bw * 0.08 + sy); add(eye)
        }
    }

    // vignette bars + HUD
    val hud = Picture.text(s"KEYS: $got / 3     ${if (got >= 3) "EXIT IS OPEN - RUN TO THE GREEN DOOR!" else "Find all keys..."}", 18)
    hud.setPenColor(Color(255, 220, 120)); hud.setPosition(-300, 175); add(hud)
    if (gameOver) {
        val ov = Picture.rectangle(W, H); ov.setFillColor(Color(120, 0, 0, 170)); ov.setPenColor(noColor)
        ov.setPosition(-W / 2, -H / 2); add(ov)
        val g = Picture.text("THE WARDEN CAUGHT YOU!  -  ENTER = restart", 26)
        g.setPenColor(white); g.setPosition(-290, 0); add(g)
    }
    if (won) {
        val g = Picture.text("YOU ESCAPED!  -  ENTER = play again", 30)
        g.setPenColor(Color(60, 255, 100)); g.setPosition(-250, 0); add(g)
    }
}

onKeyPress { k =>
    k match {
        case Kc.VK_LEFT  => turnL = true
        case Kc.VK_RIGHT => turnR = true
        case Kc.VK_UP    => fwd = true
        case Kc.VK_DOWN  => back = true
        case Kc.VK_ENTER => if (gameOver || won) reset()
        case _ =>
    }
}
onKeyRelease { k =>
    k match {
        case Kc.VK_LEFT  => turnL = false
        case Kc.VK_RIGHT => turnR = false
        case Kc.VK_UP    => fwd = false
        case Kc.VK_DOWN  => back = false
        case _ =>
    }
}

// floor and ceiling (drawn once, behind everything)
val ceil = Picture.rectangle(W, H / 2); ceil.setFillColor(Color(12, 10, 18)); ceil.setPenColor(noColor)
ceil.setPosition(-W / 2, 0); draw(ceil)
val floor = Picture.rectangle(W, H / 2); floor.setFillColor(Color(28, 20, 16)); floor.setPenColor(noColor)
floor.setPosition(-W / 2, -H / 2); draw(floor)

reset()
animate {
    update()
    render()
}
activateCanvas()