Basics
This is a moderately-sized example to show many of the features of Ki. Most features are there, notably missing are:
- asynchronous programming
- major memory management
- error handling
import math
const LAVA_SPOT_COUNT: 6
const PLAYER_COUNT: 2
const ENEMY_COUNT: 4
const MAX_AMMO: 10
flag Direction {
None
North
NorthEast
East
SouthEast
South
SouthWest
West
NorthWest
}
range AmmoCount [0..MAX_AMMO]
struct PointStruct {
var x: uint(0)
var y: uint(0)
}
type Point (PointStruct) {
staticfn random(min: &Point, max: &Point): Point {
return (Point(
math.rand_num_range(@type: uint, min.x, max.x)
math.rand_num_range(@type: uint, min.y, max.y)
))
}
fn :eq(other: &Point): bool {
return (self.x == other.x && self.y == other.y)
}
fn :str(): str {
return ("Point({{self.x}}, {{self.y}})")
}
fn randomize!(min: &Point, max: &Point) {
self.x = math.rand_num_range(@type: uint, min.x, max.x)
self.y = math.rand_num_range(@type: uint, min.y, max.y)
}
fn move!(d: Direction) {
switch (d) {
case Direction.North {
self.y--
}
case Direction.South {
self.y++
}
case Direction.East {
self.x++
}
case Direction.West {
self.x--
}
case Direction.NorthWest {
self.x--
self.y--
}
case Direction.NorthEast {
self.x++
self.y--
}
case Direction.SouthEast {
self.x++
self.y++
}
case Direction.SouthWest {
self.x--
self.y++
}
}
}
fn can_move(d: Direction): bool {
switch(d) {
case Direction.North {
if (self.y <= 0) {
return (false)
}
}
case Direction.West {
if (self.x <= 0) {
return (false)
}
}
case Direction.NorthWest {
if (self.x <= 0) {
return (false)
}
if (self.y <= 0) {
return (false)
}
}
case Direction.NorthEast {
if (self.y <= 0) {
return (false)
}
}
case Direction.SouthWest {
if (self.x <= 0) {
return (false)
}
}
}
}
get possible_moves: Direction {
var possible_directions: Direction.None
for (d in Direction) {
if (self.can_move(d)) {
possible_directions |= d
}
}
return (possible_directions)
}
}
struct OptionalPoint {
variant DoesNotExist {}
variant Exists {
var point: Point
}
}
component GameObject {
:requirements {
var position: Point
var game: Game
}
pred is_in_lava {
return (self.game.map.is_lava(&self.position))
}
get possible_moves: Direction {
var position_possible_directions: self.position.possible_moves
if (position_possible_directions == PossibleDirection.None) {
return (PossibleDirections.None)
}
var game_possible_directions: PossibleDirection.None
for (d in Direction) {
var p: self.point.move(d)
if (self.game.map.is_position_valid(&p) &&
!self.game.is_position_occupied(&p)) {
game_possible_directions |= d
}
}
return (position_possible_directions & game_possible_directions)
}
fn can_move(d: Direction): bool {
return (self.possible_moves & d != 0)
}
}
type Enemy {
:fields {
var position: Point
var game: Game
}
:components {
GameObject
}
}
type Player {
:fields {
var position: Point
var game: Game
var ammo: AmmoCount(MAX_AMMO)
}
:components {
GameObject
}
staticfn at_random_position(): Player {
return (Player(position: self.game.get_random_unoccupied_position()))
}
fn try_move!() {
var possible_moves: self.possible_moves
if (possible_moves != Direction.None) {
self.move(math.rand_flag_range(possible_moves))
}
}
fn shoot!() {
self.ammo--
}
}
array LavaSpotArray(@type: Point)
dynarray EnemyArray(@type: *Enemy)
dynarray PlayerArray(@type: *Player)
struct SizeStruct {
var width: uint(0)
var height: uint(0)
}
type Size(SizeStruct) {
fn :str(): str {
return ("{{self.width}} x {{self.height}}")
}
}
struct MapStruct {
var size: Size
var lava_spots: LavaSpotArray
}
type Map(MapStruct) {
fn is_valid_position(p: &Point): bool {
return (p.x < self.size.width && p.y < self.size.height)
}
fn is_lava(p: &Point): bool {
for (&lava_spot in self.map.lava_spots) {
if (p == lava_spot) {
return (true)
}
}
}
}
struct GameStruct {
var map: Map
var players: PlayerArray
var enemies: EnemyArray
}
type Game(GameStruct) {
fn add_player(p: *Player) {
self.players.append!(*p)
}
fn add_enemy(e: *Enemy) {
self.enemies.append!(*e)
}
fn is_position_occupied(p: &Point) (bool) {
for (&player in self.players) {
if (player.position == p) {
return (true)
}
}
for (&enemy in self.enemies) {
if (enemy.position == p) {
return (true)
}
}
return (false)
}
get random_unoccupied_position: OptionalPoint {
var point_indices: [0..(self.map.size.width * self.map.size.height))
var points: array([Point() for x in point_indices])
var unoccupied_point_count: point_indices.min
###
# We could do:
#
# for (x in [0..self.map.size.width)) {
# for (y in [0..self.map.size.height)) {
# points[x * y] = Point(x, y)
# }
# }
#
# But then the compiler can't be sure that`x * y` is a valid index into
# `points` and will error.
###
for (n in point_indices) {
# This is guaranteed to succeed because the length of `points` is
# defined using the range `point_indices`, and
# `unoccupied_point_count` is a member of that range. Therefore no
# `with` block is necessary.
var p: &points[unoccupied_point_count]!
p.x = n % self.map.size.width
p.y = n / self.map.size.height
if (!(self.is_position_occupied(&p) || self.map.is_lava(&p))) {
# This is safe because the default overflow policy is `limit`.
unoccupied_point_count++
}
}
if (unoccupied_point_count > point_indices.min) {
return (OptionalPoint->Exists(math.rand_num_range(
type: @point_indices,
point_indices.min,
unoccupied_point_count
)))
}
return (OptionalPoint->DoesNotExist)
}
get live_player_count: uint {
var live_player_count: 0
for (&player in self.players) {
if (player.health != 0) {
live_player_count++
}
}
return (live_player_count)
}
get live_enemy_count: uint {
var live_enemy_count: 0
for (&enemy in self.enemies) {
if (enemy.health != 0) {
live_enemy_count++
}
}
return (live_enemy_count)
}
pred players_are_alive {
return (self.live_player_count > 0)
}
pred enemies_are_alive {
return (self.live_enemy_count > 0)
}
fn tick() {
for (i, &player! in self.players) {
if (player.health == 0) {
continue
}
player.move()
if (player.in_lava()) {
player.health = 0
continue
}
player.shoot()
for (j, &enemy in self.enemies) {
if (enemy.health == 0) {
continue
}
if (enemy.position.x == player.position.x) {
echo("Player {{i + 1}} shot enemy {{j + 1}}!")
enemy.health = 0
continue
}
}
}
}
}
fn main() {
var map_size: Size(20, 1, 20)
var zero: Point()
var max: Point(
map_size.width - 1,
map_size.height - 1,
)
var game: Game(
map: Map(
size: map_size
lava_spots: PointArray(LAVA_SPOT_COUNT)
)
)
for (&point! in lava_spots) {
point.randomize(zero, max)
}
loop (PLAYER_COUNT) {
var unoccupied_point = game.random_unoccupied_position
if (unoccupied_point->Exists) {
game.add_player!(*Player(
game: game
position: unoccupied_point->Exists.point
))
}
}
loop (ENEMY_COUNT) {
var unoccupied_point = game.random_unoccupied_position
if (unoccupied_point->Exists) {
game.add_enemy!(*Enemy(
game: game,
position: unoccupied_point->Exists.point
))
}
}
loop {
if (game.live_player_count == 0) {
echo("All players died, enemies win :(")
break
}
if (game.live_enemy_count == 0) {
echo("All enemies died, players win :)")
break
}
}
}
# vi: set et sw=4 ts=4 tw=79: