Untitled

 avatar
unknown
plain_text
12 days ago
6.9 kB
4
Indexable
import gleam/list
import gleam/option.{None, Some}
import lustre
import lustre/attribute
import lustre/element.{type Element}
import lustre/element/html
import lustre/event

// === GAME TYPES ===

pub type Disc {
  Red
  Yellow
  Empty
}

pub type Column =
  List(Disc)

pub type Board =
  List(Column)

pub type Player {
  PlayerRed
  PlayerYellow
}

pub type Model {
  Model(board: Board, current_player: Player, winner: option.Option(Player))
}

pub type Msg {
  ClickColumn(Int)
  Restart
}

// === INIT ===

pub fn construct_board() -> Board {
  list.repeat(Empty, times: 6)
  |> fn(column) { list.repeat(column, times: 7) }
}

fn init(_flags) -> Model {
  let board = construct_board()
  Model(board: board, current_player: PlayerRed, winner: None)
}

// === UPDATE ===

fn update(model: Model, msg: Msg) -> Model {
  case msg {
    Restart -> init(Nil)

    ClickColumn(col_index) ->
      case model.winner {
        Some(_) -> model
        None -> handle_turn(model, col_index)
      }
  }
}

fn handle_turn(model: Model, col_index: Int) -> Model {
  let disc = case model.current_player {
    PlayerRed -> Red
    PlayerYellow -> Yellow
  }

  let updated_board = update_board(model.board, col_index, disc)
  let has_won = check_for_win(updated_board, disc)

  let next_player = case has_won {
    True -> model.current_player
    False -> toggle_player(model.current_player)
  }

  let winner = case has_won {
    True -> Some(model.current_player)
    False -> None
  }

  Model(board: updated_board, current_player: next_player, winner: winner)
}

fn toggle_player(player: Player) -> Player {
  case player {
    PlayerRed -> PlayerYellow
    PlayerYellow -> PlayerRed
  }
}

// === VIEW HELPERS ===

fn render_disc(disc: Disc) -> Element(Msg) {
  let class = case disc {
    Red -> "disc red"
    Yellow -> "disc yellow"
    Empty -> "disc empty"
  }

  html.div([attribute.class(class)], [])
}

fn render_column(index: Int, column: Column) -> Element(Msg) {
  let disc_elements =
    list.reverse(column)
    |> list.map(render_disc)

  html.div(
    [attribute.class("column"), event.on_click(ClickColumn(index))],
    disc_elements,
  )
}

// === VIEW ===

fn view(model: Model) -> Element(Msg) {
  let column_elements =
    list.index_map(model.board, fn(column, index) {
      render_column(index, column)
    })

  let status_text = case model.winner {
    Some(PlayerRed) -> "Red wins!"
    Some(PlayerYellow) -> "Yellow wins!"
    None ->
      case model.current_player {
        PlayerRed -> "Red's turn"
        PlayerYellow -> "Yellow's turn"
      }
  }

  html.div([attribute.class("game-container")], [
    html.h1([attribute.class("banner")], [html.text("Vier op een rij!")]),
    // 👈 ADD THIS
    html.h2([], [html.text(status_text)]),
    html.div([attribute.class("board")], column_elements),
    html.button([event.on_click(Restart)], [html.text("Restart")]),
  ])
}

// === MAIN ===

pub fn main() {
  let app = lustre.simple(init, update, view)
  let assert Ok(_) = lustre.start(app, "#app", Nil)
  Nil
}

// === GAME LOGIC ===

pub fn place_disc(column: Column, disc: Disc) -> Column {
  case column {
    [_, _, _, _, _, Empty] -> [Empty, Empty, Empty, Empty, Empty, disc]
    [_, _, _, _, Empty, r5] -> [Empty, Empty, Empty, Empty, disc, r5]
    [_, _, _, Empty, r4, r5] -> [Empty, Empty, Empty, disc, r4, r5]
    [_, _, Empty, r3, r4, r5] -> [Empty, Empty, disc, r3, r4, r5]
    [_, Empty, r2, r3, r4, r5] -> [Empty, disc, r2, r3, r4, r5]
    [Empty, r1, r2, r3, r4, r5] -> [disc, r1, r2, r3, r4, r5]
    _ -> column
  }
}

pub fn update_board(board: Board, column_number: Int, disc: Disc) -> Board {
  case board {
    [c0, ..tail] if column_number == 0 -> [place_disc(c0, disc), ..tail]
    [c0, c1, ..tail] if column_number == 1 -> [c0, place_disc(c1, disc), ..tail]
    [c0, c1, c2, ..tail] if column_number == 2 -> [
      c0,
      c1,
      place_disc(c2, disc),
      ..tail
    ]
    [c0, c1, c2, c3, ..tail] if column_number == 3 -> [
      c0,
      c1,
      c2,
      place_disc(c3, disc),
      ..tail
    ]
    [c0, c1, c2, c3, c4, ..tail] if column_number == 4 -> [
      c0,
      c1,
      c2,
      c3,
      place_disc(c4, disc),
      ..tail
    ]
    [c0, c1, c2, c3, c4, c5, ..tail] if column_number == 5 -> [
      c0,
      c1,
      c2,
      c3,
      c4,
      place_disc(c5, disc),
      ..tail
    ]
    [c0, c1, c2, c3, c4, c5, c6] if column_number == 6 -> [
      c0,
      c1,
      c2,
      c3,
      c4,
      c5,
      place_disc(c6, disc),
    ]
    _ -> board
  }
}

pub fn get_disc(board: Board, column_number: Int, row_number: Int) -> Disc {
  case list.drop(board, column_number) {
    [column, ..] ->
      case list.drop(column, row_number) {
        [disc, ..] -> disc
        [] -> Empty
      }
    [] -> Empty
  }
}

pub fn check_for_win(board: Board, disc: Disc) -> Bool {
  check_for_win_vertical(board, disc)
  || check_for_win_horizontal(board, disc)
  || check_for_win_diagonal_up(board, disc)
  || check_for_win_diagonal_down(board, disc)
}

pub fn check_for_win_vertical(board: Board, disc: Disc) -> Bool {
  let col_range = list.range(0, 6)
  let row_range = list.range(0, 2)
  list.any(col_range, fn(col) {
    list.any(row_range, fn(row) {
      get_disc(board, col, row) == disc
      && get_disc(board, col, row + 1) == disc
      && get_disc(board, col, row + 2) == disc
      && get_disc(board, col, row + 3) == disc
    })
  })
}

pub fn check_for_win_horizontal(board: Board, disc: Disc) -> Bool {
  let col_range = list.range(0, 3)
  let row_range = list.range(0, 5)
  list.any(col_range, fn(col) {
    list.any(row_range, fn(row) {
      get_disc(board, col, row) == disc
      && get_disc(board, col + 1, row) == disc
      && get_disc(board, col + 2, row) == disc
      && get_disc(board, col + 3, row) == disc
    })
  })
}

pub fn check_for_win_diagonal_down(board: Board, disc: Disc) -> Bool {
  let col_range = list.range(0, 3)
  let row_range = list.range(0, 2)
  list.any(col_range, fn(col) {
    list.any(row_range, fn(row) {
      get_disc(board, col, row) == disc
      && get_disc(board, col + 1, row + 1) == disc
      && get_disc(board, col + 2, row + 2) == disc
      && get_disc(board, col + 3, row + 3) == disc
    })
  })
}

pub fn check_for_win_diagonal_up(board: Board, disc: Disc) -> Bool {
  let col_range = list.range(0, 3)
  let row_range = list.range(3, 5)
  list.any(col_range, fn(col) {
    list.any(row_range, fn(row) {
      get_disc(board, col, row) == disc
      && get_disc(board, col + 1, row - 1) == disc
      && get_disc(board, col + 2, row - 2) == disc
      && get_disc(board, col + 3, row - 3) == disc
    })
  })
}
Editor is loading...
Leave a Comment