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

+256
View File
@@ -0,0 +1,256 @@
// Copyright (c) 2026 Delyan Angelov. All rights reserved.
// The use of this source code is governed by the MIT license.
module main
import gg
import os.asset
import rand
const n = 4
const tile = 110
const gap = 10
const pad = 24
const head = 52
const r = f32(18)
const speed = f32(0.14)
const board_px = n * tile + (n - 1) * gap
const width = pad * 2 + board_px
const height = head + board_px + 62
const wcolor = gg.rgb(104, 93, 79)
@[heap]
struct Game {
mut:
ctx &gg.Context = unsafe { nil }
board []int
moves int
won bool
anim bool
t f32
from int = -1
to int = -1
val int
}
fn main() {
mut g := &Game{}
g.shuffle()
g.ctx = gg.new_context(
bg_color: gg.rgb(247, 244, 235)
width: width
height: height
sample_count: 4
window_title: '15 Puzzle'
user_data: g
frame_fn: frame
event_fn: event
font_path: asset.get_path('../assets', 'fonts/Graduate-Regular.ttf')
)
g.ctx.run()
}
fn frame(mut g Game) {
g.step()
g.ctx.begin()
g.header()
g.board()
g.ctx.end()
}
fn event(e &gg.Event, mut g Game) {
if e.typ == .key_down {
match e.key_code {
.escape {
g.ctx.quit()
}
.r {
g.shuffle()
}
.up, .w {
if !g.anim {
g.slide(1, 0)
}
}
.down, .s {
if !g.anim {
g.slide(-1, 0)
}
}
.left, .a {
if !g.anim {
g.slide(0, 1)
}
}
.right, .d {
if !g.anim {
g.slide(0, -1)
}
}
else {}
}
return
}
if !g.anim && e.typ == .mouse_down && e.mouse_button == .left {
if i := g.hit(int(e.mouse_x), int(e.mouse_y)) {
g.move(i)
}
}
}
fn (mut g Game) header() {
g.ctx.draw_text(width / 2, 16, if g.won {
'Solved in ${g.moves} moves. Press R to reshuffle.'
} else {
'Moves: ${g.moves}'
},
size: if g.won { 16 } else { 20 }
bold: true
align: .center
color: if g.won { wcolor } else { gg.rgb(48, 44, 37) }
)
}
fn (mut g Game) board() {
g.ctx.draw_rect_filled(pad - 8, head - 8, board_px + 16, board_px + 16, gg.rgb(218, 206, 188))
for i, v in g.board {
if g.anim && i == g.from {
continue
}
x, y := xy(i)
g.tile(x, y, v, v != 0 && v == i + 1)
}
if g.anim {
x0, y0 := xy(g.from)
x1, y1 := xy(g.to)
g.tile(int(f32(x0) + f32(x1 - x0) * g.t), int(f32(y0) + f32(y1 - y0) * g.t), g.val, false)
}
g.ctx.draw_text(pad, head + board_px + 22,
'Arrow keys / WASD or click a tile next to the empty space.',
size: 16
color: wcolor
)
g.ctx.draw_text(width / 2, height - 18, '[R] Shuffle [Esc] Quit',
size: 16
bold: true
align: .center
vertical_align: .middle
color: wcolor
)
}
fn (mut g Game) tile(x int, y int, v int, ok bool) {
fill, border := if v == 0 {
gg.rgb(238, 232, 220), gg.rgb(210, 202, 191)
} else if ok {
gg.rgb(214, 173, 108), gg.rgb(168, 121, 56)
} else {
gg.rgb(84, 110, 122), gg.rgb(48, 66, 74)
}
g.ctx.draw_rounded_rect_filled(x, y, tile, tile, r, fill)
g.ctx.draw_rounded_rect_empty(x, y, tile, tile, r, border)
if v != 0 {
g.ctx.draw_text(x + tile / 2, y + tile / 2, v.str(),
size: 42
bold: true
align: .center
vertical_align: .middle
color: gg.white
)
}
}
fn (mut g Game) shuffle() {
g.board = []int{len: n * n}
for i in 0 .. g.board.len - 1 {
g.board[i] = i + 1
}
for _ in 0 .. 300 {
e := g.board.index(0)
ns := neighbors(e)
j := ns[rand.intn(ns.len) or { 0 }]
g.board[e], g.board[j] = g.board[j], g.board[e]
}
if done(g.board) {
g.board[g.board.len - 2], g.board[g.board.len - 3] = g.board[g.board.len - 3], g.board[g.board.len - 2]
}
g.moves, g.won, g.anim, g.t, g.from, g.to, g.val = 0, false, false, 0, -1, -1, 0
}
fn (mut g Game) slide(dr int, dc int) {
e := g.board.index(0)
row, col := e / n + dr, e % n + dc
if row >= 0 && row < n && col >= 0 && col < n {
g.move(row * n + col)
}
}
fn (mut g Game) move(i int) bool {
if g.won || g.anim || i < 0 || i >= g.board.len || g.board[i] == 0 {
return false
}
e := g.board.index(0)
dr, dc := i / n - e / n, i % n - e % n
if !((dr == 0 && (dc == 1 || dc == -1)) || (dc == 0 && (dr == 1 || dr == -1))) {
return false
}
g.anim, g.t, g.from, g.to, g.val = true, 0, i, e, g.board[i]
return true
}
fn (mut g Game) step() {
if !g.anim {
return
}
g.t += speed
if g.t < 1 {
return
}
g.board[g.to], g.board[g.from] = g.val, 0
g.moves++
g.won = done(g.board)
g.anim, g.t, g.from, g.to, g.val = false, 0, -1, -1, 0
}
fn (g &Game) hit(mx int, my int) ?int {
if mx < pad || my < head || mx >= pad + board_px || my >= head + board_px {
return none
}
x, y := mx - pad, my - head
col, row := x / (tile + gap), y / (tile + gap)
if x % (tile + gap) >= tile || y % (tile + gap) >= tile {
return none
}
return row * n + col
}
fn neighbors(i int) []int {
row, col := i / n, i % n
mut ns := []int{}
if row > 0 {
ns << i - n
}
if row + 1 < n {
ns << i + n
}
if col > 0 {
ns << i - 1
}
if col + 1 < n {
ns << i + 1
}
return ns
}
fn xy(i int) (int, int) {
return pad + (i % n) * (tile + gap), head + (i / n) * (tile + gap)
}
fn done(b []int) bool {
for i in 0 .. b.len - 1 {
if b[i] != i + 1 {
return false
}
}
return b[b.len - 1] == 0
}
+124
View File
@@ -0,0 +1,124 @@
module main
import os
import gg
import math
@[heap]
pub struct Window {
pub mut:
ctx &gg.Context = unsafe { 0 }
image gg.Image
}
pub fn (mut window Window) init() {
logo_path := os.join_path(@VEXEROOT, 'examples/assets/logo.png')
window.image = window.ctx.create_image(logo_path) or { panic(err) }
}
pub fn (mut window Window) draw(_ voidptr) {
window.ctx.begin()
myconfig := gg.DrawImageConfig{
img: &window.image
img_id: window.image.id
img_rect: gg.Rect{
x: 400 - window.image.width / 2
y: 300 - window.image.height / 2
width: window.image.width
height: window.image.height
}
rotation: f32(window.ctx.frame)
// effect: .alpha <-- this can be omitted completely as it is alpha by default.
}
window.ctx.draw_image_with_config(gg.DrawImageConfig{ ...myconfig, flip_x: true })
// Red
window.ctx.draw_image_with_config(gg.DrawImageConfig{
...myconfig
img_rect: gg.Rect{
...myconfig.img_rect
x: myconfig.img_rect.x + f32(math.sin(f32(window.ctx.frame) / 10.0) * 60)
y: myconfig.img_rect.y + f32(math.cos(f32(window.ctx.frame) / 10.0) * 60)
}
color: gg.Color{255, 0, 0, 255}
effect: .add
})
// Green
window.ctx.draw_image_with_config(gg.DrawImageConfig{
...myconfig
img_rect: gg.Rect{
...myconfig.img_rect
x: myconfig.img_rect.x + f32(math.sin(f32(window.ctx.frame) / 10.0) * 80)
y: myconfig.img_rect.y + f32(math.cos(f32(window.ctx.frame) / 10.0) * 80)
}
color: gg.Color{0, 255, 0, 255}
effect: .add
})
// Blue
window.ctx.draw_image_with_config(gg.DrawImageConfig{
...myconfig
img_rect: gg.Rect{
...myconfig.img_rect
x: myconfig.img_rect.x + f32(math.sin(f32(window.ctx.frame) / 10.0) * 100)
y: myconfig.img_rect.y + f32(math.cos(f32(window.ctx.frame) / 10.0) * 100)
}
color: gg.Color{0, 0, 255, 255}
effect: .add
})
// More examples
window.ctx.draw_image_with_config(gg.DrawImageConfig{
...myconfig
img_rect: gg.Rect{
...myconfig.img_rect
x: 50
y: 0
}
color: gg.Color{255, 0, 0, 255}
effect: .add
})
window.ctx.draw_image_with_config(gg.DrawImageConfig{
...myconfig
img_rect: gg.Rect{
...myconfig.img_rect
x: 50
y: 50
}
color: gg.Color{0, 255, 0, 255}
effect: .add
})
window.ctx.draw_image_with_config(gg.DrawImageConfig{
...myconfig
img_rect: gg.Rect{
...myconfig.img_rect
x: 50
y: 100
}
color: gg.Color{0, 0, 255, 255}
effect: .add
})
window.ctx.end()
}
fn main() {
mut window := &Window{}
window.ctx = gg.new_context(
window_title: 'Additive colors & image rotation'
width: 800
height: 600
user_data: window
bg_color: gg.gray
// FNs
init_fn: window.init
frame_fn: window.draw
)
window.ctx.run()
}
+168
View File
@@ -0,0 +1,168 @@
module main
import gg
import math
const win_width = 700
const win_height = 800
const bg_color = gg.white
// A transparent color is used to aid in verifying that
// rendering is precise on each of the arc types (e.g. no overlapping or double rendered slices)
const colour = gg.rgba(100, 100, 0, 100)
enum Selection {
segs = 0
len
}
struct App {
mut:
gg &gg.Context = unsafe { nil }
mouse struct {
mut:
x f32
y f32
}
sel Selection
segs int = 8
}
fn main() {
mut app := &App{}
app.gg = gg.new_context(
width: win_width
height: win_height
create_window: true
window_title: 'Arcs and Slices'
user_data: app
bg_color: bg_color
frame_fn: on_frame
event_fn: on_event
)
app.gg.run()
}
fn on_frame(mut app App) {
app.gg.begin()
start := math.tau * app.mouse.y / (win_width * app.gg.scale)
end := math.tau * app.mouse.x / (win_width * app.gg.scale)
segs := if app.sel == .segs { '[${app.segs}]' } else { '${app.segs}' }
app.gg.draw_text_def(10, 10, 'Segments: ${segs}')
app.gg.draw_text_def(250, 10, 'Drawing Angles (radians)')
app.gg.draw_text_def(200, 26, 'Start: ${start}°')
app.gg.draw_text_def(350, 26, 'End: ${end}°')
mut x, mut y := 0, -80
y += 150
x = 20
app.gg.draw_text_def(10, y + 40, 'slice')
x += 150
app.gg.draw_slice_empty(x, y + 60, 50, start, end, app.segs, colour)
app.gg.draw_text_def(x - 50, y + 120, 'r=50 empty')
x += 150
app.gg.draw_slice_empty(x, y + 60, 0, start, end, app.segs, colour)
app.gg.draw_text_def(x - 50, y + 120, 'r=0 empty')
x += 150
app.gg.draw_slice_filled(x, y + 60, 50, start, end, app.segs, colour)
app.gg.draw_text_def(x - 50, y + 120, 'r=50 filled')
x += 150
app.gg.draw_slice_filled(x, y + 60, 0, start, end, app.segs, colour)
app.gg.draw_text_def(x - 50, y + 120, 'r=0 filled')
y += 150
x = 20
app.gg.draw_text_def(10, y + 40, 'arc_empty')
x += 150
app.gg.draw_arc_empty(x, y + 60, 30, 20, start, end, app.segs, colour)
app.gg.draw_text_def(x - 50, y + 120, 'r=[30,50]')
x += 150
app.gg.draw_arc_empty(x, y + 60, -10, 60, start, end, app.segs, colour)
app.gg.draw_text_def(x - 50, y + 120, 'r=[-10,50]')
x += 150
app.gg.draw_arc_empty(x, y + 60, 50, 0, start, end, app.segs, colour)
app.gg.draw_text_def(x - 50, y + 120, 'r=[50,50]')
x += 150
app.gg.draw_arc_empty(x, y + 60, 0, 0, start, end, app.segs, colour)
app.gg.draw_text_def(x - 50, y + 120, 'r=[0,0]')
y += 150
x = 20
app.gg.draw_text_def(10, y + 40, 'arc_filled')
x += 150
app.gg.draw_arc_filled(x, y + 60, 30, 20, start, end, app.segs, colour)
app.gg.draw_text_def(x - 50, y + 120, 'r=[30,50]')
x += 150
app.gg.draw_arc_filled(x, y + 60, -10, 60, start, end, app.segs, colour)
app.gg.draw_text_def(x - 50, y + 120, 'r=[-10,50]')
x += 150
app.gg.draw_arc_filled(x, y + 60, 50, 0, start, end, app.segs, colour)
app.gg.draw_text_def(x - 50, y + 120, 'r=[50,50]')
x += 150
app.gg.draw_arc_filled(x, y + 60, 0, 0, start, end, app.segs, colour)
app.gg.draw_text_def(x - 50, y + 120, 'r=[0,0]')
y += 150
x = 20
app.gg.draw_text_def(10, y + 40, 'arc_line')
x += 150
app.gg.draw_arc_line(x, y + 60, 50, start, end, app.segs, colour)
app.gg.draw_text_def(x - 50, y + 120, 'r=50')
x += 150
app.gg.draw_arc_line(x, y + 60, 0, start, end, app.segs, colour)
app.gg.draw_text_def(x - 50, y + 120, 'r=0')
y += 150
app.gg.draw_text_def(10, y + 20, 'Use arrow keys to increase/decrease number of segments.')
app.gg.draw_text_def(10, y + 36,
'Use the mouse to adjust the start/end angles, in radians. Mouse position (0,0) is at the top-left of the window.')
app.gg.draw_text_def(10, y + 52,
'Note: because y=0 is at the top of the screen and not the bottom, angle=0 is at the bottom of an arc, not the top!')
app.gg.draw_text_def(10, y + 68,
'Compared to a graph, where y=0 is at the bottom, arcs therefore appear y-flipped.')
app.gg.end()
}
fn on_event(e &gg.Event, mut app App) {
match e.typ {
.key_down {
match e.key_code {
.escape {
app.gg.quit()
}
.up {
app.sel = unsafe { Selection(math.max(0, int(app.sel) - 1)) }
}
.down {
app.sel = unsafe { Selection(math.min(int(Selection.len) - 1, int(app.sel) + 1)) }
}
.left {
match app.sel {
.segs {
app.segs = math.max(1, app.segs / 2)
}
else {}
}
}
.right {
match app.sel {
.segs {
app.segs = math.min(64, app.segs * 2)
}
else {}
}
}
else {}
}
}
.mouse_move {
app.mouse.x = e.mouse_x
app.mouse.y = e.mouse_y
}
else {}
}
}
+42
View File
@@ -0,0 +1,42 @@
module main
import gg
const points = [f32(200.0), 200.0, 200.0, 100.0, 400.0, 100.0, 400.0, 300.0]
struct App {
mut:
gg &gg.Context = unsafe { nil }
steps int = 30
}
fn main() {
mut app := &App{}
app.gg = gg.new_context(
bg_color: gg.rgb(174, 198, 255)
width: 600
height: 400
window_title: 'Cubic Bézier curve'
frame_fn: frame
user_data: app
)
app.gg.run()
}
fn (mut app App) change(delta int) {
app.steps += delta
println('app.steps: ${app.steps}')
}
fn frame(mut app App) {
app.gg.begin()
app.gg.draw_cubic_bezier_in_steps(points, u32(app.steps), gg.blue)
app.gg.draw_cubic_bezier_recursive(points, gg.rgba(255, 50, 50, 150))
app.gg.end()
if app.gg.pressed_keys[int(gg.KeyCode.down)] {
app.change(-1)
}
if app.gg.pressed_keys[int(gg.KeyCode.up)] {
app.change(1)
}
}
+66
View File
@@ -0,0 +1,66 @@
module main
import gg
const rate = f32(1) / 60 * 10
struct App {
mut:
gg &gg.Context = unsafe { nil }
anim &Anim = unsafe { nil }
}
struct Anim {
mut:
time f32
reverse bool
}
fn (mut anim Anim) advance() {
if anim.reverse {
anim.time -= 1 * rate
} else {
anim.time += 1 * rate
}
// Use some arbitrary value that fits 60 fps
if anim.time > 80 * rate || anim.time < -80 * rate {
anim.reverse = !anim.reverse
}
}
fn main() {
mut app := &App{
anim: &Anim{}
}
app.gg = gg.new_context(
bg_color: gg.rgb(174, 198, 255)
width: 600
height: 400
window_title: 'Animated cubic Bézier curve'
frame_fn: frame
user_data: app
)
app.gg.run()
}
fn frame(mut app App) {
time := app.anim.time
p1_x := f32(200.0)
p1_y := f32(200.0) + (10 * time)
p2_x := f32(400.0)
p2_y := f32(200.0) + (10 * time)
ctrl_p1_x := f32(200.0) + (40 * time)
ctrl_p1_y := f32(100.0)
ctrl_p2_x := f32(400.0) + (-40 * time)
ctrl_p2_y := f32(100.0)
points := [p1_x, p1_y, ctrl_p1_x, ctrl_p1_y, ctrl_p2_x, ctrl_p2_y, p2_x, p2_y]
app.gg.begin()
app.gg.draw_cubic_bezier(points, gg.blue)
app.gg.end()
app.anim.advance()
}
+88
View File
@@ -0,0 +1,88 @@
@[has_globals]
module main
import gg
import rand
__global app = App{}
struct Ball {
mut:
x f32
y f32
dx f32
dy f32
color gg.Color
}
struct App {
mut:
size gg.Size = gg.Size{1024, 768}
balls []Ball
}
fn main() {
init_app()
gg.start(
window_title: 'Bouncing balls'
width: app.size.width
height: app.size.height
frame_fn: on_frame
)
}
fn on_frame(ctx &gg.Context) {
app.size = gg.window_size()
if ctx.frame % 20000 == 0 {
init_app()
}
for mut ball in app.balls {
move_ball(mut ball)
}
ctx.begin()
for ball in app.balls {
ctx.draw_circle_filled(ball.x, app.size.height - ball.y, 6, ball.color)
}
ctx.draw_text(10, 20, 'Frame: ${ctx.frame:06}', color: gg.Color{255, 255, 255, 255}, size: 20)
ctx.end()
}
fn init_app() {
app.balls.clear()
for _ in 0 .. 2500 {
app.balls << Ball{
x: rand.f32() * app.size.width
y: 3 * app.size.height / 4
dx: 5 * rand.f32()
dy: 5 * rand.f32()
color: gg.Color{rand.u8(), rand.u8(), rand.u8(), 255}
}
}
}
@[inline]
fn move_ball(mut ball Ball) {
ball.x += ball.dx
ball.y += ball.dy
ball.dy -= 9.81 * 0.01
if ball.x <= 0 || ball.x >= app.size.width - 1 {
ball.dx = -ball.dx
apply_friction(mut ball)
}
if ball.y <= 0 || ball.y >= app.size.height - 1 {
ball.dy = -ball.dy
apply_friction(mut ball)
}
// apply some clipping:
ball.x = f32_max(-5, f32_min(f32(app.size.width) * 1.3, ball.x))
ball.y = f32_max(-5, f32_min(f32(app.size.height) * 1.3, ball.y))
ball.dx = f32_max(-50, f32_min(30, ball.dx))
ball.dy = f32_max(-50, f32_min(30, ball.dy))
}
@[inline]
fn apply_friction(mut ball Ball) {
// apply some random friction on each bounce:
ball.dy *= (1 - rand.f32() * 0.1)
ball.dx *= (1 - rand.f32() * 0.01)
}
+24
View File
@@ -0,0 +1,24 @@
module main
import gg
import sokol.sapp
fn main() {
mut ctx := gg.new_context(
bg_color: gg.white
window_title: 'Cursor'
frame_fn: frame
init_fn: init
)
ctx.run()
}
fn init(mut _ctx gg.Context) {
sapp.set_mouse_cursor(.ibeam)
}
fn frame(mut ctx gg.Context) {
ctx.begin()
ctx.draw_text_def(10, 25, 'Your cursor should be an I shaped beam.')
ctx.end()
}
+178
View File
@@ -0,0 +1,178 @@
// Creates the digital rain effect from the movie, "The Matrix"
module main
import gg
import rand
import time
const font_size = 20
const rain_drops = '0123456789!@#$%^&*()-=+[]{}|;:<>?~bdjpqtvz'.bytes()
struct App {
mut:
ctx &gg.Context = unsafe { nil }
rows int
cols int
char_width int
char_height int
screen_size gg.Size
should_calc bool = true
rain_columns []RainColumn
delay time.Duration = time.millisecond * 100
}
struct RainColumn {
mut:
col int // character based postion
len int // length of the rain column in characters
head int // y position of the rain column
drops []u8 // no retained graphics in gg, store entire column
}
fn main() {
mut app := &App{}
rain(mut app)
}
fn rain(mut app App) {
app.ctx = gg.new_context(
bg_color: gg.rgb(0, 0, 0)
width: app.screen_size.width
height: app.screen_size.height
user_data: app
window_title: 'Digital Rain'
init_fn: fn (mut app App) {
gg.toggle_fullscreen()
}
event_fn: fn (event &gg.Event, mut app App) {
vprintln('event.typ: ${event.typ} | event.char_code: ${event.char_code}')
if event.typ == .resized {
app.should_calc = true
return
}
if event.typ == .char && event.char_code == `f` {
gg.toggle_fullscreen()
return
}
if event.typ == .key_up && event.key_code == .up {
app.delay = app.delay + 50 * time.millisecond
}
if event.typ == .key_up && event.key_code == .down {
new_delay := app.delay - 50 * time.millisecond
app.delay = if new_delay > 0 { new_delay } else { 0 }
}
}
frame_fn: frame
)
app.ctx.run()
}
fn frame(mut app App) {
app.ctx.begin()
if app.should_calc {
app.should_calc = false
calc_sizes(mut app)
}
// gradually add rain columns
if app.rain_columns.len < app.cols / 4 * 3 {
app.rain_columns << random_rain_column(app.cols, app.rows)
}
// update and print all rain columns
for mut rc in app.rain_columns {
update_rain_column(mut rc, app.cols, app.rows)
draw_rain_column(rc, app)
}
app.ctx.draw_text(app.screen_size.width / 2 - 190, app.screen_size.height - 15,
'press `f` to toggle fullscreen, Up/Down arrows to change speed',
color: gg.gray
)
app.ctx.end()
vprintln('frame: ${app.ctx.frame} | app.cols: ${app.cols} | app.rows: ${app.rows} | app.rain_columns.len: ${app.rain_columns.len} | app.delay: ${app.delay}')
time.sleep(app.delay)
}
@[if verbose ?]
fn vprintln(msg string) {
println(msg)
}
fn calc_sizes(mut app App) {
app.screen_size = gg.window_size()
app.ctx.set_text_cfg(gg.TextCfg{
size: font_size
color: gg.green
mono: true
})
// figure out how big character is in pixels
// Pad it or it looks too squashed
app.char_width, app.char_height = app.ctx.text_size('M')
app.char_width += 3
app.char_height += 1
// determine the size of the matrix in rows and columns
app.cols = app.screen_size.width / app.char_width
app.rows = app.screen_size.height / app.char_height
vprintln('app.cols: ${app.cols} | app.rows: ${app.rows}')
}
fn update_rain_column(mut rc RainColumn, width int, height int) {
rc.head += 1
if rc.head > height + rc.len {
rc = random_rain_column(width, height)
}
}
fn draw_rain_column(rc RainColumn, app App) {
mut y := 0
x := rc.col * app.char_width
end := rc.head - rc.len
for i in 0 .. app.rows - 1 {
if i >= end && i < rc.head {
alpha := match i - end {
0 { u8(75) }
1 { u8(100) }
2 { u8(125) }
3 { u8(150) }
4 { u8(175) }
5 { u8(200) }
6 { u8(225) }
else { u8(255) }
}
at_head := i == rc.head - 1
cfg := gg.TextCfg{
size: font_size
color: gg.Color{
r: if at_head { u8(255) } else { 0 }
g: 255
b: if at_head { u8(255) } else { 0 }
a: alpha
}
mono: true
}
if i < rc.drops.len {
app.ctx.draw_text(x, y, rc.drops[i].ascii_str(), cfg)
app.ctx.draw_text(x, y, rc.drops[(i + 10) % rc.drops.len].ascii_str(), cfg)
} else {
vprintln('BAD i: ${i} | rc.drops.len: ${rc.drops.len}')
}
}
y += app.char_height
}
}
fn random_rain_column(max_col int, max_height int) RainColumn {
min_len := 6
mut rc := RainColumn{
col: rand.int_in_range(0, max_col) or { 0 }
len: rand.int_in_range(min_len, max_height / 4 * 3) or { min_len }
drops: []u8{cap: max_height}
}
for _ in 0 .. max_height {
rc.drops << random_rain_drop()
}
return rc
}
fn random_rain_drop() u8 {
return rand.element(rain_drops) or { rain_drops[0] }
}
+61
View File
@@ -0,0 +1,61 @@
module main
import gg
import sokol.sapp
const max_files = 12
const text_size = 16
struct App {
mut:
gg &gg.Context = unsafe { nil }
dropped_file_list []string
}
fn main() {
mut app := &App{}
app.gg = gg.new_context(
bg_color: gg.rgb(174, 198, 255)
width: 600
height: 400
window_title: 'Drag and drop'
frame_fn: frame
user_data: app
event_fn: my_event_manager
// drag & drop
enable_dragndrop: true
max_dropped_files: max_files
max_dropped_file_path_length: 2048
)
app.gg.run()
}
fn my_event_manager(mut ev gg.Event, mut app App) {
// drag&drop event
if ev.typ == .files_dropped {
num_dropped := sapp.get_num_dropped_files()
app.dropped_file_list.clear()
for i in 0 .. num_dropped {
app.dropped_file_list << sapp.get_dropped_file_path(i)
}
}
}
fn frame(mut app App) {
app.gg.begin()
mut txt_conf := gg.TextCfg{
color: gg.black
align: .left
size: int(text_size * app.gg.scale + 0.5)
}
app.gg.draw_text(12, 12, 'Drag&Drop here max ${max_files} files.', txt_conf)
mut y := 40
for c, f in app.dropped_file_list {
app.gg.draw_text(12, y, '[${c}] ${f}', txt_conf)
y += text_size
}
app.gg.end()
}
+52
View File
@@ -0,0 +1,52 @@
module main
import gg
struct App {
mut:
gg &gg.Context = unsafe { nil }
pixels []f32
}
fn main() {
mut pixels := []f32{}
density := 4
for x in 30 .. 60 {
if x % density == 0 {
continue
}
for y in 30 .. 60 {
if y % density == 0 {
continue
}
pixels << f32(x + density)
pixels << f32(y + density)
}
}
mut app := &App{
pixels: pixels
}
app.gg = gg.new_context(
bg_color: gg.rgb(174, 198, 255)
width: 100
height: 100
window_title: 'Set Pixels'
frame_fn: frame
user_data: app
)
app.gg.run()
}
fn frame(mut app App) {
app.gg.begin()
// Draw a blue pixel near each corner. (Find your magnifying glass)
app.gg.draw_pixel(2, 2, gg.blue)
app.gg.draw_pixel(app.gg.width - 2, 2, gg.blue)
app.gg.draw_pixel(app.gg.width - 2, app.gg.height - 2, gg.blue)
app.gg.draw_pixel(2, app.gg.height - 2, gg.blue)
// Draw pixels in a grid-like pattern.
app.gg.draw_pixels(app.pixels, gg.red)
app.gg.end()
}
@@ -0,0 +1,33 @@
import os
import gg
println('Usage: `v run examples/gg/draw_unicode_text_with_gg.v [FONT_PATH] [TEXT_PATH]`')
font_path := os.args[1] or {
os.resource_abs_path(os.join_path('..', 'assets', 'fonts', 'RobotoMono-Regular.ttf'))
}
dump(font_path)
text_path := os.args[2] or {
os.resource_abs_path(os.join_path('..', 'ttf_font/draw_static_text.txt'))
}
dump(text_path)
text := os.read_file(text_path)!
dump(text)
gg.start(
window_title: 'Draw unicode text with gg'
bg_color: gg.Color{155, 155, 128, 255}
width: 1024
height: 140
font_path: font_path
frame_fn: fn [text] (ctx &gg.Context) {
ctx.begin()
ctx.draw_text(30, 50, text,
size: 24
color: gg.Color{50, 50, 255, 255}
)
ctx.end()
}
)
+110
View File
@@ -0,0 +1,110 @@
module main
import gg
import math.easing
const all = {
'in_functions': {
'linear': voidptr(easing.linear)
'in_sine': easing.in_sine
'in_quad': easing.in_quad
'in_cubic': easing.in_cubic
'in_quart': easing.in_quart
'in_quint': easing.in_quint
'in_expo': easing.in_expo
'in_circ': easing.in_circ
'in_back': easing.in_back
'in_elastic': easing.in_elastic
'in_bounce': easing.in_bounce
}
'out_functions': {
'linear': voidptr(easing.linear)
'out_sine': easing.out_sine
'out_quad': easing.out_quad
'out_cubic': easing.out_cubic
'out_quart': easing.out_quart
'out_quint': easing.out_quint
'out_expo': easing.out_expo
'out_circ': easing.out_circ
'out_back': easing.out_back
'out_elastic': easing.out_elastic
'out_bounce': easing.out_bounce
}
'in_out_functions': {
'linear': voidptr(easing.linear)
'in_out_sine': easing.in_out_sine
'in_out_quad': easing.in_out_quad
'in_out_cubic': easing.in_out_cubic
'in_out_quart': easing.in_out_quart
'in_out_quint': easing.in_out_quint
'in_out_expo': easing.in_out_expo
'in_out_circ': easing.in_out_circ
'in_out_back': easing.in_out_back
'in_out_elastic': easing.in_out_elastic
'in_out_bounce': easing.in_out_bounce
}
}
const all_keys = all.keys()
@[heap]
struct App {
mut:
gg &gg.Context = unsafe { nil }
x f64
t f64
h f64
kind string = all_keys.first()
}
fn (mut app App) draw_circle(label string, f easing.EasingFN) {
offset := 30
app.gg.draw_text_def(int(app.x) - 30, 5, label)
app.gg.draw_line(f32(app.x), offset, f32(app.x), f32(app.h + offset), gg.gray)
app.gg.draw_circle_filled(f32(app.x), f32(offset + f(app.t) * app.h), 10, gg.rgb(0, 0, 255))
app.x += 120
}
fn (mut app App) frame() {
size := gg.window_size()
period := u64(240)
app.t = f32_min(1.0, f32(app.gg.frame % period) / f32(period - 30))
app.x = 80
app.h = size.height - 100
app.gg.begin()
app.gg.draw_line(0, f32(app.h + 30), size.width, f32(app.h + 30), gg.gray)
current_map := unsafe { all[app.kind].clone() }
for k, e in current_map {
app.draw_circle(k, e)
}
app.gg.draw_text_def(50, int(app.h + 50),
'Note: use left and right arrows to change functions. Frame: ${app.gg.frame:010} | t: ${app.t:6.3f} | kind: ${app.kind}.')
app.gg.end()
}
fn (mut app App) change_functions(direction int) {
idx := (all_keys.len + all_keys.index(app.kind) + direction) % all_keys.len
app.kind = all_keys[idx]
}
fn (mut app App) on_event(ev &gg.Event, _x voidptr) {
if ev.typ != .key_down {
return
}
if ev.key_code == .left {
app.change_functions(-1)
} else if ev.key_code == .right {
app.change_functions(1)
}
}
mut app := &App{}
app.gg = gg.new_context(
bg_color: gg.rgb(174, 198, 255)
width: 1350
height: 800
window_title: 'Easing functions'
frame_fn: app.frame
event_fn: app.on_event
user_data: app
)
app.gg.run()
+14
View File
@@ -0,0 +1,14 @@
import gg
mut r := &gg.Rect{20, 20, 60, 20}
gg.start(
update_fn: fn [mut r] (dt f32, c &gg.Context) {
r.width++
r.height++
}
frame_fn: fn [mut r] (c &gg.Context) {
c.begin()
c.draw_rect_filled(r.x, r.y, r.width, r.height, gg.frgb(0.0, 0.4, 0.4))
c.end()
}
)
+149
View File
@@ -0,0 +1,149 @@
// Use `v -d show_fps run examples/gg/fire.v` to show a fire effect with an FPS counter.
import gg
import rand
const win_width = 800
const win_height = 600
const width = 100
const height = 140
const scale = 4
const palette = [
gg.rgb(0x07, 0x07, 0x07),
gg.rgb(0x1f, 0x07, 0x07),
gg.rgb(0x2f, 0x0f, 0x07),
gg.rgb(0x47, 0x0f, 0x07),
gg.rgb(0x57, 0x17, 0x07),
gg.rgb(0x67, 0x1f, 0x07),
gg.rgb(0x77, 0x1f, 0x07),
gg.rgb(0x8f, 0x27, 0x07),
gg.rgb(0x9f, 0x2f, 0x07),
gg.rgb(0xaf, 0x3f, 0x07),
gg.rgb(0xbf, 0x47, 0x07),
gg.rgb(0xc7, 0x47, 0x07),
gg.rgb(0xdf, 0x4f, 0x07),
gg.rgb(0xdf, 0x57, 0x07),
gg.rgb(0xdf, 0x57, 0x07),
gg.rgb(0xd7, 0x5f, 0x07),
gg.rgb(0xd7, 0x5f, 0x07),
gg.rgb(0xd7, 0x67, 0x0f),
gg.rgb(0xcf, 0x6f, 0x0f),
gg.rgb(0xcf, 0x77, 0x0f),
gg.rgb(0xcf, 0x7f, 0x0f),
gg.rgb(0xcf, 0x87, 0x17),
gg.rgb(0xc7, 0x87, 0x17),
gg.rgb(0xc7, 0x8f, 0x17),
gg.rgb(0xc7, 0x97, 0x1f),
gg.rgb(0xbf, 0x9f, 0x1f),
gg.rgb(0xbf, 0x9f, 0x1f),
gg.rgb(0xbf, 0xa7, 0x27),
gg.rgb(0xbf, 0xa7, 0x27),
gg.rgb(0xbf, 0xaf, 0x2f),
gg.rgb(0xb7, 0xaf, 0x2f),
gg.rgb(0xb7, 0xb7, 0x2f),
gg.rgb(0xb7, 0xb7, 0x37),
gg.rgb(0xcf, 0xcf, 0x6f),
gg.rgb(0xdf, 0xdf, 0x9f),
gg.rgb(0xef, 0xef, 0xc7),
gg.rgb(0xff, 0xff, 0xff),
]!
struct App {
mut:
gg &gg.Context = unsafe { nil }
buf [][]int
dying bool
tiles int
}
fn main() {
mut app := &App{
buf: [][]int{len: height - 1, init: []int{len: width}}
}
app.buf << []int{len: width, init: 36} // white fire base
app.gg = gg.new_context(
event_fn: event
frame_fn: frame
init_fn: retile
window_title: 'Fire Animation'
user_data: app
bg_color: palette[0]
width: win_width
height: win_height
)
app.gg.run()
}
fn retile(mut app App) {
size := app.gg.window_size()
app.tiles = size.width / (width * scale) + 1
}
fn (mut app App) draw() {
size := app.gg.window_size()
for t in 0 .. app.tiles {
app.gg.begin()
for y, row in app.buf {
for x, i in row {
app.gg.draw_square_filled(t * width * scale + x * scale,
(size.height - height * scale) + y * scale, scale, palette[i])
}
}
app.gg.end(
how: if t == 0 { .clear } else { .passthru }
)
// one pass per tile to avoid sgl vertex limit
}
}
@[direct_array_access]
fn (mut app App) tick() {
for x in 0 .. width {
for y in 1 .. height {
if app.buf[y][x] > 0 {
r := rand.intn(4) or { panic('failed to generate random number') }
xn := ((x - r + 1) % width + width) % width
app.buf[y - 1][xn] = app.buf[y][x] - r % 2
} else {
app.buf[y - 1][x] = 0
}
}
}
if app.dying {
for x in 0 .. width {
y := height - 1
app.buf[y][x] -= rand.intn(4) or { panic('failed to generate random number') }
if app.buf[y][x] < 0 {
app.buf[y][x] = 0
}
}
}
}
fn event(event &gg.Event, mut app App) {
match event.typ {
.key_up {
match event.key_code {
.space {
if app.dying {
app.dying = false
app.buf[height - 1] = []int{len: width, init: 36}
} else {
app.dying = true
}
}
else {}
}
}
.resized {
retile(mut app)
}
else {}
}
}
fn frame(mut app App) {
app.draw()
app.tick()
}
+74
View File
@@ -0,0 +1,74 @@
module main
import gg
import sokol.sgl
struct App {
mut:
gg &gg.Context = unsafe { nil }
}
const cmax_x = 115
const cmax_y = 81
const cs = 10
const c_fg = gg.rgb(20, 30, 255)
fn main() {
mut app := &App{}
app.gg = gg.new_context(
bg_color: gg.white
width: cmax_x * cs
height: cmax_y * cs
create_window: true
window_title: 'Grid with many rectangles (drawn with no context)'
frame_fn: frame
user_data: app
)
app.gg.run()
}
fn frame(app &App) {
app.gg.begin()
// Note: this uses 2 separate loops, with each doing its own drawing for performance reasons.
// The underlying Sokol library can eliminate sgl begin/end calls, when multiple primitives of the
// same kind are drawn one after the other, without context changes.
// However, filled rectangles are drawn with quads, while empty rectangles are drawn with lines.
// In this case, if the draw calls are put in the same loop, using app.gg.draw_rect_filled/5 and app.gg.draw_rect_empty/5,
// sokol can not batch the begin/end calls, and their overhead becomes much bigger.
//
// That is why:
// a) we use several loops here.
// b) we use a single sgl.begin_quads() before the first loop, and a single sgl.end() after it.
// c) we use draw_rect_filled_no_context/5 inside the loop, instead of draw_rect_filled/5 .
// e) we use a single sgl.begin_lines() before the second loop, and a single sgl.end() after it.
// f) we use draw_rect_empty_no_context/5 inside the second loop, instead of draw_rect_empty/5 .
// Note: the separation of the loops/kinds of draws, is several times more important for eliminating
// the performance overhead (12-15% CPU usage), compared to the use of draw_rect_filled_no_context
// vs draw_rect_filled (1-2% CPU usage), because of Sokol's optimisation.
$if !do_not_draw_rect_filled ? {
sgl.begin_quads()
for y in 0 .. cmax_y {
for x in 0 .. cmax_x {
cy, cx := y * cs, x * cs
app.gg.draw_rect_filled_no_context(cx, cy, cs - 2, cs - 2, c_fg)
}
}
sgl.end()
}
$if draw_rect_empty ? {
sgl.begin_lines()
for y in 0 .. cmax_y {
for x in 0 .. cmax_x {
cy, cx := y * cs, x * cs
app.gg.draw_rect_empty_no_context(cx + 2, cy + 2, cs - 3, cs - 3,
gg.rgb(255, 50, 0))
}
}
sgl.end()
}
app.gg.end()
}
+241
View File
@@ -0,0 +1,241 @@
import gg
import runtime
import time
const pwidth = 800
const pheight = 600
const chunk_height = 2 // the image is recalculated in chunks, each chunk processed in a separate thread
const zoom_factor = 1.1
const max_iterations = 255
struct ViewRect {
mut:
x_min f64
x_max f64
y_min f64
y_max f64
}
fn (v &ViewRect) width() f64 {
return v.x_max - v.x_min
}
fn (v &ViewRect) height() f64 {
return v.y_max - v.y_min
}
struct AppState {
mut:
gg &gg.Context = unsafe { nil }
iidx int
pixels &u32 = unsafe { vcalloc(pwidth * pheight * sizeof(u32)) }
npixels &u32 = unsafe { vcalloc(pwidth * pheight * sizeof(u32)) } // all drawing happens here, results are swapped at the end
view ViewRect = ViewRect{-3.0773593290970673, 1.4952456603855397, -2.019938598189011, 2.3106642054225945}
scale int = 1
ntasks int = runtime.nr_jobs()
}
const colors = [gg.black, gg.blue, gg.red, gg.green, gg.yellow, gg.orange, gg.purple, gg.white,
gg.indigo, gg.violet, gg.black, gg.blue, gg.orange, gg.yellow, gg.green].map(u32(it.abgr8()))
struct MandelChunk {
cview ViewRect
ymin f64
ymax f64
}
fn (mut state AppState) update() {
mut chunk_channel := chan MandelChunk{cap: state.ntasks}
mut chunk_ready_channel := chan bool{cap: 1000}
mut threads := []thread{cap: state.ntasks}
defer {
chunk_channel.close()
threads.wait()
}
for t in 0 .. state.ntasks {
threads << spawn state.worker(t, chunk_channel, chunk_ready_channel)
}
mut oview := ViewRect{}
mut sw := time.new_stopwatch()
for {
sw.restart()
cview := state.view
if oview == cview {
time.sleep(5 * time.millisecond)
continue
}
// schedule chunks, describing the work:
mut nchunks := 0
for start := 0; start < pheight; start += chunk_height {
chunk_channel <- MandelChunk{
cview: cview
ymin: start
ymax: start + chunk_height
}
nchunks++
}
// wait for all chunks to be processed:
for _ in 0 .. nchunks {
_ := <-chunk_ready_channel
}
// everything is done, swap the buffer pointers
state.pixels, state.npixels = state.npixels, state.pixels
println('${state.ntasks:2} threads; ${sw.elapsed().milliseconds():3} ms / frame; scale: ${state.scale:4}')
oview = cview
}
}
@[direct_array_access]
fn (mut state AppState) worker(_id int, input chan MandelChunk, ready chan bool) {
for {
chunk := <-input or { break }
yscale := chunk.cview.height() / pheight
xscale := chunk.cview.width() / pwidth
mut x, mut y, mut iter := 0.0, 0.0, 0
mut y0 := chunk.ymin * yscale + chunk.cview.y_min
mut x0 := chunk.cview.x_min
for y_pixel := chunk.ymin; y_pixel < chunk.ymax && y_pixel < pheight; y_pixel++ {
yrow := unsafe { &state.npixels[int(y_pixel * pwidth)] }
y0 += yscale
x0 = chunk.cview.x_min
for x_pixel := 0; x_pixel < pwidth; x_pixel++ {
x0 += xscale
x, y = x0, y0
for iter = 0; iter < max_iterations; iter++ {
x, y = x * x - y * y + x0, 2 * x * y + y0
if x * x + y * y > 4 {
break
}
}
unsafe {
yrow[x_pixel] = colors[iter & 15]
}
}
}
ready <- true
}
}
fn (mut state AppState) draw() {
mut istream_image := state.gg.get_cached_image_by_idx(state.iidx)
istream_image.update_pixel_data(unsafe { &u8(state.pixels) })
size := gg.window_size()
state.gg.draw_image(0, 0, size.width, size.height, istream_image)
}
fn (mut state AppState) zoom(zoom_factor f64) {
c_x, c_y := (state.view.x_max + state.view.x_min) / 2, (state.view.y_max + state.view.y_min) / 2
d_x, d_y := c_x - state.view.x_min, c_y - state.view.y_min
state.view.x_min = c_x - zoom_factor * d_x
state.view.x_max = c_x + zoom_factor * d_x
state.view.y_min = c_y - zoom_factor * d_y
state.view.y_max = c_y + zoom_factor * d_y
state.scale += if zoom_factor < 1 { 1 } else { -1 }
}
fn (mut state AppState) center(s_x f64, s_y f64) {
c_x, c_y := (state.view.x_max + state.view.x_min) / 2, (state.view.y_max + state.view.y_min) / 2
d_x, d_y := c_x - state.view.x_min, c_y - state.view.y_min
state.view.x_min = s_x - d_x
state.view.x_max = s_x + d_x
state.view.y_min = s_y - d_y
state.view.y_max = s_y + d_y
}
// gg callbacks:
fn graphics_init(mut state AppState) {
state.iidx = state.gg.new_streaming_image(pwidth, pheight, 4, pixel_format: .rgba8)
}
fn graphics_frame(mut state AppState) {
state.gg.begin()
state.draw()
state.gg.end()
}
fn graphics_click(x f32, y f32, btn gg.MouseButton, mut state AppState) {
if btn == .right {
size := gg.window_size()
m_x := (x / size.width) * state.view.width() + state.view.x_min
m_y := (y / size.height) * state.view.height() + state.view.y_min
state.center(m_x, m_y)
}
}
fn graphics_move(_x f32, _y f32, mut state AppState) {
if state.gg.mouse_buttons.has(.left) {
size := gg.window_size()
d_x := (f64(state.gg.mouse_dx) / size.width) * state.view.width()
d_y := (f64(state.gg.mouse_dy) / size.height) * state.view.height()
state.view.x_min -= d_x
state.view.x_max -= d_x
state.view.y_min -= d_y
state.view.y_max -= d_y
}
}
fn graphics_scroll(e &gg.Event, mut state AppState) {
state.zoom(if e.scroll_y < 0 { zoom_factor } else { 1 / zoom_factor })
}
fn graphics_keydown(code gg.KeyCode, _mod gg.Modifier, mut state AppState) {
s_x := state.view.width() / 5
s_y := state.view.height() / 5
// movement
mut d_x, mut d_y := 0.0, 0.0
if code == .enter {
println('> ViewRect{${state.view.x_min}, ${state.view.x_max}, ${state.view.y_min}, ${state.view.y_max}}')
}
if state.gg.pressed_keys[int(gg.KeyCode.left)] {
d_x -= s_x
}
if state.gg.pressed_keys[int(gg.KeyCode.right)] {
d_x += s_x
}
if state.gg.pressed_keys[int(gg.KeyCode.up)] {
d_y -= s_y
}
if state.gg.pressed_keys[int(gg.KeyCode.down)] {
d_y += s_y
}
state.view.x_min += d_x
state.view.x_max += d_x
state.view.y_min += d_y
state.view.y_max += d_y
// zoom in/out
if state.gg.pressed_keys[int(gg.KeyCode.left_bracket)]
|| state.gg.pressed_keys[int(gg.KeyCode.z)] {
state.zoom(1 / zoom_factor)
return
}
if state.gg.pressed_keys[int(gg.KeyCode.right_bracket)]
|| state.gg.pressed_keys[int(gg.KeyCode.x)] {
state.zoom(zoom_factor)
return
}
}
fn main() {
mut state := &AppState{}
state.gg = gg.new_context(
width: 800
height: 600
create_window: true
window_title: 'The Mandelbrot Set'
init_fn: graphics_init
frame_fn: graphics_frame
click_fn: graphics_click
move_fn: graphics_move
keydown_fn: graphics_keydown
scroll_fn: graphics_scroll
user_data: state
)
spawn state.update()
state.gg.run()
}
@@ -0,0 +1,55 @@
module main
import gg
import rand
const max_circles_per_pass = 1000
// prepare some random colors ahead of time
const colors = []gg.Color{len: max_circles_per_pass, init: gg.Color{
r: u8(index * 0 + rand.u8())
g: u8(index * 0 + rand.u8())
b: u8(index * 0 + rand.u8())
}}
fn frame(mut ctx gg.Context) {
// First pass, just clears the background:
ctx.begin()
ctx.end()
// We want to draw thousands of circles, but sokol has a limit for how
// many primitives can be in a single pass, and if you reach that limit
// you will not see *anything at all* for that pass.
// For the circles below, that limit is ~2520 circles per pass.
// To overcome this, we will use several passes instead, where each one
// will draw just 1000 circles.
// In other words, in total we will have 4 * 1000 = 4000 circles, drawn with
// 4 passes.
for i := 0; i < 4 * max_circles_per_pass; i += max_circles_per_pass {
ctx.begin()
for c in 0 .. max_circles_per_pass {
rx := rand.int_in_range(0, ctx.window.width) or { 0 }
ry := rand.int_in_range(0, ctx.window.height) or { 0 }
ctx.draw_circle_filled(rx, ry, 10, colors[c])
}
ctx.end(how: .passthru)
}
// The last pass, is for the fps overlay, that should be *always on top of everything*.
// Drawing it in a separate pass, guarantees, that it *will* be drawn, even if the drawing
// of all the other passes fail. Try increasing max_circles_per_pass to 3000 for example.
ctx.begin()
ctx.show_fps()
ctx.end(how: .passthru)
}
fn main() {
mut ctx := gg.new_context(
window_title: 'Many Thousands of Circles'
bg_color: gg.black
width: 600
height: 400
frame_fn: frame
)
ctx.run()
}
@@ -0,0 +1,36 @@
import gg
import rand
// The flags here override the default limits for Sokol
#flag -D_SGL_DEFAULT_MAX_VERTICES=4194304
#flag -D_SGL_DEFAULT_MAX_COMMANDS=65536
// Without the flags, `max_circles` > 5040, will just show a blue screen without
// *any circles* drawn.
// **Note** however, that increasing `_SGL_DEFAULT_MAX_VERTICES`, also increases
// the default RAM usage of your app. In this case, instead of using ~40MB on
// Ubuntu 20.04, the app instead uses ~140MB.
const max_circles = 10_000
fn main() {
gg.start(
window_title: 'Hello'
bg_color: gg.Color{50, 50, 150, 255}
width: 800
height: 600
frame_fn: fn (ctx &gg.Context) {
wsize := gg.window_size()
ctx.begin()
for _ in 0 .. max_circles {
rx := rand.int_in_range(0, wsize.width) or { 0 }
ry := rand.int_in_range(0, wsize.height) or { 0 }
cr := rand.u8()
cg := rand.u8()
cb := rand.u8()
ctx.draw_circle_filled(rx, ry, 10, gg.Color{cr, cg, cb, 255})
}
ctx.show_fps()
ctx.end()
}
)
}
+135
View File
@@ -0,0 +1,135 @@
module main
import gg
import rand
import time
const cover = gg.rgba(85, 200, 85, 255)
const csize = 120 // cell size in pixels
const letters = 'AABBOOCCVVXXYYZZMMKKHHTT'.split('')
const header_size = 30
struct Cell {
mut:
is_open bool
letter string
}
struct Game {
mut:
ctx &gg.Context = unsafe { nil }
cells []Cell
card1_idx ?int
card2_idx ?int
size int // in cells
remaining int
sw time.StopWatch = time.new_stopwatch()
revert_sw time.StopWatch = time.new_stopwatch(auto_start: false)
}
fn (mut g Game) restart() {
ncells := g.size * g.size
g.remaining = ncells
g.cells = []Cell{len: ncells, init: Cell{
letter: letters[index % letters.len]
}}
rand.shuffle(mut g.cells) or {}
g.sw = time.new_stopwatch()
g.card1_idx = none
g.card2_idx = none
}
fn (mut g Game) draw_cell(i int) {
x, y := i % g.size, i / g.size
rect_x, rect_y := x * csize, header_size + y * csize
if g.cells[i].is_open || g.sw.elapsed().milliseconds() <= 1000 {
lsize := 96
g.ctx.draw_rect_empty(rect_x + 6, rect_y + 6, csize - 10, csize - 10, gg.light_gray)
g.ctx.draw_text(rect_x + csize / 2 - lsize / 3, rect_y + csize / 2 - lsize / 2,
g.cells[i].letter, color: gg.yellow, size: lsize)
} else {
g.ctx.draw_rect_filled(rect_x + 6, rect_y + 6, csize - 10, csize - 10, cover)
}
}
fn on_frame(mut g Game) {
ws := gg.window_size()
g.ctx.begin()
message := '(r)estart (esc)ape | remaining: ${g.remaining:02} | time: ${f64(g.sw.elapsed().milliseconds()) / 1000.0:06.1f}s'
g.ctx.draw_text(ws.width / 2, 7, message, color: gg.light_gray, size: 22, align: .center)
for i in 0 .. g.cells.len {
g.draw_cell(i)
}
if g.revert_sw.elapsed().milliseconds() > 750 {
g.revert_sw = time.new_stopwatch(auto_start: false)
if g.card1_idx != none {
if g.card2_idx != none {
g.cells[g.card1_idx].is_open = false
g.cells[g.card2_idx].is_open = false
g.card1_idx = none
g.card2_idx = none
g.remaining = g.cells.count(!it.is_open)
}
}
}
g.ctx.end()
}
fn on_event(e &gg.Event, mut g Game) {
if e.typ == .key_down {
match e.key_code {
.escape { g.ctx.quit() }
.r { g.restart() }
else {}
}
return
}
if e.typ != .mouse_down {
return
}
x, y := int(e.mouse_x / csize), int((e.mouse_y - header_size) / csize)
if e.mouse_button == .left && g.card2_idx == none {
if g.remaining == 0 {
g.restart()
return
}
i := y * g.size + x
if !g.cells[i].is_open {
g.cells[i].is_open = true
if g.card1_idx == none {
g.card1_idx = i
} else {
g.card2_idx = i
if g.cells[g.card1_idx].letter == g.cells[i].letter {
g.card1_idx = none
g.card2_idx = none
} else {
// start a timer, that will be checked in the on_frame callback
g.revert_sw.start()
}
}
}
}
g.remaining = g.cells.count(!it.is_open)
if g.remaining == 0 {
g.sw.stop()
}
}
mut g := &Game{
// the CLI argument should be number of pairs, so `size` is even, and the puzzle can be solved:
size: arguments()[1] or { '3' }.int() * 2
}
g.restart()
g.ctx = gg.new_context(
bg_color: gg.black
width: g.size * csize
height: header_size + g.size * csize
window_title: 'V Memory ${g.size} x ${g.size}'
user_data: g
frame_fn: on_frame
event_fn: on_event
sample_count: 2
)
g.ctx.run()
+220
View File
@@ -0,0 +1,220 @@
module main
import gg
import rand
import os.asset
const header_size = 30
enum Cell {
empty
n1
n2
n3
n4
n5
n6
n7
n8
mine
}
@[heap]
struct Game {
mut:
ctx &gg.Context = unsafe { nil }
grid [][]Cell
flags [][]bool
revealed [][]bool
size int = 10 // in cells
csize int = 30 // in pixels
game_over bool
first_click bool = true
mines int = 10
mines_flagged int
}
@[inline]
fn (mut g Game) in_grid(cy int, cx int) bool {
return cx >= 0 && cx < g.size && cy >= 0 && cy < g.size
}
fn (mut g Game) generate_mines(first_y int, first_x int) {
mut mines_placed := 0
for mines_placed < g.mines {
x, y := rand.intn(g.size) or { 0 }, rand.intn(g.size) or { 0 }
// avoid placing mines at the position of the first click
if (x == first_x && y == first_y) || g.grid[y][x] == .mine {
continue
}
g.grid[y][x] = .mine
mines_placed++
}
for y in 0 .. g.size {
for x in 0 .. g.size {
if g.grid[y][x] == .mine {
continue
}
mut count := 0
for dy in -1 .. 2 {
for dx in -1 .. 2 {
cy, cx := y + dy, x + dx
if g.in_grid(cy, cx) {
if g.grid[cy][cx] == .mine {
count++
}
}
}
}
g.grid[y][x] = unsafe { Cell(count) }
}
}
}
fn (mut g Game) reveal(y int, x int) {
if !g.in_grid(y, x) {
return
}
if g.revealed[y][x] {
return
}
g.revealed[y][x] = true
if g.grid[y][x] == .mine {
g.game_over = true
return
}
if g.grid[y][x] == .empty {
for dy in -1 .. 2 {
for dx in -1 .. 2 {
g.reveal(y + dy, x + dx)
}
}
}
}
fn (mut g Game) restart() {
g.grid = [][]Cell{len: g.size, init: []Cell{len: g.size}}
g.flags = [][]bool{len: g.size, init: []bool{len: g.size}}
g.revealed = [][]bool{len: g.size, init: []bool{len: g.size}}
g.game_over = false
g.first_click = true
g.mines_flagged = 0
}
fn on_event(e &gg.Event, mut g Game) {
if e.typ == .key_down {
match e.key_code {
.escape { g.ctx.quit() }
.r { g.restart() }
else {}
}
return
}
if g.game_over {
return
}
if e.typ != .mouse_down {
return
}
x := int(e.mouse_x / g.csize)
y := int((e.mouse_y - header_size) / g.csize)
if e.mouse_button == .left {
if g.first_click {
g.generate_mines(y, x)
g.first_click = false
}
g.reveal(y, x)
}
if e.mouse_button == .right {
if !g.revealed[y][x] {
old := g.flags[y][x]
g.flags[y][x] = !old
g.mines_flagged += if old { -1 } else { 1 }
}
}
if e.mouse_button == .middle {
if g.revealed[y][x] {
count := g.act_on_neighbors(y, x, fn (mut g Game, cy int, cx int) int {
if g.in_grid(cy, cx) {
return int(g.flags[cy][cx])
}
return 0
})
if int(g.grid[y][x]) == count {
g.act_on_neighbors(y, x, fn (mut g Game, cy int, cx int) int {
if g.in_grid(cy, cx) && !g.flags[cy][cx] {
g.reveal(cy, cx)
}
return 0
})
}
}
}
}
fn (mut g Game) act_on_neighbors(y int, x int, f fn (mut g Game, cy int, cx int) int) int {
mut count := 0
for dy in -1 .. 2 {
for dx in -1 .. 2 {
if dy == 0 && dx == 0 {
continue
}
cy, cx := y + dy, x + dx
count += f(mut g, cy, cx)
}
}
return count
}
fn (mut g Game) draw_cell(y int, x int) {
o := header_size
rect_x, rect_y := x * g.csize, y * g.csize
if g.revealed[y][x] {
if g.grid[y][x] == .mine {
g.ctx.draw_rect_filled(rect_x, o + rect_y, g.csize, g.csize, gg.red)
g.ctx.draw_text(rect_x + 10, o + rect_y + 5, '*', color: gg.black)
} else if int(g.grid[y][x]) > 0 {
g.ctx.draw_rect_filled(rect_x, o + rect_y, g.csize, g.csize, gg.light_gray)
n := int(g.grid[y][x]).str()
g.ctx.draw_text(rect_x + 10, o + rect_y + 5, n, color: gg.black)
} else {
c := gg.rgb(240, 240, 240)
g.ctx.draw_rect_filled(rect_x, o + rect_y, g.csize, g.csize, c)
}
} else if g.flags[y][x] {
g.ctx.draw_rect_filled(rect_x, o + rect_y, g.csize, g.csize, gg.gray)
g.ctx.draw_text(rect_x + 10, o + rect_y + 5, 'F', color: gg.white)
} else {
g.ctx.draw_rect_filled(rect_x, o + rect_y, g.csize, g.csize, gg.gray)
}
g.ctx.draw_rect_empty(rect_x, o + rect_y, g.csize, g.csize, gg.black)
}
fn on_frame(mut g Game) {
g.ctx.begin()
for y in 0 .. g.size {
for x in 0 .. g.size {
g.draw_cell(y, x)
}
}
message := 'Flagged: ${g.mines_flagged:02}/${g.mines:02} (r)estart (ESC)ape'
g.ctx.draw_text(5, 7, message, color: gg.green)
g.ctx.end()
}
fn main() {
mut g := &Game{}
g.restart()
g.ctx = gg.new_context(
bg_color: gg.black
width: g.size * g.csize
height: header_size + g.size * g.csize
window_title: 'V Minesweeper'
user_data: g
frame_fn: on_frame
event_fn: on_event
font_path: asset.get_path('../assets', 'fonts/RobotoMono-Regular.ttf')
)
g.ctx.run()
}
+17
View File
@@ -0,0 +1,17 @@
import gg
gg.start(
window_title: 'Hello'
bg_color: gg.Color{240, 240, 128, 255}
width: 320
height: 240
frame_fn: fn (ctx &gg.Context) {
ctx.begin()
ctx.draw_text(40, 100, 'GG frame: ${ctx.frame:06}',
size: 30
color: gg.Color{50, 50, 255, 255}
)
ctx.show_fps()
ctx.end()
}
)
+34
View File
@@ -0,0 +1,34 @@
import gg
struct App {
mut:
x f64 = 100.0
y f64 = 100.0
}
mut app := &App{}
gg.start(
window_title: 'Moving Square'
width: 640
height: 480
update_fn: fn [mut app] (dt f32, ctx &gg.Context) {
println(' frame: ${ctx.frame:6} | dt: ${dt:9.6f}s')
if ctx.pressed_keys[gg.KeyCode.right] {
app.x = app.x + 200 * dt
}
if ctx.pressed_keys[gg.KeyCode.left] {
app.x = app.x - 200 * dt
}
if ctx.pressed_keys[gg.KeyCode.down] {
app.y = app.y + 200 * dt
}
if ctx.pressed_keys[gg.KeyCode.up] {
app.y = app.y - 200 * dt
}
}
frame_fn: fn [mut app] (ctx &gg.Context) {
ctx.begin()
ctx.draw_rect_filled(int(app.x), int(app.y), 50, 50, gg.red)
ctx.end()
}
)
@@ -0,0 +1,21 @@
MIT License
Copyright (c) [2023] [Yuyi Hao]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,33 @@
# A* path finding algorithm visualizer
This is a simple visualizer for an A* path finding algorithm written in V.
# Quick start
```console
# to run:
$ v run aStar.v
# to just compile:
$ v aStar.v
# then to run:
$ ./aStar
```
# Controls
- `q` : To quit the application
- `c`: clear the grid and start from new one
- `space`: initialize path finding algorithm
# Demo
![Demo image of the algorithm](screenshot.png)
# 🔴🔴🔴🔴 Area of improvements 🔴🔴🔴🔴🔴
- 🚧 Under Construction: We are using heap, but that is not correctly
implemented, since instead of O(log(n)) it takes O(n). For that reason,
having a bigger grid size, will break the application.
- 🌱 Growth Opportunity: make it responsive.
@@ -0,0 +1,455 @@
module main
import gg // actual graphics lib
import math // for math related function
const window_width = 800
const window_height = 800
const nrows = 50
// app struct that has property of current windows
struct App {
mut:
gg &gg.Context = unsafe { nil }
ui Ui
grid [][]Cell
start Point // start point of algorithm
end Point // end point or target point
}
// this needed to get the width and mouse position part of gg window
struct Ui {
mut:
dpi_scale f32
}
// struct for a point
struct Point {
mut:
x int
y int
}
/*
RED -> Closed
GREEN -> Open
BLACK -> Barrier
WHITE -> Empty
ORANGE -> Start
TURQOIISE -> End
PINK -> Path
*/
// struct for a cell of grid
struct Cell {
mut:
row int
col int
width int
pos Point
color gg.Color
flag int // 0->empty, 1-> closed, 2-> open, 3-> barrier, 4-> start, 5-> end, 6-> path
neighbors []Point
}
// this is a node for priority queue
struct Node {
mut:
f_score int
cell Point
count int
}
// Min heap or priority queue
struct MinHeap {
mut:
data []Node
}
// main function
fn main() {
// app variable
mut app := &App{}
// setting values of app
app.gg = gg.new_context(
bg_color: gg.black // background color
width: window_width // window width
height: window_height // window height
create_window: true // this will create a different window
window_title: 'A* Path finding algorithm visusalizer' // title of the window
frame_fn: frame // this is frame function update the frame
event_fn: on_event // it calls on every event
user_data: app // store user data
)
mut grid :=
initialise_grid() // initialize the grid variable and populate the matrix with each cell as empty
app.grid = grid // set grid to app attribute so you can access it by just passing app variable or with method of app
app.ui.dpi_scale = 1.0 // set scale this is use to make it responsive
app.start = &Point{ // set start point to -1, -1
x: -1
y: -1
}
app.end = &Point{ // set end point to -1, -1
x: -1
y: -1
}
app.gg.run() // run the app loop
}
// this function will run for every frame actually in a loop
fn frame(mut app App) {
app.gg.begin()
draw_grid(mut app)
draw_gridlines(mut app)
app.gg.end()
}
// this will handle user event which is stored in gg.event variable
fn on_event(event &gg.Event, mut app App) {
match event.typ {
.mouse_down {
x := int(event.mouse_x / app.ui.dpi_scale)
y := int(event.mouse_y / app.ui.dpi_scale)
btn := event.mouse_button
app.handle_mouse_event(x, y, btn)
}
.key_down {
app.on_key_down(event.key_code)
}
else {}
}
}
// handle mouse event to make a cell either start point end point or barrier or to clear
fn (mut app App) handle_mouse_event(x int, y int, btn_type gg.MouseButton) {
gap := window_width / nrows
row := int(y / gap)
col := int(x / gap)
match btn_type {
.left {
if app.start.x == -1 && !(row == app.end.y && col == app.end.x) {
app.start.x = col
app.start.y = row
set_cell_type(mut app.grid, app.start.y, app.start.x, 'start')
} else if app.end.x == -1 && !(row == app.start.y && col == app.start.x) {
app.end.x = col
app.end.y = row
set_cell_type(mut app.grid, app.end.y, app.end.x, 'end')
} else if !(row == app.start.y && col == app.start.x) && !(row == app.end.y
&& col == app.end.x) {
set_cell_type(mut app.grid, row, col, 'barrier')
}
}
.right {
if row == app.start.y && col == app.start.x {
app.start.x = -1
app.start.y = -1
}
if row == app.end.y && col == app.end.x {
app.end.x = -1
app.end.y = -1
}
set_cell_type(mut app.grid, row, col, 'reset')
}
else {}
}
}
// handle keyboard interaction by user ''
fn (mut app App) on_key_down(key gg.KeyCode) {
match key {
.space {
if app.start.x == -1 || app.end.x == -1 {
println('Error: either start or end node is missing')
} else {
for row := 0; row < nrows; row++ {
for j := 0; j < nrows; j++ {
update_neighbors(mut app.grid, row, j)
}
}
new_start := &Point{
x: app.start.y
y: app.start.x
}
new_end := &Point{
x: app.end.y
y: app.end.x
}
astar_path_finding(mut app, mut app.grid, new_start, new_end)
}
}
.q {
app.gg.quit()
}
.c {
draw_grid(mut app)
draw_gridlines(mut app)
mut grid := initialise_grid()
app.grid = grid // set grid to app attribute so you can access it by just passing app variable or with method of app
app.ui.dpi_scale = 1.0 // set scale this is use to make it responsive
app.start = &Point{ // set start point to -1, -1
x: -1
y: -1
}
app.end = &Point{ // set end point to -1, -1
x: -1
y: -1
}
}
else {}
}
}
// draw grid lines
fn draw_gridlines(mut app App) {
dx := window_width / nrows
dy := window_height / nrows
for i := 0; i < nrows; i++ {
// horizontal lines
app.gg.draw_line(0, i * dy, window_width, i * dy, gg.black)
// vertical lines
app.gg.draw_line(i * dx, 0, dx * i, window_height, gg.black)
}
}
// heuristic function(point manhatten distance) that calculate approximate cost to reach from a given point to end(target)
fn hf(p1 Point, p2 Point) int {
return math.abs(p1.x - p2.x) + math.abs(p1.y - p2.y)
}
// initialize grid attribute of app
fn initialise_grid() [][]Cell {
mut grid := [][]Cell{len: nrows, init: []Cell{len: nrows}}
gap := window_width / nrows
for i := 0; i < nrows; i++ {
for j := 0; j < nrows; j++ {
grid[i][j] = &Cell{
row: i
col: j
width: gap
pos: &Point{
x: j * gap
y: i * gap
}
color: gg.white
flag: 0
}
}
}
return grid
}
// draw the cells of grid
fn draw_grid(mut app App) {
for i := 0; i < nrows; i++ {
for j := 0; j < nrows; j++ {
pos := app.grid[i][j].pos
width := app.grid[i][j].width
color := app.grid[i][j].color
app.gg.draw_rect_filled(pos.x, pos.y, width, width, color)
}
}
}
// update the neighbor of each cell in which cell you can visit (if it is not barrier or end or start)
fn update_neighbors(mut grid [][]Cell, row int, col int) {
if row < nrows - 1 && grid[row + 1][col].flag != 3 {
grid[row][col].neighbors << &Point{
x: row + 1
y: col
}
}
if row > 0 && grid[row - 1][col].flag != 3 {
grid[row][col].neighbors << &Point{
x: row - 1
y: col
}
}
if col < nrows - 1 && grid[row][col + 1].flag != 3 {
grid[row][col].neighbors << &Point{
x: row
y: col + 1
}
}
if col > 0 && grid[row][col - 1].flag != 3 {
grid[row][col].neighbors << &Point{
x: row
y: col - 1
}
}
}
// construct the path after finding it shows as pink color
fn reconstruct_path(mut grid [][]Cell, mut came_from [][]Point, _start Point, end Point) {
mut x := end.x
mut y := end.y
for !(x == -1 && y == -1) {
set_cell_type(mut grid, x, y, 'path')
x = came_from[x][y].x
y = came_from[x][y].y
}
}
// a* path finding algorithm
fn astar_path_finding(mut _app App, mut grid [][]Cell, start Point, end Point) {
mut priority_queue := &MinHeap{}
mut g_score := [][]int{len: nrows, init: []int{len: nrows}}
mut f_score := [][]int{len: nrows, init: []int{len: nrows}}
mut came_from := [][]Point{len: nrows, init: []Point{len: nrows}}
for i := 0; i < nrows; i++ {
for j := 0; j < nrows; j++ {
g_score[i][j] = 1_000_000_000_00
f_score[i][j] = 1_000_000_000_00
came_from[i][j] = &Point{
x: -1
y: -1
}
}
}
g_score[start.x][start.y] = 0
f_score[start.x][start.y] = g_score[start.x][start.y] + hf(start, end)
priority_queue.insert(Node{
f_score: f_score[start.x][start.y]
cell: &Point{
x: start.x
y: start.y
}
count: 0
})
for priority_queue.len() > 0 {
curr_node := priority_queue.pop() or {
panic('There is nothing in queue how did it reach here idk')
}
curr_pos := curr_node.cell
set_cell_type(mut grid, curr_pos.x, curr_pos.y, 'close')
if curr_pos.x == end.x && curr_pos.y == end.y {
set_cell_type(mut grid, start.x, start.y, 'start')
set_cell_type(mut grid, end.x, end.y, 'end')
came_from[end.x][end.y] = came_from[curr_pos.x][curr_pos.y]
reconstruct_path(mut grid, mut came_from, start, end)
set_cell_type(mut grid, start.x, start.y, 'start')
set_cell_type(mut grid, end.x, end.y, 'end')
return
}
for neighbor in grid[curr_pos.x][curr_pos.y].neighbors {
mut temp_g_score := g_score[curr_pos.x][curr_pos.y] + 1
if temp_g_score < g_score[neighbor.x][neighbor.y] {
g_score[neighbor.x][neighbor.y] = temp_g_score
if !(neighbor.x == start.x && neighbor.y == start.y) {
priority_queue.insert(Node{
f_score: g_score[neighbor.x][neighbor.y] + hf(neighbor, end)
cell: neighbor
count: curr_node.count + 1
})
came_from[neighbor.x][neighbor.y] = curr_pos
set_cell_type(mut grid, neighbor.x, neighbor.y, 'open')
}
}
}
}
set_cell_type(mut grid, start.x, start.y, 'start')
}
// change the property of a cell
fn set_cell_type(mut grid [][]Cell, row int, col int, typ string) {
match typ {
'reset' {
grid[row][col].color = gg.white
grid[row][col].flag = 0
}
'close' {
grid[row][col].color = gg.red
grid[row][col].flag = 1
}
'open' {
grid[row][col].color = gg.green
grid[row][col].flag = 2
}
'barrier' {
grid[row][col].color = gg.black
grid[row][col].flag = 3
}
'start' {
grid[row][col].color = gg.orange
grid[row][col].flag = 4
}
'end' {
grid[row][col].color = gg.blue
grid[row][col].flag = 5
}
'path' {
grid[row][col].color = gg.pink
grid[row][col].flag = 6
}
else {}
}
}
// ------------------------------ HEAP -----------------------------
fn (mut heap MinHeap) insert(item Node) {
heap.data << item
}
// get the minimum out of all node
fn (mut heap MinHeap) pop() !Node {
if heap.len() == 0 {
return error('empty heap')
}
mut i := 0
mut curr := heap.data[0].f_score
len := heap.len()
for idx := 0; idx < len; idx++ {
if curr > heap.data[idx].f_score {
i = idx
curr = heap.data[idx].f_score
}
}
ele := heap.data[i]
heap.data.delete(i)
return ele
}
// see the top element of heap //TODO this won't give correct result as heap is not implemented correctly
fn (mut heap MinHeap) peek() !Node {
if heap.data.len == 0 {
return error('Heap is empty')
}
return heap.data[0]
}
// give length of heap total element present currently
fn (mut heap MinHeap) len() int {
return heap.data.len
}
// Index of left child of a node in heap //TODO heap not implemented
fn (mut heap MinHeap) left_child(idx int) !int {
child := 2 * idx + 1
if child >= heap.data.len {
return error('Out of Bound')
}
return child
}
// Index of right child of a node in heap //TODO heap not implemented
fn (mut heap MinHeap) right_child(idx int) !int {
child := 2 * idx + 2
if child >= heap.data.len {
return error('Out of bound')
}
return child
}
// Index of parent of a node in heap //TODO heap not implemented
fn (mut heap MinHeap) parent(idx int) int {
return (idx - 1) / 2
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

+23
View File
@@ -0,0 +1,23 @@
module main
import gg
fn main() {
mut context := gg.new_context(
bg_color: gg.rgb(174, 198, 255)
width: 600
height: 400
window_title: 'Polygons'
frame_fn: frame
)
context.run()
}
fn frame(mut ctx gg.Context) {
ctx.begin()
ctx.draw_convex_poly([f32(100.0), 100.0, 200.0, 100.0, 300.0, 200.0, 200.0, 300.0, 100.0, 300.0],
gg.blue)
ctx.draw_poly_empty([f32(50.0), 50.0, 70.0, 60.0, 90.0, 80.0, 70.0, 110.0], gg.black)
ctx.draw_triangle_filled(450, 142, 530, 280, 370, 280, gg.red)
ctx.end()
}
+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()
}
+63
View File
@@ -0,0 +1,63 @@
import gg
import time
const pwidth = 800
const pheight = 600
const pbytes = 4
struct AppState {
mut:
gg &gg.Context = unsafe { nil }
istream_idx int
pixels [pheight][pwidth]u32
}
@[direct_array_access]
fn (mut state AppState) update() {
mut rcolor := u64(state.gg.frame)
for {
for y in 0 .. pheight {
for x in 0 .. pwidth {
rcolor = rcolor * 1664525 + 1013904223
state.pixels[y][x] = u32(rcolor & 0x0000_0000_FFFF_FFFF) | 0x1010AFFF
}
}
time.sleep(33 * time.millisecond)
}
}
fn (mut state AppState) draw() {
mut istream_image := state.gg.get_cached_image_by_idx(state.istream_idx)
istream_image.update_pixel_data(unsafe { &u8(&state.pixels) })
size := gg.window_size()
state.gg.draw_image(0, 0, size.width, size.height, istream_image)
}
// gg callbacks:
fn graphics_init(mut state AppState) {
state.istream_idx = state.gg.new_streaming_image(pwidth, pheight, pbytes, pixel_format: .rgba8)
}
fn graphics_frame(mut state AppState) {
state.gg.begin()
state.draw()
state.gg.end()
}
fn main() {
mut state := &AppState{}
state.gg = gg.new_context(
width: 800
height: 600
create_window: true
window_title: 'Random Static'
init_fn: graphics_init
frame_fn: graphics_frame
user_data: state
)
spawn state.update()
state.gg.run()
}
+118
View File
@@ -0,0 +1,118 @@
import gg
import math
import time
import rand
const pwidth = 800
const pheight = 600
struct AppState {
mut:
gg &gg.Context = unsafe { nil }
istream_idx int
pixels [pheight][pwidth]gg.Color
}
@[direct_array_access]
fn (mut state AppState) update() {
for {
unsafe { vmemset(&state.pixels, 0, pwidth * pheight * sizeof[gg.Color]()) }
state.draw_sky() or {}
time.sleep(30_000 * time.millisecond)
}
}
fn (mut state AppState) draw_sky() ! {
for _ in 0 .. 2000 {
mut star_size := 1
for rand.i32_in_range(0, 100)! < 5 {
star_size += 10
}
for rand.i32_in_range(0, 100000)! < 5 {
star_size += 85
}
sx := if star_size < 10 {
rand.i32_in_range(-40, pwidth + 40)!
} else {
rand.i32_in_range(40, pwidth - 40)!
}
sy := if star_size < 10 {
rand.i32_in_range(-40, pheight + 40)!
} else {
rand.i32_in_range(40, pheight - 40)!
}
state.draw_star(sx, sy, gg.Color{
r: u8(rand.i32_in_range(50, 255)!)
g: u8(rand.i32_in_range(50, 255)!)
b: u8(rand.i32_in_range(50, 255)!)
}, star_size)
}
}
@[direct_array_access]
fn (mut state AppState) draw_star(x int, y int, c gg.Color, radius int) {
if radius == 0 {
return
}
minx := math.max(0, x - radius)
miny := math.max(0, y - radius)
maxx := math.min(pwidth, x + radius)
maxy := math.min(pheight, y + radius)
for cx in minx .. maxx {
for cy in miny .. maxy {
dx := math.abs[f32](cx - x) / f32(radius)
dy := math.abs[f32](cy - y) / f32(radius)
gradient := math.max[f32](0, math.min[f32](1, 1 - (math.sqrtf(dx) + math.sqrtf(dy))))
if gradient < 0.01 {
continue
}
mut pixel := unsafe { &state.pixels[cy][cx] }
color := gg.Color{
r: u8(math.min(255, int(pixel.r) + int(f32(c.r) * gradient)))
g: u8(math.min(255, int(pixel.g) + int(f32(c.g) * gradient)))
b: u8(math.min(255, int(pixel.b) + int(f32(c.b) * gradient)))
a: 255
}
unsafe {
*pixel = color
}
}
}
}
fn (mut state AppState) draw() {
mut istream_image := state.gg.get_cached_image_by_idx(state.istream_idx)
istream_image.update_pixel_data(unsafe { &u8(&state.pixels) })
size := gg.window_size()
sx := -50 + 50 * math.sinf(f32(state.gg.frame) / 100)
sy := -50 + 50 * math.cosf(f32(state.gg.frame) / 100)
scale := 200 + 50 * math.sinf(f32(state.gg.frame) / 300)
wx := size.width + scale
wy := size.height + scale
state.gg.draw_image(sx, sy, wx, wy, istream_image)
}
fn graphics_init(mut state AppState) {
state.istream_idx = state.gg.new_streaming_image(pwidth, pheight, 4, pixel_format: .rgba8)
}
fn graphics_frame(mut state AppState) {
state.gg.begin()
state.draw()
state.gg.end()
}
fn main() {
mut state := &AppState{}
state.gg = gg.new_context(
width: pwidth
height: pheight
window_title: 'Random stars'
init_fn: graphics_init
frame_fn: graphics_frame
user_data: state
)
spawn state.update()
state.gg.run()
}
@@ -0,0 +1,95 @@
module main
import gg
import os.asset
import math
const win_width = 600
const win_height = 700
const bg_color = gg.white
const text = '
Once upon a midnight dreary, while I pondered, weak and weary,
Over many a quaint and curious volume of forgotten lore—
While I nodded, nearly napping, suddenly there came a tapping,
As of some one gently rapping, rapping at my chamber door.
“’Tis some visitor,” I muttered, “tapping at my chamber door—
Only this and nothing more.”
Ah, distinctly I remember it was in the bleak December;
And each separate dying ember wrought its ghost upon the floor.
Eagerly I wished the morrow;—vainly I had sought to borrow
From my books surcease of sorrow—sorrow for the lost Lenore—
For the rare and radiant maiden whom the angels name Lenore—
Nameless here for evermore.
And the silken, sad, uncertain rustling of each purple curtain
Thrilled me—filled me with fantastic terrors never felt before;
So that now, to still the beating of my heart, I stood repeating
“’Tis some visitor entreating entrance at my chamber door—
Some late visitor entreating entrance at my chamber door;—
This it is and nothing more.”
Presently my soul grew stronger; hesitating then no longer,
“Sir,” said I, “or Madam, truly your forgiveness I implore;
But the fact is I was napping, and so gently you came rapping,
And so faintly you came tapping, tapping at my chamber door,
That I scarce was sure I heard you”—here I opened wide the door;—
Darkness there and nothing more.
Deep into that darkness peering, long I stood there wondering, fearing,
Doubting, dreaming dreams no mortal ever dared to dream before;
But the silence was unbroken, and the stillness gave no token,
And the only word there spoken was the whispered word, “Lenore?”
This I whispered, and an echo murmured back the word, “Lenore!”—
Merely this and nothing more.
Back into the chamber turning, all my soul within me burning,
Soon again I heard a tapping somewhat louder than before.
“Surely,” said I, “surely that is something at my window lattice;
Let me see, then, what thereat is, and this mystery explore—
Let my heart be still a moment and this mystery explore;—
’Tis the wind and nothing more!”
'
const lines = text.split('\n')
struct App {
mut:
gg &gg.Context = unsafe { nil }
}
fn main() {
mut app := &App{}
app.gg = gg.new_context(
width: win_width
height: win_height
create_window: true
window_title: 'Raven text'
user_data: app
bg_color: bg_color
frame_fn: frame
font_path: asset.get_path('../assets', 'fonts/RobotoMono-Regular.ttf')
// window_user_ptr: ctx
// native_rendering: true
)
app.gg.run()
}
fn frame(mut app App) {
app.gg.begin()
width := gg.window_size().width
mut scale_factor := math.round(f32(width) / win_width)
if scale_factor <= 0 {
scale_factor = 1
}
text_cfg := gg.TextCfg{
size: 16 * int(scale_factor)
}
mut y := 10
for line in lines {
app.gg.draw_text(10, y, line, text_cfg)
y += 30
}
app.gg.end()
}
+278
View File
@@ -0,0 +1,278 @@
// Demonstrates how raycasting works. The left side shows
// the 2D layout of the walls and player. The green lines
// represent the field of view of the player.
//
// The right side is a simple 3D projection of the field
// of view.
//
// There is no collision detection so yes, you can walk
// through walls.
//
// Watch https://www.youtube.com/watch?v=gYRrGTC7GtA to
// learn more on how this code works. There are some silly
// digressons in the video but the tech content is spot on.
import gg
import math
const player_size = 8
const map_x_size = 8
const map_y_size = 8
const map_square = 64
struct App {
mut:
ctx &gg.Context = unsafe { nil }
player_x f32
player_y f32
player_dx f32
player_dy f32
player_angle f32
map []int
}
fn main() {
mut app := App{
player_x: 230
player_y: 320
// each number represents an 8x8 square
// 1 is a wall cube, 0 is empty space
map: [
// vfmt off
1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 1,
1, 0, 1, 1, 0, 0, 0, 1,
1, 0, 1, 0, 0, 0, 0, 1,
1, 0, 0, 0, 0, 0, 0, 1,
1, 0, 0, 0, 0, 1, 0, 1,
1, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1,
// vfmt on
]
}
calc_deltas(mut app)
app.ctx = gg.new_context(
user_data: &app
window_title: 'Raycaster Demo'
width: 1024
height: 512
bg_color: gg.gray
frame_fn: draw
event_fn: handle_events
)
app.ctx.run()
}
fn draw(mut app App) {
app.ctx.begin()
draw_map_2d(app)
draw_player(app)
draw_rays_and_walls(app)
draw_instructions(app)
app.ctx.end()
}
fn draw_map_2d(app App) {
for y := 0; y < map_y_size; y++ {
for x := 0; x < map_x_size; x++ {
color := if app.map[y * map_x_size + x] == 1 { gg.white } else { gg.black }
app.ctx.draw_rect_filled(x * map_square, y * map_square, map_square - 1,
map_square - 1, color)
}
}
}
fn draw_player(app App) {
app.ctx.draw_rect_filled(app.player_x, app.player_y, player_size, player_size, gg.yellow)
cx := app.player_x + player_size / 2
cy := app.player_y + player_size / 2
app.ctx.draw_line(cx, cy, cx + app.player_dx * 5, cy + app.player_dy * 5, gg.yellow)
}
fn draw_rays_and_walls(app App) {
pi2 := math.pi / 2
pi3 := 3 * math.pi / 2
degree_radian := f32(0.0174533)
max_depth_of_field := 8
field_of_view := 60 // 60 degrees
mut distance := f32(0)
mut depth_of_field := 0
mut ray_x := f32(0)
mut ray_y := f32(0)
mut offset_x := f32(0)
mut offset_y := f32(0)
mut map_x := 0
mut map_y := 0
mut map_pos := 0
mut color := gg.red
mut ray_angle := clamp_ray_angle(app.player_angle - degree_radian * field_of_view / 2)
// each step = 1/2 degree
steps := field_of_view * 2
for step := 0; step < steps; step++ {
// check horizontal lines
mut hd := f32(max_int)
mut hx := app.player_x
mut hy := app.player_y
depth_of_field = 0
arc_tan := -1.0 / math.tanf(ray_angle)
if ray_angle > math.pi { // looking up
ray_y = f32(int(app.player_y) / map_square * map_square) - .0001
ray_x = (app.player_y - ray_y) * arc_tan + app.player_x
offset_y = -map_square
offset_x = -offset_y * arc_tan
} else if ray_angle < math.pi { // looking down
ray_y = f32(int(app.player_y) / map_square * map_square + map_square)
ray_x = (app.player_y - ray_y) * arc_tan + app.player_x
offset_y = map_square
offset_x = -offset_y * arc_tan
} else if ray_angle == 0 || ray_angle == 2 * math.pi { // looking straight left/right
ray_x = app.player_x
ray_y = app.player_y
depth_of_field = max_depth_of_field
}
for depth_of_field < max_depth_of_field {
map_x = int(ray_x) / map_square
map_y = int(ray_y) / map_square
map_pos = map_y * map_x_size + map_x
if app.map[map_pos] or { 0 } == 1 {
// hit a wall
hx = ray_x
hy = ray_y
hd = hypotenuse(app.player_x, app.player_y, hx, hy)
depth_of_field = max_depth_of_field
} else { // go to next line
ray_x += offset_x
ray_y += offset_y
depth_of_field += 1
}
}
// check vertical lines
mut vd := f32(max_int)
mut vx := app.player_x
mut vy := app.player_y
depth_of_field = 0
neg_tan := -math.tanf(ray_angle)
if ray_angle > pi2 && ray_angle < pi3 { // looking left
ray_x = f32(int(app.player_x) / map_square * map_square) - .0001
ray_y = (app.player_x - ray_x) * neg_tan + app.player_y
offset_x = -map_square
offset_y = -offset_x * neg_tan
} else if ray_angle < pi2 || ray_angle > pi3 { // looking right
ray_x = f32(int(app.player_x) / map_square * map_square + map_square)
ray_y = (app.player_x - ray_x) * neg_tan + app.player_y
offset_x = map_square
offset_y = -offset_x * neg_tan
} else if ray_angle == 0 || ray_angle == 2 * math.pi { // looking straight up/down
ray_x = app.player_x
ray_y = app.player_y
depth_of_field = max_depth_of_field
}
for depth_of_field < max_depth_of_field {
map_x = int(ray_x) / map_square
map_y = int(ray_y) / map_square
map_pos = map_y * map_x_size + map_x
if app.map[map_pos] or { 0 } == 1 {
// hit a wall
vx = ray_x
vy = ray_y
vd = hypotenuse(app.player_x, app.player_y, vx, vy)
depth_of_field = max_depth_of_field
} else { // go to next line
ray_x += offset_x
ray_y += offset_y
depth_of_field += 1
}
}
// use the shorter of the horizontal and vertical distances to draw rays
// use different colors for the two sides of the walls for lighting effect
if vd < hd {
ray_x = vx
ray_y = vy
distance = vd
color = gg.rgb(0, 100, 0)
} else if hd < vd {
ray_x = hx
ray_y = hy
distance = hd
color = gg.rgb(0, 120, 0)
}
// draw ray
cx := app.player_x + player_size / 2
cy := app.player_y + player_size / 2
app.ctx.draw_line(cx, cy, ray_x, ray_y, gg.green)
// draw wall section
mut ca := clamp_ray_angle(app.player_angle - ray_angle)
distance *= math.cosf(ca) // remove fish eye
offset_3d_view := 530
line_thickeness := 4
max_wall_height := 320
wall_height := math.min((map_square * max_wall_height) / distance, max_wall_height)
wall_offset := max_wall_height / 2 - wall_height / 2
app.ctx.draw_line_with_config(step * line_thickeness + offset_3d_view, wall_offset,
step * line_thickeness + offset_3d_view, wall_offset + wall_height, gg.PenConfig{
color: color
thickness: line_thickeness
})
// step to next ray angle
ray_angle = clamp_ray_angle(ray_angle + degree_radian / 2)
}
}
fn handle_events(event &gg.Event, mut app App) {
if event.typ == .key_down {
match event.key_code {
.up {
app.player_x += app.player_dx
app.player_y += app.player_dy
}
.down {
app.player_x -= app.player_dx
app.player_y -= app.player_dy
}
.left {
app.player_angle -= 0.1
if app.player_angle < 0 {
app.player_angle += 2 * math.pi
}
calc_deltas(mut app)
}
.right {
app.player_angle += 0.1
if app.player_angle > 2 * math.pi {
app.player_angle -= 2 * math.pi
}
calc_deltas(mut app)
}
else {}
}
}
}
fn calc_deltas(mut app App) {
app.player_dx = math.cosf(app.player_angle) * 5
app.player_dy = math.sinf(app.player_angle) * 5
}
fn hypotenuse(ax f32, ay f32, bx f32, by f32) f32 {
a2 := math.square(bx - ax)
b2 := math.square(by - ay)
return math.sqrtf(a2 + b2)
}
fn clamp_ray_angle(ra f32) f32 {
return match true {
ra < 0 { ra + 2 * math.pi }
ra > 2 * math.pi { ra - 2 * math.pi }
else { ra }
}
}
fn draw_instructions(app App) {
app.ctx.draw_text(700, app.ctx.height - 17, 'use arrow keys to move player')
}
+43
View File
@@ -0,0 +1,43 @@
module main
import gg
import os.asset
const win_width = 600
const win_height = 300
struct App {
mut:
gg &gg.Context = unsafe { nil }
image int // gg.Image
}
fn main() {
mut app := &App{}
app.gg = gg.new_context(
bg_color: gg.white
width: win_width
height: win_height
create_window: true
window_title: 'Rectangles'
frame_fn: frame
user_data: app
)
logo_path := asset.get_path('../assets', 'logo.png')
app.image = app.gg.create_image(logo_path)!.id
app.gg.run()
}
fn frame(app &App) {
app.gg.begin()
app.draw()
app.gg.end()
}
fn (app &App) draw() {
// app.gg.draw_text_def(200,20, 'hello world!')
// app.gg.draw_text_def(300,300, 'привет')
app.gg.draw_rect_filled(10, 10, 100, 30, gg.blue)
app.gg.draw_rect_empty(110, 150, 80, 40, gg.black)
app.gg.draw_image_by_id(230, 30, 200, 200, app.image)
}
@@ -0,0 +1,56 @@
import gg
import os.asset
import sokol.sgl
// If you have emscripten (see https://emscripten.org/docs/getting_started/index.html), you can compile this program to WASM, using:
// `v -os wasm32_emscripten -o examples/gg/rotating_textured_quad.html examples/gg/rotating_textured_quad.v`, and later check with:
// `emrun examples/gg/rotating_textured_quad.html` .
#flag wasm32_emscripten --embed-file @VEXEROOT/examples/assets/logo.png@/assets/logo.png
#flag wasm32_emscripten --embed-file @VEXEROOT/examples/assets/fonts/RobotoMono-Regular.ttf@/assets/RobotoMono-Regular.ttf
pub struct Window {
pub mut:
ctx &gg.Context = unsafe { nil }
img gg.Image
}
pub fn (mut window Window) init() {
image_path := asset.get_path('../assets', 'logo.png')
window.img = window.ctx.create_image(image_path) or { panic(err) }
}
pub fn (mut window Window) draw() {
angle := f32(window.ctx.frame) / 64 // since window.ctx.frame is increased by 1 on every frame -> the angle will be increasing too
window.ctx.begin()
sgl.load_pipeline(window.ctx.pipeline.alpha)
sgl.translate(250, 250, 0) // center of the screen
sgl.rotate(angle, 0.0, 0.0, 1.0) // rotate around the Z axis pointing towards the camera
sgl.enable_texture()
sgl.texture(window.img.simg, window.img.ssmp)
sgl.begin_quads()
sgl.c4b(255, 255, 255, 255)
sgl.v3f_t2f(200, 200, 0, 1.0, 1.0)
sgl.v3f_t2f(200, -200, 0, 1.0, 0.0)
sgl.v3f_t2f(-200, -200, 0, 0.0, 0.0)
sgl.v3f_t2f(-200, 200, 0, 0.0, 1.0)
sgl.end()
sgl.disable_texture()
window.ctx.end()
}
fn main() {
mut window := &Window{}
window.ctx = gg.new_context(
window_title: 'Rotating V logo'
bg_color: gg.light_green
width: 500
height: 500
user_data: window
init_fn: window.init
frame_fn: window.draw
font_path: asset.get_path('../assets', 'RobotoMono-Regular.ttf')
)
window.ctx.run()
}
+25
View File
@@ -0,0 +1,25 @@
module main
import gg
import os
fn main() {
scount := os.args[1] or { '2' }.int()
println('> sample count: ${scount}')
mut ctx := gg.new_context(
bg_color: gg.white
window_title: 'sample_count: ${scount}'
width: 320
height: 240
sample_count: scount
frame_fn: fn (mut ctx gg.Context) {
ctx.begin()
ctx.draw_rounded_rect_empty(110, 70, 100, 100, 10, gg.blue)
ctx.draw_circle_empty(160, 120, 100, gg.red)
ctx.draw_triangle_empty(160, 93, 186, 138, 132, 138, gg.green)
ctx.draw_rect_filled(159, 119, 2, 2, gg.black)
ctx.end()
}
)
ctx.run()
}
+72
View File
@@ -0,0 +1,72 @@
import gg
import math
const pixel_count = 15000
const p = 3
const s = math.pi * 2 / f32(pixel_count)
const color = gg.Color{255, 255, 255, 255}
const background = gg.Color{20, 20, 64, 5}
const colors = [
gg.Color{255, 255, 255, 255},
gg.Color{255, 0, 255, 255},
gg.Color{255, 255, 0, 255},
gg.Color{0, 255, 255, 255},
gg.Color{0, 0, 255, 255},
gg.Color{0, 0, 0, 255},
]!
mut k := 497
mut scale := 200
gg.start(
window_title: 'Spirograph'
bg_color: background
width: 900
height: 950
sample_count: 2
frame_fn: fn [mut k, mut scale] (mut ctx gg.Context) {
wsize := gg.window_size()
ctx.begin()
ctx.draw_rect_filled(0, 0, wsize.width, wsize.height, gg.Color{10, 1, 30, 60})
t := f64(ctx.frame) / 300
d := math.sin(t)
if math.abs(d) < 0.0015 {
k++
}
if ctx.pressed_keys[int(gg.KeyCode._0)] {
ctx.frame = 475
}
if ctx.pressed_keys[int(gg.KeyCode.left)] {
k--
}
if ctx.pressed_keys[int(gg.KeyCode.right)] {
k++
}
if ctx.pressed_keys[int(gg.KeyCode.up)] {
scale++
}
if ctx.pressed_keys[int(gg.KeyCode.down)] {
scale--
}
d600 := math.sin(f64(ctx.frame) / 60)
cd600 := math.cos(d600)
mut pdj, mut pkj := [p]f64{}, [p]f64{}
for j := 0; j < p; j++ {
pdj[j], pkj[j] = math.pow(d, j), math.pow(k, j)
}
for i := 0; i < pixel_count; i++ {
mut x, mut y := 0.0, 0.0
for j := 0; j < p; j++ {
a := pkj[j] * i * s
x += pdj[j] * math.sin(a - t) * cd600
y += pdj[j] * math.cos(a - t) * cd600
}
ctx.draw_rect_filled(f32(wsize.width / 2 + scale * x),
f32(wsize.height / 2 + scale * y), 1, 1, colors[i % colors.len])
}
ctx.draw_text(wsize.width / 2 - 170, wsize.height - 15,
'frame: ${ctx.frame:06}, k: ${k:4}, scale: ${scale:4}, arrows to change',
color: colors[1]
)
ctx.end(how: .passthru)
}
)
+131
View File
@@ -0,0 +1,131 @@
module main
import os.asset
import gg
import rand
import sokol.sgl
const win_width = 800
const win_height = 600
const max_stars = 5000
const max_v_letters = 5
struct Star {
mut:
x f32
y f32
z f32
r f32
g f32
b f32
}
struct VLetter {
mut:
x f32
y f32
z f32
w f32
h f32
angle f32
dz f32
dangle f32
}
struct App {
mut:
gg &gg.Context = unsafe { nil }
image gg.Image
stars []Star
v_letters []VLetter
}
fn main() {
mut app := &App{
stars: []Star{len: max_stars}
v_letters: []VLetter{len: max_v_letters}
}
app.gg = gg.new_context(
bg_color: gg.black
width: win_width
height: win_height
create_window: true
window_title: 'Star Vield'
frame_fn: frame
init_fn: init_images
user_data: app
)
for i in 0 .. max_stars {
app.stars[i].x = rand.f32_in_range(-200.0, 200.0) or { -200.0 }
app.stars[i].y = rand.f32_in_range(-200.0, 200.0) or { -200.0 }
app.stars[i].z = rand.f32_in_range(-200.0, -100.0) or { -200.0 }
app.stars[i].r = rand.f32_in_range(0.1, 1.0) or { 0.1 }
app.stars[i].g = rand.f32_in_range(0.1, 1.0) or { 0.1 }
app.stars[i].b = rand.f32_in_range(0.1, 1.0) or { 0.1 }
}
for i in 0 .. max_v_letters {
app.v_letters[i].x = rand.f32_in_range(-20.0, 20.0) or { -20.0 }
app.v_letters[i].y = rand.f32_in_range(-20.0, 20.0) or { -20.0 }
app.v_letters[i].z = rand.f32_in_range(-5.0, -1.0) or { -5.0 }
app.v_letters[i].w = rand.f32_in_range(5, 20) or { 5 }
app.v_letters[i].h = app.v_letters[i].w
app.v_letters[i].angle = rand.f32_in_range(0, 6.283184) or { 0 }
app.v_letters[i].dangle = rand.f32_in_range(-0.05, 0.05) or { -0.05 }
app.v_letters[i].dz = rand.f32_in_range(-0.1, -0.01) or { -0.1 }
}
app.gg.run()
}
fn init_images(mut app App) {
logo_path := asset.get_path('../assets', 'logo.png')
app.image = app.gg.create_image(logo_path) or { panic(err) }
}
fn frame(mut app App) {
app.gg.begin()
app.draw()
app.gg.end()
}
// fn C.glPointSize(size f32)
fn (mut app App) draw() {
sgl.defaults()
sgl.perspective(sgl.rad(90), 1.0, 1.0, 100.0)
// C.glPointSize(3.0)
sgl.begin_points()
for i in 0 .. app.stars.len {
s := app.stars[i]
sgl.v3f_c3f(s.x, s.y, s.z, s.r, s.g, s.b)
app.stars[i].z += 0.3
if app.stars[i].z > -1.0 {
app.stars[i].x = rand.f32_in_range(-200.0, 200.0) or { -200.0 }
app.stars[i].y = rand.f32_in_range(-200.0, 200.0) or { -200.0 }
app.stars[i].z = rand.f32_in_range(-200.0, -100.0) or { -200.0 }
}
}
sgl.end()
// ////
for i in 0 .. app.v_letters.len {
v := app.v_letters[i]
sgl.defaults()
sgl.perspective(sgl.rad(90), 1.0, 1.0, 100.0)
sgl.rotate(v.angle, 0, 0, 1)
app.gg.draw_image_3d(v.x, v.y, v.z, v.w, v.h, app.image)
//
app.v_letters[i].z += app.v_letters[i].dz
app.v_letters[i].angle += app.v_letters[i].dangle
if app.v_letters[i].z > -60.0 {
app.v_letters[i].x += rand.f32_in_range(-0.05, 0.05) or { -0.05 }
app.v_letters[i].y += rand.f32_in_range(-0.05, 0.05) or { -0.05 }
}
if app.v_letters[i].z < -95.0 {
app.v_letters[i].h *= 0.8
app.v_letters[i].w *= 0.8
}
if app.v_letters[i].z < -100.0 {
app.v_letters[i].z = rand.f32_in_range(-5.0, -1.0) or { -5.0 }
app.v_letters[i].h = 10.0
app.v_letters[i].w = 10.0
}
}
}
+83
View File
@@ -0,0 +1,83 @@
// Copyright(C) 2019 Lars Pontoppidan. All rights reserved.
// Use of this source code is governed by an MIT license file distributed with this software package.
module main
// Example of how to send a value through a channel from a worker thread to the main/rendering thread.
// This can be useful to do long running computations while keeping your framerate high (60 fps in this example).
import gg
import math
import time
const win_width = 600
const win_height = 700
const bg_color = gg.white
struct App {
mut:
gg &gg.Context = unsafe { nil }
ch chan i64
counter i64
}
fn main() {
mut app := &App{}
app.gg = gg.new_context(
width: win_width
height: win_height
create_window: true
window_title: 'Counter'
user_data: app
bg_color: bg_color
frame_fn: frame
init_fn: init
)
app.gg.run()
}
fn init(mut app App) {
// Spawn a new worker thread.
spawn worker(mut app)
}
// worker simulates a workload. This should be run in a separate thread.
fn worker(mut app App) {
stopwatch := time.new_stopwatch()
mut elapsed := stopwatch.elapsed()
// Do heavy operations here - like invoking a path finding algorithm, load an image or similar.
for {
now := stopwatch.elapsed()
// When done - send the result through a channel to the main/rendering thread.
app.ch <- (now - elapsed)
elapsed = now
time.sleep(1 * time.second)
}
}
fn frame(mut app App) {
app.gg.begin()
size := gg.window_size()
mut scale_factor := math.round(f32(size.width) / win_width)
if scale_factor <= 0 {
scale_factor = 1
}
text_cfg := gg.TextCfg{
size: 64 * int(scale_factor)
}
// Try a pop from the channel
mut count := i64(0)
if app.ch.try_pop(mut count) == .success {
// A value was assigned - increase the counter
app.counter += i64(f64(count) / time.second)
}
label := '${app.counter}'
label_width := (f64(label.len * text_cfg.size) / 4.0)
label_height := (f64(1 * text_cfg.size) / 2.0)
mut x := f32(size.width) * 0.5 - label_width
mut y := f32(size.height) * 0.5 - label_height
app.gg.draw_text(int(x), int(y), label, text_cfg)
app.gg.end()
}
+51
View File
@@ -0,0 +1,51 @@
import gg
import time
const pwidth = 640
const pheight = 480
struct AppState {
mut:
gg &gg.Context = unsafe { nil }
istream_idx int
pixels [pheight][pwidth]u32
}
@[direct_array_access]
fn (mut state AppState) update() {
mut rcolor := u32(0)
for {
for y in 0 .. pheight {
for x in 0 .. pwidth {
rcolor = x * y * u32(state.gg.frame)
state.pixels[y][x] = u32(rcolor) | 0xFF_00_00_00 // set the alpha channel to 255
}
}
time.sleep(16 * time.millisecond)
}
}
fn graphics_init(mut state AppState) {
state.istream_idx = state.gg.new_streaming_image(pwidth, pheight, 4, pixel_format: .rgba8)
}
fn graphics_frame(mut state AppState) {
state.gg.begin()
mut istream_image := state.gg.get_cached_image_by_idx(state.istream_idx)
istream_image.update_pixel_data(unsafe { &u8(&state.pixels) })
size := gg.window_size()
state.gg.draw_image(0, 0, size.width, size.height, istream_image)
state.gg.end()
}
mut state := &AppState{}
state.gg = gg.new_context(
width: pwidth
height: pheight
window_title: 'x*y*frame'
user_data: state
init_fn: graphics_init
frame_fn: graphics_frame
)
spawn state.update()
state.gg.run()