diff --git a/.classpath b/VirusBroadcast_by_Java/.classpath similarity index 100% rename from .classpath rename to VirusBroadcast_by_Java/.classpath diff --git a/.project b/VirusBroadcast_by_Java/.project similarity index 100% rename from .project rename to VirusBroadcast_by_Java/.project diff --git a/src/Bed.java b/VirusBroadcast_by_Java/src/Bed.java similarity index 100% rename from src/Bed.java rename to VirusBroadcast_by_Java/src/Bed.java diff --git a/src/City.java b/VirusBroadcast_by_Java/src/City.java similarity index 100% rename from src/City.java rename to VirusBroadcast_by_Java/src/City.java diff --git a/src/Constants.java b/VirusBroadcast_by_Java/src/Constants.java similarity index 100% rename from src/Constants.java rename to VirusBroadcast_by_Java/src/Constants.java diff --git a/src/Hospital.java b/VirusBroadcast_by_Java/src/Hospital.java similarity index 100% rename from src/Hospital.java rename to VirusBroadcast_by_Java/src/Hospital.java diff --git a/src/Main.java b/VirusBroadcast_by_Java/src/Main.java similarity index 100% rename from src/Main.java rename to VirusBroadcast_by_Java/src/Main.java diff --git a/src/MathUtil.java b/VirusBroadcast_by_Java/src/MathUtil.java similarity index 96% rename from src/MathUtil.java rename to VirusBroadcast_by_Java/src/MathUtil.java index 60021b9..ba592e4 100644 --- a/src/MathUtil.java +++ b/VirusBroadcast_by_Java/src/MathUtil.java @@ -1,38 +1,38 @@ -import java.util.Random; - -/** - * 数学算法工具类 - * - * @ClassName: MathUtil - * @Description: 数学算法工具类 - * @author: Bruce Young - * @date: 2020年02月06日 11:27 - */ -public class MathUtil { - /** - * 仅仅使用一个随机数生成器 - */ - private static final Random randomGen = new Random(); - - /** - * 标准正态分布化 - *
- * 流动意愿标准化后判断是在0的左边还是右边从而决定是否流动。 - *
- * 设X随机变量为服从正态分布,sigma是影响分布形态的系数 u值决定正态分布均值 - *
- *
- * 推导: - * StdX = (X-u)/sigma - * X = sigma * StdX + u - * - * @param sigma 正态标准差sigma值 - * @param u 正态均值参数mu - * @return - */ - public static double stdGaussian(double sigma, double u) { - double X = randomGen.nextGaussian(); - return sigma * X + u; - } - -} +import java.util.Random; + +/** + * 数学算法工具类 + * + * @ClassName: MathUtil + * @Description: 数学算法工具类 + * @author: Bruce Young + * @date: 2020年02月06日 11:27 + */ +public class MathUtil { + /** + * 仅仅使用一个随机数生成器 + */ + private static final Random randomGen = new Random(); + + /** + * 标准正态分布化 + *
+ * 流动意愿标准化后判断是在0的左边还是右边从而决定是否流动。 + *
+ * 设X随机变量为服从正态分布,sigma是影响分布形态的系数 u值决定正态分布均值 + *
+ *
+ * 推导:
+ * StdX = (X-u)/sigma
+ * X = sigma * StdX + u
+ *
+ * @param sigma 正态标准差sigma值
+ * @param u 正态均值参数mu
+ * @return
+ */
+ public static double stdGaussian(double sigma, double u) {
+ double X = randomGen.nextGaussian();
+ return sigma * X + u;
+ }
+
+}
diff --git a/src/MoveTarget.java b/VirusBroadcast_by_Java/src/MoveTarget.java
similarity index 100%
rename from src/MoveTarget.java
rename to VirusBroadcast_by_Java/src/MoveTarget.java
diff --git a/src/MyPanel.java b/VirusBroadcast_by_Java/src/MyPanel.java
similarity index 100%
rename from src/MyPanel.java
rename to VirusBroadcast_by_Java/src/MyPanel.java
diff --git a/src/Person.java b/VirusBroadcast_by_Java/src/Person.java
similarity index 100%
rename from src/Person.java
rename to VirusBroadcast_by_Java/src/Person.java
diff --git a/src/PersonPool.java b/VirusBroadcast_by_Java/src/PersonPool.java
similarity index 100%
rename from src/PersonPool.java
rename to VirusBroadcast_by_Java/src/PersonPool.java
diff --git a/src/Point.java b/VirusBroadcast_by_Java/src/Point.java
similarity index 100%
rename from src/Point.java
rename to VirusBroadcast_by_Java/src/Point.java
diff --git a/src/Virus.java b/VirusBroadcast_by_Java/src/Virus.java
similarity index 100%
rename from src/Virus.java
rename to VirusBroadcast_by_Java/src/Virus.java
diff --git a/VirusBroadcast_by_Kotlin/.idea/VirusBroadcast_by_Kotlin.iml b/VirusBroadcast_by_Kotlin/.idea/VirusBroadcast_by_Kotlin.iml
new file mode 100644
index 0000000..245d342
--- /dev/null
+++ b/VirusBroadcast_by_Kotlin/.idea/VirusBroadcast_by_Kotlin.iml
@@ -0,0 +1,12 @@
+
+
+ * 流动意愿标准化后判断是在0的左边还是右边从而决定是否流动。
+ *
+ * 设X随机变量为服从正态分布,sigma是影响分布形态的系数 u值决定正态分布均值
+ *
+ *
+ * 推导:
+ * StdX = (X-u)/sigma
+ * X = sigma * StdX + u
+ *
+ * @param sigma 正态标准差sigma值
+ * @param u 正态均值参数mu
+ * @return
+ */
+ fun stdGaussian(sigma:Double,u:Double) = sigma*randomGen.nextGaussian()+u
+ }
+}
\ No newline at end of file
diff --git a/VirusBroadcast_by_Kotlin/src/MyPanel.kt b/VirusBroadcast_by_Kotlin/src/MyPanel.kt
new file mode 100644
index 0000000..3d3bad6
--- /dev/null
+++ b/VirusBroadcast_by_Kotlin/src/MyPanel.kt
@@ -0,0 +1,85 @@
+import javax.swing.*
+import java.awt.*
+import java.util.Timer
+import java.util.TimerTask
+
+/**
+ * 主面板。
+ *
+ * @ClassName: MyPanel
+ * @Description: 主面板
+ * @author: Bruce Young
+ * @date: 2020年02月02日 17:03
+ */
+class MyPanel : JPanel(), Runnable {
+ val timer = Timer()
+ companion object {
+ var worldTime = 0//世界时间
+ }
+ init {
+ this.background = Color(0x444444)
+ }
+ internal inner class MyTimerTask : TimerTask() {
+ override fun run() {
+ this@MyPanel.repaint()
+ worldTime++
+ }
+ }
+ override fun run() {
+ timer.schedule(MyTimerTask(), 0, 100)//启动世界计时器,时间开始流动(突然脑补DIO台词:時は停た)
+ }
+
+ override fun paint(g: Graphics?) {
+ super.paint(g)
+ g!!.color = Color(0x00ff00)//设置医院边界颜色
+ //绘制医院边界
+ g.drawRect(Hospital.hospital.x, Hospital.hospital.y,
+ Hospital.hospital.width, Hospital.hospital.height)
+ g.font = Font("微软雅黑", Font.BOLD, 16)
+ g.color = Color(0x00ff00)
+ g.drawString("医院", Hospital.hospital.x + Hospital.hospital.width / 4, Hospital.hospital.y - 16)
+ //绘制代表人类的圆点
+ val people = PersonPool.personPool.personList
+// if (people.size == 0) return
+ for (person in people) {
+ when (person.state) {
+ Person.State.NORMAL -> g.color = Color(0xdddddd) //健康人
+ Person.State.SHADOW -> g.color = Color(0xffee00) //潜伏期感染者
+ Person.State.CONFIRMED -> g.color = Color(0xff0000) //确诊患者
+ Person.State.FREEZE -> g.color = Color(0x48FFFC) //已隔离者
+ Person.State.DEATH -> g.color = Color(0x000000) //死亡患者
+ }
+ person.update()//对各种状态的市民进行不同的处理
+ g.fillOval(person.x, person.y, 3, 3)
+ }
+
+ val captionStartOffsetX = 700 + Hospital.hospital.width + 40
+ val captionStartOffsetY = 40
+ val captionSize = 24
+
+ //显示数据信息
+ g.color = Color.WHITE
+ g.drawString("城市总人数:" + Constants.CITY_PERSON_SIZE, captionStartOffsetX, captionStartOffsetY)
+ g.color = Color(0xdddddd)
+ g.drawString("健康者人数:" + PersonPool.personPool.getPeopleSize(Person.State.NORMAL), captionStartOffsetX, captionStartOffsetY + captionSize)
+ g.color = Color(0xffee00)
+ g.drawString("潜伏期人数:" + PersonPool.personPool.getPeopleSize(Person.State.SHADOW), captionStartOffsetX, captionStartOffsetY + 2 * captionSize)
+ g.color = Color(0xff0000)
+ g.drawString("发病者人数:" + PersonPool.personPool.getPeopleSize(Person.State.CONFIRMED), captionStartOffsetX, captionStartOffsetY + 3 * captionSize)
+ g.color = Color(0x48FFFC)
+ g.drawString("已隔离人数:" + PersonPool.personPool.getPeopleSize(Person.State.FREEZE), captionStartOffsetX, captionStartOffsetY + 4 * captionSize)
+ g.color = Color(0x00ff00)
+ g.drawString("空余病床:" + Math.max(Constants.BED_COUNT - PersonPool.personPool.getPeopleSize(Person.State.FREEZE), 0), captionStartOffsetX, captionStartOffsetY + 5 * captionSize)
+ g.color = Color(0xE39476)
+ //暂定急需病床数量为 NEED = 确诊发病者数量 - 已隔离住院数量
+ val needBeds = PersonPool.personPool.getPeopleSize(Person.State.CONFIRMED) - PersonPool.personPool.getPeopleSize(Person.State.FREEZE)
+
+ g.drawString("急需病床:" + if (needBeds > 0) needBeds else 0, captionStartOffsetX, captionStartOffsetY + 6 * captionSize)
+ g.color = Color(0xccbbcc)
+ g.drawString("病死人数:" + PersonPool.personPool.getPeopleSize(Person.State.DEATH), captionStartOffsetX, captionStartOffsetY + 7 * captionSize)
+ g.color = Color(0xffffff)
+ g.drawString("世界时间(天):" + (worldTime / 10.0).toInt(), captionStartOffsetX, captionStartOffsetY + 8 * captionSize)
+ }
+
+
+}
\ No newline at end of file
diff --git a/VirusBroadcast_by_Kotlin/src/Person.kt b/VirusBroadcast_by_Kotlin/src/Person.kt
new file mode 100644
index 0000000..83fa21e
--- /dev/null
+++ b/VirusBroadcast_by_Kotlin/src/Person.kt
@@ -0,0 +1,213 @@
+import Person.State.Companion.DEATH
+import java.util.Random
+
+/**
+ * @ClassName:
+ * @Description:
+ * @Author: cnctemaR
+ * @Date: 2020/2/7 2:43
+ * */
+
+class Person(private val city: City, override var x: Int, override var y: Int) : Point(x, y) {
+ /**
+ * 正态分布N(mu,sigma)随机位移目标位置
+ */
+ private val targetXU: Double = MathUtil.stdGaussian(100.0, x.toDouble()) //x方向的均值mu
+ private val targetYU: Double = MathUtil.stdGaussian(100.0, y.toDouble()) //y方向的均值mu
+ private val targetSig: Double = 50.0 //方差sigma
+ private var moveTarget: MoveTarget? = null
+
+ private val sig = 1 //人群流动意愿影响系数:正态分布方差sigma
+ private val SAFE_DIST = 2.0//安全距离
+ var useBed: Bed? = null
+
+ var state = State.NORMAL
+ var infectedTime = 0//感染时刻
+ var confirmedTime = 0//确诊时刻
+ var dieMoment = 0//死亡时刻,为0代表未确定,-1代表不会病死
+
+
+ interface State {
+ companion object {
+ const val NORMAL = 0//正常人,未感染的健康人
+ const val SUSPECTED = NORMAL + 1//有暴露感染风险
+ const val SHADOW = SUSPECTED + 1//潜伏期
+ const val CONFIRMED = SHADOW + 1//发病且已确诊为感染病人
+ const val FREEZE = CONFIRMED + 1//隔离治疗,禁止位移
+
+ //已治愈出院的人转为NORMAL即可,否则会与作者通过数值大小判断状态的代码冲突
+ const val DEATH = FREEZE + 1//病死者
+ //int CURED = DEATH + 1;//治愈数量用于计算治愈出院后归还床位数量,该状态是否存续待定
+ }
+ }
+
+ /**
+ * 流动意愿标准化
+ *
+ * 根据标准正态分布生成随机人口流动意愿
+ *
+ * 流动意愿标准化后判断是在0的左边还是右边从而决定是否流动。
+ *
+ * 设X随机变量为服从正态分布,sigma是影响分布形态的系数,从而影响整体人群流动意愿分布
+ * u值决定正态分布的中轴是让更多人群偏向希望流动或者希望懒惰。
+ *
+ * value的推导:
+ * StdX = (X-u)/sigma
+ * X = sigma * StdX + u
+ *
+ * @return
+ */
+ fun wantMove(): Boolean = MathUtil.stdGaussian(sig.toDouble(), Constants.u) > 0
+
+ fun isInfected(): Boolean = state >= State.SHADOW
+
+ fun beInfected() {
+ state = State.SHADOW
+ infectedTime = MyPanel.worldTime
+ }
+ /**
+ * 住院
+ */
+ private fun freezy() {
+ state = State.FREEZE
+ }
+
+ /**
+ * 计算两点之间的直线距离
+ *
+ * @param person
+ * @return
+ */
+ fun distance(person: Person): Double = Math.sqrt(Math.pow((x - person.x).toDouble(), 2.0) + Math.pow((y - person.y).toDouble(), 2.0))
+
+ /**
+ * 不同状态下的单个人实例运动行为
+ */
+ private fun action() {
+
+ if (state == State.FREEZE || state == State.DEATH) {
+ return //如果处于隔离或者死亡状态,则无法行动
+ }
+ if (!wantMove()) {
+ return
+ }
+ //存在流动意愿的,将进行流动,流动位移仍然遵循标准正态分布
+ if (moveTarget == null || moveTarget!!.arrived) {
+ //在想要移动并且没有目标时,将自身移动目标设置为随机生成的符合正态分布的目标点
+ //产生N(a,b)的数:Math.sqrt(b)*random.nextGaussian()+a
+ val targetX = MathUtil.stdGaussian(targetSig, targetXU)
+ val targetY = MathUtil.stdGaussian(targetSig, targetYU)
+ moveTarget = MoveTarget(targetX.toInt(), targetY.toInt())
+
+ }
+
+ //计算运动位移
+ val dX = moveTarget!!.x - x
+ val dY = moveTarget!!.y - y
+
+ val length = Math.sqrt(Math.pow(dX.toDouble(), 2.0) + Math.pow(dY.toDouble(), 2.0))//与目标点的距离
+
+ if (length < 1) {
+ //判断是否到达目标点
+ moveTarget!!.arrived = true
+ return
+ }
+
+ var udX = (dX / length).toInt()//x轴dX为位移量,符号为沿x轴前进方向, 即udX为X方向表示量
+ if (udX == 0 && dX != 0)
+ udX = if (dX > 0) 1 else -1
+
+
+ var udY = (dY / length).toInt()//y轴dY为位移量,符号为沿x轴前进方向,即udY为Y方向表示量
+ //FIXED: 修正一处错误
+ if (udY == 0 && dY != 0)
+ udY = if (dY > 0) 1 else -1
+
+ //横向运动边界
+ if (x > Constants.CITY_WIDTH || x < 0) {
+ moveTarget = null
+ if (udX > 0)
+ udX = -udX
+ }
+ //纵向运动边界
+ if (y > Constants.CITY_HEIGHT || y < 0) {
+ moveTarget = null
+ if (udY > 0)
+ udY = -udY
+ }
+ moveTo(udX, udY)
+
+ }
+
+
+ /**
+ * 对各种状态的人进行不同的处理,更新发布市民健康状态
+ */
+ fun update() {
+ //@TODO找时间改为状态机
+ if (state == State.FREEZE || state == State.DEATH)
+ return//如果已经隔离或者死亡了,就不需要处理了
+
+ //处理已经确诊的感染者(即患者)
+ if (state == State.CONFIRMED && dieMoment == 0) {
+ val destiny = Random().nextInt(10000) + 1//幸运数字,[1,10000]随机数
+ dieMoment = if (1 <= destiny && destiny <= (Constants.FATALITY_RATE * 10000).toInt()) {
+ //如果幸运数字落在死亡区间
+ val dieTime = MathUtil.stdGaussian(Constants.DIE_VARIANCE, Constants.DIE_TIME.toDouble()).toInt()
+ confirmedTime + dieTime//发病后确定死亡时刻
+ } else {
+ -1//逃过了死神的魔爪
+ }
+ }
+ //TODO 暂时缺失治愈出院市民的处理。需要确定一个变量用于治愈时长。由于案例太少,暂不加入。
+
+
+ if (state == State.CONFIRMED && MyPanel.worldTime - confirmedTime >= Constants.HOSPITAL_RECEIVE_TIME) {
+ //如果患者已经确诊,且(世界时刻-确诊时刻)大于医院响应时间,即医院准备好病床了,可以抬走了
+ val bed = Hospital.hospital.pickBed()//查找空床位
+ if (bed == null) {
+ //没有床位了,报告需求床位数
+
+ } else {
+ //安置病人
+ useBed = bed
+ state = State.FREEZE
+ x = bed.x
+ y = bed.y
+ bed.isEmpty = false
+ }
+ }
+
+ //处理病死者
+ if ((state == State.CONFIRMED || state == State.FREEZE) && MyPanel.worldTime >= dieMoment && dieMoment > 0) {
+ state = State.DEATH//患者死亡
+ Hospital.hospital.returnBed(useBed)//归还床位
+ }
+
+ //增加一个正态分布用于潜伏期内随机发病时间
+ val stdRnShadowtime = MathUtil.stdGaussian(25.0, Constants.SHADOW_TIME / 2)
+ //处理发病的潜伏期感染者
+ if (MyPanel.worldTime - infectedTime > stdRnShadowtime && state == State.SHADOW) {
+ state = State.CONFIRMED//潜伏者发病
+ confirmedTime = MyPanel.worldTime//刷新时间
+ }
+ //处理未隔离者的移动问题
+ action()
+ //处理健康人被感染的问题
+ val people = PersonPool.personPool.personList
+ if (state >= State.SHADOW) {
+ return
+ }
+ //通过一个随机幸运值和安全距离决定感染其他人
+ for (person in people) {
+ if (person.state == State.NORMAL) {
+ continue
+ }
+ val random = Random().nextFloat()
+ if (random < Constants.BROAD_RATE && distance(person) < SAFE_DIST) {
+ this.beInfected()
+ break
+ }
+ }
+ }
+}
\ No newline at end of file