v2 / examples / gg / digital_rain.v
178 lines · 164 sloc · 4.37 KB · 8e35f4d9848f7ad35d857a187dddbfd2eca5e19d
Raw
1// Creates the digital rain effect from the movie, "The Matrix"
2module main
3
4import gg
5import rand
6import time
7
8const font_size = 20
9const rain_drops = '0123456789!@#$%^&*()-=+[]{}|;:<>?~bdjpqtvz'.bytes()
10
11struct App {
12mut:
13 ctx &gg.Context = unsafe { nil }
14 rows int
15 cols int
16 char_width int
17 char_height int
18 screen_size gg.Size
19 should_calc bool = true
20 rain_columns []RainColumn
21 delay time.Duration = time.millisecond * 100
22}
23
24struct RainColumn {
25mut:
26 col int // character based postion
27 len int // length of the rain column in characters
28 head int // y position of the rain column
29 drops []u8 // no retained graphics in gg, store entire column
30}
31
32fn main() {
33 mut app := &App{}
34 rain(mut app)
35}
36
37fn rain(mut app App) {
38 app.ctx = gg.new_context(
39 bg_color: gg.rgb(0, 0, 0)
40 width: app.screen_size.width
41 height: app.screen_size.height
42 user_data: app
43 window_title: 'Digital Rain'
44 init_fn: fn (mut app App) {
45 gg.toggle_fullscreen()
46 }
47 event_fn: fn (event &gg.Event, mut app App) {
48 vprintln('event.typ: ${event.typ} | event.char_code: ${event.char_code}')
49 if event.typ == .resized {
50 app.should_calc = true
51 return
52 }
53 if event.typ == .char && event.char_code == `f` {
54 gg.toggle_fullscreen()
55 return
56 }
57 if event.typ == .key_up && event.key_code == .up {
58 app.delay = app.delay + 50 * time.millisecond
59 }
60 if event.typ == .key_up && event.key_code == .down {
61 new_delay := app.delay - 50 * time.millisecond
62 app.delay = if new_delay > 0 { new_delay } else { 0 }
63 }
64 }
65 frame_fn: frame
66 )
67 app.ctx.run()
68}
69
70fn frame(mut app App) {
71 app.ctx.begin()
72 if app.should_calc {
73 app.should_calc = false
74 calc_sizes(mut app)
75 }
76 // gradually add rain columns
77 if app.rain_columns.len < app.cols / 4 * 3 {
78 app.rain_columns << random_rain_column(app.cols, app.rows)
79 }
80 // update and print all rain columns
81 for mut rc in app.rain_columns {
82 update_rain_column(mut rc, app.cols, app.rows)
83 draw_rain_column(rc, app)
84 }
85 app.ctx.draw_text(app.screen_size.width / 2 - 190, app.screen_size.height - 15,
86 'press `f` to toggle fullscreen, Up/Down arrows to change speed',
87 color: gg.gray
88 )
89 app.ctx.end()
90 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}')
91 time.sleep(app.delay)
92}
93
94@[if verbose ?]
95fn vprintln(msg string) {
96 println(msg)
97}
98
99fn calc_sizes(mut app App) {
100 app.screen_size = gg.window_size()
101 app.ctx.set_text_cfg(gg.TextCfg{
102 size: font_size
103 color: gg.green
104 mono: true
105 })
106 // figure out how big character is in pixels
107 // Pad it or it looks too squashed
108 app.char_width, app.char_height = app.ctx.text_size('M')
109 app.char_width += 3
110 app.char_height += 1
111 // determine the size of the matrix in rows and columns
112 app.cols = app.screen_size.width / app.char_width
113 app.rows = app.screen_size.height / app.char_height
114 vprintln('app.cols: ${app.cols} | app.rows: ${app.rows}')
115}
116
117fn update_rain_column(mut rc RainColumn, width int, height int) {
118 rc.head += 1
119 if rc.head > height + rc.len {
120 rc = random_rain_column(width, height)
121 }
122}
123
124fn draw_rain_column(rc RainColumn, app App) {
125 mut y := 0
126 x := rc.col * app.char_width
127 end := rc.head - rc.len
128 for i in 0 .. app.rows - 1 {
129 if i >= end && i < rc.head {
130 alpha := match i - end {
131 0 { u8(75) }
132 1 { u8(100) }
133 2 { u8(125) }
134 3 { u8(150) }
135 4 { u8(175) }
136 5 { u8(200) }
137 6 { u8(225) }
138 else { u8(255) }
139 }
140
141 at_head := i == rc.head - 1
142 cfg := gg.TextCfg{
143 size: font_size
144 color: gg.Color{
145 r: if at_head { u8(255) } else { 0 }
146 g: 255
147 b: if at_head { u8(255) } else { 0 }
148 a: alpha
149 }
150 mono: true
151 }
152 if i < rc.drops.len {
153 app.ctx.draw_text(x, y, rc.drops[i].ascii_str(), cfg)
154 app.ctx.draw_text(x, y, rc.drops[(i + 10) % rc.drops.len].ascii_str(), cfg)
155 } else {
156 vprintln('BAD i: ${i} | rc.drops.len: ${rc.drops.len}')
157 }
158 }
159 y += app.char_height
160 }
161}
162
163fn random_rain_column(max_col int, max_height int) RainColumn {
164 min_len := 6
165 mut rc := RainColumn{
166 col: rand.int_in_range(0, max_col) or { 0 }
167 len: rand.int_in_range(min_len, max_height / 4 * 3) or { min_len }
168 drops: []u8{cap: max_height}
169 }
170 for _ in 0 .. max_height {
171 rc.drops << random_rain_drop()
172 }
173 return rc
174}
175
176fn random_rain_drop() u8 {
177 return rand.element(rain_drops) or { rain_drops[0] }
178}
179