///|
struct GameManager {
size : Int
storage_manager : LocalStorageManager
actuator : HtmlActuator
start_tiles : Int
criteria : Int
mut grid : Grid
mut score : Int
mut over : Bool
mut won : Bool
mut keep_playing : Bool
}
///|
pub fn GameManager::new(size : Int, criteria : Int) -> GameManager {
let self = {
size,
storage_manager: LocalStorageManager::new(),
actuator: HtmlActuator::new(),
start_tiles: 2,
criteria,
grid: Grid::new(0),
score: 0,
over: false,
won: false,
keep_playing: false,
}
KeyboardInputManager::new()
..on("move", payload => self.move_(Any::to(payload)))
..on("restart", _ => self.restart())
..on("undoMove", _ => self.undo_move())
..on("restartWithConfirmation", _ => self.restart_with_confirmation())
..on("undoWithConfirmation", _ => self.undo_with_confirmation())
.on("keepPlaying", _ => self.keep_playing())
self.setup()
self
}
///|
fn GameManager::restart(self : GameManager) -> Unit {
self.storage_manager.clear_game_state()
self.actuator.continue_game()
self.setup()
}
///|
fn GameManager::init_from_game_state(
self : GameManager,
state : GameState,
) -> Unit {
self.grid = state.grid.copy()
self.over = state.over
self.won = state.won
self.keep_playing = state.keep_playing
self.score = state.score
}
///|
fn GameManager::generate_actuator_metadata(
self : GameManager,
) -> ActuatorMetadata {
{
score: self.score,
over: self.over,
won: self.won,
best_score: self.storage_manager.get_best_score(),
terminated: self.is_game_terminated(),
keep_playing: self.keep_playing,
}
}
///|
fn GameManager::undo_move(self : GameManager) -> Unit {
self.actuator.continue_game()
if self.storage_manager.pop_game_state() is Some(previous_state) {
self.init_from_game_state(previous_state)
self.actuator.actuate(self.grid, self.generate_actuator_metadata())
}
}
///|
fn GameManager::restart_with_confirmation(self : GameManager) -> Unit {
self.actuator.prompt_restart()
}
///|
fn GameManager::undo_with_confirmation(self : GameManager) -> Unit {
self.actuator.prompt_undo()
}
///|
fn GameManager::keep_playing(self : GameManager) -> Unit {
self.keep_playing = true
self.actuator.continue_game()
self.actuate()
}
///|
fn GameManager::is_game_terminated(self : GameManager) -> Bool {
self.over || (self.won && !self.keep_playing)
}
///|
fn GameManager::setup(self : GameManager) -> Unit {
if self.storage_manager.get_game_state() is Some(previous_state) {
self.init_from_game_state(previous_state)
} else {
self.grid = Grid::new(self.size)
self.score = 0
self.over = false
self.won = false
self.keep_playing = false
self.add_start_tiles()
}
self.actuate()
}
///|
fn GameManager::add_start_tiles(self : GameManager) -> Unit {
for _ in 0.. Unit {
if self.grid.random_available_cell() is Some(cell) {
let value = if rand.int(limit=10) == 0 { 4 } else { 2 }
let tile = Tile::new(cell, value)
self.grid.insert_tile(tile)
}
}
///|
fn GameManager::actuate(self : GameManager) -> Unit {
if self.storage_manager.get_best_score() < self.score {
self.storage_manager.set_best_score(self.score)
}
if self.over {
self.storage_manager.clear_game_state()
} else {
self.storage_manager.push_game_state(self.serialize())
}
self.actuator.actuate(self.grid, self.generate_actuator_metadata())
}
///|
fn GameManager::serialize(self : GameManager) -> GameState {
{
grid: self.grid,
score: self.score,
over: self.over,
won: self.won,
keep_playing: self.keep_playing,
}
}
///|
fn GameManager::prepare_tiles(self : GameManager) -> Unit {
for tile in self.grid.each_cell() {
if tile is Some(tile) {
tile.merged_from = None
tile.save_position()
}
}
}
///|
fn GameManager::move_tile(
self : GameManager,
tile : Tile,
cell : Position,
) -> Unit {
self.grid.remove_tile(tile.pos)
tile.update_position(cell)
self.grid.insert_tile(tile)
}
///|
#warnings("-unused_constructor")
priv enum Direction {
Up = 0
Right = 1
Down = 2
Left = 3
}
///|
fn Direction::get_vector(self : Direction) -> (Int, Int) {
match self {
Up => (0, -1)
Right => (1, 0)
Down => (0, 1)
Left => (-1, 0)
}
}
///|
fn GameManager::build_traversals(
self : GameManager,
vector : (Int, Int),
) -> (Array[Int], Array[Int]) {
let x = (0).until(self.size).collect()
let y = (0).until(self.size).collect()
if vector is (1, _) {
x.rev_in_place()
}
if vector is (_, 1) {
y.rev_in_place()
}
(x, y)
}
///|
fn GameManager::move_(self : GameManager, direction : Direction) -> Unit {
if self.is_game_terminated() {
return
}
let vector = direction.get_vector()
let traversals = self.build_traversals(vector)
let mut moved = false
self.prepare_tiles()
for x in traversals.0 {
for y in traversals.1 {
let cell = { x, y, }
if self.grid.cell_content(cell) is Some(tile) {
let positions = self.find_farthest_position(cell, vector)
if self.grid.cell_content(positions.next) is Some(next) &&
next.value == tile.value &&
next.merged_from is None {
let merged = Tile::new(positions.next, tile.value * 2, merged_from=[
tile, next,
])
self.grid.insert_tile(merged)
self.grid.remove_tile(tile.pos)
tile.update_position(positions.next)
self.score += merged.value
if merged.value == self.criteria {
self.won = true
}
} else {
self.move_tile(tile, positions.farthest)
}
if cell != tile.pos {
moved = true
}
}
}
}
if moved {
self.add_random_tile()
if !self.moves_available() {
self.over = true
}
self.actuate()
}
}
///|
priv struct Positions {
farthest : Position
next : Position
}
///|
fn GameManager::find_farthest_position(
self : GameManager,
cell : Position,
vector : (Int, Int),
) -> Positions {
for previous = cell, cell = { x: cell.x + vector.0, y: cell.y + vector.1, }
self.grid.within_bounds(cell) && self.grid.cell_available(cell)
previous = cell, cell = { x: cell.x + vector.0, y: cell.y + vector.1, } {
} nobreak {
{ farthest: previous, next: cell, }
}
}
///|
fn GameManager::moves_available(self : GameManager) -> Bool {
self.grid.cells_available() || self.tile_matches_available()
}
///|
fn GameManager::tile_matches_available(self : GameManager) -> Bool {
for x in 0..