![](./images/gp1.png)
--[[
GP1ASSIGNMENT1
Owen Canavan
D13125137
--]]
--define grid variables
gridWidth = 9
gridHeight = 14
tileSize = 25 --sizes need for default corona iPhone 4 screen
gap = 5
--tile data
tiles = {}
inGame = false
--end game ui container
weGotAPotentialKillScreenComingUp = {}
--hit tracking
missCount = 0 --total misses
missStreak = 0 --misses in a row
hitCount = 0 --total hits
hitCounts = {} --hits per ship
score = 0 --could be local
MAX_SCORE = 1000000
--taunt strings
FEATURE_CREEP = true --enable extra feedback messages
taunts ={}
taunts.hit = {"ouch!",
"HEY! watch the paint-job!",
"why...*sniff*...why?!",
"That hurt",
"I'm beginning to think you don't like me"
}
taunts.sunk = {"Abandon ship!",
"Man the life rafts!",
"I only just paid for that one...",
"I didn't really need that one anyway"}
taunts.miss = {"HAW HAW!",
"missed me!",
"Are you blind?",
"miles away",
"okay... that one was a bit too close",
"colder...colder",
"my blind granny can shoot better than you!",
"come on, we haven't got all day",
"someday they'll make a movie about this",
"oh just give up already!"
}
--remove device status bar
display.setStatusBar(display.HiddenStatusBar)
--[[
FUNCTION
name: init
arguments: none
description: initial setup for new game, called on app start or when 'Play Again' tapped
--]]
function init()
--check to see if there is existing display objects, if so remove them
cleanupGrid()
--find grid top left position for grid; grid_x, grid_y
screen_centre_x = display.contentWidth/2
screen_centre_y = display.contentHeight/2
total_grid_width = (gridWidth * tileSize) + (gap * (gridWidth-1))
total_grid_height = (gridHeight * tileSize) + (gap * (gridHeight-1))
grid_x = screen_centre_x - total_grid_width/2
grid_y = screen_centre_y - total_grid_height/2
grid_x = grid_x + tileSize/2
grid_y = grid_y + tileSize/2
--populate 2d array with rects
tiles = {}
for i=1,gridWidth,1 do
--new column
tiles[i] = {}
for j=1,gridHeight,1 do
--new row
--draw new tile to screen, position from top left
newTile = display.newRect(0, 0, tileSize, tileSize)
--I originally set x and y in the newRect call, as I prefer working from top left, but after updating to 2.0 that now also works from centre
--so setting it here for compatibility
newTile.x = grid_x + ((tileSize + gap) * (i - 1))
newTile.y = grid_y + ((tileSize + gap) * (j - 1))
--no ship by default
newTile.ship = false
--tap event
newTile:addEventListener("tap",tappedRect)
tiles[i][j] = newTile
end
end
--add four ships, size 2,3,4,5
for s=2,5,1 do
addShip(s)
end
--yes we are playing
inGame = true
--set defaults
score = 0
missCount = 0
hitCount = 0
hitCounts = {0,0,0,0,0}
missStreak = 0
--extra feature: text feedback
--position text below grid
taunts.text = display.newText({
x=display.contentWidth/2,
y=display.contentHeight - 16,
text="",
fontSize=16,
font=native.systemFontBold,
align="center"
})
end
--[[
FUNCTION
name: addShip
arguments: shipSize: int for length of ship
description: add a new ship, of a set length, to the grid, without overlapping on existing ships
--]]
function addShip(shipSize)
--[[
--debug ship colours
colors = {}
colors[2] = {r=255,g=0,b=255}
colors[3] = {r=0,g=255,b=255}
colors[4] = {r=0,g=127,b=255}
colors[5] = {r=255,g=255,b=0}
--]]
--determine if this ship will be placed vertical (1) or horizontal(2)
vertOrHoriz = math.random(1,2)
--directional controls. makeshift ternary
xo = vertOrHoriz == 2 and 1 or 0
yo = vertOrHoriz == 1 and 1 or 0
--loop until we have completed the ship
while(true) do
--find a random tile for first ship tile, subtract additional ship size from search range in appropriate direction
tile_x = math.random(1,gridWidth - ((shipSize - 1)*xo))
tile_y = math.random(1,gridHeight - ((shipSize - 1)*yo))
--temp holder for valid tiles
tempTiles = {}
for i=1,shipSize,1 do
--search out along ships length to see if all tiles are open for placement
if tiles[tile_x+((i-1)*xo)] and tiles[tile_x+((i-1)*xo)][tile_y+((i-1)*yo)] then
--still looking at a valid tile on the grid
if tiles[tile_x+((i-1)*xo)][tile_y+((i-1)*yo)].ship then
--there is already a ship here, invalid position for a new ship, break out start while loop again
break
else
--so far so good
--valid tile, store it in temp holder for later
tempTiles[i] = tiles[tile_x+((i-1)*xo)][tile_y+((i-1)*yo)]
if i == shipSize then
--counted through all needed space and found no ships, good to go
--loop back through temp tile holder and assign ship positions
for t = 1,#tempTiles,1 do
--this tile is indeed a bit of a ship
tempTiles[t].ship = true
--ohh shiney! brand new, never been hit... honest!
tempTiles[t].hit = false
--which ship does this bit belong to?
tempTiles[t].shipId = shipSize
--debug shipId
--tempTiles[t]:setFillColor(colors[shipSize].r,colors[shipSize].g,colors[shipSize].b)
end
--full ship has been set, don't need this function any more, return to init()
return
end
end
else
--sanity check, something went really wrong if we got here, break out and start while loop again. although it might be safer to return instead to avoid infinite loop since really-wrong-thing might happen again
break
end
end
end
end
--[[
FUNCTION
name: tappedRect
arguments: event: system tap event
description: user tapped/clicked on a rect, provide feedback as to whether or not it hit a ship or missed. if all ships sunk, win/end game.
--]]
function tappedRect(event)
--sanity check that we are still playing the game and so can only process valid shots
if inGame then
--default feedback message and message colour
msg = ''
msg_color = {r=255,g=255,b=255}
--did we hit a ship?
if event.target.ship then
--yes we did!
--did we already shoot at this tile?
if not event.target.hit then
--no, brand new shot
event.target.hit = true
--increment total valid hits
hitCount = hitCount + 1
--increment total this for this ship
hitCounts[event.target.shipId] = hitCounts[event.target.shipId] + 1
--hit something, so reset miss streak
missStreak = 0
--have we sunk a complete ship?
if hitCounts[event.target.shipId] == event.target.shipId then
--ship sunk
--was this the battleship (ship size 4)?
if event.target.shipId == 4 then
--YES!!
--print("you sunk my battleship!")
msg = "You sunk my battleship!"
else
--no... something else useless like an aircraft carrier or sub
--pick a random taunt/response
msg = taunts.sunk[math.random(1,#taunts.sunk)]
end
--red text for a sunk ship!
msg_color = {r=255,g=0,b=0}
else
--no, just a partial hit
--40% chance of a random taunt/response
if math.random(1,100) > 60 then
msg = taunts.hit[math.random(1,#taunts.hit)]
end
end
--have hit all there is to hit?
if hitCount >= 14 then
--yes!
--gameOver: winner
gameOver()
end
--debug, colour hits by ship type
--event.target:setFillColor(255 - (16 * event.target.shipId),0,0)
--this tile is hit, colour it red
event.target:setFillColor(255,0,0)
end
else
--no we didn't!
--did we already shoot at this tile?
if not event.target.hit then
--no, brand new shot
event.target.hit = true
--increment miss count for scoring purposes
missCount = missCount + 1
--increment miss streak for extra snark
missStreak = missStreak + 1
--10% chance of random miss taunt, 50% if missed 10 times in a row. hacky ternary is hacky
tauntRate = missStreak > 10 and 50 or 90
if math.random(1,100) > tauntRate then
--taunt
msg = taunts.miss[math.random(1,#taunts.miss)]
end
--this tile is a miss, colour it green
event.target:setFillColor(0,255,0)
end
end
--should we actually use our extra feedback feature?
if FEATURE_CREEP then
shout(msg, msg_color)
end
end
end
--[[
FUNCTION
name: shout
arguments: msg: string of text to be displayed
color: table of RGB values to colour text
description: display a feedback response in the text ui element below the grid
category: FEATURE CREEP!
--]]
function shout(msg, color)
if FEATURE_CREEP then
--sanity check the the ui element exists
if taunts and taunts.text then
if msg == '' then
--no message was set, just ensure the ui is hidden so it doesn't display it's previous state
taunts.text.isVisible = false
else
--we got a message
--reset the text to new value
taunts.text.text = msg
--reset the colour to new RGB values
--I've tried this in both ver 1 and 2.0, so it should work, if not comment out this block
---[[
if taunts.text.setFillColor then
--corona v2.0
taunts.text:setFillColor(color.r/255,color.g/255,color.b/255)
elseif taunts.text.setTextColor then
--corona v1
taunts.text:setTextColor(color.r,color.g,color.b)
end
--]]
--ensure the ui element is displaying
taunts.text.isVisible = true
end
end
end
end
--[[
FUNCTION
name: gaveOver
arguments: none
description: remove the grid, display end game message and Play Again button
--]]
function gameOver()
--remove the grid
cleanupGrid()
--enable paranoid mode
inGame = false
--calculate the score
-- win with no misses, 1,000,000 points
-- win by hitting every tile, 0 points
-- using floor only in case MAX_SCORE were a lot smaller
score = math.floor(MAX_SCORE * (1 - (missCount / ((gridWidth * gridHeight) - 14))))
--display win message, score and play again button
weGotAPotentialKillScreenComingUp = {}
weGotAPotentialKillScreenComingUp.bg = display.newRoundedRect(0,0, display.contentWidth, display.contentHeight/4,32)
weGotAPotentialKillScreenComingUp.bg.x = display.contentWidth/2
weGotAPotentialKillScreenComingUp.bg.y = display.contentHeight/2 --would've done it in the function call, but again version differences...
weGotAPotentialKillScreenComingUp.bg:setFillColor(224,0,0) --colour will vary depending on version. not worth checking here
weGotAPotentialKillScreenComingUp.youWin = display.newText({
x=display.contentWidth/2,
y=display.contentHeight/2 - 25,
text="You Win!",
fontSize=48,
font=native.systemFontBold,
align="center"
})
weGotAPotentialKillScreenComingUp.score = display.newText({
x=display.contentWidth/2,
y=display.contentHeight/2 + 8,
text="Your score: "..score,
fontSize=16,
font=native.systemFontBold,
align="center"
})
weGotAPotentialKillScreenComingUp.playAgain = display.newText({
x=display.contentWidth/2,
y=display.contentHeight/2 + 32,
text="[Play Again]",
fontSize=32,
font=native.systemFontBold,
align="center"
})
weGotAPotentialKillScreenComingUp.playAgain:addEventListener("tap",playAgain)
end
function playAgain()
--clean up the win screen ui. could've been in a parent to make it easier, but just being paranoid
weGotAPotentialKillScreenComingUp.bg:removeSelf()
weGotAPotentialKillScreenComingUp.youWin:removeSelf()
weGotAPotentialKillScreenComingUp.score:removeSelf()
weGotAPotentialKillScreenComingUp.playAgain:removeEventListener("tap",playAgain)
weGotAPotentialKillScreenComingUp.playAgain:removeSelf()
--build new game
init()
end
--[[
FUNCTION
name: cleanupGrid
arguments: none
description: clean up old data
--]]
function cleanupGrid()
--loop through old data and remove event listeners and display objects if they exist
if #tiles > 0 and #tiles[1] > 0 then
--old data clean it up
for i=1,#tiles,1 do
for j=1,#tiles[i],1 do
rect = tiles[i][j]
rect:removeEventListener("tap",tappedRect)
rect:removeSelf()
end
end
end
--reassign clean table
tiles = {}
--remove the feedback text ui because init() makes a new one
if taunts and taunts.text then
taunts.text:removeSelf()
taunts.text = nil
end
end
--first game starts here
init()
|