v/examples/sdl/tvintris/tvintris.v

856 lines
20 KiB
V

// Copyright (c) 2019-2020 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 filepath
import rand
import time
import os
import math
import sdl
import sdl.image as img
import sdl.mixer as mix
import sdl.ttf as ttf
[inline] fn sdl_fill_rect(s &SDL_Surface,r &SDL_Rect,c &SDL_Color){sdl.fill_rect(s,r,c)}
const (
Title = 'tVintris'
BASE = filepath.dir( os.realpath( os.executable() ) )
FontName = BASE + '/../../assets/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 (
mix_version = mix.version
ttf_version = ttf.version
)
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 = [
SDL_Color{byte(0), byte(0), byte(0), byte(0)}, // unused ?
SDL_Color{byte(0), byte(0x62), byte(0xc0), byte(0)}, // quad : darkblue 0062c0
SDL_Color{byte(0xca), byte(0x7d), byte(0x5f), byte(0)}, // tricorn : lightbrown ca7d5f
SDL_Color{byte(0), byte(0xc1), byte(0xbf), byte(0)}, // short topright : lightblue 00c1bf
SDL_Color{byte(0), byte(0xc1), byte(0), byte(0)}, // short topleft : lightgreen 00c100
SDL_Color{byte(0xbf), byte(0xbe), byte(0), byte(0)}, // long topleft : yellowish bfbe00
SDL_Color{byte(0xd1), byte(0), byte(0xbf), byte(0)}, // long topright : pink d100bf
SDL_Color{byte(0xd1), byte(0), byte(0), byte(0)}, // longest : lightred d10000
SDL_Color{byte(0), byte(170), byte(170), byte(0)}, // unused ?
]
// Background color
BackgroundColor = SDL_Color{byte(0), byte(0), byte(0), byte(0)}
// Foreground color
ForegroundColor = SDL_Color{byte(0), byte(170), byte(170), byte(0)}
// Text color
TextColor = SDL_Color{byte(0xca), byte(0x7d), byte(0x5f), 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 &SDL_Surface
texture voidptr
// AUDIO
actx AudioContext
// JOYSTICKS
jnames [2]string
jids [2]int
// V logo
v_logo &SDL_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 (sdlc 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
sdl.create_window_and_renderer(w, h, 0, &sdlc.window, &sdlc.renderer)
// C.SDL_CreateWindowAndRenderer(w, h, 0, voidptr(&sdlc.window), voidptr(&sdlc.renderer))
C.SDL_SetWindowTitle(sdlc.window, title.str)
sdlc.w = w
sdlc.h = h
sdlc.screen = sdl.create_rgb_surface(0, w, h, bpp, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000)
sdlc.texture = C.SDL_CreateTexture(sdlc.renderer, C.SDL_PIXELFORMAT_ARGB8888, C.SDL_TEXTUREACCESS_STREAMING, w, h)
C.Mix_Init(C.MIX_INIT_MOD)
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')
sdlc.actx.music = C.Mix_LoadMUS(MusicName.str)
sdlc.actx.waves[0] = C.Mix_LoadWAV(SndBlockName.str)
sdlc.actx.waves[1] = C.Mix_LoadWAV(SndLineName.str)
sdlc.actx.waves[2] = C.Mix_LoadWAV(SndDoubleName.str)
sdlc.actx.volume = C.SDL_MIX_MAXVOLUME
if C.Mix_PlayMusic(sdlc.actx.music, 1) != -1 {
C.Mix_VolumeMusic(sdlc.actx.volume)
}
njoy := C.SDL_NumJoysticks()
for i in 0..njoy {
C.SDL_JoystickOpen(i)
jn := tos_clone(sdl.joystick_name_for_index(i))
println('JOY NAME $jn')
for j in 0..NJOYMAX {
if sdlc.jnames[j] == jn {
println('FOUND JOYSTICK $j $jn ID=$i')
sdlc.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')
sdlc.v_logo = img.load(VLogo)
if !isnil(sdlc.v_logo) {
// println('got v_logo=$sdlc.v_logo')
sdlc.tv_logo = sdl.create_texture_from_surface(sdlc.renderer, sdlc.v_logo)
// println('got tv_logo=$sdlc.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().unix
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++
start_ticks := sdl.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 ???
evt := SDL_Event{}
for 0 < sdl.poll_event(&evt) {
match int(evt.@type) {
C.SDL_QUIT { should_close = true }
C.SDL_KEYDOWN {
key := evt.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(evt.jbutton.button)
joyid := evt.jbutton.which
// println('JOY BUTTON $jb $joyid')
game.handle_jbutton(jb, joyid)
game2.handle_jbutton(jb, joyid)
}
C.SDL_JOYHATMOTION {
jh := int(evt.jhat.hat)
jv := int(evt.jhat.value)
joyid := evt.jhat.which
// println('JOY HAT $jh $jv $joyid')
game.handle_jhat(jh, jv, joyid)
game2.handle_jhat(jh, jv, joyid)
}
else {}
}
}
if should_close {
break
}
end_ticks := sdl.get_perf_counter()
total_frame_ticks += end_ticks-start_ticks
elapsed_time := f64(end_ticks - start_ticks) / f64(sdl.get_perf_frequency())
// current_fps := 1.0 / elapsed_time
// should limit system to (1 / time_per_frame) fps
sdl.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) {
sdl.destroy_texture(game.sdl.tv_logo)
}
if !isnil(game.sdl.v_logo) {
sdl.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 }
else {}
}
if action == .space {
match game.state {
.running {
C.Mix_PauseMusic()
game.state = .paused
}
.paused {
C.Mix_ResumeMusic()
game.state = .running
}
else {}
}
}
if action == .fire {
match game.state {
.gameover {
game.init_game()
game.state = .running
}
else {}
}
}
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>
else {}
}
}
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 }
else {}
}
if action == .fire {
match game.state {
.gameover {
game.init_game()
game.state = .running
}
else {}
}
}
}
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>
else {}
}
}
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 in 0..FieldHeight + 2 {
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 in 0..FieldWidth + 2 {
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 in 0..TetroSize {
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]+= 2 -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 in 0..TetroSize {
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 in 0..TetroSize {
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 := SDL_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 := SDL_Rect { (WinWidth / 2) - (texw / 2), 20, texw, texh }
// Currently we can't seem to use sdl.render_copy when we need to pass a nil pointer (eg: srcrect to be NULL)
// sdl.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 SDL_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 := SDL_Rect { x, y, texw, texh }
// sdl.render_copy(g.sdl.renderer, ttext, 0, &dstrect)
C.SDL_RenderCopy(g.sdl.renderer, ttext, voidptr(0), voidptr(&dstrect))
C.SDL_DestroyTexture(ttext)
sdl.free_surface(tsurf)
}
[inline] fn (g &Game) draw_ptext(x int, y int, text string, tcol SDL_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 := SDL_Rect {0,0,g.sdl.w,g.sdl.h}
col := SDL_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 = SDL_Rect {BlockSize * FieldWidth + 2,0,2,g.sdl.h}
sdl_fill_rect(g.sdl.screen, &rect, ForegroundColor)
rect = SDL_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 = SDL_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)
// sdl.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 := 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
}