first fonctionnal game

This commit is contained in:
didier amyot committed 2023-12-11 10:30:00 -05:00
commit 171657d616
7 files changed
+539

No files matched your search

+117
View File
@@ -0,0 +1,117 @@
function basicFrenchQuestion(argumentValue){
return `Quel est la valeur retournée par la fonction si la valeur de l'argument x est ${argumentValue}?`
}
function basicQuestionValidator(expected){
return (answer) => answer.toString() === expected.toString();
}
export function question001(x){
if(x === 0){
x = x + 1;
}
return x;
}
question001.questions = [0, 16].map( x => {
return {
question_fr: basicFrenchQuestion(x),
validate: basicQuestionValidator(question001(x)),
answer: question001(x),
};
});
export function question002(x){
if(x === 0){
x = x + 1;
} else {
x = x - 1
}
return x;
}
question002.questions = [0, 6].map( x => {
return {
question_fr: basicFrenchQuestion(x),
validate: basicQuestionValidator(question002(x)),
answer: question002(x),
};
});
export function question003(x){
if(x === 0){
x = x + 1;
} else {
x = x - 1
}
return x;
}
question003.questions = [0, 17].map( x => {
return {
question_fr: basicFrenchQuestion(x),
validate: basicQuestionValidator(question003(x)),
answer: question003(x),
};
});
export function question004(x){
if(x === 0){
x = 3;
} else if (x === 3){
x = x + 1;
}else {
x = x - 1
}
return x;
}
question004.questions = [0, 3, 8].map( x => {
return {
question_fr: basicFrenchQuestion(x),
validate: basicQuestionValidator(question004(x)),
answer: question004(x),
};
});
export function question005(x){
for(let i = 0; i < 10; i ++ ){
if (i == x){
return 0;
}
}
return x;
}
question005.questions = [0, 3, 8, -2, 10].map( x => {
return {
question_fr: basicFrenchQuestion(x),
validate: basicQuestionValidator(question005(x)),
answer: question005(x),
};
});
export function question006(x){
if(x < 10){
return 54;
}
if(x < 20){
return 23;
}
if(x < 30){
return 232;
}
return 15;
}
question006.questions = [-1, 3, 10, 20, 30, 26, 31, 12].map( x => {
return {
question_fr: basicFrenchQuestion(x),
validate: basicQuestionValidator(question006(x)),
answer: question006(x),
};
});
+162
View File
@@ -0,0 +1,162 @@
import { Application, Router } from "https://deno.land/x/oak/mod.ts";
import staticFiles from "https://deno.land/x/static_files@1.1.6/mod.ts";
import * as rawQuestions from "./questions.js";
import { renderToString } from "https://esm.sh/preact-render-to-string@5.1.19?deps=preact@10.5.15";
import * as prand from 'https://unpkg.com/pure-rand/lib/esm/pure-rand.js';
import {QuestionSource, QuestionMeta} from './shared.ts';
type Question = [QuestionSource, QuestionMeta];
const questions: Question = Object.values(rawQuestions).flatMap(s => s.questions.map( q => [s, q]));
const router = new Router();
const app = new Application();
function hashString(str: string): number {
let hash = 0
for (let i = 0; i < str.length; i++) {
const chr = str.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
}
function randomPermutation(seed: number, n: number): number[] {
const rand = prand.mersenne(seed)
const base = [...Array(n).keys()];
for(let i = 0; i < (n -1); i++){
const j = prand.unsafeUniformIntDistribution(i, n-1, rand);
const tmp = base[i];
base[i] = base[j];
base[j] = tmp;
}
return base;
}
function getRandomQuestion<T>(seed: string, index: number, questions: Question[]): Question {
const numberSeed = hashString(seed);
const permutations = randomPermutation(numberSeed, questions.length)
return questions[permutations[index]];
}
function getQuestionPage(question: Question, nextQuestionUrl: string, validationUrl: string, answerQuestionUrl: string){
return (<div>
<pre> { question[0].toString() }</pre>
<div class="content mt-2 mb-2">
{ question[1].question_fr }
</div>
<div id="message">
</div>
<form>
<div class="field is-grouped">
<div class="control is-expanded">
<input class="input" type="text" placeholder="Answer" name="answer"></input>
</div>
<div class="control">
<button class="button is-info"
hx-trigger="click"
hx-target="#message"
hx-post={validationUrl} >
Verify Answer
</button>
</div>
<div class="control">
<a class="button is-info"
href="#"
hx-trigger="next-question from:body delay:1500ms, click"
hx-get={nextQuestionUrl}
hx-params="none"
hx-target="#question-body" >
Next Question
</a>
</div>
<div class="control">
<a class="button is-info"
href="#"
hx-trigger="click"
hx-get={answerQuestionUrl}
hx-params="none"
hx-target="#message" >
Show Answer
</a>
</div>
</div>
</form>
</div>);
}
function getValidAnswerMessage(){
return (<div class="notification is-primary">
Fantastic! Good answer!
</div>)
}
function getInvalidAnswerMessage(){
return (<div class="notification is-danger">
Unfortunatly, this is not the right answer.
</div>)
}
function isValidAnswer(question: Question, answer: string): boolean {
return question[1].validate(answer);
}
function processInvalidAnswer(ctx, answer: string): void {
ctx.response.body = renderToString(getInvalidAnswerMessage());
}
function processValidAnswer(ctx, answer: string): void {
ctx.response.headers.append("HX-Trigger", "next-question");
ctx.response.body = renderToString(getValidAnswerMessage());
}
router.all("/api/v1/random-questions/:seed/:idx/validate", async (ctx)=> {
const seed = ctx.params.seed;
const index = Number.parseInt(ctx.params.idx??"0");
const answer = (await ctx.request.body().value).get("answer");
const nextQuestionUrl = `/api/v1/random-questions/${seed}/${index + 1}`;
const q = getRandomQuestion(seed, index, questions);
if(isValidAnswer(q, answer)){
processValidAnswer(ctx, nextQuestionUrl)
}else{
processInvalidAnswer(ctx, nextQuestionUrl)
}
})
router.get("/api/v1/random-questions/:seed/:idx/answer", (ctx)=> {
const seed = ctx.params.seed;
const index = Number.parseInt(ctx.params.idx??"0");
const q = getRandomQuestion(seed, index, questions);
ctx.response.body = renderToString(
<div class="notification is-warning">
The answer is {q[1].answer}
</div>);
})
router.get("/api/v1/random-questions/:seed/:idx", (ctx)=> {
const seed = ctx.params.seed;
const index = Number.parseInt(ctx.params.idx??"0");
const q = getRandomQuestion(seed, index, questions);
const nextQuestionUrl = `/api/v1/random-questions/${seed}/${index + 1}`;
const validationUrl = `/api/v1/random-questions/${seed}/${index}/validate`;
const answerQuestionUrl = `/api/v1/random-questions/${seed}/${index}/answer`
ctx.response.body = renderToString(getQuestionPage(q, nextQuestionUrl, validationUrl, answerQuestionUrl));
});
app.use(router.routes());
app.use(staticFiles("public", {brotli: true, fetch: true}));
await app.listen({ port: 8000 });
+9
View File
@@ -0,0 +1,9 @@
interface QuestionSource {
toString(): string;
}
export type QuestionMeta = {
question_fr: string,
validate: (answer: string) => boolean,
answer: string | number
}