feat(89++): écran allumé, carillon de fin de timer, accords gorgées

- FLAG_KEEP_SCREEN_ON pendant la partie
- Fin du timer de défi: carillon convivial do-mi-sol (AudioTrack,
  synthèse PCM avec enveloppe de cloche) à la place des bips
  ToneGenerator télécoms
- Accord singulier/pluriel: «1 gorgée» / «N gorgées» dans les toasts,
  les stats et Game89Player (fini les «gorgée(s)»)
This commit is contained in:
Roman
2026-08-19 18:41:18 +00:00
parent de85af4e2a
commit df3650438b
4 changed files with 83 additions and 4 deletions
@@ -52,6 +52,9 @@ public class Game89GameActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game89_game);
// Garder l'écran allumé pendant la partie
getWindow().addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// Get intent data
ArrayList<String> playerNames = getIntent().getStringArrayListExtra("PLAYERS");
challengeTimerMinutes = getIntent().getIntExtra("CHALLENGE_TIMER_MINUTES", 1);
@@ -155,6 +158,9 @@ public class Game89GameActivity extends AppCompatActivity {
challengeTitleTextView.setText(currentChallenge.getTitle());
challengeDescriptionTextView.setText(currentChallenge.getDescription());
// Alerte sonore : le timer est terminé, un défi arrive
com.example.boidelov3.utils.SoundManager.getInstance(this).playChallenge();
// Vibrate to alert players
try {
android.os.Vibrator vibrator = (android.os.Vibrator) getSystemService(android.content.Context.VIBRATOR_SERVICE);
@@ -274,7 +280,7 @@ public class Game89GameActivity extends AppCompatActivity {
int count = seekBar.getProgress() + 1; // 1-8
players.get(playerIndex).addGorgees(count);
Toast.makeText(this,
players.get(playerIndex).getName() + " boit " + count + " gorgée(s)!",
players.get(playerIndex).getName() + " boit " + count + " gorgée" + (count > 1 ? "s" : "") + " !",
Toast.LENGTH_SHORT).show();
});
@@ -288,10 +294,12 @@ public class Game89GameActivity extends AppCompatActivity {
StringBuilder stats = new StringBuilder();
for (Game89Player player : players) {
int total = player.getTotalGorgees();
stats.append(player.getName())
.append(": ")
.append(player.getTotalGorgees())
.append(" gorgée(s)\n\n");
.append(total)
.append(total > 1 ? " gorgées" : " gorgée")
.append("\n\n");
}
builder.setMessage(stats.toString());
@@ -29,6 +29,6 @@ public class Game89Player {
@Override
public String toString() {
return name + " (" + totalGorgees + " gorgées)";
return name + " (" + totalGorgees + (totalGorgees > 1 ? " gorgées)" : " gorgée)");
}
}
@@ -115,6 +115,68 @@ public class SoundGenerator {
}).start();
}
/**
* Carillon convivial - annonce la fin d'un timer (nouveau défi)
* Notes sinusoïdales do-mi-sol avec décroissance douce, timbre de cloche
*/
public synchronized void playChallenge() {
if (isMuted || toneGenerator == null) return;
new Thread(() -> {
android.media.AudioTrack track = null;
try {
final int sampleRate = 44100;
final double[] frequencies = {523.25, 659.25, 783.99}; // Do5, Mi5, Sol5
final double noteDuration = 0.45; // durée de résonance d'une note (s)
final double noteSpacing = 0.15; // décalage entre les notes (s)
int totalSamples = (int) ((noteSpacing * 2 + noteDuration) * sampleRate);
short[] pcm = new short[totalSamples];
for (int n = 0; n < frequencies.length; n++) {
double freq = frequencies[n];
int start = (int) (noteSpacing * n * sampleRate);
int length = (int) (noteDuration * sampleRate);
for (int i = 0; i < length && start + i < totalSamples; i++) {
double t = (double) i / sampleRate;
// Attaque rapide + décroissance exponentielle = timbre de cloche
double envelope = Math.min(1.0, t / 0.01) * Math.exp(-t * 5.5);
// Légère harmonique pour enrichir le timbre
double sample = 0.75 * Math.sin(2 * Math.PI * freq * t)
+ 0.25 * Math.sin(2 * Math.PI * freq * 2.0 * t);
double value = pcm[start + i] / 32768.0 + sample * envelope * 0.55;
pcm[start + i] = (short) (Math.max(-1.0, Math.min(1.0, value)) * Short.MAX_VALUE);
}
}
track = new android.media.AudioTrack.Builder()
.setAudioAttributes(new android.media.AudioAttributes.Builder()
.setUsage(android.media.AudioAttributes.USAGE_MEDIA)
.setContentType(android.media.AudioAttributes.CONTENT_TYPE_MUSIC)
.build())
.setAudioFormat(new android.media.AudioFormat.Builder()
.setEncoding(android.media.AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(sampleRate)
.setChannelMask(android.media.AudioFormat.CHANNEL_OUT_MONO)
.build())
.setTransferMode(android.media.AudioTrack.MODE_STREAM)
.setBufferSizeInBytes(sampleRate)
.build();
track.play();
track.write(pcm, 0, totalSamples);
Thread.sleep((long) ((noteSpacing * 2 + noteDuration + 0.15) * 1000));
} catch (Exception e) {
Log.e("SoundGenerator", "Erreur lors de la lecture du carillon", e);
} finally {
if (track != null) {
try {
track.stop();
} catch (Exception ignored) {
}
track.release();
}
}
}).start();
}
/**
* Activer/Désactiver le son
*/
@@ -64,6 +64,15 @@ public class SoundManager {
soundGenerator.playFin();
}
}
/**
* Joue un carillon convivial (fin de timer / nouveau défi)
*/
public void playChallenge() {
if (soundGenerator != null) {
soundGenerator.playChallenge();
}
}
/**
* Active ou désactive le son