I have been trying to figure out a way to check each adjacent cell for my minesweeper game and am coming up short. I am a beginner in python and would also like to start using OOP. however before I can even get there, I need to rectify this. all the tutorials I have seen don't use basic python, but different versions to the IDLE i use, so I am struggling. can anyone help me? I need to be able to go around each adjacent cell and check if there is a bomb there. The value to check if there is a bomb there is 1 and will also turn red. Thank you all so much!
also if you could dumb it down a little for me, that would be lovely.
import random
import pygame
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
WIDTH = 20
HEIGHT = 20
MARGIN = 5
bombnum = 10
grid = []
for row in range(10):
grid.append([])
for column in range(10):
grid[row].append(0)
print(grid)
pygame.init()
WINDOW_SIZE = [255, 315]
screen = pygame.display.set_mode(WINDOW_SIZE)
pygame.display.set_caption("Array Backed Grid")
done = False
clock = pygame.time.Clock()
#class bomb:
#def revealed(self,pick):#this is the grid thats been picked
# self.shown = shown
def placebomb():
for i in range(bombnum):
while True:
row = random.randint(0,8)
column = random.randint(0,8)
if grid[row][column] == 0:
grid[row][column] = 1
break
placebomb()
# -------- Main Program Loop -----------
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.MOUSEBUTTONDOWN:
pos = pygame.mouse.get_pos()
column = pos[0] // (WIDTH + MARGIN)
row = (pos[1]-50) // (HEIGHT + MARGIN)
grid[row][column] = 1
print("Click ", pos, "Grid coordinates: ", row, column)
screen.fill(BLACK)
for row in range(10):
for column in range(10):
color = WHITE
if grid[row][column] == 1:
color = RED
pygame.draw.rect(screen,
color,
[(MARGIN + WIDTH) * (column) + MARGIN,
50+(MARGIN + HEIGHT) * row + MARGIN,
WIDTH,
HEIGHT])
clock.tick(60)
pygame.display.flip()
pygame.quit()
First up, you definitely want to make an OOP project out of this. Minesweeper's probably near the complexity limit of what you can reasonably do without object-oriented programming, but if you want to take the basic Minesweeper concept and make it more interesting / complex, you're going to need better structure.
And even if you're not considering making it more complex, thinking about what sorts of complexities you could add in is helpful in planning your classes. (Perhaps you didn't realize there's a "planning your classes" step? I'll get to that.) My steps here should work for pretty much any beginning OOP project, since it seems Minesweeper's just a convenient example.
I realize this answer will be a giant diversion from OP's how-do-I-check-the-nearest-neighbors question, but OP was also asking about OOP and answering their question in an OOP context means getting a class model in place. Trying to retrofit OOP onto non-OOP code is possible, but is usually harder than doing OOP from scratch.
If you're new to OOP in Python, check these two tutorials. Both of them run through the basics, neither requires an IDE, and neither introduces complexity / features beyond what you need to start. The rest of my answer will assume you're familiar with how to write classes. I highly recommend typing out both tutorials on your own terminal and trying a couple variants on them before you go further.
Now that you know roughly what a class is, make a list of all the operations you're going to want in your Minesweeper program, and what sorts of things come up in it. The point here isn't to write code, and natural language will serve as pseudocode here. Things you'll probably want to include: "Add a mine to a cell on the grid," "check a cell on the grid," "display the number of spots near a given cell on the grid." You might also include some "future plans" like "resize the grid in the middle of a game", "limit number of moves," "get hint," "allow mines to move," "have people that move through minefield," multiple types of checks / mines / cells, hex grid as an alternative to a square grid, strangely-shaped and 3D grids, etc. Don't worry if you don't know how to code those things yet. If you've got an interesting idea, include it on the list.
Go through that list and make some notes about the main words / concepts that keep coming up. These will be your classes. For Minesweeper, I'd list "Cell," "Grid," "Game," "Check," and "Mine," but your mileage may vary. These are not necessarily the final class list. They're just a step toward organizing your thoughts so you can turn the high-level "Minesweeper game" into something concrete & extensible. You can add / remove classes later once you get a better feel for which ones you'll actually use.
Note the relationships between objects of the classes, including which ones need to be described for the others to make sense / be instantiated. This step is analogous to how you might plan a relational database: A Game has one and only one Grid, a Grid is a collection of Cells arranged in a fixed pattern, a Cell may or may not contain a single Mine, a Check reveals whether a Mine is in a given Cell and if not, reveals the number of Mines in Cells adjacent to the Checked Cell, etc.
Open up your IDE, and start blocking out classes in a file. The point here isn't so much to write code as to get all your classes and notes into the code for easy reference. If this file starts to get big, think about how the classes group together and split that one file into several, with import statements connecting them. By "blocking out classes," I mean "writing the simplest classes which will compile, but don't try to make them run." At this stage, you can / should have classes that look like this:
class Grid:
"""A Game has one and only one Grid, a Grid is a collection of Cells arranged in a fixed pattern"""
def __init__(self, game, **setup_params):
"""Initialize the grid given a game and possibly other parameters for shape and size"""
raise NotImplementedError('This code has not yet been written.')
def resize_grid(self, new_size):
raise NotImplementedError('This code has not yet been written.')
def check_cell_at(self, position):
raise NotImplementedError('This code has not yet been written.')
Things to check: This is completely legal Python and compiles fine. All of your notes from steps 2-4 should end up in docstrings. All of the target functionality you described in your notes corresponds to particular methods on classes that have something to do with those functions. Every class you've described is present, and has a docstring describing its purpose & structure. Every class's __init__() method takes the arguments that are necessary to instantiate the class. Every method takes some parameters that will likely be helpful. You can always edit the parameter lists later, but again, the point is to organize your code before you get too far into writing it, so that relationships and functionality are easy to track. If you're using Git or another version-tracking tool, make your first commit as soon as you're done with this step.
Now that you've got all the structure blocked out, figure out what the "main entry point" is going to be, instantiate that class (probably Game in my approach), and check that your code compiles, runs, and exits with a NotImplementedError coming with the line and message you expect. (If so, that's success!) If the project feels big, or if you're going to be sharing code with other developers, you may also want to add unit tests at this stage. The expectation is that every test will fail, but writing the tests helps to document the planned functionality.
Fill in the methods one by one, add unit tests to go with new or updated methods, and try running the code a bit at a time. You don't have to write the methods in any particular order, but it will be easier to test once all the __init__() methods are complete, and those are generally straightforward.
Now, how do you check the neighbors of a cell?
You should have methods on a Cell for "get list of my neighbors," and "get whether this cell contains a mine." You probably also have a method on Grid to "get Cell at position." You might have multiple types of Checks, but the base Minesweeper game only has the one, so Cell has a method like
def check(self):
"""Returns "BOMB" if this cell has a Bomb. Otherwise, returns the number of neighbors with bombs as an integer."""
if self.has_bomb:
return "BOMB"
neighboring_mines = 0
for cell in self.grid.get_neighbors_of(self.position):
if cell.has_bomb:
neighboring_mines += 1
return neighboring_mines
There are only 8([1,1],[1,0],[1,-1],[0,1],[0,-1],[-1,1],[-1,0],[-1,-1] relative to the clicked grid coordinate) adjacent squares, so it should be easy to do what you are asking only with "if" statements
anyway, just check the surrounding squares and if true add to a variable.
import random
import pygame
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
WIDTH = 20
HEIGHT = 20
MARGIN = 5
bombnum = 10
grid = []
for row in range(10):
grid.append([])
for column in range(10):
grid[row].append(0)
#print(grid)
pygame.init()
WINDOW_SIZE = [255, 315]
screen = pygame.display.set_mode(WINDOW_SIZE)
pygame.display.set_caption("Array Backed Grid")
done = False
clock = pygame.time.Clock()
#class bomb:
#def revealed(self,pick):#this is the grid thats been picked
# self.shown = shown
def placebomb():
for i in range(bombnum):
while True:
row = random.randint(0,8)
column = random.randint(0,8)
if grid[row][column] == 0:
grid[row][column] = 1
break
placebomb()
print(grid)
# -------- Main Program Loop -----------
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.MOUSEBUTTONDOWN:
pos = pygame.mouse.get_pos()
column = pos[0] // (WIDTH + MARGIN)
row = (pos[1]-50) // (HEIGHT + MARGIN)
grid[row][column] = 1
print("Click ", pos, "Grid coordinates: ", row, column)
NBombs = 0
for i in range(-1,2):
for k in range(-1,2):
if (i!=0 or k!=0) and (row+i>=0) and (column+k>=0) and (row+i<len(grid)) and (column+k<len(grid[0])):#prevents from both being 0, or for the index to be negative or out of range
print(i,k)
if grid[row+i][column+k] == 1:
NBombs+=1
print("Number of bombs:",NBombs)
screen.fill(BLACK)
for row in range(10):
for column in range(10):
color = WHITE
if grid[row][column] == 1:
color = RED
pygame.draw.rect(screen,
color,
[(MARGIN + WIDTH) * (column) + MARGIN,
50+(MARGIN + HEIGHT) * row + MARGIN,
WIDTH,
HEIGHT])
clock.tick(60)
pygame.display.flip()
pygame.quit()
So basically like another answer in this thread, you can check the surrounding/adjacent tiles by adding -1, 0, or 1 to both your current x and y.
You could write a separate function that would return a list of the coordinates of the surrounding tiles with bombs and take the tile coordinates and grid as arguments like the following:
def check_surrounding_tiles(current_x, current_y, grid):
bomb_tiles = []
#Check starting from position (current_x - 1, current_y - 1)
for i in range(-1, 2):
for j in range(-1, 2):
#Ignore the current x and y, and do bounds checking
if (i != 0 or j != 0) and (current_x + i) > -1 and (current_y + j) > -1 and (current_x + i) < len(grid) and (current_y + j) < len(grid[0]):
if grid[current_x + i][current_y + j] == 1
bomb_tiles.append[(current_x + i, current_y + j)]
return bomb_tiles
This, like mentioned above, will return a list of coordinate pairs with bombs in them.
If you need to get the number of bombs, simply use the following:
adjacent_bomb_count = len(check_surrounding_tiles(<x position>,<y position>, grid))
I'm working on a simple simulation using Pygame. For start I need to create 20 objects and randomly place those along edges of the game window, excluding the top edge. SingleCell class manages objects and defines randomised starting positions for sprites.
This class is then being called in the main simulation class to create 20 sprites and add them to a group:
def _create_cell(self):
"""Create a single sprite and add it to group"""
for cell in range(0,self.settings.cell_count):
c = SingleCell(self)
c_width, c_height = c.rect.size
self.cells.add(c)
This all works fine, but quite a few sprites end up overlapping. In order to fix it after studying docs for pygame.sprite I decided to use pygame.sprite.spritecollideany() in a loop to check whether any of the sprites in a group do collide with one another and move them either horizontally or vertically by width or height, respectively, +1 pixel:
def _check_overlapping_cells(self):
"""Check cells group for collisions based on rect"""
for cell in self.cells:
if pygame.sprite.spritecollideany(cell, self.cells,
collided=None) != 'None':
#If the collision happens along the vertical boundary
#move the sprite down by 1 height +1 pixel
if cell.rect.x == 0 or cell.rect.x == (
self.settings.screen_width - cell.rect.width):
cell.rect.y += (cell.rect.height + 1)
#If the collision along horizontal edge then update x-coord
#by sprite width +1 pixel
elif cell.rect.y == 0:
cell.rect.x += (cell.rect.width + 1)
This worked. Sort of. Some of sprites would still be overlapping others in their new locations. So instead of if I've decided to use while cycle to keep moving them around until there are no more collisions:
def _check_overlapping_cells(self):
"""Check cells group for collisions based on rect"""
for cell in self.cells:
while pygame.sprite.spritecollideany(cell, self.cells,
collided=None) != 'None':
Unfortunately, this causes the sim to enter a seemingly neverending cycle of moving sprites around.
I'm a bit confused as to how to do it properly. Any advice?
EDIT:
I have since tried another approach of trying to check collisions when a sprite is being created by modifying _create_cell method so now it looks like this:
def _create_cell(self):
"""Create a single cell and add it to group"""
for cell in range(0,self.settings.cell_count):
c = SingleCell(self)
c_width, c_height = c.rect.size
if pygame.sprite.spritecollideany(c, self.cells,
collided=None) != 'None':
#If the collision happens along the vertical boundary
#move the sprite up by 1 height +1 pixel
if c.rect.x == 0 or c.rect.x == (
self.settings.screen_width - c.rect.width):
c.rect.y += (-c.rect.height - 1)
self.cells.add(c)
#If the collision along horizontal edge then update x-coord
#by sprite width +1 pixel
elif c.rect.y == (self.settings.screen_height - c.rect.height):
c.rect.x += (c.rect.width + 1)
self.cells.add(c)
elif pygame.sprite.spritecollideany(c, self.cells,
collided=None) == 'None':
self.cells.add(c)
But this way results in fewer than 20 sprites being created and some are still overlapping for some reason.
Perhaps something like this:
def _create_cell(self):
"""Create a single cell and add it to group"""
for cell in range(0,self.settings.cell_count):
c = SingleCell(self)
while spritecollideany(c, self.cells):
c = SingleCell(self)
c_width, c_height = c.rect.size
self.cells.add(c)
Basically, the while loop keeps generating new Cells until it finds one that doesn't collide with any in self.cells. Of course, it that is not possible, then it will loop forever. You could add a counter and abort if it tries too many times.
I am working on a snake game using python and pygame but having problem in checking whether the snake has crossed the food or not. Can anyone here help me with that?
I've tried making the location of food to be a multiple of 10 since my snake width and height is also 10 and the window (width and height) is also a multiple of 10.
food_x = random.randrange(0, displayWidth-foodWidth, 10)
food_y = random.randrange(0, displayHeight-foodHeight, 10)
I expected that doing so will make the location of the food such that there will be no collisions but direct overlap of snake and food which would make the coding easier. However, there were collisions also.
So given your snake data structure is a set of rectangles, and the snake only "eats" from the head-rectangle, it's pretty simple to determine a collision routine.
The PyGame rect library has functions for checking collisions between rectangles.
So assuming head_rect is a rect with the co-ordinates and size of your snake's head, and food_rect is an item to check:
if ( head_rect.colliderect( food_rect ) ):
# TODO - consume food
Or if there's a list of food_rect in food_list:
def hitFood( head_rect, food_list ):
""" Given a head rectangle, and a list of food rectangles, return
the first item in the list that overlaps the list items.
Return None for a no-hit """
food_hit = None
collide_index = head_rect.collidelist( food_list )
if ( collide_index != -1 ):
# snake hit something
food_hit = food_list.pop( collide_index )
return food_hit
It's much easier to use PyGame's libraries rectangle overlap functions than making your own.
I'm new to python and relatively new to programming in general and I'm trying to write a side scrolling arcade game using the pygame and random modules. However I have hit a stumbling block in the way that the game is populated with enemies. What I am trying to achieve to make it so that for every enemy that leaves the left hand side of the window a new one is spawned somewhere beyond the right hand edge of the window.
However when an enemy leaves the left hand side of the screen and my respawn function is called I get an "typeerror" that the plane object it is trying to add to the enemies list is not callable - I cannot figure out why this is.
To begin with. I have defined a class for each type of enemy in my game. I have tried to get the planes to respawn the way i want them to first and then plan to do the same for the others. so I will only include relevant code to this class of enemies.
class plane(object):
def __init__(self, start_x, start_y, speed):
self.start_x = start_x
self.start_y = start_y
self.speed = speed
self.width = 200
self.height = 60
self.Hitbox = (self.start_x, self.start_y, self.width,
self.height)
def draw(self, win):
pygame.draw.rect(win, (0,0,0), (self.start_x, self.start_y,
self.width, self.height),0)
self.Hitbox = (self.start_x, self.start_y, self.width,
self.height)
pygame.draw.rect(win, (0,255,0), self.Hitbox, 1)
I can create an initial list of enemies by using the following create level function before entering the main loop of the game and i have defined another function called Respawn() It's this Respawn() function that won't work in the way i hoped:
turrets = []
towers = []
planes = []
def createLevel():
for r in range(left_turret_number):
turrets.append(turret(random.randint(1,2651), "Diag_left"))
for r in range(right_turret_number):
turrets.append(turret(random.randint(1,2651), "up"))
for r in range(tower_number):
towers.append(tower(random.randint(150,2651),random.randint(1,450),
50))
for r in range(plane_number):
planes.append(plane(random.randint(500,2651), random.randint(1,
450), random.randint(10, 20)))
def Respawn():
random_plane_x = random.randint(500,2651)
random_plane_y = random.randint(1, 450)
random_plane_speed = random.randint(10, 20)
random_plane = plane(random_plane_x, random_plane_y,
random_plane_speed)
print(random_plane_x, random_plane_y, random_plane_speed)
planes.append(random_plane)
In my main loop the pertinent following things happen in this order:
1.) each plane is moved by their speed towards the left of the window
for plane in planes:
plane.start_x -= plane.speed
2.) each plane is checked to see whether it has completely left the left hand side of the window, and if it has, it is removed from the list and the respawn counter increases by one - I have done it this way in case two planes by chance leave the screen at the same time.
for plane in planes:
plane.start_x -= plane.speed
3.) for the number of respawn counters, the respawn function is called that many times. (this is after collisons have been detected and keyboard input checked for ect.). finally the respawn counter is reset and the game window redrawn.
if plane_respawn_counter > 0:
for r in range(plane_respawn_counter):
Respawn()
plane_respawn_counter = 0
redrawGameWindow()
When a plane leaves the left hand side of the screen and the respawn function is triggered the program simply crashes and I get the error message "TypeError: "plane" object is not callable".
Thank you for your attention - I hope someone can tell me why the object is not callable and hopefully also how I can fix it :) I hope the information I have provided is sufficient - please let me know if you need any more details or need to see any further code from my program.
You should rename your variable in your for loop from planes--planes is the name of your class. Here is where I'm talking about:
for plane in planes:
plane.start_x -= plane.speed
In your respawn function, you call plane,
random_plane = plane(random_plane_x, random_plane_y, random_plane_speed)
but planes has been redefined as an instance of your class plane, therefore your error object not callable. Try just changing your for-loop variable to something else or renaming your class to Plane (it's pretty standard for them to start with an uppercase letter).
I want to keep the enemies (red rectangles in my case) inside the rectangle.What happens is that since it's a random function , the enemies keep on moving.But slowly some of my enemies moves outside , I don't want this to happen.I want them to be inside the screen only.
I have tried subtracting the screen width and screen height but was unsuccessful in my attempt to do so.So basically what I am asking is how do I check if rectangle is inside the screen.
EDIT :
This is not a possible duplicate as my enemies are moving randomly.In that the player is controlled by the user whereas my program , the movements of the enemy are totally random.
Here is my random movement for my enemies:
def evilMove(evilGuy):
evilCoords = []
#Returns either -1, 0 or 1
randomMovex=random.randrange(-1,2)
randomMovey=random.randrange(-1,2)
evilMake={'x':evilGuy[0]['x']+randomMovex,'y':evilGuy[0]['y']+randomMovey}
del evilGuy[-1] #Delete the previous spawn of enemy so there is no trail
evilCoords.append(evilMake['x']) #Adds x coordinate
evilCoords.append(evilMake['y']) #Adds y coordinate
deadZones.append(evilCoords) #Adds the enemy coordinates in the list
evilGuy.insert(0,evilMake)
Thanks in advance.
Well, you should really look at other questions similar to this, but you could make an if statement where you test if it is outside the screen.
Lets just say your screen is 500 by 500, you would do,
if coordinatesx < 500:
do this and that
if coordinatesy < 500:
do this and that.
This checks if the coordinates of your bad guy are being checked by the screen by saying if the bad guys coordinates are not larger than the screen, then do what you need to do.
EDIT _________________________________________
def evilMove(evilGuy):
evilCoords = []
#Returns either -1, 0 or 1
if x < 500 and y < 500:
randomMovex=random.randrange(-1,2)
randomMovey=random.randrange(-1,2)
evilMake={'x':evilGuy[0]['x']+randomMovex,'y':evilGuy[0]['y']+randomMovey}
Im not sure what your x and y variable is called, just replace it with that. And replace the 500 with your sreen width.