List of commits:
Subject Hash Author Date (UTC)
New game is fully fuctional, and can generate new boards. Settings button is not functional. Still has a lot of debug data available on screen. a66d8cd9c8451631b5bee4b66cee9e63a4ef8bb4932a1512576437773abd3cc1 Sangscienta 2023-07-26 15:54:00
Initial commit for main branch 53278fe5484cf5b33500ab068535a9443763e73678d67eebcc407c262cb73081 Sangscienta 2023-07-26 14:52:18
Commit a66d8cd9c8451631b5bee4b66cee9e63a4ef8bb4932a1512576437773abd3cc1 - New game is fully fuctional, and can generate new boards. Settings button is not functional. Still has a lot of debug data available on screen.
Author: Sangscienta
Author date (UTC): 2023-07-26 15:54
Committer name: Sangscienta
Committer date (UTC): 2023-07-29 13:03
Parent(s): 53278fe5484cf5b33500ab068535a9443763e73678d67eebcc407c262cb73081
Signer:
Signing key:
Signing status: N
Tree: 9d8a07e306ab5cdfb3b30856ec0c6bdcbaf49f5b4628a03bcc6197f795b8e95d
File Lines added Lines deleted
README.md 4 35
src/app.html 2 1
src/app.postcss 2 2
src/lib/Card.svelte 109 0
src/lib/GameGridOperations.ts 71 0
src/lib/GameStateOperations.ts 26 0
src/lib/GlobalDefinitions.ts 32 0
src/routes/+layout.svelte 38 2
src/routes/+page.svelte 141 2
src/theme.postcss 98 0
static/favicon.png 0 0
static/settings.png 0 0
File README.md changed (mode: 100644) (index d890c2f..14733ad)
1 # create-svelte
1 # Card Turning Game
2 2
3 Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
3 Simple game to understand some more basics of Skeleton UI Dev
4 4
5 ## Creating a project
5 ## Game
6 6
7 If you're seeing this, you've probably already done this step. Congrats!
8
9 ```bash
10 # create a new project in the current directory
11 npm create svelte@latest
12
13 # create a new project in my-app
14 npm create svelte@latest my-app
15 ```
16
17 ## Developing
18
19 Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
20
21 ```bash
22 npm run dev
23
24 # or start the server and open the app in a new browser tab
25 npm run dev -- --open
26 ```
27
28 ## Building
29
30 To create a production version of your app:
31
32 ```bash
33 npm run build
34 ```
35
36 You can preview the production build with `npm run preview`.
37
38 > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
7 A simple match the cards game. Turn 2 cards, and see if they match. Goal is to match them all.
File src/app.html changed (mode: 100644) (index 9ea4484..e794f4f)
7 7 %sveltekit.head% %sveltekit.head%
8 8 </head> </head>
9 9 <body data-sveltekit-preload-data="hover" data-theme="rocket"> <body data-sveltekit-preload-data="hover" data-theme="rocket">
10 <div style="display: contents">%sveltekit.body%</div>
10 <div style="display: contents" class="h-full overflow-hidden">%sveltekit.body%</div>
11 11 </body> </body>
12 12 </html> </html>
13
File src/app.postcss changed (mode: 100644) (index af67528..32f474c)
1 1 /*place global styles here */ /*place global styles here */
2 2 html, html,
3 3 body { body {
4 @apply h-full;
4 @apply h-full overflow-hidden;
5 5 } }
6 6
7 7 @font-face { @font-face {
8 8 font-family: 'Space Grotesk'; font-family: 'Space Grotesk';
9 9 src: url('/fonts/SpaceGrotesk.ttf'); src: url('/fonts/SpaceGrotesk.ttf');
10 10 font-display: swap; font-display: swap;
11 }
11 }
File src/lib/Card.svelte added (mode: 100644) (index 0000000..17248f7)
1 <script context="module" lang="ts">
2 export class CardData
3 {
4 index: number;
5 value: string;
6
7 constructor(index: number, value: string)
8 {
9 this.index = index;
10 this.value = value;
11 }
12 };
13 </script>
14
15 <script lang="ts">
16 import { selectedCards } from "./GlobalDefinitions";
17
18 export let cardData: CardData;
19 let flipped = false;
20 let hasMatched = false;
21 let cantFlip =false;
22
23 export function ToggleFlip()
24 {
25 cantFlip = !cantFlip;
26 }
27
28 export function IsMatch(): boolean
29 {
30 return hasMatched;
31 }
32
33 export function Matched()
34 {
35 hasMatched = true;
36 }
37
38 export function FlipCard()
39 {
40 if (hasMatched || cantFlip) {
41 return;
42 }
43
44 flipped = !flipped;
45
46 PrepareSelectedCardsEvaluation();
47 }
48
49 function PrepareSelectedCardsEvaluation()
50 {
51 let tempSelectedCards = $selectedCards;
52
53 if (tempSelectedCards.includes(cardData.index))
54 {
55 tempSelectedCards = tempSelectedCards.filter(index => index !== cardData.index);
56 }
57 else
58 {
59 tempSelectedCards.push(cardData.index);
60 }
61
62 selectedCards.set(tempSelectedCards);
63 }
64
65 </script>
66
67 <div class="memory-card bg-secondary-100/60" class:flip={flipped} on:click={FlipCard} on:keydown={() => {FlipCard}}>
68 <div class="front-face"><img src="favicon.png" alt="Front"></div>
69 <div class="back-face flex justify-center items-center text-7xl text-black">{cardData.value}</div>
70 </div>
71
72 <style>
73 .memory-card
74 {
75 width: calc(25% - 10px);
76 height: calc(33% - 10px);
77 margin: 5px;
78 position: relative;
79 box-shadow: 1px 1px 1px rgba(0,0,0,.3);
80 transform: scale(1);
81 transform-style: preserve-3d;
82 transition: transform .5s;
83 }
84
85 .memory-card:active
86 {
87 transform: scale(0.97);
88 transition: transform .1s;
89 }
90
91 .front-face,
92 .back-face
93 {
94 width: 100%;
95 height: 100%;
96 padding: 20px;
97 position: absolute;
98 border-radius: 5px;
99 backface-visibility: hidden;
100 }
101 .back-face
102 {
103 transform: rotateY(180deg);
104 }
105 .memory-card.flip
106 {
107 transform: rotateY(180deg);
108 }
109 </style>
File src/lib/GameGridOperations.ts added (mode: 100644) (index 0000000..1e770c8)
1 import { CardData } from "./Card.svelte";
2 import { IsEven } from "./GlobalDefinitions";
3
4
5 export function CreateTestCardDataGrid(gridSize: number): CardData[]
6 {
7
8 if(gridSize < 2 || !IsEven(gridSize))
9 {
10 return [];
11 }
12
13 const grid: CardData[] = [];
14
15 for (let index = 0, symbol = 1; index < gridSize; index++, symbol++)
16 {
17 grid[index++] = new CardData(index, (symbol).toString());
18 grid[index] = new CardData(index, (symbol).toString());
19 }
20
21 return grid;
22 }
23
24 export function CreateLoopedCardDataGrid(gridSize: number): CardData[]
25 {
26
27 if(gridSize < 2 || !IsEven(gridSize))
28 {
29 return [];
30 }
31
32 const grid: CardData[] = [];
33 const halfGrid = gridSize / 2;
34
35 for (let index = 0, symbol = 1; index < gridSize; index++, symbol = (symbol++ % halfGrid) + 1)
36 {
37 grid[index] = new CardData(index, (symbol).toString());
38 }
39
40 return grid;
41 }
42
43 export function CreateFisherYatesCardDataGrid(gridSize: number): CardData[]
44 {
45
46 if(gridSize < 2 || !IsEven(gridSize))
47 {
48 return [];
49 }
50
51 const grid: CardData[] = CreateLoopedCardDataGrid(gridSize);
52
53 const FisherYatesShuffleArray = (array: CardData[]) =>
54 {
55 for (let initialIndex = array.length - 1; initialIndex > 0; initialIndex--)
56 {
57 const indexToSwap = Math.floor(Math.random() * (initialIndex + 1));
58 [array[initialIndex], array[indexToSwap]] = [array[indexToSwap], array[initialIndex]];
59 }
60 return array;
61 };
62
63 const shuffledGrid = FisherYatesShuffleArray(grid);
64
65 // Correct indexes
66 for (let index = 0; index < gridSize; index++) {
67 shuffledGrid[index].index = index;
68 }
69
70 return shuffledGrid;
71 }
File src/lib/GameStateOperations.ts added (mode: 100644) (index 0000000..c840ddc)
1 import { State, state, stateMessage, selectedCards } from "$lib/GlobalDefinitions";
2
3 export function StartGame()
4 {
5 state.set(State.PLAYING);
6 stateMessage.set("");
7 selectedCards.set([]);
8 }
9
10 export function GiveUp()
11 {
12 state.set(State.GAMEOVER);
13 stateMessage.set("The game has ended. Would you like to start a new one?");
14 }
15
16 export function Win()
17 {
18 state.set(State.GAMEOVER);
19 stateMessage.set("You won the game. Would you like to start a new one?");
20 }
21
22 export function StartScreen()
23 {
24 state.set(State.START);
25 stateMessage.set("Start a new game");
26 }
File src/lib/GlobalDefinitions.ts added (mode: 100644) (index 0000000..a61bf51)
1 import { writable } from 'svelte/store';
2
3 export enum State
4 {
5 START = 'start',
6 PLAYING = 'playing',
7 PAUSED = 'paused',
8 GAMEOVER = 'gameover'
9 };
10
11 export const CardSelection =
12 {
13
14 };
15
16 export const MAX_CARD_WIDTH = 100;
17 export const MIN_CARD_WIDTH = 40;
18 export const CARD_ASPECT_RATIO = 1.4;
19
20 export const state = writable(State.START);
21 export const stateMessage = writable("Start a new game");
22 export const selectedCards = writable<number[]>([]);
23
24 export function SleepThenDo(milliseconds: number, callback: () => void)
25 {
26 setTimeout(() => { callback(); }, milliseconds);
27 }
28
29 export function IsEven(value: number): boolean
30 {
31 return value % 2 == 0;
32 }
File src/routes/+layout.svelte changed (mode: 100644) (index b0cad91..6b355e1)
1 1 <script lang='ts'> <script lang='ts'>
2 2 // The ordering of these imports is critical to your app working properly // The ordering of these imports is critical to your app working properly
3 import '@skeletonlabs/skeleton/themes/theme-rocket.css';
3 import '../theme.postcss';
4 4 // If you have source.organizeImports set to true in VSCode, then it will auto change this ordering // If you have source.organizeImports set to true in VSCode, then it will auto change this ordering
5 5 import '@skeletonlabs/skeleton/styles/skeleton.css'; import '@skeletonlabs/skeleton/styles/skeleton.css';
6 6 // Most of your app wide CSS should be put in this file // Most of your app wide CSS should be put in this file
7 7 import '../app.postcss'; import '../app.postcss';
8 // Skeleton imports
9 import { AppShell, AppBar, Avatar } from '@skeletonlabs/skeleton';
10 // Global Definitions
11 import { state } from "$lib/GlobalDefinitions";
12 import { GiveUp, StartGame, StartScreen } from '$lib/GameStateOperations';
8 13 </script> </script>
9 14
10 <slot />
15 <svelte:head>
16 <title>CTG</title>
17 </svelte:head>
18
19 <AppShell slotSidebarLeft="bg-primary-900 w-56 p-4">
20 <svelte:fragment slot="header">
21 <AppBar gridColumns="grid-cols-3" slotDefault="place-self-center" slotTrail="place-content-end">
22 <svelte:fragment slot="lead"><Avatar src="favicon.png" initials="CTG" background="bg-slate-300" /></svelte:fragment>
23 Card Turning Game
24 <svelte:fragment slot="trail">
25 <span class="chip bg-slate-300 hover:variant-filled">
26 <img src="settings.png" alt="" class="w-10 h-10"/>
27 </span>
28 </svelte:fragment>
29 </AppBar>
30 </svelte:fragment>
31 <svelte:fragment slot="pageHeader">
32 <div class="bg-surface-900 text-center">{$state}</div>
33 </svelte:fragment>
34 <svelte:fragment slot="sidebarLeft" >
35 <div class="p-1"><button type="button" class="btn btn-l variant-filled" on:click={StartScreen}>Start Screen</button></div>
36 <div class="p-1"><button type="button" class="btn btn-l variant-filled" on:click={StartGame}>New Game</button></div>
37 <div class="p-1"><button type="button" class="btn btn-l variant-filled" on:click={GiveUp}>Give Up</button></div>
38 </svelte:fragment>
39 <!-- (sidebarRight) -->
40 <!-- Router Slot -->
41 <slot />
42 <!-- ---- / ---- -->
43 <!-- (pageFooter) -->
44 <svelte:fragment slot="footer">Footer</svelte:fragment>
45 </AppShell>
46
File src/routes/+page.svelte changed (mode: 100644) (index 60e3960..1d227cb)
1 <!-- YOU CAN DELETE EVERYTHING IN THIS PAGE -->
1 <script context="module" lang="ts">
2
3 </script>
2 4
3 <div class="container h-full mx-auto flex justify-center items-center">
5 <script lang="ts">
6 import { State, selectedCards, state, stateMessage, SleepThenDo } from '$lib/GlobalDefinitions';
7 import { StartGame, Win } from '$lib/GameStateOperations';
8 import Card from '$lib/Card.svelte';
9 import type { CardData } from '$lib/Card.svelte';
10 import { onDestroy } from 'svelte';
11 import { CreateFisherYatesCardDataGrid, CreateLoopedCardDataGrid, CreateTestCardDataGrid } from '$lib/GameGridOperations';
12
13 const NUMBER_OF_CARDS = 12;
14
15 export let cardsOnTable: CardData[] = [];
16 let allCards: Card[] = [];
17
18 // cardsOnTable = CreateTestCardDataGrid(NUMBER_OF_CARDS);
19 // cardsOnTable = CreateLoopedCardDataGrid(NUMBER_OF_CARDS);
20 cardsOnTable = CreateFisherYatesCardDataGrid(NUMBER_OF_CARDS);
21
22 function HandleChangesInSelectedCards(cardSelection: number[])
23 {
24 switch (cardSelection.length) {
25 case 2:
26 ToggleFlipping();
27
28 let firstCard = cardsOnTable[cardSelection[0]];
29 let secondCard = cardsOnTable[cardSelection[1]];
30
31 if (firstCard.value == secondCard.value)
32 {
33 ToggleFlipping();
34
35 allCards[firstCard.index].Matched();
36 allCards[secondCard.index].Matched();
37
38 selectedCards.set([]);
39 }
40 else
41 {
42 SleepThenDo(1500, () => {ResetFlippedCards(firstCard.index, secondCard.index)});
43 }
44 break;
45
46 // This is required, so we don't win in the beggining, before we have allCards on the table
47 case 0:
48 return;
49
50 default:
51 break;
52 }
53
54 let winCondition = allCards.filter(card => !card.IsMatch()).length <= 0;
55
56 if(winCondition)
57 {
58 SleepThenDo(1500, Win);
59 }
60 }
61
62 function ResetFlippedCards(firstCardIndex: number, secondCardIndex: number)
63 {
64 ToggleFlipping();
65
66 allCards[firstCardIndex].FlipCard();
67 allCards[secondCardIndex].FlipCard();
68
69 selectedCards.set([]);
70 }
71
72 function ToggleFlipping()
73 {
74 allCards.forEach(card =>
75 {
76 card.ToggleFlip();
77 });
78 }
79
80 const unsubscribeState = state.subscribe(HandleChangeGameState);
81 const unsubscribeCards = selectedCards.subscribe(HandleChangesInSelectedCards);
82 onDestroy(unsubscribeState);
83 onDestroy(unsubscribeCards);
84
85
86 function HandleChangeGameState(value: State): void {
87 switch ($state) {
88 case State.PLAYING:
89 cardsOnTable = CreateFisherYatesCardDataGrid(NUMBER_OF_CARDS);
90 break;
91
92 default:
93 break;
94 }
95 }
96 </script>
97
98 {#if $state == State.GAMEOVER || $state == State.START}
99 <div class="container bg-slate-500 h-full w-full mx-auto flex flex-wrap justify-center items-center overflow-hidden">
100 <div class="mx-auto flex flex-wrap h-36 justify-center items-center">
101 <div>{$stateMessage}</div>
102 <div class="break" />
103 <div><button type="button" class="btn btn-xl variant-filled" on:click={StartGame}>New Game</button></div>
104 </div>
4 105 </div> </div>
106
107 {:else if $state == State.PLAYING}
108 <div>Selected cards: {$selectedCards}</div>
109 <div class="bg-slate-500 h-full w-full overflow-hidden">
110 <div class="memory-game container bg-primary-900 h-full w-full mx-auto flex flex-wrap justify-center items-center">
111 {#each cardsOnTable as card, i}
112 <Card bind:this="{allCards[i]}" cardData="{card}"></Card>
113 {/each}
114 </div>
115 </div>
116 {:else}
117 <!-- else content here -->
118 {/if}
119
120 <style>
121 /* This break allows to create new lines inside flex containers with wrap activated */
122 .break
123 {
124 flex-basis: 100%;
125 height: 0;
126 }
127 /* *
128 {
129 padding: 0;
130 margin: 0;
131 box-sizing: border-box;
132 } */
133 .memory-game
134 {
135 min-width: 640px;
136 min-height: 640px;
137 margin: auto;
138 display: flex;
139 flex-wrap: wrap;
140 overflow: hidden;
141 perspective: 1000px;
142 }
143 </style>
File src/theme.postcss added (mode: 100644) (index 0000000..9b347a9)
1
2 :root {
3 /* =~= Theme Properties =~= */
4 --theme-font-family-base: system-ui;
5 --theme-font-family-heading: system-ui;
6 --theme-font-color-base: 0 0 0;
7 --theme-font-color-dark: 255 255 255;
8 --theme-rounded-base: 9999px;
9 --theme-rounded-container: 8px;
10 --theme-border-base: 1px;
11 /* =~= Theme On-X Colors =~= */
12 --on-primary: var(--color-tertiary-50);
13 --on-secondary: var(--color-primary-600);
14 --on-tertiary: 0 0 0;
15 --on-success: 0 0 0;
16 --on-warning: 0 0 0;
17 --on-error: 0 0 0;
18 --on-surface: 0 0 0;
19 /* =~= Theme Colors =~= */
20 /* primary | #1e317b */
21 --color-primary-50: 221 224 235; /* ⬅ #dde0eb */
22 --color-primary-100: 210 214 229; /* ⬅ #d2d6e5 */
23 --color-primary-200: 199 204 222; /* ⬅ #c7ccde */
24 --color-primary-300: 165 173 202; /* ⬅ #a5adca */
25 --color-primary-400: 98 111 163; /* ⬅ #626fa3 */
26 --color-primary-500: 30 49 123; /* ⬅ #1e317b */
27 --color-primary-600: 27 44 111; /* ⬅ #1b2c6f */
28 --color-primary-700: 23 37 92; /* ⬅ #17255c */
29 --color-primary-800: 18 29 74; /* ⬅ #121d4a */
30 --color-primary-900: 15 24 60; /* ⬅ #0f183c */
31 /* secondary | #eec263 */
32 --color-secondary-50: 252 246 232; /* ⬅ #fcf6e8 */
33 --color-secondary-100: 252 243 224; /* ⬅ #fcf3e0 */
34 --color-secondary-200: 251 240 216; /* ⬅ #fbf0d8 */
35 --color-secondary-300: 248 231 193; /* ⬅ #f8e7c1 */
36 --color-secondary-400: 243 212 146; /* ⬅ #f3d492 */
37 --color-secondary-500: 238 194 99; /* ⬅ #eec263 */
38 --color-secondary-600: 214 175 89; /* ⬅ #d6af59 */
39 --color-secondary-700: 179 146 74; /* ⬅ #b3924a */
40 --color-secondary-800: 143 116 59; /* ⬅ #8f743b */
41 --color-secondary-900: 117 95 49; /* ⬅ #755f31 */
42 /* tertiary | #daf2d4 */
43 --color-tertiary-50: 249 253 249; /* ⬅ #f9fdf9 */
44 --color-tertiary-100: 248 252 246; /* ⬅ #f8fcf6 */
45 --color-tertiary-200: 246 252 244; /* ⬅ #f6fcf4 */
46 --color-tertiary-300: 240 250 238; /* ⬅ #f0faee */
47 --color-tertiary-400: 229 246 225; /* ⬅ #e5f6e1 */
48 --color-tertiary-500: 218 242 212; /* ⬅ #daf2d4 */
49 --color-tertiary-600: 196 218 191; /* ⬅ #c4dabf */
50 --color-tertiary-700: 164 182 159; /* ⬅ #a4b69f */
51 --color-tertiary-800: 131 145 127; /* ⬅ #83917f */
52 --color-tertiary-900: 107 119 104; /* ⬅ #6b7768 */
53 /* success | #4cbf4a */
54 --color-success-50: 228 245 228; /* ⬅ #e4f5e4 */
55 --color-success-100: 219 242 219; /* ⬅ #dbf2db */
56 --color-success-200: 210 239 210; /* ⬅ #d2efd2 */
57 --color-success-300: 183 229 183; /* ⬅ #b7e5b7 */
58 --color-success-400: 130 210 128; /* ⬅ #82d280 */
59 --color-success-500: 76 191 74; /* ⬅ #4cbf4a */
60 --color-success-600: 68 172 67; /* ⬅ #44ac43 */
61 --color-success-700: 57 143 56; /* ⬅ #398f38 */
62 --color-success-800: 46 115 44; /* ⬅ #2e732c */
63 --color-success-900: 37 94 36; /* ⬅ #255e24 */
64 /* warning | #f986cf */
65 --color-warning-50: 254 237 248; /* ⬅ #feedf8 */
66 --color-warning-100: 254 231 245; /* ⬅ #fee7f5 */
67 --color-warning-200: 254 225 243; /* ⬅ #fee1f3 */
68 --color-warning-300: 253 207 236; /* ⬅ #fdcfec */
69 --color-warning-400: 251 170 221; /* ⬅ #fbaadd */
70 --color-warning-500: 249 134 207; /* ⬅ #f986cf */
71 --color-warning-600: 224 121 186; /* ⬅ #e079ba */
72 --color-warning-700: 187 101 155; /* ⬅ #bb659b */
73 --color-warning-800: 149 80 124; /* ⬅ #95507c */
74 --color-warning-900: 122 66 101; /* ⬅ #7a4265 */
75 /* error | #f04747 */
76 --color-error-50: 253 227 227; /* ⬅ #fde3e3 */
77 --color-error-100: 252 218 218; /* ⬅ #fcdada */
78 --color-error-200: 251 209 209; /* ⬅ #fbd1d1 */
79 --color-error-300: 249 181 181; /* ⬅ #f9b5b5 */
80 --color-error-400: 245 126 126; /* ⬅ #f57e7e */
81 --color-error-500: 240 71 71; /* ⬅ #f04747 */
82 --color-error-600: 216 64 64; /* ⬅ #d84040 */
83 --color-error-700: 180 53 53; /* ⬅ #b43535 */
84 --color-error-800: 144 43 43; /* ⬅ #902b2b */
85 --color-error-900: 118 35 35; /* ⬅ #762323 */
86 /* surface | #8b8d92 */
87 --color-surface-50: 238 238 239; /* ⬅ #eeeeef */
88 --color-surface-100: 232 232 233; /* ⬅ #e8e8e9 */
89 --color-surface-200: 226 227 228; /* ⬅ #e2e3e4 */
90 --color-surface-300: 209 209 211; /* ⬅ #d1d1d3 */
91 --color-surface-400: 174 175 179; /* ⬅ #aeafb3 */
92 --color-surface-500: 139 141 146; /* ⬅ #8b8d92 */
93 --color-surface-600: 125 127 131; /* ⬅ #7d7f83 */
94 --color-surface-700: 104 106 110; /* ⬅ #686a6e */
95 --color-surface-800: 83 85 88; /* ⬅ #535558 */
96 --color-surface-900: 68 69 72; /* ⬅ #444548 */
97
98 }
File static/favicon.png changed (mode: 100644) (index a32da05..505de21)
File static/settings.png added (mode: 100644) (index 0000000..84b8601)
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/Sangscienta/Card-Turning-Game

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/Sangscienta/Card-Turning-Game

Clone this repository using git:
git clone git://git.rocketgit.com/user/Sangscienta/Card-Turning-Game

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main