diff --git a/CMakeLists.txt b/CMakeLists.txt index a2823b4..e4feef1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,10 +6,10 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules") function(setup_target NAME) set_property(TARGET ${NAME} PROPERTY CXX_STANDARD 14) set_property(TARGET ${NAME} PROPERTY CXX_STANDARD_REQUIRED ON) - target_compile_options(${NAME} PRIVATE -Wall -Wextra) - target_compile_options(${NAME} PRIVATE -fdiagnostics-color=always) - target_compile_options(${NAME} PRIVATE $<$:-ggdb -O2>) - target_compile_options(${NAME} PRIVATE $<$:-O3 -NDEBUG>) + #target_compile_options(${NAME} PRIVATE -Wall -Wextra) + #target_compile_options(${NAME} PRIVATE -fdiagnostics-color=always) + #target_compile_options(${NAME} PRIVATE $<$:-ggdb -O2>) + #target_compile_options(${NAME} PRIVATE $<$:-O3 -NDEBUG>) endfunction(setup_target) add_subdirectory(game) diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 15c0651..cd611b0 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -22,10 +22,6 @@ set(GAME_SRC util.cpp game.cpp - # TODO: make optional! - sound/sound.cpp - sound/sound_effects.cpp - state/object.cpp state/explosion.cpp state/trace.cpp @@ -37,6 +33,14 @@ set(GAME_SRC state/state.cpp ) +set(SOUND_LIBRARIES "") + + # TODO: make optional! +set(GAME_SRC ${GAME_SRC} sound/sound.cpp sound/sound_effects.cpp) + +set(SOUND_LIBRARIES -lportaudio -lsndfile) +#set(GAME_SRC "${GAME_SRC} sound/dummy_sound.cpp sound/dummy_sound_effects.cpp") + include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) include_directories(${OPENGL_INCLUDE_DIR}) @@ -47,4 +51,4 @@ include_directories(${assimp_INCLUDE_DIRS}) add_executable(game ${GAME_SRC}) setup_target(game) -target_link_libraries(game X11 epoxy pthread ${assimp_LIBRARIES} assimp -lportaudio -lsndfile) +target_link_libraries(game X11 epoxy pthread ${assimp_LIBRARIES} assimp ${SOUND_LIBRARIES}) diff --git a/game/main.cpp b/game/main.cpp index bc0bce4..5bf4769 100644 --- a/game/main.cpp +++ b/game/main.cpp @@ -10,7 +10,9 @@ #include "network/server.hpp" #include "options.hpp" +#include "sound/sound.hpp" #include "sound/sound_effects.hpp" + #include "state/state_update_event.hpp" #include @@ -85,11 +87,11 @@ int main(int argc, char *argv[]) srand(time(NULL)); - //sound::SoundEffects *sounds = nullptr; + sound::SoundEffects *sounds = nullptr; if (soundEnabled) { - //if (sound::initSound()) { - //sounds = new sound::SoundEffects(); - //} + if (sound::initSound()) { + sounds = new sound::SoundEffects(); + } } Game game; @@ -115,9 +117,12 @@ int main(int argc, char *argv[]) // } //} - //if (sounds != nullptr) { - // sounds.advance(game->state()); - //} + if (sounds != nullptr) { + // TODO: get time diff too + sounds->advance(1/50.0f, game.state()->currentStateUpdateEvents()); + // TODO: use flag to now when to do this. + sound::deleteOldSounds(); + } game.state()->applyAndClearAllOldStateUpdates(); } diff --git a/game/opengl.cpp b/game/opengl.cpp index e1e99ef..320ef05 100644 --- a/game/opengl.cpp +++ b/game/opengl.cpp @@ -101,7 +101,7 @@ namespace endofthejedi { resize(); } else if (event.type == ClientMessage) { - if (event.xclient.data.l[0] == m_atomWmDeleteWindow) { + if (event.xclient.data.l[0] == (int) m_atomWmDeleteWindow) { stop(); } } else if (event.type == DestroyNotify) { diff --git a/game/sound/sound.cpp b/game/sound/sound.cpp index 3c9f433..e6cca93 100644 --- a/game/sound/sound.cpp +++ b/game/sound/sound.cpp @@ -14,23 +14,38 @@ // for dumping sound #include +#include + namespace sound { - typedef struct { + //struct Slot { + // bool used; + // bool usable; + // bool deleteOnCleanup; + // SoundHandle *handle; + //}; + + struct SoundContext { PaStream *stream; + // this locks access to the context + pthread_mutex_t mutex; + unsigned int framesPerBuffer; unsigned int sampleRate; - unsigned int sound_uid_counter; + size_t sound_uid_counter; // constant - unsigned int maxNumSounds; + size_t maxNumSounds; - // num active sonuds - unsigned int highestSoundIndex; + // index of highest sound or -1, if there are no sounds. + // so 0 means no sounds, > 0 could mean different things depending on + // empty sounds. + volatile ssize_t highestSoundIndex; // pointer to array of sound handles that has this number of active sounds + //Slot *soundSlots; SoundHandle **sounds; bool dumping; @@ -39,7 +54,7 @@ namespace sound { SF_INFO sfinfo; bool init; - } SoundContext; + }; // singleton so no arguments must be supplied for play() functions etc. static SoundContext context; @@ -83,7 +98,7 @@ namespace sound { if (handle->envelope != next) { #if 1 - printf("[sound] global time %4.3f #%d (%s) t=%f %5s -> %5s\n", + printf("[sound] global time %4.3f #%zd (%s) t=%f %5s -> %5s\n", global_time, handle->uid, handle->name, @@ -107,9 +122,9 @@ namespace sound { context.dumping = false; if (err < 0) { - printf("warning: stop dumping wav returned error: %d\n", err); + fprintf(stderr, "[sound] warning: stop dumping wav returned error: %d\n", err); } else { - printf("sucessfully stopped dumping to file %s\n", context.dump_filename); + printf("[sound] sucessfully stopped dumping to file %s\n", context.dump_filename); } } @@ -118,7 +133,7 @@ namespace sound { // TODO: dumping in mono? if (!context.init || !context.dumping) { - printf("warning: can't dump samples: context not initialized or not currently dumping!\n"); + fprintf(stderr, "[sound] warning: can't dump samples: context not initialized or not currently dumping!\n"); return false; } @@ -135,7 +150,7 @@ namespace sound { } if (context.dumping) { - printf("warning: already dumping to %s!\n", context.dump_filename); + printf("[sound] warning: already dumping to %s!\n", context.dump_filename); return false; } @@ -146,12 +161,12 @@ namespace sound { context.outfile = sf_open(filename, SFM_WRITE, &context.sfinfo); if (context.outfile == NULL) { - printf("Unable to dump to output file %s.\n", filename); + fprintf(stderr, "[sound] Unable to dump to output file %s.\n", filename); sf_perror(NULL); return false; }; - //printf("start dumping wav to file %s\n", filename); + printf("[sound] start dumping wav to file %s\n", filename); context.dump_filename = filename; context.dumping = true; return true; @@ -277,7 +292,7 @@ namespace sound { if (n >= handle->numHistorySamples) { // TODO clear history when looping? // or add zero samples? - printf("warning: try to access more history values than saved.\n"); + printf("[sound] warning: try to access more history values than saved.\n"); return 0.0; } @@ -311,6 +326,25 @@ namespace sound { return result; } + // return false on no success + bool tryLock() + { + int ret = pthread_mutex_trylock(&context.mutex); + return ret == 0; + } + + // wait if does not lock + void lock() + { + pthread_mutex_lock(&context.mutex); + } + + void unlock() + { + pthread_mutex_unlock(&context.mutex); + } + + // return false if playing done. static bool advanceSound(SoundHandle *handle, float *out, unsigned int framesPerBuffer) { @@ -406,7 +440,7 @@ namespace sound { break; default: - printf("warning: unimplemented sound type for #%d: %d\n", handle->uid, handle->_type); + printf("[sound] warning: unimplemented sound type for #%zd: %d\n", handle->uid, handle->_type); continue; } @@ -457,23 +491,31 @@ namespace sound { // we have 32 bit float format so cast here float *out = (float *) outputBuffer; - // set all to zero - unsigned int i; - for (i=0; ideleteFlag) { + continue; + } + if (handle->isDeleted) { + continue; + } if (!handle->_done) { #if 0 @@ -489,12 +531,14 @@ namespace sound { } // TODO: when we should not free in the loop, where else? - if (handle->_done) { + if (handle->_done && !handle->deleteFlag) { //printf("[sound] %f freeing sound #%d handle 0x%p\n", global_time, handle->uid, handle); - context.sounds[i] = NULL; - if (!handle->keep_when_done) { - deleteSound(handle); - } + printf("[sound] %f mark sound #%zd handle 0x%p for deletion\n", global_time, handle->uid, handle); + //context.sounds[i] = NULL; + handle->deleteFlag = true; + //if (!handle->keep_when_done) { + // deleteSound(handle); + //} } } @@ -548,10 +592,11 @@ namespace sound { // TODO: add max number of simultanous playing sounds, that's more important. context.sound_uid_counter = 0; - context.maxNumSounds = 50; - context.highestSoundIndex = 0; + context.maxNumSounds = 10; + context.highestSoundIndex = -1; + //context.soundSlots = (Slot*) calloc(context.maxNumSounds, 1000); // XXX HACK context.sounds = (SoundHandle **) calloc(context.maxNumSounds, 1000); // XXX HACK - assert(context.sounds != NULL); + //assert(context.soundSlots != NULL); PaError err = Pa_Initialize(); if (err != paNoError) { @@ -647,21 +692,39 @@ namespace sound { return NULL; } - size_t nextFreeIndex = 0; - while(nextFreeIndex < context.highestSoundIndex) { - if (context.sounds[nextFreeIndex] == NULL) { - // got an empyt sound to use + ssize_t nextFreeIndex = 0; + while(nextFreeIndex <= context.highestSoundIndex) { + if (context.sounds[nextFreeIndex] == NULL || context.sounds[nextFreeIndex]->isDeleted) { + // got an empty sound to use break; } nextFreeIndex++; } - if (nextFreeIndex >= context.maxNumSounds) { - printf("error: can't create more sounds, got maximum of: %d\n", context.maxNumSounds); + if (context.sounds[nextFreeIndex] != NULL) { + printf("[sound] playSound() finally free'ing old sound: %zd\n", + context.sounds[nextFreeIndex]->uid); + + free(context.sounds[nextFreeIndex]); + context.sounds[nextFreeIndex] = NULL; + } + + if (context.sounds[nextFreeIndex] != NULL) { + fprintf(stderr, "[sound] warning: playSound() nextFreeIndex is " + "%zd but that sound is not NULL!\n", nextFreeIndex); + + return NULL; + } + + if (nextFreeIndex+1 >= (ssize_t) context.maxNumSounds) { + printf("[sound] warning: can't create more sounds, got maximum of: %zd\n", context.maxNumSounds); return NULL; } SoundHandle *handle = (SoundHandle *) calloc(1, sizeof(SoundHandle)); + handle->deleteFlag = false; + handle->isDeleted = false; + handle->_done = false; handle->envelope = New; handle->amplitude = amplitude; handle->minAmplitude = 0.0; @@ -674,7 +737,6 @@ namespace sound { handle->state = 0.0; handle->leakage = 0.0; handle->scaling = 0.0; - handle->_done = false; handle->skipDirectionIfClamping = false; handle->_useEnvelope = false; @@ -725,9 +787,16 @@ namespace sound { context.sounds[nextFreeIndex] = handle; - if (nextFreeIndex == context.highestSoundIndex) { + if (nextFreeIndex == context.highestSoundIndex+1) { context.highestSoundIndex++; } + //if (nextFreeIndex < context.highestSoundIndex) { + // fprintf(stderr, "[sound] warning: nextFreeIndex > context.highestSoundIndex, " + // "this should not happen: %zd %zd\n", nextFreeIndex, context.highestSoundIndex); + //} + + printf("[sound] playSound() created sound #%zd name %s\n", handle->uid, handle->name); + printf("[sound] highestSoundIndex now %zd\n", context.highestSoundIndex); return handle; } @@ -917,8 +986,9 @@ namespace sound { int numActiveSounds(void) { int num = 0; - size_t i; - for (i=0; i_done = true; // clear from that - size_t i; - for (i=0; ideleteFlag) { + //context.sounds[i] = NULL; + printf("[sound] %f finally deleting sound #%zd handle 0x%p\n", global_time, handle->uid, handle); + if (handle->history != NULL) { + free(handle->history); + } + handle->isDeleted = true; + handle->deleteFlag = false; + //free(handle); + } + } + + ssize_t nextHighestSoundIndex = -1; + for (i=0; i<=context.highestSoundIndex; i++) { + SoundHandle *handle = context.sounds[i]; + if (handle != NULL) { + nextHighestSoundIndex = i; + } + } + + if (context.highestSoundIndex != nextHighestSoundIndex) { + printf("[sound] deleteOldSounds() setting hightest sound index from %zd to %zd\n", + context.highestSoundIndex, nextHighestSoundIndex); + + context.highestSoundIndex = nextHighestSoundIndex; + } + } } diff --git a/game/sound/sound.hpp b/game/sound/sound.hpp index ef65c07..f9225b5 100644 --- a/game/sound/sound.hpp +++ b/game/sound/sound.hpp @@ -31,12 +31,19 @@ namespace sound { SoundDecayExp = 1 }; - typedef struct { + struct SoundHandle { // just for debugging/logging. don't rely on use const char *name; // unique ID for accessing them via lookups etc. if sound is dead, this will be never reused - int uid; + size_t uid; + + // marked for deletion by the internal thread. + bool deleteFlag; + + // is now deleted. when a new sound should be used, this can be reused + // and at the end deleteFlag and isDeleted should be cleared. + bool isDeleted; // if true, don't deallocate once this is done. bool keep_when_done; @@ -110,11 +117,15 @@ namespace sound { float *history; // if != 0, history is a pointer to 2*numHistorySamples samples. size_t numHistorySamples; // if != 0, save history for this sound. size_t lastHistorySample; // index/2 of last history sample. used - } SoundHandle; + }; // initialize sound subsystem. return true if working, false on errors. bool initSound(void); + // free sounds that are not used anymore. + // must be called regularily by the application + void deleteOldSounds(); + // start playing a sound. // args: // type - type of sound. diff --git a/game/sound/sound_effects.cpp b/game/sound/sound_effects.cpp index 22ebb4e..79baba8 100644 --- a/game/sound/sound_effects.cpp +++ b/game/sound/sound_effects.cpp @@ -2,76 +2,82 @@ #include "sound.hpp" +#include "util.hpp" + namespace sound { -#if 0 - class MissileFlySoundEffect : public SoundEffect { - MissileFlySoundEffect(const game::Missile *missile) - : m_missileId(missile->id) - , m_handle(sound::playSound(sound::playFrequency(440, 0.3)) - { + void SoundEffects::addSoundHandleForObject(game::Object *obj, SoundHandle *handle) + { + std::cout<<"add sound for object: " << obj->id << std::endl; + auto pair = std::pair(obj->id, handle); + m_mapObjectToSoundHandle.insert(pair); + } + + + void SoundEffects::fadeOutSoundHandlesForObject(game::Object *obj, float fadeOutTime) + { + std::cout<<"deleting sounds for object: " << obj->id << std::endl; + std::multimap::iterator it; + for (it = m_mapObjectToSoundHandle.find(obj->id); it != m_mapObjectToSoundHandle.end(); it++) { + std::cout<<"sound #" << it->second->uid << " / " << it->second->name << std::endl; + fadeOut(it->second, fadeOutTime); } - ~MissileFlySoundEffect() - { - delete(m_handle); - } - - bool advance(float dt) - { - for (const game::StateUpdateEvent *event : updates) { - handleUpdateEvent(event); - } - - // TODO: add event if sth. comes into life or dies in a state - // cycle.. delete objects that died last cycle in the current cycle - // so pointers don't get invalid. - - bool keep = false; - for (const game::Missile *missile : state->missiles) { - if (missile->id == m_missileId) { - keep = true; - break; - } - } - - if (!keep) { - sound::stopSound(m_handle); - sound::deleteSound(m_handle); - return false; - } - - return true; - } - - private: - SoundHandle *m_handle; - size_t m_missileId; - }; + size_t numRemoved = m_mapObjectToSoundHandle.erase(obj->id); + std::cout<<"removed total sounds: " << numRemoved << std::endl; + } void SoundEffects::advance(float dt, const std::list &updates) { - //StateUpdateEvent::LifeCycle::Create, - //StateUpdateEvent::Type::Explosion + (void) dt; - std::vector rm; + for (const game::StateUpdateEvent *evt : updates) { + auto type = evt->eventType(); + auto cycle = evt->lifeCycle(); + if (type == game::EventType::Missile) { + if (cycle == game::LifeCycle::Create) { + SoundHandle *handle = playFrequency(880.0, 0.1); + if (handle == nullptr) { + continue; + } + fadeIn(handle); + handle->name = "missile-fly-sound_fade-in"; - for (SoundEffect *effect : m_effects) { - bool keep = effect->advance(dt, m_gameState); - if (!keep) { - rm.push_back(effect); + addSoundHandleForObject(evt->object(), handle); + + } else if (cycle == game::LifeCycle::Destroy) { + fadeOutSoundHandlesForObject(evt->object()); + } + } else if (type == game::EventType::Explosion) { +#if 1 + if (cycle == game::LifeCycle::Create) { + SoundHandle *handle = playSound(SoundBrownianNoise, 0.4); + if (handle == nullptr) { + continue; + } + configureEnvelope(handle, 0.2, 0.0, 2.0); + + // add big *boom* sound too + handle = playFrequency(200.0, 0.5); + if (handle == nullptr) { + continue; + } + configureEnvelope(handle, 0.1, 0.0, 1.0); + } +#endif } } - - for (SoundEffect *effect : rm) { - m_effects.remove(rm); - delete(rm); - } - - // spawn new sounds - //for (const game::Missile *missile : state->missiles) { - // for (SoundEffect - // if (missile-> - //} } -#endif + + void SoundEffects::fadeIn(SoundHandle *handle, float fadeInTime) + { + // assume sound has just started + configureEnvelope(handle, fadeInTime, 1000000.0, 0.0); + } + + void SoundEffects::fadeOut(SoundHandle *handle, float fadeOutTime) + { + // reset time so it imediately starts to fade out. + configureEnvelope(handle, 0.0, 0.0, fadeOutTime); + handle->time = 0.0; + } } diff --git a/game/sound/sound_effects.hpp b/game/sound/sound_effects.hpp index 984703a..ecf52fe 100644 --- a/game/sound/sound_effects.hpp +++ b/game/sound/sound_effects.hpp @@ -1,15 +1,14 @@ #pragma once #include +#include #include "state/state.hpp" namespace sound { -#if 0 - namespace game { - class StateUpdateEvent; - } + struct SoundHandle; +#if 0 class SoundEffect { public: virtual ~SoundEffects() @@ -19,6 +18,7 @@ namespace sound { // return false if it can be deleted virtual bool advance(float dt) = 0; }; +#endif class SoundEffects { public: @@ -26,11 +26,18 @@ namespace sound { { } - void handleUpdateEvent(game::StateUpdateEvent *event); void advance(float dt, const std::list &updates); private: - std::list m_effects; - } -#endif + void fadeIn(SoundHandle *handle, float fadeInTime=0.3f); + void fadeOut(SoundHandle *handle, float fadeOutTime=0.3f); + + void addSoundHandleForObject(game::Object *obj, SoundHandle *handle); + void fadeOutSoundHandlesForObject(game::Object *obj, float fadeOutTime=0.3f); + + private: + std::list m_soundHandles; + std::multimap m_mapObjectToSoundHandle; + //std::list m_effects; + }; } diff --git a/game/state/commands.cpp b/game/state/commands.cpp index 0d60b31..dd47870 100644 --- a/game/state/commands.cpp +++ b/game/state/commands.cpp @@ -14,7 +14,7 @@ namespace game { // angles are supplied in degrees and are CCW Missile *missile = new Missile( - state->generateMissileId(), + state->generateId(), player, player->ship->position, -util::deg2rad(m_angle), @@ -30,7 +30,8 @@ namespace game { { (void) state; - return player->alive && player->energy >= player->speed; + //return player->alive && player->energy >= player->speed; + return player->alive; } void ChangeNameCommand::apply(Player *player, State *state) const @@ -76,6 +77,8 @@ namespace game { void DeveloperCommand::apply(Player *player, State *state) const { + (void) player; + if (!state->developerMode()) { std::cout<<"ignoring dev command while not in developer mode: '" << m_payload << "'" << std::endl; @@ -96,6 +99,7 @@ namespace game { void SetDeveloperModeCommand::apply(Player *player, State *state) const { + (void) player; // TODO: check if player is admin state->setDeveloperMode(m_enable); diff --git a/game/state/state.cpp b/game/state/state.cpp index 276af81..80f22ff 100644 --- a/game/state/state.cpp +++ b/game/state/state.cpp @@ -32,9 +32,6 @@ namespace game { // TODO: clear shots etc. too - m_nextPlayerId = 0; - m_nextMissileId = 0; - m_nextShipId = 0; m_time = 0.0; m_shipRadius = 0.02; m_maxMissileDistance = 2.0; @@ -42,7 +39,6 @@ namespace game { m_defaultEnergy = 10.0; m_maxNumTraces = 10; m_developerMode = devMode; - m_nextExplosionId = 0; setPlayingFieldCenter(0, 0); @@ -144,7 +140,7 @@ namespace game { return false; } - Ship *ship = new Ship(m_nextShipId++, spawnPos, m_shipRadius); + Ship *ship = new Ship(generateId(), spawnPos, m_shipRadius); player->ship = ship; ships.push_back(ship); @@ -157,7 +153,7 @@ namespace game { size_t State::addPlayer() { - Player *player = new Player(m_nextPlayerId++); + Player *player = new Player(generateId()); players.push_back(player); return player->id; } @@ -483,7 +479,7 @@ namespace game { } Explosion *explosion = new Explosion( - m_nextExplosionId++, evt->position, + generateId(), evt->position, evt->missileVelocity, evt->hit); explosions.push_back(explosion); @@ -519,9 +515,9 @@ namespace game { m_playingFieldSize = glm::vec2(width, height); } - size_t State::generateMissileId() + size_t State::generateId() { - return m_nextMissileId++; + return m_ids.makeNextId(); } void State::applyAndClearAllOldStateUpdates() @@ -532,8 +528,8 @@ namespace game { case EventType::Explosion: { ExplosionEvent *ee = static_cast(evt); - //std::cout<<"got explosion delete event, finally deleting explosion #" - // << ee->explosion->id << std::endl; + std::cout<<"got explosion delete event, finally deleting explosion #" + << ee->object()->id << std::endl; delete(ee->object()); } @@ -542,8 +538,8 @@ namespace game { case EventType::Missile: { auto *me = static_cast(evt); - //std::cout<<"got missile delete event, finally deleting missile #" - // << me->missile->id << std::endl; + std::cout<<"got missile delete event, finally deleting missile #" + << me->object()->id << std::endl; delete(me->object()); } diff --git a/game/state/state.hpp b/game/state/state.hpp index cf9d3c4..052fd8b 100644 --- a/game/state/state.hpp +++ b/game/state/state.hpp @@ -35,6 +35,25 @@ namespace game { class Trace; class Explosion; + class IdGenerator { + public: + IdGenerator() : m_nextId(0) + { + } + + size_t makeNextId() + { + return m_nextId++; + if (m_nextId == 0) { + std::cerr << "note: id counter just wrapped to 0, " + "funny things can happen now." << std::endl; + } + } + + private: + size_t m_nextId; + }; + class State { public: /*************************************************************************/ @@ -105,7 +124,7 @@ namespace game { void setPlayingFieldSize(int width, int height); void setPlayingFieldCenter(int width, int height); - size_t generateMissileId(); + size_t generateId(); void applyAndClearAllOldStateUpdates(); @@ -159,10 +178,7 @@ namespace game { float m_shipRadius; float m_defaultEnergy; int m_maxNumTraces; - size_t m_nextPlayerId; - size_t m_nextExplosionId; - size_t m_nextMissileId; - size_t m_nextShipId; + IdGenerator m_ids; float m_time; bool m_developerMode; diff --git a/test/100players..py b/test/100players..py deleted file mode 100755 index a9cab5e..0000000 --- a/test/100players..py +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env python3 -import os -import random - -for i in range(100): - a = random.randint(0, 360) - print("spawn #%d +shoot %d" % (i, a)) - os.system("echo %d | ncat 192.168.0.191 3490" % a) -