patternpythonMinor
Pygame version of my 3D Tic Tac Toe/Connect 4
Viewed 0 times
connectversiontoetictacpygame
Problem
I posted a question a while back asking for some feedback on the code of a game I made (it was limited to typing the input and drawing the output in ASCII).
Now I've got it linked up with pygamef. Does anything look out of place? Do you notice any bugs? Do the colours work? Is there anything particularly annoying?
Use CTRL+SHIFT+d while in options (hit ESC to bring them up if you've already started the game) to reveal the debug settings, and enable to see the mouse coordinate conversion and AI stuff going on under the hood.
Instructions
The aim is to get as many complete rows as you can, and the grid will flip every 3 turns to throw you off, otherwise it gets a bit easy. The game ends when all spaces are taken (though this is a bit annoying when you are having to fill in the last few ones, so I'll just make it end when there are no points left).
At this time, I still need to make the instructions page and a 'player x won' page, though everything else is working without bugs as far as I can tell.
Normal game:
With debug enabled:
To see the entire thing, you'll need this link. If you don't have pygame (or python for that matter), here is a standalone version of the game from py2exe.
```
class MouseToBlockID(object):
"""Converts mouse coordinates into the games block ID.
The first part is to calculate which level has been clicked, which
then allows the code to treat the coordinates as level 0. From this
point, it finds the matching chunks from the new coordinates which
results in two possible blocks, then it calculates how they are
conected (highest one is to the left if even+odd, otherwise it's to
the right), and from that, it's possible to figure out which block
the cursor is over.
A chunk is a cell of a 2D grid overlaid over the isometric grid.
Each block is split into 4 chunks, and each chunk overlaps two
blocks.
"""
def __init__(self, x, y, grid_main):
self.x = x
self.y = y
sel
Now I've got it linked up with pygamef. Does anything look out of place? Do you notice any bugs? Do the colours work? Is there anything particularly annoying?
Use CTRL+SHIFT+d while in options (hit ESC to bring them up if you've already started the game) to reveal the debug settings, and enable to see the mouse coordinate conversion and AI stuff going on under the hood.
Instructions
The aim is to get as many complete rows as you can, and the grid will flip every 3 turns to throw you off, otherwise it gets a bit easy. The game ends when all spaces are taken (though this is a bit annoying when you are having to fill in the last few ones, so I'll just make it end when there are no points left).
At this time, I still need to make the instructions page and a 'player x won' page, though everything else is working without bugs as far as I can tell.
Normal game:
With debug enabled:
To see the entire thing, you'll need this link. If you don't have pygame (or python for that matter), here is a standalone version of the game from py2exe.
```
class MouseToBlockID(object):
"""Converts mouse coordinates into the games block ID.
The first part is to calculate which level has been clicked, which
then allows the code to treat the coordinates as level 0. From this
point, it finds the matching chunks from the new coordinates which
results in two possible blocks, then it calculates how they are
conected (highest one is to the left if even+odd, otherwise it's to
the right), and from that, it's possible to figure out which block
the cursor is over.
A chunk is a cell of a 2D grid overlaid over the isometric grid.
Each block is split into 4 chunks, and each chunk overlaps two
blocks.
"""
def __init__(self, x, y, grid_main):
self.x = x
self.y = y
sel
Solution
- MouseToBlockID
-
Normally an instance of a class represents some thing, that is, a persistent object or data structure. But an instance of
MouseToBlockID does not seem to represent any kind of thing. What you need here is a function that takes game coordinates and returns a block index.See Jack Diederich's talk "Stop Writing Classes".
Since this function makes use of the attributes of the
GridDrawData class, this would best be written as a method on that class:def game_to_block_index(self, gx, gy):
"""Return index of block at the game coordinates gx, gy, or None if
there is no block at those coordinates."""-
The naming of variables needs work. When you have coordinates in three dimensions, it's conventional to call them "x", "y" and "z". But here you use the name
y_coordinate for "z". That's bound to lead to confusion.-
The code is extraordinarily long and complex for what should be a simple operation. There are more than 200 lines in this class, but converting game coordinates to a block index should be a simple operation that proceeds as follows:
Adjust
gy so that it is relative to the origin of the bottom plane (the z=0 plane) rather than relative to the centre of the window:gy += self.centreFind
z:z = int(gy // self.chunk_height)Adjust
gy so that it is relative to the origin of its z-plane:gy -= z * self.chunk_heightReverse the isometric grid transform:
dx = gx / self.size_x_sm
dy = gy / self.size_y_sm
x = int((dy - dx) // 2)
y = int((dy + dx) // 2)Check that the result is in bounds, and encode position as block index:
n = self.segments
if 0 <= x < n and 0 <= y < n and 0 <= z < n:
return n ** 3 - 1 - (x + n * (y + n * z))
else:
return NoneAnd that's it. Just twelve lines.
-
It will be handy to encapsulate the transformation from block coordinates to block index in its own method:
def block_index(self, x, y, z):
"""Return the block index corresponding to the block at x, y, z, or
None if there is no block at those coordinates.
"""
n = self.segments
if 0 <= x < n and 0 <= y < n and 0 <= z < n:
return n ** 3 - 1 - (x + n * (y + n * z))
else:
return NoneSee below for how this can be used to simplify the drawing code.
-
The encoding of block indexes is backwards, with (0, 0, 0) corresponding to block index 63 and (3, 3, 3) to block index 0. You'll see that had to write
n ** 3 - 1 - (x + n (y + n z)) whereas x + n (y + n z) would be the more natural encoding.- GridDrawData
-
The computation of game coordinates for the endpoints of the lines is verbose, hard to read, and hard to check:
self.line_coordinates = [((self.size_x, self.centre - self.size_y),
(self.size_x, self.size_y - self.centre)),
((-self.size_x, self.centre - self.size_y),
(-self.size_x, self.size_y - self.centre)),
((0, self.centre - self.size_y * 2),
(0, -self.centre))]What you need is a method that transforms block coordinates into game coordinates:
def block_to_game(self, x, y, z):
"""Return the game coordinates corresponding to block x, y, z."""
gx = (x - y) * self.size_x_sm
gy = (x + y) * self.size_y_sm + z * self.chunk_height - self.centre
return gx, gyThen you can compute all the lines using block coordinates, which is much easier to read and check:
n = self.segments
g = self.block_to_game
self.lines = [(g(n, 0, n - 1), g(n, 0, 0)),
(g(0, n, n - 1), g(0, n, 0)),
(g(0, 0, n - 1), g(0, 0, 0))]
for i, j, k in itertools.product(range(n+1), range(n+1), range(n)):
self.lines.extend([(g(i, 0, k), g(i, n, k)),
(g(0, j, k), g(n, j, k))])-
Using
block_to_game you can avoid the need for relative_coordinates. Instead of:for i in self.C3DObject.range_data:
if self.C3DObject.grid_data[i] != '':
chunk = i / self.C3DObject.segments_squared
coordinate = list(self.draw_data.relative_coordinates[i % self.C3DObject.segments_squared])
coordinate[1] -= chunk * self.draw_data.chunk_height
square = [coordinate,
(coordinate[0] + self.draw_data.size_x_sm,
coordinate[1] - self.draw_data.size_y_sm),
(coordinate[0],
coordinate[1] - self.draw_data.size_y_sm * 2),
(coordinate[0] - self.draw_data.size_x_sm,
coordinate[1] - self.draw_data.size_y_sm),
coordinate]write:
```
n = self.draw_data.segments
g = self.draw_data.block_to_game
for x, y, z in itertools.product(range(n), repeat=3):
i = self.draw_data.block_index(x, y, z)
if self.C3DObject.grid_data[i] != '':
square = [g(x, y, z),
g(x + 1, y, z),
g(x + 1, y + 1, z),
Code Snippets
def game_to_block_index(self, gx, gy):
"""Return index of block at the game coordinates gx, gy, or None if
there is no block at those coordinates."""gy += self.centrez = int(gy // self.chunk_height)gy -= z * self.chunk_heightdx = gx / self.size_x_sm
dy = gy / self.size_y_sm
x = int((dy - dx) // 2)
y = int((dy + dx) // 2)Context
StackExchange Code Review Q#108411, answer score: 5
Revisions (0)
No revisions yet.