Quick question on newlines..
blargmob

Is there a newline sequence that is supported for allegro's text output methods? It seems that \n dudn't work. ???

kazzmir

no

blargmob

Dang, thanks anyways..

Neil Black

There isn't? That seems kinda not cool. Brings to mind the fact that Allegro makes cin unusable but offers no replacement...

Edgar Reynaldo

Write a wrapper function for multiline text output. Give it the starting string including newlines , parse it into lines , use function parameters for the starting position , justification , vertical line spacing and text color for foreground/background and use the allegro text functions to display each line in the proper placement. ;)

Timorg

Well I don't see what std::cin has to do with text output. :P

And std::cout still works, it outputs data to the console if one is available, otherwise the output is hidden. as with std::cin, it should read from the console, but if is a blocking function, so your game will pause while it gets input.

I am off to check that... -Tim

Edit: It definitely applies to linux, buggering off to windows to test mingw :)
Edit2: It works fine under windows too.

Under both it spawns a console when you call std::cin.

Neil Black

It has nothing to do with text output, it's just something that's on my very short list of complaints about allegro. This thread lengthened that list to two things.

Timorg

A way to implement Edgar Reynaldo's idea could be.

You could scan the string, when you find a newline, replace it with '\0', output the string, move your pointer to the character after the newline, move down 10 pixels (with the default font) and repeat til you reach the '\0'. Then run back along the string putting back all the newlines.

If you complain enough, someone will implement it for you, as its quite trivial. If I get bored later, I might even bother.

-Tim

Edgar Reynaldo

I think Timorg's improvement would be faster and pretty easy too.

Thomas Harte
Quote:

I think Timorg's improvement would be faster and pretty easy too.

There's absolutely no way you can justify not using the text_height function though.

OICW

So the multiline output is solved. Now as far as input goes you can use this.

Audric

Just a little warning on Timorg's version: It's extremely efficient but you had better document on your function that it modifies the string it received as input.
(If you pass static data that is normally in 'const' area, a touchy OS or a memory protector is allowed to complain. loudly.)

OICW

That depends whether you are doing those operations on the input string or you're copying it to some teporary string.

Timorg
1#include <allegro.h>
2 
3void textout_ml_ex(BITMAP *bmp, const FONT *f, const char *s, int x, int y, int color, int bg)
4{
5 const char *start;
6 char *p, *line_start;
7 int cy;
8 
9 start = s;
10 p = (char *)s;
11 line_start = (char *)s;
12 cy = y;
13 
14 while (1)
15 {
16 if ((*p) == '\0')
17 break;
18 if ((*p) == '\n')
19 {
20 (*p) = '\0';
21 textout_ex(bmp, f, line_start, x, cy, color, bg);
22 line_start = p + 1;
23 cy += text_height(f);
24 }
25 p++;
26 }
27 
28 textout_ex(bmp, f, line_start, x, cy, color, bg);
29 
30 while(p != start)
31 {
32 p--;
33 if ((*p) == '\0')
34 (*p) = '\n';
35 }
36}
37 
38int main()
39{
40 allegro_init();
41 install_keyboard();
42 set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
43 
44 
45 char str[] = "This is a big long string\nit has quite a few lines of text\nhere is one\nhere is another";
46 
47 textout_ex(screen, font, str, 0, 0, makecol(255, 255, 255), -1);
48 textout_ml_ex(screen, font, str, 0, 20, makecol(255, 255, 255), -1);
49 
50 readkey();
51 set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
52 
53 return 0;
54}
55END_OF_MAIN()

I got bored, and as with what Audric was saying, this code isn't thread safe, if you try to use the string elsewhere at the same time, you will get strange things happening.

As for the other part of what Audric suggested, if your using STL strings, and call std::string.c_str() and pass it to the function, it depends on the implementation of STL to what will happen. Could be fine or yet again strange things might happen.

Thomas Harte: I didn't mention text_height function, cause I was trying to keep the example simple.

-------------

Edit:

I got bored again, here is a c++ implementation that uses std::string

1#include <allegro.h>
2#include <string>
3 
4void textout_ml_ex(BITMAP *bmp, const FONT *f, const std::string s, int x, int y, int color, int bg)
5{
6 std::string leftovers = s;
7 int cy = y;
8 
9 while (1)
10 {
11 int count = leftovers.find("\n");
12 if (count == -1)
13 break;
14 std::string current = leftovers.substr(0, count);
15 leftovers = leftovers.substr(count + 1);
16 textout_ex(bmp, f, current.c_str(), x, cy, color, bg);
17 cy += text_height(f);
18 }
19 textout_ex(bmp, f, leftovers.c_str(), x, cy, color, bg);
20}
21 
22int main()
23{
24 allegro_init();
25 install_keyboard();
26 set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
27 
28 
29 std::string str("This is a big long string\nit has quite a few lines of text\nhere is one\nhere is another");
30 
31 textout_ex(screen, font, str.c_str(), 0, 0, makecol(255, 255, 255), -1);
32 textout_ml_ex(screen, font, str, 0, 20, makecol(255, 255, 255), -1);
33 
34 readkey();
35 set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
36 
37 return 0;
38}
39END_OF_MAIN()

This is thread safe, and you can pass a null terminated string into it and it will work fine. :)

Audric

I like the cleverness of the first solution anyway. My own hack would NOT have changed back the '\0' to '\n' :) Thread-proofness is certainly never an issue here (drawing to bitmap = job for one thread), and as long as the string is writable, the memory managers/watchers have nothing to complain about.

I've been maintaining a codebase which had several..hmm..peculiarities like this, and as long as the prerequisites are listed at the function declaration, it's extremely reliable.

Arthur Kalliokoski

If you guys keep going, you'll come up with textprintf_ex() !

kazzmir

Except textprintf_ex doesn't handle newlines which is what prompted the whole debate.

Kitty Cat

You don't need to scan the string manually for newlines. strchr is your friend:

1int my_textout(BITMAP *bmp, FONT *font, const char *str, int x, int y, int fg, int bg)
2{
3 char *str_copy = strdup(str);
4 char *cur, *next;
5 int i = 0;
6 
7 cur = str_copy;
8 while(cur)
9 {
10 next = strchr(cur, '\n');
11 if(next) *next = 0;
12 i += textout_ex(bmp, font, cur, x, y, fg, bg);
13 if(next) *(next++) = '\n';
14 y += text_height(font);
15 cur = next;
16 }
17 
18 free(str_copy);
19}

Neil Black
Quote:

Now as far as input goes you can use this.

Yeah, but that seems a bit much just to let the player name his character...

Timorg

After fixing the errors in Kitty Cat's code. ie. removing the redundant 'i' variable, and changing the return value to void. :P I then made an optimization, where I removed one of the if statements.

I then made a version of my code where it duplicates the string, before messing with it, so that I wouldn't have to put all the '\n's in the right place.

I then stripped out the allegro stuff, and made a program that calls each function 10000000 times.

1#include <string.h>
2#include <stdlib.h>
3 
4void textout_ml_ex0(const char *s, int x, int y)
5{
6 const char *start;
7 char *p, *line_start;
8 int cy;
9 
10 start = s;
11 p = (char *)s;
12 line_start = (char *)s;
13 cy = y;
14 
15 while (1)
16 {
17 if ((*p) == '\0')
18 break;
19 if ((*p) == '\n')
20 {
21 (*p) = '\0';
22 line_start = p + 1;
23 }
24 p++;
25 }
26 
27 
28 while(p != start)
29 {
30 p--;
31 if ((*p) == '\0')
32 (*p) = '\n';
33 }
34}
35 
36void textout_ml_ex1(const char *s, int x, int y)
37{
38 const char *start;
39 char *p, *line_start;
40 int cy;
41 
42 
43 char *copy = strdup(s);
44 
45 start = copy;
46 p = (char *)copy;
47 line_start = (char *)copy;
48 cy = y;
49 
50 while (1)
51 {
52 if ((*p) == '\0')
53 break;
54 if ((*p) == '\n')
55 {
56 (*p) = '\0';
57 line_start = p + 1;
58 }
59 p++;
60 }
61 
62 free(copy);
63}
64 
65void my_textout0(const char *str, int x, int y)
66{
67 char *str_copy = strdup(str);
68 char *cur, *next;
69 
70 cur = str_copy;
71 while(cur)
72 {
73 next = strchr(cur, '\n');
74 if(next) *next = 0;
75 if(next) *(next++) = '\n';
76 cur = next;
77 }
78 
79 free(str_copy);
80}
81 
82void my_textout1(const char *str, int x, int y)
83{
84 char *str_copy = strdup(str);
85 char *cur, *next;
86 
87 cur = str_copy;
88 while(cur)
89 {
90 next = strchr(cur, '\n');
91 if (next)
92 {
93 *next = 0;
94 *(next++) = '\n';
95 }
96 cur = next;
97 }
98 
99 free(str_copy);
100}
101 
102int main()
103{
104 char str[] = "This is a big long string\nit has quite a few lines of text\nhere is one\nhere is another";
105 int c;
106 
107 for (c = 0; c < 10000000; c++)
108 {
109 my_textout0(str, 0, 20);
110 }
111 
112 for (c = 0; c < 10000000; c++)
113 {
114 my_textout1(str, 0, 20);
115 }
116 
117 for (c = 0; c < 10000000; c++)
118 {
119 textout_ml_ex0(str, 0, 20);
120 }
121 
122 for (c = 0; c < 10000000; c++)
123 {
124 textout_ml_ex1(str, 0, 20);
125 }
126 
127 
128 return 0;
129}

I then profiled the program and got the following results.

  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ns/call  ns/call  name    
 58.79      9.67     9.67 10000000   967.05   967.05  textout_ml_ex0
 32.83     15.07     5.40 10000000   540.03   540.03  textout_ml_ex1
  3.83     15.70     0.63 10000000    63.00    63.00  my_textout0
  2.61     16.13     0.43 10000000    43.00    43.00  my_textout1
  1.95     16.45     0.32                             main

As you can see Kitty Cat's function is much more efficent than mine, its actual 15 times faster than my original code.

Then if you optimize it (-O1) the difference is even more pronounced

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ns/call  ns/call  name    
 62.63      4.44     4.44 10000000   444.02   444.02  textout_ml_ex0
 31.31      6.66     2.22 10000000   222.01   222.01  textout_ml_ex1
  2.19      6.82     0.16 10000000    15.50    15.50  my_textout0
  2.05      6.96     0.15 10000000    14.50    14.50  my_textout1
  1.83      7.09     0.13                             main

Which brings me to the final conclusion...

If you want multi-line text output use Kitty Cat's code (after you modify it so that it will actually compile without errors. ;D

-Tim

OICW
Quote:

Yeah, but that seems a bit much just to let the player name his character...

With that code you can do pretty much everything you want. I have a working console using that code for input.

Neil Black

I was saying that if the only reason you wanted text input was to get the player's name, that would be a lot of code to do it with. It's worth it if you're actually using a lot of text input, but it's just too much if you only use it once, which is about how much I would use it in a game.

OICW

As far as I know there's no other simple way of doing this in Allegro.

Neil Black

I know, that's the problem. Allegro include functions to replace (and vastly improve upon) cout, but completely ignores the need for input. I'm going to use the mind control routines in Allegro 5 to make someone put input routines in Allegro 6. ;D

OICW

The problem is with how the keyboard is managed. You can write your scanf/cin function that will hide that code above from you.

Neil Black

Yeah, I know it isn't that hard to do. But when I was first starting out I had no idea how to do it.

OICW

Reminds me of myself five years ago (that's approximately how that thread is old).

Edgar Reynaldo

I have a question about the modified version of Kitty Cat's multi line text output. Specifically the line :

           *(next++) = '\n';

From :

1void my_textout1(const char *str, int x, int y)
2{
3 char *str_copy = strdup(str);
4 char *cur, *next;
5 
6 cur = str_copy;
7 while(cur)
8 {
9 next = strchr(cur, '\n');
10 if (next)
11 {
12 *next = 0;
13 *(next++) = '\n';
14 }
15 cur = next;
16 }
17 
18 free(str_copy);
19}

Since a copy is being worked on , the newline doesn't need to be replaced , just setting the address to the next character should suffice. What I don't understand is why the character after the detected newline should be set to a newline? Isn't "*(next++)" the same as "*(next + 1)"? Since 'cur' gets set to the character position right after where the newline is detected and that character position's value has been set to a newline , why doesn't strchr(cur , '\n') keep returning the same value as 'cur'?

In any case , I came up with another version , based on Kitty Cat's as well :

1 
2void multiline_textout_v1(BITMAP* bmp , FONT *font , const char *ml_str , int x , int y , int v_spacing , int fg , int bg) {
3 // note that this will only output the text lines of ml_str if each one ends with a newline '\n' character
4 // so "Line 1\nLine 2\n" would work , but not "Line 1\nLine 2"
5 // And so a normal string would not work either - ("Line 1") would fail
6 // due to the lack of a newline character
7 char* copy = strdup(ml_str);
8
9 char* line = copy;
10 char* endline = strchr(line , '\n');
11
12 while(endline) {
13 *endline = '\0';
14 textout_ex(bmp , font , line , x , y , fg , bg);
15 endline = strchr(line , '\n');
16 line = endline + 1;
17 y += v_spacing;
18 }
19
20 free(copy);
21}

Mark Oates

Openlayer's TextRenderer does newlines automatically. 8-)

Kitty Cat
Quote:

Since a copy is being worked on , the newline doesn't need to be replaced , just setting the address to the next character should suffice.

Correct. A mistake on my part.. :P

Quote:

What I don't understand is why the character after the detected newline should be set to a newline? Isn't "*(next++)" the same as "*(next + 1)"?

No. next++ post-increments the 'next' variable. It increments the variable, but "returns" the original value. ++next would increment the variable and give the new value.

A fixed version of my code would be:

1void my_textout(BITMAP *bmp, FONT *f, const char *str, int x, int y, int fg, int bg)
2{
3 char *str_copy = strdup(str);
4 char *cur, *next;
5 
6 cur = str_copy;
7 while(cur)
8 {
9 next = strchr(cur, '\n');
10 if(next) *(next++) = 0;
11 textout_ex(bmp, f, cur, x, y, fg, bg);
12 y += text_height(f);
13 cur = next;
14 }
15 
16 free(str_copy);
17}

Edgar Reynaldo
Quote:

No. next++ post-increments the 'next' variable. It increments the variable, but "returns" the original value. ++next would increment the variable and give the new value.

Weird. Not what I would expect to happen given that it's in parentheses. I never thought of post-incrementation as returning the original value.

Had to fix a bug in the version I put up and I also added in a function pointer parameter for which text function gets called. So now you can call textout_ex , textout_centre_ex , or textout_right_ex as an input parameter. My version now also displays the trailing end of the string containing newlines so you don't have to end it with a newline character. Use the v_spacing parameter to control the vertical spacing , and enter one of the three aforementioned allegro text functions for the text_func parameter.

Revised code :

1 
2void multiline_textout_v1a(BITMAP* bmp , FONT *font , const char *ml_str , int x , int y , int v_spacing , int fg , int bg ,
3 void (*text_func) (BITMAP* , const FONT* , const char* , int , int , int , int)) {
4 
5 char* copy = strdup(ml_str);
6
7 char* line = copy;
8 char* endline = strchr(line , '\n');
9
10 while(endline) {
11 *endline = '\0';
12 text_func(bmp , font , line , x , y , fg , bg);
13 line = endline + 1;
14 endline = strchr(line , '\n');
15 y += v_spacing;
16 }
17 endline = strchr(line , '\0');
18 if (line != endline) {
19 text_func(bmp , font , line , x , y , fg , bg);
20 }
21
22 free(copy);
23}

Thread #594780. Printed from Allegro.cc