Point-based games and deciding tiebreakers

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Message
Author
User avatar
zomgenius
Regular
Posts: 27
Joined: Mon Apr 16, 2012 11:32 pm
Projects: Paper Stars
Location: USA
Contact:

Point-based games and deciding tiebreakers

#1 Post by zomgenius »

Hey there! I'm brand-new to Ren'Py, and coding all of this in general. I've been trying to figure out how to go about assigning point values to certain characters, and how to decide a winner out of those candidates. And further, how to solve a tiebreaker.

Here's what I've been trying to do:
I have about seven characters that the protagonist can choose from. I planned on assigning point values for favorable answers to questions, which is easy enough. And I learned that using 'if' and 'elif' codes, I can single out a specific person for the protagonist to continue to woo. But my question is this - what happens if two characters end up having the same point value by the time one must be chosen?

I've been reading over THIS tutorial, but it isn't much help; it only shows me what I've already learned regarding the if / elif statements and trying to make one choice over others or several simultaneous choices together.

Is there a way to make a statement that says, "in the case of a tie, allow the player to choose between the variables," and without having to make a very long list of "if A == B > max(C, D, E, F, G):" and "menu: "A": $ A +=1 " etc to choose between every single individual potential tie that exists?

So confusing!

User avatar
SleepKirby
Veteran
Posts: 255
Joined: Mon Aug 09, 2010 10:02 pm
Projects: Eastern Starlight Romance, Touhou Mecha
Organization: Dai-Sukima Dan
Location: California, USA
Contact:

Re: Point-based games and deciding tiebreakers

#2 Post by SleepKirby »

Here's one way to figure out which people have the highest number of points.

Code: Select all

python:
    points = {
        'Person A': person_a_points,
        'Person B': person_b_points,
        'Person C': person_c_points,
        'Person D': person_d_points,
        'Person E': person_e_points,
        'Person F': person_f_points,
        'Person G': person_g_points,
    }
    highest_points = max(points.values())
    people_with_highest_points = []
    for person, person_points in points.iteritems():
        if person_points == highest_points:
            people_with_highest_points.append(person)
        
if len(people_with_highest_points) > 1:
    # Let the player choose between multiple people.
else:
    # No ties.
Notes:
- points is a dictionary. This Python data structure makes it easier to keep the connections between the characters and their point values.
- people_with_highest_points is a list. If the people tied with the highest number of points turned out to be A, C, and G, then this list's value would end up being ['Person A', 'Person C', 'Person G'].
- When you have several lines of Python code in a row like this, it's often cleaner to put them in a python: block instead of putting $ in front of every line.

There's probably more work to be done after this, such as actually constructing the menu choices once you know there's a tie. So feel free to ask again if you're unsure about that.

User avatar
zomgenius
Regular
Posts: 27
Joined: Mon Apr 16, 2012 11:32 pm
Projects: Paper Stars
Location: USA
Contact:

Re: Point-based games and deciding tiebreakers

#3 Post by zomgenius »

Ah, excellent, you are such an amazing help. :D Thank you so much!!

I do have another question, though; it deals specifically with this part of the code:

Code: Select all

    highest_points = max(points.values())
    people_with_highest_points = []
    for person, person_points in points.iteritems():
        if person_points == highest_points:
            people_with_highest_points.append(person)

if len(people_with_highest_points) > 1:
    # Let the player choose between multiple people.
else:
    # No ties.
What do the various labels mean, such as 'len' or the 'points.iteritems,' etc? And once this happens in the game, how would the menu be constructed to go along with it? Would this portion of code automatically bring up the menu to decide the tiebreaker, or would I need to program that in elsewhere?

Oh, and for the different people, I assume 'Person A,' etc are labels that can be changed for each specific person. Would the 'person_a_points' be a variable set by me here, or one that automatically recalls the number of points based on the previous additions to code using the 'A +=1' sort of code earlier in the game? And that said, would the 'person_with_highest_points' bit immediately call up 'Person B' and 'Person C' if they were the two highest, and in a tie?

Thank you so much for your help so far, I really appreciate it!
Last edited by zomgenius on Tue Apr 17, 2012 12:13 pm, edited 1 time in total.

Valmoer
Regular
Posts: 53
Joined: Sat Feb 04, 2012 9:46 pm
Contact:

Re: Point-based games and deciding tiebreakers

#4 Post by Valmoer »

len is short for length : it is a function that return the length of the list that is passed as a parameter (here, the length of the lists of persons with the highest number of points.

points.iteritems() is an iterator. An iterator is an element that let you, as the name says, iterate over a collection - a collection being either a list [a,b,c], a dictonary {a:1, b:25, c:"zoo"} or a tuple (a,b,c). By passing the iterator as a parameter to the for ... in ... , it lets the program apply the same block of code to each of the values of the collection.

Here, for person, person_points in points.iteritems():
  • points is a dictionnary : it contains two values by item - the key (which let us access the item) and the value (which is... the value).
  • Thus, two variables, person and person_points are created to hold each of this value
  • Therefore, the line truely reads : "For each item in points, let's person and person_points hold the values of the two parts of the item (the key and the value).
how would the menu be constructed to go along with it? Would this portion of code automatically bring up the menu to decide the tiebreaker, or would I need to program that in elsewhere?
What you need is the renpy.display_menu(...) function.
renpy.display_menu (items, window_style='menu_window', interact=True, with_none=None):
Function: Displays a menu containing the given items, returning the value of the item the user selects.
items - A list of tuples that are the items to be added to this menu. The first element of a tuple is a string that is used for this menuitem. The second element is the value to be returned if this item is selected, or None if this item is a non-selectable caption.

User avatar
zomgenius
Regular
Posts: 27
Joined: Mon Apr 16, 2012 11:32 pm
Projects: Paper Stars
Location: USA
Contact:

Re: Point-based games and deciding tiebreakers

#5 Post by zomgenius »

Hmm... Seems like there is a lot I need to read up on, then. This has helped to clear up a lot for me, but I'm still a bit confused by how the code works as a whole.

Valmoer, thank you for your explanations! I had edited this part into my previous post right before you replied:
Oh, and for the different people, I assume 'Person A,' etc are labels that can be changed for each specific person. Would the 'person_a_points' be a variable set by me here, or one that automatically recalls the number of points based on the previous additions to code using the 'A +=1' sort of code earlier in the game? And that said, would the 'person_with_highest_points' bit immediately call up 'Person B' and 'Person C' if they were the two highest, and in a tie?
Could you help me out with this part as well? Thank you!!

Valmoer
Regular
Posts: 53
Joined: Sat Feb 04, 2012 9:46 pm
Contact:

Re: Point-based games and deciding tiebreakers

#6 Post by Valmoer »

Well, points is a dictionary : a dictionary is a collection whose access is handled by keys. Each item in a dictionary thus has two parts :
the key -> it identifies the wanted value.
the value -> pretty self explanatory.

In SleepKirby's example

Code: Select all

    points = {
        'Person A': person_a_points,
        ...
        'Person G': person_g_points,
    }
'Person A', 'Person B', ..., 'Person G' (which are text strings) are the keys of the dictionary items.
person_a_points, person_b_points, ..., person_g_points are the items initialisation values.
If, just after the initialisation, you accessed points['Person B'], you'd have a value equal to person_b_points.
You could also

Code: Select all

#change the value to a new value
points['Person B'] = newvalue

#modfiy the value (here, increment by one)
points['Person B'] += 1 
Do note that values might not always be numbers, nor keys have to be text strings. Both can be any kind of object, though the string-keyed, number-valued is the most common.

Here is the official tutorial about dictionaries
The official python tutorial is awesome as a source of information anyway : do read it.
And that said, would the 'person_with_highest_points' bit immediately call up 'Person B' and 'Person C' if they were the two highest, and in a tie?
That is actually, what SleepKirby example is all about :

Code: Select all

    highest_points = max(points.values())
    people_with_highest_points = []
    for person, person_points in points.iteritems():
        if person_points == highest_points:
            people_with_highest_points.append(person)
       
if len(people_with_highest_points) > 1:
    # Let the player choose between multiple people.
else:
    # No ties.
Translated in everyday language
  • Let highest_points be the maximum of the values in points.
  • Let there be a list called people_with_highest_points.
  • For each key-value item in points, let person hold the key and person_points hold the value
  • ---If a person's points (person_points) is equal to the maximum value of points (highest_points)
  • ------Then append that person's name (person) to the list people_with_highest_points
  • If there is more than one person with the maximum number of points
  • ---Let the player decide (with the renpy.display_menu() function)
  • Else
  • ---There is no tie*
Always try to say your algorithms out loud, in natural english.


As an additional gift, the recipe for success in renpy (and computing in general (and even in life :))))
  1. Read docs and tutorial
  2. Practice
  3. Ask questions
  4. ????
  5. Profit!
*the tie is a lie**
** the tie is a cake***
*** bowtie are cool
**** mods are asleep, post ponies

User avatar
zomgenius
Regular
Posts: 27
Joined: Mon Apr 16, 2012 11:32 pm
Projects: Paper Stars
Location: USA
Contact:

Re: Point-based games and deciding tiebreakers

#7 Post by zomgenius »

Valmoer, you are so fantastic. That helps me a ton!! :D I'm going to practice with this myself some, and I'll come back if I have more questions. But thank you so very much; this clears up a lot of questions already! One final little thing: would this example still function for if there is a key or more than one key (person) with a higher number of points (value) but not necessarily the maximum of points? So if Person A and Person B manage to get 8 / 10 points, placing them at the highest AND a tie?*

*ponies are always relevant

Valmoer
Regular
Posts: 53
Joined: Sat Feb 04, 2012 9:46 pm
Contact:

Re: Point-based games and deciding tiebreakers

#8 Post by Valmoer »

You misundertood the highest_points = max(points.values()) line. It doesn't return the 'maximum possible' (there's no such thing in computing (except if you go over the max value for an integer, which is 2^63 = 9.22337204 × 10^18 ** - so not gonna happen soon.)) It returns 'the maximum of the values in the dictionary points'*. So this function will always return the highest. As far as computing goes, maximum and highest are synonyms.
And that's how Equestria was made.
*actually, max(list) return the maximum of list's values , and points.values() (or any dictionnary.values()) return a list of the dictionnary values. So both at the same time do...
** 2^31 if you have a 32-bit computer. (Generally, for a N-bit architecture computer, 2^(N-1) for a signed integer, 2^N for an unsigned integer)

User avatar
zomgenius
Regular
Posts: 27
Joined: Mon Apr 16, 2012 11:32 pm
Projects: Paper Stars
Location: USA
Contact:

Re: Point-based games and deciding tiebreakers

#9 Post by zomgenius »

Oh!! Hahah, I feel silly now. Okay, excellent! Thank you so much. :D I'll go practice some now, and then come back. :3 You're awesome!

User avatar
SleepKirby
Veteran
Posts: 255
Joined: Mon Aug 09, 2010 10:02 pm
Projects: Eastern Starlight Romance, Touhou Mecha
Organization: Dai-Sukima Dan
Location: California, USA
Contact:

Re: Point-based games and deciding tiebreakers

#10 Post by SleepKirby »

Good stuff, Valmoer, nice explanations!

User avatar
zomgenius
Regular
Posts: 27
Joined: Mon Apr 16, 2012 11:32 pm
Projects: Paper Stars
Location: USA
Contact:

Re: Point-based games and deciding tiebreakers

#11 Post by zomgenius »

Okay, so I was trying to practice with the whole thing, when I realized... how should I introduce the points in the beginning anyhow? I figured I should introduce it using something like the following:

Code: Select all

$ hunger = 0
$ drink = 0
$ social = 0

label start
e "I wonder what I should do today!"
"She thought about her choices for a moment."

menu:
   "Get something to eat."
   $ hunger += 1
   "Get a glass of water."
   $ drink += 1
   "Call a friend."
   $ social += 1
Am I on the right track here?
And when using a menu like this, do I need to have each option then jump to a new label, or will it allow the player to continue on with the story directly after the choice is made and the variable has had a point added? And when should the entire bit of python code we've been discussing come into the script - at the end, when it is time to decide?

Next, at the end, how do I set up the renpy.displaymenu function? Will it automatically create a menu after I enter that code, or will I have to specify how the menu should look and what it should lead to? And further, does it allow the menu options to be changed in a way to show "I think I'll get another snack," or "I'd rather have another glass of water" instead of "hunger" or "drink," as per the example above?

Thank you so much for your help, and I hope I'm not too much trouble!!
Image
Paper Stars: A Visual Novel
What do you wish for?

Valmoer
Regular
Posts: 53
Joined: Sat Feb 04, 2012 9:46 pm
Contact:

Re: Point-based games and deciding tiebreakers

#12 Post by Valmoer »

Am I on the right track here?
You're mostly right, but your code needs proper indentation :

Code: Select all

hunger = 0
$ drink = 0
$ social = 0

label start:
   e "I wonder what I should do today!"
   "She thought about her choices for a moment."

menu:
   "Get something to eat.":
      $ hunger += 1
   "Get a glass of water.":
      $ drink += 1
   "Call a friend.":
      $ social += 1
Always a colon : at the end of a choice, label or menu statement, and all blocks that depends on that statement must be (evenly) indented.

And when using a menu like this, do I need to have each option then jump to a new label, or will it allow the player to continue on with the story directly after the choice is made and the variable has had a point added?
Yes to the second part. Once a choice is made, the code under the block is executed, then the code execution continues at the end of the menu block.
The concept of block and indentation is crucial in python, and thus in renpy.
And when should the entire bit of python code we've been discussing come into the script - at the end, when it is time to decide?
Exactly.
Though you can put it in a function (or a callable label) and call that function when you need, but that's for later :).

Next, at the end, how do I set up the renpy.displaymenu function?

renpy.display_menu(items):
The function display_menu, which is damn useful to create a menu made of dynamically created choices, takes as a parameter a list of 2-tuples, here called items.
The parameters is expected to be of the form : [ ( "Text of the first choice", returned_on_first choice ) , ("2nd Choice Text", returned_on_2nd_choice) , ... ].
The first part of the element will be a text string, which will be displayed as a choice button. The second part will be the value returned by the function once the user click is done.

As an example, here is some code :

Code: Select all

$ menuitems = [ ("I'll take a sandwich", "eat"), ("I'll rather have a glass of water", "drink") ]
$ result = renpy.display_menu(menuitems)
"You picked the option [result]."
Here is what the user will see :
Result of the menu screen
Result of the menu screen
And then after the click :
Result after click on the sandwich option
Result after click on the sandwich option
You'll notice that the message do carry the correct return value, ie "eat". It is inclued in the message though the "text [variable] more text" syntax, that reads the value of the variable and 'injects' it into the text string.
Thank you so much for your help, and I hope I'm not too much trouble!!
Not at all! You're polite, well-spoken (well, well-typed), you say I'm awesome (which is true) and you like ponies.

User avatar
zomgenius
Regular
Posts: 27
Joined: Mon Apr 16, 2012 11:32 pm
Projects: Paper Stars
Location: USA
Contact:

Re: Point-based games and deciding tiebreakers

#13 Post by zomgenius »

I see!! That makes a lot of sense, now. :3

So, to set up the renpy.display_menu function after the part that checks for a tie, would it look something like this?

Code: Select all

python:
    points = {
        'Person A': person_a_points,
        'Person B': person_b_points,
        'Person C': person_c_points,
    }
    highest_points = max(points.values())
    people_with_highest_points = []
    for person, person_points in points.iteritems():
        if person_points == highest_points:
            people_with_highest_points.append(person)
        
if len(people_with_highest_points) > 1:
   renpy.display_menu (items, window_style='menu_window', interact=True, with_none=None):
      $ menuitems = [ ("I'll take a sandwich", "eat"), ("I'll rather have a glass of water", "drink") ]
      $ result = renpy.display_menu(menuitems)
   "You picked the option [result]."
else:
    # No ties.
Argh, I also got confused as to which parts in the first bit of code I should be changing, based on what items have points allocated to them. :c I left them as 'Person A,' etc. since I wasn't quite sure about it. Would I be changing the 'Person A,' etc. parts and the 'person_a_points' parts to something using 'hunger,' 'drink,' or 'social,' as well? Or do I leave those alone? And the person_points and people_with_highest_points parts, too. I'm not sure if those should change as items change, or if they are general terms and fit whatever items are in the list.

Another question about the display_menu function as well -
Is there a way to set the display of a result to be completely different, and not show 'You picked [result]?' I guess, in the example we've been using, something long the lines of saying "The sandwich was delicious," instead of just saying that they chose to eat.

Ah, and will the tiebreaker bit of code be able to allocate another point to the specified item, in order to make them the highest value? Or how would that work, for moving on to the next bit of story with that character?

I suppose that maybe explaining a little of what I'm hoping to do with the code might help more, too! I plan on having the protagonist of the game choose different people for different sorts of relationships as the story progresses. Namely, rival, best friend, lover, mentor, etc. So in the process of deciding your rival, you end up with a tie. Another option will be presented to the player that allows them to choose between the tied candidates. After they make their choice, the game will continue on with that person as their rival.

Would another block of code be necessary to decide between the candidates once the tie has been resolved? And if so, would it need to be completely new, or would copying and pasting the same block of python code previously used to find the highest value work?

Thank you again, I'm really learning a lot! :D and who couldn't like ponies? they're PONIES, and are inherently wonderful c:
Image
Paper Stars: A Visual Novel
What do you wish for?

Valmoer
Regular
Posts: 53
Joined: Sat Feb 04, 2012 9:46 pm
Contact:

Re: Point-based games and deciding tiebreakers

#14 Post by Valmoer »

Argh, I also got confused as to which parts in the first bit of code I should be changing, based on what items have points allocated to them. :c I left them as 'Person A,' etc. since I wasn't quite sure about it. Would I be changing the 'Person A,' etc. parts and the 'person_a_points' parts to something using 'hunger,' 'drink,' or 'social,' as well? Or do I leave those alone? And the person_points and people_with_highest_points parts, too. I'm not sure if those should change as items change, or if they are general terms and fit whatever items are in the list.
Variables names are names.
Keys values are values (and can be either text, numbers...).
They don't have an inherent meaning. The only words that are inchangeable are the language keywords. (for, in, len,...)
I could take your code and rename everything :

Code: Select all

python:
    elements_of_harmony = {
        'Apple Bloom': cutie_marks,
        'Twilight Sparkle': books,
        'Pinkie Pie': balloon_lollipops_streamers
    }
and the code would work exactly the same way - the computer don't care for particular names : he just see you use one name for one variable, so that name is the codder access to the value stored.

Given all that, you should always use as variable names and/or as dictionary keys words that makes sense in the context of your code : here, except if i was making MLP:FiM the Visual Novel, it wouldn't mean anything (i.e. it wouldn't help the human coder understand what the variable is used for).
Is there a way to set the display of a result to be completely different, and not show 'You picked [result]?' I guess, in the example we've been using, something long the lines of saying "The sandwich was delicious," instead of just saying that they chose to eat.
Yes, yes and yes. There is a fundamental thing in coding that you must understand. Apart from using the same data, and save a few particular cases (that we'll see later, I guess) a line of code is independant of the one that came before, nor cares for the one that will come after. Basic rule of computing. Learn it. FOREVER.

So here, we have:

Code: Select all

$ menuitems = [ ("I'll take a sandwich", "eat"), ("I'll rather have a glass of water", "drink") ]
$ result = renpy.display_menu(menuitems)
"You picked the option [result]."
My lesson of "don't care about any code but my own line" applies, so as far as the ren'py.display_menu is concerned, it looks like.

Code: Select all

# Bunch of code that may or may not have set menuitems to anything
$ result = renpy.display_menu(menuitems)
# Bunch of code that may or may use the 'result' variable
Ah, and will the tiebreaker bit of code be able to allocate another point to the specified item, in order to make them the highest value? Or how would that work, for moving on to the next bit of story with that character?
You know what ? I'm gonna be evil and not answer that question. :twisted: If you did understand my lesson about the separate nature of codelines, you should be able to figure it out yourself. It will be your homework. :lol:
Would it look something like this?

Code: Select all

if len(people_with_highest_points) > 1:
   renpy.display_menu (items, window_style='menu_window', interact=True, with_none=None):
      $ menuitems = [ ("I'll take a sandwich", "eat"), ("I'll rather have a glass of water", "drink") ]
      $ result = renpy.display_menu(menuitems)
   "You picked the option [result]."
That part is all wrong.
A function call works like this : you have a function (let's call it function) that has parameters (param1 & param2, for example.)

Code: Select all

def function(param1, param2):
   #bunch of code that defines a_value from param1, param2, and other things maybe
   return a_value
When in your code, you use function, for example,

Code: Select all

$ my_result = function(2, 25)
it will execute all the code in the def function block, but param1 will be equal to 2, and param2 will be equal to 25.
When the execution of the function blocks reaches return a_value, it will return the value of a_value to the original function call. Thus, once the function call is done, the line would have the same effect (to the computer) that the line.

Code: Select all

$ my_result = a_value
but remember that the a_value's value depends of the parameters that you have given at first.

Thus, if later I code the line

Code: Select all

$ my_other_result = function(58, 33)
, my_other_result will have a different value than the one you had for my_result with 2 & 25.

Thus, in your call to renpy.display_menu(...), you must first define the values you'll pass as parameter, thus my

Code: Select all

$ menuitems = [ ("I'll take a sandwich", "eat"), ("I'll rather have a glass of water", "drink") ]
line, and after (and only after) that, you call the function with your parameters and store it into a variable.


Hey, second homework :twisted: : try explaining those two lines in "natural language", as I did before :).

Code: Select all

$ menuitems = [ ("The Number Five", 5), ("The Word 'blue'", "blue") ]
$ result = renpy.display_menu(menuitems)

User avatar
zomgenius
Regular
Posts: 27
Joined: Mon Apr 16, 2012 11:32 pm
Projects: Paper Stars
Location: USA
Contact:

Re: Point-based games and deciding tiebreakers

#15 Post by zomgenius »

Hmm.. Okay. I think I understand how I can get the renpy.display_menu bit to work how I would like it to! Once I'm on my other computer, I'll have to test it out myself; trial and error seems like a good plan, since I have a basic idea of that.

I suppose I am a little confused by the function call now. I'm not really sure what part was wrong in the code I tried to do. Should it look like this, instead?

Code: Select all

if len(people_with_highest_points) > 1:
      $ menuitems = [ ("I'll take a sandwich", "eat"), ("I'll rather have a glass of water", "drink") ]
      $ result = renpy.display_menu(menuitems)
   "You picked the option [result]."
I think I have an idea of how to get it to allocate a point to the respective options as well, though I'm not sure how to write it! I'm thinking I'll have to use 'if' and 'elif,' as well as "hunger": $ "hunger" += 1 in there to do so. Would that be correct?

Second Homework time!

Code: Select all

$ menuitems = [ ("The Number Five", 5), ("The Word 'blue'", "blue") ]
$ result = renpy.display_menu(menuitems)
  • Let there be a function called menuitems.
  • Let the function's parameters include the key items "The Number Five," and "The Word Blue."
  • Let the values of those key items include "5" and "blue."
  • Let there be a function called result.
  • For the function result, display a menu containing the parameters of the function 'menutitems.'
I got all fancy with the formatting, but I wanted to see if I could highlight the differences between the code, the parameters, and the titles of the functions. :P
Image
Paper Stars: A Visual Novel
What do you wish for?

Post Reply

Who is online

Users browsing this forum: No registered users