/* * 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.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.Random; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import com.shootoff.camera.Shot; import com.shootoff.targets.Hit; import com.shootoff.targets.Target; import com.shootoff.targets.TargetRegion; import com.shootoff.util.NamedThreadFactory; public class ShootDontShoot extends ProjectorTrainingExerciseBase implements TrainingExercise { private final static String TARGET_COL_NAME = "TARGET"; private final static int TARGET_COL_WIDTH = 60; private final static int MIN_TARGETS_PER_ROUND = 1; private final static int MAX_TARGETS_PER_ROUND = 6; private final static int ROUND_DURATION = 10; // s private static final int CORE_POOL_SIZE = 2; private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(CORE_POOL_SIZE, new NamedThreadFactory("ShootDontShootExercise")); private final AtomicBoolean continueExercise = new AtomicBoolean(true); private boolean testRun = false; private ProjectorTrainingExerciseBase thisSuper; private int missedTargets = 0; private int badHits = 0; private List<Target> shootTargets = new ArrayList<>(); private List<Target> dontShootTargets = new ArrayList<>(); private NewRound currentRound; private Random rng = new Random(); public ShootDontShoot() {} public ShootDontShoot(List<Target> targets) { super(targets); thisSuper = super.getInstance(); } @Override public void targetUpdate(Target target, TargetChange change) {} /** * This is used to make this plugin deterministic for testing. * * @param rng * an rng with a known seed */ protected ShootDontShoot(List<Target> targets, Random rng, List<Target> shootTargets, List<Target> dontShootTargets) { this(targets); this.rng = rng; this.shootTargets = shootTargets; this.dontShootTargets = dontShootTargets; } @Override public void init() { super.addShotTimerColumn(TARGET_COL_NAME, TARGET_COL_WIDTH); addTargets(shootTargets, "targets/shoot_dont_shoot/shoot.target"); addTargets(dontShootTargets, "targets/shoot_dont_shoot/dont_shoot.target"); super.showTextOnFeed("missed targets: 0\nbad hits: 0"); currentRound = new NewRound(); executorService.schedule(currentRound, ROUND_DURATION, TimeUnit.SECONDS); } // Used to call NewRound from a test protected void callNewRound() { try { testRun = true; new NewRound().run(); } catch (final Exception e) { e.printStackTrace(); } } private class NewRound implements Runnable { private boolean cancelled = false; @Override public void run() { if (cancelled || !continueExercise.get()) return; missedTargets += shootTargets.size(); if (shootTargets.size() == 1) { TextToSpeech.say(String.format("You missed %d target.", shootTargets.size())); } else if (shootTargets.size() > 1) { TextToSpeech.say(String.format("You missed %d targets.", shootTargets.size())); } thisSuper.showTextOnFeed(String.format("missed targets: %d%nbad hits: %d", missedTargets, badHits)); if (!testRun && continueExercise.get()) { for (final Target target : shootTargets) thisSuper.removeTarget(target); shootTargets.clear(); for (final Target target : dontShootTargets) thisSuper.removeTarget(target); dontShootTargets.clear(); addTargets(shootTargets, "targets/shoot_dont_shoot/shoot.target"); addTargets(dontShootTargets, "targets/shoot_dont_shoot/dont_shoot.target"); } thisSuper.clearShots(); if (continueExercise.get() && !testRun) { currentRound = new NewRound(); executorService.schedule(currentRound, ROUND_DURATION, TimeUnit.SECONDS); } } public void cancel() { cancelled = true; } } @Override public ExerciseMetadata getInfo() { return new ExerciseMetadata("Shoot Don't Shoot", "1.1", "phrack", "This exercise randomly puts up targets and gives you 10 seconds " + "to decide which ones to shoot and which ones to ignore. If " + "you do not shoot a target you are supposed to shoot, it gets " + "added to your missed targets counter and the exercise says " + "how many targets you missed. If you hit a target you were not " + "supposed to hit, the exercise says 'bad shoot!'. Shoot the targets " + "with the red ring, don't shoot the other targets."); } protected void addTargets(List<Target> targets, String target) { final int count = rng.nextInt((MAX_TARGETS_PER_ROUND - MIN_TARGETS_PER_ROUND) + 1) + MIN_TARGETS_PER_ROUND; for (int i = 0; i < count; i++) { final int x = rng.nextInt(((int) super.getArenaWidth() - 100) + 1) + 0; final int y = rng.nextInt(((int) super.getArenaHeight() - 100) + 1) + 0; final Optional<Target> newTarget = super.addTarget(new File(target), x, y); if (newTarget.isPresent()) targets.add(newTarget.get()); } } protected void removeTarget(List<Target> targets, TargetRegion region) { final Iterator<Target> it = targets.iterator(); while (it.hasNext()) { final Target target = it.next(); if (target.hasRegion(region)) { super.removeTarget(target); it.remove(); return; } } } @Override public void shotListener(Shot shot, Optional<Hit> hit) { if (hit.isPresent()) { final TargetRegion r = hit.get().getHitRegion(); if (r.tagExists("subtarget")) { switch (r.getTag("subtarget")) { case "shoot": { removeTarget(shootTargets, r); super.setShotTimerColumnText(TARGET_COL_NAME, "shoot"); if (shootTargets.isEmpty()) { currentRound.cancel(); currentRound = new NewRound(); currentRound.run(); } } break; case "dont_shoot": { removeTarget(dontShootTargets, r); badHits++; super.setShotTimerColumnText(TARGET_COL_NAME, "dont_shoot"); TextToSpeech.say("Bad shoot!"); } break; } } } } @Override public void reset(List<Target> targets) { continueExercise.set(false); executorService.shutdownNow(); missedTargets = 0; badHits = 0; for (final Target target : shootTargets) super.removeTarget(target); shootTargets.clear(); for (final Target target : dontShootTargets) super.removeTarget(target); dontShootTargets.clear(); addTargets(shootTargets, "targets/shoot_dont_shoot/shoot.target"); addTargets(dontShootTargets, "targets/shoot_dont_shoot/dont_shoot.target"); super.showTextOnFeed("missed targets: 0\nbad hits: 0"); continueExercise.set(true); executorService = Executors.newScheduledThreadPool(CORE_POOL_SIZE, new NamedThreadFactory("ShootDontShootExercise")); executorService.schedule(new NewRound(), ROUND_DURATION, TimeUnit.SECONDS); } @Override public void destroy() { continueExercise.set(false); executorService.shutdownNow(); super.destroy(); } }