From dd55b2d7a776baaca5f7634c9b5b7569bc30cc1a Mon Sep 17 00:00:00 2001 From: Raymond Date: Wed, 16 Sep 2020 00:26:58 -0400 Subject: [PATCH 1/6] Working submit --- README.md | 14 +++++++++++++ public/index.html | 44 +++++++++++++--------------------------- public/js/scripts.js | 48 ++++++++++++++++++++++++++++++++++++++++++-- server.improved.js | 37 ++++++++++++++++++++++++++++------ 4 files changed, 105 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 3945c152..ec8b41b1 100755 --- a/README.md +++ b/README.md @@ -91,3 +91,17 @@ Include a very brief summary of your project here. Be sure to include the CSS po ### Design/Evaluation Achievements - **Design Achievement 1**: + +TODO +-Style + -Use grid/flex + -id selectors + -class selectors + -element selectors + -different font +-Add delete (and unique identifier) +-Add modify (move up/down) +-Writeup +-Test? +-Add more fields (need derived field) +-Make sure it validates diff --git a/public/index.html b/public/index.html index c56d620e..89fa8bd5 100755 --- a/public/index.html +++ b/public/index.html @@ -1,41 +1,25 @@ - CS4241 Assignment 2 + Ray's Initiative Tracker + +

Raymond's Initiative Tracker

- + +
+ +
+ + + + + + +
NameInitiative
- diff --git a/public/js/scripts.js b/public/js/scripts.js index de052eae..1e5fc46c 100755 --- a/public/js/scripts.js +++ b/public/js/scripts.js @@ -1,3 +1,47 @@ -// Add some Javascript code here, to run on the front end. -console.log("Welcome to assignment 2!") \ No newline at end of file +const processJSON = (json) => { + console.log(json) + const creatures = document.getElementById("creatures"); + creatures.innerHTML = ""; + json.forEach((creature) => { + creatures.innerHTML += ` + + ${ + creature.name} + + ${ + creature.num} + + ` + }) +} + const submit = function( e ) { + // prevent default form action from being carried out + e.preventDefault() + + const name = document.querySelector( '#name' ) + const num = document.querySelector( '#initiative' ) + + const json = {name: name.value, num: num.value } + const body = JSON.stringify( json ) + + fetch( '/submit', { + method:'POST', + body + }) + .then( response => response.json()) + .then( resjson => processJSON(resjson)) + + // clear inputs + + return false + } + + window.onload = function() { + const button = document.querySelector( 'button' ) + button.onclick = submit + + fetch('/appData') + .then(response => response.json()) + .then(json => processJSON(json)); + } diff --git a/server.improved.js b/server.improved.js index 26673fc0..ed21fafb 100644 --- a/server.improved.js +++ b/server.improved.js @@ -6,10 +6,9 @@ const http = require( 'http' ), dir = 'public/', port = 3000 +// Storage, storing players from highest -> lowest initiative const appdata = [ - { 'model': 'toyota', 'year': 1999, 'mpg': 23 }, - { 'model': 'honda', 'year': 2004, 'mpg': 30 }, - { 'model': 'ford', 'year': 1987, 'mpg': 14} + { 'name': 'Drathaniel', 'num': '10'} ] const server = http.createServer( function( request,response ) { @@ -25,7 +24,15 @@ const handleGet = function( request, response ) { if( request.url === '/' ) { sendFile( response, 'public/index.html' ) - }else{ + } + else if(request.url === '/appData') { + response.writeHead( 200, "OK", {'Content-Type': 'application/json'}) + response.write(JSON.stringify(appdata)) + response.end() + + } + else{ + console.log("hi") sendFile( response, filename ) } } @@ -38,15 +45,33 @@ const handlePost = function( request, response ) { }) request.on( 'end', function() { - console.log( JSON.parse( dataString ) ) + //console.log( JSON.parse( dataString ) ) + + json = JSON.parse( dataString) + console.log(json) + + var pos = determineOrder( parseInt(json.num)) - // ... do something with the data here!!! + appdata.splice(pos, 0, json) + //console.log(appdata) response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }) + response.write(JSON.stringify(appdata)) response.end() }) } +function determineOrder ( initiative ) { + var i; + for(i=0; i parseInt(appdata[i].num)) { + return i + } + } + return i + +} + const sendFile = function( response, filename ) { const type = mime.getType( filename ) From f4cb22317f3ee9fd72f5b227f715afc79347728e Mon Sep 17 00:00:00 2001 From: Raymond Date: Wed, 16 Sep 2020 19:35:46 -0400 Subject: [PATCH 2/6] Finished funcitonality --- README.md | 3 -- public/index.html | 21 +++++++-- public/js/scripts.js | 107 +++++++++++++++++++++++++++++++++++++++++-- server.improved.js | 54 +++++++++++++++++++++- 4 files changed, 172 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ec8b41b1..4ee4f51f 100755 --- a/README.md +++ b/README.md @@ -99,9 +99,6 @@ TODO -class selectors -element selectors -different font --Add delete (and unique identifier) --Add modify (move up/down) -Writeup -Test? --Add more fields (need derived field) -Make sure it validates diff --git a/public/index.html b/public/index.html index 89fa8bd5..5ea3f242 100755 --- a/public/index.html +++ b/public/index.html @@ -9,15 +9,30 @@

Raymond's Initiative Tracker

-
- -
+
+ +
+ +
+ + + +
+ +
+
+
+ +
Name InitiativeHPAC
diff --git a/public/js/scripts.js b/public/js/scripts.js index 1e5fc46c..6daeb776 100755 --- a/public/js/scripts.js +++ b/public/js/scripts.js @@ -1,16 +1,31 @@ const processJSON = (json) => { - console.log(json) + //console.log(json) const creatures = document.getElementById("creatures"); creatures.innerHTML = ""; json.forEach((creature) => { creatures.innerHTML += ` ${ - creature.name} + creature.name} ${ - creature.num} + creature.num} + + ${ + creature.hp} + + ${ + creature.ac} + + + + + + ` }) @@ -19,11 +34,29 @@ const processJSON = (json) => { // prevent default form action from being carried out e.preventDefault() + const checkbox = document.getElementById('roll') + const initiative = document.querySelector( '#initbonus') + + // handle initiative + var init; + if(checkbox.checked) { + // Roll their initiative + + init = parseInt(initiative.value) + getRandomInt(20) + + } else { + // their initiative is what was entered. + init = document.querySelector( '#initiative' ).value + } + + // get rest of fields const name = document.querySelector( '#name' ) - const num = document.querySelector( '#initiative' ) + const hp = document.querySelector( '#hp') + const ac = document.querySelector( '#ac') - const json = {name: name.value, num: num.value } + const json = {name: name.value, num: init, ac:ac.value, hp:hp.value } const body = JSON.stringify( json ) + //console.log(json) fetch( '/submit', { method:'POST', @@ -33,10 +66,44 @@ const processJSON = (json) => { .then( resjson => processJSON(resjson)) // clear inputs + name.value = "" + initiative.value = 0 + hp.value = 0 + ac.value = 0 return false } +const removeCreature = function (e) { + e.preventDefault(); + const id = e.target.getAttribute("id"); + + const json = {delete: 'delete', id}, + body = JSON.stringify(json); + + fetch('/submit', { + method:'POST', + body, + }) + .then(response => response.json()) + .then( resjson => processJSON(resjson)) +} + +const moveCreature = function (e, dir) { + e.preventDefault(); + const id = e.target.getAttribute("id"); + + const json = {movedir: dir, id}, + body = JSON.stringify(json); + + fetch('/submit', { + method:'POST', + body, + }) + .then(response => response.json()) + .then( resjson => processJSON(resjson)) +} + window.onload = function() { const button = document.querySelector( 'button' ) button.onclick = submit @@ -45,3 +112,33 @@ const processJSON = (json) => { .then(response => response.json()) .then(json => processJSON(json)); } + + document.addEventListener("click", function (e) { + if(e.target && e.target.classList[0] == "delete") { + removeCreature(e) + } + if(e.target && e.target.classList[0] == "moveUp") { + moveCreature(e, -1) + } + if(e.target && e.target.classList[0] == "moveDown") { + moveCreature(e, 1) + } + }) + + function switchRoll() { + var checkBox = document.getElementById("roll"); + var bonus = document.getElementById("initbonusBox") + var init = document.getElementById("initiativeBox") + if(checkBox.checked == true) { + init.style.display = "none" + bonus.style.display = "block" + } else { + bonus.style.display = "none" + init.style.display = "block" + + } + } + + function getRandomInt(max) { + return Math.floor(Math.random() * Math.floor(max)) + 1 + } \ No newline at end of file diff --git a/server.improved.js b/server.improved.js index ed21fafb..e43c9427 100644 --- a/server.improved.js +++ b/server.improved.js @@ -6,9 +6,10 @@ const http = require( 'http' ), dir = 'public/', port = 3000 +var id = 1 // Storage, storing players from highest -> lowest initiative const appdata = [ - { 'name': 'Drathaniel', 'num': '10'} + { 'name': 'Drathaniel', 'num': '10', 'ac': '13', 'hp': '37', 'id': 0} ] const server = http.createServer( function( request,response ) { @@ -32,7 +33,6 @@ const handleGet = function( request, response ) { } else{ - console.log("hi") sendFile( response, filename ) } } @@ -48,6 +48,46 @@ const handlePost = function( request, response ) { //console.log( JSON.parse( dataString ) ) json = JSON.parse( dataString) + + // delete + if(json.hasOwnProperty('delete')) { + appdata.splice(appdata.findIndex(x => x.id.toString() === json.id.toString()), 1); + response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }) + response.write(JSON.stringify(appdata)) + response.end() + return false; + } + // move + if(json.hasOwnProperty('movedir')) { + var oldpos = appdata.findIndex(x => x.id.toString() === json.id.toString()) + var newpos = oldpos + parseInt(json.movedir) + // If they try to move outside the bounds, return + if( newpos < 0 || newpos >= appdata.length) { + response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }) + response.write(JSON.stringify(appdata)) + response.end() + return false; + } + + var start = appdata[oldpos] + var end = appdata[newpos] + // If it would mess up order refuse + if(parseInt(start['num']) !== parseInt(end['num'])) { + response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }) + response.write(JSON.stringify(appdata)) + response.end() + return false; + } + array_move(oldpos, newpos) + //console.log(appdata) + + response.writeHead( 200, "OK", {'Content-Type': 'text/plain' }) + response.write(JSON.stringify(appdata)) + response.end() + return false; + } + + json['id'] = id++ console.log(json) var pos = determineOrder( parseInt(json.num)) @@ -94,4 +134,14 @@ const sendFile = function( response, filename ) { }) } +function array_move( old_index, new_index) { + if (new_index >= appdata.length) { + var k = new_index - appdata.length + 1; + while (k--) { + appdata.push(undefined); + } + } + appdata.splice(new_index, 0, appdata.splice(old_index, 1)[0]); +}; + server.listen( process.env.PORT || port ) From 027df4eee7e85621401f65a9168a78a9d6d3736e Mon Sep 17 00:00:00 2001 From: Raymond Date: Wed, 16 Sep 2020 22:57:55 -0400 Subject: [PATCH 3/6] Final version --- README.md | 112 +++++++------------------------------------ public/css/style.css | 79 +++++++++++++++++++++++++++++- public/index.html | 14 ++++-- public/js/scripts.js | 34 +++++++------ server.improved.js | 2 +- 5 files changed, 127 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index 4ee4f51f..28899d35 100755 --- a/README.md +++ b/README.md @@ -1,104 +1,26 @@ -Assignment 2 - Short Stack: Basic Two-tier Web Application using HTML/CSS/JS and Node.js -=== +## Raymond's Initiative Tracker +My application is a simple initiative tracker for Dungeons and Dragons or a similar tabletop roleplaying game. In those games players and enemies take turns, so keeping track of the turn order is helpful. -Due: September 16th, by 11:59 PM. +I used flex for my title and grid for position of the rest of the elements. -This assignment aims to introduce you to the concepts and practice involved in creating a prototype (i.e. not deployment ready) two-tiered web application. The baseline aims of this assignment involve creating an application that demonstrates the use of several specific pieces of HTML, CSS, JavaScript, and Node.js functionality. +The derived field is optional. You can either enter the initiative that a player rolled when the 'roll?' box is not checked, or you can roll it in the application. If 'roll' is checked, the form asks for the initiative bonus, which is a positive or negative number. Ex. Jill Holyhand the monk may be quick and have a +4 to initiative, while Grognas Flatfoot may have a -2. When entering in this way the app takes your bonus, rolls a d20, and adds your bonus to get your initiative number. -Baseline Requirements ---- +Additionally, the arrows on the end of creatures are used to resolve ties. If two creatures have the same initiative, then the up and down arrows can be used to position them correctly (in game the tie can be resolved a number of ways, either saying the creature with the higher initiative bonus goes first, the player goes first, or through rock paper scissors). -Note that there is a very large range of application areas and possibilities that meet these baseline requirements. Make your application do something useful! A todo list, storing / retrieving high scores for a very simple game, have a little fun with it. - -Your application is required to implement the following functionalities: - -- a `Server` which not only serves files, but also maintains a tabular dataset with 3 or more fields related to your application -- a `Results` functionality which shows the entire dataset residing in the server's memory -- a `Form/Entry` functionality which allows a user to add, modify, or delete data items residing in the server's memory -- a `Server Logic` which, upon receiving new or modified "incoming" data, includes and uses a function that adds at least one additional derived field to this incoming data before integrating it with the existing dataset -- the `Derived field` for a new row of data must be computed based on fields already existing in the row. For example, a `todo` dataset with `task`, `priority`, and `creation_date` may generate a new field `deadline` by looking at `creation_date` and `priority` - -Your application is required to demonstrate the use of the following concepts: - -HTML: -- One or more [HTML Forms](https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms), with any combination of form tags appropriate for the user input portion of the application -- A results page displaying all data currently available on the server. You will most likely use a `` tag for this, but `
    ` could also work and might be simpler to work with. -- All pages should [validate](https://validator.w3.org) - -CSS: -- CSS styling of the primary visual elements in the application -- Various CSS Selector functionality must be demonstrated: - - Element selectors - - ID selectors - - Class selectors -- CSS positioning and styling of the primary visual elements in the application: - - Use of either a CSS grid or flexbox for layout - - Rules defining fonts for all text used; no default fonts! Be sure to use a web safe font or a font from a web service like [Google Fonts](http://fonts.google.com/) - -- CSS defined in a maintainable, readable form, in external stylesheets - -JavaScript: -- At minimum, a small amount of front-end JavaScript to get / fetch data from the server; a sample is provided in this repository. - -Node.js: -- An HTTP Server that delivers all necessary files and data for the application. A starting point is provided in this repository. - -Deliverables ---- - -Do the following to complete this assignment and acheive a base grade of 85%: - -1. Fork the starting project code (make sure to fork the 2020 repo!). This repo contains some starter code that may be used or discarded as needed. -2. Implement your project with the above requirements. -3. Test your project to make sure that when someone goes to your main page, it displays correctly. -4. Deploy your project to Glitch, and fill in the appropriate fields in your package.json file. -5. Ensure that your project has the proper naming scheme `a2-yourname` so we can find it. -6. Modify the README to the specifications below. -7. Create and submit a Pull Request to the original repo. Label the pull request as follows: a2-gitusername-firstname-lastname - -Acheivements ---- - -Below are suggested technical and design achievements. You can use these to help boost your grade up to an A and customize the assignment to your personal interests. These are recommended acheivements, but feel free to create/implement your own... just make sure you thoroughly describe what you did in your README and why it was challenging. ALL ACHIEVEMENTS MUST BE DESCRIBED IN YOUR README IN ORDER TO GET CREDIT FOR THEM. - -*Technical* -- (10 points) Create a single-page app that both provides a form for users to submit data and always shows the current state of the server-side data. To put it another way, when the user submits data, the server should respond sending back the updated data (including the derived field calculated on the server) and the client should then update its data display. - -*Design/UX* -- (5 points per person, with a max of 10 points) Test your user interface with other students in the class. Define a specific task for them to complete (ideally something short that takes <10 minutes), and then use the [think-aloud protocol](https://en.wikipedia.org/wiki/Think_aloud_protocol) to obtain feedback on your design (talk-aloud is also find). Important considerations when designing your study: - -1. Make sure you start the study by clearly stating the task that you expect your user to accomplish. -2. You shouldn't provide any verbal instructions on how to use your interface / accomplish the task you give them. Make sure that your interface is clear enough that users can figure it out without any instruction, or provide text instructions from within the interface itself. -3. If users get stuck to the point where they give up, you can then provde instruction so that the study can continue, but make sure to discuss this in your README. You won't lose any points for this... all feedback is good feedback! - -You'll need to use sometype of collaborative software that will enable you both to see the test subject's screen and listen to their voice as they describe their thoughts. After completing each study, briefly (one to two sentences for each question) address the following in your README: - -1. Provide the last name of each student you conduct the evaluation with. -2. What problems did the user have with your design? -3. What comments did they make that surprised you? -4. What would you change about the interface based on their feedback? - -*You do not need to actually make changes based on their feedback*. This acheivement is designed to help gain experience testing user interfaces. If you run two user studies, you should answer two sets of questions. +## Technical Achievements +- **Tech Achievement 1**: Created a single-page app that provides a form for users to submit creatures and updates the display with the new data whenever a change is made. -Sample Readme (delete the above when you're ready to submit, and modify the below so with your links and descriptions) ---- +- **Technical Achievement 2**: This app allows the user to create, remove, and (lightl) edit data. creatures are added using the form. The user can delete any player from the initiative order if there is a mistake or the monster is defeated. The player can update the order to resolve initiative ties using the up and down arrows. -## Your Web Application Title -Include a very brief summary of your project here. Be sure to include the CSS positioning technique you used, and any required instructions to use your application. - -## Technical Achievements -- **Tech Achievement 1**: Using a combination of... ### Design/Evaluation Achievements -- **Design Achievement 1**: +- **Design Achievement 1**: Classmate test + - Name: Hunter Trautz + - What problems did the user have with your design? + - He commented that it was unclear when things were highlighted and I should add hover effects. + - What comments did they make that surprised you? + - I was surprised that Hunter understood the interface so well from first glance and he thought it looked clean. + - What would you change about the interface based on their feedback? + - I would add more colors and hover effects to make the interface more easy to understand. + -TODO --Style - -Use grid/flex - -id selectors - -class selectors - -element selectors - -different font --Writeup --Test? --Make sure it validates diff --git a/public/css/style.css b/public/css/style.css index d5f842ab..130643b0 100755 --- a/public/css/style.css +++ b/public/css/style.css @@ -1 +1,78 @@ -/*Style your own assignment! This is fun! */ \ No newline at end of file +body { + font-family: 'Josefin Sans'; + background-color: #f2dca9 +} +h1 { + display: flex; + justify-content: center; + font-size: 48px; +} + +.appcontainer { + display: grid; + grid-template-columns: 25% 1fr; + column-gap: 20px; +} +form { + grid-column-start: 1; + border: 5px solid #b5bd85; + padding: 10px; +} + +table { + grid-column-start: 2; + border: 5px solid #b5bd85; + padding: 10px; +} + +th { + text-align: left; + font-size: 25px; +} + +input { + display: flex; + justify-content: right; +} + +input:hover { + background-color: LightCyan; +} + +label { + font-size: 20px; +} + +.button { + border-radius: 4px; + width: 150px; + height: 50px; + text-align: center; + font-size: 18px; + cursor: pointer; + +} +.button:hover { + background-color: LightCyan; +} + +.delete:hover { + color: red; +} + +.moveUp:hover { + color: yellow; +} +.moveDown:hover { + color: yellow; +} + +td { + font-size: 24px; + word-wrap: break-word; + max-width: 150px; +} + +#note { + text-align:right; +} \ No newline at end of file diff --git a/public/index.html b/public/index.html index 5ea3f242..5657c56c 100755 --- a/public/index.html +++ b/public/index.html @@ -1,16 +1,22 @@ + + Ray's Initiative Tracker +

    Raymond's Initiative Tracker

    +

    Note: up/down arrows are only for resolving ties.

    +
    - +
    - +

    @@ -25,10 +31,11 @@

    Raymond's Initiative Tracker


    - +
+ @@ -36,5 +43,6 @@

Raymond's Initiative Tracker

Id Name Initiative HP
+ diff --git a/public/js/scripts.js b/public/js/scripts.js index 6daeb776..641d87f7 100755 --- a/public/js/scripts.js +++ b/public/js/scripts.js @@ -6,6 +6,9 @@ const processJSON = (json) => { json.forEach((creature) => { creatures.innerHTML += ` + ${ + creature.id + } ${ creature.name} @@ -18,14 +21,11 @@ const processJSON = (json) => { ${ creature.ac} - + - + - + ` }) @@ -35,25 +35,30 @@ const processJSON = (json) => { e.preventDefault() const checkbox = document.getElementById('roll') - const initiative = document.querySelector( '#initbonus') + const initbonus = document.querySelector( '#initbonus') + const initiative = document.querySelector( '#initiative' ) + + const name = document.querySelector( '#name' ) + const hp = document.querySelector( '#hp') + const ac = document.querySelector( '#ac') + if(name.value === "" || ac.value === "" || + initbonus.value === "" || hp.value === "") { + window.alert("Please fill in all of the fields.") + return false + } // handle initiative var init; if(checkbox.checked) { // Roll their initiative - init = parseInt(initiative.value) + getRandomInt(20) + init = parseInt(initbonus.value) + getRandomInt(20) } else { // their initiative is what was entered. - init = document.querySelector( '#initiative' ).value + init = initiative.value } - // get rest of fields - const name = document.querySelector( '#name' ) - const hp = document.querySelector( '#hp') - const ac = document.querySelector( '#ac') - const json = {name: name.value, num: init, ac:ac.value, hp:hp.value } const body = JSON.stringify( json ) //console.log(json) @@ -68,6 +73,7 @@ const processJSON = (json) => { // clear inputs name.value = "" initiative.value = 0 + initbonus.value = 0 hp.value = 0 ac.value = 0 diff --git a/server.improved.js b/server.improved.js index e43c9427..418364c3 100644 --- a/server.improved.js +++ b/server.improved.js @@ -88,7 +88,7 @@ const handlePost = function( request, response ) { } json['id'] = id++ - console.log(json) + //console.log(json) var pos = determineOrder( parseInt(json.num)) From b0c7f5b523a1360d0b6bfe40a510bc3c18541865 Mon Sep 17 00:00:00 2001 From: Raymond Date: Wed, 16 Sep 2020 23:03:08 -0400 Subject: [PATCH 4/6] Final version --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 28899d35..43620b33 100755 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +View at a2-raymonddolan.glitch.me + ## Raymond's Initiative Tracker My application is a simple initiative tracker for Dungeons and Dragons or a similar tabletop roleplaying game. In those games players and enemies take turns, so keeping track of the turn order is helpful. From 2cb9ffddcc1b38a609a72efbdffe82a28ae47d3e Mon Sep 17 00:00:00 2001 From: Raymond Date: Wed, 16 Sep 2020 23:08:51 -0400 Subject: [PATCH 5/6] validation changes --- public/index.html | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/public/index.html b/public/index.html index 5657c56c..34cec3e5 100755 --- a/public/index.html +++ b/public/index.html @@ -13,7 +13,7 @@

Raymond's Initiative Tracker

Note: up/down arrows are only for resolving ties.

-
+
@@ -35,11 +35,13 @@

Raymond's Initiative Tracker

- - - - - + + + + + + +
IdNameInitiativeHPAC
IdNameInitiativeHPAC
From 5b1050396a6b105e4d01a29de0ce9dd83ed9606b Mon Sep 17 00:00:00 2001 From: Raymond Dolan <33587691+rfdolan@users.noreply.github.com> Date: Wed, 16 Sep 2020 23:09:39 -0400 Subject: [PATCH 6/6] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 43620b33..ea922fef 100755 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Additionally, the arrows on the end of creatures are used to resolve ties. If tw ## Technical Achievements - **Tech Achievement 1**: Created a single-page app that provides a form for users to submit creatures and updates the display with the new data whenever a change is made. -- **Technical Achievement 2**: This app allows the user to create, remove, and (lightl) edit data. creatures are added using the form. The user can delete any player from the initiative order if there is a mistake or the monster is defeated. The player can update the order to resolve initiative ties using the up and down arrows. +- **Technical Achievement 2**: This app allows the user to create, remove, and (lightly) edit data. creatures are added using the form. The user can delete any player from the initiative order if there is a mistake or the monster is defeated. The player can update the order to resolve initiative ties using the up and down arrows. ### Design/Evaluation Achievements