9 Replies - 3322 Views - Last Post: 12 August 2012 - 04:39 PM Rate Topic: -----

#1 Sleeper256  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 6
  • Joined: 06-August 12

Battleship

Posted 06 August 2012 - 03:09 AM

Hi, I'm new here. First of all I joined because I'm looking for a community where you can discuss dilemmas and ideas, rather than just Q and A like StackOverflow, which seems to close any topic that doesn't have an objective solution.

I'm mostly having trouble wrapping my mind around how to program (text-based) Battleship (I'm programming it for practice)...I have about 500 lines of code that I THINK is utilizing classes well, and I've pretty much gotten lost in it.... I almost just want to tear it all apart and start all over again but I worked really hard on this.

So I just basically want another pair of eyes to help guide me in nothing particular. Do we do that here?

If not, could you give me suggestions where I can do that??

Thanks

Is This A Good Question/Topic? 0
  • +

Replies To: Battleship

#2 Simown  Icon User is offline

  • Blue Sprat
  • member icon

Reputation: 319
  • View blog
  • Posts: 650
  • Joined: 20-May 10

Re: Battleship

Posted 06 August 2012 - 03:27 AM

This community (I'd say especially this forum) is perfect for discussing code ideas and promoting our own spin on things.

All we ask is that you have shown some effort. Could you provide some code?
Was This Post Helpful? 0
  • +
  • -

#3 Sleeper256  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 6
  • Joined: 06-August 12

Re: Battleship

Posted 06 August 2012 - 04:23 AM

View PostSimown, on 06 August 2012 - 03:27 AM, said:

This community (I'd say especially this forum) is perfect for discussing code ideas and promoting our own spin on things.

All we ask is that you have shown some effort. Could you provide some code?


Awesome thanks! I'll post the whole thing.


import random
import urllib
import re
import time
import pdb



class Board:
    'Game Board'
    topMarkers = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
    sideMarkers = list(range(0, 26))
    
    
    def __init__(self,h,w): #Makes the map
        self.height = h
        self.width = w
        
        self.xMarkers = []
        self.yMarkers = []
        self.hitList = []
        self.hitListCopy = []
        
        self.boardSpace = '         '
        
        wave = '~'
        self.row = []
        self.column = []
        #self.column = wave * self.width # If width is 4, column is now '~~~~'
        
        for c in range(self.width):
            self.column.append(wave)
            self.xMarkers.append(Board.topMarkers[c])
        
        for r in range(self.height): #
            self.row.append(self.column[:]) #Use the slice to make copies
            self.yMarkers.append(Board.sideMarkers[r])
            
            

    def showGrid(self):
        print self.boardSpace + '  ' +  ' '.join(self.xMarkers)
        for i in range(self.height):
            print self.boardSpace + str(self.yMarkers[i]) + ' ' + '-'.join(self.row[i])
            
    def chooseMove(self, x ='Nope',y='Nope'):
        if x == 'Nope' and y == 'Nope': #If these aren't set, then it's a player making the move. Otherwise, provided by computer.
            while True:
                x = raw_input('X').upper() #These are letters.
                y = raw_input('Y')
                print(self.xMarkers)
                print(self.yMarkers)
                if y.isdigit(): 
                    if (int(y) in self.yMarkers) and (x in self.xMarkers):
                        #Either way, validate it
                        self.y = int(y)
                        break
                    else:
                        print('Try again, that\'s not a valid move.')
                else:
                    print('Try again, your input isn\'t a number')
                    
        xLet = x
        self.x = self.xMarkers.index(x)
        return ','.join([str(xLet),str(y)])
                    
                    
    def changeRow(self, char, x = 'Nope', y = 'Nope'):
        if x == 'Nope' and y == 'Nope':
            y = self.y
            x = self.x
        
        if isHit(x, y, self.row):
            char = 'X'
            self.hitList.append([y,x]) #Use this list to determine GameOver.
            self.hitListCopy.append([y,x]) #Keep just in case, since the other will be erased.
        self.row[y][x] = char
        
    def getRowData(self):
        #print 'In'
        return self.row
    
    def getMarkerData(self):
        return [self.xMarkers,self.yMarkers]
        
    def getHitList(self):
        return self.hitList
    
    def hideShips(self, shipPos):
        self.hiddenRow = self.row[:]
        print 'This is teh map %s' % (self.hiddenRow)
        for y in range(self.height):
            for x in range(len(self.row[y])):
                if self.hiddenRow[y][x] == 'S':
                    self.hiddenRow[y][x] = '-'
                    
    
    def showHideShips(self):
        print self.boardSpace + '  ' +  ' '.join(self.xMarkers)
        for i in range(self.height):
            print self.boardSpace + str(self.yMarkers[i]) + ' ' + '-'.join(self.hiddenRow[i])
        


def playerCoordsAsk(xMarkers, yMarkers):
    while True:
        x = raw_input('X').upper() #These are letters.
        y = raw_input('Y')
        print(xMarkers)
        print(yMarkers)
        if y.isdigit(): 
            if (int(y) in yMarkers) and (x in xMarkers):
                #Either way, validate it
                y = int(y)
                break
            else:
                print('Try again, that\'s not a valid move.')
        else:
            print('Try again, your input isn\'t a number')
                
    xLet = x
    x = xMarkers.index(x)
    return [x,y]


def isNotColliding(x, y, size, ori, mapData):
    for i in range(size):
        if i > 0:
            if ori == 'V':
                y += 1
            else:
                x += 1
        if mapData[y][x] == 'S':
            return False
    return True


def isHit(x,y, mapData):
    if mapData[y][x] == 'S':
        print 'HIT!'
        return True
    else:
        return False

class Ship():
    'Ship Object. Sizes and Powers. Stored as lists of indexes and then plugged into the board.'
    
    def __init__(self, type):
        self.typeList = ['Motorboat','Normal','Destroyer']
        self.sizeList = [1,3,5]
        self.type = self.typeList[type]
        self.size = self.sizeList[type]
        self.positionList = [[],[],[],[],[],[],[]]
        
        print '%s has been created and covers %s spaces' % (self.type, self.size)
        
        
    def setShip(self, boardRows, markers, computer = False):
        #Using a copy of the boarddata, set ships.
        allGood = False
        while allGood == False:
            if computer == False:
                print 'Set your %s ship up, it will cover %s spaces.' % (self.type, self.size)
            while True: #just breaking out of this will reset the loop.
                #To truly go on you must set allGood to true, and then break
                
                if computer == False:
                    xNum, yNum = playerCoordsAsk(markers[0], markers[1])
                else:
                    xNum = random.randint(0, len(markers[0])-1)
                    yNum = random.randint(0, len(markers[1])-1)
                
                
                if self.size > 1:
                    if computer == False:
                        orientation = raw_input('Vertical or Horizontal from the point you\'ve chosen?   V or H?   ')
                    else:
                        orientation = random.choice(['V','H'])
                else:
                    orientation = 'V' #For the hell of it, to satisfy the functions.
                    if computer == False:
                        print 'No need for orientation message.'
                
                
                self.x = xNum
                self.y = yNum
                if not withinLimits(self.y, 0, boardRows):
                    if computer == False:
                        print 'That is not on the board...'
                    break
                if not withinLimits(self.x, 0, boardRows[self.y]):
                    if computer == False:
                        print 'That is not on the board...'
                    break
                
                
                
                if orientation == 'V':
                    if not withinLimits(self.y, self.size, boardRows):
                        if computer == False:
                            print 'That won\'t fit vertically'
                        break
                else:
                    if not withinLimits(self.x, self.size, boardRows[self.y]):
                        if computer == False:
                            print 'That won\'t fit horizontally'
                        break
                    
                if not isNotColliding(self.x, self.y, self.size, orientation, boardRows):
                    if computer == False:
                        print 'That would collide with another of your ships'
                    break
                
                allGood = True #If it gets here, it continues to the next part.
                break
                
        for i in range(self.size):
            if i > 0:
                if orientation == 'V':
                    self.y += 1
                else:
                    self.x += 1
                    
            boardRows[self.y][self.x] = 'S'
            self.positionList[i] = ([self.y, self.x])
            #print self.positionList
        #print 'boardRows 201 : %s' % (boardRows)
        #print self.positionList
        
    def getShip(self):
        return [self.positionList, self.type]


def withinLimits(input, size, map):
    #Returns true if the input fits in the map
    if (int(input) + int(size)) > len(map):
        return False
    else:
        return True

        
class Move:
    'Represents the moves that each player makes, and keeps track, and performs any necessary conversion'
    
    def __init__(self):
        self.moveList = []
        
    def addMove(self, coords):
        if coords not in self.moveList: #If the player didn't already make this move.
            self.moveList.append(coords)
            return True
        else:
            print 'You already made that move!!!'
            return False
        
    def getMove(self):
        if len(self.moveList) > 0:
            return self.moveList[-1]
        else:
            return self.moveList[0]
            
    def getAllMoves(self):
        return self.moveList        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
class Connection:
    'Represents the internet connection and processes from the move server and the program.'
    
    def __init__(self, player, onlineTF):
        #Upon creation, get the player who's connection this is, and most recent post's timestamp on the page.
        self.online = onlineTF
        if self.online == True:
            
            self.player = player
            
            #---------------Open connection for that player-----------------
            print 'Player %s loading spaghetti...' % (self.player)
            self.URLname = 'battleshiprecieverURL' % (player) 
            self.URL = urllib.urlopen(self.URLname)
        
        
        
    def GetTime(self):
        if self.online == False:
            return 'This is not Online Mode'
        
        lines = self.URL.readlines()
        lines2 = "".join(lines)
    

        move = re.search(r'\d{2}:\d{2}:\d{2}', lines2[::-1])
        
        if move:
            return str(self.player) + ' ' + str(move.group(0)[::-1])
        else:
            return False
        

    def Check(self):
        if self.online == False:
            return 'This is not Online Mode'
        
        time.sleep(1)
    
        print 'Player %s makes their move' % (self.player)
        self.URL = urllib.urlopen(self.URLname)
        
        lines = self.URL.readlines()
        lines2 = "".join(lines)
        
        move = re.search(r'[0-9][,]([a-z]|[A-Z])', lines2[::-1])
        
        if move:
            return 'Returning ' +  str(move.group(0)[::-1])
        else:
            return False
        
        
        
    def Send(self,coords):
        if self.online == False:
            return 'This is not Online Mode'
        
        sendName = 'http://www.battleshipsitereciever.php ' % (self.player, coords) 
        sendURL = urllib.urlopen(sendName)
        print('Sent %s' % (coords))
        

def checkForSunk(shipList, hitList):
    
    #Lists is something like this [[0,1],[0,2],[0,3]]
    #ShipList[0] is list, [1] is name of ship
    #ShipList is ALL ships.
    
    #print 'SHIPLIST : %s' % (shipList)
    #[[[[0,1],[0,2],[0,3],[0,4],[0,5],[],[]], 'Destroyer'], [[[0,1],[0,2],[0,3],[0,4],[0,5],[],[]], 'Destroyer'], [[[0,1],[0,2],[0,3],[0,4],[0,5],[],[]], 'Destroyer']]
    
    #[[[0,1],[0,2],[0,3],[0,4],[0,5],[],[]], 'Destroyer']
    #   0                                       1
    #print 'HITLIST : %s ' % (hitList)
    for j in range(len(shipList)):
        for i in shipList[j][0]:
            if i in hitList:
                print 'True in ship # %s' % (shipList[j][1])
                del shipList[j][0][shipList[j][0].index(i)] #Delete that part of the ship from the list.
    #Check if there's any empty ships, and delete the ship if there are.
    for j in range(len(shipList)-1,-1,-1):
        print shipList[j] #Problem around here!!!!!!!!!!!!
        if listIsEmpty(shipList[j][0]):
            print '%s has been sunk!' % (shipList[j][1])
            del shipList[j]
  
  
def isGameOver(shiplist):
    if shiplist == []:
        return True
    else:
        return False

def listIsEmpty(list):
    for i in range(len(list)):
        if list[i] != []: #If it finds anything thats not empty, return False. Else true
            return False
        else:
            return True





netEnable = False #Determines if Online or not. For everything having to do with the class Connection, use if statement.

gameNotOver = True

#Each player gets 2 boards, one to set their ships on (setMap) and one to attack other ships. When a player attacks, they're really checking those coords against the other player's setMap.

#setMap = Board(7,7)
#attackMap = Board(7,7)


p1 = [[Board(7,7), Board(7,7)], Move(), Connection(1, netEnable)]
p2 = [[Board(7,7), Board(7,7)], Move(), Connection(2, netEnable)]
#p3 = [[Board(7,7), Board(7,7)], Move(), Connection(3, netEnable)]

#Like this p1 = [[theirBoard, attackBoard], Moves, Connection]



#p = [p1,p2,p3]
p = [p1,p2]


ships1 = [Ship(0),Ship(0),Ship(0)] #Your ships
ships2 = [Ship(0),Ship(0),Ship(0)] #Their ships



numOfPlayers = len(p)


youPlayer = int(raw_input('Which player are you? 1,2,3:   '))

youPlayer -= 1

shipPosAll = [[],[]]

for player in range(numOfPlayers):
    
    boardr = p[player][0][1].getRowData()
    boardM = p[player][0][0].getMarkerData()
    #raw_input(boardr)
    raw_input(boardM)



    #Set Ships Dialog.

    for i in range(len(ships1)):
        if player == youPlayer:
            p[player][0][1].showGrid() 
            ships1[i].setShip(boardr, boardM)
        else:
            #ships[i].setShip(boardr, boardM)
            ships2[i].setShip(boardr,boardM, computer = True)
            
    
    ships = [ships1, ships2]
    
    for i in range(len(ships[0])): #Set each ship.
        shipPosAll[player].append(ships[player][i].getShip()) #USE THIS INFO TO FEED TO THE BOARD TO TELL IT WHERE SHIPS ARE SO YOU KNOW WHERE HITS ARE.
    
    #print 'Your Shiplist is : %s' % (shipPosAll[player])



#INIT, DO ONLY ONCE

for i in range(numOfPlayers): 
    print p[i][2].GetTime()
    
    
    
#SWAP BOARDS ONLY ONCE....BOARDS MUST BE UPDATED BEFORE THIS.
p1dupe = p1[0][1]
p2dupe = p2[0][1]

p1[0][0] = p2dupe
p2[0][0] = p1dupe


#Like this p1 = [[theirBoard, attackBoard], Moves, Connection]



#p = [p1,p2,p3]
p = [p1,p2]


raw_input('ShipAllPos is %s ' % (shipPosAll))

#MAIN LOOP
while gameNotOver:
    
    for i in range(numOfPlayers):
        playersCheck = [0,1]
        del playersCheck[youPlayer]
        
        print 'Player %s turn' % (i)
        print 'Here are your ships'
        #raw_input (p[i][0][1])
        
        p[i][0][1].showGrid() #Show the grid of the player (TheirShips)
        
        print 'Where do you want to attack?'
        #raw_input(shipPosAll[i])
        
        p[i][0][0].hideShips(shipPosAll[i])
        p[i][0][0].showHideShips() #Show the grid of the player (AttackShips)
        
        hits = p[i][0][0].getHitList()
        alreadyMade = False
        while alreadyMade == False:
            coords = p[i][0][0].chooseMove()
            alreadyMade = p[i][1].addMove(coords) #Returns true if move accepted, otherwise false
            
        lastMove = p[i][1].getMove()
        print 'The move you made was %s' % (lastMove)
        p[i][2].Send(lastMove)
        changeCoords = p[i][2].Check()
        p[i][0][0].changeRow('B')
        
        for pc in playersCheck: #Check every player who is not you. You were deleted at teh beginning.
            checkForSunk(shipPosAll[pc], hits)
            print 'Current Attacked Players shipPos: %s' % (shipPosAll[pc])
        #p1Site.Send(coords)
        #print 'Thing %s' % (shipPos[i])
            if isGameOver(shipPosAll[pc]): #PROBLEM.
                gameNotOver = False
                break

    for i in range(numOfPlayers):
        print p[i][1].getAllMoves()
        
        
print 'All Ships Sunk. The End. Wat more you want from mme its 3:48am'






I was working on this a week ago, took a break from programming, and decided to revamp it, but I forgot pretty much everything.....I'm trying to make offline mode work before adding the online. But the online is there waiting to be activated, and I know that part works.

I guess the problem I'm having now specifically is the attack phase and separating the maps and making the attack map ships invisible but still registered or something...

Also I'd like to know if anyone can tell me if I'm doing this reasonably well and if I can improve or clean this up in some way.....

How do you resist the urge to start it all over from scratch??
Was This Post Helpful? 0
  • +
  • -

#4 Sleeper256  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 6
  • Joined: 06-August 12

Re: Battleship

Posted 07 August 2012 - 12:57 AM

Anyone?
Was This Post Helpful? 0
  • +
  • -

#5 atraub  Icon User is offline

  • Pythoneer
  • member icon

Reputation: 759
  • View blog
  • Posts: 2,010
  • Joined: 23-December 08

Re: Battleship

Posted 07 August 2012 - 07:43 AM

Sorry you haven't gotten a response yet. Your code is big, which means that sitting down and dissecting it is going to take some time. I'm at work today, so I really can't dedicate the amount of focus this would need right now, but I'll do my best to get to this one soon.
Was This Post Helpful? 1
  • +
  • -

#6 Sleeper256  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 6
  • Joined: 06-August 12

Re: Battleship

Posted 08 August 2012 - 02:31 PM

Thank you sir, I understand it's big and you guys are busy, thanks!
Was This Post Helpful? 0
  • +
  • -

#7 atraub  Icon User is offline

  • Pythoneer
  • member icon

Reputation: 759
  • View blog
  • Posts: 2,010
  • Joined: 23-December 08

Re: Battleship

Posted 11 August 2012 - 11:41 PM

So I looked over your program, it looks pretty good. In terms of keeping the "attack map" invisible, that's easy enough, just don't print it! :)

Do you have any specific questions?
Was This Post Helpful? 0
  • +
  • -

#8 Sleeper256  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 6
  • Joined: 06-August 12

Re: Battleship

Posted 12 August 2012 - 01:30 AM

View Postatraub, on 11 August 2012 - 11:41 PM, said:

So I looked over your program, it looks pretty good. In terms of keeping the "attack map" invisible, that's easy enough, just don't print it! :)

Do you have any specific questions?



Oh no no I think I said it wrong, I'm trying to print the map, so that the player can see what they've already tried and where a hit was, but I don't want the opponent's ships to appear on the map, just register on it.

Thanks for looking through it! It's not too convoluted or anything? Would you use a different strategy?
Was This Post Helpful? 0
  • +
  • -

#9 atraub  Icon User is offline

  • Pythoneer
  • member icon

Reputation: 759
  • View blog
  • Posts: 2,010
  • Joined: 23-December 08

Re: Battleship

Posted 12 August 2012 - 11:39 AM

here's a thought to keep in mind: In battleship, there is not 1 map, but 4! Each player has a map where he places his ships (2 maps) and each player has a map representing the previous places he's guessed. This can be simplified to less maps but for the first version of your program you might find it easier to have more maps! What do you think about that?

EDIT:
The immediate benefit is that there will be two maps that the player will see, and everything on that map SHOULD be visible, so you won't have to worry about hiding some data.
--------

On the other hand, you could simply creating a function that takes a map and turns it into a string. When it comes across a "thing" that the player shouldn't see, it could draw it as a blank space.

EDIT 2:
Maybe this example will help,
>>> myMap = [["~" for i in range(5)] for j in range(5)]
>>> for i in range(3):
	myMap[i][4] = "M"

	
>>> for i in range(3):
	myMap[0][i] = "E"

>>> print(myMap)
[['E', 'E', 'E', '~', 'M'], ['~', '~', '~', '~', 'M'], ['~', '~', '~', '~', 'M'], ['~', '~', '~', '~', '~'], ['~', '~', '~', '~', '~']]
	
>>> def mapToString(aMap):
	outMap = [[col if col!="E" else "~" for col in row] for row in aMap]
	return "\n".join([" ".join(row) for row in outMap])

>>> print(mapToString(myMap))
~ ~ ~ ~ M
~ ~ ~ ~ M
~ ~ ~ ~ M
~ ~ ~ ~ ~
~ ~ ~ ~ ~
>>> 



I used some fancy footwork with the list comprehensions and the join function, if you would like a more thorough explanation, just ask. My thinking was, "M" for miss, "E" for enemy, and "H" for hit. This is more of a proof of concept to show you a quick and dirty method for hiding map data.

This post has been edited by atraub: 12 August 2012 - 12:10 PM

Was This Post Helpful? 0
  • +
  • -

#10 baavgai  Icon User is offline

  • Dreaming Coder
  • member icon

Reputation: 5884
  • View blog
  • Posts: 12,769
  • Joined: 16-October 07

Re: Battleship

Posted 12 August 2012 - 04:39 PM

View Postatraub, on 12 August 2012 - 02:39 PM, said:

In battleship, there is not 1 map, but 4!


Or two. Each player has a map. That map is a grid of cells. Each of those cells has two values. One value is the reference to the occupying ship or no ship. The other value is whether or not that cell has been fired upon. Keeping these two values in two separate structures is essentially the same use using parallel arrays. Don't do this.

Also, keep in mind how you want your objects to be used. You want them to be as simple as possible. Methods should not rely on any understanding of underlying structure.

Since we're sharing code, here's a quick proof of concept.

class Board(object):
	CELL_INVALID = -2
	CELL_EMPTY = -1
	def __init__(self, rows, cols):
		self.rows, self.cols = rows, cols
		self.grid = [ [ [ Board.CELL_EMPTY, False ] for col in range(cols) ] for row in range(rows) ]

	def setValue(self, row, col, value):
		self.grid[row][col][0] = value

	def setHit(self, row, col):
		self.grid[row][col][1] = True

	def getValue(self, row, col):
		return self.grid[row][col][0]

	def isHit(self, row, col):
		return self.grid[row][col][1]


def show(rows, cols, showCell):
	print('\n'.join([''.join([showCell(row,col) for col in range(cols)]) for row in range(rows)]))

def showOwner(b, ships):
	def showCell(row, col):
		v = b.getValue(row, col)
		if b.isHit(row, col):
			if v==Board.CELL_EMPTY:
				return 'x'
			else:
				return '*'
		else:
			if v==Board.CELL_EMPTY:
				return '.'
			else:
				return ships[v]
	show(b.rows, b.cols, showCell)

b = Board(5,6)


b.setValue(1,3,0)
b.setValue(2,3,0)
b.setValue(3,3,0)

b.setValue(4,4,1)

b.setHit(2,2)
b.setHit(2,3)

showOwner(b, ('M','A'))



Notice that board has four simple methods. That's all we need to know about board. The logic of ships, if they've sank, how big they are, their display name, would be handled by another object that held a board. Player, perhaps?

Hope this helps.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1