Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

HTML

              
                .container
  .game
              
            
!

CSS

              
                @import url('https://fonts.googleapis.com/css?family=Squada+One');

*,
*:before,
*:after {
  box-sizing: border-box;
}
.container {
  position: releative;
  height: 100vh;
  font-family: 'Squada One', cursive;
  font-size: calc(2vw + 1em);
  background-color: #000;
  background-image: url('https://s3.amazonaws.com/pg-image-host/codepen/Group.svg');
  background-position: 0% center;
  animation: scrollBackground .2s steps(6) infinite;
  background-size: 50%;
  &:before {
    content: '';
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background-image: linear-gradient(
      to bottom right,
      rgba(5, 0, 0, .8),
      rgba(0, 0, 0, .9)
    );
    opacity: .9;
  }
}
@keyframes scrollBackground {
  100% {
    background-position: 50% -50%;
  }
}

.game {
  position: relative;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  color: #fff;
}
.box {
  position: absolute;
  bottom: 0vh;
  width: 5vw;
  height: 5vw;
  background: blue;
  z-index: 100;
}
.playerControls {
  padding: 1em;
  position: absolute;
  bottom: 0vh;
  text-align: center;
  cursor: pointer;
  background: rgba(0, 0, 0, .6);
}
.playerControls > .playerControls-btn {
  padding: 1em;
}
.startScreen {
  height: 100vh;
  border: .5em solid #fff;
  padding: 1em;
  cursor: pointer;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  z-index: 1000;
  background: rgba(0, 0, 0, .8);
}
.startScreen h1,
.startScreen h2 {
  text-shadow: .25vw .25em rgba(75, 0, 130, .5);
}
.startScreen > div {
  flex: 100%;
  text-align: center;
}
kbd {
  background: #fff;
  color: #000;
  margin: .5em;
  padding: 0.5em;
}
.game-msg{
  color: red;
}
.game-msg.flashing{
  animation: flashing 1s infinite;
}
@keyframes flashing{
  0%{
    opacity: 0;
  }
  100%{
    opacity: 1;
  }
}
              
            
!

JS

              
                /**
 * @fileOverview A demo version of the classic game and an attempt to imitate the redux pattern without libraries. Runs on actions, reducers, and store.
 * @author     philgrayphilgray
 * @date       09/04/2017
 * @requires   A modern browser!
 **/
/**
 * initState is an object passed to the first createStore call.
 **/
const initState = {
  view: "startScreen",
  gameOn: false,
  kills: 0,
  lives: 3,
  itemModel: {
    x: 45,
    y: 30,
    height: 5,
    width: 5,
    velocity: 1
  }
};
/**
 * Actions - Keys for payloads to send to the store.
 **/
const START_GAME = "START_GAME";
const CREATE_ENEMY = "CREATE_ENEMY";
const MOVE_ITEM = "MOVE_ITEM";
const CREATE_PLAYER = "CREATE_PLAYER";
const MOVE_PLAYER = "MOVE_PLAYER";
const CHECK_COLLISION = "CHECK_COLLISION";
const FIRE_MISSILE = "FIRE_MISSILE";
const DESTROY_ITEM = "DESTROY_ITEM";
const END_GAME = "END_GAME";
/***
 * Action Creators - Return payloads of data which get passed into store.dispatch() and trigger transformations and side-effect (oops) in reducers().
 **/
/**
 * Returns the key that triggers the reducers to create a player and add it to state, set gameOn to true, set the view to gameBoard, add empty arrays for enemy and missiles to state, and initiate the main animation loop, which creates enemies and causes things to move.
 * TODO: This action is too robust and has side-effects. Is there a better way to spread out this functionality? Perhaps initiate it with a config object.
 **/
function startGame() {
  return { type: START_GAME };
}
/**
 * Action to create a new enemy.
 * @param {number} maxEnemies The maximum number of enemies that can exist in the current state.
 * @returns {object} state with a new enemy object pushed into the enemy array.
 **/
function createEnemy(maxEnemies) {
  return { type: CREATE_ENEMY, maxEnemies };
}
/**
 * Action to move any item.
 * @param {array} group An array of objects with coordinates to be transformed.
 * @param {string} dir A direction to move in. Example: "up", "down", "left", "right".
 **/
function moveItem(group, dir, vel) {
  return { type: MOVE_ITEM, group, dir, vel };
}
function createPlayer() {
  return { type: CREATE_PLAYER };
}
/**
 * TODO: Consider using a more generic action or name for handling keyboard input.
 **/
function movePlayer(event) {
  return { type: MOVE_PLAYER, event };
}

function checkCollision(first, second) {
  return { type: CHECK_COLLISION, first, second };
}

function fireMissile({ x, y, width }) {
  return { type: FIRE_MISSILE, x, y, width };
}

function destroyItem(item, index) {
  return { type: DESTROY_ITEM, item, index };
}
function endGame() {
  return { type: END_GAME };
}
/**
 * Helpers - Mostly reusable functions pulled from reducers() to improve readability.
 **/
/**
 * Create a new item from a prototype and some props.
 * @param {object} model Object to start with.
 * @param {object} newProps Properties to assign to aforementioned object.
 */

const createNewItem = (model, newProps) => {
  const newItem = Object.create(model);
  return Object.assign(newItem, newProps);
};
/**
 * Random number generator.
 * @param {number} min
 * @param {number} max
 * @return {number} A random number between min and max.
 **/
const generateRandomNumber = (min, max) => {
  return Math.floor(Math.random() * max) + min;
};
/**
 * Combines multiple reducers into one to reduce the number of calls when multiple transformations need to be performed all at once. Redux has this method, and believe this is at least a simplified version of what it does.
 * @param {array} rs An array of store.dispatch() calls.
 * @returns {object} A state object, merged with transformations.
 **/
const combineReducers = (...rs) => {
  return Object.assign(...rs);
};
/**
 * Detects if there's a collision between two objects that both have coordinates and dimmensions.
 * @param {object} a First item to compare.
 * @param {object} b Second item to compare.
 * @returns {boolean}
 **/
const detectCollision = (a, b) => {
  return (
    a.x < b.x + b.width &&
    a.x + a.width > b.x &&
    a.y < b.y + b.height &&
    a.height + a.y > b.y
  );
};
/**
 * TODO: Movement transformations should be outsourced to MOVE_ITEM or a better named function. This could be made a more generic function, for instance it could just return a keyCode and turn transform it into an action to perform. State really shouldn't be modified outside of reducers().
 **/
const handlePlayerInput = (state, k) => {
  const player = state.player;
  if (k === 13 && !state.gameOn) {
    store.dispatch(startGame()); //shoot or startGame
  }
  if (k === 32 && state.gameOn) {
    store.dispatch(fireMissile(player));
  }
  if (k === 27) {
    store.dispatch(endGame()); // escape
    state.lives = 3;
    state.kills = 0;
  }
  if (k === 37 && player.x > 0 && state.lives) {
    player.x -= player.velocity; //left
  }
  if (k === 38 && player.y < 95 && state.lives) {
    player.y += player.velocity; //up
  }
  if (k === 39 && player.x < 95 && state.lives) {
    player.x += player.velocity; //right
  }
  if (k === 40 && player.y > 0 && state.lives) {
    player.y -= player.velocity; //down
  }
  return state;
};
/**
 * Reducers - Accept an action payload and perform transformations on a copy of the previous state. Warning: this is basically an infinite loop with createStore().
 * @returns {object} state Mutated state that gets passed into createStore() to create the new state that gets rendered on every dispatch().
 **/
const reducers = (state = initialState, action) => {
  const refreshRate = 100;
  switch (action.type) {
    /**
     * Setup state for new game.
     **/
    case "START_GAME":
      console.log("start game", state);
      store.dispatch(createPlayer());
      state.gameOn = true;
      state.view = "gameBoard";
      state.enemy = [];
      state.missiles = [];
      state.counter = 0;
      if (state.gameOn) {
        //main loop to move and create all non-player items
        state.interval = setInterval(() => {
          state.counter += refreshRate / 1000;
          combineReducers([
            store.dispatch(createEnemy(3)),
            store.dispatch(moveItem(state.enemy, "down")),
            store.dispatch(moveItem(state.missiles, "up")),
            store.dispatch(checkCollision(state.enemy, state.missiles)),
            store.dispatch(checkCollision(state.enemy, [state.player]))
          ]);
        }, refreshRate);
      }
      return state;
    /**
     * Push a new enemy into state every few seconds if there aren't already too many. Runs every time the main loop runs.
     **/
    case "CREATE_ENEMY":
      if (
        state.enemy.length <
          action.maxEnemies + Math.floor(state.counter / 10) &&
        Math.round(state.counter * refreshRate) % 6 === 0
      ) {
        const enemyProps = {
          x: generateRandomNumber(1, 95),
          y: 100,
          velocity: generateRandomNumber(1, 3),
          name: "enemy"
        };
        const enemy = createNewItem(state.itemModel, enemyProps);
        state.enemy.push(enemy);
        return state;
      }
      return state;
    /**
     * @property {array} action.group An array of items to move.
     * @param {string} action.dir A direction to move in. Used to determine +/- and x/y.
     **/
    case "MOVE_ITEM":
      const dir = action.dir === "down" || action.dir === "left" ? -1 : 1;
      const xOrY = action.dir === "left" || action.dir === "right" ? "x" : "y";
      action.group.map(e => (e[xOrY] += (action.vel || e.velocity) * dir));
      action.group.map((e, i) => {
        if (e.y > 110 || e.y < -10) {
          store.dispatch(destroyItem(e, i));
        }
      });
      return state;
    case "CREATE_PLAYER":
      state.player = createNewItem(state.itemModel, {
        velocity: 6,
        name: "player"
      });
      return state;
    /**
     * TODO: Must be validated if handler is changed to dispatch actions.
     * @event e Keyboard event
     **/
    case "MOVE_PLAYER":
      handlePlayerInput(state, action.event.keyCode);
      return state;

    case "FIRE_MISSILE":
      const missileProps = {
        x: action.x + action.width / 3,
        y: action.y + 5,
        width: action.width / 6,
        velocity: 3,
        height: 2,
        name: "missiles"
      };
      const m = createNewItem(state.itemModel, missileProps);
      state.missiles.push(m);
      return state;

    case "CHECK_COLLISION":
      if (action.first.length > 0 && action.second.length > 0) {
        let currentFirst = {};
        for (let i = 0; i < action.first.length; i++) {
          currentFirst = action.first[i];
          for (let j = 0; j < action.second.length; j++) {
            if (detectCollision(currentFirst, action.second[j])) {
              if (
                currentFirst.name === "enemy" &&
                action.second[j].name === "missiles" &&
                state.lives > 0
              ) {
                state.kills++;
              }
              if (action.second[j].name === "player" && state.lives > 0) {
                state.lives--;
              }
              combineReducers([
                store.dispatch(destroyItem(action.first[i], i)),
                store.dispatch(destroyItem(action.second[j], j))
              ]);
            }
          }
        }
      }
      return state;

    case "DESTROY_ITEM":
      if (action.item.name === "player") {
        store.dispatch(endGame());
        return state;
      }
      if (state[action.item.name]) {
        state[action.item.name].splice(action.index, 1);
      }
      return state;
    /**
     * Resets view and gameOn. Stops the main loop. Does not stop the key listener. This needs to be addressed if key handler is changed to dispatch actions.
     * TODO: Calculate scores.
     **/
    case "END_GAME":
      console.log("end game");
      clearInterval(state.interval);
      state.view = "startScreen";
      state.gameOn = false;
      return state;
    default:
      return state;
  }
};
/**
 * Components
 * TODO: Better to not use innerHTML? It's nice for vanilla templating.
 * @property {string} name Not used. May be helpful for dynamic classes.
 * @property {string} view The render function uses this to filter only the components in the current view.
 * property {function} render A function that renders the raw html.
 **/
const components = [
  {
    name: "startButton",
    view: "startScreen",
    render: () => {
      /** TODO: Add game over message including score and high score. **/
      const gameMessage =
        store.getState().lives < 3
          ? `<div><h3 class="game-msg flashing">SCORE: ${
              store.getState().kills
            }</h3></div>`
          : "";
      return `<div class="startScreen" 
  onclick=store.dispatch(startGame())>
  <div><h1>spaceInvaders()</h1><h2>a redux-inspired vanilla video game</h2></div>
${gameMessage}
  <div><p><kbd>Enter</kbd></p><p>or Click to Start</p></div>
  </div>`;
    }
  },
  {
    name: "playerControls",
    view: "gameBoard",
    render: () => {
      /** a little clock to visualize the timing of state actions. not based on real time. **/
      let counter = Math.floor(store.getState().counter);
      let health = store.getState().lives
        ? `Lives: ${store.getState().lives} | Kills: ${store.getState().kills}`
        : `<span class="game-msg">Game Over. <br/>Insert 2 quarters.</a>`;
      let counterDisplay =
        counter >= 60
          ? `${Math.floor(counter / 60)}:${Math.round(counter % 60)}`
          : counter;
      return `<div class="playerControls"><div onclick=store.dispatch(endGame())><kbd>ESC</kbd> to Quit</div><p>Counter: ${counterDisplay}</p><p>${health}</p></div>`;
    }
  },
  {
    name: "player",
    view: "gameBoard",
    render: () => {
      const player = store.getState().player;
      const markup = `<div style='bottom:${player.y}vh; left:${
        player.x
      }vw' class='box'></div>`;
      return !player.destroyed ? markup : "";
    }
  },
  {
    name: "objects",
    view: "gameBoard",
    render: () => {
      const enemies = store.getState().enemy
        ? store
            .getState()
            .enemy.map(
              e =>
                `<div class="box" style="bottom:${e.y}vh; left:${
                  e.x
                }vw; background: purple;"></div>`
            )
            .join("")
        : "";
      const missiles = store.getState().missiles
        ? store
            .getState()
            .missiles.map(
              e =>
                `<div class="box" style="bottom:${e.y}vh; left:${
                  e.x
                }vw; background: red; width:${e.width}vw; height: ${
                  e.height
                }vw;"></div>`
            )
            .join("")
        : "";
      return `${enemies}${missiles}`;
    }
  }
];
/**
 * createStore()
 * @param {object} reducer
 * @param {object} preloadedState
 *
 **/
function createStore(reducer, preloadedState) {
  return (function() {
    return {
      /**
       * Sets the current state as either the new state or the initial state.
       **/
      state() {
        return preloadedState || reducer;
      },
      /**
       * Returns the current state from anywhere.
       **/
      getState() {
        return this.state();
      },
      /**
       * Passes old state to reducer, creates new state, and renders it whenever called.
       * @param {function} action
       **/
      dispatch(action) {
        const newState = createStore(reducer(this.getState(), action));
        render(newState.getState());
      }
    };
  })();
}
const game = document.querySelector(".game");
/**
 * render() - A side-effect function that replaces the DOM content every time store.dispatch is called. Filters components for which the view is equal to the current view, calls their render function, and joins them.
 **/
const render = state => {
  game.innerHTML = components
    .filter(c => c.view === state.view)
    .map(c => c.render())
    .join("");
};
/**
 * Initial render call using initial state returned from store
 **/
const store = createStore(reducers, initState);
render(store.getState());
/**
 * A global event listener for keyboard input. Calls movePlayer with event. However, also responsible for starting stopping game and most likely firing missiles.
 * @event keydown
 **/
document.addEventListener(
  "keydown",
  function handleKeys(e) {
    store.dispatch(movePlayer(e));
  },
  true
);

              
            
!
999px

Console