README

HSU

The official HSU game. Play it in a modern browser!

Gameplay and Features

overview

The game is played by using the arrow keys or WASD to move your character around the map. You can zoom in or out by pressing "z"

zoom

There are icons that are part of the Heads up Display (HUD) Right underneath them, there is a clock and in-game timer.

inventory

You can bring up your current inventory by clicking on the backpack.

inventory

You can interact with certain NPCs by pressing the "C" button. Dialogs offer multiple choices of sub-questions and answers.

inventory inventory

There is also looping background music playing.

See our roadmap for details on what features are coming up next.

Starting the game locally

Download and install nodejs

Download the repository (git clone)

git clone https://github.com/micahh2/hsu

Change your current directory

cd hsu

Install dependencies

npm i

Start local http server

npm start

The dev server opens a browser new tab when it starts, pointing to: http://localhost:8080

Development

Orienting yourself and your repository

  1. Run git status to see the status of your local instance. If you are on a branch besides master, run git checkout master to switch to the master branch. If you have changes you don't want anymore try staging them with git add and using git stash to hide them for the moment.
  2. Update your local master branch using git pull to get changes. Pay attention to the output, if there are merge conflicts they will show up here.
  3. Start the local development server with npm start
  4. In another terminal/console start the unit test runner with npm test
  5. Read the card that your working on, make sure you understand it
  6. Create a feature branch using git checkout -b, or git branch and git checkout. Try to give your branch a short but descriptive name.

Developing your code

  1. Identify changes to make
  2. (Optional) Create tests that don't pass, but will when you've made the next step (TDD)
  3. Implement changes, then check the unit test runner
  4. Repeat creating tests, and implementing functionality until the card is complete, or until you need to redefine the card to match how the feature will be implemented (e.g. break it up)

Contributing your code

  1. Double check all unit tests are passing
  2. Lint the project using npm run fix-lint, if there are errors fix them
  3. Use git add to stage files as ready for commit
  4. Use git commit to create a commit with a useful message describing your changes
  5. Use git push your new branch and use github's UI to create a pull request, don't forget to assign someone to merge the pull request.
  6. Celebrate 🎉 !!! You just contributed to the project!

Testing

To start the test runner:

npm test

To generate a test report (test-report.md)

npm run test-report

View the test status' here

View the test code coverage here

Documentation

Documentation can be found on the demo website here.

Building documentation

npm run build-docs

Starting local server for documentation

npm run serve-docs

Linting

The project uses a lightly modified version of Airbnb's eslint style guide. To run the linter and show errors:

npm run lint

To run the linter and auto-fix simple mistakes:

npm run fix-lint

While you do not need to do this before making a commit, it is encouraged. There are also a number of editor plugins that can show you linter errors and make it easier to write compliant code.

License

This software is licensed under AGPL

index.html

The whole application starts in index.html. It is a relatively small html file that sets the overall structure of the game and names the other initial files to bring in.

Below is a simplified version of the index.html

<html>
    <head>
        <title>Hochschule Ulm</title>
        <link href="main.css" rel="stylesheet" /> 
    </head>
    <body>
        <div id="images">
            <img id="character-sprite" src="assets/charBatch1_sprite.png">
        </div>
        <div id="stage">
            <canvas id="objects-layer"></canvas>
            <canvas id="layout-layer"></canvas>
        </div>
    </body>
    <script type="module" src="main.js"></script>
</html>

Index.html does three main things:

  1. Loads images to be referenced later
  2. Loads javascript/css (main.js & main.css)
  3. Sets up the canvases that will be drawn on and displayed

After this file is given to the browser, main.js takes over running the show.

main.js

main.js is a ES6 javascript module. This means that it can import other scripts using the import keyword, and that it runs in what is known as javascript's strict mode.

main.js has two main parts, an event listener that is called when the page is loaded, and auxiliary functions that support that event listener.

"Load" Event Listener

When index.html hands of execution of the game to main.js, much of the page is still being loaded. The first thing that main.js does is register an event listener using window.addEventListener(). This event listener waits for everything in index.html to be loaded, and then calls the given anonymous function.

window.addEventListener('load', async () => {
    // Do something when the page loads
});

window is a global variable available to most javascript in the browser. We avoid references to global variables outside of main.js because they don't work in NodeJS (where we execute our unit tests), and because it is important to have an organizational method of how and where the browser can be interacted with.

In essence, the 'load' event listener main.js has three responsibilities:

  1. Ensure all resources are finished loading
  2. Parse and configure resources (images, json) and output (canvas)
  3. Start the game loops

1. Ensure all resources are finished loading

Since some resources depend on other resources, they have to be loaded dynamically and might not be loaded by the time the load event is triggered. Here we make use of a newer javascript feature called async/await. There are a number of ways to execute multiple functions simultaneously in javascript (Note: this does NOT mean it's multi-threaded), Promises are simple way to have a result from something asynchronous that you can wait get later.

2. Parse and configure resources (images, json) and output (canvas)

Not everything structural can be defined in index.html or main.css, some information about the canvases need to be determined based on, for example, the size of images after they loaded. This step also involves some basic parsing of the sprites, scaling them to the sizes we will use.

3. Start the game loops

We have two game loops, one for the Story and one for the Physics. Both are not "proper" while or for loops but instead functions that are called repetitively, or messages that are passed back-and-forth in a cycle.

The physics loop is recursive, calling it self using window.requestAnimationFrame. It has two current jobs:

  • Update the game state to the next state (one "tick")
  • Display the new state on screen

The story loop runs in its own thread. It only has one job: evaluate the game logic and create updates. The story loop is started by sending an 'update-game-state' message to the storyWorker. The story worker uses the updated game state to create changes, and passes those changes back to an event handler in main.js. The main.js event-handler applies those changes to the current state (using Story.applyChanges) and sends the newly updated state back to the story worker with 'update-game-state'. This goes on in a cycle until the game ends.

Auxiliary Event Listeners and Functions

There are a few auxiliary event listeners, and functions that are used to support them. Including two very important event listeners, which handle keyboard events:

There are also some functions for updating on-screen statistics and parts of the general UI.

main.js
Static Members
physicsLoop
movePlayer(args)
clearStats()
updateStats(key, value)
updateDiagnostDisp(args)
canvasProvider()

Modules

Modules
Static Members
Camera
Characters
Map
Physics
Sprite
Story
Util
Music
PathFinding

Tests

updateViewport ✓ should provide a full view ✓ should provide a zoomed view

moveNPC ✓ should always move an npc as an integer

getSpritable ✓ should take loaded tile data and return something we can make into a sprite

loadImages ✓ should fetch images

dijikstras ✓ should generate no path ✓ should find a valid path ✓ should generate an optimal path ✓ should generate a diagonal path ✓ should go straight over a wall, then diagonal to the right ✓ should go diagonal over the wall ✓ should first go up-right (diagonal), then down a corridor

areNeighbors ✓ should return true for two areas are neighbors ✓ should return false for two areas which are not neighbors ✓ should return true for a neighbor below it ✓ should return true for an offset neighbor to the side

gridToGraph ✓ should receive a grid and return a graph ✓ should receive a blocked grid and return an empty graph ✓ should receive a mixed grid and return a graph with neighbors ✓ should should return the right neighbors for a semi-complex graph ✓ grid to graph using dijikstras

hasBlock ✓ should receive a blocked grid and return true ✓ should receive a partially blocked grid and return true

getGatewayPoint ✓ should pick the mid-point between two neighboring areas (x dir) ✓ should pick the mid-point between two neighboring areas (y dir) ✓ should pick the mid-point between two neighboring areas with a partial overlap

splitGraphIntoPoints ✓ should return a new graph with points ✓ should return a new graph with points when areas overlap

getUseableMove ✓ should move a player left if left is requested ✓ should let a player move down if down-right is requested, and right is not allowed ✓ should let a player move left if down-left is requested, and down is not allowed

updateLocationMap ✓ should remove an old player even if it has been modified

loadSpriteData ✓ should set the internal canvas size according to different scales ✓ should load sprite data into parts ✓ should load sprite data with different scales

applyChanges ✓ should apply conversation changes ✓ should apply item changes ✓ should update character waypoints/destinations

newId ✓ should return the highest id+1

setDestination ✓ should return updated destinations

createSelector ✓ should create a selector

startConversation ✓ should return the dialog of a character ✓ should select the nearest npc if no selector ✓ should do nothing if no selector and no near npc

isRecurrentEvent ✓ should be recurrent when it is of type interval

isTime ✓ should be time when it's happening exactly now ✓ should be time when it's happened in the past ✓ should not be time when it has not happened yet

isWithinInterval ✓ should be within the interval when it's happening exactly now ✓ should be within the interval when it's happening exactly now + start ✓ should be within the interval when it's happening now-ish (<threshold) ✓ should be within the interval when it's happening now-ish for the third time ✓ should not be within the interval when triggered more than the threshold ago ✓ should not be within the interval when now is later than end

isWithinDistance ✓ should be within distance when near ✓ should not be within distance when far

isTriggered ✓ should should trigger on time ✓ should trigger on interval ✓ should trigger when in area ✓ should trigger on distance ✓ should trigger on any conversation ✓ should trigger on conversation with specific npc ✓ should trigger on having an item

updateGameState ✓ should update conversation on distance ✓ should set destination on time

loadGameState ✓ shouldn't transform choordinates from relative to abs (formerly we did)

isWithinArea ✓ should be when actor is in area ✓ should not be when actor is above the area ✓ should not be when actor is beyond the area

69 passing (59ms)

View test code coverage

Roadmap

This roadmap document shows an overview of our project priorities. It is non-binding and is only useful to have a simple overview of where we are, and where we might be going.

This document is feature focused, so many tasks that support the objectives below, won't be shown. For a more detailed look at progression through these milestones, please refer to Trello and the Sprint planning documents. The roadmap should evolve over time as it is primarily an overview, and not a planning document.

Milestones

  1. Hello world

    • ✅ Setup Project
    • ✅ Create initial art assets
    • ✅ Integrate initial art into game
    • ✅ Setup tests
    • ✅ Initial Physics
  2. M.V.P. (Minimum viable product)

    • NPCs

      • ✅ Busy waiting
      • ✅ Optional conversations
      • ✅ Path finding
    • Player

      • ✅ Pickup Items
      • ✅ Receive Items
    • Story Engine

      • ✅ Detect simple complete tasks/quests
      • ✅ Show when player has won the game
    • UI

      • ✅ Show Inventory
      • ✅ Show a simple Quest
      • ✅ Show time progression
  3. Expanded Features I

    • NPCs

      • ✅ Take / "recieve" items
      • ✅ Force conversations with player
    • Player

      • Drop items
    • Story Engine

      • ✅ Zombies
    • Map

      • ✅ Realistic Map
    • Multi-player

      • Chat between players on the same network
  4. Expanded Features II

    • Player

      • Use items
    • Story Engine

      • Virus transmission
      • Multiple stages / "semesters"
    • UI

      • Receive Emails
    • Map

      • Multiple levels / areas
    • Physics

      • Thrown objects
      • Force collisions
  5. Advanced Features I

    • Multi-player

      • Players on the same network can see each-other
  6. Advanced Features II

    • God mode
    • UI

      • Send emails
    • Multi-player

      • Quests involving more than one person

Legend

  • ✅ Complete
  • 🔨 In Progress

Iterations

Iterations
Static Members
Retrospective 1
Retrospective 2

renderConversation

renderConversation.

renderConversation(conversation: Object, updateConvo: any)
Parameters
conversation (Object)
Name Description
conversation.character Character
conversation.currentDialog currentDialog
updateConvo (any)
Example
renderConversation(gameState.conversation);