copied emulator logic to main server, add parsing cl args, add JSAP lib

This commit is contained in:
MaximZaitsev
2016-09-07 17:39:39 +03:00
parent c7a0f0760d
commit ba70a330f3
20 changed files with 428 additions and 42 deletions
+3
View File
@@ -46,12 +46,15 @@ jar {
"Main-Class": "ServerMainKt") "Main-Class": "ServerMainKt")
} }
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
} }
dependencies { dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile group: 'org.asynchttpclient', name: 'async-http-client', version: '2.0.0-RC9' compile group: 'org.asynchttpclient', name: 'async-http-client', version: '2.0.0-RC9'
compile group: "com.martiansoftware", name: "jsap", version: "2.1"
compile files ("../proto/compiler/build/protokot-runtime.jar") compile files ("../proto/compiler/build/protokot-runtime.jar")
} }
+9
View File
@@ -0,0 +1,9 @@
<room>
<walls>
<wall startX="200" startY="300" endX="-150" endY="300"/>
<wall startX="-150" startY="300" endX="-150" endY="-45"/>
<wall startX="-150" startY="-45" endX="200" endY="-45"/>
<wall startX="200" startY="-45" endX="200" endY="300"/>
</walls>
</room>
+33
View File
@@ -0,0 +1,33 @@
import com.martiansoftware.jsap.FlaggedOption
import com.martiansoftware.jsap.JSAP
import com.martiansoftware.jsap.StringParser
import com.martiansoftware.jsap.Switch
fun setClOptions(clParser: JSAP) {
val optEmulator = Switch("emulator", 'e', "emulator")
val optUseRandom = Switch("random", 'r', "random")
val optSeedForRandom = getOption("seed", JSAP.LONG_PARSER, "1111", 's', "seed")
val optTestRoom = getOption("test room", JSAP.STRING_PARSER, "", null, "room")
val optHelp = getOption("help", JSAP.BOOLEAN_PARSER, "false", null, "help")
clParser.registerParameter(optEmulator)
clParser.registerParameter(optUseRandom)
clParser.registerParameter(optSeedForRandom)
clParser.registerParameter(optTestRoom)
clParser.registerParameter(optHelp)
}
fun getOption(name: String, parser: StringParser, default: String, shortFlag: Char?, longFlag: String, required: Boolean = false): FlaggedOption {
val result = FlaggedOption(name)
.setStringParser(parser)
.setDefault(default)
.setRequired(required)
if (shortFlag != null) {
result.setShortFlag(shortFlag)
}
if (!longFlag.equals("")) {
result.setLongFlag(longFlag)
}
return result
}
+32 -4
View File
@@ -1,11 +1,36 @@
import roomScanner.CarController
import roomScanner.RoomScanner
import clInterface.DebugClInterface import clInterface.DebugClInterface
import com.martiansoftware.jsap.JSAP
import net.car.Dropper import net.car.Dropper
import net.car.client.Client import net.car.client.Client
import objects.CarReal
import objects.Environment import objects.Environment
import objects.emulator.CarEmulator
import objects.emulator.EmulatedRoom
import objects.emulator.Rng
import roomScanner.CarController
import roomScanner.RoomScanner
fun main(args: Array<String>) { fun main(args: Array<String>) {
val clParser = JSAP()
setClOptions(clParser)
val argsConfig = clParser.parse(args)
if (!argsConfig.success() || argsConfig.getBoolean("help")) {
println(clParser.getHelp())
return
}
if (argsConfig.getBoolean("emulator")) {
val pathToRoomConfig = argsConfig.getString("test room")
val randomSeed = argsConfig.getLong("seed")
val useRandom = argsConfig.getBoolean("random")
val carUid = 1
val emulatedRoom = EmulatedRoom.EmulatedRoomFromFile(pathToRoomConfig)
if (emulatedRoom == null) {
println("error parsin room from file $pathToRoomConfig")
return
}
Environment.map.put(carUid, CarEmulator(carUid, emulatedRoom, useRandom, Rng(randomSeed)))
}
var roomScanner: RoomScanner? = null var roomScanner: RoomScanner? = null
val carServer = net.car.server.Server.createCarServerThread() val carServer = net.car.server.Server.createCarServerThread()
val webServer = net.web.server.Server.createWebServerThread() val webServer = net.web.server.Server.createWebServerThread()
@@ -16,8 +41,10 @@ fun main(args: Array<String>) {
if (args.contains("--scan")) { if (args.contains("--scan")) {
Environment.onCarConnect { car -> Environment.onCarConnect { car ->
roomScanner = RoomScanner(CarController(car)) if (car is CarReal) {
roomScanner!!.start() roomScanner = RoomScanner(CarController(car))
roomScanner!!.start()
}
} }
} }
@@ -29,4 +56,5 @@ fun main(args: Array<String>) {
Client.shutDownClient() Client.shutDownClient()
roomScanner?.interrupt() roomScanner?.interrupt()
} }
@@ -7,6 +7,7 @@ import SonarResponse
import algorithm.geometry.Angle import algorithm.geometry.Angle
import algorithm.geometry.AngleData import algorithm.geometry.AngleData
import objects.Car import objects.Car
import objects.CarReal
import roomScanner.CarController.Direction.* import roomScanner.CarController.Direction.*
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -6,6 +6,7 @@ import DebugResponseMemoryStats
import DebugResponseSonarStats import DebugResponseSonarStats
import roomScanner.serialize import roomScanner.serialize
import net.car.client.Client import net.car.client.Client
import objects.CarReal
import objects.Environment import objects.Environment
import java.rmi.UnexpectedException import java.rmi.UnexpectedException
@@ -14,6 +15,9 @@ class DebugInformation : CommandExecutor {
override fun execute(command: String) { override fun execute(command: String) {
val params = command.split(" ") val params = command.split(" ")
val car = Environment.map[params[1].toInt()]!! val car = Environment.map[params[1].toInt()]!!
if (!(car is CarReal)) {
return
}
val type = DebugRequest.Type.fromIntToType(params[2].toInt()) val type = DebugRequest.Type.fromIntToType(params[2].toInt())
val request = DebugRequest.BuilderDebugRequest(type).build() val request = DebugRequest.BuilderDebugRequest(type).build()
@@ -5,6 +5,7 @@ import roomScanner.serialize
import SonarExploreAngleRequest import SonarExploreAngleRequest
import SonarExploreAngleResponse import SonarExploreAngleResponse
import net.car.client.Client import net.car.client.Client
import objects.CarReal
import objects.Environment import objects.Environment
class Explore : CommandExecutor { class Explore : CommandExecutor {
@@ -12,6 +13,9 @@ class Explore : CommandExecutor {
override fun execute(command: String) { override fun execute(command: String) {
val params = command.split(" ") val params = command.split(" ")
val car = Environment.map[params[1].toInt()]!! val car = Environment.map[params[1].toInt()]!!
if (!(car is CarReal)) {
return
}
val angle = params[2].toInt() val angle = params[2].toInt()
val window = params[3].toInt() val window = params[3].toInt()
@@ -4,6 +4,7 @@ import CodedOutputStream
import RouteMetricRequest import RouteMetricRequest
import net.car.client.Client import net.car.client.Client
import objects.Car import objects.Car
import objects.CarReal
import objects.Environment import objects.Environment
import java.net.ConnectException import java.net.ConnectException
@@ -39,7 +40,11 @@ class RouteMetric : CommandExecutor {
val requestBytes = ByteArray(routeMessage.getSizeNoTag()) val requestBytes = ByteArray(routeMessage.getSizeNoTag())
routeMessage.writeTo(CodedOutputStream(requestBytes)) routeMessage.writeTo(CodedOutputStream(requestBytes))
try { try {
car.carConnection.sendRequest(Client.Request.ROUTE_METRIC, requestBytes) routeMessage.distances.forEachIndexed { idx, distance ->
val direction = roomScanner.CarController.Direction.values()
.filter { it.id == routeMessage.directions[idx] }.first()
car.moveCar(distance, direction).get()
}
} catch (e: ConnectException) { } catch (e: ConnectException) {
synchronized(Environment, { synchronized(Environment, {
Environment.map.remove(id) Environment.map.remove(id)
@@ -2,7 +2,6 @@ package clInterface.executor
import CodedOutputStream import CodedOutputStream
import SonarRequest import SonarRequest
import net.car.client.Client
import objects.Car import objects.Car
import objects.Environment import objects.Environment
import java.net.ConnectException import java.net.ConnectException
@@ -35,7 +34,7 @@ class Sonar : CommandExecutor {
val requestBytes = ByteArray(requestMessage.getSizeNoTag()) val requestBytes = ByteArray(requestMessage.getSizeNoTag())
requestMessage.writeTo(CodedOutputStream(requestBytes)) requestMessage.writeTo(CodedOutputStream(requestBytes))
try { try {
car.carConnection.sendRequest(Client.Request.SONAR, requestBytes) car.scan(requestMessage.angles, requestMessage.attempts.first(), requestMessage.windowSize, requestMessage.smoothing)
} catch (e: ConnectException) { } catch (e: ConnectException) {
synchronized(Environment, { synchronized(Environment, {
Environment.map.remove(id) Environment.map.remove(id)
+4
View File
@@ -1,6 +1,7 @@
package net.car package net.car
import net.car.client.Client import net.car.client.Client
import objects.CarReal
import objects.Environment import objects.Environment
import java.net.ConnectException import java.net.ConnectException
import kotlin.concurrent.thread import kotlin.concurrent.thread
@@ -34,6 +35,9 @@ object Dropper {
for (key in environment.map.keys) { for (key in environment.map.keys) {
try { try {
val carValue = environment.map[key] ?: continue val carValue = environment.map[key] ?: continue
if (!(carValue is CarReal)) {
continue
}
carValue.carConnection.sendRequest(Client.Request.PING, ByteArray(0)) carValue.carConnection.sendRequest(Client.Request.PING, ByteArray(0))
} catch (e: ConnectException) { } catch (e: ConnectException) {
environment.map.remove(key) environment.map.remove(key)
+4 -31
View File
@@ -1,44 +1,17 @@
package objects package objects
import CodedOutputStream
import RouteMetricRequest
import SonarRequest import SonarRequest
import net.car.client.Client
import org.asynchttpclient.ListenableFuture import org.asynchttpclient.ListenableFuture
import org.asynchttpclient.Response import org.asynchttpclient.Response
import roomScanner.CarController.Direction import roomScanner.CarController
import roomScanner.serialize
class Car constructor(val uid: Int, host: String, port: Int) { abstract class Car(val uid: Int) {
private val CHARGE_CORRECTION = 1.0//on full charge ok is 0.83 - 0.86
var x = 0.0 var x = 0.0
var y = 0.0 var y = 0.0
var angle = 0.0 var angle = 0.0
val carConnection = CarConnection(host, port)
fun moveCar(distance: Int, direction: Direction): ListenableFuture<Response> { abstract fun moveCar(distance: Int, direction: CarController.Direction): ListenableFuture<Response>
val route = RouteMetricRequest.BuilderRouteMetricRequest( abstract fun scan(angles: IntArray, attempts: Int, windowSize: Int, smoothing: SonarRequest.Smoothing): ListenableFuture<Response>
IntArray(1, { (distance * CHARGE_CORRECTION).toInt() }), IntArray(1, { direction.id }))
val bytesRoute = ByteArray(route.getSizeNoTag())
route.writeTo(CodedOutputStream(bytesRoute))
return carConnection.sendRequest(Client.Request.ROUTE_METRIC, bytesRoute)
}
fun scan(angles: IntArray, attempts: Int, windowSize: Int, smoothing: SonarRequest.Smoothing): ListenableFuture<Response> {
val message = SonarRequest.BuilderSonarRequest(
angles = angles,
attempts = IntArray(angles.size, { attempts }),
smoothing = smoothing,
windowSize = windowSize)
.build()
val data = serialize(message.getSizeNoTag(), { message.writeTo(it) })
return carConnection.sendRequest(Client.Request.SONAR, data)
}
override fun toString(): String {
return "$uid ; x:$x; y:$y; target:$angle"
}
} }
+40
View File
@@ -0,0 +1,40 @@
package objects
import CodedOutputStream
import RouteMetricRequest
import SonarRequest
import net.car.client.Client
import org.asynchttpclient.ListenableFuture
import org.asynchttpclient.Response
import roomScanner.CarController.Direction
import roomScanner.serialize
class CarReal(uid: Int, host: String, port: Int) : Car(uid) {
private val CHARGE_CORRECTION = 1.0//on full charge ok is 0.83 - 0.86
val carConnection = CarConnection(host, port)
override fun moveCar(distance: Int, direction: Direction): ListenableFuture<Response> {
val route = RouteMetricRequest.BuilderRouteMetricRequest(
IntArray(1, { (distance * CHARGE_CORRECTION).toInt() }), IntArray(1, { direction.id }))
val bytesRoute = ByteArray(route.getSizeNoTag())
route.writeTo(CodedOutputStream(bytesRoute))
return carConnection.sendRequest(Client.Request.ROUTE_METRIC, bytesRoute)
}
override fun scan(angles: IntArray, attempts: Int, windowSize: Int, smoothing: SonarRequest.Smoothing): ListenableFuture<Response> {
val message = SonarRequest.BuilderSonarRequest(
angles = angles,
attempts = IntArray(angles.size, { attempts }),
smoothing = smoothing,
windowSize = windowSize)
.build()
val data = serialize(message.getSizeNoTag(), { message.writeTo(it) })
return carConnection.sendRequest(Client.Request.SONAR, data)
}
override fun toString(): String {
return "$uid ; x:$x; y:$y; target:$angle"
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ object Environment {
@Synchronized @Synchronized
fun connectCar(host: String, port: Int): Int { fun connectCar(host: String, port: Int): Int {
uid++ uid++
val car = Car(uid, host, port) val car = CarReal(uid, host, port)
onConnect.forEach { it(car) } onConnect.forEach { it(car) }
map.put(uid, car) map.put(uid, car)
return uid return uid
@@ -0,0 +1,130 @@
package objects.emulator
import SonarRequest
import SonarResponse
import algorithm.geometry.Angle
import algorithm.geometry.Line
import algorithm.geometry.Vector
import objects.Car
import org.asynchttpclient.ListenableFuture
import org.asynchttpclient.Response
import roomScanner.CarController
import roomScanner.CarController.Direction.*
import roomScanner.serialize
class CarEmulator(uid: Int, val testRoom: EmulatedRoom, val useRandom: Boolean, val randomGenerator: Rng) : Car(uid) {
private var xReal = 0
private var yReal = 0
private var angleReal = Angle(0)
override fun moveCar(distance: Int, direction: CarController.Direction): ListenableFuture<Response> {
val coef = if (useRandom) randomGenerator.nextInt(900, 1100).toDouble() / 1000.0 else 1.0
val distanceWithRandom = (coef * distance).toInt()
when (direction) {
FORWARD -> {
xReal += (Math.cos(angleReal.rads()) * distanceWithRandom).toInt()
yReal += (Math.sin(angleReal.rads()) * distanceWithRandom).toInt()
}
BACKWARD -> {
xReal -= (Math.cos(angleReal.rads()) * distanceWithRandom).toInt()
yReal -= (Math.sin(angleReal.rads()) * distanceWithRandom).toInt()
}
LEFT -> angleReal += Angle(distanceWithRandom)
RIGHT -> angleReal -= Angle(distanceWithRandom)
}
println("x=$xReal y=$yReal angle=${angleReal.degs()}")
return ListenableFutureImpl(ByteArray(0))
}
override fun scan(angles: IntArray, attempts: Int, windowSize: Int, smoothing: SonarRequest.Smoothing): ListenableFuture<Response> {
val xSensor0 = xReal.toInt()
val ySensor0 = yReal.toInt()
val carAngle = angleReal
val distances = arrayListOf<Int>()
angles.forEach { angle ->
val angleFinal = getSensorAngle(angle, carAngle.degs())
val xSensor1: Int
val ySensor1: Double
//tg can be equal to inf if angle = 90 or 270. it vertical line. x1 = x0, y1 = y0+-[any number. eg 1]
when (angleFinal) {
90 -> {
xSensor1 = xSensor0
ySensor1 = (ySensor0 + 1).toDouble()
}
270 -> {
xSensor1 = xSensor0
ySensor1 = (ySensor0 - 1).toDouble()
}
in (90..270) -> {
xSensor1 = xSensor0 - 1
ySensor1 = ySensor0 + (xSensor1 - xSensor0) * Math.tan(angleFinal * Math.PI / 180)
}
else -> {
xSensor1 = xSensor0 + 1
ySensor1 = ySensor0 + (xSensor1 - xSensor0) * Math.tan(angleFinal * Math.PI / 180)
}
}
val sensorLine = Line(ySensor0 - ySensor1, xSensor1.toDouble() - xSensor0,
xSensor0 * ySensor1 - ySensor0 * xSensor1)
val sensorVector = Vector(xSensor0.toDouble(), ySensor0.toDouble(), xSensor1.toDouble(), ySensor1)
val distance = getDistance(xSensor0, ySensor0, sensorLine, sensorVector)
if (distance == -1) {
distances.add(distance)
} else {
val delta = if (useRandom) randomGenerator.nextInt(-2, 2) else 0//return one of -2 -1 0 1 or 2
distances.add(distance + delta)
}
}
val responseMessage = SonarResponse.BuilderSonarResponse(distances.toIntArray()).build()
val bytes = serialize(responseMessage.getSizeNoTag(), { responseMessage.writeTo(it) })
return ListenableFutureImpl(bytes)
}
private fun getDistance(xSensor0: Int, ySensor0: Int, sensorLine: Line, sensorVector: Vector): Int {
var result = Long.MAX_VALUE
for (wall in testRoom.emulatedWalls) {
val wallLine = wall.line
val slope = sensorLine.A * wallLine.B - sensorLine.B * wallLine.A
if (Math.abs(slope) < 0.01) {
//line is parallel.
continue
}
val xIntersection = (sensorLine.B * wallLine.C - wallLine.B * sensorLine.C) / slope
val yIntersection = (sensorLine.C * wallLine.A - wallLine.C * sensorLine.A) / slope
//filters by direction and intersection position
val intersectionVector = Vector(xSensor0.toDouble(), ySensor0.toDouble(), xIntersection, yIntersection)
if (intersectionVector.scalarProduct(sensorVector) < 0) {
continue
}
val wallVector1 = Vector(xIntersection, yIntersection, wall.xTo.toDouble(), wall.yTo.toDouble())
val wallVector2 = Vector(xIntersection, yIntersection, wall.xFrom.toDouble(), wall.yFrom.toDouble())
if (wallVector1.scalarProduct(wallVector2) > 0) {
continue
}
val currentDistance = Math.round(Math.sqrt(Math.pow(xIntersection - xSensor0, 2.0)
+ Math.pow(yIntersection - ySensor0, 2.0)))
if (currentDistance < result) {
result = currentDistance
}
}
return result.toInt()
}
private fun getSensorAngle(requestAngle: Int, carAngle: Int): Int {
var angleTmp = carAngle - requestAngle
while (angleTmp < 0) {
angleTmp += 360
}
return angleTmp % 360
}
}
@@ -0,0 +1,44 @@
package objects.emulator
import org.w3c.dom.Node
import java.io.File
import javax.xml.parsers.DocumentBuilderFactory
class EmulatedRoom() {
val emulatedWalls = arrayListOf<EmulatedWall>()
constructor(EmulatedWalls: Collection<EmulatedWall>) : this() {
this.emulatedWalls.addAll(EmulatedWalls)
}
companion object {
fun EmulatedRoomFromFile(pathToFile: String): EmulatedRoom? {
val factory = DocumentBuilderFactory.newInstance()
val builder = factory.newDocumentBuilder()
val doc = builder.parse(File(pathToFile))
val root = doc.getDocumentElement()
val rootChild = root.childNodes
var walls: Node? = null
for (i in 0..rootChild.length - 1) {
val node = rootChild.item(i)
if (node.nodeType == Node.ELEMENT_NODE) {
walls = node
break
}
}
walls ?: return null
val wallsChild = walls.childNodes
val result = EmulatedRoom()
for (i in 0..wallsChild.length - 1) {
val wall = wallsChild.item(i)
if (wall.nodeType != Node.ELEMENT_NODE) {
continue
}
val EmulatedWall = EmulatedWall.wallFromXml(wall) ?: return null
result.emulatedWalls.add(EmulatedWall)
}
return result
}
}
}
@@ -0,0 +1,27 @@
package objects.emulator
import algorithm.geometry.Line
import org.w3c.dom.Node
class EmulatedWall constructor(val line: Line, val xFrom: Int, val xTo: Int, val yFrom: Int, val yTo: Int) {
companion object {
fun wallFromXml(wall: Node): EmulatedWall? {
val points = wall.attributes
val startX: Int
val endX: Int
val endY: Int
val startY: Int
try {
startX = points.getNamedItem("startX").nodeValue.toInt()
startY = points.getNamedItem("startY").nodeValue.toInt()
endX = points.getNamedItem("endX").nodeValue.toInt()
endY = points.getNamedItem("endY").nodeValue.toInt()
} catch (e: NumberFormatException) {
return null
}
val line = Line((startY - endY).toDouble(), (endX - startX).toDouble(), (startX * endY - startY * endX).toDouble())
return EmulatedWall(line, startX, endX, startY, endY)
}
}
}
@@ -0,0 +1,54 @@
package objects.emulator
import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled
import org.asynchttpclient.ListenableFuture
import org.asynchttpclient.Response
import org.asynchttpclient.netty.EagerResponseBodyPart
import org.asynchttpclient.netty.NettyResponse
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
class ListenableFutureImpl(bytes: ByteArray) : ListenableFuture<Response> {
val response = NettyResponse(null, null, listOf(EagerResponseBodyPart(Unpooled.copiedBuffer(bytes), true)))
override fun done() {
}
override fun cancel(p0: Boolean): Boolean {
return false
}
override fun touch() {
}
override fun get(): Response {
return response
}
override fun get(p0: Long, p1: TimeUnit): Response {
return response
}
override fun addListener(listener: Runnable?, exec: Executor?): ListenableFuture<Response> {
return this
}
override fun abort(t: Throwable?) {
}
override fun isDone(): Boolean {
return true
}
override fun toCompletableFuture(): CompletableFuture<Response> {
throw UnsupportedOperationException("not implemented")
}
override fun isCancelled(): Boolean {
return false
}
}
@@ -0,0 +1,28 @@
package objects.emulator
class Rng(seed: Long) {
var curState = seed
val a = 1664525L
val c = 1013904223L
val mod = 2147483648L
fun abs(value: Long): Long {
if (value < 0)
return -value
return value
}
fun nextInt(): Int {
val res = curState
curState = abs(a * curState + c) % mod
return (res % mod).toInt()
}
fun nextInt(min_val: Int, max_val: Int): Int {
val rand = nextInt()
val size = max_val - min_val + 1
val randInRange = rand % size
val res = randInRange + min_val
return res
}
}
@@ -5,9 +5,9 @@ import RouteMetricRequest
import SonarRequest import SonarRequest
import SonarResponse import SonarResponse
import net.car.client.Client import net.car.client.Client
import objects.Car import objects.CarReal
class CarController(var car: Car) { class CarController(var car: CarReal) {
enum class Direction(val id: Int) { enum class Direction(val id: Int) {
FORWARD(0), FORWARD(0),
BACKWARD(1), BACKWARD(1),
@@ -6,7 +6,7 @@ class RoomScanner(val controller: CarController) : Thread() {
private val GHOST_LEVEL = 50 private val GHOST_LEVEL = 50
override fun run() { override fun run() {
println("Car connected ${controller.car.carConnection.host}") println("CarReal connected ${controller.car.carConnection.host}")
for (i in 1..100) { for (i in 1..100) {
step() step()
println(plot(points)) println(plot(points))