Py-Game Connect4 - Minimax (Program Halting Recursively) - python-3.x

main.py
from Player import Player
import tkinter as tk
import pygame
import pygame_menu
import time
import colors
import Connect4 as cFour
import Minimax as mx
def text_format(option, textSize, textColor):
"""
Creates a text object to show in the main menu
"""
newFont = pygame.font.Font(pygame_menu.font.FONT_FRANCHISE, textSize)
newText = newFont.render(option, 0, textColor)
return newText
def load_screen():
"""
This initializes the window for pygame to use
"""
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption("Connect4")
return screen
def get_player_details(screen):
"""
Creates a tkinter object(button) that gets players names
"""
root = tk.Tk()
root.title("Player Names!")
tk.Label(root, text="Player One", fg="blue").grid(row=0)
tk.Label(root, text="Player Two", fg="red").grid(row=1)
p1 = tk.Entry(root, font=(None, 15))
p2 = tk.Entry(root, font=(None, 15))
p1.grid(row=0, column=1)
p2.grid(row=1, column=1)
tk.Button(root, text='Play!', command= lambda: play_game(p1.get(),p2.get(), root, screen)).grid(row=10, column=1, sticky=tk.W)
tk.mainloop()
def get_player_ai_details(screen):
"""
Creating the panel to allow the user to select a color and go against the AI
"""
options = ["Player 1", "Player 2"]
root = tk.Tk()
root.title("Player 1(Blue) or 2(Red)?")
colorChoice= tk.StringVar(root)
colorChoice.set(options[0])
tk.OptionMenu(root, colorChoice, *options).grid(row=3)
p1 = tk.Entry(root, font=(None, 15))
p1.grid(row=3, column=1)
tk.Button(root, text="Play Computer!", command=lambda: play_computer(colorChoice.get(), p1.get(), root, screen)).grid(row=10, column=1)
tk.mainloop()
def play_computer(colorChoice, playerName, root, screen):
"""
Connect4 play function (human v computer)
"""
root.destroy()
if colorChoice == "Player 1":
mx.Minimax(Player(playerName), Player("Ed"), screen).play_computer()
else:
mx.Minimax(Player("Ed"), Player(playerName), screen).play_computer()
def play_game(p1Name, p2Name, root, screen):
"""
Connect4 play function (human v human)
"""
root.destroy()
game = cFour.Connect4(Player(p1Name.strip()), Player(p2Name.strip()), screen).play()
if __name__ == "__main__":
pygame.init()
screen = load_screen()
features = [
("Player Vs Player", colors.yellow),
("Player Vs AI", colors.red),
("Quit", colors.gray)
]
iterator = 0
menu = True
while menu:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
#This if block makes it where the user doesnt have to click arrow key up/down if they have exhausted the possible options, it will loop you throughout options
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_DOWN:
iterator += 1
if iterator == len(features):
iterator = 0
if event.key == pygame.K_UP:
iterator -= 1
if iterator < 0:
iterator = len(features) - 1
if event.key == pygame.K_RETURN:
if selected == "Player Vs Player":
get_player_details(screen)
if selected == "Player Vs AI":
get_player_ai_details(screen)
if selected == "Quit":
pygame.quit()
quit()
selected = features[iterator][0]
screen.fill(colors.blue)
screen_rect = screen.get_rect()
for i in range(0, len(features)):
counter = -50 + (i * 90) # Equation that sets distance between each choice in main menu
if i == iterator:
text = text_format(features[i][0], 80, features[i][1])
else:
text = text_format(features[i][0], 80, colors.black)
player_rect = text.get_rect(center=screen_rect.center)
player_rect[1] = player_rect[1] + counter
screen.blit(text, player_rect)
pygame.display.update()
Connect4.py
import pygame
import colors
import tkinter as tk
import pygame_menu
# import pandas as pd
import random
class Connect4:
"""
Class used to represent connect4 game
"""
def __init__(self, player1, player2, screen):
# Use 1 version of the screen instead of trying to create a new one
self.screen = screen
# Circle Radius and Width
self.WIDTH = 0
self.CIRCLERADIUS = 25
# Game-Time Variables
self.player1 = player1
self.player2 = player2
self.moveNumber = 0
self.gameOver = False
self.COLUMNS = 7
self.ROWS = 6
self.EMPTY = 99
self.board = [[self.EMPTY for x in range(self.COLUMNS)] for y in range(self.ROWS)]
# The distance between where the window starts and the game board is placed
self.DISTANCE = 90
# Space between each circle
self.DISTANCEGAP = 70
# Setting rectangle default
self.LEFT = 50
self.TOP = 70
self.HEIGHT = 470
self.RECWIDTH = 500
#Creating new tkinterobject
self.root = tk.Tk()
self.scoreboard = {self.player1.name: 0, self.player2.name: 0, "ties": 0}
# Storing locations of available moves given a user clicks the window -- Tuple of locations
self.POSITIONS = [
(
self.DISTANCE + (self.DISTANCEGAP*column) - self.CIRCLERADIUS,
self.DISTANCE + (self.DISTANCEGAP*column) + self.CIRCLERADIUS
)
for column in range(0, self.COLUMNS)
]
def who_won(self, board, piece):
"""
Determines the state of the game and finds if there is a winner
"""
# Horizontal
for col in range(0, self.COLUMNS - 3):
for row in range(0, self.ROWS):
if board[row][col] == piece and board[row][col + 1] == piece and board[row][col + 2] == piece and board[row][col + 3] == piece:
return True
# Vertical
for col in range(0, self.COLUMNS):
for row in range(0, self.ROWS - 3):
if board[row][col] == piece and board[row + 1][col] == piece and board[row + 2][col] == piece and board[row + 3][col] == piece:
return True
# Up-Left/Down-Right
for col in range(3, self.COLUMNS):
for row in range(3, self.ROWS):
if board[row][col] == piece and board[row - 1][col - 1] == piece and board[row - 2][col - 2] == piece and board[row - 3][col - 3] == piece:
return True
# Up-Right/Down-Left
for col in range(0, self.COLUMNS - 3):
for row in range(3, self.ROWS):
if board[row][col] == piece and board[row - 1][col + 1] == piece and board[row - 2][col + 2] == piece and board[row - 3][col + 3] == piece:
return True
# A winning move is not found
return False
def is_legal_move(self, position, board):
"""
Validates if a move is available/legal
"""
if board[0][position] == self.EMPTY:
return True
return False
def display_board(self):
"""
Displaying the game board to the user
"""
# Function: rect(surface, color, rectangle object, optional width) -- First one forms the outline of the board
pygame.draw.rect(self.screen, colors.salmon, (self.LEFT, self.TOP, self.RECWIDTH, self.HEIGHT), 13)
# This forms inner-most rectangle that users play on
pygame.draw.rect(self.screen, colors.burlywood, (self.LEFT, self.TOP, self.RECWIDTH, self.HEIGHT))
for column in range(0, self.COLUMNS):
colEq = self.DISTANCE + (self.DISTANCEGAP * column)
for row in range(0, self.ROWS):
# 125 is used here to make a the board placed in the center of the board and helps finding a value for self.TOP easier
rowEq = 125 + (self.DISTANCEGAP * row)
if self.board[row][column] == self.EMPTY:
color = colors.white
elif self.board[row][column] == 0:
color = colors.realBlue
elif self.board[row][column] == 1:
color = colors.red
pygame.draw.circle(self.screen, color, (colEq, rowEq), self.CIRCLERADIUS, self.WIDTH)
pygame.display.flip()
def play(self):
"""
This is the game-loop
"""
while not self.gameOver:
self.display_board()
if self.moveNumber % 2 == 0:
userText, userRect = self.display_player_name(self.player1.name, colors.realBlue)
elif self.moveNumber % 2 == 1:
userText, userRect = self.display_player_name(self.player2.name, colors.red)
self.screen.blit(userText, userRect)
for event in pygame.event.get():
self.screen.fill(colors.aquamarine) # Set up background color
if event.type == pygame.QUIT:
self.gameOver = True
elif event.type == pygame.MOUSEBUTTONDOWN:
x, y = pygame.mouse.get_pos()
position = self.get_column_position(x)
if self.moveNumber % 2 == 0 and position != self.EMPTY:
if self.is_legal_move(position, self.board):
self.drop_piece_animation(position)
if self.who_won(self.board, 0):
self.gameOver = True
self.scoreboard[self.player1.name] = self.scoreboard.get(self.player1.name) + 1
userText, userRect = self.display_player_name(self.player1.name + " " + "Wins!!!", colors.dark_gray)
elif self.check_if_tie(self.board):
self.gameOver = True
self.scoreboard["ties"] = self.scoreboard.get("ties") + 1
userText, userRect = self.display_player_name("It is a TIE!!!", colors.dark_gray)
elif self.moveNumber % 2 == 1 and position != self.EMPTY:
if self.is_legal_move(position, self.board):
self.drop_piece_animation(position)
if self.who_won(self.board, 1):
self.gameOver = True
self.scoreboard[self.player2.name] = self.scoreboard.get(self.player2.name) + 1
userText, userRect = self.display_player_name(self.player2.name + " " + "Wins!!!", colors.dark_gray)
elif self.check_if_tie(self.board):
self.gameOver = True
self.scoreboard["ties"] = self.scoreboard.get("ties") + 1
userText, userRect = self.display_player_name("It is a TIE!!!", colors.dark_gray)
self.display_board()
self.screen.blit(userText, userRect)
pygame.display.flip()
self.display_scoreboard(False)
def display_scoreboard(self, isAi):
"""
This enables the tkinter object so I can display the user options after : Victory/Loss/Tie
"""
self.root.geometry('460x150+300+0')
self.reset()
self.root.title("Choices")
# This creates the feedback information screen that the user sees after a game
tk.Label(self.root, text="Close window to go to main menu", font=(None, 15, 'underline'), anchor='w', justify='left').grid(row=0, column=1, sticky="NSEW")
tk.Label(self.root, text=self.player1.name + ": " + str(self.scoreboard.get(self.player1.name)), font=(None, 15), anchor='w', justify='left').grid(row=1, column=1, sticky = "NSEW")
tk.Label(self.root, text=self.player2.name + ": " + str(self.scoreboard.get(self.player2.name)), font=(None, 15), anchor='w', justify='left').grid(row=2, column=1, sticky="NSEW")
tk.Label(self.root, text="Ties: " + str(self.scoreboard.get("ties")), font=(None, 15), anchor='w', justify='left').grid(row=3, column=1, sticky="NSEW")
# if isAi == True:
# # tk.Button(self.root, text='Rematch!', command=self.playAi, font=(None, 12), fg="blue").grid(row=4, column=1, sticky=tk.W)
# else:
tk.Button(self.root, text='Rematch!', command=self.play, font=(None, 12), fg="blue").grid(row=4, column=1, sticky=tk.W)
# tk.Button(self.root, text='Rematch with Swap!', command= lambda: self.swapPlayers(isAi), font=(None, 12), fg="red").grid(row=4, column=2, sticky=tk.W)
tk.Entry(self.root)
self.root.mainloop()
def check_if_tie(self, board):
"""
A possible game state : Checking for a tie
"""
totalPieces = 0
for col in range(0, self.COLUMNS):
for row in range(0, self.ROWS):
if board[row][col] == 0 or board[row][col] == 1:
totalPieces += 1
if totalPieces == 42:
return True
else:
return False
def display_player_name(self, name, color):
"""
A feature to help users know who's turn it is that gets displayed
"""
font = pygame.font.Font(pygame_menu.font.FONT_FRANCHISE, 60)
text = font.render(name, True, color)
textRect = text.get_rect()
textRect.center = (len(name) * 30, 20)
return text, textRect
def drop_piece_animation(self, position):
"""
Inserting a piece at a given position with the animation of a piece drop
"""
tmpRow = 5
while self.board[tmpRow][position] == 1 or self.board[tmpRow][position] == 0:
tmpRow -= 1
for i in range(0, tmpRow + 1):
self.board[i][position] = self.moveNumber % 2
self.display_board()
pygame.time.delay(200)
pygame.display.flip()
self.board[i][position] = self.EMPTY
self.board[tmpRow][position] = self.moveNumber % 2
self.moveNumber += 1
def get_column_position(self, position):
"""
Takes a X coordinate value dependent on a click and determines what column user clicked
"""
index = 0
for i in self.POSITIONS:
if position + self.CIRCLERADIUS/2 >= i[0] and position - self.CIRCLERADIUS/2 <= i[1]:
return index
index += 1
return self.EMPTY
def reset(self):
"""
Restoring the game in its original state
"""
self.moveNumber = 0
self.board = [[self.EMPTY for x in range(self.COLUMNS)] for y in range(self.ROWS)]
self.gameOver = False
def play_computer(self):
"""
This is the game-loop used for AI play
"""
# If/else block to distinguish the human/Ai because the ai cant mouse click events
if self.player1.name == "Ed": # Ed Watkins (Staten Island)
humanPlayer = 1
computerPlayer = 0
humanName = self.player2.name
computerName = self.player1.name
elif self.player2.name == "Ed":
humanPlayer = 0
computerPlayer = 1
humanName = self.player1.name
computerName = self.player2.name
while not self.gameOver:
self.display_board()
if self.moveNumber % 2 == 0:
userText, userRect = self.display_player_name(self.player1.name, colors.blue)
elif self.moveNumber % 2 == 1:
userText, userRect = self.display_player_name(self.player2.name, colors.red)
self.screen.blit(userText, userRect)
for event in pygame.event.get():
self.screen.fill(colors.aquamarine) # Set up background color
if event.type == pygame.QUIT:
self.gameOver = True
elif event.type == pygame.MOUSEBUTTONDOWN:
x, y = pygame.mouse.get_pos()
position = self.get_column_position(x)
if self.moveNumber % 2 == humanPlayer and position != self.EMPTY:
if self.is_legal_move(position, self.board):
self.drop_piece_animation(position)
if self.who_won(self.board, humanPlayer):
self.gameOver = True
self.scoreboard[humanName] = self.scoreboard.get(humanName) + 1
userText, userRect = self.display_player_name(humanName + " " + "Wins!!!", colors.dark_gray)
elif self.check_if_tie(self.board):
self.gameOver = True
self.scoreboard["ties"] = self.scoreboard.get("ties") + 1
userText, userRect = self.display_player_name("It is a TIE!!!", colors.dark_gray)
if self.moveNumber % 2 == computerPlayer and self.gameOver == False:
move = self.generate_move(self.board, 4, computerPlayer, humanPlayer, True, self.moveNumber)
self.drop_piece_animation(move)
if self.who_won(self.board, computerPlayer):
self.gameOver = True
self.scoreboard[computerName] = self.scoreboard.get(computerName) + 1
userText, userRect = self.display_player_name(computerName + " " + "Wins!!!", colors.dark_gray)
elif self.check_if_tie(self.board):
self.gameOver = True
self.scoreboard["ties"] = self.scoreboard.get("ties") + 1
userText, userRect = self.display_player_name("It is a TIE!!!", colors.dark_gray)
self.display_board()
self.screen.blit(userText, userRect)
pygame.display.flip()
Minimax.py
from Connect4 import Connect4
import random
from copy import copy, deepcopy
import pygame
class Minimax(Connect4):
def __init__(self, player1, player2, screen):
super().__init__(player1, player2, screen)
def is_game_over(self, board):
if self.who_won(board, 1) or self.who_won(board, 0):
return True
return False
def generate_move(self, board, depth, computerPlayer, humanPlayer, maximizingPlayer, moveNumber):
if depth == 0 or self.is_game_over(board) or self.check_if_tie(board):
if self.is_game_over(board):
if self.who_won(board, computerPlayer):
return 1000000
elif self.who_won(board, humanPlayer):
return -1000000
elif self.check_if_tie(board):
return 0
else:
return self.get_game_score(board, computerPlayer, humanPlayer)
if maximizingPlayer:
maxValue = -1000000
for move in range(0, self.COLUMNS):
tmpBoard = self.copyBoard(board)
if self.is_legal_move(move, tmpBoard):
self.drop_piece_computer(move, tmpBoard, moveNumber)
result = self.generate_move(tmpBoard, depth - 1, computerPlayer, humanPlayer, False, moveNumber + 1)
if result >= maxValue:
maxValue = result
bestMove = move
return bestMove
else:
minValue = 1000000
for move in range(0,self.COLUMNS):
tmpBoard = self.copyBoard(board)
if self.is_legal_move(move, tmpBoard):
self.drop_piece_computer(move, tmpBoard, moveNumber)
result = self.generate_move(tmpBoard, depth - 1, humanPlayer, humanPlayer, True, moveNumber + 1)
if result <= minValue:
minValue = result
thismove = move
return thismove
def copyBoard(self, board):
tmpList = [[self.EMPTY for x in range(self.COLUMNS)] for y in range(self.ROWS)]
for row in range(0, self.ROWS):
for col in range(0, self.COLUMNS):
tmpList[row][col] = board[row][col]
return tmpList
def drop_piece_computer(self, position, board, moveNumber):
"""
Inserting a piece at a given position with the animation of a piece drop
"""
tmpRow = 5
while board[tmpRow][position] == 1 or board[tmpRow][position] == 0:
tmpRow -= 1
board[tmpRow][position] = moveNumber % 2
# moveNumber += 1
def get_game_score(self, board, computerPlayer, humanPlayer):
totalScore = 0
totalScore += self.get_hori_score(board, computerPlayer, humanPlayer)
# totalScore += self.get_vert_score(board, computerPlayer, humanPlayer)
# totalScore += self.get_upright_score(board, computerPlayer, humanPlayer)
# totalScore += self.get_upleft_score(board, computerPlayer, humanPlayer)
return totalScore
def get_hori_score(self, board, computerPlayer, humanPlayer):
score = 0
# List to collect all the groupings of 4(Horizontally) out of the current game state
groupingFourList = []
for col in range(0, self.COLUMNS - 3):
for row in range(0, self.ROWS):
groupingFourList.append(board[row][col])
groupingFourList.append(board[row][col + 1])
groupingFourList.append(board[row][col + 2])
groupingFourList.append(board[row][col + 3])
computerPieces = self.count_player_pieces(groupingFourList, 1)
humanPieces = self.count_player_pieces(groupingFourList, 0)
emptyPieces = self.count_player_pieces(groupingFourList, self.EMPTY)
score += self.score_metric(computerPieces, humanPieces, emptyPieces)
groupingFourList = []
return score
def get_upright_score(self, board, computerPlayer, humanPlayer):
score = 0
# List to collect all the groupings of 4(Horizontally) out of the current game state
groupingFourList = []
for col in range(0, self.COLUMNS - 3):
for row in range(3, self.ROWS):
groupingFourList.append(board[row][col])
groupingFourList.append(board[row - 1][col + 1])
groupingFourList.append(board[row - 2][col + 2])
groupingFourList.append(board[row - 3][col + 3])
computerPieces = self.count_player_pieces(groupingFourList, 1)
humanPieces = self.count_player_pieces(groupingFourList, 0)
emptyPieces = self.count_player_pieces(groupingFourList, self.EMPTY)
score += self.score_metric(computerPieces, humanPieces, emptyPieces)
groupingFourList = []
return score
def get_upleft_score(self, board, computerPlayer, humanPlayer):
score = 0
# List to collect all the groupings of 4(Horizontally) out of the current game state
groupingFourList = []
for col in range(3, self.COLUMNS):
for row in range(3, self.ROWS):
groupingFourList.append(board[row][col])
groupingFourList.append(board[row - 1][col - 1])
groupingFourList.append(board[row - 2][col - 2])
groupingFourList.append(board[row - 3][col - 3])
computerPieces = self.count_player_pieces(groupingFourList, 1)
humanPieces = self.count_player_pieces(groupingFourList, humanPlayer)
emptyPieces = self.count_player_pieces(groupingFourList, self.EMPTY)
score += self.score_metric(computerPieces, humanPieces, emptyPieces)
groupingFourList = []
return score
def get_vert_score(self, board, computerPlayer, humanPlayer):
score = 0
# List to collect all the groupings of 4(Horizontally) out of the current game state
groupingFourList = []
for col in range(0, self.COLUMNS):
for row in range(0, self.ROWS -3):
groupingFourList.append(board[row][col])
groupingFourList.append(board[row + 1][col])
groupingFourList.append(board[row + 2][col])
groupingFourList.append(board[row + 3][col])
computerPieces = self.count_player_pieces(groupingFourList, computerPlayer)
humanPieces = self.count_player_pieces(groupingFourList, humanPlayer)
emptyPieces = self.count_player_pieces(groupingFourList, self.EMPTY)
score += self.score_metric(computerPieces, humanPieces, emptyPieces)
groupingFourList = []
return score
def count_player_pieces(self, groupingFourList, playerPiece):
totalPieces = 0
for piece in groupingFourList:
if piece == playerPiece:
totalPieces += 1
return totalPieces
def score_metric(self, computerPieces, humanPieces, emptyPieces):
score = 0
# Making bot prioritize playing defense than offense
# Thats why the score is lower when regarding the enemy: AI chooses highest scoring move
if (computerPieces == 4):
score += 100
elif (computerPieces == 3 and emptyPieces == 1):
score += 20
elif (computerPieces == 2 and emptyPieces == 2):
score += 10
if (humanPieces == 3 and emptyPieces == 1):
score -= 100
return score
colors.py
"""
Valid colors to use got it from this link : https://python-forum.io/Thread-PyGame-PyGame-Colors
"""
realBlue = (0,0,255)
white = (255,255,255)
green = (0,255,0)
black = (0,0,0)
orange = (255,100,10)
blue_green = (0,255,170)
marroon = (115,0,0)
lime = (180,255,100)
pink = (255,100,180)
purple = (240,0,255)
magenta = (255,0,230)
brown = (100,40,0)
forest_green = (0,50,0)
navy_blue = (0,0,100)
rust = (210,150,75)
dandilion_yellow = (255,200,0)
highlighter = (255,255,100)
sky_blue = (0,255,255)
light_gray = (200,200,200)
dark_gray = (50,50,50)
tan = (230,220,170)
coffee_brown = (200,190,140)
moon_glow = (235, 245, 255)
burlywood = (255, 211, 155)
salmon = (139, 76, 57)
aquamarine = (127, 255, 212)
#Colors used for menu
blue = (135, 206, 250)
yellow = (255, 255, 0)
red = (255,0,0)
gray = (128, 128, 128)
Player.py
class Player():
def __init__(self, name):
self.name = name
The solution is solved but stackoverflow wont allow me to remove the question. The reason I would like the question removed because the answer provided isnt the solution so it would just throw other people off.

Ok the solution is as follows:
In this section of code this is going through a portion of the search space and evaluating the game states based on the best moves for each player. Through this algorithm the AI knows the best moves for each player and can make a "good" move.
if maximizingPlayer:
maxValue = -1000000
for move in range(0, self.COLUMNS):
tmpBoard = self.copyBoard(board)
if self.is_legal_move(move, tmpBoard):
self.drop_piece_computer(move, tmpBoard, moveNumber)
result = self.generate_move(tmpBoard, depth - 1, computerPlayer, humanPlayer, False, moveNumber + 1)
if result >= maxValue:
maxValue = result
bestMove = move
return bestMove
else:
minValue = 1000000
for move in range(0,self.COLUMNS):
tmpBoard = self.copyBoard(board)
if self.is_legal_move(move, tmpBoard):
self.drop_piece_computer(move, tmpBoard, moveNumber)
result = self.generate_move(tmpBoard, depth - 1, humanPlayer, humanPlayer, True, moveNumber + 1)
if result <= minValue:
minValue = result
thismove = move
return thismove
However, if you look closely when I recursively call the function back to the AI in the !maximizing player function I have:
result = self.generate_move(tmpBoard, depth - 1, humanPlayer, humanPlayer, True, moveNumber + 1)
In words when a simulated human player was playing on a generic board to generate the ai move it was assuming that there were 2 human players, and thats why the halt would happen because you cannot have a game with 2 of the same player.
so changing:
result = self.generate_move(tmpBoard, depth - 1, humanPlayer, humanPlayer, True, moveNumber + 1)
to this:
result = self.generate_move(tmpBoard, depth - 1, computerPlayer, humanPlayer, True, moveNumber + 1)

Putting False instead of True in the if maximizingPlayer block and True instead of False in the else clause for the second to last argument to the recursive calls to generate_move seems to fix the game.
i.e. Change False on line 33 of Minimax.py to True and change the True on line 44 to False.

Related

Im having trouble creating a zoom ability for my vtt

The main problem right now is that the player sprites disappear as soon as I zoom in or out the qrid scales correctly though. I had it working with the player sprites on screen then their movement stopped working. Got a little frustrated, then deleted the zoom altogether. Restarted, and now I'm here so I figured posting my first question because the internet didn't have the answers readily available.
import pygame as pg
class game():
def __init__(self):
self.WIDTH, self.HEIGHT = 1000, 700
self.win = pg.display.set_mode((self.WIDTH, self.HEIGHT), pg.RESIZABLE)
self.mapsurface = pg.surface.Surface((10000, 10000))
self.walls = pg.sprite.Group()
self.all_sprites = pg.sprite.Group()
self.player = Player("map assets/tokens/firegenasiwizard.png", self, 5, 5, self.mapsurface)
self.jplayer = Player("map assets/tokens/goliathbarbarian.png", self, 4, 4, self.mapsurface)
self.players = [self.player, self.jplayer]
def event_tracker(self):
global cellsize, spritesize
for event in pg.event.get():
if event.type is pg.QUIT:
pg.display.quit()
pg.quit()
sys.exit()
if event.type == pg.MOUSEBUTTONDOWN:
pos = pg.mouse.get_pos()
for play in self.players:
if play.rect.x <= pos[0] <= play.rect.x + spritesize and \
play.rect.y <= pos[1] <= play.rect.y + spritesize and play.selected is False:
play.selected = True
elif play.rect.x <= pos[0] <= play.rect.x + spritesize and \
play.rect.y <= pos[1] <= play.rect.y + spritesize and play.selected is True:
play.selected = False
if event.type == pg.KEYDOWN:
for play in self.players:
if event.key == pg.K_UP and play.selected is True:
play.move(dx=0, dy=-1)
if event.key == pg.K_DOWN and play.selected is True:
play.move(dx=0, dy=1)
if event.key == pg.K_LEFT and play.selected is True:
play.move(dx=-1, dy=0)
if event.key == pg.K_RIGHT and play.selected is True:
play.move(dx=1, dy=0)
if event.type == pg.MOUSEWHEEL:
if event.y > 0:
self.mapsurface = pg.transform.scale(self.mapsurface, (self.mapsurface.get_width() + 10,
self.mapsurface.get_height() + 10))
cellsize += 10
spritesize += 10
for play in self.players:
play.scale(10)
if event.y < 0:
self.mapsurface = pg.transform.scale(self.mapsurface, (self.mapsurface.get_width() - 10,
self.mapsurface.get_height() - 10))
cellsize -= 10
spritesize -= 10
for play in self.players:
play.scale(-10)
def draw_grid(self):
self.mapsurface.fill(white)
for x in range(0, self.mapsurface.get_width(), cellsize):
pg.draw.line(self.mapsurface, black, (x, 0), (x, 10000), 1)
for y in range(0, self.mapsurface.get_height(), cellsize):
pg.draw.line(self.mapsurface, black, (0, y), (10000, y), 1)
def draw_window(self):
self.draw_grid()
for play in self.players:
play.draw()
self.win.blit(self.mapsurface, (0, 0))
pg.display.update()

How can i fix angled line error in pyqt5?

I'm implementing paint program with pyqt5.
Currently, I am making a service that targets not only fhd but also 4k.
But it is difficult to adjust in 4K (high-resolution).
In fhd, the line is not angled, but in 4k, the line is angled.
Please help me how can i fix this problem. :(
If it is difficult to understand the code, if you request additionally, I will share it on github.
class LogoClass(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.setGeometry(0, 0, APP.desktop().screenGeometry().width(), APP.desktop().screenGeometry().height())
self.image = []
self.image.append(0)
self.image[0] = QtGui.QImage(self.size(), QtGui.QImage.Format_ARGB32)
self.image[0].fill(backColor)
self.imageDraw = []
self.imageDraw.append(0)
self.imageDraw[0] = QImage(self.size(), QImage.Format_ARGB32)
self.imageDraw[0].fill(QtCore.Qt.transparent)
print("size is " + str(self.size()) + " "+str(self.imageDraw[0].rect()))
self.imageDraw2 = QImage(self.size(), QImage.Format_ARGB32)
self.imageDraw2.fill(QtCore.Qt.transparent)
self.drawing = False
# self._clear_size = 50
self.brushColor = QtGui.QColor(QtCore.Qt.black)
self.lastPoint = QtCore.QPoint()
self.beginPoint = QtCore.QPoint()
self.last_x, self.last_y = None, None
self.timer = QTimer(self)
self.timer.setInterval(2000)
self.timer.timeout.connect(self.timeout)
self.rectanglestartx = []
self.rectanglestarty = []
self.rectanglesendx = []
self.rectanglesendy = []
self.lines = []
self.drawingPath= None
self.drawingPath_pen = None
self.stack_logo = QStackedWidget(self)
self.stack_logo.setGeometry(0, 0, APP.desktop().screenGeometry().width(), APP.desktop().screenGeometry().height())
self.setWindowFlag(QtCore.Qt.Tool)
self.startPoint = None
self.endPoint = None
self.stack_logo.addWidget(QWidget())
self.max = 1
self.point = QPoint()
def paintEvent(self, event):
canvasPainter = QPainter(self)
# canvasPainter.setRenderHints(QPainter.HighQualityAntialiasing | QPainter.SmoothPixmapTransform)
#print("current index is "+str(self.stack.currentIndex()))
canvasPainter.drawImage(self.rect(), self.image[self.stack_logo.currentIndex()], self.image[self.stack_logo.currentIndex()].rect())
canvasPainter.drawImage(self.rect(), self.imageDraw[self.stack_logo.currentIndex()], self.imageDraw[self.stack_logo.currentIndex()].rect())
canvasPainter.drawImage(self.rect(), self.imageDraw2, self.imageDraw2.rect())
if self.drawingPath:
pen = QtGui.QPen(QColor(colorR, colorG, colorB, alpha), penthick, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap,
QtCore.Qt.RoundJoin)
canvasPainter.setPen(pen)
canvasPainter.drawPath(self.drawingPath)
canvasPainter.end()
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
if cutwidget.isVisible() == 1:
cutwidget.hide()
if checkthick.isVisible() == 1:
checkthick.hide()
if checkbackgroundcolor.isVisible() == 1:
checkbackgroundcolor.hide()
if cutoption.isVisible() == 1:
cutoption.hide()
if objectoption.isVisible() == 1:
objectoption.hide()
if logooption.isVisible() == 1:
logooption.hide()
if setting.isVisible() == 1:
setting.hide()
if undertoolbar.btn_deleteAll_check == 1:
painter = QtGui.QPainter(self.imageDraw[self.stack_logo.currentIndex()])
# print("enter deletall button")
painter.save()
painter.setCompositionMode(QtGui.QPainter.CompositionMode_Clear)
painter.eraseRect(0, 0, APP.desktop().screenGeometry().width(), APP.desktop().screenGeometry().height())
painter.restore()
elif undertoolbar.btn_hold_check == 1:
self.beginPoint = event.pos()
elif undertoolbar.btn_lightpen_check == 1:
self.drawingPath = QPainterPath()
self.drawingPath.moveTo(event.pos())
elif undertoolbar.btn_pen1_check == 1:
self.drawingPath_pen = QPainterPath()
self.drawingPath_pen.moveTo(event.pos())
elif undertoolbar.btn_eraser_check == 1:
self.changeColour()
if undostack_logo.index()==0:
undocommand_logo = UndoCommand_logo(0,0,0,0,
self.imageDraw[self.stack_logo.currentIndex()], self)
undostack_logo.push(undocommand_logo)
self.drawing = True
self.lastPoint = event.pos()
self.beginPoint = event.pos()
"""if undertoolbar.btn_cut1_check == 1:
self.beginPoint = event.pos()
self.endPoint = event.pos()
self.update()"""
def mouseMoveEvent(self, event):
if event.buttons() and QtCore.Qt.LeftButton and self.drawing:
painter = QPainter(self.imageDraw[self.stack_logo.currentIndex()])
painter.setPen(
QtGui.QPen(QColor(colorR, colorG, colorB, alpha), penthick, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap,
QtCore.Qt.RoundJoin))
painter.setRenderHints(QPainter.HighQualityAntialiasing | QPainter.SmoothPixmapTransform)
if undertoolbar.btn_pen1_check == 1:
painter.drawLine(self.lastPoint, event.pos())
# self.drawingPath_pen.lineto(event.pos())
elif undertoolbar.btn_eraser_check == 1:
# print("mouse event eraser")
# self.changeColour()
r = QtCore.QRect(QtCore.QPoint(), erasersize * QtCore.QSize())
r.moveCenter(event.pos())
painter.save()
painter.setCompositionMode(QtGui.QPainter.CompositionMode_Clear)
painter.eraseRect(r)
painter.restore()
elif undertoolbar.btn_lightpen_check == 1 and self.drawingPath:
self.changeColour()
# print("mouse event lightpen")
painter.drawLine(self.lastPoint, event.pos())
# self.drawingPath.lineTo(event.pos())
elif undertoolbar.btn_hold_check == 1:
painter2 = QtGui.QPainter(self.imageDraw2)
painter2.setPen(
QtGui.QPen(QColor(255, 0, 0, 255), 3, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap,
QtCore.Qt.RoundJoin))
painter2.setRenderHints(QPainter.HighQualityAntialiasing | QPainter.SmoothPixmapTransform)
painter2.drawLine(self.lastPoint, event.pos())
self.timer.stop()
painter2.end()
painter.end()
self.lastPoint = event.pos()
self.update()
def mouseReleaseEvent(self, event):
if undertoolbar.btn_hold_check == 1:
self.timer.start()
self.rectanglestartx.append(self.beginPoint.x())
self.rectanglestarty.append(self.beginPoint.y())
self.rectanglesendx.append(self.lastPoint.x())
self.rectanglesendy.append(self.lastPoint.y())
elif undertoolbar.btn_pen1_check == 1:
undocommand_logo = UndoCommand_logo(self.lastPoint.x(), self.lastPoint.y(), event.pos().x(),
event.pos().y(),
self.imageDraw[self.stack_logo.currentIndex()], self)
undostack_logo.push(undocommand_logo)
# painter = QPainter(self.imageDraw[self.stack_logo.currentIndex()])
# painter.setRenderHints(QPainter.HighQualityAntialiasing | QPainter.SmoothPixmapTransform)
# painter.setPen(
# QtGui.QPen(QColor(colorR, colorG, colorB, 255), penthick, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap,
# QtCore.Qt.RoundJoin))
# painter.drawPath(self.drawingPath_pen)
# painter.end()
elif undertoolbar.btn_lightpen_check == 1:
undocommand_logo = UndoCommand_logo(self.lastPoint.x(), self.lastPoint.y(), event.pos().x(),
event.pos().y(),
self.imageDraw[self.stack_logo.currentIndex()], self)
undostack_logo.push(undocommand_logo)
painter = QPainter(self.imageDraw[self.stack_logo.currentIndex()])
painter.setRenderHints(QPainter.HighQualityAntialiasing | QPainter.SmoothPixmapTransform)
painter.setPen(
QtGui.QPen(QColor(colorR, colorG, colorB, alpha), penthick, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap,
QtCore.Qt.RoundJoin))
painter.drawPath(self.drawingPath)
painter.end()
elif undertoolbar.btn_eraser_check == 1:
QApplication.setOverrideCursor(Qt.ArrowCursor)
self.drawing = False
self.drawingPath = None
self.drawingPath_pen = None
def changeColour(self):
if undertoolbar.btn_eraser_check == 1:
print("change colour eraser check")
#pixmap = QtGui.QPixmap(QtCore.QSize(1, 1) * erasersize)
#pixmap.fill(QtCore.Qt.transparent)
# cursor = QtGui.QCursor(pixmap)
pixmap = QtGui.QPixmap('./icon/eraser/8.png').scaled(erasersize, erasersize, Qt.KeepAspectRatio)
cursor = QtGui.QCursor(pixmap)
QtWidgets.QApplication.setOverrideCursor(cursor)
# print(cursor)
# painter = QtGui.QPainter(pixmap)
# painter.setPen(QtGui.QPen(QtCore.Qt.black, 2))
# painter.drawRect(pixmap.rect())
# painter.end()
# elif undertoolbar.btn_pen1_check == 1 or undertoolbar.btn_lightpen_check == 1 or undertoolbar.btn_hold_check == 1 or undertoolbar.btn_pointer_check==1 or undertoolbar.btn_deleteAll_check==1 or undertoolbar.btn_cut1_check ==1 or undertoolbar.btn_background_color_check ==1:
# print("change colour pen check")
# #QtWidgets.QApplication.restoreOverrideCursor()
# # self.setCursor(QtCore.Qt.ArrowCursor)
def timeout(self):
qp = QPainter(self.imageDraw2)
for i in range(0, len(self.rectanglesendy)):
qp.save()
qp.setCompositionMode(QtGui.QPainter.CompositionMode_Clear)
qp.eraseRect(0, 0, sizex, sizey)
qp.restore()
self.update()
qp.end()
def keyPressEvent(self, event):
global undostack_logo
if event.key() == (Qt.Key_Control and Qt.Key_Y):
undostack_logo.redo()
if event.key() == (Qt.Key_Control and Qt.Key_Z):
length = len(logoclass.lines)
if length !=0:
i = length - 1
while i != 0:
if logoclass.lines[i][0] == logoclass.lines[i - 1][2] and logoclass.lines[i][1] == \
logoclass.lines[i - 1][3]:
undostack_logo.undo()
i = i - 1
else:
print(i)
undostack_logo.undo()
i = i - 1
break

Deep Q learning-Training issue

A snake game was created using Pygame and I tried to solve it using an AI. Initially I didn't increase the body length to check if the snake head moves towards the food. Grid size is 5*5. DDQN network was used. Most of time the head moves towards the wall or gets struck in a continuous loop.The maximum score attained was 4 even if I train it for 5000 episodes.
State: It is an array of size 16. The first 8 values has the distance between the head and wall at 8 directions(left , left top, top, right top, right , right bottom, bottom, left bottom). Next 8 values has the distance between head and food at 8 directions. All the values are in the range 0 to 1. 1 means the object is near and 0 means it is very far.
Action : There are 3 actions 0,1,2. 0- Head moves in same direction. 1- Head turns left. 2- Head turns right.
Reward: Reward of +50 if it collects the food and reward of -200 if it touches the wall.
I am not able to understand why my neural network learns in the wrong way. Please do help me solve this issue. I have attached the code here.
Code:
import pygame
pygame.font.init()
import time
import random
import numpy as np
from math import hypot
from collections import deque
from keras.models import Sequential
from keras.layers import Dense
from keras.optimizers import Adam
import tensorflow as tf
import os
seeds = 1001
os.environ['PYTHONHASHSEED']=str(seeds)
np.random.seed(seeds)
random.seed(seeds)
tf.random.set_seed(seeds)
batch = 16
class Food:
def __init__(self,width,n):
self.width = width
self.n = n
self.size = width //n
self.blocks = self.blocks_total()
def blocks_total(self):# Total available positions
x = y = 0
s=[]
for i in range(self.n):
for j in range(self.n):
s.append([x,y])
y += self.size
x += self.size
y = 0
return s
def food_pos(self,s):# Random food position
food_blocks = self.blocks_total()
try:
for i in self.blocks:
for j in s:
if i[0] == j[0] and i[1] == j[1]:
food_blocks.remove([j[0],j[1]])
break
a = random.choice(food_blocks)
return a
except:
return 0.1,0.1
class Agent:
def __init__(self,width,n,state_size,action_size=3,gamma = 0.98):
self.width = width
self.n = n
self.size = width //n
self.state_size = state_size
self.action_size = action_size
self.gamma = gamma
self.epsilon = 1
self.epsilon_min = 0
self.epsilon_decay = 0.99
self.memory = deque(maxlen=5000)
self.model = self.build_model()
self.train_model = self.build_model()
def reshape(self,state):# Reshaping state for input in nueral network
return np.reshape(state,[1,state.shape[0]])
def build_model(self):# Nueral network
model = Sequential()
model.add(Dense(16,input_shape=(self.state_size,),activation='relu'))
model.add(Dense(12,activation='relu'))
model.add(Dense(12,activation='relu'))
model.add(Dense(3,activation='softmax'))
model.compile(loss='mse',optimizer=Adam(0.0001))
return model
def get_action(self,state):
if np.random.rand() <= self.epsilon:
return random.randint(0,2)
a = self.reshape(state)
p = self.model.predict(a)
return np.argmax(p[0])
def remember(self,state,action,reward,new_state,done): # Saving in memory
self.memory.append((state,action,reward,new_state,done))
def replay(self): # Training of nueral network
minibatch = random.sample(self.memory,batch)
for state,action,reward,new_state,done in minibatch:
target = reward
state = self.reshape(state)
new_state = self.reshape(new_state)
if not done:
target = reward +(self.gamma*(np.max(self.train_model.predict(new_state)[0])))
target_f = self.model.predict(state)
target_f[0][action] = target
self.model.fit(state,target_f,epochs =1, verbose = 0)
if self.epsilon > self.epsilon_min:
self.epsilon *= self.epsilon_decay
def save_model(self):
self.model.save_weights('nn1.h5')
def load_model(self):
self.train_model.load_weights('nn1.h5')
class Game:
def __init__(self,width=500,n=5):
self.width = width
self.n = n
self.size = width//n
self.display = pygame.display.set_mode((width,width))
pygame.display.set_caption('A')
self.food = Food(width,n)
self.agent = Agent(width, n, state_size=16)
self.game_over = False
self.dirc = 'r'# starting direction of snake
self.snake_list = []
self.snake_length = 1
self.wall_touch = False
def display_player(self,disp,s_list,dirc):# displaying the snake head and its eyes
l = len(s_list)
if l > self.snake_length:
del s_list[0]
l -=1
for idx,i in enumerate(s_list):
if idx == l-1:
pygame.draw.rect(disp,(255,255,255),(i[0],i[1],self.size-1,self.size-1))
else:
pygame.draw.rect(disp,(255,165,0),(i[0],i[1],self.size-1,self.size-1))
a = s_list.copy()
x,y = a.pop()
rad = 10
if dirc == 'u':
pygame.draw.circle(disp,(0,0,0),(x+50,y+30),rad)
elif dirc == 'd':
pygame.draw.circle(disp,(0,0,0),(x+50,y+70),rad)
elif dirc == 'r':
pygame.draw.circle(disp,(0,0,0),(x+70,y+49),rad)
elif dirc == 'l':
pygame.draw.circle(disp,(0,0,0),(x+30,y+49),rad)
def move(self,dirc,s_list):# Constantly moving the snake on that particular direction
x,y = s_list.pop()
if dirc == 'l':
x -= self.size
if x <0:
x = 0
self.wall_touch = True
elif dirc == 'r':
x += self.size
if x > self.width - self.size:
x = self.width - self.size
self.wall_touch = True
elif dirc == 'u':
y -= self.size
if y <0:
y = 0
self.wall_touch = True
elif dirc == 'd':
y += self.size
if y > self.width - self.size:
y = self.width - self.size
self.wall_touch = True
self.snake_list.append([x,y])
def check_food_collect(self,fx,fy,s_list):# Check if head position and food position are same
x,y =s_list.pop()
if x == fx and y == fy:
return True
return False
def display_msg(self,msg,font='freesansbold.ttf',size=15,color=(255,255,255),loc=(410,15)):
mymsg = pygame.font.Font(font,size).render(msg,True,color)
self.display.blit(mymsg,loc)
def get_direction(self,change):# Changing the direction of snake based on the current direction
if self.dirc == 'r':
if change == 'r':
self.dirc= 'd'
elif change == 'l':
self.dirc= 'u'
elif self.dirc == 'l':
if change == 'r':
self.dirc= 'u'
elif change == 'l':
self.dirc= 'd'
elif self.dirc == 'd' :
if change == 'r':
self.dirc= 'l'
elif change == 'l':
self.dirc= 'r'
elif self.dirc == 'u':
if change == 'r':
self.dirc= 'r'
elif change == 'l':
self.dirc= 'l'
def near_wall(self,n,s_list): # Distance of nearby wall
a,b = n
x,y = s_list.pop()
i =0
while True:
xx = x +(self.size * i * a)
yy = y +(self.size * i * b)
dis = abs(xx-x)/self.size,abs(yy-y)/self.size
i +=1
if xx <0 or yy <0 or xx > self.width - self.size or yy > self.width - self.size:
return 1/ hypot(dis[0],dis[1])
def get_wall_dis(self,s_list): # Wall distance at 8 direction
j = [[-1,0],[-1,-1],[0,-1],[1,-1],[1,0],[1,1],[0,1],[-1,1]]
s = []
for i in j:
s.append(self.near_wall(i, s_list.copy()))
s = np.asarray(s)
return s
def near_food(self,fx,fy,n,s_list):# Head looks at 8 direction for the food
a,b = n
x,y = s_list.pop()
i =0
while True:
xx = x +(self.size * i * a)
yy = y +(self.size * i * b)
dis = abs(x-fx)/self.size,abs(fy-y)/self.size
i +=1
if xx <0 or yy <0 or xx > self.width - self.size or yy > self.width - self.size:
return 0
else:
if xx == fx and yy == fy:
if dis[0] ==0 and dis[1] ==0:
return 0
return 1/hypot(dis[0],dis[1])
def get_state(self,fx,fy,s_list,w): # Array of size 16
j = [[-1,0],[-1,-1],[0,-1],[1,-1],[1,0],[1,1],[0,1],[-1,1]]
s = []
for i in j:
s.append(self.near_food(fx,fy,i, s_list.copy()))
s = np.asarray(s)
a = np.append(w,s)
return a
def reset(self): # Initialising the value when a new game is started
self.agent.load_model()
self.game_over = False
self.wall_touch = False
self.dirc = 'r'
self.snake_length = 1
self.snake_list = []
def startgame(self,e):
sx,sy = 0,0 # Starting position of snake
self.snake_list.append([sx,sy])
fx,fy = 200,200 # Initial position of food
step = 0
action = 0
score = 0
change = None
wall = self.get_wall_dis(self.snake_list.copy())
state = self.get_state(fx, fy, self.snake_list.copy(), wall)
reward = 0
t = 0
save = False
j = 0
while not self.game_over:
j +=1
if j >500: # If snake struck in continuous loop
print('Ended')
break
action = 0
action = self.agent.get_action(state)
if action ==1:
change = 'l'
step = 1
elif action ==2:
change = 'r'
step = 1
else:
action = 0
change = None
if step == 0:
step = 1
else:
t +=1
save = True
self.get_direction(change)
change = None
self.move(self.dirc,self.snake_list.copy())
wall = self.get_wall_dis(self.snake_list.copy())
new_state = self.get_state(fx, fy, self.snake_list.copy(), wall)
if self.wall_touch:
reward = -200
print('Walled')
self.agent.remember(state, action, reward, new_state, True)
break
food_collect = self.check_food_collect(fx, fy, self.snake_list.copy())
if food_collect:
reward += 50
self.agent.remember(state, action, reward, new_state, False)
save = False
score +=1
step = 0
t = 0
reward = 0
fx,fy = self.food.food_pos(self.snake_list.copy())
if fx == 0.1 and fy ==0.1:
print('COMPLETED')
pygame.quit()
if save:
save = False
self.agent.remember(state, action, reward, new_state, False)
self.display.fill((0,0,0))
self.display_msg('Score :'+str(score))
pygame.draw.rect(self.display,(0,255,0),(fx,fy,self.size-1,self.size-1))
self.display_player(self.display, self.snake_list,self.dirc)
pygame.display.update()
#time.sleep(1)
state = new_state
if e > 2500:
time.sleep(0.1)
print('E : {} , Epsilon :{:.2} , Score : {}'.format(e,np.float32(self.agent.epsilon),score))
if e%10 ==0:
self.agent.save_model()
if len(self.agent.memory) > batch:
self.agent.replay()
game = Game()
for e in range(10000):
game.startgame(e)
game.reset()
pygame.quit()
A few things you could try:
The way you have defined your state looks a little complicated. Won't top and bottom give the same information, one being negative of the other? Also if your snake head is at (1,1) and your fruit at (3,4), well then the fruit won't show up in the state at all. There will be very limited times when the snake agent can actually see the fruit. Maybe you can try defining the state another way?
In RL, things go south very frequently so it often makes sense to start with basic agents and basic games and move up the ladder. Try using the same agent for a simple openai gym environment like mountaincar, to check if the agent class works as intended.

Why doesn't the window update in curses?

I took this nice example of a simple curses application with a list. I wanted to make it scrollable, so I changed the part of the list that gets shown. However, I can scroll down and back up, but the contents shown doesn't change (only the highlighted line, not the lines shown).
What am I doing wrong?
MVCE
#!/usr/bin/env python
import curses
from curses import panel
class Menu(object):
def __init__(self, items, stdscreen):
self.window = stdscreen.subwin(0, 0)
self.window.keypad(1)
self.panel = panel.new_panel(self.window)
self.panel.hide()
panel.update_panels()
self.position = 0
self.items = items
def navigate(self, n):
self.position += n
if self.position < 0:
self.position = 0
elif self.position >= len(self.items):
self.position = len(self.items) - 1
def display(self):
self.panel.top()
self.panel.show()
self.window.clear()
while True:
self.window.refresh()
curses.doupdate()
start = 0
# The next 3 lines seem not to work as intended
while start + (curses.LINES - 1) < self.position:
start += curses.LINES
for index, item in enumerate(self.items[start:curses.LINES - 1], start=start):
if index == self.position:
mode = curses.A_REVERSE
else:
mode = curses.A_NORMAL
msg = "%d. %s" % (index, item[0])
self.window.addstr(1 + index, 1, msg, mode)
key = self.window.getch()
if key in [curses.KEY_ENTER, ord("\n"), curses.KEY_RIGHT]:
self.items[self.position][1]()
elif key == curses.KEY_UP:
self.navigate(-1)
elif key == curses.KEY_DOWN:
self.navigate(1)
elif key == curses.KEY_LEFT:
break
self.window.clear()
self.panel.hide()
panel.update_panels()
curses.doupdate()
class MyApp(object):
def __init__(self, stdscreen):
self.screen = stdscreen
curses.curs_set(0)
submenu_items = [("beep", curses.beep), ("flash", curses.flash)]
submenu = Menu(submenu_items, self.screen)
main_menu_items = [
("beep", curses.beep),
("flash", curses.flash),
("submenu", submenu.display),
]
for i in range(200):
main_menu_items.append((f"flash {i}", curses.flash))
main_menu = Menu(main_menu_items, self.screen)
main_menu.display()
if __name__ == "__main__":
curses.wrapper(MyApp)
Basically that's because you're not updating the upper limit on the slice used in this loop:
for index, item in enumerate(self.items[start:curses.LINES - 1], start=start):
Here's a better version
MVCE
#!/usr/bin/env python
import curses
from curses import panel
class Menu(object):
def __init__(self, items, stdscreen):
self.window = stdscreen.subwin(0, 0)
self.window.keypad(1)
self.panel = panel.new_panel(self.window)
self.panel.hide()
panel.update_panels()
self.position = 0
self.items = items
def navigate(self, n):
self.position += n
if self.position < 0:
self.position = 0
elif self.position >= len(self.items):
self.position = len(self.items) - 1
def display(self):
self.panel.top()
self.panel.show()
self.window.clear()
while True:
start = 0
self.window.clear()
while start + (curses.LINES - 1) < self.position:
start += curses.LINES
myrow = self.position - start
mycol = 0
for index, item in enumerate(self.items[start:start + curses.LINES - 1], start=start):
if index == self.position:
mode = curses.A_REVERSE
else:
mode = curses.A_NORMAL
msg = "%d. %s" % (index, item[0])
self.window.addstr(index - start, 0, msg, mode)
if index == self.position:
(myrow, mycol) = self.window.getyx()
self.window.move(myrow, mycol)
key = self.window.getch()
if key in [curses.KEY_ENTER, ord("\n"), curses.KEY_RIGHT]:
self.items[self.position][1]()
elif key == curses.KEY_UP:
self.navigate(-1)
elif key == curses.KEY_DOWN:
self.navigate(1)
elif key == curses.KEY_LEFT:
break
self.window.clear()
self.panel.hide()
panel.update_panels()
curses.doupdate()
class MyApp(object):
def __init__(self, stdscreen):
self.screen = stdscreen
curses.curs_set(1)
submenu_items = [("beep", curses.beep), ("flash", curses.flash)]
submenu = Menu(submenu_items, self.screen)
main_menu_items = [
("beep", curses.beep),
("flash", curses.flash),
("submenu", submenu.display),
]
for i in range(200):
main_menu_items.append((f"flash {i}", curses.flash))
main_menu = Menu(main_menu_items, self.screen)
main_menu.display()
if __name__ == "__main__":
curses.wrapper(MyApp)

Class player - Animation stop first frame

Quick question. I have my Player Class, working perfectly. Except for a small detail. This is the class:
from dict.entity_dict import player, player_class
from collections import OrderedDict
import pyglet, random
key = pyglet.window.key
class Player(pyglet.sprite.Sprite):
dir_stand = "south"
dir_run = "south"
sprite_stand = 3
sprite_run = 3
image = None
s = 0
def __init__(self, game):
self.game = game
self.keyboard = key.KeyStateHandler()
self.statistics_base = OrderedDict()
self.image_stand = pyglet.resource.image(player.get("player_stand", {'x': None}).get("resource"))
self.image_run = pyglet.resource.image(player.get("player_run", {'x': None}).get("resource"))
self.image_stand_width = player.get("player_stand", {'x': None}).get("width")
self.image_stand_height = player.get("player_stand", {'x': None}).get("height")
self.image_run_width = player.get("player_run", {'x': None}).get("width")
self.image_run_height = player.get("player_run", {'x': None}).get("height")
self.vx = self.game.wd / 2
self.vy = self.game.wh / 2
self.load_sprite()
def class_player(self, type):
self.statistics_base["hp"] = player_class.get(type, {'x': None}).get("hp")
self.statistics_base["atk"] = player_class.get(type, {'x': None}).get("atk")
self.statistics_base["dif"] = player_class.get(type, {'x': None}).get("dif")
self.statistics_base["atk_sp"] = player_class.get(type, {'x': None}).get("atk_sp")
self.statistics_base["dif_sp"] = player_class.get(type, {'x': None}).get("dif_sp")
self.statistics_base["vel"] = player_class.get(type, {'x': None}).get("vel")
for stat in self.statistics_base:
if self.statistics_base[stat] is None:
self.statistics_base[stat] = 10
def animation(self, image, da, width, height):
frame_list = [image.get_region(x=width * i, y=height * da, width=46, height=58) for i in range(22)]
image_animation = pyglet.image.Animation.from_image_sequence(frame_list, 0.10, True)
return image_animation
def direction_sprite(self):
if self.dir_stand == "north":
self.sprite_stand = 7
elif self.dir_stand == "east":
self.sprite_stand = 5
elif self.dir_stand == "south":
self.sprite_stand = 3
elif self.dir_stand == "west":
self.sprite_stand = 1
if self.dir_run == "north":
self.sprite_run = 7
elif self.dir_run == "north-east":
self.sprite_run = 6
elif self.dir_run == "east":
self.sprite_run = 5
elif self.dir_run == "south-east":
self.sprite_run = 4
elif self.dir_run == "south":
self.sprite_run = 3
elif self.dir_run == "south-west":
self.sprite_run = 2
elif self.dir_run == "west":
self.sprite_run = 1
elif self.dir_run == "north-west":
self.sprite_run = 0
def load_sprite(self):
if not self.keyboard[key.W] and not self.keyboard[key.S] and not self.keyboard[key.D] and not self.keyboard[key.A]:
self.keyboard.clear()
img = self.image_stand
img_width = self.image_stand_width
img_height = self.image_stand_height
da = self.sprite_stand
else:
img = self.image_run
img_width = self.image_run_width
img_height = self.image_run_height
da = self.sprite_run
self.direction_sprite()
self.image = self.animation(img, da, img_width, img_height)
self.image.width, self.image.height = img_width, img_height
self.image.anchor_x, self.image.anchor_y = img_width // 2, img_height // 2
self.sprite = pyglet.sprite.Sprite(self.image, batch=self.game.Batch, group=self.game.GroupEntitySprite)
self.sprite.x = self.vx
self.sprite.y = self.vy
def key_player(self):
if self.keyboard[key.W]:
self.vy += 1
self.dir_stand = "north"
self.dir_run = "north"
if self.keyboard[key.S]:
self.vy -= 1
self.dir_stand = "south"
self.dir_run = "south"
if self.keyboard[key.D]:
self.vx += 1
self.dir_stand = "east"
self.dir_run = "east"
if self.keyboard[key.A]:
self.vx -= 1
self.dir_stand = "west"
self.dir_run = "west"
if self.keyboard[key.W] and self.keyboard[key.D]:
random1 = random.randint(1, 2)
if random1 == 1:
self.dir_stand = "north"
else:
self.dir_stand = "east"
self.dir_run = "north-east"
if self.keyboard[key.S] and self.keyboard[key.D]:
random2 = random.randint(1, 2)
if random2 == 1:
self.dir_stand = "south"
else:
self.dir_stand = "east"
self.dir_run = "south-east"
if self.keyboard[key.W] and self.keyboard[key.A]:
random3 = random.randint(1, 2)
if random3 == 1:
self.dir_stand = "north"
else:
self.dir_stand = "west"
self.dir_run = "north-west"
if self.keyboard[key.S] and self.keyboard[key.A]:
random4 = random.randint(1, 2)
if random4 == 1:
self.dir_stand = "south"
else:
self.dir_stand = "west"
self.dir_run = "south-west"
def update(self):
self.key_player()
self.load_sprite()
Since to update the Player's sprite, I need to call the "load_sprite" function, which causes the animation to be constantly called. As a result, the same animation does not appear and remains still at the first frame. So how could I solve?
Edit: Modified the script, including the for loop. If you see, I modified the group, using Batch and OrderedGroup instead. In this way we can clearly see the problem I am having.
The problem I have is due to the fact that pyglet.sprite.Sprite is called in order to update which animation is executed. However, doing so at the same time, the animations are not shown exactly because of the constant call of pyglet.sprite.Sprite.
First, you can change the sprite's animation by setting its image attribute, instead of creating a new Sprite instance. Second, only change its animation when you actually need to. That is when the direction changes.
I've added a simple example (pseudo) code to roughly show what I mean.
def animation(image, da, width, height):
frame_list = [image.get_region(x=width * i, y=height * da, width=46, height=58) for i in range(22)]
image_animation = pyglet.image.Animation.from_image_sequence(frame_list, 0.10, True)
return image_animation
class Player:
def __init__(self, standing_animations, running_animations):
# ... code ...
self.standing_animations = standing_animations # List of animations
self.running_animations = running_animations # List of animations
self.current_animation = standing_animations[0] # Just to have a default animation
self.previous_running_direction = None
self.previous_standing_direction = None
def load_sprite(self):
self.sprite.x = self.vx
self.sprite.y = self.vy
if self.previous_running_direction == self.dir_run and self.previous_standing_direction == self.dir_stand:
return # Don't do anything more in here
if not self.keyboard[key.W] and not self.keyboard[key.S] and not self.keyboard[key.D] and not self.keyboard[key.A]:
self.keyboard.clear()
self.current_animation = self.standing_animations[self.sprite_stand]
else:
self.current_animation = self.running_animations[self.sprite_run]
self.sprite.image = self.current_animation
def update(self):
self.previous_running_direction = self.dir_run
self.previous_standing_direction = self.dir_stand
self.key_player()
self.load_sprite()

Resources