Skip to content

Instantly share code, notes, and snippets.

@busypeoples
Created June 13, 2021 13:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save busypeoples/0867cb06aaf978e45b68a401754f53c0 to your computer and use it in GitHub Desktop.
Save busypeoples/0867cb06aaf978e45b68a401754f53c0 to your computer and use it in GitHub Desktop.
ReScript Game
type field =
| O
| X
type position = (field, field, field, field)
type box = (position, position, position, position)
type units = list<box>
let unit1: box = ((O, O, X, O), (O, O, X, O), (O, O, X, O), (O, O, X, O))
let unit2: box = ((O, O, X, O), (O, X, X, O), (O, O, X, O), (O, O, O, O))
let unit3: box = ((O, O, O, O), (O, X, X, O), (O, X, X, O), (O, O, O, O))
let unit4: box = ((O, X, O, O), (O, X, O, O), (O, X, X, O), (O, O, O, O))
let unit5: box = ((O, O, O, O), (O, O, X, X), (O, X, X, O), (O, O, O, O))
let unit6: box = ((O, O, X, O), (O, O, X, O), (O, X, X, O), (O, O, O, O))
let unit7: box = ((O, O, O, O), (O, X, X, O), (O, O, X, X), (O, O, O, O))
let units: units = list{unit1, unit2, unit3, unit4, unit5, unit6, unit7}
/*
constants
*/
let numberOfRows = 20
let numberOfColumns = 10
let msTimeout = 300
/*
Helper functions
*/
let rotateLeft = (box: box): box => {
let (
(a1, a2, a3, a4),
(b1, b2, b3, b4),
(c1, c2, c3, c4),
(d1, d2, d3, d4),
) = box
((a4, b4, c4, d4), (a3, b3, c3, d3), (a2, b2, c2, d2), (a1, b1, c1, d1))
}
let rotateRight = (box: box): box => {
let (
(a1, a2, a3, a4),
(b1, b2, b3, b4),
(c1, c2, c3, c4),
(d1, d2, d3, d4),
) = box
((d1, c1, b1, a1), (d2, c2, b2, a2), (d3, c3, b3, a3), (d4, c4, b4, a4))
}
let unitToArray = (box: box): array<array<field>> => {
let (
(a1, a2, a3, a4),
(b1, b2, b3, b4),
(c1, c2, c3, c4),
(d1, d2, d3, d4),
) = box
[[a1, a2, a3, a4], [b1, b2, b3, b4], [c1, c2, c3, c4], [d1, d2, d3, d4]]
}
let isIntersecting = (
rows: array<array<field>>,
box: box,
y: int,
x: int,
): bool => {
let unitArr = unitToArray(box)
let found = ref(false)
Array.iteri((i, row) => Array.iteri((j, col) =>
switch col {
| X =>
if (
y + i >= numberOfRows ||
(x + j < 0 ||
(x + j >= numberOfColumns || rows[y + i][x + j] === X))
) {
found := true
}
| _ => ()
}
, row), unitArr)
found.contents
}
let createRows = () => Array.make_matrix(numberOfRows, numberOfColumns, O)
let updateBoard = (rows: array<array<field>>, box: box, x: int, y: int): array<
array<field>,
> => {
let newRows = Array.map(row => Array.copy(row), rows)
let unitArr = unitToArray(box)
Array.iteri((i, row) => Array.iteri((j, col) =>
switch col {
| X => newRows[y + i][x + j] = X
| _ => ()
}
, row), unitArr)
newRows
}
let removeFinishedRows = (board: array<array<field>>): array<array<field>> =>
Array.fold_left((result, row) =>
switch row {
| [X, X, X, X, X, X, X, X, X, X] => result
| _ => Array.append(result, [Array.copy(row)])
}
, [], board)
let randomizeUnit = (units: units): box =>
List.nth(units, Random.int(List.length(units)))
let se = React.string
let getBackgroundColor = col =>
switch col {
| X => "#000"
| _ => "#fff"
}
type gameState =
| Initial
| Play
| Pause
| End
/*
<Board />
*/
module Board = {
@react.component
let make = (~rows, ~gameState=Initial) => {
<div
style={ReactDOM.Style.make(
~opacity=switch gameState {
| End => ".5"
| _ => "1"
},
(),
)}>
{rows
|> Array.mapi((i, row) =>
<div
key={"key" ++ string_of_int(i)}
style={ReactDOM.Style.make(~display="inline", ~float="left", ())}>
{row
|> Array.mapi((j, col) =>
<div
key={"col" ++ (string_of_int(i) ++ ("-" ++ string_of_int(j)))}
style={ReactDOM.Style.make(
~width="30px",
~height="30px",
~float="left",
~border="1px solid #eee",
~background=getBackgroundColor(col),
(),
)}
/>
)
|> React.array}
</div>
)
|> React.array}
</div>
}
}
module Info = {
@react.component
let make = (~score, ~next, ~gameState) => {
<div>
<h3> {se("ReScript Experiment")} </h3>
<br />
<div>
{switch gameState {
| Initial => se("Press the spacebar to start")
| Play => se("Press the spacebar to Pause")
| Pause => se("Press the spacebar to Continue")
| End => se("Game Over! Press the spacebar to Restart")
}}
</div>
<br />
<div> <h4> {se("Score: " ++ string_of_int(score))} </h4> </div>
<br />
<div style={ReactDOM.Style.make(~maxWidth="150px", ())}>
{se("Next:")} <br /> <br /> <Board rows=next />
</div>
<br />
<div style={ReactDOM.Style.make(~float="left", ~marginTop="2em", ())}>
{se("How to Play:")}
<br />
<br />
<div> {se("a/d: rotate (left/right)")} </div>
<div> {se("j/k: navigate (left/right)")} </div>
<div> {se("s: navigate (down)")} </div>
</div>
</div>
}
}
/*
Game
*/
type actions =
| Toggle
| Tick
| MoveLeft
| MoveRight
| MoveDown
| RotateLeft
| RotateRight
| Drop
type state = {
gameState: gameState,
score: int,
board: array<array<field>>,
box: box,
next: box,
posX: int,
posY: int,
}
let getKey = (event, reduce) => {
let key = ReactEvent.Keyboard.charCode(event)
switch key {
| 32 => reduce(Toggle)
| 97 => reduce(RotateLeft)
| 100 => reduce(RotateRight)
| 106 => reduce(MoveLeft)
| 107 => reduce(MoveRight)
| 115 => reduce(MoveDown)
| _ => ()
}
}
module Game = {
let initializeState = () => {
gameState: Initial,
score: 0,
box: randomizeUnit(units),
next: randomizeUnit(units),
posY: 0,
posX: 3,
board: Array.copy(createRows()),
}
let play = (reduce, ()) => {
reduce(Tick)
()
}
@react.component
let make = () => {
let (state, dispatch) = React.useReducer((state, action) =>
switch action {
| Toggle =>
switch state.gameState {
| Play => {...state, gameState: Pause}
| End => {...initializeState(), gameState: Play}
| _ => {...state, gameState: Play}
}
| Tick =>
if isIntersecting(state.board, state.box, state.posY + 1, state.posX) {
let nextBoard = updateBoard(
state.board,
state.box,
state.posX,
state.posY,
)
let nextRows = removeFinishedRows(nextBoard)
let rowsRemoved = numberOfRows - Array.length(nextRows)
let newRows = Array.make_matrix(rowsRemoved, 10, O)
let board = Array.append(newRows, nextRows)
let score =
state.score + 10 + rowsRemoved * rowsRemoved * numberOfColumns * 3
let box = state.next
let posY = 0
let posX = numberOfColumns / 2 - 2
let next = randomizeUnit(units)
if isIntersecting(board, state.next, 0, numberOfColumns / 2 - 2) {
{
box: box,
posY: posY,
posX: posX,
next: next,
score: score,
board: board,
gameState: End,
}
} else {
{
...state,
box: box,
posY: posY,
posX: posX,
next: next,
score: score,
board: board,
}
}
} else {
{...state, posY: state.posY + 1}
}
| MoveLeft =>
if (
state.gameState === Play &&
!isIntersecting(state.board, state.box, state.posY, state.posX - 1)
) {
{...state, posX: state.posX - 1}
} else {
state
}
| MoveRight =>
if (
state.gameState === Play &&
!isIntersecting(state.board, state.box, state.posY, state.posX + 1)
) {
{...state, posX: state.posX + 1}
} else {
state
}
| MoveDown =>
if (
state.gameState === Play &&
!isIntersecting(state.board, state.box, state.posY + 1, state.posX)
) {
{...state, posY: state.posY + 1}
} else {
state
}
| RotateLeft =>
let newPiece = rotateLeft(state.box)
if (
state.gameState === Play &&
!isIntersecting(state.board, newPiece, state.posY, state.posX)
) {
{...state, box: newPiece}
} else {
state
}
| RotateRight =>
let newPiece = rotateRight(state.box)
if (
state.gameState === Play &&
!isIntersecting(state.board, newPiece, state.posY, state.posX)
) {
{...state, box: newPiece}
} else {
state
}
| Drop => state /* Implement later on */
}
, initializeState())
let intervalId = ref(None)
React.useEffect2(() => {
if state.gameState !== Play {
switch intervalId.contents {
| Some(id) =>
Js.Global.clearInterval(id)
intervalId := None
| None => ()
}
} else {
switch intervalId.contents {
| Some(_id) => ()
| None =>
intervalId := Some(Js.Global.setInterval(play(dispatch), msTimeout))
}
}
Some(
() => {
switch intervalId.contents {
| Some(id) =>
Js.Global.clearInterval(id)
intervalId := None
| None => ()
}
},
)
}, (intervalId, state.gameState))
let {board, box, posY, posX, score, gameState, next} = state
let displayRows = switch gameState {
| Initial => board
| _ => updateBoard(board, box, posX, posY)
}
let divStyleLeft = ReactDOM.Style.make(
~width="30%",
~float="left",
~padding="3%",
~minWidth="450px",
(),
)
let divStyleRight = ReactDOM.Style.make(
~float="left",
~paddingLeft="3%",
~paddingRight="3%",
~paddingTop="1%",
~paddingBottom="5%",
(),
)
let mainStyling = ReactDOM.Style.make(
~outlineColor="#fff",
~fontSize="1.5em",
~width="90%",
(),
)
<div onKeyPress={e => getKey(e, dispatch)} style=mainStyling tabIndex=0>
{se("Click anywhere on the screen for focus")}
<div style=divStyleLeft> <Board rows=displayRows gameState /> </div>
<div style=divStyleRight>
<Info score next={unitToArray(next)} gameState />
</div>
</div>
}
}
switch ReactDOM.querySelector("#root") {
| Some(root) => ReactDOM.render(<Game />, root)
| None => ()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment