Closed FractalWire closed 5 years ago
All of your examples seem to be solvable using either bg=color
or bg=None
and they are similar to the kind of code that came up often when I was updating the Python samples. I don't think that adding bg=None
is overly verbose.
console = tcod.console.Console(width, height)
console.clear(bg=color1)
console.print(x, y, value, bg=None)
I'm very skeptical about a default of fg=None
. You're assuming that the consoles foreground will be a solid color which is rarely the case. If the default was None then some users might expect the colors to be white-on-black based on its initial observable behavior or based on the previous libtcod API and then be surprised when sometimes the foreground text is not written out as white when they mix colors with the default values.
The current reasoning for white-on-black is that it's consistent everywhere. With the new methods you can read them just by looking at the calls themselves:
# ...
# Previous code does not affect method parameter defaults in any way.
console.print(x, y, text) # White-on-black.
console.print_box(x, y, width, height, text) # White-on-black.
console.draw_rect(x, y, width, height, ch) # White-on-black.
console.clear() # White-on-black.
# Non-default colors can be traced back to named variables, instead of being hidden as a default.
console.print(x, y, text, color1, color2) # Colors from named variables.
console.print(x, y, text, bg=color2) # White-on-color2.
console.print(x, y, text, **style) # Colors and settings overridden by a named dictionary.
# Not overriding colors is a special circumstance.
console.print(x, y, text, None, None) # Use existing foreground and background color.
console.print(x, y, text, bg=None) # White text on the existing background color.
console.print(x, y, text, color1, None) # color1 text on the existing background color.
Most importantly, the newer methods avoid this exaggerated but very real example:
foo()
console.print_(x, y, text)
bar()
console.print_(x, y, text)
The natural use of setting default values as they're needed can cause a huge mess:
foo() # Breaks if default values are changed before calling.
console.print_(x, y, text) # Light-yellow-on-grey, obviously! Also, center justify!
bar() # Depends on default values set by foo, so don't ever change them in or after foo.
console.print_(x, y, text) # Different colors and blend mode than the above call to print_!
# Changing any defaults at this point will break the next call to foo.
# All of the above code works correctly, but is fragile.
If you can imagine me fixing the above code to remove the default values then you'll understand why I'm strongly against continuing to support them in any way.
bg=None
is super useful, and it's definitely going to be used more often than the default value of bg=(0, 0, 0)
, but bg=None
is too special to make it the default. I don't want to look at a call to console.print(x, y, text)
and wonder if it was intentional for it to not override the foreground or background colors.
The default color of white-on-black is not a suggestion to actually use or prefer those colors, it's just documentation for how those functions behave by default when those parameters are not provided under the assumption that you'll be reading others code or your own. It's a placeholder until you can decide on any color scheme other than white-on-black.
It's definitely a difference of appreciation here and I can for sure use None to solve my problem.
If the default was None then some users might expect the colors to be white-on-black based on its initial observable behavior or based on the previous libtcod API
It could be the other way around if they previously defined fg/bg for the part where they are printing stuff. And you can take some liberty with those new methods given that they are new, and the old one deprecated.
I tend to see the console as a place where you will assemble stuff progressively. I guess I see a console like some div element in an html page. Once I set its color and text color, I expect the child of this div to have the same color and text color, except if the child overrides it. And a div's child printing arbitrarily white-on-black would be weird. In a lot of way comparing div and console doesn't make sense, but I hope you can get what I'm going with this. If I'm writing something on top of something else in the console, I expect this new thing to respect the previously defined attribute, like I would expect a div's child to respect its parent attribute.
console.print(x, y, text, color1, color2) # Colors from named variables.
console.print(x, y, text, bg=color2) # White-on-color2.
console.print(x, y, text, **style) # Colors and settings overridden by a named dictionary.
Those examples assume perfect knowledge over where you want to print, which might not always be the case. Let's take an example :
draw_terrain(console)
draw_monster(console)
Here, monster are drawn on top of the terrain, and you can assume that a terrain does not know where are the monster, and a monster does not know on what terrain it is on. But a monster on a river will have a blue background while a monster on some grass will have a green background. You can separate drawing logic here.
Granted you can just use None
in your print for the draw_monster function, but in my opinion, it should be the default behaviour.
If you can imagine me fixing the above code to remove the default values then you'll understand why I'm strongly against continuing to support them in any way.
I totally agree, your examples of the old print_
are nasty. But the new print
does not use the default attributes and it should not. The only place where the default_bg/fg should be use is with the clear
function. The default_alignement should not exist, and I don't fully understand the blend attribute yet to say if it could be usefull or not as a default.
With the new print
, yes, parts of the console might have modified bg/fg between functions (with a default to None), but it is restricted changes, and if you don't want those, you can always override them.
bg=None
is super useful, and it's definitely going to be used more often than the default value ofbg=(0, 0, 0)
, butbg=None
is too special to make it the default. I don't want to look at a call toconsole.print(x, y, text)
and wonder if it was intentional for it to not override the foreground or background colors.
In that case, go to the bottom of it and make bg and fg mandatory arguments, not optional. White on black is too arbitrary, even if well documented.
In a lot of way comparing div and console doesn't make sense, but I hope you can get what I'm going with this. If I'm writing something on top of something else in the console, I expect this new thing to respect the previously defined attribute, like I would expect a div's child to respect its parent attribute.
This isn't weird, there are many examples of terminal applications which are divided up into multiple parts like an web page, but this does seem to explain the unusual way you've been assigning to the bg array rather than just using clear().
To me that kind of stuff is done at a high-level, where you can have a class that encapsulates the console and lets you treat it like HTML. At that point the consoles default parameters hardly matter since that class would be micromanaging everything. libtcod actually has a GUI API but it was only in C++ so I wasn't able to port it. It'd also be easy to make a new API in Python if someone wanted to.
I see consoles as a low-level object, if you took away everything but the ch,fg,bg
arrays then it'd still be a console. Anything else is just a convenient way to interact with those arrays.
draw_terrain(console) draw_monster(console)
draw_terrain
is a bad example for the print and draw methods. A good implementation of this would be to store the terrain graphics in a NumPy array and transfer it directly into the console arrays. Anything looping over all console indexes and calling Python functions would run very slow.
I'd also implement draw_monster
using NumPy as I rarely ever use libtcod's blend modes.
I assume you were trying to say that if you don't specify colors to a method then it should assume the existing colors are okay. Like setting the colors for a UI element first before printing on top of it.
I still think white-on-black is more well defined than None/None but I'll go ahead and change it anyway since None/None would run a little bit faster and that's a decent enough reason.
To me that kind of stuff is done at a high-level, where you can have a class that encapsulates the console and lets you treat it like HTML.
For me too, most of the stuff that could be html-like would be treated at a higher-level. But that is what you said :
I assume you were trying to say that if you don't specify colors to a method then it should assume the existing colors are okay. Like setting the colors for a UI element first before printing on top of it.
If I change the value of a button, I don't care about color, I care only about printing new value.
At that point the consoles default parameters hardly matter since that class would be micromanaging everything. libtcod actually has a GUI API but it was only in C++ so I wasn't able to port it. It'd also be easy to make a new API in Python if someone wanted to.
I'm actually making exactly that here. A tree-like organisation for UI element that manages mouse/keyboard focus. But it's still pretty alpha and there is some weird stuff sometime. I'll probably post it on r/roguelikdev once I feel it's more stable. But the default parameters of the console still matter as I found it more convenient to not rewrite my base color everytime when I draw something on the console.
I see consoles as a low-level object, if you took away everything but the
ch,fg,bg
arrays then it'd still be a console. Anything else is just a convenient way to interact with those arrays.
Yes, I was using width and height of the console, until I realized it was not a great idea, and that width, height should be managed at a higher level.
draw_terrain
is a bad example for the print and draw methods. A good implementation of this would be to store the terrain graphics in a NumPy array and transfer it directly into the console arrays. Anything looping over all console indexes and calling Python functions would run very slow.
I've been assuming that working directly with ch,bg,fg
is equivalent to working with a distinct numpy array and then copying this array to ch/bg/fg
.
Or rather, I've been assuming that it's better to directly work with ch,bg,fg
, as it doesn't have the overhead of copying an entire numpy array to another.
But looking at the source I don't really understand how ch,fg,bg
interact with console_c
.
If I understand well, methods like print
uses the libtcod API directly. Wouldn't it be better if instead of it they would manipulate ch,bg,fg
instead, so that it does not leave the boundary of python until you want to blit the console ?
To be clear, I am aware that looping over a Numpy array is also not a good idea, but sometime, you need the loop anyway and I assume that looping over a Numpy array is better than looping over the console_c with a call to the libtcod API.
I've been assuming that working directly with
ch,bg,fg
is equivalent to working with a distinct numpy array and then copying this array toch/bg/fg
. Or rather, I've been assuming that it's better to directly work withch,bg,fg
, as it doesn't have the overhead of copying an entire numpy array to another.But looking at the source I don't really understand how
ch,fg,bg
interact withconsole_c
. If I understand well, methods likech,bg,fg
instead, so that it does not leave the boundary of python until you want to blit the console ?
console_c
is a struct TCOD_console*
type. This struct is initialized once with pointers to the NumPy ch,fg,bg
arrays which are created and owned by the Python Console itself. When console_c
is used with a call to C only the pointer to the struct is passed and the C function can interact with the NumPy arrays as if they were managed by C the entire time. The overhead that remains comes from converting all the other Python parameters into a C format, which only adds up when you call the methods too frequently.
Re-implementing the methods in NumPy wouldn't solve the problem since the "Python -> C" step still occurs even when working with NumPy. You'd have to write "NumPy -> NumPy" code to skip that step. Also, only draw_rect
and draw_frame
can be vectorised, the printing functions would be much slower written in Python as I learned the hard way when I wrote python-tdl.
To be clear, I am aware that looping over a Numpy array is also not a good idea, but sometime, you need the loop anyway and I assume that looping over a Numpy array is better than looping over the console_c with a call to the libtcod API.
It's not always easy but if you can make sure your inputs are arrays then you can vectorise the entire operation once you're more familiar with NumPy.
console_c
is astruct TCOD_console*
type. This struct is initialized once with pointers to the NumPych,fg,bg
arrays which are created and owned by the Python Console itself. Whenconsole_c
is used with a call to C only the pointer to the struct is passed and the C function can interact with the NumPy arrays as if they were managed by C the entire time. The overhead that remains comes from converting all the other Python parameters into a C format, which only adds up when you call the methods too frequently.
Okay. I think I get it... Both C and Python manipulate object in memory that have the same data type, so they can modify those without caring what the other language do with it. That is some high-flying programming stuff. So, it is safe to directly manipulate ch,bg,fg via Numpy method, without creating another Numpy array before-hand. In fact, it is better to manipulate ch,bg,fg directly especially if you don't use the same Numpy data type between your Numpy array and ch,bg,fg (because, then there would need to be some kind of cast above the array copy). (tell me if I'm wrong)
Re-implementing the methods in NumPy wouldn't solve the problem since the "Python -> C" step still occurs even when working with NumPy. You'd have to write "NumPy -> NumPy" code to skip that step. Also, only
draw_rect
anddraw_frame
can be vectorised, the printing functions would be much slower written in Python as I learned the hard way when I wrote python-tdl.
So anything that could not be vectorized, I should use the printing function, and for the rest, I should use Numpy. So basically, I should use the printing function for anything that write text.
It's not always easy but if you can make sure your inputs are arrays then you can vectorise the entire operation once you're more familiar with NumPy.
I definitely don't have this mindset yet. Vectorisation is hard.
So, it is safe to directly manipulate ch,bg,fg via Numpy method, without creating another Numpy array before-hand. In fact, it is better to manipulate ch,bg,fg directly especially if you don't use the same Numpy data type between your Numpy array and ch,bg,fg (because, then there would need to be some kind of cast above the array copy). (tell me if I'm wrong)
Casting from any NumPy array to another is often just as fast as copying regardless of type (it'd likely hit a memory speed bottleneck first, so smaller types are faster,) but casting large amounts of Python values to anything is slow. This discourages using Python classes for tiles and such, you'll want to implement them as a NumPy dtype instead.
So anything that could not be vectorized, I should use the printing function, and for the rest, I should use Numpy. So basically, I should use the printing function for anything that write text.
Methods like draw_rect
are still good if you don't need more functionality. You might prefer python-tcod's pathfinding and field-of-view algorithms over making your own implementation, and you can profile your code to figure out what you need to improve right at this moment.
Hey,
So, I'm looking on the new print method and I find the default
bg
behaviour weird.Here is a simple example of what I would expect :
Instead of having a value with
bg=color1
, I have abg=(0,0,0)
. It's weird and forced me to do that for every print I want to use :You have de facto a default color palette here which is white on black. If you use None as default, you don't have this issue anymore.
In fact, the deprecated
default_bg/fg
could still exist, but would never interact with thisprint
method.also : see my comment here