Untitled
unknown
plain_text
11 days ago
9.4 kB
2
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 // All 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), theme: Theme, ) } pub type Msg { ClickColumn(Int) Restart SetTheme(Theme) } pub type Theme { Christmas NewYear Space } // Set up the board pub fn construct_board() -> Board { // creates a grid of 7 columns and 6 rows filled with Empty slots list.repeat(Empty, times: 6) |> fn(column) { list.repeat(column, times: 7) } } // columns are counted from 0 to 6 from left to right with the leftmost being number 0 // rows are counted from 0 to 5 from top to bottom with the topmost being number 0 // // board = [[r0, r1, r2, r3, r4, r5], // [r0, r1, r2, r3, r4, r5], // [r0, r1, r2, r3, r4, r5], // [r0, r1, r2, r3, r4, r5], // [r0, r1, r2, r3, r4, r5], // [r0, r1, r2, r3, r4, r5], // [r0, r1, r2, r3, r4, r5], // ] // // board structure: // column 0: [r0, r1, r2, r3, r4, r5] // column 1: [r0, r1, r2, r3, r4, r5] // column 2: [r0, r1, r2, r3, r4, r5] // column 3: [r0, r1, r2, r3, r4, r5] // column 4: [r0, r1, r2, r3, r4, r5] // column 5: [r0, r1, r2, r3, r4, r5] // column 6: [r0, r1, r2, r3, r4, r5] // // visual // col_0 | col_1 | col_2 | col_3 | col_4 | col_5 | col_6 // ______________________________________________________ // row_0 |c0_r0 | c1_r0 | c2_r0 | c3_r0 | c4_r0 | c5_r0 | c6_r0 | // row_0 |c0_r1 | c1_r1 | c2_r1 | c3_r1 | c4_r1 | c5_r1 | c6_r1 | // row_0 |c0_r2 | c1_r2 | c2_r2 | c3_r2 | c4_r2 | c5_r2 | c6_r2 | // row_0 |c0_r3 | c1_r3 | c2_r3 | c3_r3 | c4_r3 | c5_r3 | c6_r3 | // row_0 |c0_r4 | c1_r4 | c2_r4 | c3_r4 | c4_r4 | c5_r4 | c6_r4 | // row_0 |c0_r5 | c1_r5 | c2_r5 | c3_r5 | c4_r5 | c5_r5 | c6_r5 | fn init(_flags) -> Model { let board = construct_board() Model(board: board, current_player: PlayerRed, winner: None, theme: Christmas) } // 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) } SetTheme(theme) -> Model( board: model.board, current_player: model.current_player, winner: model.winner, theme: theme, ) } } 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, theme: model.theme, ) } fn toggle_player(player: Player) -> Player { case player { PlayerRed -> PlayerYellow PlayerYellow -> PlayerRed } } 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 theme_class = case model.theme { Christmas -> "theme-christmas" NewYear -> "theme-newyear" Space -> "theme-space" } 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 " <> theme_class)], [ html.h1([attribute.class("banner")], [html.text("Vier op een rij!")]), html.h2([attribute.class("status-text")], [html.text(status_text)]), html.div([attribute.class("board")], column_elements), html.button([event.on_click(Restart)], [html.text("Restart")]), render_theme_picker(), ]) } // Themes fn render_theme_picker() -> Element(Msg) { html.div([attribute.class("theme-picker")], [ html.button([event.on_click(SetTheme(Christmas))], [html.text("Christmas")]), html.button([event.on_click(SetTheme(NewYear))], [html.text("New Year")]), html.button([event.on_click(SetTheme(Space))], [html.text("Space")]), ]) } // Main Function pub fn main() { let app = lustre.simple(init, update, view) let assert Ok(_) = lustre.start(app, "#app", Nil) Nil } // Placing discs // Placing discs needs two functions: place_disc and update_board 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 { // Looks for the lowest Empty spot and puts the disc there [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 } } // The functions checks all the win conditions below. 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) } // We need functions for all win conditions. 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