Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,346 @@
|
||||
// vtest build: !openbsd
|
||||
module main
|
||||
|
||||
import gg
|
||||
import rand
|
||||
import sokol.audio
|
||||
import math
|
||||
|
||||
const paddle_speed_base = f32(400.0)
|
||||
const ball_speed_base = f32(300.0)
|
||||
|
||||
// Colors
|
||||
const color_bg = gg.Color{10, 15, 30, 255}
|
||||
const color_foreground = gg.Color{230, 230, 230, 255}
|
||||
const color_accent = gg.Color{0, 255, 100, 255}
|
||||
const color_secondary = gg.Color{0, 200, 80, 255}
|
||||
const color_text_dim = gg.Color{150, 150, 150, 200}
|
||||
const color_line = gg.Color{100, 100, 100, 150}
|
||||
const color_p1 = gg.Color{255, 50, 50, 255}
|
||||
const color_p2 = gg.Color{50, 100, 255, 255}
|
||||
|
||||
struct SoundManager {
|
||||
mut:
|
||||
ping []f32
|
||||
pong []f32
|
||||
buzz []f32
|
||||
}
|
||||
|
||||
fn (mut sm SoundManager) init() {
|
||||
sample_rate := f32(audio.sample_rate())
|
||||
|
||||
// Ping (Wall)
|
||||
ping_duration := f32(0.1)
|
||||
ping_frames := int(sample_rate * ping_duration)
|
||||
for i in 0 .. ping_frames {
|
||||
t := f32(i) / sample_rate
|
||||
// Use a steeper exponential-like decay for a cleaner "pluck"
|
||||
env := f32(math.pow(f32(1.0) - t / ping_duration, 3))
|
||||
sm.ping << f32(0.3) * math.sinf(t * 800.0 * 2 * math.pi) * env
|
||||
}
|
||||
// Absolute silence buffer to prevent clicks
|
||||
for _ in 0 .. 100 {
|
||||
sm.ping << 0
|
||||
}
|
||||
|
||||
// Pong (Paddle)
|
||||
pong_duration := f32(0.1)
|
||||
pong_frames := int(sample_rate * pong_duration)
|
||||
for i in 0 .. pong_frames {
|
||||
t := f32(i) / sample_rate
|
||||
env := f32(math.pow(f32(1.0) - t / pong_duration, 3))
|
||||
sm.pong << f32(0.3) * math.sinf(t * 400.0 * 2 * math.pi) * env
|
||||
}
|
||||
for _ in 0 .. 100 {
|
||||
sm.pong << 0
|
||||
}
|
||||
|
||||
// Buzz (Reset)
|
||||
buzz_duration := f32(0.3)
|
||||
buzz_frames := int(sample_rate * buzz_duration)
|
||||
for i in 0 .. buzz_frames {
|
||||
t := f32(i) / sample_rate
|
||||
env := f32(math.pow(f32(1.0) - t / buzz_duration, 2))
|
||||
// Add a harmonic for a more "buzzy" feel
|
||||
val := f32(0.25) * math.sinf(t * 100.0 * 2 * math.pi) +
|
||||
f32(0.1) * math.sinf(t * 200.0 * 2 * math.pi)
|
||||
sm.buzz << val * env
|
||||
}
|
||||
for _ in 0 .. 200 {
|
||||
sm.buzz << 0
|
||||
}
|
||||
}
|
||||
|
||||
struct App {
|
||||
mut:
|
||||
ctx &gg.Context = unsafe { nil }
|
||||
width int = 800
|
||||
height int = 600
|
||||
paddle_width f32
|
||||
paddle_height f32
|
||||
ball_size f32
|
||||
p1_y f32
|
||||
p2_y f32
|
||||
ball_x f32
|
||||
ball_y f32
|
||||
ball_vx f32
|
||||
ball_vy f32
|
||||
p1_score int
|
||||
p2_score int
|
||||
paused bool
|
||||
sounds SoundManager
|
||||
}
|
||||
|
||||
fn (mut app App) update_sizes() {
|
||||
size := app.ctx.window_size()
|
||||
app.width = size.width
|
||||
app.height = size.height
|
||||
|
||||
// Base scales on 800x600
|
||||
scale_x := f32(app.width) / 800.0
|
||||
scale_y := f32(app.height) / 600.0
|
||||
|
||||
app.paddle_width = 15.0 * scale_x
|
||||
app.paddle_height = 80.0 * scale_y
|
||||
app.ball_size = 15.0 * scale_x // Keep it circular based on width scale
|
||||
}
|
||||
|
||||
fn (mut app App) reset_ball() {
|
||||
app.ball_x = f32(app.width) / 2 - app.ball_size / 2
|
||||
app.ball_y = f32(app.height) / 2 - app.ball_size / 2
|
||||
|
||||
scale_x := f32(app.width) / 800.0
|
||||
mut vx := ball_speed_base * scale_x
|
||||
if rand.intn(2) or { 0 } == 0 {
|
||||
vx = -vx
|
||||
}
|
||||
app.ball_vx = vx
|
||||
app.ball_vy = (f32(rand.intn(301) or { 150 } - 150)) * (f32(app.height) / 600.0)
|
||||
}
|
||||
|
||||
fn (mut app App) reset_game() {
|
||||
app.p1_score = 0
|
||||
app.p2_score = 0
|
||||
app.p1_y = f32(app.height) / 2 - app.paddle_height / 2
|
||||
app.p2_y = f32(app.height) / 2 - app.paddle_height / 2
|
||||
app.reset_ball()
|
||||
app.paused = false
|
||||
}
|
||||
|
||||
fn on_frame(data voidptr) {
|
||||
mut app := unsafe { &App(data) }
|
||||
app.ctx.begin()
|
||||
|
||||
// Draw dashed center line
|
||||
line_segments := 20
|
||||
segment_height := f32(app.height) / line_segments
|
||||
for i in 0 .. line_segments {
|
||||
if i % 2 == 0 {
|
||||
app.ctx.draw_rect_filled(f32(app.width) / 2 - 1, i * segment_height, 2, segment_height,
|
||||
color_line)
|
||||
}
|
||||
}
|
||||
|
||||
// Draw paddles
|
||||
app.ctx.draw_rounded_rect_filled(20, app.p1_y, app.paddle_width, app.paddle_height, 5, color_p1)
|
||||
app.ctx.draw_rounded_rect_filled(f32(app.width) - 20 - app.paddle_width, app.p2_y,
|
||||
app.paddle_width, app.paddle_height, 5, color_p2)
|
||||
|
||||
// Draw ball
|
||||
app.ctx.draw_circle_filled(app.ball_x + app.ball_size / 2, app.ball_y + app.ball_size / 2,
|
||||
app.ball_size / 2, color_foreground)
|
||||
|
||||
// Draw ball speed (10m = 800px => 1m = 80px)
|
||||
// We scale the meter definition with width
|
||||
meter_px := 80.0 * (f32(app.width) / 800.0)
|
||||
speed_px := math.sqrt(app.ball_vx * app.ball_vx + app.ball_vy * app.ball_vy)
|
||||
speed_ms := speed_px / meter_px
|
||||
app.ctx.draw_text(app.width / 2, 20, 'Ball: ${speed_ms:.1f} m/s',
|
||||
size: 20
|
||||
color: color_foreground
|
||||
align: .center
|
||||
)
|
||||
|
||||
// Draw scores and positions
|
||||
// Moved up to be less obtrusive
|
||||
app.ctx.draw_text(app.width / 4, 30, app.p1_score.str(),
|
||||
size: 40
|
||||
color: color_accent
|
||||
align: .center
|
||||
)
|
||||
app.ctx.draw_text(app.width / 4, 80, 'Y: ${int(app.p1_y)}',
|
||||
size: 16
|
||||
color: color_secondary
|
||||
align: .center
|
||||
)
|
||||
|
||||
app.ctx.draw_text(3 * app.width / 4, 30, app.p2_score.str(),
|
||||
size: 40
|
||||
color: color_accent
|
||||
align: .center
|
||||
)
|
||||
app.ctx.draw_text(3 * app.width / 4, 80, 'Y: ${int(app.p2_y)}',
|
||||
size: 16
|
||||
color: color_secondary
|
||||
align: .center
|
||||
)
|
||||
|
||||
if app.paused {
|
||||
app.ctx.draw_text(app.width / 2, app.height / 2 - 50, 'PAUSED',
|
||||
size: 64
|
||||
color: color_accent
|
||||
align: .center
|
||||
)
|
||||
app.ctx.draw_text(app.width / 2, app.height / 2 + 10, 'Press SPACE to Resume',
|
||||
size: 20
|
||||
color: color_accent
|
||||
align: .center
|
||||
)
|
||||
} else {
|
||||
app.ctx.draw_text(app.width / 2, app.height - 25,
|
||||
'SPACE: Pause | W/S: P1 | UP/DOWN: P2 | R: Reset',
|
||||
size: 16
|
||||
color: color_text_dim
|
||||
align: .center
|
||||
)
|
||||
}
|
||||
|
||||
app.ctx.end()
|
||||
}
|
||||
|
||||
fn on_event(e &gg.Event, data voidptr) {
|
||||
mut app := unsafe { &App(data) }
|
||||
if e.typ == .resized || e.typ == .restored {
|
||||
app.update_sizes()
|
||||
}
|
||||
if e.typ == .key_down {
|
||||
match e.key_code {
|
||||
.space { app.paused = !app.paused }
|
||||
.r { app.reset_game() }
|
||||
else {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_update(dt f32, data voidptr) {
|
||||
mut app := unsafe { &App(data) }
|
||||
|
||||
if app.paused {
|
||||
return
|
||||
}
|
||||
|
||||
scale_y := f32(app.height) / 600.0
|
||||
paddle_speed := paddle_speed_base * scale_y
|
||||
|
||||
// Paddle 1 movement (W/S)
|
||||
if app.ctx.pressed_keys[gg.KeyCode.w] {
|
||||
app.p1_y -= paddle_speed * dt
|
||||
}
|
||||
if app.ctx.pressed_keys[gg.KeyCode.s] {
|
||||
app.p1_y += paddle_speed * dt
|
||||
}
|
||||
|
||||
// Paddle 2 movement (Up/Down)
|
||||
if app.ctx.pressed_keys[gg.KeyCode.up] {
|
||||
app.p2_y -= paddle_speed * dt
|
||||
}
|
||||
if app.ctx.pressed_keys[gg.KeyCode.down] {
|
||||
app.p2_y += paddle_speed * dt
|
||||
}
|
||||
|
||||
// Constrain paddles
|
||||
if app.p1_y < 0 {
|
||||
app.p1_y = 0
|
||||
}
|
||||
if app.p1_y > f32(app.height) - app.paddle_height {
|
||||
app.p1_y = f32(app.height) - app.paddle_height
|
||||
}
|
||||
if app.p2_y < 0 {
|
||||
app.p2_y = 0
|
||||
}
|
||||
if app.p2_y > f32(app.height) - app.paddle_height {
|
||||
app.p2_y = f32(app.height) - app.paddle_height
|
||||
}
|
||||
|
||||
// Ball movement
|
||||
app.ball_x += app.ball_vx * dt
|
||||
app.ball_y += app.ball_vy * dt
|
||||
|
||||
// Ball wall collision (Top/Bottom)
|
||||
if app.ball_y <= 0 {
|
||||
app.ball_y = 0
|
||||
app.ball_vy = -app.ball_vy
|
||||
audio.push(app.sounds.ping.data, app.sounds.ping.len)
|
||||
} else if app.ball_y >= f32(app.height) - app.ball_size {
|
||||
app.ball_y = f32(app.height) - app.ball_size
|
||||
app.ball_vy = -app.ball_vy
|
||||
audio.push(app.sounds.ping.data, app.sounds.ping.len)
|
||||
}
|
||||
|
||||
// Ball paddle collision
|
||||
// P1
|
||||
if app.ball_vx < 0 && app.ball_x <= 20 + app.paddle_width && app.ball_x >= 20 {
|
||||
if app.ball_y + app.ball_size >= app.p1_y && app.ball_y <= app.p1_y + app.paddle_height {
|
||||
app.ball_x = 20 + app.paddle_width
|
||||
app.ball_vx = -app.ball_vx * 1.05 // Slightly speed up
|
||||
// Add some vertical velocity based on where it hit the paddle
|
||||
hit_pos := (app.ball_y + app.ball_size / 2) - (app.p1_y + app.paddle_height / 2)
|
||||
app.ball_vy += hit_pos * 5
|
||||
audio.push(app.sounds.pong.data, app.sounds.pong.len)
|
||||
}
|
||||
}
|
||||
|
||||
// P2
|
||||
if app.ball_vx > 0 && app.ball_x + app.ball_size >= f32(app.width) - 20 - app.paddle_width
|
||||
&& app.ball_x + app.ball_size <= f32(app.width) - 20 {
|
||||
if app.ball_y + app.ball_size >= app.p2_y && app.ball_y <= app.p2_y + app.paddle_height {
|
||||
app.ball_x = f32(app.width) - 20 - app.paddle_width - app.ball_size
|
||||
app.ball_vx = -app.ball_vx * 1.05 // Slightly speed up
|
||||
// Add some vertical velocity based on where it hit the paddle
|
||||
hit_pos := (app.ball_y + app.ball_size / 2) - (app.p2_y + app.paddle_height / 2)
|
||||
app.ball_vy += hit_pos * 5
|
||||
audio.push(app.sounds.pong.data, app.sounds.pong.len)
|
||||
}
|
||||
}
|
||||
|
||||
// Score
|
||||
if app.ball_x < 0 {
|
||||
app.p2_score++
|
||||
app.reset_ball()
|
||||
audio.push(app.sounds.buzz.data, app.sounds.buzz.len)
|
||||
} else if app.ball_x > f32(app.width) {
|
||||
app.p1_score++
|
||||
app.reset_ball()
|
||||
audio.push(app.sounds.buzz.data, app.sounds.buzz.len)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut app := &App{}
|
||||
app.width = 800
|
||||
app.height = 600
|
||||
app.paddle_width = 15.0
|
||||
app.paddle_height = 80.0
|
||||
app.ball_size = 15.0
|
||||
app.p1_y = f32(app.height) / 2 - app.paddle_height / 2
|
||||
app.p2_y = f32(app.height) / 2 - app.paddle_height / 2
|
||||
app.reset_ball()
|
||||
|
||||
audio.setup(buffer_frames: 512)
|
||||
app.sounds.init()
|
||||
|
||||
app.ctx = gg.new_context(
|
||||
width: app.width
|
||||
height: app.height
|
||||
window_title: 'V Pong'
|
||||
user_data: app
|
||||
frame_fn: on_frame
|
||||
event_fn: on_event
|
||||
update_fn: on_update
|
||||
bg_color: color_bg
|
||||
resizable: true
|
||||
create_window: true
|
||||
)
|
||||
|
||||
app.ctx.run()
|
||||
}
|
||||
Reference in new issue
Block a user