1 /*
2     SPDX-FileCopyrightText: 2007 Paolo Capriotti <p.capriotti@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "controller.h"
8 
9 #include <KLocalizedString>
10 
11 #include "playerentity.h"
12 #include "aientity.h"
13 #include "networkentity.h"
14 #include "seaview.h"
15 #include "shot.h"
16 #include "audioplayer.h"
17 #include "ships.h"
18 
Controller(QObject * parent,AudioPlayer * audioPlayer,const BattleShipsConfiguration & battleShipsConfiguration)19 Controller::Controller(QObject* parent, AudioPlayer* audioPlayer, const BattleShipsConfiguration& battleShipsConfiguration)
20 : QObject(parent)
21 , m_shot(nullptr)
22 , m_ready(0)
23 , m_player(audioPlayer)
24 , m_has_ai(false)
25 , mBattleShipsConfiguration(battleShipsConfiguration)
26 {
27     m_ui = nullptr;
28     m_sea = new Sea(this, battleShipsConfiguration);
29 }
30 
createPlayer(Sea::Player player,SeaView * view,ChatWidget * chat,const QString & nick)31 PlayerEntity* Controller::createPlayer(Sea::Player player, SeaView* view,
32                                               ChatWidget* chat, const QString& nick)
33 {
34     if (m_ui) {
35         qCDebug(KNAVALBATTLE_LOG) << "Cannot create more than one human player";
36         return nullptr;
37     }
38     PlayerEntity* entity = new PlayerEntity(player, m_sea, view, chat);
39     entity->setNick(nick);
40     m_ui = entity;
41     setupEntity(m_ui);
42     return entity;
43 }
44 
createAI(Sea::Player player,SeaView * view)45 AIEntity* Controller::createAI(Sea::Player player, SeaView* view)
46 {
47     qCDebug(KNAVALBATTLE_LOG) << "created ai entity";
48     m_has_ai = true;
49     AIEntity* e = new AIEntity(player, m_sea, view);
50     e->setNick(i18n("Computer"));
51     setupEntity(e);
52 
53     return e;
54 }
55 
createRemotePlayer(Sea::Player player,SeaView * view,Protocol * protocol,bool client)56 NetworkEntity* Controller::createRemotePlayer(Sea::Player player, SeaView* view, Protocol* protocol, bool client)
57 {
58     NetworkEntity* e = new NetworkEntity(player, m_sea, view, protocol, client);
59     setupEntity(e);
60     connect(e, &NetworkEntity::restartRequested, this, &Controller::restartRequested);
61     if (client) {
62         m_sea->switchTurn();
63     }
64     return e;
65 }
66 
setupEntity(Entity * entity)67 void Controller::setupEntity(Entity* entity)
68 {
69     entity->setParent(this);
70 
71     connect(entity, &Entity::shoot,
72             this, &Controller::shoot, Qt::QueuedConnection);
73     connect(entity, &Entity::ready,
74             this, &Controller::ready);
75     connect(entity, &Entity::shipsPlaced,
76             this, &Controller::shipsPlaced);
77     connect(entity, &Entity::chat,
78             this, &Controller::receivedChat);
79     connect(entity, &Entity::nickChanged,
80             this, &Controller::nick);
81     connect(entity, &Entity::compatibility,
82             this, &Controller::compatibility);
83     connect(entity, &Entity::gameOptionsInterchanged,
84             this, &Controller::placing);
85 
86     for (Entity* e : std::as_const(m_entities)) {
87         connect(e, &Entity::compatibility,
88                 entity, &Entity::setCompatibilityLevel);
89         connect(entity, &Entity::compatibility,
90                 e, &Entity::setCompatibilityLevel);
91 
92         connect(e, &Entity::abortGame,
93                 entity, &Entity::notifyAbort);
94         connect(entity, &Entity::abortGame,
95                 e, &Entity::notifyAbort);
96         connect(e, &Entity::restartPlacingShips,
97                 this, &Controller::restartPlacingShips);
98         connect(e, &Entity::restartPlacingShips,
99                 this, &Controller::notifyRestartPlacingShips);
100     }
101 
102     m_entities.append(entity);
103 }
104 
setBattleShipsConfiguration(const BattleShipsConfiguration & battleConfiguration)105 void Controller::setBattleShipsConfiguration(const BattleShipsConfiguration& battleConfiguration)
106 {
107     mBattleShipsConfiguration = battleConfiguration;
108 }
109 
110 
allPlayers() const111 bool Controller::allPlayers() const
112 {
113     unsigned char bitmap = 0;
114     for (Entity* entity : m_entities) {
115         int player = entity->player();
116         qCDebug(KNAVALBATTLE_LOG) << "found player" << player;
117         bitmap |= (1 << player);
118     }
119 
120     qCDebug(KNAVALBATTLE_LOG) << "bitmap =" << (unsigned) bitmap;
121     return bitmap == 3;
122 }
123 
start(SeaView * view)124 bool Controller::start(SeaView* view)
125 {
126     if (!allPlayers()) {
127         return false;
128     }
129 
130     if (!m_ui) {
131         m_ui = new UIEntity(Sea::NO_PLAYER, m_sea, view);
132         setupEntity(m_ui);
133     }
134 
135     for (Entity* entity : std::as_const(m_entities)) {
136         entity->notifyGameOptions();
137     }
138 
139     for (Entity* source : std::as_const(m_entities)) {
140         for (Entity* target : std::as_const(m_entities)) {
141             if (source->player() != target->player() &&
142                 !source->nick().isEmpty()) {
143                 target->notifyNick(source->player(), source->nick());
144             }
145         }
146     }
147 
148     return true;
149 }
150 
restart()151 void Controller::restart()
152 {
153     m_ready = 0;
154     m_sea->clear(Sea::PLAYER_A);
155     m_sea->clear(Sea::PLAYER_B);
156 
157     for (Entity* entity : std::as_const(m_entities)) {
158         m_sea->clear(entity->player());
159             Q_EMIT startPlacingShips(Sea::PLAYER_A);
160             entity->startPlacing();
161     }
162 }
163 
164 
165 // It is sure the entities has interchanged the GameOptions (if any)
166 // when the opposite nick is received
placing()167 void Controller::placing()
168 {
169     for (Entity* entity : std::as_const(m_entities)) {
170         entity->startPlacing();
171     }
172 }
173 
shoot(int player,const Coord & c)174 void Controller::shoot(int player, const Coord& c)
175 {
176     Entity* entity = findEntity(Sea::opponent(Sea::Player(player)));
177     if (!entity) {
178         qCDebug(KNAVALBATTLE_LOG) << "no entity!";
179         return;
180     }
181 
182     if (m_shot) {
183         qCDebug(KNAVALBATTLE_LOG) << "shot in progress";
184         // shot in progress
185         return;
186     }
187 
188     if (m_sea->status() == Sea::PLAYING) {
189         entity->hit(m_shot = new Shot(this, Sea::Player(player), c)); // kind of CPS
190     }
191 }
192 
finalizeShot(Sea::Player player,const Coord & c,const HitInfo & info)193 void Controller::finalizeShot(Sea::Player player, const Coord& c, const HitInfo& info)
194 {
195     if (info.type != HitInfo::INVALID) {
196         // notify entities
197         notify(player, c, info);
198 
199         // play sounds
200         if (m_player) {
201             m_player->play(player, info);
202         }
203 
204         if (m_sea->status() == Sea::A_WINS) {
205             finalizeGame(Sea::PLAYER_A);
206         }
207         else if (m_sea->status() == Sea::B_WINS) {
208             finalizeGame(Sea::PLAYER_B);
209         }
210         else {
211             Q_EMIT turnChanged(m_sea->turn());
212         }
213     }
214     else {
215         qCDebug(KNAVALBATTLE_LOG) << "illegal move" << c << "for player" << player;
216     }
217 
218     delete m_shot;
219     m_shot = nullptr;
220 }
221 
notify(Sea::Player player,const Coord & c,const HitInfo & info)222 void Controller::notify(Sea::Player player, const Coord& c, const HitInfo& info)
223 {
224     for (Entity* entity : std::as_const(m_entities)) {
225         entity->notify(player, c, info);
226         if (player == entity->player()) {
227             entity->stats()->addInfo(info);
228         }
229     }
230 }
231 
shipsPlaced()232 void Controller::shipsPlaced()
233 {
234     m_ready++;
235     if (m_ready >= 2 )
236     {
237         for (Entity* entity : std::as_const(m_entities)) {
238             entity->start();
239         }
240     }
241 }
242 
243 
ready(int player)244 void Controller::ready(int player)
245 {
246     m_ready++;
247     for (Entity* entity : std::as_const(m_entities)) {
248         entity->notifyReady(Sea::Player(player));
249     }
250     // when two entities are ready (ships placed and ready)
251     // start all engines
252     if (m_ready >= 4) {
253         m_sea->startPlaying();
254 
255         for (Entity* entity : std::as_const(m_entities)) {
256             entity->startPlaying();
257         }
258         Q_EMIT playerReady(-1);
259     }
260     else {
261         Q_EMIT playerReady(player);
262     }
263 }
264 
finalizeGame(Sea::Player winner)265 void Controller::finalizeGame(Sea::Player winner)
266 {
267     // first, every entity will notify the other entity its ships
268     for (Entity* entity : std::as_const(m_entities)) {
269             entity->notifyShips(winner);
270     }
271     // then, it will notify the end of the game
272     for (Entity* entity : std::as_const(m_entities)) {
273         entity->notifyGameOver(winner);
274     }
275     Q_EMIT gameOver(winner);
276 }
277 
notifyRestartPlacingShips(Sea::Player player)278 void Controller::notifyRestartPlacingShips(Sea::Player player)
279 {
280     for (Entity* entity : std::as_const(m_entities)) {
281         if (entity->player() == player) {
282             entity->notifyRestartPlacing(player);
283         }
284     }
285 }
286 
findEntity(Sea::Player player) const287 Entity* Controller::findEntity(Sea::Player player) const
288 {
289     for (Entity* entity : m_entities) {
290         if (entity->player() == player) {
291             return entity;
292         }
293     }
294 
295     return nullptr;
296 }
297 
receivedChat(const QString & text)298 void Controller::receivedChat(const QString& text)
299 {
300     Entity* chat_sender = qobject_cast<Entity*>(sender());
301 
302     if (chat_sender) {
303         for (Entity* entity : std::as_const(m_entities)) {
304             if (entity != chat_sender) {
305                 qCDebug(KNAVALBATTLE_LOG) << "forwarding to" << entity->nick();
306                 entity->notifyChat(chat_sender, text);
307             }
308         }
309     }
310 }
311 
nick(int player,const QString & nick)312 void Controller::nick(int player, const QString& nick)
313 {
314     qCDebug(KNAVALBATTLE_LOG) << "controller: nick";
315     for (Entity* entity : std::as_const(m_entities)) {
316         if (entity->player() != Sea::Player(player)) {
317             entity->notifyNick(Sea::Player(player), nick);
318         }
319     }
320     Q_EMIT nickChanged(player, nick);
321 }
322 
turn() const323 Sea::Player Controller::turn() const
324 {
325     return m_sea->turn();
326 }
327 
hasAI() const328 bool Controller::hasAI() const
329 {
330     return m_has_ai;
331 }
332 
333 
334