highscores list

Bruno Desthuilliers bdesth.quelquechose at free.quelquepart.fr
Sat Dec 8 16:43:40 EST 2007


Shawn Minisall a écrit :
> I'm writing a game that uses two functions to check and see if a file 
> called highScoresList.txt exists in the main dir of the game program.  
> If it doesn, it creates one.  That part is working fine.  The problem is 
> arising when it goes to read in the high scores from the file when I 
> play again.
> 
> This is the error msg python is giving me
> 
> Traceback (most recent call last):
> File "<pyshell#0>", line 1, in <module>
>   main()
> File "I:\PYTHON\PROJECT #3\PROJECT3.PYW", line 330, in main
>   if(hasHighScore(wins) == True):
> File "I:\PYTHON\PROJECT #3\PROJECT3.PYW", line 175, in hasHighScore
>   scores[i],names[i] = string.split(line,"\t")
> ValueError: need more than 1 value to unpack
> 
> Here's the relavant code:
> 
> def hasHighScore(score):
>    #opens highScoresList.txt
>    infile = open("highScoresList.txt",'r')

hardcoded file names are a bad idea. And FWIW, your file will be looked 
for in the current wirking directory - which is not necessarily the 
"main dir of the game program".

>    scores = [0,0,0]
>    names = ["","",""]
> 
>    #reads in scores from highScoresList.txt
>    i=0
>    for line in infile.readlines():

You can iterate directly over the file.

>        scores[i],names[i] = string.split(line,"\t")

string.split(str, sep) is long time deprecated. Use str methods instead:
          s, n = line.split('\t')

Also, this will obviously raise if the line doesn't have exactly one tab 
in it. Like, ie, it's an empty line....

>        names[i]=string.rstrip(names[i])
>        i += 1

You can use enumerate(iterable) instead of manually counting lines.
Also, what if your file has more than 3 lines ?


>    infile.close()
>      #compares player's score with those in highScoresList.txt
>    i=0
>    for i in range(0,len(scores)):

You obviously don't know how to use Python's for loop:

      for item in scores:
         # now use item instead of scores[i]

>        if(score > int(scores[i])):
>            return True
>        else:
>            return False

You have a logic error here. This will only compare the first item in 
your score list. You want something like:

     for item in score:
        if score > int(item):
          return True
     return False

> 
>   def setHighScores(score,name):
>    #opens highScoresList.txt
>    infile = open("highScoresList.txt",'r')
>    scores = [0,0,0]
>    names = ["","",""]
> 
>    #reads in scores from highScoresList.txt
>    i=0
>    for line in infile.readlines():
>        scores[i],names[i] = string.split(line,"\t")
>        scores[i]=int(scores[i])
>        names[i]=string.rstrip(names[i])
>        i += 1
>    infile.close()

hem... don't you see something like a duplication here ? By all mean 
extract out this code in a 'read_scores' function.

>      #shuffles thru the highScoresList.txt and inserts player's score if 
> higher then those in file
>    i=len(scores)
>    while(score > scores[i-1] and i>0):
>        i -= 1
> 
>    scores.insert(i,score)
>    names.insert(i,name)
>    scores.pop(len(scores)-1)
>    names.pop(len(names)-1)

OMG.

This is ten times more complicated than it needs to be.

>      #writes new highScoresList.txt
>    outfile = open("highScoresList.txt","w")
> 
>    outfile.write ("     High Score                 Name             \n")
>    outfile.write ("-------------------------------------------------\n")
>  
>    i=0
>    for i in range(0,len(scores)):
>        outfile.write("\t" + str(scores[i]) + "\t\t\t" + names[i] + "\n")

If your file is formated that way, no surprise your code breaks. None of 
   what you write in it matches the expectations of the code that reads it.

>    outfile.close()

> And here's the call to the functions at the end of my game, included in 
> the error msg.
> 
>    #adds player's score to high score list if high enough
>    if(hasHighScore(wins) == True):
>        setHighScores(wins,getName(wins))

And you're doing two times the same parsing of the file....

> The answer is probably simple,

The answer to your question is indeed quite simple : either rewrite the 
code that reads the file to make it matches what you wrote in the file, 
or rewrite the code that writes the file to make it match the 
expectations of the code that reads it. IOW : make both parts of the 
code work on a same file format !-)

Also, learning to make effective use of Python's features would help !-)

Here's a possible reimplementation of your code - not tested, so it may 
have bugs, but it should do the trick.

The file format is a very simple 'score:name' per line. Nothing else. 
(heck, this file is for storing data, it's not meant to be seen by the 
user).

The hiscore file full path must be passed when instantiating the 
Hiscores object (that is, when initializing your program - you just need 
one Hiscore object for the whole lifetime of your program). You must 
also pass the max number of hiscores you want to keep, and an optional 
flag telling if an error while reading the hiscore file should raise an 
exception or be ignored (ending up using an empty hiscore list).

hiscores = HiScores('/path/to/your/file.ext', 3)

Once done, you just use it:

if hiscores.update(42, 'bibi'):
   print "Yay, your a boss"
else:
   print "try again..."


# hiscores.py
import sys

def _read_scores(path):
     f = open(path)
     # we don't expect a huge file so it's simpler to
     # read it all in memory
     lines = f.readlines()
     f.close()

     scores = []
     for line in filter(None, map(str.strip, lines)):
         try:
             score, name = line.split(':')
             score = int(score)
         except ValueError, e:
             # either the lines was not score:name or
             # score wasn't a proper value for an int
             err = "File %s : incorrect file format" \
                   % path
             raise ValueError(err)
         else:
             scores.append((score, name))

     # supposed to be already sorted, but we want to be sure.
     # NB : natural sort will do the RightThing(tm) here
     scores.sort()
     return scores

def _write_scores(path, scores):
     scores = "\n".join(["%s:%s" % item for item in scores])
     f = open(path, 'w')
     f.write(scores)
     f.close()

class HiScores(object):
     def __init__(self, path, nb_scores, raise_on_error=False):
         self._path = path
         self._nb_scores = nb_scores
         try:
             self._scores = _read_scores(path)[0:self.nb_scores - 1]		
         except (IOError, ValueError), e:
             if raise_on_error:
                raise
             else:
                # log the error
                err = "Error while reading hiscore file %s : %s" \
                       % (path, e)
                print >> sys.stderr, err
                # use an empty list instead
                self._scores = []

     def index(self, score):
         for i, s in enumerate(self._scores):
             if score > s[0]:
                 return i
         return -1

     def update(self, score, name):
         index = self.index(score)
         if index == -1:
             return False

         self._scores.insert(index, (score, name))
         self._scores.pop()
         _write_scores(self._path, self._scores)
         return True

     def is_hi_score(self, score):
         return self.index(score) > -1

HTH



More information about the Python-list mailing list