import rand import time import gx import gl import gg import glfw const ( BLOCK_SIZE = 20 // pixels FIELD_HEIGHT = 20 // # of blocks FIELD_WIDTH = 10 TETRO_SIZE = 4 WIN_WIDTH = BLOCK_SIZE * FIELD_WIDTH WIN_HEIGHT = BLOCK_SIZE * FIELD_HEIGHT TIMER_PERIOD = 250 // ms ) // TODO: type Tetro [TETRO_SIZE]struct{ x, y int } struct Block { x int y int } const ( // Tetros and their 4 possible states are encoded in binaries B_TETROS = [ // 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 = [ gx.rgb(0, 0, 0), gx.rgb(253, 32, 47), gx.rgb(0, 110, 194), gx.rgb(34, 169, 16), gx.rgb(170, 0, 170), gx.rgb(0, 0, 170), gx.rgb(0, 170, 0), gx.rgb(170, 85, 0), gx.rgb(0, 170, 170), ] ) struct Game { // Position of the dropping tetromino posX int posY 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 // TODO: field [][]int field array_array_int // TODO: tetro Tetro tetro []Block // Index of the dropping tetromino. Refers to its color. tetroIdx int // Index of the rotation (0-3) rotationIdx int // gg context for drawing gg *gg.GG } fn main() { mut game := &Game{} game.init_game() glfw.init() mut window := glfw.create_window(glfw.WinCfg { width: WIN_WIDTH height: WIN_HEIGHT title: 'V Tetris' ptr: game // glfw user pointer }) window.make_context_current() window.onkeydown(key_down) gg.init() game.gg = gg.new_context(gg.Cfg { width: WIN_WIDTH height: WIN_HEIGHT use_ortho: true // This is needed for 2D drawing }) go game.run() // Run the game loop in a new thread gl.clear() // For some reason this is necessary to avoid an intial flickering gl.clear_color(255, 255, 255, 255) for { gl.clear() gl.clear_color(255, 255, 255, 255) game.draw_scene() window.swap_buffers() glfw.wait_events() } } fn (g mut Game) init_game() { rand.seed() g.generate_tetro() g.field = []array_int // TODO: g.field = [][]int // Generate the field, fill it with 0's, add -1's on each edge for i := 0; i < FIELD_HEIGHT + 2; i++ { mut row := [0; FIELD_WIDTH + 2] row[0] = - 1 row[FIELD_WIDTH + 1] = - 1 g.field << row } mut first_row := g.field[0] mut last_row := g.field[FIELD_HEIGHT + 1] for j := 0; j < FIELD_WIDTH + 2; j++ { first_row[j] = - 1 last_row[j] = - 1 } } fn (g mut Game) run() { for { g.move_tetro() g.delete_completed_lines() glfw.post_empty_event() // force window redraw time.sleep_ms(TIMER_PERIOD) } } fn (g mut Game) move_tetro() { // Check each block in the dropping tetro for i := 0; i < TETRO_SIZE; i++ { tetro := g.tetro[i] y := tetro.y + g.posY + 1 x := tetro.x + g.posX // Reached the bottom of the screen or another block? // TODO: 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.posY < 2 { g.init_game() return } // Drop it and generate a new one g.drop_tetro() g.generate_tetro() return } } g.posY++ } fn (g mut Game) move_right(dx int) { for i := 0; i < TETRO_SIZE; i++ { // Reached left/right edges? tetro := g.tetro[i] y := tetro.y + g.posY x := tetro.x + g.posX + dx row := g.field[y] if row[x] != 0 { // Do not move return } } g.posX += dx } fn (g mut Game) delete_completed_lines() { for y := FIELD_HEIGHT; y >= 1; y-- { g.delete_completed_line(y) } } fn (g mut Game) delete_completed_line(y int) { for x := 1; x <= FIELD_WIDTH; x++ { f := g.field[y] if f[x] == 0 { return } } // Move everything down by 1 position for yy := y - 1; yy >= 1; yy-- { for x := 1; x <= FIELD_WIDTH; x++ { mut a := g.field[yy + 1] mut b := g.field[yy] a[x] = b[x] } } } // Place a new tetro on top fn (g mut Game) generate_tetro() { g.posY = 0 g.posX = FIELD_WIDTH / 2 - TETRO_SIZE / 2 g.tetroIdx = rand.next(B_TETROS.len) g.rotationIdx = 0 b := B_TETROS[g.tetroIdx] g.tetro = parse_binary_tetro(b[0]) } fn (g mut Game) drop_tetro() { for i := 0; i < TETRO_SIZE; i++ { tetro := g.tetro[i] x := tetro.x + g.posX y := tetro.y + g.posY // Remember the color of each block // TODO: g.field[y][x] = g.tetroIdx + 1 mut row := g.field[y] row[x] = g.tetroIdx + 1 } } fn (g mut Game) draw_tetro() { for i := 0; i < TETRO_SIZE; i++ { tetro := g.tetro[i] g.draw_block(g.posY + tetro.y, g.posX + tetro.x, g.tetroIdx + 1) } } fn (g mut Game) draw_block(i, j int, color_idx int) { g.gg.draw_rect((j - 1) * BLOCK_SIZE, (i - 1) * BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1, COLORS[color_idx]) } fn (g mut Game) draw_field() { for i := 1; i < FIELD_HEIGHT + 1; i++ { for j := 1; j < FIELD_WIDTH + 1; j++ { f := g.field[i] if f[j] > 0 { g.draw_block(i, j, f[j]) } } } } fn (g mut Game) draw_scene() { g.draw_tetro() g.draw_field() } fn parse_binary_tetro(t int) []Block { res := [Block{} ; 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 == TETRO_SIZE - 1) { // TODO: res[cnt].x = j // res[cnt].y = i mut point := &res[cnt] point.x = j point.y = i cnt++ } } } return res } // TODO: this exposes the unsafe C interface, clean up fn key_down(wnd *glfw.Window, key int, code int, action, mods int) { if action != 2 && action != 1 { return } // Fetch the game object stored in the user pointer mut game := &Game(glfw.get_window_user_pointer(wnd)) switch key { case GLFW_KEY_UP: // Rotate the tetro game.rotationIdx++ if game.rotationIdx == TETRO_SIZE { game.rotationIdx = 0 } t := B_TETROS[game.tetroIdx] // game.tetro = parse_binary_tetro(B_TETROS[game.tetroIdx][game.rotationIdx]) game.tetro = parse_binary_tetro(t[game.rotationIdx]) if game.posX < 0 { game.posX = 1 } case GLFW_KEY_LEFT: game.move_right(-1) case GLFW_KEY_RIGHT: game.move_right(1) case GLFW_KEY_DOWN: game.move_tetro() // drop faster when the player preses } }