HTML preprocessors can make writing HTML more powerful or convenient. For instance, Markdown is designed to be easier to write and read for text documents and you could write a loop in Pug.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. So you don't have access to higher-up elements like the <html>
tag. If you want to add classes there that can affect the whole document, this is the place to do it.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. If you need things in the <head>
of the document, put that code here.
The resource you are linking to is using the 'http' protocol, which may not work when the browser is using https.
CSS preprocessors help make authoring CSS easier. All of them offer things like variables and mixins to provide convenient abstractions.
It's a common practice to apply CSS to a page that styles elements such that they are consistent across all browsers. We offer two of the most popular choices: normalize.css and a reset. Or, choose Neither and nothing will be applied.
To get the best cross-browser support, it is a common practice to apply vendor prefixes to CSS properties and values that require them to work. For instance -webkit-
or -moz-
.
We offer two popular choices: Autoprefixer (which processes your CSS server-side) and -prefix-free (which applies prefixes via a script, client-side).
Any URLs added here will be added as <link>
s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.
You can apply CSS to your Pen from any stylesheet on the web. Just put a URL to it here and we'll apply it, in the order you have them, before the CSS in the Pen itself.
You can also link to another Pen here (use the .css
URL Extension) and we'll pull the CSS from that Pen and include it. If it's using a matching preprocessor, use the appropriate URL Extension and we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
JavaScript preprocessors can help make authoring JavaScript easier and more convenient.
Babel includes JSX processing.
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.
You can apply a script from anywhere on the web to your Pen. Just put a URL to it here and we'll add it, in the order you have them, before the JavaScript in the Pen itself.
If the script you link to has the file extension of a preprocessor, we'll attempt to process it before applying.
You can also link to another Pen here, and we'll pull the JavaScript from that Pen and include it. If it's using a matching preprocessor, we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
Search for and use JavaScript packages from npm here. By selecting a package, an import
statement will be added to the top of the JavaScript editor for this package.
Using packages here is powered by esm.sh, which makes packages from npm not only available on a CDN, but prepares them for native JavaScript ESM usage.
All packages are different, so refer to their docs for how they work.
If you're using React / ReactDOM, make sure to turn on Babel for the JSX processing.
If active, Pens will autosave every 30 seconds after being saved once.
If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.
If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.
Visit your global Editor Settings.
<div id='root'>
</div>
@import url("https://fonts.googleapis.com/css?family=VT323");
$color-bg: #000000;
$color-bg2: #555555;
$color-bg3: #aaaaaa;
$color-link: #5555ff;
$color-white: #ffffff;
$color-brightblue: #5555ff;
$color-blue: #0000aa;
$color-brightred: #ff5555;
$color-red: #aa0000;
$color-brightgreen: #55ff55;
$color-green: #00aa00;
$color-yellow: #ffff55;
$color-brown: #aa5500;
$color-easyenemy: $color-brightred;
$color-mediumenemy: $color-red;
$color-bossenemy: #550000;
$font-heading: 32px "VT323", monospace;
$font-body: 18px "VT323", monospace;
$footer-height: 60px;
$footer-padding: 12px;
body,
html,
#root {
background-color: $color-bg;
color: $color-white;
font: $font-body;
margin: 0;
width: 100vw;
}
#combatLog {
font-size: 16px;
ul {
li {
list-style: none;
&:before {
color: $color-bg2;
content: "> ";
}
}
margin: 0;
padding: 0;
}
}
#grid {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
overflow-x: hidden;
overflow-y: hidden;
.mapRow {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
.mapCell {
font-size: 10px;
text-align: center;
display: inline-block;
height: 10px;
width: 10px;
}
.notvisible {
visibility: hidden;
}
.boss {
background-color: $color-bossenemy;
}
.door {
background-color: $color-brown;
}
.easy {
background-color: $color-easyenemy;
}
.floor {
background-color: $color-brown;
}
.fog {
background-color: $color-bg;
}
.healing {
background-color: $color-brightgreen;
}
.item {
background-color: $color-yellow;
}
.medium {
background-color: $color-mediumenemy;
}
.player {
background-color: $color-white;
}
.stairsDown, .stairsUp {
background-color: $color-yellow;
color: $color-brown;
}
.stairsDown:after {
content: "▼"
}
.stairsUp:after {
content: "▲"
}
.wall, .corner {
background-color: $color-bg2;
}
}
}
#playerInfo {
font-size: 16px;
.playerInfoBar {
align-items: center;
display: flex;
.bar {
align-items: center;
border: 2px solid $color-bg2;
display: flex;
height: 20px;
margin: 0 auto;
padding: 0;
position: relative;
width: 200px;
.remaining,
.missing {
height: 100%;
margin: 0;
z-index: 5;
}
.remaining {
left: 0;
top: 0;
bottom: 0;
}
.missing {
top: 0;
right: 0;
bottom: 0;
}
.infoText {
background-color: transparent;
position: absolute;
text-align: center;
width: 100%;
z-index: 10;
}
}
.hp {
.remaining {
background-color: $color-red;
}
.missing {
background-color: $color-bg;
}
}
.xp {
.remaining {
background-color: $color-brightblue;
}
.missing {
background-color: $color-bg;
}
}
}
.playerLevel {
font-size: 20px;
margin-bottom: 10px;
}
.levelInfo {
font-size: 16px;
}
.playerName {
font-size: 24px;
}
p {
margin: 5px;
}
}
#main {
align-items: stretch;
display: flex;
height: 100%;
margin: 0;
}
#center {
align-items: center;
border: 2px solid $color-bg3;
display: flex;
flex-grow: 1;
justify-content: space-around;
margin: 8px 0;
overflow-x: hidden;
overflow-y: hidden;
}
#center,
.panel {
border-radius: 2px;
padding: 8px;
overflow-y: auto;
}
.panel {
border: 2px solid $color-bg2;
margin: 8px;
width: 300px;
}
#wrapper {
display: flex;
flex-direction: column;
height: 100vh;
.footer {
flex-grow: 0;
.author {
a:hover {
text-decoration: underline;
}
.button-group {
display: flex;
.button {
color: #ffffff;
padding: 8px 4px 4px 0;
}
}
}
.heading {
font: $font-heading;
}
display: flex;
height: $footer-height;
justify-content: space-between;
padding: $footer-padding;
}
#main {
flex-grow: 1;
}
a {
color: $color-link;
text-decoration: none;
}
}
/*-------TODO-------
Implement simple one-slot inventory for weapons
Spawn weapons in a new map
Allow user to choose to pick up a new weapon or ignore it
Add more monster families
Change current hp/attack system into stat system (str dex con)
Implement dodge/chance to hit to combat
Generate equipment and inventory
Update UI - inventory screen, stat screen, enemy screen
When leveled up, allow points to be allocated to stat increases
--------------------*/
console.olog = function(o) {
console.log(JSON.parse(JSON.stringify(o)));
};
const my = {
site: "https://iamlewis.com",
github: "https://github.com/Lewis65",
freecodecamp: "https://freecodecamp.com/lewis65"
};
//Map gen settings
const viewSize = 7,
maxViewSize = 12,
mapSize = 30,
roomWidthMin = 4,
roomWidthMax = 10,
roomHeightMin = 4,
roomHeightMax = 10,
minRooms = 1,
maxRooms = 5,
maxAttempts = 5;
//Monster gen and scaling settings
const monsterDensity = 0.05, //Range 0-1, suggest 0.01-0.2
monsterDensityVariation = 0.5, //Range 0-1 - how much the monster/floor tile density can vary
monsterScaling = 1.15, //Based around 1 - the amount monsters scale up per lvl
godMode = false, //for testing or otherwise cheating!
baseHp = 50, //Base hp multiplies by level and varies by hpVariation
hpPerLevel = 1.17,
hpVariation = 0.2,
baseAttack = 10,
attackPerLevel = 1.13,
attackVariation = 0.05,
baseArmor = 0, //Gets added to monster armor
armorVariation = 0.2,
armorCap = 0.8, //Max damage reduction
armorForInvincibility = 1000, //The equivalent of 100% damage reduction
damageVariation = 0.4; //The amount that damage for a given hit varies from the attack stat
//Levels and scaling
const lvlRange = {easy: [-2, 1], medium: [-1, 2], boss: [0, 3]}, //Range of monsters' level scaling relative to player's
xpModifier = {easy: 0.9, medium: 1.1, boss: 1.6}, //Modifier for xp gain based on monster tier
xpRequiredPerLevel = 1.2, //xp required for next level increase per previous level
xpVariation = 0.1, //Variation of xp received for given victory
hpToXp = 0.6; //Ratio of xp gained per hp of monster slain
//Healing item and equipment spawn variables
const healingItemDensity = 0.025, //How many healing items per open tile
healingItemRarity = 0.2; //How likely it is to spawn a rare item rather than a common item
//Healing items and their properties
const healingItems = {
common: [
{
name: "a donut",
heals: 0.1},
{
name: "a slice of pizza",
heals: 0.15
},
{
name: "some candy",
heals: 0.05
},
{
name: "precious cake",
heals: 0.2
}
],
rare: [
{
name: "a potion",
heals: 0.4
},
{
name: "a super potion",
heals: 0.8
}
]
}
//All items (weapons/equipment) and their iLvl-weighted potential properties
const items = {
slots: [],
bonuses: [],
}
//All possible 'themes' of monster and their modifiers
const monsters = [
{name: "Spider",
easy: [
{
name: "Spider Hatchling",
hp: 0.3,
attack: 0.4,
armor: 0
},
{
name: "Young Spiderling",
hp: 0.4,
attack: 0.5,
armor: 0.1
}
],
medium: [
{
name: "Spider Whelp",
hp: 0.5,
attack: 0.4,
armor: 0.2
},
{
name: "Arachnid Soldier",
hp: 0.7,
attack: 0.4,
armor: 0.3
}
],
boss: [
{
name: "Spider Matriarch",
hp: 0.85,
attack: 0.8,
armor: 0.3
},
{
name: "Mutated Arachnoid",
hp: 0.6,
attack: 1.4,
armor: 0.1
}
]}
];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
combatLog: [
"Welcome to React Roguelike.",
"This combat log will show you events and messages as you play.",
"Use WASD to move and fight monsters by walking into them.",
"Good luck!"
],
player: {
id: 0,
x: null,
y: null,
name: "John",
level: 1,
type: "player",
gear: {
head: undefined,
chest: undefined,
legs: undefined,
feet: undefined,
hands: undefined,
mainhand: undefined,
offhand: undefined
},
inv: new Array(16),
hp: 100,
hpmax: 100,
attack: baseAttack,
armor: baseArmor,
xp: 0,
xpmax: 100
},
depth: 1,
levels: [],
map: this.generateBlankMap(mapSize),
monsterCount: undefined,
npcs: [],
visibleMap: [[{state: "rock"}]]
};
}
//==========HANDLERS==========
handleKeyDown(event){
if(this.state.player.hp == 0){
return;
}
let nextMap;
if(event.key == "w"){
nextMap = this.moveEntity(this.state.player, "n");
} else
if(event.key == "a"){
nextMap = this.moveEntity(this.state.player, "w");
} else
if(event.key == "s"){
nextMap = this.moveEntity(this.state.player, "s");
} else
if(event.key == "d"){
nextMap = this.moveEntity(this.state.player, "e");
}
}
//==========LOGIC==========
combat(a, b, opportunityAttack = false){
let attacker, defender;
//Decide attack order
if(opportunityAttack){
attacker = a;
defender = b;
} else {
if(Math.random() > 0.5){
attacker = a;
defender = b;
} else {
attacker = a;
defender = b;
}
}
console.log("combat(): attacker and defender", attacker, defender);
let attackerDamage = attacker.attack
attackerDamage += attackerDamage*((Math.random()+Math.random())-1)*damageVariation;
attackerDamage *= 1-(defender.armor/armorForInvincibility);
console.log("The attacker, " + attacker.name + ", hits the " + defender.name + " with " + attackerDamage + " points of damage.");
attackerDamage = Math.round(attackerDamage);
if(attackerDamage < 0){attackerDamage = 0;}
defender.hp -= attackerDamage;
if(opportunityAttack){
this.log("As you move away from the " + attacker.name + ", you are hit for " + attackerDamage + ".");
} else {
this.log(attacker.name + " hits " + defender.name + " for " + attackerDamage + " damage!")
}
if(defender.hp > 0 && !opportunityAttack) {
//If defender didn't die, and is not fleeing, strike back
let defenderDamage = defender.attack
defenderDamage += defenderDamage*((Math.random()+Math.random())-1)*damageVariation;
defenderDamage *= 1-(attacker.armor/armorForInvincibility);
console.log("The defender, " + defender.name + ", hits the " + attacker.name + " with " + defenderDamage + " points of damage.");
defenderDamage = Math.round(defenderDamage);
if(defenderDamage < 0){defenderDamage = 0;}
attacker.hp -= defenderDamage;
this.log(defender.name + " hits " + attacker.name + " for " + defenderDamage + " damage!")
if(attacker.hp < 0){
//If attacker gets negative hp
attacker.hp = 0;
}
}
if (defender.hp < 0){
//If defender dies
defender.hp = 0;
}
let result = {};
if(attacker.id == this.state.player.id){
result.player = attacker;
result.monster = defender;
} else if (defender.id == this.state.player.id){
result.player = defender;
result.monster = attacker;
} else {
console.log("ERR: combat() could not find player's ID. attacker:",
attacker,
"defender:",
defender);
return;
}
if(godMode){
result.player.hp = result.player.hpmax;
}
//If something dies
if(result.monster.hp == 0){
this.log(result.monster.name + " was slain!");
}
if(result.player.hp == 0){
this.log("You fall to your knees, gasping in agony. Mercifully, " + result.monster.name + " finishes you off.");
this.log("You are slain.");
}
return result;
}
gainXp(player, monster){
let xp = monster.hpMax * hpToXp * xpModifier[monster.type];
xp += xp*(Math.random()+Math.random()-1)*xpVariation;
xp = Math.round(xp);
this.log("You gain " + xp + "xp.")
player.xp += xp;
if(player.xp >= player.xpmax){
player.xp = player.xp - player.xpmax;
player.level++;
player.hpmax = Math.round(player.hpmax * hpPerLevel);
player.hp = player.hpmax;
player.attack = Math.round(player.attack * attackPerLevel);
player.xpmax = Math.round(player.xpmax * xpRequiredPerLevel);
this.log("You have reached level " + player.level + "!");
}
return player;
}
//Generate an empty map of solid rock
generateBlankMap(size) {
let map = [];
for (var i = 0; i < size; i++) {
let row = [];
for (let j = 0; j < size; j++) {
row.push({
state: "rock",
x: j,
y: i
});
}
map.push(row);
}
return map;
}
generateMap() {
let player = this.state.player;
let npcs = [];
//generate a blank map
let map = this.generateBlankMap(mapSize);
//fill the blank map with rooms and walls
map = this.generateRooms(map);
//populate player
let spawnPlayer = this.spawn("player", player, map, npcs);
player = spawnPlayer.player;
map = spawnPlayer.map;
//populate monsters
let spawnMonsters = this.spawnMonsters(map);
map = spawnMonsters.map;
npcs = npcs.concat(spawnMonsters.npcs);
//populate items
map = this.spawnHealingItems(map);
//update the visible portion of the map
let visible = this.updateVisibleMap(map, player.x, player.y);
return{
player: player,
map: map,
monsterCount: spawnMonsters.monsterCount,
npcs: npcs,
visibleMap: visible
};
}
//Generate one room
generateRoom() {
let width = this.random(roomWidthMin, roomWidthMax);
let height = this.random(roomHeightMin, roomHeightMax);
let room = [];
for (let y = 0; y < height + 2; y++) {
let row = [];
for (let x = 0; x < width + 2; x++) {
if (y == 0 || y == height + 1 || x == 0 || x == width + 1) {
if (
(y == 0 && x == 0) ||
(y == 0 && x == width + 1) ||
(y == height + 1 && x == 0) ||
(y == height + 1 && x == width + 1)
) {
//Push a corner for the wall corners
row.push({ state: "corner" });
} else {
//Push a wall if it's on the edges
row.push({ state: "wall" });
}
} else {
//Else push a floor
row.push({ state: "floor" });
}
}
room.push(row);
}
//console.log("Generating room " + width + " wide and " + height + " high, not including walls:");
//console.olog(room);
return room;
}
//Generate all rooms
generateRooms(blankMap) {
//randomize roomCount
let roomCount = this.random(minRooms, maxRooms);
let roomsPlaced = 0;
console.log("The roomCount will be " + roomCount);
let map = blankMap;
console.log("Setting map to blank map...");
this.setState({ map: map });
//generate rooms until a room count or number of attempts reached
//generate room, find possible placement or retry, place room, set as new map
//for every room
console.log("Starting room generation...");
for (let i = 0; i < roomCount; i++) {
//generate a new room
let room = this.generateRoom();
//set roomPlaced to false and try to place while it's false
let roomPlaced = false;
let placeAttempts = 0;
let nextMap = map;
while (roomPlaced == false && placeAttempts < maxAttempts) {
//run placeroom and return bool
//console.log("Attempting to place room... (attempt " + placeAttempts + ")")
let placeRoom = this.placeRoom(room, i, map);
roomPlaced = placeRoom.success;
nextMap = placeRoom.nextMap;
//console.olog(roomPlaced);
placeAttempts++;
}
if (roomPlaced) {
//console.log("Room placed successfully. nextMap:");
//console.olog(nextMap);
roomsPlaced++;
map = nextMap;
} else if (placeAttempts == maxAttempts) {
//console.log("Failed to place room: Max attempts reached.");
} else {
//console.log("Room was not placed.");
}
//console.log("Finished iteration " + i + " of for loop in generateRooms. Here is map:");
//console.olog(map);
}
console.log("generateRooms complete.");
console.olog(map);
console.log("Placed " + roomsPlaced + " rooms of goal of " + roomCount);
console.log("Cleaning doors...");
//Clean up failed doors
let doors = this.scanMapFor("door", map);
//console.log("Here's all the doors:", doors);
for (let i = 0; i < doors.length; i++) {
//If a door does not connect 2+ floor tiles, revert it to a wall
let adjacent = this.getAdjacentCells(map, doors[i].x, doors[i].y);
//console.log("Here are the neighbors of the door at x:"+doors[i].x+" y:"+doors[i].y);
//console.olog(adjacent);
let floors = 0;
for (let j = 0; j < adjacent.length; j++) {
if (adjacent[j].state == "floor") {
floors++;
}
}
if (floors < 2) {
map[doors[i].y][doors[i].x].state = "wall";
}
}
return map;
}
//return the 4 adjacent cells
getAdjacentCells(map, x, y) {
let adjacent = [];
if (y < map.length - 1) {
let cell = map[y + 1][x];
cell.dir = "s";
adjacent.push(cell);
}
if (x < map[0].length - 1) {
let cell = map[y][x + 1];
cell.dir = "e";
adjacent.push(cell);
}
if (y > 0) {
let cell = map[y - 1][x];
cell.dir = "n";
adjacent.push(cell);
}
if (x > 0) {
let cell = map[y][x - 1];
cell.dir = "w";
adjacent.push(cell);
}
return adjacent;
}
goToLevel(depth){
if(depth < 1){
console.log("ERR: goToLevel(): Attempted to find level for depth " + depth);
return;
}
let combatLog = this.state.combatLog;
let levels = this.state.levels;
let nextState;
//Store the player's current state
let currentPlayer = this.state.player;
//Store the current level information
let currentLevel = {
depth: this.state.depth,
map: this.state.map,
npcs: this.state.npcs,
stairs: {
up: {
x: undefined,
y: undefined
},
down: {
x: undefined,
y: undefined
}
}
};
//Pick the player off of the current level's map
currentLevel.map[currentPlayer.y][currentPlayer.x].entity = undefined;
if(depth > this.state.levels.length){
//Going to a new depth
nextState = this.generateMap();
console.log("Didn't find previously created level for depth " + depth + ".")
combatLog.push("You climb down the stairs, pushing further into the depths.")
console.log("Generated nextLevel:");
console.olog(nextState);
nextState.map[nextState.player.y][nextState.player.x].state = "stairsUp";
} else {
//Previously generated level
nextState = this.state.levels[depth-1];
console.log("Retrieved level for nextState:");
console.olog(nextState);
console.log("Found previously created level for depth " + depth + ".");
combatLog.push("You return to floor " + depth + ".");
//Place the player in the next level at the appropriate staircase
nextState.player = currentPlayer;
console.log("stairs for nextState");
console.olog(nextState.stairs);
if(depth > this.state.depth){
//Going down to a previously visited level
console.log("going down");
nextState.player.y = nextState.stairs.up.y;
nextState.player.x = nextState.stairs.up.x;
} else if (depth < this.state.depth){
//Going up to a previously visited level
console.log("going up");
nextState.player.y = nextState.stairs.down.y;
nextState.player.x = nextState.stairs.down.x;
} else {
console.log("ERR: goToLevel(): Attempted to go to same depth as current depth")
}
nextState.map[nextState.player.y][nextState.player.x].entity = nextState.player;
}
console.log("Updating visiblemap");
console.olog(nextState);
nextState.visibleMap = this.updateVisibleMap(nextState.map, nextState.player.x, nextState.player.y);
nextState.levels = this.state.levels;
nextState.depth = depth;
nextState.monsterCount = nextState.npcs.length;
//Save the current level along with stair info in state.levels
console.log("currentLevel from which stairs are saved")
console.olog(currentLevel);
let stairsUp = this.scanMapFor("stairsUp", currentLevel.map);
if(stairsUp){currentLevel.stairs.up = stairsUp[0]}
let stairsDown = this.scanMapFor("stairsDown", currentLevel.map);
if(stairsDown){currentLevel.stairs.down = stairsDown[0]}
console.log("Storing currentLevel:");
console.olog(currentLevel);
levels[currentLevel.depth - 1] = currentLevel;
nextState.levels = levels;
//Update state to display nextLevel stuff
console.log("goToLevel(): Set nextState to:");
console.log(nextState);
this.setState(nextState);
}
log(message){
let log = this.state.combatLog;
log.push(message);
this.setState({combatLog: log});
}
moveEntity(entity, d = null){
let x = entity.x;
let y = entity.y;
let dirs = ["n", "e", "s", "w"];
if(d !== null && dirs.indexOf(d) == -1){
console.log("ERR: moveEntity(): Called with invalid d:" + d);
return;
}
let dir = d || dirs[this.random(0,3)];
let targetCoord = {
x: x,
y: y
}
switch(dir){
case "n":
targetCoord.y--;
break;
case "s":
targetCoord.y++;
break;
case "e":
targetCoord.x++;
break;
case "w":
targetCoord.x--;
break;
default:
console.log("ERR: moveEntity(): Error in switch statement. dir:" + dir);
}
let targetCell = this.state.map[targetCoord.y][targetCoord.x];
if(!(this.state.map[y] && this.state.map[y][x])){
console.log("ERR: moveEntity(): The coordinates provided to moveEntity do not exist on the map in state. y:"+y+" x:"+x);
return;
} else if (!this.state.map[y][x].entity){
console.log("ERR: moveEntity(): No entity exists at y:"+y+" x:"+x);
return;
}
let entityMoving = this.state.map[y][x].entity;
console.log("Attempting to move the entity " + entityMoving + " to x:" + targetCoord.x + " y:" + targetCoord.y + ". Target cell is:", targetCell);
if(targetCell.state === "stairsUp"){
console.log("Walked to stairs up at x:" + targetCoord.x + " y:" + targetCoord.y)
this.goToLevel(this.state.depth-1);
return;
}
if(targetCell.state === "stairsDown"){
console.log("Walked to stairs down at x:" + targetCoord.x + " y:" + targetCoord.y)
this.goToLevel(this.state.depth+1);
return;
}
let nextMap = this.state.map;
let player = this.state.player;
let nextNpcs = this.state.npcs;
let monsterCount = this.state.monsterCount;
if(targetCell.state !== "floor" && targetCell.state !== "door"){
//If it is a non-traversable cell
console.log("Can't move there.");
return;
}
if(targetCell.entity){
//If there's another entity there
if(targetCell.entity.type == "player" || entityMoving.type == "player"){
//and one of them is the player
let combat = this.combat(targetCell.entity, entityMoving);
//Then update the post-combat stats for player and monster in state
for(let m = 0; m < nextNpcs.length; m++){
if(nextNpcs[m].id == combat.monster.id){
if(combat.monster.hp == 0){
//If the monster died
combat.player = this.gainXp(combat.player, combat.monster);
nextNpcs.splice(m, 1);
nextMap[combat.monster.y][combat.monster.x].entity = undefined;
monsterCount--;
if(combat.monster.type == "boss"){
this.log("A way down appears.");
nextMap[combat.monster.y][combat.monster.x].state = "stairsDown";
}
} else {
//If it's still alive
nextNpcs[m] = combat.monster;
nextMap[combat.monster.y][combat.monster.x].entity = combat.monster;
}
break;
}
}
console.log("Combat with " + combat.monster.name + " with id " + combat.monster.id + ". combat:");
console.log(combat);
console.log("nextNpcs after combat()", nextNpcs);
player = combat.player;
console.log("And the player:", player);
}
} else {
//There's no entity there
if(entityMoving.type == "player"){
let adjacentCells = this.getAdjacentCells(nextMap, player.x, player.y);
for(let i = 0; i < adjacentCells.length; i++){
if(player.hp == 0){
//Break if already killed (used in multiple attacks of opportunity)
break;
}
if(adjacentCells[i].entity !== undefined){
let opportunityAttack = this.combat(adjacentCells[i].entity, player, true);
player = opportunityAttack.player;
}
}
if(player.hp > 0){
//Move if they didn't die to an opportunity attack
player.x = targetCoord.x;
player.y = targetCoord.y;
} else {
return;
}
} else {
let id = entityMoving.id;
let index = nextNpcs.indexOf(entityMoving);
nextNpcs[index].y = targetCoord.y;
nextNpcs[index].x = targetCoord.x;
}
if(entityMoving.type == "player" && nextMap[targetCoord.y][targetCoord.x].item){
//If the player is moving to a tile with an item
let item = nextMap[targetCoord.y][targetCoord.x].item;
if(item.type == "healing"){
nextMap[targetCoord.y][targetCoord.x].item = undefined;
let healsFor = Math.round(player.hpmax * item.heals);
console.log("The " + item.name + " heals for " + healsFor + ". player.hp:" + player.hp + "player.hpmax:" + player.hpmax);
if(healsFor > (player.hpmax - player.hp)){
healsFor = (player.hpmax - player.hp);
}
player.hp += healsFor;
this.log("You pick up " + item.name + ". It heals " + healsFor + "hp.");
}
//PUT ELSE FOR EQUIPMENT ITEMS HERE
}
nextMap[targetCoord.y][targetCoord.x].entity = entityMoving;
nextMap[y][x].entity = undefined;
}
let nextVisibleMap = this.updateVisibleMap(nextMap, player.x, player.y);
this.setState({
map: nextMap,
player: player,
monsterCount: monsterCount,
npcs: nextNpcs,
visibleMap: nextVisibleMap
})
}
//attempt to place a room
placeRoom(r, i, map) {
//length of first row
//let roomWidth = room[0].length;
//number of rows
//let roomHeight = room.length;
let nextMap = map;
//Set variables for the start coords of room (top-left corner)
//let startingX, startingY;
let room = {
//leftmost and rightmost value for possible floor placement, does not include walls
minX: null,
maxX: null,
//topmost and bottommost value for possible floor placement, does not include walls
minY: null,
maxY: null,
//coords at which the topleft corner of the room will be placed, including walls
startingX: null,
startingY: null,
//width and height, including walls
width: r[0].length,
height: r.length,
//number of the room
number: i,
//actual cells of the room
map: r,
//if the room is placeable
isPlaceable: false
};
//find coords for a door and set the room coords
if (room.number == 0) {
//First room to be placed
room.startingX = this.random(0, mapSize - room.width);
room.startingY = this.random(0, mapSize - room.height);
//console.log("Placing first room.", "Room is " + room.width + " wide and " + room.height + " high including walls:");
//console.olog(room.map);
//console.log("Placing at x:" + room.startingX + " y:" + room.startingY);
room.isPlaceable = true;
} else {
//Non-first rooms
//find all non-corner walls and choose a random one for a door
let door = this.placeDoor(map);
//console.log("Door placed: ", door)
if (door.success) {
nextMap[door.y][door.x].state = "door";
//after placing a door but before adding the room on
//find direction to go out the room through the door
//search 4 adjacent tiles and find 1 rock - if no rock find a new door position
let adjacent = this.getAdjacentCells(nextMap, door.x, door.y);
let neighborCoords = [
{ x: door.x, y: door.y - 1, dir: "n" },
{ x: door.x - 1, y: door.y, dir: "w" },
{ x: door.x, y: door.y + 1, dir: "s" },
{ x: door.x + 1, y: door.y, dir: "e" }
];
//check each y and x exists in case the room is on the edge of the map
let dir = null;
for (let i = 0; i < neighborCoords.length; i++) {
//if the cell is within the map
if (
nextMap[neighborCoords[i].y] !== undefined &&
nextMap[neighborCoords[i].x] !== undefined
) {
//and if it's an empty tile
if (nextMap[neighborCoords[i].y][neighborCoords[i].x].state == "rock") {
//save a string to show the direction out of the room
dir = neighborCoords[i].dir;
}
}
}
//check bounds of space for the room in that dir
//console.log("Dir is: ", dir)
if (!dir) {
//first, if no way out of this door to empty rock tile, return fail
//console.log("ERR: Did not find empty tile adjacent to door:", door);
//console.log("Reverting to map:", map);
return { success: false, nextMap: map };
} else {
//console.log("Setting initial min/max for x/y - this will not include walls");
//console.log("room.height:" + room.height + " room.width:" + room.width + ", including walls");
if (dir == "w" || dir == "e") {
//Primary x, secondary y
//The furthest the floor can potentially reach given room size and door connectivity.
room.yMin = door.y - (room.height - 2) + 1; //
room.yMax = door.y + (room.height - 2) - 1; //
if (dir == "w") {
room.xMin = door.x - (room.width - 2); //
room.xMax = door.x - 1; //
} else if (dir == "e") {
room.xMin = door.x + 1; //
room.xMax = door.x + (room.width - 2); //
}
//check primary axis
if (room.xMin < 1 || room.xMax > mapSize - 2) {
//console.log("ERR: No room on primary (x) axis.");
return { success: false };
}
} else if (dir == "n" || dir == "s") {
//Primary y, secondary x
//The furthest the floor can potentially reach given room size and door connectivity.
room.xMin = door.x - (room.width - 2) + 1; //
room.xMax = door.x + (room.width - 2) - 1; //
if (dir == "n") {
room.yMin = door.y - (room.height - 2); //
room.yMax = door.y - 1; //
} else if (dir == "s") {
room.yMin = door.y + 1; //
room.yMax = door.y + (room.height - 2); //
}
//check primary axis
if (room.yMin < 1 || room.yMax > mapSize - 2) {
//console.log("ERR: No room on primary (y) axis.")
return { success: false };
}
}
//console.log("xMin:" + room.xMin + " xMax:" + room.xMax + " yMin:" + room.yMin + " yMax:" + room.yMax);
//Check x/y min/max against constraints of map
//Off by one to allow for wall
if (room.xMin < 1) {
room.xMin = 1;
}
if (room.xMax > mapSize - 2) {
room.xMax = mapSize - 2;
}
if (room.yMin < 1) {
room.yMin = 1;
}
if (room.yMax > mapSize - 2) {
room.yMax = mapSize - 2;
}
//console.log("Constrained to 0/mapSize:");
//console.log("xMin:" + room.xMin + " xMax:" + room.xMax + " yMin:" + room.yMin + " yMax:" + room.yMax);
//Check x/y min/max against min requirements of the room
if (room.xMax - room.xMin + 1 < room.width - 2) {
//console.log("ERR: x range available is too small for room:")
//console.olog(room.map);
//console.log("room.width:"+room.width+" xMin:"+room.xMin+" xMax:"+room.xMax+", thus max size with walls:"+(room.xMax-room.xMin + 3))
return {
success: false
};
}
if (room.yMax - room.yMin + 1 < room.height - 2) {
//console.log("ERR: y range available is too small for room:")
//console.olog(room.map);
//console.log("roomHeight:"+room.height+" yMin:"+room.yMin+" yMax:"+room.yMax+", thus max size with walls:"+(room.yMax-room.yMin + 3))
return {
success: false
};
}
//Loop through primary axis (go straight from door)
if (dir == "w" || dir == "e") {
let yMin = room.yMin;
let yMax = room.yMax;
//Find min and max values on the y axis at each x
for (let x = room.xMin; x < room.xMax; x++) {
if (nextMap[door.y][x].state == "floor") {
//console.log("ERR: Found a floor tile on primary axis at x:"+x+" y:"+door.y+", must abandon room.");
return {
success: false
};
}
for (let y = room.yMin; y < door.y - 1; y++) {
if (nextMap[y][x].state == "floor") {
yMin = y + 1;
//console.log("Found floor tile at x:"+x+" y:"+y+", new yMin is "+yMin);
}
}
for (let y = room.yMax; y > door.y + 1; y--) {
if (nextMap[y][x].state == "floor") {
yMax = y - 1;
//console.log("Found floor tile at x:"+x+" y:"+y+", new yMax is "+yMax);
}
}
}
room.yMin = yMin;
room.yMax = yMax;
//Choose a random startpoint on secondary axis
let highestMin = door.y;
if (door.y + room.height - 3 > room.yMax) {
highestMin = room.yMax - (room.width - 2);
}
let rand = this.random(yMin, highestMin);
//starting includes walls, min does not
room.startingY = rand - 1;
room.startingX = room.xMin - 1;
} else if (dir == "n" || dir == "s") {
let xMin = room.xMin;
let xMax = room.xMax;
//Primary y, secondary x
for (let y = room.yMin; y < room.yMax; y++) {
if (nextMap[y][door.x].state == "floor") {
//console.log("ERR: Found a floor tile on primary axis at x:"+door.x+" y:"+y+", must abandon room.");
return {
success: false
};
}
for (let x = room.xMin; x < door.x - 1; x++) {
if (nextMap[y][x].state == "floor") {
xMin = x + 1;
//console.log("Found floor tile at x:"+x+" y:"+y+", new xMin is "+xMin);
}
}
for (let x = room.xMax; x > door.x + 1; x--) {
if (nextMap[y][x].state == "floor") {
xMax = x - 1;
//console.log("Found floor tile at x:"+x+" y:"+y+", new xMax is "+xMax);
}
}
}
room.xMin = xMin;
room.xMax = xMax;
//Choose a random startpoint on secondary axis
let highestMin = door.x;
if (door.x + room.width - 3 > xMax) {
highestMin = xMax - (room.width - 2);
}
let rand = this.random(xMin, highestMin);
//starting includes walls, min does not
room.startingX = rand - 1;
room.startingY = room.yMin - 1;
}
//Re-check all tiles for room placement
//console.log("Will check tiles x:"+room.startingX+"-"+(room.startingX+(room.width-1))+
// " y:"+room.startingY+"-"+(room.startingY+(room.height-1)));
room.isPlaceable = true;
for (let y = room.startingY; y < room.startingY + (room.height - 1); y++) {
for (let x = room.startingX; x < room.startingX + (room.width - 1); x++) {
//console.log("Checking cell x:"+x+" y:"+y);
if (
typeof nextMap[y] == "undefined" ||
typeof nextMap[y][x] == "undefined"
) {
//console.log("ERR: Undefined cell in tile check! x:"+x+" y:"+y)
room.isPlaceable = false;
} else {
if (nextMap[y][x].state == "floor") {
room.isPlaceable = false;
//console.log("ERR: Found floor in proposed roomspace at x"+x+" y"+y+".")
}
}
}
}
//console.log("Check complete. room.isPlaceable is " + room.isPlaceable);
}
} else {
//what to do if door could not be placed
//console.log("ERR: Could not place door. (placeDoor returned success:false)")
return {
success: false
};
}
}
//Replace empty tiles with room tiles
if (room.isPlaceable) {
for (let y = 0; y < room.height; y++) {
for (let x = 0; x < room.width; x++) {
let mapX = room.startingX + x;
let mapY = room.startingY + y;
if (
typeof nextMap[mapY] == "undefined" ||
typeof nextMap[mapY][mapX] == "undefined"
) {
console.log(
"ERR: Undefined cell in replacement block at x:" +
(room.startingX + x) +
" y:" +
(room.startingY + y)
);
return {success: false};
}
if (nextMap[mapY][mapX].state !== "rock") {
//console.log("Skipping "+nextMap[mapY][mapX].state+" at x:"+mapX+" y:"+mapY+".");
continue;
}
let cell = room.map[y][x];
cell.roomNumber = room.number;
nextMap[mapY][mapX] = cell;
//console.log("Placing " + JSON.stringify(cell) + " at x:" + (mapX) + " y:" + (mapY) +".");
}
}
console.log("Room " + room.number + " placed! nextMap:");
console.olog(nextMap);
return { success: true, nextMap: nextMap };
} else {
console.log(
"ERR: room.isPlaceable is " + room.isPlaceable + ". Room not placed."
);
return { success: false };
}
}
placeDoor(map) {
//an array of coord pairs {x:int, y:int} of all non-corner walls in map
let doorPlaced = false;
let walls = this.scanMapFor("wall", map);
//console.log("Found walls at:", walls, typeof walls);
if (typeof walls !== "object" || walls.length < 1) {
//error handling
console.log("ERR: Did not find walls to place door.");
return { success: false };
}
//if walls are found
let n = this.random(0, walls.length - 1);
let x = walls[n].x;
let y = walls[n].y;
//console.log("Placing door at y"+y+" x"+x+".");
let nextMap = map;
return {
x: x,
y: y,
success: true
};
}
//return a random int between min and max (inclusive)
random(min, max) {
return Math.floor((max - min + 1) * Math.random()) + min;
}
//apply a monster's modifiers and build their stats
randomizeMonster(monster, tier){
//Using Object.assign will prevent passing a reference to the $monster object,
//instead simply assigning $randomized the same property values.
let randomized = {};
Object.assign(randomized, monster);
randomized.level = this.random(lvlRange[tier][0], lvlRange[tier][1]) + this.state.player.level;
if(randomized.level < 1){
randomized.level = 1;
}
let hp = monster.hp * baseHp;
hp += hp*((Math.random()*hpVariation)*(randomized.level*monsterScaling));
if (hp < 1) {hp = 1;}
randomized.hp = Math.round(hp);
randomized.hpMax = randomized.hp;
let attack = monster.attack * baseAttack;
attack += attack*((Math.random()*attackVariation)*(randomized.level*monsterScaling));
if (attack < 1) {attack = 1;}
randomized.attack = Math.round(attack);
let armor = monster.armor + baseArmor;
let randomArmor = Math.random()+Math.random()-1;
armor += randomArmor*armorVariation*armor;
randomized.armor = Math.round(armor*armorForInvincibility);
return randomized;
}
//Scan a 2d array map for cells with a state and return a list of coords
scanMapFor(state, map) {
let coords = [];
//console.log("Scanning this map for " + state + ":", map);
for (let y = 0; y < map.length; y++) {
for (let x = 0; x < map[y].length; x++) {
if (map[y][x].state == state) {
coords.push({ x: x, y: y });
}
}
}
//console.log("Found " + coords.length + " results.");
return coords;
}
//Spawn a single entity
spawn(type, entity, map, npcs, x = null, y = null) {
let nextMap = map;
let nextNpcs = npcs;
let coord = {
x: x,
y: y
}
if (y && x && map[y][x].state == "floor" && !map[y][x][type]) {
//if passed coords
entity.x = x;
entity.y = y;
nextMap[y][x][type] = entity;
if(type == "entity"){
nextNpcs.push(entity);
}
} else {
if(y&&x){
console.log("Passed coords to spawn() but tile was not a floor tile or entity exists there. x:" + x + " y:" + y);
return;
}
let openTiles = this.scanMapFor("floor", map);
let random;
let attempts = 0;
let noMoreTilesAvailable = false;
let isNextToPlayer = false;
do {
random = this.random(1, openTiles.length)-1;
coord = openTiles[random];
if (coord == undefined) {
console.log("ERR: Found no more open floor tiles.");
noMoreTilesAvailable = true;
break;
}
//If there's a player, and spawning a monster, do not spawn it adjacent to player
isNextToPlayer = false;
if(type == "monster" && this.state.player.x){
//console.log("Testing if next to player...");
//console.olog(this.state.player);
let player = this.state.player
if(coord.x == player.x || coord.y == player.y) {
if(coord.x == player.x + 1 || coord.x == player.x -1) {
console.log("Skipped spawn location because it was next to the player.", coord);
isNextToPlayer = true;
} else if (coord.y == player.y + 1 || coord.y == player.y -1) {
console.log("Skipped spawn location because it was next to the player.", coord);
isNextToPlayer = true;
}
}
}
openTiles.splice(random, 1);
attempts++;
} while ((map[coord.y][coord.x].entity || isNextToPlayer) && attempts < 20);
if (attempts == 20 || noMoreTilesAvailable) {
console.log(
"ERR: Max attempts reached or no more open tiles. Cannot spawn this:", entity
);
return {
map: nextMap,
error: true
};
}
entity.y = coord.y;
entity.x = coord.x;
let category = type;
if(type == "player" || type == "monster"){
//Categorize player and monsters as entities
category = "entity";
}
nextMap[coord.y][coord.x][category] = entity;
}
console.log("Spawning " + type + " '" + entity.name + "' at x:" + coord.x + " y:" + coord.y);
if(type == "player") {
let player = this.state.player;
player.x = coord.x;
player.y = coord.y;
//To be passed to generateMap()
return {
player: player,
map: nextMap
};
} else if(type == "monster") {
//If it's a non-player entity (monster)
nextNpcs.push(entity);
return{
map: nextMap,
npcs: nextNpcs
};
} else if(type == "item") {
nextMap[coord.y][coord.x].item = entity;
return {
map: nextMap
}
}
console.log("ERR: spawn() called with unhandled type: " + type);
return;
}
spawnHealingItems(map){
let nextMap = map;
let numberToSpawn = this.scanMapFor("floor", nextMap).length*healingItemDensity;
let items = [];
for(let i = 0; i < numberToSpawn; i ++){
let item;
if(Math.random() < healingItemRarity){
item = healingItems.rare[this.random(1, healingItems.rare.length)-1];
} else {
item = healingItems.common[this.random(1, healingItems.common.length)-1];
}
item.type = "healing";
items.push(item);
}
console.log("Spawning these healing items:", items);
for(let i = 0; i < items.length-1; i++){
nextMap = this.spawn("item", items[i], nextMap, this.state.npcs).map;
}
console.log("Finished spawning healing items. nextMap:");
console.log(nextMap);
return nextMap;
}
spawnMonsters(map){
let monsterFamily = monsters[this.random(0, monsters.length-1)];
console.log("Selected " + monsterFamily.name + " family of monsters.");
let floorTiles = this.scanMapFor("floor", map);
let monsterCount = floorTiles.length*monsterDensity;
let monsterVariance = (Math.random()*2*monsterDensityVariation);
monsterCount += (monsterCount*monsterVariance);
monsterCount = Math.floor(monsterCount);
console.log("Will spawn " + monsterCount + " monsters. monsterDensity:" + monsterDensity + " monsterDensityVariation:" + monsterDensityVariation + " floorTiles.length:" + floorTiles.length);
//Make an array of random monsters to place
let monstersToPlace = [];
let e = Math.floor(monsterCount/2);
let m = monsterCount - e - 1;
let id = 1;
for (let i = 0; i < e; i++){
let monster = monsterFamily.easy[this.random(0,monsterFamily.easy.length-1)];
let randomized = this.randomizeMonster(monster, "easy");
randomized.type = "easy";
randomized.id = id;
id++
monstersToPlace.push(randomized);
}
for (let i = 0; i < m; i++){
let monster = monsterFamily.medium[this.random(0,monsterFamily.medium.length-1)];
let randomized = this.randomizeMonster(monster, "medium");
randomized.type = "medium";
randomized.id = id;
id++
monstersToPlace.push(randomized);
}
let boss = monsterFamily.boss[this.random(0,monsterFamily.boss.length-1)];
let randomized = this.randomizeMonster(boss, "boss");
randomized.type = "boss";
randomized.id = id;
monstersToPlace.push(randomized);
console.log("Here are the monstersToPlace:", monstersToPlace);
//Then place them in the map
let nextMap = map;
let nextNpcs = [];
let monstersSuccessfullySpawned = 0;
for(let j = 0; j < monstersToPlace.length; j++){
let spawn = this.spawn("monster", monstersToPlace[j], nextMap, nextNpcs);
if(spawn.error){
console.log("ERR: spawnMonsters(): Received error from spawn(). monsterDensity is probably too high (" + monsterDensity + "). Discontinuing spawning.");
break;
}
nextMap = spawn.map;
nextNpcs = spawn.npcs;
monstersSuccessfullySpawned = j+1;
}
console.log("Map with monsters:", nextMap);
console.log("spawnMonsters(): nextNpcs:", nextNpcs)
return {
npcs: nextNpcs,
map: nextMap,
monsterCount: monstersSuccessfullySpawned
}
}
updateVisibleMap(map, x, y) {
let visible = [];
let radius = viewSize;
let radiusMax = maxViewSize;
let padding = radiusMax - radius;
let xMin = x - radius;
let xMax = x + radius + 1;
let xBound = map[0].length - 1;
let yMin = y - radius;
let yMax = y + radius + 1;
let yBound = map.length - 1;
//set all currently visible tiles to not visible
for (let row = 0; row < map.length - 1; row++) {
for (let cell = 0; cell < map[0].length - 1; cell++) {
map[row][cell].visible = false;
}
}
//put new set of tiles into the visible map array
for (let row = yMin; row < yMax; row++) {
let newRow = [];
for (let cell = xMin; cell < xMax; cell++) {
let newCell;
//if out of map bounds, add empty (rock) tiles
if (cell < 0 || cell > xBound || row < 0 || row > yBound) {
newCell = {
state: "rock",
x: cell,
y: row
};
//console.log("adding nonexistent cell to visible x:" + cell + " y:" + row);
} else {
newCell = map[row][cell];
//console.log(
// "adding map cell " +
// JSON.stringify(map[row][cell]) +
// " to visible x:" +
// cell +
// " y:" +
// row
//);
}
//set as visible according to a circular radius
let sq = ((cell - x) ** 2) + ((row - y) ** 2)+1;
console.log("r**2: "+(radius**2) + " sq:"+sq + " cell:"+cell + " row:"+row);
if (radius**2 >= sq) {
newCell.visible = true;
} else {
newCell.visible = false;
}
newRow.push(newCell);
}
visible.push(newRow);
}
return visible;
}
//-----LIFECYCLE-----
componentWillMount() {
this.setState(this.generateMap());
document.addEventListener("keydown", this.handleKeyDown.bind(this))
}
render() {
return (
<div id="wrapper">
<Game map={this.state.visibleMap} player={this.state.player} log={this.state.combatLog} depth={this.state.depth} monsterCount={this.state.monsterCount}/>
<Footer title="React Roguelike" />
</div>
);
}
}
class Footer extends React.Component {
render() {
return (
<div className="footer">
<div className="heading">{this.props.title}</div>
<div className="author">
<span>
By <a href={my.site}>Lewis Horwood</a>
</span>
<div className="button-group">
<a href={my.github}>
<div className="button">
<span className="button-chevron">> </span>github
</div>
</a>
<a href={my.freecodecamp}>
<div className="button">
<span className="button-chevron">> </span>freecodecamp
</div>
</a>
</div>
</div>
</div>
);
}
}
class Game extends React.Component {
render() {
return (
<div id="main">
<Log log={this.props.log} />
<Map map={this.props.map} />
<Info
player={this.props.player}
depth={this.props.depth}
monsterCount={this.props.monsterCount}
/>
</div>
);
}
}
class CombatLog extends React.Component {
render() {
let items=this.props.log.slice(-10).reverse();
let log = items.map((i, index) => {
return <li id={index}>{i}</li>
})
return (
<div id="combatLog">
<ul>
{log}
</ul>
</div>
);
}
}
class Log extends React.Component {
render() {
return (
<div className="panel">
<CombatLog log={this.props.log}/>
</div>
);
}
}
class Map extends React.Component {
render() {
return (
<div id="center">
<div id="map">
<Grid map={this.props.map} />
</div>
</div>
);
}
}
class Grid extends React.Component {
renderMap() {
let map = this.props.map;
let output = [];
for (let y = 0; y < map.length; y++) {
let row = map[y].map((cell, index) => {
let displayClass;
if(cell.entity){
displayClass = cell.entity.type;
} else if (cell.item){
displayClass = cell.item.type;
} else {
displayClass = cell.state;
}
return <div className={"mapCell " + displayClass + " " + (cell.visible ? "visible" : "notvisible")} />;
});
output.push(
<div className="mapRow" id={y}>
{row}
</div>
);
}
console.log("Rendering map:", map);
return output;
}
render() {
return <div id="grid">{this.renderMap()}</div>;
}
}
class Info extends React.Component {
render() {
return (
<div className="panel">
<PlayerInfo
player={this.props.player}
depth={this.props.depth}
monsterCount={this.props.monsterCount}
/>
</div>
);
}
}
class PlayerInfo extends React.Component {
render() {
let pluralize = "s";
if(this.props.monsterCount < 2){pluralize = ""}
return (
<div id="playerInfo">
<p className="playerName">{this.props.player.name}</p>
<p className="playerLevel">Level {this.props.player.level}</p>
<p className="levelInfo">Floor {this.props.depth} -- {this.props.monsterCount} monster{pluralize} left</p>
<p>
<PlayerInfoBar type="hp" player={this.props.player} />
</p>
<p>
<PlayerInfoBar type="xp" player={this.props.player} />
</p>
</div>
);
}
}
class PlayerInfoBar extends React.Component {
render() {
const type = this.props.type;
let remaining, max;
if (type == "hp") {
remaining = this.props.player.hp;
max = this.props.player.hpmax;
} else if (type == "xp") {
remaining = this.props.player.xp;
max = this.props.player.xpmax;
}
return (
<div className="playerInfoBar">
<div className="typeName">{type.toUpperCase()}:</div>
<div className={"bar " + this.props.type}>
<div className="infoText">
{remaining}/{max}
</div>
<div className="remaining" style={{ width: remaining / max * 200 }} />
<div className="missing" style={{ width: (1 - remaining / max) * 200 }} />
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
Also see: Tab Triggers