├── .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 | ![tVintris screenshot](https://github.com/nsauzede/vsdl2/blob/master/examples/tvintris/tvintris.png) 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 | --------------------------------------------------------------------------------