init first version

Erster Upload
This commit is contained in:
louis.drilltzsch@gmail.com 2026-07-02 20:58:21 +02:00
commit 8d3ca5b07d
7 changed files with 309 additions and 0 deletions

4
.editorconfig Normal file
View File

@ -0,0 +1,4 @@
root = true
[*]
charset = utf-8

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

86
project.godot Normal file
View File

@ -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"

86
scene/TicTacToe.tscn Normal file
View File

@ -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

127
scripts/control.gd Normal file
View File

@ -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)

1
scripts/control.gd.uid Normal file
View File

@ -0,0 +1 @@
uid://6afx0iw42x0b