[Tutor] Improving My Simple Game Code for Speed, Memory and Learning

WolfRage wolfrage8765 at gmail.com
Sat Jan 3 17:14:54 CET 2015


On 01/03/2015 10:09 AM, Steven D'Aprano wrote:
> On Fri, Jan 02, 2015 at 09:00:22PM -0500, WolfRage wrote:
>> Python3.4+ Linux Mint 17.1 but the code will be cross platform (Mobile,
>> Windows, Linux, OSX).
>>
>> First an explanation of how the game works: The game is a simple
>> matching game but with a twist. Instead of matching a straight 3 in a
>> row, we have some rules that only certain combinations will result in an
>> elimination. In general 2 of the same value in a row with with 1 of 2
>> other possible values will eliminate all three nodes.
>
> Do you mean "1 of 2" or "1 or 2"?
1 of 2.
>
>
>> Eliminations can
>> occur either horizontally or vertically but not diagonally. Each tile
>> has a value now, that value is used when evaluating the rules. Currently
>> there are only 5 allowed values of 0-4 although more could be added
>> later, so any solution needs to be expandable.
>
> Okay, so the values are 0, 1, 2, 3, and 4. This doesn't match your code
> belong. Is the code wrong or your description?
>
My description was wrong, I am sorry I had started to simplify my code 
when I was first writing this, but realized that ruined my algorithm for 
finding matches.That is probably where I confused Dave.
>
>> These are the basic rules that allow for an elimination to occur(values
>> are put in quotes to distinguish values):
>> 2 "5"s followed or proceeded by a "19" will eliminate all 3 nodes.
> [...]
>
> Since neither 5 nor 19 is a possible value, this can never apply.

Sorry again, these are actually the correct values. But my earlier 
mistake misleads on this section that is actually correct.
>
> All the other rules you give include values greater than 4, and likewise
> can never apply. That means that no eliminations are ever possible. I
> think your description needs some work :-)
>
Yes. Sorry. Let me re-do that.

First an explanation of how the game works: The game is a simple
matching game but with a twist. Instead of matching a straight 3 in a
row, we have some rules that only certain combinations will result in an 
elimination. In general 2 of the same value in a row with with 1 of 2
other possible values will eliminate all three nodes. Eliminations can
occur either horizontally or vertically but not diagonally. Each tile
has a value now, that value is used when evaluating the rules. Currently 
there are only 5 allowed values of [5, 6, 11, 19, 20] although more 
could be added [52, 61, 67, etc] later, so any solution needs to be 
expandable.
The remainder of the explanation was correct. Sorry about that.

>
>> class GameTile():
> [...]
>>      def __str__(self):
>>          #return '%d, %d' % (self.col, self.row)
>>          if len(str(self.value)) == 1:
>>              return ' ' + str(self.value)
>>          return str(self.value)
>
> Eliminate comments which no longer apply. Don't use them for keeping
> old, obsolete or wrong code.
>
OK, good point, I should be using my bzr revision control.

> This method can be better written like this:
>
>      def __str__(self):
>          return "%2d" % self.value
>
I was switching between the 2 implementations of __str__ when I was 
testing some new code, but now need. I will just make it concrete and 
then directly request any other values.
>
>> class GameGrid():
>>      def __init__(self, cols=8, rows=7, **kwargs):
>>          self.cols = cols
>>          self.rows = rows
>>          self.make_grid()
>>
>>      def make_grid(self):
>>          # grid is 2d array as x, y ie [x][y].
>>          self.grid = []
>>          for row_num in range(self.rows):
>>              self.grid.append([GameTile(value=random.choice([5, 6, 11,
>> 19, 20]), col=col_num,
>>                  row=row_num) for col_num in range(self.cols)])
>
> That's rather gnarly code. If you're going to do that, it's probably
> nice to use a temporary variable to clarify your intention:
>
>          for row_num in range(self.rows):
>              row = [GameTile(
>                     value=random.choice([5, 6, 11,19, 20]), col=col_num,
>                     row=row_num) for col_num in range(self.cols)
>                     ]
>              self.grid.append(row)
>
>
> Which is still pretty gnarly. You only have three arguments to GameTile,
> why do you need to use keyword arguments? This is neater and less
> noisy, although it does require you get the order right:
>
I don't need them. Just hadn't settled on the order of them. But I will 
eliminate the requirement for the keyword arguments.

>          # untested
>          for row_num in range(self.rows):
>              row = [GameTile(row_num, col_num, random.choice([5, 6, 11,19, 20])
>                     for col_num in range(self.cols)
>                     ]
>              self.grid.append(row)
>
> Even better:
>
>          for row_num in range(self.rows):
>              self.make_row(row_num)
>
>      def make_row(self, row_num):
>          values = self.allowed_values   # [5, 6, 11,19, 20]
>          row = [GameTile(row_num, col_num, random.choice(values)
>                 for col_num in range(self.cols)]
>          self.grid.append(row)
>
>
> Notice that the values are stored in an attribute, for ease of
> maintanence in the future when you extend them.
>
Much better. I think I need to do better at breaking down the given 
tasks to their smallest chunks to best take advantage of functions.
>
>>      def find_eliminations(self):
>>          # I define 3 variables for holding the 3 nodes/tiles that I am
>>          # currently checking to see if an elimination possibility exists.
>>          # It uses a math trick to check for elimination by adding the values
>>          # and checking for specific totals. None of the possible totals overlap.
>
> Sorry, I'm too tired to make sense of this function right now :-(
>
>
>>      def check_total_and_eliminate(self, first, second, third):
>>          total = None
>>          if first.value == second.value:
>>              total = first.value + second.value + third.value
>>          elif second.value == third.value:
>>              total = first.value + second.value + third.value
>>          if total == 17 or total == 21 or total == 28 or total == 29 or \
>>              total == 31 or total == 42 or total == 45 or total == 46 \
>>              or total == 49 or total == 58:
>>              first.eliminated = True
>>              second.eliminated = True
>>              third.eliminated = True
>
> I think this method does too much. I would prefer to see it split into
> two methods, one to check the total, and the other to eliminate the
> cells if needed:
>
>      def eliminate(self, first, second, third):
>          first.eliminated = True
>          second.eliminated = True
>          third.eliminated = True
>
>      def check_total(self, first, second, third):
>          if first.value == second.value or second.value == third.value:
>              total = first.value + second.value + third.value
>          return total in (17, 21, 28, 29, 31, 42, 45, 46, 49, 58)
>
>
> which you then use:
>
>       if self.check_total(first, second, third):
>           self.eliminate(first, second, third)
>
Yes, another example of me trying to have my functions do too much. 
Thanks I will break it up.

Thanks again for the help, it should teach me to write better code in 
the future.


More information about the Tutor mailing list