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 ==...