Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save linuxenko/634f2738a7780a99f48c to your computer and use it in GitHub Desktop.

Select an option

Save linuxenko/634f2738a7780a99f48c to your computer and use it in GitHub Desktop.
Build a Roguelike Dungeon Crawler Game [freeCodeCamp [Data Visualization]] (Challenge)

Build a Roguelike Dungeon Crawler Game [freeCodeCamp [Data Visualization]] (Challenge)

Rule #3: You must use both Sass and React to build this project. User Story: I have health, a level, and a weapon. I can pick up a better weapon. I can pick up health items. User Story: All the items and enemies on the map are arranged at random. User Story: I can move throughout a map, discovering items. User Story: I can move anywhere within the map's boundaries, but I can't move through an enemy until I've beaten it. User Story: Much of the map is hidden. When I take a step, all spaces that are within a certain number of spaces from me are revealed.

User Story: When I beat an enemy, the enemy goes away and I get XP, which eventually increases my level.

User Story: When I fight an enemy, we take turns damaging each other until one of us loses. I do damage based off of my level and my weapon. The enemy does damage based off of its level. Damage is somewhat random within a range. User Story: When I find and beat the boss, I win. User Story: The game should be challenging, but theoretically winnable.

A Pen by Svetlana Linuxenko on CodePen.

License.

class Player {
constructor() {
this.y = 39;
this.x = 32;
this.level = 1;
this.health = 100;
this.damage = 30;
this.weapon = 'knife';
}
getDamage() {
return Math.round(this.damage + (this.damage * this.level / 3));
}
}
class HealthBox {
constructor() {
this.type = Map.TILE.HEALTH;
this.health = 40;
}
}
class WeaponAxe {
constructor() {
this.type = Map.TILE.WEAPON;
this.damage = 80;
this.weapon = 'axe';
}
}
class SimpleEnemy {
constructor() {
this.type = Map.TILE.ENEMY;
this.damage = 20;
this.health = 40;
}
}
class BossEnemy {
constructor() {
this.type = Map.TILE.BOSS;
this.damage = 60;
this.health = 160;
}
}
class Map {
static TILE = {
BOSS : 6,
ENEMY : 5,
WEAPON : 4,
HEALTH : 3,
PLAYER : 2,
WALL : 1,
FLOOR : 0
};
static CLASS = {
6 : 'tile-boss',
5 : 'tile-enemy',
4 : 'tile_weapon',
3 : 'tile-health',
2 : 'tile-player',
1 : 'tile-wall',
0 : 'tile-floor'
};
static INSTANCE = {
init : null
};
constructor() {
this.board = [];
this.initialBoard = [];
this.player = new Player();
Map.INSTANCE.init = this;
this.gameOver = false;
this.youWin = false;
this.overcast = true;
}
load(dn) {
this.width = dn.width;
this.height = dn.height;
this.tileSize = dn.tile;
this.initialBoard = this.mapBoard(dn);
this.entities = this.randomizeEntities([
new HealthBox(), new HealthBox(), new HealthBox(), new HealthBox(),
new WeaponAxe(), new SimpleEnemy(), new SimpleEnemy(), new BossEnemy()
]);
this.board = this.mapEntities();
return this;
}
randomizeEntities(entities) {
let takenPositions = [];
let isTaken = (x,y) => {
return takenPositions.filter( p => p.x === x && p.y === y).length > 0;
};
let createPos = () => {
let x = 0, y = 0;
while(this.initialBoard[y] &&
this.initialBoard[y][x] &&
this.initialBoard[y][x] !== Map.TILE.FLOOR &&
!isTaken(x,y)) {
x = Math.round(Math.random() * this.width);
y = Math.round(Math.random() * this.height);
}
takenPositions.push({x,y});
return {x,y};
};
return entities.map((e) => {
let pos = createPos();
e.x = pos.x;
e.y = pos.y;
return e;
});
}
mapBoard(dn) {
let board = [];
for (let y = 0; y < this.height; y++) {
board[y] = [];
for (let x = 0; x < this.width; x++) {
board[y][x] = dn.data[(y * this.height + x)];
}
}
return board;
}
entityAt(x,y) {
return this.entities.filter( e => e.x === x && e.y === y)[0] || null;
}
removeEntity(x,y) {
let pos = null;
for (let i = 0 ; i < this.entities.length; i++) {
if (this.entities[i].x === x && this.entities[i].y === y) {
this.entities.splice(i,1);
}
}
return this.entities;
}
mapEntities() {
let board = this.initialBoard.map((row, y) => {
return row.map((col, x) => {
let ent = this.entities ? this.entityAt(x,y) : null;
return ent !== null ? ent.type : col;
});
});
/* Map Player position */
return board.map((row, y) => {
return row.map((col, x) => {
return x === this.player.x && y === this.player.y ? Map.TILE.PLAYER : col;
});
});
}
takeHEALTH(x,y) {
let ent = this.entityAt(x,y);
this.player.health += ent.health;
this.removeEntity(x,y);
}
takeWEAPON(x,y) {
let ent = this.entityAt(x,y);
this.player.weapon = ent.weapon;
this.player.damage = ent.damage;
this.removeEntity(x,y);
}
fight(x,y) {
let ent = this.entityAt(x,y);
ent.health -= this.player.getDamage();
this.player.health -= ent.damage;
if (this.player.health <= 0) {
this.gameOver = true;
return false;
}
if (ent.health <= 0) {
this.player.level += 1;
this.removeEntity(x,y);
if (ent.type === Map.TILE.BOSS) {
this.gameOver = true;
this.youWin = true;
}
return true;
}
return false;
}
whatAt(x,y) {
return this.board[y][x];
}
static createMap(data) {
return {
type : 'CREATE_MAP',
payload : data
}
}
static movePlayer(x, y) {
return {
type : 'MOVE_PLAYER',
payload : {x, y}
}
}
static toggleVis() {
return {
type : 'TOGGLE_VIS',
payload : null
}
}
static reducer(state, action) {
if (action.type === 'CREATE_MAP') {
return Object.assign({}, action.payload);
}
if (action.type === 'TOGGLE_VIS') {
const map = Map.INSTANCE.init;
map.overcast = !map.overcast;
return Object.assign({}, map);
}
if (action.type === 'MOVE_PLAYER') {
const map = Map.INSTANCE.init;
const x = map.player.x + action.payload.x;
const y = map.player.y + action.payload.y;
const wot = map.whatAt(x,y);
switch(wot) {
case Map.TILE.HEALTH:
map.takeHEALTH(x,y);
map.player.y = y;
map.player.x = x;
break;
case Map.TILE.WEAPON:
map.takeWEAPON(x,y);
map.player.y = y;
map.player.x = x;
break;
case Map.TILE.FLOOR:
map.player.y = y;
map.player.x = x;
break;
case Map.TILE.ENEMY:
if (map.fight(x,y)) {
map.player.y = y;
map.player.x = x;
}
break;
case Map.TILE.BOSS:
if (map.fight(x,y)) {
map.player.y = y;
map.player.x = x;
}
break;
}
map.board = map.mapEntities();
return Object.assign({}, map);
}
}
}
class MapRenderer extends React.Component {
constructor(props) {
super(props);
this.state = { dim : null}
}
componentDidMount() {
this.setState({dim : document.getElementById('level-board').getBoundingClientRect()});
}
centerIt(x,y) {
if (this.state.dim === null) {
return { w : 0, h : 0};
}
const w = this.state.dim.width / 2
- this.props.game.player.x * this.props.game.tileSize;
const h = this.state.dim.height / 2
- this.props.game.player.y * this.props.game.tileSize;
return {
w : Math.round(w),
h : Math.round(h)
};
}
render() {
let rows = this.props.game.board.map((row, rowIdx) => {
let cols = row.map((col, colIdx) => {
return <div className={Map.CLASS[col]}></div>
});
return (
<div key={rowIdx} className="tiles-row">{cols}</div>
)
});
let pos = this.centerIt.call(this,this.props.game.player.x, this.props.game.player.y);
let style = {
left : pos.w + 'px',
top : pos.h + 'px',
width: this.props.game.width * this.props.game.tileSize + 'px',
height: this.props.game.height * this.props.game.tileSize + 'px'
};
let cls = this.props.game.overcast ? 'level-board overcast' : 'level-board';
return (
<div id="level-board" className={cls}>
<div className="level-map" style={style}>
{rows}
</div>
</div>
)
}
}
class GameBoard extends React.Component {
componentDidMount() {
document.addEventListener("keydown", this.onKey.bind(this), true);
}
restartGame() {
this.props.dispatch(Map.createMap(new Map().load(dngMap)));
}
onKey(e) {
switch(e.code) {
case 'KeyW' :
this.props.dispatch(Map.movePlayer(0, -1));
break;
case 'KeyS' :
this.props.dispatch(Map.movePlayer(0, 1));
break;
case 'KeyA' :
this.props.dispatch(Map.movePlayer(-1, 0));
break;
case 'KeyD' :
this.props.dispatch(Map.movePlayer(1, 0));
break;
}
e.preventDefault();
}
render() {
return (
<div className="game-container">
<div className="game-board text-center">
<h3>Roguelike Dungeon Crawler</h3>
<div className="board-info">
<span><i className="fa fa-star"></i>
{this.props.game.player.level}</span>
<span><i className="fa fa-paw"></i>
{this.props.game.player.getDamage()}</span>
<span><i className="fa fa-heart"></i>
{this.props.game.player.health}
</span>
<span><i className="fa fa-gavel"></i>
{this.props.game.player.weapon}
</span>
<span><i className="fa fa-gamepad"></i>
awsd</span>
<small className="label" onClick={this.props.dispatch.bind(this,Map.toggleVis())}>Full View</small>
</div>
{ this.props.game.gameOver ?
<div className="game-over text-center"
onClick={this.restartGame.bind(this)}>
{this.props.game.youWin ?
<span>you win</span>
:
<span>game over</span>
}
</div>
:
<MapRenderer game={this.props.game} />
}
</div>
<div className="copy">by &nbsp; <a href="http://www.linuxenko.pro">@linuxenko</a></div>
</div>
)
}
}
let gameStore = Redux.createStore(Map.reducer);
let ConnectedBoard =
ReactRedux.connect((state) => { return {game : state} })(GameBoard);
let Provider = ReactRedux.Provider;
gameStore.dispatch(Map.createMap(new Map().load(dngMap)));
React.render(
<Provider store={gameStore}>
<ConnectedBoard />
</Provider>
, document.body);
<script src="//cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/redux/3.1.6/redux.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/4.4.1/react-redux.js"></script>
<script src="http://www.linuxenko.pro/showcase/freecodecamp/dngmap.js"></script>
$wall-bg :url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEBLAEsAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQECAgICAgICAgICAgMDAwMDAwMDAwMBAQEBAQEBAQEBAQICAQICAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA//CABEIABIAEgMBEQACEQEDEQH/xAAbAAABBAMAAAAAAAAAAAAAAAAGAAIDBAUHCP/aAAgBAQAAAADnUIxqKrToNv8A/8QAFgEBAQEAAAAAAAAAAAAAAAAAAwQG/9oACAECEAAAANIDvAlQ5v8A/8QAGAEAAwEBAAAAAAAAAAAAAAAAAgMFBgj/2gAIAQMQAAAA5koTiZIJmw//xAAyEAABBAEBBQUFCQAAAAAAAAAEAgMFBgE1AAcSNDYRFRYzYSEmU1djN1JUYmRlhpbw/9oACAEBAAE/AHKyDeoj3ROoVH37PwT4xNhj6beQhURQ2eFDUhXjZ4F2zzICcONhGBRYzja1qUQnKeJW0LYbFR2JFm5ubl1RUSMmPfZFqlmsLeDx3nHhS0ATj0gREMGEqXkwXLuBVErwpvCeL291WH5fSeq+JvsQntV/G+Z099Ly/XYcYV5WHpqzb26mfGFqhpYs22syO75LUfKvvjGd7AzUw0LNtNku5ZOCQtKu1bT+M8Wc7Jm5FTZJokVbd5EWGcM3G2WkTFjwKfxvIVFAkR0fa0JmFtxbeFMGjOPNuZxw5ThScYx4vt/yl3xa5w9X3/nvudQap9TzfybXDl6l07pwnP8Al8kLy37f8b02q2vK6Z0EnoTTOZZ1H9F8H17Nv6Hy3+/l+3//xAAvEQABAQQGBwkAAAAAAAAAAAABAgADETEEITJBUWEFEhMUIjNSU1RxgYORseHw/9oACAECAQE/AH7x+pW2SjVQtUKiCjKBEhGohYOLPKMFKdqepQqY4a1Rz4oEYEDNt20R3hVjV8+mc822QcJD+jBKnGrNKVrMDPhBE7iYgYAs+fOFvE7J2kLAvRAi6INRGEL23ek9obHTdja+2oXNdS5Rlak2kuWicxa8PnFva3+9Jv/EADQRAAEDAQUEBA8AAAAAAAAAAAIBAxEEAAUSITEiMjM0QlJhwQYTFBVBUVRygYOSk6Gx8P/aAAgBAwEBPwClco7uJaWscdqLsaHEgksPYukq4hLONqW3NcvTFlqm3wMbqfvDEsFLjiNggrnAqLaSumRLOorbzr4Q9drixzIbnW3d7ss9WPV7q0dYwx5USYkFfFMFMZSRBtImpNookqwSKqZWaoHmQMaqsFoCRZAyQhXKVnZIe3OFHKJtNze20GvrHX7Ov4tVcveHG5sddzX9902u3iL8fe16PZ3W+vhf3zLf/9k=')
$player-bg :url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAN+SURBVHjaTFRdSJRpFH6+b34cf9KZC1dn0nRhQqIcgihiWYqWGMKbJAVBl40YiJB+IAIhCLrwxpugRRKEXbQLLyT3YsTU3TD2QhYmad0VotkVFHRQG/HfcX6+n87zjk0eOLwv73vOc55z3nNebXR0FA6HQ6mmaaAMDg422LZ9W/Sy6FnLsiDrrKx/iva3t7fHcSitra1q1b+AhMNhT3l5uX94ePi5OH3c399/uL29fW53d9extbXlWF9fP7ezs/OQdyMjI8+rqqr8AuL5AqhNTk4qJjMzMzeLi4s7xemCOODg4AAbGxsQQJimqYxLS0vhcrkQCATg8/liEuRFT0/PYAGIqXi93o8i6OjooJECisfjmJ+fV0C1tbUIBoMKaGVlBdFoFPX19UgmkyenpqbmnUIVkvfdtbU1HqKrqwvV1dXw+/2IRCJIpVK8RygUwsDAABKJBFZXV+F2u1WAkpKSB0LontbW1tbY4N/798mtBDSp1XLSgTtPl/D2r08oLXFj+lUYfILvWn/HfiqLKxcr0fc4gOPeLIx0Dg96yzC37DrtlGiRS41JAdFELZyosvC6L4B//vPj55er0I2Eqs/1K17c//EbhII2bDMDO2vBodsIn9nC34u+iFPoXTtVlwYsDbaVfwG98T0e/eTDm3ebsGYboR37Hr/+9g5Xz+fPzNgpAZOWsGyE6g2meI2MvvUVm7DSLL0kITXT8VXMtAGtyITzyJmVMQVImBkWfMc0Ah0no7xxjia2MnRZGbidGj1gpgzorh21L5ylTcZTYJpsBMOhNTU1/b+0tBQ0DAPSRygqKoKu6+xkHBXep9Np9bLsLbYBbekj+w+6GEx4PB6wCTc3NyHdrJ48l8spZzLmmslklA3vCOJ0OlXAsrIy2vzB1PqlF+5Kl+bTOoxEpTGFoATIZrNqnGjDPuJEsMdEf9Gbm5vnJI3empqafHGFAbtaRgUyX0rJkqwIcDR9mQam2SvdP6fzQIz6KioqUFlZmR9AicTIZNTS0qL0SD3UvWSBvb09sulVLUOD6enpDwJ2XSJM1NXVFVJikLGxMYyPjxcAKGQiqU7QZ2FhQX0pOnOlxGKxqKR1Q9J8xunmrLGQBCU7BiRrMpFHeSZMbiwuLkYL38jQ0JB6VtaBg0tH+W8axJAf2w+ynj0c7FlZp0T7Ozs743xFand3twL6LMAACzsKXspqt0QAAAAASUVORK5CYII=')
$health-bg : url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAXUlEQVR42mNggAIjI6P/2DCx8gxUMwgmsEFABCtGl/8uLIGC4QbSzCB0heji/7LywHjgDEI3YPAYhMvL1DcIV3oi2QCqG0TIQKINoLpBuAwk2QCqG4RuIAOlgFiDACEUtTgwzX5sAAAAAElFTkSuQmCC')
$axe-bg : url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAMtSURBVHjarJRdTJt1FMZ//7cv1JaVtlAKbIoDpGHrcMgicSIyWQjLxhq7BN1udGq4YIlRFg1bXGKMMhbNvFCTZcn8IO4bYwyLkhBidJkEYcg+mg3CgMHaQgtt6du1HQP6eoVBMmUXO7fnyS/nPM/JEaqq8ihK/E/P9HhOTu3TJSXbc/PzrUly0v2hmzd6f25rawV6lwpVVX0wKN9ma3i9ft/BArs9w5KZhUaSkNQEYa8bdW6Oc2fPnT5z6lQ9oCyC5OWQaofjQuPh5holFiM4M4Nr4Cb+O24GL/dgzX6Sw58f4fp1Vw3QsAgC/g3auGlT88dfflVzua+P+wvzaBCEPB5m3OMUFtqpdL6Ko6r66KXOjveWD7AUlFnXsP/AxKQPv3cCjRBMez1ohCASmKaiajtHP2w8cqmz4+CD7PgHZDQay/MKbPgCAdLNacjJyTwmy4xc6yfNbOUv123a23/rWTE1rVabm1tg262Ew/54NDppSM+Yr3S80pw0n3jGO9jLakOYscH+SMe12RLg1kOltjht6Yas78uL1+yOu/tIzIJ/Qc+PPTEn8NNKIH3RU5m7tpVmvbZ+tVTlc49xYzjI2F0dAxMSFW99gpZQe8eZEx/FIhG/0WRcH41GdcFA8AcBsCpFv3lLsbV2a7F5b0GmZPZNhfil20Pnldljc3prTklZ5Q5xL4hWl0Jx+RZ0Yo6wz5t47oUXJf90gHfq3hRyql6u3e9IO7/uiST6h8Y53hpwDfg5CZwHRlOTtfXrCtfucA9HmY0p/Hr2a8zWLG6PjEpFm7cy6fX2Aohskzi9cY26p2uQb5V5vgMuLlm1aN+BQ79HlRmz33MHSaMh1WxmfOQWz5ZVstPpxPnS82XhcKRLaCQ2LCSIA8NLzbJmZ9fuffvdlvQMi+5Kdxd6gwEEhEMh1ubZ2Ol08n7dG0093X8e+s/UhBDln5345mJpRQUnjx/DYDIhyzK6lFVkWCwkIfiiuanRdfXqpyvFb86z2V6udu76wGa358fjceLxe0QVZcE9PNTW2tLSpELfw94RQLLFat2TYjDo7ypKJDA19QcwulykqiriUT22vwcA6WJIQ5Qy8rYAAAAASUVORK5CYII=')
$enemy-bg:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3JpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDE0IDc5LjE1Njc5NywgMjAxNC8wOC8yMC0wOTo1MzowMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo3NGU4ZDMyMC02OWJjLWQxNDMtOWU0ZC1lMDk0OTBjZjIwMDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MUQyMkIxNUY5ODM1MTFFNUE2NjBEQ0U3OTE2MkZBRUMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MUQyMkIxNUU5ODM1MTFFNUE2NjBEQ0U3OTE2MkZBRUMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKFdpbmRvd3MpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6OGVlMDM3ZWMtOGE0NC1iMDQ2LTg3YjUtY2ZjNzg4OTZmMmE2IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjNFRjVENDk0OTc2MzExRTU4MUFDRDUzNDI3N0FDRTQ1Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Y2XDPAAABAJJREFUeNpklGtsU2UYx3/vabuWbmNhW4EhtwXUAJo4bgMm4jfUDwLD+HEiX1CDBhJNNEq8xA/Ge4wJXwzxEm8Y2gkBvEAgyMYlATQoUC4bE7ZJu7ZroT1tT3sen0P5YjzJ/3w47/P8z//5v//3NXIsAk4SAj4IBqGuEXz1j+urB3GXE5w8BVzwNyWoJo5RTn2Ba+9GbmlfEaa8ClPfxk8BMIqKvirVe/HZHxIIPoatzdk8lBWi68G6Nhqkm3qnG8vZz7i7lTxxWrxm/Q9laoV+VtDo9pKyI+kzOU7/CfERSOQ8fghp/d2qbdVSaFsVfJT66mISrMOhr0bkkTjMZmI1VvhLIts/h4PDEeLDBYYLeUKeWEVnx2JGm+5j709n6Dz0B5ufNxGaieEapeYqsiuC9BLNbkeeW9Qs3+74TFJjCRkduijvvLRZmlXvGy9sEnFL4j22nZOtGzbI60sR2ac4NC0mx9dp1c5Il0SRbSuQj998TaSakGr+hkh5TOTmZXn56SfkevyUUhREShlxstckO3hcumbPkr4tSrRH0Tuty1KPekqDcChu6Fo2n2o6QT41ws/RXv6+GGf1I6sI+aucPHiA30+exMmnqK+rML19OrEjOtRNz5oJPZZ6tNK1IJcXRq9dw2dVKN5MEf0uSnosQf+RfkYGLtB/YD+H9/5IoJLBziYZvHod21ES3VSK7oPIu5PG5VPkxYXI8nnz5fqpmMg//ZK79Ksc+Pp9meyvk/Url8qVvm8kdbZX0mejsm3TkzJVXTn6rBH5AKm+15I2+WcmjdeFM01O0PDKbuGCM40lyxZRKeY53XeMiQGbBs3pjJl3MWvuHIaGRskNX+KpJYaOe4RCXLMcacmY0sbIOddOzgvNVomNhtGckEpDOKx5aYMJDRogn2ZEQ1zUTAWU1Be6M5KG+9Zlzdic9nN+yXO0NMA8FYRpEcJNujARJrVqoXpHWWpBktp3R5t9pRqJays8s22rz/IZvgxrYglo/TicOGz4rc9QGNNvBWqPR6gnJjkAO7+HSxdriiSl5I1eYu2vjLNeA5lOxnytrPVC7tNR4hk4OwwNE4TmBuVQtZmcoaTnsWOujhwQ/EVB9EduuG23WbBkjSmujWCSyXYT4IQVJoKv5kE5YLhRNSSyNVEzNeKtIcHYcnscVxW6ecb8G7d1Wj1vDfidXB6TZ9Cy6bZCxKwmWsX1/BVm1IleCgZLx44oQSVbO9/eDeIWSFVLdJtiecCb3O92rMNvshjhqPrwkHvjykfu0PnVlu5ORRtmWHK7u6Q3gNyBmbvgFzO1fYuxOW/m3F+TrIfoPyj/8AnjD7Amt4JdiuHcciq5TsVKRrIPE80sZG15347/9f0rwACtvhYnp6xz3QAAAABJRU5ErkJggg==')
$boss-bg:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAlwSFlzAAALEgAACxIB0t1+/AAAA75JREFUOE9Vk2tM02cUxltuUWFhQunKZSsXoZ3+2+KFSzpu1mlwpEOcyjZQUXchCwndyJjLsmUTxj4YFJ2CjsKwXBbnGhziRtsVRFcGRkTAMSyF0goILdRir5R2z/7rMrOd5P3wPm+eX3LOc14q5f9Ftch/LJ2bMb6/6rRHrlitvm6HneJvNnl8zI9nw/xXT0d+80NNQUFBFIPBeFxdXW17ajeJzzFHT1YuqpvqYbzZA+vvI5jr/QWT0jYYxTXQfVyCodd24QozbKloC1eQl5fHk8lkgV6AqaKMOfrph86Rhjp0vH0UbpMRj7RTWBi+C0uvHOaWizBUlqOFkwDZ/t0QP09zHUtLzR4fH6d5AUNH8o2ac9VYWDKg/csqaLo68fCPMTge3Ie1+yeYJXUYPixEy1dVUE+qoRBux9mQ9Yte8+zBHNHQ0dexQD5YLFbMzejQ+HIG7GOjsKm68eTqdzCcOoHziRsxoZ6AwWCAUT+F+vAQVAX4iSi3BUm6/v3ZWDItQTujR1RcAt693I8njTUwfd+Exfoz0L61D88yInD4QgdsDjscDieaeSx84eeno9xM3eR+eEAA7YQas/MLiMopRUxZGxQZHMzXnMCDyo+gyElDxI53wDl+GbYVF1wkpPNQPirWrnFTfk4mMCRMg0apgNNux6LDBavDgUeGecyIinD3yF4Mj92HxWqFy+OBhzxm3TS+Jlg4GxEGSnsyF/17BNArurBiMsFls8JKpjColOPWzmTcyySgVfVi1enAnx43lknwQFszmsmZ1IeHgnJlY5z7t4xEqLbuwFRTC5Z1WkzeGcSvZVWQb47G7Q3BWPisAcsaDYxkrMryT9CTmYt2Igbi0EA3pTXhBd1VIhbSkCDceI6FgcyDUL1xDN18PuRRQehY7wMZm4Aqtwh9r5agk8WHNIIOJZ+DhuC1OkozM1zUymGhIZ6J86wYXOSyIOGxcYm2Dg1Bvqhb54uTgQF4k0FHPj0U35K6MjEWvalsXHgmQOTdhca4aKMsOx3iJB5qt/Eg5hIoogUjjUpFlr8P0mmhOEDEo5Qdjf73DuEOmYqUiDZ6zYWFhfQPXkpJu8TfunJrXzbkWUmI9aOiiIiDpvY01OQa62tPYfp4Ce69wsfIXgGu8eIdFWsoTC9AIpHQi4uLN5fv3rWznb/FNLxnOwYKhZj+vAw3hBkYzMuCmtQmC7IxnJuO69w441Pzv1+xtbWV1tfXF/L3XbohXKRIeVHflcJxKwQp6MnahutJm9w9qYTuGjvyn57/U38B9W5K1s3w4owAAAAASUVORK5CYII=')
html,body,.game-container
height: 100%
width: 100%
.game-container
display: flex
flex-direction: column
background: #333
.copy
display: flex
justify-content: center
color: #eee
a
color: #eee
.game-board
display: flex
margin: auto
flex-direction: column
padding: 10px
color: #fff
.board-info
padding: 20px
.label
cursor: pointer
span
margin: 0px 5px
text-transform: uppercase
font-weight: bold
i
margin-right: 4px
.game-over
font-size: 48px
color: #fff
font-weight: bold
text-transform: uppercase
cursor: pointer
.level-board.overcast
.tile-player:after
content: ' '
display: block
position: absolute
left: -490px
top: -490px
width: 1000px
background: radial-gradient(transparent 30px, #333 20%)
height: 1000px
.level-board
background: #000
position: relative
overflow: hidden
width: 615px
height: 380px
.level-map
position: absolute
.tiles-row
background: #E3DAC9
display: block
height: 18px
.tile-wall
background: $wall-bg
.tile-player
position: relative
background: $player-bg
.tile_weapon
background: $axe-bg
.tile-health
background: $health-bg
.tile-enemy
background: $enemy-bg
.tile-boss
background: $boss-bg
.tiles-row > div
display: inline-block
width: 18px
height: 18px
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment