Variable not defined in If statement (Python, Ursina Module) - python

My problem is that when I change the content of the variable "XO" inside the if statement, the one in the condition of the If statement becomes undefined.
from ursina import *
import time
app = Ursina()
window.title = "Game"
window.borderless = False
window.exit_button_visible = False
XO = "X"
class Tile(Button):
def __init__(self, position = (-5, -5, 0), texture = "assets/tile.png"):
super().__init__(
parent = scene,
model = "quad",
color = color.lime,
position = position,
texture = texture
)
def input(self, key):
if self.hovered:
if key == "left mouse down":
if XO == "X":
Tile(position = self.position, texture = "assets/tile_x")
XO = "O"
else:
Tile(position = self.position, texture = "assets/tile_0")
XO = "X"
time.sleep(.005)
destroy(self)
for x in range(3):
for y in range(3):
block = Tile((x-1, y-1))
app.run()

The biggest issue here is that you seem to be trying to reference a global variable inside of a some inner context. In general, this is not encouraged for a lot of reasons. One of the biggest in my opinion is that global code is difficult to maintain. Instead, I would encourage you refactor your code so that all of your code resides inside of a class, and work on encapsulating your code.
That being said, sometimes it is necessary to use the global scope. In order to do that, you simply use the global keyword before the name of the variable you want global scope for. In your case, you would simple do global XO at the start of your input function.

Related

Python not seeing an external object

* I believe the lvl1[(x,y)] = getattr(__import__('mapTiles'), tile_name)(x, y) is causing the problem, I changed it to a direct import with the same problem and the circular import here is... mapTiles imports world and inside the load_tiles() function, it imports mapTiles. I'm not sure how to restructure this to stop the circles, any ideas? *
I'm making a text RPG game, I have all the tiles coded and able to be interacted with but when I go to run the game, it gives me the error below. I can't see a circular import anywhere so I don't understand what's going on. (Please tell me if you need any other code)
Error: (caused by load_tiles() which is called in play)
getattr(__import__('mapTiles'), tile_name)(x, y)
AttributeError: module 'mapTiles' has no attribute 'PlainsTile'
Only showing the base class and the one tile because there are about ten different ones
mapTiles.py
import actions, items, enemies, actions, world
class MapTile:
def __init__(self,name, x, y, intro, description):
self.x = x
self.y = y
self.intro = intro
def intro_text(self):
return self.intro
def terrain_interacts(self, terrain):
# For elemental effects
pass
def randomize_interactions(self, tile):
# Randomize enemy and loot spawns
pass
# Default actions
def adjacent_moves(self):
# Returns all move actions for adjacent tiles
moves = []
if world.tile_exists(self.x + 1, self.y):
moves.append(actions.MoveEast())
if world.tile_exists(self.x - 1, self.y):
moves.append(actions.MoveWest())
if world.tile_exists(self.x, self.y - 1):
moves.append(actions.MoveNorth())
if world.tile_exists(self.x, self.y + 1):
moves.append(actions.MoveSouth())
moves.append(actions.ViewInventory)
return moves
def available_actions(self):
# Returns all available actions for the current tile
moves = self.adjacent_moves()
return moves
class PlainsTile(MapTile):
def __init__(self, x, y):
self.name = "PlainsTile"
self.intro = "You enter a plains"
self.description = "A span of clear land, with the tall grass waving in the wind"
super().__init__(
name=self.name,
intro=self.intro,
description=self.description,
x=self.x,
y=self.y
)
play.py
#play.py
import character, world
def play_game(player):
world.load_tiles()
print("Called")
#These lines load the starting room and display the text
tile = world.tile_exists(x=player.location_x, y=player.location_y)
if tile != None:
print(tile)
while player.is_alive() and not player.victory:
tile = world.tile_exists(player.location_x, player.location_y)
# Check again since the room could have changed the player's state
if player.is_alive() and not player.victory:
print("Choose an action:\n")
last[0] = player.location_x
last[1] = player.location_y
if tile != None:
available_actions = tile.available_actions()
if tile.name == 'BossTile' and player.victory:
tile.modify_character(player)
for action in available_actions:
print(available_actions.name)
for action in available_actions:
action_input = input("Choose an action ya prick: ")
if action_input == "quit":
quit()
if action_input == action.keyword:
player.do_action(action, **action.kwargs)
break
else:
print("Please choose one of the listed actions")
else:
print("You cannot go that way")
world.py
# No imports
lvl1 = {}
def load_tiles():
"""Parses a file that describes the world space into the _world object"""
with open('m1.txt', 'r') as f:
rows = f.readlines()
x_max = len(rows[0].split('\t')) # Assumes all rows contain the same number of tabs
for y in range(len(rows)):
cols = rows[y].split('\t')
for x in range(x_max):
tile_name = cols[x].replace('\n', '') # Windows users may need to replace '\r\n'
if tile_name == 'StartingRoom':
global starting_position
starting_position = (x, y)
if tile_name == '':
lvl1[(x, y)] = None
else:
getattr(__import__('mapTiles'), tile_name)(x, y)
def tile_exists(x,y):
return lvl1.get((x,y))
imports from all other files
#items.py
#No imports
#actions.py
from play import player
import items
#enemies.py
import random
#character.py
import world, items, random
I fixed it, the error was the import from getattr. I merged the two files, added a try/except to catch index errors and added a bunch of conditionals that instantiate the tile name at whatever coordinates.

How do I assign values to a variable with a schedule function in pyglet?

With the following code
x=1.0
def update(dt):
space.step(dt)
def xprinter(self, x):
print (x)
return x+1
if __name__ == "__main__":
x=pyglet.clock.schedule(xprinter,x)
pyglet.clock.schedule_interval(update, 1.0/60)
pyglet.app.run()
My return is simply 1.0 over and over. I would like for the value to be updated with each call. What am I missing?
The design here is based on that your function rely on returning a result.
Which causes a problem because Pyglet's internal functions are in charge of executing that function at a interval, meaning you're not the one executing the call - and there for you're not the one getting that return value, Pyglet is.
And since there's no meaningful way for Pyglet to relay that returned value (there are ways, but they involve hooking in and overriding certain internal functions), you will never see that return value.
The quick and easy workaround would be to do:
x=1.0
def update(dt):
space.step(dt)
def xprinter(self):
global x
print(x)
x += 1
if __name__ == "__main__":
pyglet.clock.schedule(xprinter)
pyglet.clock.schedule_interval(update, 1.0/60)
pyglet.app.run()
This way, the schedule call will update the global variable x rather than returning the result of the math equation.
A more neat approach would be to define a class with the x attribute and pass a class instance to the pyglet.clock.schedule():
class player():
def __init__(self):
self.x = 0
def update(dt):
space.step(dt)
def xprinter(self, p):
print(p)
p.x += 1
if __name__ == "__main__":
p = player()
x = pyglet.clock.schedule(xprinter, p)
pyglet.clock.schedule_interval(update, 1.0/60)
pyglet.app.run()
And if I'm not completely out of the ball park, this would remember the value across clock ticks, because of the instance.
This is also usually what you'll be using the schedule for, doing player / game / animation updates.

Python class parameters, where to define

class Camera(object):
def __init__(self, win, x=0.0, y=0.0, rot=0.0, zoom=1.0):
self.win = win
self.x = x
self.y = y
self.rot = rot
self.zoom = zoom
cam = Camera(Window,1,1,1,1)
vs
class Camera(object):
def __init__(self, win, x, y, rot, zoom):
self.win = win
self.x = x
self.y = y
self.rot = rot
self.zoom = zoom
cam = Camera(Window,1,1,1,1)
So does the first block of code just make the class static where it can only be made and not adjusted with parameters? If so, what's the usefulness of this?
The first block of code just sets default values for those variables. When the class is initialised if they are passed to the class, then they will be set. Otherwise the class variables will contain the default values.
In the second block of code, by not setting a default value you are making those parameters required for the class.
See this question for a better explanation: Why do we use __init__ in python classes?
The difference between the first one and second one is: the first one has default values while the other one dosen't. For example with the first one
cam = Camera(Window) wouldn't give you an error (x=0.0, y=0.0, rot=0.0 and zoom=1.0 have a default assigned value).
But the second code block would give you an error if you were to create new instance of the class like this cam = Camera(Windows). Wich is ultimately the difference between your first code block and your second one.
Cheers!

Procedural Generation Using Classes Pygame

I'm in the process of creating a basic game in pygame at the moment, and one part of that is the procedural generation of new areas as you go off the screen. As a test, I'm looking to generate an object once per area by defining its variables, and then save that area's object within the class for if you come back to it later. Here's what I have at the moment:
#area_gen is set to "true" if you move to a new area
#swidth and sheight are set to the size of the screen
#x_area and y_area are defined as you change areas, acting as sector coordinates
#Red is defined in globals
class areas:
def __init__(self, coords):
self.coordinates = coords
self.generated = False
def gen_objects(self):
if not self.generated:
self.objs = []
obj_type = "test object"
center_x = random.randrange(105, swidth-25)
center_y = random.randrange(25, swidth - 175)
self.objs.append([obj_type, center_x, center_y])
self.generated = True
#Within The Game Loop
if area_gen == "true":
coords = str(str(x_area) + " " + str(y_area))
area = areas(coords)
area.gen_objects()
for thing in area.objs:
if thing[0] == "test object":
pygame.draw.rect(screen, Red, (thing[1], thing[2], 250, 250))
#Bottom of the Game Loop
area_gen = "false"
What I thought the self.generated variable would do was stop the new object generation if one already existed, but that doesn't seem to be working. The square still generates at a new location even if the area has already been visited. My knowledge on classes is relatively limited, so I'm a bit stuck as to where to go from here.
Pass area_gen into the constructor for the areas class, and set self.generated = area_gen. Does this work? I can't see enough of your code to know, to be honest.

Printing from within properties

I'm trying to make a robotics kit. Its designed to be simple so I'm using properties so when the users change a parameter the property method sends the serial command which controls motors/ servos/whatever.
This is the code at the moment, directly from a previous question I asked on here.
class Servo(object):
def __init__(self, which_servo, angle = 0):
self._angle = angle;
self._servo_no = which_servo
def get_angle(self):
return self._angle
def set_angle(self, value):
self._angle = value
print "replace this print statement with the code to set servo, notice that this method knows the servo number AND the desired value"
def del_angle(self):
del self._angle
angle = property(get_angle, set_angle, del_angle, "I'm the 'angle' property.
this is then initialized as such:
class robot(object):
def __init___(self):
self.servos = [Servo(0), Servo(1), Servo(2), Servo(3)]
Now, this works in the respect that it does change the variable through the getter and setter functions, however the prints in the getter and setter never is printed, thus if I replace it with a serial command I assume it won't do anything either, can anyone shed any light on this?
Thanks
Update: Thanks for the help using the servo file this is whats happened, there are three scenarios the first works and by extension I would have assumed the next two preferable scenarios would work but they don't any ideas?
This works
import servo
class Robot(object):
def __init__(self):
self.servos = [servo.Servo(0, 0), servo.Servo(1,0), servo.Servo(2,0)]
R = Robot()
R.servos[1].angle = 25
This does not:
import servo
class Robot(object):
def __init__(self):
self.servos = [servo.Servo(0, 0), servo.Servo(1,0), servo.Servo(2,0)]
R = Robot()
left_servo = R.servos[1].angle
left_servo = 25
Neither does this
import servo
class Robot(object):
def __init__(self):
self.servos = [servo.Servo(0, 0).angle, servo.Servo(1,0).angle, servo.Servo(2,0).angle]
R = Robot()
R.servo[1] = 25
Using the preferred decorator syntax for properties, this works fine. It'll also help you avoid issues like this in the future
class Servo(object):
def __init__(self, which_servo, angle = 0):
self._angle = angle;
self._servo_no = which_servo
#property
def angle(self):
return self._angle
#angle.setter
def angle(self, value):
self._angle = value
print "replace this print statement with the code to set servo"
#angle.deleter
def angle(self):
del self._angle
Seeing as your indentation is off here, I believe this is likely an issue of indentation in your source. This should work as well if you really want to use the old property function:
class Servo(object):
def __init__(self, which_servo, angle = 0):
self._angle = angle;
self._servo_no = which_servo
def get_angle(self):
return self._angle
def set_angle(self, value):
self._angle = value
print "replace this print statement with the code to set servo"
def del_angle(self):
del self._angle
angle = property(get_angle, set_angle, del_angle,"I'm the 'angle' property.")
Both of these work successfully for me (inside a file called servo.py)
>>> import servo
>>> s = servo.Servo(1, 2)
>>> s.angle
2
>>> s.angle = 3
replace this print statement with the code to set servo
EDIT
To address your new issues. When you assign R.servos[1].angle to left_servo, its not creating a reference to the servos angle, it's just setting left_servo to whatever the angle is. When you reassign 25 to it, you're not assigning to the angle you're assigning to the left_servo.
On the second one, I'm assuming you meant R.servos and not R.servo which should be raising an AttributeError. But the real problem as I see it, is you should be saying R.servos[1].angle = 25 and you're omitting the .angle.
To (attempt to) put it simply: When you use the = operator, you are changing where a name refers to, not what it refers to.
>>> x = 1
>>> x = 2
the second assignment does not overwrite the 1 in memory with a 2, it just changes where x refers to. So if I did something like
>>> x = 1
>>> y = x
>>> y = 2
>>> print x
1
the output is 1 because your are telling y to refer to the same place that x refers. Changing y to 2 changes where y refers to, it does not change the 1 already in memory.

Categories

Resources