// Core game logic
/**
* @typedef {{level: Level, display: LevelDisplay}} LevelData
*/
// Note: Two below classes are meant only to provide used interface
/**
* Display interface used by game
*/
class LevelDisplay {
move_player_to(position) { }
move_box_to(box_index, position) { }
set_target_has_box(target_index, has_box) { }
set_box_is_satisfied(box_index, is_satisfied) { }
}
/**
* Logic interface used by game
*/
class GameLogic {
constructor() { this.satisfaction_counter = undefined }
restart() { }
complete(level) { }
save_level(level) { }
}
/**
* Actions handled by game
*/
const Actions = {
MOVE_LEFT: 0,
MOVE_RIGHT: 1,
MOVE_UP: 2,
MOVE_DOWN: 3,
RESTART: 4,
}
/** Converts action to coordinate offset
*
* @param {number} action
* @return {number[]}
*/
function action_to_offset(action) {
switch (action) {
case Actions.MOVE_LEFT: return [-1, 0]
case Actions.MOVE_RIGHT: return [1, 0]
case Actions.MOVE_UP: return [0, -1]
case Actions.MOVE_DOWN: return [0, 1]
}
}
/**
* Applies provided action using provided interface
* @param {number} action Action to apply, any of Actions member
* @param {LevelData} level_data
* @param {GameLogic} logic
* @return {boolean} true if action was applied false otherwise
*/
function apply_game_action(action, level_data, logic) {
let level = level_data.level
let display = level_data.display
if (level.completed) return false
if (action == Actions.RESTART) { logic.restart(); return false }
let offset = action_to_offset(action)
if (!can_move_or_push(level, offset)) return false
level.moves += 1
level.player = move(level.player, offset)
display.move_player_to(level.player)
let pushed_box_index = get_box_index(level, level.player)
if (pushed_box_index != undefined)
push_box(pushed_box_index, offset, level_data, logic)
if (is_level_completed(level)) {
level.completed = true
}
logic.save_level(level)
if (level.completed)
logic.complete(level)
return true
}
/** Pushes box by offset
*
* @param {number} box_index
* @param {number[]} offset
* @param {LevelData} level_data
* @param {GameLogic} logic
*/
function push_box(box_index, offset, level_data, logic) {
let box = level_data.level.boxes[box_index]
move_box_from(box_index, box, level_data, logic)
box = level_data.level.boxes[box_index] = move(box, offset)
level_data.display.move_box_to(box_index, box)
move_box_to(box_index, box, level_data, logic)
}
/** Removes box from a tile
*
* @param {number} box_index
* @param {number[]} offset
* @param {LevelData} level_data
* @param {GameLogic} logic
*/
function move_box_from(box_index, position, level_data, logic) {
let target_index = get_target_index(level_data.level, position)
if (target_index != undefined) {
level_data.display.set_target_has_box(target_index, false)
level_data.display.set_box_is_satisfied(box_index, false)
logic.satisfaction_counter.add(-1)
}
}
/** Puts box on a tile
*
* @param {number} box_index
* @param {number[]} offset
* @param {LevelData} level_data
* @param {GameLogic} logic
*/
function move_box_to(box_index, position, level_data, logic) {
let target_index = get_target_index(level_data.level, position)
if (target_index != undefined) {
level_data.display.set_target_has_box(target_index, true)
level_data.display.set_box_is_satisfied(box_index, true)
logic.satisfaction_counter.add(1)
}
}