Skip to content

Instantly share code, notes, and snippets.

@babyuniverse
Created March 27, 2024 04:17
Show Gist options
  • Save babyuniverse/67ac67b93301f44f727013495ed71164 to your computer and use it in GitHub Desktop.
Save babyuniverse/67ac67b93301f44f727013495ed71164 to your computer and use it in GitHub Desktop.
Mario 3 Mini Game in React
<div class="app"></div>
<div class="helper">Press a key or tap on mobile </div>

Mario 3 Mini Game in React

Uses react and CSS animations to recreate the slot machine mini game from Super Mario Bros. 3.

In hindsight, this was a really weird tool for this job. But I learned a bit more about React, so I have that going for me.

Some of the CSS is wonky as hell.

Also, I am not claiming copyright to a game that was released when I was a toddler. It's just a tribute. Go easy, Nintendo.

A Pen by Dario Corsi on CodePen.

License.

class App extends React.Component {
constructor(){
super();
this.state = {
rows: [
{
name: 'top',
index: 0,
value: 0,
endValue: 0,
speed: 200,
isRunning: true,
key: Math.random(),
direction: 'ltr'
},
{
name: 'center',
value: 0,
index: 1,
endValue: 0,
speed: 200,
isRunning: true,
key: Math.random(),
direction: 'rtl'
},
{
name: 'bottom',
value: 0,
index: 2,
endValue: 0,
speed: 200,
isRunning: true,
key: Math.random(),
direction: 'ltr'
}
],
prize: 'none',
activeRowIndex: 0
}
this.handleClick = this.handleClick.bind(this);
this.updateActiveRow = this.updateActiveRow.bind(this);
this.setEndValue = this.setEndValue.bind(this);
this.setRotatingValue = this.setRotatingValue.bind(this);
this.cancelInterval = this.cancelInterval.bind(this);
this.resetGame = this.resetGame.bind(this);
this.determinePrize = this.determinePrize.bind(this);
document.body.addEventListener('touchstart', this.handleClick.bind(this));
window.addEventListener('keypress', this.handleClick.bind(this));
}
handleClick(){
var index = this.state.activeRowIndex;
// If click occurs while a row is active
if(index < this.state.rows.length){
//Cancel the row's timer
this.cancelInterval(index);
//And set the value it ended on
this.setEndValue(index, this.state.rows[index].value);
this.determinePrize();
}
// Update the active row index every click
this.updateActiveRow();
}
updateActiveRow(){
//If the active section isn't a row
if( this.state.activeRowIndex < this.state.rows.length){
var index = this.state.activeRowIndex + 1;
this.setState({activeRowIndex: index });
} else{
this.resetGame();
}
}
determinePrize(){
var rows = this.state.rows;
var endValues = rows.map( function(row){
return row.endValue;
});
var prize = '';
endValues.forEach( function(value, index){
if(endValues[index] !== endValues[0]){
prize = 3; //code for 'No Prize'
} else{
prize = endValues[0];
}
});
console.log(prize);
this.setState({prize: prize});
}
resetGame(){
//Generate new key for each row. This forces re-rendering and resetting of timers.
var rows = this.state.rows.map( function(row){
//Generate new key
row.key = Math.random();
//Reset running timer
row.isRunning = true;
return row;
});
//Set the state
this.setState({rows: rows});
this.setState({activeRowIndex: 0});
}
setRotatingValue(index, value){
var rows = this.state.rows;
var row = rows[index];
row.value = value;
rows[index] = row;
this.setState({rows: rows});
}
setEndValue(index, value){
var rows = this.state.rows;
var row = rows[index];
row.endValue = value;
rows[index] = row;
this.setState({rows: rows});
}
cancelInterval(index){
var rows = this.state.rows;
var row = rows[index];
row.isRunning = false;
rows[index] = row;
this.setState({rows: rows});
}
render(){
var rows = this.state.rows.map( function(row){
return(
<Row
name={row.name}
index={row.index}
data={this.state}
setEndValue={this.setEndValue}
setRotatingValue={this.setRotatingValue}
isRunning={row.isRunning}
speed={row.speed}
key={row.key}
direction={row.direction}
/>
)
}, this);
return (
<div key={this.state.key} ref="game">
<div className="viewport">
<div className="game">
<div className="rows">
{rows}
</div>
</div>
<Results shown={this.state.activeRowIndex === 3} prize={this.state.prize}/>
</div>
</div>
)
}
}
class Row extends React.Component {
constructor(){
super();
this.state = {value: 0};
this.counterIntervalFunction = this.counterIntervalFunction.bind(this);
this.clearCounterInterval = this.clearCounterInterval.bind(this);
}
componentWillMount(){
var interval = setInterval( this.counterIntervalFunction, this.props.speed);
this.setState({interval: interval})
}
counterIntervalFunction(){
if( this.props.isRunning && this.props.direction === 'ltr'){
var value = this.state.value < 2 ? this.state.value + 1 : 0;
this.setState({value: value});
this.props.setRotatingValue(this.props.index, this.state.value);
} else if( this.props.isRunning && this.props.direction === 'rtl'){
var value = this.state.value > 0 ? this.state.value - 1 : 2;
this.setState({value: value});
this.props.setRotatingValue(this.props.index, this.state.value);
}
else{
this.clearCounterInterval();
}
}
clearCounterInterval(){
clearInterval(this.state.interval);
}
render(){
var activeRowIndex = this.props.data.activeRowIndex;
var activeClass = this.props.index === activeRowIndex ? 'active' : '';
var columnsClassList = 'columns columns-' + this.props.name;
var wrapperClassList = 'row ' + activeClass;
var animation = this.props.direction + '-transition-' + this.state.value;
var style = {
animationName: animation,
animationDuration: this.props.speed + 'ms'
}
return (
<div className={wrapperClassList}>
<div className={columnsClassList} style={style}>
<div className="column"></div>
<div className="column"></div>
<div className="column"></div>
</div>
</div>
)
}
}
class Results extends React.Component{
constructor(){
super();
this.state = {
messages: [
'3UP',
'5UP',
'2UP',
'No Prize'
]
}
}
render(){
var shown = this.props.shown ? 'shown' : '';
var classList = 'results ' + shown;
return(
<div className={classList}>
{this.state.messages[this.props.prize]}
</div>
)
}
}
// Render the app
ReactDOM.render(
<App/>,
document.querySelector('.app')
)
<script src="https://fb.me/react-15.1.0.min.js"></script>
<script src="https://fb.me/react-dom-15.1.0.min.js"></script>
<script src="https://codepen.io/dariocorsi/pen/de198bdaf7bb0bf03ac880d82fdfa5d5.js?editors=0010"></script>
$color-body-bg: #000000;
$color-game-bg: #FDCBC4;
$color-message: #B53121;
// .debug{
// position:fixed;
// bottom:0;
// right:0;
// left:0;
// background:white;
// color:black;
// }
body{
font-family: 'VT323', sans-serif;
background:$color-body-bg;
&:after{
content:'';
display:block;
position:fixed;
top:50%;
right:18%;
left:18%;
border: .5vw solid white;
bottom:44%;
padding-top:39%;
transform: translateY(-50%);
box-sizing:border-box;
}
}
.helper{
position:fixed;
bottom:1rem;
z-index:100;
color:#fff;
text-align:center;
left:0;
right:0;
pointer-events:none;
font-size:3vw;
opacity:.5;
}
.viewport{
display:block;
position:fixed;
top:0;
right:0;
bottom:0;
left:0;
width:100%;
overflow:hidden;
box-sizing:border-box;
&:before{
content:'';
display:block;
position:fixed;
top:0;
left:0;
bottom:0;
width:20%;
background:$color-body-bg;
z-index:10;
}
&:after{
content:'';
display:block;
position:fixed;
top:0;
right:0;
bottom:0;
width:20%;
background:$color-body-bg;
z-index:10;
}
}
.game{
position:absolute;
top:50%;
right:0;
left:0;
transform:translateY(-50%);
}
.results{
position:absolute;
top:0;
right:0;
bottom:0;
left:0;
display:flex;
align-items:center;
justify-content:center;
transform:translateY(100%);
transition: transform 1s ease;
color:$color-message;
font-size:6vw;
text-shadow: 2px 2px 2px #fff;
&.shown{
transform:translateY(0);
}
}
.columns{
background-color:$color-game-bg;
background-size:100% 100%;
animation-timing-function: linear;
animation-fill-mode: forwards;
&.columns-top{
background-image: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/233661/mario-top.svg');
}
&.columns-center{
background-image: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/233661/mario-center.svg');
}
&.columns-bottom{
background-image: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/233661/mario-bottom.svg');
}
&:after{
content:'';
display:block;
padding-top: 12.07%;
}
}
@keyframes ltr-transition-0 {
0%{
background-position: 0vw;
}
100%{
background-position: 33.3333vw;
}
}
@keyframes ltr-transition-1 {
0%{
background-position: 33.3333vw;
}
100%{
background-position:66.6666vw;
}
}
@keyframes ltr-transition-2 {
0%{
background-position: 66.6666vw;
}
100%{
background-position: 100vw;
}
}
@keyframes rtl-transition-0 {
0%{
background-position: -33.3333vw;
}
100%{
background-position: -66.6666vw;
}
}
@keyframes rtl-transition-1 {
0%{
background-position: -100vw;
}
100%{
background-position: -133.3333vw;
}
}
@keyframes rtl-transition-2 {
0%{
background-position: -166.6666vw;
}
100%{
background-position: -200vw;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment