From 64703106ea31bc51ec47bed8d111fd506577db19 Mon Sep 17 00:00:00 2001 From: pTalanov Date: Tue, 29 May 2012 17:29:22 +0400 Subject: [PATCH] Add tests which check that web demo canvas examples compile correctly --- .../semantics/WebDemoCanvasExamplesTest.java | 78 +++ .../webDemoCanvasExamples/cases/Creatures.kt | 334 +++++++++++++ .../cases/Fancy lines.kt | 64 +++ .../cases/Hello, Kotlin.kt | 91 ++++ .../cases/Traffic light.kt | 466 ++++++++++++++++++ 5 files changed, 1033 insertions(+) create mode 100644 js/js.tests/test/org/jetbrains/k2js/test/semantics/WebDemoCanvasExamplesTest.java create mode 100644 js/js.translator/testFiles/webDemoCanvasExamples/cases/Creatures.kt create mode 100644 js/js.translator/testFiles/webDemoCanvasExamples/cases/Fancy lines.kt create mode 100644 js/js.translator/testFiles/webDemoCanvasExamples/cases/Hello, Kotlin.kt create mode 100644 js/js.translator/testFiles/webDemoCanvasExamples/cases/Traffic light.kt diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/WebDemoCanvasExamplesTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/WebDemoCanvasExamplesTest.java new file mode 100644 index 00000000000..12d1d9d29c9 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/WebDemoCanvasExamplesTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2010-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.k2js.test.semantics; + +import closurecompiler.internal.com.google.common.collect.Lists; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.k2js.test.SingleFileTranslationTest; +import org.mozilla.javascript.EcmaError; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Pavel Talanov + */ +/* +* We can't really check that this examples work in non-browser environment, so we just check that examples compile and +* running them produce expected error. +* */ +public final class WebDemoCanvasExamplesTest extends SingleFileTranslationTest { + + private static final String CANVAS_HELPER = pathToTestFilesRoot() + "canvas_helper.js"; + + public WebDemoCanvasExamplesTest() { + super("webDemoCanvasExamples/"); + } + + public void testCreatures() throws Exception { + doTest("Creatures.kt", "document"); + } + + public void testHelloKotlin() throws Exception { + doTest("Hello, Kotlin.kt", "document"); + } + + public void testFancyLines() throws Exception { + doTest("Fancy lines.kt", "$"); + } + + public void testTrafficLight() throws Exception { + doTest("Traffic light.kt", "document"); + } + + private void doTest(@NotNull String filename, @NotNull String firstUnknownSymbolEncountered) throws Exception { + try { + checkOutput(filename, ""); + fail(); + } + catch (EcmaError e) { + String expectedErrorMessage = "ReferenceError: \"" + firstUnknownSymbolEncountered + "\" is not defined."; + assertTrue("Unexpected error when running compiled canvas examples with rhino.\n" + + "Expected: " + expectedErrorMessage + "\n" + + "Actual: " + e.getMessage(), + e.getMessage().startsWith(expectedErrorMessage)); + } + } + + @Override + protected List additionalJSFiles() { + ArrayList result = Lists.newArrayList(super.additionalJSFiles()); + result.add(CANVAS_HELPER); + return result; + } +} diff --git a/js/js.translator/testFiles/webDemoCanvasExamples/cases/Creatures.kt b/js/js.translator/testFiles/webDemoCanvasExamples/cases/Creatures.kt new file mode 100644 index 00000000000..12eebb5d0ef --- /dev/null +++ b/js/js.translator/testFiles/webDemoCanvasExamples/cases/Creatures.kt @@ -0,0 +1,334 @@ +/* +In this example strange creatures are watching the kotlin logo. You can drag'n'drop them as well as the logo. +Doubleclick to add more creatures but be careful. They may be watching you! +*/ +package creatures +// importing some of the API defined +import jquery.* +import html5.* +import java.util.ArrayList +import js.* +import js.Math +import js.setInterval +import html5.getCanvas +import html5.getKotlinLogo +import jquery.jq +import js.setTimeout +import java.util.List + + +abstract class Shape() { + + abstract fun draw(state : CanvasState) + // these two abstract methods defines that our shapes can be dragged + abstract fun contains(mousePos : Vector) : Boolean + abstract var pos : Vector + + var selected : Boolean = false + + // a couple of helper extension methods we'll be using in the derived classes + fun Context.shadowed(shadowOffset : Vector, alpha : Double, render : Context.() -> Unit) { + save() + shadowColor = "rgba(100, 100, 100, $alpha)" + shadowBlur = 5.0 + shadowOffsetX = shadowOffset.x + shadowOffsetY = shadowOffset.y + render() + restore() + } + + fun Context.fillPath(constructPath : Context.() -> Unit) { + beginPath() + constructPath() + closePath() + fill() + } +} + +val Kotlin = Logo(v(250.0, 75.0)) + +class Logo(override var pos : Vector) : Shape() +{ + val relSize : Double = 0.25 + val shadowOffset = v(-3.0, 3.0) + val imageSize = v(377.0, 393.0) + var size : Vector = imageSize * relSize + // get-only properties like this saves you lots of typing and are very expressive + val position : Vector + get() = if (selected) pos - shadowOffset else pos + + + fun drawLogo(state : CanvasState) { + size = imageSize * (state.size.x / imageSize.x) * relSize + // getKotlinLogo() is a 'magic' function here defined only for purposes of demonstration but in fact it just find an element containing the logo + state.context.drawImage(getImage("/static/images/kotlinlogowobackground.png"), 0, 0, + imageSize.x.toInt(), imageSize.y.toInt(), + position.x.toInt(), position.y.toInt(), + size.x.toInt(), size.y.toInt()) + } + + override fun draw(state : CanvasState) { + val context = state.context + if (selected) { + // using helper we defined in Shape class + context.shadowed(shadowOffset, 0.2) { + drawLogo(state) + } + } else { + drawLogo(state) + } + } + + override fun contains(mousePos: Vector): Boolean = mousePos.isInRect(pos, size) + + val centre : Vector + get() = pos + size * 0.5 +} + +val gradientGenerator = RadialGradientGenerator(getContext()) + +class Creature(override var pos : Vector, val state : CanvasState) : Shape() { + + val shadowOffset = v(-5.0, 5.0) + val colorStops = gradientGenerator.getNext() + val relSize = 0.05 + // these properties have no backing fields and in java/javascript they could be represented as little helper functions + val radius : Double + get() = state.width * relSize + val position : Vector + get() = if (selected) pos - shadowOffset else pos + val directionToLogo : Vector + get() = (Kotlin.centre - position).normalized + + //notice how the infix call can make some expressions extremely expressive + override fun contains(mousePos : Vector) = pos distanceTo mousePos < radius + + // defining more nice extension functions + fun Context.circlePath(position : Vector, rad : Double) { + arc(position.x, position.y, rad, 0.0, 2 * Math.PI, false) + } + + //notice we can use an extension function we just defined inside another extension function + fun Context.fillCircle(position : Vector, rad : Double) { + fillPath { + circlePath(position, rad) + } + } + + override fun draw(state : CanvasState) { + val context = state.context + if (!selected) { + drawCreature(context) + } else { + drawCreatureWithShadow(context) + } + } + + fun drawCreature(context : Context) { + context.fillStyle = getGradient(context) + context.fillPath { + tailPath(context) + circlePath(position, radius) + } + drawEye(context) + } + + fun getGradient(context : Context) : CanvasGradient { + val gradientCentre = position + directionToLogo * (radius / 4) + val gradient = context.createRadialGradient(gradientCentre.x, gradientCentre.y, 1.0, gradientCentre.x, gradientCentre.y, 2 * radius) + for (colorStop in colorStops) { + gradient.addColorStop(colorStop._1, colorStop._2) + } + return gradient + } + + fun tailPath(context : Context) { + val tailDirection = -directionToLogo + val tailPos = position + tailDirection * radius * 1.0 + val tailSize = radius * 1.6 + val angle = Math.PI / 6.0 + val p1 = tailPos + tailDirection.rotatedBy(angle) * tailSize + val p2 = tailPos + tailDirection.rotatedBy(-angle) * tailSize + val middlePoint = position + tailDirection * radius * 1.0 + context.moveTo(tailPos.x.toInt(), tailPos.y.toInt()) + context.lineTo(p1.x.toInt(), p1.y.toInt()) + context.quadraticCurveTo(middlePoint.x, middlePoint.y, p2.x, p2.y) + context.lineTo(tailPos.x.toInt(), tailPos.y.toInt()) + } + + fun drawEye(context : Context) { + val eyePos = directionToLogo * radius * 0.6 + position + val eyeRadius = radius / 3 + val eyeLidRadius = eyeRadius / 2 + context.fillStyle = "#FFFFFF" + context.fillCircle(eyePos, eyeRadius) + context.fillStyle = "#000000" + context.fillCircle(eyePos, eyeLidRadius) + } + + fun drawCreatureWithShadow(context : Context) { + context.shadowed(shadowOffset, 0.7) { + context.fillStyle = getGradient(context) + fillPath { + tailPath(context) + context.circlePath(position, radius) + } + } + drawEye(context) + } +} + +class CanvasState(val canvas : Canvas) { + var width = canvas.width.toDouble() + var height = canvas.height.toDouble() + val size : Vector + get() = v(width, height) + val context = getContext() + var valid = false + var shapes = ArrayList() + var selection : Shape? = null + var dragOff = Vector() + val interval = 1000 / 30 + + { + jq(canvas).mousedown { + valid = false + selection = null + val mousePos = mousePos(it) + for (shape in shapes) { + if (mousePos in shape) { + dragOff = mousePos - shape.pos + shape.selected = true + selection = shape + break + } + } + } + + jq(canvas).mousemove { + if (selection != null) { + selection.sure().pos = mousePos(it) - dragOff + valid = false + } + } + + jq(canvas).mouseup { + if (selection != null) { + selection.sure().selected = false + } + selection = null + valid = false + } + + jq(canvas).dblclick { + val newCreature = Creature(mousePos(it), this @CanvasState) + addShape(newCreature) + valid = false + } + + setInterval({ + draw() + }, interval) + } + + fun mousePos(e : MouseEvent) : Vector { + var offset = Vector() + var element : DomElement? = canvas + while (element != null) { + val el : DomElement = element.sure() + offset += Vector(el.offsetLeft, el.offsetTop) + element = el.offsetParent + } + return Vector(e.pageX, e.pageY) - offset + } + + fun addShape(shape : Shape) { + shapes.add(shape) + valid = false + } + + fun clear() { + context.fillStyle = "#FFFFFF" + context.fillRect(0, 0, width.toInt(), height.toInt()) + context.strokeStyle = "#000000" + context.lineWidth = 4.0 + context.strokeRect(0, 0, width.toInt(), height.toInt()) + } + + fun draw() { + if (valid) return + + clear() + for (shape in shapes.reversed()) { + shape.draw(this) + } + Kotlin.draw(this) + valid = true + } +} + +class RadialGradientGenerator(val context : Context) { + val gradients = ArrayList>() + var current = 0 + + fun newColorStops(vararg colorStops : #(Double, String)) { + gradients.add(colorStops) + } + + { + newColorStops(#(0.0, "#F59898"), #(0.5, "#F57373"), #(1.0, "#DB6B6B")) + newColorStops(#(0.39, "rgb(140,167,209)"), #(0.7, "rgb(104,139,209)"), #(0.85, "rgb(67,122,217)")) + newColorStops(#(0.0, "rgb(255,222,255)"), #(0.5, "rgb(255,185,222)"), #(1.0, "rgb(230,154,185)")) + newColorStops(#(0.0, "rgb(255,209,114)"), #(0.5, "rgb(255,174,81)"), #(1.0, "rgb(241,145,54)")) + newColorStops(#(0.0, "rgb(132,240,135)"), #(0.5, "rgb(91,240,96)"), #(1.0, "rgb(27,245,41)")) + newColorStops(#(0.0, "rgb(250,147,250)"), #(0.5, "rgb(255,80,255)"), #(1.0, "rgb(250,0,217)")) + } + + fun getNext() : Array<#(Double, String)> { + val result = gradients.get(current) + current = (current + 1) % gradients.size() + return result + } +} + +fun v(x : Double, y : Double) = Vector(x, y) + +class Vector(val x : Double = 0.0, val y : Double = 0.0) { + fun plus(v : Vector) = v(x + v.x, y + v.y) + fun minus() = v(-x, -y) + fun minus(v : Vector) = v(x - v.x, y - v.y) + fun times(koef : Double) = v(x * koef, y * koef) + fun distanceTo(v : Vector) = Math.sqrt((this - v).sqr) + fun rotatedBy(theta : Double) : Vector { + val sin = Math.sin(theta) + val cos = Math.cos(theta) + return v(x * cos - y * sin, x * sin + y * cos) + } + + fun isInRect(topLeft : Vector, size : Vector) = (x >= topLeft.x) && (x <= topLeft.x + size.x) && + (y >= topLeft.y) && (y <= topLeft.y + size.y) + + val sqr : Double + get() = x * x + y * y + val normalized : Vector + get() = this * (1.0 / Math.sqrt(sqr)) +} + +fun main(args : Array) { + val state = CanvasState(getCanvas()) + state.addShape(Kotlin) + state.addShape(Creature(state.size * 0.25, state)) + state.addShape(Creature(state.size * 0.75, state)) + setTimeout({ + state.valid = false + }) +} + +fun List.reversed() : List { + val result = ArrayList() + var i = size() + while (i > 0) { + result.add(get(--i)) + } + return result +} \ No newline at end of file diff --git a/js/js.translator/testFiles/webDemoCanvasExamples/cases/Fancy lines.kt b/js/js.translator/testFiles/webDemoCanvasExamples/cases/Fancy lines.kt new file mode 100644 index 00000000000..32e829dd21b --- /dev/null +++ b/js/js.translator/testFiles/webDemoCanvasExamples/cases/Fancy lines.kt @@ -0,0 +1,64 @@ +/* +This example is based on example from html5 canvas2D docs: + http://www.w3.org/TR/2dcontext/ +Note that only a subset of the api is supported for now. +*/ + +package fancylines + +import js.*; +import html5.*; +import jquery.*; + +fun main(args : Array) { + //jq is a name for JQuery function + jq { + FancyLines().run(); + } +} + +class FancyLines() { + // we use two 'magic' functions here getContext() and getCanvas() + val context = getContext(); + val height = getCanvas().height; + val width = getCanvas().width; + var x = width * Math.random(); + var y = height * Math.random(); + var hue = 0; + + fun line() { + context.save(); + + context.beginPath(); + + context.lineWidth = 20.0 * Math.random(); + context.moveTo(x.toInt(), y.toInt()); + + x = width * Math.random(); + y = height * Math.random(); + + context.bezierCurveTo(width * Math.random(), height * Math.random(), + width * Math.random(), height * Math.random(), x, y); + + hue += Math.random() * 10; + + context.strokeStyle = "hsl($hue, 50%, 50%)"; + + context.shadowColor = "white"; + context.shadowBlur = 10.0; + + context.stroke(); + + context.restore(); + } + + fun blank() { + context.fillStyle = "rgba(255,255,1,0.1)"; + context.fillRect(0, 0, width, height); + } + + fun run() { + setInterval({line()}, 40); + setInterval({blank()}, 100); + } +} \ No newline at end of file diff --git a/js/js.translator/testFiles/webDemoCanvasExamples/cases/Hello, Kotlin.kt b/js/js.translator/testFiles/webDemoCanvasExamples/cases/Hello, Kotlin.kt new file mode 100644 index 00000000000..f6d637533ee --- /dev/null +++ b/js/js.translator/testFiles/webDemoCanvasExamples/cases/Hello, Kotlin.kt @@ -0,0 +1,91 @@ +/* + This example is just simple text floating around. If u are using chrome, there is a bug that spoil the visuals. +*/ +package hello + +import js.* +import html5.* +import jquery.* + +val context = getContext() +val height = getCanvas().height +val width = getCanvas().width + +// class representing a floating text +class HelloKotlin() { + var relX = 0.2 + 0.2 * Math.random() + var relY = 0.4 + 0.2 * Math.random() + + val absX : Double + get() = (relX * width) + val absY : Double + get() = (relY * height) + + var relXVelocity = randomVelocity() + var relYVelocity = randomVelocity() + + + val message = "Hello, Kotlin!" + val textHeightInPixels = 20 + { + context.font = "bold ${textHeightInPixels}px Georgia, serif" + } + val textWidthInPixels = context.measureText(message).width + + fun draw() { + context.save() + move() + // if you using chrome chances are good you wont see the shadow + context.shadowColor = "#000000" + context.shadowBlur = 5.0 + context.shadowOffsetX = -4.0 + context.shadowOffsetY = 4.0 + context.fillStyle = "rgb(242,160,110)" + context.fillText(message, absX.toInt(), absY.toInt()) + context.restore() + } + + fun move() { + val relTextWidth = textWidthInPixels / width + if (relX > (1.0 - relTextWidth - relXVelocity.abs) || relX < relXVelocity.abs) { + relXVelocity *= -1 + } + val relTextHeight = textHeightInPixels / height + if (relY > (1.0 - relYVelocity.abs) || relY < relYVelocity.abs + relTextHeight) { + relYVelocity *= -1 + } + relX += relXVelocity + relY += relYVelocity + } + + fun randomVelocity() = 0.03 * Math.random() * (if (Math.random() < 0.5) 1 else -1) + + + val Double.abs : Double + get() = if (this > 0) this else -this +} + +fun renderBackground() { + context.save() + context.fillStyle = "#5C7EED" + context.fillRect(0, 0, width, height) + context.restore() +} + +fun main(args : Array) { + val interval = 50 + // we pass a literal that constructs a new HelloKotlin object + val logos = Array(3) { + HelloKotlin() + } + jq { + setInterval({ + renderBackground() + for (logo in logos) { + logo.draw() + } + }, interval) + } +} + + diff --git a/js/js.translator/testFiles/webDemoCanvasExamples/cases/Traffic light.kt b/js/js.translator/testFiles/webDemoCanvasExamples/cases/Traffic light.kt new file mode 100644 index 00000000000..a21e0b557e8 --- /dev/null +++ b/js/js.translator/testFiles/webDemoCanvasExamples/cases/Traffic light.kt @@ -0,0 +1,466 @@ +import jquery.* +import html5.* +import java.util.ArrayList +import js.setInterval +import js.DomElement +import js.setTimeout +import js.*; + +val PATH_TO_IMAGES = "/static/images/canvas/" + +val state = CanvasState(getCanvas()) +val colors = Colors() + +var trafficLightUp = TrafficLight(v(180.0, 181.0), "up", "red") +var trafficLightDown = TrafficLight(v(100.0, 77.0), "down", "red") +var trafficLightLeft = TrafficLight(v(228.0, 109.0), "left", "green") +var trafficLightRight = TrafficLight(v(55.0, 145.0), "right", "green") + + +fun main(args : Array) { + state.addShape(Map(v(10.0, 10.0))) + + state.addShape(trafficLightLeft) + state.addShape(trafficLightUp) + state.addShape(trafficLightDown) + state.addShape(trafficLightRight) + state.addShape(Car(v(178.0, 205.0), "up", "red")) + state.addShape(Car(v(95.0, 4.0), "down", "white")) + state.addShape(Car(v(278.0, 108.0), "left", "blue")) + state.addShape(Car(v(0.0, 142.0), "right", "black")) + state.addShape(Border()) + state.addShape(Image(PATH_TO_IMAGES + "controls.png", v(380.0, 10.0), v(190.0, 56.0))) + state.addShape(Button(PATH_TO_IMAGES + "lr.png", v(420.0, 70.0), v(120.0, 50.0))) + state.addShape(Button(PATH_TO_IMAGES + "ud.png", v(455.0, 120.0), v(50.0, 120.0))) +} + +fun v(x : Double, y : Double) = Vector(x, y) + +class Image(val src : String, override var pos : Vector, var imageSize : Vector) : Shape() { + override fun draw() { + state.context.drawImage(getImage(src), 0, 0, + imageSize.x.toInt(), imageSize.y.toInt(), + pos.x.toInt(), pos.y.toInt(), + imageSize.x.toInt(), imageSize.y.toInt()) + } + + fun contains(mousePos : Vector) : Boolean = mousePos.isInRect(pos, imageSize) +} + +class Button(val src : String, override var pos : Vector, var imageSize : Vector) : Shape() { + var isMouseOver = false + var isMouseDown = false + + override fun draw() { + if (isMouseOver) { + state.context.shadowed(v(- 3.0, 3.0), 1.2) { + state.context.drawImage(getImage(src), 0, 0, + imageSize.x.toInt(), imageSize.y.toInt(), + pos.x.toInt(), pos.y.toInt(), + imageSize.x.toInt(), imageSize.y.toInt()) + } + } else if (isMouseDown) { + state.context.shadowed(v(- 3.0, 3.0), 0.8) { + state.context.drawImage(getImage(src), 0, 0, + imageSize.x.toInt(), imageSize.y.toInt(), + pos.x.toInt(), pos.y.toInt(), + imageSize.x.toInt(), imageSize.y.toInt()) + } + } else { + state.context.drawImage(getImage(src), 0, 0, + imageSize.x.toInt(), imageSize.y.toInt(), + pos.x.toInt(), pos.y.toInt(), + imageSize.x.toInt(), imageSize.y.toInt()) + } + } + + fun mouseClick() { + isMouseDown = true + setTimeout({ + isMouseDown = false + }, 1000) + } + + fun mouseOver() { + isMouseOver = true + setTimeout({ + isMouseOver = false + }, 1000) + } + + + fun contains(mousePos : Vector) : Boolean = mousePos.isInRect(pos, imageSize) +} + +class Border() : Shape() { + override var pos = Vector(4.0, 4.0); + + override fun draw() { + state.context.fillStyle = colors.white + state.context.fillRect(2, 4, 10, 292) + state.context.fillRect(330, 4, 370, 292) + state.context.fillRect(2, 2, 330, 10) + state.context.fillRect(4, 265, 340, 380) + state.context.strokeStyle = colors.black + state.context.lineWidth = 4.0 + state.context.strokeRect(0, 0, state.width.toInt(), state.height.toInt()) + } + +} + +class Timer(override var pos : Vector) : Shape() { + var timeLeftForChangeColor : Char = 'c' + var timeStartLastChangeColor = Date().getTime(); + var timerLength = 13 + + override fun draw() { + timeLeftForChangeColor = ("" + (timerLength - (Date().getTime() - timeStartLastChangeColor) / 1000)).get(0) + state.context.font = "bold 9px Arial, serif" + state.context.fillStyle = colors.black + state.context.fillText("" + timeLeftForChangeColor, pos.x.toInt(), pos.y.toInt()) + } + + fun resetTimer() { + timeStartLastChangeColor = Date().getTime() + timerLength = 10 + } +} + +//Colors constants +class Colors() { + val black : String = "#000000" + val white = "#FFFFFF" + val grey = "#C0C0C0" + val red = "#EF4137" + val yellow = "#FCE013" + val green = "#0E9648" +} + +class TrafficLight(override var pos : Vector, val direction : String, val startColor : String) : Shape() { + val list = ArrayList() + var size = Vector(27.0, 34.0); + var timer = Timer(Vector(pos.x + 6, pos.y + 12)) + var currentColor = startColor; + var isForceColorChange = false + var changeColorForward = (startColor == "red") + + { + list.add(TrafficLightItem(v(pos.x, pos.y), PATH_TO_IMAGES + "red_color.png")) + list.add(TrafficLightItem(v(pos.x, pos.y), PATH_TO_IMAGES + "yellow_color.png")) + list.add(TrafficLightItem(v(pos.x, pos.y), PATH_TO_IMAGES + "green_color.png")) + list.add(TrafficLightItem(v(pos.x, pos.y), PATH_TO_IMAGES + "green_color_flash.png")) + } + + override fun draw() { + when (currentColor) { + "red" -> list.get(0).draw() + "yellow" -> list.get(1).draw() + "green" -> list.get(2).draw() + "green_flash" -> list.get(3).draw() + else -> { + } + } + timer.draw() + } + + fun setRed() { + if (currentColor != "red" && currentColor != "yellow" && currentColor != "green_flash") { + isForceColorChange = true + changeColor() + } + } + + fun setGreen() { + if (currentColor != "green" && currentColor != "green_flash" && currentColor != "yellow") { + isForceColorChange = true + changeColor() + } + } + + fun changeColor() { + if (changeColorForward) changeColorForward() else changeColorBackward() + } + + fun changeColorForward() { + changeColorForward = false + currentColor = "yellow" + setTimeout({ + if (!isForceColorChange) timer.resetTimer() else isForceColorChange = false + currentColor = "green" + }, 3000) + } + + + fun changeColorBackward() { + changeColorForward = true + currentColor = "green_flash" + setTimeout({ + currentColor = "yellow" + setTimeout({ + if (!isForceColorChange) timer.resetTimer() else isForceColorChange = false + currentColor = "red" + }, 1000) + }, 2000) + } + + fun canMove() : Boolean { + return (currentColor != "red" && currentColor != "yellow") + } +} + + +//One element from Traffic light +class TrafficLightItem(override var pos : Vector, val imageSrc : String) : Shape() { + val relSize : Double = 0.5 + val imageSize = v(33.0, 33.0) + var size : Vector = imageSize * relSize + + var isFlashing = (imageSrc == PATH_TO_IMAGES + "green_color_flash.png") + var isFlashNow = false + var countOfFlash = 0 + + override fun draw() { + size = imageSize * relSize + if (isFlashing) { + if (isFlashNow) { + if (countOfFlash > 6) { + isFlashNow = false + countOfFlash = 0 + } else { + countOfFlash++ + } + } else { + state.context.drawImage(getImage(PATH_TO_IMAGES + "green_color.png"), 0, 0, + imageSize.x.toInt(), imageSize.y.toInt(), + pos.x.toInt(), pos.y.toInt(), + size.x.toInt(), size.y.toInt()) + if (countOfFlash > 6) { + isFlashNow = true + countOfFlash = 0 + } else { + countOfFlash++ + } + } + } else { + state.context.drawImage(getImage(imageSrc), 0, 0, + imageSize.x.toInt(), imageSize.y.toInt(), + pos.x.toInt(), pos.y.toInt(), + size.x.toInt(), size.y.toInt()) + } + } +} + + +class Car(override var pos : Vector, val direction : String, val color : String) : Shape() { + val imageSize = v(25.0, 59.0) + var speed = getRandomArbitary(2, 10); + + override fun draw() { + if (direction == "up" || direction == "down") { + state.context.drawImage(getImage(PATH_TO_IMAGES + color + "_car.png"), 0, 0, + imageSize.x.toInt(), imageSize.y.toInt(), + pos.x.toInt(), pos.y.toInt(), + imageSize.x.toInt(), imageSize.y.toInt()) + if ((!isNearStopLine()) || (trafficLightUp.canMove() && isNearStopLine()) ) { + move() + } else { + speed = getRandomArbitary(2, 10) + } + } else { + state.context.drawImage(getImage(PATH_TO_IMAGES + color + "_car.png"), 0, 0, + imageSize.y.toInt(), imageSize.x.toInt(), + pos.x.toInt(), pos.y.toInt(), + imageSize.y.toInt(), imageSize.x.toInt()) + if ((!isNearStopLine()) || (trafficLightLeft.canMove() && isNearStopLine()) ) { + move() + } else { + speed = getRandomArbitary(2, 10) + } + + } + + } + + fun isNearStopLine() : Boolean { + when (direction) { + "up" -> return (pos.y > 198 && pos.y < 208) + "down" -> return (pos.y > 10 && pos.y < 20) + "right" -> return (pos.x > - 8 && pos.x < 2) + "left" -> return (pos.x > 243 && pos.x < 253) + else -> return false + } + + } + + fun move() { + var x = pos.x + var y = pos.y + + when (direction) { + "up" -> if (pos.y < - 50) y = 250.0 else y = pos.y - speed + "down" -> if (pos.y > 300) y = 0.0 else y = pos.y + speed + "right" -> if (pos.x > 300) x = - 10.0 else x = pos.x + speed + "left" -> if (pos.x < - 50) x = 340.0 else x = pos.x - speed + else -> { + } + } + + pos = v(x, y) + } +} + +class Map(override var pos : Vector) : Shape() { + val relSize : Double = 0.8 + val imageSize = v(420.0, 323.0) + var size : Vector = imageSize * relSize + + override fun draw() { + size = imageSize * relSize + state.context.drawImage(getImage(PATH_TO_IMAGES + "crossroads.jpg"), 0, 0, + imageSize.x.toInt(), imageSize.y.toInt(), + pos.x.toInt(), pos.y.toInt(), + size.x.toInt(), size.y.toInt()) + } +} + + +class CanvasState(val canvas : Canvas) { + val context = getContext() + var shapes = ArrayList() + + var width = canvas.width.toDouble() + var height = canvas.height.toDouble() + + val size : Vector + get() = v(width, height) + + fun addShape(shape : Shape) { + shapes.add(shape) + } + + { + jq(canvas).click { + val mousePos = mousePos(it) + for (shape in shapes) { + if (shape is Button && mousePos in shape) { + val name = shape.src + shape.mouseClick() + when (name) { + PATH_TO_IMAGES + "lr.png" -> { + + trafficLightUp.setRed() + trafficLightDown.setRed() + trafficLightLeft.setGreen() + trafficLightRight.setGreen() + } + PATH_TO_IMAGES + "ud.png" -> { + + trafficLightLeft.setRed() + trafficLightRight.setRed() + trafficLightUp.setGreen() + trafficLightDown.setGreen() + + } + else -> continue + } + + } + } + } + + jq(canvas).mousemove { + val mousePos = mousePos(it) + for (shape in shapes) { + if (shape is Button && mousePos in shape) { + val name = shape.src + shape.mouseOver() + } + } + } + + setInterval({ + draw() + }, 1000 / 30) + + setInterval({ + trafficLightUp.changeColor() + trafficLightLeft.changeColor() + trafficLightRight.changeColor() + trafficLightDown.changeColor() + }, 10000) + + + } + + fun mousePos(e : MouseEvent) : Vector { + var offset = Vector() + var element : DomElement? = canvas + while (element != null) { + val el : DomElement = element.sure() + offset += Vector(el.offsetLeft, el.offsetTop) + element = el.offsetParent + } + return v(e.pageX, e.pageY) - offset + } + + fun draw() { + clear() + for (shape in shapes) { + shape.draw() + } + } + + fun clear() { + context.fillStyle = colors.white + context.fillRect(0, 0, width.toInt(), height.toInt()) + } +} + +abstract class Shape() { + abstract var pos : Vector + abstract fun draw() + + // a couple of helper extension methods we'll be using in the derived classes + fun Context.shadowed(shadowOffset : Vector, alpha : Double, render : Context.() -> Unit) { + save() + shadowColor = "rgba(100, 100, 100, $alpha)" + shadowBlur = 5.0 + shadowOffsetX = shadowOffset.x + shadowOffsetY = shadowOffset.y + render() + restore() + } + + fun Context.fillPath(constructPath : Context.() -> Unit) { + beginPath() + constructPath() + closePath() + fill() + } + +} + +class Vector(val x : Double = 0.0, val y : Double = 0.0) { + fun plus(v : Vector) = v(x + v.x, y + v.y) + fun minus() = v(- x, - y) + fun minus(v : Vector) = v(x - v.x, y - v.y) + fun times(koef : Double) = v(x * koef, y * koef) + fun distanceTo(v : Vector) = Math.sqrt((this - v).sqr) + fun rotatedBy(theta : Double) : Vector { + val sin = Math.sin(theta) + val cos = Math.cos(theta) + return v(x * cos - y * sin, x * sin + y * cos) + } + + fun isInRect(topLeft : Vector, size : Vector) = (x >= topLeft.x) && (x <= topLeft.x + size.x) && + (y >= topLeft.y) && (y <= topLeft.y + size.y) + + val sqr : Double + get() = x * x + y * y + val normalized : Vector + get() = this * (1.0 / Math.sqrt(sqr)) +} + +fun getRandomArbitary(val min : Int, val max : Int) : Double { + return Math.random() * (max - min) + min; +}