Lists, Lambda and added objects

Hi all!

I am getting an odd problem with the following code:


racers = [ob for ob in sce.objects if 'racer' in ob]
racers.sort(key=lambda k: k['total_distance'],reverse=True)

Its attached to an object that is added via a keypress into the scene. So far, so good, no errors on startup in the output window. But as soon as the object is added, I get a single key error regarding the second line of the above code regarding lambda and the sort itself - it cannot find the [‘total_distance’] key. Once the object is added though the error does not return.

Now call me stupid (not all at once!) but why does it do this? If an object is added surely the key exists, even if the value is 0?

Any thoughts appreciated!

Paul

It sounds like one of the objects in your scene with the property ‘racer’ does not have the property ‘total_distance’. You should double check that all of your racers have this property.

Thats odd, as the key is a property defined within Python so all ships have it (or should have it) automatically.

But if I add a property in the logic window the problem goes away…

Thanks for the reminder, Mobious!

When do you define the property in Python? If it’s below the line that sorts the list by the ‘total_distance’ key, then for a single frame, the objects present in the list don’t have that variable.

In other words, are you sure that you define the variable in time?

Be aware the first line looks for “racer”
As the second line looks for “total_distance”

for debugging you could add:


print("racers without distance",[racer for racer in racers if not "total_distance" in racer])

SolarLune> the property is defined in a separate script. The script lines above is attached to a separate object (a start/finish line that detects the racers and collates them into a list to check from). The distance property is defined and used within each racer as part of a method to determine who has travelled furthest (i.e. in the lead) by the start/finish line.

Since they are separate I cannot say if one is above the other (although the error would say otherwise).

Monster> The script looks for any racers and then checks how far they have gone

Your script line returns true (i.e. it prints the message).unless I add a non Python property manually and add a property via the logic pane.

Do you have a simplified test file?

I can’t simplify it as each part depends on each other to work, but here is a description of what is where:

The 1.py script controls the start / finish line- lines 33 and 34 are the two lines from the first post.

In Ship_Control.py total distance is defined on line 46.

When the game is active, use the mouse to click on the red cube (next to the icosphere) to generate a ship. The ship sinks as I am in the process of making some new functions (so that is intentional). To make the ship fly round press space when the ship is in the scene.

And yes, its a mess. At the minute I am trying to tie together several scripts and logic blocks that I worked on separately, and get them to play together.

Attachments

Test.blend (586 KB)

You have a syntax error at line 35 of 1.py
Missing module Race_control.py

Hmm, it really looks chaotic.I do not know where to start looking :(.

After adding the above line (at line 34) I can see:


racers without distance [Red_Beard]
Python script error - object 'Start_finish_line', controller 'Python':
Traceback (most recent call last):
  File "H:\MY DOCUMENTS\PRIVATE\Blender\support\by Rubbernuke\Test.blend\1.py", line 35, in Start_Finish_Line
  File "H:\MY DOCUMENTS\PRIVATE\Blender\support\by Rubbernuke\Test.blend\1.py", line 35, in <lambda>
KeyError: 'value = gameOb[key]: KX_GameObject, key "total_distance" does not exist'

Which means Red_Beard has the property “racer” but not the property “total_distance”.

Better exclude object without this property:


racers = [ob for ob in sce.objects if 'racer' in ob and 'total_distance' in obj]

but I guess you expect this property to exist.

Edit:
in this case you should set it via GUI with a default value.

Monster, is there a difference between properties generated in Python and those set in the GUI?

I ask as after the ship Red Beard is created and starts moving round the track the problem goes away, which I guess is down to the fact that for an instant the property does not exist- if it was a bigger problem the error would surely start the minute I press P?

EDIT: Just updated the script with your amendment and it works- cool!

Off topic:

And about the chaos: I’ve never made a full game before (never made a game!!)- is there a general layout for games/ game scripts? For example, do I need one big script or do I have smaller ones? I would be interested to hear how more experienced Blender users arrange / organise things.

My experience so far is like doing a jigsaw whilst making each piece…lots of confusion but sometimes you get an :smiley: moment.

I haven’t got much time now. You might be over reaching. Do you remember maybe 16 months ago we posted about properties, listing and lambda? Maybe you’re working on the same project. See if you can find the posts and then check out lambda on python.org.

I don’t know that it applies but there was something suss, or unnecessary or something legacy about the use of lambda.

there a mix between the two modules
def Start_Finish_Line(cn):
def Split_Timings(cont):

“racers” is a list used by
def Start_Finish_Line(cn):

you cannot call it from
def Split_Timings(cont): (this instead use a list “ship”)
:wink:

make two things with the same (or much similar) task is the best way to make mistake… keep only one of this module… :wink:

Well remembered, Equip! I was just having a look at some of the posts. I must confess I know very little about the lambda syntax, its only due to the rewrites of other kind souls that it is used.

But I think you are right, the more I do the more I realise how complicated things are- the biggest unknown really is fitting it all together.

And thanks Marco! Those cubes just don’t go away eh?

now , i see the total distance problem , i had tinked to a issue of timing as SolarLune say , but instead the total distance is not NEVER declared .

not in the property brick and not in the script .

i counsill to add to the brick property the property more important, this is one of that important.

else, this must be adding on the fly to the spawner obj which add the ship

now work , I have changed some word, but many syntax error over all…

(a part “total_distance” which lack)

edit: .blend (i not deleting any brick this time :smiley: )

Attachments

Test.blend (163 KB)

hey, redundant is better than wrong , but try to clean the repetitions ,
the script become more short and more clear


    if Split_collision.positive:
        if race_ships == ship[0]:
            ow["Leader"] = race_ships.name
            if ship[0]["player"] == False:
                ow["Split_timer_start"] = True 
                print("behind")
        
            elif race_ships == ship[0]:
                ow["Leader"] = race_ships.name
                if ship[0]["player"] == True:
                    ow["Split_timer_start"] = True      
                    print("ahead")
                    
    if Split_collision.positive:        #<<<<<<<<<<<<<<<<<<<<<<<<<
        if ow["Split_timer_start"] == True:
            if race_ships["player"]== True:
                if race_ships.name != ow["Leader"]:
                    ow["Split_timer_start"] = False
                    t = ow["Split_time"] 
                    m,s,c = int(t//60), int(t%60), int((t%1)*100)
                    text = str(m) + ":" + str(s) +":"+ str(c)
                    ow["Split_time_result"] = text

    if Split_collision.positive:        #<<<<<<<<<<<<<<<<<<<<<<<<<
        if ow["Split_timer_start"] == True:
            if race_ships["player"]== False:
                if race_ships.name != ow["Leader"]:
                    if ow.name != ow["Leader"]:
                        if race_ships == ship[1]:
                            ow["Split_timer_start"] = False
                            t = ow["Split_time"]
                            m,s,c = int(t//60), int(t%60), int((t%1)*100)
                            text = str(m) + ":" + str(s) +":"+ str(c)
                            ow["Split_time_result"] = text


Cool, thanks Marco. I think I am going to freeze adding things and have a bit of a spring cleaning- I have the basic underpinnings of what I need now. Also, how did the file size drop by 400k? Is the file storing the grass jpeg used for the ground?

Not really. The only difference is that pre-setup properties (via GUI) are not in the KX_GameObject.attrDict. I guess it is a bug and nobody uses this dict :D.

The whole problem is a timing thing. In that way you can be lucky that you discovered it at this early stage.

When you add a new object it has the pre-setup properties only.
Your code expects the property “total_distance” set within the game object. But this code runs via a different game object as you mentioned before.

The code at the game object which set the property was not running at that time. That means the property is not present (similar to an undeclared variable) but you assume it is.

Solutions:
A) Make sure the properties is always set (by setting it in GUI)
B) Deal with the non-existent property

This is a design issue. You have a dependency from the “evaluating” game object to the “setting” game object.
What is it that you need? Obvious you want to check a distance.
Where does the distance come from? Something calculated it before hand and stored it in this property.

As this calculation is not triggered explicitly when needed you have to deal with the situation that this information is not present.
If there is no other reason to store the distance in the property it might be a better idea to perform the distance calculation when you need it.

You have to organize everything by yourself. It separates the men from the boys ;).
Make sure you keep your organization consistent within the single game.

A good organization is to separate different things and to keep things together that belongs together.
Example:
User UI can be in organized one or more modules (Mouse, keyboard, menu).
Game Logic should be kept separate from any UI logic, SaveLoad, Network or Selection methods etc.

Think as you want to use your code in a different game (there is no need to reinvent the wheel all the time)

If you are unsure. Try to implement your idea in a test file first. It should only contain the Logic you want to develop plus some test logic.
This makes it easier to concentrate on the single logic (rather than to deal with all side effects of other logic).
It makes it easier to identify and solve problems.
You can distribute the test file if you need help. As it does not contain irrelevant things it is much easier for us to help you.

Monster

Some hints

checking boolean values
please do not use

X==True
X==False

You can directly use

X
not X

Beside the irrelevance of such code it is much better to read :wink:

nested conditions


if A:
   if B:
      if C:

is hard to read. Especially if the code blocks become large the reader will loose overview.
Better connect the conditions via AND


if A and B and C:

if the line goes to long you can use this syntax:


if (A and 
    B and 
    C):

If that is still to hard to guess you can put it into a function


if isAnIncredibleEasyToReadCondition(params):
  ...
def isAnIncredibleEasyToReadCondition(params):
return (A and 
    B and 
    C)

If the code block becomes to large you can place it into a function too:


if isConditionA(paramsACheck):
  doProcessingA(paramsAProcessing)
if isConditionB(paramsBCheck):
  doProcessingB(paramsBProcessing)
if isConditionC(paramsCCheck):
  doProcessingC(paramsCProcessing)

Repetitive Code
Checking the same condition again and again is inefficient, hard to read and hard to maintain. Better check it once and remember the result.

Denying condition checks/complexity of code
If you do not need to worry the else clause you can skip further processing to keep the complexity of the code low:


def myFunction():
  if assumptionIsMet:
    if anotherAssumptionIsMet:
       #good case code
       #more good case code 
       #much more good case code


def myFunction():
  if not assumptionIsMet:
    return
  if anotherAssumptionIsMet:
    return
  #good case code
  #more good case code 
  #much more good case code

Be aware this style is not recommended if there is bad processing (especially repetitive bad case processing). Some coding guides suggest to avoid that. But this is good to check assumptions before processing good case code.

Try to keep the indentation level as low as possible.

I hope this helps
Monster

for the size is just a option in blender, when saving a .blend see at bottom left , there a option “compress”
:wink:

PS:
good tips Monster , i want try a bit the method for " keep the indentation level as low as possible."
some time when see the “return” in middle to the script , it seem to me a “indentation broken”, I think is a question of habit …