--[[
# moonwar
The goal is to capture the most tiles by visiting them after a set amount of time.
## Arena
The arena is a 30*20 grid of tiles.
## Energy
Every core has a energy value from zero to one. At one, it can operate at
maximum core tick rate. At zero, it dies. Energy regenerates if the core
does no action in a tick. If a core splits, the energy is split between
the cores.
## Commands
-- Uses four (4) ticks.
-- leaves the cell the core is on empty and spawns a new core at the specified tile. If the target is not empty, the movement fails, which still takes 4 ticks.
function core.move(direction: "right" | "left" | "up" | "down") -> bool
-- Uses four (4) ticks.
function core.split(direction: "right" | "left" | "up" | "down", func, energy=0.5)-> bool
-- Waits for two (2) ticks, and kills core in specified direction, if there is one.
-- The attack is performed at the end of a simulation step.
-- Returns true if the attack killed a core.
function core.attack(direction: "right" | "left" | "up" | "down") -> bool
-- Adds the given data to the cores interal data array.
-- Takes one (1) tick.
function core.send(direction, data)
-- Returns information about the cell in the specified direction. The information is retrieved at the beginning of a simulation step.
-- Takes one (1) tick.
function core.inspect("right" | "left" | "up" | "down" | "self") -> {owned: nil/true/false, data}
-- Uses four (4) world ticks. Returns the energy of the core.
function core.charge() -> float
-- Waits the given amount of world ticks.
function core.wait(ticks=1)
Example core:
```lua
function example(core)
core.data = "yes"
core.move "right"
core.attack "right"
return -- die
end
```
# Data
Each core has its own interal data which can be accessed and modified by itself and other cores.
# Simulation
Each world tick, each yielded core that has waited long enough is continued.
The cores are executed and the result is written to an intermediate state of the arena.
Conflicts in the arena (two cores on the same cell) are resolved by reverting
the movement. If that results in more conflicts, they are resolved recursively.
The intermediate state is then written to the world state.
]]
local Vector = function(x, y)
return setmetatable({
x = x,
y = y,
}, {
__add = function()
--TODO implement this
end
})
end
local dirFromString = function(dir)
if dir == "left" then
return Vector(-1, 0)
elseif dir == "right" then
return Vector(1, 0)
elseif dir == "up" then
return Vector(0, -1)
elseif dir == "down" then
return Vector(0, 1)
end
end
local Cell
local commands = {
move = function(self, pos, arena, direction)
arena.setCell(pos + dirFromString(direction), self)
arena.setCell(pos, Cell{owner=self.owner})
self.waitTime = 4
end,
split = function(self, pos, arena, direction, fun, energy)
-- TODO: don't round
arena.setCell(pos + dirFromString(direction), Cell{owner=self.owner, core=fun, energy=self.energy * energy})
self.energy = self.energy * (1 - energy)
self.waitTime = 4
end,
attack = function(self, pos, arena, direction)
-- TODO: more checks
arena.setCell(pos + dirFromString(direction), Cell{owner=self.owner})
self.waitTime = 4
end,
send = function(self, pos, arena, data, direction)
table.insert(arena.getCell(pos + dirFromString(direction)).data, data)
self.waitTime = 1
end,
inspect = function(self, pos, arena, direction)
-- TODO: better return value
self.waitTime = 1
return arena.getCell(pos + dirFromString(direction)).data
end,
charge = function(self)
self.energy = self.energy + 4
self.waitTime = 2
return self.energy
end,
wait = function(self, amount)
-- TODO: check amount
self.waitTime = amount or 1
end,
}
Cell = function(t)
t = t or {}
-- TODO: hardening
local coreState = {
data = {}
}
local core = nil
if t.core then
for command, fun in pairs(commands) do
coreState[command] = function(...)
return coroutine.yield(fun, {...})
end
end
core = function()
t.core(coreState)
end
end
local cell = {
energy = t.energy or 0,
owner = t.owner,
core = core,
data = core and coreState.data,
}
return cell
end
local Arena
Arena = function(width, height)
local arena = {
cells = {}
}
for x = 1, width do
arena.cells[x] = {}
for y = 1, height do
arena.cells[x][y] = Cell()
end
end
function arena.getCell(self, pos)
return self.cells[pos.x][pos.y]
end
function arena.setCell(self, pos, cell)
self.cells[pos.x][pos.y] = cell
end
function arena.simulate(self)
local intermediate = Arena(width, height)
for x = 1, width do
for y = 1, height do
intermediate.setCell(x, y, self.cells[x][y])
end
end
for x in ipairs(self.cells) do
for y in ipairs(self.cells) do
local cell = self.cells[x][y]
if cell.core then
cell.waitTime = cell.waitTime + 1
if cell.core.waitTime == 0 then
local _, command, args = coroutine.resume(cell.core, cell.returnValue)
-- Validate that the command is not a malicious.
local valid = false
for _, fun in pairs(commands) do
if fun == command then
valid = true
end
end
if valid then
cell.returnValue = command(cell, Vector(x, y), intermediate, table.unpack(args))
end
end
end
end
end
end
return arena
end
return arena