我正在试图找出用于管理两个交互对象之间“竞争”的最佳设计模式。例如,如果我想要一个Fox
类通过一个简单的环境追逐Rabbit
类。我想让他们“竞争”并找出哪一个获胜。最终它将成为学生可以用来试验继承和其他OO编程技能的教学工具。
此用例是否有既定的设计模式?
这是我能想到的最好的:一个类来表示托管其他对象的环境。我保持它非常简单,并假设动物只是直线运行,如果他足够接近兔子,狐狸抓住了兔子。这是一些代码,展示了我所描述的内容。我使用PHP是因为我可以快速编写它,但我不想专注于语言的细节。我的问题是关于设计模式/架构。
class Forrest() {
public $fox;
public $rabbit;
public $width = 100; //meters?
public $length = 100;
__construct() {
$this->fox = new Fox();
$this->rabbit = new Rabbit();
$this->theChase();
}
public function theChase() {
while (!$this->rabbit->isBitten) {
$this->rabbit->react($fox);
$this->fox->react($rabbit);
}
log('The fox got the rabbit!');
}
}
abstract class Animal() {
public $speed;
public $hasTeeth = false;
public $position;
public $direction;
public $isBitten = false;
public function run($distance) {
// update coordinates based on direction and speed
}
public function bite($someone) {
if (isCloseEnough( $someone ) && $this->hasTeeth) {
$someone->isBitten = true;
log(get_class($this) . ' bit the ' . get_class($someone)); //the Fox bit the Rabbit
}
}
public abstract function react($someone);
}
class Rabbit extends Animal {
__construct() {
$this->speed = 30;
$this->position = [0,0];
$this->direction = 'north';
log(get_class($this) . ' starts at 0,0');
}
public react($fox) {
//avoid the fox
}
}
class Fox extends Animal {
__construct() {
$this->speed = 20;
$this->position = [100,100];
$this->direction = 'south';
log (get_class($this) . ' starts at 100,100');
}
public react($rabbit) {
//try to catch the rabbit
}
}
我看到这种方法存在两个直接问题:
此架构会产生连续的交替操作。换句话说,首先兔子做了什么然后狐狸做了什么然后兔子做了什么...这更像是一个纸牌游戏,每个玩家轮流移动。如果两个物体能够同时作出反应会更有趣。
目前还没有一个限制每次“转弯”活动量的系统。需要对单个“转弯”中可能发生的事情进行某种限制以保持其有趣。否则,狐狸可能只是run()
run()
run()
... run()
,直到它第一次抓住兔子为止。
我可能还没有注意到其他问题。我怀疑上述(1)和(2)的答案都是某种事件系统,允许一只动物采取行动从另一只动物触发行动,反之亦然。我也认为可能需要对每个动作的时间进行一些表示,但我并不完全确定。
答案 0 :(得分:3)
因此,您的任务是在设计模式下安装类似游戏的东西,最初只为企业类软件创建。根据定义,游戏不是企业软件,这也是许多人在设计游戏时避免考虑设计模式的原因。但这并不意味着它不可行。
我的建议:
所以,如果你把上面两个结合起来(我希望第二个不在那里),那么我就是这样设计的(如果我的建议提醒我,我会提到设计模式):
步骤#4涉及一些复杂性,特别是如果准确性至关重要的话。例如如果时间跨度大约是一秒钟,并且在那一秒内(在中间的某个地方),狐狸会抓住兔子,但最终还是有距离?如果狐狸和兔子的速度是时间函数(有时它们减速,有时它们加速)会发生这种情况[顺便说一下,这听起来像一个策略模式 - 用于计算当前速度的变化 - 例如线性与时间函数)。显然,如果狐狸和兔子都只是在时间跨度结束时报告他们的位置,那么将错过捕捉时刻,这是不可取的。
以下是我将如何解决它:对于给定的时间跨度,如果超过一毫秒(假设毫秒是最短的可接受原子时间以获得足够高的准确度),则将其拆分进入每个毫秒长度的时间跨度,并且对于每毫秒,要求每个对象更新其状态。毕竟,如果对象可以根据时间跨度更新其状态,我们可以根据需要多次调用它,缩短时间跨度。显然,有不可避免的副作用 - 你需要按某种顺序更新状态,但鉴于毫秒太短的时间,这样做应该没问题。
伪代码看起来像这样:
var foxAndRabbitGame = new FoxAndRabbitGame();
foxAndRabbitGame.RunGame(screen); //visitor
/* when this line is reached, game is over. Do something with it. */
class FoxAndRabbitGame
{
private fox = new Fox(Speed.Linear()); //strategy
private rabbit = new Rabbit(Speed.Linear()); //strategy
void RunGame(screen)
{
var currentTime = NOW;
while (true)
{
var timePassed = NOW - currentTime;
currentTime = NOW;
foreach (millisecond in timePassed)
{
fox.UpdateState ( millisecond , rabbit );
rabbit.UpdateState ( millisecond, fox );
if (fox.TryBite(rabbit))
{
//game over.
return;
}
}
//usually, drawing is much slower than calculating state,
//so we do it once, after all calculations.
screen.Draw(this); //visitor
screen.Draw(Fox); //visitor
screen.Draw(rabbit); //visitor
}
}
}
答案 1 :(得分:2)
在游戏循环中,通常会更新两个对象的速度(此处在您的反应函数中),然后更新对象的位置。因此同时移动。
while(!gameOver) {
rabbit->react(fox);
fox->react(rabbit);
rabbit->updatePosition();
fox->updatePosition();
}
为了限制每转/每帧的活动,你必须想到一些聪明的东西。例如,您可以制作一组可以执行的操作,并且每个操作都有能源成本。每回合你都会获得一定的能量。你必须有多个run()动作才能使它变得有趣:)。
答案 2 :(得分:2)
答案 3 :(得分:1)
总体而言,我的方法是:
在更一般的视图中,您可以将该环境视为具有追溯的封闭系统:每个操作都会修改将影响新操作的整个状态。在这种情况下,每个构造仍然是顺序的,但每个“转向”确实是从一个状态到下一个状态的封闭事务,其中所有主体同时执行。
答案 4 :(得分:1)
阐明中介模式建议,为了增加同时性的错觉,可以将游戏状态提取到单独的对象(普通旧数据),并在所有对象做出决定后更新。例如(在java-ish语言中)
public class OpponentData {
private Position theirPosition; // + public get
// constructor with theirPosition param, keeping the class immutable
}
public interface Animal {
// returns data containing their updated data
OpponentData React(OpponentData data);
Position GetPosition();
}
public class Fox implements Animal {
public OpponentData React(OpponentData data) {
if (this.position == data.GetPosition())
// this method can be a little tricky to write, depending on your chosen technology, current architecture etc
// Fox can either have a reference to GameController to inform it about victory, or fire an event
// or maybe even do it itself, depending if you need to destroy the rabbit object in game controller
EatTheRabbit();
else {
// since the game state won't be updated immediately, I can't just run until I reach the rabbit
// I can use a bunch of strategies: always go 1 meter forward, or use a random value or something more complicated
ChaseTheRabbit();
}
return new OpponentData(this.position);
}
}
public class Rabbit implements Animal {
public OpponentData React(OpponentData data) {
KeepRunningForYourLife();
// maybe you can add something more for the rabbit to think about
// for example, bushes it can run to and hide in
return new OpponentData(this.position);
}
}
public class GameController {
private Fox fox;
private Rabbit rabbit;
private OpponentData foxData;
private OpponentData rabbitData;
private void Init() {
fox = new Fox();
rabbit = new Rabbit();
foxData = new OpponentData(fox.GetPosition());
rabbitData = new OpponentData(rabbit.GetPosition());
}
private void PerformActions() {
var oldData = foxData;
foxData = fox.React(rabbitData);
// giving the old data to the rabbit so it doesn't know where the fox just moved
rabbitData = rabbit.React(oldData);
}
}
如果您希望游戏依赖于更多因素而不仅仅是位置,您可以轻松扩展OpponentData
类,增加健康水平,力量等。
这种方法解决了你的问题,因为每个玩家(狐狸和兔子)都不知道对方在同一回合做了什么,所以兔子可以逃避狐狸而狐狸不能只是{{1它是受害者(因为它不知道兔子会在哪里移动)。有趣的事实 - 权力的游戏棋盘游戏使用相同的技术创造一种与其他玩家同时向你的军队下达命令的错觉。
答案 5 :(得分:1)
我想,应该有两个与动物抽象类相关的抽象类。(杂食动物和食肉动物类,都有不同的属性)
这里是动物抽象类
public abstract class Animal implements Runnable{
private double speed = 0 ; // Default
private Point location = new Point(new Random().nextInt(50) + 1 , new Random().nextInt(50) + 1);
abstract void runAway(Animal animal);
abstract void chase(Animal animal);
abstract void search4Feed();
abstract void feed();
public synchronized Point getLocation() {
return location;
}
public synchronized void setLocation(Point location) {
this.location = location;
}
public double getSpeed() {
return speed;
}
public void setSpeed(double speed) {
this.speed = speed;
}
}
这是Carnivore和Omnivore Classes
public abstract class Carnivore extends Animal {
Animal targetAnimal ;
}
public abstract class Omnivore extends Animal {
Animal chasingAnimal;
}
对于森林类及其实施,Iforest可以由不同的森林类实施。它需要保持自己的动物生态系统。
public class Forest implements IForest {
private List<Animal> animalList = new ArrayList<Animal>();
public Forest() {
}
@Override
public void addAnimalToEcoSystem(Animal animal) {
animalList.add(animal);
}
@Override
public void removeAnimalFromEcoSystem(Animal animal) {
animalList.remove(animal);
}
@Override
public void init() {
// to do:
}
@Override
public List<Animal> getAnimals() {
return this.animalList;
}
}
public interface IForest {
void removeAnimalFromEcoSystem(Animal animal);
void addAnimalToEcoSystem(Animal animal);
List<Animal> getAnimals();
void init();
}
这是兔子和狐狸类。 Rabbit和fox类在它们的构造函数中有IForest类实例。 追逐动物或逃离任何动物都需要成为森林生态系统 这些类必须通过IForest接口通知他们对Forest类的移动。这里我使用了Runnable线程,因为这些类需要独立移动,而不是顺序移动。在run方法中,你可以根据你指定的条件定义猎人或者狩猎的规则。
public class Rabbit extends Omnivore {
private IForest forest = null ;
public Rabbit(IForest forest) {
this.forest = forest;
this.setSpeed(40);
}
@Override
public void runAway(Animal animal) {
this.chasingAnimal = animal;
this.run();
}
@Override
public void chase(Animal animal) {
// same as fox's
}
@Override
void feed() {
// todo:
}
@Override
void search4Feed() {
}
@Override
public void run() {
double distance = 10000; //default,
this.chasingAnimal.runAway(this); // notify rabbit that it has to run away
while(distance < 5){ // fox gives chasing up when distance is greater than 5
distance = Math.hypot(this.getLocation().x - this.chasingAnimal.getLocation().x,
this.getLocation().y - this.chasingAnimal.getLocation().y);
if(distance < 1) {
break; // eaten
}
//here set new rabbit's location according to rabbit's location
}
}
}
public class Fox extends Carnivore {
private IForest forest = null ;
public Fox(IForest forest) {
this.forest = forest;
this.setSpeed(60);
}
@Override
public void chase(Animal animal) {
this.targetAnimal = animal;
this.run();
}
@Override
public void run() {
double distance = 10000; //default,
this.targetAnimal.runAway(this); // notify rabbit that it has to run away
while(distance < 5){ // fox gives chasing up when distance is greater than 5
distance = Math.hypot(this.getLocation().x - this.targetAnimal.getLocation().x,
this.getLocation().y - this.targetAnimal.getLocation().y);
if(distance < 1) {
feed();
break;
}
//here set new fox's location according to rabbit's location
}
}
@Override
public void runAway(Animal animal) {
// same as rabbit's
}
@Override
public void feed() {
// remove from forest's animal list for the this.targetAnimal
}
@Override
void search4Feed() {
// here fox searches for closest omnivore
double distance = -1;
Animal closestFeed = null;
List<Animal> l = this.forest.getAnimals();
for (Animal a : l) {
double d = Math.hypot(this.getLocation().x - a.getLocation().x, this.getLocation().y - a.getLocation().y);
if (distance != -1) {
if(d < distance){
this.chase(a);
}
}
else{
distance = d ;
}
}
}
} 下面的init方法
public static void main(String[] args) {
// you can use abstract factory pattern instead.
IForest forest = new Forest();
forest.addAnimalToEcoSystem(new Rabbit(forest));
forest.addAnimalToEcoSystem(new Fox(forest));
forest.init();
}
如果你想让这个更复杂,比如合作或其他什么 你需要使用可观察的模式。 它可以用于通过抽象工厂模式创建动物,森林。 很抱歉因为我没有太多时间而乱码。 我希望这会对你有所帮助。
答案 6 :(得分:1)
兔子,狐狸和可能的其他动物之间的竞争可以使用discrete event simulation建模,可以将其视为设计模式本身(模拟时钟,事件队列......)。 / p>
对象可以实现Strategy pattern。在这种情况下,execute
方法可以命名为decideAction
- 它将获得世界的旧状态(只读)并产生决策(操作描述)。
模拟将计划由决策产生的事件。处理事件时,模拟将改变世界的状态。因此,模拟可以被认为是Mediator pattern的一个实例,因为它将代理与直接交互隔离开来 - 他们只看到世界的状态并产生决策,同时生成新的世界状态和评估规则(如如速度和成功咬合或逃逸的检测)留待模拟。
为了使所有代理人(动物)同时做出决定,计划所有事件同时发生(在模拟时间内)并且仅在处理了在相同模拟时间发生的所有事件之后更新世界状态(决定已经制造)。
然后,您将不需要事件队列和模拟时钟。只需一个循环来收集所有决策并最终在每次迭代中更新世界状态就足够了。
但这可能不是您想要的,例如,因为最初,兔子可能需要一些时间来注意到狐狸正在接近。如果动物的事件之间的超时(反应时间)随着状态(警报,睡眠等)而变化,则可能更有趣。
当动物可以直接改变世界状态并且使用几乎任意的代码实现时,每个“转弯”的活动量不能被限制。如果动物只描述了它的动作,它的类型和参数可以通过模拟验证,并可能被拒绝。