Simple game frame

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2026-02-18 15:14:20 +0100
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2026-02-18 15:14:20 +0100
Commit 6e4ab703d370e94ed0575ee986fa6235de7009ea (patch)
-rw-r--r-- game.c 227
-rw-r--r-- termbox2.h 4307
2 files changed, 4534 insertions, 0 deletions
diff --git a/game.c b/game.c
  
1
#include <stdio.h>
  
2
  
  
3
#define TB_IMPL
  
4
#include "termbox2.h"
  
5
  
  
6
#define MIN_W 40
  
7
#define MIN_H 12
  
8
#define SIDEBAR_W 40
  
9
#define CP_H 0x2500
  
10
#define CP_V 0x2502
  
11
#define CP_TL 0x250c
  
12
#define CP_TR 0x2510
  
13
#define CP_BL 0x2514
  
14
#define CP_BR 0x2518
  
15
  
  
16
static void draw_border(int x, int y, int w, int h, uintattr_t fg) {
  
17
	int ix;
  
18
	int iy;
  
19
  
  
20
	for (ix = 0; ix < w; ix++) {
  
21
		tb_set_cell(x + ix, y, CP_H, fg, TB_DEFAULT);
  
22
		tb_set_cell(x + ix, y + h - 1, CP_H, fg, TB_DEFAULT);
  
23
	}
  
24
	for (iy = 0; iy < h; iy++) {
  
25
		tb_set_cell(x, y + iy, CP_V, fg, TB_DEFAULT);
  
26
		tb_set_cell(x + w - 1, y + iy, CP_V, fg, TB_DEFAULT);
  
27
	}
  
28
  
  
29
	tb_set_cell(x, y, CP_TL, fg, TB_DEFAULT);
  
30
	tb_set_cell(x + w - 1, y, CP_TR, fg, TB_DEFAULT);
  
31
	tb_set_cell(x, y + h - 1, CP_BL, fg, TB_DEFAULT);
  
32
	tb_set_cell(x + w - 1, y + h - 1, CP_BR, fg, TB_DEFAULT);
  
33
}
  
34
  
  
35
static void draw_room(int x, int y, int w, int h, uintattr_t fg) {
  
36
	draw_border(x, y, w, h, fg);
  
37
	tb_set_cell(x + w / 2, y, '+', fg, TB_DEFAULT);
  
38
}
  
39
  
  
40
static void get_layout(int w, int h, int *map_x, int *map_y, int *map_w,
  
41
	int *map_h, int *side_x, int *side_y, int *side_w, int *side_h,
  
42
	int *msg1_y, int *msg2_y) {
  
43
	*map_x = 0;
  
44
	*map_y = 0;
  
45
	*map_w = w - SIDEBAR_W;
  
46
	*map_h = h - 2;
  
47
	*side_x = w - SIDEBAR_W;
  
48
	*side_y = 0;
  
49
	*side_w = SIDEBAR_W;
  
50
	*side_h = h - 2;
  
51
	*msg1_y = h - 2;
  
52
	*msg2_y = h - 1;
  
53
}
  
54
  
  
55
static void draw_map(int map_x, int map_y, int map_w, int map_h, int px,
  
56
	int py) {
  
57
	if (px >= 0 && py >= 0) {
  
58
		tb_set_cell(map_x + px, map_y + py, '@', TB_WHITE | TB_BOLD, TB_DEFAULT);
  
59
	}
  
60
}
  
61
  
  
62
static void draw_progress_bar(int x, int y, int w, int value, int max) {
  
63
	int filled;
  
64
	int ix;
  
65
	int inner_w = w - 2;
  
66
  
  
67
	if (w < 4) {
  
68
		return;
  
69
	}
  
70
	if (max <= 0) {
  
71
		max = 1;
  
72
	}
  
73
	if (value < 0) {
  
74
		value = 0;
  
75
	}
  
76
	if (value > max) {
  
77
		value = max;
  
78
	}
  
79
  
  
80
	filled = (inner_w * value) / max;
  
81
	tb_set_cell(x, y, '[', TB_WHITE, TB_DEFAULT);
  
82
	for (ix = 0; ix < inner_w; ix++) {
  
83
		uintattr_t fg = ix < filled ? TB_GREEN : TB_WHITE;
  
84
		uint32_t ch = ix < filled ? '=' : ' ';
  
85
		tb_set_cell(x + 1 + ix, y, ch, fg, TB_DEFAULT);
  
86
	}
  
87
	tb_set_cell(x + w - 1, y, ']', TB_WHITE, TB_DEFAULT);
  
88
}
  
89
  
  
90
static void draw_stats(int x, int y) {
  
91
	tb_print(x, y, TB_WHITE | TB_BOLD, TB_DEFAULT, "Stats");
  
92
	tb_print(x, y + 2, TB_WHITE, TB_DEFAULT, "HP 12/12");
  
93
	draw_progress_bar(x, y + 3, 18, 12, 12);
  
94
	tb_print(x, y + 4, TB_WHITE, TB_DEFAULT, "AC: 7");
  
95
	tb_print(x, y + 5, TB_WHITE, TB_DEFAULT, "Str: 16");
  
96
	tb_print(x, y + 6, TB_WHITE, TB_DEFAULT, "Gold: 42");
  
97
}
  
98
  
  
99
static void draw_inventory(int x, int y) {
  
100
	tb_print(x, y, TB_WHITE | TB_BOLD, TB_DEFAULT, "Inventory");
  
101
	tb_print(x, y + 2, TB_WHITE, TB_DEFAULT, "a) dagger");
  
102
	tb_print(x, y + 3, TB_WHITE, TB_DEFAULT, "b) ration");
  
103
	tb_print(x, y + 4, TB_WHITE, TB_DEFAULT, "c) potion");
  
104
	tb_print(x, y + 5, TB_WHITE, TB_DEFAULT, "d) scroll");
  
105
}
  
106
  
  
107
static const char *status_msg = "";
  
108
  
  
109
static void update_status(const char *message) {
  
110
	status_msg = message ? message : "";
  
111
}
  
112
  
  
113
static void render(int px, int py, int *out_map_w, int *out_map_h) {
  
114
	int w;
  
115
	int h;
  
116
	int map_x;
  
117
	int map_y;
  
118
	int map_w;
  
119
	int map_h;
  
120
	int side_x;
  
121
	int side_y;
  
122
	int side_w;
  
123
	int side_h;
  
124
	int stats_x;
  
125
	int stats_y;
  
126
	int stats_w;
  
127
	int stats_h;
  
128
	int inv_x;
  
129
	int inv_y;
  
130
	int inv_w;
  
131
	int inv_h;
  
132
	int msg1_y;
  
133
	int msg2_y;
  
134
  
  
135
	w = tb_width();
  
136
	h = tb_height();
  
137
	get_layout(w, h, &map_x, &map_y, &map_w, &map_h, &side_x, &side_y, &side_w, &side_h, &msg1_y, &msg2_y);
  
138
  
  
139
	tb_clear();
  
140
	if (w < MIN_W || h < MIN_H || map_w < 8 || map_h < 3) {
  
141
		tb_print(1, 1, TB_RED | TB_BOLD, TB_DEFAULT, "Window too small. Resize to at least 40x12.");
  
142
		tb_present();
  
143
		*out_map_w = map_w;
  
144
		*out_map_h = map_h;
  
145
		return;
  
146
	}
  
147
  
  
148
	draw_border(map_x, map_y, map_w, map_h, TB_WHITE);
  
149
	draw_map(map_x + 1, map_y + 1, map_w - 2, map_h - 2, px, py);
  
150
  
  
151
	stats_x = side_x;
  
152
	stats_y = side_y;
  
153
	stats_w = side_w;
  
154
	stats_h = 11;
  
155
	inv_x = side_x;
  
156
	inv_y = side_y + stats_h;
  
157
	inv_w = side_w;
  
158
	inv_h = side_h - stats_h;
  
159
	if (stats_w >= 12 && stats_h >= 9) {
  
160
		draw_border(stats_x, stats_y, stats_w, stats_h, TB_WHITE);
  
161
		draw_stats(stats_x + 2, stats_y + 1);
  
162
	}
  
163
	if (inv_w >= 12 && inv_h >= 7) {
  
164
		draw_border(inv_x, inv_y, inv_w, inv_h, TB_WHITE);
  
165
		draw_inventory(inv_x + 2, inv_y + 1);
  
166
	}
  
167
  
  
168
	tb_print(2, msg1_y, TB_GREEN, TB_DEFAULT, status_msg);
  
169
	tb_print(2, msg2_y, TB_WHITE, TB_DEFAULT, "Move: arrows  Quit: q/ESC");
  
170
  
  
171
	tb_present();
  
172
	*out_map_w = map_w - 2;
  
173
	*out_map_h = map_h - 2;
  
174
}
  
175
  
  
176
static int clamp(int value, int min, int max) {
  
177
	if (value < min) {
  
178
		return min;
  
179
	}
  
180
	if (value > max) {
  
181
		return max;
  
182
	}
  
183
	return value;
  
184
}
  
185
  
  
186
int main(void) {
  
187
	int px = 6;
  
188
	int py = 4;
  
189
	int running = 1;
  
190
	int map_w = 0;
  
191
	int map_h = 0;
  
192
  
  
193
	if (tb_init() != TB_OK) {
  
194
		fprintf(stderr, "Failed to init termbox.\n");
  
195
		return 1;
  
196
	}
  
197
  
  
198
	tb_set_input_mode(TB_INPUT_ESC);
  
199
	update_status("You feel like you have a lot of potential.");
  
200
	while (running) {
  
201
		struct tb_event ev;
  
202
  
  
203
		render(px, py, &map_w, &map_h);
  
204
		tb_poll_event(&ev);
  
205
		if (ev.type == TB_EVENT_KEY) {
  
206
			if (ev.key == TB_KEY_ESC || ev.ch == 'q') {
  
207
				running = 0;
  
208
			} else if (ev.key == TB_KEY_ARROW_UP) {
  
209
				py -= 1;
  
210
			} else if (ev.key == TB_KEY_ARROW_DOWN) {
  
211
				py += 1;
  
212
			} else if (ev.key == TB_KEY_ARROW_LEFT) {
  
213
				px -= 1;
  
214
			} else if (ev.key == TB_KEY_ARROW_RIGHT) {
  
215
				px += 1;
  
216
			}
  
217
			px = clamp(px, 0, map_w > 1 ? map_w - 1 : 0);
  
218
			py = clamp(py, 0, map_h > 1 ? map_h - 1 : 0);
  
219
		} else if (ev.type == TB_EVENT_RESIZE) {
  
220
			px = clamp(px, 0, map_w > 1 ? map_w - 1 : 0);
  
221
			py = clamp(py, 0, map_h > 1 ? map_h - 1 : 0);
  
222
		}
  
223
	}
  
224
  
  
225
	tb_shutdown();
  
226
	return 0;
  
227
}
diff --git a/termbox2.h b/termbox2.h
Changes too big to display