initial commit

This commit is contained in:
Art 2022-12-27 23:56:54 +01:00
parent f4755be327
commit b8ecf7c744
Signed by: prod2
GPG Key ID: F3BB5A97A70A8DDE
12 changed files with 638 additions and 0 deletions

217
display.nim Normal file
View File

@ -0,0 +1,217 @@
import input_type
import map_type
import turtle
import turtle_type
import math
import sequtils
import strutils
import strformat
import terminal
import algorithm
const blueCutoff = 0.05
const cyanCutoff = 0.1
const greenCutoff = 0.205
const yellowCutoff = 0.335
const redCutoff = 0.505
const magentaCutoff = 1.0
proc setHotnessBackground(hotness: float, maxHotness: float) =
if hotness == 0:
stdout.setBackgroundColor(bgDefault)
else:
let hotnessFrac = hotness / maxHotness
if hotnessFrac < blueCutoff:
stdout.setBackgroundColor(bgBlue)
elif hotnessFrac < cyanCutoff:
stdout.setBackgroundColor(bgCyan)
elif hotnessFrac < greenCutoff:
stdout.setBackgroundColor(bgGreen)
elif hotnessFrac < yellowCutoff:
stdout.setBackgroundColor(bgYellow)
elif hotnessFrac < redCutoff:
stdout.setBackgroundColor(bgRed)
else:
stdout.setBackgroundColor(bgMagenta)
proc printHotmapLegend(maxHotness: float, x,y: int) =
template line(num: int) =
setCursorPos(x, y + num - 1)
template showColor(col: BackgroundColor) =
stdout.setBackgroundColor(col)
stdout.write(" ")
stdout.setBackgroundColor(bgBlack)
stdout.write(" ")
proc writeNumber(num: float) =
stdout.write(fmt"{num.int()}")
line(1)
showColor(bgDefault)
writeNumber(0.0)
line(2)
showColor(bgBlue)
writeNumber(blueCutoff * maxHotness)
line(3)
showColor(bgCyan)
writeNumber(cyanCutoff * maxHotness)
line(4)
showColor(bgGreen)
writeNumber(greenCutoff * maxHotness)
line(5)
showColor(bgYellow)
writeNumber(yellowCutoff * maxHotness)
line(6)
showColor(bgRed)
writeNumber(redCutoff * maxHotness)
line(7)
showColor(bgMagenta)
writeNumber(magentaCutoff * maxHotness)
proc displayGeneration*(generationNumber: int, timeSeconds: float, seed: int, input: Input, map: Map, turtles: var seq[Turtle]) =
# SCREEN INIT
stdout.setBackgroundColor(bgBlack)
stdout.setForegroundColor(fgWhite)
eraseScreen()
setCursorPos(0, 0)
# STATISTICS
let scoreCount = turtles.len()
var scoreSum = 0
var stepsSum = 0
for turtle in turtles:
scoreSum += turtle.score
stepsSum += turtle.steps
let scoreAvg = scoreSum.float() / scoreCount.float()
let stepsAvg = stepsSum.float() / scoreCount.float()
let variance = turtles.mapIt((it.score.float() - scoreAvg)^2).sum() / scoreCount.float()
let stddev = variance.sqrt()
# PRINT BASIC INFO
echo &"Generation number: {generationNumber}"
echo &"Computation time since last stop: {timeSeconds} s"
echo &"Seed: {seed}"
echo &"Population: {turtles.len()} turtles"
echo &"Mean score: {scoreAvg} (in {stepsAvg} steps)"
echo &"Standard deviation: {stddev}"
echo &"Top score: {turtles[0].score} (in {turtles[0].steps} steps)"
# PERCENTILS
turtles.sort(compareTurtles)
var percentils: seq[int] = @[]
for i in 1..9:
let index = (input.population div 10) * i
percentils.add(turtles[index].score)
let percentilsString = percentils.join(", ")
echo &"Score of every 10%th percentil: {percentilsString}"
# CHOOSE NOTABLE
var top10Paths: seq[Turtle] = @[]
var notables: seq[int] = @[]
var i = 0
while true:
if i > turtles.high():
break
if top10Paths.len() == 0 or top10Paths[top10Paths.high()].score != turtles[i].score:
top10Paths.add(turtles[i])
notables.add(i)
if top10Paths.len() >= 10:
break
inc i
let notableString = notables.join(", ")
echo &"Notable turtles: {notableString}"
# HOTNESS MAP
var hotness: seq[seq[int]] = @[]
var top10Hot: seq[seq[int]] = @[]
var topHot: seq[seq[int]] = @[]
for i in 0..input.size-1:
hotness.add(@[])
top10Hot.add(@[])
topHot.add(@[])
for j in 0..input.size-1:
hotness[i].add(0)
top10Hot[i].add(0)
topHot[i].add(0)
template populateHotness(turtles: seq[Turtle], hotSeq: var seq[seq[int]], maxHot: var int) =
for turtle in turtles:
let path = turtle.path
for i in 0..path.xs.high():
let x = path.xs[i]
let y = path.ys[i]
hotSeq[y][x].inc()
if hotSeq[y][x] > maxHot:
maxHot = hotSeq[y][x]
var maxHotness = 0
populateHotness(turtles, hotness, maxHotness)
var maxTop10Hotness = 0
populateHotness(top10Paths, top10Hot, maxTop10Hotness)
var maxTopHotness = 0
populateHotness(@[turtles[0]], topHot, maxTopHotness)
# DISPLAY HOTNESS MAP
echo "Hotmap of All/Notable/Best Turtles"
const yOffset = 10
let xOffset = input.size + 2
for y in 0..input.size-1:
for x in 0..input.size-1:
let hotness = hotness[y][x]
let top10hotness = top10Hot[y][x]
let topHotness = topHot[y][x]
let symbol = map[y][x].tileToString()
# hotmap
setHotnessBackground(hotness.float(), maxHotness.float())
setCursorPos(x, y+yOffset)
stdout.write(symbol)
# top10 map
setHotnessBackground(top10hotness.float(), maxTop10Hotness.float())
setCursorPos(x+xOffset, y+yOffset)
stdout.write(symbol)
# top map
setHotnessBackground(topHotness.float(), maxTopHotness.float())
setCursorPos(x+2*xOffset, y+yOffset)
stdout.write(symbol)
stdout.setBackgroundColor(bgBlack)
stdout.write("\n")
echo ""
# display legend
let legendYOffset = yOffset + 1 + input.size
printHotmapLegend(maxHotness.float(), 0, legendYOffset)
printHotmapLegend(maxTop10Hotness.float(), xOffset, legendYOffset)
printHotmapLegend(maxTopHotness.float(), 2*xOffset, legendYOffset)
echo ""
echo ""
# FINISH
echo "Press enter to continue."
discard stdin.readLine()
stdout.setBackgroundColor(bgDefault)
eraseScreen()
setCursorPos(1,1)
echo ""

8
input.json Normal file
View File

@ -0,0 +1,8 @@
{
"size": 19,
"treasure_count": 7,
"lifetime": 500,
"population": 500,
"instruction_set": "anybit",
"bits": 12
}

11
input_type.nim Normal file
View File

@ -0,0 +1,11 @@
type
Input* = object
size*: int
treasure_count*: int
lifetime*: int
population*: int
instruction_set*: string
bits*: int

View File

@ -0,0 +1,105 @@
import ../input_type
import ../turtle_type
import ../map_type
import bitops
import random
const opcodeLength = 3
# 3 if there are 8 different instructions
# anybit
proc genAnybitTurtle*(input: Input, bits: int): Turtle =
# generates a turtle of bits long memory cells
let maxAddress = (1 shl (bits - opcodeLength)) - 1
let maxValue = (1 shl bits) - 1
new(result)
for i in 0..maxAddress:
result.memory.add(rand(0..maxValue).uint32)
proc executeAnybit*(instructionIndex: uint32, turtle: Turtle, map: Map, input: Input, bits: int): int =
let maxAddress = ((1 shl (bits - opcodeLength)) - 1).uint32
let maxValue = ((1 shl bits) - 1).uint32
var newInstructionIndex: uint32
let
icIncrement = 0'u64
icDecrement = (0b001 shl (bits-opcodeLength)).uint32
icJump = (0b010 shl (bits-opcodeLength)).uint32
icMove = (0b011 shl (bits-opcodeLength)).uint32
icDoubleMove = (0b100 shl (bits-opcodeLength)).uint32
icCond = (0b101 shl (bits-opcodeLength)).uint32
icNand = (0b110 shl (bits-opcodeLength)).uint32
icXor = (0b111 shl (bits-opcodeLength)).uint32
let instruction = turtle.memory[instructionIndex]
let instructionCode = instruction.bitand(icXor)
let instructionArg = instruction.bitand(bitnot(icXor))
template addTo(target: var uint32, delta: uint32, subtract: bool = false) =
if subtract:
target = (target - delta).bitand(maxValue)
else:
target = (target + delta).bitand(maxValue)
template nextBlock: uint32 =
turtle.memory[(instructionIndex + 1'u32).bitand(maxAddress)]
template mapBounded(i: int): int =
max(min(i, input.size), 0)
if instructionCode == icIncrement:
turtle.memory[instructionArg].addTo(1)
newInstructionIndex = instructionIndex + 1'u32
elif instructionCode == icDecrement:
turtle.memory[instructionArg].addTo(1, subtract = true)
newInstructionIndex = instructionIndex + 1'u32
elif instructionCode == icJump:
newInstructionIndex = instructionArg
elif instructionCode == icMove:
let direction = instructionArg.bitand(3'u32)
if direction == 0'u32:
turtle.y += 1
elif direction == 1'u32:
turtle.y -= 1
elif direction == 2'u32:
turtle.x += 1
elif direction == 3'u32:
turtle.x -= 1
if turtle.x < 0 or turtle.y < 0 or turtle.x >= input.size or turtle.y >= input.size:
return -1
newInstructionIndex = instructionIndex + 1
elif instructionCode == icDoubleMove:
let amount = (instructionArg.bitand(28'u32) shr 2).int() + 1
let direction = instructionArg.bitand(3'u32)
if direction == 0'u32:
turtle.y += amount
elif direction == 1'u32:
turtle.y -= amount
elif direction == 2'u32:
turtle.x += amount
elif direction == 3'u32:
turtle.x -= amount
if turtle.x < 0 or turtle.y < 0 or turtle.x >= input.size or turtle.y >= input.size:
return -1
newInstructionIndex = instructionIndex + 1
elif instructionCode == icCond:
# jumps to address if the next block is even
if nextBlock().bitand(1'u32) == 0'u32:
newInstructionIndex = instructionArg
else:
newInstructionIndex = instructionIndex + 2
elif instructionCode == icNand:
# bitwise nand of the address with the next code block
turtle.memory[instructionArg] = bitnot(turtle.memory[instructionArg].bitand(nextBlock())).bitand(maxValue)
newInstructionIndex = instructionIndex + 2
elif instructionCode == icXor:
turtle.memory[instructionArg] = turtle.memory[instructionArg].bitxor(nextBlock()).bitand(maxValue)
newInstructionIndex = instructionIndex + 2
else:
raise newException(Defect, "Bad instruction code.")
return newInstructionIndex.bitand(maxAddress).int()

BIN
main Executable file

Binary file not shown.

67
main.nim Normal file
View File

@ -0,0 +1,67 @@
import strutils
import json
import random
import times
import os
import input_type
import map_type
import turtle
import turtle_type
import score
import select
import mutate
import display
let now = getTime()
var seed = now.toUnix * 1_000_000_000 + now.nanosecond
if paramCount() > 0:
seed = parseInt(paramStr(1))
randomize(seed)
# first, get the inputs
let input = readFile("input.json").parseJson().to(Input)
if input.bits < 4 or input.bits > 32:
raise newException(Defect, "bits in input json must be between 4 and 32 (inclusive).")
# get a map
let map = input.generateMap()
# get population turtles
var turtles: seq[Turtle]
for i in 0..input.population-1:
turtles.add(input.genTurtle())
var lastTime = cpuTime()
proc generation(generationNumber: int) =
# does a whole generation
# gets the path of the turtles
for turtle in turtles:
turtle.execute(map, input)
score(turtle, map, input)
# display generation
if generationNumber mod 500 == 0:
let newTime = cpuTime()
let timeSeconds = newTime - lastTime
lastTime = newTime
displayGeneration(generationNumber, timeSeconds, seed.int(), input, map, turtles)
turtles.select(input)
# add mutated turtles from previous generation
let prevGenCount = turtles.high()
while turtles.len() < input.population:
let basis = turtles[rand(prevGenCount)]
turtles.add(mutate(basis, input))
var i = 0
while true:
generation(i)
inc i

54
map_type.nim Normal file
View File

@ -0,0 +1,54 @@
import strutils
import sequtils
import random
import input_type
type
Tile* = enum
tileEmpty, tileTreasure
Map* = seq[seq[Tile]]
proc generateMap*(input: Input): Map =
let treasureCount = input.treasure_count
# create empty map
for i in 0..input.size:
result.add(@[])
for j in 0..input.size:
result[i].add(tileEmpty)
# populate treasures
var addedTreasures = 0
while addedTreasures < treasureCount:
let randX = rand(input.size - 1)
let randY = rand(input.size - 1)
if result[randX][randY] == tileEmpty:
result[randX][randY] = tileTreasure
inc addedTreasures
func tileToString*(tile: Tile): string =
case tile:
of tileEmpty:
" "
of tileTreasure:
"T"
func lineToString*(line: seq[Tile]): string =
line.map(tileToString).join()
proc mapToString*(map: Map): string =
map.map(lineToString).join("\n")
proc printMap*(map: Map) =
map.mapToString().echo()
func getTreasures*(map: Map): seq[(int, int)] =
var treasures: seq[(int, int)]
for y in 0..map.high():
for x in 0..map[0].high():
if map[y][x] == tileTreasure:
treasures.add((x, y))
return treasures

14
mutate.nim Normal file
View File

@ -0,0 +1,14 @@
import turtle
import turtle_type
import input_type
import random
proc mutate*(turtle: Turtle, input: Input): Turtle =
# returns a mutated turtle
let newTurtle = genTurtle(input)
for i in 0..newTurtle.memory.high():
if rand(100) > 2:
newTurtle.memory[i] = turtle.memory[i]
return newTurtle

38
score.nim Normal file
View File

@ -0,0 +1,38 @@
import map_type
import input_type
import turtle_type
import math
proc score*(turtle: Turtle, map: Map, input: Input) =
let path = turtle.path
# create a list of treasures
let treasures = map.getTreasures()
# create a list of distances from each treasure
var distances: seq[float] = @[]
for i in 0..treasures.high():
distances.add(-1.0)
# iterate through the path, and change the distance if it decreases
for i in 0..path.ys.high():
let x = path.xs[i]
let y = path.ys[i]
for j in 0..treasures.high():
let (tx, ty) = treasures[j]
let deltaX = abs(tx - x)
let deltaY = abs(ty - y)
# taxicab distance
let distance = (deltaX + deltaY).float()
if distances[j] < 0 or distance < distances[j]:
distances[j] = distance
# every distance contributes to score, exponentially dropping off
# by the formula Ae^(-k * dist)
var treasureScore = 0.0
for distance in distances:
const A = 100.0
const k = 0.2
treasureScore += A * exp(- k * distance)
turtle.score = treasureScore.int() - (turtle.steps div 5)

36
select.nim Normal file
View File

@ -0,0 +1,36 @@
import random
import math
import algorithm
import turtle
import turtle_type
import input_type
proc select*(turtles: var seq[Turtle], input: Input) =
# sort turtles according to score
turtles.sort(compareTurtles)
var scoreSum = 0
for turtle in turtles:
scoreSum += turtle.score
let avgScore = scoreSum div turtles.len()
var survivors: seq[Turtle] = @[]
for i in 0..turtles.high():
let turtle = turtles[i]
const smoothness = 0.08
const chanceRandomLive = 0.02
const chanceRandomDie = 0.0
let chanceExp = exp(smoothness * float(turtles[i].score - avgScore))
let chance = chanceRandomLive + (1-chanceRandomDie-chanceRandomLive) * chanceExp / (chanceExp + 1)
if rand(1.0) < chance:
survivors.add(turtle)
var i = 0
while i < turtles.len():
if turtles[i] notin survivors:
turtles.del(i)
else:
inc i

75
turtle.nim Normal file
View File

@ -0,0 +1,75 @@
import input_type
import map_type
import sequtils
import turtle_type
#import instruction_sets/eight_bit
#import instruction_sets/ten_bit
import instruction_sets/any_bit
proc compareTurtles*(left, right: Turtle): int =
-cmp(left.score, right.score)
# general:
proc genTurtle*(input: Input): Turtle =
if input.instruction_set == "anybit":
return genAnybitTurtle(input, input.bits)
else:
raise newException(Defect, "Unknown instruction set.")
proc executeInstruction(instructionIndex: int, turtle: Turtle, map: Map, input: Input): int =
if input.instruction_set == "anybit":
return executeAnybit(instructionIndex.uint32, turtle, map, input, input.bits)
else:
raise newException(Defect, "Unknown instruction set.")
proc execute*(turtle: Turtle, map: Map, input: Input) =
let treasures = map.getTreasures()
# back to start
turtle.y = input.size div 2
turtle.x = input.size div 2
turtle.path.xs = @[]
turtle.path.ys = @[]
turtle.score = 0
turtle.found = repeat(false, treasures.len())
# save turtle original code
let turtleOriginalCode = turtle.memory
template tpath: Path =
turtle.path
proc markLocation: bool =
tpath.xs.add(turtle.x)
tpath.ys.add(turtle.y)
if map[turtle.y][turtle.x] == tileTreasure:
for i in 0..treasures.high():
let treasure = treasures[i]
if treasure[0] == turtle.x and treasure[1] == turtle.y:
turtle.found[i] = true
if turtle.found.allIt(it):
return true
return false
discard markLocation()
var instructionNumber = 0
var instructionIndex = 0
while instructionNumber < input.lifetime:
instructionIndex = executeInstruction(instructionIndex, turtle, map, input)
if instructionIndex == -1:
# out of bounds of map
break
if turtle.y != tpath.ys[tpath.ys.high()] or turtle.x != tpath.xs[tpath.xs.high()]:
if markLocation():
break
inc instructionNumber
turtle.steps = instructionNumber
# restore original code
turtle.memory = turtleOriginalCode

13
turtle_type.nim Normal file
View File

@ -0,0 +1,13 @@
type
Turtle* = ref object
memory*: seq[uint32]
x*: int
y*: int
score*: int
path*: Path
steps*: int
found*: seq[bool]
Path* = object
xs*: seq[int]
ys*: seq[int]