• Wahoo Kickr Core 2 speed and distance

    Suunto Race S
    14
    0 Votes
    14 Posts
    182 Views
    R
    Another thing one might try is buying a separate cadence sensor. https://us.suunto.com/pages/suunto-race-user-guide Note under “Pairing pods and sensors” the Race lists “power POD” and “bike POD” as two different things. Power POD uses CPS (Cycling Power Service) but bike POD uses CSCS (Cycling Speed and Cadence Serivce). You’re already connecting the Wahoo Core as a power POD to get the power data, and unfortunately it is not getting “speed” data from it. Buy a separate cadence sensor and connect that as a “bike POD” then the Race will have that additional source of data. https://www.wahoofitness.com/devices/bike-sensors
  • 8 Votes
    45 Posts
    4k Views
    Adrian.SA
    @aiv4r Unfortunately, after editing the elevation in Suunto app, it still isn’t re-calculated, which I reported in this thread: https://forum.suunto.com/topic/14776/new-feature-add-elevation-treadmill-training-adjust-elevation
  • 1 Votes
    5 Posts
    149 Views
    P
    And how cool would it be to implement control theory with a bike trainer. Set a target heart rate in the suunto plus app, that then adjust the trainer power via erg mode through a PI or PID loop to keep a constant HR.
  • 1 Votes
    2 Posts
    34 Views
    F
    @onghyr, yes, https://forum.suunto.com/topic/14749/bug-vertical-2-randomly-dropping-external-bt-sensors-and-not-reconnecting Unfortunately no response from the forum big guys (moderators, field testers).
  • Next Big Update for Race 2 etc...

    Watches
    12
    1 Votes
    12 Posts
    458 Views
    D
    @cosme.costa our spy strikes again , thanx for the updates
  • 2 Votes
    2 Posts
    67 Views
    martintrailM
    @Marek-Machala Hlavni mesto Praha is still fine. I have Roznov pod Radhostem there, and it almost doesn’t fit in there I would gladly delete the entire position and leave only the weather, as you write.
  • 4 Votes
    3 Posts
    143 Views
    M
    Hey, I have a Suunto 5 watch, how many SuuntoPlus apps can I run at the same time? And can the apps connect to internet during an exercise? For example, could there be an online messaging app on the watch? Just wondering… Thanks!
  • Scare in Paris: SAFE MODE stuck in a LOOP!

    Suunto Race S
    3
    0 Votes
    3 Posts
    173 Views
    enriqueescomsE
    @2b2bff Well, I connected the charger to a power brick, although in the same socket there were USB sockets (which I didn’t use). I still don’t know the origin of the failure but I understand that the shots should be around. Have you had problems with this style too?
  • SuuntoPlus Apps Development

    SuuntoPlus™ Sports Apps
    54
    10 Votes
    54 Posts
    12k Views
    Łukasz SzmigielŁ
    @Dimitrios-Kanellopoulos lucky, lucky!
  • 0 Votes
    3 Posts
    204 Views
    withManishW
    @matt-r, I have noticed that with this watch, HR and SpO2 readings are inconsistent and inaccurate, even after following the instructions during resting.
  • Stuck to Power Save Mode

    Suunto Race S
    3
    1
    0 Votes
    3 Posts
    142 Views
    withManishW
    @Horizontal_2 Thank you, the trick that worked.
  • 1 Votes
    1 Posts
    30 Views
    No one has replied
  • Undecided for a new bike computer

    Suunto app and other software services
    38
    0 Votes
    38 Posts
    1k Views
    Stoke80S
    @Dimitrios-Kanellopoulos Thank you. Just for my guide: How would I see the zones in the fit-File? Would there be an additional row, where they are shown?
  • Vertical 2 syncing of routes

    Suunto Vertical 2
    8
    0 Votes
    8 Posts
    174 Views
    W
    @maszop totally agree! Offline re-routing on downloaded maps is perhaps the greatest Achilles’s heel of the Suunto platform and, I think, the most important difference from Garmin’s system. As said in previous posts, this Achilles’s heel is further compounded by the limited number of routes one can offload onto the SV2. I believe that watch processor and storage space are not the issues. And even if they are, why, at least, not use Suunto app on mobile device for these two jobs for eventual BT transfer to watch of gpx file (like you did with a third party app)? It’s not ideal since you have to have a mobile device on activity/exercise but you will have peace of mind that you are covered for any eventuality.
  • Examples explained

    Pinned Suunto Plus Development
    2
    0 Votes
    2 Posts
    125 Views
    S
    FowlPlay FowlPlay is a quick, light‑hearted minigame perfect for killing time while waiting during any sport. Master the slingshot, tweak your trajectory and send your bird soaring toward its waiting pig friend. manifest.json The manifest for FowlPlay only defines the strictly necessary fields: name (that app’s name), description (a short description of the app; preferably under 22 characters), version (a unique short identifier; must be changed when reuploading to ApiZone), author (credit where credit is due), modificationTime (a Epoch Unix Timestamp in whole seconds representing modification time), type (the literal string “feature” for sport apps), usage (the literal string “workout”) and template (an array of html templates used by this app). manifest.json: { "name": "FowlPlay", "description": "Unite with the pig!!", "version": "1.0", "author": "Birdy B.", "modificationTime": 1770000000, "type": "feature", "usage": "workout", "template": [{"name": "g.html"}] } main.js For this app, all rendering and game logic live directly inside the HTML template (see below). Every app still needs a main.js file and must implement the global getUserInterface function that tells the watch which template to load. The function should return an object that includes at least a template field with template’s name. The file extension (.html) should be dropped here, even though the manifest lists full file names for templates. Here’s our minimal main.js file: main.js: function getUserInterface() { return { template: 'g' }; } g.html Finally, the HTML template! The file name should match the declaration in manifest.json and value returned by getUserInterface in main.js. For your convenience the entire code with comments is listed here and explained below: g.html: <uiView onLoad=" // Set constants for world gravity (g) and bird/enemy radius(r) var g = .2, r = 5; // Set the current state (one of 'sling', 'launched', 'won' or 'reset') and initialize sleep timer (s) var state = 'sling', s = 0; // Set variables for the launch force (f), launch angle (a) and current direction (d; angle) var f = 2, a = 0, d = 0; // Define the position and velocities for the bird and enemy var bird = {x: 20, y: 61.5, dx: 0, dy: 0}; var enemy = {x: 80, y: 80 - r, dx: 0, dy: 0}; /** @param { CanvasRenderingContext2D } ctx */ function renderGame(ctx) { // Viewport width and height from canvas width and height var vw = ctx.width / 100; var vh = ctx.height / 100; // Draw sky ctx.fillStyle = '#CCEEFF'; ctx.fillRect(0, 0, 100 * vw, 100 * vh); // Draw ground ctx.fillStyle = '#14B814'; ctx.fillRect(0, 80 * vh, 100 * vw, 20 * vh); // Draw slingshot ctx.strokeStyle = '#8A380F'; ctx.fillStyle = '#8A380F'; ctx.beginPath(); ctx.lineWidth = 3 * vh; ctx.lineCap = 'round' ctx.arc(20 * vw, 61.5 * vh, 7.5 * vh, 0, 3.15); ctx.stroke(); ctx.fillRect(18 * vw, 70 * vh, 4 * vw, 10 * vh); // Draw bird circle(ctx, '#F2DF0D', bird.x * vw, bird.y * vh, r * vh); // Calculate current direction d = (state == 'sling') ? a : (!!bird.dx ? (Math.atan(bird.dy / bird.dx) + .8) / -.2 : 0); // Draw beak ctx.fillStyle = '#F97706'; ctx.beginPath(); ctx.moveTo((bird.x + Math.cos(-d * .2 - 1 ) * r * .45) * vw, (bird.y + Math.sin(-d * .2 - 1 ) * r * .45) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - .4) * r * .95) * vw, (bird.y + Math.sin(-d * .2 - .4) * r * .95) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 + .2) * r * .45) * vw, (bird.y + Math.sin(-d * .2 + .2) * r * .45) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - .4) * r * .2 ) * vw, (bird.y + Math.sin(-d * .2 - .4) * r * .2 ) * vh); ctx.closePath(); ctx.fill(); // Draw eyes ctx.fillStyle = '#000000'; ctx.beginPath(); ctx.moveTo((bird.x + Math.cos(-d * .2 - 2.1) * r * .25) * vw, (bird.y + Math.sin(-d * .2 - 2.1) * r * .25) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - 1.4) * r * .5 ) * vw, (bird.y + Math.sin(-d * .2 - 1.4) * r * .5 ) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - 1.5) * r * .75) * vw, (bird.y + Math.sin(-d * .2 - 1.5) * r * .75) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - 1.2) * r * .75) * vw, (bird.y + Math.sin(-d * .2 - 1.2) * r * .75) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - 1.3) * r * .15) * vw, (bird.y + Math.sin(-d * .2 - 1.3) * r * .15) * vh); ctx.fill(); // Draw enemy circle(ctx, '#FF80D4', enemy.x * vw, enemy.y * vh, r * vh); // Eyes circle(ctx, '#ffffff', (enemy.x - 2) * vw, enemy.y * vh, (r - 3.5) * vh); circle(ctx, '#ffffff', (enemy.x + 2) * vw, enemy.y * vh, (r - 3.5) * vh); circle(ctx, '#000000', (enemy.x - 2) * vw, (enemy.y - .25) * vh, .25 * vh); circle(ctx, '#000000', (enemy.x + 2) * vw, (enemy.y - .25) * vh, .25 * vh); //Snout circle(ctx, '#FFCCEE', enemy.x * vw, (enemy.y + 1) * vh, (r - 3) * vh); circle(ctx, '#330022', (enemy.x - .75) * vw, (enemy.y + .5) * vh, .65 * vh); circle(ctx, '#330022', (enemy.x + .75) * vw, (enemy.y + .75) * vh, .45 * vh); // Draw predictions var pred = nextPos({x: bird.x, y: bird.y, dx: bird.dx, dy: bird.dy}); if (state == 'sling') for (var i = 0; i <= 5; ++i) { pred = nextPos(nextPos(pred)); circle(ctx, '#47b4eb', pred.x * vw, pred.y * vh, 1 * vh); } // Game info ctx.fillStyle = '#0d5173'; cText(ctx, 'FowlPlay', 50 * vw, 15 * vh); if (state == 'sling') cText(ctx, 'Force: ' + f, 50 * vw, 25 * vh); if (state == 'won') cText(ctx, 'Congrats, you win!', 50 * vw, 25 * vh); // Move bird and enemy if (state == 'launched') bird = nextPos(bird); enemy = nextPos(enemy); // Calculate new direction if (state == 'sling') { bird.dx = Math.cos(-a * .2 - .8) * f; bird.dy = Math.sin(-a * .2 - .8) * f; } // Check if bird and enemy overlap if ((bird.x - enemy.x) * (bird.x - enemy.x) + (bird.y - enemy.y) * (bird.y - enemy.y) < 4 * r * r) state = 'won'; // Next attempt if bird is still for 30 frames or game is reset if ((Math.abs(bird.dx) < 0.1 && Math.abs(bird.dy) < 0.1) || state == 'won') { ++s; } else { s = 0; } if ((s > 30 && state != 'sling')|| state == 'reset') { // Reset game state, bird position and get random location for enemy from current time state = 'sling'; bird = {x: 20, y: 61.5, dx: 0, dy: 0}; $.get('/Dev/Time/LocalTime', function(v){ enemy.x = parseInt(formatValue(v, 'time s')) % 11 * 4 + 40; }) } } // Helper function to center text function cText(ctx, text, x, y) { ctx.fillText(text, x - ctx.measureText(text).width / 2, y); } // Helper function to draw a circle function circle(ctx, color, x, y, rd) { ctx.fillStyle = color; ctx.beginPath(); ctx.arc(x, y, rd, 0, 6.3); ctx.closePath(); ctx.fill(); } // Helper method to calculate next position function nextPos(obj) { obj.dy += g; obj.x += obj.dx; obj.y += obj.dy; // Snap to ground if below ground surface after movement for simplicity if (obj.y >= 80 - r) { obj.y = 80 - r; obj.dy *= -.9; obj.dx *= .9; } return obj; } // Helper function to handle push button input function handleGameIO(v) { // Up and down button-click changes the launch angle if (v == 10 && state == 'sling') ++a; if (v == 20 && state == 'sling') --a; // Up-button-longpress changes the force of the slingshot if (v == 11 && state == 'sling') f = (f % 4) + 1; // Down-button-longpress launches the bird if (v == 21 && state == 'sling') state = 'launched'; if (v == 20 && state == 'launched') state = 'reset'; } " onActivate = "$.subscribe('/Dev/Time/Tick10hz', function(){ control('#cnv', 'REFRESH'); })" > <div id="suuntoplus"> <uiViewSet id="view"> <div id="welcome"> <div style="width: 100%; height: 50%; top: 0%; left: 0%;"><img src="btn-shape-top.png" class="c-yellow"></div> <div class="f-b-s cm-fgc" style="top: 13%; left: calc(79% - 100%e);" >CHANGE FORCE</div> <div style="width: 50px; height: 50px; top: calc(30% - 50%e); left: calc(90% - 50%e);"><img class="cm-bgc" src="hint-btn-top.png" /></div> <div class="p-m"> <span class="f-m">FowlPlay</span><br/> Click buttons to aim<br /> Longpress for action<br /> Launch away! </div> <div style="width: 100%; height: 50%; top: calc(100% - 100%e); left: 0%;"><img src="btn-shape-btm.png" class="c-yellow p-b"></div> <div class="f-b-s cm-fgc" style="top: calc(87% - 100%e); left: calc(79% - 100%e);" >LAUNCH BIRD</div> <div style="width: 50px; height: 50px; top: calc(70% - 50%e); left: calc(90% - 50%e);"><img class="cm-bgc" src="hint-btn-bottom.png" /></div> <userInput> <pushButton name="down" onLongPress="navigate('#view', 'game')"> </userInput> </div> <div id="game"> <object id="cnv" type="canvas" build="ctx => renderGame(ctx)" style="width: 100%; height: 100%; top: 0%; left: 0%;"/> <userInput> <pushButton name="up" onClick="handleGameIO(10)" onLongPressStart="handleGameIO(11)" /> <pushButton name="down" onClick="handleGameIO(20)" onLongPressStart="handleGameIO(21)" /> </userInput> </div> </uiViewSet> </div> </uiView> If you have the SuuntoPlusEditor-plugin installed in Visual Studio Code and your Suunto watch connected to your laptop you are now ready to test the app: Create an empty folder and open it in Visual Studio Code Create new files manifest.json, main.js and g.html Copy-paste the code snippets above to their respective files Navigate to the bottom of the Explorer view where these files are listed. Under the SuuntoPlus Apps section hover the mouse over the newly created app and click on the watch icon (Deploy to Watch). Wait for the building and uploading to finish; the app is now available on your watch under SuuntoPlus once you start a new exercise. Please note that the app does not work in the simulator. In this example, the g.html file handles everything from layout and user interactions (watch buttons) to game logic and rendering. Let’s remove the game-related code and focus on the layout and user interactions first. <uiView> <div id="suuntoplus"> <uiViewSet id="view"> <div id="welcome"> <div style="width: 100%; height: 50%; top: 0%; left: 0%;"><img src="btn-shape-top.png" class="c-yellow"></div> <div class="f-b-s cm-fgc" style="top: 13%; left: calc(79% - 100%e);" >CHANGE FORCE</div> <div style="width: 50px; height: 50px; top: calc(30% - 50%e); left: calc(90% - 50%e);"><img class="cm-bgc" src="hint-btn-top.png" /></div> <div class="p-m"> <span class="f-m">FowlPlay</span><br/> Click buttons to aim<br /> Longpress for action<br /> Launch away! </div> <div style="width: 100%; height: 50%; top: calc(100% - 100%e); left: 0%;"><img src="btn-shape-btm.png" class="c-yellow p-b"></div> <div class="f-b-s cm-fgc" style="top: calc(87% - 100%e); left: calc(79% - 100%e);" >LAUNCH BIRD</div> <div style="width: 50px; height: 50px; top: calc(70% - 50%e); left: calc(90% - 50%e);"><img class="cm-bgc" src="hint-btn-bottom.png" /></div> <userInput> <pushButton name="down" onLongPress="navigate('#view', 'game')"> </userInput> </div> <div id="game"> <!-- The game logic will be added here --> </div> </uiViewSet> </div> </uiView> Every sport app template has a <uiView> element representing the Suunto watch as their root element. It is good practice to wrap the content inside <uiView> in a <div> (you can think of these as the <html> and <body> elements of HTML). This app has two different screens: a welcome screen and the actual game. This can be achieved with a <uiViewSet> element containing two <div> elements. All three elements have an id attribute for easy referencing in the code. This can be used for example to change which of the <div> elements is displayed. This is done with: <userInput> <pushButton name="down" onLongPress="navigate('#view', 'game')"> </userInput> The <userInput> element can contain several <pushButton> elements. A <pushButton> has a name attribute to identify the button (here set to “down” for the bottom-right button) and event listeners for different types of user actions (here we listen to a longpress with onLongPress). Because the <userInput> element is defined inside the welcome screen’s <div> element, the interactions are captured only when the welcome screen is visible. navigate is a global built-in function of the watch that takes two parameters here: a query string to identify the targeted <uiViewSet> element and the id attribute of the <div> element to display. The rest of the <div> elements are used to set the position and dimensions (setting CSS styles (top/left/width/height) with the style attribute; % represents a percentage of the parent element’s width/height while %e represents a percentage of the current element’s width/height) as well as the font and color (through class names with the class attribute) of texts and images on the welcome screen. The class names and images used here are part of the watch and can be used without explicit definition or importing. The first letter of the class name typically defines its purpose: c is for colors (fg = foreground; bg = background; cm = current (light/dark) mode), f is for font styles and p is for positioning. The game logic is defined in an event listener attached to the <uiView>. Here we use the onLoad attribute (notice the uppercase L compared to the HTML standard) that behaves similarly to the onLoad function that could be defined in main.js except the symbols (functions and variables) defined here are in scope for (and thus can be used in) the entire HTML template. We take advantage of this for rendering. First we define a drawing surface in our layout using: <object id="cnv" type="canvas" build="ctx => renderGame(ctx)" style="width: 100%; height: 100%; top: 0%; left: 0%;"/> An <object> element with a type attribute of “canvas” works similarly to a HTML5 <canvas>. The content will be rendered using the anonymous function defined by the build attribute. Here we capture the argument (that is a modified version of a CanvasRenderingContext2D from the JavaScript Canvas API) and pass it onto the function renderGame. By setting the id and style attributes we ensure the canvas can be easily referenced later and takes up the entire screen respectively. This setup draws our game just once, but for a smooth gaming experience the screen needs to be updated continuously. This is achieved by adding the following attribute to the <uiView> element: onActivate = "$.subscribe('/Dev/Time/Tick10hz', function(){ control('#cnv', 'REFRESH'); })" Here the $ object allows us to access the devices resources. Here we use the subscribe method to attach a change listener to a resource. The '/Dev/Time/Tick10hz' resource will trigger the callback function approximately 10 times per second (this essentially works like setInterval in JavaScript). control is a global built-in function of the watch that takes two parameters: a query string to identify the target element and the control signal to send it. Here this causes the <canvas> to be refreshed (=rerendered). The actual rendering is handled by the renderGame function defined in the onLoad attribute of the <uiView>: function renderGame(ctx) { // Viewport width and height from canvas width and height var vw = ctx.width / 100; var vh = ctx.height / 100; // Draw sky ctx.fillStyle = '#CCEEFF'; ctx.fillRect(0, 0, 100 * vw, 100 * vh); // Draw ground ctx.fillStyle = '#14B814'; ctx.fillRect(0, 80 * vh, 100 * vw, 20 * vh); // Draw slingshot ctx.strokeStyle = '#8A380F'; ctx.fillStyle = '#8A380F'; ctx.beginPath(); ctx.lineWidth = 3 * vh; ctx.lineCap = 'round' ctx.arc(20 * vw, 61.5 * vh, 7.5 * vh, 0, 3.15); ctx.stroke(); ctx.fillRect(18 * vw, 70 * vh, 4 * vw, 10 * vh); // Draw bird circle(ctx, '#F2DF0D', bird.x * vw, bird.y * vh, r * vh); // Calculate current direction d = (state == 'sling') ? a : (!!bird.dx ? (Math.atan(bird.dy / bird.dx) + .8) / -.2 : 0); // Draw beak ctx.fillStyle = '#F97706'; ctx.beginPath(); ctx.moveTo((bird.x + Math.cos(-d * .2 - 1 ) * r * .45) * vw, (bird.y + Math.sin(-d * .2 - 1 ) * r * .45) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - .4) * r * .95) * vw, (bird.y + Math.sin(-d * .2 - .4) * r * .95) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 + .2) * r * .45) * vw, (bird.y + Math.sin(-d * .2 + .2) * r * .45) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - .4) * r * .2 ) * vw, (bird.y + Math.sin(-d * .2 - .4) * r * .2 ) * vh); ctx.closePath(); ctx.fill(); // Draw eyes ctx.fillStyle = '#000000'; ctx.beginPath(); ctx.moveTo((bird.x + Math.cos(-d * .2 - 2.1) * r * .25) * vw, (bird.y + Math.sin(-d * .2 - 2.1) * r * .25) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - 1.4) * r * .5 ) * vw, (bird.y + Math.sin(-d * .2 - 1.4) * r * .5 ) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - 1.5) * r * .75) * vw, (bird.y + Math.sin(-d * .2 - 1.5) * r * .75) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - 1.2) * r * .75) * vw, (bird.y + Math.sin(-d * .2 - 1.2) * r * .75) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - 1.3) * r * .15) * vw, (bird.y + Math.sin(-d * .2 - 1.3) * r * .15) * vh); ctx.fill(); // Draw enemy circle(ctx, '#FF80D4', enemy.x * vw, enemy.y * vh, r * vh); // Eyes circle(ctx, '#ffffff', (enemy.x - 2) * vw, enemy.y * vh, (r - 3.5) * vh); circle(ctx, '#ffffff', (enemy.x + 2) * vw, enemy.y * vh, (r - 3.5) * vh); circle(ctx, '#000000', (enemy.x - 2) * vw, (enemy.y - .25) * vh, .25 * vh); circle(ctx, '#000000', (enemy.x + 2) * vw, (enemy.y - .25) * vh, .25 * vh); //Snout circle(ctx, '#FFCCEE', enemy.x * vw, (enemy.y + 1) * vh, (r - 3) * vh); circle(ctx, '#330022', (enemy.x - .75) * vw, (enemy.y + .5) * vh, .65 * vh); circle(ctx, '#330022', (enemy.x + .75) * vw, (enemy.y + .75) * vh, .45 * vh); // Draw predictions var pred = nextPos({x: bird.x, y: bird.y, dx: bird.dx, dy: bird.dy}); if (state == 'sling') for (var i = 0; i <= 5; ++i) { pred = nextPos(nextPos(pred)); circle(ctx, '#47b4eb', pred.x * vw, pred.y * vh, 1 * vh); } // Game info ctx.fillStyle = '#0d5173'; cText(ctx, 'FowlPlay', 50 * vw, 15 * vh); if (state == 'sling') cText(ctx, 'Force: ' + f, 50 * vw, 25 * vh); if (state == 'won') cText(ctx, 'Congrats, you win!', 50 * vw, 25 * vh); // Move bird and enemy if (state == 'launched') bird = nextPos(bird); enemy = nextPos(enemy); // Calculate new direction if (state == 'sling') { bird.dx = Math.cos(-a * .2 - .8) * f; bird.dy = Math.sin(-a * .2 - .8) * f; } // Check if bird and enemy overlap if ((bird.x - enemy.x) * (bird.x - enemy.x) + (bird.y - enemy.y) * (bird.y - enemy.y) < 4 * r * r) state = 'won'; // Next attempt if bird is still for 30 frames or game is reset if ((Math.abs(bird.dx) < 0.1 && Math.abs(bird.dy) < 0.1) || state == 'won') { ++s; } else { s = 0; } if ((s > 30 && state != 'sling')|| state == 'reset') { // Reset game state, bird position and get random location for enemy from current time state = 'sling'; bird = {x: 20, y: 61.5, dx: 0, dy: 0}; $.get('/Dev/Time/LocalTime', function(v){ enemy.x = parseInt(formatValue(v, 'time s')) % 11 * 4 + 40; }) } } The drawing is achieved by using methods familiar from the JavaScript Canvas API such as fillRect, beginPath, arc, moveTo, lineTo, fillText, measureText, stroke and fill as well as reading and modifying properties such as width, height, fillStyle, strokeStyle, lineWidth and lineCap. It is notable that the drawing context can be passed on to other function as is done in this example with the following helper functions:: // Helper function to center text function cText(ctx, text, x, y) { ctx.fillText(text, x - ctx.measureText(text).width / 2, y); } // Helper function to draw a circle function circle(ctx, color, x, y, rd) { ctx.fillStyle = color; ctx.beginPath(); ctx.arc(x, y, rd, 0, 6.3); ctx.closePath(); ctx.fill(); } We have also defined some constants and variables to manage the game’s physics and state (and once again defined a helper function to move the bird): // Set constants for world gravity (g) and bird/enemy radius(r) var g = .2, r = 5; // Set the current state (one of 'sling', 'launched', 'won' or 'reset') and initialize sleep timer (s) var state = 'sling', s = 0; // Set variables for the launch force (f), launch angle (a) and current direction (d; angle) var f = 2, a = 0, d = 0; // Define the position and velocities for the bird and enemy var bird = {x: 20, y: 61.5, dx: 0, dy: 0}; var enemy = {x: 80, y: 80 - r, dx: 0, dy: 0}; // Helper method to calculate next position function nextPos(obj) { obj.dy += g; obj.x += obj.dx; obj.y += obj.dy; // Snap to ground if below ground surface after movement for simplicity if (obj.y >= 80 - r) { obj.y = 80 - r; obj.dy *= -.9; obj.dx *= .9; } return obj; } Here a game object (the bird or pig) is defined by its x and y coordinates as well as its horizontal and vertical velocity (dx and dy). One quirk of the code is how the new pseudo-random location for the pig is calculated. $.get('/Dev/Time/LocalTime', function(v){ enemy.x = parseInt(formatValue(v, 'time s')) % 11 * 4 + 40; }) Here we fetch (the get method from the $ object works similarly to subscribe method except this is only executed once) the current time, treat the value as a time and format it to only include the current second (using the built-in function formatValue). Then we convert the seconds to a number and clamp it to fit our game world. The final step to complete the game is to attach event listeners to user input. This is similar to before except this time we use a helper function to handle the mapping of the users actions to correct changes to the game depending on the game’s state. <userInput> <pushButton name="up" onClick="handleGameIO(10)" onLongPressStart="handleGameIO(11)" /> <pushButton name="down" onClick="handleGameIO(20)" onLongPressStart="handleGameIO(21)" /> </userInput> // Helper function to handle push button input function handleGameIO(v) { // Up and down button-click changes the launch angle if (v == 10 && state == 'sling') ++a; if (v == 20 && state == 'sling') --a; // Up-button-longpress changes the force of the slingshot if (v == 11 && state == 'sling') f = (f % 4) + 1; // Down-button-longpress launches the bird if (v == 21 && state == 'sling') state = 'launched'; if (v == 20 && state == 'launched') state = 'reset'; } The app combines many different capabilities of Suunto watches. Feel free to ask any questions you might have about this example app. Hopefully it inspired you to create some awesome. Happy launching; may your idle moments forever be fowl‑filled!
  • Unboxing 90th Anniversary Edition

    Suunto Vertical 2
    25
    7 Votes
    25 Posts
    2k Views
    Tami999T
    @sky-runner said in Unboxing 90th Anniversary Edition: @Freezer When the 90th anniversary edition is supposed to ship? Some people seem to already have it. Like 2 weeks ago i was able to add Vertical 2 Limited 90th anniversary to basket and pay for it in Poland smartwatch shop. I havent seen any info its gonna be send after 10th march. Not sure if I would buy it I wouldnt already got it on my hand :D. It looks cool but as Race 2 owner I dont need it. Its cool for collectors. I’ll sell my watch when Race 3 will be released and if cool enough for switch.
  • 1 Votes
    12 Posts
    777 Views
    F
    @markman8 AFAIK it is not related to gaskets. It’s a software/hardware issue that was well reported in this forum. It has happened to me once (Summer 2024) and the watch was replaced under warranty. There are also some forum members that had multiple failures (and replacements). It was never disclosed what the actual underlying reason was but the failure is known and quite common. Suunto might not willingly acknowledge it but from a moral standpoint they should.
  • Recovery watch & App

    Suunto Race 2
    19
    1 Votes
    19 Posts
    2k Views
    N
    @emkei For me, sleep tracking is better on my Race 2 than on my 7X.
  • Erg mode for the Wahoo Kickr Core

    SuuntoPlus™ Sports Apps
    2
    1 Votes
    2 Posts
    81 Views
    E
    @peringmar Its a more general issue, i have a Suito-T elite trainer and I also can’t control it, it would be nice to tackle this for all trainers
  • Daylight Saving Time Woes

    Suunto Run
    4
    0 Votes
    4 Posts
    156 Views
    JebClydeNCJ
    @Brad_Olwin iPhone 15, latest firmware’s and App versions. I can’t help but wonder though, two things I know compared to other suunto watches: The software backend for the Run is completely different from others—evidenced by single-note tones (instead of the delightful “Suunto Song” for alarms), music, and plenty of other bits indicating its its own breed of software This is the first “spring forward” since the release of the Run—so it may be possible this bug hasn’t been noticed because it wasn’t possible before now?