andymeneely / squib

A Ruby DSL for prototyping card games.
http://squib.rocks
MIT License
918 stars 67 forks source link

More flexible save_png names #340

Closed nickolasreynolds closed 3 years ago

nickolasreynolds commented 3 years ago

It would be nice if you could simply specify a filename for save_png rather than having to muck about with prefix, suffix, and the (apparently un-disable-able) count_format.

I'm having to write a separate script to post-process the names that Squib outputs because I can't get Squib to just let me do what I want to do (which is to simply save each PNG with the card's name, without any count number attached.)

There is also inconsistent behavior with the existing functionality, for example I can successfully specify save_png prefix: deck['Name'] and my output looks like FirstCardName00.png SecondCardName01.png etc.

But save_png suffix: deck['Name'] doesn't work at all, it just outputs Card_00.png Card_01.png etc. And, again, there's no way to disable the annoying numbers!

There also doesn't seem to be a way to programmatically build up a more complicated string. Attempting to do so, e.g. 'prefix' + deck['Name'] + 'suffix' results in an error: "no implicit conversion of Array into String".

As with almost every other Squib function, I find myself being forced to manually unroll the "think in arrays" paradigm in order to get anything done:

d[NAME].each.with_index do |name, i|
unless name.nil?

    save_png
        prefix: 'N' + format('%03d', i.to_s)[-3..-1] + '_' + d[NAME][i],
        count_format: '',
        range: i

end # unless name.nil?
end # d[NAME].each.with_index do |name, i|
andymeneely commented 3 years ago

If you don't want numbers in the filename, set count_format: '' and it's gone. That's how I disable them when I don't want them.

The suffix thing I'll have to look into. It definitely should behave like prefix.

Why are you doing string formatting when that's precisely what count_format does? I'm not sure what you're trying to accomplish with formatting i right after disabling those "annoying" numbers. Also, it seems overly complicated to do a separate save_png for each i.

Yes, Ruby arrays don't have the + operator defined for Strings. I recommend map if you want to interpolate a string over an array of data.

For building up complicated strings, what about:

save_png count_format: '', prefix: data['title'].map { |t| "myprefix_#{t}_mysuffix" }
andymeneely commented 3 years ago

AFAICT suffix is treated just like prefix. Here's an example:

require 'squib'

Squib::Deck.new(width: 50, height: 50, cards: 2) do 
    background color: :white
    letters = %w(a b)
    text str: letters
    save_png suffix: letters
end

This produces card_00a.png and card_00b.png that look like this:

card_00a card_01b

nickolasreynolds commented 3 years ago

I have to format i myself if I want my cardname to be a suffix, because suffix: d[NAME] doesn't work on my end, but prefix: d[NAME] does. Anyway, this isn't a showstopper, as you may be able to tell from my many edits to the original post, I slowly found ways around all of the problems I was having.

Anyway, as I mentioned in the OP, almost everything I'm doing in my scripts requires me to simply loop through my entire deck manually and process each card individually, because it's nearly always impossible to use d['Whatever'] to achieve complicated effects the way I want to.

That's not a problem in and of itself, but it does butt heads with what I gather is one of the main design paradigms of the library, so I just thought I'd point out it would be nice if Squib truly allowed you to "think in arrays" and just refer to card attributes without carefully managing indices manually.

andymeneely commented 3 years ago

Can you come up with a minimal example of suffix not working? Did you see my example above?

Where else does it not think in arrays? I almost never have index things manually, so I'm curious what you're trying to do that's unique so we can come up with a better way to do it.

nickolasreynolds commented 3 years ago

On my end, save_png prefix: d[NAME] outputs AnActualCardName00.png, AnotherActualCardName01.png, etc. save_png suffix: d[NAME] outputs card_00.png, card_01.png, etc.

For most elements on my cards, I format things based on the unique content of the card itself. For example, most text elements have dynamically sized text so that it stays within the bounds of various background graphical elements.

Another example is that within the primary textbox, the formatting and centering change based on whether the card has rules text or flavortext or neither or both, and how long each is.

Another example is that I keep a list of the graphical extents of every element rendered on every card, so that I can "hint" literally everything if I want to, for debugging positioning issues and the like. (This bookkeeping is also necessary to dynamically position elements relative to one another.)

As I interpret the concept, the "thinking in arrays" approach would be to simply define a chain of logic and then feed it the data array representing the deck. (Maybe I'm just naive or misinterpret what "thinking in arrays" is supposed to mean.)

And that's actually what the original version of my rendering script looked like. But as I slowly complexified the functionality to render things that were closer to actual cards and not just simple mockups, and to make the script usable for people other than myself, almost every single rendered element in my script changed from something like

    png     file:       d[ART],
            x:      art_box.x1,
            y:      art_box.y1,
            width:      art_box.w,
            height:     art_box.h,

to something like

d[ART].each.with_index do |art, i|

    temp = nil

    if art.nil? and d[NAME][i]

        # Is the data file missing art for this card?
        # Try guessing NAME.png

        temp = "#{d[NAME][i]}.png"

    else

        temp = art

    end

    unless temp.nil?

        if not File.exist?(ART_PATH + temp) then next end

    png     file:       ART_PATH + temp,
            x:      art_box.x1,
            y:      art_box.y1,
            width:      art_box.w,
            height:     art_box.h,
            range:      i

    end

end # d[ART].each.with_index do |art, i|

so that in the end I'm using Squib as little more than a wrapper for some Cairo and Pango functions.

andymeneely commented 3 years ago

What version of Squib are you using? My guess is that you're on an old version. Upgrade to the latest - suffix was added in v0.16

A few ideas to simplify the code a bit. I haven't tested this just riffing. But your data table is really for all things one-off, so maybe it's time to put things like xywh into it.

# put your x/y/w/h into your data file, so it might look like this:
# | name  | x | y | w | h |
# |-------|---|---|---|---|
# | rogue | 1 | 2 | 3 | 4 |
# | mage  | 1 | 2 | 2 | 4 |

# your data goes into the variable d
# this next part is based this on this chunk of my own code: https://github.com/andymeneely/project-timber-wolf/blob/master/src/events.rb#L31-L41
# you can do a file exist check here - i do that at the moment in the above link
filenames = d.name.map { |name| "#{name.to_s.downcase}.svg" } 
png file: filenames,
    x: d.x1,
    y: d.y1,
    width: d.w,
    height: d.h

It would be nice if we had more options for error handling around the image not existing. I've got an issue open for that (#339).

But the above avoids if-statements and traditional loops altogether. I prefer to use to_s instead of nil checks.

You don't need ART_PATH + temp because you can set the image directory in the config. But, if you still want it custom, you can add that to the map method.

Also: check out the Ruby style guidelines if you really want to improve maintainability. This is the most accepted standard: https://github.com/rubocop/ruby-style-guide, and rubocop can help find a lot of those things

nickolasreynolds commented 3 years ago

Things like xywh really can't be put in a data table, they're determined separately and dynamically for every element on every card, based on the disposition of previous elements.

I use the data table for the contents of the cards, not the results of a long chain of formatting logic. (Besides, if I stored all the xywh information the data table, I'd add like 200 columns to the data table ...)

Keeping a hardcoded table of all of that information would also make changing a layout virtually impossible. As it stands right now, I can simply adjust the position of, say, the title bar, and every element on the title bar on every card (that has a title bar) automatically realigns with it, and everything positioned relative to the title bar automatically realigns with it, and everything positioned relative to any of those things automatically realign with them, etc., because the xywh of those elements refer to the xywh of their relevant "parents", not to numbers in a table.

Want to change the height or width of the entire card template? Edit a single number, and every other element on every card is automatically repositioned, no further editing is needed, everything just works. Data tables can't do that, even if I thought keeping 200 position coordinates in my card data table was reasonable.

andymeneely commented 3 years ago

Sounds complicated. I mean, a spreadsheet can do math for you and update dependencies... but I get what you're saying.

If every card is really so different, then maybe it's time to just have separate image files and do it traditionally without automation. It sounds like you're running into this phenomenon: https://xkcd.com/1319/

I can see how the organization of a card might change if things all depend on each other. But I also wonder... don't players expect consistency? If icons are in different places depending on subtle factors then I imagine players would have trouble usability-wise.

I don't know. Just my two cents.

FWIW, tonight I implemented a placeholder feature and img_missing option. It's in my dev branch if you want to play with it. Once I get a chance to test it out on my projects I'll release it to v0.18.

Anyway, FWIW I think I've answered your original question and given you all the tools you need. I'm closing this now. Best of luck!