Langton's ant

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
badidea
Posts: 2592
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Langton's ant

Post by badidea »

neil wrote: Jan 21, 2024 21:34 @badidea
It took about 20 minutes for all of the ants to find the food source.
The last two ants could have used some help finding a food source.
For a demo, I'm not sure if everyone is going to wait 20 or more minutes for it to finish.
Is there any way you could speed it up?
Then you had an extremely unlucky random seed.
There is a sleep 50,0 that you can change
Or change const NUM_ANT = 30 and/or setRndPos(map(), 5, T_FOOD, 0.7) 'set food positions
There is also an error in previous version, but a new version is coming soon.
badidea
Posts: 2592
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Langton's ant

Post by badidea »

Updated version (with more ants). The ants now create a path to the food. Visible by holding the "F" key.

Code: Select all

'https://www.freebasic.net/forum/viewtopic.php?p=301888#p301888
'An ant colony food finding simulation.
'Like here: https://onestepcode.com/ant-colony-simulation/
'Where ants:
'- search for food locations - done
'- find the way back to the nest with the food - done
'- mark a path to the food for others - done
'- optimize the path - todo
'- switch to different location when the food runs out - todo

'TODO:
'- change cell types to bitmask

#include "fbgfx.bi"

#define hRGB(rgb_) ((((((rgb_) and &hF00) shl 4) or ((rgb_) and &hF00)) shl 8) or (((((rgb_) and &h0F0) shl 4) or ((rgb_) and &h0F0)) shl 4) or ((((rgb_) and &h00F) shl 4) or ((rgb_) and &h00F)))

'define colors
const as ulong C_BLACK  = hRGB(&h000) 'nest
const as ulong C_YELLOW = hRGB(&hFF0) 'food
const as ulong C_RED    = hRGB(&hF00) 'ant
const as ulong C_LGREEN = hRGB(&h0F0)
const as ulong C_DGREEN = hRGB(&h080)
const as ulong C_BLUE   = hRGB(&h22F)
const as ulong C_BROWN  = hRGB(&h742) 'dirt
const as ulong C_LGREY  = hRGB(&h777) 'rock / obstacle
const as ulong C_DGREY  = hRGB(&h444) 'rock / obstacle

'map cell types
const T_NONE = -1 'out of bounds
const T_DIRT = 0
const T_NEST = 1 'enty location to nest
const T_FOOD = 2
const T_OBST = 3 'obstacle

dim shared as ulong cellColor(0 to 3) = {C_BROWN, C_BLACK, C_YELLOW, C_LGREY}

type cell_type
	dim as integer t 'type
	dim as integer nhm 'nest heat map
	dim as integer fhm 'food heat map
end type

'define map
const MAP_W = 100, MAP_H = 80
dim as cell_type map(MAP_W - 1, MAP_H - 1)
const MAX_HM = 99999 'for heat map

'define screen
const DRAW_SCALE = 8
const SCRN_W = MAP_W * DRAW_SCALE, SCRN_H = MAP_H * DRAW_SCALE
screenres SCRN_W, SCRN_H, 32
width SCRN_W \ 8, SCRN_H \ 16

'-------------------------------------------------------------------------------

'draw map cell (right = x+, down = y+)
sub drawCell(x as integer, y as integer, c as ulong)
	line(x * DRAW_SCALE, y * DRAW_SCALE)-_
	step(DRAW_SCALE - 1, DRAW_SCALE - 1), c, bf
end sub

'draw entire map, cell type values
sub drawMapT(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer cellType = map(x, y).t
			drawCell(x, y, cellColor(cellType))
		next
	next
end sub

const MIN_INTENSITY = 20 'minimum visible on screen?

'draw nest heat map
sub drawMapNhm(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer value = map(x, y).nhm
			dim as ulong c = 0
			if value <> MAX_HM then
				dim as integer intensity = 255 - value * 5
				if intensity < MIN_INTENSITY then intensity = MIN_INTENSITY
				c = rgb(intensity, intensity, intensity)
			end if
			drawCell(x, y, c)
		next
	next
end sub

'draw food heat map
sub drawMapFhm(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer value = map(x, y).fhm
			dim as ulong c = 0
			if value <> MAX_HM then
				dim as integer intensity = 255 - value * 5
				if intensity < MIN_INTENSITY then intensity = MIN_INTENSITY
				c = rgb(intensity, intensity, intensity)
			end if
			drawCell(x, y, c)
		next
	next
end sub

'set 'count' random locations to type,
'but not to close to border of map, determined by f
'e.g. (rnd * 0.8 + 0.1) => 0.1....0.9 range for f = 0.8
sub setRndPos(map() as cell_type, count as integer, cellType as integer, f as double)
	dim as double f2 = (1 - f) / 2
	for i as integer = 1 to count
		dim as integer x = int((rnd * f + f2) * ubound(map, 1))
		dim as integer y = int((rnd * f + f2) * ubound(map, 2))
		map(x, y).t = cellType
	next
end sub

function getCell(map() as cell_type, x as integer, y as integer) as integer
	if x < lbound(map, 1) then return T_NONE
	if x > ubound(map, 1) then return T_NONE
	if y < lbound(map, 2) then return T_NONE
	if y > ubound(map, 2) then return T_NONE
	return map(x, y).t
end function

'-------------------------------------------------------------------------------

'ant tasks
const FROZEN = 0 'or dead
const SEARCH_FOOD = 1 'random walk?
const GOTO_NEST = 2
const GOTO_FOOD = 3

type ant_type
	dim as integer x, y 'position on map
	dim as integer task
end type

const NUM_ANT = 15
dim as ant_type ant(NUM_ANT - 1)

'smaller then cell
sub drawAnt(x as integer, y as integer, c as ulong)
	line(x * DRAW_SCALE + 1, y * DRAW_SCALE + 1)-_
	step(DRAW_SCALE - 3, DRAW_SCALE - 3), c, bf
end sub

sub drawAnts(ant() as ant_type)
	for i as integer = 0 to ubound(ant)
		dim as ulong c
		select case ant(i).task
		case FROZEN : c = C_DGREY
		case SEARCH_FOOD : c = C_RED
		case GOTO_NEST : c = C_BLUE
		case GOTO_FOOD : c = C_DGREEN
		end select
		drawAnt(ant(i).x, ant(i).y, c)
	next
end sub

'-------------------------------------------------------------------------------

type nest_type
	dim as integer x, y 'position on map
end type

const NUM_NEST = 5 'entrance
dim as nest_type nest(NUM_NEST - 1)

'-------------------------------------------------------------------------------

type move_type
	dim as integer dx, dy
end type

'delta move: up, right, down, left
dim as move_type move(0 to 3) = {(0,-1), (+1,0), (0,+1), (-1,0)}

'-------------------------------------------------------------------------------

randomize timer '7878 'timer

 'set obstacles on map
setRndPos(map(), 500, T_OBST, 0.9)

'set heat maps: nest & food to 'undiscovered' / far away
for x as integer = 0 to ubound(map, 1)
	for y as integer = 0 to ubound(map, 2)
		map(x, y).nhm = MAX_HM
		map(x, y).fhm = MAX_HM
	next
next

'set food positions
const NUM_FOOD = 10
for i as integer = 0 to NUM_FOOD - 1
	dim as integer x = int((rnd * 0.8 + 0.1) * MAP_W)
	dim as integer y = int((rnd * 0.8 + 0.1) * MAP_H)
	map(x, y).t = T_FOOD
	map(x, y).fhm = 0 'distance to food is zero
next

'set nest locations
for i as integer = 0 to ubound(nest)
	nest(i).x = int((rnd * 0.6 + 0.2) * MAP_W)
	nest(i).y = int((rnd * 0.6 + 0.2) * MAP_H)
	with map(nest(i).x, nest(i).y)
		.t = T_NEST 'mark on map
		.nhm = 0 'distance to nest is zero
	end with
next

'set ant start position to a nest entrance location
for i as integer = 0 to ubound(ant)
	dim as integer iNest = int(rnd * NUM_NEST)
	ant(i).x = nest(iNest).x
	ant(i).y = nest(iNest).y
	ant(i).task = SEARCH_FOOD
next

'main loop
dim as integer showNhm = false, showFhm = false
dim as string key
dim as integer i, steps = 0, found = 0, collected = 0
do
	'display stuff
	screenlock
	if showNhm then
		drawMapNhm(map()) 'show nest heat map
	elseif showFhm then
		drawMapFhm(map()) 'show food heat map
	else
		drawMapT(map()) 'show normal surface map
	end if
	drawAnts(ant())
	draw string(10, 10), "<ESC> = exit, <N> nest heat map, <F> food heat map", C_LGREEN
	draw string(10, 30), "Steps: " + str(steps), C_LGREEN
	draw string(10, 50), "Found: " + str(found), C_LGREEN
	draw string(10, 70), "Collected: " + str(collected), C_LGREEN
	'~ for i = 0 to ubound(ant) 'list ant tasks
		'~ draw string(10, 90 + i * 16), str(ant(i).task), C_LGREEN
	'~ next
	screenunlock
	
	'update ant positions and tasks
	for i = 0 to ubound(ant)
		with ant(i)
			select case .task
			case FROZEN
				'nothing
			case SEARCH_FOOD
				dim as integer oldx = .x
				dim as integer oldy = .y
				dim as integer antdir = int(rnd * 4)
				.x += move(antdir).dx
				.y += move(antdir).dy
				dim as integer cellValue = getCell(map(), .x, .y)
				if cellValue = T_OBST or cellValue = T_NONE then
					.x = oldx 'reset position
					.y = oldy
				else 'step done
					'get prev. distance to nest
					dim as integer prevNestDist = map(oldx, oldy).nhm
					dim as integer thisNestDist = map(.x, .y).nhm
					if thisNestDist < prevNestDist then
						'implies already visited location & shorter path found, closer to nest
						map(oldx, oldy).nhm = thisNestDist + 1
					else
						map(.x, .y).nhm = prevNestDist + 1
					end if
				end if
				if cellValue = T_FOOD then
					.task = GOTO_NEST
					found += 1
				end if
			case GOTO_NEST
				dim as integer antdir = -1 'invalid direction
				dim as integer bestNestDist = map(.x, .y).nhm 'current position
				dim as integer tryx, tryy
				for i as integer = 0 to 3 'loop directions
					tryx = .x + move(i).dx
					tryy = .y + move(i).dy
					dim as integer cellValue = getCell(map(), tryx, tryy)
					if cellValue <> T_OBST and cellValue <> T_NONE then 'can go there
						if map(tryx, tryy).nhm < bestNestDist then
							antdir = i
							bestNestDist = map(tryx, tryy).nhm
						end if
					end if
				next
				if antdir <> -1 then 'update ant position
					dim as integer prevFoodDist = map(.x, .y).fhm 'previous distance to food
					.x += move(antdir).dx
					.y += move(antdir).dy
					map(.x, .y).fhm = prevFoodDist + 1 '1 step further away from food now
				end if
				'if nest reached, goto_food
				if map(.x, .y).t = T_NEST then
					collected += 1
					.task = GOTO_FOOD
				end if
			case GOTO_FOOD
				'if food gone???, search_food ????
				'if no low value on food, heatmap, clear previous cell's heatmap value ????
				dim as integer antdir = -1 'invalid direction
				dim as integer bestFoodDist = map(.x, .y).fhm 'current position
				dim as integer tryx, tryy
				for i as integer = 0 to 3 'loop directions
					tryx = .x + move(i).dx
					tryy = .y + move(i).dy
					dim as integer cellValue = getCell(map(), tryx, tryy)
					if cellValue <> T_OBST and cellValue <> T_NONE then 'can go there
						if map(tryx, tryy).fhm < bestFoodDist then
							antdir = i
							bestFoodDist = map(tryx, tryy).fhm
						end if
					end if
				next
				if antdir <> -1 then 'update ant position
					'~ dim as integer prevFoodDist = map(.x, .y).fhm 'previous distance to food
					.x += move(antdir).dx
					.y += move(antdir).dy
					'~ map(.x, .y).fhm = prevFoodDist + 1 '1 step further away from food now
				else
					'stuck, path/food lost, do rando walk search
					.task = SEARCH_FOOD
				end if
				if map(.x, .y).t = T_FOOD then
					.task = GOTO_NEST
					found += 1
				end if
			end select
		end with
	next
	
	sleep 50,0 'shorter sleep to speed up
	steps += 1
	key = inkey
	if multikey(FB.SC_N) then showNhm = true else showNhm = false
	if multikey(FB.SC_F) then showFhm = true else showFhm = false
loop while not multikey(FB.SC_ESCAPE)
print "End"
getkey()
neil
Posts: 594
Joined: Mar 17, 2022 23:26

Re: Langton's ant

Post by neil »

@badidea
Your updates are looking good. I Like the F key to see whats going on. Nice work.

I read that real ants have a keen sense of smell. If they were within 1 cm of the food, they would have smelled it.
Maybe you could make the food target larger and easier to find?
Although it's fine the way it is,.
badidea
Posts: 2592
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Langton's ant

Post by badidea »

Final version for today. The food locations now run out.
The random walkers don't pick up a food trail yet and those who do follow a trail, often go to depleted food location and get lost. Resulting in many random walkers. To be improved...

Code: Select all

'https://www.freebasic.net/forum/viewtopic.php?p=301888#p301888
'An ant colony food finding simulation.
'Like here: https://onestepcode.com/ant-colony-simulation/
'Where ants:
'- search for food locations - done
'- find the way back to the nest with the food - done
'- mark a path to the food for others - done
'- optimize the path - todo
'- switch to different location when the food runs out - done

'TODO:
'- change cell types to bitmask
'- pick up food trace during random walk
'- implement Langton's ant ovement instead of random walk?
'- possible bug with blue ants stuck?
'- fade food location with with food units running out

#include "fbgfx.bi"

#define hRGB(rgb_) ((((((rgb_) and &hF00) shl 4) or ((rgb_) and &hF00)) shl 8) or (((((rgb_) and &h0F0) shl 4) or ((rgb_) and &h0F0)) shl 4) or ((((rgb_) and &h00F) shl 4) or ((rgb_) and &h00F)))

'define colors
const as ulong C_BLACK  = hRGB(&h000) 'nest
const as ulong C_YELLOW = hRGB(&hFF0) 'food
const as ulong C_RED    = hRGB(&hF00) 'ant random food search
const as ulong C_LGREEN = hRGB(&h0F0) 'text
const as ulong C_DGREEN = hRGB(&h080) 'ant to food
const as ulong C_BLUE   = hRGB(&h22F) 'ant to nest
const as ulong C_BROWN  = hRGB(&h742) 'dirt
const as ulong C_LGREY  = hRGB(&h777) 'rock / obstacle
const as ulong C_DGREY  = hRGB(&h444) 'rock / obstacle

'map cell types
const T_NONE = -1 'out of bounds
const T_DIRT = 0
const T_NEST = 1 'enty location to nest
const T_FOOD = 2
const T_OBST = 3 'obstacle

dim shared as ulong cellColor(0 to 3) = {C_BROWN, C_BLACK, C_YELLOW, C_LGREY}

type cell_type
	dim as integer t 'type
	dim as integer nhm 'nest heat map
	dim as integer fhm 'food heat map
	dim as integer fu 'food units
end type

'define map
const MAP_W = 100, MAP_H = 80
dim as cell_type map(MAP_W - 1, MAP_H - 1)
const MAX_HM = 99999 'for heat map

'define screen
const DRAW_SCALE = 8
const SCRN_W = MAP_W * DRAW_SCALE, SCRN_H = MAP_H * DRAW_SCALE
screenres SCRN_W, SCRN_H, 32
width SCRN_W \ 8, SCRN_H \ 16

'-------------------------------------------------------------------------------

'draw map cell (right = x+, down = y+)
sub drawCell(x as integer, y as integer, c as ulong)
	line(x * DRAW_SCALE, y * DRAW_SCALE)-_
	step(DRAW_SCALE - 1, DRAW_SCALE - 1), c, bf
end sub

'draw entire map, cell type values
sub drawMapT(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer cellType = map(x, y).t
			drawCell(x, y, cellColor(cellType))
		next
	next
end sub

const MIN_INTENSITY = 20 'minimum visible on screen?

'draw nest heat map
sub drawMapNhm(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer value = map(x, y).nhm
			dim as ulong c = 0
			if value <> MAX_HM then
				dim as integer intensity = 255 - value * 5
				if intensity < MIN_INTENSITY then intensity = MIN_INTENSITY
				c = rgb(intensity, intensity, intensity)
			end if
			drawCell(x, y, c)
		next
	next
end sub

'draw food heat map
sub drawMapFhm(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer value = map(x, y).fhm
			dim as ulong c = 0
			if value <> MAX_HM then
				dim as integer intensity = 255 - value * 5
				if intensity < MIN_INTENSITY then intensity = MIN_INTENSITY
				c = rgb(intensity, intensity, intensity)
			end if
			drawCell(x, y, c)
		next
	next
end sub

'set 'count' random locations to type,
'but not to close to border of map, determined by f
'e.g. (rnd * 0.8 + 0.1) => 0.1....0.9 range for f = 0.8
sub setRndPos(map() as cell_type, count as integer, cellType as integer, f as double)
	dim as double f2 = (1 - f) / 2
	for i as integer = 1 to count
		dim as integer x = int((rnd * f + f2) * ubound(map, 1))
		dim as integer y = int((rnd * f + f2) * ubound(map, 2))
		map(x, y).t = cellType
	next
end sub

function getCell(map() as cell_type, x as integer, y as integer) as integer
	if x < lbound(map, 1) then return T_NONE
	if x > ubound(map, 1) then return T_NONE
	if y < lbound(map, 2) then return T_NONE
	if y > ubound(map, 2) then return T_NONE
	return map(x, y).t
end function

'-------------------------------------------------------------------------------

'ant tasks
const FROZEN = 0 'or dead
const SEARCH_FOOD = 1 'random walk?
const GOTO_NEST = 2
const GOTO_FOOD = 3

type ant_type
	dim as integer x, y 'position on map
	dim as integer task
end type

const NUM_ANT = 30
dim as ant_type ant(NUM_ANT - 1)

'smaller then cell
sub drawAnt(x as integer, y as integer, c as ulong)
	line(x * DRAW_SCALE + 1, y * DRAW_SCALE + 1)-_
	step(DRAW_SCALE - 3, DRAW_SCALE - 3), c, bf
end sub

sub drawAnts(ant() as ant_type)
	for i as integer = 0 to ubound(ant)
		dim as ulong c
		select case ant(i).task
		case FROZEN : c = C_DGREY
		case SEARCH_FOOD : c = C_RED
		case GOTO_NEST : c = C_BLUE
		case GOTO_FOOD : c = C_DGREEN
		end select
		drawAnt(ant(i).x, ant(i).y, c)
	next
end sub

'-------------------------------------------------------------------------------

type nest_type
	dim as integer x, y 'position on map
end type

const NUM_NEST = 10 'entrance
dim as nest_type nest(NUM_NEST - 1)

'-------------------------------------------------------------------------------

type move_type
	dim as integer dx, dy
end type

'delta move: up, right, down, left
dim as move_type move(0 to 3) = {(0,-1), (+1,0), (0,+1), (-1,0)}

'-------------------------------------------------------------------------------

const MIN_FOOD_UNITS = 5
const MAX_FOOD_UNITS = 20

sub setFoodRnd(map() as cell_type)
	tryAgain:
	dim as integer x = int((rnd * 0.8 + 0.1) * MAP_W)
	dim as integer y = int((rnd * 0.8 + 0.1) * MAP_H)
	if map(x, y).t = T_NEST then goto tryAgain 'don't erase nest entrace
	map(x, y).t = T_FOOD
	map(x, y).fhm = 0 'distance to food is zero
	map(x, y).fu = int(rnd * (MAX_FOOD_UNITS - MIN_FOOD_UNITS)) + MIN_FOOD_UNITS
end sub

sub updateFood(map() as cell_type, x as integer, y as integer, byref found as integer)
	found += 1
	map(x, y).fu -= 1 'reduce food unit count
	if map(x, y).fu = 0 then 'food depleted
		map(x, y).t = T_DIRT
		setFoodRnd(map()) 'add a new food location
	end if
end sub

'-------------------------------------------------------------------------------

randomize timer '7878 'timer

 'set obstacles on map
setRndPos(map(), 500, T_OBST, 0.9)

'set heat maps: nest & food to 'undiscovered' / far away
for x as integer = 0 to ubound(map, 1)
	for y as integer = 0 to ubound(map, 2)
		map(x, y).nhm = MAX_HM
		map(x, y).fhm = MAX_HM
	next
next

'set food positions
const NUM_FOOD = 10
for i as integer = 0 to NUM_FOOD - 1
	setFoodRnd(map())
next

'set nest locations
for i as integer = 0 to ubound(nest)
	nest(i).x = int((rnd * 0.6 + 0.2) * MAP_W)
	nest(i).y = int((rnd * 0.6 + 0.2) * MAP_H)
	with map(nest(i).x, nest(i).y)
		.t = T_NEST 'mark on map
		.nhm = 0 'distance to nest is zero
	end with
next

'set ant start position to a nest entrance location
for i as integer = 0 to ubound(ant)
	dim as integer iNest = int(rnd * NUM_NEST)
	ant(i).x = nest(iNest).x
	ant(i).y = nest(iNest).y
	ant(i).task = SEARCH_FOOD
next

'main loop
dim as integer showNhm = false, showFhm = false
dim as string key
dim as integer i, steps = 0, found = 0, collected = 0
do
	'display stuff
	screenlock
	if showNhm then
		drawMapNhm(map()) 'show nest heat map
	elseif showFhm then
		drawMapFhm(map()) 'show food heat map
	else
		drawMapT(map()) 'show normal surface map
	end if
	drawAnts(ant())
	draw string(10, 10), "<ESC> = exit, <N> nest heat map, <F> food heat map", C_LGREEN
	draw string(10, 30), "Steps: " + str(steps), C_LGREEN
	draw string(10, 50), "Found: " + str(found), C_LGREEN
	draw string(10, 70), "Collected: " + str(collected), C_LGREEN
	'~ for i = 0 to ubound(ant) 'list ant tasks
		'~ draw string(10, 90 + i * 16), str(ant(i).task), C_LGREEN
	'~ next
	screenunlock
	
	'update ant positions and tasks
	for i = 0 to ubound(ant)
		with ant(i)
			select case .task
			case FROZEN
				'nothing
			case SEARCH_FOOD
				dim as integer oldx = .x
				dim as integer oldy = .y
				dim as integer antdir = int(rnd * 4)
				.x += move(antdir).dx
				.y += move(antdir).dy
				dim as integer cellValue = getCell(map(), .x, .y)
				if cellValue = T_OBST or cellValue = T_NONE then
					.x = oldx 'reset position
					.y = oldy
				else 'step done
					'get prev. distance to nest
					dim as integer prevNestDist = map(oldx, oldy).nhm
					dim as integer thisNestDist = map(.x, .y).nhm
					if thisNestDist < prevNestDist then
						'implies already visited location & shorter path found, closer to nest
						map(oldx, oldy).nhm = thisNestDist + 1
					else
						map(.x, .y).nhm = prevNestDist + 1
					end if
				end if
				if cellValue = T_FOOD then
					.task = GOTO_NEST
					updateFood(map(), .x, .y, found)
				end if
			case GOTO_NEST
				dim as integer antdir = -1 'invalid direction
				dim as integer bestNestDist = map(.x, .y).nhm 'current position
				dim as integer tryx, tryy
				for i as integer = 0 to 3 'loop directions
					tryx = .x + move(i).dx
					tryy = .y + move(i).dy
					dim as integer cellValue = getCell(map(), tryx, tryy)
					if cellValue <> T_OBST and cellValue <> T_NONE then 'can go there
						if map(tryx, tryy).nhm < bestNestDist then
							antdir = i
							bestNestDist = map(tryx, tryy).nhm
						end if
					end if
				next
				if antdir <> -1 then 'update ant position
					dim as integer prevFoodDist = map(.x, .y).fhm 'previous distance to food
					.x += move(antdir).dx
					.y += move(antdir).dy
					map(.x, .y).fhm = prevFoodDist + 1 '1 step further away from food now
				end if
				'if nest reached, goto_food
				if map(.x, .y).t = T_NEST then
					collected += 1
					.task = GOTO_FOOD
				end if
			case GOTO_FOOD
				dim as integer antdir = -1 'invalid direction
				dim as integer bestFoodDist = map(.x, .y).fhm 'current position
				dim as integer tryx, tryy
				for i as integer = 0 to 3 'loop directions
					tryx = .x + move(i).dx
					tryy = .y + move(i).dy
					dim as integer cellValue = getCell(map(), tryx, tryy)
					if cellValue <> T_OBST and cellValue <> T_NONE then 'can go there
						if map(tryx, tryy).fhm < bestFoodDist then
							antdir = i
							bestFoodDist = map(tryx, tryy).fhm
						end if
					end if
				next
				if antdir <> -1 then 'update ant position
					.x += move(antdir).dx
					.y += move(antdir).dy
				else
					'stuck, path/food lost, do random walk search
					.task = SEARCH_FOOD
					map(.x, .y).fhm = MAX_HM
				end if
				if map(.x, .y).t = T_FOOD then
					.task = GOTO_NEST
					updateFood(map(), .x, .y, found)
				end if
			end select
		end with
	next
	
	sleep 50,0 'shorter sleep to speed up
	steps += 1
	key = inkey
	if multikey(FB.SC_N) then showNhm = true else showNhm = false
	if multikey(FB.SC_F) then showFhm = true else showFhm = false
loop while not multikey(FB.SC_ESCAPE)
print "End"
getkey()
Last edited by badidea on Jan 22, 2024 0:22, edited 1 time in total.
neil
Posts: 594
Joined: Mar 17, 2022 23:26

Re: Langton's ant

Post by neil »

@badidea
When the random walkers find the food trail that should speed things up.

Here's an ant logo for you.

Code: Select all

Print "\("")/"
Print "-( )-"
Print "/(_)\"
Sleep
neil
Posts: 594
Joined: Mar 17, 2022 23:26

Re: Langton's ant

Post by neil »

@basicidea
Checkout BasicCoder2's: Start of a Fishing Derby Game.
viewtopic.php?p=301964#p301964

You can play the Atari version of Fishing Derby online.
https://atarionline.org/atari-2600/fishing-derby
badidea
Posts: 2592
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Langton's ant

Post by badidea »

Further improved version. The food searching ants now detect the food trails, but they still get confused by old trails.
I could use 'back tracking' to erase old paths completely, but that sounds to smart for ants.
I'll try fading out of all trails over time as an alternative in the next version.
Also, most constants to play with, are moved to the top of the code.
Increase DRAW_SCALE for bigger display window.

Code: Select all

'https://www.freebasic.net/forum/viewtopic.php?p=301888#p301888
'An ant colony food finding simulation.
'Like here: https://onestepcode.com/ant-colony-simulation/
'Where ants:
'- search for food locations - done
'- find the way back to the nest with the food - done
'- mark a path to the food for others - done
'- switch to different location when the food runs out - done
'- pick up food trace during random walk - done
'- optimize the path - todo

'TODO:
'- implement Langton's ant movement instead of random walk?
'- change nest entrance locations
'- fade out (old) food trails 

#include "fbgfx.bi"

const NUM_ANT = 30
const NUM_NEST = 3 'nest entrances
const NUM_FOOD = 10
const NUM_OBST = 1000 'obstacles / rocks

const MIN_FOOD_UNITS = 10
const MAX_FOOD_UNITS = 50

#define hRGB(rgb_) ((((((rgb_) and &hF00) shl 4) or ((rgb_) and &hF00)) shl 8) or (((((rgb_) and &h0F0) shl 4) or ((rgb_) and &h0F0)) shl 4) or ((((rgb_) and &h00F) shl 4) or ((rgb_) and &h00F)))

'define colors
const as ulong C_BLACK  = hRGB(&h000) 'nest
const as ulong C_YELLOW = hRGB(&hFF0) 'food
const as ulong C_RED    = hRGB(&hF00) 'ant random food search
const as ulong C_LGREEN = hRGB(&h0F0) 'text
const as ulong C_DGREEN = hRGB(&h080) 'ant to food
const as ulong C_BLUE   = hRGB(&h22F) 'ant to nest
const as ulong C_BROWN  = hRGB(&h532) 'dirt
const as ulong C_LGREY  = hRGB(&h777) 'rock / obstacle
const as ulong C_DGREY  = hRGB(&h444) 'rock / obstacle

'map cell types
const T_NONE = -1 'out of bounds
const T_DIRT = 0
const T_NEST = 1 'enty location to nest
const T_FOOD = 2
const T_OBST = 3 'obstacle

dim shared as ulong cellColor(0 to 3) = {C_BROWN, C_BLACK, C_YELLOW, C_LGREY}

type cell_type
	dim as integer t 'type
	dim as integer nhm 'nest heat map
	dim as integer fhm 'food heat map
	dim as integer fu 'food units
end type

'define map
const MAP_W = 100, MAP_H = 80
dim as cell_type map(MAP_W - 1, MAP_H - 1)
const MAX_HM = 99999 'for heat map

'define screen
const DRAW_SCALE = 8
const SCRN_W = MAP_W * DRAW_SCALE, SCRN_H = MAP_H * DRAW_SCALE
screenres SCRN_W, SCRN_H, 32
width SCRN_W \ 8, SCRN_H \ 16

'-------------------------------------------------------------------------------

'draw map cell (right = x+, down = y+)
sub drawCell(x as integer, y as integer, c as ulong)
	line(x * DRAW_SCALE, y * DRAW_SCALE)-_
	step(DRAW_SCALE - 1, DRAW_SCALE - 1), c, bf
end sub

'draw entire map, cell type values
sub drawMapT(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer cellType = map(x, y).t
			if cellType = T_FOOD then
				'less bright is less food, range 100...255
				dim as integer foodUnits = map(x, y).fu
				dim as integer intensity =  100 + (155 * foodUnits) / MAX_FOOD_UNITS
				dim as ulong c = rgb(intensity, intensity, 0)
				drawCell(x, y, c)
			else
				drawCell(x, y, cellColor(cellType))
			end if
		next
	next
end sub

const MIN_INTENSITY = 20 'minimum visible on screen?

'draw nest heat map
sub drawMapNhm(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer value = map(x, y).nhm
			dim as ulong c = 0
			if value <> MAX_HM then
				dim as integer intensity = 255 - value * 5
				if intensity < MIN_INTENSITY then intensity = MIN_INTENSITY
				c = rgb(intensity, intensity, intensity)
			end if
			drawCell(x, y, c)
		next
	next
end sub

'draw food heat map
sub drawMapFhm(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer value = map(x, y).fhm
			dim as ulong c = 0
			if value <> MAX_HM then
				dim as integer intensity = 255 - value * 5
				if intensity < MIN_INTENSITY then intensity = MIN_INTENSITY
				c = rgb(intensity, intensity, intensity)
			end if
			drawCell(x, y, c)
		next
	next
end sub

'set 'count' random locations to type,
'but not to close to border of map, determined by f
'e.g. (rnd * 0.8 + 0.1) => 0.1....0.9 range for f = 0.8
sub setRndPos(map() as cell_type, count as integer, cellType as integer, f as double)
	dim as double f2 = (1 - f) / 2
	for i as integer = 1 to count
		dim as integer x = int((rnd * f + f2) * ubound(map, 1))
		dim as integer y = int((rnd * f + f2) * ubound(map, 2))
		map(x, y).t = cellType
	next
end sub

function getCell(map() as cell_type, x as integer, y as integer) as integer
	if x < lbound(map, 1) then return T_NONE
	if x > ubound(map, 1) then return T_NONE
	if y < lbound(map, 2) then return T_NONE
	if y > ubound(map, 2) then return T_NONE
	return map(x, y).t
end function

'-------------------------------------------------------------------------------

'ant tasks
const FROZEN = 0 'or dead
const SEARCH_FOOD = 1 'random walk?
const GOTO_NEST = 2
const GOTO_FOOD = 3

type ant_type
	dim as integer x, y 'position on map
	dim as integer task
end type

dim as ant_type ant(NUM_ANT - 1)

'smaller then cell
sub drawAnt(x as integer, y as integer, c as ulong)
	line(x * DRAW_SCALE + 1, y * DRAW_SCALE + 1)-_
	step(DRAW_SCALE - 3, DRAW_SCALE - 3), c, bf
end sub

sub drawAnts(ant() as ant_type)
	for i as integer = 0 to ubound(ant)
		dim as ulong c
		select case ant(i).task
		case FROZEN : c = C_DGREY
		case SEARCH_FOOD : c = C_RED
		case GOTO_NEST : c = C_BLUE
		case GOTO_FOOD : c = C_DGREEN
		end select
		drawAnt(ant(i).x, ant(i).y, c)
	next
end sub

'-------------------------------------------------------------------------------

type nest_type
	dim as integer x, y 'position on map
end type

dim as nest_type nest(NUM_NEST - 1)

'-------------------------------------------------------------------------------

type move_type
	dim as integer dx, dy
end type

'delta move: up, right, down, left
dim as move_type move(0 to 3) = {(0,-1), (+1,0), (0,+1), (-1,0)}

'-------------------------------------------------------------------------------

sub setFoodRnd(map() as cell_type)
	tryAgain:
	dim as integer x = int((rnd * 0.8 + 0.1) * MAP_W)
	dim as integer y = int((rnd * 0.8 + 0.1) * MAP_H)
	if map(x, y).t = T_NEST then goto tryAgain 'don't erase nest entrace
	map(x, y).t = T_FOOD
	map(x, y).fhm = 0 'distance to food is zero
	map(x, y).fu = int(rnd * (MAX_FOOD_UNITS - MIN_FOOD_UNITS)) + MIN_FOOD_UNITS
end sub

sub updateFood(map() as cell_type, x as integer, y as integer, byref found as integer)
	found += 1
	map(x, y).fu -= 1 'reduce food unit count
	if map(x, y).fu = 0 then 'food depleted
		map(x, y).t = T_DIRT
		setFoodRnd(map()) 'add a new food location
	end if
end sub

'-------------------------------------------------------------------------------

randomize timer '7878 'timer

 'set obstacles on map
setRndPos(map(), NUM_OBST, T_OBST, 0.95)

'set heat maps: nest & food to 'undiscovered' / far away
for x as integer = 0 to ubound(map, 1)
	for y as integer = 0 to ubound(map, 2)
		map(x, y).nhm = MAX_HM
		map(x, y).fhm = MAX_HM
	next
next

'set food positions
for i as integer = 0 to NUM_FOOD - 1
	setFoodRnd(map())
next

'set nest locations
for i as integer = 0 to ubound(nest)
	nest(i).x = int((rnd * 0.6 + 0.2) * MAP_W)
	nest(i).y = int((rnd * 0.6 + 0.2) * MAP_H)
	with map(nest(i).x, nest(i).y)
		.t = T_NEST 'mark on map
		.nhm = 0 'distance to nest is zero
	end with
next

'set ant start position to a nest entrance location
for i as integer = 0 to ubound(ant)
	dim as integer iNest = int(rnd * NUM_NEST)
	ant(i).x = nest(iNest).x
	ant(i).y = nest(iNest).y
	ant(i).task = SEARCH_FOOD
next

'main loop
dim as integer showNhm = false, showFhm = false
dim as string key
dim as integer i, steps = 0, found = 0, collected = 0
do
	'display stuff
	screenlock
	if showNhm then
		drawMapNhm(map()) 'show nest heat map
	elseif showFhm then
		drawMapFhm(map()) 'show food heat map
	else
		drawMapT(map()) 'show normal surface map
	end if
	drawAnts(ant())
	draw string(10, 10), "<ESC> = exit, <N> nest heat map, <F> food heat map", C_LGREEN
	draw string(10, 30), "Steps: " + str(steps), C_LGREEN
	draw string(10, 50), "Found: " + str(found), C_LGREEN
	draw string(10, 70), "Collected: " + str(collected), C_LGREEN
	'~ for i = 0 to ubound(ant) 'list ant tasks
		'~ draw string(10, 90 + i * 16), str(ant(i).task), C_LGREEN
	'~ next
	screenunlock
	
	'update ant positions and tasks
	for i = 0 to ubound(ant)
		with ant(i)
			select case .task
			case FROZEN
				'nothing
			case SEARCH_FOOD
				dim as integer oldx = .x
				dim as integer oldy = .y
				dim as integer antdir = int(rnd * 4)
				.x += move(antdir).dx
				.y += move(antdir).dy
				dim as integer cellValue = getCell(map(), .x, .y)
				if cellValue = T_OBST or cellValue = T_NONE then
					.x = oldx 'reset position
					.y = oldy
				else 'step done
					dim as integer prevNestDist = map(oldx, oldy).nhm 'get prev. distance to nest
					dim as integer thisNestDist = map(.x, .y).nhm
					if thisNestDist < prevNestDist then
						'implies already visited location & shorter path found, closer to nest
						map(oldx, oldy).nhm = thisNestDist + 1
					else
						'normal step, further away from nest
						map(.x, .y).nhm = prevNestDist + 1
					end if
				end if
				'check for food trail, follow if found, gather food if on food
				if map(.x, .y).fhm < MAX_HM then
					if cellValue = T_FOOD then 'on food
						.task = GOTO_NEST
						updateFood(map(), .x, .y, found)
					else
						.task = GOTO_FOOD
					end if
				end if
			case GOTO_NEST
				dim as integer antdir = -1 'invalid direction
				dim as integer bestNestDist = map(.x, .y).nhm 'current position
				dim as integer tryx, tryy
				for i as integer = 0 to 3 'loop directions
					tryx = .x + move(i).dx
					tryy = .y + move(i).dy
					dim as integer cellValue = getCell(map(), tryx, tryy)
					if cellValue <> T_OBST and cellValue <> T_NONE then 'can go there
						if map(tryx, tryy).nhm < bestNestDist then
							antdir = i
							bestNestDist = map(tryx, tryy).nhm
						end if
					end if
				next
				if antdir <> -1 then 'update ant position
					dim as integer prevFoodDist = map(.x, .y).fhm 'previous distance to food
					.x += move(antdir).dx
					.y += move(antdir).dy
					map(.x, .y).fhm = prevFoodDist + 1 '1 step further away from food now
				end if
				'if nest reached, goto_food
				if map(.x, .y).t = T_NEST then
					collected += 1
					.task = GOTO_FOOD
				end if
			case GOTO_FOOD
				dim as integer antdir = -1 'invalid direction
				dim as integer bestFoodDist = map(.x, .y).fhm 'current position
				dim as integer tryx, tryy
				for i as integer = 0 to 3 'loop directions
					tryx = .x + move(i).dx
					tryy = .y + move(i).dy
					dim as integer cellValue = getCell(map(), tryx, tryy)
					if cellValue <> T_OBST and cellValue <> T_NONE then 'can go there
						if map(tryx, tryy).fhm < bestFoodDist then
							antdir = i
							bestFoodDist = map(tryx, tryy).fhm
						end if
					end if
				next
				if antdir <> -1 then 'update ant position
					.x += move(antdir).dx
					.y += move(antdir).dy
				else
					'stuck, path/food lost, do random walk search
					.task = SEARCH_FOOD
					map(.x, .y).fhm = MAX_HM 'destroy food trail (last part)
				end if
				if map(.x, .y).t = T_FOOD then
					.task = GOTO_NEST
					updateFood(map(), .x, .y, found)
				end if
			end select
		end with
	next
	
	sleep 50,0 'shorter sleep to speed up
	steps += 1
	key = inkey
	if multikey(FB.SC_N) then showNhm = true else showNhm = false
	if multikey(FB.SC_F) then showFhm = true else showFhm = false
loop while not multikey(FB.SC_ESCAPE)
print "End"
getkey()
neil
Posts: 594
Joined: Mar 17, 2022 23:26

Re: Langton's ant

Post by neil »

It's looking good so far. Fading out the old trails sounds like a good solution.
I know what you are doing is not an easy task.
badidea
Posts: 2592
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Langton's ant

Post by badidea »

Better now with fading paths.

Code: Select all

'https://www.freebasic.net/forum/viewtopic.php?p=301888#p301888
'An ant colony food finding simulation.
'Like here: https://onestepcode.com/ant-colony-simulation/
'Where ants:
'- search for food locations - done
'- find the way back to the nest with the food - done
'- mark a path to the food for others - done
'- switch to different location when the food runs out - done
'- pick up food trace during random walk - done
'- optimize the path - todo

'TODO:
'- implement Langton's ant movement instead of random walk?
'- change nest entrance locations

#include "fbgfx.bi"

const NUM_ANT = 30
const NUM_NEST = 3 'nest entrances
const NUM_FOOD = 10
const NUM_OBST = 1000 'obstacles / rocks

const MIN_FOOD_UNITS = 10
const MAX_FOOD_UNITS = 50

const MAX_HM = 500 'for heat map
'lower for faster decay of path
'but limits max path length as well)
'and it can cause food paths to farther away source to disappear, due to preffred for nearby sources

#define hRGB(rgb_) ((((((rgb_) and &hF00) shl 4) or ((rgb_) and &hF00)) shl 8) or (((((rgb_) and &h0F0) shl 4) or ((rgb_) and &h0F0)) shl 4) or ((((rgb_) and &h00F) shl 4) or ((rgb_) and &h00F)))

'define colors
const as ulong C_BLACK  = hRGB(&h000) 'nest
const as ulong C_YELLOW = hRGB(&hFF0) 'food
const as ulong C_RED    = hRGB(&hF00) 'ant random food search
const as ulong C_LGREEN = hRGB(&h0F0) 'text
const as ulong C_DGREEN = hRGB(&h080) 'ant to food
const as ulong C_BLUE   = hRGB(&h22F) 'ant to nest
const as ulong C_BROWN  = hRGB(&h532) 'dirt
const as ulong C_LGREY  = hRGB(&h777) 'rock / obstacle
const as ulong C_DGREY  = hRGB(&h444) 'rock / obstacle

'map cell types
const T_NONE = -1 'out of bounds
const T_DIRT = 0
const T_NEST = 1 'enty location to nest
const T_FOOD = 2
const T_OBST = 3 'obstacle

dim shared as ulong cellColor(0 to 3) = {C_BROWN, C_BLACK, C_YELLOW, C_LGREY}

type cell_type
	dim as integer t 'type
	dim as integer nhm 'nest heat map
	dim as integer fhm 'food heat map
	dim as integer fu 'food units
end type

'define map
const MAP_W = 100, MAP_H = 80
dim as cell_type map(MAP_W - 1, MAP_H - 1)

'define screen
const DRAW_SCALE = 8
const SCRN_W = MAP_W * DRAW_SCALE, SCRN_H = MAP_H * DRAW_SCALE
screenres SCRN_W, SCRN_H, 32
width SCRN_W \ 8, SCRN_H \ 16

'-------------------------------------------------------------------------------

'draw map cell (right = x+, down = y+)
sub drawCell(x as integer, y as integer, c as ulong)
	line(x * DRAW_SCALE, y * DRAW_SCALE)-_
	step(DRAW_SCALE - 1, DRAW_SCALE - 1), c, bf
end sub

'draw entire map, cell type values
sub drawMapT(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer cellType = map(x, y).t
			if cellType = T_FOOD then
				'less bright is less food, range 100...255
				dim as integer foodUnits = map(x, y).fu
				dim as integer intensity =  100 + (155 * foodUnits) / MAX_FOOD_UNITS
				dim as ulong c = rgb(intensity, intensity, 0)
				drawCell(x, y, c)
			else
				drawCell(x, y, cellColor(cellType))
			end if
		next
	next
end sub

const MIN_INTENSITY = 20 'minimum visible on screen?

'draw nest heat map
sub drawMapNhm(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer value = map(x, y).nhm
			dim as ulong c = 0
			if value <> MAX_HM then
				dim as integer intensity = 255 - value * 5
				if intensity < MIN_INTENSITY then intensity = MIN_INTENSITY
				c = rgb(intensity, intensity, intensity)
			end if
			drawCell(x, y, c)
		next
	next
end sub

'draw food heat map
sub drawMapFhm(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer value = map(x, y).fhm
			dim as ulong c = 0
			if value < MAX_HM then
				dim as integer intensity = 255 - value * 2
				if intensity < MIN_INTENSITY then intensity = MIN_INTENSITY
				c = rgb(intensity, intensity, intensity)
			end if
			drawCell(x, y, c)
		next
	next
end sub

'set 'count' random locations to type,
'but not to close to border of map, determined by f
'e.g. (rnd * 0.8 + 0.1) => 0.1....0.9 range for f = 0.8
sub setRndPos(map() as cell_type, count as integer, cellType as integer, f as double)
	dim as double f2 = (1 - f) / 2
	for i as integer = 1 to count
		dim as integer x = int((rnd * f + f2) * ubound(map, 1))
		dim as integer y = int((rnd * f + f2) * ubound(map, 2))
		map(x, y).t = cellType
	next
end sub

function getCell(map() as cell_type, x as integer, y as integer) as integer
	if x < lbound(map, 1) then return T_NONE
	if x > ubound(map, 1) then return T_NONE
	if y < lbound(map, 2) then return T_NONE
	if y > ubound(map, 2) then return T_NONE
	return map(x, y).t
end function

'-------------------------------------------------------------------------------

'ant tasks
const FROZEN = 0 'or dead
const SEARCH_FOOD = 1 'random walk?
const GOTO_NEST = 2
const GOTO_FOOD = 3

type ant_type
	dim as integer x, y 'position on map
	dim as integer task
end type

dim as ant_type ant(NUM_ANT - 1)

'smaller then cell
sub drawAnt(x as integer, y as integer, c as ulong)
	line(x * DRAW_SCALE + 1, y * DRAW_SCALE + 1)-_
	step(DRAW_SCALE - 3, DRAW_SCALE - 3), c, bf
end sub

sub drawAnts(ant() as ant_type)
	for i as integer = 0 to ubound(ant)
		dim as ulong c
		select case ant(i).task
		case FROZEN : c = C_DGREY
		case SEARCH_FOOD : c = C_RED
		case GOTO_NEST : c = C_BLUE
		case GOTO_FOOD : c = C_DGREEN
		end select
		drawAnt(ant(i).x, ant(i).y, c)
	next
end sub

'-------------------------------------------------------------------------------

type nest_type
	dim as integer x, y 'position on map
end type

dim as nest_type nest(NUM_NEST - 1)

'-------------------------------------------------------------------------------

type move_type
	dim as integer dx, dy
end type

'delta move: up, right, down, left
dim as move_type move(0 to 3) = {(0,-1), (+1,0), (0,+1), (-1,0)}

'-------------------------------------------------------------------------------

sub setFoodRnd(map() as cell_type)
	tryAgain:
	dim as integer x = int((rnd * 0.8 + 0.1) * MAP_W)
	dim as integer y = int((rnd * 0.8 + 0.1) * MAP_H)
	if map(x, y).t = T_NEST then goto tryAgain 'don't erase nest entrace
	map(x, y).t = T_FOOD
	map(x, y).fhm = 0 'distance to food is zero
	map(x, y).fu = int(rnd * (MAX_FOOD_UNITS - MIN_FOOD_UNITS)) + MIN_FOOD_UNITS
end sub

sub updateFood(map() as cell_type, x as integer, y as integer, byref found as integer)
	found += 1
	map(x, y).fu -= 1 'reduce food unit count
	if map(x, y).fu = 0 then 'food depleted
		map(x, y).fhm = MAX_HM
		map(x, y).t = T_DIRT
		setFoodRnd(map()) 'add a new food location
	end if
end sub

'-------------------------------------------------------------------------------

randomize timer '7878 'timer

 'set obstacles on map
setRndPos(map(), NUM_OBST, T_OBST, 0.95)

'set heat maps: nest & food to 'undiscovered' / far away
for x as integer = 0 to ubound(map, 1)
	for y as integer = 0 to ubound(map, 2)
		map(x, y).nhm = MAX_HM
		map(x, y).fhm = MAX_HM
	next
next

'set food positions
for i as integer = 0 to NUM_FOOD - 1
	setFoodRnd(map())
next

'set nest locations
for i as integer = 0 to ubound(nest)
	nest(i).x = int((rnd * 0.6 + 0.2) * MAP_W)
	nest(i).y = int((rnd * 0.6 + 0.2) * MAP_H)
	with map(nest(i).x, nest(i).y)
		.t = T_NEST 'mark on map
		.nhm = 0 'distance to nest is zero
	end with
next

'set ant start position to a nest entrance location
for i as integer = 0 to ubound(ant)
	dim as integer iNest = int(rnd * NUM_NEST)
	ant(i).x = nest(iNest).x
	ant(i).y = nest(iNest).y
	ant(i).task = SEARCH_FOOD
next

'main loop
dim as integer showNhm = false, showFhm = false
dim as string key
dim as integer i, steps = 0, found = 0, collected = 0
do
	'display stuff
	screenlock
	if showNhm then
		drawMapNhm(map()) 'show nest heat map
	elseif showFhm then
		drawMapFhm(map()) 'show food heat map
	else
		drawMapT(map()) 'show normal surface map
	end if
	drawAnts(ant())
	draw string(10, 10), "<ESC> = exit, <N> nest heat map, <F> food heat map", C_LGREEN
	draw string(10, 30), "Steps: " + str(steps), C_LGREEN
	draw string(10, 50), "Found: " + str(found), C_LGREEN
	draw string(10, 70), "Collected: " + str(collected), C_LGREEN
	screenunlock
	
	'fade out food trails
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
		'fhm: 0 = food, MAX_HM = no trail
			if map(x, y).fhm < MAX_HM then 'filter out most cases
				if map(x, y).fhm > 0 then
					map(x, y).fhm += 1
				end if
			end if
		next
	next
	
	'update ant positions and tasks
	for i = 0 to ubound(ant)
		with ant(i)
			select case .task
			case FROZEN
				'nothing
			case SEARCH_FOOD 'red ant
				dim as integer oldx = .x
				dim as integer oldy = .y
				dim as integer antdir = int(rnd * 4)
				.x += move(antdir).dx
				.y += move(antdir).dy
				dim as integer cellValue = getCell(map(), .x, .y)
				if cellValue = T_OBST or cellValue = T_NONE then
					.x = oldx 'reset position
					.y = oldy
				else 'step done
					dim as integer prevNestDist = map(oldx, oldy).nhm 'get prev. distance to nest
					dim as integer thisNestDist = map(.x, .y).nhm
					if thisNestDist < prevNestDist then
						'implies already visited location & shorter path found, closer to nest
						map(oldx, oldy).nhm = thisNestDist + 1
					else
						'normal step, further away from nest
						map(.x, .y).nhm = prevNestDist + 1
					end if
				end if
				'check for food trail, follow if found, gather food if on food
				if map(.x, .y).fhm < MAX_HM then
					if cellValue = T_FOOD then 'on food
						.task = GOTO_NEST
						updateFood(map(), .x, .y, found)
					else
						.task = GOTO_FOOD
					end if
				end if
			case GOTO_NEST 'blue ant
				dim as integer antdir = -1 'invalid direction
				dim as integer bestNestDist = map(.x, .y).nhm 'current position
				dim as integer tryx, tryy
				for i as integer = 0 to 3 'loop directions
					tryx = .x + move(i).dx
					tryy = .y + move(i).dy
					dim as integer cellValue = getCell(map(), tryx, tryy)
					if cellValue <> T_OBST and cellValue <> T_NONE then 'can go there
						if map(tryx, tryy).nhm < bestNestDist then
							antdir = i
							bestNestDist = map(tryx, tryy).nhm
						end if
					end if
				next
				if antdir <> -1 then 'update ant position
					dim as integer prevFoodDist = map(.x, .y).fhm 'previous distance to food
					.x += move(antdir).dx
					.y += move(antdir).dy
					map(.x, .y).fhm = prevFoodDist + 1 '1 step further away from food now
				else
					'.task = FROZEN
					'lost path to nest, mark spot as 'not so good'
					map(.x, .y).nhm += 1
				end if
				'if nest reached, goto_food
				if map(.x, .y).t = T_NEST then
					collected += 1
					.task = GOTO_FOOD
				end if
			case GOTO_FOOD 'green ant
				dim as integer antdir = -1 'invalid direction
				dim as integer bestFoodDist = map(.x, .y).fhm 'current position
				dim as integer tryx, tryy
				for i as integer = 0 to 3 'loop directions
					tryx = .x + move(i).dx
					tryy = .y + move(i).dy
					dim as integer cellValue = getCell(map(), tryx, tryy)
					if cellValue <> T_OBST and cellValue <> T_NONE then 'can go there
						if map(tryx, tryy).fhm < bestFoodDist then
							antdir = i
							bestFoodDist = map(tryx, tryy).fhm
						end if
					end if
				next
				if antdir <> -1 then 'update ant position
					.x += move(antdir).dx
					.y += move(antdir).dy
				else
					'stuck, path/food lost, do random walk search
					.task = SEARCH_FOOD
					'map(.x, .y).fhm = MAX_HM 'destroy food trail (last part)
				end if
				if map(.x, .y).t = T_FOOD then
					.task = GOTO_NEST
					updateFood(map(), .x, .y, found)
				end if
			end select
		end with
	next
	
	sleep 50,0 'shorter sleep to speed up
	steps += 1
	key = inkey
	if multikey(FB.SC_N) then showNhm = true else showNhm = false
	if multikey(FB.SC_F) then showFhm = true else showFhm = false
loop while not multikey(FB.SC_ESCAPE)
print "End"
getkey()
Update: But blue ants still can getting stuck,should be fixed now.
@BasicCoder2: Yes, this is turning into a project, but a bit late now to start a new topic and/or move it all.
neil
Posts: 594
Joined: Mar 17, 2022 23:26

Re: Langton's ant

Post by neil »

@badidea
It's getting there. The only thing it's missing is the Aardvark.
https://en.wikipedia.org/wiki/Aardvark
badidea
Posts: 2592
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Langton's ant

Post by badidea »

No aardvarks or anteaters yet, but you can now annoy the ants by pressing "r" to randomly relocated an nest entrance.

Code: Select all

'https://www.freebasic.net/forum/viewtopic.php?p=301888#p301888
'An ant colony food finding simulation.
'Like here: https://onestepcode.com/ant-colony-simulation/
'Where ants:
'- search for food locations - done
'- find the way back to the nest with the food - done
'- mark a path to the food for others - done
'- switch to different location when the food runs out - done
'- pick up food trace during random walk - done
'- optimize the path - todo

'TODO:
'- implement Langton's ant movement instead of random walk? Or basiccoder2 algorithm.
'- teleport ants to other nest when entering nest, after delay (based on nest distance)
'- put stuff into functions like try_move?

#include "fbgfx.bi"

const NUM_ANT = 30
const NUM_NEST = 3 'nest entrances
const NUM_FOOD = 10
const NUM_OBST = 1000 'obstacles / rocks

const MIN_FOOD_UNITS = 10
const MAX_FOOD_UNITS = 50

const MAX_HM = 500 'for heat map
'lower for faster decay of path
'but limits max path length as well
'and it can cause food paths to farther away source to disappear, due to preffred for nearby sources

#define hRGB(rgb_) ((((((rgb_) and &hF00) shl 4) or ((rgb_) and &hF00)) shl 8) or (((((rgb_) and &h0F0) shl 4) or ((rgb_) and &h0F0)) shl 4) or ((((rgb_) and &h00F) shl 4) or ((rgb_) and &h00F)))

'define colors
const as ulong C_BLACK  = hRGB(&h000) 'nest
const as ulong C_YELLOW = hRGB(&hFF0) 'food
const as ulong C_RED    = hRGB(&hF00) 'ant random food search
const as ulong C_LGREEN = hRGB(&h0F0) 'text
const as ulong C_DGREEN = hRGB(&h080) 'ant to food
const as ulong C_BLUE   = hRGB(&h22F) 'ant to nest
const as ulong C_BROWN  = hRGB(&h532) 'dirt
const as ulong C_LGREY  = hRGB(&h777) 'rock / obstacle
const as ulong C_DGREY  = hRGB(&h333) 'dead ant

'map cell types
const T_NONE = -1 'out of bounds
const T_DIRT = 0
const T_NEST = 1 'enty location to nest
const T_FOOD = 2
const T_OBST = 3 'obstacle

dim shared as ulong cellColor(0 to 3) = {C_BROWN, C_BLACK, C_YELLOW, C_LGREY}

type cell_type
	dim as integer t 'type
	dim as integer nhm 'nest heat map
	dim as integer fhm 'food heat map
	dim as integer fu 'food units
end type

'define map
const MAP_W = 100, MAP_H = 80
dim as cell_type map(MAP_W - 1, MAP_H - 1)

'define screen
const DRAW_SCALE = 8
const SCRN_W = MAP_W * DRAW_SCALE, SCRN_H = MAP_H * DRAW_SCALE
screenres SCRN_W, SCRN_H, 32
width SCRN_W \ 8, SCRN_H \ 16

'-------------------------------------------------------------------------------

'draw map cell (right = x+, down = y+)
sub drawCell(x as integer, y as integer, c as ulong)
	line(x * DRAW_SCALE, y * DRAW_SCALE)-_
	step(DRAW_SCALE - 1, DRAW_SCALE - 1), c, bf
end sub

'draw entire map, cell type values
sub drawMapT(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer cellType = map(x, y).t
			if cellType = T_FOOD then
				'less bright is less food, range 100...255
				dim as integer foodUnits = map(x, y).fu
				dim as integer intensity =  100 + (155 * foodUnits) / MAX_FOOD_UNITS
				dim as ulong c = rgb(intensity, intensity, 0)
				drawCell(x, y, c)
			else
				drawCell(x, y, cellColor(cellType))
			end if
		next
	next
end sub

const MIN_INTENSITY = 20 'minimum visible on screen?

'draw nest heat map
sub drawMapNhm(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer value = map(x, y).nhm
			dim as ulong c = 0
			if value <> MAX_HM then
				dim as integer intensity = 255 - value
				if intensity < MIN_INTENSITY then intensity = MIN_INTENSITY
				c = rgb(intensity, intensity, intensity)
			end if
			drawCell(x, y, c)
		next
	next
end sub

'draw food heat map
sub drawMapFhm(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer value = map(x, y).fhm
			dim as ulong c = 0
			if value < MAX_HM then
				dim as integer intensity = 255 - value
				if intensity < MIN_INTENSITY then intensity = MIN_INTENSITY
				c = rgb(intensity, intensity, intensity)
			end if
			drawCell(x, y, c)
		next
	next
end sub

'set 'count' random locations to type,
'but not to close to border of map, determined by f
'e.g. (rnd * 0.8 + 0.1) => 0.1....0.9 range for f = 0.8
sub setRndPos(map() as cell_type, count as integer, cellType as integer, f as double)
	dim as double f2 = (1 - f) / 2
	for i as integer = 1 to count
		dim as integer x = int((rnd * f + f2) * ubound(map, 1))
		dim as integer y = int((rnd * f + f2) * ubound(map, 2))
		map(x, y).t = cellType
	next
end sub

function getCell(map() as cell_type, x as integer, y as integer) as integer
	if x < lbound(map, 1) then return T_NONE
	if x > ubound(map, 1) then return T_NONE
	if y < lbound(map, 2) then return T_NONE
	if y > ubound(map, 2) then return T_NONE
	return map(x, y).t
end function

'-------------------------------------------------------------------------------

'ant tasks
const FROZEN = 0 'or dead
const SEARCH_FOOD = 1 'random walk?
const GOTO_NEST = 2
const GOTO_FOOD = 3

type ant_type
	dim as integer x, y 'position on map
	dim as integer task
end type

dim as ant_type ant(NUM_ANT - 1)

'smaller then cell
sub drawAnt(x as integer, y as integer, c as ulong)
	line(x * DRAW_SCALE + 1, y * DRAW_SCALE + 1)-_
	step(DRAW_SCALE - 3, DRAW_SCALE - 3), c, bf
end sub

sub drawAnts(ant() as ant_type)
	for i as integer = 0 to ubound(ant)
		dim as ulong c
		select case ant(i).task
		case FROZEN : c = C_DGREY
		case SEARCH_FOOD : c = C_RED
		case GOTO_NEST : c = C_BLUE
		case GOTO_FOOD : c = C_DGREEN
		end select
		drawAnt(ant(i).x, ant(i).y, c)
	next
end sub

'-------------------------------------------------------------------------------

type nest_type
	dim as integer x, y 'position on map
end type

sub setNestPos(map() as cell_type, nest() as nest_type, i as integer) 'i = next index
	tryAgain:
	dim as integer x = int((rnd * 0.6 + 0.2) * MAP_W)
	dim as integer y = int((rnd * 0.6 + 0.2) * MAP_H)
	if map(x, y).t <> T_DIRT then goto tryAgain
	nest(i).x = x
	nest(i).y = y
	map(x, y).t = T_NEST 'mark on map
	map(x, y).nhm = 0 'distance to nest is zero
end sub

dim as nest_type nest(NUM_NEST - 1)

'-------------------------------------------------------------------------------

type move_type
	dim as integer dx, dy
end type

'delta move: up, right, down, left
dim as move_type move(0 to 3) = {(0,-1), (+1,0), (0,+1), (-1,0)}

'-------------------------------------------------------------------------------

sub setFoodRnd(map() as cell_type)
	tryAgain:
	dim as integer x = int((rnd * 0.8 + 0.1) * MAP_W)
	dim as integer y = int((rnd * 0.8 + 0.1) * MAP_H)
	if map(x, y).t <> T_DIRT then goto tryAgain 'don't erase other stuff
	map(x, y).t = T_FOOD
	map(x, y).fhm = 0 'distance to food is zero
	map(x, y).fu = int(rnd * (MAX_FOOD_UNITS - MIN_FOOD_UNITS)) + MIN_FOOD_UNITS
end sub

sub updateFood(map() as cell_type, x as integer, y as integer, byref found as integer)
	found += 1
	map(x, y).fu -= 1 'reduce food unit count
	if map(x, y).fu = 0 then 'food depleted
		map(x, y).fhm = MAX_HM
		map(x, y).t = T_DIRT
		setFoodRnd(map()) 'add a new food location
	end if
end sub

'-------------------------------------------------------------------------------

randomize timer '7878 'timer

 'set obstacles on map
setRndPos(map(), NUM_OBST, T_OBST, 0.95)

'set heat maps: nest & food to 'undiscovered' / far away
for x as integer = 0 to ubound(map, 1)
	for y as integer = 0 to ubound(map, 2)
		map(x, y).nhm = MAX_HM
		map(x, y).fhm = MAX_HM
	next
next

'set food positions
for i as integer = 0 to NUM_FOOD - 1
	setFoodRnd(map())
next

'set nest locations
for i as integer = 0 to ubound(nest)
	setNestPos(map(), nest(), i)
next

'set ant start position to a nest entrance location
for i as integer = 0 to ubound(ant)
	dim as integer iNest = int(rnd * NUM_NEST)
	ant(i).x = nest(iNest).x
	ant(i).y = nest(iNest).y
	ant(i).task = SEARCH_FOOD
next

'main loop
dim as integer showNhm = false, showFhm = false
dim as string key
dim as integer steps = 0, found = 0, collected = 0
do
	'display stuff
	screenlock
	if showNhm then
		drawMapNhm(map()) 'show nest heat map
	elseif showFhm then
		drawMapFhm(map()) 'show food heat map
	else
		drawMapT(map()) 'show normal surface map
	end if
	drawAnts(ant())
	draw string(10, 10), "<ESC> = exit, <N> nest heat map, <F> food heat map, <R> relocate a nest entrance", C_LGREEN
	draw string(10, 30), "Steps: " + str(steps), C_LGREEN
	draw string(10, 50), "Found: " + str(found), C_LGREEN
	draw string(10, 70), "Collected: " + str(collected), C_LGREEN
	screenunlock
	
	'fade out food trails
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			'fhm: 0 = food, MAX_HM = no trail
			if map(x, y).fhm < MAX_HM then 'filter out most cases
				if map(x, y).fhm > 0 then
					map(x, y).fhm += 1
				end if
			end if
		next
	next

	'fade out nest heat map
	if steps mod 10 = 0 then
		for x as integer = 0 to ubound(map, 1)
			for y as integer = 0 to ubound(map, 2)
				'nhm: 0 = nest, MAX_HM = nothing
				if map(x, y).nhm < MAX_HM then
					if map(x, y).nhm > 0 then
						map(x, y).nhm += 1
					end if
				end if
			next
		next
	end if
	
	'update ant positions and tasks
	for i as integer = 0 to ubound(ant)
		with ant(i)
			select case .task
			case FROZEN
				'nothing
			case SEARCH_FOOD 'red ant
				dim as integer oldx = .x
				dim as integer oldy = .y
				dim as integer antdir = int(rnd * 4)
				.x += move(antdir).dx
				.y += move(antdir).dy
				dim as integer cellValue = getCell(map(), .x, .y)
				if cellValue = T_OBST or cellValue = T_NONE then
					.x = oldx 'reset position
					.y = oldy
				else 'step done
					dim as integer prevNestDist = map(oldx, oldy).nhm 'get prev. distance to nest
					dim as integer thisNestDist = map(.x, .y).nhm
					if thisNestDist < prevNestDist then
						'implies already visited location & shorter path found, closer to nest
						if map(oldx, oldy).nhm > 0 then map(oldx, oldy).nhm = thisNestDist + 1
					else
						'normal step, further away from nest
						if map(.x, .y).nhm > 0 then map(.x, .y).nhm = prevNestDist + 1
					end if
				end if
				'check for food trail, follow if found, gather food if on food
				if map(.x, .y).fhm < MAX_HM then
					if cellValue = T_FOOD then 'on food
						.task = GOTO_NEST
						updateFood(map(), .x, .y, found)
					else
						.task = GOTO_FOOD
					end if
				end if
			case GOTO_NEST 'blue ant
				dim as integer antdir = -1 'invalid direction
				dim as integer bestNestDist = map(.x, .y).nhm 'current position
				dim as integer tryx, tryy
				for i as integer = 0 to 3 'loop directions
					tryx = .x + move(i).dx
					tryy = .y + move(i).dy
					dim as integer cellValue = getCell(map(), tryx, tryy)
					if cellValue <> T_OBST and cellValue <> T_NONE then 'can go there
						if map(tryx, tryy).nhm < bestNestDist then
							antdir = i
							bestNestDist = map(tryx, tryy).nhm
						end if
					end if
				next
				if antdir <> -1 then 'update ant position
					dim as integer prevFoodDist = map(.x, .y).fhm 'previous distance to food
					.x += move(antdir).dx
					.y += move(antdir).dy
					if map(.x, .y).fhm > 0 then
						map(.x, .y).fhm = prevFoodDist + 1 '1 step further away from food now
					end if
				else
					'lost path to nest, mark spot as 'not so good'
					if map(.x, .y).nhm < MAX_HM then
						if map(.x, .y).nhm > 0 then map(.x, .y).nhm += 1
					else
						'totally lost
						'.task = FROZEN 'problem! Do random move?
						.task = SEARCH_FOOD 'discard current food unit, find new food location
					end if
				end if
				'if nest reached, goto_food
				if map(.x, .y).t = T_NEST then
					collected += 1
					.task = GOTO_FOOD
				end if
			case GOTO_FOOD 'green ant
				dim as integer antdir = -1 'invalid direction
				dim as integer bestFoodDist = map(.x, .y).fhm 'current position
				dim as integer tryx, tryy
				for i as integer = 0 to 3 'loop directions
					tryx = .x + move(i).dx
					tryy = .y + move(i).dy
					dim as integer cellValue = getCell(map(), tryx, tryy)
					if cellValue <> T_OBST and cellValue <> T_NONE then 'can go there
						if map(tryx, tryy).fhm < bestFoodDist then
							antdir = i
							bestFoodDist = map(tryx, tryy).fhm
						end if
					end if
				next
				if antdir <> -1 then 'update ant position
					dim as integer prevNestDist = map(.x, .y).nhm 'previous distance to nest
					.x += move(antdir).dx
					.y += move(antdir).dy
					if map(.x, .y).nhm > 0 then 'don't delete nhm of a nest
						map(.x, .y).nhm = prevNestDist + 1 '1 step further away from nest now
					end if
				else
					'stuck, path/food lost, do random walk search
					.task = SEARCH_FOOD
					'map(.x, .y).fhm = MAX_HM 'destroy food trail (last part)
				end if
				if map(.x, .y).t = T_FOOD then
					.task = GOTO_NEST
					updateFood(map(), .x, .y, found)
				end if
			end select
		end with
	next
	
	sleep 50,0 'shorter sleep to speed up
	steps += 1
	key = inkey
	if ucase(key) = "R" then 'move a nest entrance
		dim as integer i = int(rnd * NUM_NEST)
		with map(nest(i).x, nest(i).y) 'erase entrance
			.t = T_DIRT 'into dirt
			.nhm = MAX_HM 'distance to nest max value
		end with
		setNestPos(map(), nest(), i) 'assign new location
	end if
	if multikey(FB.SC_N) then showNhm = true else showNhm = false
	if multikey(FB.SC_F) then showFhm = true else showFhm = false
loop while not multikey(FB.SC_ESCAPE)
print "End"
getkey()
badidea
Posts: 2592
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: Langton's ant

Post by badidea »

Ok, final update, I think. The project is getting boring.
I included Langton's ant movement and BasicCoder2 ant movement as an alternative for the random walk during food search.
Set WALK_METHOD at line 34 to WALK_RANDOM or WALK_BC2 or WALK_LANGTON

Code: Select all

'https://www.freebasic.net/forum/viewtopic.php?p=301888#p301888
'An ant colony food finding simulation.
'Like here: https://onestepcode.com/ant-colony-simulation/
'Where ants:
'- search for food locations - done
'- find the way back to the nest with the food - done
'- mark a path to the food for others - done
'- switch to different location when the food runs out - done
'- pick up food trace during random walk - done
'- optimize the path - todo

'TODO:
'- teleport ants to other nest when entering nest, after delay (based on nest distance)

#include "fbgfx.bi"

const NUM_ANT = 30
const NUM_NEST = 3 'nest entrances
const NUM_FOOD = 10
const NUM_OBST = 1000 'obstacles / rocks

const MIN_FOOD_UNITS = 10
const MAX_FOOD_UNITS = 50

const MAX_HM = 500 'for heat map
'lower for faster decay of path
'but limits max path length as well
'and it can cause food paths to farther away source to disappear, due to preffred for nearby sources

const WALK_RANDOM = 0
const WALK_BC2 = 1
const WALK_LANGTON = 2

const WALK_METHOD = WALK_LANGTON 'select random walk algorithm, from list above

#define hRGB(rgb_) ((((((rgb_) and &hF00) shl 4) or ((rgb_) and &hF00)) shl 8) or (((((rgb_) and &h0F0) shl 4) or ((rgb_) and &h0F0)) shl 4) or ((((rgb_) and &h00F) shl 4) or ((rgb_) and &h00F)))

'define colors
const as ulong C_WHITE  = hRGB(&hFFF) '
const as ulong C_BLACK  = hRGB(&h000) 'nest
const as ulong C_YELLOW = hRGB(&hFF0) 'food
const as ulong C_RED    = hRGB(&hF00) 'ant random food search
const as ulong C_LGREEN = hRGB(&h0F0) 'text
const as ulong C_DGREEN = hRGB(&h080) 'ant to food
const as ulong C_BLUE   = hRGB(&h22F) 'ant to nest
const as ulong C_BROWN  = hRGB(&h532) 'dirt
const as ulong C_LGREY  = hRGB(&h777) 'rock / obstacle
const as ulong C_DGREY  = hRGB(&h333) 'dead ant

'map cell types
const T_NONE = -1 'out of bounds
const T_DIRT = 0
const T_NEST = 1 'enty location to nest
const T_FOOD = 2
const T_OBST = 3 'obstacle

dim shared as ulong cellColor(0 to 3) = {C_BROWN, C_BLACK, C_YELLOW, C_LGREY}

type cell_type
	dim as integer t 'type
	dim as integer nhm 'nest heat map
	dim as integer fhm 'food heat map
	dim as integer fu 'food units
	dim as integer bw 'black or white, for langtons ant algoritm
end type

'define map
const MAP_W = 100, MAP_H = 80
dim as cell_type map(MAP_W - 1, MAP_H - 1)

'define screen
const DRAW_SCALE = 8
const SCRN_W = MAP_W * DRAW_SCALE, SCRN_H = MAP_H * DRAW_SCALE
screenres SCRN_W, SCRN_H, 32
width SCRN_W \ 8, SCRN_H \ 16

'-------------------------------------------------------------------------------

type move_type
	dim as integer dx, dy
end type

'delta move: up, right, down, left
dim shared as move_type move(0 to 3) = {(0,-1), (+1,0), (0,+1), (-1,0)}

'-------------------------------------------------------------------------------

'ant tasks
const FROZEN = 0 'or dead
const SEARCH_FOOD = 1 'random walk?
const GOTO_NEST = 2
const GOTO_FOOD = 3

type ant_type
	dim as integer x, y 'position on map
	dim as integer dir_ '0 = up, 1 = right, 2 = down, 3 = left
	dim as integer task
	declare sub turn(rirection as integer)
end type

'-1 = trun left, +1 turn right
sub ant_type.turn(turnDir as integer)
	select case turnDir
	case -1 'left
		this.dir_ -= 1
		if this.dir_ < 0 then this.dir_ = 3
	case +1 'right
		this.dir_ += 1
		if this.dir_ > 3 then this.dir_ = 0
	end select
end sub

dim as ant_type ant(NUM_ANT - 1)

'smaller then cell
sub drawAnt(x as integer, y as integer, c as ulong)
	line(x * DRAW_SCALE + 1, y * DRAW_SCALE + 1)-_
	step(DRAW_SCALE - 3, DRAW_SCALE - 3), c, bf
end sub

sub drawAnts(ant() as ant_type)
	for i as integer = 0 to ubound(ant)
		dim as ulong c
		select case ant(i).task
		case FROZEN : c = C_DGREY
		case SEARCH_FOOD : c = C_RED
		case GOTO_NEST : c = C_BLUE
		case GOTO_FOOD : c = C_DGREEN
		end select
		drawAnt(ant(i).x, ant(i).y, c)
	next
end sub

'-------------------------------------------------------------------------------

'draw map cell (right = x+, down = y+)
sub drawCell(x as integer, y as integer, c as ulong)
	line(x * DRAW_SCALE, y * DRAW_SCALE)-_
	step(DRAW_SCALE - 1, DRAW_SCALE - 1), c, bf
end sub

'draw entire map, cell type values
sub drawMapT(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer cellType = map(x, y).t
			if cellType = T_FOOD then
				'less bright is less food, range 100...255
				dim as integer foodUnits = map(x, y).fu
				dim as integer intensity =  100 + (155 * foodUnits) / MAX_FOOD_UNITS
				dim as ulong c = rgb(intensity, intensity, 0)
				drawCell(x, y, c)
			else
				drawCell(x, y, cellColor(cellType))
			end if
		next
	next
end sub

const MIN_INTENSITY = 20 'minimum visible on screen?

'draw nest heat map
sub drawMapNhm(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer value = map(x, y).nhm
			dim as ulong c = 0
			if value <> MAX_HM then
				dim as integer intensity = 255 - value
				if intensity < MIN_INTENSITY then intensity = MIN_INTENSITY
				c = rgb(intensity, intensity, intensity)
			end if
			drawCell(x, y, c)
		next
	next
end sub

'draw food heat map
sub drawMapFhm(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as integer value = map(x, y).fhm
			dim as ulong c = 0
			if value < MAX_HM then
				dim as integer intensity = 255 - value
				if intensity < MIN_INTENSITY then intensity = MIN_INTENSITY
				c = rgb(intensity, intensity, intensity)
			end if
			drawCell(x, y, c)
		next
	next
end sub

'draw food heat map
sub drawMapLbw(map() as cell_type)
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			dim as ulong c = C_WHITE
			if map(x, y).bw = 1 then c = C_BLACK
			drawCell(x, y, c)
		next
	next
end sub

'set 'count' random locations to type,
'but not to close to border of map, determined by f
'e.g. (rnd * 0.8 + 0.1) => 0.1....0.9 range for f = 0.8
sub setRndPos(map() as cell_type, count as integer, cellType as integer, f as double)
	dim as double f2 = (1 - f) / 2
	for i as integer = 1 to count
		dim as integer x = int((rnd * f + f2) * ubound(map, 1))
		dim as integer y = int((rnd * f + f2) * ubound(map, 2))
		map(x, y).t = cellType
	next
end sub

function getCell(map() as cell_type, x as integer, y as integer) as integer
	if x < lbound(map, 1) then return T_NONE
	if x > ubound(map, 1) then return T_NONE
	if y < lbound(map, 2) then return T_NONE
	if y > ubound(map, 2) then return T_NONE
	return map(x, y).t
end function

'can go there?
function canWalk(map() as cell_type, x as integer, y as integer) as integer
	if x < lbound(map, 1) then return false
	if x > ubound(map, 1) then return false
	if y < lbound(map, 2) then return false
	if y > ubound(map, 2) then return false
	return iif(map(x, y).t = T_OBST, false, true)
	'dim as integer cellValue = getCell(map(), x, y)
	'return iif(cellValue <> T_OBST and cellValue <> T_NONE, true, false)
end function

sub walk(map() as cell_type, byref ant as ant_type)
	select case WALK_METHOD
	case WALK_RANDOM
		dim as integer antdir = int(rnd * 4) 'previous direction 'ant.dir_' irrelevant
	case WALK_BC2
		'BasicCoder2: 50% likely step forward, 25% veer left and 25% veer right
		dim as integer selection = int(rnd * 4)
		select case selection
		case 0, 1 'forward
			'ant.dir_ unchanged
		case 2 'turn left
			ant.turn(-1)
		case 3 'turn right
			ant.turn(+1)
		end select
	case WALK_LANGTON
		'At a white square, turn 90° clockwise, flip the color of the square, move forward one unit
		'At a black square, turn 90° counter-clockwise, flip the color of the square, move forward one unit
		'With odification: If blocked, do turn, don't flip (white = 0, black = 1)
		if map(ant.x, ant.y).bw = 0 then
			ant.turn(+1) 'turn right
		else
			ant.turn(-1) 'turn left
		end if
		if canWalk(map(), ant.x + move(ant.dir_).dx, ant.y + move(ant.dir_).dy) then
			map(ant.x, ant.y).bw xor= 1 'flip
		end if
	end select
	ant.x += move(ant.dir_).dx
	ant.y += move(ant.dir_).dy
end sub

'-------------------------------------------------------------------------------

type nest_type
	dim as integer x, y 'position on map
end type

sub setNestPos(map() as cell_type, nest() as nest_type, i as integer) 'i = next index
	tryAgain:
	dim as integer x = int((rnd * 0.6 + 0.2) * MAP_W)
	dim as integer y = int((rnd * 0.6 + 0.2) * MAP_H)
	if map(x, y).t <> T_DIRT then goto tryAgain
	nest(i).x = x
	nest(i).y = y
	map(x, y).t = T_NEST 'mark on map
	map(x, y).nhm = 0 'distance to nest is zero
end sub

dim as nest_type nest(NUM_NEST - 1)

'-------------------------------------------------------------------------------

sub setFoodRnd(map() as cell_type)
	tryAgain:
	dim as integer x = int((rnd * 0.8 + 0.1) * MAP_W)
	dim as integer y = int((rnd * 0.8 + 0.1) * MAP_H)
	if map(x, y).t <> T_DIRT then goto tryAgain 'don't erase other stuff
	map(x, y).t = T_FOOD
	map(x, y).fhm = 0 'distance to food is zero
	map(x, y).fu = int(rnd * (MAX_FOOD_UNITS - MIN_FOOD_UNITS)) + MIN_FOOD_UNITS
end sub

sub updateFood(map() as cell_type, x as integer, y as integer, byref found as integer)
	found += 1
	map(x, y).fu -= 1 'reduce food unit count
	if map(x, y).fu = 0 then 'food depleted
		map(x, y).fhm = MAX_HM
		map(x, y).t = T_DIRT
		setFoodRnd(map()) 'add a new food location
	end if
end sub

'-------------------------------------------------------------------------------

randomize timer '7878 'timer

 'set obstacles on map
setRndPos(map(), NUM_OBST, T_OBST, 0.95)

'set heat maps: nest & food to 'undiscovered' / far away
for x as integer = 0 to ubound(map, 1)
	for y as integer = 0 to ubound(map, 2)
		map(x, y).nhm = MAX_HM
		map(x, y).fhm = MAX_HM
	next
next

'set food positions
for i as integer = 0 to NUM_FOOD - 1
	setFoodRnd(map())
next

'set nest locations
for i as integer = 0 to ubound(nest)
	setNestPos(map(), nest(), i)
next

'set ant start position to a nest entrance location
for i as integer = 0 to ubound(ant)
	dim as integer iNest = int(rnd * NUM_NEST)
	ant(i).x = nest(iNest).x
	ant(i).y = nest(iNest).y
	ant(i).task = SEARCH_FOOD
next

'main loop
dim as integer showNhm = false, showFhm = false, showLbw = false
dim as string key
dim as integer steps = 0, found = 0, collected = 0
do
	'display stuff
	screenlock
	if showNhm then
		drawMapNhm(map()) 'show nest heat map
	elseif showFhm then
		drawMapFhm(map()) 'show food heat map
	elseif showLbw then
		drawMapLbw(map()) 'show langtons black white color
	else
		drawMapT(map()) 'show normal surface map
	end if
	drawAnts(ant())
	draw string(10, 10), "<ESC> = exit", C_LGREEN
	draw string(10, 30), "<N> nest heat map", C_LGREEN
	draw string(10, 50), "<F> food heat map", C_LGREEN
	draw string(10, 70), "<R> relocate a nest entrance", C_LGREEN
	draw string(10, 90), "<L> Langton color map", C_LGREEN
	draw string(10, 130), "Steps: " + str(steps), C_LGREEN
	draw string(10, 150), "Found: " + str(found), C_LGREEN
	draw string(10, 170), "Collected: " + str(collected), C_LGREEN
	screenunlock
	
	'fade out food trails
	for x as integer = 0 to ubound(map, 1)
		for y as integer = 0 to ubound(map, 2)
			'fhm: 0 = food, MAX_HM = no trail
			if map(x, y).fhm < MAX_HM then 'filter out most cases
				if map(x, y).fhm > 0 then
					map(x, y).fhm += 1
				end if
			end if
		next
	next

	'fade out nest heat map
	if steps mod 10 = 0 then
		for x as integer = 0 to ubound(map, 1)
			for y as integer = 0 to ubound(map, 2)
				'nhm: 0 = nest, MAX_HM = nothing
				if map(x, y).nhm < MAX_HM then
					if map(x, y).nhm > 0 then
						map(x, y).nhm += 1
					end if
				end if
			next
		next
	end if
	
	'update ant positions and tasks
	for i as integer = 0 to ubound(ant)
		with ant(i)
			select case .task
			case FROZEN
				'nothing
			'-------------------------------------------------------------------
			case SEARCH_FOOD 'red ant
			'-------------------------------------------------------------------
				dim as integer oldx = .x
				dim as integer oldy = .y
				walk(map(), ant(i)) 'updates .x, .y
				if canWalk(map(), .x, .y) then
					dim as integer prevNestDist = map(oldx, oldy).nhm 'get prev. distance to nest
					dim as integer thisNestDist = map(.x, .y).nhm
					if thisNestDist < prevNestDist then
						'implies already visited location & shorter path found, closer to nest
						if map(oldx, oldy).nhm > 0 then map(oldx, oldy).nhm = thisNestDist + 1
					else
						'normal step, further away from nest
						if map(.x, .y).nhm > 0 then map(.x, .y).nhm = prevNestDist + 1
					end if
				else 'reset position
					.x = oldx
					.y = oldy
				end if
				'check for food trail, follow if found, gather food if on food
				if map(.x, .y).fhm < MAX_HM then
					if map(.x, .y).t = T_FOOD then 'on food
						.task = GOTO_NEST
						updateFood(map(), .x, .y, found)
					else
						.task = GOTO_FOOD
					end if
				end if
			'-------------------------------------------------------------------
			case GOTO_NEST 'blue ant
			'-------------------------------------------------------------------
				dim as integer antdir = -1 'invalid direction
				dim as integer bestNestDist = map(.x, .y).nhm 'current position
				dim as integer tryx, tryy
				for i as integer = 0 to 3 'loop directions
					tryx = .x + move(i).dx
					tryy = .y + move(i).dy
					if canWalk(map(), tryx, tryy) then
						if map(tryx, tryy).nhm < bestNestDist then
							antdir = i
							bestNestDist = map(tryx, tryy).nhm
						end if
					end if
				next
				if antdir <> -1 then 'update ant position
					dim as integer prevFoodDist = map(.x, .y).fhm 'previous distance to food
					.x += move(antdir).dx
					.y += move(antdir).dy
					if map(.x, .y).fhm > 0 then
						map(.x, .y).fhm = prevFoodDist + 1 '1 step further away from food now
					end if
				else
					'lost path to nest, mark spot as 'not so good'
					if map(.x, .y).nhm < MAX_HM then
						if map(.x, .y).nhm > 0 then map(.x, .y).nhm += 1
					else
						'totally lost
						'.task = FROZEN 'problem! Do random move?
						.task = SEARCH_FOOD 'discard current food unit, find new food location
					end if
				end if
				'if nest reached, goto_food
				if map(.x, .y).t = T_NEST then
					collected += 1
					.task = GOTO_FOOD
				end if
			'-------------------------------------------------------------------
			case GOTO_FOOD 'green ant
			'-------------------------------------------------------------------
				dim as integer antdir = -1 'invalid direction
				dim as integer bestFoodDist = map(.x, .y).fhm 'current position
				dim as integer tryx, tryy
				for i as integer = 0 to 3 'loop directions
					tryx = .x + move(i).dx
					tryy = .y + move(i).dy
					if canWalk(map(), tryx, tryy) then
						if map(tryx, tryy).fhm < bestFoodDist then
							antdir = i
							bestFoodDist = map(tryx, tryy).fhm
						end if
					end if
				next
				if antdir <> -1 then 'update ant position
					dim as integer prevNestDist = map(.x, .y).nhm 'previous distance to nest
					.x += move(antdir).dx
					.y += move(antdir).dy
					if map(.x, .y).nhm > 0 then 'don't delete nhm of a nest
						map(.x, .y).nhm = prevNestDist + 1 '1 step further away from nest now
					end if
				else
					'stuck, path/food lost, do random walk search
					.task = SEARCH_FOOD
					'map(.x, .y).fhm = MAX_HM 'destroy food trail (last part)
				end if
				if map(.x, .y).t = T_FOOD then
					.task = GOTO_NEST
					updateFood(map(), .x, .y, found)
				end if
			end select
		end with
	next
	
	sleep 50,0 'shorter sleep to speed up
	steps += 1
	key = inkey
	if ucase(key) = "R" then 'move a nest entrance
		dim as integer i = int(rnd * NUM_NEST)
		with map(nest(i).x, nest(i).y) 'erase entrance
			.t = T_DIRT 'into dirt
			.nhm = MAX_HM 'distance to nest max value
		end with
		setNestPos(map(), nest(), i) 'assign new location
	end if
	if multikey(FB.SC_N) then showNhm = true else showNhm = false
	if multikey(FB.SC_F) then showFhm = true else showFhm = false
	if multikey(FB.SC_L) then showLbw = true else showLbw = false
loop while not multikey(FB.SC_ESCAPE)
print "End"
getkey()
neil
Posts: 594
Joined: Mar 17, 2022 23:26

Re: Langton's ant

Post by neil »

@badidea
Does this mean no Aardvark?
Interesting project. Well done.
Post Reply