Skip to content

Commit

Permalink
layout improvements & parameter tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
linomp committed Jul 13, 2024
1 parent 59f6056 commit 1898538
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 60 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
# The Predictive Maintenance Game

![immagine](https://github.com/linomp/pdm-game/assets/40581019/fe7fbee0-bf31-487b-a727-f34472d94840)

![immagine](./mvp/media/13_07_2024_trim.PNG)

- **_Your mission_**: maximize profit & machine lifespan.
- **_Your tools_**: data, intuition & nerve!
- **_Your opponents_**: machine degradation & limited funds!


### **_Will you accept the challenge?_**

https://app.pdmgame.xmp.systems/

---

### Dev roadmap

- [X] Basic UI
- [X] Basic machine degradation model
- [X] Sensor & prediction model purchase
- [X] Live machine parameters visualization
- [X] Leaderboard
- [X] Basic RUL prediction model
- [ ] In-game events (e.g. production peak, score multipliers)
- [X] In-game events (e.g. production peak, score multipliers)

56 changes: 16 additions & 40 deletions mvp/client/ui/src/components/MachineData.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
gameOver,
gameSession,
globalSettings,
isOnNarrowScreen,
predictionPurchaseButtonDisabled,
sensorPurchaseButtonDisabled,
} from "src/stores/stores";
import Sensor from "src/components/Sensor.svelte";
import UserMessages from "src/components/UserMessages.svelte";
export let updateGameSession: (newGameSessionDto: GameSessionDTO) => void;
Expand Down Expand Up @@ -73,6 +75,11 @@

{#if isNotUndefinedNorNull($gameSession)}
<div class="machine-data">
{#if $isOnNarrowScreen}
{#key 'narrow'}
<UserMessages messages={$gameSession?.user_messages ?? {}}/>
{/key}
{/if}
<div class="sensors-display">
{#each Object.entries($gameSession?.machine_state?.operational_parameters ?? {}) as [parameter, value]}
<Sensor
Expand All @@ -85,7 +92,7 @@
{/each}
</div>
<div class="rul-display">
<span> {"Remaining Useful Life"}: </span>
<span> {"Remaining Useful Life 🔮"}: </span>
<span>{$gameSession?.machine_state?.predicted_rul
? `${$gameSession.machine_state?.predicted_rul} steps`
: "???"}
Expand All @@ -103,23 +110,22 @@
</button>
</span>
</div>
<div class="user-messages">
{#each Object.entries($gameSession?.user_messages ?? {}) as [key, message]}
<div class="message-card {message.type}">
<span>{message.content}</span>
</div>
{/each}
</div>
{#if !$isOnNarrowScreen}
{#key 'wide'}
<UserMessages messages={$gameSession?.user_messages ?? {}}/>
{/key}
{/if}
</div>
{/if}

<style>
.machine-data {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 2em;
position: relative;
gap: 2em;
align-items: center;
flex-direction: column;
}
.sensors-display {
Expand All @@ -130,39 +136,9 @@
}
.rul-display {
margin-top: 1em;
display: flex;
flex-direction: column;
gap: 0.5rem;
align-items: center;
}
.user-messages {
display: flex;
flex-direction: column;
gap: 1rem;
margin-top: 2em;
align-items: center;
width: 80%;
}
.message-card {
border-radius: 8px;
padding: 1em;
width: 100%;
text-align: center;
border: 1px solid #ccc;
transition: opacity 0.3s;
font-size: smaller;
}
.message-card.WARNING {
background-color: #ffffe0;
color: #000000;
}
.message-card.INFO {
background-color: rgba(129, 248, 94, 0.86);
color: #000000;
}
</style>
29 changes: 24 additions & 5 deletions mvp/client/ui/src/components/SessionData.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,13 @@
{#if isNotUndefinedNorNull($gameSession) && !$gameOver}
<div class="session-data">
<p>Current Step: {$gameSession?.current_step}</p>
<p class:highlight-funds={($gameSession?.cash_multiplier??0) > 1}>
Available Funds: {formatNumber($gameSession?.available_funds)}
<p>
Available Funds:
<span
class:rainbow-funds={($gameSession?.cash_multiplier??0) > 1}>{formatNumber($gameSession?.available_funds)}</span>
<span
hidden={($gameSession?.cash_multiplier??0) <= 1}>🔥 (3x !!)
</span>
</p>
<div class={`session-controls ${$isOnNarrowScreen ? "flex-row" : "flex-col"}`}>
<button on:mousedown={advanceToNextDay} disabled={$dayInProgress}>
Expand All @@ -83,7 +88,8 @@

<style>
.session-data {
margin: 1em;
margin-top: 1em;
margin-right: 1em;
}
.session-controls {
Expand All @@ -99,8 +105,21 @@
flex-direction: column;
}
.highlight-funds {
color: #00b971;
.rainbow-funds {
background-image: linear-gradient(to right, red, orange, #f3bd5a, green, cadetblue, dodgerblue);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-weight: bold;
animation: rainbow-animation 5s linear infinite;
}
@keyframes rainbow-animation {
0% {
background-position: 0% 50%;
}
100% {
background-position: 100% 50%;
}
}
</style>
41 changes: 41 additions & 0 deletions mvp/client/ui/src/components/UserMessages.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<script lang="ts">
export let messages: Record<string, { type: string; content: string }>;
</script>

<div class="user-messages">
{#each Object.entries(messages) as [key, message]}
<div class="message-card {message.type}">
<span>{message.content}</span>
</div>
{/each}
</div>

<style>
.user-messages {
display: flex;
flex-direction: column;
gap: 1rem;
align-items: center;
width: 80%;
}
.message-card {
border-radius: 8px;
padding: 1em;
width: 100%;
text-align: center;
border: 1px solid #ccc;
transition: opacity 0.3s;
font-size: smaller;
}
.message-card.WARNING {
background-color: #ffffe0;
color: #000000;
}
.message-card.INFO {
background-color: rgba(129, 248, 94, 0.86);
color: #000000;
}
</style>
Binary file added mvp/media/13_07_2024.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mvp/media/13_07_2024_trim.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions mvp/server/core/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Gameplay
DEFAULT_SESSION_ID = 'test'
GAME_TICK_INTERVAL = 0.03 # 30ms
GAME_TICK_INTERVAL = 0.015 # 30ms
IDLE_SESSION_TTL_SECONDS = 60 * 30 # 15 minutes
SESSION_CLEANUP_INTERVAL_SECONDS = 60 * 60 # 60 minutes
TIMESTEPS_PER_MOVE = 24 # "hours"
Expand All @@ -22,9 +22,9 @@

# Financials
INITIAL_CASH = 0
REVENUE_PER_DAY = 20
MAINTENANCE_COST = 50
REVENUE_PER_DAY = 15
MAINTENANCE_COST = 40
SENSOR_COST = 30
PREDICTION_MODEL_COST = 80
DEMAND_PEAK_EVENT_PROBABILITY = 0.25
DEMAND_PEAK_BONUS_MULTIPLIER = 2
DEMAND_PEAK_EVENT_PROBABILITY = 0.20
DEMAND_PEAK_BONUS_MULTIPLIER = 3
13 changes: 7 additions & 6 deletions mvp/server/core/game/GameSession.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,13 @@ async def advance_one_turn(self) -> list[MachineState] | None:

self.update_game_over_flag()
if self.is_game_over:
break
return

self.current_step += 1
self.machine_state.update_parameters(self.current_step)

# Player earns money for the production at every timestep,
# proportional to the health of the machine (bad health = less efficient production)
self.available_funds += self.cash_multiplier * (
self.machine_state.health_percentage / 60) * REVENUE_PER_DAY / TIMESTEPS_PER_MOVE
# Player earns money for the production at every timestep
self.available_funds += self.cash_multiplier * REVENUE_PER_DAY / TIMESTEPS_PER_MOVE

# Publish state every 2 steps (to reduce the load on the MQTT broker)
if self.current_step % 2 == 0:
Expand All @@ -112,7 +110,10 @@ async def advance_one_turn(self) -> list[MachineState] | None:
self.update_rul_prediction()
self.cash_multiplier = 1

if (random.random() < DEMAND_PEAK_EVENT_PROBABILITY) or os.getenv("DEV_FORCE_DEMAND_PEAK_EVENT", False):
# 😈 probability of bonus multiplier increases with time, when it is also most risky for the player to skip maintenance!
# TODO: organize this better, there are too many things mixed here... probably won't remember what this code does in 1 week!
if ((random.random() / min(1, self.current_step)) < DEMAND_PEAK_EVENT_PROBABILITY) or os.getenv(
"DEV_FORCE_DEMAND_PEAK_EVENT", False):
self.user_messages["demand_peak_bonus"] = UserMessage(
type="INFO",
content=f"Demand Peak! - Skip maintenance and earn {DEMAND_PEAK_BONUS_MULTIPLIER}x cash in the next turn!"
Expand Down

0 comments on commit 1898538

Please sign in to comment.