Created
June 13, 2021 13:42
-
-
Save busypeoples/0867cb06aaf978e45b68a401754f53c0 to your computer and use it in GitHub Desktop.
ReScript Game
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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