From b9eca47a2f5f85172557f38837b1a13433bb07de Mon Sep 17 00:00:00 2001 From: Pahlevi Fikri Auliya Date: Mon, 2 Oct 2017 19:18:16 +0700 Subject: [PATCH] Major refactor. Separate module into files --- bsconfig.json | 2 +- src/retris/RootComp.re | 1 + .../{board.re => components/BoardComp.re} | 4 +- .../{game.re => components/GameComp.re} | 24 +- src/retris/engine.re | 522 ------------------ src/retris/engine.rei | 62 --- src/retris/engine/block.re | 26 + src/retris/engine/block.rei | 7 + src/retris/engine/board.re | 238 ++++++++ src/retris/engine/board.rei | 26 + src/retris/engine/coordinate.re | 8 + src/retris/engine/coordinate.rei | 8 + src/retris/engine/engine.re | 70 +++ src/retris/engine/engine.rei | 11 + src/retris/engine/matrix.re | 47 ++ src/retris/engine/matrix.rei | 8 + src/retris/engine/tetromino.re | 119 ++++ src/retris/engine/tetromino.rei | 21 + src/retris/retrisRoot.re | 1 - webpack.config.js | 2 +- 20 files changed, 606 insertions(+), 601 deletions(-) create mode 100644 src/retris/RootComp.re rename src/retris/{board.re => components/BoardComp.re} (93%) rename src/retris/{game.re => components/GameComp.re} (75%) delete mode 100644 src/retris/engine.re delete mode 100644 src/retris/engine.rei create mode 100644 src/retris/engine/block.re create mode 100644 src/retris/engine/block.rei create mode 100644 src/retris/engine/board.re create mode 100644 src/retris/engine/board.rei create mode 100644 src/retris/engine/coordinate.re create mode 100644 src/retris/engine/coordinate.rei create mode 100644 src/retris/engine/engine.re create mode 100644 src/retris/engine/engine.rei create mode 100644 src/retris/engine/matrix.re create mode 100644 src/retris/engine/matrix.rei create mode 100644 src/retris/engine/tetromino.re create mode 100644 src/retris/engine/tetromino.rei delete mode 100644 src/retris/retrisRoot.re diff --git a/bsconfig.json b/bsconfig.json index 85ccecc..f3b55f4 100644 --- a/bsconfig.json +++ b/bsconfig.json @@ -10,7 +10,7 @@ "sources": [ { "dir": "src", - "subdirs": ["retris"] + "subdirs": ["retris", "retris/components", "retris/engine"] } ], "namespace": true, diff --git a/src/retris/RootComp.re b/src/retris/RootComp.re new file mode 100644 index 0000000..869c789 --- /dev/null +++ b/src/retris/RootComp.re @@ -0,0 +1 @@ +ReactDOMRe.renderToElementWithId "index"; diff --git a/src/retris/board.re b/src/retris/components/BoardComp.re similarity index 93% rename from src/retris/board.re rename to src/retris/components/BoardComp.re index bf2e6d7..f6d81e2 100644 --- a/src/retris/board.re +++ b/src/retris/components/BoardComp.re @@ -5,8 +5,8 @@ let basic_colors = [|"#C0C0C0", "#808080", "#000000", "#FF0000", "#800000", "#FF let make ::board _children => { ...component, render: fun _self => { - let m = Engine.Board.matrix board; - let m' = Engine.Matrix.transpose m; + let m = Board.matrix board; + let m' = Matrix.transpose m; diff --git a/src/retris/game.re b/src/retris/components/GameComp.re similarity index 75% rename from src/retris/game.re rename to src/retris/components/GameComp.re index 622ceff..2a98dd2 100644 --- a/src/retris/game.re +++ b/src/retris/components/GameComp.re @@ -1,5 +1,5 @@ type state = { - game: option Engine.Game.t, + game: option Engine.t, timer_id: ref (option Js.Global.intervalId) }; type action = | Tick | ClickLeft | ClickRight | ClickRotate | Restart | PressKey string; @@ -14,23 +14,23 @@ let make _children => { }, reducer: fun action state => { let new_game = switch (state.game) { - | None => Some (Engine.Game.create (10, 15)) + | None => Some (Engine.create (10, 15)) | Some g => { switch action { - | Tick => Some (Engine.Game.tick g) - | ClickLeft => Some (Engine.Game.move g Left) - | ClickRight => Some (Engine.Game.move g Right) - | ClickRotate => Some (Engine.Game.rotate g) + | Tick => Some (Engine.tick g) + | ClickLeft => Some (Engine.move g Left) + | ClickRight => Some (Engine.move g Right) + | ClickRotate => Some (Engine.rotate g) | PressKey key => { switch (key) { - | "h" | "H" => Some (Engine.Game.move g Left) - | "l" | "L" => Some (Engine.Game.move g Right) - | "j" | "J" => Some (Engine.Game.tick g) - | "k" | "K" => Some (Engine.Game.rotate g) + | "h" | "H" => Some (Engine.move g Left) + | "l" | "L" => Some (Engine.move g Right) + | "j" | "J" => Some (Engine.tick g) + | "k" | "K" => Some (Engine.rotate g) | _ => None }; } - | Restart => Some (Engine.Game.create (10, 15)) + | Restart => Some (Engine.create (10, 15)) } } }; @@ -53,7 +53,7 @@ let make _children => {
(ReasonReact.stringToElement "Gameover")
| Playing => {
- + diff --git a/src/retris/engine.re b/src/retris/engine.re deleted file mode 100644 index 90710bb..0000000 --- a/src/retris/engine.re +++ /dev/null @@ -1,522 +0,0 @@ -type width = int; -type height = int; -type dimension = (width, height); - -type x = int; -type y = int; -type position = (x, y); - -type direction = | Down | Right | Left; - -module Matrix = { - type t = array (array int); - let multiply x y => { - let x_width = Array.length x; - let x_height = Array.length x.(0); - let y_width = Array.length y; - let z = Array.make_matrix y_width x_height 0; - for i in 0 to (x_height-1) { - for j in 0 to (y_width-1) { - for k in 0 to (x_width-1) { - z.(j).(i) = z.(j).(i) + x.(k).(i) * y.(j).(k); - } - } - }; - z - }; - - let negate x => x |> Array.map(fun c => c |> Array.map (fun v => -v)); - let add x y => x |> Array.mapi (fun ix c => c |> Array.mapi (fun iy v => y.(ix).(iy) + v)); - let substract x y => add x (negate y); - - let print m => { - let width = Array.length m; let height = Array.length m.(0); - Js.log ((string_of_int height) ^ " x " ^ (string_of_int width)); - - for i in 0 to (height - 1) { - let accum = ref ""; - for j in 0 to (width - 1) { - accum := !accum ^ "\t" ^ (string_of_int m.(j).(i)); - }; - Js.log !accum; - }; - }; - - let rotation_matrix = { - let m = Array.make_matrix 2 2 0; - m.(0).(0) = 0; m.(1).(0) = 1; m.(0).(1) = -1; m.(1).(1) = 0; - m - }; - - let transpose m => { - let (width, height) = ((Array.length m), Array.length m.(0)); - let m' = Array.make_matrix height width 0; - m |> Array.iteri (fun i col => - col |> Array.iteri (fun j _ => m'.(j).(i) = m.(i).(j)) - ); - m'; - } -}; - -module Block = { - type t = position; - - let to_matrix t => { - let m = Array.make_matrix 1 2 0; - let (x, y) = t; m.(0) = [|x, y|]; - m; - }; - - let from_matrix m => { - let x = m.(0).(0); let y = m.(0).(1); (x, y) - }; - - let rotate t origin => { - let m_t = to_matrix t; let m_origin = to_matrix origin; - let m' = Matrix.add m_origin (Matrix.multiply Matrix.rotation_matrix (Matrix.substract m_t m_origin)); - from_matrix m' - }; - - let scale t ::factor => { - t |> List.map(fun | (x, y) => - (int_of_float (factor *. float(x)), int_of_float (factor *. float(y))) - ); - }; - - let print (x, y) => Js.log ("(" ^ (string_of_int x) ^ ", " ^ (string_of_int y) ^ ")"); - - let equal (x1, y1) (x2, y2) => (x1 == x2) && (y1 == y2); -}; - -module Tetromino = { - type tetromino_type = - | Fixed - | Moveable; - type shape = - | I | O | L | T | S | J | Z; - - type t = { - id: int, - blocks: list Block.t, - klazz: tetromino_type, - shape: shape, - }; - - let size t => - t.blocks - |> List.fold_left - (fun accum p => { - let (x, y) = p; - (max (max x accum) y) - }) 0 - |> (fun s => s + 1); - - let print t => { - let s = size t; - let m = Array.make_matrix s s 0; - t.blocks |> List.iter (fun p => { - let (x, y) = p; - m.(x).(y) = 1; - }); - Matrix.print m; - }; - - let id = ref (0); - - let create (shape: shape) => { - id := !id + 1; - switch shape { - | I => { - /* 0000 */ - /* 0000 */ - /* 0000 */ - /* 1111 */ - { id: !id, blocks: [(0, 3), (1, 3), (2, 3), (3, 3)], klazz: Moveable, shape }; - } - | O => { - /* 11 */ - /* 11 */ - { id: !id, blocks: [(0, 0), (0, 1), (1, 0), (1, 1)], klazz: Moveable, shape }; - } - | L => { - /* 100 */ - /* 100 */ - /* 110 */ - { id: !id, blocks: [(0, 0), (0, 1), (0, 2), (1, 2)], klazz: Moveable, shape }; - } - | T => { - /* 000 */ - /* 010 */ - /* 111 */ - { id: !id, blocks: [(0, 2), (1, 2), (2, 2), (1, 1)], klazz: Moveable, shape }; - } - | S => { - /* 000 */ - /* 011 */ - /* 110 */ - { id: !id, blocks: [(0, 2), (1, 2), (1, 1), (2, 1)], klazz: Moveable, shape }; - } - | J => { - /* 010 */ - /* 010 */ - /* 110 */ - { id: !id, blocks: [(1, 0), (1, 1), (0, 2), (1, 2)], klazz: Moveable, shape }; - } - | Z => { - /* 000 */ - /* 110 */ - /* 011 */ - { id: !id, blocks: [(2, 2), (1, 2), (1, 1), (0, 1)], klazz: Moveable, shape }; - } - }; - }; - - let rotate t => { - let scale_factor = switch (t.shape) { - | I | O => 2.0 - | J | L | T | S | Z => 1.0 - }; - let origin = switch (t.shape) { - | I => (3, 3) - | O => (1, 1) - | J | L | T | S | Z => (1, 1) - }; - { - ...t, - blocks: t.blocks - |> Block.scale factor::scale_factor - |> fun blocks => blocks |> List.map (fun b => Block.rotate b origin) - |> Block.scale factor::(1.0/.scale_factor) - }; - }; - - let freeze t => { ...t, klazz: Fixed}; - let delete_block t block => { - ...t, - blocks: t.blocks |> List.filter (fun b => not (Block.equal b block)) - }; - let move_down_blocks_above (t:t) (by) => { - /* Js.log ("move_down_blocks_above " ^ (string_of_int by)); */ - /* Block.print block; */ - /* print t; */ - /* let (bx, by) = block; */ - let res = { - ...t, - blocks: t.blocks - |> List.map (fun (x, y) => (y < by) ? (x, y + 1) : (x, y)) - }; - /* print res; */ - res; - } -}; - -module Board = { - type tetromino_on_board = { - tetromino: Tetromino.t, - top_left_position: position - }; - type t = { - tetrominos_on_board: list tetromino_on_board, - dimension: dimension - }; - type movement = | Moved t | Collide | Still | NoActiveTetromino | Full; - type collision = | Intersection (list position) | HitBottom | HitLeftRight | NoCollision; - type id_and_block = (int, Block.t); - - let create dimension => { tetrominos_on_board: [], dimension }; - - let in_moveable_space t ((x, y):position) => { - let (width, height) = t.dimension; - (x >= 0 && x < width && y < height); - }; - - let in_board t ((x, y):position) => (y >= 0 && (in_moveable_space t (x, y))); - - let get_id_and_blocks t: list id_and_block => { - t.tetrominos_on_board - |> List.map (fun tob => { - let tetromino = tob.tetromino; - let (dis_x, dis_y) = tob.top_left_position; - tetromino.blocks - |> List.map (fun block => { - let (x, y) = block; - (tetromino.id, (x + dis_x, y + dis_y)); - }) - }) - |> List.concat - }; - - let matrix t => { - let (width, height) = t.dimension; - let m = Array.make_matrix width height 0; - t |> get_id_and_blocks |> List.iter (fun idb => { - let (id, (x, y)) = idb; - if (in_board t (x, y)) { - m.(x).(y) = id; - } - }); - m - }; - - let print t => { - Matrix.print (matrix t); - }; - - let does_collide t tetrominos_on_board new_tetromino_on_board => { - let extract_blocks idb => { - let (_, (x, y)) = idb; - (x, y) - }; - - let blocks1 = get_id_and_blocks { - ...t, - tetrominos_on_board: [new_tetromino_on_board] - } |> List.map(extract_blocks); - - let blocks2 = get_id_and_blocks { - ...t, - tetrominos_on_board: tetrominos_on_board - } |> List.map(extract_blocks); - - let intersections = blocks1 |> List.filter(fun b => - blocks2 |> List.exists(fun c => Block.equal b c)); - - let out_of_moveable_space = blocks1 - |> List.filter (fun b => b |> (in_moveable_space t) |> (not)); - - if ((List.length intersections) > 0) { - /* Js.log "Intersect"; */ - Intersection intersections; - } else if ((List.length out_of_moveable_space) > 0) { - let (_, height) = t.dimension; - let does_hit_bottom = out_of_moveable_space - |> List.exists (fun (_, y) => y == height); - if (does_hit_bottom) { - /* Js.log "HitBottom"; */ - HitBottom; - } else { - /* Js.log "HitLeftRight"; */ - HitLeftRight; - } - } else { - /* Js.log "NoCollision"; */ - NoCollision; - } - }; - - let put t tetromino top_left_position => { - /* let (displacement_x, displacement_y) = diplacement; */ - let new_tetromino_on_board = { tetromino, top_left_position }; - let remainders = t.tetrominos_on_board |> List.filter (fun t => { - switch (t.tetromino.klazz) { - | Moveable => false - | Fixed => true - }; - }); - switch (does_collide t remainders new_tetromino_on_board) { - | Intersection _ => { - /* Js.log "Collide!"; */ - let (_, tl_y) = top_left_position; - if (tl_y < 0) { - Full; - } else { - Collide; - } - } - | HitLeftRight => Still - | HitBottom => Collide - | NoCollision => { - let b = { - ...t, - tetrominos_on_board: [new_tetromino_on_board, ...remainders] - }; - Moved b; - } - } - }; - - exception DuplicateActiveteTetromino; - - let active_tetromino t => { - t.tetrominos_on_board - |> List.filter (fun tetromino_on_board => { - switch (tetromino_on_board.tetromino.klazz) { - | Moveable => true - | Fixed => false - } - }) - |> (fun ls => switch (ls) { - | [] => None - | [h] => Some h - | [_, ..._] => raise DuplicateActiveteTetromino - }); - }; - - let stop_active_tetromino (t:t):t => { - ...t, - tetrominos_on_board: t.tetrominos_on_board |> List.map (fun tetromino_on_board => - { - ...tetromino_on_board, - tetromino: (Tetromino.freeze tetromino_on_board.tetromino) - } - ) - }; - - let move_tetromino t direction => { - let active = active_tetromino t; - switch (active) { - | Some a => { - let (x, y) = a.top_left_position; - let (dx, dy) = switch (direction) { - | Down => (0, 1) - | Right => (1, 0) - | Left => (-1, 0) - }; - let (x', y') = (x + dx, y + dy); - put t a.tetromino (x', y'); - } - | None => NoActiveTetromino; - } - }; - - let rotate_tetromino t => { - let active = active_tetromino t; - switch (active) { - | Some a => { - let (x, y) = a.top_left_position; - put t (Tetromino.rotate a.tetromino) (x, y); - } - | None => NoActiveTetromino; - } - }; - - let remove_lines (t:t) => { - let (width, _) = t.dimension; - let full_rows = t - |> matrix - |> Matrix.transpose - |> Array.mapi (fun i row => { - let block_in_row = row |> Array.fold_left (fun accum x => (x != 0) ? accum + 1 : accum) 0; - (i, block_in_row); - }) - |> Array.to_list - |> List.filter (fun (_, count) => count == width) - |> List.map (fun (i, _) => i); - full_rows |> List.iter (fun l => Js.log ("Full Row: " ^ (string_of_int l))); - - if ((List.length full_rows) > 0) { - Js.log "remove_lines"; - print t; - }; - let tobs' = t.tetrominos_on_board |> List.map(fun tob => { - let tetromino = tob.tetromino; - let (_, dis_y) = tob.top_left_position; - - let tetromino' = full_rows |> (List.fold_left (fun (tet: Tetromino.t) full_row => { - /* Js.log ("Row:" ^ (string_of_int full_row)); */ - /* Js.log ("dis_y" ^ (string_of_int dis_y)); */ - /* Tetromino.print tet; */ - let to_be_deleted_blocks = tet.blocks - |> List.filter (fun (_, y) => y + dis_y == full_row); - - let tetromino' = to_be_deleted_blocks - |> (List.fold_left (fun accum b => { - b |> (Tetromino.delete_block accum); - }) tet); - /* Js.log "Deleted"; */ - /* Tetromino.print tetromino'; */ - - let tetromino' = Tetromino.move_down_blocks_above tetromino' (full_row - dis_y); - - /* Js.log "Moved down"; */ - /* Tetromino.print tetromino'; */ - tetromino'; - }) tetromino); - { - ...tob, - tetromino: tetromino' - } - }); - let res = { - ...t, - tetrominos_on_board: tobs' - }; - if ((List.length full_rows) > 0) { - print res; - }; - res; - } -}; - -module Game = { - type state = | Playing | Gameover; - type t = { - board: Board.t, - state: state - }; - - let create dimension => { - Random.self_init (); - { - board: (Board.create dimension), - state: Playing - }; - }; - - let rec update t (m:Board.movement) direction => { - switch (m) { - | Moved board => { ...t, board } - | Still | NoActiveTetromino => t - | Collide => { - switch (direction) { - | Down => { - let new_t = { - ...t, - board: (Board.remove_lines (Board.stop_active_tetromino t.board)) - }; - tick new_t - } - | Left | Right => t - } - } - | Full => { - /* Js.log "Gameover!"; */ - {...t, state: Gameover} - } - } - } and tick (t: t) :t => { - let m = switch (Board.active_tetromino t.board) { - | None => { - let all_shapes = [|Tetromino.I, O, L, T, S|]; - let random_shape = all_shapes.(Random.int (Array.length all_shapes)); - let (width, _) = t.board.dimension; - let random_tetromino = Tetromino.create random_shape; - let rt_size = Tetromino.size random_tetromino; - Board.put t.board random_tetromino (width / 2, (-rt_size)); - } - | Some _ => { - Board.move_tetromino t.board Down; - } - }; - update t m Down; - }; - - let rotate t => update t (Board.rotate_tetromino t.board) Down; - let move t direction => update t (Board.move_tetromino t.board direction) direction; - let matrix t => Board.matrix t.board -}; - -let dimension = (10, 10); - -let game = ref None; - -let start () => { - game := Some (Game.create dimension); -}; - -let tick () => { - switch (!game) { - | None => start () - | Some g => game := Some (Game.tick g) - }; -}; diff --git a/src/retris/engine.rei b/src/retris/engine.rei deleted file mode 100644 index 2d51cb6..0000000 --- a/src/retris/engine.rei +++ /dev/null @@ -1,62 +0,0 @@ -type width = int; -type height = int; -type dimension = (width, height); - -type x = int; -type y = int; -type position = (x, y); -type direction = | Down | Right | Left; - -module Matrix: { - type t = array (array int); - let transpose: t => t; -}; - -module Block: { - type t = position; -}; - -module Tetromino: { - type tetromino_type = - | Fixed - | Moveable; - type shape = - | I | O | L | T | S | J | Z; - - type t = { - id: int, - blocks: list Block.t, - klazz: tetromino_type, - shape: shape, - }; -}; - -module Board: { - type tetromino_on_board = { - tetromino: Tetromino.t, - top_left_position: position - }; - type t = { - tetrominos_on_board: list tetromino_on_board, - dimension: dimension - }; - type id_and_block = (int, Block.t); - - let get_id_and_blocks: t => (list id_and_block); - let print: t => unit; - let matrix: t => array (array int); -}; - -module Game: { - type state = | Playing | Gameover; - type t = { - board: Board.t, - state: state - }; - let create: dimension => t; - let tick: t => t; - let rotate: t => t; - let move: t => direction => t; -}; -let start: unit => unit; -let tick: unit => unit; diff --git a/src/retris/engine/block.re b/src/retris/engine/block.re new file mode 100644 index 0000000..1ca721e --- /dev/null +++ b/src/retris/engine/block.re @@ -0,0 +1,26 @@ +type t = Coordinate.position; + +let to_matrix t => { + let m = Array.make_matrix 1 2 0; + let (x, y) = t; m.(0) = [|x, y|]; + m; +}; + +let from_matrix m => { + let x = m.(0).(0); let y = m.(0).(1); (x, y) +}; + +let rotate t origin => { + let m_t = to_matrix t; let m_origin = to_matrix origin; + let m' = Matrix.add m_origin (Matrix.multiply Matrix.rotation_matrix (Matrix.substract m_t m_origin)); + from_matrix m' +}; + +let scale t ::factor => { + let (x, y) = t; + (int_of_float (factor *. float(x)), int_of_float (factor *. float(y))) +}; + +let print (x, y) => Js.log ("(" ^ (string_of_int x) ^ ", " ^ (string_of_int y) ^ ")"); + +let equal (x1, y1) (x2, y2) => (x1 == x2) && (y1 == y2); diff --git a/src/retris/engine/block.rei b/src/retris/engine/block.rei new file mode 100644 index 0000000..b58c596 --- /dev/null +++ b/src/retris/engine/block.rei @@ -0,0 +1,7 @@ +type t = Coordinate.position; +let to_matrix: t => Matrix.t; +let from_matrix: Matrix.t => t; +let rotate: t => t => t; +let scale: t => factor::float => t; +let print: t => unit; +let equal: t => t => bool; diff --git a/src/retris/engine/board.re b/src/retris/engine/board.re new file mode 100644 index 0000000..1b54cec --- /dev/null +++ b/src/retris/engine/board.re @@ -0,0 +1,238 @@ +type tetromino_on_board = { + tetromino: Tetromino.t, + top_left_position: Coordinate.position +}; +type t = { + tetrominos_on_board: list tetromino_on_board, + dimension: Coordinate.dimension +}; +type direction = | Down | Right | Left; +type movement = | Moved t | Collide | Still | NoActiveTetromino | Full; +type collision = | Intersection (list Coordinate.position) | HitBottom | HitLeftRight | NoCollision; +type id_and_block = (int, Block.t); + +let create dimension => { tetrominos_on_board: [], dimension }; + +let in_moveable_space t ((x, y):Coordinate.position) => { + let (width, height) = t.dimension; + (x >= 0 && x < width && y < height); +}; + +let in_board t ((x, y):Coordinate.position) => (y >= 0 && (in_moveable_space t (x, y))); + +let get_id_and_blocks t: list id_and_block => { + t.tetrominos_on_board + |> List.map (fun tob => { + let tetromino = tob.tetromino; + let (dis_x, dis_y) = tob.top_left_position; + tetromino.blocks + |> List.map (fun block => { + let (x, y) = block; + (tetromino.id, (x + dis_x, y + dis_y)); + }) + }) + |> List.concat +}; + +let matrix t => { + let (width, height) = t.dimension; + let m = Array.make_matrix width height 0; + t |> get_id_and_blocks |> List.iter (fun idb => { + let (id, (x, y)) = idb; + if (in_board t (x, y)) { + m.(x).(y) = id; + } + }); + m +}; + +let print t => { + Matrix.print (matrix t); +}; + +let does_collide t tetrominos_on_board new_tetromino_on_board => { + let extract_blocks idb => { + let (_, (x, y)) = idb; + (x, y) + }; + + let blocks1 = get_id_and_blocks { + ...t, + tetrominos_on_board: [new_tetromino_on_board] + } |> List.map(extract_blocks); + + let blocks2 = get_id_and_blocks { + ...t, + tetrominos_on_board: tetrominos_on_board + } |> List.map(extract_blocks); + + let intersections = blocks1 |> List.filter(fun b => + blocks2 |> List.exists(fun c => Block.equal b c)); + + let out_of_moveable_space = blocks1 + |> List.filter (fun b => b |> (in_moveable_space t) |> (not)); + + if ((List.length intersections) > 0) { + /* Js.log "Intersect"; */ + Intersection intersections; + } else if ((List.length out_of_moveable_space) > 0) { + let (_, height) = t.dimension; + let does_hit_bottom = out_of_moveable_space + |> List.exists (fun (_, y) => y == height); + if (does_hit_bottom) { + /* Js.log "HitBottom"; */ + HitBottom; + } else { + /* Js.log "HitLeftRight"; */ + HitLeftRight; + } + } else { + /* Js.log "NoCollision"; */ + NoCollision; + } +}; + +let put t tetromino top_left_position => { + /* let (displacement_x, displacement_y) = diplacement; */ + let new_tetromino_on_board = { tetromino, top_left_position }; + let remainders = t.tetrominos_on_board |> List.filter (fun t => { + switch (t.tetromino.klazz) { + | Moveable => false + | Fixed => true + }; + }); + switch (does_collide t remainders new_tetromino_on_board) { + | Intersection _ => { + /* Js.log "Collide!"; */ + let (_, tl_y) = top_left_position; + if (tl_y < 0) { + Full; + } else { + Collide; + } + } + | HitLeftRight => Still + | HitBottom => Collide + | NoCollision => { + let b = { + ...t, + tetrominos_on_board: [new_tetromino_on_board, ...remainders] + }; + Moved b; + } + } +}; + +exception DuplicateActiveteTetromino; + +let active_tetromino t => { + t.tetrominos_on_board + |> List.filter (fun tetromino_on_board => { + switch (tetromino_on_board.tetromino.klazz) { + | Moveable => true + | Fixed => false + } + }) + |> (fun ls => switch (ls) { + | [] => None + | [h] => Some h + | [_, ..._] => raise DuplicateActiveteTetromino + }); +}; + +let stop_active_tetromino (t:t):t => { + ...t, + tetrominos_on_board: t.tetrominos_on_board |> List.map (fun tetromino_on_board => + { + ...tetromino_on_board, + tetromino: (Tetromino.freeze tetromino_on_board.tetromino) + } + ) +}; + +let move_tetromino t direction => { + let active = active_tetromino t; + switch (active) { + | Some a => { + let (x, y) = a.top_left_position; + let (dx, dy) = switch (direction) { + | Down => (0, 1) + | Right => (1, 0) + | Left => (-1, 0) + }; + let (x', y') = (x + dx, y + dy); + put t a.tetromino (x', y'); + } + | None => NoActiveTetromino; + } +}; + +let rotate_tetromino t => { + let active = active_tetromino t; + switch (active) { + | Some a => { + let (x, y) = a.top_left_position; + put t (Tetromino.rotate a.tetromino) (x, y); + } + | None => NoActiveTetromino; + } +}; + +let remove_lines (t:t) => { + let (width, _) = t.dimension; + let full_rows = t + |> matrix + |> Matrix.transpose + |> Array.mapi (fun i row => { + let block_in_row = row |> Array.fold_left (fun accum x => (x != 0) ? accum + 1 : accum) 0; + (i, block_in_row); + }) + |> Array.to_list + |> List.filter (fun (_, count) => count == width) + |> List.map (fun (i, _) => i); + full_rows |> List.iter (fun l => Js.log ("Full Row: " ^ (string_of_int l))); + + if ((List.length full_rows) > 0) { + Js.log "remove_lines"; + print t; + }; + let tobs' = t.tetrominos_on_board |> List.map(fun tob => { + let tetromino = tob.tetromino; + let (_, dis_y) = tob.top_left_position; + + let tetromino' = full_rows |> (List.fold_left (fun (tet: Tetromino.t) full_row => { + /* Js.log ("Row:" ^ (string_of_int full_row)); */ + /* Js.log ("dis_y" ^ (string_of_int dis_y)); */ + /* Tetromino.print tet; */ + let to_be_deleted_blocks = tet.blocks + |> List.filter (fun (_, y) => y + dis_y == full_row); + + let tetromino' = to_be_deleted_blocks + |> (List.fold_left (fun accum b => { + b |> (Tetromino.delete_block accum); + }) tet); + /* Js.log "Deleted"; */ + /* Tetromino.print tetromino'; */ + + let tetromino' = Tetromino.move_down_blocks_above tetromino' (full_row - dis_y); + + /* Js.log "Moved down"; */ + /* Tetromino.print tetromino'; */ + tetromino'; + }) tetromino); + { + ...tob, + tetromino: tetromino' + } + }); + let res = { + ...t, + tetrominos_on_board: tobs' + }; + if ((List.length full_rows) > 0) { + print res; + }; + res; +} + + diff --git a/src/retris/engine/board.rei b/src/retris/engine/board.rei new file mode 100644 index 0000000..a3129af --- /dev/null +++ b/src/retris/engine/board.rei @@ -0,0 +1,26 @@ +type tetromino_on_board = { + tetromino: Tetromino.t, + top_left_position: Coordinate.position +}; +type t = { + tetrominos_on_board: list tetromino_on_board, + dimension: Coordinate.dimension +}; +type direction = | Down | Right | Left; +type movement = | Moved t | Collide | Still | NoActiveTetromino | Full; +type collision = | Intersection (list Coordinate.position) | HitBottom | HitLeftRight | NoCollision; +type id_and_block = (int, Block.t); + +let create: Coordinate.dimension => t; +let in_moveable_space: t => Coordinate.position => bool; +let in_board: t => Coordinate.position => bool; +let get_id_and_blocks: t => list id_and_block; +let matrix: t => Matrix.t; +let print: t => unit; +let does_collide: t => list tetromino_on_board => tetromino_on_board => collision; +let put: t => Tetromino.t => Coordinate.position => movement; +let active_tetromino: t => (option tetromino_on_board); +let stop_active_tetromino: t => t; +let move_tetromino: t => direction => movement; +let rotate_tetromino: t => movement; +let remove_lines: t => t; diff --git a/src/retris/engine/coordinate.re b/src/retris/engine/coordinate.re new file mode 100644 index 0000000..16f9708 --- /dev/null +++ b/src/retris/engine/coordinate.re @@ -0,0 +1,8 @@ +type width = int; +type height = int; +type dimension = (width, height); + +type x = int; +type y = int; +type position = (x, y); + diff --git a/src/retris/engine/coordinate.rei b/src/retris/engine/coordinate.rei new file mode 100644 index 0000000..16f9708 --- /dev/null +++ b/src/retris/engine/coordinate.rei @@ -0,0 +1,8 @@ +type width = int; +type height = int; +type dimension = (width, height); + +type x = int; +type y = int; +type position = (x, y); + diff --git a/src/retris/engine/engine.re b/src/retris/engine/engine.re new file mode 100644 index 0000000..29c3ed4 --- /dev/null +++ b/src/retris/engine/engine.re @@ -0,0 +1,70 @@ +type state = | Playing | Gameover; +type t = { + board: Board.t, + state: state +}; + +let create dimension => { + Random.self_init (); + { + board: (Board.create dimension), + state: Playing + }; +}; + +let rec update t (m:Board.movement) direction => { + switch (m) { + | Moved board => { ...t, board } + | Still | NoActiveTetromino => t + | Collide => { + switch (direction) { + | Board.Down => { + let new_t = { + ...t, + board: (Board.remove_lines (Board.stop_active_tetromino t.board)) + }; + tick new_t + } + | Left | Right => t + } + } + | Full => { + /* Js.log "Gameover!"; */ + {...t, state: Gameover} + } + } +} and tick (t: t) :t => { + let m = switch (Board.active_tetromino t.board) { + | None => { + let all_shapes = [|Tetromino.I, O, L, T, S|]; + let random_shape = all_shapes.(Random.int (Array.length all_shapes)); + let (width, _) = t.board.dimension; + let random_tetromino = Tetromino.create random_shape; + let rt_size = Tetromino.size random_tetromino; + Board.put t.board random_tetromino (width / 2, (-rt_size)); + } + | Some _ => { + Board.move_tetromino t.board Down; + } + }; + update t m Down; +}; + +let rotate t => update t (Board.rotate_tetromino t.board) Down; +let move t direction => update t (Board.move_tetromino t.board direction) direction; +let matrix t => Board.matrix t.board + +/* let dimension = (10, 10); */ +/* */ +/* let game = ref None; */ +/* */ +/* let start () => { */ +/* game := Some (Game.create dimension); */ +/* }; */ +/* */ +/* let tick () => { */ +/* switch (!game) { */ +/* | None => start () */ +/* | Some g => game := Some (Game.tick g) */ +/* }; */ +/* }; */ diff --git a/src/retris/engine/engine.rei b/src/retris/engine/engine.rei new file mode 100644 index 0000000..a5e4d40 --- /dev/null +++ b/src/retris/engine/engine.rei @@ -0,0 +1,11 @@ +type state = | Playing | Gameover; +type t = { + board: Board.t, + state: state +}; +let create: Coordinate.dimension => t; +let tick: t => t; +let rotate: t => t; +let move: t => Board.direction => t; +/* let start: unit => unit; */ +/* let tick: unit => unit; */ diff --git a/src/retris/engine/matrix.re b/src/retris/engine/matrix.re new file mode 100644 index 0000000..7903273 --- /dev/null +++ b/src/retris/engine/matrix.re @@ -0,0 +1,47 @@ +type t = array (array int); +let multiply x y => { + let x_width = Array.length x; + let x_height = Array.length x.(0); + let y_width = Array.length y; + let z = Array.make_matrix y_width x_height 0; + for i in 0 to (x_height-1) { + for j in 0 to (y_width-1) { + for k in 0 to (x_width-1) { + z.(j).(i) = z.(j).(i) + x.(k).(i) * y.(j).(k); + } + } + }; + z +}; + +let negate x => x |> Array.map(fun c => c |> Array.map (fun v => -v)); +let add x y => x |> Array.mapi (fun ix c => c |> Array.mapi (fun iy v => y.(ix).(iy) + v)); +let substract x y => add x (negate y); + +let print m => { + let width = Array.length m; let height = Array.length m.(0); + Js.log ((string_of_int height) ^ " x " ^ (string_of_int width)); + + for i in 0 to (height - 1) { + let accum = ref ""; + for j in 0 to (width - 1) { + accum := !accum ^ "\t" ^ (string_of_int m.(j).(i)); + }; + Js.log !accum; + }; +}; + +let rotation_matrix = { + let m = Array.make_matrix 2 2 0; + m.(0).(0) = 0; m.(1).(0) = 1; m.(0).(1) = -1; m.(1).(1) = 0; + m +}; + +let transpose m => { + let (width, height) = ((Array.length m), Array.length m.(0)); + let m' = Array.make_matrix height width 0; + m |> Array.iteri (fun i col => + col |> Array.iteri (fun j _ => m'.(j).(i) = m.(i).(j)) + ); + m'; +} diff --git a/src/retris/engine/matrix.rei b/src/retris/engine/matrix.rei new file mode 100644 index 0000000..6192ccf --- /dev/null +++ b/src/retris/engine/matrix.rei @@ -0,0 +1,8 @@ +type t = array (array int); +let multiply: t => t => t; +let negate: t => t; +let add: t => t => t; +let substract: t => t => t; +let print: t => unit; +let rotation_matrix: t; +let transpose: t => t; diff --git a/src/retris/engine/tetromino.re b/src/retris/engine/tetromino.re new file mode 100644 index 0000000..00c3947 --- /dev/null +++ b/src/retris/engine/tetromino.re @@ -0,0 +1,119 @@ +type tetromino_type = + | Fixed + | Moveable; +type shape = + | I | O | L | T | S | J | Z; + +type t = { + id: int, + blocks: list Block.t, + klazz: tetromino_type, + shape: shape, +}; + +let size t => + t.blocks + |> List.fold_left + (fun accum p => { + let (x, y) = p; + (max (max x accum) y) + }) 0 + |> (fun s => s + 1); + +let print t => { + let s = size t; + let m = Array.make_matrix s s 0; + t.blocks |> List.iter (fun p => { + let (x, y) = p; + m.(x).(y) = 1; + }); + Matrix.print m; +}; + +let id = ref (0); + +let create (shape: shape) => { + id := !id + 1; + switch shape { + | I => { + /* 0000 */ + /* 0000 */ + /* 0000 */ + /* 1111 */ + { id: !id, blocks: [(0, 3), (1, 3), (2, 3), (3, 3)], klazz: Moveable, shape }; + } + | O => { + /* 11 */ + /* 11 */ + { id: !id, blocks: [(0, 0), (0, 1), (1, 0), (1, 1)], klazz: Moveable, shape }; + } + | L => { + /* 100 */ + /* 100 */ + /* 110 */ + { id: !id, blocks: [(0, 0), (0, 1), (0, 2), (1, 2)], klazz: Moveable, shape }; + } + | T => { + /* 000 */ + /* 010 */ + /* 111 */ + { id: !id, blocks: [(0, 2), (1, 2), (2, 2), (1, 1)], klazz: Moveable, shape }; + } + | S => { + /* 000 */ + /* 011 */ + /* 110 */ + { id: !id, blocks: [(0, 2), (1, 2), (1, 1), (2, 1)], klazz: Moveable, shape }; + } + | J => { + /* 010 */ + /* 010 */ + /* 110 */ + { id: !id, blocks: [(1, 0), (1, 1), (0, 2), (1, 2)], klazz: Moveable, shape }; + } + | Z => { + /* 000 */ + /* 110 */ + /* 011 */ + { id: !id, blocks: [(2, 2), (1, 2), (1, 1), (0, 1)], klazz: Moveable, shape }; + } + }; +}; + +let rotate t => { + let scale_factor = switch (t.shape) { + | I | O => 2.0 + | J | L | T | S | Z => 1.0 + }; + let origin = switch (t.shape) { + | I => (3, 3) + | O => (1, 1) + | J | L | T | S | Z => (1, 1) + }; + { + ...t, + blocks: t.blocks + |> List.map (Block.scale factor::scale_factor) + |> fun blocks => blocks |> List.map (fun b => Block.rotate b origin) + |> List.map (Block.scale factor::(1.0/.scale_factor)) + }; +}; + +let freeze t => { ...t, klazz: Fixed}; +let delete_block t block => { + ...t, + blocks: t.blocks |> List.filter (fun b => not (Block.equal b block)) +}; +let move_down_blocks_above (t:t) (by) => { + /* Js.log ("move_down_blocks_above " ^ (string_of_int by)); */ + /* Block.print block; */ + /* print t; */ + /* let (bx, by) = block; */ + let res = { + ...t, + blocks: t.blocks + |> List.map (fun (x, y) => (y < by) ? (x, y + 1) : (x, y)) + }; + /* print res; */ + res; +} diff --git a/src/retris/engine/tetromino.rei b/src/retris/engine/tetromino.rei new file mode 100644 index 0000000..d7de409 --- /dev/null +++ b/src/retris/engine/tetromino.rei @@ -0,0 +1,21 @@ +type tetromino_type = + | Fixed + | Moveable; +type shape = + | I | O | L | T | S | J | Z; + +type t = { + id: int, + blocks: list Block.t, + klazz: tetromino_type, + shape: shape, +}; + +let size: t => int; +let print: t => unit; +let id: ref int; +let create: shape => t; +let rotate: t => t; +let freeze: t => t; +let delete_block: t => Block.t => t; +let move_down_blocks_above: t => int => t; diff --git a/src/retris/retrisRoot.re b/src/retris/retrisRoot.re deleted file mode 100644 index fd941f0..0000000 --- a/src/retris/retrisRoot.re +++ /dev/null @@ -1 +0,0 @@ -ReactDOMRe.renderToElementWithId "index"; diff --git a/webpack.config.js b/webpack.config.js index b6a7588..a744a4a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,7 +2,7 @@ const path = require('path'); module.exports = { entry: { - retris: './lib/js/src/retris/retrisRoot.js', + retris: './lib/js/src/retris/RootComp.js', }, output: { path: path.join(__dirname, "bundledOutputs"),