Към съдържанието
wannacode

Epic Cosmos 1: обектите в космоса

Проблем

Космическа надпревара

Човечеството вече може да лети до далечни планети. Корпорации и държави се надпреварват за ресурсите им. Ти управляваш една от мисиите и искаш да станеш най-силната сила в космоса.

Правилата на Epic Cosmos:

  • Точки се трупат с откриване на нови планети и обекти, добив на ресурси, строеж на сгради и кораби, научни открития.
  • Обектите са звезди, планети и спътници. Звездата дава енергия. Някои планети имат редки ресурси, а атмосферата им решава какво може да се строи. Спътникът е за връзка и разузнаване.
  • Ресурсите са нужни за строеж, проучвания и поддръжка на базите и корабите.
  • Сградите добиват ресурси, проучват технологии, пазят базата и правят кораби.
  • Технологиите отключват нови сгради, кораби и умения.
  • Конфликти: в космоса не си сам. Има пирати и други играчи.
  • Край: играта свършва, когато някой стигне определен брой точки или след определен брой ходове. Печели този с най-много точки.

Преди сградите и корабите ти трябва самият свят: какви обекти има, къде са и кой около кого може да обикаля. Това е урокът. Ресурсите, сградите и корабите са в следващия.

Идеята

Всичко в космоса е SpaceObject

Звездата, планетата и спътникът имат общо: име, координати x, y, z и маса. Общото отива в абстрактен клас:

abstract class SpaceObject {
private String name;
private double coordinateX;
private double coordinateY;
private double coordinateZ;
private double mass;
public SpaceObject(String name, double coordinateX, double coordinateY, double coordinateZ, double mass) {
this.name = name;
this.coordinateX = coordinateX;
this.coordinateY = coordinateY;
this.coordinateZ = coordinateZ;
this.mass = mass;
}
public String getName() {
return name;
}
public double getCoordinateX() {
return coordinateX;
}
public double getCoordinateY() {
return coordinateY;
}
public double getCoordinateZ() {
return coordinateZ;
}
public double getMass() {
return mass;
}
public abstract String getKind();
public void displayInfo() {
System.out.printf("%s %s: (%.1f, %.1f, %.1f), маса %.2f%n", getKind(), name, coordinateX, coordinateY, coordinateZ, mass);
}
protected void moveTo(double x, double y, double z) {
coordinateX = x;
coordinateY = y;
coordinateZ = z;
}
}
  • Координатите са в милиони километри от Слънцето, а масата е в земни маси: Земята е 1, Слънцето е 333 000.
  • DisplayInfo() е общ за всички. Видът (GetKind()) е абстрактен: всеки наследник казва сам какъв е.
  • MoveTo е protected: обектите не се местят отвън, но кораб, който е наследник, ще може да се мести.

Звездата е най-простият наследник:

class Star extends OrbitableSpaceObject {
public Star(String name, double coordinateX, double coordinateY, double coordinateZ, double mass) {
super(name, coordinateX, coordinateY, coordinateZ, mass);
}
@Override
public String getKind() {
return "Звезда";
}
}

Трите вида обекти в един масив от SpaceObject. Цикълът не знае кой какъв е, а всеки казва вида си:

SpaceObject[] objects = {
new Star("Слънце", 0, 0, 0, 333000),
new Planet("Земя", 149.6, 0, 0, 1),
new Satellite("Луна", 150, 0, 0, 0.012),
};
for (SpaceObject object : objects) {
object.displayInfo();
}
Изход
Звезда Слънце: (0.0, 0.0, 0.0), маса 333000.00
Планета Земя: (149.6, 0.0, 0.0), маса 1.00
Спътник Луна: (150.0, 0.0, 0.0), маса 0.01

Идеята

Около кого се обикаля

Около звездата обикалят планети, около планетата обикалят спътници. И звездата, и планетата трябва да помнят кой обикаля около тях. Това е OrbitableSpaceObject: абстрактен клас между SpaceObject и тях.

abstract class OrbitableSpaceObject extends SpaceObject {
private SpaceObject[] orbiters = new SpaceObject[10];
private int orbiterCount = 0;
public OrbitableSpaceObject(String name, double coordinateX, double coordinateY, double coordinateZ, double mass) {
super(name, coordinateX, coordinateY, coordinateZ, mass);
}
public void addOrbiter(SpaceObject orbiter) {
if (orbiterCount == orbiters.length) {
throw new IllegalStateException("Около " + getName() + " вече обикалят " + orbiters.length + " обекта");
}
orbiters[orbiterCount] = orbiter;
orbiterCount++;
}
public SpaceObject[] getOrbiters() {
SpaceObject[] result = new SpaceObject[orbiterCount];
for (int i = 0; i < orbiterCount; i++) {
result[i] = orbiters[i];
}
return result;
}
}
  • Обикалящите са в масив с място за 10 и брояч на заетите места. Новият отива на мястото с номер брояча, после броячът расте.
  • Масивът не може да расте. Ако се запълни, AddOrbiter хвърля готовото изключение за „неподходящ момент“.
  • GetOrbiters() връща нов масив точно със заетите места, без празните.

Списък, който расте сам, е по-удобен. Той идва в „Уеб и десктоп“, тогава масивът и броячът стават един ред.

По-точно, за любопитните
  • GetOrbiters() връща копие, а не самия масив. Иначе код отвън може да запише в масива и броячът ще излъже.
  • Star наследява OrbitableSpaceObject, а не направо SpaceObject. Така звездата получава и обикалящите, и всичко от SpaceObject.

Идеята

Правилото за орбитите

Планета обикаля около звезда. Спътник обикаля около планета. Всичко друго е невалидно. Обектите, които могат да обикалят, имат общ договор:

interface Orbiter {
void orbit(SpaceObject target) throws NotValidSpaceObjectException;
}

Ако правилото е нарушено, Orbit хвърля свое изключение. Името казва точно какво е станало:

class NotValidSpaceObjectException extends Exception {
public NotValidSpaceObjectException(String message) {
super(message);
}
}

Ето как го пази планетата:

@Override
public void orbit(SpaceObject target) throws NotValidSpaceObjectException {
if (target instanceof Star star) {
star.addOrbiter(this);
System.out.println(getName() + " обикаля около " + star.getName());
} else {
throw new NotValidSpaceObjectException(getName() + " не може да обикаля около " + target.getName() + ": планета обикаля само звезда");
}
}
  • target is Star star проверява типа и веднага дава променлива от тип Star.
  • Ако проверката не мине, изключението спира метода. Планетата не се добавя никъде.
  • Satellite има същия Orbit, само че проверява за Planet.
По-точно, за любопитните

В C# няма проверени изключения. Интерфейсът не казва, че Orbit може да хвърли, и компилаторът не кара викащия да го хване. Програмата ще спре, ако никой не го хване, затова в играта винаги го хващаме.

Виж го

Кой около кого

Star sun = new Star("Слънце", 0, 0, 0, 333000);
Planet earth = new Planet("Земя", 149.6, 0, 0, 1);
Satellite moon = new Satellite("Луна", 150, 0, 0, 0.012);
TryOrbit(earth, sun);
TryOrbit(moon, earth);
TryOrbit(moon, sun);
Стъпка по стъпка, с думи
  1. Три обекта: звезда, планета и спътник, всеки със свои координати и маса.
  2. Планета около звезда: правилото го позволява, Земята тръгва по орбитата.
  3. Спътник около планета: позволено. Луната обикаля Земята и се движи с нея.
  4. Спътник около звезда: Orbit хвърля NotValidSpaceObjectException, а catch в TryOrbit печата защо.

Земята минава проверката на звездата и се добавя към обикалящите я. Луната минава проверката на Земята. Когато Луната опита да обикаля около Слънцето, проверката за Planet не минава. Orbit хвърля и Луната остава около Земята.

Изключението се хваща в малък помощен метод. Той пробва орбитата и при отказ казва защо:

static void tryOrbit(Orbiter orbiter, SpaceObject target) {
try {
orbiter.orbit(target);
} catch (NotValidSpaceObjectException e) {
System.out.println("Отказано: " + e.getMessage());
}
}

Пробвай

Колко обикалят?

Марс опитва да обикаля около Земята.

Какво ще отпечата програмата?

static void TryOrbit(Orbiter orbiter, SpaceObject target)
{
try
{
orbiter.Orbit(target);
}
catch (NotValidSpaceObjectException e)
{
Console.WriteLine("Отказано: " + e.Message);
}
}
Star sun = new Star("Слънце", 0, 0, 0, 333000);
Planet earth = new Planet("Земя", 149.6, 0, 0, 1);
Planet mars = new Planet("Марс", 227.9, 0, 0, 0.11);
Satellite moon = new Satellite("Луна", 150, 0, 0, 0.012);
TryOrbit(earth, sun);
TryOrbit(mars, earth);
TryOrbit(moon, earth);
Console.WriteLine("Около Слънце: " + sun.GetOrbiters().Length);
Console.WriteLine("Около Земя: " + earth.GetOrbiters().Length);
Отговор

Идеята

Кораб, който лети

Корабите не обикалят, а летят от обект до обект. Това е друг договор:

interface Fly {
void flyTo(SpaceObject destination) throws NotValidSpaceObjectException;
}

SpaceShip наследява SpaceObject и изпълнява Fly. До звезда не може да лети. До всичко друго лети и взима координатите на целта:

@Override
public void flyTo(SpaceObject destination) throws NotValidSpaceObjectException {
if (destination instanceof Star) {
throw new NotValidSpaceObjectException(getName() + " не може да лети до " + destination.getName() + ": звездата ще го изгори");
}
moveTo(destination.getCoordinateX(), destination.getCoordinateY(), destination.getCoordinateZ());
System.out.println(getName() + " пристигна при " + destination.getName());
}
SpaceShip ship = new SpaceShip("Скиталец", 149.6, 0, 0, 0);
Planet mars = new Planet("Марс", 227.9, 0, 0, 0.11);
Star sun = new Star("Слънце", 0, 0, 0, 333000);
try {
ship.flyTo(mars);
ship.displayInfo();
ship.flyTo(sun);
ship.displayInfo();
} catch (NotValidSpaceObjectException e) {
System.out.println("Отказано: " + e.getMessage());
}
Изход
Скиталец пристигна при Марс
Кораб Скиталец: (227.9, 0.0, 0.0), маса 0.00
Отказано: Скиталец не може да лети до Слънце: звездата ще го изгори

Вторият DisplayInfo() не се изпълнява: изключението прескача до catch.

По-точно, за любопитните
Един клас може да изпълнява няколко интерфейса. Спътник за разузнаване, който може и да лети, би бил class Probe : SpaceObject, Orbiter, Fly.

Пробвай

Намери грешката

Луната обикаля около Слънцето, а не трябва. На кой ред в Orbit на спътника е грешката?

Избери реда с грешката.

Упражнение

Космосът в код

Реши задачите в редактора си, с класовете от урока.

1. Слънчевата система. Опиши осемте планети с три масива: имена, разстояния от Слънцето (милиони км) и маси (земни маси). В цикъл създай всяка планета на x = разстоянието и я накарай да обикаля около Слънцето. После покажи всички обикалящи.

Планета Разстояние Маса
Меркурий 57,9 0,06
Венера 108,2 0,82
Земя 149,6 1
Марс 227,9 0,11
Юпитер 778,5 317,8
Сатурн 1432 95,2
Уран 2867 14,5
Нептун 4515 17,1
Изход
Меркурий обикаля около Слънце
Венера обикаля около Слънце
Земя обикаля около Слънце
Марс обикаля около Слънце
Юпитер обикаля около Слънце
Сатурн обикаля около Слънце
Уран обикаля около Слънце
Нептун обикаля около Слънце
Около Слънце обикалят 8 планети:
Планета Меркурий: (57.9, 0.0, 0.0), маса 0.06
Планета Венера: (108.2, 0.0, 0.0), маса 0.82
Планета Земя: (149.6, 0.0, 0.0), маса 1.00
Планета Марс: (227.9, 0.0, 0.0), маса 0.11
Планета Юпитер: (778.5, 0.0, 0.0), маса 317.80
Планета Сатурн: (1432.0, 0.0, 0.0), маса 95.20
Планета Уран: (2867.0, 0.0, 0.0), маса 14.50
Планета Нептун: (4515.0, 0.0, 0.0), маса 17.10

2. Разстояние. Напиши static double Distance(SpaceObject a, SpaceObject b) с теоремата на Питагор в три измерения: корен от сбора на квадратите на разликите по x, y и z. Земята е в (149,6; 0; 0), Марс е в (0; 227,9; 0), Луната е в (149,6; 0,4; 0), а Слънцето е в началото.

Изход
Земя – Марс: 272.6 млн. км
Земя – Луна: 0.4 млн. км
Слънце – Марс: 227.9 млн. км

3. Цялото дърво. Промени DisplayInfo така, че звездата да показва и планетите си, а всяка планета и спътниците си, с два интервала отстъп на ниво. Подсказка: метод с параметър за отстъпа, който OrbitableSpaceObject презаписва (virtual в SpaceObject). Земята и Марс обикалят Слънцето, Луната обикаля Земята, Фобос обикаля Марс.

Изход
Земя обикаля около Слънце
Марс обикаля около Слънце
Луна обикаля около Земя
Фобос обикаля около Марс
Звезда Слънце: (0.0, 0.0, 0.0), маса 333000.00
Планета Земя: (149.6, 0.0, 0.0), маса 1.00
Спътник Луна: (150.0, 0.0, 0.0), маса 0.01
Планета Марс: (227.9, 0.0, 0.0), маса 0.11
Спътник Фобос: (227.9, 0.0, 0.0), маса 0.00

Още задачи

  • Двоен опит. Сега Земята може да се добави два пъти към обикалящите Слънцето. Направи така, че втори опит да хвърля NotValidSpaceObjectException.
  • Пълна звезда. Добави 11 планети към една звезда и хвани изключението при единадесетата.
  • Сонда. Направи Probe, която и обикаля, и лети (виж „По-точно“ при корабите).

Тест

Провери се

1. Защо GetKind() е абстрактен метод, а DisplayInfo() не е?
2. Защо OrbitableSpaceObject пази и брояч, освен масива?
3. Спътник опитва moon.Orbit(sun). Какво става?
4. В C# Orbit може да хвърли NotValidSpaceObjectException. Какво трябва да пише в интерфейса Orbiter за това?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·