Skip to content

Commit

Permalink
GH-1 question-service with more questions
Browse files Browse the repository at this point in the history
  • Loading branch information
georglundesgaard committed Dec 17, 2019
1 parent 2e43a8f commit c7ab5b1
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,55 @@ import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import java.lang.String.format
import kotlin.random.Random.Default.nextInt

@RestController
class QuestionController {
private val questions: HashMap<String, Question<Int>> = HashMap()
private val questionMap = questionMap()
private val questions: HashMap<String, Question> = HashMap()

@GetMapping("/types")
fun types(): String = format("%s\n", questionMap.keys.joinToString())

@GetMapping("/random")
fun randomQuestion(): String {
val r = nextInt(100)
val q = if (r < 33) AdditionQuestion() else if (r < 66) SubtractionQuestion() else MultiplicationQuestion()
questions[q.id] = q
return format("%s\n", q)
val i = nextInt(questionMap.size)
val q = questionMap.values.toList()[i].call()
return processQuestion(q)
}

@GetMapping("/warmup")
fun warmupQuestion(@RequestParam(required = false, defaultValue = "unknown") p: String): String {
val q = WarmupQuestion(p)
return processQuestion(q)
}

@GetMapping("/{id}")
fun question(@PathVariable id: String): String {
val q = questions[id] ?: throw MissingQuestionException(id)
@GetMapping("/{idOrType}")
fun question(@PathVariable idOrType: String): String =
processQuestion(questions[idOrType] ?: questionMap[idOrType]?.call() ?: throw MissingIdOrTypeException(idOrType))

private fun processQuestion(q: Question): String {
questions[q.id] = q
return format("%s\n", q)
}

@PostMapping("/{id}")
fun checkAnswer(@PathVariable id: String, @RequestBody answer: String): String {
val q = questions[id] ?: throw MissingQuestionException(id)
return format("%s\n", answer == q.correctAnswer().toString())
}
fun checkAnswer(@PathVariable id: String, @RequestParam(required = false, defaultValue = "") a: String): String =
format("%s\n", questions[id]?.checkAnswer(a) ?: throw MissingIdException(id))

@DeleteMapping("/{id}")
fun delete(@PathVariable id: String):String {
val q = questions.remove(id)
return format("%s\n", q?.id ?: "")
}
fun delete(@PathVariable id: String): String = format("%s\n", questions.remove(id)?.id ?: "")

@GetMapping("/")
fun questions(): String = format("%d\n", questions.size)
fun questionCount(): String = format("%d\n", questions.size)
}

@ResponseStatus(NOT_FOUND)
class MissingQuestionException(id: String): RuntimeException(format("Unknown question id: %s", id))
class MissingIdOrTypeException(idOrType: String): RuntimeException(format("Unknown question id or type: %s", idOrType))

@ResponseStatus(NOT_FOUND)
class MissingIdException(id: String): RuntimeException(format("Unknown question id: %s", id))
Original file line number Diff line number Diff line change
@@ -1,29 +1,195 @@
package no.lundesgaard.extremestartup.question

import no.lundesgaard.extremestartup.question.QuestionType.ADDITION
import no.lundesgaard.extremestartup.question.QuestionType.ADDITION_ADDITION
import no.lundesgaard.extremestartup.question.QuestionType.ADDITION_MULTIPLICATION
import no.lundesgaard.extremestartup.question.QuestionType.FIBONACCI
import no.lundesgaard.extremestartup.question.QuestionType.MAXIMUM
import no.lundesgaard.extremestartup.question.QuestionType.MULTIPLICATION
import no.lundesgaard.extremestartup.question.QuestionType.MULTIPLICATION_ADDITION
import no.lundesgaard.extremestartup.question.QuestionType.POWER
import no.lundesgaard.extremestartup.question.QuestionType.PRIMES
import no.lundesgaard.extremestartup.question.QuestionType.SQUARE_CUBE
import no.lundesgaard.extremestartup.question.QuestionType.SUBTRACTION
import no.lundesgaard.extremestartup.question.QuestionType.WARMUP
import java.util.*
import kotlin.math.absoluteValue
import kotlin.math.pow
import kotlin.math.roundToInt
import kotlin.math.sqrt
import kotlin.random.Random.Default.nextInt

fun questionId() = UUID.randomUUID().toString().substring(0, 8)
fun questionMap() = linkedMapOf(
ADDITION.typeName to ::AdditionQuestion,
MAXIMUM.typeName to ::MaximumQuestion,
MULTIPLICATION.typeName to ::MultiplicationQuestion,
SQUARE_CUBE.typeName to ::SquareCubeQuestion,
// GENERAL_KNOWLEDGE.typeName to GeneralKnowledgeQuestion(),
PRIMES.typeName to ::PrimesQuestion,
SUBTRACTION.typeName to ::SubtractionQuestion,
FIBONACCI.typeName to ::FibonacciQuestion,
POWER.typeName to ::PowerQuestion,
ADDITION_ADDITION.typeName to ::AdditionAdditionQuestion,
ADDITION_MULTIPLICATION.typeName to ::AdditionMultiplicationQuestion,
MULTIPLICATION_ADDITION.typeName to ::MultiplicationAdditionQuestion
// ANAGRAM to AnagramQuestion,
// SCRABBLE to ScrabbleQuestion
)

abstract class Question<A>(val id: String = questionId()) {
private fun questionId() = UUID.randomUUID().toString().substring(0, 8)

private fun Int.pow(n: Int) = this.toDouble().pow(n).toInt()

private fun initNumbers(candidateNumbers: IntArray): IntArray {
val size = nextInt(2) + 1
return randomNumbers()
.take(size)
.union(candidateNumbers.toList()
.shuffled()
.subList(0, size))
.shuffled()
.toIntArray()
}

private fun randomNumbers() = IntArray(5) { nextInt(1000) }

private fun squareCubeCandidateNumbers(): IntArray {
val squareCubes = IntArray(100) { (it + 1).pow(3) }.filter(::isSquare)
val squares = IntArray(50).map { (it + 1).pow(2) }
return squareCubes.union(squares).toIntArray()
}

private fun isSquare(number: Int) = number == 0 || number % sqrt(number.toDouble()).roundToInt() == 0

private fun isCube(number: Int) = number == 0 || number % number.toDouble().pow(1/3.toDouble()).roundToInt() == 0

private fun isPrime(number: Int) = (2..number/2).none{ number % it == 0 }

private fun primes(count: Int) = generateSequence(1){ it + 1 }.filter(::isPrime).take(count).toList().toIntArray()

enum class QuestionType(val typeName: String) {
ADDITION("add"),
MAXIMUM("max"),
MULTIPLICATION("mult"),
SQUARE_CUBE("sq_cb"),
PRIMES("primes"),
SUBTRACTION("sub"),
FIBONACCI("fib"),
POWER("pow"),
ADDITION_ADDITION("add_add"),
ADDITION_MULTIPLICATION("add_mult"),
MULTIPLICATION_ADDITION("mult_add"),
WARMUP("warmup");

override fun toString() = typeName
}

abstract class Question(val id: String = questionId()) {
abstract val type: QuestionType
open val points = 10
fun checkAnswer(answer: String) = (if (correctAnswer() == answer) points else -points).toString()
abstract fun asText(): String
abstract fun correctAnswer(): A
abstract fun correctAnswer(): String
override fun toString() = "%s: %s".format(id, asText())
}

abstract class BinaryMathsQuestion(val n1: Int = nextInt(20), val n2: Int = nextInt(20)): Question<Int>()
abstract class BinaryMathsQuestion(val n1: Int = nextInt(20), val n2: Int = nextInt(20)) : Question()

abstract class TernaryMathsQuestion(val n1: Int = nextInt(20), val n2: Int = nextInt(20), val n3: Int = nextInt(20)) : Question()

abstract class SelectFromListOfNumbersQuestion(val candidateNumbers: IntArray, val numbers: IntArray = initNumbers(candidateNumbers)) : Question() {
override fun correctAnswer(): String = numbers.filter { shouldBeSelected(it) }.joinToString()
abstract fun shouldBeSelected(number: Int): Boolean
}

class MaximumQuestion : SelectFromListOfNumbersQuestion(IntArray(100) { it + 1 }) {
override val type = MAXIMUM
override val points = 40
override fun asText() = "which of the following numbers is the largest: %s".format(numbers.joinToString())
override fun shouldBeSelected(number: Int) = number == numbers.max()
}

class AdditionQuestion: BinaryMathsQuestion() {
class AdditionQuestion : BinaryMathsQuestion() {
override val type = ADDITION
override fun asText() = "what is %d plus %d".format(n1, n2)
override fun correctAnswer() = n1 + n2
override fun correctAnswer() = (n1 + n2).toString()
}

class SubtractionQuestion: BinaryMathsQuestion() {
class SubtractionQuestion : BinaryMathsQuestion() {
override val type = SUBTRACTION
override fun asText() = "what is %d minus %d".format(n1, n2)
override fun correctAnswer() = n1 - n2
override fun correctAnswer() = (n1 - n2).toString()
}

class MultiplicationQuestion: BinaryMathsQuestion() {
class MultiplicationQuestion : BinaryMathsQuestion() {
override val type = MULTIPLICATION
override fun asText() = "what is %d multiplied by %d".format(n1, n2)
override fun correctAnswer() = n1 * n2
override fun correctAnswer() = (n1 * n2).toString()
}

class AdditionAdditionQuestion : TernaryMathsQuestion() {
override val type = ADDITION_ADDITION
override val points = 30
override fun asText() = "what is %d plus %d plus %d".format(n1, n2, n3)
override fun correctAnswer() = (n1 + n2 + n3).toString()
}

class AdditionMultiplicationQuestion : TernaryMathsQuestion() {
override val type = ADDITION_MULTIPLICATION
override val points = 60
override fun asText() = "what is %d plus %d multiplied by %d".format(n1, n2, n3)
override fun correctAnswer() = (n1 + n2 * n3).toString()
}

class MultiplicationAdditionQuestion : TernaryMathsQuestion() {
override val type = MULTIPLICATION_ADDITION
override val points = 50
override fun asText() = "what is %d multiplied by %d plus %d".format(n1, n2, n3)
override fun correctAnswer() = (n1 * n2 + n3).toString()
}

class PowerQuestion : BinaryMathsQuestion() {
override val type = POWER
override val points = 20
override fun asText() = "what is %d to the power of %d".format(n1, n2)
override fun correctAnswer() = n1.pow(n2).toString()
}

class SquareCubeQuestion : SelectFromListOfNumbersQuestion(squareCubeCandidateNumbers()) {
override val type = SQUARE_CUBE
override val points = 60
override fun asText() = "which of the following numbers is both a square and a cube: %s".format(numbers.joinToString())
override fun shouldBeSelected(number: Int) = isSquare(number) && isCube(number)
}

class PrimesQuestion : SelectFromListOfNumbersQuestion(primes(100)) {
override val type = PRIMES
override val points = 60
override fun asText() = "which of the following numbers are primes: %s".format(numbers.joinToString())
override fun shouldBeSelected(number: Int) = isPrime(number)
}

class FibonacciQuestion : BinaryMathsQuestion() {
override val type = FIBONACCI
override val points = 50
override fun asText() = "what is the %s number in the Fibonacci sequence".format(ordinalize(n1 + 4))
override fun correctAnswer() = fibonacci(n1 + 4).toString()
private fun ordinalize(number: Int) = "%d%s".format(number, ordinal(number))
private fun ordinal(number: Int) =
if ((11..13).contains(number.absoluteValue % 100))
"th"
else when (number.absoluteValue % 10) {
1 -> "st"
2 -> "nd"
3 -> "rd"
else -> "th"
}

private fun fibonacci(number: Int, a: Int = 0, b: Int = 1): Int = if (number > 0) fibonacci(number - 1, b, a + b) else a
}

class WarmupQuestion(private val playerName: String = "unknown") : Question() {
override val type = WARMUP
override fun asText() = "what is your name"
override fun correctAnswer() = playerName
}

0 comments on commit c7ab5b1

Please sign in to comment.