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:
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!
How would you store/model this map?
Forum rules
Before you make a thread asking for help, read this.
Before you make a thread asking for help, read this.
How would you store/model this map?
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
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
Re: How would you store/model this map?
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.
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.
My boat driving game demo: https://dusoft.itch.io/captain-bradley- ... itius-demo
Re: How would you store/model this map?
And I would recommend some path finding such as this (although it might be overkill for this purpose):
https://github.com/Yonaba/Jumper
https://github.com/Yonaba/Jumper
My boat driving game demo: https://dusoft.itch.io/captain-bradley- ... itius-demo
Re: How would you store/model this map?
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).
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).
Re: How would you store/model this map?
Small level editor, but without export, sorry.
Maybe need some optimizations.
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
- Attachments
-
- bezier-roads-01.love
- (1.49 KiB) Downloaded 286 times
Re: How would you store/model this map?
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
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
Re: How would you store/model this map?
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
}
- Attachments
-
- bezier-roads-02.love
- (1.97 KiB) Downloaded 77 times
Re: How would you store/model this map?
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.
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
- Attachments
-
- ladder-roads-01.love
- (1.25 KiB) Downloaded 72 times
Re: How would you store/model this map?
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
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
Re: How would you store/model this map?
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
- Attachments
-
- ladder-roads-02.love
- (1.05 KiB) Downloaded 251 times
Who is online
Users browsing this forum: Bing [Bot] and 4 guests