feat: rng & commands impl & tests
This commit is contained in:
+7
-2
@@ -26,7 +26,7 @@ export const GameContext = createModel((root: Context) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
parts,
|
||||
regions,
|
||||
@@ -35,4 +35,9 @@ export const GameContext = createModel((root: Context) => {
|
||||
popContext,
|
||||
latestContext,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/** 创建游戏上下文实例 */
|
||||
export function createGameContext(root: Context = { type: 'game' }) {
|
||||
return new GameContext(root);
|
||||
}
|
||||
+58
-4
@@ -5,7 +5,7 @@ import {RNG} from "../utils/rng";
|
||||
export type Region = Entity & {
|
||||
// aligning axes of the region
|
||||
axes: RegionAxis[];
|
||||
|
||||
|
||||
// current children; expect no overlapped positions
|
||||
children: EntityAccessor<Part>[];
|
||||
}
|
||||
@@ -27,7 +27,49 @@ export type RegionAxis = {
|
||||
*/
|
||||
export function applyAlign(region: Region){
|
||||
for (const axis of region.axes) {
|
||||
// TODO implement this
|
||||
if (region.children.length === 0) continue;
|
||||
|
||||
// 获取当前轴向上的所有位置
|
||||
const positions = region.children.map(accessor => accessor.value.position);
|
||||
|
||||
// 根据当前轴的位置排序 children
|
||||
region.children.sort((a, b) => {
|
||||
const posA = a.value.position[0] ?? 0;
|
||||
const posB = b.value.position[0] ?? 0;
|
||||
return posA - posB;
|
||||
});
|
||||
|
||||
if (axis.align === 'start' && axis.min !== undefined) {
|
||||
// 从 min 开始紧凑排列
|
||||
region.children.forEach((accessor, index) => {
|
||||
const currentPos = accessor.value.position.slice();
|
||||
currentPos[0] = axis.min! + index;
|
||||
accessor.value.position = currentPos;
|
||||
});
|
||||
} else if (axis.align === 'end' && axis.max !== undefined) {
|
||||
// 从 max 开始向前紧凑排列
|
||||
const count = region.children.length;
|
||||
region.children.forEach((accessor, index) => {
|
||||
const currentPos = accessor.value.position.slice();
|
||||
currentPos[0] = axis.max! - (count - 1 - index);
|
||||
accessor.value.position = currentPos;
|
||||
});
|
||||
} else if (axis.align === 'center') {
|
||||
// 居中排列
|
||||
const count = region.children.length;
|
||||
const min = axis.min ?? 0;
|
||||
const max = axis.max ?? count - 1;
|
||||
const range = max - min;
|
||||
const center = min + range / 2;
|
||||
|
||||
region.children.forEach((accessor, index) => {
|
||||
const currentPos = accessor.value.position.slice();
|
||||
// 计算相对于中心的偏移
|
||||
const offset = index - (count - 1) / 2;
|
||||
currentPos[0] = center + offset;
|
||||
accessor.value.position = currentPos;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,5 +79,17 @@ export function applyAlign(region: Region){
|
||||
* @param rng
|
||||
*/
|
||||
export function shuffle(region: Region, rng: RNG){
|
||||
// TODO implement this
|
||||
}
|
||||
if (region.children.length <= 1) return;
|
||||
|
||||
// Fisher-Yates 洗牌算法
|
||||
const children = [...region.children];
|
||||
for (let i = children.length - 1; i > 0; i--) {
|
||||
const j = rng.nextInt(i + 1);
|
||||
// 交换位置
|
||||
const posI = children[i].value.position.slice();
|
||||
const posJ = children[j].value.position.slice();
|
||||
|
||||
children[i].value.position = posJ;
|
||||
children[j].value.position = posI;
|
||||
}
|
||||
}
|
||||
|
||||
+63
-7
@@ -9,24 +9,80 @@ export type RuleContext<T> = Context & {
|
||||
resolution?: T;
|
||||
}
|
||||
|
||||
function invokeRuleContext<T>(pushContext: (context: Context) => void, type: string, rule: Generator<string, T, Command>){
|
||||
/**
|
||||
* 调用规则生成器并管理其上下文
|
||||
* @param pushContext - 用于推送上下文到上下文栈的函数
|
||||
* @param type - 规则类型
|
||||
* @param rule - 规则生成器函数
|
||||
* @returns 规则执行结果
|
||||
*/
|
||||
export function invokeRuleContext<T>(
|
||||
pushContext: (context: Context) => void,
|
||||
type: string,
|
||||
rule: Generator<string, T, Command>
|
||||
): RuleContext<T> {
|
||||
const ctx: RuleContext<T> = {
|
||||
type,
|
||||
actions: [],
|
||||
handledActions: 0,
|
||||
invocations: [],
|
||||
resolution: undefined,
|
||||
}
|
||||
};
|
||||
|
||||
// 执行生成器直到完成或需要等待动作
|
||||
const executeRule = () => {
|
||||
try {
|
||||
const result = rule.next();
|
||||
|
||||
if (result.done) {
|
||||
// 规则执行完成,设置结果
|
||||
ctx.resolution = result.value;
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果生成器 yield 了一个动作类型,等待处理
|
||||
// 这里可以扩展为实际的动作处理逻辑
|
||||
const actionType = result.value;
|
||||
|
||||
// 继续执行直到有动作需要处理或规则完成
|
||||
if (!result.done) {
|
||||
executeRule();
|
||||
}
|
||||
} catch (error) {
|
||||
// 规则执行出错,抛出错误
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 使用 effect 来跟踪响应式依赖
|
||||
const dispose = effect(() => {
|
||||
if(ctx.resolution) {
|
||||
if (ctx.resolution !== undefined) {
|
||||
dispose();
|
||||
return;
|
||||
}
|
||||
executeRule();
|
||||
});
|
||||
|
||||
pushContext(rule);
|
||||
// 将规则上下文推入栈中
|
||||
pushContext(ctx);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
function* rule(){
|
||||
const play: Command = yield 'play';
|
||||
}
|
||||
/**
|
||||
* 创建一个规则生成器辅助函数
|
||||
* @param type - 规则类型
|
||||
* @param fn - 规则逻辑函数
|
||||
*/
|
||||
export function createRule<T>(
|
||||
type: string,
|
||||
fn: (ctx: RuleContext<T>) => Generator<string, T, Command>
|
||||
): Generator<string, T, Command> {
|
||||
return fn({
|
||||
type,
|
||||
actions: [],
|
||||
handledActions: 0,
|
||||
invocations: [],
|
||||
resolution: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user