JSFiddle - React, Tailwind, and code Playground

by Gwyn Milcote

HTML

Select discipline: <select id='select-discipline'>
    <option value='casual'>Casual Riding</option>
    <option value='dressage' selected>Dressage</option>
    <option value='sj'>Showjumping</option>
    <option value='cc'>Cross Country</option>
    <option value='driving'>Carriage Driving</option>
</select>
<button id='load-discipline'>Load</button>
<hr>

<div id='game-container'></div>
<div id='game-controls'>

    <!-- Universal. Arrow keys to move/turn -->
    <div id='move-controls' class='controls'>
        <div class='key-btn key-arrow-up' data-key='38' data-type='arrow'>^</div>
        <div class='key-btn key-arrow-left' data-key='37' data-type='arrow'><</div>
        <div class='key-btn key-arrow-right' data-key='39' data-type='arrow'>></div>
        <div class='key-btn key-arrow-down' data-key='40' data-type='arrow'>v</div>
        <!-- Switch -->
        <div class='key-btn key-pause' data-key='40' data-type='arrow'>Pause</div>
        <div class='key-btn key-play' data-key='40' data-type='arrow' style='display:none'>Play</div>
    </div>
    
    <!-- Universal. W/S zoom, A/D rotate -->
    <div id='camera-controls' class='controls'>
        <div class='key-btn key-rotate-left' data-key='65' data-type='camera'><--</div>
        <div class='key-btn key-rotate-right' data-key='68' data-type='camera'>--></div>
        <div class='key-btn key-zoom-in' data-key='87' data-type='camera'>+</div>
        <div class='key-btn key-zoom-out' data-key='83' data-type='camera'>-</div>
    </div>
    
    <!-- Universal. Status bar: points/penalties -->
    <div id='status-panel' class='controls'>
        Status info: current gait/speed, current/next dressage instruction, points/penalties...
    </div>
    
    <!-- Dressage only. Select a gait -->
    <div id='gait-controls' class='controls'>
        <!-- Need separate div, for display: flex -->
        <div id='gait-container'></div>
    </div>
    
    <!-- Jumping only. Show image of next jump. -->
    <div...

CSS

div, img, canvas, button, p, span {
    box-sizing: border-box;
}

/* Three will render game in here. 500 x 340px */
#game-container {
    width: 520px;
    height: 360px;
    margin: 10px auto;
    background-color: silver;
    border: 1px solid grey;
    padding: 10px;
}

#game-controls {
    position: relative;
    width: 520px;
    height: 200px;
    margin: auto;
    background-color: #eee;
}

#game-controls .controls {
    background-color: silver;
    border: 1px solid grey;
    position: absolute;
}

#game-controls #move-controls {
    top: 0;
    left: 0;
    width: 160px;
    height: 150px;
}

#game-controls #camera-controls {
    top: 160px;
    left: 0;
    width: 160px;
    height: 40px;
}

#game-controls #gait-controls {
    top: 40px;
    left: 170px;
    width: 350px;
    height: 160px;
}

#game-controls #status-panel {
    top: 0;
    left: 170px;
    width: 350px;
    height: 30px;
}

#game-controls #next-jump {
    top: 40px;
    left: 360px;
    width: 160px;
    height: 160px;
}

#game-controls #minimap {
    top: 40px;
    left: 170px;
    width: 180px;
    height: 160px;
}

/* Dressage gait buttons */
#gait-container {
    display: flex;
    flex-flow: row wrap;
    padding: 1px 2px;
}
#gait-container .key-btn {
    width: 34px;
    height: 34px;
    margin: 2px;
    background-color: #ffccee;
}
#gait-container .key-btn:hover {
    cursor: pointer;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.4);
}
#gait-container .active-gait {
    box-shadow: inset 0 0 0 1px black;
}

/* Movement, arrow keys */
#move-controls .key-btn {
    position: absolute;
    width: 50px;
    height: 50px;
    font-size: 40px;
    display: flex;
    justify-content: center;
    align-items: center;
    background-color: #eee;
}
#move-controls .key-btn:hover {
    cursor: pointer;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.4);
}
#move-controls .key-arrow-up {
    top: 0;
    left: 54px;
}
#move-controls .key-arrow-down {
    top: 98px;
    left:...

JavaScript

const gaits = [
    {letter: "s", name: "Stop"},
    {letter: "fw", name: "Free Walk"},
    {letter: "mw", name: "Medium Walk"},
    {letter: "cw", name: "Collected Walk"},
    {letter: "ew", name: "Extended Walk"},
    {letter: "th", name: "Turn on Haunches"},
    {letter: "wt", name: "Working Trot"},
    {letter: "mt", name: "Medium Trot"},
    {letter: "ct", name: "Collected Trot"},
    {letter: "et", name: "Extended Trot"},
    {letter: "pa", name: "Passage"},
    {letter: "pi", name: "Piaffe"},
    {letter: "wc", name: "Working Canter"},
    {letter: "mc", name: "Medium Canter"},
    {letter: "cc", name: "Collected Canter"},
    {letter: "ec", name: "Extended Canter"},
    {letter: "hpt", name: "Half Pass Trot"},
    {letter: "hpc", name: "Half Pass Canter"},
    {letter: "si", name: "Shoulder In"},
    {letter: "hi", name: "Haunches In"},
    {letter: "ho", name: "Haunches Out"},
    {letter: "v", name: "Volte"},
    {letter: "pr", name: "Pirouette"},
    {letter: "wb", name: "Walk Back"},
    // Just for fun
    {letter: "lev", name: "Levade"},
    {letter: "cro", name: "Croupade"},
    {letter: "cap", name: "Capriole"},
    {letter: "spa", name: "Spanish Walk"}
];

class RidingGame {

    constructor(){
        this.panels = [];
        let panelNames = [
            ["gait", "gait-controls"],
            ["status", "status-panel"],
            ["minimap", "minimap"],
            ["jump", "next-jump"]
        ]
        for(let p of panelNames){
            this.panels[p[0]] = document.getElementById(p[1]);
        }
        
        this.renderGaitButtons();
        this.loadDiscipline("dressage");
    }
    
    loadDiscipline(name){
        if(name == "dressage"){
            this.hidePanel("minimap");
            this.hidePanel("jump");
            this.showPanel("gait");
        }else if(name == "cc" || name == "sj"){
            this.showPanel("minimap");
            this.showPanel("jump");
            this.hidePanel("gait");
        }else if(name ==...