Pen Settings

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

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

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

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation

     

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.

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.

            
              <div id="elGameboard" data-next-action="EndRotate"></div> 


<div class="ui">
  <div id="elKeyboardControls"></div>
  <div id="elTurnCounter"></div>
</div>

<div id="elBanner"></div>
            
          
!
            
              * { box-sizing: border-box; }
body {
  --size-block: 75px;
  --size-cube: calc(var(--size-block) / 2);
  --color-background: #000;
  --color-foreground: #fff;
  --color-highlight: #ff1ead;
  height: 100vh;
  perspective: 100vh;
  margin: 0;
  display: grid;
  place-items: center;
  padding: 1rem;
  background: var(--color-background);
  color: var(--color-foreground); 
}


.player {
  transition-timing-function: cubic-bezier(.73,.55,1,.93);
}


/*
 * Gameboard .grid
 * Just a simple 6x6 Grid that allows 3d children
 */
.grid {
  font-size: var(--size-block);
  transform-style: preserve-3d;
  display: grid;
  grid-template-columns: repeat(6, 1em);
  grid-template-rows: repeat(6, 1em);
}
.grid > div {
  height: 1em;
  width: 1em;
  transform-style: preserve-3d;
}


/*
 * root-3d
 * Sets up the element and it's children to become a 3D object.
 */
.root-3d,
.root-3d > div {
  font-size: var(--size-block);
  height: 1em;
  width: 1em;
}
.root-3d {
  transform-style: preserve-3d;
  position: relative;
}
.root-3d > div {
  /* I have some faces rotated the wrong way, instead of fixing them,
     I can hide them by showing the backfaces.
     TODO: Fix the mixed up faces.
   */
/*   backface-visibility: hidden; */
  position: absolute;
}


/* Rotate each face to make a cube */
/* Rotatate and move the faces into place */
.cube-3d > div:nth-child(1) { transform: rotateY(0deg)   translateZ(0.5em);}
.cube-3d > div:nth-child(2) { transform: rotateX(-90deg) translateZ(0.5em);}
.cube-3d > div:nth-child(3) { transform: rotateY(-90deg) translateZ(0.5em);}
.cube-3d > div:nth-child(4) { transform: rotateY(90deg)  translateZ(0.5em);}
.cube-3d > div:nth-child(5) { transform: rotateX(90deg)  translateZ(0.5em);}
.cube-3d > div:nth-child(6) { transform: rotateY(180deg) translateZ(0.5em);}




/**
 * A 3D Building in CSS
 */
.building-3d > div {
  background-color: var(--color-background);
  border: thin dashed var(--color-foreground);
}
/* Create a brush for box-shadow drawing. */
.building-3d > div::before {
  content: '';
  display: block;
  height: 0.20em;
  width: 0.20em;
  position: absolute;
  bottom: 0;
  left: 0;  
}


/* Tall Building type */
.building-3d.tall > div {
  height: 2em;
}
/* Roof  */ .building-3d.tall > div:nth-child(1) {transform: translateZ(1.5em); height: 1em;}
/* Front */ .building-3d.tall > div:nth-child(2) {transform: rotateX(-90deg) translateY(-0.5em);}
/* Left  */ .building-3d.tall > div:nth-child(3) {transform: rotateX(90deg) rotateY(90deg) translateZ(-0.5em) translateX(-0.5em) translateY(0.5em);}
/* Right */ .building-3d.tall > div:nth-child(4) {transform: rotateX(90deg) rotateY(90deg) translateZ(0.5em) translateX(-0.5em) translateY(0.5em);}
/* Back  */ .building-3d.tall > div:nth-child(5) {transform: translateY(-1em) translateZ(0.5em) rotateX(90deg);}

/* Short Building type */
.building-3d.short > div {
  height: 1em;
}
/* Roof  */ .building-3d.short > div:nth-child(1) {transform: translateZ(0.5em); width: 2em;}
/* Front */ .building-3d.short > div:nth-child(2) {transform: rotateX(90deg) translateZ(-0.5em); width: 2em;}
/* Back  */ .building-3d.short > div:nth-child(3) {transform: rotateX(90deg) translateZ(0.5em);  width: 2em;}
/* Left  */ .building-3d.short > div:nth-child(4) {transform: rotateX(90deg) rotateY(90deg) translateZ(-0.5em);}
/* Right */ .building-3d.short > div:nth-child(5) {transform: rotateX(90deg) rotateY(90deg) translateZ(1.5em);}


/**
 * UI is a fixed 2d layer.
 * Used to provied UI elements over the 3d space.
 */
.ui {
  font-size: 20pt;
  position: fixed;
  bottom: 1em;
  left: 2em;
  right: 2em;
  display: flex;
  justify-content: space-around;
}

#elTurnCounter {
  font-family: monospace;
  text-shadow: 1px 1px 0.15em var(--color-foreground);
}

.keyboard-wasd {
  display: grid;
  grid-template-columns: repeat(3, 1em);
  grid-template-rows: repeat(2, 1em);
  grid-gap: 1em;
}
.keyboard-wasd > div,
.keyboard--key {
  /* Center the content */
  display: grid;
  place-items: center;
  /* Style it to look like a keyboard key */
  border-radius: 0.3em;
  border: 1px solid #444;
  box-shadow: 1px 1px 10px 1px #222;
  border-bottom-color: #555;
  /* Give a mouse pointer over it */
  cursor: pointer;
}
.keyboard-wasd > div {
  width: 1.5em;
  height: 1.5em;
}
/* Hide the Q/E Keys unless pressed */
.keyboard-wasd [code="KeyQ"],
.keyboard-wasd [code="KeyE"] {
  color: var(--color-background);
  border: none;
  box-shadow: none;
  cursor: default;
}
/* Position on the "keyboard" */
.keyboard-wasd :nth-child(1) {
  grid-column: 2;
}
.keyboard-wasd :nth-child(1),
.keyboard-wasd :nth-child(5),
.keyboard-wasd :nth-child(6) {
  grid-row: 1;
}
.keyboard-wasd :nth-child(2),
.keyboard-wasd :nth-child(3),
.keyboard-wasd :nth-child(4) {
  grid-row: 2;
}
.keyboard-wasd :nth-child(5) {
  grid-column: 1;
}
.keyboard-wasd :nth-child(6) {
  grid-column: 3;
}
/* Animate when the key changes */
.keyboard-wasd[lastKey="KeyW"] > [code="KeyW"],
.keyboard-wasd[lastKey="KeyA"] > [code="KeyA"],
.keyboard-wasd[lastKey="KeyS"] > [code="KeyS"],
.keyboard-wasd[lastKey="KeyD"] > [code="KeyD"],
.keyboard-wasd[lastKey="KeyQ"] > [code="KeyQ"],
.keyboard-wasd[lastKey="KeyE"] > [code="KeyE"] {
  animation: fadeout 1.5s;
}


@keyframes fadeout {
  from {
    background-color: var(--color-highlight);
  }
  to {
    background-color: var(--color-background);
  }
}




#elBanner {
  position: fixed;
  width: 100%;
  background-color: var(--color-foreground);
  color: var(--color-background);
  box-shadow: 1px 1px 10px 10px var(--color-foreground);
  padding: 1em 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
#elBanner > * {
  width: 30vw;
  text-align: center;
  padding: 0;
  margin: 0;
}
#elBanner:empty {
  display: none;
}



#elGameboard {
  position: relative;
  font-size: var(--size-block);
  width: 6em;
  height: 6em;
  transform: rotateX(0deg);
  transition: transform 1s ease-in-out;
  transform-style: preserve-3d; /* preserve-3d so children can stay 3D */
}
#elGameboard > .grid {
  position: absolute;
}


.grid.layer--floor > div {
  border: thin solid var(--color-foreground);
}
.grid.layer--objects {
  transform: translateZ(0.5em);
}

#elGameboard .grid .player {
  border: none;
}
.player > div {
  background-color: var(--color-highlight);
  border: 3px dashed var(--color-background);
}





/**
 * Colors!
 */
.palette--black {
  background: var(--color-background);
}
.palette--white {
  background: var(--color-foreground)
}
.palette--active {
  background: var(--color-highlight);
}


.layer--floor .palette--active {
  box-shadow: 0px 0px 5px 2px var(--color-foreground);
}


/*
 * Box Shadow Pixel art
 * There are better/more programic ways to generate theses.
 * But I want to get better at using them, so long form it is.
 */

/* 
 * Building "Skin" Art 
 */
/* Roof Skins */
.building-3d.tall > div:nth-child(1)::before {
  box-shadow: inset 0 0 0.1em 0.02em var(--color-foreground);
  width: 1em;
  height: 1em;
}
.building-3d.tall .skin-1::before {
   box-shadow: 
    0.15em -0.3em 5px var(--color-background),  0.6em -0.3em 5px var(--color-background),
    0.15em -0.3em 5px var(--color-foreground),  0.6em -0.3em 5px var(--color-foreground),
     
    0.15em -0.6em 5px var(--color-background),  0.6em -0.6em 5px var(--color-background),
    0.15em -0.6em 5px var(--color-foreground),  0.6em -0.6em 5px var(--color-foreground),
     
    /*0.15em -0.9em 5px var(--color-background),*/  0.6em -0.9em 5px var(--color-background),
    0.15em -0.9em 5px var(--color-foreground),  0.6em -0.9em 5px var(--color-foreground),
     
    0.15em -1.2em 5px var(--color-background),  0.6em -1.2em 5px var(--color-background),
    0.15em -1.2em 5px var(--color-foreground),  0.6em -1.2em 5px var(--color-foreground),
     
    0.15em -1.5em 5px var(--color-background),  0.6em -1.5em 5px var(--color-background),
    0.15em -1.5em 5px var(--color-foreground),  0.6em -1.5em 5px var(--color-foreground); 
}
.building-3d.tall .skin-2::before {
   box-shadow: 
    /*0.15em -0.3em 5px var(--color-background),*/  0.6em -0.3em 5px var(--color-background),
    0.15em -0.3em 5px var(--color-foreground),  0.6em -0.3em 5px var(--color-foreground),
     
    0.15em -0.6em 5px var(--color-background),  0.6em -0.6em 5px var(--color-background),
    0.15em -0.6em 5px var(--color-foreground),  0.6em -0.6em 5px var(--color-foreground),
     
    0.15em -0.9em 5px var(--color-background),  /*0.6em -0.9em 5px var(--color-background),*/
    0.15em -0.9em 5px var(--color-foreground),  0.6em -0.9em 5px var(--color-foreground),
     
    0.15em -1.2em 5px var(--color-background),  /*0.6em -1.2em 5px var(--color-background),*/
    0.15em -1.2em 5px var(--color-foreground),  0.6em -1.2em 5px var(--color-foreground),
     
    0.15em -1.5em 5px var(--color-background),  0.6em -1.5em 5px var(--color-background),
    0.15em -1.5em 5px var(--color-foreground),  0.6em -1.5em 5px var(--color-foreground); 
}
.building-3d.tall .skin-3::before {
   box-shadow: 
    0.15em -0.3em 5px var(--color-background),  0.6em -0.3em 5px var(--color-background),
    0.15em -0.3em 5px var(--color-foreground),  0.6em -0.3em 5px var(--color-foreground),
     
    /*0.15em -0.6em 5px var(--color-background),*/  0.6em -0.6em 5px var(--color-background),
    0.15em -0.6em 5px var(--color-foreground),  0.6em -0.6em 5px var(--color-foreground),
     
    0.15em -0.9em 5px var(--color-background),  0.6em -0.9em 5px var(--color-background),
    0.15em -0.9em 5px var(--color-foreground),  0.6em -0.9em 5px var(--color-foreground),
     
    0.15em -1.2em 5px var(--color-background),  /*0.6em -1.2em 5px var(--color-background),*/
    0.15em -1.2em 5px var(--color-foreground),  0.6em -1.2em 5px var(--color-foreground),
     
/*     0.15em -1.5em 5px var(--color-background),  0.6em -1.5em 5px var(--color-background), */
    0.15em -1.5em 5px var(--color-foreground),  0.6em -1.5em 5px var(--color-foreground); 
}
.building-3d.tall .skin-4::before {
   box-shadow: 
/*     0.15em -0.3em 5px var(--color-background),  0.6em -0.3em 5px var(--color-background), */
    0.15em -0.3em 5px var(--color-foreground),  0.6em -0.3em 5px var(--color-foreground),
     
/*     0.15em -0.6em 5px var(--color-background),  0.6em -0.6em 5px var(--color-background), */
    0.15em -0.6em 5px var(--color-foreground),  0.6em -0.6em 5px var(--color-foreground),
     
    0.15em -0.9em 5px var(--color-background),  0.6em -0.9em 5px var(--color-background),
    0.15em -0.9em 5px var(--color-foreground),  0.6em -0.9em 5px var(--color-foreground),
     
/*     0.15em -1.2em 5px var(--color-background),  0.6em -1.2em 5px var(--color-background), */
    0.15em -1.2em 5px var(--color-foreground),  0.6em -1.2em 5px var(--color-foreground),
     
    /*0.15em -1.5em 5px var(--color-background),*/  0.6em -1.5em 5px var(--color-background),
    0.15em -1.5em 5px var(--color-foreground),  0.6em -1.5em 5px var(--color-foreground); 
}




/* Short Building Skins
 */
.building-3d.short > div:nth-child(1)::before {
  height: 1em;
  width: 2em;
  box-shadow: 
    inset 0 0 0.1em 0.02em var(--color-foreground);
}
.building-3d.short > div:nth-child(2)::before,
.building-3d.short > div:nth-child(3)::before {
  top: 0;
  height: 0.15em;
  width: 0.15em;
  /* Create a bunch of "open" windows */
/*   box-shadow: 
    0.1em 0.65em 0.1em 0.02em var(--color-foreground), 0.5em 0.65em 0.1em 0.02em var(--color-foreground), 0.9em 0.65em 0.1em 0.02em var(--color-foreground), 1.3em 0.65em 0.1em 0.02em var(--color-foreground), 1.7em 0.65em 0.1em 0.02em var(--color-foreground),
    0.1em 0.3em 0.1em 0.02em var(--color-foreground), 0.5em 0.3em 0.1em 0.02em var(--color-foreground), 0.9em 0.3em 0.1em 0.02em var(--color-foreground), 1.3em 0.3em 0.1em 0.02em var(--color-foreground), 1.7em 0.3em 0.1em 0.02em var(--color-foreground)
    ; */
}
.building-3d.short > div:nth-child(2).skin-1::before,
.building-3d.short > div:nth-child(3).skin-1::before {
  box-shadow: 
    0.1em 0.65em 0.1em 0.02em var(--color-background), /*0.5em 0.65em 0.1em 0.02em var(--color-background),*/ 0.9em 0.65em 0.1em 0.02em var(--color-background), /*1.3em 0.65em 0.1em 0.02em var(--color-background),*/ 1.7em 0.65em 0.1em 0.02em var(--color-background),
    0.1em 0.65em 0.1em 0.02em var(--color-foreground), 0.5em 0.65em 0.1em 0.02em var(--color-foreground), 0.9em 0.65em 0.1em 0.02em var(--color-foreground), 1.3em 0.65em 0.1em 0.02em var(--color-foreground), 1.7em 0.65em 0.1em 0.02em var(--color-foreground),
    
    /*0.1em 0.3em 0.1em 0.02em var(--color-background), 0.5em 0.3em 0.1em 0.02em var(--color-background),*/ 0.9em 0.3em 0.1em 0.02em var(--color-background), 1.3em 0.3em 0.1em 0.02em var(--color-background), 1.7em 0.3em 0.1em 0.02em var(--color-background),
    0.1em 0.3em 0.1em 0.02em var(--color-foreground), 0.5em 0.3em 0.1em 0.02em var(--color-foreground), 0.9em 0.3em 0.1em 0.02em var(--color-foreground), 1.3em 0.3em 0.1em 0.02em var(--color-foreground), 1.7em 0.3em 0.1em 0.02em var(--color-foreground);
}
.building-3d.short > div:nth-child(2).skin-2::before,
.building-3d.short > div:nth-child(3).skin-2::before {
  box-shadow: 
    /*0.1em 0.65em 0.1em 0.02em var(--color-background),*/ 0.5em 0.65em 0.1em 0.02em var(--color-background), /*0.9em 0.65em 0.1em 0.02em var(--color-background), 1.3em 0.65em 0.1em 0.02em var(--color-background),*/ 1.7em 0.65em 0.1em 0.02em var(--color-background),
    0.1em 0.65em 0.1em 0.02em var(--color-foreground), 0.5em 0.65em 0.1em 0.02em var(--color-foreground), 0.9em 0.65em 0.1em 0.02em var(--color-foreground), 1.3em 0.65em 0.1em 0.02em var(--color-foreground), 1.7em 0.65em 0.1em 0.02em var(--color-foreground),
    
    /*0.1em 0.3em 0.1em 0.02em var(--color-background), 0.5em 0.3em 0.1em 0.02em var(--color-background),*/ 0.9em 0.3em 0.1em 0.02em var(--color-background), 1.3em 0.3em 0.1em 0.02em var(--color-background), /*1.7em 0.3em 0.1em 0.02em var(--color-background),*/
    0.1em 0.3em 0.1em 0.02em var(--color-foreground), 0.5em 0.3em 0.1em 0.02em var(--color-foreground), 0.9em 0.3em 0.1em 0.02em var(--color-foreground), 1.3em 0.3em 0.1em 0.02em var(--color-foreground), 1.7em 0.3em 0.1em 0.02em var(--color-foreground);
}
.building-3d.short > div:nth-child(2).skin-3::before,
.building-3d.short > div:nth-child(3).skin-3::before {
  box-shadow: 
    0.1em 0.65em 0.1em 0.02em var(--color-background), /*0.5em 0.65em 0.1em 0.02em var(--color-background), 0.9em 0.65em 0.1em 0.02em var(--color-background),*/ 1.3em 0.65em 0.1em 0.02em var(--color-background), /*1.7em 0.65em 0.1em 0.02em var(--color-background),*/
    0.1em 0.65em 0.1em 0.02em var(--color-foreground), 0.5em 0.65em 0.1em 0.02em var(--color-foreground), 0.9em 0.65em 0.1em 0.02em var(--color-foreground), 1.3em 0.65em 0.1em 0.02em var(--color-foreground), 1.7em 0.65em 0.1em 0.02em var(--color-foreground),
    
    0.1em 0.3em 0.1em 0.02em var(--color-background), /*0.5em 0.3em 0.1em 0.02em var(--color-background),*/ 0.9em 0.3em 0.1em 0.02em var(--color-background), /*1.3em 0.3em 0.1em 0.02em var(--color-background),*/ 1.7em 0.3em 0.1em 0.02em var(--color-background),
    0.1em 0.3em 0.1em 0.02em var(--color-foreground), 0.5em 0.3em 0.1em 0.02em var(--color-foreground), 0.9em 0.3em 0.1em 0.02em var(--color-foreground), 1.3em 0.3em 0.1em 0.02em var(--color-foreground), 1.7em 0.3em 0.1em 0.02em var(--color-foreground);
}
.building-3d.short > div:nth-child(2).skin-4::before,
.building-3d.short > div:nth-child(3).skin-4::before {
  box-shadow: 
    0.1em 0.65em 0.1em 0.02em var(--color-background), 0.5em 0.65em 0.1em 0.02em var(--color-background), 0.9em 0.65em 0.1em 0.02em var(--color-background), 1.3em 0.65em 0.1em 0.02em var(--color-background), 1.7em 0.65em 0.1em 0.02em var(--color-background),
    0.1em 0.65em 0.1em 0.02em var(--color-foreground), 0.5em 0.65em 0.1em 0.02em var(--color-foreground), 0.9em 0.65em 0.1em 0.02em var(--color-foreground), 1.3em 0.65em 0.1em 0.02em var(--color-foreground), 1.7em 0.65em 0.1em 0.02em var(--color-foreground),
    
    0.1em 0.3em 0.1em 0.02em var(--color-background), /*0.5em 0.3em 0.1em 0.02em var(--color-background),*/ 0.9em 0.3em 0.1em 0.02em var(--color-background), /*1.3em 0.3em 0.1em 0.02em var(--color-background),*/ 1.7em 0.3em 0.1em 0.02em var(--color-background),
    0.1em 0.3em 0.1em 0.02em var(--color-foreground), 0.5em 0.3em 0.1em 0.02em var(--color-foreground), 0.9em 0.3em 0.1em 0.02em var(--color-foreground), 1.3em 0.3em 0.1em 0.02em var(--color-foreground), 1.7em 0.3em 0.1em 0.02em var(--color-foreground);
}
/* Short Side */
.building-3d.short > div:nth-child(4)::before,
.building-3d.short > div:nth-child(5)::before {
  top: 0;
  height: 0.15em;
  width: 0.15em;
  /* Create a bunch of "open" windows */
/*   box-shadow: 
    0.2em 0.65em 0.1em 0.02em var(--color-foreground), 0.6em 0.65em 0.1em 0.02em var(--color-foreground),     
    0.2em 0.3em 0.1em 0.02em var(--color-foreground), 0.6em 0.3em 0.1em 0.02em var(--color-foreground)
    ; */
}
.building-3d.short > div:nth-child(4).skin-1::before,
.building-3d.short > div:nth-child(5).skin-1::before {
  box-shadow: 
    0.2em 0.65em 0.1em 0.02em var(--color-background), 0.6em 0.65em 0.1em 0.02em var(--color-background),
    0.2em 0.65em 0.1em 0.02em var(--color-foreground), 0.6em 0.65em 0.1em 0.02em var(--color-foreground),     
    
    0.2em 0.3em 0.1em 0.02em var(--color-background), 0.6em 0.3em 0.1em 0.02em var(--color-background),
    0.2em 0.3em 0.1em 0.02em var(--color-foreground), 0.6em 0.3em 0.1em 0.02em var(--color-foreground)
    ;
}
.building-3d.short > div:nth-child(4).skin-2::before,
.building-3d.short > div:nth-child(5).skin-2::before {
    box-shadow: 
    /*0.2em 0.65em 0.1em 0.02em var(--color-background),*/ 0.6em 0.65em 0.1em 0.02em var(--color-background),
    0.2em 0.65em 0.1em 0.02em var(--color-foreground), 0.6em 0.65em 0.1em 0.02em var(--color-foreground),     
    
   /* 0.2em 0.3em 0.1em 0.02em var(--color-background), 0.6em 0.3em 0.1em 0.02em var(--color-background),*/
    0.2em 0.3em 0.1em 0.02em var(--color-foreground), 0.6em 0.3em 0.1em 0.02em var(--color-foreground)
    ;
}
.building-3d.short > div:nth-child(4).skin-3::before,
.building-3d.short > div:nth-child(5).skin-3::before {
  box-shadow: 
    /*0.2em 0.65em 0.1em 0.02em var(--color-background),*/ 0.6em 0.65em 0.1em 0.02em var(--color-background),
    0.2em 0.65em 0.1em 0.02em var(--color-foreground), 0.6em 0.65em 0.1em 0.02em var(--color-foreground),     
    
    0.2em 0.3em 0.1em 0.02em var(--color-background),/* 0.6em 0.3em 0.1em 0.02em var(--color-background),*/
    0.2em 0.3em 0.1em 0.02em var(--color-foreground), 0.6em 0.3em 0.1em 0.02em var(--color-foreground)
    ;
}
.building-3d.short > div:nth-child(4).skin-4::before,
.building-3d.short > div:nth-child(5).skin-4::before {
  box-shadow: 
    /*0.2em 0.65em 0.1em 0.02em var(--color-background), 0.6em 0.65em 0.1em 0.02em var(--color-background),*/
    0.2em 0.65em 0.1em 0.02em var(--color-foreground), 0.6em 0.65em 0.1em 0.02em var(--color-foreground),     
    
   /* 0.2em 0.3em 0.1em 0.02em var(--color-background), 0.6em 0.3em 0.1em 0.02em var(--color-background),*/
    0.2em 0.3em 0.1em 0.02em var(--color-foreground), 0.6em 0.3em 0.1em 0.02em var(--color-foreground)
    ;  
}
            
          
!
            
              import {render, html} from '//unpkg.com/lighterhtml?module';
const WIDTH = 6;
const defaultState = window.defaultState = {
  didWin: false,
  camera: { y: 45, x: 0, z: 200, rotation: 0 },
  colors: ['#000', '#fff', '#ff1ead'],
  collisionMap: [],
  turnCount: 0.0,
  lastKey: null,
  levelIndex: 0,
  levels: [{
    turnPar: 6.0,
    gameboard: [
      0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0,
      0, 0, 0, 1, 0, 0,
    ],
    player: {x: 4, y: 1, type: 'player'},
    objects: [
      {x: 0, y: 0, type: 'building', subtype: 'short', width: 2},
      {x: 1, y: 3, type: 'building', subtype: 'tall'},
      {x: 2, y: 3, type: 'building', subtype: 'short', direction: 90, height: 2},
      {x: 3, y: 0, type: 'building', subtype: 'tall'},
      {x: 4, y: 2, type: 'building', subtype: 'tall'},
      {x: 5, y: 0, type: 'building', subtype: 'tall'},
      {x: 5, y: 1, type: 'building', subtype: 'short', direction: 90, height: 2},
      {x: 5, y: 4, type: 'building', subtype: 'short', direction: 90, height: 2},
    ],
  }, {
    turnPar: 7.0,
    gameboard: [
      0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0,
      0, 0, 0, 1, 0, 0,
    ],
    player: {x: 1, y: 2, type: 'player'},
    objects: [
      {x: 0, y: 3, type: 'building', subtype: 'short', width: 2},
      {x: 0, y: 0, type: 'building', subtype: 'short', width: 2},
      {x: 0, y: 2, type: 'building', subtype: 'tall'},
      {x: 4, y: 5, type: 'building', subtype: 'tall'},
      {x: 5, y: 1, type: 'building', subtype: 'tall'},
      {x: 1, y: 5, type: 'building', subtype: 'tall'},
    ],
  }, {
    turnPar: 9.0,
    gameboard: [
      0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0,
      0, 0, 0, 1, 0, 0,
    ],
    player: {x: 4, y: 1, type: 'player'},
    objects: [
      {x: 0, y: 0, type: 'building', subtype: 'tall'},
      {x: 2, y: 0, type: 'building', subtype: 'tall'},
      {x: 5, y: 1, type: 'building', subtype: 'short', hight: 2, direction: -90},
      {x: 3, y: 4, type: 'building', subtype: 'tall'},
      {x: 1, y: 4, type: 'building', subtype: 'tall'},
      {x: 1, y: 2, type: 'building', subtype: 'short', width: 2},
      {x: 4, y: 2, type: 'building', subtype: 'tall'},
      {x: 2, y: 5, type: 'building', subtype: 'short', hight: 2, direction: -90},
      {x: 5, y: 5, type: 'building', subtype: 'tall'},
    ],
  }],
};
const BLOCK_PATTERNS = [
  /*0*/ {type: 'empty'},
  /*1*/ {className: 'palette--active', isGoal: true },
  /*2*/ {className: 'palette--white'},
];
//
//
// Start the game.
// The JSON.parse(JSON.stringify junk just guarantees a deep clone. Not something we actually need here in this codepen.
window.gameState = JSON.parse(JSON.stringify(defaultState));
//WISH: that I could pass in the inital state here, and all the actions/events everyone will somehow have the latest game state. (instead of the global window.gameState that use now.)
doAction({type: 'NewGame'});

// Listen to events on elements we are not creating.
// of course, if we did a react style App, we wouldn't need to do this.
// But I like showing that the system works with external components and internal ones.
// Bind the camera controls.
document.addEventListener('keyup', keyupToAction);
// Bind gameboard rotation events.
window.elGameboard.addEventListener('transitionend', endAnimationToAction);








//
// Render/Update the DOM with state data.
// This could also be <App /> or something.
// I like using HTML as much as possible, saving the generated markup for annoying/boring stuff.
//
function renderState(state) {
  // Render the Gameboard
  const elGameboard = window.elGameboard;
  render(elGameboard, renderGameboard.bind(null, state));
  
  // Rotate the world/gameboard based on the "camera position".
  elGameboard.style.transform = `rotateX(${state.camera.y}deg) rotateZ(${state.camera.x}deg)`;
  document.body.style.perspective = `${state.camera.z}vh`;
  
  // Keyboard Controls
  const elKeyboardControls = window.elKeyboardControls;
  render(elKeyboardControls, renderKeyboardControls.bind(null, state));
  
  // Turn Counter
  const elTurnCounter = window.elTurnCounter;
  render(elTurnCounter, renderTurnCounter.bind(null, state));
  
  const elBanner = window.elBanner;
  if (state.didWin) {
    render(elBanner, renderBanner.bind(null, {
      turnCount: state.turnCount,
      turnPar: state.turnPar,
      isLastLevel: state.levelIndex >= state.levels.length-1,
    }));
  }
  else {
    render(elBanner, () => html``);
  }
  
  // Update the color vars!
  document.body.style.setProperty('--color-background', state.colors[0]);
  document.body.style.setProperty('--color-foreground', state.colors[1]);
  document.body.style.setProperty('--color-highlight',  state.colors[2]);
}

// Render/Update the gamebaord based on state.
// The Gameboard is made from several layered CSS Grids.
function renderGameboard(state) {
  const { gameboard, objects, player } = state;
  const objectLayer = objects.concat(player); // Add the Player to the list of layer objects.
  
  return html`<div class="layer--floor grid">${gameboard.map((cell, index) => {
    const pattern = BLOCK_PATTERNS[cell];
    return html`<div data-index=${index} class=${pattern.className}></div>`;
  })}</div>
  <div class="layer--objects grid">${objectLayer.map(renderObject)}</div>`;
}

// Render the WASD UI controls.
function renderKeyboardControls(state) {
  const { lastKey } = state;
  // elControlsCamera.setAttribute('lastKey', state.lastKey);
  return html`<div class="keyboard-wasd" lastKey=${lastKey} onclick=${clickToAction}>
  <div code="KeyW">W</div>
  <div code="KeyA">A</div>
  <div code="KeyS">S</div>
  <div code="KeyD">D</div>
  <div code="KeyQ">Q</div>
  <div code="KeyE">E</div>
  </div>`;
}

function renderTurnCounter({turnCount}) {
  return html`<h2>Turn: ${turnCount.toFixed(1)}</h2>`;
}

function renderBanner({turnCount, turnPar, isLastLevel}) {
  console.log('renderBanner', arguments);
  let nextButton = html`<div onclick=${loadNextLevel} class="keyboard--key">Next Level</div>`;
  return html`
  <h1>You Win in ${turnCount.toFixed(1)} Turns!</h1>
  <h3>Par is ${turnPar.toFixed(1)} Turns</h3>
  ${!isLastLevel ? nextButton : ''}
  `;
}

// Renders an object based on props.
function renderObject(props) {
  const transform = `rotateZ(${props.direction || 0}deg)`;
  const style = `grid-area: ${props.y+1} / ${props.x+1}; transform: ${transform};`;
  let renderModel;
  
  switch(props.type) {
    case 'player':
    renderModel = renderPlayerModel;
    break;
    case 'building':
    renderModel = renderBuildingModel;
    break;
    default:
    renderModel = () => html`<h1>ROSE</h1>`;
  }
  return html`<div style=${style}>${renderModel(props)}</div>`;
}
// Renders the player
function renderPlayerModel(props) {
  const { offsetX = 0, offsetY = 0 } = props;
  let style = 'transition: none;';
  // if we have an offset, enable the transition animation.
  if (offsetX !== 0 || offsetY !== 0) {
    style = `transition: transform 1s; transform: translateX(${offsetX}em) translateY(${offsetY}em);`;
  }
  
  return html`<div 
  class="root-3d cube-3d player"
  style=${style}
  ontransitionend=${endAnimationToAction}
  data-next-action="EndSlide">
  <div></div><div></div><div></div><div></div><div></div><div></div>
  </div>`;
}
// Buildings are rectangles with "skins" via box-shadow art.
function renderBuildingModel(props) {
  const { skins, subtype } = props;
  const className = `root-3d building-3d ${subtype}`;
  return html`<div class=${className}>
  <div class="roof"></div>
  ${skins.map((skin) => {
    return html`<div class="${skin}"></div>`;
  })}
  </div>`;
}



//
// Events to Actions
//
// Converts the keyUp event into a cameraUpdate action.
function keyupToAction(event) {
  const { code, shiftKey } = event;
  let x = 0;
  let y = 0;
  let z = 0;
  let turnDelta = 0;
  
  // Camera controls
  if (['KeyW', 'KeyA', 'KeyS', 'KeyD', 'KeyQ', 'KeyE'].includes(code)) {
    // Convert keyboard into into an action.
    if (code === 'KeyA') { 
      x = -1;
      turnDelta += 0.5;
    } 
    else if (code === 'KeyD') { 
      x = 1;
      turnDelta += 0.5;
    }
    
    if (code === 'KeyW') { y = -1;} 
    else if (code === 'KeyS') { y = 1;}
    
    if (code === 'KeyQ') { z = -1;} 
    else if (code === 'KeyE') { z = 1;}
    
    // Hold shift to move slower
    if (!shiftKey) {
      x *= 3;
      y *= 3;
    }
    
    doAction({
      type: 'CameraUpdate',
      keyCode: code,
      turnDelta,
      x, y, z,
    });
  }
  
  // Invert the colors!
  // Surprise for those reading the code! (Or mashing keys 😃)
  if (code === 'KeyI') {
    doAction({
      type: 'InvertColors',
    });
  }
}
function clickToAction(event) {
  const { target } = event;
  // Ignore if the attribute is missing.
  if (!target.hasAttribute('code')) { return; }
  
  // Trigger as the key input.
  keyupToAction({
    code: target.getAttribute('code'),
  });
}

function endAnimationToAction(event) {
  //BUG: we are getting extra calls when a child transitions.
  //   : To work around this issue, ignore calls from targets that are not us.
  if (this !== event.target) { return; }
  
  const nextAction = this.dataset.nextAction;
  doAction({
    type: nextAction,
  });
}

function loadNextLevel(event) {
  console.log('loadNextLevel');
  doAction({
    type: 'LoadNextLevel',
  });
}







//
// Action/State Game Logic
// Updates gameState and triggers a re-render.
//
function doAction(action = {}) {
  const { type } = action;
  let state = window.gameState;
  // console.log('action', action);
  state.lastKey = null;
  // update state,
  switch(type) {
    case 'NewGame':
      state = Object.assign(state, JSON.parse(JSON.stringify(state.levels[state.levelIndex])));
      state = createCollisionMap(state);
      // Hydrate the buildings with helpful data.
      state.objects.filter(p => p.type === 'building').forEach((props) => {
        // Pick random skins for each side on the buildings.
        props.skins = randomSort(['skin-1', 'skin-2','skin-3','skin-4']);
      });
      state.turnCount = 0;
      state.didWin = false;
      break;
    case 'CameraUpdate':
      state.camera.x += action.x * 15;
      state.camera.y += action.y * 15;
      state.camera.z += action.z * 10;
      state.lastKey = action.keyCode;
      // Create a rotation value that is only between 0-360 so we can calculate which direction we are facing.
      // Because the user can rotate positive and negative, allowing the x value to exceed 360.
      state.camera.rotation += action.x * 15;
      if (state.camera.rotation < 0) {
        state.camera.rotation = 360 + state.camera.rotation;
      }
      else if (state.camera.rotation === Math.abs(360)) {
        state.camera.rotation = 0;
      }

      if (action.turnDelta) {
        state.turnCount += action.turnDelta;
      }
      break;
    
    case 'InvertColors':
      const tmp = state.colors[0];
      state.colors[0] = state.colors[1];
      state.colors[1] = tmp;
      break;
    //
    // After rotating, see if the player should change positions.
    case 'EndRotate':
      const newPosition = findSlideEnd(state);
      state.player.nextX = newPosition.x;
      state.player.nextY = newPosition.y;
      state.player.offsetX = newPosition.x - state.player.x;
      state.player.offsetY = newPosition.y - state.player.y;
      break;
    case 'EndSlide':
      // Move the player to the next position and remove the temp vars.
      state.player.x = state.player.nextX;
      state.player.y = state.player.nextY;
      state.player.nextX = -1;
      state.player.nextY = -1;
      state.player.offsetX = 0;
      state.player.offsetY = 0;

      state.didWin = didWin(state.gameboard, state.player);
      break;
      
    //
    // Attempts to load the next level.
    case 'LoadNextLevel':
      state.levelIndex += 1;
      // We are out of levels once we reach the end of the levels array.
      if (state.levelIndex < state.levels.length) {
        doAction({type: 'NewGame'});
      }
      break;
  }
  
  // call renderState with new state.
  // I use lighterhtml/hyperHTML so the DOM will update intelligently.
  renderState(state);
  window.gameState = state;
}
window.doAction = doAction;



// Finds the farthest spot the player can slide to from their current position including rotation.
function findSlideEnd(state) {
  const rotation = state.camera.rotation;
  const { player, objects, collisionMap } = state;
  const { x: deltaX, y: deltaY } = rotationToDelta(rotation);
  // abort if there is no change.
  //TODO: this is the wrong place for this. don't call findSlideEnd if we don't need to slide.
  if (deltaX === 0 && deltaY === 0) { return {x: player.x, y: player.y}; }
  let x = player.x + deltaX;
  let y = player.y + deltaY;
  
  // Find the first non-empty cell in delta direction.
  while (isCellEmpty(collisionMap, x, y)) {
    x += deltaX;
    y += deltaY;
  }
  
  // We need to subtract the detla before returning
  // Because we walked until it was not empty, so the one before is empty.
  return {
    x: x - deltaX, 
    y: y - deltaY,
  };
}

// Turns rotation into x,y delta values.
function rotationToDelta(rotation) {
  if (/*North*/rotation % 360 === 0) {
    return {x: 0, y: 1};
  }
  else if (/*West*/rotation % 270 === 0) {
    return {x: -1, y: 0};
  }
  else if (/*South*/rotation % 180 === 0) {
    return {x: 0, y: -1};
  }
  else if (/*East*/rotation % 90 === 0) {
    return {x: 1, y:0};
  }
  return {x: 0, y: 0};
}

// Checks if a cell on the map is empty
function isCellEmpty(collisionMap, x, y) {
  const GRID_WIDTH = WIDTH-1; // length of 6 minus 1 for index 0
  // anything off grid is not empty.
  if (x < 0 || x > GRID_WIDTH || y < 0 || y > GRID_WIDTH) {
    return false;
  } 
  const index = pointToIndex({x, y});
  return collisionMap[index] === 0;
}

function didWin(grid, player) {
  const index = pointToIndex(player);
  const patternIndex = grid[index];
  const block = BLOCK_PATTERNS[patternIndex];
  return block.isGoal === true;
}

// Creates a collision map based on the gameboard and objects.
function createCollisionMap(state) {
  const { gameboard, objects } = state;
  const map = (new Array(gameboard.length)).fill(0);
  
  // Mark the grid if there is an object on top of it.
  objects.forEach((props) => {
    const { x=0, y=0, width=1, height=1 } = props;
    let index = pointToIndex({x, y});
    map[index] = 1;
    
    // also mark along the width/height.
    let i = width;
    while (--i) {
      index = pointToIndex({ x: x + (width-i), y });
      map[index] = 1;
    }
    i = height;
    while (--i) {
      index = pointToIndex({ x, y: y + (height-i) });
      map[index] = 1;
    }
  });
  
  state.collisionMap = map;
  return state;
}

// Somethings are easier with points, others with index.
// handy functions to convert between the two formats.
function indexToPoint(index) {
  return {
    x: 0| index % WIDTH,
    y: 0| index / WIDTH,
  }
}
function pointToIndex({x, y}) {
  return x + (y * WIDTH);
}

function randomSort(array) {
  return array.sort(() => (0|Math.random()*3)-1)
}

            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.

Console