diff --git a/vlib/builtin/cfns.v b/vlib/builtin/cfns.v index e2203a4db1..9597520fde 100644 --- a/vlib/builtin/cfns.v +++ b/vlib/builtin/cfns.v @@ -110,7 +110,7 @@ fn C.RegOpenKeyExW(hKey voidptr, lpSubKey &u16, ulOptions u32, samDesired u32, p fn C.RegCloseKey() fn C.RegQueryValueEx() voidptr fn C.RemoveDirectory() int -fn C.GetStdHandle() int +fn C.GetStdHandle() voidptr fn C.SetConsoleMode() fn C.GetConsoleMode() int fn C._putws() @@ -131,7 +131,7 @@ fn C._putenv() int fn C._waccess() int fn C._wremove() fn C.ReadConsole() -fn C.fgetws() int +fn C.fgetws() voidptr fn C.GetModuleFileName() int fn C._wchdir() fn C._wgetcwd() int diff --git a/vlib/os/os.v b/vlib/os/os.v index 280923f054..c1abee636e 100644 --- a/vlib/os/os.v +++ b/vlib/os/os.v @@ -838,7 +838,7 @@ pub fn is_dir(path string) bool { $if windows { _path := path.replace('/', '\\') attr := C.GetFileAttributesW(_path.to_wide()) - if int(attr) == C.INVALID_FILE_ATTRIBUTES { + if attr == u32(C.INVALID_FILE_ATTRIBUTES) { return false } if int(attr) & C.FILE_ATTRIBUTE_DIRECTORY != 0 { diff --git a/vlib/sdl/LICENSE b/vlib/sdl/LICENSE new file mode 100644 index 0000000000..6ece64cce4 --- /dev/null +++ b/vlib/sdl/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Nicolas Sauzede + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vlib/sdl/README.md b/vlib/sdl/README.md new file mode 100644 index 0000000000..3f50fed253 --- /dev/null +++ b/vlib/sdl/README.md @@ -0,0 +1,54 @@ +# sdl +SDL2 V module -- libSDL2 wrapper + +Current APIs available/tested in examples : +- basic graphics (2D drawing) +- [Image](image/README.md) +- TTF font (text rendering) +- input handling (keyboard/joystick events) +- sounds (WAV mixing) +- music (MOD mixing) +- more to come.. (networking ?) + +# Support +sdl is supported on : +- linux (major distros) +- MacOS (brew) +- windows (msys2/mingw64 only for now) + +# Examples + +[tVintris](examples/tvintris) + +![tVintris screenshot](examples/tvintris/images/tvintris.png) + +You can run the tVintris example from the V root folder like this : +``` +v run vlib/sdl/examples/tvintris/tvintris.v +``` + +# Dependencies + +## Linux +Fedora : +`$ sudo dnf install SDL2-devel SDL2_ttf-devel SDL2_mixer-devel SDL2_image-devel` + +Ubuntu : +`$ sudo apt install libsdl2-ttf-dev libsdl2-mixer-dev libsdl2-image-dev` + +ClearLinux : +`$ sudo swupd bundle-add devpkg-SDL2_ttf devpkg-SDL2_mixer devpkg-SDL2_image` + +## MacOS +Brew : +`$ brew install sdl2 sdl2_gfx sdl2_ttf sdl2_mixer sdl2_image sdl2_net` + +## Windows +Windows/MSYS2 : +`$ pacman -S mingw-w64-x86_64-SDL2_ttf mingw-w64-x86_64-SDL2_mixer mingw-w64-x86_64-SDL2_image` + +# Contributions + +nsauzede +spytheman +adlesh diff --git a/vlib/sdl/examples/tvintris/README.md b/vlib/sdl/examples/tvintris/README.md new file mode 100644 index 0000000000..4f23877858 --- /dev/null +++ b/vlib/sdl/examples/tvintris/README.md @@ -0,0 +1,17 @@ +# tVintris + +tvintris.v is a dual-player (local) version based on original source from tetris example by Alex M. +It is largely inspired by ancient game Twintris. +-uses vlib sdl module + +![tVintris screenshot](images/tvintris.png) + +# how to run tVintris + +`$ v run .` + +# Credits + +Colors, Music and Sounds inspired/ripped from amiga title Twintris (1990 nostalgia !) +- Graphician : Svein Berge +- Musician : Tor Bernhard Gausen (Walkman/Cryptoburners) diff --git a/vlib/sdl/examples/tvintris/fonts/RobotoMono-Regular.ttf b/vlib/sdl/examples/tvintris/fonts/RobotoMono-Regular.ttf new file mode 100644 index 0000000000..b158a334eb Binary files /dev/null and b/vlib/sdl/examples/tvintris/fonts/RobotoMono-Regular.ttf differ diff --git a/vlib/sdl/examples/tvintris/images/tvintris.png b/vlib/sdl/examples/tvintris/images/tvintris.png new file mode 100644 index 0000000000..31bc7f58dc Binary files /dev/null and b/vlib/sdl/examples/tvintris/images/tvintris.png differ diff --git a/vlib/sdl/examples/tvintris/images/v-logo_30_30.png b/vlib/sdl/examples/tvintris/images/v-logo_30_30.png new file mode 100644 index 0000000000..ae8c476c93 Binary files /dev/null and b/vlib/sdl/examples/tvintris/images/v-logo_30_30.png differ diff --git a/vlib/sdl/examples/tvintris/sounds/TwintrisThosenine.mod b/vlib/sdl/examples/tvintris/sounds/TwintrisThosenine.mod new file mode 100644 index 0000000000..4fbceed7b8 Binary files /dev/null and b/vlib/sdl/examples/tvintris/sounds/TwintrisThosenine.mod differ diff --git a/vlib/sdl/examples/tvintris/sounds/block.wav b/vlib/sdl/examples/tvintris/sounds/block.wav new file mode 100644 index 0000000000..e66eb0689d Binary files /dev/null and b/vlib/sdl/examples/tvintris/sounds/block.wav differ diff --git a/vlib/sdl/examples/tvintris/sounds/single.wav b/vlib/sdl/examples/tvintris/sounds/single.wav new file mode 100644 index 0000000000..bc2fd5c078 Binary files /dev/null and b/vlib/sdl/examples/tvintris/sounds/single.wav differ diff --git a/vlib/sdl/examples/tvintris/sounds/triple.wav b/vlib/sdl/examples/tvintris/sounds/triple.wav new file mode 100644 index 0000000000..d501c4218a Binary files /dev/null and b/vlib/sdl/examples/tvintris/sounds/triple.wav differ diff --git a/vlib/sdl/examples/tvintris/tvintris.v b/vlib/sdl/examples/tvintris/tvintris.v new file mode 100644 index 0000000000..5f6b7cfbca --- /dev/null +++ b/vlib/sdl/examples/tvintris/tvintris.v @@ -0,0 +1,843 @@ +// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +// SDL2 port+wrapper, Twintris-like dual-game logic, +// and more, by Nicolas Sauzede 2019. + +module main + +import rand +import time +import os +import math +import sdl +import sdl.image as img +[inline] fn sdl_fill_rect(s &vsdl2.Surface,r &vsdl2.Rect,c &vsdl2.Color){vsdl2.fill_rect(s,r,c)} + +const ( + Title = 'tVintris' + BASE = os.dir( os.realpath( os.executable() ) ) + FontName = BASE + '/fonts/RobotoMono-Regular.ttf' + MusicName = BASE + '/sounds/TwintrisThosenine.mod' + SndBlockName = BASE + '/sounds/block.wav' + SndLineName = BASE + '/sounds/single.wav' + SndDoubleName = BASE + '/sounds/triple.wav' + VLogo = BASE + '/images/v-logo_30_30.png' + BlockSize = 20 // pixels + FieldHeight = 20 // # of blocks + FieldWidth = 10 + TetroSize = 4 + WinWidth = BlockSize * FieldWidth * 3 + WinHeight = BlockSize * FieldHeight + TimerPeriod = 250 // ms + TextSize = 16 + AudioBufSize = 1024 + + P2FIRE = C.SDLK_l + P2UP = C.SDLK_UP + P2DOWN = C.SDLK_DOWN + P2LEFT = C.SDLK_LEFT + P2RIGHT = C.SDLK_RIGHT + + P1FIRE = C.SDLK_s + P1UP = C.SDLK_w + P1DOWN = C.SDLK_x + P1LEFT = C.SDLK_a + P1RIGHT = C.SDLK_d + + NJOYMAX = 2 + // joystick name => enter your own device name + JOYP1NAME = 'Generic X-Box pad' + // following are joystick button number + JBP1FIRE = 1 + // following are joystick hat value + JHP1UP = 1 + JHP1DOWN = 4 + JHP1LEFT = 8 + JHP1RIGHT = 3 + + // joystick name => enter your own device name + JOYP2NAME = 'RedOctane Guitar Hero X-plorer' + // following are joystick button number + JBP2FIRE = 0 + // following are joystick hat value + JHP2UP = 4 + JHP2DOWN = 1 + JHP2LEFT = 8 + JHP2RIGHT = 2 +) + +const ( + // Tetros' 4 possible states are encoded in binaries + BTetros = [ + // 0000 0 + // 0000 0 + // 0110 6 + // 0110 6 + [66, 66, 66, 66], + // 0000 0 + // 0000 0 + // 0010 2 + // 0111 7 + [27, 131, 72, 232], + // 0000 0 + // 0000 0 + // 0011 3 + // 0110 6 + [36, 231, 36, 231], + // 0000 0 + // 0000 0 + // 0110 6 + // 0011 3 + [63, 132, 63, 132], + // 0000 0 + // 0011 3 + // 0001 1 + // 0001 1 + [311, 17, 223, 74], + // 0000 0 + // 0011 3 + // 0010 2 + // 0010 2 + [322, 71, 113, 47], + // Special case since 15 can't be used + // 1111 + [1111, 9, 1111, 9], + ] + // Each tetro has its unique color + Colors = [ + vsdl2.Color{byte(0), byte(0), byte(0), byte(0)}, // unused ? + vsdl2.Color{byte(0), byte(0x62), byte(0xc0), byte(0)}, // quad : darkblue 0062c0 + vsdl2.Color{byte(0xca), byte(0x7d), byte(0x5f), byte(0)}, // tricorn : lightbrown ca7d5f + vsdl2.Color{byte(0), byte(0xc1), byte(0xbf), byte(0)}, // short topright : lightblue 00c1bf + vsdl2.Color{byte(0), byte(0xc1), byte(0), byte(0)}, // short topleft : lightgreen 00c100 + vsdl2.Color{byte(0xbf), byte(0xbe), byte(0), byte(0)}, // long topleft : yellowish bfbe00 + vsdl2.Color{byte(0xd1), byte(0), byte(0xbf), byte(0)}, // long topright : pink d100bf + vsdl2.Color{byte(0xd1), byte(0), byte(0), byte(0)}, // longest : lightred d10000 + vsdl2.Color{byte(0), byte(170), byte(170), byte(0)}, // unused ? + ] + // Background color + BackgroundColor = vsdl2.Color{byte(0), byte(0), byte(0), byte(0)} +// BackgroundColor = vsdl2.Color{byte(255), byte(255), byte(255), byte(0)} + // Foreground color + ForegroundColor = vsdl2.Color{byte(0), byte(170), byte(170), byte(0)} +// ForegroundColor = vsdl2.Color{byte(0), byte(0), byte(0), byte(0)} + // Text color + TextColor = vsdl2.Color{byte(0xca), byte(0x7d), byte(0x5f), byte(0)} +// TextColor = vsdl2.Color{byte(0), byte(0), byte(0), byte(0)} +) + +// TODO: type Tetro [TetroSize]struct{ x, y int } +struct Block { + mut: + x int + y int +} + +enum GameState { + paused running gameover +} + +struct AudioContext { +mut: + music voidptr + volume int + waves [3]voidptr +} + +struct SdlContext { +pub: +mut: +// VIDEO + w int + h int + window voidptr + renderer voidptr + screen &vsdl2.Surface + texture voidptr +// AUDIO + actx AudioContext +// JOYSTICKS + jnames [2]string + jids [2]int +// V logo + v_logo &vsdl2.Surface + tv_logo voidptr +} + +struct Game { +mut: + // Score of the current game + score int + // Count consecutive lines for scoring + lines int + // State of the current game + state GameState + // X offset of the game display + ofs_x int + // keys + k_fire int + k_up int + k_down int + k_left int + k_right int + // joystick ID + joy_id int + // joystick buttons + jb_fire int + // joystick hat values + jh_up int + jh_down int + jh_left int + jh_right int + // game rand seed + seed int + seed_ini int + // Position of the current tetro + pos_x int + pos_y int + // field[y][x] contains the color of the block with (x,y) coordinates + // "-1" border is to avoid bounds checking. + // -1 -1 -1 -1 + // -1 0 0 -1 + // -1 0 0 -1 + // -1 -1 -1 -1 + field [][]int + // TODO: tetro Tetro + tetro []Block + // TODO: tetros_cache []Tetro + tetros_cache []Block + // Index of the current tetro. Refers to its color. + tetro_idx int + // Index of the next tetro. Refers to its color. + tetro_next int + // tetro stats : buckets of drawn tetros + tetro_stats []int + // total number of drawn tetros + tetro_total int + // Index of the rotation (0-3) + rotation_idx int + // SDL2 context for drawing + sdl SdlContext + // TTF context for font drawing + font voidptr +} + +fn (sdl mut SdlContext) set_sdl_context(w int, h int, title string) { + C.SDL_Init(C.SDL_INIT_VIDEO | C.SDL_INIT_AUDIO | C.SDL_INIT_JOYSTICK) + C.atexit(C.SDL_Quit) + C.TTF_Init() + C.atexit(C.TTF_Quit) + bpp := 32 + vsdl2.create_window_and_renderer(w, h, 0, &sdl.window, &sdl.renderer) +// C.SDL_CreateWindowAndRenderer(w, h, 0, voidptr(&sdl.window), voidptr(&sdl.renderer)) + C.SDL_SetWindowTitle(sdl.window, title.str) + sdl.w = w + sdl.h = h + sdl.screen = vsdl2.create_rgb_surface(0, w, h, bpp, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000) + sdl.texture = C.SDL_CreateTexture(sdl.renderer, C.SDL_PIXELFORMAT_ARGB8888, C.SDL_TEXTUREACCESS_STREAMING, w, h) + + C.Mix_Init(0) + C.atexit(C.Mix_Quit) + if C.Mix_OpenAudio(48000,C.MIX_DEFAULT_FORMAT,2,AudioBufSize) < 0 { + println('couldn\'t open audio') + } + println('opening music $MusicName') + sdl.actx.music = C.Mix_LoadMUS(MusicName.str) + sdl.actx.waves[0] = C.Mix_LoadWAV(SndBlockName.str) + sdl.actx.waves[1] = C.Mix_LoadWAV(SndLineName.str) + sdl.actx.waves[2] = C.Mix_LoadWAV(SndDoubleName.str) + sdl.actx.volume = C.SDL_MIX_MAXVOLUME + if C.Mix_PlayMusic(sdl.actx.music, 1) != -1 { + C.Mix_VolumeMusic(sdl.actx.volume) + } + njoy := C.SDL_NumJoysticks() + for i := 0; i < njoy; i++ { + C.SDL_JoystickOpen(i) + jn := tos_clone(vsdl2.joystick_name_for_index(i)) + println('JOY NAME $jn') + for j := 0; j < NJOYMAX; j++ { + if sdl.jnames[j] == jn { + println('FOUND JOYSTICK $j $jn ID=$i') + sdl.jids[j] = i + } + } + } + flags := C.IMG_INIT_PNG + imgres := img.img_init(flags) + if ((imgres & flags) != flags) { + println('error initializing image library.') + } + println('opening logo $VLogo') + sdl.v_logo = img.load(VLogo) + if !isnil(sdl.v_logo) { +// println('got v_logo=$sdl.v_logo') + sdl.tv_logo = vsdl2.create_texture_from_surface(sdl.renderer, sdl.v_logo) +// println('got tv_logo=$sdl.tv_logo') + } + C.SDL_JoystickEventState(C.SDL_ENABLE) +} + +fn main() { + println('tVintris -- tribute to venerable Twintris') + mut game := &Game{ font: 0 } + game.sdl.jnames[0] = JOYP1NAME + game.sdl.jnames[1] = JOYP2NAME + game.sdl.jids[0] = -1 + game.sdl.jids[1] = -1 + game.sdl.set_sdl_context(WinWidth, WinHeight, Title) + game.font = C.TTF_OpenFont(FontName.str, TextSize) + seed := time.now().uni + mut game2 := &Game{ font: 0 } + game2.sdl = game.sdl + game2.font = game.font + + game.joy_id = game.sdl.jids[0] +// println('JOY1 id=${game.joy_id}') + game2.joy_id = game.sdl.jids[1] +// println('JOY2 id=${game2.joy_id}') + + // delay uses milliseconds so 1000 ms / 30 frames (30fps) roughly = 33.3333 ms/frame + time_per_frame := 1000.0 / 30.0 + + game.k_fire = P1FIRE + game.k_up = P1UP + game.k_down = P1DOWN + game.k_left = P1LEFT + game.k_right = P1RIGHT + game.jb_fire = JBP1FIRE + game.jh_up = JHP1UP + game.jh_down = JHP1DOWN + game.jh_left = JHP1LEFT + game.jh_right = JHP1RIGHT + game.ofs_x = 0 + game.seed_ini = seed + game.init_game() + game.state = .running + go game.run() // Run the game loop in a new thread + + game2.k_fire = P2FIRE + game2.k_up = P2UP + game2.k_down = P2DOWN + game2.k_left = P2LEFT + game2.k_right = P2RIGHT + game2.jb_fire = JBP2FIRE + game2.jh_up = JHP2UP + game2.jh_down = JHP2DOWN + game2.jh_left = JHP2LEFT + game2.jh_right = JHP2RIGHT + game2.ofs_x = WinWidth * 2 / 3 + game2.seed_ini = seed + game2.init_game() + game2.state = .running + go game2.run() // Run the game loop in a new thread + + mut g := Game{ font: 0 } + mut should_close := false + mut total_frame_ticks := u64(0) + mut total_frames := u32(0) + + for { + total_frames += 1 + start_ticks := vsdl2.get_perf_counter() + + g1 := game + g2 := game2 + // here we determine which game contains most recent state + if g1.tetro_total > g.tetro_total { + g = *g1 + } + if g2.tetro_total > g.tetro_total { + g = *g2 + } + g.draw_begin() + + g1.draw_tetro() + g1.draw_field() + + g2.draw_tetro() + g2.draw_field() + + g.draw_middle() + + g1.draw_score() + g2.draw_score() + + g.draw_stats() + + g.draw_v_logo() + g.draw_end() + +// game.handle_events() // CRASHES if done in function ??? + ev := vsdl2.Event{} + for 0 < vsdl2.poll_event(&ev) { + match int(ev._type) { + C.SDL_QUIT { should_close = true } + C.SDL_KEYDOWN { + key := int(ev.key.keysym.sym) + if key == C.SDLK_ESCAPE { + should_close = true + break + } + game.handle_key(key) + game2.handle_key(key) + } + C.SDL_JOYBUTTONDOWN { + jb := int(ev.jbutton.button) + joyid := int(ev.jbutton.which) +// println('JOY BUTTON $jb $joyid') + game.handle_jbutton(jb, joyid) + game2.handle_jbutton(jb, joyid) + } + C.SDL_JOYHATMOTION { + jh := int(ev.jhat.hat) + jv := int(ev.jhat.value) + joyid := int(ev.jhat.which) +// println('JOY HAT $jh $jv $joyid') + game.handle_jhat(jh, jv, joyid) + game2.handle_jhat(jh, jv, joyid) + } + } + } + if should_close { + break + } + end_ticks := vsdl2.get_perf_counter() + + total_frame_ticks += end_ticks-start_ticks + elapsed_time := f64(end_ticks - start_ticks) / f64(vsdl2.get_perf_frequency()) + // current_fps := 1.0 / elapsed_time + + // should limit system to (1 / time_per_frame) fps + vsdl2.delay(u32(math.floor(time_per_frame - elapsed_time))) + } + if game.font != voidptr(0) { + C.TTF_CloseFont(game.font) + } + if game.sdl.actx.music != voidptr(0) { + C.Mix_FreeMusic(game.sdl.actx.music) + } + C.Mix_CloseAudio() + if game.sdl.actx.waves[0] != voidptr(0) { + C.Mix_FreeChunk(game.sdl.actx.waves[0]) + } + if game.sdl.actx.waves[1] != voidptr(0) { + C.Mix_FreeChunk(game.sdl.actx.waves[1]) + } + if game.sdl.actx.waves[2] != voidptr(0) { + C.Mix_FreeChunk(game.sdl.actx.waves[2]) + } + if !isnil(game.sdl.tv_logo) { + vsdl2.destroy_texture(game.sdl.tv_logo) + } + if !isnil(game.sdl.v_logo) { + vsdl2.free_surface(game.sdl.v_logo) + } +} + +enum Action { + idle space fire +} +fn (game mut Game) handle_key(key int) { + // global keys + mut action := Action(.idle) + match key { + C.SDLK_SPACE { action = .space } + game.k_fire { action = .fire } + } + + if action == .space { + match game.state { + .running { + C.Mix_PauseMusic() + game.state = .paused + } + .paused { + C.Mix_ResumeMusic() + game.state = .running + } + } + } + + if action == .fire { + match game.state { + .gameover { + game.init_game() + game.state = .running + } + } + } + if game.state != .running { return } + // keys while game is running + match key { + game.k_up { game.rotate_tetro() } + game.k_left { game.move_right(-1) } + game.k_right { game.move_right(1) } + game.k_down { game.move_tetro() } // drop faster when the player presses + } +} + +fn (game mut Game) handle_jbutton(jb int, joyid int) { + if joyid != game.joy_id { + return + } + // global buttons + mut action := Action(.idle) + match jb { + game.jb_fire { action = .fire } + } + + if action == .fire { + match game.state { + .gameover { + game.init_game() + game.state = .running + } + } + } +} + +fn (game mut Game) handle_jhat(jh int, jv int, joyid int) { + if joyid != game.joy_id { + return + } + if game.state != .running { return } +// println('testing hat values.. joyid=$joyid jh=$jh jv=$jv') + // hat values while game is running + match jv { + game.jh_up { game.rotate_tetro() } + game.jh_left { game.move_right(-1) } + game.jh_right { game.move_right(1) } + game.jh_down { game.move_tetro() } // drop faster when the player presses + } +} + +fn (g mut Game) init_game() { + g.score = 0 + g.tetro_total = 0 + g.tetro_stats = [0, 0, 0, 0, 0, 0, 0] + g.parse_tetros() + g.seed = g.seed_ini + g.generate_tetro() + g.field = [] + // Generate the field, fill it with 0's, add -1's on each edge + for i := 0; i < FieldHeight + 2; i++ { + mut row := [0].repeat(FieldWidth + 2) + row[0] = - 1 + row[FieldWidth + 1] = - 1 + g.field << row + } + mut first_row := g.field[0] + mut last_row := g.field[FieldHeight + 1] + for j := 0; j < FieldWidth + 2; j++ { + first_row[j] = - 1 + last_row[j] = - 1 + } +} + +fn (g mut Game) parse_tetros() { + for b_tetros in BTetros { + for b_tetro in b_tetros { + for t in parse_binary_tetro(b_tetro) { + g.tetros_cache << t + } + } + } +} + +fn (g mut Game) run() { + for { + if g.state == .running { + g.move_tetro() + n := g.delete_completed_lines() + if n > 0 { + g.lines += n + } else { + if g.lines > 0 { + if g.lines > 1 { + C.Mix_PlayChannel(0, g.sdl.actx.waves[2], 0) + } else if g.lines == 1 { + C.Mix_PlayChannel(0, g.sdl.actx.waves[1], 0) + } + g.score += 10 * g.lines * g.lines + g.lines = 0 + } + } + } + time.sleep_ms(TimerPeriod) // medium delay between game step + } +} + +fn (game mut Game) rotate_tetro() { + // Rotate the tetro + old_rotation_idx := game.rotation_idx + game.rotation_idx++ + if game.rotation_idx == TetroSize { + game.rotation_idx = 0 + } + game.get_tetro() + if !game.move_right(0) { + game.rotation_idx = old_rotation_idx + game.get_tetro() + } + if game.pos_x < 0 { + game.pos_x = 1 + } +} + +fn (g mut Game) move_tetro() { + // Check each block in current tetro + for block in g.tetro { + y := block.y + g.pos_y + 1 + x := block.x + g.pos_x + // Reached the bottom of the screen or another block? + // TODO: if g.field[y][x] != 0 + //if g.field[y][x] != 0 { + row := g.field[y] + if row[x] != 0 { + // The new tetro has no space to drop => end of the game + if g.pos_y < 2 { + g.state = .gameover + g.tetro_total = 0 + return + } + // Drop it and generate a new one + g.drop_tetro() + g.generate_tetro() + C.Mix_PlayChannel(0, g.sdl.actx.waves[0], 0) + return + } + } + g.pos_y++ +} + +fn (g mut Game) move_right(dx int) bool { + // Reached left/right edge or another tetro? + for i := 0; i < TetroSize; i++ { + tetro := g.tetro[i] + y := tetro.y + g.pos_y + x := tetro.x + g.pos_x + dx + row := g.field[y] + if row[x] != 0 { + // Do not move + return false + } + } + g.pos_x += dx + return true +} + +fn (g &Game) delete_completed_lines() int { + mut n := 0 + for y := FieldHeight; y >= 1; y-- { + n += g.delete_completed_line(y) + } + return n +} + +fn (g &Game) delete_completed_line(y int) int { + for x := 1; x <= FieldWidth; x++ { + f := g.field[y] + if f[x] == 0 { + return 0 + } + } + // Move everything down by 1 position + for yy := y - 1; yy >= 1; yy-- { + for x := 1; x <= FieldWidth; x++ { + mut a := g.field[yy + 1] + b := g.field[yy] + a[x] = b[x] + } + } + return 1 +} + +// Draw a rand tetro index +fn (g mut Game) rand_tetro() int { + cur := g.tetro_next + g.tetro_next = rand.rand_r(&g.seed) + g.tetro_next = g.tetro_next % BTetros.len + return cur +} + +// Place a new tetro on top +fn (g mut Game) generate_tetro() { + g.pos_y = 0 + g.pos_x = FieldWidth / 2 - TetroSize / 2 + g.tetro_idx = g.rand_tetro() +// println('idx=${g.tetro_idx}') + g.tetro_stats[g.tetro_idx] += 1 + g.tetro_total++ + g.rotation_idx = 0 + g.get_tetro() +} + +// Get the right tetro from cache +fn (g mut Game) get_tetro() { + idx := g.tetro_idx * TetroSize * TetroSize + g.rotation_idx * TetroSize + g.tetro = g.tetros_cache[idx .. idx + TetroSize] +} + +fn (g &Game) drop_tetro() { + for i := 0; i < TetroSize; i++ { + tetro := g.tetro[i] + x := tetro.x + g.pos_x + y := tetro.y + g.pos_y + // Remember the color of each block + // TODO: g.field[y][x] = g.tetro_idx + 1 + mut row := g.field[y] + row[x] = g.tetro_idx + 1 + } +} + +fn (g &Game) draw_tetro() { + for i := 0; i < TetroSize; i++ { + tetro := g.tetro[i] + g.draw_block(g.pos_y + tetro.y, g.pos_x + tetro.x, g.tetro_idx + 1) + } +} + +fn (g &Game) draw_block(i, j, color_idx int) { + rect := vsdl2.Rect {g.ofs_x + (j - 1) * BlockSize, (i - 1) * BlockSize, + BlockSize - 1, BlockSize - 1} + col := Colors[color_idx] + sdl_fill_rect(g.sdl.screen, &rect, &col) +} + +fn (g &Game) draw_field() { + for i := 1; i < FieldHeight + 1; i++ { + for j := 1; j < FieldWidth + 1; j++ { + f := g.field[i] + if f[j] > 0 { + g.draw_block(i, j, f[j]) + } + } + } +} + +fn (g &Game) draw_v_logo() { + if isnil(g.sdl.tv_logo) { + return + } + texw := 0 + texh := 0 + C.SDL_QueryTexture(g.sdl.tv_logo, 0, 0, &texw, &texh) + dstrect := vsdl2.Rect { (WinWidth / 2) - (texw / 2), 20, texw, texh } + // Currently we can't seem to use vsdl2.render_copy when we need to pass a nil pointer (eg: srcrect to be NULL) +// vsdl2.render_copy(g.sdl.renderer, tv_logo, 0, &dstrect) + C.SDL_RenderCopy(g.sdl.renderer, g.sdl.tv_logo, voidptr(0), voidptr(&dstrect)) +} + +fn (g &Game) draw_text(x int, y int, text string, tcol vsdl2.Color) { + _tcol := C.SDL_Color{tcol.r, tcol.g, tcol.b, tcol.a} + tsurf := C.TTF_RenderText_Solid(g.font, text.str, _tcol) + ttext := C.SDL_CreateTextureFromSurface(g.sdl.renderer, tsurf) + texw := 0 + texh := 0 + C.SDL_QueryTexture(ttext, 0, 0, &texw, &texh) + dstrect := vsdl2.Rect { x, y, texw, texh } +// vsdl2.render_copy(g.sdl.renderer, ttext, 0, &dstrect) + C.SDL_RenderCopy(g.sdl.renderer, ttext, voidptr(0), voidptr(&dstrect)) + C.SDL_DestroyTexture(ttext) + vsdl2.free_surface(tsurf) +} + +[inline] fn (g &Game) draw_ptext(x int, y int, text string, tcol vsdl2.Color) { + g.draw_text(g.ofs_x + x, y, text, tcol) +} + +[live] +fn (g &Game) draw_begin() { +// println('about to clear') + C.SDL_RenderClear(g.sdl.renderer) + mut rect := vsdl2.Rect {0,0,g.sdl.w,g.sdl.h} + col := vsdl2.Color{byte(00), byte(00), byte(0), byte(0)} +// sdl_fill_rect(g.sdl.screen, &rect, BackgroundColor) + sdl_fill_rect(g.sdl.screen, &rect, col) + + rect = vsdl2.Rect {BlockSize * FieldWidth + 2,0,2,g.sdl.h} + sdl_fill_rect(g.sdl.screen, &rect, ForegroundColor) + rect = vsdl2.Rect {WinWidth - BlockSize * FieldWidth - 4,0,2,g.sdl.h} + sdl_fill_rect(g.sdl.screen, &rect, ForegroundColor) + + mut idx := 0 + for st in g.tetro_stats { + mut s := 10 + if g.tetro_total > 0 { + s += 90 * st / g.tetro_total + } + w := BlockSize + h := s * 4 * w / 100 + rect = vsdl2.Rect {(WinWidth - 7 * (w + 1)) / 2 + idx * (w + 1), WinHeight * 3 / 4 - h, w, h} + sdl_fill_rect(g.sdl.screen, &rect, Colors[idx + 1]) + idx++ + } +} + +fn (g &Game) draw_middle() { + C.SDL_UpdateTexture(g.sdl.texture, 0, g.sdl.screen.pixels, g.sdl.screen.pitch) +// vsdl2.render_copy(g.sdl.renderer, g.sdl.texture, voidptr(0), voidptr(0)) + C.SDL_RenderCopy(g.sdl.renderer, g.sdl.texture, voidptr(0), voidptr(0)) +} + +fn (g &Game) draw_score() { + if g.font != voidptr(0) { + g.draw_ptext(1, 2, 'score: ' + g.score.str() + ' nxt=' + g.tetro_next.str(), TextColor) + if g.state == .gameover { + g.draw_ptext(1, WinHeight / 2 + 0 * TextSize, 'Game Over', TextColor) + g.draw_ptext(1, WinHeight / 2 + 2 * TextSize, 'FIRE to restart', TextColor) + } else if g.state == .paused { + g.draw_ptext(1, WinHeight / 2 + 0 * TextSize, 'Game Paused', TextColor) + g.draw_ptext(1, WinHeight / 2 + 2 * TextSize, 'SPACE to resume', TextColor) + } + } +} + +fn (g &Game) draw_stats() { + if g.font != voidptr(0) { + g.draw_text(WinWidth / 3 + 10, WinHeight * 3 / 4 + 0 * TextSize, 'stats: ' + g.tetro_total.str() + ' tetros', TextColor) + mut stats := '' + for st in g.tetro_stats { + mut s := 0 + if g.tetro_total > 0 { + s = 100 * st / g.tetro_total + } + stats += ' ' + stats += s.str() + } + g.draw_text(WinWidth / 3 - 8, WinHeight * 3 / 4 + 2 * TextSize, stats, TextColor) + } +} + +fn (g &Game) draw_end() { + C.SDL_RenderPresent(g.sdl.renderer) +} + +fn parse_binary_tetro(t_ int) []Block { + mut t := t_ + res := [Block{}].repeat(4) + mut cnt := 0 + horizontal := t == 9// special case for the horizontal line + for i := 0; i <= 3; i++ { + // Get ith digit of t + p := int(math.pow(10, 3 - i)) + mut digit := int(t / p) + t %= p + // Convert the digit to binary + for j := 3; j >= 0; j-- { + bin := digit % 2 + digit /= 2 + if bin == 1 || (horizontal && i == TetroSize - 1) { + // TODO: res[cnt].x = j + // res[cnt].y = i + mut point := &res[cnt] + point.x = j + point.y = i + cnt++ + } + } + } + return res +} diff --git a/vlib/sdl/image/README.md b/vlib/sdl/image/README.md new file mode 100644 index 0000000000..39b00103a4 --- /dev/null +++ b/vlib/sdl/image/README.md @@ -0,0 +1,4 @@ +# SDL2 Image Library +Assuming you've installed the dependencies for sdl (already include `SDL2_image` dependency) + +See the Tvintris example for usage diff --git a/vlib/sdl/image/image.v b/vlib/sdl/image/image.v new file mode 100644 index 0000000000..4e32e195ae --- /dev/null +++ b/vlib/sdl/image/image.v @@ -0,0 +1,29 @@ +module image + +#flag linux -lSDL2_image +#include + +// following kludge until `sdl2-config ...` is supported also on windows +#flag windows -I/msys64/mingw64/include/SDL2 +#flag windows -L/mingw64/lib -lSDL2_image + +////////////////////////////////////////////////////////// +// SDL_Image.h +////////////////////////////////////////////////////////// +//fn C.IMG_Load_RW(logo &vsdl2.RwOps, free_src int) &vsdl2.Surface +fn C.IMG_Init(flags int) int +fn C.IMG_Quit() +fn C.IMG_Load(file byteptr) voidptr + +pub fn img_init(flags int) int { + return C.IMG_Init(flags) +} + +pub fn quit() { + C.IMG_Quit() +} + +pub fn load(file string) &vsdl2.Surface { + res := C.IMG_Load(file.str) + return res +} diff --git a/vlib/sdl/sdl.v b/vlib/sdl/sdl.v new file mode 100644 index 0000000000..5770872778 --- /dev/null +++ b/vlib/sdl/sdl.v @@ -0,0 +1,308 @@ +// Copyright(C) 2019 Nicolas Sauzede. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module sdl + +#flag linux `sdl2-config --cflags --libs` -lSDL2_ttf -lSDL2_mixer -lSDL2_image +#flag darwin `sdl2-config --cflags --libs` -lSDL2_ttf -lSDL2_mixer -lSDL2_image + +//#flag windows `sdl2-config --cflags` +//#flag windows `sdl2-config --libs` -lSDL2_ttf -lSDL2_mixer -lSDL2_image +//#flag `sdl2-config --cflags --libs` -lSDL2_ttf -lSDL2_mixer -lSDL2_image + +#flag -DSDL_DISABLE_IMMINTRIN_H + +// following kludge until `sdl2-config ...` is supported also on windows +#flag windows -I/msys64/mingw64/include/SDL2 +#flag windows -Dmain=SDL_main +#flag windows -L/mingw64/lib -lmingw32 -lSDL2main -lSDL2 -lSDL2_ttf -lSDL2_mixer -lSDL2_image + +#include +#include +#include + + +//struct C.SDL_Color{ +pub struct Color{ +pub: + r byte /**< Red value 0-255 */ + g byte /**< Green value 0-255 */ + b byte /**< Blue value 0-255 */ + a byte /**< Alpha value 0-255 */ +} +//type Color C.SDL_Color + +pub struct C.SDL_Color{ +pub: + r byte + g byte + b byte + a byte +} + +//struct C.SDL_Rect { +pub struct Rect { +pub: + x int /**< number of pixels from left side of screen */ + y int /**< num of pixels from top of screen */ + w int /**< width of rectangle */ + h int /**< height of rectangle */ +} +//type Rect C.SDL_Rect + +//pub struct C.SDL_Surface { +pub struct Surface { +pub: + flags u32 + format voidptr + w int + h int + pitch int + pixels voidptr + userdata voidptr + locked int + lock_data voidptr + clip_rect Rect + map voidptr + refcount int +} +//type Surface C.SDL_Surface +//type Surface Surface + +///////////////////////////////////////////////////////// + +struct QuitEvent { + _type u32 /**< SDL_QUIT */ + timestamp u32 +} +struct Keysym { +pub: + scancode int /**< hardware specific scancode */ + sym int /**< SDL virtual keysym */ + mod u16 /**< current key modifiers */ + unused u32 /**< translated character */ +} +struct KeyboardEvent { +pub: + _type u32 /**< SDL_KEYDOWN or SDL_KEYUP */ + timestamp u32 + windowid u32 + state byte /**< SDL_PRESSED or SDL_RELEASED */ + repeat byte + padding2 byte + padding3 byte + keysym Keysym +} +struct JoyButtonEvent { +pub: + _type u32 /**< SDL_JOYBUTTONDOWN or SDL_JOYBUTTONUP */ + timestamp u32 + which int /**< The joystick device index */ + button byte /**< The joystick button index */ + state byte /**< SDL_PRESSED or SDL_RELEASED */ +} +struct JoyHatEvent { +pub: + _type u32 /**< SDL_JOYHATMOTION */ + timestamp u32 + which int /**< The joystick device index */ + hat byte /**< The joystick hat index */ + value byte /**< The hat position value: + * SDL_HAT_LEFTUP SDL_HAT_UP SDL_HAT_RIGHTUP + * SDL_HAT_LEFT SDL_HAT_CENTERED SDL_HAT_RIGHT + * SDL_HAT_LEFTDOWN SDL_HAT_DOWN SDL_HAT_RIGHTDOWN + * Note that zero means the POV is centered. + */ +} + +//pub union EventU { +pub union Event { +pub: + _type u32 + quit QuitEvent + key KeyboardEvent + jbutton JoyButtonEvent + jhat JoyHatEvent + _pad56 [56]byte +} +//type Event EventU + + +//struct C.SDL_AudioSpec { +pub struct AudioSpec { +pub: +mut: + freq int /**< DSP frequency -- samples per second */ + format u16 /**< Audio data format */ + channels byte /**< Number of channels: 1 mono, 2 stereo */ + silence byte /**< Audio buffer silence value (calculated) */ + samples u16 /**< Audio buffer size in samples (power of 2) */ + size u32 /**< Necessary for some compile environments */ + callback voidptr + userdata voidptr +} + +// pub struct RwOps { +// pub: +// mut: +// seek voidptr +// read voidptr +// write voidptr +// close voidptr +// type_ u32 +// hidden voidptr +// } +//type AudioSpec C.voidptrioSpec + +type atexit_func_t fn () +fn C.atexit(atexit_func_t) + +/////////////////////////////////////////////////// +fn C.SDL_MapRGB(fmt voidptr byte, g byte, b byte) u32 +fn C.SDL_CreateRGBSurface(flags u32, width int, height int, depth int, Rmask u32, Gmask u32, Bmask u32, Amask u32) voidptr +fn C.SDL_PollEvent(&Event) int +fn C.SDL_NumJoysticks() int +fn C.SDL_JoystickNameForIndex(device_index int) voidptr +fn C.SDL_RenderCopy(renderer voidptr, texture voidptr, srcrect voidptr, dstrect voidptr) int +fn C.SDL_CreateWindow(title byteptr, x int, y int, w int, h int, flags u32) voidptr +fn C.SDL_CreateWindowAndRenderer(width int, height int, window_flags u32, window &voidptr, renderer &voidptr) int +fn C.SDL_DestroyWindow(window voidptr) +fn C.SDL_GetWindowSize(window voidptr, w voidptr, h voidptr) +fn C.SDL_SetHint(name byteptr, value byteptr) C.SDL_bool +//fn C.SDL_RWFromFile(byteptr, byteptr) &RwOps +//fn C.SDL_CreateTextureFromSurface(renderer &C.SDL_Renderer, surface &C.SDL_Surface) &C.SDL_Texture +fn C.SDL_CreateTextureFromSurface(renderer voidptr, surface voidptr) voidptr +fn C.SDL_CreateTexture(renderer voidptr, format u32, access int, w int, h int) voidptr +fn C.SDL_FillRect(dst voidptr, dstrect voidptr, color u32) int +fn C.SDL_RenderPresent(renderer voidptr) +fn C.SDL_RenderClear(renderer voidptr) int +fn C.SDL_UpdateTexture(texture voidptr, rect voidptr, pixels voidptr, pitch int) int +fn C.SDL_QueryTexture(texture voidptr, format voidptr, access voidptr, w voidptr, h voidptr) int +fn C.SDL_DestroyTexture(texture voidptr) +fn C.SDL_FreeSurface(surface voidptr) +fn C.SDL_Init(flags u32) int +fn C.SDL_Quit() +fn C.SDL_SetWindowTitle(window voidptr, title byteptr) +// following is wrong : SDL_Zero is a macro accepting an argument +fn C.SDL_zero() +fn C.SDL_LoadWAV(file byteptr, spec voidptr, audio_buf voidptr, audio_len voidptr) voidptr +fn C.SDL_FreeWAV(audio_buf voidptr) +fn C.SDL_OpenAudio(desired voidptr, obtained voidptr) int +fn C.SDL_CloseAudio() +fn C.SDL_PauseAudio(pause_on int) +fn C.SDL_JoystickOpen(device_index int) int +fn C.SDL_JoystickEventState(state int) int + +////////////////////////////////////////////////////////// +// SDL_Timer.h +////////////////////////////////////////////////////////// +fn C.SDL_GetTicks() u32 +fn C.SDL_TICKS_PASSED(a,b u32) bool +fn C.SDL_GetPerformanceCounter() u64 +fn C.SDL_GetPerformanceFrequency() u64 +fn C.SDL_Delay(ms u32) + +////////////////////////////////////////////////////////// +// TTF +////////////////////////////////////////////////////////// +fn C.TTF_Init() int +fn C.TTF_Quit() +fn C.TTF_OpenFont(file byteptr, ptsize int) voidptr +fn C.TTF_CloseFont(font voidptr) +//fn C.TTF_RenderText_Solid(voidptr, voidptr, SdlColor) voidptr +fn C.TTF_RenderText_Solid(voidptr, voidptr, C.SDL_Color) voidptr +////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////// +// MIX +////////////////////////////////////////////////////////// +fn C.Mix_Init(flags int) int +fn C.Mix_OpenAudio(frequency int, format u16, channels int, chunksize int) int +fn C.Mix_LoadMUS(file byteptr) voidptr +fn C.Mix_LoadWAV(file byteptr) voidptr +fn C.Mix_PlayMusic(music voidptr, loops int) int +fn C.Mix_VolumeMusic(volume int) int +fn C.Mix_FreeMusic(music voidptr) +fn C.Mix_CloseAudio() +fn C.Mix_FreeChunk(chunk voidptr) +fn C.Mix_PauseMusic() +fn C.Mix_ResumeMusic() +fn C.Mix_PlayChannel(channel int, chunk voidptr, loops int) int + +////////////////////////////////////////////////////////// +// GL +////////////////////////////////////////////////////////// +fn C.SDL_GL_SetAttribute(attr int, value int) int +fn C.SDL_GL_CreateContext(window voidptr) voidptr +fn C.SDL_GL_MakeCurrent(window voidptr, context voidptr) int +fn C.SDL_GL_SetSwapInterval(interval int) int +fn C.SDL_GL_SwapWindow(window voidptr) +fn C.SDL_GL_DeleteContext(context voidptr) + +pub fn create_texture_from_surface(renderer voidptr, surface &Surface) voidptr { + return C.SDL_CreateTextureFromSurface(renderer, voidptr(surface)) +} + +pub fn create_window_and_renderer(width int, height int, window_flags u32, window voidptr, renderer voidptr) int { + return C.SDL_CreateWindowAndRenderer(width, height, window_flags, window, renderer) +} + +pub fn joystick_name_for_index(device_index int) byteptr { + return byteptr(C.SDL_JoystickNameForIndex(device_index)) +} + +pub fn fill_rect(screen &Surface, rect &Rect, _col &Color) { + col := C.SDL_MapRGB(screen.format, _col.r, _col.g, _col.b) + _screen := voidptr(screen) + _rect := voidptr(rect) + C.SDL_FillRect(_screen, _rect, col) +} + +pub fn create_rgb_surface(flags u32, width int, height int, depth int, rmask u32, gmask u32, bmask u32, amask u32) &Surface { + res := C.SDL_CreateRGBSurface(flags, width, height, depth, rmask, gmask, bmask, amask) + return res +} + +pub fn render_copy(renderer voidptr, texture voidptr, srcrect &Rect, dstrect &Rect) int { + _srcrect := voidptr(srcrect) + _dstrect := voidptr(dstrect) + return C.SDL_RenderCopy(renderer, texture, _srcrect, _dstrect) +} + +pub fn poll_event(event &Event) int { + return C.SDL_PollEvent(voidptr(event)) +} + +pub fn destroy_texture(text voidptr) { + C.SDL_DestroyTexture(text) +} + +pub fn free_surface(surf &Surface) { + _surf := voidptr(surf) + C.SDL_FreeSurface(_surf) +} + +pub fn get_ticks() u32 { + return C.SDL_GetTicks() +} + +pub fn ticks_passed(a, b u32) bool { + return C.SDL_TICKS_PASSED(a,b) +} + +pub fn get_perf_counter() u64 { + return C.SDL_GetPerformanceCounter() +} + +pub fn get_perf_frequency() u64 { + return C.SDL_GetPerformanceFrequency() +} + +pub fn delay(ms u32) { + C.SDL_Delay(ms) +} + +pub const ( + version = '0.2' // hack to avoid unused module warning in the main program +)