raylib/game_of_life

Code

game_of_life.odin ¶
253 linesSource

1package game_of_life
2
3/*********************************************************************
4                            GAME  OF  LIFE
5                            (using raylib)
6
7 This example shows a simple setup for a game with Input processing,
8 updating game state and drawing game state to the screen.
9
10 You can
11 * Left-Click to bring a cell alive
12 * Right-Click to kill a cell
13 * Press <Space> to (un)pause the game
14 * Press <Esc> to close the game
15
16 The game starts paused.
17
18**********************************************************************/
19
20
21import time "core:time"
22import rl   "vendor:raylib"
23
24
25Window :: struct {
26	name:          cstring,
27	width:         i32,
28	height:        i32,
29	fps:           i32,
30	control_flags: rl.ConfigFlags,
31}
32
33Game :: struct {
34	tick_rate: time.Duration,
35	last_tick: time.Time,
36	pause:     bool,
37	colors:    []rl.Color,
38	width:     i32,
39	height:    i32,
40}
41
42World :: struct {
43	width:  i32,
44	height: i32,
45	alive:  []u8,
46}
47
48Cell :: struct {
49	width:  f32,
50	height: f32,
51}
52
53User_Input :: struct {
54	left_mouse_clicked:   bool,
55	right_mouse_clicked:  bool,
56	toggle_pause:         bool,
57	mouse_world_position: i32,
58	mouse_tile_x:         i32,
59	mouse_tile_y:         i32,
60}
61
62
63/*
64 Game Of Life rules:
65 * (1) A cell with 2 alive neighbors stays alive/dead
66 * (2) A cell with 3 alive neighbors stays/becomes alive
67 * (3) Otherwise: the cell dies/stays dead
68
69 reads from world, writes into next_world
70*/
71update_world :: #force_inline proc(world: ^World, next_world: ^World) {
72	for x: i32 = 0; x < world.width; x += 1 {
73		for y: i32 = 0; y < world.height; y += 1 {
74			neighbors := count_neighbors(world, x, y)
75			index := y * world.width + x
76
77			switch neighbors {
78			case 2: next_world.alive[index] = world.alive[index]
79			case 3: next_world.alive[index] = 1
80			case:   next_world.alive[index] = 0
81			}
82		}
83	}
84}
85
86/*
87 Just a branch-less version of adding all neighbors together
88*/
89count_neighbors :: #force_inline proc(w: ^World, x: i32, y: i32) -> u8 {
90	// our world is a torus!
91	left  := (x - 1) %% w.width
92	right := (x + 1) %% w.width
93	up    := (y - 1) %% w.height
94	down  := (y + 1) %% w.height
95
96	top_left     := w.alive[up   * w.width + left ]
97	top          := w.alive[up   * w.width + x    ]
98	top_right    := w.alive[up   * w.width + right]
99
100	mid_left     := w.alive[y    * w.width + left ]
101	mid_right    := w.alive[y    * w.width + right]
102
103	bottom_left  := w.alive[down * w.width + left ]
104	bottom       := w.alive[down * w.width + x    ]
105	bottom_right := w.alive[down * w.width + right]
106
107	top_row    := top_left    + top     + top_right
108	mid_row    := mid_left              + mid_right
109	bottom_row := bottom_left + bottom  + bottom_right
110
111	total      := top_row     + mid_row + bottom_row
112	return total
113}
114
115/*
116 Draws all the tiles of world
117*/
118draw_world :: #force_inline proc(world: ^World, cell: Cell, colors: []rl.Color) {
119	x, y: i32
120	for y = 0; y < world.height; y += 1 {
121		for x = 0; x < world.width; x += 1 {
122			index := y * world.width + x
123			color := colors[world.alive[index]]
124
125			rect := rl.Rectangle {
126				x      = f32(x) * cell.width,
127				y      = f32(y) * cell.height,
128				width  = cell.width,
129				height = cell.height,
130			}
131			rl.DrawRectangleRec(rect, color)
132		}
133	}
134}
135
136/*
137 Draws a yellow cell where the mouse points to
138*/
139draw_cursor :: proc(user_input: User_Input, cell: Cell) {
140
141	rect := rl.Rectangle {
142		x      = f32(user_input.mouse_tile_x) * cell.width,
143		y      = f32(user_input.mouse_tile_y) * cell.height,
144		width  = cell.width,
145		height = cell.height,
146	}
147	rl.DrawRectangleRec(rect, rl.YELLOW)
148}
149
150/**
151 The user input is processed such that the rest of the code does not need
152 to know anything about what the user input was. (You could process a controller here)
153**/
154process_user_input :: proc(user_input: ^User_Input, window: Window, world: World) {
155	m_pos   := rl.GetMousePosition()
156	mouse_x := i32((m_pos[0] / f32(window.width)) * f32(world.width))
157	mouse_y := i32((m_pos[1] / f32(window.height)) * f32(world.height))
158
159	//Keep in bounds while painting with torus wrapping
160	if user_input.left_mouse_clicked || user_input.right_mouse_clicked {
161		mouse_x %%= world.width
162		mouse_y %%= world.height
163	}
164
165	user_input^ = User_Input {
166		left_mouse_clicked   = rl.IsMouseButtonDown(.LEFT),
167		right_mouse_clicked  = rl.IsMouseButtonDown(.RIGHT),
168		toggle_pause         = rl.IsKeyPressed(.SPACE),
169		mouse_world_position = i32(mouse_y * world.width + mouse_x),
170		mouse_tile_x         = mouse_x,
171		mouse_tile_y         = mouse_y,
172	}
173}
174
175main :: proc() {
176	window := Window{"Game Of Life", 1024, 1024, 60, rl.ConfigFlags{.WINDOW_RESIZABLE}}
177
178	game := Game {
179		tick_rate = 300 * time.Millisecond,
180		last_tick = time.now(),
181		pause     = true,
182		colors    = []rl.Color{rl.BLACK, rl.WHITE},
183		width     = 64,
184		height    = 64,
185	}
186
187	world      := World{game.width, game.height, make([]u8, game.width * game.height)}
188	next_world := World{game.width, game.height, make([]u8, game.width * game.height)}
189	defer delete(world.alive)
190	defer delete(next_world.alive)
191
192	cell := Cell {
193		width  = f32(window.width) / f32(world.width),
194		height = f32(window.height) / f32(world.width),
195	}
196
197	user_input: User_Input
198
199	rl.InitWindow(window.width, window.height, window.name)
200	rl.SetWindowState(window.control_flags)
201	rl.SetTargetFPS(window.fps)
202
203	// Infinite game loop. Breaks on pressing <Esc>
204	for !rl.WindowShouldClose() {
205
206		// If the user resized the window, we adjust the cell size to keep drawing over the entire window
207		if rl.IsWindowResized() {
208			window.width = rl.GetScreenWidth()
209			window.height = rl.GetScreenHeight()
210
211			cell.width = f32(window.width) / f32(world.width)
212			cell.height = f32(window.height) / f32(world.width)
213		}
214
215		// Step 1: Process user input
216		// First the user input gets translated into meaninngful attribute names
217		// Then we use those to taken action based on them
218		process_user_input(&user_input, window, world)
219
220		if user_input.left_mouse_clicked {
221			world.alive[user_input.mouse_world_position] = 1
222		}
223		if user_input.right_mouse_clicked {
224			world.alive[user_input.mouse_world_position] = 0
225		}
226		if user_input.toggle_pause {
227			game.pause = !game.pause
228		}
229
230		// Step 2: Update the world state
231		// There is always a current state of the world that we read from
232		// and a future state of the world that we write to
233		if !game.pause && time.since(game.last_tick) > game.tick_rate {
234			game.last_tick = time.now()
235			update_world(&world, &next_world)
236
237			// this is how you swap 2 variables in ODIN! 
238			world, next_world = next_world, world
239		}
240
241		// Step 3: Draw the world
242		// The background gets cleared to a high contrast color, so it's easy
243		// to see if there was any pixel missed
244		rl.BeginDrawing()
245		rl.ClearBackground(rl.PINK)
246		draw_world(&world, cell, game.colors)
247		draw_cursor(user_input, cell)
248
249		rl.EndDrawing()
250	}
251
252
253}

Declarations Used 26