├── assets ├── ss1.png ├── ss2.png └── basis33.ttf ├── keyboard.lua ├── LICENSE ├── todo.md ├── README.md ├── globals.lua ├── touch.lua ├── shoot.lua ├── ui.lua ├── main.lua └── logic.lua /assets/ss1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whyboris/Gravity-Wars/HEAD/assets/ss1.png -------------------------------------------------------------------------------- /assets/ss2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whyboris/Gravity-Wars/HEAD/assets/ss2.png -------------------------------------------------------------------------------- /assets/basis33.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whyboris/Gravity-Wars/HEAD/assets/basis33.ttf -------------------------------------------------------------------------------- /keyboard.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Code related to keyboard interactions 3 | ---------------------------------------------------------------------------------------------------- 4 | function love.keypressed(key) 5 | 6 | print(key) 7 | 8 | if key == 'right' then 9 | keyRight = true 10 | end 11 | 12 | if key == 'left' then 13 | keyLeft = true 14 | end 15 | 16 | if key == 'up' then 17 | keyUp = true 18 | 19 | player1.force = player1.force * 1.01 20 | 21 | end 22 | 23 | if key == 'down' then 24 | keyDown = true 25 | end 26 | 27 | if key == 'x' then 28 | explode(400, 300) 29 | end 30 | 31 | if key == 'n' then 32 | newGame() 33 | end 34 | 35 | if key == 'space' then 36 | playerPressedShootButton() 37 | end 38 | 39 | if key == 'escape' then 40 | os.exit() 41 | end 42 | 43 | end 44 | 45 | function love.keyreleased(key) 46 | 47 | keyRight = false 48 | keyLeft = false 49 | keyUp = false 50 | keyDown = false 51 | 52 | end 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Boris - Gravity Wars 4 | 5 | Copyright (c) 2004, 2005 Tristan Grimmer - basis33.ttf 6 | Copyright (c) 2014 Manchson - basis33.ttf 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | Need to implement: 4 | 5 | - Improve UI 6 | - Every draw method should start with its own `setColor` 7 | - Create a helper method for `setColor` 8 | - Render all shot trails to one canvas, render ships, planets, and UI to another canvas. Update independently as needed 9 | - better code to prevent planet-planet & planet-ship overlap 10 | - randomize map again if total net force on ship is too high (pick some sensible cutoff) 11 | 12 | ### Shot types 13 | 14 | It may be fun to have a variety of shot types that can be used. Here are some ideas. 15 | 16 | - single shot 17 | - tripple (narrow) 18 | - tripple (wide) 19 | - four corners (shoot 4 bullets at 90 degrees to each other) 20 | - octoshot (shoot 8 bullets at 45 degrees to each other) 21 | - planet exploder (makes a planet disappear) 22 | - tracers (many in all directions, expire after some time, can not kill) 23 | 24 | Multi shots 25 | 26 | _press the 'fire' button again to trigger the further effect_ 27 | 28 | - teleport (_fire_: teleport to where the bullet is) 29 | - split 3 x1 (_fire_: split into 3) 30 | - split 2 x2 (_fire_: split into 2, then 4) 31 | - split 3 x2 (_fire_: split into 3; then 9) 32 | - split 2 x3 (_fire_: split into 2, then 4, then 8) 33 | - firework (explode after some period of time) 34 | 35 | Not shots but would be used instead of firing a shot 36 | 37 | - teleport to a random location 38 | - spacequake (moves all planets randomly a bit) 39 | - draw vector field (overlay a grid of benign bullets and make them draw for 3 iterations) 40 | - summon asteroids 41 | - quitter (starts new map, but opponent has 3 shots before you) 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gravity Wars 2 | 3 | *Gravity Wars* is an [artillery game](https://en.wikipedia.org/wiki/Artillery_game) like [_Scorched Earth_](https://en.wikipedia.org/wiki/Scorched_Earth_(video_game)) or [_Worms_](https://en.wikipedia.org/wiki/Worms_(series)) but with many planets affecting the path of the projectile. 4 | 5 | ![gravitywars](https://user-images.githubusercontent.com/17264277/87047616-be62fe00-c1c8-11ea-9395-70a5d344923c.png) 6 | 7 | ## Background 8 | 9 | _Gravity Wars_ is not my original idea. In 2007 I played a 1992 MAC version of this game with my friend and thought it was fun. From my brief correspondence with the creator (Rhys Hollow) of the version for Macintosh I learned the original game was written for Amiga (and not by him). In 2009 I learned how to code to create a clone of the game: https://yboris.dev/gravitywars 10 | 11 | In 2015 I tried to re-create the game for the iPad using [Codea](https://codea.io/) but lost interest. I finally got around to sharing my code for it in [2019](https://codea.io/talk/discussion/9563/gravity-wars-giving-away-my-code-on-unfinished-game). 12 | 13 | In 2020 a co-worker shared [LÖVE](https://love2d.org/) (aka _Love2D_) which, like Codea, uses the *Lua* programming language. I decided to port over my code to _LÖVE_ so as to share this fun game with more people. 14 | 15 | The initial commit code is a very quick and dirty port of my 2015 prototype code, which in turn is a quick and dirty port of my 2009 self-taught code. In subsequent commits I improved the code somewhat, but I wouldn't say the current codebase is a good example of how to code well. 16 | 17 | ## Development 18 | 19 | 1. Install [LÖVE](https://love2d.org/) (game confirmed to work with v11.3) 20 | 2. In your terminal run `love .` 21 | 22 | ## License 23 | 24 | Please feel free to use this code (in full or in part) however you'd like, under the permissive _MIT_ license. 25 | 26 | Thank you to Manchson for the _MIT_ licensed [`basis33.ttf`](https://github.com/Manchson/basis33) font. 27 | -------------------------------------------------------------------------------- /globals.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Code setting up all the global variables to be used 3 | ---------------------------------------------------------------------------------------------------- 4 | function loadExternalAssets() 5 | 6 | ss1 = love.graphics.newImage("assets/ss1.png") 7 | ss2 = love.graphics.newImage("assets/ss2.png") 8 | pixelFont = love.graphics.newFont("assets/basis33.ttf", 16) 9 | 10 | end 11 | 12 | function setVariables() 13 | 14 | uiFont = love.graphics.getFont() -- default font 15 | 16 | dragCursor = love.mouse.getSystemCursor("sizewe") 17 | 18 | WIDTH = 1000 19 | HEIGHT = 800 20 | 21 | -- Mouse click interactions 22 | dragging = false 23 | draggingType = nil -- `force` or `angle` 24 | mouseXinitial = 0 25 | mouseXcurrent = 0 26 | 27 | -- FUN kaleidoscope time! 28 | -- colorMode = 1 29 | 30 | -- bullet type (should be changeable by player) 31 | bulType = 1 32 | 33 | -- set number of bullets 34 | numOfBullets = 10 35 | 36 | -- set number of Planets 37 | numOfPlanets = 7 38 | 39 | allPlanets = {} -- each planet will have `mass`, `r`, `x`, `y` 40 | 41 | allBullets = {} -- list of bullets, each will have `x`, `y`, and `vx`, `vy` (velocity x & y components) 42 | 43 | player1 = { 44 | x = 100, 45 | y = 100, 46 | angle = 0, 47 | force = 1.5, 48 | lives = 3, 49 | health = 100, 50 | lastAngle = nil, 51 | lastForce = nil, 52 | } 53 | 54 | player2 = { 55 | x = 500, 56 | y = 500, 57 | angle = 180, 58 | force = 1.5, 59 | lives = 3, 60 | health = 100, 61 | lastAngle = nil, 62 | lastForce = nil, 63 | } 64 | 65 | -- whose turn 66 | turn = 1 67 | 68 | -- used to end the turn sometime 69 | shotInProgress = false 70 | 71 | -- integer to provide time-out so your bullet doesn't kill you in the first 50 iterations 72 | -- benign while it's less than 50 iterations of bullet flight, for example (see collision check) 73 | benign = 0 74 | 75 | -- if end of round = 1 it restarts the game after all the bullets end their path 76 | endOfRound = false 77 | 78 | -- kaleidoscope mode colors for shot trails 79 | rainbow = { 80 | r = 1, 81 | g = 1, 82 | b = 1 83 | } 84 | 85 | end 86 | -------------------------------------------------------------------------------- /touch.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Code related to touch interactions (from Codea, not yet updated for Love2D) 3 | ---------------------------------------------------------------------------------------------------- 4 | -- toggle booleans if clicked in areas where button / sliders are 5 | function love.mousepressed(x, y, button) 6 | 7 | -- print(x) 8 | -- print(y) 9 | 10 | --[[ fun times 11 | if shotInProgress == true then 12 | explode(x,y) 13 | end 14 | ]] -- 15 | 16 | -- rectangle on bottom-right to detect pressing shoot button 17 | if shotInProgress == false and (x > (WIDTH - 100) and y > HEIGHT - 100) then 18 | playerPressedShootButton() 19 | end 20 | 21 | if shotInProgress == false then 22 | clickNearShip = nearShip(x, y, currentPlayer()) 23 | 24 | if clickNearShip == 'force' then 25 | mouseXinitial = x 26 | draggingType = 'force' 27 | dragging = true 28 | love.mouse.setCursor(dragCursor) 29 | elseif clickNearShip == 'angle' then 30 | mouseXinitial = x 31 | draggingType = 'angle' 32 | dragging = true 33 | love.mouse.setCursor(dragCursor) 34 | end 35 | 36 | end 37 | 38 | end 39 | 40 | -- returns 'force', 'angle', 'no' 41 | function nearShip(x, y, playerN) 42 | 43 | forceOffset = 100 * playerN.force / 5 44 | 45 | distance = math.pow(math.pow((playerN.x - x), 2) + math.pow((playerN.y - y), 2), 0.5) 46 | 47 | if distance < clamp(forceOffset, 10, 90) then 48 | return 'force' 49 | elseif distance < 100 then 50 | return 'angle' 51 | else 52 | return 'no' 53 | end 54 | 55 | end 56 | 57 | 58 | -- short circuit the love.update function with boolean 59 | function love.mousereleased(x, y, button) 60 | dragging = false 61 | love.mouse.setCursor() 62 | end 63 | 64 | -- if the mouse is being dragged after clicking, update the values of force or angle 65 | -- uses distance from initial click for smoothly 66 | function love.update(dt) 67 | if dragging then 68 | 69 | mouseXcurrent = love.mouse.getX() 70 | 71 | if mouseXinitial ~= mouseXcurrent then 72 | 73 | diff = mouseXcurrent - mouseXinitial 74 | 75 | if draggingType == 'angle' then 76 | 77 | if turn == 1 then 78 | player1.angle = getAngle(player1, diff) 79 | elseif turn == 2 then 80 | player2.angle = getAngle(player2, diff) 81 | end 82 | 83 | elseif draggingType == 'force' then 84 | 85 | if turn == 1 then 86 | player1.force = getForce(player1, diff) 87 | elseif turn == 2 then 88 | player2.force = getForce(player2, diff) 89 | end 90 | 91 | end 92 | 93 | love.graphics.setCanvas(canvas) 94 | drawUI() 95 | love.graphics.setCanvas() 96 | 97 | end 98 | end 99 | 100 | -- if shotInProgress == false then 101 | -- if keyUp then 102 | -- player1.force = player1.force * 1.01 103 | -- end 104 | -- end 105 | end 106 | 107 | function getForce(playerN, diff) 108 | 109 | return clamp(playerN.force + clamp(math.pow(diff / 1000, 3), -0.02, 0.02), 0, 5) 110 | 111 | end 112 | 113 | function getAngle(playerN, diff) 114 | 115 | angle = playerN.angle + clamp(math.pow(diff / 200, 3), -1, 1) 116 | if angle > 360 then 117 | angle = angle - 360 118 | elseif angle < 0 then 119 | angle = angle + 360 120 | end 121 | 122 | return angle 123 | 124 | end 125 | 126 | -- make the input value never go below min or above max 127 | function clamp(value, min, max) 128 | 129 | return math.max(math.min(value, max), min) 130 | 131 | end 132 | -------------------------------------------------------------------------------- /shoot.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Code related to shooting bullets 3 | ---------------------------------------------------------------------------------------------------- 4 | -- this function sets the initial velocities for each bullet 5 | -- it doesn't shoot - it relies on the "drawShot" function to continue 6 | -- 7 | -- f - force additional to player's chosen force, 8 | -- t - theta (angle) additional to player's chosen angle, 9 | -- bulletIndex - index of the bullet 10 | function setBulletInitialVelocity(f, t, bulletIndex) 11 | 12 | if turn == 1 then 13 | -- preturb it by a bit for jitter so it's not the same 14 | f = player1.force + f -- + math.random() * 2 15 | t = player1.angle + t -- + math.random() * 10 16 | elseif turn == 2 then 17 | f = player2.force + f -- + math.random() * 2 18 | t = player2.angle + t -- + math.random() * 10 19 | end 20 | 21 | -- *** REDUCE THE FORCE BY some amount and reduce weight of planets by some amount 22 | -- *** this will make the resolution of the shot higher!!?! *** 23 | f = f / 2 24 | 25 | -- calculate the x and y components of shot for initial force 26 | allBullets[bulletIndex].vx = f * math.cos(0.0174533 * t) 27 | allBullets[bulletIndex].vy = f * math.sin(0.0174533 * t) 28 | 29 | end 30 | 31 | -- to split a bullet (special bullet type allows for splitting while in air) 32 | function split(x, y) 33 | 34 | love.graphics.ellipse('line', x, y, 10, 10) 35 | 36 | -- print("SPLIT @ ", x, y) 37 | 38 | temp = 0 39 | 40 | for i = 1, 3 do 41 | temp = numOfBullets + i 42 | allBullets[i] = {} 43 | allBullets[temp].x = x 44 | allBullets[temp].y = y 45 | end 46 | 47 | numOfBullets = numOfBullets + 3 48 | temp = 0 49 | 50 | for i = 1, 3 do 51 | temp = numOfBullets - 3 + i 52 | setBulletInitialVelocity(0.01, math.random(0, 360), temp) 53 | end 54 | 55 | end 56 | 57 | function shotType1() 58 | 59 | -- shoot only one bullet 60 | numOfBullets = 1 61 | 62 | for i = 1, numOfBullets do 63 | allBullets[i] = {} 64 | allBullets[i].x = shipX 65 | allBullets[i].y = shipY 66 | setBulletInitialVelocity(1, 1, i) 67 | end 68 | 69 | end 70 | 71 | function shotType2() 72 | 73 | -- shoot three bullets 74 | numOfBullets = 30 75 | 76 | for i = 1, numOfBullets do 77 | allBullets[i] = {} 78 | allBullets[i].x = shipX 79 | allBullets[i].y = shipY 80 | setBulletInitialVelocity(1, 1, i) 81 | end 82 | 83 | end 84 | 85 | function shotType3() 86 | 87 | -- shoot five bullets 88 | numOfBullets = 5 89 | 90 | for i = 1, numOfBullets do 91 | allBullets[i] = {} 92 | allBullets[i].x = shipX 93 | allBullets[i].y = shipY 94 | setBulletInitialVelocity(1, 1, i) 95 | end 96 | 97 | end 98 | 99 | function shotType4() 100 | 101 | -- shoot only one bullet 102 | numOfBullets = 1 103 | 104 | for i = 1, numOfBullets do 105 | allBullets[i] = {} 106 | allBullets[i].x = shipX 107 | allBullets[i].y = shipY 108 | setBulletInitialVelocity(1, 1, i) 109 | end 110 | 111 | end 112 | 113 | function playerPressedShootButton() 114 | 115 | dimTrails() 116 | 117 | print("Whose is shooting? Player ", turn) 118 | 119 | -- origin of the shot set to player's location 120 | if turn == 1 then 121 | shipX = player1.x 122 | shipY = player1.y 123 | player1.lastAngle = player1.angle 124 | player1.lastForce = player1.force 125 | elseif turn == 2 then 126 | shipX = player2.x 127 | shipY = player2.y 128 | player2.lastAngle = player2.angle 129 | player2.lastForce = player2.force 130 | end 131 | 132 | allBullets = {} -- reset to empty 133 | 134 | -- make bullet benign 135 | benign = 0 136 | 137 | if bulType == 1 then 138 | shotType1() 139 | elseif bulType == 2 then 140 | shotType2() 141 | elseif bulType == 3 then 142 | shotType3() 143 | elseif bulType == 4 then 144 | shotType4() 145 | else 146 | for i = 1, numOfBullets do 147 | allBullets[i] = {} 148 | allBullets[i].x = shipX 149 | allBullets[i].y = shipY 150 | setBulletInitialVelocity(2, 2, i) 151 | end 152 | end 153 | 154 | love.graphics.setCanvas(canvas) 155 | drawUI() 156 | love.graphics.setCanvas() 157 | 158 | shotInProgress = true 159 | 160 | end 161 | 162 | -- blow up a location in a pretty way 163 | function explode(x, y) 164 | 165 | temp = 0 166 | 167 | love.graphics.setCanvas(canvas) 168 | 169 | for i = 1, 10 do 170 | temp = numOfBullets + i 171 | allBullets[temp] = {} 172 | 173 | randX = math.random(-20, 20) 174 | randY = math.random(-20, 20) 175 | 176 | radius = math.random(5, 15) 177 | setExplodyColor() 178 | love.graphics.ellipse('fill', x + randX, y + randY, radius, radius) 179 | 180 | allBullets[temp].x = x + randX 181 | allBullets[temp].y = y + randY 182 | end 183 | 184 | love.graphics.setCanvas() 185 | 186 | numOfBullets = numOfBullets + 10 187 | temp = 0 188 | 189 | for i = 1, 10 do 190 | temp = numOfBullets - 10 + i 191 | setBulletInitialVelocity(1, math.random(0, 360), temp) 192 | end 193 | 194 | end 195 | 196 | function setExplodyColor() 197 | 198 | -- red 1 0 0 199 | -- orange 1 0.5 0 200 | -- yellow 1 1 0 201 | 202 | love.graphics.setColor(1, math.random(0, 255) / 255, 0, 1) 203 | end -------------------------------------------------------------------------------- /ui.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Code related to drawing the UI (user interface) 3 | ---------------------------------------------------------------------------------------------------- 4 | function drawUI() 5 | 6 | love.graphics.setColor(1, 1, 1, 1) 7 | 8 | -- rectangle for health bars and around play area 9 | love.graphics.rectangle('line', 2, 2, WIDTH - 4, 30) 10 | love.graphics.rectangle('line', 2, 32, WIDTH - 4, HEIGHT - 34) 11 | 12 | drawShootButton() 13 | 14 | drawLives() 15 | 16 | drawHealthBars() 17 | 18 | love.graphics.setColor(1, 1, 1, 1) 19 | 20 | end 21 | 22 | -- shoot BUTTON rectangle -- change states while shot in progress 23 | function drawShootButton() 24 | 25 | if shotInProgress == true then 26 | love.graphics.setColor(0.5, 0, 0, 1) 27 | love.graphics.ellipse('fill', WIDTH - 50, HEIGHT - 50, 30, 30) 28 | else 29 | love.graphics.setColor(16 / 255, 178 / 255, 197 / 255, 1) 30 | love.graphics.ellipse('fill', WIDTH - 50, HEIGHT - 50, 30, 30) 31 | end 32 | 33 | end 34 | 35 | -- draw how many lives each has 36 | function drawLives() 37 | 38 | love.graphics.setColor(1, 1, 0, 1) 39 | for i = 1, player1.lives do 40 | love.graphics 41 | .ellipse('fill', WIDTH / 2 + 35 + 20 * i, 16, 8, 8) 42 | end 43 | 44 | for i = 1, player2.lives do 45 | love.graphics 46 | .ellipse('fill', WIDTH / 2 - 35 - 20 * i, 16, 8, 8) 47 | end 48 | 49 | end 50 | 51 | -- draw health bars - responsive depending on WIDTH 52 | function drawHealthBars() 53 | 54 | topOffset = 10 55 | 56 | barWidth = WIDTH / 2 - 200 57 | 58 | -- right health bar 59 | love.graphics.setColor((255 - player1.health * 2.55) / 255, 1, 0, 1) 60 | love.graphics.rectangle('fill', WIDTH / 2 + 150, topOffset, barWidth, 10) 61 | 62 | if player1.health < 100 then 63 | love.graphics.setColor(223 / 255, (45 + player1.health) / 255, 64 | (45 + player1.health) / 255, 1) 65 | love.graphics.rectangle('fill', (WIDTH / 2 + 150) + barWidth - barWidth * 66 | ((100 - player1.health) / 100), topOffset, 67 | barWidth - barWidth * ((player1.health) / 100), 10) 68 | end 69 | 70 | -- left health bar 71 | love.graphics.setColor((255 - player2.health * 2.55) / 255, 1, 0, 1) 72 | love.graphics.rectangle('fill', WIDTH / 2 - barWidth - 150, topOffset, barWidth, 10) 73 | 74 | if player2.health < 100 then 75 | love.graphics.setColor(224 / 255, (45 + player2.health) / 255, 76 | (45 + player2.health) / 255, 1) 77 | love.graphics.rectangle('fill', (WIDTH / 2 - barWidth - 150), topOffset, 78 | barWidth - barWidth * ((player2.health) / 100), 10) 79 | end 80 | 81 | end 82 | 83 | function drawForceAndAngle(playerN, tempHack) 84 | 85 | -- different offset depending on player 86 | if tempHack == 1 then 87 | xOffset = playerN.x - 140 88 | angleY = playerN.y + 80 89 | forceY = playerN.y + 92 90 | else 91 | xOffset = playerN.x + 55 92 | angleY = playerN.y + 78 93 | forceY = playerN.y + 90 94 | end 95 | 96 | -- black background behind angle and force 97 | love.graphics.setColor(0, 0, 0, 0.7) 98 | love.graphics.rectangle('fill', xOffset + 12, angleY, 70, 13, 4, 4) 99 | love.graphics.rectangle('fill', xOffset + 24, forceY + 1, 66, 12, 4, 4) 100 | 101 | -- print force (red) 102 | love.graphics.setColor(1, 0, 0, 1) 103 | love.graphics.setFont(pixelFont, 20) 104 | love.graphics.print('GJ', xOffset + 76, forceY) -- GigaJoules 105 | love.graphics.printf(string.format("%.5f", playerN.force), xOffset, forceY, 75, 'right') 106 | 107 | -- print angle (white) 108 | love.graphics.setColor(1, 1, 1, 1) 109 | love.graphics.printf(string.format("%.5f", 360 - playerN.angle), xOffset, angleY, 75, 'right') 110 | love.graphics.ellipse('line', xOffset + 78, angleY + 3, 2, 2) 111 | 112 | end 113 | 114 | 115 | function drawShips() 116 | 117 | -- in the user interface (top left and top right) 118 | love.graphics.draw(ss1, 24, 11) 119 | love.graphics.draw(ss2, WIDTH - 32, 11) 120 | 121 | -- love.graphics.setColor(1, 0.6, 0.6, 1) 122 | -- love.graphics.ellipse('fill', player1.x, player1.y, 8, 8) 123 | -- love.graphics.ellipse('fill', player2.x, player2.y, 8, 8) 124 | 125 | if player1.health > 0 then 126 | love.graphics.draw(ss1, player1.x - 4, player1.y - 4) 127 | end 128 | 129 | if player2.health > 0 then 130 | love.graphics.draw(ss2, player2.x - 4, player2.y - 4) 131 | end 132 | 133 | -- love.graphics.draw(ss1, player1.x, player1.y, player1.angle, 1, 1, 4, 4) 134 | 135 | end 136 | 137 | --[[ 138 | Dim all the shot trails on the screen 139 | meant to run after every shot 140 | works by drawing a black rectangle with low opacity over the whole screen 141 | then redraws all other elements on top 142 | --]] 143 | function dimTrails() 144 | 145 | print("dimTrails EXECUTED") 146 | 147 | love.graphics.setCanvas(canvas) 148 | love.graphics.setColor(0, 0, 0, 0.15) -- don't fortget to reset ? 149 | love.graphics.rectangle('fill', 0, 0, WIDTH, HEIGHT) 150 | love.graphics.setColor(1, 1, 1, 1) -- reset back !? 151 | drawPlanets() -- execute inside `canvas` ?! 152 | drawShips() 153 | drawUI() 154 | love.graphics.setCanvas() -- reset canvas ?! 155 | 156 | end 157 | 158 | function drawPlanets() 159 | 160 | for i = 1, numOfPlanets do 161 | love.graphics.setColor(0.1, 0.1, 0.1, 1) 162 | love.graphics.ellipse('fill', allPlanets[i].x, allPlanets[i].y, 163 | allPlanets[i].r, allPlanets[i].r) 164 | love.graphics.setColor(1, 1, 1, 1) 165 | love.graphics.ellipse('line', allPlanets[i].x, allPlanets[i].y, 166 | allPlanets[i].r, allPlanets[i].r) 167 | end 168 | 169 | end 170 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Code for imports, the initial load, and the `draw` loop 3 | ---------------------------------------------------------------------------------------------------- 4 | require "globals" 5 | require "ui" 6 | require "shoot" 7 | require "logic" 8 | require "touch" 9 | require "keyboard" 10 | 11 | -- Use this function to perform your initial setup 12 | function love.load() 13 | 14 | -- math.randomseed(42) -- goes out of bounds 15 | -- math.randomseed(232) -- shot comes close to opponent; set `shotType3()` numOfBullets to 15 to hit opponent 16 | math.randomseed(os.time()) -- randomize map every time 17 | 18 | loadExternalAssets() -- globals.lua 19 | setVariables() -- globals.lua 20 | 21 | love.window.setMode(WIDTH, HEIGHT) 22 | 23 | canvas = love.graphics.newCanvas(WIDTH, HEIGHT, { msaa = 2 }) 24 | 25 | canvas2 = love.graphics.newCanvas(WIDTH, HEIGHT, { msaa = 2 }) 26 | 27 | -- love.graphics.setCanvas(canvas) 28 | -- love.graphics.setBlendMode("alpha") -- what does this do ?!?? 29 | -- love.graphics.setCanvas() 30 | 31 | newGame() 32 | 33 | bulType = 1 34 | -- playerPressedShootButton() -- disable this to let the player take the first shot 35 | 36 | end 37 | 38 | -- This function gets called once every frame 39 | 40 | function love.draw() 41 | 42 | -- love.graphics.setBackgroundColor(0,0,50/255) 43 | 44 | if endOfRound == true and shotInProgress == false then 45 | endOfRound = false 46 | newGame() 47 | end 48 | 49 | if colorMode == 1 then 50 | -- this has potential to run over (how many minutes till it crashes?) 51 | -- might want it to reset to 0 sometimes 52 | rainbow.r = rainbow.r + 1 53 | rainbow.g = rainbow.g + 2 54 | rainbow.b = rainbow.b + 3 55 | 56 | -- this code oscilates the color all the time (mathematically heavy) 57 | love.graphics.setColor(math.floor(200 * math.abs(math.sin(rainbow.r * 0.005)) + 54) / 255, 58 | math.floor(200 * math.abs(math.sin(rainbow.g * 0.004)) + 54) / 255, 59 | math.floor(200 * math.abs(math.sin(rainbow.b * 0.003)) + 54) / 255, 1) 60 | elseif colorMode == 2 then 61 | love.graphics.setColor(1, 1, 1, 1) 62 | end 63 | 64 | -- only draw things if shot is in progress 65 | if shotInProgress == true then 66 | for i = 1, numOfBullets do drawShot(i) end 67 | 68 | for i = 1, numOfBullets do collisonCheck(i) end 69 | 70 | benign = benign + 1 -- benign makes your own bullet not kill you for the first few moments when you shoot 71 | 72 | bulletsInFlight = false -- TODO: fix this hacky thing: if the x location of each shot == 0 then end the turn 73 | for i = 1, numOfBullets do 74 | if allBullets[i].x ~= 0 and allBullets[i].y ~= 0 then 75 | bulletsInFlight = true -- HACK -- assumes position of disabled bullet is = (0, 0) 76 | end 77 | end 78 | 79 | if bulletsInFlight == false then 80 | print("all shots have finished") 81 | 82 | shotInProgress = false 83 | 84 | if turn == 1 then 85 | turn = 2 86 | elseif turn == 2 then 87 | turn = 1 88 | end 89 | 90 | love.graphics.setCanvas(canvas) 91 | drawUI() 92 | love.graphics.setCanvas() 93 | end 94 | 95 | end 96 | 97 | -- this code here WILL dim the trails continuously when explosion occurs 98 | -- works too fast so I can slow it down by dimming every 5th frame (temp int) 99 | -- if benign < 0 then dimTrails() end 100 | 101 | -- drawUI() -- maybe do not draw continuously ? 102 | 103 | -- starts dimming the playing field more aggressively 104 | if endOfRound == true then 105 | -- dimTrails() 106 | -- love.graphics.setColor(0,0,0,1) 107 | -- love.graphics.rectangle('fill',-1,-1,WIDTH+5,HEIGHT+5) 108 | end 109 | 110 | -- RENDER THE CANVAS NOW 111 | love.graphics.draw(canvas) 112 | 113 | if shotInProgress == false then 114 | 115 | love.graphics.setCanvas(canvas2) 116 | love.graphics.clear() 117 | 118 | if turn == 1 then 119 | drawPlayerAngleAndForce(player1) 120 | if player1.lastAngle ~= nil then 121 | drawAngleDiff(player1, 0) 122 | end 123 | drawForceAndAngle(player1, 1) 124 | else 125 | drawPlayerAngleAndForce(player2) 126 | if player2.lastAngle ~= nil then 127 | drawAngleDiff(player2, 1) 128 | end 129 | drawForceAndAngle(player2, 2) 130 | end 131 | 132 | love.graphics.setCanvas() 133 | love.graphics.draw(canvas2) 134 | 135 | end 136 | 137 | drawShips() 138 | 139 | love.graphics.setColor(1, 1, 1, 1) 140 | 141 | end 142 | 143 | 144 | function drawPlayerAngleAndForce(playerN) 145 | 146 | -- large black circle 147 | love.graphics.setColor(0, 0, 0, 0.6) 148 | love.graphics.ellipse('fill', playerN.x, playerN.y, 100, 100) 149 | 150 | -- red arc showing force 151 | love.graphics.setColor(1, 0, 0, 1) 152 | love.graphics.arc('fill', 'pie', playerN.x, playerN.y, playerN.force * 20, 0.0174533 * playerN.angle - 0.1, 0.0174533 * playerN.angle + 0.1) 153 | 154 | -- smaller red circle showing force 155 | love.graphics.setColor(1, 0, 0, 0.4) 156 | love.graphics.ellipse('line', playerN.x, playerN.y, playerN.force * 20, playerN.force * 20) 157 | 158 | -- love.graphics.setColor(1, 0, 0, 1) 159 | -- love.graphics.line(playerN.x, playerN.y, 160 | -- playerN.x + math.cos(0.0174533 * playerN.angle) * 100 * playerN.force / 5, 161 | -- playerN.y + math.sin(0.0174533 * playerN.angle) * 100 * playerN.force / 5) 162 | 163 | -- large white circle showing maximum 164 | love.graphics.setColor(1, 1, 1, 0.6) 165 | love.graphics.ellipse('line', playerN.x, playerN.y, 100, 100) 166 | 167 | -- long line showing angle 168 | love.graphics.setColor(1, 1, 1, 1) 169 | love.graphics.ellipse('line', playerN.x, playerN.y, 10, 10) 170 | love.graphics.line(playerN.x, playerN.y, 171 | playerN.x + math.cos(0.0174533 * playerN.angle) * 100, 172 | playerN.y + math.sin(0.0174533 * playerN.angle) * 100) 173 | 174 | end 175 | 176 | 177 | function drawAngleDiff(playerN, playerOffsetHack) 178 | 179 | angleDiff = playerN.lastAngle - playerN.angle 180 | 181 | forceDiff = playerN.force - playerN.lastForce 182 | 183 | if angleDiff > 180 then 184 | angleDiff = angleDiff - 360 185 | end 186 | 187 | if angleDiff < -180 then 188 | angleDiff = angleDiff + 360 189 | end 190 | 191 | if angleDiff < 10 and angleDiff > -10 then 192 | 193 | xOffset = playerN.x - 76 + playerOffsetHack * 90 194 | 195 | fontOpacity = math.pow((10 - math.abs(angleDiff))/10, 0.5) 196 | love.graphics.setFont(pixelFont) 197 | 198 | if (angleDiff ~= 0) then 199 | -- rectangle 200 | love.graphics.setColor(0, 0, 0, 0.5) 201 | love.graphics.rectangle('fill', xOffset + 5, playerN.y - 6, 56, 12, 4, 4) 202 | -- text 203 | love.graphics.setColor(1, 1, 1, fontOpacity) 204 | love.graphics.printf(string.format("%.5f", angleDiff), xOffset, playerN.y - 7, 60, 'right') 205 | end 206 | 207 | love.graphics.setColor(1, 0, 0, fontOpacity) 208 | 209 | if (forceDiff ~= 0) then 210 | -- rectangle 211 | love.graphics.setColor(0, 0, 0, 0.5) 212 | love.graphics.rectangle('fill', xOffset + 5, playerN.y + 6, 56, 12, 4, 4) 213 | -- text 214 | love.graphics.setColor(1, 0, 0, fontOpacity) 215 | love.graphics.printf(string.format("%.5f", forceDiff), xOffset, playerN.y + 5, 60, 'right') 216 | end 217 | 218 | love.graphics.setColor(1, 1, 1, 1) 219 | end 220 | 221 | end 222 | -------------------------------------------------------------------------------- /logic.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------------------------------- 2 | -- Code related to creating new game, planets, collision check, and draw shot 3 | ---------------------------------------------------------------------------------------------------- 4 | function newGame() 5 | 6 | setInitialPositions() 7 | 8 | player1.angle = 0 9 | player2.angle = 180 10 | 11 | player1.lastAngle = nil 12 | player2.lastAngle = nil 13 | 14 | player1.health = 100 15 | player2.health = 100 16 | 17 | love.graphics.setCanvas(canvas) 18 | love.graphics.clear() 19 | drawPlanets() 20 | drawShips() 21 | drawUI() 22 | love.graphics.setCanvas() 23 | 24 | print("New Game Started") 25 | 26 | end 27 | 28 | -- returns `player1` or `player2` table (object) depending on whose turn it is 29 | function currentPlayer() 30 | 31 | if turn == 1 then 32 | return player1 33 | else 34 | return player2 35 | end 36 | end 37 | 38 | -- Randomize placement of planets & ships 39 | -- check for overlap, retry if check fails 40 | function setInitialPositions() 41 | 42 | print('randomizing planets attempt') 43 | 44 | allPlanets = {} -- reset whatever we had before first 45 | 46 | -- include parameters for max and min planet locations 47 | 48 | -- TODO: experiment with mass 49 | -- having the mass depend on radius is less fun - small planets stop mattering 50 | -- mass = (math.pow(allPlanets[i].r,3)/25) -- MASS depends on radius^3 *** this affects speed of drawing 51 | 52 | for i = 1, numOfPlanets do 53 | allPlanets[i] = { 54 | x = math.random(100, WIDTH - 100), 55 | y = math.random(150, HEIGHT - 250), 56 | r = math.random(15, 50), 57 | mass = math.random(10, 50) * 10 58 | } 59 | end 60 | 61 | -- reposition ships 62 | player1.x = math.random(200, WIDTH / 2 - 100) 63 | player1.y = math.random(200, HEIGHT - 200) 64 | player2.x = math.random(WIDTH / 2 + 100, WIDTH - 200) 65 | player2.y = math.random(200, HEIGHT - 200) 66 | 67 | -- TODO - compute distance with square roots - not just x & y distance 68 | -- check for planet overlap 69 | -- check for ship overlapping with planet too 70 | -- space them out by 50 px at least 71 | for i = 1, numOfPlanets do 72 | for j = 1, numOfPlanets do 73 | if allPlanets[i].x == allPlanets[j].x and allPlanets[i].y == 74 | allPlanets[j].y then 75 | -- do nothing 76 | elseif math.abs(allPlanets[i].x - allPlanets[j].x) < 40 and 77 | math.abs(allPlanets[i].y - allPlanets[j].y) < 40 then 78 | setInitialPositions() 79 | goto done 80 | elseif math.abs(allPlanets[i].x - player1.x) < 90 and 81 | math.abs(allPlanets[i].y - player1.y) < 90 then 82 | setInitialPositions() 83 | goto done 84 | elseif math.abs(allPlanets[i].x - player2.x) < 90 and 85 | math.abs(allPlanets[i].y - player2.y) < 90 then 86 | setInitialPositions() 87 | goto done 88 | end 89 | end 90 | end 91 | 92 | ::done:: 93 | 94 | end 95 | 96 | -- check collisions with every shot that is drawn 97 | -- dims trails after every collision 98 | function collisonCheck(b) 99 | 100 | -- insert code that will set shotInProgress = false if all bullets are gone and end the turn with it 101 | 102 | -- whenever the bullet hits the planet - remove from drawing & computing 103 | for i = 1, numOfPlanets do 104 | 105 | if (math.sqrt(math.pow(allPlanets[i].x - allBullets[b].x, 2) + 106 | math.pow(allPlanets[i].y - allBullets[b].y, 2))) < 107 | allPlanets[i].r then 108 | 109 | -- remove the bullet from the playing field 110 | -- as long as it's placed outside the cutoff set in the drawShot() 111 | -- it will not compute!!! no wasted CPU cycles! 112 | allBullets[b].x = 0 113 | allBullets[b].y = 0 114 | allBullets[b].vx = 0 115 | allBullets[b].vy = 0 116 | 117 | -- this dims the trails on every collision -- probably should disable 118 | -- dimTrails() 119 | love.graphics.setCanvas(canvas) 120 | drawPlanets() -- draw planets because a collision overlaps with a planet :( 121 | love.graphics.setCanvas() 122 | end 123 | end 124 | 125 | -- grace period when your bullet can't kill you 126 | if benign > 50 then 127 | didYouHitPlayer(player1, b) 128 | didYouHitPlayer(player2, b) 129 | if turn == 1 then 130 | updateHealthBar(player2, player1, b) 131 | else 132 | updateHealthBar(player1, player2, b) 133 | end 134 | end 135 | 136 | end 137 | 138 | -- don't forget `b` the bullet index 139 | function didYouHitPlayer(playerN, b) 140 | 141 | if (math.sqrt(math.pow(playerN.x - allBullets[b].x, 2) + 142 | math.pow(playerN.y - allBullets[b].y, 2))) < 10 then 143 | print("you hit someone!") 144 | 145 | -- only decrease 1 life! 146 | if endOfRound == false then playerN.lives = playerN.lives - 1 end 147 | 148 | endOfRound = true 149 | 150 | playerN.health = 0 151 | 152 | -- store location where player is 153 | explodeX = playerN.x 154 | explodeY = playerN.y 155 | 156 | -- hide player from the board 157 | playerN.x = -100 158 | playerN.y = -100 159 | 160 | -- explode this location !!! 161 | explode(explodeX, explodeY) 162 | 163 | if playerN.lives == 0 then print("GAME OVER, SOMEONE WON!") end 164 | end 165 | 166 | end 167 | 168 | -- don't forget `b` the bullet index 169 | function updateHealthBar(playerN, opponent, b) 170 | 171 | distanceFromShot = math.sqrt(math.pow(playerN.x - allBullets[b].x, 2) + 172 | math.pow(playerN.y - allBullets[b].y, 2)) 173 | 174 | if opponent.health > distanceFromShot then 175 | opponent.health = distanceFromShot 176 | love.graphics.setCanvas(canvas) 177 | drawUI() 178 | love.graphics.setCanvas() 179 | end 180 | 181 | end 182 | 183 | -- TODO: figure out a sensible default for resolution of the shot and speed of the shot 184 | -- warning: planets with all the small radii may allow bullets to pass through 185 | -- because the shot rosolution is LOW 186 | 187 | -- TODO: allow shot to be outside the border by at least a little bit 188 | -- TODO: include code so it doesn't do the calculation if the shot is too far from border 189 | -- if shot is within bounds of the screen, draw it 190 | 191 | --[[ 192 | 193 | THE MOST IMPORTANT FUNCTION - draws the lines for the shot "b" where b (think "bullet") is the shot name 194 | 195 | 1) if shot is outside some boundary, discard it 196 | 2) if the shot is outside drawing area, compute, but don't draw (TODO: this is not implemented yet) 197 | 3) if the shot is within screen: 198 | a) compute x and y components from each planet on the current bullet (store in fpx & fpy variables) 199 | b) sum up all the forces into a single vfx & vfy 200 | c) add final force to bullet's initial force 201 | d) draw the small segment 202 | e) update bullet's 'initial' velocity for next iteration 203 | 204 | --]] 205 | function drawShot(b) 206 | 207 | -- Variables involved: 208 | -- b - bulletIndex `[b]` 209 | 210 | -- (1) 211 | -- set the shot outside if it hits outside the play border 212 | if allBullets[b].x > WIDTH - 10 or allBullets[b].x < 10 or allBullets[b].y > 213 | WIDTH - 10 or allBullets[b].y < 10 then 214 | allBullets[b].x = 0 215 | allBullets[b].y = 0 216 | end 217 | 218 | -- (3) 219 | if allBullets[b].x < WIDTH - 10 and allBullets[b].x > 10 and allBullets[b].y < 220 | WIDTH - 10 and allBullets[b].y > 10 then 221 | 222 | -- array to store forces from each planet to each shot (temp use always) 223 | fpx = {} 224 | fpy = {} 225 | 226 | -- (a) 227 | -- calculate force of planet on x1 and y1 228 | for i = 1, numOfPlanets do 229 | xDiff = allPlanets[i].x - allBullets[b].x 230 | yDiff = allPlanets[i].y - allBullets[b].y 231 | 232 | fpx[i] = xDiff / 233 | (math.pow(math.sqrt((xDiff * xDiff) + (yDiff * yDiff)), 234 | 3)); 235 | fpy[i] = yDiff / 236 | (math.pow(math.sqrt((xDiff * xDiff) + (yDiff * yDiff)), 237 | 3)); 238 | end 239 | 240 | -- reset velocity -- TEMPORARY VARIABLES 241 | vfx = 0 242 | vfy = 0 243 | 244 | -- (b) 245 | -- for each planet add all forces multiplied by gravity of each planet 246 | for i = 1, numOfPlanets do 247 | vfx = vfx + fpx[i] * allPlanets[i].mass 248 | end 249 | 250 | for i = 1, numOfPlanets do 251 | vfy = vfy + fpy[i] * allPlanets[i].mass 252 | end 253 | 254 | -- (c) 255 | -- add initial velocity to the final velocity 256 | vfx = vfx + allBullets[b].vx 257 | vfy = vfy + allBullets[b].vy 258 | 259 | -- set velocity of each bullet to its final velocity 260 | allBullets[b].vx = vfx 261 | allBullets[b].vy = vfy 262 | 263 | -- (d) 264 | -- Draw shot to canvas 265 | love.graphics.setCanvas(canvas) 266 | love.graphics.line(allBullets[b].x, allBullets[b].y, 267 | allBullets[b].x + vfx, allBullets[b].y + vfy) 268 | love.graphics.setCanvas() 269 | 270 | -- (e) 271 | allBullets[b].x = allBullets[b].x + vfx 272 | allBullets[b].y = allBullets[b].y + vfy 273 | 274 | end 275 | 276 | end 277 | --------------------------------------------------------------------------------