1
0
Fork 0

Merge remote-tracking branch 'upstream/main'

main
Oliver Stolle 2026-04-21 19:16:48 +00:00
commit 1b7756f3cd
27 changed files with 1664 additions and 17 deletions

View File

@ -1,4 +1,4 @@
module gitty.informatik.hs-mannheim.de/steger/pr3-sose2026/go/03-modules
module gitty.informatik.th-mannheim.de/steger/pr3-sose2026/go/03-modules
go 1.26.1

View File

@ -5,11 +5,11 @@ import (
// importing the myMath package with an alias mm. This allows us to use mm instead of myMath to access the functions in the package.
// go packages are imported using their full path, which is the path to the package relative to the GOPATH or module root. .
mm "gitty.informatik.hs-mannheim.de/steger/pr3-sose2026/go/03-modules/myMath"
mm "gitty.informatik.th-mannheim.de/steger/pr3-sose2026/go/03-modules/myMath"
// the underscore is used to import a package solely for its side effects (i.e., the init function).
// This is useful when you want to initialize a package but do not need to access any of its exported functions or variables.
// _ "gitty.informatik.hs-mannheim.de/steger/pr3-sose2026/go/03-modules/myMath"
// _ "gitty.informatik.th-mannheim.de/steger/pr3-sose2026/go/03-modules/myMath"
// importing a third-party package from GitHub. This package provides functions for generating UUIDs (Universally Unique Identifiers).
// To use this package, you need to run go get github.com/google/uuid to download and install the package in your Go workspace.

View File

@ -3,7 +3,7 @@ package myMath_test
import (
"testing"
"gitty.informatik.hs-mannheim.de/steger/pr3-sose2026/go/03-modules/myMath"
"gitty.informatik.th-mannheim.de/steger/pr3-sose2026/go/03-modules/myMath"
)
func TestAdd(t *testing.T) {

View File

@ -0,0 +1,3 @@
module gitty.informatik.th-mannheim.de/steger/pr3-sose2026/go/04-design-pattern
go 1.26.1

View File

@ -0,0 +1,102 @@
package main
import (
"fmt"
"gitty.informatik.th-mannheim.de/steger/pr3-sose2026/go/04-design-pattern/patterns"
)
// Counter is a concrete implementation of the Subject interface, which maintains a count and notifies observers whenever the count changes
type Counter struct {
patterns.Subject // Embedding the Subject interface allows Counter to inherit its methods and be treated as a Subject, enabling it to register observers and notify them of changes
Count int
}
// NewCounter creates and returns a new instance of the Counter struct, initializing the embedded Subject using the NewSubject function from the patterns package
func NewCounter() Counter {
return Counter{patterns.NewSubject(), 0}
}
// Inc increments the counter's count and notifies all registered observers of the change
func (c *Counter) Inc() {
c.Count++
c.Notify()
}
// Dec decrements the counter's count and notifies all registered observers of the change
func (c *Counter) Dec() {
c.Count--
c.Notify()
}
// CountPrinter is a concrete implementation of the Observer interface, which prints the current count whenever it receives an update notification from the Counter
type CountPrinter struct {
counter *Counter
}
func (cp *CountPrinter) Update() {
fmt.Println("count updated to ", cp.counter.Count)
}
var _ patterns.Observer = (*CountPrinter)(nil) // Compile-time assertion to ensure CountPrinter implements the Observer interface
// CounterHistory is a concrete implementation of the Observer interface, which maintains a history of the counter's count values whenever it receives an update notification from the Counter
type CounterHistory struct {
counter *Counter
history []int
}
// Update appends the current count value to the history slice whenever it receives an update notification from the Counter
func (ch *CounterHistory) Update() {
ch.history = append(ch.history, ch.counter.Count)
}
var _ patterns.Observer = (*CounterHistory)(nil) // Compile-time assertion to ensure CounterHistory implements the Observer interface
// The main function demonstrates the usage of the Counter, CountPrinter, and CounterHistory in an interactive command-line application.
// It allows the user to increment or decrement the counter and see the updates printed, while also maintaining a history of count values.
func main() {
// Create a new Counter instance, which will serve as the Subject in the Observer pattern
counter := NewCounter()
// Create a new CounterHistory instance and register it as an observer of the counter to track the history of count values
history := CounterHistory{&counter, []int{}}
counter.Register(&history)
// Create a new CountPrinter instance and register it as an observer of the counter to print the current count whenever it changes
printer := CountPrinter{&counter}
counter.Register(&printer)
printerRegistered := true
var input string
for {
fmt.Print("Enter a command (+: increment, -: decrement, q: quit): ")
fmt.Scanln(&input)
if len(input) != 1 {
fmt.Println("Please enter a single character.")
continue
}
switch input[0] {
case '+':
counter.Inc()
case '-':
counter.Dec()
case 'p':
// Toggle the registration of the CountPrinter observer. If it's currently registered, unregister it; if it's currently unregistered, register it.
if printerRegistered {
counter.Unregister(&printer)
} else {
counter.Register(&printer)
}
printerRegistered = !printerRegistered
case 'q':
fmt.Println("Exiting...")
fmt.Println(history.history)
return
default:
fmt.Println("Unknown command. Use '+', '-', or 'q'.")
}
}
}

View File

@ -0,0 +1,45 @@
package patterns
// Observer pattern implementation in Go
type Observer interface {
Update()
}
// Subject interface defines methods for registering, unregistering, and notifying observers
type Subject interface {
Register(o Observer)
Unregister(o Observer)
Notify()
}
// Concrete implementation of the Subject interface. Lowercase 'subject' to make it unexported, as it will be created through the NewSubject function.
type subject struct {
observers map[Observer]bool // Using a map to store observers for efficient add/remove operations
}
// NewSubject creates and returns a new instance of the subject
func NewSubject() Subject {
// Needs to return a pointer to the subject struct to ensure that the same instance is modified when registering/unregistering observers
return &subject{
observers: make(map[Observer]bool),
}
}
// Register adds an observer to the subject's set of observers
// Pointer receiver is used to modify the subject's state (the observers map) when registering an observer
func (s *subject) Register(o Observer) {
s.observers[o] = true
}
// Unregister removes an observer from the subject's set of observers
// Pointer receiver is used to modify the subject's state (the observers map) when unregistering an observer
func (s *subject) Unregister(o Observer) {
delete(s.observers, o)
}
// Notify calls the Update method on all registered observers to notify them of a change
func (s *subject) Notify() {
for o := range s.observers {
o.Update()
}
}

View File

@ -0,0 +1,69 @@
package patterns_test
import (
"testing"
"gitty.informatik.th-mannheim.de/steger/pr3-sose2026/go/04-design-pattern/patterns"
)
// MockObserver is a test implementation of the Observer interface, used to verify that the Notify method correctly calls the Update method on registered observers
type MockObserver struct {
callCount int
}
// Update increments the callCount to track how many times the Update method has been called on this observer
func (mo *MockObserver) Update() {
mo.callCount++
}
// Table driven tests for the Subject's Notify method, covering various scenarios of registered and unregistered observers
func TestSubject_Notify(t *testing.T) {
type observer struct {
MockObserver
toRegister bool
expectedCallcount int
}
tests := []struct {
name string
observers []*observer
}{
{"No observers", []*observer{}},
{"Single unregistered observer", []*observer{&observer{MockObserver{0}, false, 0}}},
{"Single registered observer", []*observer{&observer{MockObserver{0}, true, 1}}},
{"Two registered observers", []*observer{&observer{MockObserver{0}, true, 1}, &observer{MockObserver{0}, true, 1}}},
{"One registered and one unregistered observers", []*observer{&observer{MockObserver{0}, true, 1}, &observer{MockObserver{0}, false, 0}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := patterns.NewSubject()
for _, o := range tt.observers {
if o.toRegister {
s.Register(o)
}
}
s.Notify()
for _, o := range tt.observers {
if o.callCount != o.expectedCallcount {
t.Errorf("%s: expected a callcount of %v, actual: %v", tt.name, o.expectedCallcount, o.callCount)
}
//reset callcount & unregister
o.callCount = 0
s.Unregister(o)
}
//call notify when all observers are unregistered
s.Notify()
//expect a callcount of 0
for _, o := range tt.observers {
if o.callCount != 0 {
t.Errorf("%s: expected a callcount of 0, actual: %v", tt.name, o.callCount)
}
}
})
}
}

View File

@ -1,17 +1,65 @@
package main
func print(ci <-chan int) {
//TODO: implement
import (
"fmt"
"sync"
"time"
)
/**
* This function reads integers from a receive-only channel and prints them to the console.
* It simulates some work by sleeping for 100 milliseconds after printing each integer.
* The function uses a done channel to signal when it has finished processing all integers from the input channel.
* When the input channel is closed and all integers have been printed, the function sends a value to the done channel to indicate that it is done.
*
* @param ci A receive-only channel of integers that the function will read from and print. The function will continue to read from the channel until it is closed.
* @param done A send-only channel of booleans that the function will use to signal when it has finished processing the input channel. The function will send a value to this channel when it is done.
*/
func printChannel(ci <-chan int, done chan<- bool) {
defer func() { done <- true }()
for i := range ci {
time.Sleep(100 * time.Millisecond)
fmt.Println(i)
}
}
/**
* This function is similar to printChannel but uses a WaitGroup instead of a done channel to signal completion.
* The WaitGroup allows us to wait for the goroutine to finish without needing a separate channel for signaling.
*
* @param ci A receive-only channel of integers that the function will read from and print. The function will continue to read from the channel until it is closed.
* @param wg A pointer to a sync.WaitGroup that will be used to signal when the goroutine has finished processing the channel.
* The WaitGroup should have its counter incremented before calling this function, and this function will call Done() on the WaitGroup when it finishes.
*/
func printWaitgroup(ci <-chan int, wg *sync.WaitGroup) {
defer func() { wg.Done() }()
for i := range ci {
time.Sleep(100 * time.Millisecond)
fmt.Println(i)
}
}
func main() {
c := make(chan int)
c := make(chan int) //no capacity, unbuffered channel. This means that sends to the channel will block until another goroutine is ready to receive from the channel, and receives from the channel will block until another goroutine sends a value to the channel.
// This is useful for synchronizing between goroutines, as it ensures that the sender and receiver are coordinated.
// Use a channel named done to signal when the printChannel goroutine has finished processing the input channel.
// This allows us to wait for the goroutine to complete before exiting the main function.
//done := make(chan bool)
//go printChannel(c, done)
// Use a WaitGroup to wait for the printWaitgroup goroutine to finish instead of using a done channel.
wg := sync.WaitGroup{}
wg.Add(1)
go printWaitgroup(c, &wg)
c <- 1
c <- 2
c <- 3
close(c)
close(c) // Close the channel to signal to the printChannel goroutine that there are no more values to read.
// This will cause the for loop in printChannel to exit and the function to send a value to the done channel, allowing the main function to wait for it to finish.
print(c)
//<-done //alternative to using a done channel, we can wait for the WaitGroup to finish instead.
wg.Wait() // Wait for the printWaitgroup goroutine to finish before exiting the main function.
}

View File

@ -4,38 +4,206 @@ import (
"fmt"
"math/rand"
"sort"
"sync"
"time"
)
func mergeSortSequential(data []float64) {
if len(data) <= 1 {
/*
*
* Sorts a slice of float64 values using the merge sort algorithm in parallel with channels.
*
* @param data The slice of float64 values to be sorted.
* @param sorted The output channel where the sorted values will be sent. This channel should be buffered to avoid blocking.
* The function will close this channel when sorting is complete.
*/
func mergeSortChannelsInternal(data []float64, sorted chan<- float64) {
// close the sorted channel when we're done sending sorted values. This ensures that the receiver can detect when all sorted values have been sent and can stop waiting for more data.
defer close(sorted)
if len(data) <= 10000 {
// For small slices, sort them sequentially to avoid the overhead of creating goroutines and channels. This threshold is chosen to balance the overhead of concurrency with the benefits of parallelism.
sortedData := make([]float64, len(data))
copy(sortedData, data)
mergeSortSequential(sortedData)
// Send sorted values to the output channel. We iterate over the sorted slice and send each value to the sorted channel.
for _, x := range sortedData {
sorted <- x
}
} else {
// For larger slices, split them into two halves and sort each half in parallel using channels.
mid := len(data) / 2
left := data[:mid]
right := data[mid:]
sortedLeft := make(chan float64, 1000)
sortedRight := make(chan float64, 1000)
go mergeSortChannelsInternal(left, sortedLeft)
go mergeSortChannelsInternal(right, sortedRight)
// Merge the sorted halves and send the merged sorted values to the output channel.
mergeChannels(sortedLeft, sortedRight, sorted)
}
}
/**
* Merges two sorted channels into a single sorted channel.
*
* @param a The first sorted channel.
* @param b The second sorted channel.
* @param out The output channel where the merged sorted values will be sent. This channel should be buffered to avoid blocking.
*/
func mergeChannels(a, b <-chan float64, out chan<- float64) {
var okA, okB bool
var valA, valB float64
for {
// If we don't have a value from channel a, try to read from it.
if !okA {
valA, okA = <-a
}
// If we don't have a value from channel b, try to read from it.
if !okB {
valB, okB = <-b
}
// If both channels are closed and we've read all values, break the loop.
if !okA && !okB {
break
}
// If one channel is closed, we can only read from the other channel.
if !okA {
out <- valB
okB = false
continue
}
if !okB {
out <- valA
okA = false
continue
}
// Both channels have values, compare them and send the smaller one to the output channel.
if valA < valB {
out <- valA
okA = false
} else {
out <- valB
okB = false
}
}
}
/**
* Sorts a slice of float64 values using the merge sort algorithm in parallel with channels.
*
* @param data The slice of float64 values to be sorted. The sorting is done in-place, so the original slice will be modified.
*/
func mergeSortChannels(data []float64) {
sorted := make(chan float64, 1000)
go mergeSortChannelsInternal(data, sorted)
sortedData := make([]float64, len(data))
i := 0
for x := range sorted {
sortedData[i] = x
i++
}
copy(data, sortedData)
}
/**
* Sorts a slice of float64 values using the merge sort algorithm in parallel.
*
* @param data The slice of float64 values to be sorted. The sorting is done in-place, so the original slice will be modified.
*/
func mergeSortParallel(data []float64) {
// Base case: if the slice has 10000 or fewer elements, sort it sequentially. This threshold is chosen to balance
// the overhead of creating goroutines with the benefits of parallelism. For small slices, the overhead of goroutines
// may outweigh the performance gains, so we sort them sequentially.
if len(data) <= 10000 {
mergeSortSequential(data)
return
}
// Split the slice into two halves. The mid index is calculated as the length of the data divided by 2, which gives us the midpoint of the slice.
mid := len(data) / 2
left := data[:mid]
right := data[mid:]
mergeSortSequential(left)
mergeSortSequential(right)
// Use a WaitGroup to wait for both halves to be sorted before merging. The WaitGroup allows us to synchronize the completion
// of the two goroutines that will sort the left and right halves in parallel.
var wg sync.WaitGroup
wg.Add(2)
// Sort left and right halves in parallel using goroutines. Each goroutine will call mergeSortParallel
// on its respective half and then signal when it's done using the WaitGroup.
go func() { mergeSortParallel(left); wg.Done() }()
go func() { mergeSortParallel(right); wg.Done() }()
wg.Wait()
copy(data, merge(left, right))
}
/**
* Sorts a slice of float64 values using the merge sort algorithm sequentially.
*
* @param data The slice of float64 values to be sorted. The sorting is done in-place, so the original slice will be modified.
*/
func mergeSortSequential(data []float64) {
// Base case: if the slice has 0 or 1 element, it's already sorted.
if len(data) <= 1 {
return
}
// Recursive case: split the slice into two halves, ...
mid := len(data) / 2
left := data[:mid]
right := data[mid:]
// ... sort each half recursively, ...
mergeSortSequential(left)
mergeSortSequential(right)
// ... and then merge the sorted halves back together.
copy(data, merge(left, right))
}
/**
* Merges two sorted slices into a single sorted slice.
*
* @param left The first sorted slice.
* @param right The second sorted slice.
* @return A new slice containing all elements from both input slices, sorted in ascending order.
*/
func merge(left, right []float64) []float64 {
// Create a new slice to hold the merged result. The capacity is set to the sum of the lengths of left and right for efficiency.
result := make([]float64, 0, len(left)+len(right))
i, j := 0, 0
// Merge elements from left and right until one of the slices is exhausted.
for i < len(left) && j < len(right) {
if left[i] < right[j] {
// Append the smaller element from left to the result and move the pointer i.
result = append(result, left[i])
i++
} else {
// Append the smaller element from right to the result and move the pointer j.
result = append(result, right[j])
j++
}
}
// Append any remaining elements from left or right. The ... operator is used to expand the slice into individual elements.
result = append(result, left[i:]...)
result = append(result, right[j:]...)
@ -45,7 +213,7 @@ func merge(left, right []float64) []float64 {
func main() {
//1. DATA CREATION
data := make([]float64, 1000*1000*20)
data := make([]float64, 1000*1000*20) //*1000*20)
for i := range data {
data[i] = rand.Float64() * 100 // Random floats between 0 and 100
@ -58,7 +226,9 @@ func main() {
//2. SORTING
start := time.Now()
mergeSortSequential(data)
//mergeSortSequential(data)
//mergeSortParallel(data)
mergeSortChannels(data)
elapsed := time.Since(start)
fmt.Printf("MergeSort took %s\n", elapsed)

View File

@ -1,6 +1,7 @@
package main
import (
"fmt"
"math/rand"
"time"
)
@ -8,12 +9,54 @@ import (
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
// Simulate some work by sleeping for a random duration between 1 and 5 seconds before sending a message to ch1.
time.Sleep(time.Duration(rand.Intn(5)+1) * time.Second)
ch1 <- "Message from channel 1"
}()
msg1 := <-ch1
println("Received:", msg1)
go func() {
// Simulate some work by sleeping for a random duration between 1 and 5 seconds before sending a message to ch2.
time.Sleep(time.Duration(rand.Intn(5)+1) * time.Second)
ch2 <- "Message from channel 2"
}()
// Create multiple dynamic channels and send messages to them in separate goroutines.
// This demonstrates how we can use select to wait for messages from a variable number of channels.
count := 10
var channels []chan string
for i := range count {
ch := make(chan string)
channels = append(channels, ch)
go func() {
time.Sleep(time.Duration(rand.Intn(5)+1) * time.Second)
ch <- fmt.Sprintf("Message from dynamic channel %d", i)
}()
}
// Fan-in pattern: Create a single channel to receive messages from multiple channels.
// This allows us to use select to wait for messages from all channels in a single case.
fanIn := make(chan string)
for _, ch := range channels {
go func(c chan string) {
fanIn <- <-c // Read a message from the dynamic channel and send it to the fanIn channel. A temporary variable is needed to capture the current value of ch.
}(ch)
}
// Use select to wait for messages from multiple channels. The select statement allows us to wait on multiple channel operations
// and proceed with the one that is ready first.
select {
case msg1 := <-ch1:
println("Received:", msg1)
case msg2 := <-ch2:
println("Received:", msg2)
case msg := <-fanIn:
println("Received:", msg)
case <-time.After(3 * time.Second): // The time.After function returns a channel that will send the current time after the specified duration. This case will be selected if no other channels are ready within 3 seconds, allowing us to handle a timeout scenario.
println("Timeout occurred")
//default: // The default case is executed if none of the channels are ready. Not to be used in this example, as it would cause the select statement to proceed immediately.
// println("Nothing")
}
}

View File

@ -0,0 +1,63 @@
# Assignment - Airport Simulation
This go-program is a simulation of the departure process of an airport that consists of *aircrafts*, *passengers*, *checkin-counter*, *security-checks*, *gates*, a *baggage handling system*, a *runway*, a *flight schedule* and *gates*. Passengers go through the following steps:
- **Checkin**: The ticket is verified, baggage is checked in and handed over to the baggage handling system, and a boarding pass is issued.
- **Security-Check**: The boarding pass is verified and the passenger is checked for prohibited items.
- **Gate**: The passenger walks to the correct gate and waits there until boarding time. The gate takes then care of boarding the aircraft, while simultaneously loading baggage into the aircraft.
Each step has a processing time and there are several waiting times in between.
Aircrafts arrive at the gate on boarding time and leave to the runway on departure time according to the flight schedule.
## Task
Your task is to complete the implementation by going through the **TODO** statements in the files `baggageHandlingSystem.go`, `securityCheck.go`, and `gate.go`. All other files already contain an implementation.
**Hint**: The expected behavior is defined in detail in the corresponding unit tests.
**Hint**: Be aware of synchronization issues between the different goroutines for all actors. Use `channels` whenever applicable. See `checkin.go` for inspiration.
## Example Output
```
Flight Number Destination Gate Boarding Time Departure Time
--------------------------------------------------------------------------------
AF2062 ZAG 7 13:59:27 13:59:57
BA8851 SOF 9 13:59:47 14:00:17
SK4243 FRA 5 14:01:46 14:02:16
KL5728 WAW 9 14:03:06 14:03:36
SK8183 PRG 8 14:03:12 14:03:42
AF8488 LIS 5 13:59:45 14:00:15
KL5937 RIX 1 14:01:26 14:01:56
AY1065 BRU 7 14:00:37 14:01:07
BA1529 ZRH 5 14:02:03 14:02:33
IB2405 IST 3 14:01:51 14:02:21
What/Who arrives at airport? (p for passenger, c for car, t for train, q to quit): p
stoic_dijkstra has arrived at the airport for the flight number IB2405 to IST in 3.11 minutes with 2 bags and carry-on items: [Medicine Knife Sharpener]
stoic_dijkstra completed the checkin procedure and is now heading to security
stoic_dijkstra has successfully passed the security check and is now walking to the gate
elated_hellman has arrived at the airport for the flight number KL5728 to WAW in 4.19 minutes with 2 bags and carry-on items: [Explosives]
vibrant_maxwell has arrived at the airport for the flight number BA8851 to SOF in 0.88 minutes with 2 bags and carry-on items: [Keys Matches]
dazzling_lewin has arrived at the airport for the flight number AF2062 to ZAG in 0.54 minutes with 1 bags and carry-on items: []
nifty_hermann has arrived at the airport for the flight number IB2405 to IST in 2.94 minutes with 1 bags and carry-on items: [Resistance Bands Gloves]
elated_hellman completed the checkin procedure and is now heading to security
vibrant_maxwell completed the checkin procedure and is now heading to security
elated_hellman: prohibited item detected: [Explosives]
flight AF2062 is ready for boarding
dazzling_lewin completed the checkin procedure and is now heading to security
vibrant_maxwell has successfully passed the security check and is now walking to the gate
nifty_hermann completed the checkin procedure and is now heading to security
dazzling_lewin has successfully passed the security check and is now walking to the gate
nifty_hermann has successfully passed the security check and is now walking to the gate
dazzling_lewin has successfully boarded flight AF2062 to ZAG
flight AF8488 is ready for boarding
flight BA8851 is ready for boarding
loaded baggage {[Goggles Fire Extinguisher Controller]} onto flight BA8851
loaded baggage {[Book Clipboard]} onto flight BA8851
vibrant_maxwell has successfully boarded flight BA8851 to SOF
boarding completed for flight number AF2062
Flight AF2062 is waiting for takeoff
Flight AF2062 to ZAG is taking off
Flight AF2062 to ZAG has taken off with 1 passengers and 0 bags on board
Closing airport
The following baggage was lost today: []
```

View File

@ -0,0 +1,39 @@
package airport
import (
"fmt"
)
type Aircraft struct {
Flight Flight
Passengers []Passenger
Baggage []Baggage
}
var _ BaggageProcessor = &Aircraft{}
func NewAircraft(flight Flight) Aircraft {
return Aircraft{flight, []Passenger{}, []Baggage{}}
}
func (ac *Aircraft) ProcessBaggage(fn FlightNumber, b Baggage) error {
if fn != ac.Flight.Id {
return fmt.Errorf("wrong flight %s", fn)
}
ac.Baggage = append(ac.Baggage, b)
fmt.Printf("loaded baggage %v onto flight %v\n", b, fn)
return nil
}
func (ac *Aircraft) Process(p Passenger) error {
if p.BoardingPass.FlightNumber != ac.Flight.Id {
return fmt.Errorf("%s is unauthorized to enter aircraft", p.Name)
}
ac.Passengers = append(ac.Passengers, p)
return nil
}
func (ac *Aircraft) Start(gate *Gate, rwy *Runway) {
gate.ProcessAircraft(ac)
rwy.ProcessAircraft(*ac)
}

View File

@ -0,0 +1,62 @@
package airport
import (
"time"
)
type Airport struct {
Checkin Checkin
Security SecurityCheck
Gates map[GateNumber]*Gate
Runway Runway
Bhs *BaggageHandlingSystem
FlightSchedule FlightSchedule
}
func DefaultAirport() Airport {
ap := Airport{
Security: NewSecurityCheck(time.Duration(1 * time.Second)),
Gates: NewGates(10),
Runway: NewRunway(time.Duration(3 * time.Second)),
}
fs := GenerateRandomFlightSchedule(ap.Gates, 10)
ap.FlightSchedule = fs
baggageSinks := make(map[FlightNumber](BaggageProcessor))
for _, g := range ap.Gates {
var flightsForGate []Flight
for _, f := range fs.AllFlights() {
if f.GateNumber == g.id {
flightsForGate = append(flightsForGate, f)
}
baggageSinks[f.Id] = g
}
g.SetFlights(flightsForGate)
ap.Gates[g.id] = g
}
bhs := NewBaggageHandlingSystem(time.Duration(1*time.Second), baggageSinks)
ap.Bhs = &bhs
ap.Checkin = NewCheckin(time.Duration(1*time.Second), &fs, &bhs)
return ap
}
func (a *Airport) Start() {
go a.Checkin.Start()
go a.Security.Start()
go a.Bhs.Start()
go a.Runway.Start()
for _, g := range a.Gates {
go g.Start()
}
}
func (a *Airport) NewPassenger() Passenger {
return NewRandomPassenger(a.FlightSchedule.AllFlights())
}

View File

@ -0,0 +1,65 @@
package airport
import (
"fmt"
"time"
)
type BaggageProcessor interface {
ProcessBaggage(FlightNumber, Baggage) error
}
type BaggageHandlingSystem struct {
processingTime time.Duration
sinks map[FlightNumber]BaggageProcessor
lostBaggage chan LostBaggage
}
type LostBaggage struct {
Baggage
FlightNumber FlightNumber
Err error
}
var _ BaggageProcessor = &BaggageHandlingSystem{}
func NewBaggageHandlingSystem(processingTime time.Duration, sinks map[FlightNumber]BaggageProcessor) BaggageHandlingSystem {
return BaggageHandlingSystem{
processingTime: processingTime,
sinks: sinks,
lostBaggage: make(chan LostBaggage),
}
}
func (bhs *BaggageHandlingSystem) ProcessBaggage(fn FlightNumber, b Baggage) error {
sink, ok := bhs.sinks[fn]
if !ok {
return fmt.Errorf("invalid flight %v", fn)
}
go func() {
time.Sleep(bhs.processingTime)
if err := sink.ProcessBaggage(fn, b); err != nil {
bhs.lostBaggage <- LostBaggage{b, fn, err}
}
}()
return nil
}
func (bhs *BaggageHandlingSystem) CollectLostBaggage() []LostBaggage {
lostBaggage := []LostBaggage{}
for {
select {
case lb := <-bhs.lostBaggage:
lostBaggage = append(lostBaggage, lb)
default:
return lostBaggage
}
}
}
func (bhs *BaggageHandlingSystem) Start() {
}

View File

@ -0,0 +1,133 @@
package airport_test
import (
"fmt"
"reflect"
"testing"
"time"
"gitty.informatik.hs-mannheim.de/steger/pr3-sose2026/go/06-airport/airport"
)
type BaggageSpyData struct {
fn airport.FlightNumber
b airport.Baggage
t time.Time
}
type BaggageProcessorSpy struct {
Baggage []BaggageSpyData
Err error
}
func (bps *BaggageProcessorSpy) ProcessBaggage(fn airport.FlightNumber, b airport.Baggage) error {
bps.Baggage = append(bps.Baggage, BaggageSpyData{fn, b, time.Now()})
return bps.Err
}
var _ airport.BaggageProcessor = &BaggageProcessorSpy{}
func compareSink(t *testing.T, expected []airport.Baggage, expectedFlightNumber airport.FlightNumber, got BaggageProcessorSpy, start time.Time) {
for _, b := range got.Baggage {
if elapsed := b.t.Sub(start); elapsed < 1*time.Millisecond {
t.Errorf("processing time too short: got %v, want at least 1ms", elapsed)
}
if b.fn != expectedFlightNumber {
t.Errorf("expected flight number: %v, got: %v", expectedFlightNumber, b.fn)
}
}
if len(got.Baggage) != len(expected) {
t.Errorf("expected number of baggage: %d, got: %d", len(expected), len(got.Baggage))
}
deliveredBaggage := []airport.Baggage{}
for _, b := range got.Baggage {
deliveredBaggage = append(deliveredBaggage, b.b)
}
if !reflect.DeepEqual(deliveredBaggage, expected) {
t.Errorf("expected delivered baggage for %v: %v, got: %v", expectedFlightNumber, expected, deliveredBaggage)
}
}
func TestBaggageHandlingSystem(t *testing.T) {
sink123 := BaggageProcessorSpy{nil, nil}
sink456 := BaggageProcessorSpy{nil, fmt.Errorf("aircraft already gone")}
sinks := map[airport.FlightNumber]airport.BaggageProcessor{
airport.FlightNumber("LH123"): &sink123,
airport.FlightNumber("LH456"): &sink456,
}
sut := airport.NewBaggageHandlingSystem(1*time.Millisecond, sinks)
go sut.Start()
testCases := []struct {
name string
baggage airport.Baggage
fn airport.FlightNumber
expectErr bool
expectDeliveredBaggage123 []airport.Baggage
expectDeliveredBaggage456 []airport.Baggage
expectLostBaggage []airport.LostBaggage
}{
{
name: "Valid baggage",
baggage: airport.Baggage{[]string{"Socks", "Shirts"}},
fn: "LH123",
expectErr: false,
expectDeliveredBaggage123: []airport.Baggage{{[]string{"Socks", "Shirts"}}},
expectDeliveredBaggage456: []airport.Baggage{},
expectLostBaggage: []airport.LostBaggage{},
},
{
name: "Aircraft already gone",
baggage: airport.Baggage{[]string{"Socks", "Shirts"}},
fn: "LH456",
expectErr: false,
expectDeliveredBaggage123: []airport.Baggage{},
expectDeliveredBaggage456: []airport.Baggage{{[]string{"Socks", "Shirts"}}},
expectLostBaggage: []airport.LostBaggage{
{
airport.Baggage{[]string{"Socks", "Shirts"}},
"LH456",
fmt.Errorf("aircraft already gone"),
},
},
},
{
name: "Unknown flight",
baggage: airport.Baggage{[]string{"Socks", "Shirts"}},
fn: "XX999",
expectErr: true,
expectDeliveredBaggage123: []airport.Baggage{},
expectDeliveredBaggage456: []airport.Baggage{},
expectLostBaggage: []airport.LostBaggage{},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
sink123.Baggage = []BaggageSpyData{}
sink456.Baggage = []BaggageSpyData{}
start := time.Now()
err := sut.ProcessBaggage(tc.fn, tc.baggage)
time.Sleep(10 * time.Millisecond) //not really threadsafe because the 10 milliseconds have never been guaranteed. TODO: replace by sync mechanism
if (err != nil) != tc.expectErr {
t.Errorf("expected error: %v, got: %v", tc.expectErr, err != nil)
}
compareSink(t, tc.expectDeliveredBaggage123, "LH123", sink123, start)
compareSink(t, tc.expectDeliveredBaggage456, "LH456", sink456, start)
lostBaggage := sut.CollectLostBaggage()
if !reflect.DeepEqual(lostBaggage, tc.expectLostBaggage) {
t.Errorf("expected lost baggage: %v, got: %v", tc.expectLostBaggage, lostBaggage)
}
})
}
}

View File

@ -0,0 +1,72 @@
package airport
import (
"errors"
"fmt"
"time"
)
type Checkin struct {
processingTime time.Duration
queue chan passengerCheckinData
flightProvider FlightProvider
baggageProcessor BaggageProcessor
}
type passengerCheckinData struct {
p *Passenger
err chan error
}
func NewCheckin(processingTime time.Duration, fp FlightProvider, bp BaggageProcessor) Checkin {
return Checkin{
processingTime: processingTime,
queue: make(chan passengerCheckinData),
flightProvider: fp,
baggageProcessor: bp,
}
}
func (c Checkin) Process(p *Passenger) error {
err := make(chan error)
c.queue <- passengerCheckinData{p: p, err: err}
return <-err
}
func (c Checkin) Start() {
defer fmt.Println("checkin terminated")
for element := range c.queue {
time.Sleep(c.processingTime)
//1. check ticket
if element.p.Name != element.p.Ticket.Name {
element.err <- fmt.Errorf("name %s on ticket does not match", element.p.Ticket.Name)
continue
}
flight, err := c.flightProvider.GetFlight(element.p.Ticket.FlightNumber)
if err != nil {
element.err <- err
continue
}
//2. issue boarding pass
element.p.BoardingPass = &BoardingPass{element.p.Ticket, flight.GateNumber}
//3. check in bags
var baggageErr error
for _, bag := range element.p.Bags {
if err := c.baggageProcessor.ProcessBaggage(flight.Id, bag); err != nil {
baggageErr = errors.Join(baggageErr, err)
}
}
if baggageErr != nil {
element.err <- baggageErr
continue
}
element.p.Bags = []Baggage{}
element.err <- nil
}
}

View File

@ -0,0 +1,116 @@
package airport_test
import (
"fmt"
"reflect"
"testing"
"time"
"gitty.informatik.hs-mannheim.de/steger/pr3-sose2026/go/06-airport/airport"
)
type FlightProviderStub struct {
}
func (fps FlightProviderStub) GetFlight(fn airport.FlightNumber) (airport.Flight, error) {
if fn == "LH123" {
return airport.Flight{"LH123", "FRA", airport.GateNumber(1), time.Now(), time.Now()}, nil
}
return airport.Flight{}, fmt.Errorf("unknown flight %v", fn)
}
var _ airport.FlightProvider = FlightProviderStub{}
func TestCheckin(t *testing.T) {
fp := FlightProviderStub{}
bps := BaggageProcessorSpy{}
sut := airport.NewCheckin(1*time.Millisecond, &fp, &bps)
go sut.Start()
testCases := []struct {
name string
passenger airport.Passenger
expectErr bool
expectBaggage []airport.Baggage
expectBoardingpass *airport.BoardingPass
expectBPSBaggage []airport.Baggage
}{
{
name: "Valid passenger with valid flight",
passenger: airport.Passenger{
"Max Mustermann",
airport.Ticket{"Max Mustermann", "FRA", "LH123"},
nil,
[]airport.Baggage{{[]string{"Socks", "Shirts"}}},
[]string{},
},
expectErr: false,
expectBaggage: []airport.Baggage{},
expectBoardingpass: &airport.BoardingPass{airport.Ticket{"Max Mustermann", "FRA", "LH123"}, airport.GateNumber(1)},
expectBPSBaggage: []airport.Baggage{{[]string{"Socks", "Shirts"}}},
},
{
name: "Passenger with unknown flight",
passenger: airport.Passenger{
"John Doe",
airport.Ticket{"John Doe", "MUC", "LH999"},
nil,
[]airport.Baggage{{[]string{"Socks", "Shirts"}}},
[]string{},
},
expectErr: true,
expectBaggage: []airport.Baggage{{[]string{"Socks", "Shirts"}}},
expectBoardingpass: nil,
expectBPSBaggage: []airport.Baggage{},
},
{
name: "Passenger with wrong ticket",
passenger: airport.Passenger{
"Max Mustermann",
airport.Ticket{"Someone else", "FRA", "LH123"},
nil,
[]airport.Baggage{{[]string{"Socks", "Shirts"}}},
[]string{},
},
expectErr: true,
expectBaggage: []airport.Baggage{{[]string{"Socks", "Shirts"}}},
expectBoardingpass: nil,
expectBPSBaggage: []airport.Baggage{},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
bps.Baggage = []BaggageSpyData{}
start := time.Now()
err := sut.Process(&tc.passenger)
if elapsed := time.Since(start); elapsed < 1*time.Millisecond {
t.Errorf("processing time too short: got %v, want at least 1ms", elapsed)
}
if (err != nil) != tc.expectErr {
t.Errorf("expected error: %v, got: %v", tc.expectErr, err != nil)
}
if !reflect.DeepEqual(tc.passenger.BoardingPass, tc.expectBoardingpass) {
t.Errorf("expected boarding pass: %v, got: %v", tc.expectBoardingpass, tc.passenger.BoardingPass)
}
if !reflect.DeepEqual(tc.passenger.Bags, tc.expectBaggage) {
t.Errorf("expected baggage: %v, got: %v", tc.expectBaggage, tc.passenger.Bags)
}
gotBPSBaggage := []airport.Baggage{}
for _, b := range bps.Baggage {
gotBPSBaggage = append(gotBPSBaggage, b.b)
}
if !reflect.DeepEqual(gotBPSBaggage, tc.expectBPSBaggage) {
t.Errorf("expected baggage processor baggage: %v, got: %v", tc.expectBPSBaggage, gotBPSBaggage)
}
})
}
}

View File

@ -0,0 +1,104 @@
package airport
import (
"fmt"
"math/rand"
"strings"
"time"
)
type FlightNumber string
type Flight struct {
Id FlightNumber
Destination string
GateNumber GateNumber
BoardingTime time.Time
DepartureTime time.Time
}
type FlightProvider interface {
GetFlight(FlightNumber) (Flight, error)
}
type FlightSchedule struct {
flights map[FlightNumber]Flight
}
var possibleDestinations = []string{
"AMS", "ATH", "BCN", "BRU", "BUD", "CPH", "DUB", "DUS", "EDI", "FCO",
"FRA", "GVA", "HEL", "IST", "LIS", "LYS", "MAD", "MUC", "OSL", "PRG",
"RIX", "SOF", "STO", "TLL", "VIE", "WAW", "ZAG", "ZRH", "LUX", "MLA",
}
func GenerateRandomFlightNumbers(count int) []FlightNumber {
airlines := []string{"LH", "AF", "KL", "BA", "SK", "AZ", "IB", "TK", "AY", "OS"}
flightNumbers := make([]FlightNumber, count)
for i := 0; i < count; i++ {
airline := airlines[rand.Intn(len(airlines))]
number := rand.Intn(9000) + 1000 // Random 4-digit number
flightNumbers[i] = FlightNumber(fmt.Sprintf("%s%04d", airline, number))
}
return flightNumbers
}
func GenerateRandomFlightSchedule(gates map[GateNumber]*Gate, count int) FlightSchedule {
flightNumbers := GenerateRandomFlightNumbers(count)
flights := make(map[FlightNumber]Flight)
gateNumbers := make([]GateNumber, 0, len(gates))
for gn, _ := range gates {
gateNumbers = append(gateNumbers, gn)
}
for _, flightNumber := range flightNumbers {
destination := possibleDestinations[rand.Intn(len(possibleDestinations))]
gateNumber := gateNumbers[rand.Intn(len(gateNumbers))]
departureTime := time.Now().Add(time.Duration(rand.Intn(5*60)) * time.Second)
boardingTime := departureTime.Add(-30 * time.Second)
flights[flightNumber] = Flight{
Id: flightNumber,
Destination: destination,
GateNumber: gateNumber,
BoardingTime: boardingTime,
DepartureTime: departureTime,
}
}
return FlightSchedule{flights: flights}
}
func (fs *FlightSchedule) AllFlights() []Flight {
flights := make([]Flight, 0, len(fs.flights))
for _, flight := range fs.flights {
flights = append(flights, flight)
}
return flights
}
func (fs *FlightSchedule) GetFlight(flightNumber FlightNumber) (Flight, error) {
if flight, ok := fs.flights[flightNumber]; ok {
return flight, nil
} else {
return Flight{}, fmt.Errorf("invalid flight number %v", flightNumber)
}
}
func (fs *FlightSchedule) Print() {
fmt.Printf("%-15s %-15s %-10s %-20s %-20s\n", "Flight Number", "Destination", "Gate", "Boarding Time", "Departure Time")
fmt.Println(strings.Repeat("-", 80))
for _, flight := range fs.AllFlights() {
fmt.Printf("%-15s %-15s %-10d %-20s %-20s\n",
flight.Id,
flight.Destination,
flight.GateNumber,
flight.BoardingTime.Format("15:04:05"),
flight.DepartureTime.Format("15:04:05"),
)
}
}

View File

@ -0,0 +1,63 @@
package airport
import (
"math/rand"
"time"
)
var distantFuture = time.Unix(1<<63-1, 0) // Max value for Unix timestamp
type GateNumber int
type Gate struct {
id GateNumber
walkingTimeFromSecurity time.Duration
taxiTimeToRunway time.Duration
//TODO: extend
}
var _ BaggageProcessor = &Gate{}
func NewGate(id GateNumber, walkingTimeFromSecurity time.Duration, taxiTimeToRunway time.Duration) Gate {
return Gate{id,
walkingTimeFromSecurity,
taxiTimeToRunway,
}
}
func NewGates(count int) map[GateNumber]*Gate {
gates := make(map[GateNumber]*Gate)
for i := range count {
walkingTime := time.Duration(5+rand.Intn(6)) * time.Second
taxiTime := time.Duration(5+rand.Intn(6)) * time.Second
gate := NewGate(GateNumber(i+1), walkingTime, taxiTime)
gates[GateNumber(i+1)] = &gate
}
return gates
}
func (g *Gate) SetFlights(flights []Flight) {
//TODO: implement
}
// Blocks for the walking distance to the gate + the time until the passenger has boarded
func (g *Gate) Process(passenger Passenger) error {
//TODO: implement
return nil
}
// Blocks until departure (includes taxi time to runway)
func (g *Gate) ProcessAircraft(ac *Aircraft) {
//TODO: implement
}
func (g *Gate) ProcessBaggage(fn FlightNumber, b Baggage) error {
//TODO: implement
return nil
}
func (g Gate) Start() {
//TODO: implement
}

View File

@ -0,0 +1,112 @@
package airport_test
import (
"reflect"
"sync"
"testing"
"time"
"gitty.informatik.hs-mannheim.de/steger/pr3-sose2026/go/06-airport/airport"
)
func TestGateSingleFlight(t *testing.T) {
sut := airport.NewGate(airport.GateNumber(25), time.Duration(3*time.Millisecond), time.Duration(1*time.Millisecond))
flight := airport.Flight{"LH123", "FRA", 25, time.Now().Add(5 * time.Millisecond), time.Now().Add(10 * time.Millisecond)}
sut.SetFlights([]airport.Flight{
flight,
})
go sut.Start()
start := time.Now()
wg := sync.WaitGroup{}
//the aircraft
wg.Add(1)
ac := airport.NewAircraft(flight)
departureTime := time.Now()
go func() { sut.ProcessAircraft(&ac); departureTime = time.Now(); wg.Done() }()
//the baggage
wg.Add(1)
baggage := airport.Baggage{[]string{"socks", "shirt"}}
var bag1err error
go func() { bag1err = sut.ProcessBaggage("LH123", baggage); wg.Done() }()
//a passenger arriving on time
wg.Add(1)
ticket1 := airport.Ticket{"John Doe", "FRA", "LH123"}
pax1 := airport.Passenger{"John Doe", ticket1, &airport.BoardingPass{ticket1, 25}, []airport.Baggage{}, []string{}}
pax1boardingTime := time.Now()
var pax1err error
go func() { pax1err = sut.Process(pax1); pax1boardingTime = time.Now(); wg.Done() }()
//another passenger arriving too late
wg.Add(1)
ticket2 := airport.Ticket{"Jane Doe", "FRA", "LH123"}
pax2 := airport.Passenger{"Jane Doe", ticket2, &airport.BoardingPass{ticket2, 25}, []airport.Baggage{}, []string{}}
var pax2err error
go func() { time.Sleep(8 * time.Millisecond); pax2err = sut.Process(pax2); wg.Done() }()
//another passenger arriving sometime during boarding
wg.Add(1)
ticket3 := airport.Ticket{"Alex Smith", "FRA", "LH123"}
pax3 := airport.Passenger{"Alex Smith", ticket3, &airport.BoardingPass{ticket3, 25}, []airport.Baggage{}, []string{}}
pax3boardingTime := time.Now()
var pax3err error
go func() {
time.Sleep(6 * time.Millisecond)
pax3err = sut.Process(pax3)
pax3boardingTime = time.Now()
wg.Done()
}()
//------------------------FROM HERE: checking ----------------------------------
wg.Wait()
//--------------------------check aircraft-------------------
if elapsed := departureTime.Sub(start); elapsed < 11*time.Millisecond {
t.Errorf("departure time too short: got %v, want at least 11ms", elapsed)
}
expectedBoardedPax := []airport.Passenger{pax1, pax3}
if !reflect.DeepEqual(expectedBoardedPax, ac.Passengers) {
t.Errorf("boarded passengers mismatch: got %v, want %v", ac.Passengers, expectedBoardedPax)
}
expectedBaggageOnboard := []airport.Baggage{baggage}
if !reflect.DeepEqual(expectedBaggageOnboard, ac.Baggage) {
t.Errorf("baggage on board mismatch: got %v, want %v", ac.Baggage, expectedBaggageOnboard)
}
//----------------------------check baggage--------------------
if bag1err != nil {
t.Errorf("baggage encountered an error during processing: %v", bag1err)
}
//--------------------------check passengers-------------------
//boarding can only happen between boarding (5ms from start) and departure (10ms from start)
if elapsed := pax1boardingTime.Sub(start); elapsed < 5*time.Millisecond || elapsed > 10*time.Millisecond {
t.Errorf("passenger 1 boarding time out of range: got %v, want between 5ms and 10ms", elapsed)
}
if pax1err != nil {
t.Errorf("passenger 1 encountered an error during boarding: %v", pax1err)
}
if pax2err == nil {
t.Errorf("passenger 2 did not encounter an error during boarding, but was expected to")
}
//boarding can only happen after boarding (5ms from start)
if elapsed := pax3boardingTime.Sub(start); elapsed < 5*time.Millisecond {
t.Errorf("passenger 3 boarding time out of range: got %v, want more than 5ms", elapsed)
}
if pax3err != nil {
t.Errorf("passenger 3 encountered an error during boarding: %v", pax3err)
}
}

View File

@ -0,0 +1,128 @@
package airport
import (
"fmt"
"math/rand"
"time"
"github.com/docker/docker/pkg/namesgenerator"
)
type Ticket struct {
Name string
Destination string
FlightNumber FlightNumber
}
type BoardingPass struct {
Ticket
Gate GateNumber
}
type Baggage struct {
Items []string
}
type Passenger struct {
Name string
Ticket Ticket
BoardingPass *BoardingPass
Bags []Baggage
CarryOnItems []string
}
var PossibleItems = []string{
"Laptop", "Tablet", "Smartphone", "Headphones", "Charger", "Book", "Magazine", "Notebook", "Pen", "Pencil", "Carabiner",
"Water Bottle", "Snacks", "Sandwich", "Camera", "Tripod", "Gun", "Clothes", "Shoes", "Toothbrush", "Toothpaste",
"Shampoo", "Conditioner", "Soap", "Deodorant", "Perfume", "Makeup Kit", "Hairbrush", "Comb", "Razor", "Shaving Cream",
"First Aid Kit", "Medicine", "Umbrella", "Hat", "Sunglasses", "Scarf", "Gloves", "Wallet", "Passport", "Camping Gear",
"Keys", "Watch", "Jewelry", "Flashlight", "Power Bank", "Knife", "USB Drive", "External Hard Drive", "Gaming Console", "Controller",
"Board Game", "Puzzle", "Toy", "Explosives", "Stuffed Animal", "Blanket", "Pillow", "Sleeping Bag", "Towel", "Swimsuit",
"Goggles", "Flip Flops", "Backpack", "Handbag", "Suitcase", "Briefcase", "Binoculars", "Map", "Guidebook", "Notebook",
"Sketchpad", "Paints", "Brushes", "Musical Instrument", "Sheet Music", "Sports Equipment", "Yoga Mat", "Jump Rope", "Resistance Bands",
"Tent", "Cooking Utensils", "Portable Stove", "Food Containers", "Water Filter", "Fishing Rod", "Knife Sharpener", "Multi-tool", "Rope",
"Compass", "GPS Device", "Safety Vest", "Helmet", "Protective Gear", "Fire Extinguisher", "Matches", "Lighter", "Candles", "Notebook",
"Clipboard", "Calculator", "Documents", "Folders", "Envelopes",
}
func NewRandomPassenger(flights []Flight) Passenger {
// Generate random ticket details
name := namesgenerator.GetRandomName(0)
flight := flights[rand.Intn(len(flights))]
ticket := Ticket{
Name: name,
Destination: flight.Destination,
FlightNumber: flight.Id,
}
// Generate random baggage
numBags := rand.Intn(3) // Random number of bags (0 to 2)
bags := make([]Baggage, numBags)
for i := 0; i < numBags; i++ {
numItems := rand.Intn(5) + 1 // Random number of items (1 to 5)
items := make([]string, numItems)
for j := 0; j < numItems; j++ {
items[j] = PossibleItems[rand.Intn(len(PossibleItems))]
}
bags[i] = Baggage{
Items: items,
}
}
// Generate random carry-on items
numCarryOnItems := rand.Intn(3) // Random number of carry-on items (0 to 2)
carryOnItems := make([]string, numCarryOnItems)
for i := 0; i < numCarryOnItems; i++ {
carryOnItems[i] = PossibleItems[rand.Intn(len(PossibleItems))]
}
return Passenger{
Name: name,
Ticket: ticket,
BoardingPass: nil,
Bags: bags,
CarryOnItems: carryOnItems,
}
}
func (p *Passenger) Start(ap *Airport) {
fmt.Printf("%s has arrived at the airport for the flight number %s to %s in %.02f minutes with %d bags and carry-on items: %v\n",
p.Name,
p.Ticket.FlightNumber,
p.Ticket.Destination,
time.Until(ap.FlightSchedule.flights[p.Ticket.FlightNumber].DepartureTime).Minutes(),
len(p.Bags),
p.CarryOnItems)
err := ap.Checkin.Process(p)
if err != nil {
fmt.Printf("\033[31m%s: %v\033[0m\n", p.Name, err)
return
}
fmt.Printf("%s completed the checkin procedure and is now heading to security\n", p.Name)
err = ap.Security.Process(*p)
if err != nil {
fmt.Printf("\033[31m%s: %v\033[0m\n", p.Name, err)
return
}
fmt.Printf("%s has successfully passed the security check and is now walking to the gate\n", p.Name)
gate, ok := ap.Gates[p.BoardingPass.Gate]
if !ok {
fmt.Printf("\033[31m%s: invalid gate number %v\033[0m\n", p.Name, p.BoardingPass.Gate)
return
}
err = gate.Process(*p)
if err != nil {
fmt.Printf("\033[31mError: %s: %v\033[0m\n", p.Name, err)
return
}
fmt.Printf("\033[32m%s has successfully boarded flight %s to %s\033[0m\n", p.Name, p.Ticket.FlightNumber, p.Ticket.Destination)
}

View File

@ -0,0 +1,33 @@
package airport
import (
"fmt"
"time"
)
type Runway struct {
takeoffDuration time.Duration
queue chan Aircraft
}
func NewRunway(takeoffDuration time.Duration) Runway {
return Runway{
takeoffDuration: takeoffDuration,
queue: make(chan Aircraft),
}
}
func (r *Runway) Start() {
defer fmt.Println("runway terminated")
for ac := range r.queue {
fmt.Printf("Flight %v to %v is taking off\n", ac.Flight.Id, ac.Flight.Destination)
time.Sleep(r.takeoffDuration)
fmt.Printf("\033[32mFlight %v to %v has taken off with %v passengers and %v bags on board\033[0m\n", ac.Flight.Id, ac.Flight.Destination, int(len(ac.Passengers)), int(len(ac.Baggage)))
}
}
func (r *Runway) ProcessAircraft(ac Aircraft) {
fmt.Printf("Flight %v is waiting for takeoff\n", ac.Flight.Id)
r.queue <- ac
}

View File

@ -0,0 +1,32 @@
package airport
import (
"time"
)
var prohibitedItems = map[string]bool{"Knife": true, "Gun": true, "Explosives": true}
type SecurityCheck struct {
processingTime time.Duration
//TODO: extend
}
func NewSecurityCheck(processingTime time.Duration) SecurityCheck {
return SecurityCheck{
processingTime: processingTime,
}
}
// processes one passenger at a time. Each passenger must at least spend the processingTime at the security check
func (sc *SecurityCheck) Start() {
//TODO: implement
}
// returns an error if
// - the passenger has no boarding pass
// - the passenger's boardings pass does not match the name
// - the passenger carries a prohibited item
func (sc SecurityCheck) Process(p Passenger) error {
//TODO: implement
return nil
}

View File

@ -0,0 +1,81 @@
package airport_test
import (
"testing"
"time"
"gitty.informatik.hs-mannheim.de/steger/pr3-sose2026/go/06-airport/airport"
)
func TestSecurityCheck(t *testing.T) {
sut := airport.NewSecurityCheck(1 * time.Millisecond)
go sut.Start()
testCases := []struct {
name string
passenger airport.Passenger
expectErr bool
}{
{
name: "Valid passenger",
passenger: airport.Passenger{
"Max Mustermann",
airport.Ticket{"Max Mustermann", "FRA", "LH123"},
&airport.BoardingPass{airport.Ticket{"Max Mustermann", "FRA", "LH123"}, airport.GateNumber(1)},
[]airport.Baggage{},
[]string{"Laptop", "Hat"},
},
expectErr: false,
},
{
name: "Passenger without boarding pass",
passenger: airport.Passenger{
"Max Mustermann",
airport.Ticket{"Max Mustermann", "FRA", "LH123"},
nil,
[]airport.Baggage{},
[]string{"Laptop", "Hat"},
},
expectErr: true,
},
{
name: "Passenger with wrong boarding pass",
passenger: airport.Passenger{
"Max Mustermann",
airport.Ticket{"Max Mustermann", "FRA", "LH123"},
&airport.BoardingPass{airport.Ticket{"Someone else", "FRA", "LH123"}, airport.GateNumber(1)},
[]airport.Baggage{},
[]string{"Laptop", "Hat"},
},
expectErr: true,
},
{
name: "Passenger with prohibited item",
passenger: airport.Passenger{
"Max Mustermann",
airport.Ticket{"Max Mustermann", "FRA", "LH123"},
&airport.BoardingPass{airport.Ticket{"Max Mustermann", "FRA", "LH123"}, airport.GateNumber(1)},
[]airport.Baggage{},
[]string{"Laptop", "Hat", "Knife"},
},
expectErr: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
start := time.Now()
err := sut.Process(tc.passenger)
if elapsed := time.Since(start); elapsed < 1*time.Millisecond {
t.Errorf("processing time too short: got %v, want at least 1ms", elapsed)
}
if (err != nil) != tc.expectErr {
t.Errorf("expected error: %v, got: %v", tc.expectErr, err != nil)
}
})
}
}

View File

@ -0,0 +1,5 @@
module gitty.informatik.hs-mannheim.de/steger/pr3-sose2026/go/06-airport
go 1.26.1
require github.com/docker/docker v28.5.2+incompatible

View File

@ -0,0 +1,2 @@
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=

View File

@ -0,0 +1,57 @@
package main
import (
"bufio"
"fmt"
"os"
"strings"
"gitty.informatik.hs-mannheim.de/steger/pr3-sose2026/go/06-airport/airport"
)
func main() {
ap := airport.DefaultAirport()
ap.FlightSchedule.Print()
defer func() {
lostBaggage := ap.Bhs.CollectLostBaggage()
fmt.Println("The following baggage was lost today: ", lostBaggage)
}()
ap.Start()
for _, flight := range ap.FlightSchedule.AllFlights() {
ac := airport.NewAircraft(flight)
gate := ap.Gates[flight.GateNumber]
go ac.Start(gate, &ap.Runway)
}
reader := bufio.NewReader(os.Stdin)
fmt.Print("What/Who arrives at airport? (p for passenger, c for car, t for train, q to quit): ")
for {
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
switch input {
case "q":
fmt.Println("Closing airport")
return
case "p":
p := ap.NewPassenger()
go p.Start(&ap)
case "c":
for i := 0; i < 4; i++ {
p := ap.NewPassenger()
go p.Start(&ap)
}
case "t":
for i := 0; i < 50; i++ {
p := ap.NewPassenger()
go p.Start(&ap)
}
default:
fmt.Println("Invalid command.")
}
}
}