How would you store/model this map?

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
User avatar
togFox
Party member
Posts: 835
Joined: Sat Jan 30, 2021 9:46 am
Location: Brisbane, Oztralia

How would you store/model this map?

Post by togFox »

I'd like to try to recreate a classic board game - Formula D. A key game mechanic on the board game is learning how to position your token (car) so you get the fast inside lane on corners. This leads to interesting game play but complex maps. Here is an example:

Image

You can see there are multiple lanes and you can move between lanes, the lanes are offset, and when you get to the corners there are restrictions on how and where you can change lanes.

I'd use a large image to draw the board/road/cells but how would I map all the possible moves from one 'cell' to the next? Do I simply number/label each cell behind the scenes and hand code each cell's permissible neighbor into a large table? In most cases, each cell has 2 or 3 permissible neighbors.

For drawing tokens, I guess each cell would have an x/y to mark the centre of the cell and then an orientation (radians). Any token sitting on any given cell could then be drawn on the screen in the right location with the correct facing.

Now that I've typed it out - it doesn't seem that hard. I guess large tracks = a lot of hand coding but for prototyping and play-testing I could start with small tracks.

Would you do this the same way?

For those that have played the classic board game, I hope to add a lot of meta to each car and driver. Finishing races = experience = upgrades and better performances etc. I could even go cyber-punk and put forward/rear facing weapons (more meta). Drivers retire over time meaning you need new drivers so the meta is always changing. Coding bots is always a challenge. I might make a basic machine-learning algorithm so they can teach themselves to drive. I've done that before and may do that again.

Thanks for reading!
Last project:
https://togfox.itch.io/hwarang
A card game that brings sword fighting to life.
Current project:
Idle gridiron. Set team orders then idle and watch: https://togfox.itch.io/pad-and-pencil-gridiron
User avatar
dusoft
Party member
Posts: 715
Joined: Fri Nov 08, 2013 12:07 am
Location: Europe usually
Contact:

Re: How would you store/model this map?

Post by dusoft »

Wouldn't this work with a standard grid map?

E.g. center "rectangle"/lane would be shorter being closer to the center point with outer lane longer by a couple of corner squares. Offsets are more tricky, but if you would use squares, then you could just have some squares merged to create a longer position (e.g. standard square = position, extended double square position, extended triple square position).

EDIT: I see that you want to have just different lengths in corners? If that's so, then disregard my example.
Attachments
1.png
1.png (5.1 KiB) Viewed 2216 times
User avatar
dusoft
Party member
Posts: 715
Joined: Fri Nov 08, 2013 12:07 am
Location: Europe usually
Contact:

Re: How would you store/model this map?

Post by dusoft »

And I would recommend some path finding such as this (although it might be overkill for this purpose):
https://github.com/Yonaba/Jumper
User avatar
darkfrei
Party member
Posts: 1216
Joined: Sat Feb 08, 2020 11:09 pm

Re: How would you store/model this map?

Post by darkfrei »

I see the trach as an object, that contains:
First: piece lengths as relative values>
line1 = {30, 40, 40,30}
line2 = {20, 40, 40, 40}
line3 = {10, 40, 40, 50}
where numbers are length of track pieces,

second: the array of bezier lines,

third: three arrays of transitions:
lane1 = {{right}, {right}, {right}, {right}}
lane2 = {{right, left}, {right}, {left}, {right, left}}
lane3 = {{left}, {left}, {left}, {left}}
(the strait direction is always enabled).
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
darkfrei
Party member
Posts: 1216
Joined: Sat Feb 08, 2020 11:09 pm

Re: How would you store/model this map?

Post by darkfrei »

Small level editor, but without export, sorry.
Maybe need some optimizations.

Code: Select all

-- bezier-roads
-- License cc0, darkfrei 2023

Active = {
	x=0, y=400, 
	angle = 0, 
--	type = "straight"
	xEnd = 0,
	yEnd = 0,
	line= {},
}

Track = {}


--------------------------------------------------------
--------------------- special functions ----------------
--------------------------------------------------------

function nearestPointToRay(mx, my, startX, startY, angle)
  local dx = mx - startX
  local dy = my - startY
  local s = math.sin(angle)
  local c = math.cos(angle)
  local rotatedX = dx * c + dy * s
  local rotatedY = dx * s - dy * c
  local t = math.max(rotatedX, 0)
  return startX + t * c, startY + t * s
end



function addStraightRoad (length)
	local x1, y1 = Active.x, Active.y
	local angle = Active.angle
	local x2 = x1 + length*math.cos(angle)
	local y2 = y1 + length*math.sin(angle)
	local line = {x1, y1, x2, y2}
--	local line = Active.line 
	local road = {line=line}
	table.insert (Track, road)
	-- new position
	Active.x, Active.y = x2, y2
--	State.updateLines ()
end

function addCurveRoad ()
	local road = {line=Active.line}
	table.insert (Track, road)
	Active.x, Active.y = Active.xEnd, Active.yEnd
	Active.angle = Active.nextAngle
end


function getIntersection(startX, startY, startAngle, endX, endY)
	local tangentX, tangentY = math.cos(startAngle), math.sin(startAngle)
	local midX, midY = (startX + endX) / 2, (startY + endY) / 2
	local rayAngle = math.atan2(midY - startY, midX - startX)
	local rayX, rayY = math.cos(rayAngle), math.sin(rayAngle)
	local t = ((midX - startX) * tangentX + (midY - startY) * tangentY) / (tangentX * rayX + tangentY * rayY)
	return startX + t * tangentX, startY + t * tangentY
end


--------------------------------------------------------
--------------------- state machine --------------------
--------------------------------------------------------

States = {
	straight = {name = "straight"},
	curve = {name = "curve"},
}
State = States.straight
--State = States.curve

function States.straight.updateLines ()
	local mx, my = love.mouse.getPosition ()
	local x, y = Active.x, Active.y
	local angle = Active.angle
	local xEnd = x + 1000*math.cos(angle)
	local yEnd = y + 1000*math.sin(angle)
-- ray to far point:
	Active.controlPoints = {x, y, xEnd, yEnd}
	xEnd, yEnd = nearestPointToRay(mx, my, x, y, angle)
	Active.xEnd, Active.yEnd = xEnd, yEnd
	Active.line = {x, y, xEnd, yEnd}
end

function States.straight.mousemoved (mx, my, dx, dy)
	States.straight.updateLines (mx, my)
end

function States.straight.mousepressed (mx, my)
	if Active.xEnd then
		local dx = Active.xEnd - Active.x
		local dy = Active.yEnd - Active.y
		local length = (dx*dx+dy*dy)^0.5
		if length > 10 then
			addStraightRoad (length)
			State = States.curve
			State.updateLines ()
		end
	end
end

function States.curve.updateLines ()
	local x, y = Active.x, Active.y
	local startAngle = Active.angle
--	local xEnd, yEnd = mx, my
	local mx, my = love.mouse.getPosition()
	local controlX, controlY = getIntersection(x, y, startAngle, mx, my)
	
	local endAngle = math.atan2(my - controlY, mx - controlX)
	local controlPoints = {x, y, controlX, controlY, mx, my}
	
	local bezierCurve = love.math.newBezierCurve( controlPoints )
	local line = bezierCurve:render()
	
	Active.line = line
	Active.controlPoints = controlPoints
	Active.nextAngle = endAngle
	Active.xMiddle, Active.yMiddle = controlX, controlY
	Active.xEnd, Active.yEnd = mx, my
end

function States.curve.mousemoved (mx, my, dx, dy)
	States.curve.updateLines ()
end

function States.curve.mousepressed (mx, my)
	if Active.xEnd then
		local dx = Active.xEnd - Active.x
		local dy = Active.yEnd - Active.y
		local length = (dx*dx+dy*dy)^0.5
		if length > 10 then
			addCurveRoad ()
			State.updateLines ()
		end
	end
end

function love.load ()
	addStraightRoad (10)
	State.updateLines ()
end

function love.draw ()
	love.graphics.setLineWidth (2)
	love.graphics.setColor (0.5,0.5,0)
	if Active.controlPoints then
		love.graphics.line (Active.controlPoints)
	end
	love.graphics.setColor (1,1,1)
	if #Active.line > 2 then
		love.graphics.line (Active.line)
	end

	
	
	love.graphics.setLineWidth (3)
	love.graphics.setColor (1,1,1)
	for i, road in ipairs (Track) do
		if road.line then
			love.graphics.line (road.line)
			love.graphics.circle ('line', road.line[1], road.line[2], 1)
		end
	end
	
	love.graphics.circle ('line', Active.x, Active.y, 1)
	
	if Active.xEnd then
		love.graphics.circle ('line', Active.xEnd, Active.yEnd, 4)
	end
	
	love.graphics.setColor (1,1,1)
	love.graphics.print ('State: '..State.name,0,0)
	love.graphics.print ('Press SPACE to change state',0,14)
	love.graphics.print ('Click mouse to add the point',0,28)
	love.graphics.print ('Press Esc to exit',0,42)
end




function love.mousemoved (mx, my, dx, dy)
	State.mousemoved (mx, my, dx, dy)
end

function love.mousepressed (mx, my)
	State.mousepressed (mx, my)
end


function save ()
	
end

function love.keypressed (key, scancode, isrepeat)
	if key == "escape" then
		save ()
		love.event.quit()
	elseif key == "space" then
		if State.name == "straight" then
			State = States.curve
		else
			State = States.straight
		end
		State.updateLines ()
	else
	end
end
Screenshot 2023-04-07 174251.png
Screenshot 2023-04-07 174251.png (28.79 KiB) Viewed 2108 times
Attachments
bezier-roads-01.love
(1.49 KiB) Downloaded 286 times
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
togFox
Party member
Posts: 835
Joined: Sat Jan 30, 2021 9:46 am
Location: Brisbane, Oztralia

Re: How would you store/model this map?

Post by togFox »

Thanks for the code! I can export the table with a few lines of code. :)
Last project:
https://togfox.itch.io/hwarang
A card game that brings sword fighting to life.
Current project:
Idle gridiron. Set team orders then idle and watch: https://togfox.itch.io/pad-and-pencil-gridiron
User avatar
darkfrei
Party member
Posts: 1216
Joined: Sat Feb 08, 2020 11:09 pm

Re: How would you store/model this map?

Post by darkfrei »

Updated version, press Ctrl+C to copy the track:

Code: Select all

-- bezier-roads, 15 pieces
{
	{0, 400, 10, 400}, -- line
	{10, 400, 92, 400}, -- line
	{92, 400, 133, 400, 163, 360}, -- bezier
	{163, 360, 186, 330, 186, 288}, -- bezier
	{186, 288, 186, 140}, -- line
	{186, 140, 186, 103, 231, 82}, -- bezier
	{231, 82, 264, 67, 302, 71}, -- bezier
	{302, 71, 634, 106}, -- line
	{634, 106, 667, 109, 690, 140}, -- bezier
	{690, 140, 714, 173, 716, 217}, -- bezier
	{716, 217, 726, 446}, -- line
	{726, 446, 728, 496, 667, 526}, -- bezier
	{667, 526, 616, 551, 561, 488}, -- bezier
	{561, 488, 531, 453, 474, 456}, -- bezier
	{474, 456, 15, 480}, -- line

}
Screenshot 2023-04-09 085300.png
Screenshot 2023-04-09 085300.png (27.03 KiB) Viewed 2036 times
Attachments
bezier-roads-02.love
(1.97 KiB) Downloaded 77 times
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
darkfrei
Party member
Posts: 1216
Joined: Sat Feb 08, 2020 11:09 pm

Re: How would you store/model this map?

Post by darkfrei »

The next example for other side of model:

You have a trach with lanes, the each lane has cells, that can be connected to other cells on other lanes.

Code: Select all

-- ladder-roads
-- license cc0, darkfrei 2023

Lanes = {}

function createLines (length, amount)
	local lane2 = {}
	for i = 1, amount do
		local cell = {length = length, right=true, left=true} 
		if love.math.random (3) == 1 then
			cell.right = false
		end
		if love.math.random (3) == 1 then
			cell.left = false
		end
		table.insert (lane2, cell)
	end
	
	local lane1 = {}
	local cell1 = {length = length/2, right=false, left=false} 
	table.insert (lane1, cell1)
	
	local lane3 = {}
	local cell3 = {length = length/2, right=false, left=false} 
	table.insert (lane3, cell3)
	
	for i = 2, amount do
		local cell1 = {length = length, right=true, left=false} 

		table.insert (lane1, cell1)
		local cell3 = {length = length, right=false, left=true} 
		table.insert (lane3, cell3)
	end
	
	cell1 = {length = length/2, right=false, left=false} 
	table.insert (lane1, cell1)
	cell3 = {length = length/2, right=false, left=false} 
	table.insert (lane3, cell3)
	
	print (#lane1, #lane2, #lane3)
	Lanes[1] = lane1
	Lanes[2] = lane2
	Lanes[3] = lane3
	
	
	local y0 = 100
	local dy = 40
	local h = dy
	for iLane = 1, #Lanes do
		local lane = Lanes[iLane]
		local x0 = 0
		for iCell = 1, #lane do
			local cell = lane[iCell]
			cell.iLane = iLane
			cell.iCell = iCell
			local x = x0
			local y = y0 + dy*(iLane-1)
			local w = cell.length
			cell.x=x -- x,y,w,h as rectangle
			cell.y=y
			cell.w=w
			cell.h=h
			cell.cx = x + w/2 -- middle
			cell.cy = y + h/2
			x0 = x0 + w
		end
	end
	
	-- transitions
	for iLane = 1, #Lanes do
		local lane = Lanes[iLane]
		for iCell = 1, #lane do
			local cell = lane[iCell]
			if cell.right then
				local rightIndex = iCell + (iLane+1)%2
				local rightCell = Lanes[iLane+1][rightIndex]
				cell.rightIndex = rightIndex
				cell.rightTransition = {cell.cx, cell.cy, rightCell.cx, rightCell.cy}
			end
			if cell.left then
				local leftIndex = iCell + (iLane+1)%2
				local leftCell = Lanes[iLane-1][leftIndex]
				cell.leftIndex = leftIndex
				cell.leftTransition = {cell.cx, cell.cy, leftCell.cx, leftCell.cy}
			end
		end
	end
end


function love.load ()
	createLines (60, 12)
end

function drawArrow (x1, y1, x2, y2)
	local dx = x2-x1
	local dy = y2-y1
--	local length = (dx*dx+dy*dy)^0.5
	
	love.graphics.line (x1, y1, x2, y2)
	love.graphics.line (x1+dx/2-dy/8, y1+dy/2+dx/8, x2, y2)
	love.graphics.line (x1+dx/2+dy/8, y1+dy/2-dx/8, x2, y2)
end

table.unpack = table.unpack or unpack

function isOn (mx, my, x, y, w, h)
	return mx > x and mx < x+w and my>y and my<y+h
end

function drawCell (cell)
	
	-- border
	love.graphics.rectangle ('line', cell.x, cell.y, cell.w, cell.h)
-- arrow
	love.graphics.line (cell.cx-cell.w/4, cell.cy, cell.cx, cell.cy)
	drawArrow (cell.cx, cell.cy, cell.cx+cell.w/2, cell.cy)
	
	-- transition
	if cell.rightTransition then
		local x1, y1, x2, y2 = table.unpack (cell.rightTransition)
		drawArrow (x1, y1, x1+(x2-x1)/2, y1+(y2-y1)/2)
	end
	if cell.leftTransition then
		local x1, y1, x2, y2 = table.unpack (cell.leftTransition)
		drawArrow (x1, y1, x1+(x2-x1)/2, y1+(y2-y1)/2)
	end
end

function love.draw ()
	local mx, my = love.mouse.getPosition ()
	
	local hoveredCell
	
	love.graphics.setColor (1,1,0)
		love.graphics.setLineWidth (0.5)
	for iLane, lane in ipairs (Lanes) do
		for iCell, cell in ipairs (lane) do
			if isOn (mx, my, cell.x, cell.y, cell.w, cell.h) then
				hoveredCell = cell 
			end
			drawCell (cell)
		end
	end
	
	if hoveredCell then
		love.graphics.setColor (1,1,1)
		love.graphics.setLineWidth (2)
		drawCell (hoveredCell)
		
		-- next cells as green
		love.graphics.setColor (0,1,0)
		local nextCell = Lanes[hoveredCell.iLane][hoveredCell.iCell+1]
		if nextCell then
			drawCell (nextCell)
		end
		if hoveredCell.right then
			local rightCell = Lanes[hoveredCell.iLane+1][hoveredCell.rightIndex]
			drawCell (rightCell)
		end
		if hoveredCell.left then
			local leftCell = Lanes[hoveredCell.iLane-1][hoveredCell.leftIndex]
			drawCell (leftCell)
		end
	end
end
Screenshot 2023-04-09 160951.png
Screenshot 2023-04-09 160951.png (12.45 KiB) Viewed 2008 times
Attachments
ladder-roads-01.love
(1.25 KiB) Downloaded 72 times
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
togFox
Party member
Posts: 835
Joined: Sat Jan 30, 2021 9:46 am
Location: Brisbane, Oztralia

Re: How would you store/model this map?

Post by togFox »

That last example is looking closer to the top screenshot. I'll try to adapt that. Thanks!
Last project:
https://togfox.itch.io/hwarang
A card game that brings sword fighting to life.
Current project:
Idle gridiron. Set team orders then idle and watch: https://togfox.itch.io/pad-and-pencil-gridiron
User avatar
darkfrei
Party member
Posts: 1216
Joined: Sat Feb 08, 2020 11:09 pm

Re: How would you store/model this map?

Post by darkfrei »

togFox wrote: Mon Apr 10, 2023 12:08 am That last example is looking closer to the top screenshot. I'll try to adapt that. Thanks!
You are need to use both of them: as source of lines and as a way to move connect them together:

Code: Select all

-- ladder-roads
-- license cc0, darkfrei 2023


TrackLine = 
-- bezier-roads, 11 pieces
{
	{0, 400, 10, 400}, -- line
	{10, 400, 123, 400, 203, 282}, -- bezier
	{203, 282, 279, 169, 452, 172}, -- bezier
	{452, 172, 518, 173, 569, 234}, -- bezier
	{569, 234, 625, 302}, -- line
	{625, 302, 664, 348, 639, 422}, -- bezier
	{639, 422, 610, 508, 472, 495}, -- bezier
	{472, 495, 407, 489, 359, 429}, -- bezier
	{359, 429, 306, 363, 189, 422}, -- bezier
	{189, 422, 136, 449, 73, 448}, -- bezier
	{73, 448, 4, 447}, -- line
}

-- restore bezier:
Line = {} -- the line as list of position pairs
for iRoad = 1, #TrackLine do
	local road = TrackLine[iRoad]
	if #road > 4 then -- bezier
		local bezierObj = love.math.newBezierCurve (road)
		local amount = 32
		for t = 0, amount-1 do -- don't add last point
			local x, y = bezierObj:evaluate (t/amount)
			table.insert (Line, x)
			table.insert (Line, y)
		end
	else
		for i = 1, #road-3, 2 do -- don't add last point
			table.insert (Line, road[i])
			table.insert (Line, road[i+1])
		end
	end
end


local function get_points_along_line (line, gap)
-- from https://github.com/darkfrei/love2d-lua-tests/blob/main/railway-track/railways.lua#L88
	local points = {}
	local tangents = {}
	local rest = gap/2 -- rest is gap to start point on this section
	local x1, y1, x2, y2, dx, dy = line[1],line[2]
	for i=3, #line-1, 2 do
		x2, y2 = line[i],line[i+1]
		dx, dy = x2-x1, y2-y1
		local sector_length = (dx*dx+dy*dy)^0.5
		if sector_length > rest then
			-- rest is always shorter than gap; sector is shorter than rest (or gap)
			dx, dy = dx/sector_length, dy/sector_length
			while sector_length > rest do
				local x, y = x1+rest*dx, y1+rest*dy
				table.insert (points, x)
				table.insert (points, y)
				table.insert (tangents, dx)
				table.insert (tangents, dy)
				rest = rest + gap
			end
		else -- no point in this distance
		end
		-- the tail for the next 
		rest = rest-sector_length
		x1, y1 = x2, y2
	end
	return points, tangents
end

--add  equidistant points
EquidistantPoints, Tangents = get_points_along_line (Line, 15)

function love.draw ()
	love.graphics.line (Line)
	for i = 1, #EquidistantPoints-1, 2 do
		love.graphics.circle ('line', EquidistantPoints[i], EquidistantPoints[i+1], 3)
	end
end
Screenshot 2023-04-10 184423.png
Screenshot 2023-04-10 184423.png (30.49 KiB) Viewed 1944 times
Attachments
ladder-roads-02.love
(1.05 KiB) Downloaded 251 times
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
Post Reply

Who is online

Users browsing this forum: Bing [Bot] and 4 guests