用户:
周某a查看:0 回复:0 评论:0 创建时间:2020-06-03T17:07:50
console.clear();
const KOMI = 7.5;
const RESIGN_RE = /认输/i;
const PASS_RE = /过/i;
const TICKS_PER_SECOND = (1024 / 喵);
const MAIN_TICKS = 10 * 60 * TICKS_PER_SECOND;
const LIGHTNING_TICKS = 10 * TICKS_PER_SECOND;
const ALLOW_SELF_PLAY = false;
const DIGIT = [
'zero',
'one',
'two',
'three',
'four',
'five',
'six',
'seven',
'eight',
'nine',
];
const B = {
'+': voxels.id('board1'),
'T': voxels.id('board4'),
'L': voxels.id('board3'),
'.': voxels.id('board2'),
}
const R = {
N: 0x8000,
S: 0,
E: 0xc000,
W: 0x4000,
};
const COLORS = [
'黑',
'白',
];
const STONE_MESH = [
world.querySelector('.black_stone').mesh,
world.querySelector('.white_stone').mesh,
];
const CURRENT_STONE_MESH = [
world.querySelector('.cur_black_stone').mesh,
world.querySelector('.cur_white_stone').mesh,
];
const BOWL_MESH = [
world.querySelector('.black_bowl').mesh,
world.querySelector('.white_bowl').mesh,
];
const CHOOSE_BUTTON_MESH = world.querySelector('.choose_button').mesh;
const teleport = (function(/* 传送相关 */) {
const locations = {};
world.querySelectorAll('.地点').forEach((e) => {
locations[e.id] = e.position;
e.destroy();
});
world.onEntityContact(({entity, other}) => {
if (other.isPlayer && entity.hasTag('绝对传送')) {
const attr = getEntityAttr(entity);
const pos = locations[attr['目的地']];
if (pos) other.position.copy(pos);
}
});
world.onEntityContact(({entity, other}) => {
if (other.isPlayer && entity.hasTag('相对传送')) {
const attr = getEntityAttr(entity);
other.position.addEq({
x: Number(attr.x) || 0,
y: Number(attr.y) || 0,
z: Number(attr.z) || 0,
});
}
});
return function(e, name) {
const pos = locations[name];
if (e && e.isPlayer && pos) e.position.copy(pos);
}
})();
async function playGo (name, size, originX, originY, originZ) {
const DOT_MOD = {
19: [6, 3],
13: [3, 0],
9: [2, 0],
7: [2, 0],
};
const SCORE_LENGTH = size <= 9 ? 3 : 5;
if (!(size in DOT_MOD)) {
size = 19;
}
const captures = [];
const pieces = new Array(size * size);
const board = new Array(size * size);
const visited = new Array(size * size);
const prevBoards = [];
const playerEntities = [ null, null ];
const score = [ 0, 0 ];
const clock = [ 0, 0 ];
let currentPlayer = 0;
let gameState = 'wait';
let passCount = 0;
let lastPieceIdx = -1;
const visitors = new Set();
function broadcast(s) {
world.say(`[${name}]: ${s}`);
}
function leaveVisotor(e) {
teleport(e, `${name}观战室`);
visitors.delete(e);
}
world.querySelector(`.${name}离开房间`).onEntityContact(({other}) => {
if (gameState === 'play') {
resign(other);
} else {
leaveVisotor(other);
}
});
world.querySelector(`.${name}入口`).onEntityContact(({other}) => {
if (gameState !== 'play') {
visitors.add(other);
other.position.y -= 5;
}
});
function createBoard () {
const [MOD, VAL] = DOT_MOD[size];
const border = voxels.id('palace_lamp');
const borderLight = voxels.id('lantern_02');
voxels.setVoxelId(originX - 1, originY + 1, originZ - 1, borderLight);
voxels.setVoxelId(originX + size, originY + 1, originZ - 1, borderLight);
voxels.setVoxelId(originX - 1, originY + 1, originZ + size, borderLight);
voxels.setVoxelId(originX + size, originY + 1, originZ + size, borderLight);
for (let i = 0; i <= size; ++i) {
voxels.setVoxelId(originX + i, originY, originZ + size, border);
voxels.setVoxelId(originX + i - 1, originY, originZ - 1, border);
voxels.setVoxelId(originX + size, originY, originZ + i - 1, border);
voxels.setVoxelId(originX - 1, originY, originZ + i , border);
}
for (let x = 0; x < size; ++x) {
for (let z = 0; z < size; ++z) {
let p = B['+'];
if (x === 0 && z === 0) {
p = B['L'] | R['S'];
} else if (x === 0 && z === size - 1) {
p = B['L'] | R['E'];
} else if (x === size -1 && z === 0) {
p = B['L'] | R['W'];
} else if (x === size -1 && z === size - 1) {
p = B['L'] | R['N'];
} else if (x === 0) {
p = B['T'] | R['E'];
} else if (x === size -1) {
p = B['T'] | R['W'];
} else if (z === 0) {
p = B['T'] | R['S'];
} else if (z === size -1) {
p = B['T'] | R['N'];
} else if (x > 0 && x < size - 1 && z > 0 && z < size - 1 && (x % MOD) === VAL && (z % MOD) === VAL) {
p = B['.'];
}
voxels.setVoxelId(x + originX, originY, z + originZ, p);
}
}
}
function drawNumber (x, y, z, n, dx, dz, rotation) {
for (let i = 0; i < SCORE_LENGTH; ++i) {
voxels.setVoxel(x, y, z, DIGIT[n % 10], rotation);
x += dx;
z += dz;
n = (n / 10) | 0;
}
}
function drawTime(time) {
drawNumber(originX + size + 2, originY - 1, originZ + size - SCORE_LENGTH + 1, time !== undefined ? time : (clock[0] / TICKS_PER_SECOND) | 0, 0, 1, 3);
drawNumber(originX - 3, originY - 1, originZ + SCORE_LENGTH - 2, time !== undefined ? time : (clock[1] / TICKS_PER_SECOND) | 0, 0, -1, 5);
}
function index (x, y) {
return x + y * size;
}
function setup () {
captures.forEach((c) => c.destroy());
captures.length = 0;
for (let i = 0; i < size * size; ++i) {
board[i] = -1;
if (pieces[i]) {
pieces[i].destroy();
}
pieces[i] = null;
}
score[0] = 0;
score[1] = KOMI;
currentPlayer = 0;
prevBoards.length = 0;
playerEntities[0] = playerEntities[1] = null;
gameState = 'wait';
passCount = 0;
clock[0] = clock[1] = MAIN_TICKS;
}
function hasLiberties (pX, pY) {
visited.fill(false);
const toVisit = [ pX, pY ];
const c = board[index(pX, pY)];
visited[index(pX, pY)] = true;
let libs = false;
function push (x, y) {
const idx = index(x, y);
if (visited[idx]) {
return;
}
const d = board[idx];
if (d === c) {
toVisit.push(x, y);
visited[idx] = true;
} else if (d < 0) {
libs = true;
}
}
for (let i = 0; i < toVisit.length; i += 2) {
const x = toVisit[i];
const y = toVisit[i + 1];
if (x > 0) { push(x - 1, y); }
if (x < size - 1) { push(x + 1, y); }
if (y > 0) { push(x, y - 1); }
if (y < size - 1) { push(x, y + 1); }
}
return libs;
}
function capture (x, y, koTest) {
if (board[index(x, y)] < 0) {
return;
}
const libs = hasLiberties(x, y);
if (gameState === 'play' && libs) {
return;
}
let count = 0;
for (let x = 0; x < size; ++x) {
for (let y = 0; y < size; ++y) {
const idx = index(x, y);
if (visited[idx]) {
if (!koTest) {
const c = board[idx] ^ 1;
const p = pieces[idx];
score[c] += 1;
p.destroy();
pieces[idx] = null;
captures.push(p);
}
board[idx] = -1;
}
}
}
}
function placePiece (x, y) {
const c = currentPlayer;
const idx = index(x, y);
// 检查放置棋子的位置是否为空
if (board[idx] >= 0) {
return;
}
// 尝试放置棋子
board[idx] = c;
// 保存棋盘状态
const koTest = board.slice();
// 对劫规则的处理
if (x > 0) { capture(x - 1, y, true); }
if (x < size - 1) { capture(x + 1, y, true); }
if (y > 0) { capture(x, y - 1, true); }
if (y < size - 1) { capture(x, y + 1, true); }
capture(x, y, true);
// 计算当前状态
const ko = board.join();
// 恢复棋盘状态
for (let j = 0; j < koTest.length; ++j) {
board[j] = koTest[j];
}
// 检查当前棋盘状态是否已经出现过
for (let i = 0; i < prevBoards.length; ++i) {
if (prevBoards[i] === ko) {
board[idx] = -1;
return;
}
}
// 创建棋子对象
pieces[idx] = world.createEntity({
position: [
originX + x + 0.5,
originY + 1.125,
originZ + y + 0.5,
],
meshScale: [1 / 24, 1 / 24, 1 / 24],
mesh: CURRENT_STONE_MESH[c],
gravity: false,
collides: true,
fixed: true,
});
// 计算提子
if (x > 0) { capture(x - 1, y); }
if (x < size - 1) { capture(x + 1, y); }
if (y > 0) { capture(x, y - 1); }
if (y < size - 1) { capture(x, y + 1); }
capture(x, y);
// 保存当前棋盘状态
prevBoards.push(board.join());
// 切换到下个玩家
currentPlayer ^= 1;
// 清空pass计数
passCount = 0;
// 更新clock
clock[currentPlayer] = Math.max(clock[currentPlayer], LIGHTNING_TICKS);
if (lastPieceIdx >= 0 && pieces[lastPieceIdx]) {
pieces[lastPieceIdx].mesh = STONE_MESH[board[lastPieceIdx]];
}
lastPieceIdx = idx;
return true;
}
// 统计占地
function countScore () {
visited.fill(false);
const adj = [ false, false ];
function count (x, y) {
if (x < 0 || y < 0 || x >= size || y >= size) {
return 0;
}
const idx = index(x, y);
if (visited[idx]) {
return 0;
}
visited[idx] = true;
const b = board[idx];
if (b === -1) {
return (1 +
count(x - 1, y) +
count(x + 1, y) +
count(x, y - 1) +
count(x, y + 1)
);
}
adj[b] = true;
return 0;
}
for (let x = 0; x < size; ++x) {
for (let y = 0; y < size; ++y) {
adj[0] = adj[1] = false;
const s = count(x, y);
if (adj[0] && !adj[1]) {
score[0] += s;
} else if (adj[1] && !adj[0]) {
score[1] += s;
}
}
}
}
function pass (entity) {
if (gameState === 'play') {
if (entity === playerEntities[currentPlayer]) {
broadcast(`${entity.player.name} 弃权一手`);
passCount += 1;
currentPlayer ^= 1;
// 更新clock
clock[currentPlayer] = Math.max(clock[currentPlayer], LIGHTNING_TICKS);
if (passCount >= 2) {
// broadcast('count territory, remove dead stones');
gameState = 'done';
passCount = 0;
}
}
} else if (gameState === 'count') {
for (let i = 0; i < playerEntities.length; ++i) {
if (entity === playerEntities[i]) {
if (passCount & (1 << i) === 0) {
broadcast(`${entity.player.name} finshed counting`);
passCount |= (1 << i);
}
}
}
if (passCount === 3) {
broadcast('done counting');
gameState = 'done';
}
}
}
function resign (entity) {
for (let i = 0; i < 2; ++i) {
if (entity === playerEntities[i]) {
if (gameState === 'play' ||
gameState === 'count') {
broadcast(`${entity.player.name} 认输`);
score[i] = -Infinity;
gameState = 'done';
}
playerEntities[i] = null;
}
}
}
createBoard();
drawTime(0);
[
[originX + size + 3, originZ],
[originX - 3, originZ + size],
].map(([x, z], i) => world.createEntity({
position: [
x,
originY + 0.5,
z,
],
mesh: BOWL_MESH[i],
meshScale: [1 / 16, 1 / 16, 1 / 16],
fixed: true,
collides: true,
gravity: false,
}));
const chooseButtons = [originX + size + 2.5, originX - 2.5].map((x) => world.createEntity({
position: [
x,
originY + 0.3,
originZ + size / 2,
],
mesh: CHOOSE_BUTTON_MESH,
meshScale: [1 / 24, 1 / 24, 1 / 24],
fixed: true,
collides: true,
gravity: false,
}))
world.onPress(({ entity, button, raycast:{ hitPosition:{x, y, z}, hitEntity } }) => {
if (gameState === 'play') {
if (button !== 'action0' ||
entity !== playerEntities[currentPlayer] ||
Math.abs(y - originY) > 2) {
return;
}
const dx = Math.floor(x) - originX;
const dz = Math.floor(z) - originZ;
if (dx < 0 || dx >= size || dz < 0 || dz >= size) {
return;
}
placePiece(dx, dz);
} else if (gameState === 'count' && hitEntity) {
for (let i = 0; i < playerEntities.length; ++i) {
if (entity === playerEntities[i] && pieces.indexOf(hitEntity) >= 0) {
capture(
Math.floor(hitEntity.position.x - originX),
Math.floor(hitEntity.position.z - originZ));
}
}
}
});
chooseButtons.forEach((button, c) => {
button.onEntityContact(({ other }) => {
if (gameState === 'wait' && other.isPlayer && playerEntities[c] !== other) {
broadcast(`${other.player.name}执${COLORS[c]}棋`);
playerEntities[c] = other;
if (!ALLOW_SELF_PLAY && playerEntities[c^1] === other) {
playerEntities[c^1] = null;
}
}
});
});
// pas喵utton.onEntityContact(({ other }) => pass(other));
// resignButton.onEntityContact(({ other }) => resign(other));
world.onPlayerLeave(({ entity }) => resign(entity));
world.onChat(({ entity, message }) => {
if (message.match(PASS_RE)) {
pass(entity);
} else if (message.match(RESIGN_RE)) {
resign(entity);
}
});
while (true) {
// 初始化
setup();
// 等待双方玩家选择颜色
while (!playerEntities[0] || !playerEntities[1]) {
await world.nextTick();
}
broadcast('对局开始');
gameState = 'play';
for (const v of visitors) {
if (playerEntities.includes(v)) continue;
teleport(v, `${name}观战室`);
}
visitors.clear();
// 处理每回合
while (gameState === 'play') {
await world.nextTick();
if (--clock[currentPlayer] <= 0) {
pass(playerEntities[currentPlayer]);
}
drawTime();
}
broadcast('对局结束');
// 计算占地
while (gameState === 'count') {
await world.nextTick();
}
countScore();
broadcast(`黑棋${score[0]}目 vs 白棋${score[1]}目`);
if (score[0] > score[1]) {
broadcast('黑棋胜');
} else {
broadcast('白棋胜');
}
drawTime(0);
playerEntities.forEach((e) => teleport(e, `${name}观战室`));
// 让棋盘上的棋子动起来, 表示本局结束
pieces.forEach((p) => {
if (p) {
p.collides = true;
p.fixed = false;
p.gravity = true;
p.velocity.x += 0.1 * (Math.random() - 0.5);
p.velocity.y += 0.1 * Math.random();
p.velocity.z += 0.1 * (Math.random() - 0.5)
}
});
// 十秒后开始下一轮
await sleep(10 * 1000);
}
}
function getEntityAttr(e) {
const attr = {};
e.tags().forEach((tag) => {
const g = tag.match(/(\S+)\s*:\s*(\S+)/);
if (g) {
attr[g[1]] = g[2];
}
});
return attr;
}
// 生成棋盘
world.querySelectorAll('.go_spawner').forEach((e) => {
const attr = getEntityAttr(e);
if ('name' in attr) {
playGo(attr.name, parseInt(attr.size) || 9,
Math.floor(e.position.x),
Math.floor(e.position.y),
Math.floor(e.position.z));
}
});
// 删除临时实体
world.querySelectorAll('.temporary').forEach((e) => e.destroy());
console.log(world.onPlayerJoin);
world.onPlayerJoin(({ entity }) => {
entity.player.enableActionA = true;
});