I'm using Python 3.7.4. I'm using Pycharm 2020 Community as my IDE. I'm using Pygame 1.9.6 as my game interpreter.
I was wondering on how I could keep track of the players click on the chess board. I was thinking it should should in my main() running operation, but where in it?
So on a chessboard it's an 8x8 board, so 64 squares. Like say they click on the square once to select the square with a piece on it, then click on another square to move said piece to other square if the square is empty. An empty square on my chess board is: "01". Like do I need someway to get to the dimensions of the board say, like a row or a column?
I have the dimensions of the board.
WIDTH = HEIGHT = 512 # 400 is another option
DIMENSION = 8 # dimensions of a chess board are 8x8
SQ_SIZE = HEIGHT // DIMENSION
MAX_FPS = 15 # for animations later on
IMAGES = {}
def main():
p.init()
screen = p.display.set_mode((WIDTH, HEIGHT))
clock = p.time.Clock()
screen.fill(p.Color("white"))
gs = ChessEngine.GameState()
loadImages() # only do this once, before the while loop
running = True
while running:
for e in p.event.get():
if e.type == p.QUIT:
running = False
drawGameState(screen, gs)
clock.tick(MAX_FPS)
p.display.flip()
I've never done mouse clicks before so I was wondering on how I could do that?
The index of the clicked field can be computed by the // (floor division) operator. Get the MOUSEBUTTONDOWN event (see pygame.event) and divide the mouse position coordinates by SQ_SIZE:
def main():
# [...]
while running:
for e in p.event.get():
if e.type == p.QUIT:
running = False
if e.type == p.MOUSEBUTTONDOWN:
mousePos = e.pos
column, row = mousePos[0] // SQ_SIZE, mousePos[1] // SQ_SIZE
columnName, rowName = chr(ord('a') + column), str(row+1)
print("clicked at " + columnName + rowName)
You can get the mouse coordinates and divide it by SQ_SIZE, but use floor-division (mousex//SQ_Size), using the two division signs terminates the decimal point and you will be left with the index of the board. For example if you click at (230, 213), you could have...
if pygame.mouse.get_pressed()[0]: # Is the left mouse button down
mousex, mousey = pygame.mouse.get_pos() # (230, 213)
col = mousex // SQ_SIZE # 230 // 64 = 3
row = mousey // SQ_SIZE # 213 // 64 = 3
pieceSelected = chessBoard[row][col]
This would allow you to get the piece that the user clicked on.
Related
So I am trying to make a mini game in which you have to answer some questions and based on those you draw a qrcode. For this I have to be able to toggle between white and black as the user should have more than one shot at guessing the answer of a question.
Here is the code:
back = pygame.image.load("qrcode3outerM.jpg").convert()
def draw_scene(screen):
screen.fill(WHITE)
#draw the outer from a picture because I am too lazy to generate it
screen.blit(back,(0,0))
for row in range(size):
for col in range(size):
rect = pygame.Rect((MARGIN + row)*BOX_SIZE,(MARGIN+col)*BOX_SIZE, BOX_SIZE, BOX_SIZE)
pygame.draw.rect(screen, BLACK, rect, 1)
# if answ[row][col]:
# pygame.draw.rect(screen, BLACK, ((MARGIN + col) * BOX_SIZE, (MARGIN + row)*BOX_SIZE, BOX_SIZE, BOX_SIZE))
if __name__ == '__main__':
done = False
draw_scene(screen)
while not done:
state = 0
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
#get the location of the cell in terms of tiles where the mouse was clicked
elif event.type == pygame.MOUSEBUTTONDOWN:
pos = pygame.mouse.get_pos()
x = pos[0] // BOX_SIZE - 12 #magic numbers here to make it work for the time being
y = pos[1] // BOX_SIZE - 12
state = screen.get_at((x,y))
print(f'Current state of the pixel {state}')
print(f'coordinate of the mouse press: {x} {y} ')
print(answ[x][y])
print('black' if answ[x][y] else 'white')
user_answ[x][y] = state
pygame.draw.rect(screen, BLACK if state else WHITE, ((MARGIN + x) * BOX_SIZE, (MARGIN + y) * BOX_SIZE, BOX_SIZE, BOX_SIZE))
pygame.display.update()
pygame.quit()
Here I am drawing all of the tiles white first and then black at the location at which the mouse was pressed.
My idea was to get the current state of each cell using the get_at() method but for some reason even after I click a cell and it turns black on my screen the get_at() method still returns (255,255,255,255).
The question is: why does it still return white when it should return black
Also, if you have a better method of toggling please let me know
You calculate x and y as follows:
x = pos[0] // BOX_SIZE - 12 #magic numbers here to make it work for the time being
y = pos[1] // BOX_SIZE - 12
x and y are not the position of the tile on the screen, but the column and row of the tile. The top left position of the tile is ((MARGIN + x) * BOX_SIZE, (MARGIN + y) * BOX_SIZE). You don't need to determine the color of the pixel at all, because the state of the tile is stored in user_answ[x][y]. All you need to do is get the state from user_answ, then invert the state and write the new state back:
state = user_answ[x][y]
state = not state
user_answ[x][y] = state
I have created a simple piano tiles game clone in pygame.
Everything working fine except the way i am generating tiles after every certain interval, but as the game speed increases this leaves a gap between two tiles.
In the original version of the game, there's no lag (0 distance ) between two incoming tiles.
Here's a preview of the game:
Currently I am generating tiles like this:
ADDBLOCK = pygame.USEREVENT + 1
ADDTIME = 650
pygame.time.set_timer(ADDBLOCK, ADDTIME)
if event.type == ADDBLOCK:
x_col = random.randint(0,3)
block = Block(win, (67.5 * x_col, -120))
block_group.add(block)
But with time, these tiles speed increase so there's remain a gap between generation of two tiles as shown by red line in the preview. Is there any way to generate tiles consecutively?
Source Code
Use a variable number to know how many tiles have been generated since the start. This variable will start with 0, then you will add 1 to this variable every time a tile is generated.
Then, you can use a variable like scrolling which increases continuously. You will add this scrolling to every tile y pos to render them.
Now you just have to add a tile which y position is like -tile_height - tile_height * number.
If that doesn't make sense to you, look at this MRE:
import pygame
from pygame.locals import *
from random import randint
pygame.init()
screen = pygame.display.set_mode((240, 480))
clock = pygame.time.Clock()
number_of_tiles = 0
tile_height = 150
tile_surf = pygame.Surface((60, tile_height))
tiles = [] # [column, y pos]
scrolling = 0
score = 0
speed = lambda: 200 + 5*score # increase with the score
time_passed = 0
while True:
click = None # used to click on a tile
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
exit()
if event.type == MOUSEBUTTONDOWN:
click = event.pos
screen.fill((150, 200, 255))
if scrolling > number_of_tiles * tile_height:
# new tile
# use "while" instead of "if" if you want to go at really high speed
tiles.append([randint(0, 3), -tile_height - number_of_tiles * tile_height])
number_of_tiles += 1
for x, y in tiles:
screen.blit(tile_surf, (60 * x, y + scrolling))
if y + scrolling > 480: # delete any tile that is no longer visible
tiles.remove([x, y])
if click is not None and Rect((60 * x, y + scrolling), tile_surf.get_size())
.collidepoint(click):
tiles.remove([x, y]) # delete any tile that has been clicked
score += 1 # used to calculate speed
scrolling += speed() * time_passed
pygame.display.flip()
time_passed = clock.tick() / 1000
For my levels screen I use images for each level button, there are 15 on a page. I was able to blit them all to the screen in a nice layout using some iteration and math, I have been trying to figure out a way to do this with the click detection aswell. But I do not want to type "var = get_rect" 15 times a page.
All of the images are in a list for iteration,
self.levels_p1 = [self.l1, self.l2, self.l3, self.l4, self.l5,
self.l6, self.l7, self.l8, self.l9, self.l10,
self.l11, self.l12, self.l13, self.l14, self.l15,]
and are added to the screen in a 5 x 3 like this,
for i, lvl in enumerate(self.levels_p1, start=1):
x = ((WIDTH - 100) * (i % 5) // 6) + 17
y = HEIGHT // 10 + ((i // 5) * 125)
if i % 5 == 0:
x = ((WIDTH - 100) * 5 // 6) + 17
y = HEIGHT // 10 + (((i // 5) - 1) * 125)
self.screen.blit(lvl, (x, y))
if self.level_unlocked < i:
self.screen.blit(self.level_shade, (x, y))
And I have been detecting clicks like this,
mousepos = pg.mouse.get_pos()
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
if event.type == pg.MOUSEBUTTONDOWN:
#to menu
if self.back_rect.collidepoint(*mousepos):
on_levels = False
self.swipe.play()
self.start()
#to levels2 page
if self.arrow_rect.collidepoint(*mousepos):
on_levels = False
self.swipe.play()
self.levels_page2()
#to level 1
if self.l1_rect.collidepoint(*mousepos):
on_levels = False
self.swipe.play()
self.load_level(1)
But that will only work for all the levels if I manually define a rect for all of the buttons, which is what I am trying to avoid.
I was wondering if there is a good way to detect the clicks on each and every one of these buttons? Similarly to how I displayed them in the 2nd code bit.
Much appreciated.
pygame.Surface.blit returns a rectangle with the area of the affected pixels:
self.rect = self.screen.blit(self.level_shade, (x, y))
Alternatively you can get the bounding rectangle of a pygame.Surface by get_rect(). The location of this rectangle is (0, 0). Hence, you have to set the position by a keyword argument:
self.rect = self.level_shade.get_rect(topleft = (x, y))
if self.arrow_rect.collidepoint(*mousepos)
I don't know what you're packing on the line above. If you want to detect clicks, I think you should get mouse position first. I'll do something like this:
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
if event.type == pg.MOUSEBUTTONDOWN:
#to menu
mousepos = pg.mouse.get_pos() #Here lies the magic and no need to unpack
if self.back_rect.collidepoint(mousepos):
on_levels = False
self.swipe.play()
self.start()
#to levels2 page
if self.arrow_rect.collidepoint(mousepos):
on_levels = False
self.swipe.play()
self.levels_page2()
#to level 1
if self.l1_rect.collidepoint(mousepos):
on_levels = False
self.swipe.play()
self.load_level(1)
If that solves your problem please let me know and of it doesn't still let me know!
Edit: Try if/elif block instead of just if.
mousepos = pg.mouse.get_pos()
lvls = []
for i, lvl in enumerate(self.levels_p1):
#x, y topleft for 5x3 grid with easement and centered
x = ((WIDTH - 100) * (i % 5) // 5) + 110
y = HEIGHT // 10 + ((i // 5) * 125)
#add to screen
temp = self.screen.blit(lvl, (x, y))
#if not unlocked
if self.level_unlocked < i:
#darken
self.screen.blit(self.level_shade, (x, y))
#if unlocked
else:
#enlarged version if hovering over and unlocked
if temp.collidepoint(*mousepos):
self.screen.blit(self.levels_1l[i], (x-6, y-6))
#rect list for click detection
lvls.append(temp)
#back button interactive
if self.back_rect.collidepoint(*mousepos):
self.screen.blit(self.t_back2, self.back_rect2) # bigger
else:
self.screen.blit(self.t_back, self.back_rect) # normal
#arrow button interactive
if self.arrow_rect.collidepoint(*mousepos):
self.screen.blit(self.arrowr2, self.arrow_rect2) # bigger
else:
self.screen.blit(self.arrowr, self.arrow_rect) # normal
pg.display.flip()
#all button presses
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
if event.type == pg.MOUSEBUTTONDOWN:
#to menu
if self.back_rect.collidepoint(*mousepos):
on_levels = False
self.swipe.play()
self.start()
#to levels2 page
if self.arrow_rect.collidepoint(*mousepos):
on_levels = False
self.swipe.play()
self.levels_page2()
#to level
for i, val in enumerate(lvls):
#if clicked pos = level pos
if lvls[i].collidepoint(*mousepos):
on_levels = False
self.swipe.play()
#level to load
self.load_level(i+1)
I'm currently working on a school project where I'm making a "hexcells" similar game in pygame and now I'm trying to blit an a new image if the user has clicked a current image. It will blit an image in the top left area, if clicked in the top left area, but not if I click any of the existing images. I told the program to print the coordinates from the images with help of the .get_rect() function, but it remains the same whereever I click and the coordinates aren't even where a image is. Can someone help me understand how this works and help me blit the new images on top of the existing images? Code below is not the entire document, however there is so much garbage/trash/unused code so I'd thought I spare you the time of looking at irrelevant code. Also sorry if the formatting is wrong or the information isn't enough, I tried my best.
import pygame, sys
from pygame.locals import *
#Magic numbers
fps = 30
winW = 640
winH = 480
boxSize = 40
gapSize = 75
boardW = 3
boardH = 3
xMargin = int((winW - (boardW * (boxSize + gapSize))) / 2)
yMargin = int((winW - (boardW * (boxSize + gapSize))) / 2)
#Lil bit o' color R G B
NAVYBLUE = ( 60, 60, 100)
correctCords = [[175,275,375],[375,275,175]]
bgColor = NAVYBLUE
unC = pygame.image.load("unC.png")
cor = pygame.image.load("correct.png")
inc = pygame.image.load("wrong.png")
correct = "Correct"
inCorrect = "Incorrect"
def main():
global FPSCLOCK, DISPLAYSURF
pygame.init()
FPSCLOCK = pygame.time.Clock()
DISPLAYSURF = pygame.display.set_mode((winW, winH))
mousex = 0 #stores x-coordinate of mouse event
mousey = 0 #stores y-coordinate of mouse event
pygame.display.set_caption("Branches")
DISPLAYSURF.fill(bgColor)
gridGame(inCorrect, correct,gapSize,xMargin,yMargin,boxSize)
while True:
mouseClicked = False
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
elif event.type == MOUSEMOTION:
mousex,mousey = event.pos
elif event.type == MOUSEBUTTONUP:
mousex, mousey = event.pos
pos = pygame.mouse.get_pos()
mouseClicked = True
unCa = unC.get_rect()
corA = cor.get_rect()
print unCa
print corA
print pos
if unCa.collidepoint(pos):
DISPLAYSURF.blit(cor,(mousey,mousex))
"""lada = unC.get_rect()
lada =
if mousex and mousey == lada:
for x in correctCords:
for y in x:
for z in x:
if mousey and mousex == z and y:
DISPLAYSURF.blit(cor,(mousey,mousex))
print lada"""
pygame.display.update()
FPSCLOCK.tick(fps)
def gridGame(inCorrect, correct,gapSize,xMargin,yMargin,boxSize):
grid = []
cordX = []
cordY = []
correctRecs = []
#cordinates = []
#cordinates.append([])
#cordinates.append([])
#cordinates.append([])
#this is basically getBoard() all over again
#This part will arrange the actual backend grid
for row in range(3):
grid.append([])
#cordinates[0].append(gapSize+(row+1)*100)
#cordinates[1].append(gapSize+(row+1)*100)
#cordinates[2].append(gapSize+(row+1)*100)
for column in range(3):
grid[row].append(inCorrect)
for row in range(3):
cordX.append([])
for column in range(3):
cordX[row].append(gapSize+(row+1)*100)
for row in range(3):
cordY.append([])
for column in range(3):
cordY[row].append(gapSize+(column+1)*100)
#print cordX[0][0], cordY[0][0]
grid[0][2] = correct
grid[1][1] = correct
grid[2][0] = correct
#Y-AXEL SKRIVS FoRST ([Y][X])
#print cordinates[2][1]
DISPLAYSURF.blit(cor,(100,100))
#Let's draw it as well
for row in range(3):
for column in range(3):
DISPLAYSURF.blit(unC,(gapSize+(row+1)*100,gapSize+(column+1)*100))
main()
Also real sorry about the horrible variable naming and occasional swedish comments.
unCa = unC.get_rect() gives you only image size - so use it only once at start (before while True) - and later use the same unCa all the time to keep image position and change it.
btw: better use more readable names - like unC_rect
ie.
# move 10 pixel to the right
unC_rect.x += 10
# set new position
unC_rect.x = 10
unC_rect.right = 100
unC_rect.topleft = (10, 200)
unC_rect.center = (10, 200)
# center on screen
unC_rect.center = DISPLAYSURF.get_rect().center
etc.
And then use this rect to blit image
blit(unC, unC_rect)
and check collision with other rect
if unC_rect.colliderect(other_rect):
or with point - like mouse position
elif event.type == MOUSEMOTION:
if unC_rect.collidepoint(pygame.mouse.get_pos()):
hover = True
# shorter
elif event.type == MOUSEMOTION:
hover = unC_rect.collidepoint(pygame.mouse.get_pos()):
I'm new to stackoverflow, but was hoping for a little insight from more advanced programmers. I am switching majors to Computer Science next semester and am taking an intro class learning some beginner's Python programming. I have already finished the program below (the assignment was to make a program that draws ovals on the window surface by filling in some of the professor's code, not too bad at all) but I wanted to add a little something extra: As you can see, I have the color of the ovals set to be random, but it stays the same until the program is restarted entirely i.e. all of the ovals are that particular color for the length of the program. With the code written the way it is, I can't figure out a way to get the color to change for each oval. Keep in mind, this is all for kicks, but if anyone's feeling especially helpful or creative, I'm curious to see what you have to say. Let me know if I can expound on anything. Thanks!
import pygame, random, sys
WINDOWWIDTH = 700
WINDOWHEIGHT = 700
BACKGROUNDCOLOR = (150,160,100)
#A different color every run
OVAL_COLOR = (random.randint (0,255),random.randint (0,255),
random.randint (0,255))
pygame.init()
windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
pygame.display.set_caption("Mobile Ovals")
#The draw variable is used later to indicate the mouse is still pressed
ovals = []
completedOvals = []
finished = False
draw = False
startXY = (-1, -1)
while not finished:
for event in pygame.event.get():
if event.type == pygame.QUIT or (event.type == pygame.KEYUP and
event.key == pygame.K_ESCAPE):
finished = True
elif event.type == pygame.KEYDOWN:
pressed = pygame.key.get_pressed()
if pressed[pygame.K_F4] and (pressed[pygame.K_LALT] or
pressed[pygame.K_RALT]):
finished = True
elif event.type == pygame.MOUSEBUTTONDOWN:
startXY = event.pos
draw = True
elif event.type == pygame.MOUSEBUTTONUP:
draw = False
for oval in ovals:
completedOvals.append (oval)
if draw == True:
del ovals [:]
#The above function ensures only one oval is onscreen at any given time
endXY = event.pos
width = (abs(endXY[0]-startXY[0]))
height = (abs(endXY[1]-startXY[1]))
#The code below allows the user to drag any direction
if endXY[0] < startXY[0]:
left = endXY[0]
else:
left = startXY[0]
if endXY[1] < startXY[1]:
top = endXY[1]
else:
top = startXY[1]
ovals.append (pygame.Rect (left, top, width, height))
windowSurface.fill(BACKGROUNDCOLOR)
for oval in ovals:
pygame.draw.ellipse(windowSurface, OVAL_COLOR, oval)
for completedOval in completedOvals:
pygame.draw.ellipse(windowSurface, OVAL_COLOR, completedOval)
pygame.display.update()
pygame.quit()
Your problem is quite simple. You set OVAL_COLOR once. But every time you make reference to the variable OVAL_COLOR, you're not creating a new random color, you're re-using the RGB color that was randomly generated when you created the variable.
Now, the way your program is structured, you maintain a list of all complete ovals that you're re-drawing every time the draw variable is set to true. If you place the OVAL_COLOR variable inside the for loop, you will update the color with every mouse movement, changing the color of the oval being drawn, as well as the color of all the old ovals being re-drawn.
The solution to have a new random oval color is to set the variable OVAL_COLOR when the mouse button goes down. That way, the oval color won't change as you drag the mouse to adjust the oval. But, given the current structure of the program, you'll need to save the oval colors assigned to completed ovals, or you'll still have the oval color change each time.
When the mouse button is pressed down, we want a new random color for our circle. Generate a random value, which will be used every time the circle is re-drawn.
elif event.type == pygame.MOUSEBUTTONDOWN:
startXY = event.pos
OVAL_COLOR = (random.randint (0,255),random.randint (0,255),
random.randint (0,255))
draw = True
When the mouse button is released, save the coordinates for the oval, along with the color that it was drawn with.
elif event.type == pygame.MOUSEBUTTONUP:
draw = False
# print len(ovals) # (always ==1)
completedOvals.append ((ovals[-1], OVAL_COLOR))
When we iterate through these completed ovals, draw them with the same color each time.
for (completedOval, color) in completedOvals:
pygame.draw.ellipse(windowSurface, color, completedOval)
Create a simple Oval() class, that contains it's color, and size.
import pygame
from pygame.locals import *
class Oval(object):
"""handle, and draw basic ovals. stores Rect() and Color()"""
def __init__(self, startXY, endXY):
self.color = Color(random.randint(0,255), random.randint(0,255), random.randint(0,255))
self.rect = Rect(0,0,1,1)
self.coord_to_oval(startXY, endXY)
def draw(self):
pygame.draw.ellipse(windowSurface, self.color, self.rect)
def coord_to_oval(self, startXY, endXY):
width = (abs(endXY[0]-startXY[0]))
height = (abs(endXY[1]-startXY[1]))
#The code below allows the user to drag any direction
if endXY[0] < startXY[0]:
left = endXY[0]
else:
left = startXY[0]
if endXY[1] < startXY[1]:
top = endXY[1]
else:
top = startXY[1]
self.rect = Rect(left, top, width, height)
# main loop
while not finished:
for event in pygame.event.get():
# events, and creation:
# ... your other events here ...
elif event.type == MOUSEBUTTONDOWN:
startXY = event.pos
draw = True
elif event.type ==MOUSEBUTTONUP:
# on mouseup, create instance.
endXY = event.pos
oval_new = Oval(startXY, endXY)
completedOvals.append(oval_new)
# draw them:
for oval in ovals:
oval.draw()
for oval in completedOvals:
oval.draw()
I mostly left out your non-completed ovals. Was that to show the size before clicking?