vlib: sdl module + tVintris example

pull/2979/head
Nicolas Sauzede 2019-12-05 09:25:55 +01:00 committed by Alexander Medvednikov
parent 6dd1270539
commit 9d854c5df0
16 changed files with 1279 additions and 3 deletions

View File

@ -110,7 +110,7 @@ fn C.RegOpenKeyExW(hKey voidptr, lpSubKey &u16, ulOptions u32, samDesired u32, p
fn C.RegCloseKey() fn C.RegCloseKey()
fn C.RegQueryValueEx() voidptr fn C.RegQueryValueEx() voidptr
fn C.RemoveDirectory() int fn C.RemoveDirectory() int
fn C.GetStdHandle() int fn C.GetStdHandle() voidptr
fn C.SetConsoleMode() fn C.SetConsoleMode()
fn C.GetConsoleMode() int fn C.GetConsoleMode() int
fn C._putws() fn C._putws()
@ -131,7 +131,7 @@ fn C._putenv() int
fn C._waccess() int fn C._waccess() int
fn C._wremove() fn C._wremove()
fn C.ReadConsole() fn C.ReadConsole()
fn C.fgetws() int fn C.fgetws() voidptr
fn C.GetModuleFileName() int fn C.GetModuleFileName() int
fn C._wchdir() fn C._wchdir()
fn C._wgetcwd() int fn C._wgetcwd() int

View File

@ -838,7 +838,7 @@ pub fn is_dir(path string) bool {
$if windows { $if windows {
_path := path.replace('/', '\\') _path := path.replace('/', '\\')
attr := C.GetFileAttributesW(_path.to_wide()) attr := C.GetFileAttributesW(_path.to_wide())
if int(attr) == C.INVALID_FILE_ATTRIBUTES { if attr == u32(C.INVALID_FILE_ATTRIBUTES) {
return false return false
} }
if int(attr) & C.FILE_ATTRIBUTE_DIRECTORY != 0 { if int(attr) & C.FILE_ATTRIBUTE_DIRECTORY != 0 {

21
vlib/sdl/LICENSE 100644
View File

@ -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.

54
vlib/sdl/README.md 100644
View File

@ -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

View File

@ -0,0 +1,17 @@
# tVintris
tvintris.v is a dual-player (local) version based on original source from <a href='https://github.com/vlang/v/examples/tetris'>tetris example</a> 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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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 <down>
}
}
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 <down>
}
}
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
}

View File

@ -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

View File

@ -0,0 +1,29 @@
module image
#flag linux -lSDL2_image
#include <SDL_image.h>
// 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
}

308
vlib/sdl/sdl.v 100644
View File

@ -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 <SDL.h>
#include <SDL_ttf.h>
#include <SDL_mixer.h>
//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
)