Getsrevel

Løse noter om programmering og andet “Work In Progress”.

Hvornår begynder bogen

“Hvis du nyder at veksle mellem at føle dig som den klogeste i verden og historiens største fjols, begge dele i løbet af den samme dag, så er programmering sikkert lige noget for dig.” – Anonym programmør

Subsections of Getsrevel

Teknologi

Eksempler med teknologier vi arbejder med i programmering.

Subsections of Tech

Html

HTML er et opmærkningssprog, der bruges til at kommunikere strukturen af indholdet i et dokument til en browser.

Her er et eksempel på en html struktur.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Titlen på dokumentet</title>
  </head>
  <body>
    Indhold på siden
  </body>
</html>

HTML elementer

Når du skriver dit indhold benyttes en række forskellige tags, til at angive hvordan de forskellige dele (HTML elementer) af dokumentet skal fortolkes.

Grundlæggende er html elementer opbygget således.

<TAG_NAVN ATTRIBUT_NAVN="ATTRIBUT_VÆRDI">INDHOLD_AF_HTML_ELEMENT</TAG_NAVN>

Et html element består alså af disse elementer

  • et start tag <TAG_NAVN>
  • et slut tag der matcher start tag men med en skråstreg: </TAG_NAVN>
  • en eller flere attributter, bestående af et navn og en værdi.

Her er et par eksempler på HTML tags.

<h1>Overskrift</h1>
<p>Dette er et afsnit med noget tekst.</p>

Indlejrede elementer

Nogle html elementer er beregnet til at indeholde andre html elementer, et eksempel er når man laver en opremsning med en liste.

<ul>
  <li>punkt 1</li>
  <li>punkt 2</li>
  <li>punkt 3</li>
</ul>

Typisk ender et dokument med at bestå af en række HTML elemementer, hver med et eller flere indlejrede dokumenter indeni.

Ikke afsluttede tags

Det er ikke alle HTML elementer, hvor det semantisk giver mening at de har indhold. Derfor kan de også bestå af et tag der slutter sig selv. Et eksempel er hr (Horizontal rule)

<hr />

Et andet eksempel er når der indsættes et billede. Her angives en URL til hvor billedets kilde kan findes som en attribut.

<img src="path/to/image/file.jpg" title="En beskrivende title" alt="Beskrivelse af billedet i fald det ikke kan vises" />

Alle de mange tags og hvordan de bruges kan du læse mere om i denne HTML Tutorial.

Demo dokument

Her er et lidt mere fyldigt eksempel.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Titlen på dokumentet</title>
  </head>
  <body>

    <h1>Eksempel på HTML dokument</h1>
    <p>Dette er et afsnit med noget tekst. Bemærk at jeg har sat et billede ind herunder</p>

    <img src="IMG_9747.jpg" alt="kameleon i skovbunden" title="Hovedet skiftede fra rødt til grønt mens jeg fandt kameraet frem" />

    <hr />

    <p>Her følger en liste med et par HTML elementer.</p>
    <ul>
      <li>punkt 1</li>
      <li>punkt 2</li>
      <li>punkt 3</li>
    </ul>

    <p>Man kan nemt ændre det til en ordnet liste.</p>
    <ol>
      <li>punkt 1</li>
      <li>punkt 2</li>
      <li>punkt 3</li>
    </ol>

  </body>
</html>

På denne side kan du se resultatet i din browser.

Materiale

HTML introduktion

Video kursus (På dansk)

Quiz

Css

HTML bruges til at opbygge den semantiske struktur af et dokument. Dette har ikke nogen direkte kobling til det visuelle udtryk.

Når vi skal styre udseendet på en webside bruges i stedet Cascading Style Sheets (CSS).

CSS introduktion

CSS er opbygget af en række regler, der angiver hvordan et eller flere HTML elementer skal vises.

SELECTOR_ONE, SELECTOR_TWO {
    PROPERTY_ONE : VALUE_ONE;
    PROERTY_TWO : VALUE_TWO;
}

En regel består altså af

  • En SELECTOR der udvælger de elementer, der er omfattet af reglen.
  • Hvis der er flere selectors, adskilles listen med kommaer ,
  • En eller flere PROPERTIES (egenskaber) med en tilhørende værdi
  • Listen af egenskaber er indkapslet som en blok i et sæt krøllede paranteser { ... }
  • En egenskab og den tilhørende værdi er adskilt med et Kolon :
  • Listen af egenskabs-/værdipar er adskilt af semikolon ;

Her er et konkret eksempel, der styrer fontstørrelsen og farven for overskrifter på niveau 2 (h2 elementer):

h2 {
    font-size: 24px;
    color: blue;
}

Samspil med HTML

Der er 3 måder at insætte css i en html struktur.

  • Definition i ekstern fil
  • Definition i internt i html strukturen
  • Inline i html elementerne som værdi i attribut

Der er mange forskellige måder at lave en selector, der udvælger bestemte html elementer. De mest almindelige er

  • Direkte vha. navn på elementet
  • Ved brug af en klasse
  • Ved brug af et ID

Kodeeksempel

De forskellige måder at bruge styles og udvælge elementer er illustreret i dette eksempel.

Formålet med eksemplet er ikke at lave en webside, der er visuelt pæn, men blot at illustrere et udvalg af hvordan styles og selectors kan bruges.

Her er html strukturen i demo.html. Bemærk hvordan det eksterne stylesheet er inkluderet, definition af style internt i dokument, samt brug af inline style.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" type="text/css" href="external-style.css" >
  <title>CSS på 3 måder</title>
  <style>
    h3 {
      font-style: italic;
    }
  </style>
</head>
<body>
  <header>
    <h2>Ekstern Definition</h2>
    <p>Udseendet af denne sektion er defineret i en ekstern css fil.</p>
    <p>Selector er lavet vha. af elementets navn.</p>
  </header>
  
  <main>
    <h1>Tre måder at bruge CSS i HTML</h1>
    <ul>
      <li>Ekstern CSS Fil.</li>
      <li>Intern CSS i dokument.</li>
      <li>Inline CSS definition i style attribut.</li>
    </ul>

    <h3>Intern definition</h3>
    <p>Som eksempel er der lavet en <strong>intern definition</strong>, der ændrer skriften til kursiv for <code>h3</code> elementer.</p>

    <h2>Selectors</h2>
    <p>Der er flere måde at udvælge elementer vha. selectors. De mest almindelige er:</p>
    <ul>
      <li>Direkte ved hjælp af navn på elementet.</li>
      <li>Ved brug af en klasse.</li>
      <li>Ved brug af et ID.</li>
    </ul>

    <div>
      <h3>Element</h3>
      <p>Man kan bruge html elementer direkte som selector, så vil alle elementer der matches bruge samme stil.</p>
      <p>Dette er lavet som eksempel i dette dokument, med <code>div</code> elementer Der bliver vist som indrammede kasser.</p>
    </div>

    <div class="my-custom-class">
      <h3>Klasse</h3>
      <p>Man kan bruge selectors til at vælge bestemte html elementer vha. deres navn. Dette er lavet som eksempel i dette dokument, med <code>div</code> elementer Der bliver vist som indrammede kasser.</p>
      <p>Man kan bruge html elementer direkte som selector, så vil alle elementer der matches bruge samme stil.</p>
    </div>

    <div id="my-custom-id">
      <h3>Selector med ID</h3>
      <p>Man kan vælge at tildele et ID som en attribut til et element, og bruge det til styling.</p>
      <p>For denne blok er stilen lavet med et ID</p>
      <p><em>Bemærk:</em> at et id skal være unikt indefor et dokument.</p>
    </div>

    <div class="my-custom-class">
      <h3>Klasser fortsat</h3>
      <p>Man kan bruge Samme klasse flere steder i sit dokument.</p>
    </div>
  </main>

  <footer>
    <h2>Demonstration af css</h2>
    <p>Her er et eksempel på en <span style="font-weight: bold; color: orange;">inline style</span></p>
  </footer>
</body>
</html>

Her er css filen strukturen i external-style.css

/* Man kan indsætte kommentarer i CSS på denne måde */

/*
Kommentarer
kan sprede sig
over flere 
linjer
*/

header {
  display: inline-flexbox;
  justify-content: flex-end;
  margin: 1em 1em;
  padding: 1em;
  background-color: yellow;
  border-radius: .5em;
  -webkit-box-shadow: 8px 13px 16px 0px rgba(0, 0, 0, 0.85);
  -moz-box-shadow: 8px 13px 16px 0px rgba(0, 0, 0, 0.85);
  box-shadow: 8px 13px 16px 0px rgba(0, 0, 0, 0.85);
}

body {
  margin: 0;
}

footer {
  background-color: gray;
  padding: .2em 3em;
  margin: 0;
}

h3 {
  color: brown;
  font-style: italic;
}

main {
  margin: 0 2em;
}

div {
  background-color: lightgrey;
  margin-bottom: 1em;
  padding: .1em 1em;
  border: 2px solid gray;
}

.my-custom-class {
  color: rgb(0, 92, 0);
  background-color: rgb(136, 207, 136);
  border-color: green;
}

#my-custom-id {
  background-color: lightpink;
  border-color: darkred;
  margin-left: 2em;
  margin-right: 2em;
  border-radius: .5em;
  border-width: 5px;
}

Demo

Prøv det kørende eksempel.

Materiale

Eksempler

Quiz

Css zen garden

Eksempler på forskellige styles brugt på den samme html struktur.

Subsections of Css

Flexbox Layout

Dette eksempel viser hvordan man kan bruge flexbox layout til at lave et layout på en webside.

Det består af et enkelt html dokument og en tilhørende css fil.

Her er den html struktur der er brugt.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>Page Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" type="text/css" media="screen" href="style.css" />
  </head>
  <body>
    <div class="top">
      <div class="box">logo</div>
      <div class="box">Main Title</div>
    </div>
    <div class="menu">
      <ul>
        <li>pkt L1</li>
        <li>pkt L2</li>
        <li>pkt L3</li>
        <li>pkt L4</li>
      </ul>
      <ul>
        <li>pkt R1</li>
        <li>pkt R2</li>
      </ul>
    </div>
    <div class="middle">
      <div class="box">1</div>
      <div class="box">2</div>
      <div class="box">3</div>
      <div class="box">4</div>
      <div class="box">5</div>
      <div class="box">6</div>
      <div class="box">7</div>
      <div class="box">8</div>
      <div class="box">9</div>
    </div>
    <div class="bottom-wrapper">
      <footer class="bottom">
        <div class="box">1</div>
        <div class="box">2</div>
        <div class="box">3</div>
      </footer>
    </div>
  </body>
</html>

CSS filen ser således ud.

.box {
  background-color: #f1f1f1;
  min-width: 100px;
  margin: 10px;
  padding: 10px;
  text-align: center;
  font-size: 30px;
}

div {
  margin: 0;
  padding: 0;
}

body {
  margin: 0;
  padding: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: burlywood;
}

.top {
  display: flex;
  background-color: green;
}

.menu {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  background-color: brown;
}

.menu ul {
  display: flex;
  flex-direction: row;
  list-style: none;
  background-color: aqua;
  padding: 0;
}

.menu>ul:last-child {
  display: flex;
  align-items: flex-end;
  list-style: none;
  background-color: purple;
}

.menu li {
  text-align: center;
  padding: 5px;
  margin: 3px;
  border: 2px solid black;
  background-color: olive;
}

.menu li:hover {
  background-color: chartreuse;
}

.middle {
  display: flex;
  flex-wrap: wrap;
}

.bottom-wrapper {
  margin-top: auto;
}

.bottom {
  display: flex;
  flex: 1;
  flex-direction: row;
  justify-content: space-around;
  background-color: black;
}

Demo

Prøv det kørende eksempel.

Materiale

CSS Flexbox

Eksempler

Spil

Grid Layout

Dette eksempel viser hvordan man kan bruge css grid til at lave et responsive layout på en webside.

Det består af et enkelt html dokument og en tilhørende css fil.

Her er den html struktur der er brugt.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>CSS grid layout</title>
  <link rel="stylesheet" type="text/css" href="style.css" >
</head>
<body>

<div class="grid-container">
  <div class="item1"><h2>
    Header
  </h2>
  <p>Prøv at ændre i bredden på vinduet og læg mærke til hvad der sker med layoutet.</p>
</div>
  <div class="item2">
    <h2>Menu</h2>
    <ul>
      <li><a href="#">Menu item 1</a></li>
      <li><a href="#">Menu item 2</a></li>
      <li><a href="#">Menu item 3</a></li>
      <li><a href="#">Menu item 4</a></li>
      <li><a href="#">Menu item 5</a></li>
    </ul>
    <p>Lorem ipsum dolor sit amet consectetur, adipisicing elit.</p>
  </div>
  <div class="item3">
    <h2>Main</h2>
    <div class="breaking">
      <h2>Under udarbejdelse</h2>
    </div>
    <p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Aperiam tenetur, quidem reiciendis itaque beatae dolores consequuntur eos fugiat iste consequatur rem molestias, exercitationem veniam nisi iure minima fuga illum ea.</p>
    <p>Quod voluptates accusantium rerum a cum, quidem modi velit voluptatum magnam corrupti repudiandae minus, eveniet ipsum sunt soluta labore aliquam possimus. Natus tempora itaque eos aliquid earum quae asperiores blanditiis!</p>
    <p>Itaque velit molestias provident amet magni voluptas, voluptatibus maxime esse. Quaerat id molestias odio dolore animi nemo, suscipit modi magnam quasi temporibus omnis sint natus ducimus dignissimos labore fugit voluptatem!</p>
    <p>Similique vitae architecto sequi error soluta nobis iste quos voluptatem expedita repudiandae numquam fugit fugiat, debitis omnis incidunt ipsam possimus sint magni quas cumque aspernatur! Voluptatem saepe sed quis corporis.</p>
  </div>  

  <div class="item4">
    <h2>Right</h2>
      <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quia, repellat. Ducimus accusamus temporibus cum perspiciatis, incidunt illo saepe error ex ad reiciendis at magni, deleniti velit in voluptatibus iusto eos.</p>
      <p>Voluptatum, dicta quis iste cumque magni harum quaerat cupiditate adipisci culpa libero quae accusantium praesentium excepturi quod eum obcaecati dolorum iusto aperiam est. Soluta, enim? Culpa autem temporibus rem eaque.</p>
      <p>Possimus animi minima aliquam molestiae laboriosam sit aliquid tenetur ut consectetur deserunt architecto, perspiciatis facere earum magnam totam minus quisquam incidunt placeat optio quidem! Minima, repellendus. Natus dolorum optio ducimus.</p>
      <p>Expedita aliquid, quam iusto voluptas totam possimus laboriosam earum fugiat ducimus, esse soluta rerum, non iure. Ex nemo animi molestias aliquid aperiam placeat ducimus qui numquam provident nostrum, magnam ipsa.</p>
  </div>
  <div class="item5"><h2>Footer</h2></div>
</div>

</body>
</html>

CSS filen ser således ud.

.item1 { grid-area: header; }
.item2 { grid-area: menu; }
.item3 { grid-area: main; }
.item4 { grid-area: right; }
.item5 { grid-area: footer; }

.grid-container {
  display: grid;
  grid-template-areas:
    'header header header header header header'
    'menu main main main right right'
    'menu footer footer footer right right';
  grid-gap: 10px;
  background-color: #2196F3;
  padding: 10px;
}

@media only screen and (max-width: 70em) {
  .grid-container {
    background-color: rgb(10, 192, 10);
    grid-template-areas:
      'header header '
      'main main '
      'menu right'
      'footer footer';
  }
}

@media only screen and (max-width: 50em) {
  .grid-container {
    background-color: rgb(226, 45, 32);
    grid-template-areas:
      'header'
      'main'
      'menu'
      'right'
      'footer';
  }
}

.grid-container > div {
  background-color: rgba(218, 226, 231, 0.822);
  padding: 1em 1em;
  font-size: 30px;
}

.breaking {
  text-align: center;
  margin-top: 1em;
  padding: 1em;
  background-color: yellow;
  border-radius: .5em;
  -webkit-box-shadow: 8px 13px 16px 0px rgba(0, 0, 0, 0.85);
  -moz-box-shadow: 8px 13px 16px 0px rgba(0, 0, 0, 0.85);
  box-shadow: 8px 13px 16px 0px rgba(0, 0, 0, 0.85);
}

Demo

Prøv det kørende eksempel.

Materiale

Spil

Subsections of Arduino

Button Trigger

Dette eksempel viser hvordan man kan måle tiden mellem to hændelser.

  • Pin 2: starter timer når forbindelsen til GND afbrydes.
  • Pin 3: stopper tidtagningen når forbindelsen til GND sluttes.

Den indbyggede LED er tændt, mens tidtagningen er i gang.

const int startPin = 2;
const int gatePin = 3;
const int ledPin =  13;

int isStarted = false;
int hasTriggeredGate = false;

unsigned long startTime = 0;
unsigned long gateTime = 0;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(startPin, INPUT_PULLUP);
  pinMode(gatePin, INPUT_PULLUP);

  Serial.begin(9600);
  Serial.println("Ready...");
}

void loop() {
  int startState = digitalRead(startPin);

  // Turn on LED while measurement is running
  int measRunning = isStarted && !hasTriggeredGate;
  digitalWrite(ledPin, measRunning);

  // Handle start trigger
  if (HIGH == startState && !isStarted) {
    startTime = millis();
    isStarted = true;
    Serial.print("Start time 1: ");
    Serial.println(startTime);
  }

  // Handle gate trigger
  int gateState = digitalRead(gatePin);
  if (LOW == gateState && !hasTriggeredGate) {
    gateTime = millis();
    hasTriggeredGate = true;
    Serial.print("Gate time: ");
    Serial.println(gateTime);
    Serial.print("Time diff: ");
    unsigned long timeDiff = gateTime - startTime; 
    Serial.println(timeDiff);
  }  
}

Neopixel

Dette eksempel laver et “løbelys” med blå farver.

Hardware setup

Arduino og Neopixel LED array

Arduino og LED array hardware

LED modulerne er forbunde i en kæde, så data signalets output er forbundet til input på næste LED modul. Alle modulerne forsynes med 5V fra arduino. Der er monteret en kondensator for at udjævne spændingen. Datasignalet tages fra pin 6 på Arduino.

Arduino & NeoPixel

For at kunne styre arrayet af NeoPixel LED’er kan du benytte softwarebiblioteket Adafruit Neopixel.

Det kan tilføjes til din Arduino IDE installation ved at bruge udvidelsesværkttøjet (Værktøjer –> Manage libraries…), som vist på dette skærmbillede.

Arduino Library manager

Søg efter neopixel i Arduino Library manager

Der følger en del eksempelkode med i til Neopixel biblioteket til Arduino. Denne guide beskriver hvordan man bruger eksempler i Arduino.

Prøv f.eks. det der hedder simple, i Adafruit Neopixel biblioteket. Husk at ændre antallet af LED’er i koden, så det passer til hardwaren. Bemærk også, at du skal bruge samme output pin i koden, som du har forbundet med ledninger.

Kode eksempel

Her er en sketch, der laver et “løbelys” med blå farver.

#include <Adafruit_NeoPixel.h>

// Which pin on the Arduino is connected to the NeoPixels?
#define PIN        6 // On Trinket or Gemma, suggest changing this to 1

// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS 10

Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

// Time (in milliseconds) to pause between pixels
#define DELAYVAL 500

void setup() {
  // INITIALIZE NeoPixel strip object (REQUIRED)
  pixels.begin();
}

void loop() {
  pixels.clear(); // Set all pixel colors to 'off'

  // The first NeoPixel in a strand is #0, second is 1, all the way up
  // to the count of pixels minus one.
  for(int i=0; i<NUMPIXELS; i++) { // For each pixel...

    // pixels.Color() takes RGB values, from 0,0,0 up to 255,255,255
    pixels.setPixelColor(i, pixels.Color(0, 20, 150));

    // Send the updated pixel colors to the hardware.
    pixels.show();

    // Pause before next pass through loop
    delay(DELAYVAL);
  }
}

Materiale

Hex String

Dette eksempel illustrerer hvordan man kan arbejde med strenge, og konvertere de enkelte bytes i en streng til hex tal med foranstillede nuller.

char my_str[] = {72, 101, 106, 44, 32, 10, 86, 101, 114, 100, 101, 110, 33, 0};

char outputBuffer[3];

// Helper function for converting byte value to 2-digit hex string
void byte2HexStr(byte val, char* outputBuffer){
  const char HEX_DIGITS[16] = "0123456789ABCDEF";

  byte upper_nibble_index = (val & 0xf0) >> 4;
  byte lower_nibble_index = val & 0xf;

  outputBuffer[0] = HEX_DIGITS[upper_nibble_index];
  outputBuffer[1] = HEX_DIGITS[lower_nibble_index];
  outputBuffer[2] = '\0';
}


void setup()
{
  Serial.begin(115200);

  // Output some example strings
  Serial.println("Content:");
  Serial.println(my_str);
  Serial.println();

  Serial.println("Content, decimal values of bytes:");
  for(int i = 0; i < sizeof(my_str); i++) {
    Serial.print(my_str[i], DEC);
    Serial.print(" ");
  }
  Serial.println('\n');

  Serial.println("Content, hex values of bytes:");
  for(int i = 0; i < sizeof(my_str); i++) {
    Serial.print(my_str[i], HEX);
    Serial.print(" ");
  }
  Serial.println('\n');

  Serial.println("Content, hex values zero-padded bytes:");
  for(int i = 0; i < sizeof(my_str); i++) {
    byte2HexStr(my_str[i], outputBuffer);
    Serial.print(outputBuffer);
    Serial.print(" ");
  }
  Serial.println('\n');

  Serial.println("Content, append to string object:");
  String myStrObject = "";
  for(int i = 0; i < sizeof(my_str); i++) {
    myStrObject += String(my_str[i], HEX);
  }
  Serial.println(myStrObject);
  Serial.println();

  Serial.println("Content, append to string object using byte2HexStr helper:");
  myStrObject = "";
  for(int i = 0; i < sizeof(my_str); i++) {
    byte2HexStr(my_str[i], outputBuffer);
    myStrObject += outputBuffer;
  }
  Serial.println(myStrObject);
  Serial.println();

  Serial.println("Content, append to string object with separator:");
  myStrObject = "";
  for(int i = 0; i < sizeof(my_str); i++) {
    myStrObject += String(my_str[i], HEX);

    // add separator except for the last element
    if(i < (sizeof(my_str) - 1)){
      myStrObject += ":";
    }
    
  }
  Serial.println(myStrObject);
  Serial.println();

  Serial.println("Content, append to string object using byte2HexStr helper with separator:");
  myStrObject = "";
  for(int i = 0; i < sizeof(my_str); i++) {
    byte2HexStr(my_str[i], outputBuffer);
    myStrObject += outputBuffer;

    // add separator except for the last element
    if(i < (sizeof(my_str) - 1)){
      myStrObject += ":";
    }
    
  }
  Serial.println(myStrObject);
  Serial.println();

}

void loop()
{
}

Når eksemplet køres på en arduino generes dette output på seriel porten.

Bemærk at baudrate er sat til 115200.

Content:
Hej, 
Verden!

Content, decimal values of bytes:
72 101 106 44 32 10 86 101 114 100 101 110 33 0 

Content, hex values of bytes:
48 65 6A 2C 20 A 56 65 72 64 65 6E 21 0 

Content, hex values zero-padded bytes:
48 65 6A 2C 20 0A 56 65 72 64 65 6E 21 00 

Content, append to string object:
48656a2c20a56657264656e210

Content, append to string object using byte2HexStr helper:
48656A2C200A56657264656E2100

Content, append to string object with separator:
48:65:6a:2c:20:a:56:65:72:64:65:6e:21:0

Content, append to string object using byte2HexStr helper with separator:
48:65:6A:2C:20:0A:56:65:72:64:65:6E:21:00

Materiale

Json Command Receiver

Dette eksempel viser hvordan man kan bruge JSON data formatet til at udveksle information med en arduino og sende kommandoer via seriel porten.

Ved at sende en passende kommando kan man ændre på hastighed og duty cycle for en blinkende LED.

For nemt at kunne arbejde med JSON i Arduino koden benyttes biblioteket Arduino JSON. Derfor er det nødvendigt at installere dette på udviklingsmaskinen, inden denne sketch kan kompileres, det klares via. Arduino Library Manager, se hvordan du installerer det her.

#include <ArduinoJson.h>

// Allocate the JSON document
//
// Inside the brackets, 200 is the capacity of the memory pool in bytes.
// Don't forget to change this value to match your JSON document.
// Use arduinojson.org/v6/assistant to compute the capacity.
//  StaticJsonDocument<200> doc;

// StaticJsonDocument<N> allocates memory on the stack, it can be
// replaced by DynamicJsonDocument which allocates in the heap.
//
DynamicJsonDocument doc(200);

int periodMs = 2000;
int dutyPct = 50;

void setup()
{
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);

  Serial.begin(115200);
  // wait for serial port to connect. Needed for native USB
  while (!Serial)
  {
    continue;
  }
  Serial.println("Ready for commands");
}

void loop()
{
  myLedControl();
  parseCommands();
}


// Example of test input json
/* 
{"ts": 1000, "duty": 10 }
 */
void parseCommands()
{
  // reply only when you receive data:
  if (Serial.available() > 0)
  {

    // Deserialize the JSON document
    DeserializationError error = deserializeJson(doc, Serial);

    Serial.println("Received:");
    serializeJson(doc, Serial);
    Serial.println();

    // Test if parsing succeeds.
    if (error)
    {
      Serial.print(F("deserializeJson() failed: "));
      Serial.println(error.c_str());
      return;
    }

    if (doc["ts"])
    {
      unsigned int ts = doc["ts"];
      Serial.print("got ts: ");
      Serial.println(ts);
      periodMs = ts;
    }

    if (doc["duty"])
    {
      int duty = doc["duty"];
      Serial.print("got duty: ");
      Serial.println(duty);
      duty = constrain(duty, 0, 100);
      Serial.print("constrained duty to [0, 100], using: ");
      Serial.println(duty);
      dutyPct = duty;
    }

  }
}

// toggle the builtin LED state
void myLedControl()
{
  //map(value, fromLow, fromHigh, toLow, toHigh)
  int highDelayMs = map(dutyPct, 0, 100, 0, periodMs);
  int lowDelayMs = periodMs - highDelayMs;
  
  digitalWrite(LED_BUILTIN, HIGH);
  delay(highDelayMs);
  digitalWrite(LED_BUILTIN, LOW);
  delay(lowDelayMs);
}

NB! Serielporten er sat til at køre 115200 Baud.

Prøv at sende nogle kommandoer vha. serial monitor, og læg mærke til hvordan blinkrate og duty cycle ændres for den inbyggede LED på Arduino.

{"ts": 1000, "duty": 10 }
{"ts": 500, "duty": 80 }

Når eksemplet køres på en Arduino og de to ovenstående kommandoer sendes en ad gangen, kommer der dette output fra Arduino på seriel porten.

Ready for commands
Received:
{"ts":1000,"duty":10}
got ts: 1000
got duty: 10
constrained duty to [0, 100], using: 10
Received:
{"ts":500,"duty":80}
got ts: 500
got duty: 80
constrained duty to [0, 100], using: 80

Materiale

Task Loop using Function Pointers

Dette eksempel illustrerer, hvordan man kan bruge function pointers til at køre forskellige opgaver med hvert sit interval, uden at de blokerer for hinanden i længere tid end det tager at eksekvere en enkelt opgave.

typedef struct MyTask {
   void (*handler)();
   int intervalMs;
   int taskId;
   unsigned long lastRunMs;
} MyTask;


// Forward declaration of the task runner functions
void task_main();
void task_A();
void task_B();
void task_C();

MyTask tasks[] = {
  {.handler=task_main},
  {task_A,300},
  {task_B,500},
  {task_C,3000}
};

const int arrSize = sizeof(tasks)/sizeof(MyTask);

void setup() {
  // initialize serial communication at 115200 bits per second:
  Serial.begin(115200);
  
  Serial.print("Task count: ");
  Serial.println(arrSize);

  for(int i=0 ; i<arrSize; i++){
    if(tasks[i].intervalMs <= 0){
      tasks[i].intervalMs = (i+1)*100;
    }
    String msg = "";
    msg = "task id: ";
    msg += i;
    msg += ", interval ";
    msg += tasks[i].intervalMs;
    Serial.println(msg);
  }
  
  Serial.println("Setup DONE");
}

// example of non-blocking asyncronous wait loop using function pointers
void loop() {
  for(int i=0 ; i<arrSize; i++){
    runInterval(&tasks[i]);
  }
}

void runInterval(struct MyTask *t){
  unsigned long tickMs = millis();
  unsigned long diffMs = tickMs - t->lastRunMs;

  // Handle first run
  if(0 == t->lastRunMs){
    t->lastRunMs = tickMs;
  }
  bool shouldRun = (t->intervalMs < diffMs);
  if(!shouldRun){
    return;
  }
  // Store last run
  t->lastRunMs = tickMs;

  // Perform the process by calling the handler function
  (t->handler)();
}

// Create some tasks to run in the example
void task_main(){
  static int runCount = 0;
  Serial.print(".");
  if(0 == runCount % 30){
    Serial.println();
    Serial.print(runCount);
    Serial.print(" : ");
  }
  runCount++;
}

void task_A(){
  Serial.print("A");
}

void task_B(){
  Serial.print("B");
}

void task_C(){
  Serial.print("C");
}

Når eksemplet køres på en arduino generes dette output på seriel porten.

Bemærk at baudrate er sat til 115200.

Task count: 4
task id: 0, interval 100
task id: 1, interval 300
task id: 2, interval 500
task id: 3, interval 3000
Setup DONE
.
0 : .A..B.A...A.B..A...BA...A..B.A...A.B..A...CBA..
30 : .A..B.A...A.B..A...BA...A..B.A...A.B..A...CBA..
60 : .A..B.A...A.B..A...BA...A..B.A...A.B..A...CBA..
90 : .A..B.A...A.B..A...BA...A..B.A...A.B..A..C.BA..
120 : .A.B..A...AB...A..B.A...A.B..A..A.B..A...CBA...

Materiale

Json Beacon

Dette eksempel illustrerer hvordan man kan generere json output på seriel porten, og er tænkt som en en stub der kan bruges til at arbejde med seriel input på en en anden computer, f.eks. vha. node.js.

#include <ArduinoJson.h>

const int WAIT_MS_MIN = 500;
const int WAIT_MS_MAX = 8000;
const unsigned char TAG_COUNT = 5;

char* tagIds[TAG_COUNT] = {
  "4B 61 73 70 65 72 36 37 38 39 3A 3B",
  "E2 00 00 1B 63 15 02 48 16 00 DB 24",
  "E2 00 00 1B 63 15 02 48 17 00 DA F8",
  "E2 00 00 1B 63 15 02 48 17 20 DA F4",
  "E2 00 00 1B 63 15 02 48 15 90 DB 2C",
};

int pinOne = 2;
int pinTwo = 3;

void setup() {
  Serial.begin(115200);

   pinMode(pinOne, INPUT_PULLUP);
   pinMode(pinTwo, INPUT_PULLUP);
}

void loop() {
  int waitTime = random(WAIT_MS_MIN, WAIT_MS_MAX);
  int tagIdIndex = random(0, TAG_COUNT);

  bool isRandom = true;

  if(!digitalRead(pinOne)){
    tagIdIndex = 0;
    isRandom = false;
    waitTime = 300;
  }

  if(!digitalRead(pinTwo)){
    tagIdIndex = 1;
    isRandom = false;
    waitTime = 150;
  }

  DynamicJsonDocument doc(1024);
  // Insert some debugging information
  doc["isRandom"] = isRandom;
  doc["waitTime"] = waitTime;
  doc["tagIdIndex"] = tagIdIndex;
  // Insert the selected tag ID
  doc["tagId"] = tagIds[tagIdIndex];
  
  serializeJson(doc, Serial);

  Serial.println();
  delay(waitTime);
}

Der vælges en tilfældig værdi i tagIds arrayet, som så serialiseres som json sammen med oplysning om index og delay tiden, og udskrives på serielporten med tilfældige tidsintervaller.

Det er også mulighed for at vælge 2 bestemte tags ved at trække input pin 2 eller 3 lav, ved fysisk at forbinde til GND.

NB! Serielporten er sat til at køre 115200 Baud.

For nemt at kunne arbejde med JSON i Arduino koden benyttes biblioteket Arduino JSON. Derfor er det nødvendigt at installere dette på udviklingsmaskinen, inden denne sketch kan kompileres, det klares via. Arduino Library Manager, se hvordan du installerer det her.

Eksempel på output

Output på serielporten kommer til at se ca. sådan ud. Bemærk at det er kopieret fra Serial Monitor i Arduino IDE, og timestamps er slået til, derfor er der et tidsstempel på alle linier i output. Tidstempler, delay intervaller og rækkefølgen af tagId’er vil være forskellig for hver kørsel, da de bliver genereret tilfældigt.

21:00:56.034 -> {"waitTime":2492,"tagIdIndex":2,"tagId"B 63 15 02 48 17 00 DA F8"}
21:00:56.034 -> {"waitTime":2307,"tagIdIndex":4,"tagId":"E2 00 00 1B 63 15 02 48 15 90 DB 2C"}
21:00:58.350 -> {"waitTime":3073,"tagIdIndex":3,"tagId":"E2 00 00 1B 63 15 02 48 17 20 DA F4"}
21:01:01.410 -> {"waitTime":6930,"tagIdIndex":2,"tagId":"E2 00 00 1B 63 15 02 48 17 00 DA F8"}
21:01:08.346 -> {"waitTime":3044,"tagIdIndex":3,"tagId":"E2 00 00 1B 63 15 02 48 17 20 DA F4"}
21:01:11.414 -> {"waitTime":5923,"tagIdIndex":4,"tagId":"E2 00 00 1B 63 15 02 48 15 90 DB 2C"}
21:01:17.328 -> {"waitTime":4940,"tagIdIndex":0,"tagId":"4B 61 73 70 65 72 36 37 38 39 3A 3B"}
21:01:22.256 -> {"waitTime":2492,"tagIdIndex":2,"tagId":"E2 00 00 1B 63 15 02 48 17 00 DA F8"}
21:01:24.769 -> {"waitTime":5987,"tagIdIndex":3,"tagId":"E2 00 00 1B 63 15 02 48 17 20 DA F4"}

Materiale

C#

Noter og eksempler med C#.

Subsections of C#

C# Intro

Setup

For at kunne arbejde med C# kildekoden har vi brug for en god editor. Envidere får er der brug for en kompiler, så vi kan oversætte kildeteksten til et program der kan eksekveres på maskinen.

Code editor

Der er flere muligheder at vælge mellem når man skal arbejde med C# kode. For ikke at bruge en masse tid på at lære et nyt miljø at kende, vælger vi at bruge Visual Studio Code, da den kan bruges både på Windows og Mac, og det er den samme editor vi har brugt til at arbjede med javascript.

Efter du har installeret Visual Studio Code vil det være en fordel at installere en udvidelse, der gør det nemmere at arbejde med C# filer, kopilering, og debugging af disse.

For at installere udvidelsen vælges plugin i menuen, og søg efter C#, på listen skulle du gerne kunne finde dette C# plugin fra microsoft.

Compiler

C# adskiller sig fra javascript bl.a. ved, at det er nødvendigt at oversætte kildeteksten til et eksekverbart program, inden det kan afvikles på computeren. Derfor har vi brug for en kompiler.

Derfor skal vi hente og installere .NET Core.

Efter installationen er færdig kan du åbne en terminal f.eks. Powershell og verificere din version af .NET Core med kommandoen dotnet --version. På min maskine giver den dette output.

$ dotnet --version
5.0.401

Hvilket vil sige at jeg har version 5.0.401 installeret.

Opret projekt

Den nemmeste måde at starte et nyt .NET Core projekt er vha. terminalen. Start med at åbne en terminal i den mappe du ønsker at arbejde i.

For at oprette et nyt projekt skal du åbne en terminal og køre kommandoen.

dotnet new console -o my-console-app

Dette laver en ny console app med navnet my-console-app, og opretter det en undermappe med samme navn.

Afvikling fra kommando linjen

For at sikre at det virker efter hensigten, starter vi med at oversætte og køre programmet fra kommando linjen.

Skift til det netop oprettede projekt med kommandoen:

cd my-console-app

Du står nu i mappen med projektet, og kan nu oversætte (compile) og køre (run) det med kommandoen dotnet run. På min maskine giver det dette output.

my-console-app $ dotnet run
Hello World!

Hvis du kan få et lignede resultat er det lykkedes at kompilere og oversætte programmet.

Arbejd med C# i VS code

Nu er det vist tid til at rette lidt i koden, åben derfor mappen med projektet i VS code.

Åben filen Program.cs i projektet. Indholdet burde se således ud:

using System;

namespace hmm_demo
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

Prøv nu at rette den fremhævede linje, så den skriver Hej Programmering! i stedet for Hello World!. Husk at gemme filen.

C# extension

For at gøre det nemmere at arbejde med C# i VS Code skal vi have installeret en program udvidelse. Søg efter c# i extensions i VS code og tryk på install.

Screenshot af VS code hvor der er søgt efter C# udvidelsen.

Kompilering / Debug

Med den nye extension er det muligt at oversætte, køre og fejlsøge i C# programmer vha. knapper i VS Code.

For at køre programmet skal du vælge run -> debug i menuen, eller trykke på f5.

Måske kommer der en boks frem der beder dig vælge det udviklingsmiljø du vil bruge. Her skal du vælge .NET Core. Dette genererer instillinger i mappen .vscode, som bruges af C# udvidelsen når koden skal oversættes og køres.

Når programmet kører får jeg på min maskine dette output:

-------------------------------------------------------------------
You may only use the Microsoft .NET Core Debugger (vsdbg) with
Visual Studio Code, Visual Studio or Visual Studio for Mac software
to help you develop and test your applications.
-------------------------------------------------------------------
Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.10\System.Private.CoreLib.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Loaded 'C:\code\c-sharp\2021\my-console-app\bin\Debug\net5.0\my-console-app.dll'. Symbols loaded.
Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.10\System.Runtime.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.10\System.Console.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.10\System.Threading.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.10\System.Text.Encoding.Extensions.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Hello World!
The program '[14664] my-console-app.dll' has exited with code 0 (0x0).

Det meste er beskeder fra compiler og runtime miljøet, men der kommer også det forventede output fra koden jeg selv har skrevet, på den fremhævede linje.

Håndtering af stdin / stdout

Afhængig af typen af det program du laver, kan det være hensigtsmæssigt at ændre måden det eksekveres i fra VS Code på.

Der er 3 muligheder, som vælges ved at ændre i instillingerne i .vscode/launch.json. Prøv at ændre i parameteren console til en af disse værdier, og vælg den der passer bedst til dit brugsscenarie.

  • "console": "internalConsole"
  • "console": "integratedTerminal"
  • "console": "externalTerminal"

Hvis jeg vælger indstillingen externalTerminal, vises output fra mit program fra tidligere på denne måde på min windows 10 maskine.

Screenshot af terminal output.

Gitignore

Når .NET Core projektet bygges, bliver der genereret en række objektfiler og binære biblioteker osv. af kompileren. Disse er nødvendige for at programmet kan afvikles, men hvis de andre der arbejder selv har en kompiler installeret, kan disse nemt gendannes. Derfor kan det være nyttigt at undlade at tilføje genererede filer til versionsstyringssystemet.

Hvis man f.eks. benytter git kan man lave en liste af filer og mapper der skal ignoreres af git. Dette kan klares ved at oprette en fil med navnet .gitignore, i roden af projektet.

# files and folders to be ignored by git

# ignore build artifacts
bin/
obj/

Med dette indhold bliver indholdet af mapperne bin og obj ignoreret af git.

Man kan nemt justere i hvilke filer og mapper der skal ignoreres ved at ændre i .gitignore filen. Se også dokumentationen af gitignore.

Materiale

Command Line Interface Arguments

Eksemplet viser hvordan man kan bruge argumenter fra kommandolinjen til at påvirke afviklingen af et program.

using System;

namespace cmd_args_demo
{
  class Program
  {

    static void Main(string[] args)
    {
      ShowArguments(args);

      int multiplier = 7;
      if (args.Length > 0)
      {
        int.TryParse(args[0], out multiplier);
      }

      if (args.Length > 1)
      {
        int max = 10;
        int.TryParse(args[1], out max);
      }

      doStuff(multiplier, 10);
    }

    static void ShowArguments(string[] args)
    {
      for (int i = 0; i < args.Length; i++)
      {
        string item = args[i];
        Console.WriteLine($"argument {i} : {item}");
      }
    }

    static void doStuff(int multiplier, int iterationCount)
    {
      for (int i = 1; i <= iterationCount; i++)
      {
        int value = multiplier * i;
        Console.WriteLine($"{i} * {multiplier} : {value}");
      }
    }
  }
}

Materiale

Temperature Assessment

Eksemplet viser hvordan man kan bede en bruger om input, og bruge det til at vurdere temperaturen. NB! Temperaturen er i grader celcius.

using System;

namespace temperature_assessment
{
  class Program
  {
    private InputHandler inputHandler;
    private TemperatureAssessor temperatureAssessor;

    public Program(InputHandler ih, TemperatureAssessor ta)
    {
      this.inputHandler = ih;
      this.temperatureAssessor = ta;
    }

    public void Run()
    {
      do
      {
        double temperature = inputHandler.PromptForNumber("Hvad er temperaturen i grader celsius?");
        string result = temperatureAssessor.EvaluateTemperature(temperature);
        Console.WriteLine(result);
      } while (inputHandler.Confirm("Vil du prøve igen?"));
    }

    static void Main(string[] args)
    {
      Console.WriteLine("Temperatur vurdering");

      InputHandler inputHandler = new InputHandler();
      TemperatureAssessor temperatureAssessor = new TemperatureAssessor();
      Program myProgram = new Program(inputHandler, temperatureAssessor);

      myProgram.Run();
    }
  }

  class TemperatureAssessor
  {
    public string EvaluateTemperature(double temperature)
    {
      if (temperature <= 10) return "For koldt til at stå op - bliv i sengen";
      if (temperature <= 15) return "For koldt til at arbejde - bliv hjemme";
      if (temperature <= 20) return "OK temperatur til en tur i skoven";
      if (temperature <= 22) return "Perfekt pausetemperatur";
      return "For varmt til at arbejde - tag til stranden";
    }
  }

  class InputHandler
  {
    public double PromptForNumber(string message)
    {
      double value;
      bool isSuccess;
      do
      {
        string line = Prompt($"{message} ");
        isSuccess = Double.TryParse(line, out value);
        if (!isSuccess)
        {
          Console.Error.WriteLine("Ugyldigt input: Du skal skrive et tal.");
        }
      } while (!isSuccess);

      return value;
    }

    public bool Confirm(string question)
    {
      string confirm = "ja";
      string reject = "nej";
      string response;
      bool isValidResponse;
      do
      {
        response = Prompt($"{question} [{confirm}/{reject}] ");
        isValidResponse = response.Equals(reject) || response.Equals(confirm);
        if (!isValidResponse)
        {
          Console.Error.WriteLine($"Ugyldigt input: Du skal svare enten '{confirm}' eller '{reject}'");
        }
      } while (!isValidResponse);

      return response.Equals(confirm);
    }

    string Prompt(string message)
    {
      Console.Write($"{message} ");
      return Console.ReadLine();
    }
  }
}

Materiale

Guessing Game

Eksemplet viser hvordan man kan lave et “spil”, der går ud på at gætte det hemmelige tal.

using System;

namespace guessing_game
{

  class Program
  {
    static void Main(string[] args)
    {
      int min = 1;
      if (args.Length > 1)
      {
        if (!int.TryParse(args[0], out min))
        {
          Console.Error.WriteLine("Invalid Argument: minimum must be an integer.");
          return;
        }
      }

      int max = 1000;
      if (args.Length > 0)
      {
        string maxStr = (args.Length > 1)? args[1]: args[0];
        if (!int.TryParse(maxStr, out max))
        {
          Console.Error.WriteLine("Invalid Argument: maximum must be an integer.");
          return;
        }
      }


      if (!(min < max))
      {
        Console.Error.WriteLine("Invalid Argument: minimum must be greater than maximum");
        return;
      }

      GuessingGame game = new GuessingGame(min, max);

      do
      {
        game.play();
      }
      while (game.wantsRematch());
    }
  }

  class GuessingGame
  {
    private int min;
    private int max;

    public GuessingGame(int max)
    {
      this.min = 1;
      this.max = max;
    }

    public GuessingGame(int min, int max)
    {
      this.min = min;
      this.max = max;
    }

    public bool wantsRematch()
    {
      Console.Write("Prøv igen? (Y/n): ");
      string answer = Console.ReadLine();
      bool isAfirmative = "y".Equals(answer.ToLower()) || "".Equals(answer);
      return isAfirmative;
    }

    public void play()
    {
      Random randomNumberGenerator = new Random();
      int secretNumber = randomNumberGenerator.Next(min, max);

      Console.WriteLine($"Gæt et tal mellem {min} og {max}");

      bool done = false;
      while (!done)
      {
        Console.Write("Skriv dit gæt: ");
        var guessLine = Console.ReadLine();

        bool isValidInteger = int.TryParse(guessLine, out int guess);
        if (!isValidInteger)
        {
          Console.WriteLine("Dit gæt skal være et tal!");
          continue;
        }

        bool isInValidRange = min <= guess && guess <= max;
        if (!isInValidRange)
        {
          Console.WriteLine($"Dit gæt skal være melem {min} og {max}!");
          continue;
        }

        if (guess == secretNumber)
        {
          Console.WriteLine($"Rigtigt! Flot gættet. Det hemmelige tal er {secretNumber}");
          done = true;
        }
        else
        {
          if (guess > secretNumber)
          {
            Console.WriteLine("Dit gæt er for højt");
          }
          if (guess < secretNumber)
          {
            Console.WriteLine("Dit gæt er for lavt");
          }

          Console.WriteLine("Gæt igen");
        }
      }
    }
  }
}

Materiale

React

Eksempler med React.

Subsections of React

React Intro

Dette eksempel viser hvordan man kan starte med at arbejde med React.

For at gøre indlæringskurven knap så stejl, benyttes Create React App til at oprette projektstrukturen, så man ikke selv skal sætte det hele op fra bunden.

Forudsætninger

For at kunne komme i gang kræves en fungerende installation af node.js.

Start med at kontrollere din version af node og npm, hvilket kan gøres med disse kommandoer.

node --version
npm --version

På min maskine giver de følgende output.

$ node --version
v19.8.1
$ npm --version
9.6.0

I dette eksempel er benyttet node version v11.15.0 og npm version 6.7.0

Opret projekt strukturen

Sørg for at din terminal er i den mappe hvor du ønsker at oprette dit projekt. Dernæst kan du oprette et projekt med denne kommando.

npx create-react-app my-app

Efter scriptet er afsluttte med success, burde du så kunne skifte bibliotek til det oprettede projekt.

cd my-app

Derefter kan projektet køres med kommandoen.

npm start

Dette kører din react app i udviklings-mode, hvilket bla. vil sige at der kører en server på maskine, så du kan se din app ved at åbne http://localhost:3000 i en browser.

Når du åbner siden burde du se noget i stil med det der er vist på figuren.

Screenshot af den kørende react app.

React Components

Prøv nu at lave din egen react komponent.

For at undgå at blande en masse forskellige komponenter sammen i laves den nye komponent i en separat fil. Opret filen src/components/Clock.js og indsæt følgende.

import React from "react";

function Clock() {
  return <div>Klokken er: TODO</div>;
}

export default Clock;

Nu kan Clock komponenten benyttes i andre dele af react app’en. Åben filen src/App.js og erstat indholdet med dette.

import React from "react";
import "./App.css";

import Clock from "./components/Clock";

function App() {
  return (
    <div className="App">
        <Clock />
    </div>
  );
}

export default App;

Bemærk hvordan Clock komponenten importeres og indsættes i renderingen af App komponenten.

Styling

Vi har ikke brug for de css styles, der blev oprettet sammen med eksempel koden. Men lidt plads omkring indholdet er ok.

Erstat derfor indholdet af src/App.css med dette.

.App {
  padding: 1em;
}

State Hook

Det virker utilfredsstillende at bruge så meget energi på at lave et ur der ikke viser klokken.

Det kan løses med lidt javascript. Man kan tilknytte state til enhver react komponent, så i dette tilfælde vil vi gøre det med tiden for uret ved at tilføje disse ændringer til src/components/Clock.js.

import React, {useState} from "react";

function Clock() {
  const [time, setTime] = useState(new Date().toLocaleTimeString());
    
  return <div>Klokken er: {time}</div>;
}

export default Clock;

Nu vises klokken, men desværre skal man genindlæse siden for at få uret til at gå.

Effect Hook

For at få uret til at gå kan vi benytte en useEffect hook, der kaldes ved bestemte hændelser i komponentens livs-cyclus (life-cycle-events).

Vi er intereseret i at opdatere uret hvert sekund. Hvilket kan opnås ved at ændre i Clock komponenten igen, så filen src/components/Clock.js ender med at se sådan ud.

import React, { useState, useEffect } from "react";

function Clock() {
  const [time, setTime] = useState(new Date().toLocaleTimeString());

  const updateTime = () => {
    setTime(new Date().toLocaleTimeString());
  };

  useEffect(() => {
    setInterval(updateTime, 1000);
  });

  return <div>Klokken er: {time}</div>;
}

export default Clock;

Nu skulle uret gerne opdatere tiden en gang i sekundet.

Materiale

Components + Data

Her er et eksempel på hvordan man kan bruge properties på react komponenter til at vise udvalgte informationer, f.eks. fra en database eller et API.

Eksemplet bygger videre på den app der blev oprettet i React intro.

Input data

For at have noget data at vise, oprettes følgende datastruktur oprettet i filen src/lib/fake_highscores.js.

// Fake data - could come from a database or similar
const data = {
  userIdOne: {
    username: "jens",
    firstName: "Jens",
    lastName: "Hansen",
    scores: [
      { date: "2020-03-16T10:30:45", score: 7 },
      { date: "2020-03-16T12:30:45", score: 15 },
      { date: "2020-03-16T12:32:45", score: 10 },
    ]
  },
  userIdTwo: {
    username: "jj",
    firstName: "Jørgen",
    lastName: "Jyde",
    scores: [
      { date: "2020-03-16T11:30:42", score: 120 },
      { date: "2020-03-16T12:35:31", score: 12 },
      { date: "2020-03-16T12:37:20", score: 23 },
      { date: "2020-03-17T09:07:34", score: 17 },
      { date: "2020-03-17T09:31:10", score: 25 },
    ]
  },
  userIdThree: {
    username: "john",
    firstName: "John",
    lastName: "Doe",
    scores: [
      { date: "2020-03-16T10:57:45", score: 11 },
      { date: "2020-03-16T11:05:45", score: 8 },
      { date: "2020-03-16T11:21:12", score: 13 },
    ]
  }
};

export default data;

I en rigtig app kan vi forestille os at data er gemt i en database, i og ikke blot gemt i en statisk fil.

Præsentation i browser

For at kunne vise highscore data i browseren laves en par nye komponenter i filen src/components/Highscore.js, så den har dette indhold.

import React from "react";

import data from '../lib/fake_highscores';

function Highscore() {
  return (
    <div>
      <h1>Highscores</h1>
      {
        Object.values(data).map(item => (
          <User key={item.username} user={item} />
        ))
      }
    </div>
  );
}

const User = props => {
  return (
    <div>
      <h2>
        {props.user.firstName} {props.user.lastName}
      </h2>
      <p>Username: {props.user.username}</p>
      <table>
        <thead>
          <tr>
            <th>Time</th>
            <th>Score</th>
          </tr>
        </thead>

        <tbody>
          {props.user.scores.map((item, i) => (
            <tr key={i}>
              <td>{item.date}</td>
              <td>{item.score}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};

export default Highscore;

Bemærk hvordan der i Highscore komponenten itereres over data objektets værdier, og oprettes en react komponenter af typen User for hver entry.

Derefter indsættes Highscore komponenten i vores App komponent, så src/App.js nu ser således ud:

import React from 'react';
import './App.css';

import Clock from './components/Clock'
import Highscore from './components/Highscore';

function App() {
  return (
    <div className="App">
      <Clock />
      <Highscore />
    </div>
  );
}

export default App;

Styling af tabeller

For at få html tabellerner med highscores til at se en smule pænere ud kan vi tilføje lidt css, så filen src/App.css nu ser således ud.

.App {
  padding: 1em;
}

table {
  border-collapse: collapse;
  width: 30em;
}

th,
td {
  padding: 8px;
  text-align: left;
  border-bottom: 1px solid #ddd;
}

tr:hover {
  background-color: #f5f5f5;
}

Materiale

React + Firebase

Dette eksempel viser hvordan man kan integrere Firebase Firestore i en react app. Det tager udgangspunkt i samme simple eksempel, som blev brugt i hotdog demoen. Dog vil vi her nøjes med at læse data fra Firestore.

Eksemplet bygger videre på den app der blev oprettet i React intro, og udvidet i Components + Data.

Installation af Firebase

For at kunne benytte funktionaliteter fra firebase i vores react app, er det nødvendigt at installere firebase SDK. Dette kan klares med følgende kommando, som henter pakken fra npm og tilføjer den til package.json, så vi har styr på projektets afhængigheder.

npm install firebase --save

Konfiguration af Firebase

Til dette eksempel er det ikke nødvendigt at oprette en ny firebase app, da den bruge samme Firestore database som denne demonstrationen fra tidligere. Det er dog stadig nødvendigt at konfigurere firebase SDK til at kommunikere med den ønskede backend.

For at kunne benytte firebase på en nem måde i flere forskellige komponenter laves opsætning af og initialisering af firebase SDK i filen src/lib/Firebase.js, som så kan importeres af de komponenter der skal bruge firebase.

import firebase from "firebase/app";
import "firebase/firestore";

// Your web app's Firebase configuration
const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_FIREBASE_APP_NAME.firebaseapp.com",
  databaseURL: "https://YOUR_FIREBASE_APP_NAME.firebaseio.com",
  projectId: "YOUR_FIREBASE_APP_NAME",
  storageBucket: "YOUR_FIREBASE_APP_NAME.appspot.com",
  messagingSenderId: "YOUR_SENDER_ID",
  appId: "YOUR_APP_ID",
  measurementId: "YOUR_MEASUREMENT_ID"
};

// Initialize Firebase
firebase.initializeApp(firebaseConfig);

export default firebase;

De konkrete settings, der skal bruges i dit projekt, finder du i firebase konsollen ved at gå ind i den app du vil bruge som backend. Åben derefter “Settings” og “General”. Måske skal der oprettes et web endpoint, hvis du ikke allerede har gjort det. For yderligere forklaringer kan denne intro sikkert bruges.

Læs fra Firestore

Nu er fundament på plads, så det er tid til at lave en react komponent, der henter data fra Firestore. Det gøres i filen src/components/Hotdog.js, og koden er som vist herunder.

import React, { useEffect, useState } from "react";

// import the firebase configuration settings
import firebase from "../lib/Firebase";

// initialize firestore
const firestore = firebase.firestore();

const docRef = firestore.doc("samples/sandwichData");

function Hotdog() {

  const thingToDoWhithDocumentData = doc => {
    if (doc && doc.exists) {
      const myData = doc.data();
      setHotdogStatus(myData.hotdogStatus);
    }
  };

  const getRealtimeUpdates = () => {
    docRef.onSnapshot(thingToDoWhithDocumentData);
  };

  const [hotdogStatus, setHotdogStatus] = useState("");

  useEffect(() => {
    // subscribe to realtime updates when component loads or updates
    getRealtimeUpdates();
  });

  return (
    <div className="hotdog-status">
      <b>Breaking News</b>
      <h1>Hotdog status: <span>{hotdogStatus}</span></h1>
    </div>
  );
}

export default Hotdog;

Derefter mangler vi blot at indsætte Hotdog i App komponenten, så src/App.js nu ser således ud:

import React from 'react';
import './App.css';

import Clock from './components/Clock'
import Highscore from './components/Highscore';
import Hotdog from './components/Hotdog';

function App() {
  return (
    <div className="App">
      <Clock />
      <Hotdog />
      <Highscore />
    </div>
  );
}

export default App;

Vi tilføjer en smule css i src/App.css for at fremhæve den vigtige information om hotdogs.

.App {
  padding: 1em;
}

table {
  border-collapse: collapse;
  width: 30em;
}

th,
td {
  padding: 8px;
  text-align: left;
  border-bottom: 1px solid #ddd;
}

tr:hover {
  background-color: #f5f5f5;
}

.hotdog-status {
  display: inline-block;
  margin-top: 1em;
  padding: 1em;
  background-color: yellow;
  border-radius: .5em;
  -webkit-box-shadow: 8px 13px 16px 0px rgba(0, 0, 0, 0.85);
  -moz-box-shadow: 8px 13px 16px 0px rgba(0, 0, 0, 0.85);
  box-shadow: 8px 13px 16px 0px rgba(0, 0, 0, 0.85);
}

App Demo med Hotdog Status

Den kørende app burde nu ligne noget i stil med det der er vist på figuren herunder.

Screenshot af den kørende react app.

Materiale

React + Firestore Fake Weather

Dette eksempel bygger videre på react eksemplet med firestore, og demonstrationen af hvordan man kan uploade data med node.js.

Statisk data visning

Først laves en fil med samme struktur som forventes at være i firestore databasen, når upload scriptet har kørt nogle gange. Data strukturen laves i filen src/lib/fake_weather.js, med dette indhold.

import firebase from "../lib/Firebase";

const Timestamp =  firebase.firestore.Timestamp;

// helper for generating firestore timestamps
const t = timeString => Timestamp.fromDate(new Date(timeString));

// Fake data - could come from a database or similar
const data = [
  {
    lastUpdate: t('2020-03-13T11:30:42'),
    key: "dataIdOne",
    windMeasurements: [
      { time: t('2020-03-13T10:20:42'), windSpeed: 12, windDir: 277 },
      { time: t('2020-03-13T11:30:42'), windSpeed: 7, windDir: 270 },
    ]
  },
  {
    lastUpdate: t("2020-03-14T09:31:10"),
    key: "dataIdTwo",
    windMeasurements: [
      { time: t("2020-03-14T09:07:34"), windSpeed: 17, windDir: 179 },
      { time: t("2020-03-14T09:31:10"), windSpeed: 25, windDir: 183 },
      { time: t("2020-03-14T11:30:42"), windSpeed: 12, windDir: 180 },
      { time: t("2020-03-14T12:37:20"), windSpeed: 23, windDir: 171 },
    ]
  },
];

export default data;

Derefter laves en ny react komponent i src/components/Weather.js, som viser indholdet i browseren.

import React, { useEffect, useState } from "react";

import fakeWeatherData from '../lib/fake_weather';

function Weather() {
  return (
    <div>
      <h1>Fake Weather data (from file)</h1>
      <MeasurementList weatherData={fakeWeatherData} />
    </div>
  );
}

const MeasurementList = props => {
  return (
    <div>
      {
        props.weatherData.map(it => (
          <MeasurementDay key={it.key} item={it} />
        ))
      }
    </div>
  )
}

const MeasurementDay = props => {
  return (
    <div>
      <h2>Fake wind data, Updated: {props.item.lastUpdate.toDate().toLocaleString()}</h2>
      <table>
        <thead>
          <tr>
            <th>Time</th>
            <th>Wind speed</th>
            <th>Wind direction</th>
          </tr>
        </thead>

        <tbody>
          {
            props.item.windMeasurements.map((it, i) => (
              <tr key={i}>
                <td>{it.time.toDate().toISOString()}</td>
                <td>{it.windSpeed}</td>
                <td>{it.windDir}</td>
              </tr>
            ))
          }
        </tbody>
      </table>
    </div>
  );
};

export default Weather;

Bemærk hvordan vi først importeret datastrukturen fra filen src/lib/fake_weather.js, sender den ind i MeasurementList komponenten og itererer over indholdet af data i MeasurementList og MeasurementBody komponenterner.

Weather komponenten indsættes i src/App.js, således:

import React from 'react';
import './App.css';

import Clock from './components/Clock'
import Highscore from './components/Highscore';
import Hotdog from './components/Hotdog';
import Weather from './components/Weather';

function App() {
  return (
    <div className="App">
      <Clock />
      <Hotdog />
      <Weather />
      <Highscore />
    </div>
  );
}

export default App;

Firestore itegration

For at hente data ud fra firestore skal der tilføjes mere kode i Weather komponenten.

import React, { useEffect, useState } from "react";

import fakeWeatherData from '../lib/fake_weather';

// import the firebase configuration settings
import firebase from "../lib/Firebase";

// initialize firestore
const firestore = firebase.firestore();

function Weather() {

  const handleData = snapshot => {
    if (snapshot.empty) {
      console.log("No matching documents");
      return;
    }

    let weatherDataArray = [];

    snapshot.forEach(doc => {
      let myData = doc.data();
      myData["key"] = doc.id;
      weatherDataArray.push(myData);
    })

    setWeatherData(weatherDataArray);
  };

  const subscribeToRealtimeUpdates = () => {
    const query = firestore.collection("weather")
      .orderBy('lastUpdate', 'desc')
      .limit(3);
    query.onSnapshot(handleData);
  };

  useEffect(
    () => {
      subscribeToRealtimeUpdates();
    },
    // provide empty array to avoid infinite loop
    []
  );

  const [weatherData, setWeatherData] = useState([]);

  return (
    <div>
      <h1>Fake Weather data from firebase</h1>
      <MeasurementList weatherData={weatherData} />

      <h1>Fake Weather data (from file)</h1>
      <MeasurementList weatherData={fakeWeatherData} />
    </div>
  );
}

const MeasurementList = props => {
  return (
    <div>
      {
        props.weatherData.map(it => (
          <MeasurementDay key={it.key} item={it} />
        ))
      }
    </div>
  )
}

const MeasurementDay = props => {
  return (
    <div>
      <h2>Fake wind data, Updated: {props.item.lastUpdate.toDate().toLocaleString()}</h2>
      <table>
        <thead>
          <tr>
            <th>Time</th>
            <th>Wind speed</th>
            <th>Wind direction</th>
          </tr>
        </thead>

        <tbody>
          {
            props.item.windMeasurements.map((it, i) => (
              <tr key={i}>
                <td>{it.time.toDate().toISOString()}</td>
                <td>{it.windSpeed}</td>
                <td>{it.windDir}</td>
              </tr>
            ))
          }
        </tbody>
      </table>
    </div>
  );
};

export default Weather;

Først importeres firebase konfigurationen, og firebase initialiseres.

Derefter bruges oprettes forbindelse til firebase og der abbonneres på databaseopdateringer.

Endelig tilføjes en enstra MeasurementList komponent, så indholdet vises på skærmen.

Hvis appen startes, vi der nu automatisk opdateres med nye målinger i tabellerne, når de tilføjes til firestore databasen med upload scriptet.

App Demo

Denne demo viser resultatet af anstrengelserne og skulle gerne minde om det du kan se på figuren herunder.

Når du åbner siden burde du se noget i stil med det der er vist på figuren herunder.

Bemærk: For at kunne tilføje data til databasen, kræver det at du selv laver det på din egen maskine.

Screenshot af den kørende react app.

Materiale

Chart.js

Hvis man gerne vil have tegnet en graf over noget data der er opsamlet, f.eks. fra en sensor, kan det være nyttigt at benytte et bibliotek af funktioner, som kan klare tegnearbejdet, så man ikke selv skal programmere det hele fra bunden.

Chart.js er et eksempel på sådan et bibliotek, der kan bruges til at få en graf frem på en hjemmeside.

Eksempel

I dette eksempel vises hvordan man kan indsætte en graf med to datasæt.

<canvas id="myChart"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
<script src="chart-demo.js"></script>

Først skal der laves et canvas element, som kan indeholde grafen. Dernæst skal chart.js bibilioteket loades, hvilket kan gøres på flere måder som beskrevet i denne guide.

Derefter skal grafen laves hvilket gøres ved hjælp af javascript.

I dette eksempel er opsætning af grafen lavet i filen chart-demo.js, som så er hentet ind i html siden som vist ovenfor.

Herunder ses indholdet af filen chart-demo.js

var ctx = document.getElementById("myChart").getContext("2d");

const xValues = [
  "January",
  "February",
  "March",
  "April",
  "May",
  "June",
  "July"
];

// generate some random values to plot
const yValues = [];
for (let i = 0; i < xValues.length; i++) {
  yValues.push(Math.random() * 50);
}

var chart = new Chart(ctx, {
  // The type of chart we want to create
  type: "line",

  // The data for our dataset
  data: {
    labels: xValues,
    datasets: [
      {
        label: "Random dataset",
        borderColor: "rgb(255, 99, 132)",
        data: yValues,
        fill: false
      },
      {
        label: "Fixed dataset",
        borderColor: "rgb(132, 99, 255)",
        data: [20, 5, 5, 2, 24, 13, 25],
        fill: false
      }
    ]
  },
});

Bemærk at den ene serie består af faste værdier, og den anden er en række tilfældigt generede tal.

Demo

Her ses resultatet af anstrengelserne. Hvis du geninlæser siden gennereres der ny tilfældige værdier for den ene serie.

Materiale

Subsections of Firebase

Firestore Client

I dette eksempel forbindes der til Firestore fra en browser ved hjælp af javascript.

Videoen viser en gennemgang af de fundamentale principper i Cloud Firestore.

Getting Started With Cloud Firestore on the Web - Firecasts

Demo

Hvis du følger videoen burde du ende op med et kørendene eksempel, der ser nogenlunde sådan ud.

Hot dog status

Koden du ender op kommer til at ligne dette.
// Your web app's Firebase configuration
var firebaseConfig = {
  apiKey: "AIzaSyAof7A2wpEylAFHsKWvqeJU8VkYN7G1VMI",
  authDomain: "coldhawaiiweather.firebaseapp.com",
  databaseURL: "https://coldhawaiiweather.firebaseio.com",
  projectId: "coldhawaiiweather",
  storageBucket: "coldhawaiiweather.appspot.com",
  messagingSenderId: "645018619046",
  appId: "1:645018619046:web:2ae11bff4009e1e965f867",
  measurementId: "G-61E1DX0Z3Q"
};

// Initialize Firebase
firebase.initializeApp(firebaseConfig);
firebase.analytics();

const firestore = firebase.firestore();

const docRef = firestore.doc("samples/sandwichData");

const outputHeader = document.querySelector("#hotdogOutPut");
const inputTextField = document.querySelector("#latestHotdogStatus");
const saveButton = document.querySelector("#saveButton");

saveButton.addEventListener("click", e => {
  const textToSave = inputTextField.value;
  console.log("save clicked " + textToSave);

  docRef
    .set({
      hotdogStatus: textToSave
    })
    .then(() => {
      console.log("saved!");
    })
    .catch(error => {
      console.log("got an error: ", error);
    });
});

thingToDoWhithDocumentData = doc => {
  if (doc && doc.exists) {
    const myData = doc.data();
    console.log("Got snapshot doc: ", doc);
    outputHeader.innerText = "Hot dog Status: " + myData.hotdogStatus;
  }
};

getRealtimeUpdates = () => {
  docRef.onSnapshot(
    { includeMetadataChanges: true },
    thingToDoWhithDocumentData
  );
};

getRealtimeUpdates();

Du kan også se demonstrationen på en separat side.

Materiale

Firecasts firebase intro

Firestore Node Upload

I dette eksempel forbindes der til Firestore via Node.js. Dvs. koden der skal afvikles på serveren kan skrives i javascript, og vi kan derfor bruge viden om syntaks fra arbejdet med p5js.

Opsætning af Node projekt

For at kunne arbejde med firestore fra node.js, skal der laves en pakke / projekt at arbejde i. Dette kan gøres ved at oprette en mappe til projektet og køre denne kommando for at generere en package.json fil.

npm init

Man bliver så bedt om at svare på en række spørgsmål, og ender med en struktur der ser nogenlunde sådan ud:

{
  "name": "firestore-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

For at kunne benytte Firebase SDK skal vi have dette installeret, hvilket kan klares med kommandoen.

npm install firebase-admin --save

Vi får også brug for at arbejde med formatering af dato og tidspunkter, så derfor vælger vi at bruge biblioteket Moment.js for at lette arbejdet med disse. Moment.js kan installeres med denne kommando.

npm install moment --save

Nu burde Firebase SDK og Moment.js være installeret og tilføjet som afhængigheder og package.json ser nu nogenlunde således ud.

{
  "name": "firestore-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@google-cloud/firestore": "^3.4.1",
    "firebase-admin": "^8.9.1",
    "moment": "^2.24.0"
  }
}

Som det ses er der nu tilføjet afhængigheder til Firebase og moment pakkerne. De nærmere detaljer kan findes i denne guide.

Dette er et eksempel på en hvordan man kan lave et lille program, der indsætter en stump data i firestore.

Firestore upload eksempel

// Add the Firebase Admin SDK to Your Server
const firebase = require("firebase-admin");
// Get the helper classes
FieldValue = firebase.firestore.FieldValue;
Timestamp = firebase.firestore.Timestamp;

const moment = require("moment");

// Import crecential for the service account
const serviceAccount = require("./serviceAccountKey.json");

// Initialize the default app
const app = firebase.initializeApp({
  credential: firebase.credential.cert(serviceAccount),
  databaseURL: "https://coldhawaiiweather.firebaseio.com"
});

const db = firebase.firestore();

// Helper - Generates random integer values 
function getRandomInt(max, min) {
  const diff = max - min;
  return Math.floor(Math.random() * Math.floor(diff) + min);
}

async function storeData() {

  // Set time of update
  const updateTime = Timestamp.now();

  // Generate some fake weather data
  const windData = {
    time: updateTime,
    windSpeed: getRandomInt(15, 3),
    windDir: getRandomInt(0, 359),
  };

  // Document ID should be todays date
  const today = moment(updateTime.toDate());
  const docPath = `weather/${today.format("YYYY-MM-DD")}`;
  const docRef = db.doc(docPath);

  const data = {
    lastUpdate: updateTime,
    windMeasurements: FieldValue.arrayUnion(windData),
  };
  
  const options = { merge: true }
  await docRef.set(data, options);

}

storeData();

Der skal bruges en såkaldt Service Account, for at kunne forbindes fra node til firebase. Guiden forklarer, hvad du skal gøre for at oprette en service account til dit firebase projekt. Disse oplysninger er private, så vær opmærksom på ikke at holde dem for dig selv. For eksemplet virker skal oplysningerne om din Service Account oprettes i file serviceAccountKey.json.

Håndtering af private nøgler

For at undgå at sprede disse oplysninger er det en god ide at tilføje denne fil til listen over filer der skal ignoreres af git. Dvs. at din .gitignore fil kunne se således ud.

# ignore packages downloaded from npm
node_modules/

# avoid storing firebase login credentials
serviceAccountKey.json

Hvis javascript eksemplet gemmes i en fil med navnet firebase-upload-demo.js, kan programmet køres med kommandoen

node firebase-upload-demo.js

Materiale

Regex

Her er en stump tekst som kunne være et eksempel på noget input, hvor vi gerne vil finde alle telefonnumre.

Here is my number 123-456-7893.
Should you not be able to reach me there you can contact me at work (223)456-4305, or call my wife at 234.343.4521.

Opgaven med at finde telefonnumre kan i dette tilfælde løses ved at at bruge denne regular expression eller regex.

[\(]\*\d{3}[)-\.]\d{3}[\.-]\d{4}

Her er eksempel på hvordan det kan benyttes i javascript.

const re = /\(?\d{3}[)-\.]\d{3}[\.-]\d{4}/g;

const txt = `Here is my number 123-456-7893.
Should you not be able to reach me there you can contact me at work (223)456-4305, or call my wife at 234.343.4521.`;

const phoneNumbers = txt.match(re);
console.log(phoneNumbers);

Materiale

2.1: Introduction to Regular Expressions - Programming with Text

2.2: Regular Expressions: Meta-characters - Programming with Text

2.3: Regular Expressions: Character Classes - Programming with Text

2.4: Regular Expressions: Capturing Groups - Programming with Text

2.5: Regular Expressions: Back References - Programming with Text

Mindstorms

En nem måde at komme i gang med at programmere til lego mindstorms EV3, er ved at bruge værktøjet Makecode. For at kunne benytte dette, kræves at din EV3 klods er opdateret til firmware version 1.10E eller nyere.

Programmering

Du kan lave dit program ved hjælp af blok programmering, som du måske kender hvis du har arbejdet med Scratch, eller Applab.

Makecode udviklingsmiljøet i blokprogrammerings mode. Simulatoren ses i venstre side.

Udviklingsmiljøet indeholder mulighed for at simulere dit program direkte i din browser uden at overføre det til en fysisk EV3 brik. Du kan se en grafisk visning af de enheder, der forventes at være tilsluttet til din EV3, på baggrund af indholdet af din kode. Du kan også køre dit program som en simulering, og se om det opfører sig som forventet, ved at manipulere de virtuelle input i browseren. Det kan dog være vanskeligt at simulere noget, hvor der er en fysisk kobling mellem input og output, som f.eks. en linjefølger, så du får også brug for at teste med en EV3 klods.

Som det også er tilfældet med Applab, kan du vælge at skrive dit program i javascript. Her er et eksempel på et program der starter og stopper en motor, afhængigt af om en knap er trykket ind.

forever(function () {
    if (sensors.touch2.isPressed()) {
        motors.mediumC.run(50)
    } else {
        motors.mediumC.stop()
    }
})

En af fordelene ved at bruge tekstbaseret kode som f.eks. javascript er, at du nemt kan klippe/klistre stumper af kode fra forskellige programmer, og sætte dem sammen til et nyt program.

Materiale

Subsections of Mindstorms

Two Motor Turning

Robot der styres med 2 motorer.

Dette eksempel forudsætter at man har bygget en robot der kan køre ved at styre 2 separater motorer, en til hvert hjul. Motorerne sidder på B og C udgangene.

Det kan være sværet at vurdere hvor hurtigt robotten kører med forskellige motorhastigheder. Derfor ender man ofte med at downloade talrige programmer til EV3 brick’en, for at teste forskellige motorhastigheder. Endnu sværere bliver det at afgøre hvor skarp robottet drejer, når motorerne kører med forskellige hastigheder.

Derfor kan dette eksempel bruges til nemt at afprøve forskellige motorhastigheder.

Indstillinger for speed og turn ratio kan indstilles med knapperne på EV3 brick, og displayet viser de valgte indstillinger.

Eksempel på visning i displayet i simulatoren.

Blokprogrammering

Her er koden der bruges i eksemplet lavet med blokprogrammering.

Programmet lavet med blokprogrammering.

Javascript udgave

Den samme kode er her vist som javascript.

let turnRatio = 0
let speed = 0
brick.buttonUp.onEvent(ButtonEvent.Pressed, function () {
    speed += 10
})
brick.buttonRight.onEvent(ButtonEvent.Pressed, function () {
    turnRatio += 10
})
brick.buttonLeft.onEvent(ButtonEvent.Pressed, function () {
    turnRatio += 0 - 10
})
brick.buttonDown.onEvent(ButtonEvent.Pressed, function () {
    speed += 0 - 10
})
brick.showString("Steer tester", 1)
brick.showString("Connect motors BC", 7)
brick.showString("up/down : speed", 8)
brick.showString("left/right : turn", 9)
forever(function () {
    motors.largeBC.steer(turnRatio, speed)
    brick.showValue("speed", speed, 2)
    brick.showValue("turnRatio", turnRatio, 3)
    brick.showValue("motor B speed", motors.largeB.speed(), 4)
    brick.showValue("motor C speed", motors.largeC.speed(), 5)
    pause(100)
})

Hugo Static Site Generator

Kom igang med Hugo

Tilføj indhold

Indholdet skrives ved hjælp af markdown, som bliver oversat til html.

Diagrammer med Mermaid.js

Note

Læs dokumentation af mermaid.js for mange flere detaljer.

Her er et eksempel på hvordan man kan lave et flowchart.

{{< mermaid >}}
graph TD;
    EDIT("opret/rediger/slet fil(er)") --> STAGE("udvælg ændringer") 
    STAGE --> COMMIT("Commit i lokalt depot")
    COMMIT --> DO_PUSH{Klar til offentliggørelse?}
    DO_PUSH -->|Nej| EDIT
    DO_PUSH -->|Ja| PUSH("Skub ændriger til 'remote'")
    PUSH --> EDIT
{{< / mermaid >}}

Resultatet kommer til at se således ud.

graph LR;
    EDIT("opret/rediger/slet fil(er)") --> STAGE("udvælg ændringer") 
    STAGE --> COMMIT("Commit i lokalt depot")
    COMMIT --> DO_PUSH{Klar til offentliggørelse?}
    DO_PUSH -->|Nej| EDIT
    DO_PUSH -->|Ja| PUSH("Skub ændriger til 'remote'")
    PUSH --> EDIT

Subsections of Node.js

Intro

Her er nogle links til at komme i gang med node.js.

Node gør det muligt at lave programmer der kan køres fra kommandolinien. Her er et simpelt eksempel.

// this is a comment
/*
This
is 
a 
multiline
comment
*/

// Erklæring af variabel
let name;
// Tildering af variable
name = "Allan";

// Erklæring og tildeling på samme linje
let greeting = "Hej ";

// Iteration med for-løkke
for (let i = 0; i < 5; i++) {
  let separator = '';
  if (i < 3) {
    separator = ', ';

  } else if (i < 4) {
    separator = ' og ';
  }
  greeting += name + separator;
}

// Udskrift til konsol
console.log(greeting);

// Matematiske beregninger
let a = 5;
let b = 3;
let number = a * b;
console.log(a + " gange " + b + " giver " + number);

// Betingelser og forgreninger
if (number > 5) {
  console.log("Det var et stort tal");
} else {
  console.log("Ok tak");
}

// Definition af egne funktioner
function myAdd(a, b) {
  return a + b;
}

result = myAdd(number, 34);
console.log(result);

// Brug af biblioteks funktion
let root = Math.sqrt(result);

console.log(root);

console.log("Program afslutter");

Hvis du gemmer koden i filen node-demo.js, kan du eksekvere programmet med denne kommando.

node node-demo.js

Programmet burde så give følgende output i terminalen.

Hej Allan, Allan, Allan, Allan og Allan
5 gange 3 giver 15
Det var et stort tal
49
7
Program afslutter

Discussing node.js - Computerphile

Command Line Interface Arguments

Eksempler på forskellige måder at man kan bruge argumenter fra kommandolinjen til at påvirke afviklingen af et program.

Simpel brug af CLI argumenter

Kommandolinje argumenter kan f.eks. benyttes således

const args = process.argv.slice(2)
console.log(args)

if(1 == args.length){
  const name = args[0]
  console.log(`Hello, ${name}!`)
} else {
  console.log("Hello, world!")  
}

Programmet giver dette output ved afvikling af med forskellig input argumenter.

$ node cli-args-demo.js
[]
Hello, world!
$ node cli-args-demo.js Bjarne
[ 'Bjarne' ]
Hello, Bjarne!
$ node cli-args-demo.js Finn, Find og Bjarne
[ 'Finn', 'Find', 'og', 'Bjarne' ]
Hello, world!

Minimist pakken

Det kan være fordelagtigt at benytte npm pakken minimist, hvis der er brug for mere end de aller simpleste argumenter. Det gør det langt nemmere at parse indholdet in i variabler, der kan benyttes i programmet.

const args = require('minimist')(process.argv.slice(2))
args['name']

console.log(args)

if(args.name !== undefined){
  console.log(`Hello, ${args.name}!`)
} else {
  console.log("Hello, world!")  
}

Her er et eksempel på output fra programmet.

$ node cli-minimist-demo.js
{ _: [] }
Hello, world!
$ node cli-minimist-demo.js Bjarne
{ _: [ 'Bjarne' ] }
Hello, world!
$ node cli-minimist-demo.js Finn, Find og Bjarne
{ _: [ 'Finn', 'Find', 'og', 'Bjarne' ] }
Hello, world!
$ node cli-minimist-demo.js Find --name=Bjarne og Finn
{ _: [ 'Find', 'og', 'Finn' ], name: 'Bjarne' }
Hello, Bjarne!

Husk at installere minimist inden eksemplet køres, med kommandoen nmp install minimist

Yargs pakken

Er der brug for mere avanceret håndtering af argumenter på kommandolinjen, kan man benytte npm pakken yargs. Det giver blandt andet mulighed for at tilknytte beskrivelser, typer og aliaser for de forskellige argumenter.

Her er et eksempel på håndtering af mere indviklede argumenter på kommandolinjen.

const yargs = require('yargs');

const argv = yargs
  .command('lyr', 'Tells whether an year is leap year or not', {
    year: {
      description: 'the year to check for',
      alias: 'y',
      type: 'number',
    }
  })
  .option('time', {
    alias: 't',
    description: 'Tell the present Time',
    type: 'boolean',
  })
  .help()
  .alias('help', 'h')
  .argv;

if (argv.time) {
  console.log('The current time is: ', new Date().toLocaleTimeString());
}

if (argv._.includes('lyr')) {
  const year = argv.year || new Date().getFullYear();
  if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) {
    console.log(`${year} is a Leap Year`);
  } else {
    console.log(`${year} is NOT a Leap Year`);
  }
}

console.log(argv);

For at kunne køre eksemplet kræver det at pakken yargs er installeret, hvilket kan gøres med kommandoen npm i yargs.

Her er indholdet af package.json.

{
  "name": "cli-args",
  "version": "1.0.0",
  "description": "",
  "main": "cli-yargs-demo.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "yargs": "^17.2.1"
  }
}

Her er et eksempel på output fra programmet.

$ node cli-yargs-demo.js       
{ _: [], '$0': 'cli-yargs-demo.js' }
$ node cli-yargs-demo.js -h    
cli-yargs-demo.js [command]

Commands:
  cli-yargs-demo.js lyr  Tells whether an year is leap year or not

      --version  Show version number                                   [boolean]
  -t, --time     Tell the present Time                                 [boolean]
$ node cli-yargs-demo.js --version
1.0.0
$ node cli-yargs-demo.js --help lyr
cli-yargs-demo.js lyr


Options:
      --version  Show version number                                   [boolean]
  -t, --time     Tell the present Time                                 [boolean]
  -h, --help     Show help                                             [boolean]
  -y, --year     the year to check for                                  [number]
$ node cli-yargs-demo.js lyr       
2021 is NOT a Leap Year
{ _: [ 'lyr' ], '$0': 'cli-yargs-demo.js' }
$ node cli-yargs-demo.js lyr --time
The current time is:  22.48.45
2021 is NOT a Leap Year
{ _: [ 'lyr' ], time: true, t: true, '$0': 'cli-yargs-demo.js' }
$ node cli-yargs-demo.js lyr --time -y 3000
The current time is:  22.48.59
3000 is NOT a Leap Year
{
  _: [ 'lyr' ],
  time: true,
  t: true,
  y: 3000,
  year: 3000,
  '$0': 'cli-yargs-demo.js'
}

Materiale

Discord Bot

Dette eksempel viser hvordan man kan lave en Discord bot, som kan reagere på kommandoer i en tekstkanal.

Oprettelse af Discord Application og Bot

Start med at oprette en application i Discord udvikler portalen.

Derefter skal der tilføjes en Bot til din application.

For at give din bot adgang til din discord server, kan du bruge denne URL:

https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&scope=bot

Bemærk at YOUR_CLIENT_ID skal erstattes med det relevante id for din specifikke bot. Du kan også få en korrekt URL genereret ved at gå ind under OAuth2 i menuen, og vælge bot under scopes.

Efter dette er forarbejdet gjort og du er klar til at starte på koden til din bot.

Der er mere information om de tekniske detaljer på Discord Developer Portalen.

Opsætning af node projekt

Først skal der laves et projekt så node kan finde ud af at køre programmet, og har en package.json fil til at holde styr på projektet og afhængigheder af biblioteksmoduler.

Start med at lave en mappe, som kan indeholde dit projekt. Kald den f.eks. discord-bot. I denne mappe skal du køre følgende kommando, for at oprette projekt filen package.json.

npm init

Udfyld passende værdier som svar på de spørgsmål programmet stiller. Jeg foreslår at ændre din main fil til bot.js.

Dernæst har du mulighed for at installere de pakker, der skal bruges i projektet som afhængigheder. Dette gøres med disse kommandoer.

npm install discord.js
npm install dotenv
npm install axios
npm install cowsay
npm install random-fortune

Når programmet skal startes kan det gøres med kommandoen.

node bot.js

For at undgå at huske på hvilken fil der skal køres for at starte programmet, kan man tilføje en start action i scripts sectionen i package.json. Derefter kan du køre din bot, med denne kommando.

npm start

Her er indholdet den fulde af package.json.

{
  "name": "discordbot",
  "version": "1.0.0",
  "description": "",
  "main": "bot.js",
  "scripts": {
    "start": "node bot.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.26.1",
    "cowsay": "^1.5.0",
    "discord.js": "^13.6.0",
    "dotenv": "^16.0.0",
    "random-fortune": "^0.2.0"
  }
}

Bot koden

Hovedprogrammet til robotten benyttes sig af pakken discord.js. Koden er forholdsvis kortfattet, og placeres i filen bot.js. Bemærk hvordan kommandohåndteringen er uddelegeret til et andet modul.

require('dotenv').config();
const TOKEN = process.env.TOKEN;

const {Client, Intents} = require('discord.js');
const config = {intents:[Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES]};
const client = new Client(config);
client.login(TOKEN);

client.on('ready', () => {
  console.log(`Logged in as ${client.user.tag}!`);
});

const commandHandler = require('./commands');
client.on('message', commandHandler);

console.log('Beep beep! 🤖');

Kommandofortolker

Håndtering af de indkomne kommandoer er placeret i filen commands.js. Bemærk hvordan der uden større ændringer kan tilføjes flere kommandoer til systemet.

const serverID = process.env.SERVERID;
const channelID = process.env.CHANNELID;

const help = (msg, args) => {
  const reply = `Avaliable commands are:
  !help  : Show this help message
  !ping  : Responds with pong
  !hmm   : Get a random response
  !dog   : Shows a dog
  !toast : Get a quote from the toaster
  `;
  msg.channel.send(reply);
};

const dog = require('./commands/dog.js');
const hmm = require('./commands/hmm.js');
const ping = require('./commands/ping.js');
const toast = require('./commands/toast.js');

const commands = { help, hmm, dog, ping, toast };

module.exports = async function (msg) {
  // Only for this server and this channel
  if (msg.guild.id === serverID && msg.channel.id === channelID) {
    // Handle command
    let tokens = msg.content.split(' ');
    let command = tokens.shift();
    if (command.charAt(0) === '!') {
      command = command.substring(1);
      // check that command exists
      if (commands.hasOwnProperty(command)) {
        commands[command](msg, tokens);
        console.log(`got valid command, '${command}'`);
      } else {
        console.log(`got invalid command: '${command}', showing help`);
        help(msg, tokens);
      }
    }
  }
};

Brug af miljøvariabler

For at undgå at kode login tokens, server og kanal id’er ind i kildeteksten til programmet, benyttes pakken dotenv, således at disse kan hentes fra miljøvariable i stedet. Konkrete værdier placeres i en hjælpefil sammen med koden i dette format. Filen skal navngives .env.

SERVERID=replace_with_server_id
CHANNELID=replace_with_channel_id
TOKEN=replace_with_secret_bot_token

Pladsholderne skal erstattes med konkrete værdier fra den server bot’en skal være tilgængelig på.

Udelad private indstillinger i git

For at undgå utilsigtet deling af disse oplysninger er det en god ide at undlade at tilføje filen i git. Derfor tilføjes den til filen .gitignore, der f.eks kan se således ud.

node_modules/
.env

Discord specifikke værdier

De omtalte miljøvariabler skal som nævnt sættes til konkrete værdier, der afhænger af hvilken server/guild bot’en skal fungere sammen med.

For at kunne find de oplysninger, der skal indsættes i .env filen, er det nødvendigt at aktivere udviklertilstanden i discord.

  • Gå til brugerindstillinger > udseende
  • Scroll ned til avanceret
  • Tænd for udviklertilstand

Server ID kan nu tilgås sådan: højre-klik på serverens icon, vælg Kopiér ID i menuen.

Ligeledes kan Kanal ID fås sådan: højre-klik på tekst-kanalen, vælg Kopiér ID i menuen.

Bot Kommandoer

For at få en overskuelig struktur i koden er de enkelte kommandoer, som bot’en kan reagere på, implementeret i hver sin fil i mappen commands/.

!ping

Denne kommando svarer tilbage med pong. Den er implementeret i filen commands/ping.js. Bemærk at der ved at bruge funktionen reply(), automatisk insættes en @mention til den bruger der har aktiveret bot’en.

module.exports = (msg, args) => {
  msg.reply('pong!');
};

!hmm

Denne kommando svarer tilbage med en besked tilfældigt udvalgt fra en liste med svarmuligheder. Den er implementeret i filen commands/hmm.js.

const ufoUrl = 'https://i.pinimg.com/originals/39/b2/34/39b234d75c67da28abdcb38b1b4cf649.png';
const replies = [
  ufoUrl,
  '🤖 says go 🏄 and ⛷️',
  '(╯°□°)╯︵ ┻━┻',
  '¯\_(ツ)_/¯',
  'Så er der 🍰'
];

module.exports = (msg, args) => {
  const index = Math.floor(Math.random() * replies.length);
  console.log(replies[index]);
  msg.channel.send(replies[index]);
};

!dog

Denne kommando svarer tilbage med et tilfældigt udvalgt billede af en hund, som hentes fra et web-api. Den er implementeret i filen commands/dog.js. Kommandoen benytter pakken axios til at udføre kald til web-api’et.

const axios = require('axios');

const url = "https://dog.ceo/api/breeds/image/random";

module.exports = async (msg, args) => {

  const response = await axios.get(url);
  const dogImgUrl = response.data.message;
  console.log(`dog image url: ${dogImgUrl}`);

  if ("success" === response.data.status) {
    msg.channel.send(dogImgUrl);
  }
};

!toast

Denne kommando svarer tilbage med et tilfældigt udvalgt citat, præsenteret af en toaster. Kommandoen benytter pakken random-fortune til at generere tilfældigt udvalgte citater. Desuden benyttes pakken cowsay til den visuelle præsentation af tekst output som ascii-art. Kommandoen er implementeret i filen commands/toast.js.

const fortune = require('random-fortune');
const cowsay = require("cowsay");
 
module.exports = (msg, args) => {

  const fortuneText = fortune.fortune();

  const options = {
    text : fortuneText,
    f: 'toaster',
    W: 50
  };

  const res = cowsay.say(options);

  msg.channel.send(`\n\`${res}\``);
};

Her er et eksempelt på et svar fra robotten.

 _________________________________________
/ "Buy land.  They've stopped making it." \
\ -- Mark Twain                           /
 -----------------------------------------
   \                     .___________.
    \                    |           |
     \    ___________.   |  |    /~\ |
         / __   __  /|   | _ _   |_| |
        / /:/  /:/ / |   !________|__!
       / /:/  /:/ /  |            |
      / /:/  /:/ /   |____________!
     / /:/  /:/ /    |
    / /:/  /:/ /     |
   /  ~~   ~~ /      |
   |~~~~~~~~~~|      |
   |    ::    |     /
   |    ==    |    /
   |    ::    |   /
   |    ::    |  /
   |    ::  @ | /
   !__________!/

Afprøvning

Nu burde bot’en være funktionel. Den kan som tidligere nævnt startes med kommandoen npm start. Husk at det er nødvendigt at genstarte programmet, hvis der laves ændringer i det.

Materiale

Coding train discord bot series

Javascript - MDN Web Docs

NPM pakker

Json API

Web Server

Dette er et eksempel på en simpel web-server, lavet med node js og det ofte benyttede web framework express. Ved at benytte pakker fra npm, kan man ret nemt lave systemet uden at skulle programmere alt op fra grunden.

Opsætning af projekt

Først skal der laves et projekt så node kan finde ud af at køre programmet, og har en package.json fil til at holde styr på projektet og afhængigheder af biblioteksmoduler.

Start med at lave en mappe, som kan indeholde dit projekt. Kald den f.eks. web-server. I denne mappe skal du køre følgende kommando, for at oprette projekt filen package.json.

npm init

Udfyld passende værdier som svar på de spørgsmål programmet stiller. Jeg foreslår at ændre din main fil til server.js.

Dernæst har du mulighed for at installere afhængigheder.

npm install express

Du burde nu have en package.json fil, der ser nogenlunde sådan ud:

{
  "name": "web-server-demo",
  "version": "1.0.0",
  "description": "Demo web server",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1"
  }
}

Bemærk at installerede pakker er listet under afhængigheder.

Simple server med output

Derefter skal du oprette filen server.js med følgende indhold.

const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

Nu kan du køre eksemplet med denne kommando:

node server.js

Åben din browser og gå til http://localhost:3000. Her vil du se en side der blot indeholder teksten Hello, World!.

Som du sikkert har bemærket er der ikke noget html på siden. Ved at ændre lidt på serveren kan vi indsætte et par tags således.

const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('<html><body><h1>Hello, World!</h1></body></html>')
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

Husk at genstarte serveren i terminalen, og genindlæse siden i browseren for at se ændringen.

Det bliver hurtigt unødvendigt besværligt at skrive html indholdet midt i server koden. Derfor ændrer vi serveren til at læse det statiske indhold fra filer i en mappe på harddisken.

const express = require('express')
const app = express()
const port = 3000

app.use(express.static(__dirname + "/public"));

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

Opret en mappe med navnet public, i samme bibliotek som server.js.

Nu kan vi oprette alle de statiske filer og mapper der ønskes gjort tilgængelige på webserveren, ved at placere dem i mappen public.

Som eksempel kan vi lave en forside, ved at lave filen public/index.html, og give den følgende indhold.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Min Side</title>
</head>
<body>
  <h1>Hej Verden</h1>
  <p>Velkommen til express js webserver server lyn kursus.</p>
</body>
</html>

Herefter er det blot fantasien, der sætter grænser for indholdet. Måske har du brug for at læse en kort introduktion til HTML eller CSS inden du går i gang.

Materiale

Webrtc Server

Dette er eksempel viser hvordan man kan streame indholdet af et html canvas element (en p5js sketch) til en anden maskine. Streaming delen håndteres af WebRTC, og etablering af forbindelsen håndteres ved hjælp af Socket.io.

Opsætning af projekt

Først skal der laves et projekt så node kan finde ud af at køre programmet, og har en package.json fil til at holde styr på projektet og afhængigheder af biblioteksmoduler.

Start med at lave en mappe, som kan indeholde dit projekt. Kald den f.eks. webrtc-server. I denne mappe skal du køre følgende kommando, for at oprette projekt filen package.json.

npm init

Udfyld passende værdier som svar på de spørgsmål programmet stiller. Jeg foreslår at ændre din main fil til server.js.

Dernæst har du mulighed for at installere disse afhængigheder.

npm install express
npm install socket.io

Indsæt en start action i script sektionen.

Du burde nu have en package.json fil, der ser nogenlunde sådan ud:

{
  "name": "webrtccanvasbroadcast",
  "version": "1.0.0",
  "description": "Example of streaming canvas content using WebRTC",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1",
    "socket.io": "^3.1.0"
  }
}

Bemærk de to pakker der er listet under afhængigheder.

Server

De statiske web-resourcer placeres i mappen public, og de gøres tilgængelige på samme måde som i eksemplet med den simple web server.

Derudover består serveren af en række endpoints, der håndterer socket.io beskeder. Dette er nødvendigt for at kunne styre transmission af videostrømmen, da dette ikke er indbygget i WebRTC.

Koden til serveren placeres i filen server.js.

const express = require("express");
const app = express();

let broadcaster;
const port = 4000;

const http = require("http");
const server = http.createServer(app);

const io = require("socket.io")(server);
app.use(express.static(__dirname + "/public"));

io.on("error", e => console.log(e));

io.on("connection", socket => {
  socket.on("broadcaster", () => {
    broadcaster = socket.id;
    console.log(`Broadcaster id: ${broadcaster}`)
    socket.broadcast.emit("broadcaster");
  });
  socket.on("watcher", () => {
    console.log(`Watcher id: ${socket.id}`)
    socket.to(broadcaster).emit("watcher", socket.id);
  });
  socket.on("offer", (id, message) => {
    console.log(`Offer id: ${socket.id}, message: ${message}`)
    socket.to(id).emit("offer", socket.id, message);
  });
  socket.on("answer", (id, message) => {
    console.log(`Answer id: ${socket.id}, message: ${message}`)
    socket.to(id).emit("answer", socket.id, message);
  });
  socket.on("candidate", (id, message) => {
    console.log(`Candidate id: ${socket.id}, message: ${message}`)
    socket.to(id).emit("candidate", socket.id, message);
  });
  socket.on("disconnect", () => {
    console.log(`Disconnect id: ${socket.id}`)
    socket.to(broadcaster).emit("disconnectPeer", socket.id);
  });
});

server.listen(port, () => console.log(`Server is running on port ${port}`));

Når du ønsker at starte serveren, kan det gøres med kommandoen.

npm start

Afsender af video stream

For at have noget at sende afsted laves en sketch ved hjælp af p5js, der streames til modtagerne med WebRTC.

Tegn på canvas

Der laves en sketch, der tegner noget på det canvas element, som skal transmitteres. I eksemplet tegnes blot en rød cirkel med sort omrids på musens position. Dette laves i filen public/broadcast/sketch.js.

let stream;

function setup() {
  // Capture the canvas content as a stream
  const c = createCanvas(400, 400);
  const htmlCanvas = c.elt;
  stream = htmlCanvas.captureStream();

  gotStream(stream);
}

function draw() {
  background(220);
  // Draw a red circle at the position of the mouse
  fill('red');
  strokeWeight(5);
  circle(mouseX, mouseY, 50);
}

Styring af video stream

For at kunne håndtere WebRTC forbindelsen kommunikeres med serveren via Socket.io.

Klient-delen der styrer afsendelsen laves i public/broadcast/webrtc.js

const peerConnections = {};
const config = {
  iceServers: [
    {
      "urls": "stun:stun.l.google.com:19302",
    },
  ]
};

const socket = io.connect(window.location.origin);

socket.on("answer", (id, description) => {
  peerConnections[id].setRemoteDescription(description);
});

socket.on("watcher", id => {
  const peerConnection = new RTCPeerConnection(config);
  peerConnections[id] = peerConnection;

  let stream = window.stream;
  stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));

  peerConnection.onicecandidate = event => {
    if (event.candidate) {
      socket.emit("candidate", id, event.candidate);
    }
  };

  peerConnection
    .createOffer()
    .then(sdp => peerConnection.setLocalDescription(sdp))
    .then(() => {
      socket.emit("offer", id, peerConnection.localDescription);
    });
});

socket.on("candidate", (id, candidate) => {
  peerConnections[id].addIceCandidate(new RTCIceCandidate(candidate));
});

socket.on("disconnectPeer", id => {
  peerConnections[id].close();
  delete peerConnections[id];
});

window.onunload = window.onbeforeunload = () => {
  socket.close();
};

function gotStream(stream) {
  window.stream = stream;
  socket.emit("broadcaster");
}

function handleError(error) {
  console.error("Error: ", error);
}

Visning i browser

Javascript koden kan ikke stå alene. For at kunne eksekvere den i browseren bliver den indsat på en simpel web side i filen public/broadcast/index.html.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Video stream - Broadcaster</title>
    <script src="/p5lib/p5.min.js"></script>
    <script src="/p5lib/p5.sound.min.js"></script>
    <link rel="stylesheet" type="text/css" href="../styles.css">
  </head>
  <body>
    <h1>Video stream - Broadcaster</h1>
    <script src="sketch.js"></script>

    <script src="/socket.io/socket.io.js"></script>
    <script src="webrtc.js"></script>
  </body>
</html>

Bemærk at filen /socket.io/socket.io.js genereres automatisk af serveren når socket.io pakken benyttes.

For at kunne tegne på canvas med p5js i public/broadcast/sketch.js er det nødvendigt at inkludere p5 biblioteksfilerne. Disse er placeret i public/p5lib.

Der benyttes også en smule css som placeres i public/styles.css.

html, body {
  margin: 0;
  padding: 0;
}

canvas {
  display: block;
}

video {
  width: 400px;
  height: 400px;
  background-color: black;
}

Modtager af video stream

Den modtagende ende af videostrømmen laves også som en simpel webside. Det er den man skal se, når man besøger serveren.

Lav filen public/index.html med dette indhold.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Video stream - Viewer</title>
    <link href="/styles.css" rel="stylesheet" />
  </head>
  <body>
    <h1>Video stream - Viewer</h1>
    <video playsinline autoplay muted></video>
    <script src="/socket.io/socket.io.js"></script>
    <script src="/watch.js"></script>
  </body>
</html>

Til at styre modtagelsen af WebRTC strømmen benyttes scriptet public/watch.js med dette indhold.

let peerConnection;
const config = {
  iceServers: [
      { 
        "urls": "stun:stun.l.google.com:19302",
      },
      // { 
      //   "urls": "turn:TURN_IP?transport=tcp",
      //   "username": "TURN_USERNAME",
      //   "credential": "TURN_CREDENTIALS"
      // }
  ]
};

const socket = io.connect(window.location.origin);
const video = document.querySelector("video");

socket.on("offer", (id, description) => {
  peerConnection = new RTCPeerConnection(config);
  peerConnection
    .setRemoteDescription(description)
    .then(() => peerConnection.createAnswer())
    .then(sdp => peerConnection.setLocalDescription(sdp))
    .then(() => {
      socket.emit("answer", id, peerConnection.localDescription);
    });
  peerConnection.ontrack = event => {
    video.srcObject = event.streams[0];
  };
  peerConnection.onicecandidate = event => {
    if (event.candidate) {
      socket.emit("candidate", id, event.candidate);
    }
  };
});

socket.on("candidate", (id, candidate) => {
  peerConnection
    .addIceCandidate(new RTCIceCandidate(candidate))
    .catch(e => console.error(e));
});

socket.on("connect", () => {
  socket.emit("watcher");
});

socket.on("broadcaster", () => {
  socket.emit("watcher");
});

window.onunload = window.onbeforeunload = () => {
  socket.close();
  peerConnection.close();
};

Afprøvning

Nu burde du kunne start serveren med kommandoen.

npm start

For at starte transmissionen skal åbne http://localhost:4000/broadcast.

Hvis du derefter besøger http://localhost:4000 i et andet browservindue, kan du se en video transmission af din sketch.

Screenshot af to browserviduer.

Screenshot af de to browserviduer med sender og modtager.

Bemærk at videoen stopper, hvis du lukker det første browservindue. Den burde starte igen, næste gang du besøger http://localhost:4000/broadcast.

Materiale

Readline on Stdin

Dette eksempel viser en måde at tage imod input fra kommandolinien.

Opret filen readline-demo.js med følgende indhold.

const readline = require('readline');

const rl = readline.createInterface({ input: process.stdin, output: process.stdout });

const getLine = (function () {
  const getLineGen = (async function* () {
    for await (const line of rl) {
      yield line;
    }
  })();
  return async () => ((await getLineGen.next()).value);
})();

const main = async () => {
  console.log('Type value for a');
  let a = Number(await getLine());
  console.log(`Got a: ${a}`);
  
  console.log('Type value for b');
  let b = Number(await getLine());
  console.log(`Got b: ${b}`);

  console.log('Result of a+b');
  console.log(a + b);
  process.exit(0);
};

main();

Nu kan du køre eksemplet med denne kommando:

node readline-demo.js

Når eksemplet kører bliver der bedt om først en værdi for variablen a, dernæst for variablen b.

Hvis eksemplet køres og værdierne 42 og 31 indtastes, giver programmet dette output.

readline-stdin$ node .\readline-demo.js
Type value for a
42
Got a: 42
Type value for b
31
Got b: 31
Result of a+b
73
readline-stdin$

Input via pipe

Hvis du er på windows og bruger powershell kan du også give input til stdin direkte på kommandolinien.

echo "42`n31" | node readline-demo.js

Bemærk powershell har en række escape sekvenser der bruges til at insætte special tegn, `n bruges at indsætte et linieskift i en streng.

Bruger du mac eller linux kan du opnå samme resultat med denne kommando.

printf "42\n31\n" | node readline-demo.js

Materiale

Serial Json

Dette eksempel viser hvordan man kan læse fra en serielport vha. node.js. For at kunne køre eksemplet på en meningsfuld måde, kræver det at der er tilsluttet en enhed til serielporten på computeren. Ydermere skal denne enhed sende beskeder afsted i json format, og porten skal være sat op med passende instillinger.

Jeg har tidligere lavet et eksempel, der kan sende json beskeder som output på serielporten på en Arduino, og det er disse data der forventes at være input til dette eksempel.

Først skal der laves et projekt så node kan finde ud af at køre programmet, og har en package.json fil til at holde styr på projektet og afhængigheder af biblioteksmoduler.

Start med at lave en mappe, som kan indeholde dit projekt. Kald den f.eks. serial-json. I denne mappe skal du køre følgende kommando, for at oprette projekt filen package.json.

npm init -y

Dernæst har du mulighed for at installere disse afhængigheder.

npm install serialport --save
{
  "name": "serial-json-read",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "serialport": "^8.0.7"
  }
}

Bemærk de to pakker der er listet under afhængigheder.

Derefter skal du oprette filen serial-json-read.js med følgende indhold.

const SerialPort = require("serialport");
const Readline = require("@serialport/parser-readline");

// Change the serial port to fit your serup
// on linux the device could look like this: "/dev/tty-usbserial1"
const serialPortName = "COM6";

const portOptions = {
  baudRate: 115200
};
const port = new SerialPort(serialPortName, portOptions);

// Open errors will be emitted as an error event
port.on('error', function(err) {
  console.log('Error: ', err.message)
});

const parser = port.pipe(new Readline({ delimiter: "\r\n" }));

parser.on("data", doSomethingWithData);

// Callback function for processing the received data
function doSomethingWithData(data) {
  console.log("--- Line received on serial port ------------");
  console.log('Raw data: ', data);

  // parse json data
  const obj = JSON.parse(data);

  // print object
  console.log("Parsed object:");
  console.log(obj);

  console.log("Contents of object property:");
  console.log(obj.tagId);
  console.log("--- data processing done -------------------");
}

NB! Du skal ændre navnet på serielporten, så det passer med den port din Arduino er tilsluttet til.

Nu kan du køre eksemplet med denne kommando:

node serial-json-read.js

For at få noget meningsfuldt ud af at køre eksemplet skal der, som nævnt ovenfor, være en Arduino tilkoblet til en serielporten på computeren, og denne Arduino skal sende beskeder i det forventede json format. Når dette ellers er opfyldt vil man se noget lignende dette output i terminalen.

$ node .\serial-json-read.js
--- Line received on serial port ------------
Raw data:  {"waitTime":2307,"tagIdIndex":4,"tagId":"E2 00 00 1B 63 15 02 48 15 90 DB 2C"}
Parsed object:
{ waitTime: 2307,
  tagIdIndex: 4,
  tagId: 'E2 00 00 1B 63 15 02 48 15 90 DB 2C' }
Contents of object property:
E2 00 00 1B 63 15 02 48 15 90 DB 2C
--- data processing done -------------------
--- Line received on serial port ------------
Raw data:  {"waitTime":3073,"tagIdIndex":3,"tagId":"E2 00 00 1B 63 15 02 48 17 20 DA F4"}
Parsed object:
{ waitTime: 3073,
  tagIdIndex: 3,
  tagId: 'E2 00 00 1B 63 15 02 48 17 20 DA F4' }
Contents of object property:
E2 00 00 1B 63 15 02 48 17 20 DA F4
--- data processing done -------------------
--- Line received on serial port ------------
Raw data:  {"waitTime":6930,"tagIdIndex":2,"tagId":"E2 00 00 1B 63 15 02 48 17 00 DA F8"}
Parsed object:
{ waitTime: 6930,
  tagIdIndex: 2,
  tagId: 'E2 00 00 1B 63 15 02 48 17 00 DA F8' }
Contents of object property:
E2 00 00 1B 63 15 02 48 17 00 DA F8
--- data processing done -------------------

Materiale

Socket IO

Dette eksempel viser hvordan man kan lave en webserver, der kan køre et eksempel hvor clienter på forskellige maskiner kan kommunikere via en web-socket.

Det er baseret på en videotutorial af Daniel Shiffman fra The Coding Train.

Socket io video tutorial

12.1: Introduction to Node - WebSockets and p5.js Tutorial

  • Introduktion til web sockets.
  • Installation af node
  • “hello world” demo

12.2: Using Express with Node - WebSockets and p5.js Tutorial

  • Hosting af statiske klient filer på node server med Express.

12.3: Connecting Client to Server with Socket.io - WebSockets and p5.js Tutorial

  • Tilføjelse af Socket.io til klient og server
  • Vis klientens ID når de forbinder sig

12.4: Shared Drawing Canvas - WebSockets and p5.js Tutorial

  • Afsendelse af hændelser (events) fra klienter
  • Broadcast af events fra server
  • Tegning på skærmen i de modtagende klienter

Materiale

Kode eksempel

Det kan være nødvendigt med et par små justeringer ift. koden i videoerne.

Her følger indholdet af mine filer

Indholdet i package.json.

{
  "name": "socket-io-demo",
  "version": "1.0.0",
  "description": "Socket io demo with p5js",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Getsrevel",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1",
    "socket.io": "^2.3.0"
  }
}

Bemærk at socket-io klienten hentes fra den lokale server i filen public/index.html, som beskrevet i socket.io - Get started.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Socket io demo</title>
    <script src="/socket.io/socket.io.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.min.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/addons/p5.dom.min.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/addons/p5.sound.min.js"></script>
    <script type="text/javascript" src="sketch.js"></script>
    <style>
      body {
        padding: 20px;
      }
    </style>
</head>
<body>
    <h1>Socket demo</h1>
</body>
</html>
server.js
const express = require('express');

const app = express();

const port = process.env.PORT || 3000;

// Set up the server
// process.env.PORT is related to deploying on heroku
const server = app.listen(port);
app.use(express.static(__dirname +'/public'));
console.log(`Server is running on http://localhost:${port}`);


const socket = require('socket.io');
const io = socket(server);

io.sockets.on('connection', newConnection);

function newConnection(socket) {
    console.log(`New connection ${socket.id}`);

    socket.on('mouse', mouseMsg);

    function mouseMsg(data) {
        socket.broadcast.emit('mouse', data);
        // NB! send to all listeners (including source of incomming event)
        // io.emit('some event, theDataToSend)
        console.log(data);
    }
}
public/sketch.js
const socket = io();

function setup() {
  createCanvas(400, 400);
  background(0);

  // Set up listener for incomming socket events
  socket.on('mouse', newDrawing);
}

const lineWidth = 10;

function newDrawing(data) {
  // Draw some white circles with different colors
  colorMode(RGB, 255);
  fill(255, 0, 100);
  noStroke();
  ellipse(data.x, data.y, lineWidth, lineWidth);
}

function mouseDragged() {
  let currentNumX = mouseX;
  let lowerBound = 0;
  let upperBoundX = width; //100;
  let normalizedX = norm(currentNumX, lowerBound, upperBoundX);
  let currentNumY = mouseY;
  let upperBoundY = height; //100;
  let normalizedY = norm(currentNumY, lowerBound, upperBoundY);
  colorMode(HSB, 255);
  let c = color(normalizedX * 255, normalizedY * 255, 255);
  // Draw some white circles
  fill(255);
  noStroke();
  ellipse(mouseX, mouseY, lineWidth, lineWidth);


  const data = {
    x: mouseX,
    y: mouseY,
  };

  console.log(`Sending ${data.x} ${data.y} `);
  socket.emit("mouse", data);
}

Serveren kan startes op med denne kommando.

npm run start

Web Scraping

Dette er et eksempel på en scraper, der finder alle links på en side, og skriver deres URL ud i konsollen. Den benytter to pakker fra npm, så hele systemet ikke skal programmeres op fra grunden. Pakken axios benyttes i eksemplet til at arbejde med http requests, og cheerio bruges til at parse html strukturen, der returneres fra serveren, og uddrage de data man er interesseret i at arbejde videre med i programmet. Disse pakker skal installeres fra npm for at eksemplet kan eksekveres på din maskine.

Først skal der laves et projekt så node kan finde ud af at køre programmet, og har en package.json fil til at holde styr på projektet og afhængigheder af biblioteksmoduler.

Start med at lave en mappe, som kan indeholde dit projekt. Kald den f.eks. web-scraping. I denne mappe skal du køre følgende kommando, for at oprette projekt filen package.json.

npm init -y

Dernæst har du mulighed for at installere disse afhængigheder.

npm --save install axios
npm --save install cheerio

Du burde nu have en package.json fil, der ser nogenlunde sådan ud:

{
  "name": "web-scraping",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.19.1",
    "cheerio": "^1.0.0-rc.3"
  }
}

Bemærk de to pakker der er listet under afhængigheder.

Derefter skal du oprette filen simple-scraper.js med følgende indhold.

const cheerio = require("cheerio");
const axios = require("axios");

// Swap this for the url you want to scrape
const url =
  "https://en.wikipedia.org/wiki/List_of_Presidents_of_the_United_States";

axios
  .get(url)
  .then(response => {
    let $ = cheerio.load(response.data);
    $("a").each(function(i, e) {
      let links = $(e).attr("href");
      console.log(links);
    });
  })
  .catch(function(e) {
    console.log(e);
  });

Nu kan du køre eksemplet med denne kommando:

node simple-scraper.js

Async / await version

Her er et eksempel med samme funktionalitet, men hvor der bruges async / await syntaks i stedet for promise.then().

// import the library modules you installed from npm
const cheerio = require("cheerio");
const axios = require("axios");

// Define the scraping algorithm
async function runScraper(url) {
  try {
    const html = (await axios.get(url)).data;
    const $ = cheerio.load(html);

    $("a").each(function(i, e) {
      const url = $(e).attr("href");
      console.log(url);
    });
  } catch (error) {
    console.log("error: " + error);
  }
}

// Swap this for the url you want to scrape
const url =
  "https://en.wikipedia.org/wiki/List_of_Presidents_of_the_United_States";

// run the scraper by calling the function
runScraper(url);

Materiale

NPM pakker

Her er en række pakker, der kan bruges til at lave scraping af websider.

Javascript emner

Tutorial

Denne tutorial er et eksempel på hvordan man kan hente en side med links til amerikanske præsidenter fra wikipedia, og ved at følge disse links hente de enkelte præsidenters navne og fødselsdage.

Websider der anvendes i eksemplet:

p5.js

Eksempler med p5.js.

Subsections of p5.js

Intro til P5js

Materiale

Tegn på skærmen

Denne video forklarer de grundlæggende principper i computergrafik.

Setup and draw

stateDiagram
    [*] --> Setup
    Setup --> Draw
    Draw-->Draw : render next frame

Tegneprimitiver

Her nogle få udvalgte, der formentlig kan bruges til at løse opgaven.

Her er nogle eksempler på hvordan de kan bruges i koden: en linie, et rektangel, en ellipse, et buestykke.

line(x1, y1, x2, y2);
rect(x, y, w, h);
arc(x, y, w, h, start, stop);

Brug af farver

Når du skal tegne kan du vælge egenskaber for din “pensel”. Dette gøres på inden du “maler”, på samme måde som med en rigtig pensel.

  • stroke() vælger stregens farve.
  • strokeWeight() vælger stregens tykkelse.
  • fill() vælger fyldfarven. Denne har kun betydning for lukkede figurer som f.eks. firkanter og cirkler.

Du kan bruge color() til at oprette en farve, og gemme værdien i en variabel, så den nemt kan genbruges forskellige steder i programmet. Her er et eksempel:

  let myColor = color(250, 142, 0)
  fill(myColor);
  circle(100, 150, 42);

Canvas

Dette eksempel viser hvordan man opretter et lærred og tegner figurer på skærmen. Der benyttes variabler og musemarkørens position til at styre hvor på skærmen der tegnes.

function setup() {
  createCanvas(windowWidth, windowHeight);
}

function draw() {
  background(220);
  fill(255);

  // variable declaration
  let x;
  // variable assignment
  x = mouseX;

  // declaration and assignment in one line
  let y = mouseY;

  circle(x, y, 150);

  // assign a new value to x
  x = 200;

  ellipse(x, y, 80, 40);

  fill(255, 0, 0);
  circle(width, height, 120);

  let cx = width / 2;
  let cy = height / 2;
  circle(cx, cy, 50);
}

Demo

Prøv det kørende eksempel

Materiale

Objects Simple

Dette eksempel viser hvordan man kan bruge vektorer i en klasse til at tegne figurer på skærmen.

Når man klikker med musen genereres et tilfældigt antal rektangler, som tegnes på skærmen.

Det grundlæggende programflow håndteres i filen sketch.js.

let shapes = []

function setup() {
  createCanvas(windowWidth, windowHeight);
  createShapes()
}

function draw() {
  background('LightSkyBlue');

  for (const shape of shapes) {
    shape.render()
  }
}

// === Helper functions
function createShapes() {
  shapes = []
  let p1 = new p5.Vector()
  let p2 = new p5.Vector()

  let shapeCount = random(10)
  for (let i = 0; i < shapeCount; i++) {
    p1.x = random(0, width)
    p1.y = random(0, height)
    p2.x = random(0, width)
    p2.y = random(0, height)

    let s = new Shape(p1, p2)
    shapes.push(s)
  }  
}

// === Event handling
function mousePressed() {
  createShapes()
}

Klassen Shape er defineret i filen shape.js.

class Shape {
  constructor(startPos, endPos) {
    this.startPos = startPos.copy()
    this.endPos = endPos.copy()
  }

  render() {
    push()
    stroke('navy')
    strokeWeight(5)
    line(this.startPos.x, this.startPos.y, this.endPos.x, this.endPos.y)
    pop()
  }
}

For at de to filer kan fungere sammen, skal de begge inkluderes i html strukturen, f.eks. som vist herunder.

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="../p5lib/p5.js"></script>
    <script src="../p5lib/p5.sound.min.js"></script>
    <link rel="stylesheet" type="text/css" href="../p5lib/default-p5-style.css" />
    <meta charset="utf-8" />
  </head>
  <body>
    <script src="shape.js"></script>
    <script src="sketch.js"></script>
  </body>
</html>

Demo

Prøv det kørende eksempel

Materiale

Circle Rain

Eksemplet tegner en mængde cirkler på skærmen, der falder mod bunder af skærmen som en slags regn. Der defineres en funktion der kan ændre cirklernes egenskaber: position og hastighed. Der benyttes arrays til at kan holde styr de mange cirklers individuelle egenskaber.

const ballCount = 100
let diameters = new Array(ballCount);
let xPositions = new Array(ballCount);
let yPositions = new Array(ballCount);

let ySpeeds = new Array(ballCount);

function setup() {
  createCanvas(windowWidth, windowHeight);
  reset(50)
}

function draw() {
  background(220);

  for (let i = 0; i < ballCount; i++) {
    circle(xPositions[i], yPositions[i], diameters[i])
    yPositions[i] += ySpeeds[i];
  }
}

function reset(maxSize) {
  for (let i = 0; i < ballCount; i++) {
    diameters[i] = random(0, maxSize);
    ySpeeds[i] = random(0.01, 10);
    xPositions[i] = random(0, width);
    yPositions[i] = random(0, height) - height;
  }  
}

function mouseClicked() {
  const maxSize = map(mouseY, 0, height,0,200)
  reset(maxSize)
}

Demo

Prøv det kørende eksempel

Materiale

Input Events

Eksemplet viser, hvordan man kan benytte input events fra keyboard og mus til at ændre på figurens udseende.

let fillColor = 'black'
let sizeMultiplier = 1

function setup() {
  createCanvas(windowWidth, windowHeight)
  rectMode(CENTER)
}

function draw() {
  background(220);
  text("Press 'r', 'b' or click mouse to change color", 40, 40)
  textSize(20)
  let xPos = mouseX
  let yPos = mouseY
  let shapeSize = 80 * sizeMultiplier

  fill(fillColor)
  if (keyIsPressed) {
    rect(xPos, yPos, shapeSize)
  } else {
    circle(xPos, yPos, shapeSize)
  }
}

function keyPressed() {
  console.log("pressed", key)
  if ('b' == key) {
    fillColor = 'blue'
  }
  if ('r' == key) {
    fillColor = 'red'
  }
}

function keyReleased() {
  console.log("released", key)
  sizeMultiplier = 1
}

function mousePressed() {
  console.log("mouse pressed")
  fillColor = 'yellow'
  sizeMultiplier = 3
}

function mouseReleased() {
  console.log("mouse released")
  fillColor = 'black'
  sizeMultiplier = 3
}

Demo

Prøv det kørende eksempel

Materiale

Circle Collision

Dette er et eksempel på, hvordan man kan benytte p5.Vector klassen til at simulere kollision mellem 2 cirkler.

Den resulterende hastighedsvektor efter kollisionen beregnes ved at benytte reflektion om normalvektoren til de sammenstødende cirkelperiferier.

Reflektionen kan beregnes ved hjælp af formlen

$$\vec r = \vec d - 2 (\vec d \cdot \vec n) \vec n$$

hvor \( \vec d \) er den indkommende vektor, \( \vec n = \frac{\vec n}{|\vec n|}\) er en normaliseret normal-vektor til cirkelperiferien, og \( \vec r \) er reflektionen af \( \vec d \) omkring \( \vec n \).

Se også implementationen af reflect().

Udledning af formlen

Reflektion af vektor omkring overfladenormal.

\(\vec n\) er en normal vektor til overfladen, og denne er normaliseret dvs. $$\vec n = \frac{\vec n}{|\vec n|}$$

Projektionen \(\vec p\) af \(\vec d\) på overfladens normalvektor \(\vec n\) findes ved hjælp af skalar produktet $$\vec p = (\vec d \cdot \vec n)\vec n$$

Vektoren \(\vec e\) bruges som hjælp i beregningen

$$\vec e = \vec d - \vec p$$

Da indfaldsvinkel og udfaldsvinkel er ens, og desuden er størrelsen af den indgående vektor og reflektionen ens \(|\vec d| = |\vec r|\) fås

$$\vec r = -\vec d + 2\vec e$$

Derefter kan formlerne kombineres

$$ \begin{aligned} \vec r &= -\vec d + 2(\vec d - \vec p) \\ &= -\vec d + 2(\vec d - (\vec d \cdot \vec n)\vec n) \\ &= -\vec d + 2\vec d - 2(\vec d \cdot \vec n)\vec n \\ &= \vec d - 2(\vec d \cdot \vec n)\vec n \end{aligned} $$

Struktur af programmet

Eksemplet består er opdelt i 3 filer, som er inkluderet i html filen således:

<script src="ball.js"></script>
<script src="bat.js"></script>
<script src="sketch.js"></script>

Det overordnede programflow styres i sketch.js.

let ball;
let bat;
function setup() {
  createCanvas(windowWidth, windowHeight);
  ball = new Ball(width / 2, height / 2)
  bat = new Bat(0,0)
}

function draw() {
  background(220);
  ball.render();
  ball.update();

  bat.render();
  bat.update();
  bat.collision(ball)
}

Filen ball.js indeholder en klassen Ball.

class Ball {
  constructor(x, y) {
    this.pos = createVector(x, y)
    this.vel = createVector(12, 3)
    this.r = 40
    this.isColliding = false
    this.collisionHandled = false
  }

  update() {
    this.pos.add(this.vel);
    this.edges();
  }

  edges() {
    const x = this.pos.x;
    if (x - this.r < 0 || x + this.r >= width) {
      this.vel.x = -this.vel.x
    }
    const y = this.pos.y;
    if (y - this.r < 0 || y + this.r >= height) {
      this.vel.y = -this.vel.y
    }
  }

  render() {
    push();
    if (this.isColliding) {
      strokeWeight(5)
    }
    if (this.collisionHandled) {
      fill('green')
    }
    circle(this.pos.x, this.pos.y, this.r * 2);
    pop();
  }
}

Filen bat.js indeholder en klassen Bat.

class Bat {
  constructor(x, y) {
    this.pos = createVector(x, y)
    this.r = 60
  }

  update() {
    this.pos = createVector(mouseX, mouseY)
  }

  render() {
    push();
    fill('red')
    circle(this.pos.x, this.pos.y, this.r * 2);
    pop();
  }

  collision(other) {
    const distance = this.pos.dist(other.pos);

    other.isColliding = distance < this.r + other.r
    if (other.isColliding) {
      if (!other.collisionHandled) {
        this._resolveCollision(other);
        other.collisionHandled = true;
      }
    } else {
      other.collisionHandled = false;
    }
  }

  _resolveCollision(other) {
    const surfaceNormal = p5.Vector.sub(this.pos, other.pos);
    other.vel.reflect(surfaceNormal);
  }
}

Bemærk hvordan reflect() benyttes til beregning af hastigheden efter kollisionen.

Demo

Prøv det kørende eksempel

Materiale

Shape Drawing

Dette eksempel viser hvordan man kan tegne figurer interaktivt ved at opbygge lister af punkter der forbindes.

Listen af punkter opbygges ved at klikke med musen, og den fremkomne figur kan føjes til listen og eller fjernes igen med keyboard kommandoer.

let currentShape = [];
let shapes = [];

function setup() {
  createCanvas(windowWidth, windowHeight);
  loadData();
}

function draw() {
  background(0);

  renderHelp();
  renderShapes();
  renderCurrentShape();
}

function renderHelp() {
  push();
  fill(220)
  textSize(20);
  text("k : Add shape", 30, 30);
  text("j : Revert last added shape", 30, 55);
  text("u : Undo last point", 30, 80);
  text("r : Clear drawing", 30, 105);
  pop();
}

function renderShapes() {
  push();
  for (const shape of shapes) {
    noFill();
    stroke('greenyellow');
    strokeWeight(2);
    beginShape();
    curveVertex(shape[0].x, shape[0].y);
    for (const p of shape) {
      curveVertex(p.x, p.y);
    }
    const lastPoint = shape[shape.length - 1];
    curveVertex(lastPoint.x, lastPoint.y);
    endShape();
  }
  pop();
}

function renderCurrentShape() {
  push();
  noFill();
  stroke('red')
  strokeWeight(5)

  for (const p of currentShape) {
    point(p.x, p.y);
  }

  strokeWeight(2)
  beginShape();
  for (const p of currentShape) {
    vertex(p.x, p.y);
  }
  endShape();
  pop();
}

function clearDrawing() {
  shapes = [];
  currentShape = [];
}

function addShape() {
  const aCopy = currentShape.slice();
  shapes.push(aCopy);
  currentShape = [];
}

function mousePressed() {
  const p = createVector(mouseX, mouseY);
  currentShape.push(p);
}

function keyPressed() {
  if (key == 'r') {
    clearDrawing();
  }
  if (key == 'j') {
    if (0 < shapes.length) {
      currentShape = shapes.pop();
    }
  }
  if (key == 'u') {
    currentShape.pop();
  }
  if (key == 'k') {
    addShape();
  }
  if (key == 'd') {
    loadData();
  }
}

function loadData() {
  shapes = [
    [
      new p5.Vector(210, 165),
      new p5.Vector(119, 198),
      new p5.Vector(96, 266),
      new p5.Vector(122, 334),
      new p5.Vector(200, 388),
      new p5.Vector(311, 374),
      new p5.Vector(330, 273),
      new p5.Vector(309, 189),
      new p5.Vector(214, 141),
      new p5.Vector(175, 147),
      new p5.Vector(153, 124),
    ],
    [
      new p5.Vector(184, 225),
      new p5.Vector(165, 231),
      new p5.Vector(166, 257),
      new p5.Vector(182, 259),
      new p5.Vector(200, 240),
      new p5.Vector(189, 226),
    ],
    [
      new p5.Vector(240, 232),
      new p5.Vector(238, 250),
      new p5.Vector(253, 266),
      new p5.Vector(270, 250),
      new p5.Vector(264, 231),
      new p5.Vector(249, 225),
      new p5.Vector(241, 226),
    ],
    [
      new p5.Vector(165, 300),
      new p5.Vector(190, 321),
      new p5.Vector(242, 333),
      new p5.Vector(271, 321),
      new p5.Vector(280, 302),
    ],
  ];

  // Center the shape horizontally
  const xOffset = width / 2 - 220;
  for (const shape of shapes) {
    for (const p of shape) {
      p.x += xOffset;
    }
  }
}

Demo

Prøv det kørende eksempel

Materiale

P5js

Arrays & iteration

Nested Loops

Dette er et eksempel på hvordan man bruge indlejrede løkker (løkker inden i andre løkker).

function setup() {
  createCanvas(windowWidth, windowHeight);
}

function draw(){
  background(0);

  const gridSize = 35;
  const focusX = mouseX
  const focusY = mouseY
  
  for (let x = gridSize; x <= width - gridSize; x += gridSize) {
    for (let y = gridSize; y <= height - gridSize; y += gridSize) {
      stroke(255, random(40, 100));
      strokeWeight(3)
      point(x, y);
      stroke(255, 20);
      strokeWeight(1)
      line(x, y, focusX, focusY);
    }
  }
}

Demo

Prøv det kørende eksempel

Materiale

Ball Objects

Dette eksempel viser hvordan kan benytte klasser og nedarvning i til at lave en simulering af hoppende bolde.

Der benyttes 3 klasser som illustreret i dette klasse diagram.

classDiagram
  Ball <|-- BouncingBall
  BouncingBall <|-- RandomBouncingBall
  Ball : constructor(position)
  Ball : update()
  Ball : render()
  Ball : r
  Ball : pos
  Ball : speed
  BouncingBall : update()
  RandomBouncingBall : lineWidth
  RandomBouncingBall : strokeColor
  RandomBouncingBall : fillColor
  RandomBouncingBall : constructor(position)
  RandomBouncingBall : render()
  RandomBouncingBall : _generateColors()

Det grundlæggende programflow håndteres i filen sketch.js.

let balls = []
let bouncingBalls = []
let randomBouncinBalls = []
let pos = new p5.Vector()

function setup() {
  createCanvas(windowWidth, windowHeight)
  pos.x = width / 2
  pos.y = height / 2
  reset()
  createBalls()
}

function draw() {
  background(220)
  renderBalls()
}

// === Helper functions
function reset(position) {
  balls = []
  bouncingBalls = []
  randomBouncinBalls = []
}

function createBalls() {
  for (let i = 0; i < 50; i++) {
    balls.push(new Ball(pos))
  }
}

function createBouncingBalls() {
  for (let i = 0; i < 20; i++) {
    bouncingBalls.push(new BouncingBall(pos))
  }
}

function createRandomBalls() {
  let spawnPoint = createVector(random(50, width - 50), random(50, height - 50))
  for (let i = 0; i < 20; i++) {
    randomBouncinBalls.push(new RandomBouncingBall(spawnPoint))
  }
}

function renderBalls() {
  for (const b of balls) {
    b.update()
    b.render()
  }
  for (const b of bouncingBalls) {
    b.update()
    b.render()
  }
  for (const b of randomBouncinBalls) {
    b.update()
    b.render()
  }
}

// === events ===
function mousePressed() {
  pos.x = mouseX
  pos.y = mouseY
  balls = []
  createBalls()

  if (keyIsDown(SHIFT)) {
    createBouncingBalls()
  }
}

function keyPressed() {
  if ('c' == key) {
    reset()
  }

  if ('r' == key) {
    createRandomBalls()
  } else if ('R' == key) {
    randomBouncinBalls = []
    createRandomBalls()
  }
}

Koden til klassen Ball ser sådan ud.

class Ball {
  constructor(position) {
    this.r = random(5, 40)
    this.pos = position.copy()

    this.speed = createVector()
    this.speed.x = random(-20,20)
    this.speed.y = random(-20,20)
  }

  update() {
    this.pos.add(this.speed)
  }

  render() {
    circle(this.pos.x, this.pos.y, this.r * 2)
  }
}

Koden til klassen BouncingBall ser sådan ud.

class BouncingBall extends Ball {
  update() {
    if (this.pos.x + this.r > width || this.pos.x - this.r < 0) {
      this.speed.x = -this.speed.x
    }
    if (this.pos.y + this.r > height || this.pos.y - this.r < 0) {
      this.speed.y = -this.speed.y
    }

    super.update()
  }
}

Her er koden til klassen RandomBouncingBall.

class RandomBouncingBall extends BouncingBall {
  constructor(pos) {
    super(pos);
    this.lineWidth = random(1,20)
    this._generateColors()
  }

  _generateColors() {
    this.strokeColor = color(random(255), random(255), random(255))
    this.fillColor = color(random(255), random(255), random(255))
  }

  render() {
    push()
    stroke(this.strokeColor)
    strokeWeight(this.lineWidth)
    fill(this.fillColor)
    super.render()
    pop()
  }
}

Demo

Prøv det kørende eksempel. Bemærk hvordan en del af boldene forsvinder ud af skærmen.

Materiale

Map Range

Dette rutediagram viser hvordan en løkke fungerer.

flowchart TD
  start((S)) --> init[INITIALIZE]
  init --> cond{CONDITION?}
  cond -->|true| body[LOOP_BODY]
  body --> step[POST_STEP]
  step --> cond
  cond ---->|false| end_loop((E))
  
  classDef termination fill:#fff,stroke:#000,color:#fff,stroke-width:4px;
  class start,end_loop termination
  
  %%classDef myClass fill:#ddd, stroke:#000;
  %%class init,cond,body,step myClass

For løkke

Her er for-løkken vist med pseudokode.

for(INITIALIZE;CONDITION;POST_STEP){
  LOOP_BODY
}

Her er et konkret eksempel i javascript, der printer tal fra 0 til 9.

for(let i = 0; i < 10; i++){
  console.log(i);
}

While løkke

En anden måde at lave en løkker er ved at bruge while.

INITIALIZE;
while(CONDITION){
  LOOP_BODY;
  POST_STEP;
}

Her er eksemplet, der printer tal fra 0 til 9, lavet med en while løkke.

let i = 0
while(i < 10){
  console.log(i);
  i++;
}

Eksempel med Løkke

Dette eksempel viser hvordan man kan benytte funktionen map(), til at lave lineær interpolation. Endepunkternes position kan varieres ved at ændre musemarkørens x-position. De små cirkler generes i for-løkken, og antallet af cirkler styres af musens y-position.

function setup() {
  createCanvas(windowWidth, windowHeight);
  fill(133, 18, 9);
  stroke(209, 52, 40)
  strokeWeight(3)
}

function draw() {
  background(70);

  // Define tilt based on mouse horizontal position
  const yRange = height / 4
  const deltaY = map(mouseX, 0, width, -yRange, yRange, true)
  
  // Define control points
  const ax = 50
  const ay = height / 2 - deltaY
  const bx = width - ax
  const by = height / 2 + deltaY

  // Draw large cirles at control poins
  const diameter = 50
  circle(ax, ay, diameter)
  circle(bx, by, diameter)

  // Define number of circles based on mouse vertical position
  let n = map(mouseY, 0, height, 30, 2, true)
  n = ceil(n)

  // Draw circles using a loop
  for (let i = 0; i <= n; i++) {
    const x = map(i, 0, n, ax, bx)
    const y = map(i, 0, n, ay, by)
    circle(x, y, diameter / 2)
  }
}

Demo

Prøv det kørende eksempel

Materiale

Logical Operators

Dette eksempel viser hvordan man kan benytte de forskellige logiske operatorer: OR, AND, NOT, XOR.

OR (||)

let result = a || b
abresult
FALSE ❌FALSE ❌FALSE ❌
FALSE ❌TRUE ✔️TRUE ✔️
TRUE ✔️FALSE ❌TRUE ✔️
TRUE ✔️TRUE ✔️TRUE ✔️

AND (&&)

let result = a && b
abresult
FALSE ❌FALSE ❌FALSE ❌
FALSE ❌TRUE ✔️FALSE ❌
TRUE ✔️FALSE ❌FALSE ❌
TRUE ✔️TRUE ✔️TRUE ✔️

NOT (!)

let result = !a
aresult
FALSE ❌TRUE ✔️
TRUE ✔️FALSE ❌

XOR

Der findes ikke en selvstændig operator, der laver XOR i javascript. Denne kan opbygges af de andre logiske operationer.

let result = a && !b || !a && b
abresult
FALSE ❌FALSE ❌FALSE ❌
FALSE ❌TRUE ✔️TRUE ✔️
TRUE ✔️FALSE ❌TRUE ✔️
TRUE ✔️TRUE ✔️FALSE ❌

Visualisering med kode

function setup() {
  createCanvas(windowWidth, windowHeight);
  rectMode(CENTER);
}

function draw() {
  background(220)
  const size = windowHeight/8
  let w = size
  let h = size
  const x = mouseX
  const y = mouseY
  const xLimit = width / 2
  const yLimit = height / 2

  // Boolean expression
  const xIsSmall = x < xLimit
  const yIsSmall = y < yLimit

  // NOT operator
  const xIsLarge = !xIsSmall
  const yIsLarge = !yIsSmall

  // defaults
  let c = color(220)
  let lineWidth = 2

  // BRANCH on condition
  if (xIsSmall) {
    w = w / 2;
  }

  if (yIsSmall) {
    h = h / 2
  }

  // OR operator
  if (xIsSmall || yIsSmall) {
    c = color("red");
  }
  // AND operator
  if (xIsLarge && yIsLarge) {
    lineWidth = lineWidth * 3;
  }

  // XOR
  const showAsRectangle = xIsLarge && !yIsLarge || !xIsLarge && yIsLarge;

  // Draw moving shape
  fill(c);
  strokeWeight(lineWidth);
  if (showAsRectangle) {
    rect(x, y, w, h)
  } else {
    ellipse(x, y, w, h);
  }

  // Draw limits
  line(xLimit, 0, xLimit, height)
  line(0, yLimit, width, yLimit)
}

Demo

Prøv det kørende eksempel

Materiale

Ball Bounce

I dette eksempel demonstreres hvordan man kan benytte forgreninger og boolske udtryk til at ændre bevægelsesretningen på en cirkel, så det minder om en bold, der hopper når den rammer siderne.

// Lav en variabel og kald den x
// giv x værdien 200
let x = 200;

// opret flere variabler
let xSpeed = 5;
let y = 200;
let ySpeed = 3;

let c;
let cFill;

// Definer en funktion der kan ændre fyld og stregfarve
function changeColor() {
  c = color(random(255), random(255), random(255));
  cFill = color(random(255), random(255), random(255));
  strokeWeight(10);
  stroke(c);
  fill(cFill);
}

function setup() {
  createCanvas(windowWidth, windowHeight);
  const speedScale = 128
  xSpeed = windowWidth / speedScale
  ySpeed = windowHeight / speedScale
  changeColor();
}

function draw() {
  background(c);

  rect(0, 0, width, height);

  // opret variabler til radius og diameter
  let r = 60;
  let d = r * 2;

  // tegn en cirkel med centrum i (x, y) og diameter d
  circle(x, y, d);

  // hvis x er større end bredden af lærredet
  // så sæt xSpeed til -xSpeed
  let isPastRightSide = width < x + r
  if (isPastRightSide) {
    changeColor();
    xSpeed = -xSpeed;
  }

  let isPastLeftSide = 0 > x - r
  if (isPastLeftSide) {
    changeColor();
    xSpeed = -xSpeed;
  }

  let isBelowBottom = height < y + r
  let isAboveTop = 0 > y - r
  if (isBelowBottom || isAboveTop) {
    changeColor();
    ySpeed = -ySpeed;
  }

  x = x + xSpeed;

  // samme som 
  // y = y + ySpeed;
  y += ySpeed;
}

Demo

Prøv det kørende eksempel

Materiale

Random Color

Dette eksempel tegner cirkler med tilfældigt valgt fyldfarve. Når venstre museknap er trykket ned skiftes til fyldfarver i tilfældige gråtoner.

I eksemplet viser hvordan man kan bruge en betingelse (museknappen er trykket ned), og en en forgrening (if-sætning) til at få programmet til at ændre opførsel.

function setup() {
  createCanvas(windowWidth, windowHeight);
}

function draw() {
  if (mouseIsPressed) {
    fill(random(255));
  } else {
    fill(random(255), random(255),random(255));
  }
  circle(mouseX, mouseY, 80);
}

Demo

Prøv det kørende eksempel

Materiale

Times Table

Dette eksempel er baseret på ideer fra denne video.

let r;
let factor = 0;
let total = 0;

function setup() {
  createCanvas(windowWidth, windowHeight);
  const maxSize = min(width, height);
  r = maxSize / 2 - 16;
}

function getVector(index, total) {
  const angle = map(index % total, 0, total, 0, TWO_PI);
  const v = p5.Vector.fromAngle(angle + PI);
  v.mult(r);
  return v;
}

let xoff = 0;

function updateColor() {
  xoff = xoff + 0.01;
  let r = noise(xoff) * 255;
  let g = noise(xoff + 200) * 255;
  let b = noise(xoff + 400) * 255;
  stroke(r, g, b);
}

function draw() {
  background(0);

  let autoRun = true;
  if (autoRun) {
    total = 100;
    factor += 0.01;
  } else {
    total = map(mouseY, 0, height, 0, 200);
    factor = map(mouseX, 0, width, 0, 20);
  }

  translate(width / 2, height / 2);

  textSize(32);
  text("total  " + nf(total, 0, 2), -width / 2 + 10, height / 2 - 70);
  text("factor " + nf(factor, 0, 3), -width / 2 + 10, height / 2 - 30);

  noFill();
  strokeWeight(2);
  updateColor();
  ellipse(0, 0, r * 2);

  strokeWeight(1);
  for (let i = 0; i < total; i++) {
    const a = getVector(i, total);
    const b = getVector(i * factor, total);
    line(a.x, a.y, b.x, b.y);
  }
}

Demo

Prøv det kørende eksempel

Materiale

Times Tables, Mandelbrot and the Heart of Mathematics

Key Press Multi

Her er et eksempel der viser hvordan man kan håndtere input fra flere taster på en gang.

let cx;
let cy;

let state = {
  north: false,
  south: false,
  west: false,
  east: false,
  boost: false
};

function setup() {
  createCanvas(windowWidth, windowHeight);
  cx = width / 2;
  cy = height / 2;
}

function draw() {
  background(220);
  fill(0);
  noStroke();
  textSize(20);
  text(`Move using arrow keys and space`, 10, 30);

  // show
  if (state.boost) {
    fill("orange");
    stroke("red");
  } else {
    fill("gray");
    stroke(0);
  }

  strokeWeight(5);
  circle(cx, cy, 50);

  // update position
  const step = state.boost ? 5 : 1;
  if (state.west) {
    cx -= step;
  }
  if (state.east) {
    cx += step;
  }
  if (state.north) {
    cy -= step;
  }
  if (state.south) {
    cy += step;
  }
}

function keyPressed() {
  console.log("Pressed:", key, keyCode)
  if (key === " ") {
    state.boost = true;
  }
  if (keyCode === LEFT_ARROW) {
    state.west = true;
  }
  if (keyCode === RIGHT_ARROW) {
    state.east = true;
  }
  if (keyCode === UP_ARROW) {
    state.north = true;
  }
  if (keyCode === DOWN_ARROW) {
    state.south = true;
  }
  return true
}

function keyReleased() {
  console.log("Released:", key, keyCode)
  if (key === " ") {
    state.boost = false;
  }
  if (keyCode === LEFT_ARROW) {
    state.west = false;
  }
  if (keyCode === RIGHT_ARROW) {
    state.east = false;
  }
  if (keyCode === UP_ARROW) {
    state.north = false;
  }
  if (keyCode === DOWN_ARROW) {
    state.south = false;
  }
  return true
}

Bemærk at både keyPressed() og keyReleased() hændelser håndteres for alle gyldige input.

Status for de enkelte input registreres i variablen state, som er et objekt med de nødvendige attributter.

I eksemplet er håndteringen af venstre piletast fremhævet.

Denne måde at håndtere input bevirker at figuren kan bevæges diagonalt, ved at trykke på to taster samtidigt. Desuden er bevægelsen uafhængig af repeat rate i keyboard indstillingerne.

Demo

Prøv det kørende eksempel

Materiale

Keyboard

I dette eksempel bruges keyIsPressed til at ændre fyldfarven på figuren.

function setup() {
  let canvas = createCanvas(400, 400);
  canvas.parent("sketch-holder");
}

function draw() {
  background(100, 0, 0);
  fill("red");
  circle(50, 150, 50);
  circle(width - 50, 50, 50);

  if (keyIsPressed === true) {
    fill(0);
  } else {
    fill(255);
  }
  rect(25, 25, 50, 50);
}

Demo

Her kan du se en kørende version af programmet. Prøv at trykke på en vilkårlig tast på tastaturet.

Prøv også det kørende eksempel på en selvstændig side.

Materiale

Fullscreen toggle

Dette eksempel viser hvordan man kan køre en sketch, der fylder hele skærmen.

let cnv;

function setup() {
  cnv = createCanvas(windowWidth, windowHeight);
  centerCanvas();
  background(255, 0, 200);
}

function draw() {
  background(220);

  const cSize = 50;
  fill(20);
  circle(width / 2, height / 2, cSize);
  fill(255);
  circle(100, 100, cSize);
  circle(width - 100, 100, cSize);
  circle(100, height - 100, cSize);
  circle(width - 100, height - 100, cSize);

  fill(0, 102, 153);
  textSize(20);
  textAlign(CENTER);
  text("Click me!", width / 2, height / 2 - cSize);
}

function mousePressed() {
  if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
    let fs = fullscreen();
    fullscreen(!fs);
  }
}

function centerCanvas() {
  let x = (windowWidth - width) / 2;
  let y = (windowHeight - height) / 2;
  cnv.position(x, y);
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  centerCanvas();
}

Demo

Prøv det kørende eksempel

Key Pressed Simple

Eksempel på simpel håndtering af input-hændelser (events) fra tastaturet med keyPressed().

let cx;
let cy;

function setup() {
  createCanvas(windowWidth, windowHeight);
  cx = width / 2;
  cy = height / 2;
}

function draw() {
  background(20);
  fill("gray");
  textSize(20);
  text(`Move using arrow keys`, 10, 30);

  fill("red");
  noStroke();
  circle(cx, cy, 50);
}

function keyPressed() {
  const step = 10;
  if (keyCode === LEFT_ARROW) {
    cx -= step;
  }
  if (keyCode === RIGHT_ARROW) {
    cx += step;
  }
  if (keyCode === UP_ARROW) {
    cy -= step;
  }
  if (keyCode === DOWN_ARROW) {
    cy += step;
  }
}

Demo

Prøv det kørende eksempel

Materiale

Multitouch

Dette er et eksempel på hvordan man man håndtere flere samtidige berørings hændelser, f.eks. som knapper i et mobil spil, der kræver flere samtidige inputs.

let buttons = {
  left: false,
  right: false
};
const bSize = 80;
const margin = 20;

let leftButton;
let rightButton;

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);

  leftButton = createButton("left");
  leftButton.mousePressed(leftPressed);
  leftButton.mouseReleased(leftReleased);
  leftButton.touchStarted(leftPressed);
  leftButton.touchEnded(leftReleased);
  leftButton.style("background-color", "red");
  leftButton.class("noselect");
  leftButton.size(bSize, bSize);

  rightButton = createButton("right");
  rightButton.mousePressed(rightPressed);
  rightButton.mouseReleased(rightReleased);
  rightButton.touchStarted(rightPressed);
  rightButton.touchEnded(rightReleased);
  rightButton.style("background-color", "green");
  rightButton.class("noselect");
  rightButton.size(bSize, bSize);

  handleButtonPositions();
}

function draw() {
  background(100);

  // left button
  fill(buttons.left ? "red" : "pink");
  rect(margin, margin, bSize, bSize);

  // right button
  fill(buttons.right ? "green" : "teal");
  rect(width - margin - bSize, margin, bSize, bSize);
}

const leftPressed = () => (buttons.left = true);
const leftReleased = () => (buttons.left = false);
const rightPressed = () => (buttons.right = true);
const rightReleased = () => (buttons.right = false);

const handleButtonPositions = () => {
  leftButton.position(margin, height - margin - bSize);
  rightButton.position(width - margin - bSize, height - margin - bSize);
};

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  handleButtonPositions();
}

Bemærk at det er nødvendigt med en smule styling via CSS for at få eksemplet til at virke. Derfor er der tilføjet en klasse til button elementerne, og denne regel er tilføjet til stylesheet for siden.

.noselect {
  -webkit-touch-callout: none; /* iOS Safari */
    -webkit-user-select: none; /* Safari */
     -khtml-user-select: none; /* Konqueror HTML */
       -moz-user-select: none; /* Old versions of Firefox */
        -ms-user-select: none; /* Internet Explorer/Edge */
            user-select: none; /* Non-prefixed version, currently
                                  supported by Chrome, Opera and Firefox */
}

Demo

Prøv det kørende eksempel

Polar Loop

Eksempel på brug af polære koordinater.

function setup() {
  createCanvas(windowWidth, windowHeight);
}

function draw() {
  background(220);
  noFill();
  
  const scale = map(mouseX, 50, width - 50, 0, 30, true);
  const tick = map(mouseY, 100, height - 100, PI / 12, (2 * PI) / 3, true);
  
  translate(width / 2, height / 2);
  const diameter = 20;
  circle(0, 0, diameter);

  const magnitude = 10 * scale;
  circle(0, 0, magnitude * 2);

  fill("blue");
  for (let theta = 0; theta < 2 * PI; theta += tick) {
    const cx = magnitude * cos(theta);
    const cy = magnitude * sin(theta);

    circle(cx, cy, diameter);
  }
}

Demo

Prøv det kørende eksempel

Materiale

Polar Spinner

Eksempel med omregning mellem polære og kartesiske koordinater.

let angle = 0;

function setup() {
  createCanvas(windowWidth, windowHeight);
}

function draw() {
  background(0);
  let diameter = 50;
  translate(width / 2, height / 2);

  rotate(angle);
  const speed = map(mouseX, 0, width, -1, 1, true);
  angle += 0.1 * speed;
  const showDots = mouseY < height/2;
  
  fill(0);
  textSize(40);
  text("Tak for kaffe :-)", 30, 10);
  
  noFill();
  strokeWeight(5);
  stroke('greenyellow')
  beginShape();
  magnitude = 0;
  const magStep = map(mouseY, 0, height, 0, 2, true);
  for (let theta = 0; theta < 100 * PI; theta += 0.2) {
    let cx = magnitude * cos(theta);
    let cy = magnitude * sin(theta);
    if(showDots){
      point(cx, cy);
    } else {
      vertex(cx, cy);
    }
    magnitude += magStep;
  }
  endShape();
}

Demo

Prøv det kørende eksempel

Materiale

Tacospin

Dette er et eksempel på hvordan man kan loade et billede ind i p5, og ændre på skalering og rotation.

let img;

function preload() {
  img = loadImage("assets/taco.jpg");
}

function setup() {
  createCanvas(windowWidth, windowHeight);
}

let angle = 0;
function draw() {
  background(220);

  const speed = map(mouseX, 0, width, -1, 1, true);
  // show angle value
  const tHeight = 40;
  const margin = 10;
  textSize(tHeight);
  let tposY = margin + tHeight;
  text(`Angle ${nfc(angle, 2)}`, margin, tposY);
  tposY += margin + tHeight;
  text(`Speed ${nfc(speed, 2)}`, margin, tposY);

  // draw at center of canvas
  const cx = width / 2;
  const cy = height / 2;
  translate(cx, cy);

  // compute image scale
  const scale = 0.3;
  const dw = img.width * scale;
  const dh = img.height * scale;

  // draw image
  rotate(angle);
  imageMode(CENTER);
  image(img, 0, 0, dw, dh);

  angle += 0.1 * speed;
}

Demo

Prøv det kørende eksempel

Materiale

HSL Colors

Et eksempel der viser hvordan HSL farver kan bruges i p5.

function setup() {
  createCanvas(windowWidth, windowHeight);
  slider = createSlider(0, 255, 200, 1);
  slider.position(width/2-255, height/2-300);
  slider.style('width', `${255*2}px`);
}

function draw() {
  translate(width / 2, height / 2);

  const x1 = 0;
  const y1 = 0;
  const x2 = mouseX - width / 2;
  const y2 = mouseY - height / 2;

  const angle = atan2(y2, x2);

  // d is the length of the line
  // the distance from point 1 to point 2.
  const d = int(dist(x1, y1, x2, y2));
  const saturation = constrain(d, 0, 255);
  const hueVal = map(angle, -PI, PI, 0, 255);
  const brightnessVal = slider.value();

  const x3 = saturation * cos(angle);
  const y3 = saturation * sin(angle);

  // background(200);
  colorMode(HSB, 255);
  const c = color(hueVal, saturation, brightnessVal);
  noFill();
  circle(x1, y1, 255*2);
  fill(c);
  // line(x1, y1, x3, y3);
  const circleDiameter = 30;
  stroke(0)
  strokeWeight(3)
  circle(x1, y1, circleDiameter * 2);
  noStroke()
  circle(x3, y3, circleDiameter);

  // Write values as text
  let msg = `abs: ${d}, angle: ${nfc(angle, 2)}`;
  let hsvMsg = `hue: ${nfc(hueVal, 0)}, saturation: ${nfc(saturation)}, brightness: ${nfc(brightnessVal)}`;

  push();
  translate(x1 -200, y1 - 320);
  noStroke();
  fill(255);
  rect(-10,-50,400,60)
  fill(0);
  textSize(20);
  text(msg, 0, -5);
  text(hsvMsg, 0, -30);
  pop();
}

Demo

Prøv det kørende eksempel

Materiale

Stickman Objects

Denne demonstration er et eksempel på hvordan man kan bruge klasser og objekter til at strukturere koden. I eksemplet tegnes nogle hoppende tændstikmænd med forskellige egenskaber.

Filen sketch.js indeholder den sædvanlige struktur for et program skrevet i p5js.

I setup() oprettes et lærred, og der oprettes tre objekter af klassen stickman. Bemærk at de bliver initialiseret med forskellige egenskaber via deres constructor.

I draw() kaldes metoderne render() og update() på begge de to StickMan objekter.

Derudover er metoden mouseClicked() implementeret, og denne sørger for at kalde metoden jump på de to stickman objekter, med den effekt at de begge hopper når der klikkes med musen.

let man;
let child;

function setup() {
  createCanvas(windowWidth, windowHeight);
  const halfWidth = width/2;
  man = new StickMan(halfWidth +150, 70, 25, "blue");
  woman = new StickMan(halfWidth - 150, 60, 15, "red");
  child = new StickMan(halfWidth, 30, 45, "greenyellow");
}

function draw() {
  background(220);
  textSize(20)
  text("Klik med musen for at hoppe", 30, 40)
  man.render();
  man.update();

  woman.render();
  woman.update();

  child.render();
  child.update();
}

function mouseClicked() {
  man.jump();
  woman.jump();
  child.jump();
}

Herunder ses implementationen af stickman klassen, som er lavet separat i filen stickman.js for at gøre koden mere overskuelig.

class StickMan {
  constructor(x, minHeight, jumpSpeed, color) {
    this.x = x;
    this.jumpSpeed = jumpSpeed;
    this.minHeight = minHeight;
    this.height = minHeight;
    this.color = color;
    this.y = height / 2;
    this.vy = 0;
    this.gravity = 1.5;
  }

  jump() {
    if (this.y == height) {
      this.vy = - this.jumpSpeed;
    }
  }

  update() {
    // fall down
    this.y += this.vy;
    // increase fall speed due to gravity
    this.vy += this.gravity;
    // stop at floor
    this.y = constrain(this.y, -3000, height);
    // change the size
    this.height = map(mouseX, 0, width, this.minHeight, this.minHeight * 6);
  }

  render() {
    // compute the dimensions of the body parts
    const headDiameter = this.height * 0.2;
    const headRadius = headDiameter / 2;
    const bodyHeight = (this.height - headDiameter) * 0.4;
    const legHeight = this.height - headDiameter - bodyHeight;

    const legWidth = headDiameter * 0.5;
    const armWidth = headDiameter * 0.75;
    const armHeight = headDiameter * 1.8;
    const neckLength = headDiameter * 0.3;

    const headX = this.x;
    const headY = this.y - this.height + headRadius;

    // draw the stickman
    push();
    fill(this.color);
    strokeWeight(3);
    // head
    circle(headX, headY, headDiameter);
    // body
    line(headX, headY + headRadius, headX, headY + headRadius + bodyHeight);
    // arms
    const shoulderY = headY + headRadius + neckLength;
    line(headX, shoulderY, headX - armWidth, shoulderY + armHeight);
    line(headX, shoulderY, headX + armWidth, shoulderY + armHeight);
    // legs
    const legY = headY + headRadius + bodyHeight;
    line(headX, legY, headX - legWidth, legY + legHeight);
    line(headX, legY, headX + legWidth, legY + legHeight);
    pop();
  }
}

Husk at begge filer skal inkluderes i html strukturen. Hvis de ligger i samme mappe som html filen kan det gøres således:

<script src="stickman.js"></script>
<script src="sketch.js"></script>

Demo

Prøv det kørende eksempel

Materiale

Sound

Her er et eksempel på hvordan man kan arbejde med lyd i p5.

let flickSound;
let whistleSound;
let whistleShortSound

function preload() {
  flickSound = loadSound('flick.mp3');
  whistleSound = loadSound('whistle.mp3', doneLoadingWhistleSound);
  whistleShortSound = loadSound('whistle-short.mp3', () => { 
    console.log('short ready');
  });
}

function setup() {
  createCanvas(windowWidth, windowHeight);
}

function doneLoadingWhistleSound(){
  console.log("doneLoadingWhistleSound");
}

function draw() {
  background(220);
  fill("blue");
  circle(width / 2, height / 2, 50);
  textSize(20);
  text("Tryk på: SPACE, K, L, W", 50,50);
  text("eller klik med musen", 50,80);

}

function mouseClicked(){
  console.log("flick started by mouse");
  flickSound.play();
}

function keyPressed(){
  if(' ' == key){
    console.log("flick started by keyboad");
    flickSound.play();
  }

  if('l' == key){
    whistleSound.setLoop(true);
    whistleSound.play();
  }
  if('k' == key){
    whistleSound.setLoop(false);
  }

  if('w' == key) {
    console.log("short whistle");
    whistleShortSound.play();
  }
}

Demo

Prøv det kørende eksempel.

Materiale

17.5: Adding Sound Effects - p5.js Sound Tutorial

Projekter

En samling ideer til opgaver og software projekter.

“The true sign of intelligence is not knowledge but imagination.” – Albert Einstein

Subsections of Project

HTML Basics

For at kunne løse opgaven har du sikkert brug for en kort introduktion til HTML.

Opgave: Mig og min kageopskrift

Lav et website i html der præsenterer dig selv og din yndlings kage:

  • Der skal være 2 sider.
    • En beskrivelse af dig selv der navngives “profil.html”.
    • En anden side med en mad opskrift “opskrift.html”.
  • Begge sider skal have en titel.
  • Der skal være links så man kan navigere mellem de to sider.

På profil siden

  • Brug forskellige niveauer af overskrifter.
  • Lav mindst et afsnit med brødtekst (f.eks. En kort beskrivelse af dig selv).

På siden med opskriften

  • Lav overskrifter.
  • Indsæt et billede af kagen.
    • Giv det en figurtekst.
  • Lav en unummereret punktopstilling med ingredienser.
  • Lav en nummeret punktopstilling med fremgangsmåden.
  • Lav en henvisning (link) til kilden (hvor du fandt opskriften).
  • Lav et link til “toppen” lokalt link på siden.

Opgave: Styling med CSS

Arbejd med styling af HTML elementer vha CSS i eksternt stylesheet

  • Brug html struktur fra opgaven med kageopskriften (index.html)
  • Lav styling af siden ved hjælp af CSS i en ekstern fil (style.css)

Materiale

String Manipulation

  1. Skriv et program, der finder positionen af det første mellemrum i en streng.
  2. Skriv et program, der fjerner det første ord i en sætning (indtil første mellemrum).
  3. Skriv et program, der tæller antallet af mellemrum i en tekst.
  4. Skriv et program, der fjerner den første forekomst af ordet “måske” fra en tekst. Ændr derefter programmet, så det fjerner alle forekomster af ordet (brug f.eks. en løkke).
  5. Skriv et program, der finder og fjerner alle forekomster af ordet “måske” fra en tekst, uanset om det er skrevet med store eller små bogstaver.
  6. Skriv et program, der undersøger, om en tekst er et palindrom, dvs. med samme stavning forfra og bagfra (som f.eks. “regninger”, “russerdressur”, “vær dog god ræv”).
  7. Udvid programmet til at tage højde for store/små bogstaver, tegnsætning og mellemrum, sådan at de følgende palindromer også genkendes: “Selmas lakserøde garagedøre skal samles” og “Åge lo, da baronesse Nora bad Ole gå”.

Materiale

Palindromer

  • rotor
  • Anna
  • RADAR
  • kajak
  • pop
  • madam
  • regninger
  • otto
  • Otto
  • programmering
  • russerdressur
  • vær dog god ræv
  • Selmas lakserøde garagedøre skal samles
  • Hej med dig jeg hedder Kaj
  • Åge lo, da baronesse Nora bad Ole gå

Python

C#

Array Stats

  1. Lav et program, der simulerer kast med 6 terninger. Der udføres f.eks. 100 kast. Optæl i et array hyppigheden af summen af øjenantallene.
  2. Udvid programmet til at kunne lave statistik på kast med et vilkårligt antal terninger.
  3. Udvid programmet til at kunne lave statistik på kast med terninger med et vilkårligt antal sider.
  4. Ændr programmet, så man kan angive antallet af terninger, gentagelser og sider på kommandolinjen.

Hints

For at lave en ’terning’ med seks sider kan du bruge denne stump kode, der generer et tilfældigt tal mellem 1 og 6.

let value = Math.floor(Math.random() * 6) + 1

Kommandolinje argumenter kan f.eks. benyttes således

const args = process.argv.slice(2)
console.log(args)

if(1 == args.length){
  const name = args[0]
  console.log(`Hello, ${name}!`)
} else {
  console.log("Hello, world!")  
}

Her en lidt mere udførlig introduktion til CLI argumenter.

Materiale

Mastermind

Mastermind spillet.

I dette projekt skal der laves en løsning der gør de muligt at dyste mod computeren i Mastermind.

Opgave: Mastermind Bot

Denne opgave går ud på at lave en discord bot som man kan dyste mod i “Mastermind”.

Del 1: Discord bot (fundament)

Lav en discord bot som beskrevet i disse guides:

Del 2: Mastermind Bot (udvidelse)

Lav en ny kommando i din discord bot, der gør det muligt at spille “Mastermind” mod computeren.

Start med at studere reglerne til spillet. Kender du ikke spillet i forvejen, kan du med fordele spille et par spil vha. en af de mange apps, der er lavet. F.eks denne udgave af mastermind til Android, eller i en web udgave af mastermind.

Kravspecifikation

  • Det skal være muligt at få hjælp til interaktion med spillet (e.g syntaks for gyldige kommandoer).
  • Det skal være muligt at starte spillet.
  • Computeren skal generere en hemmelig kode.
  • Koden skal bestå af 4 ud af mulige 6 forskellige farver (eller tegn).
  • Der må gerne forekomme gentagelser af den samme farve i koden.
  • Det skal være muligt at afgive et gæt.
  • Bot’en skal give tilbagemelding på hvert gæt bestående af
    • Antal modtagne gæt.
    • Gentagelse af det afgivne gæt
    • Angivelse af antal rigtigt placerede farver
    • Angivelse af antal rigtige farver
  • Hvis man gætter koden, skal der gives tilbagemelding om at man har vundet.
  • Hvis koden ikke er gættet efter 10 forsøg, har computeren vundet. Dette skal annonceres i svaret, og den hemmelige kode afsløres, så man kan kontrollere at alt er gået retfærdigt til.

Ekstra ideer og nice to have funktioner

  • Man skal have mulighed for at give op, få vist koden og starte forfra.
  • Hold styr på hvor mange gange parterne har vundet.

Her er et eksempel på hvordan interaktionen med programmet kan laves.

!mm help
Welcome to Mastermind!

Guess the secrect code.
Code consist of 4 numbers in the range 1 through 6.

Show this help message

  !mm
or
  !mm help

Start / restart game

  !mm start

Make a guess for the code 1234

  !mm g 1234 

The game response shows your guesses and provides feedback
  o : code contains number
  x : correct placement of number
!mm start
Starting new mastermind game, generated secrect code.
!mm g 1234
1 : [1, 2, 3, 4] | ooo
!mm g 1235
1 : [1, 2, 3, 4] | ooo
2 : [1, 2, 3, 5] | ooo
!mm g 1236
1 : [1, 2, 3, 4] | ooo
2 : [1, 2, 3, 5] | ooo
3 : [1, 2, 3, 6] | oo
!mm g 2354
1 : [1, 2, 3, 4] | ooo
2 : [1, 2, 3, 5] | ooo
3 : [1, 2, 3, 6] | oo
4 : [2, 3, 5, 4] | xooo
!mm g 2543
1 : [1, 2, 3, 4] | ooo
2 : [1, 2, 3, 5] | ooo
3 : [1, 2, 3, 6] | oo
4 : [2, 3, 5, 4] | xooo
5 : [2, 5, 4, 3] | xxxx
### You win ###
Secret code is: [2, 5, 4, 3]

Rock Paper Scissor

Opgave: RPS CLI

Lav et kommandobaseret program der gør det muligt at spille “Sten-saks-papir” mod computeren.

Programmet skal kunne tage mod simple input kommandoer, tolke betydningen, og vise dem i output som en “tegning” lavet med nogle ascii symboler, f.eks.:

InputBetydningSymbol
rsten0
ppapir
ssaks>8

Programmet skal også holde styr på hvor mange gange parterne har vundet.

Hvordan dette håndteres internt i programmet er op til programmøren at finde en god løsning på.

Her er et eksempel på hvordan output kunne se ud.

rock-paper-scissor $ .\bin\Debug\netcoreapp3.1\rock-paper-scissor.exe
Welcome to RPS!

Make your selection!
Rock(r), Paper (p), Scissor (s)!?
Result:    >8 vs 0     => Computer WINS
Score: Player(0) vs Computer(1)
Press any key to continue or ESC to quit

Make your selection!
Rock(r), Paper (p), Scissor (s)!?
Result:     0 vs 0     => TIE
Score: Player(0) vs Computer(1)
Press any key to continue or ESC to quit

Make your selection!
Rock(r), Paper (p), Scissor (s)!?
Result:   --- vs 0     => Player WINS
Score: Player(1) vs Computer(1)
Press any key to continue or ESC to quit

Opgave: RPS Discord Bot

Denne opgave går ud på at lave en discord bot som man kan dyste mod i “sten-saks-papir”.

Del 1: Discord bot (fundament)

Lav en discord bot som beskrevet i disse guides:

Del 2: Sten-Saks-Papir (udvidelse)

Lav en ny kommando i din discord bot, der gør det muligt at spille “Sten-Saks-Papir” mod computeren. Hold styr på hvor mange gange parterne har vundet.

Her er et eksempel på hvordan interaktionen med programmet kan laves.

!rps scissor
Result:    >8 vs 0     => Computer WINS
Score: Player(0) vs Computer(1)
!rps rock
Result:     0 vs 0     => TIE
Score: Player(0) vs Computer(1)
!rps paper
Result:   --- vs 0     => Player WINS
Score: Player(1) vs Computer(1)

Shuffle & Sort

I dette projekt arbejdes med algoritmer til blanding og sortering.

Opgave: Shuffle

Der skal laves et program, der kan blande en række heltal i et array.

  • Lav en beskrivelse af hvordan algoritmen fungerer.
  • Din beskrivelse skal indeholde et flow chart.
  • Lav en implementation i C#. Hint: Du kan benytte koden i ShuffleProgram.cs som udgangspunkt.

Hint: Du får sikkert brug for at generere tilfældige tal, hvilket kan gøres med System.Random.Next().

I kan f.eks. benytte Fisher–Yates shuffle som blande-algoritme.

Opgave: Sort

  • Lav en beskrivelse af en sorterings algoritme, som også indeholder et flowchart / pseudokode.
  • Lav en implementation af en sorteringsalgoritme i C#.

Hint: Det er oplagt at i bruge output fra shuffle algoritmen som input til sorteringsalgoritmen, så den let kan afprøves med forskellige inputs.

Materiale

John Conway's: Game of life

Opgave

  • Læs og forstå de simple regler for Game of Life.
  • Lav manuel simulering af algoritmen. Den kan simuleres ved at afvikle game of life på et stykke papir eller en ternet spilleplade. Brug perler / lego / eller andre små genstande til at symbolisere levende celler.
  • Lav derefter din egen version af version i p5.js. Som inspiration kan du bruge denne videopræsentation.

Coding Challenge #85: The Game of Life

Materiale

Volapyk-dansk

Opgave: Simulering af ordblindhed

  • Lav et program der kan lave tekster om til “volapykdansk” ala nedenstående.
  • Reglerne for konvertering af teksten er beskrevet i det følgende.

Iøfgle en uerndsøeglse på Cmabrigde Uinvertisy bteyedr det ikke ngoet i hivklen rkkæefgløe bgotsavrnee såtr i et ord, det eestne vgitgie er at det frøtse og sditse bgsoatv i odret er på de rtete psadler. Rsteen kan lngie vloaypk, men du vil sdtaig vræe i snatd til at lsæe det. Det er frodi den mnenesekilge hejrne ikke lesær hevrt bgotasv, men odret som en hhleed.

Materiale

Mobile Game

Opgave: Mobile Game / App

  • Tema: FNs Verdensmål
  • Lav et spil.
  • Målgruppen for spillet skal defineres klart.
  • Spillet skal kunne køre på en android telefon.
  • Der må gerne være et socialt element, f.eks. multiplayer via netværk.
  • Der skal bruges input fra de tilgængelige sensorer, som er i en smartphone (kompas, gps, accelerometer, gyro, etc.)
    • Som minimum skal input en af sensorerne anvendes i spillet

Opgaven laves i grupper.

Der skal laves en projektrapport, der dokumenterer udviklingen af produktet (jeres spil / app).

Processen i udviklingen af spillet skal dokumenteres i en logbog

Materiale

I gang med Unity og android

Tutorials (til at komme i gang)

3D assets

  • Poly 3D model bibliotek, Google

Inspiration

Unity ideas

Code Lock

Dette projekt går ud på at lave en kodelås til en automatisk bom ved at programmere Lego Mindstorms.

Hardware setup

Start med at bygge en konstruktion af din automatiske bom med kodelæser. Du skal også bruge et antal farver, der kan scannes når du tester programmet.

Konstruktionen kan laves ud fra denne byggevejledning.

Forbindelser til EV3 brikken

ModulPort
Stor motorC
color sensor2
trykknap4

Opgave

Lav en kodelås der styrer en automatisk bom.

  • Man skal kunne låse den op ved hjælp af indlæsning af farver i en bestemt sekvens.
  • Indlæsningen af hver farve bekræftes med et tryk på den tilsluttede knap.
  • Man skal kunne ændre på farverne og længden af sekvensen uden at lave en større modifikation af programmet (Hint: Benyt f.eks et array).

Forslag til kodestruktur

Denne struktur kan bruges som skabelon til at løse opgaven.

let code: number[] = []
let isCorrect = false

function openGate() {
    // TODO Scan code and determine if it matches correct
}

sensors.color2.setMode(ColorSensorMode.Color)
motors.largeC.setBrake(true)

code = [ColorSensorColor.Red, ColorSensorColor.Green, ColorSensorColor.Blue, ColorSensorColor.Brown]
forever(function () {
    // Indicate program is running
    brick.showImage(images.eyesBlackEye)
    isCorrect = true

    // TODO Scan code and determine if it matches correct sequence

    if (isCorrect) {
        openGate()
    } else {
        // TODO indicate wrong code
    }
})

Materiale

Styring af LED via web interface

Arduino og Neopixel LED array

LED array hardware

I dette projekt handler det om at styre lyset i en række multifarvede LED’er, som vist på billedet.

Tanken er at bygge videre på tre kode eksempler, så enkeltdelene kan bruges til at lave et system der kan styre lyset i LED arrayet fra en browser, f.eks. i en mobiltelefon.

Opgaven

Lav et system, der kan styre en række Neopixel LED’er fra et webinterface i en browser.

Systemet tænkes at bestå af følgende komponenter:

  • En Arduino med tilsluttet array af NeoPixel LEDs.
  • En server lavet i node.js, der håndterer kommandoer fra brugerens browser.
  • Et simpelt node modul, der håndterer kommunikation med Arduino via serielporten.
  • Brugerinterface baseret på HTML, CSS og Javascript, der via websockets sender beskeder til serveren om styring af LED lys.

Kode eksempler

  • Styring af neopixel LED array med arduino - Dette indeholder også beskrivelse af hardware.
  • Klient / server kommunikation. Dette eksempel viser hvordan man kan kommunikere mellem klienter via Socket.io, ved at lave en server i node.js.
  • Arduino JSON commands. Viser hvordan man kan håndtere udveksling af beskeder mellem computer og Arduino.
  • Serial Port Kommunikation. Dette eksempel viser hvordan man kan sende og modtage json beskeder via seriel porten, ved hjælp af et script i node.js. Teknikken kan bruges til kommunikation mellem computer og Arduino ved hjælp af simple kommandoer.

Materiale

Arduino

Styring af LED’er

Command parsing (JSON) on the Arduino

Node.js Serial port kommunikation

Weather Station

Opgave: Mobil vejrstation

Lav et it system der gør det muligt at se data fra en vejrstatation på en nem og overskuelig måde. Løsningen skal virke på mobil, tablet og computeren.

  • Opgaven løses i grupper.
  • Der skal laves en kravspecifikation.
  • I skal bruge et projetstyrings værktøj til at styre jeres opgaver, f.eks. Trello eller Milanote. “Oversæt” krav fra kravspecifikationen til konkrete opgaver på jeres trello board.
  • Løsningen skal designes så den ikke belaster vejrstationen unødigt når den bruges samtidig af mange brugere, f.eks. ved at sende unødigt mange forespørgsler.
  • Design og implementation skal dokumenteres.
  • Brug git og github til at samarbejde om koden.

Som kilde til vejrdata kan f.eks. benyttes vejrobservationer fra Hanstholm havns vejrstation. Som datalager kan f.eks. benyttes Firebase.

Materiale

Firebase

Uploading to firestore

Hotdog status - Firebase tutorial

Firecasts firebase intro

Asteroids Game

Opgave: Lav et “Asteroids” spil

  • Opgaven skal laves i grupper.
  • I skal lave en kravspecifikation
  • I skal lave implementeringen i javascript ved hjælp af p5js.
  • I skal bruge Github projects til at styre jeres opgaver. “Oversæt” krav til jeres spil fra kravspecifikationen til opgaver på jeres project board.
  • Brug git og github til at samarbejde om koden.

Materiale

Javascript emner

For at kunne lave spillet bliver det formentlig nyttigt at kende til disse koncepter

Coding Challenge #46.1: Asteroids with p5.js - Part 1

Coding Challenge #46.2: Asteroids with p5.js - Part 2

3.2: Trigonometry and Polar Coordinates - The Nature of Code

Lydeffekter

Her er lidt inspiration til hvordan der kan lægges lydeffekter på spillet.

17.5: Adding Sound Effects - p5.js Sound Tutorial

Rekursion

Intro

Undersøg hvad rekursion betyder. Søg på recursion på google.

Towers of Hanoi

Denne gennemgang viser hvordan man kan bruge rekursion til at løse “Towers of Hanoi” spillet.

Python kode fra videoen.
def move(f,t):
    print("Flyt: {} ==> {}!".format(f,t))

def moveVia(f,v,t):
    move(f,v)
    move(v,t)

def hanoi(n,f,h,t):
    if n == 0:
        pass
    else:
        hanoi(n-1, f, t, h)
        move(f,t)
        hanoi(n-1, h, f ,t)

Opgave 1: Towers of hanoi i javascript

Lav en implementation af løsningen på towers of hanoi i javascript ved hjælp af rekursion.

Opgave 2: Towers of hanoi i C#

Lav en implementation af løsningen på towers of hanoi i C# ved hjælp af rekursion.

Materiale

Stickman

Skitse af tændstikmand

Eksempel på skitse af tændstikmand. Bemærk at y-aksen regnes positivt fra toppen og ned.

Opgave: Lav en tændstikmand

Opgaven går ud på at eksperimentere med computergrafik. Ved hjælp af p5js skal du lave et program der benytter forskellige tegne funktioner, til at generere en “tændstikmand”.

Han skal som minimum bestå af:

  • hoved
  • mund
  • øjne
  • krop
  • arme
  • ben

Ekstra: til den “kvikke elev”; få din tændstikmand til at ændre udseende dynamisk f.eks. vha. museinput, knaptryk

  • hovedets størrelse
  • blinke med øjnene
  • ændre farver

Du kan med fordel tegne en skitse først, så du nemmere kan holde styr på koordinater.

Vær kreativ :-)

Materiale

Værktøj

Her er en samling af software udviklingsværktøj.

Subsections of Tools

Command Line Basics

Når man skal arbejde med programering og softwareudvikling, er det ofte nyttigt at kunne anvende et kommandolinjeinterface, til nogle basale opgaver.

Nyttige kommandoer

  • pwd (path to working directory) viser stien til den mappe man befinder sig i.
  • cd (change directory) skifter til den mappe, der angives som argument.
  • ls (list directory) viser indholdet i en mappe.
  • mkdir (make directory) opretter en en mappe med det angivne navn.
  • rmdir (remove directory) sletter en mappe.
  • cat udskriver indholdet af en fil til skærmen.
  • echo udskriver argumentet til skærmen
  • date udskriver det aktuelle klokkeslet til skærmen
  • touch opretter en fil med det angivne navn. Hvis filen allerede findes, opdateres ændringstidspunktet til det aktuelle klokkeslet.
  • ^C (ctrl+c) afbryder et kørende program / kommando uden at vente på det afslutter af sig selv. Dette er nyttigt hvis man f.eks. kommer til at at lave en uendelig løkke.

Omdirigering af output

Her er et eksempel på hvordan man kan bruge omdirigering af output fra kommandoer (linjer der starter med ‘$’ er prompten hvor kommandoen skrives).

  $ date > my-file.txt
  $ cat .\my-file.txt
  Fri Sep 24 11:20:58 Rom, sommertid 2021
  $ date >> my-file.txt
  $ date > my-file.txt 
  $ echo "test af append to file" >> .\my-file.txt
  $ date >> my-file.txt
  $ cat .\my-file.txt
  Fri Sep 24 11:21:19 Rom, sommertid 2021
  test af append to file
  Fri Sep 24 11:21:58 Rom, sommertid 2021
  $

Git

Her er nogle få kommandoer til håndtering af git depoter (repositories).

Opsætning af git

  • git config --global user.name "FIRST_NAME LAST_NAME" sætter navnet på brugeren af git globalt for alle depoter på maskinen.
  • git config --global user.email "user@example.com" sætter email adressen på brugeren af git globalt for alle depoter på maskinen.

Initialisering og Kloning

  • git init opretter et depot.
  • git clone [URL] opretter en klon af et depot fra f.eks. github. URL er der hvor depotet befinder sig.

Dagligt arbejde med git

  • git status viser status for det lokale depot.
  • git add . tilføjer alle ændringer i arbejdsområdet rekursivt.
  • git commit -m "COMMIT MESSAGE HERE" committer ind i det lokale depot.
  • git pull henter og fletter ændringer ind fra en remote.
  • git push skubber ændringer ud til en remote.
  • git fetch henter ændringer fra remote uden at flette dem sammen automatisk.
  • git merge [BRANCH] fletter ændringer fra BRANCH ind i den aktuelle gren (branch).

Iterativ udvikling

Code Editors and IDE's

For at kunne arbejde med HTML, CSS, javascript og andre tekst filer har vi brug for en god editor.

Visual Studio Code

Husk at vælge alle tilvalg i additional tasks under installationen af VS Code, som vist på billedet. Det gør det nemmere at åbne hele mapper som projekter, i stedet for enkelte filer.

Husk at alle krydser under installationen.

Her finder du information om Visual Studio Code og en kort video intro.

Jetbrains produkter

Der er mulighed for at bruge en lang række udviklingsmiljøer fra JetBrains i gratis “studenter versioner”.

Git Intro

Git introduktion

Basal Arbejdsgang

Den følgende illustration viser hvordan forskellige kommandoer påvirker komponenterne i git systemet.

Denne figur illustrer hvordan de enkelte filer skifter tilstand når man arbejder med git.

Samarbejde via github

For at kunne samarbejde om den samme kodebase er det nødvendigt med en smule setup for at komme igang.

Hvis vi tager udgangspunkt i et nyt projekt kræves denne opsætning.

  • opret et repository i github
  • lave en klon på den/de computere der skal deltage.
  • Ejeren af “repository” skal tilføje de andre bruger som samarbejdspartnere (Collaborators)
  • Samarbejdspartnere skal acceptere invitationen (typisk via et link i en email)
  • Efter accept af samabejdsinvitation kan de inviterede brugere skubbe kode ind i projektet.

Samarbejde via Git

Her er en visualisering af opsætningen med to samarbejdende udviklere.

Basale kommandoer

Ønsker du at bruge git fra en kommandoprompt er her en liste med basale kommandoer

Github pages

Her finder du en guide til at komme igang med Github pages.

Denne videoguide forklarer hvordan man kommer igang med at bruge github pages til at hoste sin webside.

Konfliktløsning

Når man er flere der samarbejder om samme kode kompleks opstår der uungåeligt det at man på et tidspunkt kommer til at ændre i en fil , der allerede er blevet ændret af en anden person. Derfor er det nødvendigt at vide hvordan man løser disse konflikter i git, så man kan komme videre med arbejdet.

Konfliktløsningsscenariet kan illustreres med et eksempel i et sekvensdiagram.

Konfliktløsning i git er også beskrevet i denne video.

1.9: Resolving Merge Conflicts - Git and GitHub for Poets