drpanwe

brkout

Dec 8th, 2025 (edited)
3,776
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Go 21.24 KB | Gaming | 0 0
  1. // BRKOUT - A Breakout Clone for Sega Dreamcast
  2. // Ported from: https://3e8.org/hacks/brkout/
  3. //
  4. // This example demonstrates how to build a complete game using Go on the
  5. // Dreamcast. It showcases:
  6. //   - Loading textures and sounds from the romdisk filesystem
  7. //   - Reading controller input (D-pad and analog stick)
  8. //   - Rendering 2D graphics using the PowerVR hardware
  9. //   - Simple collision detection and game physics
  10. //   - Game state management with menus and screens
  11. //
  12. // Controls:
  13. //   D-Pad / Analog Stick - Move paddle
  14. //   A or START          - Select menu option
  15. //   START + B           - Return to menu during gameplay
  16.  
  17. package main
  18.  
  19. import "kos"
  20.  
  21. // =============================================================================
  22. // GAME CONFIGURATION
  23. // =============================================================================
  24. //
  25. // All game parameters are defined as constants, making them easy to tweak.
  26. // Try changing these values to see how they affect gameplay!
  27.  
  28. // Screen size (Dreamcast outputs 640x480 by default)
  29. const (
  30.     ScreenWidth  = 640
  31.     ScreenHeight = 480
  32. )
  33.  
  34. // The playing field is where the action happens
  35. const (
  36.     FieldLeft   = 10  // Pixels from left edge
  37.     FieldTop    = 40  // Pixels from top edge
  38.     FieldWidth  = 500 // Width in pixels
  39.     FieldHeight = 420 // Height in pixels
  40. )
  41.  
  42. // Brick grid - the targets to destroy
  43. const (
  44.     BrickCols   = 20 // Columns of bricks
  45.     BrickRows   = 15 // Maximum rows of bricks
  46.     BrickWidth  = 25 // Width of each brick
  47.     BrickHeight = 12 // Height of each brick
  48. )
  49.  
  50. // Paddle - the player's bat
  51. const (
  52.     PaddleWidth  = 70               // Width in pixels
  53.     PaddleHeight = 10               // Height in pixels
  54.     PaddleSpeed  = 6.0              // Movement speed
  55.     PaddleY      = FieldHeight - 30 // Distance from field bottom
  56. )
  57.  
  58. // Ball physics
  59. const (
  60.     BallSize      = 12           // Ball diameter
  61.     BallRadius    = BallSize / 2 // Ball radius
  62.     StartSpeed    = 3.0          // Initial ball speed
  63.     MaxSpeed      = 7.0          // Maximum ball speed
  64.     SpeedIncrease = 0.5          // Speed boost per level
  65. )
  66.  
  67. // Input sensitivity
  68. const (
  69.     StickDeadzone = 15   // Ignore small joystick movements
  70.     StickScale    = 18.0 // How fast joystick moves paddle
  71. )
  72.  
  73. // Game rules
  74. const (
  75.     StartingLives  = 3  // Lives at game start
  76.     PointsPerBrick = 10 // Base score for each brick
  77. )
  78.  
  79. // =============================================================================
  80. // COLOR PALETTE
  81. // =============================================================================
  82. //
  83. // Colors are packed into 32-bit values: Alpha, Red, Green, Blue (0.0 to 1.0)
  84. // We use helper functions to make the code more readable.
  85.  
  86. func white() uint32      { return kos.PlxPackColor(1, 1, 1, 1) }
  87. func gray() uint32       { return kos.PlxPackColor(1, 0.5, 0.5, 0.5) }
  88. func darkBlue() uint32   { return kos.PlxPackColor(1, 0.05, 0.05, 0.1) }
  89. func wallBlue() uint32   { return kos.PlxPackColor(1, 0.4, 0.4, 0.6) }
  90. func paddleBlue() uint32 { return kos.PlxPackColor(1, 0.4, 0.4, 0.7) }
  91. func shadow() uint32     { return kos.PlxPackColor(0.7, 0, 0, 0) }
  92.  
  93. // Rainbow colors for bricks - each row gets a different color!
  94. var brickColors = []uint32{
  95.     kos.PlxPackColor(1, 1.0, 0.2, 0.2), // 🔴 Red
  96.     kos.PlxPackColor(1, 1.0, 0.6, 0.2), // 🟠 Orange
  97.     kos.PlxPackColor(1, 1.0, 1.0, 0.2), // 🟡 Yellow
  98.     kos.PlxPackColor(1, 0.2, 1.0, 0.2), // 🟢 Green
  99.     kos.PlxPackColor(1, 0.2, 1.0, 1.0), // 🩵 Cyan
  100.     kos.PlxPackColor(1, 0.2, 0.2, 1.0), // 🔵 Blue
  101.     kos.PlxPackColor(1, 0.8, 0.2, 1.0), // 🟣 Purple
  102. }
  103.  
  104. // =============================================================================
  105. // GAME ASSETS
  106. // =============================================================================
  107. //
  108. // Textures and sounds loaded from the romdisk (virtual filesystem compiled
  109. // into the game executable).
  110.  
  111. var (
  112.     titleImage *kos.PlxTexture // Title screen background
  113.     fontImage  *kos.PlxTexture // Bitmap font for text
  114.     fieldImage *kos.PlxTexture // Playing field background
  115.  
  116.     soundClick  kos.SfxHandle // Menu navigation
  117.     soundBounce kos.SfxHandle // Ball bouncing
  118.     soundHit    kos.SfxHandle // Brick destroyed
  119.     soundLose   kos.SfxHandle // Lost a life
  120.     soundWin    kos.SfxHandle // Level complete
  121. )
  122.  
  123. func loadAssets() {
  124.     // Initialize the PowerVR graphics chip
  125.     kos.PvrInitDefaults()
  126.  
  127.     // Load images from the /rd (romdisk) filesystem
  128.     titleImage = kos.PlxTxrLoad("/rd/brkout.png", false, 0)
  129.     fontImage = kos.PlxTxrLoad("/rd/font.png", true, 0) // true = has transparency
  130.     fieldImage = kos.PlxTxrLoad("/rd/field.png", false, 0)
  131.  
  132.     // Initialize sound system and load effects
  133.     kos.SndStreamInit()
  134.     soundClick = kos.SndSfxLoad("/rd/click.wav")
  135.     soundBounce = kos.SndSfxLoad("/rd/bounce.wav")
  136.     soundHit = kos.SndSfxLoad("/rd/toggled.wav")
  137.     soundLose = kos.SndSfxLoad("/rd/failure.wav")
  138.     soundWin = kos.SndSfxLoad("/rd/success.wav")
  139. }
  140.  
  141. // =============================================================================
  142. // CONTROLLER INPUT
  143. // =============================================================================
  144. //
  145. // The Dreamcast controller has a D-pad, analog stick, and buttons.
  146. // We track the previous frame's buttons to detect new presses.
  147.  
  148. var (
  149.     buttonsNow  uint32 // Buttons held this frame
  150.     buttonsPrev uint32 // Buttons held last frame
  151.     stickX      int32  // Analog stick X position (-128 to 127)
  152. )
  153.  
  154. func readController() {
  155.     buttonsPrev = buttonsNow
  156.  
  157.     // Find the first controller plugged in
  158.     controller := kos.MapleEnumType(0, kos.MAPLE_FUNC_CONTROLLER)
  159.     if controller == nil {
  160.         buttonsNow, stickX = 0, 0
  161.         return
  162.     }
  163.  
  164.     // Read its current state
  165.     state := controller.ContState()
  166.     if state == nil {
  167.         buttonsNow, stickX = 0, 0
  168.         return
  169.     }
  170.  
  171.     buttonsNow = state.Buttons
  172.     stickX = state.Joyx
  173. }
  174.  
  175. // justPressed returns true only on the frame when a button is first pressed
  176. func justPressed(button uint32) bool {
  177.     wasUp := (buttonsPrev & button) == 0
  178.     isDown := (buttonsNow & button) != 0
  179.     return wasUp && isDown
  180. }
  181.  
  182. // isHeld returns true every frame while a button is held down
  183. func isHeld(button uint32) bool {
  184.     return (buttonsNow & button) != 0
  185. }
  186.  
  187. // getStickMovement returns paddle movement from the analog stick
  188. func getStickMovement() float32 {
  189.     // Ignore tiny movements (deadzone prevents drift)
  190.     if stickX > -StickDeadzone && stickX < StickDeadzone {
  191.         return 0
  192.     }
  193.     return float32(stickX) / StickScale
  194. }
  195.  
  196. // =============================================================================
  197. // GAME STATE
  198. // =============================================================================
  199. //
  200. // All the variables that track what's happening in the game.
  201.  
  202. var (
  203.     // Paddle position (X coordinate within the field)
  204.     paddleX float32
  205.  
  206.     // Ball position and velocity
  207.     ballX, ballY       float32
  208.     ballSpeedX, speedY float32
  209.  
  210.     // Brick grid: 0 = empty, 1-7 = brick color
  211.     bricks     [BrickCols * BrickRows]int
  212.     bricksLeft int
  213.  
  214.     // Player progress
  215.     lives int
  216.     score int
  217.     level int
  218. )
  219.  
  220. // =============================================================================
  221. // DRAWING HELPERS
  222. // =============================================================================
  223. //
  224. // The Dreamcast's PowerVR chip renders 3D polygons. For 2D games, we draw
  225. // rectangles (quads) at a fixed Z depth. The PLX library makes this easy!
  226.  
  227. // setupColors prepares for drawing solid-colored shapes
  228. func setupColors(list uint32) {
  229.     kos.PlxCxtInit()
  230.     kos.PlxCxtTexture(nil) // No texture, just colors
  231.     kos.PlxCxtCulling(kos.PLX_CULL_NONE)
  232.     kos.PlxCxtSend(int32(list))
  233. }
  234.  
  235. // setupTexture prepares for drawing textured shapes
  236. func setupTexture(list uint32, texture *kos.PlxTexture) {
  237.     kos.PlxCxtInit()
  238.     kos.PlxCxtTexture(texture.Ptr())
  239.     kos.PlxCxtCulling(kos.PLX_CULL_NONE)
  240.     kos.PlxCxtSend(int32(list))
  241. }
  242.  
  243. // drawRect draws a solid colored rectangle
  244. func drawRect(x, y, w, h, z float32, color uint32) {
  245.     // A quad needs 4 vertices, last one marked as "end of strip"
  246.     kos.PlxVertInp(kos.PLX_VERT, x, y+h, z, color)
  247.     kos.PlxVertInp(kos.PLX_VERT, x, y, z, color)
  248.     kos.PlxVertInp(kos.PLX_VERT, x+w, y+h, z, color)
  249.     kos.PlxVertInp(kos.PLX_VERT_EOS, x+w, y, z, color)
  250. }
  251.  
  252. // drawImage draws a textured rectangle with UV coordinates
  253. func drawImage(x, y, w, h, z float32, color uint32, u1, v1, u2, v2 float32) {
  254.     kos.PlxVertIfp(kos.PLX_VERT, x, y+h, z, color, u1, v2)
  255.     kos.PlxVertIfp(kos.PLX_VERT, x, y, z, color, u1, v1)
  256.     kos.PlxVertIfp(kos.PLX_VERT, x+w, y+h, z, color, u2, v2)
  257.     kos.PlxVertIfp(kos.PLX_VERT_EOS, x+w, y, z, color, u2, v1)
  258. }
  259.  
  260. // =============================================================================
  261. // TEXT RENDERING
  262. // =============================================================================
  263. //
  264. // We use a bitmap font stored in a texture. Each character is 12x24 pixels,
  265. // arranged in a 16x16 grid (256 characters total, ASCII order).
  266.  
  267. const (
  268.     charW    = 12    // Character width
  269.     charH    = 24    // Character height
  270.     fontSize = 256.0 // Texture size
  271. )
  272.  
  273. // drawText draws a string at the given position
  274. func drawText(x, y, z float32, color uint32, text string) {
  275.     for i, ch := range text {
  276.         if ch < 32 {
  277.             continue // Skip control characters
  278.         }
  279.         // Calculate UV coordinates for this character
  280.         col := int(ch) % 16
  281.         row := int(ch) / 16
  282.         u1 := float32(col*charW) / fontSize
  283.         v1 := float32(row*charH) / fontSize
  284.         u2 := u1 + charW/fontSize
  285.         v2 := v1 + charH/fontSize
  286.         // Draw the character
  287.         drawImage(x+float32(i*charW), y, charW, charH, z, color, u1, v1, u2, v2)
  288.     }
  289. }
  290.  
  291. // drawNumber draws an integer as text
  292. func drawNumber(x, y, z float32, color uint32, n int) {
  293.     if n == 0 {
  294.         drawText(x, y, z, color, "0")
  295.         return
  296.     }
  297.     // Build string from digits (right to left)
  298.     text := ""
  299.     for n > 0 {
  300.         digit := byte(n % 10)
  301.         text = string('0'+digit) + text
  302.         n = n / 10
  303.     }
  304.     drawText(x, y, z, color, text)
  305. }
  306.  
  307. // =============================================================================
  308. // SOUND EFFECTS
  309. // =============================================================================
  310. //
  311. // Simple wrappers for playing sounds with panning (left/center/right)
  312.  
  313. func playSound(sound kos.SfxHandle)      { kos.SndSfxPlay(sound, 160, 128) }
  314. func playSoundLeft(sound kos.SfxHandle)  { kos.SndSfxPlay(sound, 140, 64) }
  315. func playSoundRight(sound kos.SfxHandle) { kos.SndSfxPlay(sound, 140, 192) }
  316.  
  317. // =============================================================================
  318. // MATH HELPERS
  319. // =============================================================================
  320.  
  321. func abs(x float32) float32 {
  322.     if x < 0 {
  323.         return -x
  324.     }
  325.     return x
  326. }
  327.  
  328. // =============================================================================
  329. // TITLE MENU
  330. // =============================================================================
  331.  
  332. func showMenu() bool {
  333.     kos.PvrSetBgColor(0, 0, 0) // Black background
  334.     choice := 0                // 0 = Play, 1 = Quit
  335.  
  336.     for {
  337.         readController()
  338.  
  339.         // Navigate with D-pad
  340.         if justPressed(kos.CONT_DPAD_UP) || justPressed(kos.CONT_DPAD_DOWN) {
  341.             choice = 1 - choice // Toggle between 0 and 1
  342.             playSound(soundClick)
  343.         }
  344.  
  345.         // Select with A or START
  346.         if justPressed(kos.CONT_A) || justPressed(kos.CONT_START) {
  347.             playSound(soundHit)
  348.             fadeOutMenu(choice)
  349.             return choice == 1 // true = quit
  350.         }
  351.  
  352.         renderMenu(choice, 1.0)
  353.     }
  354. }
  355.  
  356. func fadeOutMenu(choice int) {
  357.     for frame := 30; frame >= 0; frame-- {
  358.         renderMenu(choice, float32(frame)/30.0)
  359.     }
  360. }
  361.  
  362. func renderMenu(choice int, alpha float32) {
  363.     kos.PvrWaitReady()
  364.     kos.PvrSceneBegin()
  365.  
  366.     // Draw title image (opaque layer)
  367.     kos.PvrListBegin(kos.PVR_LIST_OP_POLY)
  368.     if titleImage != nil {
  369.         setupTexture(kos.PVR_LIST_OP_POLY, titleImage)
  370.         tint := kos.PlxPackColor(1, alpha, alpha, alpha)
  371.         drawImage(0, 0, ScreenWidth, ScreenHeight, 100, tint, 0, 0, 640.0/1024.0, 480.0/512.0)
  372.     }
  373.     kos.PvrListFinish()
  374.  
  375.     // Draw menu cursor (translucent layer)
  376.     kos.PvrListBegin(kos.PVR_LIST_TR_POLY)
  377.     setupColors(kos.PVR_LIST_TR_POLY)
  378.     cursorY := float32(314 + choice*50)
  379.     drawRect(250, cursorY, 20, 20, 200, kos.PlxPackColor(alpha, 1, 1, 1))
  380.     kos.PvrListFinish()
  381.  
  382.     kos.PvrSceneFinish()
  383. }
  384.  
  385. // =============================================================================
  386. // GAME INITIALIZATION
  387. // =============================================================================
  388.  
  389. func startNewGame() {
  390.     lives = StartingLives
  391.     score = 0
  392.     level = 1
  393.     setupLevel()
  394. }
  395.  
  396. func setupLevel() {
  397.     // Center the paddle
  398.     paddleX = float32(FieldWidth-PaddleWidth) / 2.0
  399.  
  400.     // Reset the ball
  401.     resetBall()
  402.  
  403.     // Fill brick grid (more rows at higher levels)
  404.     rows := 3 + level
  405.     if rows > BrickRows {
  406.         rows = BrickRows
  407.     }
  408.  
  409.     bricksLeft = 0
  410.     numColors := len(brickColors)
  411.  
  412.     for row := 0; row < BrickRows; row++ {
  413.         for col := 0; col < BrickCols; col++ {
  414.             i := row*BrickCols + col
  415.             if row < rows {
  416.                 bricks[i] = (row % numColors) + 1 // Color index 1-7
  417.                 bricksLeft++
  418.             } else {
  419.                 bricks[i] = 0 // Empty
  420.             }
  421.         }
  422.     }
  423. }
  424.  
  425. func resetBall() {
  426.     // Position ball above paddle
  427.     ballX = paddleX + PaddleWidth/2.0 - BallRadius
  428.     ballY = FieldHeight - 50.0
  429.  
  430.     // Calculate speed for current level
  431.     speed := StartSpeed + float32(level)*SpeedIncrease
  432.     if speed > MaxSpeed {
  433.         speed = MaxSpeed
  434.     }
  435.  
  436.     // Launch at an angle
  437.     ballSpeedX = speed * 0.6
  438.     speedY = -speed
  439. }
  440.  
  441. // =============================================================================
  442. // GAME LOOP
  443. // =============================================================================
  444.  
  445. func playGame() {
  446.     kos.PvrSetBgColor(0.05, 0.05, 0.1) // Dark blue background
  447.     startNewGame()
  448.  
  449.     for lives > 0 {
  450.         readController()
  451.  
  452.         // Return to menu with START+B
  453.         if isHeld(kos.CONT_START) && isHeld(kos.CONT_B) {
  454.             return
  455.         }
  456.  
  457.         updatePaddle()
  458.         updateBall()
  459.         renderGame()
  460.     }
  461. }
  462.  
  463. // =============================================================================
  464. // PADDLE MOVEMENT
  465. // =============================================================================
  466.  
  467. func updatePaddle() {
  468.     // D-pad control
  469.     if isHeld(kos.CONT_DPAD_LEFT) {
  470.         paddleX -= PaddleSpeed
  471.     }
  472.     if isHeld(kos.CONT_DPAD_RIGHT) {
  473.         paddleX += PaddleSpeed
  474.     }
  475.  
  476.     // Analog stick control
  477.     paddleX += getStickMovement()
  478.  
  479.     // Keep paddle inside the field
  480.     if paddleX < 0 {
  481.         paddleX = 0
  482.     }
  483.     if paddleX > FieldWidth-PaddleWidth {
  484.         paddleX = FieldWidth - PaddleWidth
  485.     }
  486. }
  487.  
  488. // =============================================================================
  489. // BALL PHYSICS
  490. // =============================================================================
  491.  
  492. func updateBall() {
  493.     // Move ball
  494.     ballX += ballSpeedX
  495.     ballY += speedY
  496.  
  497.     // Check all collisions
  498.     checkWalls()
  499.     checkPaddle()
  500.     checkBricks()
  501.     checkLost()
  502.     checkWin()
  503. }
  504.  
  505. func checkWalls() {
  506.     // Left wall
  507.     if ballX < 0 {
  508.         ballX = 0
  509.         ballSpeedX = -ballSpeedX
  510.         playSoundLeft(soundBounce)
  511.     }
  512.  
  513.     // Right wall
  514.     if ballX > FieldWidth-BallSize {
  515.         ballX = FieldWidth - BallSize
  516.         ballSpeedX = -ballSpeedX
  517.         playSoundRight(soundBounce)
  518.     }
  519.  
  520.     // Top wall
  521.     if ballY < 0 {
  522.         ballY = 0
  523.         speedY = -speedY
  524.         playSound(soundBounce)
  525.     }
  526. }
  527.  
  528. func checkPaddle() {
  529.     // Ball center coordinates
  530.     ballCenterX := ballX + BallRadius
  531.     ballBottom := ballY + BallSize
  532.  
  533.     // Is ball at paddle height?
  534.     if ballBottom < PaddleY || ballY > PaddleY+PaddleHeight {
  535.         return
  536.     }
  537.  
  538.     // Is ball above paddle?
  539.     if ballCenterX < paddleX || ballCenterX > paddleX+PaddleWidth {
  540.         return
  541.     }
  542.  
  543.     // Bounce!
  544.     ballY = PaddleY - BallSize
  545.     speedY = -abs(speedY)
  546.  
  547.     // Angle depends on where ball hit the paddle
  548.     // Hit left = go left, hit right = go right
  549.     hitPoint := (ballCenterX - paddleX) / PaddleWidth // 0.0 to 1.0
  550.     ballSpeedX = (hitPoint - 0.5) * 8.0               // -4.0 to +4.0
  551.  
  552.     playSound(soundBounce)
  553. }
  554.  
  555. func checkBricks() {
  556.     // Ball center coordinates
  557.     ballCenterX := ballX + BallRadius
  558.     ballCenterY := ballY + BallRadius
  559.  
  560.     // Which brick is the ball in?
  561.     col := int(ballCenterX) / BrickWidth
  562.     row := int(ballCenterY) / BrickHeight
  563.  
  564.     // Out of bounds?
  565.     if col < 0 || col >= BrickCols || row < 0 || row >= BrickRows {
  566.         return
  567.     }
  568.  
  569.     // Is there a brick here?
  570.     i := row*BrickCols + col
  571.     if bricks[i] == 0 {
  572.         return
  573.     }
  574.  
  575.     // Destroy the brick!
  576.     bricks[i] = 0
  577.     bricksLeft--
  578.     score += PointsPerBrick * level
  579.  
  580.     // Bounce off brick (figure out which side we hit)
  581.     brickCenterX := float32(col*BrickWidth) + BrickWidth/2.0
  582.     brickCenterY := float32(row*BrickHeight) + BrickHeight/2.0
  583.  
  584.     horizDist := abs(ballCenterX-brickCenterX) / BrickWidth
  585.     vertDist := abs(ballCenterY-brickCenterY) / BrickHeight
  586.  
  587.     if horizDist > vertDist {
  588.         ballSpeedX = -ballSpeedX // Hit side
  589.     } else {
  590.         speedY = -speedY // Hit top/bottom
  591.     }
  592.  
  593.     playSound(soundHit)
  594. }
  595.  
  596. func checkLost() {
  597.     // Ball fell below the field?
  598.     if ballY <= FieldHeight {
  599.         return
  600.     }
  601.  
  602.     lives--
  603.     playSound(soundLose)
  604.  
  605.     if lives <= 0 {
  606.         showGameOver()
  607.         return
  608.     }
  609.  
  610.     // Continue with new ball
  611.     resetBall()
  612.     waitFrames(60) // Brief pause
  613. }
  614.  
  615. func checkWin() {
  616.     // All bricks destroyed?
  617.     if bricksLeft > 0 {
  618.         return
  619.     }
  620.  
  621.     playSound(soundWin)
  622.     level++
  623.     setupLevel()
  624.     waitFrames(60) // Brief pause
  625. }
  626.  
  627. func waitFrames(n int) {
  628.     for i := 0; i < n; i++ {
  629.         renderGame()
  630.     }
  631. }
  632.  
  633. // =============================================================================
  634. // GAME OVER SCREEN
  635. // =============================================================================
  636.  
  637. func showGameOver() {
  638.     bg := kos.PlxPackColor(1, 0.1, 0.1, 0.15)
  639.  
  640.     for frame := 0; frame < 180; frame++ { // 3 seconds
  641.         kos.PvrWaitReady()
  642.         kos.PvrSceneBegin()
  643.  
  644.         // Background
  645.         kos.PvrListBegin(kos.PVR_LIST_OP_POLY)
  646.         setupColors(kos.PVR_LIST_OP_POLY)
  647.         drawRect(0, 0, ScreenWidth, ScreenHeight, 1, bg)
  648.         kos.PvrListFinish()
  649.  
  650.         // Text
  651.         kos.PvrListBegin(kos.PVR_LIST_TR_POLY)
  652.         if fontImage != nil {
  653.             setupTexture(kos.PVR_LIST_TR_POLY, fontImage)
  654.             drawText(220, 180, 100, white(), "GAME OVER")
  655.             drawText(220, 230, 100, gray(), "Score:")
  656.             drawNumber(320, 230, 100, white(), score)
  657.         }
  658.         kos.PvrListFinish()
  659.  
  660.         kos.PvrSceneFinish()
  661.  
  662.         // Skip with A button
  663.         readController()
  664.         if justPressed(kos.CONT_A) {
  665.             return
  666.         }
  667.     }
  668. }
  669.  
  670. // =============================================================================
  671. // GAME RENDERING
  672. // =============================================================================
  673.  
  674. func renderGame() {
  675.     kos.PvrWaitReady()
  676.     kos.PvrSceneBegin()
  677.  
  678.     // === OPAQUE LAYER ===
  679.     // Draw solid objects that don't need transparency
  680.     kos.PvrListBegin(kos.PVR_LIST_OP_POLY)
  681.     drawBackground()
  682.     drawField()
  683.     drawWalls()
  684.     drawBricks()
  685.     drawPaddle()
  686.     drawBall()
  687.     kos.PvrListFinish()
  688.  
  689.     // === TRANSLUCENT LAYER ===
  690.     // Draw text (font has transparent background)
  691.     kos.PvrListBegin(kos.PVR_LIST_TR_POLY)
  692.     drawHUD()
  693.     kos.PvrListFinish()
  694.  
  695.     kos.PvrSceneFinish()
  696. }
  697.  
  698. func drawBackground() {
  699.     setupColors(kos.PVR_LIST_OP_POLY)
  700.     drawRect(0, 0, ScreenWidth, ScreenHeight, 1, darkBlue())
  701. }
  702.  
  703. func drawField() {
  704.     if fieldImage == nil {
  705.         return
  706.     }
  707.     setupTexture(kos.PVR_LIST_OP_POLY, fieldImage)
  708.     drawImage(FieldLeft, FieldTop, FieldWidth, FieldHeight, 50, white(),
  709.         0, 0, float32(FieldWidth)/512.0, float32(FieldHeight)/512.0)
  710. }
  711.  
  712. func drawWalls() {
  713.     setupColors(kos.PVR_LIST_OP_POLY)
  714.     color := wallBlue()
  715.     drawRect(FieldLeft-5, FieldTop, 5, FieldHeight, 100, color)          // Left
  716.     drawRect(FieldLeft+FieldWidth, FieldTop, 5, FieldHeight, 100, color) // Right
  717.     drawRect(FieldLeft-5, FieldTop-5, FieldWidth+10, 5, 100, color)      // Top
  718. }
  719.  
  720. func drawBricks() {
  721.     numColors := len(brickColors)
  722.  
  723.     for row := 0; row < BrickRows; row++ {
  724.         for col := 0; col < BrickCols; col++ {
  725.             colorIndex := bricks[row*BrickCols+col]
  726.             if colorIndex == 0 {
  727.                 continue // Empty space
  728.             }
  729.  
  730.             // Calculate screen position
  731.             x := float32(FieldLeft + col*BrickWidth)
  732.             y := float32(FieldTop + row*BrickHeight)
  733.  
  734.             // Draw shadow first (slightly offset)
  735.             drawRect(x+2, y+2, BrickWidth-2, BrickHeight-2, 99, shadow())
  736.  
  737.             // Draw brick on top
  738.             color := brickColors[(colorIndex-1)%numColors]
  739.             drawRect(x, y, BrickWidth-2, BrickHeight-2, 100, color)
  740.         }
  741.     }
  742. }
  743.  
  744. func drawPaddle() {
  745.     x := float32(FieldLeft) + paddleX
  746.     y := float32(FieldTop + PaddleY)
  747.  
  748.     // Shadow
  749.     drawRect(x+3, y+3, PaddleWidth, PaddleHeight, 99, shadow())
  750.     // Paddle
  751.     drawRect(x, y, PaddleWidth, PaddleHeight, 100, paddleBlue())
  752. }
  753.  
  754. func drawBall() {
  755.     x := float32(FieldLeft) + ballX
  756.     y := float32(FieldTop) + ballY
  757.     drawRect(x, y, BallSize, BallSize, 100, white())
  758. }
  759.  
  760. func drawHUD() {
  761.     if fontImage == nil {
  762.         return
  763.     }
  764.  
  765.     setupTexture(kos.PVR_LIST_TR_POLY, fontImage)
  766.  
  767.     // Position HUD to the right of the playing field
  768.     x := float32(FieldLeft + FieldWidth + 15)
  769.  
  770.     drawText(x, 50, 200, white(), "BRKOUT")
  771.  
  772.     drawText(x, 100, 200, gray(), "Level")
  773.     drawNumber(x, 125, 200, white(), level)
  774.  
  775.     drawText(x, 170, 200, gray(), "Score")
  776.     drawNumber(x, 195, 200, white(), score)
  777.  
  778.     drawText(x, 250, 200, gray(), "Lives")
  779.     drawNumber(x, 275, 200, white(), lives)
  780. }
  781.  
  782. // =============================================================================
  783. // MAIN ENTRY POINT
  784. // =============================================================================
  785.  
  786. func main() {
  787.     // Load all game assets (textures, sounds)
  788.     loadAssets()
  789.  
  790.     // Main game loop - keeps returning to menu after each game
  791.     for {
  792.         if showMenu() {
  793.             return // User chose "Quit"
  794.         }
  795.         playGame()
  796.     }
  797. }
  798.  
Advertisement