ronaldoussoren / py2app

py2app is a Python setuptools command which will allow you to make standalone Mac OS X application bundles and plugins from Python scripts.
Other
356 stars 35 forks source link

Service exited with abnormal code #297

Open LouiseCav opened 4 years ago

LouiseCav commented 4 years ago

I am using py2app 0.21 with python 3.7.7 on macOS Catalina version 10.15.4

When I use -A the app produced will run successfully , but when I exit I get an error and my system log shows the following

com.apple.xpc.launchd[1] (org.pythonmac.unspecified.Othello.6516[41752]): Service exited with abnormal code: 255

If I don't use -A the app produced doesn't run at all, I get an error when I launch it & my system log shows the following (the same as above but with different numbers)

com.apple.xpc.launchd[1] (org.pythonmac.unspecified.Othello.6528[41978]): Service exited with abnormal code: 255

My setup file -

"""
This is a setup.py script generated by py2applet

Usage:
    python setup.py py2app
"""

from setuptools import setup

APP = ['Othello.py']
DATA_FILES = ["othello.png"]
OPTIONS = {
    'argv_emulation': True,
    'iconfile': 'othello.icns',
    'packages': ['pygame','random']
}

setup(
    app=APP,
    data_files=DATA_FILES,
    options={'py2app': OPTIONS},
    setup_requires=['py2app'],
)

My script -

import pygame, random
from pygame.locals import *
pygame.init()

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
BRIGHT_RED = (200, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
PALE_GREY = (230, 230, 230)
DARK_GREY = ( 75, 75, 75)

icon = pygame.image.load("othello.png")
pygame.display.set_icon(icon)

clock = pygame.time.Clock()
fps = 60

width = 8
height = 8
margin = 80
box_size = 100
screen_size = ((2 * margin) + (width * box_size))
radius = 40

window = pygame.display.set_mode((screen_size, screen_size))
pygame.display.set_caption ('Othello')

def player_black():
    player_tile = 'X'
    computer_tile = 'O'
    game_loop(player_tile, computer_tile)

def player_white():
    player_tile = 'O'
    computer_tile = 'X'
    game_loop(player_tile, computer_tile)

def draw_text(message, size, align, x, y):
    basicFont = pygame.font.SysFont('comicsans', size)
    text = basicFont.render(message, True, BLACK,)
    textRect = text.get_rect()
    if align == 'centre':
        textRect.centerx = x
    elif align == 'left':
        textRect.left = x
    elif align == 'right':
        textRect.right = x

    textRect.centery = y
    window.blit(text, textRect)

def draw_board(board):

    window.fill(GREEN)

    for x in range(width):
        for y in range(height):
            pygame.draw.rect(window, BLACK, (margin +( x * box_size), margin + ( y * box_size), box_size, box_size), 2)

            if board[x][y] == 'X':
                draw_counter(BLACK, x, y)

            elif board[x][y] == 'O':
                draw_counter(WHITE, x, y)

def draw_counter(color, x, y):
    box_x = (x * box_size) + margin + int(0.5 * box_size)
    box_y = (y * box_size) + margin + int(0.5 * box_size)
    pygame.draw.circle(window, color, (box_x, box_y), radius)

# convert board coordinates to pixel coordinates
def left_top_coords_of_box(box_x, box_y):
    left = (box_x * box_size) + margin
    top = (box_y * box_size) + margin
    return (left, top)

# Convert from pixel coords (where mouse clicks) to box coords (which box mouse is over)
def get_box_at_pixel(x, y):
    for box_x in range(screen_size):
        for box_y in range(screen_size):                            # for each box
            left, top = left_top_coords_of_box(box_x, box_y)        # pixel coordinates of top left of box
            box_rect = pygame.Rect(left, top, box_size, box_size)     # full square of box
            if box_rect.collidepoint(x, y):             # if 'collision' between square of box & point (x, y) is True
                return (box_x, box_y)                               # return box coords     
    return (None, None)

def get_new_board():
    # Create a new, completely blank board
    board = []
    for i in range(width):
        board.append([' ',' ',' ',' ',' ',' ',' ',' '])
    return board

def is_valid_move(board, tile, xstart, ystart):
    # Calculate if move (xstart, ystart) is valid
    # If space already taken or not on board return false
    if is_on_board(xstart,ystart) == False or board[xstart][ystart] != ' ':
        return False

    if tile == 'X':
        other_tile ='O'
    else:
        other_tile = 'X'
    tiles_to_flip = []
    for xdir, ydir in [[0,1],[1,1],[1,0],[1,-1],[0,-1],[-1,-1],[-1,0],[-1,1]]:
        x, y = xstart, ystart       # xstart & ystart will remain the same so the computer
        x += xdir
        y += ydir

        while is_on_board(x,y) and board[x][y] == other_tile:      
             # Keep moving in this direction
            x += xdir
            y += ydir
            if is_on_board(x,y) and board[x][y] == tile:
            # There are pieces to flip, go in reverse direction until we reach the original space, noting all tiles
                while True:
                    x -= xdir
                    y -= ydir
                    if x == xstart and y == ystart:
                        break
                    tiles_to_flip.append([x,y])
    # If there are no tiles to flip its not a valid move
    if len(tiles_to_flip) == 0:
        return False
    return tiles_to_flip

def is_on_board(x,y):
    # Return True if co-ords are on the board
    return x >= 0 and x <= width-1 and y >= 0 and y <= height-1

def get_board_with_valid_moves(board, tile):
    # Return a new board with . marking the valid moves the player
    board_copy = get_board_copy(board)

    for x, y in get_valid_moves(board_copy, tile):
        board_copy[x][y] = '.'
    return board_copy

def get_valid_moves(board, tile):
    # Return a list of [x,y] lsts of valid moves for the given player on the given board
    valid_moves = []

    for x in range(width):
        for y in range(height):
            if is_valid_move(board, tile, x, y) != False:
                valid_moves.append([x,y])
    return valid_moves

def get_score_of_board(board):
    # Get score by counting tiles. returns a dictionary with keys 'X' & 'O'
    xscore = 0
    oscore = 0
    for x in range (width):
        for y in range(height):
            if board[x][y] == 'X':
                xscore += 1
            if board[x][y] =='O':
                oscore += 1
    return {'X': xscore, 'O': oscore}

def who_goes_first():
    # Randomly chooses who goes first
    if random.randint(0,1) == 0:
        return 'computer'
    else:
        return 'player'

def make_move(board, tile, xstart, ystart):
    # Place the tile on the board at xstart, ystart & flip any of the opponents pieces
    # Check if move is valid
    tiles_to_flip = is_valid_move(board, tile, xstart, ystart)

    if tiles_to_flip == False:
        return False

    board[xstart][ystart] = tile
    for x, y in tiles_to_flip:
        board[x][y] = tile
    return True

def get_board_copy(board):
    # make a duplicate of the board list & return it
    board_copy = get_new_board()

    for x in range(width):
        for y in range(height):
            board_copy[x][y] = board[x][y]

    return board_copy

def is_on_corner(x, y):

    return(x == 0 or x == width-1) and (y == 0 or y == height-1)

def get_player_move(board, player_tile):
    # Ask the player to enter their move 

    while True:

        mouse_x = 0
        mouse_y = 0
        mouse_clicked = False

        for event in pygame.event.get():
            if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
                pygame.quit()
                quit()
            elif event.type == MOUSEMOTION:
                mouse_x, mouse_y = event.pos
            elif event.type == MOUSEBUTTONUP:
                mouse_x, mouse_y = event.pos
                mouse_clicked = True

        box_x, box_y = get_box_at_pixel (mouse_x, mouse_y)

        if box_x != None and box_y != None:         # the mouse is currently over a box

            if is_valid_move(board, player_tile, box_x, box_y) == False:
                continue
            elif mouse_clicked == False:
                # Highlight box
                continue
            else:
                x = box_x
                y = box_y

                break

    return [x, y]

def get_computer_move(board, computer_tile):

    while True:

        # get all possible moves & randomly shuffle the order
        possible_moves = get_valid_moves(board, computer_tile)
        random.shuffle(possible_moves)

        # always go for a corner if available
        for x, y in possible_moves:
            if is_on_corner(x, y):
                return (x, y)
        # Find the highest scoring possible move.
        best_score = -1
        for x, y in possible_moves:
            board_copy = get_board_copy(board)
            make_move(board_copy, computer_tile, x, y)
            score = get_score_of_board(board_copy)[computer_tile]
            if score > best_score:
                best_move = [x, y]
                best_score = score

        return best_move

def print_score(board, player_tile, computer_tile):
    # Print the scores
    scores = get_score_of_board(board)
    player_score = 'Player : ' + str(scores[player_tile])
    computer_score = 'Computer : ' + str(scores[computer_tile])

    draw_text(player_score, 30, 'left', margin, (margin/2))
    draw_text(computer_score, 30, 'right',(screen_size - margin), (margin/2))

def button(msg, x, y, w, h, ic, ac, tc, action=None):

    mouse = pygame.mouse.get_pos()
    click = pygame.mouse.get_pressed()

    if x+w/2 > mouse[0] > x-w/2 and y+h/2 > mouse[1] > y-h/2:

        pygame.draw.rect(window, ac, ((x-w/2),(y-h/2), w, h))

        if click[0] == 1 and action != None:

            action()

    else:
        pygame.draw.rect(window, ic, ((x-w/2),(y-h/2), w, h))

    basicFont = pygame.font.SysFont('comicsans', 40)
    text = basicFont.render(msg, True, tc,)
    textRect = text.get_rect()
    textRect.center = (x, y)
    window.blit(text, textRect)
    pygame.display.update()

#Game Loop

def play_game(player_tile, computer_tile):

    global turn

    # clear the board & place the starting pieces
    board = get_new_board()
    board[3][3] = 'X'
    board[3][4] = 'O'
    board [4][3] = 'O'
    board[4][4] = 'X'

    draw_board(board)

    pygame.display.update()
    clock.tick(fps)

    while True:
    # Check for a stalemate
        player_valid_moves = get_valid_moves(board, player_tile)
        computer_valid_moves = get_valid_moves(board, computer_tile)
    # If no-one can move end the game
        if player_valid_moves == [] and computer_valid_moves == []:
            return board        

        elif turn == 'player':
            if player_valid_moves != []:
                draw_board(board)
                print_score(board, player_tile, computer_tile)
                draw_text("Click where you wish to place your counter", 50, "centre", (screen_size/2),(screen_size - (margin/2)))
                pygame.display.update()

                move = get_player_move(board, player_tile)

                make_move(board, player_tile, move[0], move[1])
                draw_board(board)
                print_score(board, player_tile, computer_tile)
                pygame.display.update()
                clock.tick(fps)

            turn = 'computer'

        elif turn == 'computer':
            if computer_valid_moves != []:

                while True:
                    draw_board(board)
                    print_score(board, player_tile, computer_tile)

                    for event in pygame.event.get():
                        if event.type == pygame.QUIT:
                            pygame.quit()
                            quit()

                    mouse = pygame.mouse.get_pos()
                    click = pygame.mouse.get_pressed()
                    w = 400
                    h = 60
                    ic = WHITE
                    ac = PALE_GREY

                    if int(screen_size/2)+w/2 > mouse[0] > int(screen_size/2)-w/2 and int(screen_size - margin/2)+h/2 > mouse[1] > int(screen_size - margin/2)-h/2:

                        pygame.draw.rect(window, ac, ((int(screen_size/2)-w/2),(int(screen_size - margin/2)-h/2), w, h))

                        if click[0] == 1:

                            break

                    else:
                        pygame.draw.rect(window, ic, ((int(screen_size/2)-w/2),(int(screen_size - margin/2)-h/2), w, h))

                    basicFont = pygame.font.SysFont('comicsans', 40)
                    text = basicFont.render("Click for Computer's move", True, BLACK,)
                    textRect = text.get_rect()
                    textRect.center = (int(screen_size/2)),(int(screen_size - margin/2))
                    window.blit(text, textRect)
                    pygame.display.update()
                    clock.tick(fps)

                move = get_computer_move(board, computer_tile)
                make_move(board, computer_tile, move[0], move[1])
                draw_board(board)
                print_score(board, player_tile, computer_tile)
                pygame.display.update()
                clock.tick(fps)

            turn = 'player'

def intro():
    global turn
    intro = True

    turn = who_goes_first()
    first = 'The '  + turn + ' will go first.'

    while intro:
        for event in pygame.event.get():

            if event.type == pygame.QUIT:
                pygame.quit()
                quit()

        window.fill(GREEN)

        draw_text('Othello', 115, 'centre', screen_size/2, screen_size/5)
        draw_text(first, 80,'centre', screen_size/2, screen_size*2/5)
        draw_text('Choose your colour to start', 80, 'centre', screen_size/2, screen_size*3/5)

        button("Black", screen_size/3, screen_size*4/5, 100, 50,BLACK, DARK_GREY, WHITE , player_black)
        button("White", screen_size*2/3, screen_size*4/5, 100, 50,WHITE, PALE_GREY, BLACK , player_white)

        pygame.display.update()
        clock.tick(fps)

def game_loop(player_tile, computer_tile):

    while True:

        final_board = play_game(player_tile, computer_tile)

        while True:

            # Display the final score
            draw_board(final_board)
            scores = get_score_of_board(final_board)

            for event in pygame.event.get():
                    if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
                        pygame.quit()
                        quit()

            if scores[player_tile] > scores[computer_tile]:
                player_win = 'You beat the computer by ' + str(scores[player_tile] - scores[computer_tile]) + ' points. Congratulations!'
                draw_text(player_win, 50, 'centre', (screen_size/2),(margin/2))

            elif scores[player_tile] < scores[computer_tile]:
                computer_win = 'You lost. The computer beat you by ' + str(scores[computer_tile] - scores[player_tile]) + ' points.'
                draw_text(computer_win, 50, 'centre', (screen_size/2),(margin/2))

            else:
                draw_text('The game was a tie.', 50, 'centre', (screen_size/2),(margin/2))

            button('Play again', (screen_size/3), (screen_size-(margin/2)), 150, 60, BLACK, DARK_GREY, WHITE , intro)
            button('Quit',(screen_size*2/3), (screen_size-(margin/2)), 150, 60, WHITE, PALE_GREY, BLACK, quit_game)

            pygame.display.update()
            clock.tick(fps)

def quit_game():
    pygame.quit()
    quit()

intro()
LouiseCav commented 4 years ago

I have update to MacOS 10.15.5 and still have the same problem. However I have tried it on a system running MacOS 10.14.6 (using the same versions of python, py2app and pygame) and the app produces works OK but gives the error when quit (as the app produced using -A on MacOS 10.15.4 did).

ronaldoussoren commented 3 years ago

Sorry about the slow response.

This "should" just work and I'm not sure why it doesn't.

Annoyingly (?) the program works for me, although I did have to fix the script (quit_gamecalls a non-existent global function quit()). I still get an py2app generated error pop-up when closing the application. The usual way to debug this is to start the application in a shell (dist/Othello.app/Contets/MacOS/Othello), but for some reason that doesn't work reliably for me (most of the time the GUI doesn't actually launch until I quit the script using CTRL+C, which defeats the purpose of launching the script like this).