Add full-length Step 3 Day 1 simulation
This commit is contained in:
@@ -20,6 +20,9 @@ var core_current_had_wrong_attempt := false
|
||||
var current_question_started_at := 0.0
|
||||
var current_question_pause_started_at := 0.0
|
||||
var core_status_message := ""
|
||||
var simulation_block_index := 0
|
||||
var simulation_total_blocks := 12
|
||||
var simulation_deadline_unix := 0.0
|
||||
|
||||
const CORE_MIN_TARGET_SECONDS := 45.0
|
||||
const CORE_MAX_TARGET_SECONDS := 150.0
|
||||
@@ -65,6 +68,45 @@ func start_core_run() -> bool:
|
||||
return true
|
||||
|
||||
|
||||
func start_simulation_block(questions: Array, block_index: int, total_blocks: int, deadline_unix: float) -> void:
|
||||
system_name = "All Systems"
|
||||
mode = "Simulation"
|
||||
session_kind = "simulation"
|
||||
active_questions = questions.duplicate(true)
|
||||
for index in range(active_questions.size()):
|
||||
active_questions[index] = _prepare_question_choices(active_questions[index])
|
||||
answers.clear()
|
||||
answers.resize(active_questions.size())
|
||||
current_index = 0
|
||||
simulation_block_index = block_index
|
||||
simulation_total_blocks = total_blocks
|
||||
simulation_deadline_unix = deadline_unix
|
||||
seconds_remaining = maxf(0.0, deadline_unix - Time.get_unix_time_from_system())
|
||||
block_started = true
|
||||
current_question_started_at = Time.get_ticks_msec() / 1000.0
|
||||
session_changed.emit()
|
||||
|
||||
|
||||
func restore_simulation_responses(saved_responses: Array) -> void:
|
||||
if session_kind != "simulation":
|
||||
return
|
||||
for response in saved_responses:
|
||||
if typeof(response) != TYPE_DICTIONARY:
|
||||
continue
|
||||
var question_id := str(response.get("questionExternalId", ""))
|
||||
for index in range(active_questions.size()):
|
||||
if str(active_questions[index].get("id", "")) == question_id:
|
||||
answers[index] = {
|
||||
"question": active_questions[index].duplicate(true),
|
||||
"selected_choice_id": str(response.get("choiceId", "")),
|
||||
"is_correct": false,
|
||||
"eliminated_choice_ids": response.get("eliminatedChoiceIds", []).duplicate(),
|
||||
"flagged": bool(response.get("flagged", false)),
|
||||
"elapsed_seconds": int(response.get("elapsedSeconds", 0))
|
||||
}
|
||||
break
|
||||
|
||||
|
||||
func get_current_question() -> Dictionary:
|
||||
if active_questions.is_empty() or current_index < 0 or current_index >= active_questions.size():
|
||||
return {}
|
||||
@@ -86,6 +128,11 @@ func answer_current(choice_id: String, eliminated: Array, flagged: bool) -> Dict
|
||||
"target_seconds": core_target_seconds if session_kind == "core" else 0.0
|
||||
}
|
||||
answers[current_index] = answer
|
||||
if session_kind == "simulation":
|
||||
answer["is_correct"] = false
|
||||
answers[current_index] = answer
|
||||
session_changed.emit()
|
||||
return answer
|
||||
if session_kind == "core":
|
||||
_apply_core_answer_rewards(answer)
|
||||
session_changed.emit()
|
||||
@@ -184,6 +231,8 @@ func move_to(index: int) -> bool:
|
||||
func get_progress_text() -> String:
|
||||
if session_kind == "core":
|
||||
return str(core_answer_count + 1)
|
||||
if session_kind == "simulation":
|
||||
return "Block %d/%d | Question %d/%d" % [simulation_block_index + 1, simulation_total_blocks, current_index + 1, active_questions.size()]
|
||||
return "%d / %d" % [min(current_index + 1, active_questions.size()), active_questions.size()]
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
extends Node
|
||||
|
||||
const EndpointConfig := preload("res://scripts/autoload/api_endpoint_config.gd")
|
||||
const DEFAULT_API_URL := "https://s3q.ranaze.com/api/exam-simulations"
|
||||
|
||||
signal readiness_changed
|
||||
signal state_changed
|
||||
signal request_failed(message: String)
|
||||
signal outcome_changed
|
||||
|
||||
var api_url := DEFAULT_API_URL
|
||||
var outcome_api_url := "https://s3q.ranaze.com/api/step3-outcomes"
|
||||
var request: HTTPRequest
|
||||
var readiness := {}
|
||||
var simulation := {}
|
||||
var outcome := {}
|
||||
var is_loading := false
|
||||
var pending_action := ""
|
||||
var request_queue: Array = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
api_url = EndpointConfig.resolve(
|
||||
str(ProjectSettings.get_setting("step3/exam_simulation_api_url", DEFAULT_API_URL)),
|
||||
"/api/exam-simulations")
|
||||
request = HTTPRequest.new()
|
||||
request.request_completed.connect(_on_request_completed)
|
||||
add_child(request)
|
||||
outcome_api_url = EndpointConfig.resolve("https://s3q.ranaze.com/api/step3-outcomes", "/api/step3-outcomes")
|
||||
|
||||
|
||||
func load_readiness() -> void:
|
||||
_send("readiness", HTTPClient.METHOD_GET, "/readiness")
|
||||
|
||||
|
||||
func load_active() -> void:
|
||||
_send("active", HTTPClient.METHOD_GET, "/active")
|
||||
|
||||
|
||||
func create_simulation() -> void:
|
||||
_send("create", HTTPClient.METHOD_POST, "")
|
||||
|
||||
|
||||
func start_next_block() -> void:
|
||||
if simulation.is_empty():
|
||||
return
|
||||
_send("start", HTTPClient.METHOD_POST, "/%s/blocks/start" % str(simulation.get("id", "")))
|
||||
|
||||
|
||||
func save_response(question_id: String, choice_id: String, eliminated: Array, flagged: bool, elapsed_seconds: int) -> void:
|
||||
if simulation.is_empty():
|
||||
return
|
||||
_send("save", HTTPClient.METHOD_PUT, "/%s/responses" % str(simulation.get("id", "")), {
|
||||
"questionExternalId": question_id,
|
||||
"choiceId": choice_id,
|
||||
"eliminatedChoiceIds": eliminated,
|
||||
"flagged": flagged,
|
||||
"elapsedSeconds": elapsed_seconds
|
||||
})
|
||||
|
||||
|
||||
func submit_block() -> void:
|
||||
if simulation.is_empty():
|
||||
return
|
||||
_send("submit", HTTPClient.METHOD_POST, "/%s/blocks/submit" % str(simulation.get("id", "")))
|
||||
|
||||
|
||||
func abandon() -> void:
|
||||
if simulation.is_empty():
|
||||
return
|
||||
_send("abandon", HTTPClient.METHOD_POST, "/%s/abandon" % str(simulation.get("id", "")))
|
||||
|
||||
|
||||
func load_outcome() -> void:
|
||||
_send_absolute("outcome_get", HTTPClient.METHOD_GET, "%s/me" % outcome_api_url.rstrip("/"))
|
||||
|
||||
|
||||
func save_outcome(official_score: int, exam_date: String) -> void:
|
||||
_send_absolute("outcome_save", HTTPClient.METHOD_PUT, "%s/me" % outcome_api_url.rstrip("/"), {
|
||||
"officialScore": official_score,
|
||||
"examDate": "%sT00:00:00Z" % exam_date,
|
||||
"consent": true
|
||||
})
|
||||
|
||||
|
||||
func delete_outcome() -> void:
|
||||
_send_absolute("outcome_delete", HTTPClient.METHOD_DELETE, "%s/me" % outcome_api_url.rstrip("/"))
|
||||
|
||||
|
||||
func get_block_deadline_unix() -> float:
|
||||
var value := str(simulation.get("blockDeadlineUtc", ""))
|
||||
if value == "":
|
||||
return 0.0
|
||||
return Time.get_unix_time_from_datetime_string(value)
|
||||
|
||||
|
||||
func get_current_questions() -> Array:
|
||||
return simulation.get("questions", [])
|
||||
|
||||
|
||||
func _send(action: String, method: int, suffix: String, payload: Dictionary = {}) -> void:
|
||||
_send_absolute(action, method, "%s%s" % [api_url.rstrip("/"), suffix], payload)
|
||||
|
||||
|
||||
func _send_absolute(action: String, method: int, url: String, payload: Dictionary = {}) -> void:
|
||||
if AuthState.access_token == "":
|
||||
return
|
||||
if is_loading:
|
||||
request_queue.append({"action": action, "method": method, "url": url, "payload": payload.duplicate(true)})
|
||||
return
|
||||
is_loading = true
|
||||
pending_action = action
|
||||
var headers := PackedStringArray(["Authorization: Bearer %s" % AuthState.access_token])
|
||||
var body := ""
|
||||
if not payload.is_empty():
|
||||
headers.append("Content-Type: application/json")
|
||||
body = JSON.stringify(payload)
|
||||
var err := request.request(url, headers, method, body)
|
||||
if err != OK:
|
||||
is_loading = false
|
||||
pending_action = ""
|
||||
request_failed.emit("The simulation request could not be started.")
|
||||
|
||||
|
||||
func _on_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||
var action := pending_action
|
||||
pending_action = ""
|
||||
is_loading = false
|
||||
call_deferred("_process_request_queue")
|
||||
if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
|
||||
request_failed.emit("Simulation request failed with status %d." % response_code)
|
||||
return
|
||||
if response_code == 204:
|
||||
if action == "active" or action == "abandon":
|
||||
simulation = {}
|
||||
state_changed.emit()
|
||||
elif action == "outcome_delete" or action == "outcome_get":
|
||||
outcome = {}
|
||||
outcome_changed.emit()
|
||||
return
|
||||
var parsed: Variant = JSON.parse_string(body.get_string_from_utf8())
|
||||
if typeof(parsed) != TYPE_DICTIONARY:
|
||||
request_failed.emit("The simulation API returned an invalid response.")
|
||||
return
|
||||
if action == "readiness":
|
||||
readiness = parsed
|
||||
readiness_changed.emit()
|
||||
return
|
||||
if action.begins_with("outcome_"):
|
||||
outcome = parsed
|
||||
outcome_changed.emit()
|
||||
return
|
||||
simulation = _normalize_simulation(parsed)
|
||||
state_changed.emit()
|
||||
|
||||
|
||||
func _process_request_queue() -> void:
|
||||
if is_loading or request_queue.is_empty():
|
||||
return
|
||||
var queued: Dictionary = request_queue.pop_front()
|
||||
_send_absolute(str(queued.get("action", "")), int(queued.get("method", HTTPClient.METHOD_GET)), str(queued.get("url", "")), queued.get("payload", {}))
|
||||
|
||||
|
||||
func _normalize_simulation(raw: Dictionary) -> Dictionary:
|
||||
var normalized := raw.duplicate(true)
|
||||
var normalized_questions: Array = []
|
||||
for question in raw.get("questions", []):
|
||||
if typeof(question) != TYPE_DICTIONARY:
|
||||
continue
|
||||
normalized_questions.append({
|
||||
"id": str(question.get("externalId", "")),
|
||||
"stem": str(question.get("stem", "")),
|
||||
"lead_in": str(question.get("leadIn", "")),
|
||||
"choices": question.get("choices", []),
|
||||
"discipline": str(question.get("discipline", "")),
|
||||
"system": str(question.get("system", "")),
|
||||
"performance_category": str(question.get("performanceCategory", "")),
|
||||
"labs": question.get("labs", []),
|
||||
"media": question.get("media", []),
|
||||
"correct_choice_id": "",
|
||||
"explanation": ""
|
||||
})
|
||||
normalized["questions"] = normalized_questions
|
||||
return normalized
|
||||
@@ -0,0 +1 @@
|
||||
uid://hyeimg6wp3p7
|
||||
Reference in New Issue
Block a user