diff --git a/backend/cmd/backend/backend.go b/backend/cmd/backend/backend.go index 1821d2ee..91815afe 100644 --- a/backend/cmd/backend/backend.go +++ b/backend/cmd/backend/backend.go @@ -19,6 +19,7 @@ func isFlagSet(name string) bool { } func main() { + roundsConfigFilename := flag.String("rounds-config", config.DefaultRoundsConfigPath, "Rounds config file") canvasConfigFilename := flag.String("canvas-config", config.DefaultCanvasConfigPath, "Canvas config file") databaseConfigFilename := flag.String("database-config", config.DefaultDatabaseConfigPath, "Database config file") backendConfigFilename := flag.String("backend-config", config.DefaultBackendConfigPath, "Backend config file") @@ -27,6 +28,11 @@ func main() { flag.Parse() + roundsConfig, err := config.LoadRoundsConfig(*roundsConfigFilename) + if err != nil { + panic(err) + } + canvasConfig, err := config.LoadCanvasConfig(*canvasConfigFilename) if err != nil { panic(err) @@ -49,7 +55,7 @@ func main() { databases := core.NewDatabases(databaseConfig) defer databases.Close() - core.ArtPeaceBackend = core.NewBackend(databases, canvasConfig, backendConfig, *admin) + core.ArtPeaceBackend = core.NewBackend(databases, roundsConfig, canvasConfig, backendConfig, *admin) routes.InitRoutes() diff --git a/backend/cmd/consumer/consumer.go b/backend/cmd/consumer/consumer.go index ae8dffe6..5f87e2eb 100644 --- a/backend/cmd/consumer/consumer.go +++ b/backend/cmd/consumer/consumer.go @@ -20,6 +20,7 @@ func isFlagSet(name string) bool { } func main() { + roundsConfigFilename := flag.String("rounds-config", config.DefaultRoundsConfigPath, "Rounds config file") canvasConfigFilename := flag.String("canvas-config", config.DefaultCanvasConfigPath, "Canvas config file") databaseConfigFilename := flag.String("database-config", config.DefaultDatabaseConfigPath, "Database config file") backendConfigFilename := flag.String("backend-config", config.DefaultBackendConfigPath, "Backend config file") @@ -27,6 +28,11 @@ func main() { flag.Parse() + roundsConfig, err := config.LoadRoundsConfig(*roundsConfigFilename) + if err != nil { + panic(err) + } + canvasConfig, err := config.LoadCanvasConfig(*canvasConfigFilename) if err != nil { panic(err) @@ -49,7 +55,7 @@ func main() { databases := core.NewDatabases(databaseConfig) defer databases.Close() - core.ArtPeaceBackend = core.NewBackend(databases, canvasConfig, backendConfig, false) + core.ArtPeaceBackend = core.NewBackend(databases, roundsConfig, canvasConfig, backendConfig, false) routes.InitBaseRoutes() indexer.InitIndexerRoutes() diff --git a/backend/cmd/video-gen/video.go b/backend/cmd/video-gen/video.go index 6620761d..aec5698e 100644 --- a/backend/cmd/video-gen/video.go +++ b/backend/cmd/video-gen/video.go @@ -10,12 +10,18 @@ import ( ) func main() { + roundsConfigFilename := flag.String("rounds-config", config.DefaultRoundsConfigPath, "Rounds config file") canvasConfigFilename := flag.String("canvas-config", config.DefaultCanvasConfigPath, "Canvas config file") databaseConfigFilename := flag.String("database-config", config.DefaultDatabaseConfigPath, "Database config file") backendConfigFilename := flag.String("backend-config", config.DefaultBackendConfigPath, "Backend config file") flag.Parse() + roundsConfig, err := config.LoadRoundsConfig(*roundsConfigFilename) + if err != nil { + panic(err) + } + canvasConfig, err := config.LoadCanvasConfig(*canvasConfigFilename) if err != nil { panic(err) @@ -34,7 +40,7 @@ func main() { databases := core.NewDatabases(databaseConfig) defer databases.Close() - core.ArtPeaceBackend = core.NewBackend(databases, canvasConfig, backendConfig, true) + core.ArtPeaceBackend = core.NewBackend(databases, roundsConfig, canvasConfig, backendConfig, true) routes.InitBaseRoutes() routes.InitCanvasRoutes() diff --git a/backend/config/rounds.go b/backend/config/rounds.go new file mode 100644 index 00000000..b5198d1c --- /dev/null +++ b/backend/config/rounds.go @@ -0,0 +1,47 @@ +package config + +import ( + "encoding/json" + "os" +) + +type Round3 struct { + Width uint `json:"width"` + Height uint `json:"height"` + Timer uint `json:"timer"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` +} + +type RoundsConfig struct { + Round3 Round3 `json:"round3"` +} + +var DefaultRoundsConfig = &RoundsConfig{ + Round3: Round3{ + Width: 256, + Height: 192, + Timer: 5, + StartTime: "2024-12-01T00:00:00Z", + EndTime: "2025-01-01T00:00:00Z", + }, +} + +var DefaultRoundsConfigPath = "../configs/rounds.config.json" + +func LoadRoundsConfig(roundsConfigPath string) (*RoundsConfig, error) { + roundsConfig := &RoundsConfig{} + + roundsConfigFile, err := os.Open(roundsConfigPath) + if err != nil { + return nil, err + } + defer roundsConfigFile.Close() + + jsonParser := json.NewDecoder(roundsConfigFile) + if err = jsonParser.Decode(roundsConfig); err != nil { + return nil, err + } + + return roundsConfig, nil +} diff --git a/backend/core/backend.go b/backend/core/backend.go index 62c460db..b580ab41 100644 --- a/backend/core/backend.go +++ b/backend/core/backend.go @@ -15,6 +15,7 @@ type Backend struct { WSConnections []*websocket.Conn WSConnectionsLock sync.Mutex + RoundsConfig *config.RoundsConfig CanvasConfig *config.CanvasConfig BackendConfig *config.BackendConfig @@ -23,9 +24,10 @@ type Backend struct { var ArtPeaceBackend *Backend -func NewBackend(databases *Databases, canvasConfig *config.CanvasConfig, backendConfig *config.BackendConfig, adminMode bool) *Backend { +func NewBackend(databases *Databases, roundsConfig *config.RoundsConfig, canvasConfig *config.CanvasConfig, backendConfig *config.BackendConfig, adminMode bool) *Backend { return &Backend{ Databases: databases, + RoundsConfig: roundsConfig, CanvasConfig: canvasConfig, BackendConfig: backendConfig, AdminMode: adminMode, diff --git a/backend/routes/rounds.go b/backend/routes/rounds.go new file mode 100644 index 00000000..53a907b1 --- /dev/null +++ b/backend/routes/rounds.go @@ -0,0 +1,31 @@ +package routes + +import ( + "encoding/json" + "net/http" + + "github.com/keep-starknet-strange/art-peace/backend/core" + routeutils "github.com/keep-starknet-strange/art-peace/backend/routes/utils" +) + +func InitRoundsRoutes() { + http.HandleFunc("/get-rounds-config", getRoundsConfig) +} + +func getRoundsConfig(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + routeutils.WriteErrorJson(w, http.StatusMethodNotAllowed, "Method not allowed") + return + } + + config := core.ArtPeaceBackend.RoundsConfig + + // Marshal the config to JSON + configJson, err := json.Marshal(config) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Error marshalling config to JSON") + return + } + + routeutils.WriteDataJson(w, string(configJson)) +} diff --git a/backend/routes/routes.go b/backend/routes/routes.go index 18aaefb7..ef6c3f6f 100644 --- a/backend/routes/routes.go +++ b/backend/routes/routes.go @@ -28,4 +28,5 @@ func InitRoutes() { InitWorldsRoutes() InitStencilsRoutes() InitStencilsStaticRoutes() + InitRoundsRoutes() } diff --git a/configs/rounds.config.json b/configs/rounds.config.json new file mode 100644 index 00000000..94f382ba --- /dev/null +++ b/configs/rounds.config.json @@ -0,0 +1,9 @@ +{ + "round3": { + "width": 256, + "height": 192, + "timer": 5, + "startTime": "2024-12-06T00:00:00Z", + "endTime": "2025-01-01T00:00:00Z" + } +} diff --git a/frontend/src/tabs/worlds/WorldsCreationPanel.js b/frontend/src/tabs/worlds/WorldsCreationPanel.js index 3afcfd22..eb0aace4 100644 --- a/frontend/src/tabs/worlds/WorldsCreationPanel.js +++ b/frontend/src/tabs/worlds/WorldsCreationPanel.js @@ -26,6 +26,8 @@ const WorldsCreationPanel = (props) => { Yrs: 1000 * 60 * 60 * 24 * 365 }; + const [isCompetitionWorld, setIsCompetitionWorld] = useState(true); + const createWorldCall = async ( name, width, @@ -191,16 +193,25 @@ const WorldsCreationPanel = (props) => { } if (!checkInputs()) return; + + const submitWidth = isCompetitionWorld ? getCompetitionWidth() : worldWidth; + const submitHeight = isCompetitionWorld + ? getCompetitionHeight() + : worldHeight; + const submitTimer = isCompetitionWorld ? getCompetitionTimer() : timer; + const submitStart = isCompetitionWorld ? getCompetitionStart() : start; + const submitEnd = isCompetitionWorld ? getCompetitionEnd() : end; + if (!devnetMode) { await createWorldCall( worldName, worldSlug, - worldWidth, - worldHeight, - timer, + submitWidth, + submitHeight, + submitTimer, palette, - start, - end + submitStart, + submitEnd ); return; } @@ -213,12 +224,12 @@ const WorldsCreationPanel = (props) => { host: host, name: toHex(worldName), unique_name: toHex(worldSlug), - width: worldWidth.toString(), - height: worldHeight.toString(), - time_between_pixels: timer.toString(), + width: submitWidth.toString(), + height: submitHeight.toString(), + time_between_pixels: submitTimer.toString(), color_palette: palette.toString(), - start_time: Math.floor(start / 1000).toString(), - end_time: Math.floor(end / 1000).toString() + start_time: Math.floor(submitStart / 1000).toString(), + end_time: Math.floor(submitEnd / 1000).toString() }) }); if (response.result) { @@ -248,6 +259,49 @@ const WorldsCreationPanel = (props) => { } }, [worldName]); + const [competitionConfig, setCompetitionConfig] = useState(null); + + // Fetch competition config when component mounts + useEffect(() => { + const fetchRoundsConfig = async () => { + try { + const response = await fetchWrapper('get-rounds-config'); + if (response.data) { + setCompetitionConfig(response.data.round3); + } + } catch (error) { + console.error('Failed to fetch competition config:', error); + } + }; + + fetchRoundsConfig(); + }, []); + + const getCompetitionWidth = () => { + return competitionConfig?.width || 128; // Fallback to default + }; + + const getCompetitionHeight = () => { + return competitionConfig?.height || 128; // Fallback to default + }; + + // Use competition values from config when available + const getCompetitionTimer = () => { + return competitionConfig?.timer || 7; // Fallback to default + }; + + const getCompetitionStart = () => { + return competitionConfig?.startTime + ? new Date(competitionConfig.startTime).getTime() + : new Date('2024-12-07T00:00:00Z').getTime(); + }; + + const getCompetitionEnd = () => { + return competitionConfig?.endTime + ? new Date(competitionConfig.endTime).getTime() + : new Date('2025-01-02T00:00:00Z').getTime(); + }; + return (

{

{nameError &&

{nameError}

} -
-

Size

-
-
+ {isCompetitionWorld ? ( +
+

Size

+

+ {getCompetitionWidth()} x {getCompetitionHeight()} +

+
+ ) : ( +
+

Size

+
- { - if (e.target.value < minWorldSize) { - setWorldWidth(minWorldSize); - } else if (e.target.value > maxWorldSize) { - setWorldWidth(maxWorldSize); - } else { - setWorldWidth(e.target.value); - } +
-

- Width -

-
-

 x 

-
- { - if (e.target.value < minWorldSize) { - setWorldHeight(minWorldSize); - } else if (e.target.value > maxWorldSize) { - setWorldHeight(maxWorldSize); - } else { - setWorldHeight(e.target.value); - } + > + { + if (e.target.value < minWorldSize) { + setWorldWidth(minWorldSize); + } else if (e.target.value > maxWorldSize) { + setWorldWidth(maxWorldSize); + } else { + setWorldWidth(e.target.value); + } + }} + /> +

+ Width +

+
+

 x 

+
-

- Height -

+ > + { + if (e.target.value < minWorldSize) { + setWorldHeight(minWorldSize); + } else if (e.target.value > maxWorldSize) { + setWorldHeight(maxWorldSize); + } else { + setWorldHeight(e.target.value); + } + }} + /> +

+ Height +

+
-
+ )}
-

Timer

- setTimer(Math.round(e.target.value))} - /> -

Seconds between pixels

+ {isCompetitionWorld ? ( + <> +

Timer

+

+ {getCompetitionTimer()} seconds between pixels +

+ + ) : ( + <> +

Timer

+ setTimer(Math.round(e.target.value))} + /> +

Seconds between pixels

+ + )}

Palette

@@ -448,83 +522,121 @@ const WorldsCreationPanel = (props) => {
-

Start

- setStart(new Date(e.target.value).getTime())} - /> -
setStart(new Date().getTime())} - > - Now -
-
-
-

End  

-
- setEnd(new Date(e.target.value).getTime())} - /> -
+ {isCompetitionWorld ? ( + <> +

Start

+

+ {new Date(getCompetitionStart()).toLocaleDateString()} +

+ + ) : ( + <> +

Start

+ setStart(new Date(e.target.value).getTime())} + />
setStart(new Date().getTime())} > + Now +
+ + )} +
+
+ {isCompetitionWorld ? ( + <> +

End

+

+ {new Date(getCompetitionEnd()).toLocaleDateString()} +

+ + ) : ( + <> +

End

+
{ - let newEnd = start + timeUnits[timeUnit] * e.target.value; - if (newEnd < start + timeUnits[timeUnit]) { - newEnd = start + timeUnits[timeUnit]; - } - setEnd(newEnd); - }} - style={{ width: '13rem' }} + type='datetime-local' + value={new Date(end).toISOString().slice(0, -1)} + onChange={(e) => setEnd(new Date(e.target.value).getTime())} />
{ - let keys = Object.keys(timeUnits); - let index = keys.indexOf(timeUnit); - index++; - if (index >= keys.length) { - index = 0; - } - setTimeUnit(keys[index]); + style={{ + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-around', + width: '100%' }} > -
{timeUnit}
+
+ { + let newEnd = + start + timeUnits[timeUnit] * e.target.value; + if (newEnd < start + timeUnits[timeUnit]) { + newEnd = start + timeUnits[timeUnit]; + } + setEnd(newEnd); + }} + style={{ width: '13rem' }} + /> +
{ + let keys = Object.keys(timeUnits); + let index = keys.indexOf(timeUnit); + index++; + if (index >= keys.length) { + index = 0; + } + setTimeUnit(keys[index]); + }} + > +
{timeUnit}
+
+
+
+ setEnd( + new Date().getTime() + 1000 * 60 * 60 * 24 * 1000000 + ) // "No Limit" + } + > + No End +
-
- setEnd(new Date().getTime() + 1000 * 60 * 60 * 24 * 1000000) // "No Limit" - } - > - No End -
-
+ + )} +
+
+
+ setIsCompetitionWorld(e.target.checked)} + /> +