1 /**
2  * D port of rlutil.h
3  * https://github.com/tapio/rlutil
4  * 
5  * About: Description
6  * This file provides some useful utilities for console mode
7  * roguelike game development with C and C++. It is aimed to
8  * be cross-platform (at least Windows and Linux).
9  *
10  * License:
11  * <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License</a>
12  * Authors:
13  * <a href="http://github.com/danyalzia">Danyal Zia</a>
14  */
15 
16 module rlutil.rlutil;
17 
18 import std.stdio : printf;
19 import std.process : executeShell;
20 import std..string : toStringz;
21 import std.c.stdio;
22 import std.c.stdlib;
23 
24 /// Define: RLUTIL_USE_ANSI
25 /// Define this to use ANSI escape sequences also on Windows
26 /// (defaults to using WinAPI instead).
27 enum RLUTIL_USE_ANSI = true;
28 
29 version (Windows)
30     enum Windows = true;
31 else
32     enum Windows = false;
33 
34 version (Posix)
35     enum Linux = true;
36 else
37     enum Linux = false;
38 
39 version (Windows) {
40 	import std.c.windows.windows;
41 	
42 	// Unfortunately, D2 doesn't provide these functions
43 	extern (C) int getch();
44 	extern (C) int kbhit();
45 }
46 
47 version (Posix) {
48 	import core.sys.posix.unistd,
49 	core.sys.posix.sys.ioctl,
50 	core.sys.linux.termios,
51 	core.sys.posix.termios,
52 	core.sys.posix.fcntl,
53 	core.sys.posix.sys.time;
54 }
55 
56 alias RLUTIL_STRING_T = string;
57 
58 /// Function: getch
59 /// Get character without waiting for Return to be pressed.
60 /// Windows has this in conio.h
61 version (Posix) {
62 	int getch() {
63 		// Here be magic.
64 		termios oldt, newt;
65 		int ch;
66 		tcgetattr(STDIN_FILENO, &oldt);
67 		newt = oldt;
68 		newt.c_lflag &= ~(ICANON | ECHO);
69 		tcsetattr(STDIN_FILENO, TCSANOW, &newt);
70 		ch = getchar();
71 		tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
72 		return ch;
73 	}
74 
75 	/// Function: kbhit
76 	/// Determines if keyboard has been hit.
77 	/// Windows has this in conio.h
78 	int kbhit() {
79 		// Here be dragons.
80 		static termios oldt, newt;
81 		int cnt = 0;
82 		tcgetattr(STDIN_FILENO, &oldt);
83 		newt = oldt;
84 		newt.c_lflag    &= ~(ICANON | ECHO);
85 		newt.c_iflag     = 0; // input mode
86 		newt.c_oflag     = 0; // output mode
87 		newt.c_cc[VMIN]  = 1; // minimum time to wait
88 		newt.c_cc[VTIME] = 1; // minimum characters to wait for
89 		tcsetattr(STDIN_FILENO, TCSANOW, &newt);
90 		ioctl(0, FIONREAD, &cnt); // Read count
91 		timeval tv;
92 		tv.tv_sec  = 0;
93 		tv.tv_usec = 100;
94 		select(STDIN_FILENO+1, null, null, null, &tv); // A small time delay
95 		tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
96 		return cnt; // Return number of characters
97 	}
98 }
99 
100 /// Function: gotoxy
101 /// Same as <rlutil.locate>.
102 void gotoxy(int x, int y) {
103 	locate(x,y);
104 }
105 
106 /**
107  * Enums: Color codes
108  *
109  * BLACK - Black
110  * BLUE - Blue
111  * GREEN - Green
112  * CYAN - Cyan
113  * RED - Red
114  * MAGENTA - Magenta / purple
115  * BROWN - Brown / dark yellow
116  * GREY - Grey / dark white
117  * DARKGREY - Dark grey / light black
118  * LIGHTBLUE - Light blue
119  * LIGHTGREEN - Light green
120  * LIGHTCYAN - Light cyan
121  * LIGHTRED - Light red
122  * LIGHTMAGENTA - Light magenta / light purple
123  * YELLOW - Yellow (bright)
124  * WHITE - White (bright)
125  */
126 enum {
127 	BLACK,
128 	BLUE,
129 	GREEN,
130 	CYAN,
131 	RED,
132 	MAGENTA,
133 	BROWN,
134 	GREY,
135 	DARKGREY,
136 	LIGHTBLUE,
137 	LIGHTGREEN,
138 	LIGHTCYAN,
139 	LIGHTRED,
140 	LIGHTMAGENTA,
141 	YELLOW,
142 	WHITE
143 };
144 
145 /**
146  * Consts: ANSI color strings
147  *
148  * ANSI_CLS - Clears screen
149  * ANSI_BLACK - Black
150  * ANSI_RED - Red
151  * ANSI_GREEN - Green
152  * ANSI_BROWN - Brown / dark yellow
153  * ANSI_BLUE - Blue
154  * ANSI_MAGENTA - Magenta / purple
155  * ANSI_CYAN - Cyan
156  * ANSI_GREY - Grey / dark white
157  * ANSI_DARKGREY - Dark grey / light black
158  * ANSI_LIGHTRED - Light red
159  * ANSI_LIGHTGREEN - Light green
160  * ANSI_YELLOW - Yellow (bright)
161  * ANSI_LIGHTBLUE - Light blue
162  * ANSI_LIGHTMAGENTA - Light magenta / light purple
163  * ANSI_LIGHTCYAN - Light cyan
164  * ANSI_WHITE - White (bright)
165  */
166 RLUTIL_STRING_T ANSI_CLS = "\033[2J";
167 RLUTIL_STRING_T ANSI_BLACK = "\033[22;30m";
168 RLUTIL_STRING_T ANSI_RED = "\033[22;31m";
169 RLUTIL_STRING_T ANSI_GREEN = "\033[22;32m";
170 RLUTIL_STRING_T ANSI_BROWN = "\033[22;33m";
171 RLUTIL_STRING_T ANSI_BLUE = "\033[22;34m";
172 RLUTIL_STRING_T ANSI_MAGENTA = "\033[22;35m";
173 RLUTIL_STRING_T ANSI_CYAN = "\033[22;36m";
174 RLUTIL_STRING_T ANSI_GREY = "\033[22;37m";
175 RLUTIL_STRING_T ANSI_DARKGREY = "\033[01;30m";
176 RLUTIL_STRING_T ANSI_LIGHTRED = "\033[01;31m";
177 RLUTIL_STRING_T ANSI_LIGHTGREEN = "\033[01;32m";
178 RLUTIL_STRING_T ANSI_YELLOW = "\033[01;33m";
179 RLUTIL_STRING_T ANSI_LIGHTBLUE = "\033[01;34m";
180 RLUTIL_STRING_T ANSI_LIGHTMAGENTA = "\033[01;35m";
181 RLUTIL_STRING_T ANSI_LIGHTCYAN = "\033[01;36m";
182 RLUTIL_STRING_T ANSI_WHITE = "\033[01;37m";
183 
184 /**
185  * Consts: Key codes for keyhit()
186  *
187  * KEY_ESCAPE  - Escape
188  * KEY_ENTER   - Enter
189  * KEY_SPACE   - Space
190  * KEY_INSERT  - Insert
191  * KEY_HOME    - Home
192  * KEY_END     - End
193  * KEY_DELETE  - Delete
194  * KEY_PGUP    - PageUp
195  * KEY_PGDOWN  - PageDown
196  * KEY_UP      - Up arrow
197  * KEY_DOWN    - Down arrow
198  * KEY_LEFT    - Left arrow
199  * KEY_RIGHT   - Right arrow
200  * KEY_F1      - F1
201  * KEY_F2      - F2
202  * KEY_F3      - F3
203  * KEY_F4      - F4
204  * KEY_F5      - F5
205  * KEY_F6      - F6
206  * KEY_F7      - F7
207  * KEY_F8      - F8
208  * KEY_F9      - F9
209  * KEY_F10     - F10
210  * KEY_F11     - F11
211  * KEY_F12     - F12
212  * KEY_NUMDEL  - Numpad del
213  * KEY_NUMPAD0 - Numpad 0
214  * KEY_NUMPAD1 - Numpad 1
215  * KEY_NUMPAD2 - Numpad 2
216  * KEY_NUMPAD3 - Numpad 3
217  * KEY_NUMPAD4 - Numpad 4
218  * KEY_NUMPAD5 - Numpad 5
219  * KEY_NUMPAD6 - Numpad 6
220  * KEY_NUMPAD7 - Numpad 7
221  * KEY_NUMPAD8 - Numpad 8
222  * KEY_NUMPAD9 - Numpad 9
223  */
224 const int KEY_ESCAPE  = 0;
225 const int KEY_ENTER   = 1;
226 const int KEY_SPACE   = 32;
227 
228 const int KEY_INSERT  = 2;
229 const int KEY_HOME    = 3;
230 const int KEY_PGUP    = 4;
231 const int KEY_DELETE  = 5;
232 const int KEY_END     = 6;
233 const int KEY_PGDOWN  = 7;
234 
235 const int KEY_UP      = 14;
236 const int KEY_DOWN    = 15;
237 const int KEY_LEFT    = 16;
238 const int KEY_RIGHT   = 17;
239 
240 const int KEY_F1      = 18;
241 const int KEY_F2      = 19;
242 const int KEY_F3      = 20;
243 const int KEY_F4      = 21;
244 const int KEY_F5      = 22;
245 const int KEY_F6      = 23;
246 const int KEY_F7      = 24;
247 const int KEY_F8      = 25;
248 const int KEY_F9      = 26;
249 const int KEY_F10     = 27;
250 const int KEY_F11     = 28;
251 const int KEY_F12     = 29;
252 
253 const int KEY_NUMDEL  = 30;
254 const int KEY_NUMPAD0 = 31;
255 const int KEY_NUMPAD1 = 127;
256 const int KEY_NUMPAD2 = 128;
257 const int KEY_NUMPAD3 = 129;
258 const int KEY_NUMPAD4 = 130;
259 const int KEY_NUMPAD5 = 131;
260 const int KEY_NUMPAD6 = 132;
261 const int KEY_NUMPAD7 = 133;
262 const int KEY_NUMPAD8 = 134;
263 const int KEY_NUMPAD9 = 135;
264 
265 /// Function: getkey
266 /// Reads a key press (blocking) and returns a key code.
267 ///
268 /// See <Key codes for keyhit()>
269 ///
270 /// Note:
271 /// Only Arrows, Esc, Enter and Space are currently working properly.
272 int getkey() {
273 version(Posix)
274 	int cnt = kbhit(); // for ANSI escapes processing
275 
276 	int k = getch();
277 	switch(k) {
278 		case 0: {
279 			int kk;
280 			switch (kk = getch()) {
281 				case 71: return KEY_NUMPAD7;
282 				case 72: return KEY_NUMPAD8;
283 				case 73: return KEY_NUMPAD9;
284 				case 75: return KEY_NUMPAD4;
285 				case 77: return KEY_NUMPAD6;
286 				case 79: return KEY_NUMPAD1;
287 				case 80: return KEY_NUMPAD4;
288 				case 81: return KEY_NUMPAD3;
289 				case 82: return KEY_NUMPAD0;
290 				case 83: return KEY_NUMDEL;
291 				default: return kk-59+KEY_F1; // Function keys
292 			}}
293 		case 224: {
294 			int kk;
295 			switch (kk = getch()) {
296 				case 71: return KEY_HOME;
297 				case 72: return KEY_UP;
298 				case 73: return KEY_PGUP;
299 				case 75: return KEY_LEFT;
300 				case 77: return KEY_RIGHT;
301 				case 79: return KEY_END;
302 				case 80: return KEY_DOWN;
303 				case 81: return KEY_PGDOWN;
304 				case 82: return KEY_INSERT;
305 				case 83: return KEY_DELETE;
306 				default: return kk-123+KEY_F1; // Function keys
307 			}}
308 		case 13: return KEY_ENTER;
309 version (Windows)
310 		case 27: return KEY_ESCAPE;
311 version (Posix) {
312 		case 155: // single-character CSI
313 		case 27: {
314 			// Process ANSI escape sequences
315 			if (cnt >= 3 && getch() == '[') {
316 				switch (k = getch()) {
317 					case 'A': return KEY_UP;
318 					case 'B': return KEY_DOWN;
319 					case 'C': return KEY_RIGHT;
320 					case 'D': return KEY_LEFT;
321 				}
322 			} else return KEY_ESCAPE;
323 		}
324 }
325 		default: return k;
326 	}
327 }
328 
329 /// Function: nb_getch
330 /// Non-blocking getch(). Returns 0 if no key was pressed.
331 int nb_getch() {
332 	if (kbhit()) return getch();
333 	else return 0;
334 }
335 
336 /// Function: getANSIColor
337 /// Return ANSI color escape sequence for specified number 0-15.
338 ///
339 /// See <Color Codes>
340 RLUTIL_STRING_T getANSIColor(const(int) c) {
341 	switch (c) {
342 		case 0 : return ANSI_BLACK;
343 		case 1 : return ANSI_BLUE; // non-ANSI
344 		case 2 : return ANSI_GREEN;
345 		case 3 : return ANSI_CYAN; // non-ANSI
346 		case 4 : return ANSI_RED; // non-ANSI
347 		case 5 : return ANSI_MAGENTA;
348 		case 6 : return ANSI_BROWN;
349 		case 7 : return ANSI_GREY;
350 		case 8 : return ANSI_DARKGREY;
351 		case 9 : return ANSI_LIGHTBLUE; // non-ANSI
352 		case 10: return ANSI_LIGHTGREEN;
353 		case 11: return ANSI_LIGHTCYAN; // non-ANSI;
354 		case 12: return ANSI_LIGHTRED; // non-ANSI;
355 		case 13: return ANSI_LIGHTMAGENTA;
356 		case 14: return ANSI_YELLOW; // non-ANSI
357 		case 15: return ANSI_WHITE;
358 		default: return "";
359 	}
360 }
361 
362 /// Function: setColor
363 /// Change color specified by number (Windows / QBasic colors).
364 ///
365 /// See <Color Codes>
366 void setColor(int c) {
367 	version (Windows) {
368 		HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
369 		SetConsoleTextAttribute(hConsole, cast(WORD)c);
370 	}
371 	
372 	
373 	version (Posix) {
374 		printf("%s", getANSIColor(c).toStringz);
375 	}
376 }
377 
378 void cls() {
379 	if (Windows == true) {
380 		// TODO: This is cheating...
381 		executeShell("cls");
382 	}
383 
384 	else {
385 		printf("%s", "\033[2J\033[H".toStringz);
386 	}
387 }
388 
389 /// Function: locate
390 /// Sets the cursor position to 1-based x,y.
391 //void locate(int x, int y) {
392 void locate(int x, int y) {
393 	version (Windows) {
394 		import std.conv;
395 		COORD coord;
396 		coord.X = to!(short)(x-1);
397 		coord.Y = to!(short)(y-1); // Windows uses 0-based coordinates
398 		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
399 	}
400 
401 	version (Posix) {
402 		char* buf;
403 		const(char*) format = "\033[%d;%df";
404 		sprintf(buf, format, y, x);
405 		printf("%s", buf);
406 	}
407 }
408 
409 /// Function: hidecursor
410 /// Hides the cursor.
411 void hidecursor() {
412 	version (Windows) {
413 		HANDLE hConsoleOutput;
414 		CONSOLE_CURSOR_INFO structCursorInfo;
415 		hConsoleOutput = GetStdHandle( STD_OUTPUT_HANDLE );
416 		GetConsoleCursorInfo( hConsoleOutput, &structCursorInfo ); // Get current cursor size
417 		structCursorInfo.bVisible = FALSE;
418 		SetConsoleCursorInfo( hConsoleOutput, &structCursorInfo );
419 	}
420 
421 	version (Posix) {
422 		printf("%s", "\033[?25l".toStringz);
423 	}
424 }
425 
426 /// Function: showcursor
427 /// Shows the cursor.
428 void showcursor() {
429 	version (Windows) {
430 		HANDLE hConsoleOutput;
431 		CONSOLE_CURSOR_INFO structCursorInfo;
432 		hConsoleOutput = GetStdHandle( STD_OUTPUT_HANDLE );
433 		GetConsoleCursorInfo( hConsoleOutput, &structCursorInfo ); // Get current cursor size
434 		structCursorInfo.bVisible = TRUE;
435 		SetConsoleCursorInfo( hConsoleOutput, &structCursorInfo );
436 	}
437 
438 	version (Posix) {
439 		printf("%s", "\033[?25h".toStringz);
440 	}
441 }
442 
443 /// Function: msleep
444 /// Waits given number of milliseconds before continuing.
445 void msleep(uint ms) {
446 	version (Windows) {
447 		Sleep(ms);
448 	}
449 	
450 	version (Posix) {
451 		// usleep argument must be under 1 000 000
452 		if (ms > 1000) sleep(ms/1000000);
453 		usleep((ms % 1000000) * 1000);
454 	}
455 }
456 
457 ///// Function: trows
458 ///// Get the number of rows in the terminal window or -1 on error.
459 //int trows() {
460 	//version (Windows) {
461 		//CONSOLE_SCREEN_BUFFER_INFO csbi;
462 		//if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
463 			//return -1;
464 		//else
465 			//return csbi.srWindow.Bottom - csbi.srWindow.Top + 1; // Window height
466 			//// return csbi.dwSize.Y; // Buffer height
467 	//}
468 	
469 	//ttysize ts;
470 	//ioctl(STDIN_FILENO, TIOCGSIZE, &ts);
471 	//return ts.ts_lines;
472 //}
473 
474 ///// Function: tcols
475 ///// Get the number of columns in the terminal window or -1 on error.
476 //int tcols() {
477 	//version (Windows) {
478 		//CONSOLE_SCREEN_BUFFER_INFO csbi;
479 		//if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
480 			//return -1;
481 		//else
482 			//return csbi.srWindow.Right - csbi.srWindow.Left + 1; // Window width
483 			//// return csbi.dwSize.X; // Buffer width
484 			
485 		//winsize ts;
486 		//ioctl(STDIN_FILENO, TIOCGWINSZ, &ts);
487 		//return ts.ws_col;
488 	//}
489 //}
490 
491 /// Function: anykey
492 /// Waits until a key is pressed.
493 void anykey() {
494 	getch();
495 }