Skip to content

Commit

Permalink
Merge pull request #54 from linomp/feat/in-game-events
Browse files Browse the repository at this point in the history
Feat/in game events
  • Loading branch information
linomp authored Jul 13, 2024
2 parents 85cfe75 + c174456 commit 1fd2b77
Show file tree
Hide file tree
Showing 21 changed files with 312 additions and 186 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)

1 change: 1 addition & 0 deletions mvp/client/ui/src/api/generated/models/GameSessionDTO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export type GameSessionDTO = {
game_over_reason?: (string | null);
final_score?: (number | null);
user_messages?: Record<string, UserMessage>;
cash_multiplier?: number;
};
1 change: 0 additions & 1 deletion mvp/client/ui/src/api/generated/models/UserMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
export type UserMessage = {
type: UserMessage.type;
content: string;
seen?: boolean;
};

export namespace UserMessage {
Expand Down
48 changes: 16 additions & 32 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,31 +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; /* Center horizontally */
}
.message-card {
border-radius: 8px;
padding: 1em;
width: 80%;
text-align: center;
border: 1px solid #ccc;
}
.message-card.WARNING {
background-color: #ffffe0; /* Light yellow */
color: #000000; /* Black */
}
</style>
34 changes: 30 additions & 4 deletions mvp/client/ui/src/components/SessionData.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import {type GameSessionDTO, PlayerActionsService, SessionsService,} from "src/api/generated";
import {formatNumber, isNotUndefinedNorNull, isUndefinedOrNull,} from "src/shared/utils";
import {type GameSessionDTO, PlayerActionsService, SessionsService} from "src/api/generated";
import {formatNumber, isNotUndefinedNorNull, isUndefinedOrNull} from "src/shared/utils";
import {
dayInProgress,
gameOver,
Expand Down Expand Up @@ -67,7 +67,14 @@
{#if isNotUndefinedNorNull($gameSession) && !$gameOver}
<div class="session-data">
<p>Current Step: {$gameSession?.current_step}</p>
<p>Available Funds: {formatNumber($gameSession?.available_funds)}</p>
<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}>
Advance to next day
Expand All @@ -81,7 +88,8 @@

<style>
.session-data {
margin: 1em;
margin-top: 1em;
margin-right: 1em;
}
.session-controls {
Expand All @@ -96,4 +104,22 @@
.flex-col {
flex-direction: column;
}
.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>
1 change: 0 additions & 1 deletion mvp/client/ui/src/pages/HomePage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@
.game-area {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 2em;
flex-wrap: wrap;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// vite.config.mjs
import { defineConfig } from "file:///C:/Users/LM2P/Documents/000_programming/000-projects/pdm-game/mvp/client/ui/node_modules/vite/dist/node/index.js";
import { svelte } from "file:///C:/Users/LM2P/Documents/000_programming/000-projects/pdm-game/mvp/client/ui/node_modules/@sveltejs/vite-plugin-svelte/src/index.js";
import { run } from "file:///C:/Users/LM2P/Documents/000_programming/000-projects/pdm-game/mvp/client/ui/node_modules/vite-plugin-run/dist/index.mjs";
import * as path from "path";
var __vite_injected_original_dirname = "C:\\Users\\LM2P\\Documents\\000_programming\\000-projects\\pdm-game\\mvp\\client\\ui";
var isTest = process.env.NODE_ENV === "test";
var vite_config_default = defineConfig({
resolve: {
alias: {
src: path.resolve(__vite_injected_original_dirname, "./src")
},
conditions: isTest ? ["browser"] : []
},
plugins: [
svelte({ hot: !process.env.VITEST }),
run({
silent: !!process.env.VITEST,
input: [
{
name: "typecheck",
run: ["npm", "run", "check"],
pattern: ["src/**/*.ts", "src/**/*.svelte"]
}
]
})
],
server: {
port: isTest ? 8678 : 5173,
proxy: isTest ? void 0 : {
"/api": {
target: "http://localhost:8000",
changeOrigin: false
}
}
},
build: {
outDir: "build",
target: "es2020",
cssCodeSplit: false
},
optimizeDeps: {
include: isTest ? ["@testing-library/svelte", "chai"] : void 0
},
test: {
globals: true,
environment: "jsdom",
setupFiles: "src/setup-tests.ts"
}
});
export {
vite_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcubWpzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiQzpcXFxcVXNlcnNcXFxcTE0yUFxcXFxEb2N1bWVudHNcXFxcMDAwX3Byb2dyYW1taW5nXFxcXDAwMC1wcm9qZWN0c1xcXFxwZG0tZ2FtZVxcXFxtdnBcXFxcY2xpZW50XFxcXHVpXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCJDOlxcXFxVc2Vyc1xcXFxMTTJQXFxcXERvY3VtZW50c1xcXFwwMDBfcHJvZ3JhbW1pbmdcXFxcMDAwLXByb2plY3RzXFxcXHBkbS1nYW1lXFxcXG12cFxcXFxjbGllbnRcXFxcdWlcXFxcdml0ZS5jb25maWcubWpzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9DOi9Vc2Vycy9MTTJQL0RvY3VtZW50cy8wMDBfcHJvZ3JhbW1pbmcvMDAwLXByb2plY3RzL3BkbS1nYW1lL212cC9jbGllbnQvdWkvdml0ZS5jb25maWcubWpzXCI7Ly8vIDxyZWZlcmVuY2UgdHlwZXM9XCJ2aXRlc3RcIiAvPlxyXG5pbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJ1xyXG5pbXBvcnQgeyBzdmVsdGUgfSBmcm9tICdAc3ZlbHRlanMvdml0ZS1wbHVnaW4tc3ZlbHRlJ1xyXG5pbXBvcnQgeyBydW4gfSBmcm9tICd2aXRlLXBsdWdpbi1ydW4nO1xyXG5pbXBvcnQgKiBhcyBwYXRoIGZyb20gJ3BhdGgnXHJcblxyXG5jb25zdCBpc1Rlc3QgPSBwcm9jZXNzLmVudi5OT0RFX0VOViA9PT0gJ3Rlc3QnXHJcblxyXG4vLyBodHRwczovL3ZpdGVqcy5kZXYvY29uZmlnL1xyXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoe1xyXG4gIHJlc29sdmU6IHtcclxuICAgIGFsaWFzOiB7XHJcbiAgICAgIHNyYzogcGF0aC5yZXNvbHZlKF9fZGlybmFtZSwgJy4vc3JjJylcclxuICAgIH0sXHJcbiAgICBjb25kaXRpb25zOiBpc1Rlc3QgPyBbJ2Jyb3dzZXInXSA6IFtdXHJcbiAgfSxcclxuICBwbHVnaW5zOiBbXHJcbiAgICBzdmVsdGUoeyBob3Q6ICFwcm9jZXNzLmVudi5WSVRFU1QgfSksXHJcbiAgICBydW4oe1xyXG4gICAgICBzaWxlbnQ6ICEhcHJvY2Vzcy5lbnYuVklURVNULFxyXG4gICAgICBpbnB1dDogW1xyXG4gICAgICAgIHtcclxuICAgICAgICAgIG5hbWU6ICd0eXBlY2hlY2snLFxyXG4gICAgICAgICAgcnVuOiBbJ25wbScsICdydW4nLCAnY2hlY2snXSxcclxuICAgICAgICAgIHBhdHRlcm46IFsnc3JjLyoqLyoudHMnLCAnc3JjLyoqLyouc3ZlbHRlJ10sXHJcbiAgICAgICAgfVxyXG4gICAgICBdXHJcbiAgICB9KVxyXG4gIF0sXHJcbiAgc2VydmVyOiB7XHJcbiAgICBwb3J0OiBpc1Rlc3QgPyA4Njc4IDogNTE3MyxcclxuICAgIHByb3h5OiBpc1Rlc3QgPyB1bmRlZmluZWQgOiB7XHJcbiAgICAgICcvYXBpJzoge1xyXG4gICAgICAgIHRhcmdldDogJ2h0dHA6Ly9sb2NhbGhvc3Q6ODAwMCcsXHJcbiAgICAgICAgY2hhbmdlT3JpZ2luOiBmYWxzZVxyXG4gICAgICB9XHJcbiAgICB9XHJcbiAgfSxcclxuICBidWlsZDoge1xyXG4gICAgb3V0RGlyOiAnYnVpbGQnLFxyXG4gICAgdGFyZ2V0OiAnZXMyMDIwJyxcclxuICAgIGNzc0NvZGVTcGxpdDogZmFsc2VcclxuICB9LFxyXG4gIG9wdGltaXplRGVwczoge1xyXG4gICAgaW5jbHVkZTogaXNUZXN0ID8gWydAdGVzdGluZy1saWJyYXJ5L3N2ZWx0ZScsICdjaGFpJ10gOiB1bmRlZmluZWRcclxuICB9LFxyXG4gIHRlc3Q6IHtcclxuICAgIGdsb2JhbHM6IHRydWUsXHJcbiAgICBlbnZpcm9ubWVudDogJ2pzZG9tJyxcclxuICAgIHNldHVwRmlsZXM6ICdzcmMvc2V0dXAtdGVzdHMudHMnXHJcbiAgfVxyXG59KVxyXG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQ0EsU0FBUyxvQkFBb0I7QUFDN0IsU0FBUyxjQUFjO0FBQ3ZCLFNBQVMsV0FBVztBQUNwQixZQUFZLFVBQVU7QUFKdEIsSUFBTSxtQ0FBbUM7QUFNekMsSUFBTSxTQUFTLFFBQVEsSUFBSSxhQUFhO0FBR3hDLElBQU8sc0JBQVEsYUFBYTtBQUFBLEVBQzFCLFNBQVM7QUFBQSxJQUNQLE9BQU87QUFBQSxNQUNMLEtBQVUsYUFBUSxrQ0FBVyxPQUFPO0FBQUEsSUFDdEM7QUFBQSxJQUNBLFlBQVksU0FBUyxDQUFDLFNBQVMsSUFBSSxDQUFDO0FBQUEsRUFDdEM7QUFBQSxFQUNBLFNBQVM7QUFBQSxJQUNQLE9BQU8sRUFBRSxLQUFLLENBQUMsUUFBUSxJQUFJLE9BQU8sQ0FBQztBQUFBLElBQ25DLElBQUk7QUFBQSxNQUNGLFFBQVEsQ0FBQyxDQUFDLFFBQVEsSUFBSTtBQUFBLE1BQ3RCLE9BQU87QUFBQSxRQUNMO0FBQUEsVUFDRSxNQUFNO0FBQUEsVUFDTixLQUFLLENBQUMsT0FBTyxPQUFPLE9BQU87QUFBQSxVQUMzQixTQUFTLENBQUMsZUFBZSxpQkFBaUI7QUFBQSxRQUM1QztBQUFBLE1BQ0Y7QUFBQSxJQUNGLENBQUM7QUFBQSxFQUNIO0FBQUEsRUFDQSxRQUFRO0FBQUEsSUFDTixNQUFNLFNBQVMsT0FBTztBQUFBLElBQ3RCLE9BQU8sU0FBUyxTQUFZO0FBQUEsTUFDMUIsUUFBUTtBQUFBLFFBQ04sUUFBUTtBQUFBLFFBQ1IsY0FBYztBQUFBLE1BQ2hCO0FBQUEsSUFDRjtBQUFBLEVBQ0Y7QUFBQSxFQUNBLE9BQU87QUFBQSxJQUNMLFFBQVE7QUFBQSxJQUNSLFFBQVE7QUFBQSxJQUNSLGNBQWM7QUFBQSxFQUNoQjtBQUFBLEVBQ0EsY0FBYztBQUFBLElBQ1osU0FBUyxTQUFTLENBQUMsMkJBQTJCLE1BQU0sSUFBSTtBQUFBLEVBQzFEO0FBQUEsRUFDQSxNQUFNO0FBQUEsSUFDSixTQUFTO0FBQUEsSUFDVCxhQUFhO0FBQUEsSUFDYixZQUFZO0FBQUEsRUFDZDtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg==
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.
2 changes: 1 addition & 1 deletion mvp/server/core/analysis/rul_prediction.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import numpy as np
import onnxruntime as rt

from mvp.server.core.machine.OperationalParameters import OperationalParameters
from mvp.server.core.machine.MachineState import OperationalParameters

# Load the SVR pipeline from ONNX file
onnx_path = "mvp/server/core/analysis/artifacts/svr_pipeline_23_06_24.onnx"
Expand Down
8 changes: 5 additions & 3 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,7 +22,9 @@

# Financials
INITIAL_CASH = 0
REVENUE_PER_DAY = 20
REVENUE_PER_DAY = 15
MAINTENANCE_COST = 40
SENSOR_COST = 30
PREDICTION_MODEL_COST = 50
PREDICTION_MODEL_COST = 80
DEMAND_PEAK_EVENT_PROBABILITY = 0.20
DEMAND_PEAK_BONUS_MULTIPLIER = 3
Loading

0 comments on commit 1fd2b77

Please sign in to comment.