Meine Simulation wird mit Javascript realisiert und als HTML dargestellt.
Das Besondere dabei ist:
Die Ampelschaltung und die Verarbeitung der Autos funktionieren unabhängig voneinander. Die Prototypen haben quasi keine geteilten Variablen und kommunizieren lediglich durch den DOM. Die Ampelschaltung weist den Ampeln also Zustände zu. Die Autos reagieren dann auf diese Zustände. Sie überprüfen, ob die Ampel grün ist und ob sie als Linksabbieger freie Fahrt haben.
Ich hab folgende Schwierigkeits-Level berücksichtigt:
[X] alle 4 Ampeln schalten in regelmäßigen Abständen lt. normalen Verkehrsregeln
[X] Die Ampel verhält sich wie eine "offizielle" Ampel (unterschiedliches Timing für die einzelnen Phasen)
[_] Eine oder mehrere Ampel-Lampen können kaputt gehen, je nach Defekt wird "irgendwie" darauf reagiert (seid Kreativ) es darf nur kein Unfall passieren können.
[X] Autos fahren in regelmäßigen Abständen über die Kreuzung und fahren jeweils nur gerade aus.
[X] Autos fahren in regelmäßigen Abständen über die Kreuzung und biegen ab und an rechts ab.
[X] Autos fahren in regelmäßigen Abständen über die Kreuzung und biegen rechts und links ab.
[_] Autos fahren in zufälligen Abständen und zufälligen Fluss-raten über die Kreuzung, die Ampeln reagieren darauf und schalten unterschiedlich lange Grün-Phasen
[_] Kollisionserkennung, 2 Autos dürfen keinen "Unfall" bauen und biegen nur ab, insofern genug Platz frei ist. Bis zu diesem Schwierigkeit-Grad dürfen sich Autos auch überlappen.
[src=html5]<div id="sim">
<!-- Darstellung der Straßen -->
<div class="vertical street">
<div class="stop top"></div>
<div class="stop bottom"></div>
</div>
<div class="horizontal street">
<div class="stop left"></div>
<div class="stop right"></div>
</div>
<div class="street crossing"></div>
<div class="t t1 top">
<div class="r"></div>
<div class="y"></div>
<div class="g"></div>
</div>
<!-- Darstellung der Ampeln-->
<div class="t t2 left">
<div class="r"></div>
<div class="y"></div>
<div class="g"></div>
</div>
<div class="t t2 right">
<div class="r"></div>
<div class="y"></div>
<div class="g"></div>
</div>
<div class="t t1 bottom">
<div class="r"></div>
<div class="y"></div>
<div class="g"></div>
</div>
<!-- Darstellung der Warteschlangen von Autos -->
<ol class="queue bottom"></ol>
<ol class="queue right"></ol>
<ol class="queue left"></ol>
<ol class="queue top"></ol>
</div>
<!-- Darstellung der Steuerelemente -->
<div id="controls">
<div>
<label for="car_speed">Auto-Geschwindigkeit:</label>
<input type="range" min="0" max="1" value="0.2" step="0.01" id="car_speed" />
</div>
<div>
<label for="t_speed">Ampel-Geschwindigkeit:</label>
<input type="range" min="0.1" max="20" value="1" step="0.01" id="t_speed" />
</div>
</div>[/src]
[src=javascript]NodeList.prototype.forEach = Array.prototype.forEach;
/*
------------------------------------------------------------------------------------------------------------------------
Die Farben der Ampeln werden als Konstanten definiert, um Fehler bei der Abfrage der Farben schneller zu erkennen.
*/
var RED = 'RED', GREEN = 'GREEN', YELLOW = 'YELLOW', REDYELLOW = 'REDYELLOW';
/*
------------------------------------------------------------------------------------------------------------------------
Diese Funktion kann zufällige Farben generieren, die hinterher den neu erzeugten Autos zugewiesen werden.
Quelle: http://stackoverflow.com/questions/1484506/random-color-generator-in-javascript
*/
var random_color = function(){
return '#' + (Math.random()*0xFFFFFF<<0).toString(16);
}
/*
------------------------------------------------------------------------------------------------------------------------
Beim Abarbeiten der Auto-Warteschlangen muss manchmal geprüft werden, wie der Gegenverkehr aussieht. Dafür kann über
die Attribute dieses Objekts iteriert werden.
*/
var opposites = {
left: 'right',
right: 'left',
top: 'bottom',
bottom: 'top'
};
/*
------------------------------------------------------------------------------------------------------------------------
Der Prototyp für eine Ampel
*/
var TrafficLightSet = function(qs){
/*
qs: Wird beim Erzeugen zugewiesen und erhält als String den Namen einer CSS-Klasse.
*/
this.qs = qs;
/*
set: Beim Aufruf dieser Methode werden alle Ampeln, die diesen Zustand anzeigen sollen (also Elemente mit der CSS-Klasse "qs")
auf eine bestimmte Farbe geschaltet. Das geschieht über die Zuweisung eines data-Attributs. Die Farbe wird hinterher
vom Stylesheet entsprechend dargestellt.
*/
this.set = function(color){
document.querySelectorAll(this.qs).forEach(function(elem){
elem.dataset.is = color;
});
};
};
/*
------------------------------------------------------------------------------------------------------------------------
Der Prototyp für eine Ampelschaltung
*/
var Circuit = function(){
/*
Im Konstruktur werden 2 Ampelgruppen angelegt, denen jeweils der Name einer CSS-Klasse bekannt ist, die
hinterher für die Darstellung der Ampeln und ihrer Zustände dient.
*/
var t1 = new TrafficLightSet('.t1');
var t2 = new TrafficLightSet('.t2');
/*
states: Die Steuerung der Ampeln erfolgt bei mir ganz einfach über die Definition von 8 Zuständen, die jeweils alle
Informationen darüber enthalten, welche Ampel was anzeigen soll. Jeder Zustand enthält eine Angabe, wie lange er
aktiv sein soll und welche Ampelgruppe welchen Zustand anzeigen soll.
*/
this.states = [
{
time: 7,
assign: [
{ dest: t1, state: RED },
{ dest: t2, state: GREEN }
]
},
{
time: 3,
assign: [
{ dest: t1, state: RED },
{ dest: t2, state: YELLOW }
]
},
{
time: 1,
assign: [
{ dest: t1, state: RED },
{ dest: t2, state: RED }
]
},
{
time: 3,
assign: [
{ dest: t1, state: REDYELLOW },
{ dest: t2, state: RED }
]
},
{
time: 7,
assign: [
{ dest: t1, state: GREEN },
{ dest: t2, state: RED }
]
},
{
time: 3,
assign: [
{ dest: t1, state: YELLOW },
{ dest: t2, state: RED }
]
},
{
time: 1,
assign: [
{ dest: t1, state: RED },
{ dest: t2, state: RED }
]
},
{
time: 3,
assign: [
{ dest: t1, state: RED },
{ dest: t2, state: REDYELLOW }
]
}
];
/*
current_state: Welcher Zustand ist gerade aktiv?
*/
this.current_state = 0;
/*
next: Hier wird der Zustand der Ampelgruppen geändert:
Es wird der aktuelle Zustand ermittelt und jeder Ampelgruppe wird ihr jeweils aktueller Zustand zugewiesen.
Anschließend wird ein Timer gesetzt, der den nächsten Zustand vorbereitet und die Methode ruft sich selbst
wieder auf. Der Timer wird dabei auf den Wert gesetzt, den der Zustand haben soll und durch die Eingabe des
Benutzers geteilt, sodass man festlegen kann, wie schnell das Schalten der Ampeln ablaufen soll.
*/
this.next = function(){
var state = this.states[this.current_state];
state.assign.forEach(function(s){
s.dest.set(s.state);
});
setTimeout(function(c){
c.current_state = (c.current_state + 1) % c.states.length;
c.next();
}, state.time * 500 / parseFloat(document.getElementById('t_speed').value), this);
}
};
/*
------------------------------------------------------------------------------------------------------------------------
Eine Funtion, die beim Aufruf ein neues Auto erstellen kann und gleichzeitig die vorhandenen Warteschlangen abarbeitet.
*/
var process_cars = function(time){
/*
Es soll nicht bei jedem Aufruf ein neues Auto erstellt werden, sondern zufällig, im Schnitt bei jedem 2. Aufruf.
Es wird ebenfalls zufällig entschieden, in welche Richtung das Auto fahren soll und welcher Warteschlange es
hinzugefügt wird. Dadurch soll ein realistischer Verkehr simuliert werden.
Richtung | Wahrscheinlichkeit
----------+-------------------
Links | 25%
Rechts | 25%
Geradeaus | 25%
Warteschlange | Wahrscheinlichkeit
--------------+-------------------
Oben | 15%
Unten | 15%
Links | 35%
Rechts | 35%
Anschließend wird ein neues HTML-Element erzeugt, das ein Auto repräsentiert. Ihm wird die Richtung und eine zufällige
Farbe zugewiesen und es wird in einer der Warteschlangen angehängt, die ebenfalls durch CSS visualisiert werden.
*/
if(Math.random() < 0.5){
var dir_r = Math.random();
var queue_r = Math.random();
var direction = dir_r < 0.25 ? 'left' : dir_r < 0.5 ? 'right' : 'straight';
var queue = queue_r < 0.15 ? 'top' : queue_r < 0.3 ? 'bottom' : queue_r < 0.65 ? 'left' : 'right';
var car = document.createElement('li');
car.className = 'car';
car.dataset.direction = direction;
car.style.background = random_color();
document.querySelector('.queue.' + queue).appendChild(car);
}
/*
Etwas später wird dann ermittelt, welche der Autos gerade fahren können.
Dafür werden alle Warteschlangen überprüft und der jeweilige Gegenverkehr ermittelt.
Dann wird ermittelt, ob die gerade überprüfte Warteschlange ein Auto enthält, ob der Gegenverkehr ein Auto enthält,
in welche Richtung das Auto abbiegen möchte, in welche Richtung das gegenüberliegende Auto abbiegen möchte und welche
Farbe die Ampel gerade zeigt.
Falls die Warteschlange ein Auto enthält und die Ampel gerade grün ist, werden weitere Prüfungen vorgenommen:
Wenn das Auto geradeaus fahren oder rechts abbiegen möchte, wird es sofort verareitet.
Falls das Auto links abbiegen möchte, wird überprüft, ob kein Gegenverkehr da ist oder ob der Gegenverkehr ebenfalls
links abbiegen möchte. Wenn ja, wird das Auto verarbeitet und der Gegenverkehr, falls existent, ebenfalls.
Ein Auto wird abgearbeitet, indem es einfach aus dem DOM gelöscht wird.
*/
setTimeout(function(){
for(var look_at in opposites){
var look_at_opposite = opposites[look_at];
var car = document.querySelector('.queue.' + look_at + ' .car:first-child');
var opposite_car = document.querySelector('.queue.' + look_at_opposite + ' .car:first-child');
var direction = car ? car.dataset.direction : null;
var opposite_direction = opposite_car ? opposite_car.dataset.direction : null;
var color = document.querySelector('.t.' + look_at).dataset.is;
if(car && color == GREEN){
if(direction == 'straight' || direction == 'right'){
car.parentNode.removeChild(car);
}
else if(direction == 'left' && (opposite_direction == null || opposite_direction == 'left')){
car.parentNode.removeChild(car);
if(opposite_car){
opposite_car.parentNode.removeChild(opposite_car);
}
return;
}
}
};
}, time);
};
/*
------------------------------------------------------------------------------------------------------------------------
Hier wird die Simulation initialisiert:
Die Ampeln werden das erste mal geschaltet und es wird auf Änderungen beim Slider für die Auto-Geschwindigkeit reagiert.
*/
window.addEventListener('load', function(){
(new Circuit()).next();
var car_speed = document.getElementById('car_speed');
var car_timer = (1 / (parseFloat(car_speed.value) + 0.01)) * 100;
var car_interval = setInterval(process_cars, car_timer, car_timer);
car_speed.addEventListener('change', function(){
car_timer = (1 / (parseFloat(this.value) + 0.01)) * 100;
clearInterval(car_interval);
car_interval = setInterval(process_cars, car_timer);
});
});[/src]
[src=css]@-webkit-keyframes signal {
0% { opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { opacity: 0; }
}
@-moz-keyframes signal {
0% { opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { opacity: 0; }
}
@-o-keyframes signal {
0% { opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { opacity: 0; }
}
@keyframes signal {
0% { opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { opacity: 0; }
}
body {
background: #666;
font-family: arial;
}
ol, li {
list-style-type: none;
margin: 0;
padding: 0;
}
#sim .t {
position: absolute;
border: 1px solid #000;
width: 28px;
height: 76px;
border-radius: 3px;
background: #444444;
box-shadow: inset 0 2px 2px rgba(255, 255, 255, 0.25), inset 0 -2px 2px rgba(0, 0, 0, 0.1);
}
#sim .t:after {
content: '';
display: block;
background: #333;
box-shadow: inset 0 5px 3px 1px rgba(255, 255, 255, 0.5);
position: absolute;
top: 38px;
left: 0;
right: 0;
margin: auto;
width: 4px;
height: 100px;
z-index: -1;
}
#sim .t.top {
margin: auto;
margin-bottom: 201px;
bottom: 50%;
left: 0;
right: 200px;
}
#sim .t.left {
margin: auto;
left: 50%;
margin-left: -174px;
top: 0;
bottom: 0;
}
#sim .t.right {
margin: auto;
right: 50%;
margin-right: -174px;
top: -400px;
bottom: 0;
}
#sim .t.bottom {
margin: auto;
margin-top: 120px;
top: 50%;
left: 200px;
right: 0;
}
#sim .r,
#sim .y,
#sim .g {
width: 20px;
height: 20px;
margin: 3px;
border-radius: 50px;
border: 1px solid #000;
box-shadow: inset 0 5px 5px rgba(255, 255, 255, 0.05), inset 0 -5px 5px rgba(0, 0, 0, 0.67), 0 -1px 0 #000, 0 -2px 0 #777;
}
#sim .r {
background: #290001;
}
#sim .y {
background: #2d1f00;
}
#sim .g {
background: #333833;
}
#sim .t[data-is="RED"] .r,
#sim .t[data-is="REDYELLOW"] .r {
background: #ffbbbb;
box-shadow: inset 0 0 10px 4px #ff0000, 0 0 5px 3px rgba(255, 0, 0, 0.67), 0 -1px 0 #800, 0 -2px 0 #777;
border: 1px solid #cc0000;
}
#sim .t[data-is="YELLOW"] .y,
#sim .t[data-is="REDYELLOW"] .y{
background: #ffeabf;
box-shadow: inset 0 0 10px 4px #ffd300, 0 0 5px 3px rgba(255, 209, 0, 0.67), 0 -1px 0 #887300, 0 -2px 0 #777;
border: 1px solid #cca900;
}
#sim .t[data-is="GREEN"] .g {
background: #bbffbb;
box-shadow: inset 0 0 10px 4px #00ff3a, 0 0 5px 3px rgba(0, 255, 36, 0.67), 0 -1px 0 #00881b, 0 -2px 0 #777;
border: 1px solid #00cc48;
}
#sim .street {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
margin: auto;
background: #888;
box-shadow: 0 0 40px rgba(0, 0, 0, 0.2);
overflow: hidden;
z-index: -2;
}
#sim .street.horizontal {
height: 200px;
}
#sim .street.vertical {
width: 200px;
}
#sim .street:before {
display: block;
content: '';
width: 100%;
height: 100%;
border: 4px dashed #fff;
margin: -4px;
position: relative;
}
#sim .street.horizontal:before {
height: 50%;
top: -2px;
}
#sim .street.vertical:before {
width: 50%;
left: -2px;
}
#sim .stop {
position: absolute;
width: 20px;
height: 20px;
background: #fff;
}
#sim .stop.left {
top: 50%;
right: 50%;
margin-right: 200px;
height: 50%;
margin-top: -2px;
}
#sim .stop.right {
top: 0;
left: 50%;
margin-left: 200px;
height: 50%;
margin-top: 2px;
}
#sim .stop.top {
width: 50%;
bottom: 50%;
margin-bottom: 200px;
margin-left: 2px;
}
#sim .stop.bottom {
top: 50%;
margin-top: 200px;
width: 50%;
left: 50%;
margin-left: -2px;
}
#sim .crossing {
width: 200px;
height: 400px;
box-shadow: none;
overflow: visible;
}
#sim .crossing:before {
background: inherit;
width: 400px;
height: 200px;
margin-top: 100px;
margin-left: -100px;
border: none;
}
#sim .queue {
position: absolute;
width: 100px;
height: 100px;
padding: 20px;
overflow: hidden;
box-sizing: border-box;
z-index: -1;
}
#sim .queue.bottom {
top: 50%;
left: 50%;
padding-top: 220px;
height: 50%;
}
#sim .queue.right {
margin-top: -100px;
top: 50%;
left: 50%;
padding-left: 230px;
width: 50%;
white-space: nowrap;
}
#sim .queue.left {
top: 50%;
margin-right: 50%;
right: 220px;
width: 50%;
height: 100px;
}
#sim .queue.top {
bottom: 50%;
margin-bottom: 200px;
height: 50%;
left: 50%;
margin-left: -100px;
transform: rotate(180deg);
}
#sim .car {
width: 60px;
height: 60px;
background: blue;
border-radius: 10px;
color: #fff;
position: relative;
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.25);
}
#sim .bottom .car,
#sim .top .car {
height: 120px;
margin: 20px 0;
}
#sim .right .car,
#sim .left .car{
width: 120px;
margin: 0 10px;
margin-bottom: 40px;
display: inline-block;
}
#sim .left .car {
float: right;
}
#sim .top .car {
transform: rotate(-180deg);
position: relative;
}
#sim .bottom .car[data-direction="left"]:before,
#sim .bottom .car[data-direction="left"]:after,
#sim .bottom .car[data-direction="right"]:before,
#sim .bottom .car[data-direction="right"]:after,
#sim .top .car[data-direction="left"]:before,
#sim .top .car[data-direction="left"]:after,
#sim .top .car[data-direction="right"]:before,
#sim .top .car[data-direction="right"]:after,
#sim .left .car[data-direction="left"]:before,
#sim .left .car[data-direction="left"]:after,
#sim .left .car[data-direction="right"]:before,
#sim .left .car[data-direction="right"]:after,
#sim .right .car[data-direction="left"]:before,
#sim .right .car[data-direction="left"]:after,
#sim .right .car[data-direction="right"]:before,
#sim .right .car[data-direction="right"]:after{
position: absolute;
content: '';
display: block;
background: #fff;
box-shadow: 0 0 10px 4px rgb(255, 165, 0), inset 0 0 2px 1px rgb(255, 126, 0);
-webkit-animation: signal 1s infinite;
-moz-animation: signal 1s infinite;
-o-animation: signal 1s infinite;
animation: signal 1s infinite;
}
#sim .left .car[data-direction="left"]:before,
#sim .left .car[data-direction="left"]:after,
#sim .left .car[data-direction="right"]:before,
#sim .left .car[data-direction="right"]:after,
#sim .right .car[data-direction="left"]:before,
#sim .right .car[data-direction="left"]:after,
#sim .right .car[data-direction="right"]:before,
#sim .right .car[data-direction="right"]:after{
width: 10px;
height: 5px;
}
#sim .left .car:before,
#sim .right .car:before {
left: 5%;
}
#sim .left .car:after,
#sim .right .car:after {
right: 5%;
}
#sim .left .car[data-direction="left"]:before,
#sim .left .car[data-direction="left"]:after {
bottom: 100%;
}
#sim .left .car[data-direction="right"]:before,
#sim .left .car[data-direction="right"]:after {
top: 100%;
}
#sim .right .car[data-direction="left"]:before,
#sim .right .car[data-direction="left"]:after {
top: 100%;
border-radius: 0 0 5px 5px;
}
#sim .right .car[data-direction="right"]:before,
#sim .right .car[data-direction="right"]:after {
bottom: 100%;
border-radius: 5px 5px 0 0;
}
#sim .bottom .car[data-direction="left"]:before,
#sim .bottom .car[data-direction="left"]:after,
#sim .bottom .car[data-direction="right"]:before,
#sim .bottom .car[data-direction="right"]:after,
#sim .top .car[data-direction="left"]:before,
#sim .top .car[data-direction="left"]:after,
#sim .top .car[data-direction="right"]:before,
#sim .top .car[data-direction="right"]:after{
width: 5px;
height: 10px;
}
#sim .bottom .car:before,
#sim .top .car:before {
top: 5%;
}
#sim .bottom .car:after,
#sim .top .car:after {
bottom: 5%;
}
#sim .bottom .car[data-direction="left"]:before,
#sim .bottom .car[data-direction="left"]:after {
right: 100%;
}
#sim .bottom .car[data-direction="right"]:before,
#sim .bottom .car[data-direction="right"]:after {
left: 100%;
}
#sim .top .car[data-direction="left"]:before,
#sim .top .car[data-direction="left"]:after {
left: 100%;
}
#sim .top .car[data-direction="right"]:before,
#sim .top .car[data-direction="right"]:after {
right: 100%;
}
#controls {
color: #fff;
}
#controls > div {
margin-bottom: 0.5em;
overflow: hidden;
}
#controls label {
width: 200px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
display: inline-block;
float: left;
padding: 5px;
text-align: right;
}[/src]
Und hier gibt's die Demo:
https://fiddle.jshell.net/v5Lfn5mr/show/light/