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

Dave Angel davea at davea.name
Sat Jan 3 04:21:11 CET 2015

```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

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.

--
DaveA
```