├── .gitignore
├── v.mod
├── examples
├── tvintris
│ ├── tvintris.png
│ ├── sounds
│ │ ├── block.wav
│ │ ├── single.wav
│ │ ├── triple.wav
│ │ └── TwintrisThosenine.mod
│ ├── images
│ │ └── v-logo_30_30.png
│ ├── fonts
│ │ └── RobotoMono-Regular.ttf
│ ├── README.md
│ └── tvintris.v
└── main_v.v
├── image
├── README.md
└── image.v
├── LICENSE
├── README.md
└── vsdl2.v
/.gitignore:
--------------------------------------------------------------------------------
1 | *.tmp.c
2 |
--------------------------------------------------------------------------------
/v.mod:
--------------------------------------------------------------------------------
1 | Module {
2 | name: 'vsdl2'
3 | version: '0.0.1'
4 | deps: []
5 | }
6 |
--------------------------------------------------------------------------------
/examples/tvintris/tvintris.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nsauzede/vsdl2/HEAD/examples/tvintris/tvintris.png
--------------------------------------------------------------------------------
/examples/tvintris/sounds/block.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nsauzede/vsdl2/HEAD/examples/tvintris/sounds/block.wav
--------------------------------------------------------------------------------
/examples/tvintris/sounds/single.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nsauzede/vsdl2/HEAD/examples/tvintris/sounds/single.wav
--------------------------------------------------------------------------------
/examples/tvintris/sounds/triple.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nsauzede/vsdl2/HEAD/examples/tvintris/sounds/triple.wav
--------------------------------------------------------------------------------
/examples/tvintris/images/v-logo_30_30.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nsauzede/vsdl2/HEAD/examples/tvintris/images/v-logo_30_30.png
--------------------------------------------------------------------------------
/examples/tvintris/fonts/RobotoMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nsauzede/vsdl2/HEAD/examples/tvintris/fonts/RobotoMono-Regular.ttf
--------------------------------------------------------------------------------
/examples/tvintris/sounds/TwintrisThosenine.mod:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nsauzede/vsdl2/HEAD/examples/tvintris/sounds/TwintrisThosenine.mod
--------------------------------------------------------------------------------
/image/README.md:
--------------------------------------------------------------------------------
1 | # SDL2 Image Library
2 | Assuming you've installed the dependencies for vsdl2 (already include `SDL2_image` dependency)
3 |
4 | See the Tvintris example for usage
5 |
--------------------------------------------------------------------------------
/examples/tvintris/README.md:
--------------------------------------------------------------------------------
1 | # tVintris
2 |
3 | tvintris.v is a dual-player (local) version based on original source from vlang/v example by Alex M.
4 | It is largely inspired by ancient game Twintris.
5 | -uses published vpm module nsauzede.vsdl2
6 |
7 |
8 |
9 | Colors, Music and Sounds ripped from amiga title Twintris (1990 nostalgia !)
10 | - Graphician : Svein Berge
11 | - Musician : Tor Bernhard Gausen (Walkman/Cryptoburners)
12 |
13 | # how to run tVintris
14 |
15 | `$ v install nsauzede.vsdl2`
16 | `$ v run .`
17 |
--------------------------------------------------------------------------------
/image/image.v:
--------------------------------------------------------------------------------
1 | module image
2 |
3 | import nsauzede.vsdl2
4 |
5 | #flag linux -lSDL2_image
6 | #include
7 |
8 | // following kludge until `sdl2-config ...` is supported also on windows
9 | #flag windows -I/msys64/mingw64/include/SDL2
10 | #flag windows -L/msys64/mingw64/lib -lSDL2_image
11 |
12 | //////////////////////////////////////////////////////////
13 | // SDL_Image.h
14 | //////////////////////////////////////////////////////////
15 | //fn C.IMG_Load_RW(logo &vsdl2.RwOps, free_src int) &vsdl2.Surface
16 | fn C.IMG_Init(flags int) int
17 | fn C.IMG_Quit()
18 | fn C.IMG_Load(file byteptr) voidptr
19 |
20 | const (
21 | vsdl2_version = vsdl2.version
22 | )
23 |
24 | pub fn img_init(flags int) int {
25 | return C.IMG_Init(flags)
26 | }
27 |
28 | pub fn quit() {
29 | C.IMG_Quit()
30 | }
31 |
32 | pub fn load(file string) &vsdl2.Surface {
33 | res := C.IMG_Load(file.str)
34 | return res
35 | }
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Nicolas Sauzede
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vsdl2
2 | SDL2 V module -- libSDL2 wrapper
3 |
4 | *IMPORTANT
5 | vsdl2 has been integrated in V `vlib/sdl` [here](https://github.com/vlang/v/tree/master/vlib/sdl).
6 | Not sure what will become of this legacy SDL PoC..*
7 |
8 | In fact, the `sdl` component of upstrean vlang seems to have moved [there](https://github.com/vlang/sdl) and be pretty much out of date.
9 | Thus, I'll continue maintaining my initial SDL2 work here, as time permits..
10 |
11 |
12 | Current APIs available/tested in examples :
13 | - basic graphics (2D drawing)
14 | - [Image](image/README.md)
15 | - TTF font (text rendering)
16 | - input handling (keyboard/joystick events)
17 | - sounds (WAV mixing)
18 | - music (MOD mixing)
19 | - more to come.. (networking ?)
20 |
21 | # Support
22 | vsdl2 is supported on :
23 | - linux (major distros)
24 | - MacOS (brew)
25 | - windows (msys2/mingw64 only for now)
26 |
27 | # Installation
28 | `v install nsauzede.vsdl2`
29 |
30 | # Examples
31 |
32 | [tVintris](https://github.com/nsauzede/vsdl2/tree/master/examples/tvintris)
33 |
34 | 
35 |
36 | Once you have installed nsauzede.vsdl2 (see above), you can run the example yourself like this :
37 | On linux:
38 | ```
39 | v run ~/.vmodules/nsauzede/vsdl2/examples/tvintris/tvintris.v
40 | ```
41 | On Windows (MSYS2):
42 | ```
43 | v run /c/Users/${USER}/.vmodules/nsauzede/vsdl2/examples/tvintris/tvintris.v
44 | ```
45 |
46 | # Dependencies
47 |
48 | ## Linux
49 | Fedora :
50 | `$ sudo dnf install SDL2-devel SDL2_ttf-devel SDL2_mixer-devel SDL2_image-devel`
51 |
52 | Ubuntu :
53 | `$ sudo apt install libsdl2-ttf-dev libsdl2-mixer-dev libsdl2-image-dev`
54 |
55 | ClearLinux :
56 | `$ sudo swupd bundle-add devpkg-SDL2_ttf devpkg-SDL2_mixer devpkg-SDL2_image`
57 |
58 | ## MacOS
59 | Brew :
60 | `$ brew install sdl2 sdl2_gfx sdl2_ttf sdl2_mixer sdl2_image sdl2_net`
61 |
62 | ## Windows
63 | Windows (MSYS2) :
64 | `$ pacman -S mingw-w64-x86_64-SDL2_ttf mingw-w64-x86_64-SDL2_mixer mingw-w64-x86_64-SDL2_image`
65 |
66 | # Contributions
67 |
68 | Thanks to spytheman and adlesh for their contributions to vsdl2
69 |
--------------------------------------------------------------------------------
/examples/main_v.v:
--------------------------------------------------------------------------------
1 | // Copyright(C) 2019 Nicolas Sauzede. All rights reserved.
2 | // Use of this source code is governed by an MIT license
3 | // that can be found in the LICENSE_v.txt file.
4 | module main
5 |
6 | import nsauzede.vsdl2
7 | import time
8 | import os
9 |
10 | const (
11 | colors = [
12 | vsdl2.Color{u8(255), u8(255), u8(255), u8(0)},
13 | vsdl2.Color{u8(255), u8(0), u8(0), u8(0)},
14 | ]
15 | )
16 |
17 | struct AudioContext {
18 | mut:
19 | // audio_pos *u8
20 | audio_pos voidptr
21 | audio_len u32
22 | wav_spec vsdl2.AudioSpec
23 | wav_buffer &u8 = voidptr(0)
24 | wav_length u32
25 | wav2_buffer &u8 = voidptr(0)
26 | wav2_length u32
27 | }
28 |
29 | fn acb(userdata voidptr, stream &u8, _len int) {
30 | unsafe{ mut ctx := &AudioContext(userdata)
31 | // println('acb!!! wav_buffer=${ctx.wav_buffer} audio_len=${ctx.audio_len}')
32 | if ctx.audio_len == u32(0) {
33 | C.memset(stream, 0, _len)
34 | return
35 | }
36 | mut len := u32(_len)
37 | if len > ctx.audio_len {
38 | len = ctx.audio_len
39 | }
40 | C.memcpy(stream, ctx.audio_pos, len)
41 | ctx.audio_pos = voidptr(u64(ctx.audio_pos) + u64(len))
42 | ctx.audio_len -= len
43 | }}
44 |
45 | @[live]
46 | fn livemain() {
47 | println('hello SDL 2 [v]\n')
48 | w := 200
49 | h := 400
50 | bpp := 32
51 | sdl_window := voidptr(0)
52 | sdl_renderer := voidptr(0)
53 | C.SDL_Init(C.SDL_INIT_VIDEO | C.SDL_INIT_AUDIO)
54 | C.atexit(C.SDL_Quit)
55 | C.TTF_Init()
56 | C.atexit(C.TTF_Quit)
57 | font := C.TTF_OpenFont(c'fonts/RobotoMono-Regular.ttf', 16)
58 | // println('font=$font')
59 | C.SDL_CreateWindowAndRenderer(w, h, 0, &sdl_window, &sdl_renderer)
60 | // println('renderer=$sdl_renderer')
61 | screen := vsdl2.create_rgb_surface(0, w, h, bpp, 0x00FF0000, 0x0000FF00, 0x000000FF,
62 | 0xFF000000)
63 | sdl_texture := C.SDL_CreateTexture(sdl_renderer, C.SDL_PIXELFORMAT_ARGB8888, C.SDL_TEXTUREACCESS_STREAMING,
64 | w, h)
65 | mut actx := AudioContext{}
66 | // C.SDL_zero(actx)
67 | C.SDL_LoadWAV(c'sounds/door2.wav', &actx.wav_spec, &actx.wav_buffer, &actx.wav_length)
68 | // C.SDL_LoadWAV('sounds/block.wav', &actx.wav_spec, &actx.wav_buffer, &actx.wav_length)
69 | // println('got wav_buffer=${actx.wav_buffer}')
70 | C.SDL_LoadWAV(c'sounds/single.wav', &actx.wav_spec, &actx.wav2_buffer, &actx.wav2_length)
71 | actx.wav_spec.callback = voidptr(acb)
72 | actx.wav_spec.userdata = &actx
73 | if C.SDL_OpenAudio(&actx.wav_spec, 0) < 0 {
74 | println("couldn't open audio")
75 | return
76 | }
77 | mut quit := false
78 | mut ballx := 0
79 | bally := h / 2
80 | balld := 10
81 | ballm := 1
82 | mut balldir := ballm
83 | for !quit {
84 | ev := vsdl2.Event{}
85 | for 0 < C.SDL_PollEvent(&ev) {
86 | match int(unsafe{ev.@type}) {
87 | C.SDL_QUIT {
88 | quit = true
89 | }
90 | C.SDL_KEYDOWN {
91 | match int(unsafe{ev.key.keysym.sym}) {
92 | C.SDLK_ESCAPE {
93 | quit = true
94 | }
95 | C.SDLK_SPACE {
96 | actx.audio_pos = actx.wav2_buffer
97 | actx.audio_len = actx.wav2_length
98 | C.SDL_PauseAudio(0)
99 | }
100 | else {}
101 | }
102 | }
103 | else {}
104 | }
105 | }
106 | if quit {
107 | break
108 | }
109 | // rect := vsdl2.Rect {x: 0, y: 0, w: w, h: h } // TODO doesn't compile ???
110 | mut rect := vsdl2.Rect{0, 0, w, h}
111 | mut col := vsdl2.Color{u8(255), u8(255), u8(255), u8(0)}
112 | vsdl2.fill_rect(screen, &rect, col)
113 |
114 | rect = vsdl2.Rect{ballx, bally, balld, balld}
115 | col = vsdl2.Color{colors[1].r, colors[1].g, colors[1].b, 0}
116 | vsdl2.fill_rect(screen, &rect, col)
117 | ballx += balldir
118 | if balldir == ballm {
119 | if ballx == w - balld * 4 {
120 | // println('+1WAV =>')
121 | actx.audio_pos = actx.wav2_buffer
122 | // actx.audio_len = actx.wav2_length
123 | C.SDL_PauseAudio(0)
124 | } else if ballx >= w - balld {
125 | // println('+1WAV <= -1')
126 | balldir = -ballm
127 | actx.audio_pos = actx.wav_buffer
128 | actx.audio_len = actx.wav_length
129 | C.SDL_PauseAudio(0)
130 | }
131 | } else {
132 | if ballx == balld * 4 {
133 | // println('-1WAV2 <=')
134 | actx.audio_pos = actx.wav2_buffer
135 | // actx.audio_len = actx.wav2_length
136 | C.SDL_PauseAudio(0)
137 | } else if ballx <= 0 {
138 | // println('-1WAV => 1')
139 | balldir = ballm
140 | actx.audio_pos = actx.wav_buffer
141 | actx.audio_len = actx.wav_length
142 | C.SDL_PauseAudio(0)
143 | }
144 | }
145 |
146 | C.SDL_UpdateTexture(sdl_texture, 0, screen.pixels, screen.pitch)
147 | C.SDL_RenderClear(sdl_renderer)
148 | C.SDL_RenderCopy(sdl_renderer, sdl_texture, 0, 0)
149 |
150 | // tcol := C.SDL_Color {u32(0), u32(0), u32(0)} // TODO doesn't compile ?
151 | // tcol := [u8(0), u8(0), u8(0), u8(0)]
152 | tcol := C.SDL_Color{u8(3), u8(2), u8(1), u8(0)}
153 | // tsurf := C.TTF_RenderText_Solid(font,'Hello SDL_ttf', tcol)
154 | // tsurf := *voidptr(0xdeadbeef)
155 | // println('tsurf=$tsurf')
156 | // C.stubTTF_RenderText_Solid(font,'Hello SDL_ttf V !', &tcol, &tsurf)
157 | tsurf := C.TTF_RenderText_Solid(font, c'Hello SDL_ttf V !', tcol)
158 | // println('tsurf=$tsurf')
159 | // tsurf := C.TTF_RenderText_Solid(font,'Hello SDL_ttf', 0)
160 | // println('tsurf=$tsurf')
161 | // println('tsurf=' + $tsurf')
162 | ttext := C.SDL_CreateTextureFromSurface(sdl_renderer, tsurf)
163 | // println('ttext=$ttext')
164 | texw := 0
165 | texh := 0
166 | C.SDL_QueryTexture(ttext, 0, 0, &texw, &texh)
167 | dstrect := vsdl2.Rect{0, 0, texw, texh}
168 | C.SDL_RenderCopy(sdl_renderer, ttext, 0, &dstrect)
169 | C.SDL_DestroyTexture(ttext)
170 | vsdl2.free_surface(tsurf)
171 |
172 | C.SDL_RenderPresent(sdl_renderer)
173 | C.SDL_Delay(10)
174 | }
175 | if !isnil(font) {
176 | C.TTF_CloseFont(font)
177 | }
178 | C.SDL_CloseAudio()
179 | if voidptr(actx.wav_buffer) != voidptr(0) {
180 | C.SDL_FreeWAV(actx.wav_buffer)
181 | }
182 | }
183 |
184 | fn main() {
185 | // these 3 lines just silence the v unused modules warnings (errors when -prod is given)
186 | println('vsdl2 version: $vsdl2.version')
187 | println('current time: $time.now().format_ss()')
188 | println('executable: $os.executable()')
189 | livemain()
190 | }
191 |
--------------------------------------------------------------------------------
/vsdl2.v:
--------------------------------------------------------------------------------
1 | // Copyright(C) 2019 Nicolas Sauzede. All rights reserved.
2 | // Use of this source code is governed by an MIT license
3 | // that can be found in the LICENSE file.
4 |
5 | module vsdl2
6 |
7 | //#flag linux -I/usr/include/SDL2 -D_REENTRANT -lSDL2 -lSDL2_ttf -lSDL2_mixer -lSDL2_image
8 | #flag linux -I/usr/include/SDL2
9 | #flag linux -D_REENTRANT
10 | #flag linux -lSDL2 -lSDL2_ttf -lSDL2_mixer -lSDL2_image
11 | //#flag linux `sdl2-config --cflags --libs` -lSDL2_ttf -lSDL2_mixer -lSDL2_image
12 | #flag darwin `sdl2-config --cflags --libs` -lSDL2_ttf -lSDL2_mixer -lSDL2_image
13 |
14 | //#flag windows `sdl2-config --cflags`
15 | //#flag windows `sdl2-config --libs` -lSDL2_ttf -lSDL2_mixer -lSDL2_image
16 | //#flag `sdl2-config --cflags --libs` -lSDL2_ttf -lSDL2_mixer -lSDL2_image
17 |
18 | #flag -DSDL_DISABLE_IMMINTRIN_H
19 |
20 | // following kludge until `sdl2-config ...` is supported also on windows
21 | #flag windows -I/msys64/mingw64/include/SDL2
22 | #flag windows -Dmain=SDL_main
23 | #flag windows -L/msys64/mingw64/lib
24 | #flag windows -lmingw32 -lSDL2main -lSDL2 -lSDL2_ttf -lSDL2_mixer -lSDL2_image
25 |
26 | #include
27 | #include
28 | #include
29 |
30 | @[typedef]
31 | struct C.TTF_Font {}
32 |
33 | //struct C.SDL_Color{
34 | pub struct Color{
35 | pub:
36 | r u8 /**< Red value 0-255 */
37 | g u8 /**< Green value 0-255 */
38 | b u8 /**< Blue value 0-255 */
39 | a u8 /**< Alpha value 0-255 */
40 | }
41 | //type Color C.SDL_Color
42 |
43 | pub struct C.SDL_Color{
44 | pub:
45 | r u8
46 | g u8
47 | b u8
48 | a u8
49 | }
50 |
51 | //struct C.SDL_Rect {
52 | pub struct Rect {
53 | pub:
54 | x int /**< number of pixels from left side of screen */
55 | y int /**< num of pixels from top of screen */
56 | w int /**< width of rectangle */
57 | h int /**< height of rectangle */
58 | }
59 | //type Rect C.SDL_Rect
60 |
61 | //pub struct C.SDL_Surface {
62 | pub struct Surface {
63 | pub:
64 | flags u32
65 | format voidptr
66 | w int
67 | h int
68 | pitch int
69 | pixels voidptr
70 | userdata voidptr
71 | locked int
72 | lock_data voidptr
73 | clip_rect Rect
74 | map voidptr
75 | refcount int
76 | }
77 | //type Surface C.SDL_Surface
78 | //type Surface Surface
79 |
80 | /////////////////////////////////////////////////////////
81 |
82 | struct QuitEvent {
83 | @type u32 /**< SDL_QUIT */
84 | timestamp u32
85 | }
86 | struct Keysym {
87 | pub:
88 | scancode int /**< hardware specific scancode */
89 | sym int /**< SDL virtual keysym */
90 | mod u16 /**< current key modifiers */
91 | unused u32 /**< translated character */
92 | }
93 | struct KeyboardEvent {
94 | pub:
95 | @type u32 /**< SDL_KEYDOWN or SDL_KEYUP */
96 | timestamp u32
97 | windowid u32
98 | state u8 /**< SDL_PRESSED or SDL_RELEASED */
99 | repeat u8
100 | padding2 u8
101 | padding3 u8
102 | keysym Keysym
103 | }
104 | struct JoyButtonEvent {
105 | pub:
106 | @type u32 /**< SDL_JOYBUTTONDOWN or SDL_JOYBUTTONUP */
107 | timestamp u32
108 | which int /**< The joystick device index */
109 | button u8 /**< The joystick button index */
110 | state u8 /**< SDL_PRESSED or SDL_RELEASED */
111 | }
112 | struct JoyHatEvent {
113 | pub:
114 | @type u32 /**< SDL_JOYHATMOTION */
115 | timestamp u32
116 | which int /**< The joystick device index */
117 | hat u8 /**< The joystick hat index */
118 | value u8 /**< The hat position value:
119 | * SDL_HAT_LEFTUP SDL_HAT_UP SDL_HAT_RIGHTUP
120 | * SDL_HAT_LEFT SDL_HAT_CENTERED SDL_HAT_RIGHT
121 | * SDL_HAT_LEFTDOWN SDL_HAT_DOWN SDL_HAT_RIGHTDOWN
122 | * Note that zero means the POV is centered.
123 | */
124 | }
125 |
126 | //pub union EventU {
127 | pub union Event {
128 | pub:
129 | @type u32
130 | quit QuitEvent
131 | key KeyboardEvent
132 | jbutton JoyButtonEvent
133 | jhat JoyHatEvent
134 | pad56_ [56]u8
135 | }
136 | //type Event EventU
137 |
138 |
139 | //struct C.SDL_AudioSpec {
140 | pub struct AudioSpec {
141 | pub mut:
142 | freq int /**< DSP frequency -- samples per second */
143 | format u16 /**< Audio data format */
144 | channels u8 /**< Number of channels: 1 mono, 2 stereo */
145 | silence u8 /**< Audio buffer silence value (calculated) */
146 | samples u16 /**< Audio buffer size in samples (power of 2) */
147 | size u32 /**< Necessary for some compile environments */
148 | callback voidptr
149 | userdata voidptr
150 | }
151 |
152 | // pub struct RwOps {
153 | // pub:
154 | // mut:
155 | // seek voidptr
156 | // read voidptr
157 | // write voidptr
158 | // close voidptr
159 | // type_ u32
160 | // hidden voidptr
161 | // }
162 | //type AudioSpec C.voidptrioSpec
163 |
164 | ///////////////////////////////////////////////////
165 | fn C.SDL_MapRGB(fmt voidptr, r u8, g u8, b u8) u32
166 | fn C.SDL_CreateRGBSurface(flags u32, width int, height int, depth int, Rmask u32, Gmask u32, Bmask u32, Amask u32) voidptr
167 | fn C.SDL_PollEvent(&Event) int
168 | fn C.SDL_NumJoysticks() int
169 | fn C.SDL_JoystickOpen(device_index int) int
170 | fn C.SDL_JoystickEventState(state int) int
171 | fn C.SDL_JoystickNameForIndex(device_index int) voidptr
172 | fn C.SDL_RenderCopy(renderer voidptr, texture voidptr, srcrect voidptr, dstrect voidptr) int
173 | fn C.SDL_CreateWindow(title byteptr, x int, y int, w int, h int, flags u32) voidptr
174 | fn C.SDL_CreateWindowAndRenderer(width int, height int, window_flags u32, window &voidptr, renderer &voidptr) int
175 | fn C.SDL_DestroyWindow(window voidptr)
176 | fn C.SDL_GetWindowSize(window voidptr, w voidptr, h voidptr)
177 | fn C.SDL_SetHint(name byteptr, value byteptr) int // C.SDL_bool
178 | //fn C.SDL_RWFromFile(byteptr, byteptr) &RwOps
179 | //fn C.SDL_CreateTextureFromSurface(renderer &C.SDL_Renderer, surface &C.SDL_Surface) &C.SDL_Texture
180 | fn C.SDL_CreateTextureFromSurface(renderer voidptr, surface voidptr) voidptr
181 | fn C.SDL_CreateTexture(renderer voidptr, format u32, access int, w int, h int) voidptr
182 | fn C.SDL_FillRect(dst voidptr, dstrect voidptr, color u32) int
183 | fn C.SDL_RenderPresent(renderer voidptr)
184 | fn C.SDL_RenderClear(renderer voidptr) int
185 | fn C.SDL_UpdateTexture(texture voidptr, rect voidptr, pixels voidptr, pitch int) int
186 | fn C.SDL_QueryTexture(texture voidptr, format voidptr, access voidptr, w voidptr, h voidptr) int
187 | fn C.SDL_DestroyTexture(texture voidptr)
188 | fn C.SDL_FreeSurface(surface voidptr)
189 | fn C.SDL_Init(flags u32) int
190 | fn C.SDL_Quit()
191 | fn C.SDL_SetWindowTitle(window voidptr, title byteptr)
192 | // following is wrong : SDL_Zero is a macro accepting an argument
193 | fn C.SDL_zero(x)
194 | fn C.SDL_LoadWAV(file byteptr, spec voidptr, audio_buf voidptr, audio_len voidptr) voidptr
195 | fn C.SDL_FreeWAV(audio_buf voidptr)
196 | fn C.SDL_OpenAudio(desired voidptr, obtained voidptr) int
197 | fn C.SDL_CloseAudio()
198 | fn C.SDL_PauseAudio(pause_on int)
199 |
200 | //////////////////////////////////////////////////////////
201 | // TTF
202 | //////////////////////////////////////////////////////////
203 | fn C.TTF_Init() int
204 | fn C.TTF_Quit()
205 | fn C.TTF_OpenFont(file byteptr, ptsize int) voidptr
206 | fn C.TTF_CloseFont(font voidptr)
207 | //fn C.TTF_RenderText_Solid(voidptr, voidptr, SdlColor) voidptr
208 | fn C.TTF_RenderText_Solid(voidptr, voidptr, C.SDL_Color) voidptr
209 | //////////////////////////////////////////////////////////
210 |
211 | //////////////////////////////////////////////////////////
212 | // MIX
213 | //////////////////////////////////////////////////////////
214 | fn C.Mix_Init(flags int) int
215 | fn C.Mix_OpenAudio(frequency int, format u16, channels int, chunksize int) int
216 | fn C.Mix_LoadMUS(file byteptr) voidptr
217 | fn C.Mix_LoadWAV(file byteptr) voidptr
218 | fn C.Mix_PlayMusic(music voidptr, loops int) int
219 | fn C.Mix_VolumeMusic(volume int) int
220 | fn C.Mix_FreeMusic(music voidptr)
221 | fn C.Mix_CloseAudio()
222 | fn C.Mix_FreeChunk(chunk voidptr)
223 | fn C.Mix_PauseMusic()
224 | fn C.Mix_ResumeMusic()
225 | fn C.Mix_PlayChannel(channel int, chunk voidptr, loops int) int
226 |
227 | //////////////////////////////////////////////////////////
228 | // GL
229 | //////////////////////////////////////////////////////////
230 | fn C.SDL_GL_SetAttribute(attr int, value int) int
231 | fn C.SDL_GL_CreateContext(window voidptr) voidptr
232 | fn C.SDL_GL_MakeCurrent(window voidptr, context voidptr) int
233 | fn C.SDL_GL_SetSwapInterval(interval int) int
234 | fn C.SDL_GL_SwapWindow(window voidptr)
235 | fn C.SDL_GL_DeleteContext(context voidptr)
236 |
237 | pub fn create_texture_from_surface(renderer voidptr, surface &Surface) voidptr {
238 | return C.SDL_CreateTextureFromSurface(renderer, voidptr(surface))
239 | }
240 |
241 | pub fn create_window_and_renderer(width int, height int, window_flags u32, window voidptr, renderer voidptr) int {
242 | return C.SDL_CreateWindowAndRenderer(width, height, window_flags, window, renderer)
243 | }
244 |
245 | pub fn joystick_name_for_index(device_index int) byteptr {
246 | return byteptr(C.SDL_JoystickNameForIndex(device_index))
247 | }
248 |
249 | pub fn fill_rect(screen &Surface, rect &Rect, col_ &Color) {
250 | col := C.SDL_MapRGB(screen.format, col_.r, col_.g, col_.b)
251 | screen_ := voidptr(screen)
252 | rect_ := voidptr(rect)
253 | C.SDL_FillRect(screen_, rect_, col)
254 | }
255 |
256 | pub fn create_rgb_surface(flags u32, width int, height int, depth int, rmask u32, gmask u32, bmask u32, amask u32) &Surface {
257 | res := C.SDL_CreateRGBSurface(flags, width, height, depth, rmask, gmask, bmask, amask)
258 | return res
259 | }
260 |
261 | pub fn render_copy(renderer voidptr, texture voidptr, srcrect &Rect, dstrect &Rect) int {
262 | srcrect_ := voidptr(srcrect)
263 | dstrect_ := voidptr(dstrect)
264 | return C.SDL_RenderCopy(renderer, texture, srcrect_, dstrect_)
265 | }
266 |
267 | pub fn poll_event(event &Event) int {
268 | return C.SDL_PollEvent(voidptr(event))
269 | }
270 |
271 | pub fn destroy_texture(text voidptr) {
272 | C.SDL_DestroyTexture(text)
273 | }
274 |
275 | pub fn free_surface(surf &Surface) {
276 | surf_ := voidptr(surf)
277 | C.SDL_FreeSurface(surf_)
278 | }
279 |
280 | //////////////////////////////////////////////////////////
281 | // SDL_Timer.h
282 | //////////////////////////////////////////////////////////
283 | fn C.SDL_GetTicks() u32
284 | fn C.SDL_TICKS_PASSED(a u32, b u32) bool
285 | fn C.SDL_GetPerformanceCounter() u64
286 | fn C.SDL_GetPerformanceFrequency() u64
287 | fn C.SDL_Delay(ms u32)
288 |
289 | pub fn get_ticks() u32 {
290 | return C.SDL_GetTicks()
291 | }
292 |
293 | pub fn ticks_passed(a u32, b u32) bool {
294 | return C.SDL_TICKS_PASSED(a,b)
295 | }
296 |
297 | pub fn get_perf_counter() u64 {
298 | return C.SDL_GetPerformanceCounter()
299 | }
300 |
301 | pub fn get_perf_frequency() u64 {
302 | return C.SDL_GetPerformanceFrequency()
303 | }
304 |
305 | pub fn delay(ms u32) {
306 | C.SDL_Delay(ms)
307 | }
308 |
309 | pub const (
310 | version = '0.2' // hack to avoid unused module warning in the main program
311 | )
312 |
--------------------------------------------------------------------------------
/examples/tvintris/tvintris.v:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
2 | // Use of this source code is governed by an MIT license
3 | // that can be found in the LICENSE file.
4 |
5 | // SDL2 port+wrapper, Twintris-like dual-game logic,
6 | // and more, by Nicolas Sauzede 2019.
7 |
8 | module main
9 |
10 | import time
11 | import os
12 | import math
13 | import nsauzede.vsdl2
14 | import nsauzede.vsdl2.image as img
15 |
16 | @[inline]
17 | fn sdl_fill_rect(s &vsdl2.Surface, r &vsdl2.Rect, c &vsdl2.Color) {
18 | vsdl2.fill_rect(s, r, c)
19 | }
20 |
21 | const (
22 | vsdl2_version = vsdl2.version
23 | title = 'tVintris'
24 | base = os.resource_abs_path('.')
25 | font_name = base + '/fonts/RobotoMono-Regular.ttf'
26 | music_name = base + '/sounds/TwintrisThosenine.mod'
27 | snd_block_name = base + '/sounds/block.wav'
28 | snd_line_name = base + '/sounds/single.wav'
29 | snd_double_name = base + '/sounds/triple.wav'
30 | vlogo = base + '/images/v-logo_30_30.png'
31 | block_size = 20 // pixels
32 | field_height = 20 // # of blocks
33 | field_width = 10
34 | tetro_size = 4
35 | win_width = block_size * field_width * 3
36 | win_height = block_size * field_height
37 | timer_period = 250 // ms
38 | text_size = 16
39 | audio_buffer_size = 1024
40 |
41 | p2fire = C.SDLK_l
42 | p2up = C.SDLK_UP
43 | p2down = C.SDLK_DOWN
44 | p2left = C.SDLK_LEFT
45 | p2right = C.SDLK_RIGHT
46 |
47 | p1fire = C.SDLK_s
48 | p1up = C.SDLK_w
49 | p1down = C.SDLK_x
50 | p1left = C.SDLK_a
51 | p1right = C.SDLK_d
52 |
53 | njoymax = 2
54 | // joystick name => enter your own device name
55 | joyp1name = 'Generic X-Box pad'
56 | // following are joystick button number
57 | jbp1fire = 1
58 | // following are joystick hat value
59 | jhp1up = 1
60 | jhp1down = 4
61 | jhp1left = 8
62 | jhp1right = 3
63 |
64 | // joystick name => enter your own device name
65 | joyp2name = 'RedOctane Guitar Hero X-plorer'
66 | // following are joystick button number
67 | jbp2fire = 0
68 | // following are joystick hat value
69 | jhp2up = 4
70 | jhp2down = 1
71 | jhp2left = 8
72 | jhp2right = 2
73 | )
74 |
75 | const (
76 | // Tetros' 4 possible states are encoded in binaries
77 | b_tetros = [
78 | // 0000 0
79 | // 0000 0
80 | // 0110 6
81 | // 0110 6
82 | [66, 66, 66, 66],
83 | // 0000 0
84 | // 0000 0
85 | // 0010 2
86 | // 0111 7
87 | [27, 131, 72, 232],
88 | // 0000 0
89 | // 0000 0
90 | // 0011 3
91 | // 0110 6
92 | [36, 231, 36, 231],
93 | // 0000 0
94 | // 0000 0
95 | // 0110 6
96 | // 0011 3
97 | [63, 132, 63, 132],
98 | // 0000 0
99 | // 0011 3
100 | // 0001 1
101 | // 0001 1
102 | [311, 17, 223, 74],
103 | // 0000 0
104 | // 0011 3
105 | // 0010 2
106 | // 0010 2
107 | [322, 71, 113, 47],
108 | // Special case since 15 can't be used
109 | // 1111
110 | [1111, 9, 1111, 9],
111 | ]
112 | // Each tetro has its unique color
113 | colors = [
114 | vsdl2.Color{u8(0), u8(0), u8(0), u8(0)}, // unused ?
115 | vsdl2.Color{u8(0), u8(0x62), u8(0xc0), u8(0)}, // quad : darkblue 0062c0
116 | vsdl2.Color{u8(0xca), u8(0x7d), u8(0x5f), u8(0)}, // tricorn : lightbrown ca7d5f
117 | vsdl2.Color{u8(0), u8(0xc1), u8(0xbf), u8(0)}, // short topright : lightblue 00c1bf
118 | vsdl2.Color{u8(0), u8(0xc1), u8(0), u8(0)}, // short topleft : lightgreen 00c100
119 | vsdl2.Color{u8(0xbf), u8(0xbe), u8(0), u8(0)}, // long topleft : yellowish bfbe00
120 | vsdl2.Color{u8(0xd1), u8(0), u8(0xbf), u8(0)}, // long topright : pink d100bf
121 | vsdl2.Color{u8(0xd1), u8(0), u8(0), u8(0)}, // longest : lightred d10000
122 | vsdl2.Color{u8(0), u8(170), u8(170), u8(0)}, // unused ?
123 | ]
124 | // Background color
125 | background_color = vsdl2.Color{u8(0), u8(0), u8(0), u8(0)}
126 | // background_color = vsdl2.Color{u8(255), u8(255), u8(255), u8(0)}
127 | // Foreground color
128 | foreground_color = vsdl2.Color{u8(0), u8(170), u8(170), u8(0)}
129 | // foreground_color = vsdl2.Color{u8(0), u8(0), u8(0), u8(0)}
130 | // Text color
131 | text_color = vsdl2.Color{u8(0xca), u8(0x7d), u8(0x5f), u8(0)}
132 | // text_color = vsdl2.Color{u8(0), u8(0), u8(0), u8(0)}
133 | )
134 |
135 | // TODO: type Tetro [tetro_size]struct{ x, y int }
136 | struct Block {
137 | mut:
138 | x int
139 | y int
140 | }
141 |
142 | enum GameState {
143 | paused
144 | running
145 | gameover
146 | }
147 |
148 | struct AudioContext {
149 | mut:
150 | music voidptr
151 | volume int
152 | waves [3]voidptr
153 | }
154 |
155 | struct SdlContext {
156 | pub mut:
157 | // VIDEO
158 | w int
159 | h int
160 | window voidptr
161 | renderer voidptr
162 | screen &vsdl2.Surface = voidptr(0)
163 | texture voidptr
164 | // AUDIO
165 | actx AudioContext
166 | // JOYSTICKS
167 | jnames [2]string
168 | jids [2]int
169 | // V logo
170 | v_logo &vsdl2.Surface = voidptr(0)
171 | tv_logo voidptr
172 | }
173 |
174 | struct Game {
175 | mut:
176 | // Score of the current game
177 | score int
178 | // Count consecutive lines for scoring
179 | lines int
180 | // State of the current game
181 | state GameState
182 | // X offset of the game display
183 | ofs_x int
184 | // keys
185 | k_fire int
186 | k_up int
187 | k_down int
188 | k_left int
189 | k_right int
190 | // joystick ID
191 | joy_id int
192 | // joystick buttons
193 | jb_fire int
194 | // joystick hat values
195 | jh_up int
196 | jh_down int
197 | jh_left int
198 | jh_right int
199 | // game rand seed
200 | seed int
201 | seed_ini int
202 | // Position of the current tetro
203 | pos_x int
204 | pos_y int
205 | // field[y][x] contains the color of the block with (x,y) coordinates
206 | // "-1" border is to avoid bounds checking.
207 | // -1 -1 -1 -1
208 | // -1 0 0 -1
209 | // -1 0 0 -1
210 | // -1 -1 -1 -1
211 | field [][]int
212 | // TODO: tetro Tetro
213 | tetro []Block
214 | // TODO: tetros_cache []Tetro
215 | tetros_cache []Block
216 | // Index of the current tetro. Refers to its color.
217 | tetro_idx int
218 | // Index of the next tetro. Refers to its color.
219 | tetro_next int
220 | // tetro stats : buckets of drawn tetros
221 | tetro_stats []int
222 | // total number of drawn tetros
223 | tetro_total int
224 | // Index of the rotation (0-3)
225 | rotation_idx int
226 | // SDL2 context for drawing
227 | sdl SdlContext
228 | // TTF context for font drawing
229 | font voidptr
230 | }
231 |
232 | fn rand_r(seed &int) int {
233 | unsafe {
234 | mut rs := seed
235 | ns := (*rs * 1103515245 + 12345)
236 | *rs = ns
237 | return ns & 0x7fffffff
238 | }
239 | }
240 |
241 | fn (mut sdl SdlContext) set_sdl_context(w int, h int, title string) {
242 | C.SDL_Init(C.SDL_INIT_VIDEO | C.SDL_INIT_AUDIO | C.SDL_INIT_JOYSTICK)
243 | C.atexit(C.SDL_Quit)
244 | C.TTF_Init()
245 | C.atexit(C.TTF_Quit)
246 | bpp := 32
247 | vsdl2.create_window_and_renderer(w, h, 0, &sdl.window, &sdl.renderer)
248 | // C.SDL_CreateWindowAndRenderer(w, h, 0, voidptr(&sdl.window), voidptr(&sdl.renderer))
249 | C.SDL_SetWindowTitle(sdl.window, title.str)
250 | sdl.w = w
251 | sdl.h = h
252 | sdl.screen = vsdl2.create_rgb_surface(0, w, h, bpp, 0x00FF0000, 0x0000FF00, 0x000000FF,
253 | 0xFF000000)
254 | sdl.texture = C.SDL_CreateTexture(sdl.renderer, C.SDL_PIXELFORMAT_ARGB8888, C.SDL_TEXTUREACCESS_STREAMING,
255 | w, h)
256 |
257 | C.Mix_Init(0)
258 | C.atexit(voidptr(C.Mix_Quit))
259 | if C.Mix_OpenAudio(48000, C.MIX_DEFAULT_FORMAT, 2, audio_buffer_size) < 0 {
260 | println("couldn't open audio")
261 | }
262 | println('opening music $music_name')
263 | sdl.actx.music = C.Mix_LoadMUS(music_name.str)
264 | sdl.actx.waves[0] = C.Mix_LoadWAV(snd_block_name.str)
265 | sdl.actx.waves[1] = C.Mix_LoadWAV(snd_line_name.str)
266 | sdl.actx.waves[2] = C.Mix_LoadWAV(snd_double_name.str)
267 | sdl.actx.volume = C.SDL_MIX_MAXVOLUME
268 | if C.Mix_PlayMusic(sdl.actx.music, 1) != -1 {
269 | C.Mix_VolumeMusic(sdl.actx.volume)
270 | }
271 | njoy := C.SDL_NumJoysticks()
272 | for i := 0; i < njoy; i++ {
273 | C.SDL_JoystickOpen(i)
274 | jn := unsafe { tos_clone(vsdl2.joystick_name_for_index(i)) }
275 | println('JOY NAME $jn')
276 | for j := 0; j < njoymax; j++ {
277 | if sdl.jnames[j] == jn {
278 | println('FOUND JOYSTICK $j $jn ID=$i')
279 | sdl.jids[j] = i
280 | }
281 | }
282 | }
283 | flags := C.IMG_INIT_PNG
284 | imgres := img.img_init(flags)
285 | if (imgres & flags) != flags {
286 | println('error initializing image library.')
287 | }
288 | println('opening logo $vlogo')
289 | sdl.v_logo = img.load(vlogo)
290 | if !isnil(sdl.v_logo) {
291 | // println('got v_logo=$sdl.v_logo')
292 | sdl.tv_logo = vsdl2.create_texture_from_surface(sdl.renderer, sdl.v_logo)
293 | // println('got tv_logo=$sdl.tv_logo')
294 | }
295 | C.SDL_JoystickEventState(C.SDL_ENABLE)
296 | }
297 |
298 | fn main() {
299 | println('tVintris -- tribute to venerable Twintris')
300 | mut game := &Game{
301 | font: 0
302 | }
303 | game.sdl.jnames[0] = joyp1name
304 | game.sdl.jnames[1] = joyp2name
305 | game.sdl.jids[0] = -1
306 | game.sdl.jids[1] = -1
307 | game.sdl.set_sdl_context(win_width, win_height, title)
308 | game.font = C.TTF_OpenFont(font_name.str, text_size)
309 | seed := time.now().unix
310 | mut game2 := &Game{
311 | font: 0
312 | }
313 | game2.sdl = game.sdl
314 | game2.font = game.font
315 |
316 | game.joy_id = game.sdl.jids[0]
317 | // println('JOY1 id=${game.joy_id}')
318 | game2.joy_id = game.sdl.jids[1]
319 | // println('JOY2 id=${game2.joy_id}')
320 |
321 | // delay uses milliseconds so 1000 ms / 30 frames (30fps) roughly = 33.3333 ms/frame
322 | time_per_frame := 1000.0 / 30.0
323 |
324 | game.k_fire = p1fire
325 | game.k_up = p1up
326 | game.k_down = p1down
327 | game.k_left = p1left
328 | game.k_right = p1right
329 | //
330 | game.jb_fire = jbp1fire
331 | game.jh_up = jhp1up
332 | game.jh_down = jhp1down
333 | game.jh_left = jhp1left
334 | game.jh_right = jhp1right
335 | //
336 | game.ofs_x = 0
337 | game.seed_ini = int(seed)
338 | game.init_game()
339 | game.state = .running
340 | go game.run() // Run the game loop in a new thread
341 |
342 | game2.k_fire = p2fire
343 | game2.k_up = p2up
344 | game2.k_down = p2down
345 | game2.k_left = p2left
346 | game2.k_right = p2right
347 | //
348 | game2.jb_fire = jbp2fire
349 | game2.jh_up = jhp2up
350 | game2.jh_down = jhp2down
351 | game2.jh_left = jhp2left
352 | game2.jh_right = jhp2right
353 | //
354 | game2.ofs_x = win_width * 2 / 3
355 | game2.seed_ini = int(seed)
356 | game2.init_game()
357 | game2.state = .running
358 | go game2.run() // Run the game loop in a new thread
359 |
360 | mut g := Game{
361 | font: 0
362 | }
363 | mut should_close := false
364 | mut total_frames := u32(0)
365 |
366 | for {
367 | total_frames++
368 | start_ticks := vsdl2.get_perf_counter()
369 |
370 | g1 := game
371 | g2 := game2
372 | // here we determine which game contains most recent state
373 | if g1.tetro_total > g.tetro_total {
374 | g = *g1
375 | }
376 | if g2.tetro_total > g.tetro_total {
377 | g = *g2
378 | }
379 | g.draw_begin()
380 |
381 | g1.draw_tetro()
382 | g1.draw_field()
383 |
384 | g2.draw_tetro()
385 | g2.draw_field()
386 |
387 | g.draw_middle()
388 |
389 | g1.draw_score()
390 | g2.draw_score()
391 |
392 | g.draw_stats()
393 |
394 | g.draw_v_logo()
395 | g.draw_end()
396 |
397 | // game.handle_events() // CRASHES if done in function ???
398 | ev := vsdl2.Event{}
399 | for 0 < vsdl2.poll_event(&ev) {
400 | match int(unsafe { ev.@type }) {
401 | C.SDL_QUIT {
402 | should_close = true
403 | }
404 | C.SDL_KEYDOWN {
405 | key := unsafe { ev.key.keysym.sym }
406 | if key == C.SDLK_ESCAPE {
407 | should_close = true
408 | break
409 | }
410 | game.handle_key(key)
411 | game2.handle_key(key)
412 | }
413 | C.SDL_JOYBUTTONDOWN {
414 | jb := int(unsafe { ev.jbutton.button })
415 | joyid := unsafe { ev.jbutton.which }
416 | // println('JOY BUTTON $jb $joyid')
417 | game.handle_jbutton(jb, joyid)
418 | game2.handle_jbutton(jb, joyid)
419 | }
420 | C.SDL_JOYHATMOTION {
421 | jh := int(unsafe { ev.jhat.hat })
422 | jv := int(unsafe { ev.jhat.value })
423 | joyid := unsafe { ev.jhat.which }
424 | // println('JOY HAT $jh $jv $joyid')
425 | game.handle_jhat(jh, jv, joyid)
426 | game2.handle_jhat(jh, jv, joyid)
427 | }
428 | else {}
429 | }
430 | }
431 | if should_close {
432 | break
433 | }
434 | end_ticks := vsdl2.get_perf_counter()
435 |
436 | elapsed_time := f64(end_ticks - start_ticks) / f64(vsdl2.get_perf_frequency())
437 | // current_fps := 1.0 / elapsed_time
438 |
439 | // should limit system to (1 / time_per_frame) fps
440 | vsdl2.delay(u32(math.floor(time_per_frame - elapsed_time)))
441 | }
442 | if game.font != voidptr(0) {
443 | C.TTF_CloseFont(game.font)
444 | }
445 | if game.sdl.actx.music != voidptr(0) {
446 | C.Mix_FreeMusic(game.sdl.actx.music)
447 | }
448 | C.Mix_CloseAudio()
449 | if game.sdl.actx.waves[0] != voidptr(0) {
450 | C.Mix_FreeChunk(game.sdl.actx.waves[0])
451 | }
452 | if game.sdl.actx.waves[1] != voidptr(0) {
453 | C.Mix_FreeChunk(game.sdl.actx.waves[1])
454 | }
455 | if game.sdl.actx.waves[2] != voidptr(0) {
456 | C.Mix_FreeChunk(game.sdl.actx.waves[2])
457 | }
458 | if !isnil(game.sdl.tv_logo) {
459 | vsdl2.destroy_texture(game.sdl.tv_logo)
460 | }
461 | if !isnil(game.sdl.v_logo) {
462 | vsdl2.free_surface(game.sdl.v_logo)
463 | }
464 | }
465 |
466 | enum Action {
467 | idle
468 | space
469 | fire
470 | }
471 |
472 | fn (mut game Game) handle_key(key int) {
473 | // global keys
474 | mut action := Action.idle
475 | match key {
476 | C.SDLK_SPACE { action = .space }
477 | game.k_fire { action = .fire }
478 | else {}
479 | }
480 |
481 | if action == .space {
482 | match game.state {
483 | .running {
484 | C.Mix_PauseMusic()
485 | game.state = .paused
486 | }
487 | .paused {
488 | C.Mix_ResumeMusic()
489 | game.state = .running
490 | }
491 | else {}
492 | }
493 | }
494 |
495 | if action == .fire {
496 | match game.state {
497 | .gameover {
498 | game.init_game()
499 | game.state = .running
500 | }
501 | else {}
502 | }
503 | }
504 | if game.state != .running {
505 | return
506 | }
507 | // keys while game is running
508 | match key {
509 | game.k_up { game.rotate_tetro() }
510 | game.k_left { game.move_right(-1) }
511 | game.k_right { game.move_right(1) }
512 | game.k_down { game.move_tetro() } // drop faster when the player presses
513 | else {}
514 | }
515 | }
516 |
517 | fn (mut game Game) handle_jbutton(jb int, joyid int) {
518 | if joyid != game.joy_id {
519 | return
520 | }
521 | // global buttons
522 | mut action := Action.idle
523 | match jb {
524 | game.jb_fire { action = .fire }
525 | else {}
526 | }
527 |
528 | if action == .fire {
529 | match game.state {
530 | .gameover {
531 | game.init_game()
532 | game.state = .running
533 | }
534 | else {}
535 | }
536 | }
537 | }
538 |
539 | fn (mut game Game) handle_jhat(jh int, jv int, joyid int) {
540 | if joyid != game.joy_id {
541 | return
542 | }
543 | if game.state != .running {
544 | return
545 | }
546 | // println('testing hat values.. joyid=$joyid jh=$jh jv=$jv')
547 | // hat values while game is running
548 | match jv {
549 | game.jh_up { game.rotate_tetro() }
550 | game.jh_left { game.move_right(-1) }
551 | game.jh_right { game.move_right(1) }
552 | game.jh_down { game.move_tetro() } // drop faster when the player presses
553 | else {}
554 | }
555 | }
556 |
557 | fn (mut g Game) init_game() {
558 | g.score = 0
559 | g.tetro_total = 0
560 | g.tetro_stats = [0, 0, 0, 0, 0, 0, 0]
561 | g.parse_tetros()
562 | g.seed = g.seed_ini
563 | g.generate_tetro()
564 | g.field = []
565 | // Generate the field, fill it with 0's, add -1's on each edge
566 | for i := 0; i < field_height + 2; i++ {
567 | mut row := [0].repeat(field_width + 2)
568 | row[0] = -1
569 | row[field_width + 1] = -1
570 | g.field << row
571 | }
572 | mut first_row := g.field[0]
573 | mut last_row := g.field[field_height + 1]
574 | for j := 0; j < field_width + 2; j++ {
575 | first_row[j] = -1
576 | last_row[j] = -1
577 | }
578 | }
579 |
580 | fn (mut g Game) parse_tetros() {
581 | for btetros in b_tetros {
582 | for btetro in btetros {
583 | for t in parse_binary_tetro(btetro) {
584 | g.tetros_cache << t
585 | }
586 | }
587 | }
588 | }
589 |
590 | fn (mut g Game) run() {
591 | for {
592 | if g.state == .running {
593 | g.move_tetro()
594 | n := g.delete_completed_lines()
595 | if n > 0 {
596 | g.lines += n
597 | } else {
598 | if g.lines > 0 {
599 | if g.lines > 1 {
600 | C.Mix_PlayChannel(0, g.sdl.actx.waves[2], 0)
601 | } else if g.lines == 1 {
602 | C.Mix_PlayChannel(0, g.sdl.actx.waves[1], 0)
603 | }
604 | g.score += 10 * g.lines * g.lines
605 | g.lines = 0
606 | }
607 | }
608 | }
609 | time.sleep(timer_period * time.millisecond) // medium delay between game step
610 | }
611 | }
612 |
613 | fn (mut game Game) rotate_tetro() {
614 | // Rotate the tetro
615 | old_rotation_idx := game.rotation_idx
616 | game.rotation_idx++
617 | if game.rotation_idx == tetro_size {
618 | game.rotation_idx = 0
619 | }
620 | game.get_tetro()
621 | if !game.move_right(0) {
622 | game.rotation_idx = old_rotation_idx
623 | game.get_tetro()
624 | }
625 | if game.pos_x < 0 {
626 | game.pos_x = 1
627 | }
628 | }
629 |
630 | fn (mut g Game) move_tetro() {
631 | // Check each block in current tetro
632 | for block in g.tetro {
633 | y := block.y + g.pos_y + 1
634 | x := block.x + g.pos_x
635 | // Reached the bottom of the screen or another block?
636 | // TODO: if g.field[y][x] != 0
637 | // if g.field[y][x] != 0 {
638 | row := g.field[y]
639 | if row[x] != 0 {
640 | // The new tetro has no space to drop => end of the game
641 | if g.pos_y < 2 {
642 | g.state = .gameover
643 | g.tetro_total = 0
644 | return
645 | }
646 | // Drop it and generate a new one
647 | g.drop_tetro()
648 | g.generate_tetro()
649 | C.Mix_PlayChannel(0, g.sdl.actx.waves[0], 0)
650 | return
651 | }
652 | }
653 | g.pos_y++
654 | }
655 |
656 | fn (mut g Game) move_right(dx int) bool {
657 | // Reached left/right edge or another tetro?
658 | for i := 0; i < tetro_size; i++ {
659 | tetro := g.tetro[i]
660 | y := tetro.y + g.pos_y
661 | x := tetro.x + g.pos_x + dx
662 | row := g.field[y]
663 | if row[x] != 0 {
664 | // Do not move
665 | return false
666 | }
667 | }
668 | g.pos_x += dx
669 | return true
670 | }
671 |
672 | fn (g &Game) delete_completed_lines() int {
673 | mut n := 0
674 | for y := field_height; y >= 1; y-- {
675 | n += g.delete_completed_line(y)
676 | }
677 | return n
678 | }
679 |
680 | fn (g &Game) delete_completed_line(y int) int {
681 | for x := 1; x <= field_width; x++ {
682 | f := g.field[y]
683 | if f[x] == 0 {
684 | return 0
685 | }
686 | }
687 | // Move everything down by 1 position
688 | for yy := y - 1; yy >= 1; yy-- {
689 | for x := 1; x <= field_width; x++ {
690 | mut a := g.field[yy + 1]
691 | b := g.field[yy]
692 | a[x] = b[x]
693 | }
694 | }
695 | return 1
696 | }
697 |
698 | // Draw a rand tetro index
699 | fn (mut g Game) rand_tetro() int {
700 | cur := g.tetro_next
701 | g.tetro_next = rand_r(&g.seed)
702 | g.tetro_next = g.tetro_next % b_tetros.len
703 | return cur
704 | }
705 |
706 | // Place a new tetro on top
707 | fn (mut g Game) generate_tetro() {
708 | g.pos_y = 0
709 | g.pos_x = field_width / 2 - tetro_size / 2
710 | g.tetro_idx = g.rand_tetro()
711 | // println('idx=${g.tetro_idx}')
712 | g.tetro_stats[g.tetro_idx] += 2 - 1
713 | g.tetro_total++
714 | g.rotation_idx = 0
715 | g.get_tetro()
716 | }
717 |
718 | // Get the right tetro from cache
719 | fn (mut g Game) get_tetro() {
720 | idx := g.tetro_idx * tetro_size * tetro_size + g.rotation_idx * tetro_size
721 | g.tetro = g.tetros_cache[idx..idx + tetro_size]
722 | }
723 |
724 | fn (g &Game) drop_tetro() {
725 | for i := 0; i < tetro_size; i++ {
726 | tetro := g.tetro[i]
727 | x := tetro.x + g.pos_x
728 | y := tetro.y + g.pos_y
729 | // Remember the color of each block
730 | // TODO: g.field[y][x] = g.tetro_idx + 1
731 | mut row := g.field[y]
732 | row[x] = g.tetro_idx + 1
733 | }
734 | }
735 |
736 | fn (g &Game) draw_tetro() {
737 | for i := 0; i < tetro_size; i++ {
738 | tetro := g.tetro[i]
739 | g.draw_block(g.pos_y + tetro.y, g.pos_x + tetro.x, g.tetro_idx + 1)
740 | }
741 | }
742 |
743 | fn (g &Game) draw_block(i int, j int, color_idx int) {
744 | rect := vsdl2.Rect{g.ofs_x + (j - 1) * block_size, (i - 1) * block_size, block_size - 1, block_size - 1}
745 | col := colors[color_idx]
746 | sdl_fill_rect(g.sdl.screen, &rect, &col)
747 | }
748 |
749 | fn (g &Game) draw_field() {
750 | for i := 1; i < field_height + 1; i++ {
751 | for j := 1; j < field_width + 1; j++ {
752 | f := g.field[i]
753 | if f[j] > 0 {
754 | g.draw_block(i, j, f[j])
755 | }
756 | }
757 | }
758 | }
759 |
760 | fn (g &Game) draw_v_logo() {
761 | if isnil(g.sdl.tv_logo) {
762 | return
763 | }
764 | texw := 0
765 | texh := 0
766 | C.SDL_QueryTexture(g.sdl.tv_logo, 0, 0, &texw, &texh)
767 | dstrect := vsdl2.Rect{(win_width / 2) - (texw / 2), 20, texw, texh}
768 | // Currently we can't seem to use vsdl2.render_copy when we need to pass a nil pointer (eg: srcrect to be NULL)
769 | // vsdl2.render_copy(g.sdl.renderer, tv_logo, 0, &dstrect)
770 | C.SDL_RenderCopy(g.sdl.renderer, g.sdl.tv_logo, voidptr(0), voidptr(&dstrect))
771 | }
772 |
773 | fn (g &Game) draw_text(x int, y int, text string, tcol vsdl2.Color) {
774 | tcol_ := C.SDL_Color{tcol.r, tcol.g, tcol.b, tcol.a}
775 | tsurf := C.TTF_RenderText_Solid(g.font, text.str, tcol_)
776 | ttext := C.SDL_CreateTextureFromSurface(g.sdl.renderer, tsurf)
777 | texw := 0
778 | texh := 0
779 | C.SDL_QueryTexture(ttext, 0, 0, &texw, &texh)
780 | dstrect := vsdl2.Rect{x, y, texw, texh}
781 | // vsdl2.render_copy(g.sdl.renderer, ttext, 0, &dstrect)
782 | C.SDL_RenderCopy(g.sdl.renderer, ttext, voidptr(0), voidptr(&dstrect))
783 | C.SDL_DestroyTexture(ttext)
784 | vsdl2.free_surface(tsurf)
785 | }
786 |
787 | @[inline]
788 | fn (g &Game) draw_ptext(x int, y int, text string, tcol vsdl2.Color) {
789 | g.draw_text(g.ofs_x + x, y, text, tcol)
790 | }
791 |
792 | @[live]
793 | fn (g &Game) draw_begin() {
794 | // println('about to clear')
795 | C.SDL_RenderClear(g.sdl.renderer)
796 | mut rect := vsdl2.Rect{0, 0, g.sdl.w, g.sdl.h}
797 | col := vsdl2.Color{u8(0), u8(0), u8(0), u8(0)}
798 | // sdl_fill_rect(g.sdl.screen, &rect, background_color)
799 | sdl_fill_rect(g.sdl.screen, &rect, col)
800 |
801 | rect = vsdl2.Rect{block_size * field_width + 2, 0, 2, g.sdl.h}
802 | sdl_fill_rect(g.sdl.screen, &rect, foreground_color)
803 | rect = vsdl2.Rect{win_width - block_size * field_width - 4, 0, 2, g.sdl.h}
804 | sdl_fill_rect(g.sdl.screen, &rect, foreground_color)
805 |
806 | mut idx := 0
807 | for st in g.tetro_stats {
808 | mut s := 10
809 | if g.tetro_total > 0 {
810 | s += 90 * st / g.tetro_total
811 | }
812 | w := block_size
813 | h := s * 4 * w / 100
814 | rect = vsdl2.Rect{(win_width - 7 * (w + 1)) / 2 + idx * (w + 1), win_height * 3 / 4 - h, w, h}
815 | sdl_fill_rect(g.sdl.screen, &rect, colors[idx + 1])
816 | idx++
817 | }
818 | }
819 |
820 | fn (g &Game) draw_middle() {
821 | C.SDL_UpdateTexture(g.sdl.texture, 0, g.sdl.screen.pixels, g.sdl.screen.pitch)
822 | // vsdl2.render_copy(g.sdl.renderer, g.sdl.texture, voidptr(0), voidptr(0))
823 | C.SDL_RenderCopy(g.sdl.renderer, g.sdl.texture, voidptr(0), voidptr(0))
824 | }
825 |
826 | fn (g &Game) draw_score() {
827 | if g.font != voidptr(0) {
828 | g.draw_ptext(1, 2, 'score: ' + g.score.str() + ' nxt=' + g.tetro_next.str(), text_color)
829 | if g.state == .gameover {
830 | g.draw_ptext(1, win_height / 2 + 0 * text_size, 'Game Over', text_color)
831 | g.draw_ptext(1, win_height / 2 + 2 * text_size, 'FIRE to restart', text_color)
832 | } else if g.state == .paused {
833 | g.draw_ptext(1, win_height / 2 + 0 * text_size, 'Game Paused', text_color)
834 | g.draw_ptext(1, win_height / 2 + 2 * text_size, 'SPACE to resume', text_color)
835 | }
836 | }
837 | }
838 |
839 | fn (g &Game) draw_stats() {
840 | if g.font != voidptr(0) {
841 | g.draw_text(win_width / 3 + 10, win_height * 3 / 4 + 0 * text_size, 'stats: ' +
842 | g.tetro_total.str() + ' tetros', text_color)
843 | mut stats := ''
844 | for st in g.tetro_stats {
845 | mut s := 0
846 | if g.tetro_total > 0 {
847 | s = 100 * st / g.tetro_total
848 | }
849 | stats += ' '
850 | stats += s.str()
851 | }
852 | g.draw_text(win_width / 3 - 8, win_height * 3 / 4 + 2 * text_size, stats, text_color)
853 | }
854 | }
855 |
856 | fn (g &Game) draw_end() {
857 | C.SDL_RenderPresent(g.sdl.renderer)
858 | }
859 |
860 | fn parse_binary_tetro(t_ int) []Block {
861 | mut t := t_
862 | mut res := [Block{}].repeat(4)
863 | mut cnt := 0
864 | horizontal := t == 9 // special case for the horizontal line
865 | for i := 0; i <= 3; i++ {
866 | // Get ith digit of t
867 | p := int(math.pow(10, 3 - i))
868 | mut digit := int(t / p)
869 | t %= p
870 | // Convert the digit to binary
871 | for j := 3; j >= 0; j-- {
872 | bin := digit % 2
873 | digit /= 2
874 | if bin == 1 || (horizontal && i == tetro_size - 1) {
875 | res[cnt].x = j
876 | res[cnt].y = i
877 | cnt++
878 | }
879 | }
880 | }
881 | return res
882 | }
883 |
--------------------------------------------------------------------------------