I am teaching myself Python by reinventing the wheel, i.e. writing a simple console adventure game with interconnected rooms. My interest is not so much in finishing a real game, rather in learning about abstraction and data structures.
I have defined a few basic classes such as World, which contains references to Rooms. These Rooms have Actions and Items. The program checks which Actions are available for each Room and displays them in a list. All of this is working pretty well so I'm trying to complexify things a bit.
I have two problems I can't get my head around. I'll try to explain
them with as little detail as possible.
This is how I defined a possible Action for a Room, in this instance it's a Room referenced by the variable called main (for now data is declared in a module, later to be read from a CSV or XML file):
main.actions = [Action(type = 'move',
result = 'west',
desc = 'Go to the West Room.')]
I need to define an action type because some actions are not movements (e.g. pick up, pull a lever). I'll modify this later to uses subclasses of Action, it's not the issue here.
The 'west' string here refers to the Room which will be the result of the action, i.e. it will become the current room when the action is executed.
However, I'd like the result to be the Room object itself, not a
string ID. But I can't do that until all the Rooms have been initialized.
So I've solved this with the following method of the World object, which works:
def connect_rooms(self):
'''
Once all the rooms have been created,
replace all the string reference to other rooms by the Room objects themselves.
'''
for room in self.rooms:
for action in room.actions:
if action.type == 'move':
room_object = fetch_room(self, action.result)
action.result = room_object
The fetch_room() function (global scope) just does this:
def fetch_room(world, shortname):
# Find a room item by its shortname
for room in world.rooms:
if room.shortname == shortname:
return room
return None
I am sure there is a better way to handle this, since creating connections between nodes seems to be a basic abstract idea.
The other (related) problem is that I'm trying to create conditions built into the Actions themselves, so that the program only proposes them to the player if the conditions defined in the Action are met. For the initial building of the data I can't reference anything else because it hasn't been created yet. I thought of adding a condition in string form and run it later with exec(), but it seems very silly and ugly:
main.actions = [Action(type = 'move',
result = 'west',
desc = 'Go to the West Room.',
conditions = ["player.has('smallkey')"])]
If there is a text somewhere about build such data structures without going insane, I'd be happy to read it.
Thank you.
Related
I am working on a personal project that analyzes hockey player shot data. One thing that I would like to investigate is the effects of different game-states (5v5, power play, short handed). The problem that I have is that I am not sure how to structure this part of my program.
My initial thought is to define a dictionary with 3600 sub-dictionaries as follows:
game_state = {}
game_state[time] = {}
game_state[time][home] = []
game_state[time][away] = []
I can then use the time for each shot event that I am interested in to lookup each teams' game-state. This, however, seems like an inefficient way of doing things.
As I am writing this question up it occurs to me that most of the game is 5v5 for both teams. Perhaps I could set up a similar dictionary but only use the times that the game-state is not 5v5 to generate the keys, and then when looking up play data assume that no entry means a 5v5 game-state.
My Question: Is there something better suited than a dictionary for this kind of application?
Edit:
To #Karl Knechtel's point, I do not need to save any of this information beyond one iteration of a for loop in my main file. In the main file I loop through game_data (a pickled JSON file) and collect x, y coordinates for all shots to later be binned and plotted. I am trying to refine the shot data to consider only a specific game state by introducing an additional check into my data parsing loop.
This sounds like the perfect use case for a relational database like SQLite or Postgres. Without getting too much into the nitty gritties, you could define a relation called Shot with time as a primary key. This would allow you to also look up more interesting questions like "How many shots are made short handed when it's a powerplay?" You could potentially also have a relation called Game which allows you to know which shots happened in which game.
If you want a less labor intensive solution, I think grouping the data into a class would be good idea. For example,
class Shot:
def __init__(self, time: int, short_hand: bool, num_players: int):
self.time = time
self.short_hand = short_hand
self.num_players = num_players
You could then have a dictionary that maps time to Shot instances.
shots: dict[int, Shot] = {}
shots[100] = Shot(100, False, 10)
shots[150] = Shot(150, True, 9)
...
NB: I highly suggest the first option since it sounds like it will be more useful for your case.
I'm trying to write a text-based adventure in Python, for which I created a class called Room. A Room consists of a description and 4 other Rooms in each direction (north, south, ...).
But when I create, for instance, two rooms next to each other, I have to create one of them first, meaning it can't understand what I pass as the Room next to it because it's a line below.
I was wondering what ways there are to fix such a problem, other than perhaps making and importing a new file for each room. I'll add a small example.
room_north = Room("RoomNorth", room_south)
room_south = Room("RoomSouth", room_north)
Thanks in advance :)
One way is to change your Room class so that it doesn't need the connections at construction. First create the rooms, and then add the connections:
room_north = Room("RoomNorth")
room_south = Room("RoomSouth")
room_north.south = room_south
room_south.north = room_north
Another way is restructure your code to store all rooms in a dictionary, identified by strings:
rooms = {
"room_north": Room("RoomNorth", "room_south"),
"room_south": Room("RoomSouth", "room_north"),
}
A possible drawback is that every lookup of a room will have to go through this dictionary, of course.
just starting out with neo4j, py2neo and Cypher.
I have encountered the following problem and google and my knowledge of what to ask have not yet given me an answer or a helpful hint in the right direction. Anyway:
Problem:
I don't know how to, in python/py2neo, create relations between a unique starting node and a number of following nodes that I create dynamically in a for loop.
Background:
I have a json object which defines a person object, who will have an id, and several properties, such as favourite colour, favourite food etc.
So at the start of my py2neo script I define my person. After this I loop through my json for every property this person has.
This works fine, and with no relations I end up with a neo4j chart with several nodes with the right parameters.
If I'm understanding the docs right I have to make a match to find my newly created person, for each new property I want to link. This seems absurd to me as I just created this person and still have the reference to the person object in memory. But for me it is unclear on how to actually write the code for creating the relation. Also, as a relative newbie in both python and Cypher, best practices are still an unknown to me.
What I understand is I can use py2neo
graph = Graph(http://...)
tx = graph.begin()
p = Node("Person", id)
tx.create(p)
and then I can reference p later on. But for my properties, of which there can be many, I create a string in python like so (pseudocode here, I have a nice oneliner for this that fits my actual case with lambda, join, map, format and so on)
for param in params:
par = "MERGE (par:" + param + ... )
tx.append(par)
tx.process()
tx.commit()
How do I create a relation "likes" back to the person for each and every par in the for loop?
Or do I need to rethink my whole solution?
Help?! :-)
//Jonas
Considering you've created a node Alice and you want to create the other as dynamic, I'll suggest while dynamically parsing through the nodes, store it everytime (in the loop) in a variable, make a node out of it and then implement in Relationship Syntax. The syntax is
Relationship(Node_1, Relation, Node_2)
Now key thing to know here is type(Node_1) and type(Node_2) both will be Node.
I've stored all the nodes (only their names) from json in a list named nodes.
Since you mentioned you only have reference to Alice
a = ("Person", name:"Alice")
for node in nodes: (excluding Alice)
= Node(, name:"")
= Relationship(a, ,
Make sure to iterate variable name, else it'll keep overwriting.
I'm quite green on Python and have been looking around for an answer to my particular question. Though I'm not sure if it's a Python specific question, or if I'm simply getting my OOP / design patterns confused.
I've got three files: main.py, board.py and player.py. Board and player each only hold a class Player and Board, main simply starts the game.
However I'm struggling with validating player positions when they are added to the board. What I want is to instantiate the board and consecutively new player object(s) in main.py, but check the board size in player.py when a new player is added to the board, to ensure the player is not outside of bounds upon creation.
As it is now I'm getting a TypeError (getX() missing 1 required positional argument: 'self') when attempting to access the board's size inside of player.py.
Most likely because the board isn't instantiated in that scope. But if I instantiate it in the scope that will be counted as a new object, won't it? And if I pass the board to the player as a variable that would surely be counted as bad practice, wouldn't it?
So how do I go about accessing the instance variables of one class from another class?
I have no idea if this will help, but I made a post on how to save and load using the Pickle import. In the saving function, it refers back to the Player class I created. It might help you, it might not. Here is the link anyway.
Your question is asking about a concept called "dependency injection." You should take some time to read up on it. It details the ways of making one object available to another object that wants to interact with it. While that's too broad to write up here, here are some of the basics:
You could have all objects you care about be global, or contained in a global container. They can all see each other and interact with each other as necessary. This isn't very object-oriented, and is not the best practice. It's brittle (all the objects are tightly bound together, and it's hard to change or replace one), and it's not a good design for testing. But, it is a valid option, if not a good one.
You could have objects that care about each other be passed to each other. This would be the responsibility of something outside of all of the objects (in your case, basically your main function). You can pass the objects in every method that cares (e.g. board.verify_player_position(player1)). This works well, but you may find yourself passing the same parameter into almost every function. Or you could set the parameter either through a set call (e.g. board.set_player1(player1)), or in the constructor (e.g. board = Board(player1, player2)). Any of these are functionally pretty much the same, it's just a question of what seems to flow best for you. You still have the objects pretty tightly bound. That may be unavoidable. But at least testing is easier. You can make stub classes and pass them in to the appropriate places. (Remember that python works well with duck typing: "If it walks like a duck and quacks like a duck, then it's a duck." You can make testing code that has the same functions as your board and player class, and use that to test your functions.)
A frequent pattern is to have these objects be fairly dumb, and to have a "game_logic" or some other kind of controller. This would be given the instances of the board and the two players, and take care of maintaining all of the rules of the game. Then your main function would basically create the board and players, and simply pass them into your controller instance. If you went in this direction, I'm not sure how much code you would need your players or board to have, so you may not like this style.
There are other patterns that will do this, as well, but these are some of the more basic.
To answer your direct questions: yes, the error you're seeing is because you're trying to invoke the class function, and you need it to be on an object. And yes, instantiating in that case would be bad. But no, passing an instance of one class to another is not a bad thing. There's no use in having objects if they don't interact with something; most objects will need to interact with some other object at some point.
You mentioned that you have code available, but it's a good thing to think out your object interactions a little bit before getting too into the coding. So that's the question for you: do you want player1.check_valid_position(board), or board.check_player(player1), or rules.validate_move(player, some_kind_of_position_variable)`. They're all valid, and they all have the objects inter-relate; it's just a question of which makes the most sense to you to write.
It's hard to know your exact issue without seeing some code, but I hope this is useful!
class Player:
def __init__(self, x, y, player_id):
self.x = x
self.y = y
self.id = player_id
class Board:
def __init__(self, width, height):
self.width = width
self.height = height
self.players = {}
def add_player(self, player):
"""keep track of all the players"""
self._validate_player(player)
# add the player to a dict so we can access them quickly
self.players[player.id] = player
def _validate_player(self, player):
"""whatever validation you need to do here"""
if player.x < 0 or player.x >= self.width:
raise ValueError("The player didn't land on the board! Please check X")
if player.y < 0 or player.y >= self.height:
raise ValueError("The player didn't land on the board! Please check Y")
# whatever other checks you need to do
# player is in a valid location!
def main():
# we can make as few or as many players as we'd like!
p1 = Player(10, 20, 0)
p2 = Player(-1, 10, 1) # invalid player
board = Board(50, 50) # we only need to make one board
board.add_player(p1)
board.add_player(p2) # value error
running = True
while running: # simple game loop
player.take_turn() # maybe prompt user to input something
board.update_players() # update player positions
board.display()
running = board.keep_playing() # check for win state
# whatever code you want to run
if __name__ == "__main__":
main()
Here we create an instance of a Player by assigning an x and y position, and in this case also a player ID which we can use to get that player when we need them. If there's only going to be one player, we could just do something like board.player.
In my example, a ValueError is raised when an invalid Player is provided, you can of course do whatever you'd like in the event that a Player is invalid, also your game could have any number of other cases for a Player being invalid.
I've added some method calls for some methods that might make sense for a board game.
As a side note, in python, you generally don't need to write getters/setters, it's perfectly okay to access Class fields directly.
player.x = 10
if player.y == 11: etc.
and if you have need for validation of some sort that could belong in a getter/setter, you can use the #property decorator.
I have a function that takes several arguments, one of which is a contact number. The data provided to the function is used to generate documents, and if one option is selected, that document is immediately returned inline, where the other option takes the contact number and generates an email. In the original version of this function, the contact number was immediately parsed at the start of the function, but I moved it into the else block as that is where the email is actually generated that uses that contact number and I saw no reason to create a new variable if it was not used half of the time. An example of this is below, and is built in Python using the Django framework:
def function(request, object, number=None):
obj = ObjectItem.objects.get(id=object)
# Originally number processed here
if request.method == 'POST':
if 'inline' in request.POST:
data = {
'object': obj,
}
return generate_document(data, inline=True)
else:
if number:
contact = '{}'.format(number)
else:
contact = obj.contact
data = {
'object': obj,
}
document = generate_document(data, inline=False)
return message(document, contact)
else:
return redirect()
While looking at my code, I realize that I could move the data dict creation outside of the processing for the inline vs no inline in the POST, but I do not know if moving the processing of the number argument into the else block in that processing actually saves any time or is the more standard way of doing things. I know that as Python is a scripting language, there is not any kind of optimizations that would be performed automatically like they would rearranging that kind of declaration in a compiled language, so I am looking for the most efficient way of doing this.
From a performance perspective, it makes no difference whether you create data above the if or in the if. Python will only hit the line once and the dict will only be created once. But you should move it above the if for design reasons.
First, don't repeat yourself - if you can reasonably implement a bit of code in one place, don't sprinkle it around your code. Suppose you decide a defaultdict is better later, you only have to change it in one place.
Second, placement implies intent. If you put it above your if you've made a statement that you plan to use that data structure everywhere. In your current code, readers will ask the same question you do... why wasn't that above the if? Its kinda trivial but the reading of the code shouldn't raise more questions.