commit 8d3ca5b07deaca3dbd2a084cae40ba1c1138efd3 Author: louis.drilltzsch@gmail.com Date: Thu Jul 2 20:58:21 2026 +0200 init first version Erster Upload diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f28239b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +root = true + +[*] +charset = utf-8 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0af181c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Godot 4+ specific ignores +.godot/ +/android/ diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..2a7f75d --- /dev/null +++ b/project.godot @@ -0,0 +1,86 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Tic-Tac-Toe" +run/main_scene="uid://dqveewprna0jt" +config/features=PackedStringArray("4.7", "GL Compatibility") + +[display] + +window/stretch/mode="canvas_items" +window/stretch/aspect="expand" + +[file_customization] + +folder_colors={ +"res://res/": "red", +"res://scene/": "purple", +"res://scripts/": "green" +} + +[input] + +pad_9={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194447,"key_label":0,"unicode":57,"location":0,"echo":false,"script":null) +] +} +pad_8={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194446,"key_label":0,"unicode":56,"location":0,"echo":false,"script":null) +] +} +pad_7={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194445,"key_label":0,"unicode":55,"location":0,"echo":false,"script":null) +] +} +pad_6={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194444,"key_label":0,"unicode":54,"location":0,"echo":false,"script":null) +] +} +pad_5={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194443,"key_label":0,"unicode":53,"location":0,"echo":false,"script":null) +] +} +pad_4={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194442,"key_label":0,"unicode":52,"location":0,"echo":false,"script":null) +] +} +pad_3={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194441,"key_label":0,"unicode":51,"location":0,"echo":false,"script":null) +] +} +pad_2={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194440,"key_label":0,"unicode":50,"location":0,"echo":false,"script":null) +] +} +pad_1={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194439,"key_label":0,"unicode":49,"location":0,"echo":false,"script":null) +] +} + +[physics] + +3d/physics_engine="Jolt Physics" + +[rendering] + +rendering_device/driver.windows="d3d12" +renderer/rendering_method="gl_compatibility" +renderer/rendering_method.mobile="gl_compatibility" diff --git a/scene/TicTacToe.tscn b/scene/TicTacToe.tscn new file mode 100644 index 0000000..8ffa341 --- /dev/null +++ b/scene/TicTacToe.tscn @@ -0,0 +1,86 @@ +[gd_scene format=3 uid="uid://dqveewprna0jt"] + +[ext_resource type="Script" uid="uid://6afx0iw42x0b" path="res://scripts/control.gd" id="1_d4uqn"] + +[node name="TicTacToe" type="Node2D" unique_id=1284704153] + +[node name="Control" type="Control" parent="." unique_id=643084856] +layout_mode = 3 +anchors_preset = 0 +offset_right = 40.0 +offset_bottom = 40.0 +script = ExtResource("1_d4uqn") + +[node name="Board" type="GridContainer" parent="Control" unique_id=106189434] +custom_minimum_size = Vector2(500, 500) +layout_mode = 0 +offset_right = 500.0 +offset_bottom = 500.0 +columns = 3 + +[node name="Button7_ObenLinks" type="Button" parent="Control/Board" unique_id=1766568008] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Button8_ObenMitte" type="Button" parent="Control/Board" unique_id=1708324161] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Button9_ObenRechts" type="Button" parent="Control/Board" unique_id=88820554] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Button4_MitteLinks" type="Button" parent="Control/Board" unique_id=1367556442] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Button5_MitteMitte" type="Button" parent="Control/Board" unique_id=863510856] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Button6_MitteRechts" type="Button" parent="Control/Board" unique_id=1730224545] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Button1_UntenLinks" type="Button" parent="Control/Board" unique_id=417363038] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Button2_UntenMitte" type="Button" parent="Control/Board" unique_id=2081092678] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Button3_UntenRechts" type="Button" parent="Control/Board" unique_id=92172665] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="StatusLabel" type="Label" parent="Control" unique_id=401811983] +layout_mode = 1 +anchors_preset = 5 +anchor_left = 0.5 +anchor_right = 0.5 +offset_left = -20.0 +offset_top = -70.0 +offset_right = 480.0 +offset_bottom = -21.0 +grow_horizontal = 2 +theme_override_font_sizes/font_size = 35 +text = "Status" +horizontal_alignment = 1 + +[node name="Camera2D" type="Camera2D" parent="Control" unique_id=501207243] +position = Vector2(250, 230) + +[node name="WinLine" type="Line2D" parent="Control" unique_id=627323966] +width = 8.0 +begin_cap_mode = 2 +end_cap_mode = 2 diff --git a/scripts/control.gd b/scripts/control.gd new file mode 100644 index 0000000..696911c --- /dev/null +++ b/scripts/control.gd @@ -0,0 +1,127 @@ +extends Control + +# Spieler-Definitionen +const PLAYER_X = "X" +const PLAYER_O = "O" +const EMPTY = "" + +var current_player = PLAYER_X +var game_over = false + +# Referenzen zu den Nodes +@onready var board_grid = $Board +@onready var status_label = $StatusLabel +@onready var win_line = $WinLine # Die neue Line2D Node + +# Mapping von Numpad-Aktionen zu den Array-Indizes (0 bis 8) +var numpad_mapping = { + "pad_7": 0, "pad_8": 1, "pad_9": 2, + "pad_4": 3, "pad_5": 4, "pad_6": 5, + "pad_1": 6, "pad_2": 7, "pad_3": 8 +} + +var buttons = [] + +# Gewinnkombinationen global definieren, da wir sie auch für die Linie brauchen +var win_conditions = [ + [0, 1, 2], [3, 4, 5], [6, 7, 8], # Horizontal (0=oben, 1=mitte, 2=unten) + [0, 3, 6], [1, 4, 7], [2, 5, 8], # Vertikal (3=links, 4=mitte, 5=rechts) + [0, 4, 8], [2, 4, 6] # Diagonal (6=Hauptdiagonale, 7=Nebendiagonale) +] + +func _ready(): + board_grid.custom_minimum_size = Vector2(500, 500) + buttons = board_grid.get_children() + + # Gewinnlinie am Anfang unsichtbar machen + win_line.clear_points() + + for i in range(buttons.size()): + buttons[i].text = EMPTY + buttons[i].size_flags_horizontal = Control.SIZE_EXPAND_FILL + buttons[i].size_flags_vertical = Control.SIZE_EXPAND_FILL + buttons[i].add_theme_font_size_override("font_size", 64) + buttons[i].pressed.connect(_on_button_pressed.bind(i)) + + update_status_label() + +func _input(event): + if game_over: + return + + for action in numpad_mapping.keys(): + if event.is_action_pressed(action): + var index = numpad_mapping[action] + if buttons[index].text == EMPTY: + make_move(index) + +func _on_button_pressed(index): + if game_over or buttons[index].text != EMPTY: + return + make_move(index) + +func make_move(index): + buttons[index].text = current_player + + var winning_combo = check_win() + if winning_combo != null: + status_label.text = "Spieler " + current_player + " hat gewonnen!" + game_over = true + animate_win_line(winning_combo) # Hier starten wir die Animation + elif check_draw(): + status_label.text = "Unentschieden!" + game_over = true + else: + current_player = PLAYER_O if current_player == PLAYER_X else PLAYER_X + update_status_label() + +func update_status_label(): + status_label.text = "Spieler " + current_player + " ist am Zug." + +# Gibt nun die Gewinn-Kombination zurück, wenn jemand gewinnt +func check_win(): + for cond in win_conditions: + if buttons[cond[0]].text == current_player and \ + buttons[cond[1]].text == current_player and \ + buttons[cond[2]].text == current_player: + return cond # Gibt das Array der 3 Buttons zurück (z.B. [0, 1, 2]) + return null + +func check_draw() -> bool: + for button in buttons: + if button.text == EMPTY: + return false + return true + +# Berechnet die Mitte eines Buttons basierend auf seiner globalen Position +func get_button_center(button: Button) -> Vector2: + return button.global_position + (button.size / 2) + +# Animiert den Strich von Button 1 zu Button 3 und geht über die Ränder hinaus +func animate_win_line(combo: Array): + var start_pos = get_button_center(buttons[combo[0]]) + var end_pos = get_button_center(buttons[combo[2]]) + + # 1. Richtung zwischen den beiden Punkten berechnen (Normalisierter Vektor) + var direction = (end_pos - start_pos).normalized() + + # 2. Festlegen, wie viele Pixel der Strich links und rechts überstehen soll + var padding = 40.0 + + # 3. Start- und Endpunkt nach außen verschieben + var extended_start = start_pos - (direction * padding) + var extended_end = end_pos + (direction * padding) + + # Initialisiere die Punkte der Linie am neuen, erweiterten Startpunkt + win_line.clear_points() + win_line.add_point(extended_start) + win_line.add_point(extended_start) # Der zweite Punkt startet hier und wandert + + # Erstelle den Tween für die flüssige Animation + var tween = create_tween() + tween.tween_method( + func(pos): win_line.set_point_position(1, pos), + extended_start, + extended_end, # Wandert bis zum erweiterten Endpunkt + 0.4 + ).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT) diff --git a/scripts/control.gd.uid b/scripts/control.gd.uid new file mode 100644 index 0000000..08d1211 --- /dev/null +++ b/scripts/control.gd.uid @@ -0,0 +1 @@ +uid://6afx0iw42x0b