Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- '''
- Battleship! Game
- Licensed under CC BY-NC-SA 4.0.
- https://creativecommons.org/licenses/by-nc-sa/4.0/
- By Mark Wu
- @markwu.me
- '''
- import pygame,sys,copy,random
- from pygame.locals import *
- class Struct: pass
- # ---------------- MODULE LEVEL DEFINITION ----------------
- gameData = Struct()
- colors = Struct()
- FPSCLOCK = pygame.time.Clock()
- DISPLAYSURF = pygame.display.set_mode((800, 600))
- # FOR DEBUG USE
- VIEW_ALL = False
- # THIS SHOULD BE SET TO TRUE
- VIEW_COMPUTER_OVERLAY = True
- # ---------------- FUNCTIONS ----------------
- def makeTextObjs(text, font, color):
- '''
- a shortcut which returns two obj: textSurfaceObj, textRectObj
- :return: textSurfaceObj, textRectObj
- '''
- surfObj = font.render(text, True, color)
- return surfObj, surfObj.get_rect()
- def terminate():
- '''
- a shortcut to end the program
- '''
- pygame.quit()
- sys.exit()
- def runStartScreen():
- DISPLAYSURF.fill(colors.WHITE)
- helpBG = pygame.image.load('data/help.fw.png')
- bg = pygame.image.load('data/start_BG.fw.png')
- while True:
- if gameData.showHelp:
- DISPLAYSURF.blit(helpBG, (0, 0))
- else:
- DISPLAYSURF.blit(bg, (0, 0))
- for event in pygame.event.get():
- if event.type == QUIT: terminate()
- if event.type == MOUSEBUTTONUP:
- gameData.showHelp = True
- if event.type == KEYDOWN:
- # end the Start Screen
- gameData.showHelp = False
- return
- pygame.display.update()
- FPSCLOCK.tick(gameData.FPS)
- def drawCell(tl_pos, size, color):
- '''
- this function draws individual cells with top_left corner(pos)
- '''
- pygame.draw.rect(DISPLAYSURF, color, (tl_pos[0], tl_pos[1], size, size))
- def getCellColor(board, row, col):
- '''
- This returns the color needed to be drawed at that cell
- '''
- cell_info = board[row][col]
- if cell_info == '':
- return colors.EMPTY_CELL
- else:
- cell_data = readCellContent(board, row, col)
- # [player,shipName,orientation,posIndex, status]
- if cell_data[4] == gameData.STATUS_HIT:
- return colors.BLACK
- if cell_data[0] == 'p1':
- return colors.P1_SHIP_BG
- if cell_data[0] == 'p2':
- return colors.P2_SHIP_BG
- return colors.WHITE
- def drawBoarderLine(tl_pos, cell_size):
- right_x = tl_pos[0]+gameData.col*cell_size
- bottom_y = tl_pos[1]+gameData.row*cell_size
- for row in xrange(1, gameData.row):
- pygame.draw.aaline(DISPLAYSURF, colors.CELL_BOARDERLINE,
- (tl_pos[0], tl_pos[1] + row * cell_size),
- (right_x, tl_pos[1] + row * cell_size))
- for col in xrange(1, gameData.col):
- pygame.draw.aaline(DISPLAYSURF, colors.CELL_BOARDERLINE,
- (tl_pos[0]+col*cell_size, tl_pos[1]),
- (tl_pos[0]+col*cell_size, bottom_y))
- # draw outer boarder
- inner_margin = 8
- boarderRect = [tl_pos[0]-inner_margin,tl_pos[1]-inner_margin,
- gameData.col * gameData.boardUI_cellSize+inner_margin,
- gameData.row * gameData.boardUI_cellSize+inner_margin]
- pygame.draw.rect(DISPLAYSURF, colors.BLACK, boarderRect, 8)
- def drawBoard(board, tl_pos):
- '''
- This function draws the board with top_left corner(pos)
- '''
- cell_size = gameData.boardUI_cellSize
- # draw indiv cell
- for row in xrange(gameData.row):
- for col in xrange(gameData.col):
- # draw cell
- cellColor = getCellColor(board, row, col)
- cell_pos = (tl_pos[0] + col * cell_size,
- tl_pos[1] + row * cell_size)
- drawCell(cell_pos, cell_size, cellColor)
- # draw boarder line
- drawBoarderLine(tl_pos, cell_size)
- def drawEnergyBar():
- # update txt
- prompt = 'Your Energy: ' if gameData.currentPlayer == 'p1'else\
- "Enemy's Turn: "
- gameData.txt_EB = prompt + ('%s / %s' %
- (gameData.currentEnergy[gameData.currentPlayer],
- gameData.maxEnergy[gameData.currentPlayer]))
- BG_color = colors.EB_BG if gameData.currentPlayer == 'p1'else\
- colors.EB_BG2
- # draw boarder
- pygame.draw.rect(DISPLAYSURF, BG_color, gameData.EB_Pos)
- pygame.draw.rect(DISPLAYSURF, colors.EB_BOARDER,gameData.EB_Pos,4)
- # draw text
- txt_EB, txt_EBRect = \
- makeTextObjs(gameData.txt_EB, gameData.BASICFONT, colors.BLACK)
- txt_EBRect.center = pygame.Rect(gameData.EB_Pos).center
- DISPLAYSURF.blit(txt_EB, txt_EBRect)
- def getSkillName(skills_list, i):
- '''
- return the skills name at index i of the skills_list
- '''
- if skills_list[i] == '':
- return gameData.blankSkillName
- else:
- return skills_list[i]
- def drawSkillsPanal(skills_list):
- # draw bg
- pygame.draw.rect(DISPLAYSURF, colors.UI_BG, gameData.Skills_Pos)
- pygame.draw.rect(DISPLAYSURF, colors.BLACK,gameData.Skills_Pos,1)
- # draw central split line
- pygame.draw.aaline(DISPLAYSURF, colors.BLACK,
- (gameData.Skills_Pos[0]+24, gameData.Skills_Pos[1]+78),
- (gameData.Skills_Pos[0]+301, gameData.Skills_Pos[1]+78),
- 2)
- # draw Skill Boxs
- for i in range(len(gameData.skillBox)):
- box_rect = gameData.skillBox[i]
- pygame.draw.rect(DISPLAYSURF, colors.WHITE, box_rect)
- pygame.draw.rect(DISPLAYSURF, colors.BLACK, box_rect,1)
- # fill skill icon
- skill_name = getSkillName(skills_list,i)
- DISPLAYSURF.blit(gameData.IMAGESDICT[skill_name],
- (box_rect[0],box_rect[1]))
- # draw text
- tl = gameData.Skills_txt_Pos
- margin = 25
- line = 1
- for txt in gameData.txt_skillDescription:
- txt_skillDescription, txt_skillDescriptionRect = \
- makeTextObjs(txt, gameData.BASICFONT,
- colors.BLACK)
- txt_skillDescriptionRect.center = (tl[0], tl[1]+margin*line)
- DISPLAYSURF.blit(txt_skillDescription, txt_skillDescriptionRect)
- line += 1
- def drawShipImages(player):
- '''
- cover the ship with images only for p1
- '''
- # eg. {'submarine': [(12, 9), (13, 9)],
- # 'battleship': [(7, 3), (8, 3), (9, 3), (10, 3)], }
- for ship_name in gameData.shipImagePos[player]:
- ship_data = gameData.shipImagePos[player][ship_name]
- imageRect = convertBoardToPixel(*ship_data[0])
- DISPLAYSURF.blit(gameData.IMAGESDICT[ship_name],
- imageRect)
- def drawUIOverlay(player):
- '''
- print relevent UI Overlay features
- '''
- tl_pos = gameData.boardUI_TL_Pos
- cell_size = gameData.boardUI_cellSize
- # draw indiv cell
- for row in xrange(gameData.row):
- for col in xrange(gameData.col):
- # draw cell
- content = gameData.UIOverLay[player][row][col]
- if content == '': continue
- if content == gameData.S0:
- # because this one is bigger
- cell_pos = (tl_pos[0] + col * cell_size + 0.5 * cell_size,
- tl_pos[1] + row * cell_size + 0.5 * cell_size)
- ui_rect = gameData.IMAGESDICT[content].get_rect()
- ui_rect.center = cell_pos
- DISPLAYSURF.blit(gameData.IMAGESDICT[content],ui_rect)
- continue
- cell_pos = (tl_pos[0] + col * cell_size,
- tl_pos[1] + row * cell_size)
- DISPLAYSURF.blit(gameData.IMAGESDICT[content],
- (cell_pos[0], cell_pos[1]))
- def drawBoxPanal():
- '''
- draw the bottom left box area and buttons
- '''
- # draw bg
- pygame.draw.rect(DISPLAYSURF, colors.UI_BG, gameData.Box_Pos)
- pygame.draw.rect(DISPLAYSURF, colors.BLACK,gameData.Box_Pos,1)
- # draw text
- gameData.txt_box[0] = 'Ship Remain: %i - %i' % (gameData.shipCount['p1'],
- gameData.shipCount['p2'])
- tl = gameData.Box_txt_Pos
- margin = 25
- line = 1
- for txt in gameData.txt_box:
- txt_Description, txt_DescriptionRect = \
- makeTextObjs(txt, gameData.BASICFONT,colors.BLACK)
- txt_DescriptionRect.center = (tl[0], tl[1]+margin*line)
- DISPLAYSURF.blit(txt_Description, txt_DescriptionRect)
- line += 1
- # draw button
- pygame.draw.rect(DISPLAYSURF, colors.WHITE, gameData.btnConcede_Rect)
- pygame.draw.rect(DISPLAYSURF, colors.BLACK,gameData.btnConcede_Rect,1)
- txt_Description, txt_DescriptionRect = \
- makeTextObjs('CONCEDE', gameData.BASICFONT, colors.BLACK)
- txt_DescriptionRect.center = gameData.btnConcede_Rect.center
- DISPLAYSURF.blit(txt_Description, txt_DescriptionRect)
- pygame.draw.rect(DISPLAYSURF, colors.WHITE, gameData.btnNext_Rect)
- pygame.draw.rect(DISPLAYSURF, colors.BLACK, gameData.btnNext_Rect, 1)
- txt_Description, txt_DescriptionRect = \
- makeTextObjs('NEXT TURN', gameData.BASICFONT, colors.BLACK)
- txt_DescriptionRect.center = gameData.btnNext_Rect.center
- DISPLAYSURF.blit(txt_Description, txt_DescriptionRect)
- def drawShipSelectedBox(player='p1'):
- '''
- Highlight the currently selected ship
- '''
- currentShip = gameData.shipSelected
- if currentShip not in gameData.shipImagePos[player]: return
- orient = gameData.ship[player][currentShip][0]
- positionList = gameData.shipImagePos[player][currentShip]
- if orient == gameData.HORIZONTAL:
- width = len(gameData.ships[currentShip]) * gameData.boardUI_cellSize
- height = gameData.boardUI_cellSize
- else:
- height = len(gameData.ships[currentShip]) * gameData.boardUI_cellSize
- width = gameData.boardUI_cellSize
- pos = convertBoardToPixel(positionList[0][0], positionList[0][1])
- rect = pygame.Rect(pos[0],pos[1],width,height)
- pygame.draw.rect(DISPLAYSURF, colors.RED, rect, 2)
- def drawGameUI(player='p1'):
- '''
- redraw the game UI
- '''
- DISPLAYSURF.fill(colors.BG_BLUE)
- # draw bg
- DISPLAYSURF.blit(gameData.IMAGESDICT['game_BG'],(0,0))
- # draw board
- if VIEW_ALL: drawBoard(gameData.board, gameData.boardUI_TL_Pos)
- else: drawBoard(gameData.view['p1'], gameData.boardUI_TL_Pos)
- # draw ships images
- drawShipImages(player)
- # draw overlay UI
- drawUIOverlay(player)
- if VIEW_COMPUTER_OVERLAY: drawUIOverlay('p2')
- # draw SHIP SELECTED BOX
- drawShipSelectedBox()
- # draw other UI
- drawEnergyBar()
- drawSkillsPanal(gameData.skill['p1'])
- drawBoxPanal()
- def getRandomOrientation():
- '''
- randomly return an orientation (h/v)
- '''
- if random.randint(0,1) == 1:
- return gameData.HORIZONTAL
- else:
- return gameData.VERTICAL
- def checkValidPosition(board, assumedShipPos):
- '''
- return True if all shipPos is on board
- and don't collide with other ship
- '''
- for row,col in assumedShipPos:
- if not 0<=row<gameData.row or not 0<=col<gameData.col:
- return False
- # now it's on board. check collision
- if board[row][col] != '':
- return False
- return True
- def putShipOnBoard(board, shipPos_list, ship_name, player, orientation,
- validate=True):
- '''
- merge all element in shipPos_list on to board
- WILL OVERRIDE EXISTING CELL DATA
- validate will check if ship is in dict; don't use it when init.
- '''
- if validate == True:
- if ship_name not in gameData.shipImagePos[player] or\
- ship_name not in gameData.ship[player]:
- return
- index = 0
- for each in shipPos_list:
- if each == False:
- row,col = gameData.shipImagePos[player][ship_name][index]
- status = gameData.STATUS_HIT
- else:
- row = each[0]
- col = each[1]
- status = gameData.STATUS_NORMAL
- board[row][col] = '%s_%s_%s_%s_%s' % \
- (str(player), str(ship_name), str(orientation),
- str(index),str(status))
- index += 1
- def placeSubmarine(player, board):
- '''
- randomly update submarine pos on board and return position list
- '''
- length = len(gameData.ships[gameData.SUBMARINE])
- orientation = gameData.VERTICAL
- colBoundary = (0,gameData.col-1)
- row = gameData.row-length if player == 'p1' else 0
- tlPos = (row, random.randint(*colBoundary))
- # assured OK position so no need to validate
- assumedShipPos = [(tlPos[0] + i, tlPos[1])
- for i in range(length)]
- assumedShipPos.insert(0, orientation)
- putShipOnBoard(board, assumedShipPos[1:], gameData.SUBMARINE,
- player,orientation, False)
- return assumedShipPos
- def getRandomShipLocation(player):
- '''
- randomly deploy ship and returns the ship dictionary
- will only deploy ship at the players end
- boundary is [lower,upper]
- '''
- # set up
- res_dict = dict()
- board = [['' for col in xrange(gameData.col)]
- for row in xrange(gameData.row)]
- colBoundary = (0,gameData.col-1)
- if player == 'p2': rowBoundary = (1,gameData.row/2-1) # ai bonus
- else: rowBoundary = (gameData.row/2, gameData.row-1)
- # First place submarine and update board
- res_dict[gameData.SUBMARINE] = placeSubmarine(player, board)
- # Then other ships
- for each_ship in gameData.ships:
- if each_ship == gameData.SUBMARINE: continue
- # place each ship into the board
- length = len(gameData.ships[each_ship])
- valid = False
- while not valid:
- orientation = getRandomOrientation()
- tlPos = (random.randint(*rowBoundary), random.randint(*colBoundary))
- if orientation == gameData.HORIZONTAL:
- assumedShipPos = [(tlPos[0], tlPos[1] + i)
- for i in range(length)]
- else:
- # VERTICAL also needs to check if across the boarder
- if tlPos[0]+length-1 >= rowBoundary[1]: continue
- assumedShipPos = [(tlPos[0] + i, tlPos[1])
- for i in range(length)]
- if checkValidPosition(board,assumedShipPos):
- res_dict[each_ship] = [orientation]
- putShipOnBoard(board, assumedShipPos, each_ship,
- player, orientation, False)
- res_dict[each_ship].extend(assumedShipPos)
- valid = True
- return res_dict
- def setShipLocation():
- '''
- This sets the ship location for both players.
- Currently all will be random
- '''
- # get ship location
- # {'submarine': ['vertical', (8, 7), (9, 7), (10, 7)],
- # 'battleship': ['vertical', (11, 3), (11, 4), (11, 5), (11, 6)]}
- gameData.ship['p1'] = getRandomShipLocation('p1')
- gameData.ship['p2'] = getRandomShipLocation('p2')
- # merge to shipImagePos to track images
- for player in gameData.ship:
- for each_name in gameData.ship[player]:
- gameData.shipImagePos[player][each_name] =\
- copy.deepcopy(gameData.ship[player][each_name][1:])
- # merge to the board
- for player in gameData.ship:
- ship_dict = gameData.ship[player]
- for each_name in ship_dict:
- each_posList = ship_dict[each_name]
- putShipOnBoard(gameData.board, each_posList[1:], each_name,
- player, each_posList[0], False)
- # rotate image if necessary
- if each_posList[0] == gameData.VERTICAL and player == 'p1':
- gameData.IMAGESDICT[each_name] = \
- pygame.transform.rotate(gameData.IMAGESDICT[each_name],90)
- def convertBoardToPixel(row, col):
- '''
- return the pixel(true) cord of the TL of board location
- :return: (x,y)
- '''
- x = gameData.boardUI_TL_Pos[0] + col * gameData.boardUI_cellSize
- y = gameData.boardUI_TL_Pos[1] + row * gameData.boardUI_cellSize
- return (x,y)
- def readCellContent(board, row, col):
- '''
- Decode the content stored in the input location of the board
- :return: [player,shipName,orientation,posIndex, status]
- '''
- # format: 'player-shipName-orientation-posIndex-status'
- try:
- content = board[row][col]
- res = content.split('_')
- return res
- except:
- print "Exception at readCellContent (board, row, col)", board, row, col
- return None
- def updateView(player):
- '''
- This updates the player's view
- '''
- # update player's ship info from ship[player]
- gameData.view[player] = buildBoard()
- for ship_name in gameData.ship[player]:
- ship_data = gameData.ship[player][ship_name]
- putShipOnBoard(gameData.view[player],
- ship_data[1:],ship_name,
- player, ship_data[0])
- # update additional view
- for row in range(len(gameData.revealed[player])):
- for col in range(len(gameData.revealed[player][0])):
- if gameData.revealed[player][row][col] == True:
- gameData.view[player][row][col] = gameData.board[row][col]
- def updateUIOverLay(player):
- '''
- update the UIOverLay for that player
- remove 'recents' activity and change 'just' to 'recent' activity
- :param player:
- '''
- board = copy.deepcopy(gameData.UIOverLay[player])
- for row in range(len(board)):
- for col in range(len(board[0])):
- content = board[row][col]
- if content == gameData.D0: board[row][col] = gameData.D1
- elif content == gameData.H0: board[row][col] = gameData.H1
- elif content == gameData.S0: board[row][col] = gameData.S1
- elif content == gameData.M0: board[row][col] = gameData.M1
- elif content == gameData.T0: board[row][col] = gameData.T1
- elif content == gameData.D1 or content == gameData.H1\
- or content == gameData.S1 or content == gameData.M1\
- or content == gameData.T1:
- board[row][col] = ''
- gameData.UIOverLay[player] = board
- def buildBoard(fill=None):
- '''
- return an empty board
- :return:
- '''
- if fill == None: fill = ''
- return [[fill for col in xrange(gameData.col)]
- for row in xrange(gameData.row)]
- def updateBoard():
- '''
- update gameData.board with the latest ship data of all two players
- WILL flush the board
- '''
- gameData.board = buildBoard()
- for player in gameData.ship:
- ship_dict = gameData.ship[player]
- for each_name in ship_dict:
- each_shipData = ship_dict[each_name]
- putShipOnBoard(gameData.board, each_shipData[1:], each_name,
- player, each_shipData[0])
- def checkIfGameOver():
- '''
- check if any player lost all ships/ any ships reach enemy bot row
- update gameData.isGameOver
- '''
- if gameData.shipCount['p1'] == gameData.shipCount['p2'] == 0:
- gameData.isGameOver = True
- gameData.winner = 'draw'
- return
- if gameData.shipCount['p1'] == 0:
- gameData.isGameOver = True
- gameData.winner = 'p2'
- return
- if gameData.shipCount['p2'] == 0:
- gameData.isGameOver = True
- gameData.winner = 'p1'
- return
- # check ship reached end line
- for player in ('p1','p2'):
- endRow = 0 if player == 'p1' else gameData.row-1
- for ship_name in gameData.shipImagePos[player]:
- for points in gameData.shipImagePos[player][ship_name]:
- if points[0] == endRow:
- gameData.winner = player
- gameData.isGameOver = True
- return
- gameData.isGameOver = False
- def destroyShip(player, ship_name):
- '''
- Destroy the given ship.
- Will remove ship name from gameData.ship and gameData.shipImagePos
- Also decrease the count and update D overlay
- '''
- print 'deleting gameData.ship: ',player, ship_name
- for each_pos in gameData.shipImagePos[player][ship_name]:
- # notify both player
- gameData.UIOverLay['p1'][each_pos[0]][each_pos[1]] = gameData.D0
- gameData.UIOverLay['p2'][each_pos[0]][each_pos[1]] = gameData.D0
- del gameData.ship[player][ship_name]
- del gameData.shipImagePos[player][ship_name]
- gameData.shipCount[player] -= 1
- if gameData.shipSelected == ship_name:
- gameData.shipSelected = ''
- # also increase the energy for that player!
- enemy = 'p2' if player == 'p1' else 'p1'
- gameData.maxEnergy[enemy] += 1
- if gameData.maxEnergy[enemy] >= gameData.MAX_ENERGY:
- gameData.maxEnergy[enemy] = gameData.MAX_ENERGY
- checkIfGameOver()
- def randomlyTakeDamage(player, ship_name, amount):
- '''
- randomly take amount damage to the ship
- modify gameData.ship data
- :return: None
- '''
- ship_data = copy.deepcopy(gameData.ship[player][ship_name])
- ship_status = copy.deepcopy(ship_data[1:])
- if amount >= len(filter(None, ship_status)):
- destroyShip(player, ship_name)
- return
- while amount > 0:
- available_index = []
- for i in range(len(ship_status)):
- if ship_status[i] != False: available_index.append(i)
- # random pick an i to hit
- choice = random.choice(available_index)
- ship_status[choice] = False
- amount -= 1
- ship_data[1:] = ship_status
- gameData.ship[player][ship_name] = ship_data
- def dealDamageToCell(row,col,ui_player):
- '''
- mark the given cell hit and update ship_data
- check destroyship()
- :param row:
- :param col:
- :return:
- '''
- if gameData.board[row][col] == '':
- # attack missed
- gameData.UIOverLay[ui_player][row][col] = gameData.M0
- return False
- else:
- cell = readCellContent(gameData.board,row,col)
- # [player, shipName, orientation, posIndex, status]
- # update UI
- gameData.UIOverLay[ui_player][row][col] = gameData.H0
- # update ship status
- gameData.ship[cell[0]][cell[1]][int(cell[3])+1] = False
- # check if ship destoryed
- if all([each == False for each in gameData.ship[cell[0]][cell[1]][1:]]):
- print 'Last Hit!'
- destroyShip(cell[0], cell[1])
- return True
- def scanArea(player, row, col):
- '''
- reveal the 3*3 area for one turn
- '''
- rows = range(row-1,row+2)
- cols = range(col-1,col+2) # -1 col +1
- for each_row in rows:
- for each_col in cols:
- if 0<=each_row<gameData.row and 0<=each_col<gameData.col:
- gameData.revealed[player][each_row][each_col] = True
- # update UI Overlay
- enemy = 'p1' if gameData.currentPlayer== 'p2' else 'p2'
- ship_name = isRowColOnOwnShip(each_row,each_col,enemy)
- if ship_name != '':
- if (each_row, each_col) in \
- gameData.ship[enemy][ship_name]:
- # then cell is not damaged
- # print "Update UI at ", each_row,each_col
- gameData.UIOverLay[player][each_row][each_col]\
- = gameData.T0
- updateView(player)
- def action_scan(row, col, player):
- '''
- Handle scan event
- Also update UI and view
- '''
- if gameData.currentEnergy[player] < gameData.SCAN_MP[player]:
- #'Not enough energy'
- return
- scanArea(player, row, col)
- gameData.UIOverLay[player][row][col] = gameData.S0
- gameData.currentEnergy[player] -= gameData.SCAN_MP[player]
- updateView(player)
- def action_Fire(row, col, player):
- '''
- Handle one fire action and reduce mana
- '''
- if gameData.currentEnergy[player] < gameData.FIRE_MP[player]:
- #'Not enough energy'
- return
- dealDamageToCell(row,col,player)
- updateBoard()
- updateView('p1')
- updateView('p2')
- gameData.currentEnergy[player] -= gameData.FIRE_MP[player]
- def action_move(player, ship_name, drow, dcol):
- if gameData.currentEnergy[player] < gameData.MOVE_MP[player]:
- print 'Not enough energy', gameData.currentEnergy[player]
- return
- if moveShip(ship_name, player, drow, dcol):
- gameData.currentEnergy[player] -= gameData.MOVE_MP[player]
- def handleShipCollision(shipAData, newPos, oldPos,playerA,shipNameA):
- '''
- checks if the shipA(first parameter)'s newPos hits any other ship
- if so handle collision damage and updates affected ship
- :param shipAData: [ori, pos1,pos2,...]
- :param newPos: assumed new ship pos list
- :return: False if destroyed
- '''
- affectedShipPoints = []
- for row,col in newPos:
- if (row,col) in oldPos: continue
- # assume if cell not empty then must hit others
- if gameData.board[row][col] != '':
- decodedData = readCellContent(gameData.board, row, col)
- # [player, shipname, orientation, pos_index, status]
- affectedShipPoints.append(decodedData)
- if affectedShipPoints == []:
- return newPos
- # now we find all collided ships
- # make a ship list
- affectedShip = {each[0]+'_'+each[1] for each in affectedShipPoints}
- # [[player, ship], ...]
- affectedShip = [each.split('_') for each in affectedShip]
- att = len(filter(None, shipAData[1:])) # damaged ship part = False
- # find combined def strength
- defence = 0
- for each in affectedShip:
- # count non-False item in each ship's pos data
- defence += len(filter(None, gameData.ship[each[0]][each[1]][1:]))
- # handleShipCollsion
- if att == defence:
- destroyShip(playerA, shipNameA)
- # destroy all affected ships
- for each in affectedShip:
- destroyShip(each[0], each[1])
- return False # notify that shipA destroyed
- elif att > defence:
- randomlyTakeDamage(playerA, shipNameA, defence)
- for each in affectedShip:
- destroyShip(each[0], each[1])
- else: # att < defence
- destroyShip(playerA, shipNameA)
- for each in affectedShip:
- randomlyTakeDamage(each[0], each[1], att)
- return False
- return True
- def checkIfOKMove(oldPos, newPos):
- '''
- check if a move don't go out of boundary / run into own ships
- '''
- for row,col in newPos:
- # exclude previous self location
- if (row,col) in oldPos: continue
- if not 0<=row<gameData.row or not 0<=col<gameData.col:
- return False
- # now it's on board. check collision
- # check if going to hit own ship
- if isRowColOnOwnShip(row,col,gameData.currentPlayer)!='':
- return False
- return True
- def moveShip(ship_name, player, drow, dcol):
- '''
- move player's ship for drow / dcol with invalid position rejected
- and collision handle / win check
- update board and (shipImagePos)
- :return: bool: isMoveSuccessful
- '''
- if ship_name not in gameData.ship[player]: return
- ship_data = copy.deepcopy(gameData.ship[player][ship_name])
- oldPos = gameData.shipImagePos[player][ship_name]
- newPos = [(each[0]+drow,each[1]+dcol) for each in oldPos]
- if checkIfOKMove(oldPos, newPos):
- # checkCollision
- if handleShipCollision(ship_data,newPos,oldPos, player,ship_name):
- # if ship lives
- gameData.shipImagePos[player][ship_name] = newPos
- for i in range(1,len(gameData.ship[player][ship_name])):
- if gameData.ship[player][ship_name][i] != False:
- gameData.ship[player][ship_name][i] = newPos[i-1]
- # draw to board
- updateBoard()
- updateView(player)
- checkIfGameOver()
- return True
- return False
- def clearRevealedArea(player):
- '''
- clear gameData.revealed for that player
- '''
- gameData.revealed[player] = buildBoard(False)
- def getMouseBoardPosition(posx, posy):
- '''
- :return: the (row,col) on the board, else False
- '''
- boardPos = gameData.boardUI_TL_Pos
- size = gameData.boardUI_cellSize
- if not boardPos[0]<=posx<=boardPos[0] + gameData.col * size or\
- not boardPos[1]<=posy<=boardPos[1] + gameData.row * size:
- return False
- else:
- col = (posx - boardPos[0]) / size
- row = (posy - boardPos[1]) / size
- return (row,col)
- def isRowColOnOwnShip(row, col, player):
- '''
- return the ship name if the row col is on player's ship,
- empty string if not.
- '''
- cell_data = readCellContent(gameData.board, row,col)
- # [player, shipName, orientation, posIndex, status]
- if cell_data[0] == player:
- return cell_data[1]
- return ''
- def endTurn():
- '''
- end current player's turn and switch player's turn
- also update the data for next turn
- '''
- player = gameData.currentPlayer
- nextPlayer = 'p1' if player == 'p2' else 'p2'
- clearRevealedArea(player)
- updateView(player)
- updateView(nextPlayer)
- # update currentPlayer
- gameData.currentPlayer = nextPlayer
- gameData.currentEnergy[nextPlayer] = gameData.maxEnergy[nextPlayer]
- updateUIOverLay(nextPlayer)
- drawGameUI('p1')
- gameData.shipSelected = ''
- # pygame.time.wait(500)
- pygame.event.clear()
- def concede(player):
- '''
- Make the player concede and trigger game over
- '''
- enemy = 'p2' if player == 'p1' else 'p1'
- gameData.winner = enemy
- gameData.isGameOver = True
- def handleP1Events():
- for event in pygame.event.get():
- if event.type == QUIT: terminate()
- # events that's only available at players turn
- if gameData.currentPlayer == 'p1':
- # if currentMode == '':
- # # let player choose mode
- # checkForModeSelection(event)
- # print 'P1 EVENT CHECKING'
- # FIRST check buttons hit or not
- if event.type == MOUSEBUTTONUP\
- and gameData.btnConcede_Rect.collidepoint(event.pos):
- concede(gameData.currentPlayer)
- elif event.type == MOUSEBUTTONUP \
- and gameData.btnNext_Rect.collidepoint(event.pos):
- endTurn()
- continue
- if event.type == KEYDOWN:
- if event.key == K_n:
- # another way to end turn
- endTurn()
- continue
- # handle move
- if gameData.shipSelected != '':
- if event.key in (K_LEFT, K_a):
- action_move('p1', gameData.shipSelected, 0, -1)
- elif event.key in (K_RIGHT, K_d):
- action_move('p1', gameData.shipSelected, 0, 1)
- elif event.key in (K_UP, K_w):
- action_move('p1', gameData.shipSelected, -1, 0)
- elif event.key in (K_DOWN, K_s):
- action_move('p1', gameData.shipSelected, 1, 0)
- if event.type == MOUSEBUTTONUP:
- pos = getMouseBoardPosition(event.pos[0], event.pos[1])
- if pos == False: continue
- if event.button == 1:
- # -------first check ship selection-------
- clicked_ship = isRowColOnOwnShip(pos[0], pos[1], 'p1')
- # returned ship name
- if clicked_ship != '':
- # Ship selected
- gameData.shipSelected = clicked_ship
- continue
- # -------then fire action-------
- if pos != False:
- action_Fire(pos[0], pos[1], gameData.currentPlayer)
- elif event.button == 3: # right button
- action_scan(pos[0],pos[1], gameData.currentPlayer)
- def AI_updateTargets():
- gameData.targets_pos = []
- for row in range(len(gameData.UIOverLay['p2'])):
- for col in range(len(gameData.UIOverLay['p2'][0])):
- if gameData.UIOverLay['p2'][row][col] == gameData.T0 or \
- gameData.UIOverLay['p2'][row][col] == gameData.T1:
- gameData.targets_pos.append((row,col))
- def AI_turn():
- '''
- this handles all AI action
- '''
- if gameData.currentEnergy['p2'] >= min(gameData.FIRE_MP['p2'],
- gameData.MOVE_MP['p2'],
- gameData.SCAN_MP['p2'],):
- rand = random.randint(0,100)
- if 0<=rand<60:
- # do fire action
- f_rand = random.randint(0, 100)
- if 0<=f_rand<30 and len(gameData.targets_pos) == 0:
- # do scan
- pos = (random.randint(1,gameData.row-2),
- random.randint(1,gameData.col-2))
- action_scan(pos[0],pos[1], 'p2')
- AI_updateTargets()
- print gameData.targets_pos
- else:
- if len(gameData.targets_pos) > 0:
- if len(gameData.targets_pos) == 1:
- fireIndex = 0
- else:
- # multiple target
- fireIndex = random.randint(0,
- len(gameData.targets_pos)-1)
- firePos = gameData.targets_pos[fireIndex]
- action_Fire(firePos[0], firePos[1], 'p2')
- # print 'Targetted', gameData.currentEnergy['p2'], firePos
- gameData.targets_pos.pop(fireIndex)
- gameData.UIOverLay['p2'][firePos[0]][firePos[1]] = ''
- else:
- firePos = (0,0)
- while True: # get's the correct location
- firePos = (random.randint(0,gameData.row-1),
- random.randint(0, gameData.col-1))
- data = readCellContent(gameData.view['p2'],
- firePos[0],firePos[1])
- # can't hit itself
- if data[0] != 'p2': break
- action_Fire(firePos[0], firePos[1], 'p2')
- print 'Random', gameData.currentEnergy['p2'], firePos
- else:
- # do move action
- selectedShip_name = random.choice(gameData.ship['p2'].keys())
- dir = random.choice([(0,-1),(0,1),(1,0),(1,0),(1,0),])
- action_move('p2', selectedShip_name, dir[0], dir[1])
- else:
- endTurn()
- return
- def showGameOverScreen():
- '''
- this shows the game over screen
- '''
- # draw bg
- DISPLAYSURF.blit(gameData.IMAGESDICT['win_BG'],gameData.win_bg_pos)
- # draw texts
- txt_first, txt_firstRect = \
- makeTextObjs('Game Over', gameData.FONT36, colors.WHITE)
- txt_firstRect.center = gameData.win_txt1_pos
- DISPLAYSURF.blit(txt_first, txt_firstRect)
- second = 'You WIN!' if gameData.winner == 'p1' else 'AI WIN!'
- txt_second, txt_secondRect = \
- makeTextObjs(second, gameData.FONT36, colors.WHITE)
- txt_secondRect.center = gameData.win_txt2_pos
- DISPLAYSURF.blit(txt_second, txt_secondRect)
- txt_third, txt_thirdRect = \
- makeTextObjs("Press any key to continue"
- , gameData.BASICFONT, colors.WHITE)
- txt_thirdRect.center = gameData.win_txt3_pos
- DISPLAYSURF.blit(txt_third, txt_thirdRect)
- def waitForKeyPressed(key=None):
- '''
- pause and wait for any key input.
- If key is not given, use all keys
- '''
- while True:
- for event in pygame.event.get():
- if event.type == QUIT: terminate()
- if event.type == KEYDOWN:
- if key != None:
- if event.key == key:
- return
- else:
- return
- pygame.display.update()
- FPSCLOCK.tick(gameData.FPS)
- def runGame():
- '''
- This is the main function for actual game part
- '''
- gameInit()
- setShipLocation()
- updateView('p1')
- updateView('p2')
- drawGameUI('p1')
- # Game States
- # choices are: "move"
- # currentMode = ''
- # Main Game Loop
- while True:
- updateView('p1')
- updateView('p2')
- drawGameUI('p1')
- if gameData.isGameOver == True:
- print 'Game Over', gameData.winner
- showGameOverScreen()
- waitForKeyPressed()
- return
- # game Continues
- elif gameData.currentPlayer == 'p2':
- # AI's move
- pygame.time.wait(500)
- AI_turn()
- else:
- # player's turn
- handleP1Events()
- # check if no energy
- if gameData.currentEnergy['p1'] < min(gameData.FIRE_MP['p1'],
- gameData.MOVE_MP['p1'],
- gameData.SCAN_MP['p1'], ):
- pygame.time.wait(500)
- endTurn()
- pygame.display.update()
- FPSCLOCK.tick(gameData.FPS)
- def gameInit_UIOverLay():
- '''
- define UI OverLay elements
- D0 = damaged this turn
- D1 = damaged last turn etc.
- H = HIT
- M = MISSED
- S = SCANNED
- T = TARGET
- '''
- gameData.UIOverLay = {'p1': copy.deepcopy(gameData.board),
- 'p2': copy.deepcopy(gameData.board)}
- gameData.D0 = 'd0'
- gameData.D1 = 'd1'
- gameData.H0 = 'h0'
- gameData.H1 = 'h1'
- gameData.M0 = 'm0'
- gameData.M1 = 'm1'
- gameData.S0 = 's0'
- gameData.S1 = 's1'
- gameData.T0 = 't0'
- gameData.T1 = 't1'
- def init_images():
- # Images
- gameData.IMAGESDICT = {
- 'game_BG': pygame.image.load('data/game_BG.png'),
- 'win_BG': pygame.image.load('data/win_bg.fw.png'),
- 'scan1': pygame.image.load('data/scan1.fw.png'),
- 'fire1': pygame.image.load('data/fire1.fw.png'),
- 'move1': pygame.image.load('data/move1.fw.png'),
- 'locked': pygame.image.load('data/locked.png'),
- 'carrier': pygame.image.load('data/carrier.fw.png'),
- 'battleship': pygame.image.load('data/battleship.fw.png'),
- 'cruiser': pygame.image.load('data/cruiser.fw.png'),
- 'destroyer': pygame.image.load('data/destroyer.fw.png'),
- 'submarine': pygame.image.load('data/submarine.fw.png'),
- 'm0': pygame.image.load('data/ui_m.fw.png'),
- 'h0': pygame.image.load('data/ui_h.fw.png'),
- 'd0': pygame.image.load('data/ui_d.fw.png'),
- 's0': pygame.image.load('data/ui_s.fw.png'),
- 't0': pygame.image.load('data/ui_t.fw.png'),
- 'm1': pygame.image.load('data/ui_m1.fw.png'),
- 'h1': pygame.image.load('data/ui_h1.fw.png'),
- 'd1': pygame.image.load('data/ui_d1.fw.png'),
- 's1': pygame.image.load('data/ui_s1.fw.png'),
- 't1': pygame.image.load('data/ui_t1.fw.png'),
- }
- def gameInit():
- # init_images
- init_images()
- # used to set up a game!
- gameData.board = buildBoard()
- gameData.view = {'p1': copy.deepcopy(gameData.board),
- 'p2': copy.deepcopy(gameData.board)}
- # noinspection PyTypeChecker
- gameData.revealed = {'p1': buildBoard(False),'p2': buildBoard(False)}
- # define game controllor variable
- gameData.isGameOver = False
- gameData.winner = 'p1'
- gameData.shipCount = {'p1': gameData.STARTSHIPCOUNT,
- 'p2': gameData.STARTSHIPCOUNT}
- gameData.shipSelected = ''
- # define game states
- gameData.currentPlayer = 'p1'
- # define resources
- gameData.skill = {'p1':['fire1','scan1','move1','',''],
- 'p2':['fire1','scan1','move1','','']}
- gameData.maxEnergy = {'p1': gameData.START_ENERGY,
- 'p2': gameData.START_ENERGY}
- gameData.currentEnergy = {'p1': gameData.maxEnergy['p1'],
- 'p2': gameData.maxEnergy['p2']}
- # set up initial MP
- gameData.FIRE_MP = {'p1': 2,'p2': 2}
- gameData.MOVE_MP = {'p1': 2,'p2': 2}
- gameData.SCAN_MP = {'p1': 3,'p2': 3}
- # define UI elements
- gameInit_UIOverLay()
- gameData.txt_skillDescription_D = ['Left Click to Fire',
- 'Right Click to Scan',
- 'Click ship to Move']
- gameData.txt_skillDescription = \
- copy.deepcopy(gameData.txt_skillDescription_D)
- gameData.txt_box_D = ['Ship Remain: 5-5']
- gameData.txt_box = \
- copy.deepcopy(gameData.txt_box_D)
- gameData.txt_EB = 'Your Turn'
- # will be updated immediately after
- gameData.ship = {'p1':{},
- 'p2':{}}
- gameData.shipImagePos = {'p1':{},
- 'p2':{}}
- # for AI use
- gameData.targets_pos = []
- def init_ships():
- '''
- used to generate ship modal for games
- '''
- gameData.HORIZONTAL = 'h'
- gameData.VERTICAL = 'v'
- gameData.STATUS_HIT = 'h'
- gameData.STATUS_NORMAL = 'n'
- # capitals in gameData is ship name
- gameData.CARRIER = 'carrier'
- gameData.BATTLESHIP = 'battleship'
- gameData.CRUISER = 'cruiser'
- gameData.DESTROYER = 'destroyer'
- gameData.SUBMARINE = 'submarine'
- # ships store the actual design
- gameData.ships = dict()
- gameData.ships[gameData.CARRIER] = [True] * 5
- gameData.ships[gameData.BATTLESHIP] = [True] * 4
- gameData.ships[gameData.CRUISER] = [True] * 3
- gameData.ships[gameData.DESTROYER] = [True] * 3
- gameData.ships[gameData.SUBMARINE] = [True] * 2
- gameData.STARTSHIPCOUNT = len(gameData.ships)
- def init():
- '''
- initialize all variable before game starts
- '''
- # essentials
- gameData.FPS = 30
- pygame.display.set_caption('BattleShip')
- # game settings
- gameData.START_ENERGY = 6
- gameData.MAX_ENERGY = 8
- # board
- gameData.row = 14
- gameData.col = 10
- gameData.boardUI_cellSize = 36
- gameData.boardUI_TL_Pos = (397, 48)
- # UIs
- gameData.showHelp = False
- gameData.BG_Pos = (-45,-50,921,679)
- gameData.EB_Pos = (39,40,324,61)
- gameData.Skills_Pos = (40,128,323,201)
- gameData.Skills_txt_Pos = (193,223)
- gameData.Box_txt_Pos = (201,381)
- gameData.Box_Pos = (40,349,323,211)
- gameData.btnConcede_Rect = pygame.Rect(71,494,120,48)
- gameData.btnNext_Rect = pygame.Rect(212,494,120,48)
- gameData.win_bg_pos = (0,124)
- gameData.win_txt1_pos = (524,210)
- gameData.win_txt2_pos = (524,275)
- gameData.win_txt3_pos = (580,442)
- # Skill Boxes
- gameData.skillBox = [(57,141,50,50),
- (117,141,50,50),
- (177,141,50,50),
- (237,141,50,50),
- (297,141,50,50)]
- # Skill Names
- gameData.blankSkillName = 'locked'
- # ships
- init_ships()
- # fonts
- gameData.BASICFONT = pygame.font.Font('freesansbold.ttf', 18)
- gameData.FONT36 = pygame.font.SysFont('Arial', 36)
- # colors
- colors.WHITE = (255,255,255)
- colors.BLACK = (0,0,0)
- colors.RED = (255,0,0)
- colors.GREEN = (15, 255, 118)
- colors.EMPTY_CELL = (225, 245, 254) # pale blue
- colors.P1_SHIP_BG = (45, 80, 133)
- colors.P2_SHIP_BG = (172, 57, 57)
- colors.DEEP_BLUE = (40, 53, 147)
- colors.CELL_BOARDERLINE = (40, 53, 147)
- colors.BG_BLUE = (144, 202, 249)
- colors.UI_BOARDER = (129, 136, 152)
- colors.EB_BG = (249, 236, 209)
- colors.EB_BG2 = (160, 28, 28)
- colors.EB_BOARDER = (129, 136, 152)
- colors.UI_BG = (232, 234, 246)
- def main():
- global gameData, colors, DISPLAYSURF, FPSCLOCK
- gameData = Struct()
- colors = Struct()
- pygame.init()
- gameData.windowWidth = 800
- gameData.windowHeight = 600
- FPSCLOCK = pygame.time.Clock()
- DISPLAYSURF = pygame.display.set_mode((gameData.windowWidth,
- gameData.windowHeight))
- init()
- # Main Scene Loop
- while True:
- # start 'scenes' in the game
- runStartScreen()
- runGame()
- # ---------------- RUN ----------------
- if __name__ == '__main__':
- main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement