Untitled
unknown
plain_text
7 months ago
6.9 kB
8
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