Skip to content

Instantly share code, notes, and snippets.

@busypeoples
Last active January 16, 2018 08:10
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save busypeoples/ee924fcbd17616dcfc1da3b4f030686e to your computer and use it in GitHub Desktop.
Save busypeoples/ee924fcbd17616dcfc1da3b4f030686e to your computer and use it in GitHub Desktop.
ReactGame Example: React + Flow
// @flow
import React from 'react';
import { render } from 'react-dom';
type Field = 0 | 1;
type Position = [Field, Field, Field, Field];
type Unit = [Position, Position, Position, Position];
type Units = Array<Unit>;
type Board = Array<Array<Field>>;
const unit1: Unit = [[0, 0, 1, 0], [0, 0, 1, 0], [0, 0, 1, 0], [0, 0, 1, 0]];
const unit2: Unit = [[0, 0, 1, 0], [0, 1, 1, 0], [0, 0, 1, 0], [0, 0, 0, 0]];
const unit3: Unit = [[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]];
const unit4: Unit = [[0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 1, 0], [0, 0, 0, 0]];
const unit5: Unit = [[0, 0, 0, 0], [0, 0, 1, 1], [0, 1, 1, 0], [0, 0, 0, 0]];
const unit6: Unit = [[0, 0, 1, 0], [0, 0, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]];
const unit7: Unit = [[0, 0, 0, 0], [0, 1, 1, 0], [0, 0, 1, 1], [0, 0, 0, 0]];
const units: Units = [unit1, unit2, unit3, unit4, unit5, unit6, unit7];
/*
constants
*/
const numberOfRows = 20;
const numberOfColumns = 10;
const msTimeout = 300;
/*
Helper functions
*/
const rotateLeft = (unit: Unit): Unit => {
let [
[a1, a2, a3, a4],
[b1, b2, b3, b4],
[c1, c2, c3, c4],
[d1, d2, d3, d4]
] = unit;
return [
[a4, b4, c4, d4],
[a3, b3, c3, d3],
[a2, b2, c2, d2],
[a1, b1, c1, d1]
];
};
const rotateRight = (unit: Unit): Unit => {
const [
[a1, a2, a3, a4],
[b1, b2, b3, b4],
[c1, c2, c3, c4],
[d1, d2, d3, d4]
] = unit;
return [
[d1, c1, b1, a1],
[d2, c2, b2, a2],
[d3, c3, b3, a3],
[d4, c4, b4, a4]
];
};
let isIntersecting = (
rows: Board,
unit: Unit,
x: number,
y: number
): boolean => {
const foundIntersections = unit.filter((row, i) => {
const found = row.filter((col, j) => {
return (
col === 1 &&
(y + i >= numberOfRows ||
x + j < 0 ||
x + j >= numberOfColumns ||
rows[y + i][x + j] === 1)
);
});
return found.length > 0;
});
return foundIntersections.length > 0;
};
const emptyRow: Array<Field> = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
const createRows = (
rows: number = numberOfRows,
cols: number = numberOfColumns
): Board => {
return Array.from(Array(rows).keys()).map(row => emptyRow);
};
const updateBoard = (rows: Board, unit: Unit, x: number, y: number): Board => {
let newRows = rows.map(row => row.map(col => col));
unit.forEach((row, i) =>
row.forEach((col, j) => {
if (col === 1) {
newRows[y + i][x + j] = 1;
}
})
);
return newRows;
};
const hasEmptyField = (row: Array<Field>): boolean => {
const found = row.filter(col => col === 0);
return found.length > 0;
};
const removeFinishedRows = (board: Board): Board =>
board.reduce((result, row) => {
return hasEmptyField(row) ? [...result, [...row]] : result;
}, []);
const randomizeUnit = (units: Units): Unit =>
units[Math.floor(Math.random() * units.length)];
const getBackgroundColor = col => {
switch (col) {
case 1:
return '#000';
default:
return '#fff';
}
};
const GameStateTypes = {
Initial: 'Initial',
Play: 'Play',
Pause: 'Pause',
End: 'End'
};
type GameState = $Values<typeof GameStateTypes>;
/*
<Board />
*/
type BoardProps = {
rows: Board | Unit,
gameState?: GameState
};
const gameStateInit = GameStateTypes.Initial;
const GameBoard = ({ rows, gameState = gameStateInit }: BoardProps) => (
<div style={{ opacity: gameState === GameStateTypes.End ? '.5' : '1' }}>
{rows.map((row, i) => (
<div key={`key-${i}`} style={{ display: 'inline' }}>
{row.map((col, j) => (
<div
key={`col-${i}-${j}`}
style={{
width: '30px',
height: '30px',
float: 'left',
border: '1px solid #eee',
background: getBackgroundColor(col)
}}
/>
))}
<br style={{ clear: 'both' }} />
</div>
))}
</div>
);
const getGameStateInfo = (gameState: GameState): string => {
switch (gameState) {
case GameStateTypes.Initial:
return 'Press the spacebar to start';
case GameStateTypes.Play:
return 'Press the spacebar to Pause';
case GameStateTypes.Pause:
return 'Press the spacebar to Continue';
case GameStateTypes.End:
return 'Game Over! Press the spacebar to Restart';
default:
return 'Press the spacebar to start';
}
};
/*
<Info />
*/
type InfoProps = {
score: number,
next: Unit,
gameState: GameState
};
const Info = ({ score, next, gameState }: InfoProps) => (
<div>
<h3>ReasonML Experiment</h3>
<br />
<div>{getGameStateInfo(gameState)}</div>
<br />
<div>
{' '}
<h4>Score: {score}</h4>{' '}
</div>
<br />
<div>
Next: <br /> <br /> <GameBoard rows={next} />{' '}
</div>
<br />
<div>
How to Play:
<br />
<br />
<div>a/d: rotate (left/right)</div>
<div>j/k: navigate (left/right)</div>
<div>s: navigate (down)</div>
</div>
</div>
);
const Toggle = 'Toggle';
const Tick = 'Tick';
const MoveLeft = 'MoveLeft';
const MoveRight = 'MoveRight';
const MoveDown = 'MoveDown';
const RotateLeft = 'RotateLeft';
const RotateRight = 'RotateRight';
const Drop = 'Drop';
/*
Game
*/
type Actions =
| 'Toggle'
| 'Tick'
| 'MoveLeft'
| 'MoveRight'
| 'MoveDown'
| 'RotateLeft'
| 'RotateRight'
| 'Drop';
const runEvent = (event: SyntheticKeyboardEvent<HTMLElement>, reduce) => {
const key = event.charCode;
switch (key) {
case 32:
reduce(() => Toggle);
break;
case 97:
reduce(() => RotateLeft);
break;
case 100:
reduce(() => RotateRight);
break;
case 106:
reduce(() => MoveLeft);
break;
case 107:
reduce(() => MoveRight);
break;
case 115:
reduce(() => MoveDown);
break;
default:
// Do Nothing...
}
};
type GameComponentState = {
gameState: GameState,
score: number,
board: Board,
unit: Unit,
next: Unit,
posX: number,
posY: number
};
const initializeState = (units): GameComponentState => ({
gameState: GameStateTypes.Initial,
score: 0,
unit: randomizeUnit(units),
next: randomizeUnit(units),
posX: 3,
posY: 0,
board: [...createRows()]
});
const divStyleLeft = {
width: '30%',
float: 'left',
padding: '3%',
minWidth: '450px'
};
let divStyleRight = {
float: 'left',
paddingLeft: '3%',
paddingRight: '3%',
paddingTop: '1%',
paddingBottom: '5%'
};
let mainStyling = {
outlineColor: '#fff',
fontSize: '1.5em',
width: '90%'
};
const play = reduce => () => {
reduce(() => Tick);
};
class Game extends React.Component<{}, GameComponentState> {
intervalId: ?number;
reducer: Function;
constructor(props) {
super(props);
this.state = initializeState(units);
this.intervalId = null;
this.reducer = this.reducer.bind(this);
}
reducer(actionFn) {
const action: Actions = actionFn();
const state = this.state;
switch (action) {
case Toggle:
switch (state.gameState) {
case GameStateTypes.Play:
this.setState(
state => ({ gameState: GameStateTypes.Pause }),
() => {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
);
break;
case GameStateTypes.End:
this.setState(
state => ({
...initializeState(units),
gameState: GameStateTypes.Play
}),
() => {
if (!this.intervalId) {
this.intervalId = setInterval(play(this.reducer), msTimeout);
}
}
);
break;
default:
this.setState(
state => ({ gameState: GameStateTypes.Play }),
() => {
if (!this.intervalId) {
this.intervalId = setInterval(play(this.reducer), msTimeout);
}
}
);
}
break;
case Tick:
if (state.gameState === GameStateTypes.End) {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
} else if (
isIntersecting(state.board, state.unit, state.posX, state.posY + 1)
) {
const nextBoard = updateBoard(
state.board,
state.unit,
state.posX,
state.posY
);
const nextRows = removeFinishedRows(nextBoard);
const rowsRemoved = numberOfRows - nextRows.length;
const board = rowsRemoved
? [...createRows(rowsRemoved), ...nextRows]
: nextRows;
const score =
state.score + 10 + rowsRemoved * rowsRemoved * numberOfColumns * 3;
const unit = state.next;
const posX = numberOfColumns / 2 - 2;
const posY = 0;
const next = randomizeUnit(units);
if (isIntersecting(board, state.next, numberOfColumns / 2 - 2, 0)) {
this.setState(state => ({
unit,
posX,
posY,
next,
score,
board,
gameState: GameStateTypes.End
}));
} else {
this.setState(state => ({
...state,
unit,
posX,
posY,
next,
score,
board
}));
}
} else {
this.setState(state => ({ posY: state.posY + 1 }));
}
break;
case MoveLeft:
if (
state.gameState === GameStateTypes.Play &&
!isIntersecting(state.board, state.unit, state.posX - 1, state.posY)
) {
this.setState(state => ({ posX: state.posX - 1 }));
}
break;
case MoveRight:
if (
state.gameState === GameStateTypes.Play &&
!isIntersecting(state.board, state.unit, state.posX + 1, state.posY)
) {
this.setState(state => ({ posX: state.posX + 1 }));
}
break;
case MoveDown:
if (
state.gameState === GameStateTypes.Play &&
!isIntersecting(state.board, state.unit, state.posX, state.posY + 1)
) {
this.setState(state => ({ posY: state.posY + 1 }));
}
break;
case RotateLeft:
const nextUnit = rotateLeft(state.unit);
if (
state.gameState === GameStateTypes.Play &&
!isIntersecting(state.board, nextUnit, state.posX, state.posY)
) {
this.setState(state => ({ unit: nextUnit }));
}
break;
case RotateRight:
const newPiece = rotateRight(state.unit);
if (
state.gameState === GameStateTypes.Play &&
!isIntersecting(state.board, newPiece, state.posX, state.posY)
) {
this.setState(state => ({ unit: newPiece }));
}
break;
case Drop:
/* Implement later on */
break;
default:
// Do Nothing...
}
}
render() {
const { board, unit, posX, posY, score, gameState, next } = this.state;
const displayRows =
gameState === GameStateTypes.Initial
? board
: updateBoard(board, unit, posX, posY);
return (
<div
onKeyPress={e => runEvent(e, this.reducer)}
style={mainStyling}
tabIndex="0"
>
Click anywhere on the screen for focus
<div style={divStyleLeft}>
<GameBoard rows={displayRows} gameState={gameState} />
</div>
<div style={divStyleRight}>
<Info score={score} next={next} gameState={gameState} />
</div>
</div>
);
}
}
const root = document.getElementById('root');
if (root) {
render(<Game />, root);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment