diff --git a/.editorconfig b/.editorconfig old mode 100644 new mode 100755 diff --git a/.env.example b/.env.example old mode 100644 new mode 100755 diff --git a/.gitattributes b/.gitattributes old mode 100644 new mode 100755 diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 08542eb..3de40c8 --- a/README.md +++ b/README.md @@ -119,3 +119,7 @@ ## Contributing ## License This project is licensed under the [MIT License](LICENSE). + +## TODO +Explain setting up oauth +https://twitchapps.com/tmi/ \ No newline at end of file diff --git a/app/Helpers/TwitchHelper b/app/Helpers/TwitchHelper deleted file mode 100644 index b179a12..0000000 --- a/app/Helpers/TwitchHelper +++ /dev/null @@ -1,32 +0,0 @@ - 'place', - 'x' => $matches[1], - 'y' => $matches[2], - 'color' => $matches[3] - ]; - } - - if (preg_match($shortCommandPattern, $message, $matches)) { - return [ - 'command' => 'place', - 'x' => $matches[1], - 'y' => $matches[2], - 'color' => $matches[3] - ]; - } - - return null; - } -} diff --git a/app/Helpers/TwitchHelper.php b/app/Helpers/TwitchHelper.php new file mode 100755 index 0000000..3b4ba30 --- /dev/null +++ b/app/Helpers/TwitchHelper.php @@ -0,0 +1,29 @@ + $message]); + + // Match commands like !place A 1 red, !p A 1 red, !paint A 1 red + $commandPattern = '/^(?:!place|!p|!paint)\s([A-P])\s(\d{1,2})\s(\w+)$/i'; + + if (preg_match($commandPattern, $message, $matches)) { + Log::debug('TwitchHelper::parseMessage - Command matched', ['matches' => $matches]); + return [ + 'command' => 'place', + 'x' => $matches[1], + 'y' => $matches[2], + 'color' => $matches[3] + ]; + } + + Log::debug('TwitchHelper::parseMessage - No match found'); + return null; + } +} diff --git a/app/Http/Controllers/Auth/AuthenticatedSessionController.php b/app/Http/Controllers/Auth/AuthenticatedSessionController.php old mode 100644 new mode 100755 diff --git a/app/Http/Controllers/Auth/ConfirmablePasswordController.php b/app/Http/Controllers/Auth/ConfirmablePasswordController.php old mode 100644 new mode 100755 diff --git a/app/Http/Controllers/Auth/EmailVerificationNotificationController.php b/app/Http/Controllers/Auth/EmailVerificationNotificationController.php old mode 100644 new mode 100755 diff --git a/app/Http/Controllers/Auth/EmailVerificationPromptController.php b/app/Http/Controllers/Auth/EmailVerificationPromptController.php old mode 100644 new mode 100755 diff --git a/app/Http/Controllers/Auth/NewPasswordController.php b/app/Http/Controllers/Auth/NewPasswordController.php old mode 100644 new mode 100755 diff --git a/app/Http/Controllers/Auth/PasswordController.php b/app/Http/Controllers/Auth/PasswordController.php old mode 100644 new mode 100755 diff --git a/app/Http/Controllers/Auth/PasswordResetLinkController.php b/app/Http/Controllers/Auth/PasswordResetLinkController.php old mode 100644 new mode 100755 diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php old mode 100644 new mode 100755 diff --git a/app/Http/Controllers/Auth/VerifyEmailController.php b/app/Http/Controllers/Auth/VerifyEmailController.php old mode 100644 new mode 100755 diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php old mode 100644 new mode 100755 diff --git a/app/Http/Controllers/GameSessionController.php b/app/Http/Controllers/GameSessionController.php new file mode 100644 index 0000000..535a3c2 --- /dev/null +++ b/app/Http/Controllers/GameSessionController.php @@ -0,0 +1,36 @@ +input('session_name'); + $session = GameSession::create(['session_name' => $sessionName]); + + return response()->json($session); + } + + public function end(Request $request) + { + $sessionId = $request->input('session_id'); + $session = GameSession::find($sessionId); + + if ($session) { + $session->update(['ended_at' => now()]); + return response()->json($session); + } + + return response()->json(['error' => 'Session not found'], 404); + } + + public function current() + { + $session = GameSession::whereNull('ended_at')->latest()->first(); + return response()->json($session); + } +} diff --git a/app/Http/Controllers/PaletteController.php b/app/Http/Controllers/PaletteController.php new file mode 100644 index 0000000..2a023e2 --- /dev/null +++ b/app/Http/Controllers/PaletteController.php @@ -0,0 +1,68 @@ +validate([ + 'name' => 'required|unique:palettes,name', + ]); + + $palette = Palette::create(['name' => $request->name]); + return response()->json($palette); + } + + public function addColor(Request $request, Palette $palette) + { + $request->validate([ + 'name' => 'required|regex:/^[a-z-]+$/', + 'hex_value' => 'required|regex:/^#[a-fA-F0-9]{6}$/' + ]); + + $color = PaletteColor::create([ + 'palette_id' => $palette->id, + 'name' => $request->name, + 'hex_value' => $request->hex_value + ]); + + return response()->json($color); + } + + public function updateColor(Request $request, Palette $palette, PaletteColor $color) + { + $request->validate([ + 'name' => 'required|regex:/^[a-z-]+$/', + 'hex_value' => 'required|regex:/^#[a-fA-F0-9]{6}$/' + ]); + + $color->update([ + 'name' => $request->name, + 'hex_value' => $request->hex_value + ]); + + return response()->json($color); + } + + public function deleteColor(Palette $palette, PaletteColor $color) + { + $color->delete(); + return response()->json(['message' => 'Color deleted']); + } + + public function getColors(Palette $palette) + { + return response()->json($palette->colors); + } +} diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php old mode 100644 new mode 100755 diff --git a/app/Http/Controllers/TwitchController.php b/app/Http/Controllers/TwitchController.php old mode 100644 new mode 100755 index 650096b..d0cbc60 --- a/app/Http/Controllers/TwitchController.php +++ b/app/Http/Controllers/TwitchController.php @@ -4,23 +4,97 @@ use Illuminate\Http\Request; use App\Helpers\TwitchHelper; +use App\Models\TwitchUser; +use App\Models\CommandHistory; +use App\Models\CurrentState; +use App\Models\GameSession; +use Illuminate\Support\Facades\Log; class TwitchController extends Controller { public function parseChatMessage(Request $request) { + Log::info("Entered parseChatMessage"); $message = $request->input('message'); + $username = $request->input('username'); + $gameSession = $this->getCurrentGameSession(); - if (!$message) { - return response()->json(['error' => 'No message provided'], 400); + Log::debug('TwitchController::parseChatMessage', [ + 'message' => $message, + 'username' => $username, + 'gameSessionId' => $gameSession->id + ]); + + if (!$message || !$username || !$gameSession) { + Log::debug('TwitchController::parseChatMessage - Missing input or invalid game session', [ + 'message' => $message, + 'username' => $username, + 'gameSession' => $gameSession + ]); + return response()->json(['error' => 'No message, username, or valid game session provided'], 400); } + $user = TwitchUser::firstOrCreate(['twitch_username' => $username]); + $parsedCommand = TwitchHelper::parseMessage($message); + Log::debug('TwitchController::parseChatMessage - Parsed Command', ['parsedCommand' => $parsedCommand]); if ($parsedCommand) { + CommandHistory::create([ + 'user_id' => $user->id, + 'command' => $parsedCommand['command'], + 'x' => $parsedCommand['x'] ?? null, + 'y' => $parsedCommand['y'] ?? null, + 'color' => $parsedCommand['color'] ?? null, + 'game_session_id' => $gameSession->id, + ]); + + switch ($parsedCommand['command']) { + case 'place': + $this->handlePlaceCommand($parsedCommand, $user->id); + break; + // Add cases for other commands like 'row', 'column', 'fill' + } + return response()->json($parsedCommand); } + Log::debug('TwitchController::parseChatMessage - Invalid command', ['message' => $message]); return response()->json(['error' => 'Invalid command'], 400); } + + private function handlePlaceCommand($parsedCommand, $userId) + { + Log::debug('TwitchController::handlePlaceCommand', ['command' => $parsedCommand]); + + $x = $parsedCommand['x']; + $y = $parsedCommand['y']; + $color = $parsedCommand['color']; + + $currentState = CurrentState::where('x', $x)->where('y', $y)->first(); + Log::debug('TwitchController::handlePlaceCommand - Retrieved Current State', ['currentState' => $currentState]); + + if ($currentState) { + $currentState->update(['color' => $color, 'updated_by' => $userId]); + } else { + CurrentState::create(['x' => $x, 'y' => $y, 'color' => $color, 'updated_by' => $userId]); + } + } + + private function getCurrentGameSession() + { + $gameSession = GameSession::whereNull('ended_at')->latest()->first(); + + if (!$gameSession) { + $gameSession = GameSession::create(['session_name' => 'Session ' . now()]); + } + + return $gameSession; + } + + public function getCurrentState() + { + $currentState = CurrentState::all(); + return response()->json($currentState); + } } diff --git a/app/Http/Requests/Auth/LoginRequest.php b/app/Http/Requests/Auth/LoginRequest.php old mode 100644 new mode 100755 diff --git a/app/Http/Requests/ProfileUpdateRequest.php b/app/Http/Requests/ProfileUpdateRequest.php old mode 100644 new mode 100755 diff --git a/app/Models/CommandHistory.php b/app/Models/CommandHistory.php new file mode 100755 index 0000000..f8eeb58 --- /dev/null +++ b/app/Models/CommandHistory.php @@ -0,0 +1,15 @@ +hasMany(PaletteColor::class); + } +} diff --git a/app/Models/PaletteColor.php b/app/Models/PaletteColor.php new file mode 100644 index 0000000..893da95 --- /dev/null +++ b/app/Models/PaletteColor.php @@ -0,0 +1,18 @@ +belongsTo(Palette::class); + } +} diff --git a/app/Models/SourceImage.php b/app/Models/SourceImage.php new file mode 100755 index 0000000..27e23fd --- /dev/null +++ b/app/Models/SourceImage.php @@ -0,0 +1,13 @@ +id(); + $table->string('twitch_username')->unique(); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('twitch_users'); + } +} diff --git a/database/migrations/2024_07_20_201601_create_game_sessions_table.php b/database/migrations/2024_07_20_201601_create_game_sessions_table.php new file mode 100755 index 0000000..3956163 --- /dev/null +++ b/database/migrations/2024_07_20_201601_create_game_sessions_table.php @@ -0,0 +1,22 @@ +id(); + $table->string('session_name'); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('game_sessions'); + } +} diff --git a/database/migrations/2024_07_20_201602_create_current_state_table.php b/database/migrations/2024_07_20_201602_create_current_state_table.php new file mode 100755 index 0000000..60b438c --- /dev/null +++ b/database/migrations/2024_07_20_201602_create_current_state_table.php @@ -0,0 +1,35 @@ +id(); + $table->string('x'); + $table->string('y'); + $table->string('color'); + $table->foreignId('updated_by')->constrained('twitch_users')->onDelete('cascade'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('current_state'); + } +} diff --git a/database/migrations/2024_07_20_201603_create_command_history_table.php b/database/migrations/2024_07_20_201603_create_command_history_table.php new file mode 100755 index 0000000..2bd5db7 --- /dev/null +++ b/database/migrations/2024_07_20_201603_create_command_history_table.php @@ -0,0 +1,37 @@ +id(); + $table->foreignId('user_id')->constrained('twitch_users')->onDelete('cascade'); + $table->string('command'); + $table->string('x')->nullable(); + $table->string('y')->nullable(); + $table->string('color')->nullable(); + $table->foreignId('game_session_id')->constrained('game_sessions')->onDelete('cascade'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('command_history'); + } +} diff --git a/database/migrations/2024_07_20_201604_create_source_images_table.php b/database/migrations/2024_07_20_201604_create_source_images_table.php new file mode 100755 index 0000000..ac562e3 --- /dev/null +++ b/database/migrations/2024_07_20_201604_create_source_images_table.php @@ -0,0 +1,24 @@ +id(); + $table->string('name'); + $table->json('pixel_state'); + $table->json('palette'); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('source_images'); + } +} diff --git a/database/migrations/2024_07_20_214228_add_ended_at_to_game_sessions_table.php b/database/migrations/2024_07_20_214228_add_ended_at_to_game_sessions_table.php new file mode 100644 index 0000000..9ebb6ef --- /dev/null +++ b/database/migrations/2024_07_20_214228_add_ended_at_to_game_sessions_table.php @@ -0,0 +1,36 @@ +timestamp('ended_at')->nullable(); + } + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('game_sessions', function (Blueprint $table) { + if (Schema::hasColumn('game_sessions', 'ended_at')) { + $table->dropColumn('ended_at'); + } + }); + } +} diff --git a/database/migrations/2024_07_21_033036_create_palettes_table.php b/database/migrations/2024_07_21_033036_create_palettes_table.php new file mode 100644 index 0000000..93cf4c8 --- /dev/null +++ b/database/migrations/2024_07_21_033036_create_palettes_table.php @@ -0,0 +1,78 @@ +id(); + $table->string('name'); + $table->timestamps(); + }); + + Schema::create('palette_colors', function (Blueprint $table) { + $table->id(); + $table->foreignId('palette_id')->constrained()->onDelete('cascade'); + $table->string('name'); + $table->string('hex_value'); + $table->timestamps(); + }); + + // Insert default palette + DB::table('palettes')->insert([ + 'name' => 'default-colors', + 'created_at' => now(), + 'updated_at' => now(), + ]); + + $defaultColors = [ + ["white", "#FFFFFF"], + ["light-gray", "#E4E4E4"], + ["medium-gray", "#888888"], + ["dark-gray", "#222222"], + ["pink", "#FFA7D1"], + ["red", "#E50000"], + ["orange", "#E59500"], + ["brown", "#A06A42"], + ["yellow", "#E5D900"], + ["light-green", "#94E044"], + ["green", "#02BE01"], + ["cyan", "#00D3DD"], + ["blue", "#0083C7"], + ["dark-blue", "#0000EA"], + ["purple", "#CF6EE4"], + ["dark-purple", "#820080"], + ["black", "#000000"] + ]; + + foreach ($defaultColors as $color) { + DB::table('palette_colors')->insert([ + 'palette_id' => 1, + 'name' => $color[0], + 'hex_value' => $color[1], + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('palette_colors'); + Schema::dropIfExists('palettes'); + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php old mode 100644 new mode 100755 diff --git a/package-lock.json b/package-lock.json old mode 100644 new mode 100755 diff --git a/package.json b/package.json old mode 100644 new mode 100755 diff --git a/phpunit.xml b/phpunit.xml old mode 100644 new mode 100755 diff --git a/postcss.config.js b/postcss.config.js old mode 100644 new mode 100755 diff --git a/public/.htaccess b/public/.htaccess old mode 100644 new mode 100755 diff --git a/public/favicon.ico b/public/favicon.ico old mode 100644 new mode 100755 diff --git a/public/index.php b/public/index.php old mode 100644 new mode 100755 diff --git a/public/robots.txt b/public/robots.txt old mode 100644 new mode 100755 diff --git a/resources/css/app.css b/resources/css/app.css old mode 100644 new mode 100755 diff --git a/resources/css/grid.css b/resources/css/grid.css new file mode 100755 index 0000000..ea4983b --- /dev/null +++ b/resources/css/grid.css @@ -0,0 +1,35 @@ +body { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; + font-family: Arial, sans-serif; +} + +:root { + --grid-size: 16; /* Adjust this value for grid size (e.g., 3 for 3x3) */ +} + +.grid-container { + height: 100vh; + width: 100vh; /* Keep the grid square */ + max-width: 100vh; + display: grid; + grid-template-columns: repeat(calc(var(--grid-size) + 2), 1fr); + grid-template-rows: repeat(calc(var(--grid-size) + 2), 1fr); +} + +.label { + display: flex; + justify-content: center; + align-items: center; + background-color: #f5f5f5; + border: 1px solid #ddd; + font-size: calc(0.5vw + 0.5vh); /* Adjust font size for better visibility */ +} + +.cell { + background-color: #fff; + border: 1px solid #ddd; +} diff --git a/resources/css/palette-editor.css b/resources/css/palette-editor.css new file mode 100644 index 0000000..e4a8042 --- /dev/null +++ b/resources/css/palette-editor.css @@ -0,0 +1,6 @@ +.color-picker { + border: 1px solid black; + border-radius: 4px; + width: 100%; + height: 36px; +} diff --git a/resources/js/app.js b/resources/js/app.js old mode 100644 new mode 100755 diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js old mode 100644 new mode 100755 diff --git a/resources/js/client.js b/resources/js/client.js index 5745275..88c12a3 100644 --- a/resources/js/client.js +++ b/resources/js/client.js @@ -30,34 +30,26 @@ ws.onmessage = (event) => { if (parsedMessage && parsedMessage.command === 'PRIVMSG') { const chatMessage = parsedMessage.params.slice(1).join(' ').trim(); const username = parsedMessage.tags['display-name'] || parsedMessage.prefix.split('!')[0]; + const gameSessionId = 1; // Replace with logic to get the current game session ID + console.log('Chat Message:', chatMessage); console.log('Username:', username); window.dispatchEvent(new CustomEvent('chat-message', { detail: { user: username, message: chatMessage } })); - // Process !place command fetch('/twitch/parse', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content') }, - body: JSON.stringify({ message: chatMessage }) + body: JSON.stringify({ message: chatMessage, username: username, game_session_id: gameSessionId }) }) .then(response => response.json()) .then(data => { if (data && data.command === 'place') { - console.log('Parsed Command:', data); - // Call the existing API with parsed coordinates and color - // Example API call: /api/place/{x}/{y}/{color} - fetch(`/api/place/${data.x}/${data.y}/${data.color}`, { method: 'POST' }) - .then(response => response.json()) - .then(result => { - console.log('API Result:', result); - }) - .catch(error => { - console.error('API Error:', error); - }); + console.log('Parsed Place Command:', data); + colorCell(`${data.x}${data.y}`, data.color); } }) .catch(error => { @@ -66,6 +58,7 @@ ws.onmessage = (event) => { } }; + ws.onerror = (error) => { console.error('WebSocket Error:', error); }; diff --git a/resources/js/grid.js b/resources/js/grid.js new file mode 100755 index 0000000..2958ba3 --- /dev/null +++ b/resources/js/grid.js @@ -0,0 +1,93 @@ +function colorCell(cellName, colorName) { + console.log(`colorCell called with: ${cellName}, ${colorName}`); + + // Map of valid named colors to their hex values + const validColors = { + "white": "#FFFFFF", + "lightGray": "#E4E4E4", + "mediumGray": "#888888", + "darkGray": "#222222", + "pink": "#FFA7D1", + "red": "#E50000", + "orange": "#E59500", + "brown": "#A06A42", + "yellow": "#E5D900", + "lightGreen": "#94E044", + "green": "#02BE01", + "cyan": "#00D3DD", + "blue": "#0083C7", + "darkBlue": "#0000EA", + "purple": "#CF6EE4", + "darkPurple": "#820080", + "black": "#000000" + }; + + // Check if the provided color name is valid + if (!validColors[colorName]) { + console.warn(`Invalid color name: ${colorName}. Please use a valid named color.`); + return; + } + + // Find the cell using its class name + const cell = document.querySelector(`.cell.${cellName}`); + + // If the cell exists, change its background color to the corresponding hex value + if (cell) { + cell.style.backgroundColor = validColors[colorName]; + } else { + console.warn(`Cell ${cellName} not found.`); + } +} + +// Attach colorCell function to the window object to make it globally accessible +window.colorCell = colorCell; + +document.addEventListener('DOMContentLoaded', function() { + const gridSize = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--grid-size').trim()); + const grid = document.querySelector('.grid-container'); + + function getLabel(index) { + let label = ''; + while (index >= 0) { + label = String.fromCharCode((index % 26) + 65) + label; + index = Math.floor(index / 26) - 1; + } + return label; + } + + // Create top labels + grid.innerHTML += '
'; // Empty top-left corner + for (let i = 0; i < gridSize; i++) { + grid.innerHTML += `