-1

I have a Python function that takes as arguments a player's name and score and determines whether this is the player's highest score. It does so by comparing the arguments against a shelve object.

The shelve should only store the high score for each player; there should not be to scores for someone named "Joe" for example.

Unfortunately, I can't figure out how to isolate a dict from the list of dicts (shelf) for comparison against the incoming player dict.

Here's my code:

import shelve
import os


def highscore(player_name, player_score):
    """
    Function to return the high score from our
    persistent storage of score records for a given
    person.
    """
    # Get our working directory
    working_dir = os.getcwd()
    # Create our shelf object for a player
    highscore_fn = os.path.join(working_dir, 'highscore.shelve')
    # Set our player info
    player = {'name': player_name, 'score': player_score}

    with shelve.open(highscore_fn, writeback=True) as shelf:
        # Check if any records exist in the shelf
        if len(shelf) == 0:
            # Assign the shelf to an empty list
            shelf['player_data'] = []
            # Append player data to shelf
            shelf['player_data'].append(player)
            # Current high score for player
            high_score = player.get('score')
        else:
            # Loop through our player data list
            for data in shelf['player_data']:
                # Check to see if we have data for a player
                if player['name'] in data['name']:
                    existing_record = data
                    # Compare the player's new score against previous score
                    if player.get('score') > existing_record.get('score'):
                        high_score = player.get('score')
                        # Update our record for the player
                        existing_record.update(player)
                    else:
                        high_score = existing_record.get('score')
                else:
                    high_score = player.get('score')
                    shelf['player_data'].append(player)

    # Return the high score
    return high_score

Any tips would be appreciated!

Patrick Beeson
  • 1,667
  • 21
  • 36

3 Answers3

1

If you don't have another reason to use a list of dicts, just using a dict of dicts (or even a simple dict) would greatly simplify your code. Assuming your shelf looks something like

{
   "user_data": { 
      "joe": {"score": 2999, "name":"joe"}, 
      "walt": {"score": 1784, "name":"walt"}, 
      "bunny": {"score": 87441, "name":"bunny"}, 
      # etc
      },
}

Then your code would look like:

player = {'name': player_name, 'score': player_score}
high_score = player_score
with shelve.open(highscore_fn, writeback=True) as shelf:
    user_data = shelf["user_data"]
    # Check if any records exist in the shelf
    found = user_data.get(player_name)
    if found:
        if found["score"] < player_score:
            user_data[player_name] = player
        else:
            high_score = found["score"]
    else:
        user_data[player_name] = player
    shelf["user_data"] = user_data

return high_score

Note that if the shelf only contains "user_data", you can get rid of this level and directly store your dicts in the shelf itself. Also if you only have scores to save, you can turn your dict of dicts into a simple dict, ie:

=> your shelf:

{
  "joe": : 2999, 
  "walt": 1784, 
  "bunny": 87441, 
  # etc
}

=> your code:

high_score = player_score
with shelve.open(highscore_fn, writeback=True) as shelf:
    # Check if any records exist in the shelf
    found = shelf.get(player_name, 0)
    if found > player_score:
        high_score = found
    else:
        shelf[player_name] = player_score

return player_score

EDIT: The following code JustWorks(tm) on 2.7.3:

# scores.py
import shelve

DATA = {
    "user_data": { 
        "joe": {"score": 2999, "name":"joe"}, 
        "walt": {"score": 1784, "name":"walt"}, 
        "bunny": {"score": 87441, "name":"bunny"}, 
        # etc
        },
    }


class Score(object):
    def __init__(self, path):
        self.path = path

    def init_data(self, data):
        shelf = shelve.open(self.path)
        shelf["user_data"] = data["user_data"]
        shelf.close()

    def read_data(self):
        d = {}
        shelf = shelve.open(self.path)
        d["user_data"] = shelf["user_data"]
        shelf.close()
        return d

    def highscore(self, name, score):
        player = {'name': name, 'score': score}
        high_score = score
        shelf = shelve.open(self.path)
        user_data = shelf["user_data"]
        found = user_data.get(name)
        if found:
            if found["score"] < score:
                user_data[name] = player
            else:
                high_score = found["score"]
        else:
            user_data[name] = player
        shelf["user_data"] = user_data
        shelf.sync()
        shelf.close()
        return high_score


>>> import scores
>>> s = scores.Score("scores.dat")
>>> s.init_data(scores.DATA)
>>> s.read_data()
{'user_data': {'walt': {'score': 1784, 'name': 'walt'}, 'joe': {'score': 2999, 'name': 'joe'}, 'bunny': {'score': 87441, 'name': 'bunny'}}}
>>> s.highscore("walt", 10000)
10000
>>> s.read_data()
{'user_data': {'walt': {'score': 10000, 'name': 'walt'}, 'joe': {'score': 2999, 'name': 'joe'}, 'bunny': {'score': 87441, 'name': 'bunny'}}}
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • So, this wasn't quite what I needed, though it got me thinking in the right direction. This doesn't store the player's high score if subsequent scores are entered for the same player. – Patrick Beeson Oct 31 '14 at 01:41
  • 1
    @PatrickBeeson: there was a typo and a missing in the first example indeed. FWIW I don't know which Python version you're using but on the one I have here (2.7.3), `with shelve.open()` raises "AttributeError: DbfilenameShelf instance has no attribute '__exit__'". – bruno desthuilliers Oct 31 '14 at 14:04
0

EDITED Here's the refactored code that addressed my question:

import shelve
import os


def highscore(name, score):
    """
    Function to return the high score from our
    persistent storage of score records for a given
    person.
    """
    # Get our working directory
    working_dir = os.getcwd()
    # Create our shelf object for a player
    highscore_fn = os.path.join(working_dir, 'highscore.shelve')
    # Open our shelf
    with shelve.open(highscore_fn, writeback=True) as shelf:
        # Check if player exists in shelf
        if name in shelf:
            # Check if score is greater than existing score
            if score > shelf[name]:
                # Assign our new high score
                high_score = score
                # Assign the value of player to our shelf
                shelf[name] = score
            else:
                # Existing high score stands for player
                high_score = shelf[name]
        else:
            # Assign the player to the shelf
            shelf[name] = score
            # Assign the high score to the player score
            high_score = score

    return high_score
Patrick Beeson
  • 1,667
  • 21
  • 36
0

I played a little with you script. It works fine, but when I looked what contained the shelve, i found :

{ 'player_data' : [ { 'score': 10, 'name': 'joe' }, { 'score': 5, 'name': 'jim'} ] }

That it one dictionary (the shelve itself) that contains a list of dicts, that in turn only have 2 attributes.

And in your code it is clear that all you want is the attributes (here [high]score) of a player (here a name).

My advice would be to rewrite it, unless the format of the shelve is part of a kink of public API, so that the shelve becomes :

{ 'joe': 10, 'jim' : 5 }

or if you later want to add other attributes per player :

{ 'joe': { 'score': 10}, 'jim' : { 'score': 5} }

That way, all that becomes :

player = {'name': player_name, 'score': player_score}

with shelve.open(highscore_fn) as shelf:
    high_score = shelf[player_name] if shelf.has_key(player_name) else None
    if (high_score is None) or (high_score < player_score):
        high_score = player_score
        shelf[player_name] = high_score
    shelf.close()

return high_score

It is only a slight variation on bruno desthuilliers answer, but I tested id and :

  • it correctly keep and displays higher score per user
  • the version that only keeps higher score does not need writeback=true since what is changed is only a top level element of the shelf
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252