diff --git a/examples/tetris/README.md b/examples/tetris/README.md
new file mode 100644
index 0000000000..a5db860ebc
--- /dev/null
+++ b/examples/tetris/README.md
@@ -0,0 +1 @@
+
diff --git a/examples/tetris/screenshot.png b/examples/tetris/screenshot.png
new file mode 100644
index 0000000000..67013c1011
Binary files /dev/null and b/examples/tetris/screenshot.png differ
diff --git a/examples/tetris/tetris.v b/examples/tetris/tetris.v
new file mode 100644
index 0000000000..003cee8ef5
--- /dev/null
+++ b/examples/tetris/tetris.v
@@ -0,0 +1,322 @@
+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
+ }
+}
+