17 |
(se("SI"))
18 |
19 | (
20 | [(Board.P1, "Player 1"), (Board.P2, "Player 2")]
21 | |> List.mapi(
22 | (i, (player, name)) =>
23 |
true
34 | | _ => false
35 | }
36 | )
37 | ])
38 | )>
39 |
"Sidebar-playerBead Sidebar-playerBead--p1"
43 | | P2 => "Sidebar-playerBead Sidebar-playerBead--p2"
44 | }
45 | )
46 | />
47 | (se(name))
48 |
49 | )
50 | |> Array.of_list
51 | |> ReasonReact.arrayToElement
52 | )
53 |
54 |
64 |
65 |
66 |
67 | (se("New game"))
68 |
69 |
70 |
71 |
72 | (se("About"))
73 |
74 |
75 |
76 |
77 | };
78 |
--------------------------------------------------------------------------------
/src/board.re:
--------------------------------------------------------------------------------
1 | let numRows = 4;
2 |
3 | let iList = [0, 1, 2, 3];
4 |
5 | let ijList =
6 | iList |> List.map((i) => iList |> List.map((j) => (i, j))) |> List.flatten;
7 |
8 | let ijkList =
9 | ijList
10 | |> List.map(((i, j)) => iList |> List.map((k) => (i, j, k)))
11 | |> List.flatten;
12 |
13 | type player =
14 | | P1
15 | | P2;
16 |
17 | type state = array(array(array(option(player))));
18 |
19 | let emptyState: state =
20 | None |> Array.make(numRows) |> Array.make(numRows) |> Array.make(numRows);
21 |
22 | let winner = (state) => {
23 | /* Pair of coordinates at two ends of 4 consecutive places */
24 | let allPairs =
25 | ijkList
26 | |> List.map(
27 | ((z1, x1, y1)) =>
28 | ijkList |> List.map(((z2, x2, y2)) => ((z1, x1, y1), (z2, x2, y2)))
29 | )
30 | |> List.flatten
31 | |> List.filter(
32 | (((z1, x1, y1), (z2, x2, y2))) =>
33 | (z1 != z2 || x1 != x2 || y1 != y2)
34 | && [z1 - z2, x1 - x2, y1 - y2]
35 | |> List.for_all((d) => d mod (numRows - 1) == 0)
36 | );
37 | let interpolate = (a, b, i) => (b - a) / (numRows - 1) * i + a;
38 | let allPositionsForPair = (((z1, x1, y1), (z2, x2, y2))) =>
39 | iList
40 | |> List.map(
41 | (i) => (
42 | interpolate(z1, z2, i),
43 | interpolate(x1, x2, i),
44 | interpolate(y1, y2, i)
45 | )
46 | );
47 | let checkWinner = (p, ((z1, x1, y1), (z2, x2, y2))) =>
48 | allPositionsForPair(((z1, x1, y1), (z2, x2, y2)))
49 | |> List.map(((z, x, y)) => state[z][x][y])
50 | |> List.for_all((el) => el == Some(p));
51 | let isWinner = (p) => allPairs |> List.exists(checkWinner(p));
52 | let findWinnerPair = (p) => allPairs |> List.find(checkWinner(p));
53 | switch (isWinner(P1), isWinner(P2)) {
54 | | (true, true) => None
55 | | (true, false) => Some((P1, allPositionsForPair(findWinnerPair(P1))))
56 | | (false, true) => Some((P2, allPositionsForPair(findWinnerPair(P2))))
57 | | (false, false) => None
58 | }
59 | };
60 |
61 | let isFull = (state) =>
62 | ijList |> List.for_all(((x, y)) => state[numRows - 1][x][y] != None);
63 |
64 | let isEnd = (state) => winner(state) != None || isFull(state);
65 |
66 | let isValidMove = ((x, y), state) =>
67 | ! isEnd(state) && state[numRows - 1][x][y] == None;
68 |
69 | let move = ((x, y), player, state) =>
70 | isValidMove((x, y), state) ?
71 | {
72 | let putPieceOnLayer = ((x, y), player, layer) => {
73 | let newLayer = layer |> Array.map((row) => Array.copy(row));
74 | newLayer[x][y] = Some(player);
75 | newLayer
76 | };
77 | let (newState, _) =
78 | Array.fold_left(
79 | ((curState, hasPlaced), layer) =>
80 | ! hasPlaced && layer[x][y] == None ?
81 | (
82 | Array.append(
83 | curState,
84 | [|putPieceOnLayer((x, y), player, layer)|]
85 | ),
86 | true
87 | ) :
88 | (Array.append(curState, [|layer|]), hasPlaced),
89 | ([||], false),
90 | state
91 | );
92 | newState
93 | } :
94 | state;
95 |
--------------------------------------------------------------------------------
/src/components/BoardView.re:
--------------------------------------------------------------------------------
1 | /* Module called BoardView to avoid conflicting filename with the board logic module */
2 | [@bs.val] external requestAnimationFrame : (unit => unit) => unit = "";
3 |
4 | [@bs.val] external getElementById : string => Dom.element =
5 | "document.getElementById";
6 |
7 | let columnKey = (x, y) => {j|$x-$y|j};
8 |
9 | let columnBaseTransform = ((x, y)) =>
10 | "translate("
11 | ++ (string_of_float(x) ++ ("px, " ++ (string_of_float(y) ++ "px)")));
12 |
13 | let emptyColumnPositions =
14 | (0., 0.) |> Array.make_matrix(Board.numRows, Board.numRows);
15 |
16 | type state = {columnPositions: array(array((float, float)))};
17 |
18 | type actions =
19 | | UpdateColumnPositions;
20 |
21 | let component = ReasonReact.reducerComponent("Board");
22 |
23 | let make = (~rotation, ~board, ~move, _children) => {
24 | ...component,
25 | initialState: () => {columnPositions: emptyColumnPositions},
26 | reducer: (action, _) =>
27 | switch action {
28 | | UpdateColumnPositions =>
29 | ReasonReact.Update({
30 | columnPositions:
31 | emptyColumnPositions
32 | |> Array.mapi(
33 | (x, row) =>
34 | row
35 | |> Array.mapi(
36 | (y, _) => {
37 | let rect =
38 | ReactDOMRe.domElementToObj(
39 | getElementById(BoardBase.markerId(x, y))
40 | )##getBoundingClientRect
41 | ();
42 | (rect##left, rect##top)
43 | }
44 | )
45 | )
46 | })
47 | },
48 | didMount: ({reduce}) => {
49 | let rec onAnimationFrame = () => {
50 | reduce((_) => UpdateColumnPositions, ());
51 | requestAnimationFrame(onAnimationFrame)
52 | };
53 | requestAnimationFrame(onAnimationFrame);
54 | ReasonReact.NoUpdate
55 | },
56 | render: ({state: {columnPositions}}) =>
57 |
58 |
true
63 | | None => false
64 | }
65 | )
66 | />
67 | (
68 | Board.ijList
69 | /* Figure out order in render perspective */
70 | |> List.sort(
71 | ((x1, y1), (x2, y2)) => {
72 | let (_, yPx1) = columnPositions[x1][y1];
73 | let (_, yPx2) = columnPositions[x2][y2];
74 | int_of_float(yPx1) - int_of_float(yPx2)
75 | }
76 | )
77 | |> List.mapi((i, (x, y)) => (i, (x, y)))
78 | /* Put columns back in stable order, otherwise they rerender everytime */
79 | |> List.sort(
80 | ((_, (x1, y1)), (_, (x2, y2))) => x1 == x2 ? y1 - y2 : x1 - x2
81 | )
82 | |> List.map(
83 | ((i, (x, y))) =>
84 |
96 |
102 | if (Board.isValidMove((x, y), board)) {
103 | move((x, y))
104 | }
105 | )
106 | winner=(Board.winner(board))
107 | />
108 |
109 | )
110 | |> Array.of_list
111 | |> ReasonReact.arrayToElement
112 | )
113 |
114 | };
115 |
--------------------------------------------------------------------------------