Log In  


Cart [#16969#] | Copy | Code | 2015-11-24 | Link

Cart [#16909#] | Copy | Code | 2015-11-23 | Link

Cart [#16877#] | Copy | Code | 2015-11-22 | Link

Not quite finished entry for http://midwestgamejam.org

left/right to steer, 'z' to draw in the sail, 'x' to let it out. Finish all the waypoints without running aground!

General Sailing tips:

Sailing with the wind is easy enough, just let the sail out all the way. You can get the most power this way, but you can't sail faster than the wind.

To sail perpendicular to the wind pull the sail in to about a 45 degree angle. This will give you a lot of power for acceleration. Then you can pull the sail in even more to trade power for speed. It's sort of like gears on a car, and you can sail much faster than the speed of the wind this way.

Lastly, you can't sail directly against the wind, but you can still sail well up to 45 degrees of the wind if you pull the sail in close. Then just zigzag back and forth across the wind. This is called tacking.

1.1 - Made tacking more forgiving and added red limit lines to make the sail mechanic more intuitive.

P#16878 2015-11-22 05:05


That's quite fun, and goes to show that I have very little understanding of sailing as I spend a minute trying to go right into the wind.

P#16916 2015-11-23 11:17


I can't see how to win... The wind never changes, and you don't keep your momentum...?

Aha! I finally got the Z/X buttons to do something! ... And I was going in the wrong direction! Okay.

P#16923 2015-11-23 12:17


I guess I should include some sailing tips along with the instructions! It's supposed to be mildly realistic, so it's probably not very intuitive if you haven't done it before. (Sorry :( )

Sailing with the wind is easy enough, just let the sail out all the way. You can get the most power this way, but you can't sail faster than the wind.

To sail perpendicular to the wind pull the sail in to about a 45 degree angle. This will give you a lot of power for acceleration. Then you can pull the sail in even more to trade power for speed. It's sort of like gears on a car, and you can sail much faster than the speed of the wind this way.

Lastly, you can't sail directly against the wind, but you can still sail well going NW or SW if you pull the sail in close. Then just zigzag back and forth. This is called tacking.

P#16925 2015-11-23 12:26


Coming into this already knowing how to sail a small boat, this game was not too difficult but it's great fun! <3 Hell it could even be a teaching tool. Of course it leaves out some crucial details, but how realistic is a Pico-8 sailing sim gonna be?

I was just thinking it would be cool to simulate the rudder, so that you see it turn either way. You could even make the boat steer faster or slower based on it's speed, but this would make the game harder. (not that I would mind xD)

EDIT: 161 seconds

love this game

P#16928 2015-11-23 13:46


Yeah. "mildly realistic" :D You do turn faster based on speed, but I don't simulate the center of drag or anything so you'd get stuck if there wasn't a minimum turning speed.

Glad you liked it! Maybe I should add rain somehow to it so I can enter into the Pico8 Jam too. heh.

My high score is 124 seconds, but I also played it far too many times. ;)

P#16930 2015-11-23 14:10


congrats on shipping this. REALLY great work.

P#16933 2015-11-23 14:41


That was surprisingly fun! Probably my favorite game with "simulator" in the (working) title.

I'm thinking of showing this to my father. Let's see if real world experience helps him.

I really like the little flapping of the sail when it's not catching wind, very nice touch.

P#16936 2015-11-23 15:46


This is awesome! I never thought about how sailing works and it was a blast figuring it out on this tiny simulation.

P#16942 2015-11-23 21:20


The landscapes are adorable.

P#16947 2015-11-24 00:30


So close to breaking 100 seconds. Bah!

P#17044 2015-11-25 16:45


I've been dreaming about this game for year. Thanks so much for making it. You really nailed making the controls feel good. If there was more to do in the world (Trading ports? Pirate ships?) I would definitely spend many many hour playing this. Really any story or procedural content could easily turn this from an arcade style game in to a much longer experience. Not that there's anything wrong with the arcade style.


P#17054 2015-11-25 20:08


Nice work! This is very pretty and really challenging, even with some sailing experience. Glad you don't have to make decisions that quickly in a real boat!

P#33565 2016-12-16 10:33


Thanks. :)

P#33593 2016-12-16 20:33


I was thinking of making a sailing simulator too... You beat me to it! :-)

The graphics are very nice but I think the relationship between speed and scale could be improved... The boat picks up speed so quickly (including backwards) it's easy to run aground without having any time to react. Perhaps you could slow things down a bit?

Or maybe I just need to understand sailing dynamics better! ;-)

Anyway, nice work!

P#33897 2016-12-22 01:54


@fuchikoma71 - I think this is what was meant by 'arcade' game feel... but regardless I also think it would be better to scale how boat pickup speed or make much bigger map (but not on pico sadly).

P#33905 2016-12-22 03:34


@dagondev - a much bigger map would be feasible in pico with procedural methods, but yes, a "real-time" sailing simulator would probably not be fun to play on the platform.

Still, I think a compromise between arcade and simulation is often possible (this is what I was aiming for with pic-Orion :-)

P#33914 2016-12-22 05:46


Yeah, something between sounds cool. I am very interested in your project then! Do you have twitter or other way to follow your development?

P#33915 2016-12-22 06:08


I'm just a "bedroom programmer" who doesn't even have a twitter account... My cartridge is on the repository though. The interface is a bit "cold" and functional by pico-8 standards and the gameplay is a admittedly rather basic. Much more so than "Fair Winds". But it does what I wanted it to do :-)
Look it up, give it a go and let me know what you think...

P#33916 2016-12-22 06:20


I see only picorion though, I am interested in your sailing simulator which I assumed you will start working on. But I will keep tab on your profile then. :p

P#33917 2016-12-22 06:50


Thanks... At the moment, I'm not working on the sailing simulator but on something completely different (vintage cyberpunk-inspired puzzler). But I'll keep it in mind for a future project ;-)

P#33918 2016-12-22 07:05


@dagondev I would play more sailing simulators if such things existed. This was just a 24 hour game jam game, so it doesn't really push Pico8 to it's limits or anything. ;)

Yah, the map is the biggest it can be for a static tilemap. I did consider procedural ones, but never really got around to it. I have plenty of code room for it. I think maybe 3/4 of the tokens remaining.

P#33964 2016-12-22 11:52


Sounds like a good idea for a expanding base game. Btw. Would have something against using your code? (ofc with proper credits) I would like experiment a little with this. :)

EDIT: any chance for explanation what variables mean/what is happening in crucial part of the code? It would speed up tinkering around this. Thanks!

P#34022 2016-12-23 06:59


Sure. Go for it. I think all of the carts posted to the forum have an implicit creative commons license anyway.

Anyway, it was a game jam game so I never bothered to comment the code, but here is a version with comments:

--trinagle drawing swiped from: http://www.lexaloffle.com/bbs/?tid=2734
function lerp(a,b,alpha)
    return a*(1.0-alpha)+b*alpha

function clip(v)
    return max(-1,min(128,v))

function dtri(x1,y1,x2,y2,x3,y3,c)
    if(y2<y1) then
        if(y3<y2) then
            local tmp = y1
            y1 = y3
            y3 = tmp
            tmp = x1
            x1 = x3
            x3 = tmp
            local tmp = y1
            y1 = y2
            y2 = tmp
            tmp = x1
            x1 = x2
            x2 = tmp
        if(y3<y1) then
            local tmp = y1
            y1 = y3
            y3 = tmp
            tmp = x1
            x1 = x3
            x3 = tmp

    y1 += 0.001 -- offset to avoid divide per 0

    local miny = min(y2,y3)
    local maxy = max(y2,y3)

    local fx = x2
    if(y2<y3) then
        fx = x3

    local d12 = (y2-y1)
    if(d12 != 0) d12 = 1.0/d12
    local d13 = (y3-y1)
    if(d13 != 0) d13 = 1.0/d13

    local cl_y1 = clip(y1)
    local cl_miny = clip(miny)
    local cl_maxy = clip(maxy)

    for y=cl_y1,cl_miny do
        local sx = lerp(x1,x3, (y-y1) * d13 )
        local ex = lerp(x1,x2, (y-y1) * d12 )
    local sx = lerp(x1,x3, (miny-y1) * d13 )
    local ex = lerp(x1,x2, (miny-y1) * d12 )

    local df = (maxy-miny)
    if(df != 0) df = 1.0/df

    for y=cl_miny,cl_maxy do
        local sx2 = lerp(sx,fx, (y-miny) * df )
        local ex2 = lerp(ex,fx, (y-miny) * df )

-- Create a 2x3 "translate, rotate, scale" transform matrix.
function trs(tx, ty, angle, sx, sy)
    rx, ry = cos(angle), -sin(angle)
    return {rx, ry, -ry, rx, tx, ty}

-- Multiply two transform matrices.
function transform_mult(t1, t2)
    return {
        t1[1]*t2[1] + t1[3]*t2[2],
        t1[2]*t2[1] + t1[4]*t2[2],
        t1[1]*t2[3] + t1[3]*t2[4],
        t1[2]*t2[3] + t1[4]*t2[4],
        t1[1]*t2[5] + t1[3]*t2[6] + t1[5],
        t1[2]*t2[5] + t1[4]*t2[6] + t1[6],

-- Invert a transform matrix.
function transform_inv(t)
  local inv_det = 1/(t[1]*t[4] - t[3]*t[2])
  return {
         t[4]*inv_det, -t[2]*inv_det,
        -t[3]*inv_det,  t[1]*inv_det,
        (t[3]*t[6] - t[5]*t[4])*inv_det,
        (t[5]*t[2] - t[1]*t[6])*inv_det,

-- Transform a vector.
function transform_v(m, x, y)
    return m[1]*x + m[3]*y, m[2]*x + m[4]*y

-- Transform a point.
function transform_p(m, x, y)
    return m[1]*x + m[3]*y + m[5], m[2]*x + m[4]*y + m[6]

-- Number of frames since the game started.
ticks = 0

-- Direction the wind blows.
wind_dir = {x = 2, y = 0}

-- Arrays for wind/wave effect sprites.
winds = {}
waves = {}
wakes = {}

-- x-offset of the mast on the boat.
-- Used for drawing the sail.
mastx = 6

-- Current waypoint.
wayx, wayy = 0, 0
-- Waypoint trigger radius.
wayr = 20
-- Waypoints.
waypoints = {
    {x = 117, y =  91},
    {x =  31, y = 142},
    {x = 354, y =  75},
    {x = 250, y = 140},
    {x =  47, y = 236},
    {x =  33, y = 150},
    {x = 140, y =  15},
    {x = 136, y = 294},
    {x =  35, y = 106},
    {x = 245, y =  17}

-- Drop the current waypoint and return the coordinates of the next.
function pop_waypoint()
    local wayp = waypoints[1]
    del(waypoints, wayp)

    if wayp then
        return wayp.x, wayp.y
        finish = ticks

function _init()
    -- Add wind and wave sprites.
    for i = 1, 16 do
        add(winds, {x = rnd(128), y = rnd(128)})
        add(waves, {x = rnd(128), y = rnd(128)})

    -- Setup the boat object.
    boat = {
        -- Current transform matrix.
        m = {1, 0, 0, 1, 0, 0},
        -- Mast's transform matrix.
        mast_m = trs(mastx, 0, 0, 1, 1),
        -- Current rotation.
        rotation = 0.25,
        -- Current angle of the sail boom.
        boom = 0.25,
        -- Current angle limit of the sail boom.
        -- (How much the rope will let it out)
        limit = 0.25,
        vel = {x = 0, y = 0},

    -- Get the first waypoint.
    wayx, wayy = pop_waypoint()
    speed = 0

function _update()
--  debug_str = ""

    -- Get the input vales for the turning and sail.
    local tiller = (btn(0, 0) and -1 or 0) + (btn(1, 0) and 1 or 0)
    local sail = (btn(4, 0) and -1 or 0) + (btn(5, 0) and 1 or 0)

    -- Rotate the boat.
    boat.rotation += tiller*0.007
    -- Adjust the sail's max angle.
    boat.limit = max(0.01, min(boat.limit + 0.006*sail, 0.25))

    local m = boat.m
    local mast = transform_mult(m, boat.mast_m)
    local vx, vy = boat.vel.x, boat.vel.y

    -- Relative velocity of the boat to the wind.
    local vrx = wind_dir.x - vx
    local vry = wind_dir.y - vy

    -- The direction of the force applied to the sail by the wind.
    local fn = mast[3]*vrx + mast[4]*vry
    -- The value of the force applied to the sail by the wind.
    local fx, fy = mast[3]*fn, mast[4]*fn

    -- Slow down the boat slightly, and push it along using the force.
    vx = 0.99*vx + 0.1*fx
    vy = 0.99*vy + 0.1*fy

    -- Rapidly slow the boat down in the lateral direction.
    local keel_drag = 0.2*(m[3]*vx + m[4]*vy)
    vx -= keel_drag*m[3]
    vy -= keel_drag*m[4]

    -- Update the boat's properties
    boat.vel.x = vx
    boat.vel.y = vy
    speed = sqrt(vx*vx + vy*vy)

    local x = boat.m[5] + vx
    local y = boat.m[6] + vy
    boat.m = trs(x, y, boat.rotation, 1, 1)

    -- Torque applied to the boom by the wind.
    local btorque = wind_dir.x*mast[3] + wind_dir.y*mast[4]
    -- Rotate the boom based on the torque applied to it.
    boat.boom = max(-boat.limit, min(boat.boom - 0.1*btorque, boat.limit))
    boat.mast_m = trs(mastx, 0, boat.boom, 1, 1)

    -- Add sprites for the boat's wake
    if ticks%4 == 0 then
        local wx, wy = transform_p(boat.m, -10, 0)
        local wdx, wdy = transform_v(boat.m, 0, 1)
        add(wakes, {x = wx, y = wy, dx = wdx, dy = wdy})
        if #wakes > 20 then
            del(wakes, wakes[1])

    -- Check if the boat is inside the waypoint.
    if wayx then
        local dx, dy = (x - wayx)/wayr, (y - wayy)/wayr
        if dx*dx + dy*dy < 1 then
            wayx, wayy = pop_waypoint()

    ticks += 1

-- Drawing code. Mostly self-explanatory?
black = 0
dgrey = 5
lgrey = 6
white = 7
red = 8
brown = 4
blue = 12
peach = 15

function draw_wind(tx, ty)
    local count = #winds
    local gust = 0.3

    for i = 1, #winds do
        local phase = 0.01*ticks + i/count

        local dx = wind_dir.x + gust*sin(phase)
        local dy = wind_dir.y + gust*sin(0.6*phase + 0.5)

        local wind = winds[i]
        wind.x = (wind.x + dx)%128
        wind.y = (wind.y + dy)%128

        pset((wind.x + tx)%128, (wind.y + ty)%128, lgrey)

function draw_waves(tx, ty)
    local count = #waves
    local duration = count*3

    for i = 1, count do
        local phase = (ticks/2 + i)/count%1

        local wave = waves[i]
        spr(flr(6*phase), (wave.x + tx)%128, (wave.y + ty)%128)

        if phase == 0 then
            wave.x, wave.y = rnd(128), rnd(128)

function draw_boat(view)
    local m = transform_mult(view, boat.m)
    local x1, y1 = transform_p(m, -10, -4)
    local x2, y2 = transform_p(m,   8, -4)
    local x3, y3 = transform_p(m,   8,  4)
    local x4, y4 = transform_p(m, -10,  4)
    local x5, y5 = transform_p(m,  12,  0)

    dtri(x1, y1, x2, y2, x3, y3, brown)
    dtri(x1, y1, x3, y3, x4, y4, brown)
    dtri(x2, y2, x3, y3, x5, y5, brown)

    local mast = transform_mult(m, boat.mast_m)
    local sdir = wind_dir.x*mast[3] + wind_dir.y*mast[4]

    local mx, my = transform_p(mast, 0, 0)
    local sx1, sy1 = transform_p(mast, -15, 0)
    local sx2, sy2 = transform_p(mast, -13, 5*max(-1, min(3*sdir, 1)))
    dtri(mx, my, sx1, sy1, sx2, sy2, peach)

    local mx2, my2 = transform_p(mast, -16, 0)
    line(mx, my, mx2, my2, dgrey)

function draw_wakes(tx, ty)
    local count = #wakes
    for i = 1, count do
        local wake = wakes[i]
        local x, y = wake.x + tx, wake.y + ty
        local t = 3 + 0.5*(count - i)
        local dx, dy = wake.dx*t, wake.dy*t
        pset(x, y, white)
        pset(x + dx, y + dy, white)
        pset(x - dx, y - dy, white)

function draw_waypoint(tx, ty)
    if not wayx then return end

    local x, y = wayx + tx, wayy + ty
    local sx, sy = x - 64, y - 64
    local div = max(abs(sx), abs(sy))
    if div > 64 then
        circ(64/div*sx + 64, 64/div*sy + 64, 2, red)

    circ(x, y, wayr + 5*sin(ticks/60), red)

function _draw()
    rectfill(0, 0, 128, 128, blue)

    local m = boat.m
    local tx, ty = 64 - m[5], 64 - m[6]
    local view = {1, 0, 0, 1, tx, ty}
    draw_wakes(tx, ty)
    draw_waves(tx, ty)
    draw_waypoint(tx, ty)
    draw_wind(tx, ty)

    print("speed: "..flr(30*speed).." knots")

    if(finish) then
        print("Finished: "..(finish/30).." s")
        print("time: "..flr(ticks/30).." s")

    if debug_str then
P#34045 2016-12-23 16:06

Log in to post a comment


New User | Account Help
:: New User
About | Contact | Updates | Terms of Use
Follow Lexaloffle:
Generated 2017-03-26 13:02 | 0.033s | 2097k