diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/chunk.rhm | 58 | ||||
| -rw-r--r-- | src/entity.rhm | 147 | ||||
| -rw-r--r-- | src/item.rhm | 21 | ||||
| -rw-r--r-- | src/test.rhm | 67 | ||||
| -rw-r--r-- | src/world.rhm | 26 | 
5 files changed, 319 insertions, 0 deletions
diff --git a/src/chunk.rhm b/src/chunk.rhm new file mode 100644 index 0000000..3ded42d --- /dev/null +++ b/src/chunk.rhm @@ -0,0 +1,58 @@ +#lang rhombus/static + +export: +    Direction +    TileType +    Tile +    Chunk +    flip_block + +enum Direction: +    north +    north_east +    east +    south_east +    south +    south_west +    west +    north_west + +enum TileType: +    empty +    wall_north + +fun flip_block(block :: maybe(Direction)) :: maybe(Direction): +    match block +    | Direction.north: Direction.south +    | Direction.north_east: Direction.south_west +    | Direction.east: Direction.west +    | Direction.south_east: Direction.north_west +    | Direction.south: Direction.north +    | Direction.south_west: Direction.north_east +    | Direction.west: Direction.east +    | Direction.north_west: Direction.south_east +    | ~else: #false + +class Tile(type :: TileType): +    method get_block() :: maybe(Direction): +        match type +        | TileType.empty: #false +        | TileType.wall_north: Direction.north + +class Chunk(width :: Int, +            height :: Int, +            offset_x :: Int, +            offset_y :: Int, +            tiles :: Array.now_of(Tile), +            neighbours :: MutableMap.now_of(Direction, Chunk)): +    constructor(width :: Int, height :: Int, offset_x :: Int, offset_y :: Int): +        super(width, height, offset_x, offset_y, Array.make(width * height, Tile(TileType.empty)), MutableMap()) + +    method add_neighbour(chunk :: Chunk, direction :: Direction): +        neighbours[direction] := chunk + +    method get_tile(x :: Int, y :: Int) :: Tile: +        tiles[y * width + x] + +    method set_tile(x :: Int, y :: Int, tile :: Tile): +        tiles[y * width + x] := tile diff --git a/src/entity.rhm b/src/entity.rhm new file mode 100644 index 0000000..3fcd812 --- /dev/null +++ b/src/entity.rhm @@ -0,0 +1,147 @@ +#lang rhombus/static +import: +    rhombus/random +    "chunk.rhm" open +    "item.rhm" open + +export: +    Entity +    EntityGatherable +    EntityPlayer +    ActionResponse +    Slot + +let rand = random.Random() + +class Entity(id:: Int, mutable x :: Int, mutable y :: Int, mutable current_chunk :: Chunk): +    nonfinal +    // Validate that the provided coordinates are within the max distance range of +    // entities current coordinates. +    method validate_dist(x :: Int, y :: Int, max_dist :: Int): +        let diff_x = math.abs(this.x - x) +        let diff_y = math.abs(this.y - y) +        // Diagonal case is moving in the same direction (same x & y) and that amount +        // is less than or equal to max distance +        let diagonal = diff_x == diff_y && diff_x <= max_dist +        let x_axis = diff_x <= max_dist && diff_y == 0 +        let y_axis = diff_y <= max_dist && diff_x == 0 +        let no_movement = diff_x == 0 && diff_y == 0 +        !no_movement && (diagonal || x_axis || y_axis) + +    // Validate that the x,y coordinates map to a chunk that is +    // near the current coordinates of the entity +    method validate_chunk(x :: Int, y :: Int) :: maybe(Chunk): +        let current_chunk = this.current_chunk +        let local_x = x - current_chunk.offset_x * current_chunk.width +        let local_y = y - current_chunk.offset_y * current_chunk.height +        let less_x = local_x < 0 +        let less_y = local_y < 0 +        let more_x = local_x >= current_chunk.width +        let more_y = local_y >= current_chunk.height +        let inside_x = !(less_x || more_x) +        let inside_y = !(less_y || more_y) +        let next_chunk :: maybe(Chunk) = cond +        | inside_x && less_y: current_chunk.neighbours.get(Direction.north, #false) +        | more_x && less_y:   current_chunk.neighbours.get(Direction.north_east, #false) +        | more_x && inside_y: current_chunk.neighbours.get(Direction.east, #false) +        | more_x && more_y:   current_chunk.neighbours.get(Direction.south_east, #false) +        | inside_x && more_y: current_chunk.neighbours.get(Direction.south, #false) +        | less_x && more_y:   current_chunk.neighbours.get(Direction.south_west, #false) +        | less_x && inside_y: current_chunk.neighbours.get(Direction.west, #false) +        | less_x && less_y:   current_chunk.neighbours.get(Direction.north_west, #false) +        | ~else:              current_chunk + +        next_chunk + +    // Validate that the tile at the coordinates would not block a movement of the entity +    // to those coordinates from its current coordinates +    method validate_move(x :: Int, y :: Int, max_dist :: Int, target_chunk :: Chunk): +        let current_tile = this.current_chunk.get_tile(this.x, this.y) +        let target_tile = target_chunk.get_tile(x, y) +        let diff_x = x - this.x +        let diff_y = y - this.y +        let direction = cond +        | diff_x == 0 && diff_y < 0:  Direction.north +        | diff_x > 0  && diff_y < 0:  Direction.north_east +        | diff_x > 0  && diff_y == 0: Direction.east +        | diff_x > 0  && diff_y > 0:  Direction.south_east +        | diff_x == 0 && diff_y > 0:  Direction.south +        | diff_x < 0 && diff_y > 0:   Direction.south_west +        | diff_x < 0  && diff_y == 0: Direction.west +        | diff_x < 0  && diff_y < 0:  Direction.north_west +        let current_block = current_tile.get_block() +        let target_block = flip_block(target_tile.get_block()) +        !(direction == current_block || direction == target_block) + +    method +    | move(x :: Int, y :: Int): move(x, y, 1) +    | move(x :: Int, y :: Int, max_dist :: Int) :: Boolean: +        let target_chunk = validate_chunk(x, y) + +        cond +        | validate_dist(x, y, max_dist) && target_chunk != #false && validate_move(x, y, max_dist, target_chunk!!): +            this.current_chunk := target_chunk!! +            this.x := x +            this.y := y +            #true +        | ~else: +            #false + +class EntityGatherable(quantity :: Int, odds :: Real, cap :: ItemCap): +    extends Entity + +    constructor(id :: Int, x :: Int, y :: Int, current_chunk :: Chunk, cap :: ItemCap): +        super(id, x, y, current_chunk)(10, 1 / 10, cap) + +enum ActionResponse: +    ok +    unsuccessful +    invalid +    cooldown + +enum Slot: +    right_hand + +class EntityPlayer(mutable timer :: Int): +    extends Entity + +    field max_timer = 4 +    field inventory :: Array.now_of(maybe(Stack)) = Array.make(20, #false) +    field equip :: MutableMap.now_of(Slot, Item) = MutableMap() + +    constructor(id :: Int, x :: Int, y :: Int, current_chunk :: Chunk): +        super(id, x, y, current_chunk)(0) + +    method tick(): +        // TODO this "ticks" the entity. Things like healing and other +        // stuff should happen within here. In the future we probably +        // want to set a state where the player is gathering and in tick +        // we calculate the odds that they did gather properly +        if timer == 0: +        | timer := max_timer +        | timer := timer - 1 + +    method gather(target_entity :: Entity) :: ActionResponse: +        let x = target_entity.x +        let y = target_entity.y +        let target_chunk = validate_chunk(x, y) +        let right_type = target_entity is_a EntityGatherable + +        fun validate_gather_resource(entity :: EntityGatherable) :: ActionResponse: +            let right_tool = item_get_cap(equip.get(Slot.right_hand, #false)) == entity.cap +            // TODO odds should change based on skills of player +            // we should also validate player is carrying the right tool +            // we should also have the timer count down every tick +            // not just inside the gathering call +            let roll = rand.random() +            cond +            | !right_tool: ActionResponse.invalid +            | rand.random() < entity.odds: ActionResponse.ok +            | ~else: ActionResponse.unsuccessful + +        cond +        | timer != 0: ActionResponse.cooldown +        | right_type && validate_dist(x, y, 1) && target_chunk != #false: +            validate_gather_resource(target_entity) +        | ~else: +            ActionResponse.invalid diff --git a/src/item.rhm b/src/item.rhm new file mode 100644 index 0000000..6716dbc --- /dev/null +++ b/src/item.rhm @@ -0,0 +1,21 @@ +#lang rhombus/static + +export: +    Item +    Stack +    ItemCap +    item_get_cap + +enum Item: +    bronze_axe + +enum ItemCap: +    none +    chop + +fun item_get_cap(item :: maybe(Item)) :: ItemCap: +    match item: +    | Item.bronze_axe: ItemCap.chop +    | ~else: ItemCap.none + +class Stack(item :: Item, quantity :: Int) diff --git a/src/test.rhm b/src/test.rhm new file mode 100644 index 0000000..5648c53 --- /dev/null +++ b/src/test.rhm @@ -0,0 +1,67 @@ +#lang rhombus/static + +import: +    "world.rhm" + +fun test(test_result :: Boolean, test_str :: String): +    if test_result +    | println("Passed " +& test_str) +    | println("Failed " +& test_str) + +fun +| reset_entity(entity :: world.Entity): reset_entity(entity, 0, 0) +| reset_entity(entity :: world.Entity, x :: Int, y :: Int): +    entity.x := x +    entity.y := y + +let wrld = world.World() +let chunk = world.Chunk(64, 64, 0, 0) +wrld.chunks.add(chunk) +let entity = world.EntityPlayer(0, 0, 0, chunk) +wrld.entities.add(entity) + + +reset_entity(entity) +test(entity.move(1, 0), "move horizontal") + +reset_entity(entity, 1, 1) +test(entity.move(0, 1), "move vertical") + +reset_entity(entity) +test(entity.move(1, 1), "move diagonal") + +reset_entity(entity) +test(!entity.move(2, 0), "move diagonal") + +reset_entity(entity) +test(!entity.move(0, 0), "starting pos") + +let new_chunk = world.Chunk(64, 64, 1, 0) +chunk.add_neighbour(new_chunk, world.Direction.east) +reset_entity(entity, 63, 0) +test(entity.move(64, 0), "cross chunk") +test(entity.current_chunk == new_chunk, "changed chunk") + +entity.current_chunk := chunk +reset_entity(entity, 0, 1) +entity.current_chunk.set_tile(0, 1, world.Tile(#'wall_north)) +test(!entity.move(0, 0), "blocked by wall") +test(entity.move(1,1), "move by wall") + +reset_entity(entity, 0, 0) +entity.equip[world.Slot.right_hand] := world.Item.bronze_axe +let other_ent = world.EntityGatherable(1, 1, 1, chunk, world.ItemCap.chop) +let gather_result = entity.gather(other_ent) +test(gather_result == world.ActionResponse.ok || gather_result == world.ActionResponse.unsuccessful, "gathering") + +reset_entity(other_ent, 2, 2) +entity.timer := 0 +test(entity.gather(other_ent) == world.ActionResponse.invalid, "gather too far away") + +reset_entity(other_ent, 64, 0) +reset_entity(entity, 63, 0) +entity.timer := 0 +let gather_result = entity.gather(other_ent) +test(gather_result == world.ActionResponse.ok || gather_result == world.ActionResponse.unsuccessful, "gather accross chunk") +entity.tick() +test(entity.gather(other_ent) == world.ActionResponse.cooldown, "gather cooldown") diff --git a/src/world.rhm b/src/world.rhm new file mode 100644 index 0000000..9357422 --- /dev/null +++ b/src/world.rhm @@ -0,0 +1,26 @@ +#lang rhombus/static +import: +    "chunk.rhm" open +    "entity.rhm" open +    "item.rhm" open + +export: +    Chunk +    ActionResponse +    Direction +    World +    Tile +    Entity +    EntityPlayer +    EntityGatherable +    Direction +    ItemCap +    Item +    Slot + +class World(chunks :: MutableList.now_of(Chunk), +            entities :: MutableList.now_of(Entity)): + +    constructor(): +        super(MutableList(), MutableList()) +  |