Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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] }
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
)
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
}
|
||||
)
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
)
|
||||
@@ -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
|
||||

|
||||
|
||||
# 🔴🔴🔴🔴 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 |
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
Reference in new issue
Block a user