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 ! 
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
####################################################################################################