之前一直使用的是阿里云上ubuntu 12.04系统,为了使用docker方便有过一次升级,不过那个时候升级比较粗暴,我直接更新了源,然后用apt-get upgrade升的级。
今天想升级到16.04才突然发现了do–release–upgrade,整个过程轻松加愉快。
直接运行
sudo do-release-upgrade -d
升级之后检查一下
lsb_release -a

之前一直使用的是阿里云上ubuntu 12.04系统,为了使用docker方便有过一次升级,不过那个时候升级比较粗暴,我直接更新了源,然后用apt-get upgrade升的级。
今天想升级到16.04才突然发现了do–release–upgrade,整个过程轻松加愉快。
直接运行
sudo do-release-upgrade -d
升级之后检查一下
lsb_release -a

最近由于各种原因,需要将之前的一个网站关闭。
关闭之后因为域名没有到期,所以还是希望能够有一个静态页面让平时的常客至少下载一下存档,所以就做了一个静态网站。
因为静态网站只有一个页面和一个zip存档,所以需要在nginx中将所有请求都转发到这个页面上。
配置如下:
location / {
try_files $uri /index.html;
}
这样的话默认请求存在的文件的时候就会直接访问这些文件,否则就会跳转回首页。
通常一个项目,特别是开源项目有很多常见的操作,比如merge一个pull request或者发布一个版本。
merge一个pull request可以通过github的issue页面完成,但是发布版本通常需要一些准备,特别是证书等等。
Rultor是一个辅助工具,可以完成合并,发布等功能。
Rultor的配置是.rultor.yml,这是一个例子
decrypt:
settings.xml: "repo/settings.xml.asc"
pubring.gpg: "repo/pubring.gpg.asc"
secring.gpg: "repo/secring.gpg.asc"
docker:
image: "yegor256/java8"
release:
script: |
mvn versions:set "-DnewVersion=${tag}"
git commit -am "${tag}"
mvn clean deploy -P central-deploy --settings /home/r/settings.xml -P cxf-spring-boot-starter-maven-plugin
使用的时候很简单,比如需要发布新版本,直接创建一个issue,在其中@tultor即可
@rultor release, tag is 1.0.0.RELEASE
一个参考效果如下

http://www.rultor.com/
Jenkins应该是最常用的CI/CD工具了,而Jenkins也有官方的Docker版本,可以很方便的获得一个可用的Jenkins。
但是大部分时候我们期待的是从Docker启动Jenkins之后一切环境都是准备就绪的,比如对应的插件啥的。
对于插件来说还比较简单,直接提供一个plugin.txt文件即可
credentials:1.18 maven-plugin:2.7.1
Jenkins的任务是可以在master直接运行的,但是很多时候,特别会需要很多不同构建环境的时候我们就会需要多个不同的Jenkins Slave了。
Docker-Compose可以做到启动多个容器,这样我们就可以直接在其中就行配置了,比如一个java7的Slave,一个java8的,再来一个node的,但是最后还是需要到jenkins中添加这些Slave。
Jenkins提供了Groovy脚本来实现一些配置和功能,比如需要修改执行器数量(默认为2),可以使用一些
import jenkins.model.* Jenkins.instance.setNumExecutors(5)
对于添加Slave的脚本就稍微复杂一些,需要用到import hudson.plugins.sshslaves.*
大概的脚本如下
List<Entry> env = new ArrayList<Entry>();
EnvironmentVariablesNodeProperty envPro = new EnvironmentVariablesNodeProperty(env);
Slave slave = new DumbSlave(
"java-7-node","",
"/var/jenkins_home",
"1",
Node.Mode.NORMAL,
"agent-node-label",
new SSHLauncher("java-7-node",22,"jenkins","password","","","","",""),
new RetentionStrategy.Always(),
new LinkedList())
slave.getNodeProperties().add(envPro)
Jenkins.instance.addNode(slave)
然后将这个脚本拷贝到init.groovy.d目录
FROM jenkins COPY slaves.groovy /usr/share/jenkins/ref/init.groovy.d/slaves.groovy
Gdx-ai提供了常见的AI算法,当然也包括寻路算法,比如常用的A*。
Gdx-ai自身的API抽象也很易懂,也方便自定义寻路。
首先由Graph和Connection提供图中的连通性和对应连接的开销。 简单一点直接用DefaultConnection,它提供的默认开销为1。
而PathFinder就是具体执行寻路的类,而返回的结果是GraphPath,它包含了路的结果,对应的连接还有其他一些数据。
这是Meritxell Calvo Palanques画的示意图

另外就是Heuristic,它并不是必须的,但是一个有效的Heuristic函数可以加快搜索,不过不方便提供,或者不想提供,最坏的结果无非就是退化为Dijkstra。(我提供的例子会直接返回一个错误的启发函数,不过依然可以获得结果)。
例子就简单一点吧,一般地图的数据是从Tiled地图或者其他格式的文件中获得的,这里我直接用硬编码来。
这个图只有四个点,其中点1,2,3相互连接,点3连接点4,我们要寻找从点1到点4的路径。
首先表示这个图
IndexedGraph<Node> graph = new IndexedGraph<Node>() {
@Override
public Array<Connection<Node>> getConnections(Node fromNode) {
Array<Connection<Node>> connections = new Array<Connection<Node>>();
switch (fromNode.getId()) {
case 1:
connections.add(new DefaultConnection<Node>(n1, n2));
connections.add(new DefaultConnection<Node>(n1, n3));
break;
case 2:
connections.add(new DefaultConnection<Node>(n2, n1));
connections.add(new DefaultConnection<Node>(n2, n3));
break;
case 3:
connections.add(new DefaultConnection<Node>(n3, n4));
connections.add(new DefaultConnection<Node>(n3, n1));
break;
default:
connections.add(new DefaultConnection<Node>(n4, n3));
}
return connections;
}
@Override
public int getIndex(Node node) {
return node.getId() - 1;
}
@Override
public int getNodeCount() {
return 4;
}
};
然后使用A*寻路并提供一个随机的启发函数
IndexedAStarPathFinder<Node> pathFinder = new IndexedAStarPathFinder<Node>(graph);
GraphPath<Connection<Node>> out = new DefaultGraphPath<Connection<Node>>();
final RandomXS128 randomXS128 = new RandomXS128();
pathFinder.searchConnectionPath(n1, n4, new Heuristic<Node>() {
@Override
public float estimate(Node node, Node endNode) {
return randomXS128.nextFloat();
}
}, out);
最后从GraphPath<Connection<Node>> out获取数据即可

LIbgdx旗下的gdx-ai库提供了大量使用的AI算法,其中包括了最常见的有限状态机FSM。
gdx-ai是一个普通的java库,并不存在平台相关依赖,所以直接在core中引入就可。
dependencies {
compile "com.badlogicgames.gdx:gdx-ai:1.7.0"
}
StateMachine是有限状态机的接口,对应的实现目前有两个DefaultStateMachine和StackStateMachine。
StackStateMachine以栈的形式保存状态,所以可以提供了方法获取上一个状态,一般使用DefaultStateMachine就行了。
FSM的例子很多,我们直接来一个简单的。
首先主体对象是人,而人只有两个状态,一个走路,一个打伞。
状态切换的条件是时间,也就是假定下雨的时间符合一定的条件。
首先创建一个Person类
public class Person extends Image {
private TextureRegion walk;
private TextureRegion rain;
private StateMachine<Person, PersonState> stateMachine;
private float time;
public Person(final TextureRegion walk, final TextureRegion rain) {
super(walk);
this.walk = walk;
this.rain = rain;
stateMachine = new DefaultStateMachine<Person, PersonState>(this, PersonState.WALK);
}
public void showRain() {
setDrawable(new TextureRegionDrawable(rain));
setSize(getPrefWidth(), getPrefHeight());
}
public void showWalk() {
setDrawable(new TextureRegionDrawable(walk));
setSize(getPrefWidth(), getPrefHeight());
}
public void update(float delta) {
time += delta;
stateMachine.update();
}
}
当初始化时默认状态是没有下雨了,而界面的表现就是显示不同的图片


而time就是总共经历的时间
在最关键状态处理逻辑,包括进入,更新,退出,接受消息,是放在对应的State中的。这里我们不考虑接受消息的情况,所以永远返回false。
public enum PersonState implements State<Person> {
WALK {
@Override
public void enter(Person entity) {
entity.showWalk();
}
@Override
public void update(Person entity) {
float v = entity.getTime() % 10;
if (v >= 6 || v <= 4) {
entity.getStateMachine().changeState(PersonState.RUN);
}
}
@Override
public void exit(Person entity) {
}
@Override
public boolean onMessage(Person entity, Telegram telegram) {
return false;
}
},
RUN {
@Override
public void enter(Person entity) {
entity.showRain();
}
@Override
public void update(Person entity) {
float v = entity.getTime() % 10;
if (v < 6 && v > 4) {
entity.getStateMachine().changeState(PersonState.WALK);
}
}
@Override
public void exit(Person entity) {
}
@Override
public boolean onMessage(Person entity, Telegram telegram) {
return false;
}
}
}
当在update中切换状态时,会自动触发对应的进入(enter)事件,所以也不需要额外的处理。
切换状态时的对应代码实现
@Override
public void changeState (S newState) {
// Keep a record of the previous state
previousState = currentState;
// Call the exit method of the existing state
if (currentState != null) currentState.exit(owner);
// Change state to the new state
currentState = newState;
// Call the entry method of the new state
if (currentState != null) currentState.enter(owner);
}
https://github.com/libgdx/gdx-ai/wiki/State-Machine
Libgdx对于资源管理提供了AssetManager来做资源的管理,如果你需要使用它那么你就不得不使用这样的代码
manager.load("data/mytexture.png", Texture.class);
manager.load("data/myfont.fnt", BitmapFont.class);
manager.load("data/mymusic.ogg", Music.class);
其中的文件路径是字符串。然后在你使用的地方
Texture tex = manager.get("data/mytexture.png", Texture.class);
BitmapFont font = manager.get("data/myfont.fnt", BitmapFont.class);
这样使用其实蛮不方便的,如果你把文件路径作为一个全局变量的话,其实很多代码是重复的。
而AnnotationAssetManager提供了反射的方法来完成这个工作
package com.huangyunkun.hundred;
import com.badlogic.gdx.graphics.Texture;
import net.dermetfan.gdx.assets.AnnotationAssetManager;
public class Assets {
@AnnotationAssetManager.Asset(Texture.class)
public static final String ball = "ball.png";
}
比如有一个球的图片,那么只需要声明一个字符串。然后加载的时候直接传入这个类本身,由加载器自己去寻找所有需要加载的资源即可。
public class QuickStartScreen extends ScreenAdapter {
private Stage stage;
private AnnotationAssetManager annotationAssetManager;
public QuickStartScreen() {
this.annotationAssetManager = new AnnotationAssetManager();
this.stage = new Stage(new StretchViewport(960, 480));
this.annotationAssetManager.load(Assets.class);
this.annotationAssetManager.finishLoading();
this.stage.addActor(new Image(this.annotationAssetManager.get(Assets.ball, Texture.class)));
}
@Override
public void render(float delta) {
GdxUtilities.clearScreen();
stage.draw();
}
}
Asset类本身还可以根据需要拆成不同的小类。
而@Asset注解还支持参数,比如
@Asset(value = Texture.class, params = "textureParams") public static final String grass = "img/grass.png";
之前用的ubuntu一直配置的上海交通大学的源,速度也不错,最近升级到16.04以后又准备继续使用,突然发现可以直接使用mirrors自动选择最合适的源。
默认的可用的列表文件在这里 http://mirrors.ubuntu.com/mirrors.txt
在source.list直接使用
deb mirror://mirrors.ubuntu.com/mirrors.txt xenial main restricted universe multiverse deb mirror://mirrors.ubuntu.com/mirrors.txt xenial-updates main restricted universe multiverse deb mirror://mirrors.ubuntu.com/mirrors.txt xenial-backports main restricted universe multiverse deb mirror://mirrors.ubuntu.com/mirrors.txt xenial-security main restricted universe multiverse
Spring Boot提供了一个很便利的命令bootRun来快速启动应用,项目中的依赖会在启动时作为classpath的一部分传递给java,但是windows系统下有一个问题,如果classpath很长的时候就会直接报错,无法运行。但是如果你生成jar文件,再运行jar文件本身就不会遇到这个问题。
bootRun是一个很便利的功能,自然我们不希望这个功能无法使用,有个取巧的办法。
首先生成一个jar,这个jar文件的manifest中包含了所有依赖,然后bootRun直接依赖这个jar。
task pathingJar(type: Jar) {
dependsOn configurations.runtime
appendix = 'pathing'
doFirst {
manifest {
attributes "Class-Path": configurations.runtime.files.collect {
it.toURL().toString().replaceFirst(/file:/+/, '/')
}.join(' ')
}
}
}
bootRun {
dependsOn pathingJar
doFirst {
classpath = files("$buildDir/classes/main", "$buildDir/resources/main", pathingJar.archivePath)
}
}
Docker的镜像拉取有时候会花很长的时间,而国内有很多提供镜像服务的公司,随便找一个即可。
但是在配置的时候经常会出现无法配置成功的问题,比如DaoCloud会告诉你修改profile文件,要么使用它的主机监控程序。一般情况我只想用镜像加速,并不想主机监控服务,所以选用修改profile文件,但是每次重启之后都没有生效。
比较了阿里百川还有灵雀云的加速配置文档,基本是一个逻辑,但是依照文档,修改镜像的方法只有两种,第一种是创建的时候直接指定
docker-machine create ... --engine-registry-mirror=<你的加速地址>
或者是修改config.json中的RegistryMirror配置

这样在docker-machine重启以后配置才会真正生效。
参考文档:
https://github.com/docker/machine/blob/master/docs/reference/create.md