examples/tetris
							parent
							
								
									f2801fb9b1
								
							
						
					
					
						commit
						eaf7eca8ef
					
				| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					<img src='https://raw.githubusercontent.com/vlang/v/master/examples/tetris/screenshot.png' width=540>
 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 96 KiB  | 
| 
						 | 
					@ -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 <down>
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue