855 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			V
		
	
	
			
		
		
	
	
			855 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 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 = os.dir( os.real_path( 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
 | 
						|
}
 |