BattleShip Part 2: Breaking out different components

The next thing I’d want to do is start thinking about breaking this project up into different components. Right now everything is all in one class.

import random
#from os import system, name

class BS:
    
    #GameValues
    height = 10
    width = 10
    gap = 1
    direction = ['up','down','left','right']
    gameOver = False
    
    #Ships
    ship = [5,4,3,3,2]
    shipChar = ['A','B','C','D','E'] #Need to be unique
    shipHealth = ship.copy()
    shipPieces = 0
    
    #GridCharacters
    emptyCell = "O"
    sunkShip = "X"
    gridHor = ['-','A','B','C','D','E','F','G','H','I','J']
    gridVert = ['0','1','2','3','4','5','6','7','8','9']
    
    #Game Grid
    grid = []
    
    def __init__(self):
        #Creates Grid
        for i in range(self.height):
            w = []
            for _ in range(self.width):
                w.append(self.emptyCell)
            self.grid.append(w)
            
        self.generateLevel(self.ship, self.shipChar)
            
    def printGrid(self,hide = True):
        #Prints the Grid
        #Print the Horizontal Line Label
        for _ in self.gridHor: print(_,end="")
        print("")
        #Print each Horizontal Line + Vertical Label
        for _ in range(self.height):
            #Vertical Label
            print(self.gridVert[_],end="")
            for i in self.grid[_]:
                if i != self.emptyCell and i != self.sunkShip and hide:
                    print(self.emptyCell,end="") #Makes the ships hidden!
                else:
                    print(i,end="")
            print("")
            
    def addShip(self,pos,size,char,direction):
        #Adds the ship based on pos, size, character and direction given
        if direction == "up":
            for y in range(pos[1],pos[1]-(size-1) - 1,-1):
                self.grid[y][pos[0]] = char
        elif direction == "down":
            for y in range(pos[1],pos[1]+(size-1) + 1):
                self.grid[y][pos[0]] = char
        elif direction == "left":
            for x in range(pos[0],pos[0]-(size-1) - 1,-1):
                self.grid[x][pos[1]] = char
        elif direction == "right":
            for x in range(pos[0],pos[0]+(size-1) + 1):
                self.grid[pos[1]][x] = char
            
    def clamp(self,value,minimum,maximum):
        if value <= minimum: return minimum
        elif value >= maximum: return maximum
        else: return value
            
    def generateLevel(self,ships,chars):
        #Generates the Game Level
        for index in range(len(ships)):
            
            #Counts Turns
            turns = 0
            while True:
                
                #Iterate the turns
                turns += 1
                #Stops itself if it runs out of points to randomly choose
                if turns > self.height * self.width : break
                
                #Shuffles the direction list
                random.shuffle(self.direction)
                
                #Generates each ship by one by one
                x = random.randint(0,self.width-1)
                y = random.randint(0,self.height-1)
                
                #Checks if existing ship is in that position
                if self.grid[y][x] != self.emptyCell : continue
            
                chosenDir = None
            
                #Size Check
                for d in self.direction:
                    if d == "up":
                        if y-(ships[index]-1) >= 0 : chosenDir = d; break
                    if d == "down":
                        if y+(ships[index]-1) < self.height : chosenDir = d; break
                    if d == "left":
                        if x-(ships[index]-1) >= 0 : chosenDir = d; break
                    if d == "right":
                        if x+(ships[index]-1) < self.width : chosenDir = d; break
                    
                if chosenDir == None: continue
        
                #Gap/Valid Point Check
                if chosenDir == "up":
                    for pointX in range(self.clamp(x-self.gap,0,self.width-1),
                                        self.clamp(x+self.gap,0,self.width-1)+1):
                        for pointY in range(self.clamp(y-(ships[index]-1),0,self.height-1),
                                            self.clamp(y+self.gap,0,self.height-1)+1):
                            if self.grid[pointY][pointX] != self.emptyCell:
                                chosenDir = None
                                break
                        if chosenDir == None: break
                    
                elif chosenDir == "down":
                    for pointX in range(self.clamp(x-self.gap,0,self.width-1),
                                        self.clamp(x+self.gap,0,self.width-1)+1):
                        for pointY in range(self.clamp(y-self.gap,0,self.height-1),
                                            self.clamp(y+self.gap+(ships[index]-1),0,self.height-1)+1):
                            if self.grid[pointY][pointX] != self.emptyCell:
                                chosenDir = None
                                break
                        if chosenDir == None: break
                    
                elif chosenDir == "left":
                    for pointY in range(self.clamp(y-self.gap,0,self.height-1),
                                        self.clamp(y+self.gap,0,self.height-1)+1):
                        for pointX in range(self.clamp(x-(ships[index]-1),0,self.width-1),
                                            self.clamp(x+self.gap,0,self.width-1)+1):
                            if self.grid[pointY][pointX] != self.emptyCell:
                                chosenDir = None
                                break
                        if chosenDir == None: break
                    
                elif chosenDir == "right":
                    for pointY in range(self.clamp(y-self.gap,0,self.height-1),
                                        self.clamp(y+self.gap,0,self.height-1)+1):
                        for pointX in range(self.clamp(x-self.gap,0,self.width-1),
                                            self.clamp(x+self.gap+(ships[index]-1),0,self.width-1)+1):
                            if self.grid[pointY][pointX] != self.emptyCell:
                                print(self.grid[pointY][pointX],x,y,chosenDir,ships[index])
                                chosenDir = None
                                break
                        if chosenDir == None: break
                
                if chosenDir != None : 
                    self.shipPieces += ships[index]
                    self.addShip([x,y], ships[index], chars[index], chosenDir)
                    break
                
    def attackShip(self,pos):
        #Converts Labels to number position (Takes horizontal first and then vertical)
        position = [self.gridHor.index(pos[0])-1, self.gridVert.index(pos[1])]
        if self.grid[position[1]][position[0]] != self.emptyCell and self.grid[position[1]][position[0]] != self.sunkShip:
            self.shipHealth[self.shipChar.index(self.grid[position[1]][position[0]])] = self.shipHealth[self.shipChar.index(self.grid[position[1]][position[0]])] - 1
            self.grid[position[1]][position[0]] = self.sunkShip
            self.shipPieces -= 1
            if self.shipPieces <= 0 : self.gameOver = True
            
            return True
        else:
            return False
            
    def shipCount(self):
        count = 0
        for ship in self.shipHealth:
            if ship != 0: 
                count += 1
        return count

In this section, I will think about the different parts of this program and how they should be organized.

Battle Ship Game

I think some class that represents the game makes sense. A game has a board. It has some sort of status (i.e. game is in progress or not). A user can specify some kind of move (attack based on position). The game also lets you know how many ships are left and what the board looks like.

Board

A game will have a board. The board consists of a grid. A board has ships on it. You can attack a coordinate and the board will let you know if you hit a ship or not.

Ship

A ship can have different sizes. It will also keep track of which coordinates have been attacked.

CLI / User Interface

The user needs something to interact with. In this case, it’s a command line game. The cli will translate user inputs into valid actions accepted by the game. It will also be in charge of taking data from the game and converting it into a view for the user.

Sketch of components

Next, I’ll consider the main functionality that each of these components needs to fulfill. This might change later.

  • Game
    • initialize with a board and ships
    • attack coordinate
    • get layout
    • get remaining ships
    • game is over
  • Board
    • initialized with grid sizes
    • place ship
    • coordinate has ship?
  • Ship
    • is destroyed
    • set damage
  • Cli
    • accept user input
    • display result
    • display board

One thing to note here is that this is just a brainstorm. Often as you implement a program, you find that some components don’t make sense or that others are needed. Very likely this will change, but it’s a rough starting poiint.

One response to “BattleShip Part 2: Breaking out different components”

  1. […] BattleShip Part 2: Breaking out different components […]

    Like

Leave a reply to BattleShip Project Review – Level up SE Cancel reply