Levelling up Python 3 game 2D OpenGL coding/development - [2] Selecting and setting up Gaming/OpenGL modules

[2] Selecting and setting up Gaming/OpenGL modules

What would you recommend as the "proper" modules, libraries or tools to use here?

I've seen references to Pygame (not deemed suitable), PyOpenGL and GLUT. I am sure that there are others (some likely far better than those) that I have not have seen or heard about, but which experienced programmers.

Regarding futures, IF things went well with this 2D Game, I "might" level-up again and try my hand at a "version" geared to 3D, but that might be something that I may choose to avoid. Only time will tell.

If I can be so bold, I would like to hear from people who have programmed with Python for the equivalent of at least 1 full year of "commercial" experience (i.e. or similarly about 3 months per year for 5 years), hoping to get the voice of "experience under pressure". :slight_smile: I only spelled it out like that to give a sense of the kind of sweat and challenges that drives solid "profound" learning, but I am not too categorical on that.

So, given that, I would appreciate hearing recommendations that identify modules, libraries or tools

  • specifically-geared to a plain 2D game structure, or
  • forcing a 2D-only landscape/view on an underlying 3D-based implementation (for that mentioned future possibility).

Just want to mention that once I have implemented a basic 2D capability in my currently-working Python "game-skeleton" (features coded and functional, but not optimized for portability/speed), I plan to post the basic code in this forum as a potential Community-participating/-contributed project, unless people think it would be best done thru Codeberg, GitLab or GitHub. ... or ... I could just drop in into my existing GitHub account (but I must say that a recent editorial has convinced me that I should probably migrate to Codeberg. I am still hedging on that migration.

I have pretty much ditched GitHub and migrated to GitLab. Thanks for bringing Codeberg to my attention, too. It's always good to know about the alternatives.

This forum isn't really set up for development collaboration; so I think you'd be much better off on one of those other services.

I haven't done any professional Python development, nor game development; but I do count a few (professional) game developers amongst the people I know. I frequent some independent game developer meetups, too.

You wouldn't hurt yourself with pygame; if you want to get a game done and get people involved - it's good to know tools like that which are well known.

On the other hand ... solo developers tend to stick to tools that they like for specific ideological reasons, and have their own agendas and interests. I probably can't provide any advice given my position well outside of that community.

Lastly, I probably wouldn't learn GL. It's obviously handy for older hardware - but otherwise it's going to become more obscure and deprecated in favour of Vulkan.

1 Like

Thank you very much for that feedback, Stephen.

Having looked at

How is Vulkan supposed to superced OpenGL in practice ?

I feel reassured enough by that posting that I think I will stick to what I know, which is the OpenGL, even if it will be different working thru Python modules.

1 Like

Does anyone with Python experience can advise if the approach (using a "facade") being presented here

is a good one to emulate/implement ?

I'd suggest pyopengl & 'pygame-ce'.


IMO that is the correct option.

Well, Stephen, you were right!

I couldn't seem to find anything easy enough to assimilate to leverage the OpenGL approach ... so I "settled" for Pygame and, as they say, the rest is history !

I have my first program (A) which is focused on the fleet creation, ship placement and strike identification logic. I shared code segments with issues from that Python script here previously.

I have also put together my second piece of Python code (B), this time with the Pygame graphics integrated. I think I have fleshed out enough of the code to say that I have have managed to define a really good start to the GUI, into which the "smarts" of (A) will be later merged in. The code for (B) is below, as previously promised, being shared as a Community project, for which I would like to put out a call for guidance regarding hosting site or setting up the Community project. I don't know that I want to manage the site so much as to be able to contribute to it and benefit from the resulting game, which I have assigned the name "Yet Another Battleship" ! How's that for an original name, not ! :slight_smile:

Any suggestions/recommendations for the following:

[1] adding a scrolling text "box" within the window, to the right/left of the plot grid?

[2] a simple approach to adding an 8-image animation "gif" for (a) explosion or for (b) splash?




Snapshot of current game window:




Python script "yet_another_battleship.py":

#!/usr/bin/python3

####################################################################################################
###
###                                 Yet Another Battleship
###
####################################################################################################
###
###
###     Created:    2025-01-30
###     Creator:    Eric Marceau, https://github.com/ericmarceau-rogers
###     LICENSE:    CC-BY-NC-ND 4.0
###                 Opting instead to license under CC-BY-NC-SA 4.0 will be considered
###                   when a Core project team has been identified and formalized.
###
###         Choice of license was made to ensure the growth of a single code base
###         in order
###             - to promote focus of development on the original code-base,
###             - promote developer ongoing commitment to a steadily improving application,
###             - limit emigration of code functionality to competing applications,
###             - limit dilution of development efforts, and
###             - grow and protect the game's marketshare in it's attempt to supplant
###                 traditional commercial games of similar focus (a.k.a. Battleship clones)
###
###     Personal version control (initial contribution)
###     To be finalized:    Community project hosting site
###     To be finalized:    Community members with control authority
###
###	    $Id: yet_another_battleship.py,v 1.1 2025/01/30 22:27:43 ericthered Exp $
###
###     NOTE Development Status:   Work-In-Progress (RELEASE 1)
###     
###
###     FOUNDATIONAL PROJECT ASPIRATIONS
###
###	    This is an attempt to create a Community-driven, and supported, game 
###     similar to the traditional Battleship board game by making some 
###     choice game-play enhancements to increase player desire to repeatedly
###     face the challenge of defeating the enemy using their abilities 
###     to judiciously choose attack methods to quickly identify, 
###     and eliminate, the enemy ships.
###
###     It is hoped that it will be perceived as decent enough to be at a level
###     of challenge superior to, and equally as entertaining as, the "Mines" game, 
###     such that it could become another of the default games in the games suite 
###     delivered with Linux distros.
###
###         [1] Low-load Sea-like background
###         [2] Multi-shot salvos (choice of salvo pattern at every turn)
###         [3] Low-load animation for Ship Strikes and Splash Shots
###         [4] Option to load custom JSON-format animations (a.k.a. API)
###         [5] Impact on salvo size reflecting loss of firepower associated with stricken/sunk ships
###         [6] User load of Country-specific fleet of ships (using defined JSON-format, a.k.a. API)
###         [7] User creation of game Task Force by selection of ships from available fleet assets
###         [8] User ability to externally save Task Force formation as a named JSON-format file (a.k.a API)
###         [9] Separate zone within window for srolling text of "Notifications/Messages/Orders"
###               TBD:  Still deciding if better to define the zone as part of the same window
###                     or another window dedicated to that, with possible additional functionality.
###
###     END-OF-GAME CONDITION
###         [E1]  Mode 1:  Pre-defined number of turns
###         [E2]  Mode 2:  Destruction of Enemy Fleet
###
###     [PLANNED RELEASE 1  Game Play]
###         [R1-a]  Single-Player mode vs Computer
###         [R1-b]  All ships placed with ONLY horizontal orientation.
###         [R1-c]  Random selection of previously defined layouts for Enemy Fleet asset placement
###
###     [PLANNED RELEASE 2  Game Play]
###         [R2-a]  Choice of Two-Player mode (Peer-to-Peer, NO COORDINATING SERVER)
###         [R2-b]  All ships placed with choice of horizontal or vertical orientation.
###
###     
###     FUTURES:  (features still under consideration, due to complexity of implementation)
###         [F1]  Computer-generated assignment of Enemy Fleet asset placement
###         [F2]  Add element of realism by adding Player-plotted navigational course 
###                 for ship/fleet patrol (game-piece movement about the game-board)
###         [F3]  Mechanism for "aging" of Splash Shots so that those grid postions
###                 previously deemed "clear of enemy" are no longer deemed as such
###                 and are subject to being revisited for potential identification
###                 as "Enemy Presence".
###     
###
####################################################################################################
#23456789+123456789+123456789+123456789+123456789+123456789+123456789+123456789+123456789+123456789+


debug = 1

import pygame
import random


####################################################################################################
###     Code Segment -- Tile Pattern Colour Styles
####################################################################################################
#
# Initial build using plain single-colour tiling approach
#
# Custom Colours
global greyMid
global blueSea
global orange
global lgtYellow
global blueSeaSplash
greyMid       = (  39,  39,  39)           # should not attempt to make darker
blueSea       = (   0,  15,  47)           # should not attempt to make darker
orange        = ( 255,  79,   0)           # colour is balanced for brightness and visibility
lgtYellow     = ( 199, 199,   0)
blueSeaSplash = (  95,  47,  47)           # colour is balanced for contrast with low brightness
#
# OS Standard Colours
global white
global yellow
global red
global black
global green
white         = ( 255, 255, 255)
yellow        = ( 255, 255,   0)
red           = ( 255,   0,   0)
black         = (   0,   0,   0)
green         = (   0, 255,   0)



####################################################################################################
###     Code Segment -- Display Management
####################################################################################################

global mapLabelLet
mapLabelLet = [
    'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , 'I' , 'J' ,
    'K' , 'L' , 'M' , 'N' , 'O' , 'P' , 'Q' , 'R' , 'S' , 'T' ,
    'U' , 'V' , 'W' , 'X' , 'Y' , 'Z' ,
    'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' ,
    'k' , 'l' , 'm' , 'n' , 'o' , 'p' , 'q' , 'r' , 's' , 't' ,
    'u' , 'v' , 'w' , 'x' , 'y' , 'z'
]
#print( mapLabelLet) 

global mapLabelNum
mapLabelNum = [
    '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , '10' ,
    '11' , '12' , '13' , '14' , '15' , '16' , '17' , '18' , '19' , '20' ,
    '21' , '22' , '23' , '24' , '25' , '26' , '27' , '28' , '29', '30'
]
#print( mapLabelNum) 

global displayCols
global displayRows
displayCols = int(54)
displayRows = int(32)

global boardCols
global boardRows
boardCols = int(displayCols-2)          # !!! don't understand why this isn't displayCols-2 to get desired coverage
boardRows = int(displayRows-2)          # !!! don't understand why this isn't displayCols-2 to get desired coverage

global maxIndxCol
global maxIndxRow
maxIndxCol = int(displayCols-1)
maxIndxRow = int(displayRows-1)

# Define width and height of grid cells
global gridWidth
global gridHeight
gridWidth = 20
gridHeight = 20

# Define width of margins between cells
global cellMargin
cellMargin = 1

# Create a 2 dimensional game board array
global displayPanel
displayPanel = []

#for row in range(boardRows):
for row in range(displayRows):
    displayPanel.append([])                     # Initialize row to contain grid cells
    #for col in range(boardCols):
    for col in range(displayCols):
        displayPanel[row].append(0)             # Initialize grid cells with default value
print("Created ... Display Grid: ", displayCols, " x ", displayRows )
print("Created ... Game Board: ", boardCols, " x ", boardRows )

# Initialize Game Engine (pygame)
pygame.init()
pygame.font.init()

# Set the width and height of the gridScreen window
global gridScreen
windowGeometry = [ 1 + displayCols * ( gridHeight + 1 ) ,  1 + displayRows * ( gridHeight + 1 ) ]
gridScreen = pygame.display.set_mode(windowGeometry)

# Give game window a branded title
pygame.display.set_caption("Yet Another Battleship")

# Initialize parameter for controlling screen refresh rate
global displayRefresh
displayRefresh = pygame.time.Clock()
if debug == 1 :
    print( "\t displayRefresh = ", displayRefresh )

# Set game looping conditions
global abandonGame
abandonGame = False

global gridClick
gridClick = False

gridLableFont = 'Calibri'
gridLableFontSize = 12


####################################################################################################
def overlayLabel( \
        labelFont, \
        labelFontSize, \
        labelText, \
        column, row ) :
####################################################################################################
    # Select the font to use:   name,      size,          bold,  italics
    font = pygame.font.SysFont( labelFont, labelFontSize, False, False)
 
    # Render the text. "True" means anti-aliased text.
    # Black is the color. This creates an image of the
    # letters, but does not put it on the screen
    text = font.render( labelText, True, lgtYellow)
 
    #xPixel = int( column * ( cellMargin + gridWidth ) )
    #yPixel = int( row    * ( cellMargin + gridHeight ) )
    xPixel = int( column * ( cellMargin + gridWidth )  + ( gridWidth *.25 ) )
    yPixel = int( row    * ( cellMargin + gridHeight ) + ( gridWidth *.25 ) )
    # Put the image of the text on the screen at 250x250
    gridScreen.blit( text, [xPixel, yPixel])
# def overlayLabel( ################################################################################



####################################################################################################
def isGridBorder( thisCol, thisRow ) :
####################################################################################################
    return ( thisCol == 0              and  thisRow >= 0  and  thisRow < displayRows ) \
       or  ( thisRow == 0              and  thisCol >= 0  and  thisCol < displayCols ) \
       or  ( thisCol == displayCols-1  and  thisRow >= 0  and  thisRow < displayRows ) \
       or  ( thisRow == displayRows-1  and  thisCol >= 0  and  thisCol < displayCols )
# def isGridBorder( ################################################################################



####################################################################################################
def isGridLabelRow( thisCol, thisRow ) :
####################################################################################################
    return  ( thisCol == 0              and  thisRow > 0  and  thisRow < displayRows-1 ) \
        or  ( thisCol == displayCols-1  and  thisRow > 0  and  thisRow < displayRows-1 )
# def isGridLabelRow( ##############################################################################



####################################################################################################
def isGridLabelCol( thisCol, thisRow ) :
####################################################################################################
    return  ( thisRow == 0              and  thisCol > 0  and  thisCol < displayCols-1 ) \
        or  ( thisRow == displayRows-1  and  thisCol > 0  and  thisCol < displayCols-1 )
# def isGridLabelCol( ##############################################################################



####################################################################################################
def isGridTile( thisCol, thisRow ) :
####################################################################################################
    return  row >0 and row < displayRows-1 and column >0 and column < displayCols-1
# def isGridTile( ##################################################################################



####################################################################################################
def isEnemyShip( thisCol, thisRow ) :
####################################################################################################
    ####################################################################################
    ###     SIMULATE play by assigning hit on random basis
    ####################################################################################
    if  random.randint(0, 9) == 7 :
        displayPanel[ thisRow ][ thisCol ] = 1
        return  True
    else :
        displayPanel[ thisRow ][ thisCol ] = 2
        return  False
# def isEnemyShip( ##################################################################################



####################################################################################################
def shipStrikeConfirmed( thisCol, thisRow ) :
####################################################################################################
    tilePattern = orange
    pygame.draw.rect(   gridScreen,
                        tilePattern,
                        [   cellMargin + thisCol * (cellMargin + gridWidth) ,
                            cellMargin + thisRow * (cellMargin + gridHeight) ,
                            gridWidth ,
                            gridHeight
                        ]
                    )
    print("\t STRIKE at ", thisCol, thisRow )
# def shipStrikeConfirmed( #########################################################################



####################################################################################################
def splash( thisCol, thisRow ) :
####################################################################################################
    tilePattern = blueSeaSplash
    pygame.draw.rect(   gridScreen,
                        tilePattern,
                        [   cellMargin + thisCol * (cellMargin + gridWidth) ,
                            cellMargin + thisRow * (cellMargin + gridHeight) ,
                            gridWidth ,
                            gridHeight
                        ]
                    )
    print("\t SPLASH at ", thisCol, thisRow )
# def splash( ######################################################################################



####################################################################################################
###     Code Segment -- Game Board Initialization
####################################################################################################
# Set the background for entire defined window
gridScreen.fill(greyMid)

####################################################################################################
# Overlay background with game board grid initial state
####################################################################################################
for row in range( displayRows ):
    for column in range( displayCols ):
        tilePattern = blueSea

        if  isGridBorder( column, row ) :
            tilePattern = greyMid
        else :
            tilePattern = blueSea

        pygame.draw.rect( gridScreen,
                          tilePattern,
                          [ cellMargin + column * (cellMargin + gridWidth) ,
                            cellMargin + row    * (cellMargin + gridHeight) ,
                            gridWidth ,
                            gridHeight
                          ]
                        )

####################################################################################################
# Overlay background with board grid border legends
####################################################################################################
for row in range( displayRows ):
    #for column in range( 1, boardCols ):
    for column in range( displayCols ):
        if    isGridLabelRow( column, row ) :
            if debug == 1 :
                print("\t Display label ROW ", mapLabelNum[row-1], column, row )
            overlayLabel( gridLableFont, gridLableFontSize, mapLabelNum[row-1], column, row )
        elif  isGridLabelCol( column, row ) :
            if debug == 1 :
                print("\t Display label COLUMN ", mapLabelLet[column-1], column, row )
            overlayLabel( gridLableFont, gridLableFontSize, mapLabelLet[column-1], column, row )

# Show full updated window contents
pygame.display.flip()



####################################################################################################
###     Code Segment -- Game Interraction and Actions
####################################################################################################
while not abandonGame :

    ################################################################################################
    ###     Get interrupt or user-click at grid position within defined window range
    ################################################################################################
    #bypass = 0

    for event in pygame.event.get() :
        gridClick = False

        if event.type == pygame.QUIT :
            ########################################################################################
            ###     Set flag to abandon game-play loop
            ########################################################################################
            abandonGame = True

        elif event.type == pygame.MOUSEBUTTONDOWN :
            ########################################################################################
            ###     Actions to perform only on detection of Mouse-Click
            ########################################################################################
            gridClick = True
            displayPanel[row][column] = 0
            pos = pygame.mouse.get_pos()

            # Convert window-pixel coordinates to game-grid coordinates
            if debug == 1 :
                print( "Input ", pos )
            clickCol = int( pos[0] // ( cellMargin + gridWidth ) )
            clickRow = int( pos[1] // ( cellMargin + gridHeight ) )

            ########################################################################################
            ###     Take action on in-range identified grid position
            ########################################################################################
            if    isGridBorder( clickCol, clickRow ) :
                #if debug == 1 :
                #    print( "\t Grid coordinates: ", clickCol, clickRow )
                displayPanel[ clickRow ][ clickCol ] = 0
            else :
                if debug == 1 :
                    print( "\t Grid coordinates: ", clickCol, clickRow )

                ####################################################################################
                ###     Apply action based on HIT or MISS
                ####################################################################################
                if    isEnemyShip( clickCol, clickRow ) :
                    shipStrikeConfirmed( clickCol, clickRow )
                else :
                    splash( clickCol, clickRow )


#        if gridClick == True :
#            if  debug == 1 :
#                print( "\t display click")


    # This game is not a Real-Time Role-Playing Game, 
    # so limiting update scan rate to 10 frames per second
    displayRefresh.tick(10)

    # Display full window with updates from click-actions
    pygame.display.flip()


####################################################################################################
###     Perform all necessary action at detection of QUIT signal
####################################################################################################
pygame.quit()


####################################################################################################
###                                 Yet Another Battleship
####################################################################################################
1 Like