Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+44
View File
@@ -0,0 +1,44 @@
A small Pong game written in V using the `gg` and `sokol.audio` modules.
## Features
- **Proportional Resizing**: The game scales ball, paddles, and speeds based on the window size.
- **Audio Effects**: Synthesized sounds for wall bounces, paddle hits, and scoring.
- **Modern-Retro UI**: Deep blue background with vibrant accent colors.
- **Real-time Metrics**: Displays ball speed in m/s (assuming a 10m wide playfield).
## AI use note:
This example was created by Google gemini-cli `Auto (Gemini 3)`, using the following prompts:
1. create a small Pong game using gg in examples/gg/pong.v .
2. the game should start not paused. Show the current score for each player on the corresponding
side of the screen. Show the current vertical position for each player below the score too.
Show a green "PAUSED, press space to unpause" label in the bottom middle of the screen,
when the game is paused.
Show a yellow label "Press space - pause; W/S - left player; Up/Down - right player." in the
bottom middle of the screen, when the game is not paused.
3. make the score and position labels green
4. make the ball be a filled white circle
5. make the paddles be slightly rounded
6. make the pause key work when a key_down event is received; it is not like the other keys
7. make the background very dark blue
8. be a good game designer, and tweak the colors and positions of all the screen elements,
if they do not look good.
9. move the bottom status text even more down, so that the last stripe of the center line does not
intersect it. Move the paused label in the center of the screen and make it bigger
10. move the paused text about 80 pixels to the top; make the bottom status text slightly bigger
11. move the paused text about 30 pixels down
12. use the `sokol.audio` module and add simple sound effects to the game: when the ball bounces
on a wall, it should produce a ping sound; when the ball bounces on a paddle it should produce
a pong sound (slightly lower pitched); when the ball escapes behind a paddle, and the game
resets, produce a low pitched buzzing sound
13. the sounds work fine, but there is a very slight click at the end, that is especially
pronounced at the end of the buzz sound
14. the buzz sound still has a slight click at the end
15. make the left paddle bight red, and the right paddle bright blue
16. assume that the whole playfield is 10 meters wide (5 on the left and 5 on the right side).
Calculate the ball speed. Show the ball speed in the top center of the screen
(for example `Ball: 5.2 m/s`, note it should be always rounded at
the first digit after the dot (i.e. show 5 as `Ball: 5.0 m/s`).
17. make sure to resize the ball, paddles and playfield proportionally, when the screen
is maximized
18. append all my current user prompts to examples/gg/pong/README.md
+346
View File
@@ -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()
}