/*
* ShootOFF - Software for Laser Dry Fire Training
* Copyright (C) 2016 phrack
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.shootoff.plugins;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.shootoff.camera.Shot;
import com.shootoff.gui.DelayedStartListener;
import com.shootoff.targets.Hit;
import com.shootoff.targets.Target;
import com.shootoff.targets.TargetRegion;
import com.shootoff.util.NamedThreadFactory;
import javafx.scene.paint.Color;
public class ISSFStandardPistol extends TrainingExerciseBase implements TrainingExercise, DelayedStartListener {
private static final Logger logger = LoggerFactory.getLogger(ISSFStandardPistol.class);
private final static String SCORE_COL_NAME = "Score";
private final static int SCORE_COL_WIDTH = 60;
private final static String ROUND_COL_NAME = "Round";
private final static int ROUND_COL_WIDTH = 80;
private final static int START_DELAY = 10; // s
private static final int CORE_POOL_SIZE = 4;
private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(CORE_POOL_SIZE,
new NamedThreadFactory("ISSFStandardPistolExercise"));
private ScheduledFuture<?> endRound;
private TrainingExerciseBase thisSuper;
private static int[] ROUND_TIMES = { 150, 20, 10 };
private int roundTimeIndex = 0;
private int round = 1;
private int shotCount = 0;
private int runningScore = 0;
private final Map<Integer, Integer> sessionScores = new HashMap<>();
private int delayMin = 4;
private int delayMax = 8;
private boolean repeatExercise = true;
private boolean coloredRows = false;
private boolean testing = false;
public ISSFStandardPistol() {}
public ISSFStandardPistol(List<Target> targets) {
super(targets);
thisSuper = super.getInstance();
setInitialValues();
}
@Override
public void targetUpdate(Target target, TargetChange change) {}
private void setInitialValues() {
roundTimeIndex = 0;
round = 1;
shotCount = 0;
runningScore = 0;
for (final int time : ROUND_TIMES) {
sessionScores.put(time, 0);
}
}
@Override
public void init() {
super.pauseShotDetection(true);
super.getDelayedStartInterval(this);
startExercise();
}
// For testing
protected void init(final int delayMin, final int delayMax) {
this.delayMin = delayMin;
this.delayMax = delayMax;
testing = true;
startExercise();
}
private void startExercise() {
super.addShotTimerColumn(SCORE_COL_NAME, SCORE_COL_WIDTH);
super.addShotTimerColumn(ROUND_COL_NAME, ROUND_COL_WIDTH);
if (!testing) {
executorService.schedule(new SetupWait(), START_DELAY, TimeUnit.SECONDS);
} else {
new SetupWait().run();
}
}
@Override
public void updatedDelayedStartInterval(int min, int max) {
delayMin = min;
delayMax = max;
}
private class SetupWait implements Runnable {
@Override
public void run() {
if (!repeatExercise) return;
TrainingExerciseBase.playSound(new File("sounds/voice/shootoff-makeready.wav"));
final int randomDelay = new Random().nextInt((delayMax - delayMin) + 1) + delayMin;
if (!testing) {
executorService.schedule(new StartRound(), randomDelay, TimeUnit.SECONDS);
} else {
new StartRound().run();
}
}
}
private class StartRound implements Runnable {
@Override
public void run() {
shotCount = 0;
if (!repeatExercise) return;
if (coloredRows) {
thisSuper.setShotTimerRowColor(Color.LIGHTGRAY);
} else {
thisSuper.setShotTimerRowColor(null);
}
coloredRows = !coloredRows;
TrainingExerciseBase.playSound("sounds/beep.wav");
thisSuper.pauseShotDetection(false);
endRound = executorService.schedule(new EndRound(), ROUND_TIMES[roundTimeIndex], TimeUnit.SECONDS);
}
}
private class EndRound implements Runnable {
@Override
public void run() {
if (!repeatExercise) return;
thisSuper.pauseShotDetection(true);
TrainingExerciseBase.playSound(new File("sounds/voice/shootoff-roundover.wav"));
final int randomDelay = new Random().nextInt((delayMax - delayMin) + 1) + delayMin;
if (round < 4) {
// Go to next round
round++;
if (!testing) {
executorService.schedule(new StartRound(), randomDelay, TimeUnit.SECONDS);
} else {
new StartRound().run();
}
} else if (roundTimeIndex < ROUND_TIMES.length - 1) {
// Go to round 1 for next time
round = 1;
roundTimeIndex++;
if (!testing) {
executorService.schedule(new StartRound(), randomDelay, TimeUnit.SECONDS);
} else {
new StartRound().run();
}
} else {
TextToSpeech.say("Event over... Your score is " + runningScore);
thisSuper.pauseShotDetection(false);
// At this point we end and the user has to hit reset to
// start again
}
}
}
@Override
public ExerciseMetadata getInfo() {
return new ExerciseMetadata("ISSF 25M Standard Pistol", "1.0", "phrack",
"This exercise implements the ISSF event describe at: "
+ "http://www.pistol.org.au/events/disciplines/issf. You "
+ "can use any scored target with this exercise, but use "
+ "the ISSF target for the most authentic experience.");
}
@Override
public void shotListener(Shot shot, Optional<Hit> hit) {
shotCount++;
int hitScore = 0;
if (hit.isPresent()) {
final TargetRegion r = hit.get().getHitRegion();
if (r.tagExists("points")) {
hitScore = Integer.parseInt(r.getTag("points"));
sessionScores.put(ROUND_TIMES[roundTimeIndex],
sessionScores.get(ROUND_TIMES[roundTimeIndex]) + hitScore);
runningScore += hitScore;
}
final StringBuilder message = new StringBuilder();
for (final Integer time : ROUND_TIMES) {
message.append(String.format("%ss score: %d%n", time, sessionScores.get(time)));
}
super.showTextOnFeed(message.toString() + "total score: " + runningScore);
}
final String currentRound = String.format("R%d (%ds)", round, ROUND_TIMES[roundTimeIndex]);
super.setShotTimerColumnText(SCORE_COL_NAME, String.valueOf(hitScore));
super.setShotTimerColumnText(ROUND_COL_NAME, currentRound);
if (shotCount == 5 && !endRound.isDone()) {
try {
thisSuper.pauseShotDetection(true);
endRound.cancel(true);
new EndRound().run();
} catch (final Exception e) {
logger.error("Error ending current ISSF round (five shots detected)", e);
}
}
}
@Override
public void reset(List<Target> targets) {
super.pauseShotDetection(true);
repeatExercise = false;
executorService.shutdownNow();
setInitialValues();
thisSuper.setShotTimerRowColor(null);
super.showTextOnFeed("");
repeatExercise = true;
executorService = Executors.newScheduledThreadPool(CORE_POOL_SIZE,
new NamedThreadFactory("ISSFStandardPistolExercise"));
executorService.schedule(new SetupWait(), START_DELAY, TimeUnit.SECONDS);
}
@Override
public void destroy() {
repeatExercise = false;
executorService.shutdownNow();
super.destroy();
}
}