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

Dave Angel davea at davea.name
Sat Jan 3 12:58:34 CET 2015


On 01/02/2015 10:21 PM, Dave Angel wrote:
> On 01/02/2015 09:00 PM, 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. 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.
>> 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.
>> 2 "5"s followed or proceeded by an "11" will eliminate all 3 nodes.
>> 2 "6"s followed or proceeded by a "5" will eliminate all 3 nodes.
>> 2 "6"s followed or proceeded by a "19" will eliminate all 3 nodes.
>> 2 "11"s followed or proceeded by a "6" will eliminate all 3 nodes.
>> 2 "11"s followed or proceeded by a "20" will eliminate all 3 nodes.
>> 2 "19"s followed or proceeded by a "20" will eliminate all 3 nodes.
>> 2 "19"s followed or proceeded by an "11" will eliminate all 3 nodes.
>> 2 "20"s followed or proceeded by a "6" will eliminate all 3 nodes.
>> 2 "20"s followed or proceeded by a "5" will eliminate all 3 nodes.
>>
>> The main focus of the code is the find_eliminations method. I think its
>> implementation is smart since it uses a math trick, but then again I
>> wrote it.
>> My math trick works by first finding 2 matching nodes next to each other
>> out of every 3 nodes, then adding the values of all 3 nodes together and
>> checking for specific totals. None of the possible totals overlap.
>>
>> Here is the code:
>>
>> import random
>>
>>
>> class GameTile():
>>      def __init__(self, value, col, row, **kwargs):
>>          # id is grid (X,Y) which is equal to grid (col,row)
>>          self.id = str(col) + ',' + str(row)
>>          self.col = col
>>          self.row = row
>>          self.value = value
>>          self.eliminated = False
>>
>>      def __str__(self):
>>          #return '%d, %d' % (self.col, self.row)
>>          if len(str(self.value)) == 1:
>>              return ' ' + str(self.value)
>>          return str(self.value)
>>
>>
>> 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)])
>>
>>      def print_by_col(self):
>>          # As in going down the column assuming we started at the top.
>>          for col_num in range(self.cols):
>>              for row_list in self.grid:
>>                  print(row_list[col_num])
>>
>>      def print_by_row(self):
>>          # As in going right across the row assuming we started at the
>> left.
>>          for row in self.grid:
>>              for node in row:
>>                  print(node)
>>
>>      def check_bounds(self, x, y):
>>          return (0 <= x < self.rows) and (0 <= y < self.cols)
>>
>>      def lookup_node(self, x, y):
>>          if not self.check_bounds(x, y):
>>              return False
>>          return self.grid[x][y]
>>
>>      def draw(self):
>>          for col in self.grid:
>>              print(end='| ')
>>              for node in col:
>>                  print(node, end=' | ')
>>              print()
>>
>>      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.
>>          #First Down the columns.
>>          for col_num in range(self.cols):
>>              first = None
>>              second = None
>>              third = None
>>              for row_list in self.grid:
>>                  if row_list[col_num].row == 0:
>>                      first = row_list[col_num]
>>                  elif row_list[col_num].row == 1:
>>                      second = row_list[col_num]
>>                  elif row_list[col_num].row == 2:
>>                      third = row_list[col_num]
>>                  else:
>>                      first = second
>>                      second = third
>>                      third = row_list[col_num]
>
> This code is way too complex for what it accomplishes. See  below.
>
>>                  if third is not None:
>>                      self.check_total_and_eliminate(first, second, third)
>>          # Now across the rows.
>>          for row in self.grid:
>>              first = None
>>              second = None
>>              third = None
>>              for node in row:
>>                  if node.col == 0:
>>                      first = node
>>                  elif node.col == 1:
>>                      second = node
>>                  elif node.col == 2:
>>                      third = node
>>                  else:
>>                      first = second
>>                      second = third
>>                      third = node
>
> If the self.rows is 3, this is equivalent to just:
>                 first, second, third = row
> If the self.rows is larger, it's equivalent to:
>                 first, second, third = row[p: p+3]  for some value of p
>
>>                  if third is not None:
>>                      self.check_total_and_eliminate(first, second, third)
>
> But even better, let the method   check_total_and_eliminate take a slice
> as its argument.
>                    self.check-total_and_eliminate(row[p: p+3])
>
>>          # Set all eliminated nodes to a value of 0.
>>          for col in self.grid:
>>              for node in col:
>>                  if node.eliminated is True:
>>                      node.eliminated = False
>>                      node.value = 0
>>
>>      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
>>
>
> This doesn't come close to implementing the test you describe above. For
> example, a total of 29 could be your first case, 5,5,19.  But it could
> also be 4,4,21.   Or 6,6,17.  Etc.
>
> I suggest instead that you compare the values against a table of
> interesting values.  If you make your table a list of 3-tuples, it can
> be searched simply by:
>        if tuple(nodes) in table:
>              do-something

My error.  Since nodes are GameTile objects, not values, converting to a 
tuple is a little trickier:

        values = tuple( [node.value for node in nodes] )
        if values in table:
             do-something

>
> So you don't need the separate variables first, second, and third at all.
>
>>
>>
>> grid = GameGrid(4, 8)
>> grid.draw()
>> grid.find_eliminations()
>> print('After Eliminations')
>> grid.draw()
>>
>>
>> # END CODE
>>
>> After this part has been improved, I then need to search the columns
>> backwards to be able to drop any floating values. Any none zero values
>> should not have a zero value below it. That is what I mean by drop
>> floating values. I think this will be simple by just using range method
>> to count backwards. I will be working on coding that in the meantime.
>>
>
> for what you've described so far, it might be convenient to store the
> board and its transposed equivalent.  Then search the one for columns,
> and the other for rows.  That's a later optimization, but it might save
> time getting the more complicated parts figured out.
>

For example, print_by_col would look just like print_by_row, except that 
it would start with self.transposed_grid

The only real assumption needed for this simplification is that once the 
grid is set up, you never create any more GameTile objects, just 
manipulate the existing rectangle of objects.

To transpose a grid, you want to use the zip() function.
     self.transposed_grid = zip(*self.grid)


-- 
DaveA


More information about the Tutor mailing list