换个音乐的说

发表于KormiMeiko | 一条评论

opensuse leap 15.1体验感觉

良好。

发表在 日常记录 | 留下评论

[#1]オーディオはCode Veinから来ました

发表在 分享, 游戏 | 一条评论

庵野秀明の大学時代の作品

庵野秀明の大学時代の作品

痞子牛逼!

发表在 分享, 日常记录, 转载 | 留下评论

“祖安出租车司机”——情人节篇

把我笑死了(嘿嘿嘿)

发表在 日常记录, 转载 | 一条评论

历史总是惊人的相似——《某科学的最惨路人反派》&《某科学的超威蓝猫》

!历史总是惊人的相似!

发表在 番剧 | 留下评论

【产品经理目光】手把手教你用C#做疫情传播仿真

这个效果可以这样解读,如果不加以任何控制,疫情会很快蔓延到整个城市,只有8:1000床位的城市,将很快失去控制,一年后运行效果如下(非常惨烈):

参数与使用

代码中实际有很多参数可以操作:

static double MoveWilling = 0.90f; // 移动意愿,0-1
static bool WearMask = false; // 是否戴口罩
static int HospitalBeds = 40; // 床位数

const float InffectRate = 0.8f; // 靠得够近时,被携带者感染的机率
const float SecondsPerDay = 0.3f; // 模拟器的秒数,对应真实一天
const float MovingDistancePerDay = 10.0f; // 每天移动距离
const int InitialInfectorCount = 5; // 最初感染者数
const double DeathRate = 0.021; // 死亡率

// 要靠多近,才会触发感染验证
static float SafeDistance() => WearMask ? 1.5f : 3.5f;

// 住院治愈时间,最短5天,最长12.75天,平均约7天
static float GenerateCureDays() => random.NextFloat(5, 12.75f);
// 潜伏期,1-14天
static float GenerateShadowDays() => random.Next(1, 14);
// 发病后,就医时间,0-3天
static float GenerateToHospitalDays() => random.Next(0, 3);

由于参数太多,很难在运行时全部都做调整,我选取了是否戴口罩移动意愿医院床数作为参数,代码如下:

protected override void OnKeyPress(KeyPressEventArgs e)
{
    switch (e.KeyChar)
    {
        case '1': MoveWilling = 0.10f; break;
        case '2': MoveWilling = 0.50f; break;
        case '3': MoveWilling = 0.90f; break;
        case 'M': WearMask = !WearMask; break;
        case 'A': HospitalBeds += 40; break;
        case 'D': HospitalBeds -= 40; break;
        case 'R':
            {
                if (MessageBox.Show("要重来吗?", "确认", MessageBoxButtons.YesNo) == DialogResult.Yes)
                {
                    City = City.Create();
                }
                break;
            }
    }
}

其中,按数字键123可以指定不同的移动意愿,其中1表示居民最不愿意出门,3表示最愿意,按M可以控制居民是否能戴上口罩,按A可以添加医院可接纳病人数。

一些演示

超多床位

这里我将床位调成240个(比例为48:1000):

static int HospitalBeds = 240; // 床位数

也可以运行时增加床位,按键盘A即可(不用改代码),运行效果如下:

可见床位变多后,死亡率会降低,治愈人数大大提高,但仍然无法控制住疫情的发展。

“理想”情况1·没有潜伏期

可以这样配置:

// 0潜伏期
static float GenerateShadowDays() => 0;

运行效果如下:

可见就算潜伏期为0,由于没有及时就医,疫情仍会失去控制。

“理想”情况2·没有潜伏期、且立刻隔离就医

像那个Java版中,会先介绍一个“理想”情况,即感染即发病,发病即就就医。此种模型的参数,可以这样配置:

// 0潜伏期
static float GenerateShadowDays() => 0;
// 发病后,立即就医。
static float GenerateToHospitalDays() => 0;

在这种情况下,运行效果如下:

可见,由最初的5人,最终只感染了6人,理想情况下局势很快就被控制。当然这种情况是不存在的。

戴口罩出门

假如居民都戴口罩出门,可以用如下配置(也可以运行时按下M键):

static bool WearMask = true; // 一定戴口罩

运行效果如下:

坚持了107天,疫情发展速度大大降低,但由于医疗资源的匮乏,死亡率仍然居高不下,最终仍会失去控制。

居民呆在家中不出门,且戴口罩

配置如下:

static bool WearMask = true; // 一定戴口罩
static double MoveWilling = 0.10f; // 每天只有10%的人愿意出门

运行效果如下:

病毒在最初的5人身上,只感染了2人。效率可谓惊人。

模拟现实模型

我想贴近现实,模拟一些现实情况,然后看看效果如何,我的脚本如下:

  1. 20天,不作任何控制
  2. 20天,全民戴口罩,移动意愿降低至50%,床位增加40个(模拟进入重大突发公共卫生事件Ⅰ级响应
  3. 25天,移动愿意降低至10%,床位增加80个(模拟火神山医院)
  4. 30天,床位再增加80个,共240个(模拟雷神山医院)

为了尽可能短地截取gif,我将时间缩短了,真实时间可能会和这个比例为1:2(也就是相当于前40天不作任何控制),脚本如下:

void StepDay()
{
    if (day < 20)
    {
        WearMask = false;
        MoveWilling = 0.9;
        HospitalBeds = 40;
    }
    if (day >= 20)
    {
        WearMask = true;
        MoveWilling = 0.5;
        HospitalBeds = 80;
    }
    if (day >= 25)
    {
        MoveWilling = 0.1;
        HospitalBeds = 160;
    }
    if (day >= 30)
    {
        HospitalBeds = 240;
    }

    // 其它代码
}

运行效果如下:

可见这真是与病毒的一部史诗级斗争。

20天,没有任何控制,病毒疯狂肆虐,前期只花了9天,就把医院给挤满了;

20之后,居民带上口罩,传播进度迅速下降,但由于潜伏期较长,发病人数仍然持续上升;

30天之后,由于5天前床位(医护资源)的跟进,发病人数增速降低;

35天后,发病人数开始下降;

68天后,除了医院中的病人,城市中已经没有病人;

78天,已经没有被病毒感染的人了。

总结

所有这些代码,我都上传到了我的博客数据网站,各们可以下载代码,通过LINQPad 6直接模拟运行,或者拷到Visual Studio中亦可。

发表在 技术, 教程, 转载 | 留下评论

【程序员目光】手把手教你用C#做疫情传播仿真

注意看,程序中的信息,包含信息统计、城市居民展示和医院展示三个部分,其中居民按状态的不同,显示为不同的颜色。

本文将先从程序员的角度,说说程序中的实现细节,细节中会聊一聊与与Java版的不同,最后进行总结。

细节介绍

细节介绍一 · 从“人”说起

居民类如下所示:

struct Person
{
    public PersonStatus Status;
    public Vector2 Position;
    public float EstimateDays;
    public float Direction;

    public static Person Create(float citySize)
    {
        // ...
    }

    public void Draw(DeviceContext ctx, XResource x)
    {
        // ...
    }

    public void MoveAroundInCity(float dt, float citySize)
    {
        // ...
    }
}

enum PersonStatus
{
    Healthy, // 健康
    InfectedInShadow, // 被感染,处于潜伏期
    Illness, // 发病
    InHospital, // 发病并进入医院
    Cured, // 治愈
    Dead, //死亡
}

一个城市将会模拟5000个居民,因此在设计这个类的时候,应该尽可能地考虑性能、节约内存。

所以,状态最好越少越好,在设计这个类的时候,我谨慎地保留了状态Status、当前位置Position、用于做状态机的EstimateDays和移动方向Direction这四个状态。

细节介绍二 – 居民的状态变更流

居民状态扭转过程如下所示:

 (有传染性,传染给健康人)
    👇   ⬆       ⬆
    👇   ⬆       ⬆
健康 ➡ 潜伏期 ➡ 发病 ➡ 入院隔离 ➡ 治愈
                  ↘     ↙
                    ↘ ↙
                     死亡

其中,健康被感染的验证除了状态检测外,还要由居民之间的距离决定。而是否戴口罩,又会影响其判断距离,这些逻辑用代码表示如下:

const float InffectRate = 0.8f; // 靠得够近时,被携带者感染的机率
static bool WearMask = false; // 是否戴口罩
// 要靠多近,才会触发感染验证
static float SafeDistance() => WearMask ? 1.5f : 3.5f;

void StepDay()
{
    // ...

    // healthy -> infected
    List<int> newlyInffectedIds = new List<int>();
    newlyInffectedIds = healthyIds
        .AsParallel()
        .Where(x =>
        {
            foreach (var infectorId in infectorIds)
            {
                if (Vector2.DistanceSquared(Persons[x].Position, Persons[infectorId].Position) <= SafeDistance() * SafeDistance())
                    return true;
            }
            return false;
        })
        .ToList();

    foreach (int personId in newlyInffectedIds)
    {
        Infect(personId);
    }
}

EstimateDays字段用于控制潜伏期发病到去医院的等待时间治愈时间,这个字段用得较为巧妙。正常可能需要三个字段,但这三种状态之间,不存在状态共享,因此可以使用一个共享的字段来代替。

比如,infected -> illness状态扭转的代码表述如下:

void StepDay()
{
    for (var i = 0; i < Persons.Length; ++i)
    {
        // ... 其它代码

        // infected -> illness
        if (Persons[i].Status == PersonStatus.InfectedInShadow)
        {
            --Persons[i].EstimateDays;
            if (Persons[i].EstimateDays <= 0)
            {
                Persons[i].Status = PersonStatus.Illness;
                Persons[i].EstimateDays = GenerateToHospitalDays();
            }
            continue;
        }
    }

    // ... 其它代码
}

注意,代码中总会使用EstimateDays,来判断是否要进入下一个状态,而进入下一个状态后,便会重新指定新的EstimateDays。通过这样的状态共享,便可为Person类节省许多状态。

细节介绍3 – 性能优化

注意上文中的代码,它原本可能会是一个5000x5000的大循环,而每帧的时间仅仅只有1/60=13.33ms

经过反复思考,我使用了三种方法来优化。

优化1 · 索引与缓存

首先是在城市类City中,我使用了一个索引:

class City
{
    public Person[] Persons;
    private SortedSet<int> infectorIds = new SortedSet<int>();
    private SortedSet<int> healthyIds = new SortedSet<int>();

    // ... 其它代码
}

该索引维护了两个索引infectorIdshealthyIds,保存好这两个索引后,这个双层循环检测性能可以从5000x5000降低到02000x2000,最优情况是初期和未期,数据规模趋近于0,最差情况在中期,数据规模趋近于2000x2000,总之会比简单的双层循环快很多。

注意:索引是有明显缺点的,索引的本质是缓存,缓存的本质是状态,状态的属性之一,就是bug,多一份索引,就需要多加一处维护索引的位置,就多加了一层“写bug”的风险。另外索引过多,可能会影响性能。

我会尽我一切努力,不给程序引入额外状态。除非我有一个无法拒绝的理由。

优化2 · 多线程

这算是.NET的福利吧。

如代码所示,我使用了PLINQ,这是从.NET 4.0推出的新玩意,只需一条简单的AsParallel(),就可以让代码几乎不变,就能享受多核CPU带来的性能红利,我完全不需要处理同步等机制。

优化3 · 使用值类型

也如代码所示,我特意为Person类选择了值类型(struct),它的优点在本程序中体现在两处:

一是在于创建时,无需分配堆内存,要知道内存分配需要请求操作系统(就像浏览器请求服务器那样)非常缓慢;

二是值类型数据的值,在内存中是连续的。这对CPU缓存是个天大的好消息。无论是否是现代CPU,对连续型的内存访问,性能总是最高的,在一性能测试中,连续内存与非连续内存的CPU访问速度差,高达50倍之大。

注意:Java中没提供类似于struct这样的关键字,无法自定义值类型。但通过一定技巧,如创建基元类型数组,也能实现高性能的连续内存访问。

细节介绍四 – 时间控制

我尝试写过很多游戏和动态模拟器,我认为时间控制的优劣,最能体现出一个模拟器/游戏制作者的用心。一般程序员都喜欢将垂直同步事件当作游戏的心脏,这样最简单,用代码表述如下(已简化):

void Render()
{
    float dt = RenderTimer.LastFrameTimeInSecond;

    Update(dt);
    Draw(ctx);

    SwapChain.Present(1, 0);
}

这样的好处是逻辑可能比较简单,可以在大脑中脑补每秒60帧,然后按60帧设置参数,想事情。

这样一来,更新逻辑Update(dt)可能就会和垂直同步事件强绑定。要知道有些投影仪可能只有50帧,而某些显示器,有144帧;然后就是它也和垂直同步选项强绑定,一旦关闭垂直同步,Update逻辑可能就会过快而导致程序运行不正常。

我的做法是将这些逻辑稍作封装,代码中的配置,只与真实世界中的时间相关,而与垂直同步选项无关:

const float SecondsPerDay = 0.3f; // 模拟器的秒数,对应真实一天

class City
{
    float dayAccumulate = 0;
    public void Update(float dt)
    {
        // step move
        for (var i = 0; i < Persons.Length; ++i)
        {
            Persons[i].MoveAroundInCity(dt, CitySize);
        }

        // step status
        dayAccumulate += dt;
        day += (dt / SecondsPerDay);

        while (dayAccumulate >= SecondsPerDay)
        {
            StepDay();
            dayAccumulate -= SecondsPerDay;
        }
    }
}

注意我使用了一个SecondsPerDay,来控制模拟器的运行速度,将这个值调大或调小,不影响运行的最终结果。

我还使用了一个dayAccumulate值,用于做按“”更新判断,这样的话,无论函数调用频率如何,调用StepDay()时都会确保相隔“一整天”。

细节介绍五 – 缩放管理

和时间管理一样,我认为窗口大小与缩放控制也很重要,否则程序只能以一种固定的分辨率、DPI来运行。我使用的是我自己写的“准”游戏引擎FlysEngine,它基于Direct2D,可以通过矩阵变换轻松地管理好程序缩放:

protected override void OnDraw(DeviceContext ctx)
{
    ctx.Clear(Color.DarkGray);

    float minEdge = Math.Min(ClientSize.Width / 2, ClientSize.Height / 2);
    float scale = minEdge / 540; // relative coordinate
    ctx.Transform =
        Matrix3x2.Scaling(scale) *
        Matrix3x2.Translation(ClientSize.Width / 2, ClientSize.Height / 2);
    City.Draw(ctx, XResource);
}

注意我定义了一个“魔法值”——540,它是FHD 1920x1080中,短边1080的一半。

这样一来,有两个好处。

首先,我程序后面所有代码,都可以按照1920x1080的“相对值”进行设计。无论客户的桌面分辨率是4k UHD还是1366x768,都会以相同的比例做缩放。

其次我还将坐标原点设为屏幕的正中心,这样也更加简化了我的后续代码,比如在控制Person的出生点时,我可以通过极坐标系直接生成:

struct Person
{
    public static Person Create(float citySize)
    {
        float phi = random.NextFloat(0, MathUtil.TwoPi);
        float r = random.NextFloat(0, citySize);
        var p = new Person { Status = PersonStatus.Healthy };
        p.Position.X = MathF.Sin(phi) * r;
        p.Position.Y = -MathF.Cos(phi) * r;
        p.Direction = random.NextFloat(0, MathF.PI * 2);
        return p;
    }
    
    // 其它代码
}

总结

本文从五个细节聊了我的【.NET疫情传播程序】的代码,其实这些代码不光应用在这个程序中,也应用到了我写过的许多小游戏和模拟器,都非常重要。

发表在 技术, 教程, 转载 | 留下评论

Code Vein拿音乐思路[伪]

【大众化一点,所以直接拿现成工具干就完了,奥利给!】

*事情起因*(这得从一只蝙蝠说起….(bushi))

虽然我在一些音乐平台上找到了Code Vein的OST,但不全啊,尤其是那个新手教程的钢琴曲,太好听了,可惜没收录…那没办法喽,内录不好整,那我就直接从资源包里把你搞出来了…

*不知道写什么*

这画面,这特效,3D的二次元小姐姐,经典的吸血鬼文化,满级渲染的音乐,世末主题,凄惨感人的故事,有趣的灵魂,漂亮的皮囊……哇,太爽了。嗯,不多解释,虚幻4引擎。

*让我康康*

进入游戏目录,去CodeVein>Content>Paks文件夹,看到里面的*.pak文件了没有?嗯,那就是目标。我们第一步需要拿一个叫UE4PakUnpacker的工具把这两个包解了(推荐解之前先备份,不多解释,习惯)

解完后,CodeVein文件夹会多处一点东西,继续去往Content目录,找到WwiseAudio文件夹,进去,里面有个Windows文件夹,进去。

其中里面有个English(US)文件夹和一个Japanese文件夹,这两个不用管,我也不解释。你会在里面看到很多*.wem文件,我们就是要干这个。【*.wem –> *.ogg】

这里需要一个MGRR wem工具,可以在网上搞到,下载完解压,下面是MGRR wem内容清单:

convert.bat ; packed_codebooks_aoTuV_603.bin ; revorb.exe ; ww2ogg.exe

把这一堆东西复制到Windows目录下,双击convert.bat等他跑完即可。

然后你就在一堆*.ogg文件里找吧,推荐使用大小排序一下,在1MB+的文件中找。

不过你也可以白嫖我这里找到的结果,我只给文件名:

114441702.ogg

205798536.ogg

387398963.ogg

454004654.ogg

477784966.ogg

878677980.ogg

*其他*

如果想提取模型等其他资源的我就不用多解释了吧,包既然都解了,还有什么干不成的呢?

发表在 技术, 游戏 | 留下评论

【转】【李云龙】- 处处吻

地址:https://www.bilibili.com/video/av85653479

临行前温热烈酒一樽
洋洒青山间祭奠亡魂
晶莹勾勒风霜的划痕
载满半生情愫渐渗入坟
若战争成吻 以血成文
一步失一人 任谁都生分 噢噢
假使以这般暧昧叫阵
愈是离分 愈是叫人沉沦

这一程 一朝孤蓬万里征
一骑匿去无人等
笑这吻痕太冰冷
劝君来世莫为僧
一念一刻悟死生
一情一线连秋风
一炮空余坠地声
一人埋入平安城

兵戈随红唇 送入罗生门
与其思不忿 更待谁无问

弹坑是大地承受的吻
骨堆是战争扫过的痕
子弹是通往极乐的门
尸首是未出口的疑问
却愈吻愈烈 曝尸荒野
唇齿正亦邪 吻痕明又灭 噢噢
放眼神州已皆是罪孽
死亡与血 蜷缩滥情的街

这余生 一时莫逆炮火摧
一日携手两相背
一枪送得旧人归
一抔黄土东流水
一马遥看万人围
一臂但执青锋锐
一兵嘶声振军威
一抹鲜红魂安睡

痛吻却使人轻盈
恍惚间才站定 再无人归营
热吻却赠人彻骨寒意
帽檐半遮眉宇 孤高且睥睨
别了他 它吻他
吻上永世的伤疤 埋入黄泉之下
再待来生繁华
一个它 它吻他
近处只会剩下无声的厮杀 与坚毅的步伐

你小心 一技红缨扫四境
一面红旗嵌金星
吻痕留不住坚定
远眺可及的黎明
一声掷地的号令
一把蒙尘的刀柄
一梦环顾巨龙起
一国奋起四方定

别了它 吻的痂
拭去吻痕与黄沙 故土无人可侵
还复告慰门庭
下个它 它吻他
他会脱身且顾家 若红唇是责罚
以清酒泡淡茶
若世间 痛吻我
欲求我报之以歌

发表在 文章, 转载 | 一条评论