477 lines
16 KiB
GDScript
477 lines
16 KiB
GDScript
extends ColorRect
|
|
|
|
export var rows: int = 44
|
|
export var cols: int = 63
|
|
export var h_margin: int = 2
|
|
export var v_margin: int = 2
|
|
export var grid_color: Color = Color.black
|
|
var READY := false
|
|
|
|
const ByteArray2D = Common.ByteArray2D
|
|
const IntArray2D = Common.IntArray2D
|
|
|
|
onready var cell_numbers: ByteArray2D
|
|
const NUMBER_NONE := 255
|
|
onready var cell_colors: ByteArray2D
|
|
onready var corner_marks: IntArray2D
|
|
|
|
var cell_image := Image.new()
|
|
var cell_texture := ImageTexture.new()
|
|
var grid_image := Image.new()
|
|
var grid_texture := ImageTexture.new()
|
|
|
|
enum UndoActions {
|
|
SET_L_RIGHT, SET_L_DOWN, SET_X_RIGHT, SET_X_DOWN,
|
|
CLEAR_L_RIGHT, CLEAR_L_DOWN, CLEAR_X_RIGHT, CLEAR_X_DOWN,
|
|
SET_COLOR
|
|
}
|
|
enum CornerMark {
|
|
L_RIGHT=1, X_RIGHT=2, L_DOWN=4, X_DOWN=8,
|
|
DARC_1=16, DARC_2=32, DARC_3=64, DARC_4=128,
|
|
SARC_1=256, SARC_2=512, SARC_3=1024, SARC_4=2048
|
|
}
|
|
func _ready():
|
|
# load_puzzle('res://slither_blank.txt')
|
|
load_puzzle('res://slither-202005192059.txt')
|
|
|
|
|
|
func draw_string_centered(font, position, string, color := Color.white):
|
|
draw_string(font, Vector2(position.x - font.get_string_size(string).x/2.0, position.y + font.get_ascent()), string, color)
|
|
|
|
const font := preload('res://dynamicfont.tres')
|
|
const COLORS = [null, Color.lightgreen, Color.lightyellow, Color.lightblue, Color.purple]
|
|
const N_COLORS = 5
|
|
|
|
var grid_0 := Vector2(0.0, 0.0)
|
|
var grid_space := Vector2(1.0, 1.0)
|
|
var grid_rect := Rect2(0.0, 0.0, 1.0, 1.0)
|
|
var h0 := 0.0
|
|
var v0 := 0.0
|
|
var h_space := 1.0
|
|
var v_space := 1.0
|
|
func update_grid_spacing() -> void:
|
|
h_space = rect_size.x / (h_margin * 2 + cols)
|
|
v_space = rect_size.y / (v_margin * 2 + rows)
|
|
if h_space > v_space:
|
|
h_space = v_space
|
|
else:
|
|
v_space = h_space
|
|
h0 = h_space * h_margin
|
|
v0 = v_space * v_margin
|
|
grid_0 = Vector2(h0, v0)
|
|
grid_space = Vector2(h_space, v_space)
|
|
grid_rect = Rect2(h0, v0, h_space*cols, v_space*rows)
|
|
|
|
func grid_corner(row, col) -> Vector2:
|
|
return (Vector2(col, row) * grid_space) + grid_0
|
|
|
|
func to_grid_space(position: Vector2) -> Vector2:
|
|
return (position - grid_0)/grid_space
|
|
|
|
func regenerate_cell_texture() -> void:
|
|
# cell_image.create_from_data(rows, cols, false, Image.FORMAT_R8, cell_colors._array)
|
|
cell_image.create(cols, rows, false, Image.FORMAT_RGBA8)
|
|
cell_image.lock()
|
|
for row in rows:
|
|
for col in cols:
|
|
var color = COLORS[cell_colors.get_cell(row, col)]
|
|
if color != null:
|
|
cell_image.set_pixel(col, row, color)
|
|
cell_image.unlock()
|
|
cell_texture.create_from_image(cell_image, 0)
|
|
|
|
func regenerate_grid_texture() -> void:
|
|
# grid_image.create_from_data(rows, cols, false, Image.FORMAT_R8, cell_corners._array)
|
|
grid_image.create(cols+1, rows+1, false, Image.FORMAT_RGBA8)
|
|
grid_image.lock()
|
|
for row in rows+1:
|
|
for col in cols+1:
|
|
var flags = corner_marks.get_cell(row, col)
|
|
grid_image.set_pixel(col, row, Color(float(flags&3)/2.0, float((flags>>2)&3)/2.0, 0.0))
|
|
grid_image.unlock()
|
|
grid_texture.create_from_image(grid_image, 0)
|
|
$TextureRect.set_texture(grid_texture)
|
|
|
|
func _draw() -> void:
|
|
if not READY:
|
|
return
|
|
update_grid_spacing()
|
|
font.set_size(int(ceil(v_space-5)))
|
|
|
|
# Colors
|
|
# for row in rows:
|
|
# for col in cols:
|
|
# var color = COLORS[cell_colors.get_cell(row, col)]
|
|
# if color != null:
|
|
# draw_rect(Rect2(grid_corner(row, col), Vector2(h_space, v_space)), color, true)
|
|
regenerate_cell_texture()
|
|
draw_texture_rect(cell_texture, Rect2(h0, v0, h_space*cols, v_space*rows), false)
|
|
|
|
# Lines
|
|
# for row in rows:
|
|
# for col in cols:
|
|
# if corner_marks.get_flag(row, col, CornerMark.L_RIGHT):
|
|
# draw_line(grid_corner(row, col), grid_corner(row, col+1), Color.blue)
|
|
# elif corner_marks.get_flag(row, col, CornerMark.X_RIGHT):
|
|
# draw_line(grid_corner(row, col+0.4), grid_corner(row, col+0.6), Color.red)
|
|
# else:
|
|
# draw_line(grid_corner(row, col), grid_corner(row, col+1), Color.white)
|
|
#
|
|
# if corner_marks.get_flag(row, col, CornerMark.L_DOWN):
|
|
# draw_line(grid_corner(row, col), grid_corner(row+1, col), Color.blue)
|
|
# elif corner_marks.get_flag(row, col, CornerMark.X_DOWN):
|
|
# draw_line(grid_corner(row+0.4, col), grid_corner(row+0.6, col), Color.red)
|
|
# else:
|
|
# draw_line(grid_corner(row, col), grid_corner(row+1, col), Color.white)
|
|
# for col in cols:
|
|
# if corner_marks.get_flag(rows, col, CornerMark.L_RIGHT):
|
|
# draw_line(grid_corner(rows, col), grid_corner(rows, col+1), Color.blue)
|
|
# elif corner_marks.get_flag(rows, col, CornerMark.X_RIGHT):
|
|
# draw_line(grid_corner(rows, col+0.4), grid_corner(rows, col+0.6), Color.red)
|
|
# else:
|
|
# draw_line(grid_corner(rows, col), grid_corner(rows, col+1), Color.white)
|
|
# for row in rows:
|
|
# if corner_marks.get_flag(row, cols, CornerMark.L_DOWN):
|
|
# draw_line(grid_corner(row, cols), grid_corner(row+1, cols), Color.blue)
|
|
# elif corner_marks.get_flag(row, cols, CornerMark.X_DOWN):
|
|
# draw_line(grid_corner(row+0.4, cols), grid_corner(row+0.6, cols), Color.red)
|
|
# else:
|
|
# draw_line(grid_corner(row, cols), grid_corner(row+1, cols), Color.white)
|
|
regenerate_grid_texture()
|
|
$TextureRect.margin_left = h0
|
|
$TextureRect.margin_top = v0
|
|
$TextureRect.rect_size = Vector2(h_space*cols, v_space*rows)
|
|
|
|
# Arc pencil marks
|
|
|
|
# Numbers
|
|
for row in rows:
|
|
for col in cols:
|
|
var num = cell_numbers.get_cell(row, col)
|
|
if num < 4:
|
|
draw_string_centered(font, grid_corner(row, col+0.5), str(num), Color.black)
|
|
|
|
# COORDS
|
|
for row in rows:
|
|
draw_string_centered(font, Vector2(h0/2, v0+row*v_space), '%02d'%row, Color.black)
|
|
for col in cols:
|
|
draw_string_centered(font, Vector2(h0+(col+0.5)*h_space, v0/4), Common.num2alpha(col), Color.black)
|
|
|
|
# Undo Stack
|
|
var u = len(undo_stack)
|
|
for i in u:
|
|
draw_string(font, grid_corner(i, cols+1), str(undo_stack[u-i-1]), Color.black if i>=undo_preview_pos else Color.gray)
|
|
# print(action)
|
|
|
|
func _process(delta: float) -> void:
|
|
# update()
|
|
pass
|
|
|
|
const num_dict := {'.': NUMBER_NONE, '0': 0, '1': 1, '2': 2, '3': 3}
|
|
const colornum_dict := {'0': 0, '1': 1, '2': 2}
|
|
func load_puzzle(filename: String):
|
|
var file := File.new()
|
|
var error = file.open(filename, File.READ)
|
|
if file.get_line() != 'pzprv3.1':
|
|
return
|
|
if file.get_line() != 'slither':
|
|
return
|
|
rows = int(file.get_line())
|
|
cols = int(file.get_line())
|
|
cell_numbers = ByteArray2D.new(rows, cols)
|
|
cell_colors = ByteArray2D.new(rows, cols)
|
|
corner_marks = IntArray2D.new(rows+1, cols+1, 0)
|
|
|
|
var row = 0
|
|
var stage = 0
|
|
var until = [rows, rows, rows, rows+1, 0]
|
|
while not file.eof_reached():
|
|
if row >= until[stage]:
|
|
stage += 1
|
|
row = 0
|
|
match stage:
|
|
0: # Puzzle definition numbers
|
|
var line = file.get_csv_line(' ')
|
|
var col = 0
|
|
for numstr in line:
|
|
if col < cols:
|
|
cell_numbers.set_cell(row, col, num_dict.get(numstr, NUMBER_NONE))
|
|
col += 1
|
|
1: # cell colors
|
|
var line = file.get_csv_line(' ')
|
|
var col = 0
|
|
for numstr in line:
|
|
if col < cols:
|
|
cell_colors.set_cell(row, col, colornum_dict.get(numstr, 0))
|
|
col += 1
|
|
2: # vert lines
|
|
var line = file.get_csv_line(' ')
|
|
var col = 0
|
|
for numstr in line:
|
|
if col < cols+1:
|
|
match int(numstr):
|
|
1:
|
|
corner_marks.set_flag(row, col, CornerMark.L_DOWN)
|
|
-1:
|
|
corner_marks.set_flag(row, col, CornerMark.X_DOWN)
|
|
col += 1
|
|
3: # horiz lines
|
|
var line = file.get_csv_line(' ')
|
|
var col = 0
|
|
for numstr in line:
|
|
if col < cols+1:
|
|
match int(numstr):
|
|
1:
|
|
corner_marks.set_flag(row, col, CornerMark.L_RIGHT)
|
|
-1:
|
|
corner_marks.set_flag(row, col, CornerMark.X_RIGHT)
|
|
col += 1
|
|
4:
|
|
break
|
|
row += 1
|
|
file.close()
|
|
READY = true
|
|
|
|
enum DragAction {TEST_LINE, DRAW_LINE, REMOVE_LINE, DRAW_X, REMOVE_X, DRAW_COLOR}
|
|
var drag_action = 0
|
|
var drag_color := 0
|
|
func _input(event: InputEvent) -> void:
|
|
if event is InputEventMouse:
|
|
if !grid_rect.has_point(event.position):
|
|
return
|
|
var gridpos = to_grid_space(event.position)
|
|
var dx = fmod(gridpos.x, 1.0)
|
|
var dy = fmod(gridpos.y, 1.0)
|
|
var adx = 0.5 - abs(dx-0.5)
|
|
var ady = 0.5 - abs(dy-0.5)
|
|
var threshold = 0.2
|
|
var sel_col
|
|
var sel_row
|
|
|
|
if event is InputEventMouseButton and event.pressed:
|
|
if adx < threshold:
|
|
if ady < threshold:
|
|
drag_action = DragAction.TEST_LINE
|
|
else:
|
|
sel_row = int(gridpos.y)
|
|
sel_col = int(round(gridpos.x))
|
|
if corner_marks.get_flag(sel_row, sel_col, CornerMark.X_DOWN):
|
|
drag_action = DragAction.REMOVE_X
|
|
clear_x_down(sel_row, sel_col)
|
|
else:
|
|
drag_action = DragAction.DRAW_X
|
|
set_x_down(sel_row, sel_col)
|
|
else:
|
|
if ady < threshold:
|
|
sel_row = int(round(gridpos.y))
|
|
sel_col = int(gridpos.x)
|
|
if corner_marks.get_flag(sel_row, sel_col, CornerMark.X_RIGHT):
|
|
drag_action = DragAction.REMOVE_X
|
|
clear_x_right(sel_row, sel_col)
|
|
else:
|
|
drag_action = DragAction.DRAW_X
|
|
set_x_right(sel_row, sel_col)
|
|
else:
|
|
sel_row = int(gridpos.y)
|
|
sel_col = int(gridpos.x)
|
|
drag_action = DragAction.DRAW_COLOR
|
|
drag_color = posmod(cell_colors.get_cell(sel_row, sel_col) + (1 if event.button_index==BUTTON_LEFT else -1), N_COLORS)
|
|
set_cell_color(sel_row, sel_col, drag_color)
|
|
|
|
elif event is InputEventMouseMotion and event.button_mask:
|
|
if drag_action == DragAction.TEST_LINE:
|
|
if adx < threshold and ady > threshold:
|
|
drag_action = DragAction.REMOVE_LINE if corner_marks.get_flag(int(gridpos.y), int(round(gridpos.x)), CornerMark.L_DOWN) else DragAction.DRAW_LINE
|
|
elif adx > threshold and ady < threshold:
|
|
drag_action = DragAction.REMOVE_LINE if corner_marks.get_flag(int(round(gridpos.y)), int(gridpos.x), CornerMark.L_RIGHT) else DragAction.DRAW_LINE
|
|
match drag_action:
|
|
DragAction.DRAW_LINE:
|
|
if adx < threshold and ady > threshold:
|
|
set_line_down(int(gridpos.y), int(round(gridpos.x)))
|
|
elif adx > threshold and ady < threshold:
|
|
set_line_right(int(round(gridpos.y)), int(gridpos.x))
|
|
DragAction.REMOVE_LINE:
|
|
if adx < threshold and ady > threshold:
|
|
clear_line_down(int(gridpos.y), int(round(gridpos.x)))
|
|
elif adx > threshold and ady < threshold:
|
|
clear_line_right(int(round(gridpos.y)), int(gridpos.x))
|
|
DragAction.DRAW_X:
|
|
if adx < threshold and ady > threshold:
|
|
set_x_down(int(gridpos.y), int(round(gridpos.x)))
|
|
elif adx > threshold and ady < threshold:
|
|
set_x_right(int(round(gridpos.y)), int(gridpos.x))
|
|
DragAction.REMOVE_X:
|
|
if adx < threshold and ady > threshold:
|
|
clear_x_down(int(gridpos.y), int(round(gridpos.x)))
|
|
elif adx > threshold and ady < threshold:
|
|
clear_x_right(int(round(gridpos.y)), int(gridpos.x))
|
|
DragAction.DRAW_COLOR:
|
|
sel_row = int(gridpos.y)
|
|
sel_col = int(gridpos.x)
|
|
set_cell_color(sel_row, sel_col, drag_color)
|
|
elif event is InputEventKey and event.pressed:
|
|
match event.scancode:
|
|
KEY_Z:
|
|
undo()
|
|
KEY_DOWN:
|
|
self.undo_preview_pos += 1
|
|
KEY_UP:
|
|
self.undo_preview_pos -= 1
|
|
|
|
var undo_stack = []
|
|
var undo_preview_pos := 0 setget set_undo_preview_pos
|
|
|
|
func apply_undo_preview():
|
|
undo_stack.resize(len(undo_stack)-undo_preview_pos)
|
|
undo_preview_pos = 0
|
|
update()
|
|
|
|
func do_action(action):
|
|
if undo_preview_pos > 0:
|
|
apply_undo_preview()
|
|
action.activate()
|
|
undo_stack.append(action)
|
|
update()
|
|
|
|
func undo():
|
|
if !undo_stack.empty():
|
|
if undo_preview_pos > 0:
|
|
apply_undo_preview()
|
|
else:
|
|
var action = undo_stack.pop_back()
|
|
action.undo()
|
|
update()
|
|
|
|
func set_undo_preview_pos(value):
|
|
if value < 0: # Negative is past the end of the stack
|
|
value = 0
|
|
elif value > len(undo_stack):
|
|
value = len(undo_stack)
|
|
|
|
var old_pos = undo_preview_pos
|
|
undo_preview_pos = value
|
|
var delta = value - old_pos
|
|
if delta > 0: # We are undoing
|
|
for i in delta:
|
|
undo_stack[-old_pos-1-i].undo()
|
|
elif delta < 0: # We are redoing
|
|
for i in -delta:
|
|
undo_stack[-old_pos+i].activate()
|
|
update()
|
|
|
|
class UndoAction:
|
|
var action_type
|
|
var cell_row: int
|
|
var cell_col: int
|
|
var activated: bool
|
|
func _init(action, row, col) -> void:
|
|
action_type = action
|
|
cell_row = row
|
|
cell_col = col
|
|
activated = false
|
|
|
|
class UndoActionCorner extends UndoAction:
|
|
var corner_marks
|
|
var corner_mark
|
|
var set: bool # False = clear
|
|
func _init(row, col, corner_mark, set, corner_marks).(corner_mark, row, col) -> void:
|
|
self.corner_marks = corner_marks
|
|
self.corner_mark = corner_mark
|
|
self.set = set
|
|
|
|
func _to_string() -> String:
|
|
var prefix = '+' if set else '-'
|
|
match corner_mark:
|
|
CornerMark.L_RIGHT:
|
|
return prefix+'L %s%d:%d'%[Common.num2alpha(cell_col), cell_row-1, cell_row]
|
|
CornerMark.X_RIGHT:
|
|
return prefix+'X %s%d:%d'%[Common.num2alpha(cell_col), cell_row-1, cell_row]
|
|
CornerMark.L_DOWN:
|
|
return prefix+'L %s%d:%s'%[Common.num2alpha(cell_col-1), cell_row, Common.num2alpha(cell_col)]
|
|
CornerMark.X_DOWN:
|
|
return prefix+'X %s%d:%s'%[Common.num2alpha(cell_col-1), cell_row, Common.num2alpha(cell_col)]
|
|
_:
|
|
return 'unk %s%d %s%d'%[prefix, corner_mark, Common.num2alpha(cell_col), cell_row]
|
|
|
|
func activate() -> void:
|
|
activated = true
|
|
if set:
|
|
corner_marks.set_flag(cell_row, cell_col, corner_mark)
|
|
else:
|
|
corner_marks.clear_flag(cell_row, cell_col, corner_mark)
|
|
|
|
func undo() -> void:
|
|
activated = false
|
|
if set:
|
|
corner_marks.clear_flag(cell_row, cell_col, corner_mark)
|
|
else:
|
|
corner_marks.set_flag(cell_row, cell_col, corner_mark)
|
|
|
|
class UndoActionCell extends UndoAction:
|
|
var set_state
|
|
var old_state
|
|
var cell_colors
|
|
func _init(action, row, col, set_state, cell_colors).(action, row, col) -> void:
|
|
self.set_state = set_state
|
|
self.old_state = set_state
|
|
self.cell_colors = cell_colors
|
|
|
|
func _to_string() -> String:
|
|
match action_type:
|
|
UndoActions.SET_COLOR:
|
|
return 'C%d<-%d %s%d'%[set_state, old_state, Common.num2alpha(cell_col), cell_row]
|
|
_:
|
|
return 'unk %d %s%d'%[action_type, Common.num2alpha(cell_col), cell_row]
|
|
|
|
func activate() -> void:
|
|
assert(not activated)
|
|
activated = true
|
|
match action_type:
|
|
UndoActions.SET_COLOR:
|
|
old_state = cell_colors.get_cell(cell_row, cell_col)
|
|
cell_colors.set_cell(cell_row, cell_col, set_state)
|
|
|
|
func undo() -> void:
|
|
assert(activated)
|
|
activated = false
|
|
match action_type:
|
|
UndoActions.SET_COLOR:
|
|
cell_colors.set_cell(cell_row, cell_col, old_state)
|
|
|
|
func set_cell_color(row, col, new_color):
|
|
if new_color != cell_colors.get_cell(row, col):
|
|
do_action(UndoActionCell.new(UndoActions.SET_COLOR, row, col, new_color, cell_colors))
|
|
|
|
# TODO: Refactor all this nonsense a bit more
|
|
func set_line_right(row, col):
|
|
if !corner_marks.get_flag(row, col, CornerMark.L_RIGHT) and !corner_marks.get_flag(row, col, CornerMark.X_RIGHT):
|
|
do_action(UndoActionCorner.new(row, col, CornerMark.L_RIGHT, true, corner_marks))
|
|
|
|
func set_line_down(row, col):
|
|
if !corner_marks.get_flag(row, col, CornerMark.L_DOWN) and !corner_marks.get_flag(row, col, CornerMark.X_DOWN):
|
|
do_action(UndoActionCorner.new(row, col, CornerMark.L_DOWN, true, corner_marks))
|
|
|
|
func clear_line_right(row, col):
|
|
if corner_marks.get_flag(row, col, CornerMark.L_RIGHT):
|
|
do_action(UndoActionCorner.new(row, col, CornerMark.L_RIGHT, false, corner_marks))
|
|
|
|
func clear_line_down(row, col):
|
|
if corner_marks.get_flag(row, col, CornerMark.L_DOWN):
|
|
do_action(UndoActionCorner.new(row, col, CornerMark.L_DOWN, false, corner_marks))
|
|
|
|
func set_x_right(row, col):
|
|
if !corner_marks.get_flag(row, col, CornerMark.L_RIGHT) and !corner_marks.get_flag(row, col, CornerMark.X_RIGHT):
|
|
do_action(UndoActionCorner.new(row, col, CornerMark.X_RIGHT, true, corner_marks))
|
|
|
|
func set_x_down(row, col):
|
|
if !corner_marks.get_flag(row, col, CornerMark.L_DOWN) and !corner_marks.get_flag(row, col, CornerMark.X_DOWN):
|
|
do_action(UndoActionCorner.new(row, col, CornerMark.X_DOWN, true, corner_marks))
|
|
|
|
func clear_x_right(row, col):
|
|
if corner_marks.get_flag(row, col, CornerMark.X_RIGHT):
|
|
do_action(UndoActionCorner.new(row, col, CornerMark.X_RIGHT, false, corner_marks))
|
|
|
|
func clear_x_down(row, col):
|
|
if corner_marks.get_flag(row, col, CornerMark.X_DOWN):
|
|
do_action(UndoActionCorner.new(row, col, CornerMark.X_DOWN, false, corner_marks))
|