JSFiddle - React, Tailwind, and code Playground

by Gwyn Milcote

HTML

<div id='df-container'>

    <!-- Title screen. Choose an option. -->
    <div id='df-screen-title' style='display: none;'>
	    // generate a world
		// load a world (input json)
	    // start a new fortress
		// continue a fortress (input json)
	</div>

	
    <!-- Canvas in here. -->
	<div id='df-map'></div>

    <!-- Visible in TR corner, if menu hidden. -->
    <button id='show-menu-btn' style='display: none;'>Menu</button>
	
    <div id='df-menu'>
	
	    <div id='df-menu-top'>
		    <!-- Toggle menu visibility. -->
		    <button id='hide-menu-btn'>Hide</button>
            <!-- Show index tab. -->
            <button class='df-menu-tab-btn' data-tab='index'>Index</button>
			<!-- Pause/resume game. -->
		    <button id='pause-btn'>Pause</button>
			<!-- Bring up the settings menu. -->
			<button id='settings-btn'>Settings</button>
		</div>
		
		<!-- These inner menus are switched in/out, updated. -->
	    <div id='df-menu-inner'>
		    
            
            Hovering pixels: <span id='map-hover-coords'></span>
            <br>Hovered tile: <span id='map-hover-tile'></span>
            <br>Selected tiles: <span id='map-selected-tiles'></span>
            
            <hr>
            
            <!-- Switch between tabs. -->
            <div class='df-menu-tab' id='df-menu-tab-index'>
                <button class='df-menu-tab-btn' data-tab='lists'>Lists</button>
                <button class='df-menu-tab-btn' data-tab='mining'>Mining</button>
                <button class='df-menu-tab-btn' data-tab='farming'>Farming</button>
                
            </div>
            
			<!-- Bring up lists of things, in separate boxes. -->
			<div class='df-menu-tab' id='df-menu-tab-lists' style='display: none;'>
                <p>Lists: a record of everything in the database. Some details will be vague if you don't have a proper bookkeeper.</p>
			    <button class='show-list-btn' data-list='dwarves'>Dwarves</button>
			    <button class='show-list-btn'...

CSS

div, img, p, button, table, tr, td, canvas {
    box-sizing: border-box;
}


#df-container {
    width: 800px;
    height: 500px;
    background-color: #222;
    color: #ccc;
    position: relative;
    font-family: monospace;
    font-size: 13px;
}

#df-container button {
    font-family: monospace;
    font-size: 13px;
    padding: 2px 5px;
    background-color: #aaa;
    border: 1px solid #000;
    border-radius: 0;
    transition: 0.1s;
}
#df-container button:hover {
    cursor: pointer;
    background-color: #eee;
    transition: 0.1s;
}



#df-map {
    position: absolute;
    top: 0;
    left: 0;
    width: 500px; /* 780 if expanded */
    height: 100%;
    border: 10px solid #aaa;
}/*
#df-map canvas {
    position: absolute;
    top: 0;
    left: 0;
    background-color: orange;
}*/



#show-menu-btn {
    position: absolute;
    top: 10px;
    right: 10px;
}

#df-menu {
    position: absolute;
    top: 0;
    right: 0;
    width: 300px;
    height: 100%;
    border: 10px solid #aaa;
    padding: 10px;
}
#df-menu-top {
    display: flex;
    flex-flow: row;
    justify-content: space-between;
    border-bottom: 1px solid #aaa;
    padding-bottom: 5px;
    margin-bottom: 5px;
}

#df-menu-inner {
    height: 430px;
    overflow: auto;
}

JavaScript

/*
* Return index of object, or -1 if not found.
* Needed for un-selecting tiles.
*/
function arrayIncludesObject(array, object){
    let o = JSON.stringify(object);
    return array.findIndex(row => JSON.stringify(row) == o);
}




class GameHandler {

    constructor(){
        /*
        * Visual size of the map canvas.
        * Taking 10px CSS borders into account.
        */
        this.canvasWidth = 480; // 780 at max
        this.canvasHeight = 480;
        
        /*
        * Camera position. Starts in top left corner.
        * Add these numbers to a tile's indices when
        * looking up the actual tile clicked on screen.
        */
        this.cameraX = 0;
        this.cameraY = 0;
        
        /*
        * Which tab is currently open.
        */
        this.showMenuTab("index");
        
        /*
        * Whether to detect cursor/click on canvas.
        * Set to true when designating, etc.
        */
        this.detectHover = true;
        this.detectClick = true;
    }

    showMenu(){
        $("#show-menu-btn").hide();
        $("#df-menu").show();
        $("#df-map").width(480);
        this.canvasWidth = 480;
        Canvas.setSize(480, 480);
        Canvas.update();
    }
    
    hideMenu(){
        $("#df-menu").hide();
        $("#show-menu-btn").show();
        $("#df-map").width(780);
        this.canvasWidth = 780;
        Canvas.setSize(780, 480);
        Canvas.update();
    }
    
    setCanvasSize(w, h){
        this.canvasWidth = w;
        this.canvasHeight = h;
    }
    
    showMenuTab(tab){
        this.menuTab = tab;
        $(".df-menu-tab").hide();
        $("#df-menu-tab-" + tab).show();
    }
        

}


/*
* One canvas is displayed on page. Made of 2 layers.
* The map, then a layer for highlighting/selecting tiles.
*/
class CanvasHandler {

    constructor(){
        this.tileSize = 16;
        
        /*
        * Store XY coords of relevant tiles.
        * For selecting stuff, before confirming.
    ...