diff --git a/README.md b/README.md index 3ae10a92..2d36a5ce 100644 --- a/README.md +++ b/README.md @@ -3,59 +3,8 @@ Assignment 4 - Creative Coding: Interactive Multimedia Experiences Due: October 4th, by 11:59 AM. -For this assignment we will focus on client-side development using popular audio/graphics/visualization technologies. The goal of this assignment is to refine our JavaScript knowledge while exploring the multimedia capabilities of the browser. +## Goat Cheese -[WebAudio / Canvas / Three Tutorial](https://github.com/cs4241-21a/cs4241-21a.github.io/blob/main/webaudio_canvas_three.md) -[SVG + D3 tutorial](https://github.com/cs4241-21a/cs4241-21a.github.io/blob/main/using_svg_and_d3.md) +Link: https://a4-joseph-scheufele.glitch.me -Baseline Requirements ---- - -Your application is required to implement the following functionalities: - -- A server created using Express. This server can be as simple as needed. -- A client-side interactive experience using at least one of the following web technologies frameworks. - - [Three.js](https://threejs.org/): A library for 3D graphics / VR experiences - - [D3.js](https://d3js.org): A library that is primarily used for interactive data visualizations - - [Canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API): A 2D raster drawing API included in all modern browsers - - [SVG](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API): A 2D vector drawing framework that enables shapes to be defined via XML. - - [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API): An API for audio synthesis, analysis, processing, and file playback. -- A user interface for interaction with your project, which must expose at least four parameters for user control. [tweakpane](https://cocopon.github.io/tweakpane/) is highly recommended for this, but you can also use regular HTML `` tags (the `range` type is useful to create sliders). You might also explore interaction by tracking mouse movement via the `window.onmousemove` event handler in tandem with the `event.clientX` and `event.clientY` properties. Consider using the [Pointer Events API](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events) to ensure that that both mouse and touch events will both be supported in your app. -- Your application should display basic documentation for the user interface when the application first loads. - -The interactive experience should possess a reasonable level of complexity. Some examples: -### Three.js -- A generative algorithm creates simple agents that move through a virtual world. Your interface controls the behavior / appearance of these agents. -- A simple 3D game... you really want this to be a simple as possible or it will be outside the scope of this assignment. -- An 3D audio visualization of a song of your choosing. User interaction should control aspects of the visualization. -### Canvas -- Implement a generative algorithm such as [Conway's Game of Life](https://bitstorm.org/gameoflife/) (or 1D cellular automata) and provide interactive controls. Note that the Game of Life has been created by 100s of people using ; we'll be checking to ensure that your implementation is not a copy of these. -- Design a 2D audio visualizer of a song of your choosing. User interaction should control visual aspects of the experience. -### Web Audio API -- Create a screen-based musical instrument using the Web Audio API. You can use projects such as [Interface.js](http://charlie-roberts.com/interface/) or [Nexus UI](https://nexus-js.github.io/ui/api/#Piano) to provide common musical interface elements, or use dat.GUI in combination with mouse/touch events (use the Pointer Events API). Your GUI should enable users to control aspects of sound synthesis. If you want to use higher-level instruments instead of the raw WebAudio API sounds, consider trying the instruments provided by [Tone.js]() or [Gibber](https://github.com/charlieroberts/gibber.audio.lib). -### D3.js -- Create visualizations using the datasets found at [Awesome JSON Datasets](https://github.com/jdorfman/Awesome-JSON-Datasets). Experiment with providing different visualizations of the same data set, and providing users interactive control over visualization parameters and/or data filtering. Alternatively, create a single visualization with using one of the more complicated techniques shown at [d3js.org](d3js.org) and provide meaningful points of interaction for users. - -Deliverables ---- - -Do the following to complete this assignment: - -1. Implement your project with the above requirements. -3. Test your project to make sure that when someone goes to your main page on Glitch/Heroku/etc., it displays correctly. -4. Ensure that your project has the proper naming scheme `a4-firstname-lastname` so we can find it. -5. Fork this repository and modify the README to the specifications below. *NOTE: If you don't use Glitch for hosting (where we can see the files) then you must include all project files that you author in your repo for this assignment*. -6. Create and submit a Pull Request to the original repo. Name the pull request using the following template: `a4-firstname-lastname`. - -Sample Readme (delete the above when you're ready to submit, and modify the below so with your links and descriptions) ---- - -## Your Web Application Title - -your hosting link e.g. http://a4-charlieroberts.glitch.me - -Include a very brief summary of your project here. Images are encouraged when needed, along with concise, high-level text. Be sure to include: - -- the goal of the application -- challenges you faced in realizing the application -- the instructions you present in the website should be clear enough to use the application, but if you feel any need to provide additional instructions please do so here. +This is an implementation of the famous snake game, but with a creative twist in that the snake is replaced with an ever growing line of hungry goats looking for a block of cheese. The arrow keys are used to move the goat herd around the screen. One challenge I faced was rendering the line of goats, I needed to find the correct size to make the images and the correct collision detection. diff --git a/cheese.png b/cheese.png new file mode 100644 index 00000000..269562d6 Binary files /dev/null and b/cheese.png differ diff --git a/goat.png b/goat.png new file mode 100644 index 00000000..7cbdf702 Binary files /dev/null and b/goat.png differ diff --git a/package.json b/package.json new file mode 100644 index 00000000..4fa385fc --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "CS_4241_Assignment_4", + "version": "0.1.0", + "description": "My own interpretation of the game, Space Invaders", + "author": "Joseph Scheufele", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.17.1" + }, + "engines": { + "node": "14.x" + } + } + \ No newline at end of file diff --git a/public/css/styles.css b/public/css/styles.css new file mode 100644 index 00000000..a5166735 --- /dev/null +++ b/public/css/styles.css @@ -0,0 +1,35 @@ +* { + font-family: 'Press Start 2P', cursive; + } + + html, body { + padding-top: 1em; + padding-left: 7em; + padding-right: 7em; + padding-bottom: 1em; + height:90%; + } + + #gameCanvas { + height:100%; + width:100% + } + + #gameContainer { + width:95%; + height:95% + } + + #header { + display:grid; + grid-template-rows: 1fr; + grid-template-columns: 1fr 1fr; + } + + #start { + margin: .3em + } + + #score { + margin: .3em + } diff --git a/public/js/script.js b/public/js/script.js new file mode 100644 index 00000000..f0291d55 --- /dev/null +++ b/public/js/script.js @@ -0,0 +1,197 @@ +const gameboard = document.getElementById("gameCanvas") +const gameboardctx = gameboard.getContext("2d") +const board_border = 'black' +const board_background = "white" + +const parent = document.getElementById("gameContainer") +gameboard.width = parent.offsetWidth +gameboard.height = parent.offsetHeight + +const snake_col = 'lightblue' +const snake_border = 'darkblue' + +const INCREMENT = 20 + +let gompei = [ + {x:200, y:200}, + {x:190, y:200}, + {x:180, y:200}, + {x:170, y:200}, + {x:160, y:200}, +] + +const goat = new Image() +goat.src = "https://cdn.glitch.com/a7611cfe-7b75-43fc-b92b-b75661f1bb15%2Fgoat.png?v=1633296688197" + +function drawGompeiPart(gompeiPart) { + gameboardctx.drawImage(goat, gompeiPart.x, gompeiPart.y, INCREMENT, INCREMENT) +} + +function drawGompei() { + gompei.forEach(drawGompeiPart) +} + +function clearCanvas() { + gameboardctx.fillStyle = board_background + gameboardctx.strokestyle = board_border + gameboardctx.fillRect(0, 0, gameboard.width, gameboard.height) + gameboardctx.strokeRect(0, 0, gameboard.width, gameboard.height) +} + +let dx = INCREMENT +let dy = 0 +let food_x +let food_y +let score = 0 +let changing_direction = false +let started = false + +const cheese = new Image() +cheese.src = "https://cdn.glitch.com/a7611cfe-7b75-43fc-b92b-b75661f1bb15%2Fcheese.png?v=1633321691951" + +const goatsound = new Audio("https://cdn.glitch.com/a7611cfe-7b75-43fc-b92b-b75661f1bb15%2Fgoatwav.mp3?v=1633345750329") + +function move_gompei() { + const head = {x: gompei[0].x + dx, y: gompei[0].y + dy} + gompei.unshift(head) + const has_eaten_food = gompei[0].x+(INCREMENT/2) >= food_x && gompei[0].x+(INCREMENT/2) <= food_x+INCREMENT && gompei[0].y+(INCREMENT/2) >= food_y && gompei[0].y+(INCREMENT/2) <= food_y+INCREMENT + if (has_eaten_food) { + goatsound.currentTime = 1 + goatsound.play() + score +=1 + document.getElementById("score").innerText = "Score: "+ score + gen_food() + } else { + gompei.pop() + } +} + +document.addEventListener("keydown", change_direction) + +function change_direction(event) +{ + const LEFT_KEY = 37 + const RIGHT_KEY = 39 + const UP_KEY = 38 + const DOWN_KEY = 40 + + const keyPressed = event.keyCode + const goingUp = dy === -INCREMENT + const goingDown = dy === INCREMENT + const goingRight = dx === INCREMENT + const goingLeft = dx === -INCREMENT + + if (keyPressed === LEFT_KEY && !goingRight) + { + dx = -INCREMENT + dy = 0 + } + + if (keyPressed === UP_KEY && !goingDown) + { + dx = 0 + dy = -INCREMENT + } + + if (keyPressed === RIGHT_KEY && !goingLeft) + { + dx = INCREMENT + dy = 0 + } + + if (keyPressed === DOWN_KEY && !goingUp) + { + dx = 0 + dy = INCREMENT + } +} + +function has_game_ended() +{ + for (let i = 4; i < gompei.length; i++) + { + const has_collided = gompei[i].x === gompei[0].x && gompei[i].y === gompei[0].y + if (has_collided) { + started = false + return true + } + } + const hitLeftWall = gompei[0].x < 0 + const hitRightWall = gompei[0].x > gameboard.width + const hitToptWall = gompei[0].y < 0 + const hitBottomWall = gompei[0].y > gameboard.height + + return hitLeftWall || hitRightWall || hitToptWall || hitBottomWall +} + +function random_food(min, max) +{ + return Math.round((Math.random() * (max-min) + min) / 10) * 10 +} + +function gen_food() +{ + food_x = random_food(0, gameboard.width - INCREMENT) + food_y = random_food(0, gameboard.height - INCREMENT) + gompei.forEach(function has_gompei_eaten_food(part) { + const has_eaten = part.x == food_x && part.y == food_y; + if (has_eaten) gen_food() + }) +} + +function drawFood() { + gameboardctx.drawImage(cheese, food_x, food_y, INCREMENT, INCREMENT) + +} + +const endgame = function() { + gameboard.remove() + document.querySelector("#start").innerText = "Play Again" + const prompt = document.createElement("H1") + prompt.className = "nes-text is-error" + prompt.innerText = "You Lost, Play Again?" + document.getElementById("gameContainer").appendChild(prompt) + +} + +function main() { + if (has_game_ended()){ + clearCanvas() + endgame() + return + } + changing_direction = false + setTimeout(function onTick() { + clearCanvas() + drawFood() + move_gompei() + drawGompei() + main() + }, 100) +} + +const start = function () { + if (!started) { + started = true + window.location.reload() + } +} + +main() +gen_food() + +window.onload = function() { + const start_button = document.querySelector("#start") + start_button.onclick = start +} + +window.onresize = function() { + const gameboard = document.getElementById("gameCanvas") + const gameboardctx = gameboard.getContext("2d") + const board_border = 'black' + const board_background = "white" + + const parent = document.getElementById("gameContainer") + gameboard.width = parent.width + gameboard.height = parent.height +} diff --git a/server.js b/server.js new file mode 100644 index 00000000..35b96d5a --- /dev/null +++ b/server.js @@ -0,0 +1,11 @@ +const express = require("express"), + app = express() + +app.use(express.static(__dirname + '/public')) +app.get("/", (req,res) => res.sendFile(__dirname + "/views/index.html")) + +let port = process.env.PORT; +if (port == null || port == "") { + port = 3000; +} +app.listen(port); \ No newline at end of file diff --git a/views/index.html b/views/index.html new file mode 100644 index 00000000..908ae5dc --- /dev/null +++ b/views/index.html @@ -0,0 +1,32 @@ + + + + Goat + + + + + + + + + + + + + + + + +

Goat....cheese!

+

Use arrow keys to move Gompei!

+ +
+ +
+ + +