引言
相扑是日本传统的摔跤竞技运动,两名力士在土俵(圆形比赛场地)中竞技,通过推、摔、拉等技巧使对手倒地或逼出界外。本文用Java实现相扑对抗的核心物理模拟与推力平衡算法,讲解重心控制、摩擦力学与博弈决策在竞技对抗中的应用。
物理模型建立
核心物理量
相扑对抗的本质是重心控制与力的平衡。两名力士可简化为具有质量、重心位置和支撑面的刚体模型:
- 质量与身高:影响惯性和稳定性
- 重心高度:重心越低越稳定
- 支撑面:两脚之间的区域构成支撑多边形
- 摩擦力:防止滑动的最大静摩擦力
- 推力:对手施加的水平力
胜负判定条件
- 倒地:重心投影超出支撑面
- 出界:身体任何部位触及土俵外
- 触地:除脚底外任何部位接触地面
核心算法设计
1. 力士数据结构与状态
import java.util.*;
/**
* 相扑力士类
* 包含物理属性、位置状态和竞技行为
*/
class SumoWrestler {
// 力士名称
String name;
// 质量 (kg)
double mass;
// 身高 (m)
double height;
// 重心高度 (m),下蹲时降低
double centerOfMassHeight;
// 当前位置 (x, z)
double posX, posZ;
// 当前速度
double velX, velZ;
// 两脚间距,决定支撑面宽度
double stanceWidth;
// 是否下蹲(降低重心)
boolean isCrouching;
// 是否倒地
boolean isDown;
// 是否出界
boolean isOut;
// 剩余体力
double stamina;
// 最大推力
double maxPushForce;
public SumoWrestler(String name, double mass, double height) {
this.name = name;
this.mass = mass;
this.height = height;
this.centerOfMassHeight = height * 0.55; // 站立时重心约在身高55%处
this.stanceWidth = 0.6; // 默认站距
this.stamina = 100.0;
this.maxPushForce = mass * 2.0; // 最大推力约体重的2倍
this.isDown = false;
this.isOut = false;
this.isCrouching = false;
}
/**
* 获取当前重心在水平面上的投影坐标
* 相扑中通过调整姿势可使重心前后移动
*/
public double[] getCenterOfMassProjection() {
// 前倾时重心前移
double leanOffset = isCrouching ? 0.05 : 0.0;
return new double[]{posX + leanOffset, posZ};
}
/**
* 获取支撑面边界
* 支撑面为两脚之间的矩形区域
*
* @return double[]{minX, maxX, minZ, maxZ}
*/
public double[] getSupportBounds() {
double halfWidth = stanceWidth / 2;
return new double[]{
posX - halfWidth, posX + halfWidth,
posZ - 0.15, posZ + 0.15
};
}
/**
* 判断当前姿势是否稳定
* 重心投影必须在支撑面内
*/
public boolean isStable() {
double[] com = getCenterOfMassProjection();
double[] bounds = getSupportBounds();
return com[0] >= bounds[0] && com[0] <= bounds[1]
&& com[1] >= bounds[2] && com[1] <= bounds[3];
}
/**
* 下蹲动作:降低重心提高稳定性,但移动速度下降
*/
public void crouch() {
isCrouching = true;
centerOfMassHeight = height * 0.35; // 下蹲后重心降低
stanceWidth = 0.8; // 蹲姿两脚更宽
maxPushForce = mass * 2.5; // 蹲姿推力更大
}
/**
* 站起动作:恢复站立姿势
*/
public void standUp() {
isCrouching = false;
centerOfMassHeight = height * 0.55;
stanceWidth = 0.6;
maxPushForce = mass * 2.0;
}
/**
* 应用推力,更新速度和位置
* 考虑摩擦力的限制
*
* @param forceX X方向推力
* @param forceZ Z方向推力
* @param dt 时间步长
*/
public void applyForce(double forceX, double forceZ, double dt) {
// 地面最大静摩擦力 = 摩擦系数 * 正压力
double frictionCoeff = 0.7; // 土俵表面摩擦系数
double normalForce = mass * 9.8; // 正压力 = 重力
double maxFriction = frictionCoeff * normalForce;
// 总推力大小
double totalForce = Math.sqrt(forceX * forceX + forceZ * forceZ);
// 如果推力超过最大静摩擦力,力士开始滑动
if (totalForce > maxFriction) {
// 滑动时受动摩擦力(较小)
double kineticFriction = 0.5 * normalForce;
double scale = kineticFriction / totalForce;
forceX *= scale;
forceZ *= scale;
}
// 更新加速度、速度、位置
double accelX = forceX / mass;
double accelZ = forceZ / mass;
velX += accelX * dt;
velZ += accelZ * dt;
// 速度阻尼(模拟地面阻力)
velX *= 0.95;
velZ *= 0.95;
posX += velX * dt;
posZ += velZ * dt;
// 消耗体力
stamina -= totalForce * dt * 0.001;
}
}
2. 土俵场地与比赛规则
/**
* 相扑土俵场地类
* 直径4.55米的圆形场地
*/
class Dohyo {
// 土俵半径 (m)
static final double RADIUS = 2.275;
// 中心位置
double centerX, centerZ;
public Dohyo(double centerX, double centerZ) {
this.centerX = centerX;
this.centerZ = centerZ;
}
/**
* 判断位置是否在土俵内
*/
public boolean isInside(double x, double z) {
double dx = x - centerX;
double dz = z - centerZ;
return Math.sqrt(dx * dx + dz * dz) <= RADIUS;
}
/**
* 获取到土俵边界的距离
*/
public double distanceToEdge(double x, double z) {
double dx = x - centerX;
double dz = z - centerZ;
double distFromCenter = Math.sqrt(dx * dx + dz * dz);
return RADIUS - distFromCenter;
}
}
3. 推力平衡与对抗模拟
/**
* 相扑对抗物理引擎
* 模拟两名力士的力学交互
*/
class SumoPhysics {
/**
* 计算两名力士之间的相互作用力
* 基于弹性碰撞模型,考虑身体接触变形
*
* @param w1 力士1
* @param w2 力士2
* @return double[]{forceOnW1X, forceOnW1Z, forceOnW2X, forceOnW2Z}
*/
public static double[] calculateInteractionForces(SumoWrestler w1, SumoWrestler w2) {
double dx = w2.posX - w1.posX;
double dz = w2.posZ - w1.posZ;
double distance = Math.sqrt(dx * dx + dz * dz);
// 接触距离阈值(两人身体半径之和约0.6m)
double contactThreshold = 0.6;
if (distance > contactThreshold) {
// 未接触,无相互作用力
return new double[]{0, 0, 0, 0};
}
// 接触时的弹性斥力(防止穿透)
double overlap = contactThreshold - distance;
double springConstant = 50000; // 弹性系数
double elasticForce = springConstant * overlap;
// 方向归一化
double nx = dx / distance;
double nz = dz / distance;
// 推力:每名力士主动施加的力
double push1 = w1.maxPushForce * (w1.stamina / 100.0);
double push2 = w2.maxPushForce * (w2.stamina / 100.0);
// 合力计算
// 力士1受到的力:力士2的推力 + 弹性斥力(方向远离力士2)
double forceOn1X = nx * (push2 + elasticForce);
double forceOn1Z = nz * (push2 + elasticForce);
// 力士2受到的力:力士1的推力 + 弹性斥力(方向远离力士1)
double forceOn2X = -nx * (push1 + elasticForce);
double forceOn2Z = -nz * (push1 + elasticForce);
return new double[]{forceOn1X, forceOn1Z, forceOn2X, forceOn2Z};
}
}
4. AI决策算法:推力平衡策略
/**
* 相扑AI决策类
* 使用推力平衡博弈算法决定每一步动作
*/
class SumoAI {
/**
* 评估当前局面的得分
* 分数越高对我方越有利
*
* @param me 我方力士
* @param opponent 对手力士
* @param dohyo 土俵
* @return 局面评分
*/
public static double evaluatePosition(SumoWrestler me, SumoWrestler opponent, Dohyo dohyo) {
double score = 0;
// 1. 稳定性得分(重心越居中越稳定)
double[] com = me.getCenterOfMassProjection();
double[] bounds = me.getSupportBounds();
double centerX = (bounds[0] + bounds[1]) / 2;
double stabilityScore = 1.0 - Math.abs(com[0] - centerX) / ((bounds[1] - bounds[0]) / 2);
score += stabilityScore * 30;
// 2. 位置得分(离土俵中心越近越有利)
double distToCenter = Math.sqrt(
(me.posX - dohyo.centerX) * (me.posX - dohyo.centerX) +
(me.posZ - dohyo.centerZ) * (me.posZ - dohyo.centerZ)
);
score += (1.0 - distToCenter / Dohyo.RADIUS) * 20;
// 3. 对手不利得分(对手越靠近边界越好)
double opponentDistToEdge = dohyo.distanceToEdge(opponent.posX, opponent.posZ);
score += (1.0 - opponentDistToEdge / Dohyo.RADIUS) * 25;
// 4. 体力优势
score += (me.stamina - opponent.stamina) * 0.5;
// 5. 重心高度优势(越低越稳定)
double heightAdvantage = (opponent.centerOfMassHeight - me.centerOfMassHeight) * 10;
score += heightAdvantage;
return score;
}
/**
* 贪心策略:选择最优动作
* 评估所有候选动作,选择使局面评分最高的
*
* @param me 我方力士
* @param opponent 对手力士
* @param dohyo 土俵
* @return 最优动作
*/
public static Action chooseBestAction(SumoWrestler me, SumoWrestler opponent, Dohyo dohyo) {
List<Action> candidates = generateActions(me);
Action bestAction = null;
double bestScore = Double.NEGATIVE_INFINITY;
for (Action action : candidates) {
// 模拟执行动作
simulateAction(me, opponent, dohyo, action);
double score = evaluatePosition(me, opponent, dohyo);
undoAction(me, opponent, action);
if (score > bestScore) {
bestScore = score;
bestAction = action;
}
}
return bestAction != null ? bestAction : new Action(ActionType.PUSH_FORWARD, 0.5);
}
/**
* 生成候选动作列表
*/
private static List<Action> generateActions(SumoWrestler me) {
List<Action> actions = new ArrayList<>();
// 前冲(主动进攻)
for (double intensity = 0.3; intensity <= 1.0; intensity += 0.35) {
actions.add(new Action(ActionType.PUSH_FORWARD, intensity));
}
// 侧向移动
actions.add(new Action(ActionType.STEP_LEFT, 0.5));
actions.add(new Action(ActionType.STEP_RIGHT, 0.5));
// 下蹲防守
actions.add(new Action(ActionType.CROUCH, 1.0));
// 站起恢复
if (me.isCrouching) {
actions.add(new Action(ActionType.STAND_UP, 1.0));
}
// 后退诱敌(风险高但可诱使对手失衡)
actions.add(new Action(ActionType.RETREAT, 0.3));
return actions;
}
/**
* 模拟执行一个动作
*/
private static void simulateAction(SumoWrestler me, SumoWrestler opponent,
Dohyo dohyo, Action action) {
double dt = 0.1; // 模拟时间步
switch (action.type) {
case PUSH_FORWARD:
// 向对手方向施加推力
double dx = opponent.posX - me.posX;
double dz = opponent.posZ - me.posZ;
double dist = Math.sqrt(dx * dx + dz * dz);
if (dist > 0) {
double force = me.maxPushForce * action.intensity;
me.applyForce(dx / dist * force, dz / dist * force, dt);
}
break;
case STEP_LEFT:
me.applyForce(-me.maxPushForce * 0.3, 0, dt);
break;
case STEP_RIGHT:
me.applyForce(me.maxPushForce * 0.3, 0, dt);
break;
case CROUCH:
me.crouch();
break;
case STAND_UP:
me.standUp();
break;
case RETREAT:
double rdx = me.posX - opponent.posX;
double rdz = me.posZ - opponent.posZ;
double rdist = Math.sqrt(rdx * rdx + rdz * rdz);
if (rdist > 0) {
double rforce = me.maxPushForce * 0.5;
me.applyForce(rdx / rdist * rforce, rdz / rdist * rforce, dt);
}
break;
}
// 模拟对手的被动反应(简化:对手也前冲)
double odx = me.posX - opponent.posX;
double odz = me.posZ - opponent.posZ;
double odist = Math.sqrt(odx * odx + odz * odz);
if (odist > 0) {
double oforce = opponent.maxPushForce * 0.5;
opponent.applyForce(odx / odist * oforce, odz / odist * oforce, dt);
}
}
/**
* 撤销动作(简化恢复)
*/
private static void undoAction(SumoWrestler me, SumoWrestler opponent, Action action) {
// 在实际应用中需要保存/恢复完整状态
// 这里简化处理
}
}
/**
* 动作类型枚举
*/
enum ActionType {
PUSH_FORWARD, // 前冲
STEP_LEFT, // 左移
STEP_RIGHT, // 右移
CROUCH, // 下蹲
STAND_UP, // 站起
RETREAT // 后退
}
/**
* 动作类
*/
class Action {
ActionType type;
double intensity; // 动作强度 0~1
public Action(ActionType type, double intensity) {
this.type = type;
this.intensity = intensity;
}
}
5. 完整比赛模拟
/**
* 相扑比赛模拟器
*/
public class SumoGame {
public static void main(String[] args) {
// 创建土俵
Dohyo dohyo = new Dohyo(0, 0);
// 创建两名力士
SumoWrestler wrestler1 = new SumoWrestler("横纲·山岚", 180, 1.85);
SumoWrestler wrestler2 = new SumoWrestler("大关·海鸣", 165, 1.78);
// 初始位置:土俵两侧
wrestler1.posX = -0.8;
wrestler1.posZ = 0;
wrestler2.posX = 0.8;
wrestler2.posZ = 0;
System.out.println("=== 相扑比赛开始 ===");
System.out.printf("红方: %s (%.0fkg, %.2fm)%n", wrestler1.name, wrestler1.mass, wrestler1.height);
System.out.printf("蓝方: %s (%.0fkg, %.2fm)%n", wrestler2.name, wrestler2.mass, wrestler2.height);
double dt = 0.05; // 50ms时间步
double totalTime = 0;
int step = 0;
final int MAX_STEPS = 2000;
while (step < MAX_STEPS) {
step++;
totalTime += dt;
// 双方AI决策
Action action1 = SumoAI.chooseBestAction(wrestler1, wrestler2, dohyo);
Action action2 = SumoAI.chooseBestAction(wrestler2, wrestler1, dohyo);
// 执行动作
executeAction(wrestler1, wrestler2, dohyo, action1, dt);
executeAction(wrestler2, wrestler1, dohyo, action2, dt);
// 计算相互作用力
double[] forces = SumoPhysics.calculateInteractionForces(wrestler1, wrestler2);
wrestler1.applyForce(forces[0], forces[1], dt);
wrestler2.applyForce(forces[2], forces[3], dt);
// 检查稳定性
if (!wrestler1.isStable()) {
wrestler1.isDown = true;
}
if (!wrestler2.isStable()) {
wrestler2.isDown = true;
}
// 检查出界
if (!dohyo.isInside(wrestler1.posX, wrestler1.posZ)) {
wrestler1.isOut = true;
}
if (!dohyo.isInside(wrestler2.posX, wrestler2.posZ)) {
wrestler2.isOut = true;
}
// 输出状态(每100步输出一次)
if (step % 100 == 0) {
System.out.printf("[%5.1fs] %s: pos(%+.2f, %+.2f) stamina=%.1f | %s: pos(%+.2f, %+.2f) stamina=%.1f%n",
totalTime,
wrestler1.name, wrestler1.posX, wrestler1.posZ, wrestler1.stamina,
wrestler2.name, wrestler2.posX, wrestler2.posZ, wrestler2.stamina);
}
// 判定胜负
String winner = null;
if (wrestler1.isDown || wrestler1.isOut) {
winner = wrestler2.name;
} else if (wrestler2.isDown || wrestler2.isOut) {
winner = wrestler1.name;
}
if (winner != null) {
System.out.println("\n=== 比赛结束 ===");
System.out.println("获胜者: " + winner);
System.out.printf("总用时: %.1f秒, 总步数: %d%n", totalTime, step);
break;
}
}
if (step >= MAX_STEPS) {
System.out.println("\n=== 比赛时间到 ===");
// 根据位置判定(更接近中心者胜)
double d1 = Math.sqrt(wrestler1.posX * wrestler1.posX + wrestler1.posZ * wrestler1.posZ);
double d2 = Math.sqrt(wrestler2.posX * wrestler2.posX + wrestler2.posZ * wrestler2.posZ);
System.out.println("判定结果: " + (d1 < d2 ? wrestler1.name : wrestler2.name));
}
}
private static void executeAction(SumoWrestler me, SumoWrestler opponent,
Dohyo dohyo, Action action, double dt) {
switch (action.type) {
case PUSH_FORWARD:
double dx = opponent.posX - me.posX;
double dz = opponent.posZ - me.posZ;
double dist = Math.sqrt(dx * dx + dz * dz);
if (dist > 0) {
double force = me.maxPushForce * action.intensity;
me.applyForce(dx / dist * force, dz / dist * force, dt);
}
break;
case STEP_LEFT:
me.applyForce(-me.maxPushForce * 0.3, 0, dt);
break;
case STEP_RIGHT:
me.applyForce(me.maxPushForce * 0.3, 0, dt);
break;
case CROUCH:
me.crouch();
break;
case STAND_UP:
me.standUp();
break;
case RETREAT:
double rdx = me.posX - opponent.posX;
double rdz = me.posZ - opponent.posZ;
double rdist = Math.sqrt(rdx * rdx + rdz * rdz);
if (rdist > 0) {
double rforce = me.maxPushForce * 0.5;
me.applyForce(rdx / rdist * rforce, rdz / rdist * rforce, dt);
}
break;
}
}
}
算法复杂度分析
| 组件 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 重心计算 | O(1) | O(1) |
| 稳定性判断 | O(1) | O(1) |
| 作用力计算 | O(1) | O(1) |
| AI决策(贪心) | O(K) | O(1) |
| 单步模拟 | O(K) | O(1) |
| 完整比赛 | O(N·K) | O(1) |
其中 K 为候选动作数,N 为模拟步数。
策略分析
相扑的核心策略
- 低重心原则:下蹲降低重心,扩大支撑面,是最基础也最稳定的防守姿态
- 中心优势:占据土俵中心位置可最大化利用场地空间
- 推力博弈:主动前冲可压制对手,但过度前倾会牺牲自身稳定性
- 体力管理:持续高强度对抗会快速消耗体力,需要把握进攻节奏
算法扩展方向
- 深度博弈树:使用Minimax算法预测多步对抗
- 强化学习:通过自我对弈训练最优策略网络
- 多体物理:引入更精确的刚体动力学和碰撞检测
- 不确定因素:模拟裁判误判、地面湿滑等随机因素
总结
本文通过Java实现了相扑对抗的核心物理模拟与推力平衡算法。关键算法包括:
– 重心稳定性模型:通过重心投影与支撑面的关系判断倾倒
– 弹性碰撞力学:模拟两人接触时的推力与反作用力
– 贪心博弈策略:评估局面并选择最优动作
相扑是研究重心控制、摩擦力学与博弈决策的经典场景,其算法思想在机器人摔跤、自动驾驶避障等领域有广泛应用。